Antlr语义谓词

时间:2013-01-07 18:00:38

标签: java antlr antlr3

我正在尝试获取语义谓词。这似乎是直截了当但不知何故不起作用,基于一个布尔条件,我需要执行一个规则(吐出一个AST)或只是或手动构建一个

下面的

是解析器规则。

displayed_column
  :   
    {columnAliases.containsKey($text)}? 
    =>-> ^(PROPERTY_LIST ^(PROPERTY IDENTIFIER[columnAliases.get($text)])) 
  | sql_expression
  ;

我也尝试了所有的gated和歧义,但在运行代码时,它总是转到第二个规则(sql_expression)。

有人可以帮帮我吗?

由于

修改 我刚刚意识到$ text是空的,而谓词正在运行,这就是为什么它总是匹配第二个规则。我将规则更改为此并且可以正常工作

displayed_column
  :
        sql_expression
        -> {columnAliases.containsKey($text)}? ^(PROPERTY_LIST ^(PROPERTY IDENTIFIER[columnAliases.get($text)])) 
        -> sql_expression

然而,我现在遇到了一个不同的问题,我意识到手动构建树将无法工作,我需要使用新文本(columnAliases Map中的值)重新运行规则displayed_column,这可能吗?

这是我原来的问题 https://stackoverflow.com/questions/14170541/antlr-dynamic-input-stream-modification-during-parsing

基本上我试图以交互方式解析和解释sql之类的语句 例如:

select a.b.c from pool;
select min(abc.def[*]) from pool;

由于列名可能有点长,我已经给用户优先选择别名列名(通过不同的命令),例如用户可能设置首选项然后运行他的命令

set column_alias a.b.c d;
select d from pool;

现在,在解析时,我将首选项(Map)注入到生成的解析器中,我试图将新列替换/映射回原始列,然后继续解释。 在解析器中处理它似乎是我唯一的选择,因为我认为由于该列跨越多个规则而很难用树语法。

我可以发布整个语法,但它有点太长了,这里是缩小版的

select_stmt:
  : 'select' displayed_column 'from' pool
  ;

displayed_column
  : sql_expression 
  ;

sql_expression
  : term ( (PLUS^ | MINUS^) term)*
  ;

term  : factor ( (ASTERISK^ | DIVIDE^) factor)*
  ;

... <more_rules> ...

我坚持这一点,使用字符串模板输出翻译的语句,然后重新分析似乎是我唯一的选择,但这需要重写整个语法到输出模板(现在我有一个组合语法与输出AST和解释它的树语法)。 如果有人能告诉我哪种方式不那么具有干扰性,我们将不胜感激。

再次感谢。

1 个答案:

答案 0 :(得分:4)

为什么不将实际的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();};

解析输入:

select d from pool;
set alias a.b.c d;
select d from pool;

将导致以下AST:

enter image description here

修改

  

感谢巴特!唯一的问题是我需要在数据存储中保留这些首选项,以便用户不需要再次重新输入它们,希望我能序列化CommonTree。

:(唉,它不是Serializable。

在这种情况下,您可以将值存储为字符串,并使用小辅助方法createNameAST(String alias)动态创建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, 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 T.g 
javac -cp antlr-3.3.jar *.java
java -cp .:antlr-3.3.jar Main > ast.dot

你会得到一个代表之前发布的相同AST的DOT文件。