【问题标题】:Source code parsing and macro-like handling of similar constructions源代码解析和类似结构的宏处理
【发布时间】:2016-04-11 20:56:42
【问题描述】:

TL;DR VERSION:是否有支持以下内容的解析器生成器:当某些规则被缩减时(我假设为 LALR(1) 解析器),则不执行缩减,但解析器后退并用不同的代码替换输入使用此规则中的值并解析该代码。如果需要,请重复。所以如果代码是“i++”并且规则是“expr POST_INCR”,我可以做更多或更少:

expr POST_INCR -> "(tmp = expr; expr = expr + 1; tmp)"

所以基本上是使用宏重写代码?

长版:

我编写了另一种简单的解释语言(为简单起见,用 Java 编写)。它工作正常,但它提出了一些问题。简介很长,但很简单,有助于清楚地展示我的问题(我认为)。

我有“while”循环。这很简单,因为:

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

这是 Java 或 C 中已知的“for 循环”。根据上述规则,我或多或少地创建了以下内容:

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);
}

由于我没有使用类型,我只是假设“表达式”(所以右侧,在冒号之后)是我可以迭代的东西(并且数组不可迭代),我调用一个函数的结果这个“表达式”返回 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? 但它不在任何地方接近我需要的,因为它应该作为一个单独的步骤而不是在编译期间工作,所以它不符合条件。

我们将不胜感激。

【问题讨论】:

    标签: java parsing lalr


    【解决方案1】:

    我认为您试图过多地弯曲解析器。您可以简单地构建 包含宏的树,然后对树进行后处理以用您想要的任何替换替换宏。

    您可以通过遍历生成的树、检测宏节点(或您想要进行替换的地方)并使用程序树黑客技术简单地拼接替换来做到这一点。不漂亮但可行。您应该能够使用任何解析器生成器/AST 构建机器的结果来执行此操作。

    如果您想要更结构化的方法,您可以构建您的 AST,然后使用源到源转换将宏“重写”到其内容。 出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) ";
    

    这里的引号是“域元引号”而不是字符串文字引号。 双引号外的文本是 DMS 规则语法。引号内的文本是来自您的语言 (MyToyLangauge) 的语法,并使用您提供的解析器进行解析,对模式变量(如 \e)进行了一些特殊转义。 (您无需对语法进行任何操作即可获得这种模式解析功能;DMS 会处理这一点。

    按照 DMS 的约定,我们通常将文字标记命名为 POST_INCR 在词法分析器中使用带引号的等效“++”,而不是使用这样的名称。 而不是

    #token POST_INCR "\+\+"
    

    词法分析器规则如下所示:

    #token '++'  "\+\+"
    

    如果你这样做,那么你的语法规则如下:

    term = expr '++' ;
    

    你的重写规则现在看起来像:

    rule replace_POST_INCR(e: expr): term->term
      = "\e++" -> " (tmp = \e; \e = \e + 1; tmp) ";
    

    按照这个约定,语法(词法分析器和 BNF) (恕我直言)更具可读性, 并且重写规则也更具可读性,因为它们保持不变 非常接近实际的语言语法。

    【讨论】:

    • 谢谢,这很有趣。我当然买不起 DMS,因为“我只是个穷小子”,但这似乎正是我一直在寻找的。​​span>
    • 我不知道该如何回应“DMS 不是免费的,所以我买不起”。的确,按照大众市场软件的标准,它并不便宜,但它不是大众市场软件;它不是数百万用户需要的那种产品。对于试图构建真正严肃的工具的工程师来说,它可能会节省一年或更长时间的工程时间。那有什么价值?最后,令人惊讶的是,有多少人买不起一些软件,但却设法购买了 PC、显示器和打印机。 (如果一个项目是一种爱好,我可以理解人们可能不想购买额外的软件。)
    • 信不信由你,但前段时间我读了很多关于你的工具的文章,我不认为它们很贵——它们看起来非常有用,我什至把它们推荐给我的上级,但它决定“我们不需要他们”。好吧,他们当然不需要, 和我的团队需要他们。我在评论中的意思是,作为一个私人,如果它们不是绝对必要的话,我买不起——这个项目一种爱好,我不这样做有很多时间,很遗憾。在一个相对分开的笔记上:我读了你的文章/文章“解析后的生活”,它很棒。
    • @JędrzejDudkiewicz:很好,我们互相理解。 PS:再次感谢您的称赞 :-} 祝您的团队好运,尽管您有管理,我希望您的爱好取得进展。
    【解决方案2】:

    也许您正在寻找类似 ANTLR 的树重写规则之类的东西。

    您可以通过定义一些辅助函数使您的 AST 构造语法更具可读性。在我看来,有很多冗余(为什么运算符需要枚举和字符串?)但我不是 Java 程序员。

    您可能采取的一种方法:

    从已经生成 AST 的解析器开始。添加一两个词法语法来处理模板参数和生成符号。然后编写一个 AST walker,将 AST 序列化为重新生成 AST 所需的代码(Java 或字节码)。使用它,您可以使用自己的解析器生成宏模板函数,这意味着它将自动与您可能对 AST 所做的任何更改保持同步。

    【讨论】:

    • 感谢“树重写”关键字。我将研究 ATLR 的灵魂。这不是我想要的,但看起来很有希望(而且是免费的:>)。宏节点也很有趣,我看看用这个方法能实现什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-06
    • 1970-01-01
    • 1970-01-01
    • 2017-10-21
    相关资源
    最近更新 更多