Как динамически генерировать код Mathematica?
Я хочу сделать мини-язык программирования в ММА. От текстового файла к модулю (ам) в пакете. В идеале я должен иметь возможность генерировать пакет и модули из Mathematica с помощью функций из другого пакета.
Вопрос: возможно ли это? Я ищу ссылку или пример, чтобы начать это.
РЕДАКТИРОВАТЬ: Например:
Представьте себе банк памяти с n целочисленными регистрами.
Инструкции:
1 Z (n)
2 С (м, н)
3 Дж (м, н, кв)
4 S (n)
У каждой строки есть адрес. Первая строка 1, вторая 2 и т. Д. Z(n) сохраняет 0 в регистре n. C(m,n) сохраняет значение регистра m в регистре n. J(m,n,q), если значение регистра m равно значению регистра n, перейти к строке с адресом q. S(n) добавляет 1 к значению в регистре n.
Затем с учетом двух рабочих программ P и Q я хочу сгенерировать каскадную программу P+Q.
Затем с учетом двух рабочих программ P и Q я хочу сгенерировать замену Q после P.
Наконец, я хочу начать экспериментировать с рекурсией... целью этого "мини-проекта".
2 ответа
Ваш вопрос состоит из нескольких частей. Во-первых, если вы хотите использовать некоторый синтаксис не-mma для вашего языка, вам нужно сделать парсер из вашего языка в выражение mma (AST вашего кода). Я опущу этот вопрос (поскольку это отдельная тема) и предположу, что вы готовы использовать синтаксис mma или имеете средства для переноса вашей программы в какое-либо выражение mma.
Что касается генерации mma-кода, Mathematica очень хорошо подходит для него, поскольку она охватывает парадигму "код-данные-данные". Самая сложная часть здесь - это контроль оценки - мы хотим убедиться, что ни один из наших сгенерированных фрагментов кода не оценивается в процессе генерации кода. Для этого могут быть успешно использованы стандартные методы контроля оценки, но это, как правило, усложнит ситуацию. Я проиллюстрирую одну технику генерации кода mma, которая не самая лучшая / самая мощная, но самая простая.
Рассмотрим игрушечный язык, созданный этими определениями:
SetAttributes[testSet, HoldFirst];
SetAttributes[testIf, HoldRest];
SetAttributes[testVar, HoldAll];
SetAttributes[module, HoldAll];
SetAttributes[{package, inContext}, HoldRest];
testPlus[x_, y_] := Plus[x, y];
testTimes[x_, y_] := Times[x, y];
testDivide[x_, y_] := If[y == 0, Inf, Times[x, Power[y, -1]]];
testPower[x_, y_] := If[x == 0 && y < 0, Inf, Power[x, y]];
testSet[HoldPattern[testVar[x_]], expr_] := Set[x, expr];
testVar[x_] := If[ValueQ[x], x, Throw[$Failed, {"varundef", x}]];
testIf[cond_, expr_] := If[cond, expr];
testIf[cond_, expr_, else_] := If[cond, expr, else];
module[{vars__}, body_] := Module[{vars}, body];
package[name_, code_] := (BeginPackage[name]; code; EndPackage[]);
inContext[name_, code_] := (Begin[name]; code; End[]);
Вот небольшой фрагмент кода на этом новом языке (завернутый в Hold
):
cd =
Hold[module[{a}, testSet[testVar[a],
testPlus[testTimes[testTimes[testPlus[1, 2],
testPower[testPlus[3, 4], -1]], testPlus[5, 6]], -7]]; testVar[a]]]
Это соответствует этому ММА-коду:
Module[{a},a = (1 + 2)/(3 + 4)*(5 + 6) - 7; a]
Наш генератор кода основан на очень простой идее - мы будем неоднократно применять локальные правила к нашему хранимому коду. Локальные правила будут извлечены из определений наших функций, например так:
ClearAll[expansionRules];
expansionRules[heads : {__Symbol}] := Flatten[DownValues /@ heads]
Нам нужно предоставить список глав для нашего языка. Я сделаю это вручную, но это легко автоматизировать, создавая собственные операторы присваивания.
allHeadsToExpand[] := {testIf, testVar, testPlus, testTimes, testDivide,
testPower, testSet, testIf,module,package, inContext}
Теперь мы генерируем наш код:
In[195]:= expanded = cd//.expansionRules[allHeadsToExpand[]]
Out[195]=
Hold[Module[{a},
a = ((1 + 2) If[3 + 4 == 0 && -1 < 0, Inf, 1/(3 + 4)]) (5 + 6) - 7;
If[ValueQ[a], a, Throw[$Failed, {"varundef", a}]]]]
Чтобы выполнить его, вы можете просто использовать ReleaseHold
:
In[197]:= ReleaseHold[expanded]
Out[197]= -(16/7)
Преимущество нашей конструкции в том, что мы также можем выполнить нашу AST напрямую:
In[198]:= ReleaseHold[cd]
Out[198]= -(16/7)
Чтобы сохранить это в пакете, вы можете просто использовать Put
команда. Также легко расширить язык любым удобным для вас способом. Конечно, то, как выглядит код на этом языке, не очень красиво, поскольку по сути это AST, выраженный в виде выражения mma. Чтобы сделать его красивее, вам нужно ввести собственный синтаксис и написать парсер из него в mma AST, но это уже другая история.
РЕДАКТИРОВАТЬ
Что касается автоматизации генерации кода и сохранения сгенерированного кода в пакет: вот пара утилит для этого.
Clear[generateCode];
generateCode[code_Hold] :=
code //. expansionRules[allHeadsToExpand[]] //.
HoldPattern[
CompoundExpression[left___, CompoundExpression[middle___], right___]] :>
(left; middle; right);
Clear[formatCode];
formatCode[code_Hold] :=
StringReplace[Function[Null, ToString[Unevaluated[#], InputForm], HoldAll] @@
code, ";" :> ";\n"];
Clear[saveCode];
saveCode[file_, generatedCode_] :=
With[{result = BinaryWrite[file, formatCode@generatedCode]},
Close[file];
result];
Вот тот же пример, но помещенный в пакет:
cdp = Hold[
package["myPackage`",
inContext["`Private`",
module[{a},
testSet[testVar[a],
testPlus[testTimes[testTimes[testPlus[1, 2],
testPower[testPlus[3, 4], -1]], testPlus[5, 6]], -7]];
testVar[a]]]]]
Мы генерируем и сохраняем код следующим образом:
In[101]:= file = FileNameJoin[{"C:","Temp","myPackage.m"}]
Out[101]= C:\Temp\myPackage.m
In[106]:= saved =saveCode[file,generateCode[cdp]]
Out[106]= C:\Temp\myPackage.m
Мы можем Import
это проверить:
In[107]:= Import[file,"Text"]
Out[107]=
BeginPackage["myPackage`"];
Begin["`Private`"];
Module[{a}, a = ((1 + 2)*If[3 + 4 == 0 && -1 < 0, Inf, (3 + 4)^(-1)])*(5 + 6) - 7;
If[ValueQ[a], a, Throw[$Failed, {"varundef", a}]]];
End[];
EndPackage[]
РЕДАКТИРОВАТЬ 2
Относительно того, как будет выглядеть код на вашем языке, вы можете сделать его более красивым, не затрачивая все силы на создание собственного синтаксического анализатора, используя пакет Notation, чтобы изменить способ ввода кода и Format
/ FormatValues
управлять тем, как он отображается FrontEnd.
Это касается вопроса, но вы можете найти важную полезность в настройке CellEvaluationFunction
как описано в посте WReach.