目录
任务描述
本关任务:加深对词法分析器的工作过程的理解;加强对词法分析方法的掌握;能够采用一种编程语言实现简单的词法分析程序;能够使用自己编写的分析程序对简单的程序段进行词法分析。
相关知识
为了完成本关任务,你需要掌握:词法分析程序设计与实现。
词法分析的基本知识
词法分析程序( Lexical analyzer,简称 Lexer ),负责从左到右逐个字符地对源程序进行扫描和分解,根据语言的词法规则识别出一个个的单词符号。
因此一个词法分析程序应具备如下功能:
从左至右扫描构成源程序的字符流
识别出有词法意义的单词
返回单词记录,或词法错误信息
由上可知词法分析中的一个重要环节为识别单词符号类型,为了便于语法分析,通常将单词符号分为五类。
标识符 用来命名程序中出现的变量、数组、函数、过程、标号等,通常是一个字母开头的字母数字串,如 length,nextch 等。
基本字 也可以成为关键字或保留字。如 if,while,for,do,goto 等。他们具有标识符的形式,但他们不是由用户而是由语言定义的,其意义是约定的。多数语言中规定,他们不能作为标识符或者标识符的前缀,即用户不能使用它们来定义用户使用的名字,故我们称它为保留字,这些语言如 Pascal 和 C 等。但也有的语言允许将基本字作为标识符或者标识符的前缀,这类语言如 Fortran 等。
常数 包括各种类型的常数,如整型、实型、字符型、布尔型等。如:
5
、3.1415926
、a
、TRUE
等都是常数。运算符 算术运算符
+
、-
、×
、÷
;关系运算符<
,<=
,>
,>=
,==
,!=
以及逻辑运算符&&
,()
,||
或者!
等。界符 如
,
、;
等单字界符和/
,/
,//
等双字界符,空白符等。
在进行词法分析后,识别出来的单词应该采用某种中间表示形式,以便为编译后续阶段方便地引用。通常一个单词用一个二元式来表示: (单词类别,单词的属性)
其中,第一元用于区分单词所属的类别,以整数编码表示。第二元用于区分该类别中的哪一个单词符号,即单词符号的值。
实验步骤
由一个词法分析程序应具备的功能来看,我们的程序具有如下要求:
- 对单词的构词规则有明确的定义;
- 编写的分析程序能够正确识别源程序中的单词符号;
- 识别出的单词以<种别码,值>的形式保存在符号表中,正确设计和维护符号表;
- 对于源程序中的词法错误,能够做出简单的错误处理,给出简单的错误提示,保证顺利完成整个源程序的词法分析;
下面我们进行实验代码的步骤分析。
-
定义目标语言的可用符号表和构词规则。
我们需要对五种单词符号进行识别分析,这里将单词符号分为三大块进行识别。首先判断字符是否为关键字或者标识符,并与已定义好的关键字进行比较,从而判断为关键字或者标识符;然后是数字的识别;最后是其他字符的判断,它们被一一定义好的判断进行识别,这样所有的字符便被识别出来了。标示符和关键字的判断 -
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) //可能是标示符或者关键字
字符与关键字的区别通过对比得出:
if (strcmp(token, rwtab1[n]) == 0) { syn = 2; break; } else if (strcmp(token, rwtab[n]) == 0) { syn = 1; break; } }
对于数字的识别:
else if ((ch >= '0' && ch <= '9')) //数字
其他字符的识别,他们被一一定义进行识别:
else switch (ch) //其他字符 { case'<':m = 0; token[m++] = ch; ch = prog[p++]; if (ch == '>') { syn = 4; token[m++] = ch; } else if (ch == '=') { syn = 4; token[m++] = ch; } else { syn = 4; p--; } break; case'>':m = 0; token[m++] = ch; ch = prog[p++]; if (ch == '=') { syn = 4; token[m++] = ch; } else { syn = 4; p--; } break; case':':m = 0; token[m++] = ch; ch = prog[p++]; if (ch == '=') { syn = 4; token[m++] = ch; } else { syn = 4; p--; } break; case'*':syn = 4; token[0] = ch; break; case'/':syn = 4; token[0] = ch; break; case'+':syn = 4; token[0] = ch; break; case'-':syn = 4; token[0] = ch; break; case'=':syn = 4; token[0] = ch; break; case';':syn = 5; token[0] = ch; break; case',':syn = 5; token[0] = ch; break; case'(':syn = 5; token[0] = ch; break; case')':syn = 5; token[0] = ch; break; case'{':syn = 5; token[0] = ch; break; case'}':syn = 5; token[0] = ch; break; case'#':syn = 0; token[0] = ch; break; case'\n':syn = -2; break; default: syn = -1; break; } }
-
依次读入源程序符号,对源程序进行单词切分和识别,直到源程序结束。
字符的输入我们使用cin.get()
获取,并切分保存在prog
中:p = 0; row = 1; cout << "Please input string:" << endl; do { cin.get(ch); prog[p++] = ch; } while (ch != '#'); p = 0;
-
对正确的单词,按照它的种别以<种别码,值>的形式保存在符号表中;
-
对不正确的单词,做出错误处理。 单词识别后,我们对返回的符号按3,4的规则进行输出:
{ scaner(); switch (syn) { case 0: break; case 3: cout << "(" << syn << "," << sum << ")" << endl; break; case -1: cout << "Error in row " << row << "!" << endl; break; case -2: row = row++; break; default: cout << "(" << syn << "," << token << ")" << endl; break; } } while (syn != 0);
开始你的任务吧,祝你成功!
实验代码其实很好补充,原代码有一个很大的问题就是prog数组太小,导致数组溢出,改大一点,这里我改成了200,运行即可通过。
实验代码
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
char prog[200], token[20];
char ch;
int syn, p, m = 0, n, row, sum = 0;
const char* rwtab[8] = { "if","int","for","while","do","return","break","continue" };
const char* rwtab1[8] = { "main","a","b","c","d","e","f","g" };
void scaner()
{
/*
共分为三大块,分别是标示符、数字、符号,对应下面的 if else if 和 else
*/
for (n = 0; n < 8; n++) token[n] = NULL;
ch = prog[p++];
while (ch == ' ')
{
ch = prog[p];
p++;
}
/*******Beign*******/
/*进行标示符或者关键字的识别*/
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) //可能是标示符或者关键字
{
m = 0;
while ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
{
token[m++] = ch;
ch = prog[p++];
}
token[m++] = '\0';
p--;
syn = 2;
for (n = 0; n < 8; n++) //将识别出来的字符和已定义的关键字作比较,
if (strcmp(token, rwtab1[n]) == 0)
{
syn = 2;
break;
}
else if (strcmp(token, rwtab[n]) == 0) {
syn = 1;
break;
}
}
/********End********/
/*******Beign*******/
/*进行数字的识别*/
else if ((ch >= '0' && ch <= '9')) //数字
{
{
sum = 0;
while ((ch >= '0' && ch <= '9'))
{
sum = sum * 10 + ch - '0';
ch = prog[p++];
syn = 3;
}
}
p--;
syn = 3;
if (sum > 32767)
syn = -1;
}
/********End********/
/*******Beign*******/
/*进行其他字符的识别*/
else switch (ch) //其他字符
{
case'<':m = 0; token[m++] = ch;
ch = prog[p++];
if (ch == '>')
{
syn = 4;
token[m++] = ch;
}
else if (ch == '=')
{
syn = 4;
token[m++] = ch;
}
else
{
syn = 4;
p--;
}
break;
case'>':m = 0; token[m++] = ch;
ch = prog[p++];
if (ch == '=')
{
syn = 4;
token[m++] = ch;
}
else
{
syn = 4;
p--;
}
break;
case':':m = 0; token[m++] = ch;
ch = prog[p++];
if (ch == '=')
{
syn = 4;
token[m++] = ch;
}
else
{
syn = 4;
p--;
}
break;
case'*':syn = 4; token[0] = ch; break;
case'/':syn = 4; token[0] = ch; break;
case'+':syn = 4; token[0] = ch; break;
case'-':syn = 4; token[0] = ch; break;
case'=':syn = 4; token[0] = ch; break;
case';':syn = 5; token[0] = ch; break;
case',':syn = 5; token[0] = ch; break;
case'(':syn = 5; token[0] = ch; break;
case')':syn = 5; token[0] = ch; break;
case'{':syn = 5; token[0] = ch; break;
case'}':syn = 5; token[0] = ch; break;
case'#':syn = 0; token[0] = ch; break;
case'\n':syn = -2; break;
default: syn = -1; break;
}
/********End********/
}
int main()
{
/*******Beign*******/
/*输入*/
p = 0;
row = 1;
cout << "Please input string:" << endl;
do
{
cin.get(ch);
prog[p++] = ch;
} while (ch != '#');
/********End********/
/*******Beign*******/
/*输出*/
p = 0;
do
{
scaner();
switch (syn)
{
case 0: break;
case 3: cout << "(" << syn << "," << sum << ")" << endl; break;
case -1: cout << "Error in row " << row << "!" << endl; break;
case -2: row++; break;
default: cout << "(" << syn << "," << token << ")" << endl; break;
}
} while (syn != 0);
/********End********/
}
运行成功
本代码仅供参考,不一定正确,只是能通过平台测试,希望大家提出宝贵的意见,感谢!