基于C语言的词法分析器构建
C 解析器
描述C–语言的文法(grammar)规则如下:
<程序>∷=void main() <语句块>
<语句块>∷={<语句串>}
<语句串>∷=<语句串><语句>|
<语句>∷=<赋值语句>|<输入语句>|<输出语句>
<赋值语句>∷=<标识符> = E;
<标识符>∷=<字母>|_|<标识符><字母>|<标识符>_|<标识符><数字>
<整数>∷=<数字>|<非0数字><整数串><数字>|<非0数字><数字>
<整数串>∷=<整数串><数字>|<数字>
<非0数字>∷=1|2|3|…|9
<数字>∷=0|<非0数字>
<字母>∷=a|b|c|…|z
E∷=T|E+T
T∷=F|T*F
F∷= (E)|<标识符>|整数
<输入语句>∷=cin>><标识符>;
<输出语句>∷=cout<<<标识符>;
+-------+ +--------+
-- source code --> | lexer | --> token stream --> | parser | --> assembly+-------+ +--------+
构建词法分析器有两大类方法
- 手写的语法分析器,以及手写的词法分析器
- 清晰,但开发周期长
- 用lex/yacc或类似的生成器构建
- 可快速构建原型系统或小语言
编译原理课程手写的词法分析器例子
环境
MAC下自带了Flex 和 Bosin 的编译环境
$ gcc -vConfigured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/c++/4.2.1
Apple LLVM version 10.0.1 (clang-1001.0.46.3)
Target: x86_64-apple-darwin18.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
其他的操作系统下需要自行配置环境(gcc, lex/flex, yacc/bison)
- Lex - A Lexical Analyzer Generator
- Yacc: Yet Another Compiler-Compiler
- Flex, A fast scanner generator
- Bison, The YACC-compatible Parser Generator
关于lex,yacc,flex,bison,文档以及一些小例子可以查看:文档
项目目录
.
├── Makefile
├── README.md
├── doc.md
├── src
│ ├── analyzer.tab.c
│ ├── analyzer.tab.h
│ ├── analyzer.y
│ ├── node.h
│ ├── parser
│ ├── scanner.c
│ ├── scanner.l
│ ├── testPair
│ └── testPair.l
└── test├── cacl.txt├── in.txt├── input.c--└── square.pl0
其中 Makefile
如下:
all:gcc -g -o src/parser src/*.c./src/parser < test/input.c--bison:cd src/ && bison -d analyzer.ylex:flex -o src/scanner.c src/scanner.lclean:rm src/*.cpair:flex -o src/testPair.c src/testPair.lgcc -g -o src/testPair src/testPair.c -ll./src/testPair < test/input.c--
阮一峰的Makefile教程
Makefile
部分解释如下:
- 对lex语言描述的词法文件
scanner.l
使用flex
转换得到c语言描述的词法文件, 默认输出为lex.yy.c
我们重命名为:-o scanner.c
- 对bison语言描述的语法文件
analyzer.y
进行转换得到c语言描述的语法文件, 参数-d
将.h
文件和.c
文件分离, 默认输出为analyzer.tab.c
和analyzer.tab.h
, 将analyzer.tab.h
include进lex
文件以进行下一步的联合编译 - 对
1
,2
中生成文件进行联合编译, 输出可执行程序parser
, 对输入文件input.c--
进行语法分析. - 若lex文件没有main函数和yywrap,则在MAC上编译需添加
-ll
参数, windows下用-lfl
参数, 意思都是将flex库包含进去
命令解释:
$ make clean // 清除所有生成的源文件
$ make bison // 使用bison生成语法解释源程序
$ make lex // 使用flex生成词法解释源程序
$ make // 编译生成目标解释程序$ make clean // 清除所有生成的源文件
$ make pair // 测试词法的匹配
初步实验: 对PL/0语言进行词法分析
– 来自于官方文档 –
待分析文件: square.pl0
VAR x, squ;PRECEDURE square;
BEGINsqu:= x * x
END;BEGINx := 1;WHILE x <= 10 DOBEGINCALL square;1 squ;x := x + 1END
END.
scanner.l
符号识别分类:
/* scanner for a toy Pascal-like language */%{/* need this for the call to atof() below */
#include <math.h>
%}DIGIT [0-9]
ID [a-z][a-z0-9]*%%{DIGIT}+ {printf( "An integer: %s (%d)\n", yytext,atoi( yytext ) );}{DIGIT}+"."{DIGIT}* {printf( "A float: %s (%g)\n", yytext,atof( yytext ) );}if|then|begin|end|procedure|function {printf( "A keyword: %s\n", yytext );}{ID} printf( "An identifier: %s\n", yytext );"+"|"-"|"*"|"/" printf( "An operator: %s\n", yytext );"{"[^}\n]*"}" /* eat up one-line comments */[ \t\n]+ /* eat up whitespace */. printf( "Unrecognized character: %s\n", yytext );%%main( argc, argv )
int argc;
char **argv;{++argv, --argc; /* skip over program name */if ( argc > 0 )yyin = fopen( argv[0], "r" );elseyyin = stdin;yylex();}
%%
执行命令:
flex -o src/scanner.c src/scanner.l
gcc -g -o src/parser src/scanner.c -ll
./src/parser < test/square.pl0
分类后的部分结果如下:
Unrecognized character: V
Unrecognized character: A
Unrecognized character: R
An identifier: x
Unrecognized character: ,
An identifier: squ
...http://www.biyezuopin.vip
....
成功识别了标识符x
和squ
二步实验: 对C–语言进行词法分析
待分析文件: input.c--
#include <stdio.h>void main() {double a_0;double b;double c;double d;cout << a_0;a_0 = 4 + 3 / 2 - 1 * 0;cout << a_0;b = 3;c = 3.14;d = b + c;cout << d;
}
描述词法
正规文法转换成正则匹配式
根据公式:
右线性文法(X=aX|b)通解形式:X=a*b
左线性文法(X=Xa|b)通解形式:X=ba*
标识符
转换过程如下:
<标识符>∷=<字母>||<标识符><字母>|<标识符>|<标识符><数字>
即
<标识符>∷=<标识符>(<字母>++<数字>)+<字母>+
即
<标识符>∷=(<字母>|)(<字母>||<数字>)*
即
ID [_|A-Za-z]+(_|[A-Za-z]|[0-9])*
整数
转换过程如下
联立:
<整数>∷=<数字>|<非0数字><整数串><数字>|<非0数字><数字>
<整数串>∷=<整数串><数字>|<数字>
即
<整数>∷=<数字>|<非0数字><整数串><数字>|<非0数字><数字>
<整数串>∷=<数字>+
即
<整数>∷=<数字>|<非0数字><数字>+<数字>|<非0数字><数字>
<数字>∷=0|<非0数字>
即
INTEGER [0-9]+
flex文件由以下三个部分构成
definitions
%%
rules
%%
user code
由此构建出C–语言的相关词法(testPair.l
):
/* scanner for a toy C language */
#include <stdlib.h>
void yyerror(char *);
%}
http://www.biyezuopin.vip
INTEGER [0-9]+
FLOAT ([1-9][0-9]*)|0|([0-9]+\.[0-9]*)
ID [_|A-Za-z]+(_|[A-Za-z]|[0-9])*
HD #include<*.*>
AT \/\*(.*)\*\/
DM ,|;|\(|\)|\{|\}|\[|\]|\'|\"|<|>
STR \".*?\"|\'.*?\'
%%
[\n] {}
{HD} { printf("Header\n"); }
"main" {printf("Main:%s\n", yytext);}
"cin" {printf("Cin:%s\n", yytext);}
"cout" {printf("Cout:%s\n", yytext);} /*...*/
{ID} {printf("ID:%s\n", yytext);}
{INTEGER} {printf("int: %d\n", atoi(yytext));}
{FLOAT} {printf("double: %.10g\n", atof(yytext));}
[()<>=+\-/\*\;{}] { printf("OP:%s\n", yytext); }
[ \t]+ /* eat up whitespace */
. printf( "Unrecognized character: %s\n", yytext );
%%int yywrap(void) {return 1;
}
部分分析结果如下所示
```bash
$ make pair
flex -o src/testPair.c src/testPair.l
gcc -g -o src/testPair src/testPair.c -ll
./src/testPair < test/input.c--
Header
ID:void
Main:main
OP:(
OP:)
OP:{ID:double
ID:a_0
...
描述文法
语义信息
符号的语义信息存储于全局变量 yylval
中,
yylval = value; /* Put value onto Bison stack. */
return INT; /* Return the type of the token. */
%union {int intval;double val;symrec *tptr;
}...
yylval.intval = value; /* Put value onto Bison stack. */
return INT; /* Return the type of the token. */
...http://www.biyezuopin.vip
以上代码将符号压栈,并返回类型
移进规约
例:
Bison的解析栈(parser stack)中已经移进(shift)了4个字符:
1 + 5 * 3
接下来遇到了另起一行的符号,根据以下公式将解析栈中的后3个字符进行规约(reduce)
expr: expr '*' expr;
解析栈中变成3个字符1 + 15
自下而上的解析器将不断对读到的句子进行移进规约操作,直到输入序列只剩文法的开始符号
向前看符号(Look-ahead tokens)
look-ahead:读到字符但是不急着规约的行为
若定义加法表达式(expr)和符号(term)如下:
expr: term '+' expr| term;term: '(' expr ')'| term '!'| NUMBER;
若解析栈中移进:1 + 2
情况一:
若接着遇到右括号“)”,则栈中三个符号要被规约为expr
才能接着被规约为term
, 但这些操作没有对应规则(缺了一个左括号不能完成规约)
情况二:
若接着遇到阶乘符号“!”,否则若栈中1+2
先发生规约而没有向前看,则得到3!=6
而不是1+2!=3
向前看符号
会被存储在yychar
变量中
另外在编写的时候还要注意:移进规约冲突,符号优先级,上下文相关优先级
三步实验: 对C–语言进行语法分析(v1.0)
程序: v1.0
拿到一份待分析的代码文件,首先做语法分析,更细的再做词法分析;而程序员首先定义词法,再定义语法。
待分析代码input.c--
如下:
#include <stdio.h>
void main() {int a;cin >> a;a = a * 5 + 3.1415926536 - a / a;cout << x;
}
分析结果如下:
➜ bace01 git:(master) ✗ make bison
cd src/ && bison -d analyzer.y
analyzer.y: conflicts: 7 shift/reduce
analyzer.y:52.5: warning: rule never reduced because of conflicts: stmt: /* empty */
flex -o src/scanner.c src/scanner.l
gcc -g -o src/parser src/*.c
./src/parser < test/input.c--
Header
定义INT变量
语句串
输入语句
语句串
赋值语句
语句串
输出语句
语句串
语句块
函数
V1.2
程序: v1.2
语义解析: 加减乘除, 输出语句, while 循环, if-else
待分析代码input.c--
如下:
#include <stdio.h>void main() {double a_0;double b;double c;double d;cout << a_0;a_0 = 4 + 3 / 2 - 1 * 0;cout << a_0;b = 3;c = 3.14;d = b + c;cout << d;int i = 0;while( i <= 10 ) {cout << i;i = i + 1;}cout << (i + 999);double e = d;while (e <= 20) {if(e <= 15) {cout << e;} else {cout << 20;}e = e + 1;}
}
执行结果如下:
➜ bace01 git:(master) make clean
rm src/*.c
➜ bace01 git:(master) make bison
cd src/ && bison -d analyzer.y
➜ bace01 git:(master) make lex
flex -o src/scanner.c src/scanner.l
➜ bace01 git:(master) make
gcc -g -o src/parser src/scanner.c src/analyzer.tab.c
./src/parser < test/input.c--
0
5.5
6.14
0
1
2
3
4
5
6
7
8
9
10
1010
6.14
7.14
8.14
9.14
10.14
11.14
12.14
13.14
14.14
20
20
20
20
20
词法解析
- 对所有的词进行识别和分类, 在
scanner.l
中定义如下规则:
lex语法中使用正则表达式识别文法, 编写正则表达式时, 可以使用在线工具测试匹配情况.
方便起见, 在最初的版本中所有用于计算的数据类型都统一成double
型
首先是一些正则表达式的定义(可以看做是宏定义), 写在definitions
段:
INTEGER [0-9]+
FLOAT ([1-9][0-9]*)|0|([0-9]+\.[0-9]*)
ID [_|A-Za-z]+(_|[A-Za-z]|[0-9])*
HD #include<*.*>
...
接着定义匹配到的词语所要进行的操作, 写在rule
段:
[\n] {row_cnt ++; }
{HD} {} /* 系统保留字单独定义 */
"main" return FUN;return INT;
"double" return DOUBLE;
"int" return INT;
"void" return VOID;
"cin" return CIN;
">>" return TO_RIGHT;
...{ID} {varNode * vp = searchVarTab(yytext);if (vp == 0) vp = insertVarTab(yytext);yylval.tptr = vp;return VAR;} /* 每遇到一个标识符就存入变量表 */
{INTEGER} {yylval.val = atof(yytext); return NUM;}/*...*/
{FLOAT} {yylval.val = atof(yytext); return NUM;}
[()<>=+\-/\*\;\{\}] return *yytext;
[ \t]+ /* eat up whitespace */
. printf( "Unrecognized character: %s\n", yytext );
大写字符表示终结符
, 会被Bison
生成器解析为宏定义, 这些大写字符定义在.y
文件中, 用%token
声明, 解析后的结果可以在*.tab.h
文件中找到, 因此.l
文件需要#include <*.tab.h>
, 以将这张包含了全部宏定义的table
包含进去, 以后遇到大写字母
就替换成数字
了:
/* Tokens. */
#define NUM 258
#define VAR 259
#define FUN 260
#define DEF 261
#define INT 262
...
遇到的以yy*
开头的变量, 基本都是生成器自动定义的全局变量, 便于我们把值存在这些全局变量中, 在.l
与.y
文件之间传递
例如, 程序识别到的词会存储在char *yytext
变量中, 可以将其赋值给YYSTYPE yylval
变量, YYSTYPE
默认是int
类型, 当然我们可以自行定义为结构体类型, 以让程序理解更丰富的语义. YYSTYPE
就是栈中的元素类型, 后续对这些元素进行移进规约操作.
例如, 匹配到整数之后, 将char*
转化成float
类型, 赋值给全局变量yylval
, 返回NUM
替换成的数字, 告知语法识别程序.y
, 识别到了类型为NUM
的符号, 这个符号的值, 存储在yylval
变量中, 若要使用可以在.y
文件中通过$
符号访问;
定义一个全局变量表varNode * var_table
, 每识别一个标识符ID
, 在表中按名查找变量, 若是查询到变量则返回指向这个变量的指针, 否则将这个变量头插法插入到变量表中, 并赋初值为0;
在node.h
中定义的变量表结构如下:
/* 变量表中的结点 */
typedef struct varNode
{char *name; /* 变量名称 */double value; /* 变量值 */struct varNode *next;
}varNode;extern varNode * var_table;
extern varNode * insertVarTab ();
extern varNode * searchVarTab ();
语法解析
对所有的语句, 以及语句中的子语句规定一个统一的结构, 以便用统一的接口进行解析.
所有的元素因为具有统一的结构, 根据移进规约规则可以构造出一棵 R语言基于自定义函数构建xgboost模型并使用LIME解释器进行模型预测结果解释:基于训练数据以及模型构建LIME解释器解释一个iris数据样本的预测结果.LIME解释器进行模型预测结果解释并可视化 ... R语言基于自定义函数构建xgboost模型并使用LIME解释器进行模型预测结果解释:基于训练数据以及模型构建LIME解释器解释多个iris数据样本的预测结果.使用LIME解释器进行模型预测结果解释并可 ... R语言splines包构建基于logistic回归的自然样条分析:南非心脏病数据集.非线性:基函数展开和样条分析.你简单分析的不重要特征,可能只是线性不显著.而非线性是显著的 目录 R语言neuralnet包构建神经网络模型:基于乳腺癌数据集 目录 R语言neuralnet包构建神经网络模型:基于乳腺癌数据集 R语言xgboost模型构建:基于prima糖尿病数据集 目录 R语言xgboost模型构建:基于prima糖尿病数据集 电影评分系统是一种常见的推荐系统.现在使用R语言基于协同过滤算法来构建一个电影评分预测模型. 一,前提准备 1.R语言包:ggplot2包(绘图),recommenderlab包,re ... 最终内容请以原文为准:https://wangwei.one/posts/df1... 引言 区块链技术是一项比人工智能更具革命性的技术,人工智能只是提高了人类的生产力,而区块链则将改变人类社会的生产 ... 基于Java语言构建区块链(四)-- 交易(UTXO) 2018年03月11日 00:48:01 wangwei_hz 阅读数:909 标签: 区块链比特币 更多 个人分类: 区块链 文章的主要思想和 ... 基于Java语言构建区块链(五)-- 地址(钱包) 2018年03月25日 18:02:06 wangwei_hz 阅读数:1292更多 个人分类: 区块链bitcoin比特币 文章的主要思想和内容均 ...语法
基于C语言的词法分析器构建相关推荐
最新文章
热门文章