CS143 PA2 词法分析器

PA2中使用flex生成一个词法分析器(lexical analyzer,又称scanner)。

我花了大概4-5天,30个小时左右,困扰我的主要是以下两点:

  1. 一开始不知道要做什么
  2. 正则表达式的匹配问题

得分:

flex

.flex文件的格式如下:

%{
	Declarations
%}
Definitions
%%
	Rules
%%
User subroutines

其中:

  • Declarations中可以用C++写声明,可以在这里面添加一些变量用于后面。
  • Definitions中可以给一段正则表达式命名,这样使程序可读性更强,如DIGIT [0-9],格式与宏定义类似,详见这里
  • Rules中写正则表达式以及匹配时执行的代码,详见这里和几个例子
  • User subroutines可以不用管。

主要文件

cool.flex

需要补充的文件,在这里面填写正则表达式和代码等内容。

test.cl

用于测试的程序,但是不能涵盖所有情况,可以使用判题脚本进行更加全面地检查。

文件缺失问题

打开PA2下的README,它会让你检查你的文件夹下面是否有指定的几个文件,我使用的是老师提供的环境,缺少了几个文件,这些文件在这个包中可以找到:

链接:https://pan.baidu.com/s/13Sbfxah3E5PWwGpbwy2tVA
提取码:2paa

将缺少的文件放到共享文件夹中,然后在虚拟机中移动到正确的文件夹下就可以了。

准备

输出Hello World

通过一个简单的输出来熟悉一下做作业流程

打开cool.flex,在rule处添加如下内容,该句中.*的意思是匹配任意一行,若匹配成功,则打印Hello World!

.*	{ cout << "Hello World!"; }

在命令行处输入make lexer,生成词法分析器:

输入make dotest,进行测试:


样例中的每一行都会匹配.*,因此每行都导致输出Hello World!

执行perl pa1-grading.pl判分,这行命令会用你写的规则生成词法分析器,并用更多的测试用例测试:

不通过的用例会像上面图片中一样显示,可以在grading目录下查看脚本中的测试用例,以及你的输出(第一次运行脚本后,grading目录就会出现)。

除此之外,为了详细地了解如何做以及做的对不对,我们可以用老师已经实现的一个词法分析器来标准输出,然后用标准输出与我们的输出进行比对——

老师实现的词法分析器:

test.cl运行这个分析器,输入../../bin/reflexer test.cl

在这里插入图片描述

通过比对自己的输出以及标准输出,来修改cool.flex

作业流程

流程:修改cool.flex文件、判分、修改、重复直到满分。
下面从简单到困难,完成各个部分正则表达式的编写。

关键字和一些符号

所有关键字、符号及其代表的值可以在cool-parse.h文件中找到。

除了truefalse的其他关键字大小写不敏感,而truefalse的首字母必须小写。

阅读flex文档的Patterns部分,想要匹配大小写不敏感的字母,需要在前面添加?i:,如匹配大小写不敏感的class,要写:

?:class

因此,在Definition部分中写如下内容(包括多字符操作符),注意truefalse和其他关键字格式不同:

DARROW      =>
ASSIGN		<-
LE			<=

CLASS		(?i:class)
INHERITS	(?i:inherits)
IF			(?i:if)
THEN		(?i:then)
ELSE		(?i:else)
FI			(?i:fi)
WHILE		(?i:while)
LOOP		(?i:loop)
POOL		(?i:pool)
LET			(?i:let)
IN			(?i:in)
FALSE		(f(?i:alse))
TRUE		(t(?i:rue))
ISVOID 		(?i:isvoid)
CASE		(?i:case)
ESAC		(?i:esac)
NEW			(?i:new)
OF			(?i:of)
NOT			(?i:not)

然后在rules部分添加:

{DARROW}	{ return DARROW; }
{ASSIGN}	{ return ASSIGN; }
{LE}		{ return LE; }
{CLASS}		{ return CLASS; }
{INHERITS}	{ return INHERITS; }
{IF}		{ return IF; }
{THEN}		{ return THEN; }
{ELSE}		{ return ELSE; }
{FI}		{ return FI; }
{WHILE}		{ return WHILE; }
{LOOP}		{ return LOOP; }
{POOL}		{ return POOL; }
{LET}		{ return LET; }
{IN}		{ return IN; }
{ISVOID} 	{ return ISVOID; }
{CASE}		{ return CASE; }
{ESAC}		{ return ESAC; }
{NEW}		{ return NEW; }
{OF}		{ return OF; }
{NOT}		{ return NOT; }
{TRUE}		{ 
      cool_yylval.boolean = true;
      return BOOL_CONST;		
}
{FALSE}		{ 
      cool_yylval.boolean = false;
      return BOOL_CONST;
}

