Python递归下降Parser怎么实现
1. 算术运算表达式求值
要解析这类文本,需要另外一种特定的语法规则。我们这里介绍可以表示上下文无关文法(context free grammer)的语法规则巴科斯范式(BNF)和扩展巴科斯范式(EBNF)。从小到一个算术运算表达式,到大到几乎所有程序设计语言,都是利用上下文无关文法来定义的。
对于简单的算术运算表达式,假定我们已经用分词技术将其转化为输入的tokens流,如NUM+NUM*NUM
(分词方法参见上一篇博文)。
在此基础上,我们定义BNF规则定义如下:
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
BNF和EBNF每一条规则(形如::=的式子)都可以看做是一种替换,即左侧的符号可以被右侧的符号所替换。我们在解析过程中尝试使用BNF/EBNF将输入文本与语法规则进行匹配,以完成各种替换和扩展。在EBNF中,被放置在{...}*内的规则是可选的,而*则表示可以重复零次或多次(类比于正则表达式)。
下图形象地展示了递归下降解析器(parser)中“递归”和“下降”部分和ENBF的关系:
在实际的解析过程中,我们对tokens流从左到右进行扫描,在扫描的过程中处理token,如果卡住就产生一个语法错误。每一条语法规则都被转化为一个函数或方法,例如上面的ENBF规则被转换成下述方法:
class ExpressionEvaluator(): ... def expr(self): ... def term(self): ... def factor(self): ...
在调用某个规则对应方法的过程中,如果我们发现接下来的符号需要采用另一个规则来匹配,则我们就会“下降”到另一个规则方法(如在expr中调用term,term中调用factor),则也就是递归下降中“下降”的部分。
有时也会调用已经在执行的方法(比如在expr中调用term,term中调用factor后,又在factor中调用expr,相当于一条衔尾蛇),这也就是递归下降中“递归”的部分。
对于语法中出现的重复部分(例如expr ::= term { (+|-) term }*
),我们则通过while循环来实现。
下面我们来看具体的代码实现。首先是分词部分,我们参照上一篇介绍分词博客的代码。
import re import collections # 定义匹配token的模式 NUM = r'(?P<NUM>\d+)' # \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+)' # 别忘记空格,\s表示空格,+表示任意长度 master_pat = re.compile( '|'.join([NUM, PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN, WS])) # Tokenizer Token = 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
下面是表达式求值器的具体实现:
class ExpressionEvaluator(): """ 递归下降的Parser实现,每个语法规则都对应一个方法, 使用 ._accept()方法来测试并接受当前处理的token,不匹配不报错, 使用 ._except()方法来测试当前处理的token,并在不匹配的时候抛出语法错误 """ def parse(self, text): """ 对外调用的接口 """ self.tokens = generate_tokens(text) self.tok, self.next_tok = None, None # 已匹配的最后一个token,下一个即将匹配的token self._next() # 转到下一个token return self.expr() # 开始递归 def _next(self): """ 转到下一个token """ self.tok, self.next_tok = self.next_tok, next(self.tokens, None) def _accept(self, tok_type): """ 如果下一个token与tok_type匹配,则转到下一个token """ if self.next_tok and self.next_tok.type == tok_type: self._next() return True else: return False def _except(self, tok_type): """ 检查是否匹配,如果不匹配则抛出异常 """ if not self._accept(tok_type): raise SyntaxError("Excepted"+tok_type) # 接下来是语法规则,每个语法规则对应一个方法 def expr(self): """ 对应规则: expression ::= term { ('+'|'-') term }* """ exprval = self.term() # 取第一项 while self._accept("PLUS") or self._accept("DIVIDE"): # 如果下一项是"+"或"-" 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._except("RPAREN") # 别忘记检查是否有右括号,没有则抛出异常 return exprval else: raise SyntaxError("Expected NUMBER or LPAREN")
我们输入以下表达式进行测试:
e = ExpressionEvaluator() print(e.parse("2")) print(e.parse("2+3")) print(e.parse("2+3*4")) print(e.parse("2+(3+4)*5"))
求值结果如下:
2
5
14
37
如果我们输入的文本不符合语法规则:
print(e.parse("2 + (3 + * 4)"))
则会抛出SyntaxError异常:Expected NUMBER or LPAREN
。
综上,可见我们的表达式求值算法运行正确。
2. 生成表达式树
上面我们是得到表达式的结果,但是如果我们想分析表达式的结构,生成一棵简单的表达式解析树呢?那么我们需要对上述类的方法做一定修改:
class ExpressionTreeBuilder(ExpressionEvaluator): def expr(self): """ 对应规则: expression ::= term { ('+'|'-') term }* """ exprval = self.term() # 取第一项 while self._accept("PLUS") or self._accept("DIVIDE"): # 如果下一项是"+"或"-" op = self.tok.type # 再取下一项,即运算符右值 right = self.term() if op == "PLUS": exprval = ('+', exprval, right) elif op == "MINUS": exprval -= ('-', 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 = ('*', termval, right) elif op == "DIVIDE": termval = ('/', 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._except("RPAREN") # 别忘记检查是否有右括号,没有则抛出异常 return exprval else: raise SyntaxError("Expected NUMBER or LPAREN")
输入下列表达式测试一下:
print(e.parse("2+3")) print(e.parse("2+3*4")) print(e.parse("2+(3+4)*5")) print(e.parse('2+3+4'))
以下是生成结果:
('+', 2, 3)
('+', 2, ('*', 3, 4))
('+', 2, ('*', ('+', 3, 4), 5))
('+', ('+', 2, 3), 4)
可以看到表达式树生成正确。
我们上面的这个例子非常简单,但递归下降的解析器也可以用来实现相当复杂的解析器,例如Python代码就是通过一个递归下降解析器解析的。您要是对此跟感兴趣可以检查Python源码中的Grammar
文件来一探究竟。然而,下面我们接着会看到,自己动手写一个解析器会面对各种陷阱和挑战。
左递归和运算符优先级陷阱
任何涉及左递归形式的语法规则,都没法用递归下降parser来解决。所谓左递归,即规则式子右侧最左边的符号是规则头,比如对于以下规则:
items ::= items ',' item | item
完成该解析你可能会定义以下方法:
def items(self): itemsval = self.items() # 取第一项,然而此处会无穷递归! if itemsval and self._accept(','): itemsval.append(self.item()) else: itemsval = [self.item()]
这样做会在第一行就无穷地调用self.items()
从而产生无穷递归错误。
还有一种是语法规则自身的错误,比如运算符优先级。我们如果忽视运算符优先级直接将表达式简化如下:
expr ::= factor { ('+'|'-'|'*'|'/') factor }* factor ::= '(' expr ')' | NUM
PYTHON 复制 全屏
这个语法从技术上可以实现,但是没有遵守计算顺序约定,导致"3+4*5"的运算结果为35,而不是预期的23。因此,需要使用单独的expr和term规则来确保计算结果的正确性。
以上是Python递归下降Parser怎么实现的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undress AI Tool
免费脱衣服图片

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

