📄 scanner.cpp
字号:
/****************************************************/
/* 文件 scanner.cpp */
/* 说明 TINY编译器的词法扫描器实现 */
/* 主题 编译器结构:原理和实例 */
/****************************************************/
/************ 该代码文件所包含的头文件 **************/
#include "globals.h" /* 该头文件globals.h定义了全局类与变量 */
#include "string.h"
#include "ctype.h" /* 用到了该库中的isalnum,isalpha,isdigit函数 */
#include "util.h" /* 该头文件util.h定义了相关功能函数 */
#include "math.h" /* 用到了这个库里的取模运算 */
/*****************词法分析器确定性有限自动机DFA的状态类型*************/
/* START 开始状态; INASSIGN 赋值状态; INRANGE 下标范围状态; */
/* INNUM 数字状态; INID 标识符状态; DONE 完成状态; */
/* INCHAR 字符状态;INCOMMENT 注释状态; */
typedef enum
{ START,INASSIGN,INRANGE,INCOMMENT,INNUM,INID,INCHAR,DONE }
StateType;
/* tokenString用于保存标识符和保留字单词的词元,长度41 */
char tokenString[MAXTOKENLEN+1];
/* BUFLEN源代码行的输入缓冲区长度为256 */
#define BUFLEN 256
/* lineBuf为当前输入代码行缓冲区 */
static char lineBuf[BUFLEN];
/* linepos为在代码缓冲区LineBuf中的当前字符位置,初始为0 */
static int linepos = 0;
/* bufsize为当前缓冲器中所存字串大小 */
static int bufsize = 0;
/* EOF_flag当为文件尾时,改变函数ungetNextChar功能 */
static int EOF_flag = FALSE;
/*******************************************************************/
/* 函数名 getNextChar */
/* 功 能 取得下一非空字符函数 */
/* 说 明 该函数从输入缓冲区lineBuf中取得下一个非空字符 */
/* 如果lineBuf中的字串已经读完,则从源代码文件中读入一新行 */
/*******************************************************************/
static int getNextChar(void)
{
/* 当前代码输入行缓冲器lineBuf已经耗尽 */
if (!(linepos < bufsize))
{
/* 源代码行号lineno加1 */
lineno++;
/* 从源文件source中读入BUFLEN-2(254)个字符到行缓冲区lineBuf中 *
* fgets在的lineBuf末尾保留换行符.并在末尾加了一个NULL字符表示结束 */
if (fgets(lineBuf,BUFLEN-1,source))
{
/* 如果源文件追踪标志EchoSource为TRUE *
* 将源程序行号lineno及行内容lineBuf在词法扫描时写入列表文件listing */
if (EchoSource) fprintf(listing,"%4d: %s",lineno,lineBuf);
/* 取得当前输入源代码行的实际长度,送给变量bufsize */
bufsize = strlen(lineBuf);
/* 输入行缓冲区lineBuf中当前字符位置linepos指向lineBuf开始位置 */
linepos = 0;
/* 取得输入行缓冲区lineBuf中下一字符 */
return lineBuf[linepos++];
}
else
{
/* 未能成功读入新的代码行,fget函数返回值为NULL *
* 已经到源代码文件末尾,设置EOF_flag标志为TRUE */
EOF_flag = TRUE;
/* 函数返回EOF */
return EOF;
}
}
/* 行输入缓冲区lineBuf中字符还未读完,直接取其中下一字符,函数返回所取字符 */
else return lineBuf[linepos++];
}
/********************************************************/
/* 函数名 ungetNextChar */
/* 功 能 字符回退函数 */
/* 说 明 该过程在行输入缓冲区lineBuf中回退一个字符 */
/* 用于超前读字符后不匹配时候的回退 */
/********************************************************/
static void ungetNextChar(void)
{
/* 如果EOF_flag标志为FALSE,不是处于源文件末尾 *
* 输入行缓冲区lineBuf中当前字符位置linepos减1 */
if (!EOF_flag) linepos-- ;
}
/******************* 保留字查找表 ********************/
static struct
{ char* str;
LexType tok;
} reservedWords[MAXRESERVED]
= { {"program",PROGRAM},{"type",TYPE},{"var",VAR},{"procedure",PROCEDURE}
,{"begin",BEGIN},{"end",END},{"array",ARRAY},{"of",OF},{"record",RECORD}
,{"if",IF},{"then",THEN},{"else",ELSE},{"fi",FI},{"while",WHILE}
,{"do",DO},{"endwh",ENDWH},{"read",READ},{"write",WRITE},{"return",RETURN}
,{"integer",INTEGER},{"char",CHAR} };
/**************************************************************/
/* 函数名 reservedLookup */
/* 功 能 保留字查找函数 */
/* 说 明 使用线性查找,查看一个标识符是否是保留字 */
/* 标识符如果在保留字表中则返回相应单词,否则返回单词ID */
/**************************************************************/
static LexType reservedLookup (char * s)
{
int i;
/* 在保留字表中查找,MAXRESERVED已经定义为8,为保留字数 */
for (i=0;i<MAXRESERVED;i++)
/* 线性查保留字表,察看函数参数s指定标识符是否在表中 *
* 当两字符串匹配的时候,函数strcmp返回值为0(FALSE) */
if (!strcmp(s,reservedWords[i].str))
/* 字符串s与保留字表中某一表项匹配,函数返回对应保留字单词 */
return reservedWords[i].tok;
/* 字符串s未在保留字表中找到,函数返回标识符单词ID */
return ID;
}
/****************************************
********* 词法扫描器基本函数 **********
****************************************/
/************************************************************/
/* 函数名 getTokenlist */
/* 功 能 取得单词函数 */
/* 说 明 函数从源文件字符串序列中获取所有Token序列 */
/* 使用确定性有限自动机DFA,采用直接转向法 */
/* 超前读字符,对保留字采用查表方式识别 */
/* 产生词法错误时候,仅仅略过产生错误的字符,不加改正 */
/************************************************************/
void getTokenlist(void)
{
ChainNodeType *chainHead; /*链表的表头指针*/
ChainNodeType *currentNode; /*指向处理当前Token的当前结点*/
ChainNodeType *preNode; /*指向当前结点的前驱结点*/
ChainNodeType *tempNode; /*临时指针,用于释放链表部分*/
TokenType currentToken; /*存放当前的Token*/
/*产生链表的第一个结点*/
chainHead=preNode=currentNode=(ChainNodeType *)malloc(CHAINNODELEN);
/*初始化当前结点中,指向下一个结点的指针为空*/
(*currentNode).nextToken=NULL;
do
{ /* tokenStringIndex用于记录当前正在识别单词的词元存储区 *
* tokenString中的当前正在识别字符位置,初始为0 */
int tokenStringIndex = 0;
/* 当前状态标志state,始终都是以START作为开始 */
StateType state = START;
/* tokenString的存储标志save,整数类型 *
* 决定当前识别字符是否存入当前识别单词词元存储区tokenString */
int save;
/* 当前确定性有限自动机DFA状态state不是完成状态DONE */
while (state != DONE)
{
/* 从源代码文件中获取下一个字符,送入变量c作为当前字符 */
int c = getNextChar();
/* 当前正识别字符的存储标志save初始为TRUE */
save = TRUE;
switch (state)
{
/* 当前DFA状态state为开始状态START,DFA处于当前单词开始位置 */
case START:
/* 当前字符c为数字,当前DFA状态state设置为数字状态INNUM *
* 确定性有限自动机DFA处于数字类型单词中 */
if (isdigit(c))
state = INNUM;
/* 当前字符c为字母,当前DFA状态state设置为标识符状态INID *
* 确定性有限自动机DFA处于标识符类型单词中 */
else if (isalpha(c))
state = INID;
/* 当前字符c为冒号,当前DFA状态state设置为赋值状态INASSIGN *
* 确定性有限自动机DFA处于赋值类型单词中 */
else if (c == ':')
state = INASSIGN;
/* 当前字符c为.,当前DFA状态state设置为数组下标界限状态*/
/* INRANGE,确定性有限自动机DFA处于数组下标界限类型单词中*/
else if (c == '.')
state = INRANGE;
/* 当前字符c为',当前DFA状态state设置为字符标志状态*/
/* INCHAR,确定性有限自动机DFA处于字符标志类型单词中*/
else if (c == '\'')
{
save = FALSE;
state = INCHAR;
}
/* 当前字符c为空白(空格,制表符,换行符),字符存储标志save设置为FALSE *
* 当前字符为分隔符,不需要产生单词,无须存储 */
else if ((c == ' ') || (c == '\t') || (c == '\n'))
save = FALSE;
/* 当前字符c为左括号,字符存储标志save设置为FALSE *
* 当前DFA状态state设置为注释状态INCOMMENT *
* 确定性有限自动机DFA处于注释中,不生成单词,无需存储 */
else if (c == '{')
{
save = FALSE;
state = INCOMMENT;
}
/* 当前字符c为其它字符,当前DFA状态state设置为完成状态DONE *
* 确定性有限自动机DFA处于单词的结束位置,需进一步分类处理 */
else
{
state = DONE;
switch (c)
{
/* 当前字符c为EOF,字符存储标志save设置为FALSE,无需存储 *
* 当前识别单词返回值currentToken设置为文件结束单词ENDFILE */
case EOF:
save = FALSE;
currentToken.Lex = ENDFILE;
break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -