北航编译原理总结 C文法

定位:传说中北航计算机学院最头疼课程其实也没有辣么难,一点点的完成,并不会出现传说中的刷夜~

0.pascal-s和PL/0编译器源码有必要结合编译器基础知识认真读一下,当然不必细枝末节,重点是看一下人家的编译器中所谓的“词法分析”“语法分析”等阶段以及符号表的建立需要什么量,每个量分别代表什么,以及运行栈出现在什么时候,如何设计(可以和后期优化结合起来~)等。

1.我的文法为 扩充c0文法-高,具体文法如下:

<加法运算符> ::= +|-

<乘法运算符>  ::= *|/

<关系运算符>  ::=  <|<=|>|>=|!=|==

<字母>   ::= _|a|...|z|A|...|Z

<数字>   ::= 0|<非零数字>

<非零数字>  ::= 1|...|9

<字符>    ::=  ‘<加法运算符>‘|‘<乘法运算符>‘|‘<字母>‘|‘<数字>‘

<字符串>   ::=  "{十进制编码为32,33,35-126的ASCII字符}"

<程序>    ::= [<常量说明>][<变量说明>]{<有返回值函数定义>|<无返回值函数定义>}<主函数>

<常量说明> ::=  const<常量定义>;{ const<常量定义>;}

<常量定义>   ::=   int<标识符>=<整数>{,<标识符>=<整数>}

| char<标识符>=<字符>{,<标识符>=<字符>}

<无符号整数>  ::= <非零数字>{<数字>}

<整数>        ::= [+|-]<无符号整数>|0

<标识符>    ::=  <字母>{<字母>|<数字>}

<声明头部>   ::=  int<标识符>|char<标识符>

<变量说明>  ::= <变量定义>;{<变量定义>;}

<变量定义>  ::= <类型标识符>(<标识符>|<标识符>‘[’<无符号整数>‘]’){,(<标识符>|<标识符>‘[’<无符号整数>‘]’) }

<类型标识符>      ::=  int
| char

<有返回值函数定义>  ::=  <声明头部>‘(’<参数>‘)’ ‘{’<复合语句>‘}’

<无返回值函数定义>  ::= void<标识符>‘(’<参数>‘)’‘{’<复合语句>‘}’

<复合语句>   ::=  [<常量说明>][<变量说明>]<语句列>

<参数>    ::= <参数表>

<参数表>    ::=  <类型标识符><标识符>{,<类型标识符><标识符>}| <空>

<主函数>    ::= void main‘(’‘)’ ‘{’<复合语句>‘}’

<表达式>    ::= [+|-]<项>{<加法运算符><项>}

<项>     ::= <因子>{<乘法运算符><因子>}

<因子>    ::= <标识符>|<标识符>‘[’<表达式>‘]’|<整数>|<字符>|<有返回值函数调用语句>|‘(’<表达式>‘)’

<语句>    ::= <条件语句>|<循环语句>|‘{’<语句列>‘}’|<有返回值函数调用语句>; |<无返回值函数调用语句>;|<赋值语句>;|<读语句>;|<写语句>;|<空>;|<返回语句>;

<赋值语句>   ::=  <标识符>=<表达式>|<标识符>‘[’<表达式>‘]’=<表达式>

<条件语句>  ::=  if ‘(’<条件>‘)’<语句>[else<语句>]

<条件>    ::=  <表达式><关系运算符><表达式>|<表达式>

<循环语句>   ::=  do<语句>while ‘(’<条件>‘)’ |for‘(’<标识符>=<表达式>;<条件>;<标识符>=<标识符>(+|-)<步长>‘)’<语句>

<步长>::= <无符号整数>

<有返回值函数调用语句> ::= <标识符>‘(’<值参数表>‘)’

<无返回值函数调用语句> ::= <标识符>‘(’<值参数表>‘)’

<值参数表>   ::= <表达式>{,<表达式>}|<空>

<语句列>   ::={<语句>}

