C 编译器 (GCC) 在使用 Flex 和 Bison 时出现几个错误 附加说明

问题描述

我正在尝试为我设计的一种小型语言制作编译器。我正在尝试使用 Flex 作为词法分析器生成器,使用 Bison 作为解析器生成器。我已经阅读了关于 Bison 的维基百科页面,以及关于让 Flex 和 Bison 很好地协同工作的 stackoverflow 上的几篇文章。出于某种原因,我仍然遇到错误。 这是生成文件

CC      = gcc
CFLAGS  = -O2 -Wall -Wextra -Wpedantic -lfl
INFILES = main.c Parser.c Lexer.c
OUTFILE = language

default: Lexer.c Parser.c
    $(CC) $(CFLAGS) $(INFILES) -o $(OUTFILE)

Lexer.c: funlang.l
    flex funlang.l

Parser.c: funlang.y Lexer.c
    bison -d -Wcounterexamples funlang.y

clean:
    rm Lexer.* Parser.* language

main.c:

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "eval.h"



int main(int argc,char *argv[]){
 if(argc < 2){
  printf("%s error: No input file supplied\n",argv[0]);
  return 0;
 }
 if(argc > 3){
  printf("%s error: Too many arguments supplied\n",argv[0]);
  return 0;
 }
 FILE *file = fopen(argv[1],"r");
 if(file == NULL){
  printf("Error opening file %s\n",argv[1]);
  return 0;
 }
 fseek(file,SEEK_END);
 size_t input_length = (size_t) ftell(file);
 rewind(file);
 size_t bytes_read = 0;
 char *input = (char *) malloc(sizeof(char) * input_length);
 bytes_read = fread(input,1,input_length,file);
 if(bytes_read != input_length){
  puts("Error reading file");
  return 0;
 }
 fclose(file);
 
 /* I'm still not doing anything with the parser or lexer yet,because I've not gotten them to work */
 
 return 0; 
}

eval.h:

#include "Parser.h"
#include "Lexer.h"

funlang.l:

%option outfile="Lexer.c" header-file="Lexer.h"

%option bison-bridge bison-locations never-interactive reentrant
%option warn nodefault nounistd yylineno noinput nounput
%option noyywrap batch

%{
#include <stdint.h>
#include <string.h>
#include "Parser.h"
#include "Lexer.h"
size_t line_count = 1;
%}


int_literal        ((-)?((0x)[0-9A-Fa-f]+|[0-9]+))
str_literal        ("[A-Za-z0-9 \t!#-&(-/:-@[-_{-~]*")
whitespace         [ \t\r]
identifier         ([A-Za-z])([A-Za-z0-9]+)

%%
"func"                {return FUNCTION_KEYWORD; }
"if"                  {return IF;               }
"else"                {return ELSE;             }
"int"                 {return INT_KEYWORD       }
"str"                 {return STR_KEYWORD;      }
"bool"                {return BOOL_KEYWORD;     }
"true"                {return BOOL_LIteraL;     }
"false"               {return BOOL_LIteraL;     }

{int_literal}         {yylval.val = (intmax_t) strtol(yytext,(char **) NULL,0);
                       return INT_LIteraL;      }

{str_literal}         {return STR_LIteraL;      }
"->"                  {return GIVES_TYPE;       }
{identifier}          {yylval.name = strdup(yytext);
                       return IDENTIFIER;       }
\n                    {line_count++; }
{whitespace}
.                     {printf("Error,unrecognized char at line #%li",line_count); 
                       return OTHER;            }

%%

和 funlang.y:

%define api.pure full
%locations
%param {yyscan_t scanner}

%code top{
 #include <stdio.h>
 #include <stdint.h>
}

%code requires{
 typedef void* yyscan_t;
}

%code{
 int yylex(YYSTYPE* yylvalp,YYLTYPE* yyllocp,yyscan_t scanner);
}

%{
#include "Parser.h"
%}

%token FUNCTION_KEYWORD IF ELSE INT_KEYWORD STR_KEYWORD BOOL_KEYWORD
%token BOOL_LIteraL STR_LIteraL GIVES_TYPE OTHER
%token <val>  INT_LIteraL
%token <name> IDENTIFIER

