Lex 入门
First!定义
%%
规则
%%
用户代码
int num_lines = 0, num_chars = 0;
%%
n ++num_lines; ++num_chars;
. ++num_chars;
%%
main()
{
yylex();
printf( "# of lines = %d, # of chars = %dn",
num_lines, num_chars );
}
字数统计程序的声明
%{
int wordCount = 0;
%}
chars [A-za-z_'."]
numbers ([0-9])+
delim [" "nt]
whitespace {delim}+
words {chars}+
%%
两个百分号标记指出了 Lex 程序中这一段的结束和三段中第二段的开始。
Lex 的模式匹配规则
{words} { wordCount++; /*
increase the word count by one*/ }
{whitespace} { /* do
nothing*/ }
{numbers} { /* one may
want to add some processing here*/ }
%%
void main()
{
yylex(); /* start the
analysis*/
printf(" No of words:
%dn", wordCount);
}
int yywrap()
{
return 1;
}
Lex 编程的基本元素就这样搞定了,它将帮助你编写简单的词法分析程序。
yyin FILE* 类型。 它指向 lexer 正在解析的当前文件。
yyout FILE* 类型。 它指向记录 lexer 输出的位置。 缺省情况下,yyin 和 yyout 都指向标准输入和输出。
yytext 匹配模式的文本存储在这一变量中(char*)。
yyleng 给出匹配模式的长度。
yylineno 提供当前的行数信息。(lexer不一定支持。)
Lex 函数
yylex() 这一函数开始分析。 它由 Lex 自动生成。
yywrap() 这一函数在文件(或输入)的末尾调用。如果函数的返回值是1,就停止解析。 因此它可以用来解析多个文件。代码可以写在第三段 ,这就能够解析多个文件。 方法是使用 yyin 文件指针(见上表)指向不同的文件,直到所有的文件都被解析 。最后,yywrap() 可以返回 1 来表示解析的结束。
yyless(int n) 这一函数可以用来送回除了前�n? 个字符外的所有读出标记。
yymore() 这一函数告诉 Lexer 将下一个标记附加到当前标记后。
/* scanner for a toy Pascal-like language */
%{ 内的东西会原封不动地出现在输出文件中 }%
%{
/* need this for the call to atof() below */
#include <math.h>
%}
DIGIT [0-9]
ID [a-z][a-z0-9]*%%
{DIGIT}+ {
printf( "An integer: %s (%d)n", yytext,
atoi( yytext ) );
}{DIGIT}+"."{DIGIT}* {
printf( "A float: %s (%g)n", yytext,
atof( yytext ) );
}if|then|begin|end|procedure|function {
printf( "A keyword: %sn", yytext );
}{ID} printf( "An identifier: %sn", yytext );"+"|"-"|"*"|"/" printf( "An operator: %sn", yytext );
"{"[^}n]*"}" /* eat up one-line comments */[ tn]+ /* eat up whitespace */
. printf( "Unrecognized character: %sn", yytext );%%
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* skip over program name */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
yylex();
}
模式 动作模式部分是正则表达式,动作部分是处理方法,动作部分如果时
[ t]+ putchar( ' ' );
[ t]+$ /* ignore this token */
我们先定义三个动作:
ECHO 将yytext输出
BEGIN 开始一个条件处理块
REJECT 指示简析器对当前规则不做处理,而是采用第二匹配规则。
int word_count = 0;%%frob special(); REJECT;
[^ tn]+ ++word_count;
%%
a |
ab |
abc |
abcd ECHO; REJECT;
.|n /* eat up any unmatched character */
`yymore()' 告诉解析器下一次匹配的规则,满足的部分将会添加到当前yytex
%%
mega- ECHO; yymore();
kludge ECHO;
%%
foobar ECHO; yyless(3);
[a-z]+ ECHO;
{
int i;
/* Copy yytext because unput() trashes yytext */
char *yycopy = strdup( yytext );
unput( ')' );
for ( i = yyleng - 1; i >= 0; --i )
unput( yycopy[i] );
unput( '(' );
free( yycopy );
}
%%
"/*" {
register int c;
for ( ; ; )
{
while ( (c = input()) != '*' &&
c != EOF )
; /* eat up text of comment */if ( c == '*' )
{
while ( (c = input()) == '*' )
;
if ( c == '/' )
break; /* found the end */
}
if ( c == EOF )注意: 如果简析器采用用C++编译,input()被yyinput(
{
error( "EOF in comment" );
break;
}
}
}
Lex进阶
模式模式在第一阶段或第二个阶段使用,也就是在申明或规则阶段中出现,模式定义了匹配的目标,目标被匹配后将会执行动作。
对于模式不想做太多说明,使用正则表达式定义,可以参看 regex 或 pcre.
开始条件
lex提供了根据条件激活规则的机制。在<sc>前缀的规则将会在解析器在"sc"的开始条件下被匹配。
将会在启动条件"STRING"的情况下被激活。<STRING>[^"]* { /* eat up the string body ... */...}
将会在 "INITIAL", "STRING", "QUOTE"三者之一的条件下被激活。<INITIAL,STRING,QUOTE>. { /* handle an escape ... */...}
开始条件在输入源的定义(第一)部分被申明,在‘%s' 或 ’%x'后跟随着名字列表。 %s申明了包含的开始条件,%x申明了排他的开始条件。开始条件被BEGIN动作激活。直到下一个BEGIN动作,满足开始条件名称的规则将会被规则,不满足启动条件的规则将不会被执行。
如果是包含条件,没有开始条件的规则也会被激活执行,如果时排他条件,只有满足开始条件的规则才会被执行。具有相同排他条件的规则的集合可以使解析器独立于其他的规则。 因此,排他条件可以容易地创建微型解析器处理输入源中的独立与其他部分的一部分(如,注释)。如果对于包含与排他条件还有混淆,可以看下面的例子。
%s example %%<example>foo do_something();bar something_else();
等同于
上面的程序中如果没有 <INITIAL,example> ,在example条件下bar规则将永远不会被激活。如果使用<example>,将会导致只能在exmaple开始条件下激活,而INITIAL条件下不会被激活。而第一个程序中在任何条件下bar都被会激活。因为第一个程序用example时%s,时包含条件。 页可以通过特殊开始条件<*>来配置任何开始条件,上面的程序还可以写为:%x example %%<example>foo do_something();<INITIAL,example>bar something_else();
缺省规则(显示任何未被匹配的字符)在开始条件下仍然生效。等同于:%x example %%<example>foo do_something();<*>bar something_else();
‘BEGIN(0)’在无开始条件的规则激活条件下返回原始状态,这个状态同于开始条件下的'INITIAL',所以‘BEGIN(INITIAL)'等同于’BEGIN(0)'。<*>.|\n ECHO;
BEGIN行为在规则部分的开头是默认的代码(
BEGIN
actions can also be given as indented code at the beginning of the rules section.请翻译) 例如,下面的代码将会仅需SPECIAL开始条件,不管合适yylex()被调用并且全局变量enter_special是true。
为了说明开始条件,我们用两种方法处理"123.456".缺省将会被解析为 '123','.','456'三个标记,如果expect-floats后面将会被解析为浮点数 123.456int enter_special;%x SPECIAL %%if ( enter_special )BEGIN(SPECIAL);<SPECIAL>blahblahblah ...more rules follow...
下面的代码能够是被C语言注释并且统计行数。%{ #include <math.h> %} %s expect%% expect-floats BEGIN(expect);<expect>[0-9]+"."[0-9]+ {printf( "found a float, = %fn",atof( yytext ) );} <expect>n {/* that's the end of the line, so* we need another "expect-number"* before we'll recognize any more* numbers*/BEGIN(INITIAL);}[0-9]+ {printf( "found an integer, = %dn",atoi( yytext ) );}"." printf( "found a dotn" );
%x comment %%int line_num = 1;"/*" BEGIN(comment);<comment>[^*n]* /* eat anything that's not a '*' */ <comment>"*"+[^*/n]* /* eat up '*'s not followed by '/'s */ <comment>n ++line_num; <comment>"*"+"/" BEGIN(INITIAL);
实际上,编写高速解析程序的办法时在每个规则中做尽可能多的匹配。注意: 开始条件的名字实际上时一个整形值并且能够被保存,所以,上面的代码可以扩展为:
This scanner goes to a bit of trouble to match as much text as possible with each rule. In general, when attempting to write a high-speed scanner try to match as much possible in each rule, as it's a big win.
而且,可能易使用YY_START宏来访问当前的开始条件。如上面的赋值条件可以改写为%x comment foo %%int line_num = 1;int comment_caller;"/*" {comment_caller = INITIAL;BEGIN(comment);}...<foo>"/*" {comment_caller = foo;BEGIN(comment);}<comment>[^*n]* /* eat anything that's not a '*' */ <comment>"*"+[^*/n]* /* eat up '*'s not followed by '/'s */ <comment>n ++line_num; <comment>"*"+"/" BEGIN(comment_caller);
comment_caller = YY_STARTYYSTATE是YY_START的别名(因为AT&T lex使用了YYSTATE)。
注意 开始条件没有他们的名字空间; %s 与 %x 申明与 #define形式一样。
到这里,时一个使用排他开始条件如何匹配C风格的引用字符串的处理。包含的扩展的转义,但不包括检查,因为代码太长。
通常,如上面的例子中所看到你,会有许多相同开始条件的处理。开始条件范围可以简化重复操作。%x str%%char string_buf[MAX_STR_CONST];char *string_buf_ptr;" string_buf_ptr = string_buf; BEGIN(str);<str>" { /* saw closing quote - all done */BEGIN(INITIAL);*string_buf_ptr = '0';/* return string constant token type and* value to parser*/}<str>n {/* error - unterminated string constant *//* generate error message */}<str>\[0-7]{1,3} {/* octal escape sequence */int result;(void) sscanf( yytext + 1, "%o", &result );if ( result > 0xff )/* error, constant is out-of-bounds */*string_buf_ptr++ = result;}<str>\[0-9]+ {/* generate error - bad escape sequence; something* like '48' or '0777777'*/}<str>\n *string_buf_ptr++ = 'n'; <str>\t *string_buf_ptr++ = 't'; <str>\r *string_buf_ptr++ = 'r'; <str>\b *string_buf_ptr++ = 'b'; <str>\f *string_buf_ptr++ = 'f';<str>\(.|n) *string_buf_ptr++ = yytext[1];<str>[^\n"]+ {char *yptr = yytext;while ( *yptr )*string_buf_ptr++ = *yptr++;}
<SCs>{ }
SCs 是一个或开始条件的列表。在这个开始条件范围内,每个规则将会自动具有前缀 `<SCs>' 直到 `}' 与开始的 `{' 匹配. 例如
<ESC>{"\n" return 'n';"\r" return 'r';"\f" return 'f';"\0" return '0'; }
等价于
<ESC>"\n" return 'n'; <ESC>"\r" return 'r'; <ESC>"\f" return 'f'; <ESC>"\0" return '0';
开始条件页可以嵌套,下面时三个管理开始条件堆栈的参数。
`void yy_push_state(int new_state)'
- 将当前的开始条件压栈,切换到 new_state 与使用 `BEGIN new_state'类似。
`void yy_pop_state()'- 从栈顶弹出,类似于
`int yy_top_state()'BEGIN
.- 返回栈顶值,不改变栈内容。
开始条件栈动态增长,没有固定限制,如果内容用尽,程序竟会终止。
为了使用开始条件栈,需要使用 `%option stack' 指令。
多输入缓存区
一些允许include文件解析器的解析器要求从几个输入流中读取内容。YY_INPUT只在结束缓存时被调用,碰到 include 后需要切换输入源,而解析一个描述也许需要很长时间。为了解决此类问题,解析器提供了创建并在多个输入缓存中创建的机制。输入缓存可以通过下面的方式创建:
YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
参数为与缓存关联的输入文件指针,以及足够的可维持size字符(如果不确定,size可以使用YY_BUF_SIZE)。返回一个YY_BUFFER_STATE,可以传递到其他的处理过程。YY_BUFFER_STATE是一个不可见结构yy_buffer_state的指针,所以可以安全地使用`((YY_BUFFER_STATE) 0)'来初始化YY_BUFFER_STATE,如果你愿意,你可以在解析器之外的源程序中引用这个不透明结构来正确的申明输入缓存。可以通过下面的参数来选择一个缓存区。
void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )
切换解析器的输入缓存将会导致记接下来的匹配项来自于新的缓存中。yy_switch_to_buffer可能出现在yywrap中为继续解析做准备,替换打开一个新的文件并执行yyin. 通过yy_switch_to_buffer 或 yywrap切换输入源不改变开始条件。
void yy_delete_buffer( YY_BUFFER_STATE buffer )
用于收回与缓存关联的空间。你可以使用下面的函数清空当前内容:
void yy_flush_buffer( YY_BUFFER_STATE buffer )
此函数废弃缓存内容,下一个解析器试图匹配一个内容时将会使用YY_INPUT来更新缓存区。
`yy_new_buffer()' 是 `yy_create_buffer()' 的一个别名,用于提供C++使用new 与 delete操作创建与销毁动态对象的兼容性。
最后, YY_CURRENT_BUFFER 宏返回 YY_BUFFER_STATE 指针,表示当前的缓存。
这里是一个扩展include使用的一个解析器 (`<<EOF>>' 特性将会在以后讨论):
/* "incl" 状态用于获取include的文件名 */
%x incl
%{
#define MAX_INCLUDE_DEPTH 10
YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
int include_stack_ptr = 0;
%}
%%
include BEGIN(incl);
[a-z]+ ECHO;
[^a-zn]*n? ECHO;
<incl>[ t]* /* eat the whitespace */
<incl>[^ tn]+ { /* got the include file name */
if ( include_stack_ptr >= MAX_INCLUDE_DEPTH )
{
fprintf( stderr, "Includes nested too deeply" );
exit( 1 );
}
include_stack[include_stack_ptr++] =
YY_CURRENT_BUFFER;
yyin = fopen( yytext, "r" );
if ( ! yyin )
error( ... );
yy_switch_to_buffer(
yy_create_buffer( yyin, YY_BUF_SIZE ) );
BEGIN(INITIAL);
}
<<EOF>> {
if ( --include_stack_ptr < 0 )
{
yyterminate();
}
else
{
yy_delete_buffer( YY_CURRENT_BUFFER );
yy_switch_to_buffer(
include_stack[include_stack_ptr] );
}
}
提供三个过程来实现内存字符串而不是文件输入缓存的解析。它们都要创建一个输入缓存来解析字符串,并且返回YY_BUFFER_STATE (可以在完成解析后用 `yy_delete_buffer()' 删除).,也可以通过`yy_switch_to_buffer()'来切换, 下一次调用`yylex()' 将会解析字符串。
`yy_scan_string(const char *str)' 解析0结尾字符串。
`yy_scan_bytes(const char *bytes, int len)' 解析bytes开始的len个字符(可能包含 0 字符)
注意,上面的两个函数会创建字符串或字节串的副本。(这也许时期望的,因为`yylex()' 会修改被解析缓存的内容) 可以使用下面的方式来拒绝使用副本:
`yy_scan_buffer(char *base, yy_size_t size)'将会从base开始解析,包含size个字节, 最后的两个字节必须是 YY_END_OF_BUFFER_CHAR (ASCII NUL)。他们不会被解析, 解析范围从 `base[0]' 到 `base[size-2]'(包含)。如果你没能按照这种规定使用base(如,忘记了最后的两个YY_END_OF_BUFFER_CHAR字节), `yy_scan_buffer()' 将会返回空指针而不创建YY_BUFFER_STATE。yy_size_t类型是个整型,可以转化为整数来反映buffer的长度。
文件结束规则
特殊规则 "<<EOF>>" 只是规则在文件结束位置发生且yywrap()返回非0值。(如,没有更多的文件要处理). 这个动作必须完成下面四件事情之一:
赋值给yyin一个新的文件 (早期版本的flex, 此操作后必须调用特殊动作 YY_NEW_FILE; 这个操作已经不需要了);
执行一个返回申明;
执行一个特殊的`yyterminate()' 动作;
或者使用`yy_switch_to_buffer()' 切换到一个新的输入缓存区.
<<EOF>> 不能与其他模式一起使用;它也许仅在开始条件列表申明。如果指定了不合法 <<EOF>> 规则, 它将会应用到所有的开始条件而不仅是 <<EOF>> 动作. 指定 <<EOF>> 规则仅在 initial 开始条件下匹配,就是用:
<INITIAL><<EOF>>
下面的规则可以发现象不关闭的注释类的问题。
%x quote
%%
...other rules for dealing with quotes...
<quote><<EOF>> {
error( "unterminated quote" );
yyterminate();
}
<<EOF>> {
if ( *++filelist )
yyin = fopen( *filelist, "r" );
else
yyterminate();
}