代码之家  ›  专栏  ›  技术社区  ›  Sébastien Le Callonnec

如何使用Antlr实现函数调用,以便在定义之前就可以调用它?

  •  6
  • Sébastien Le Callonnec  · 技术社区  · 15 年前

    一旦构建了AST,实现树行者的最佳方法是什么,以便可以按任何顺序定义和调用函数?

    例如,这在PHP中有效:

    <?php
    f(); // function called before it’s defined
    function f() {
      print 3;
    }
    ?>
    

    1 回复  |  直到 15 年前
        1
  •  8
  •   Bart Kiers    15 年前

    是的,你说得对:这是在一次以上的AST传球中完成的。

    首先创建一个生成源AST的语法,然后创建一个用于遍历树并发现所有已定义函数的树语法。然后,可以使用另一个树语法对脚本求值,该语法从上一个树语法中获取所发现的函数。

    获取源:

    <?php
    f(); // function called before it’s defined
    function f() {
      g();
    }
    function g() {}
    ?>
    

    它被解析为以下AST:

    alt text

    grammar PHPMin;
    
    options { 
      output=AST; 
    }
    
    tokens {
      SCRIPT; F_CALL; F_DECL; F_BODY;
    }
    
    parse
      :  script EOF -> script
      ;
    
    script
      :  '<?php' atom* '?>' -> ^(SCRIPT atom*)
      ;
    
    atom
      :  functionCall
      |  functionDecl
      ;
    
    functionCall
      :  Identifier '(' ')' ';' -> ^(F_CALL Identifier)
      ;
    
    functionDecl
      :  'function' Identifier '(' ')' '{' functionBody '}' -> ^(F_DECL Identifier functionBody)
      ;
    
    functionBody
      :  functionCall* -> ^(F_BODY functionCall*)
      ;
    
    Identifier  : ('a'..'z' | 'A'..'Z' | '_') ('a'..'z' | 'A'..'Z' | '_' | '0'..'9')* ;
    LineComment : '//' ~('\r' | '\n')* ('\r'? '\n' | EOF){skip();} ;
    Space       : (' ' | '\t' | '\r' | '\n'){skip();} ;
    

    然后使用从以下树语法生成的“树行者”来发现声明的函数:

    tree grammar PHPMinFunctionWalker;
    
    options {
        tokenVocab=PHPMin;
        ASTLabelType=CommonTree;
    }
    
    @members {
        java.util.Set<String> declared = new java.util.HashSet<String>();
    }
    
    discover
      :  script
      ;
    
    script
      :  ^(SCRIPT atom*)
      ;
    
    atom
      :  functionCall
      |  functionDecl
      ;
    
    functionCall
      :  ^(F_CALL Identifier)
      ;
    
    functionDecl
      :  ^(F_DECL Identifier functionBody) {declared.add($Identifier.text);}
      ;
    
    functionBody
      :  ^(F_BODY functionCall*)
      ;
    

    // A
    java -cp antlr-3.2.jar org.antlr.Tool PHPMin.g
    
    // B 
    java -cp antlr-3.2.jar org.antlr.Tool PHPMinFunctionWalker.g
    
    // C
    javac -cp antlr-3.2.jar *.java
    
    // D     
    java -cp .:antlr-3.2.jar Main    // *nix 
    java -cp .;antlr-3.2.jar Main    // Windows
    

    并运行以下主类(D):

    import org.antlr.runtime.*;
    import org.antlr.runtime.tree.*;
    import org.antlr.stringtemplate.*;
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
    
            String source = "<?php                                          \n" + 
                            "f(); // function called before it’s defined    \n" + 
                            "function f() {                                 \n" + 
                            "  g();                                         \n" + 
                            "}                                              \n" + 
                            "function g() {}                                \n" + 
                            "?>                                             \n";
    
            // create a lexer and parser for the source
            ANTLRStringStream in = new ANTLRStringStream(source);
            PHPMinLexer lexer = new PHPMinLexer(in);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            PHPMinParser parser = new PHPMinParser(tokens);
            PHPMinParser.parse_return returnValue = parser.parse();
            CommonTree tree = (CommonTree)returnValue.getTree();
    
            // create a tree walker to discover all declared functions
            CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree);
            nodes.setTokenStream(tokens);
            PHPMinFunctionWalker functions = new PHPMinFunctionWalker(nodes);
            functions.discover();
            System.out.println("Declared functions: "+functions.declared);
        }
    }
    

    产生以下输出:

    Declared functions: [f, g]
    

    当然,这只是一个如何处理的例子,而不是如何做得最好。我可以想象(当使用Java解释脚本时),您不会将声明的函数作为简单字符串存储在 Set<String> ,而是作为 Map<String, CommonTree> 轻松获取函数的根并在调用时对其求值。

    进一步阅读: http://www.antlr.org/wiki/display/ANTLR3/Simple+tree-based+interpeter

    编辑

    tree grammar PHPMinValidateWalker;
    
    options {
        tokenVocab=PHPMin;
        ASTLabelType=CommonTree;
    }
    
    @members {
        java.util.Set<String> declared = new java.util.HashSet<String>();
    }
    
    validate
      :  script
      ;
    
    script
      :  ^(SCRIPT atom*)
      ;
    
    atom
      :  functionCall
      |  functionDecl
      ;
    
    functionCall
      :  ^(F_CALL Identifier) 
         {
           if(!declared.contains($Identifier.text)) {
             throw new RuntimeException("no such function: " +  $Identifier.text);
           }
         }
      ;
    
    functionDecl
      :  ^(F_DECL Identifier functionBody)
      ;
    
    functionBody
      :  ^(F_BODY functionCall*)
      ;
    

    使用测试:

    import org.antlr.runtime.*;
    import org.antlr.runtime.tree.*;
    import org.antlr.stringtemplate.*;
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
    
            String source = "<?php                                          \n" + 
                            "f(); // function called before it’s defined    \n" + 
                            "function f() {                                 \n" + 
                            "  g();                                         \n" + 
                            "  x();                                         \n" + 
                            "}                                              \n" + 
                            "function g() {}                                \n" + 
                            "?>                                             \n";
    
            // create a lexer and parser for the source
            ANTLRStringStream in = new ANTLRStringStream(source);
            PHPMinLexer lexer = new PHPMinLexer(in);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            PHPMinParser parser = new PHPMinParser(tokens);
            PHPMinParser.parse_return returnValue = parser.parse();
            CommonTree tree = (CommonTree)returnValue.getTree();
    
            // create a tree walker to discover all declared functions
            CommonTreeNodeStream nodes = new CommonTreeNodeStream(tree);
            nodes.setTokenStream(tokens);
            PHPMinFunctionWalker functions = new PHPMinFunctionWalker(nodes);
            functions.discover();
            System.out.println("Declared functions: "+functions.declared);
    
            // PHPMinValidateWalker
            nodes = new CommonTreeNodeStream(tree);
            nodes.setTokenStream(tokens);
            PHPMinValidateWalker validator = new PHPMinValidateWalker(nodes);
            validator.declared = functions.declared;
            validator.validate();
        }
    }
    

    产生异常,因为 x() 在任何地方都没有定义。从源代码中删除它将导致树行者不产生异常。

    推荐文章