1 """Logic expressions handling
6 at present this is mainly needed for facts.py , feel free however to improve
7 this stuff for general purpose.
11 """'not' in fuzzy logic"""
34 """Logical expression"""
38 # {} 'op' -> LogicClass
42 def __new__(cls
, args
):
43 obj
= object.__new
__(cls
)
44 obj
.args
= tuple(args
)
46 # XXX do we need this:
47 #print 'L: %s' % (obj.args,)
48 assert not isinstance(obj
.args
[0], tuple)
54 return hash( (type(self
).__name
__, self
.args
) )
58 if not isinstance(b
, type(a
)):
61 return a
.args
== b
.args
64 if not isinstance(b
, type(a
)):
67 return a
.args
!= b
.args
71 if type(a
) is not type(b
):
72 return cmp( str(type(a
)), str(type(b
)) )
75 return cmp(a
.args
, b
.args
)
80 # XXX later, we may want to change how expressions are printed
82 return '%s(%s)' % (self
.op
, ', '.join(str(a
) for a
in self
.args
))
84 # XXX this is not good ...
96 # XXX this is not general, but good enough
99 lexpr
= None # current logical expression
100 schedop
= None # scheduled operation
103 # pop next term and exit loop if there is no terms left
111 if schedop
is not None:
112 raise ValueError('double op forbidden: "%s %s"' % (term
, schedop
))
115 raise ValueError('%s cannot be in the beginning of expression' % term
)
121 # already scheduled operation, e.g. '&'
123 lexpr
= Logic
.op_2class
[schedop
] ( *(lexpr
, term
) )
127 # this should be atom
128 if lexpr
is not None:
129 raise ValueError('missing op between "%s" and "%s"' % (lexpr
, term
))
134 # let's check that we ended up in correct state
135 if schedop
is not None:
136 raise ValueError('premature end-of-expression in "%s"' % text
)
138 raise ValueError('"%s" is empty' % text
)
140 # everything looks good now
145 class AndOr_Base(Logic
):
149 def __new__(cls
, *args
):
151 raise TypeError('%s requires at least one argument' % cls
.__name
__)
153 # process bool args early
159 if a
== cls
.op_x_notx
:
162 # &(T, ...) -> &(...)
163 # |(F, ...) -> |(...)
164 elif a
== (not cls
.op_x_notx
):
165 continue # skip this argument
180 args
= cls
.flatten(args
)
182 # canonicalize arguments
183 # XXX do we always need this?
184 # NB: this is needed to reduce number of &-nodes in beta-network
187 # now let's kill duplicate arguments, e.g. &(a,a,b) -> &(a,b)
202 # when we are at this stage, it means that _all_ arguments were T/F and
203 # all arguments were accepted as "let's see what follows next", so at
204 # _this_ point the rule is:
208 return not cls
.op_x_notx
210 return Logic
.__new
__(cls
, args
)
214 def flatten(cls
, args
):
215 # quick-n-dirty flattening for And and Or
216 args_queue
= list(args
)
222 arg
= args_queue
.pop(0)
226 if isinstance(arg
, Logic
):
228 #print 'flattening...', fargs, i, arg.args
229 args_queue
.extend( arg
.args
)
233 # another op -- leave it as is
241 class And(AndOr_Base
):
247 def _eval_propagate_not(self
):
248 # !(a&b&c ...) == !a | !b | !c ...
249 return Or( *[Not(a
) for a
in self
.args
] )
252 # (a|b|...) & c == (a&c) | (b&c) | ...
256 for i
in range(len(self
.args
)):
258 if isinstance(arg
, Or
):
259 arest
= self
.args
[:i
] + self
.args
[i
+1:]
261 orterms
= [And( *(arest
+ (a
,)) ) for a
in arg
.args
]
262 for j
in range(len(orterms
)):
263 if isinstance(orterms
[j
], Logic
):
264 orterms
[j
] = orterms
[j
].expand()
272 def dbg_expand(self
):
274 print '%sexpand %s' % (' '*expand_lvl
, self
)
278 return self
.old_expand()
286 class Or(AndOr_Base
):
292 def _eval_propagate_not(self
):
293 # !(a|b|c ...) == !a & !b & !c ...
294 return And( *[Not(a
) for a
in self
.args
] )
301 def __new__(cls
, arg
):
302 if isinstance(arg
, str):
305 elif isinstance(arg
, bool):
308 elif isinstance(arg
, Logic
):
309 # XXX this is a hack to expand right from the beginning
310 arg
= arg
._eval
_propagate
_not
()
313 obj
= Logic
.__new
__(cls
, (arg
,))
317 raise ValueError('Not: unknow argument %r' % (arg
,))
323 Logic
.op_2class
['&'] = And
324 Logic
.op_2class
['|'] = Or
325 Logic
.op_2class
['!'] = Not