编译原理 实验一:词法分析器的自动实现(Lex词法分析)

一、实验内容

1.借助词法分析工具Flex或Lex完成(参考网络资源)
2.输入:高级语言源代码(如helloworld.c)
3.输出:以二元组表示的单词符号序列。

二、实验目的

通过设计、编制、调试一个具体的词法分析程序,加深对词法分析原理的理解,并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。

三、实验分析

由于各种不同的高级程序语言中单词总体结构大致相同,基本上都可用一组正则表达式描述,所以构造这样的自动生成系统:只要给出某高级语言各类单词词法结构的一组正则表达式以及识别各类单词时词法分析程序应采取的语义动作,该系统便可自动产生此高级程序语言的词法分析程序。Lex就是一个词法分析程序的自动生成工具。一个Lex源程序经过Lex编译系统可生成词法分析程序L。
一个Lex源程序具有如下形式:

声明部分
%%
转换规则
%%
辅助函数

在声明部分中,定义变量与常量。也可以声明正则表达式。
而Lex的每个转换规则具有如下形式:
模式 {动作}
其中,模式是正则表达式,可以使用声明部分中给出的正则定义。动作是代码片段,利用c语言编写。
Lex源程序存储在.l文件中,利用win_flex可以将其生成输出lex.yy.c文件。lex.yy.c文件可以被c语言编译器编译并运行。
为了识别一个真实的c语言源程序,需要用到如下辅助定义:
letter->a-zA-Z
digit->0-9
这两个辅助定义用于识别单个字母字符与单个数字字符。
在本实验中,需要识别的c语言程序保留字如下:while、if、else、switch、case、int、main、using、namespace、std、printf。可用一个正则表达式进行识别:
(while)|(if)|(else)|(switch)|(case)|(int)|(main)|(using)|(namespace)|(std)|(printf)
c语言的标识符id规则如下:由字母、数字、下划线组成,且第一个字符只能为字母或下划线。则标识符的正则表达式为:({letter}|_)({letter}|{digit}|_)*
整数的正则表达式如下:(‘+’|’-’)?{digit}*
其中,问号表示’+’号或’-’号要么不出现,要么出现了有且只有一次。这一正则表达式可以接受正数与负数。
浮点数的正则表达式如下:{digit}+.{digit}+((E|e)(‘+’|’-’)?{digit}+)?
在小数点之前至少有一位数字,在小数点之后也至少有一位数字。在数字后可以附带这个数需要乘10的多少次幂,也可以不附带。
c语言中的运算符除了加减乘除、比较、赋值外,还有流输入输出。正则表达式如下:\+|-|\*|<=|<|==|=|>=|>|>>|<<。因为正则表达式本身有加号与乘号,所以在加号与乘号前追加反斜杠进行转义。
识别空格、tab、换行符、回车符(\r)的正则表达式为: \t\n\r,记为delim。为了识别多个空白,可用正则表达式{delim}+
识别字符串的正则表达式为:”[^”]*”,即在两个双引号内可以有任意个除双引号字符以外的字符。为了转义,在c语言程序中需写作:\"[^"]*\"
识别c语言程序开头包含的头文件时,需要先识别#号,再识别出除换行符以外的任何字符。正则表达式语句如下:"#".*
在lex程序中,在读取完成需要进行词法分析的源代码文件后,调用yylex()即可自动开始进行词法分析,词法分析得到的词法单元属性值暂存在全局变量yylval中,而识别得到的单词字符串存放在变量yytext中。
在运行过程中,每将输入的文件匹配到一个正则表达式,就自动执行其动作。在本实验中,被执行的动作为以二元组形式表示当前单词。即为:(单词种别,单词自身的值)。这一过程反复进行,并会在识别到非法字符时报错。直到读取到文件结束时,这一过程结束。

四、实验流程

在下载完成win_flex的压缩包后,对该文件夹解压可以看到win_flex.exe的文件。这一exe文件不能直接运行。需要首先配置环境变量,将这一文件所在的路径添加到系统变量的Path内。在cmd窗口,对Lex源程序的.l文件执行win_flex --nounistd lex.l,就能生成出lex.yy.c。添加参数–nounistd的目的是生成出能在windows环境下编译运行的lex.yy.c而不是只能在linux/unix环境下执行的lex.yy.c。
在编写完.l文件后,执行win_flex,生成lex.yy.c。在visual studio等编译器中可以编译运行这个文件,将待分析的另一个c语言源程序进行词法分析并输出到控制塔上。
因此,实验的整体操作流程如下

图1 整体流程

五、实验代码

5.1 代码说明

在%{ 与%}之间是插入c语言程序,include相关库并定义全局变量为计数器记录当前在分析第几个单词。

%{
	#include <stdio.h>
	#include <stdlib.h>
	int count = 0;
%}

正则表达式的声明部分,与第三章分析的各个单词类型对应的正则表达式是一致的。

