在 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"
时,解析器将 yyin
从 stdin
交换到文件指针(使用函数 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 两次)。
在这种情况下,我会建议生成一个可重入的解析器和扫描器并简单地进行递归调用。这是一个简洁明了的解决方案,避免使用全局变量总是好的。
一个简单的例子。下面的简单语言只有 echo
和 eval
语句,这两个语句都需要带引号的字符串参数。
有多种方法可以将可重入扫描器和可重入解析器连接在一起。它们都有一些怪癖,文档(尽管绝对值得一读)也有一些漏洞。这是我发现有用的解决方案。请注意,大多数外部可见函数都在扫描器文件中定义,因为它们依赖于该文件中定义的接口来操作可重入扫描器上下文 object。您可以使用 flex 导出具有适当定义的 header,但我通常发现编写自己的包装函数并导出它们更简单。 (我通常也不导出 yyscan_t
;通常我会创建自己的上下文 object,其中有一个 yyscan_t
成员。)
有一个恼人的循环,这主要是由于 bison 不允许在 yyparse
的顶部引入用户代码的可能性。因此,有必要将用于调用词法分析器的 yyscan_t
作为参数传递给 yyparse
,这意味着有必要在 bison 文件中声明 yyscan_t
。 yyscan_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;
}
}
%%
我在 Bison 中有以下代码,它扩展了指南中提出的 mfcalc
,使用 FLEX 在外部实现了一些功能,例如 yylex()
。
为了理解我的问题,关键规则位于语法开头称为 line
的非终结符标记中。具体来说,规则 EVAL CLOSED_STRING '\n'
和 END
(当检测到 EOF 时,此标记由 FLEX 发送。第一个打开文件并将输入指向该文件。第二个关闭文件并将输入指向stdin
输入。
我正在尝试制定规则 eval "file_path"
以从文件加载标记并对其进行评估。最初我有 yyin = stdin
(我使用函数 setStandardInput()
来执行此操作)。
当用户引入 eval "file_path"
时,解析器将 yyin
从 stdin
交换到文件指针(使用函数 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 两次)。
在这种情况下,我会建议生成一个可重入的解析器和扫描器并简单地进行递归调用。这是一个简洁明了的解决方案,避免使用全局变量总是好的。
一个简单的例子。下面的简单语言只有 echo
和 eval
语句,这两个语句都需要带引号的字符串参数。
有多种方法可以将可重入扫描器和可重入解析器连接在一起。它们都有一些怪癖,文档(尽管绝对值得一读)也有一些漏洞。这是我发现有用的解决方案。请注意,大多数外部可见函数都在扫描器文件中定义,因为它们依赖于该文件中定义的接口来操作可重入扫描器上下文 object。您可以使用 flex 导出具有适当定义的 header,但我通常发现编写自己的包装函数并导出它们更简单。 (我通常也不导出 yyscan_t
;通常我会创建自己的上下文 object,其中有一个 yyscan_t
成员。)
有一个恼人的循环,这主要是由于 bison 不允许在 yyparse
的顶部引入用户代码的可能性。因此,有必要将用于调用词法分析器的 yyscan_t
作为参数传递给 yyparse
,这意味着有必要在 bison 文件中声明 yyscan_t
。 yyscan_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;
}
}
%%