lex [-cntv] [-e | -w] [-V -Q [y | n]] [file]...
lex 实用程序可生成用于对字符输入进行词法处理的 C 程序,这些程序可用作 yacc 的接口。这些 C 程序由 lex 源代码生成,并且符合 ISO C 语言标准。通常,lex 实用程序会将其生成的程序写入文件 lex.yy.c 中。如果 lex 以非零的退出状态退出,则不指定此文件的状态。有关 lex 输入语言的完整说明,请参见“扩展描述”。
支持以下选项:
表示 C 语言操作(缺省选项)。
生成一个可处理 EUC 字符的程序(不能与 –w 选项一起使用)。yytext[ ] 的类型为 unsigned char[ ]。
禁用汇总统计信息,通常通过 –v 选项写入这些统计信息。如果在 lex 源代码中未指定表大小,且未指定 –v 选项,则隐式指定了 –n 选项。
将生成的程序写入标准输出,而非 lex.yy.c。
将汇总的 lex 统计信息写入标准错误。(请参见“lex 中的定义”标题下关于 lex 表大小的讨论。)如果在 lex 源代码中指定了表大小,但未指定 –n 选项,则可以启用 –v 选项。
生成一个可处理 EUC 字符的程序(不能与 –e 选项一起使用)。与 –e 选项不同,yytext[ ] 的类型为 wchar_t[ ]。
在标准错误中输出版本信息。
使用 –Qy 在输出文件 lex.yy.c 中输出版本信息。–Qn 选项不会输出版本信息,且为缺省值。
支持下列操作数:
输入文件的路径名称。如果指定了多个此类 file,则会将所有文件连接起来,生成一个 lex 程序。如果未指定 file 操作数,或者如果 file 操作数为 −,则会使用标准输入。
下面介绍了 lex 输出文件。
如果指定了 –t 选项,则 lex 的 C 源代码输出的文本文件将写入标准输出。
如果指定了 –t 选项,则有关 lex 源代码输入内容的信息性消息、错误消息和警告消息将写入标准错误。
如果未指定 –t 选项:
有关 lex 源代码输入内容的信息性消息、错误消息和警告消息会写入标准输出或标准错误。
如果指定了 –v 选项,但未指定 –n 选项,则 lex 统计信息也会写入标准错误。如果在“lex 中的定义”部分使用 % 运算符指定了表大小(请参见“扩展描述”),则只要未指定 –n 选项,也可以生成这些统计信息。
包含 C 源代码的文本文件会写入 lex.yy.c 或标准输出(如果存在 –t 选项)。
每个输入文件都包含 lex 源代码,这是一个表,包含各种正则表达式以及具有 C 程序片段形式的相应操作。
对 lex.yy.c 进行编译并与 lex 库进行链接(通过 c89 或 cc 并使用 –l l 操作数)之后,生成的程序会从标准输入读取字符输入,并将其分解为与给定表达式匹配的字符串。
当与某个表达式匹配后,会发生以下操作:
匹配的输入字符串作为以 null 结尾的字符串保留在 yytext 中;yytext 可能是外部字符数组或字符串的指针。如“lex 中的定义”中所述,可以使用 %array 或 %pointer 声明来明确选择类型,但是缺省类型为 %array。
外部 int yyleng 设置为匹配字符串的长度。
表达式的相应程序片段或操作将会执行。
在模式匹配过程中,lex 会搜索模式集以查找一个可能的最长匹配项。在与相同字符数匹配的规则中,会选择给定的第一个规则。
lex 源的通用格式为:
Definitions %% Rules %% User Subroutines
第一个 %% 是必需的,用来标记规则(正则表达式和操作)的开头,而第二个 %% 仅当后面跟有用户子例程时才需要。
在 Definitions in lex 部分中以空白字符开头的任何行都被假定为 C 程序片段,将会复制到 lex.yy.c 文件的外部定义区域。同样,“lex 中的定义”部分中包含在分隔符行(仅包含 %{ 和 %})之间的任何内容,也会不加更改地复制到 lex.yy.c 文件的外部定义区域。
在 Rules 部分的开头,在指定任何规则之前出现的任何此类输入(以空白字符开头或处于 %{ 和 %} 分隔符行之间)都会写入 lex.yy.c,位于 yylex 函数的变量声明之后,而在 yylex 中的首行代码之前。因此,可在此处声明 yylex 的本地用户变量,以及进入 yylex 时要执行的应用程序代码。
在遇到以空白字符开头或位于 %{ 和 %} 分隔符行之间的任何输入(出现在 Rules 部分中,但在已定义一个或多个规则之后)时,lex 要执行的操作未予以定义。出现此类输入会导致 yylex 函数定义错误。
Definitions in lex 出现在第一个 %% 分隔符之前。此部分中不包含在 %{ 和 %} 行之间,且不以空白字符开头的任何行均假设为用于定义 lex 替换字符串。这些行的格式为:
name substitute
如果 name 不符合 ISO C 标准中标识符的要求,其结果不确定。在规则中使用时,字符串 substitute 会替换字符串 { name }。在此上下文中,仅当 name 字符串带有花括号且不出现在方括号表达式或双引号中时,才会对其进行识别。
在 Definitions in lex 部分中,以 %(百分比符号)字符开头,后跟以 s 或 S 开头的字母数字单词的任何行可定义一组起始条件。以 % 开头,后跟以 x 或 X 开头的单词的任何行可定义一组互斥起始条件。当生成的扫描程序处于 %s 状态时,未指定状态的模式也处于活动状态;当扫描程序处于 %x 状态时,此类模式处于不活动状态。行中首个单词之后的剩余部分被视为一个或多个以空白字符分隔的起始条件名称。起始条件名称的构成方式与定义名称的构成方式相同。起始条件可用于将正则表达式的匹配操作限制为“lex 中的常规表达式”中所述的一个或多个状态。
程序的实现可在“lex 中的定义”部分中接受下列两个互斥声明中的任一个:
声明 yytext 的类型为以 null 结尾的字符数组。
声明 yytext 的类型为以 null 结尾的字符串的指针。
使用 %pointer 选项时,不能同时使用 yyless 函数来更改 yytext。
%array 为缺省值。如果指定了 %array(或 %array 和 %pointer 均未指定),则创建 yyext 的外部引用的正确方式是使用下列格式的声明:
extern char yytext[ ]
如果指定了 %pointer,则创建外部引用的正确方式是使用下列格式的声明:
extern char *yytext;
lex 接受“lex 中的定义”部分中用于设置某些内部表大小的声明。这些声明如下表所示。
lex 中的表大小声明
|
lex 生成的程序需要 –e 或 –w 选项来处理某些输入,其中包含来自辅助代码集的 EUC 字符。如果这两个选项均未指定,则 yytext 的类型为 char[ ],且生成的程序只能处理 ASCII 字符。
使用 –e 选项时,yytext 的类型为 unsigned char[ ],且 yyleng 会给出匹配字符串中的总字节数。使用此选项时,宏 input()、unput(c) 和 output(c) 应以与使用常规 ASCII lex 相同的方式,执行基于字节的 I/O。使用 –e 选项时还有另外两个变量 yywtext 和 yywleng,其行为与使用 –w 选项时的 yytext 和 yyleng 相同。
使用 –w 选项时,yytext 的类型为 wchar_t[ ],且 yyleng 会给出匹配字符串中的总字符数。如果在使用此选项时提供您自己的 input()、unput(c) 或 output(c) 宏,则它们必须返回或接受宽字符格式 (wchar_t) 的 EUC 字符。这允许在您的程序和 lex 内部之间使用其他界面,以便加速某些程序。
Rules in lex 源文件是一个表,左列中包含正则表达式,右列中包含识别出表达式时要执行的相应操作(C 程序片段)。
ERE action ERE action ...
一行中的扩展正则表达式 (ERE, extended regular expression) 部分通过一个或多个空白字符与 action 分隔开。在下列条件之一下可识别包含空白字符的正则表达式:
整个表达式出现在双引号中。
空白字符出现在双引号或方括号中。
每个空白字符前面有反斜杠字符。
用户子例程部分中的所有内容都会复制到 lex.yy.c 中,位于 yylex 之后。
lex 实用程序支持 regex(5) 中介绍的扩展正则表达式 (ERE, Extended Regular Expressions) 集,并在语法上有下列增加内容和例外:
括在双引号中的任何字符串表示双引号中的字符本身,除非识别出转义反斜杠(在下表中列出)。任何反斜杠转义序列都以右引号结尾。例如," \ 01""1" 表示一个字符串:后跟字符 1 的八进制值 1。
<state>r
仅当程序处于由 state、state1 等表示的起始条件之一时,才对正则表达式 r 进行匹配。有关更多信息,请参见“lex 中的操作”。作为本文档其余部分的印刷约定的例外情况,此处 <state> 不表示元变量,而是将符号括起来的文字尖括号字符。起始条件仅在正则表达式的开头才会以此种方式识别。
正则表达式 r 仅当后面跟有正则表达式 x 时才会进行匹配。yytext 中返回的标记仅与 r 匹配。如果 r 的结尾部分与 x 的开头匹配,其结果不确定。r 表达式不能包含更多的结尾上下文或 $(匹配行结尾)运算符;x 不能包含 ^(匹配行开头)运算符、结尾上下文以及 $ 运算符。也就是说,在 lex 正则表达式中仅允许出现一次结尾上下文,且 ^ 运算符只能在此类表达式的开头使用。另一个限制是结尾上下文运算符 /(斜杠)不能在括号中分组。
如果 name 为 Definitions 部分的替换符号之一,则包含花括号在内的该字符串将替换为 substitute 值。substitute 值在扩展的正则表达式中被视为已括在括号中。如果 {name} 出现在方括号表达式或双引号中,则不会执行替换。
在 ERE 中,反斜杠字符( \\、\ a、\ b、\ f、\ n、\ r、\ t、\ v)被视为开始转义序列。此外,还会识别下表中的转义序列。
ERE 中不能出现文本换行符;转义序列 \ n 可用于表示换行符。不能使用句点运算符匹配换行符。
lex 中的转义序列
|
下表从高到低显示了对 lex 扩展正则表达式给予的优先级顺序。
转义的字符条目并不表示它们是运算符,包括在此表中是为了显示它们与真正运算符之间的关系。由于此部分中介绍的位置限制,此表省略了起始条件、结尾上下文以及锚定表示法;它们只能出现在 ERE 的开头或结尾。
|
表中不显示 ERE 锚定运算符( ^ 和 $ )。使用 lex 正则表达式时,这些运算符的使用受以下限制:^ 运算符只能用于整个正则表达式的开头,而 $ 运算符只用于结尾。这些运算符适用于整个正则表达式。因此,并未定义例如 (^abc)|(def$) 这样的模式,而是可以将其写成两个单独的规则,一个用于正则表达式 ^abc,另一个用于 def$,两者通过特殊的 | 操作共享一个共同的操作(请参见下文)。如果模式写为 ^abc|def$,它将通过自身在一行上匹配 abc 或 def。
与一般 ERE 规则不同,大多数以前的 lex 实现不允许使用嵌入式锚定。嵌入式锚定的一个例子是,使用 (^)foo($)) 这样的模式来匹配作为完整单词存在的 foo。使用现有的 lex 功能可实现此功能:
^foo/[ \ n]| " foo"/[ \ n] /* found foo as a separate word */
另请注意,$ 是结尾上下文的一种形式(它等效于 /\ n),因此不能用于包含其他运算符实例的正则表达式(请参见前面对结尾上下文的讨论)。
如果其他正则表达式结尾上下文运算符 /(斜杠)出现在双引号中 (" / ")、以反斜杠开头 (\ /) 或位于方括号表达式中 ([ / ]),则可用作普通字符。起始条件 < 和 > 运算符仅在正则表达式开头的起始条件中为特殊字符,在正则表达式的其他位置被视为普通字符。
以下示例阐明了 lex 正则表达式和在本文档其他位置出现的正则表达式之间的区别。对于 r/x 形式的正则表达式,始终返回与 r 匹配的字符串;如果 x 的开头与 r 的结尾部分匹配,则会产生混淆。例如,如果给定正则表达式 a*b/cc 和输入 aaabcc,则 yytext 找到的匹配项中会包含字符串 aaab。但是,如果给定正则表达式 x*/xy 和输入 xxxy,则某些实现会由于 xxx 匹配 x* 而返回令牌 xxx,而不是 xx。
在规则 ab*/bc 中,位于 r 结尾的 b* 会将 r 的匹配内容扩展到结尾上下文的开始部分,因此结果不确定。但是,如果此规则为 ab/bc,则此规则会与后跟文本 bc 的文本 ab 匹配。在后一种情况下,r 的匹配无法扩展到 x 的开头,因此结果是确定的。
ERE 匹配时要执行的操作可以是 C 程序片段,或下面介绍的特殊操作;程序片段可以包含一个或多个 C 语句,还可以包含特殊操作。空 C 语句 ; 是有效的操作;实际会忽略或跳过在 lex.yy.c 输入中与此类规则的模式部分匹配的的所有字符串。但是,缺少操作是无效的,未定义 lex 在这种情况下所执行的操作。
如果操作的规范(包括 C 语句和特殊操作)括在花括号中,则可以跨多行进行扩展:
ERE <one or more blanks> { program statement program statement }
当 lex.yy.c 程序的输入中的字符串与任何表达式均不匹配时,缺省操作为将字符串复制到输出中。由于 lex 生成的程序的缺省行为是读取输入并将其复制到输出,因此仅包含 %% 的最小 lex 源程序会生成只将输入不加更改地复制到输出的 C 程序。
提供了四个特殊操作:
| ECHO; REJECT; BEGIN
操作 | 表示下一规则的操作即为此规则的操作。与其他三个操作不同,| 不能括在花括号中,也不能以分号结尾。必须在没有其他操作的情况下单独指定。
将字符串 yytext 的内容写入到输出中。
通常输入中的给定字符串仅与一个表达式匹配。REJECT 表示继续查找与当前输入匹配的下一个表达式,从而导致任何规则都成为要为同一个输入执行的当前规则之后的第二选择。因此,可以为一个输入字符串或重叠输入字符串匹配和执行多个规则。例如,如果给定了正则表达式 xyz 和 xy,以及输入 xyz,则通常只有正则表达式xyz 会匹配。下一个尝试的匹配会从 z 之后开始。如果 xyz 规则中的最后一个操作为 REJECT,则会同时执行此规则和 xy 规则。REJECT 操作可按这样的方式实现:在此操作之后不继续执行控制流,就如同相对于 goto 一样,跳到 yylex 的另一部分。使用 REJECT 会导致扫描程序更大且更慢。
操作:
BEGIN newstate;
将状态(起始条件)切换到 newstate。如果之前未在 Definitions in lex 部分将字符串 newstate 声明为起始条件,则结果不确定。初始状态由数字 0 或令牌 INITIAL 指定。
包含在 lex 输入中的用户代码不可访问下述函数或宏。未指定它们是出现在 lex 的 C 代码输出中,还是只能通过 c89 或 cc(lex 库)的 –l l 操作数访问。
对输入执行词法分析;这是 lex 实用程序生成的主要函数。到达输入结尾时,此函数会返回零;否则将返回由选择的操作确定的非零值(令牌)。
调用此函数时,将指示在识别出下一个输入字符串时,将其附加到 yytext 的当前值,而不是替换该值;yyleng 中的值将相应地调整。
在 yytext 中保留 n 个初始字符,以 null 结尾,并将剩余的字符视为尚未读取;yyleng 中的值将相应地调整。
从输入返回下一个字符,或在文件结尾则返回零。它从流指针 yyin 获取输入(尽管可能通过中间缓冲区)。因此,开始扫描后,更改 yyin 的值的影响不确定。读取的字符会从扫描程序的输入流中删除,而扫描程序不会进行任何处理。
将字符 c 返回到输入;在匹配下一个表达式之前,yytext 和 yyleng 处于不确定状态。针对比输入内容更多的字符使用 unput 时,其结果不确定。
以下函数仅出现在可通过 –l l 操作数访问的 lex 库中;因此可移植应用程序可以重新定义这些函数:
由 yylex 于文件结尾调用;缺省 yywrap 始终返回 1。如果应用程序需要 yylex 继续处理其他输入源,则该应用程序可包含函数 yywrap,该函数可将另一个文件与外部变量 FILE *yyin 关联,并返回为零的值。
调用 yylex 以执行词法分析,然后退出。用户代码可包含 main 来执行特定于应用程序的操作,并视情况调用 yylex。
之所以将这些函数分到两个列表中,原因是可移植应用程序只能对 libl.a 中的函数可靠地重新定义。
除 input、unput 和 main 以外,由 lex 生成的所有外部和静态名称都以 yy 或 YY 为前缀。
可移植应用程序需要注意,“lex 中的规则”部分不接受没有操作的 ERE,但是无需由 lex 对此作为错误来检测。这会导致编译或运行时错误。
对于词法分析而言,input 的目的是从输入流中剥离某些字符,将其丢弃。常见用法是识别注释的开头后,就丢弃该注释的正文。
在 lex 源代码或生成的词法分析器中,lex 实用程序对正则表达式的处理方法未完全国际化。执行词法分析器时,似乎需要让词法分析器根据指定的环境解释 lex 源中给定的正则表达式,但是当前的 lex 技术不可能实现这一点。此外,lex 生成的词法分析器在本质上必须与所描述的输入语言的词法要求紧密关联,而这通常总是特定于语言环境的。(例如,编写的适用于法语文本的分析器并不会自动适用于处理其他语言。)
下面是 lex 程序的示例,该程序实现一个类 Pascal 语法的基本扫描程序:
%{ /* need this for the call to atof() below */ #include <math.h> /* need this for printf(), fopen() and stdin below */ #include <stdio.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: %s\n", yytext); } {ID} printf("An identifier: %s\n", yytext); "+"|"-"|"*"|"/" printf("An operator: %s\n", yytext); "{"[^}\n]*"}" /* eat up one-line comments */ [ \t\n]+ /* eat up white space */ . printf("Unrecognized character: %s\n", yytext); %% int main(int argc, char *argv[ ]) { ++argv, --argc; /* skip over program name */ if (argc > 0) yyin = fopen(argv[0], "r"); else yyin = stdin; yylex(); }
有关影响 lex 执行的以下环境变量的描述,请参见 environ(5):LANG、LC_ALL、LC_COLLATE、LC_CTYPE、LC_MESSAGES 和 NLSPATH。
将返回以下退出值:
成功完成。
出现错误。
有关下列属性的说明,请参见 attributes(5):
|
yacc(1)、attributes(5)、environ(5)、regex(5)、standards(5)
如果 .l (ell) 文件中的 yyback()、yywrap() 和 yylock() 等例程将作为外部 C 函数,则用于编译 C++ 程序的命令行必须定义 __EXTERN_C__ 宏。例如:
example% CC –D__EXTERN_C__ ... file