18

I'm trying to get a deeper understanding of how Python works, and I've been looking at the grammar shown at http://docs.python.org/3.3/reference/grammar.html.

I notice it says you would have to change parsermodule.c also, but truthfully I'm just not following what's going on here.

I understand that a grammar is a specification for how to read the language, but...I can't even tell what this is written in. It looks almost like Python but then it isn't.

I'm looking to get a better understanding of this specification and how it is used internally by Python to....do things. What depends on it (the answer is everything, but I mean specifically which aspect of the "engine" is processing it), what uses it, how does it tie in to compiling/running a script?

It's hard to believe that the whole language comes down to a two page specification...

4

4 回答 4

17

语法用于描述语言中所有可能的字符串。它在指定解析器应该如何解析语言时也很有用。

在这个语法中,他们似乎正在使用他们自己的EBNF版本,其中非终结符是任何小写单词,而终结符是全部大写或被引号包围。例如,NEWLINE 是终结符,arith_expr 是非终结符,'if' 也是终结符。任何非终结符都可以被其各自生产规则的冒号右侧的任何内容替换。例如,如果您查看第一条规则:

单输入:NEWLINE | simple_stmt | Compound_stmt NEWLINE

我们可以将 single_input 替换为 NEWLINE、simple_stmt 或 Compound_stmt 后跟 NEWLINE 之一。假设我们将其替换为“compound_stmt NEWLINE”,那么我们将寻找 Compound_stmt 的产生式规则:

Compound_stmt:if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | 函数定义 | 类定义 | 装饰

并选择我们要使用的其中一个,并将其替换为“compound_stmt”(将 NEWLINE 保留在原处)

假设我们想要生成有效的 python 程序:

if 5 < 2 + 3 or not 1 == 5:
    raise

我们可以使用以下推导:

  1. 单输入
  2. Compound_stmt NEWLINE
  3. if_stmt NEWLINE
  4. 'if' 测试 ':' 套件 NEWLINE
  5. 'if' or_test ':' NEWLINE INDENT stmt stmt DEDENT NEWLINE
  6. 'if' and_test 'or' and_test ':' NEWLINE INDENT simple_stmt DEDENT NEWLINE
  7. 'if' not_test 'or' not_test ':' NEWLINE INDENT small_stmt DEDENT NEWLINE
  8. 'if' 比较 'or' 'not' not_test ':' NEWLINE INDENT flow_stmt DEDENT NEWLINE
  9. 'if' expr comp_op expr 'or' 'not' 比较 ':' NEWLINE INDENT raise_stmt DEDENT NEWLINE
  10. 'if' arith_expr '<' arith_expr 'or' 'not' arith_expr comp_op arith_expr ':' NEWLINE INDENT 'raise' DEDENT NEWLINE
  11. 'if' term '<' term '+' term 'or' 'not' arith_expr == arith_expr ':' NEWLINE INDENT 'raise' DEDENT NEWLINE
  12. 'if' NUMBER '<' NUMBER '+' NUMBER 'or' 'not' NUMBER == NUM​​BER ':' NEWLINE INDENT 'raise' DEDENT NEWLINE

这里有几点注意事项,首先,我们必须从列为起始非终结符的非终结符之一开始。在该页面中,他们将它们列为 single_input、file_input 或 eval_input。其次,一旦所有符号都是终结符(因此得名),推导就完成了。第三,每行进行一次替换更为常见,为了简洁起见,我一次完成了所有可能的替换,并在接近尾声时开始跳过步骤。

给定语言中的字符串,我们如何找到它的派生?这是解析器的工作。解析器对生产序列进行逆向工程,首先检查它是否确实是一个有效的字符串,以及如何从语法中导出它。值得注意的是,许多语法可以描述一种语言。但是,对于给定的字符串,它的派生当然会因每种语法而不同。所以从技术上讲,我们为语法而不是语言编写解析器。有些语法更容易解析,有些语法更容易阅读/理解。这个属于前者。

此外,这并没有指定整个语言,只是它的外观。语法对语义只字未提。

