使用IF-THEN-ELSE三元运算符解析命题逻辑的解析器

时间:2017-09-08 01:48:28

标签: parsing bison yacc shift-reduce-conflict ml-yacc

我想为命题逻辑实现Parser,它具有以下优先级顺序的运算符:

  1. NOT p
  2. p AND q
  3. p OR q
  4. IF p THEN q
  5. p IFF q
  6. IF p THEN q ELSE r
  7. 主要问题是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) )
    

    但我似乎没有正确的想法如何消除减少移位冲突。正确解析的一些示例是:

    1. 如果那么如果那么c ________ a->(b-> c)
    2. 如果那么,如果这样,那么我或是_______ IFTHENELSE(a,b-> c,d< =&e; \ f)
    3. 任何帮助/指针都会非常有用。感谢。

2 个答案:

答案 0 :(得分:1)

让我的Yacc坐起来乞求

我比以往任何时候都更加确信这里的正确方法是GLR语法,如果可能的话。然而,受@ Kaz的启发,我使用LALR(1)语法生成了以下yacc / bison语法(甚至没有使用优先声明)。

当然,它作弊,因为用LALR(1)语法无法解决问题。在适当的时间间隔,它会遍历构造的IF THENIF THEN ELSE表达式树,并根据需要移动ELSE子句。

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

为了正确回答问题,还需要重新排列一些IFF个节点,因为IFFTHEN子句的绑定更弱,而且比ELSE更紧密。 {1}}条款。我认为这意味着:

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子表达式,则必须使用括号; IF p THEN q IFF r生成p → (q ↔ r)p IFF IF q THEN r是语法错误。

坦率地说,我认为使用箭头进行条件和双向变换(如上面的glosses)更简单,并且仅将IF THEN ELSE用于三元选择器表达式(上面用C风格{{1}编写)语法,这是另一种可能性)。这将产生更少的惊喜。但这不是我的语言。

具有浮动优先级的双向运算符的一种解决方案是在两遍中解析。第一次传递仅使用与此处提议的机制类似的机制识别没有附加? :的{​​{1}}运算符,并通过删除IF p THEN qELSE将其更改为p -> q更改IF的拼写。其他运算符将不会被解析,并且将保留括号。然后,它将生成的令牌流提供给具有更传统语法风格的第二个LALR解析器。我可能只是因为我认为两次通过野牛解析器偶尔会有用而且只有很少的例子可以用来编码。

这是树重写解析器。我为这个长度道歉:

THEN
勒克斯几乎是微不足道的。 (这个使用小写的关键字,因为我的手指更喜欢这个,但改变是微不足道的。)

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

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

typedef struct Node Node;
enum AstType { ATOM, NEG, CONJ, DISJ, IMPL, BICOND, TERNARY,
               IFSEQ
};
struct Node {
  enum AstType type;
  union {
    const char* atom;
    Node* child[3];
  };
};
Node* node(enum AstType type, Node* op1, Node* op2, Node* op3);
Node* atom(const char* name);
void  node_free(Node*);
void  node_print(Node*, FILE*);

typedef struct ElseStack ElseStack;
struct ElseStack {
  Node* action;
  ElseStack* next;
};

ElseStack* build_else_stack(Node*, ElseStack*);
ElseStack* shift_elses(Node*, ElseStack*);
%}

%union {
  const char* name;
  struct Node* node;
}

%token <name> T_ID
%token T_AND  "and"
       T_ELSE "else"
       T_IF   "if"
       T_IFF  "iff"
       T_NOT  "not"
       T_OR   "or"
       T_THEN "then"
%type <node> term conj disj bicond cond mat unmat tail expr

%%
prog : %empty | prog stmt;
stmt : expr '\n'       { node_print($1, stdout); putchar('\n'); node_free($1); }
     | '\n'
     | error '\n'
term : T_ID            { $$ = atom($1); }
     | "not" term      { $$ = node(NEG, $2, NULL, NULL); }
     | '(' expr ')'    { $$ = $2; }
conj : term
     | conj "and" term { $$ = node(CONJ, $1, $3, NULL); }
disj : conj
     | disj "or" conj  { $$ = node(DISJ, $1, $3, NULL); }
bicond: disj
     | disj "iff" bicond { $$ = node(BICOND, $1, $3, NULL); }
mat  : bicond
     | "if" expr "then" mat "else" mat
                       { $$ = node(IFSEQ, $2, $4, $6); }
unmat: "if" expr "then" mat
                       { $$ = node(IFSEQ, $2, $4, NULL); }
     | "if" expr "then" unmat
                       { $$ = node(IFSEQ, $2, $4, NULL); }
     | "if" expr "then" mat "else" unmat
                       { $$ = node(IFSEQ, $2, $4, $6); }
