我想用Flex& Bison实现表达式验证工具。在我的工具中,我接受以';'结尾的表达式并检查表达式中是否有错误。当发生错误时,我想获得错误令牌的正确位置。问题是,当发生多个错误时,我总是出错。
解析器:
%{
# include <stdio.h>
# include <stdlib.h>
# include "roofexp.h"
# include "symbol.h"
%}
%locations
%union {
struct ast *a;
double d;
struct symbol *s; /* which symbol */
struct symlist *sl;
int fn; /* which function */
char *str;
}
/* edeclare tokens */
%token <d> NUMBER
%token <str> STRING
%token <s> NAME
%token <fn> FUNC
%token EOL
%token IF THEN ELSE WHILE DO LET
%nonassoc <fn> CMP
%right '='
%left '+' '-'
%left '*' '/'
%nonassoc '|' UMINUS
%type <a> exp stmt list explist
%start calclist
%%
calclist: /* nothing */
| calclist stmt ';' {
if(debug)
dumpast($2, 0);
printf("= %4.4g\n> ", eval($2));
treefree($2);
free_string_table();
FreeSymbolTable();
}
| calclist error EOL { YYERROR; }
;
stmt: IF exp THEN list { $$ = newflow('I', $2, $4, NULL); }
| IF exp THEN list ELSE list { $$ = newflow('I', $2, $4, $6); }
| exp
;
list: /* nothing */ { $$ = NULL; }
| stmt ';' list { if ($3 == NULL)
$$ = $1;
else
$$ = newast('L', $1, $3);
}
;
exp: exp CMP exp { $$ = newcmp($2, $1, $3); }
| exp '+' exp { $$ = newast('+', $1,$3); }
| exp '-' exp { $$ = newast('-', $1,$3);}
| exp '*' exp { $$ = newast('*', $1,$3); }
| exp '/' exp {
$$ = newast('/', $1, $3);
}
| '|' exp { $$ = newast('|', $2, NULL); }
| '(' exp ')' { $$ = $2; }
| '-' exp %prec UMINUS { $$ = newast('M', $2, NULL); }
| NUMBER { $$ = newnum($1); }
| STRING { $$ = newstr($1); add_string($1); }
| FUNC '(' explist ')' { $$ = newfunc($1, $3); }
| NAME { $$ = newref($1); }
| NAME '=' exp { $$ = newasgn($1, $3); }
| NAME '(' explist ')' { $$ = newcall($1, $3); }
;
explist: exp
| exp ',' explist { $$ = newast('L', $1, $3); }
;
词法分析:
%%
%{
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
# include "roofexp.h"
# include "roofexp.tab.h"
# include "symbol.h"
/* handle locations */
int yycolumn = 1;
#define YY_USER_ACTION \
yylloc.first_line = yylloc.last_line = yylineno; \
yylloc.first_column = yycolumn; \
yylloc.last_column = yycolumn + yyleng - 1; \
yycolumn += yyleng;
%}
%option yylineno noyywrap
/* float exponent */
EXP ([Ee][-+]?[0-9]+)
%%
/* single character ops */
"#" |
"+" |
"-" |
"*" |
"/" |
"=" |
"|" |
"," |
";" |
"(" |
")" { return yytext[0]; }
/* comparison ops */
">" { yylval.fn = 1; return CMP; }
"<" { yylval.fn = 2; return CMP; }
"<>" { yylval.fn = 3; return CMP; }
"==" { yylval.fn = 4; return CMP; }
">=" { yylval.fn = 5; return CMP; }
"<=" { yylval.fn = 6; return CMP; }
/* keywords */
"if" { return IF; }
"then" { return THEN; }
"else" { return ELSE; }
"while" { return WHILE; }
"do" { return DO; }
"let" { return LET;}
/* built in functions */
"sin" { yylval.fn = FUNC_sin; return FUNC; }
"cos" { yylval.fn = FUNC_cos; return FUNC; }
"pow" { yylval.fn = FUNC_pow; return FUNC; }
"GetDz" { yylval.fn = FUNC_GetDz; return FUNC;}
/* debug hack */
"debug"[0-9]+ { debug = atoi(&yytext[5]); printf("debug set to %d\n", debug); }
/* names */
[_a-zA-Z][_a-zA-Z0-9]* {
if(LookupSymbolTable(yytext, 0, VARIABLE) == NULL)
yyerror("未定义的变量: %s", yytext);
else
yylval.s = lookup(yytext); return NAME;
}
[0-9]+"."[0-9]*{EXP}? |
"."?[0-9]+{EXP}? { yylval.d = atof(yytext); return NUMBER; }
\"[^\"\n]*\" { printf("string=%s\n", yytext); }
\"[^\"\n]*$ { yyerror("unterminated string literal: %s\n", yytext); }
"//".*
[ \t]
\n { yycolumn = 1; }
. { yyerror("Mystery character %c\n", *yytext); }
%%
表达:
pow(2)+
pow(2, 4)
;
回声:
3-1: error: at ';': too few arguments for call
但正确的位置应该是1-1! 我的词法分析器和解析器有什么问题。如果我想获得正确的位置,我会怎么做?
答案 0 :(得分:1)
如果您显示生成错误消息的代码会有所帮助,但我的猜测是您的yyerror
函数只使用当前值yyloc
,这将对应于最后读取的标记。因此,如果在表达式结尾处的分号之前未诊断出错误(如“参数太少”),则yyloc
将具有分号的位置,如示例所示。
当您为野牛指定%locations
时,野牛会为每个非终端以及非终端保留一个源范围。 (默认情况下,范围从生产中的第一个组件的开头到最后一个组件的末尾。)您可以使用@N
从bison操作访问位置结构(对于组件N
)或@$
(对于整个减少范围)。
但是,在@$
操作中使用calclist stmt ';'
(假设在调用eval
期间产生错误)将不会给您更多精确度。此时您能够做的最好的事情是将错误报告为源代码范围1:1-3:1
中的某个位置。为了生成更准确的消息,您需要在AST中的每个节点中包含该位置。然后eval
只需要知道哪个AST节点导致错误。
当然,在解析函数调用时可能会产生too few arguments
之类的错误,假设您知道每个函数在该点需要多少个参数。但这更难以维持而且不那么通用。
虽然野牛做了很多维护位置所需的工作,但你必须自己保持位置信息与AST节点的关联。通常的做法是在每个AST节点中包含YYLTYPE
结构(您的struct ast
);您可以在创建节点的操作中将适当的位置复制到AST节点。