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} 将匹配 hahapapalala。但它不会匹配 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 确保所有可能性都符合某些规则)也可以帮助您捕获简单的模式错误。