若是你會編譯原理,對其中的詞法分析算法,語法分析算法足夠了解,那麼用什麼語言來作這樣的一件事情都是能夠的,之因此使用Python只是由於本人會的編程語言中, Python的使用時間最長,也最駕輕就熟。所謂性能什麼的不在本文的考慮範圍內, 本文主要重點是語法分析的表達式的解析,語法解析使用的是普拉特分析法,一種自頂向下的語法解析方法。python
文章目錄以下:git
怎麼解決讓代碼算出如下解決結果?(假設問題代碼保存文1.txt)github
1 + 2 * 3 - 4 / 5
不用語法分析, 最簡答的解決辦法就是算法
with open("1.txt") as rf: print(eval(rf.read())) # 輸出結果 6.2
那麼若是是如下面的代碼呢?(假設問題代碼保存文2.txt)express
add(1, add(1,2))
不用語法分析, 最簡單的解決辦法是編程
def add(a, b): return a + b with open("2.txt") as rf: print(eval(rf.read()), dict(add=add)) # 輸出結果 4 {'add': <function add at 0x0000013E8033AD90>}
若是要求加法的優先級大於乘法呢?就是說先加後乘,好比1+2*3=9而不是正常狀況下的準確答案7。理論上能夠經過重載python的加減乘除來解決這個問題,可是這裏就不研究這個方法了,這個問題就留在文章的末尾解決吧。數組
PS: 怎麼可能會有這麼坑爹的需求?做爲程序猿遇到的坑爹需求還少麼?微信
總的來講上面的解決辦法老是差點意思,咱們沒有更深刻的研究它的語法結構,也就沒辦法得到更多的控制權。app
詞法分析就是講文本里面的代碼分紅一個個最小的組成部分, 這個最小的組成部分你們稱之爲Token.編程語言
什麼是token呢?首先看下面的python代碼。
1+2*3
若是經過詞法分析處理,那麼上面的代碼大概是這樣的表示
Token("整型數字", "1") Token("運算符", "+") Token("整型數字", 2) Token("運算符", "*") Token("整型數字", "3")
因此1,2,3分別是一個Token, +,*分別也是一個Token。
PS: 一個抽象的東西總喜歡用抽象的名詞來解釋。
這裏主要分析一種語法的結構。
四則表達式雖然看起來很簡單,可是應該算的上是編譯原理中一個比較難的部分了吧,主要是算數優先級的問題。
首先定義基本組成元素:
四則運算的表達式定義以下:
1. 數字 2. 數字(加減乘除 數字)* # 加減乘除表明+-*/中的任意一個算術符, (加減乘除 數字)+表明+ 1或者 * 2 這樣的結構可以重複一到無數次 # 好比: 1 或者 1 + 2 或者 1+2+3
PS: 對於這種語法的定義有一種專門的語法來表示,叫作BNF, 若是本文用BNF來講明就是兩個問題了,因此這裏就用中文來表示了。畢竟本文是從無到有系列,若是個人解釋,定義看不懂能夠學習一下BNF,在回來看應該就明白了。
因此本文中如下語法是合法的:
1+2-3*412/53 12 1-4
如下語法是不合法的:
1 + * 1 1+2+
如下面代碼爲例。
1 + 2 - 3 * 4 / 5
經過對上面的語法定義,以及對代碼的觀察,咱們能夠總結出如下兩點:
# -*- coding: utf-8 -*- from __future__ import print_function import string from collections import namedtuple # 定義一個namedtuple類型的Token類型用於表示Token Token = namedtuple("Token", ["type", "value"]) class Lexer(object): # 全部整型數字 numbers = set(map(str, range(10))) # {'2', '9', '1', '0', '6', '3', '7', '5', '8', '4'} # 全部大小寫英文字母 letters = set(string.ascii_letters) # {'W', 'b', 'g', 'a', 'V', 'G', 'h', 'I', 'N', 'X', 'S', 'r', 'e', 'M', 'p', 'F', 'O', 'Z', 't', 'j', 'q', 'L', 'd', 'J', 'R', 'k', 'Y', 'D', 's', 'K', 'o', 'x', 'u', 'A', 'H', 'T', 'i', 'w', 'm', 'n', 'v', 'f', 'C', 'y', 'c', 'E', 'Q', 'P', 'l', 'B', 'z', 'U'} # 加減乘除 ADD = "+" SUB = "-" MUL = "*" DIV = "/" operators = set([ADD, SUB, MUL, DIV]) # END OF FILE 表示文本終結的Token EOF = Token("EOF", "") def parse(self, text): self.tokens = [] self.text = text self.cur_pos = 0 self.cur_char = self.text[self.cur_pos] while self.cur_char is not self.EOF: if self.cur_char == " ": self.next() continue elif self.cur_char in self.numbers: token = self.read_integer() elif self.cur_char in self.operators: token = Token("operator", self.cur_char) self.next() else: raise "未知字符: %s" % self.cur_char self.tokens.append(token) # 加一個EOF是爲了標識整段代碼已經到盡頭 self.tokens.append(self.EOF) return self.tokens def next(self): """使當前字符的位置不斷的向右移動""" self.cur_pos += 1 if self.cur_pos >= len(self.text): self.cur_char = self.EOF else: self.cur_char = self.text[self.cur_pos] def read_integer(self): integer = self.cur_char self.next() while self.cur_char in self.numbers: integer += self.cur_char self.next() return Token("Integer", integer) if __name__ == "__main__": text = "1 + 2" mylexer = Lexer() print("1+2") print(mylexer.parse("1+2")) print() print("3 *4/ 5") print(mylexer.parse("3 *4/ 5"))
程序輸出以下:
1+2 [Token(type='Integer', value='1'), Token(type='operator', value='+'), Token(type='Integer', value='2'), Token(type='EOF', value='EOF')] 3 *4/ 5 [Token(type='Integer', value='3'), Token(type='operator', value='*'), Token(type='Integer', value='4'), Token(type='operator', value='/'), Token(type='Integer', value='5'), Token(type='EOF', value='EOF')]
至此,咱們將代碼分紅了一個一個的Token.
上面咱們將要執行的代碼分紅了一個一個的Token,這一節要將這一個個的Token組成一顆語法樹。如下面代碼爲例。
1 + 2 - 3 * 4
代碼生成的語法樹是這樣的。
爲何要用一棵樹來表示呢?由於樹這樣的結構能夠將優先級的問題解決.
咱們只要自下而上的依次執行,那麼得到結果就是正確的優先級執行的結果。根據圖中的樹咱們能夠這樣計算,先計算[Token(type='Integer', value='1'), Token(type='Integer', value='2'), 這兩個Token的計算結果分別是1和2,而後將其與父節點,即Token(type='operator', value='+')結合,那麼結果是下圖
而後同理計算右邊,獲得結果以下
最後計算3 - 12,獲得結果以下。
import operator class Node(object): """表示語法樹中的一個節點""" def eval(self): """子類應該實現的方法, 計算自身節點的方式""" # 不想寫這句話用abc模塊 raise "須要子類實現" def repr(self): """子類應該實現的方法,用於數據展現""" raise "須要子類實現" def __str__(self): return self.repr() def __repr__(self): return self.repr() class Interger(Node): """表明一個整數節點""" def __init__(self, token): self.token = token def eval(self): return int(self.token.value) def repr(self): return self.token.value class OperatorExpression(Node): """表明一個算數表達式, 好比1+2""" operator_map = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv } def __init__(self, token, left, right): self.token = token self.op = self.operator_map[self.token.value] self.left = left self.right = right def eval(self): # 注意這裏的left, right也能夠是一個OperatorExpression,因此會遞歸調用 return self.op(self.left.eval(), self.right.eval()) def repr(self): # 注意這裏的left, right也能夠是一個OperatorExpression,因此會遞歸調用 return "(" + self.left.repr() + self.token.value + self.right.repr() + ")" class Parser(object): # 定義每一個操做符的優先級,默認+-小於*/ operator_precedence_map = { "EOF": 0, "+": 1, "-": 1, "*": 2, "/": 2, } def __init__(self, precedences=None): if precedences: self.operator_precedence_map = precedences def parse_infix_expression(self, token, left): """ 解析中序表達式 中序表達式是指操做符在兩個對象之間, 好比+-*/, 有中序天然還有前序及後續,可是這裏不涉及 """ precedence = self.operator_precedence_map[token.value] # 這裏會遞歸調用parse_expression,可是傳入的precedence最2,因此不會進入while循環 right = self.parse_expression(precedence) expr = OperatorExpression(token, left, right) return expr def parse_integer(self, token): return Interger(token) def parse_expression(self, precedence=0): current_token = self.next_token self.next_token = self.next() left_expr = self.parse_integer(current_token) # 默認的precedence是0,因此當下一個token是+-*/的時候都會進入while循環,將表達式進行左結合,不斷的遞歸 # 而最後到EOF的時候,EOF的優先級是0, 因此致使while循環終止,返回最終的表達式 while precedence < self.operator_precedence_map[self.next_token.value]: current_token = self.next_token self.next_token = self.next() left_expr = self.parse_infix_expression(current_token, left_expr) return left_expr def next(self): return next(self.iter_tokens) def parse(self, tokens): self.tokens = tokens self.iter_tokens = iter(tokens) self.next_token = self.next() return self.parse_expression() def eval(self, expression): return expression.eval() if __name__ == "__main__": from xlexer import Lexer text = "1 + 2 - 3 * 4 / 5" mylexer = Lexer() myparser = Parser() tokens = mylexer.parse(text) expr = myparser.parse(tokens) print(expr) print(myparser.eval(expr))
輸出以下:
((1+2)-((3*4)/5)) 0.6000000000000001
如今讓咱們回到文章開始的問題,若是+-的優先級大於*/怎麼讓其實現,
咱們只須要傳入一個咱們自定義的優先級字典。代碼以下
custom_precedences = { "+": 2, "-": 2, "*": 1, "/": 1, } if __name__ == "__main__": from xlexer import Lexer from xparser import Parser text = "1 + 2 - 3 * 4 / 5" mylexer = Lexer() myparser = Parser(custom_precedences) tokens = mylexer.parse(text) expr = myparser.parse(tokens) print(expr) print(myparser.eval(expr))
輸出結果以下
((((1+2)-3)*4)/5) 0.0
"1 + 2 - 3 * 4 / 5"正確答案的是0.6, 可是將優先級調換後,結果變成了0,符合預期。
主要的用處集中在特定語法組成的代碼分析及轉換,語法多是特定編程語言的語法,也多是某個工具的DSL(領域特定語言).我暫時能想到的就下面兩個,用到的只有第二個,第三個。
表達式的解析應該是最難的部分了。
後面可能會完成這個系列吧,定義一套完整的語法,而後完成該語法的詞法分析,語法分析,語法執行。
一套完整的語法應該包括賦值語句,控制結構,如if,while,函數定義及調用,若是須要面向對象則還須要類。
其實除了解釋執行還能夠將代碼經過繼承編譯工具編譯成二進制執行文件,這樣子就像一個編譯語言了,不過這是後話了
其實經過學習編譯原理,能夠增長一種看待編程語法的角度,在各個編程語言之間遊刃有餘,應該會更快的學會一門以前沒接觸過的編程語言吧。
https://github.com/youerning/blog/tree/master/new_program
若是期待後續文章能夠關注個人微信公衆號(又耳筆記),頭條號(又耳筆記),github。