简单的 ANTLR 语法不会消除标识符和文字的歧义

Simple ANTLR grammar not disambiguating identifiers and literals

我正在尝试匹配这个语法:

P = 100
require P
credit account:subaccount P

第一个是作业。第二个是“检查” P 是否真实。第三个是将 100 移动到 account:subaccount 的指令。问题是我写的语法认为第三行只是一个缺少等号的赋值。我不明白为什么。

program: (stmt NEWLINE)+;

stmt: require | entry;

require: 'require' filtrex;
entry: (CREDIT | DEBIT) JOURNAL filtrex (IF filtrex)? (LPARENHASH EXTID RPAREN)?;
assign: ID EQ filtrex;

filtrex: math;

math
   :  math (TIMES | DIV)  math
   |  math (PLUS | MINUS) math
   |  LPAREN math RPAREN
   |  (PLUS | MINUS)* atom
   ;

atom: NUMBER
   | ID
   ;

NUMBER
   : ('0' .. '9') + ('.' ('0' .. '9') +)?
   ;

fragment SIGN
   : ('+' | '-')
   ;

ID: [a-zA-Z]+[0-9a-zA-Z]*;
EQ: '=';
JOURNAL: [a-zA-Z:]+;
EXTID: [a-zA-Z0-9-]+;
COLON: ':';
CREDIT: 'credit';
DEBIT: 'debit';
IF: 'if';
NEWLINE : [\r\n];
NUM     : [0-9.]+;
LPAREN: '(';
RPAREN: ')';
LPARENHASH: '(#';
PLUS: '+';
MINUS: '-';
TIMES: '*';
DIV: '/' ;
POINT: '.';
WS: [ \r\n\t] + -> skip;

更新 多亏了下面的建议,我有一些似乎可以正常工作的东西。现在来实现逻辑...

grammar Txl;

// High level language
program: stmt (NEWLINE stmt)* NEWLINE? EOF;

stmt: require | entry | assignment;

require: 'require' expr;
entry: (CREDIT | DEBIT) journal expr (IF expr)? (LPAREN 'id:' EXTID RPAREN)?;
assignment: IDENT ASSIGN expr;

journal: IDENT COLON IDENT;

expr: expr MULT expr
    | expr DIV expr
    | expr PLUS expr
    | expr MINUS expr
    | expr MOD expr
    | expr POW expr
    | MINUS expr
    | expr AND expr
    | expr OR expr
    | NOT expr
    | expr EQ expr
    | expr NEQ expr
    | expr LTE expr
    | expr LT expr
    | expr GTE expr
    | expr GT expr
    | expr QUESTION expr COLON expr
    | LPAREN expr RPAREN
    | NUMBER
    | IDENT LPAREN args RPAREN
    | IDENT
    ;

fnArg: expr | journal;

args: fnArg
    | fnArg COMMA fnArg
    |
    ;

// Reserved words
CREDIT: 'credit';
DEBIT: 'debit';
IF: 'if';
REQUIRE: 'require';

// Operators
MULT: '*';
DIV: '/';
MINUS: '-';
PLUS: '+';
POW: '^';
MOD: '%';
LPAREN: '(';
RPAREN: ')';
LBRACE: '[';
RBRACE: ']';
COMMA: ',';
EQ: '==';
NEQ: '!=';
GTE: '>=';
LTE: '<=';
GT: '>';
LT: '<';
ASSIGN: '=';
QUESTION: '?';
COLON: ':';
AND: 'and';
OR: 'or';
NOT: 'not';
HASH: '#';
NEWLINE : [\r\n];
WS: [ \t] + -> skip;

// Entities
NUMBER: ('0' .. '9') + ('.' ('0' .. '9') +)?;
IDENT: [a-zA-Z]+[0-9a-zA-Z]*;
EXTID: [a-zA-Z0-9-]+;

那是因为输入 credit 没有被您的 CREDIT 规则匹配,而是被 ID 规则匹配。词法分析器总是尝试匹配尽可能多的字符。因此,输入 credit 可以匹配为:IDJOURNALEXTIDCREDIT。每当发生多个规则可以匹配相同字符时,第一个定义的规则“获胜”(在这种情况下为 ID)。词法分析器不“监听”解析器试图匹配的内容,它独立于解析器运行。

注意 EXTID 也会导致输入 - 被它匹配,导致 MINUS 规则永远不会被匹配。

解决方案:将关键字 放在语法 ID 规则 之前:

CREDIT     : 'credit';
DEBIT      : 'debit';
REQUIRE    : 'require';
ID         : [a-zA-Z]+ [0-9a-zA-Z]*;

而且,如果可能的话,我还会删除 JOURNALEXTID 词法分析器规则,并尝试将它们“提升”为解析器规则:

journal
 : ID COLON ID
 ;

extid
 : ID (MINUS ID)*
 ;

NUMBERNUM也可以匹配相同的,而NUM也可以匹配1........2.......22222...这样的输入。我会删除 NUM 规则,只保留 NUMBER.

WS: [ \r\n\t] + -> skip; 中删除 \r\n 部分,因为它们已经与您的 NEWLINE 规则匹配。

通过执行 (stmt NEWLINE)+,每个 stmt 必须以新行结束(也是最后一行)。这可能是更好的解决方案:stmt (NEWLINE stmt)* NEWLINE?.

语法可能如下所示:

program
 : stmt (NEWLINE stmt)* NEWLINE? EOF
 ;

stmt
 : require
 | entry
 | assign
 ;

require
 : REQUIRE filtrex
 ;

entry
 : (CREDIT | DEBIT) journal filtrex (IF filtrex)? (LPARENHASH extid RPAREN)?
 ;

assign
 : ID EQ filtrex
 ;

journal
 : ID COLON ID
 ;

extid
 : ID (MINUS ID)*
 ;

filtrex
 : math
 ;

math
 : math (TIMES | DIV)  math
 | math (PLUS | MINUS) math
 | LPAREN math RPAREN
 | (PLUS | MINUS)* atom
 ;

atom
 : NUMBER
 | ID
 ;

NUMBER     : [0-9]+ ('.' [0-9]+)?;
CREDIT     : 'credit';
DEBIT      : 'debit';
REQUIRE    : 'require';
IF         : 'if';
ID         : [a-zA-Z]+ [0-9a-zA-Z]*;
EQ         : '=';
COLON      : ':';
NEWLINE    : [\r\n];
LPAREN     : '(';
RPAREN     : ')';
LPARENHASH : '(#';
PLUS       : '+';
MINUS      : '-';
TIMES      : '*';
DIV        : '/' ;
POINT      : '.';
WS         : [ \t] + -> skip;

它将像这样解析您的示例输入: