如何根据以下内容减少解析器堆栈或“取消”当前令牌?

时间:2016-02-16 11:19:29

标签: parsing compiler-construction grammar bison

鉴于以下语言描述为:

  • 正式:(identifier operator identifier+)*
  • 用简单的英语:零或多个操作写为标识符(左值),然后是运算符,然后是一个或多个标识符(右值)

在给定任意运算符@的情况下,该语言的一系列操作示例:

A @ B C X @ Y

空白并不重要,也可以更清楚地写成:

A @ B C
X @ Y

您如何使用类似yacc的LALR解析器解析它?

到目前为止我尝试了什么

我知道如何解析显式分隔的操作,比如A @ B C ; X @ Y,但我想知道解析上面的输入是否可行以及如何解析。以下是使用Flex / Bison的(非功能性)最小示例。

lex.l:

%{
#include "y.tab.h"
%}

%option noyywrap
%option yylineno

%%
[a-zA-Z][a-zA-Z0-9_]*   { return ID; }
@                       { return OP; }
[ \t\r\n]+              ; /* ignore whitespace */
.                       { return ERROR; } /* any other character causes parse error */
%%

yacc.y:

%{
#include <stdio.h>

extern int yylineno;
void yyerror(const char *str);
int yylex();
%}

%define parse.lac full
%define parse.error verbose

%token ID OP ERROR
%left OP

%start opdefs

%%
opright:
       | opright ID
       ;

opdef: ID OP ID opright
     ;

opdefs:
      | opdefs opdef
      ;
%%

void yyerror(const char *str) {
    fprintf(stderr, "error@%d: %s\n", yylineno, str);
}

int main(int argc, char *argv[]) {
    yyparse();
}

构建:$ flex lex.l && yacc -d yacc.y --report=all --verbose && gcc lex.yy.c y.tab.c

问题:我无法将 的解析器包含在 rvalue 的下一个左值标识符中第一次行动。

$ ./a.out
A @ B C X @ Y
error@1: syntax error, unexpected OP, expecting $end or ID

上述内容始终解析为:reduce(A @ B reduce(C X)) @ Y

我感觉我必须以某种方式在前瞻标记上设置条件,如果它是操作符,则不应移动最后一个标识符并且应当减少当前堆栈:

A @ B C X @ Y
        ^ *    // ^: current, *: lookahead
-> reduce 'A @ B C' !
-> shift 'X' !

我尝试了所有类型的运算符优先级安排但无法使其正常工作。

我愿意接受不适用于Bison的解决方案。

2 个答案:

答案 0 :(得分:3)

该语言的天真语法是LALR(2),而野牛不生成LALR(2)解析器。

任何LALR(2)语法都可以通过机械修改来生成带有兼容解析树的LALR(1)语法,但我不知道有任何自动工具可以做到这一点。

手动进行转换是可能但很烦人,但请注意,您需要调整操作以恢复正确的解析树:

%{
  typedef struct IdList  { char* id; struct IdList* next; };
  typedef struct Def     { char* lhs; IdList* rhs; };
  typedef struct DefList { Def* def; struct DefList* next; };
%}
union {
  Def*     def;
  DefList* defs;
  char*    id;
}
%type <def>  ophead
%type <defs> opdefs
%token <id>   ID

%%

prog  : opdefs        { $1->def->rhs = IdList_reverse($1->def->rhs);
                        DefList_show(DefList_reverse($1)); }
ophead: ID '@' ID     { $$ = Def_new($1);
                        $$->rhs = IdList_push($$->rhs, $3); } 
opdefs: ophead        { $$ = DefList_push(NULL, $1); }
      | opdefs ID     { $1->def->rhs = IdList_push($1->def->rhs, $2); }
      | opdefs ophead { $1->def->rhs = IdList_reverse($1->def->rhs);
                        $$ = DefList_push($1, $2); }

具有讽刺意味的是,这个确切的问题是bison本身的一部分,因为制作不需要;终止符。 Bison使用它自己生成一个解析器,它解决了词法分析器中的这个问题,而不是如上所述跳过循环。在词法分析器中,一旦找到ID,扫描将继续到下一个非空白字符。如果那是:,则词法分析器返回identifier-definition标记;否则,非空白字符将返回到输入流,并返回一个普通的identifier标记。

这是在词法分析器中实现它的一种方法:

%x SEEK_AT
%%
  /* See below for explanation, if needed */
  static int deferred_eof = 0;
  if (deferred_eof) { deferred_eof = 0; return 0; }
[[:alpha:]][[:alnum:]_]*  yylval = strdup(yytext); BEGIN(SEEK_AT);
[[:space:]]+              ;                /* ignore whitespace */
   /* Could be other rules here */
.                         return *yytext;  /* Let the parser handle errors */

