我们可以使用 BNF 代替正则表达式来进行解析和词法分析吗?

Can we use BNF for parsing AND lexing instead of regex?

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

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

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

<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 为任何上下文无关语言编写完整的语法。

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

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

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

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

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

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

尽管如此,许多词素确实没有任何有趣的内部结构,而且尽管将正则表达式重写为正则语法很容易,但将其重写为明确的语法却相当困难,确定性正则文法,更不用说 LALR(1) 正则文法了。 (这是 scannerless 解析通常与 PEG 相关联的原因之一,但也可以使用 GLL 或 GLR 解析器解决,但效率略有下降。)