Как реализовать вызов функции с ANTLR, чтобы его можно было назвать даже до определенного?

StackOverflow https://stackoverflow.com/questions/4075510

Вопрос

После того, как AST будет построен, какой лучший способ реализует Walker Tree, чтобы функции можно определить и вызвать в любом порядке?

Например, это действительно в PHP:

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

Я предполагаю, что как-то там должен быть второй проход, или трансформация дерева, но я не могу найти ничего интересного на эту тему. Проблема, вероятно, не является аналогичным, но если вы могли бы указать мне пример ANTLR, как это делается, даже лучше!

Это было полезно?

Решение

Да, вы правы: это делается в более чем один проход через 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*)
  ;

Чтобы проверить все это, создайте Lexer и Parser (A), генерируйте «Tree-Walker» (B), компилируйте все исходные файлы (C):

// 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() не определяется где угодно. Удаление его из источника приведет к тому, что дерево-ходунки не вызывают исключения.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top