Tiny语言编译器:Tiny词法分析器模块

这是《编译原理》课程的一个作业,要求为Tiny语言的拓展Tiny+编写一个编译器,第一阶段要实现的是对Tiny+语言的词法分析。首先,简单的解释下Tiny+语言的构成:
 
TINY+

     We define here a programming language called TINY+, which is a superset of TINY in that it includes declarations, if statement, do-while statement, string type and so on.

The following consists of:

1     Lexical conventions of the language, including a description of the tokens of the language

2     EBNF description of each language construct

3     An description of the main semantics

4     Sample programs in TINY+

Part 1 Lexical Conventions of TINY+

1.     The keywords of the language are the following:

true         false     or         and      not         int         bool     string     while     do     if         then     else     end         repeat     until     read     write      

All keywords are reserved and must be written in lowcase

2.     Special symbols are the following:

     >         <=         >=         ,     '  

{     }     ;     :=     +     -     *     /     (     )     <     =

3.     Other tokens are ID, NUM and STRING which are defined by the following regular expressions:

ID=letter (letter | digit)*

     Identifier is letter followed by letters and digits

NUM=digit digit*

STRING=' any character except ' '

A STRING is enclosed in brackets '…', any character except ' can appear in a STRING. A STRING can’t be defined more than a line

letter=a|…|z|A|…|Z

digit=0|…|9

Lower and uppercase letters are distinct

4.     White space consists of blanks, newlines and tabs. White space is ignored except that it must separate IDs, NUMs, and keywords

5.     Comments are enclosed in curly brackets {…} and cannot be nested. Comments can include more than one line. 

Part 2 Syntax of TINY+

An EBNF grammar for TINY+ is as follows:

1     program         ->     declarations stmt-sequence

2     declarations     -> decl ; declarations |ε

3     decl         -> type-specifier varlist

4     type-specifier     -> int | bool | string

5     varlist     -> identifier { , identifier }

6     stmt-sequence     -> statement { ; statement }

7     statement     -> if-stmt | repeat-stmt | assign-stmt | read-stmt | write-stmt | while-stmt

8     while-stmt -> while bool-exp do stmt-sequence end

9     if-stmt     -> if   bool-exp then stmt-sequence [else stmt-sequence] end

10     repeat-stmt     -> repeat stmt-sequence until bool-exp

11     assign-stmt     -> identifier:=exp

12     read-stmt     -> read identifier

13     write-stmt     -> write exp

14     exp     -> arithmetic-exp | bool-exp | string-exp| comparison-exp

15     comparison-exp -> arithmetic-exp comparison-op arithmetic-exp

16     comparison-op     -> < | = | > | >= | <= 

17     arithmetic-exp     -> term { addop term } 

18     addop     -> + | -

19     term     -> factor { mulop factor }

20     mulop     -> * | /

21     factor     -> (arithmetic-exp) | number | identifier

22     bool-exp     -> bterm { or bterm }

23     bterm     -> bfactor { and   bfactor}

24     bfactor     -> true | false | identifier | (bool-exp) | not bfactor | (comparison-exp)

25     string-exp         -> string | identifier

26     Part 3 Main semantics description of TINY+

     A program consists of variable declarations and a sequence of statements. Variable declarations may be empty but there must be at least one statement.

     All variables must be declared before they are used, and each variable name can be declared only once

     The type of variables and expressions may be int, bool or string, type checking must be done on them

Part 4 Sample programs in TINY+

string str;

int x, fact;

str:= 'sample program in TINY+ language- computes factorial' ;

read x;

if x>0 and x<100 then {don’t compute if x<=0}

     fact:=1;

     while x>0 do

         fact:=fact*x;

         x:=x-1

     end;

     write fact

end

从上面我们可以看到有关Tiny+的关键字,数据定义,词法定义以及语法定义等等。
最后记录下自己的学习过程:

Tiny+开发环境:
Intel处理器
Microsoft Windows 7 操作系统 
Visual Studio 2010 与业版 
 .Net 4 版本


本Tiny+编译器采用图形操作窗口,而丌是基于命令控件台输入输出方式,这样更友好的与用户进行交互,编译器提供了下面功能: 
      词法分析功能模块; 
      语法分析功能模块; 
      词/语法分析结果保存模块; 
      文本编辑模块; 
      高级功能模块; 
      代码输入模块; 
      分析结果输出模块; 

TINY+编译器核心算法 

