目標
Coldfusion CFscript用のVarscoperを作成するプロジェクトに取り組んでいます。基本的に、これは、ソースコードファイルをチェックして、開発者が変数を適切にvar
したことを確認することを意味します。
ANTLR V4で2日間作業した後、GUIビューに非常にナイスな解析ツリーを生成する文法があります。次に、そのツリーを使用して、ノードをプログラムでクロールして変数宣言を探し、それらが関数内にある場合は適切なスコープが設定されていることを確認する方法が必要です。可能であれば、文法ファイルではこれを行わないでください。言語の定義とこの特定のタスクを混在させる必要があるためです。
私が試したこと
私の最新の試みはParserRuleContext
を使用しており、それをchildren
経由でgetPayload()
を介して試みました。 getPayLoad()
のクラスを確認した後、ParserRuleContext
オブジェクトまたはToken
オブジェクトのいずれかを取得します。残念ながら、それを使用して、特定のノードの実際のルールタイプを取得する方法を見つけることはできませんでした。テキストのみが含まれています。各ノードのルールタイプは、そのテキストノードが無視される右辺の式、変数の割り当て、または関数の宣言であるかどうかが重要であるため必要です。
質問
これが私のサンプルです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";
}
}
もし私があなただったら、私はこれを手動で歩かなかったでしょう。レクサーとパーサーを生成した後、ANTLRはCfscriptBaseListener
というファイルも生成します。このファイルには、すべてのパーサールールに対して空のメソッドがあります。 ANTLRにツリーをウォークさせて、関心のあるメソッド/ルールのみをオーバーライドするカスタムツリーリスナーをアタッチできます。
あなたのケースでは、(新しいスコープを作成するために)新しい関数が作成されるたびに通知を受け取る必要があり、変数の割り当て(variableStatement
およびnonVarVariableStatement
)に興味がある可能性があります。リスナーはVarListener
を呼び出しましょう。ANTLRがツリーをウォークするときにすべてのスコープを追跡します。
私は1つのルールを少し変更しました(objectLiteralEntry
を追加しました):
objectLiteral : '{'(objectLiteralEntry( '、' objectLiteralEntry)*)? '}' ; objectLiteralEntry :識別子 '='式 ;
これは、次のデモで人生を楽にします:
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
オブジェクトは、次のように単純にすることができます。
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);
}
}
これをすべてテストするために、小さなメインクラスを次に示します。
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