什么是ANTLR中的"语义谓词"?

Bar*_*ers 100 antlr antlr3 antlr4

ANTLR中的语义谓词是什么?

Bar*_*ers 161

ANTLR 4

对于ANTLR 4中的谓词,检查这些堆栈溢出问答:


ANTLR 3

语义谓词是执行在使用时的明码语法操作的额外(语义)规则的方法.

语义谓词有3种类型:

  • 验证语义谓词;
  • 门控语义谓词;
  • 消除语义谓词的歧义.

示例语法

假设你有一个文本块,只包含用逗号分隔的数字,忽略任何空格.您想要解析此输入,确保数字最多为3位"长"(最多999).以下语法(Numbers.g)会做这样的事情:

grammar Numbers;

// entry point of this parser: it parses an input string consisting of at least 
// one number, optionally followed by zero or more comma's and numbers
parse
  :  number (',' number)* EOF
  ;

// matches a number that is between 1 and 3 digits long
number
  :  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

// matches a single digit
Digit
  :  '0'..'9'
  ;

// ignore spaces
WhiteSpace
  :  (' ' | '\t' | '\r' | '\n') {skip();}
  ;
Run Code Online (Sandbox Code Playgroud)

测试

可以使用以下类测试语法:

import org.antlr.runtime.*;

public class Main {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");
        NumbersLexer lexer = new NumbersLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        NumbersParser parser = new NumbersParser(tokens);
        parser.parse();
    }
}
Run Code Online (Sandbox Code Playgroud)

通过生成词法分析器和解析器来测试它,编译所有.java文件并运行Main该类:

java -cp antlr-3.2.jar org.antlr.Tool Numbers.g
javac -cp antlr-3.2.jar *.java
java -cp .:antlr-3.2.jar Main

执行此操作时,控制台上不会打印任何内容,这表示没有任何问题.尝试改变:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");
Run Code Online (Sandbox Code Playgroud)

成:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7777   , 89");
Run Code Online (Sandbox Code Playgroud)

并再次进行测试:您将在字符串后面的控制台上看到错误777.


语义谓词

这将我们带到语义谓词.假设你要解析1到10位数之间的数字.像这样的规则:

number
  :  Digit Digit Digit Digit Digit Digit Digit Digit Digit Digit
  |  Digit Digit Digit Digit Digit Digit Digit Digit Digit
     /* ... */
  |  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;
Run Code Online (Sandbox Code Playgroud)

会变得很麻烦.语义谓词可以帮助简化这种类型的规则.


1.验证语义谓词

一个验证语义谓词无非是后跟一个问号的代码块更多:

RULE { /* a boolean expression in here */ }?
Run Code Online (Sandbox Code Playgroud)

要使用验证 语义谓词解决上述问题number,请将语法中的规则更改为:

number
@init { int N = 0; }
  :  (Digit { N++; } )+ { N <= 10 }?
  ;
Run Code Online (Sandbox Code Playgroud)

部分{ int N = 0; }{ N++; }是纯Java语句其中第一种是当解析器"进入"初始化number规则.实际谓词是:{ N <= 10 }?,这导致解析器在FailedPredicateException 数字长度超过10位时抛出 .

使用以下方法测试它ANTLRStringStream:

// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890"); 
Run Code Online (Sandbox Code Playgroud)

这不会产生异常,而以下情况会产生异常:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");
Run Code Online (Sandbox Code Playgroud)

2.门控语义谓词

门控语义谓词类似于验证语义谓词,只有门控版本产生的一个语法错误代替FailedPredicateException.

门控语义谓词的语法是:

{ /* a boolean expression in here */ }?=> RULE
Run Code Online (Sandbox Code Playgroud)

为了解决上述问题,使用gated谓词来匹配长达10位数的数字,你会写:

number
@init { int N = 1; }
  :  ( { N <= 10 }?=> Digit { N++; } )+
  ;
Run Code Online (Sandbox Code Playgroud)

再次测试它们:

// all equal or less than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,1234567890"); 
Run Code Online (Sandbox Code Playgroud)

和:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");
Run Code Online (Sandbox Code Playgroud)

你会看到最后一个会抛出一个错误.


3.消除语义谓词的歧义

谓词的最终类型是消除歧义的语义谓词,它看起来有点像验证谓词({boolean-expression}?),但更像是门控语义谓词(当布尔表达式求值时不会抛出异常false).您可以在规则的开头使用它来检查规则的某些属性,并让解析器匹配所述规则.

假设示例语法创建Number令牌(词法分析器规则而不是解析器规则),它将匹配0..999范围内的数字.现在在解析器中,您希望区分低数字和高数字(低:0..500,高:501..999).这可以使用消除歧义的语义谓词来完成,您可以在其中检查stream(input.LT(1))中的下一个令牌,以检查它是低还是高.

演示:

grammar Numbers;

parse
  :  atom (',' atom)* EOF
  ;

atom
  :  low  {System.out.println("low  = " + $low.text);}
  |  high {System.out.println("high = " + $high.text);}
  ;

low
  :  {Integer.valueOf(input.LT(1).getText()) <= 500}? Number
  ;

high
  :  Number
  ;

Number
  :  Digit Digit Digit
  |  Digit Digit
  |  Digit
  ;

fragment Digit
  :  '0'..'9'
  ;

WhiteSpace
  :  (' ' | '\t' | '\r' | '\n') {skip();}
  ;
Run Code Online (Sandbox Code Playgroud)

如果你现在解析字符串"123, 999, 456, 700, 89, 0",你会看到以下输出:

low  = 123
high = 999
low  = 456
high = 700
low  = 89
low  = 0
Run Code Online (Sandbox Code Playgroud)

  • 男人,你真的应该考虑写一个ANTLR的初学者指南:P (11认同)
  • @Bart Kiers:请写一本关于ANTLR的书 (5认同)
  • 对于ANTLR v4,`input.LT(1)`现在是`getCurrentToken()`:-) (2认同)

Kal*_*son 11

我总是在wincent.com上使用对ANTLR谓词的简洁引用作为我的向导.

  • 是的,一个很好的链接!但是,正如你所提到的,对于一个(相对)新的ANTLR来说可能有点困难.我只希望我的回答(有点)对ANTLR草料斗更友好.:) (5认同)