如何动态生成Mathematica代码?

10
我希望在mma中创建一个迷你编程语言,从文本文件到打包中的模块。理想情况下,我应该能够通过其他包中的函数在Mathematica中生成打包和模块。
问题: 这是可能的吗?我正在寻找一个参考或一个示例来开始这个项目。
编辑: 例如:
想象一个具有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) 在寄存器n中存储寄存器m的值。 J(m,n,q) 如果寄存器m的值等于寄存器n的值,则跳转到地址为q的行。 S(n) 将寄存器n中的值加1。
然后,给定两个工作程序P和Q,我想生成串联的程序P+Q。
然后,给定两个工作程序P和Q,我想生成将Q代替P的程序。
最后,我想开始尝试递归...这个“小项目”的目的。

1
不太清楚您想要实现什么。请澄清一下。一旦您为符号创建了一些定义,就可以使用“保存”将它们保存到文件(如果您喜欢,也可以称之为“包”)。或者您的问题是如何通过编程方式创建这些定义?请给出具体的例子。 - Szabolcs
2个回答

13
你的问题有几个部分。首先,如果你想为你的语言使用一些非数学语法,你需要从你的语言到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, 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

我们可以导入来测试:

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如何呈现它,从而让它看起来更漂亮,而不必完全创建自己的解析器。


2
Leonid的回答 /.{AST->"抽象语法树"} - Dr. belisarius
2
我甚至都不想回答这个问题,因为我知道你会发布更好的内容。 - Mr.Wizard
1
@Mr.Wizard 谢谢。这是我真正感兴趣的话题。 - Leonid Shifrin
3
@Leonid:我无法忘记这个:Hold[ package["myPackage`", inContext["`Private`"... ಠ_ಠ ... 如果这是有意为之的,你真是个天才! - user616736
@Leonid - 你写的内容仍然在线上,可能对我或其他人在未来有用。- 我不是专业的Mathematica用户。我在我的数学学生/业余数学家的角色中使用它。- 我认为我将在未来几个月内为一个研究项目编写相当多的mma代码。- 我将发布代码供审查。最有可能从我目前正在测试的树数据结构的代码开始。 - nilo de roock
显示剩余8条评论

4

虽然这和问题无关,但你可能会在设置CellEvaluationFunction中发现重要的工具性用途,详情请参考WReach在一篇帖子中的描述


这是我“工具包”中最喜欢的之一。我真的希望像这样的东西在文档中有所涵盖。(+1 为了传播信息) - telefunkenvf14

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接