<读语句>    ::=  scanf ‘(’<标识符>{,<标识符>}‘)’

<写语句>    ::=  printf‘(’<字符串>,<表达式>‘)’|printf ‘(’<字符串>‘)’|printf ‘(’<表达式>‘)’

<返回语句>   ::=  return[‘(’<表达式>‘)’]

附加说明:

(1)char类型的表达式,用字符的ASCII码对应的整数参加运算,在写语句中输出字符

(2)标识符区分大小写字母

(3)写语句中的字符串原样输出

(4)数组的下标从0开始

首先,你需要看懂你的文法,它就是一套编译世界运行的规则,包括一些细节的问题如:在规定的文法中do while 循环最后可以没有分号,不同于C语言标准文法。当然加上;也是可以的,因为文法中有<语句>=<空>;这么一条。

2.词法分析-->语法分析-->语义分析和代码生成-->优化后的代码生成.明确各个阶段干了什么,想清楚如何设计四元式,如何设计运行栈,再动手码代码。



词法分析:平日里运行的代码,其实就是一个个的字符组成的字符串。而词法分析的作用就是将这么个大的字符串分隔和组合成有意义的单词,并做相应的记录。

a.可以将输入的源代码出现的单词分为:关键字(即保留字,如char,int,main等)、单分界符、预读分界符、整数、标识符等。如下图所示,在下图中表格的帮助下,我们在语法分析阶段每获取有个单词,就知道他是什么性质,从而分析接下来源程序可能要干什么。

b.注意,有的时候需要预读字符才能知道 当前单词是什么性质的o~

如果下一个字符是回车,也要能够正确处理!

语法分析:结合文法规则和并调用词法分析程序,实现明确源代码语法成分分析。

如对于如下代码:const int a=0,b=2;

在读到第一逗号处,应当识别出是常量定义,在分号处应当识别出是又一个常量定义,并紧接着报告常量说明。我采用了如下的方式说明,比较清晰。

const   CONSTSY

int     INTSY

a   DIENT

=   EQUSY

0    INTSY

,       ---  常量定义

b   DIENT

=     EQUSY

2    INTSY

;      ---  常量定义

       ---   常量说明

另外,这个阶段需要明确很多词的含义,并生成符号表,供之后使用。相信你已经看到书中对于Pascal-s编译器符号表的设计,其中对于符号表中的各个量的设计在不同的语言中不同,但是如果你能明白PASCAL-S符号表中各个量的含义,那对应你需要设计的应该没有问题,不懂的可以看书,书上有!细心看!

特别说明:符号表中addr 对于函数名代表其相应目标代码的入口地址,对于变量(数组同理,数组只不过是很多的变量)可以代表其在运行栈中的相对地址,对于常数,可以代表其实际对应的ASCII码值。

3.语义分析

这个最难想的一个阶段。符号表存储的信息和源代码生成四元式呢?

其实,语义分析和语法分析是紧密结合在一起的,“四元式在有当前期储备好的足够信息时,即可生成。”如下面比较复杂的转四元式的例子:

for(i=0①;②i<10③;④i=i+1⑤)

{

。。。

。。。

}

分别在for循环的7个地方需要生成相应的中间代码,其实生成中间代码的过程就是程序的执行流程。其中,标签的对应比较重要。

①      :ASSIGN 0 i

①      :SET label1

②      :BGE i 10 label2 ;GOTO label3

③      :SET label4

④      :ADD i 1 $t1;ASSIGN $t1 i ;GOTO label1

⑤      :SET label3

⑥      :GOTO label4

⑧    :SET label2

可见,一个for循环框架在8处转化为若干条中间代码,即四元式。语义分析是在语义分析代码的基础上,各个细节处增加相应的代码即可。

四元式的设计可以参考如下:

四元式的数据结构如下:

struct {

char op[1000];

char op1[1000];

char op2[1000];

char res[1000];

}midCode[10000];

四元式的结构如下表所示:


分组


编号


四元式


说明


基本算术运算


