Closed cons0l3 closed 3 years ago
Actually it would not require to evaluate the left-hand-side, as the right-hand-side of the OR already fully defines the result.
That's true for this case. The reduction takes place at parsing time though (when you initialize the Rule
instance), so it doesn't account for default values because it doesn't know which will be missing and any should be called to be missing or specified later on at evaluation time. So that's why it's not fully reduced.
You can get all of the symbols a rule references using rule.context.symbols
, for example:
In [3]: rule = Rule("""(a in [1,2] and c=='abc') or b==1""")
In [4]: rule.context
Out[4]: <rule_engine.engine.Context at 0x7faaf43c0e80>
In [5]: rule.context.symbols
Out[5]: {'a', 'b', 'c'}
You could use a sort of resolver-wrapper though to identify this. For example:
In [13]: class CustomResolver(object):
...: def __init__(self, resolver):
...: self.resolver = resolver
...: self.missing_symbols = set()
...: def __call__(self, thing, name):
...: try:
...: self.resolver(thing, name)
...: except SymbolResolutionError as error:
...: self.missing_symbols.add(name)
...: raise error
...: custom_resolver = CustomResolver(resolve_item)
In [14]: context = Context(resolver=custom_resolver, default_value=None)
In [15]: rule = Rule("""(a in [1,2] and c=='abc') or b==1""", context=context)
In [16]: rule.evaluate({'b': 1})
Out[16]: False
In [17]: rule.context.symbols # all symbols
Out[17]: {'a', 'b', 'c'}
In [18]: custom_resolver.missing_symbols # symbols where the default was used
Out[18]: {'a'}
Keep in mind though that this would not be a thread-safe solution. If you are evaluating the rules in different threads, then you'd need to use thread local data to organize the values (this is what the rule engine does for a couple of things).
Thanks Spencer,
I had to adjust jour example a little to make it work:
...
try:
**return** self.resolver(thing, name)
except SymbolResolutionError as error:
...
as a rule.evaluate({'b':1}) should result in true. And the CustomerResolver did not find the undefined "c". Mostlikely because the raised exception, that aborted the AST-tree-walking during evaluation of the left hand side of the OR-node.
I was already playing around with the concept of "find what is undefined". It is much more straight forward:
input_data = {'b':1}
undefined_symbols = rule.context.symbols.difference(set(input_data.keys()))
print(undefined_symbols)
=> {'c', 'a'}
I come to the conclusion, that I need to evalute the AST from the parser with the following features myself:
1) variables/symbols may be defined or undefined. variables may be None in which case they are defined, but have a value of None
2) None is False
3) None AND False
evaluates as false, None AND True
evaluates as false, None OR true
evaluates as true, NOT None
is true ...
4) False AND undefined
== undefined AND False
evaluates as false, undefined OR True
evaluates as true, ...
5) A Rule (AST-node) may evaluate as "undefined" and return a list of "missing" symbols in such a case.
6) A Rule (AST-node) evaluates to the value if it is defined enough for the node-type by the values of its children.
...
I threw together a small peg parser (thanks Guido for putting the urge into my head to play around with peg parsers) and am currently working on a visitor pattern to walk and evaluate the tree on the above stated rules.
Thanks for your support Spencer,
Cheers Carsten
Hi @zeroSteiner,
love your work in this open source contribution. Thanks!
I currently work on evaluating purely "boolean" expressions. a simplified example is:
(a in [1,2] and c=='abc') or b==1
. My "things" are not always fully defined. e.g.rule.match({'b':1})
the "thing" is not completly defined and will throw theSymbolResolutionError
rightly so.If I define
default_value=None
in the context as work around, I get passed theSymbolResolutionError
and in the above example will receive a "true" as the OR will never evaluate to anything else but true. Actually it would not require to evaluate the left-hand-side, as the right-hand-side of the OR already fully defines the result.My problem with the
Default_Value=None
work around is, that I cannot determine easily if the "False" is really false or false because it could not completely evaluate.After a match I would love to have list of symbols that tell me, if I need more attributes defined in the "thing".
Can I convice the rule-engine to "partially" evaluate a rule with a incomplete thing (maybe with the reduce function) and see if either the rule is reduced to a literal or some expression is left over? That would of course require that if b is "1" in thing, the reduced rule will be "true" literally.
Hope you understand what I am getting at?
Cheers Carsten