编译原理实验1——词法分析程序设计原理与实现

1.理论传授

源程序输入与词法分析程序输出的基本方法;正则文法及其状态转换图的基本概念,正则表达式及有限自动机的基本概念;正规文法构造相应的状态转换图的基本方法;正则表达式构造有限自动机的基本方法及不确定有限自动机确定化的基本方法;词法分析程序的设计与编写。

2.目标任务

给出补充后描述 C 语言子集单词符号的正则文法,设计并实现其词法分析程序。

[设计说明]

(1)可将该语言设计成大小写不敏感,也可设计成大小写敏感,用户定义的标识符最长不超过32个字符;
(2)字母为a-z,A-Z,数字为0-9;
(3)可以对上述文法进行扩充和改造;(4)“/……/”和“//”(一行内)为程序的注释部分。

[设计要求]

(1)给出各单词符号的类别编码;
(2)词法分析程序应能发现输入串中的错误;
(3)词法分析作为单独一遍编写,词法分析结果为二元式序列组成的中间文件;(4)设计两个测试用例(尽可能完备),并给出测试结果。3.任务分析重点解决正则文法到状态转换图的转化问题,词法分析程序的实现。

实验完成:

修改后的正则文法:
<标识符>->字母|<标识符>字母|<标识符>数字
<无符号整数>->数字|<无符号整数>数字
<单字符分界符>->;|,|(|)|{|}|!
<双字符分界符>-><大于>=|<大于>>|<小于>=|<小于><|<小于>>|<感叹号>=|<等于>=|<斜竖>|<加号>|<减号>|<乘号>|<斜竖>=|<与>&| <或>’|’
<小于>-><
<大于>->>
<等于>->=
<斜竖>->/
<感叹号>->!
<加号>->+
<减号>->-
<乘号>->*
<与>->&
<或>->|

因为在新添加的双字符分界符中,有++,–这样的符号,所以把加减乘符号从单字符分界符中单分出来,这样可以避免绘制状态转换图的时候出现一个输入对应多个状态结果的NFA情况。

程序功能描述:

可以对给定路径的TXT文件实现C语言词法分析,可以将一个C语言程序分为关键字,标识符,运算符,无符号整数,浮点数,分隔符,注释。

程序结构描述:

1.编写词法分析程序思路

一开始,我的计划是仿照之前选做作业中的无符号数识别,通过正则文法绘制一个简单的状态转换图,接下来通过一个Switch-case结构,判断每一个输入的格式并分析是否合法。
但是,当我简单绘制了一个修改好的状态转换图之后,我发现本文法的状态太多,如果使用Switch-case格式的话,会导致整体程序过于冗长,并且因为有一些输入后状态的判断是要结合前后输入的,这样一来就会导致需要回退,所以经过综合考量之后,我决定放弃这种编码方式。
而且按照单字符分界和双字符分界进行划分是需要频繁的结合前后输入的,所以为了便于后续的整理,我将所有的分界符分成了运算符和分隔符。
最后我选择使用的编码思路是:使用string类型的变量result存储文件流一个个输出出来的符号,并且每输入一个就进行一轮判断,如果被认定为是标识符或者数字或者分隔符之类的,就可以清空result,并把判定的结果存进string类型的数组中resultArray中,进行后续的输入。最后将所有resultArray中的结果统一进行输出。

2.词法分析程序具体函数功能实现

首先,是对于关键字的说明:

我定义了一个关键字数组,其中包含有int,char,double,string等二十余个编程常用的关键字,用数组模式进行保存

接下来进行简单的函数分析和说明:
isKey函数:遍历关键字数组和此时的result字符串,判断此时读入的输入部分是不是关键字。
isOperator:判断此时的指针所指的输入符号是不是运算符号
isSeparator:判断此时的指针所指的输入符号是不是分割符号

