106

ANTLR中的语义谓词是什么?

4

2 回答 2

180

蚂蚁4

对于 ANTLR 4 中的谓词,请查看以下堆栈溢出问答:


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

测试

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

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

通过生成词法分析器和解析器、编译所有.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 主要

这样做时,控制台不会打印任何内容,这表明没有出现任何问题。尝试改变:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7   , 89");

进入:

ANTLRStringStream in = new ANTLRStringStream("123, 456, 7777   , 89");

并再次进行测试:您将在控制台上看到一个错误出现在 string 之后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
  ;

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


1. 验证语义谓词

验证语义谓词只不过是一段代码,后跟一个问号:

RULE { /* a boolean expression in here */ }?

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

number
@init { int N = 0; }
  :  (Digit { N++; } )+ { N <= 10 }?
  ;

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

使用以下命令对其进行测试ANTLRStringStream

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

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

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");

2. 门控语义谓词

门控语义谓词类似于验证语义谓词,只有门控版本会产生语法错误而不是FailedPredicateException.

门控语义谓词的语法是:

{ /* a boolean expression in here */ }?=> RULE

要改为使用门控谓词来匹配长达 10 位的数字来解决上述问题,您可以编写:

number
@init { int N = 1; }
  :  ( { N <= 10 }?=> Digit { N++; } )+
  ;

用两者再次测试:

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

和:

// '12345678901' is more than 10 digits
ANTLRStringStream in = new ANTLRStringStream("1,23,12345678901");

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


3. 消除语义谓词歧义

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

假设示例语法创建Number将匹配 0..999 范围内的数字的标记(词法分析器规则而不是解析器规则)。现在在解析器中,您想要区分低数字和高数字(低:0..500,高:501..999)。这可以使用消除歧义的语义谓词来完成,您可以在流中检查下一个标记 ( 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();}
  ;

如果您现在解析 string "123, 999, 456, 700, 89, 0",您将看到以下输出:

low  = 123
high = 999
low  = 456
high = 700
low  = 89
low  = 0
于 2010-06-16T19:28:11.487 回答
12

我一直使用 wincent.com 上对ANTLR 谓词的简洁引用作为我的指南。

于 2010-06-16T19:40:36.703 回答