Como implementar uma chamada de função com a ANTLR para que possa ser chamada antes mesmo de ser definida?

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

Pergunta

Depois que o AST é construído, qual é a melhor maneira de implementar o Walker de árvores para que as funções possam ser definidas e chamadas em qualquer ordem?

Por exemplo, isso é válido no PHP:

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

Suponho que, de alguma forma, deve haver um segundo passe ou uma transformação de árvores, mas não consigo encontrar nada interessante sobre esse assunto. O problema provavelmente não é específico da ANTLR, mas se você pudesse me indicar um exemplo de como isso é feito, melhor ainda!

Foi útil?

Solução

Sim, você está certo: isso é feito em mais de um passe sobre o AST.

Primeiro, você cria uma gramática que constrói um AST da fonte e, em seguida, cria uma gramática de árvore usada para iterar sobre a árvore e descobre toda a função definida. Você pode avaliar o script usando outra gramática de árvore que pega as funções descobertas da gramática da árvore anterior.

Uma demonstração.

Pegue a fonte:

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

que é analisado no seguinte AST:

alt text

Usando a gramática (combinada):

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

Em seguida, descubra as funções declaradas usando um "caminhante da árvore" gerado a partir da seguinte gramática da árvore:

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

Para testar tudo, criar um lexer e analisador (a), gerar o "Walker" (b), compilar todos os arquivos de origem (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

e execute a seguinte classe principal (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);
    }
}

que produz a seguinte saída:

Declared functions: [f, g]

Obviamente, este é apenas um exemplo de como abordá -lo, não de como é melhor. Eu posso imaginar (ao usar o Java para interpretar o script), você não armazenaria as funções declaradas como cordas simples em um Set<String>, mas sim como um Map<String, CommonTree> Para obter facilmente a raiz de uma função e avaliá -la quando chamado.

Leitura adicional: http://www.antlr.org/wiki/display/antlr3/simple+tree baseado+InterPeter

Boa sorte!

EDITAR

O passe dos segundos pode verificar se todas as funções são definidas à frente dele usando o Walker anterior:

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

Usando o teste:

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

produz uma exceção desde x() não está definido em nenhum lugar. Remover-o da fonte fará com que o Walker da Árvore não produza exceção.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top