2

我正在尝试让语义谓词起作用。这似乎很简单,但不知何故不起作用,基于布尔条件我需要执行一个规则(它会吐出一个 AST)或者只是或手动构建一个

下面是解析器规则。

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

我也尝试了所有门控和消除歧义,但是在运行代码时,它总是进入第二条规则(sql_expression)。

谁能帮帮我?

谢谢

编辑: 我刚刚意识到 $text 在谓词运行时为空,这就是为什么它总是匹配第二条规则。我将规则更改为此并且它有效

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

但是我现在遇到了一个不同的问题,我意识到手动构建树将不起作用,我需要使用新文本(来自 columnAliases Map 的值)再次重新运行规则显示列,这可能吗?

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

基本上我正在尝试以交互方式解析和解释 sql,如 statments ex:

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

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

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

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

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

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 和一个解释它的树语法)。如果有人能告诉我侵入性较小的方式,将不胜感激。

再次感谢。

4

1 回答 1

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

解析输入:

从池中选择 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。

于 2013-01-08T11:18:32.853 回答