1


ADD  SRC1  SRC2  DST


加:DST = SRC1 + SRC2


2


SUB  SRC1  SRC2  DST


减:DST = SRC1 - SRC2


3


MULT  SRC1  SRC2  DST


乘:DST = SRC1 * SRC2


4


DIV  SRC1  SRC2  DST


除:DST = SRC1 / SRC2


 


普通赋值


5


ASSIGN  SRC1  DST  ----


DST = SRC1


数组存取


6


STOA  VALUE  ARRAY  OFF


ARRAY[OFF] = VALUE


7


GETA ARRAY  OFF  VALUE


VALUE = ARRAY[OFF]


函数调用


8


CALL  SRC1  ----  ----


调用函数


逻辑判断跳转


9


BNE  SRC1  SRC2  LABLE


SRC1 != SRC2则跳转到LABEL


10


BEQ  SRC1  SRC2  LABLE


SRC1 == SRC2则跳转到LABEL


11


BLE  SRC1  SRC2  LABLE


SRC1 <= SRC2则跳转到LABEL


12


BLT  SRC1  SRC2  LABLE


SRC1 < SRC2则跳转到LABEL


13


BGE  SRC1  SRC2  LABLE


SRC1 >= SRC2则跳转到LABEL


14


BGT  SRC1  SRC2  LABLE


SRC1 > SRC2则跳转到LABEL


无条件跳转


15


GOTO  LABLE  ----   ----


跳转到LABEL


传参数


16


PARA  SRC  DST     ----


把SRC作为参数传给函数DST


函数返回


17


RETURN  DST  ----   ----


DST作为返回值返回


18


RETURN  ----   ----   ----


无返回值函数返回


函数结束


19


END  ----   ----   ----


函数结束


读写指令


20


PRINTS   DST   ----     ----


写字符串


21


PRINTC  DST   ----     ----


写字符


22


PRINTF  DST   ----     ----


写整型


23


SCANC  DST   ----     ----


读字符


24


SCANF  DST   ----     ----


读整型

4.代码生成

目标代码生成是编译的最后阶段,将编译器此前生成的中间代码和符号表以及其他相关信息作为输入,输出与源程序语义等价的目标程序代码。

从四元式到目标代码的生成需要结合符号表,因为需要明确变量的作用域,检验是否满足语义规则等信息。对于跳转和返回,需要结合自己的运行栈设计结合以及符号表中存储的信息进行必要的运行栈加减操作。

具体实现举例:

ADD a b res

如果a,b,res都是局部变量,且保存在内存中那么汇编指令生成如下:

其中x表示在符号表中保存的a的相对偏移。y为b的相对偏移,z为res的相对偏移。

lw $s1,x($fp);

lw $s2,y($fp);

add $s1,$s2,$s3;

sw $s3,z($fp);

如果a,b,res分别分配了$s1,$s2,$s3寄存器,那么汇编指令如下:

add $s1,$s2,$s3;

如果a,b,res中有任何是全局变量,那么应当从相对于全局的位置中获取或保存。

我的运行栈设计如下:

5.错误处理

由于错误处理要求,不遗漏错误且不能因为局部错误影响后续正确代码的判断。我处理的方式是,发生错误如遗漏分号,当做有分号已读取继续后面的分析。这样不会跳过太多的代码而导致漏诊。

6.我的编译器涉及到的MIPS指令

 


编号


指令


例子


例子说明


1


j


j main


跳转到main


2


subi


subi $sp, $sp, 4


$sp = $sp – 4

这个指令用于获取一段栈空间


3


sw


sw $ra, 0($sp)


把寄存器$ra中的值存到地址0($sp)


4


bnez


bnez $t1, label


如果$t1 != 0, 则跳转到label


5


beqz


beqz $t1, label


如果$t1
== 0, 则跳转到label


6


bgez


bgez $t1, label


如果$t1 >= 0, 则跳转到label


7


bgtz


bgtz $t1, label


如果$t1
> 0, 则跳转到label


