statement: assignment | expr | if_statement
expr: expr '+' term | expr '-' term | term
term: term '*' atom | term '/' atom | atom
atom: NAME | NUMBER | '(' expr ')'
assignment: target '=' expr
target: NAME
if_statement: 'if' expr ':' statement
statement
,我们有如下函数:def statement():
if assignment():
return True
if expr():
return True
if if_statement():
return True
return False
get_token()
,它返回输入内容中的下一个标记,每次消费掉几个字符。tokenize
模块对它作了进一步简化:它的基础 API 是一个生成器,每次生成(yield)一个标记。TypeInfo
对象,它有几个字段,其中最重要之一表示的是标记的类型(例如 NAME
、NUMBER
、STRING
),还有一个很重要的是字符串值,表示该标记所包含的字符(例如 abc
、42
或者 "hello world"
)。还有的字段会指明每个标记出现在输入文件中的坐标,这对于报告错误很有用。ENDMARKER
,它表示的是抵达了输入文件的末尾。如果你忽略它,并尝试获取下一个标记,则生成器会终结。itertools.tee()
来做,但是根据文档中的警告,在我们这种情况下,效率可能较低。)Tokenizer
对象封装了一个数组,存放标记及其位置信息。get_token()
返回下一个标记,并推进数组的索引(如果到了数组末尾,则从源码中读取另一个标记)mark()
返回数组的当前索引reset(pos)
设置数组的索引(参数必须从 mark() 方法中得到)peek_token()
,它返回下一个标记且不推进索引。class Tokenizer:
def __init__(self, tokengen):
"""Call with tokenize.generate_tokens(...)."""
self.tokengen = tokengen
self.tokens = []
self.pos = 0
def mark(self):
return self.pos
def reset(self, pos):
self.pos = pos
def get_token(self):
token = self.peek_token()
self.pos += 1
return token
def peek_token(self):
if self.pos == len(self.tokens):
self.tokens.append(next(self.tokengen))
return self.tokens[self.pos]
Parser
类一个 expect()
方法,它可以像解析类方法一样,表示执行成功或失败。expect()
的参数是一个预期的标记——一个字符串(像“+”)或者一个标记类型(像NAME
)。Node
对象,在失败时返回 None
。Node
类可以超级简单:class Node:
def __init__(self, type, children):
self.type = type
self.children = children
class Parser:
def __init__(self, tokenizer):
self.tokenizer = tokenizer
def mark(self):
return self.tokenizer.mark()
def reset(self, pos):
self.tokenizer.reset(pos)
def expect(self, arg):
token = self.tokenizer.peek_token()
if token.type == arg or token.string == arg:
return self.tokenizer.get_token()
return None
class ToyParser(Parser):
def statement(self):
if a := self.assignment():
return a
if e := self.expr():
return e
if i := self.if_statement():
return i
return None
def expr(self):
if t := self.term():
pos = self.mark()
if op := self.expect("+"):
if e := self.expr():
return Node("add", [t, e])
self.reset(pos)
if op := self.expect("-"):
if e := self.expr():
return Node("sub", [t, e])
self.reset(pos)
return t
return None
def term(self):
# Very similar...
def atom(self):
if token := self.expect(NAME):
return token
if token := self.expect(NUMBER):
return token
pos = self.mark()
if self.expect("("):
if e := self.expr():
if self.expect(")"):
return e
self.reset(pos)
return None
token
库中导入。(这能令我们快速地进入 Python 的标记过程;但如果想要构建一个更加通用的 PEG 解析器,则应该探索一些其它方法。)expr
是左递归的,但我的解析器用了右递归,因为递归下降解析器不适用于左递归的语法规则。 def statement(self):
with self.alt():
return self.assignment()
with self.alt():
return self.expr()
with self.alt():
return self.if_statement()
raise ParsingFailure
atom()
中用来识别带括号的表达式的 if-语句,可以变成: with self.alt():
self.expect("(")
e = self.expr()
self.expect(")")
return e