Bison 在规则完成后接受输入

Bison accept input after rule done

我想为单个查询解析文本。此查询将以分号结束。它会像 sql。例如:CREATE TABLE 'somename'; 我的 y 文件是

%{
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>
#include "ast.h"

extern int yylex(void);
extern void yyerror(char const *msg);

QueryNode *queryNode;

%}

%union {
int integer;
char *str;
char chr;
bool boolean;
int intval;
char *strval;
ObjectTypeNode *objectTypeNode;
CreateClauseNode *createClauseNode;
QueryNode *queryNode;
}

%token  NUMBER
%token  INTNUM

%token<str> CREATE_KEYWORD
%token<str> DATABASE_KEYWORD
%token<str> TABLE_KEYWORD
%token<str> LETTER
%token<str> STRING
%token<str> IDENTIFIER
%token<chr> LEFT_BRACKET RIGHT_BRACKET COMMA SEMICOLON EOL

%type<objectTypeNode> object_type
%type<createClauseNode> create_clause
%type<queryNode> query

%start input

%%
input:      SEMICOLON EOL                               { queryNode = NULL; }
        |   query   SEMICOLON EOL                       { queryNode = ; }
        ;

query:  create_clause                                   { $$ = CreateQueryNode(, CREATE_CLAUSE_TYPE); }
        ;

create_clause:  CREATE_KEYWORD  object_type STRING      { $$ = CreateCreateClauseNode(, ); }
                ;

object_type:    DATABASE_KEYWORD                        { $$ = CreateObjectTypeNode(DATABASE_OBJECT); }
            |   TABLE_KEYWORD                           { $$ = CreateObjectTypeNode(TABLE_OBJECT); }
            ;
%%
void yyerror(char const *msg) {
    printf("Error: %s\n", msg);
}

而我的 l 文件是

%{
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdarg.h>
#include "ast.h"
#include "y.tab.h"
%}

%option noyywrap nodefault yylineno case-insensitive

%%
CREATE                  { yylval.strval = "create"; return CREATE_KEYWORD; }
DATABASE                { return DATABASE_KEYWORD; }
TABLE                   { return TABLE_KEYWORD; }
"("                     { return LEFT_BRACKET; }
")"                     { return RIGHT_BRACKET; }
";"                     { return SEMICOLON; }

-?[0-9]+                { yylval.intval = atoi(yytext); return INTNUM; }

L?'(\.|[^\'])+'   |
L?\"(\.|[^\"])*\"     { yylval.strval = yytext;   return STRING; }

[a-zA-Z]+[0-9]*         { return IDENTIFIER; }
[a-zA-Z]+               { return LETTER; }
[\n]                    { printf("eol\n"); return EOL; }
[ \t\f\v]               { ; }

.                       { return *yytext; }
%%

我在其他主函数中使用了 yyparse() 函数。 main.c 文件是

#include <stdio.h>
#include <stdlib.h>
#include "ast.h"
#include "y.tab.h"

extern QueryNode *queryNode;

int main(int argc, char *argv[]) {
    int result = yyparse();
    if(result == 0 && queryNode != NULL) {
        printf("AST created\n");
    } else {
        printf("Problem!\n");
    }
    return 0;
}

当我输入 CREATE TABLE 'testo'; 时,yyparse 不会终止,程序会在 int result = yyparse(); 行等待。我该如何解决?我使用 flex 和 bison。我想用这个输入终止。

在这个问题的原始版本中,语法规范中的主要规则是:

输入:SEMICOLON { queryNode = NULL; YY接受; } |查询分号 { queryNode = $1; YY接受; } ;

正如我在这个答案的原始版本中所说,这些规则保证一旦遇到分号,yacc 就会接受后跟分号的查询,因为 YYACCEPT 操作:

yacc "accepts" because you used YYACCEPT in an action. YYACCEPT means "as soon as this production is recognised, accept the input even if it has not been fully consumed." So it is doing what you asked it to.

然后我建议删除 YYACCEPT 操作,以便解析器不会 return 直到词法分析器发出输入结束信号:

If you only want to accept input if the entire input matches the grammar, just don't call YYACCEPT. Yacc will automatically accept if the start production matches and the next token is the end-of-input marker.

但这当然不会在遇到换行符时神奇地导致阅读停止。它所做的只是确保如果 整个输入 是单个命令,它将被接受,否则将被拒绝。但由于它正在检查以确保命令后没有任何内容,因此它将继续请求输入,直到获得一些输入。

如果您希望词法分析器只读取一行必须是有效命令的行,您可以通过从解析器操作中删除 YYACCEPT 并让扫描器 return 结束来轻松地做到这一点看到换行符时的文件指示:

\n    { return 0; }

(返回零是扫描仪发出输入结束信号的方式。)

如果您真正想要的是构建一个读取多行输入、独立解析每一行并在每一行之后 returning 的程序,那么上述解决方案就可以正常工作。

您也可以在解析器中玩游戏,就像您的新提案一样,但是让扫描器 return 在看到换行符时使用换行符标记。然后,您可以在收到换行符时接受或拒绝输入,使用 YYACCEPTYYABORTerror 产生式:

input: SEMICOLON EOL              { queryNode = NULL; YYACCEPT; }
     | query SEMICOLON EOL        { queryNode = ;   YYACCEPT; }
     | query error EOL            { YYABORT; }
     ;

为了在遇到语法错误时刷新该行的其余部分,必须产生错误。否则,对解析器的下一次调用将从产生错误的行的中间开始,在一个稍微不可预测的点(因为这将取决于解析器在发出错误信号时是否持有先行标记。)

虽然这个解决方案确实有一些优点,但它比读取换行符时 returns 0 的解决方案稍微复杂一些。所以很难证明额外的复杂性是合理的。

无论如何,这两种解决方案都不是真正理想的。在某些时候,您几乎肯定需要处理太长而无法方便地在一行中键入的输入。


现在您已经包含了完整的扫描器,我可以看出您将遇到另一个严重的问题,因为您在将令牌字符串存储到 yylval 之前没有复制它。保留令牌的地址(它是扫描器内部输入缓冲区的一部分)是不正确的;该缓冲区将在没有警告的情况下被扫描器更改(例如,当它需要更多输入时)。特别是,一旦扫描器开始处理下一个令牌,它将覆盖它之前用于终止令牌的 NUL 字节,这将明显影响令牌的字符串更改为两个(或更多)连续的令牌。您可以在此站点上找到有关此问题的大量讨论。