實現一個簡單的直譯器(6)
阿新 • • 發佈:2020-03-05
譯自:https://ruslanspivak.com/lsbasi-part6/
(已獲得作者授權)
今天,我們通過將帶括號的表示式新增到語法,並實現一個能夠計算任意深度巢狀表示式的直譯器來結束對算術表示式的討論。
讓我們開始吧!
首先,讓我們修改語法以支援括號內的表示式,正如在第5部分中所記得的那樣,factor規則用於表示式中的基本單位,在那篇文章中,我們僅有的基本單位是整數,今天我們添加了另外一個基本單位,也就是帶括號的表示式。
這是我們更新的語法:
![](https://img2020.cnblogs.com/blog/1133903/202003/1133903-20200304213010239-420552336.png)
expr和term與第5部分完全相同,唯一的變化是factor的產生式,其中LPAREN表示左括號'(',RPAREN表示右括號')',而括號之間的非終結符expr表示expr規則。
這是factor的更新語法圖:
![](https://img2020.cnblogs.com/blog/1133903/202003/1133903-20200304213024575-154979202.png)
因為expr和term的語法規則沒有改變,所以它們的語法圖看起來與[第5部分](https://www.cnblogs.com/Xlgd/p/12402997.html)中的相同:
![](https://img2020.cnblogs.com/blog/1133903/202003/1133903-20200304213034820-852414097.png)
這是我們新語法的一個有趣功能:遞迴,如果嘗試推導表示式2 * (7 + 3),則將從expr起始符開始,之後將遞迴地再次使用expr規則來推導表示式(7 + 3)這一部分。
讓我們根據語法分解表示式2 *(7 + 3):
![](https://img2020.cnblogs.com/blog/1133903/202003/1133903-20200304213046186-613001245.png)
好的,讓我們開始將新的更新語法轉換為程式碼。
以下是對上一篇文章程式碼的主要更改:
1、對Lexer進行修改,以返回另外兩個標記:LPAREN用於左括號,而RPAREN用於右括號。
2、對直譯器的factor函式進行修改,可以解析(parse)除整數以外的帶括號的表示式。
這是計算器的完整程式碼,可以計算任意數量的加,減,乘和除整數運算以及帶有任意深度巢狀的帶括號的表示式:
```pascal
# Token types
#
# EOF (end-of-file) token is used to indicate that
# there is no more input left for lexical analysis
INTEGER, PLUS, MINUS, MUL, DIV, LPAREN, RPAREN, EOF = (
'INTEGER', 'PLUS', 'MINUS', 'MUL', 'DIV', '(', ')', 'EOF'
)
class Token(object):
def __init__(self, type, value):
self.type = type
self.value = value
def __str__(self):
"""String representation of the class instance.
Examples:
Token(INTEGER, 3)
Token(PLUS, '+')
Token(MUL, '*')
"""
return 'Token({type}, {value})'.format(
type=self.type,
value=repr(self.value)
)
def __repr__(self):
return self.__str__()
class Lexer(object):
def __init__(self, text):
# client string input, e.g. "4 + 2 * 3 - 6 / 2"
self.text = text
# self.pos is an index into self.text
self.pos = 0
self.current_char = self.text[self.pos]
def error(self):
raise Exception('Invalid character')
def advance(self):
"""Advance the `pos` pointer and set the `current_char` variable."""
self.pos += 1
if self.pos > len(self.text) - 1:
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. One token at a time.
"""
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, '-')
if self.current_char == '*':
self.advance()
return Token(MUL, '*')
if self.current_char == '/':
self.advance()
return Token(DIV, '/')
if self.current_char == '(':
self.advance()
return Token(LPAREN, '(')
if self.current_char == ')':
self.advance()
return Token(RPAREN, ')')
self.error()
return Token(EOF, None)
class Interpreter(object):
def __init__(self, lexer):
self.lexer = lexer
# set current token to the first token taken from the input
self.current_token = self.lexer.get_next_token()
def error(self):
raise Exception('Invalid syntax')
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.lexer.get_next_token()
else:
self.error()
def factor(self):
"""factor : INTEGER | LPAREN expr RPAREN"""
token = self.current_token
if token.type == INTEGER:
self.eat(INTEGER)
return token.value
elif token.type == LPAREN:
self.eat(LPAREN)
result = self.expr()
self.eat(RPAREN)
return result
def term(self):
"""term : factor ((MUL | DIV) factor)*"""
result = self.factor()
while self.current_token.type in (MUL, DIV):
token = self.current_token
if token.type == MUL:
self.eat(MUL)
result = result * self.factor()
elif token.type == DIV:
self.eat(DIV)
result = result / self.factor()
return result
def expr(self):
"""Arithmetic expression parser / interpreter.
calc> 7 + 3 * (10 / (12 / (3 + 1) - 1))
22
expr : term ((PLUS | MINUS) term)*
term : factor ((MUL | DIV) factor)*
factor : INTEGER | LPAREN expr RPAREN
"""
result = self.term()
while self.current_token.type in (PLUS, MINUS):
token = self.current_token
if token.type == PLUS:
self.eat(PLUS)
result = result + self.term()
elif token.type == MINUS:
self.eat(MINUS)
result = result - self.term()
return result
def main():
while True:
try:
# To run under Python3 replace 'raw_input' call
# with 'input'
text = raw_input('calc> ')
except EOFError:
break
if not text:
continue
lexer = Lexer(text)
interpreter = Interpreter(lexer)
result = interpreter.expr()
print(result)
if __name__ == '__main__':
main()
```
將上面的程式碼儲存到calc6.py檔案中,體驗一下新直譯器是否正確計算了具有不同運算子和括號的算術表示式。
這是執行效果:
```
$ python calc6.py
calc> 3
3
calc> 2 + 7 * 4
30
calc> 7 - 8 / 4
5
calc> 14 + 2 * 3 - 6 / 2
17
calc> 7 + 3 * (10 / (12 / (3 + 1) - 1))
22
calc> 7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)
10
calc> 7 + (((3 + 2)))
12
```
這是今天的練習:
![](https://img2020.cnblogs.com/blog/1133903/202003/1133903-20200304213104746-923473412.png)
1、如本文所述,編寫你自己的算術表示式直譯器版本,記住:重複是所有學習的源泉。
嘿,你一直閱讀到最後! 恭喜你已經學會了如何實現一個基本的遞迴下降解析器/直譯器,它可以評估非常複雜的算術表示式。
在下一篇文章中,我將詳細討論遞迴下降解析器。 我還將在直譯器和編譯器的構造中介紹一個重要且廣泛使用的資料結構,我們將在整個系列中使用。
請繼續關注,很快再見。在此之前請你繼續實現自己的直譯器,最重要的是:盡情享受這一