最后是具体实现词法分析的主函数部分:
首先通过文件流,读取需要分析的文件。
接下来进行如下循环:
1.如果此时指针指向的输入符号是字母(可以用C++中自带的isalpha函数判断),文件指针后移一位,字母进入result字符串中
1.1进行后续判断:此时的result是不是关键字,如果是,清空result输出结果,
1.2如果不是关键字,并且后续依旧是字母或数字,那么就重复上述操作,通过循环将输入符号进入result,期间一直进行iskey判断,这里对于循环的次数进行限制,因为标识符不能够超过32位。
1.3 如果后续的输入符号是运算符号或者分隔符号,那么标识符的读取结束。进行iskey判定,是则输出关键字,反之输出标识符
1.4输入符号不满足上述规则的,输出error

2.如果此时指针指向的输入符号是数字,文件指针后移一位,数字进入result字符串中
2.1 如果此时后续还是数字,那么就继续读进result中
2.2 如果此时后续是分隔符或者是运算符,那么此时无符号整数读入已经完成,清空result,把结果存进resultArray中。
2.3 如果此时后续是小数点,那么继续执行2.1和2.2规则,不再对小数点进行判定,避免多个小数点的情况,最后输出的时候判定结果为浮点数。
2.4不满足上述规则的,输出error

3.如果此时指针指向的输入符号是运算符,文件指针后移一位,符号进入result字符串中
3.1对于双字符的符号进行判断,首先看result中存储的符号,再看此时文件指针指向的符号,进行if-else判断。
3.2 对于注释格式/,进行循环输入,直到读入/结束。
3.3 对于所有不满足上述组合的符号,输出error

4.如果此时指针指向的输入符号是分隔符,文件指针后移一位,符号进入result字符串中
最后,将resultArray中的词法分析结果输出,并存入test文件中。

具体代码内容如下:

#include <iostream>
#include <fstream>
#include <cassert>
#include <string>
#define LENGTH 32
using namespace std;

//判断当前字符串是否为关键字
bool isKey(string s){
    //关键字数组
    string keyArray[] = {"int","char","string","void","bool","float","double","true","false","return",
                        "if","else","while","for","default","do","public","static","switch","case","include"};
    //与当前字符串一一对比
    for(int i=0;i<sizeof(keyArray);i++){
        if(s==keyArray[i]){
            return true;
        }
        if("include"==keyArray[i]){
        	break;
		}
    }
    return false;
}

//判断当前字符是否是运算符
bool isOperator(char ch){
    if('+'==ch || '-'==ch || '*'==ch || '/'==ch || '='==ch || '<'==ch || '>'==ch || '!'==ch|| '&'==ch|| '|'==ch)
        return true;
    else
        return false;
}

//判断当前字符是否是分隔符
bool isSeparator(char ch){
    if(','==ch || ';'==ch || '{'==ch || '}'==ch || '('==ch || ')'==ch|| ':'==ch)
        return true;
    else
        return false;
}


