1

我需要为具有前向引用的语言创建语法。我认为实现这一点的最简单方法是对生成的 AST 进行多次传递,但我需要一种将符号信息存储在树中的方法。

现在我的解析器正确地生成了一个 AST 并计算了变量和函数定义的范围。问题是,我不知道如何将范围信息保存到树中。

我的语法片段:

composite_instruction
scope JScope;
@init {
    $JScope::symbols = new ArrayList();
    $JScope::name = "level "+ $JScope.size();
}
@after {
    System.out.println("code block scope " +$JScope::name + " = " + $JScope::symbols);
}
    : '{' instruction* '}' -> ^(INSTRUCTION_LIST instruction*)
    ;

我想将对当前范围的引用放入树中,例如:

    : '{' instruction* '}' -> ^(INSTRUCTION_LIST instruction* {$JScope::symbols})

甚至可能吗?有没有其他方法可以将当前范围存储在生成的树中?我可以在树语法中生成范围信息,但它不会改变任何东西,因为我仍然必须将它存储在某个地方以便在树上进行第二次传递。

4

1 回答 1

2

据我所知,重写规则的语法不允许像您的暂定代码段所建议的那样直接分配值。这部分是因为解析器并不真正知道应该将值添加到树/节点的哪个部分。

然而,ANTLR 生成的 AST 的一个很酷的特性是解析器不对节点的类型做任何假设。只需要实现一个 TreeAdapator,它作为新节点的工厂和树结构的导航器。因此,可以在节点中填充可能需要的任何信息,如下所述。

ANTLR 提供了一个默认的树节点实现CommonTree,并且在大多数情况下(如手头的情况)我们只需要

  • 通过向 CommonTree 添加一些自定义字段来子类化 CommonTree
  • 子类化 CommonTreeAdaptor 以覆盖它的 create() 方法,即它产生新节点的方式。

但也可以为一些奇怪的图结构或诸如此类的东西创建一种新颖的节点类型。对于手头的情况,以下内容就足够了(如果这不是java,请适应特定的目标语言)

import org.antlr.runtime.tree.*;
import org.antlr.runtime.Token;

public class NodeWithScope extends CommonTree {

    /* Just declare the extra fields for the node */
    public ArrayList symbols;
    public string    name;
    public object    whatever_else;

    public NodeWithScope (Token t) {
        super(t);
    }
}

/* TreeAdaptor: we just need to override create method */
class NodeWithScopeAdaptor extends CommonTreeAdaptor {
    public Object create(Token standardPayload) {
        return new NodeWithScope(standardPayload);
    }
}

然后需要稍微修改解析过程的启动方式,以便 ANTLR(或者更确切地说是 ANTLR 生成的解析器)知道使用 NodeWithScopeAdaptor 而不是 CommnTree。
(下面的步骤 4.1,其余的如果是标准的 ANTLR 测试台)

// ***** Typical ANTLR pipe rig  *****
//  ** 1. input stream 
ANTLRInputStream input = new ANTLRInputStream(my_input_file);
//  ** 2, Lexer 
MyGrammarLexer lexer = new MyGrammarLexer(input);
//  ** 3. token stream produced by lexer
CommonTokenStream tokens = new CommonTokenStream(lexer);
//  ** 4. Parser
MyGrammarParser parser = new MyGrammarParser(tokens);

//     4.1  !!! Specify the TreeAdapter
NodeWithScopeAdaptor  adaptor = new NodeWithScopeAdaptor();
parser.setTreeAdaptor(adaptor); // use my adaptor

//  ** 5. Start process by invoking the root rule
    r = parser.MyTopRule();
//  ** 6. AST tree
NodeWithScope  t = (NodeWithScope)r.getTree();
//  ** 7.  etc. parse the tree or do whatever is needed on it.

最后,您的语法必须使用类似于以下内容的内容进行调整
(请注意,节点 [for the current rule] 仅在 @after 部分中可用。但是它可能会引用语法级别的任何标记属性和其他上下文变量,使用通常的 $rule.attribute 表示法)

composite_instruction
scope JScope;
@init {
    $JScope::symbols = new ArrayList();
    $JScope::name = "level "+ $JScope.size();
}
@after {
      ($composite_instruction.tree).symbols = $JScope::symbols;
      ($composite_instruction.tree).name    = $JScope::name;
      ($composite_instruction.tree).whatever_else
            = new myFancyObject($x.Text, $y.line, whatever, blah);
}
    : '{' instruction* '}' -> ^(INSTRUCTION_LIST instruction*)
    ;
于 2010-11-18T02:54:05.813 回答