编译原理动手实操,用java实现编译器-算术表达式及其语法解析器的实现

76 篇文章 10 订阅
55 篇文章 6 订阅

大家在参考本节时,请先阅读以下博文,进行预热:

http://blog.csdn.net/tyler_download/article/details/50708807

 

本节代码下载地址:

http://pan.baidu.com/s/1sjWiwPn

代码的理解和运行是吃透编译原理的关键,如果我们看的是爱情动作片,自然选择无码的好,但如果看得是计算机课程,则必须有码,无码的计算机理论都是耍流氓。


当前,java所实现的简易编译器目的是将一条或一组含有加号和乘号的算术表达式编译成类似汇编语言的伪码,因此必须给算术表达式设立一组语法规则,那么程序才能对输入的表达式进行分析。我们把一组带有分号的算术表达式称为statements, 例如:

1+2*3+4;

2+3*4+5;

3+4*5+6;

这三个表达式的集合称为statements.同时将一组表达式中的某一条带有分号的表达式称为expression, 这样statements 就是由一个或多个expression组成的:


因此statements的语法规则可以写成:

statements -> expression; | expression; statements

 

大家看到该语法定义跟我在上一篇文章中举的例子有所不同:

1.    在-> 右边有两组解析规则,他们用符号 | 分割开。

2.    -> 左边的被解析的对象居然在右边的解析规则中出现,形成了一种循环,也就是用自己来解释自己,这种情况在编译原理中称为左循环LR (Left recursive).


这里,大家可能会发现语法定义的一些问题:

1.    右边有两组解析规则,用右边替换左边时,到底选取哪一组?

2.    左边的符号(statements) 出现在右边的规则中,替换的话就会出现死循环:

statements(buffer) {

expression(buffer);

 

statements(buffer); //此处将导致循环调用

}

这些问题,在后面我们再加以解决,暂且先继续给出余下的语法规则:

 

Expression ->expression + term | term;

term -> term* factor | factor

factor ->NUMBER | (expression)


看到这,大家会不会有点恼火,为什么这组语法规则能够用来解析一组算术表达式? 你是根据什么办法给出这组规则的?我以前在读编译原理的相关书籍时也会有这些郁闷和困惑,都不知道作者是怎么想到的,书中解释有含糊不清,直到现在我才明白,在学习的早期,有些地方你必须先囫囵吞枣,带着疑惑看到后面,你自然就会明白,所以大家在此先无需理解我是怎么给出这组语法定义的,先记着,然后把代码跑一边,看看结果,有个感性认识,在后续文章中,我会慢慢解释,如何根据要编译的文本,给出相应的语法规则。


现在我们来解决前面提到的两个问题, ->右边有两组替换规则,在语法解析的时候,如何决定选取哪一组?在编译原理的实现技巧中,有一种方法叫look ahead, 举个例子,对规则:

statements ->expression; | expression; statements

替换时用“| “ 左边的 expression; 还是右边的expression; statements呢,办法是当我们在程序中,读到第一个分号”;” 时,再继续读入下一个符号,如果继续读入符号时,返回的是输入的结束标志(EOI) 那么我们就使用“|” 左边的规则来替换,如果继续读入的符号不是结束标志,那意味着分号后边还有需要解析的信息,那就使用“|” 右边的替换规则,这种技巧在语法解析中就叫look ahead.


如何解决语法规则中出现循环调用呢?我们需要对语法规则做一些更改,更改原理在以后的文章中再做进一步的解释,请大家再囫囵吞枣一次,我知道吃东西不消化会对胃不好,黄天在上,这里是最后一次这样,请大家原谅,修改后的语法规则如下:

1. statements -> ‘空‘ | expression; statements

2. expression-> term expression'

3. expression'-> +term expression' | ‘空‘

4. term -> factor  term'

5. term' -> * factor  term' | ‘空‘

6. factor -> number | (expression)

这组修改后的语法规则比修改前更加难以理解,但能确保,这组规则不会出现修改前那样导致解析死循环。语法规则中的’空’ 表示结束,什么都不做。例如如果我们输入一个空字符串””给语法解析器,那么规则1中就以”空”来解析输入的空字符串,其结果就是程序什么都不做,直接返回,在程序中”空” 相当于return语句。


我们用表达式:1 + 2 ; 看看语法规则形成的解析树是怎样的:




在下面给出的视频中,我将对代码实现进行详细的讲解,同时通过运行代码,让大家体会到执行的效果,以帮助大家对语法解析的原理和实现有深一步的认识,大家把代码下下来,对着视频中的步骤运行一次,便可得知一个语法解析器的“五脏六腑"是如何组合运行的。由于视频中会出现代码解析,如果画面分辨率过低,可能无法看清代码,请大家在观看视频时将分辨率设置成高清或1080P。


由于csdn无法插入视频,我将视频地址给出如下:

