2017-06-20 69 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