姓名:
学号:
指导老师:于中华
编译原理实验一报告
——手工构造 C-语言的词法分析器
1、 目的及意义
掌握正则表达式;掌握 DFA 的构造方法,能根据实际情况构造合适 DFA;
能根据 DFA 转化出成词法分析器的具体实现代码;设计一个 C-词法分析
程序,理解词法分析器实现的原理,掌握程序设计语言中的各类单词的词
法分析方法,加深对词法分析原理的理解。
2、 C-语言词法特点及正则表达式
1) 关键字:else,if,int,return,void,while 所有关键字都是保留字,
并且都是小些。
2) 专用符号:+ - * / < <= > >= == != = ; , () [] {},/* */
3) 其他标记是 ID 和 NUM,正则表达式为:
ID = letter letter*
NUM = digit digit*
letter = a|…|z|A|…|Z
digit = 0|…|9
所有大写和小写字母是有区别的。
4) 空格有空白、换行、和制表符组成。空格通常被忽略,除了它必须分
开的 ID、NUM 关键字。
5) 注释通常用通用的 C 语言符号/*…*/围起来。注释可以放在任何空白
出现的位置(级注释不能放在标记内)上,且可以超过一行。注释不
能嵌套。
3、 Token 的 DFA
页 1
姓名:
学号:
指导老师:于中华
4、 重要的数据类型数据结构
enum
ERROR,//文件结尾和错误识别
用枚举量保存 C-所有的保留字,以及后面识别出的标识符类型:
typedef
{
ENDFILE,
ID,
IF,
ASSIGN,EQ,LT,LTEQ,BG,BGEQ,PLUS,MINUS,TIMES,OVER,LPAREN,RPAREN,S
EMI,NOTEQ,CMMA,FLPAREN,FRPAREN,DLPAREN,DRPAREN//所有类型的
Token
}TokenType;
NUM,//ID 和数字
ELSE,
WHILE,//保留字
RETURN,
VOID,
INT,
enum
用枚举量保存程序中 DFA 状态转换的所有状态:这些类型在上面 DFA 图中
都有表示:
typedef
{
START,INASSIGN,INCOMMENT,INNUM,INID,DONE,SLT,SBG,SEQ,SDEV,SNOTE
Q,STIME,ENDCOMMENT,INCOMMENT1
}StateType;
用两个文件指针分别指向源文件和创建的目标文件:
FILE*source;
FILE*listing;
用一个字符类型的数组保存从文件读出的每行代码
#define
static
lineBuf[BUFLEN];
BUFLEN
char
256
struct
用一个结构保存 C-的转换保留字,因为 C 语言中小写也是保留字,为了
避免冲突,所以程序中先转为大写,在最后输出的时候,再转为小写
static
{
char*str;
TokenType
}reservedWords[MAXRESERVED]
tok;
IF
},
{
RETURN
},
"else",
"v
{
ELSE
},
{
"int",
INT
=
},
{
{
"if",
{
"return",
oid",
VOID
},
{
"while",
WHILE
}
};
用一个字符类型的数组保存识别的 Token
char
tokenString[MAXTOKENLEN
+
1];
5、 介绍 DFA 实现代码的各种方法的特点,说明自己选择的方案
页 2
姓名:
学号:
指导老师:于中华
1) 方法一:
{starting in state 1}
If the next character is a letter then
Advance the input;
{now in state 2}
While the next character is a letter or a digit do
Advance the input;{stay in 2}
End while;
{go to state 3 without advancing the input}
Accept;
Else
{error or other cases}
End if;
这种方法使用于没有太多的状态(要求有许多嵌套层)且 DFA 中的循环
叫小;它的缺点是首先它是特殊的,即必须使用循环不同的方法处理各个 DFA,
而且规定一个用这种办法将每个 DFA 翻译为代码的算法非常困难;其次当状态增
多或明确时,切当相异的状态与仁义路径增多时,代码会变得非常复杂。所以没
有选择方法一。
2) 方法二:
State :=1;{start}
While state = 1 or 2 do
Case state of
1: case input character of
Letter:advance the input;
State :=2;
Else state := …{error or other};
End case;
2 ;case input character of
Letter,digit:advance the input;
State := 2;{actually unnecessary}
Else state := 3;
End case;
End case;
End while;
If state := 3 then accept else error;
方法二利用一个变量保持当前的状态,并将转换写成以恶双层嵌套的
case 语句;其中第一个 case 语句测试当前的状态,嵌套的第二层测
试输入字符及所给状态。这种方法转换与对 state 变量新赋的状态相
对应,并提前输入。这种方法实现比较简单,所以 C-语言词法分析器
就采用的正中方法。
3) 方法三:
State := 1;
Ch := next input character;
While not accept[state] and not error (state) do
页 3
姓名:
学号:
指导老师:于中华
Newstate := T[state,ch];
If advance[state,ch] then ch := next input char;
State := newstate;
End while;
If accept [state] then accept;
这种方法代码的长度缩短了,相同的代码可以解决许多不同的问题,
代码也叫容易改动;但是彪哥会变得非常大,是的程序要求使用的空
间也变得很大。所以也不采用这种方法。
6、 实现的关键代码分析
1) TokenType getToken(void)函数
在判断注释的时候设置了三个状态分别为 INCOMMENT,INCOMMENT1 和
ENDCOMMENT,当输入为/时进入 INCOMMENT,进行判断若下一个字符为*则
设置状态为 INCOMMENT1 否则把 currentToken 设置为 Over;在出注释状
态时,若*则设置状态为 ENDCOMMENT 若下一个字符为/则状态设置为 END,
否者回到状态 INCOMMENT1;
在判断< <=;> >=;!=;==时,以< <=为例,先判断第一个字符,若为<则置
状态为 SLT,接着判断下一个状态若为=这 currentToken 值为 LTEQ,否则
currentToken 值为 LT,状态都设置为 DONE。若为+ - * ; ,符号是直接
判断是否为相应的符号若是则置 currentToken 为相应的状态,否则继续
执行程序。
关键词法分析代码如下:
TokenType getToken(void)
{
int tokenStringIndex = 0;
TokenType currentToken;
StateType state = START;
int save;
while (state != DONE)
{
char c = getNextChar();
save = TRUE;
switch (state)
{ case START:
if (isdigit(c))
state = INNUM;
else if(isalpha(c))
state = INID;
else if(c == '/')
{
state = INCOMMENT;
}
else if(c == '<')
{
页 4
姓名:
学号:
指导老师:于中华
state = SLT;
}
else if(c == '>')
{
state = SBG;
}
else if(c == '=')
{
state = SEQ;
}
else if(c == '!')
{
state = SNOTEQ;
}
else if((c == ' ') || (c == '\t') || (c == '\n'))
save = FALSE;
else
{
state = DONE;
switch (c)
{
case EOF:
save = FALSE;
currentToken = ENDFILE;
break;
case '+':
currentToken = PLUS;
break;
case '-':
currentToken = MINUS;
break;
case '*':
currentToken = TIMES;
break;
case '(':
currentToken = LPAREN;
break;
case ')':
currentToken = RPAREN;
break;
case '[':
currentToken = FLPAREN;
break;
case ']':
页 5
姓名:
学号:
指导老师:于中华
currentToken = FRPAREN;
break;
case '{':
currentToken = DLPAREN;
break;
case '}':
currentToken = DRPAREN;
break;
case ';':
currentToken = SEMI;
break;
case ',':
currentToken = CMMA;
break;
default:
currentToken = ERROR;
break;
}
}
break;
case INCOMMENT:
if(c == '*')
{
state = INCOMMENT1;
}
else
{
state = DONE;
ungetNextChar();
currentToken = OVER;
}
break;
case INCOMMENT1:
if(c == EOF)
{
state = DONE;
currentToken = ENDFILE;
}
else if(c == '*')
{
state = ENDCOMMENT;
}
else
state = INCOMMENT1;
页 6
姓名:
学号:
指导老师:于中华
break;
case ENDCOMMENT:
if(c == '/')
{
state = DONE;
currentToken = COMMENT;
}
else
{
state = INCOMMENT1;
}
break;
case SLT:
if(c == '=')
{
currentToken = LTEQ;
}
else
{
currentToken = LT;
}
state = DONE;
break;
case SBG:
if(c == '=')
{
currentToken = BGEQ;
}
else
{
currentToken = BG;
}
state = DONE;
break;
case SEQ:
if(c == '=')
{
currentToken = EQ;
}
else
{
currentToken = ASSIGN;
}
页 7
姓名:
学号:
指导老师:于中华
state = DONE;
break;
case SNOTEQ:
if(c == '=')
{
currentToken = NOTEQ;
}
else
{
ungetNextChar();
save = FALSE;
currentToken = ERROR;
}
break;
case INNUM:
if(!isdigit(c))
{
ungetNextChar();
save = FALSE;
state = DONE;
currentToken = NUM;
}
break;
case INID:
if(!isalpha(c))
{
ungetNextChar();
save = FALSE;
state = DONE;
currentToken = ID;
}
break;
case DONE:
default:
fprintf(listing,"Scanner Bug: state= %d\n",state);
state = DONE;
currentToken = ERROR;
break;
}
if((save) && (tokenStringIndex <= MAXTOKENLEN))
{
tokenString[tokenStringIndex++] = (char) c;
}
页 8