在语句结束时解析可选分号

时间:2012-06-10 17:23:56

标签: parsing bison yacc lexer

我正在编写一个解析器来解析类似C的语法。

首先,它现在可以解析代码:

a = 1;
b = 2;

现在我想在行尾添加分号。

最初的YACC规则是:

stmt: expr ';' { ... }

由我自己编写的词法分析器处理新行(代码被简化):

rule(/\r\n|\r|\n/)          { increase_lineno(); return :PASS }

指令:此处的PASS相当于在LEX中不返回任何内容,它会删除当前匹配的文本并跳到下一个规则,就像通常使用空格一样。

因此,我不能简单地将我的YACC规则更改为:

stmt: expr end_of_stmt { ... }
;
end_of_stmt: ';'
    | '\n'
;

所以我选择相应地通过解析器动态地改变词法分析器的状态。

像这样:

stmt: expr { state = :STATEMENT_END } ';' { ... }

添加一个可以将新行与新状态匹配的词法规则:

rule(/\r\n|\r|\n/, :STATEMENT_END) { increase_lineno(); state = nil; return ';' }

这意味着词法分析器处于以下状态:STATEMENT_END状态。它将像往常一样首先增加行号,然后将状态设置为初始状态,然后假装自己是分号。

奇怪的是它实际上并不适用于以下代码:

a = 1
b = 2

我调试它并得到它实际上并没有得到';'在数字1之后扫描换行符时所期望的,并且状态指定的规则并未真正执行。

设置新状态的代码在扫描新行并且没有返回任何内容后执行,这意味着,这些工作按以下顺序完成:

  1. 扫描a=1
  2. 扫描换行符并跳过,因此请获取下一个值b
  3. 执行插入的代码({ state = :STATEMENT_END }
  4. 引发错误 - 意外b此处
  5. 这就是我的期望:

    1. 扫描a=1
    2. 发现它符合规则expr,因此请缩减为stmt
    3. 执行插入的代码以设置新的词法分析器状态
    4. 扫描换行符并根据新的州匹配规则
    5. 返回;
    6. 继续扫描&解析以下行
    7. 内省后我发现可能因为YACC使用LALR(1)而引起,此解析器将首先读取一个令牌。当它扫描到那里时,状态尚未设置,因此无法获得正确的令牌。

      我的问题是:如何使其按预期工作?我对此一无所知。

      感谢。

2 个答案:

答案 0 :(得分:6)

首先要认识到,拥有这样的可选行终止符会使您的语言含糊不清,因此您首先需要决定解决歧义的方式。在这种情况下,主要歧义来自可能是中缀或前缀的运算符。例如:

a = b
-c;

您是希望将上述内容视为单个expr语句,还是将第一个分号排除在两个单独的语句之后?使用类似C语言的函数调用语法会出现类似的潜在歧义:

a = b
(c);

如果您希望将这些语句解析为两个语句,则可以使用您尝试过的方法;你只需要提前设置一个令牌。这很棘手,因为如果你有未闭括号,你不想设置状态,所以你最终需要一个额外的状态var来记录paren嵌套深度,并且只设置insert-semi-before-newline状态,当它是0

如果你想将上述情况作为一个陈述来解决,事情变得棘手,因为你实际上需要更多的预测来决定换行何时应该结束一个陈述 - 至少你需要在换行符之后查看令牌(以及任何评论或其他被忽略的东西)。在这种情况下,你可以让词法分析器做额外的前瞻。如果你使用flex(你显然不是吗?),我建议使用/运算符(直接查找),或者推迟返回分号,直到匹配下一个标记的词法分析器。< / p>

一般来说,在进行这种令牌状态记录时,我觉得最简单的方法是尽可能完全在词法分析器中完成,所以你不需要担心额外的前瞻标记(有时候并不总是)由解析器。在这种特定情况下,一个简单的方法是让词法分析器记录括号((为+1,)为-1),并返回最后一个标记。然后,在换行规则中,如果paren级别为0且最后一个标记可以结束表达式(ID或常量或)或仅后缀运算符),则返回额外的; < / p>

另一种方法是让词法分析器返回NEWLINE作为自己的标记。然后,您将更改解析器以接受stmt: expr NEWLINE以及语法中大多数其他令牌之间的可选换行符。这会将歧义直接暴露给解析器(现在它不是LALR(1)),因此您需要通过使用yacc的运算符优先级规则(棘手且容易出错)或使用类似bison的%glr-parser选项或btyacc的回溯能力直接处理歧义。

答案 1 :(得分:3)

你所尝试的当然是可能的。

事实上,Ruby正是这样做的,它有一个yacc解析器。换行软终止语句,分号是可选的,并且语句会在“如果需要”的情况下自动在多行上继续。

解析器和词法分析器之间的通信可能是必要的,是的,遗留的yacc是LALR(1)。

我不确切知道Ruby是如何做到的。我的猜测一直是它实际上没有通信(很多),而是词法分析器识别显然没有完成的构造,并且默默地将换行视为空格直到parens和括号平衡。当线条以二元运算符或逗号结尾时也必须注意,并且也会使用这些换行符。

只是一个猜测,但我相信这种技术会起作用。如果您想确切了解Ruby is open source...是如何做到的,请Matz