类C语言编译器demo。
定义的类C语言支持基本数据类型、类型定义、I/O、if语句、while循环、for循环、结构体、函数、注释等多种元素。
编译器实现了词法分析器、递归下降法语法分析器、LL(1)语法分析器、语义分析等功能。
输入文件:F:\mySource.bxc
输出文件:F:\LineList.txt 和 F:\TokenList.txt
实验采用的BXC语言为自行定义的模型语言,它是一种类C的高级程序设计语言。BXC语言的数据结构比较丰富,除了整型、字符型等简单数据类型外,还有数组、结构体等结构数据类型,函数不支持嵌套定义,但允许递归调用。事实上,BXC语言基本上包含了高级程序设计语言的所有常用的成分。
BXC语言的一个特点为主函数必须是程序的最后一个函数,并且不能缺省。可以改进的方向有函数不必带返回值,检测return语句使用的正确性,等等。
类型声明:typedef TypeName int ;
数组声明:int[10] VarName ;
结构体声明:struct StructName {
int VarName1 ; /*注释:变量声明*/
char VarName2 ;
} StructVarName ;
函数声明:int main () { return 0 ; } /* 返回语句 */
输出语句:cout << Expression ; 输入语句:cin >> VarName ;
字符表 a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|
A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|
0|1|2|3|4|5|6|7|8|9|
+|-|*|/|<|>|=|(|)|[|]|{|}|.|,|;|'|EOF|空白
注:符号EOF在程序中由符号#实现;英文字母区分大小写;保留字只能由小写字母组成。
枚举类型LexType中的ENDFILE、ERROR和END_POP不是终极符。
LL(1)文法在此文法上加以改进,把几乎所有不为产生式首个文法符号的终极符ID提取出来,单独作为一条产生式,目的是准确填入相应语法树节点的语义信息(name字段)。
进行递归下降法和LL(1)方法的语法分析,都需要用到Predict集,按照BXC的文法求得Predict集
分析矩阵的作用是帮助当前非终极符和当前输入符确定应该选择的语法规则,它的行对应非终极符,列对应终极符,矩阵的值有两种:一种是产生式编号,另外一种是错误编号。由于时间紧促,故BXC语言的LL(1)分析表的值只包含产生式编号信息。
LL(1)分析矩阵在程序中的实现方式为一个二维数组。行为非终极符,列为终极符 + #。
注:# 在程序中由枚举类型LexType中的ENDFILE实现。
为方便理解,语法树节点的数据结构借鉴了SNL(Small Nested Language)语言编译器。
下表为BXC语言语法树节点的数据结构。
child |
sibling |
lineno |
nodekind |
kind |
idnum |
name |
table |
attr |
||||||
0 |
1 |
2 |
3 |
dec |
stmt |
exp |
type_name |
… |
||||||
注:Java中没有共用体,故成员kind以及attr使用类结构。attr的内部成员采用了内部类结构。
1) 分层建立符号表,使各分程序的符号表项连续地排列在一起,不会被内层分程序的符号表所割裂。
2) 建立一个分程序表,用来记录各层分程序符号表的起始位置。
3) 由于不允许函数的嵌套声明,故符号表只能有两层。
4) 分层打印符号表。对每一个结构体类型的声明,都需要打印域表。
分程序表用scope栈实现。scope栈的声明如下:
/* 使用scope栈的局部符号表方法中所用到的scope栈 */
public ArrayList<ArrayList<SymbTable>> scope = new ArrayList<ArrayList<SymbTable>>();
scope栈相当于一个数组,数组的元素是ArrayList<SymbTable>,也就是符号表。
符号表采用顺序表的数据结构存储,节点是SymbTable类的对象。
注:需要在语义分析开始前,检测语法树的最后一个函数是否为main函数。