Как динамически генерировать математический код?

Я хочу сделать мини-язык программирования в mma. Из текстового файла в модуль в пакете. В идеале я должен иметь возможность генерировать пакет и модули из Mathematica с помощью функций в другом пакете.

Вопрос: Это возможно? Я ищу ссылку или пример, чтобы это началось.

EDIT: Например:

Представьте себе банк памяти с n целыми типизированными регистрами.

Инструкции:

1 Z (n)

2 C (m, n)

3 J (m, n, q)

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.

Наконец, я хочу начать экспериментировать с рекурсией... Цель этого "мини-проекта".

Ответ 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.