Python中的BNF表示法简介

在本教程中,您将了解 Python 的 BNF 表示法的基础知识,并学习如何利用它来深入了解该语言的语法和文法。

什么是巴科斯-诺尔范式表示法 (BNF)
巴科斯-诺尔形式或巴科斯范式( BNF ) 是上下文无关语法的元语法表示法。计算机科学家经常使用这种表示法来描述编程语言的语法,因为它允许他们编写语言语法的详细描述。

BNF(Backus-Naur Form)是一种用于描述编程语言语法的形式化表示法。它由约翰·巴克斯(John Backus)和彼得·诺尔(Peter Naur)在20世纪60年代提出,最初是为了描述ALGOL 60语言的语法设计。现在它被广泛应用于描述各种编程语言的语法,包括Python。

举例:BNF 表示法(巴科斯-诺尔形式)的片段,如下所示:
name      ::= lc_letter (lc_letter | "_")*
lc_letter ::= "a"..."z"


BNF表示法使用一系列规则来描述语法结构。每个规则通常由:

  1. 一个非终结符和它的定义组成。
  2. 非终结符表示语法结构的部分,
  3. 而终结符表示语法结构中的实际元素,比如关键字、标识符、操作符等。

规则的定义由终结符和非终结符组成,用特殊符号 "->" 表示。例如:

<expression> ::= <term> | <expression> "+" <term> | <expression> "-" <term>
<term> ::= <factor> | <term>
"*" <factor> | <term> "/" <factor>
<factor> ::= <number> |
"(" <expression> ")"
<number> ::= [0-9]+

在这个简单的示例中,<expression>、<term>、<factor>和<number>是非终结符,而"+"、"-"、"*"、"/"和括号是终结符。这些规则描述了基本的数学表达式语法,其中包括加法、减法、乘法、除法以及括号。

在Python中,BNF表示法通常用于描述Python语言的语法规则,包括语句、表达式、函数定义等。Python的语法规则可以在官方文档中找到,或者在Python的语言参考手册中查看。

BNFL表示法是一种简洁而形式化的方法,用于描述编程语言的语法结构,对于理解语言的语法和解析器的设计非常有帮助。

BNF 规则及其组成部分
通过组合终结符和非终结符,您可以创建 BNF 规则。这些规则通常遵循以下语法:

<symbol> ::= expression

在 BNF 规则语法中,有以下部分:

  • <symbol>是一个非终结符变量,通常用尖括号 ( <>) 括起来。
  • ::=表示左侧的非终结符将被右侧的表达式替换。
  • expression由一系列终结符、非终结符和其他定义特定语法的符号组成。

在构建 BNF 规则时,可以使用各种具有特定含义的符号。例如,如果您要使用 BNF Playground 站点来编译和测试您的规则,那么您会发现自己使用了以下一些符号:

""    包含一个终端符号
<>    表示非终结符
()    表示一组有效选项
+    指定一个或多个前一元素
*    指定零个或多个前一个元素
?    指定前一个元素出现零次或一次
|    表示您可以选择其中一个选项
[x-z]    表示字母或数字间隔

一旦您知道如何编写 BNF 规则以及使用哪些符号,您就可以开始创建自己的规则。请注意,BNF Playground 有几个额外的符号和语法结构,您可以在规则中使用它们。如需完整参考,请单击页面顶部的语法帮助部分。

现在,是时候开始使用一些自定义 BNF 规则了。首先,您将从一个通用示例开始。

通用示例:全名语法
假设您需要创建一个上下文无关语法来定义用户应如何输入一个人的全名。在这种情况下,全名将由三个部分组成:

  1. 中间名字

在每个组件之间,您需要恰好放置一个空格。您还应该将中间名视为可选。以下是定义此规则的方法:

<full_name> ::= <first_name> " " (<middle_name> " ")? <family_name>

BNF 规则的左侧部分是一个非终端变量,用于标识该人的全名。::= 符号表示 <full_name> 将被规则的右侧部分替换。

规则的右侧部分由几个部分组成。首先是名字,使用 <first_name> 非终端定义。然后,需要一个空格来分隔第一个名字和下面的部分。要定义这个空格,需要使用终端,终端由引号之间的空格符组成。

在第一个名字之后,可以接受一个中间名,在中间名之后,需要另一个空格。因此,需要用括号将这两个元素分组。然后创建 <middle_name> 和 " " 终端。这两个元素都是可选的,所以要在后面使用问号(?)

最后,您需要家族名称。要定义这个组件,需要使用另一个非终端 <family_name>。就是这样!您已经构建了第一条 BNF 规则。但是,你仍然没有一个有效的语法。您只有一个根规则。

要完成语法,您需要定义 <first_name>、<middle_name> 和 <family_name> 的规则。为此,您需要满足一些要求:

  • 每个名称组件只接受字母。
  • 每个名称组件将以大写字母开始,然后以小写字母继续。

