编译原理-词法分析实践(flex)

词法分析程序flex

基本格式

%{
C语言声明,一般声明全局变量和函数,会复制进lex.yy.c中
%}
定义正则表达式的名字,可以在规则段中使用
%%
规则段,每一行都是一条规则,每一条规则由匹配模式和事件组成。每当一个模式被匹配到,后面的事件被执行!
%%
用户自定义过程,直接复制到lex.yy.c末尾

使用详解

%{
#define T_WORD 1
int numChars = 0, numWords = 0, numLines = 0;
%}

WORD		([^ \t\n\r\a]+)

%%
\n			{ numLines++; numChars++; }
{WORD}		{ numWords++; numChars += yyleng; return T_WORD; }
<<EOF>>		{ return 0; }
.			{ numChars++; }
%%

int main() {
	int token_type;
	while (token_type = yylex()) {
		printf("WORD:\t%s\n", yytext);
	}
	printf("\nChars\tWords\tLines\n");
	printf("%d\t%d\t%d\n", numChars, numWords, numLines);
	return 0;
}

int yywrap() {
	return 1;
}

1.声明(Declarations)

%{ 和 %} 之间的为 声明(Declarations)。

%{
#define T_WORD 1
int numChars = 0, numWords = 0, numLines = 0;
%}

都是 C 代码,这些代码会被原样的复制到 lex.yy.c 文件中,一般在这里声明一些全局变量函数,这样在后面可以使用这些变量和函数。

2.定义

%} 和 %% 之间的为 定义(Definitions)

WORD		([^ \t\n\r\a]+)

在这里可以定义正则表达式中的一些名字,可以在 规则(Rules) 段被使用,如本文件中定义了 WORD 为 ( [ ^ \t\n\r\a] +) , 这样在后面可以用 {WORD} 代替这个正则表达式。

3.规则(rules)

%% 和 %% 之间的内容被称为 规则(rules)

%%
\n			{ numLines++; numChars++; }
{WORD}		{ numWords++; numChars += yyleng; return T_WORD; }
<<EOF>>		{ return 0; }
.			{ numChars++; }
%%

flex 会将本段内容翻译成一个名为 yylex 的函数,该函数的作用就是扫描输入文件(默认情况下为标准输入)。如果没有匹配到 return 语句,则执行完这些 C 代码后, yylex 函数会继续运行,开始下一轮的扫描和匹配。

4. 用户定义过程(User subroutines)

flex 会将这些代码原样的复制到 lex.yy.c 文件的最后面。

int main() {
	int token_type;
	while (token_type = yylex()) {
		printf("WORD:\t%s\n", yytext);
	}
	printf("\nChars\tWords\tLines\n");
	printf("%d\t%d\t%d\n", numChars, numWords, numLines);
	return 0;
}

int yywrap() {
	return 1;
}
yywrap 函数

输入文件中最后一行的 yywrap 函数的作用是将多个输入文件打包成一个输入,当 yylex 函数读入到一个文件结束(EOF)时,它会向 yywrap 函数询问。
yywrap 函数返回 1 的意思是告诉 yylex 函数后面没有其他输入文件了,此时 yylex 函数结束,yywrap 函数也可以打开下一个输入文件,再向 yylex 函数返回 0 ,告诉它后面还有别的输入文件,此时 yylex 函数会继续解析下一个输入文件。总之,由于我们不考虑连续解析多个文件,因此此处返回 1 。


以上 4 段中,除了 Rules 段是必须要有的外,其他三个段都是可选的。

本例中的 action 中有 return 语句,而 main 函数内是一个 while 循环,只要 yylex 函数的返回值不为 0 ,则 yylex 函数将被继续调用,此时将从下一个字符开始新一轮的扫描。

本例中使用到了 flex 提供的两个全局变量 yytext 和 yyleng,分别用来表示刚刚匹配到的字符串以及它的长度。

使用 flex 对 TinyC 源文件进行词法分析

步骤

  1. 列出 TinyC 中所有类型的 token;

  2. 为每种类型的 token 分配一个唯一的编号,同时写出此 token 的正则表达式;

  3. 写出每种 token 的 rule (相应的 pattern 和 action )。

1. 列出 TinyC 中所有类型的 token;

TinyC 中的 token 的种类非常少,按其词法特性,分为以下三大类。

  • 第 1 类为单字符运算符,一共 15 种:
    + * - / % = , ; ! < > ( ) { }
  • 第 2 类为双字符运算符和关键字,一共 16 种:
    <=, >=, ==, !=, &&, ||
    void, int, while, if, else, return, break, continue, print, readint
  • 第 3 类为整数常量、字符串常量和标识符(变量名和函数名),一共 3 种。
