PyDecisionGraph 0.3.1.post2__cp313-cp313-win_amd64.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.
- decision_graph/__init__.py +26 -0
- decision_graph/decision_tree/__init__.py +85 -0
- decision_graph/decision_tree/bake/__infra__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.py +29 -0
- decision_graph/decision_tree/bake/c_action.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/decision_tree/bake/c_action.pxd +54 -0
- decision_graph/decision_tree/bake/c_action.pyi +126 -0
- decision_graph/decision_tree/bake/c_action.pyx +65 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
- decision_graph/decision_tree/bake/c_bake.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/decision_tree/bake/c_bake.pxd +52 -0
- decision_graph/decision_tree/bake/c_bake.pyi +151 -0
- decision_graph/decision_tree/bake/c_bake.pyx +142 -0
- decision_graph/decision_tree/bake/c_collections.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/decision_tree/bake/c_collections.pxd +52 -0
- decision_graph/decision_tree/bake/c_collections.pyi +193 -0
- decision_graph/decision_tree/bake/c_collections.pyx +161 -0
- decision_graph/decision_tree/bake/c_const.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/decision_tree/bake/c_const.pxd +56 -0
- decision_graph/decision_tree/bake/c_const.pyi +305 -0
- decision_graph/decision_tree/bake/c_const.pyx +278 -0
- decision_graph/decision_tree/bake/c_edge.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/decision_tree/bake/c_edge.pxd +117 -0
- decision_graph/decision_tree/bake/c_edge.pyi +192 -0
- decision_graph/decision_tree/bake/c_edge.pyx +270 -0
- decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/decision_tree/bake/c_eval.pxd +40 -0
- decision_graph/decision_tree/bake/c_expr.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/decision_tree/bake/c_expr.pxd +129 -0
- decision_graph/decision_tree/bake/c_expr.pyi +280 -0
- decision_graph/decision_tree/bake/c_expr.pyx +272 -0
- decision_graph/decision_tree/bake/c_hierarchy.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
- decision_graph/decision_tree/bake/c_logic_group.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
- decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
- decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
- decision_graph/decision_tree/bake/c_node.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/decision_tree/bake/c_node.pxd +381 -0
- decision_graph/decision_tree/bake/c_node.pyi +438 -0
- decision_graph/decision_tree/bake/c_node.pyx +639 -0
- decision_graph/decision_tree/bake/c_reconstruct.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
- decision_graph/decision_tree/bake/c_var.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/decision_tree/bake/c_var.pxd +205 -0
- decision_graph/decision_tree/bake/c_var.pyi +271 -0
- decision_graph/decision_tree/bake/c_var.pyx +228 -0
- decision_graph/decision_tree/capi/__init__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.py +57 -0
- decision_graph/decision_tree/capi/c_abc.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_abc.pxd +246 -0
- decision_graph/decision_tree/capi/c_abc.pyi +770 -0
- decision_graph/decision_tree/capi/c_abc.pyx +1529 -0
- decision_graph/decision_tree/capi/c_collection.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_collection.pxd +22 -0
- decision_graph/decision_tree/capi/c_collection.pyi +245 -0
- decision_graph/decision_tree/capi/c_collection.pyx +138 -0
- decision_graph/decision_tree/capi/c_node.cp313-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_node.pxd +95 -0
- decision_graph/decision_tree/capi/c_node.pyi +492 -0
- decision_graph/decision_tree/capi/c_node.pyx +551 -0
- decision_graph/decision_tree/exc.py +189 -0
- decision_graph/decision_tree/native/__init__.py +57 -0
- decision_graph/decision_tree/native/abc.py +1164 -0
- decision_graph/decision_tree/native/collection.py +134 -0
- decision_graph/decision_tree/native/node.py +521 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59143 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39220 -0
- decision_graph/logic_group/__init__.py +25 -0
- decision_graph/logic_group/base.py +100 -0
- decision_graph/logic_group/pending_request.py +384 -0
- decision_graph/webui/__init__.py +24 -0
- decision_graph/webui/bake/__init__.py +65 -0
- decision_graph/webui/bake/app.py +488 -0
- decision_graph/webui/bake/static/d3.v7.min.js +2 -0
- decision_graph/webui/bake/static/script.js +1440 -0
- decision_graph/webui/bake/static/style.css +920 -0
- decision_graph/webui/bake/templates/index.html +183 -0
- decision_graph/webui/bake/templates/offline.html +184 -0
- decision_graph/webui/capi/__init__.py +54 -0
- decision_graph/webui/capi/app.py +366 -0
- decision_graph/webui/capi/main.py +53 -0
- decision_graph/webui/capi/static/d3.v7.min.js +2 -0
- decision_graph/webui/capi/static/script.js +1026 -0
- decision_graph/webui/capi/static/style.css +656 -0
- decision_graph/webui/capi/templates/index.html +93 -0
- decision_graph/webui/capi/templates/offline.html +94 -0
- decision_graph/webui/native/__init__.py +54 -0
- decision_graph/webui/native/app.py +366 -0
- decision_graph/webui/native/main.py +53 -0
- decision_graph/webui/native/static/d3.v7.min.js +2 -0
- decision_graph/webui/native/static/script.js +1026 -0
- decision_graph/webui/native/static/style.css +656 -0
- decision_graph/webui/native/templates/index.html +93 -0
- decision_graph/webui/native/templates/offline.html +94 -0
- pydecisiongraph-0.3.1.post2.dist-info/METADATA +130 -0
- pydecisiongraph-0.3.1.post2.dist-info/RECORD +144 -0
- pydecisiongraph-0.3.1.post2.dist-info/WHEEL +5 -0
- pydecisiongraph-0.3.1.post2.dist-info/licenses/LICENSE +373 -0
- pydecisiongraph-0.3.1.post2.dist-info/top_level.txt +2 -0
- tests/bake_c_evaluator.cp313-win_amd64.pyd +0 -0
- tests/bake_eval_probe.cp313-win_amd64.pyd +0 -0
|
@@ -0,0 +1,1529 @@
|
|
|
1
|
+
import linecache
|
|
2
|
+
import operator
|
|
3
|
+
import sys
|
|
4
|
+
import traceback
|
|
5
|
+
import uuid
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
from cpython.mem cimport PyMem_Calloc, PyMem_Free
|
|
9
|
+
from cpython.pystate cimport PyThreadState_Get
|
|
10
|
+
from cpython.ref cimport Py_INCREF, Py_DECREF
|
|
11
|
+
from cython import final
|
|
12
|
+
from libc.stdint cimport uintptr_t
|
|
13
|
+
|
|
14
|
+
from .. import LOGGER
|
|
15
|
+
from ..exc import *
|
|
16
|
+
|
|
17
|
+
LOGGER = LOGGER.getChild('abc')
|
|
18
|
+
|
|
19
|
+
cdef dict GLOBAL_SINGLETON = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
cdef class Singleton:
|
|
23
|
+
def __cinit__(self):
|
|
24
|
+
cdef tuple reg_key = (self.__class__.__module__, self.__class__.__qualname__)
|
|
25
|
+
if reg_key in GLOBAL_SINGLETON:
|
|
26
|
+
raise RuntimeError(f'Can not initialize new singleton {self.__class__}')
|
|
27
|
+
else:
|
|
28
|
+
GLOBAL_SINGLETON[reg_key] = self
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
cdef class NodeEdgeCondition(Singleton):
|
|
32
|
+
def __cinit__(self, object value=None):
|
|
33
|
+
if value is not None:
|
|
34
|
+
Py_INCREF(value)
|
|
35
|
+
self.value_addr = <PyObject*> value
|
|
36
|
+
else:
|
|
37
|
+
self.value_addr = NULL
|
|
38
|
+
|
|
39
|
+
def __hash__(self):
|
|
40
|
+
return (<object> self.value_addr).__hash__()
|
|
41
|
+
|
|
42
|
+
def __repr__(self):
|
|
43
|
+
return f'<{self.__class__.__name__} {<uintptr_t> <PyObject*> self:#0x}>'
|
|
44
|
+
|
|
45
|
+
property value:
|
|
46
|
+
def __get__(self):
|
|
47
|
+
if not self.value_addr:
|
|
48
|
+
raise ValueError("Condition has no value assigned.")
|
|
49
|
+
return <object> self.value_addr
|
|
50
|
+
|
|
51
|
+
def __set__(self, value):
|
|
52
|
+
Py_INCREF(self.value)
|
|
53
|
+
if self.value_addr is not NULL:
|
|
54
|
+
Py_DECREF(<object> self.value_addr)
|
|
55
|
+
self.value_addr = <PyObject*> value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
cdef class ConditionElse(NodeEdgeCondition):
|
|
59
|
+
def __cinit__(self, *args, **kwargs):
|
|
60
|
+
self.value_addr = NULL
|
|
61
|
+
|
|
62
|
+
def __hash__(self):
|
|
63
|
+
return <uintptr_t> <PyObject*> self
|
|
64
|
+
|
|
65
|
+
def __repr__(self):
|
|
66
|
+
return f'<CONDITION Internal {<uintptr_t> <PyObject*> self:#0x}>(Else)'
|
|
67
|
+
|
|
68
|
+
def __str__(self):
|
|
69
|
+
return 'Else'
|
|
70
|
+
|
|
71
|
+
property value:
|
|
72
|
+
def __set__(self, value):
|
|
73
|
+
raise NotImplementedError()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
cdef class ConditionAny(NodeEdgeCondition):
|
|
77
|
+
def __cinit__(self, *args, **kwargs):
|
|
78
|
+
self.value_addr = NULL
|
|
79
|
+
|
|
80
|
+
def __hash__(self):
|
|
81
|
+
return <uintptr_t> <PyObject*> self
|
|
82
|
+
|
|
83
|
+
def __repr__(self):
|
|
84
|
+
return f'<CONDITION {<uintptr_t> <PyObject*> self:#0x}>(Unconditional)'
|
|
85
|
+
|
|
86
|
+
def __str__(self):
|
|
87
|
+
return 'Unconditional'
|
|
88
|
+
|
|
89
|
+
property value:
|
|
90
|
+
def __set__(self, value):
|
|
91
|
+
raise NotImplementedError()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
cdef class ConditionAuto(NodeEdgeCondition):
|
|
95
|
+
def __cinit__(self, *args, **kwargs):
|
|
96
|
+
self.value_addr = NULL
|
|
97
|
+
|
|
98
|
+
def __hash__(self):
|
|
99
|
+
raise NotImplementedError()
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
return f'<CONDITION Internal {<uintptr_t> <PyObject*> self:#0x}>(AutoInfer)'
|
|
103
|
+
|
|
104
|
+
def __str__(self):
|
|
105
|
+
return 'AutoInfer'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
cdef class BinaryCondition(NodeEdgeCondition):
|
|
109
|
+
def __cinit__(self, *args, **kwargs):
|
|
110
|
+
self.value_addr = NULL
|
|
111
|
+
|
|
112
|
+
def __hash__(self):
|
|
113
|
+
return <uintptr_t> <PyObject*> self
|
|
114
|
+
|
|
115
|
+
property value:
|
|
116
|
+
def __set__(self, value):
|
|
117
|
+
raise NotImplementedError()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
cdef class ConditionTrue(BinaryCondition):
|
|
121
|
+
def __repr__(self):
|
|
122
|
+
return f'<CONDITION {<uintptr_t> <PyObject*> self:#0x}>(True)'
|
|
123
|
+
|
|
124
|
+
def __str__(self):
|
|
125
|
+
return 'True'
|
|
126
|
+
|
|
127
|
+
def __bool__(self):
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
def __int__(self):
|
|
131
|
+
return 1
|
|
132
|
+
|
|
133
|
+
def __neg__(self):
|
|
134
|
+
return FALSE_CONDITION
|
|
135
|
+
|
|
136
|
+
def __invert__(self):
|
|
137
|
+
return FALSE_CONDITION
|
|
138
|
+
|
|
139
|
+
property value:
|
|
140
|
+
def __get__(self):
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
cdef class ConditionFalse(BinaryCondition):
|
|
145
|
+
def __repr__(self):
|
|
146
|
+
return f'<CONDITION {<uintptr_t> <PyObject*> self:#0x}>(False)'
|
|
147
|
+
|
|
148
|
+
def __str__(self):
|
|
149
|
+
return 'False'
|
|
150
|
+
|
|
151
|
+
def __bool__(self):
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
def __int__(self):
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
def __neg__(self):
|
|
158
|
+
return TRUE_CONDITION
|
|
159
|
+
|
|
160
|
+
def __invert__(self):
|
|
161
|
+
return TRUE_CONDITION
|
|
162
|
+
|
|
163
|
+
property value:
|
|
164
|
+
def __get__(self):
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
cdef ConditionAny NO_CONDITION = ConditionAny()
|
|
169
|
+
cdef ConditionElse ELSE_CONDITION = ConditionElse()
|
|
170
|
+
cdef ConditionAuto AUTO_CONDITION = ConditionAuto()
|
|
171
|
+
cdef ConditionTrue TRUE_CONDITION = ConditionTrue()
|
|
172
|
+
cdef ConditionFalse FALSE_CONDITION = ConditionFalse()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
globals().update(
|
|
176
|
+
NO_CONDITION=NO_CONDITION,
|
|
177
|
+
ELSE_CONDITION=ELSE_CONDITION,
|
|
178
|
+
AUTO_CONDITION=AUTO_CONDITION,
|
|
179
|
+
TRUE_CONDITION=TRUE_CONDITION,
|
|
180
|
+
FALSE_CONDITION=FALSE_CONDITION
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
cdef class SkipContextsBlock:
|
|
185
|
+
def __cinit__(self):
|
|
186
|
+
self.skip_exception = type(f"{self.__class__.__name__}SkipException", (EmptyBlock,), {"owner": self})
|
|
187
|
+
self.tracer_override = False
|
|
188
|
+
self.default_entry_check = True
|
|
189
|
+
|
|
190
|
+
cdef bint c_entry_check(self):
|
|
191
|
+
return self.default_entry_check
|
|
192
|
+
|
|
193
|
+
cdef void c_on_enter(self):
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
cdef void c_on_exit(self):
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
# === Python Interfaces ===
|
|
200
|
+
@final
|
|
201
|
+
def __enter__(self):
|
|
202
|
+
if self.c_entry_check(): # Check if the expression evaluates to True
|
|
203
|
+
self.c_on_enter()
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
cdef PyThreadState* tstate = PyThreadState_Get()
|
|
207
|
+
PyThreadState_EnterTracing(tstate)
|
|
208
|
+
|
|
209
|
+
self.cframe = sys._getframe()
|
|
210
|
+
self.enter_line = (self.cframe.f_code.co_filename, self.cframe.f_lineno)
|
|
211
|
+
|
|
212
|
+
self.cframe_tracer_sig_count = 0
|
|
213
|
+
self.cframe_tracer = self.cframe.f_trace
|
|
214
|
+
self.cframe.f_trace = self.cframe_tracer_skipper
|
|
215
|
+
if self.cframe_tracer is not None:
|
|
216
|
+
warnings.warn('Not supporting a custom tracer, must clear it before passing in.')
|
|
217
|
+
|
|
218
|
+
self.global_tracer_sig_count = 0
|
|
219
|
+
self.global_tracer = sys.gettrace()
|
|
220
|
+
sys.settrace(self.global_tracer_skipper)
|
|
221
|
+
|
|
222
|
+
self.global_profiler_sig_count = 0
|
|
223
|
+
self.global_profiler = sys.getprofile()
|
|
224
|
+
sys.setprofile(self.global_profile_tracer)
|
|
225
|
+
|
|
226
|
+
self.tracer_override = True
|
|
227
|
+
PyThreadState_LeaveTracing(tstate)
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
@final
|
|
231
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
232
|
+
self.restore_tracers(True)
|
|
233
|
+
|
|
234
|
+
if exc_type is None:
|
|
235
|
+
self.c_on_exit()
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
if issubclass(exc_type, self.skip_exception):
|
|
239
|
+
# in this case, the block is not even entered, so no need to call c_on_exit cleanup.
|
|
240
|
+
return True
|
|
241
|
+
|
|
242
|
+
self.c_on_exit()
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
def restore_tracers(self, override=False):
|
|
246
|
+
if self.tracer_override:
|
|
247
|
+
print('[restore_tracers] restoring tracers...')
|
|
248
|
+
if self.cframe_tracer is not None:
|
|
249
|
+
print('[restore_tracers] restoring cframe tracer:', self.cframe_tracer)
|
|
250
|
+
self.cframe.f_trace = self.cframe_tracer
|
|
251
|
+
else:
|
|
252
|
+
self.cframe.f_trace = None
|
|
253
|
+
|
|
254
|
+
if self.global_profiler is not None:
|
|
255
|
+
print('[restore_tracers] restoring global profiler:', self.global_profiler)
|
|
256
|
+
sys.setprofile(self.global_profiler)
|
|
257
|
+
else:
|
|
258
|
+
sys.setprofile(None)
|
|
259
|
+
|
|
260
|
+
if self.global_tracer is not None:
|
|
261
|
+
print('[restore_tracers] restoring global tracer:', self.global_tracer)
|
|
262
|
+
sys.settrace(self.global_tracer)
|
|
263
|
+
else:
|
|
264
|
+
sys.settrace(None)
|
|
265
|
+
|
|
266
|
+
self.tracer_override = False
|
|
267
|
+
|
|
268
|
+
def cframe_tracer_skipper(self, frame, event, arg):
|
|
269
|
+
print(f'[cframe_tracer_skipper] sig {self.cframe_tracer_sig_count}...', frame, event, arg)
|
|
270
|
+
cdef PyThreadState* tstate
|
|
271
|
+
cdef str line
|
|
272
|
+
self.cframe_tracer_sig_count += 1
|
|
273
|
+
if event == 'line':
|
|
274
|
+
line = linecache.getline(frame.f_code.co_filename, frame.f_lineno).strip()
|
|
275
|
+
print('[cframe_tracer_skipper] line:', line)
|
|
276
|
+
if line.startswith(('pass', '...')):
|
|
277
|
+
return self.cframe_tracer_skipper
|
|
278
|
+
elif self.enter_line == (frame.f_code.co_filename, frame.f_lineno):
|
|
279
|
+
tstate = PyThreadState_Get()
|
|
280
|
+
PyThreadState_EnterTracing(tstate)
|
|
281
|
+
self.restore_tracers()
|
|
282
|
+
PyThreadState_LeaveTracing(tstate)
|
|
283
|
+
return self.cframe_tracer_skipper
|
|
284
|
+
tstate = PyThreadState_Get()
|
|
285
|
+
PyThreadState_EnterTracing(tstate)
|
|
286
|
+
self.restore_tracers()
|
|
287
|
+
PyThreadState_LeaveTracing(tstate)
|
|
288
|
+
raise self.skip_exception('')
|
|
289
|
+
return self.cframe_tracer_skipper
|
|
290
|
+
|
|
291
|
+
def global_tracer_skipper(self, frame, event, arg):
|
|
292
|
+
print(f'[global_tracer_skipper] sig {self.global_tracer_sig_count}...', frame, event, arg)
|
|
293
|
+
self.global_tracer_sig_count += 1
|
|
294
|
+
return self.global_tracer_skipper
|
|
295
|
+
|
|
296
|
+
def global_profile_tracer(self, frame, event, arg):
|
|
297
|
+
print(f'[global_profile_tracer] sig {self.global_profiler_sig_count}...', frame, event, arg)
|
|
298
|
+
cdef PyThreadState* tstate
|
|
299
|
+
self.global_profiler_sig_count += 1
|
|
300
|
+
if event == 'c_call':
|
|
301
|
+
tstate = PyThreadState_Get()
|
|
302
|
+
PyThreadState_EnterTracing(tstate)
|
|
303
|
+
self.restore_tracers()
|
|
304
|
+
PyThreadState_LeaveTracing(tstate)
|
|
305
|
+
raise self.skip_exception('')
|
|
306
|
+
return self.global_profile_tracer
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
cdef class LogicExpression(SkipContextsBlock):
|
|
310
|
+
def __cinit__(self, *, object expression=None, type dtype=None, str repr=None, object uid=None, **kwargs):
|
|
311
|
+
self.expression = expression
|
|
312
|
+
self.dtype = dtype
|
|
313
|
+
self.repr = repr if repr is not None else str(expression)
|
|
314
|
+
self.uid = uuid.uuid4() if uid is None else uid
|
|
315
|
+
|
|
316
|
+
cdef bint c_entry_check(self):
|
|
317
|
+
return bool(self.c_eval(False))
|
|
318
|
+
|
|
319
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
320
|
+
if isinstance(self.expression, (float, int, bool, str)):
|
|
321
|
+
value = self.expression
|
|
322
|
+
elif callable(self.expression):
|
|
323
|
+
value = self.expression()
|
|
324
|
+
elif isinstance(self.expression, Exception):
|
|
325
|
+
raise self.expression
|
|
326
|
+
else:
|
|
327
|
+
raise TypeError(f"Unsupported expression type: {type(self.expression)}.")
|
|
328
|
+
|
|
329
|
+
if self.dtype is None:
|
|
330
|
+
pass # No type enforcement
|
|
331
|
+
elif enforce_dtype:
|
|
332
|
+
value = self.dtype(value)
|
|
333
|
+
elif not isinstance(value, self.dtype):
|
|
334
|
+
LOGGER.warning(f"Evaluated value {value} does not match dtype {self.dtype.__name__}.")
|
|
335
|
+
|
|
336
|
+
return value
|
|
337
|
+
|
|
338
|
+
@staticmethod
|
|
339
|
+
cdef LogicExpression c_cast(object value, type dtype):
|
|
340
|
+
if isinstance(value, LogicExpression):
|
|
341
|
+
return value
|
|
342
|
+
if isinstance(value, (int, float, bool)):
|
|
343
|
+
return LogicExpression(
|
|
344
|
+
expression=value,
|
|
345
|
+
dtype=dtype or type(value),
|
|
346
|
+
repr=str(value)
|
|
347
|
+
)
|
|
348
|
+
if callable(value):
|
|
349
|
+
return LogicExpression(
|
|
350
|
+
expression=value,
|
|
351
|
+
dtype=dtype,
|
|
352
|
+
repr=f"Eval({value})"
|
|
353
|
+
)
|
|
354
|
+
if isinstance(value, Exception):
|
|
355
|
+
return LogicExpression(
|
|
356
|
+
expression=value,
|
|
357
|
+
dtype=dtype,
|
|
358
|
+
repr=f"Raises({type(value).__name__}: {value})"
|
|
359
|
+
)
|
|
360
|
+
raise TypeError(f"Unsupported type for LogicExpression conversion: {type(value)}.")
|
|
361
|
+
|
|
362
|
+
@staticmethod
|
|
363
|
+
cdef LogicExpression c_math_op(LogicExpression self, object other, object op, str operator_str, type dtype):
|
|
364
|
+
other_expr = LogicExpression.cast(other)
|
|
365
|
+
|
|
366
|
+
if dtype is None:
|
|
367
|
+
dtype = self.dtype
|
|
368
|
+
|
|
369
|
+
new_expr = LogicExpression(
|
|
370
|
+
expression=lambda: op(self.eval(), other_expr.eval()),
|
|
371
|
+
dtype=dtype,
|
|
372
|
+
repr=f"({self.repr} {operator_str} {other_expr.repr})",
|
|
373
|
+
)
|
|
374
|
+
return new_expr
|
|
375
|
+
|
|
376
|
+
# === Python Interface ===
|
|
377
|
+
|
|
378
|
+
def eval(self, enforce_dtype=False):
|
|
379
|
+
return self.c_eval(enforce_dtype)
|
|
380
|
+
|
|
381
|
+
@classmethod
|
|
382
|
+
def cast(cls, object value, type dtype=None):
|
|
383
|
+
return LogicExpression.c_cast(value, dtype)
|
|
384
|
+
|
|
385
|
+
def __bool__(self) -> bool:
|
|
386
|
+
return bool(self.eval())
|
|
387
|
+
|
|
388
|
+
def __and__(self, object other) -> LogicExpression:
|
|
389
|
+
other_expr = self.cast(value=other, dtype=bool)
|
|
390
|
+
new_expr = LogicExpression(
|
|
391
|
+
expression=lambda: self.eval() and other_expr.eval(),
|
|
392
|
+
dtype=bool,
|
|
393
|
+
repr=f"({self.repr} and {other_expr.repr})"
|
|
394
|
+
)
|
|
395
|
+
return new_expr
|
|
396
|
+
|
|
397
|
+
def __eq__(self, object other) -> LogicExpression:
|
|
398
|
+
if isinstance(other, LogicExpression):
|
|
399
|
+
other_value = other.eval()
|
|
400
|
+
else:
|
|
401
|
+
other_value = other
|
|
402
|
+
|
|
403
|
+
return LogicExpression(
|
|
404
|
+
expression=lambda: self.eval() == other_value,
|
|
405
|
+
dtype=bool,
|
|
406
|
+
repr=f"({self.repr} == {repr(other_value)})"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def __or__(self, object other) -> LogicExpression:
|
|
410
|
+
other_expr = self.cast(value=other, dtype=bool)
|
|
411
|
+
new_expr = LogicExpression(
|
|
412
|
+
expression=lambda: self.eval() or other_expr.eval(),
|
|
413
|
+
dtype=bool,
|
|
414
|
+
repr=f"({self.repr} or {other_expr.repr})"
|
|
415
|
+
)
|
|
416
|
+
return new_expr
|
|
417
|
+
|
|
418
|
+
# Math operators
|
|
419
|
+
def __add__(self, object other):
|
|
420
|
+
return LogicExpression.c_math_op(self, other, operator.add, "+", None)
|
|
421
|
+
|
|
422
|
+
def __sub__(self, object other):
|
|
423
|
+
return LogicExpression.c_math_op(self, other, operator.sub, "-", None)
|
|
424
|
+
|
|
425
|
+
def __mul__(self, object other):
|
|
426
|
+
return LogicExpression.c_math_op(self, other, operator.mul, "*", None)
|
|
427
|
+
|
|
428
|
+
def __truediv__(self, object other):
|
|
429
|
+
return LogicExpression.c_math_op(self, other, operator.truediv, "/", None)
|
|
430
|
+
|
|
431
|
+
def __floordiv__(self, object other):
|
|
432
|
+
return LogicExpression.c_math_op(self, other, operator.floordiv, "//", None)
|
|
433
|
+
|
|
434
|
+
def __pow__(self, object other):
|
|
435
|
+
return LogicExpression.c_math_op(self, other, operator.pow, "**", None)
|
|
436
|
+
|
|
437
|
+
# Comparison operators, note that __eq__, __ne__ is special and should not implement as math operator
|
|
438
|
+
def __lt__(self, object other):
|
|
439
|
+
return LogicExpression.c_math_op(self, other, operator.lt, "<", bool)
|
|
440
|
+
|
|
441
|
+
def __le__(self, object other):
|
|
442
|
+
return LogicExpression.c_math_op(self, other, operator.le, "<=", bool)
|
|
443
|
+
|
|
444
|
+
def __gt__(self, object other):
|
|
445
|
+
return LogicExpression.c_math_op(self, other, operator.gt, ">", bool)
|
|
446
|
+
|
|
447
|
+
def __ge__(self, object other):
|
|
448
|
+
return LogicExpression.c_math_op(self, other, operator.ge, ">=", bool)
|
|
449
|
+
|
|
450
|
+
def __repr__(self) -> str:
|
|
451
|
+
return f"<{self.__class__.__name__}>(dtype={'Any' if self.dtype is None else self.dtype.__name__}, repr={self.repr})"
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
cdef class LogicGroupManager(Singleton):
|
|
455
|
+
def __cinit__(self):
|
|
456
|
+
self._cache = {}
|
|
457
|
+
self._active_groups = <LogicGroupStack*> PyMem_Calloc(1, sizeof(LogicGroupStack))
|
|
458
|
+
self._active_nodes = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
459
|
+
self._breakpoint_nodes = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
460
|
+
self._shelved_state = <ShelvedStateStack*> PyMem_Calloc(1, sizeof(ShelvedStateStack))
|
|
461
|
+
|
|
462
|
+
self.inspection_mode = False # run node graph in inspection mode, without evaluating value, to map the graph
|
|
463
|
+
self.vigilant_mode = False # disable auto generation of missing action nodes
|
|
464
|
+
|
|
465
|
+
def __dealloc__(self):
|
|
466
|
+
if self._active_groups:
|
|
467
|
+
while self._active_groups.size:
|
|
468
|
+
self.c_lg_exit()
|
|
469
|
+
PyMem_Free(self._active_groups)
|
|
470
|
+
|
|
471
|
+
if self._active_nodes:
|
|
472
|
+
while self._active_nodes.size:
|
|
473
|
+
LogicGroupManager.c_ln_stack_pop(self._active_nodes)
|
|
474
|
+
PyMem_Free(self._active_nodes)
|
|
475
|
+
|
|
476
|
+
if self._breakpoint_nodes:
|
|
477
|
+
while self._breakpoint_nodes.size:
|
|
478
|
+
LogicGroupManager.c_ln_stack_pop(self._breakpoint_nodes)
|
|
479
|
+
PyMem_Free(self._breakpoint_nodes)
|
|
480
|
+
|
|
481
|
+
if self._shelved_state:
|
|
482
|
+
while self._shelved_state.size:
|
|
483
|
+
self.c_unshelve()
|
|
484
|
+
PyMem_Free(self._shelved_state)
|
|
485
|
+
|
|
486
|
+
@staticmethod
|
|
487
|
+
cdef inline void c_ln_stack_push(LogicNodeStack* stack, LogicNode logic_node):
|
|
488
|
+
cdef LogicNodeFrame* frame = <LogicNodeFrame*> PyMem_Calloc(1, sizeof(LogicNodeFrame))
|
|
489
|
+
frame.logic_node = <PyObject*> logic_node
|
|
490
|
+
Py_INCREF(logic_node)
|
|
491
|
+
frame.prev = stack.top
|
|
492
|
+
stack.top = frame
|
|
493
|
+
stack.size += 1
|
|
494
|
+
|
|
495
|
+
@staticmethod
|
|
496
|
+
cdef inline void c_ln_stack_pop(LogicNodeStack* stack, LogicNode logic_node=None):
|
|
497
|
+
if not stack.top:
|
|
498
|
+
raise RuntimeError("No active LogicNode")
|
|
499
|
+
cdef LogicNodeFrame* frame = stack.top
|
|
500
|
+
cdef PyObject* ln = NULL if logic_node is None else <PyObject*> logic_node
|
|
501
|
+
|
|
502
|
+
if ln and frame.logic_node != ln:
|
|
503
|
+
raise AssertionError("The LogicNode is not currently active.")
|
|
504
|
+
|
|
505
|
+
stack.top = frame.prev
|
|
506
|
+
stack.size -= 1
|
|
507
|
+
Py_DECREF(<object> frame.logic_node)
|
|
508
|
+
PyMem_Free(frame)
|
|
509
|
+
|
|
510
|
+
@staticmethod
|
|
511
|
+
cdef inline void c_ln_stack_remove(LogicNodeStack* stack, LogicNode logic_node):
|
|
512
|
+
cdef PyObject* target = <PyObject*> logic_node
|
|
513
|
+
cdef LogicNodeFrame* prev = NULL
|
|
514
|
+
cdef LogicNodeFrame* curc = stack.top
|
|
515
|
+
|
|
516
|
+
# Traverse the stack linked list
|
|
517
|
+
while curc is not NULL:
|
|
518
|
+
if curc.logic_node == target:
|
|
519
|
+
# Found. Unlink it.
|
|
520
|
+
if prev:
|
|
521
|
+
prev.prev = curc.prev
|
|
522
|
+
else:
|
|
523
|
+
stack.top = curc.prev
|
|
524
|
+
|
|
525
|
+
stack.size -= 1
|
|
526
|
+
|
|
527
|
+
Py_DECREF(<object> curc.logic_node)
|
|
528
|
+
PyMem_Free(curc)
|
|
529
|
+
return
|
|
530
|
+
|
|
531
|
+
prev = curc
|
|
532
|
+
curc = curc.prev
|
|
533
|
+
|
|
534
|
+
# Not found
|
|
535
|
+
raise NodeNotFountError("LogicNode not found in stack")
|
|
536
|
+
|
|
537
|
+
@staticmethod
|
|
538
|
+
cdef inline LogicNodeFrame* c_ln_stack_locate(LogicNodeStack* stack, LogicNode logic_node):
|
|
539
|
+
cdef PyObject* target = <PyObject*> logic_node
|
|
540
|
+
cdef LogicNodeFrame* frame = stack.top
|
|
541
|
+
|
|
542
|
+
# Traverse the stack linked list
|
|
543
|
+
while frame:
|
|
544
|
+
if frame.logic_node == target:
|
|
545
|
+
return frame
|
|
546
|
+
frame = frame.prev
|
|
547
|
+
return NULL
|
|
548
|
+
|
|
549
|
+
@staticmethod
|
|
550
|
+
cdef inline void c_ln_stack_relocate(LogicNodeStack* original_stack, LogicNodeStack* new_stack, LogicNode logic_node):
|
|
551
|
+
cdef PyObject* target = <PyObject*> logic_node
|
|
552
|
+
cdef LogicNodeFrame* prev = NULL
|
|
553
|
+
cdef LogicNodeFrame* frame = original_stack.top
|
|
554
|
+
|
|
555
|
+
# Locate the frame in original_stack
|
|
556
|
+
while frame:
|
|
557
|
+
if frame.logic_node == target:
|
|
558
|
+
# Found: unlink from original_stack
|
|
559
|
+
if prev is NULL:
|
|
560
|
+
# Frame is at the top
|
|
561
|
+
original_stack.top = frame.prev
|
|
562
|
+
else:
|
|
563
|
+
prev.prev = frame.prev
|
|
564
|
+
|
|
565
|
+
original_stack.size -= 1
|
|
566
|
+
|
|
567
|
+
# Push onto new_stack by relinking
|
|
568
|
+
frame.prev = new_stack.top
|
|
569
|
+
new_stack.top = frame
|
|
570
|
+
new_stack.size += 1
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
prev = frame
|
|
574
|
+
frame = frame.prev
|
|
575
|
+
raise NodeNotFountError("LogicNode not found in original stack")
|
|
576
|
+
|
|
577
|
+
cdef inline LogicGroup c_cached_init(self, str name, type cls, dict kwargs):
|
|
578
|
+
cdef tuple reg_key = (cls.__module__, cls.__qualname__)
|
|
579
|
+
cdef dict registry
|
|
580
|
+
if reg_key in self._cache:
|
|
581
|
+
registry = self._cache[reg_key]
|
|
582
|
+
else:
|
|
583
|
+
registry = self._cache[reg_key] = {}
|
|
584
|
+
|
|
585
|
+
if name in registry:
|
|
586
|
+
return registry[name]
|
|
587
|
+
|
|
588
|
+
cdef LogicGroup logic_group = cls(name=name, **kwargs)
|
|
589
|
+
registry[name] = logic_group
|
|
590
|
+
return logic_group
|
|
591
|
+
|
|
592
|
+
cdef inline void c_lg_enter(self, LogicGroup logic_group):
|
|
593
|
+
# Step 1: Update parent info
|
|
594
|
+
cdef LogicGroupFrame* frame = self._active_groups.top
|
|
595
|
+
if frame:
|
|
596
|
+
logic_group.parent = <LogicGroup> <object> frame.logic_group
|
|
597
|
+
|
|
598
|
+
# Step 2: Push to active_groups stack
|
|
599
|
+
frame = <LogicGroupFrame*> PyMem_Calloc(1, sizeof(LogicGroupFrame))
|
|
600
|
+
frame.logic_group = <PyObject*> logic_group
|
|
601
|
+
Py_INCREF(logic_group)
|
|
602
|
+
frame.prev = self._active_groups.top
|
|
603
|
+
self._active_groups.top = frame
|
|
604
|
+
self._active_groups.size += 1
|
|
605
|
+
|
|
606
|
+
cdef inline void c_lg_exit(self, LogicGroup logic_group=None):
|
|
607
|
+
cdef LogicGroupFrame* frame = self._active_groups.top
|
|
608
|
+
|
|
609
|
+
if not frame:
|
|
610
|
+
raise RuntimeError("No active LogicGroup")
|
|
611
|
+
|
|
612
|
+
# Step 1: Validate logic group if provided
|
|
613
|
+
if logic_group is not None:
|
|
614
|
+
if frame.logic_group != <PyObject*> logic_group:
|
|
615
|
+
raise AssertionError("The LogicGroup is not currently active.")
|
|
616
|
+
else:
|
|
617
|
+
logic_group = <LogicGroup> <object> frame.logic_group
|
|
618
|
+
|
|
619
|
+
# Step 2: Activate pending breakpoints
|
|
620
|
+
cdef LogicNodeFrame* breakpoint_frame = self._breakpoint_nodes.top
|
|
621
|
+
cdef BreakpointNode breakpoint_node
|
|
622
|
+
while breakpoint_frame:
|
|
623
|
+
breakpoint_node = <BreakpointNode> <object> breakpoint_frame.logic_node
|
|
624
|
+
if breakpoint_node.break_from is logic_group:
|
|
625
|
+
breakpoint_node.await_connection = True
|
|
626
|
+
breakpoint_frame = breakpoint_frame.prev
|
|
627
|
+
|
|
628
|
+
# Step 3: Pop logic group from active stack
|
|
629
|
+
self._active_groups.top = frame.prev
|
|
630
|
+
self._active_groups.size -= 1
|
|
631
|
+
Py_DECREF(logic_group)
|
|
632
|
+
PyMem_Free(frame)
|
|
633
|
+
|
|
634
|
+
cdef inline void c_ln_enter(self, LogicNode logic_node):
|
|
635
|
+
# Step 1: Filter action node
|
|
636
|
+
if isinstance(logic_node, ActionNode):
|
|
637
|
+
LOGGER.error('Enter the with code block of an ActionNode rejected. Check is this intentional?')
|
|
638
|
+
return
|
|
639
|
+
|
|
640
|
+
# Step 2: Connect to breakpoints
|
|
641
|
+
cdef LogicNodeFrame* breakpoint_frame = self._breakpoint_nodes.top
|
|
642
|
+
cdef LogicNodeFrame* breakpoint_frame_next
|
|
643
|
+
cdef LogicNodeFrame* breakpoint_frame_prev = NULL
|
|
644
|
+
cdef BreakpointNode breakpoint_node
|
|
645
|
+
while breakpoint_frame:
|
|
646
|
+
breakpoint_frame_next = breakpoint_frame.prev
|
|
647
|
+
breakpoint_node = <BreakpointNode> <object> breakpoint_frame.logic_node
|
|
648
|
+
if breakpoint_node.await_connection:
|
|
649
|
+
# connect the breakpoint with current node
|
|
650
|
+
breakpoint_node.c_connect(logic_node)
|
|
651
|
+
# unlink from breakpoint stack
|
|
652
|
+
if breakpoint_frame_prev:
|
|
653
|
+
breakpoint_frame_prev.prev = breakpoint_frame_next
|
|
654
|
+
else:
|
|
655
|
+
self._breakpoint_nodes.top = breakpoint_frame_next
|
|
656
|
+
self._breakpoint_nodes.size -= 1
|
|
657
|
+
Py_DECREF(<object> breakpoint_node)
|
|
658
|
+
PyMem_Free(breakpoint_frame)
|
|
659
|
+
# will NOT update breakpoint_frame_prev
|
|
660
|
+
breakpoint_frame = breakpoint_frame_next
|
|
661
|
+
else:
|
|
662
|
+
breakpoint_frame_prev = breakpoint_frame
|
|
663
|
+
breakpoint_frame = breakpoint_frame_next
|
|
664
|
+
|
|
665
|
+
# Step 3: Get current active node
|
|
666
|
+
cdef LogicNodeFrame* active_frame = self._active_nodes.top
|
|
667
|
+
|
|
668
|
+
# Step 3.1: First active node, push to stack directly
|
|
669
|
+
if not active_frame:
|
|
670
|
+
LogicGroupManager.c_ln_stack_push(self._active_nodes, logic_node)
|
|
671
|
+
return
|
|
672
|
+
|
|
673
|
+
# Step 4: Locate first placeholder
|
|
674
|
+
cdef LogicNode active_node = <LogicNode> <object> active_frame.logic_node
|
|
675
|
+
cdef PlaceholderNode placeholder = active_node.c_get_placeholder()
|
|
676
|
+
|
|
677
|
+
# Step 5: Replace placeholder
|
|
678
|
+
active_node.c_replace(placeholder, logic_node)
|
|
679
|
+
|
|
680
|
+
# Step 6: Push self to active stack
|
|
681
|
+
LogicGroupManager.c_ln_stack_push(self._active_nodes, logic_node)
|
|
682
|
+
|
|
683
|
+
cdef inline void c_ln_exit(self, LogicNode logic_node):
|
|
684
|
+
LogicGroupManager.c_ln_stack_pop(self._active_nodes, logic_node)
|
|
685
|
+
|
|
686
|
+
cdef inline void c_shelve(self):
|
|
687
|
+
cdef ShelvedStateFrame* frame = <ShelvedStateFrame*> PyMem_Calloc(1, sizeof(ShelvedStateFrame))
|
|
688
|
+
frame.active_groups = self._active_groups
|
|
689
|
+
frame.active_nodes = self._active_nodes
|
|
690
|
+
frame.breakpoint_nodes = self._breakpoint_nodes
|
|
691
|
+
frame.inspection_mode = self.inspection_mode
|
|
692
|
+
frame.vigilant_mode = self.vigilant_mode
|
|
693
|
+
|
|
694
|
+
frame.prev = self._shelved_state.top
|
|
695
|
+
self._shelved_state.top = frame
|
|
696
|
+
self._shelved_state.size += 1
|
|
697
|
+
|
|
698
|
+
self._active_groups = <LogicGroupStack*> PyMem_Calloc(1, sizeof(LogicGroupStack))
|
|
699
|
+
self._active_nodes = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
700
|
+
self._breakpoint_nodes = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
701
|
+
|
|
702
|
+
cdef inline void c_unshelve(self):
|
|
703
|
+
if not self._shelved_state.top:
|
|
704
|
+
raise RuntimeError("No shelved state to unshelve.")
|
|
705
|
+
|
|
706
|
+
cdef ShelvedStateFrame* frame = self._shelved_state.top
|
|
707
|
+
cdef LogicGroupStack* active_groups = frame.active_groups
|
|
708
|
+
cdef LogicNodeStack* active_nodes = frame.active_nodes
|
|
709
|
+
cdef LogicNodeStack* breakpoint_nodes = frame.breakpoint_nodes
|
|
710
|
+
cdef bint inspection_mode = frame.inspection_mode
|
|
711
|
+
cdef bint vigilant_mode = frame.vigilant_mode
|
|
712
|
+
|
|
713
|
+
self._shelved_state.top = frame.prev
|
|
714
|
+
self._shelved_state.size -= 1
|
|
715
|
+
PyMem_Free(frame)
|
|
716
|
+
|
|
717
|
+
self.c_clear()
|
|
718
|
+
self._active_groups = active_groups
|
|
719
|
+
self._active_nodes = active_nodes
|
|
720
|
+
self._breakpoint_nodes = breakpoint_nodes
|
|
721
|
+
self.inspection_mode = inspection_mode
|
|
722
|
+
self.vigilant_mode = vigilant_mode
|
|
723
|
+
|
|
724
|
+
cdef inline void c_clear(self):
|
|
725
|
+
if self._active_groups:
|
|
726
|
+
while self._active_groups.size:
|
|
727
|
+
self.c_lg_exit()
|
|
728
|
+
PyMem_Free(self._active_groups)
|
|
729
|
+
self._active_groups = NULL
|
|
730
|
+
|
|
731
|
+
if self._active_nodes:
|
|
732
|
+
while self._active_nodes.size:
|
|
733
|
+
LogicGroupManager.c_ln_stack_pop(self._active_nodes)
|
|
734
|
+
PyMem_Free(self._active_nodes)
|
|
735
|
+
self._active_nodes = NULL
|
|
736
|
+
|
|
737
|
+
if self._breakpoint_nodes:
|
|
738
|
+
while self._breakpoint_nodes.size:
|
|
739
|
+
LogicGroupManager.c_ln_stack_pop(self._breakpoint_nodes)
|
|
740
|
+
PyMem_Free(self._breakpoint_nodes)
|
|
741
|
+
self._breakpoint_nodes = NULL
|
|
742
|
+
|
|
743
|
+
def __call__(self, str name, type cls, **kwargs) -> LogicGroup:
|
|
744
|
+
return self.c_cached_init(name, cls, kwargs)
|
|
745
|
+
|
|
746
|
+
def __contains__(self, LogicGroup instance) -> bool:
|
|
747
|
+
cdef type cls = instance.__class__
|
|
748
|
+
cdef str name = instance.name
|
|
749
|
+
cdef tuple reg_key = (cls.__module__, cls.__qualname__)
|
|
750
|
+
if reg_key not in self._cache:
|
|
751
|
+
return False
|
|
752
|
+
cdef dict registry = self._cache[reg_key]
|
|
753
|
+
return name in registry
|
|
754
|
+
|
|
755
|
+
def shelve(self):
|
|
756
|
+
self.c_shelve()
|
|
757
|
+
|
|
758
|
+
def unshelve(self):
|
|
759
|
+
self.c_unshelve()
|
|
760
|
+
|
|
761
|
+
def clear(self):
|
|
762
|
+
self._cache.clear()
|
|
763
|
+
self.c_clear()
|
|
764
|
+
|
|
765
|
+
self._active_groups = <LogicGroupStack*> PyMem_Calloc(1, sizeof(LogicGroupStack))
|
|
766
|
+
self._active_nodes = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
767
|
+
self._breakpoint_nodes = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
768
|
+
|
|
769
|
+
property active_group:
|
|
770
|
+
def __get__(self):
|
|
771
|
+
if not self._active_groups.top:
|
|
772
|
+
return None
|
|
773
|
+
|
|
774
|
+
cdef LogicGroupFrame* frame = self._active_groups.top
|
|
775
|
+
cdef PyObject* lg = frame.logic_group
|
|
776
|
+
return <LogicGroup> <object> lg
|
|
777
|
+
|
|
778
|
+
property active_node:
|
|
779
|
+
def __get__(self):
|
|
780
|
+
if not self._active_nodes.top:
|
|
781
|
+
return None
|
|
782
|
+
cdef LogicNodeFrame* frame = self._active_nodes.top
|
|
783
|
+
cdef PyObject* ln = frame.logic_node
|
|
784
|
+
return <LogicNode> <object> ln
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
cdef LogicGroupManager LGM = LogicGroupManager()
|
|
788
|
+
globals()['LGM'] = LGM
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
cdef class LogicGroup:
|
|
792
|
+
def __cinit__(self, *, str name=None, LogicGroup parent=None, dict contexts=None, **kwargs):
|
|
793
|
+
self.name = f"{self.__class__.__name__}.{uuid.uuid4()}" if name is None else name
|
|
794
|
+
|
|
795
|
+
if self in LGM:
|
|
796
|
+
raise RuntimeError(f"LogicGroup {name} of type {self.__class__.__name__} already exists!")
|
|
797
|
+
|
|
798
|
+
self.parent = parent
|
|
799
|
+
self.Break = type(f"{self.__class__.__name__}Break", (BreakBlock,), {})
|
|
800
|
+
self.contexts = {} if contexts is None else contexts
|
|
801
|
+
|
|
802
|
+
cdef void c_break_inspection(self):
|
|
803
|
+
cdef LogicNodeFrame* frame = LGM._active_nodes.top
|
|
804
|
+
|
|
805
|
+
# Case 1: No active node, breaks affect nothing
|
|
806
|
+
if not frame:
|
|
807
|
+
return
|
|
808
|
+
|
|
809
|
+
cdef LogicNode active_node = <LogicNode> <object> frame.logic_node
|
|
810
|
+
# Case 2: Active node located, traverse its children to find placeholder to mark as breakpoint
|
|
811
|
+
# will not break from scope in inspection mode
|
|
812
|
+
|
|
813
|
+
# Step 2.1: Locating the placeholder node for breakpoint
|
|
814
|
+
cdef PlaceholderNode placeholder = active_node.c_get_placeholder()
|
|
815
|
+
cdef BreakpointNode breakpoint_node = BreakpointNode(break_from=self)
|
|
816
|
+
active_node.c_replace(placeholder, breakpoint_node)
|
|
817
|
+
# Step 2.2: Push the breakpoint node to the global breakpoint stack
|
|
818
|
+
LogicGroupManager.c_ln_stack_push(LGM._breakpoint_nodes, breakpoint_node)
|
|
819
|
+
|
|
820
|
+
cdef void c_break_active(self):
|
|
821
|
+
cdef LogicGroupFrame* frame = LGM._active_groups.top
|
|
822
|
+
if not frame:
|
|
823
|
+
raise RuntimeError("No active LogicGroup to break from.")
|
|
824
|
+
cdef PyObject* active_node = frame.logic_group
|
|
825
|
+
if active_node != <PyObject*> self:
|
|
826
|
+
raise IndexError('Not breaking from the top active LogicGroup.')
|
|
827
|
+
raise self.Break()
|
|
828
|
+
|
|
829
|
+
cdef void c_break_runtime(self):
|
|
830
|
+
if not LGM._active_nodes.size:
|
|
831
|
+
raise RuntimeError("No active LogicGroup to break from.")
|
|
832
|
+
|
|
833
|
+
cdef PyObject* addr_self = <PyObject*> self
|
|
834
|
+
cdef LogicGroupFrame* frame = LGM._active_groups.top
|
|
835
|
+
cdef bint found = False
|
|
836
|
+
|
|
837
|
+
# step 1: Validate that the break scope is in the active stack
|
|
838
|
+
while frame:
|
|
839
|
+
if frame.logic_group == addr_self:
|
|
840
|
+
found = True
|
|
841
|
+
break
|
|
842
|
+
frame = frame.prev
|
|
843
|
+
|
|
844
|
+
if not found:
|
|
845
|
+
raise ValueError(f"Break scope {self} not in active LogicGroup stack.")
|
|
846
|
+
|
|
847
|
+
# step 2: recursive breaking from top of the stack
|
|
848
|
+
frame = LGM._active_groups.top
|
|
849
|
+
cdef LogicGroup active_group
|
|
850
|
+
while frame:
|
|
851
|
+
active_group = <LogicGroup> <object> frame.logic_group
|
|
852
|
+
active_group.c_break_active()
|
|
853
|
+
if active_group is self:
|
|
854
|
+
break
|
|
855
|
+
frame = frame.prev
|
|
856
|
+
|
|
857
|
+
# === Python Interfaces ===
|
|
858
|
+
|
|
859
|
+
def __repr__(self):
|
|
860
|
+
return f'<{self.__class__.__name__}>({self.name!r})'
|
|
861
|
+
|
|
862
|
+
def __enter__(self):
|
|
863
|
+
LGM.c_lg_enter(self)
|
|
864
|
+
return self
|
|
865
|
+
|
|
866
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
|
867
|
+
LGM.c_lg_exit(self)
|
|
868
|
+
|
|
869
|
+
if exc_type is None:
|
|
870
|
+
return None
|
|
871
|
+
|
|
872
|
+
if issubclass(exc_type, self.Break):
|
|
873
|
+
return True
|
|
874
|
+
return False
|
|
875
|
+
|
|
876
|
+
@classmethod
|
|
877
|
+
def break_(cls, LogicGroup scope=None):
|
|
878
|
+
if scope is None:
|
|
879
|
+
scope = LGM.active_group
|
|
880
|
+
|
|
881
|
+
if scope is None:
|
|
882
|
+
raise RuntimeError("No active LogicGroup to break from.")
|
|
883
|
+
|
|
884
|
+
if LGM.inspection_mode:
|
|
885
|
+
scope.c_break_inspection()
|
|
886
|
+
else:
|
|
887
|
+
scope.c_break_runtime()
|
|
888
|
+
|
|
889
|
+
def break_active(self):
|
|
890
|
+
self.c_break_active()
|
|
891
|
+
|
|
892
|
+
def break_inspection(self):
|
|
893
|
+
self.c_break_inspection()
|
|
894
|
+
|
|
895
|
+
def break_runtime(self):
|
|
896
|
+
self.c_break_runtime()
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
cdef class LogicNode(LogicExpression):
|
|
900
|
+
def __cinit__(self, *, **kwargs):
|
|
901
|
+
self.subordinates = <LogicNodeStack*> PyMem_Calloc(1, sizeof(LogicNodeStack))
|
|
902
|
+
self.condition_to_parent = NO_CONDITION
|
|
903
|
+
self.parent = None
|
|
904
|
+
self.children = {}
|
|
905
|
+
self.labels = []
|
|
906
|
+
self.autogen = False
|
|
907
|
+
|
|
908
|
+
# update labels from active groups
|
|
909
|
+
cdef LogicGroupFrame* frame = LGM._active_groups.top
|
|
910
|
+
cdef LogicGroup lg
|
|
911
|
+
while frame:
|
|
912
|
+
lg = <LogicGroup> <object> frame.logic_group
|
|
913
|
+
self.labels.append(lg.name)
|
|
914
|
+
frame = frame.prev
|
|
915
|
+
|
|
916
|
+
def __dealloc__(self):
|
|
917
|
+
if self.subordinates:
|
|
918
|
+
while self.subordinates.size:
|
|
919
|
+
LogicGroupManager.c_ln_stack_pop(self.subordinates)
|
|
920
|
+
PyMem_Free(self.subordinates)
|
|
921
|
+
|
|
922
|
+
cdef NodeEdgeCondition c_infer_condition(self, LogicNode child):
|
|
923
|
+
# infer condition based on registered children
|
|
924
|
+
cdef LogicNodeStack* stack = self.subordinates
|
|
925
|
+
cdef LogicNodeFrame* frame = stack.top
|
|
926
|
+
|
|
927
|
+
cdef size_t size = stack.size
|
|
928
|
+
|
|
929
|
+
# Case 1: No child node registered, the first child is always TRUE unless specified
|
|
930
|
+
if size == 0:
|
|
931
|
+
return TRUE_CONDITION
|
|
932
|
+
|
|
933
|
+
cdef LogicNode last_node = <LogicNode> <object> frame.logic_node
|
|
934
|
+
cdef NodeEdgeCondition last_condition = last_node.condition_to_parent
|
|
935
|
+
|
|
936
|
+
# Case 1: Only 1 child node registered, the second child is always opposite of the first
|
|
937
|
+
if size == 1 and isinstance(last_condition, BinaryCondition):
|
|
938
|
+
if last_condition is TRUE_CONDITION:
|
|
939
|
+
return FALSE_CONDITION
|
|
940
|
+
else:
|
|
941
|
+
return TRUE_CONDITION
|
|
942
|
+
|
|
943
|
+
# Case 2: Only 1 child node registered, and the first condition is not specified.
|
|
944
|
+
if size == 1 and isinstance(last_condition, ConditionElse):
|
|
945
|
+
# Case 2.1: If the child is auto generated, we can assume this child should be TRUE
|
|
946
|
+
if last_node.autogen:
|
|
947
|
+
return TRUE_CONDITION
|
|
948
|
+
# Case 2.2: Otherwise, we cannot infer the condition
|
|
949
|
+
|
|
950
|
+
if size == 1:
|
|
951
|
+
raise EdgeValueError('Cannot auto-infer condition from single existing non-binary condition.')
|
|
952
|
+
|
|
953
|
+
cdef LogicNode second_node = <LogicNode> <object> frame.prev.logic_node
|
|
954
|
+
cdef NodeEdgeCondition second_condition = second_node.condition_to_parent
|
|
955
|
+
# Case 3: check if any 2 node is from autogeneration
|
|
956
|
+
# Returning the condition of the auto-generated node, so that it can be replaced later.
|
|
957
|
+
if last_node.autogen:
|
|
958
|
+
return last_condition
|
|
959
|
+
elif second_node.autogen:
|
|
960
|
+
return second_condition
|
|
961
|
+
|
|
962
|
+
if size > 2:
|
|
963
|
+
raise TooManyChildren(size)
|
|
964
|
+
# Case 4: both conditions are non-binary, cannot infer
|
|
965
|
+
raise EdgeValueError('Cannot auto-infer condition for non-binary existing condition.')
|
|
966
|
+
|
|
967
|
+
cdef PlaceholderNode c_get_placeholder(self):
|
|
968
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
969
|
+
cdef PlaceholderNode placeholder
|
|
970
|
+
|
|
971
|
+
# Case 1: No child node registered, return a TRUE placeholder
|
|
972
|
+
if not frame:
|
|
973
|
+
placeholder = PlaceholderNode(auto_connect=False)
|
|
974
|
+
self.c_append(placeholder, TRUE_CONDITION)
|
|
975
|
+
return placeholder
|
|
976
|
+
|
|
977
|
+
# Case 2: Traverse the stack to find existing placeholder
|
|
978
|
+
cdef LogicNode node
|
|
979
|
+
while frame:
|
|
980
|
+
node = <LogicNode> <object> frame.logic_node
|
|
981
|
+
if isinstance(node, PlaceholderNode):
|
|
982
|
+
return <PlaceholderNode> node
|
|
983
|
+
frame = frame.prev
|
|
984
|
+
|
|
985
|
+
# Case 3: No existing placeholder, create a new one with AUTO_CONDITION.
|
|
986
|
+
placeholder = PlaceholderNode(auto_connect=False)
|
|
987
|
+
# c_append will validate and infer the condition, and raise error as we intended.
|
|
988
|
+
self.c_append(placeholder, AUTO_CONDITION)
|
|
989
|
+
return placeholder
|
|
990
|
+
|
|
991
|
+
cdef void c_append(self, LogicNode child, NodeEdgeCondition condition):
|
|
992
|
+
if condition is None:
|
|
993
|
+
raise ValueError("LogicNode must have an valid edge condition.")
|
|
994
|
+
|
|
995
|
+
if condition is AUTO_CONDITION:
|
|
996
|
+
condition = self.c_infer_condition(child)
|
|
997
|
+
|
|
998
|
+
if condition in self.children:
|
|
999
|
+
raise KeyError(f"Edge {condition} already registered.")
|
|
1000
|
+
|
|
1001
|
+
self.children[condition] = child
|
|
1002
|
+
LogicGroupManager.c_ln_stack_push(self.subordinates, child)
|
|
1003
|
+
child.parent = self
|
|
1004
|
+
child.condition_to_parent = condition
|
|
1005
|
+
|
|
1006
|
+
cdef void c_overwrite(self, LogicNode new_node, NodeEdgeCondition condition):
|
|
1007
|
+
if condition is None:
|
|
1008
|
+
raise ValueError("LogicNode must have an valid edge condition.")
|
|
1009
|
+
|
|
1010
|
+
if condition is AUTO_CONDITION:
|
|
1011
|
+
condition = self.c_infer_condition(new_node)
|
|
1012
|
+
|
|
1013
|
+
if condition not in self.children:
|
|
1014
|
+
raise KeyError(f"Edge {condition} not registered, cannot overwrite.")
|
|
1015
|
+
|
|
1016
|
+
cdef LogicNode original_node = self.children[condition]
|
|
1017
|
+
self.children[condition] = new_node
|
|
1018
|
+
new_node.parent = self
|
|
1019
|
+
new_node.condition_to_parent = condition
|
|
1020
|
+
|
|
1021
|
+
cdef LogicNodeFrame* frame = LogicGroupManager.c_ln_stack_locate(self.subordinates, original_node)
|
|
1022
|
+
if not frame:
|
|
1023
|
+
raise LookupError(f'Failed to locate {original_node} from subordinates, buffer corruption detected.')
|
|
1024
|
+
|
|
1025
|
+
Py_DECREF(<object> frame.logic_node)
|
|
1026
|
+
frame.logic_node = <PyObject*> new_node
|
|
1027
|
+
Py_INCREF(<object> frame.logic_node)
|
|
1028
|
+
original_node.parent = None
|
|
1029
|
+
original_node.condition_to_parent = NO_CONDITION
|
|
1030
|
+
|
|
1031
|
+
cdef void c_replace(self, LogicNode original_node, LogicNode new_node):
|
|
1032
|
+
cdef NodeEdgeCondition condition
|
|
1033
|
+
cdef LogicNode child
|
|
1034
|
+
cdef LogicNodeFrame* frame
|
|
1035
|
+
|
|
1036
|
+
# Step 1: Safety check:
|
|
1037
|
+
frame = LogicGroupManager.c_ln_stack_locate(LGM._active_nodes, original_node)
|
|
1038
|
+
if frame:
|
|
1039
|
+
raise RuntimeError('Must not replace active node. Existing first required.')
|
|
1040
|
+
|
|
1041
|
+
# Step 2: Locate original node from stack
|
|
1042
|
+
frame = LogicGroupManager.c_ln_stack_locate(self.subordinates, original_node)
|
|
1043
|
+
if not frame:
|
|
1044
|
+
raise NodeNotFountError(f'Failed to locate {original_node} from subordinates.')
|
|
1045
|
+
|
|
1046
|
+
self.children[original_node.condition_to_parent] = new_node
|
|
1047
|
+
new_node.parent = self
|
|
1048
|
+
new_node.condition_to_parent = original_node.condition_to_parent
|
|
1049
|
+
|
|
1050
|
+
Py_DECREF(<object> frame.logic_node)
|
|
1051
|
+
frame.logic_node = <PyObject*> new_node
|
|
1052
|
+
Py_INCREF(<object> frame.logic_node)
|
|
1053
|
+
|
|
1054
|
+
original_node.parent = None
|
|
1055
|
+
original_node.condition_to_parent = NO_CONDITION
|
|
1056
|
+
|
|
1057
|
+
cdef void c_validate(self):
|
|
1058
|
+
cdef size_t size = self.subordinates.size
|
|
1059
|
+
if size != <size_t> len(self.children):
|
|
1060
|
+
raise NodeValueError('Subordinate stack size does not match registered children.')
|
|
1061
|
+
|
|
1062
|
+
cdef LogicNode child
|
|
1063
|
+
cdef NodeEdgeCondition condition
|
|
1064
|
+
cdef LogicNodeFrame* frame
|
|
1065
|
+
for condition, child in self.children.items():
|
|
1066
|
+
if child.condition_to_parent is not condition:
|
|
1067
|
+
raise EdgeValueError('Child node condition does not match registered condition.')
|
|
1068
|
+
frame = LogicGroupManager.c_ln_stack_locate(self.subordinates, child)
|
|
1069
|
+
# in the locating function the existence is already checked
|
|
1070
|
+
# but add this for a clearer error message
|
|
1071
|
+
if not frame:
|
|
1072
|
+
raise ValueError(f"LogicNode {child} not found in stack")
|
|
1073
|
+
|
|
1074
|
+
cdef tuple c_eval_recursively(self, list path=None, object default=NO_DEFAULT):
|
|
1075
|
+
if path is None:
|
|
1076
|
+
path = []
|
|
1077
|
+
else:
|
|
1078
|
+
path.append(self)
|
|
1079
|
+
|
|
1080
|
+
cdef object value
|
|
1081
|
+
if LGM.vigilant_mode:
|
|
1082
|
+
try:
|
|
1083
|
+
value = self.c_eval(False)
|
|
1084
|
+
except Exception as e:
|
|
1085
|
+
raise ExpressEvaluationError(f"Failed to evaluate {self}, {traceback.format_exc()}") from e
|
|
1086
|
+
else:
|
|
1087
|
+
value = self.c_eval(False)
|
|
1088
|
+
|
|
1089
|
+
if self.is_leaf:
|
|
1090
|
+
return value, path
|
|
1091
|
+
|
|
1092
|
+
cdef LogicNode else_branch = None
|
|
1093
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
1094
|
+
cdef LogicNode child
|
|
1095
|
+
cdef NodeEdgeCondition condition
|
|
1096
|
+
|
|
1097
|
+
while frame:
|
|
1098
|
+
child = <LogicNode> <object> frame.logic_node
|
|
1099
|
+
condition = child.condition_to_parent
|
|
1100
|
+
if condition is ELSE_CONDITION:
|
|
1101
|
+
else_branch = child
|
|
1102
|
+
elif condition is NO_CONDITION or value == condition.value:
|
|
1103
|
+
return child.c_eval_recursively(path, default)
|
|
1104
|
+
frame = frame.prev
|
|
1105
|
+
|
|
1106
|
+
if else_branch is not None:
|
|
1107
|
+
return else_branch.c_eval_recursively(path, default)
|
|
1108
|
+
|
|
1109
|
+
if default is NO_DEFAULT:
|
|
1110
|
+
raise ValueError(f"No matching condition found for value {value} at '{self.repr}'.")
|
|
1111
|
+
|
|
1112
|
+
LOGGER.warning(f"No matching condition found for value {value} at '{self.repr}', using default {default}.")
|
|
1113
|
+
return default, path
|
|
1114
|
+
|
|
1115
|
+
cdef void c_auto_fill(self):
|
|
1116
|
+
cdef size_t size = len(self.children)
|
|
1117
|
+
cdef LogicNode no_action = NoAction(auto_connect=False, autogen=True)
|
|
1118
|
+
|
|
1119
|
+
# Case 1: No child node registered
|
|
1120
|
+
if size == 0:
|
|
1121
|
+
LOGGER.warning(f"{self} having no [True] branch. Check the <with> statement code block to see if this is intended.")
|
|
1122
|
+
self.c_append(no_action, NO_CONDITION)
|
|
1123
|
+
return
|
|
1124
|
+
|
|
1125
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
1126
|
+
cdef LogicNode node = <LogicNode> <object> frame.logic_node
|
|
1127
|
+
cdef NodeEdgeCondition condition = node.condition_to_parent
|
|
1128
|
+
|
|
1129
|
+
# Case 2: Signal node
|
|
1130
|
+
if size == 1:
|
|
1131
|
+
# Case 2.1: single child with no condition
|
|
1132
|
+
if condition is NO_CONDITION:
|
|
1133
|
+
return
|
|
1134
|
+
|
|
1135
|
+
# Case 2.2: single child deliberately set to ELSE, which indicate it is expecting a true branch
|
|
1136
|
+
if condition is ELSE_CONDITION:
|
|
1137
|
+
LOGGER.warning(f"{self} having no [True] branch. Check the <with> statement code block to see if this is intended.")
|
|
1138
|
+
self.c_overwrite(no_action, TRUE_CONDITION)
|
|
1139
|
+
return
|
|
1140
|
+
|
|
1141
|
+
# Case 2.3: no need to check for auto condition, as it is not hashable
|
|
1142
|
+
|
|
1143
|
+
# Case 2.4: single child with binary condition
|
|
1144
|
+
if isinstance(condition, BinaryCondition):
|
|
1145
|
+
self.c_append(no_action, ~condition)
|
|
1146
|
+
return
|
|
1147
|
+
|
|
1148
|
+
cdef LogicNode second_node = <LogicNode> <object> frame.prev.logic_node
|
|
1149
|
+
cdef NodeEdgeCondition second_condition = second_node.condition_to_parent
|
|
1150
|
+
# Case 3: Double Node
|
|
1151
|
+
if size == 2:
|
|
1152
|
+
# Case 3.1: if one of the branch is unconditioned, raise
|
|
1153
|
+
if condition is NO_CONDITION or second_condition is NO_CONDITION:
|
|
1154
|
+
raise TooManyChildren('Cannot have unconditioned branch when there are two branches.')
|
|
1155
|
+
|
|
1156
|
+
# Case3.2: if one of the branch is ELSE, and the other is any valid condition except ELSE too, all good
|
|
1157
|
+
# Since we already validate node, there is no same registered condition
|
|
1158
|
+
if condition is ELSE_CONDITION or second_condition is ELSE_CONDITION:
|
|
1159
|
+
return
|
|
1160
|
+
|
|
1161
|
+
# Case 3.3: both conditions are binary, all good
|
|
1162
|
+
if isinstance(condition, BinaryCondition) and isinstance(second_condition, BinaryCondition):
|
|
1163
|
+
return
|
|
1164
|
+
|
|
1165
|
+
# Case 3.4: all conditions are non-binary, add a protective else branch
|
|
1166
|
+
if not isinstance(condition, BinaryCondition) and not isinstance(second_condition, BinaryCondition):
|
|
1167
|
+
self.c_append(no_action, ELSE_CONDITION)
|
|
1168
|
+
return
|
|
1169
|
+
|
|
1170
|
+
raise EdgeValueError(f'Conflicting conditions detected, {condition} and {second_condition}.')
|
|
1171
|
+
|
|
1172
|
+
# Case 3: Multiple nodes, it must be of all generic conditions
|
|
1173
|
+
cdef bint else_detected = False
|
|
1174
|
+
for condition in self.children:
|
|
1175
|
+
if condition is NO_CONDITION:
|
|
1176
|
+
raise TooManyChildren('Cannot have unconditioned branch when there are multiple branches.')
|
|
1177
|
+
|
|
1178
|
+
if condition is ELSE_CONDITION:
|
|
1179
|
+
else_detected = True
|
|
1180
|
+
|
|
1181
|
+
if isinstance(condition, BinaryCondition):
|
|
1182
|
+
raise TooManyChildren('Cannot have binary branch when there are more than 3 branches.')
|
|
1183
|
+
|
|
1184
|
+
if not else_detected:
|
|
1185
|
+
self.c_append(no_action, ELSE_CONDITION)
|
|
1186
|
+
|
|
1187
|
+
cdef size_t c_consolidate_placeholder(self):
|
|
1188
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
1189
|
+
cdef LogicNode node
|
|
1190
|
+
cdef size_t placeholder_count = 0
|
|
1191
|
+
|
|
1192
|
+
while frame:
|
|
1193
|
+
node = <object> frame.logic_node
|
|
1194
|
+
if isinstance(node, PlaceholderNode):
|
|
1195
|
+
self.c_replace(node, NoAction(auto_connect=False, autogen=True))
|
|
1196
|
+
placeholder_count += 1
|
|
1197
|
+
frame = frame.prev
|
|
1198
|
+
return placeholder_count
|
|
1199
|
+
|
|
1200
|
+
cdef bint c_entry_check(self):
|
|
1201
|
+
if LGM.inspection_mode:
|
|
1202
|
+
return True
|
|
1203
|
+
return bool(self.c_eval(False))
|
|
1204
|
+
|
|
1205
|
+
cdef void c_on_enter(self):
|
|
1206
|
+
# Placeholders must be in reversed order, so that locating placeholder works correctly.
|
|
1207
|
+
self.c_append(PlaceholderNode(auto_connect=False), FALSE_CONDITION)
|
|
1208
|
+
self.c_append(PlaceholderNode(auto_connect=False), TRUE_CONDITION)
|
|
1209
|
+
LGM.c_ln_enter(self)
|
|
1210
|
+
|
|
1211
|
+
cdef void c_on_exit(self):
|
|
1212
|
+
self.c_validate()
|
|
1213
|
+
self.c_auto_fill()
|
|
1214
|
+
self.c_consolidate_placeholder()
|
|
1215
|
+
LGM.c_ln_exit(self)
|
|
1216
|
+
|
|
1217
|
+
# === Python Interfaces ===
|
|
1218
|
+
|
|
1219
|
+
def __rshift__(self, LogicNode other):
|
|
1220
|
+
self.c_append(other, AUTO_CONDITION)
|
|
1221
|
+
return other # Allow chaining
|
|
1222
|
+
|
|
1223
|
+
def __call__(self, object default=None):
|
|
1224
|
+
if default is None:
|
|
1225
|
+
default = NoAction(auto_connect=False, autogen=True)
|
|
1226
|
+
|
|
1227
|
+
cdef bint inspection_mode = LGM.inspection_mode
|
|
1228
|
+
if inspection_mode:
|
|
1229
|
+
LOGGER.info('LGM inspection mode temporally disabled to evaluate correctly.')
|
|
1230
|
+
LGM.inspection_mode = False
|
|
1231
|
+
|
|
1232
|
+
cdef list path = []
|
|
1233
|
+
cdef object value
|
|
1234
|
+
value, path = self.c_eval_recursively(path, default)
|
|
1235
|
+
LGM.inspection_mode = inspection_mode
|
|
1236
|
+
return value
|
|
1237
|
+
|
|
1238
|
+
def __repr__(self):
|
|
1239
|
+
return f'<{self.__class__.__name__}>({self.repr!r})'
|
|
1240
|
+
|
|
1241
|
+
def __hash__(self):
|
|
1242
|
+
return <uintptr_t> <PyObject*> self
|
|
1243
|
+
|
|
1244
|
+
def append(self, LogicNode child, NodeEdgeCondition condition=AUTO_CONDITION):
|
|
1245
|
+
self.c_append(child, condition)
|
|
1246
|
+
|
|
1247
|
+
def overwrite(self, LogicNode new_node, NodeEdgeCondition condition):
|
|
1248
|
+
self.c_overwrite(new_node, condition)
|
|
1249
|
+
|
|
1250
|
+
def replace(self, LogicNode original_node, LogicNode new_node):
|
|
1251
|
+
self.c_replace(original_node, new_node)
|
|
1252
|
+
|
|
1253
|
+
def eval_recursively(self, list path=None, object default=NO_DEFAULT):
|
|
1254
|
+
return self.c_eval_recursively(path, default)
|
|
1255
|
+
|
|
1256
|
+
def list_labels(self) -> dict[str, list[LogicNode]]:
|
|
1257
|
+
labels = {}
|
|
1258
|
+
|
|
1259
|
+
def traverse(node):
|
|
1260
|
+
for group in node.labels:
|
|
1261
|
+
if group not in labels:
|
|
1262
|
+
labels[group] = []
|
|
1263
|
+
labels[group].append(node)
|
|
1264
|
+
for _, child in node.children.items():
|
|
1265
|
+
traverse(child)
|
|
1266
|
+
|
|
1267
|
+
traverse(self)
|
|
1268
|
+
return labels
|
|
1269
|
+
|
|
1270
|
+
property leaves:
|
|
1271
|
+
def __get__(self):
|
|
1272
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
1273
|
+
if not frame:
|
|
1274
|
+
yield self
|
|
1275
|
+
cdef LogicNode child
|
|
1276
|
+
while frame:
|
|
1277
|
+
child = <LogicNode> <object> frame.logic_node
|
|
1278
|
+
yield from child.leaves
|
|
1279
|
+
frame = frame.prev
|
|
1280
|
+
|
|
1281
|
+
property is_leaf:
|
|
1282
|
+
def __get__(self):
|
|
1283
|
+
return not self.children
|
|
1284
|
+
|
|
1285
|
+
property child_stack:
|
|
1286
|
+
def __get__(self):
|
|
1287
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
1288
|
+
cdef LogicNode child
|
|
1289
|
+
while frame:
|
|
1290
|
+
child = <LogicNode> <object> frame.logic_node
|
|
1291
|
+
yield child
|
|
1292
|
+
frame = frame.prev
|
|
1293
|
+
|
|
1294
|
+
property descendants:
|
|
1295
|
+
def __get__(self):
|
|
1296
|
+
cdef LogicNodeFrame* frame = self.subordinates.top
|
|
1297
|
+
cdef LogicNode child
|
|
1298
|
+
while frame:
|
|
1299
|
+
child = <LogicNode> <object> frame.logic_node
|
|
1300
|
+
yield child
|
|
1301
|
+
yield from child.descendants
|
|
1302
|
+
frame = frame.prev
|
|
1303
|
+
|
|
1304
|
+
|
|
1305
|
+
cdef class BreakpointNode(LogicNode):
|
|
1306
|
+
def __cinit__(self, *, LogicGroup break_from=None, object expression=None, str repr=None, **kwargs):
|
|
1307
|
+
self.break_from = break_from
|
|
1308
|
+
self.expression = NoAction(auto_connect=False, autogen=True) if expression is None else expression
|
|
1309
|
+
self.repr = f'Breakpoint(from={break_from.name})' if repr is None else repr
|
|
1310
|
+
self.autogen = True
|
|
1311
|
+
self.await_connection = False
|
|
1312
|
+
|
|
1313
|
+
cdef void c_connect(self, LogicNode child):
|
|
1314
|
+
if self.subordinates.size:
|
|
1315
|
+
raise TooManyChildren(f'{self.__class__.__name__} must not have more than one child node.')
|
|
1316
|
+
|
|
1317
|
+
# Breakpoint will only maintain a virtual connection to child node.
|
|
1318
|
+
# That is if a child already have a parent, it will not modify the child's parent pointer.
|
|
1319
|
+
# Otherwise, it will assign itself as the parent. (Which will be override if the child append to other node later.)
|
|
1320
|
+
cdef LogicNode existing_parent = child.parent
|
|
1321
|
+
|
|
1322
|
+
self.c_append(child, NO_CONDITION)
|
|
1323
|
+
if existing_parent is not None:
|
|
1324
|
+
child.parent = existing_parent
|
|
1325
|
+
|
|
1326
|
+
self.await_connection = False
|
|
1327
|
+
|
|
1328
|
+
cdef void c_on_enter(self):
|
|
1329
|
+
if self.subordinates.size:
|
|
1330
|
+
raise TooManyChildren(f'{self.__class__.__name__} must not have more than one child node.')
|
|
1331
|
+
# On enter, the breakpoint is disabled and removed from LGM._breakpoint_nodes
|
|
1332
|
+
# So that it is not managed and auto connected by LGM anymore.
|
|
1333
|
+
self.await_connection = False
|
|
1334
|
+
try:
|
|
1335
|
+
LogicGroupManager.c_ln_stack_remove(LGM._breakpoint_nodes, self)
|
|
1336
|
+
except NodeNotFountError as _:
|
|
1337
|
+
pass
|
|
1338
|
+
self.c_append(PlaceholderNode(auto_connect=False), NO_CONDITION)
|
|
1339
|
+
LogicGroupManager.c_ln_stack_push(LGM._active_nodes, self)
|
|
1340
|
+
# LGM.c_ln_enter(self)
|
|
1341
|
+
|
|
1342
|
+
cdef void c_on_exit(self):
|
|
1343
|
+
LGM.c_ln_exit(self)
|
|
1344
|
+
|
|
1345
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1346
|
+
if not self.subordinates.size:
|
|
1347
|
+
if LGM.vigilant_mode:
|
|
1348
|
+
raise NodeValueError(f'{self} not connected.')
|
|
1349
|
+
return self.expression
|
|
1350
|
+
|
|
1351
|
+
cdef LogicNode linked_to = <LogicNode> <object> self.subordinates.top.logic_node
|
|
1352
|
+
return linked_to.c_eval(enforce_dtype)
|
|
1353
|
+
|
|
1354
|
+
cdef tuple c_eval_recursively(self, list path=None, object default=NO_DEFAULT):
|
|
1355
|
+
if path is None:
|
|
1356
|
+
path = [self]
|
|
1357
|
+
else:
|
|
1358
|
+
path.append(self)
|
|
1359
|
+
|
|
1360
|
+
if not self.subordinates.size:
|
|
1361
|
+
if LGM.vigilant_mode:
|
|
1362
|
+
raise NodeValueError(f'{self} not connected.')
|
|
1363
|
+
return self.expression, path
|
|
1364
|
+
cdef LogicNode linked_to = <LogicNode> <object> self.subordinates.top.logic_node
|
|
1365
|
+
return linked_to.c_eval_recursively(path, default)
|
|
1366
|
+
|
|
1367
|
+
def __repr__(self):
|
|
1368
|
+
if self.subordinates.size:
|
|
1369
|
+
return f'<{self.__class__.__name__} connected>(break_from={self.break_from})'
|
|
1370
|
+
elif self.await_connection:
|
|
1371
|
+
return f'<{self.__class__.__name__} active>(break_from={self.break_from})'
|
|
1372
|
+
elif self.parent:
|
|
1373
|
+
return f'<{self.__class__.__name__} idle>(break_from={self.break_from})'
|
|
1374
|
+
else:
|
|
1375
|
+
return f'<{self.__class__.__name__} dangling>(break_from={self.break_from})'
|
|
1376
|
+
|
|
1377
|
+
@classmethod
|
|
1378
|
+
def break_(cls, LogicGroup break_from, **kwargs):
|
|
1379
|
+
cdef BreakpointNode breakpoint_node = BreakpointNode(break_from=break_from, **kwargs)
|
|
1380
|
+
cdef LogicNodeFrame* active_frame = LGM._active_nodes.top
|
|
1381
|
+
cdef LogicNode active_node = <LogicNode> <object> active_frame.logic_node
|
|
1382
|
+
cdef PlaceholderNode placeholder = active_node.c_get_placeholder()
|
|
1383
|
+
active_node.c_replace(placeholder, breakpoint_node)
|
|
1384
|
+
return breakpoint_node
|
|
1385
|
+
|
|
1386
|
+
def connect(self, LogicNode child):
|
|
1387
|
+
self.c_connect(child)
|
|
1388
|
+
|
|
1389
|
+
property linked_to:
|
|
1390
|
+
def __get__(self):
|
|
1391
|
+
if self.subordinates.size:
|
|
1392
|
+
return <LogicNode> <object> self.subordinates.top.logic_node
|
|
1393
|
+
return None
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
cdef class ActionNode(LogicNode):
|
|
1397
|
+
def __cinit__(self, *, object action=None, bint auto_connect=True, **kwargs):
|
|
1398
|
+
self.action = action
|
|
1399
|
+
|
|
1400
|
+
if auto_connect:
|
|
1401
|
+
self.c_auto_connect()
|
|
1402
|
+
|
|
1403
|
+
cdef void c_auto_connect(self):
|
|
1404
|
+
cdef LogicNodeFrame* frame = LGM._active_nodes.top
|
|
1405
|
+
|
|
1406
|
+
# This might come from a forward declaration used by python interface.
|
|
1407
|
+
# Graceful exit if not vigilant
|
|
1408
|
+
if not frame:
|
|
1409
|
+
if LGM.vigilant_mode:
|
|
1410
|
+
raise NodeValueError(f'Can not set ActionNode {self} as root node.')
|
|
1411
|
+
return
|
|
1412
|
+
|
|
1413
|
+
cdef LogicNode active_node = <LogicNode> <object> frame.logic_node
|
|
1414
|
+
cdef PlaceholderNode placeholder = active_node.c_get_placeholder()
|
|
1415
|
+
active_node.c_replace(placeholder, self)
|
|
1416
|
+
|
|
1417
|
+
cdef void c_post_eval(self):
|
|
1418
|
+
if self.action is not None:
|
|
1419
|
+
self.action()
|
|
1420
|
+
|
|
1421
|
+
cdef void c_append(self, LogicNode child, NodeEdgeCondition condition):
|
|
1422
|
+
raise TooManyChildren('Action node must not have any child node.')
|
|
1423
|
+
|
|
1424
|
+
cdef void c_on_enter(self):
|
|
1425
|
+
raise NodeContextError('ActionNode does not support context management with <with> statement.')
|
|
1426
|
+
|
|
1427
|
+
cdef void c_on_exit(self):
|
|
1428
|
+
pass
|
|
1429
|
+
|
|
1430
|
+
cdef tuple c_eval_recursively(self, list path=None, object default=NO_DEFAULT):
|
|
1431
|
+
if path is None:
|
|
1432
|
+
path = []
|
|
1433
|
+
path.append(self)
|
|
1434
|
+
|
|
1435
|
+
value = self.c_eval(False)
|
|
1436
|
+
|
|
1437
|
+
self.c_post_eval()
|
|
1438
|
+
|
|
1439
|
+
if not self.is_leaf:
|
|
1440
|
+
raise TooManyChildren('Action node must not have any child node.')
|
|
1441
|
+
|
|
1442
|
+
return value, path
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
cdef class PlaceholderNode(ActionNode):
|
|
1446
|
+
def __cinit__(self, *, **kwargs):
|
|
1447
|
+
self.autogen = True
|
|
1448
|
+
self.action = NoAction(auto_connect=False, autogen=True)
|
|
1449
|
+
|
|
1450
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1451
|
+
if LGM.vigilant_mode:
|
|
1452
|
+
return self
|
|
1453
|
+
return self.action
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
cdef class NoAction(ActionNode):
|
|
1457
|
+
def __cinit__(self, *, ssize_t sig=0, str repr=None, bint auto_connect=True, bint autogen=False, **kwargs):
|
|
1458
|
+
self.sig = sig
|
|
1459
|
+
self.repr = 'NoAction' if repr is None else repr
|
|
1460
|
+
self.autogen = autogen
|
|
1461
|
+
|
|
1462
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1463
|
+
return self
|
|
1464
|
+
|
|
1465
|
+
def __repr__(self):
|
|
1466
|
+
return f'<{self.__class__.__name__}>(sig={self.sig})'
|
|
1467
|
+
|
|
1468
|
+
def __int__(self):
|
|
1469
|
+
return self.sig
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
cdef class LongAction(ActionNode):
|
|
1473
|
+
def __cinit__(self, *, ssize_t sig=1, str repr=None, bint auto_connect=True, **kwargs):
|
|
1474
|
+
self.sig = sig
|
|
1475
|
+
self.repr = 'LongAction' if repr is None else repr
|
|
1476
|
+
|
|
1477
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1478
|
+
return self
|
|
1479
|
+
|
|
1480
|
+
def __repr__(self):
|
|
1481
|
+
return f'<{self.__class__.__name__}>(sig={self.sig})'
|
|
1482
|
+
|
|
1483
|
+
def __int__(self):
|
|
1484
|
+
return self.sig
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
cdef class ShortAction(ActionNode):
|
|
1488
|
+
def __cinit__(self, *, ssize_t sig=-1, str repr=None, bint auto_connect=True, **kwargs):
|
|
1489
|
+
self.sig = sig
|
|
1490
|
+
self.repr = 'ShortAction' if repr is None else repr
|
|
1491
|
+
|
|
1492
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1493
|
+
return self
|
|
1494
|
+
|
|
1495
|
+
def __repr__(self):
|
|
1496
|
+
return f'<{self.__class__.__name__}>(sig={self.sig})'
|
|
1497
|
+
|
|
1498
|
+
def __int__(self):
|
|
1499
|
+
return self.sig
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
cdef class CancelAction(ActionNode):
|
|
1503
|
+
def __cinit__(self, *, ssize_t sig=0, str repr=None, bint auto_connect=True, **kwargs):
|
|
1504
|
+
self.sig = sig
|
|
1505
|
+
self.repr = 'CancelAction' if repr is None else repr
|
|
1506
|
+
|
|
1507
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1508
|
+
return self
|
|
1509
|
+
|
|
1510
|
+
def __repr__(self):
|
|
1511
|
+
return f'<{self.__class__.__name__}>(sig={self.sig})'
|
|
1512
|
+
|
|
1513
|
+
def __int__(self):
|
|
1514
|
+
return self.sig
|
|
1515
|
+
|
|
1516
|
+
|
|
1517
|
+
cdef class ClearAction(ActionNode):
|
|
1518
|
+
def __cinit__(self, *, ssize_t sig=0, str repr=None, bint auto_connect=True, **kwargs):
|
|
1519
|
+
self.sig = sig
|
|
1520
|
+
self.repr = 'ClearAction' if repr is None else repr
|
|
1521
|
+
|
|
1522
|
+
cdef object c_eval(self, bint enforce_dtype):
|
|
1523
|
+
return self
|
|
1524
|
+
|
|
1525
|
+
def __repr__(self):
|
|
1526
|
+
return f'<{self.__class__.__name__}>(sig={self.sig})'
|
|
1527
|
+
|
|
1528
|
+
def __int__(self):
|
|
1529
|
+
return self.sig
|