TL; DR VERSION:是否有一个解析器生成器支持以下内容:当某些规则减少时(我假设LALR(1)解析器),然后执行还原,但解析器退出并替换输入使用此规则中的值并解析该代码的不同代码。如果需要重复。所以如果代码是" i ++"并且规则是" expr POST_INCR",我可以做或多或少:
expr POST_INCR -> "(tmp = expr; expr = expr + 1; tmp)"
使用宏进行基本代码重写?
LONG VERSION:
我写了另一种简单的解释语言(为简单起见,用Java编写)。它工作正常,但它提出了一些问题。介绍很长,但很简单,有助于清楚地显示我的问题(我认为)。
我有"而"环。这很简单,给出:
WHILE LPARE boolean_expression RPAREN statement
我或多或少产生以下内容:
new WhileNode(boolean_expression, statement);
这将创建一个新节点,稍后访问该节点时,会为我的虚拟机生成代码。但我也有以下内容:
FOR LPAREN for_init_expr SEMICOLON boolean_expression SEMICOLON for_post_expr RPAREN statement
这是" for循环"从Java或C中已知。从上述规则我创建或多或少的以下内容:
new ListNode(
for_init_expr,
new WhileNode(
boolean_expression,
new ListNode(
statement,
new ListNode(for_post_expr, null))))
这当然是简单的转变,来自:
for (for_init ; boolean_expression ; for_post_expr)
statement
为:
for_init
while (boolean_expression) {
statement
for_post_expr;
}
一切都很好,花花公子,但事情变得多毛了以下:
FOR LPAREN var_decl COLON expression RPAREN statement
这个如果众所周知且喜欢:
for (int x : new int[] { 1, 2 })
print(x);
我没有发布生成AST的代码,因为基本for循环已经有点长了,而我们在这里得到的更糟糕。这种结构等于:
int[] tmp = new int[] { 1, 2 };
for (int it = 0 ; it < tmp.length; it = it + 1) {
int x = tmp[it];
print(x);
}
由于我没有使用类型,我只是假设&#34;表达&#34; (所以右边,在COLON之后)是我可以迭代的东西(并且数组不可迭代),我在这个&#34;表达式&#34;的结果上调用一个函数。返回Iterable的实例。所以,事实上,我重写的代码并不像上面那样简单,或多或少是这样的:
Iterator it = makeIterable(new int[] { 1, 2 });
while (it.hasNext()) {
int x = it.next();
print(x);
}
它看起来并不坏,但请注意,AST会生成三个函数调用和while循环。为了告诉你它是什么混乱,我发布我现在拥有的东西:
113 | FOR LPAREN var_decl_name.v PIPE simple_value_field_or_call.o RPAREN statement.s
114 {: Symbol sv = ext(_symbol_v, _symbol_o);
115 String autoVarName = generateAutoVariableName();
116 Node iter = new StatementEndNode(sv, "",
117 new BinNode(sv, CMD.SET, "=",
118 new VarDeclNode(sv, autoVarName),
119 new CallNode(sv, "()",
120 new BinNode(sv, CMD.DOT, ".",
121 new MkIterNode(sv, o),
122 new PushConstNode(sv, "iterator")))));
123 Node varinit = new StatementEndNode(sv, "",
124 new BinNode(sv, CMD.SET, "=",
125 v,
126 new PushConstNode(sv, "null")));
127 Node hasnext = new CallNode(sv, "()",
128 new BinNode(sv, CMD.DOT, ".",
129 new VarNode(sv, autoVarName),
130 new PushConstNode(sv, "hasNext")));
131 Node vargennext = new StatementEndNode(sv, "",
132 new BinNode(sv, CMD.SET, "=",
133 new VarNode(sv, v.name),
134 new CallNode(sv, "()",
135 new BinNode(sv, CMD.DOT, ".",
136 new VarNode(sv, autoVarName),
137 new PushConstNode(sv, "next")))));
138 return new ListNode(sv, "",
139 new ListNode(sv, "",
140 new ListNode(sv, "",
141 iter
142 ),
143 varinit
144 ),
145 new WhileNode(sv, "while",
146 hasnext,
147 new ListNode(sv, "",
148 new ListNode(sv, "",
149 vargennext
150 ),
151 s)));
回答你的问题:是的,我对这段代码感到羞耻。
问题:是否有解析器生成器让我对它做一些事情,即给定规则:
FOR LPAREN var_decl COLON expr RPAREN statement
告诉解析器重写它就好像它是其他东西一样。我想这需要一些LISP的宏机制(由于基本上没有语法,这在lisp中很容易),可能类似于:
FOR LPAREN var_decl COLON expr RPAREN statement =
{ with [ x = generateAutoName(); ]
emit [ "Iterator $x = makeIterable($expr).iterator();"
"while (${x}.hasNext()) {"
"$var_decl = ${x}.next();"
"$statement"
"}"
]
}
我不知道这是否是一个众所周知的问题,我根本不知道该找什么 - 我找到的最相似的问题就是这个问题:Any software for pattern-matching and -rewriting source code?但它并不是我所需要的,因为它应该作为一个单独的步骤而不是在编译期间工作,所以它没有资格。
任何帮助将不胜感激。
答案 0 :(得分:1)
也许你正在寻找类似ANTLR的树重写规则。
通过定义一些辅助函数,您可以使AST构造语法更具可读性。在我看来,有很多冗余(为什么你需要枚举和操作符的字符串?)但我不是Java程序员。
您可能采取的一种方法:
从您的解析器开始,该解析器已生成AST。添加一个或两个词法语法来处理模板参数和gensyms。然后编写一个AST walker,它将AST序列化为重新生成AST所需的代码(Java或字节码)。使用它,您可以使用自己的解析器生成宏模板函数,这意味着它将自动与您对AST所做的任何更改保持同步。
答案 1 :(得分:1)
我认为你试图过度弯曲解析器。你可以简单地构建 带有宏的树,然后对树进行后处理,用你想要的任何替换替换宏。
您可以通过遍历生成的树,检测宏节点(或您要进行替换的位置),并简单地使用程序树hackery拼接替换来完成此操作。不漂亮但可行。您应该能够使用任何解析器生成器/ AST构建机制的结果来执行此操作。
如果你想要一个更结构化的方法,你可以建立你的AST,然后使用源到源的转换来重写&#34;重写&#34;宏的内容。 Out DMS Software Reengineering Toolkit可以执行此操作,您可以阅读更多详细信息 关于transforms look like。
的内容使用DMS方法,您的概念:
expr POST_INCR -> "(tmp = expr; expr = expr + 1; tmp)"
要求您解析常规中的原始文本 方法与语法规则:
term = expr POST_INCR ;
您可以将所有这些语法规则提供给DMS并让它 根据语法解析源代码并构建AST。
然后将DMS重写应用于生成的树:
domain MyToyLanguage; -- tells DMS to use your grammar to process the rule below
rule replace_POST_INCR(e: expr): term->term
= "\e POST_INCR" -> " (tmp = \e; \e = \e + 1; tmp) ";
此处的引号是&#34;域元引用&#34;而不是字符串文字引号。 双引号外的文本是DMS规则语法。引号内的文本是来自您的语言(MyToyLangauge)的语法,并使用您提供的解析器进行解析,对模式变量(如\ e)进行一些特殊的转义。 (你不必为你的语法做任何事情来获得这种模式解析功能; DMS负责这一点。)
按照惯例,我们经常命名像POST_INCR这样的文字标记 引用的等价物&#39; ++&#39;在词法分析器中,而不是使用这样的名称。 而不是
#token POST_INCR "\+\+"
然后,词法分析器规则如下:
#token '++' "\+\+"
如果你这样做,那么你的语法规则如下:
term = expr '++' ;
现在您的重写规则如下:
rule replace_POST_INCR(e: expr): term->term
= "\e++" -> " (tmp = \e; \e = \e + 1; tmp) ";
遵循这个惯例,语法(词法分析器和BNF) 是(恕我直言)更具可读性 并且重写规则也更具可读性,因为它们保留 非常接近实际的语言语法。