digit		[0-9]
letter		[a-zA-Z]
reservedWord	[w][h][i][l][e]|[i][f]|[e][l][s][e]|[s][w][i][t][c][h]|[c][a][s][e]|[i][n][t]|[m][a][i][n]|[u][s][i][n][g]|[n][a][m][e][s][p][a][c][e]|[s][t][d]|[p][r][i][n][t][f]
id	({letter}|_)({letter}|{digit}|_)*
num	{digit}+
operator	\+|-|\*|<=|<|==|=|>=|>|>>|<<
delim		[ \t\n\r]
whitespace	{delim}+
semicolon [\;]
str \"[^"]*\"
other		.
%%

辅助函数部分,针对每个正则表达式都执行对应动作:计数器加一,分析当前单词的单词种别与单词自身的值。

%%
{reservedWord}  {count++;printf("%d\t(reserved word,\'%s\')\n",count,yytext);}
{id}    {count++;printf("%d\t(id,yytext);}
{num}	{count++;printf("%d\t(num,yytext);}
{operator}      {count++;printf("%d\t(op,yytext);}
{whitespace}    { /* do    nothing*/ }
{str} {count++;printf("%d\t(string,yytext);}
"(" {count++;printf("%d\t(left bracket,yytext);}
")" {count++;printf("%d\t(right bracket,yytext);}
"{" {count++;printf("%d\t(left bracket,yytext);}
"}" {count++;printf("%d\t(right bracket,yytext);}
":" {count++;printf("%d\t(colon,yytext);}
";" {count++;printf("%d\t(semicolon,yytext);}
"#".* {count++;printf("%d\t(head,yytext);}
{other}		{printf("illegal character:\'%s\'\n",yytext);}
%%

main函数的作用就是读取文件到yyin,并调用yylex()自动地对yyin的文件进行词法分析。

int main(){
	yyin=fopen("F:/HOMEWORK/Compiler/Lab2/test.c","r");
	yylex();
	return 0;
}
 int yywrap()
 {
 	return 1;
 }

5.2 完整代码

%{
	#include <stdio.h>
	#include <stdlib.h>
	int count = 0;

%}
digit		[0-9]
letter		[a-zA-Z]
reservedWord	[w][h][i][l][e]|[i][f]|[e][l][s][e]|[s][w][i][t][c][h]|[c][a][s][e]|[i][n][t]|[m][a][i][n]|[u][s][i][n][g]|[n][a][m][e][s][p][a][c][e]|[s][t][d]|[p][r][i][n][t][f]
id	({letter}|_)({letter}|{digit}|_)*
num	{digit}+
operator	\+|-|\*|<=|<|==|=|>=|>|>>|<<
delim		[ \t\n\r]
whitespace	{delim}+
semicolon [\;]
str \"[^"]*\"
other		.
%%
{reservedWord}  {count++;printf("%d\t(reserved word,yytext);}
%%
int main(){
	yyin=fopen("F:/HOMEWORK/Compiler/Lab2/test.c","r");
	yylex();
	return 0;
}
 int yywrap(){
 	return 1;
 }

六、运行结果

图2 编译运行.l文件


编译运行.l文件,加入参数nounistd,生成了可在Windows 10 环境下可以运行的lex.yy.c,如图3所示。

图3 生成文件


图4 测试文件


如图4,编写用于测试词法分析器的真实c语言代码。

图5 词法分析结果


对lex.yy.c代码进行编译运行,并将结果输出在控制台上,如图5。该词法分析器能自动忽略空格、换行符、tab,按照顺序逐一将每个单词进行编号,并生成二元组(单词种别,单词自身的值)。
图5词法分析结果与图4的测试文件一一对应。
头文件首先被识别出来。识别语句using namespace std,并识别语句的分号。
识别int main,并识别紧随其后的左右小括号与大括号。
源代码“int a1=0;”被识别如下:保留字int,标识符a1,赋值号=,数字0。并识别分号。
源代码“if(a1==1);”被识别如下:保留字if、左括号、标识符a1、比较相等的符号==、数字1、右括号、分号。
源代码“printf(“hello world”)”被识别为:保留字printf、左括号、字符串”hello world”、右括号。需要说明的是,”hello world”字符串是一个单词,没有因为中间有空格而被识别为两个单词。
else、switch等单词均为保留字。识别标识符a1、括号的过程都是同理的。
源代码“case 1:printf(“hello”);”被识别为:保留字case、数字1、保留字printf、左括号、字符串”hello”、右括号、分号。源代码“case 2:printf(“world”);”同理。
最后识别两个右大括号。读取到文件结束符后,程序结束。

七、实验感悟

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


jquery.validate使用攻略(表单校验) 目录 jquery.validate使用攻略1 第一章&#160;jquery.validate使用攻略1 第二章&#160;jQuery.validate.js API7 Custom selectors7 Utilities8 Validato
/\s+/g和/\s/g的区别 正则表达式/\s+/g和/\s/g,目的均是找出目标字符串中的所有空白字符,但两者到底有什么区别呢? 我们先来看下面一个例子: let name = &#39;ye wen jun&#39;;let ans = name.replace(/\s/g, &#39;&#3
自整理几个jquery.Validate验证正则: 1. 只能输入数字和字母 /^[0-9a-zA-Z]*$/g jQuery.validator.addMethod(&quot;letters&quot;, function (value, element) { return this.optio
this.optional(element)的用法 this.optional(element)是jquery.validator.js表单验证框架中的一个函数,用于表单控件的值不为空时才触发验证。 简单来说,就是当表单控件值为空的时候不会进行表单校验,此函数会返回true,表示校验通过,当表单控件
jQuery.validate 表单动态验证 实际上jQuery.validate提供了动态校验的方法。而动态拼JSON串的方式是不支持动态校验的。牺牲jQuery.validate的性能优化可以实现(jQuery.validate的性能优化见图1.2 jQuery.validate源码 )。 也可
自定义验证之这能输入数字(包括小数 负数 ) &lt;script type=&quot;text/javascript&quot;&gt; function onlyNumber(obj){ //得到第一个字符是否为负号 var t = obj.value.charAt(0); //先把非数字的都
// 引入了外部的验证规则 import { validateAccountNumber } from &quot;@/utils/validate&quot;; validator.js /*是否合法IP地址*/ export function validateIP(rule, value,cal
VUE开发--表单验证(六十三) 一、常用验证方式 vue 中表单字段验证的写法和方式有多种,常用的验证方式有3种: data 中验证 表单内容: &lt;!-- 表单 --&gt; &lt;el-form ref=&quot;rulesForm&quot; :rules=&quot;formRul
正则表达式 座机的: 例子: 座机有效写法: 0316-8418331 (010)-67433539 (010)67433539 010-67433539 (0316)-8418331 (0316)8418331 正则表达式写法 0\d{2,3}-\d{7,8}|\(?0\d{2,3}[)-]?\d
var reg = /^0\.[1-9]{0,2}$/;var linka = 0.1;console.log (reg.test (linka)); 0到1两位小数正则 ^(0\.(0[1-9]|[1-9]{1,2}|[1-9]0)$)|^1$ 不含0、0.0、0.00 // 验证是否是[1-10
input最大长度限制问题 &lt;input type=&quot;text&quot; maxlength=&quot;5&quot; /&gt; //可以 &lt;input type=&quot;number&quot; maxlength=&quot;5&quot; /&gt; //没有效
js输入验证是否为空、是否为null、是否都是空格 目录 1.截头去尾 trim 2.截头去尾 会去掉开始和结束的空格,类似于trim 3.会去掉所有的空格,包括开始,结束,中间 1.截头去尾 trim str=str.trim(); // 强烈推荐 最常用、最实用 or $.trim(str);
正则表达式语法大全 字符串.match(正则):返回符合的字符串,若不满足返回null 字符串.search(正则):返回搜索到的位置,若非一个字符,则返回第一个字母的下标,若不匹配则返回-1 字符串.replace(正则,新的字符串):找到符合正则的内容并替换 正则.test(字符串):在字符串中
正整数正则表达式正数的正则表达式(包括0,小数保留两位): ^((0{1}.\d{1,2})|([1-9]\d.{1}\d{1,2})|([1-9]+\d)|0)$正数的正则表达式(不包括0,小数保留两位): ^((0{1}.\d{1,2})|([1-9]\d.{1}\d{1,2})|([1-9]+
JS 正则验证 test() /*用途:检查输入手机号码是否正确输入:s:字符串返回:如果通过验证返回true,否则返回false /function checkMobile(s){var regu =/[1][3][0-9]{9}$/;var re = new RegExp(regu);if (r
请输入保留两位小数的销售价的正则: /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/ 1.只能输入英文 &lt;input type=&quot;text&quot; onkeyup=&quot;value
判断价格的正则表达式 价格的正则表达式 /(^[1-9]\d*(\.\d{1,2})?$)|(^0(\.\d{1,2})?$)/; 1 解析:价格符合两种格式 ^ [1-9]\d*(.\d{1,2})?$ : 1-9 开头,后跟是 0-9,可以跟小数点,但小数点后要带上 1-2 位小数,类似 2,2
文章浏览阅读106次。这篇文章主要介绍了最实用的正则表达式整理,比如校验邮箱的正则,号码相关,数字相关等等,本文给大家列举的比较多,需要的朋友可以参考下。_/^(?:[1-9]d*)$/ 手机号
文章浏览阅读1.2k次。4、匹配中的==、an==、== an9、i9 == "9i"和99p==请注意下面这部分的作用,它在匹配中间内容的时候排除了说明:当html字符串如下时,可以匹配到两处,表示匹配的字符串不包含and且不包含空白字符。说明:在上面的正则表达式中,_gvim正则表达式匹配不包含某个字符串
文章浏览阅读897次。【代码】正则表达式匹配a标签的href。_auto.js 正则匹配herf