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部分解释如下:

  1. 对lex语言描述的词法文件scanner.l
    使用flex转换得到c语言描述的词法文件, 默认输出为 lex.yy.c 我们重命名为: -o scanner.c
  2. 对bison语言描述的语法文件analyzer.y进行转换得到c语言描述的语法文件, 参数-d.h文件和.c文件分离, 默认输出为analyzer.tab.canalyzer.tab.h, 将analyzer.tab.hinclude进lex文件以进行下一步的联合编译
  3. 1,2中生成文件进行联合编译, 输出可执行程序parser, 对输入文件input.c--进行语法分析.
  4. 若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
....

成功识别了标识符xsqu


二步实验: 对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

词法解析

  1. 对所有的词进行识别和分类, 在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 ();

语法解析

对所有的语句, 以及语句中的子语句规定一个统一的结构, 以便用统一的接口进行解析.

所有的元素因为具有统一的结构, 根据移进规约规则可以构造出一棵语法

基于C语言的词法分析器构建相关推荐

  1. R语言基于自定义函数构建xgboost模型并使用LIME解释器进行模型预测结果解释:基于训练数据以及模型构建LIME解释器解释一个iris数据样本的预测结果、LIME解释器进行模型预测结果解释并可视化

    R语言基于自定义函数构建xgboost模型并使用LIME解释器进行模型预测结果解释:基于训练数据以及模型构建LIME解释器解释一个iris数据样本的预测结果.LIME解释器进行模型预测结果解释并可视化 ...

  2. R语言基于自定义函数构建xgboost模型并使用LIME解释器进行模型预测结果解释:基于训练数据以及模型构建LIME解释器解释多个iris数据样本的预测结果、使用LIME解释器进行模型预测结果解释

    R语言基于自定义函数构建xgboost模型并使用LIME解释器进行模型预测结果解释:基于训练数据以及模型构建LIME解释器解释多个iris数据样本的预测结果.使用LIME解释器进行模型预测结果解释并可 ...

  3. R语言splines包构建基于logistic回归的自然样条分析:南非心脏病数据集、非线性:基函数展开和样条分析、你简单分析的不重要特征,可能只是线性不显著、而非线性是显著的

    R语言splines包构建基于logistic回归的自然样条分析:南非心脏病数据集.非线性:基函数展开和样条分析.你简单分析的不重要特征,可能只是线性不显著.而非线性是显著的 目录

  4. R语言neuralnet包构建神经网络模型:基于乳腺癌数据集

    R语言neuralnet包构建神经网络模型:基于乳腺癌数据集 目录 R语言neuralnet包构建神经网络模型:基于乳腺癌数据集

  5. R语言xgboost模型构建:基于prima糖尿病数据集

    R语言xgboost模型构建:基于prima糖尿病数据集 目录 R语言xgboost模型构建:基于prima糖尿病数据集

  6. 基于R语言构建的电影评分预测模型

    电影评分系统是一种常见的推荐系统.现在使用R语言基于协同过滤算法来构建一个电影评分预测模型. 一,前提准备         1.R语言包:ggplot2包(绘图),recommenderlab包,re ...

  7. 基于Java语言构建区块链(一)—— 基本原型

    最终内容请以原文为准:https://wangwei.one/posts/df1... 引言 区块链技术是一项比人工智能更具革命性的技术,人工智能只是提高了人类的生产力,而区块链则将改变人类社会的生产 ...

  8. 基于Java语言构建区块链(四)—— 交易(UTXO)

    基于Java语言构建区块链(四)-- 交易(UTXO) 2018年03月11日 00:48:01 wangwei_hz 阅读数:909 标签: 区块链比特币 更多 个人分类: 区块链 文章的主要思想和 ...

  9. 基于Java语言构建区块链(五)—— 地址(钱包)

    基于Java语言构建区块链(五)-- 地址(钱包) 2018年03月25日 18:02:06 wangwei_hz 阅读数:1292更多 个人分类: 区块链bitcoin比特币 文章的主要思想和内容均 ...

最新文章

  1. BZOJ 4059: [Cerc2012]Non-boring sequences ( )
  2. python如何调用参数配置文件_python参数设置
  3. [hive学习翻译]Hive - Introduction
  4. 【SpringBoot】在普通类中获取spring容器中的bean
  5. 作业帮电脑版在线使用_一起作业学生app 手机版免费在线下载
  6. cri-o 与 cni的集成分析
  7. RabbitMQ原理及实现
  8. nodejs实践录:我的nodejs编码风格
  9. mapreduce与spark的区别--内容详细
  10. JavaScript 入门·JavaScript 具有全范围的运算符
  11. H3C 静态路由的配置
  12. sessionStorage跨标签取值
  13. Codeforces Round #435 (Div. 2) E. Mahmoud and Ehab and the function[二分]
  14. 车牌识别的matlab程序(程序_讲解_模板),车牌识别的matlab程序(程序-讲解-模板)资料...
  15. c语言字符画小狗,C语言字符画,字符闪画
  16. 跑马灯C语言实验报告,51单片机跑马灯实验报告 分析与小结,思考题源码下载
  17. 一个计算机爱好者的不完整回忆(十)插播游戏
  18. html.partial mvc5,.net mvc5的 完整源码(深入学习MVC非常有用)
  19. 在CentOS7上源码安装MongoDB 3.2.7
  20. html5绘制图形渐变-径向渐变

热门文章

  1. 前端实习生应该掌握什么技能?
  2. SpringBoot整合JWT实现API身份校验
  3. 计算机考试internet应用好考吗,职称计算机考试《Internet应用》基础习题
  4. 【数分书单】分析思维《一本小小的蓝色逻辑书》第五章小结
  5. 华为云开发者日震撼来袭!11月20日,上海见
  6. 音视频---速搭建语音聊天室技术分析
  7. vue中引用tinymce图标不显示解决方法
  8. 把商品添加到购物车的方法
  9. [SSD固态硬盘技术 8] 固件概述和固件升级
  10. django 微信支付成功回调url(notify_url)