【编译原理】实验:词法分析器的实现与分析(本校实验平台的题)

实验题目:实现词法分析器

注:本题解参考文章在最后列出(部分代码引用的是那里面的);实现语言为:C++

注:本文只提供一个思路,作为学习过程中的一个参考,实现词法分析器有很多方法,本文只是其中一种,若想用本文代码提交作业请多改,不然查重率过不了。
【问题描述】

【文法定义】:

<标识符>::=<字母>{<字母>|<数字>} //标识符和关键字都不区分大小写,比如if和IF均为关键字,不允许出现与关键字相同的标识符
<字母>::=_|a|...|z|A|...|Z
<数字>::=0|1|...|9
<整数>::=[+|-]<无符号整数>
<无符号整数>::=<数字>{<数字>}
<字符>::=‘<加法运算符>’|’<乘法运算符>’|’<字母>’|’<数字>’
<加法运算符>::=+|-
<乘法运算符>::=*|/
<字符串>::="{十进制编码为32,33,35-126的ASCII字符}" //字符串中要求至少有一个字符

ASCII码

【问题描述】
请根据给定的文法设计并实现词法分析程序,从源程序中识别出单词,记录其单词类别和单词值,输入输出及处理要求如下:
(1)数据结构和与语法分析程序的接口请自行定义;类别码需按下表格式统一定义;
(2)为了方便进行自动评测,输入的被编译源文件统一命名为testfile.txt(注意不要写错文件名);输出的结果文件统一命名为output.txt(注意不要写错文件名),结果文件中每行按如下方式组织:
单词类别码 单词的字符/字符串形式(中间仅用一个空格间隔)

单词的类别码请统一按如下形式定义:
类别码

【输入形式】testfile.txt中的符合文法要求的测试程序。
【输出形式】要求将词法分析结果输出至output.txt中。

【样例输入】

const int const1 = 1, const2 = -100;
const char const3 = ‘_’;
int change1;
char change3;
int gets1(int var1,int var2){
change1 = var1 + var2;
return (change1);
}
void main(){
printf(“Hello World”);
printf(gets1(10, 20));
}

【样例输出】

CONSTTK const
INTTK int
IDENFR const1
ASSIGN =
INTCON 1
COMMA ,
IDENFR const2
ASSIGN =
MINU -
INTCON 100
SEMICN ;
CONSTTK const
CHARTK char
IDENFR const3
ASSIGN =
CHARCON _
SEMICN ;
INTTK int
IDENFR change1
SEMICN ;
CHARTK char
IDENFR change3
SEMICN ;
INTTK int
IDENFR gets1
LPARENT (
INTTK int
IDENFR var1
COMMA ,
INTTK int
IDENFR var2
RPARENT )
LBRACE {
IDENFR change1
ASSIGN =
IDENFR var1
PLUS +
IDENFR var2
SEMICN ;
RETURNTK return
LPARENT (
IDENFR change1
RPARENT )
SEMICN ;
RBRACE }
VOIDTK void
MAINTK main
LPARENT (
RPARENT )
LBRACE {
PRINTFTK printf
LPARENT (
STRCON Hello World
RPARENT )
SEMICN ;
PRINTFTK printf
LPARENT (
IDENFR gets1
LPARENT (
INTCON 10
COMMA ,
INTCON 20
RPARENT )
RPARENT )
SEMICN ;
RBRACE }

以下是代码思路:
把读入的文件内容全存在ScanBuffer[1000]这个字符串数组里面,里面完整存,空格回车这些都存了。然后用char变量 ch 一个个的读出来,随后判断读出来的是什么类型,再进行分析归类,然后输出到文件里面。

1.代码部分注释很多,不清楚的地方可以仔细看一下注释;
2.有不懂的地方建议自行搜索相关知识点;
3.代码里有些地方是为了测试输出写的,都已经注释掉了;
4.代码里有没用到的变量比如show_strToken变量,是为了debug时候查看全局变量strToken而设置的(全局变量string类型在debug时候没法看到里面的值);
5.代码能够直接在实验平台上运行通过。

#include <iostream>
#include <fstream>
#include<string>
using namespace std;
#define NOT_Found 404 //自己定义的数字404,因为用到它的地方那个数字不可能是404

//全局变量
char ch;//用来存字符
string strToken = "";//当前字符串
string ScanBuffer[1000];//我开了1000来存,里面存放文件里面所有内容,文件以EOF结尾,叫缓存区
int row = 0;
int col = 0; //缓存区的行与列
int endrow;


