我们可以使用 BNF 来解析 AND 词法而不是正则表达式吗?

问题描述

使用 Backus-Naur 形式语法 (BNF),我们可以指定编程语言的语法,以便解析生成抽象语法树 (AST)。

<if> ::= "if" <expression> "then" <action> "end"

但我们也可以使用 BNF 语法指定标记,就像 ALGOL-60 中 BNF 的第一次使用一样:

<digit> ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
<digit_with_zero> ::= <digit> | "0"
<integer> ::= <digit> | <digit_with_zero> <integer>

但是,为了 lex(= 生成最小有意义的单位列表,又名标记)的这种使用 BNF 已被弃用,取而代之的是正则表达式(如 [1-9][0 -9]*)。

很明显,正则表达式要简洁得多。

对于将处理解析器生成的 AST 的解释器或编译器来说,保留 if 语句的结构似乎也很有趣,但保留整数(或浮点数)的结构则不然。

但是您同意 BNF 可以用于词法分析和解析吗? 您是否同意使正则表达式更适合词法分析的原因? 或者还有其他的吗?

解决方法

正则表达式(在数学意义上)与正则文法等价,正则文法可以用 BNF 编写。因此,从这个意义上说,显然可以用纯 BNF 为任何上下文无关语言编写完整的语法。

事实上,甚至不需要维护词法分析器/解析器二分法。一些程序员发现使用 scannerless parsing 很方便(这篇文章不是很好,但它有一些有趣的参考资料),尽管其中许多是基于 PEG 形式主义(不是上下文无关的)而不是 BNF。 (尽管表面相似,但它们并不相同。)

也就是说,它可能不方便。一般来说,与大多数与解析器结构相关的问题一样,答案将更少地基于理论,更多地基于实用性(参考特定用例)和程序员偏见的结合。

众所周知,纯度很少是最实用的。大多数现实生活中的解析器和扫描器生成器都偏离了纯理论模型,以提供更易于使用、更易于有效实现或更强大的机制。例如,在扫描器生成器中几乎通用的字符类语法 ([a-zA-Z]) 是正则表达式语法的明确扩展,它有意避免需要明确列出集合的全部内容。可以说,在我刚刚展示的示例中,列表是隐含和明确的,但大多数扫描仪生成器还允许使用 [[:alnum:]](“字母数字符号”)之类的类,其中匹配符号的精确列表是语言环境- 依赖,或者在 Unicode 世界中,将来可以扩展。无论如何,这显然是一个有用的扩展。

虽然正则表达式的某些方面确实比其等效的正则语法更紧凑——尤其是 Kleene 星号运算符,它在 BNF 中需要一个额外的非终结符,因此需要一个额外的名称——但也有一些情况命名子表达式的能力使正则文法更加紧凑。许多扫描仪生成器,从 Lex 开始,允许命名子模式作为另一个正则表达式扩展。此外,可以(有一些警告)将 Kleene 星和其他运算符作为宏添加到 BNF,许多解析器生成器都这样做。所以符号有一定的收敛性。

如您所说,扫描器和解析器之间的一个区别是扫描器通常不会尝试解析词素的子结构。但是,没有词素具有子结构是不正确的,而且这些子结构通常确实需要进行分析。最臭名昭著的例子大概就是浮点数了,浮点数要解析成乘数和指数,乘数也解析成整数部分和小数部分。这种分析通常使用扫描器实现语言中可用的原始函数(例如 C 扫描器的 strtod)来完成,但这确实意味着第二次词法扫描。 (使用内置函数避免了编写数学上正确的字符串到内部转换器的相当不便,这比最初出现的问题要困难得多。不建议滚动您自己的数字转换器。)

其他具有内部结构的词素包括字符串文字(可能包含转义序列)和在某些语言中可用的大量更复杂的词素(日期和时间、IP 地址、HTML 标签等)。所有这些都倾向于模糊扫描和解析之间的界限。这很好,因为正如我所说,边界是有条件的,不受任何绝对的自然法则约束。

不过,许多词素确实没有任何有趣的内部结构,此外,虽然将正则表达式改写为正则文法很容易,但将其改写为明确的、确定性的则要困难得多。正则文法,更不用说 LALR(1) 正则文法了。 (这是无扫描仪解析通常与 PEG 相关联的原因之一,但也可以使用 GLL 或 GLR 解析器解决,但效率略有损失。)