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

前面几节我们完成了if语句以及判断条件成立时代码对应的中间代码生成,这次我们完成最后一笔,那就是针对else部分代码完成相应的中间代码生成。本质上这一步比较简单,它会在原来if语句中间代码的基础上稍作修改即可,我们先看看这次我们要编译的代码内容:

`{int a; int b; int c; int d;
		        int e;
		        a = 1;
				b = 2;
				c = 3;
				d = 4;
				if (b == a && c != d) {
					e = 2;
				} else {
                    e = 3;
                }
				
	}`

我们在代码中增加了else语句块,我们看看完成本节代码后的执行结果:
请添加图片描述
从结果看,e=2对应if大括号里面的代码,e=3对应else部分代码,与前面不同的是,编译器在实现if里面代码后,在末尾添加一个goto语句直接越过else部分代码,进入到else之后的代码,从输出看,逻辑应该没有问题。

下面我们看看代码实现,首先为else语句部分增加一个节点,因此添加else.go文件,然后编写代码如下:

package inter

import (
	"errors"
	"strconv"
)

type Else struct {
	stmt  *Stmt
	expr  ExprInterface
	stmt1 StmtInterface
	stmt2 StmtInterface
}

func NewElse(line uint32, expr ExprInterface, stmt1 StmtInterface, stmt2 StmtInterface) *Else {
	if expr.Type().Lexeme != "bool" {
		err := errors.New("bool type required in if")
		panic(err)
	}
	return &Else{
		stmt:  NewStmt(line),
		expr:  expr,
		stmt1: stmt1,
		stmt2: stmt2,
	}
}

func (e *Else) Errors(str string) error {
	return e.stmt.Errors(str)
}

func (e *Else) NewLabel() uint32 {
	return e.stmt.NewLabel()
}

func (e *Else) EmitLabel(i uint32) {
	e.stmt.EmitLabel(i)
}

func (e *Else) Emit(code string) {
	e.stmt.Emit(code)
}

func (e *Else) Gen(_ uint32, end uint32) {
	label1 := e.NewLabel()
	label2 := e.NewLabel()

	e.expr.Jumping(0, label2)
	e.EmitLabel(label1) //生成if条件判断中代码
	e.stmt1.Gen(label1, end) //生成if成立后大括号里面代码的中间代码
	e.Emit("goto L" + strconv.Itoa(int(end))) //增加goto语句跳过else部分代码
	e.EmitLabel(label2) 
	e.stmt2.Gen(label2, end) //生成else里面代码对应中间代码
}

上面代码跟我们前面实现的if节点类似,它的创建需要输入三部分,首先在构造函数中,第一个参数expr对应if里面的条件判断表达式,stmt1对应if成立时大括号里面的语句集合,stmt2对应else部分的语句集合,值得关注的地方在它的gen函数,它首先执行s.xpr.Jumping, e.stmt1.Gen生成条件判断语句和if成立时语句块的中间代码,最重要的是它在if语句块里面的代码完成生成后加入一条goto语句,这个goto语句的作用是越过else部分的代码。很显然当if语句判断成立后,我们执行了if内部代码就肯定不能再执行else部分代码,所以在if内部语句块的后面加上goto越过else部分指令是合理的。

接下来我们看看语法解析部分的修改:

func (s *SimpleParser) stmt() inter.StmtInterface {
	/*
		if "(" bool ")"
		if -> "(" bool ")" ELSE stmt

		bool -> bool "||"" join | join
		join -> join "&&" equality | equality
		equality -> equality "==" rel | equality != rel | rel
		rel -> expr < expr | expr <= expr | expr >= expr | expr > expr | expr
		rel : a > b , a < b, a <= b
		a < b && c > d || e < f
	*/
	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() //越过}

		//判断if 后面是否跟着else
		if s.cur_tok.Tag != lexer.ELSE {
			return inter.NewIf(s.lexer.Line, x, s1)
		} else {
			s.move_forward() //越过else关键字
			err = s.matchLexeme("{")
			if err != nil {
				panic(err)
			}
			s.move_forward() //越过{
			s2 := s.stmt()   //else 里面包含的代码块
			err = s.matchLexeme("}")
			if err != nil {
				panic(err)
			}
			return inter.NewElse(s.lexer.Line, x, s1, s2)
		}

	default:
		return s.expression()
	}

}

在代码中我们增加了对else关键字的解析,我们解析完if部分后,接着判断是否有else关键字跟随其后,如果有我们则对else内部代码调用stmt来进行解析,完成上面代码后运行起来就能得到相应结果。更详细的调试演示请在b站搜索coding迪斯尼,更多干货:http://m.study.163.com/provider/7600199/index.htm?share=2&shareId=7600199

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目:C0编译器的设计与实现(10周) C0语言的语法结构定义如下: ->[] {} -> int id {, id}; -> ( int id | void id) '(' ')' ->void main'(' ')' ->'{' [] '}' -> {} -> | | '{''}' | | | | | | ; ->if '('')' [else ] ->while '(' ')' ->; ->id = ; ->return ['(' ')'] ; ->scanf '(' id ')'; ->printf '(' [ ] ')'; -> [+|-] { (+|-) } -> {(*|/) } -> id|'(' ')' | num | ->id '(' ')' 其中,id代表标识符,num代表整数,其含义及构成方式与C语言相一致;C0源程序中的变量需先定义后使用,其作用域与生存期与C语言相一致;自定义函数可超前使用(调用在前,定义在后)。 根据上面给定的C0文法及其说明和下列定义的假想栈式指令系统,按递归下降分析法设计并实现该C0语言的编译器,生成栈式目标代码;编栈式指令系统的解释执行程序,输出目标代码的解释执行结果。 假想的栈式指令系统表 LIT 0 a 将常数到栈顶,a为常数 LOD t a 将变量到栈顶,a为相对地址,t为层差 STO t a 将栈顶内容送入某变量单元中,a为相对地址,t为层差 CAL 0 a 调用函数,a为函数地址 INT 0 a 在运行栈中为被调用的过程开辟a个单元的数据区 JMP 0 a 无条件跳转至a地址 JPC 0 a 条件跳转,当栈顶为0,则跳转至a地址,否则顺序执行 ADD 0 0 次栈顶与栈顶相加,退两个栈元素,结果进栈 SUB 0 0 次栈顶减去栈顶,退两个栈元素,结果进栈 MUL 0 0 次栈顶乘以栈顶,退两个栈元素,结果进栈 DIV 0 0 次栈顶除以栈顶,退两个栈元素,结果进栈 RED 0 0 从命令行读入一个输入置于栈顶 WRT 0 0 栈顶输出至屏幕并换行 RET 0 0 函数调用结束后,返回调用点并退栈

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值