Parsing 命题逻辑解析器与IF-THEN-ELSE三元算子的冲突

Parsing 命题逻辑解析器与IF-THEN-ELSE三元算子的冲突,parsing,bison,yacc,shift-reduce-conflict,ml-yacc,Parsing,Bison,Yacc,Shift Reduce Conflict,Ml Yacc,我想实现命题逻辑的解析器,该解析器具有以下按优先级降序排列的运算符: 不是p p和q p或q 如果p那么q p-IFF-q 如果p,那么q,否则r 主要问题在于IF-THEN-ELSE操作符。没有它,我就能正确地写语法。目前,我的yacc文件看起来像 %term PARSEPROG | AND | NOT | OR | IF | THEN | ELSE | IFF | LPAREN | RPAREN | ATOM of string | SEMICOLON | EOF %nonterm

我想实现命题逻辑的解析器,该解析器具有以下按优先级降序排列的运算符:

不是p p和q p或q 如果p那么q p-IFF-q 如果p,那么q,否则r 主要问题在于IF-THEN-ELSE操作符。没有它,我就能正确地写语法。目前,我的yacc文件看起来像

%term
    PARSEPROG | AND | NOT | OR | IF | THEN | ELSE | IFF | LPAREN | RPAREN | ATOM of string | SEMICOLON | EOF

%nonterm
    start of Absyn.program | EXP of Absyn.declaration

%start start
%eop EOF SEMICOLON
%pos int
%verbose

%right ELSE
%right IFF
%right THEN
%left AND OR
%left NOT

%name Fol

%noshift EOF

%%

start : PARSEPROG EXP (Absyn.PROGRAM(EXP))


EXP: ATOM ( Absyn.LITERAL(ATOM) )
    | LPAREN EXP RPAREN (EXP)
    | EXP AND EXP ( Absyn.CONJ(EXP1, EXP2) )
    | EXP OR EXP ( Absyn.DISJ(EXP1, EXP2) )
    | IF EXP THEN EXP ELSE EXP ( Absyn.IFTHENELSE(EXP1, EXP2, EXP3) )
    | IF EXP THEN EXP ( Absyn.IMPLI(EXP1, EXP2) )
    | EXP IFF EXP ( Absyn.BIIMPLI(EXP1, EXP2) )
    | NOT EXP ( Absyn.NEGATION(EXP) )
但我似乎没有正确的想法如何消除和减少轮班冲突。正确解析的一些示例包括:

如果a然后如果b然后c\uuuuuuuuuuuuuuuuuuuuuua->b->c 如果a,那么如果b,那么c,如果e或f,如果e,那么b->c,de/\f
任何帮助/指示都会非常有用。谢谢。

处理此需求的一个相对简单的方法是创建一个过度生成的语法,然后拒绝我们不希望使用语义的语法

具体来说,我们使用如下语法:

expr : expr AND expr
     | expr OR expr
     | expr IFF expr
     | IF expr THEN expr
     | expr ELSE expr   /* generates some sentences we don't want! */
     | '(' expr ')'
     | ATOM
     ;
请注意,ELSE只是一个普通的低优先级运算符:任何表达式都可以后跟ELSE和另一个表达式。但是在语义规则中,我们实现了一个检查,确保ELSE的左侧是IF表达式。如果不是,那么我们将提出一个错误

这种方法不仅易于实现,而且便于最终用户记录,因此易于理解和使用。最终用户可以接受这样一个简单的理论,即ELSE只是另一个优先级非常低的二进制运算符,以及一条当它不与IF/THEN组合时拒绝它的规则

下面是我用C语言编写的使用经典Yacc的完整程序的测试运行:

$ echo 'a AND b OR c' | ./ifelse 
OR(AND(a, b), c)
$ echo 'a OR b AND c' | ./ifelse 
OR(a, AND(b, c))
$ echo 'IF a THEN b' | ./ifelse 
IF(a, b)
普通单曲IF/ELSE符合我们的要求:

$ echo 'IF a THEN b ELSE c' | ./ifelse 
IFELSE(a, b, c)
你追求的关键是:

$ echo 'IF a THEN IF x THEN y ELSE c' | ./ifelse
IFELSE(a, IF(x, y), c)
正确地说,ELSE与外部IF一起使用。以下是bad ELSE的错误案例:

该程序通过构建一个AST,然后遍历它以前缀FX,Y语法打印它来显示它正在使用的解析。作为一名Lisp程序员,我不得不稍微抑制一下呕吐反射

AST结构还允许ELSE规则检测其左参数是否是正确类型的表达式

注意:您可能希望处理以下内容,但事实并非如此:

$ echo 'IF a THEN IF x THEN y ELSE z ELSE w' | ./ifelse 
error: ELSE must pair with IF
<invalid>
这是:

%{

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

typedef struct astnode {
  int op;
  struct astnode *left, *right;
  char *lexeme;
} astnode;

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

void *xmalloc(size_t size)
{
  void *p = malloc(size);
  if (p)
    return p;

  yyerror("out of memory");
  abort();
}

char *xstrdup(char *in)
{
  size_t sz = strlen(in) + 1;
  char *out = xmalloc(sz);
  return strcpy(out, in);
}

astnode *astnode_cons(int op, astnode *left, astnode *right, char *lexeme)
{
  astnode *a = xmalloc(sizeof *a);
  a->op = op;
  a->left = left;
  a->right = right;
  a->lexeme = lexeme;
  return a;
}

int yylex(void);

astnode *ast;

%}

%union {
  astnode *node;
  char *lexeme;
  int none;
}

%token<none> '(' ')'

%token<lexeme> ATOM

%left<none> ELSE
%left<none> IF THEN
%right<none> IFF
%left<none> OR
%left<none> AND

%type<node> top expr

%%

top : expr { ast = $1; }

expr : expr AND expr
       { $$ = astnode_cons(AND, $1, $3, 0); }
     | expr OR expr
       { $$ = astnode_cons(OR, $1, $3, 0); }
     | expr IFF expr
       { $$ = astnode_cons(IFF, $1, $3, 0); }
     | IF expr THEN expr
       { $$ = astnode_cons(IF, $2, $4, 0); }
     | expr ELSE expr
       { if ($1->op != IF)
         { yyerror("ELSE must pair with IF");
           $$ = 0; }
         else
         { $$ = astnode_cons(ELSE, $1, $3, 0); } }
     | '(' expr ')'
       { $$ = $2; }
     | ATOM
       { $$ = astnode_cons(ATOM, 0, 0, $1); }
     ;

%%

int yylex(void)
{
  int ch;
  char tok[64], *te = tok + sizeof(tok), *tp = tok;

  while ((ch = getchar()) != EOF) {
    if (isalnum((unsigned char) ch)) {
      if (tp >= te - 1)
        yyerror("token overflow");
      *tp++ = ch;
    } else if (isspace(ch)) {
      if (tp > tok)
        break;
    } else if (ch == '(' || ch == ')') {
      if (tp == tok)
        return ch;
      ungetc(ch, stdin);
      break;
    } else {
      yyerror("invalid character");
    }
  }

  if (tp > tok) {
    yylval.none = 0;
    *tp++ = 0;
    if (strcmp(tok, "AND") == 0)
      return AND;
    if (strcmp(tok, "OR") == 0)
      return OR;
    if (strcmp(tok, "IFF") == 0)
      return IFF;
    if (strcmp(tok, "IF") == 0)
      return IF;
    if (strcmp(tok, "THEN") == 0)
      return THEN;
    if (strcmp(tok, "ELSE") == 0)
      return ELSE;
    yylval.lexeme = xstrdup(tok);
    return ATOM;
  }

  return 0;
}

