电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術(shù)文章
文章詳情頁

Python實(shí)現(xiàn)一個(gè)簡單的遞歸下降分析器

瀏覽:9日期:2022-07-15 15:10:50

問題

你想根據(jù)一組語法規(guī)則解析文本并執(zhí)行命令,或者構(gòu)造一個(gè)代表輸入的抽象語法樹。 如果語法非常簡單,你可以不去使用一些框架,而是自己寫這個(gè)解析器。

解決方案

在這個(gè)問題中,我們集中討論根據(jù)特殊語法去解析文本的問題。 為了這樣做,你首先要以BNF或者EBNF形式指定一個(gè)標(biāo)準(zhǔn)語法。 比如,一個(gè)簡單數(shù)學(xué)表達(dá)式語法可能像下面這樣:

expr ::= expr + term | expr - term | term

term ::= term * factor | term / factor | factor

factor ::= ( expr ) | NUM

或者,以EBNF形式:

expr ::= term { (+|-) term }*

term ::= factor { (*|/) factor }*

factor ::= ( expr ) | NUM

在EBNF中,被包含在 {...}* 中的規(guī)則是可選的。*代表0次或多次重復(fù)(跟正則表達(dá)式中意義是一樣的)。

現(xiàn)在,如果你對BNF的工作機(jī)制還不是很明白的話,就把它當(dāng)做是一組左右符號(hào)可相互替換的規(guī)則。 一般來講,解析的原理就是你利用BNF完成多個(gè)替換和擴(kuò)展以匹配輸入文本和語法規(guī)則。 為了演示,假設(shè)你正在解析形如 3 + 4 * 5 的表達(dá)式。 這個(gè)表達(dá)式先要通過使用2.18節(jié)中介紹的技術(shù)分解為一組令牌流。 結(jié)果可能是像下列這樣的令牌序列:

NUM + NUM * NUM

在此基礎(chǔ)上, 解析動(dòng)作會(huì)試著去通過替換操作匹配語法到輸入令牌:

exprexpr ::= term { (+|-) term }*expr ::= factor { (*|/) factor }* { (+|-) term }*expr ::= NUM { (*|/) factor }* { (+|-) term }*expr ::= NUM { (+|-) term }*expr ::= NUM + term { (+|-) term }*expr ::= NUM + factor { (*|/) factor }* { (+|-) term }*expr ::= NUM + NUM { (*|/) factor}* { (+|-) term }*expr ::= NUM + NUM * factor { (*|/) factor }* { (+|-) term }*expr ::= NUM + NUM * NUM { (*|/) factor }* { (+|-) term }*expr ::= NUM + NUM * NUM { (+|-) term }*expr ::= NUM + NUM * NUM

下面所有的解析步驟可能需要花點(diǎn)時(shí)間弄明白,但是它們原理都是查找輸入并試著去匹配語法規(guī)則。 第一個(gè)輸入令牌是NUM,因此替換首先會(huì)匹配那個(gè)部分。 一旦匹配成功,就會(huì)進(jìn)入下一個(gè)令牌+,以此類推。 當(dāng)已經(jīng)確定不能匹配下一個(gè)令牌的時(shí)候,右邊的部分(比如 { (*/) factor }* )就會(huì)被清理掉。 在一個(gè)成功的解析中,整個(gè)右邊部分會(huì)完全展開來匹配輸入令牌流。

有了前面的知識(shí)背景,下面我們舉一個(gè)簡單示例來展示如何構(gòu)建一個(gè)遞歸下降表達(dá)式求值程序:

