coldfusion - tradusir - Una vez que se completa la gramática, ¿cuál es la mejor manera de caminar un árbol ANTLR v4?
traductor spanish (1)
Gol
Estoy trabajando en un proyecto para crear un Varscoper para Coldfusion CFscript. Básicamente, esto significa verificar a través de los archivos de código fuente para garantizar que los desarrolladores hayan var
adecuadamente sus variables.
Después de un par de días de trabajar con ANTLR V4, tengo una gramática que genera un muy buen árbol de análisis en la vista de la GUI. Ahora, usando ese árbol necesito una manera de avanzar y retroceder por los nodos programáticamente buscando declaraciones de variables y asegurarme de que si están dentro de las funciones tienen el alcance adecuado. Si es posible, preferiría NO hacer esto en el archivo de gramática ya que eso requeriría mezclar la definición del idioma con esta tarea específica.
Lo que he intentado
Mi último intento fue usar el ParserRuleContext
y tratar de revisar sus children
través de getPayload()
. Después de verificar la clase de getPayLoad()
, tendría un objeto ParserRuleContext
o un objeto Token
. Desafortunadamente, al usar eso nunca pude encontrar la manera de obtener el tipo de regla real para un nodo específico, solo que contiene texto. El tipo de regla para cada nodo es necesario porque importa si ese nodo de texto es una expresión de mano derecha ignorada, una asignación de variable o una declaración de función.
Preguntas
- Soy muy nuevo en ANTLR, ¿es este el enfoque correcto o existe una forma mejor de atravesar el árbol?
Aquí está mi código de Java de muestra:
Cfscript.java
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.Trees;
public class Cfscript {
public static void main(String[] args) throws Exception {
ANTLRInputStream input = new ANTLRFileStream(args[0]);
CfscriptLexer lexer = new CfscriptLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CfscriptParser parser = new CfscriptParser(tokens);
parser.setBuildParseTree(true);
ParserRuleContext tree = parser.component();
tree.inspect(parser); // show in gui
/*
Recursively go though tree finding function declarations and ensuring all variableDeclarations are varred
but how?
*/
}
}
Cfscript.g4
grammar Cfscript;
component
: ''component'' keyValue* ''{'' componentBody ''}''
;
componentBody
: (componentElement)*
;
componentElement
: statement
| functionDeclaration
;
functionDeclaration
: Identifier? Identifier? ''function'' Identifier argumentsDefinition ''{'' functionBody ''}''
;
argumentsDefinition
: ''('' argumentDefinition ('','' argumentDefinition)* '')''
| ''()''
;
argumentDefinition
: Identifier? Identifier? argumentName (''='' expression)?
;
argumentName
: Identifier
;
functionBody
: (statement)*
;
statement
: variableStatement
| nonVarVariableStatement
| expressionStatement
;
variableStatement
: ''var'' variableName ''='' expression '';''
;
nonVarVariableStatement
: variableName ''='' expression '';''
;
expressionStatement
: expression '';''
;
expression
: assignmentExpression
| arrayLiteral
| objectLiteral
| StringLiteral
| incrementExpression
| decrementExpression
| ''true''
| ''false''
| Identifier
;
incrementExpression
: variableName ''++''
;
decrementExpression
: variableName ''--''
;
assignmentExpression
: Identifier (assignmentExpressionSuffix)*
| assignmentExpression ((''+''|''-''|''/''|''*'') assignmentExpression)+
;
assignmentExpressionSuffix
: ''.'' assignmentExpression
| ArrayIndex
| (''()'' | ''('' expression ('','' expression)* '')'' )
;
methodCall
: Identifier (''()'' | ''('' expression ('','' expression)* '')'' )
;
variableName
: Identifier (variableSuffix)*
;
variableSuffix
: ArrayIndex
| ''.'' variableName
;
arrayLiteral
: ''['' expression ('','' expression)* '']''
;
objectLiteral
: ''{'' (Identifier ''='' expression ('','' Identifier ''='' expression)*)? ''}''
;
keyValue
: Identifier ''='' StringLiteral
;
StringLiteral
: ''"'' (~(''//'|''"''))* ''"''
;
ArrayIndex
: ''['' [1-9] [0-9]* '']''
| ''['' StringLiteral '']''
;
Identifier
: [a-zA-Z0-9]+
;
WS
: [ /t/r/n]+ -> skip
;
COMMENT
: ''/*'' .*? ''*/'' -> skip
;
Test.cfc (archivo de código de prueba)
component something = "foo" another = "more" persistent = "true" datasource = "#application.env.dsn#" {
var method = something.foo.test1;
testing = something.foo[10];
testingagain = something.foo["this is a test"];
nuts["testing"]++;
blah.test().test3["test"]();
var math = 1 + 2 - blah.test().test4["test"];
var test = something;
var testing = somethingelse;
var testing = {
test = more,
mystuff = {
interior = test
},
third = "third key"
};
other = "Idunno homie";
methodCall(interiorMethod());
public function bar() {
var new = "somebody i used to know";
something = [1, 2, 3];
}
function nuts(required string test1 = "first", string test = "second", test3 = "third") {
}
private boolean function baz() {
var this = "something else";
}
}
Yo no caminaría esto manualmente si fuera tú. Después de generar un lexer y un analizador, ANTLR también habría generado un archivo llamado CfscriptBaseListener
que tiene métodos vacíos para todas sus reglas de analizador. Puedes dejar que ANTLR recorra tu árbol y adjuntar un detector de árbol personalizado en el que anulas solo aquellos métodos / reglas que te interesan.
En su caso, es probable que desee que le avisemos siempre que se cree una nueva función (para crear un nuevo ámbito) y probablemente le interesen las asignaciones de variableStatement
( variableStatement
y nonVarVariableStatement
). Su oyente, vamos a llamar es VarListener
hará un seguimiento de todos los ámbitos ANTLR camina por el árbol.
objectLiteralEntry
poco la regla 1 (agregué objectLiteralEntry
):
objectLiteral : ''{'' (objectLiteralEntry ('','' objectLiteralEntry)*)? ''}'' ; objectLiteralEntry : Identifier ''='' expression ;
lo que hace la vida más fácil en la siguiente demostración:
VarListener.java
public class VarListener extends CfscriptBaseListener {
private Stack<Scope> scopes;
public VarListener() {
scopes = new Stack<Scope>();
scopes.push(new Scope(null));
}
@Override
public void enterVariableStatement(CfscriptParser.VariableStatementContext ctx) {
String varName = ctx.variableName().getText();
Scope scope = scopes.peek();
scope.add(varName);
}
@Override
public void enterNonVarVariableStatement(CfscriptParser.NonVarVariableStatementContext ctx) {
String varName = ctx.variableName().getText();
checkVarName(varName);
}
@Override
public void enterObjectLiteralEntry(CfscriptParser.ObjectLiteralEntryContext ctx) {
String varName = ctx.Identifier().getText();
checkVarName(varName);
}
@Override
public void enterFunctionDeclaration(CfscriptParser.FunctionDeclarationContext ctx) {
scopes.push(new Scope(scopes.peek()));
}
@Override
public void exitFunctionDeclaration(CfscriptParser.FunctionDeclarationContext ctx) {
scopes.pop();
}
private void checkVarName(String varName) {
Scope scope = scopes.peek();
if(scope.inScope(varName)) {
System.out.println("OK : " + varName);
}
else {
System.out.println("Oops : " + varName);
}
}
}
Un objeto Scope
podría ser tan simple como:
Scope.java
class Scope extends HashSet<String> {
final Scope parent;
public Scope(Scope parent) {
this.parent = parent;
}
boolean inScope(String varName) {
if(super.contains(varName)) {
return true;
}
return parent == null ? false : parent.inScope(varName);
}
}
Ahora, para probar todo esto, aquí hay una pequeña clase principal:
Main.java
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class Main {
public static void main(String[] args) throws Exception {
CfscriptLexer lexer = new CfscriptLexer(new ANTLRFileStream("Test.cfc"));
CfscriptParser parser = new CfscriptParser(new CommonTokenStream(lexer));
ParseTree tree = parser.component();
ParseTreeWalker.DEFAULT.walk(new VarListener(), tree);
}
}
Si ejecuta esta clase Main
, se imprimirá lo siguiente:
Oops : testing Oops : testingagain OK : test Oops : mystuff Oops : interior Oops : third Oops : other Oops : something
Sin lugar a dudas, esto no es exactamente lo que quieres y probablemente me haya ingeniado algunas reglas de alcance de Coldfusion. Pero creo que esto te dará una idea de cómo resolver tu problema correctamente. Creo que el código es bastante auto explicativo, pero si este no es el caso, no dude en pedir una aclaración.
HTH