A.1 Makefiles和Flex
在本附录中,我们提供了编写Makefile来构建扫描器的技巧。
在传统的构建环境中,我们说.c文件是源文件,而.o文件是中间文件。但是在使用flex时,.l文件是源文件,而生成的.c文件(以及.o文件)是中间文件。这需要你仔细规划Makefile。
现代的make程序明白foo.l旨在生成lex.yy.c或foo.c,并将相应的执行。下面的Makefile没有明确指示make如何从foo.l构建foo.c。相反,它依赖于make程序的隐式规则来构建中间文件scan.c:
# Basic Makefile -- relies on implicit rules
# Creates "myprogram" from "scan.l" and "myprogram.c"
#
LEX=flex
myprogram: scan.o myprogram.o
scan.o: scan.l
对于简单的情况,上述可能就足够了。对于其它情况,您可能必须明确指示make如何构建扫描器。下面是一个包含显式规则的Makefile示例:
# Basic Makefile -- provides explicit rules
# Creates "myprogram" from "scan.l" and "myprogram.c"
#
LEX=flex
myprogram: scan.o myprogram.o$(CC) -o $@ $(LDFLAGS) $^myprogram.o: myprogram.c$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $^scan.o: scan.c$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $^scan.c: scan.l$(LEX) $(LFLAGS) -o $@ $^clean:$(RM) *.o scan.c
注意,在上面的示例中,scan.c位于clean目标中。这是因为我们认为scan.c是一个中间文件。
最后,我们提供了一个与bison解析器一起使用flex扫描器的实际示例。我们必须处理一个棘手的问题。由于flex扫描器通常会包含解析器生成的头文件(例如y.tab.h),因此我们需要确保在扫描器编译之前生成了头文件。我们在下面的例子中处理这种情况:
# Makefile example -- scanner and parser.
# Creates "myprogram" from "scan.l", "parse.y", and "myprogram.c"
#
LEX = flex
YACC = bison -y
YFLAGS = -d
objects = scan.o parse.o myprogram.omyprogram: $(objects)
scan.o: scan.l parse.c
parse.o: parse.y
myprogram.o: myprogram.c
在上面的示例中,注意下面这行:
scan.o: scan.l parse.c
它将文件parse.c(生成的解析器)列为scan.o的依赖项。我们希望确保在编译扫描器之前创建解析器,上面的代码行似乎可以做到这一点。请随意试验make的具体实现。
关于编写makefile的更多详细信息,参见GNU Make手册。
A.2 带Bison解析器的C扫描器
这个章节描述了在将flex与GNU bison集成时有用的flex特性。如果你的扫描器不使用bison,请跳过本节。这里我们只讨论flex和bison对的flex的这一半。我们不会详细讨论bison。有关生成bison解析器的更多信息,参见GNU Bison手册。
通过声明’%option bison-bridge’或在从命令行调用flex时提供’--bison-bridge’,可以生成兼容的bison扫描器。这指示flex可以使用宏yylval。yylval的数据类型YYSTYPE,通常在头文件中定义,包含在flex输入文件的第一节中。有关有效的函数和宏的列表,请查看bison-functions章节。
yylex的声明变为:
int yylex(YYSTYPE * lvalp,yyscan_t scanner);
如果%option bison-locations被指定,则声明变为:
int yylex(YYSTYPE * lvalp,YYLTYPE *llocp, yyscan_t scanner);
注意,宏yylval和yylloc计算的值为指针。对yylloc的支持在bison中是可选的,因此在flex中也是可选的。下面是一个与bison兼容的flex扫描器的示例。
/* Scanner for "C" assignment statements... sort of. */
%{
#include "y.tab.h" /* Generated by bison. */
%}%option bison-bridge bison-locations
%[[:digit:]]+ { yylval->num = atoi(yytext); return NUMBER;}
[[:alnum:]]+ { yylval->str = strdup(yytext); return STRING;}
"="|";" { return yytext[0];}
. {}
%
正如你所看到的,这里真的没有魔法。我们只是像对其它变量一样使用yylval。yylval的数据类型由bison生成,并包含在文件y.tab.h中。下面是相应的bison解析器:
/* Parser to convert "C" assignments to lisp. */
%{
/* Pass the argument to yyparse through to yylex. */
#define YYPARSE_PARAM scanner
#define YYLEX_PARAM scanner
%}
%locations
%pure_parser
%union {int num;char* str;
}
%token <str> STRING
%token <num> NUMBER
%%
assignment:STRING '=' NUMBER ';' {printf( "(setf %s %d)", $1, $3 );}
;
A.3 M4依赖
宏处理器M4必须安装在flex安装的地方。flex调用’m4’,通过搜索PATH环境变量中的目录找到。你在第一节或动作中放置的任何代码都将通过m4发送。请遵循这些规则来保护你的代码免受不必要的m4处理。
1、不要使用以m4开头的符号,如’m4 define’或’m4 include’,因为这些是为m4宏名称保留的。如果出于某种原因你需要m4作为前缀,使用预处理器#define使你的符号经过m4后不被打乱。
2、不要在代码的任何地方使用字符串’[[’或’]]’。前者在C语言中无效,除非在注释和字符串中有效,但后者在x[y[z]]等代码中有效。解决办法很简单。要获取字面值字符串’]]’使用”]””]”。为了获取数组符号x[y[z]],请使用x[y[z] ]。flex将尝试检测用户代码中的这些序列,并对它们进行转义。但是,最好通过从代码中删除这些序列来尽可能避免这种复杂性。
m4只需要在你运行flex的时候。生成的扫描器是普通的C或C++,不需要m4。
A.4 通用模式
本附录提供了可能在扫描器中使用的常用正则表达式的示例。
A.4.1 数字
C99十进制常量
([[:digit:]]{-}[0])[[:digit:]]*
C99十六进制常量
0[xX][[:xdigit:]]+
C99八进制常量
0[01234567]*
C99浮点常量
{dseq} ([[:digit:]]+)
{dseq_opt} ([[:digit:]])
{frac} (({dseq_opt}"."{dseq})|{dseq}".")
{exp} ([eE][+-]?{dseq})
{exp_opt} ({exp}?)
{fsuff} [flFL]
{fsuff_opt} ({fsuff}?)
{hpref} (0[xX])
{hdseq} ([[:xdigit:]]+)
{hdseq_opt} ([[:xdigit:]])
{hfrac} (({hdseq_opt}"."{hdseq})|({hdseq}"."))
{bexp} ([pP][+-]?{dseq})
{dfc} (({frac}{exp_opt}{fsuff_opt})|({dseq}{exp}{fsuff_opt}))
{hfc} (({hpref}{hfrac}{bexp}{fsuff_opt})|({hpref}{hdseq}{bexp}{fsuff_opt}))
{c99_floating_point_constant} ({dfc}|{hfc})
请参考C99第6.4.4.2节了解详细的细节。
A.4.2 标识符
C99标识符
ucn ((\u([[:xdigit:]]{4}))|(\U([[:xdigit:]]{8})))
nondigit [[:alpha:]]
c99_id ([[:alpha:]]|{ucn})([_[:alnum:]]|{ucn})*
从技术上讲,上述模式并不包含所有可能的C99标识符,因为C99允许”实现自定义”的字符。实际上,C编译器遵循上述模式,并添加了’$’字符。
UTF-8编码的Unicode编码
[\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|\xE1-\xEC\xEE\xEF|\xED[\x80-\x9F][\x80-\xBF]|\xF0\x90-\xBF|\xF1-\xF3|\xF4\x80-\x8F
A.4.3 引用结构
C99字符串文字
L?"([^"\\n]|(\['"?\abfnrtv])|(\([0123456]{1,3}))|(\x[[:xdigit:]]+)|(\u([[:xdigit:]]{4}))|(\U([[:xdigit:]]{8})))"
C99注释
("/"([^]|""[^/])"/")|("/"(\\n)"/"[^\n])
请注意在C99中,’//’样式的注释可能被分割成几行,并且与流行的看法相反,它不包括末尾的’\n’字符。
扫描’/* */’注释的更好方法是逐行扫描,而不是一次性匹配可能很大的注释。这将允许你扫描无限长度的注释,只要以相同的间隔出现换行。当与自动行号处理一起使用时,这也更有效。查看option-yylineno章节。
"/" BEGIN(COMMENT);
}
"
[^\n]+ ;
""[^/] ;
\n ;
}
A.4.4 地址
IPv4地址
dec-octet [0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]
IPv4address {dec-octet}.{dec-octet}.{dec-octet}.{dec-octet}
IPv6地址
h16 [0-9A-Fa-f]{1,4}
ls32 {h16}:{h16}|{IPv4address}
IPv6address ({h16}: ){6}{ls32}|
:: ({h16}: ){5}{ls32}|
({h16})?:: ({h16}: ){4}{ls32}|
(({h16}: ){0,1}{h16})?:: ({h16}: ){3}{ls32}|
(({h16}: ){0,2}{h16})?:: ({h16}: ){2}{ls32}|
(({h16}: ){0,3}{h16})?::{h16}:{ls32}|
(({h16}: ){0,4}{h16})?::{ls32}|
(({h16}: ){0,5}{h16})?::{h16}|
(({h16}: ){0,6}{h16})?::
参考RFC 2373了解更详细的信息。请注意,你必须将IPv6地址的定义折叠成一行,并且它还匹配”未指定地址””::”。
URI
(([^:/?#]+): )?("//"([^/?#]))?([^?#])(?([^#]))?(#(.))?
这个模式几乎是无用的,因为它允许在URI中出现任何字符,包括空格和控制字符。参考RFC 2396了解更详细的信息。