#!/usr/bin/env python# -*- encoding: utf-8 -*-'''Topic: 下降解析器Desc :'''import reimport collections# Token specificationNUM = r’(?P<NUM>d+)’PLUS = r’(?P<PLUS>+)’MINUS = r’(?P<MINUS>-)’TIMES = r’(?P<TIMES>*)’DIVIDE = r’(?P<DIVIDE>/)’LPAREN = r’(?P<LPAREN>()’RPAREN = r’(?P<RPAREN>))’WS = r’(?P<WS>s+)’master_pat = re.compile(’|’.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS]))# TokenizerToken = collections.namedtuple(’Token’, [’type’, ’value’])def generate_tokens(text): scanner = master_pat.scanner(text) for m in iter(scanner.match, None): tok = Token(m.lastgroup, m.group()) if tok.type != ’WS’: yield tok# Parserclass ExpressionEvaluator: ’’’ Implementation of a recursive descent parser. Each method implements a single grammar rule. Use the ._accept() method to test and accept the current lookahead token. Use the ._expect() method to exactly match and discard the next token on on the input (or raise a SyntaxError if it doesn’t match). ’’’ def parse(self, text): self.tokens = generate_tokens(text) self.tok = None # Last symbol consumed self.nexttok = None # Next symbol tokenized self._advance() # Load first lookahead token return self.expr() def _advance(self): ’Advance one token ahead’ self.tok, self.nexttok = self.nexttok, next(self.tokens, None) def _accept(self, toktype): ’Test and consume the next token if it matches toktype’ if self.nexttok and self.nexttok.type == toktype: self._advance() return True else: return False def _expect(self, toktype): ’Consume next token if it matches toktype or raise SyntaxError’ if not self._accept(toktype): raise SyntaxError(’Expected ’ + toktype) # Grammar rules follow def expr(self): 'expression ::= term { (’+’|’-’) term }*' exprval = self.term() while self._accept(’PLUS’) or self._accept(’MINUS’): op = self.tok.type right = self.term() if op == ’PLUS’:exprval += right elif op == ’MINUS’:exprval -= right return exprval def term(self): 'term ::= factor { (’*’|’/’) factor }*' termval = self.factor() while self._accept(’TIMES’) or self._accept(’DIVIDE’): op = self.tok.type right = self.factor() if op == ’TIMES’:termval *= right elif op == ’DIVIDE’:termval /= right return termval def factor(self): 'factor ::= NUM | ( expr )' if self._accept(’NUM’): return int(self.tok.value) elif self._accept(’LPAREN’): exprval = self.expr() self._expect(’RPAREN’) return exprval else: raise SyntaxError(’Expected NUMBER or LPAREN’)def descent_parser(): e = ExpressionEvaluator() print(e.parse(’2’)) print(e.parse(’2 + 3’)) print(e.parse(’2 + 3 * 4’)) print(e.parse(’2 + (3 + 4) * 5’)) # print(e.parse(’2 + (3 + * 4)’)) # Traceback (most recent call last): # File '<stdin>', line 1, in <module> # File 'exprparse.py', line 40, in parse # return self.expr() # File 'exprparse.py', line 67, in expr # right = self.term() # File 'exprparse.py', line 77, in term # termval = self.factor() # File 'exprparse.py', line 93, in factor # exprval = self.expr() # File 'exprparse.py', line 67, in expr # right = self.term() # File 'exprparse.py', line 77, in term # termval = self.factor() # File 'exprparse.py', line 97, in factor # raise SyntaxError('Expected NUMBER or LPAREN') # SyntaxError: Expected NUMBER or LPARENif __name__ == ’__main__’: descent_parser()

討論

文本解析是一個(gè)很大的主題, 一般會(huì)占用學(xué)生學(xué)習(xí)編譯課程時(shí)剛開始的三周時(shí)間。 如果你在找尋關(guān)于語法,解析算法等相關(guān)的背景知識(shí)的話,你應(yīng)該去看一下編譯器書籍。 很顯然,關(guān)于這方面的內(nèi)容太多,不可能在這里全部展開。

盡管如此,編寫一個(gè)遞歸下降解析器的整體思路是比較簡單的。 開始的時(shí)候,你先獲得所有的語法規(guī)則,然后將其轉(zhuǎn)換為一個(gè)函數(shù)或者方法。 因此如果你的語法類似這樣:

expr ::= term { (’+’|’-’) term }*term ::= factor { (’*’|’/’) factor }*factor ::= ’(’ expr ’)’ | NUM

你應(yīng)該首先將它們轉(zhuǎn)換成一組像下面這樣的方法:

