# 解析输入

> 原文： [https://www.fuzzingbook.org/html/Parser.html](https://www.fuzzingbook.org/html/Parser.html)

在[语法](Grammars.html)的章节中，我们讨论了如何使用语法来表示各种语言。 我们还看到了如何使用语法生成相应语言的字符串。 语法也可以执行相反的操作。 也就是说，给定一个字符串，可以将字符串分解为与用于生成它的语法部分相对应的组成部分-该字符串的*派生树*。 这些部分（以及其他相似字符串的部分）以后可以使用相同的语法重新组合以产生新的字符串。

在本章中，我们使用语法将一组有效的种子输入解析并分解为它们对应的派生树。 这种结构表示使我们能够变异，交叉和重组它们的部分，以生成新的有效的，稍有变化的输入（即模糊）

**前提条件**

*   您应该阅读语法的[一章。](Grammars.html)
*   还需要了解有关语法模糊检查器的[一章中的派生树。](GrammarFuzzer.html)

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.Parser](Parser.html) import <identifier>

```

然后利用以下功能。

本章介绍`Parser`类，将字符串解析为[派生树，如](GrammarFuzzer.html)[关于高效语法模糊](GrammarFuzzer.html)的一章中介绍的那样。 提供了两个重要的解析器类：

*   [解析表达式语法解析器](#Parsing-Expression-Grammars)（`PEGParser`），它非常有效，但仅限于特定的语法结构； 和
*   [Earley解析器](#Parsing-Context-Free-Grammars)（`EarleyParser`），它们接受任何类型的无上下文语法。

但是，使用其中任何一个都非常容易。 首先，用语法实例化它们：

```py
>>> from [Grammars](Grammars.html) import US_PHONE_GRAMMAR
>>> us_phone_parser = EarleyParser(US_PHONE_GRAMMAR)

```

然后，使用`parse()`方法检索可能的派生树的列表：

```py
>>> trees = us_phone_parser.parse("(555)987-6543")
>>> tree = list(trees)[0]
>>> display_tree(tree)

```

!

这些派生树然后可以用于测试生成，尤其是用于变异和重组现有输入。

## 模糊简单程序

这是一个简单的程序，它接受车辆详细信息的CSV文件并处理该信息。

```py
def process_inventory(inventory):
    res = []
    for vehicle in inventory.split('\n'):
        ret = process_vehicle(vehicle)
        res.extend(ret)
    return '\n'.join(res)

```

CSV文件包含每行一辆车的详细信息。 每行都在`process_vehicle()`中进行处理。

```py
def process_vehicle(vehicle):
    year, kind, company, model, *_ = vehicle.split(',')
    if kind == 'van':
        return process_van(year, company, model)

    elif kind == 'car':
        return process_car(year, company, model)

    else:
        raise Exception('Invalid entry')

```

根据车辆的种类，处理会发生变化。

```py
def process_van(year, company, model):
    res = ["We have a %s  %s van from %s vintage." % (company, model, year)]
    iyear = int(year)
    if iyear > 2010:
        res.append("It is a recent model!")
    else:
        res.append("It is an old but reliable model!")
    return res

```

```py
def process_car(year, company, model):
    res = ["We have a %s  %s car from %s vintage." % (company, model, year)]
    iyear = int(year)
    if iyear > 2016:
        res.append("It is a recent model!")
    else:
        res.append("It is an old but reliable model!")
    return res

```

这是`process_inventory()`接受的输入示例。

```py
mystring = """\
1997,van,Ford,E350
2000,car,Mercury,Cougar\
"""
print(process_inventory(mystring))

```

```py
We have a Ford E350 van from 1997 vintage.
It is an old but reliable model!
We have a Mercury Cougar car from 2000 vintage.
It is an old but reliable model!