要实现PHP结合AI进行文本纠错与语法优化,需按以下步骤操作:1.选择适合的AI模型或API,如百度、腾讯API或开源NLP库;2.通过PHP的curl或Guzzle调用API并处理返回结果;3.在应用中展示纠错信息并允许用户选择是否采纳;4.使用php-l和PHP_CodeSniffer进行语法检测与代码优化;5.持续收集反馈并更新模型或规则以提升效果。选择AIAPI时应重点评估准确率、响应速度、价格及对PHP的支持。代码优化应遵循PSR规范、合理使用缓存、避免循环查询、定期审查代码,并借助X

用户语音输入通过前端JavaScript的MediaRecorderAPI捕获并发送至PHP后端;2.PHP将音频保存为临时文件后调用STTAPI(如Google或百度语音识别)转换为文本;3.PHP将文本发送至AI服务(如OpenAIGPT)获取智能回复;4.PHP再调用TTSAPI(如百度或Google语音合成)将回复转为语音文件;5.PHP将语音文件流式返回前端播放,完成交互。整个流程由PHP主导数据流转与错误处理,确保各环节无缝衔接。

本文为您精选了多个顶级的Python“成品”项目网站与高水平“大片”级学习资源入口。无论您是想寻找开发灵感、观摩学习大师级的源代码,还是系统性地提升实战能力,这些平台都是不容错过的宝库,能帮助您快速成长为Python高手。

要入门量子机器学习(QML),首选工具是Python,需安装PennyLane、Qiskit、TensorFlowQuantum或PyTorchQuantum等库;接着通过运行示例熟悉流程,如使用PennyLane构建量子神经网络;然后按照数据集准备、数据编码、构建参数化量子线路、经典优化器训练等步骤实现模型;实战中应避免一开始就追求复杂模型,关注硬件限制,采用混合模型结构,并持续参考最新文献和官方文档以跟进发展。

收集用户行为数据需通过PHP记录浏览、搜索、购买等信息至数据库,并清洗分析以挖掘兴趣偏好;2.推荐算法选择应根据数据特征决定:基于内容、协同过滤、规则或混合推荐;3.协同过滤在PHP中可实现为计算用户余弦相似度、选K近邻、加权预测评分并推荐高分商品;4.性能评估用准确率、召回率、F1值及CTR、转化率并通过A/B测试验证效果;5.冷启动问题可通过商品属性、用户注册信息、热门推荐和专家评价缓解;6.性能优化手段包括缓存推荐结果、异步处理、分布式计算与SQL查询优化,从而提升推荐效率与用户体验。

在Python中,使用join()方法合并字符串需注意以下要点:1.使用str.join()方法,调用时前面的字符串作为连接符,括号里的可迭代对象包含要连接的字符串;2.确保列表中的元素都是字符串,若含非字符串类型需先转换;3.处理嵌套列表时需先展平结构再连接。

掌握Python网络爬虫需抓住三个核心步骤:1.使用requests发起请求,通过get方法获取网页内容,注意设置headers、处理异常及遵守robots.txt;2.利用BeautifulSoup或XPath提取数据,前者适合简单解析,后者更灵活适用于复杂结构;3.针对动态加载内容使用Selenium模拟浏览器操作,虽速度较慢但能应对复杂页面,也可尝试寻找网站API接口提高效率。

选择合适的PHP框架需根据项目需求综合考虑:Laravel适合快速开发,提供EloquentORM和Blade模板引擎,便于数据库操作和动态表单渲染;Symfony更灵活,适合复杂系统;CodeIgniter轻量,适用于对性能要求较高的简单应用。2.确保AI模型准确性需从高质量数据训练、合理选择评估指标(如准确率、召回率、F1值)、定期性能评估与模型调优入手,并通过单元测试和集成测试保障代码质量,同时持续监控输入数据以防止数据漂移。3.保护用户隐私需采取多项措施:对敏感数据进行加密存储(如AES
