使用ANTLR(或任何其他工具)对术语重写解析器/表达式求值程序进行编码

时间:2015-03-25 11:10:52

标签: java javascript parsing antlr

我正在尝试编写一个软件,该软件应仅使用这些功能执行基本编程语言的指令:

  • 算术表达式计算器(加,减,多,除,括号,......)
  • if-else statement
  • 功能定义

它应该一次一步显示“减少”或“简化”的代码,并让我展示示例输出的示例:

迭代1:

a=3;
b=2;
c=true;
if(c && (a < 3 * (5 -2) ) || b >= 3 * (5 -2))){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代2:

if(true && (a < 3 * (5 -2) ) || b >= 3 * (5 -2))){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代3:

if(true && (3 < 3 * (5 -2) ) || 2 >= 3 * (5 -2))){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代4:

if(true && (3 < 9 ) || 2 >= 3 * (5 -2))){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代5:

if(true && (3 < 9 ) || 2 >= 3 * 3)){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代6:

if(true && (3 < 9 ) || 2 >= 9)){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代7:

if(true && true || 2 >= 9)){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代8:

if(true && (true || false)){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代9:

if(true && false){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代10:

if(false){
    System.out.println("going through if");
}else{
    System.out.println("going through else");
}

迭代11:

System.out.println("going through else");

因此它应该解析输入代码并在每次迭代时同时执行它,只进行一次基本操作,替换操作结果并在不再需要简化步骤时完成循环。任何人都知道如何做到这一点,例如使用ANTLR工具?我一直在检查ANTLR的Tree Listener功能,因为它似乎要走了,但我不清楚如何实现它。

另一个原因是最佳的是它是用Javascript实现的,以便能够在Web浏览器中执行,但Java代码就足够了(即作为Java applet执行)。

语法:

program
    :   (variable | function)*
        statement*
    ;


variable
    :   IDENT ('=' expression)? ';'
    ;

type
    :   'int'
    |   'boolean'
    |   'String'
    |   'char'
    ;


statement
    :   assignmentStatement
    |   ifStatement
    ;


ifStatement
    :   'if' '(' expression ')' '{' statement+ '}'
        ('else if' '(' expression ')' '{' statement+)* '}'
        ('else' '{' statement+)? '}'
    ;

assignmentStatement
    :   IDENT '=' expression ';'
    ;


returnStatement
    :   'return' expression ';'
    ;


function
    :   'function' IDENT '(' parameters? ')' '{'
        (statement|returnStatement)*
        '}'
    ;   

parameters
    :   IDENT (',' IDENT)*
    ;

term
    :   IDENT
    |   '(' expression ')'
    |   INTEGER
    ;

negation
    :   '-' -> NEGATION
    ;

unary
    :   ('+'! | negation^)* term
    ;

mult
    :   unary (('*' | '/' | 'mod') unary)*
    ;

add
    :   mult (('+' | '-') mult)*
    ;

relation
    :   add (('=' | '/=' | '<' | '<=' | '>=' | '>') add)*
    ;

expression
    :   relation (('and' | 'or') relation)*
    ;


fragment LETTER : ('a'..'z' | 'A'..'Z') ;
fragment DIGIT : '0'..'9';
INTEGER : DIGIT+ ;
IDENT : LETTER (LETTER | DIGIT)*;
WS : (' ' | '\t' | '\n' | '\r' | '\f')+ {$channel = HIDDEN;};
COMMENT : '//' .* ('\n'|'\r') {$channel = HIDDEN;};

2 个答案:

答案 0 :(得分:6)

[OP:...(或任何其他工具)]

您使用短语term rewriting,然后展示一个有趣的示例,通过替换值并执行constant folding来生成最终的程序答案,逐步处理程序的源代码。

抽象地说,你想从术语重写系统中得到的是能够从一组术语重写开始的能力。规则,实质上指定

if you see this, replace it by that

e.g。

" if (true) then ... "  ==>   " ... "

并以有组织的方式反复应用这些规则,直到达到一些停止条件(通常是&#34;不再适用规则&#34;)。

有两种方法可以实现术语重写,既可以从术语开始(在你的情况下是程序的AST),也可以产生相同的结果。不同之处在于重写规则的实际实施方式。

程序树重写

第一种方式在程序上指定重写步骤。也就是说,构建一个访问者来遍历AST,它在程序上检查节点类型,以及有趣节点类型的子树(&#34;匹配&#34;),并且在找到匹配的地方,根据以下内容修改树。规则的预期效果。

对于&#34; if&#34;上面的例子,访问者会发现&#34; if&#34;语句子树根,检查左/条件子树,看它是否是&#34; true&#34;子树,如果是的话,替换&#34; if&#34;由右子树生成修改树的子树。

常量折叠有点特殊:访问者检查操作员节点,检查其所有子节点是否为常量值,在这些常量上计算运算符的结果,然后用包含计算结果的节点替换运算符。

要使用一组规则实现重写,您必须首先记下所有抽象规则,然后对此访问者进行编码,并结合所有规则中的所有测试。这可能会产生一个非常混乱的代码,它会检查节点类型并在子树上上下走动以进行进一步检查。 (你也可以按规则实现这一个访问者,这使得它们更容易编写,但现在你有大量的访问者,你必须在树上重复运行它们......这最终会非常慢)。

访问者有点聪明:你不希望它在他们的时间和#34;之前处理子树#34;考虑这个代码,由重写器处理:

  if (x) then y = 3/ 0; else y = 3;

你不想经常弃牌&#34; 3/0&#34; 之前的已经过评估。

您可以从任何解析器生成器(包括ANTLR)开始执行ASTs的过程重写;写访客只是汗流。背。也许很多。

由于将所有规则匹配组合到访问者中的问题,程序重写很难实现。如果你有几十个规则,这就变得难以管理,速度很快。 (如果您要使用规则来处理完整的计算机语言,那么每个语法位置至少会有一条规则;在这种情况下很容易获得数十条规则,如果不是数百条规则的话。

获得&#34;增量&#34; OP所需的显示方面,你必须在每个匹配/替换步骤后停止,然后重新打印AST,例如,从AST重新生成表面语法文本。修改访问者在每次树修改后调用prettyprint并不是很难。

生成AST的解析器生成器通常不会为执行漂亮的打印步骤提供很好的帮助。它比看起来漂亮印刷更难。细节太复杂了,不能放在这里;有关如何执行此操作的详细信息,请参阅我的SO answer on how to prettyprint

下一个复杂问题:当遇到正在评估的程序中的变量时,应该在树中替换什么值?为此,需要一个符号表用于语言,并且必须使该符号表保持最新的变量值分配。

如果程序格式不正确,不会讨论会发生什么。它们将是: - {很可能rwrites需要很多&#34;错误检查&#34;防止无意义的计算(例如,&#34; x / y&#34;其中y是一个字符串)。

直接树重写

理想情况下,您想要的是直接接受明确的术语重写规则的引擎,并且可以应用它们。

Program Transformation Systems (PTS)这样做。具体系统包括Mathematica(现称为#34; Wolfram语言&#34;?),DMS,TXL,Stratego / XT。 (OP正在寻找一个用Java实现的:我认为Stratego有一个Java版本,其他的绝对没有。)

这些工具接受使用目标语言的表面语法编写的重写规则,将规则本质上转换为模式树对(a&#34;匹配&#34;带有可变占位符的树)和&#34;替换&#34;具有(相同)可变占位符的树。这些工具中的重写引擎将采用指定规则的任意子集,并将它们应用于树,通过比较&#34;匹配&#34;来检查所有匹配。当找到匹配时,树替换目标树,并用匹配的占位符替换替换树。这是编写复杂的重写集的一个主要方便。 (如果你考虑一下,这仍然是程序上的重写...只是引擎正在做它而不是你,规则说明符。尽管方便。)

这样的PTS包括构建AST的解析器生成器(Mathematica没有)和完整的prettyprinter(或者至少允许你方便地定义一个)。

对于DMS,您可以编写如下规则:

 rule fold_true_ifTE(s: statement, t:statement): statement->statement =
 "  if (true) then \s else \t " ->  " \s ";

 rule fold_false_ifTE(s: statement, t:statement): statement->statement =
 "  if (false) then \s else \t " ->  " \t ";

 rule fold_constants_add(x:NUMBER,y:NUMBER):sum -> sum =
 " \x + \y " -> " \Add\(\x\,\y\)";

前两条规则实现了&#34; if&#34;我们之前草拟的声明重写;你还需要规则只是&#34; if-then&#34;声明。引号是 metaquotes ;它们将规则规范语言(RSL)的文本与规则操纵的语言文本分开。 metaescaped 字母(s,t,x,y)是元变量,表示规则匹配的子树。这些元变量必须具有规则参数列表指定的AST类型(例如, s:statement 表示s是&#34;任何语句节点&#34;)。

第三条规则为&#34;添加&#34;实现了常量折叠。该模式寻找&#34; +&#34;操作;只有当找到一个同时具有数字常量的子项时才会获得匹配。它的工作原理是调用外部程序&#34; \ Add&#34;对其运营商; Add返回一个包含总和的树,重写引擎将其拼接到位。

在DMS的情况下,在每次重写尝试(失败和成功)之后调用重写机制都有一个钩子来跟踪重写结果。这个钩子将是OP称为prettyprinter的地方,以显示每个步骤后树的变化情况。

有关如何编写规则以评估代数表达式的详细示例,请参阅how to implement Algebra with rewrite rules

有关DMS重写规则如何工作的更详细说明,以及将它们应用于&#34;简化&#34;的示例。 (评估)Nikolas Wirth的编程语言Oberon,见DMS Rewrite Rules

在任何一种情况下都不显示控制规则应用顺序的方法。因为排序约束可以是任意的,所以必须介入并引导重写引擎。如果需要,DMS提供规则排序的完整程序控制。通常可以将规则划分为不同的集合:可以不加选择地应用的规则,以及需要排序的规则(例如,if-then简化规则)。

PTS不会使符号表问题消失; OP仍然需要一个。大多数PTS都没有为此提供任何支持。 DMS为此提供了明确的支持(配置需要一些努力,但比没有任何东西时要少得多!)以及构建静态类型检查器以帮助在开始执行之前验证程序是否良好。实际上,有许多问题需要解决以准备执行程序(例如,可能想要构建标签的映射到源代码点以实现有效的GOTO模拟)。请参阅Life After Parsing

答案 1 :(得分:1)

在下面的Symja示例代码段中,您可以尝试内置的Java术语重写引擎。

术语重写和模式匹配引擎的主要部分在包中实现:GIT: org.matheclipse.core.patternmatching

通过实现IEvalStepListener接口,您可以看到引擎在内部运行的步骤:

package org.matheclipse.core.examples;

import org.matheclipse.core.eval.EvalEngine;
import org.matheclipse.core.eval.ExprEvaluator;
import org.matheclipse.core.interfaces.AbstractEvalStepListener;
import org.matheclipse.core.interfaces.IExpr;
import org.matheclipse.parser.client.SyntaxError;
import org.matheclipse.parser.client.math.MathException;

public class SO_StepListenerExample {
    private static class StepListener extends AbstractEvalStepListener {
        /** Listens to the evaluation step in the evaluation engine. */
        @Override
        public void add(IExpr inputExpr, IExpr resultExpr, int recursionDepth, long iterationCounter) {
            System.out.println("Depth " + recursionDepth + " Iteration " + iterationCounter + ": " + inputExpr.toString() + " ==> "
                    + resultExpr.toString());
        }
    }

    public static void main(String[] args) {
        try {
            ExprEvaluator util = new ExprEvaluator( );
            EvalEngine engine = util.getEvalEngine();
            engine.setStepListener(new StepListener());

            IExpr result = util.evaluate(
                    "a=3;b=2;c=True;If(c && (a < 3 * (5 -2) ) || ( b >= 3 * (5 -2)),"
                    + "GOINGTHROUGHIF,"
                    + "GOINGTHROUGHELSE )");
            System.out.println("Result: " + result.toString());
            // disable trace mode if the step listener isn't necessary anymore
            engine.setTraceMode(false);
        } catch (SyntaxError e) {
            // catch Symja parser errors here
            System.out.println(e.getMessage());
        } catch (MathException me) {
            // catch Symja math errors here
            System.out.println(me.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

该示例生成以下输出:

Depth 4 Iteration 0: a=3 ==> 3
Depth 4 Iteration 0: b=2 ==> 2
Depth 3 Iteration 0: a=3;b=2 ==> 2
Depth 3 Iteration 0: c=True ==> True
Depth 2 Iteration 0: a=3;b=2;c=True ==> True
Depth 5 Iteration 0: c ==> True
Depth 6 Iteration 0: a ==> 3
Depth 8 Iteration 0: (-1)*2 ==> -2
Depth 7 Iteration 0: -2+5 ==> -2+5
Depth 7 Iteration 1: 5-2 ==> 3
Depth 6 Iteration 0: 3*(-2+5) ==> 3*3
Depth 6 Iteration 1: 3*3 ==> 9
Depth 5 Iteration 0: a<3*(-2+5) ==> 3<9
Depth 5 Iteration 1: 3<9 ==> True
Depth 4 Iteration 0: c&&a<3*(-2+5) ==> True
Depth 3 Iteration 0: c&&a<3*(-2+5)||b>=3*(-2+5) ==> True
Depth 2 Iteration 0: If(c&&a<3*(-2+5)||b>=3*(-2+5),goingthroughif,goingthroughelse) ==> goingthroughif
Depth 1 Iteration 0: a=3;b=2;c=True;If(c&&a<3*(-2+5)||b>=3*(-2+5),goingthroughif,goingthroughelse) ==> goingthroughif
Result: goingthroughif