【问题标题】:How do I get my flex/bison grammar parser to give a syntax error for unrecognized tokens如何让我的 flex/bison 语法解析器为无法识别的标记提供语法错误
【发布时间】:2019-09-29 17:40:03
【问题描述】:

我正在尝试使用 flex 和 bison 编写一个语法识别器来确定输入字符串是否在 L(G) 中,其中语言是以下各项的并集:

L(G) = {a^i b^j c^k d^l e^m} 其中 i,j,k,l,m > 0 且 i=m 且 k=l

和

L(G) = {e^i d^j c^k b^l a^m} 其中 i,j,k,l,m > 0 and i=2m k=3l and j=2

现在我可以正常工作,但仅在使用语言中的标记时。如果我包含任何其他令牌,它似乎会被忽略,并且测试会根据其他允许的令牌通过或失败。这是有问题的,因为它允许诸如“abcdef”之类的字符串通过解析,即使“f”不在该语言中。

我现在测试的错误输入是“abcdef”。 “abcde”部分是正确的并给出了正确的输出,但在末尾添加“f”会导致来自 yyerror("syntax error") 的语法错误消息和从 main 到的“congratulations; parse succeeded”打印语句打印。

使用“fabcde”与我上面描述的一样。它给了我错误,但它也给了我成功的打印声明。我正在使用“if(yyparse() == 0))”在 main 中打印成功语句,我认为这可能是罪魁祸首,尽管当我将 print 语句移动到 . y 文件,只在 main 中使用了 yyparse() 和 return(1)。

这是我的 .in 文件(减去包括):

%%

a return A;

b return B;

c return C;

d return D;

e return E;

. yyerror("syntax error\n\nSorry, Charlie, input string not in L(G)\n"); /* working but still prints success message too */

%%

这是我的 .y 文件(减去包括):

%token A

%token B

%token C

%token D

%token E


%% /* Grammar Rules */

string: as bs cs ds es
{
if(($1 == $5) && ($3 == $4)) {
return(0);
}
else
{
return(-1);
}
}
;

string: es ds cs bs as
{
if(($1 == (2 * $5) && ($3 == (3 * $4)) && ($2 = 2)) {
return(0);
}
else
{
return(-1);
}
}
;


as: A as {$$ = $2 +1;}
;

as: A {$$ = 1;}
;

bs: B bs {$$ = $2 +1;}
;

bs: B {$$ = 1;}
;

cs: C cs {$$ = $2 +1;}
;

cs: C {$$ = 1;}
;

ds: D ds {$$ = $2 +1;}
;

ds: D {$$ = 1;}
;

es: E es {$$ = $2 +1;}
;

es: E {$$ = 1;}
;

%%

我的 .c 文件很简单,如果 yyparse() == 0 则返回“恭喜;解析成功”,否则返回“输入字符串不在 L(G) 中”。

当输入字符串仅包含 a、b、c、d 和 e 时,一切正常。如果输入字符串中除了它们之外还有任何标记,我只需要弄清楚如何使解析器在没有成功语句的情况下给出语法错误。

这是一张有助于显示我的问题的图片: The first two parses work as intended. The third one shows my issue.

【问题讨论】:

  • 输入错误是什么?例如,我希望以f 开头的输入会产生错误返回。
  • @rici 我现在正在测试的错误输入是“abcdef”。 “abcde”部分是正确的并给出了正确的输出,但在末尾添加“f”会导致来自 yyerror("syntax error") 的语法错误消息和从 main 到的“congratulations; parse succeeded”打印语句打印。
  • 是的,这很容易解释。请在您的问题中添加这些详细信息。
  • @rici 刚刚看到您对评论的编辑。使用“fabcde”与我在上一条评论中描述的相同。它给了我错误,但它也给了我成功的打印声明。我正在使用“if(yyparse() == 0))”在 main 中打印成功语句,我认为这可能是罪魁祸首,尽管当我将 print 语句移动到 . y 文件,只在 main.y 文件中使用了 yyparse() 和 return(1)。也谢谢你的建议,我会把它添加到我的帖子中。
  • 我的猜测是这是未定义的行为:return yyerror("syntax error\n\nSorry, Charlie, input string not in L(G)\n"); 除非您的 yyerror 函数实际上返回了一些东西(大多数都没有)。

