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.
- decision_graph/__infra__.pxd +1 -0
- decision_graph/__init__.pxd +1 -0
- decision_graph/__init__.py +39 -0
- decision_graph/decision_tree/__init__.pxd +2 -0
- decision_graph/decision_tree/__init__.py +85 -0
- decision_graph/decision_tree/bake/__infra__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.pxd +502 -0
- decision_graph/decision_tree/bake/__init__.py +29 -0
- decision_graph/decision_tree/bake/c_action.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/decision_tree/bake/c_action.pxd +54 -0
- decision_graph/decision_tree/bake/c_action.pyi +126 -0
- decision_graph/decision_tree/bake/c_action.pyx +65 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
- decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
- decision_graph/decision_tree/bake/c_bake.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/decision_tree/bake/c_bake.pxd +52 -0
- decision_graph/decision_tree/bake/c_bake.pyi +151 -0
- decision_graph/decision_tree/bake/c_bake.pyx +142 -0
- decision_graph/decision_tree/bake/c_collections.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/decision_tree/bake/c_collections.pxd +52 -0
- decision_graph/decision_tree/bake/c_collections.pyi +193 -0
- decision_graph/decision_tree/bake/c_collections.pyx +161 -0
- decision_graph/decision_tree/bake/c_const.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/decision_tree/bake/c_const.pxd +56 -0
- decision_graph/decision_tree/bake/c_const.pyi +305 -0
- decision_graph/decision_tree/bake/c_const.pyx +278 -0
- decision_graph/decision_tree/bake/c_edge.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/decision_tree/bake/c_edge.pxd +117 -0
- decision_graph/decision_tree/bake/c_edge.pyi +192 -0
- decision_graph/decision_tree/bake/c_edge.pyx +270 -0
- decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/decision_tree/bake/c_eval.pxd +40 -0
- decision_graph/decision_tree/bake/c_expr.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/decision_tree/bake/c_expr.pxd +129 -0
- decision_graph/decision_tree/bake/c_expr.pyi +280 -0
- decision_graph/decision_tree/bake/c_expr.pyx +272 -0
- decision_graph/decision_tree/bake/c_hierarchy.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
- decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
- decision_graph/decision_tree/bake/c_logic_group.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
- decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
- decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
- decision_graph/decision_tree/bake/c_node.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/decision_tree/bake/c_node.pxd +381 -0
- decision_graph/decision_tree/bake/c_node.pyi +438 -0
- decision_graph/decision_tree/bake/c_node.pyx +639 -0
- decision_graph/decision_tree/bake/c_reconstruct.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
- decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
- decision_graph/decision_tree/bake/c_var.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/decision_tree/bake/c_var.pxd +205 -0
- decision_graph/decision_tree/bake/c_var.pyi +271 -0
- decision_graph/decision_tree/bake/c_var.pyx +228 -0
- decision_graph/decision_tree/capi/__infra__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.pxd +33 -0
- decision_graph/decision_tree/capi/__init__.py +57 -0
- decision_graph/decision_tree/capi/c_abc.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_abc.pxd +246 -0
- decision_graph/decision_tree/capi/c_abc.pyi +770 -0
- decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
- decision_graph/decision_tree/capi/c_collection.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_collection.pxd +22 -0
- decision_graph/decision_tree/capi/c_collection.pyi +245 -0
- decision_graph/decision_tree/capi/c_collection.pyx +138 -0
- decision_graph/decision_tree/capi/c_node.cp315-win_amd64.pyd +0 -0
- decision_graph/decision_tree/capi/c_node.pxd +95 -0
- decision_graph/decision_tree/capi/c_node.pyi +492 -0
- decision_graph/decision_tree/capi/c_node.pyx +551 -0
- decision_graph/decision_tree/exc.py +189 -0
- decision_graph/decision_tree/native/__init__.py +57 -0
- decision_graph/decision_tree/native/abc.py +1167 -0
- decision_graph/decision_tree/native/collection.py +134 -0
- decision_graph/decision_tree/native/node.py +521 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
- decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
- decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
- decision_graph/logic_group/__init__.py +25 -0
- decision_graph/logic_group/base.py +100 -0
- decision_graph/logic_group/pending_request.py +384 -0
- decision_graph/webui/__init__.py +24 -0
- decision_graph/webui/bake/__init__.py +65 -0
- decision_graph/webui/bake/app.py +488 -0
- decision_graph/webui/bake/static/d3.v7.min.js +2 -0
- decision_graph/webui/bake/static/script.js +1440 -0
- decision_graph/webui/bake/static/style.css +920 -0
- decision_graph/webui/bake/templates/index.html +183 -0
- decision_graph/webui/bake/templates/offline.html +184 -0
- decision_graph/webui/capi/__init__.py +54 -0
- decision_graph/webui/capi/app.py +366 -0
- decision_graph/webui/capi/main.py +53 -0
- decision_graph/webui/capi/static/d3.v7.min.js +2 -0
- decision_graph/webui/capi/static/script.js +1026 -0
- decision_graph/webui/capi/static/style.css +656 -0
- decision_graph/webui/capi/templates/index.html +93 -0
- decision_graph/webui/capi/templates/offline.html +94 -0
- decision_graph/webui/native/__init__.py +54 -0
- decision_graph/webui/native/app.py +366 -0
- decision_graph/webui/native/main.py +53 -0
- decision_graph/webui/native/static/d3.v7.min.js +2 -0
- decision_graph/webui/native/static/script.js +1026 -0
- decision_graph/webui/native/static/style.css +656 -0
- decision_graph/webui/native/templates/index.html +93 -0
- decision_graph/webui/native/templates/offline.html +94 -0
- pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
- pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
- pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
- pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
- pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
- tests/bake_c_evaluator.cp315-win_amd64.pyd +0 -0
- tests/bake_eval_probe.cp315-win_amd64.pyd +0 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
"""The node layer: the graph's nodes, their edges, and the wrapper registry.
|
|
2
|
+
|
|
3
|
+
A node is a block in C and a wrapper here, and the two are held together by
|
|
4
|
+
address: ``NODE_REGISTRY`` is keyed by the block's address, so one node is one
|
|
5
|
+
Python object for as long as the layer holds it.
|
|
6
|
+
|
|
7
|
+
A node that is entered with the ``with`` statement is a scope a build descends
|
|
8
|
+
into - the manager reserves the arms its branches will fill - and the children
|
|
9
|
+
dict is the graph as Python sees it, keyed by the edge each child hangs by.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Self
|
|
13
|
+
from uuid import UUID
|
|
14
|
+
|
|
15
|
+
from cbase.bytemap import BoundByteMap
|
|
16
|
+
|
|
17
|
+
from .c_edge import AUTO_CONDITION, NodeEdgeCondition
|
|
18
|
+
from .c_logic_group import LogicGroupManager
|
|
19
|
+
from .c_var import VarView
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LogicNode:
|
|
23
|
+
"""A node of a bake graph: a block in C, wrapped here.
|
|
24
|
+
|
|
25
|
+
Building is the ``with`` statement: entering a node reserves the arms its
|
|
26
|
+
branches will be filled into, and whatever a build leaves reserved is closed
|
|
27
|
+
by the C layer on the way out. A node reached from C - a child the C layer
|
|
28
|
+
grew, a node named by an address - is turned back into a wrapper by
|
|
29
|
+
``c_reconstruct``, which is the only place a node's class is decided.
|
|
30
|
+
|
|
31
|
+
**The evaluation protocol.** Evaluating a graph walks it from the top down,
|
|
32
|
+
one node at a time, and comes to rest on a single leaf: each node's value is
|
|
33
|
+
produced (from its type, or from a hook), the value selects which edge the
|
|
34
|
+
walk follows, and the node that had no edge to follow is where the walk
|
|
35
|
+
stops. A node off that path keeps the value it had.
|
|
36
|
+
|
|
37
|
+
A subclass may take part in that, at any of three points around the value:
|
|
38
|
+
|
|
39
|
+
- ``pre_eval_fn`` runs before the node's value is produced. A refusal here
|
|
40
|
+
stops the node where it stands.
|
|
41
|
+
- ``eval_fn`` produces the node's value, and is what a subclass overrides to
|
|
42
|
+
compute rather than to react. A subclass that overrides neither this nor
|
|
43
|
+
its Cython counterpart evaluates by the rule for its type.
|
|
44
|
+
- ``post_eval_fn`` runs after the value is produced, so it reacts to a value
|
|
45
|
+
that is already in the slot.
|
|
46
|
+
|
|
47
|
+
Each hook answers with the code the node ends with: return ``None`` to let
|
|
48
|
+
the evaluation continue, or a negative ``DCG_ERR_*`` code to refuse it. An
|
|
49
|
+
exception raised inside a hook is reported as that exception rather than as a
|
|
50
|
+
code - the hook's failure is the hook's to describe.
|
|
51
|
+
|
|
52
|
+
Hooks are installed **once, when the node is built**, and only for the hooks
|
|
53
|
+
a subclass has actually overridden: a node that overrides none carries no
|
|
54
|
+
hooks at all, so the evaluator has nothing to call. What that means is that a
|
|
55
|
+
hook cannot be added to a node that is already built, and that a wrapper
|
|
56
|
+
which does **not own** its node cannot carry one at all - the C node would
|
|
57
|
+
end up holding a pointer to a Python object nothing keeps alive. A read built
|
|
58
|
+
by a store is the store's, so it is the one kind of node that cannot hook.
|
|
59
|
+
|
|
60
|
+
The same three points exist one level down, as ``cdef`` hooks
|
|
61
|
+
(``c_pre_eval_fn``, ``c_eval_fn``, ``c_post_eval_fn``), which a subclass
|
|
62
|
+
written in Cython overrides to keep the hook on the C side of the call. Those
|
|
63
|
+
answer with a ``DCG_ERR_*`` code too, and a node whose class overrides both
|
|
64
|
+
runs the Cython one first.
|
|
65
|
+
|
|
66
|
+
A hook is an OVERRIDE of what the node would do anyway, and most types have
|
|
67
|
+
no work to do at all: a literal's value, a read's entry, an action's
|
|
68
|
+
self-reference and a root's truth are all settled when the node is BUILT, so
|
|
69
|
+
evaluating one is nothing. What an evaluation actually runs is an operator
|
|
70
|
+
node's rule - and a hook, where a subclass has one, is reached instead.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self, *, repr: str | None = None, uid: Any | None = None, **kwargs: Any) -> None:
|
|
74
|
+
"""Initialize a node of no particular type, which is refused.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
repr: Display text to copy.
|
|
78
|
+
uid: Stable identity, minted when it is not given.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
NodeTypeError: Always - the base has no node type to build.
|
|
82
|
+
"""
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def get_manager() -> LogicGroupManager:
|
|
87
|
+
"""The manager this layer builds through.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The process-wide logic group manager.
|
|
91
|
+
"""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def __repr__(self) -> str:
|
|
95
|
+
"""The wrapper's own text: its class and its display text."""
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def __rshift__(self, other: LogicNode) -> LogicNode:
|
|
99
|
+
"""Link a child under the inherited edge, and answer with the child.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
other: Node to link under this one.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The child, so a shift chain reads as the graph it builds.
|
|
106
|
+
"""
|
|
107
|
+
...
|
|
108
|
+
|
|
109
|
+
def __enter__(self) -> Self:
|
|
110
|
+
"""Enter the node as a scope a build continues inside.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
This node.
|
|
114
|
+
"""
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> bool:
|
|
118
|
+
"""Leave the node, closing whatever the build left reserved.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
False, so an exception raised inside the block keeps travelling.
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
def append(self, child: LogicNode, condition: NodeEdgeCondition = AUTO_CONDITION) -> None:
|
|
126
|
+
"""Link a child under an explicit edge, or under an inferred one.
|
|
127
|
+
|
|
128
|
+
The default is the auto edge, which hands the arm to the PARENT to work
|
|
129
|
+
out - the same edge ``>>`` links by - so ``node.append(child)`` and
|
|
130
|
+
``node >> child`` add a child the same way. A build that knows which arm
|
|
131
|
+
it is filling passes the edge, and one that is filling a branch's second
|
|
132
|
+
arm normally should not have to say so.
|
|
133
|
+
|
|
134
|
+
The edge is what a build settles, not what a graph runs: an auto edge is
|
|
135
|
+
resolved when the child joins, and what the parent ends up holding is the
|
|
136
|
+
arm itself - which is what ``children`` is keyed by and what a
|
|
137
|
+
reconstruction restores.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
child: Node to link.
|
|
141
|
+
condition: Edge to link it by; the auto edge when not given.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
RuntimeError: When the C layer refuses the link - an arm the parent
|
|
145
|
+
has no room for, or one the child cannot take.
|
|
146
|
+
TypeError: When ``condition`` is not a ``NodeEdgeCondition``.
|
|
147
|
+
"""
|
|
148
|
+
...
|
|
149
|
+
|
|
150
|
+
def overwrite(self, new_node: LogicNode, condition: NodeEdgeCondition) -> None:
|
|
151
|
+
"""Put a node into an edge another node already holds.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
new_node: Node to place.
|
|
155
|
+
condition: The edge to place it on.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
KeyError: When no child hangs by that edge.
|
|
159
|
+
"""
|
|
160
|
+
...
|
|
161
|
+
|
|
162
|
+
def replace(self, original_node: LogicNode, new_node: LogicNode) -> None:
|
|
163
|
+
"""Put a node where another one is, inheriting its edge and position.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
original_node: Node being displaced.
|
|
167
|
+
new_node: Node taking its place.
|
|
168
|
+
"""
|
|
169
|
+
...
|
|
170
|
+
|
|
171
|
+
def detach(self) -> None:
|
|
172
|
+
"""Unlink this node from its parent, keeping its own subtree."""
|
|
173
|
+
...
|
|
174
|
+
|
|
175
|
+
def label(self, name: str) -> None:
|
|
176
|
+
"""Add a label to this node.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
name: Label to add.
|
|
180
|
+
"""
|
|
181
|
+
...
|
|
182
|
+
|
|
183
|
+
def unlabel(self, name: str) -> None:
|
|
184
|
+
"""Remove a label from this node.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
name: Label to remove.
|
|
188
|
+
"""
|
|
189
|
+
...
|
|
190
|
+
|
|
191
|
+
def has_label(self, name: str) -> bool:
|
|
192
|
+
"""Whether this node carries a label.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
name: Label to look for.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
True when the label is on this node.
|
|
199
|
+
"""
|
|
200
|
+
...
|
|
201
|
+
|
|
202
|
+
def render(self, max_depth: int = 0, show_labels: bool = True, show_out: bool = False, style: str = 'unicode') -> str:
|
|
203
|
+
"""Render the subtree as text.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
max_depth: How deep to walk; 0 for the layer's own limit.
|
|
207
|
+
show_labels: Whether to print each node's labels.
|
|
208
|
+
show_out: Whether to print each node's value.
|
|
209
|
+
style: ``'unicode'`` for box-drawing, anything else for ASCII.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
The tree, one node per line.
|
|
213
|
+
|
|
214
|
+
Raises:
|
|
215
|
+
BufferError: When the tree does not fit the render buffer.
|
|
216
|
+
"""
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
def validate(self) -> tuple[int, int, int, int] | None:
|
|
220
|
+
"""Check the graph this node heads.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
None when the graph is well formed, else the report as
|
|
224
|
+
``(code, errors, nodes, depth)``.
|
|
225
|
+
"""
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
def pre_eval_fn(self) -> None:
|
|
229
|
+
"""Run before this node's value is produced.
|
|
230
|
+
|
|
231
|
+
Override to set a node up, or to refuse it: returning a negative
|
|
232
|
+
``DCG_ERR_*`` code stops the node where it stands, and so does raising.
|
|
233
|
+
The default does nothing.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
None, so the evaluation goes on to produce the value.
|
|
237
|
+
"""
|
|
238
|
+
...
|
|
239
|
+
|
|
240
|
+
def eval_fn(self) -> None:
|
|
241
|
+
"""Produce this node's value.
|
|
242
|
+
|
|
243
|
+
Override to compute the value rather than to take it from the node's
|
|
244
|
+
type. A hook written in Python watches the value rather than setting it -
|
|
245
|
+
the slot is the C layer's to write, which is what the ``c_eval_fn`` hook
|
|
246
|
+
next to this one is for. The default does nothing, which lets the
|
|
247
|
+
built-in rule for the node's type produce the value.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
None, so the evaluation goes on to the post stage.
|
|
251
|
+
"""
|
|
252
|
+
...
|
|
253
|
+
|
|
254
|
+
def post_eval_fn(self) -> None:
|
|
255
|
+
"""Run after this node's value has been produced.
|
|
256
|
+
|
|
257
|
+
Override to react to a value that is already in the slot. The default
|
|
258
|
+
does nothing.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
None, so the evaluation goes on to select the next edge.
|
|
262
|
+
"""
|
|
263
|
+
...
|
|
264
|
+
|
|
265
|
+
def eval(self) -> Any:
|
|
266
|
+
"""Evaluate this node - and only this node - for the value it holds.
|
|
267
|
+
|
|
268
|
+
The node's three stages run and the slot keeps what they produced, but
|
|
269
|
+
nothing else is written and no child is reached: the node's stage bits,
|
|
270
|
+
error code, run id, depth and visit count are left exactly as they were,
|
|
271
|
+
so a node can be asked between two walks without disturbing them.
|
|
272
|
+
|
|
273
|
+
Whether the value makes sense on its own is the node's own business. A
|
|
274
|
+
literal answers with itself, an action with the node itself (an action
|
|
275
|
+
*is* what it decides), a read with what its store entry holds now, and an
|
|
276
|
+
expression with its operands evaluated and applied - which is why a read
|
|
277
|
+
of an entry nothing has landed in reports ``UNBOUND`` rather than a
|
|
278
|
+
value, and an expression over one of those stops at the read.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
The value the node came to hold, as a Python object.
|
|
282
|
+
|
|
283
|
+
Raises:
|
|
284
|
+
EvalFailureError: When the node cannot produce a value - an
|
|
285
|
+
unevaluable type, an operand outside its operator's domain, a
|
|
286
|
+
read of an entry with no value in it, or a hook that refused the
|
|
287
|
+
node. It carries the node that refused, the code it ended with,
|
|
288
|
+
the stage that failed, what was running then and the run it
|
|
289
|
+
happened in, and a hook's own exception travels as its cause.
|
|
290
|
+
"""
|
|
291
|
+
...
|
|
292
|
+
|
|
293
|
+
def dry_run(self) -> Any:
|
|
294
|
+
"""Ask this node what it WOULD evaluate to, and leave it as it was.
|
|
295
|
+
|
|
296
|
+
The same three stages run as in ``eval``, and then the node is put back
|
|
297
|
+
the way it was found: the value the evaluation produced comes back to the
|
|
298
|
+
caller instead of staying in the slot, and the slot keeps what it held.
|
|
299
|
+
The node's own outcome is still written - how far it got and what it
|
|
300
|
+
ended with - since that is about this evaluation; nothing about a run is,
|
|
301
|
+
because this is not one.
|
|
302
|
+
|
|
303
|
+
This is the question a build asks about a node it does not want to
|
|
304
|
+
disturb: what a branch says before a subtree under it is walked, and what
|
|
305
|
+
a node a walk has already valued says now.
|
|
306
|
+
|
|
307
|
+
It is ONE node, like ``eval`` on every node but a root: asking a root this
|
|
308
|
+
way asks for the root's own value, not for the decision its graph reaches.
|
|
309
|
+
A walk is ``RootLogicNode.eval``.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
The value the node would come to hold, as a Python object.
|
|
313
|
+
|
|
314
|
+
Raises:
|
|
315
|
+
EvalFailureError: When the node cannot produce a value, with the
|
|
316
|
+
fields and the message ``eval`` reports.
|
|
317
|
+
"""
|
|
318
|
+
...
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def repr(self) -> str:
|
|
322
|
+
"""The node's display text."""
|
|
323
|
+
...
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def type(self) -> str:
|
|
327
|
+
"""The node's type name, as the C layer reports it."""
|
|
328
|
+
...
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def uuid(self) -> UUID:
|
|
332
|
+
"""The node's stable identity, minted when the node was built."""
|
|
333
|
+
...
|
|
334
|
+
|
|
335
|
+
@property
|
|
336
|
+
def autogen(self) -> bool:
|
|
337
|
+
"""Whether the builder generated this node rather than a caller."""
|
|
338
|
+
...
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def is_leaf(self) -> bool:
|
|
342
|
+
"""Whether the node has no children."""
|
|
343
|
+
...
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def labels(self) -> list[str]:
|
|
347
|
+
"""The labels on this node, in the order they were added."""
|
|
348
|
+
...
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def size(self) -> int:
|
|
352
|
+
"""How many nodes the subtree under this one holds, itself included."""
|
|
353
|
+
...
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def out(self) -> VarView:
|
|
357
|
+
"""A read-only view of the value this node holds.
|
|
358
|
+
|
|
359
|
+
Every node has one output slot, and what is in it depends on the node: a
|
|
360
|
+
literal holds its value there (which is what makes a constant a valid
|
|
361
|
+
place to take a view from), an evaluated node holds what it last produced,
|
|
362
|
+
and a node that has not run holds nothing. The view reads the slot live
|
|
363
|
+
and keeps it alive only through this node.
|
|
364
|
+
"""
|
|
365
|
+
...
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def eval_hooks(self) -> tuple[str, ...]:
|
|
369
|
+
"""The eval hooks this node carries, by the name of the method that runs.
|
|
370
|
+
|
|
371
|
+
What is installed is decided once, when the node is built: the hooks a
|
|
372
|
+
subclass overrode, and only those. The names tell the two levels apart -
|
|
373
|
+
``'pre_eval_fn'`` is the Python hook and ``'c_pre_eval_fn'`` its
|
|
374
|
+
Cython counterpart - and an empty tuple means the node has no hooks at all, so
|
|
375
|
+
the built-in rule for its type is what produces its value.
|
|
376
|
+
"""
|
|
377
|
+
...
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def address(self) -> int:
|
|
381
|
+
"""The C block's address, which is what the registry is keyed by."""
|
|
382
|
+
...
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def parent(self) -> LogicNode | None:
|
|
386
|
+
"""The node this one hangs from, or None for a parentless node."""
|
|
387
|
+
...
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def children(self) -> dict[NodeEdgeCondition, LogicNode]:
|
|
391
|
+
"""The children, keyed by the edge each one hangs by."""
|
|
392
|
+
...
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def condition_to_parent(self) -> NodeEdgeCondition:
|
|
396
|
+
"""The edge this node hangs by - unconditional when it has no parent."""
|
|
397
|
+
...
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
class PlaceholderNode(LogicNode):
|
|
401
|
+
"""A reserved arm: where a branch goes if a build gives it one.
|
|
402
|
+
|
|
403
|
+
A placeholder is the C layer's stand-in for a branch that has not been built
|
|
404
|
+
yet. A branch that is built replaces it, and one that never is becomes an
|
|
405
|
+
auto-generated no-action when the node around it is left.
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
def __init__(self, **kwargs: Any) -> None:
|
|
409
|
+
"""Allocate a stand-in.
|
|
410
|
+
|
|
411
|
+
Raises:
|
|
412
|
+
MemoryError: When the block cannot be allocated.
|
|
413
|
+
"""
|
|
414
|
+
...
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class LogicNodeRegistry(BoundByteMap):
|
|
418
|
+
"""The wrapper every node is looked up by, keyed by its block's address.
|
|
419
|
+
|
|
420
|
+
A lookup that finds the address answers with the wrapper the layer holds for
|
|
421
|
+
it; one that misses hands the node to the reconstruction, which is what makes
|
|
422
|
+
a node reached from C come back as the class its type names.
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
def __getitem__(self, key: int) -> LogicNode:
|
|
426
|
+
"""The wrapper at an address, rebuilding it when it is not held.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
key: Address of the C node block.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
The wrapper for that address.
|
|
433
|
+
"""
|
|
434
|
+
...
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# The registry every node is looked up by.
|
|
438
|
+
NODE_REGISTRY: LogicNodeRegistry
|