# 练习 33：解析器

def hello(x, y):
print(x + y)

hello(10, 20)


* root
* Function
- name = hello
- parameters = x, y
- code:
* Call
- name = print
- parameters =
* Expression
- a = x
- b = y
* Call
- name = hello
- parameters = 10, 20


## 递归下降解析

RDP 使用多个相互递归的函数调用，它实现了给定语法的树形结构。RDP 解析器的代码看起来像你正在处理的实际语法，只要遵循一些规则，它们就很容易编写。RDP 解析器的两个缺点是：它们可能不是非常有效，并且通常需要手动编写它们，因此它们的错误比生成的解析器更多。对于 RDP 解析器可以解析的东西，还有一些理论上的限制，但是由于你手动编写它们，你通常可以解决很多限制。

peek

match

skip

def function_definition(tokens):
name = match(tokens, 'NAME')
match(tokens, 'LPAREN')
params = parameters(tokens)
match(tokens, 'RPAREN')
match(tokens, 'COLON')
return {'type': 'FUNCDEF', 'name': name, 'params': params}


## BNF 语法

root = *(funccal / funcdef)
funcdef = DEF name LPAREN params RPAREN COLON body
funccall = name LPAREN params RPAREN
params = expression *(COMMA expression)
expression = name / plus / integer
plus = expression PLUS expression
PLUS = "+"
LPAREN = "("
RPAREN = ")"
COLON = ":"
COMMA = ","
DEF = "def"


funcdef =

DEF

name

LPAREN

params

RPAREN

COLON

body

## 简单的示例黑魔法解析器

from scanner import *
from pprint import pprint

def root(tokens):
"""root = *(funccal / funcdef)"""
first = peek(tokens)

if first == 'DEF':
return function_definition(tokens)
elif first == 'NAME':
name = match(tokens, 'NAME')
second = peek(tokens)

if second == 'LPAREN':
return function_call(tokens, name)
else:
assert False, "Not a FUNCDEF or FUNCCALL"

def function_definition(tokens):
"""
funcdef = DEF name LPAREN params RPAREN COLON body
I ignore body for this example 'cause that's hard.
I mean, so you can learn how to do it.
"""
name = match(tokens, 'NAME')
match(tokens, 'LPAREN')
params = parameters(tokens)
match(tokens, 'RPAREN')
match(tokens, 'COLON')
return {'type': 'FUNCDEF', 'name': name, 'params': params}

def parameters(tokens):
"""params = expression *(COMMA expression)"""
params = []
start = peek(tokens)
while start != 'RPAREN':
params.append(expression(tokens))
start = peek(tokens)
if start != 'RPAREN':
assert match(tokens, 'COMMA')
return params

def function_call(tokens, name):
"""funccall = name LPAREN params RPAREN"""
match(tokens, 'LPAREN')
params = parameters(tokens)
match(tokens, 'RPAREN')
return {'type': 'FUNCCALL', 'name': name, 'params': params}

def expression(tokens):
"""expression = name / plus / integer"""
start = peek(tokens)

if start == 'NAME':
name = match(tokens, 'NAME')
if peek(tokens) == 'PLUS':
return plus(tokens, name)
else:
return name
elif start == 'INTEGER':
number = match(tokens, 'INTEGER')
if peek(tokens) == 'PLUS':
return plus(tokens, number)
else:
return number
else:
assert False, "Syntax error %r" % start

def plus(tokens, left):
"""plus = expression PLUS expression"""
match(tokens, 'PLUS')
right = expression(tokens)
return {'type': 'PLUS', 'left': left, 'right': right}

def main(tokens):
results = []
while tokens:
results.append(root(tokens))
return results

parsed = main(scan(code))
pprint(parsed)


## 挑战练习

• 接受一个Scanner对象并执行自身。你可以假设任何默认函数是语法的起始。
• 拥有错误处理代码，比我简单的assert用法更好。

• 不是仅仅产生dicts的列表，你应该为每个语法生产式的结果创建类。这些类之后成为列表中的对象。
• 这些类只需要存储被解析的记号，但是要准备做更多事情。
• 你只需要解析这个微型语言，但你应该尝试解决“Python 缩进”问题。你可能需要秀阿贵扫描器，使其更智能，才能在行的开头匹配INDENT空白字符，并在其他位置忽略它。你还需要跟踪如何多少缩进了多少，同时也记录零缩进，所以你可以“压缩”代码块。

## 深入学习

• #### Python 资源大全中文版

伯乐在线 python 1页 2018年6月6日
10237

• #### scikit-learn (sklearn) 官方文档中文版

ApacheCN python 65页 2019年5月26日
2022

• #### What the f*ck Python中文版

leisurelicht python 70页 2019年5月26日
7300

• #### Office 365 开发入门指南

chenxizhang visualstudio 51页 2018年8月3日
98

• #### 前端开发者手册

dwqs html5 javascript css3 92页 2018年6月5日
548

• #### 开发经验总结

phodal code 1页 2018年8月3日
641