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