void ast_print(astnode *a)
{
  if (a == 0) {
    fputs("<invalid>", stdout);
    return;
  }

  switch (a->op) {
  case ATOM:
    fputs(a->lexeme, stdout);
    break;
  case AND:
  case OR:
  case IF:
  case IFF:
    switch (a->op) {
    case AND:
      fputs("AND(", stdout);
      break;
    case OR:
      fputs("OR(", stdout);
      break;
    case IF:
      fputs("IF(", stdout);
      break;
    case IFF:
      fputs("IFF(", stdout);
      break;
    }
    ast_print(a->left);
    fputs(", ", stdout);
    ast_print(a->right);
    putc(')', stdout);
    break;
  case ELSE:
    fputs("IFELSE(", stdout);
    ast_print(a->left->left);
    fputs(", ", stdout);
    ast_print(a->left->right);
    fputs(", ", stdout);
    ast_print(a->right);
    putc(')', stdout);
    break;
  }
}

int main(void)
{
   yyparse();
   ast_print(ast);
   puts("");
   return 0;
}
让我的Yacc坐起来乞讨 我比以往任何时候都更加确信,如果可能的话,正确的方法是GLR语法。然而,受@Kaz的启发,我使用LALR1语法生成了下面的yacc/bison语法,甚至不使用优先级声明

当然,它会作弊,因为这个问题无法用LALR1语法解决。以适当的间隔,它遍历构建的IF-THEN和IF-THEN-ELSE表达式树,并根据需要移动ELSE子句

需要重新检查可能的运动的节点被赋予AST nodetype IFSEQ,ELSE子句使用传统的最紧密匹配语法,使用经典的匹配if/不匹配if语法。完全匹配的IF-THEN-ELSE子句不需要重新排列;树重写将应用于与右操作数不匹配的第一个其他操作数关联的表达式(如果有)。保持IF表达式的完全匹配前缀与需要重新排列的尾部分开几乎需要重复一些规则;几乎重复的规则的不同之处在于,它们的操作直接生成三元节点,而不是IFSEQ节点

为了正确回答这个问题,还需要重新排列一些IFF节点,因为IFF绑定比THEN子句弱,比ELSE子句紧。我认为这意味着:

IF p THEN q IFF IF r THEN s  ==>  ((p → q) ↔ (r → s))
IF p THEN q IFF r ELSE s IFF t ==> (p ? (q ↔ r) : (s ↔ t))
IF p THEN q IFF IF r THEN s ELSE t IFF u ==> (p ? (q ↔ (r → s)) : (t ↔ u))
虽然我不确定这是什么要求,特别是最后一个,我真的不认为这是一个好主意。在下面的语法中,如果希望IFF应用于if p THEN q子表达式,则必须使用括号;如果p,那么q-IFF-r产生p→ Q↔ r和p如果q那么r是语法错误

坦白地说,我认为这整件事将更容易使用箭头作为条件和双条件,如上面的gloss中所示,并且使用IF-THEN-ELSE仅用于上面用C风格编写的三元选择器表达式:语法,这是另一种可能性。这将产生更少的惊喜。但这不是我的语言

具有浮动优先级的双条件运算符的一个解决方案是在两个过程中进行解析。第一步将只识别IF p THEN q操作符,而不使用附加的ELSE,使用一种类似于本文所建议的机制,并通过删除IF和更改THEN的拼写将它们更改为p->q。其他运算符将不会被解析,括号将被保留。然后,它会将生成的令牌流馈送到具有更传统语法风格的第二个LALR解析器中。我可能会开始编写这种代码,因为我认为双过程bison解析器偶尔有用,而且很少有示例

这是树重写解析器。我很抱歉这么长时间:

%{ 包括 包括 包括 无效常数 char*msg; int-yylexvoid; typedef结构节点; 枚举类型{ATOM,NEG,CONJ,DISJ,IMPL,BICOND,trimal, IFSEQ }; 结构节点{ 枚举类型; 联合{ 常量字符*原子; 节点*子节点[3]; }; }; 节点*NODENEUM AstType类型,节点*op1,节点*op2,节点*op3; 节点*atomconst char*名称; void node_freeNode*; void node_printNode*,文件*; 类型定义结构ElseStack ElseStack; 结构艾尔塞斯塔克{ 节点*动作; 艾尔塞斯塔克*下一个; }; ElseStack*构建其他堆栈节点*,ElseStack*; ElseStack*shift_elsesNode*,ElseStack*; %} %联合{ 常量字符*名称; 结构节点*节点; } %令牌T_ID %令牌T_和 还有别的吗 如果 敌我识别 不是吗 托奥 那么 %类型项conj disj bicond cond mat unmat tail expr %% 程序:%empty |程序stmt; stmt:expr'\n'{node_print$1,stdout;putchar'\n';node_free$1;} |“\n” |错误'\n' 术语:T_ID{$$=atom$1;} |非术语{$$=nodeNEG,$2,NULL,NULL;} |expr{$$=$2;} conj:术语 |conj和术语{$$=nodeCONJ,$1,$3,NULL;} disj:conj |disj或conj{$$=nodeDISJ,$1,$3,NULL;} bicond:disj |disj-iff-bicond{$$=nodeBICOND,$1,$3,NULL;} 马特:比康 |如果为expr,则为mat else mat {$$=nodeIFSEQ,$2,$4,$6;} 取消匹配:如果为expr,则为mat {$$=nodeIFSEQ,$2,$4,NULL;} |如果expr则取消匹配 {$$=nodeIFSEQ,$2,$4,NULL;} |如果为expr,则为mat else unmat {$$=nodeIFSEQ,$2,$4,$6;} 尾部:如果为expr,则为mat {$$=nodeIFSEQ,$2,$4,NULL;} |如果expr则取消匹配 {$$=nodeIFSEQ,$2,$4,NULL;} 康德:比康德 |尾部{shift\u elses$$,build\u else\u stack$$,NULL;} |如果为expr,则为mat else cond {$$=nodeternal,$2,$4,$6;} 表达式:cond %% /*在树中遍历IFSEQ节点,按任意 *在else堆栈上找到else子句,它将 *返回。 */ ElseStack*构建\其他\堆栈节点*如果,ElseStack*堆栈{ 如果ifs&&ifs->type!=IFSEQ{ stack=build\u else\u stackifs->child[1],stack; 如果ifs->child[2]{ ElseStack*top=mallocsizeof*top; *top=ElseStack{ifs->child[2],stack}; stack=build\u else\u stackifs->child[2],顶部; } } 返回栈; } /*在树中遍历IFSEQ节点,从 *其他堆栈。 *弹出else堆栈,释放弹出 *对象,并返回堆栈的新顶部。 */ ElseStack*shift_elsesNode*n,ElseStack*堆栈{ 如果n&&n->type==IFSEQ{ 如果堆栈{ ElseStack*top=堆栈; stack=shift_elsesn->child[2], shift_elsesn->child[1],stack->next; n->类型=三元; n->child[2]=顶部; 自由顶; } 否则{ shift_elsesn->child[2], shift_elsesn->child[1],空; n->type=IMPL; n->child[2]=NULL; } } 返回栈; } 节点*NODENEUM AstType类型,节点*op1,节点*op2,节点*op3{ 节点*rv=mallocsizeof*rv; *rv=节点{type,.child={op1,op2,op3}; 返回rv; } 节点*atomconst char*名称{ 节点*rv=mallocsizeof*rv; *rv=节点{ATOM,.ATOM=name}; 返回rv; } 无效节点\u自由节点*n{ 如果n{ 如果n->type==ATOM freechar*n->ATOM; 对于int i=0;i<3;++i节点自由->子节点[i]; 弗里恩; } } 常量字符*类型名称枚举AstType类型{ 开关类型{ 案例原子:返回原子; 案例NEG:不返回; 案例CONJ:返回CONJ; case-DISJ:返回DISJ; 案例IMPL:返回IMPL; 案例BICOND:返回BICOND; 案例三元:返回三元; case IFSEQ:返回IF_SEQ; } 返回**坏节点类型**; } 无效节点\u打印节点*n,文件*out{ 如果n{ 如果n->type==ATOM fputsn->atom,out; 否则{ fprintfout,%s,typenamen->type; 对于int i=0;i<3&&n->child[i];++i{ fputc“”,out;节点\u printn->child[i],out; } fputc,out; } } } void yyerrorconst char*msg{ fprintfstderr,%s\n,msg; } int main argc,字符**argv{ 返回解析; } lexer几乎是微不足道的。这一个使用小写的关键字,因为我的手指更喜欢,但它是微不足道的改变

%{ 包括ifelse.tab.h %} %选项noinput nounput noyywrap nodefault %% 和{返回T_和;} else{return T_else;} if{返回T_if;} iff{return T_iff;} not{return T_not;} 或{返回T_或;} 然后{返回T_then;} [[:alpha:][]+{yylval.name=strdupyytext; 返回T_ID;} [[:space:]{-}[\n]+; \n{返回'\n';} . {return*yytext;} 如前所述,解析器/词法分析器一次读取一行,并为每行打印AST,因此不允许使用多行表达式。我希望清楚地知道如何更改它。

请参阅本手册的第二部分
或者搜索fir悬空ELS答案提到了如何匹配IF a THEN IF b THEN c ELSE d到IF a THEN IFTHENELSEb,c,d但我希望它匹配到IFTHENELSEa,IFTHENb,c,d,LRk解析器无法找到解析,因为在遇到第二个ELSE之前,不可能知道是否减少IF b THEN c,这意味着它不能用有限的前瞻来预测。然而,一个明确的语法确实存在,你可以用GLR解析器来解析它。看,是的,明白了。那么我猜解析不能在ML yacc中完成,因为它是一个LALR解析器。无论如何,非常感谢。这是一个合理的方法,但是如果a然后如果b然后x或者y或者z,这个实现失败了,我认为这是可以工作的。虽然谁知道呢。哎呀,你编辑了这个事实。抱歉,在我发表评论之前没有看到编辑。@rici你能想出osme的方法来传播其他人的信息吗?第一部分如果a然后如果b然后x ELSE y被视为一个单元,变成一个ifelse节点。然后另一种情况发生了。在这一点上,我们可以检测到一个ELSE正在应用于一个已经转换的IFELSE。我们可以重新安排ifelse,这样我们就可以把ELSE推到一个必须存在的内部if,或者抛出一个错误。这就为外部的ELSE打开了一个滑入的空间。这应该是可能的,而不需要检查语义动作的有效性,只要IFs和ELSE的整个混乱形成一个单一的语义实体,直到它们被迫成为expr。语义单元有一个IFs队列和一个ELSE堆栈。当它转换为expr时,队列和堆栈并行运行,相互匹配最外层的IF。@rici我正在尝试一些探索性的Lisp代码,它采用类似ELSE foo的表达式。。。并将其转换为foo。。。其中,通过将bar插入到嵌入的if表单中,foo已被转换。重复这个过程,直到没有其他的了。两个尾部产品在unmat中完全复制;我认为尾巴只是生成了一个unmat来代替这两个。@kaz:是的。也可以将mat | unmat收集到标准演示中常见的非终端。
$ yacc ifelse.y
$ gcc -o ifelse y.tab.c
%{

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

typedef struct astnode {
  int op;
  struct astnode *left, *right;
  char *lexeme;
} astnode;

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

void *xmalloc(size_t size)
{
  void *p = malloc(size);
  if (p)
    return p;

  yyerror("out of memory");
  abort();
}

char *xstrdup(char *in)
{
  size_t sz = strlen(in) + 1;
  char *out = xmalloc(sz);
  return strcpy(out, in);
}

astnode *astnode_cons(int op, astnode *left, astnode *right, char *lexeme)
{
  astnode *a = xmalloc(sizeof *a);
  a->op = op;
  a->left = left;
  a->right = right;
  a->lexeme = lexeme;
  return a;
}

int yylex(void);

astnode *ast;

%}

%union {
  astnode *node;
  char *lexeme;
  int none;
}

%token<none> '(' ')'

%token<lexeme> ATOM

%left<none> ELSE
%left<none> IF THEN
%right<none> IFF
%left<none> OR
%left<none> AND

%type<node> top expr

%%

top : expr { ast = $1; }

expr : expr AND expr
       { $$ = astnode_cons(AND, $1, $3, 0); }
     | expr OR expr
       { $$ = astnode_cons(OR, $1, $3, 0); }
     | expr IFF expr
       { $$ = astnode_cons(IFF, $1, $3, 0); }
     | IF expr THEN expr
       { $$ = astnode_cons(IF, $2, $4, 0); }
     | expr ELSE expr
       { if ($1->op != IF)
         { yyerror("ELSE must pair with IF");
           $$ = 0; }
         else
         { $$ = astnode_cons(ELSE, $1, $3, 0); } }
     | '(' expr ')'
       { $$ = $2; }
     | ATOM
       { $$ = astnode_cons(ATOM, 0, 0, $1); }
     ;

%%

int yylex(void)
{
  int ch;
  char tok[64], *te = tok + sizeof(tok), *tp = tok;

  while ((ch = getchar()) != EOF) {
    if (isalnum((unsigned char) ch)) {
      if (tp >= te - 1)
        yyerror("token overflow");
      *tp++ = ch;
    } else if (isspace(ch)) {
      if (tp > tok)
        break;
    } else if (ch == '(' || ch == ')') {
      if (tp == tok)
        return ch;
      ungetc(ch, stdin);
      break;
    } else {
      yyerror("invalid character");
    }
  }

  if (tp > tok) {
    yylval.none = 0;
    *tp++ = 0;
    if (strcmp(tok, "AND") == 0)
      return AND;
    if (strcmp(tok, "OR") == 0)
      return OR;
    if (strcmp(tok, "IFF") == 0)
      return IFF;
    if (strcmp(tok, "IF") == 0)
      return IF;
    if (strcmp(tok, "THEN") == 0)
      return THEN;
    if (strcmp(tok, "ELSE") == 0)
      return ELSE;
    yylval.lexeme = xstrdup(tok);
    return ATOM;
  }

  return 0;
}

void ast_print(astnode *a)
{
  if (a == 0) {
    fputs("<invalid>", stdout);
    return;
  }

  switch (a->op) {
  case ATOM:
    fputs(a->lexeme, stdout);
    break;
  case AND:
  case OR:
  case IF:
  case IFF:
    switch (a->op) {
    case AND:
      fputs("AND(", stdout);
      break;
    case OR:
      fputs("OR(", stdout);
      break;
    case IF:
      fputs("IF(", stdout);
      break;
    case IFF:
      fputs("IFF(", stdout);
      break;
    }
    ast_print(a->left);
    fputs(", ", stdout);
    ast_print(a->right);
    putc(')', stdout);
    break;
  case ELSE:
    fputs("IFELSE(", stdout);
    ast_print(a->left->left);
    fputs(", ", stdout);
    ast_print(a->left->right);
    fputs(", ", stdout);
    ast_print(a->right);
    putc(')', stdout);
    break;
  }
}

int main(void)
{
   yyparse();
   ast_print(ast);
   puts("");
   return 0;
}
IF p THEN q IFF IF r THEN s  ==>  ((p → q) ↔ (r → s))
IF p THEN q IFF r ELSE s IFF t ==> (p ? (q ↔ r) : (s ↔ t))
IF p THEN q IFF IF r THEN s ELSE t IFF u ==> (p ? (q ↔ (r → s)) : (t ↔ u))