如何使用ANTLR区分关键字类关键字和关键字?

时间:2011-08-04 02:09:25

标签: antlr grammar keyword

当语法允许非关键字与关键字具有类似的“外观”时,我无法区分关键字和非关键字。

这是语法:

grammar Query;

options {
  output = AST;
  backtrack = true;
}
tokens {
  DefaultBooleanNode;
}

// Parser

startExpression : expression EOF ;

expression : withinExpression ;

withinExpression
  : defaultBooleanExpression
    (WSLASH^ NUMBER defaultBooleanExpression)*

defaultBooleanExpression
  : (queryFragment   -> queryFragment)
    (e=queryFragment -> ^(DefaultBooleanNode $defaultBooleanExpression $e))*
  ;

queryFragment : unquotedQuery ;

unquotedQuery : UNQUOTED | NUMBER ;

// Lexer

WSLASH    : ('W'|'w') '/';

NUMBER    : Digit+ ('.' Digit+)? ;

UNQUOTED : UnquotedStartChar UnquotedChar* ;

fragment UnquotedStartChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
     | ':' | '"' | '/' | '(' | ')' | '[' | ']'
     | '{' | '}' | '-' | '+' | '~' | '&' | '|'
     | '!' | '^' | '?' | '*' )
  ;

fragment UnquotedChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
     | ':' | '"' | '(' | ')' | '[' | ']' | '{'
     | '}' | '~' | '&' | '|' | '!' | '^' | '?'
     | '*' )
  ;

fragment EscapeSequence
  : '\\'
    ( 'u' HexDigit HexDigit HexDigit HexDigit
    | ~( 'u' )
    )
  ;

fragment Digit : ('0'..'9') ;
fragment HexDigit : ('0'..'9' | 'a'..'f' | 'A'..'F') ;

WHITESPACE : ( ' ' | '\r' | '\t' | '\u000C' | '\n' ) { skip(); };

我已经将其简化为足以摆脱分心但我认为删除它会消除这个问题。

  • 允许在不带引号的查询片段中间使用斜杠。
  • 布尔查询尤其没有必需的关键字。
  • 正在引入新语法(例如W / 3),但我试图不影响看似相似的现有查询(例如X / Y)
  • 由于'/'作为单词的一部分有效,因此ANTLR似乎将“W / 3”作为UNQUOTED类型的单个标记,而不是WSLASH后跟一个NUMBER。
  • 由于上述原因,我最终得到了一个树:DefaultBooleanNode(DefaultBooleanNode(~first clause~,“W / 3”),〜second clause~),而我真正想要的是WSLASH(〜第一个子句〜 ,“3”,〜第二条〜)。

我想做的是以某种方式将UNQUOTED规则写成“我现在拥有的,但不匹配~~~~”,但我不知道如何做到这一点。

我意识到我可以完全拼写出来,例如:

  • 来自UnquotedStartChar的任何字符,除了'w',其次是规则的其余部分
  • 'w'后跟UnquotedChar中的任何字符,除了'/',然后是规则的其余部分
  • 'w /'后跟UnquotedChar中除数字
  • 之外的任何字符
  • ...

然而,这看起来很糟糕。 :)

1 个答案:

答案 0 :(得分:3)

当ANTLR生成的词法分析器“看到”某个输入可以匹配超过1个规则时,它会选择最长匹配。如果您希望较短的匹配优先,则需要将所有类似的规则合并为一个,然后检查gated sematic predicate是否提前匹配较短的匹配。如果提前较短的匹配,则更改令牌的类型。

演示:

Query.g

grammar Query;

tokens {
  WSlash;
}

@lexer::members {
  private boolean ahead(String text) {
    for(int i = 0; i < text.length(); i++) {
      if(input.LA(i + 1) != text.charAt(i)) {
        return false;
      }
    }
    return true;
  }
}

parse
  :  (t=. {System.out.printf("\%-10s \%s\n", tokenNames[$t.type], $t.text);} )* EOF
  ;

NUMBER
  :  Digit+ ('.' Digit+)? 
  ;

UNQUOTED 
  :  {ahead("W/")}?=> 'W/' { $type=WSlash; /* change the type of the token */ }
  |  {ahead("w/")}?=> 'w/' { $type=WSlash; /* change the type of the token */ }
  |  UnquotedStartChar UnquotedChar* 
  ;

fragment UnquotedStartChar
  :  EscapeSequence
  |  ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
      | ':' | '"' | '/' | '(' | ')' | '[' | ']'
      | '{' | '}' | '-' | '+' | '~' | '&' | '|'
      | '!' | '^' | '?' | '*' )
  ;

fragment UnquotedChar
  : EscapeSequence
  | ~( ' ' | '\r' | '\t' | '\u000C' | '\n' | '\\'
     | ':' | '"' | '(' | ')' | '[' | ']' | '{'
     | '}' | '~' | '&' | '|' | '!' | '^' | '?'
     | '*' )
  ;

fragment EscapeSequence
  :  '\\'
     ( 'u' HexDigit HexDigit HexDigit HexDigit
     | ~'u'
     )
  ;

fragment Digit    : '0'..'9';
fragment HexDigit : '0'..'9' | 'a'..'f' | 'A'..'F';

WHITESPACE : (' ' | '\r' | '\t' | '\u000C' | '\n') { skip(); };

Main.java

import org.antlr.runtime.*;

public class Main {
  public static void main(String[] args) throws Exception {
    QueryLexer lexer = new QueryLexer(new ANTLRStringStream("P/3 W/3"));
    QueryParser parser = new QueryParser(new CommonTokenStream(lexer));
    parser.parse();
  }
}

在* nix / MacOS上运行演示:

java -cp antlr-3.3.jar org.antlr.Tool Query.g
javac -cp antlr-3.3.jar *.java
java -cp .:antlr-3.3.jar Main

或在Windows上:

java -cp antlr-3.3.jar org.antlr.Tool Query.g
javac -cp antlr-3.3.jar *.java
java -cp .;antlr-3.3.jar Main

将打印以下内容:

UNQUOTED   P/3
WSlash     W/
NUMBER     3

修改

要在解析器规则中使用WSlash标记时消除警告,只需在语法中添加空片段规则:

 fragment WSlash : /* empty */ ;

这有点像黑客,但这就是它的完成方式。没有更多的警告。