技术专题

lex-两年前的文章了。。

2008年11月17日 阅读(523)

2006-4-26日  星期三 凌晨1.08  作者:phylips@bmy
    九点之前搞了一下关于编译原理的词法分析器的东西,使用java语言写的。搞得流程全是自己判断的。虽然之前我曾经
 考虑到可以使用几种方案来实现。根据老师的要求有三种:1。手工实现2。采用LEX自动生成。3。采用老师提供的一个scanerlib 。由于那个lib使用的是C#,其实我也不知道是啥,也没看,只是听别人说的,所以便放弃了第3种方案。而第一方案侧重于程序逻 辑的编写,主意是编码。而第二种则侧重于工具的使用,以及个人学习的能力。
         其实按照我的想法其实也是三种方案,跟老师的大同小异,首先我认为手工实现有两种方式,一种就是纯手工的,另一种则是充分利用java的正则表达式。由于java自1.4版本对正则表达式提供了强大的支持。所以依靠它应该完全可以解决词法分析的问题。
         完全手工实现,即自己编码判断是标识符还是数字。
         无论是手工实现还是采用正则表达式均需要比较大的力气,且通用性不好。
         考虑到LEX的简便性,可以为广大的对编程不熟悉的人使用。
         为了服务于广大贫民阶层,我对此进行了简单的研究。
         本来只是知道lex是个词法分析器,可以根据定义的规则,进行分析,产生转换矩阵。
         直到今天看了,才对他真正的流程有了点了解。仅做如下总结:
         FLEX的工作流程如下
         lex程序-〉c程序-〉编译执行c程序
         可以看到,lex利用自己定义的语言(或者协议),自己进行编译,生成了C原言的源程序。在这个c的源程序里,有保存了转换矩阵信息的二维数组。
         
         lex使用方法:
         一开始的时候,直接双击FLEX.exe,但是没有反应。
         1。正确的方式,为进入cmd命令行,使用flex file.l命令进行编译,才会生成了file.yy.c的c语言源代码。
         2。然后将此c语言程序,使用c的编译器,编译执行即可以。
         
         其间还碰到过如下问题:
         1.由于采用了人家的源程序,有些地方导致include **.h文件找不到,而无法编译
         2.直接从网页上拷贝源代码,使用flex命令出现如下错误premature EOF的错误,无法生成c的源文件
         解决方法:自己重新建立文件,一个一个输入,而不要从网页上直接拷贝。估计跟采用的编码有关。
         3.自己定义俄main函数,生成的c文件,编译有错误。
         打开c文件:可以发现这里有两个main方法,删掉不是你定义的那个,再试一下。
         
         下面简要介绍一下flex源程序的语法:
         
             以下几点可以记住
             1.%{  %}内的东西,直接输到生成的c文件中。
             2.附加函数部分,也是直接输到生成的c文件中。
             3.书写lex实际上很多一部分除了定义规则外,就是书写c语言了
             4.如果要进行文件操作,需要对FILE *yyin,*yyout:为指向字符输入和结果输出文件的指针。如用户未对其定义,则设为标准输入文件stdin和stdout。
            5.同时要注意识别时flex遵循两个原则:
            one:规则定义在前面的优先进行匹配,这就要求关键字要写在标识符前面
            two:进行最长匹配
            6.有时候
            如果,{a }与{a}某个规则的名称错了,最常见的是末尾加入了空格,出现如下错误提示:
            EOF encountered inside an action
            如果  {a}{printf("error");}而应当是{a} {printf("error");}
            EOF encountered inside an action
            7.出现如下警告:
            8.以下可用变量:
            常用全局变量和宏

   lex.yy.c中常用全局变量、函数和宏很多,在此仅指出一些最常用的,若需要更详细信息,请阅读源文件。

   (1) FILE *yyin,*yyout:为指向字符输入和结果输出文件的指针。如用户未对其定义,则设为标准输入文件stdin和stdout。
   (2) int yylex():为词法分析程序,它自动移动文件指针yyin和yyout。在定义模式动作时,用户可用return语句结束yylex(),return 必须返回一整数。由于yylex()的运行环境都是以全局变量的方式保存,因此,在下一次调用yylex()时,yylex()可从上次扫描的断点处继续扫描,在语法分析时,可利用这一特性。若用户未定义相应的return语句,则yylex()继续分析被扫描的文件,直到碰到文件结束标志EOF。在读到EOF时,yylex()调用int yywrap()函数(该函数用户必须提供),若该函数返回非0值,则yylex()返回0而结束。否则,yylex()继续对yyin指向的文件扫描。
   (3) char *yytext:存放当前被识别的词形。
   (4) int yyleng:存放字符串yytext的长度。
   (5) int yywrap():参见(2)
   (6) yymore():将当前识别的词形保留在yytext中,分析器下次扫描时的词形将加追加在yytext中。例模式定义如下
   ……
   hello {printf(“%s!”,yytext);yymore();}
   world {printf(“%s!”,yytext);}
   ……
   当输入串为”helloworld”时,将输出”hello!helloworld!”
   (7) yyless(int n):回退当前识别的词形中n个字符到输入中
   (8) unput(char c):回退字符c到输入,它将作为下一次扫描的开始字符
   (9) input():让分析器从输入缓冲区中读取当前字符,并将yyin指向下一字符
   (10)yyterminate():中断对当前文件的分析,将yyin指向EOF。
   (11)yyrestart(FILE * file):重新设置分析器的扫描文件为file
   (12)ECHO:将当前识别的字符串拷贝到yyout
   (13)BEGIN:激活开始条件对应的模式
   (14)REJECT:放弃当前匹配的字符串和当前的模式,让分析器重新扫描当前的字符串,并选择另一个最佳的模式再次进行匹配。
         
            
