利用FLEX构造 C-Minus-f 词法分析器
学号:XXXXXXXXXXXX
姓名:没有早八的人
一、实验目的
学习和掌握词法分析程序的逻辑原理与构造⽅法。
通过 FLEX 进⾏实践,构造 C-Minus-f 词法分析器。
二、实验任务
- 学习 C-Minus-f 的词法规则
- 学习 FLEX ⼯具使⽤⽅法
- 使⽤ FLEX ⽣成 C-Minus-f 的词法分析器,并进⾏验证
三、实验要求
本次实验需要根据
cminux-f
的词法补全lexical_analyer.l文件,完成词法分析器,能够输出识别出的token
,type
,line(刚出现的行数)
,pos_start(该行开始位置)
,pos_end(结束的位置,不包含)
。如:文本输入:
int a;
则识别结果应为:
int 280 1 2 5 a 285 1 6 7 ; 270 1 7 8
四、实验难点
针对C语言的某些语句,有着奇怪的规则(实验要求的是cminus-f,不是C,因此对于代码倒是没有):
//下列式子是合法的 int ___; ___ = -000232358.e-0003; //下列式子是不合法的 ___ = -00034;
对于 cminus-f 而言,整数的正则表达式中不包含正负号,因此
-1
应当被识别成-
和1
由于注释可以分为多行,故需要在识别到注释时,进行额外分析,分析其内所含的换行符
\n
。识别到换行符,则将lines
加1,重置pos_end
。因此,代码编写如下
case COMMENT: //STUDENT TO DO for(int i = 0;i < strlen(yytext);i++) { pos_end++; if( yytext[i] == '\n' ) { lines += 1;pos_end = 1; } } break;
五、实验设计
Token
先通过示例文件tokens,并参考网上资料,找到各Token符号对应的字符
//运算 ADD = 259, 加号:+ SUB = 260, 减号:- MUL = 261, 乘号:* DIV = 262, 除法:/ LT = 263, 小于:< LTE = 264, 小于等于:<= GT = 265, 大于:> GTE = 266, 大于等于:>= EQ = 267, 相等:== NEQ = 268, 不相等:!= ASSIN = 269,单个等于号:= //符号 SEMICOLON = 270, 分号:; COMMA = 271, 逗号:, LPARENTHESE = 272, 左括号:( RPARENTHESE = 273, 右括号:) LBRACKET = 274, 左中括号:[ RBRACKET = 275, 右中括号:] LBRACE = 276, 左大括号:{ RBRACE = 277, 右大括号:} //关键字 ELSE = 278, else IF = 279, if INT = 280, int FLOAT = 281, float RETURN = 282, return VOID = 283, void WHILE = 284, while //ID和NUM IDENTIFIER = 285, 变量名,例如a,low,high INTEGER = 286, 整数,例如10,1 FLOATPOINT = 287, 浮点数,例如11.1 ARRAY = 288, 数组,例如[] LETTER = 289, 单个字母,例如a,z //others EOL = 290, 换行符,\n或\0 COMMENT = 291, 注释 BLANK = 292, 空格 ERROR = 258 错误
写出对应的正则表达式
参考:
Cminus语法规则 - 百度文库 (baidu.com)
在线测试:
正则表达式在线测试 | 菜鸟工具 (runoob.com)
/******** 运算 ********/ /* ADD */ \+ /* SUB */ \- /* MUL */ \* /* DIV */ \/ /* LT,less */ \< /* LTE,less and equal */ \<\= /* GT,greater */ \> /* GTE,greater and qual */ \>\= /* EQ,equal */ \=\= /* NEQ,not equal */ \!\= /* ASSIN,= */ \= /******** 符号 ********/ /* SEMICOLON,; */ \; /* COMMA */ \, /* LPARENTHESE */ \( /* RPARENTHESE */ \) /* LBRACKET */ \[ /* RBRACKET */ \] /* LBRACE */ \{ /* RBRACE */ \} /******** 关键字 ********/ /* ELSE */ else /* IF */ if /* INT */ int /* FLOAT */ float /* RETURN */ return /* VOID */ void /* WHILE */ while /******** ID和NUM ********/ /* IDENTIFIER */ [a-zA-Z]+ /* INTEGER */ [0-9]+ /* FLOATPOINT */ [0-9]+\.|[0-9]*\.[0-9]+ /* ARRAY */ \[\] /* LETTER */ [a-zA-Z] /******** others ********/ /* EOL */ [\n] /* COMMENT */ \/\*([*]*(([^*/])+([/])*)*)*\*\/ /* BLANK */ [ \f\n\r\t\v] /* ERROR */ .
写出指定模式匹配时对应的动作
动作分为两步,一步是更新
lines
、pos_start
、pos_end
等参数,另一步是将当前识别的结果进行返回,即return
。
pos_start
表示当前识别的词的起始位置,pos_end
表示结束位置,lines
表示对应的行数,return
则会将token进行返回。因此代码编写如下:
/******** 运算 ********/ /* ADD */ \+ { pos_start = pos_end;pos_end++;return ADD;} /* SUB */ \- { pos_start = pos_end;pos_end++;return SUB;} /* MUL */ \* { pos_start = pos_end;pos_end++;return MUL;} /* DIV */ \/ { pos_start = pos_end;pos_end++;return DIV;} /* LT,less */ \< { pos_start = pos_end;pos_end++;return LT;} /* LTE,less and equal */ \<\= { pos_start = pos_end;pos_end += 2;return LTE;} /* GT,greater */ \> { pos_start = pos_end;pos_end++;return GT;} /* GTE,greater and qual */ \>\= { pos_start = pos_end;pos_end += 2;return GTE;} /* EQ,equal */ \=\= { pos_start = pos_end;pos_end += 2;return EQ;} /* NEQ,not equal */ \!\= { pos_start = pos_end;pos_end += 2;return NEQ;} /* ASSIN,= */ \= { pos_start = pos_end;pos_end++;return ASSIN;} /******** 符号 ********/ /* SEMICOLON,; */ \; { pos_start = pos_end;pos_end++;return SEMICOLON;} /* COMMA */ \, { pos_start = pos_end;pos_end++;return COMMA;} /* LPARENTHESE */ \( { pos_start = pos_end;pos_end++;return LPARENTHESE;} /* RPARENTHESE */ \) { pos_start = pos_end;pos_end++;return RPARENTHESE;} /* LBRACKET */ \[ { pos_start = pos_end;pos_end++;return LBRACKET;} /* RBRACKET */ \] { pos_start = pos_end;pos_end++;return RBRACKET;} /* LBRACE */ \{ { pos_start = pos_end;pos_end++;return LBRACE;} /* RBRACE */ \} { pos_start = pos_end;pos_end++;return RBRACE;} /******** 关键字 ********/ /* ELSE */ else { pos_start = pos_end;pos_end += 4;return ELSE;} /* IF */ if { pos_start = pos_end;pos_end += 2;return IF;} /* INT */ int { pos_start = pos_end;pos_end += 3;return INT;} /* FLOAT */ float { pos_start = pos_end;pos_end += 5;return FLOAT;} /* RETURN */ return { pos_start = pos_end;pos_end += 6;return RETURN;} /* VOID */ void { pos_start = pos_end;pos_end += 4;return VOID;} /* WHILE */ while { pos_start = pos_end;pos_end += 5;return WHILE;} /******** ID和NUM ********/ /* IDENTIFIER */ [a-zA-Z]+ { pos_start = pos_end;pos_end += strlen(yytext);return IDENTIFIER;} /* INTEGER */ [0-9]+ { pos_start = pos_end;pos_end += strlen(yytext);return INTEGER;} /* FLOATPOINT */ [0-9]+\.|[0-9]*\.[0-9]+ { pos_start = pos_end;pos_end += strlen(yytext);return FLOATPOINT;} /* ARRAY */ \[\] { pos_start = pos_end;pos_end += 2;return ARRAY;} /* LETTER */ [a-zA-Z] { pos_start = pos_end;pos_end++;return LETTER;} /******** others ********/ /* EOL */ [\n] { pos_start = 1;pos_end = 1;lines++;return EOL;} /* COMMENT */ \/\*([*]*(([^*/])+([/])*)*)*\*\/ { return COMMENT;} /* BLANK */ [ \f\n\r\t\v] { pos_end++;pos_start = pos_end;return BLANK;} /* ERROR */ . {return ERROR;}
补充C语言对应的代码
由于注释可以分为多行,故需要在识别到注释时,进行额外分析,分析其内所含的换行符
\n
。识别到换行符,则将lines
加1,重置pos_end
。因此,代码编写如下
case COMMENT: //STUDENT TO DO for(int i = 0;i < strlen(yytext);i++) { pos_end++; if( yytext[i] == '\n' ) { lines += 1;pos_end = 1; } } break;
六、实验结果验证
成功编译
输出结果和标准结果一致,通过了6个测试样例
七、实验反馈
这个实验的环境的环境当初配了好久,因为不会
Win 10 使用 WSL
,因此一开始尝使用虚拟机镜像。但在下载了对应的.vdi
文件,并加载到虚拟机后,不会执行后续步骤了。查了很多,并且尝试了很多,还是失败。之后,在同学的帮助下,我在自己的
Win 10
上使用WSL
,并安装了对应版本的llvm
、flex
、bison
,搭建好了git
上的仓库,并进行了链接。最终,成功搭建好了环境。在编写代码的时候,一开始因为以为
C-Minus-f
作为C
的子集,可以按照C
的去编写(以为仅是缺少了部分符号,例如'
)。但后来才发现,无论是整数的定义还是浮点数的定义,C-Minus-f
和C
都有较大的差别,例如:C
可以接受200.23e-2
作为浮点数,但是C-Minus-f
则不接受,其浮点数的正则表达式为(digit+. | digit*.digit+)
。包括C
语言可以用//
作为注释,但C-Minus-f
则不行,其只可用/**/
作为注释。因此在后来检查时,又修改了原本为C
编写的正则表达式,改为了C-Minus-f
的版本。最后,成功编译通过,也通过了6个测试样例,也通过了自己写的一些测试样例。
成功做完了这个实验,还是比较有成就感的。