35

ゴール

私はColdfusionCFscript用のVarscoperを作成するプロジェクトに取り組んでいます。var基本的に、これは、開発者が変数を適切に作成したことを確認するためにソースコードファイルをチェックすることを意味します。

ANTLR V4を数日間使用した後、GUIビューで非常に優れた解析ツリーを生成する文法ができました。ここで、そのツリーを使用して、プログラムでノードを上下にクロールして変数宣言を探し、それらが関数内にある場合は適切なスコープを持っていることを確認する方法が必要です。可能であれば、言語の定義をこの特定のタスクと混合する必要があるため、文法ファイルでこれを行わない方がよいでしょう。

私が試したこと

私の最近の試みは、を使用して、を介しParserRuleContextてそれを通過しようとしたことでした。私のクラスをチェックした後、オブジェクトまたはオブジェクトのいずれかがあります。残念ながら、それを使用すると、特定のノードの実際のルールタイプを取得する方法を見つけることができず、テキストが含まれているだけでした。各ノードのルールタイプは、そのテキストノードが無視される右辺式であるか、変数の割り当てであるか、関数宣言であるかが重要であるため、必要です。childrengetPayload()getPayLoad()ParserRuleContextToken

質問

  1. 私はANTLRに非常に慣れていませんが、これは正しいアプローチですか、それともツリーをトラバースするためのより良い方法がありますか?

これが私のサンプルJavaコードです:

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(テストコードファイル)

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";
    }
}
4

1 に答える 1

48

私があなたなら、これを手動で歩くことはありません。レクサーとパーサーを生成した後、ANTLRはCfscriptBaseListener 、すべてのパーサールールに対して空のメソッドを持つというファイルも生成します。ANTLRにツリーをウォークさせ、関心のあるメソッド/ルールのみをオーバーライドするカスタムツリーリスナーをアタッチすることができます。

あなたの場合、(新しいスコープを作成するために)新しい関数が作成されるたびに通知を受け取りたいと思うでしょうし、変数の割り当て(variableStatementnonVarVariableStatement)に興味があるでしょう。あなたのリスナー、呼び出しましょうはVarListener、ANTLRがツリーを歩くときにすべてのスコープを追跡します。

1つのルールを少し変更しました(追加しましたobjectLiteralEntry):

objectLiteral
    :'{'(objectLiteralEntry('、' objectLiteralEntry)*)?'}'
    ;

objectLiteralEntry
    :識別子'='式
    ;
    

これにより、次のデモでの作業が楽になります。

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

Scopeオブジェクトは次のように単純にすることができます。

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

さて、これをすべてテストするために、ここに小さなメインクラスがあります:

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

このMainクラスを実行すると、次のように出力されます。

おっと:テスト
おっと:もう一度テストします
OK:テスト
おっと:mystuff
おっと:インテリア
おっと:3番目
おっと:その他
おっと:何か

間違いなく、これはあなたが望んでいることではなく、おそらくColdfusionのいくつかのスコープルールを間違えました。しかし、これにより、問題を適切に解決する方法についての洞察が得られると思います。コードはかなり自明だと思いますが、そうでない場合は、遠慮なく説明を求めてください。

HTH

于 2013-02-24T11:15:51.680 に答える