0

我有一个基于 ANTLR 4 并使用侦听器而不是访问者的解析器。它已经识别并存储了函数、变量等的声明。

我正在尝试使用语义谓词解决一些语法歧义,例如在解析 VHDL 源代码时将函数调用与数组/向量访问分开。这对于避免完整语法的进一步复杂化很重要。

在以下示例中:

3 + j * f(i)

f(i)可以是f带参数的函数,也可以是由 index 访问i的数组。下面的简化示例显示了谓词如何帮助解决这种歧义:fi

expression:
    expression OPERATOR expression | simple_expression;
simple_expression:
    function_expression | array_expression | ID | NUMBER;
function_expression:
    {is_function()}? ID '(' expression_list ')';
array_expression:
    {is_array()}? ID '(' expression ')';
expression_list:
    expression ( ',' expression )*;

侦听器解析声明并将函数和数组标识符存储在数据库中,这允许知道标识符ID是函数、数组还是未声明的(为了简单起见,我没有在此处显示这些声明的任何语法示例)。

谓词的一个例子是,在语法文件的顶部:

@parser::members {
    Definitions defs;
    boolean is_function() {
        return defs.isFunction(getCurrentToken().getText());
    }
    boolean is_array() {
        return defs.isArray(getCurrentToken().getText());
    }
}

但是,我不能在谓词中使用该信息,因为在调用声明的侦听器来构建 ID 数据库之前,它们被调用得太早。如果我System.out.print在这些函数中以及在侦听器中添加 a,我会看到

  • 表达式谓词首先在被解析的整个文件上调用,
  • 然后才调用所有声明侦听器,即使声明在文件中的这些表达式之前也是如此。

我知道解析器正在向前看,但是有没有办法尽快加快声明侦听器的速度,以便为与文件其余部分中的表达式相关的谓词准备好它们的信息?

或者这是使用谓词的错误方式?我想尽可能避免语法中的源代码,例如在解析声明期间存储初步信息的解决方法,其中嵌入了语法文件中的代码。2-pass 解析器似乎有点尴尬。

4

1 回答 1

1

正如隐含地认识到的那样,问题在于该陈述

3 + j * f(i)

是模棱两可的。

鉴于解析器在执行 tree-walk 之前运行完成,tree-walker 无法通知语义谓词在 walker 中做出的语义决策。

更好的方法是认识到解析器只能区分语法。因此,语法可以写成:

expression
    : expression OPERATOR expression                       #op
    | ID LPAREN expression ( COMMA expression )* RPAREN    #simple
    | ID                                                   #id
    | NUMBER                                               #num
    ;

现在遍历解析树以使用推导出的语义信息来注释现有节点,例如,给定SimpleExpressionContext节点是否表示函数或数组。注释可以使用ParseTreeProperty.

优选地,使用多个游走,每个游走都专注于一些不同的语义分析方面,无论是离散的还是建立在/使用先前游走的结果。(每次遍历在执行性能方面相对便宜,允许关注点分离,增强可维护性等)

有一些准备行走、符号表构建行走、评估行走和输出行走的情况并不少见。

而且,解析器语法中很少或没有本机代码或复杂的谓词。

于 2018-04-07T20:06:45.913 回答