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,639 @@
1
+ from uuid import UUID
2
+
3
+ from cpython.bytes cimport PyBytes_AsString, PyBytes_FromStringAndSize
4
+ from cpython.dict cimport PyDict_Contains
5
+ from cpython.object cimport PyObject
6
+ from cpython.unicode cimport PyUnicode_AsUTF8, PyUnicode_FromString, PyUnicode_FromStringAndSize
7
+ from libc.stdint cimport uintptr_t
8
+ from libc.stdio cimport fprintf, stderr
9
+
10
+ from cbase.bytemap cimport c_bytemap_gen_seq_id
11
+
12
+ from .c_allocator_protocol cimport DCG_DEFAULT_ALLOCATOR
13
+ from .c_edge cimport AUTO_CONDITION, C_AUTO_CONDITION, EDGE_REGISTRY, NO_CONDITION, NodeEdgeCondition, dcg_node_edge_condition
14
+ from .c_var cimport VarView, c_dcg_ret_code_name, c_dcg_var_dealloc, c_dcg_var_init, c_dcg_var_pyunpack, dcg_ret_code
15
+ from ..exc import EvalFailureError, NodeTypeError
16
+
17
+
18
+ cdef inline int c_eval_hook_code(object exc):
19
+ """The code a raised hook ends its node with: the one it carries, or the hook's own error.
20
+
21
+ A hook hands the protocol a code two ways - returning it (a Cython hook, and a
22
+ Python one that returns an int) or raising an EvalFailureError that carries it.
23
+ Raising anything else is the generic hook failure, and the exception itself
24
+ travels as the cause of what the evaluation reports.
25
+ """
26
+ if isinstance(exc, EvalFailureError) and exc.code:
27
+ return <int> exc.code
28
+ return dcg_ret_code.DCG_ERR_HOOK
29
+
30
+
31
+ cdef const size_t DCG_RENDER_BUFSIZE = 1 << 16
32
+
33
+
34
+ cdef class LogicNode:
35
+ def __cinit__(self, *args, **kwargs):
36
+ self.children = {}
37
+ self.condition_to_parent = NO_CONDITION
38
+ # self.callback_id = 0
39
+
40
+ def __init__(self, *, str repr=None, object uid=None, **kwargs):
41
+ raise NodeTypeError(
42
+ f'{self.__class__.__name__} is the node base: bake has no type for a bare node yet. '
43
+ 'Build an action leaf (NoAction, LongAction, ...), a root, or an expression node.'
44
+ )
45
+
46
+ def __dealloc__(self):
47
+ if not self.owner:
48
+ return
49
+
50
+ if self.header:
51
+ if self.callback_id:
52
+ c_dcg_node_unregister_callback(self.header, self.callback_id)
53
+ c_dcg_node_free_generic(self.header)
54
+
55
+ @staticmethod
56
+ cdef inline LogicNode c_from_header(dcg_node* header, bint owner=False):
57
+ cdef LogicNode instance = LogicNode.__new__(LogicNode)
58
+ instance.header = header
59
+ instance.owner = owner
60
+
61
+ # The registry is keyed by the header address, so the parent is looked
62
+ # up by ITS header - a wrapper is not an address, and a parentless node
63
+ # has none to look up.
64
+ cdef uintptr_t parent_addr = 0
65
+ instance.parent = None
66
+ if header.parent:
67
+ parent_addr = <uintptr_t> header.parent
68
+ if parent_addr in NODE_REGISTRY:
69
+ instance.parent = NODE_REGISTRY[parent_addr]
70
+ return instance
71
+
72
+ @staticmethod
73
+ cdef inline dcg_logic_group_manager* c_get_manager():
74
+ if not C_LGM:
75
+ from .c_logic_group import LGM as _LGM
76
+ global C_LGM, LGM
77
+ C_LGM = <dcg_logic_group_manager*> <uintptr_t> _LGM.address
78
+ LGM = _LGM
79
+ return C_LGM
80
+
81
+ @staticmethod
82
+ cdef LogicNode c_dcg_node_reconstruct(dcg_node* header, bint owner=False):
83
+ from .c_reconstruct import c_dcg_node_reconstruct_from_address
84
+ return c_dcg_node_reconstruct_from_address(<uintptr_t> header, owner)
85
+
86
+ @staticmethod
87
+ def get_manager():
88
+ if not LGM:
89
+ LogicNode.c_get_manager()
90
+ return LGM
91
+
92
+ cdef inline void c_register_node(self):
93
+ NODE_REGISTRY[<uintptr_t> self.header] = self
94
+
95
+ cdef int ret_code = c_dcg_node_register_callback(self.header, LogicNode.c_node_callback_event_adaptor, <void*> <PyObject*> self, &self.callback_id)
96
+ if ret_code != dcg_ret_code.DCG_OK:
97
+ raise RuntimeError(f'c_dcg_node_register_callback failed with err code: {ret_code}')
98
+
99
+ self.c_bind_eval_callback()
100
+
101
+ # === Cython Internal Eval Binding ===
102
+
103
+ @staticmethod
104
+ cdef inline int c_pre_eval_callback_adaptor(dcg_node* node, void* user_data) noexcept:
105
+ cdef LogicNode wrapper = <LogicNode> <PyObject*> user_data
106
+ cdef uint32_t flags = wrapper.eval_hook_flags
107
+ cdef int ret_code = dcg_ret_code.DCG_OK
108
+ cdef object result = None
109
+
110
+ if flags & (dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_PRE_EVAL):
111
+ try:
112
+ ret_code = wrapper.c_pre_eval_fn()
113
+ except BaseException as exc:
114
+ wrapper.c_eval_exception = exc
115
+ return c_eval_hook_code(exc)
116
+ if ret_code != dcg_ret_code.DCG_OK:
117
+ return ret_code
118
+ if flags & (dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_PRE_EVAL):
119
+ try:
120
+ result = wrapper.pre_eval_fn()
121
+ except BaseException as exc:
122
+ wrapper.c_eval_exception = exc
123
+ return c_eval_hook_code(exc)
124
+ return dcg_ret_code.DCG_OK if result is None else <int> result
125
+ return dcg_ret_code.DCG_OK
126
+
127
+ @staticmethod
128
+ cdef inline int c_eval_fn_callback_adaptor(dcg_node* node, void* user_data) noexcept:
129
+ cdef LogicNode wrapper = <LogicNode> <PyObject*> user_data
130
+ cdef uint32_t flags = wrapper.eval_hook_flags
131
+ cdef int ret_code = dcg_ret_code.DCG_OK
132
+ cdef object result = None
133
+
134
+ if flags & (dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_EVAL):
135
+ try:
136
+ ret_code = wrapper.c_eval_fn()
137
+ except BaseException as exc:
138
+ wrapper.c_eval_exception = exc
139
+ return c_eval_hook_code(exc)
140
+ if ret_code != dcg_ret_code.DCG_OK:
141
+ return ret_code
142
+ if flags & (dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_EVAL):
143
+ try:
144
+ result = wrapper.eval_fn()
145
+ except BaseException as exc:
146
+ wrapper.c_eval_exception = exc
147
+ return c_eval_hook_code(exc)
148
+ return dcg_ret_code.DCG_OK if result is None else <int> result
149
+ return dcg_ret_code.DCG_OK
150
+
151
+ @staticmethod
152
+ cdef inline int c_post_eval_callback_adaptor(dcg_node* node, void* user_data) noexcept:
153
+ cdef LogicNode wrapper = <LogicNode> <PyObject*> user_data
154
+ cdef uint32_t flags = wrapper.eval_hook_flags
155
+ cdef int ret_code = dcg_ret_code.DCG_OK
156
+ cdef object result = None
157
+
158
+ if flags & (dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_POST_EVAL):
159
+ try:
160
+ ret_code = wrapper.c_post_eval_fn()
161
+ except BaseException as exc:
162
+ wrapper.c_eval_exception = exc
163
+ return c_eval_hook_code(exc)
164
+ if ret_code != dcg_ret_code.DCG_OK:
165
+ return ret_code
166
+ if flags & (dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_POST_EVAL):
167
+ try:
168
+ result = wrapper.post_eval_fn()
169
+ except BaseException as exc:
170
+ wrapper.c_eval_exception = exc
171
+ return c_eval_hook_code(exc)
172
+ return dcg_ret_code.DCG_OK if result is None else <int> result
173
+ return dcg_ret_code.DCG_OK
174
+
175
+ cdef int c_pre_eval_fn(self):
176
+ return dcg_ret_code.DCG_OK
177
+
178
+ cdef int c_eval_fn(self):
179
+ return dcg_ret_code.DCG_OK
180
+
181
+ cdef int c_post_eval_fn(self):
182
+ return dcg_ret_code.DCG_OK
183
+
184
+ cdef void c_bind_eval_callback(self):
185
+ if not self.header:
186
+ return
187
+
188
+ cdef bytes class_name = type(self).__name__.encode()
189
+ cdef uint32_t flags = 0
190
+ if <void*> self.c_pre_eval_fn != <void*> LogicNode.c_pre_eval_fn:
191
+ flags |= dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_PRE_EVAL
192
+ if <void*> self.c_eval_fn != <void*> LogicNode.c_eval_fn:
193
+ flags |= dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_EVAL
194
+ if <void*> self.c_post_eval_fn != <void*> LogicNode.c_post_eval_fn:
195
+ flags |= dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_POST_EVAL
196
+
197
+ if type(self).pre_eval_fn is not getattr(LogicNode, 'pre_eval_fn'):
198
+ flags |= dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_PRE_EVAL
199
+ if type(self).eval_fn is not getattr(LogicNode, 'eval_fn'):
200
+ flags |= dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_EVAL
201
+ if type(self).post_eval_fn is not getattr(LogicNode, 'post_eval_fn'):
202
+ flags |= dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_POST_EVAL
203
+
204
+ if not flags:
205
+ return
206
+
207
+ if not self.owner:
208
+ fprintf(stderr, 'c_bind_eval_callback: %s at %p overrides an eval hook but does not own its node - the hook is not installed\n', <const char*> class_name, <const void*> self.header)
209
+ return
210
+
211
+ self.eval_hook_flags = flags
212
+ cdef int ret_code = 0
213
+
214
+ if flags & ((dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE | dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE) << dcg_node_hook_type.DCG_HOOK_PRE_EVAL):
215
+ ret_code = c_dcg_node_register_eval_hook(self.header, dcg_node_hook_type.DCG_HOOK_PRE_EVAL, LogicNode.c_pre_eval_callback_adaptor, <void*> <PyObject*> self)
216
+ if ret_code != dcg_ret_code.DCG_OK:
217
+ raise RuntimeError(f'c_dcg_node_register_eval_hook failed with err code: {ret_code}')
218
+ if flags & ((dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE | dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE) << dcg_node_hook_type.DCG_HOOK_EVAL):
219
+ ret_code = c_dcg_node_register_eval_hook(self.header, dcg_node_hook_type.DCG_HOOK_EVAL, LogicNode.c_eval_fn_callback_adaptor, <void*> <PyObject*> self)
220
+ if ret_code != dcg_ret_code.DCG_OK:
221
+ raise RuntimeError(f'c_dcg_node_register_eval_hook failed with err code: {ret_code}')
222
+ if flags & ((dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE | dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE) << dcg_node_hook_type.DCG_HOOK_POST_EVAL):
223
+ ret_code = c_dcg_node_register_eval_hook(self.header, dcg_node_hook_type.DCG_HOOK_POST_EVAL, LogicNode.c_post_eval_callback_adaptor, <void*> <PyObject*> self)
224
+ if ret_code != dcg_ret_code.DCG_OK:
225
+ raise RuntimeError(f'c_dcg_node_register_eval_hook failed with err code: {ret_code}')
226
+
227
+ @staticmethod
228
+ cdef list c_eval_stage_name_list(dcg_eval_stage stage):
229
+ cdef list names = []
230
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_PRE_EVAL:
231
+ names.append('pre')
232
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_EVAL:
233
+ names.append('eval')
234
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_POST_EVAL:
235
+ names.append('post')
236
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_DONE:
237
+ names.append('done')
238
+ return names
239
+
240
+ @staticmethod
241
+ cdef str c_eval_stage_names(dcg_eval_stage stage):
242
+ cdef list names = LogicNode.c_eval_stage_name_list(stage)
243
+ return ', '.join(names) if names else 'none'
244
+
245
+ @staticmethod
246
+ cdef str c_eval_failed_stage_name(dcg_eval_stage stage):
247
+ """The stage a failure stopped at, read off the mask of what completed.
248
+
249
+ A failure that happened AT a stage is a stage that did not complete, and
250
+ the protocol walks them in order - so the first one missing from the mask
251
+ is where it stopped. None when the mask says the node got through all of
252
+ them, which is not a failure a node reports.
253
+ """
254
+ if not (stage & dcg_eval_stage.DCG_EVAL_STAGE_PRE_EVAL):
255
+ return 'pre'
256
+ if not (stage & dcg_eval_stage.DCG_EVAL_STAGE_EVAL):
257
+ return 'eval'
258
+ if not (stage & dcg_eval_stage.DCG_EVAL_STAGE_POST_EVAL):
259
+ return 'post'
260
+ return None
261
+
262
+ @staticmethod
263
+ cdef str c_eval_source_name(dcg_eval_stage stage):
264
+ """What was running, read off the same mask that says how far it got.
265
+
266
+ The two questions a failure asks - which stage, and what was running - are
267
+ read from one word: the producer bits are the eval stage's own, and a node
268
+ that never got that far has the stage itself for an answer, because the pre
269
+ and post hooks produce no value - what ran there IS the hook.
270
+ """
271
+ cdef str failed_at = LogicNode.c_eval_failed_stage_name(stage)
272
+ if failed_at == 'pre':
273
+ return 'pre_hook'
274
+ if failed_at == 'post':
275
+ return 'post_hook'
276
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_HOOK:
277
+ return 'hook'
278
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_TYPE_RULE:
279
+ return 'type_rule'
280
+ if stage & dcg_eval_stage.DCG_EVAL_STAGE_BUILTIN:
281
+ return 'builtin'
282
+ return None
283
+
284
+ cdef void c_check_eval_code(self, int ret_code, dcg_node* subject=NULL):
285
+ """Report a failed evaluation as an EvalFailureError - the one place it is built.
286
+
287
+ Every door that evaluates a node comes through here, so a failure reports
288
+ the same things whichever one asked: the node that refused, the code it
289
+ ended with, the stage that failed, what was running (the built-in rule, the
290
+ rule the node's type installed, or a hook), and the run it happened in.
291
+
292
+ WHERE it stopped is the failing node's own state, not this wrapper's: in a
293
+ walk the node that reported the code is the one whose stages say how far it
294
+ got, and a root that reached it is not the node that refused.
295
+
296
+ A hook's own exception - kept by the adapter that caught it - becomes the
297
+ CAUSE of this one, so nothing the hook said is lost and the code the node
298
+ ends with is still the protocol's to name.
299
+ """
300
+ if ret_code == dcg_ret_code.DCG_OK:
301
+ return
302
+
303
+ cdef object cause = self.c_eval_exception
304
+ self.c_eval_exception = None
305
+
306
+ cdef dcg_node* failed = subject if subject else self.header
307
+ cdef dcg_node_eval_ctx* ctx = &failed.eval_ctx
308
+ cdef str code = PyUnicode_FromString(c_dcg_ret_code_name(<dcg_ret_code> ret_code))
309
+ cdef str run = f'run {ctx.eval_seq_id:#0x}' if ctx.eval_seq_id else 'no run'
310
+ cdef str text = PyUnicode_FromString(failed.repr) if failed.repr else 'unrepr'
311
+ cdef str type_str = PyUnicode_FromString(c_dcg_node_type_name(failed.ntype))
312
+ cdef object failed_at = LogicNode.c_eval_failed_stage_name(ctx.stage)
313
+ cdef object source = LogicNode.c_eval_source_name(ctx.stage)
314
+ cdef str stopped = f'; stopped at {failed_at} in {source}' if failed_at and source else ''
315
+
316
+ raise EvalFailureError(
317
+ f'{type_str}({text!r}) at {<uintptr_t> failed:#0x} failed to evaluate: {code} ({ret_code}); '
318
+ f'stages completed: {LogicNode.c_eval_stage_names(ctx.stage)}; {run}{stopped}',
319
+ code=ret_code,
320
+ code_name=code,
321
+ node=NODE_REGISTRY[<uintptr_t> failed],
322
+ node_type=type_str,
323
+ node_repr=text,
324
+ address=<uintptr_t> failed,
325
+ source=source,
326
+ failed_at=failed_at,
327
+ stages=tuple(LogicNode.c_eval_stage_name_list(ctx.stage)),
328
+ run_id=ctx.eval_seq_id,
329
+ ) from cause
330
+
331
+ # === Cython Internal Binding ===
332
+
333
+ @staticmethod
334
+ cdef void c_node_callback_event_adaptor(dcg_node_event event, dcg_node* node, dcg_node* subject, uint64_t seq_id, void* user_data) noexcept:
335
+ if not node or not user_data:
336
+ return
337
+
338
+ cdef LogicNode wrapper = <LogicNode> <PyObject*> user_data
339
+ cdef LogicNode child
340
+ cdef NodeEdgeCondition condition
341
+
342
+ if event == DCG_NODE_EVENT_CHILD_ADDED or event == DCG_NODE_EVENT_CHILD_UPDATED:
343
+ child = NODE_REGISTRY[<uintptr_t> subject]
344
+ condition = EDGE_REGISTRY[<uintptr_t> subject.condition_to_parent]
345
+ wrapper.children[condition] = child
346
+ child.parent = wrapper
347
+ child.condition_to_parent = condition
348
+ elif event == DCG_NODE_EVENT_CHILD_REMOVED:
349
+ for condition, child in wrapper.children.items():
350
+ if <uintptr_t> child.header == <uintptr_t> subject:
351
+ break
352
+ else:
353
+ raise BufferError(f'Node {<uintptr_t> node:#0x} not found!')
354
+ wrapper.children.pop(condition)
355
+ child = NODE_REGISTRY[<uintptr_t> subject]
356
+ child.parent = None
357
+ child.condition_to_parent = NO_CONDITION
358
+ elif event == DCG_NODE_EVENT_CHILD_CLEARED:
359
+ wrapper.children.clear()
360
+
361
+ cdef void c_append(self, dcg_node* child, dcg_node_edge_condition* condition):
362
+ cdef int ret_code = c_dcg_node_append(self.header, child, condition)
363
+ if ret_code != dcg_ret_code.DCG_OK:
364
+ raise RuntimeError(f'c_dcg_node_append failed with err code: {ret_code}')
365
+
366
+ cdef void c_replace(self, dcg_node* old_node, dcg_node* new_node):
367
+ cdef int ret_code = c_dcg_node_replace(old_node, new_node)
368
+ if ret_code != dcg_ret_code.DCG_OK:
369
+ raise RuntimeError(f'c_dcg_node_replace failed with err code: {ret_code}')
370
+
371
+ cdef void c_detach(self):
372
+ cdef int ret_code = c_dcg_node_detach(self.header)
373
+ if ret_code != dcg_ret_code.DCG_OK:
374
+ raise RuntimeError(f'c_dcg_node_detach failed with err code: {ret_code}')
375
+
376
+ cdef str c_render(self, int max_depth, bint show_labels, bint show_out, str style):
377
+ cdef dcg_render_opts opts
378
+ cdef size_t cap = DCG_RENDER_BUFSIZE
379
+ cdef int ret
380
+ cdef bytes buffer
381
+ cdef char* out
382
+
383
+ c_dcg_render_opts_default(&opts)
384
+ opts.max_depth = max_depth
385
+ opts.show_labels = show_labels
386
+ opts.show_out = show_out
387
+ opts.style = DCG_RENDER_UNICODE if style == 'unicode' else DCG_RENDER_ASCII
388
+
389
+ buffer = PyBytes_FromStringAndSize(NULL, <Py_ssize_t> cap)
390
+ out = PyBytes_AsString(buffer)
391
+ ret = c_dcg_node_render_to_string(self.header, out, cap, &opts)
392
+ if ret == dcg_ret_code.DCG_ERR_FULL:
393
+ raise BufferError(f'The render does not fit the {cap}-byte buffer.')
394
+ if ret < 0:
395
+ raise RuntimeError(f'c_dcg_node_render_to_string failed with err code: {ret}')
396
+ return PyUnicode_FromStringAndSize(out, ret)
397
+
398
+ # === Python Dunders ===
399
+
400
+ def __repr__(self):
401
+ if not self.header:
402
+ return f'<{self.__class__.__name__} (Uninitialized)>'
403
+ return f'<{self.__class__.__name__}({self.repr!r})>'
404
+
405
+ def __rshift__(self, LogicNode other):
406
+ self.c_append(other.header, C_AUTO_CONDITION)
407
+ return other
408
+
409
+ def __enter__(self):
410
+ cdef int ret_code = c_dcg_lgm_enter_node(LogicNode.c_get_manager(), self.header)
411
+ if ret_code != dcg_ret_code.DCG_OK:
412
+ raise RuntimeError(f'c_dcg_lgm_enter_node failed with err code: {ret_code}')
413
+ self.get_manager().node_stack_append(self)
414
+ return self
415
+
416
+ def __exit__(self, exc_type, exc_value, traceback):
417
+ cdef int ret_code = c_dcg_lgm_exit_node(LogicNode.c_get_manager(), self.header)
418
+ if ret_code != dcg_ret_code.DCG_OK:
419
+ raise RuntimeError(f'c_dcg_lgm_exit_node failed with err code: {ret_code}')
420
+ self.get_manager().node_stack_pop(self)
421
+ return False
422
+
423
+ # === Python Interfaces ===
424
+
425
+ def append(self, LogicNode child, NodeEdgeCondition condition=AUTO_CONDITION):
426
+ self.c_append(child.header, condition.header if condition is not None else C_AUTO_CONDITION)
427
+
428
+ def overwrite(self, LogicNode new_node, NodeEdgeCondition condition):
429
+ cdef LogicNode original = self.children.get(condition)
430
+ if original is None:
431
+ raise KeyError(f'Edge {condition} not registered, cannot overwrite.')
432
+ self.c_replace(original.header, new_node.header)
433
+
434
+ def replace(self, LogicNode original_node, LogicNode new_node):
435
+ self.c_replace(original_node.header, new_node.header)
436
+
437
+ def detach(self):
438
+ self.c_detach()
439
+
440
+ def label(self, str name):
441
+ cdef int ret_code = c_dcg_node_add_label(self.header, PyUnicode_AsUTF8(name))
442
+ if ret_code != dcg_ret_code.DCG_OK:
443
+ raise RuntimeError(f'c_dcg_node_add_label failed with err code: {ret_code}')
444
+
445
+ def unlabel(self, str name):
446
+ cdef int ret_code = c_dcg_node_remove_label(self.header, PyUnicode_AsUTF8(name))
447
+ if ret_code != dcg_ret_code.DCG_OK:
448
+ raise RuntimeError(f'c_dcg_node_remove_label failed with err code: {ret_code}')
449
+
450
+ def has_label(self, str name):
451
+ return bool(c_dcg_node_has_label(self.header, PyUnicode_AsUTF8(name)))
452
+
453
+ def render(self, int max_depth=0, bint show_labels=True, bint show_out=False, str style='unicode'):
454
+ return self.c_render(max_depth, show_labels, show_out, style)
455
+
456
+ def validate(self):
457
+ cdef dcg_validate_report report
458
+ if c_dcg_node_validate(self.header, &report):
459
+ return None
460
+ return report.code, report.errors, report.nodes, report.depth
461
+
462
+ # === Eval Protocol ===
463
+
464
+ def pre_eval_fn(self):
465
+ pass
466
+
467
+ def eval_fn(self):
468
+ pass
469
+
470
+ def post_eval_fn(self):
471
+ pass
472
+
473
+ cdef void c_eval(self):
474
+ if not self.header:
475
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
476
+ self.c_check_eval_code(c_dcg_node_eval(self.header))
477
+
478
+ def eval(self):
479
+ self.c_eval()
480
+ return c_dcg_var_pyunpack(&self.header.out)
481
+
482
+ def dry_run(self):
483
+ if not self.header:
484
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
485
+
486
+ cdef dcg_var_t value
487
+ c_dcg_var_init(&value)
488
+
489
+ cdef int ret_code = c_dcg_node_dryrun(self.header, &value)
490
+ cdef object answer = c_dcg_var_pyunpack(&value)
491
+ c_dcg_var_dealloc(&value)
492
+
493
+ if ret_code != dcg_ret_code.DCG_OK:
494
+ self.c_check_eval_code(ret_code)
495
+ return answer
496
+
497
+ property eval_hooks:
498
+ def __get__(self):
499
+ if not self.header:
500
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
501
+
502
+ cdef uint32_t flags = self.eval_hook_flags
503
+ cdef list installed = []
504
+
505
+ if flags & (dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_PRE_EVAL):
506
+ installed.append('c_pre_eval_fn')
507
+ if flags & (dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_PRE_EVAL):
508
+ installed.append('pre_eval_fn')
509
+ if flags & (dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_EVAL):
510
+ installed.append('c_eval_fn')
511
+ if flags & (dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_EVAL):
512
+ installed.append('eval_fn')
513
+ if flags & (dcg_eval_override_flag.DCG_EVAL_CDEF_OVERRIDE << dcg_node_hook_type.DCG_HOOK_POST_EVAL):
514
+ installed.append('c_post_eval_fn')
515
+ if flags & (dcg_eval_override_flag.DCG_EVAL_PY_OVERRIDE << dcg_node_hook_type.DCG_HOOK_POST_EVAL):
516
+ installed.append('post_eval_fn')
517
+ return tuple(installed)
518
+
519
+ # === Python Properties ===
520
+
521
+ property repr:
522
+ def __get__(self):
523
+ if not self.header:
524
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
525
+ if not self.header.repr:
526
+ return None
527
+ return PyUnicode_FromString(self.header.repr)
528
+
529
+ property type:
530
+ def __get__(self):
531
+ if not self.header:
532
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
533
+ return PyUnicode_FromString(c_dcg_node_type_name(self.header.ntype))
534
+
535
+ property uuid:
536
+ def __get__(self):
537
+ if not self.header:
538
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
539
+ return UUID(bytes=self.header.uid[:16])
540
+
541
+ property autogen:
542
+ def __get__(self):
543
+ if not self.header:
544
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
545
+ return bool(self.header.autogen)
546
+
547
+ property is_leaf:
548
+ def __get__(self):
549
+ if not self.header:
550
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
551
+ return c_dcg_node_is_leaf(self.header)
552
+
553
+ property labels:
554
+ def __get__(self):
555
+ cdef list out = []
556
+ if not self.header:
557
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
558
+ cdef dcg_node_label* entry = self.header.labels
559
+ while entry != NULL:
560
+ if entry.label != NULL:
561
+ out.append(PyUnicode_FromString(entry.label))
562
+ entry = entry.next
563
+ return out
564
+
565
+ property size:
566
+ def __get__(self):
567
+ if not self.header:
568
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
569
+ return c_dcg_node_subtree_size(self.header)
570
+
571
+ property out:
572
+ def __get__(self):
573
+ if not self.header:
574
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
575
+ return VarView.c_from_header(&self.header.out)
576
+
577
+ property address:
578
+ def __get__(self):
579
+ if not self.header:
580
+ raise RuntimeError(f'<{self.__class__.__name__}> not initialized!')
581
+ return <uintptr_t> self.header
582
+
583
+
584
+ cdef class PlaceholderNode(LogicNode):
585
+ def __init__(self, **kwargs):
586
+ cdef dcg_node* node = c_dcg_node_new_placeholder(DCG_DEFAULT_ALLOCATOR)
587
+ if not node:
588
+ raise MemoryError('Failed to allocate a PlaceholderNode.')
589
+
590
+ self.header = node
591
+ self.owner = True
592
+ self.c_register_node()
593
+
594
+
595
+ cdef class LogicNodeRegistry(BoundByteMap):
596
+ @staticmethod
597
+ cdef LogicNodeRegistry c_from_header(bytemap* header, bint owner=False):
598
+ cdef LogicNodeRegistry instance = LogicNodeRegistry.__new__(LogicNodeRegistry)
599
+ instance.owner = owner
600
+ instance.seq_id = c_bytemap_gen_seq_id(<void*> instance)
601
+ instance.c_bind(header)
602
+ return instance
603
+
604
+ cdef const char* c_serialize_key(self, object obj, size_t* key_len):
605
+ # A header address is provided in format of int (uintptr_t)
606
+ if isinstance(obj, int):
607
+ self._ws_key_buf = <void*> <uintptr_t> obj
608
+ if key_len:
609
+ key_len[0] = sizeof(void*)
610
+ return <const char*> &self._ws_key_buf
611
+ return BoundByteMap.c_serialize_key(self, obj, key_len)
612
+
613
+ cdef object c_deserialize_key(self, const char* key, size_t key_len):
614
+ return <uintptr_t> <void*> key
615
+
616
+ cdef const char* c_serialize_value(self, object obj, size_t* value_len):
617
+ cdef LogicNode node = <LogicNode> obj
618
+ self._ws_ptr = <void*> node.header
619
+ if value_len:
620
+ value_len[0] = sizeof(void*)
621
+ return <const char*> &self._ws_ptr
622
+
623
+ cdef object c_deserialize_value(self, const char* value, size_t value_len):
624
+ cdef void** vp = <void**> value
625
+ cdef dcg_node* node = <dcg_node*> vp[0]
626
+ return LogicNode.c_dcg_node_reconstruct(node)
627
+
628
+ def __getitem__(self, object key):
629
+ cdef int ret_code = PyDict_Contains(<dict> self, key)
630
+ if ret_code == 0:
631
+ return LogicNode.c_dcg_node_reconstruct(<dcg_node*> <uintptr_t> key)
632
+ return super().__getitem__(key)
633
+
634
+ # Local Cached LGM Pointer
635
+ cdef dcg_logic_group_manager* C_LGM = NULL
636
+ cdef object LGM = None
637
+
638
+ cdef LogicNodeRegistry NODE_REGISTRY = LogicNodeRegistry()
639
+ globals()['NODE_REGISTRY'] = NODE_REGISTRY