自己动手写编译器:实现简单if语句的跳转代码生成

Posted tyler_download

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自己动手写编译器:实现简单if语句的跳转代码生成相关的知识,希望对你有一定的参考价值。

本节我们要完成的目的是将下面带有if语句的代码转换为中间代码:

int x; int y; int z;
		        x = 1;
				y = 2;
				if (y > x) 
					z = 2;
				
				z = 3;
	

完成本节代码后,上面代码会被转译成如下中间代码:

L1:
        x = 1
L3:
        y = 2
L4:
        http://m.study.163.com/provider/7600199/index.htm?share=2&shareId=7600199 y > x goto L5
L6:
        z = 2
L5:
        z = 3
L2:

注意看L4, L5, L6三个地方的分支,中间代码有一个指令叫iffalse,后面跟着一个表达式,如果表达式结果能转换为false,那么goto语句就产生作用,跳转到它对应的语句,如果表达式结果为true,那么控制流直接跳转到L4下面的语句。

我们先看看if语句对应的语法规则表达式:

stmt -> if "(" bool ")" stmt
bool -> expr rel expr
rel -> LE | LESS_OPERATOR | GE | GREATER_OPERATOR | NE | EQ

if语句的规则是,在关键字if后面必须跟着左括号,然后对应bool表达式,它实际上是两个算术表达式进行比较操作,也就是两个表达式之间对应这<, <=, >, >=, !=, ==等比较操作符,两个表达式比较完后就会得出true或是false的结果,然后使用iffalse指令在表达式比较结果上对执行流进行操作。

因此我们实现的方法是,在遇到if语句前先给他一个跳转标签,也就是前面例子中的L4,然后if条件成立时对应的语句集合其实是一个stmt,所有语句对应一个标签,也就是L5。由于if语句后面会跟着一个左大括号,里面对应这如果判断条件成立就要执行的代码,于是对应右大括号后面的语句就是if判断条件不成立时要执行的代码,那么这些代码对应的跳转标签就紧接着L5,也就是上面例子中的L6。因此本节难点在于:1,为if语句生成对应代码,由于我们要由浅入深,因此本节if对应判断条件就是两个ID对象,或是ID和Constant常量对象比较,后面我们还会加上&&和||这种运算符。2,如何决定跳转的标签号。这些逻辑不好用言语表述,还是得在代码实现和调试中更好理解。

接下来我看代码实现,首先要修改一下ExprInterface的接口:

type ExprInterface interface 
	NodeInterface
	Gen() ExprInterface
	Reduce() ExprInterface
	Type() *Type
	ToString() string
	//新增两个接口
	Jumping(t uint32, f uint32)
	EmitJumps(test string, t uint32, f uint32)

这里新增两个接口分别为Jumping和EmitJumps,它们用来设置if, if…else…,for, while, do…while等控制语句的跳转,由于接口修改了,因此任何实现它的实例都得修改,我们下面只显示正要的修改,其他修改他家可以直接下载代码查看,代码下载地址我在末尾给出。首先要修改的是expr.go:

func (e *Expr) Jumping(t uint32, f uint32) 
	e.EmitJumps(e.ToString(), t, f)


func (e * Expr) EmitJumps(test string, t uint32, f uint32) 
	if t != 0 && f != 0 
		e.Emit("if " + test + " got L" + strconv.Itoa(int(t)))
		e.Emit("goto L" + strconv.Itoa(int(f)))
	 else if t != 0 
		e.Emit("if " + test + " goto L" + strconv.Itoa(int(t)))
	 else if f != 0 
		e.Emit("iffalse " + test + " goto L" + strconv.Itoa(int(f)))
	

Jumping接收两个参数t,f,他们其实对应true和false,然后调用EmitJumps,后者根据传入的t,f值来输出跳转代码,如果t等于0或者是f等于0,那意味着不用输出对应的跳转代码。在上面代码中我们目前需要关系的是:

e.Emit("ifalse " + test + "goto L" + strconv.Itoa(int(f))) 

这条语句就输出了我们前面例子中对应的:

 iffalse y > x goto L5

原理我们算术表达式使用"+“或者”-“进行连接,现在表达式之间也能用”>", “<”, "<="等比较符号连接,因此我们也要针对这种情况创建一个实现ExprInterface接口的实例,因此我们创建文件rel.g实现代码如下:

package inter

import (
	"lexer"
)

type Rel struct 
	logic *Logic
	expr1 ExprInterface
	expr2 ExprInterface
	token *lexer.Token


func relCheckType(p1 *Type, p2 *Type) *Type 
	if p1.Lexeme == p2.Lexeme 
		return NewType("bool", lexer.BASIC, 1)
	

	return nil


func NewRel(line uint32, token *lexer.Token,
	expr1 ExprInterface, expr2 ExprInterface) *Rel 
	return &Rel
		logic: NewLogic(line, token, expr1, expr2, relCheckType),
		expr1: expr1,
		expr2: expr2,
		token: token,
	


