我在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", $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;
}
答案 0 :(得分:2)
创建可以递归调用的解析器和扫描器并不困难。 (参见下面的示例。)但是默认的bison生成的解析器和flex生成的扫描器都不是可重入的。因此,使用默认的解析器/扫描程序,您不应该在yyparse()
内调用SetStandardInput
,因为该函数本身由yyparse
调用。
另一方面,如果你有一个递归的解析器和扫描器,你可以大大简化你的逻辑。你可以摆脱END
令牌(在任何情况下,实际上从来都不是一个好主意),只是在yyparse
的行动中递归调用EVAL CLOSED_STRING '\n'
。
如果你想使用默认的解析器和扫描器,那么你最好的解决方案是使用Flex的缓冲堆栈来推送并随后弹出一个&#34;缓冲区&#34;对应于要评估的文件。 (单词&#34;缓冲区&#34;我认为这有点令人困惑。一个Flex&#34;缓冲区&#34;实际上是一个输入源,例如一个文件;它被称为缓冲区,因为只有一部分是在内存中,但Flex将读取整个输入源作为处理&#34;缓冲区的一部分。)
您可以在flex manual中了解缓冲区堆栈的使用情况,其中包括示例代码。请注意,在示例代码中,文件结束条件完全在扫描程序内部处理,这通常用于此体系结构。
在这种情况下,可以制作文件结束指示符(尽管您不能使用END
,因为它用于指示所有输入的结束)。这样做的好处是可以确保将评估文件的内容作为一个整体进行解析,而不会将部分解析泄漏回包含文件,但是您仍然希望在扫描程序中弹出缓冲区堆栈,因为它很难获得结束文件处理正确而不违反任何API约束(其中一个是你无法在相同的&#34;缓冲区&#34;上无法可靠地读取EOF两次。)
在这种情况下,我建议生成一个可重入的解析器和扫描器,然后简单地进行递归调用。它是一个简洁明了的解决方案,避免全局变量总是好的。
一个简单的例子。下面的简单语言只有echo
和eval
语句,这两个语句都需要带引号的字符串参数。
有多种方法可以将重入扫描程序和重入解析器连接在一起。所有这些都有一些怪癖和文档(虽然绝对值得一读)有一些漏洞。这是我发现有用的解决方案。请注意,大多数外部可见功能都在扫描程序文件中定义,因为它们依赖于该文件中定义的接口来操作可重入扫描程序上下文对象。您可以使用flex来导出带有approriate定义的标头,但我通常发现编写自己的包装函数并导出它们更简单。 (我通常也不会导出yyscan_t
;通常我会创建一个我自己的上下文对象,其中有一个yyscan_t
成员。)
有一种烦人的循环,这很大程度上是野牛的结果,不允许在yyparse
的顶部引入用户代码。因此,有必要将用于调用词法分析器的yyscan_t
作为参数传递给yyparse
,这意味着必须在bison文件中声明yyscan_t
。 yyscan_t
实际上是在扫描程序生成的文件中声明的(或者如果您要求的话,也可以在flex生成的标题中声明),但是您不能在生成的bison中包含flex生成的标题header,因为flex生成的标头需要{b}生成的标头中声明的YYSTYPE
。
我通常使用推送解析器来避免这种循环,但是这推动了这个问题的界限,所以我只是采用了通常的解决方法,即插入
typedef void* yyscan_t;
在野牛文件中。 (这是yyscan_t
的实际定义,其实际内容应该是不透明的。)
我希望这个例子的其余部分是不言而喻的,但如果有任何你不理解的事情,请随时要求澄清。
%{
#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);
}
%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;
}
}
%%