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,731 @@
|
|
|
1
|
+
#ifndef C_DCG_BAKE_HIERARCHY_H
|
|
2
|
+
#define C_DCG_BAKE_HIERARCHY_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stddef.h>
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
#include <stdio.h>
|
|
8
|
+
|
|
9
|
+
#include <cbase/allocator_protocol/c_allocator_protocol.h>
|
|
10
|
+
|
|
11
|
+
#include <decision_graph/decision_tree/bake/c_action.h>
|
|
12
|
+
#include <decision_graph/decision_tree/bake/c_const.h>
|
|
13
|
+
#include <decision_graph/decision_tree/bake/c_node.h>
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The expression family, DECLARED rather than included: its include sits at the
|
|
17
|
+
* bottom (see the note there), and these two are all this header's body names
|
|
18
|
+
* before it - the family's node as a cast, and the family's own free. Both are
|
|
19
|
+
* satisfied by a pointer, so the incomplete type is enough.
|
|
20
|
+
*/
|
|
21
|
+
typedef struct dcg_expression_node dcg_expression_node;
|
|
22
|
+
static inline void c_dcg_node_free_expr(dcg_expression_node* node);
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The types the graph's own structure is made of, and the one way a graph is
|
|
26
|
+
* torn down:
|
|
27
|
+
*
|
|
28
|
+
* - the root: the one node that may have no parent, and the entry a walk
|
|
29
|
+
* starts from. Its value is true, so a branch can be read off it directly,
|
|
30
|
+
* and it carries what a walk needs from an entry - whether it inherits the
|
|
31
|
+
* contexts it is entered from, how deep it may go, and the record of the
|
|
32
|
+
* path it took;
|
|
33
|
+
* - the breakpoint: an inspection sink. It carries the eval breakpoint flag
|
|
34
|
+
* and the two fields its group logic needs - the group it breaks from, and
|
|
35
|
+
* whether it is still waiting to be connected back.
|
|
36
|
+
*
|
|
37
|
+
* Both own everything they hold as nested blocks - a root's eval path included
|
|
38
|
+
* - so their _free is the base teardown and nothing else goes with them. What
|
|
39
|
+
* is NOT local is the graph: c_dcg_node_teardown_root() is the single place a
|
|
40
|
+
* graph is walked and freed, leaf first, and c_dcg_node_free_generic() is the
|
|
41
|
+
* step it takes per node - the free of whatever type that node turns out to be.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// ========== Constants ==========
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Display text a type is given when the caller does not retitle it: a build can
|
|
48
|
+
* name them its own way, and the capi names the same things the same way.
|
|
49
|
+
*/
|
|
50
|
+
#ifndef DCG_DEF_REPR_ROOT
|
|
51
|
+
#define DCG_DEF_REPR_ROOT "Entry Point"
|
|
52
|
+
#endif
|
|
53
|
+
|
|
54
|
+
#ifndef DCG_DEF_REPR_BREAKPOINT
|
|
55
|
+
#define DCG_DEF_REPR_BREAKPOINT "Breakpoint"
|
|
56
|
+
#endif
|
|
57
|
+
|
|
58
|
+
/** Nodes the teardown record starts with; it doubles as the walk grows. */
|
|
59
|
+
#ifndef DCG_TEARDOWN_INITIAL_CAPACITY
|
|
60
|
+
#define DCG_TEARDOWN_INITIAL_CAPACITY 16U
|
|
61
|
+
#endif
|
|
62
|
+
|
|
63
|
+
// ========== Structs ==========
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @brief The path a walk took through the graph: the nodes and their values.
|
|
67
|
+
*
|
|
68
|
+
* One entry per node visited, in order: the node and a snapshot of the value it
|
|
69
|
+
* held when it was left. It is the bake counterpart of the capi's eval path,
|
|
70
|
+
* which is what a caller reads back to see how a decision was reached.
|
|
71
|
+
*
|
|
72
|
+
* The record is DATA - two arrays and the outcome of the walk - and it owns
|
|
73
|
+
* neither the nodes it names nor the graph they belong to. Where its blocks come
|
|
74
|
+
* from is the caller's to decide, and the two ways are the two ways a record is
|
|
75
|
+
* used:
|
|
76
|
+
*
|
|
77
|
+
* - a record of its own, from c_dcg_node_eval_path_new(): one block holding
|
|
78
|
+
* the arrays, released with it;
|
|
79
|
+
* - the record embedded in a root (dcg_root_node.eval_path), whose blocks are
|
|
80
|
+
* nested under the root and released with it - which is why the root's own
|
|
81
|
+
* walk appends through a variant of its own.
|
|
82
|
+
*
|
|
83
|
+
* The outcome of the walk is part of the record, not something a caller has to
|
|
84
|
+
* infer from the entries: `code` is what the walk ended with (DCG_OK when it
|
|
85
|
+
* reached a leaf), `leaf` the node it landed on, `failed` the node that reported
|
|
86
|
+
* the failure when there was one, and `seq_id` the run the record belongs to.
|
|
87
|
+
* `node` and `eval_val` are the entries themselves, `capacity` the room the pair
|
|
88
|
+
* of blocks has and `n_nodes` how much of it is used - and a record that needs
|
|
89
|
+
* more room takes it, so no walk is ever recorded in part (see
|
|
90
|
+
* c_dcg_eval_path_append).
|
|
91
|
+
*
|
|
92
|
+
* A snapshot is a SHALLOW copy of the slot as it stood: a payload the node owns
|
|
93
|
+
* is not copied, so the record borrows it - valid for as long as the node is not
|
|
94
|
+
* evaluated again, which is the whole of the run that wrote it.
|
|
95
|
+
*/
|
|
96
|
+
typedef struct dcg_node_eval_path {
|
|
97
|
+
dcg_node** node; // eval node
|
|
98
|
+
dcg_var_t* eval_val; // eval node value snapshot
|
|
99
|
+
size_t capacity; // Entries the blocks have room for.
|
|
100
|
+
size_t n_nodes; // Entries in use.
|
|
101
|
+
dcg_ret_code code; // Outcome of the walk: DCG_OK when it reached a leaf.
|
|
102
|
+
dcg_node* leaf; // The node the walk landed on; NULL when it did not land.
|
|
103
|
+
dcg_node* failed; // The node that reported the failure, when one did.
|
|
104
|
+
uint64_t seq_id; // The run this record belongs to.
|
|
105
|
+
} dcg_node_eval_path;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @brief The graph entry point: the base node plus what a walk starts with.
|
|
109
|
+
*
|
|
110
|
+
* `eval_path` is that record - the nodes a walk visited and the value each one
|
|
111
|
+
* produced. `inherit_contexts` is the capi's field of the same name: whether
|
|
112
|
+
* the entry takes the contexts of the groups it is entered from in with it, its
|
|
113
|
+
* default (as there) being not to. It is what chooses between the two halves of
|
|
114
|
+
* the shelving a root's enter performs, and only the root reads it.
|
|
115
|
+
*
|
|
116
|
+
* The base node must stay the FIRST member: a dcg_root_node* is therefore a
|
|
117
|
+
* valid dcg_node*.
|
|
118
|
+
*/
|
|
119
|
+
typedef struct dcg_root_node {
|
|
120
|
+
dcg_node base; // The common node header. Must stay first.
|
|
121
|
+
dcg_node_eval_path eval_path;
|
|
122
|
+
bool inherit_contexts;
|
|
123
|
+
} dcg_root_node;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @note What entering a root does to the manager's stacks, and its undo, are
|
|
127
|
+
* declared AND defined in c_logic_group.h - the only header that can reach the
|
|
128
|
+
* shelving. They cannot be declared here: a `static inline` must be defined in
|
|
129
|
+
* every translation unit that declares it, and a unit that includes this header
|
|
130
|
+
* for the node types has no reason to include the manager's.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @brief An inspection sink: the base node plus its group's two fields.
|
|
135
|
+
*
|
|
136
|
+
* `await_connection` is the capi's field of the same name: the manager raises it
|
|
137
|
+
* when the group the breakpoint belongs to is left, and lowers it when the
|
|
138
|
+
* breakpoint is connected to the node that resumes outside it, so a breakpoint
|
|
139
|
+
* that is still waiting is not an inspection point yet.
|
|
140
|
+
*
|
|
141
|
+
* `break_from` names the group it breaks out of. It is NOT owned - a group
|
|
142
|
+
* outlives every breakpoint raised from it - and it is what the manager matches
|
|
143
|
+
* on when a group is left (see c_dcg_lgm_exit_group).
|
|
144
|
+
*
|
|
145
|
+
* The base node must stay the FIRST member: a dcg_breakpoint_node* is therefore
|
|
146
|
+
* a valid dcg_node*.
|
|
147
|
+
*/
|
|
148
|
+
typedef struct dcg_breakpoint_node {
|
|
149
|
+
dcg_node base; // The common node header. Must stay first.
|
|
150
|
+
dcg_logic_group* break_from; // The group this breaks out of. // NOT owned.
|
|
151
|
+
bool await_connection; // Waiting for the node it resumes into.
|
|
152
|
+
} dcg_breakpoint_node;
|
|
153
|
+
|
|
154
|
+
// ========== Forward Declarations ==========
|
|
155
|
+
|
|
156
|
+
// Lifecycle
|
|
157
|
+
static inline dcg_root_node* c_dcg_node_new_root(const char* repr, allocator_protocol* allocator);
|
|
158
|
+
static inline int c_dcg_node_ctx_enter_breakpoint(dcg_node* node, dcg_logic_group_manager* mgr);
|
|
159
|
+
static inline int c_dcg_node_ctx_exit_breakpoint(dcg_node* node, dcg_logic_group_manager* mgr);
|
|
160
|
+
static inline dcg_breakpoint_node* c_dcg_node_new_breakpoint(dcg_logic_group* break_from, const char* repr, allocator_protocol* allocator);
|
|
161
|
+
static inline dcg_breakpoint_node* c_dcg_node_get_breakpoint(const dcg_node* node);
|
|
162
|
+
static inline int c_dcg_node_resolve_breakpoint(dcg_node* node);
|
|
163
|
+
static inline void c_dcg_node_free_root(dcg_root_node* node);
|
|
164
|
+
static inline void c_dcg_node_free_breakpoint(dcg_breakpoint_node* node);
|
|
165
|
+
|
|
166
|
+
// Teardown - freeing a node, and freeing a whole graph
|
|
167
|
+
static inline void c_dcg_node_free_generic(dcg_node* node);
|
|
168
|
+
static inline int c_dcg_node_teardown_root(dcg_node* node);
|
|
169
|
+
static inline int c_dcg_node_remove(dcg_node* node);
|
|
170
|
+
static inline int c_dcg_node_clear_children(dcg_node* node);
|
|
171
|
+
static inline int c_dcg_node_clean(dcg_node* node);
|
|
172
|
+
|
|
173
|
+
// ========== Lifecycle Methods ==========
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @brief Allocate the graph entry point.
|
|
177
|
+
*
|
|
178
|
+
* The value is true - the entry is taken, which is what lets a branch be read
|
|
179
|
+
* off the root directly - and the walk's record starts empty: nothing has been
|
|
180
|
+
* walked yet, so there is no path to report and no block to own.
|
|
181
|
+
*
|
|
182
|
+
* @param repr Display text to copy; NULL takes the root's own default.
|
|
183
|
+
* @param allocator Allocator for the block; NULL falls back to the plain heap.
|
|
184
|
+
* @return The node, or NULL on OOM.
|
|
185
|
+
*/
|
|
186
|
+
static inline dcg_root_node* c_dcg_node_new_root(const char* repr, allocator_protocol* allocator) {
|
|
187
|
+
dcg_root_node* node = (dcg_root_node*) c_ap_alloc(sizeof(dcg_root_node), allocator);
|
|
188
|
+
if (!node) return NULL;
|
|
189
|
+
|
|
190
|
+
if (c_dcg_node_init(&node->base, DCG_NODE_ROOT, repr ? repr : DCG_DEF_REPR_ROOT) != DCG_OK) {
|
|
191
|
+
c_ap_free_owned(node);
|
|
192
|
+
return NULL;
|
|
193
|
+
}
|
|
194
|
+
(void) c_dcg_var_init_bool(&node->base.out, true);
|
|
195
|
+
|
|
196
|
+
/* The record starts with no room and no blocks: they are nested under this
|
|
197
|
+
* node the first time a walk records into it, and released with it - see
|
|
198
|
+
* c_dcg_root_node_eval_path_append in c_eval.h. */
|
|
199
|
+
node->eval_path.node = NULL;
|
|
200
|
+
node->eval_path.eval_val = NULL;
|
|
201
|
+
node->eval_path.capacity = 0;
|
|
202
|
+
node->eval_path.n_nodes = 0;
|
|
203
|
+
node->eval_path.code = DCG_OK;
|
|
204
|
+
node->eval_path.leaf = NULL;
|
|
205
|
+
node->eval_path.failed = NULL;
|
|
206
|
+
node->eval_path.seq_id = 0;
|
|
207
|
+
return node;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @brief Allocate a breakpoint sink.
|
|
212
|
+
*
|
|
213
|
+
* The breakpoint flag is set here, so an evaluator that walks the graph stops at
|
|
214
|
+
* this node without having to know which nodes are inspection points. The group
|
|
215
|
+
* fields start empty: nothing has been entered or left yet.
|
|
216
|
+
*
|
|
217
|
+
* @param break_from The group this breaks out of (not owned; may be NULL).
|
|
218
|
+
* @param repr Display text to copy; NULL takes the breakpoint's own default.
|
|
219
|
+
* @param allocator Allocator for the block; NULL falls back to the plain heap.
|
|
220
|
+
* @return The node, or NULL on OOM.
|
|
221
|
+
*/
|
|
222
|
+
static inline dcg_breakpoint_node* c_dcg_node_new_breakpoint(dcg_logic_group* break_from, const char* repr, allocator_protocol* allocator) {
|
|
223
|
+
dcg_breakpoint_node* node = (dcg_breakpoint_node*) c_ap_alloc(sizeof(dcg_breakpoint_node), allocator);
|
|
224
|
+
if (!node) return NULL;
|
|
225
|
+
|
|
226
|
+
if (c_dcg_node_init(&node->base, DCG_NODE_BREAKPOINT, repr ? repr : DCG_DEF_REPR_BREAKPOINT) != DCG_OK) {
|
|
227
|
+
c_ap_free_owned(node);
|
|
228
|
+
return NULL;
|
|
229
|
+
}
|
|
230
|
+
node->base.eval_ctx.flags |= DCG_EVAL_FLAG_BREAKPOINT;
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
* What entering and leaving a breakpoint mean, armed here with everything
|
|
234
|
+
* else the type is built with. The table in c_node.h cannot give them: their
|
|
235
|
+
* bodies need the breakpoint struct, and that is this header - one the
|
|
236
|
+
* table's header sits below. A node is what its constructor makes it, so
|
|
237
|
+
* this is where the answers belong, and nothing has to correct the node
|
|
238
|
+
* afterwards - neither the manager's fallback exit (which is the closing a
|
|
239
|
+
* binary node gets, and the wrong one for a breakpoint: see
|
|
240
|
+
* c_dcg_node_ctx_exit_breakpoint) nor the enter's own override.
|
|
241
|
+
*/
|
|
242
|
+
node->base.ctx_ops.enter_fn = c_dcg_node_ctx_enter_breakpoint;
|
|
243
|
+
node->base.ctx_ops.exit_fn = c_dcg_node_ctx_exit_breakpoint;
|
|
244
|
+
|
|
245
|
+
node->base.autogen = true;
|
|
246
|
+
node->await_connection = false;
|
|
247
|
+
node->break_from = break_from;
|
|
248
|
+
return node;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @brief Tear down a root and free its buf.
|
|
253
|
+
*
|
|
254
|
+
* The eval path is a pair of blocks nested under the root, so the base free
|
|
255
|
+
* releases the record with the node - there is nothing else to release, and
|
|
256
|
+
* the children are let go like every other node's (see c_dcg_node_dealloc).
|
|
257
|
+
*
|
|
258
|
+
* @param node Node to free (NULL-safe).
|
|
259
|
+
*/
|
|
260
|
+
static inline void c_dcg_node_free_root(dcg_root_node* node) {
|
|
261
|
+
if (!node) return;
|
|
262
|
+
c_dcg_node_free(&node->base);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* @brief Take a breakpoint out of a graph, leaving its continuation in its place.
|
|
267
|
+
*
|
|
268
|
+
* A breakpoint is SCAFFOLDING: it stands where a branch broke so a build can
|
|
269
|
+
* carry on somewhere else, and once the graph is built there is nothing left for
|
|
270
|
+
* it to do. What the branch actually does is what the break resumed into, so the
|
|
271
|
+
* bake splices that node into the breakpoint's place - and the arm the
|
|
272
|
+
* breakpoint held becomes the continuation's, because where the branch goes did
|
|
273
|
+
* not change when the scaffolding came down.
|
|
274
|
+
*
|
|
275
|
+
* This is where the "take over" belongs. Doing it while the graph is being BUILT
|
|
276
|
+
* would mean a build could not name the break it made afterwards, and a node
|
|
277
|
+
* that vanishes from under its own wrapper is a wrapper nobody can use; done
|
|
278
|
+
* here, the graph the walk sees has no breakpoints in it and the wrapper still
|
|
279
|
+
* owns a block that is simply no longer linked.
|
|
280
|
+
*
|
|
281
|
+
* The ELSE arm is the continuation, and it is the only arm a breakpoint has: one
|
|
282
|
+
* child, the else the enter opened or the manager connected. The node is
|
|
283
|
+
* detached rather than freed - the caller that built the breakpoint holds the
|
|
284
|
+
* block, and freeing it here would leave that handle dangling.
|
|
285
|
+
*
|
|
286
|
+
* @param node Breakpoint to resolve. Must be one, and must have resumed.
|
|
287
|
+
* @return DCG_OK, DCG_ERR_TYPE (not a breakpoint), DCG_ERR_UNRESOLVED (it broke
|
|
288
|
+
* and never carried on - nothing to splice in) or the link's own code.
|
|
289
|
+
*/
|
|
290
|
+
static inline int c_dcg_node_resolve_breakpoint(dcg_node* node) {
|
|
291
|
+
if (!node || node->ntype != DCG_NODE_BREAKPOINT) return DCG_ERR_TYPE;
|
|
292
|
+
|
|
293
|
+
dcg_node* continuation = c_dcg_node_child_by_condition(node, DCG_ELSE_CONDITION);
|
|
294
|
+
if (!continuation) return DCG_ERR_UNRESOLVED;
|
|
295
|
+
|
|
296
|
+
/* Read off the breakpoint BEFORE the splice: the replace re-parents the
|
|
297
|
+
* continuation and rewrites the edge it hangs by. */
|
|
298
|
+
const dcg_node_edge_condition* inherited = node->condition_to_parent;
|
|
299
|
+
|
|
300
|
+
/*
|
|
301
|
+
* The continuation is let go of the breakpoint first, because a replace
|
|
302
|
+
* refuses a node that still has a parent - its `new` is expected to be free
|
|
303
|
+
* to link - and the breakpoint is its parent until this moment.
|
|
304
|
+
*
|
|
305
|
+
* `replace_shared` is the other door for a parented node, and it is the wrong
|
|
306
|
+
* one here: it lands the node at the END of the new parent's list, which for
|
|
307
|
+
* a breakpoint holding a branch's true arm would put the continuation after
|
|
308
|
+
* the false arm and swap the two around. Detaching first keeps the slot: the
|
|
309
|
+
* branch goes on meaning what it meant.
|
|
310
|
+
*/
|
|
311
|
+
int ret = c_dcg_node_detach(continuation);
|
|
312
|
+
if (ret != DCG_OK) return ret;
|
|
313
|
+
|
|
314
|
+
ret = c_dcg_node_replace(node, continuation);
|
|
315
|
+
if (ret != DCG_OK) return ret;
|
|
316
|
+
|
|
317
|
+
c_dcg_node_adopt_condition(continuation, inherited);
|
|
318
|
+
|
|
319
|
+
/* The breakpoint is out of the graph, and it has to stop pointing INTO it.
|
|
320
|
+
* Detaching the continuation leaves the breakpoint's child list naming a
|
|
321
|
+
* node that now belongs to another parent - a stale pointer that anything
|
|
322
|
+
* walking the block afterwards follows, the block's own free included. */
|
|
323
|
+
node->children = NULL;
|
|
324
|
+
node->flags &= ~(uint32_t) DCG_NODE_FLAG_VISITED; /* out of the graph: the walks must not reach it */
|
|
325
|
+
return DCG_OK;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @brief Enter a breakpoint: open the one arm a build inside it fills.
|
|
330
|
+
*
|
|
331
|
+
* Entering is how a build goes INSIDE a breakpoint and carries the branch on
|
|
332
|
+
* from there - what it builds becomes the node the breakpoint resumed into. The
|
|
333
|
+
* breakpoint itself is not discarded: it stays on its arm until the bake takes
|
|
334
|
+
* it down (c_dcg_node_resolve_breakpoint), and a build that wants to name the
|
|
335
|
+
* break it made can go on doing so.
|
|
336
|
+
*
|
|
337
|
+
* The arm is ONE, and its condition is the ELSE. A breakpoint stands for "the
|
|
338
|
+
* branch carried on here", which is what an else says: not a value the parent's
|
|
339
|
+
* result is compared against, but the path taken where the branch that broke had
|
|
340
|
+
* nothing left to decide.
|
|
341
|
+
*
|
|
342
|
+
* There are TWO ways a breakpoint resumes, and this call settles which one is in
|
|
343
|
+
* play. The other is the manager's, and it is the manager's to give up: a queued
|
|
344
|
+
* breakpoint is connected to the next node entered anywhere, and a breakpoint
|
|
345
|
+
* cannot resume into itself - see c_dcg_lgm_connect_awaiting, which drops the
|
|
346
|
+
* breakpoint being entered as it passes. What is left here is the arm, which is
|
|
347
|
+
* the node's own business and needs nothing the node does not carry.
|
|
348
|
+
*
|
|
349
|
+
* Entering a breakpoint that already resumed into something is refused rather
|
|
350
|
+
* than given a second arm: a breakpoint resumes into exactly one node.
|
|
351
|
+
*
|
|
352
|
+
* @param node Breakpoint being entered.
|
|
353
|
+
* @param mgr Manager holding the build (unused: opening an arm is the node's
|
|
354
|
+
* own; the queue half is settled where the queue is).
|
|
355
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_TYPE (not a breakpoint),
|
|
356
|
+
* DCG_ERR_BUSY (it already resumes into something) or DCG_ERR_OOM.
|
|
357
|
+
*/
|
|
358
|
+
static inline int c_dcg_node_ctx_enter_breakpoint(dcg_node* node, dcg_logic_group_manager* mgr) {
|
|
359
|
+
(void) mgr;
|
|
360
|
+
|
|
361
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
362
|
+
if (node->ntype != DCG_NODE_BREAKPOINT) return DCG_ERR_TYPE;
|
|
363
|
+
if (node->children) return DCG_ERR_BUSY;
|
|
364
|
+
|
|
365
|
+
/* The build is inside it now, so it is not waiting for anything: the flag is
|
|
366
|
+
* a fact about the node, which is why it is cleared here rather than where
|
|
367
|
+
* the queue is. The QUEUE half is the manager's, and the manager drops it as
|
|
368
|
+
* it passes - see c_dcg_lgm_connect_awaiting. */
|
|
369
|
+
((dcg_breakpoint_node*) node)->await_connection = false;
|
|
370
|
+
|
|
371
|
+
dcg_node* arm = c_dcg_node_new_placeholder(c_ap_protocol_from_ptr(node));
|
|
372
|
+
if (!arm) return DCG_ERR_OOM;
|
|
373
|
+
|
|
374
|
+
int ret = c_dcg_node_append(node, arm, DCG_ELSE_CONDITION);
|
|
375
|
+
if (ret != DCG_OK) {
|
|
376
|
+
c_dcg_node_free(arm);
|
|
377
|
+
return ret;
|
|
378
|
+
}
|
|
379
|
+
return DCG_OK;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @brief Leave a breakpoint: retire its stand-ins, and fill NOTHING.
|
|
384
|
+
*
|
|
385
|
+
* The closing every other type gets is c_dcg_node_ctx_exit_closed, and the half
|
|
386
|
+
* of it a breakpoint must not have is the auto fill. That fill reads an ELSE arm
|
|
387
|
+
* as a fallback whose TRUE arm is missing and supplies one - the rule for a node
|
|
388
|
+
* that decides between two branches, which is exactly what a breakpoint is not.
|
|
389
|
+
* A breakpoint's ELSE is "the branch carried on here" (see the enter above), so
|
|
390
|
+
* there is no arm missing and nothing to supply: inventing one puts a child under
|
|
391
|
+
* scaffolding the bake then takes down, and a node under a detached block is a
|
|
392
|
+
* node nothing walks to and nothing frees.
|
|
393
|
+
*
|
|
394
|
+
* What it keeps is the other half: whatever is STILL a placeholder when the
|
|
395
|
+
* build leaves becomes a no-action, so a graph that has closed holds no stand-in
|
|
396
|
+
* - a breakpoint a build entered and did not carry on from included.
|
|
397
|
+
*
|
|
398
|
+
* @param node Node being left.
|
|
399
|
+
* @param mgr Manager holding the build (unused: closing is the node's own).
|
|
400
|
+
* @return DCG_OK, or DCG_ERR_INVALID_ARG.
|
|
401
|
+
*/
|
|
402
|
+
static inline int c_dcg_node_ctx_exit_breakpoint(dcg_node* node, dcg_logic_group_manager* mgr) {
|
|
403
|
+
(void) mgr;
|
|
404
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
405
|
+
|
|
406
|
+
c_dcg_node_consolidate_placeholder(node);
|
|
407
|
+
return DCG_OK;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @brief The first breakpoint below a node that has not resumed into anything.
|
|
412
|
+
*
|
|
413
|
+
* What a caller is asking for is the break a build left behind: the place the
|
|
414
|
+
* graph stopped, which another build can take up and carry on from. That is a
|
|
415
|
+
* breakpoint with no child - one that has resumed into something is where a
|
|
416
|
+
* branch CONTINUED, not where it stopped - and it is the same test the capi's
|
|
417
|
+
* ``get_breakpoint`` makes by looking for a breakpoint among the leaves.
|
|
418
|
+
*
|
|
419
|
+
* The walk does NOT descend into a breakpoint, and that is load-bearing rather
|
|
420
|
+
* than tidy: a resumed node stands in two parents' lists at once (see the join
|
|
421
|
+
* in c_dcg_node_append), so a breakpoint's child list is BORROWED and its
|
|
422
|
+
* sibling chain runs on into the other parent's children. Walking there would
|
|
423
|
+
* leave the tree and could come back round to a node already visited. A waiting
|
|
424
|
+
* breakpoint has no such list, so stopping at every breakpoint keeps the walk
|
|
425
|
+
* inside a real tree - each node reached once, from its own parent.
|
|
426
|
+
*
|
|
427
|
+
* @param node Node to search below (NULL-safe).
|
|
428
|
+
* @return The waiting breakpoint, or NULL when there is none.
|
|
429
|
+
*/
|
|
430
|
+
static inline dcg_breakpoint_node* c_dcg_node_get_breakpoint(const dcg_node* node) {
|
|
431
|
+
if (!node) return NULL;
|
|
432
|
+
|
|
433
|
+
for (const dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
434
|
+
if (child->ntype == DCG_NODE_BREAKPOINT) {
|
|
435
|
+
if (!child->children) return (dcg_breakpoint_node*) child;
|
|
436
|
+
continue; /* resumed: its child list is borrowed, not ours to walk */
|
|
437
|
+
}
|
|
438
|
+
dcg_breakpoint_node* found = c_dcg_node_get_breakpoint(child);
|
|
439
|
+
if (found) return found;
|
|
440
|
+
}
|
|
441
|
+
return NULL;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* @brief Tear down a breakpoint and free its buf.
|
|
446
|
+
*
|
|
447
|
+
* The group fields name blocks the breakpoint does not own, so the base free
|
|
448
|
+
* is the whole of it.
|
|
449
|
+
*
|
|
450
|
+
* @param node Node to free (NULL-safe).
|
|
451
|
+
*/
|
|
452
|
+
static inline void c_dcg_node_free_breakpoint(dcg_breakpoint_node* node) {
|
|
453
|
+
if (!node) return;
|
|
454
|
+
c_dcg_node_free(&node->base);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ========== Teardown ==========
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* @brief Free a node whose type is only known at run time.
|
|
461
|
+
*
|
|
462
|
+
* c_dcg_node_free() releases the base half of a node; a family that puts state
|
|
463
|
+
* after the base header releases that state in its own _free. This is the one
|
|
464
|
+
* place that knows both: the node's own `ntype` is the dispatch flag, and the
|
|
465
|
+
* free of the type it names is the one called - a literal and a variable
|
|
466
|
+
* through their two input frees, the operator family through
|
|
467
|
+
* c_dcg_node_free_expr(), the whole action family through
|
|
468
|
+
* c_dcg_node_free_action(), a root and a breakpoint through theirs.
|
|
469
|
+
*
|
|
470
|
+
* A family whose types have different layouts is split here, not inside its own
|
|
471
|
+
* free: a variable is not a literal, and a free that took one as the other
|
|
472
|
+
* would be reading a block that is not that shape.
|
|
473
|
+
*
|
|
474
|
+
* A type this dispatcher has no free for - a variant it has never heard of -
|
|
475
|
+
* is reported on stderr, and its base is released all the same: the silent
|
|
476
|
+
* alternative would hand back half a node and leak whatever the type put
|
|
477
|
+
* behind the base, while the caller that built such a node is the one that
|
|
478
|
+
* can fix it.
|
|
479
|
+
*
|
|
480
|
+
* Like every other free, this is LOCAL: the node's children are let go, not
|
|
481
|
+
* freed. Walk and free a whole graph with c_dcg_node_teardown_root().
|
|
482
|
+
*
|
|
483
|
+
* @param node Node to free (NULL-safe).
|
|
484
|
+
*/
|
|
485
|
+
static inline void c_dcg_node_free_generic(dcg_node* node) {
|
|
486
|
+
if (!node) return;
|
|
487
|
+
|
|
488
|
+
switch ((int) node->ntype & DCG_NODE_FAMILY_MASK) {
|
|
489
|
+
case DCG_NODE_INPUT:
|
|
490
|
+
if (node->ntype == DCG_NODE_VARIABLE) {
|
|
491
|
+
c_dcg_node_free_var((dcg_variable_node*) node);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
c_dcg_node_free_const((dcg_constant_node*) node);
|
|
495
|
+
return;
|
|
496
|
+
case DCG_NODE_OP:
|
|
497
|
+
c_dcg_node_free_expr((dcg_expression_node*) node);
|
|
498
|
+
return;
|
|
499
|
+
case DCG_NODE_ACTION:
|
|
500
|
+
c_dcg_node_free_action((dcg_action_node*) node);
|
|
501
|
+
return;
|
|
502
|
+
case DCG_NODE_SPECIAL:
|
|
503
|
+
if (node->ntype == DCG_NODE_ROOT) {
|
|
504
|
+
c_dcg_node_free_root((dcg_root_node*) node);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (node->ntype == DCG_NODE_BREAKPOINT) {
|
|
508
|
+
c_dcg_node_free_breakpoint((dcg_breakpoint_node*) node);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
break;
|
|
512
|
+
default:
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* Every type with a free of its own returned above, so getting here means
|
|
517
|
+
* the type is one this dispatcher cannot take apart. Say so: a node like
|
|
518
|
+
* that is a bug in whoever built it, and the message is the only trace the
|
|
519
|
+
* caller gets. */
|
|
520
|
+
(void) fprintf(stderr, "c_dcg_node_free_generic: no free for node type %s (0x%04x) at %p - releasing the base only\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node);
|
|
521
|
+
c_dcg_node_free(node);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* @brief Tear down a whole graph from its top: every node, leaf first.
|
|
526
|
+
*
|
|
527
|
+
* The one place a graph is freed. It starts from a node that has no parent -
|
|
528
|
+
* the graph's ROOT, or any parentless fragment top - because a node that still
|
|
529
|
+
* has a parent is part of somebody else's graph (a sub-root kept for regional
|
|
530
|
+
* management): tearing it down from there would free blocks the graph above
|
|
531
|
+
* still points at. A node with a parent is refused with DCG_ERR_BUSY and
|
|
532
|
+
* nothing is touched.
|
|
533
|
+
*
|
|
534
|
+
* The walk is breadth-first over a record of the nodes it reached, so the graph
|
|
535
|
+
* is not assumed to be a tree: a node reached twice (an interlink - a child of
|
|
536
|
+
* two parents, only one of which its metadata logs) is recorded once, and a
|
|
537
|
+
* back edge cannot make the walk spin. Every edge of the record is then
|
|
538
|
+
* dropped, and the nodes are freed in the reverse of the record, which puts
|
|
539
|
+
* every child before its parent - so nothing a free walks into is already
|
|
540
|
+
* gone, and the allocator never sees a block that still owns children.
|
|
541
|
+
*
|
|
542
|
+
* Collecting before freeing is also what makes the failure path safe: when the
|
|
543
|
+
* record cannot be added to, nothing has been freed yet.
|
|
544
|
+
*
|
|
545
|
+
* What the record cannot do is make sharing safe: a node this graph reaches is
|
|
546
|
+
* freed, and a graph that was NOT torn down yet still holds it - which is the
|
|
547
|
+
* caller's interlink to keep track of, exactly as it is in the capi.
|
|
548
|
+
*
|
|
549
|
+
* @param node Top of the graph to tear down: a node without a parent.
|
|
550
|
+
* @return The number of nodes freed (1 or more), or a DCG_ERR_* code:
|
|
551
|
+
* DCG_ERR_INVALID_ARG (NULL), DCG_ERR_BUSY (the node has a parent),
|
|
552
|
+
* DCG_ERR_OOM (the record could not be allocated).
|
|
553
|
+
*/
|
|
554
|
+
static inline int c_dcg_node_teardown_root(dcg_node* node) {
|
|
555
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
556
|
+
if (node->parent) return DCG_ERR_BUSY; /* somebody else's subtree - not a top */
|
|
557
|
+
|
|
558
|
+
allocator_protocol* allocator = c_ap_protocol_from_ptr(node);
|
|
559
|
+
size_t capacity = DCG_TEARDOWN_INITIAL_CAPACITY;
|
|
560
|
+
size_t count = 0;
|
|
561
|
+
dcg_node** order = (dcg_node**) c_ap_alloc_child(capacity * sizeof(dcg_node*), allocator, node);
|
|
562
|
+
if (!order) return DCG_ERR_OOM;
|
|
563
|
+
|
|
564
|
+
/* Collect: the root, then the children of every node collected, each node
|
|
565
|
+
* once. Nothing is freed until the whole graph is on the record. */
|
|
566
|
+
order[count++] = node;
|
|
567
|
+
node->flags |= DCG_NODE_FLAG_VISITED;
|
|
568
|
+
|
|
569
|
+
for (size_t head = 0; head < count; head++) {
|
|
570
|
+
for (dcg_node* child = order[head]->children; child; child = child->next_sibling) {
|
|
571
|
+
if (child->flags & DCG_NODE_FLAG_VISITED) continue; /* interlink or back edge */
|
|
572
|
+
child->flags |= DCG_NODE_FLAG_VISITED;
|
|
573
|
+
|
|
574
|
+
if (count == capacity) {
|
|
575
|
+
size_t grown_capacity = capacity * 2;
|
|
576
|
+
dcg_node** grown = (dcg_node**) c_ap_realloc(order, grown_capacity * sizeof(dcg_node*), allocator);
|
|
577
|
+
if (!grown) {
|
|
578
|
+
/* Nothing has been freed: put the marks back and leave the graph be. */
|
|
579
|
+
for (size_t i = 0; i < count; i++) order[i]->flags &= ~(uint32_t) DCG_NODE_FLAG_VISITED;
|
|
580
|
+
c_ap_free_owned(order);
|
|
581
|
+
return DCG_ERR_OOM;
|
|
582
|
+
}
|
|
583
|
+
order = grown;
|
|
584
|
+
capacity = grown_capacity;
|
|
585
|
+
}
|
|
586
|
+
order[count++] = child;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* Unwire the record before anything is freed. Every node on it is about to
|
|
591
|
+
* go, and dropping the edges first means no free can walk into a node that
|
|
592
|
+
* is already gone - which a back edge, or a child two parents reach, would
|
|
593
|
+
* otherwise make possible.
|
|
594
|
+
*
|
|
595
|
+
* Two passes, in this order: an unlink hands the removed node's NEXT
|
|
596
|
+
* sibling to the parent's child list, so a list can gain an entry while the
|
|
597
|
+
* unwiring runs - and it must be dropped after, not before, that happens.
|
|
598
|
+
* Once both passes are done, every node of the record is a lone block. */
|
|
599
|
+
for (size_t i = 0; i < count; i++) c_dcg_node_unlink(order[i]);
|
|
600
|
+
for (size_t i = 0; i < count; i++) order[i]->children = NULL;
|
|
601
|
+
|
|
602
|
+
/* Free leaf first: the reverse of the record puts every child before its
|
|
603
|
+
* parent, so the allocator never sees a block that still owns children.
|
|
604
|
+
* The record is a block nested under the top, so the last free of all
|
|
605
|
+
* releases it. */
|
|
606
|
+
for (size_t i = count; i-- > 0;) c_dcg_node_free_generic(order[i]);
|
|
607
|
+
|
|
608
|
+
return (int) count;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* @brief Unlink a node and tear its whole subtree down.
|
|
613
|
+
*
|
|
614
|
+
* The node leaves the graph first - its edge is dropped and the former parent
|
|
615
|
+
* is told - and the detached subtree is then torn down from its top, which is
|
|
616
|
+
* what lets a branch be removed without a parent check anywhere.
|
|
617
|
+
*
|
|
618
|
+
* @param node Node to remove (NULL-safe: a detached node is a no-op tree).
|
|
619
|
+
* @return The number of nodes freed, or a DCG_ERR_* code: DCG_ERR_BUSY when the
|
|
620
|
+
* parent is FROZEN, DCG_ERR_OOM when the teardown could not collect -
|
|
621
|
+
* in which case the node is left detached, with its subtree intact.
|
|
622
|
+
*/
|
|
623
|
+
static inline int c_dcg_node_remove(dcg_node* node) {
|
|
624
|
+
if (!node) return DCG_OK;
|
|
625
|
+
if (node->parent && (node->parent->flags & DCG_NODE_FLAG_FROZEN)) return DCG_ERR_BUSY;
|
|
626
|
+
|
|
627
|
+
c_dcg_node_unlink(node); /* silent: the subtree is about to go anyway */
|
|
628
|
+
return c_dcg_node_teardown_root(node);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* @brief Drop every child of a node, tearing their subtrees down.
|
|
633
|
+
*
|
|
634
|
+
* Each child is unlinked first - so the list cannot be mutated under the walk -
|
|
635
|
+
* and its subtree is then torn down. A teardown that could not collect its
|
|
636
|
+
* record (OOM) leaves that child detached but alive, and the children after it
|
|
637
|
+
* still attached; the CLEARED event is only announced when every child went.
|
|
638
|
+
*
|
|
639
|
+
* @param node Node to clear (NULL-safe). FROZEN nodes are left untouched.
|
|
640
|
+
* @return The number of nodes freed with the dropped subtrees, or the first
|
|
641
|
+
* DCG_ERR_* a teardown failed with.
|
|
642
|
+
*/
|
|
643
|
+
static inline int c_dcg_node_clear_children(dcg_node* node) {
|
|
644
|
+
if (!node || !node->children) return 0;
|
|
645
|
+
if (node->flags & DCG_NODE_FLAG_FROZEN) return 0;
|
|
646
|
+
|
|
647
|
+
int freed = 0;
|
|
648
|
+
int error = DCG_OK;
|
|
649
|
+
|
|
650
|
+
while (node->children) {
|
|
651
|
+
dcg_node* child = node->children;
|
|
652
|
+
c_dcg_node_unlink(child); /* out of the list before its subtree goes */
|
|
653
|
+
|
|
654
|
+
int child_result = c_dcg_node_teardown_root(child);
|
|
655
|
+
if (child_result < 0) {
|
|
656
|
+
if (error == DCG_OK) error = child_result;
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
freed += child_result;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (error != DCG_OK) return error; /* a child that could not be collected */
|
|
664
|
+
|
|
665
|
+
c_dcg_node_invoke_callbacks(node, DCG_NODE_EVENT_CHILD_CLEARED, node, (uint64_t) -1);
|
|
666
|
+
return freed;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* @brief Back to a fresh state, keeping the node's identity and bindings.
|
|
671
|
+
*
|
|
672
|
+
* Drops the subtree and the labels, resets the value slot and the eval
|
|
673
|
+
* scratch, but KEEPS: the type, the operator, the repr and its ownership
|
|
674
|
+
* flag, the uid, the eval hooks, the mutation callbacks and the
|
|
675
|
+
* user_payload. This is what a builder calls to reuse a node block.
|
|
676
|
+
*
|
|
677
|
+
* An action's slot is the one value the reset puts BACK, because it is not a
|
|
678
|
+
* result the node produced: it is what the node IS.
|
|
679
|
+
*
|
|
680
|
+
* The CLEARED event is announced once: by the child drop when there were
|
|
681
|
+
* children to drop, and by the reset itself when there were none.
|
|
682
|
+
*
|
|
683
|
+
* @param node Node to clean (NULL-safe).
|
|
684
|
+
* @return The number of nodes freed with the dropped subtrees, or the first
|
|
685
|
+
* DCG_ERR_* a teardown failed with - the reset itself always happens.
|
|
686
|
+
*/
|
|
687
|
+
static inline int c_dcg_node_clean(dcg_node* node) {
|
|
688
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
689
|
+
|
|
690
|
+
bool had_children = node->children != NULL;
|
|
691
|
+
int ret = c_dcg_node_clear_children(node);
|
|
692
|
+
|
|
693
|
+
dcg_node_label* label = node->labels;
|
|
694
|
+
node->labels = NULL;
|
|
695
|
+
while (label) {
|
|
696
|
+
dcg_node_label* next = label->next;
|
|
697
|
+
c_ap_free_owned(label);
|
|
698
|
+
label = next;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (node->out.dtype == VAR_TYPE_STRING && node->out.value.as_string) {
|
|
702
|
+
c_ap_free_owned((void*) node->out.value.as_string);
|
|
703
|
+
}
|
|
704
|
+
(void) c_dcg_var_init(&node->out);
|
|
705
|
+
|
|
706
|
+
/* An action's slot is the node itself, written when it was built rather than
|
|
707
|
+
* produced by any evaluation - so a clean that empties the slot has to put it
|
|
708
|
+
* back, or the node stops being its own value for good. */
|
|
709
|
+
if (c_dcg_node_type_is_action(node->ntype)) (void) c_dcg_var_init_node(&node->out, node);
|
|
710
|
+
|
|
711
|
+
node->eval_ctx.run = NULL;
|
|
712
|
+
node->eval_ctx.flags = DCG_EVAL_FLAG_NONE;
|
|
713
|
+
node->eval_ctx.depth = 0;
|
|
714
|
+
node->eval_ctx.visits = 0;
|
|
715
|
+
|
|
716
|
+
if (ret >= 0 && !had_children) c_dcg_node_invoke_callbacks(node, DCG_NODE_EVENT_CHILD_CLEARED, node, (uint64_t) -1);
|
|
717
|
+
return ret;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/*
|
|
721
|
+
* The expression family, and through it the protocol - LAST, because the two are
|
|
722
|
+
* a cycle with this header: this one frees the family's nodes by type, and the
|
|
723
|
+
* protocol's walk needs the root and the path record this header defines, both
|
|
724
|
+
* as COMPLETE types. Including either one up top would parse it while this
|
|
725
|
+
* header's own structs are still unread, which is the one order that cannot
|
|
726
|
+
* work. Down here they are all declared, and the header is finished but for this
|
|
727
|
+
* line, so the cycle closes with every type already known.
|
|
728
|
+
*/
|
|
729
|
+
#include <decision_graph/decision_tree/bake/c_expr.h>
|
|
730
|
+
|
|
731
|
+
#endif // C_DCG_BAKE_HIERARCHY_H
|