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.
Files changed (144) hide show
  1. decision_graph/__init__.py +26 -0
  2. decision_graph/decision_tree/__init__.py +85 -0
  3. decision_graph/decision_tree/bake/__infra__.pxd +502 -0
  4. decision_graph/decision_tree/bake/__init__.pxd +502 -0
  5. decision_graph/decision_tree/bake/__init__.py +29 -0
  6. decision_graph/decision_tree/bake/c_action.cp313-win_amd64.pyd +0 -0
  7. decision_graph/decision_tree/bake/c_action.h +324 -0
  8. decision_graph/decision_tree/bake/c_action.pxd +54 -0
  9. decision_graph/decision_tree/bake/c_action.pyi +126 -0
  10. decision_graph/decision_tree/bake/c_action.pyx +65 -0
  11. decision_graph/decision_tree/bake/c_allocator_protocol.cp313-win_amd64.pyd +0 -0
  12. decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
  13. decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
  14. decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
  15. decision_graph/decision_tree/bake/c_bake.cp313-win_amd64.pyd +0 -0
  16. decision_graph/decision_tree/bake/c_bake.h +629 -0
  17. decision_graph/decision_tree/bake/c_bake.pxd +52 -0
  18. decision_graph/decision_tree/bake/c_bake.pyi +151 -0
  19. decision_graph/decision_tree/bake/c_bake.pyx +142 -0
  20. decision_graph/decision_tree/bake/c_collections.cp313-win_amd64.pyd +0 -0
  21. decision_graph/decision_tree/bake/c_collections.h +692 -0
  22. decision_graph/decision_tree/bake/c_collections.pxd +52 -0
  23. decision_graph/decision_tree/bake/c_collections.pyi +193 -0
  24. decision_graph/decision_tree/bake/c_collections.pyx +161 -0
  25. decision_graph/decision_tree/bake/c_const.cp313-win_amd64.pyd +0 -0
  26. decision_graph/decision_tree/bake/c_const.h +395 -0
  27. decision_graph/decision_tree/bake/c_const.pxd +56 -0
  28. decision_graph/decision_tree/bake/c_const.pyi +305 -0
  29. decision_graph/decision_tree/bake/c_const.pyx +278 -0
  30. decision_graph/decision_tree/bake/c_edge.cp313-win_amd64.pyd +0 -0
  31. decision_graph/decision_tree/bake/c_edge.h +511 -0
  32. decision_graph/decision_tree/bake/c_edge.pxd +117 -0
  33. decision_graph/decision_tree/bake/c_edge.pyi +192 -0
  34. decision_graph/decision_tree/bake/c_edge.pyx +270 -0
  35. decision_graph/decision_tree/bake/c_eval.h +848 -0
  36. decision_graph/decision_tree/bake/c_eval.pxd +40 -0
  37. decision_graph/decision_tree/bake/c_expr.cp313-win_amd64.pyd +0 -0
  38. decision_graph/decision_tree/bake/c_expr.h +1900 -0
  39. decision_graph/decision_tree/bake/c_expr.pxd +129 -0
  40. decision_graph/decision_tree/bake/c_expr.pyi +280 -0
  41. decision_graph/decision_tree/bake/c_expr.pyx +272 -0
  42. decision_graph/decision_tree/bake/c_hierarchy.cp313-win_amd64.pyd +0 -0
  43. decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  44. decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
  45. decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
  46. decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
  47. decision_graph/decision_tree/bake/c_logic_group.cp313-win_amd64.pyd +0 -0
  48. decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  49. decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
  50. decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
  51. decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
  52. decision_graph/decision_tree/bake/c_node.cp313-win_amd64.pyd +0 -0
  53. decision_graph/decision_tree/bake/c_node.h +2660 -0
  54. decision_graph/decision_tree/bake/c_node.pxd +381 -0
  55. decision_graph/decision_tree/bake/c_node.pyi +438 -0
  56. decision_graph/decision_tree/bake/c_node.pyx +639 -0
  57. decision_graph/decision_tree/bake/c_reconstruct.cp313-win_amd64.pyd +0 -0
  58. decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
  59. decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
  60. decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
  61. decision_graph/decision_tree/bake/c_var.cp313-win_amd64.pyd +0 -0
  62. decision_graph/decision_tree/bake/c_var.h +2054 -0
  63. decision_graph/decision_tree/bake/c_var.pxd +205 -0
  64. decision_graph/decision_tree/bake/c_var.pyi +271 -0
  65. decision_graph/decision_tree/bake/c_var.pyx +228 -0
  66. decision_graph/decision_tree/capi/__init__.pxd +33 -0
  67. decision_graph/decision_tree/capi/__init__.py +57 -0
  68. decision_graph/decision_tree/capi/c_abc.cp313-win_amd64.pyd +0 -0
  69. decision_graph/decision_tree/capi/c_abc.pxd +246 -0
  70. decision_graph/decision_tree/capi/c_abc.pyi +770 -0
  71. decision_graph/decision_tree/capi/c_abc.pyx +1529 -0
  72. decision_graph/decision_tree/capi/c_collection.cp313-win_amd64.pyd +0 -0
  73. decision_graph/decision_tree/capi/c_collection.pxd +22 -0
  74. decision_graph/decision_tree/capi/c_collection.pyi +245 -0
  75. decision_graph/decision_tree/capi/c_collection.pyx +138 -0
  76. decision_graph/decision_tree/capi/c_node.cp313-win_amd64.pyd +0 -0
  77. decision_graph/decision_tree/capi/c_node.pxd +95 -0
  78. decision_graph/decision_tree/capi/c_node.pyi +492 -0
  79. decision_graph/decision_tree/capi/c_node.pyx +551 -0
  80. decision_graph/decision_tree/exc.py +189 -0
  81. decision_graph/decision_tree/native/__init__.py +57 -0
  82. decision_graph/decision_tree/native/abc.py +1164 -0
  83. decision_graph/decision_tree/native/collection.py +134 -0
  84. decision_graph/decision_tree/native/node.py +521 -0
  85. decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
  86. decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
  87. decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
  88. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
  89. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
  90. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
  91. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
  92. decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
  93. decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
  94. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
  95. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
  96. decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
  97. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
  98. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
  99. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
  100. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  101. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
  102. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  103. decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
  104. decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
  105. decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
  106. decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
  107. decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
  108. decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59143 -0
  109. decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
  110. decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39220 -0
  111. decision_graph/logic_group/__init__.py +25 -0
  112. decision_graph/logic_group/base.py +100 -0
  113. decision_graph/logic_group/pending_request.py +384 -0
  114. decision_graph/webui/__init__.py +24 -0
  115. decision_graph/webui/bake/__init__.py +65 -0
  116. decision_graph/webui/bake/app.py +488 -0
  117. decision_graph/webui/bake/static/d3.v7.min.js +2 -0
  118. decision_graph/webui/bake/static/script.js +1440 -0
  119. decision_graph/webui/bake/static/style.css +920 -0
  120. decision_graph/webui/bake/templates/index.html +183 -0
  121. decision_graph/webui/bake/templates/offline.html +184 -0
  122. decision_graph/webui/capi/__init__.py +54 -0
  123. decision_graph/webui/capi/app.py +366 -0
  124. decision_graph/webui/capi/main.py +53 -0
  125. decision_graph/webui/capi/static/d3.v7.min.js +2 -0
  126. decision_graph/webui/capi/static/script.js +1026 -0
  127. decision_graph/webui/capi/static/style.css +656 -0
  128. decision_graph/webui/capi/templates/index.html +93 -0
  129. decision_graph/webui/capi/templates/offline.html +94 -0
  130. decision_graph/webui/native/__init__.py +54 -0
  131. decision_graph/webui/native/app.py +366 -0
  132. decision_graph/webui/native/main.py +53 -0
  133. decision_graph/webui/native/static/d3.v7.min.js +2 -0
  134. decision_graph/webui/native/static/script.js +1026 -0
  135. decision_graph/webui/native/static/style.css +656 -0
  136. decision_graph/webui/native/templates/index.html +93 -0
  137. decision_graph/webui/native/templates/offline.html +94 -0
  138. pydecisiongraph-0.3.1.post2.dist-info/METADATA +130 -0
  139. pydecisiongraph-0.3.1.post2.dist-info/RECORD +144 -0
  140. pydecisiongraph-0.3.1.post2.dist-info/WHEEL +5 -0
  141. pydecisiongraph-0.3.1.post2.dist-info/licenses/LICENSE +373 -0
  142. pydecisiongraph-0.3.1.post2.dist-info/top_level.txt +2 -0
  143. tests/bake_c_evaluator.cp313-win_amd64.pyd +0 -0
  144. 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