【问题标题】:ANTLR - grandchild nodes in tree constructionANTLR - 树构造中的孙节点
【发布时间】:2012-12-11 02:10:17
【问题描述】:

我正在尝试编写一个声明性语法,其中声明和其他语句的顺序并不重要。但是,对于解析,我想让语法以有序的方式输出一棵树。假设语言由声明(decl)和赋值(assign)组成。一个例子可能是:

decl x
assign y 2
assign x 1
decl y

我想让程序用一棵树表示,所有声明都在一个子树中,所有赋值都在另一个子树中。对于上面的示例,类似于:

(PROGRAM
    (DECLARATIONS x y)
    (ASSIGNMENTS
        (y 2)
        (x 1)))

我可以在树的构建过程中进行这种重新排列,还是应该写一个树的语法?

【问题讨论】:

    标签: antlr grammar declarative


    【解决方案1】:

    我认为这里有一个比另一个更简单的答案:

    token { DECLS; ASSIGNS; }
    
    prog: (d+=decl | a+=assign)* EOF -> ^(DECLS $d*) ^(ASSIGNS $a*) ;
    
    ...
    

    当然,这可以适应任意多的规则。

    但是,您确定需要这样做吗?为什么不在解析器中构建 DECL 指令的符号表,然后只构建 ASSIGN 的 AST,您可以在 tree walk 中检查。

    吉姆

    【讨论】:

    • 对于这种简单的情况,这当然是一种快速的处理方式。
    • 为了简单而接受 - 谢谢!要回答你的问题,我不完全确定我需要这样做,但正如我对另一个答案的评论,我不喜欢将代码混入语法中。对我来说,合乎逻辑的步骤是在 AST 中分离出声明,单独的解析代码可以轻松处理它们。
    【解决方案2】:

    我可以在树的构建过程中进行这种重新排列,还是应该写一个树的语法?

    两者都可以,但我建议在令牌解析期间对节点进行分组。

    我对我编写的组节点的任何树重写语法都不满意,因为这些语法必须重新发现每个可分组节点的位置——因此需要分组。令牌解析器在常规处理期间触及所有这些数据,并且树语法最终会为这些节点遍历树,就像令牌解析器已经遍历其输入的令牌一样。如果树解析器只是只是用于分组,我认为树解析器不值得麻烦。

    无论如何,在解析器中管理分组归结为在 declassign 节点生成后保存它们,然后在它们的分组级别发生时再次将它们推出。这是一个简单的例子。

    声明性.g

    grammar Declarative;
    
    options { 
        output = AST;
    }
    
    tokens { 
        PROGRAM; DECLARATIONS; ASSIGNMENTS;
    } 
    
    @parser::header { 
        import java.util.ArrayList;
    }
    
    @members {
        private ArrayList<Object> assigns = new ArrayList<Object>(); 
        private ArrayList<Object> decls = new ArrayList<Object>();
        
        private Object createTree(int ttype,  ArrayList<Object> children) {
            Object tree = adaptor.create(ttype, tokenNames[ttype]);
            for (Object child : children){
                adaptor.addChild(tree, child);
            }
            
            return tree; 
        }
    }
    
    compilationUnit     : statement* EOF -> ^(PROGRAM {createTree(DECLARATIONS, decls)} {createTree(ASSIGNMENTS, assigns)}); 
    
    statement           : decl {decls.add($decl.tree);}
                        | assign {assigns.add($assign.tree);}
                        ;
                        
    decl                : DECL^ ID;
    assign              : ASSIGN^ ID INT;
                        
    DECL    : 'decl';
    ASSIGN  : 'assign';
    ID      : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z')*;
    INT     : ('0'..'9')+;                  
    WS      : (' '|'\t'|'\f'|'\n'|'\r'){skip();};
    

    每个decl 节点都被statement 规则保存在decls 列表中,对于每个assign 节点也是如此。

    方法createTree 使用解析器的TreeAdaptor 构建组节点并填充它们。

    CommonTree tree = (CommonTree) adaptor.create(ttype, tokenNames[ttype]);
    for (Object child : children){
        adaptor.addChild(tree, child);
    }
    
    return tree; 
    

    compilationUnit 的产生式是^(PROGRAM {createTree(DECLARATIONS, decls)} {createTree(ASSIGNMENTS, assigns)}),它将分组节点添加到PROGRAM。方法createTree用于一次性构建分组节点及其子节点。

    可能有一种棘手的方法可以让 ANTLR 为您整合所有内容,但这很有效,而且不言自明。

    所以给定这个输入...

    decl x
    assign y 2
    assign x 1
    decl y
    

    ...为上述语法生成的标记解析器生成此树作为输出:

    (PROGRAM 
        (DECLARATIONS 
            (decl x) 
            (decl y)) 
        (ASSIGNMENTS 
            (assign y 2) 
            (assign x 1)))
    

    【讨论】:

    • 感谢您的详细解答!我个人对将代码与语法混合存在偏见,但我想我将不得不克服这一天......
    • @DanielBuckmaster 我不怪你的偏见,我的回答是针对更一般的情况(从深度 n 到任意深度
    • 我认为我的问题最终是用词不当......我最初想知道我如何才能找到匹配的规则并拉出它的孩子,然后意识到这并不是我真正想要的:P。 IMO,给定的语法应该可用于任何语言目标。但我想我们还没有到那个阶段。我只是试图将目标语言特定的东西排除在我的语法之外!
    猜你喜欢
    • 1970-01-01
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多