词法分析主要是读入源程序的输入字符,区分成词素,生成词法单元序列,序列中的每个词法单元对应一个词素。此外,它还会完成其他的任务,如过滤掉源程序中的注释和空白(空格、换行符、制表符以及在输入中用于分割词法单元的其他字符);以及将编译器生成的错误消息与源程序中的位置联系起来。
词法单元:由词法单元名和可选的属性组成;词法单元名可以是特定关键词,或者是代表标识符、数字常量、字符常量等。
模式:一个词法单元的词素所具有的所有可能的形式;如果是词法单元是关键字,那就是这个关键字的字符序列;对于例如标识符,那么就是更复杂的结构,可以和很多字符串匹配。
词素:源程序的一个具体的字符序列,能够匹配某个模式,并被词法分析器识别。
在很多程序设计语言中,下面的类别覆盖了大部分或者所有的词法单元:
每个关键字有一个词法单元;一个关键字的模式就是该关键字本身;
表示运算符的词法单元;它可以表示单个运算符;或者像上图中的comparison一样表示一类运算符;
表示所有标识符的词法单元;
一个或多个表示常量的词法单元,比如数字和字面值字符串;
每一个标点符号有一个词法单元,比如左右括号、逗号和分号。
如果有多个词素可以和一个模式匹配,那么词法分析器必须向编译器的后续阶段提供有关匹配词素的附加信息。因此,词法分析器不仅向语法分析器返回一个词法单元名字,还会返回描述该词法单元的词素的属性值。词法单元的名字影响语法分析过程中的决定,而属性则会影响语法分析之后对词法单元的翻译。
一般来说,假设词法单元至多有一个相关的属性值,当然,这个属性值可以是一个组合了多种信息的结构化数据。譬如,和标识符相关的信息包括词素、类型、第一次出现的位置等,都是它的属性,并被保存在符号表中。因此,一个标识符的属性值是一个指向了符号表中该标识符对应条目的指针。
接下来,我们正式地定义一些概念,使用一些标记,研究一下正则表达式的形式化表示方法。正规式又称正则表达式, 是一种特殊的字符串用来描述一类的字符串的集合。我们把可用正规式描述(其结构)的语言称为正规语言或正规集。
字母表:符号的有限集合, 例: [公式] = { 0, 1}
串:符号的有穷序列,例:0110, [公式]
语言:字母表上的一个串集 {
[公式] , 0, 00, 000, …}, { [公式] }, [公式]
句子:属于语言的串
字母表(alphabet)是一个有限的符号集合,符号的典型例子包括字母、数字和标点符号。集合{0,1}就是一个字母表,这个字母表能够构成所有的二进制串。ASCII是字母表的重要例子,用于很多软件系统中。
在词法分析中,最重要的语言上运算是并、连接和闭包运算。下面是运算的正规定义:
在定义好正则表达式之后,怎么基于正则表达式构造代码来检查输入字符串,并返回和模式匹配的词素呢?
作为构造词法分析器的中间步骤,首先将模式转换为具有特定风格的流图,称为“状态转换图”。我们首先看看如何使用手工方法来构造,然后研究如何自动从正则表达式构造状态转换图。
状态转换图(transition diagram)有组称为状态(state)的节点或圆圈,图中还包括有向边,每条边上面有一个或多个标号,代表从一个状态转到另一个状态的条件。转换图的每一个状态代表了词法分析器扫描输入串的过程中可能遇到的情况。状态图的边从图的一个状态指向另一个状态,每条边的标号包括一个或多个符号。如果我们处于某个状态s,并且下一个输入符号是 a ,那么我们会去找一条从s状态离开并且标号为 a 的边。目前假设所有的状态都是确定的,也即对任何的给定状态和给定的符号,最多只有一条从该状态离开的边包括该符号。
关于状态转换图的重要约定如下:
某些状态被称为接收状态或者最终状态,这些状态表明已经找到了一个词素,可以有多个接收状态
如果相应的词素并不包括在最后一步使我们到达接受状态的符号,那么可以在该接受状态附近加一个*;意味着最后的这个字符需要另外处理,所以一般会回退;
有一个状态被指定为开始状态,也成为初始状态,该状态由一条没有出发节点的,标号为start的边指明。在读入任何输入符号之前,状态转换图总是处于开始状态。
下面的图是识别所有与词法单元relop的词素的状态转换图,识别关系运算符。
词法分析是编译器工作的第一步。词法分析负责将用户的代码从左至右依次读入,识别出来其中的词素(lexeme),并将词素映射为token(role)。一个token就是一个语法的类型,譬如自然语言中,有名词、动词、形容词等;对于编程语言而言,就是标识符、整型、关键字等。譬如说,position 被识别为一个词素,然后映射成标识符(identifier);if 被识别为一个词素,被映射为IF(关键字)。另外,有一些称作whitespace的词素识别出来之后就直接丢掉;有一些token有另外的属性,譬如词素的值,词素所在的行等信息。所以,这里我们主要做的事情就是定义好token,token定义好之后,可以通过一些现成的工具或者自己写代码分析来完成token的分析。
完成词法分析有很多现成的工具,称作lexer或者scanner。我们先看一个例子,使用flex。使用现成工具的好处是可以先集中考虑下词法到底怎么定义。如果定义好了词法,相应的代码也可以写出来。Flex手册中给出的定义如下:
Flex是一个生成扫描器的工具,能够识别文本中的词法模式。Flex
读入给定的输入文件,如果没有给定文件名的话,则从标准输入读取,从而获得一个关于需要生成的扫描器的描述。此描述叫做规则,由正则表达式和
C代码对组成。Flex 的输出是一个 C 代码文件——lex.yy.c——其中定义了yylex()
函数。编译输出文件可以生成一个可执行文件。当运行可执行文件的时候,它分析输入文件,为每一个正则表达式寻找匹配。当发现一个匹配时,它执行与此正则表达式相关的C代码。Flex
不是GNU工程,但是GNU为Flex 写了手册。
yylex()是由flex创建的扫描程序的入口点,调用yylex()启动或者重新开始扫描。Lex编写的yylex()从名为yyin的FILE *文件指针中读取字符。 如果未设置yyin,则默认为标准输入。 它输出到yyout,如果未设置默认为stdout。 还可以在yywrap()函数中修改yyin,该函数在文件末尾调用。 它允许打开另一个文件,并继续解析。如果是这种情况,将其返回0。如果要结束此文件的解析,将其返回1。一般来说,每次调用yylex()都会返回一个表示标记类型的整数值。
flex的语法和结构是什么样的呢?flex通过读取一个*.l或者*.lex文件里的单词匹配规则生成C语言的实现代码。一个*.l的文件里的结构大概如下,用%%分隔开来。
%{ /*declaration*/ %}
/* Definition */
%%
/* Rules */
%%
/* C Code */
定义区包含一些简单的名字定义(name definitions)来简化词法扫描器(scanner)的规则,并且还有起始条件(start condition)的定义。
规则区包含了一系列具有pattern-action形式的规则,并且模式 pattern 位于行首不能缩进,action 也应该起始于同一行。
用户代码区 的代码将被原封不动地拷贝到输出文件中,并且这些代码通常会被扫描器调用,当然,该区是可选的,如果 Flex 源文件中不存在该区,那么可以省略第二个 “%%” 。
最近在联系写一个简单的编译器,借助flex&bison完成前端词法分析和语法分析
flex部分(没有完善)
%option yylineno
%{
#include
#include
#include"struct.hpp"
%}
/*数字定义*/
DECIMAL [1-9][0-9]*
OCTAL 0[0-7]*
HEXDECIMAL 0(x|X)[0-9a-fA-F]+
/*标识符*/
IDENT [a-z_A-Z][a-z_A-Z0-9]*
PUTF "putf"
STARTTIME "starttime"
STOPTIME "stoptime"
EOL (\r\n|\n|\r)
WHITE [\t ]
/*单行注释*/
LINECOMMENT \/\/[^\n]*
BLOCKCOMMENT_BEGIN "/*"
BLOCKCOMMENT_ELE .
BLOCKCOMMENT_LINE (\r\n|\n|\r)
BLOCKCOMMENT_END "*/"
%x BLOCKCOMMENT
%%
{EOL} yylineno++;
{WHITE}
{LINECOMMENT}
{BLOCKCOMMENT_BEGIN} {BEGIN BLOCKCOMMENT;}
<BLOCKCOMMENT>{BLOCKCOMMENT_ELE} {}
<BLOCKCOMMENT>{BLOCKCOMMENT_LINE} {yylineno++;}
<BLOCKCOMMENT>{BLOCKCOMMENT_END} {BEGIN INITIAL;}
{WHITE} {}
";" {yylval.no = new GrammaNode(lineno, SEMI_, yytext);return SEMI;}
"," {yylval.no = new GrammaNode(lineno, COMM_,yytext);return COMM;}
"(" {yylval.no = new GrammaNode(lineno, RDBRAL_,yytext);return RDBRAL;}
")" {yylval.no = new GrammaNode(lineno, RDBRAR_,yytext);return RDBRAR;}
"[" {yylval.no = new GrammaNode(lineno, SQBRAL_,yytext);return SQBRAL;}
"]" {yylval.no = new GrammaNode(lineno, SQBRAR_,yytext);return SQBRAR;}
"{" {yylval.no = new GrammaNode(lineno, BRAL_,yytext);return BRAL;}
"}" {yylval.no = new GrammaNode(lineno, BRAR_,yytext);return BRAR;}
"=" {yylval.no = new GrammaNode(lineno, ASSIGN_,yytext);return ASSIGN;}
"+" {yylval.no = new GrammaNode(lineno, ADD_,yytext);return ADD;}
"-" {yylval.no = new GrammaNode(lineno, SUB_,yytext);return SUB;}
"/" {yylval.no = new GrammaNode(lineno, DIV_,yytext);return DIV;}
"*" {yylval.no = new GrammaNode(lineno, MUL_,yytext);return MUL;}
"%" {yylval.no = new GrammaNode(lineno, MOD_,yytext);return MOD;}
"==" {yylval.no = new GrammaNode(lineno, EQ_,yytext);return EQ;}
"!" {yylval.no = new GrammaNode(lineno, NOT_,yytext);return NOT;}
"!=" {yylval.no = new GrammaNode(lineno, NEQ_,yytext);return NEQ;}
"||" {yylval.no = new GrammaNode(lineno, OR_,yytext);return OR;}
"&&" {yylval.no = new GrammaNode(lineno, AND_,yytext);return AND;}
"<" {yylval.no = new GrammaNode(lineno, LT_,yytext);return LT;}
">" {yylval.no = new GrammaNode(lineno, BG_,yytext);return BG;}
"<=" {yylval.no = new GrammaNode(lineno, LQ_,yytext);return LQ;}
">=" {yylval.no = new GrammaNode(lineno, BQ_,yytext);return BQ;}
"const" {yylval.no = new GrammaNode(lineno, CONST_,yytext);return CONST;}
"int" { yylval.no = new GrammaNode(lineno, INT_,yytext);return INT;}
"void" { yylval.no = new GrammaNode(lineno, VOID_,yytext);return VOID;}
"if" { yylval.no = new GrammaNode(lineno, IF_,yytext);return IF;}
"else" { yylval.no = new GrammaNode(lineno, ELSE_,yytext);return ELSE;}
"while" { yylval.no = new GrammaNode(lineno, WHILE_,yytext);return WHILE;}
"break" {yylval.no = new GrammaNode(lineno, BREAK_,yytext);return BREAK;}
"continue" {yylval.no = new GrammaNode(lineno, CONTINUE_,yytext);return CONTINUE;}
"return" {yylval.no = new GrammaNode(lineno, RETURN_,yytext);return RETURN;}
{OCTAL} {yylval.no = new GrammaNode(lineno, IntConst_O_,yytext);return IntConst_O;}
{DECIMAL} {yylval.no = new GrammaNode(lineno, IntConst_D_,yytext);return IntConst_D;}
{HEXDECIMAL} {yylval.no = new GrammaNode(lineno, IntConst_H_,yytext);return IntConst_H;}
{IDENT} {yylval.no = new GrammaNode(lineno, Ident_,yytext);return Ident;}
%%
int main(int argc, char* argv[]) {
yylex();
return 0;
}
int yywrap() {
return 1;
}