/* Definitions, note: \042 is '"' */
INTEGER             ([0-9]+)
UNTERM_STRING       (\042[^\042\n]*)
STRING              (\042[^\042\n]*\042)
IDENTIFIER          ([_a-zA-Z][_a-zA-Z0-9]*)
OPERATOR            ([+*-/%=,;!<>(){}])
SINGLE_COMMENT1     ("//"[^\n]*)
SINGLE_COMMENT2     ("#"[^\n]*)

2. 为每种类型的 token 分配一个唯一的编号,同时写出此 token 的正则表达式;

token 的编号原则为:单字符运算符的 token 编号就是其字符的数值,其他类型的 token 则从 256 开始编号。

token.h

#ifndef TOKEN_H
#define TOKEN_H

typedef enum {
    T_Le = 256, T_Ge, T_Eq, T_Ne, T_And, T_Or, T_IntConstant,
    T_StringConstant, T_Identifier, T_Void, T_Int, T_While,
    T_If, T_Else, T_Return, T_Break, T_Continue, T_Print,
    T_ReadInt
} TokenType;

static void print_token(int token) {
    static char* token_strs[] = {
        "T_Le", "T_Ge", "T_Eq", "T_Ne", "T_And", "T_Or", "T_IntConstant",
        "T_StringConstant", "T_Identifier", "T_Void", "T_Int", "T_While",
        "T_If", "T_Else", "T_Return", "T_Break", "T_Continue", "T_Print",
        "T_ReadInt"
    };

    if (token < 256) {
        printf("%-20c", token);//-20表示输出长度为20,左对齐,右边19个为空格。
    } else {
        printf("%-20s", token_strs[token-256]);//-20表示左对齐,输出长度为20。
    }
}

#endif

3. 写出每种 token 的 rule (相应的 pattern 和 action )

省略。。。

完整框架

%{
#include "token.h"
int cur_line_num = 1;
void init_scanner();
void lex_error(char* msg, int line);
%}

/* Definitions, note: \042 is '"' */
INTEGER             ([0-9]+)
UNTERM_STRING       (\042[^\042\n]*)
STRING              (\042[^\042\n]*\042)
IDENTIFIER          ([_a-zA-Z][_a-zA-Z0-9]*)
OPERATOR            ([+*-/%=,;!<>(){}])
SINGLE_COMMENT1     ("//"[^\n]*)
SINGLE_COMMENT2     ("#"[^\n]*)

%%

[\n]                { cur_line_num++;                       }
[ \t\r\a]+          { /* ignore all spaces */               }
{SINGLE_COMMENT1}   { /* skip for single line comment */    }
{SINGLE_COMMENT2}   { /* skip for single line commnet */    }

{OPERATOR}          { return yytext[0];         }   

"<="                { return T_Le;              }
">="                { return T_Ge;              }
"=="                { return T_Eq;              }
"!="                { return T_Ne;              }
"&&"                { return T_And;             }
"||"                { return T_Or;              }
"void"              { return T_Void;            }
"int"               { return T_Int;             }
"while"             { return T_While;           }
"if"                { return T_If;              }
"else"              { return T_Else;            }
"return"            { return T_Return;          }
"break"             { return T_Break;           }
"continue"          { return T_Continue;        }
"print"             { return T_Print;           }
"readint"           { return T_ReadInt;         }

{INTEGER}           { return T_IntConstant;     }
{STRING}            { return T_StringConstant;  }
{IDENTIFIER}        { return T_Identifier;      }

<<EOF>>             { return 0; }

{UNTERM_STRING}     { lex_error("Unterminated string constant", cur_line_num);  }
.                   { lex_error("Unrecognized character", cur_line_num);        }

%%

int main(int argc, char* argv[]) {
    int token;
    init_scanner();
    while (token = yylex()) {
        print_token(token);
        puts(yytext);
    }
    return 0;
}

void init_scanner() {
    printf("%-20s%s\n", "TOKEN-TYPE", "TOKEN-VALUE");
    printf("-------------------------------------------------\n");
}

void lex_error(char* msg, int line) {
    printf("\nError at line %-3d: %s\n\n", line, msg);
}

int yywrap(void) {
    return 1;
}

参考

自己动手写编译器[https://pandolia.net/tinyc/ch8_flex.html]

flex test.l //此后会生成C文件lex.yy.c
gcc lex.yy.c //使用gcc编译成可执行文件

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值