解析表达式 - 一元、二元和 incrementing/decrementing 运算符

Parsing expressions - unary, binary and incrementing/decrementing operators

我正在尝试为我的语言编写一个解析器(用于学习和娱乐)。问题是我不知道如何解析像 a--ba----b 这样的表达式。当有多个由 - 字符组成的运算符时 - 一元 (-x)、二进制 (x-y)、预递减 (--x,就像在 C 中一样) 和 post-递减(x--)。 a--ba----b 都应该有效并产生:

a--b -> sub     a----b -> sub
       +-+-+            +--+--+
       a  neg         decr   neg
           |           |      |
           b           a      b

当 lexer 分词时 a--b 它不知道它是递减还是减号重复了两次,所以解析器必须找出它是哪一个。
我如何确定 - 是递减运算符的一部分还是只是减号?

问题不在于解析,而在于决定规则。

为什么 a----b 应该是 a-- - -b 而不是 a - -(--b)?就此而言,a---b 应该是 a-- - b 还是 a - --b

那么 a---33---a 呢? 3----3 都没有任何意义,所以如果标准是“选择(其中一个)合理的解释”,你最终会得到 a-- - 33 - --a。但即使无需额外努力即可实现,它也会给编码人员和代码 readers 带来巨大的认知负担。

曾几何时,提交程序执行是一个费力且有时官僚的过程,并且由于编译器找不到正确的解释而取消 运行 是非常令人沮丧的。 .我还怀念着学生时代的记忆,排着队把我的程序交给电脑操作员,然后又排着队去接收打印的结果。

因此,创造一种不遗余力地寻找对所给内容的有效解释的编程语言一时变得流行起来。但这种努力也意味着一些错误无误地通过了,因为程序员和编程语言对“自然解释”可能是什么有不同的理解。

如果您使用 C/C++ 编程,您很可能有时会写成 a & 3 == 0 而不是 (a & 3) == 0。幸运的是,如果启用警告,现代编译器会警告您有关此错误的信息。但询问是否应该允许这种构造至少是合理的。尽管必须添加括号并重新编译有点烦人,但它并不像尝试调试由此产生的模糊行为那样令人沮丧。或者在代码审查中接受了代码而没有注意到细微的错误。

如今,编译/测试/编辑周期要快得多,所以没有理由坚持清晰。如果我今天正在编写编译器,我可能会将任何可能不明确的运算符字符序列标记为错误。但这可能太过分了。

在大多数语言中,使用了一个相对简单的规则:在程序的每一点,词法分析都选择可能最长的标记,无论它是否“有意义”。这就是 C 和 C++ 所做的(大部分),它具有易于实现和易于验证的优点。 (即便如此,在代码审查中我会坚持将 a---b 写成 a-- -b。)

您可以稍微修改此规则,以便仅将第一对 -- 作为标记,这将捕获您想要的一些解析而不会给代码带来太多负担 reader .

您可以使用更复杂的规则,但请记住,无论您实施什么,都必须记录下来。如果很难记录清楚,那可能是不合适的。

一旦你阐明了你的规则列表,你就开始实施。在许多情况下,最简单的方法就是按顺序、回溯或并行地尝试各种可能性。或者您可以预先计算可能的解析。

或者,您可以使用能够在歧义语法中找到所有解析的 GLR 或 GLL 解析器生成器,然后 select 基于您喜欢的任何标准的“最佳”解析。