rule-engine 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. rule_engine/__init__.py +65 -0
  2. rule_engine/_vendor/__init__.py +4 -0
  3. rule_engine/_vendor/ply/LICENSE +32 -0
  4. rule_engine/_vendor/ply/__init__.py +5 -0
  5. rule_engine/_vendor/ply/lex.py +1098 -0
  6. rule_engine/_vendor/ply/yacc.py +3433 -0
  7. rule_engine/ast/__init__.py +92 -0
  8. rule_engine/ast/base.py +343 -0
  9. rule_engine/ast/binary/__init__.py +103 -0
  10. rule_engine/ast/binary/arithmetic.py +211 -0
  11. rule_engine/ast/binary/base.py +112 -0
  12. rule_engine/ast/binary/comparison.py +154 -0
  13. rule_engine/ast/expression/__init__.py +35 -0
  14. rule_engine/ast/expression/access.py +434 -0
  15. rule_engine/ast/expression/control.py +256 -0
  16. rule_engine/ast/expression/resolution.py +220 -0
  17. rule_engine/ast/literal.py +183 -0
  18. rule_engine/builtins.py +230 -0
  19. rule_engine/debug_ast.py +65 -0
  20. rule_engine/debug_repl.py +159 -0
  21. rule_engine/engine/__init__.py +34 -0
  22. rule_engine/engine/_attribute_resolver.py +353 -0
  23. rule_engine/engine/context.py +438 -0
  24. rule_engine/engine/rule.py +148 -0
  25. rule_engine/errors.py +359 -0
  26. rule_engine/parser/__init__.py +614 -0
  27. rule_engine/parser/base.py +86 -0
  28. rule_engine/parser/utilities.py +114 -0
  29. rule_engine/suggestions.py +105 -0
  30. rule_engine/types/__init__.py +61 -0
  31. rule_engine/types/_object.py +318 -0
  32. rule_engine/types/coercion.py +141 -0
  33. rule_engine/types/dataclass.py +149 -0
  34. rule_engine/types/datatype.py +398 -0
  35. rule_engine/types/definitions.py +454 -0
  36. rule_engine/types/sqlalchemy.py +171 -0
  37. rule_engine-5.0.0.dist-info/METADATA +126 -0
  38. rule_engine-5.0.0.dist-info/RECORD +41 -0
  39. rule_engine-5.0.0.dist-info/WHEEL +5 -0
  40. rule_engine-5.0.0.dist-info/licenses/LICENSE +28 -0
  41. rule_engine-5.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # rule_engine/ast/binary/arithmetic.py
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above
13
+ # copyright notice, this list of conditions and the following disclaimer
14
+ # in the documentation and/or other materials provided with the
15
+ # distribution.
16
+ # * Neither the name of the project nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+
33
+ import datetime
34
+ import functools
35
+ import operator
36
+ from typing import Any, Callable
37
+
38
+ from ... import errors
39
+ from ...types import DataType, coerce_value
40
+ from ...types import _DataTypeDef
41
+
42
+ from ..base import _assert_is_bytes, _assert_is_natural_number, _assert_is_numeric, _assert_is_string, _assert_not_nullable, _is_reduced
43
+ from .base import BinaryExpressionBase
44
+
45
+ class AddExpression(BinaryExpressionBase):
46
+ """A class for representing addition expressions from the grammar text."""
47
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.BYTES, DataType.FLOAT, DataType.STRING, DataType.DATETIME, DataType.TIMEDELTA)
48
+ result_type: _DataTypeDef = DataType.UNDEFINED
49
+
50
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
51
+ super(AddExpression, self).__init__(*args, **kwargs)
52
+ _assert_not_nullable(self.left.result_type, role="left operand of '+'")
53
+ _assert_not_nullable(self.right.result_type, role="right operand of '+'")
54
+ if self.left.result_type != DataType.UNDEFINED and self.right.result_type != DataType.UNDEFINED:
55
+ if self.left.result_type == DataType.DATETIME:
56
+ if self.right.result_type != DataType.TIMEDELTA:
57
+ raise errors.EvaluationError('data type mismatch')
58
+ self.result_type = self.left.result_type
59
+ elif self.left.result_type == DataType.TIMEDELTA:
60
+ if self.right.result_type not in (DataType.DATETIME, DataType.TIMEDELTA):
61
+ raise errors.EvaluationError('data type mismatch')
62
+ self.result_type = self.right.result_type
63
+ elif self.left.result_type != self.right.result_type:
64
+ raise errors.EvaluationError('data type mismatch')
65
+ else:
66
+ self.result_type = self.left.result_type
67
+
68
+ def _op_add(self, thing: Any) -> Any:
69
+ left_value = self.left.evaluate(thing)
70
+ right_value = self.right.evaluate(thing)
71
+ if isinstance(left_value, datetime.datetime):
72
+ if not isinstance(right_value, datetime.timedelta):
73
+ raise errors.EvaluationError('data type mismatch (not a timedelta value)')
74
+ elif isinstance(left_value, datetime.timedelta):
75
+ if not isinstance(right_value, (datetime.timedelta, datetime.datetime)):
76
+ raise errors.EvaluationError('data type mismatch (not a datetime or timedelta value)')
77
+ elif isinstance(left_value, bytes) or isinstance(right_value, bytes):
78
+ _assert_is_bytes(left_value, right_value)
79
+ elif isinstance(left_value, str) or isinstance(right_value, str):
80
+ _assert_is_string(left_value, right_value)
81
+ else:
82
+ _assert_is_numeric(left_value, right_value)
83
+ return operator.add(left_value, right_value)
84
+
85
+ class SubtractExpression(BinaryExpressionBase):
86
+ """
87
+ A class for representing subtraction expressions from the grammar text.
88
+
89
+ .. versionadded:: 3.5.0
90
+ """
91
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.FLOAT, DataType.DATETIME, DataType.TIMEDELTA)
92
+ result_type: _DataTypeDef = DataType.UNDEFINED
93
+
94
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
95
+ super(SubtractExpression, self).__init__(*args, **kwargs)
96
+ _assert_not_nullable(self.left.result_type, role="left operand of '-'")
97
+ _assert_not_nullable(self.right.result_type, role="right operand of '-'")
98
+ if self.left.result_type != DataType.UNDEFINED and self.right.result_type != DataType.UNDEFINED:
99
+ if self.left.result_type == DataType.DATETIME:
100
+ if self.right.result_type == DataType.DATETIME:
101
+ self.result_type = DataType.TIMEDELTA
102
+ elif self.right.result_type == DataType.TIMEDELTA:
103
+ self.result_type = DataType.DATETIME
104
+ else:
105
+ raise errors.EvaluationError('data type mismatch')
106
+ elif self.left.result_type == DataType.TIMEDELTA:
107
+ if self.right.result_type != DataType.TIMEDELTA:
108
+ raise errors.EvaluationError('data type mismatch')
109
+ self.result_type = self.left.result_type
110
+ elif self.left.result_type != self.right.result_type:
111
+ raise errors.EvaluationError('data type mismatch')
112
+ else:
113
+ self.result_type = self.left.result_type
114
+
115
+ def _op_sub(self, thing: Any) -> Any:
116
+ left_value = self.left.evaluate(thing)
117
+ right_value = self.right.evaluate(thing)
118
+ if isinstance(left_value, datetime.datetime):
119
+ if not isinstance(right_value, (datetime.datetime, datetime.timedelta)):
120
+ raise errors.EvaluationError('data type mismatch (not a datetime or timedelta value)')
121
+ elif isinstance(left_value, datetime.timedelta):
122
+ if not isinstance(right_value, datetime.timedelta):
123
+ raise errors.EvaluationError('data type mismatch (not a timedelta value)')
124
+ else:
125
+ _assert_is_numeric(left_value, right_value)
126
+ return operator.sub(left_value, right_value)
127
+
128
+ class ArithmeticExpression(BinaryExpressionBase):
129
+ """A class for representing arithmetic expressions from the grammar text such as multiplication and division."""
130
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.FLOAT,)
131
+ result_type: _DataTypeDef = DataType.FLOAT
132
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
133
+ super(ArithmeticExpression, self).__init__(*args, **kwargs)
134
+ _assert_not_nullable(self.left.result_type, role='left arithmetic operand')
135
+ _assert_not_nullable(self.right.result_type, role='right arithmetic operand')
136
+
137
+ def __op_arithmetic(self, op: Callable[[Any, Any], Any], thing: Any) -> Any:
138
+ left_value = self.left.evaluate(thing)
139
+ _assert_is_numeric(left_value)
140
+ right_value = self.right.evaluate(thing)
141
+ _assert_is_numeric(right_value)
142
+ try:
143
+ result = op(left_value, right_value)
144
+ except ZeroDivisionError:
145
+ raise errors.ArithmeticError('arithmetic error: division by zero') from None
146
+ except ArithmeticError:
147
+ raise errors.ArithmeticError('arithmetic error') from None
148
+ return result
149
+
150
+ _op_fdiv = functools.partialmethod(__op_arithmetic, operator.floordiv)
151
+ _op_tdiv = functools.partialmethod(__op_arithmetic, operator.truediv)
152
+ _op_mod = functools.partialmethod(__op_arithmetic, operator.mod)
153
+ _op_mul = functools.partialmethod(__op_arithmetic, operator.mul)
154
+ _op_pow = functools.partialmethod(__op_arithmetic, operator.pow)
155
+
156
+ class BitwiseExpression(BinaryExpressionBase):
157
+ """
158
+ A class for representing bitwise arithmetic expressions from the grammar text such as XOR and shifting operations.
159
+ """
160
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.FLOAT, DataType.SET)
161
+ result_type: _DataTypeDef = DataType.UNDEFINED
162
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
163
+ super(BitwiseExpression, self).__init__(*args, **kwargs)
164
+ _assert_not_nullable(self.left.result_type, role='left bitwise operand')
165
+ _assert_not_nullable(self.right.result_type, role='right bitwise operand')
166
+ # don't use DataType.is_compatible, because for sets the member type isn't important
167
+ if self.left.result_type != DataType.UNDEFINED and self.right.result_type != DataType.UNDEFINED:
168
+ if self.left.result_type.__class__ != self.right.result_type.__class__:
169
+ raise errors.EvaluationError('data type mismatch')
170
+ if self.left.result_type == DataType.FLOAT:
171
+ if _is_reduced(self.left):
172
+ _assert_is_natural_number(self.left.evaluate(None))
173
+ self.result_type = DataType.FLOAT
174
+ if self.right.result_type == DataType.FLOAT:
175
+ if _is_reduced(self.right):
176
+ _assert_is_natural_number(self.right.evaluate(None))
177
+ self.result_type = DataType.FLOAT
178
+ if DataType.is_type(self.left.result_type, DataType.SET) or DataType.is_type(self.right.result_type, DataType.SET):
179
+ self.result_type = DataType.SET # this discards the member type info
180
+
181
+ def _op_bitwise(self, op: Callable[[Any, Any], Any], thing: Any) -> Any:
182
+ left = self.left.evaluate(thing)
183
+ if DataType.from_value(left) == DataType.FLOAT:
184
+ return self._op_bitwise_float(op, thing, left)
185
+ elif DataType.is_type(DataType.from_value(left), DataType.SET):
186
+ return self._op_bitwise_set(op, thing, left)
187
+ raise errors.EvaluationError('data type mismatch')
188
+
189
+ def _op_bitwise_float(self, op: Callable[[Any, Any], Any], thing: Any, left: Any) -> Any:
190
+ _assert_is_natural_number(left)
191
+ right = self.right.evaluate(thing)
192
+ _assert_is_natural_number(right)
193
+ return coerce_value(op(int(left), int(right)))
194
+
195
+ def _op_bitwise_set(self, op: Callable[[Any, Any], Any], thing: Any, left: Any) -> Any:
196
+ right = self.right.evaluate(thing)
197
+ if not DataType.is_compatible(DataType.from_value(right), DataType.SET):
198
+ raise errors.EvaluationError('data type mismatch')
199
+ return op(left, right)
200
+
201
+ _op_bwand = functools.partialmethod(_op_bitwise, operator.and_)
202
+ _op_bwor = functools.partialmethod(_op_bitwise, operator.or_)
203
+ _op_bwxor = functools.partialmethod(_op_bitwise, operator.xor)
204
+
205
+ class BitwiseShiftExpression(BitwiseExpression):
206
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.FLOAT,)
207
+ result_type: _DataTypeDef = DataType.FLOAT
208
+ def _op_bitwise_shift(self, *args: Any, **kwargs: Any) -> Any:
209
+ return self._op_bitwise(*args, **kwargs)
210
+ _op_bwlsh = functools.partialmethod(_op_bitwise_shift, operator.lshift)
211
+ _op_bwrsh = functools.partialmethod(_op_bitwise_shift, operator.rshift)
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # rule_engine/ast/binary/base.py
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above
13
+ # copyright notice, this list of conditions and the following disclaimer
14
+ # in the documentation and/or other materials provided with the
15
+ # distribution.
16
+ # * Neither the name of the project nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+
33
+ from typing import TYPE_CHECKING, Any, Callable
34
+
35
+ from ... import errors
36
+ from ...types import DataType
37
+ from ...types import _DataTypeDef
38
+
39
+ from ..base import ExpressionBase, LiteralExpressionBase, _is_reduced
40
+
41
+ if TYPE_CHECKING:
42
+ from ...engine.context import Context
43
+
44
+ class BinaryExpressionBase(ExpressionBase):
45
+ """
46
+ A base class for representing complex expressions composed of a left side and a right side, separated by an
47
+ operator.
48
+ """
49
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.ARRAY, DataType.BOOLEAN, DataType.DATETIME, DataType.TIMEDELTA, DataType.FLOAT, DataType.MAPPING, DataType.NULL, DataType.SET, DataType.STRING)
50
+ """
51
+ A tuple containing the compatible data types that the left and right expressions must return. This can for example
52
+ be used to indicate that arithmetic operations are compatible with :py:attr:`~.DataType.FLOAT` but not
53
+ :py:attr:`~.DataType.STRING` values.
54
+ """
55
+ result_type: _DataTypeDef = DataType.BOOLEAN
56
+ def __init__(self, context: 'Context', type_: str, left: ExpressionBase, right: ExpressionBase) -> None:
57
+ """
58
+ :param context: The context to use for evaluating the expression.
59
+ :type context: :py:class:`~rule_engine.engine.Context`
60
+ :param str type_: The grammar type of operator at the center of the expression. Subclasses must define operator
61
+ methods to handle evaluation based on this value.
62
+ :param left: The expression to the left of the operator.
63
+ :type left: :py:class:`.ExpressionBase`
64
+ :param right: The expression to the right of the operator.
65
+ :type right: :py:class:`.ExpressionBase`
66
+ """
67
+ self.context = context
68
+ type_ = type_.lower()
69
+ self.type = type_
70
+ evaluator = getattr(self, '_op_' + type_, None)
71
+ if evaluator is None:
72
+ raise errors.EngineError('unsupported operator: ' + type_)
73
+ self._evaluator: Callable[[Any], Any] = evaluator
74
+ self._assert_type_is_compatible(left)
75
+ self.left = left
76
+ self._assert_type_is_compatible(right)
77
+ self.right = right
78
+
79
+ @classmethod
80
+ def build(cls, context: 'Context', type_: str, left: ExpressionBase, right: ExpressionBase) -> ExpressionBase: # type: ignore[override]
81
+ left_built = left.build()
82
+ assert isinstance(left_built, ExpressionBase)
83
+ right_built = right.build()
84
+ assert isinstance(right_built, ExpressionBase)
85
+ reduced = cls(context, type_, left_built, right_built).reduce()
86
+ assert isinstance(reduced, ExpressionBase)
87
+ return reduced
88
+
89
+ def _assert_type_is_compatible(self, value: ExpressionBase) -> None:
90
+ if value.result_type == DataType.UNDEFINED:
91
+ return
92
+ if any(DataType.is_compatible(dt, value.result_type) for dt in self.compatible_types):
93
+ return
94
+ raise errors.EvaluationError('data type mismatch')
95
+
96
+ def __repr__(self) -> str:
97
+ return "<{} type={!r} >".format(self.__class__.__name__, self.type)
98
+
99
+ def evaluate(self, thing: Any) -> Any:
100
+ return self._evaluator(thing)
101
+
102
+ def reduce(self) -> ExpressionBase:
103
+ if not _is_reduced(self.left, self.right):
104
+ return self
105
+ return LiteralExpressionBase.from_value(self.context, self.evaluate(None))
106
+
107
+ def to_graphviz(self, digraph: Any, *args: Any, **kwargs: Any) -> None:
108
+ digraph.node(str(id(self)), "{}\ntype={!r}".format(self.__class__.__name__, self.type))
109
+ self.left.to_graphviz(digraph, *args, **kwargs)
110
+ self.right.to_graphviz(digraph, *args, **kwargs)
111
+ digraph.edge(str(id(self)), str(id(self.left)), label='left')
112
+ digraph.edge(str(id(self)), str(id(self.right)), label='right')
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # rule_engine/ast/binary/comparison.py
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above
13
+ # copyright notice, this list of conditions and the following disclaimer
14
+ # in the documentation and/or other materials provided with the
15
+ # distribution.
16
+ # * Neither the name of the project nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+
33
+ import functools
34
+ import operator
35
+ import re
36
+ from typing import Any, Callable
37
+
38
+ from ... import errors
39
+ from ...types import DataType, coerce_value
40
+ from ...types import _DataTypeDef
41
+
42
+ from ..base import _assert_not_nullable
43
+ from ..literal import StringExpression
44
+ from .base import BinaryExpressionBase
45
+
46
+ class LogicExpression(BinaryExpressionBase):
47
+ """A class for representing logical expressions from the grammar text such as "and" and "or"."""
48
+ def _op_and(self, thing: Any) -> bool:
49
+ return bool(self.left.evaluate(thing) and self.right.evaluate(thing))
50
+
51
+ def _op_or(self, thing: Any) -> bool:
52
+ return bool(self.left.evaluate(thing) or self.right.evaluate(thing))
53
+
54
+ class ComparisonExpression(BinaryExpressionBase):
55
+ """A class for representing comparison expressions from the grammar text such as equality checks."""
56
+ compatible_types: tuple[_DataTypeDef, ...] = BinaryExpressionBase.compatible_types + (DataType.OBJECT,)
57
+ def _op_eq(self, thing: Any) -> bool:
58
+ left_value = self.left.evaluate(thing)
59
+ right_value = self.right.evaluate(thing)
60
+ if type(left_value) is not type(right_value):
61
+ return False
62
+ return operator.eq(left_value, right_value)
63
+
64
+ def _op_ne(self, thing: Any) -> bool:
65
+ left_value = self.left.evaluate(thing)
66
+ right_value = self.right.evaluate(thing)
67
+ if type(left_value) is not type(right_value):
68
+ return True
69
+ return operator.ne(left_value, right_value)
70
+
71
+ class ArithmeticComparisonExpression(ComparisonExpression):
72
+ """
73
+ A class for representing arithmetic comparison expressions from the grammar text such as less-than-or-equal-to and
74
+ greater-than.
75
+ """
76
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.ARRAY, DataType.BOOLEAN, DataType.DATETIME, DataType.TIMEDELTA, DataType.FLOAT, DataType.NULL, DataType.STRING)
77
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
78
+ super(ArithmeticComparisonExpression, self).__init__(*args, **kwargs)
79
+ _assert_not_nullable(self.left.result_type, role='left ordered-comparison operand')
80
+ _assert_not_nullable(self.right.result_type, role='right ordered-comparison operand')
81
+ if self.left.result_type != DataType.UNDEFINED and self.right.result_type != DataType.UNDEFINED:
82
+ if self.left.result_type != self.right.result_type:
83
+ raise errors.EvaluationError('data type mismatch')
84
+
85
+ def __op_arithmetic(self, op: Callable[[Any, Any], Any], thing: Any) -> Any:
86
+ left_value = self.left.evaluate(thing)
87
+ right_value = self.right.evaluate(thing)
88
+ return self.__op_arithmetic_values(op, left_value, right_value)
89
+
90
+ def __op_arithmetic_arrays(self, op: Callable[[Any, Any], Any], left_value: Any, right_value: Any) -> Any:
91
+ for subleft_value, subright_value in zip(left_value, right_value):
92
+ if self.__op_arithmetic_values(operator.ne, subleft_value, subright_value):
93
+ return self.__op_arithmetic_values(op, subleft_value, subright_value)
94
+ if len(left_value) != len(right_value):
95
+ return self.__op_arithmetic_values(op, len(left_value), len(right_value))
96
+ return op in (operator.ge, operator.le)
97
+
98
+ def __op_arithmetic_values(self, op: Callable[[Any, Any], Any], left_value: Any, right_value: Any) -> Any:
99
+ if left_value is None and right_value is None:
100
+ return op in (operator.ge, operator.le)
101
+ elif isinstance(left_value, tuple) and isinstance(right_value, tuple):
102
+ return self.__op_arithmetic_arrays(op, left_value, right_value)
103
+ elif type(left_value) is not type(right_value):
104
+ raise errors.EvaluationError('data type mismatch')
105
+ return op(left_value, right_value)
106
+
107
+ _op_ge = functools.partialmethod(__op_arithmetic, operator.ge)
108
+ _op_gt = functools.partialmethod(__op_arithmetic, operator.gt)
109
+ _op_le = functools.partialmethod(__op_arithmetic, operator.le)
110
+ _op_lt = functools.partialmethod(__op_arithmetic, operator.lt)
111
+
112
+ class FuzzyComparisonExpression(ComparisonExpression):
113
+ """
114
+ A class for representing regular expression comparison expressions from the grammar text such as search and does not
115
+ match.
116
+ """
117
+ compatible_types: tuple[_DataTypeDef, ...] = (DataType.NULL, DataType.STRING)
118
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
119
+ super(FuzzyComparisonExpression, self).__init__(*args, **kwargs)
120
+ _assert_not_nullable(self.left.result_type, role='left regex operand')
121
+ _assert_not_nullable(self.right.result_type, role='right regex operand')
122
+ if isinstance(self.right, StringExpression):
123
+ self._right = self._compile_regex(self.right.evaluate(None))
124
+
125
+ def _compile_regex(self, regex: str) -> re.Pattern[str]:
126
+ try:
127
+ result = re.compile(regex, flags=self.context.regex_flags)
128
+ except re.error as error:
129
+ raise errors.RegexSyntaxError('invalid regular expression', error=error, value=regex) from None
130
+ return result
131
+
132
+ def __op_regex(self, regex_function: str, modifier: Callable[[Any, Any], Any], thing: Any) -> Any:
133
+ left = self.left.evaluate(thing)
134
+ if not isinstance(left, str) and left is not None:
135
+ raise errors.EvaluationError('data type mismatch')
136
+ if isinstance(self.right, StringExpression):
137
+ regex = self._right
138
+ else:
139
+ regex = self.right.evaluate(thing)
140
+ if isinstance(regex, str):
141
+ regex = self._compile_regex(regex)
142
+ elif regex is not None:
143
+ raise errors.EvaluationError('data type mismatch')
144
+ if left is None or regex is None:
145
+ return not modifier(left, regex)
146
+ match = getattr(regex, regex_function)(left)
147
+ if match is not None:
148
+ self.context._tls.regex_groups = coerce_value(match.groups())
149
+ return modifier(match, None)
150
+
151
+ _op_eq_fzm = functools.partialmethod(__op_regex, 'match', operator.is_not)
152
+ _op_eq_fzs = functools.partialmethod(__op_regex, 'search', operator.is_not)
153
+ _op_ne_fzm = functools.partialmethod(__op_regex, 'match', operator.is_)
154
+ _op_ne_fzs = functools.partialmethod(__op_regex, 'search', operator.is_)
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # rule_engine/ast/expression/__init__.py
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are
8
+ # met:
9
+ #
10
+ # * Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # * Redistributions in binary form must reproduce the above
13
+ # copyright notice, this list of conditions and the following disclaimer
14
+ # in the documentation and/or other materials provided with the
15
+ # distribution.
16
+ # * Neither the name of the project nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+
33
+ from .access import ContainsExpression, GetAttributeExpression, GetItemExpression, GetSliceExpression
34
+ from .control import ComprehensionExpression, TernaryExpression, UnaryExpression
35
+ from .resolution import FunctionCallExpression, SymbolExpression