string Reserved_words[]={"const","int","char","void","main","if","else","do","while","for","scanf","printf","return"};//保留字,进来字符串的时候验证一下
string Category_code[] ={"CONSTTK","INTTK","CHARTK","VOIDTK","MAINTK","IFTK","ELSETK","DOTK","WHILETK","FORTK","SCANFTK","PRINTFTK","RETURNTK"};//保留字对应的类别码,要与上面一一对应,这样可以对应下标

//声明全局函数
void Input();//子程序过程,将源代码输入进扫描缓冲区
void GetChar();//子过程程序,将下一输入字符读到ch中,搜索指示器前移一字符位置
void GetDel();//子程序过程,检查ch中的字符是否为分隔符(空格、回车之类的)。若是,则调用GetChar直至ch中进入一个非分隔符(delimiters->分隔符)
void Concat();//子程序过程,,将ch中的字符连接到strToken之后
bool IsLetter();//布尔函数过程,判断ch中的字符是否为字母
bool IsDigit();//布尔函数构成,判断ch中的字符是否为数字
int Reserve();//整型函数过程,对strToken中的字符串查找保留字表,若它是一个保留字则返回它的index下标,否则返回NOT_Found(404不是保留字的编码)
void Retract();//子程序过程,将搜索指示器回调一个字符位置,将ch置为空白字符
int GetNumFromStr(string s);//将数字字符串返回原来int型数字,存起来输出用(本文法中的数字只有int型的呢)
int Str_Length(string str);//返回字符串长度,用来判断在字符串内的‘a’、‘2’为char类型
void Lexical_analysis();//词法分析函数实现

//函数实现
void Input(){  //空格回车都在里面了
	int i=0;
	char c;//输入流中的当前字符
        fstream in;
		in.open("testfile5.txt",ios::in);
		while(in.peek()!=EOF){
			c=in.get();
			ScanBuffer[i].push_back(c);
			if(c=='\n') i++;
		}
		endrow=--i;
		//测试,看能不能完整输入:
/*		cout <<"endrow =="<<endrow<<endl; //7行代码,endrow=7;
		int l = 100;
		for(int k=0;k<l;k++){
            cout<<ScanBuffer[k]<<endl;
		}
*/
}
void GetChar(){
	ch=ScanBuffer[row][col];
	col++;
}
void GetDel(){
	while(ch=='\n'){
		row++;
		col=0;
		GetChar();
	}
	while(ch==' '||ch=='\t')
        GetChar();
}
void Concat(){
	strToken.push_back(ch);
}
bool IsLetter(){ //注意本文法中的字母,还包括了下划线
    if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')||(ch=='_'))
        return true;
	return false;
}
bool IsDigit(){
    if(ch >='0'&&ch<='9')
        return true;
	return false;
}

int Reserve(){ //找保留字表,
	int i;
	for(i=0;i<13;i++)
		if(Reserved_words[i]==strToken)
            return i;
	return NOT_Found; //循环之后发现不是保留字
}


void Retract(){//回退一个
	col--;
	ch='\0';
}
int GetNumFromStr(string s){
    int ans = 0;
	for(int i=0;i<s.size();i++) ans = ans * 10+ s[i]-'0';
	return ans;
}
/*
bool IsAllDigit(){
    int l = strToken.length();
    bool flag=true;
    for(int k =0;k<l;k++){
        if(strToken[k]>='0'&&strToken[k]<='9'){

        }//flag 保持true
        else{
        flag = false;
        break;}
    }
    return flag;
}
*/
int Str_Length(string str){
    return str.length();
}