http://v.youku.com/v_show/id_XMTQ4MTI2NzgyMA==.html?firsttime=0&from=y1.4-2

 

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
java精神(基于函数式组合子逻辑的javaparser框架) 一。 释名。 为什么叫精神? 如果你熟悉c++,那么你可能知道一个叫做”spirit”的parser库。它利用c++的模板元编程能力,使用c++语言本身提供了一个递归下降文法解析的框架。 我这里介绍的jparsec库,就是一个java里面的递归下降文法解析框架。 不过,它并非是spirit的java版本。 Jparsec的蓝本来自Haskell语言的parsec库。Parsec是一个基于monad的parser组合子库。 这个库的目的是要在java中提供一个类似parsec, spirit的库,这种组合子库并非c++的专利,java/c#也可以做到。这个库还将在java5.0上被改写,类型安全上它将也不再逊色于c++。 那么,为什么叫“函数式”呢?java是面向对象的嘛。 如果你使用过haskell, lisp等语言,这个函数式不用解释你也知道是怎么回事了。 如果你是一个老牌的c++/java程序员,那么这里还要稍微解释一下。当然如果您对这些虚头八脑的名词不感兴趣,那么,你尽可以跳过这一章,不知道什么是“函数式”,并不会影响你对这个库的理解的。 C++这几年随着gp的普及,“函数式”这个老孔乙己逐渐又被人从角落里面拽了出来。一个c++程序员所熟悉的“函数式”很可能是stl的for_each, transform,count_if这些函数。 怎么说呢,就象我不能否定str.length()这个调用属于OO一样,我也无法说for_each, transform不是函数式。 但是,“函数式”的精髓不在于此。 一般归纳起来,就像我们说OO是什么多态,封装,继承一样,“函数式”的特征被总结为: 1。无副作用。 2。高阶函数。 3。延迟计算 而最最有意义的(至少我认为如此),是基于高阶函数的函数组合能力。一些人把这叫做glue。 简短地说,什么让函数式编程如此强大?是用简单的函数组合出复杂函数的能力。 我可以想象,说到这里,你还是一头雾水。“什么是组合?1+1不是也把两个1组合成2了吗?new A(new B(), new C())不也是从B和C组合成A了?”
很高兴能回答您的问题!编写一个小型语法编译器需要包含以下几个步骤:词法分析、语法分析、语义分析、中间代码生成和目标代码生成。其中,词法分析和语法分析的过程可以通过可视化工具展示出来。 以下是一个简单的语法编译器实现,主要实现了词法分析和语法分析的功能。该编译器可以识别简单的算术表达式,并将其转换为语法树。 ```python from enum import Enum # Token类型 class TokenType(Enum): PLUS = "+" MINUS = "-" MUL = "*" DIV = "/" INTEGER = "INTEGER" LPAREN = "(" RPAREN = ")" EOF = "EOF" # Token类 class Token(object): def __init__(self, type, value): self.type = type self.value = value def __str__(self): return f"Token({self.type}, {self.value})" # Lexer类 class Lexer(object): def __init__(self, text): self.text = text self.pos = 0 self.current_char = self.text[self.pos] def error(self): raise Exception("Invalid character") def advance(self): self.pos += 1 if self.pos > len(self.text) - 1: self.current_char = None 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): 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): while self.current_char is not None: if self.current_char.isspace(): self.skip_whitespace() continue if self.current_char.isdigit(): return Token(TokenType.INTEGER, self.integer()) if self.current_char == "+": self.advance() return Token(TokenType.PLUS, "+") if self.current_char == "-": self.advance() return Token(TokenType.MINUS, "-") if self.current_char == "*": self.advance() return Token(TokenType.MUL, "*") if self.current_char == "/": self.advance() return Token(TokenType.DIV, "/") if self.current_char == "(": self.advance() return Token(TokenType.LPAREN, "(") if self.current_char == ")": self.advance() return Token(TokenType.RPAREN, ")") self.error() return Token(TokenType.EOF, None) # AST节点类 class AST(object): pass class BinOp(AST): def __init__(self, left, op, right): self.left = left self.token = self.op = op self.right = right # 数字节点 class Num(AST): def __init__(self, token): self.token = token self.value = token.value # 解析器 class Parser(object): def __init__(self, lexer): self.lexer = lexer self.current_token = self.lexer.get_next_token() def error(self): raise Exception("Invalid syntax") def eat(self, token_type): if self.current_token.type == token_type: self.current_token = self.lexer.get_next_token() else: self.error() def factor(self): token = self.current_token if token.type == TokenType.INTEGER: self.eat(TokenType.INTEGER) return Num(token) elif token.type == TokenType.LPAREN: self.eat(TokenType.LPAREN) node = self.expr() self.eat(TokenType.RPAREN) return node def term(self): node = self.factor() while self.current_token.type in (TokenType.MUL, TokenType.DIV): token = self.current_token if token.type == TokenType.MUL: self.eat(TokenType.MUL) elif token.type == TokenType.DIV: self.eat(TokenType.DIV) node = BinOp(left=node, op=token, right=self.factor()) return node def expr(self): node = self.term() while self.current_token.type in (TokenType.PLUS, TokenType.MINUS): token = self.current_token if token.type == TokenType.PLUS: self.eat(TokenType.PLUS) elif token.type == TokenType.MINUS: self.eat(TokenType.MINUS) node = BinOp(left=node, op=token, right=self.term()) return node # 可视化输出AST树 def print_ast(ast, indent=0): print(" " * indent, end="") print(ast.token.type) for child in ast.__dict__.values(): if isinstance(child, AST): print_ast(child, indent + 4) # 测试 def main(): while True: try: text = input("calc> ") except EOFError: break if not text: continue lexer = Lexer(text) parser = Parser(lexer) ast = parser.expr() print_ast(ast) if __name__ == '__main__': main() ``` 通过运行上述代码,可以在控制台输入算术表达式进行词法分析和语法分析,并可视化输出语法树。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值