<SEEK_AT>{
  [[:space:]]+            ;                /* ignore whitespace */
  "@"                     BEGIN(INITIAL); return ID_AT;
  .                       BEGIN(INITIAL); yyless(0); return ID;
  <EOF>                   BEGIN(INITIAL); deferred_eof = 1; return ID;
}

SEEK_AT开始条件下,我们只对@感兴趣。如果我们找到一个,那么ID就是def的开头,我们返回正确的令牌类型。如果我们找到任何其他内容(除了空格),我们使用yyless将字符返回到输入流,并返回ID令牌类型。请注意,yylval的初始扫描已设置ID,因此无需担心此问题。

上述代码中唯一复杂的部分是EOF处理。检测到EOF后,无法将其重新插入输入流,无论是yyless还是unputc。让扫描仪再次读取EOF也不合法。所以需要充分处理。不幸的是,在SEEK_AT启动条件下,完全处理EOF需要发送两个令牌:首先是已检测到的ID令牌,然后是yyparse将识别为结束的0输入。如果没有推送解析器,我们就无法通过单个扫描程序操作发送两个令牌,因此我们需要注册已收到EOF的事实,并在下次调用扫描程序时检查该事件。

在第一个规则插入yylex函数顶部之前缩进代码,因此它可以声明局部变量并执行扫描开始之前需要完成的任何操作。如上所述,此词法分析器不可重入,但可重新启动,因为if (deferred_eof)操作中的持久状态已重置。要使其可重入,您只需将deferred_eof置于yystate结构中,而不是将其设置为静态本地。

答案 1 :(得分:0)

关注rici有用的评论和回答,以下是我提出的建议:

lex.l:

%{
#include "y.tab.h"
%}

%option noyywrap
%option yylineno

%%
[a-zA-Z][a-zA-Z0-9_]*   { yylval.a = strdup(yytext); return ID; }
@                       { return OP; }
[ \t\r\n]+              ; /* ignore whitespace */
.                       { return ERROR; } /* any other character causes parse error */
%%

yacc.y:

%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

extern int yylineno;
void yyerror(const char *str);
int yylex();


#define STR_OP    " @ "
#define STR_SPACE " "

char *concat3(const char *, const char *, const char *);

struct oplist {
    char **ops;
    size_t capacity, count;
} my_oplist = { NULL, 0, 0 };

int oplist_append(struct oplist *, char *);
void oplist_clear(struct oplist *);
void oplist_dump(struct oplist *);
%}

%union {
    char *a;
}

%define parse.lac full
%define parse.error verbose

%token ID OP END ERROR

%start input

%%

opbase: ID OP ID {
         char *s = concat3($<a>1, STR_OP, $<a>3);
         free($<a>1);
         free($<a>3);
         assert(s && "opbase: allocation failed");

         $<a>$ = s;
     }
     ;

ops: opbase {
       $<a>$ = $<a>1;
   }
   | ops opbase {
       int r = oplist_append(&my_oplist, $<a>1);
       assert(r == 0 && "ops: allocation failed");

       $<a>$ = $<a>2;
   }
   | ops ID {
       char *s = concat3($<a>1, STR_SPACE, $<a>2);
       free($<a>1);
       free($<a>2);
       assert(s && "ops: allocation failed");

       $<a>$ = s;
   }
   ;

input: ops {
         int r = oplist_append(&my_oplist, $<a>1);
         assert(r == 0 && "input: allocation failed");
     }
     ;       
%%

char *concat3(const char *s1, const char *s2, const char *s3) {
    size_t len = strlen(s1) + strlen(s2) + strlen(s3);
    char *s = malloc(len + 1);
    if (!s)
        goto concat3__end;

    sprintf(s, "%s%s%s", s1, s2, s3);

concat3__end:
    return s;
}


int oplist_append(struct oplist *oplist, char *op) {
    if (oplist->count == oplist->capacity) {  
        char **ops = realloc(oplist->ops, (oplist->capacity + 32) * sizeof(char *));
        if (!ops)
            return 1;

        oplist->ops = ops;
        oplist->capacity += 32;
    } 

    oplist->ops[oplist->count++] = op;
    return 0;
}

void oplist_clear(struct oplist *oplist) {
    if (oplist->count > 0) {
        for (size_t i = 0; i < oplist->count; ++i)
            free(oplist->ops[i]);
        oplist->count = 0;
    }

    if (oplist->capacity > 0) {
        free(oplist->ops);
        oplist->capacity = 0;
    }
}

void oplist_dump(struct oplist *oplist) {
    for (size_t i = 0; i < oplist->count; ++i)
        printf("%2zu: '%s'\n", i, oplist->ops[i]);
}


void yyerror(const char *str) {
    fprintf(stderr, "error@%d: %s\n", yylineno, str);
}

int main(int argc, char *argv[]) {
    yyparse();

    oplist_dump(&my_oplist);
    oplist_clear(&my_oplist);
}

使用A @ B C X @ Y输出:

 0: 'A @ B C'
 1: 'X @ Y'