词法分析器采取的操作

当词法分析器与说明文件规则部分中的一个扩展正则表达式匹配时,它执行与扩展正则表达式相对应的操作。没有足够的规则匹配输入流中的所有字符串,词法分析器则将输入复制到标准输出。因此,不要创建仅将输入复制到输出的规则。缺省的输出能够帮助在规则中查找间隔。

当使用 lex 命令处理由 yacc 命令产生的解析器的输入时,请提供与所有输入字符串匹配的规则。那些规则必须生成 yacc 命令能够解释的输出。

空操作

要忽略与扩展正则表达式关联的输入,请使用 ;(C 语言空语句)作为操作。下面的示例忽略了三个间隔字符(空白、制表符和换行):

[ \t\n] ;

与下一个操作相同

要避免反复写相同的操作,请使用 |(管道符号)。此字符指示此规则的操作与下一条规则的操作相同。例如,先前忽略空白、制表符和换行字符的示例也可写成:

" "                     |
"\t"                    |
"\n"                    ;

\n\t 两边的引号并不需要。

打印匹配字符串

要确定哪个文本与说明文件的规则部分的表达式匹配,您可以包扩 C 语言 printf 子例程调用作为该表达式的一个操作。当词法分析器在输入流中找到匹配,程序将匹配字符串放入外部字符(char)和宽字符(wchar_t)数组中,分别称为 yytextyywtext。例如,您能使用下面的规则打印匹配字符串:

[a-z]+            printf("%s",yytext);

C 语言 printf 子例程接受格式参数和要打印的数据。在此示例中,printf 子例程的参数具有下面的含义:

%s 在打印之前将数据转换为类型字符串的符号
%S 在打印之前将数据转换为宽字符串(wchar_t)的符号
yytext 包含要打印的数据的数组的名称
yywtext 包含要打印的多字节类型(wchar_t)数据的数组名称

lex 命令定义 ECHO;作为要打印 yytext 的内容的特殊操作。例如,下面的两条规则是等价的:

[a-z]+       ECHO;
[a-z]+       printf("%s",yytext);

您可以在 lex 说明文件的定义部分使用 %array 或者 %pointer 如下更改 yytext 的说明:

%array yytext 定义为以 null 结束的字符数组。这是缺省操作。
%pointer yytext 定义为指向以 null 结束的字符串的指针。

查找匹配字符串的长度

要查找词法分析器与特定的扩展正则表达式所匹配的字符数,请使用 yyleng 或者 yywleng 外部变量。

yyleng 跟踪匹配的字节数。
yywleng 跟踪匹配字符串中的宽字符数。多字节字符的长度大于 1。

要对输入的字数和字中的字符数进行计数,请使用下面的操作:

[a-zA-Z]+       {words++;chars += yyleng;}

此操作总计匹配的字中的字符数,并将该数字赋予 chars

下面的表达式在匹配字符串中查找最后一个字符:

yytext[yyleng-1]

匹配字符串中的字符串

lex 命令对输入流进行分区,并不搜索每个表达式的所有可能的匹配字符串。每个字符仅计算一次。要覆盖此选项并搜索可能重叠或者互相包含的项,请使用 REJECT 操作。例如,要对 shehe 的所有实例(包括包含在 she 中的 he)计数,请使用下面的操作:

she              {s++; REJECT;}
he               {h++}
\n          |.           ;

在对 she 的出现次数进行计数后,lex 命令拒绝输入字符串,然后对 he 的出现次数进行计数。因为 he 并不包括 she,所以 REJECT 操作不必在 he 上。

将结果添加到 yytext 数组

典型情况下,来自输入流的下一个字符串覆盖 yytext 数组中的当前项。如果您使用 yymore 子例程,来自输入流的下一个字符串将被添加到 yytext 数组的当前项的尾部。

例如,下面的词法分析器搜索字符串:

%s instring
%%
<INITIAL>\"     {  /* start of string */
         BEGIN instring;
         yymore();
        }
