Flex 和 Bison:词法分析在输入结束前停止
Flex and Bison: Lexical analysis stops before end of input
我正在使用 Flex 和 Bison 来解析简化的 SQL 语法。我 运行 遇到一个问题,即 Flex 将在文件末尾之前停止标记化。当我通过仅包含以下内容的测试用例时:
create database this;
一切正常,令牌 CREATE、DATEABASE 和 ID 已创建。但是当我通过一个仅包含以下内容的测试用例时:
create table this (integer qty);
它将分词到 "qty" 并包括在内,但不会读取 ')' 或 ';'。相反,它会直接跳转到解析并打印 "REJECT"(因为它应该使用不正确的标记)。我怎样才能修复我的代码以标记所有内容,以便我通过 CREATE, TABLE, ID, (, INTEGER, ID, ), ;作为 Bison 的代币?下面是我的 .l .y 和 makefile。预先感谢您的帮助!
sql.l
%{
#include "sql.tab.h"
%}
Delimiter [ \t]
WhiteSpace {Delimiter}+
Letter [A-Za-z]
Name [A-Za-z][A-Za-z0-9_]*
Digit [0-9]
Integer {Digit}+
Float {Digit}+"."{Digit}+
Date (0[1-9]|1[012])"/"(0[1-9]|[12][0-9]|3[01])"/"[0-9][0-9][0-9][0-9]
Other [-!@#$%&+:"~`]
%%
{WhiteSpace} { ; }
[Cc][Rr][Ee][Aa][Tt][Ee] {printf("token: CREATE\n");return(CREATE);}
[Dd][Rr][Oo][Pp] {printf("token: DROP\n");return(DROP);}
[Ll][Oo][Aa][Dd] {printf("token: LOAD\n");return(LOAD);}
[Ss][Aa][Vv][Ee] {printf("token: SAVE\n");return(SAVE);}
[Dd][Aa][Tt][Aa][Bb][Aa][Ss][Ee] {printf("token: DATABASE\n");return(DATABASE);}
[Tt][Aa][Bb][Ll][Ee] {printf("token: TABLE\n");return(TABLE);}
[Ii][Nn][Ss][Ee][Rr][Tt] {printf("token: INSERT\n");return(INSERT);}
[Ii][Nn][Tt][Oo] {printf("token: INTO\n");return(INTO);}
[Ff][Rr][Oo][Mm] {printf("token: FROM\n");return(FROM);}
[Ww][Hh][Ee][Rr][Ee] {printf("token: WHERE\n");return(WHERE);}
[Ss][Ee][Tt] {printf("token: SET\n");return(SET);}
[Dd][Ee][Ll][Ee][Tt][Ee] {printf("token: DELETE\n");return(DELETE);}
[Uu][Pp][Dd][Aa][Tt][Ee] {printf("token: UPDATE\n");return(UPDATE);}
[Ss][Ee][Ll][Ee][Cc][Tt] {printf("token: SELECT\n");return(SELECT);}
[Ww][Ss][Ee][Ll][Ee][Cc][Tt] {printf("token: WSELECT\n");return(WSELECT);}
[Vv][Aa][Ll][Uu][Ee][Ss] {printf("token: VALUES\n");return(VALUES);}
[Ii][Nn][Tt][Ee][Gg][Ee][Rr] {printf("token: INTEGER\n");return(INTEGER);}
[Nn][Uu][Mm][Bb][Ee][Rr] {printf("token: NUMBER\n");return(NUMBER);}
[Cc][Hh][Aa][Rr][Aa][Cc][Tt][Ee][Rr] {printf("token: CHARACTER\n");return(CHARACTER);}
[Dd][Aa][Tt][Ee] {printf("token: DATE\n");return(DATE);}
{Name} {printf("token: ID %s\n", yytext);return(ID);}
"*" {printf("token: *\n");return('*');}
"(" {printf("token: (\n");return('(');}
")" {printf("token: )\n");return(')');}
"," {printf("token: ,\n");return(',');}
"<" {printf("token: LT\n");return(LT);}
">" {printf("token: GT\n");return(GT);}
"<=" {printf("token: LTEQ\n");return(LTEQ);}
">=" {printf("token: GTEQ\n");return(GTEQ);}
"==" {printf("token: EQEQ\n");return(EQEQ);}
"!=" {printf("token: NOTEQ\n");return(NOTEQ);}
";" {printf("token: ;\n");return(';');}
{Float} {printf("token: DEC\n");return(DEC);}
"-"{Float} {printf("token: DEC\n");return(DEC);}
{Integer} {printf("token: INT\n");return(INT);}
"-"{Integer} {printf("token: INT\n");return(INT);}
"'"[ \ta-zA-Z0-9]+"'" {printf("token: STR\n");return(STR);}
{Date} {printf("token: DATETYPE\n");return(DATETYPE);}
\n { ; }
. {printf("Character %s not tokenized\n", yytext);exit(0)
;}
sql.y
%{
#define YYSTYPE char*
#include <stdio.h>
%}
%start START
%token ID
%token CREATE DROP LOAD SAVE DATABASE TABLE INSERT INTO FROM
%token WHERE SET DELETE UPDATE SELECT WSELECT VALUES
%token DEC INT STR DATETYPE DATE NUMBER CHARACTER INTEGER
%token LT GT LTEQ GTEQ EQ EQEQ NOTEQ
%%
START : COMMAND_LIST
COMMAND_LIST : COMMAND COMMAND_LIST2
COMMAND_LIST2 : COMMAND COMMAND_LIST2
| /* EMPTY */
COMMAND : SYSTEM_COMMAND
| DDL_COMMAND
| DML_COMMAND
SYSTEM_COMMAND : CREATE_DATABASE_COMMAND
| DROP_DATABASE_COMMAND
| SAVE_COMMAND
| LOAD_DATABASE_COMMAND
DDL_COMMAND : CREATE_TABLE_COMMAND
| DROP_TABLE_COMMAND
DDL_COMMAND : CREATE_TABLE_COMMAND
| DROP_TABLE_COMMAND
DML_COMMAND : INSERT_INTO_COMMAND
| DELETE_FROM_COMMAND
| UPDATE_COMMAND
| SELECT_COMMAND
| W_SELECT_COMMAND
CREATE_DATABASE_COMMAND : CREATE DATABASE ID ';'
DROP_DATABASE_COMMAND : DROP DATABASE ID ';'
SAVE_COMMAND : SAVE ';'
LOAD_DATABASE_COMMAND : LOAD DATABASE ID ';'
CREATE_TABLE_COMMAND : CREATE TABLE ID '(' FIELD_DEF_LIST ')' ';'
DROP_TABLE_COMMAND : DROP TABLE ID ';'
INSERT_INTO_COMMAND : INSERT INTO ID INSERT_INTO_COMMAND2
INSERT_INTO_COMMAND2 : '(' FIELD_LIST ')' VALUES '(' LITERAL_LIST ')' ';'
| VALUES '(' LITERAL_LIST ')' ';'
DELETE_FROM_COMMAND : DELETE FROM ID DELETE_FROM_COMMAND2
DELETE_FROM_COMMAND2 : WHERE CONDITION ';' | ';'
UPDATE_COMMAND : UPDATE ID SET ID '=' LITERAL UPDATE_COMMAND2
UPDATE_COMMAND2 : ',' ID '=' LITERAL UPDATE_COMMAND2
| UPDATE_COMMAND3
UPDATE_COMMAND3 : WHERE CONDITION ';'
| ';'
SELECT_COMMAND : SELECT '*' FROM ID ';'
W_SELECT_COMMAND : WSELECT W_SELECT_COMMAND2
W_SELECT_COMMAND2 : '*' FROM ID W_SELECT_COMMAND3
| '(' FIELD_LIST ')' FROM ID W_SELECT_COMMAND3
W_SELECT_COMMAND3 : WHERE CONDITION ';'
| ';'
FIELD_DEF_LIST : FIELD_DEF FIELD_DEF_LIST2
FIELD_DEF_LIST2 : ',' FIELD_DEF FIELD_DEF_LIST2
| /* EMPTY */
FIELD_DEF : FIELD_TYPE ID
FIELD_LIST : ID FIELD_LIST2
FIELD_LIST2 : ',' ID FIELD_LIST2
| /* EMPTY */
FIELD_TYPE : INTEGER '(' INT ')'
| INT
| NUMBER '(' INT ')'
| NUMBER '(' INT ',' INT ')'
| NUMBER
| CHARACTER '(' INT ')'
| DATE
LITERAL_LIST : LITERAL LITERAL_LIST2
LITERAL_LIST2 : ',' LITERAL LITERAL_LIST2
| /* EMPTY */
LITERAL : INT
| DEC
| STR
| DATETYPE
CONDITION : ID COMP LITERAL
COMP : LT
| GT
| EQEQ
| LTEQ
| GTEQ
| NOTEQ
%%
main()
{
yyparse();
}
yyerror()
{
printf("REJECT\n");
}
yywrap()
{
printf("ACCEPT\n");
}
makefile
trial: lex.yy.o sql.tab.o
cc -o trial lex.yy.o sql.tab.o
sql.tab.o: sql.tab.c
cc -c sql.tab.c
sql.tab.c: sql.y
bison -d sql.y
lex.yy.o: lex.yy.c
cc -c lex.yy.c
lex.yy.c: sql.l sql.tab.c
flex sql.l
顺便说一句,我在 .l 文件中有一些用于调试的打印语句。喜欢的可执行文件称为 'trial'.
一旦 bison 解析器得到一个错误(无法解析的东西),它会调用 yyerror
和 "syntax error" 消息,然后进入错误恢复模式。在错误恢复模式下,它会查找右侧带有 error
的规则(错误恢复规则),并将使用其中之一来尝试恢复。如果语法没有错误恢复规则(如你的),那么它将找不到任何错误,因此 yyparse 将简单地 return 而不会尝试读取任何进一步的标记。
如果你想尝试从语法错误中恢复并继续解析替代命令(通常是语法错误部分之后的东西),你需要添加一些错误恢复规则。编写良好的错误恢复规则有些棘手,您可以做很多不同的事情。在你的情况下,你可以尝试的最简单的事情就是在顶层恢复,所以它会丢弃当前命令的其余部分(有语法错误)并尝试找到下一个命令:
START : /* empty */
| START COMMAND
COMMAND : SYSTEM_COMMAND
| DDL_COMMAND
| DML_COMMAND
| error ';'
请注意,我已经摆脱了无用的、多余的 COMMAND_LIST
规则,并将其更改为使用左递归而不是右递归(因为野牛通常使用左递归更好——你应该只使用右递归递归,如果你真的需要它并且知道你在做什么)。
每当您尝试解析命令时,此错误恢复规则将处于活动状态,并且会通过丢弃输入标记直到出现“;”来工作。被看到。一旦它到达';',它会将其视为命令的结尾(它将减少 COMMAND : error ';'
规则,然后是 START : START COMMAND
规则),并继续解析(寻找另一个 COMMAND
)
请注意,一旦错误恢复规则从错误中恢复,解析器将继续解析,如果到达输入末尾而没有进一步的错误,yyparse
将 return 成功.
我正在使用 Flex 和 Bison 来解析简化的 SQL 语法。我 运行 遇到一个问题,即 Flex 将在文件末尾之前停止标记化。当我通过仅包含以下内容的测试用例时:
create database this;
一切正常,令牌 CREATE、DATEABASE 和 ID 已创建。但是当我通过一个仅包含以下内容的测试用例时:
create table this (integer qty);
它将分词到 "qty" 并包括在内,但不会读取 ')' 或 ';'。相反,它会直接跳转到解析并打印 "REJECT"(因为它应该使用不正确的标记)。我怎样才能修复我的代码以标记所有内容,以便我通过 CREATE, TABLE, ID, (, INTEGER, ID, ), ;作为 Bison 的代币?下面是我的 .l .y 和 makefile。预先感谢您的帮助!
sql.l
%{
#include "sql.tab.h"
%}
Delimiter [ \t]
WhiteSpace {Delimiter}+
Letter [A-Za-z]
Name [A-Za-z][A-Za-z0-9_]*
Digit [0-9]
Integer {Digit}+
Float {Digit}+"."{Digit}+
Date (0[1-9]|1[012])"/"(0[1-9]|[12][0-9]|3[01])"/"[0-9][0-9][0-9][0-9]
Other [-!@#$%&+:"~`]
%%
{WhiteSpace} { ; }
[Cc][Rr][Ee][Aa][Tt][Ee] {printf("token: CREATE\n");return(CREATE);}
[Dd][Rr][Oo][Pp] {printf("token: DROP\n");return(DROP);}
[Ll][Oo][Aa][Dd] {printf("token: LOAD\n");return(LOAD);}
[Ss][Aa][Vv][Ee] {printf("token: SAVE\n");return(SAVE);}
[Dd][Aa][Tt][Aa][Bb][Aa][Ss][Ee] {printf("token: DATABASE\n");return(DATABASE);}
[Tt][Aa][Bb][Ll][Ee] {printf("token: TABLE\n");return(TABLE);}
[Ii][Nn][Ss][Ee][Rr][Tt] {printf("token: INSERT\n");return(INSERT);}
[Ii][Nn][Tt][Oo] {printf("token: INTO\n");return(INTO);}
[Ff][Rr][Oo][Mm] {printf("token: FROM\n");return(FROM);}
[Ww][Hh][Ee][Rr][Ee] {printf("token: WHERE\n");return(WHERE);}
[Ss][Ee][Tt] {printf("token: SET\n");return(SET);}
[Dd][Ee][Ll][Ee][Tt][Ee] {printf("token: DELETE\n");return(DELETE);}
[Uu][Pp][Dd][Aa][Tt][Ee] {printf("token: UPDATE\n");return(UPDATE);}
[Ss][Ee][Ll][Ee][Cc][Tt] {printf("token: SELECT\n");return(SELECT);}
[Ww][Ss][Ee][Ll][Ee][Cc][Tt] {printf("token: WSELECT\n");return(WSELECT);}
[Vv][Aa][Ll][Uu][Ee][Ss] {printf("token: VALUES\n");return(VALUES);}
[Ii][Nn][Tt][Ee][Gg][Ee][Rr] {printf("token: INTEGER\n");return(INTEGER);}
[Nn][Uu][Mm][Bb][Ee][Rr] {printf("token: NUMBER\n");return(NUMBER);}
[Cc][Hh][Aa][Rr][Aa][Cc][Tt][Ee][Rr] {printf("token: CHARACTER\n");return(CHARACTER);}
[Dd][Aa][Tt][Ee] {printf("token: DATE\n");return(DATE);}
{Name} {printf("token: ID %s\n", yytext);return(ID);}
"*" {printf("token: *\n");return('*');}
"(" {printf("token: (\n");return('(');}
")" {printf("token: )\n");return(')');}
"," {printf("token: ,\n");return(',');}
"<" {printf("token: LT\n");return(LT);}
">" {printf("token: GT\n");return(GT);}
"<=" {printf("token: LTEQ\n");return(LTEQ);}
">=" {printf("token: GTEQ\n");return(GTEQ);}
"==" {printf("token: EQEQ\n");return(EQEQ);}
"!=" {printf("token: NOTEQ\n");return(NOTEQ);}
";" {printf("token: ;\n");return(';');}
{Float} {printf("token: DEC\n");return(DEC);}
"-"{Float} {printf("token: DEC\n");return(DEC);}
{Integer} {printf("token: INT\n");return(INT);}
"-"{Integer} {printf("token: INT\n");return(INT);}
"'"[ \ta-zA-Z0-9]+"'" {printf("token: STR\n");return(STR);}
{Date} {printf("token: DATETYPE\n");return(DATETYPE);}
\n { ; }
. {printf("Character %s not tokenized\n", yytext);exit(0)
;}
sql.y
%{
#define YYSTYPE char*
#include <stdio.h>
%}
%start START
%token ID
%token CREATE DROP LOAD SAVE DATABASE TABLE INSERT INTO FROM
%token WHERE SET DELETE UPDATE SELECT WSELECT VALUES
%token DEC INT STR DATETYPE DATE NUMBER CHARACTER INTEGER
%token LT GT LTEQ GTEQ EQ EQEQ NOTEQ
%%
START : COMMAND_LIST
COMMAND_LIST : COMMAND COMMAND_LIST2
COMMAND_LIST2 : COMMAND COMMAND_LIST2
| /* EMPTY */
COMMAND : SYSTEM_COMMAND
| DDL_COMMAND
| DML_COMMAND
SYSTEM_COMMAND : CREATE_DATABASE_COMMAND
| DROP_DATABASE_COMMAND
| SAVE_COMMAND
| LOAD_DATABASE_COMMAND
DDL_COMMAND : CREATE_TABLE_COMMAND
| DROP_TABLE_COMMAND
DDL_COMMAND : CREATE_TABLE_COMMAND
| DROP_TABLE_COMMAND
DML_COMMAND : INSERT_INTO_COMMAND
| DELETE_FROM_COMMAND
| UPDATE_COMMAND
| SELECT_COMMAND
| W_SELECT_COMMAND
CREATE_DATABASE_COMMAND : CREATE DATABASE ID ';'
DROP_DATABASE_COMMAND : DROP DATABASE ID ';'
SAVE_COMMAND : SAVE ';'
LOAD_DATABASE_COMMAND : LOAD DATABASE ID ';'
CREATE_TABLE_COMMAND : CREATE TABLE ID '(' FIELD_DEF_LIST ')' ';'
DROP_TABLE_COMMAND : DROP TABLE ID ';'
INSERT_INTO_COMMAND : INSERT INTO ID INSERT_INTO_COMMAND2
INSERT_INTO_COMMAND2 : '(' FIELD_LIST ')' VALUES '(' LITERAL_LIST ')' ';'
| VALUES '(' LITERAL_LIST ')' ';'
DELETE_FROM_COMMAND : DELETE FROM ID DELETE_FROM_COMMAND2
DELETE_FROM_COMMAND2 : WHERE CONDITION ';' | ';'
UPDATE_COMMAND : UPDATE ID SET ID '=' LITERAL UPDATE_COMMAND2
UPDATE_COMMAND2 : ',' ID '=' LITERAL UPDATE_COMMAND2
| UPDATE_COMMAND3
UPDATE_COMMAND3 : WHERE CONDITION ';'
| ';'
SELECT_COMMAND : SELECT '*' FROM ID ';'
W_SELECT_COMMAND : WSELECT W_SELECT_COMMAND2
W_SELECT_COMMAND2 : '*' FROM ID W_SELECT_COMMAND3
| '(' FIELD_LIST ')' FROM ID W_SELECT_COMMAND3
W_SELECT_COMMAND3 : WHERE CONDITION ';'
| ';'
FIELD_DEF_LIST : FIELD_DEF FIELD_DEF_LIST2
FIELD_DEF_LIST2 : ',' FIELD_DEF FIELD_DEF_LIST2
| /* EMPTY */
FIELD_DEF : FIELD_TYPE ID
FIELD_LIST : ID FIELD_LIST2
FIELD_LIST2 : ',' ID FIELD_LIST2
| /* EMPTY */
FIELD_TYPE : INTEGER '(' INT ')'
| INT
| NUMBER '(' INT ')'
| NUMBER '(' INT ',' INT ')'
| NUMBER
| CHARACTER '(' INT ')'
| DATE
LITERAL_LIST : LITERAL LITERAL_LIST2
LITERAL_LIST2 : ',' LITERAL LITERAL_LIST2
| /* EMPTY */
LITERAL : INT
| DEC
| STR
| DATETYPE
CONDITION : ID COMP LITERAL
COMP : LT
| GT
| EQEQ
| LTEQ
| GTEQ
| NOTEQ
%%
main()
{
yyparse();
}
yyerror()
{
printf("REJECT\n");
}
yywrap()
{
printf("ACCEPT\n");
}
makefile
trial: lex.yy.o sql.tab.o
cc -o trial lex.yy.o sql.tab.o
sql.tab.o: sql.tab.c
cc -c sql.tab.c
sql.tab.c: sql.y
bison -d sql.y
lex.yy.o: lex.yy.c
cc -c lex.yy.c
lex.yy.c: sql.l sql.tab.c
flex sql.l
顺便说一句,我在 .l 文件中有一些用于调试的打印语句。喜欢的可执行文件称为 'trial'.
一旦 bison 解析器得到一个错误(无法解析的东西),它会调用 yyerror
和 "syntax error" 消息,然后进入错误恢复模式。在错误恢复模式下,它会查找右侧带有 error
的规则(错误恢复规则),并将使用其中之一来尝试恢复。如果语法没有错误恢复规则(如你的),那么它将找不到任何错误,因此 yyparse 将简单地 return 而不会尝试读取任何进一步的标记。
如果你想尝试从语法错误中恢复并继续解析替代命令(通常是语法错误部分之后的东西),你需要添加一些错误恢复规则。编写良好的错误恢复规则有些棘手,您可以做很多不同的事情。在你的情况下,你可以尝试的最简单的事情就是在顶层恢复,所以它会丢弃当前命令的其余部分(有语法错误)并尝试找到下一个命令:
START : /* empty */
| START COMMAND
COMMAND : SYSTEM_COMMAND
| DDL_COMMAND
| DML_COMMAND
| error ';'
请注意,我已经摆脱了无用的、多余的 COMMAND_LIST
规则,并将其更改为使用左递归而不是右递归(因为野牛通常使用左递归更好——你应该只使用右递归递归,如果你真的需要它并且知道你在做什么)。
每当您尝试解析命令时,此错误恢复规则将处于活动状态,并且会通过丢弃输入标记直到出现“;”来工作。被看到。一旦它到达';',它会将其视为命令的结尾(它将减少 COMMAND : error ';'
规则,然后是 START : START COMMAND
规则),并继续解析(寻找另一个 COMMAND
)
请注意,一旦错误恢复规则从错误中恢复,解析器将继续解析,如果到达输入末尾而没有进一步的错误,yyparse
将 return 成功.