Scanner :这部分的功能的核心函数是 getToken()函数。该函数中,它首先通过使用一个循环不断地调用 getNextchar()(还有相对应的 getNextcharT()函数)来判断获取 tiny+的源代码的字符(每次一个字符),然后根据状态转换表中“当前状态 – 输入字符“ 来转换到新的状态,然后再根据 advance 表中的”当前状体 –当前输入字符“ 来判断是否接受新的字符,如果接受那么就接受新字符,否则,判断当前状态变量 state,看是否为不可接受并丏是丌是错误状态。如果当前状态可以接受,标明当前 tokenString 已经是一个 token,那么就可以退出循环,或者当前状态是错误,那么也可以退出循环。如果 state 是不可以接受并且 state 不是错误的,那么就可以继续接受字符。
代码如下:

 while(!acceptlist[state]&& state!=MERROR )                   

         { 

                  ch = getNextCharT(c);         

                  newState = (SateType)(translateTable[(int)state])[ch] ;             

                  if( tokenStringIndex < MAXTOKENLENTH && //当前的字符是否要保存到当前的            

                                                                                          //tokenString  中 

                           newState        != START    &&   

                           newState        != INCOMENT ) 

                           tokenString[tokenStringIndex++] = c ; 

                  if((advanceTable[int(state)])[ch] == 1)                          //是否可以接受下一个字符               

                           c    =    getNextChar(); 

                  oldState = state ; 

                  state = newState; 

         }  

退出循环之后,就可以判断当前状态是否是可以接受状态,如果可以接受,那么判断当前状态 state是 rolldone,还是 notrolldone 
1).如果是 rolldone : 那么就回滚一个字符。即不保存读到的最后一个字符。 
2).如果是 notrolldone: 那么就不用回滚一个字符。 
接着根据 state 在没有转换到 done(rolldone 戒者 notrolldone)状态之前的状态来判断当前 token 的类型

 if(acceptlist[state])   

         { 

                  if(state == ROLLDONE){ 

                           tokenString[tokenStringIndex -1] = '\0'; 

                           ungetNextChar(); 

                  } 

                  else{ 

                           tokenString[tokenStringIndex ++] = '\0'; 

                  } 

                  switch (oldState){     

                           case START : 

                                    { 

                                             if( *tokenString == -1){ 

                                                      currentToken = ENDFILE; 

//      华南理工大学软件学院  08 级  陈俊边    李本卿 

                                                      return currentToken      ; 

                                             } 

                                             else   

                                                      currentToken =    symbolLookup(tokenString); 

                                    }break ; 

                           case INNUM : currentToken =    NUM ;break ; 

                           case INID    : currentToken =    reservedLookup(tokenString)    ;break ; 

                           case INSTRING :currentToken =    STRINGS ;break ; 

                           default:currentToken = symbolLookup(tokenString);break ; 

                  } 

                   

         } 

         else{ 

                  currentToken =    EORROR; 

         } 

         if(TranceScan){ 

                  fprintf(listing,"%d ",lineno); 

                  printToken(currentToken,tokenString,listing); 

                  fprintf(listingDetails,"\t%d ",lineno); 

                  printToken(currentToken,tokenString,listingDetails); 

         } 

                  return currentToken; 

 

最后,如上面代码最后 7 行,把当前的 tokenString 输入到保存文件中,接着返回当前 token 的类型。 其中,提供给外界调用的接口是:int scan(); 

一点小总结:

简单而言,编译器就是将“高级语言”翻译为“机器语言(低级语言)”的程序。一个现代编译器的主要工作流程:源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 汇编程序 (assembler) → 目标代码 (object code) → 链接器 (Linker) → 可执行程序 (executables)。 从上面可以看出,在实现一个编译器的过程中涉及了很多编程技术,一个真正的技术人员应该对这些编程技术都非常的熟悉,并且,在实现一个编译器的过程中需要的不单是相关的编程技术,也需要编程人员拥有良好的开发态度和软件开发思想,所以通过这次实验不仅能学到有关编译原理的知识,还能真正应用到自己的编程实践中去。其实,课本里的这些知识丌单可以应用到编写编译器上去,在我们平时的软件开发实践中也经常会用到,像正则表达式的应用,还有对 XML 文件的操作等等。

在做词法扫描器中,遇到的最大的问题,莫过于对状态转换表的设计由于状态转换表是整个程序的核心,通过状态装换表,程序才能够实现通过接受每个字符,识别每个 token,最终把整个 tiny+的源程序扫描处来。因此,我们花了较多的时间来讨论表的设计,通过跟其他同学讨论,和自己思考,最后把一个状态转换表设计出来。 

由于时间和能力的限制,整个表还是存在着一些问题,对以某些错误还是不能够很好的识别,例如出现 112ab 这样的情况,程序会任务 112a 是错误的 token ,而 b 是 ID ,而这很明显是有问题的。正确的分析结果应该是 112ab 都是错的。有鉴于此,为了能有让程序有更好的可修改性,方便于以后修改,我把状态转换表写到一个 XML 配置文件中,然后程序每次要运行才从中读取,这样以后如果发现状态转换表中的转换逻辑出错,只需要修改配置文件就可以了。 

第二个问题就是程序出现了一些问题,例如字符编码的问题等等。 这次实现使得我们意识到在编程的前期,确实应该要注意些不同开发环境下编程的兼容问题。要么或者在早期就应该确定要用相同的环境。这样才可以避免像我们这次程序所遇到的问题。还有确实表驱动的方法对于初学者来说,真的是一个不错的方法,利用该方法可以减少代码量和减低程序的复杂度,程序更容易理解和更容易完成。 
2010-10-26 20:31:42 

你可能感兴趣的:(编译器)