如何使用yacc创建将De Morgan定理应用于表达式的语法?

时间:2016-02-10 11:31:47

标签: parsing grammar yacc lex

我想使用yacc和lex将Demorgan's theorem应用于输入。

输入可以是任何表达式,例如a + b,!(A + B)等:

  • 表达式a + b应该导致!a∙!b
  • 表达式!(a + b)应该导致a + b

我认为lex部分已经完成,但我对将法律应用于表达式所需的yacc语法有困难。

我尝试实施的是以下算法。考虑以下等式作为输入:Y = A + B

在应用De Morgan法律后,它变为:!Y =!(A + B)

最后,扩大括号应该导致!Y =!A∙!B

这里是lex代码:

%{
#include <stdio.h>
#include "y.tab.h"

extern int yylval;
int yywrap (void);
%}

%%
[a-zA-Z]+   {yylval = *yytext; return ALPHABET;}
"&&"        return AND;
"||"        return OR;
"="     return ('=');
[\t]        ;
\n      return 0;
.       return yytext[0];
"0exit"     return 0;
%%

int yywrap (void)
{
    return 1;
}

这是我的yacc代码:

%{
#include <stdio.h>

int yylex (void);
void yyerror (char *);
extern FILE* yyin;
%}

%token ALPHABET
%left '+''*'
%right '=' '!' NOT

%left AND OR
%start check

%%
check : expr  {printf("%s\n",$$);}
      ; 
expr : plus
     |plus '+' plus {$$ = $1 + $3;}
     ;
plus : times
     |times '*' times   {$$ = $1 * $3;}
     ;
times : and_op
      |and_op AND and_op{$$ = $1 && $3;}
      ;
and_op : or_op
       |or_op OR or_op  {$$ = $1 || $3;}
       ;
or_op : not_op
      |'!' not_op   {$$ = !$2;}
      ;
not_op : paren
       |'(' paren ')'   {$$ = $2;}
       ;
paren : 
      |ALPHABET     {$$ = $1;}
      ;

/*  
 E: E '+' E     {$$ = $1 + $3;}
  |E '*' E  {$$ = $1 * $3;}
  |E '=' E  {$$ = $1 = $3;}
  |E AND E  {$$ = ($1 && $3);}
  |E OR E   {$$ = ($1 || $3);}
  |'(' E ')'    {$$ = $2;}
  |'!' E %prec NOT {$$ = !$2;}
  |ALPHABET     {$$ = $1;}
  ;*/
%%

int main()
{
    char filename[30];
    char * line = NULL;
    size_t len = 0;
    printf("\nEnter filename\n");
    scanf("%s",filename);
    FILE *fp = fopen(filename, "r");
    if(fp == NULL)
    {
        fprintf(stderr,"Can't read file %s\n",filename);
        exit(EXIT_FAILURE);
    }
    yyin = fp;
//  while (getline(&line, &len, fp) != -1)
//  {
//      printf("%s",line);
//  }
//  printf("Enter the expression:\n");
    do
    {
        yyparse();
    }while(!feof(yyin)); 

    return 0;   
}

1 个答案:

答案 0 :(得分:0)

您正在尝试构建计算机代数系统。

您的任务在概念上很简单:

  1. 为&#34; boolean&#34;的原子定义词法分析器表达式
  2. 根据词汇
  3. 定义命题逻辑的解析器
  4. 构建存储表达式的树
  5. 定义实现逻辑等价的过程(DeMorgan定理是一个),在树中找到一个可以通过匹配树结构应用它的位置,然后相应地修改树
  6. 运行这些过程以实现您想要的逻辑重写
  7. 将最终的AST作为答案
  8. 但概念上简单并不一定意味着容易做到并且做得很好。

    (f)lex和yacc旨在帮助您以相对简单的方式执行步骤1-3;他们的文档包含一个很好的指南。 他们根本没有帮助完成第4-6步,这就是真正的工作发生的地方。 (你的语法对于这部分看起来是个不错的开始)。

    (你可以做1-3而没有flex和yacc by building a recursive descent parser that also happens to build the AST as it goes)。

    第4步可能很麻烦,因为你必须决定你想要使用哪些逻辑定理,然后为每一个编写一个程序来进行树匹​​配和树粉碎,以达到理想的结果。你能行的;它只是在树上走来走去的程序代码,将节点类型和关系与子节点进行比较,然后将节点脱钩,删除节点,创建节点,并重新链接它们以实现树修改。这只是一堆代码。

    代数重写的微妙之处现在要咬你:(布尔)代数有关联和交换运算符。这意味着一些代数规则将适用于任意相隔的树的部分。考虑这条规则:

        a*(b + !a) =>  a*(b)
    

    当解析的实际术语如下所示时会发生什么:

        q*(a + b + c + ... !q ... + z)
    

    &#34;简单&#34;现在,查看树的程序代码必须在子树上任意地走,以找到规则可以应用的位置。突然对匹配逻辑进行编码并不容易,树木粉碎也不能实现效果。

    如果我们忽略关联和交换问题,对于复杂的匹配和修改,代码可能有点笨拙,难以阅读;在你完成它之后,这将是显而易见的。如果你只想做DeMorgan-over-,你可以通过编码来相对容易地做到。如果你想实现许多布尔代数规则以简化,那将开始变得痛苦。您理想的做法是用与布尔逻辑相同的符号表达逻辑规则,以便它们易于表达,但现在您需要能够读取和解释逻辑规则的东西。 是一段复杂的代码,但如果做得对,你可以编写逻辑规则,如下所示:

     rule deMorgan_for_or(t1:boolexp, t2:boolexp):boolexp->boolexp
       " ! (\t1 + \t2) " ->   " !\t1 * !\t2 ";
    

    相关问题(步骤5)是, 你想要应用逻辑规则吗?仅仅因为你可以用一个非常大的逻辑术语在15个地方应用德莫根定律,并不意味着你一定想要这样做。所以在某个地方你需要有一个控制机制来决定你应该应用哪些规则,以及应该应用哪些规则。这会让你进入元编程,这是一个全新的话题。

    如果你的规则是&#34; monotonic&#34;,也就是说,它们实际上只能应用一次,你可以简单地在所有地方运行它们并获得终止计算,如果那个单调答案是你想要的那个。如果您有反转的规则(例如,!(x + y)=&gt;!x *!y,并且!a *!b =&gt;!(a + b)),那么您的规则可能会一再运行并撤消重写。因此,您必须小心确保终止。

    最后,当您拥有修改后的树时,您需要以可读的形式将其打印出来(步骤6)。查看我的SO answer on how to build a prettyprinter

    自己做一两条规则是一项很好的学习练习。

    以创造有用工具的想法来实现它是一个完全不同的动物。您想要的是一组基础设施,使其易于表达:program transformation system。您可以使用表面语法重写规则,包括处理关联和可交换重写问题,看到它的完整示例for a system doing arithmetic rather than boolean computations。在另一个示例中,您可以看到what it looks like for boolean logic (see simplify_boolean near end of page),它显示了我上面编写的规则的真实示例。