int main( )
{
    //定义字符变量,保存从源程序中读取的单个字符
    char ch;
    //定义字符串,保存从源程序中连续读取的字符串
    string result;
    //存放每个获取的单词的值
    string resultArray[999];
    //记录获取单词的个数
    int resultNum=0;

    //代码存放的文件名
    string file = "input1.txt";

    ifstream infile;
    //将文件流对象与文件连接起来
    infile.open(file.data());
    //若失败,则输出错误消息,并终止程序运行
    assert(infile.is_open());

    //txt文本中读取空格符与换行符
    //infile >> noskipws;
    //读取文本中的一个字符
    infile>>ch;

    while (!infile.eof())
    {
        //ch是英文字母
        if(isalpha(ch)){
            result.append(1,ch);
            infile>>ch;
            //判断是否为关键字
            if(isKey(result)){
                resultArray[resultNum++]="(关键字,\""+result+"\")";
                result="";
            }
            //读入首字符为字母,继续读入字母、数字,组成标识符或者关键字
            while(isalpha(ch) || isdigit(ch)){
                result.append(1,ch);
                infile>>ch;
                if(isKey(result)){
	                resultArray[resultNum++]="(关键字,\""+result+"\")";
	                result="";						
                }
                if(result.length()==LENGTH){
                	break;
				}
            }
            //读入操作符或者分割符,正确保存标识符或者关键字
            if(isSeparator(ch) || isOperator(ch)){
                if(isKey(result)){
                    resultArray[resultNum++]="(关键字,\""+result+"\")";
                    result="";
                    continue;
                }
                else{
                    resultArray[resultNum++]="(1,\""+result+"\")";
                    result="";
                    continue;
                }

            }
            //读入不是字母、数字、运算符、标识符,继续读入直到遇到运算符或者分隔符
            else{
                result.append(1,ch);
                infile>>ch;
                while(!isSeparator(ch) && !isOperator(ch)){
                    result.append(1,ch);
                    infile>>ch;
                }
                resultArray[resultNum++]="(Error,标识符中有违规符号,\""+result+"\")";
                result="";
                continue;
            }
        }
        //读入数字
        else if(isdigit(ch)){
            result.append(1,ch);
            infile>>ch;
            //继续读入数字,组成常数
            while(isdigit(ch)){
                result.append(1,ch);
                infile>>ch;
            }
            //遇到操作符或者运算符,正常终止
            if(isOperator(ch) || isSeparator(ch)){
                resultArray[resultNum++]="(无符号整数,\""+result+"\")";
                result="";
                continue;
            }
            //也可以读小数
			else if('.'==ch){
            	result.append(1,ch);
            	infile>>ch;	
            	int num=0;
				while(isdigit(ch)){
					num++;
					result.append(1,ch);
            		infile>>ch;
				}
				if(num==0){
					resultArray[resultNum++]="(Error,小数点后没有数字,\""+result+"\")";
	                result="";
	                continue;
				}
	            if(isOperator(ch) || isSeparator(ch)){
	                resultArray[resultNum++]="(浮点数,\""+result+"\")";
	                result="";
	                continue;
	            }
				else{
	                result.append(1,ch);
	                infile>>ch;
	                while(!isSeparator(ch) && !isOperator(ch)&& !infile.eof()) {
	                    result.append(1,ch);
	                    infile>>ch;
	                }
	                resultArray[resultNum++]="(Error,浮点数后有未知符号,\""+result+"\")";
	                result="";
	                continue;
	            }			
			} 
            //读入其他错误字符
            else{
                result.append(1,ch);
                infile>>ch;
                while(!isSeparator(ch) && !isOperator(ch)&& !infile.eof()) {
                    result.append(1,ch);
                    infile>>ch;
                }
                resultArray[resultNum++]="(Error,整数后有错误符号,\""+result+"\")";
                result="";
                continue;
            }
        }
        //遇到运算符
        else if(isOperator(ch)){
            result.append(1,ch);
            infile>>ch;
            //判断是否存在<=、>=、!=、==、+=、-=、*=
            if("<"==result || ">"==result || "!"==result|| "="==result ||"+"==result ||"-"==result ||"*"==result){
                if('='==ch){
                    result.append(1,ch);
                    infile>>ch;
                }
            }
            if("+"==result){
            	if('+'==ch){
            		result.append(1,ch);
                    infile>>ch;
				}
			}
            if("-"==result){
            	if('-'==ch){
            		result.append(1,ch);
                    infile>>ch;
				}
			}
			if(">"==result){
            	if('>'==ch){
            		result.append(1,ch);
                    infile>>ch;
				}
			}
			if("<"==result){
            	if('<'==ch||'>'==ch){
            		result.append(1,ch);
                    infile>>ch;
				}
			}
			if("&"==result){
            	if('&'==ch){
            		result.append(1,ch);
                    infile>>ch;
				}
				else{
					resultArray[resultNum++]="(Error:逻辑与运算符号错误:\""+result+"\")";
                	result="";
				}
			}
			if("|"==result){
            	if('|'==ch){
            		result.append(1,ch);
                    infile>>ch;
				}
				else{
					resultArray[resultNum++]="(Error:逻辑或运算符号错误:\""+result+"\")";
                	result="";
				}
			}
            if("/"==result){
            	if('*'==ch){
            		result.append(1,ch);
                    infile>>ch;
            		while(1){
            			//这里一定要把文件指针往后移动两位,不然的话/*/形式的也会被无认为是注释(虽然应该不会有人这么写) 
            			result.append(1,ch);
                    	infile>>ch;
                    	char ch2 = result.at(result.length()-1);
                    	if('*'==ch2 && '/'==ch){
                    		result.append(1,ch);
                    		infile>>ch;
                    		resultArray[resultNum++]="(可以跨行的注释内容:\""+result+"\")";
                			result="";
                    		break;
						}
					}
					continue;
				}
				else if('='==ch){
					result.append(1,ch);
                    infile>>ch;
				}
			}
            //下一个读入符为字母、数字、分隔符,即正确
            if(isalpha(ch) || isdigit(ch) || isSeparator(ch)){
                resultArray[resultNum++]="(运算符,\""+result+"\")";
                result="";
                continue;
            }
            else{
                //将错误输入符一起读入,直到正确
                while(!isSeparator(ch) && !isalpha(ch) && !isdigit(ch) && !infile.eof()){
                    result.append(1,ch);
                    infile>>ch;
                }
                resultArray[resultNum++]="(Error,\""+result+"\")";
                result="";
                continue;
            }
        }
        //读取到分隔符
        else if(isSeparator(ch)){
            result.append(1,ch);
            resultArray[resultNum++]="(分隔符,\""+result+"\")";
            result="";
            infile>>ch;
        }
        //读取到未定义输入
        else{
            //出错处理
            result.append(1,ch);
            resultArray[resultNum++]="(Error,遇到了未定义输入(非字母数字,运算符,分隔符),且不在注释中,\""+result+"\")";
            result="";
            infile>>ch;

        }
    }
    //关闭文件输入流
    infile.close();

    //以 (单词类编码,值) 输出结果
    for(int i=0;i<resultNum;i++){
        cout<<resultArray[i]<<endl;
    }

    ofstream ofs;						//定义流对象
    ofs.open("text.txt",ios::out);		//以写的方式打开文件
    for(int i=0;i<resultNum;i++){
        ofs<<resultArray[i]<<endl;
    }
    ofs.close();

	
   	return 0;
}

