如何使用 yacc 解析器检测错误行号

Aro*_*ron 5 yacc lex bison

我们不知道如何跟踪 yacc 解析器中的错误。yylineno我们尝试在 lex 文件中使用并尝试添加%option yylineno,但它仍然不起作用,我们无法在 yacc 中访问这些变量。

error我们想要的只是使用yacc 和行号打印出语法错误。

这是我们的.l文件

%{
#include <stdio.h>
#include <stdlib.h>
#include "y.tab.h"
int yylineno=1;

%}

%option yylineno

identifier  [a-zA-Z_][a-zA-Z0-9_]*
int_constant    [0-9]+
delimiter       ;

%%

"int"       {return INT;}
{int_constant}  return INT_CONST;
{identifier}    return IDENT;
\=      {return ASOP;}
\+      {return PLUS;}
\-      {return MINUS;}
\*      {return MULT;}
\/      {return DIV;}
\,      {return COMMA;}
\(      {return OP;} /*OP CP = Opening Closing Parenthesis*/
\)      {return CP;}
\[      {return OB;} /*OB CB = Opening Closing Brace*/
\]      {return CB;}
\{      {return OCB;} /*OCB CCB = Opening Closing Curly Brace*/
\}      {return CCB;}
{delimiter} return DEL;
[ \t]
[\n]        {yylineno++;}


%%
Run Code Online (Sandbox Code Playgroud)

现在这是我们的.y文件

%{
#include <stdio.h>
#include <string.h>
#include "y.tab.h"

extern FILE *yyin;

%}

%token INT INT_CONST IDENT ASOP PLUS MINUS MULT DIV DEL COMMA CP CB CCB
%left OP OB OCB


%%

program:        program_unit;
program_unit:   program_unit component | component
component:  var_decl DEL | func_decl DEL | func_defn ;
var_decl:       dt list;
dt:     INT;
list:       list COMMA var | var 
        | error {printf("before ';' token\n"); yyerrok;}
        | error INT_CONST {printf("before numeric constant\n"); yyerrok;};
var:        IDENT
        |IDENT init;
init:       ASOP IDENT init | ASOP expr | ASOP IDENT ;
expr:       IDENT op expr | const op expr | const | OP expr CP;
const:      INT_CONST;
op:     PLUS | MINUS | MULT | DIV;
func_decl:  dt mult_func;
mult_func:  mult_func COMMA mfunc | sfunc;
mfunc:      IDENT OP CP;
sfunc:      IDENT OP CP OCB func_body CCB;
func_body:  program_unit;

func_defn:  dt IDENT OP CP OCB func_body CCB
        | IDENT OP CP OCB func_body CCB; 

%%

int yyerror(char *s){
    extern int yylineno;
    fprintf(stderr,"At line %d %s ",s,yylineno);  
}

int yywrap(){
    return 1;
}

int main(int argc, char *argv[]){
    yyin=fopen("test.c","r");
    yyparse();
    fclose(yyin);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

ric*_*ici 6

这些文件存在许多问题,但它们都不会阻止yylineno您的 bison 生成的解析器使用。

您对 的定义将会产生编译时警告yyerror。或者可能有几个警告。

首先,正确的签名是:

void yyerror(const char *msg);
Run Code Online (Sandbox Code Playgroud)

可以返回 anint但该值从未被使用;但是,您对函数的定义刚刚结束,因此编译器会抱怨没有返回任何值。此外,yyerror通常使用不可变的文字字符串参数进行调用;标准 C 允许将文字字符串传递给参数类型为非常量的函数,但不建议使用,编译器可能会发出警告。更重要的是,

fprintf(stderr,"At line %d %s ",s,yylineno);
Run Code Online (Sandbox Code Playgroud)

%d(整数)格式应用于s(字符串)并将%s(字符串)格式应用于yylineno(整数);同样,这应该会产生编译时警告,如果您忽略该错误,您的程序可能会出现段错误。

最后(与 相关),如果您在输入中yylineno指定(如果您想计算行数,这是一个好主意),那么 Flex 生成的扫描仪将定义并初始化并为您进行计数。因此,您在文件中的定义将触发编译时错误(重新定义)。此外,当您显式递增( ) 时,您最终会重复计算行;将由扫描仪递增,然后由您的操作再次递增。我的建议:指定然后让 flex 为你做一切。您只需按照文件中的方式声明它(就像您所做的那样)。您只需添加到忽略的空白字符列表即可。%option yylinenoflexyylinenoyylineno.lyylinenoyylineno[\n] {++yylineno;}yylineno%option yylinenoexternbison\n

需要注意的是:yylineno直接使用 inbison意味着您不会获得语法错误的确切位置,因为bison生成的解析器通常会读取一个先行标记,并且yylineno此时已经更新为该标记末尾的行号bison注意到语法错误。有时这会产生误导,特别是在由于缺少标记而导致语法错误的情况下。

其他一些问题:

  • 使用文字字符标记的风格(恕我直言)比定义标记名称并将bison其与flex文件协调要好得多。如果您只使用文字字符,那么两个文件更容易保持彼此同步;语法更具可读性;你不需要像这样的评论

    /*OP CP = Opening Closing Parenthesis*/
    
    Run Code Online (Sandbox Code Playgroud)

    相反,只需')'在语法中使用,在词法分析器中您可以执行以下操作:

    [][=+*/,(){}-]  { return yytext[0]; }
    
    Run Code Online (Sandbox Code Playgroud)

    或者你甚至可以在最后使用默认规则:

    .  { return yytext[0]; }
    
    Run Code Online (Sandbox Code Playgroud)
  • 与上述相关,以及我通常选择第二个选项(默认规则)的原因,您的词法分析器没有针对所有可能字符的规则,因此将使用 flex 提供的默认规则。flex 提供的默认规则是仅将无效字符回显到yyout。这绝不是您在实际编译器中想要的,结果是输入错误(或扫描器错误)被悄悄隐藏。最好使用像我上面建议的那样的默认规则,并通过%option nodefault避免使用 Flex 生成的默认规则来保护自己。使用 时%option nodefault,如果输入可能不匹配,flex 会向您发出警告;请不要忽略此警告。