Let’s Build A Simple Interpreter 2

If you learn only methods, you’ll be tied to your methods. But if you learn
principles, you can devise your own methods.

原文地址:https://ruslanspivak.com/lsbasi-part2/

这是第二个版本V2.0,第二个版本较第一个版V1.0,它可以做到:

  1. 处理输入字符串中任何位置的空白符
  2. 处理输入中的多位数
  3. 两个整数相减(版本V1.0中只有加法)

下面先给出V2.0的新版计算器的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Token types
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
INTEGER, PLUS, MINUS, EOF = 'INTEGER', 'PLUS', 'MINUS', 'EOF'

class Token():
    def __init__(self, type, value):
	# token type: 'INTEGER', 'PLUS', 'MINUS', or 'EOF'
	self.type  = type
	# token value: non-negative integer value, '+', '-', or None
	self.value = value

    def __str__(self):
	"""String representation of class instance

	Examples:
	    Token(INTEGER, 3)
	    Token(PLUS, '+')
	"""
		return f'Token({self.type}, {self.value})'

    def __repr__(self):
		return self.__str__()
##__repr__() 与 __str__() 的主要区别在于,前者在交互式步骤中显示结果,后者在 print 函数中显示结果。
    
    
class Interpreter():

    def __init__(self, text):
	# client string input, e.g. "3 + 5", "12 - 5", etc
	self.text = text
	# self.pos is an index into self.text
	self.pos  = 0
	# current token instance
	self.current_token = None
	self.current_char  = self.text[self.pos]

    def error(self):
	raise Exception('Error parsing input')
    
    ########新增#######
    def advance(self):
	"""Advance the 'pos' pointer and set the 'current_char' variable."""
	self.pos += 1
	if self.pos >= len(self.text):
	    self.current_char = None  # Indicates end of input
	else:
	    self.current_char = self.text[self.pos]

    def skip_whitespace(self):
	while self.current_char is not None and self.current_char.isspace():
	    self.advance()

    def integer(self):
	"""Return a (multidigit) integer consumed from the input."""
	result = ''
	while self.current_char is not None and self.current_char.isdigit():
	    result += self.current_char
	    self.advance()
	return int(result)
  	##新增 完毕#####
    
    def get_next_token(self):
	"""Lexical analyzer (also known as scanner or tokenizer)

	This method is responsible for breaking a sentence
	apart into tokens.
	"""
	while self.current_char is not None:
	    if self.current_char.isspace():
		self.skip_whitespace()
		continue
	    if self.current_char.isdigit():
		return Token(INTEGER, self.integer())
	    if self.current_char == '+':
		self.advance()
		return Token(PLUS, '+')
	    if self.current_char == '-':
		self.advance()
		return Token(MINUS, '-')

	    self.error()

	return Token(EOF, None)

    def eat(self, token_type):
	# compare the current token type with the passed token
	# type and if they match then "eat" the current token
	# and assign the next token to the self.current_token,
	# otherwise raise an exception.
	if self.current_token.type == token_type:
	    self.current_token = self.get_next_token()
	else:
	    self.error()

    def expr(self):
	"""Parser / Interpreter

	expr -> INTEGER PLUS INTEGER
	expr -> INTEGER MINUS INTEGER
	"""

	# set current token to the first token from the input
	self.current_token = self.get_next_token()

	# we expect the current token to be an integer
	left = self.current_token
	self.eat(INTEGER)

	# we expect the current token to be either a '+' or '-'
	op = self.current_token
	if op.type == PLUS:
	    self.eat(PLUS)
	elif op.type == MINUS:
	    self.eat(MINUS)
	else:
	    self.error()

	# we expect the current token to be an integer
	right = self.current_token
	self.eat(INTEGER)
	# after the above call the self.current_token is set to
	# EOF token

	# at this point either the INTEGER PLUS INTEGER or
	# the INTEGER MINUS INTEGER sequence of tokens
	# has been successfully found and the method can just
	# return the result of adding or subtracting two integers,
	# thus effectively interpreting client input
	if op.type == PLUS:
	    result = left.value + right.value
	elif op.type == MINUS:
	    result = left.value - right.value
	else:
	    self.error()
	return result

