ANTLR 解析不匹配令牌异常

1 投票
2 回答
2823 浏览
提问于 2025-04-16 19:37

我正在尝试为我自己写的一个简单语言编写一个解析器。这个语言是由后缀表达式组成的。目前,我在解析器上遇到了一些问题。当我用输入 2 2 * test >> 运行它时,出现了一个叫做 MismatchedTokenException 的错误。

另外,我该如何实现一个递归的后缀解析器呢?

这是我的代码:

grammar star;

options {
    language=Python;
    output=AST;
    ASTLabelType=CommonTree;
}

tokens {DECL;}
//start
//  :   decl ;
//decl
//  :   type ID -> ^(DECL type ID)
//  ;

program
    :   (body)+
    ;

body    :   (nested WS)*
    |   (var WS)*
    |   (get WS)*
    ;

var
    :   nested ID '>>'
    ;

get
    :   ID '<<'
    ;

//expressions

term
    :   INT
    ;

expr
    :   term (term operator)*
    ;

nested
    :   expr (expr operator)*
    ;

operator 
    :   ('*' | '+' | '/' | '%' | '-')
    ;

ID
    :   ('a'..'z' | 'A'..'Z') ('a..z' | '0'..'9' | 'A'..'Z')*
    ;

INT
    :   '0'..'9'+
    ;

WS
    :   (' ' | '\n' | '\t' | '\r') {$channel=HIDDEN;}
    ;

2 个回答

-1

问题在于你的规则没有结束条件,因为它可以匹配到什么都不匹配的情况。我没有使用ANTLR,因为我不太喜欢弄这个,所以我用C++重写了你的语法(使用AXE解析器生成器),并添加了一些打印语句来跟踪匹配的过程,结果在解析"2 2 * test >>"时得到了以下结果:

parsed term: 2
parsed expr: 2
parsed nested: 2
parsed term: 2
parsed expr: 2
parsed nested: 2
parsed body: 2 2
parsed body:
parsed body: ... here goes your infinite loop

如果你想调试这个测试案例,下面是AXE的语法,可以在打印语句处设置断点,逐步查看解析过程:

using namespace axe;
typedef std::string::iterator It;

auto space = r_any(" \t\n\r");
auto int_rule = r_numstr();
auto id = r_ident();
auto op = r_any("*+/%-");
auto term = int_rule 
    >> e_ref([](It i1, It i2)
{ 
    std::cout << "\nparsed term: " << std::string(i1, i2); 
});
auto expr = (term & *(term & op)) 
    >> e_ref([](It i1, It i2)
{ 
    std::cout << "\nparsed expr: " << std::string(i1, i2); 
});
auto nested = (expr & *(expr & op))
    >> e_ref([](It i1, It i2)
{ 
    std::cout << "\nparsed nested: " << std::string(i1, i2); 
});
auto get = (id & "<<")  
    >> e_ref([](It i1, It i2)
{ 
    std::cout << "\nparsed get: " << std::string(i1, i2); 
});
auto var = (nested & id & ">>")  
    >> e_ref([](It i1, It i2)
{ 
    std::cout << "\nparsed var: " << std::string(i1, i2); 
});
auto body = (*(nested & space) | *(var & space) | *(get & space))
     >> e_ref([](It i1, It i2)
{ 
    std::cout << "\nparsed body: " << std::string(i1, i2); 
});
auto program = +(body)
    | r_fail([](It i1, It i2) 
{
    std::cout << "\nparsing failed, parsed portion: " 
        << std::string(i1, i2);
});
// test parser
std::ostringstream text;
text << "2 2 * test >>";
std::string str = text.str();
program(str.begin(), str.end());
4

有几个地方不太对:

1

你把 WS 这个标记放在了 HIDDEN 通道上,这样就让解析规则无法使用它。所以在你的 body 规则中的所有 WS 标记都是错误的。

2

_(你最近的编辑解决了左递归的问题,但我还是想提一下 抱歉,你的 另一个问题 里有一个左递归的规则 (expr),所以我还是把这些信息留在这里)_

ANTLR 是一个 LL 解析器生成器,所以你不能创建左递归的语法。下面这个就是左递归:

expr
  :  term term operator
  ;

term
  :  INT
  |  ID
  |  expr
  ;

因为在 expr 规则中的第一个 term 可能会匹配到 expr 规则本身。像所有的 LL 解析器一样,ANTLR 生成的解析器无法处理左递归。

3

如果你解决了 WS 的问题,你的 body 规则会产生以下错误信息:

(1/7) Decision can match input such as "INT" using multiple alternatives

这意味着解析器无法“看出” INT 这个标记属于哪个规则。这是因为你所有的 body 选项可以重复零次或多次,而 exprnested 也可以重复。而且它们都可以匹配到一个 INT,这就是 ANTLR 抱怨的地方。如果你像这样去掉 *

body
    :   nested
    |   var
    |   get
    ;

// ...

expr
    :   term (term operator)
    ;

nested
    :   expr (expr operator)
    ;

错误就会消失(虽然这仍然不会让你的输入被正确解析!)。

我知道这可能听起来还是有点模糊,但要解释清楚并不简单(如果你对这些内容不熟悉的话)。

4

为了正确处理在 expr 内部的递归 expr,你需要避免左递归,正如我在#2中解释的那样。你可以这样做:

expr
  :  term (expr operator | term operator)*
  ;

这仍然是模糊的,但在用 LL 语法描述后缀表达式时,这是不可避免的。为了解决这个问题,你可以在语法的 options { ... } 部分启用全局回溯:

options {
  language=Python;
  output=AST;
  backtrack=true;
}

演示

一个解析递归表达式的小演示可能是这样的:

grammar star;

options {
  language=Python;
  output=AST;
  backtrack=true;
}

parse
  :  expr EOF -> expr
  ;

expr
  :  (term -> term) ( expr2 operator -> ^(operator $expr expr2) 
                    | term operator  -> ^(operator term term)
                    )*
  ;

expr2 
  :  expr
  ;

term
  :  INT
  |  ID
  ;

operator 
  :  ('*' | '+' | '/' | '%' | '-')
  ;

ID
  :  ('a'..'z' | 'A'..'Z') ('a..z' | '0'..'9' | 'A'..'Z')*
  ;

INT
  :  '0'..'9'+
  ;

WS
  :  (' ' | '\n' | '\t' | '\r') {$channel=HIDDEN;}
  ;

测试脚本:

#!/usr/bin/env python
import antlr3
from antlr3 import *
from antlr3.tree import *
from starLexer import *
from starParser import *

def print_level_order(tree, indent):
  print '{0}{1}'.format('   '*indent, tree.text)
  for child in tree.getChildren():
    print_level_order(child, indent+1)

input = "5 1 2 + 4 * + 3 -"
char_stream = antlr3.ANTLRStringStream(input)
lexer = starLexer(char_stream)
tokens = antlr3.CommonTokenStream(lexer)
parser = starParser(tokens)
tree = parser.parse().tree 
print_level_order(tree, 0)

产生以下输出:

-
   +
      5
      *
         +
            1
            2
         4
   3

这对应于以下的抽象语法树(AST):

enter image description here

撰写回答