标签: parsing bison flex-lexer


【解决方案1】:

如果 (f)lex 规则没有返回任何内容,则它匹配的标记将被忽略。这适用于 cmets,但不适用于您想要错误的标记。如果您将包罗万象的 flex 规则更改为

.    return *yytext;

然后输入中所有无法识别的字符(除了换行符,这是唯一 . 不匹配的字符)将被返回,并且可能会导致来自解析器的 Syntax error 消息(以及来自 yyparse 的失败返回. 如果您的语法包含文字字符标记(例如,'#' 匹配该字符),那么它当然会匹配。

【讨论】:

  • 这是我尝试的第一件事,但没有成功。也许我只是用错了?
  • @DawsonJBailey 是的,你用错了,这已经工作了几十年了。
【解决方案2】:

bison/yacc 生成的解析器期望解析整个正确的输入,直到输入结束标记(包括输入结束标记),然后才返回成功指示(返回值为 0)。

当然,如果输入在语法上不正确,解析器可能会提前返回一个错误指示(对于语法错误,该值始终为 1,如果内存不足,则为 2)。在这种情况下,在解析器返回之前,它会清理其内部状态并释放所有分配的内存。

让解析器执行此操作很重要。从 bison/yacc 解析器中的语义操作返回充其量是不明智的(因为它几乎可以肯定是内存泄漏),并且还可能产生混淆,因为它可能导致在生成错误消息后成功返回。

例如,考虑输入abcdea 的情况,它是一个有效字符串后跟一个无效的a。 string 的语义操作可能会在解析器尝试处理最后一个 a 之前运行,因为解析器表压缩(延迟错误操作以保存表条目)。但是您的语义操作实际上返回 0,绕过了解析器的错误报告和清理。如果输入是 abcdef 并且您的扫描器调用 yyerror 来获取无效令牌(这也不是一个特别好的主意),那么操作顺序将是:

  1. 扫描仪打印错误
  2. 解析器执行string语义动作,返回0。

同样,语义操作中的return 语句绕过了正确的错误处理和清理。

所以不要那样做。如果您想报告语义操作中的错误,请使用YYABORT,它将干净地终止解析并返回错误。另一方面,如果您的顶级生产是正确的,则什么也不做。然后解析器将验证下一个输入标记是输入结束标记并返回成功。

【讨论】:

  • 我应该把 YYABORT 放在哪里?那我不应该使用 yyerror 吗?
  • 好的,所以在我的语法规则中使用 YYABORT 几乎解决了我的所有问题,除了输出需要包含导致语法错误的符号,如字符串的“>>>> CONTEXTUAL 解析”
  • 这不在您的原始问题中。 :-) 正如bison manual 中指出的——值得一读——全局变量yychar 是前瞻标记;你可以在你的yyerror 实现中使用它来打印出有问题的令牌。这里的“令牌”是yylex 返回的值,因此它可以是令牌枚举值(A、B、C、D 或 E),或者,如果您的后备 lex 规则返回*yytext,它可能是一个ASCII字符代码。也可以是YYEMPTY 和YYEOF....的特殊值之一。
  • ... 因此,将其转化为有意义的错误消息需要一些工作,但可以做到。如果您使用'a', ... 而不是A... 它会更简单,因为您可以使用%c printf 转换将其打印出来,但您仍然需要先检查特殊值。此外,问题不一定是前瞻标记,因为您的野牛规则首先读取 abcde 序列,然后检查计数;如果某个字母的计数错误,则可能是该字母需要显示,但该字母早已被野牛处理。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多