【问题标题】:Swap between stdin and file in Bison在 Bison 中的标准输入和文件之间交换
【发布时间】:2017-06-20 19:36:54
【问题描述】:

我在 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", $1);                                   }   
        |   asig    END                 {   printf("\t%f\n", $1);                                   }       
        |   LS                          {   print_table();                                          }
        |   EVAL CLOSED_STRING  '\n'    {   
                                            // Getting the file path
                                            char * filePath = deleteStringSorroundingQuotes($2);
                                            setFileInput(filePath);

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

;

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

                                            lex * result = table_search($1, FUNCTION);

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


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

                                        }

;


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

                                            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;

                                            }
                                            $$ = $3;

                                        }
;

%%


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;
}

【问题讨论】:

    标签: c bison flex-lexer yacc


    【解决方案1】:

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

    另一方面,如果您有递归解析器和扫描器,则可以显着简化您的逻辑。您可以摆脱END 令牌(无论如何,这实际上绝不是一个好主意),只需在您的操作中递归调用yyparse 以获取EVAL CLOSED_STRING '\n'

    如果您想使用默认的解析器和扫描器,那么您最好的解决方案是使用 Flex 的缓冲区堆栈来推送和稍后弹出与要评估的文件对应的“缓冲区”。 (我认为这里的“缓冲区”一词有点混乱。Flex“缓冲区”实际上是一个输入源,例如文件;之所以称为缓冲区,是因为它只有一部分在内存中,但 Flex 会读取整个输入源作为处理“缓冲区”的一部分。)

    您可以在flex manual 中阅读有关缓冲区堆栈使用情况的信息,其中包括示例代码。请注意,在示例代码中,文件结束条件完全在扫描仪内部处理,这在此架构中很常见。

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

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

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

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

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

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

    typedef void* yyscan_t;
    

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

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

    文件 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] = '\0';
                   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", $2); }
    eval: "eval" STRING  { FILE* f = fopen($2, "r");
                           if (f) {
                             parseFILE(f);
                             close(f);
                           }
                           else {
                             fprintf(stderr, "Could not open file '%s'\n",
                                             $2);
                             YYABORT;
                           }
                         }
    
    %%
    

    【讨论】:

    • 谢谢,这比我预期的要容易。我使用了手动函数参考,并创建了一个简单的堆栈,其中包含函数 yypush_buffer_state(yy_create_buffer( yyin, YY_BUF_SIZE )); 推送和 yypop_buffer_state(); 弹出。这行得通,在堆栈底部维护stdin,并在读取一堆文件后恢复正常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-05
    • 2012-10-15
    • 1970-01-01
    • 1970-01-01
    • 2013-05-25
    • 2014-12-28
    • 1970-01-01
    相关资源
    最近更新 更多