Flex 缓冲区无法轻松地从一台扫描仪传输到另一台扫描仪。许多细节对于扫描仪来说是私有的,需要进行逆向工程,从而导致可维护性的丧失。
然而,只要语义类型兼容,将两个(或多个)扫描器定义组合成单个扫描器并不困难。只需给它们不同的启动条件即可。由于甚至可以在扫描仪操作之外设置启动条件,因此从一个扫描仪定义切换到另一种扫描仪定义很简单。
由于 Flex 扫描仪是基于桌面的,因此将两台扫描仪结合起来并不存在真正的低效率;事实上,不重复代码可能有一定的价值。组合表可能比各个表的总和稍大,因为可能有更多的字符等价类,但另一方面,较大的表可能允许更好的表压缩。这些影响可能都不明显。
这是一个简单但可能有用的示例。该解析器读取文件并替换${arithmetic expressions}
与评估的表达式。 (由于它只是一个示例,因此只允许非常基本的表达式,但它应该很容易扩展。)
由于词法扫描器需要在启动条件下启动SC_ECHO
,需要初始化。就我个人而言,我更愿意从INITIAL
为了避免在这个简单的情况下进行初始化,但有时扫描仪需要能够处理各种启动条件,所以我保留了代码。错误处理可以改进,但它是功能性的。
解析器使用一个非常简单的error
重新同步并跟踪替换错误的规则。非终结符的语义值subst
, file
and start
是文件的错误计数;的语义值expr
是表达式的值。在这个简单的例子中,它们都是整数,所以默认类型yylval
works.
未终止的替换处理不优雅;特别是,如果在词法扫描期间读取 EOF 进行替换,则不会将任何指示插入到输出中。我把解决这个问题作为练习。 :)
这是词法分析器:
%{
#include "xsub.tab.h"
%}
%option noinput nounput noyywrap nodefault
%option yylineno
%x SC_ECHO
%%
/* In a reentrant lexer, this would go into the state object */
static int braces;
/* This start condition just echos until it finds ${... */
<SC_ECHO>{
"${" braces = 0; BEGIN(INITIAL);
[^$\n]+ ECHO;
"$" ECHO;
\n ECHO;
}
/* We need to figure out where the substitution ends, which is why we can't
* just use a standard calculator. Here we deal with terminations.
*/
"{" ++braces; return '{';
"}" { if (braces) { --braces; return '}'; }
else { BEGIN(SC_ECHO); return FIN; }
}
/* The rest is just a normal calculator */
[0-9]+ yylval = strtol(yytext, NULL, 10); return NUMBER;
[[:blank:]]+ /* Ignore white space */
\n /* Ignore newlines, too (but could also be an error) */
. return yytext[0];
%%
void initialize_scanner(void) {
BEGIN(SC_ECHO);
}
解析器导出一个接口:
int parseFile(FILE *in, *out);
如果一切顺利,则返回 0,否则返回错误替换的数量(以上述未终止替换的问题为模)。这是文件:
%{
#include <stdio.h>
int yylex(void);
void yyerror(const char* msg);
void initialize_scanner(void);
extern int yylineno;
extern FILE *yyin, *yyout;
%}
%token NUMBER FIN UNOP
%left '+' '-'
%left '*' '/' '%'
%nonassoc UNOP
%define parse.lac full
%define parse.error verbose
%%
start: file { if ($1) YYABORT; else YYACCEPT; }
file : { $$ = 0; }
| file subst { $$ = $1 + $2; }
subst: expr FIN { fprintf(yyout, "%d", $1); $$ = 0; }
| error FIN { fputs("${ BAD SUBSTITUTION }", yyout); $$ = 1; }
expr : NUMBER
| '-' expr %prec UNOP { $$ = -$2; }
| '(' expr ')' { $$ = $2; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = $1 / $3; }
| expr '%' expr { $$ = $1 % $3; }
%%
void yyerror(const char* msg) {
fprintf(stderr, "%d: %s\n", yylineno, msg);
}
int parseFile(FILE* in, FILE* out) {
initialize_scanner();
yyin = in;
yyout = out;
return yyparse();
}
还有一个简单的驱动程序:
#include <stdio.h>
int parseFile(FILE* in, FILE* out);
int main() {
return parseFile(stdin, stdout);
}