def main():
    while True:
	try:
	    # To run under Python3 replace 'raw_input' call with 'input'
	    text = input('calc> ')
	except EOFError:
	    break
	if not text:
	    continue
	interpreter = Interpreter(text)
	result      = interpreter.expr()
	print(result)

if __name__ == '__main__':
    main()

把以上代码保存到名为 calc2.py 中,或者直接从 GitHub 上下载。试一试。亲眼看一下 它可以按预期运行:它可以处理输入中的任何位置的空白符;它接受多位整数,除了整数相 加还可以处理整数相减。

下面可以再笔记本上次尝试:

1
2
3
4
5
6
$ python calc2.py
calc> 27 + 3
30
calc> 27 - 7
20
calc>

第一部分相比代码的主要变化有:

  1. get_next_token 方法做了一点重构。增加指针 pos 的逻辑被重构到了方法 advance 中。
  2. 增加了两个方法: skip_whitespace 用来忽略空白符, integer 用来处理输入中的多位整数。
  3. expr 方法在修改后,除了可以识别 INTEGER -> PLUS -> INTEGER 这个组合(phrase) 之外,还可以识别INTEGER -> MINUS -> INTEGER。而且在成功识别相应的组合后,也可以进行相应的加减操作。

第一部分你尝到了两个重要的概念,即 token词法分析器 。今天我想聊一聊 lexemeparsingparser

你已经知道 token 了。但为了叙述方便,需要介绍一下 lexeme。什么是 lexeme? lexeme 是组成 token 的一个字符序列。在下面的图片中是一些 token 和 lexeme 的例子, 希望它能把两者之间的关系表达清楚:

img

现在还记得 expr 方法吗?我以前说过这是真正解释算术表达式的地方。但在解释一个表达式之前,你需要知道它是哪种组合,比如相加或相减。这是 expr 方法本质上做的事: 它从 get_next_token 方法得到的 token 流中找到结构,然后解释它识别出的组合,产 生算术表达式的结果。

又到了做练习的时间了。

  1. 扩展计算器以处理两个整数相乘
  2. 扩展计算器以处理两个整数相除
  3. 修改代码以使它可以解释包含任意个数字的加减操作,如“9 - 5 + 3 + 11”

本节检测:

  1. 什么是 lexeme?
  2. 在 token 流中找到结构的过程叫什么?或者这么问,在 token 流中识别出特定组合的过程叫什么?
  3. 解释器(编译器)做 parsing 工作的部分叫什么?

梳理

  1. 首先输入一个表达式,如“3+9”,送给解释器Interpreter
  2. 开始对这个表达式拆分成一个个的token,这一步叫做词法分析,由词法分析器(lexical analyzer)来完成。在本文中,函数get_next_token就相当于词法分析器。
    • 词法分析器get_next_token处理完之后的是一个个的token(type , value)
  3. 然后开始进行语法分析(由于本文中只实现了加法、减法),所以这里的语法分析就是分析:加、减法的表达式。执行这个步骤的是expr函数,其中expr 方法使用了辅助方法eat来验证传给token的类型与当前类型是否相匹配。与现有语法规则(即,加法规则和减法规则)不匹配,eat就会抛出异常。
  4. 语法分析完成之后,就直接得出算式的结果。(这也很符合解释器的特性嘛:边解释边执行,不会生成目标代码)

其他的函数:

Interpreter

  • error(self):当有错误的时候,就调用它
  • advance(self):增加指针pos的作用,即,将指向下一个token
  • skip_whitespace(self):跳过空格,V2.0新增加的功能
  • integer(self):为了可以算多位数的加减法。
    • 这里要注意:如3和233是两个整数类型的token,但是值得注意的是这篇文章中的token是按照输入的一串字符串(如“3+155”)每一个都当成一个token。也就是说本来155是一个token,但是从细节上看,在代码实现中155是被当成了三次“token”然后经过integer函数才形成了最终形体155这个token。

在本系列的下一篇文章中你会扩展你的计算器来处理更复杂的算术表达式。


发布了33 篇原创文章 · 获赞 9 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览