void Lexical_analysis()
{
    ofstream out("output.txt");//输出到文件,之后把cout全变成out就行了。
	while(row!=endrow+1){ //别超行读ScanBuffer
	    string Category_code_num;//要输出的类别码
		int store_number;//用IsAllDigit()看一下,纯数字输入的话把用数字存在这里面然后输出。
	//	strToken="";
//因为debug不能查看string全局变量,所以需要一个局部变量show_strToken转接一下
        string show_strToken=strToken;
		GetChar();
		GetDel();//得到一个ch合法输入(没有空格、回车的ch)

		if(IsLetter()){  //字母或下划线开头的进这里
			while(IsLetter()||IsDigit()){
				Concat();
				GetChar();
			}
//此处
			show_strToken=strToken;

			Retract();//上面循环里取多了还回去
			int isfind; //接收保留字表的下标
			isfind=Reserve();
			if(isfind != NOT_Found){ //在表里面找到保留字啦!
			    Category_code_num = Category_code[isfind];
                out<<Category_code_num<<" "<<strToken<<endl;
                strToken="";//输出之后就清零!!
			}
			else{ //不是保留字,那就是标识符咯(字符串有自己的判定语句,在下面)
                out<<"IDENFR "<<strToken<<endl;
                strToken="";//用完之后就清零!!
			}
		}
		else if(IsDigit()){ //数字开头的进这里
			while(IsDigit()){
				Concat();
				GetChar();
			}
//此处
			show_strToken=strToken;

			Retract();//上面循环里取多了还回去
			//把数字存起来,存进变量:store_number
			store_number = GetNumFromStr(strToken);
			strToken="";//用完之后就清零!!
			out<<"INTCON "<<store_number<<endl;
		}
		//其他符号的进下面
		else if(ch=='+')
			out<<"PLUS "<<ch<<endl;
		else if(ch=='-')
			out<<"MINU "<<ch<<endl;
		else if(ch=='*')
			out<<"MULT "<<ch<<endl;
		else if(ch=='/')
			out<<"DIV "<<ch<<endl;
		else if(ch=='<'){ //判断一下是<还是<=,要是下一个字符不是=,拿了的给人还回去,用Retract()函数
			GetChar();
			if(ch=='=')
				out<<"LEQ "<<"<="<<endl;
			else{
				Retract();//把人家还回去
				out<<"LSS "<<"<"<<endl;
			}
		}
		else if(ch=='>'){
			GetChar();
			if(ch=='=')
				out<<"GEQ >="<<endl;
			else{
				Retract();
				out<<"GRE >"<<endl;
			}
		}
		else if(ch=='='){//同上,看一下是不是俩等号‘==’
            GetChar();
            if(ch=='='){
                out<<"EQL =="<<endl;
            }else{
                Retract();//还回去
                out<<"ASSIGN ="<<endl;
            }
		}
		else if(ch=='!'){//由于都是合法输入(测试样例里面),!只在字符串里面出现,所以此处出现的得是!=
            GetChar();
            if(ch == '='){
                out<<"NEQ !="<<endl;
            }
            else{
                Retract();//还回去并且无操作
            }
		}
		else if(ch==';')
			out<<"SEMICN ;"<<endl;
		else if(ch==',')
			out<<"COMMA ,"<<endl;
		else if(ch=='(')
			out<<"LPARENT ("<<endl;
		else if(ch==')')
			out<<"RPARENT )"<<endl;
        else if(ch=='[')
			out<<"LBRACK ["<<endl;
        else if(ch==']')
			out<<"RBRACK ]"<<endl;
        else if(ch=='{')
			out<<"LBRACE {"<<endl;
        else if(ch=='}')
			out<<"RBRACE }"<<endl;
        else if(int(ch)==34||int(ch)==39){//判断字符串 ps:根据对本题的输入输出案例分析,双引号单引号只用来识别字符串用,不输出,不属于字符类别也不属于字符串类别
                //用ASCII码来比较,中文的双引号是-95,英文的双引号是34,单引号是39,已测试过,可行。
            GetChar();
            while(int(ch)!=34&&int(ch)!=39){
                Concat();
				GetChar();
//此处
			show_strToken=strToken;

            }//出循环的时候,ch等于回引号,不需要还回去;字符串收集结束。
            int length = Str_Length(strToken);
            if(length ==1){ //说明是单引号里面的字符型
                out<<"CHARCON "<<strToken<<endl;
            }
            else{
                out<<"STRCON "<<strToken<<endl;
            }
            strToken="";//输出之后就清零!!
        }

	}//while end

}//function end

int main()
{
    Input();
    Lexical_analysis();
    return 0;
}

测试样例输出与标准答案比对:
用word2010的审阅–比较功能:
word比对
结果:
成功

主要的参考文档:【编译原理】实验一 词法分析器设计与实现

由以上文法定义可以看出,比实际文法要简单,只有整数型变量,且-100分开判定,并不是整数-100而是负号和整数100,所以此词法分析器只做学习使用。

在实验平台上运行时选择正确的编译器很重要,本人因为选了C编译器编译不过一度自闭,直到求助的某位美女告诉我可能是我选错编译器了,才破了案。

over。