8


blez


blez $t1, label


如果$t1 <= 0, 则跳转到label


9


bltz


bltz $t1, label


如果$t1
< 0, 则跳转到label


10


li


li $t0, 1


$t0 = 1


11


add


add $t0, $t1, $t2


$t0 = $t1 + $t2


12


lw


lw $t0, 0($sp)


将地址0($sp)中的值加载到$t0


13


sub


sub $t0, $t1, $t2


$t0 = $t1 - $t2


14


la


la $t0, addr


把地址addr加载到$t0


15


sll


sll $t0, 2


$t0 = $t0 << 2


16


addi


addi $t0, $t1, 4


$t0 = $t1 + 4


17


jr


jr $ra


跳转到$ra存储的地址处


18


move


move $t0, $t1


$t0 = $t1


19


mult


mult $t0, $t1


(Hi, Lo) = $t0 * $t1

Hi和Lo是存储乘除法指令使用的寄存器


20


mflo


mflo $t0


$t0 = Lo


21


div


div $t0, $t1


Lo = $t0 / $t1

Hi = $t0 % $t1


22


sw


sw $ra, 0($sp)


把$ra的内容存入地址0($sp)


编号


系统调用


说明


1


li $v0, 1

syscall


打印整型值


2


li $v0, 4

syscall


打印字符串


3


li $v0, 5

syscall


读整型值


4


li $v0, 10

syscall


退出


5


li $v0, 11

syscall


打印字符


12


li $v0, 12

syscall


读字符

6.优化不要偷懒,建议还是做一下。是比较辛苦,因为好多同学可能都去玩了~但是,相比较于收获,付出还是值得的。

参考书目:

MIPS-C指令集

高级编译器设计与实现(Steven.S.Muchnick)(中文版)

计算机体系结构—量化研究方法(第5版)

计算机体系结构-量化研究方法(中文第五版)


2


li $v0, 4

syscall


打印字符串


3


li $v0, 5

syscall


读整型值


4


li $v0, 10

syscall


退出


5


li $v0, 11

syscall


打印字符


12


li $v0, 12

syscall


读字符

时间: 2024-10-25 15:37:43

北航编译原理总结 C文法的相关文章

编译原理之形式语言文法分类

高级程序设计语言的三个基本因素: 语法:描述语言成分的构成规则(包括词法规则和语法规则) 语义:描述语法成分的含义 语用:描述语法成分的使用方法 形式语言理论(formal language theory)是用数学方法研究自然语言(如英语)和人工语言(如程序设计语言)的产生方式.一般性质和规则的理论.形式语言是模拟这些语言的一类数学语言,它采用数学符号,按照严格的语法规则构成.从广义上说,形式语言是符号取自某个字母表的字符串的集合.如同自然语言具有语法规则一样,形式语言也是由形式文法生成的.一个

编译原理LL(1)文法

从左向右扫描输入,然后产生最左推导(就是每次都把最左边的非终结字符用产生式代替). (一)First集合 比如有产生式 A-> + T | - P , 当我们读到串为 +开头的时候,我们可以很直接地判断选择 A-> + T 这个生成式:串为- 开头的时候,选择 A-> - P 这个生成式.但如果文法是类似于A →T | P 这样的都以非终结字符开头的呢?一眼就很难判断的,我们就需要知道,T 是怎么展开的,如果 T -> a |b ,P->c|d , 那当串以a或b开头的时候,

编译原理之理解文法和语言

简介 一个程序设计语言是一个记号系统,如同自然语言一样,它的完整定义应包括语法和语义两个方面.所谓一个语言的语法是指一组规则,用它可以形成和产生一个合适的程序.目前广泛使用的手段是上下文无关文法,即用上下文无关文法作为程序设计语言语法的描述工具.语法只是定义什么样的符号序列是合法的,与这些符号的含义毫无关系,比如对于一个Pascal程序来说,一个上下文无关文法可以定义符号串A:=B+C是一个合乎语法的赋值语句,而A:=B+就不是.但是,如果B是实型的,而C是布尔型的,或者B.C中任何一个变量没有