class ExpressionEvaluator: ... def expr(self): ... def term(self): ... def factor(self): ...

每個(gè)方法要完成的任務(wù)很簡單 - 它必須從左至右遍歷語法規(guī)則的每一部分,處理每個(gè)令牌。 從某種意義上講,方法的目的就是要么處理完語法規(guī)則,要么產(chǎn)生一個(gè)語法錯(cuò)誤。 為了這樣做,需采用下面的這些實(shí)現(xiàn)方法:

如果規(guī)則中的下個(gè)符號(hào)是另外一個(gè)語法規(guī)則的名字(比如term或factor),就簡單的調(diào)用同名的方法即可。 這就是該算法中”下降”的由來 - 控制下降到另一個(gè)語法規(guī)則中去。 有時(shí)候規(guī)則會(huì)調(diào)用已經(jīng)執(zhí)行的方法(比如,在 factor ::= ’(’expr ’)’ 中對expr的調(diào)用)。 這就是算法中”遞歸”的由來。 如果規(guī)則中下一個(gè)符號(hào)是個(gè)特殊符號(hào)(比如(),你得查找下一個(gè)令牌并確認(rèn)是一個(gè)精確匹配)。 如果不匹配,就產(chǎn)生一個(gè)語法錯(cuò)誤。這一節(jié)中的 _expect() 方法就是用來做這一步的。 如果規(guī)則中下一個(gè)符號(hào)為一些可能的選擇項(xiàng)(比如 + 或 -), 你必須對每一種可能情況檢查下一個(gè)令牌,只有當(dāng)它匹配一個(gè)的時(shí)候才能繼續(xù)。 這也是本節(jié)示例中 _accept() 方法的目的。 它相當(dāng)于_expect()方法的弱化版本,因?yàn)槿绻粋€(gè)匹配找到了它會(huì)繼續(xù), 但是如果沒找到,它不會(huì)產(chǎn)生錯(cuò)誤而是回滾(允許后續(xù)的檢查繼續(xù)進(jìn)行)。 對于有重復(fù)部分的規(guī)則(比如在規(guī)則表達(dá)式 ::= term { (’+’|’-’) term }* 中), 重復(fù)動(dòng)作通過一個(gè)while循環(huán)來實(shí)現(xiàn)。 循環(huán)主體會(huì)收集或處理所有的重復(fù)元素直到?jīng)]有其他元素可以找到。 一旦整個(gè)語法規(guī)則處理完成,每個(gè)方法會(huì)返回某種結(jié)果給調(diào)用者。 這就是在解析過程中值是怎樣累加的原理。 比如,在表達(dá)式求值程序中,返回值代表表達(dá)式解析后的部分結(jié)果。 最后所有值會(huì)在最頂層的語法規(guī)則方法中合并起來。

盡管向你演示的是一個(gè)簡單的例子,遞歸下降解析器可以用來實(shí)現(xiàn)非常復(fù)雜的解析。 比如,Python語言本身就是通過一個(gè)遞歸下降解析器去解釋的。 如果你對此感興趣,你可以通過查看Python源碼文件Grammar/Grammar來研究下底層語法機(jī)制。 看完你會(huì)發(fā)現(xiàn),通過手動(dòng)方式去實(shí)現(xiàn)一個(gè)解析器其實(shí)會(huì)有很多的局限和不足之處。

其中一個(gè)局限就是它們不能被用于包含任何左遞歸的語法規(guī)則中。比如,假如你需要翻譯下面這樣一個(gè)規(guī)則:

items ::= items ’,’ item | item

為了這樣做,你可能會(huì)像下面這樣使用 items() 方法:

def items(self): itemsval = self.items() if itemsval and self._accept(’,’): itemsval.append(self.item()) else: itemsval = [ self.item() ]

唯一的問題是這個(gè)方法根本不能工作,事實(shí)上,它會(huì)產(chǎn)生一個(gè)無限遞歸錯(cuò)誤。

關(guān)于語法規(guī)則本身你可能也會(huì)碰到一些棘手的問題。 比如,你可能想知道下面這個(gè)簡單扼語法是否表述得當(dāng):