```

让我们尝试模糊该程序。 假设`process_inventory()`带有CSV文件，我们可以编写一个简单的语法来生成逗号分隔的值，并生成所需的CSV行。 为了方便起见，我们直接对`process_vehicle()`进行模糊处理。

```py
import [string](https://docs.python.org/3/library/string.html)

```

```py
CSV_GRAMMAR = {
    '<start>': ['<csvline>'],
    '<csvline>': ['<items>'],
    '<items>': ['<item>,<items>', '<item>'],
    '<item>': ['<letters>'],
    '<letters>': ['<letter><letters>', '<letter>'],
    '<letter>': list(string.ascii_letters + string.digits + string.punctuation + ' \t\n')
}

```

首先，我们需要一些基础结构来查看语法。

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
from [Grammars](Grammars.html) import EXPR_GRAMMAR, START_SYMBOL, RE_NONTERMINAL, is_valid_grammar, syntax_diagram
from [Fuzzer](Fuzzer.html) import Fuzzer
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer, FasterGrammarFuzzer, display_tree, tree_to_string, dot_escape

from [ExpectError](ExpectError.html) import ExpectError
from [Timer](Timer.html) import Timer

```

```py
syntax_diagram(CSV_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 199.5 62" width="199.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="99.75" y="35">csvline</text></g></g></g></g></svg>

```py
csvline

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 182.5 62" width="182.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">items</text></g></g></g></g></svg>

```py
items

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 305.0 92" width="305.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="87.0" y="35">item</text></g> <g class="terminal"><text x="148.25" y="35">,</text></g> <g class="non-terminal"><text x="213.75" y="35">items</text></g></g> <g><g class="non-terminal"><text x="152.5" y="65">item</text></g></g></g></g></svg>

```py
item

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 199.5 62" width="199.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="99.75" y="35">letters</text></g></g></g></g></svg>

```py
letters

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 290.5 92" width="290.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="95.5" y="35">letter</text></g> <g class="non-terminal"><text x="190.75" y="35">letters</text></g></g> <g><g class="non-terminal"><text x="145.25" y="65">letter</text></g></g></g></g></svg>

```py
letter

```

 <svg class="railroad-diagram" height="618" viewBox="0 0 611.0 618" width="611.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="283">i</text></g></g> <g><g class="terminal"><text x="84.25" y="253">h</text></g></g> <g><g class="terminal"><text x="84.25" y="223">g</text></g></g> <g><g class="terminal"><text x="84.25" y="193">f</text></g></g> <g><g class="terminal"><text x="84.25" y="163">e</text></g></g> <g><g class="terminal"><text x="84.25" y="133">d</text></g></g> <g><g class="terminal"><text x="84.25" y="103">c</text></g></g> <g><g class="terminal"><text x="84.25" y="73">b</text></g></g> <g><g class="terminal"><text x="84.25" y="43">a</text></g></g> <g><g class="terminal"><text x="84.25" y="313">j</text></g></g> <g><g class="terminal"><text x="84.25" y="343">k</text></g></g> <g><g class="terminal"><text x="84.25" y="373">l</text></g></g> <g><g class="terminal"><text x="84.25" y="403">m</text></g></g> <g><g class="terminal"><text x="84.25" y="433">n</text></g></g> <g><g class="terminal"><text x="84.25" y="463">o</text></g></g> <g><g class="terminal"><text x="84.25" y="493">p</text></g></g> <g><g class="terminal"><text x="84.25" y="523">q</text></g></g> <g><g class="terminal"><text x="84.25" y="553">r</text></g></g> <g><g class="terminal"><text x="84.25" y="583">s</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="283">B</text></g></g> <g><g class="terminal"><text x="172.75" y="253">A</text></g></g> <g><g class="terminal"><text x="172.75" y="223">z</text></g></g> <g><g class="terminal"><text x="172.75" y="193">y</text></g></g> <g><g class="terminal"><text x="172.75" y="163">x</text></g></g> <g><g class="terminal"><text x="172.75" y="133">w</text></g></g> <g><g class="terminal"><text x="172.75" y="103">v</text></g></g> <g><g class="terminal"><text x="172.75" y="73">u</text></g></g> <g><g class="terminal"><text x="172.75" y="43">t</text></g></g> <g><g class="terminal"><text x="172.75" y="313">C</text></g></g> <g><g class="terminal"><text x="172.75" y="343">D</text></g></g> <g><g class="terminal"><text x="172.75" y="373">E</text></g></g> <g><g class="terminal"><text x="172.75" y="403">F</text></g></g> <g><g class="terminal"><text x="172.75" y="433">G</text></g></g> <g><g class="terminal"><text x="172.75" y="463">H</text></g></g> <g><g class="terminal"><text x="172.75" y="493">I</text></g></g> <g><g class="terminal"><text x="172.75" y="523">J</text></g></g> <g><g class="terminal"><text x="172.75" y="553">K</text></g></g> <g><g class="terminal"><text x="172.75" y="583">L</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="283">U</text></g></g> <g><g class="terminal"><text x="261.25" y="253">T</text></g></g> <g><g class="terminal"><text x="261.25" y="223">S</text></g></g> <g><g class="terminal"><text x="261.25" y="193">R</text></g></g> <g><g class="terminal"><text x="261.25" y="163">Q</text></g></g> <g><g class="terminal"><text x="261.25" y="133">P</text></g></g> <g><g class="terminal"><text x="261.25" y="103">O</text></g></g> <g><g class="terminal"><text x="261.25" y="73">N</text></g></g> <g><g class="terminal"><text x="261.25" y="43">M</text></g></g> <g><g class="terminal"><text x="261.25" y="313">V</text></g></g> <g><g class="terminal"><text x="261.25" y="343">W</text></g></g> <g><g class="terminal"><text x="261.25" y="373">X</text></g></g> <g><g class="terminal"><text x="261.25" y="403">Y</text></g></g> <g><g class="terminal"><text x="261.25" y="433">Z</text></g></g> <g><g class="terminal"><text x="261.25" y="463">0</text></g></g> <g><g class="terminal"><text x="261.25" y="493">1</text></g></g> <g><g class="terminal"><text x="261.25" y="523">2</text></g></g> <g><g class="terminal"><text x="261.25" y="553">3</text></g></g> <g><g class="terminal"><text x="261.25" y="583">4</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="283">$</text></g></g> <g><g class="terminal"><text x="349.75" y="253">#</text></g></g> <g><g class="terminal"><text x="349.75" y="223">"</text></g></g> <g><g class="terminal"><text x="349.75" y="193">!</text></g></g> <g><g class="terminal"><text x="349.75" y="163">9</text></g></g> <g><g class="terminal"><text x="349.75" y="133">8</text></g></g> <g><g class="terminal"><text x="349.75" y="103">7</text></g></g> <g><g class="terminal"><text x="349.75" y="73">6</text></g></g> <g><g class="terminal"><text x="349.75" y="43">5</text></g></g> <g><g class="terminal"><text x="349.75" y="313">%</text></g></g> <g><g class="terminal"><text x="349.75" y="343">&</text></g></g> <g><g class="terminal"><text x="349.75" y="373">'</text></g></g> <g><g class="terminal"><text x="349.75" y="403">(</text></g></g> <g><g class="terminal"><text x="349.75" y="433">)</text></g></g> <g><g class="terminal"><text x="349.75" y="463">*</text></g></g> <g><g class="terminal"><text x="349.75" y="493">+</text></g></g> <g><g class="terminal"><text x="349.75" y="523">,</text></g></g> <g><g class="terminal"><text x="349.75" y="553">-</text></g></g> <g><g class="terminal"><text x="349.75" y="583">.</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="283">[</text></g></g> <g><g class="terminal"><text x="438.25" y="253">@</text></g></g> <g><g class="terminal"><text x="438.25" y="223">?</text></g></g> <g><g class="terminal"><text x="438.25" y="193">></text></g></g> <g><g class="terminal"><text x="438.25" y="163">=</text></g></g> <g><g class="terminal"><text x="438.25" y="133"><</text></g></g> <g><g class="terminal"><text x="438.25" y="103">;</text></g></g> <g><g class="terminal"><text x="438.25" y="73">:</text></g></g> <g><g class="terminal"><text x="438.25" y="43">/</text></g></g> <g><g class="terminal"><text x="438.25" y="313">\</text></g></g> <g><g class="terminal"><text x="438.25" y="343">]</text></g></g> <g><g class="terminal"><text x="438.25" y="373">^</text></g></g> <g><g class="terminal"><text x="438.25" y="403">_</text></g></g> <g><g class="terminal"><text x="438.25" y="433">`</text></g></g> <g><g class="terminal"><text x="438.25" y="463">{</text></g></g> <g><g class="terminal"><text x="438.25" y="493">|</text></g></g> <g><g class="terminal"><text x="438.25" y="523">}</text></g></g> <g><g class="terminal"><text x="438.25" y="553">~</text></g></g></g></g></g></svg>

我们生成`1000`值，然后分别评估`process_vehicle()`。

```py
gf = GrammarFuzzer(CSV_GRAMMAR, min_nonterminals=4)
trials = 1000
valid = []
time = 0
for i in range(trials):
    with Timer() as t:
        vehicle_info = gf.fuzz()
        try:
            process_vehicle(vehicle_info)
            valid.append(vehicle_info)
        except:
            pass
        time += t.elapsed_time()
print("%d valid strings, that is GrammarFuzzer generated %f%% valid entries from %d inputs" %
      (len(valid), len(valid) * 100.0 / trials, trials))
print("Total time of %f seconds" % time)

```

```py
0 valid strings, that is GrammarFuzzer generated 0.000000% valid entries from 1000 inputs
Total time of 6.165338 seconds

```

这显然是行不通的。 但为什么？

```py
gf = GrammarFuzzer(CSV_GRAMMAR, min_nonterminals=4)
trials = 10
valid = []
time = 0
for i in range(trials):
    vehicle_info = gf.fuzz()
    try:
        print(repr(vehicle_info), end="")
        process_vehicle(vehicle_info)
    except Exception as e:
        print("\t", e)
    else:
        print()

```

```py
'9w9J\'/,LU<"l,|,Y,Zv)Amvx,c\n'	 Invalid entry
'(n8].H7,qolS'	 not enough values to unpack (expected at least 4, got 2)
'\nQoLWQ,jSa'	 not enough values to unpack (expected at least 4, got 2)
'K1,\n,RE,fq,%,,sT+aAb'	 Invalid entry
"m,d,,8j4'),-yQ,B7"	 Invalid entry
'g4,s1\t[}{.,M,<,\nzd,.am'	 Invalid entry
',Z[,z,c,#x1,gc.F'	 Invalid entry
'pWs,rT`,R'	 not enough values to unpack (expected at least 4, got 3)
'iN,br%,Q,R'	 Invalid entry
'ol,\nH<\tn,^#,=A'	 Invalid entry

```

除非模糊器可以产生`van`或`car`，否则所有条目都不会通过。 确实，原因是语法本身无法捕获有关格式的完整信息。 所以这是另一个想法。 我们对`GrammarFuzzer`进行了修改，以对我们的格式有所了解。

```py
import [copy](https://docs.python.org/3/library/copy.html)

```

```py
import [random](https://docs.python.org/3/library/random.html)

```

```py
class PooledGrammarFuzzer(GrammarFuzzer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._node_cache = {}

    def update_cache(self, key, values):
        self._node_cache[key] = values

    def expand_node_randomly(self, node):
        (symbol, children) = node
        assert children is None
        if symbol in self._node_cache:
            if random.randint(0, 1) == 1:
                return super().expand_node_randomly(node)
            return copy.deepcopy(random.choice(self._node_cache[symbol]))
        return super().expand_node_randomly(node)

```

让我们再试一次！

```py
gf = PooledGrammarFuzzer(CSV_GRAMMAR, min_nonterminals=4)
gf.update_cache('<item>', [
    ('<item>', [('car', [])]),
    ('<item>', [('van', [])]),
])
trials = 10
valid = []
time = 0
for i in range(trials):
    vehicle_info = gf.fuzz()
    try:
        print(repr(vehicle_info), end="")
        process_vehicle(vehicle_info)
    except Exception as e:
        print("\t", e)
    else:
        print()

```

```py
',h,van,|'	 Invalid entry
'M,w:K,car,car,van'	 Invalid entry
'J,?Y,van,van,car,J,~D+'	 Invalid entry
'S4,car,car,o'	 invalid literal for int() with base 10: 'S4'
'2*-,van'	 not enough values to unpack (expected at least 4, got 2)
'van,%,5,]'	 Invalid entry
'van,G3{y,j,h:'	 Invalid entry
'$0;o,M,car,car'	 Invalid entry
'2d,f,e'	 not enough values to unpack (expected at least 4, got 3)
'/~NE,car,car'	 not enough values to unpack (expected at least 4, got 3)

```

至少我们到了某个地方！ 如果*我们可以将我们对采样数据的了解纳入我们的模糊器中，那就太好了。* 实际上，如果我们可以*从样本中提取*模板和有效值，并在模糊测试中使用它们，那就太好了。 我们该怎么做？ 这个问题的快速答案是：使用*解析器*。

## 使用解析器

一般来说，*解析器*是处理（结构化）输入的程序的一部分。 我们在本章中讨论的解析器将输入字符串转换为*派生树*（在有关有效语法模糊的[章中进行了讨论）。 从用户的角度来看，解析输入只需要两个步骤：](GrammarFuzzer.html)

1.  用语法初始化解析器，如

    ```py
    parser = Parser(grammar)
    ```

2.  使用解析器检索派生树列表：

```
trees = parser.parse(input)

```py

解析完树后，就可以像语法模糊产生的派生树一样使用它。

我们讨论了许多这样的解析器，特别是

*   [解析表达语法解析器](#Parsing-Expression-Grammars)（`PEGParser`），它非常有效，但仅限于特定的语法结构； 和
*   [Earley解析器](#Parsing-Context-Free-Grammars)（`EarleyParser`），它们接受任何类型的无上下文语法。

如果您只想*使用*解析器（例如，因为您的主要重点是测试），则可以在此处停下来，将[移至下一章](LangFuzzer.html)，我们将在其中学习如何使用 的输入进行突变和重组。 但是，如果您想*了解*解析器的工作原理，那么本章非常适合您。

## 临时解析器

正如我们在上一节中所看到的，程序员经常必须提取遵守某些规则的部分数据。 例如，对于 *CSV* 文件，一行中的每个元素都由*逗号*分隔，并且多个原始数据用于存储数据。

为了提取信息，我们编写了一个临时解析器`parse_csv()`。

```
def parse_csv(mystring):
    children = []
    tree = (START_SYMBOL, children)
    for i, line in enumerate(mystring.split('\n')):
        children.append(("record %d" % i, [(cell, [])
                                           for cell in line.split(',')]))
    return tree

```py

我们还将图形的默认方向从左至右更改为，而不是从上至下*从上至下*，以便于使用`lr_graph()`进行查看。

```
def lr_graph(dot):
    dot.attr('node', shape='plain')
    dot.graph_attr['rankdir'] = 'LR'

```py

`display_tree()`显示了解析后的CSV文件的结构。

```
tree = parse_csv(mystring)
display_tree(tree, graph_attr=lr_graph)

```py

<svg height="254pt" viewBox="0.00 0.00 216.00 254.00" width="216pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 250)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20" y="-118.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-168.8">record 0</text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-69.8">record 1</text></g> <g class="edge" id="edge6"><title>0->6</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-234.8">1997</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-201.8">van</text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-168.8">Ford</text></g> <g class="edge" id="edge4"><title>1->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-135.8">E350</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-102.8">2000</text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-69.8">car</text></g> <g class="edge" id="edge8"><title>6->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-36.8">Mercury</text></g> <g class="edge" id="edge9"><title>6->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="183.5" y="-3.8">Cougar</text></g> <g class="edge" id="edge10"><title>6->10</title></g></g></svg>

这当然很简单。 如果遇到更多复杂性该怎么办？ 再次，来自维基百科的另一个例子。

```
mystring = '''\
1997,Ford,E350,"ac, abs, moon",3000.00\
'''
print(mystring)

```py

```
1997,Ford,E350,"ac, abs, moon",3000.00

```

我们定义了一种新的注释方法`highlight_node()`来标记有趣的节点。

```py
def highlight_node(predicate):
    def hl_node(dot, nid, symbol, ann):
        if predicate(dot, nid, symbol, ann):
            dot.node(repr(nid), dot_escape(symbol), fontcolor='red')
        else:
            dot.node(repr(nid), dot_escape(symbol))
    return hl_node

```

使用`highlight_node()`，我们可以突出显示错误解析的特定节点。

```py
tree = parse_csv(mystring)
bad_nodes = {5, 6, 7, 12, 13, 20, 22, 23, 24, 25}

```

```py
def hl_predicate(_d, nid, _s, _a): return nid in bad_nodes

```

```py
highlight_err_node = highlight_node(hl_predicate)
display_tree(tree, log=False, node_attr=highlight_err_node,
             graph_attr=lr_graph)

```

<svg height="221pt" viewBox="0.00 0.00 213.00 221.00" width="213pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 217)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20" y="-102.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-102.8">record 0</text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-201.8">1997</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-168.8">Ford</text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-135.8">E350</text></g> <g class="edge" id="edge4"><title>1->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-102.8">"ac</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-69.8">abs</text></g> <g class="edge" id="edge6"><title>1->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-36.8">moon"</text></g> <g class="edge" id="edge7"><title>1->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="182" y="-3.8">3000.00</text></g> <g class="edge" id="edge8"><title>1->8</title></g></g></svg>

标记的节点指示我们的解析错误。 我们当然可以扩展解析器以理解引号。 首先，我们定义一些辅助函数`parse_quote()`，`find_comma()`和`comma_split()`

```py
def parse_quote(string, i):
    v = string[i + 1:].find('"')
    return v + i + 1 if v >= 0 else -1

```

```py
def find_comma(string, i):
    slen = len(string)
    while i < slen:
        if string[i] == '"':
            i = parse_quote(string, i)
            if i == -1:
                return -1
        if string[i] == ',':
            return i
        i += 1
    return -1

```

```py
def comma_split(string):
    slen = len(string)
    i = 0
    while i < slen:
        c = find_comma(string, i)
        if c == -1:
            yield string[i:]
            return
        else:
            yield string[i:c]
        i = c + 1

```

我们可以更新`parse_csv()`过程以使用高级报价解析器。

```py
def parse_csv(mystring):
    children = []
    tree = (START_SYMBOL, children)
    for i, line in enumerate(mystring.split('\n')):
        children.append(("record %d" % i, [(cell, [])
                                           for cell in comma_split(line)]))
    return tree

```

我们的新产品`parse_csv()`现在可以正确处理引号。

```py
tree = parse_csv(mystring)
display_tree(tree, graph_attr=lr_graph)

```

<svg height="155pt" viewBox="0.00 0.00 256.00 155.00" width="256pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 151)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20" y="-69.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-69.8">record 0</text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="203.5" y="-135.8">1997</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="203.5" y="-102.8">Ford</text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="203.5" y="-69.8">E350</text></g> <g class="edge" id="edge4"><title>1->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="203.5" y="-36.8">"ac, abs, moon"</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="203.5" y="-3.8">3000.00</text></g> <g class="edge" id="edge6"><title>1->6</title></g></g></svg>

那当然不会长久生存：

```py
mystring = '''\
1999,Chevy,"Venture \\"Extended Edition, Very Large\\"",,5000.00\
'''
print(mystring)

```

```py
1999,Chevy,"Venture \"Extended Edition, Very Large\"",,5000.00

```

一些嵌入的引号足以使我们的解析器再次困惑。

```py
tree = parse_csv(mystring)
bad_nodes = {4, 5}
display_tree(tree, node_attr=highlight_err_node, graph_attr=lr_graph)

```

<svg height="174pt" viewBox="0.00 0.00 329.00 174.00" width="329pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 170)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20" y="-71.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-71.8">record 0</text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="240" y="-154.8">1999</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="240" y="-121.8">Chevy</text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="240" y="-88.8">"Venture \"Extended Edition</text></g> <g class="edge" id="edge4"><title>1->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="240" y="-55.8">Very Large\""</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node7"><title>6</title></g> <g class="edge" id="edge6"><title>1->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="240" y="-3.8">5000.00</text></g> <g class="edge" id="edge7"><title>1->7</title></g></g></svg>

这是该CSV文件中的另一条记录：

```py
mystring = '''\
1996,Jeep,Grand Cherokee,"MUST SELL!
air, moon roof, loaded",4799.00
'''
print(mystring)

```

```py
1996,Jeep,Grand Cherokee,"MUST SELL!
air, moon roof, loaded",4799.00

```

```py
tree = parse_csv(mystring)
bad_nodes = {5, 6, 7, 8, 9, 10}
display_tree(tree, node_attr=highlight_err_node, graph_attr=lr_graph)

```

<svg height="221pt" viewBox="0.00 0.00 263.00 221.00" width="263pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 217)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20" y="-53.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-135.8">record 0</text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node7"><title>6</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-53.8">record 1</text></g> <g class="edge" id="edge6"><title>0->6</title></g> <g class="node" id="node11"><title>10</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="99.5" y="-20.8">record 2</text></g> <g class="edge" id="edge10"><title>0->10</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-201.8">1996</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-168.8">Jeep</text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-135.8">Grand Cherokee</text></g> <g class="edge" id="edge4"><title>1->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-102.8">"MUST SELL!</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node8"><title>7</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-69.8">air</text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-36.8">moon roof</text></g> <g class="edge" id="edge8"><title>6->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#ff0000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="207" y="-3.8">loaded",4799.00</text></g> <g class="edge" id="edge9"><title>6->9</title></g></g></svg>

要解决此问题，将需要同时修改内部`parse_quote()`和外部`parse_csv()`过程。 我们注意到，每个功能实际上都记录在CSV [RFC 4180](https://tools.ietf.org/html/rfc4180) 中

确实，每一个额外的改进都变得有些分散，即使有一点额外的复杂性。 当人们遇到递归表达式时，这个问题变得很严重。 例如，JSON是CSV文件的常用替代方案，用于保存数据。 同样，如果要从Web获取数据，则可能不得不从HTML表而不是CSV文件解析数据。

可能会尝试通过一点点临时解析来解决此问题，并添加一些*正则表达式*。但是，这是[通往精神错乱](https://stackoverflow.com/a/1732454)的途径。

*正式解析器*在这里闪耀。 主要思想是，任何给定的字符串集都属于一种语言，并且这些语言可以由其语法指定（正如我们在关于的语法[一章中所看到的）。 关于语法的伟大之处在于它们可以由*组成*。 即，可以在不影响外部结构的情况下将越来越精细的细节引入内部结构，并且类似地，可以在不对内部结构造成很大影响的情况下改变外部结构。 我们将在下一节中简要描述语法。](Grammars.html)

## 语法

正如您从语法的[章中所阅读的，语法是一组*规则*，它们解释了如何扩展起始符号。 每个规则都有一个名称，也称为*非终结点*，以及一组如何扩展非终结点的*替代选择*。](Grammars.html)

```py
A1_GRAMMAR = {
    "<start>": ["<expr>"],
    "<expr>": ["<expr>+<expr>", "<expr>-<expr>", "<integer>"],
    "<integer>": ["<digit><integer>", "<digit>"],
    "<digit>": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
}

```

```py
syntax_diagram(A1_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 174.0 62" width="174.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="87.0" y="35">expr</text></g></g></g></g></svg>

```py
expr

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 296.5 122" width="296.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="87.0" y="35">expr</text></g> <g class="terminal"><text x="148.25" y="35">+</text></g> <g class="non-terminal"><text x="209.5" y="35">expr</text></g></g> <g><g class="non-terminal"><text x="87.0" y="65">expr</text></g> <g class="terminal"><text x="148.25" y="65">-</text></g> <g class="non-terminal"><text x="209.5" y="65">expr</text></g></g> <g><g class="non-terminal"><text x="148.25" y="95">integer</text></g></g></g></g></svg>

```py
integer

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 282.0 92" width="282.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">digit</text></g> <g class="non-terminal"><text x="182.25" y="35">integer</text></g></g> <g><g class="non-terminal"><text x="141.0" y="65">digit</text></g></g></g></g></svg>

```py
digit

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 522.5 109" width="522.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">6</text></g></g> <g><g class="terminal"><text x="349.75" y="73">7</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">8</text></g></g> <g><g class="terminal"><text x="438.25" y="73">9</text></g></g></g></g></g></svg>

在以上表达式中，规则`<expr> : [<expr>+<expr>,<expr>-<expr>,<integer>]`对应于如何扩展非终端`<expr>`。 表达式`<expr>+<expr>`对应于备选选择之一。 我们称此为非末端`<expr>`的*替代*扩展。 最后，在表达式`<expr>+<expr>`中，`<expr>`，`+`和`<expr>`中的每个都是该扩展中的*符号*。 根据符号的扩展在语法中是否可用，符号可以是非终结符也可以是终结符。

这是一个代表我们要解析的算术表达式的字符串，由上面的语法指定：

```py
mystring = '1+2'

```

用于此表达式的*派生树*的表达式为：

```py
tree = ('<start>', [('<expr>',
                     [('<expr>', [('<integer>', [('<digit>', [('1', [])])])]),
                      ('+', []),
                      ('<expr>', [('<integer>', [('<digit>', [('2',
                                                               [])])])])])])
assert mystring == tree_to_string(tree)
display_tree(tree)

```

<svg height="278pt" viewBox="0.00 0.00 149.00 278.00" width="149pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 274)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-258.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-207.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><expr></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-156.8">+</text></g> <g class="edge" id="edge6"><title>1->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="113.5" y="-156.8"><expr></text></g> <g class="edge" id="edge7"><title>1->7</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><integer></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8"><digit></text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-3.8">1</text></g> <g class="edge" id="edge5"><title>4->5</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="113.5" y="-105.8"><integer></text></g> <g class="edge" id="edge8"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="113.5" y="-54.8"><digit></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="113.5" y="-3.8">2</text></g> <g class="edge" id="edge10"><title>9->10</title></g></g></svg>

虽然可以使用一种语法来指定一种给定的语言，但是可能有多个语法与同一种语言相对应。 例如，这是描述相同加法表达式的另一种语法。

```py
A2_GRAMMAR = {
    "<start>": ["<expr>"],
    "<expr>": ["<integer><expr_>"],
    "<expr_>": ["+<expr>", "-<expr>", ""],
    "<integer>": ["<digit><integer_>"],
    "<integer_>": ["<integer>", ""],
    "<digit>": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
}

```

```py
syntax_diagram(A2_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 174.0 62" width="174.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="87.0" y="35">expr</text></g></g></g></g></svg>

```py
expr

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 282.0 62" width="282.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="99.75" y="35">integer</text></g> <g class="non-terminal"><text x="190.75" y="35">expr_</text></g></g></g></g></svg>

```py
expr_

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 222.5 122" width="222.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">+</text></g> <g class="non-terminal"><text x="135.5" y="35">expr</text></g></g> <g><g class="terminal"><text x="74.25" y="65">-</text></g> <g class="non-terminal"><text x="135.5" y="65">expr</text></g></g></g></g></svg>

```py
integer

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 290.5 62" width="290.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">digit</text></g> <g class="non-terminal"><text x="186.5" y="35">integer_</text></g></g></g></g></svg>

```py
integer_

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 199.5 92" width="199.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="99.75" y="35">integer</text></g></g></g></g></svg>

```py
digit

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 522.5 109" width="522.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">6</text></g></g> <g><g class="terminal"><text x="349.75" y="73">7</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">8</text></g></g> <g><g class="terminal"><text x="438.25" y="73">9</text></g></g></g></g></g></svg>

相应的派生树由下式给出：

```py
tree = ('<start>', [('<expr>', [('<integer>', [('<digit>', [('1', [])]),
                                               ('<integer_>', [])]),
                                ('<expr_>', [('+', []),
                                             ('<expr>',
                                              [('<integer>',
                                                [('<digit>', [('2', [])]),
                                                 ('<integer_>', [])]),
                                               ('<expr_>', [])])])])])
assert mystring == tree_to_string(tree)
display_tree(tree)

```

<svg height="329pt" viewBox="0.00 0.00 262.00 329.00" width="262pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 325)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="121" y="-309.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="121" y="-258.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="86" y="-207.8"><integer></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="156" y="-207.8"><expr_></text></g> <g class="edge" id="edge6"><title>1->6</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="21" y="-156.8"><digit></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="91" y="-156.8"><integer_></text></g> <g class="edge" id="edge5"><title>2->5</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="21" y="-105.8">1</text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="152" y="-156.8">+</text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="195" y="-156.8"><expr></text></g> <g class="edge" id="edge8"><title>6->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="160" y="-105.8"><integer></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="230" y="-105.8"><expr_></text></g> <g class="edge" id="edge13"><title>8->13</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="125" y="-54.8"><digit></text></g> <g class="edge" id="edge10"><title>9->10</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="195" y="-54.8"><integer_></text></g> <g class="edge" id="edge12"><title>9->12</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="125" y="-3.8">2</text></g> <g class="edge" id="edge11"><title>10->11</title></g></g></svg>

实际上，可能存在描述同一语言的不同类别的语法。 例如，第一个语法`A1_GRAMMAR`是同时向右和*向左*递归的语法，而第二个语法`A2_GRAMMAR`在以下任何一个的非终结符中都没有向左递归 其产品，但包含 *epsilon* 产品。 （epsilon生产是在右侧具有空字符串的生产。）

您可能已经注意到，我们在其自己的定义中重复使用了`<expr>`一词。 在其自己的定义中使用相同的非终结符称为*递归*。 解析有两种特定的递归类型，我们将在下一节中看到。

#### 递归

如果语法的任何非终结符均为左递归，则语法为*左递归*，并且如果语法的任何非产生符最左端的符号本身为非语法，则非终结符直接为左递归。

```py
LR_GRAMMAR = {
    '<start>': ['<A>'],
    '<A>': ['<A>a', ''],
}

```

```py
syntax_diagram(LR_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="74.25" y="35">A</text></g></g></g></g></svg>

```py
A

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 197.0 92" width="197.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="74.25" y="35">A</text></g> <g class="terminal"><text x="122.75" y="35">a</text></g></g></g></g></svg>

```py
mystring = 'aaaaaa'
display_tree(
    ('<start>', (('<A>', (('<A>', (('<A>', []), ('a', []))), ('a', []))), ('a', []))))

```

<svg height="176pt" viewBox="0.00 0.00 93.50 176.00" width="94pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 172)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="64" y="-156.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="47" y="-105.8"><A></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="82" y="-105.8">a</text></g> <g class="edge" id="edge6"><title>0->6</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="30" y="-54.8"><A></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="65" y="-54.8">a</text></g> <g class="edge" id="edge5"><title>1->5</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="13" y="-3.8"><A></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="48" y="-3.8">a</text></g> <g class="edge" id="edge4"><title>2->4</title></g></g></svg>

如果可以使用其定义来扩展最左边的符号中的任何一个，以产生非终结符作为扩展的最左边的符号，则语法是间接左递归的。 如果在非终结符的一系列扩展过程中，一个左递归被称为*隐藏左递归*，则该规则到达了一个规则，其中该规则在其他符号的前缀之后包含相同的非终结符，并且这些符号可以派生 空字符串。 例如，在`A1_GRAMMAR`中，如果`<digit>`可以派生空字符串，则将`<integer>`视为左隐藏递归。

右递归语法的定义与此类似。 下面是正确的递归语法的派生树，该递归树表示与`LR_GRAMMAR`相同的语言。

```py
RR_GRAMMAR = {
    '<start>': ['<A>'],
    '<A>': ['a<A>', ''],
}

```

```py
syntax_diagram(RR_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="74.25" y="35">A</text></g></g></g></g></svg>

```py
A

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 197.0 92" width="197.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">a</text></g> <g class="non-terminal"><text x="122.75" y="35">A</text></g></g></g></g></svg>

```py
display_tree(('<start>', ((
    '<A>', (('a', []), ('<A>', (('a', []), ('<A>', (('a', []), ('<A>', []))))))),)))

```

<svg height="227pt" viewBox="0.00 0.00 93.50 227.00" width="94pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 223)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20.5" y="-207.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20.5" y="-156.8"><A></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="3.5" y="-105.8">a</text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="38.5" y="-105.8"><A></text></g> <g class="edge" id="edge3"><title>1->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="20.5" y="-54.8">a</text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="55.5" y="-54.8"><A></text></g> <g class="edge" id="edge5"><title>3->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="37.5" y="-3.8">a</text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="72.5" y="-3.8"><A></text></g> <g class="edge" id="edge7"><title>5->7</title></g></g></svg>

#### 歧义

为了使问题进一步复杂化，可能有多个派生树（也称为*解析*），它们对应于同一语法中的同一字符串。 例如，字符串`1+2+3`可以通过以下两种方式使用`A1_GRAMMAR`进行解析

```py
mystring = '1+2+3'
tree = ('<start>',
        [('<expr>',
          [('<expr>', [('<expr>', [('<integer>', [('<digit>', [('1', [])])])]),
                       ('+', []),
                       ('<expr>', [('<integer>',
                                    [('<digit>', [('2', [])])])])]), ('+', []),
           ('<expr>', [('<integer>', [('<digit>', [('3', [])])])])])])
assert mystring == tree_to_string(tree)
display_tree(tree)

```

<svg height="329pt" viewBox="0.00 0.00 215.00 329.00" width="215pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 325)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="130.5" y="-309.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="130.5" y="-258.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="81.5" y="-207.8"><expr></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="130.5" y="-207.8">+</text></g> <g class="edge" id="edge12"><title>1->12</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="176.5" y="-207.8"><expr></text></g> <g class="edge" id="edge13"><title>1->13</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><expr></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-156.8">+</text></g> <g class="edge" id="edge7"><title>2->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="113.5" y="-156.8"><expr></text></g> <g class="edge" id="edge8"><title>2->8</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><integer></text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8"><digit></text></g> <g class="edge" id="edge5"><title>4->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-3.8">1</text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="112.5" y="-105.8"><integer></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="112.5" y="-54.8"><digit></text></g> <g class="edge" id="edge10"><title>9->10</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="112.5" y="-3.8">2</text></g> <g class="edge" id="edge11"><title>10->11</title></g> <g class="node" id="node15"><title>14</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-156.8"><integer></text></g> <g class="edge" id="edge14"><title>13->14</title></g> <g class="node" id="node16"><title>15</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-105.8"><digit></text></g> <g class="edge" id="edge15"><title>14->15</title></g> <g class="node" id="node17"><title>16</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-54.8">3</text></g> <g class="edge" id="edge16"><title>15->16</title></g></g></svg>

```py
tree = ('<start>',
        [('<expr>', [('<expr>', [('<integer>', [('<digit>', [('1', [])])])]),
                     ('+', []),
                     ('<expr>',
                      [('<expr>', [('<integer>', [('<digit>', [('2', [])])])]),
                       ('+', []),
                       ('<expr>', [('<integer>', [('<digit>', [('3',
                                                                [])])])])])])])
assert tree_to_string(tree) == mystring
display_tree(tree)

```

<svg height="329pt" viewBox="0.00 0.00 215.00 329.00" width="215pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 325)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="87.5" y="-309.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="87.5" y="-258.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="38.5" y="-207.8"><expr></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="87.5" y="-207.8">+</text></g> <g class="edge" id="edge6"><title>1->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="133.5" y="-207.8"><expr></text></g> <g class="edge" id="edge7"><title>1->7</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><integer></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><digit></text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8">1</text></g> <g class="edge" id="edge5"><title>4->5</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="93.5" y="-156.8"><expr></text></g> <g class="edge" id="edge8"><title>7->8</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="136.5" y="-156.8">+</text></g> <g class="edge" id="edge12"><title>7->12</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-156.8"><expr></text></g> <g class="edge" id="edge13"><title>7->13</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="94.5" y="-105.8"><integer></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="94.5" y="-54.8"><digit></text></g> <g class="edge" id="edge10"><title>9->10</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="94.5" y="-3.8">2</text></g> <g class="edge" id="edge11"><title>10->11</title></g> <g class="node" id="node15"><title>14</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-105.8"><integer></text></g> <g class="edge" id="edge14"><title>13->14</title></g> <g class="node" id="node16"><title>15</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-54.8"><digit></text></g> <g class="edge" id="edge15"><title>14->15</title></g> <g class="node" id="node17"><title>16</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="179.5" y="-3.8">3</text></g> <g class="edge" id="edge16"><title>15->16</title></g></g></svg>

有许多解决歧义的方法。 下一节中解释的*解析表达式语法*采取的一种方法是指定特定的分辨率顺序，然后选择第一个。 另一种方法是简单地返回所有可能的派生树，这是我们稍后开发的 *Earley解析器*所采用的方法。

接下来，我们开发不同的解析器。 为此，我们定义了一个最小的解析接口，所有解析器都遵守该接口。 有两种使用语法解析字符串的方法。

1.  传统方法是使用*词法分析器*（也称为*标记器*或*扫描器*）首先标记输入的字符串，然后在一个 时间。 该词法分析器通常是一个较小的解析器，它接受*常规语言*。 这种方法的优点是解析器使用的语法可以避开令牌化的细节。 此外，在解析结束时得到一浅层推导树，该树可直接用于生成*抽象语法树*。
2.  第二种方法是在完整解析之后使用树修剪器。 通过这种方法，可以使用一种语法，其中包含语法的完整细节。 接下来，修剪对应于令牌的节点，并用其对应的字符串替换为叶节点。 此方法的实用性是解析器功能更强大，并且*词法分析*和*语法分析*之间没有人为的区别。

在本章中，我们使用第二种方法。 此方法在`prune_tree`方法中实现。

我们在下面定义的 *Parser* 类提供了最小的接口。 实现此接口的类需要实现的主要方法是`parse_prefix`和`parse`。 `parse_prefix`返回一个元组，该元组包含直到成功完成解析为止的索引，以及直到该索引为止的解析林。 如果解析成功，则方法`parse`返回派生树的列表。

```py
class Parser(object):
    def __init__(self, grammar, **kwargs):
        self._grammar = grammar
        self._start_symbol = kwargs.get('start_symbol', START_SYMBOL)
        self.log = kwargs.get('log', False)
        self.coalesce_tokens = kwargs.get('coalesce', True)
        self.tokens = kwargs.get('tokens', set())

    def grammar(self):
        return self._grammar

    def start_symbol(self):
        return self._start_symbol

    def parse_prefix(self, text):
        """Return pair (cursor, forest) for longest prefix of text"""
        raise NotImplemented()

    def parse(self, text):
        cursor, forest = self.parse_prefix(text)
        if cursor < len(text):
            raise SyntaxError("at " + repr(text[cursor:]))
        return [self.prune_tree(tree) for tree in forest]

    def coalesce(self, children):
        last = ''
        new_lst = []
        for cn, cc in children:
            if cn not in self._grammar:
                last += cn
            else:
                if last:
                    new_lst.append((last, []))
                    last = ''
                new_lst.append((cn, cc))
        if last:
            new_lst.append((last, []))
        return new_lst

    def prune_tree(self, tree):
        name, children = tree
        if self.coalesce_tokens:
            children = self.coalesce(children)
        if name in self.tokens:
            return (name, [(tree_to_string(tree), [])])
        else:
            return (name, [self.prune_tree(c) for c in children])

```

## 解析表达式语法

*[解析表达语法](http://bford.info/pub/lang/peg)* （ *PEG* ）[ [Ford *等人*，2004。](https://doi.org/10.1145/982962.964011)是*基于识别的形式语法*，用于指定解析给定字符串所要采取的步骤顺序。 *解析表达语法*与*上下文无关的语法*（ *CFG* ）非常相似，例如我们在[语法](Grammars.html)一章中看到的语法 ]。 与CFG中一样，解析表达式语法由一组非终结符和相应的替代项表示，该替代项代表如何匹配每个语法。 例如，这是与`a`或`b`匹配的PEG。

```py
PEG1 = {
    '<start>': ['a', 'b']
}

```

但是，与 *CFG* 不同，替代方案表示*有序选择*。 也就是说，我们没有选择所有可能匹配的规则，而是在成功的第一个匹配处停止。 例如，下面的 *PEG* 可以与`ab`匹配，但与 *CFG* 都可以匹配的`abc`不匹配。 （我们将有序选择表达式*选择表达式*的序列，而不是使与 *CFG* 区别清楚的替代方案。）

```py
PEG2 = {
    '<start>': ['ab', 'abc']
}

```

*选择表达式*中的每个选择代表有关如何满足该特定选择的规则。 选择是与 *CFG* 中的给定文本匹配的符号序列（末端和非末端）。

除了到目前为止我们所看到的语法定义的语法之外， *PEG* 还可以包含一些其他元素。 有关更多信息，请参见本章末尾的练习。

PEG为手写递归下降解析器中的典型实践建模，因此可以认为它更直观易懂。 接下来我们看一下PEG的解析器。

### 谓词表达语法的Packrat解析器

缺少手动旋转解析器的功能， *Packrat* 解析是最简单的解析技术之一，也是解析PEG的技术之一。 *Packrat* 解析器之所以如此命名，是因为它试图缓存来自较简单问题的所有结果，以希望这些解决方案可用于以后避免重新计算。 接下来，我们将开发一个最小的 *Packrat* 解析器。

但是在此之前，我们需要实现一些支持工具。

我们从关于语法的[一章中导入的`EXPR_GRAMMAR`面向生成。 具体来说，生产规则存储为字符串。 我们需要稍微修饰一下此表示，以使其符合规范表示，其中规则中的每个标记都单独表示。 `canonical`格式使用单独的标记表示扩展中的每个符号。](Grammars.html)

```py
import [re](https://docs.python.org/3/library/re.html)

```

```py
def canonical(grammar, letters=False):
    def split(expansion):
        if isinstance(expansion, tuple):
            expansion = expansion[0]

        return [token for token in re.split(
            RE_NONTERMINAL, expansion) if token]

    def tokenize(word):
        return list(word) if letters else [word]

    def canonical_expr(expression):
        return [
            token for word in split(expression)
            for token in ([word] if word in grammar else tokenize(word))
        ]

    return {
        k: [canonical_expr(expression) for expression in alternatives]
        for k, alternatives in grammar.items()
    }

```

```py
canonical(EXPR_GRAMMAR)

```

```py
{'<start>': [['<expr>']],
 '<expr>': [['<term>', ' + ', '<expr>'],
  ['<term>', ' - ', '<expr>'],
  ['<term>']],
 '<term>': [['<factor>', ' * ', '<term>'],
  ['<factor>', ' / ', '<term>'],
  ['<factor>']],
 '<factor>': [['+', '<factor>'],
  ['-', '<factor>'],
  ['(', '<expr>', ')'],
  ['<integer>', '.', '<integer>'],
  ['<integer>']],
 '<integer>': [['<digit>', '<integer>'], ['<digit>']],
 '<digit>': [['0'],
  ['1'],
  ['2'],
  ['3'],
  ['4'],
  ['5'],
  ['6'],
  ['7'],
  ['8'],
  ['9']]}

```

在解析过程中使用`canonical`表示更容易。 因此，我们更新了解析器类以存储`canonical`表示形式。

```py
class Parser(Parser):
    def __init__(self, grammar, **kwargs):
        self._grammar = grammar
        self._start_symbol = kwargs.get('start_symbol', START_SYMBOL)
        self.log = kwargs.get('log', False)
        self.tokens = kwargs.get('tokens', set())
        self.coalesce_tokens = kwargs.get('coalesce', True)
        self.cgrammar = canonical(grammar)

```

### 解析器

我们首先从`Parser`基类派生，然后接受`parse()`方法中要解析的文本，该方法进而将`unify_key()`与`start_symbol`一起调用。

**注意。** 虽然我们的PEG解析器只能生成一个唯一的解析树，但其他解析器却可以为歧义语法生成多个解析。 因此，我们返回树的列表（在本例中为单个元素）。

```py
class PEGParser(Parser):
    def parse_prefix(self, text):
        cursor, tree = self.unify_key(self.start_symbol(), text, 0)
        return cursor, [tree]

```

#### 统一密钥

`unify_key()`算法很简单。 如果给定终端符号，它将尝试使符号与文本中的当前位置匹配。 如果符号和文本匹配，它将成功返回新的分析索引`at`。

另一方面，如果给它一个非终结符，它将检索对应于键的选择表达式，并尝试使用`unify_rule()`按顺序匹配每个选择*。 如果**中的任何**规则都成功与给定文本统一，则该解析被视为成功，并且我们将返回`unify_rule()`返回的新解析索引。*

```py
class PEGParser(PEGParser):
    def unify_key(self, key, text, at=0):
        if self.log:
            print("unify_key: %s with %s" % (repr(key), repr(text[at:])))
        if key not in self.cgrammar:
            if text[at:].startswith(key):
                return at + len(key), (key, [])
            else:
                return at, None
        for rule in self.cgrammar[key]:
            to, res = self.unify_rule(rule, text, at)
            if res:
                return (to, (key, res))
        return 0, None

```

```py
mystring = "1"
peg = PEGParser(EXPR_GRAMMAR, log=True)
peg.unify_key('1', mystring)

```

```py
unify_key: '1' with '1'

```

```py
(1, ('1', []))

```

```py
mystring = "2"
peg.unify_key('1', mystring)

```

```py
unify_key: '1' with '2'

```

```py
(0, None)

```

#### 统一规则

`unify_rule()`方法相似。 它检索与需要与文本统一的规则相对应的标记，并依次对它们调用`unify_key()`。 如果**所有**令牌与文本成功统一，则解析成功。

```py
class PEGParser(PEGParser):
    def unify_rule(self, rule, text, at):
        if self.log:
            print('unify_rule: %s with %s' % (repr(rule), repr(text[at:])))
        results = []
        for token in rule:
            at, res = self.unify_key(token, text, at)
            if res is None:
                return at, None
            results.append(res)
        return at, results

```

```py
mystring = "0"
peg = PEGParser(EXPR_GRAMMAR, log=True)
peg.unify_rule(peg.cgrammar['<digit>'][0], mystring, 0)

```

```py
unify_rule: ['0'] with '0'
unify_key: '0' with '0'

```

```py
(1, [('0', [])])

```

```py
mystring = "12"
peg.unify_rule(peg.cgrammar['<integer>'][0], mystring, 0)

```

```py
unify_rule: ['<digit>', '<integer>'] with '12'
unify_key: '<digit>' with '12'
unify_rule: ['0'] with '12'
unify_key: '0' with '12'
unify_rule: ['1'] with '12'
unify_key: '1' with '12'
unify_key: '<integer>' with '2'
unify_rule: ['<digit>', '<integer>'] with '2'
unify_key: '<digit>' with '2'
unify_rule: ['0'] with '2'
unify_key: '0' with '2'
unify_rule: ['1'] with '2'
unify_key: '1' with '2'
unify_rule: ['2'] with '2'
unify_key: '2' with '2'
unify_key: '<integer>' with ''
unify_rule: ['<digit>', '<integer>'] with ''
unify_key: '<digit>' with ''
unify_rule: ['0'] with ''
unify_key: '0' with ''
unify_rule: ['1'] with ''
unify_key: '1' with ''
unify_rule: ['2'] with ''
unify_key: '2' with ''
unify_rule: ['3'] with ''
unify_key: '3' with ''
unify_rule: ['4'] with ''
unify_key: '4' with ''
unify_rule: ['5'] with ''
unify_key: '5' with ''
unify_rule: ['6'] with ''
unify_key: '6' with ''
unify_rule: ['7'] with ''
unify_key: '7' with ''
unify_rule: ['8'] with ''
unify_key: '8' with ''
unify_rule: ['9'] with ''
unify_key: '9' with ''
unify_rule: ['<digit>'] with ''
unify_key: '<digit>' with ''
unify_rule: ['0'] with ''
unify_key: '0' with ''
unify_rule: ['1'] with ''
unify_key: '1' with ''
unify_rule: ['2'] with ''
unify_key: '2' with ''
unify_rule: ['3'] with ''
unify_key: '3' with ''
unify_rule: ['4'] with ''
unify_key: '4' with ''
unify_rule: ['5'] with ''
unify_key: '5' with ''
unify_rule: ['6'] with ''
unify_key: '6' with ''
unify_rule: ['7'] with ''
unify_key: '7' with ''
unify_rule: ['8'] with ''
unify_key: '8' with ''
unify_rule: ['9'] with ''
unify_key: '9' with ''
unify_rule: ['<digit>'] with '2'
unify_key: '<digit>' with '2'
unify_rule: ['0'] with '2'
unify_key: '0' with '2'
unify_rule: ['1'] with '2'
unify_key: '1' with '2'
unify_rule: ['2'] with '2'
unify_key: '2' with '2'

```

```py
(2, [('<digit>', [('1', [])]), ('<integer>', [('<digit>', [('2', [])])])])

```

```py
mystring = "1 + 2"
peg = PEGParser(EXPR_GRAMMAR, log=False)
peg.parse(mystring)

```

```py
[('<start>',
  [('<expr>',
    [('<term>', [('<factor>', [('<integer>', [('<digit>', [('1', [])])])])]),
     (' + ', []),
     ('<expr>',
      [('<term>',
        [('<factor>', [('<integer>', [('<digit>', [('2', [])])])])])])])])]

```

这两种方法是相互递归的，并且假设`unify_key()`会尝试每个替代方法直到成功，`unify_key`可以使用相同的参数多次调用。 因此，记住`unify_key`的结果很重要。 Python提供了一个简单的装饰器`lru_cache`，用于记住任何具有可哈希参数的函数调用。 我们将其添加到实现中，以便使用相同的参数重复调用`unify_key()`可获得缓存的结果。

该提示为算法提供了名称- *Packrat* 。

```py
from [functools](https://docs.python.org/3/library/functools.html) import lru_cache

```

```py
class PEGParser(PEGParser):
    @lru_cache(maxsize=None)
    def unify_key(self, key, text, at=0):
        if key not in self.cgrammar:
            if text[at:].startswith(key):
                return at + len(key), (key, [])
            else:
                return at, None
        for rule in self.cgrammar[key]:
            to, res = self.unify_rule(rule, text, at)
            if res:
                return (to, (key, res))
        return 0, None

```

我们将`PEGParser`的初始化和调用包装在`Parser`基类中已经实现的方法`parse()`中，该方法接受要与语法一起解析的文本。

这是我们的解析器的一些示例。

```py
mystring = "1 + (2 * 3)"
peg = PEGParser(EXPR_GRAMMAR)
for tree in peg.parse(mystring):
    assert tree_to_string(tree) == mystring
    display_tree(tree)

```

```py
mystring = "1 * (2 + 3.35)"
for tree in peg.parse(mystring):
    assert tree_to_string(tree) == mystring
    display_tree(tree)

```

应该知道，虽然语法看起来像 *CFG* ，但 *PEG* 所描述的语言可能有所不同。 实际上，只有 *LL（1）*语法可以保证代表PEG和其他解析器的相同语言。 PEG对其他类别语法的行为可能令人惊讶[ [Redziejowski *等*，2008。](http://dl.acm.org/citation.cfm?id=2365896.2365924)。

## 解析上下文无关文法

### PEG 出现问题

虽然 *PEG* 乍看之下很简单，但在某些情况下它们的行为可能有点不直观。 例如，下面是一个示例[未解决的引用：redziejowski。]：

```py
PEG_SURPRISE = {
    "<A>": ["a<A>a", "aa"]
}

```

当解释为 *CFG* 并用作字符串生成器时，它将生成格式为`aa, aaaa, aaaaaa`的字符串，即，生成的字符串中`a`的数量为$ 2 * n $其中$ n > 0 $。

```py
strings = []
for e in range(4):
    f = GrammarFuzzer(PEG_SURPRISE, start_symbol='<A>')
    tree = ('<A>', None)
    for _ in range(e):
        tree = f.expand_tree_once(tree)
    tree = f.expand_tree_with_strategy(tree, f.expand_node_min_cost)
    strings.append(tree_to_string(tree))
    display_tree(tree)
strings

```

```py
['aa', 'aaaa', 'aaaaaa', 'aaaaaaaa']

```

但是， *PEG* 解析器只能识别形式为$ 2 ^ n $的字符串

```py
peg = PEGParser(PEG_SURPRISE, start_symbol='<A>')
for s in strings:
    with ExpectError():
        for tree in peg.parse(s):
            display_tree(tree)
        print(s)

```

```py
aa
aaaa
aaaaaaaa

```

```py
Traceback (most recent call last):
  File "<ipython-input-70-dec55ebf796e>", line 4, in <module>
    for tree in peg.parse(s):
  File "<ipython-input-49-abe75b43d33f>", line 22, in parse
    raise SyntaxError("at " + repr(text[cursor:]))
  File "<string>", line None
SyntaxError: at 'aa' (expected)

```

这不是*分析表达式语法*的唯一问题。 尽管 *PEG* 是表达性的，而用于解析它们的 *packrat* 解析器却简单而直观，但是 *PEG* 出于我们的目的而存在严重不足。 *PEG* 面向语言识别，尚不清楚如何将任意 *PEG* 转换为 *CFG* 。 正如我们之前提到的，将 *PEG* 天真地重新解释为 *CFG* 的效果不是很好。 此外，还不清楚 *PEG* 表示的语言类别与 *CFG* 表示的语言类别之间的确切关系是什么。 由于我们的主要关注点是*模糊化*-即字符串的*生成*-，因此我们接下来来看一下*解析器，该解析器可以接受上下文无关的语法*。

*CFG* 解析器的总体思想如下：窥视输入文本以获取允许的字符数，并使用它们以及解析器的状态来确定可以应用哪些规则来完成解析。 接下来，我们来看一个典型的 *CFG* 解析算法，Earley Parser。

### Earley解析器

Earley解析器是一种通用解析器，它能够解析任意 *CFG* 。 它是由Jay Earley [ [Earley *等人*，1970。](https://doi.org/10.1145/362007.362035)]发明的，用于计算语言学。 虽然解析具有任意语法的字符串的计算复杂度为$ O（n ^ 3）$，但它可以在$ O（n ^ 2）$的时间内解析所有语法清晰的字符串，并且所有 *[LR（k）](https://en.wikipedia.org/wiki/LR_parser)* 线性时间语法（$ O（n）$ [ [Joop MIM Leo，1991。](https://doi.org/https://doi.org/10.1016/0304-3975(91)90180-A)]）。 Aycock等人发明了进一步的改进，特别是处理epsilon规则。 [John Aycock *等人*，2002。]。

请注意，我们实现的一个限制是开始符号在其替代表达式中只能有一个替代。 这实际上不受限制，因为可以用一个以原始开始符号作为唯一选择的新开始符号来扩展任何具有多个替代其开始符号的语法。 也就是说，给定以下语法，

```py
grammar = {
    '<start>': ['<A>', '<B>'],
    ...
}
```

可以按照下面的*单备*规则重写它。

```py
grammar = {
    '<start>': ['<start_>'],
    '<start_>': ['<A>', '<B>'],
    ...
}
```

我们首先实现一个更简单的解析器，该解析器几乎是所有 *CFG* 的解析器。 特别是，我们的解析器不理解 *epsilon规则* –派生空字符串的规则。 稍后我们将展示如何扩展解析器来处理这些解析器。

在下面的示例中，我们使用以下语法。

```py
SAMPLE_GRAMMAR = {
    '<start>': ['<A><B>'],
    '<A>': ['a<B>c', 'a<A>'],
    '<B>': ['b<C>', '<D>'],
    '<C>': ['c'],
    '<D>': ['d']
}
C_SAMPLE_GRAMMAR = canonical(SAMPLE_GRAMMAR)

```

```py
syntax_diagram(SAMPLE_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 197.0 62" width="197.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="74.25" y="35">A</text></g> <g class="non-terminal"><text x="122.75" y="35">B</text></g></g></g></g></svg>

```py
A

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 245.5 92" width="245.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">a</text></g> <g class="non-terminal"><text x="122.75" y="35">B</text></g> <g class="terminal"><text x="171.25" y="35">c</text></g></g> <g><g class="terminal"><text x="98.5" y="65">a</text></g> <g class="non-terminal"><text x="147.0" y="65">A</text></g></g></g></g></svg>

```py
B

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 197.0 92" width="197.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">b</text></g> <g class="non-terminal"><text x="122.75" y="35">C</text></g></g> <g><g class="non-terminal"><text x="98.5" y="65">D</text></g></g></g></g></svg>

```py
C

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">c</text></g></g></g></g></svg>

```py
D

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">d</text></g></g></g></g></svg>

Earley解析的基本思想如下：

*   从与START_SYMBOL对应的替代表达式开始。 这些代表从高层解析字符串的可能方法。 本质上，每个表达式都代表一个解析路径。 将每个表达式放在我们可能的字符串解析集中。 表达式的解析索引是已经识别的表达式部分。 在分析的开始，所有表达式的分析索引都在开始。 此外，每个字母都有一个表达式队列，可以在我们的解析中识别该字母。
*   检查我们可能解析的队列，并检查其中是否有非终结符开头。 如果是这样，则在解析给定规则之前，需要从输入中识别该非终结符。 因此，将与非终结符相对应的替代表达式添加到队列中。 递归执行此操作。
*   至此，我们已准备好前进。 检查输入中的当前字母，并选择在解析索引处具有该特定字母的所有表达式。 这些表达式现在可以前进一个步骤。 通过增加它们的解析索引来前进这些选定的表达式，并将它们添加到排队的表达式队列中，以识别下一个输入字母。
*   如果在执行这些操作时发现任何表达式已完成解析，我们将获取其对应的非终结符，并在其解析索引处推进所有具有该非终结符的表达式。
*   递归地继续此过程，直到处理完我们排队等待当前字母的所有表达式为止。 然后开始处理下一个字母的队列。

我们将在接下来的部分中通过示例详细解释每个步骤。

解析器使用动态编程来生成一个表，该表包含每个字母索引处可能包含解析的*森林–该表包含与输入中的字母一样多的列，并且每个列在不同阶段包含不同的解析规则 的解析。*

例如，给定输入`adcd`，列0将包含以下内容：

```py
<start> : ● <A> <B>
```

这是表明我们当前正在解析规则`<start>`的起始规则，并且解析状态就在识别符号`<A>`之前。 它还将包含以下内容，这是完成解析可能要采用的两条替代路径。

```py
<A> : ● a <B> c
<A> : ● a <A>
```

第1列将包含以下内容，表示读取`a`之后可能完成的操作。

```py
<A> : a ● <B> c
<A> : a ● <A>
<B> : ● b <C>
<B> : ● <D>
<A> : ● a <B> c
<A> : ● a <A>
<D> : ● d
```

阅读`d`后，第2列将包含以下内容

```py
<D> : d ●
<B> : <D> ●
<A> : a <B> ● c
```

同样，第3列在阅读`c`之后将包含以下内容

```py
<A> : a <B> c ●
<start> : <A> ● <B>
<B> : ● b <C>
<B> : ● <D>
<D> : ● d
```

最后，在读取`d`之后，第4列将包含以下内容，其中`<start>`规则末尾的`●`表示解析成功。

```py
<D> : d ●
<B> : <D> ●
<start> : <A> <B> ●
```

从上面可以看到，我们实际上是根据阅读的每个字母以及可以应用的语法规则填充条目的表（表也称为**图表**）。 该图表为解析器起了另一个名字-图表解析。

### 列

我们首先定义`Column`。 `Column`由其自身的`index`在输入字符串中初始化，而`letter`在该索引处初始化。 在内部，我们还跟踪随着解析的进行而添加到列中的状态。

```py
class Column(object):
    def __init__(self, index, letter):
        self.index, self.letter = index, letter
        self.states, self._unique = [], {}

    def __str__(self):
        return "%s chart[%d]\n%s" % (self.letter, self.index, "\n".join(
            str(state) for state in self.states if state.finished()))

```

`Column`仅存储唯一的`states`。 因此，当新的`state`是我们的`Column`的`added`时，我们将检查它是否已知。

```py
class Column(Column):
    def add(self, state):
        if state in self._unique:
            return self._unique[state]
        self._unique[state] = state
        self.states.append(state)
        state.e_col = self
        return self._unique[state]

```

### 项目

一个项目代表针对特定规则的正在进行的*解析。* 因此，该项目包含非终结符的名称，以及共同构成规则的相应替代表达式（`expr`），以及该表达式中当前的解析位置-`dot`。

**注意。** 如果您熟悉 [LR解析](https://en.wikipedia.org/wiki/LR_parser)，您会注意到一个项目就是一个`LR0`项目。

```py
class Item(object):
    def __init__(self, name, expr, dot):
        self.name, self.expr, self.dot = name, expr, dot

```

我们还提供了一些方便的方法。 方法`finished()`检查`dot`是否已移出`expr`中的最后一个元素。 方法`advance()`产生具有`dot`高级一个令牌的新`Item`，并表示解析的高级。 方法`at_dot()`返回正在解析的当前符号。

```py
class Item(Item):
    def finished(self):
        return self.dot >= len(self.expr)

    def advance(self):
        return Item(self.name, self.expr, self.dot + 1)

    def at_dot(self):
        return self.expr[self.dot] if self.dot < len(self.expr) else None

```

这是可以使用的项目。 我们首先定义我们的项目

```py
item_name = '<B>'
item_expr = C_SAMPLE_GRAMMAR[item_name][1]
an_item = Item(item_name, tuple(item_expr), 0)

```

要确定解析状态，我们使用`at_dot()`

```py
an_item.at_dot()

```

```py
'<D>'

```

也就是说，下一个要解析的符号是`<D>`

如果我们推进该项目，则会得到另一个代表完成的解析规则`<B>`的项目。

```py
another_item = an_item.advance()

```

```py
another_item.finished()

```

```py
True

```

### 状态

对于`Earley`解析，解析的状态只是一个`Item`以及一些元信息，例如每种状态的开始`s_col`和结束列`e_col`。 因此，我们从`Item`继承来创建`State`。 由于我们有兴趣比较状态，因此我们用相应的方法定义了`hash()`和`eq()`。

```py
class State(Item):
    def __init__(self, name, expr, dot, s_col, e_col=None):
        super().__init__(name, expr, dot)
        self.s_col, self.e_col = s_col, e_col

    def __str__(self):
        def idx(var):
            return var.index if var else -1

        return self.name + ':= ' + ' '.join([
            str(p)
            for p in [*self.expr[:self.dot], '|', *self.expr[self.dot:]]
        ]) + "(%d,%d)" % (idx(self.s_col), idx(self.e_col))

    def copy(self):
        return State(self.name, self.expr, self.dot, self.s_col, self.e_col)

    def _t(self):
        return (self.name, self.expr, self.dot, self.s_col.index)

    def __hash__(self):
        return hash(self._t())

    def __eq__(self, other):
        return self._t() == other._t()

    def advance(self):
        return State(self.name, self.expr, self.dot + 1, self.s_col)

```

`State`的用法类似于`Item`的用法。 唯一的区别是，它与`Column`一起用于跟踪解析状态。 例如，我们将第一列初始化如下：

```py
col_0 = Column(0, None)
item_expr = tuple(*C_SAMPLE_GRAMMAR[START_SYMBOL])
start_state = State(START_SYMBOL, item_expr, 0, col_0)
col_0.add(start_state)
start_state.at_dot()

```

```py
'<A>'

```

然后使用`Column`的`add()`方法更新第一列

```py
sym = start_state.at_dot()
for alt in C_SAMPLE_GRAMMAR[sym]:
    col_0.add(State(sym, tuple(alt), 0, col_0))
for s in col_0.states:
    print(s)

```

```py
<start>:= | <A> <B>(0,0)
<A>:= | a <B> c(0,0)
<A>:= | a <A>(0,0)

```

### 解析算法

*Earley* 算法通过用列初始化图表（与输入中的字母一样多）开始。 我们还以表示与起始符号相对应的表达式的状态作为第一列的种子。 在我们的情况下，状态与起始符号相对应，其中`0`处的`dot`表示如下。 `●`符号代表解析状态。 在这种情况下，我们没有解析任何内容。

```py
<start>: ● <A> <B>
```

我们将此局部图表传递给填充其余解析图表的方法。

```py
class EarleyParser(Parser):
    def __init__(self, grammar, **kwargs):
        super().__init__(grammar, **kwargs)
        self.cgrammar = canonical(grammar, letters=True)

```

在开始解析之前，我们在图表上植入表示开始符号正在进行解析的状态。

```py
class EarleyParser(EarleyParser):
    def chart_parse(self, words, start):
        alt = tuple(*self.cgrammar[start])
        chart = [Column(i, tok) for i, tok in enumerate([None, *words])]
        chart[0].add(State(start, alt, 0, chart[0]))
        return self.fill_chart(chart)

```

`fill_chart()`中的主要解析循环具有三个基本操作。 `predict()`，`scan()`和`complete()`。 接下来我们讨论`predict`。

### 预测状态

我们已经将状态为`[<A>,<B>]`的`chart[0]`和状态为`dot`的种子`0`植入了种子。 接下来，假设`<A>`是一个非终结符，我们`predict`可能会解析此状态。 也就是说，它可以是`a <B> c`或`A <A>`。

`predict()`的一般思想如下：假设您具有上述语法中名称为`<A>`的状态，并且表达式包含`[a,<B>,c]`。 假设您已经看到`a`，这意味着`dot`将在`<B>`上。 下面是我们解析状态的表示。 ●的左侧代表已经解析的部分（`a`），右侧代表尚未解析的部分（`<B> c`）。

```py
<A>: a  ●  <B> c
```

为了识别`<B>`，我们看一下`<B>`的定义，它具有不同的替代表达。 `predict()`步骤将这些替代方案的每一个添加到状态集，其中`dot`为`0`。

```py
<A>: a ● <B> c
<B>: ● b c
<B>: ● <D>
```

实质上，`predict()`方法在与当前非终结符一起调用时，会提取与该非终结符相对应的替代表达式，并将这些作为预测的*子*状态添加到*当前*列中。

```py
class EarleyParser(EarleyParser):
    def predict(self, col, sym, state):
        for alt in self.cgrammar[sym]:
            col.add(State(sym, tuple(alt), 0, col))

```

要查看如何使用`predict`，我们首先像以前一样构造第0列，然后将构造的列分配给EarleyParser的实例。

```py
col_0 = Column(0, None)
col_0.add(start_state)
ep = EarleyParser(SAMPLE_GRAMMAR)
ep.chart = [col_0]

```

它应包含一个状态-`<start> at 0`

```py
for s in ep.chart[0].states:
    print(s)

```

```py
<start>:= | <A> <B>(0,0)

```

我们应用预测填充第0列，该列应包含可能的解析路径。

```py
ep.predict(col_0, '<A>', s)
for s in ep.chart[0].states:
    print(s)

```

```py
<start>:= | <A> <B>(0,0)
<A>:= | a <B> c(0,0)
<A>:= | a <A>(0,0)

```

### 扫描令牌

如果状态不是终结符，而是包含终结符（例如字母），该怎么办？ 在这种情况下，我们准备取得一些进展。 例如，考虑第二种状态：

```py
<B>: ● b c
```

我们`scan`下一栏的字母。 假设下一个标记是`b`。 如果字母与我们所拥有的匹配，则通过将当前状态前进一个字母来创建一个新状态。

```py
<B>: b ● c
```

此新状态将添加到下一列（即具有匹配字母的列）。

```py
class EarleyParser(EarleyParser):
    def scan(self, col, state, letter):
        if letter == col.letter:
            col.add(state.advance())

```

和以前一样，我们首先构造部分解析，这次添加一个新列，以便我们可以观察到`scan()`的效果

```py
ep = EarleyParser(SAMPLE_GRAMMAR)
col_1 = Column(1, 'a')
ep.chart = [col_0, col_1]

```

```py
new_state = ep.chart[0].states[1]
print(new_state)

```

```py
<A>:= | a <B> c(0,0)

```

```py
ep.scan(col_1, new_state, 'a')
for s in ep.chart[1].states:
    print(s)

```

```py
<A>:= a | <B> c(0,1)

```

### 完成处理

前进时，如果我们实际`complete()`处理当前规则该怎么办？ 如果是这样，我们不仅要更新此状态，还要更新从中导出此状态的所有*父级*状态。 例如，假设我们具有以下状态。

```py
<A>: a ● <B> c
<B>: b c ●
```

状态`<B>: b c ●`现已完成。 因此，我们需要向前推进`<A>: a ● <B> c`。

我们如何确定父状态？ 从`predict`注意，我们将预测的子状态添加到*与检查状态相同的*列中。 因此，我们看一下当前状态的开始列，其符号`at_dot`与完成状态的名称相同。

对于找到的每个这样的父对象，我们使该父对象前进（因为我们刚刚完成了对非终端的`at_dot`解析），并将新状态添加到当前列。

```py
class EarleyParser(EarleyParser):
    def complete(self, col, state):
        return self.earley_complete(col, state)

    def earley_complete(self, col, state):
        parent_states = [
            st for st in state.s_col.states if st.at_dot() == state.name
        ]
        for st in parent_states:
            col.add(st.advance())

```

这是完成处理的示例。 首先，我们完成第0列

```py
ep = EarleyParser(SAMPLE_GRAMMAR)
col_1 = Column(1, 'a')
col_2 = Column(2, 'd')
ep.chart = [col_0, col_1, col_2]
ep.predict(col_0, '<A>', s)
for s in ep.chart[0].states:
    print(s)

```

```py
<start>:= | <A> <B>(0,0)
<A>:= | a <B> c(0,0)
<A>:= | a <A>(0,0)

```

然后我们使用`scan()`填充第1列

```py
for state in ep.chart[0].states:
    if state.at_dot() not in SAMPLE_GRAMMAR:
        ep.scan(col_1, state, 'a')
for s in ep.chart[1].states:
    print(s)

```

```py
<A>:= a | <B> c(0,1)
<A>:= a | <A>(0,1)

```

```py
for state in ep.chart[1].states:
    if state.at_dot() in SAMPLE_GRAMMAR:
        ep.predict(col_1, state.at_dot(), state)
for s in ep.chart[1].states:
    print(s)

```

```py
<A>:= a | <B> c(0,1)
<A>:= a | <A>(0,1)
<B>:= | b <C>(1,1)
<B>:= | <D>(1,1)
<A>:= | a <B> c(1,1)
<A>:= | a <A>(1,1)
<D>:= | d(1,1)

```

然后我们再次使用`scan()`填充第2列

```py
for state in ep.chart[1].states:
    if state.at_dot() not in SAMPLE_GRAMMAR:
        ep.scan(col_2, state, state.at_dot())

for s in ep.chart[2].states:
    print(s)

```

```py
<D>:= d |(1,2)

```

现在，我们可以使用`complete()`：

```py
for state in ep.chart[2].states:
    if state.finished():
        ep.complete(col_2, state)

for s in ep.chart[2].states:
    print(s)

```

```py
<D>:= d |(1,2)
<B>:= <D> |(1,2)
<A>:= a <B> | c(0,2)

```

### 填写图表

`fill_chart()`中的主驱动回路本质上按顺序调用这些操作。 我们按顺序遍历每一列。

*   对于每一列，一次获取该列中的一个状态，并检查该状态是否为`finished`。
    *   如果是，那么我们根据该状态`complete()`所有父状态。
*   如果状态尚未完成，我们将检查该状态的当前符号`at_dot`是否为非终结符。
    *   如果它是非终结符，则我们`predict()`可能的延续，并使用这些状态更新当前列。
    *   如果不是，我们`scan()`下一列，如果当前状态与下一个字母匹配，则前进当前状态。

```py
class EarleyParser(EarleyParser):
    def fill_chart(self, chart):
        for i, col in enumerate(chart):
            for state in col.states:
                if state.finished():
                    self.complete(col, state)
                else:
                    sym = state.at_dot()
                    if sym in self.cgrammar:
                        self.predict(col, sym, state)
                    else:
                        if i + 1 >= len(chart):
                            continue
                        self.scan(chart[i + 1], state, sym)
            if self.log:
                print(col, '\n')
        return chart

```

现在，我们可以将给定的字符串识别为属于语法表示的语言。

```py
ep = EarleyParser(SAMPLE_GRAMMAR, log=True)
columns = ep.chart_parse('adcd', START_SYMBOL)

```

```py
None chart[0]

a chart[1]

d chart[2]
<D>:= d |(1,2)
<B>:= <D> |(1,2) 

c chart[3]
<A>:= a <B> c |(0,3) 

d chart[4]
<D>:= d |(3,4)
<B>:= <D> |(3,4)
<start>:= <A> <B> |(0,4) 

```

我们上面打印的图表仅显示每个索引处的完成条目。 括号表达式表示刚识别出第一个字符之前的列和结尾列。

注意`<start>`非终端如何显示完全解析的状态。

```py
last_col = columns[-1]
for s in last_col.states:
    if s.name == '<start>':
        print(s)

```

```py
<start>:= <A> <B> |(0,4)

```

由于`chart_parse()`返回完成的表，因此我们现在需要提取派生树。

### 解析方法

为了确定我们可以解析的距离，我们只需从`chart_parse()`中找到`start_symbol`的位置查找最后一个索引。

```py
class EarleyParser(EarleyParser):
    def parse_prefix(self, text):
        self.table = self.chart_parse(text, self.start_symbol())
        for col in reversed(self.table):
            states = [
                st for st in col.states if st.name == self.start_symbol()
            ]
            if states:
                return col.index, states
        return -1, []

```

这是正在运行的`parse_prefix()`。

```py
ep = EarleyParser(SAMPLE_GRAMMAR)
cursor, last_states = ep.parse_prefix('adcd')
print(cursor, [str(s) for s in last_states])

```

```py
4 ['<start>:= <A> <B> |(0,4)']

```

以下内容摘自 [Loup Vaillant](http://loup-vaillant.fr/tutorials/earley-parsing/) 关于Earley解析的出色参考。

我们的`parse()`方法如下。 它取决于接下来将定义的两种方法`parse_forest()`和`extract_trees()`。

```py
class EarleyParser(EarleyParser):
    def parse(self, text):
        cursor, states = self.parse_prefix(text)
        start = next((s for s in states if s.finished()), None)

        if cursor < len(text) or not start:
            raise SyntaxError("at " + repr(text[cursor:]))

        forest = self.parse_forest(self.table, start)
        for tree in self.extract_trees(forest):
            yield self.prune_tree(tree)

```

### 解析路径

`parse_paths()`方法尝试将`named_expr`中的给定表达式与已解析的字符串统一。 为此，它将提取`named_expr`中的最后一个符号，并检查它是否为终端符号。 如果是，则检查`til`处的图表以查看与该位置相对应的字母是否与端子符号匹配。 如果是这样，请将我们的起始索引扩展符号的长度。

如果该符号是非终止符号，则我们在与非终止符号相对应的当前结束列索引（`til`）处检索已解析的状态，并收集起始索引。 这些是其余表达式的结尾列索引。

给定我们的起始索引列表，我们从其余表达式中获取解析路径。 如果可以获取任何内容，则返回解析路径。 如果没有，我们将返回一个空列表。

```py
class EarleyParser(EarleyParser):
    def parse_paths(self, named_expr, chart, frm, til):
        def paths(state, start, k, e):
            if not e:
                return [[(state, k)]] if start == frm else []
            else:
                return [[(state, k)] + r
                        for r in self.parse_paths(e, chart, frm, start)]

        *expr, var = named_expr
        starts = None
        if var not in self.cgrammar:
            starts = ([(var, til - len(var),
                        't')] if til > 0 and chart[til].letter == var else [])
        else:
            starts = [(s, s.s_col.index, 'n') for s in chart[til].states
                      if s.finished() and s.name == var]

        return [p for s, start, k in starts for p in paths(s, start, k, expr)]

```

这是正在运行的`parse_paths()`

```py
print(SAMPLE_GRAMMAR['<start>'])
ep = EarleyParser(SAMPLE_GRAMMAR)
completed_start = last_states[0]
paths = ep.parse_paths(completed_start.expr, columns, 0, 4)
for path in paths:
    print([list(str(s_) for s_ in s) for s in path])

```

```py
['<A><B>']
[['<B>:= <D> |(3,4)', 'n'], ['<A>:= a <B> c |(0,3)', 'n']]

```

也就是说，给定输入`adcd`的`<start>`的解析路径包括识别表达式`<A><B>`。 这被两个状态识别：从输入（0）到输入（2）的`<A>`进一步涉及识别规则`a<B>c`，从输入（3）的下一个状态`<B>`涉及识别规则`<D>` ]。

### 解析森林

`parse_forest()`方法采用表示已完成解析的状态，并确定其表达式与解析后的表达式相对应的可能方式。 例如，假设我们正在解析`1+2+3`，并且状态在`expr`中具有`[<expr>,+,<expr>]`。 它可能已解析为`[{<expr>:1+2},+,{<expr>:3}]`或`[{<expr>:1},+,{<expr>:2+3}]`。

```py
class EarleyParser(EarleyParser):
    def forest(self, s, kind, chart):
        return self.parse_forest(chart, s) if kind == 'n' else (s, [])

    def parse_forest(self, chart, state):
        pathexprs = self.parse_paths(state.expr, chart, state.s_col.index,
                                     state.e_col.index) if state.expr else []
        return state.name, [[(v, k, chart) for v, k in reversed(pathexpr)]
                            for pathexpr in pathexprs]

```

```py
ep = EarleyParser(SAMPLE_GRAMMAR)
result = ep.parse_forest(columns, last_states[0])
result

```

```py
('<start>',
 [[(<__main__.State at 0x11a7a01d0>,
    'n',
    [<__main__.Column at 0x11a7c5dd8>,
     <__main__.Column at 0x11a7c5be0>,
     <__main__.Column at 0x11a7c5a20>,
     <__main__.Column at 0x11a7c56a0>,
     <__main__.Column at 0x11a7c5780>]),
   (<__main__.State at 0x11a7a0c88>,
    'n',
    [<__main__.Column at 0x11a7c5dd8>,
     <__main__.Column at 0x11a7c5be0>,
     <__main__.Column at 0x11a7c5a20>,
     <__main__.Column at 0x11a7c56a0>,
     <__main__.Column at 0x11a7c5780>])]])

```

### 提取树

我们从`parse_forest()`中得到的是一片森林。 我们需要从该森林中提取一棵树。 如下完成。

（现在，我们返回第一个可用的派生树。为此，我们需要从与`start`对应的状态中提取解析森林。）

```py
class EarleyParser(EarleyParser):
    def extract_a_tree(self, forest_node):
        name, paths = forest_node
        if not paths:
            return (name, [])
        return (name, [self.extract_a_tree(self.forest(*p)) for p in paths[0]])

    def extract_trees(self, forest):
        yield self.extract_a_tree(forest)

```

现在，我们验证解析器可以解析给定的表达式。

```py
A3_GRAMMAR = {
    "<start>": ["<bexpr>"],
    "<bexpr>": [
        "<aexpr><gt><aexpr>", "<aexpr><lt><aexpr>", "<aexpr>=<aexpr>",
        "<bexpr>=<bexpr>", "<bexpr>&<bexpr>", "<bexpr>|<bexpr>", "(<bexrp>)"
    ],
    "<aexpr>":
    ["<aexpr>+<aexpr>", "<aexpr>-<aexpr>", "(<aexpr>)", "<integer>"],
    "<integer>": ["<digit><integer>", "<digit>"],
    "<digit>": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
    "<lt>": ['<'],
    "<gt>": ['>']
}

```

```py
syntax_diagram(A3_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 182.5 62" width="182.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">bexpr</text></g></g></g></g></svg>

```py
bexpr

```

 <svg class="railroad-diagram" height="80" viewBox="0 0 1837.5 80" width="1837.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="non-terminal"><text x="101.25" y="44">aexpr</text></g> <g class="non-terminal"><text x="171.0" y="44">gt</text></g> <g class="non-terminal"><text x="240.75" y="44">aexpr</text></g></g></g> <g><g><g class="non-terminal"><text x="363.25" y="44">aexpr</text></g> <g class="non-terminal"><text x="433.0" y="44">lt</text></g> <g class="non-terminal"><text x="502.75" y="44">aexpr</text></g></g></g> <g><g><g class="non-terminal"><text x="625.25" y="44">aexpr</text></g> <g class="terminal"><text x="690.75" y="44">=</text></g> <g class="non-terminal"><text x="756.25" y="44">aexpr</text></g></g></g> <g><g><g class="non-terminal"><text x="878.75" y="44">bexpr</text></g> <g class="terminal"><text x="944.25" y="44">=</text></g> <g class="non-terminal"><text x="1009.75" y="44">bexpr</text></g></g></g> <g><g><g class="non-terminal"><text x="1132.25" y="44">bexpr</text></g> <g class="terminal"><text x="1197.75" y="44">&</text></g> <g class="non-terminal"><text x="1263.25" y="44">bexpr</text></g></g></g> <g><g><g class="non-terminal"><text x="1385.75" y="44">bexpr</text></g> <g class="terminal"><text x="1451.25" y="44">|</text></g> <g class="non-terminal"><text x="1516.75" y="44">bexpr</text></g></g></g> <g><g><g class="terminal"><text x="1622.25" y="44">(</text></g> <g class="non-terminal"><text x="1687.75" y="44">bexrp</text></g> <g class="terminal"><text x="1753.25" y="44">)</text></g></g></g></g></g></svg>

```py
aexpr

```

 <svg class="railroad-diagram" height="152" viewBox="0 0 313.5 152" width="313.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="65">aexpr</text></g> <g class="terminal"><text x="156.75" y="65">-</text></g> <g class="non-terminal"><text x="222.25" y="65">aexpr</text></g></g> <g><g class="non-terminal"><text x="91.25" y="35">aexpr</text></g> <g class="terminal"><text x="156.75" y="35">+</text></g> <g class="non-terminal"><text x="222.25" y="35">aexpr</text></g></g> <g><g class="terminal"><text x="91.25" y="95">(</text></g> <g class="non-terminal"><text x="156.75" y="95">aexpr</text></g> <g class="terminal"><text x="222.25" y="95">)</text></g></g> <g><g class="non-terminal"><text x="156.75" y="125">integer</text></g></g></g></g></svg>

```py
integer

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 282.0 92" width="282.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="91.25" y="35">digit</text></g> <g class="non-terminal"><text x="182.25" y="35">integer</text></g></g> <g><g class="non-terminal"><text x="141.0" y="65">digit</text></g></g></g></g></svg>

```py
digit

```

 <svg class="railroad-diagram" height="109" viewBox="0 0 522.5 109" width="522.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g><g class="terminal"><text x="84.25" y="43">0</text></g></g> <g><g class="terminal"><text x="84.25" y="73">1</text></g></g></g> <g><g><g class="terminal"><text x="172.75" y="43">2</text></g></g> <g><g class="terminal"><text x="172.75" y="73">3</text></g></g></g> <g><g><g class="terminal"><text x="261.25" y="43">4</text></g></g> <g><g class="terminal"><text x="261.25" y="73">5</text></g></g></g> <g><g><g class="terminal"><text x="349.75" y="43">6</text></g></g> <g><g class="terminal"><text x="349.75" y="73">7</text></g></g></g> <g><g><g class="terminal"><text x="438.25" y="43">8</text></g></g> <g><g class="terminal"><text x="438.25" y="73">9</text></g></g></g></g></g></svg>

```py
lt

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35"><</text></g></g></g></g></svg>

```py
gt

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">></text></g></g></g></g></svg>

```py
mystring = '(1+24)=33'
parser = EarleyParser(A3_GRAMMAR)
for tree in parser.parse(mystring):
    assert tree_to_string(tree) == mystring
    display_tree(tree)

```

现在，我们有了一个完整的解析器，可以解析几乎任意的 *CFG* 。 还有一个小小的角落需要解决-有关epsilon规则的情况，我们将在后面看到。

### 歧义解析

模糊语法是可以为给定字符串生成多个派生树的语法。 例如，`A3_GRAMMAR`可以以两种不同的方式解析`1+2+3`-`[1+2]+3`和`1+[2+3]`。

对于明确的解析，提取单个树可能是合理的。 但是，如果给定字符串时给定语法产生歧义怎么办？ 在这种情况下，我们需要提取所有派生树。 我们增强了`extract_trees()`方法，以提取多个派生树。

```py
class EarleyParser(EarleyParser):
    def extract_trees(self, forest_node):
        name, paths = forest_node
        if not paths:
            yield (name, [])
        results = []
        for path in paths:
            ptrees = [self.extract_trees(self.forest(*p)) for p in path]
            for p in zip(*ptrees):
                yield (name, p) 

```

和以前一样，我们验证一切正常。

```py
mystring = '1+2'
parser = EarleyParser(A1_GRAMMAR)
for tree in parser.parse(mystring):
    assert mystring == tree_to_string(tree)
    display_tree(tree)

```

也可以使用`GrammarFuzzer`验证一切正常。

```py
gf = GrammarFuzzer(A1_GRAMMAR)
for i in range(5):
    s = gf.fuzz()
    print(i, s)
    for tree in parser.parse(s):
        assert tree_to_string(tree) == s

```

```py
0 045+3+2-9+7-7-5-1-449
1 0+9+5-2+1-8+4-3+7+2
2 76413
3 9339
4 62

```

### Aycock Epsilon修复程序

解析时，通常需要知道给定的非终结符是否可以派生空字符串。 例如，在下面的语法中，A可以派生一个空字符串，而B则不能。 可以派生空字符串的非终结符称为*可为空的*非终结符。 例如，在下面的语法`E_GRAMMAR_1`中，`<A>`是*可为空*，并且由于`<A>`是`<start>`的替代之一，因此`<start>`也是*可为空* 。 但是`<B>`不能为*为空*。

```py
E_GRAMMAR_1 = {
    '<start>': ['<A>', '<B>'],
    '<A>': ['a', ''],
    '<B>': ['b']
}

```

最初的Earley实现的问题之一是，它无法处理可以很好地导出空字符串的规则。 例如，给定的语法应匹配`a`

```py
EPSILON = ''
E_GRAMMAR = {
    '<start>': ['<S>'],
    '<S>': ['<A><A><A><A>'],
    '<A>': ['a', '<E>'],
    '<E>': [EPSILON]
}

```

```py
syntax_diagram(E_GRAMMAR)

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="74.25" y="35">S</text></g></g></g></g></svg>

```py
S

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 294.0 62" width="294.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="74.25" y="35">A</text></g> <g class="non-terminal"><text x="122.75" y="35">A</text></g> <g class="non-terminal"><text x="171.25" y="35">A</text></g> <g class="non-terminal"><text x="219.75" y="35">A</text></g></g></g></g></svg>

```py
A

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 148.5 92" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">a</text></g></g> <g><g class="non-terminal"><text x="74.25" y="65">E</text></g></g></g></g></svg>

```py
E

```

```py
mystring = 'a'
parser = EarleyParser(E_GRAMMAR)
with ExpectError():
    trees = parser.parse(mystring)

```

Aycock等人[John Aycock *等人*，2002。]提出了一种简单的解决方法。 他们的想法是预先计算`nullable`集，并使用它来推进`nullable`状态。 但是，在执行此操作之前，我们需要计算`nullable`集。 `nullable`集由所有可以导出空字符串的非终结符组成。

计算`nullable`集需要迭代地扩展语法中的每个生成规则，并检查给定规则是否可以导出空字符串。 每次迭代都需要考虑已发现为`nullable`的新终端。 当我们获得稳定的结果时，该过程停止。 该过程可以抽象为更通用的方法`fixpoint`。

#### 固定点

函数的`fixpoint`是函数域中的元素，因此将其映射到自身。 例如，1是平方根的`fixpoint`，因为`squareroot(1) == 1`。

（我们使用`str`而不是`hash`来检查`fixpoint`中的相等性，因为我们想用作参数的数据结构`set`具有良好的字符串表示形式，但不能进行哈希处理）。

```py
def fixpoint(f):
    def helper(arg):
        while True:
            sarg = str(arg)
            arg_ = f(arg)
            if str(arg_) == sarg:
                return arg
            arg = arg_

    return helper

```

还记得第一章中的[中的`my_sqrt()`吗？ 我们可以使用定位点定义`my_sqrt()`。](Intro_Testing.html)

```py
def my_sqrt(x):
    @fixpoint
    def _my_sqrt(approx):
        return (approx + x / approx) / 2

    return _my_sqrt(1)

```

```py
my_sqrt(2)

```

```py
1.414213562373095

```

#### 可空

同样，我们可以使用`fixpoint`定义`nullable`。 我们实质上提供了单个中间步骤的定义。 也就是说，假设`nullables`包含当前的`nullable`非终结符，我们遍历该语法以查找`nullable`的产生式-也就是说，整个序列可以在某个扩展上产生空字符串的产生式。

我们需要遍历不同的替代表达式及其对应的非终结符。 因此，我们定义了`rules()`方法，将字典表示形式转换为该对格式。

```py
def rules(grammar):
    return [(key, choice)
            for key, choices in grammar.items()
            for choice in choices]

```

`terminals()`方法从`canonical`语法表示中提取所有终端符号。

```py
def terminals(grammar):
    return set(token
               for key, choice in rules(grammar)
               for token in choice if token not in grammar)

```

```py
def nullable_expr(expr, nullables):
    return all(token in nullables for token in expr)

```

```py
def nullable(grammar):
    productions = rules(grammar)

    @fixpoint
    def nullable_(nullables):
        for A, expr in productions:
            if nullable_expr(expr, nullables):
                nullables |= {A}
        return (nullables)

    return nullable_({EPSILON})

```

```py
for key, grammar in {
        'E_GRAMMAR': E_GRAMMAR,
        'E_GRAMMAR_1': E_GRAMMAR_1
}.items():
    print(key, nullable(canonical(grammar)))

```

```py
E_GRAMMAR {'', '<S>', '<E>', '<start>', '<A>'}
E_GRAMMAR_1 {'', '<start>', '<A>'}

```

因此，一旦设置了`nullable`，我们要做的就是在对应于非终端的状态下调用`predict`后，检查它是否为`nullable`，如果是，则前进并添加 状态到当前列。

```py
class EarleyParser(EarleyParser):
    def __init__(self, grammar, **kwargs):
        super().__init__(grammar, **kwargs)
        self.cgrammar = canonical(grammar, letters=True)
        self.epsilon = nullable(self.cgrammar)

    def predict(self, col, sym, state):
        for alt in self.cgrammar[sym]:
            col.add(State(sym, tuple(alt), 0, col))
        if sym in self.epsilon:
            col.add(state.advance())

```

```py
mystring = 'a'
parser = EarleyParser(E_GRAMMAR)
for tree in parser.parse(mystring):
    display_tree(tree)

```

为了确保我们的解析器能够解析所有语法，让我们再尝试两个测试用例。

```py
DIRECTLY_SELF_REFERRING = {
    '<start>': ['<query>'],
    '<query>': ['select <expr> from a'],
    "<expr>": [ "<expr>", "a"],
}
INDIRECTLY_SELF_REFERRING = {
    '<start>': ['<query>'],
    '<query>': ['select <expr> from a'],
    "<expr>": [ "<aexpr>", "a"],
    "<aexpr>": [ "<expr>"],
}

```

```py
mystring = 'select a from a'
for grammar in [DIRECTLY_SELF_REFERRING, INDIRECTLY_SELF_REFERRING]:
    trees = EarleyParser(grammar).parse(mystring)
    for tree in trees:
        assert mystring == tree_to_string(tree)
        display_tree(tree)

```

### 更多Earley解析

Earley解析器还存在许多其他优化。 具有快速工业实力的Earley解析器实现是 [Marpa解析器](https://jeffreykegler.github.io/Marpa-web-site/)。 此外，Earley解析不必限于字符数据。 也可以使用广义的Earley解析器来解析流（音频和视频流）[Qi *等人*，2018。]。

## 测试解析器

虽然我们定义了两个解析器变体，但最好能确认我们的解析器工作良好。 尽管可以正式证明它们有效，但生成随机语法，它们对应的字符串并使用相同的语法来解析它们，则更为令人满意。

```py
def prod_line_grammar(nonterminals, terminals):
    g = {
        '<start>': ['<symbols>'],
        '<symbols>': ['<symbol><symbols>', '<symbol>'],
        '<symbol>': ['<nonterminals>', '<terminals>'],
        '<nonterminals>': ['<lt><alpha><gt>'],
        '<lt>': ['<'],
        '<gt>': ['>'],
        '<alpha>': nonterminals,
        '<terminals>': terminals
    }

    if not nonterminals:
        g['<nonterminals>'] = ['']
        del g['<lt>']
        del g['<alpha>']
        del g['<gt>']

    return g

```

```py
syntax_diagram(prod_line_grammar(["A", "B", "C"], ["1", "2", "3"]))

```

```py
start

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 199.5 62" width="199.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="99.75" y="35">symbols</text></g></g></g></g></svg>

```py
symbols

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 290.5 92" width="290.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="95.5" y="35">symbol</text></g> <g class="non-terminal"><text x="190.75" y="35">symbols</text></g></g> <g><g class="non-terminal"><text x="145.25" y="65">symbol</text></g></g></g></g></svg>

```py
symbol

```

 <svg class="railroad-diagram" height="92" viewBox="0 0 242.0 92" width="242.0" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="121.0" y="35">nonterminals</text></g></g> <g><g class="non-terminal"><text x="121.0" y="65">terminals</text></g></g></g></g></svg>

```py
nonterminals

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 296.5 62" width="296.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="non-terminal"><text x="78.5" y="35">lt</text></g> <g class="non-terminal"><text x="148.25" y="35">alpha</text></g> <g class="non-terminal"><text x="218.0" y="35">gt</text></g></g></g></g></svg>

```py
lt

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35"><</text></g></g></g></g></svg>

```py
gt

```

 <svg class="railroad-diagram" height="62" viewBox="0 0 148.5 62" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">></text></g></g></g></g></svg>

```py
alpha

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 148.5 122" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">A</text></g></g> <g><g class="terminal"><text x="74.25" y="65">B</text></g></g> <g><g class="terminal"><text x="74.25" y="95">C</text></g></g></g></g></svg>

```py
terminals

```

 <svg class="railroad-diagram" height="122" viewBox="0 0 148.5 122" width="148.5" xmlns="http://www.w3.org/2000/svg"><g transform="translate(.5 .5)"><g><g><g class="terminal"><text x="74.25" y="35">1</text></g></g> <g><g class="terminal"><text x="74.25" y="65">2</text></g></g> <g><g class="terminal"><text x="74.25" y="95">3</text></g></g></g></g></svg>

```py
def make_rule(nonterminals, terminals, num_alts):
    prod_grammar = prod_line_grammar(nonterminals, terminals)

    gf = GrammarFuzzer(prod_grammar, min_nonterminals=3, max_nonterminals=5)
    name = "<%s>" % ''.join(random.choices(string.ascii_uppercase, k=3))

    return (name, [gf.fuzz() for _ in range(num_alts)])

```

```py
make_rule(["A", "B", "C"], ["1", "2", "3"], 3)

```

```py
('<KWR>', ['<A><A>', '<B>12', '<A>11'])

```

```py
from [Grammars](Grammars.html) import unreachable_nonterminals

```

```py
def make_grammar(num_symbols=3, num_alts=3):
    terminals = list(string.ascii_lowercase)
    grammar = {}
    name = None
    for _ in range(num_symbols):
        nonterminals = [k[1:-1] for k in grammar.keys()]
        name, expansions = \
            make_rule(nonterminals, terminals, num_alts)
        grammar[name] = expansions

    grammar[START_SYMBOL] = [name]

    # Remove unused parts
    for nonterminal in unreachable_nonterminals(grammar):
        del grammar[nonterminal]

    assert is_valid_grammar(grammar)

    return grammar

```

```py
make_grammar()

```

```py
{'<YNK>': ['oxz', 'gh', 'm'],
 '<AGT>': ['m<YNK>fy', 'f<YNK>uv', '<YNK>aj'],
 '<XSJ>': ['<AGT>oy', 'y<YNK>gd', '<YNK>dk'],
 '<start>': ['<XSJ>']}

```

现在，我们验证Earley解析器是否可以使用我们的任意语法。

```py
for i in range(5):
    my_grammar = make_grammar()
    print(my_grammar)
    parser = EarleyParser(my_grammar)
    mygf = GrammarFuzzer(my_grammar)
    s = mygf.fuzz()
    print(s)
    for tree in parser.parse(s):
        assert tree_to_string(tree) == s
        display_tree(tree)

```

```py
{'<HIT>': ['kk', '', 'c'], '<ROZ>': ['<HIT>gy', 'nyz', '<HIT>md'], '<start>': ['<ROZ>']}
gy
{'<YYM>': ['t', 'dw', 'gki'], '<TZU>': ['<YYM>rx', '<YYM>uix', '<YYM>j'], '<XUG>': ['<TZU>jvj', '<YYM>s', '<YYM>d'], '<start>': ['<XUG>']}
ts
{'<OAK>': ['d', 't', 'g'], '<VFU>': ['<OAK>ta', '<OAK>qy', '<OAK>m<OAK>b'], '<PIQ>': ['<VFU>ic', 'p<VFU>np', '<OAK>v'], '<start>': ['<PIQ>']}
gv
{'<DYN>': ['c', 'ff', 'w'], '<DNQ>': ['<DYN>eoa', '<DYN><DYN>', 'beu<DYN><DYN>'], '<XYW>': ['<DNQ>o', '<DYN>rx', '<DYN>k'], '<start>': ['<XYW>']}
wrx
{'<JPV>': ['rxx', 'knkbb', 'aq'], '<JSE>': ['c<JPV>i', 'oh<JPV><JPV><JPV>h', '<JPV><JPV>l'], '<JLL>': ['<JSE>qo', '<JSE>wx', '<JSE>hm'], '<start>': ['<JLL>']}
rxxaqlqo

```

至此，我们已经完成了*任意* CFG的实现和测试，现在可以与`LangFuzzer`一起使用以生成更好的模糊输入。

## 背景

存在许多解析技术，这些解析技术可以使用给定的语法来解析给定的字符串，并产生相应的一个或多个派生树。 但是，其中一些技巧仅适用于特定的语法类别。 这些语法类别以可以接受该类别语法的特定类型的解析器命名。 也就是说，解析器功能的上限定义了以该解析器命名的语法类。

*LL* 和 *LR* 解析是解析的主要传统。 在此， *LL* 表示从左到右，最左边的派生，并且表示自顶向下的方法。 另一方面，LR（从左到右，最右边的推导）表示自下而上的方法。 另一种看待它的方式是，LL解析器以*前置*递增地计算派生树，而LR解析器以*后置* [Pingali *等人*，2015。]）。

不同类别的语法在用户可用于编写该类别的语法的功能方面有所不同。 也就是说，相应类型的解析器将无法解析使用了超出允许范围的功能的语法。 例如，`A2_GRAMMAR`是 *LL* 语法，因为它缺少左递归，而`A1_GRAMMAR`不是 *LL* 语法。 这是因为 *LL* 解析器从左到右解析其输入，并通过扩展其遇到的非终端来构造其输入的最左派生。 如果这些规则之一中存在左递归，则 *LL* 解析器将进入无限循环。

同样，如果语法可以由具有k个超前标记的LL解析器解析，则语法为LL（k），而LR（k）语法只能由具有k个超前标记的LR解析器解析。 这些语法很有趣，因为LL（k）和LR（k）语法都具有$ O（n）$解析器，并且与其他语法相比，可以在相对有限的计算预算下使用。

可以提供 *LL（k）*语法的语言称为 *LL（k）*语言（其中k是所需的最小前瞻）。 类似地， *LR（k）*被定义为具有 *LR（k）*语法的语言集。 就语言而言，LL（k）$ \ subset $ LL（k + 1）和LL（k）$ \ subset $ LR（k），以及 *LR（k）* $ = $ *LR（1）*。 所有确定性 *CFL* 都具有 *LR（1）*语法。 但是，存在 *CFL* 本质上是模棱两可的[Ogden *等人*，1968。]，因此，这些人无法提供 *LR（1）* 语法。

*CFG* 的其他主要解析算法是GLL [Scott *等*，2010。]，GLR [Tomita *等*，1987。，富田*等）。* ，2012。]和CYK [Grune *等，*，2008。]。 另一方面，ALL（*）（由ANTLR使用）是一种语法表示形式，它像谓词一样使用*正则表达式*（类似于高级PEG –请参阅[练习](#Exercise-3:-PEG-Predicates)），而不是固定的先行 。 因此，与CFG相比，ALL（*）可以接受更大类别的语法。

就解析的计算限制而言，主CFG解析器的任意语法的复杂度为$ O（n ^ 3）$。 但是，使用任意 *CFG* 进行解析可简化为布尔矩阵乘法[ [Valiant *等人*，1975年。](https://doi.org/10.1016/S0022-0000(75)80046-8)]（反之则是[ [Lee *等*，2002。](https://doi.org/10.1145/505241.505242)]。 目前，这受$ O（2 ^ {23728639} $）的限制[ [Le Gall *等人*，2014。](https://doi.org/10.1145/2608628.2608664)]。 因此，更糟的情况是，解析任意CFG的复杂度可能接近三次。

关于PEG， *PEG* 中可表达的实际语言类别目前未知。 特别地，我们知道 *PEG* 可以表达某些语言，例如$ a ^ n b ^ n c ^ n $。 但是，我们不知道是否存在 *CFL* 与 *PEG* 无法表达。 在第2.3节中，我们提供了一个违反直觉的PEG语法实例。 尽管对于我们的目的很重要（我们使用语法来生成输入），但这并不是对PEG解析的批评。 PEG专注于编写用于识别给定语言的语法，而不一定要解释任意PEG可能产生的语言。 给定要解析的上下文无关语言，几乎总是可以在PEG中为其编写语法，并且鉴于1）PEG可以在$ O（n）$时间内解析任何字符串，以及2）目前我们知道 （3）与 *LR* 语法相比，PEG通常更直观，因为当编写语言解析器PEG时，它可以自上而下地解释 应该认真考虑。

## 经验教训

*   语法可以用于生成给定字符串的派生树。
*   解析表达式文法很直观，易于实现，但在编写时需要格外小心。
*   Earley解析器可以解析任意上下文无关文法。

## 后续步骤

*   使用已解析的输入来[重新组合现有输入](LangFuzzer.html)

## 练习

### 练习1：替代Packrat

在 *Packrat* 解析器中，我们展示了如何实现简单的 *PEG* 解析器。 该解析器使用索引来跟踪文本中的当前位置。 您可以修改解析器，使其仅使用当前子字符串而不跟踪索引吗？ 即，它不再应具有`at`参数。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**解决方案。** 这是一个可能的解决方案：

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习2：更多PEG语法

*PEG* 语法提供了一些符号上的便利，使人想起了正则表达式。 例如，它支持以下运算符（字母`T`和`A`表示可以是终端或非终端的令牌。`ε`是一个空字符串，而`/`是与无序类似的有序选择运算符 选择运算符`|`）：

*   `T?`表示T的可选贪婪匹配，`A := T?`等效于`A := T/ε`。
*   `T*`表示`T`的零个或多个贪婪匹配，并且`A := T*`等于`A := T A/ε`。
*   `T+`代表一个或多个贪婪匹配-等同于`TT*`

如果您查看上面的三种表示法，则每种表示法都可以用基本语法表示在语法中。 还记得[中有关语法](Grammars.html)的一章的练习，该章开发了`define_ex_grammar()`可以将语法表示为Python代码吗？ 将`define_ex_grammar()`扩展到`define_peg()`以支持上述符号上的便利。 装饰器应将包含这些符号的给定语法重写为基本语法中的等效语法。

### 练习3：PEG谓词

除了这些符号上的便利之外，它还支持两个谓词，这些谓词可以提供强大的超前功能，而不会消耗任何输入。

*   如果匹配`T`，则`T&A`表示与`T`匹配的*和谓词*，紧随其后的是`A`
*   如果匹配`T`，则`T!A`表示与`T`匹配的*非谓词*，并且紧随其后是`A`的是*而不是*。

在我们的 *PEG* 解析器中实现这些谓词。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习4：Earley填充图

在`Earley Parser`和`Column`类中，即使订购了`dict`，我们也将状态既保持为`list`，也保持为`dict`。 你能解释为什么吗？

**提示**：请参见`fill_chart`方法。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习5：Leo Parser

最初的Earley解析器的问题之一是，尽管它可以使用任意*上下文自由语法*解析字符串，但其在右递归语法上的性能是二次的。 也就是说，它需要$ O（n ^ 2）$运行时间和空间来解析右递归语法。 例如，考虑通过两个不同的语法`LR_GRAMMAR`和`RR_GRAMMAR`解析以下字符串。

```py
mystring = 'aaaaaa'

```

要查看该问题，我们需要启用日志记录。 这是使用`LR_GRAMMAR`进行解析的记录版本

```py
result = EarleyParser(LR_GRAMMAR, log=True).parse(mystring)
for _ in result: pass # consume the generator so that we can see the logs

```

```py
None chart[0]
<A>:= |(0,0)
<start>:= <A> |(0,0) 

a chart[1]
<A>:= <A> a |(0,1)
<start>:= <A> |(0,1) 

a chart[2]
<A>:= <A> a |(0,2)
<start>:= <A> |(0,2) 

a chart[3]
<A>:= <A> a |(0,3)
<start>:= <A> |(0,3) 

a chart[4]
<A>:= <A> a |(0,4)
<start>:= <A> |(0,4) 

a chart[5]
<A>:= <A> a |(0,5)
<start>:= <A> |(0,5) 

a chart[6]
<A>:= <A> a |(0,6)
<start>:= <A> |(0,6) 

```

将其与`RR_GRAMMAR`的解析进行比较，如下所示：

```py
result = EarleyParser(RR_GRAMMAR, log=True).parse(mystring)
for _ in result: pass

```

```py
None chart[0]
<A>:= |(0,0)
<start>:= <A> |(0,0) 

a chart[1]
<A>:= |(1,1)
<A>:= a <A> |(0,1)
<start>:= <A> |(0,1) 

a chart[2]
<A>:= |(2,2)
<A>:= a <A> |(1,2)
<A>:= a <A> |(0,2)
<start>:= <A> |(0,2) 

a chart[3]
<A>:= |(3,3)
<A>:= a <A> |(2,3)
<A>:= a <A> |(1,3)
<A>:= a <A> |(0,3)
<start>:= <A> |(0,3) 

a chart[4]
<A>:= |(4,4)
<A>:= a <A> |(3,4)
<A>:= a <A> |(2,4)
<A>:= a <A> |(1,4)
<A>:= a <A> |(0,4)
<start>:= <A> |(0,4) 

a chart[5]
<A>:= |(5,5)
<A>:= a <A> |(4,5)
<A>:= a <A> |(3,5)
<A>:= a <A> |(2,5)
<A>:= a <A> |(1,5)
<A>:= a <A> |(0,5)
<start>:= <A> |(0,5) 

a chart[6]
<A>:= |(6,6)
<A>:= a <A> |(5,6)
<A>:= a <A> |(4,6)
<A>:= a <A> |(3,6)
<A>:= a <A> |(2,6)
<A>:= a <A> |(1,6)
<A>:= a <A> |(0,6)
<start>:= <A> |(0,6) 

```

从每个字母的分析日志中可以看出，表示为`<A>: a <A> ● (i, j)`的状态的数量在每个阶段都增加，而这些仅仅是前一个字母的剩余。 除了简单地完成这些条目之外，它们对解析没有任何贡献。 但是，它们占用空间，并且需要检查资源，因此在分析中贡献了`n`因子。

Joop Leo [ [Joop M.I.M. Leo，1991年。](https://doi.org/https://doi.org/10.1016/0304-3975(91)90180-A)发现，通过检测右递归可以避免这种低效率。 这个想法是，在开始`completion`步骤之前，检查当前项目是否具有*确定性还原路径*。 如果存在这样的路径，则将*确定性还原路径*的最顶层元素的副本添加到当前列，然后返回。 如果不是，请执行原始的`completion`步骤。

**定义2.1** ：如果某项在$ [B \ rightarrow \ alpha A。，k] $之上，则认为该项目在$ [A \ rightarrow \ gamma。，i] $上方的确定性归约路径上 $ [B \ rightarrow \ alpha。 A，k] $是$ I_i $中唯一一个点在A前面的项目，或者如果它在$ [B \ rightarrow \ alpha A。，k] $以上的确定性归约路径上。 如果确定路径上的确定性归约路径上没有项目，则该路径上的项目称为*最顶部*。 [Joop M.I.M. Leo，1991。](https://doi.org/https://doi.org/10.1016/0304-3975(91)90180-A)。

找到*确定性还原路径*如下：

给定一个完整的状态，以`<A> : seq_1 ● (s, e)`表示，其中`s`是该规则的起始列，`e`当前列，则在上方有一个*确定性还原路径* **如果满足两个约束。**

1.  在`s`列中以`<B> : seq_2 ● <A> (k, s)`的形式存在*单个*项目。
2.  那应该是s中的*单个*项目，在`<A>`前面加点

生成的项目的格式为`<B> : seq_2 <A> ● (k, e)`，它只是来自（1）的高级项目，在确定性约简路径中被视为`<A>:.. (s, e)`以上。 `seq_1`和`seq_2`是任意符号序列。

这形成以下链接链，其中`<A>:.. (s_1, e)`是`<B>:.. (s_2, e)`等的子级。

这是可视化链的一种方法：

```py
<C> : seq_3 <B> ● (s_3, e)  
             |  constraints satisfied by <C> : seq_3 ● <B> (s_3, s_2)
            <B> : seq_2 <A> ● (s_2, e)  
                         | constraints satisfied by <B> : seq_2 ● <A> (s_2, s_1)
                        <A> : seq_1 ● (s_1, e)
```

本质上，我们要做的是确定潜在的确定性权利递归候选者，对其进行补充，然后*丢弃结果*。 我们这样做直到到达最高点。 参见Grune等人[Grune *等人*，2008。]以获取更多信息。

请注意，完成项位于同一列（`e`）中，每个候选对象的约束都在越来越多的早期列中得到满足（如下所示）：

```py
<C> : seq_3 ● <B> (s_3, s_2)  -->              <C> : seq_3 <B> ● (s_3, e)
               |
              <B> : seq_2 ● <A> (s_2, s_1) --> <B> : seq_2 <A> ● (s_2, e)  
                             |
                            <A> : seq_1 ●                        (s_1, e)
```

在此链之后，最顶层的项是没有父项的项`<C>:.. (s_3, e)`。 需要保存的最上面的项目被Leo称为*可传递的*项目，并且与开始查找的非终端符号相关联。 需要将传递项目添加到我们检查的每个列中。

这是解析器`LeoParser`的框架。

```py
class LeoParser(EarleyParser):
    def complete(self, col, state):
        return self.leo_complete(col, state)

    def leo_complete(self, col, state):
        detred = self.deterministic_reduction(state)
        if detred:
            col.add(detred.copy())
        else:
            self.earley_complete(col, state)

    def deterministic_reduction(self, state):
        raise NotImplemented()

```

您可以实现`deterministic_reduction()`方法来获取最高元素吗？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**Solution.** Here is a possible solution:

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**进阶：**我们更正了复杂度界限。 但是，由于我们只保存了正确递归的最顶层项，因此我们需要修复解析器，以在提取解析树时了解我们的修复。 你能修好它吗？

**提示：** Leo建议将Leo项目集简单转换为普通Earley集，而确定性约简的结果将扩展到其原始结果。 为此，请记住我们之前绘制的约束链图。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**解决方案。** 这是一个可能的解决方案。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习6：过滤的Earley分析器

我们的Earley和Leo解析器的问题之一是，当使用替代中包含令牌重复的语法进行解析时，它可能陷入无限循环。 例如，考虑下面的语法。

```py
RECURSION_GRAMMAR = {
    "<start>": ["<A>"],
    "<A>": ["<A>", "<A>aa", "AA", "<B>"],
    "<B>": ["<C>", "<C>cc" ,"CC"],
    "<C>": ["<B>", "<B>bb", "BB"]
}

```

使用这种语法，可以产生`<A>`的无限导数链（直接递归）或`<B> -> <C> -> <B> ...`的无限导数链（间接递归）。 问题在于，我们的实现可能会陷入尝试推导这些无限链之一的过程。

```py
from [ExpectError](ExpectError.html) import ExpectTimeout

```

```py
with ExpectTimeout(1, print_traceback=False):
    mystring = 'AA'
    parser = LeoParser(RECURSION_GRAMMAR)
    tree, *_ = parser.parse(mystring)
    assert tree_to_string(tree) == mystring
    display_tree(tree)

```

```py
TimeoutError (expected)

```

您是否可以实施一种解决方案，以便丢弃包含此类链的任何树？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**Solution.** Here is a possible solution.

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习7：迭代Earley解析器

在某些情况下，递归算法非常方便，但有时由于内存或速度问题，我们可能需要迭代而不是递归。

您可以实现`EarleyParser`的迭代版本吗？

**提示：**通常，您可以使用堆栈将迭代算法替换为递归算法。 一种简单的方法是将参数压入堆栈，而不是将其传递给递归函数。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**Solution.** Here is a possible solution.

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习8：第一组非终结符

先前，我们提供了一种提取`nullable`（ε）集合的方法，该集合通常用于解析。 与`nullable`一起，解析算法通常使用另外两个集合 [`first`和`follow`](https://en.wikipedia.org/wiki/Canonical_LR_parser#FIRST_and_FOLLOW_sets) 。 终端符号的第一集合是其本身，非终端的第一集合由可以在该非终端的任何派生的开始出现的终端符号组成。 可以派生空字符串的任何非终结符的第一组应包含`EPSILON`。 例如，使用我们的`A1_GRAMMAR`，`<expr>`和`<start>`的第一组都是`{0,1,2,3,4,5,6,7,8,9}`。 任何自递归非终结符的提取第一集都非常简单。 只需简单地递归计算其选择表达式的第一个元素的第一组即可。 为自递归非终结符设置`first`的计算非常棘手。 必须递归计算第一个集合，直到确定没有更多终端可以添加到第一个集合。

您可以使用我们的`fixpoint()`装饰器实现`first`集吗？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习9：遵循非终结符[的集合](#Exercise-9:-Follow-Set-of-a-Nonterminal)

跟随集合的定义与第一个集合相似。 非终结符的后续集合是在任何派生中使用该非终结符之后可能发生的终结符集合。 起始符号的跟随集是`EOF`，任何非终止符的跟随集是在任何选择表达式中之后的所有符号的第一组的超集。

例如，`A1_GRAMMAR`中的`<expr>`的跟随集合是集合`{EOF, +, -}`。

与上一练习中一样，使用`fixpoint()`装饰器实现`followset()`。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

### 练习10：一个LL（1）解析器-Parser)

如我们前面提到的，存在其他种类的解析器，它们以最右导数（ *LR（k）*）从左到右操作，或者以最左导数（ *LL（ k）*）与 *k* 表示允许解析器使用的超前量。

先行者应该怎么做？ 该前瞻可用于确定要应用的规则。 对于 *LL（1）*解析器，要应用的规则是通过查看不同规则的第一个*第一个*集来确定的。 我们之前实现了`first_expr()`，它接受一个表达式，即`nullables`的集合，并计算该规则的第一组。

如果一个规则可以导出一个空集，则该规则也可以适用，如果看到相应非终结符的`follow()`集。

#### 第1部分：LL（1）解析表-Parsing-Table)

本练习的第一部分是实现*解析表*，该表描述了 *LL（1）*解析器在提前看清终端符号时应采取的措施。 该表应采用*字典*的形式，以使键表示非终结符，并且该值应包含另一个以键为终结符的字典，并包含特定规则以继续解析为值。

让我们用一个例子来说明这个表。 `parse_table()`方法填充应符合以下要求的`self.table`数据结构：

```py
class LL1Parser(Parser):
    def parse_table(self):
        self.my_rules = rules(self.cgrammar)
        self.table = ...          # fill in here to produce

    def rules(self):
        for i, rule in enumerate(self.my_rules):
            print(i, rule)

    def show_table(self):
        ts = list(sorted(terminals(self.cgrammar)))
        print('Rule Name\t| %s' % ' | '.join(t for t in ts))
        for k in self.table:
            pr = self.table[k]
            actions = list(str(pr[t]) if t in pr else ' ' for t in ts)
            print('%s  \t| %s' % (k, ' | '.join(actions)))

```

调用`LL1Parser(A2_GRAMMAR).show_table()`时，应显示在下表中：

```py
for i, r in enumerate(rules(canonical(A2_GRAMMAR))):
    print("%d\t  %s := %s" % (i, r[0], r[1]))

```

```py
0	 <start> := ['<expr>']
1	 <expr> := ['<integer>', '<expr_>']
2	 <expr_> := ['+', '<expr>']
3	 <expr_> := ['-', '<expr>']
4	 <expr_> := []
5	 <integer> := ['<digit>', '<integer_>']
6	 <integer_> := ['<integer>']
7	 <integer_> := []
8	 <digit> := ['0']
9	 <digit> := ['1']
10	 <digit> := ['2']
11	 <digit> := ['3']
12	 <digit> := ['4']
13	 <digit> := ['5']
14	 <digit> := ['6']
15	 <digit> := ['7']
16	 <digit> := ['8']
17	 <digit> := ['9']

```

| 规则名称 |  | + | - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 开始 |  |  |  | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| EXPR |  |  |  | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| expr_ |  | 2 | 3 |  |  |  |  |  |  |  |  |  |  |
| 整数 |  |  |  | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 5 |
| 整数_ |  | 7 | 7 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
| 数字 |  |  |  | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

#### 第2部分：解析器

获得解析表后，将执行以下解析器：考虑要分析的标记序列中的第一项，并使用起始符号为堆栈添加种子。

当堆栈不为空时，请从堆栈中提取第一个符号，如果该符号是终端，请验证该符号是否与输入流中的项匹配。 如果符号是非终结符，请使用符号和输入项从解析表中查找下一个规则。 将找到的规则插入堆栈的顶部。 跟踪要分析以构建分析表的表达式。

使用先前定义的解析表来实现完整的LL（1）解析器。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.

**解决方案。** 这是完整的解析器：

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Parser.ipynb#Exercises) to work on the exercises and see solutions.