问题描述
我正在尝试为我设计的一种小型语言制作编译器。我正在尝试使用 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
是编译成功所必需的。上面答案中的版本应该是一个好的开始。
附加说明
您的代码中存在一些其他错误。
-
在
funlang.l
的第 25 行,缺少一个;
-
在第 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*
;这将与您的词法扫描器中的使用兼容,并且如果有人碰巧提供了一个长标识符,则不会导致缓冲区溢出。 -
你的字符串文字正则表达式是错误的,因为它是一个带引号的字符串。在 (f)lex 中,模式可以包含带引号的字符串,这被证明非常有用,但遗憾的是在我知道的任何正则表达式库中都没有实现。您需要将
"
转义为\"
才能获得所需内容。它也很难阅读;我建议使用否定字符类来显示您排除的字符。但这只是一个建议。 -
你有
%option yylineno
,它会为你跟踪行号。在我的YY_USER_ACTION
中有一个lexer.l
的定义,它使用它来填充位置对象,以便野牛可以使用令牌位置。如果您使用该代码,您将不需要自己跟踪行号,因此您不需要line_no
变量或\n
的规则,它们可以添加到您的空白规则中。 -
关于空白规则,flex——与 lex 不同——通常允许没有动作的规则,但这是不正确的,可能会导致问题,特别是在某些时候你使用不同的扫描仪生成器时。规则必须有动作;如果您希望模式不执行任何操作,请使用操作
;
(这是通常的约定)或{}
。 -
无需像
main
那样将输入读入内存。 (f)lex 扫描器会处理读取文件的所有细节,无论它有多长。