与其将字符串存储为值,不如将实际的 AST 存储在您的地图中?然后可以通过在重写规则中将它们包装在{
...中来注入这些 AST。}
一个演示:
grammar T;
options {
output=AST;
ASTLabelType=CommonTree;
}
tokens {
STATS;
DISPLAYED_COLUMN;
NAME;
SELECT;
}
@parser::header {
import java.util.Map;
import java.util.HashMap;
}
@parser::members {
private Map<String, CommonTree> aliases = new HashMap<String, CommonTree>();
}
parse
: (stmt ';')+ EOF -> ^(STATS stmt+)
;
stmt
: set_stmt
| select_stmt
;
set_stmt
: 'set' 'alias' name Id {aliases.put($Id.text, $name.tree);} -> /* AST can be omitted */
;
select_stmt
: 'select' displayed_column 'from' name -> ^(SELECT displayed_column name)
;
displayed_column
: sql_expression -> {aliases.containsKey($text)}? ^(DISPLAYED_COLUMN {aliases.get($text)})
-> ^(DISPLAYED_COLUMN sql_expression)
;
sql_expression
: term (('+' | '-')^ term)*
;
term
: factor (('*' | '/')^ factor)*
;
factor
: Num
| name
| '(' sql_expression ')'
;
name
: Id ('.' Id)* -> ^(NAME Id+)
;
Id : 'a'..'z'+;
Num : '0'..'9'+;
Space : (' ' | '\t' | '\r' | '\n')+ {skip();};
解析输入:
从池中选择 d;
设置别名 abc d;
从池中选择 d;
将导致以下 AST:
编辑
谢谢巴特!唯一的事情是我需要将这些首选项保存在数据存储中,这样用户就不需要再次重新输入它们,希望我可以序列化 CommonTree。
:( 唉,它不是可序列化的。
在这种情况下,您可以将值存储为字符串,并使用小型辅助方法动态创建 AST,createNameAST(String alias)
并注入此方法创建的 AST:
grammar T;
options {
output=AST;
ASTLabelType=CommonTree;
}
tokens {
STATS;
DISPLAYED_COLUMN;
NAME;
SELECT;
}
@parser::header {
import java.util.Map;
import java.util.HashMap;
}
@parser::members {
private Map<String, String> aliases = new HashMap<String, String>();
private CommonTree createNameAST(String alias) {
try {
TLexer lexer = new TLexer(new ANTLRStringStream(aliases.get(alias)));
TParser parser = new TParser(new CommonTokenStream(lexer));
return (CommonTree)parser.name().getTree();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}
parse
: (stmt ';')+ EOF -> ^(STATS stmt+)
;
stmt
: set_stmt
| select_stmt
;
set_stmt
: 'set' 'alias' name Id {aliases.put($Id.text, $name.text);} -> /* AST can be omitted */
;
select_stmt
: 'select' displayed_column 'from' name -> ^(SELECT displayed_column name)
;
displayed_column
: sql_expression -> {aliases.containsKey($text)}? ^(DISPLAYED_COLUMN {createNameAST($text)})
-> ^(DISPLAYED_COLUMN sql_expression)
;
sql_expression
: term (('+' | '-')^ term)*
;
term
: factor (('*' | '/')^ factor)*
;
factor
: Num
| name
| '(' sql_expression ')'
;
name
: Id ('.' Id)* -> ^(NAME Id+)
;
Id : 'a'..'z'+;
Num : '0'..'9'+;
Space : (' ' | '\t' | '\r' | '\n')+ {skip();};
如果您使用的是 ANTLRWorks 的调试器:该方法可能存在问题,createNameAST
因为它使用TParser
. 手动创建一个小测试用例:
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 src =
"select d from pool; \n" +
"set alias a.b.c.x d; \n" +
"select d from pool;";
TLexer lexer = new TLexer(new ANTLRStringStream(src));
TParser parser = new TParser(new CommonTokenStream(lexer));
CommonTree tree = (CommonTree)parser.parse().getTree();
DOTTreeGenerator gen = new DOTTreeGenerator();
StringTemplate st = gen.toDOT(tree);
System.out.println(st);
}
}
并在命令行上运行这一切:
java -cp antlr-3.3.jar org.antlr.Tool Tg
javac -cp antlr-3.3.jar *.java
java -cp .:antlr-3.3.jar Main > ast.dot
你会得到一个 DOT 文件,它代表之前发布的相同 AST。