下面是一个最简单的flex的程序。
%{
#include "stdio.h"
#include "stdlib.h"
%}
digit [0-9]+
id [a-zA-Z][a-zA-Z0-9]*
%%
{digit} {printf("digit");}
{id} {printf("id");}
%%
void main(){
yyin=fopen("C:/test.txt",r);
yylex();
}
int yywrap(){return 1;}

一个比较复杂的:     LEX源文件格式

   LEX对源文件的格式要求非常严格,比如若将要求顶行书写的语句变成非顶行书写就会产生致命错误。而LEX本身的查错能力很弱,所以书写时一定要注意。

   LEX的源文件由三个部份组成,每个部分之间用顶行的“%%”分割,其格式如下:

   定义部份
   %%
   规则部份 
   %%
   用户附加C语言部份
  1定义部份
   定义部份由C语言代码、模式的宏定义、条件模式的开始条件说明三部份组成。
   其中,C代码部份由顶行的%{和}%引入,LEX扫描源文件时将%{和}%之间的部分原封不动的拷贝到输出文件lex.yy.c中.
模式的宏定义部份如同C语言中的宏定义,通过宏名定义一个模式,这样,可以简化在源文件中多次出现的正规表达式的书写。格式为:
   宏名1 宏定义1
   宏名2 宏定义2 
   ……

   例如:
   DIGIT [0-9]
   ID [A-Za-z][A-Za-z0-9_]*

   宏名是以字母和下划线”_”开始,以字母、数字和下划线组成的字符串,且大小写敏感。宏名必须顶行写,宏名和宏定义必须写在同一行上。宏名和宏定义之间以不包括换行符的白字符(空格符、TAB符、换行符)隔开。

   条件模式的开始条件说明格式如下:

   %start s1 s2 s3
   其中,s1、s2、s3为条件名。必须为大小写敏感的标识符。关于条件模式的使用,我们将在后面作说明。

  2 规则部份

   规则部份是LEX源文件的核心部份,它包括一组模式和在生成分析器识别相应模式后对相应模式进行处理的C语言动作(Action)。格式如下

   C语言代码
   模式1 动作1
   模式2 |
   模式3 动作3
   ……

   同定义部分一样,C语言代码必须出现在第一个模式之前,包括在%{和}%之中,且%{必须顶行书写。%{和}%之间的代码部份可用来定义yylex()用到的局部变量。模式必须顶行书写。模式可为正规式或用{}括起且在定义部份定义过的宏名。动作为用{}括起的C代码。且开始括号{与模式之间用白字符隔开,且须和模式在同一行上。注意,在模式后加一|表示模式2和3采用同一动作3.|和模式2以白字符隔开。

3用户附加C语言部份

   LEX对此部份不作任何处理,仅仅将之直接拷贝到输出文件lex.yy.c的尾部。在些部份,可定义对模式进行处理的C语言函数、主函数和yylex要调用的函数yywrap()等。如果用户在其它C模块中提供这些函数,用户代码部份可以省略。

3.4 源文件格式小结

  综上所述,LEX源文件详细格式如下:
 %{
   /*此模块为定义模块中C语言代码部份,在下面填入相应C代码*/
   }%
   模式宏名1 模式1
   模式宏名2 模式2
   ……
   %start s1 s2 s3
   %%
   %{
   /*此模块为规则模块中C语言代码部份,在下面填入相应C代码*/
   }%
   模式1 动作1
   模式2 动作2
   ……
   %%

   /*此模块为用户附加C语言部份,在下面填入相应C代码*/

   注意:以上三部份及其中任何一子部份,均可省去。且如无第三部分,第二个%%也可省去,但第一个%%决不可省。

LEX的工作原理

LEX通过对源文件的扫描,经过宏替换后,将规则部份的正规表达式转换成与之相应的DFA,并由之产生一个名为int yylex()的词法分析函数,将之拷贝到输出文件lex.yy.c中。由于考虑到C代码的可移植性和运行效率问题,lex.yy.c中大量使用了宏定义,且文件较大(30-50kb)。因此,几乎是不可读的。但是,其可移植性相当好。

lex.yy.c中定义了很多用户可定义的全局变量,以及在LEX源文件的动作中可调用的函数和宏。但是,由于lex.yy.c太过复杂,建议初学者不要随意修改它。用户在了解其的前提下,可在其它C模块中引用之。

INT INT
FOR FOR
WHILE WHILE
DO DO
RETURN RETURN
BREAK BREAK
CONTINUE CONTINUE
PROCEDURE PROCEDURE
{>=} {printf("type=>=\n");}
{<=} {printf("type=<=\n");}
{:=} {printf("type=:=\n");}
{:} {printf("type=:\n");}

You Might Also Like