实验结果:

使用的输入文件:
main(){
	int  a2,b;
	a2 == 10**4;
	b = a + 20;
%
	int 3ab;
	if(a<=3){
		a=5.4343#;
	}
}
/*yfjo
fwjoer*/

得到结果为:

在这里插入图片描述

使用的测试输入文件2为:

int isprime(int);
main()
{
    int i,c=0;
    for(i=2;i<=100;i++)
    {
        if(isprime(i))
        {
            printf("%4d",i);
            c++;
            if(c%10==0)printf("\n");
        }
    }
    printf("\n");
}
int isprime(int n)
{
    int i;
    for(i=2;i<=sqrt(n);i++)
        if(n%i==0)return 0;
    return 1;
}

测试样例2得到的结果为:
(关键字,“int”)
(标识符,“isprime”)
(分隔符,“(”)
(关键字,“”)
(分隔符,“)”)
(分隔符,“;”)
(标识符,“main”)
(分隔符,“(”)
(分隔符,“{”)
(关键字,“i”)
(分隔符,“,”)
(标识符,“c”)
(运算符,“=”)
(无符号整数,“0”)
(分隔符,“;”)
(关键字,“for”)
(标识符,“(”)
(标识符,“i”)
(运算符,“2”)
(分隔符,“<=”)
(无符号整数,“100”)
(分隔符,“++”)
(分隔符,“if”)
(标识符,“{”)
(标识符,“printf”)
(分隔符,“(”)
(Error,遇到了未定义输入(非字母数字,运算符,分隔符),且不在注释中,“”“)
(Error,遇到了未定义输入(非字母数字,运算符,分隔符),且不在注释中,”%“)
(Error,整数后有错误符号,“4d””)
(分隔符,“(”)
(Error,标识符中有违规符号,“c%10”)
(运算符,“" )
(无符号整数,“)”)
(标识符,”“)
(Error,标识符中有违规符号,“n””)
(分隔符,“;”)
(分隔符,“}”)
(分隔符,“}”)
(标识符,“}”)
(关键字,“n”)
(分隔符,“<=”)
(标识符,“sqrt”)
(分隔符,“)”)
(关键字,“n%i”)
(运算符,"
”)
(无符号整数,“return”)
(标识符,“1”)
(分隔符,“}”)

原文地址:https://blog.csdn.net/weixin_51529433

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 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