expr ::= factor { (’+’|’-’|’*’|’/’) factor }*factor ::= ’(’ expression ’)’ | NUM

這個(gè)語法看上去沒啥問題,但是它卻不能察覺到標(biāo)準(zhǔn)四則運(yùn)算中的運(yùn)算符優(yōu)先級。 比如,表達(dá)式 '3 + 4 * 5' 會(huì)得到35而不是期望的23. 分開使用”expr”和”term”規(guī)則可以讓它正確的工作。

對于復(fù)雜的語法,你最好是選擇某個(gè)解析工具比如PyParsing或者是PLY。 下面是使用PLY來重寫表達(dá)式求值程序的代碼:

from ply.lex import lexfrom ply.yacc import yacc# Token listtokens = [ ’NUM’, ’PLUS’, ’MINUS’, ’TIMES’, ’DIVIDE’, ’LPAREN’, ’RPAREN’ ]# Ignored characterst_ignore = ’ tn’# Token specifications (as regexs)t_PLUS = r’+’t_MINUS = r’-’t_TIMES = r’*’t_DIVIDE = r’/’t_LPAREN = r’(’t_RPAREN = r’)’# Token processing functionsdef t_NUM(t): r’d+’ t.value = int(t.value) return t# Error handlerdef t_error(t): print(’Bad character: {!r}’.format(t.value[0])) t.skip(1)# Build the lexerlexer = lex()# Grammar rules and handler functionsdef p_expr(p): ’’’ expr : expr PLUS term | expr MINUS term ’’’ if p[2] == ’+’: p[0] = p[1] + p[3] elif p[2] == ’-’: p[0] = p[1] - p[3]def p_expr_term(p): ’’’ expr : term ’’’ p[0] = p[1]def p_term(p): ’’’ term : term TIMES factor | term DIVIDE factor ’’’ if p[2] == ’*’: p[0] = p[1] * p[3] elif p[2] == ’/’: p[0] = p[1] / p[3]def p_term_factor(p): ’’’ term : factor ’’’ p[0] = p[1]def p_factor(p): ’’’ factor : NUM ’’’ p[0] = p[1]def p_factor_group(p): ’’’ factor : LPAREN expr RPAREN ’’’ p[0] = p[2]def p_error(p): print(’Syntax error’)parser = yacc()

這個(gè)程序中,所有代碼都位于一個(gè)比較高的層次。你只需要為令牌寫正則表達(dá)式和規(guī)則匹配時(shí)的高階處理函數(shù)即可。 而實(shí)際的運(yùn)行解析器,接受令牌等等底層動(dòng)作已經(jīng)被庫函數(shù)實(shí)現(xiàn)了。

下面是一個(gè)怎樣使用得到的解析對象的例子:

>>> parser.parse(’2’)2>>> parser.parse(’2+3’)5>>> parser.parse(’2+(3+4)*5’)37>>>

如果你想在你的編程過程中來點(diǎn)挑戰(zhàn)和刺激,編寫解析器和編譯器是個(gè)不錯(cuò)的選擇。 再次,一本編譯器的書籍會(huì)包含很多底層的理論知識(shí)。不過很多好的資源也可以在網(wǎng)上找到。 Python自己的ast模塊也值得去看一下。