%output  "Parser.c"
%defines "Parser.h"

%union{
 char name[16];
 intmax_t val;
};


%%

function: FUNCTION_KEYWORD IDENTIFIER '(' parameters ')' GIVES_TYPE INT_KEYWORD '{' statement '}'
|         FUNCTION_KEYWORD IDENTIFIER '(' parameters ')' GIVES_TYPE STR_KEYWORD '{' statement '}'
|         FUNCTION_KEYWORD IDENTIFIER '(' parameters ')' GIVES_TYPE BOOL_KEYWORD '{' statement '}';

parameters: parameter
|           %empty;

parameter: INT_KEYWORD                IDENTIFIER
|          parameter ',' INT_KEYWORD  IDENTIFIER
|          STR_KEYWORD                IDENTIFIER
|          parameter ',' STR_KEYWORD  IDENTIFIER
|          BOOL_KEYWORD               IDENTIFIER
|          parameter ',' BOOL_KEYWORD IDENTIFIER;

call_parameters: parameter
|                %empty;


call_parameter: IDENTIFIER
|               parameter ',' IDENTIFIER;

array_list: INT_LIteraL
|           IDENTIFIER
|           function_call
|           indexing_expression
|           STR_LIteraL
|           BOOL_LIteraL
|           array_list ',' IDENTIFIER
|           array_list ',' function_call
|           array_list ',' indexing_expression
|           array_list ',' INT_LIteraL
|           array_list ',' STR_LIteraL
|           array_list ',' BOOL_LIteraL;


var_deFinition: INT_KEYWORD IDENTIFIER '=' INT_LIteraL ';'
|               INT_KEYWORD IDENTIFIER '=' math_expression ';'
|               INT_KEYWORD IDENTIFIER '=' function_call ';'
|               INT_KEYWORD IDENTIFIER ';'
|               INT_KEYWORD indexing_expression '=' array_list ';'
|               INT_KEYWORD indexing_expression ';'
|               STR_KEYWORD IDENTIFIER '=' STR_LIteraL ';'
|               STR_KEYWORD IDENTIFIER '=' function_call ';'
|               STR_KEYWORD IDENTIFIER ';'
|               STR_KEYWORD indexing_expression '=' array_list ';'
|               STR_KEYWORD indexing_expression ';'
|               BOOL_KEYWORD IDENTIFIER '=' BOOL_LIteraL ';'
|               BOOL_KEYWORD IDENTIFIER '=' function_call ';'
|               BOOL_KEYWORD IDENTIFIER ';'
|               BOOL_KEYWORD indexing_expression '=' array_list ';'
|               BOOL_KEYWORD indexing_expression ';';


function_call: IDENTIFIER '(' call_parameters ')';

boolean_statement: '(' IDENTIFIER "==" IDENTIFIER ')'
|                  '(' IDENTIFIER ')'               
|                  '(' indexing_expression ')'     
|                  '(' boolean_statement ')'
|                  '(' boolean_statement "&&" boolean_statement ')'
|                  '(' boolean_statement "||" boolean_statement ')'
|                  '(' '!' boolean_statement ')'
|                  '(' function_call ')';            

BINARY_OPERATOR: '+'| '-' | '*' | '/' | '&' | '|';

math_expression: '(' BINARY_OPERATOR INT_LIteraL       INT_LIteraL         ')'
|                '(' BINARY_OPERATOR INT_LIteraL       IDENTIFIER          ')'
|                '(' BINARY_OPERATOR IDENTIFIER        INT_LIteraL         ')'
|                '(' BINARY_OPERATOR INT_LIteraL       function_call       ')'
|                '(' BINARY_OPERATOR function_call     INT_LIteraL         ')'
|                '(' BINARY_OPERATOR function_call     function_call       ')'
|                '(' BINARY_OPERATOR INT_LIteraL       math_expression     ')'
|                '(' BINARY_OPERATOR math_expression   INT_LIteraL         ')'
|                '(' BINARY_OPERATOR math_expression   IDENTIFIER          ')'
|                '(' BINARY_OPERATOR IDENTIFIER        math_expression     ')'
|                '(' BINARY_OPERATOR math_expression   function_call       ')'
|                '(' BINARY_OPERATOR function_call     math_expression     ')'
|                '(' BINARY_OPERATOR math_expression   math_expression     ')';

