词法分析器,又叫扫描器,其功能是从左往右逐个字符地对源程序进行扫描,然后按照源程序的构词规则识别出一个个单词符号,把作为字符串的源程序等价的转换为单词符号串的中间程序。单词符号是程序设计语言中的基本语法单元。我们尝试设计一个能扫描C语言子集的词法分析器,我们的单词分为以下5种:
引入一个概念:词法单元。表示单词类,常用整数进行编码,这个编码也叫种别码。
词法单元中每个单词符号也叫词素,每个词素应该具备不同的属性值,这个值是反映单词特征或者特性的值,是编译中其他阶段需要的信息。如果一个词法单元只含有一个词素,那么可以不必设置属性值,如果一个词法单元含有多个词素,那么就应该给每个词素一个属性值用以区分他们。
(刚刚这一段不懂暂时也没关系,如果想要详细了解请翻看龙书第三章)
LEX的输入是用LEX源语言编写的程序,它是扩展名为.l或.lex的文件。LEX源程序经过LEX系统处理后输出一个C程序文件,此文件再经过C编译器的编译就能产生一个可执行程序。
一般而言,一个LEX源程序由“%%”分隔的三部分组成,其整体格式为:
定义部分
%%
识别规则部分
%%
辅助函数部分
定义部分对规则部分要引用的文件和变量进行说明,通常可包含头文件、常数定义、全局变量定义、外部变量定义以及正规式定义等。正规式定义用来定义在规则部分引用的正规式,类似于C语言中的宏定义,所以也称宏定义。
除了宏定义外,定义部分的其他代码需要用“%{”和“%}”括起来,LEX会将这部分内容直接复制到C文件生成的lex.yy.c文件中。例:
%{
#define ID 68 //identifyer
#define NUMBER 69 //number
#define DEFAULT 70//default
%}
white [\t\n ]
digit [0-9]
letter [A-Za-z]
id ({letter}|_)({letter}|{digit}|_)*
number [1-9]{digit}*|0
如果你还没学过正则表达式,这里有一篇文章个人认为总结的比较到位可供参考正则表达式
这部分用来定义对每一种词法单元的识别动作,也就是词法分析器要执行的操作。例如:
{id} {fprintf(yyout,"ID %d %s\n",ID,yytext);}
{number} {fprintf(yyout,"NUMBER %d %s\n",NUMBER,yytext);}
在识别规则部分中如果用到的函数不是库函数,则需要在这部分给出函数定义,由用户用C语言编写,它们会由LEX系统直接复制到输出的C程序文件中。
下表列出了LEX中常用的一些变量和函数
函数名 | 定义 |
---|---|
yyin | FILE*类型,指向LEX输入文件,缺省情况下指向标准输入 |
yyout | FILE*类型,指向LEX输出文件,缺省情况下指向标准输出 |
yytext | char*类型,指向识别规则中的一个正规式匹配的单词的首字符 |
yyleng | int类型,记录与识别规则中正规式匹配的单词的长度 |
yylex() | 从该函数开始分析,可由lex自动生成 |
yywrap() | 文件结束处理函数,如果返回值为1就停止解析。可以用来解析多个文件。 |
echo | 将yytext打印到yyout |
我的词法分析器只支持了C语言的一个子集,具体有哪些请自行浏览代码。
LEX文件的编译只需要两条命令,加入我们的LEX文件保存在C.lex中,我们只需在终端执行:
lex C.lex(执行完后会生成lex.yy.c)
gcc lex.yy.c -o MyC_analyze -ll(执行完后可执行文件即为MyC_analyze)
具体代码如下:
这是我根据作业要求修改后的版本,引入符号表存储标识符,并尝试对不同作用域的同名变量进行区分,按理说作用域其实是一个树的结构,但我尝试借助大括号,用某种算法去进行区分。目前的思路是一共三个int变量,其中两个分别记录左右大括号的数量,还有一个用来标记新的作用域产生。具体实现请参照我对id、“{”、“}”识别的动作。这个算法目前并不严谨,但由于测试用例比较简单,目前还没发现漏洞所在,但由于显然我现在这个算法并不能实现对树结构的层次区分,所以日后随着我的编译器的功能的加深可能会暴露出问题,到时候再来修改吧。
%top{
#include
#include
}
%{
#define VOID 1
#define CHAR 2
#define INT 3
#define SIZEOF 4
#define CONST 5
#define RETURN 6
#define CONTINUE 7
#define BREAK 8
#define WHILE 9
#define IF 10
#define ELSE 11
#define SWITCH 12
#define CASE 13
#define FOR 14
#define DO 15
#define SCANF 16
#define PRINTF 17
#define LC 35 //{
#define RC 36 //}
#define LB 37 //[
#define RB 38 //]
#define LP 39 //(
#define RP 40 //)
#define LOGRE 41//~
#define INPLUS 42//++
#define INMINUS 43//--
#define LOCRE 44//!
#define AND 45//&
#define STAR 46//*
#define DIVOP 47// /
#define COMOP 71// %
#define PLUS 48//+
#define MINUS 49//-
#define RELG 50//>
#define RELGEQ 51//>=
#define RELL 52//<
#define RELLEQ 53//<=
#define EQUOP 54//==
#define UEQUOP 55//!=
#define ANDAND 56//&&
#define OROR 57// ||
#define EQUAL 58// =
#define ASSIGNDIV 59// /=
#define ASSIGNSTAR 60 //*=
#define ASSIGNCOM 61 //%=
#define ASSIGNPLUS 62 //+=
#define ASSIGNMINUS 63 //-=
#define COMMA 64 //,
#define SHA 65 //#
#define SEMI 66//;
#define COLON 67//:
#define ID 70 //identifyer
#define NUMBER 69 //number
#define DEFAULT 68//default
int IDcount=0;//IDcount
char map[100][100];//符号表
int l_scope=0;//左括号数量
int r_scope=0;//右括号数量
int new_scope=1;//作用域标记符
%}
white [\t\n ]
digit [0-9]
letter [A-Za-z]
id ({letter}|_)({letter}|{digit}|_)*
number [1-9]{digit}*|0
commentbegin "/*"
commentelement .|\n
commentend "*/"
%x COMMENT
%%
{commentbegin} {BEGIN COMMENT; fprintf(yyout,"Begin a comment:\n");}
<COMMENT>{commentelement} {fprintf(yyout,"%s",yytext);}
<COMMENT>{commentend} {BEGIN INITIAL; fprintf(yyout,"\nthis comment End!\n");}
{white}+ ;
{number} {fprintf(yyout,"NUMBER %d %s\n",NUMBER,yytext);}
"void" {fprintf(yyout,"VOID %d %s\n",VOID,yytext);}
"char" {fprintf(yyout,"CHAR %d %s\n",CHAR,yytext);}
"int" {fprintf(yyout,"INT %d %s\n",INT,yytext);}
"sizeof" {fprintf(yyout,"SIZEOF %d %s\n",SIZEOF,yytext);}
"const" {fprintf(yyout,"CONST %d %s\n",CONST,yytext);}
"return" {fprintf(yyout,"RETURN %d %s\n",RETURN,yytext);}
"continue" {fprintf(yyout,"CONTINUE %d %s\n",CONTINUE,yytext);}
"break" {fprintf(yyout,"BREAK %d %s\n",BREAK,yytext);}
"if" {fprintf(yyout,"IF %d %s\n",IF,yytext);}
"else" {fprintf(yyout,"ELSE %d %s\n",ELSE,yytext);}
"switch" {fprintf(yyout,"SWITCH %d %s\n",SWITCH,yytext);}
"case" {fprintf(yyout,"CASE %d %s\n",CASE,yytext);}
"default" {fprintf(yyout,"DEFAULT %d %s\n",DEFAULT,yytext);}
"for" {fprintf(yyout,"FOR %d %s\n",FOR,yytext);}
"do" {fprintf(yyout,"DO %d %s\n",DO,yytext);}
"while" {fprintf(yyout,"WHILE %d %s\n",WHILE,yytext);}
"scanf" {fprintf(yyout,"SCANF %d %s\n",SCANF,yytext);}
"printf" {fprintf(yyout,"PRINTF %d %s\n",PRINTF,yytext);}
{id} {
int flag = 0;
int i = 0;
for(i=IDcount-1;i>0;i--)
{
if(strcmp(yytext,map[i])==0)
{
flag=1;break;
}
}
if(flag==1&&new_scope!=0)//匹配到已有字符但仍处在旧的作用域
{
fprintf(yyout,"ID %d %s\n",i+70,yytext);
}
else//没有匹配到已有字符或者来到了新的作用域
{
IDcount++;
strcpy(map[IDcount-1],yytext);
new_scope=1;//每次插入一个新字符后需要将作用域标记符重新置1
fprintf(yyout,"ID %d %s\n",IDcount-1+70,yytext);
}
}
"{" {if(l_scope-r_scope==0)
//如果之前左右括号数量一样意味着这个左括号开启了一个新的作用域
new_scope=0;
else
new_scope=1;
l_scope++;
fprintf(yyout,"LC %d %s\n",LC,yytext);}
"}" {r_scope++;
fprintf(yyout,"RC %d %s\n",RC,yytext);}
"[" {fprintf(yyout,"LB %d %s\n",LB,yytext);}
"]" {fprintf(yyout,"RB %d %s\n",RB,yytext);}
"(" {fprintf(yyout,"LP %d %s\n",LP,yytext);}
")" {fprintf(yyout,"RP %d %s\n",RP,yytext);}
"~" {fprintf(yyout,"LOGRE %d %s\n",LOGRE,yytext);}
"++" {fprintf(yyout,"INPLUS %d %s\n",INPLUS,yytext);}
"--" {fprintf(yyout,"INMINUS %d %s\n",INMINUS,yytext);}
"!" {fprintf(yyout,"LOCRE %d %s\n",LOCRE,yytext);}
"*" {fprintf(yyout,"STAR %d %s\n",STAR,yytext);}
"/" {fprintf(yyout,"DIVOP %d %s\n",DIVOP,yytext);}
"%" {fprintf(yyout,"COMOP %d %s\n",COMOP,yytext);}
"+" {fprintf(yyout,"PLUS %d %s\n",PLUS,yytext);}
"-" {fprintf(yyout,"MINUS %d %s\n",MINUS,yytext);}
">" {fprintf(yyout,"RELG %d %s\n",RELG,yytext);}
"<" {fprintf(yyout,"RELL %d %s\n",RELL,yytext);}
">=" {fprintf(yyout,"RELGEQ %d %s\n",RELGEQ,yytext);}
"<=" {fprintf(yyout,"RELLEQ %d %s\n",RELLEQ,yytext);}
"==" {fprintf(yyout,"EQUOP %d %s\n",EQUOP,yytext);}
"!=" {fprintf(yyout,"UEQUOP %d %s\n",UEQUOP,yytext);}
"&&" {fprintf(yyout,"ANDAND %d %s\n",ANDAND,yytext);}
"||" {fprintf(yyout,"OROR %d %s\n",OROR,yytext);}
"=" {fprintf(yyout,"EQUAL %d %s\n",EQUAL,yytext);}
"/=" {fprintf(yyout,"ASSIGNDIV %d %s\n",ASSIGNDIV,yytext);}
"*=" {fprintf(yyout,"ASSIGNSTAR %d %s\n",ASSIGNSTAR,yytext);}
"%=" {fprintf(yyout,"ASSIGNCOM %d %s\n",ASSIGNCOM,yytext);}
"+=" {fprintf(yyout,"ASSIGNPLUS %d %s\n",ASSIGNPLUS,yytext);}
"-=" {fprintf(yyout,"ASSIGNMINUS %d %s\n",ASSIGNMINUS,yytext);}
"," {fprintf(yyout,"COMMA %d %s\n",COMMA,yytext);}
"#" {fprintf(yyout,"SHA %d %s\n",SHA,yytext);}
";" {fprintf(yyout,"SEMI %d %s\n",SEMI,yytext);}
":" {fprintf(yyout,"COLON %d %s\n",COLON,yytext);}
%%
int main()
{
memset(map, 0, sizeof(map));
yyin=fopen("testin.c","r");
yyout=fopen("testout.txt","w");
fprintf(yyout,"token name value\n");
yylex();
return 0;
}
int yywrap()
{
return 1;
}
如果觉得本文对你有帮助请点个赞吧!如果想持续学习编译原理的有关内容可以关注一波!