@sangyy
2015-12-27T10:41:32.000000Z
字数 23505
阅读 4841
compiler2015
安装 Linux,LLVM 3.6.0,GCC,使用GCC/Clang来编译C程序,并运行编译生成的可执行程序。初步熟悉Makefile,了解编译器的过程。
C1语言的特征参见附2。用C1语言编写3个以上的程序,其中至少有解决数组排序问题的程序,并使用GCC/Clang编译。
* 注:编译时不需要加任何参数,将C1源文件当成C文件编译即可。
* 注:为了方便调试,可增加scanf、printf等指令。最后务必输出结果,方便我批改
* 注:请在README中说明应执行哪些指令以验证你程序的正确性,并简要描述程序的功能。在doc/中较详细地说明程序的设计思路、如何克服C1简陋的语法造成的困难。
阅读教材11.1节和LLVM的资料,了解预处理、编译、连接的含义和命令(写入doc/中),并使用。
文件名 | 功能 |
---|---|
README | 提交内容说明,包含如何编译运行所提交的内容等 |
Makefile | |
src/ | 存放源程序文件(*.h, *.c) |
doc/ | 实验设计文档 |
bin/ | 帮助快速编译和运行实验内容的shell脚本文件,不要把编译出的可执行文件放进去! 不要把编译出的可执行文件放进去! 不要把编译出的可执行文件放进去! |
test/ | 存放测试程序 |
src/
下放3个以上的C1程序。这些程序都要能输出结果(可以违反C1语法,添加printf),好体现你程序的正确性。
bin/
下可以写一个shell脚本,快速运行并编译运行所有程序。(但不强制要求)
Makefile
写一个自动编译所有程序的神奇文件,见教程链接。(不强制要求,但是迟早要用的)
README
里给出编译和运行程序的指令,如果写了shell脚本,就说明如何调用该脚本。还要简单地注明各个程序的功能。
doc/
下详细说明各程序的设计思路,如何克服C1语法的简陋性(如没有for循环,函数不能传参数)。另外要回答第3题,介绍预处理、编译、链接的含义,以及LLVM的相关指令。
目测用不到test/
从这次实验开始,我们会学习LLVM提供的教程Kaleidoscope,结合Flex和Bison进行一些扩展。
此外将开始阅读Clang源代码,以增加对一个真实编译器的了解。
我们的目的是使用LLVM、Flex、Bison从头构造一个C1的编译器。下面列出的实验标题可能随课程进度有所改动。
gettok()
如何向调用者传递token类别、token语义值(数字值、变量名)/*comment*/
这样的多行注释<digit, 123>
;检测到运算符+,就输出<op, +>
;不需要输出注释),请提交修改后的代码阅读Flex manual和一个简单的表达式语言的词法分析例子,了解Flex的输入词法规范文件的格式,以及Flex生成的词法分析器的接口形式和实现。你可以从flex-2.5.35.tar.gz的examples目录下获得更多使用Flex的例子。
提示
可以参考如下的代码和Makefile
//include/tok.h如下:
...
//声明token编号略
//声明语义值如下
extern std::string* var_val;
extern int num_val;
...
//src/tok.cpp
...
std::string* var_val;
int num_val;
...
//config/lexer.l
%{
...
#include "tok.h"
%}
...
//src/main.cpp
...
#include "tok.hh"
extern FILE *yyin; // file pointer used in lexer
extern int yylex(); // lexer function provided by lexer.l
...
定义print_token
...
int main() {
FILE *fp = fopen("xxx.c1","r");
打开文件略
yyin = fp;
while (int token = yylex())
print_token(token, stdout);
return 0;
}
Makefile(教程链接):
# This Makefile requires GNU make.
# replace indent with tab.
SHELL = /bin/sh
CC = g++
LEX = flex
PROGRAM = c1c
BIN = bin
SRC = src
INC = include
CONF = config
CFLAGS = -I ${INC}
LFLAGS =
YFLAGS =
default:: ${BIN}/c1c
${BIN}/c1c: ${BIN}/lexer.o ${BIN}/main.o ${BIN}/tok.o
${CC} ${CFLAGS} -o $@ $^
${SRC}/lexer.cpp: ${CONF}/lexer.l
@mkdir -p ${SRC}
${LEX} ${LFLAGS} -o $@ $<
${BIN}/lexer.o: ${SRC}/lexer.cpp ${INC}/tok.h
@mkdir -p ${BIN}
${CC} ${CFLAGS} -c -o $@ $<
${BIN}/tok.o: ${SRC}/tok.cpp
@mkdir -p ${BIN}
${CC} ${CFLAGS} -c -o $@ $<
${BIN}/main.o: ${SRC}/main.cpp ${INC}/tok.h
@mkdir -p ${BIN}
${CC} ${CFLAGS} -c -o $@ $<
.PHONY: clean
clean:
-rm -f ${BIN}/*.o ${SRC}/lexer.cpp
建议使用IDE阅读Clang代码,可以使用Code Blocks, Code Lite, Eclipse CDT, NetBeans, Dev C++等。或者用Vim+ctags+cscope
下载3.6.0版本的LLVM和Clang源码,阅读Clang/lib/Lex/和Clang/include/clang/Lex下的源码。
文件名 | 功能 |
---|---|
README | 提交内容说明,包含如何编译运行所提交的内容等 |
Makefile | |
src/ | 存放源程序文件(*.h, *.cpp) |
config/ | 存放lex和yacc文件 |
include/ | 头文件 |
doc/ | 实验设计文档 |
bin/ | 帮助快速编译和运行实验内容的shell脚本文件 |
test/ | 存放测试程序 |
config/
:lexer.l
src/
:main.cpp, tok.cpp等,不要放lex自动生成的cpp文件。还有修改后的Kaleidoscope编译器。
include/
:tok.h
bin/
:可以写一个shell脚本,快速运行并编译运行所有程序。(但不强制要求)
Makefile
README
里给出编译和运行程序的指令,简单注明各个程序的功能。
doc/
:详细说明程序的设计思路。另外要回答1,2,4题,其中第2题要简介flex词法文件的格式,以及生成的词法分析器的接口。第4题的部分内容已经在讨论课讨论过,但依然要写入文档。此外如果你也可以一些有意思的发现,助教会酌情加分。
test/
:P1的3个C1文件。
注:提交前执行make clean,清空bin/里生成的.o和可执行文件,以及flex自动生成生成的cpp文件。
阅读LLVM教程的第二章、第五章和第七章扩展if/then/else、for循环和变量的词法、语法部分(可忽略代码生成),文法见附5。了解自顶向下的语法分析过程以及AST的结构和创建方法。可通过git clone git@202.38.75.124:comppub
查看扩展后的代码(在Kaleidoscope-dump
目录下)。回答下列问题:
Error
, ErrorP
, ErrorF
的作用,举例说明它们在语法分析中应用。{...}
或begin...end
?基于你对if/then/else和for循环的理解,扩展Kaleidoscope编译器,使其支持while循环,并在程序结束后输出AST对应的DOT图。
git clone git@202.38.75.124:comppub
, cd Kaleidoscope-dump
这份代码基于第五章教程扩展了AST=>DOTwhile Expr Expr
文件名 | 功能 |
---|---|
README | 提交内容说明,包含如何编译运行所提交的内容等 |
Makefile | |
src/ | 存放源程序文件(*.h, *.cpp) |
include/ | 头文件 |
doc/ | 实验设计文档 |
bin/ | 帮助快速编译和运行实验内容的shell脚本文件 |
test/ | 存放测试程序 |
src/
:toy.cpp, dumpdot.cpp
include/
:ast.h, dumpdot.h
bin/
:可以写一个shell脚本,快速运行并编译运行所有程序(但不强制要求)
Makefile
README
里给出编译和运行程序的指令,简单注明各个程序的功能。
doc/
:详细说明程序的设计思路。另外要回答第1题。
test/
:目测用不到。
本实验旨在熟悉Bison,并利用Flex和Bison来为C1语言构造词法分析器和语法分析器,所构造的分析器能识别几种错误并从这些错误中恢复,还能输出在自下而上分析时归约所使用的产生式。
git clone git@202.38.75.124:comppub
,学习comppub库中bison-examples目录下的各个例子。C1语言的特征参见附2。在编写C1语言的Lex词法规范文件和Yacc文法规范文件时,可参考ANSI-C语言的lex词法规范和Yacc文法规范。
该项实验的要求:
const int id = exp;
误写成const id = exp;
,应报warninga+b-c
误写为a b-c
),请设计一种合理的恢复方式,且需要报errordoc/
:包含一个说明提交内容以及完成/未完成/参考/问题等情况的文档,以及4.1中对Bison的理解、对各个问题的回答
test/
:包含至少5个C1程序(要求说明程序的特征)作为测试程序
bin/
:包含P4的编译运行脚本文件。
src/
:源文件
config/
:flex和bison源文件
注:comppub/ast2ast已于2015-11-05更新
阅读comppub/asgn2ast
源码,了解AST的数据结构、构造和使用方法
include/node.hh
,了解各node的成员变量config/parser.y
,以input -> empty | input line
为例,说明其AST的结构和构建过程,解释$$
、$1
、$2
、@$
等变量的含义(写入文档)src/dumpdot.cc
的BinaryExpNode::dumpdot
和InputNode::dumpdot
函数,学习如何输出这两类数据结构的DOT图config/lexer.l
的YY_USER_ACTION
以及config/parser.y
、src/node.cc
、include/node.hh
中的setLoc
,了解如何记录AST结点在源程序中的位置信息,思考该方法的局限性/*comment*/
这样的多行注释,在有多行注释的前提下依然能正确保存位置信息。asgn2ast
的dumpdot.cc
输出结点的DOT图doc/
:回答5.1的第2问和5.2的第1问,并说明你代码的设计思路
src/
:源文件
include/
:头文件
config/
:lex和yacc文件
test/
:至少3个测试文件,要体现出error和warning
bin/
:脚本文件
本实验通过阅读clang源代码中位于lib
、include
、tools
子目录中的部分源程序文件,来理解clang的语法分析和静态语义检查的实现机制
tools
子目录:其中的各个子目录分别提供独立的可执行工具,如: driver
子目录:编译、连接得到clang
或clang++
可执行程序,用该可执行程序可以编译C/C++
程序;clang-check
子目录:编译、连接得到clang-check
可执行程序,用该可执行程序可以对源程序进行检查参照该博客使用ninja编译LLVM和Clang,注意使用与LLVM3.6.0配套的代码。一般来说编译用时在20分钟以内。
本节以编译后的LLVM为根目录进行说明
tools/clang/lib/Parse/ParseStmt.cpp
ParseIfStatement
函数,大致说明其工作流程,至少说明其中3处错误处理的手段ParseWhileStatement
函数,结合ParseBreakStatement
函数,说明clang是如何处理作用域(scope)的?其中WhileScope
和InnerScope
有何区别?scope在释放时利用ScopeCache减少了malloc的次数,请加以解释。你可能还要顺着ParseScope
类的声明看更多代码tools/clang/lib/Parse/ParseExpr.cpp
的ParseExpression
和ParseRHSOfBinaryExpression
函数,大致说明clang处理二元运算的原理,重点说明运算优先级和结合性体现在哪里。(不需要说明对错误的处理)clang的静态分析器(static analyzer)由两部分构成:一个静态分析器引擎(static analysis engine)和一些static checker。其中引擎提供CheckerVisitor接口,checker按照接口规范编写各类静态检验。
tools/clang/lib/staticAnalyser/README.txt
,说明其工作原理在命令行中输入clang -cc1 -analyzer-checker-help
,了解有哪些checker可用(可能下列的部分Checker和你电脑上的Checker名不一样)。创建一个测试程序cfg.c
,输入下列代码。 输入指令clang -cc1 -analyze -analyzer-checker=debug.DumpCFG test.c
,绘制它对应的CFG图(提交dot或png文件)。
int main() {
int a, n, ans, i;
if (a >= 0)
for (i = 0; i<n; i++)
ans = ans + i;
else
while (a < 0)
a = a + n;
return 0;
}
写一个利用递归计算Fibonacci数的程序,在main函数中调用函数求第5个Fibonacci数,代码保存为fib.c
。 输入指令clang -cc1 -analyze -analyzer-checker=debug.DumpCalls fib.c
,在文档中解释输出。
创建测试程序unreachable.c
,输入下列代码。 输入指令clang -cc1 -analyze -analyzer-checker=alpha.deadcode.UnreachableCode fib.c
。结合tools/clang/lib/StaticAnalyzer/Checker/UnreachableCodeChecker.cpp
大致解释其工作原理。提供一个文件,它包含不可达代码,但是Clang的UnreachableCode Checker无法检测出它。
int main() {
int n = 0;
if (1 > n) return 0;
n = 1;
}
tools/clang/lib/
目录下的代码并不能直接运行,需要结合tools/clang/tools/
目录下的代码形成可执行文件。我们来看两个例子。
tools/clang/tools/clang-check
中的代码,结合6.1节编译后生成的build/tools/clang/StaticAnalyzer/Checkers/Checkers.inc
(如果你编译失败了,可以用编译好的Checkers.inc),说明clang是如何加载各个Checker并在代码上进行检验的。(以DumpCalls和UnreachableCode为例)tools/clang/tools/driver/
中的文件会生成clang,main函数在driver.cpp
中,请大致说明它如何调用负责编译的函数?如何处理cc1参数?(可以忽略和其它参数相关的代码)include/clang/StaticAnalyzer/Core/CheckerRegistry.h
和examples/analyzer-plugin/
设计一个Checker插件(自行设计功能)。如果你设计的功能较复杂,不妨参考lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
lib/StaticAnalyzer/Checker/MallocChecker.cpp
,解释其原理doc/
:回答上面提到的问题
test/
:放入cfg.c
, fib.c
, unreachable.c
和DumpCFG一题要求的DOT文件或PNG图
注意:P7工作量比较大,请及早准备
阅读LLVM教程的第三章、第五章和第七章,学习LLVM生成IR的方法并回答下列问题(你可以使用compub/llvm3.6.0中的网页镜像和复制好的代码):
Module
、IRBuilder<>
的作用;结合教程7.7节,说明: Kaleidoscope的符号表使用了什么结构、如何处理不同作用域的变量重名问题?ThenBB = Builder.GetInsertBlock()
。提示:
clang -cc1 test.c -emit-llvm
得到IR文件test.ll,再用llc -march=cpp test.ll
得到test.cpp,这个cpp文件会详细地展示如何使用LLVM的API生成test.ll(当然其中有许多愚蠢的代码,你可以提取有用的部分放入自己的编译器中)你可以考虑如下扩展(当然扩展后必须要生成对应的LLVM IR)
C1语言是一个类C的小型实验语言。在数据类型上,只支持整数类型和一维整型数组类型。一个C1语言程序文件包含若干常量定义、全局变量声明、无参函数定义,其中必须包含一个名为main的函数,作为程序执行的主函数。函数体中可以有常量定义、变量声明和语句。C1语言有赋值语句、函数调用语句、复合语句、条件语句、循环语句和空语句。
由于上面这些语言概念已为大家熟知,因此不再进行语义解释。下面用习题3.7 所介绍的扩展方式来给C1语言的文法。
CompUnit → [ CompUnit ] (Decl | FuncDef ) CompUnit代表一个C1程序文件,其中必须包含一个名为main的函数定义FuncDef 这里假设一个C1程序只存放在一个C1程序文件中 Decl → ConstDecl | VarDecl ConstDecl → const int ConstDef {, ConstDef} ';' ConstDef → ident '=' Exp | ident '[' [ Exp ] ']' '=' '{' Exp { ',' Exp } '}' ConstDecl常量声明:可以声明一个或多个整型常量、整型常量数组,如: const int a[]={2,3}; 声明了长度为2的常量数组a,a[0]、a[1]的值分别为2和3; 数组在声明时若未指明长度,则长度为初始化表达式中值的个数。 在程序中,不能对常量重新赋值。 [注1] VarDecl → int Var{, Var}';' Var → ident | ident '[' Exp ']' | ident '=' Exp | ident '[' [ Exp ] ']' '=' '{' Exp { ',' Exp } '}' VarDecl 变量声明:可以声明整型和一维整型数组变量。 [注2] FuncDef → void ident '(' ')' Block FuncDef 函数定义:无参函数,且必须有一个名字为main 的函数 Block →'{' { BlockItem } '}' BlockItem → Decl | Stmt Block语句块:表示函数体或者复合语句,其中可以包含0个或多个声明或语句 Stmt → LVal '=' Exp ';' | ident '(' ')' ';' | Block | if '( Cond ')' Stmt [else Stmt ] | while '(' Cond ')' Stmt | ';' LVal → ident| ident '[' Exp ']' Cond → Exp RelOp Exp RelOp →'==' | '!=' | '<' | '>' | '<=' | '>=' Exp → Exp BinOp Exp | UnaryOp Exp | '(' Exp ')' | LVal | number BinOp → '+' | '−' | '*' | '/' | '%' UnaryOp → '+' | '−' 运算符的优先级和结合性与C语言中的一样
注1:对于ConstDef定义中初始化表达式Exp和数组长度Exp是否能使用之前已经声明的const变量,gcc4.6.3和clang3.3有不同的处理语义,具体举例说明如下(为简便起见,省略编译器的版本号):
const int b=3, c[3]={2+b,3};
const int b=3, c[b];
const int a1[b+3]={2, 2};
注2:对于Var定义中初始化表达式Exp和数组长度Exp是否能使用已经声明的变量,gcc和clang也有不同的处理语义,处理规则与上述的ConstDef中的类似,这里不再赘述。类似地,你需要选择其一作为C1语言的语义约束规定,你需要确保这种选择与上面关于ConstDef中的语义约束规定相一致。
以下内容来自Bison-2.4.1源码中src/tables.h里的注释:
YYTRANSLATE = vector mapping yylex's token numbers into bison's token numbers.
YYTNAME = vector of string-names indexed by bison token number.
YYTOKNUM = vector of yylex token numbers corresponding to entries in YYTNAME.
YYRLINE = vector of line-numbers of all rules. For yydebug printouts.
YYRHS = vector of items of all rules. This is exactly what RITEMS contains. For yydebug and for semantic parser.
YYPRHS[R] = index in YYRHS of first item for rule R.
YYR1[R] = symbol number of symbol that rule R derives.
YYR2[R] = number of symbols composing right hand side of rule R.
YYSTOS[S] = the symbol number of the symbol that leads to state S.
YYDEFACT[S] = default rule to reduce with in state s, when YYTABLE doesn't specify something else to do. Zero means the default is an error.
YYDEFGOTO[I] = default state to go to after a reduction of a rule that generates variable NTOKENS + I, except when YYTABLE specifies something else to do.
YYPACT[S] = index in YYTABLE of the portion describing state S. The lookahead token's type is used to index that portion to find out what to do. If the value in YYTABLE is positive, we shift the token and go to that state. If the value is negative, it is minus a rule number to reduce by. If the value is zero, the default action from YYDEFACT[S] is used.
YYPGOTO[I] = the index in YYTABLE of the portion describing what to do after reducing a rule that derives variable I + NTOKENS. This portion is indexed by the parser state number, S, as of before the text for this nonterminal was read. The value from YYTABLE is the state to go to if the corresponding value in YYCHECK is S.
YYTABLE = a vector filled with portions for different uses, found via YYPACT and YYPGOTO.
YYCHECK = a vector indexed in parallel with YYTABLE. It indicates, in a roundabout way, the bounds of the portion you are trying to examine. Suppose that the portion of YYTABLE starts at index P and the index to be examined within the portion is I. Then if YYCHECK[P+I] != I, I is outside the bounds of what is actually allocated, and the default (from YYDEFACT or YYDEFGOTO) should be used. Otherwise, YYTABLE[P+I] should be used.
YYFINAL = the state number of the termination state.
YYLAST ( = high) the number of the last element of YYTABLE, i.e., sizeof (YYTABLE) - 1.
本压缩包围绕2个小型编程语言:L-expr(简单的表达式语言)、L-asgn(赋值语句序列语言),给出如何利用Flex和Bison构造编译器,其中涉及程序位置跟踪、符号表的管理、抽象语法树的构造、错误信息管理等。
input ::= ε | input line
line ::= EOL | expr EOL
expr ::= NUMBER
| expr PLUS expr # PLUS – ‘+’加号
| expr MINUS expr # MINUS – ‘-’减号
| expr MULT expr # MULT – ‘*’乘号
| expr DIV expr # DIV – ‘/’除号
| MINUS expr # MINUS – ‘-’负号
| expr EXPON expr # EXPON – ‘^’乘幂
| LB expr RB # LB, RB – ‘(’, ’)’ 左右括号
input ::= ε | input line
line ::= EOL
| asgnexp ';' EOL
asgnexp ::= IDENTIFIER ASGN exp # ASGN - ‘=’赋值号
expr ::= NUMBER
| IDENTIFIER # IDENTIFIER – 标识符
| expr PLUS expr # PLUS – ‘+’ 加号
| expr MINUS expr # MINUS – ‘-’减号
| expr MULT expr # MULT – ‘*’乘号
| expr DIV expr # DIV – ‘/’ 除号
| MINUS expr # MINUS – ‘-’负号
| expr EXPON expr # EXPON – ‘^’乘幂
| LB expr RB # LB, RB – ‘(’, ’)’ 左右括号
在L-asgn语言中,标识符代表变量,标识符需要先赋值再使用。如果程序使用了未赋值的变量,则需要给出警告并默认初值为0。
|- README 对本压缩包的说明文档 |- Makefile 用于构造语言的词法、语法分析器并将它们编译成可执行文件 |- run.sh 以./run.sh x y命令启动bin子目录下的x编译器编译test子目录下的y程序 |- config 存放语言的词法和语法文法文件 |- expr.lex L-expr的Flex词法规范。 以-oexpr.lex.c选项执行flex,会根据该文件生成词法分析器expr.lex.c |- expr.y L-expr的Yacc语法规范,它利用Yacc提供的优先级声明来消除文法的二义性。该语法规范内嵌有边分析边对表达式求值的语义动作。 以-b expr -o expr.tab.c选项执行Bison,会根据该文件生成语法分析器expr.tab.c以及词法、语法分析都需使用的符号声明头文件expr.tab.h |- expr1.y L-expr的Yacc语法规范,它通过增加文法非终结符,将文法改写成无二义的文法。可以用Bison处理该文件,生成expr1.tab.h(.c)。该语法规范内嵌有边分析边对表达式求值的语义动作。 |- exprL.y L属性定义举例:L-expr的Yacc语法规范,在规则中嵌入打印行号的语义动作,行号通过全局变量传递。 |- exprL1.y L属性定义举例:L-expr扩展语言略(每行开始增加行号)的Yacc语法规范,在规则中嵌入打印源程序中所给行号的语义动作。 |- midrule.y L属性定义举例:使用$<val>N引用语义值栈中任意位置的语义值,N可以为正整数、0、负整数,<val>为所引用的语义值的类型。 |- asgn.lex L-asgn的Flex词法规范。 |- asgn.y L-asgn的Yacc语法规范。该语法规范内嵌有边分析边对赋值语句求值的语义动作。 |- asgn1.y L-asgn的Yacc语法规范,它与asgn.y的区别在于引入一个表示双目运算符的非终结符op。 |- asgn2ast.y L-asgn的Yacc语法规范。该语法规范内嵌有边分析边构造抽象语法树的语义动作。 |- asgn_err.lex L-asgn的Flex词法规范,它与asgn.lex的区别在于引入统计尚未匹配的左括号数的全局量lparen_num,以便在词法分析的过程中进行括号匹配跟踪。 |- asgn_err.y L-asgn的Yacc语法规范。它与asgn_err.lex协作,支持对不合法的L-asgn程序的分析,提供错误恢复以及错误信息的记录并在分析结束时集中输出。 |- include 存放用户自定义的头文件 |-util.h 存放一些实用的宏、类型声明、函数声明,如动态分配相关的宏定义(如NEW, NEW0);线性表、线性表迭代器的数据结构的定义以及相关接口函数的声明。 |-common.h 编译器需要使用的一些公共的声明和定义,如符号表的结构、错误信息的结构、错误容器(或称错误工厂)的结构、语法树及其结点的结构定义,相关函数的声明等等 |-op.h 保存所处理的运算符及其对应的文本信息的配置文件 |-errcfg.h 保存所处理的错误类别及其对应的提示信息的配置文件 |- src 存放源程序文件 |-list.c 线性表的接口函数的实现(采用链式结构存储)、线性表迭代器Iterator的接口函数的实现 |-symtab.c 符号表的接口函数的实现(采用链地址结构的Hash表存储) |-error.c 错误信息及其管理的接口函数的实现 |-ast.c 抽象语法树相关的接口函数的实现 |- expr.1ex.c, expr.tab.h, expr.tab.c, expr1.tab.c 利用Bison和Flex处理config目录下的expr.lex, expr.y, expr1.y生成的词法、语法分析器的源代码文件。 |- asgn.1ex.c, asgn.tab.h, asgn.tab.c, asgn1.tab.c, asgn2ast.tab.c 利用Bison和Flex处理config目录下的asgn.lex, asgn.y, asgn1.y, asgn2ast.y生成的词法、语法分析器的源代码文件。 |- asgn_err.1ex.c, asgn_err.tab.h, asgn_err.tab.c 利用Bison和Flex处理config目录下的asgn_err.lex, asgn_err.y生成的词法、语法分析器的源代码文件。 |- bin 存放可执行文件 |- expr, expr1, asgn, asgn1, asgn2ast, asgn_err 执行make或者make expr ( 或expr1, asgn, asgn1, asgn2ast, asgn_err)后得到的L-expr、L-asgn语言的编译器的可执行文件 |- test 存放测试程序 |- expr.in 一个合法的L-expr程序 |- asgn.in 一个合法的L-asgn程序 |- asgn_err.in 一个不合法的L-asgn程序
bison-examples中config/asgn.lex和asgn.y给出了如何使生成的分析器能跟踪程序位置的示例。这里简述要点:
%{int yycolumn = 1;
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno; \
yylloc.first_column = yycolumn; yylloc.last_column = yycolumn+yyleng-1; \
yycolumn += yyleng;
%}
%option yylineno
@$.first_line
、@1.last_column
来访问位置信息,其中@$
表示产生式左部符号(LHS, left-hand side)对应的位置信息,@1
表示产生式右部符号(RHS, right-hand side) 对应的位置信息。在bison-examples中使用预编译指令灵活处理错误类别以及操作符等可配置的信息,并根据实际的配置信息生成相关类型和全局变量等。这里以操作符为例,简述处理方法:
opxx(PLUS, " + " )
在include/common.h中有如下代码段:
enum {
#define opxx(a, b) OP_##a,
#include "op.h"
OPLAST
};
该代码段将由op.h里的配置信息产生枚举常量,如PLUS对应的为OP_PLUS
在src/ast.c中有如下代码段:
char *opname[]={
#undef opxx
#define opxx(a, b) b,
#include "op.h"
"Undefined Op"
};
该代码段将由op.h里的配置信息来初始化操作符名称数组opname的取值。
#define NEW(p) ((p) = malloc(sizeof *(p)))
#define NEW0(p) memset(NEW(p), 0, sizeof *(p))
若要构建结点类型为A的结点,则可以:
A *p, *q;
NEW(p); // 创建A类型的结点,使p指向A类型的结点
NEW0(q); //创建A类型的结点并将所创建的结点清0,使q指向该结点
在.y文件中,翻译规则可以内嵌语义动作代码,如config/exprL.y中有如下片段:
input : …
| input
{ lineno ++;
printf("Line %d:\t", lineno);
}
line
每个用一对花括号括起的内嵌语义动作也视为一个文法符号(匿名符号),故上例中右部input {…}
line共计3个文法符号,line对应的语义值通过$3
引用。
可以在翻译规则的内嵌语义动作代码中设置该文法符号对应的语义值,如config/exprL1.y中有如下片段:
line : ...
| NUMBER { ...
$<val>lineno = $1;
//$<val>$ = $1;
}[lineno]
exp EOL { ...
printf("Line %d: %g\n", (int)$<val>lineno, $3);
...
}
其中:
$<val>lineno = $1;
是在设置lineno文法符号的语义值,<val>
是lineno文法符号的语义值类型。$<val>lineno
也可以换成$<val>$
。后者可以在未指定语义动作对应的文法符号名时引用该语义值单元。$<val>lineno
是在引用第2个文法符号(即lineno)的语义值,这里$<val>lineno
可以换成$<val>2
。可以在一个翻译规则的语义动作代码中使用存储在栈中任意固定相对位置的语义值,如config/midrule.y中有如下片段:
exp: a_1 a_2 { $<val>$ = 3; } { $<val>$ = $<val>3 + 1; } a_5
sum_of_the_five_previous_values
{
USE (($1, $2, $<foo>3, $<foo>4, $5));
printf ("%d\n", $6);
}
sum_of_the_five_previous_values:
{
$$ = $<val>0 + $<val>-1 + $<val>-2 + $<val>-3 + $<val>-4;
}
其中,sum_of_the_five_previous_values文法符号对应的规则中$<val>0
、 $<val>-1
、$<val>-2
、$<val>-3
、$<val>-4
分别表示栈中a_5
、{ $<val>$ = $<val>3 + 1; }
、{ $<val>$ = 3; }
、a_2
、a_1
文法符号的语义值。
identifierexpr ::= identifier | identifier '(' expression* ')' numberexpr ::= number parenexpr ::= '(' expression ')' binop ::= '=' | '+' | '-' | '*' | '<' binoprhs ::= (binop primary)* expression ::= primary binoprhs prototype ::= id '(' id* ')' definition ::= 'def' prototype expression external ::= 'extern' prototype toplevelexpr ::= expression top ::= definition | external | toplevelexpr | ';' primary ::= identifierexpr |numberexpr | parenexpr | ifexpr | forexpr // ch5 | varexpr // ch7 // ch5 ifexpr ::= 'if' expression 'then' expression 'else' expression forexpr ::= 'for' identifier '=' expr ',' expr (',' expr)? 'in' expression // ch7 varexpr ::= 'var' identifier ('=' expression)? (',' identifier ('=' expression)?)* 'in' expression //extend whileexpr ::= 'while' expression expression