当前位置: 首页 > 科技观察

用Python开发Python解释器

时间:2023-03-23 11:07:57 科技观察

计算机只能理解机器码。归根结底,编程语言只是一串单词,旨在让人类更容易地编写他们想让计算机做的事情。真正的魔法是由编译器和解释器完成的,它们弥合了两者之间的鸿沟。解释器逐行读取代码并将其转换为机器代码。在本文中,我们将设计一个可以执行算术运算的解释器。我们不会重新发明轮子。本文将使用由DavidM.Beazley开发的词法解析器-PLY(PythonLex-Yacc(https://github.com/dabeaz/ply))。PLY可以通过以下方式下载:$pipinstallply我们将粗略地看一下创建解释器所需的基础知识。有关更多信息,请参阅此GitHub存储库(https://github.com/dabeaz/ply)。令牌是为解释器提供有意义信息的最小字符单元。标签由一对名称和属性值组成。让我们从创建标签名称列表开始。这是必要的步骤。tokens=(#datatype"NUM","FLOAT",#arithmeticoperation"PLUS","MINUS","MUL","DIV",#brackets"LPAREN","RPAREN",)词法分析器(Lexer)将语句转换为标记的过程称为标记化或词法分析。执行词法分析的程序是词法分析器。#标记正则表达式t_PLUS=r"\+"t_MINUS=r"\-"t_MUL=r"\*"t_DIV=r"/"t_LPAREN=r"\("t_RPAREN=r"\)"t_POW=r"\^"#忽略空格和制表符t_ignore="\t"#为每个规则添加操作deft_FLOAT(t):r"""\d+\.\d+"""t.value=float(t.value)returntdeft_NUM(t):r"""\d+"""t.value=int(t.value)returnt#未定义规则字符的错误处理deft_error(t):#这里的t.value包含未标记的其余输入print(f"keywordnotfound:{t.value[0]}\nline{t.lineno}")t.lexer.skip(1)#如果遇到\n,则设置为换行deft_newline(t):r"""\n+"""t.lexer.lineno+=t.value.count("\n")是导入词法分析器,我们会用到:importply.lex因为lext_是一个特殊的前缀,代表定义token的规则。每个词法规则都是使用正则表达式制作的,与Python中的re模块兼容。正则表达式可以根据规则扫描输入,搜索匹配的符号串。由正则表达式定义的文法称为正则文法。由正则文法定义的语言称为正则语言。定义好规则后,我们将构建词法分析器。data='a=2+(10-8)/1.0'lexlexer=lex.lex()lexer.input(data)whileetok:=lexer.token():print(tok)要传递输入字符串,我们使用lexer。输入数据)。lexer.token()将返回下一个LexToken实例,最后返回None。根据上述规则,代码2+(10-8)/1.0的标记为:紫色字符代表标记名称,后面是标记的具体内容。Backus-NaurForm(BNF)大多数编程语言都可以使用上下文无关文法来编写。它比常规语言更复杂。对于上下文无关文法,我们使用上下文无关文法,它是描述语言中所有可能文法的规则集。BNF是一种定义语法的方法,它描述了编程语言的句法。让我们看一个例子:symbol:alternative1|alternative2...根据产生式,将:的左侧替换为右侧的值之一。右边的值用|分隔(理解为定义为alternative1或alternative2或...等的符号)。对于我们的算术解释器,语法规范如下:表达式:表达式'+'表达式|表达式'-'表达式|表达式'/'表达式|表达式'*'表达式|表达式'^'表达式|+表达式|-表达式|(expression)|NUM|FLOAT输入标记是NUM、FLOAT、+、-、*、/等符号,称为终结符(不能进一步分解或产生其他符号的字符)。一个表达式由一个终结符和一个规则集组成,这样的表达式称为非终结符。解析器(Parser)我们将使用YACC(YetAnotherCompiler编译器)作为解析器生成器。导入模块:将ply.yacc导入为yacc。fromoperatorimport(add,sub,mul,truediv,pow)#我们的解释器支持的运算符列表ops={"+":add,"-":sub,"*":mul,"/":truediv,"^":pow,}defp_expression(p):"""expression:expressionPLUSexpression|expressionMINUSexpression|expressionDIVexpression|expressionMULexpression|expressionPOWexpression"""if(p[2],p[3])==("/",0):#If除以0,取"INF"(无穷大)为值p[0]=float("INF")else:p[0]=ops[p[2]](p[1],p[3])defp_expression_uplus_or_expr(p):"""expression:PLUSexpression%precUPLUS|LPARENexpressionRPAREN"""p[0]=p[2]defp_expression_uminus(p):"""expression:MINUSexpression%precUMINUS"""p[0]=-p[2]defp_expression_num(p):"""expression:NUM|FLOAT"""p[0]=p[1]#语法错误时的规则defp_error(p):print(f"Syntaxerrorin{p.value}")在文档字符串中,我们将添加适当的语法规范。p列表中的元素与语法符号一一对应,如下:expression:expressionPLUSexpressionp[0]p[1]p[2]p[3]上面使用%precUPLUS和%precUMINUS表示自定义计算。%prec是优先级的缩写。symbol中没有UPLUS和UMINUS(本文中这两个自定义操作分别代表一元正号和符号,但UPLUS和UMINUS只是名字,大家可以随便取)。之后,我们可以添加基于表达式的规则。YACC允许为每个令牌分配优先级。我们可以使用:precedence=(("left","PLUS","MINUS"),("left","MUL","DIV"),("left","POW"),("right","UPLUS","UMINUS"))在优先级声明中,标记按优先级从低到高的顺序列出。PLUS和MINUS具有相同的优先级并且是左关联的(操作从左到右执行)。MUL和DIV的优先级高于PLUS和MINUS,并且也是左结合的。POW也是一样,但是优先级更高。UPLUS和UMINUS是右结合的(运算从右到左执行)。解析输入我们将使用:parser=yacc.yacc()result=parser.parse(data)print(result)完整代码如下:#######################################导入模块#######################################fromloggingimport(basicConfig,INFO,getLogger)fromoperatorimport(add,sub,mul,truediv,pow)importply.lexasleximportply.yaccasyacc#我们的解释器支持的操作符列表ops={"+":添加,"-":sub,"*":mul,"/":truediv,"^":pow,}#########################################标签集#######################################tokens=(#数据类型"NUM","FLOAT",#算术运算"PLUS","MINUS","MUL","DIV","POW",#brackets"LPAREN","RPAREN",)#########################################标记的正则表达式########################################t_PLUS=r"\+"t_MINUS=r"\-"t_MUL=r"\*"t_DIV=r"/"t_LPAREN=r"\("t_RPAREN=r"\)"t_POW=r"\^"#忽略空格和制表符t_ignore="\t"#为每个规则添加动作deft_FLOAT(t):r"""\d+\.\d+"""t.value=float(t.value)returndeft_NUM(t):r"""\d+"""t.value=int(t.value)return#undefinedrule错误处理字符deft_error(t):#t.valueherecontainstherestoftheunmarkedinputprint(f"keywordnotfound:{t.value[0]}\nline{t.lineno}")t.lexer.skip(1)#如果看到\n,将其设置为新行deft_newline(t):r"""\n+"""t.lexer.lineno+=t.value.count("\n")#######################################设置符号优先级#########################################优先级=((“左”,“加”,“减”),(“左”,“MUL","DIV"),("left","POW"),("right","UPLUS","UMINUS"))#######################################编写BNF规则########################################defp_expression(p):"""expression:expressionPLUSexpression|expressionMINUSexpression|expressionDIVexpression|expressionMULexpression|expressionPOWexpression"""if(p[2],p[3])==("/",0):#如果除以0,取"INF"(无穷大)为值p[0]=float("INF")else:p[0]=ops[p[2]](p[1],p[3])defp_expression_uplus_or_expr(p):"""expression:PLUSexpression%precUPLUS|LPARENexpressionRPAREN"""p[0]=p[2]defp_expression_uminus(p):"""expression:MINUSexpression%precUMINUS"""p[0]=-p[2]defp_expression_num(p):"""expression:NUM|FLOAT"""p[0]=p[1]#语法错误时的规则defp_error(p):打印(f"Syntaxerrorin{p.value}")########################################主程序########################################if__name__==”__main__":basicConfig(level=INFO,filename="logs.txt")lexlexer=lex.lex()parser=yacc.yacc()whileTrue:try:result=parser.parse(input(">>>"),debug=getLogger())print(result)exceptAttributeError:print("invalidsyntax")结语由于篇幅较大,本文无法将事情说清楚,但希望你能很好地理解文中所涵盖的粗浅知识

最新推荐
猜你喜欢