2017-06-20 33 views
1

我在野牛中有以下代码,它扩展了指南中提出的mfcalc,使用FLEX从外部实现了一些函数,如yylex()在标准输入和文件之间交换野牛

为了理解我的问题,关键规则是在文法开始处的非终结令牌line中。具体而言,EVAL CLOSED_STRING '\n'END(此令牌由FLEX当检测到EOF发送的规则,第一打开一个文件和点输入到该文件。第二关闭该文件,并指出该输入到stdin输入。

我“M试图使规则eval "file_path"从文件加载令牌,并对其进行评估。起初我还yyin = stdin(我用的功能setStandardInput()做到这一点)。

当用户介绍eval "file_path"解析器互换yyinstdin到文件指针(功能setFileInput()),令牌重新正确。

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

注意:我认为在语法中没有错误,因为错误恢复不完整。在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; 
} 

回答

1

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

另一方面,如果你有一个递归解析器和扫描器,你可以显着简化你的逻辑。您可以摆脱END标记(无论如何,这实际上从来都不是一个好主意),并且只需在EVAL CLOSED_STRING '\n'的行动中递归调用yyparse即可。

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

可以读取关于在flex manual缓冲器堆栈使用,其中包括示例代码。请注意,在示例代码中,文件结束条件完全是在扫描器内处理的,这对于此架构来说通常是这样。

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

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

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

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

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

%% 
+0

谢谢,这比我想象的更容易。我使用了手动函数引用,并创建了一个函数为'yypush_buffer_state(yy_create_buffer(yyin,YY_BUF_SIZE));'push和'yypop_buffer_state();'来弹出的简单堆栈。这工作,维护'stdin'在堆栈的底部,并在读取一堆文件后恢复正常。 – Marco