<instring>\"    {  /* end of string */
         printf("matched %s\n", yytext);
         BEGIN INITIAL;
        }
<instring>.     {
         yymore();
        }
<instring>\n    {
         printf("Error, new line in string\n");
         BEGIN INITIAL;
        }

尽管通过匹配多个规则,字符串可能被识别,但是反复调用 yymore 子例程可以确保 yytext 数组包含整个字符串。

将字符返回到输入流

要将字符返回给输入流,请使用下面的调用:

yyless(n)

其中 n 是当前字符串中要保持的字符数。字符串中超过此数目的字符被返回到输入流。yyless 子例程提供的先行函数类型与 /(斜杠)运算符所使用的相同,但是它允许更多对其用法的控制。

不止一次使用 yyless 子例程处理文本。例如,当语法分析 C 语言程序时,诸如 x=-a 之类的表达式很难理解。它表示 x等于-a,还是 x -= a 的旧的表述形式(意味着将 x减去a)?要将此表达式作为 x等于-a 处理,但是要打印警告消息则请使用如下的规则:

=-[a-zA-Z]      {
                printf("Operator (=-) ambiguous\n");
                yyless(yyleng-1);
                ... action for = ...
                }

输入/输出子例程

lex 程序允许程序使用下述输入/输出(I/O)子例程:

input() 返回下一个输入字符
output(c) 将字符 c 写到输出
unput(c) 将字符 c 推回输入流,稍后再通过 input 子例程读出
winput() 返回下一个多字节输入字符
woutput(C) 将多字节字符 C 写回输出流
wunput(C) 将多字节字符 C 推回输入流,以通过 winput 子例程读出

lex 程序提供这些子例程作为宏定义。子例程的代码在 lex.yy.c 文件中。您能覆盖它们并提供其他版本。

定义 winputwunputwoutput 宏以使用 yywinputyywunputyywoutput 子例程。考虑到兼容性,yy 子例程随后使用 inputunputoutput 子例程来读、写和替换完全多字节字符中需要数目的字节。

这些子例程定义外部文件和内部字符之间的关系。如果您更改子例程,请以相同的方式将它们全部更改。这些子例程应该遵循这些规则:

  • 所有的子例程必须使用相同的字符集。
  • input 子例程必须返回 0 值以指示文件的末尾。
  • 不要更改 unput 子例程和 input 子例程的关系,否则先行函数会不起作用。

lex.yy.c 文件允许词法分析器最多备份 200 个字符。

要读包含 NULL 的文件,请创建不同版本的 input 子例程。在 input 子例程的正常版本中,(从空字符)返回的值 0 表明这是文件的末尾,且将终止输入。

字符集

lex 命令生成的词法分析器通过 input、outputunput 子例程处理字符 I/O。因此,要在 yytext 子例程中返回值,lex 命令使用这些子例程使用的字符说明。但是,在内部 lex 命令使用小整数代表每一个字符。当使用标准库时,此整数是计算机用来表示字符的位模式的值。正常情况下,字母 a 用与字符常量 a 相同的格式表示。如果您使用不同的 I/O 子例程更改此解释,请将转换表放到说明文件的定义部分。转换表在包含下述条目的行开始和结束:

%T

转换表包含指示与每个字符关联的值的其他行。例如:

%T
{integer}       {character string}
{integer}       {character string}
{integer}       {character string}
%T

文件末尾处理

当词法分析器到达文件末尾时,它调用 yywrap 库子例程,此调用返回值 1,指示词法分析器应该继续在输入末尾正常结束。

但是,如果词法分析器从多个源接收到输入,请更改 yywrap 子例程。新的函数必须获取新的输入并将值 0 返回给词法分析器。返回值 0 指示程序应该继续处理。

您也可以包含代码,以在词法分析器在新版本的 yywrap 子例程中终止时,打印摘要报告和表。yywrap 子例程是强制 yylex 子例程识别输入末尾的唯一途径。