以上就是Python實(shí)現(xiàn)一個(gè)簡單的遞歸下降分析器的詳細(xì)內(nèi)容,更多關(guān)于Python實(shí)現(xiàn)遞歸下降分析器的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Python 編程
相關(guān)文章:
主站蜘蛛池模板: 压接机|高精度压接机|手动压接机|昆明可耐特科技有限公司[官网] 胶泥瓷砖胶,轻质粉刷石膏,嵌缝石膏厂家,腻子粉批发,永康家德兴,永康市家德兴建材厂 | 安徽成考网-安徽成人高考网| 深圳3D打印服务-3D打印加工-手板模型加工厂-悟空打印坊 | 磷酸肌酸二钠盐,肌酐磷酰氯-沾化欣瑞康生物科技 | 安规电容|薄膜电容|陶瓷电容|智旭JEC安规电容厂家 | 干粉砂浆设备_干混砂浆生产线_腻子粉加工设备_石膏抹灰砂浆生产成套设备厂家_干粉混合设备_砂子烘干机--郑州铭将机械设备有限公司 | HV全空气系统_杭州暖通公司—杭州斯培尔冷暖设备有限公司 | 工业插头-工业插头插座【厂家】-温州罗曼电气 | 金属切削液-脱水防锈油-电火花机油-抗磨液压油-深圳市雨辰宏业科技发展有限公司 | 聚合氯化铝价格_聚合氯化铝厂家_pac絮凝剂-唐达净水官网 | 正压密封性测试仪-静态发色仪-导丝头柔软性测试仪-济南恒品机电技术有限公司 | 对辊式破碎机-对辊制砂机-双辊-双齿辊破碎机-巩义市裕顺机械制造有限公司 | 飞歌臭氧发生器厂家_水处理臭氧发生器_十大臭氧消毒机品牌 | 真空泵维修保养,普发,阿尔卡特,荏原,卡西亚玛,莱宝,爱德华干式螺杆真空泵维修-东莞比其尔真空机电设备有限公司 | 食品机械专用传感器-落料放大器-低价接近开关-菲德自控技术(天津)有限公司 | LED灯杆屏_LED广告机_户外LED广告机_智慧灯杆_智慧路灯-太龙智显科技(深圳)有限公司 | 上海办公室装修公司_办公室设计_直营办公装修-羚志悦装 | 长城人品牌官网| 耐驰泵阀管件制造-耐驰泵阀科技(天津)有限公司 | 活性炭厂家-蜂窝活性炭-粉状/柱状/果壳/椰壳活性炭-大千净化-活性炭 | POM塑料_PBT材料「进口」聚甲醛POM杜邦原料、加纤PBT塑料报价格找利隆塑料 | 氧氮氢联合测定仪-联测仪-氧氮氢元素分析仪-江苏品彦光电 | 高压直流电源_特种变压器_变压器铁芯-希恩变压器定制厂家 | 3d可视化建模_三维展示_产品3d互动数字营销_三维动画制作_3D虚拟商城 【商迪3D】三维展示服务商 广东健伦体育发展有限公司-体育工程配套及销售运动器材的体育用品服务商 | 金库门,金库房,金库门厂家,金库门价格-河北特旺柜业有限公司 | 杭州厂房降温,车间降温设备,车间通风降温,厂房降温方案,杭州嘉友实业爽风品牌 | 英超直播_英超免费在线高清直播_英超视频在线观看无插件-24直播网 | 安徽净化板_合肥岩棉板厂家_玻镁板厂家_安徽科艺美洁净科技有限公司 | 太平洋亲子网_健康育儿 品质生活| 润滑油加盟_润滑油厂家_润滑油品牌-深圳市沃丹润滑科技有限公司 琉璃瓦-琉璃瓦厂家-安徽盛阳新型建材科技有限公司 | 沈阳缠绕包装机厂家直销-沈阳海鹞托盘缠绕包装机价格 | 彩超机-黑白B超机-便携兽用B超机-多普勒彩超机价格「大为彩超」厂家 | 带式过滤机厂家_价格_型号规格参数-江西核威环保科技有限公司 | 阜阳成人高考_阜阳成考报名时间_安徽省成人高考网 | 房车价格_依维柯/大通/东风御风/福特全顺/江铃图片_云梯搬家车厂家-程力专用汽车股份有限公司 | 大立教育官网-一级建造师培训-二级建造师培训-造价工程师-安全工程师-监理工程师考试培训 | 步进驱动器「一体化」步进电机品牌厂家-一体式步进驱动 | 比士亚-专业恒温恒湿酒窖,酒柜,雪茄柜的设计定制 | 浙江栓钉_焊钉_剪力钉厂家批发_杭州八建五金制造有限公司 | 成都热收缩包装机_袖口式膜包机_高速塑封机价格_全自动封切机器_大型套膜机厂家 | 钢格板_钢格栅_格栅板_钢格栅板 - 安平县鑫拓钢格栅板厂家 |