要注意的是左侧{}中的单词与右侧返回的单词不是一个东西,左侧的代表的是我们刚刚在Definition中定义的正则表达式,而右侧的是C++语言中的宏定义,其实质是整数。

除此之外,理解一下这段是如何运行的.cool文件作为输入,程序会调用int yylex()函数(关于这个函数,看这里),用正则表达式匹配输入的字符,若某一部分匹配成功,则执行右侧的代码,而yylex()的返回值就是代码中的返回值,如return NOT;就让yylex()返回了一个整数,这个整数代表关键字not

yylex会反复执行直到读取文件结束。

整数Integer

当遇到整数时,代码中先在inttable中记录这个数字,然后返回INT_CONST,其指明这是一个整型:

[0-9]+	{ 
				cool_yylval.symbol = inttable.add_string(yytext); 
				return INT_CONST;
}

注意:

标识符identier

包括类型名和变量名两类:

类型名

大写字母开头,跟数字、字母、下划线:

[A-Z][a-zA-Z0-9_]*	{
					cool_yylval.symbol = idtable.add_string(yytext);
					return TYPEID;
}

变量名

小写字母开头,跟数字、字母、下划线:

[a-z][a-zA-Z0-9_]*	{
					cool_yylval.symbol = idtable.add_string(yytext);
					return OBJECTID;
}

操作符

多字符操作符,如<-,已经在关键字部分中写好了,剩下还有.@~+-*\=<

[\.@~]		{ return yytext[0]; }
[\+\-\*\/\=\<]	{ return yytext[0]; }

其中yytext的类型是char*,即字符串,它的内容就是匹配成功的字符串。

标点符号

这部分也比较简单:

[\{\}\:\;\(\)\,]	{ return yytext[0]; }

空白符号

包括5个符号,遇到\n行数增加,遇到其他空白符跳过就可以了,被跳过的字符会原封不动地被打印在输出中。

\n	{ line_num++; curr_lineno = line_num;  }
[ \f\r\t\v]		/* --skip-- */

注意:

  • line_num是自己定义的一个全局变量,在Declaration部分添加就可以了。
  • curr_linenoflex给我们提供的一个全局变量,用于记录当前的行数。

无效字符

这部分必须放在整个文件的最后,匹配所有没被正确的表达式匹配的字符。

. { 
    yylval.error_msg = yytext; 
    return ERROR;
}

注释

单行注释

--.*  /* ---skip--- */

.不会匹配换行符\n,因此这样写只会匹配一行。

多行注释

多行注释是比较难的部分。

在开始前,需要阅读flex文档中的第10章,了解一下这个flex的语法糖,这大大方便了我们的编写。

Cool语言的注释匹配模式与C/C++不同,看一个对比的例子:

(*   (*    *)

/*   /*    */

第一行的Cool注释报错,而第三行的C注释不会报错,这是因为Cool语言的注释会检查匹配的层数,(*有2个,但*)仅一个,不匹配。

为此,我们需要一个变量记录注释的层数,具体的规则如下所示:

  • 每遇到一个(*层数加1,遇到一个*)层数减1
  • 层数大于0表示在注释模式中
  • 层数从大于0减至0则退出注释模式
  • 若在一般模式下遇到*)则报错
  • 到文件结束时若层数依然大于0则报错

我们需要表示层数以及注释模式,这可以通过一个变量以及上面所说的语法糖实现,在Declaration中添加int deepth = 0表示层数,在Definition中添加:

%x 		COMMENT

COMMENT表示注释模式。

至此,正则表达式如下:

"(*"				{ deepth = 1; BEGIN(COMMENT); }
<COMMENT><<EOF>>	{ 
                        cool_yylval.error_msg = "EOF in comment";
                        BEGIN(INITIAL);
                        return ERROR; 
}
<COMMENT>"(*"		{ deepth++; }
<COMMENT>"*)"		{ 
					  --deepth; 
					  if(deepth == 0) 
					  	BEGIN(INITIAL); 
}
<COMMENT>\\.		
<COMMENT>\n			{ line_num++; }
<COMMENT>"*"[^\)\*\n]	/* ---- eat anything ---- */
<COMMENT>.				/* ---- eat anything ---- */

\*\)	{
  cool_yylval.error_msg = "Unmatched *)";
  return (ERROR);
}

字符串

字符串匹配是最难的部分,一些规则老师没有在文档中说明。

这部分地匹配与上面的注释匹配比较类似,难点是对于'\0'的匹配、以及各种报错的先后顺序问题。

当在INITIAL的模式下遇到了",那么就进入字符串匹配模式,在Definition中添加:

%x		STR

由于字符串中可能会遇到转义字符,我们再定义一个转义字符模式:

%x		STR_ESCAPE

我们还需要一个char*数组记录字符串,i表示当前的位置下标:

char* my_string;
int i;

STR模式下进行处理的规则:

  • 遇到",说明字符串结束了,代码中判断字符串是否合法,若非法则报错,否则将字符串添加到stringtable中。
  • 遇到'\n',立即报错。
  • 遇到'\0',做一个记录,但不报错,继续读取字符串。
  • 遇到'\',进入STR_ESCAPE模式。
  • 遇到EOF即文件结束了,立即报错。
  • 遇到其他字符,则在my_string中添加。
  • 若字符串长度超过限度,则做一个记录,不报错,继续读取。

STR_ESCAPE模式下:

  • 遇到nbft中的一个(记为x),则在my_string中添加'\x',返回STR模式
  • 遇到'\0',做一个记录,返回STR模式
  • 遇到EOF,报错
  • 遇到其他字符(记为x),在my_string中添加'x',返回到STR模式。

报错规则和顺序如下:

  • 在读取过程中遇到了\n,则立即报Unterminated string constant,并返回INITIAL模式
  • 读取过程中若检查到字符串中有\0,则在读取结束时报String contains null character
  • 读取过程中若检查到字符串过长,则在读取结束时报String constant too long

最后,该部分代码实现如下:

{%

char* my_string;
int i;

#define MAX_ERROR_NUMBER 4
int* error_queue; /*错误队列*/
int error_size; /*记录错误个数*/
bool containNull;

%}

\"		{
          my_string = new char[MAX_STR_CONST];
          error_queue = new int[MAX_ERROR_NUMBER];
          i = 0;
          error_size = 0;
          BEGIN(STR);
}
<STR>\"		{
          curr_lineno = line_num;
          if (error_size > 0) {
            int error = error_queue[0];
            if (error == 1 || error == 3) {
	            containNull = false;
              if(error == 1) yylval.error_msg = "String contains null character";
              else yylval.error_msg = "String contains escaped null character";
              BEGIN(INITIAL);
              return ERROR;
            }
            if (error == 2) {
              yylval.error_msg = "String constant too long";
              BEGIN(INITIAL);
              return ERROR;
            }
          }
          yylval.symbol = stringtable.add_string(my_string);	
          BEGIN(INITIAL);
          return STR_CONST;
}
<STR>\\ { BEGIN(STR_ESCAPE); }
<STR,STR_ESCAPE><<EOF>> {
                          yylval.error_msg = "EOF in string constant";
                          BEGIN(INITIAL);
                          return ERROR;
}
<STR_ESCAPE>[nbft]  {
                      if (i < MAX_STR_CONST - 1) {
                        char cur = yytext[0];
                        if (cur == 'n') my_string[i++] = '\n';
                        else if (cur == 'b') my_string[i++] = '\b';
                        else if (cur == 'f') my_string[i++] = '\f';
                        else my_string[i++] = '\t';
                        
                      }
                      else { 
                            if(error_size < MAX_ERROR_NUMBER) 
				                      error_queue[error_size] = 2;
                            error_size++;
                      }
		      BEGIN(STR);
}
<STR_ESCAPE>\0  {
                  containNull = true;
                  if(error_size < MAX_ERROR_NUMBER) 
                    error_queue[error_size] = 3;
                  error_size++;
                  BEGIN(STR);
}
<STR_ESCAPE>[^\0]	{
                    if (i < MAX_STR_CONST - 1) {
                      if(yytext[0] == '\n') line_num++;
                      my_string[i++] = yytext[0];
                      BEGIN(STR);
                    } 
                    else { 
                          if(error_size < MAX_ERROR_NUMBER) 
                            error_queue[error_size] = 2;
                          error_size++;
			              }
}
<STR>\0		{
            containNull = true;
            if(error_size < MAX_ERROR_NUMBER) 
              error_queue[error_size] = 1;
            error_size++;
}
<STR>\n		{ 
		  line_num++;
		  curr_lineno = line_num;
		  if (containNull) {
			  	containNull = false;
                yylval.error_msg = "String contains null character";
                BEGIN(INITIAL);
                return ERROR;
		  }
		  yylval.error_msg = "Unterminated string constant";
		  BEGIN(INITIAL);
		  return ERROR;
}
<STR>.  {	
          if (i < MAX_STR_CONST - 1) {
            my_string[i++] = yytext[0];
          } 
          else {
            if(error_size < MAX_ERROR_NUMBER)
			        error_queue[error_size] = 2;
            error_size++;
          }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值