简单的c编译器(c语言实现),实现简易的C语言编译器(part 3)

在前几部分中,我们重点分析了前处理和词法分析过程,已经将源代码拆解成一个个的token了。接下来,我们将分析这些token的逻辑结构是否严格按照C语言定义的语法规则来排布,这就是将要介绍的内容:语法分析。

还是从代码入手分析,这样更加生动形象。给定下面这段C代码:

int a;

int main()

{

int b;

b = 1;

return (a + 2) - b;

}

数学家对物理学家说:“只要你能将物理问题转化为数学问题,我就都能解决。”这段话告诉我们要学会抽象。具体而言,

显然,int a和int b是完全类似的,我们可以将其归为一类。更宽泛的,除了声明变量,也可以将函数变量的声明int main也归类为同一类别,并统之为declaration,因为它们都是一类型符int开始的。

b = 1和(a + 2) * 2 - b都是操作声明的变量,统称为expression。

expression以分号结尾,表明一个具体的操作,称为statement。

可能有点绕,归纳起来,采用缩进表示层级关系,上面代码可以这样进行描述:

program

declaration

declaration --> function_definition

statement --> compound_statement

declaration

statement --> expression_statement

expression

statement --> return_statement

expression

箭头右边所指对应着该名字的详细分类,我们会在后面深入研究。

通过这种方式,我们就将C语言中看似复杂多变、毫无头绪的结构变成了一种抽象的、方便处理的形式。值得注意的是,这些划分和教科书上面的介绍肯定有所出入,只是在参考资料的基础上,我想按照自己的理解进行介绍,不至于按照条条框框地介绍让人觉得枯燥无味。

其实,无论多复杂的C语言代码,都可以将其分成三大类:表达式、语句和声明。如果大家接受这样一套定义规则,那么我们将依次介绍这些内容,开始语法分析的处理过程。

3.1 表达式(expression)

先来研究在上面代码中出现的这段表达式:

(a + 2) - b --> a, 2, b

箭头右边的变量和具体的值,是我们所能拆分的最小单元了。并且恰好是词法分析过程中得到的那些token,我们将其记为primary_expression。于是,primary_expression的组成形式为:

primary_expression : ID

| INT

其中|表示“或者”,表示可以取任何一个类型的值。ID代表标识符,如a和b,INT是具体的数值所对应的类型,后面还会介绍的CHAR也是这样的。这些primary_expression通过加减运算符号联系起来,形成稍微复杂一点的additive_expression。于是,括号内的部分可以表示成:

additive_expression : primary_expression + primary_expression

进一步的,如何表示(a + 2) - b呢?如果能够将之表示为:

additive_expression : additive_expression - primary_expression

那么,相信你已经对这样一套流程有了初步的认识了。值得注意的是,上面的表述方法中,我们还没有将括号纳入其中,但由于括号优先级最高,又必须考虑。怎么办呢?那就将括号内的部分打包当成最小单元,问题也就引刃而解了。从而可以更新primary_expression的组成形式,得到:

primary_expression : ( additive_expression )

| ID

| INT

再将两条additive_expression合并起来:

additive_expression : primary_expression + primary_expression

| additive_expression - primary_expression

这样就构成了表达式(a + 2) - b的完整描述。这里面其实暗含着一种递归,直到我们能够返回ID或者INT为止。那么对照着,下面的表达式:

((a + 1) + 2) - b

也能用上面的表示方法描述。于是乎,就达到了我们抽象问题的目的。但是,如果包含乘法,或者更加复杂一些的表达式呢?我先直接写出完整的四则运算的描述:

primary_expression : ( additive_expression )

| CHAR

| INT

| ID

unary_expression : primary_expression

| + unary_expression

| - unary_expression

multiple_expression : unary_expression

| multiple_expression * unary_expression

| multiple_expression / unary_expression

additive_expression : multiple_expression

| additive_expression + multiple_expression

| additive_expression - multiple_expression

怎么来理解这些表达式的描述呢?举个例子,假设有这样一个表达式:

(3 - 2 / 2)

我们用下图来理解这些表达式之间的关系:

95e93f8e3bb8

图3-1. 表达式:(3 - 2 / 2)图示

此刻,相信大家一定也看明白了整个的分析过程。接下来,我们开始实现这样一个过程。首先,定义一个类来描述这种结构。

class Parser(object):

def __init__(self, lexer):

self.lexer = lexer

self.current_token = self.lexer.get_next_token()

def eat(self, token_type):

if self.current_token.type == token_type:

self.current_token = self.lexer.get_next_token()

else:

raise Exception()

这里的eat函数其实就是调用词法分析中的get_next_token(),但是增加了一个判断,使得我们能够按照期望的token来进行匹配,否则就会抛出异常。这样,就形成了按照给定语法结构进行语法分析的基本框架。

对照着上文四则运算的描述,具体的实现代码如下:

def additive_expression(self):

node = self.multiple_expression()

while self.current_token.type in (PLUS, MINUS):

token = self.current_token

self.eat(token.type)

node = BinOp(left=node, op=token.value, \

right=self.multiple_expression())

return node

def multiple_expression(self):

node = self.unary_expression()

while self.current_token.type in (MUL, DIVIDE):

token = self.current_token

self.eat(token.type)

node = BinOp(left=node, op=token.value, \

right=self.unary_expression()))

return node

def unary_expression(self):

token = self.current_token

if token.type == PLUS:

self.eat(token.type)

node = self.unary_expression()

elif token.type == MINUS:

self.eat(token.type)

node = Negative(self.unary_expression())

else:

node = self.primary_expression()

return node

def primary_expression(self):

token = self.current_token

if token.type in (INT, CHAR, ID):

self.eat(token.type)

return token.value

elif token.type == LPAREN:

self.eat(LPAREN)

node = self.additive_expression()

self.eat(RPAREN)

return node

return None

这段代码中,类似于PLUS这种全部由大写字母组成的名词就是token。而BinOp和Negative则是对处理得到的数据的一种组合,方便后续处理,我们会在后面详细介绍。

有了这些内容,写个简单的计算器应用应该会非常简单了。但对于C语言而言,光有这些这些基本类型还远远不够,我们进一步来研究对数组和结构体等这些稍微复杂的数据类型,所组成的表达式应该如何描述。例如:

str[3]

point->x

person.id

&x

这里,处理括号的思想将不再适用,数组下标或者结构体成员都是具体的primary_expression,我们只能增加表达式的描述内容。因此,扩展后的表达式描述如下:

unary_expression : postfix_expression

| & unary_expression

| * unary_expression

| + unary_expression

| - unary_expression

postfix_expression : primary_expression

| postfix_expression [ expression ]

| postfix_expression . ID

| postfix_expression -> ID

那么,对于具体的实现,就只需要依葫芦画瓢,这里就不再赘述了。

不要忘了,还有函数的使用。C语言中,调用函数的方式如下:

printf("%s", "Hello world!");

add(1, 2);

callBackCb();

于是,根据结构,继续补充描述:

postfix_expression : primary_expression

| postfix_expression [ expression ]

| postfix_expression . ID

| postfix_expression -> ID

| ( )

| ( argument_expression_list )

argument_expression_list : primary_expression

| argument_expression_list, primary_expression

这里留下一个问题,对于C语言定义的其它运算,如逻辑运算符(&&,||)、关系运算符(>=)等等又该如何处理呢?可以按照上面给定的这些描述触类旁通。具体的细节,我们留在下一部分进行详细介绍。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值