啥也不说,上代码先。
cool.flex
/*
* The scanner definition for COOL.
*/
/*
* Stuff enclosed in %{ %} in the first section is copied verbatim to the
* output, so headers and global definitions are placed here to be visible
* to the code in the file. Don't remove anything that was here initially
*/
%{
#include <cool-parse.h>
#include <stringtab.h>
#include <utilities.h>
/* The compiler assumes these identifiers. */
#define yylval cool_yylval
#define yylex cool_yylex
/* Max size of string constants */
#define MAX_STR_CONST 1025
#define YY_NO_UNPUT /* keep g++ happy */
extern FILE *fin; /* we read from this file */
/* define YY_INPUT so we read from the FILE fin:
* This change makes it possible to use this scanner in
* the Cool compiler.
*/
#undef YY_INPUT
#define YY_INPUT(buf,result,max_size) \
if ( (result = fread( (char*)buf, sizeof(char), max_size, fin)) < 0) \
YY_FATAL_ERROR( "read() in flex scanner failed");
char string_buf[MAX_STR_CONST]; /* to assemble string constants */
char *string_buf_ptr;
extern int curr_lineno;
extern int verbose_flag;
extern YYSTYPE cool_yylval;
/*
* Add Your own definitions here
*/
extern IdTable idtable;
extern IntTable inttable;
extern StrTable stringtable;
%}
/*
* Define names for regular expressions here.
*/
CLASS [cC][lL][aA][sS][sS]
ELSE [eE][lL][sS][eE]
FI [fF][iI]
IF [iI][fF]
IN [iI][nN]
INHERITS [iI][nN][hH][eE][rR][iI][tT][sS]
LET [lL][eE][tT]
LOOP [lL][oO][oO][pP]
POOL [pP][oO][oO][lL]
THEN [tT][hH][eE][nN]
WHILE [wW][hH][iI][lL][eE]
CASE [cC][aA][sS][eE]
ESAC [eE][sS][aA][cC]
OF [oO][fF]
DARROW "=>"
NEW [nN][eE][wW]
ISVOID [iI][sS][vV][oO][iI][dD]
ASSIGN "<-"
NOT [nN][oO][tT]
LE "<="
INT_CONST [0-9]+
LETTER [a-zA-Z]
%x COMMENT
%x STRING
%x ESCAPE
%%
/*
* Nested comments
*/
/*
* The multiple-character operators.
*/
\n { ++curr_lineno; }
<INITIAL>{CLASS} { return (CLASS); }
<INITIAL>{DARROW} { return (DARROW); }
<INITIAL>{ELSE} { return (ELSE); }
<INITIAL>{FI} { return (FI); }
<INITIAL>{IF} { return (IF); }
<INITIAL>{IN} { return (IN); }
<INITIAL>{INHERITS} { return (INHERITS); }
<INITIAL>{ISVOID} { return (ISVOID); }
<INITIAL>{LET} { return (LET); }
<INITIAL>{LOOP} { return (LOOP); }
<INITIAL>{POOL} { return (POOL); }
<INITIAL>{THEN} { return (THEN); }
<INITIAL>{WHILE} { return (WHILE); }
<INITIAL>{CASE} { return (CASE); }
<INITIAL>{ESAC} { return (ESAC); }
<INITIAL>{NEW} { return (NEW); }
<INITIAL>{OF} { return (OF); }
<INITIAL>{ASSIGN} { return (ASSIGN); }
<INITIAL>{NOT} { return (NOT); }
<INITIAL>{LE} {return (LE);}
<INITIAL>t[rR][uU][eE] {
cool_yylval.boolean=true;
return (BOOL_CONST);
}
<INITIAL>f[aA][lL][sS][eE] {
cool_yylval.boolean=false;
return (BOOL_CONST);
}
<INITIAL>{INT_CONST} {
//数字常量
cool_yylval.symbol=inttable.add_string(yytext,yyleng);;
return (INT_CONST);
}
<INITIAL>[A-Z][a-zA-Z0-9_]* {
cool_yylval.symbol=idtable.add_string(yytext,yyleng);
return (TYPEID);
}
<INITIAL>[a-z][a-zA-Z0-9_]* {
cool_yylval.symbol=idtable.add_string(yytext,yyleng);
return (OBJECTID);
}
<INITIAL>[ \t\n] {}
<INITIAL>--.* {}
<INITIAL>"(*" {
//cout<<"begin"<<endl;
BEGIN(COMMENT);
}
<INITIAL>"*)" {
cool_yylval.error_msg = "EOF in comment";
return (ERROR);
}
<COMMENT><<EOF>> {
BEGIN(INITIAL);
cool_yylval.error_msg = "EOF in comment";
return (ERROR);
}
<COMMENT>[^(\*\))] {
if(yytext[0]=='\n')
++curr_lineno;
}
<COMMENT>"*)" {
BEGIN(INITIAL);
//cout<<"end"<<endl;
}
<COMMENT>. {
//什么都不做
}
<INITIAL>"\\" {
//在非字符串的状态下,\是非法字符
cool_yylval.error_msg = "wrong escape sysbol";
return (ERROR);
}
<INITIAL>\" {
BEGIN(STRING);
string_buf_ptr=string_buf;
}
<STRING>\\ {
BEGIN(ESCAPE);
}
<STRING>[^"\\] {
*string_buf_ptr++=yytext[0];
}
<STRING>\" {
*string_buf_ptr++='\0';
cool_yylval.symbol=idtable.add_string(string_buf);
BEGIN(INITIAL);
return (STR_CONST);
}
<ESCAPE>. {
char ch=yytext[0];
switch (ch){
case 'n':
*string_buf_ptr++='\n';
break;
case 't':
*string_buf_ptr++='\t';
break;
case 'b':
*string_buf_ptr++='\b';
break;
case 'f':
*string_buf_ptr++='\f';
break;
case '"':
*string_buf_ptr++='"';
break;
case '\'':
case '\\':
case '\n':
*string_buf_ptr++=ch;
break;
case '\0':
default:
BEGIN(INITIAL);
cool_yylval.error_msg = "EOF in comment";
return (ERROR);
}
BEGIN(STRING);
}
<INITIAL>[\]\[\'>] {
//错误字符
cool_yylval.error_msg = yytext;
return (ERROR);
}
<INITIAL>[,:\{\}\(\);\.@] {return yytext[0];}
<INITIAL>[=\-+<\*~/] {
//运算符号
return yytext[0];
}
/*
* Keywords are case-insensitive except for the values true and false,
* which must begin with a lower-case letter.
*/
/*
* String constants (C syntax)
* Escape sequence \c is accepted for all characters c. Except for
* \n \t \b \f, the result is c.
*
*/
%%
词法解析的流程
刚开始看的时候一脸蒙逼,仔细PA2.pdf里,有讲flex的基础过程,因为不是很熟悉,我就大概看了个一下,后面具体不会的问gpt。
flex语法简单介绍
%{
Declarations
%}
Definitions
%%
Rules
%%
User subroutines
这里的Declarations相当于你的代码解析的时候,用上的一些全局变量和包含的库。
%{
#include <cool-parse.h>
#include <stringtab.h>
#include <utilities.h>
/* The compiler assumes these identifiers. */
#define yylval cool_yylval
#define yylex cool_yylex
/* Max size of string constants */
#define MAX_STR_CONST 1025
#define YY_NO_UNPUT /* keep g++ happy */
extern FILE *fin; /* we read from this file */
/* define YY_INPUT so we read from the FILE fin:
* This change makes it possible to use this scanner in
* the Cool compiler.
*/
#undef YY_INPUT
#define YY_INPUT(buf,result,max_size) \
if ( (result = fread( (char*)buf, sizeof(char), max_size, fin)) < 0) \
YY_FATAL_ERROR( "read() in flex scanner failed");
char string_buf[MAX_STR_CONST]; /* to assemble string constants */
char *string_buf_ptr;
extern int curr_lineno;
extern int verbose_flag;
extern YYSTYPE cool_yylval;
/*
* Add Your own definitions here
*/
%}
这里几乎都是c++的代码, fin是读入的文件,string_buf是解析的时候使用的字符数组,*string_buf_ptr是字符指针,curr_lineno是行数,每解析到一个\n (不在字符串里的\n),需要对他加一,这个会现实到token结果的第一个元素里。
Definitions 你在匹配的时候使用的一些变量,状态集。
CLASS [cC][lL][aA][sS][sS]
ELSE [eE][lL][sS][eE]
FI [fF][iI]
IF [iI][fF]
IN [iI][nN]
INHERITS [iI][nN][hH][eE][rR][iI][tT][sS]
LET [lL][eE][tT]
LOOP [lL][oO][oO][pP]
POOL [pP][oO][oO][lL]
THEN [tT][hH][eE][nN]
WHILE [wW][hH][iI][lL][eE]
CASE [cC][aA][sS][eE]
ESAC [eE][sS][aA][cC]
OF [oO][fF]
DARROW "=>"
NEW [nN][eE][wW]
ISVOID [iI][sS][vV][oO][iI][dD]
ASSIGN "<-"
NOT [nN][oO][tT]
LE "<="
INT_CONST [0-9]+
LETTER [a-zA-Z]
%x COMMENT
%x STRING
%x ESCAPE
前面的是简单的defination ,我感觉是类似于c++的define语句,单纯的把这些值替换过去。
下面的 %x 用于定义一个状态,初始的状态是 INITIAL,COMMENT是解析到 (*的时候,进入到解析注释的状态,STRING是解析到 “ 的时候,进入到字符串的解析状态, ESCAPE是在STRING状态之后,碰到 \ , 进入到转义符号的状态。
RULES就是你匹配的规则了。
<INITIAL>{LE} {return (LE);}
<INITIAL>"(*" {
//cout<<"begin"<<endl;
BEGIN(COMMENT);
}
<INITIAL>f[aA][lL][sS][eE] {
cool_yylval.boolean=false;
return (BOOL_CONST);
}
前面是匹配的时候的状态,“(*”就算匹配的正则表达式,后面大括号里的,就是匹配到这些字符之后,需要在匹配之后,执行什么代码,我当时就奇怪,这里的cool_yylval.boolean是怎么知道的,我们后面再说。
BEGIN(COMMENT)就是开始一个新的状态。
流程
我们在lextest.cc里找到了关键的执行函数
cout << "#name \"" << argv[optind] << "\"" << endl;
while ((token = cool_yylex()) != 0) {
dump_cool_token(cout, curr_lineno, token, cool_yylval);
}
fclose(fin);
optind++;
dump_cool_token就是打印出token的内容,注意到这里需要curr_lineno和cool_yylval,这个我猜测是解析到每一个token之后,这些值都需要更新的。
转到cool_yylval的定义,这玩意就是YYSTYPE结构。
typedef union YYSTYPE
#line 46 "cool.y"
{
Boolean boolean;
Symbol symbol;
Program program;
Class_ class_;
Classes classes;
Feature feature;
Features features;
Formal formal;
Formals formals;
Case case_;
Cases cases;
Expression expression;
Expressions expressions;
char *error_msg;
}
/* Line 1489 of yacc.c. */
#line 124 "cool.tab.h"
YYSTYPE;
这是一个union,相当于可以是这些数据结构里的任何一个,但只能是一个,和struct不同。
cool_yylval就相当于你解析到啥,把他存起来就好了。
<INITIAL>[a-z][a-zA-Z0-9_]* {
cool_yylval.symbol=idtable.add_string(yytext,yyleng);
return (OBJECTID);
}
但是解析到id的时候,我们是不好直接把char*转换到symbol里的,这里老师肯定是给了相应的接口函数,把char*转换到symbol结构里。
IdTable idtable;
IntTable inttable;
StrTable stringtable;
我之前第一次看到这个答案的时候,就很蒙,这个变量哪里来的,我在stringtab.cc里找到了答案,这里IdTable是StringTable的子类,他提供了char*到Symbol的转换方法。
所以我们在声明自己定义的时候,需要 把这三个变量extern进入,不进去也行,链接的时候也不会报错,但是小心全局变量重名。
extern YYSTYPE cool_yylval;
/*
* Add Your own definitions here
*/
extern IdTable idtable;
extern IntTable inttable;
extern StrTable stringtable;
解析
关键词
CLASS [cC][lL][aA][sS][sS]
ELSE [eE][lL][sS][eE]
FI [fF][iI]
IF [iI][fF]
IN [iI][nN]
INHERITS [iI][nN][hH][eE][rR][iI][tT][sS]
LET [lL][eE][tT]
LOOP [lL][oO][oO][pP]
POOL [pP][oO][oO][lL]
THEN [tT][hH][eE][nN]
WHILE [wW][hH][iI][lL][eE]
CASE [cC][aA][sS][eE]
ESAC [eE][sS][aA][cC]
OF [oO][fF]
DARROW "=>"
NEW [nN][eE][wW]
ISVOID [iI][sS][vV][oO][iI][dD]
ASSIGN "<-"
NOT [nN][oO][tT]
LE "<="
INT_CONST [0-9]+
LETTER [a-zA-Z]
%x COMMENT
%x STRING
%x ESCAPE
%%
/*
* Nested comments
*/
/*
* The multiple-character operators.
*/
\n { ++curr_lineno; }
<INITIAL>{CLASS} { return (CLASS); }
<INITIAL>{DARROW} { return (DARROW); }
<INITIAL>{ELSE} { return (ELSE); }
<INITIAL>{FI} { return (FI); }
<INITIAL>{IF} { return (IF); }
<INITIAL>{IN} { return (IN); }
<INITIAL>{INHERITS} { return (INHERITS); }
<INITIAL>{ISVOID} { return (ISVOID); }
<INITIAL>{LET} { return (LET); }
<INITIAL>{LOOP} { return (LOOP); }
<INITIAL>{POOL} { return (POOL); }
<INITIAL>{THEN} { return (THEN); }
<INITIAL>{WHILE} { return (WHILE); }
<INITIAL>{CASE} { return (CASE); }
<INITIAL>{ESAC} { return (ESAC); }
<INITIAL>{NEW} { return (NEW); }
<INITIAL>{OF} { return (OF); }
<INITIAL>{ASSIGN} { return (ASSIGN); }
<INITIAL>{NOT} { return (NOT); }
<INITIAL>{LE} {return (LE);}
<INITIAL>t[rR][uU][eE] {
cool_yylval.boolean=true;
return (BOOL_CONST);
}
<INITIAL>f[aA][lL][sS][eE] {
cool_yylval.boolean=false;
return (BOOL_CONST);
}
我们在cool_manual里发现,关键词是大小写不敏感的,我也是后面测试的时候才去翻文档的,
然后这里return bool_const的时候,需要记录下来是true还是false.
数字,类名,变量名
<INITIAL>{INT_CONST} {
//数字常量
cool_yylval.symbol=inttable.add_string(yytext,yyleng);;
return (INT_CONST);
}
<INITIAL>[A-Z][a-zA-Z0-9_]* {
cool_yylval.symbol=idtable.add_string(yytext,yyleng);
return (TYPEID);
}
<INITIAL>[a-z][a-zA-Z0-9_]* {
cool_yylval.symbol=idtable.add_string(yytext,yyleng);
return (OBJECTID);
}
这里yytext是解析出来的内容,是char* ,yyleng是解析出来的长度,是flex这个工具自己定义的变量。
文档说类名是大写开头,包含字母数字和下划线,变量名是小写开头。
空格,换行符
<INITIAL>[ \t] {}
<INITIAL>--.* {}
不处理,直接抛弃就好了
注释
<COMMENT><<EOF>> {
BEGIN(INITIAL);
cool_yylval.error_msg = "EOF in comment";
return (ERROR);
}
<COMMENT>[^(\*\))] {
if(yytext[0]=='\n')
++curr_lineno;
}
<COMMENT>"*)" {
BEGIN(INITIAL);
//cout<<"end"<<endl;
}
<COMMENT>. {
//什么都不做
}
就算需要在解析到\n的时候,需要curr_lineno里将行数加一,然后因为是最长匹配嘛,第二条规则是当后面有两个字符的时候,会出现奇怪的内容,我就多加了一条。
字符串
<INITIAL>\" {
BEGIN(STRING);
string_buf_ptr=string_buf;
}
<STRING>\\ {
BEGIN(ESCAPE);
}
<STRING>[^"\\] {
*string_buf_ptr++=yytext[0];
}
<STRING>\" {
*string_buf_ptr++='\0';
cool_yylval.symbol=stringtable.add_string(string_buf);
BEGIN(INITIAL);
return (STR_CONST);
}
解析到“ 的时候,开启字符串状态,然后把解析出来的字符储存在str_buf里,再碰到"的时候,在stringtable里加一项就好了。
转移字符
<ESCAPE>. {
char ch=yytext[0];
switch (ch){
case 'n':
*string_buf_ptr++='\n';
break;
case 't':
*string_buf_ptr++='\t';
break;
case 'b':
*string_buf_ptr++='\b';
break;
case 'f':
*string_buf_ptr++='\f';
break;
case '"':
*string_buf_ptr++='"';
break;
case '\'':
case '\\':
case '\n':
*string_buf_ptr++=ch;
break;
case '\0':
default:
BEGIN(INITIAL);
cool_yylval.error_msg = "EOF in comment";
return (ERROR);
}
BEGIN(STRING);
}
就是把 两个字符位置的 "\n"转换成一个字符的'\n'就好了。
错误字符,运算字符,标点
<INITIAL>[\]\[\'>] {
//错误字符
cool_yylval.error_msg = yytext;
return (ERROR);
}
<INITIAL>[,:\{\}\(\);\.@] {return yytext[0];}
<INITIAL>[=\-+<\*~/] {
//运算符号
return yytext[0];
}
该返回返回,该报错报错。
测试
通过了老师的test.cl之后,我把所有的example里的.cl文件都解析了一遍,然后比对老师写的lexer输出的结果。
这是脚本
#!/bin/bash
# colordiff -u (lexer test.cl | psub) (./lexer test.cl | psub)
make lexer > /dev/null
make > /dev/null
for file in /home/sjk/project/cool-compiler/examples/*.cl; do
if [ -f "$file" ]; then
echo "Running colordiff -u for file: $file"
colordiff -u <(lexer "$file") <(./lexer "$file")
fi
done
我这里是fish终端,然后其他的需要把 < 改成 | ,linux的管道。colordiff自己下载就行。
这样就ok