flex/bison:如何在同一输入文件上切换两个lexer

flex/bison:如何在同一输入文件上切换两个lexer,bison,flex-lexer,yacc,lex,Bison,Flex Lexer,Yacc,Lex,如何将打开的文件(例如,由另一个扫描仪读取的文件)移交给下一个扫描仪,并将其交给解析器?Flex缓冲区无法轻松地从一个扫描仪转移到另一个扫描仪。许多细节是扫描仪专有的,需要进行逆向工程,从而失去可维护性 但是,如果语义类型兼容,将两个(或多个)扫描程序定义合并到一个扫描程序中并不困难。只需给它们不同的启动条件。由于启动条件甚至可以在扫描程序操作之外设置,因此从一个扫描程序定义切换到另一个扫描程序定义很简单 由于Flex扫描器是基于表格的,所以将两个扫描器结合起来并没有真正的低效;事实上,不复制代

如何将打开的文件(例如,由另一个扫描仪读取的文件)移交给下一个扫描仪,并将其交给解析器?

Flex缓冲区无法轻松地从一个扫描仪转移到另一个扫描仪。许多细节是扫描仪专有的,需要进行逆向工程,从而失去可维护性

但是,如果语义类型兼容,将两个(或多个)扫描程序定义合并到一个扫描程序中并不困难。只需给它们不同的启动条件。由于启动条件甚至可以在扫描程序操作之外设置,因此从一个扫描程序定义切换到另一个扫描程序定义很简单

由于Flex扫描器是基于表格的,所以将两个扫描器结合起来并没有真正的低效;事实上,不复制代码可能有一些价值。组合表可能略大于单个表的总和,因为可能有更多的字符等价类,但另一方面,较大的表可能允许更好的表压缩。这两种影响都不太可能明显


下面是一个简单但可能有用的示例。此解析器读取一个文件,并用求值表达式替换
${算术表达式}
。(因为这只是一个示例,只允许使用非常基本的表达式,但应该易于扩展。)

由于词法扫描程序需要在启动条件下启动,因此需要对其进行初始化。就我个人而言,我更喜欢在
INITIAL
中开始,以避免在这种简单的情况下进行初始化,但有时扫描仪需要能够处理各种启动条件,因此我将代码保留在。可以改进错误处理,但它是功能性的

解析器使用一个非常简单的
error
规则来重新同步并跟踪替换错误。非终端
subst
file
start
的语义值是该文件的错误计数;
expr
的语义值是表达式的值。在这个简单的例子中,它们都只是整数,因此
yylval
的默认类型有效

未终止的替换未被优雅地处理;特别是,如果在替换的词法扫描期间读取EOF,则不会在输出中插入任何指示。我把修好它当作一种练习。:)

这是lexer:

%{
#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);
}
如果一切顺利,则返回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();
}
%{
#包括
int yylex(无效);
无效错误(常量字符*消息);
void初始化u扫描器(void);
外部内部yylineno;
外部文件*yyin,*yyout;
%}
%令牌号FIN UNOP
%左'+''-'
%左'*''/''%
%非联索行动
%定义parse.lac full
%详细定义parse.error
%%
开始:文件{if($1)YYABORT;else yyaccpt;}
文件:{$$=0;}
|文件子文件{$$=$1+$2;}
subst:expr FIN{fprintf(yyout,“%d”,$1);$$=0;}
|错误FIN{fputs(“${BAD SUBSTITUTION}”,yyout);$$=1;}
表达式:数字
|“-”表达式%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;}
%%
无效错误(常量字符*消息){
fprintf(标准,“%d:%s\n”,yylineno,msg);
}
int parseFile(文件*in,文件*out){
初始化_扫描器();
yyin=in;
yyout=out;
返回yyparse();
}
还有一个简单的驱动程序:

#include <stdio.h>
int parseFile(FILE* in, FILE* out);
int main() {
  return parseFile(stdin, stdout);
}
#包括
int parseFile(文件*in,文件*out);
int main(){
返回解析文件(stdin、stdout);
}

Flex缓冲区无法轻松地从一台扫描仪转移到另一台扫描仪。许多细节是扫描仪专有的,需要进行逆向工程,从而失去可维护性

但是,如果语义类型兼容,将两个(或多个)扫描程序定义合并到一个扫描程序中并不困难。只需给它们不同的启动条件。由于启动条件甚至可以在扫描程序操作之外设置,因此从一个扫描程序定义切换到另一个扫描程序定义很简单

由于Flex扫描器是基于表格的,所以将两个扫描器结合起来并没有真正的低效;事实上,不复制代码可能有一些价值。组合表可能略大于单个表的总和,因为可能有更多的字符等价类,但另一方面,较大的表可能允许更好的表压缩。这两种影响都不太可能明显


下面是一个简单但可能有用的示例。此解析器读取一个文件,并用求值表达式替换
${算术表达式}
。(因为这只是一个示例,只允许使用非常基本的表达式,但应该易于扩展。)

由于词法扫描程序需要在启动条件下启动,因此需要对其进行初始化。就我个人而言,我更喜欢在
INITIAL
中开始,以避免在这种简单的情况下进行初始化,但有时扫描仪需要能够处理各种启动条件,因此我将代码保留在。可以改进错误处理,但它是功能性的

解析器使用一个非常简单的
error
规则来重新同步并跟踪替换错误。非终端
subst
file
start
的语义值是该文件的错误计数;
expr
的语义值是表达式的值。在这个简单的例子中,它们都只是整数,因此
yylval
的默认类型有效

未终止的替换未被优雅地处理;特别是,如果在t期间读取EOF
#include <stdio.h>
int parseFile(FILE* in, FILE* out);
int main() {
  return parseFile(stdin, stdout);
}