C–源代码词法分析

文章目录

  • C--源代码词法分析
    • 一、实现目标
    • 二、C-Minus语法
    • 三、Flex
      • Flex简介
      • Flex正则表达式
      • Flex安装与使用
      • Flex文件编写
        • 定义
        • 规则
      • 用户代码
    • 四、总结

一、实现目标

  1. 编写一个程序对C–语言(C语言子集)书写的源代码进行词法分析,并打印分析结果。
  2. 程序要能够检查源代码中可能包含的词法错误:
    1. 最低要求1.1:能够识别词法中未定义的字符;
    2. 其他要求1.2:能识别指数形式的浮点数;
    3. 其他要求1.3:能识别8进制/16进制数。
  3. 词法分析程序能定位错误位置。

二、C-Minus语法

关键字

TOKEN CONTENT
STRUCT struct
RETURN return
IF if
ELSE else
WHILE while
TYPE int float

特殊符号

TOKEN CONTENT
SEMI ;
COMMA ,
ASSIGNOP =
PLUS +
MINUS -
STAR *
DIV /
AND &&
DOT .
NOT !
LP (
RP )
LB [
RB ]
LC {
RC }

数字表示:十进制、二进制、八进制、十六进制、浮点数、科学计数

TOKEN DESCRIBE CONTENT
INT 十进制:表示的是所有无符号的整型常数,除“0”外,首位数字不为0 0
INT_HEX 十六进制:十六进制以0x或者0X开头(如0xFF32) 0[xX][a-fA-F0-9]+
INT_OCT 八进制:八进制整数以数字0开头(如:0237) 0[1-7][0-7]*
INT_BIN 二进制:二进制整数以0b或者0B开头 0[bB][01]+
FLOAT 浮点数:表示的是所有无符号的浮点型常数,小数点前后必须有数字出现 {INT}.[0-9]+
SCIENCE 科学计数法:包含基数、指数符号和指数三个部分,基数的小数点可以出现在数字串的任何位 置,指数符号用E或e表示,指数可带±符号或是不带。01.23E2、43.e-4、.5E03都是符合要求的浮点数 (([0-9]+.[0-9]*)

标识符

TOKEN DESCRIBE CONTENT
ID 标识符由下划线_、数字0-9、字母a-zA-Z组成,开头不能是数字 [a-z_A-Z][a-z_A-Z0-9]*

其他字符

TOKEN DESCRIBE CONTENT
COMMENT 注释:// “/"([](([^/])+([/])))"/”
SPACE 空白符 [ \t\n\r\f\v]+
AERROR 其他未匹配字符(用于错误处理) .

三、Flex

Flex简介

Lex是LEXical compiler的缩写,是Unix环境下非常著名的工具,主要功能是生成一个词法分析器(scanner)的C源码,描述规则采用正则表达式(regular expression)。描述词法分析器的文件*.l,经过lex编译后,生成一个lex.yy.c 的文件,然后由C编译器编译生成一个词法分析器。词法分析器,简单来说,其任务就是将输入的各种符号,转化成相应的标识符(token),转化后的标识符 很容易被后续阶段处理。

Flex正则表达式

符号 含义
|
[] 括号中的字符取其一
- a-z表示ascii码中介于a-z包括a.z的字符
\ 转义(flex不能识别除字母外的字符)
* 0或多个字符
? 0或1个字符
+ 1或多个字符
^ 除此之外的其余字符
. 除\n外的所有字符,等价于^\n

Flex安装与使用

实验环境:Ubantu 12,已安装gcc
在Linux命令行中安装flex、bison:

sudo apt-get install flex bison

查看版本号,检查是否安装成功:

flex -V
bison -V

写好词法分析的文件(以.l结尾),进入文件所在目录,使用flex进行编译:

flex 词法分析源文件名

编译过后会生成名为lex.yy.c的文件,使用gcc进行编译生成可执行文件:

gcc lex.yy.c -o 可执行文件名

使用编译后的可执行文件分析测试文件的词法:

./可执行文件名 测试文件名

在控制台中查看输出的分析结果

Flex文件编写

Flex的.l文件内容可以分为三个部分:定义、规则、用户代码

定义:definition
%%
规则:rules
%%
用户代码:code

定义

定义又可以分为四部分:添加头文件、变量声明、设置flex属性、正则表达式定义

  1. 头文件和变量声明一般包括在括号%{…%}中
/*第一部分 头文件和变量*/
%{#include <stdlib.h>#include <stdio.h>FILE* f;int i;int comment_flag;int comment_begin;
%}

该处声明的变量可以使用在用户代码定义的函数中,作为全局变量
如果变量声明不在%{%}之间,则需要使用tab来缩进

int a;int b;
  1. 设置flex属性

flex的选项影响最终生成的词法分析器的属性和行为。这些选项可以在运行flex命令时在终端输入,也可以在.l文件中使用%option指定。
本次实验中需要使用到当前分析的词所在的行数:

/*flex属性,记录符号所在行号*/
%option yylineno
  1. 正则表达式声明方式:
{表达式名称} 正则表达式

在定义正则表达式时可以直接使用已经定义过的表达式名称,需要加上{}
以本次实验中数字类型和标识符为例:

/*第二部分 定义正则表达式*/
/*十进制*/
INT 0|[1-9][0-9]*
/*十六进制*/
INT_HEX 0[xX][a-fA-F0-9]+
/*八进制*/
INT_OCT 0[1-7][0-7]*
/*二进制*/
INT_BIN 0[bB][01]+
/*浮点数*/
FLOAT {INT}\.[0-9]+
/*科学计数法*/
SCIENCE (([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+)|INT)[Ee][-+]?[0-9]+
/*数字类型汇总*/
NUMBER {INT_HEX}|{INT}|{INT_OCT}|{INT_BIN}|{SCIENCE}|{FLOAT}/*标识符*/
ID [a-z_A-Z][a-z_A-Z0-9]*

匹配关键字:

/*关键字*/
STRUCT struct
RETURN return
IF if
ELSE else
WHILE while
TYPE int|float/*部分符号*/
/*标点*/
SEMI  ;
COMMA ,
/*运算*/
ASSIGNOP  =
PLUS  \+
MINUS \-
STAR  \*
DIV   \/
AND   &&
DOT   \.
NOT   \!
/*括号*/
LP    \(
RP    \)
LB    \[
RB    \]
LC    \{
RC    \}

空白符和注释部分正则表达式如下:

/*其它字符*/
/*注释*/
COMMENT ("//".*)|("/*"([*]*(([^*/])+([/])*)*)*"*/")
COMMENT_BEGIN "/*"
/*空白符*/
SPACE [ \f\n\r\t\v]+
/*未定义字符*/
AERROR .
/*十六进制错误*/
INT_HEX_ERROR 0[xX][a-fA-F0-9]*[g-zG-Z]+[a-fA-F0-9]*
/*八进制错误*/
INT_OCT_ERROR 0[0-7]*[89]+[0-7]*
/*二进制错误*/
INT_BIN_ERROR 0[bB][01]*[2-9]+[01]*

当所有的正则表达式都无法匹配当前词时,就会被匹配到AERROR(需要在规则部分的最后进行处理,优先匹配其他类型),从而做出匹配到为定义字符的错误处理

规则

整个规则部分需要包括在

%%
...
%%

并且规则部分中的注释行必须顶一个空格,例如:

/*...*/

编写规则时,一个规则一行,每行有两部分构成:

正则表达式 {动作函数}

也可以使用

正则表达式 |
正则表达式 {...}

此处 “正则表达式” 和 ’|’中间用一个空格隔开
动作函数可以调用在用户代码中定义的函数
以本实验为例:

/*第三部分 操作 action 这里面的注释必须顶格一个空格*/
%%/*跳过空白和注释*/
{SPACE} {}
{COMMENT} { printf("注释,行数: %d 字符:%s\n",yylineno,yytext);}
{COMMENT_BEGIN} {comment_flag = 1;comment_begin = yylineno;}/*未终结注释错误*/
<<EOF>> {if(comment_flag == 1){printf("UNTERMINATED_COMMENT at line %d\n",yylineno);comment_flag = 0;}yyterminate();
}/*关键字*/
{TYPE} |
{STRUCT} |
{RETURN} |
{IF} |
{ELSE} |
{WHILE} { printf("关键字,行数: %d 字符:%s\n",yylineno,yytext);}/*数字类型错误*/
{INT_HEX_ERROR} {if(comment_flag!=1) printf("INT_HEX_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_OCT_ERROR} {if(comment_flag!=1) printf("INT_OCT_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}
{INT_BIN_ERROR} {if(comment_flag!=1) printf("INT_BIN_ERROR at line %d: charachters \"%s\"\n",yylineno,yytext);}/*数字类型表示*/
{NUMBER} {if(comment_flag!=1)  printf("数字,行数: %d 字符:%s\n",yylineno,yytext);}/*标点*/
{SEMI} |
{COMMA} {if(comment_flag!=1)  printf("标点,行数: %d 字符:%s\n",yylineno,yytext);}/*运算符*/
{ASSIGNOP} |
{PLUS} |
{MINUS} |
{STAR} |
{DIV} |
{AND} |
{DOT} |
{NOT} {if(comment_flag!=1)  printf("运算符,行数: %d 字符:%s\n",yylineno,yytext);}/*括号*/
{LP} |
{RP} |
{LB} |
{RB} |
{LC} |
{RC} {if(comment_flag!=1)  printf("括号,行数: %d 字符:%s\n",yylineno,yytext);}/*标识符*/
{ID} {if(comment_flag!=1)  printf("标识符,行数:%d 字符:%s\n",yylineno,yytext);}/*错误*/
{AERROR} {if(comment_flag!=1) printf("Error type A at line %d: mysterious charachter '%s'\n",yylineno,yytext);}
%%

为了展现词法分析效果,将所有匹配情况都进行了输出,实际上只输出AERROR类型即可
这里使用yylineno表示当前所分析的字符串在文件的第几行,yytext是lex内部已经定义好的指针变量,lex分析过程是将输入字符串按程序员预先设计好的正则表达式进行匹配,yytext总是指向当前获得匹配的字符串

用户代码

该部分必须定义main函数,用于指定需要扫描分析的文件,可以内部指定,也可以通过控制台传入参数:

int main(int argc,char** argv){if(argc<2){/*由lex创建的扫描程序的入口点yylex()调用yylex()启动或者重新开始扫描。如果lex动作执行讲数值传递给调用的程序return,那么对yylex()的下次调用就从它的停止地方继续。*/yylex();return 0;}for(i=1;i<argc;i++){f=fopen(argv[i],"r");if(!f){/*C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。*/perror(argv[i]);return 1;}comment_flag = 0;/*yyrestart使词法分析器从f中读取标准输入文件*/yyrestart(f);yylex();fclose(f);}return 0;
}

这段代码中实现了传入多个文件参数时的处理
yylex():由lex创建的扫描程序的入口点yylex(),调用*yylex()启动或者重新开始扫描。如果lex动作执行讲数值传递给调用的程序return,那么对yylex()*的下次调用就从它的停止地方继续
C 库函数 *void perror(const char str) :把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格
yyrestart():使词法分析器从f中读取标准输入文件

/*第四部分 函数 function*/
int yywrap()
{/*此函数必须由用户提供,或者声明 %option noyywrap当词法分析程序遇到文件结尾时,它调用例程yywrap()来找出下一步要做什么如果返回0,扫描程序继续扫描,如果返回1,扫描程序就返回报告文件结尾*/return 1;
}

除了定义main函数,用户还需要自己定义yywrap()函数,或者声明 %option noyywrap;当词法分析程序遇到文件结尾时,它调用例程yywrap()来找出下一步要做什么,如果返回0,扫描程序继续扫描,如果返回1,扫描程序就返回报告文件结尾。

四、总结

完成的内容

  • 能够识别C-Minus中定义的词法(按照C-Tokens文件实现)
  • 能识指数形式的整数和浮点数(科学计数法)
  • 能识别2进制/8进制/16进制数
  • 能识别注释内容、空白字符
  • 能识别未定义的字符,并定位错误位置

这里留下几个测试文件,读者可自行测试,本人已经测试过了,就不再贴结果

文件1

struct Complex
{float real, image;
}int main()
{struct Complex x;float a[10] = 1.5;int i = 100;x.image = ~i;if (a[1][2] == 0) i =1 else i =0;
}

预测结果:
第11行存在未定义字符~,其他字符、字符串都可以正常识别

文件2

int main()
{int i = 0123;int j = 0x3F;int k = 0967;
}

预测结果
第五行八进制书写错误,词法分析程序会将0967分析为十进制0和十进制967,交给语法分析器进行进一步处理


本文为原创,如有错误欢迎指正,感谢阅读。

附上参考文献:
编译原理-用FLEX构造词法分析程序、LEX/FLEX词法分析器

编译原理 C-Minus词法分析(FLEX)相关推荐

  1. 编译原理实验:词法分析

    编译原理实验:词法分析 1. 实验题目:词法分析 实验目的 实验内容 实验要求 输入输出 2. 设计思想 3.算法流程 4. 源程序 5. 调试数据 1. 实验题目:词法分析 实验目的 根据PL/0语 ...

  2. 编译原理课程设计词法分析

      编译原理课程设计词法分析任务书 5)参考文献: (1)张素琴,吕映芝. 编译原理[M]., 清华大学出版社 (2)蒋立源.康慕宁等,编译原理(第2版)[M],西安:西北工业大学出版社 6)课程设计 ...

  3. 编译原理:用lex/flex做词法分析

    最近在自学<编译原理>,感觉对于我来说有点难度. 写这个的目的是为了做笔记,感谢https://blog.csdn.net/xiaowei_cqu/article/details/7760 ...

  4. 编译原理实验代码c语言,编译原理实验 简单词法分析(含源代码和实验结果)

    可直接运行 原创!! 附录一 实验报告样式 <编译原理>实验报告 实验2 简单词法分析 姓名 陈婷婷 学号 1009050121 班级 计科1001班 时间: 2012/4/5 地点:文波 ...

  5. 编译原理实验一 词法分析程序设计与实现

    一.实验目的 通过编写和调试一个词法分析程序,掌握在对程序设计语言的源程序进行扫描的过程中,将字符流形式的源程序转化为一个由各类单词构成的序列的词法分析方法. 二.基本实验内容与要求 假定一种高级程序 ...

  6. 编译原理java课程设计_编译原理课程设计词法分析

    一.课程设计任务及要求 1.1.目的 通过使用一个通用的能够自动根据正规表达式生成词法分析程序的工具程序设计一个简单语言的词法分析器,使学生充分理解课程理论内容和工具软件的使用技巧,掌握所涉及的典型数 ...

  7. 编译原理笔记3 词法分析 龙书

    正则表达式(Regular Expression,RE) 啥是正则表达式//比较简单略写- 正则表达式(Regular Expression,RE) 是一种用来描述正则语言(3型语言)的更紧凑的表示方 ...

  8. 用c++自制词法分析器_编译原理笔记 02 词法分析

    # 词法分析 ## 主要任务 从左向右逐行扫描源程序的字符,识别出各个单词,确定单词的类型.将识别的单词转换成统一的机内表述---词法单元(token)形式 ## 词法分析的状态转换图 图片来源: 编 ...

  9. 第一次编译原理大作业---用FLEX编写C语言的词法分析器

    一开始看到这个作业的时候真的很懵圈,因为感觉每个字都认识但合起来就不知道到底是什么意思.开始的时候先去官网看了flex的介绍,官网上推荐和visual studio一起用,然后又去下载了vs.搞了半天 ...

  10. 编译原理--词法分析

    词法单元,模式和词素 1.词法单元由一个词法单元名,一个可选的属性值组成. 2.模式描述了一个词法单元的词素可能具有的形式. 3.词素是源程序中的一个字符序列,和某个词法单元的模式匹配, 并被词法分析 ...

最新文章

  1. C语言中文件的读写(fputc和fgetc)(putchar和getchar)
  2. 武汉大学计算机学院 曹老师,关于校园网吧建设的计算机网络综合设计.doc
  3. JavaWeb学习之路——SSM框架之Spring(四)
  4. 五大常用算法之五:分支限界法
  5. Scrapy从理论到爬图
  6. 本地安装Docker
  7. linux下执行mysql的sql文件
  8. php7 memcached sasl,Mac安装memcached扩展支持sasl
  9. JAVA 使用类的继承和接口实现多态
  10. Google 开源 ChromeOS.dev,在 ChromeOS 上构建应用更容易!
  11. php 提取二维数组的key,PHP 获取二维数组中某个key的集合
  12. 计算当前时间往加N天后的时间,Date,SimpleDateFormat,Calendar
  13. Android Application基本组成部分
  14. [error]:启用sqlserver配置管理器异常,内存不足
  15. PIC中档单片机汇编指令详解(2)
  16. 施耐德驱动器维修ELAU控制器维修C400C600
  17. 汽车行业DMS系统介绍
  18. linux路由表怎么看懂,教你读懂Linux路由表
  19. Unity开发 Photon Pun 多人游戏组件
  20. 微信支付必须是服务器,微信付款码不需要联网的原理是什么?怎么生成?

热门文章

  1. 尝鲜用 React Hook + Parcel 构建真心话大冒险简单页面
  2. 离线语音识别库_离线语音识别_离线语音识别sdk - 云+社区 - 腾讯云
  3. Mysql高可用集群搭建(一)一主两从服务搭建
  4. 美团基于知识图谱的剧本杀标准化建设与应用
  5. Vue.js从0开始到实战开发1:通过简单案例从0开始了解Vue
  6. 如何让table边框变为单实线?
  7. 【错误汇总】PYTHON开发
  8. The 2018 JUST Collegiate Programming Contest H题 Cube
  9. Java调用Bartender控制条码打印机
  10. 服务器主机外接显示器,服务器主机连接显示器