Ответ 1
В вашем вопросе есть несколько частей. Во-первых, если вы хотите использовать некоторый синтаксис не-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]]]
Это соответствует этому mma-коду:
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, [email protected]]},
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.