### 回答1: 很抱歉,作为AI语言模型,我无法设计实现语法分析程序。但是,我可以提供一些关于语法分析程序的基本知识和步骤: 语法分析程序编译器中的一个重要组成部分,它的主要作用是将源代码转换为抽象语法树(AST),以便后续的语义分析和代码生成。语法分析程序实现需要遵循给定文法规则,通常采用自顶向下的递归下降分析方法或自底向上的移进-归约分析方法。 具体来说,语法分析程序实现步骤包括以下几个方面: 1. 定义文法规则:根据编程语言的语法规则,定义相应的文法规则,通常采用巴克斯-诺尔范式(BNF)或扩展巴克斯-诺尔范式(EBNF)表示。 2. 构建词法分析器:将源代码分解为单词(token),并标记其类型和属性,以便后续的语法分析。 3. 实现语法分析器:根据文法规则,实现语法分析器,通常采用递归下降或移进-归约方法。递归下降方法是从文法规则的起始符号开始,递归地向下分析,直到匹配到终结符号;移进-归约方法则是从终结符号开始,逐步向上归约,直到匹配到起始符号。 4. 构建抽象语法树:在语法分析过程中,根据文法规则构建抽象语法树,以便后续的语义分析和代码生成。 5. 错误处理:在语法分析过程中,需要对语法错误进行处理,通常采用错误恢复或错误报告的方式。 以上是语法分析程序的基本步骤,实现语法分析程序需要掌握编程语言的语法规则和相应的算法和数据结构知识。 ### 回答2: 语法分析编译器的一个重要部分,它用于分析代码中的语法结构并生成具有层次结构的抽象语法树。语法分析实现需要依据给定文法来进行设计文法是一个形式化的规则集合,用于描述语言结构的准确规则。 在设计语法分析程序时,需要首先了解给定文法文法包含终结符号和非终结符号两种符号,终结符号是代码中的实际字符,例如标识符、关键字、运算符等,而非终结符号则是用来描述代码结构的符号,例如表达式、语句等。文法中还包含产生式,它是一条规则,用来描述如何从一些符号派生另一些符号序列。通常采用巴科斯-瑙尔范式(BNF)来描述文法设计语法分析程序的步骤如下: 1. 确定程序的输入和输语法分析程序的输入通常是代码文件,输则是抽象语法树。 2. 根据文法设计语法分析表。语法分析表通常采用LR分析法,这是一种自底向上的语法分析方法,它从代码的底部开始,逐步分析代码结构,直到构建完整的抽象语法树。 3. 实现语法分析器。根据语法分析表,可以实现一个自动机来对代码进行分析,并生成抽象语法树。生成抽象语法树的过程通常采用递归下降分析法,这是一种自顶向下的语法分析方法。程序从代码的顶部开始,逐步向下解析代码,直到构建完整的抽象语法树。 4. 测试和调试。对于语法分析程序,测试和调试非常重要。需要对程序进行系统测试,以确保它能够正确处理各种情况。同时还需要进行调试,以解决程序中的错误和漏洞。 总之,设计实现语法分析程序需要遵循严格的文法规则和程序规范。同时,需要不断进行测试和调试,以确保程序的正确性和稳定性。 ### 回答3: 语法分析是编译过程中的一个重要组成部分,其目的是将源代码转换为抽象语法树(AST),从而进行语义分析、优化和代码生成等工作。在实现语法分析程序时,首先需要设计一个文法,并在此基础上构建相应的解析器。 文法是一组规则,用于描述一种语言的构成方式,它通常由终结符和非终结符构成。终结符表示语言中的实际元素,如变量名、关键字、标点符号等;非终结符表示语言中的语法结构,如表达式、语句、函数等。一个文法中的规则通常采用巴克斯-瑙尔茨范式(BNF)表示,如下所示: <语句> ::= <变量声明> | <赋值语句> | <控制语句> <变量声明> ::= 'var' <变量名> '=' <表达式> ';' <赋值语句> ::= <变量名> '=' <表达式> ';' <控制语句> ::= 'if' '(' <条件表达式> ')' <语句> ('else' <语句>)? 上述文法表示了一个简单的编程语言中的语法规则,其中语句可以是变量声明、赋值语句或控制语句,并且控制语句可以有一个可选的else分支。 根据上述文法,可以设计一个递归下降解析器,实现语法分析的过程。递归下降解析器需要为每个非终结符设计一个函数,用于递归地解析语法结构,并将其转换为AST。例如,可以实现一个语句解析函数,如下所示: function parseStatement() { if (lookaheadIsVariableDeclaration()) { return parseVariableDeclaration(); } else if (lookaheadIsAssignment()) { return parseAssignment(); } else if (lookaheadIsControlFlow()) { return parseControlFlow(); } else { throw new Error('Unexpected token: ' + lookahead); } } 上述代码中的parseStatement()函数,通过调用lookaheadIsVariableDeclaration()、lookaheadIsAssignment()和lookaheadIsControlFlow()等函数,判断当前输入符号序列是否与相应的语法规则相匹配,并相应地调用parseVariableDeclaration()、parseAssignment()和parseControlFlow()等函数,递归地构建AST。 在实现具体的解析器之前,还需要考虑一些实现细节,例如如何进行输入符号序列的管理、如何对文法中的优先级和结合性进行处理、如何处理错误等。同时,还需考虑优化解析器的效率,避免重复解析和不必要的回溯。完成这些工作后,即可实现一个完整的语法分析程序,使输入的源代码能够被转换为对应的AST,为下一步的语义分析和代码生成铺平道路。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值