Flex/Bison 有时会漏掉 Re
Flex/Bison sometimes misses Re
我使用 flex/bison 构建了一个 CLI,我发现 flex 有时无法获取令牌。
我的 .l 看起来像这样:
%{
#include <stdio.h>
#include <string.h>
#include "hmd.tab.h"
#include "cmd.h"
%}
%option debug
%option verbose
%option backup
%option noyywrap nounput noinput
%option reentrant bison-bridge
digit [0-9]
integer [+-]?{digit}+
uinteger {digit}+
real [+-]?({digit}+[.]{digit}*)|({digit}*[.]{digit}+)
exp [+-]?({integer}|{real})[eE]-?{integer}
alpha [:alpha:]+
any [^[:space:]](.|\n)+
printing [^[:space:]]+
%x ID ACTION ZONE_FIELD VALUE
%%
/*subsystems*/
<INITIAL>zone {
BEGIN(ID);
printf("ZONE '%s'\n", yytext);
return (cmd_sys_zone);
}
<INITIAL>device {
return (cmd_sys_device);
}
<INITIAL>system {
return (cmd_sys_system);
}
<INITIAL>help {
return (cmd_sys_help);
}
<INITIAL>ver|version {
return (cmd_sys_ver);
}
<ID>{uinteger} {
printf("ID '%s'\n", yytext);
yylval->number = strtoll(yytext, NULL, 0);
BEGIN (ACTION);
return (cmd_id);
}
/*actions*/
<ACTION>set {
BEGIN (ZONE_FIELD);
printf("SET '%s'\n", yytext);
return (cmd_action_set);
}
<ACTION>get {
BEGIN (ZONE_FIELD);
return (cmd_action_get);
}
<ACTION>start {
BEGIN (ZONE_FIELD);
return (cmd_action_start);
}
<ACTION>stop {
BEGIN (ZONE_FIELD);
return (cmd_action_stop);
}
<ZONE_FIELD>{alpha} {
printf("ZONE_FIELD '%s'\n", yytext);
yylval->name = strdup(yytext);
BEGIN (VALUE);
return (cmd_field);
}
<VALUE>{any} {
yylval->name = strdup(yytext);
printf("VALUE '%s'\n", yytext);
return(cmd_value);
}
%%
int cmd_parse(cmd_t *command) {
yyscan_t scanner;
YY_BUFFER_STATE buffer;
int ret_val;
ret_val = 0;
if ((ret_val = yylex_init(&scanner)) != 0) {
goto exit_point;
}
printf("INPUT '%s'\n", command->buffer);
buffer = yy_scan_buffer(command->buffer, command->len, scanner);
yyparse(command, scanner);
yy_delete_buffer(buffer, scanner);
yylex_destroy(scanner);
exit_point:
return 0;
}
我的 .y 看起来像这样:
%{
#define YYDEBUG 1
#include <stdio.h>
#include <stdint.h>
#include "cmd.h"
#include "hmd.tab.h"
int yylex();
int yyerror(void *userdata, void *scanner, const char *s);
%}
%debug
%define api.pure
%define parse.error verbose
/*System tokens*/
%token cmd_sys_zone cmd_sys_device cmd_sys_system cmd_sys_ver cmd_sys_help
%token cmd_num cmd_unum cmd_real cmd_other
/*ID token*/
%token cmd_id
/*Fields*/
%token cmd_field
/*Action tokens*/
%token cmd_action_set cmd_action_get cmd_action_start cmd_action_stop
/*Value*/
%token cmd_value
%type <number> cmd_num
%type <unumber> cmd_unum
%type <real> cmd_real
%type <unumber> cmd_id
%type <name> cmd_field
%type <name> cmd_value
%type <name> cmd_other
%destructor {
if ($$ == NULL) {
free($$);
}
} <name>
%union {
char *name;
int64_t number;
uint64_t unumber;
double real;
}
%parse-param {void *user_data}
%param {void *scanner}
%%
prog:
stmts
;
stmts:
| stmt stmts
stmt:
cmd_sys_zone cmd_id cmd_action_set cmd_field cmd_value {
cmd_zone_set(user_data, , , );
cmd_free();
cmd_free();
} |
cmd_sys_zone cmd_id cmd_action_get cmd_field {
cmd_zone_get(, );
cmd_free();
} |
cmd_sys_ver {
cmd_ver(user_data);
} |
cmd_sys_help {
cmd_help();
} |
cmd_other {
yyerror(user_data, NULL, );
cmd_free();
}
%%
int yyerror(void *userdata, void *scanner, const char *s)
{
(void) scanner;
cmd_t *cmd;
cmd = (cmd_t*) userdata;
cmd->response_len = sprintf(cmd->response, "ERR: %s\r\n", s);
return 0;}
两个相似的测试用例:
INPUT 'zone 2 set haha some good result
'
ZONE 'zone'
ID '2'
SET 'set'
ZONE_FIELD 'haha'
VALUE 'some good result
'
2022-05-17T04:31:43 I CMD_SET_ZONE '2' 'haha' 'some good result /*Output of the handler*/
'
INPUT 'zone 2 set blab some bad result
'
ZONE 'zone'
ID '2'
SET 'set'
bZONE_FIELD 'la' /*b is missed by Flex*/
VALUE 'b some bad result /*That b should be part of ZONE_FIELD*/
'
2022-05-17T04:31:59 I CMD_SET_ZONE '2' 'la' 'b some bad result /*Output of the handler*/
'
如您所见,我向解析器提供了几乎相同数量的数据,但结果却不同。第二次,有一堆字节不匹配,整个语法崩溃
如果您在生成扫描器时使用 --debug
(或 -d
)command-line 标志,flex 将插入记录所有规则匹配(以及某些其他重要事件)的代码。在像您这样的可重入扫描器中,您还需要插入对 yyset_debug(1, scanner);
的调用以启用日志;在 non-reentrant 扫描仪中,默认情况下启用日志。与在扫描器操作中插入自己的 printf
调用相比,这通常可以为您提供更好的调试信息,而且工作量要少得多。 (尤其是到了关闭它的时候。)
我怀疑它会为您提供足够的信息来查看您代码中的拼写错误,即定义
alpha [:alpha:]+
而不是正确的:
alpha [[:alpha:]]+
如所写,{alpha}
将匹配 haha
、papa
和 lala
。但它不会匹配 blabla
,因为 b
不是字母 ahlp
之一,也不是冒号。启用调试(如上所述)后,您会在输出中看到类似这样的内容:
--accepting rule at line 85 ("set")
SET 'set'
--accepting default rule (" ")
--accepting default rule ("b")
--accepting rule at line 103 ("la")
bZONE_FIELD 'la'
除了显示 b
与 {alpha}
不匹配外,它还表明您没有正确处理空格;可能,您应该添加一个匹配并忽略水平空格(或者可能是所有空格)的模式:
<*>[ \t]+ ;
我还建议不要依赖自动回退规则。编写匹配所有可能性的模式集(并使用 %option nodefault
确保所有可能性都符合某些规则)也可以帮助您捕获简单的模式错误。
我使用 flex/bison 构建了一个 CLI,我发现 flex 有时无法获取令牌。
我的 .l 看起来像这样:
%{
#include <stdio.h>
#include <string.h>
#include "hmd.tab.h"
#include "cmd.h"
%}
%option debug
%option verbose
%option backup
%option noyywrap nounput noinput
%option reentrant bison-bridge
digit [0-9]
integer [+-]?{digit}+
uinteger {digit}+
real [+-]?({digit}+[.]{digit}*)|({digit}*[.]{digit}+)
exp [+-]?({integer}|{real})[eE]-?{integer}
alpha [:alpha:]+
any [^[:space:]](.|\n)+
printing [^[:space:]]+
%x ID ACTION ZONE_FIELD VALUE
%%
/*subsystems*/
<INITIAL>zone {
BEGIN(ID);
printf("ZONE '%s'\n", yytext);
return (cmd_sys_zone);
}
<INITIAL>device {
return (cmd_sys_device);
}
<INITIAL>system {
return (cmd_sys_system);
}
<INITIAL>help {
return (cmd_sys_help);
}
<INITIAL>ver|version {
return (cmd_sys_ver);
}
<ID>{uinteger} {
printf("ID '%s'\n", yytext);
yylval->number = strtoll(yytext, NULL, 0);
BEGIN (ACTION);
return (cmd_id);
}
/*actions*/
<ACTION>set {
BEGIN (ZONE_FIELD);
printf("SET '%s'\n", yytext);
return (cmd_action_set);
}
<ACTION>get {
BEGIN (ZONE_FIELD);
return (cmd_action_get);
}
<ACTION>start {
BEGIN (ZONE_FIELD);
return (cmd_action_start);
}
<ACTION>stop {
BEGIN (ZONE_FIELD);
return (cmd_action_stop);
}
<ZONE_FIELD>{alpha} {
printf("ZONE_FIELD '%s'\n", yytext);
yylval->name = strdup(yytext);
BEGIN (VALUE);
return (cmd_field);
}
<VALUE>{any} {
yylval->name = strdup(yytext);
printf("VALUE '%s'\n", yytext);
return(cmd_value);
}
%%
int cmd_parse(cmd_t *command) {
yyscan_t scanner;
YY_BUFFER_STATE buffer;
int ret_val;
ret_val = 0;
if ((ret_val = yylex_init(&scanner)) != 0) {
goto exit_point;
}
printf("INPUT '%s'\n", command->buffer);
buffer = yy_scan_buffer(command->buffer, command->len, scanner);
yyparse(command, scanner);
yy_delete_buffer(buffer, scanner);
yylex_destroy(scanner);
exit_point:
return 0;
}
我的 .y 看起来像这样:
%{
#define YYDEBUG 1
#include <stdio.h>
#include <stdint.h>
#include "cmd.h"
#include "hmd.tab.h"
int yylex();
int yyerror(void *userdata, void *scanner, const char *s);
%}
%debug
%define api.pure
%define parse.error verbose
/*System tokens*/
%token cmd_sys_zone cmd_sys_device cmd_sys_system cmd_sys_ver cmd_sys_help
%token cmd_num cmd_unum cmd_real cmd_other
/*ID token*/
%token cmd_id
/*Fields*/
%token cmd_field
/*Action tokens*/
%token cmd_action_set cmd_action_get cmd_action_start cmd_action_stop
/*Value*/
%token cmd_value
%type <number> cmd_num
%type <unumber> cmd_unum
%type <real> cmd_real
%type <unumber> cmd_id
%type <name> cmd_field
%type <name> cmd_value
%type <name> cmd_other
%destructor {
if ($$ == NULL) {
free($$);
}
} <name>
%union {
char *name;
int64_t number;
uint64_t unumber;
double real;
}
%parse-param {void *user_data}
%param {void *scanner}
%%
prog:
stmts
;
stmts:
| stmt stmts
stmt:
cmd_sys_zone cmd_id cmd_action_set cmd_field cmd_value {
cmd_zone_set(user_data, , , );
cmd_free();
cmd_free();
} |
cmd_sys_zone cmd_id cmd_action_get cmd_field {
cmd_zone_get(, );
cmd_free();
} |
cmd_sys_ver {
cmd_ver(user_data);
} |
cmd_sys_help {
cmd_help();
} |
cmd_other {
yyerror(user_data, NULL, );
cmd_free();
}
%%
int yyerror(void *userdata, void *scanner, const char *s)
{
(void) scanner;
cmd_t *cmd;
cmd = (cmd_t*) userdata;
cmd->response_len = sprintf(cmd->response, "ERR: %s\r\n", s);
return 0;}
两个相似的测试用例:
INPUT 'zone 2 set haha some good result
'
ZONE 'zone'
ID '2'
SET 'set'
ZONE_FIELD 'haha'
VALUE 'some good result
'
2022-05-17T04:31:43 I CMD_SET_ZONE '2' 'haha' 'some good result /*Output of the handler*/
'
INPUT 'zone 2 set blab some bad result
'
ZONE 'zone'
ID '2'
SET 'set'
bZONE_FIELD 'la' /*b is missed by Flex*/
VALUE 'b some bad result /*That b should be part of ZONE_FIELD*/
'
2022-05-17T04:31:59 I CMD_SET_ZONE '2' 'la' 'b some bad result /*Output of the handler*/
'
如您所见,我向解析器提供了几乎相同数量的数据,但结果却不同。第二次,有一堆字节不匹配,整个语法崩溃
如果您在生成扫描器时使用 --debug
(或 -d
)command-line 标志,flex 将插入记录所有规则匹配(以及某些其他重要事件)的代码。在像您这样的可重入扫描器中,您还需要插入对 yyset_debug(1, scanner);
的调用以启用日志;在 non-reentrant 扫描仪中,默认情况下启用日志。与在扫描器操作中插入自己的 printf
调用相比,这通常可以为您提供更好的调试信息,而且工作量要少得多。 (尤其是到了关闭它的时候。)
我怀疑它会为您提供足够的信息来查看您代码中的拼写错误,即定义
alpha [:alpha:]+
而不是正确的:
alpha [[:alpha:]]+
如所写,{alpha}
将匹配 haha
、papa
和 lala
。但它不会匹配 blabla
,因为 b
不是字母 ahlp
之一,也不是冒号。启用调试(如上所述)后,您会在输出中看到类似这样的内容:
--accepting rule at line 85 ("set")
SET 'set'
--accepting default rule (" ")
--accepting default rule ("b")
--accepting rule at line 103 ("la")
bZONE_FIELD 'la'
除了显示 b
与 {alpha}
不匹配外,它还表明您没有正确处理空格;可能,您应该添加一个匹配并忽略水平空格(或者可能是所有空格)的模式:
<*>[ \t]+ ;
我还建议不要依赖自动回退规则。编写匹配所有可能性的模式集(并使用 %option nodefault
确保所有可能性都符合某些规则)也可以帮助您捕获简单的模式错误。