func (r *Rel) Errors(s string) error 
	return r.logic.Errors(s)


func (r *Rel) NewLabel() uint32 
	return r.logic.NewLabel()


func (r *Rel) EmitLabel(l uint32) 
	r.logic.EmitLabel(l)


func (r *Rel) Emit(code string) 
	r.logic.Emit(code)


func (r *Rel) Gen() ExprInterface 
	return r.logic.Gen()


func (r *Rel) Reduce() ExprInterface 
	return r


func (r *Rel) Type() *Type 
	return r.logic.Type()


func (r *Rel) ToString() string 
	return r.logic.ToString()


func (r *Rel) Jumping(t uint32, f uint32) 
	expr1 := r.expr1.Reduce()
	expr2 := r.expr2.Reduce()
	test := expr1.ToString() + " " + r.token.ToString() + " " + expr2.ToString()
	r.EmitJumps(test, t, f)


func (r *Rel) EmitJumps(test string, t uint32, l uint32) 
	r.logic.EmitJumps(test, t, l)


上面代码中,构造函数NewRel接收三个重要参数,分别是token, expr1, expr2,后面两个就是对应要比较的算术表达式,token对应衔接两个表达式的比较符号,也就是">“,”<“,”<="这些。这里需要确定两个表达式expr1,expr2属于相同类型,如果一个类型是数值,另一个类型是字符串,那么它们在逻辑上就没有可比性,于是代码中有了relCheckType函数,它判断两个表达式的类型必须一样,构造rel节点才能合法。

其实不同类型也能比较,例如int和float应该能相互比较,只不过为了简单起见,我们暂时不做考虑。我能还需要关系Jumping的实现,它分别调用了两个表达式的Reduce接口,如果表达式是复杂类型,例如 (a+b) > (c+d)这种,那么expr1对应a+b,调用它的Reduce后,根据前面我们的实现,编译器会将a+b的结果赋值给一个临时寄存器,然后用该寄存器来表示它,也就是a+b会先转译成:

t1 = a + b

同理c+d会被转译成:

t2 = c + d

最后代码会生成中间指类似如下:

iffalse t1 > t2 goto L5

上面代码中用到一个logic对象,它的作用在后面我们实现&&,||这种连接符时才有用,因此这里我们先把它的代码贴出来,不过暂时不用理解它,因为它的我们本节的影响不大,logic.go的内容如下:

package inter

import (
	"errors"
	"lexer"
	"strconv"
)

/*
实现or, and , !等操作
*/

type Logic struct 
	expr      ExprInterface
	token     *lexer.Token
	expr1     ExprInterface
	expr2     ExprInterface
	expr_type *Type
	line      uint32


type CheckType func(type1 *Type, type2 *Type) *Type

func logicCheckType(type1 *Type, type2 *Type) *Type 

	if type1.Lexeme == "bool" && type2.Lexeme == "bool" 
		return type1
	

	return nil


func NewLogic(line uint32, token *lexer.Token,
	expr1 ExprInterface, expr2 ExprInterface, checkType CheckType) *Logic 
	expr_type := checkType(expr1.Type(), expr2.Type())
	if expr_type == nil 
		err := errors.New("type error")
		panic(err)
	

	return &Logic
		expr:      NewExpr(line, token, expr_type),
		token:     token,
		expr1:     expr1,
		expr2:     expr2,
		expr_type: expr_type,
		line:      line,
	


func (l *Logic) Errors(s string) error 
	return l.expr.Errors(s)


func (l *Logic) NewLabel() uint32 
	return l.expr.NewLabel()


func (l *Logic) EmitLabel(label uint32) 
	l.expr.EmitLabel(label)


func (l *Logic) Emit(code string) 
	l.expr.Emit(code)


func (l *Logic) Type() *Type 
	return l.expr_type


func (l *Logic) Gen() ExprInterface 
	f := l.NewLabel()
	a := l.NewLabel()
	temp := NewTemp(l.line, l.expr_type)
	l.Jumping(0, f)
	l.Emit(temp.ToString() + " = true")
	l.Emit("goto L" + strconv.Itoa(int(a)))
	l.EmitLabel(f)
	l.Emit(temp.ToString() + "=false")
	l.EmitLabel(a)
	return temp


func (l *Logic) Reduce() ExprInterface 
	return l


func (l *Logic) ToString() string 
	return l.expr1.ToString() + " " + l.token.ToString() + " " + l.expr2.ToString()


func (l *Logic) Jumping(t uint32, f uint32) 
	l.expr.Jumping(t, f)


func (l *Logic) EmitJumps(test string, t uint32, f uint32) 
	l.expr.EmitJumps(test, t, f)


