{"title":"一个完整编译器的实现(一) 词法分析","date_published":"2015-02-05T16:27:28.000Z","tags":["post","创作集"],"attributes":[{"trait_type":"xlog_slug","value":"1754"}],"content":"[GitHub地址](https://github.com/DIYgod/Compiler) [各阶段源码](http://www.anotherhome.net/file/compiler/) [各阶段说明集合](http://www.anotherhome.net/1751)\n\n为了将一个程序从一种语言翻译成另一种语言,编译器必须首先把程序的各种成分拆开,并搞清其结构和含义,然后再用另一种方式把这些成分组合起来。编译器的前端执行分析,后端进行合成。\n\n而分析一般分为3种:词法分析 语法分析 语义分析\n\n本阶段进行的是词法分析,目的是将输入文件分解成一个个独立的词法符号,即单词。\n\n**根据虎书的提示,在本阶段分了三个模块:**\n\n1.错误处理模块(errormsg.c errormsg.h):用来产生含文件名和行号的报错信息\n2.词法分析模块(lexical.lex token.h):通过Lex进行词法分析\n3.常用工具模块(util.c util.h):定义一些常用的函数\n\n词法分析模块与错误处理模块:两者通过 errormsg.h 中声明的变量和函数进行通信:EM_tokPos 变量传递每个单词以字符为单位的位置;EM_newline()函数记录行号;EM_error() 输出报错信息。\n\n错误处理模块与常用工具模块:错误处理模块使用 util.h 中声明的 checked_malloc() 分配内存函数\n\n另外还包含了 驱动程序(driver.c)测试文件(test.c) makefile\n\n**下面主要介绍本阶段最重要的词法分析模块。**\n\n**tokens.h**:定义词法单词常量以及yylval\n
typedef union {\n\tint ival;\n \tchar cval;\n \tdouble dval;\n\tstring sval;\n\t} YYSTYPE;\nextern YYSTYPE yylval;\n上述代码定义了yylval,yylval是一个表示不同语义值的集合,其中的ival cval dval sval 分别用来保存 整数 字符 浮点数 字符串 单词的语义值。\n
# define ID 128\n# define STRING 129\n# define COMMA 130\n# define COLON 131\n# define SEMICOLON 132\n# define LPAREN 133\n# define RPAREN 134\n# define LBRACK 135\n# define RBRACK 136\n# define LBRACE 137\n# define RBRACE 138\n# define DOT 139\n# define PLUS 140\n# define MINUS 141\n# define TIMES 142\n# define DIVIDE 143\n... ... ... ...\n这段定义了一些常数,这些常数供 lexical.lex 使用,它们指明被匹配的是何种类型的单词。\n\n**lexical.lex**:Lex的源文件,可以通过Lex生成一个词法分析器\n\nLex是一个可以将正则表达式转换城词法分析器的生成器,它由词法规范生成一个C程序(lex.yy.c)。该规范包含一个正则表达式和一个动作。这个动作将单词类型(可能和其他信息一起)传给编译器的下一处理阶段。\n
%{\n#include <string.h>\n#include \"util.h\"\n#include \"tokens.h\"\n#include \"errormsg.h\"\n\nint charPos=1; //记录每个单词的位置\n\nint yywrap(void) //Lex函数, 返回1就停止解析, 可以用来解析多个文件\n{\n charPos=1;\n return 1;\n}\n\nvoid adjust(void) //计算单词位置, 并通过EM_tokPos传给错误信息模块\n{\n EM_tokPos=charPos;\n charPos+=yyleng;\n}\n\n%}\n\n%%\n[\" \"\"\\t\"] {adjust(); continue;}\n\"\\n\" {adjust(); EM_newline(); continue;}\n(\\\")([A-Za-z0-9])*(\\\") {adjust(); yylval.sval = yytext; return STRING_V;}\nstring {adjust(); return STRING;}\n'[A-Za-z0-9]' {adjust(); yylval.cval = yytext[1]; return CHAR_V;}\nchar {adjust(); return CHAR;}\nshort {adjust(); EM_error(EM_tokPos, \"暂不支持short类型\");}\n-?[0-9]+ {adjust(); yylval.ival=atoi(yytext); return INT_V;}\nint {adjust(); return INT;}\nunsigned {adjust(); EM_error(EM_tokPos, \"暂不支持unsigned类型\");}\nlong {adjust(); EM_error(EM_tokPos, \"暂不支持long类型\");}\nfloat {adjust(); EM_error(EM_tokPos, \"暂不支持float类型\");}\n-?[0-9]+(\\.[0-9]+)? {adjust(); yylval.dval = atof(yytext); return DOUBLE_V;}\ndo {adjust(); return DO;}\ndouble {adjust(); return DOUBLE;}\nstruct {adjust(); return STRUCT;}\nunion {adjust(); return UNION;}\nvoid {adjust(); return VOID;}\nenum {adjust(); return ENUM;}\nsigned {adjust(); EM_error(EM_tokPos, \"暂不支持signed类型\");}\nconust {adjust(); return CONUST;}\nvolatile {adjust(); EM_error(EM_tokPos, \"暂不支持volatile\");}\ntypedef {adjust(); return TYPEDEF;}\nauto {adjust(); EM_error(EM_tokPos, \"暂不支持auto\");}\nregister {adjust(); EM_error(EM_tokPos, \"暂不支持register\");}\nstatic {adjust(); return STATIC;}\nextern {adjust(); return EXTERN;}\nbreak {adjust(); return BREAK;}\ncase {adjust(); return CASE;}\ncontinue {adjust(); return CONTINUE;}\ndefault {adjust(); return DEFAULT;}\nelse {adjust(); return ELSE;}\nfor {adjust(); return FOR;}\ngoto {adjust(); return GOTO;}\nif {adjust(); return IF;}\nreturn {adjust(); return RETURN;}\nswitch {adjust(); return SWITCH;}\nwhile {adjust(); return WHILE;}\nsizeof {adjust(); return SIZEOF;}\n[A-Za-z]+\\[[0-9]+\\] {adjust(); return ARRAY;}\n[A-Za-z_]([A-Za-z0-9_])* {adjust(); yylval.sval = yytext; return ID;}\n\",\" {adjust(); return COMMA;}\n\":\" {adjust(); return COLON;}\n\";\" {adjust(); return SEMICOLON;}\n\"(\" {adjust(); return LPAREN;}\n\")\" {adjust(); return RPAREN;}\n\"[\" {adjust(); return LBRACK;}\n\"]\" {adjust(); return RBRACK;}\n\"{\" {adjust(); return LBRACE;}\n\"}\" {adjust(); return RBRACE;}\n\".\" {adjust(); return DOT;}\n\"+\" {adjust(); return PLUS;}\n\"-\" {adjust(); return MINUS;}\n\"*\" {adjust(); return TIMES;}\n\"/\" {adjust(); return DIVIDE;}\n\"!=\" {adjust(); return NEQ;}\n\"==\" {adjust(); return ASSIGN;}\n\"=\" {adjust(); return EQ;}\n\"<=\" {adjust(); return LE;}\n\"<\" {adjust(); return LT;}\n\">=\" {adjust(); return GE;}\n\">\" {adjust(); return GT;}\n\"&\" {adjust(); return AND;}\n\"|\" {adjust(); return OR;}\n第一部分,即位于%{...%}之间的部分,包含有若干由此文件其余部分C代码使用的include和声明。\n\n第二部分,即位于%}...%%之间的部分,包含正则表达式的简写形式和状态说明,比如你可以写上\n
digits [0-9]+\n那么第三部分中就可以用{digits}代替[0-9]+了。\n\n第三部分,即位于%%后面的部分,包含正则表达式和动作。每个动作返回一个 int 类型的值(token.h定义的常数),指出匹配的是哪一种单词。\n其中有两条匹配的原则来消除二义性:\n规则优先:对于一个特定的最长初始子串,第一个与之匹配的正则式决定这个子串的单词类型;\n最长匹配:通过规则优先确定正则式之后,子串取与正则式匹配的最长的字符串。\n\n几个变量:yytext是正则式匹配的字符串;yyleng是所匹配的字符串的长度;charPos追踪每一个单词的位置,并告知EM_tokPos。\n\n \n\n词法分析 Done.","external_urls":["https://blog.diygod.me/1754"],"sources":["xlog"]}