[翻译&演绎]LLVM教程::我的第一个基于LLVM的语言前端::第二章::语法分析器与抽象语法树

导言

这段时间LLVM这个词突然经常出现在脑海里,例如一想到做Fuzz要各种插桩,然后AFL的插桩就是用到的LLVM,代码混淆框架 OLLVM 也是基于LLVM实现的。再加上以前也经常尝试写编译器,结果经常烂尾,撸到AST完成,后面就基本撸不动了,代码也比较乱,唯一一个能跑的是个Lisp解释器。想想LLVM天生是个强大的编译器设计框架,就来劲想彻底搞个小编译器出来,简单的能弄个程序出来,就开始学习官网的例子,自己同时用Flex, Bison设计前端,肝了三四天,最后勉勉强强弄了个出来。看看官网的教程没什么中文资料,打算翻译下打发时间。

我不打算直接翻译完整原文,首先原文其实挺容易理解,另外细节上的算法参考编译原理的书籍会更合适,手撸完整前端的过程有点浪费时间,且不是很有必要,所以我打算把我结合Flex/Bison等其他工程实现的过程做个介绍。

原文地址
我的实现::Fibol语言 基于Flex/Bison/LLVM实现的编译器

第二章 语法分析器与抽象语法树

本章我们将向您展示如何使用第一章中构建好的的lexer来为我们的语言构建一个完整的语法解析器。一旦有了语法解析器,我们将定义并构建抽象语法树(AST)。

我们将初步介绍一些定义,构建语法分析器的理论知识(详细内容请参考编译原理相关教材),还有许多有关AST的定义和构建,最后我们将选择使用Bison工具自动化构建语法分析器并嵌入AST构建的动作。

表达式语法解析

为了介绍语法解析的基本理论,我们将以表达式的语法作为参考。

首先要为我们表达式定义语法,介绍两个概念,终结符号可以理解为他只能被描述成一个特定的来自词法器的token,非终结符号,带有一定的推导规则,推导规则中还有可能包含其他非终结符号。语法分析器实际上就是接受一串词法分析的token输入,然后检查能否满足我们给出的开始非终结符号的推导,如果可以就接受,不行就语法错误。

对于表达式我们可以这样定义他的语法

expression -> conditional_expression

conditional_expression -> additive_expression relation_op additive_expression 
						| additive_expression
						
additive_expression ->  additive_expression add_op multiplicative_expression
 					|  multiplicative_expression
 
multiplicative_expression ->  multiplicative_expression mul_op primary_expression
						   | primary_expression	
						   
primary_expression: -> TOKENS_ID | TOKENS_NUMBER | TOKENS_LBRACKET expression TOKENS_RBRACKET

relation_op -> TOKENS_GREATER | TOKENS_LESSER

add_op -> TOKENS_PLUS | TOKENS_MINUS

mul_op -> TOKENS_STAR | TOKENS_DIV 

以上规则中,小写的是非终结符号,大写的是终结符号,| 符号表示其他可能性,-> 表示推导,第一行是这套语法的开始符号,如果以他构建语法分析器,语法分析器就是检测输入满不满足expression非终结符号。

例如a+b最后就会被推倒为

TOKENS_ID TOKENS_PLUS TOKENS_ID

表达式我们都知道是有运算优先级的,有趣的一点在于,文法的定义已经隐式的描述了这种优先级关系,我们推到非终结符号的过程有点类似深度优先搜索,形如 a+bc 这样的串,推导过程的形成的路径树,bc 会在 a 的下一层,他们优先结合,如果我们要计算这个表达式,一个简单的中序遍历就能完成。

关于手动实现这样的语法规则,可以使用递归的方式,非终结符号用函数表示,例如用lexer()表示词法器 匹配如下这个简单的文法

exp -> var TOKENS_PLUS var
var -> TOKENS_ID | TOKENS_NUM
bool var(){
	int tok = lexer();
	if(tok != TOKENS_ID && tok != TOKENS_NUM){
		return False;
	}
	return True;
}

bool exp(){
	if(!var()){
		return False;
	}	
	if(lexer() != TOKENS_PLUS){
		return False;
	}
	if(!var()){
		return False;
	}
	return True;
}

当然这很简单,复杂的文法还需要考虑定义中是否得当,否则实现过程也会变复杂,具体细节请参考编译原理资料,而本章后面将介绍Bison这个工具,和Flex一样只要给出规则,Bison就能帮你完成文法器的实现,他还能自动解决很多文法冲突问题,很方便。

抽象语法树 AST

抽象语法树AST是在代码生成和语法解析中间的一个部分,为什么我们需要这个部分?如之前所说,语法解析的过程就会形成一个路径树,把这个路径树记录下来,可以方便我们后面进行具体代码生成,这样可以解除模块之间的耦合,但这不是重点,AST的重点是抽象,如果简单的就形成一颗语法树,实际上这并不方便引入了很多无用的东西,比如考虑下面的定义函数的过程

function hello(){
	print("Hello");
}

如果直接按照语法器分析形成语法树,其中包含了许多无用的节点如括号,function这样的保留。函数定义的重点其实就三个,名称,参数,代码块。抽象语法树的目的就是从程序语言中抽象出核心的部分,时期无关与前端具体语法学形式,所以AST应该有不错的复用性,模块化程度更高。

考虑表达式的抽象语法树定义

class ExprAST {
public:
  virtual ~ExprAST() {}
};

class Con
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值