从代码可以看到,rel.EmitJumps虽然调用了logic的EmitJumps,但本质上都是调用expr.EmitJumps,因此logci对rel没有产生实质性影响。现在我们回到语法解析,增加其对if语句的解析,首先我们要创建一个继承了StmtInterface接口的If节点,它用来生成if语句对应的中间代码,其内容如下:

package inter

import (
	"errors"
)

type If struct 
	stmt    StmtInterface
	expr    ExprInterface
	if_stmt StmtInterface


func NewIf(line uint32, expr ExprInterface, if_stmt StmtInterface) *If 
	if expr.Type().Lexeme != "bool" 
		err := errors.New("bool type required in if")
		panic(err)
	
	return &If
		stmt:    NewStmt(line),
		expr:    expr,
		if_stmt: if_stmt,
	


func (i *If) Errors(str string) error 
	return i.stmt.Errors(str)


func (i *If) NewLabel() uint32 
	return i.stmt.NewLabel()


func (i *If) EmitLabel(label uint32) 
	i.stmt.EmitLabel(label)


func (i *If) Emit(code string) 
	i.stmt.Emit(code)


func (i *If) Gen(_ uint32, end uint32) 
	label := i.NewLabel()
	i.expr.Jumping(0, end)
	i.EmitLabel(label)
	i.if_stmt.Gen(label, end)


它的逻辑如下,由于if语句会对一个数学表达式进行判断,根据判断结果来执行if成立时对应的语句集合,因此If节点对应两个参数,一个是ExprInterface实例,它对应的就是if后面用于判断的表达式,另一个是StmtInterface实例,它对应if成立后要执行的语句。所以在它的Gen函数中,end对应如果if条件不成立所要执行的代码的跳转标签,它生成了一个label,对应的就是if判断成立时,所要执行语句块的标签。i.expr.Jumping是在解析if 后面表达式后,跳转到判断成立时对于语句的地址标签,i.if_stmt.Gen用于生成if判断条件成立后,大括号里面的语句。

我们再看看语法解析的过程,在list_parser.go中做如下修改:

func (s *SimpleParser) stmt() inter.StmtInterface 
	/*
		stmt -> if "(" bool ")" stmt
		bool -> expr rel expr
		rel -> LE | LESS_OPERATOR | GE | GREATER_OPERATOR | NE | EQ
	*/
	switch s.cur_tok.Tag 
	case lexer.IF:
		s.move_forward()
		err := s.matchLexeme("(")
		if err != nil 
			panic(err)
		
		s.move_forward()
		x := s.bool()
		err = s.matchLexeme(")")
		if err != nil 
			panic(err)
		
		s.move_forward() //越过 )
		s.move_forward() //越过
		s1 := s.stmt()
		err = s.matchLexeme("")
		if err != nil 
			panic(err)
		
		s.move_forward() //越过
		return inter.NewIf(s.lexer.Line, x, s1)
	default:
		return s.expression()
	



func (s *SimpleParser) bool() inter.ExprInterface 
	expr1 := s.expr()
	var tok *lexer.Token

	switch s.cur_tok.Tag 
	case lexer.LE:
	case lexer.LESS_OPERATOR:
		fallthrough
	case lexer.GE:
		fallthrough
	case lexer.GREATER_OPERATOR:
		fallthrough
	case lexer.NE:
		tok = lexer.NewTokenWithString(s.cur_tok.Tag, s.lexer.Lexeme)
	default:
		tok = nil
	

	if tok == nil 
		panic("wrong operator in if")
	
	s.move_forward()
	expr2 := s.expr()
	return inter.NewRel(s.lexer.Line, tok, expr1, expr2)

上面代码逻辑是,首先判断当前读到的是否为if标签,如果是,那么进入都if语句的解析流程,bool()解析if语句对应的判断条件,它首先解析比较符号左边的表达式,然后读取比较符号,然后解析右边的表达式,最后将左边表达式,比较符合,右边表达式合在一起形成一个Rel节点。Rel节点会结合到If节点里,If在Gen调用生成代码时,就会调用Rel节点生成判断表达式的代码。在语法解析中,产生If节点的时候,除了解析if后面的表达式,代码还通过stmt()来解析if大括号里面的代码,最终形成If节点后,它的Reduce函数也能为大括号里面的代码生成中间代码。

代码下载地址:https://github.com/wycl16514/dragon-compiler-if-code-generation.git,更多干货

创作打卡挑战赛 赢取流量/现金/CSDN周边激励大奖

以上是关于自己动手写编译器:实现简单if语句的跳转代码生成的主要内容,如果未能解决你的问题,请参考以下文章

自己动手写编译器:实现if判断中“||“和“&&“条件判断的中间代码生成

自己动手写编译器:实现else语句块的中间代码生成

自己动手写编译器:实现else语句块的中间代码生成

自己动手写事件总线(EventBus)

通过goto语句学习if...elseswitch语句并简单优化

五子棋项目的实现具体的总结