在 Bison 中的标准输入和文件之间交换

Swap between stdin and file in Bison

我在 Bison 中有以下代码,它扩展了指南中提出的 mfcalc,使用 FLEX 在外部实现了一些功能,例如 yylex()

为了理解我的问题,关键规则位于语法开头称为 line 的非终结符标记中。具体来说,规则 EVAL CLOSED_STRING '\n'END(当检测到 EOF 时,此标记由 FLEX 发送。第一个打开文件并将输入指向该文件。第二个关闭文件并将输入指向stdin输入。

我正在尝试制定规则 eval "file_path" 以从文件加载标记并对其进行评估。最初我有 yyin = stdin(我使用函数 setStandardInput() 来执行此操作)。

当用户引入 eval "file_path" 时,解析器将 yyinstdin 交换到文件指针(使用函数 setFileInput())并且标记被正确读取。

当解析器达到 END 规则时,它会尝试恢复 stdin 输入,但它会被窃听。这个错误意味着计算器没有结束,但我在输入中写的内容没有被评估。

注意:我以为语法没有错误,因为错误恢复还不完整。在file_path中可以使用简单的算术运算。

总而言之,我想在 stdin 和文件指针之间交换作为输入,但是当我交换到 stdin 时它被窃听了,除了我用 stdin 启动计算器默认。

%{


/* Library includes */

#include <stdio.h>
#include <math.h>
#include "utils/fileutils.h"
#include "lex.yy.h"
#include "utils/errors.h"
#include "utils/stringutils.h"
#include "table.h"


void setStandardInput();
void setFileInput(char * filePath);


/* External functions and variables from flex */

extern size_t yyleng;
extern FILE * yyin;
extern int parsing_line;
extern char * yytext;
//extern int yyerror(char *s);
extern int yyparse();
extern int yylex();
int yyerror(char * s);

%}





/***** TOKEN DEFINITION *****/

%union{
    char * text;
    double value;
}

%type <value> exp asig



%token LS
%token EVAL
%token <text> ID
%token <text> VAR
%token <value> FUNCTION
%token <value> LEXEME
%token <value> RESERVED_WORD
%token <value> NUMBER
%token <value> INTEGER
%token <value> FLOAT
%token <value> BINARY
%token <value> SCIENTIFIC_NOTATION
%token <text> CLOSED_STRING
%token DOCUMENTATION
%token COMMENT
%token POW
%token UNRECOGNIZED_CHAR 
%token MALFORMED_STRING_ERROR 
%token STRING_NOT_CLOSED_ERROR
%token COMMENT_ERROR 
%token DOCUMENTATION_ERROR
%token END
%right '='
%left '+' '-'
%left '/' '*'
%left NEG_MINUS
%right '^'
%right '('
%%




input:      /* empty_expression */      |   
            input line
;

line:       '\n'                        

        |   asig    '\n'                {   printf("\t%f\n", );                                   }   
        |   asig    END                 {   printf("\t%f\n", );                                   }       
        |   LS                          {   print_table();                                          }
        |   EVAL CLOSED_STRING  '\n'    {   
                                            // Getting the file path
                                            char * filePath = deleteStringSorroundingQuotes();
                                            setFileInput(filePath);

        | END                           { closeFile(yyin);  setStandardInput();}

;

exp:        NUMBER                      {   $$ = ;                                                }
        |   VAR                         {   
                                            lex * result = table_search(, LEXEME);
                                            if(result != NULL) $$ = result->value;
                                        }
        |   VAR '(' exp ')'             {   

                                            lex * result = table_search(, FUNCTION);

                                            // If the result is a function, then invokes it
                                            if(result != NULL)  $$ = (*(result->function))();
                                            else yyerror("That identifier is not a function.");


                                        }
        |   exp '+' exp                 {   $$ =  + ;                                           }
        |   exp '-' exp                 {   $$ =  - ;                                           }
        |   exp '*' exp                 {   $$ =  * ;                                           }
        |   exp '/' exp                 {   
                                            if( != 0){ $$ =  / ;};    
                                            yyerror("You can't divide a number by zero");   
                                        }
        |   '-' exp %prec NEG_MINUS     {   $$ = -;                                               }
        |   exp '^' exp                 {   $$ = pow(, );                                       }
        |   '(' exp ')'                 {   $$ = ;                                                }
        |   '(' error ')'               {   
                                            yyerror("An error has ocurred between the parenthesis.");   yyerrok; yyclearin;     

                                        }

;


asig:       exp                         { $$ = ;                                                  }   
        |   VAR '=' asig                {   
                                            int type = insertLexeme(, );

                                            if(type == RESERVED_WORD){ 
                                                yyerror("You tried to assign a value to a reserved word.");
                                                YYERROR;

                                            }else if(type == FUNCTION){
                                                yyerror("You tried to assign a value to a function.");
                                                YYERROR;

                                            }
                                            $$ = ;

                                        }
;

%%


void setStandardInput(){

    printf("Starting standard input:\n");
    yyin = NULL;

    yyin = stdin;
    yyparse();

}

void setFileInput(char * filePath){
    FILE * inputFile = openFile(filePath);

    if(inputFile == NULL){
        printf("The file couldn't be loaded. Redirecting to standard input: \n");
        setStandardInput();
    }else{
        yyin = inputFile;
    }
}



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


    create_table();         // Table instantiation and initzialization

    initTable();            // Symbol table initzialization

    setStandardInput();     // yyin = stdin

    while(yyparse()!=1);

    print_table();


    // Table memory liberation
    destroyTable();


    return 0;
}


int yyerror(char * s){
    printf("---------- Error in line %d --> %s ----------------\n", parsing_line, s);
    return 0;
}

创建一个可以递归调用的解析器和扫描器并不难。 (参见下面的示例。)但是默认的 bison-generated 解析器和 flex-generated 扫描器都不是设计为可重入的。因此,使用默认 parser/scanner,您不应在 SetStandardInput 中调用 yyparse(),因为该函数本身由 yyparse 调用。

另一方面,如果您有递归解析器和扫描器,则可以显着简化您的逻辑。您可以去掉 END 标记(在任何情况下,这实际上都不是一个好主意),只需在 EVAL CLOSED_STRING '\n'.

的操作中递归调用 yyparse

如果您想使用默认的解析器和扫描器,那么您最好的解决方案是使用 Flex 的缓冲区堆栈来推送并稍后弹出一个 "buffer" 对应于要评估的文件。 (我觉得这里的"buffer"这个词有点让人迷惑,一个Flex"buffer"其实就是一个输入源,比如一个文件;之所以叫缓冲区,是因为只有一部分在内存中,但是作为处理 "buffer" 的一部分,Flex 将读取整个输入源。)

您可以在 flex manual 中阅读有关缓冲区堆栈用法的信息,其中包括示例代码。请注意,在示例代码中,文件结束条件完全在扫描器内部处理,这对于此体系结构来说很常见。

在这种情况下可以制造一个 end-of-file 指示器(尽管您不能使用 END,因为它用于指示所有输入的结束)。这样做的好处是确保评估文件的内容作为一个整体进行解析,而不会将部分解析泄漏回包含文件,但您仍然希望在扫描器中弹出缓冲区堆栈,因为获取 end-of-file 在不违反任何 API 约束的情况下正确处理(其中之一是您无法在同一个 "buffer" 上可靠地读取 EOF 两次)。

在这种情况下,我会建议生成一个可重入的解析器和扫描器并简单地进行递归调用。这是一个简洁明了的解决方案,避免使用全局变量总是好的。

一个简单的例子。下面的简单语言只有 echoeval 语句,这两个语句都需要带引号的字符串参数。

有多种方法可以将可重入扫描器和可重入解析器连接在一起。它们都有一些怪癖,文档(尽管绝对值得一读)也有一些漏洞。这是我发现有用的解决方案。请注意,大多数外部可见函数都在扫描器文件中定义,因为它们依赖于该文件中定义的接口来操作可重入扫描器上下文 object。您可以使用 flex 导出具有适当定义的 header,但我通常发现编写自己的包装函数并导出它们更简单。 (我通常也不导出 yyscan_t;通常我会创建自己的上下文 object,其中有一个 yyscan_t 成员。)

有一个恼人的循环,这主要是由于 bison 不允许在 yyparse 的顶部引入用户代码的可能性。因此,有必要将用于调用词法分析器的 yyscan_t 作为参数传递给 yyparse,这意味着有必要在 bison 文件中声明 yyscan_tyyscan_t 实际上在扫描仪生成的文件中声明(或 flex-generated header,如果您要求的话),但您不能包含 flex-generated [= bison-generated header 中的 80=] 因为 flex-generated header 需要 YYSTYPE,它在 bison-generated header 中声明。

我通常通过使用推送解析器来避免这种循环,但那是在突破这个问题的界限,所以我只是求助于通常的 work-around,即插入

typedef void* yyscan_t;

在野牛文件中。 (这是 yyscan_t 的实际定义,其实际内容应该是不透明的。)

我希望示例的其余部分是self-evident,但如果您有任何不明白的地方,请随时要求澄清。

文件recur.l

%{
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include "recur.tab.h"
%}

%option reentrant bison-bridge 
%option noinput nounput nodefault noyywrap
%option yylineno

%%
"echo"       { return T_ECHO; }
"eval"       { return T_EVAL; }
[[:alpha:]][[:alnum:]]*  {
               yylval->text = strdup(yytext);
               return ID;
             }
["]          { yyerror(yyscanner, "Unterminated string constant"); }
["][^"\n]*["] {
               yylval->text = malloc(yyleng - 1);
               memcpy(yylval->text, yytext + 1, yyleng - 2);
               yylval->text[yyleng - 2] = '[=11=]';
               return STRING;
             }
"."          { return yytext[0]; }
[[:digit:]]*("."[[:digit:]]*)? {
               yylval->number = strtod(yytext, NULL);
               return NUMBER;
             }
[ \t]+       ;
.|\n         { return yytext[0]; }

%%
/* Use "-" or NULL to parse stdin */
int parseFile(const char* path) {
  FILE* in = stdin;
  if (path && strcmp(path, "-") != 0) {
    in = fopen(path, "r");
    if (!in) {
      fprintf(stderr, "Could not open file '%s'\n", path);
      return 1;
    }
  }
  yyscan_t scanner;
  yylex_init (&scanner);
  yyset_in(in, scanner);
  int rv = yyparse(scanner);
  yylex_destroy(scanner);
  if (in != stdin) fclose(in);
  return rv;
}
void yyerror(yyscan_t yyscanner, const char* msg) {
  fprintf(stderr, "At line %d: %s\n", yyget_lineno(yyscanner), msg);
}

文件recur.y

%code {
  #include <stdio.h>
}
%define api.pure full
%param { scanner_t context }
%union {
  char*  text;
  double number;
}
%code requires {
  int parseFILE(FILE* in);
}
%token ECHO "echo" EVAL "eval"
%token STRING ID NUMBER
%%
program: %empty | program command '\n'
command: echo | eval | %empty
echo: "echo" STRING  { printf("%s\n", ); }
eval: "eval" STRING  { FILE* f = fopen(, "r");
                       if (f) {
                         parseFILE(f);
                         close(f);
                       }
                       else {
                         fprintf(stderr, "Could not open file '%s'\n",
                                         );
                         YYABORT;
                       }
                     }

%%