indexing_expression: IDENTIFIER '[' INT_LIteraL ']'
|                    IDENTIFIER '[' IDENTIFIER  ']'; 

statement: IF boolean_statement '{' statement '}'
|          ELSE '{' statement '}'
|          "return" function_call  ';'
|          "return" IDENTIFIER     ';'
|          "return" INT_LIteraL    ';'
|          "return" STR_LIteraL    ';'
|          "return" BOOL_LIteraL   ';'
|          var_deFinition;

%%

我遇到的一个大问题是,在编译所有内容时,C 编译器会在几个阶段说 'yyin'、'yyout'、'yyleng' 和其他几个未声明。例如,Lexer.c:800:10: error: ‘yyin’ undeclared (first use in this function)

如果我遗漏了任何重要信息,或者其他方面不清楚,我很抱歉。我会尽力澄清任何歧义。提前致谢

解决方法

未定义符号错误都是你放的结果

#include "Lexer.h"

在您生成的词法分析器中。你绝不能那样做;词法分析器头信息已经包含在生成的词法分析器中,如果包含两次会导致错误。

同样,你不应该把

#include "Parser.h"

进入生成的解析器。同样,生成的解析器已经有了必要的定义,双重包含会导致问题。

我试图在 this answer 中展示一个可重入的 bison/flex 项目的轮廓。该答案仅谨慎使用必要的包含,并试图解释一些基本原理。

还要注意 yyerror 是编译成功所必需的。上面答案中的版本应该是一个好的开始。


附加说明

您的代码中存在一些其他错误。

  1. funlang.l 的第 25 行,缺少一个 ;

  2. 在第 31 和 36 行的操作中,您需要考虑这一段(来自可重入解析器的链接答案):

    扫描仪操作中对 yylval 的所有引用也需要修改,因为 bison 的可重入 API 传递指向语义值和位置对象的指针。如果语义类型是联合(通常通过在 bison 源中放置 %union 声明产生),则 您需要将使用 yylval.tag 的扫描仪操作更改为 yylval->tag 。同样,如果您使用单一语义类型,默认类型或使用 %define api.value.type 声明的类型(在野牛源中),那么您需要将 yylval = ... 替换为 *yylval = ... ,如上面的示例代码。

    即使有这个修复,yylval->name = strdup(yytext) 也不会工作,因为您的解析器的 %union 声明将 name 定义为一个固定长度的字符数组,并且您不能分配给一个数组。我强烈建议将 name 更改为 char*;这将与您的词法扫描器中的使用兼容,并且如果有人碰巧提供了一个长标识符,则不会导致缓冲区溢出。

  3. 你的字符串文字正则表达式是错误的,因为它是一个带引号的字符串。在 (f)lex 中,模式可以包含带引号的字符串,这被证明非常有用,但遗憾的是在我知道的任何正则表达式库中都没有实现。您需要将 " 转义为 \" 才能获得所需内容。它也很难阅读;我建议使用否定字符类来显示您排除的字符。但这只是一个建议。

  4. 你有 %option yylineno ,它会为你跟踪行号。在我的 YY_USER_ACTION 中有一个 lexer.l 的定义,它使用它来填充位置对象,以便野牛可以使用令牌位置。如果您使用该代码,您将不需要自己跟踪行号,因此您不需要 line_no 变量或 \n 的规则,它们可以添加到您的空白规则中。

  5. 关于空白规则,flex——与 lex 不同——通常允许没有动作的规则,但这是不正确的,可能会导致问题,特别是在某些时候你使用不同的扫描仪生成器时。规则必须有动作;如果您希望模式不执行任何操作,请使用操作 ;(这是通常的约定)或 {}

  6. 无需像 main 那样将输入读入内存。 (f)lex 扫描器会处理读取文件的所有细节,无论它有多长。