Tree パーサーを使用する必要があると思いますが、StringTemplate と統合する方法がわかりません。
StringTemplate をツリー パーサーに統合することは、基本的にそれをトークン パーサーに統合することと同じです。output
オプションを として定義しtemplate
、それに応じてルール生成を記述します。
以下は、出力にテンプレートを使用する小さなツリー文法です。この文法と以前の回答で説明した文法との唯一の意味のある違いは、これがトークンではなくツリー ノードで動作することです。テンプレートは同じように機能します。
tree grammar AstToTemplateParser;
options {
output = template;
tokenVocab = JavaLikeToAst;
ASTLabelType = CommonTree;
}
program
: ^(PROGRAM decls+=decl+) -> write(text={$decls})
;
decl
: ^(DECL ID ^(op args+=arg*)) -> assign(name={$ID.text}, op={$op.st}, args={$args})
| ^(DECL ID ^(CALL method args+=arg*)) -> assign(name={$ID.text}, op={$method.st}, args={$args})
;
arg
: ID -> write(text={$ID.text})
| INT -> write(text={$INT.text})
;
method
: ID -> op(name={$ID.text})
;
op : STAR -> op(name={$STAR.text})
| DIV -> op(name={$DIV.text})
| PLUS -> op(name={$PLUS.text})
| MINUS -> op(name={$MINUS.text})
;
さらに、x1、x2、x3 などの変数を追加していますが、この状況を処理する方法がわかりません。
それがキッカーです。私が知っている最も簡単な方法 (これはそれほど簡単ではありません) は、最初にトークン パーサーを使用してベースライン AST を生成し、次にツリー パーサーを使用して AST を宣言のリストにフラット化し、それぞれが元の入力からの式を表すことです -- x1
、x2
、およびx3
あなたの質問に。
中間段階は次のようになります。
元の入力
var a = (ln(b) + avg(c))*2
ベースラインAST
平坦化された AST
標準テンプレート適用
var var0 = log_N(b);
var var1 = average(c);
var var2 = add(var0, var1);
var a = multiply(var2, 2);
PLSQL テンプレートの適用
log_N(var0, b);
average(var1, c);
add(var2, var0, var1);
multiply(a, var2, 2);
これは、質問にあるものに似たベースライン AST を単純に生成するトークン パーサーの文法です。ここでは特に注目すべき点はなく、典型的な AST を生成するだけです。
grammar JavaLikeToAst;
options {
output = AST;
}
tokens {
PROGRAM; DECL; CALL;
}
compilationUnit : statement* EOF -> ^(PROGRAM statement*);
statement : decl;
decl : VAR ID EQ expr -> ^(DECL ID expr);
expr : add_expr;
add_expr : mul_expr ((PLUS|MINUS)^ mul_expr)*;
mul_expr : call_expr ((STAR|DIV)^ call_expr)*;
call_expr : ID LPAR arglist? RPAR -> ^(CALL ID arglist?)
| primary_expr;
arglist : expr (COMMA! expr)*;
primary_expr : ID | INT | LPAR! expr RPAR!;
VAR : 'var';
ID : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;
INT : ('0'..'9')+;
COMMA : ',';
SEMI : ';';
LCUR : '{';
RCUR : '}';
LPAR : '(';
RPAR : ')';
EQ : '=';
PLUS : '+';
MINUS : '-';
STAR : '*';
DIV : '/';
WS : (' '|'\t'|'\f'|'\r'|'\n'){skip();};
ここが醜いところです(少なくとも私が選んだ方法では;))。以下のツリー文法は、上から生成されたベースライン AST を、AST の式 (フラット化された AST) に対応する宣言のリストに変換します。この下で、文法の楽しい部分について説明します。
tree grammar AstToFlatAstParser;
options {
output = AST;
tokenVocab = JavaLikeToAst;
ASTLabelType = CommonTree;
filter = true;
}
@header {
import java.util.HashMap;
}
@members {
private int currentId = 0;
private HashMap<Integer, Object> exprs = new HashMap<Integer, Object>();
private boolean newDecls = false;
private int nextId() {
return currentId++;
}
private Object generateId(int id) {
return adaptor.create(ID, "var" + id);
}
private void saveExpr(int id, Object expr){
newDecls = true;
exprs.put(id, expr);
}
private Object buildNewDecls() {
Object newDecls = adaptor.nil();
for (int i = 0; i < currentId; ++i){
if (!exprs.containsKey(i)){
continue; //This id was generated but not used.
}
Object expr = exprs.get(i);
Object decl = adaptor.create(DECL, tokenNames[DECL]);
adaptor.addChild(decl, adaptor.create(ID, "var" + i));
adaptor.addChild(decl, expr);
adaptor.addChild(newDecls, decl);
}
exprs.clear();
return newDecls;
}
}
bottomup
: exit_program
| exit_op
;
exit_op
@init {
int myId = nextId();
}
: ^(binary_op reduced reduced)
{$start.parent != null && $start.parent.getType() != DECL}?
{saveExpr(myId, $start);}
-> {generateId(myId)}
| ^(CALL ID .*)
{$start.parent != null && $start.parent.getType() != DECL}?
{saveExpr(myId, $start);}
-> {generateId(myId)}
;
binary_op : STAR | DIV | PLUS | MINUS;
reduced : ID | INT;
exit_program
//Only rebuild PROGRAM if a new declaration is going to be built, that is, when "newDecls" is true.
//Otherwise PROGRAM is considered changed when it isn't and the processing never ends.
: {newDecls}? ^(PROGRAM old+=.*) {newDecls = false;}
-> ^(PROGRAM {buildNewDecls()} $old*)
;
まず、文法はほとんどが Java コードであることに注意してください。パーサー規則は 5 つしかなく、そのほとんどは単純です。これはfilter
ツリー文法なので、規則bottomup
とtopdown
はエントリ ポイントです。この場合のみbottomup
が必要なので、topdown
指定されていません。出力ツリーが変更されなくなるまでルールbottomup
が繰り返されます。これは、生成する宣言がなくなり、ツリーが完全に平坦化されることを意味します。
exit_program
次に、新しい宣言が AST に書き出される場所がルールであることに注意してください。セマンティック述語 ( {newDecls}?
) を使用してPROGRAM
、新しい宣言が追加されたときにのみ変更されるようにしています。bottomup
変更が行われなくなるまで呼び出されると言ったことを覚えていますか? このセマンティック述語がなければ、exit_program
は常に変更PROGRAM
され、ツリーの解析は決して処理を停止しませんbottomup
。これは、この特殊なケースの大雑把な回避策ですが、機能します。PROGRAM
新しい宣言は、参照される前に確実に表示されるように、の先頭に挿入されます。x1
10行も予定通りに定義してはダメです。
3 番目に、ルールが式 ( など) を宣言 ( など) にexit_op
置き換えることに注意してください。次のいずれかに該当する場合、式は置き換えられます。ln(b)
var0
式は、オペランドが両方とも「縮小」されている (つまり、両方とも整数または変数 ID である) 二項演算であり、DECL
ノードの子ではありません。は宣言の子であるvar a = 1 + 2
ため変更されません。は、2 つの「縮小された」オペランドがあり、ノードの子ではない ( in の子である)ため、変更されます。1 + 2
var b = a + (2 + c)
(2 + c)
DECL
+
a + ...
式は、ノードCALL
の子ではないです。は変更されていませんが、 の子であるため変更されています。DECL
var a = ln(b)
var a = ln(b) + 3
ln(b)
+
exprs
式はID に置き換えられる前に格納されます。exit_program
ルールが を呼び出すと、ルールで再構成されますbuildNewDecls
。buildNewDecls
パーサーの組み込みTreeAdaptor
メンバー (という名前adaptor
) を使用DECL
して、フラット化された AST に表示されるノードを生成するだけです。アダプター メソッドの Javadoc は、呼び出しが何をするかを説明する適切な仕事をするので、詳細には触れません。
警告:上記の文法によって生成されたパーサーは、提示された非常に限られたケースでうまく機能します。より広範なシナリオに適用した場合にどのようなエラーが発生するかはわかりません。
2 番目の問題は、目的の言語の 1 つが plsql のような言語であることです。この場合、すべての出力変数がカーソルになり、それらを関数に渡すようにする必要があります...
上記のように、AST が単なる宣言のフラット リストである場合、テンプレートが管理できるものになります。
フラット化された AST をテンプレート ベースのツリー パーサー (上の図のようなパーサー) に渡して、リストしたようなさまざまなテキスト バージョンを生成します。この場合、テンプレートは宣言のすべての部分 (変数名、操作/メソッド名、およびオペランド/引数) を受け入れ、使用するテンプレートに応じてvariable = method(arg0, arg1)
またはのようなテキストを生成します。method(variable, arg0, arg1)
重要なのは、入力がフラットであることと、テンプレートが宣言に関連するすべてを受け取ることです。
以下は、すべてを結び付けるテスト アプリケーションです。
JavaLikeToAstTest.java
public class JavaLikeToAstTest {
public static void main(String[] args) throws Exception {
final String code = "var a = (ln(b) + avg(c))*2";
CharStream input = new ANTLRStringStream(code);
JavaLikeToAstLexer lexer = new JavaLikeToAstLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
JavaLikeToAstParser parser = new JavaLikeToAstParser(tokens);
JavaLikeToAstParser.compilationUnit_return result = parser
.compilationUnit();
if (lexer.getNumberOfSyntaxErrors() > 0 || parser.getNumberOfSyntaxErrors() > 0) {
throw new Exception("Syntax Errors encountered!");
}
CommonTree tree = (CommonTree) result.tree;
System.out.printf("Baseline AST: %s%n%n", tree.toStringTree());
tree = flatten(tree);
System.out.printf("Flattened AST: %s%n%n", tree.toStringTree());
translate(tree, "AstToPlsql.stg");
translate(tree, "AstToGlobal.stg");
}
private static CommonTree flatten(CommonTree tree) {
AstToFlatAstParser parser = new AstToFlatAstParser(
new CommonTreeNodeStream(tree));
return (CommonTree) parser.downup(tree, true);
}
private static void translate(CommonTree tree, String templateResourceName)
throws Exception {
AstToTemplateParser parser = new AstToTemplateParser(
new CommonTreeNodeStream(tree));
InputStream stream = JavaLikeToTemplateTest.class
.getResourceAsStream(templateResourceName);
Reader reader = new InputStreamReader(stream);
parser.setTemplateLib(new StringTemplateGroup(reader));
reader.close();
stream.close();
System.out.printf("Result for %s%n%n%s%n%n", templateResourceName,
parser.program().st.toString());
}
翻訳プロセスを処理する 2 つの単純な StringTemplate グループ ファイルを次に示します。
AstToGlobal.stg
group AstToGlobal;
methods ::= ["*":"multiply", "/":"divide", "+":"add", "-":"subtract", "avg":"average", "ln":"log_N", default:key]
assign(name, op, args) ::= <<var <name> = <op>(<args;separator=", ">) >>
op(name) ::= "<methods.(name)>"
write(text) ::= << <text;separator="\n"> >>
AsToPlsql.stg
group AstToPlsql;
methods ::= ["*":"multiply", "/":"divide", "+":"add", "-":"subtract", "avg":"average", "ln":"log_N", default:key]
assign(name, op, args) ::=<< <op>(<name>, <args;separator=", ">) >>
op(name) ::= "<methods.(name)>"
write(text) ::= << <text;separator="\n"> >>
アプリケーションは次の出力を生成します。
Baseline AST: (PROGRAM (DECL a (* (+ (CALL ln b) (CALL avg c)) 2)))
(CALL ln b) -> var0
(CALL avg c) -> var1
(+ var0 var1) -> var2
(PROGRAM (DECL a (* var2 2))) -> (PROGRAM (DECL var0 (CALL ln b)) (DECL var1 (CALL avg c)) (DECL var2 (+ var0 var1)) (DECL a (* var2 2)))
Flattened AST: (PROGRAM (DECL var0 (CALL ln b)) (DECL var1 (CALL avg c)) (DECL var2 (+ var0 var1)) (DECL a (* var2 2)))
Result for AstToPlsql.stg
log_N(var0, b )
average(var1, c )
add(var2, var0 , var1 )
multiply(a, var2 , 2 )
Result for AstToGlobal.stg
var var0 = log_N(b )
var var1 = average(c )
var var2 = add(var0 , var1 )
var a = multiply(var2 , 2 )
AstToTemplate.g
またはテンプレートには、 のような単純な割り当てを処理するコードはありませんがvar a = 3
、op/メソッドの割り当てをガイドとして使用して、それを処理するコードを追加するのは簡単だと思います。