Flex/Bison:从标准输入和文件中传播EOF

3
我有一个扫描器、解析器和一个主程序,通过以下方式创建可执行文件:bison -d parser.y; flex scanner.l; gcc main.c parer.tab.c lex.yy.c。运行./a.out后,如果按下Ctrl+D,则会检测到EOF并使main相应地执行。这意味着:如果yyinstdin,则按下Return将结束该行的解析,主循环将等待下一行输入。按下Ctrl+D将在主循环中使用break结束输入解析并退出。如果输入来自文件,例如testFile,那么该文件可以包含1个表达式直到EOF结束。在文件情况下,新行应被视为空格和制表符。所有这些内容都应该像从stdin输入时的解释器一样运行,并且像从文件输入时的脚本评估器一样运行。这样一个测试文件的示例内容可能是:test\n。在这里,EOF未被检测到。我有困惑为什么会这样。换句话说,我想要一个问题这里的扩展版,以便能够处理输入文件 parser.y:
%{
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

/* stuff from flex that bison needs to know about: */
int yylex();
int yyparse();
FILE *yyin;

static int parseValue;

void yyerror(const char *s);
%}

%token TWORD
%token TEOF
%token TJUNK

%start input 

%%
input: word                         {   printf("W"); parseValue =  1;   }   
    | eof                           {   printf("eof"); parseValue = -11;}
    | /* empty */                   {   printf("_"); parseValue = -1;   }   
    | error                         {   printf("E"); parseValue = -2;   }   
    ;

eof: TEOF
    ;

word: TWORD
    ;
%%

void yyerror(const char *s) {
    printf("nope...");
}

int getWord( FILE *file) {
    int err;

    if (file) {
        yyin = file;
    } else /* error */ {
        printf("file not valid");
        return -3; 
    }   

    err = yyparse();
    if (!err) {
        return parseValue;
    } else /* error */ {
        printf("parse error");
        return -4;
    }
}

scanner.l:

%{
#include <stdio.h>
#include "parser.tab.h"
#define YYSTYPE int

int yylex();
%}

/* avoid: implicit declaration of function ‘fileno’ */
/*%option always-interactive*/

%option noyywrap
/* to avoid warning: ‘yyunput’ defined but not used */
%option nounput
/* to avoid warning: ‘input’ defined but not used */
%option noinput

%%
<<EOF>>                     {   return TEOF;    }
[ \t]                       {   }
[\n]                        {   if (yyin == stdin) return 0;   }
[a-zA-Z][a-zA-Z0-9]*        {   return TWORD; }
.                           {   return TJUNK;   }
%%

main.c:

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>

int main(int argc, char *argv[]) {

    int result = 0;
    FILE *fOut = stdout, *fIn = stdin;

    /* skip over program name */
    ++argv, --argc;
    if ( argc > 0 ) { 
        fIn = fopen( argv[0], "r" );
    }   

    while (true) {
        fprintf(fOut, "\nTEST : ", result);

        result = getWord(fIn);

        if (result == -11) {
            printf(" %i ", result); printf("--> EOF");
            break;
        }   
        if (result < 0) {
            printf(" %i ", result); printf("--> <0");
            /*continue;*/
            break;
        }   

        fprintf(fOut, " => %i", result);
    }   

    fprintf(fOut, "\n\n done \n ");
    exit(EXIT_SUCCESS);
}

我尝试根据这里或者这里的建议重写解析器,但是并没有太大的成功。当从文件读取输入时,main函数如何正确地意识到EOF呢?

更新: 有一种建议是问题可能是由于在\n上的return 0;引起的。作为一个快速测试,我只在yyin == stin时返回0,但是调用./a.out testFile仍然无法捕获EOF更新2: 我通过使用yywrap让它正常工作。我摆脱了所有的TEOF的东西。扫描器有一个部分:

extern int eof;

最后:

int yywrap() {
    eof = 1;
    return 1;
}

在解析器中有一个:
int eof = 0;

而在文件的下方:

err = yyparse();
if (err != 0) return -4;
else if (eof) return -11;
else return parseValue;

如果有更加优雅的解决方案,我仍然会很感激。 这个 链接可能是制作干净版本的好方法。
1个回答

3
正如您的链接中所述,flex有语法来识别输入文件或流(例如从字符串输入)的结尾。
实际上,默认情况下,flex在所有时候都有效地具有这样的规则。默认情况下,该规则调用yywrap。您关闭了此功能(使用%noyywrap)。这很好,但是...
遇到“EOF标记”的默认操作是返回0。
由bison(和byacc)生成的解析器需要看到此零标记。请参见this answerEND OF FILE token with flex and bison (only works without it)
您的词法分析器在遇到换行符时返回0标记。那会引起各种麻烦,并且无疑会导致您从文件中读取时观察到的问题。
编辑:好的,既然这些问题都解决了并且更新已应用,那么让我们考虑一下你的语法。
请记住,bison会添加一个特殊的产生式来寻找零标记。我们可以用$来表示它(通常人们这样做,有时也会用$end)。因此,你的整个语法(没有动作,并且已经删除了“error”产生式,因为它也是特殊的)如下:
$all : input $;

input: word | eof | /* empty */;

word: TWORD;

eof: TEOF;

这意味着你的语法只接受以下句子:
TWORD $

或者:

TEOF $

或者:

$

当您调用 yyparse() 时,yyparse()内部的循环将从词法分析器中预读一个标记,并在标记为零值的文件结尾标记$时接受(并返回)结果。如果不是,则该标记需要是TWORDTEOF之一(其他任何内容都会导致调用yyerror()并尝试重新同步)。如果标记是两个有效标记之一,则yyparse()将再次调用词法分析器以验证下一个标记是否为零值的文件结尾标记$
如果所有这些都成功,yyparse()将返回成功。
添加操作后,您应该会看到printf输出,并且根据用于识别(最多一个)令牌的还原规则,得到存储在parseValue中的值。

我已经删除了return 0'\n',但文件解析仍然无法正常工作;它仍在循环中。不确定如何继续。 - oops
它应该在yyparse()内循环,直到词法分析器返回0来表示输入结束。我记得已经有一段时间了,但是据我回忆,flex实际上会在返回TEOF标记(一次)后返回0。(有一些宏可以中止解析或提前接受,但您没有使用它们...) - torek
再次感谢。我通过 yywrap() 函数使其正常工作了。如果您有更好的想法,我仍然欢迎您的评论。 - oops
我希望新添加的解释能够使它更清晰易懂,如果还有疑问,请告诉我。 - oops
好的,我建议您发布一个新问题,明确定义当从非标准输入文件或标准输入读取时,您希望主循环如何工作。例如,如果您希望yyparse()在从标准输入读取每个换行符时返回已完成的解析(可能不成功),而在从文件读取时仅在达到“真正的EOF”时才返回。 - torek
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接