这个 Bison 语法有什么问题?
What is wrong with this Bison grammar?
我正在尝试构建 Bison 语法,但似乎遗漏了一些东西。我保持它非常基本,但我仍然遇到语法错误并且无法弄清楚原因:
这是我的 Bison 代码:
%{
#include <stdlib.h>
#include <stdio.h>
int yylex(void);
int yyerror(char *s);
%}
// Define the types flex could return
%union {
long lval;
char *sval;
}
// Define the terminal symbol token types
%token <sval> IDENT;
%token <lval> NUM;
%%
Program:
Def ';'
;
Def:
IDENT '=' Lambda { printf("Successfully parsed file"); }
;
Lambda:
"fun" IDENT "->" "end"
;
%%
main() {
yyparse();
return 0;
}
int yyerror(char *s)
{
extern int yylineno; // defined and maintained in flex.flex
extern char *yytext; // defined and maintained in flex.flex
printf("ERROR: %s at symbol \"%s\" on line %i", s, yytext, yylineno);
exit(2);
}
这是我的 Flex 代码
%{
#include <stdlib.h>
#include "bison.tab.h"
%}
ID [A-Za-z][A-Za-z0-9]*
NUM [0-9][0-9]*
HEX [$][A-Fa-f0-9]+
COMM [/][/].*$
%%
fun|if|then|else|let|in|not|head|tail|and|end|isnum|islist|isfun {
printf("Scanning a keyword\n");
}
{ID} {
printf("Scanning an IDENT\n");
yylval.sval = strdup( yytext );
return IDENT;
}
{NUM} {
printf("Scanning a NUM\n");
/* Convert into long to loose leading zeros */
char *ptr = NULL;
long num = strtol(yytext, &ptr, 10);
if( errno == ERANGE ) {
printf("Number was to big");
exit(1);
}
yylval.lval = num;
return NUM;
}
{HEX} {
printf("Scanning a NUM\n");
char *ptr = NULL;
/* convert hex into decimal using offset 1 because of the $ */
long num = strtol(&yytext[1], &ptr, 16);
if( errno == ERANGE ) {
printf("Number was to big");
exit(1);
}
yylval.lval = num;
return NUM;
}
";"|"="|"+"|"-"|"*"|"."|"<"|"="|"("|")"|"->" {
printf("Scanning an operator\n");
}
[ \t\n]+ /* eat up whitespace */
{COMM}* /* eat up one-line comments */
. {
printf("Unrecognized character: %s at linenumber %d\n", yytext, yylineno );
exit(1);
}
%%
这是我的 Makefile:
all: parser
parser: bison flex
gcc bison.tab.c lex.yy.c -o parser -lfl
bison: bison.y
bison -d bison.y
flex: flex.flex
flex flex.flex
clean:
rm bison.tab.h
rm bison.tab.c
rm lex.yy.c
rm parser
编译一切正常,我没有收到任何错误运行nin make all。
这是我的测试文件
f = fun x -> end;
这是输出:
./parser < a0.0
Scanning an IDENT
Scanning an operator
Scanning a keyword
Scanning an IDENT
ERROR: syntax error at symbol "x" on line 1
因为 x 似乎被识别为 IDENT 规则应该是正确的,但我仍然遇到语法错误。
我觉得我错过了一些重要的东西,希望有人能帮助我。
提前致谢!
编辑:
我尝试删除Lambda规则和测试文件中的IDENT
,现在似乎运行通过了这条线,但仍然抛出
ERROR: syntax error at symbol "" on line 1
在 EOF 之后。
您的扫描器可以识别关键字(并打印出调试行,但请参阅下文),但它不会向解析器报告任何内容。所以它们实际上被忽略了。
在您的 bison 定义文件中,您使用(例如)"fun" 作为终端,但您没有为终端提供可在扫描仪中使用的名称。扫描器需要这个名称,因为它必须 return 解析器的令牌 ID。
总而言之,您需要的是这样的东西:
在你的语法中,在 %%
:
之前
token T_FUN "fun"
token T_IF "if"
token T_THEN "then"
/* Etc. */
在您的扫描仪定义中:
fun { return T_FUN; }
if { return T_IF; }
then { return T_THEN; }
/* Etc. */
一些其他注意事项:
您用于识别运算符的扫描器规则也无法 return 任何内容,因此运算符也将被忽略。这显然是不可取的。 flex 和 bison 为单字符运算符提供了一种更简单的解决方案,即让字符成为它自己的标记 id。这避免了必须创建令牌名称。在解析器中,一个单引号字符表示一个token-id,其值为该字符;这与 双引号 字符串完全不同,后者是声明的令牌名称的别名。所以你可以这样做:
"=" { return '='; }
/* Etc. */
但一次完成所有单字符标记更容易:
[;+*.<=()-] { return yytext[0]; }
最后使用默认规则更容易:
. { return yytext[0]; }
这将通过 return 将未知标记 ID 发送给解析器来处理无法识别的字符,这将导致语法错误。
这不适用于“->”,因为它不是单个字符标记,必须以与关键字相同的方式处理。
如果您在创建扫描器时使用 -d
标志,Flex 将自动生成调试输出。这比插入您自己的调试打印输出要容易得多,因为您只需删除 -d
选项即可将其关闭。 (如果不想更改 makefile 中的 flex 调用,可以使用 %option debug
。)它也更好,因为它提供了一致的信息,包括位置信息。
一些小问题:
- 模式
[0-9][0-9]*
可以更容易地写成 [0-9]+
- 注释模式
"//".*
不需要在末尾进行 $
前瞻,因为 .*
将始终匹配最长的非换行符序列;因此,第一个不匹配的字符必须是换行符或 EOF。 $
如果模式以 EOF 终止,则前瞻将不匹配,如果文件以没有换行符的注释结尾,则会导致奇怪的错误。
- 使用
{COMM}*
没有意义,因为注释模式不匹配终止注释的换行符,所以不可能有两个连续的注释匹配。但是不管怎么说,匹配一个注释和后面的换行符后,flex会继续匹配后面的注释,所以{COMM}
就够了。 (就我个人而言,我不会使用 COMM
缩写;恕我直言,它确实没有增加可读性。)
我正在尝试构建 Bison 语法,但似乎遗漏了一些东西。我保持它非常基本,但我仍然遇到语法错误并且无法弄清楚原因:
这是我的 Bison 代码:
%{
#include <stdlib.h>
#include <stdio.h>
int yylex(void);
int yyerror(char *s);
%}
// Define the types flex could return
%union {
long lval;
char *sval;
}
// Define the terminal symbol token types
%token <sval> IDENT;
%token <lval> NUM;
%%
Program:
Def ';'
;
Def:
IDENT '=' Lambda { printf("Successfully parsed file"); }
;
Lambda:
"fun" IDENT "->" "end"
;
%%
main() {
yyparse();
return 0;
}
int yyerror(char *s)
{
extern int yylineno; // defined and maintained in flex.flex
extern char *yytext; // defined and maintained in flex.flex
printf("ERROR: %s at symbol \"%s\" on line %i", s, yytext, yylineno);
exit(2);
}
这是我的 Flex 代码
%{
#include <stdlib.h>
#include "bison.tab.h"
%}
ID [A-Za-z][A-Za-z0-9]*
NUM [0-9][0-9]*
HEX [$][A-Fa-f0-9]+
COMM [/][/].*$
%%
fun|if|then|else|let|in|not|head|tail|and|end|isnum|islist|isfun {
printf("Scanning a keyword\n");
}
{ID} {
printf("Scanning an IDENT\n");
yylval.sval = strdup( yytext );
return IDENT;
}
{NUM} {
printf("Scanning a NUM\n");
/* Convert into long to loose leading zeros */
char *ptr = NULL;
long num = strtol(yytext, &ptr, 10);
if( errno == ERANGE ) {
printf("Number was to big");
exit(1);
}
yylval.lval = num;
return NUM;
}
{HEX} {
printf("Scanning a NUM\n");
char *ptr = NULL;
/* convert hex into decimal using offset 1 because of the $ */
long num = strtol(&yytext[1], &ptr, 16);
if( errno == ERANGE ) {
printf("Number was to big");
exit(1);
}
yylval.lval = num;
return NUM;
}
";"|"="|"+"|"-"|"*"|"."|"<"|"="|"("|")"|"->" {
printf("Scanning an operator\n");
}
[ \t\n]+ /* eat up whitespace */
{COMM}* /* eat up one-line comments */
. {
printf("Unrecognized character: %s at linenumber %d\n", yytext, yylineno );
exit(1);
}
%%
这是我的 Makefile:
all: parser
parser: bison flex
gcc bison.tab.c lex.yy.c -o parser -lfl
bison: bison.y
bison -d bison.y
flex: flex.flex
flex flex.flex
clean:
rm bison.tab.h
rm bison.tab.c
rm lex.yy.c
rm parser
编译一切正常,我没有收到任何错误运行nin make all。
这是我的测试文件
f = fun x -> end;
这是输出:
./parser < a0.0
Scanning an IDENT
Scanning an operator
Scanning a keyword
Scanning an IDENT
ERROR: syntax error at symbol "x" on line 1
因为 x 似乎被识别为 IDENT 规则应该是正确的,但我仍然遇到语法错误。
我觉得我错过了一些重要的东西,希望有人能帮助我。
提前致谢!
编辑:
我尝试删除Lambda规则和测试文件中的IDENT
,现在似乎运行通过了这条线,但仍然抛出
ERROR: syntax error at symbol "" on line 1
在 EOF 之后。
您的扫描器可以识别关键字(并打印出调试行,但请参阅下文),但它不会向解析器报告任何内容。所以它们实际上被忽略了。
在您的 bison 定义文件中,您使用(例如)"fun" 作为终端,但您没有为终端提供可在扫描仪中使用的名称。扫描器需要这个名称,因为它必须 return 解析器的令牌 ID。
总而言之,您需要的是这样的东西:
在你的语法中,在 %%
:
token T_FUN "fun"
token T_IF "if"
token T_THEN "then"
/* Etc. */
在您的扫描仪定义中:
fun { return T_FUN; }
if { return T_IF; }
then { return T_THEN; }
/* Etc. */
一些其他注意事项:
您用于识别运算符的扫描器规则也无法 return 任何内容,因此运算符也将被忽略。这显然是不可取的。 flex 和 bison 为单字符运算符提供了一种更简单的解决方案,即让字符成为它自己的标记 id。这避免了必须创建令牌名称。在解析器中,一个单引号字符表示一个token-id,其值为该字符;这与 双引号 字符串完全不同,后者是声明的令牌名称的别名。所以你可以这样做:
"=" { return '='; } /* Etc. */
但一次完成所有单字符标记更容易:
[;+*.<=()-] { return yytext[0]; }
最后使用默认规则更容易:
. { return yytext[0]; }
这将通过 return 将未知标记 ID 发送给解析器来处理无法识别的字符,这将导致语法错误。
这不适用于“->”,因为它不是单个字符标记,必须以与关键字相同的方式处理。
如果您在创建扫描器时使用
-d
标志,Flex 将自动生成调试输出。这比插入您自己的调试打印输出要容易得多,因为您只需删除-d
选项即可将其关闭。 (如果不想更改 makefile 中的 flex 调用,可以使用%option debug
。)它也更好,因为它提供了一致的信息,包括位置信息。一些小问题:
- 模式
[0-9][0-9]*
可以更容易地写成[0-9]+
- 注释模式
"//".*
不需要在末尾进行$
前瞻,因为.*
将始终匹配最长的非换行符序列;因此,第一个不匹配的字符必须是换行符或 EOF。$
如果模式以 EOF 终止,则前瞻将不匹配,如果文件以没有换行符的注释结尾,则会导致奇怪的错误。 - 使用
{COMM}*
没有意义,因为注释模式不匹配终止注释的换行符,所以不可能有两个连续的注释匹配。但是不管怎么说,匹配一个注释和后面的换行符后,flex会继续匹配后面的注释,所以{COMM}
就够了。 (就我个人而言,我不会使用COMM
缩写;恕我直言,它确实没有增加可读性。)
- 模式