tail : "if" expr "then" mat
                       { $$ = node(IFSEQ, $2, $4, NULL); }
     | "if" expr "then" unmat
                       { $$ = node(IFSEQ, $2, $4, NULL); }
cond : bicond
     | tail            { shift_elses($$, build_else_stack($$, NULL)); }
     | "if" expr "then" mat "else" cond
                       { $$ = node(TERNARY, $2, $4, $6); }
expr : cond

%%

/* Walk the IFSEQ nodes in the tree, pushing any
 * else clause found onto the else stack, which it
 * returns. 
 */
ElseStack* build_else_stack(Node* ifs, ElseStack* stack) {
  if (ifs && ifs->type != IFSEQ) {
    stack = build_else_stack(ifs->child[1], stack);
    if (ifs->child[2]) { 
      ElseStack* top = malloc(sizeof *top);
      *top = (ElseStack) { ifs->child[2], stack };
      stack = build_else_stack(ifs->child[2], top);
    }
  }
  return stack;
}
/* Walk the IFSEQ nodes in the tree, attaching elses from
 * the else stack.
 * Pops the else stack as it goes, freeing popped 
 * objects, and returns the new top of the stack.
 */
ElseStack* shift_elses(Node* n, ElseStack* stack) {
  if (n && n->type == IFSEQ) {
    if (stack) {
      ElseStack* top = stack;
      stack = shift_elses(n->child[2],
                          shift_elses(n->child[1], stack->next));
      n->type = TERNARY;
      n->child[2] = top;
      free(top);
    }
    else {
      shift_elses(n->child[2],
                  shift_elses(n->child[1], NULL));
      n->type = IMPL; 
      n->child[2] = NULL;
    }
  }
  return stack;
}

Node* node(enum AstType type, Node* op1, Node* op2, Node* op3) {
  Node* rv = malloc(sizeof *rv);
  *rv = (Node){type, .child = {op1, op2, op3}};
  return rv;
}

Node* atom(const char* name) {
  Node* rv = malloc(sizeof *rv);
  *rv = (Node){ATOM, .atom = name};
  return rv;
}

void node_free(Node* n) {
  if (n) {
    if (n->type == ATOM) free((char*)n->atom);
    else for (int i = 0; i < 3; ++i) node_free(n->child[i]);
    free(n);
  }
}

const char* typename(enum AstType type) {
  switch (type) {
    case ATOM:    return "ATOM";
    case NEG:     return "NOT" ;
    case CONJ:    return "CONJ";
    case DISJ:    return "DISJ";
    case IMPL:    return "IMPL";
    case BICOND:  return "BICOND";
    case TERNARY: return "TERNARY" ;
    case IFSEQ:   return "IF_SEQ";
  }
  return "**BAD NODE TYPE**";
}

void node_print(Node* n, FILE* out) {
  if (n) {
    if (n->type == ATOM)
      fputs(n->atom, out);
    else {
      fprintf(out, "(%s", typename(n->type));
      for (int i = 0; i < 3 && n->child[i]; ++i) {
        fputc(' ', out); node_print(n->child[i], out);
      }
      fputc(')', out);
    }
  }
}

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

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

如上所述,解析器/词法分析器一次读取一行,并为每行打印AST(因此不允许使用多行表达式)。我希望很清楚如何改变它。

答案 1 :(得分:0)

处理这个要求的一个相对简单的方法是创建一个过度生成的语法,然后拒绝我们不想使用语义的语法。

具体地说,我们使用这样的语法:

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组合时拒绝它的规则。

这是我编写的完整程序的测试运行(使用经典的Yacc,在C中):

$ 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一致。以下是错误ELSE的错误情况:

$ echo 'a OR b ELSE c' | ./ifelse 
error: ELSE must pair with IF
<invalid>

这是括号,以强制通常的&#34;否则与最接近的&#34;行为:

$ echo 'IF a THEN (IF x THEN y ELSE c)' | ./ifelse 
IF(a, IFELSE(x, y, c))

程序通过构建AST然后遍历它以前缀F(X, Y)语法打印它来显示正在使用的解析。 (作为一名Lisp程序员,我不得不忍住一点点的呕吐反射。)

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

注意:您可能希望处理以下内容,但它不是:

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

此处的问题是ELSE wIFELSE表达式配对。

可能有一种更复杂的方法,可能有趣的探索。解析器可以将ELSE视为普通的二元运算符,并以此方式生成AST。然后,整个单独的步行可以检查树是否有效ELSE用法并根据需要进行转换。或许我们可以在这里使用ELSE的关联性,并以适当的方式处理解析器操作中的级联ELSE

完整的源代码,我保存在一个名为ifelse.y的文件中,并使用:

构建
$ 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;
}
相关问题