我想使用yacc和lex将Demorgan's theorem应用于输入。
输入可以是任何表达式,例如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;
}
答案 0 :(得分:0)
您正在尝试构建计算机代数系统。
您的任务在概念上很简单:
但概念上简单并不一定意味着容易做到并且做得很好。
(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),它显示了我上面编写的规则的真实示例。