一、实验目的
1. 学习和掌握词法分析程序的构造
2. 学习Tiny的词法分析器
二、实验内容与要求
- 实验内容
TINY 语言的词法由TINY Syntax.ppt 描述;
TINY 语言的词法分析器由TINY Scanner.rar 的C 语言代码实现;
TINY+语言的词法由TINY+ Syntax.doc 描述。
任务:理解TINY 语言的词法及词法分析器的实现,并基于该词法分析器,
实现拓展语言TINY+的词法分析器。
要求:
1) TINY+词法分析器以TINY+源代码为输入,输出为识别出的token 序列;
2) 词法分析器以最长匹配为原则,例如‘:=’应识别为赋值符号而非单独的‘:’及‘=’;
3) Token 以(种别码,属性值)表示,包含以下类型的种别码:
- KEY 为关键字;
- SYM 为系统特殊字符;
- ID 为变量;
- NUM 为数值常量;
- STR 为字符串常量。
4) 识别词法错误。词法分析器可以给出词法错误的行号并打印出对应的出错消息,主要包含以下类型的词法错误:
- 非法字符。即不属于TINY+字母表的字符,比如$就是一个非法字符;
- 字符串匹配错误,比如右部引号丢失,如‘scanner
- 注释的右部括号丢失或匹配错误,如{this is an example
三、实验步骤与过程
1. 理解TINY 语言的词法及词法分析器的实现
首先我们来观察Global.h文件,其中包含了词法分析器中包含的全局变量。
以下全局变量分别用于表示源代码文件、输出text文件、中间代码文件以及当前行号。
以下为枚举类型的保留字TokenType,可以看到 TINY 语言中的关键字 KEY 和系统特殊字符 SYM 也是在这里定义的。
以下是6个跟踪标志EchoSource、TraceScan、TraceParse、TraceAnalyze、TraceCode、Error。
EchoSource为真时表示源程序在解析期间以行号回显到列表文件。
TraceScan为真时表示每个token都被扫描器识别,然后将token信息打印到列表文件。
TraceParse为真时表示语法树以线性化形式打印到列表文件(对子项使用缩进)。
TraceAnalyze为真时表示要报告给列表文件的符号表插入和查找。
TraceCode为真时表示生成代码时要写入 TM 代码文件的注释。
Error为真时表示如果发生错误,将会程序进行下一步。
接下来我们来观察scan.h和scan.c文件,它们是作为词法分析的源程序。
以下为枚举类型的保存状态StateType,可以看到共有START, INASSIGN, INCOMMENT, INNUM, INID, DONE 6个状态。
以下为保存标识符tokenString,其长度为MAXTOKENLEN+1。
以下全局变量分别用于表示读取一行字符保存、指示缓存中第几个字符、当前缓存中字符串长度以及错误标识。
以下是结构体reservedWords,其用于保存保留字结构,方便查询。
以下是函数getNextChar,它的作用是获取缓存中下一个字符。
以下是函数ungetNextChar,它的作用是如果文件没有结束,那么直接将linepos减去1就可以重新读取该字符以实现回吐的目的。
以下是函数reservedLookup,它的作用是根据getToken返回的字符,在保留字数组中进行查找。如果找到,说明该词素为保留字,返回保留字的类型。否则返回ID表示该词素的类型为ID。
以下是函数getToken,也是该文件的主要功能,它的作用是返回标识符,以下对函数进行详细分析。
首先函数声明了4个标号,其中tokenStringIndex用于保存标识符,currentToken用于保存要返回的Token,state则表示当前的状态,其初值为开始状态start,save表示是否需要保存到 tokenString。
接下来进入循环,循环条件为state不等于DONE,先调用getNextChar函数获取缓存中下一个字符并放到临时变量c中,再根据state的状态值进行下一步,如果是开始状态START,则判断c是哪种字符(数字、字母、冒号、空格、换行符、制表符、左括号),然后设置对应的state状态值,比如如果c是数字,就把state设为数值常量状态INNUM。
如果c不是以上情况,那么再判断c是否为系统特殊字符或EOF,如果都不是,说明这是一个非法字符,以上是state为start的情况。
如果state是INCOMMENT,说明是注释状态,不用进行保存。
如果state是INASSIGN,则查看c是否为'=',如果不是,则不合法,并调用ungetNextChar函数回退一个字符以及把ERROR赋给currentToken。
如果state是INNUM,则查看c是否为数字,如果不是,则调用ungetNextChar函数回退一个字符。
如果state是INID,则查看c是否为字符,如果不是,则调用ungetNextChar函数回退一个字符。
如果state在这里已经为DONE或者state为其他状态,说明程序出现问题,有BUG。
接下来如果save为真,而且tokenStringIndex长度合法,则把c赋值给tokenString字符数组。
如果state为DONE,说明读取结束,并把终结符赋给tokenString字符数组,再判断currentToken是否为ID,如果是,则调用reservedLookup函数查看标识符是否为保留字。
然后判断TraceScan是否为真,如果为真,则将token信息打印到列表文件,最后把currentToken返回。
然后我们来观察util.h和util.c文件,它们是作为词法单元输出程序。
比较重要的是printToken函数,它被词法分析程序scan.c调用,用来输出所识别到的词法单元的内容以及词素。
最后我们来观察main.c文件,它则是作为编译器入口,其中设置了编译选项,从中读取文件,然后传递给词法分析器。
2. 实现拓展语言TINY+的词法分析器
下面,我们基于以上的知识与TINY词法分析器,来实现TINY+的词法分析器。
首先要添加识别下图红框中新的关键字。
我们在Global.h中的枚举类型TokenType中添加新的关键字,如下图所示。
其中因为TRUE和FALSE已经被define了,所以要换成TRUE1和FALSE1来表示。
这里我们还要修改MAXRESERVED的值到正确的数量,否则程序会出问题。
接下来修改scan.c中的reservedWords,添加新的关键字。如下图所示。
然后还要修改util.c中的printToken函数,添加新的关键字对应的case。如下图所示。
至此,我们完成了对新的关键字的添加。
下面我们添加识别下图红框中新的系统特殊字符。
我们同样在Global.h中的枚举类型TokenType中添加新的系统特殊字符GT(>)、LE(<=)、GE(>=)、COMMA(,)、UPDOX(')、PERCENT(%)、DOUBLETIMES(**),如下图所示。
对于单个字符组成且没有特殊情况的系统特殊字符,我们可以直接在scan.c中的getToken函数添加对该特殊字符的判断。如下图:
然后因为词法分析器是以最长匹配为原则,而要添加的系统特殊字符中有1个符号以上组成的特殊字符(<=,>=,**),所以我们的词法分析器要对这些情况进行额外判断,修改scan.c中的枚举类型StateType,添加新的状态INLT,INGT,INTIMES。
而上引号(')因为涉及到字符串常量STR,所以也要额外进行判断,添加新的状态INUPDOX。
下面在scan.c中的getToken函数添加在state为start状态时对最长匹配原则的判断。
接着在下方添加对新的状态的判断。如果读到了下一个符号,说明这是一个由2个符号组成的系统特殊字符,如果没读到,则说明这是单个系统特殊字符,调用ungetNextChar函数回退一个字符并把该单个系统特殊字符返回。
然后还要修改util.c中的printToken函数,添加新的特殊字符对应的case。
至此,我们完成了对新的系统特殊字符的添加。
下面我们添加识别下图红框中变量ID的规则。原本的词法分析器对于ID只有对字母alpha的判断,因此要实现 TINY+语言的词法我们需要添加对数字digit的判断。
然后我们只需在scan.c中的getToken函数,在state为INID状态时添加对数字的判断就行了。
下面我们添加识别字符串常量STR, 对于TINY+语言 ,STR是由两个上引号(')加上中间任意字符除去上引号组成的,并且不会多于一行。
我们先在scan.c中的getToken函数添加在state为start,读到上引号的情况。
接着如果读到下一个上引号,那么这就是一个字符串常量STR,如果这一行结束前还没读到下一个上引号,则代表语法错误,把出错消息和出错状态返回。
最后修改util.c中的printToken函数,添加字符串常量STR对应的case。
接下来我们来完善词法分析器,识别其他词法错误。
对于TINY+语言,注释部分是可以多于一行但不能嵌套的。下面来添加识别注释的右部括号丢失与匹配错误。
非法字符,即不属于TINY+字母表的字符,如果词法分析器识别到一个非法字符,则输出对应的出错信息,也就是在state为start状态时对字符的判断会去到default阶段,我们在这里加上出错消息。
TINY+字母表的字符中,有符号":="却没有符号":",所以如果识别到":"之后没有读到"=",那么这就是语法错误了。我们在对应的状态加上对错误的判断与错误信息。
至此,我们完成了对拓展语言TINY+的词法分析器的实现,下面我们来对词法分析器进行测试。
提供的测试代码:
tiny+1.txt
tiny+2.txt
自己编写测试文件:
tiny+_bugTest.txt
对所有的可能错误情况都进行了测试,可以看到错误信息正确给出。
四、实验结论或心得体会
顺利的完成了实验,通过这次实验,我学习和掌握了Tiny的词法分析器以及词法分析程序的构造,并根据以上知识完成了对拓展语言TINY+的词法分析器的实现,在词法分析器加入识别词法错误。总的来说是一次不错的体验。