在这种情况下,可以先定义两条规则,一条用于大写字母,一条用于小写字母:

<full_name>        ::= <first_name> " " (<middle_name> " ")? <family_name>
[b][i]<uppercase_letter> ::= [A-Z]
<lowercase_letter> ::= [a-z][/i][/b]

在语法片段中突出显示的几行中,您创建了两条非常相似的规则。第一条规则接受从大写 A 到 Z 的所有 ASCII 字母,第二条规则接受所有小写字母。在本例中,不支持重音或其他非 ASCII 字母。

有了这些规则,就可以建立其他规则了。首先,添加 <first_name> 规则:

<full_name>        ::= <first_name> " " (<middle_name> " ")? <family_name>
<uppercase_letter> ::= [A-Z]
<lowercase_letter> ::= [a-z]
[b][i]<first_name>       ::= <uppercase_letter> <lowercase_letter>*[/i][/b]

要定义 <first_name> 规则,首先要使用 <uppercase_letter> 非终端来表示名称的第一个字母必须是大写字母。然后,继续使用 <小写字母> 非终端,后面跟一个星号 (*)。星号表示在第一个大写字母之后,第一个名称可以接受零个或多个小写字母。

您可以按照同样的模式建立 <middle_name> 和 <family_name> 规则。您想试试吗?

完整的语法:

<full_name>        ::= <first_name> " " (<middle_name> " ")? <family_name>
<uppercase_letter> ::= [A-Z]
<lowercase_letter> ::= [a-z]
[b][i]<first_name>       ::= <uppercase_letter> <lowercase_letter>*
<middle_name>      ::= <uppercase_letter> <lowercase_letter>*
<family_name>      ::= <uppercase_letter> <lowercase_letter>*[/i][/b]

您可以使用 BNF Playground 网站检查全名语法是否有效。

编程相关示例标识符
在上一节中,您学习了如何创建一个 BNF 语法,以定义用户必须如何提供人名。这是一个通用示例,可能与编程有关,也可能无关。在本节中,您将编写一套简短的 BNF 规则,以验证假定编程语言中的标识符,从而获得更多技术知识。

标识符可以是变量、函数、类或对象的名称。在示例中,您将编写一组规则来检查给定字符串是否符合以下要求:

  • 第一个字符是大写或小写字母或下划线。
  • 其余字符可以是大写或小写字母、数字或下划线。

以下是标识符的根规则:
<identifier> ::= <char> (<char> | <digit>)*

在这条规则中,<identifier> 非终端变量定义了根。在右侧,首先是 <char> 非终端变量。标识符的其余部分都在括号内分组。组后的星号表示该组中的元素可以出现 0 次或更多次。每个这样的元素要么是一个字符,要么是一个数字。

现在,你需要用自己的专用规则定义 <char> 和 <digit> 非终结符。它们看起来就像下面的代码:

<identifier> ::= <char> (<char> | <digit>)*
<char>       ::= [A-Z] | [a-z] | "_"
<digit>      ::= [0-9]

<char> 规则接受一个小写或大写的 ASCII 字母。此外,它还可以接受下划线。最后,<digit> 规则接受 0 至 9 的数字。现在,你的规则集已经完成。请在 BNF Playground 网站上试试吧。

对于程序员来说,阅读 BNF 规则是一项非常有用的技能。例如,你经常会发现许多编程语言的官方文档都包含了这些语言的全部或部分 BNF 语法。因此,阅读 BNF 可以让你更好地理解语言的语法和复杂性。

了解 Python 的 BNF 变体
Python 使用 BNF 符号的自定义变体来定义语言的语法。在 Python 文档的许多部分,您都可以找到 BNF 语法的片段。这些片段可以帮助您更好地理解正在学习的任何语法结构。

Python 的 BNF 变体使用以下样式:

name    保存规则或非终结符的名称
::=    意味着扩展到
|    分隔替代品
*    接受前一项的零次或多次重复
+    接受前一项的一次或多次重复
[]    接受零次或一次出现,这意味着随附的项目是可选的
()    群组选项
""    定义文字字符串
space    仅对分隔token有意义


这些符号定义了 Python 的 BNF 变体。与常规 BNF 规则的一个显著区别是,Python 不使用角括号 (<>) 来括弧非终端符号。它只使用非终端标识符或名称。可以说,这使得规则更简洁、更易读。

还要注意,方括号 ([]) 对 Python 有不同的含义。到此为止,您已经用它们括起了 [a-z] 这样的字符集。在 Python 中,这些括号表示括起来的元素是可选的。要在 Python 的 BNF 变体中定义 [a-z] 这样的内容,您可以使用 "a"... "z "来代替。

您可以在 Python 文档中找到许多 BNF 片段。作为 Python 开发者,学习如何浏览和阅读它们是一项非常有用的技能。因此,在下面的章节中,您将探索 Python 文档中的一些 BNF 规则示例,并学习如何阅读它们。