编译原理——算符优先分析文法(附源代码)

算符优先分析文法 一.写在前面 算符优先分析文法是一种工具,在编译的过程中,隶属于语法分析环节,却又与中间代码的生成息息相关,编译可以分为五个阶段:词法分析.语法分析.语义分析(中间代码的生成).代码优化.目标代码生成.语法分析是指:在词法分析基础上,将单词符号串转化为语法单位(语法范畴)(短语.子句.句子.程序段.程序),并确定整个输入串是否构成语法上正确的程序.也就是说语法分析是检验输入串的语法是否正确,注意这里的语法正确,只是简单地符合自己定义的规范,而不能检测出运行时错误,比如"X/0&

编译原理123

1.编译原理就是什么? 个人理解,编译就是像翻译一样,将一种语言翻译成另一种语言,编译就是将高级语言或汇编语言翻译成电脑能识别的二进制机器语言,编译原理就是讲解这 个编译的过程.内容包括语言和文法.词法分析.语法分析.语法制导翻译.中间代码生成.存储管理.代码优化和目标代码生成. 编译原理是计算机专业设置的一门重要的专业课程.虽然只有少数人从事编译方面的工作,但是这门课在理论.技术.方法上都对学生提供了系统而有效的训练,有 利于提高软件人员的素质和能力. 2.学习编译原理有什么好处? 学习编译原

编译原理的理解

编译原理内容包括语言和文法.词法分析.语法分析.语法制导翻译.中间代码生成.存储管理.代码优化和目标代码生成.大一点的应用可以做到一定的技术先进性,从而让你在本行业站稳脚跟:分析和分解用户输入的SQL语句,理解是否有害和是否有SQL注入等.如果不学,对于不是不是本行业的人来说就没损失,如果是本行业的专业人士,不学只会令自己的编译理解更慢.

编译原理基础概念介绍

关于编译原理 语法树 句柄 简单短语 短语 的区分,通过两个例子来理解概念以及方法: 例子1——语法树 S -> a|b|(T)  T -> TdS|S Vt={a,b,d,(,)}.Vn={S,T},S是开始符 句型(Sd(T)db)是S的一个推导,其中___是句柄;____是最左素短语:____是该句型的直接短语,_____是短语.     素短语的概念:它是一个递归的定义,至少含有一个终结符,并且除它自身之外不再含任何更小的素短语,所谓最左素短语就是处于句型最左边的素短语的短语.而一个算

python实现算术表达式的词法语法语义分析(编译原理应用)

本学期编译原理的一个大作业,我的选题是算术表达式的词法语法语义分析,当时由于学得比较渣,只用了递归下降的方法进行了分析. 首先,用户输入算术表达式,其中算术表达式可以包含基本运算符,括号,数字,以及用户自定义变量. 词法分析,检查单词变量是否正确:语法分析,检查算术表达式语法是否正确并输出生成语法树:语义分析,输出四元表达式. 最终效果图: 例如输入: 词法分析结果: 语法分析结果: 语义分析结果: 算术表达式的组成语法如下: 无符号整数 = 〈数字〉{〈数字〉} 〈标识符〉= 〈字母〉{〈字母

编译原理随笔

最近初步接触到了编译原理这门课程,通过老师提供的一些链接,以及课上的知识,对这门课有了一些认识与了解. 编译原理旨在介绍编译程序构造的一般原理和基本方法.内容包括语言和文法.词法分析.语法分析.语法制导翻译.中间代码生成.存储管理.代码优化和目标代码生成.从源语言提取需要的信息:把源语言翻译成目标语言:自动生成满足一定规范的文本... 学习编译原理可以更加容易的理解在一个语言种哪些写法是等价的,哪些是有差异的可以更加客观的比较不同语言的差异,更不容易被某个特定语言的宣扬者忽悠,学习新的语言是效率