Skip to content
This repository has been archived by the owner on May 6, 2021. It is now read-only.

Commit

Permalink
3.5
Browse files Browse the repository at this point in the history
  • Loading branch information
wizardforcel committed Sep 5, 2016
1 parent 55321f8 commit 498ee6c
Showing 1 changed file with 118 additions and 0 deletions.
118 changes: 118 additions & 0 deletions 3.5.md
Expand Up @@ -183,3 +183,121 @@ calc> ^DCalculation completed.

在我们将解释器推广到除了计算器之外的语言时,我们会看到,`read_eval_print_loop`由解析函数、求值函数,和由`try`语句处理的异常类型参数化。除了这些修改之外,任何 REPL 都可以使用相同的结构来实现。

## 3.5.2 解析

解析是从原始文本输入生成表达式树的过程。这是求值函数的任务,用来解释这些表达式树,但是解析器必须提供复合格式的表达式树给求值器。解析器实际上由两个组件组成,此法分析器和语法分析器。首先,词法分析器将输入字符串拆成标记(token),它们是语言的最小语法单元,就像名称和符号。其次,语法分析器从这个标记序列中构建表达式树。

```py
>>> def calc_parse(line):
"""Parse a line of calculator input and return an expression tree."""
tokens = tokenize(line)
expression_tree = analyze(tokens)
if len(tokens) > 0:
raise SyntaxError('Extra token(s): ' + ' '.join(tokens))
return expression_tree
```

标记序列由叫做`tokenize`的词法分析器产生,并被叫做`analyze`语法分析器使用。这里,我们定义了`calc_parse`,它只接受复合格式的计算器表达式。一些语言的解析器为接受以换行符、分号或空格分隔的多种表达式而设计。我们在引入 Logo 语言之前会推迟实现这种复杂性。

**词法分析。**用于将字符串解释为标记序列的组件叫做分词器(tokenizer ),或者词法分析器。在我们的视线中,分词器是个叫做`tokenize`的函数。计算器语言由包含数值、运算符名称和运算符类型的符号(比如`+`)组成。这些符号总是由两种分隔符划分:逗号和圆括号。每个符号本身都是标记,就像每个逗号和圆括号那样。标记可以通过像输入字符串添加空格,之后在每个空格处分割字符串来分开。

```py
>>> def tokenize(line):
"""Convert a string into a list of tokens."""
spaced = line.replace('(',' ( ').replace(')',' ) ').replace(',', ' , ')
return spaced.split()
```

对符合格式的计算器表达式分词不会损坏名称,但是会分开所有符号和分隔符。

```py
>>> tokenize('add(2, mul(4, 6))')
['add', '(', '2', ',', 'mul', '(', '4', ',', '6', ')', ')']
```

拥有更加复合语法的语言可能需要更复杂的分词器。特别是,许多分析器会解析每种返回的标记的语法类型。例如,计算机中的标记类型可能是预案算符、名称、数值或分隔符。这个分类可以简化标记序列的解析。

**语法分析。**将标记序列解释为表达式树的组件叫做语法分析器。在我们的视线中,语法分析有叫做`analyze`的递归函数完成。它是递归的,因为分析标记序列经常涉及到分析这些表达式树中的标记的子序列,它本身作为更大的表达式树的子分支(比如操作数)。递归会生成由求值器使用的层次结构。

`analyze`函数接受标记列表,以符合格式的表达式开始。它会分析第一个标记,将表示数值的字符串强制转换为数字的值。之后要考虑计算机中国的两个合法表达式类型。数字标记本身就是完整的基本表达式树。复合表达式以运算符开始,之后是操作数表达式的列表,由圆括号分隔。我们以一个不检查语法错误的实现开始。

```py
>>> def analyze(tokens):
"""Create a tree of nested lists from a sequence of tokens."""
token = analyze_token(tokens.pop(0))
if type(token) in (int, float):
return token
else:
tokens.pop(0) # Remove (
return Exp(token, analyze_operands(tokens))
>>> def analyze_operands(tokens):
"""Read a list of comma-separated operands."""
operands = []
while tokens[0] != ')':
if operands:
tokens.pop(0) # Remove ,
operands.append(analyze(tokens))
tokens.pop(0) # Remove )
return operands
```

最后,我们需要实现`analyze_token``analyze_token`函数将数值文本转换为数值。我们并不自己实现这个逻辑,而是依靠内建的 Python 类型转换,使用`int``float`构造器来将标记转换为这种类型。

```py
>>> def analyze_token(token):
"""Return the value of token if it can be analyzed as a number, or token."""
try:
return int(token)
except (TypeError, ValueError):
try:
return float(token)
except (TypeError, ValueError):
return token
```

我们的`analyze`实现就完成了。它能够正确将复合格式的计算机表达式解析为表达式树。这些树有`str`函数转换回计算器表达式。

```py
>>> expression = 'add(2, mul(4, 6))'
>>> analyze(tokenize(expression))
Exp('add', [2, Exp('mul', [4, 6])])
>>> str(analyze(tokenize(expression)))
'add(2, mul(4, 6))'
```

`analyse`函数只会返回复合格式的表达式树,并且他必须检测输入中的语法错误。特别是,它必须检测表达式是否完整、正确分隔,以及只是用已知的运算符。下面的修订,确保了语法分析的每一步都找到了预期的标记。

```py
>>> known_operators = ['add', 'sub', 'mul', 'div', '+', '-', '*', '/']
>>> def analyze(tokens):
"""Create a tree of nested lists from a sequence of tokens."""
assert_non_empty(tokens)
token = analyze_token(tokens.pop(0))
if type(token) in (int, float):
return token
if token in known_operators:
if len(tokens) == 0 or tokens.pop(0) != '(':
raise SyntaxError('expected ( after ' + token)
return Exp(token, analyze_operands(tokens))
else:
raise SyntaxError('unexpected ' + token)
>>> def analyze_operands(tokens):
"""Analyze a sequence of comma-separated operands."""
assert_non_empty(tokens)
operands = []
while tokens[0] != ')':
if operands and tokens.pop(0) != ',':
raise SyntaxError('expected ,')
operands.append(analyze(tokens))
assert_non_empty(tokens)
tokens.pop(0) # Remove )
return elements
>>> def assert_non_empty(tokens):
"""Raise an exception if tokens is empty."""
if len(tokens) == 0:
raise SyntaxError('unexpected end of line')
```

大量的语法错误在本质上提升了解释器的可用性。在上面,`SyntaxError `异常包含所发生的问题描述。这些错误字符串也用作这些分析函数的定义文档。

这个定义完成了我们的计算器解释器。你可以获取这个单独的 Python 3 源码 [`calc.py`](http://www-inst.eecs.berkeley.edu/~cs61a/sp12/book/calc.py)来测试。我们的解释器对错误健壮,用户在`calc>`提示符后面的每个输入都会求值为数值,或者产生合适的错误,描述输入为什么不是符合格式的计算器表达式。

0 comments on commit 498ee6c

Please sign in to comment.