如果您对解析和语法感兴趣,我推荐Grune, Jacobs - Parsing Techniques。它是免费的,适合自学。

于 2013-12-30T08:03:12.600 回答
8

与大多数其他语法一样,python 语法以BNFBackus-Naur 形式给出。尝试阅读如何阅读它,但基本结构是:

<something> ::= (<something defined elsewhere> | [some fixed things]) [...]

这被解读为<something> 被定义为 something else 任何重复多次的固定事物。

BNF 基于一种近 2000 年历史的用于描述语言允许结构的格式,非常简洁,将描述给定语言中所有允许的结构,不一定是所有有意义的结构。

例子

基本算术可以描述为:

<simple arithmetic expression> ::= <numeric expr>[ ]...(<operator>[ ]...<numeric expr>|<simple arithmetic expression>)
<numeric expr> ::= [<sign>]<digit>[...][.<digit>[...]]
<sign> ::= +|-
<operator> ::= [+-*/]
<digit> ::= [0123456789]

它说一个简单的算术运算是一个可选带符号的数字,由一个或多个数字组成,可能带有一个小数点和一个或多个后续数字,可选地后跟空格,后跟恰好一个+-*/,可选地后跟空格, 后跟一个数字或另一个简单的算术运算,即后面跟着一个数字,等等。

这几乎描述所有基本的算术运算,并且可以扩展到包括函数等。请注意,确实允许有效语法的无效运算,例如:22.34 / -0.0即使结果无效,在语法上也是有效的。

它有时可以让您意识到您可能没有想到的操作是可能的,例如:56+-50是一个有效的操作,2*-102*/3不是。

请注意,SGMLXML / Schema都是相关但不同的描述任何语言结构的方法。YAML 是另一种用计算机特定语言描述允许结构的方法。

免责声明:我的 BNF 有点生疏,所以如果我在上面犯了任何重大错误,我深表歉意,请纠正我。

于 2013-10-13T22:44:42.507 回答
6

这基本上是一个EBNF(扩展巴科斯-瑙尔形式)规范。

于 2013-10-13T22:42:40.210 回答
4

当您用一种语言编写程序时,您的解释器/编译器为了从字符序列转换为实际操作,首先要做的就是将该字符序列翻译成更高复杂度的结构。为此,首先它将您的程序分成一系列标记,表达每个“单词”所代表的内容。例如,构造

if foo == 3: print 'hello'

将被转换为

1,0-1,2:    NAME    'if'
1,3-1,6:    NAME    'foo'
1,7-1,9:    OP  '=='
1,10-1,11:  NUMBER  '3'
1,11-1,12:  OP  ':'
1,13-1,18:  NAME    'print'
1,19-1,26:  STRING  "'hello'"
2,0-2,0:    ENDMARKER   ''

但请注意,即使像“if if if”之类的东西也被正确地制成了标记

1,0-1,2:    NAME    'if'
1,3-1,5:    NAME    'if'
1,6-1,8:    NAME    'if'
1,9-1,11:   NAME    'if'
2,0-2,0:    ENDMARKER   ''

标记化之后是解析到更高级别的结构,该结构分析标记是否真的合在一起有意义,后一个示例没有,但第一个示例确实如此。为此,解析器必须识别标记的实际含义(例如 if 是关键字,而 foo 是变量),然后从标记构建一棵树,将它们组织成层次结构,看看这个层次结构是否真的使感觉。这就是您所看到的语法的来源。该语法在 BNF 中,这是一种表示语言可以识别的结构的符号。该语法由一个程序(例如,bison)消化,该程序具有获取该语法并生成实际的 C 代码的神奇属性,这些代码为您完成繁重的工作,通常是通过识别标记,组织它们,返回一个解析树,

简短版本:开发一种语言是关于定义标记以及如何将这些标记组合在一起以提供有意义的东西。这是通过语法完成的,您可以使用该语法通过自动化工具生成实际的“解析器”代码。

于 2013-12-19T22:00:42.983 回答