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,2660 @@
|
|
|
1
|
+
#ifndef C_DCG_BAKE_NODE_H
|
|
2
|
+
#define C_DCG_BAKE_NODE_H
|
|
3
|
+
|
|
4
|
+
#include <stdarg.h>
|
|
5
|
+
#include <stdbool.h>
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
#include <stdio.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <sys/types.h>
|
|
10
|
+
|
|
11
|
+
#include <cbase/allocator_protocol/c_allocator_protocol.h>
|
|
12
|
+
#include <cbase/bytemap/c_bytemap.h>
|
|
13
|
+
#include <cbase/bytemap/xxh3.h>
|
|
14
|
+
|
|
15
|
+
#include <decision_graph/decision_tree/bake/c_edge.h>
|
|
16
|
+
#include <decision_graph/decision_tree/bake/c_var.h>
|
|
17
|
+
|
|
18
|
+
#ifdef _WIN32
|
|
19
|
+
#include <process.h>
|
|
20
|
+
#else
|
|
21
|
+
#include <unistd.h>
|
|
22
|
+
#endif
|
|
23
|
+
|
|
24
|
+
// ========== Constants ==========
|
|
25
|
+
|
|
26
|
+
/** Capacity of the local buffer used to render a node prefix / uid. */
|
|
27
|
+
#ifndef DCG_NODE_STRING_MAXLEN
|
|
28
|
+
#define DCG_NODE_STRING_MAXLEN 256
|
|
29
|
+
#endif
|
|
30
|
+
|
|
31
|
+
/** Depth at which the renderer stops descending (0 = unlimited). */
|
|
32
|
+
#ifndef DCG_NODE_RENDER_MAX_DEPTH
|
|
33
|
+
#define DCG_NODE_RENDER_MAX_DEPTH 0
|
|
34
|
+
#endif
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Display text of the auto-generated no-action. It lives here rather than with
|
|
38
|
+
* the rest of the action family's default reprs because two headers need it and
|
|
39
|
+
* only one of them can see c_action.h: the fill that adds one (c_action.h), and
|
|
40
|
+
* the consolidation that turns an unfilled placeholder into one (this header).
|
|
41
|
+
*/
|
|
42
|
+
#ifndef DCG_DEF_REPR_NOACTION
|
|
43
|
+
#define DCG_DEF_REPR_NOACTION "NoAction"
|
|
44
|
+
#endif
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* Display text of the auto-generated placeholder. It lives here rather than with
|
|
48
|
+
* the action family's default reprs because the placeholder is not an action: it
|
|
49
|
+
* is a plain node the builder leaves behind, and both its constructor and the
|
|
50
|
+
* consolidation that retires it are in this header.
|
|
51
|
+
*/
|
|
52
|
+
#ifndef DCG_DEF_REPR_PLACEHOLDER
|
|
53
|
+
#define DCG_DEF_REPR_PLACEHOLDER "Placeholder"
|
|
54
|
+
#endif
|
|
55
|
+
|
|
56
|
+
// ========== Structs ==========
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @brief A node's identity: 16 raw bytes, laid out as an RFC 4122 UUID.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately a byte array rather than a 128-bit integer. The value is never
|
|
62
|
+
* arithmetic - it is compared bytewise, copied whole, and sliced into the 16
|
|
63
|
+
* bytes a Python `uuid.UUID` is built from - and as a char array it asks the
|
|
64
|
+
* node block for no alignment beyond what its widest integer member already
|
|
65
|
+
* needs.
|
|
66
|
+
*/
|
|
67
|
+
typedef unsigned char uuid_t[16];
|
|
68
|
+
|
|
69
|
+
typedef struct dcg_node dcg_node;
|
|
70
|
+
|
|
71
|
+
typedef struct dcg_logic_group dcg_logic_group;
|
|
72
|
+
|
|
73
|
+
typedef struct dcg_logic_group_manager dcg_logic_group_manager;
|
|
74
|
+
|
|
75
|
+
typedef struct dcg_node_ctx_ops {
|
|
76
|
+
int (*enter_fn)(dcg_node* node, dcg_logic_group_manager* mgr);
|
|
77
|
+
int (*exit_fn)(dcg_node* node, dcg_logic_group_manager* mgr);
|
|
78
|
+
} dcg_node_ctx_ops;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @brief Type of a node, encoded as family | variant.
|
|
82
|
+
*
|
|
83
|
+
* The high nibble is the family and is what the family predicates test
|
|
84
|
+
* (masked with DCG_NODE_FAMILY_MASK and compared against the family head);
|
|
85
|
+
* the low byte identifies the variant. The "Mask and Generics" members are
|
|
86
|
+
* the family heads - a generic member is legal wherever the family is
|
|
87
|
+
* expected.
|
|
88
|
+
*/
|
|
89
|
+
typedef enum dcg_node_type {
|
|
90
|
+
// === Input Node ===
|
|
91
|
+
DCG_NODE_INPUT = 0x0000, // <- Mask and Generics
|
|
92
|
+
DCG_NODE_TRUE = 0x0001, // Boolean true constant.
|
|
93
|
+
DCG_NODE_FALSE = 0x0002, // Boolean false constant.
|
|
94
|
+
DCG_NODE_DOUBLE = 0x0003, // Double constant.
|
|
95
|
+
DCG_NODE_STRING = 0x0004, // String constant (payload borrowed).
|
|
96
|
+
DCG_NODE_INT = 0x0005, // Integer constant.
|
|
97
|
+
DCG_NODE_VARIABLE = 0x0006, // A live read from a logic group's store.
|
|
98
|
+
// === Operator Node ===
|
|
99
|
+
DCG_NODE_OP = 0x0100, // <- Mask and Generics
|
|
100
|
+
DCG_NODE_UNARY = 0x0101, // One operand child.
|
|
101
|
+
DCG_NODE_BINARY = 0x0102, // Two operand children.
|
|
102
|
+
DCG_NODE_TERNARY = 0x0103, // Three operand children (cond, then, else).
|
|
103
|
+
DCG_NODE_CALL = 0x0104, // Variadic operator (call-like).
|
|
104
|
+
// 0x0200 - RESERVED. The collection family stood here; a mapping, a
|
|
105
|
+
// sequence and a generator are logic groups, not nodes, and live in
|
|
106
|
+
// c_logic_group.h and c_collections.h.
|
|
107
|
+
// === Action Node ===
|
|
108
|
+
DCG_NODE_ACTION = 0x0400, // <- Mask and Generics
|
|
109
|
+
DCG_NODE_NOACTION = 0x0401, // Leaf action: do nothing.
|
|
110
|
+
DCG_NODE_LONGACTION = 0x0402, // Leaf action: long position signal.
|
|
111
|
+
DCG_NODE_SHORTACTION = 0x0403, // Leaf action: short position signal.
|
|
112
|
+
DCG_NODE_CANCELACTION = 0x0404, // Leaf action: cancel outstanding signal.
|
|
113
|
+
DCG_NODE_CLEARACTION = 0x0405, // Leaf action: flatten.
|
|
114
|
+
DCG_NODE_PLACEHOLDER = 0x0406, // Auto-generated stand-in, replaced on consolidation.
|
|
115
|
+
// === Special Node ===
|
|
116
|
+
DCG_NODE_SPECIAL = 0x0800, // <- Mask and Generics
|
|
117
|
+
DCG_NODE_ROOT = 0x0801, // Graph entry point - the only parentless node.
|
|
118
|
+
DCG_NODE_BREAKPOINT = 0x0802 // Inspection sink - evaluation stops here.
|
|
119
|
+
} dcg_node_type;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @brief Bit masks over a node type: family nibble and variant number.
|
|
123
|
+
*/
|
|
124
|
+
typedef enum dcg_node_mask {
|
|
125
|
+
DCG_NODE_FAMILY_MASK = 0x0F00, // Extracts the family nibble of a dcg_node_type.
|
|
126
|
+
DCG_NODE_VARIANT_MASK = 0x00FF // Extracts the variant number of a dcg_node_type.
|
|
127
|
+
} dcg_node_mask;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @brief Per-node behaviour flags.
|
|
131
|
+
*/
|
|
132
|
+
typedef enum dcg_node_flag {
|
|
133
|
+
DCG_NODE_FLAG_NONE = 0, // No flags.
|
|
134
|
+
DCG_NODE_FLAG_FROZEN = 1 << 0, // Baked: structural mutation is refused.
|
|
135
|
+
DCG_NODE_FLAG_VISITED = 1 << 1, // Traversal scratch; cleared by the walker.
|
|
136
|
+
DCG_NODE_FLAG_PRUNED = 1 << 2 // Removed by a bake-time optimisation, kept for inspection.
|
|
137
|
+
} dcg_node_flag;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @brief A node label - one entry of the node's label list.
|
|
141
|
+
*/
|
|
142
|
+
typedef struct dcg_node_label {
|
|
143
|
+
const char* label; // Label text.
|
|
144
|
+
bool owned; // true when the node owns the text and frees it.
|
|
145
|
+
struct dcg_node_label* next; // Next label in the list.
|
|
146
|
+
} dcg_node_label;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @brief Compile-time switch: teach each node its own evaluation as it is built.
|
|
150
|
+
*
|
|
151
|
+
* A node's built-in value comes from its type. Each family INSTALLS its own rule
|
|
152
|
+
* as the node's eval hook as the node is built, so the evaluator calls a function
|
|
153
|
+
* pointer: the dispatch that would find the rule from the type happens once, at
|
|
154
|
+
* bake time, and a hot walk pays only the call. Define this to 0 and nothing is
|
|
155
|
+
* installed, so the evaluator finds the rule by dispatching on the type every
|
|
156
|
+
* time instead - one switch per evaluated node (see c_dcg_node_eval_default in
|
|
157
|
+
* c_eval.h).
|
|
158
|
+
*
|
|
159
|
+
* It costs one pointer per node (the ctx's type_eval_fn, which a caller's hook
|
|
160
|
+
* still overrides) and a pointer test per evaluation; what it saves is the
|
|
161
|
+
* dispatch. Which way that nets out is what tests/bake/bench_c_eval.c measures -
|
|
162
|
+
* build it both ways and compare.
|
|
163
|
+
*
|
|
164
|
+
* One rule is installed either way: a read a STORE makes carries the store's own
|
|
165
|
+
* rule from birth, because that rule is the read's (see c_collections.h) and the
|
|
166
|
+
* dispatch is not asked to find it.
|
|
167
|
+
*
|
|
168
|
+
* This is a BAKE-TIME switch and not a run-time one on purpose: a node that
|
|
169
|
+
* installs a hook has it installed for good, so the layer it belongs to has to
|
|
170
|
+
* be built the same way everywhere (a graph built by a TU with the switch off
|
|
171
|
+
* and evaluated by one with it on still works - it just dispatches).
|
|
172
|
+
*/
|
|
173
|
+
#ifndef DCG_EVAL_DIRECT_HOOKS
|
|
174
|
+
#define DCG_EVAL_DIRECT_HOOKS 1
|
|
175
|
+
#endif
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @brief Flags describing the state of a running evaluation.
|
|
179
|
+
*/
|
|
180
|
+
typedef enum dcg_eval_flag {
|
|
181
|
+
DCG_EVAL_FLAG_NONE = 0, // No flags.
|
|
182
|
+
DCG_EVAL_FLAG_SKIP_CHILDREN = 1 << 0, // Do not descend below this node.
|
|
183
|
+
DCG_EVAL_FLAG_CACHED = 1 << 1, // `out` is already valid; the EVAL hook may be skipped.
|
|
184
|
+
DCG_EVAL_FLAG_BREAKPOINT = 1 << 2, // Evaluation is suspended at this node.
|
|
185
|
+
DCG_EVAL_FLAG_TRACE = 1 << 3 // The node wants its hook calls traced.
|
|
186
|
+
} dcg_eval_flag;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @brief How far a node's own evaluation got, as the bits it completed.
|
|
190
|
+
*
|
|
191
|
+
* The stages are a mask rather than a position because a node that failed in
|
|
192
|
+
* the middle has genuinely completed the ones before it, and a caller asking
|
|
193
|
+
* "did the value get produced?" wants an answer that does not depend on where
|
|
194
|
+
* the failure happened to land. The producer bits ride in the same word for the
|
|
195
|
+
* same reason: which stage ran and what ran it are one answer about one
|
|
196
|
+
* evaluation, and splitting them across two fields would let them disagree.
|
|
197
|
+
*
|
|
198
|
+
* It lives in the NODE layer rather than with the protocol that fills it, because
|
|
199
|
+
* the node's own context records it (see dcg_node_eval_ctx.stage) and a context
|
|
200
|
+
* cannot name a type from a header above it.
|
|
201
|
+
*/
|
|
202
|
+
typedef enum dcg_eval_stage {
|
|
203
|
+
DCG_EVAL_STAGE_NONE = 0, // Nothing ran.
|
|
204
|
+
DCG_EVAL_STAGE_PRE_EVAL = 1 << 0, // The pre hook ran and returned OK.
|
|
205
|
+
DCG_EVAL_STAGE_EVAL = 1 << 1, // The value is in the node's out slot.
|
|
206
|
+
DCG_EVAL_STAGE_POST_EVAL = 1 << 2, // The post hook ran and returned OK.
|
|
207
|
+
DCG_EVAL_STAGE_DONE = 1 << 3, // The node's own evaluation completed.
|
|
208
|
+
|
|
209
|
+
/* WHO produced the value, in the same word rather than in a field of its own:
|
|
210
|
+
* exactly one of these accompanies DCG_EVAL_STAGE_EVAL, so the mask says both
|
|
211
|
+
* how far the node got and what got it there - and a failure names the
|
|
212
|
+
* producer as readily as the stage it stopped at. */
|
|
213
|
+
DCG_EVAL_STAGE_HOOK = 1 << 4, // ... from the caller's eval hook.
|
|
214
|
+
DCG_EVAL_STAGE_TYPE_RULE = 1 << 5, // ... from the rule the node's type gave it.
|
|
215
|
+
DCG_EVAL_STAGE_BUILTIN = 1 << 6 // ... from the built-in evaluation, found from the type.
|
|
216
|
+
} dcg_eval_stage;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @brief Which of the three eval hooks of a node.
|
|
220
|
+
*/
|
|
221
|
+
typedef enum dcg_node_hook_type {
|
|
222
|
+
DCG_HOOK_PRE_EVAL = 0, // Runs before the node is evaluated.
|
|
223
|
+
DCG_HOOK_EVAL = 1, // Produces the node's value, into node->out.
|
|
224
|
+
DCG_HOOK_POST_EVAL = 2 // Runs after the value is produced.
|
|
225
|
+
} dcg_node_hook_type;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* @brief An eval hook: a strategy the evaluator calls on a node.
|
|
229
|
+
*
|
|
230
|
+
* Unlike the fire-and-forget mutation callbacks, an eval hook produces (or
|
|
231
|
+
* adjusts) a value and can fail, hence the int return code. It needs nothing
|
|
232
|
+
* but its node: the node carries its own context (node->eval_ctx) and its own
|
|
233
|
+
* value slot (node->out), and the opaque data registered with it arrives as
|
|
234
|
+
* `user_data` - the same pointer for all three hooks of that node.
|
|
235
|
+
*/
|
|
236
|
+
typedef int (*dcg_node_hook_fn)(dcg_node* node, void* user_data);
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @brief The evaluation context of a node - the host of its callback protocol.
|
|
240
|
+
*
|
|
241
|
+
* Each node carries its own context: the three eval hooks, the opaque data
|
|
242
|
+
* they share, and the state a running evaluation needs. The evaluator reaches
|
|
243
|
+
* all of it through the node, so a hook needs no context parameter.
|
|
244
|
+
*
|
|
245
|
+
* `type_eval_fn` is not one of the three: it is the node's own evaluation, the
|
|
246
|
+
* rule its type gives it, installed as the node is built when the build asks for
|
|
247
|
+
* it (DCG_EVAL_DIRECT_HOOKS) - and, for an operator node, the rule its OPERATOR
|
|
248
|
+
* gives it, installed when the operator is set and in every build (see
|
|
249
|
+
* c_expr.h). A caller's `eval_fn` OVERRIDES it, which is why the two are
|
|
250
|
+
* separate slots rather than one: the layer's rule and somebody's callback are
|
|
251
|
+
* different things, and only the second belongs to an installer.
|
|
252
|
+
*
|
|
253
|
+
* `stage` records the last evaluation of THIS node twice over, in one word: how
|
|
254
|
+
* far it got (the progression the protocol walks) and WHAT produced its value -
|
|
255
|
+
* the caller's hook, the rule the node's type gave it, or the built-in
|
|
256
|
+
* evaluation (see dcg_eval_stage). `err_code` is what that evaluation failed
|
|
257
|
+
* with, and `eval_seq_id` is the id of the run that wrote the value, so a node
|
|
258
|
+
* can be asked which run it last took part in. All of them are written by the
|
|
259
|
+
* protocol; a dry run restores the node's VALUE, not this record, because what
|
|
260
|
+
* the record says is about the evaluation that just happened.
|
|
261
|
+
*/
|
|
262
|
+
typedef struct dcg_node_eval_ctx {
|
|
263
|
+
dcg_node_hook_fn pre_eval_fn; // Runs before the node is evaluated.
|
|
264
|
+
dcg_node_hook_fn eval_fn; // Produces the node's value, into node->out.
|
|
265
|
+
dcg_node_hook_fn post_eval_fn; // Runs after the value is produced.
|
|
266
|
+
dcg_node_hook_fn type_eval_fn; // The rule the node's TYPE gives it - see DCG_EVAL_DIRECT_HOOKS.
|
|
267
|
+
void* user_data; // Opaque data shared by the three hooks.
|
|
268
|
+
void* run; // Per-run state owned by the evaluator.
|
|
269
|
+
uint64_t flags; // dcg_eval_flag bits.
|
|
270
|
+
dcg_ret_code err_code; // Outcome of the node's last evaluation.
|
|
271
|
+
dcg_eval_stage stage; // What ran, and what produced the value - see dcg_eval_stage.
|
|
272
|
+
uint64_t eval_seq_id; // The run whose value the node holds.
|
|
273
|
+
size_t depth; // Depth of this node at its last visit.
|
|
274
|
+
size_t visits; // How many times the node was evaluated.
|
|
275
|
+
} dcg_node_eval_ctx;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @brief Mutation events observed by the registered callbacks.
|
|
279
|
+
*/
|
|
280
|
+
typedef enum dcg_node_event {
|
|
281
|
+
DCG_NODE_EVENT_CHILD_ADDED = 0x0101, // A child was linked under this node.
|
|
282
|
+
DCG_NODE_EVENT_CHILD_UPDATED = 0x0102, // A child was linked under this node.
|
|
283
|
+
DCG_NODE_EVENT_CHILD_REMOVED = 0x0103,
|
|
284
|
+
DCG_NODE_EVENT_CHILD_CLEARED = 0x0104,
|
|
285
|
+
DCG_NODE_EVENT_MODIFIED = 0x0201, // Metadata changed (repr, labels, hooks).
|
|
286
|
+
DCG_NODE_EVENT_EVALUATED = 0x0202,
|
|
287
|
+
} dcg_node_event;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @brief Fire-and-forget notification callback.
|
|
291
|
+
*
|
|
292
|
+
* @param event What happened.
|
|
293
|
+
* @param self The node the callback is registered on.
|
|
294
|
+
* @param subject The other node involved (the child), or NULL.
|
|
295
|
+
* @param seq_id Sequence id of the mutating caller, for self-suppression.
|
|
296
|
+
* @param user_data Opaque data passed at registration.
|
|
297
|
+
*/
|
|
298
|
+
typedef void (*dcg_node_callback_fn)(dcg_node_event event, dcg_node* self, dcg_node* subject, uint64_t seq_id, void* user_data);
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @brief Linked-list node of a registered mutation callback.
|
|
302
|
+
*/
|
|
303
|
+
typedef struct dcg_node_callback_ctx {
|
|
304
|
+
dcg_node_callback_fn fn; // Callback implementation.
|
|
305
|
+
void* user_data; // Opaque data from registration.
|
|
306
|
+
uintptr_t id; // Opaque id for unregistration.
|
|
307
|
+
struct dcg_node_callback_ctx* next; // Next registered callback.
|
|
308
|
+
} dcg_node_callback_ctx;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @brief A node of the decision graph.
|
|
312
|
+
*
|
|
313
|
+
* The node owns its `repr`, its labels and their text, a string `out` and any
|
|
314
|
+
* standalone condition adopted onto it. Everything it owns is allocated as a
|
|
315
|
+
* nested child block, so one c_ap_free_owned() - what c_dcg_node_free() ends
|
|
316
|
+
* with - releases the whole of it without a teardown walk. The five built-in
|
|
317
|
+
* edge conditions are static objects, never blocks, and are left alone.
|
|
318
|
+
*
|
|
319
|
+
* What a node does NOT own is its graph: `children` names blocks of its own
|
|
320
|
+
* type that happen to hang below it. c_dcg_node_free() is therefore a LOCAL
|
|
321
|
+
* operation - it unlinks the node from its parent and lets its children go
|
|
322
|
+
* (they become parentless fragments and stay alive). Tearing a whole graph
|
|
323
|
+
* down is the hierarchy's job: c_dcg_node_teardown_root() walks it and frees
|
|
324
|
+
* every node through the free of its type, leaf first.
|
|
325
|
+
*
|
|
326
|
+
* The payload a family adds after the base header - the operands of an
|
|
327
|
+
* expression, the key of a variable - is released by that family's _free,
|
|
328
|
+
* which is what c_dcg_node_free_generic() reaches for a node in hand as a plain
|
|
329
|
+
* dcg_node*.
|
|
330
|
+
*/
|
|
331
|
+
typedef struct dcg_node {
|
|
332
|
+
// === Eval Callbacks ===
|
|
333
|
+
dcg_node_eval_ctx eval_ctx; // Hooks + run state of this node.
|
|
334
|
+
// === Context Callbacks ===
|
|
335
|
+
dcg_node_ctx_ops ctx_ops; // What entering and leaving this type means.
|
|
336
|
+
// === Out Buffer ===
|
|
337
|
+
dcg_var_t out; // Value produced by the last evaluation. OWNED if a string.
|
|
338
|
+
// === Meta Data ===
|
|
339
|
+
dcg_node_type ntype; // Node type.
|
|
340
|
+
const char* repr; // Display text. // OWNED - a nested copy.
|
|
341
|
+
uuid_t uid; // Stable identity: minted at init, mirrored by the Python layer.
|
|
342
|
+
dcg_node_label* labels; // Label list.
|
|
343
|
+
bool autogen; // Generated by the builder rather than written by the user.
|
|
344
|
+
uint32_t flags; // dcg_node_flag bits.
|
|
345
|
+
void* user_payload; // Opaque slot for the binding layer.
|
|
346
|
+
dcg_node_callback_ctx* callbacks; // Registered mutation observers (calloc/free).
|
|
347
|
+
// === Hierarchy ===
|
|
348
|
+
const dcg_node_edge_condition* condition_to_parent; // Edge condition. // OWNED if OWN_CONDITION.
|
|
349
|
+
struct dcg_node* parent; // Parent node, NULL for the root.
|
|
350
|
+
struct dcg_node* children; // First child, NULL for a leaf.
|
|
351
|
+
struct dcg_node* next_sibling; // Next child of the same parent.
|
|
352
|
+
struct dcg_node* prev_sibling; // Previous child of the same parent.
|
|
353
|
+
} dcg_node;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* @brief Result of a validation walk.
|
|
357
|
+
*/
|
|
358
|
+
typedef struct dcg_validate_report {
|
|
359
|
+
dcg_ret_code code; // First problem found (DCG_OK when the graph is valid).
|
|
360
|
+
dcg_node* node; // Node that produced the first problem.
|
|
361
|
+
size_t errors; // Total problems.
|
|
362
|
+
size_t warnings; // Total warnings (unresolved AUTO on a non-baked graph).
|
|
363
|
+
size_t nodes; // Nodes visited.
|
|
364
|
+
size_t depth; // Deepest level visited.
|
|
365
|
+
} dcg_validate_report;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* @brief Tree drawing style of the console renderer.
|
|
369
|
+
*/
|
|
370
|
+
typedef enum dcg_render_style {
|
|
371
|
+
DCG_RENDER_ASCII = 0, // +-- / |-- style.
|
|
372
|
+
DCG_RENDER_UNICODE = 1 // Box-drawing glyphs for the branch guides.
|
|
373
|
+
} dcg_render_style;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @brief Console renderer options.
|
|
377
|
+
*
|
|
378
|
+
* Zero-initialize and call c_dcg_render_opts_default() to get the standard
|
|
379
|
+
* layout, then override the fields you care about.
|
|
380
|
+
*/
|
|
381
|
+
typedef struct dcg_render_opts {
|
|
382
|
+
dcg_render_style style; // Drawing style.
|
|
383
|
+
size_t max_depth; // Stop below this depth (0 = unlimited).
|
|
384
|
+
bool show_uid; // Render each node's uid.
|
|
385
|
+
bool show_labels; // Render each node's labels.
|
|
386
|
+
bool show_condition; // Render the edge condition.
|
|
387
|
+
bool show_out; // Render the node's out value.
|
|
388
|
+
bool show_flags; // Render the flag bits.
|
|
389
|
+
bool show_hooks; // Render which eval hooks are registered.
|
|
390
|
+
bool show_children; // Render the child count.
|
|
391
|
+
bool show_address; // Render the node address.
|
|
392
|
+
} dcg_render_opts;
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @brief A bounded string builder over a caller-provided buffer.
|
|
396
|
+
*
|
|
397
|
+
* Appends never overflow: once `used` reaches `cap` the builder keeps
|
|
398
|
+
* counting the characters that WOULD have been written, so the caller can
|
|
399
|
+
* detect truncation (used >= cap) instead of silently losing text.
|
|
400
|
+
*/
|
|
401
|
+
typedef struct dcg_strbuf {
|
|
402
|
+
char* data; // Destination buffer. NOT owned.
|
|
403
|
+
size_t cap; // Capacity of data.
|
|
404
|
+
size_t used; // Characters that fit (excluding the terminating NUL).
|
|
405
|
+
} dcg_strbuf;
|
|
406
|
+
|
|
407
|
+
// ========== Forward Declarations ==========
|
|
408
|
+
|
|
409
|
+
// Utilities
|
|
410
|
+
static inline const char* c_dcg_node_type_name(dcg_node_type ntype);
|
|
411
|
+
static inline bool c_dcg_node_type_is_input(dcg_node_type ntype);
|
|
412
|
+
static inline bool c_dcg_node_type_is_op(dcg_node_type ntype);
|
|
413
|
+
static inline bool c_dcg_node_type_is_flat(dcg_node_type ntype);
|
|
414
|
+
static inline bool c_dcg_node_type_is_action(dcg_node_type ntype);
|
|
415
|
+
static inline bool c_dcg_node_type_is_special(dcg_node_type ntype);
|
|
416
|
+
static inline size_t c_dcg_node_type_arity(dcg_node_type ntype);
|
|
417
|
+
static inline uint64_t c_dcg_node_gen_seq_id(const void* ptr);
|
|
418
|
+
static inline void c_dcg_node_unlink(dcg_node* node);
|
|
419
|
+
static inline void c_dcg_node_adopt_condition(dcg_node* node, const dcg_node_edge_condition* condition);
|
|
420
|
+
|
|
421
|
+
// Lifecycle
|
|
422
|
+
static inline int c_dcg_node_init(dcg_node* node, dcg_node_type ntype, const char* repr);
|
|
423
|
+
static inline dcg_node_ctx_ops c_dcg_node_ctx_ops_for(dcg_node_type ntype);
|
|
424
|
+
static inline dcg_node* c_dcg_node_new(dcg_node_type ntype, const char* repr, allocator_protocol* allocator);
|
|
425
|
+
static inline void c_dcg_node_dealloc(dcg_node* node);
|
|
426
|
+
static inline void c_dcg_node_free(dcg_node* node);
|
|
427
|
+
|
|
428
|
+
// Node metadata
|
|
429
|
+
static inline int c_dcg_node_set_repr(dcg_node* node, const char* repr);
|
|
430
|
+
static inline int c_dcg_node_set_string(dcg_node* node, const char* value);
|
|
431
|
+
|
|
432
|
+
// Eval hooks
|
|
433
|
+
static inline int c_dcg_node_register_eval_hook(dcg_node* node, dcg_node_hook_type hook, dcg_node_hook_fn fn, void* user_data);
|
|
434
|
+
static inline void c_dcg_node_unregister_eval_hooks(dcg_node* node);
|
|
435
|
+
|
|
436
|
+
// Mutation callbacks
|
|
437
|
+
static inline int c_dcg_node_register_callback(dcg_node* node, dcg_node_callback_fn fn, void* user_data, uintptr_t* out_id);
|
|
438
|
+
static inline int c_dcg_node_unregister_callback(dcg_node* node, uintptr_t callback_id);
|
|
439
|
+
static inline void c_dcg_node_invoke_callbacks(dcg_node* node, dcg_node_event event, dcg_node* subject, uint64_t seq_id);
|
|
440
|
+
static inline size_t c_dcg_node_callback_count(const dcg_node* node);
|
|
441
|
+
|
|
442
|
+
// Graph building
|
|
443
|
+
static inline int c_dcg_node_append(dcg_node* parent, dcg_node* child, const dcg_node_edge_condition* condition);
|
|
444
|
+
static inline int c_dcg_node_append_auto(dcg_node* parent, dcg_node* child);
|
|
445
|
+
static inline int c_dcg_node_append_at(dcg_node* parent, dcg_node* child, const dcg_node_edge_condition* condition, size_t index);
|
|
446
|
+
static inline int c_dcg_node_append_at_binary(dcg_node* parent, dcg_node* child, bool condition, size_t index);
|
|
447
|
+
static inline int c_dcg_node_detach(dcg_node* node);
|
|
448
|
+
static inline int c_dcg_node_replace(dcg_node* old_node, dcg_node* new_node);
|
|
449
|
+
static inline int c_dcg_node_replace_shared(dcg_node* old_node, dcg_node* new_node);
|
|
450
|
+
static inline dcg_node* c_dcg_node_new_placeholder(allocator_protocol* allocator);
|
|
451
|
+
static inline size_t c_dcg_node_consolidate_placeholder(dcg_node* node);
|
|
452
|
+
static inline dcg_node* c_dcg_node_get_placeholder(dcg_node* node);
|
|
453
|
+
|
|
454
|
+
// Queries
|
|
455
|
+
static inline size_t c_dcg_node_child_count(const dcg_node* node);
|
|
456
|
+
static inline dcg_node* c_dcg_node_first_child(const dcg_node* node);
|
|
457
|
+
static inline dcg_node* c_dcg_node_last_child(const dcg_node* node);
|
|
458
|
+
static inline dcg_node* c_dcg_node_child_at(const dcg_node* node, size_t index);
|
|
459
|
+
static inline ssize_t c_dcg_node_child_index(const dcg_node* node);
|
|
460
|
+
static inline dcg_node* c_dcg_node_child_by_condition(const dcg_node* node, const dcg_node_edge_condition* condition);
|
|
461
|
+
static inline dcg_node* c_dcg_node_next_sibling(const dcg_node* node);
|
|
462
|
+
static inline dcg_node* c_dcg_node_prev_sibling(const dcg_node* node);
|
|
463
|
+
static inline dcg_node* c_dcg_node_root(dcg_node* node);
|
|
464
|
+
static inline size_t c_dcg_node_depth(const dcg_node* node);
|
|
465
|
+
static inline size_t c_dcg_node_height(const dcg_node* node);
|
|
466
|
+
static inline size_t c_dcg_node_subtree_size(const dcg_node* node);
|
|
467
|
+
static inline size_t c_dcg_node_leaf_count(const dcg_node* node);
|
|
468
|
+
static inline bool c_dcg_node_is_leaf(const dcg_node* node);
|
|
469
|
+
static inline bool c_dcg_node_is_root(const dcg_node* node);
|
|
470
|
+
static inline bool c_dcg_node_is_ancestor(const dcg_node* node, const dcg_node* descendant);
|
|
471
|
+
static inline dcg_node* c_dcg_node_find_by_uid(const dcg_node* root, const uuid_t uid);
|
|
472
|
+
static inline size_t c_dcg_node_collect_leaves(const dcg_node* root, dcg_node** out, size_t cap);
|
|
473
|
+
static inline size_t c_dcg_node_collect_descendants(const dcg_node* root, dcg_node** out, size_t cap);
|
|
474
|
+
static inline size_t c_dcg_node_path_to(const dcg_node* node, const dcg_node** out, size_t cap);
|
|
475
|
+
|
|
476
|
+
// Labels
|
|
477
|
+
static inline int c_dcg_node_add_label(dcg_node* node, const char* label);
|
|
478
|
+
static inline bool c_dcg_node_has_label(const dcg_node* node, const char* label);
|
|
479
|
+
static inline int c_dcg_node_remove_label(dcg_node* node, const char* label);
|
|
480
|
+
static inline size_t c_dcg_node_label_count(const dcg_node* node);
|
|
481
|
+
|
|
482
|
+
// Validation
|
|
483
|
+
static inline bool c_dcg_node_validate(const dcg_node* root, dcg_validate_report* report);
|
|
484
|
+
static inline int c_dcg_node_validate_print(const dcg_node* root, FILE* stream);
|
|
485
|
+
|
|
486
|
+
// Rendering
|
|
487
|
+
static inline void c_dcg_render_opts_default(dcg_render_opts* opts);
|
|
488
|
+
static inline int c_dcg_node_render(const dcg_node* node, FILE* stream, const dcg_render_opts* opts);
|
|
489
|
+
static inline int c_dcg_node_print(const dcg_node* node);
|
|
490
|
+
static inline int c_dcg_node_render_to_string(const dcg_node* node, char* out, size_t cap, const dcg_render_opts* opts);
|
|
491
|
+
|
|
492
|
+
// Identity
|
|
493
|
+
static inline void c_dcg_node_arm_uuid(dcg_node* node);
|
|
494
|
+
static inline int c_dcg_node_format_uid(const uuid_t uid, char* out, size_t cap);
|
|
495
|
+
|
|
496
|
+
// Internal helpers (exposed for reuse and testing - not part of the stable surface)
|
|
497
|
+
static inline uint64_t c_dcg_node_getpid(void);
|
|
498
|
+
static inline void c_dcg_sb_init(dcg_strbuf* buf, char* data, size_t cap);
|
|
499
|
+
static inline void c_dcg_sb_puts(dcg_strbuf* buf, const char* text);
|
|
500
|
+
static inline void c_dcg_sb_printf(dcg_strbuf* buf, const char* fmt, ...);
|
|
501
|
+
static inline const dcg_node_edge_condition* c_dcg_node_infer_condition(const dcg_node* parent);
|
|
502
|
+
static inline int c_dcg_node_link(dcg_node* parent, dcg_node* child, const dcg_node_edge_condition* condition, dcg_node* anchor);
|
|
503
|
+
static inline void c_dcg_validate_fail(dcg_validate_report* report, dcg_node* node, dcg_ret_code err, bool* valid);
|
|
504
|
+
static inline bool c_dcg_node_validate_walk(const dcg_node* node, dcg_validate_report* report, size_t depth);
|
|
505
|
+
static inline int c_dcg_node_format_flags(uint32_t flags, char* out, size_t cap);
|
|
506
|
+
static inline int c_dcg_node_format_line(const dcg_node* node, char* out, size_t cap, const dcg_render_opts* opts);
|
|
507
|
+
static inline int c_dcg_node_render_walk(const dcg_node* node, FILE* stream, const dcg_render_opts* opts, const char* prefix, bool is_last, size_t depth);
|
|
508
|
+
static inline size_t c_dcg_node_collect_leaves_walk(const dcg_node* node, dcg_node** out, size_t cap, size_t* written);
|
|
509
|
+
static inline size_t c_dcg_node_collect_descendants_walk(const dcg_node* node, dcg_node** out, size_t cap, size_t* written);
|
|
510
|
+
|
|
511
|
+
// ========== Utility Functions ==========
|
|
512
|
+
|
|
513
|
+
/*
|
|
514
|
+
* One name table per family, indexed by the variant number (the low byte of
|
|
515
|
+
* the type). Adding a variant means adding a name in the right table and
|
|
516
|
+
* nothing else - the family is selected by the mask, never by parsing.
|
|
517
|
+
*/
|
|
518
|
+
static const char* const DCG_NODE_INPUT_NAMES[] = {"INPUT", "TRUE", "FALSE", "DOUBLE", "STRING", "INT", "VARIABLE"};
|
|
519
|
+
static const char* const DCG_NODE_OP_NAMES[] = {"OP", "UNARY", "BINARY", "TERNARY", "CALL"};
|
|
520
|
+
static const char* const DCG_NODE_ACTION_NAMES[] = {"ACTION", "NOACTION", "LONGACTION", "SHORTACTION", "CANCELACTION", "CLEARACTION", "PLACEHOLDER"};
|
|
521
|
+
static const char* const DCG_NODE_SPECIAL_NAMES[] = {"SPECIAL", "ROOT", "BREAKPOINT"};
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* @brief Look a variant number up in its family's name table.
|
|
525
|
+
*
|
|
526
|
+
* @param names Name table of the family.
|
|
527
|
+
* @param count Entries in the table.
|
|
528
|
+
* @param variant Variant number (the low byte of the type).
|
|
529
|
+
* @return The name, or "UNKNOWN" past the end of the table.
|
|
530
|
+
*/
|
|
531
|
+
static inline const char* c_dcg_name_at(const char* const* names, size_t count, size_t variant) {
|
|
532
|
+
if (!names || variant >= count) return "UNKNOWN";
|
|
533
|
+
return names[variant];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* @brief Stable display name of a node type (the enum name without its prefix).
|
|
538
|
+
*
|
|
539
|
+
* @param ntype Node type.
|
|
540
|
+
* @return Static string; "UNKNOWN" for an out-of-range type.
|
|
541
|
+
*/
|
|
542
|
+
static inline const char* c_dcg_node_type_name(dcg_node_type ntype) {
|
|
543
|
+
size_t variant = (size_t) ((int) ntype & DCG_NODE_VARIANT_MASK);
|
|
544
|
+
|
|
545
|
+
switch ((int) ntype & DCG_NODE_FAMILY_MASK) {
|
|
546
|
+
case DCG_NODE_INPUT:
|
|
547
|
+
return c_dcg_name_at(DCG_NODE_INPUT_NAMES, sizeof(DCG_NODE_INPUT_NAMES) / sizeof(char*), variant);
|
|
548
|
+
case DCG_NODE_OP:
|
|
549
|
+
return c_dcg_name_at(DCG_NODE_OP_NAMES, sizeof(DCG_NODE_OP_NAMES) / sizeof(char*), variant);
|
|
550
|
+
case DCG_NODE_ACTION:
|
|
551
|
+
return c_dcg_name_at(DCG_NODE_ACTION_NAMES, sizeof(DCG_NODE_ACTION_NAMES) / sizeof(char*), variant);
|
|
552
|
+
case DCG_NODE_SPECIAL:
|
|
553
|
+
return c_dcg_name_at(DCG_NODE_SPECIAL_NAMES, sizeof(DCG_NODE_SPECIAL_NAMES) / sizeof(char*), variant);
|
|
554
|
+
default:
|
|
555
|
+
return "UNKNOWN";
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* @brief Predicate: is the type in the input family?
|
|
561
|
+
*
|
|
562
|
+
* The input family is what a graph is fed: a literal it carries, or a read it
|
|
563
|
+
* takes from the store of a logic group.
|
|
564
|
+
*
|
|
565
|
+
* @param ntype Node type.
|
|
566
|
+
* @return true for input types (the family head included).
|
|
567
|
+
*/
|
|
568
|
+
static inline bool c_dcg_node_type_is_input(dcg_node_type ntype) {
|
|
569
|
+
return ((int) ntype & DCG_NODE_FAMILY_MASK) == DCG_NODE_INPUT;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* @brief Predicate: is the type in the operator family?
|
|
574
|
+
*
|
|
575
|
+
* @param ntype Node type.
|
|
576
|
+
* @return true for operator types.
|
|
577
|
+
*/
|
|
578
|
+
static inline bool c_dcg_node_type_is_op(dcg_node_type ntype) {
|
|
579
|
+
return ((int) ntype & DCG_NODE_FAMILY_MASK) == DCG_NODE_OP;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* @brief Predicate: does the type live in the base header alone?
|
|
584
|
+
*
|
|
585
|
+
* A literal IS the whole struct - its variant adds no field of its own - so a
|
|
586
|
+
* dcg_node block holds it. An expression carries its operand array after the
|
|
587
|
+
* header, a variable its key and its group, an action its placement fields, a
|
|
588
|
+
* root or a breakpoint its walk state: those need their family's constructor,
|
|
589
|
+
* because only the family knows how big the block has to be.
|
|
590
|
+
*
|
|
591
|
+
* @param ntype Node type.
|
|
592
|
+
* @return true when a plain dcg_node block is enough for the type.
|
|
593
|
+
*/
|
|
594
|
+
static inline bool c_dcg_node_type_is_flat(dcg_node_type ntype) {
|
|
595
|
+
// A placeholder is the one action-family type that carries nothing beyond
|
|
596
|
+
// the base node - no signal, no payload, no connect flag - so its block is
|
|
597
|
+
// one dcg_node like an input's, and the base constructor can build it. It
|
|
598
|
+
// is asked first because the family switch below reads "input, and nothing
|
|
599
|
+
// else", which would otherwise refuse it.
|
|
600
|
+
if (ntype == DCG_NODE_PLACEHOLDER) return true;
|
|
601
|
+
|
|
602
|
+
switch ((int) ntype & DCG_NODE_FAMILY_MASK) {
|
|
603
|
+
case DCG_NODE_INPUT:
|
|
604
|
+
return ntype != DCG_NODE_VARIABLE; // a variable carries its key and its group
|
|
605
|
+
default:
|
|
606
|
+
return false; // an operator carries operands, the rest carry their own state
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @brief Predicate: is the type in the action family?
|
|
612
|
+
*
|
|
613
|
+
* Action nodes are leaves: the graph terminates (or hands over control) at them.
|
|
614
|
+
*
|
|
615
|
+
* @param ntype Node type.
|
|
616
|
+
* @return true for action types.
|
|
617
|
+
*/
|
|
618
|
+
static inline bool c_dcg_node_type_is_action(dcg_node_type ntype) {
|
|
619
|
+
return ((int) ntype & DCG_NODE_FAMILY_MASK) == DCG_NODE_ACTION;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* @brief Predicate: is the type in the special family (root / breakpoint)?
|
|
624
|
+
*
|
|
625
|
+
* @param ntype Node type.
|
|
626
|
+
* @return true for special types.
|
|
627
|
+
*/
|
|
628
|
+
static inline bool c_dcg_node_type_is_special(dcg_node_type ntype) {
|
|
629
|
+
return ((int) ntype & DCG_NODE_FAMILY_MASK) == DCG_NODE_SPECIAL;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* @brief Fixed operand count of a node type.
|
|
634
|
+
*
|
|
635
|
+
* @param ntype Node type.
|
|
636
|
+
* @return 1 / 2 / 3 for the fixed-arity operators; 0 for everything else
|
|
637
|
+
* (inputs, actions, and the variadic CALL).
|
|
638
|
+
*/
|
|
639
|
+
static inline size_t c_dcg_node_type_arity(dcg_node_type ntype) {
|
|
640
|
+
switch (ntype) {
|
|
641
|
+
case DCG_NODE_UNARY:
|
|
642
|
+
return 1;
|
|
643
|
+
case DCG_NODE_BINARY:
|
|
644
|
+
return 2;
|
|
645
|
+
case DCG_NODE_TERNARY:
|
|
646
|
+
return 3;
|
|
647
|
+
default:
|
|
648
|
+
return 0;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* @brief Process id, used as the seq_id seed (portable across POSIX and Windows).
|
|
654
|
+
*
|
|
655
|
+
* @return The current process id.
|
|
656
|
+
*/
|
|
657
|
+
static inline uint64_t c_dcg_node_getpid(void) {
|
|
658
|
+
#ifdef _WIN32
|
|
659
|
+
return (uint64_t) _getpid();
|
|
660
|
+
#else
|
|
661
|
+
return (uint64_t) getpid();
|
|
662
|
+
#endif
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @brief Generate a sequence id for a node - the project-standard pointer/PID mix.
|
|
667
|
+
*
|
|
668
|
+
* A seq_id lets a callback recognise the mutation it caused itself
|
|
669
|
+
* (self-suppression): the Python wrapper regenerates its id whenever it is
|
|
670
|
+
* (re)bound, so a callback that sees `seq_id == wrapper->seq_id` knows the
|
|
671
|
+
* wrapper already applied the change locally and can return early.
|
|
672
|
+
*
|
|
673
|
+
* @param ptr Address to derive the id from - the node block start.
|
|
674
|
+
* @return The sequence id (0 when ptr is NULL).
|
|
675
|
+
*/
|
|
676
|
+
static inline uint64_t c_dcg_node_gen_seq_id(const void* ptr) {
|
|
677
|
+
if (!ptr) return 0;
|
|
678
|
+
uint64_t addr = (uint64_t) (uintptr_t) ptr;
|
|
679
|
+
return XXH3_64bits_withSeed(&addr, sizeof(addr), c_dcg_node_getpid());
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* @brief Mint a node's identity from what makes it distinct, and arm the field.
|
|
684
|
+
*
|
|
685
|
+
* Hashed, not counted: a process-unique id needs no registry to hand out and no
|
|
686
|
+
* allocation to store, and the three inputs below are exactly what tells one
|
|
687
|
+
* node from another - the block it lives in, the type it is, and the text a
|
|
688
|
+
* reader of the graph knows it by. `c_dcg_node_init` calls this once the node
|
|
689
|
+
* is whole (type, out and repr all in place); calling it again later re-mints.
|
|
690
|
+
*
|
|
691
|
+
* The last two assignments lay the digest out as a valid RFC 4122 UUID -
|
|
692
|
+
* version 4, variant 1 - so the 16 bytes round-trip through `uuid.UUID` at the
|
|
693
|
+
* Python boundary without further massaging.
|
|
694
|
+
*
|
|
695
|
+
* @param node Node to arm (NULL-safe).
|
|
696
|
+
*/
|
|
697
|
+
static inline void c_dcg_node_arm_uuid(dcg_node* node) {
|
|
698
|
+
if (!node) return;
|
|
699
|
+
|
|
700
|
+
/* Stack state, no allocation. */
|
|
701
|
+
XXH3_state_t st;
|
|
702
|
+
XXH3_128bits_reset(&st);
|
|
703
|
+
|
|
704
|
+
/* 1. block identity - unique within the process while the node lives */
|
|
705
|
+
uintptr_t addr = (uintptr_t) node;
|
|
706
|
+
XXH3_128bits_update(&st, &addr, sizeof(addr));
|
|
707
|
+
|
|
708
|
+
/* 2. the type, so a recycled block never inherits its predecessor's id */
|
|
709
|
+
XXH3_128bits_update(&st, &node->ntype, sizeof(node->ntype));
|
|
710
|
+
|
|
711
|
+
/* 3. the repr, which is what the tree is read by */
|
|
712
|
+
if (node->repr) XXH3_128bits_update(&st, node->repr, strlen(node->repr));
|
|
713
|
+
|
|
714
|
+
XXH128_hash_t h = XXH3_128bits_digest(&st);
|
|
715
|
+
memcpy(node->uid, &h.low64, sizeof(h.low64));
|
|
716
|
+
memcpy(node->uid + sizeof(h.low64), &h.high64, sizeof(h.high64));
|
|
717
|
+
|
|
718
|
+
/* RFC 4122 layout: version 4, variant 1. */
|
|
719
|
+
node->uid[6] = (node->uid[6] & 0x0F) | 0x40;
|
|
720
|
+
node->uid[8] = (node->uid[8] & 0x3F) | 0x80;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* @brief Bind a string builder to a caller-provided buffer.
|
|
725
|
+
*
|
|
726
|
+
* @param buf Builder to initialize.
|
|
727
|
+
* @param data Destination buffer.
|
|
728
|
+
* @param cap Capacity of data.
|
|
729
|
+
*/
|
|
730
|
+
static inline void c_dcg_sb_init(dcg_strbuf* buf, char* data, size_t cap) {
|
|
731
|
+
if (!buf) return;
|
|
732
|
+
buf->data = data;
|
|
733
|
+
buf->cap = cap;
|
|
734
|
+
buf->used = 0;
|
|
735
|
+
if (data && cap > 0) data[0] = '\0';
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* @brief Append a string to a builder.
|
|
740
|
+
*
|
|
741
|
+
* @param buf Builder (NULL-safe).
|
|
742
|
+
* @param text Text to append (NULL is skipped).
|
|
743
|
+
*/
|
|
744
|
+
static inline void c_dcg_sb_puts(dcg_strbuf* buf, const char* text) {
|
|
745
|
+
if (!buf || !text) return;
|
|
746
|
+
size_t len = strlen(text);
|
|
747
|
+
if (buf->used + len < buf->cap) {
|
|
748
|
+
memcpy(buf->data + buf->used, text, len);
|
|
749
|
+
buf->data[buf->used + len] = '\0';
|
|
750
|
+
}
|
|
751
|
+
buf->used += len;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* @brief Append formatted text to a builder.
|
|
756
|
+
*
|
|
757
|
+
* @param buf Builder (NULL-safe).
|
|
758
|
+
* @param fmt printf format.
|
|
759
|
+
* @param ... Format arguments.
|
|
760
|
+
*/
|
|
761
|
+
static inline void c_dcg_sb_printf(dcg_strbuf* buf, const char* fmt, ...) {
|
|
762
|
+
if (!buf || !fmt) return;
|
|
763
|
+
|
|
764
|
+
char scratch[DCG_NODE_STRING_MAXLEN];
|
|
765
|
+
va_list args;
|
|
766
|
+
va_start(args, fmt);
|
|
767
|
+
int n = vsnprintf(scratch, sizeof(scratch), fmt, args);
|
|
768
|
+
va_end(args);
|
|
769
|
+
|
|
770
|
+
if (n < 0) return;
|
|
771
|
+
if ((size_t) n < sizeof(scratch)) {
|
|
772
|
+
c_dcg_sb_puts(buf, scratch);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Long payload: re-format straight into the destination buffer.
|
|
777
|
+
if (buf->used >= buf->cap) {
|
|
778
|
+
buf->used += (size_t) n;
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
size_t room = buf->cap - buf->used;
|
|
782
|
+
va_start(args, fmt);
|
|
783
|
+
int written = vsnprintf(buf->data + buf->used, room, fmt, args);
|
|
784
|
+
va_end(args);
|
|
785
|
+
if (written >= 0 && (size_t) written < room) buf->used += (size_t) written;
|
|
786
|
+
else buf->used += (size_t) n;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* @brief Unlink a node from its parent's child list, without any policy check.
|
|
791
|
+
*
|
|
792
|
+
* Shared by the detach API and by teardown: a node being torn down must
|
|
793
|
+
* leave the parent's list even when the parent is FROZEN, otherwise the
|
|
794
|
+
* parent would keep a dangling pointer.
|
|
795
|
+
*
|
|
796
|
+
* @param node Node to unlink (NULL-safe: a parentless node is a no-op).
|
|
797
|
+
*/
|
|
798
|
+
static inline void c_dcg_node_unlink(dcg_node* node) {
|
|
799
|
+
if (!node || !node->parent) return;
|
|
800
|
+
|
|
801
|
+
dcg_node* parent = node->parent;
|
|
802
|
+
if (node->prev_sibling) node->prev_sibling->next_sibling = node->next_sibling;
|
|
803
|
+
else parent->children = node->next_sibling;
|
|
804
|
+
if (node->next_sibling) node->next_sibling->prev_sibling = node->prev_sibling;
|
|
805
|
+
|
|
806
|
+
node->parent = NULL;
|
|
807
|
+
node->next_sibling = NULL;
|
|
808
|
+
node->prev_sibling = NULL;
|
|
809
|
+
node->condition_to_parent = DCG_NO_CONDITION;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* @brief Adopt an edge condition onto a node.
|
|
814
|
+
*
|
|
815
|
+
* A node owns the condition of its edge: the condition becomes a child block
|
|
816
|
+
* of the node, so it is released with the node and needs no teardown step of
|
|
817
|
+
* its own. The five built-ins are static objects, never blocks, and are left
|
|
818
|
+
* alone - which is the one case worth distinguishing, and it is a plain
|
|
819
|
+
* pointer comparison.
|
|
820
|
+
*
|
|
821
|
+
* Everything else is expected to be an allocator-protocol block, which is what
|
|
822
|
+
* c_dcg_edge_new() hands out. Sniffing for that is deliberately NOT done here:
|
|
823
|
+
* asking whether a pointer is a block means reading the bytes before it, which
|
|
824
|
+
* is out of bounds for anything that is not one.
|
|
825
|
+
*
|
|
826
|
+
* @param node Node to adopt onto (NULL-safe).
|
|
827
|
+
* @param condition Condition to adopt (NULL-safe).
|
|
828
|
+
*/
|
|
829
|
+
static inline void c_dcg_node_adopt_condition(dcg_node* node, const dcg_node_edge_condition* condition) {
|
|
830
|
+
if (!node || !condition) return;
|
|
831
|
+
if (c_dcg_condition_is_sentinel(condition)) return; // a static built-in, never a block
|
|
832
|
+
c_ap_acquire_ownership((void*) condition, node); // re-parent it under the node
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ========== Lifecycle Methods ==========
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* @brief Initialize a node buf, taking a copy of its display text.
|
|
839
|
+
*
|
|
840
|
+
* The buf is populated from scratch (zeroing it first, so a reused buf is
|
|
841
|
+
* safe). The repr is always copied into a block owned by the node - a node
|
|
842
|
+
* never borrows display text - and both the node block and that copy come from
|
|
843
|
+
* the allocator protocol: a node is always an allocator block, never an
|
|
844
|
+
* embedded field, which is what lets it own everything it carries.
|
|
845
|
+
*
|
|
846
|
+
* @param node Node to initialize.
|
|
847
|
+
* @param ntype Node type.
|
|
848
|
+
* @param repr Display text to copy (may be NULL).
|
|
849
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_INVALID_BUF or DCG_ERR_OOM.
|
|
850
|
+
*/
|
|
851
|
+
static inline int c_dcg_node_init(dcg_node* node, dcg_node_type ntype, const char* repr) {
|
|
852
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
853
|
+
|
|
854
|
+
memset(node, 0, sizeof(*node));
|
|
855
|
+
node->ntype = ntype;
|
|
856
|
+
(void) c_dcg_var_init(&node->out);
|
|
857
|
+
|
|
858
|
+
int ret_code = c_dcg_node_set_repr(node, repr);
|
|
859
|
+
if (ret_code != DCG_OK) return ret_code;
|
|
860
|
+
|
|
861
|
+
node->ctx_ops = c_dcg_node_ctx_ops_for(ntype);
|
|
862
|
+
|
|
863
|
+
/* Identity last: the digest reads the type and the repr the lines above set. */
|
|
864
|
+
c_dcg_node_arm_uuid(node);
|
|
865
|
+
return DCG_OK;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/* ========== Context Enter ========== */
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* @brief Enter a BINARY branching node: reserve one arm for each of its two branches.
|
|
872
|
+
*
|
|
873
|
+
* The two-way case, and the enter every type gets unless its own arity says
|
|
874
|
+
* otherwise: a branching constant or expression, and a breakpoint, all reserve
|
|
875
|
+
* this pair. A type whose entering differs says so with its own callback - the
|
|
876
|
+
* root reserves a single arm instead, and a leaf or a stand-in refuses.
|
|
877
|
+
*
|
|
878
|
+
* The arms go in TRUE-then-FALSE - the order they are READ in - and
|
|
879
|
+
* `get_placeholder` hands back the FIRST standing one, so the first branch
|
|
880
|
+
* built inside this node takes the TRUE arm and the pair stays in that order in
|
|
881
|
+
* the child list. The capi keeps the pair the other way round, FALSE first, and
|
|
882
|
+
* this layer deliberately does not: here the child order is the reading order,
|
|
883
|
+
* so a sequential check tests the true branch first and a render shows it above
|
|
884
|
+
* the fallback.
|
|
885
|
+
*
|
|
886
|
+
* Each arm is a placeholder the node owns for as long as it stands: an arm that
|
|
887
|
+
* gets taken is replaced, and the stand-in it displaced is released here.
|
|
888
|
+
*
|
|
889
|
+
* @param node Node being entered.
|
|
890
|
+
* @param mgr Manager holding the build (unused: reserving is the node's own).
|
|
891
|
+
* @return DCG_OK, or a DCG_ERR_* code.
|
|
892
|
+
*/
|
|
893
|
+
static inline int c_dcg_node_ctx_enter_binary(dcg_node* node, dcg_logic_group_manager* mgr) {
|
|
894
|
+
(void) mgr;
|
|
895
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
896
|
+
|
|
897
|
+
dcg_node* true_arm = c_dcg_node_new_placeholder(c_ap_protocol_from_ptr(node));
|
|
898
|
+
if (!true_arm) return DCG_ERR_OOM;
|
|
899
|
+
|
|
900
|
+
int ret = c_dcg_node_append(node, true_arm, DCG_TRUE_CONDITION);
|
|
901
|
+
if (ret != DCG_OK) {
|
|
902
|
+
c_dcg_node_free(true_arm);
|
|
903
|
+
return ret;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
dcg_node* false_arm = c_dcg_node_new_placeholder(c_ap_protocol_from_ptr(node));
|
|
907
|
+
if (!false_arm) return DCG_ERR_OOM;
|
|
908
|
+
|
|
909
|
+
ret = c_dcg_node_append(node, false_arm, DCG_FALSE_CONDITION);
|
|
910
|
+
if (ret != DCG_OK) {
|
|
911
|
+
c_dcg_node_free(false_arm);
|
|
912
|
+
return ret;
|
|
913
|
+
}
|
|
914
|
+
return DCG_OK;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* @brief Enter the graph's entry point: reserve its single unconditioned arm.
|
|
919
|
+
*
|
|
920
|
+
* A root takes exactly one child and takes it unconditionally, so it reserves
|
|
921
|
+
* one arm rather than the binary pair every other branching node reserves.
|
|
922
|
+
*
|
|
923
|
+
* @param node The root being entered.
|
|
924
|
+
* @param mgr Manager holding the build (unused: reserving is the node's own).
|
|
925
|
+
* @return DCG_OK, or a DCG_ERR_* code.
|
|
926
|
+
*/
|
|
927
|
+
static inline int c_dcg_node_ctx_enter_root(dcg_node* node, dcg_logic_group_manager* mgr) {
|
|
928
|
+
(void) mgr;
|
|
929
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
930
|
+
|
|
931
|
+
dcg_node* arm = c_dcg_node_new_placeholder(c_ap_protocol_from_ptr(node));
|
|
932
|
+
if (!arm) return DCG_ERR_OOM;
|
|
933
|
+
|
|
934
|
+
int ret = c_dcg_node_append(node, arm, DCG_NO_CONDITION);
|
|
935
|
+
if (ret != DCG_OK) {
|
|
936
|
+
c_dcg_node_free(arm);
|
|
937
|
+
return ret;
|
|
938
|
+
}
|
|
939
|
+
return DCG_OK;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* @brief Refuse to enter: a leaf and a stand-in hold nothing a build continues in.
|
|
944
|
+
*
|
|
945
|
+
* @param node Node being entered (unused).
|
|
946
|
+
* @param mgr Manager holding the build (unused).
|
|
947
|
+
* @return DCG_ERR_TYPE, always.
|
|
948
|
+
*/
|
|
949
|
+
static inline int c_dcg_node_ctx_enter_refused(dcg_node* node, dcg_logic_group_manager* mgr) {
|
|
950
|
+
(void) node;
|
|
951
|
+
(void) mgr;
|
|
952
|
+
return DCG_ERR_TYPE;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* @brief The enter half of a type's context ops.
|
|
957
|
+
*
|
|
958
|
+
* The type is what decides, so this is the single place the mapping lives. The
|
|
959
|
+
* special family is not uniform: a root reserves one arm instead of the binary
|
|
960
|
+
* pair, a breakpoint reserves the pair like any other branching node. The exit
|
|
961
|
+
* half is left NULL here and installed by the manager - see c_dcg_node_ctx_ops.
|
|
962
|
+
*
|
|
963
|
+
* @param ntype Node type.
|
|
964
|
+
* @return The pair, with `exit_fn` NULL.
|
|
965
|
+
*/
|
|
966
|
+
static inline dcg_node_ctx_ops c_dcg_node_ctx_ops_for(dcg_node_type ntype) {
|
|
967
|
+
dcg_node_ctx_ops ops;
|
|
968
|
+
ops.enter_fn = NULL;
|
|
969
|
+
ops.exit_fn = NULL;
|
|
970
|
+
|
|
971
|
+
switch (ntype) {
|
|
972
|
+
case DCG_NODE_ROOT:
|
|
973
|
+
ops.enter_fn = c_dcg_node_ctx_enter_root;
|
|
974
|
+
break;
|
|
975
|
+
case DCG_NODE_PLACEHOLDER:
|
|
976
|
+
ops.enter_fn = c_dcg_node_ctx_enter_refused;
|
|
977
|
+
break;
|
|
978
|
+
default:
|
|
979
|
+
ops.enter_fn = c_dcg_node_type_is_action(ntype) ? c_dcg_node_ctx_enter_refused : c_dcg_node_ctx_enter_binary;
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
return ops;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* @brief Give a node its own copy of a display text.
|
|
987
|
+
*
|
|
988
|
+
* The copy is nested under the node and replaces (and releases) whatever repr
|
|
989
|
+
* the node held before, so a node's repr is always owned and always dies with
|
|
990
|
+
* the node.
|
|
991
|
+
*
|
|
992
|
+
* @param node Node to modify.
|
|
993
|
+
* @param repr Text to copy (NULL clears the repr).
|
|
994
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_INVALID_BUF or DCG_ERR_OOM.
|
|
995
|
+
*/
|
|
996
|
+
static inline int c_dcg_node_set_repr(dcg_node* node, const char* repr) {
|
|
997
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
998
|
+
|
|
999
|
+
char* copy = NULL;
|
|
1000
|
+
if (repr) {
|
|
1001
|
+
size_t len = strlen(repr);
|
|
1002
|
+
copy = (char*) c_ap_alloc_child(len + 1, NULL, node);
|
|
1003
|
+
if (!copy) return DCG_ERR_OOM;
|
|
1004
|
+
memcpy(copy, repr, len + 1);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (node->repr) c_ap_free_owned((void*) node->repr); // the copy we are replacing
|
|
1008
|
+
node->repr = copy;
|
|
1009
|
+
return DCG_OK;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* @brief Allocate and initialize a node.
|
|
1014
|
+
*
|
|
1015
|
+
* @param ntype Node type.
|
|
1016
|
+
* @param repr Borrowed display text (may be NULL).
|
|
1017
|
+
* @param allocator Allocator for the block; NULL falls back to the plain heap.
|
|
1018
|
+
* @return The node, or NULL on OOM.
|
|
1019
|
+
*/
|
|
1020
|
+
static inline dcg_node* c_dcg_node_new(dcg_node_type ntype, const char* repr, allocator_protocol* allocator) {
|
|
1021
|
+
/* The block is one dcg_node, so the types that carry more are refused here
|
|
1022
|
+
* and built by the constructor named after their family - see c_const.h,
|
|
1023
|
+
* c_expr.h and c_action.h. */
|
|
1024
|
+
if (!c_dcg_node_type_is_flat(ntype)) return NULL;
|
|
1025
|
+
|
|
1026
|
+
dcg_node* node = (dcg_node*) c_ap_alloc(sizeof(dcg_node), allocator);
|
|
1027
|
+
if (!node) return NULL;
|
|
1028
|
+
|
|
1029
|
+
if (c_dcg_node_init(node, ntype, repr) != DCG_OK) {
|
|
1030
|
+
c_ap_free_owned(node);
|
|
1031
|
+
return NULL;
|
|
1032
|
+
}
|
|
1033
|
+
return node;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* @brief Tear down a node - leaves a zeroed buf.
|
|
1038
|
+
*
|
|
1039
|
+
* Releases what the BASE owns: the labels and their text, the callback
|
|
1040
|
+
* contexts, the repr, a string out and an adopted condition. The node is then
|
|
1041
|
+
* unlinked from its parent and its children are let go - each one is unlinked
|
|
1042
|
+
* (c_dcg_node_unlink), so it survives as a parentless fragment. A node owns
|
|
1043
|
+
* its block, not its graph: the subtree below it is c_dcg_node_teardown_root's
|
|
1044
|
+
* business, and the payload a family adds after the header is that family's
|
|
1045
|
+
* own _free (see c_dcg_node_free_generic).
|
|
1046
|
+
*
|
|
1047
|
+
* The node's own buf is NOT freed - that is _free's job. A borrowed repr is
|
|
1048
|
+
* left untouched; a built-in condition is a static object, never a block.
|
|
1049
|
+
*
|
|
1050
|
+
* @param node Node to tear down (NULL-safe).
|
|
1051
|
+
*/
|
|
1052
|
+
static inline void c_dcg_node_dealloc(dcg_node* node) {
|
|
1053
|
+
if (!node) return;
|
|
1054
|
+
|
|
1055
|
+
// An adopted condition is a nested block of this node. It goes before the
|
|
1056
|
+
// unlink, which drops the pointer to it.
|
|
1057
|
+
if (node->condition_to_parent && !c_dcg_condition_is_sentinel(node->condition_to_parent)) {
|
|
1058
|
+
c_ap_free_owned((void*) node->condition_to_parent);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Unlink from the parent first: a teardown must never leave a dangling
|
|
1062
|
+
// link behind in the parent's child list. This deliberately bypasses the
|
|
1063
|
+
// FROZEN guard - a frozen parent must not be able to pin a dead child.
|
|
1064
|
+
dcg_node* parent = node->parent;
|
|
1065
|
+
c_dcg_node_unlink(node);
|
|
1066
|
+
if (parent) c_dcg_node_invoke_callbacks(parent, DCG_NODE_EVENT_CHILD_REMOVED, node, (uint64_t) -1);
|
|
1067
|
+
|
|
1068
|
+
// The children are blocks of their own: they are let go, not freed, and
|
|
1069
|
+
// each keeps its own subtree. Unlinking pops one child per round, so the
|
|
1070
|
+
// list we are walking is the one being emptied.
|
|
1071
|
+
while (node->children) c_dcg_node_unlink(node->children);
|
|
1072
|
+
|
|
1073
|
+
// Labels are nested blocks, and each entry's text is nested under the
|
|
1074
|
+
// entry: one free per entry releases the pair.
|
|
1075
|
+
dcg_node_label* label = node->labels;
|
|
1076
|
+
node->labels = NULL;
|
|
1077
|
+
while (label) {
|
|
1078
|
+
dcg_node_label* next = label->next;
|
|
1079
|
+
c_ap_free_owned(label);
|
|
1080
|
+
label = next;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Callback contexts are short-lived registration state: calloc / free.
|
|
1084
|
+
dcg_node_callback_ctx* cb = node->callbacks;
|
|
1085
|
+
while (cb) {
|
|
1086
|
+
dcg_node_callback_ctx* next = cb->next;
|
|
1087
|
+
free(cb);
|
|
1088
|
+
cb = next;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// The repr and a string out are nested blocks, released here so the block
|
|
1092
|
+
// owns nothing once the teardown is done.
|
|
1093
|
+
if (node->repr) c_ap_free_owned((void*) node->repr);
|
|
1094
|
+
if (node->out.dtype == VAR_TYPE_STRING && node->out.value.as_string) {
|
|
1095
|
+
c_ap_free_owned((void*) node->out.value.as_string);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
memset(node, 0, sizeof(*node));
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* @brief Tear down a node and free its buf - the node itself, nothing below it.
|
|
1103
|
+
*
|
|
1104
|
+
* The children are unlinked and left alive (see c_dcg_node_dealloc), so this
|
|
1105
|
+
* is the free of a node, not of a subtree. Free a whole graph from its top
|
|
1106
|
+
* with c_dcg_node_teardown_root() - never by calling this on a branch.
|
|
1107
|
+
*
|
|
1108
|
+
* @param node Node to free (NULL-safe): a base node, or one of the types the
|
|
1109
|
+
* base alone holds. A family that adds state after the header -
|
|
1110
|
+
* an expression, a variable, a root, a breakpoint - is
|
|
1111
|
+
* torn down by its own _free, or by c_dcg_node_free_generic() when
|
|
1112
|
+
* only its dcg_node* is in hand.
|
|
1113
|
+
*/
|
|
1114
|
+
static inline void c_dcg_node_free(dcg_node* node) {
|
|
1115
|
+
if (!node) return;
|
|
1116
|
+
c_dcg_node_dealloc(node);
|
|
1117
|
+
c_ap_free_owned(node); // everything the node nested is released here
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// ========== Public APIs - Typed Constructors ==========
|
|
1121
|
+
|
|
1122
|
+
/*
|
|
1123
|
+
* The payload-free types. Everything with state of its own - literals,
|
|
1124
|
+
* expressions, variables - is constructed by its own header.
|
|
1125
|
+
*/
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* @brief Give a node an owned copy of a string value.
|
|
1129
|
+
*
|
|
1130
|
+
* The copy is nested under the node, so it dies with the node; a previous
|
|
1131
|
+
* string value of the node is released first.
|
|
1132
|
+
*
|
|
1133
|
+
* @param node Node to modify.
|
|
1134
|
+
* @param value Text to copy (NULL stores an empty value).
|
|
1135
|
+
* @return DCG_OK, or a DCG_ERR_* code.
|
|
1136
|
+
*/
|
|
1137
|
+
static inline int c_dcg_node_set_string(dcg_node* node, const char* value) {
|
|
1138
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
1139
|
+
|
|
1140
|
+
if (node->out.dtype == VAR_TYPE_STRING && node->out.value.as_string) {
|
|
1141
|
+
c_ap_free_owned((void*) node->out.value.as_string);
|
|
1142
|
+
(void) c_dcg_var_init(&node->out);
|
|
1143
|
+
}
|
|
1144
|
+
if (!value) return c_dcg_var_init_string(&node->out, NULL);
|
|
1145
|
+
|
|
1146
|
+
size_t len = strlen(value);
|
|
1147
|
+
char* copy = (char*) c_ap_alloc_child(len + 1, NULL, node);
|
|
1148
|
+
if (!copy) return DCG_ERR_OOM;
|
|
1149
|
+
memcpy(copy, value, len + 1);
|
|
1150
|
+
|
|
1151
|
+
return c_dcg_var_init_string(&node->out, copy);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ========== Eval Hooks ==========
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* @brief Register one eval hook of a node.
|
|
1158
|
+
*
|
|
1159
|
+
* The three hooks of a node share ONE `user_data` - it is a property of the
|
|
1160
|
+
* node, not of the hook - so the first registration sets it and the rest have
|
|
1161
|
+
* to agree: registering with a different pointer is refused rather than quietly
|
|
1162
|
+
* splitting the three hooks of one node across two owners.
|
|
1163
|
+
*
|
|
1164
|
+
* A hook is never replaced by accident either. A slot that already holds one is
|
|
1165
|
+
* refused with DCG_ERR_BUSY, and a node that wants a different set gives the
|
|
1166
|
+
* three back first (c_dcg_node_unregister_eval_hooks) and registers again. A
|
|
1167
|
+
* hook is a function POINTER, and NULL is a hook that is not there - which is
|
|
1168
|
+
* how the evaluator decides whether to call one at all.
|
|
1169
|
+
*
|
|
1170
|
+
* @param node Node to attach the hook to.
|
|
1171
|
+
* @param hook Which of the three (a dcg_node_hook_type).
|
|
1172
|
+
* @param fn The hook to install (never NULL - a hook is given back, not
|
|
1173
|
+
* blanked, which is what unregistering is for).
|
|
1174
|
+
* @param user_data Opaque data the node's three hooks share.
|
|
1175
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG (a NULL node or hook, a hook type that is
|
|
1176
|
+
* not one of the three - reported, since a switch that says nothing
|
|
1177
|
+
* would leave a caller believing its hook was installed - or a
|
|
1178
|
+
* `user_data` that disagrees with the node's), or DCG_ERR_BUSY (a hook
|
|
1179
|
+
* is already installed there).
|
|
1180
|
+
*/
|
|
1181
|
+
static inline int c_dcg_node_register_eval_hook(dcg_node* node, dcg_node_hook_type hook, dcg_node_hook_fn fn, void* user_data) {
|
|
1182
|
+
if (!node || !fn) return DCG_ERR_INVALID_ARG;
|
|
1183
|
+
|
|
1184
|
+
dcg_node_hook_fn* slot = NULL;
|
|
1185
|
+
switch (hook) {
|
|
1186
|
+
case DCG_HOOK_PRE_EVAL:
|
|
1187
|
+
slot = &node->eval_ctx.pre_eval_fn;
|
|
1188
|
+
break;
|
|
1189
|
+
case DCG_HOOK_EVAL:
|
|
1190
|
+
slot = &node->eval_ctx.eval_fn;
|
|
1191
|
+
break;
|
|
1192
|
+
case DCG_HOOK_POST_EVAL:
|
|
1193
|
+
slot = &node->eval_ctx.post_eval_fn;
|
|
1194
|
+
break;
|
|
1195
|
+
default:
|
|
1196
|
+
(void) fprintf(stderr, "c_dcg_node_register_eval_hook: no hook %d on node type %s (0x%04x) at %p - nothing registered\n", (int) hook, c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node);
|
|
1197
|
+
return DCG_ERR_INVALID_ARG;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
if (*slot) return DCG_ERR_BUSY; /* one hook, one install - give the set back to change it */
|
|
1201
|
+
|
|
1202
|
+
/* An empty node has no data yet, so this registration is what sets it; one
|
|
1203
|
+
* that already carries a hook has to agree with what is there. The rule the
|
|
1204
|
+
* node's type gives it is not one of the three and holds no data, so it
|
|
1205
|
+
* neither blocks a registration nor disagrees with one. */
|
|
1206
|
+
bool empty = node->eval_ctx.pre_eval_fn == NULL && node->eval_ctx.eval_fn == NULL && node->eval_ctx.post_eval_fn == NULL;
|
|
1207
|
+
if (!empty && node->eval_ctx.user_data != user_data) return DCG_ERR_INVALID_ARG;
|
|
1208
|
+
|
|
1209
|
+
*slot = fn;
|
|
1210
|
+
node->eval_ctx.user_data = user_data;
|
|
1211
|
+
return DCG_OK;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* @brief Give a node's eval hooks back, and the data they shared.
|
|
1216
|
+
*
|
|
1217
|
+
* The three go together and are released together, because they are one
|
|
1218
|
+
* protocol: a node left carrying a pre hook and no eval hook has lost half of
|
|
1219
|
+
* what it was told to do, and a node left carrying a hook it cannot describe is
|
|
1220
|
+
* worse than one with none. What it is for is changing them - registering is
|
|
1221
|
+
* only allowed where nothing is installed.
|
|
1222
|
+
*
|
|
1223
|
+
* @param node Node to clear (NULL-safe).
|
|
1224
|
+
*/
|
|
1225
|
+
static inline void c_dcg_node_unregister_eval_hooks(dcg_node* node) {
|
|
1226
|
+
if (!node) return;
|
|
1227
|
+
|
|
1228
|
+
node->eval_ctx.pre_eval_fn = NULL;
|
|
1229
|
+
node->eval_ctx.eval_fn = NULL;
|
|
1230
|
+
node->eval_ctx.post_eval_fn = NULL;
|
|
1231
|
+
node->eval_ctx.user_data = NULL; /* the type's own rule is the node's, not an installer's: it stays */
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// ========== Mutation Callbacks ==========
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* @brief Register a mutation observer on a node.
|
|
1238
|
+
*
|
|
1239
|
+
* Observers are a LIST, not a slot: any number of them can watch one node, and
|
|
1240
|
+
* each is handed back its own id to unregister with. Unlike an eval hook, a
|
|
1241
|
+
* mutation callback cannot fail and reports nothing - it is fire-and-forget.
|
|
1242
|
+
*
|
|
1243
|
+
* @param node Node to observe.
|
|
1244
|
+
* @param fn Callback to invoke on every mutation.
|
|
1245
|
+
* @param user_data Opaque data handed back to the callback.
|
|
1246
|
+
* @param out_id Receives the id for unregistration (may be NULL).
|
|
1247
|
+
* @return DCG_OK, or DCG_ERR_INVALID_ARG / DCG_ERR_OOM.
|
|
1248
|
+
*/
|
|
1249
|
+
static inline int c_dcg_node_register_callback(dcg_node* node, dcg_node_callback_fn fn, void* user_data, uintptr_t* out_id) {
|
|
1250
|
+
if (!node || !fn) return DCG_ERR_INVALID_ARG;
|
|
1251
|
+
|
|
1252
|
+
dcg_node_callback_ctx* cb = (dcg_node_callback_ctx*) calloc(1, sizeof(dcg_node_callback_ctx));
|
|
1253
|
+
if (!cb) return DCG_ERR_OOM;
|
|
1254
|
+
|
|
1255
|
+
cb->fn = fn;
|
|
1256
|
+
cb->user_data = user_data;
|
|
1257
|
+
cb->id = (uintptr_t) cb;
|
|
1258
|
+
|
|
1259
|
+
if (!node->callbacks) {
|
|
1260
|
+
node->callbacks = cb;
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
dcg_node_callback_ctx* tail = node->callbacks;
|
|
1264
|
+
while (tail->next) tail = tail->next;
|
|
1265
|
+
tail->next = cb;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (out_id) *out_id = cb->id;
|
|
1269
|
+
return DCG_OK;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* @brief Unregister a mutation observer by its id.
|
|
1274
|
+
*
|
|
1275
|
+
* @param node Node to modify.
|
|
1276
|
+
* @param callback_id Id returned by c_dcg_node_register_callback.
|
|
1277
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_NOT_FOUND.
|
|
1278
|
+
*/
|
|
1279
|
+
static inline int c_dcg_node_unregister_callback(dcg_node* node, uintptr_t callback_id) {
|
|
1280
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
1281
|
+
|
|
1282
|
+
dcg_node_callback_ctx* prev = NULL;
|
|
1283
|
+
dcg_node_callback_ctx* curr = node->callbacks;
|
|
1284
|
+
while (curr) {
|
|
1285
|
+
if (curr->id == callback_id) {
|
|
1286
|
+
if (prev) prev->next = curr->next;
|
|
1287
|
+
else node->callbacks = curr->next;
|
|
1288
|
+
free(curr);
|
|
1289
|
+
return DCG_OK;
|
|
1290
|
+
}
|
|
1291
|
+
prev = curr;
|
|
1292
|
+
curr = curr->next;
|
|
1293
|
+
}
|
|
1294
|
+
return DCG_ERR_NOT_FOUND;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* @brief Notify every registered observer of a mutation.
|
|
1299
|
+
*
|
|
1300
|
+
* The next pointer is snapshotted before each call, so a callback may
|
|
1301
|
+
* unregister itself (or another callback) while being invoked.
|
|
1302
|
+
*
|
|
1303
|
+
* @param node Node whose observers to notify (NULL-safe).
|
|
1304
|
+
* @param event What happened.
|
|
1305
|
+
* @param subject The other node involved (may be NULL).
|
|
1306
|
+
* @param seq_id Id of the mutating caller; pass (uint64_t) -1 for
|
|
1307
|
+
* lifecycle events that have no self-suppression semantics.
|
|
1308
|
+
*/
|
|
1309
|
+
static inline void c_dcg_node_invoke_callbacks(dcg_node* node, dcg_node_event event, dcg_node* subject, uint64_t seq_id) {
|
|
1310
|
+
if (!node || !node->callbacks) return;
|
|
1311
|
+
|
|
1312
|
+
dcg_node_callback_ctx* cb = node->callbacks;
|
|
1313
|
+
while (cb) {
|
|
1314
|
+
dcg_node_callback_ctx* next = cb->next;
|
|
1315
|
+
if (cb->fn) cb->fn(event, node, subject, seq_id, cb->user_data);
|
|
1316
|
+
cb = next;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* @brief How many mutation observers are registered on a node.
|
|
1322
|
+
*
|
|
1323
|
+
* @param node Node to inspect (NULL-safe).
|
|
1324
|
+
* @return The observer count.
|
|
1325
|
+
*/
|
|
1326
|
+
static inline size_t c_dcg_node_callback_count(const dcg_node* node) {
|
|
1327
|
+
if (!node) return 0;
|
|
1328
|
+
size_t count = 0;
|
|
1329
|
+
dcg_node_callback_ctx* cb = node->callbacks;
|
|
1330
|
+
while (cb) {
|
|
1331
|
+
count++;
|
|
1332
|
+
cb = cb->next;
|
|
1333
|
+
}
|
|
1334
|
+
return count;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// ========== Graph Building ==========
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* @brief Infer the edge condition of a child that is about to be appended.
|
|
1341
|
+
*
|
|
1342
|
+
* This is the bake port of the capi's c_infer_condition(): the rule reads
|
|
1343
|
+
* the parent's EXISTING branches - the node's dtype plays no part in it.
|
|
1344
|
+
*
|
|
1345
|
+
* - no children yet -> TRUE (the first branch is always TRUE)
|
|
1346
|
+
* - one binary child -> the opposite binary
|
|
1347
|
+
* - one else child -> TRUE, when that child is auto-generated
|
|
1348
|
+
* - one other child -> unresolved
|
|
1349
|
+
* - two or more children -> the newest auto-generated child's condition,
|
|
1350
|
+
* so the builder can find and replace the edge
|
|
1351
|
+
* it created itself; otherwise unresolved
|
|
1352
|
+
*
|
|
1353
|
+
* @param parent Parent the edge will be attached to (NULL-safe).
|
|
1354
|
+
* @return The inferred condition, or NULL when nothing can be inferred.
|
|
1355
|
+
*/
|
|
1356
|
+
static inline const dcg_node_edge_condition* c_dcg_node_infer_condition(const dcg_node* parent) {
|
|
1357
|
+
size_t count = c_dcg_node_child_count(parent);
|
|
1358
|
+
dcg_node* last = c_dcg_node_last_child(parent);
|
|
1359
|
+
|
|
1360
|
+
if (count == 0) return DCG_TRUE_CONDITION;
|
|
1361
|
+
|
|
1362
|
+
if (count == 1 && last) {
|
|
1363
|
+
const dcg_node_edge_condition* last_condition = last->condition_to_parent;
|
|
1364
|
+
if (c_dcg_condition_is_binary(last_condition)) {
|
|
1365
|
+
return c_dcg_condition_is_true(last_condition) ? DCG_FALSE_CONDITION : DCG_TRUE_CONDITION;
|
|
1366
|
+
}
|
|
1367
|
+
if (c_dcg_condition_is_else(last_condition) && last->autogen) return DCG_TRUE_CONDITION;
|
|
1368
|
+
return NULL;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
if (last && last->autogen) return last->condition_to_parent;
|
|
1372
|
+
dcg_node* second = last ? last->prev_sibling : NULL;
|
|
1373
|
+
if (second && second->autogen) return second->condition_to_parent;
|
|
1374
|
+
return NULL;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
/**
|
|
1378
|
+
* @brief Link a child into a parent's child list, applying every edge rule.
|
|
1379
|
+
*
|
|
1380
|
+
* Shared by c_dcg_node_append() and c_dcg_node_append_at(). The condition
|
|
1381
|
+
* is resolved (AUTO inference), the root's unary/unconditioned rule is
|
|
1382
|
+
* enforced, and one edge per condition is guaranteed - the capi keys its
|
|
1383
|
+
* children dict by condition, so a duplicate edge is a build error there
|
|
1384
|
+
* and here alike.
|
|
1385
|
+
*
|
|
1386
|
+
* @param parent Parent to link into.
|
|
1387
|
+
* @param child Child to link (must be parentless).
|
|
1388
|
+
* @param condition Requested edge condition (never NULL).
|
|
1389
|
+
* @param anchor Insert before this child, or NULL to append at the tail.
|
|
1390
|
+
* @return DCG_OK or a DCG_ERR_* code.
|
|
1391
|
+
*/
|
|
1392
|
+
static inline int c_dcg_node_link(dcg_node* parent, dcg_node* child, const dcg_node_edge_condition* condition, dcg_node* anchor) {
|
|
1393
|
+
if (parent == child) return DCG_ERR_CYCLE;
|
|
1394
|
+
if (child->parent) return DCG_ERR_BUSY;
|
|
1395
|
+
if (parent->flags & DCG_NODE_FLAG_FROZEN) return DCG_ERR_BUSY;
|
|
1396
|
+
if (c_dcg_node_is_ancestor(child, parent)) return DCG_ERR_CYCLE;
|
|
1397
|
+
|
|
1398
|
+
if (parent->ntype == DCG_NODE_ROOT) {
|
|
1399
|
+
// A root is strictly unary (one entry point) and strictly unconditioned.
|
|
1400
|
+
if (parent->children) return DCG_ERR_TYPE;
|
|
1401
|
+
if (!c_dcg_condition_is_none(condition) && !c_dcg_condition_is_auto(condition)) return DCG_ERR_EDGE;
|
|
1402
|
+
condition = DCG_NO_CONDITION;
|
|
1403
|
+
}
|
|
1404
|
+
else if (c_dcg_condition_is_auto(condition)) {
|
|
1405
|
+
condition = c_dcg_node_infer_condition(parent);
|
|
1406
|
+
if (!condition) return DCG_ERR_UNRESOLVED;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (c_dcg_node_child_by_condition(parent, condition)) return DCG_ERR_DUPLICATE;
|
|
1410
|
+
|
|
1411
|
+
// An else branch is the fallback, so it is always the last child: nothing
|
|
1412
|
+
// may be appended behind it, and it can never be inserted in front of
|
|
1413
|
+
// another child. Inserting a regular branch somewhere before a trailing
|
|
1414
|
+
// else is fine - the else stays last.
|
|
1415
|
+
if (!anchor && c_dcg_node_child_by_condition(parent, DCG_ELSE_CONDITION)) return DCG_ERR_EDGE;
|
|
1416
|
+
if (anchor && c_dcg_condition_is_else(condition)) return DCG_ERR_EDGE;
|
|
1417
|
+
|
|
1418
|
+
child->parent = parent;
|
|
1419
|
+
child->condition_to_parent = condition;
|
|
1420
|
+
c_dcg_node_adopt_condition(child, condition); // a standalone condition dies with its child
|
|
1421
|
+
|
|
1422
|
+
if (anchor) {
|
|
1423
|
+
child->next_sibling = anchor;
|
|
1424
|
+
child->prev_sibling = anchor->prev_sibling;
|
|
1425
|
+
if (child->prev_sibling) child->prev_sibling->next_sibling = child;
|
|
1426
|
+
else parent->children = child;
|
|
1427
|
+
anchor->prev_sibling = child;
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
child->prev_sibling = c_dcg_node_last_child(parent);
|
|
1431
|
+
child->next_sibling = NULL;
|
|
1432
|
+
if (child->prev_sibling) child->prev_sibling->next_sibling = child;
|
|
1433
|
+
else parent->children = child;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
c_dcg_node_invoke_callbacks(parent, DCG_NODE_EVENT_CHILD_ADDED, child, (uint64_t) -1);
|
|
1437
|
+
return DCG_OK;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* @brief Append a child at the tail of a node's child list.
|
|
1442
|
+
*
|
|
1443
|
+
* DCG_AUTO_CONDITION is resolved by inference over the parent's existing
|
|
1444
|
+
* branches (see c_dcg_node_infer_condition) before it is stored; every
|
|
1445
|
+
* other condition is used verbatim. A NULL condition is rejected - the
|
|
1446
|
+
* capi makes the same call, refusing to guess an edge.
|
|
1447
|
+
*
|
|
1448
|
+
* Rejected: a NULL argument, a child that already has a parent (detach it
|
|
1449
|
+
* first), a child that is the parent or one of its ancestors (a cycle), a
|
|
1450
|
+
* condition already registered on this parent, an else branch registered
|
|
1451
|
+
* anywhere but last, a root that already has its entry child, a condition
|
|
1452
|
+
* a root may not carry, an edge that cannot be inferred, and any mutation
|
|
1453
|
+
* of a FROZEN (baked) node.
|
|
1454
|
+
*
|
|
1455
|
+
* @param parent Node to append to.
|
|
1456
|
+
* @param child Node to append (must be currently parentless).
|
|
1457
|
+
* @param condition Edge condition, never NULL. A standalone (allocator
|
|
1458
|
+
* block) condition is adopted by the child and dies with
|
|
1459
|
+
* it; the built-ins and borrowed conditions are not.
|
|
1460
|
+
* @return DCG_OK, or DCG_ERR_INVALID_ARG / BUSY / CYCLE / DUPLICATE / EDGE /
|
|
1461
|
+
* TYPE / UNRESOLVED.
|
|
1462
|
+
*/
|
|
1463
|
+
static inline int c_dcg_node_append(dcg_node* parent, dcg_node* child, const dcg_node_edge_condition* condition) {
|
|
1464
|
+
if (!parent || !child || !condition) return DCG_ERR_INVALID_ARG;
|
|
1465
|
+
return c_dcg_node_link(parent, child, condition, NULL);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* @brief Append a child, letting the parent infer the branch condition.
|
|
1470
|
+
*
|
|
1471
|
+
* Equivalent to c_dcg_node_append(parent, child, NULL) for a non-boolean
|
|
1472
|
+
* parent and to the AUTO inference for a boolean one.
|
|
1473
|
+
*
|
|
1474
|
+
* @param parent Node to append to.
|
|
1475
|
+
* @param child Node to append (must be currently parentless).
|
|
1476
|
+
* @return DCG_OK or a DCG_ERR_* code.
|
|
1477
|
+
*/
|
|
1478
|
+
static inline int c_dcg_node_append_auto(dcg_node* parent, dcg_node* child) {
|
|
1479
|
+
return c_dcg_node_append(parent, child, DCG_AUTO_CONDITION);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* @brief Insert a child at a given position of a node's child list.
|
|
1484
|
+
*
|
|
1485
|
+
* Applies the same rules as c_dcg_node_append and inserts at `index`
|
|
1486
|
+
* instead of the tail; an index at or past the end appends. The else
|
|
1487
|
+
* ordering rule makes an insert behind an else branch impossible, and an
|
|
1488
|
+
* else branch itself can only ever be appended at the tail.
|
|
1489
|
+
*
|
|
1490
|
+
* @param parent Node to insert into.
|
|
1491
|
+
* @param child Node to insert (must be currently parentless).
|
|
1492
|
+
* @param condition Edge condition, never NULL.
|
|
1493
|
+
* @param index Position to insert at; >= child_count appends.
|
|
1494
|
+
* @return DCG_OK or a DCG_ERR_* code.
|
|
1495
|
+
*/
|
|
1496
|
+
static inline int c_dcg_node_append_at(dcg_node* parent, dcg_node* child, const dcg_node_edge_condition* condition, size_t index) {
|
|
1497
|
+
if (!parent || !child || !condition) return DCG_ERR_INVALID_ARG;
|
|
1498
|
+
return c_dcg_node_link(parent, child, condition, c_dcg_node_child_at(parent, index));
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* @brief Insert a child at a position, with a boolean branch edge.
|
|
1503
|
+
*
|
|
1504
|
+
* The convenience form of c_dcg_node_append_at() for the common two-way case:
|
|
1505
|
+
* the condition is the branch itself, not a condition object.
|
|
1506
|
+
*
|
|
1507
|
+
* @param parent Node to insert into.
|
|
1508
|
+
* @param child Node to insert (must be currently parentless).
|
|
1509
|
+
* @param condition true inserts under DCG_TRUE_CONDITION, false under DCG_FALSE_CONDITION.
|
|
1510
|
+
* @param index Position to insert at; >= child_count appends.
|
|
1511
|
+
* @return DCG_OK or a DCG_ERR_* code.
|
|
1512
|
+
*/
|
|
1513
|
+
static inline int c_dcg_node_append_at_binary(dcg_node* parent, dcg_node* child, bool condition, size_t index) {
|
|
1514
|
+
return c_dcg_node_append_at(parent, child, condition ? DCG_TRUE_CONDITION : DCG_FALSE_CONDITION, index);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* @brief Unlink a node from its parent, keeping its own subtree intact.
|
|
1519
|
+
*
|
|
1520
|
+
* The node keeps its subtree but loses its edge: the condition falls back
|
|
1521
|
+
* to NO_CONDITION, exactly as a displaced node ends up in the capi.
|
|
1522
|
+
*
|
|
1523
|
+
* @param node Node to unlink (NULL-safe: a parentless node is a no-op).
|
|
1524
|
+
* @return DCG_OK, or DCG_ERR_BUSY when the parent is FROZEN.
|
|
1525
|
+
*/
|
|
1526
|
+
static inline int c_dcg_node_detach(dcg_node* node) {
|
|
1527
|
+
if (!node || !node->parent) return DCG_OK;
|
|
1528
|
+
if (node->parent->flags & DCG_NODE_FLAG_FROZEN) return DCG_ERR_BUSY;
|
|
1529
|
+
|
|
1530
|
+
dcg_node* parent = node->parent;
|
|
1531
|
+
c_dcg_node_unlink(node);
|
|
1532
|
+
c_dcg_node_invoke_callbacks(parent, DCG_NODE_EVENT_CHILD_REMOVED, node, (uint64_t) -1);
|
|
1533
|
+
return DCG_OK;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* @brief Put a node into the slot of another one, inheriting its edge.
|
|
1538
|
+
*
|
|
1539
|
+
* The replacement takes over the old node's position and condition; the old
|
|
1540
|
+
* node is detached but NOT freed (its subtree stays alive), so the caller
|
|
1541
|
+
* decides what happens to it.
|
|
1542
|
+
*
|
|
1543
|
+
* @param old_node Node currently in the graph.
|
|
1544
|
+
* @param new_node Node to put in its place (must be parentless).
|
|
1545
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_BUSY or DCG_ERR_CYCLE.
|
|
1546
|
+
*/
|
|
1547
|
+
static inline int c_dcg_node_replace(dcg_node* old_node, dcg_node* new_node) {
|
|
1548
|
+
if (!old_node || !new_node) return DCG_ERR_INVALID_ARG;
|
|
1549
|
+
if (old_node == new_node) return DCG_OK;
|
|
1550
|
+
if (new_node->parent) return DCG_ERR_BUSY;
|
|
1551
|
+
if (c_dcg_node_is_ancestor(old_node, new_node)) return DCG_ERR_CYCLE;
|
|
1552
|
+
|
|
1553
|
+
dcg_node* parent = old_node->parent;
|
|
1554
|
+
if (!parent) return DCG_ERR_INVALID_ARG;
|
|
1555
|
+
|
|
1556
|
+
const dcg_node_edge_condition* condition = old_node->condition_to_parent;
|
|
1557
|
+
dcg_node* anchor = old_node->next_sibling;
|
|
1558
|
+
|
|
1559
|
+
int ret = c_dcg_node_detach(old_node);
|
|
1560
|
+
if (ret != DCG_OK) return ret;
|
|
1561
|
+
|
|
1562
|
+
// The slot is reused as-is, so the else-ordering rule cannot be violated
|
|
1563
|
+
// here: link straight into the anchor instead of going through append_at.
|
|
1564
|
+
// link() adopts the condition onto the replacement, so an owned condition
|
|
1565
|
+
// travels with the edge instead of dying with the displaced node.
|
|
1566
|
+
return c_dcg_node_link(parent, new_node, condition, anchor);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* @brief Put a node into another's slot when a parent elsewhere already holds it.
|
|
1571
|
+
*
|
|
1572
|
+
* c_dcg_node_replace() refuses a replacement that has a parent, and in a tree
|
|
1573
|
+
* that is right: one node, one parent. A control-flow JOIN is the exception.
|
|
1574
|
+
* The node a breakpoint resumes into is reached from the breakpoint AND goes on
|
|
1575
|
+
* being the branch it was entered as, so it has to be placed here while the
|
|
1576
|
+
* parent that handed it over keeps its pointer to it - which is what the capi
|
|
1577
|
+
* does when it re-parents the node a breakpoint just connected to.
|
|
1578
|
+
*
|
|
1579
|
+
* What moves is the TREE link, not the list entry: the node's `parent` becomes
|
|
1580
|
+
* the node it displaces, while its former parent goes on naming it as a child.
|
|
1581
|
+
* That former parent is then a second entry point to the same block - a walk
|
|
1582
|
+
* reaches it twice and frees it once, which is what c_dcg_node_teardown_root()
|
|
1583
|
+
* is built to survive.
|
|
1584
|
+
*
|
|
1585
|
+
* It is representable only while the node is ALONE in the list it is leaving,
|
|
1586
|
+
* because the sibling links belong to the list being entered: a node with
|
|
1587
|
+
* neighbours still attached would leave them pointing into a chain it is no
|
|
1588
|
+
* longer in. A node that is not alone is therefore refused rather than
|
|
1589
|
+
* silently corrupting the walk that reads those links.
|
|
1590
|
+
*
|
|
1591
|
+
* @param old_node Node currently in the graph.
|
|
1592
|
+
* @param new_node Node to put in its place (alone in its own list, if it has one).
|
|
1593
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_BUSY or DCG_ERR_CYCLE.
|
|
1594
|
+
*/
|
|
1595
|
+
static inline int c_dcg_node_replace_shared(dcg_node* old_node, dcg_node* new_node) {
|
|
1596
|
+
if (!old_node || !new_node) return DCG_ERR_INVALID_ARG;
|
|
1597
|
+
if (!old_node->parent) return DCG_ERR_INVALID_ARG;
|
|
1598
|
+
if (new_node->next_sibling || new_node->prev_sibling) return DCG_ERR_BUSY; /* not alone in its own list */
|
|
1599
|
+
|
|
1600
|
+
dcg_node* parent = old_node->parent;
|
|
1601
|
+
const dcg_node_edge_condition* condition = old_node->condition_to_parent;
|
|
1602
|
+
|
|
1603
|
+
/*
|
|
1604
|
+
* Shed the tree link, keep the list entry: the former parent is what makes
|
|
1605
|
+
* this a join, and dropping its pointer would make it nothing.
|
|
1606
|
+
*
|
|
1607
|
+
* The join lands at the END of this parent's list rather than in the slot
|
|
1608
|
+
* the displaced node held. A node carries one sibling chain, and its former
|
|
1609
|
+
* parent walks that chain to reach it - landing it mid-list would hand that
|
|
1610
|
+
* parent the rest of this list to walk into, and from there back round to
|
|
1611
|
+
* itself. At the end, the chain it walks stops where it should.
|
|
1612
|
+
*/
|
|
1613
|
+
new_node->parent = NULL;
|
|
1614
|
+
|
|
1615
|
+
int ret = c_dcg_node_detach(old_node);
|
|
1616
|
+
if (ret != DCG_OK) return ret;
|
|
1617
|
+
|
|
1618
|
+
return c_dcg_node_append(parent, new_node, condition);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* @brief Allocate an auto-generated placeholder.
|
|
1623
|
+
*
|
|
1624
|
+
* A placeholder is what an unfinished branch leaves behind. It is a plain node,
|
|
1625
|
+
* not an action: it carries none of the fields the action family's struct exists
|
|
1626
|
+
* for - no signal, no payload, no connect flag - and it is flagged autogen so
|
|
1627
|
+
* consolidation can tell it from a node the caller built.
|
|
1628
|
+
*
|
|
1629
|
+
* The opening half of the placeholder discipline: a branch reserves its slot
|
|
1630
|
+
* with this and fills it later, and whatever is still a placeholder when the
|
|
1631
|
+
* graph closes is retired by c_dcg_node_consolidate_placeholder below.
|
|
1632
|
+
*
|
|
1633
|
+
* @param allocator Allocator for the block; NULL falls back to the plain heap.
|
|
1634
|
+
* @return The node, or NULL on OOM.
|
|
1635
|
+
*/
|
|
1636
|
+
static inline dcg_node* c_dcg_node_new_placeholder(allocator_protocol* allocator) {
|
|
1637
|
+
dcg_node* node = c_dcg_node_new(DCG_NODE_PLACEHOLDER, DCG_DEF_REPR_PLACEHOLDER, allocator);
|
|
1638
|
+
if (!node) return NULL;
|
|
1639
|
+
|
|
1640
|
+
node->autogen = true;
|
|
1641
|
+
|
|
1642
|
+
/* A stand-in is an action like any other, so it stands for itself from the
|
|
1643
|
+
* moment it exists - which is what a branch built over it reads before the
|
|
1644
|
+
* build has put anything there (see c_dcg_node_new_action). */
|
|
1645
|
+
if (c_dcg_var_init_node(&node->out, node) != DCG_OK) {
|
|
1646
|
+
c_dcg_node_free(node);
|
|
1647
|
+
return NULL;
|
|
1648
|
+
}
|
|
1649
|
+
return node;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* @brief Turn every placeholder of a subtree into an auto-generated no-action.
|
|
1654
|
+
*
|
|
1655
|
+
* The closing half of the placeholder discipline: the builder reserves a
|
|
1656
|
+
* branch with a placeholder and fills it later, so whatever is STILL a
|
|
1657
|
+
* placeholder when the graph closes becomes a NoAction leaf - a baked graph
|
|
1658
|
+
* never contains a placeholder.
|
|
1659
|
+
*
|
|
1660
|
+
* The conversion happens in place: the node keeps its slot, its edge, its
|
|
1661
|
+
* labels, its hooks and whatever subtree it grew, and only its type and its
|
|
1662
|
+
* display text change (reported as a MODIFIED mutation). The retitle is the
|
|
1663
|
+
* one step that allocates, so a node whose new text cannot be had keeps the
|
|
1664
|
+
* text it had and says so on stderr; the type has already flipped either way,
|
|
1665
|
+
* which is the structural half. A placeholder that was left holding children
|
|
1666
|
+
* keeps them - and validation will say so, which is the honest report of a
|
|
1667
|
+
* builder that never filled the slot properly.
|
|
1668
|
+
*
|
|
1669
|
+
* The walk is depth-first over the whole subtree and idempotent, so it can
|
|
1670
|
+
* be called once at the root or at every level like the capi does.
|
|
1671
|
+
*
|
|
1672
|
+
* @param node Subtree to consolidate (NULL-safe).
|
|
1673
|
+
* @return Number of placeholders converted.
|
|
1674
|
+
*/
|
|
1675
|
+
static inline size_t c_dcg_node_consolidate_placeholder(dcg_node* node) {
|
|
1676
|
+
if (!node) return 0;
|
|
1677
|
+
|
|
1678
|
+
size_t replaced = 0;
|
|
1679
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
1680
|
+
replaced += c_dcg_node_consolidate_placeholder(child);
|
|
1681
|
+
|
|
1682
|
+
if (child->ntype == DCG_NODE_PLACEHOLDER) {
|
|
1683
|
+
child->ntype = DCG_NODE_NOACTION;
|
|
1684
|
+
child->autogen = true;
|
|
1685
|
+
if (c_dcg_node_set_repr(child, DCG_DEF_REPR_NOACTION) != DCG_OK) {
|
|
1686
|
+
(void) fprintf(stderr, "c_dcg_node_consolidate_placeholder: no text for the no-action at %p - keeping \"%s\"\n", (void*) child, child->repr ? child->repr : "");
|
|
1687
|
+
}
|
|
1688
|
+
/* The conversion is a change to the PARENT's child, so the parent is
|
|
1689
|
+
* told: the edge still holds a node, but it is no longer a
|
|
1690
|
+
* placeholder, and a wrapper of the parent has to rebuild it as what
|
|
1691
|
+
* it now is. The converted node cannot carry that news itself - a
|
|
1692
|
+
* node the C layer grew has no wrapper and no callback to hear a
|
|
1693
|
+
* MODIFIED. */
|
|
1694
|
+
c_dcg_node_invoke_callbacks(node, DCG_NODE_EVENT_CHILD_UPDATED, child, (uint64_t) -1);
|
|
1695
|
+
c_dcg_node_invoke_callbacks(child, DCG_NODE_EVENT_MODIFIED, child, (uint64_t) -1);
|
|
1696
|
+
replaced++;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return replaced;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* @brief The placeholder slot under a node: the one already reserved, or a new one.
|
|
1704
|
+
*
|
|
1705
|
+
* The opening half of the placeholder discipline - the closing half is
|
|
1706
|
+
* c_dcg_node_consolidate_placeholder(). A branch that is about to be built
|
|
1707
|
+
* reserves its slot first, and whatever fills the slot replaces the
|
|
1708
|
+
* placeholder; a slot nothing ever fills becomes a no-action when the graph
|
|
1709
|
+
* closes.
|
|
1710
|
+
*
|
|
1711
|
+
* The rules:
|
|
1712
|
+
*
|
|
1713
|
+
* - the FIRST standing placeholder is reused, so a build that descends into a
|
|
1714
|
+
* node fills the slot that was reserved first rather than reserving a
|
|
1715
|
+
* second. First and not last, because a node entered by a `with` block
|
|
1716
|
+
* reserves its arms in the order they are read - TRUE then FALSE - and the
|
|
1717
|
+
* arm taken first is the one the evaluator would test first. Taking the
|
|
1718
|
+
* last one here would fill the fallback first and leave the pair in the
|
|
1719
|
+
* order the read does not use;
|
|
1720
|
+
* - otherwise a placeholder is appended on the INFERRED edge, which is what
|
|
1721
|
+
* makes this fail exactly when the node has no room left for a branch -
|
|
1722
|
+
* a node with one non-binary child and no free edge, say. A root is no
|
|
1723
|
+
* exception: the inference hands back the unconditioned edge it requires.
|
|
1724
|
+
*
|
|
1725
|
+
* The placeholder is allocated from the same allocator the node came from, so
|
|
1726
|
+
* the two are one allocation family; it is NOT nested under the node, because
|
|
1727
|
+
* a node does not own its children (see c_dcg_node_free).
|
|
1728
|
+
*
|
|
1729
|
+
* @param node Node to reserve a slot under (NULL-safe).
|
|
1730
|
+
* @return The placeholder, or NULL on OOM / when no edge can be inferred.
|
|
1731
|
+
*/
|
|
1732
|
+
static inline dcg_node* c_dcg_node_get_placeholder(dcg_node* node) {
|
|
1733
|
+
if (!node) return NULL;
|
|
1734
|
+
|
|
1735
|
+
dcg_node* standing = NULL;
|
|
1736
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
1737
|
+
if (child->ntype == DCG_NODE_PLACEHOLDER) {
|
|
1738
|
+
standing = child;
|
|
1739
|
+
break;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (standing) return standing;
|
|
1743
|
+
|
|
1744
|
+
dcg_node* placeholder = c_dcg_node_new_placeholder(c_ap_protocol_from_ptr(node));
|
|
1745
|
+
if (!placeholder) return NULL;
|
|
1746
|
+
|
|
1747
|
+
if (c_dcg_node_append_auto(node, placeholder) != DCG_OK) {
|
|
1748
|
+
c_dcg_node_free(placeholder);
|
|
1749
|
+
return NULL;
|
|
1750
|
+
}
|
|
1751
|
+
return placeholder;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// ========== Queries ==========
|
|
1755
|
+
|
|
1756
|
+
/**
|
|
1757
|
+
* @brief Number of direct children.
|
|
1758
|
+
*
|
|
1759
|
+
* @param node Node to inspect (NULL-safe).
|
|
1760
|
+
* @return The child count.
|
|
1761
|
+
*/
|
|
1762
|
+
static inline size_t c_dcg_node_child_count(const dcg_node* node) {
|
|
1763
|
+
if (!node) return 0;
|
|
1764
|
+
size_t count = 0;
|
|
1765
|
+
dcg_node* child = node->children;
|
|
1766
|
+
while (child) {
|
|
1767
|
+
count++;
|
|
1768
|
+
child = child->next_sibling;
|
|
1769
|
+
}
|
|
1770
|
+
return count;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* @brief First child of a node.
|
|
1775
|
+
*
|
|
1776
|
+
* @param node Node to inspect (NULL-safe).
|
|
1777
|
+
* @return The first child, or NULL for a leaf.
|
|
1778
|
+
*/
|
|
1779
|
+
static inline dcg_node* c_dcg_node_first_child(const dcg_node* node) {
|
|
1780
|
+
if (!node) return NULL;
|
|
1781
|
+
return node->children;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
/**
|
|
1785
|
+
* @brief Last child of a node.
|
|
1786
|
+
*
|
|
1787
|
+
* @param node Node to inspect (NULL-safe).
|
|
1788
|
+
* @return The last child, or NULL for a leaf.
|
|
1789
|
+
*/
|
|
1790
|
+
static inline dcg_node* c_dcg_node_last_child(const dcg_node* node) {
|
|
1791
|
+
if (!node) return NULL;
|
|
1792
|
+
dcg_node* child = node->children;
|
|
1793
|
+
if (!child) return NULL;
|
|
1794
|
+
while (child->next_sibling) child = child->next_sibling;
|
|
1795
|
+
return child;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
/**
|
|
1799
|
+
* @brief Child at a position.
|
|
1800
|
+
*
|
|
1801
|
+
* @param node Node to inspect (NULL-safe).
|
|
1802
|
+
* @param index Zero-based position.
|
|
1803
|
+
* @return The child, or NULL when out of range.
|
|
1804
|
+
*/
|
|
1805
|
+
static inline dcg_node* c_dcg_node_child_at(const dcg_node* node, size_t index) {
|
|
1806
|
+
if (!node) return NULL;
|
|
1807
|
+
dcg_node* child = node->children;
|
|
1808
|
+
while (child && index > 0) {
|
|
1809
|
+
child = child->next_sibling;
|
|
1810
|
+
index--;
|
|
1811
|
+
}
|
|
1812
|
+
return child;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
1816
|
+
* @brief Position of a node among its siblings.
|
|
1817
|
+
*
|
|
1818
|
+
* @param node Node to inspect (NULL-safe).
|
|
1819
|
+
* @return The zero-based index, or -1 for the root (a node without a parent).
|
|
1820
|
+
*/
|
|
1821
|
+
static inline ssize_t c_dcg_node_child_index(const dcg_node* node) {
|
|
1822
|
+
if (!node || !node->parent) return -1;
|
|
1823
|
+
|
|
1824
|
+
ssize_t index = 0;
|
|
1825
|
+
dcg_node* child = node->parent->children;
|
|
1826
|
+
while (child) {
|
|
1827
|
+
if (child == node) return index;
|
|
1828
|
+
index++;
|
|
1829
|
+
child = child->next_sibling;
|
|
1830
|
+
}
|
|
1831
|
+
return -1;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* @brief First child registered under a given edge condition.
|
|
1836
|
+
*
|
|
1837
|
+
* Conditions compare by identity for the built-ins and by payload for the
|
|
1838
|
+
* rest (see c_dcg_condition_equals), which is what backs both the capi's
|
|
1839
|
+
* condition-keyed children dict and the one-edge-per-condition rule.
|
|
1840
|
+
*
|
|
1841
|
+
* @param node Node to inspect (NULL-safe).
|
|
1842
|
+
* @param condition Condition to look for (NULL = the unconditional edge).
|
|
1843
|
+
* @return The child, or NULL when there is none.
|
|
1844
|
+
*/
|
|
1845
|
+
static inline dcg_node* c_dcg_node_child_by_condition(const dcg_node* node, const dcg_node_edge_condition* condition) {
|
|
1846
|
+
if (!node) return NULL;
|
|
1847
|
+
dcg_node* child = node->children;
|
|
1848
|
+
while (child) {
|
|
1849
|
+
if (c_dcg_condition_equals(child->condition_to_parent, condition)) return child;
|
|
1850
|
+
child = child->next_sibling;
|
|
1851
|
+
}
|
|
1852
|
+
return NULL;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* @brief Next sibling of a node.
|
|
1857
|
+
*
|
|
1858
|
+
* @param node Node to inspect (NULL-safe).
|
|
1859
|
+
* @return The next sibling, or NULL.
|
|
1860
|
+
*/
|
|
1861
|
+
static inline dcg_node* c_dcg_node_next_sibling(const dcg_node* node) {
|
|
1862
|
+
if (!node) return NULL;
|
|
1863
|
+
return node->next_sibling;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* @brief Previous sibling of a node.
|
|
1868
|
+
*
|
|
1869
|
+
* @param node Node to inspect (NULL-safe).
|
|
1870
|
+
* @return The previous sibling, or NULL.
|
|
1871
|
+
*/
|
|
1872
|
+
static inline dcg_node* c_dcg_node_prev_sibling(const dcg_node* node) {
|
|
1873
|
+
if (!node) return NULL;
|
|
1874
|
+
return node->prev_sibling;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/**
|
|
1878
|
+
* @brief Walk up to the topmost ancestor - the root of the graph.
|
|
1879
|
+
*
|
|
1880
|
+
* @param node Node to start from (NULL-safe).
|
|
1881
|
+
* @return The root, or NULL.
|
|
1882
|
+
*/
|
|
1883
|
+
static inline dcg_node* c_dcg_node_root(dcg_node* node) {
|
|
1884
|
+
if (!node) return NULL;
|
|
1885
|
+
while (node->parent) node = node->parent;
|
|
1886
|
+
return node;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* @brief Number of edges between a node and its root.
|
|
1891
|
+
*
|
|
1892
|
+
* @param node Node to inspect (NULL-safe).
|
|
1893
|
+
* @return The depth (0 for a root).
|
|
1894
|
+
*/
|
|
1895
|
+
static inline size_t c_dcg_node_depth(const dcg_node* node) {
|
|
1896
|
+
size_t depth = 0;
|
|
1897
|
+
while (node && node->parent) {
|
|
1898
|
+
depth++;
|
|
1899
|
+
node = node->parent;
|
|
1900
|
+
}
|
|
1901
|
+
return depth;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
/**
|
|
1905
|
+
* @brief Longest downward path from a node to one of its leaves.
|
|
1906
|
+
*
|
|
1907
|
+
* @param node Node to inspect (NULL-safe).
|
|
1908
|
+
* @return The height (0 for a leaf).
|
|
1909
|
+
*/
|
|
1910
|
+
static inline size_t c_dcg_node_height(const dcg_node* node) {
|
|
1911
|
+
if (!node || !node->children) return 0;
|
|
1912
|
+
|
|
1913
|
+
size_t best = 0;
|
|
1914
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
1915
|
+
size_t h = c_dcg_node_height(child);
|
|
1916
|
+
if (h > best) best = h;
|
|
1917
|
+
}
|
|
1918
|
+
return best + 1;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
/**
|
|
1922
|
+
* @brief Number of nodes in the subtree rooted at a node (the node included).
|
|
1923
|
+
*
|
|
1924
|
+
* @param node Node to inspect (NULL-safe).
|
|
1925
|
+
* @return The subtree size.
|
|
1926
|
+
*/
|
|
1927
|
+
static inline size_t c_dcg_node_subtree_size(const dcg_node* node) {
|
|
1928
|
+
if (!node) return 0;
|
|
1929
|
+
size_t size = 1;
|
|
1930
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
1931
|
+
size += c_dcg_node_subtree_size(child);
|
|
1932
|
+
}
|
|
1933
|
+
return size;
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
/**
|
|
1937
|
+
* @brief Number of leaves in the subtree rooted at a node.
|
|
1938
|
+
*
|
|
1939
|
+
* @param node Node to inspect (NULL-safe).
|
|
1940
|
+
* @return The leaf count (a childless node counts as one leaf).
|
|
1941
|
+
*/
|
|
1942
|
+
static inline size_t c_dcg_node_leaf_count(const dcg_node* node) {
|
|
1943
|
+
if (!node) return 0;
|
|
1944
|
+
if (!node->children) return 1;
|
|
1945
|
+
|
|
1946
|
+
size_t count = 0;
|
|
1947
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
1948
|
+
count += c_dcg_node_leaf_count(child);
|
|
1949
|
+
}
|
|
1950
|
+
return count;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
/**
|
|
1954
|
+
* @brief Predicate: does the node have no children?
|
|
1955
|
+
*
|
|
1956
|
+
* @param node Node to inspect (NULL-safe).
|
|
1957
|
+
* @return true for a leaf.
|
|
1958
|
+
*/
|
|
1959
|
+
static inline bool c_dcg_node_is_leaf(const dcg_node* node) {
|
|
1960
|
+
return node && node->children == NULL;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
/**
|
|
1964
|
+
* @brief Predicate: is the node parentless?
|
|
1965
|
+
*
|
|
1966
|
+
* @param node Node to inspect (NULL-safe).
|
|
1967
|
+
* @return true for the root.
|
|
1968
|
+
*/
|
|
1969
|
+
static inline bool c_dcg_node_is_root(const dcg_node* node) {
|
|
1970
|
+
return node && node->parent == NULL;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* @brief Predicate: is one node an ancestor of another?
|
|
1975
|
+
*
|
|
1976
|
+
* @param node Candidate ancestor (NULL-safe).
|
|
1977
|
+
* @param descendant Candidate descendant (NULL-safe).
|
|
1978
|
+
* @return true when descendant sits anywhere below node.
|
|
1979
|
+
*/
|
|
1980
|
+
static inline bool c_dcg_node_is_ancestor(const dcg_node* node, const dcg_node* descendant) {
|
|
1981
|
+
if (!node || !descendant) return false;
|
|
1982
|
+
for (const dcg_node* walk = descendant->parent; walk; walk = walk->parent) {
|
|
1983
|
+
if (walk == node) return true;
|
|
1984
|
+
}
|
|
1985
|
+
return false;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
/**
|
|
1989
|
+
* @brief Depth-first search for the first node carrying a uid.
|
|
1990
|
+
*
|
|
1991
|
+
* @param root Subtree to search (NULL-safe).
|
|
1992
|
+
* @param uid Identity to look for (16 bytes).
|
|
1993
|
+
* @return The node, or NULL when there is none.
|
|
1994
|
+
*/
|
|
1995
|
+
static inline dcg_node* c_dcg_node_find_by_uid(const dcg_node* root, const uuid_t uid) {
|
|
1996
|
+
if (!root || !uid) return NULL;
|
|
1997
|
+
if (memcmp(root->uid, uid, sizeof(uuid_t)) == 0) return (dcg_node*) root;
|
|
1998
|
+
|
|
1999
|
+
for (dcg_node* child = root->children; child; child = child->next_sibling) {
|
|
2000
|
+
dcg_node* found = c_dcg_node_find_by_uid(child, uid);
|
|
2001
|
+
if (found) return found;
|
|
2002
|
+
}
|
|
2003
|
+
return NULL;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* @brief Recursive worker of c_dcg_node_collect_leaves.
|
|
2008
|
+
*
|
|
2009
|
+
* @param node Subtree to walk.
|
|
2010
|
+
* @param out Destination array (may be NULL to only count).
|
|
2011
|
+
* @param cap Capacity of out.
|
|
2012
|
+
* @param written Running number of entries already stored (NULL-safe).
|
|
2013
|
+
* @return Number of leaves in this subtree.
|
|
2014
|
+
*/
|
|
2015
|
+
static inline size_t c_dcg_node_collect_leaves_walk(const dcg_node* node, dcg_node** out, size_t cap, size_t* written) {
|
|
2016
|
+
if (!node) return 0;
|
|
2017
|
+
|
|
2018
|
+
if (!node->children) {
|
|
2019
|
+
if (out && written && *written < cap) out[*written] = (dcg_node*) node;
|
|
2020
|
+
if (written) (*written)++;
|
|
2021
|
+
return 1;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
size_t total = 0;
|
|
2025
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
2026
|
+
total += c_dcg_node_collect_leaves_walk(child, out, cap, written);
|
|
2027
|
+
}
|
|
2028
|
+
return total;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* @brief Collect the leaves of a subtree, depth-first, left to right.
|
|
2033
|
+
*
|
|
2034
|
+
* @param root Subtree to walk (NULL-safe).
|
|
2035
|
+
* @param out Destination array (may be NULL to only count).
|
|
2036
|
+
* @param cap Capacity of out.
|
|
2037
|
+
* @return The total number of leaves; when it exceeds cap, the array holds
|
|
2038
|
+
* the first cap of them.
|
|
2039
|
+
*/
|
|
2040
|
+
static inline size_t c_dcg_node_collect_leaves(const dcg_node* root, dcg_node** out, size_t cap) {
|
|
2041
|
+
size_t written = 0;
|
|
2042
|
+
return c_dcg_node_collect_leaves_walk(root, out, cap, &written);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
/**
|
|
2046
|
+
* @brief Recursive worker of c_dcg_node_collect_descendants.
|
|
2047
|
+
*
|
|
2048
|
+
* @param node Subtree to walk.
|
|
2049
|
+
* @param out Destination array (may be NULL to only count).
|
|
2050
|
+
* @param cap Capacity of out.
|
|
2051
|
+
* @param written Running number of entries already stored (NULL-safe).
|
|
2052
|
+
* @return Number of descendants in this subtree.
|
|
2053
|
+
*/
|
|
2054
|
+
static inline size_t c_dcg_node_collect_descendants_walk(const dcg_node* node, dcg_node** out, size_t cap, size_t* written) {
|
|
2055
|
+
if (!node) return 0;
|
|
2056
|
+
|
|
2057
|
+
size_t total = 0;
|
|
2058
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
2059
|
+
if (out && written && *written < cap) out[*written] = (dcg_node*) child;
|
|
2060
|
+
if (written) (*written)++;
|
|
2061
|
+
total++;
|
|
2062
|
+
total += c_dcg_node_collect_descendants_walk(child, out, cap, written);
|
|
2063
|
+
}
|
|
2064
|
+
return total;
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* @brief Collect every node of a subtree below the root, pre-order.
|
|
2069
|
+
*
|
|
2070
|
+
* @param root Subtree to walk (NULL-safe).
|
|
2071
|
+
* @param out Destination array (may be NULL to only count).
|
|
2072
|
+
* @param cap Capacity of out.
|
|
2073
|
+
* @return The total number of descendants; when it exceeds cap, the array
|
|
2074
|
+
* holds the first cap of them.
|
|
2075
|
+
*/
|
|
2076
|
+
static inline size_t c_dcg_node_collect_descendants(const dcg_node* root, dcg_node** out, size_t cap) {
|
|
2077
|
+
size_t written = 0;
|
|
2078
|
+
return c_dcg_node_collect_descendants_walk(root, out, cap, &written);
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
/**
|
|
2082
|
+
* @brief Collect the chain of nodes from the root down to a node.
|
|
2083
|
+
*
|
|
2084
|
+
* The node itself is the last entry. Useful for recording an eval path.
|
|
2085
|
+
*
|
|
2086
|
+
* @param node Node to trace (NULL-safe).
|
|
2087
|
+
* @param out Destination array (may be NULL to only count).
|
|
2088
|
+
* @param cap Capacity of out.
|
|
2089
|
+
* @return The path length (root..node inclusive); entries beyond cap are
|
|
2090
|
+
* dropped from the front when the path does not fit.
|
|
2091
|
+
*/
|
|
2092
|
+
static inline size_t c_dcg_node_path_to(const dcg_node* node, const dcg_node** out, size_t cap) {
|
|
2093
|
+
size_t depth = c_dcg_node_depth(node);
|
|
2094
|
+
for (size_t i = 0; i <= depth && node; i++) {
|
|
2095
|
+
size_t index = depth - i;
|
|
2096
|
+
if (out && index < cap) out[index] = node;
|
|
2097
|
+
node = node->parent;
|
|
2098
|
+
}
|
|
2099
|
+
return depth + 1;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
// ========== Labels ==========
|
|
2103
|
+
|
|
2104
|
+
/**
|
|
2105
|
+
* @brief Attach a label to a node, owning a copy of its text.
|
|
2106
|
+
*
|
|
2107
|
+
* The text is copied into a block nested under the node, and the list entry
|
|
2108
|
+
* itself is a nested block too, so removing the label - or freeing the node -
|
|
2109
|
+
* releases both without a separate step. Duplicates are refused.
|
|
2110
|
+
*
|
|
2111
|
+
* @param node Node to label.
|
|
2112
|
+
* @param label Label text to copy.
|
|
2113
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_INVALID_BUF or DCG_ERR_TYPE.
|
|
2114
|
+
*/
|
|
2115
|
+
static inline int c_dcg_node_add_label(dcg_node* node, const char* label) {
|
|
2116
|
+
if (!node || !label) return DCG_ERR_INVALID_ARG;
|
|
2117
|
+
if (c_dcg_node_has_label(node, label)) return DCG_ERR_TYPE;
|
|
2118
|
+
|
|
2119
|
+
dcg_node_label* entry = (dcg_node_label*) c_ap_alloc_child(sizeof(dcg_node_label), NULL, node);
|
|
2120
|
+
if (!entry) return DCG_ERR_OOM;
|
|
2121
|
+
|
|
2122
|
+
size_t len = strlen(label);
|
|
2123
|
+
char* copy = (char*) c_ap_alloc_child(len + 1, NULL, entry);
|
|
2124
|
+
if (!copy) {
|
|
2125
|
+
c_ap_free_owned(entry);
|
|
2126
|
+
return DCG_ERR_OOM;
|
|
2127
|
+
}
|
|
2128
|
+
memcpy(copy, label, len + 1);
|
|
2129
|
+
|
|
2130
|
+
entry->label = copy;
|
|
2131
|
+
entry->next = node->labels;
|
|
2132
|
+
node->labels = entry;
|
|
2133
|
+
|
|
2134
|
+
c_dcg_node_invoke_callbacks(node, DCG_NODE_EVENT_MODIFIED, node, (uint64_t) -1);
|
|
2135
|
+
return DCG_OK;
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/**
|
|
2139
|
+
* @brief Predicate: does the node carry a label?
|
|
2140
|
+
*
|
|
2141
|
+
* @param node Node to inspect (NULL-safe).
|
|
2142
|
+
* @param label Label text to look for.
|
|
2143
|
+
* @return true when the label is attached.
|
|
2144
|
+
*/
|
|
2145
|
+
static inline bool c_dcg_node_has_label(const dcg_node* node, const char* label) {
|
|
2146
|
+
if (!node || !label) return false;
|
|
2147
|
+
for (const dcg_node_label* entry = node->labels; entry; entry = entry->next) {
|
|
2148
|
+
if (entry->label && strcmp(entry->label, label) == 0) return true;
|
|
2149
|
+
}
|
|
2150
|
+
return false;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
/**
|
|
2154
|
+
* @brief Detach a label from a node, releasing its copy of the text.
|
|
2155
|
+
*
|
|
2156
|
+
* @param node Node to modify.
|
|
2157
|
+
* @param label Label text to remove.
|
|
2158
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_NOT_FOUND.
|
|
2159
|
+
*/
|
|
2160
|
+
static inline int c_dcg_node_remove_label(dcg_node* node, const char* label) {
|
|
2161
|
+
if (!node || !label) return DCG_ERR_INVALID_ARG;
|
|
2162
|
+
|
|
2163
|
+
dcg_node_label* prev = NULL;
|
|
2164
|
+
dcg_node_label* curr = node->labels;
|
|
2165
|
+
while (curr) {
|
|
2166
|
+
if (curr->label && strcmp(curr->label, label) == 0) {
|
|
2167
|
+
if (prev) prev->next = curr->next;
|
|
2168
|
+
else node->labels = curr->next;
|
|
2169
|
+
if (curr->label) c_ap_free_owned((void*) curr->label);
|
|
2170
|
+
c_ap_free_owned(curr);
|
|
2171
|
+
c_dcg_node_invoke_callbacks(node, DCG_NODE_EVENT_MODIFIED, node, (uint64_t) -1);
|
|
2172
|
+
return DCG_OK;
|
|
2173
|
+
}
|
|
2174
|
+
prev = curr;
|
|
2175
|
+
curr = curr->next;
|
|
2176
|
+
}
|
|
2177
|
+
return DCG_ERR_NOT_FOUND;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* @brief Number of labels attached to a node.
|
|
2182
|
+
*
|
|
2183
|
+
* @param node Node to inspect (NULL-safe).
|
|
2184
|
+
* @return The label count.
|
|
2185
|
+
*/
|
|
2186
|
+
static inline size_t c_dcg_node_label_count(const dcg_node* node) {
|
|
2187
|
+
if (!node) return 0;
|
|
2188
|
+
size_t count = 0;
|
|
2189
|
+
for (const dcg_node_label* entry = node->labels; entry; entry = entry->next) count++;
|
|
2190
|
+
return count;
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
// ========== Validation ==========
|
|
2194
|
+
|
|
2195
|
+
/**
|
|
2196
|
+
* @brief Record a validation problem and mark the graph invalid.
|
|
2197
|
+
*
|
|
2198
|
+
* @param report Report being filled (NULL-safe: counting is then skipped).
|
|
2199
|
+
* @param node Node the problem was found on.
|
|
2200
|
+
* @param err Error code.
|
|
2201
|
+
* @param valid Validity accumulator (NULL-safe).
|
|
2202
|
+
*/
|
|
2203
|
+
static inline void c_dcg_validate_fail(dcg_validate_report* report, dcg_node* node, dcg_ret_code err, bool* valid) {
|
|
2204
|
+
if (report) {
|
|
2205
|
+
report->errors++;
|
|
2206
|
+
if (report->code == DCG_OK) {
|
|
2207
|
+
report->code = err;
|
|
2208
|
+
report->node = node;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
if (valid) *valid = false;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
/**
|
|
2215
|
+
* @brief Recursive worker of c_dcg_node_validate.
|
|
2216
|
+
*
|
|
2217
|
+
* @param node Node to check.
|
|
2218
|
+
* @param report Report being filled (NULL-safe).
|
|
2219
|
+
* @param depth Depth of node.
|
|
2220
|
+
* @return true when the subtree is valid.
|
|
2221
|
+
*/
|
|
2222
|
+
static inline bool c_dcg_node_validate_walk(const dcg_node* node, dcg_validate_report* report, size_t depth) {
|
|
2223
|
+
if (!node) return true;
|
|
2224
|
+
if (report) {
|
|
2225
|
+
report->nodes++;
|
|
2226
|
+
if (depth > report->depth) report->depth = depth;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
bool valid = true;
|
|
2230
|
+
size_t count = c_dcg_node_child_count(node);
|
|
2231
|
+
|
|
2232
|
+
if (node->ntype == DCG_NODE_ROOT) {
|
|
2233
|
+
const dcg_node* entry = node->children;
|
|
2234
|
+
if (node->parent) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_TYPE, &valid);
|
|
2235
|
+
if (count > 1) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_TYPE, &valid);
|
|
2236
|
+
if (entry && !c_dcg_condition_is_none(entry->condition_to_parent)) {
|
|
2237
|
+
c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_EDGE, &valid);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
else if (node->parent == NULL) {
|
|
2241
|
+
c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_TYPE, &valid);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
if (c_dcg_node_type_is_action(node->ntype) && count > 0) {
|
|
2245
|
+
c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_TYPE, &valid);
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
size_t arity = c_dcg_node_type_arity(node->ntype);
|
|
2249
|
+
if (arity > 0 && count != arity) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_TYPE, &valid);
|
|
2250
|
+
|
|
2251
|
+
size_t index = 0;
|
|
2252
|
+
dcg_node* else_child = NULL;
|
|
2253
|
+
bool has_binary = false;
|
|
2254
|
+
bool has_value = false;
|
|
2255
|
+
bool has_unconditioned = false;
|
|
2256
|
+
|
|
2257
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling, index++) {
|
|
2258
|
+
const dcg_node_edge_condition* cond = child->condition_to_parent;
|
|
2259
|
+
|
|
2260
|
+
if (c_dcg_condition_is_auto(cond)) {
|
|
2261
|
+
c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_UNRESOLVED, &valid);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
if (c_dcg_condition_is_else(cond)) {
|
|
2265
|
+
if (else_child) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_DUPLICATE, &valid);
|
|
2266
|
+
else_child = child;
|
|
2267
|
+
if (index + 1 != count) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_EDGE, &valid);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
// Branch shapes: a binary branch, a value branch, an else fallback (a
|
|
2271
|
+
// legitimate branch), or the unconditional/auto pair.
|
|
2272
|
+
if (c_dcg_condition_is_binary(cond)) has_binary = true;
|
|
2273
|
+
else if (c_dcg_condition_is_else(cond)) { /* the fallback is a branch of its own */
|
|
2274
|
+
}
|
|
2275
|
+
else if (c_dcg_condition_is_sentinel(cond)) has_unconditioned = true;
|
|
2276
|
+
else has_value = true;
|
|
2277
|
+
|
|
2278
|
+
if (c_dcg_node_is_ancestor(child, node)) {
|
|
2279
|
+
c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_CYCLE, &valid);
|
|
2280
|
+
continue; // Report it and stop - descending would never come back.
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
if (!c_dcg_node_validate_walk(child, report, depth + 1)) valid = false;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
// Branch-shape rules apply to decision nodes only: a root is unconditioned
|
|
2287
|
+
// by construction, and everything else branches.
|
|
2288
|
+
if (count > 1 && node->ntype != DCG_NODE_ROOT) {
|
|
2289
|
+
if (has_unconditioned) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_EDGE, &valid);
|
|
2290
|
+
if (has_binary && has_value) c_dcg_validate_fail(report, (dcg_node*) node, DCG_ERR_EDGE, &valid);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
return valid;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2296
|
+
/**
|
|
2297
|
+
* @brief Validate a graph, walking it depth-first from the given node.
|
|
2298
|
+
*
|
|
2299
|
+
* Enforces the structural invariants a baked graph must satisfy: exactly one
|
|
2300
|
+
* parentless ROOT, at most one child under a root, fixed operator arity,
|
|
2301
|
+
* action nodes as leaves, at most one trailing ELSE per node, resolved
|
|
2302
|
+
* conditions, consistent branch shapes, and acyclicity.
|
|
2303
|
+
*
|
|
2304
|
+
* Problems are reported as dcg_ret_code values, so a caller handles a bad
|
|
2305
|
+
* graph with the same vocabulary it handles every other failure:
|
|
2306
|
+
* - DCG_ERR_TYPE a detached fragment, a root with too many children,
|
|
2307
|
+
* a wrong operand count, an action holding children
|
|
2308
|
+
* - DCG_ERR_EDGE a root edge that is not unconditional, an else that
|
|
2309
|
+
* is not last, conflicting or unconditioned branches
|
|
2310
|
+
* - DCG_ERR_DUPLICATE more than one else branch
|
|
2311
|
+
* - DCG_ERR_UNRESOLVED an AUTO condition that survived into the graph
|
|
2312
|
+
* - DCG_ERR_CYCLE a cycle (reported, then the walk stops there)
|
|
2313
|
+
*
|
|
2314
|
+
* A cycle is reported and the walk stops there instead of descending into
|
|
2315
|
+
* it. The other tree walks (teardown, size and leaf queries, rendering)
|
|
2316
|
+
* assume an acyclic graph - this is the check that establishes it.
|
|
2317
|
+
*
|
|
2318
|
+
* @param root Node to validate from (typically the graph root).
|
|
2319
|
+
* @param report Receives the outcome; may be NULL when only the verdict matters.
|
|
2320
|
+
* @return true when the graph is valid.
|
|
2321
|
+
*/
|
|
2322
|
+
static inline bool c_dcg_node_validate(const dcg_node* root, dcg_validate_report* report) {
|
|
2323
|
+
if (report) memset(report, 0, sizeof(*report));
|
|
2324
|
+
if (!root) return false;
|
|
2325
|
+
return c_dcg_node_validate_walk(root, report, 0);
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
/**
|
|
2329
|
+
* @brief Validate a graph and print every problem found, one per line.
|
|
2330
|
+
*
|
|
2331
|
+
* The report is produced by a plain validation run; this only renders it.
|
|
2332
|
+
*
|
|
2333
|
+
* @param root Node to validate from.
|
|
2334
|
+
* @param stream Destination stream.
|
|
2335
|
+
* @return Number of errors found, or DCG_ERR_INVALID_ARG.
|
|
2336
|
+
*/
|
|
2337
|
+
static inline int c_dcg_node_validate_print(const dcg_node* root, FILE* stream) {
|
|
2338
|
+
if (!stream || !root) return DCG_ERR_INVALID_ARG;
|
|
2339
|
+
|
|
2340
|
+
dcg_validate_report report;
|
|
2341
|
+
bool valid = c_dcg_node_validate(root, &report);
|
|
2342
|
+
|
|
2343
|
+
(void) fprintf(
|
|
2344
|
+
stream,
|
|
2345
|
+
"validate: %s nodes=%zu depth=%zu errors=%zu first=%s\n",
|
|
2346
|
+
valid ? "OK" : "INVALID",
|
|
2347
|
+
report.nodes,
|
|
2348
|
+
report.depth,
|
|
2349
|
+
report.errors,
|
|
2350
|
+
c_dcg_ret_code_name(report.code)
|
|
2351
|
+
);
|
|
2352
|
+
if (!valid && report.node) {
|
|
2353
|
+
(void) fprintf(stream, " first error at %s \"%s\"\n", c_dcg_node_type_name(report.node->ntype), report.node->repr ? report.node->repr : "");
|
|
2354
|
+
}
|
|
2355
|
+
return (int) report.errors;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// ========== Rendering ==========
|
|
2359
|
+
|
|
2360
|
+
/**
|
|
2361
|
+
* @brief Fill in the standard renderer options.
|
|
2362
|
+
*
|
|
2363
|
+
* @param opts Options to populate (NULL-safe).
|
|
2364
|
+
*/
|
|
2365
|
+
static inline void c_dcg_render_opts_default(dcg_render_opts* opts) {
|
|
2366
|
+
if (!opts) return;
|
|
2367
|
+
memset(opts, 0, sizeof(*opts));
|
|
2368
|
+
opts->style = DCG_RENDER_UNICODE;
|
|
2369
|
+
opts->max_depth = DCG_NODE_RENDER_MAX_DEPTH;
|
|
2370
|
+
opts->show_condition = true;
|
|
2371
|
+
opts->show_out = true;
|
|
2372
|
+
opts->show_children = true;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* @brief Render a node's flags as a compact string.
|
|
2377
|
+
*
|
|
2378
|
+
* @param flags Flag bits.
|
|
2379
|
+
* @param out Destination buffer.
|
|
2380
|
+
* @param cap Capacity of out.
|
|
2381
|
+
* @return Number of characters written, or DCG_ERR_*.
|
|
2382
|
+
*/
|
|
2383
|
+
static inline int c_dcg_node_format_flags(uint32_t flags, char* out, size_t cap) {
|
|
2384
|
+
if (!out || cap == 0) return DCG_ERR_INVALID_ARG;
|
|
2385
|
+
|
|
2386
|
+
static const struct {
|
|
2387
|
+
uint32_t bit;
|
|
2388
|
+
const char* name;
|
|
2389
|
+
} entries[] = {
|
|
2390
|
+
{DCG_NODE_FLAG_FROZEN, "frozen"},
|
|
2391
|
+
{DCG_NODE_FLAG_VISITED, "visited"},
|
|
2392
|
+
{DCG_NODE_FLAG_PRUNED, "pruned"},
|
|
2393
|
+
};
|
|
2394
|
+
|
|
2395
|
+
dcg_strbuf buf;
|
|
2396
|
+
c_dcg_sb_init(&buf, out, cap);
|
|
2397
|
+
|
|
2398
|
+
size_t matched = 0;
|
|
2399
|
+
for (size_t i = 0; i < sizeof(entries) / sizeof(entries[0]); i++) {
|
|
2400
|
+
if (!(flags & entries[i].bit)) continue;
|
|
2401
|
+
c_dcg_sb_printf(&buf, "%s%s", matched ? "|" : "", entries[i].name);
|
|
2402
|
+
matched++;
|
|
2403
|
+
}
|
|
2404
|
+
if (matched == 0) c_dcg_sb_puts(&buf, "-");
|
|
2405
|
+
|
|
2406
|
+
return buf.used >= buf.cap ? DCG_ERR_FULL : (int) buf.used;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
/**
|
|
2410
|
+
* @brief Render a node's uid as a 32-character hex string (no dashes).
|
|
2411
|
+
*
|
|
2412
|
+
* Byte order is the array's own: the first byte of the field leads, which is
|
|
2413
|
+
* what makes the string a straight hex dump of the 16 bytes `uuid.UUID.bytes`
|
|
2414
|
+
* hands back.
|
|
2415
|
+
*
|
|
2416
|
+
* @param uid Identity to render (16 bytes).
|
|
2417
|
+
* @param out Destination buffer (at least 33 bytes, terminator included).
|
|
2418
|
+
* @param cap Capacity of out.
|
|
2419
|
+
* @return Number of characters written, or DCG_ERR_*.
|
|
2420
|
+
*/
|
|
2421
|
+
static inline int c_dcg_node_format_uid(const uuid_t uid, char* out, size_t cap) {
|
|
2422
|
+
if (!uid || !out) return DCG_ERR_INVALID_ARG;
|
|
2423
|
+
if (cap < sizeof(uuid_t) * 2 + 1) return DCG_ERR_FULL;
|
|
2424
|
+
|
|
2425
|
+
static const char hex[] = "0123456789abcdef";
|
|
2426
|
+
for (size_t i = 0; i < sizeof(uuid_t); i++) {
|
|
2427
|
+
out[i * 2] = hex[(uid[i] >> 4) & 0x0F];
|
|
2428
|
+
out[i * 2 + 1] = hex[uid[i] & 0x0F];
|
|
2429
|
+
}
|
|
2430
|
+
out[sizeof(uuid_t) * 2] = '\0';
|
|
2431
|
+
|
|
2432
|
+
return (int) (sizeof(uuid_t) * 2);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
/**
|
|
2436
|
+
* @brief Render one node's own line (without the tree prefix).
|
|
2437
|
+
*
|
|
2438
|
+
* @param node Node to render.
|
|
2439
|
+
* @param out Destination buffer.
|
|
2440
|
+
* @param cap Capacity of out.
|
|
2441
|
+
* @param opts Renderer options (NULL = defaults).
|
|
2442
|
+
* @return Number of characters written, or DCG_ERR_*.
|
|
2443
|
+
*/
|
|
2444
|
+
static inline int c_dcg_node_format_line(const dcg_node* node, char* out, size_t cap, const dcg_render_opts* opts) {
|
|
2445
|
+
if (!node || !out || cap == 0) return DCG_ERR_INVALID_ARG;
|
|
2446
|
+
|
|
2447
|
+
dcg_render_opts dflt;
|
|
2448
|
+
if (!opts) {
|
|
2449
|
+
c_dcg_render_opts_default(&dflt);
|
|
2450
|
+
opts = &dflt;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
dcg_strbuf buf;
|
|
2454
|
+
c_dcg_sb_init(&buf, out, cap);
|
|
2455
|
+
|
|
2456
|
+
char scratch[DCG_NODE_STRING_MAXLEN];
|
|
2457
|
+
|
|
2458
|
+
if (opts->show_condition) {
|
|
2459
|
+
(void) c_dcg_condition_format(node->condition_to_parent, "", scratch, sizeof(scratch));
|
|
2460
|
+
c_dcg_sb_printf(&buf, "[%s] ", scratch);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
/* The operator itself lives in the expression variant, which this header
|
|
2464
|
+
* does not see; an expression's repr carries its symbol (see c_expr.h). */
|
|
2465
|
+
c_dcg_sb_puts(&buf, c_dcg_node_type_name(node->ntype));
|
|
2466
|
+
|
|
2467
|
+
if (node->repr) c_dcg_sb_printf(&buf, " \"%s\"", node->repr);
|
|
2468
|
+
|
|
2469
|
+
if (opts->show_out) {
|
|
2470
|
+
(void) c_dcg_var_format(&node->out, scratch, sizeof(scratch));
|
|
2471
|
+
c_dcg_sb_printf(&buf, " out=%s", scratch);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
if (opts->show_children) c_dcg_sb_printf(&buf, " children=%zu", c_dcg_node_child_count(node));
|
|
2475
|
+
|
|
2476
|
+
if (opts->show_hooks) {
|
|
2477
|
+
c_dcg_sb_puts(&buf, " hooks=[");
|
|
2478
|
+
if (node->eval_ctx.pre_eval_fn) c_dcg_sb_puts(&buf, "pre");
|
|
2479
|
+
if (node->eval_ctx.eval_fn) c_dcg_sb_printf(&buf, "%seval", node->eval_ctx.pre_eval_fn ? "," : "");
|
|
2480
|
+
if (node->eval_ctx.post_eval_fn) c_dcg_sb_printf(&buf, "%spost", (node->eval_ctx.pre_eval_fn || node->eval_ctx.eval_fn) ? "," : "");
|
|
2481
|
+
c_dcg_sb_puts(&buf, "]");
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
if (opts->show_labels && node->labels) {
|
|
2485
|
+
c_dcg_sb_puts(&buf, " labels=[");
|
|
2486
|
+
size_t index = 0;
|
|
2487
|
+
for (const dcg_node_label* entry = node->labels; entry; entry = entry->next, index++) {
|
|
2488
|
+
c_dcg_sb_printf(&buf, "%s%s", index ? "," : "", entry->label ? entry->label : "?");
|
|
2489
|
+
}
|
|
2490
|
+
c_dcg_sb_puts(&buf, "]");
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
if (opts->show_flags && node->flags) {
|
|
2494
|
+
(void) c_dcg_node_format_flags(node->flags, scratch, sizeof(scratch));
|
|
2495
|
+
c_dcg_sb_printf(&buf, " flags=%s", scratch);
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
if (opts->show_uid) {
|
|
2499
|
+
(void) c_dcg_node_format_uid(node->uid, scratch, sizeof(scratch));
|
|
2500
|
+
c_dcg_sb_printf(&buf, " uid=%s", scratch);
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
if (opts->show_address) c_dcg_sb_printf(&buf, " @%p", (const void*) node);
|
|
2504
|
+
|
|
2505
|
+
return buf.used >= buf.cap ? DCG_ERR_FULL : (int) buf.used;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
/**
|
|
2509
|
+
* @brief Recursive worker of c_dcg_node_render.
|
|
2510
|
+
*
|
|
2511
|
+
* @param node Node to render.
|
|
2512
|
+
* @param stream Destination stream.
|
|
2513
|
+
* @param opts Renderer options.
|
|
2514
|
+
* @param prefix Prefix drawn to the left of this node's line.
|
|
2515
|
+
* @param is_last true when the node is the last child of its parent.
|
|
2516
|
+
* @param depth Depth of the node.
|
|
2517
|
+
* @return Number of characters written, or DCG_ERR_*.
|
|
2518
|
+
*/
|
|
2519
|
+
static inline int c_dcg_node_render_walk(const dcg_node* node, FILE* stream, const dcg_render_opts* opts, const char* prefix, bool is_last, size_t depth) {
|
|
2520
|
+
if (!node) return 0;
|
|
2521
|
+
|
|
2522
|
+
char line[DCG_NODE_STRING_MAXLEN * 2];
|
|
2523
|
+
int n = c_dcg_node_format_line(node, line, sizeof(line), opts);
|
|
2524
|
+
if (n < 0) return n;
|
|
2525
|
+
|
|
2526
|
+
bool unicode = (opts->style == DCG_RENDER_UNICODE);
|
|
2527
|
+
const char* guide = "";
|
|
2528
|
+
/* The unicode guides are spelled as UTF-8 bytes - U+2514 / U+251C elbow and
|
|
2529
|
+
* tee, U+2500 rule - so the source stays ASCII and a compiler reading it
|
|
2530
|
+
* under any code page produces the same bytes. */
|
|
2531
|
+
if (depth > 0) guide = is_last ? (unicode ? "\xE2\x94\x94\xE2\x94\x80\xE2\x94\x80 " : "`-- ") : (unicode ? "\xE2\x94\x9C\xE2\x94\x80\xE2\x94\x80 " : "|-- ");
|
|
2532
|
+
|
|
2533
|
+
int written = fprintf(stream, "%s%s%s\n", prefix, guide, line);
|
|
2534
|
+
if (written < 0) return DCG_ERR_FORMAT;
|
|
2535
|
+
|
|
2536
|
+
char child_prefix[DCG_NODE_STRING_MAXLEN];
|
|
2537
|
+
dcg_strbuf buf;
|
|
2538
|
+
c_dcg_sb_init(&buf, child_prefix, sizeof(child_prefix));
|
|
2539
|
+
c_dcg_sb_puts(&buf, prefix);
|
|
2540
|
+
if (depth > 0) c_dcg_sb_puts(&buf, is_last ? " " : (unicode ? "\xE2\x94\x82 " : "| "));
|
|
2541
|
+
|
|
2542
|
+
if (opts->max_depth && depth + 1 > opts->max_depth) {
|
|
2543
|
+
if (node->children) {
|
|
2544
|
+
n = fprintf(stream, "%s... (%zu children hidden)\n", child_prefix, c_dcg_node_child_count(node));
|
|
2545
|
+
if (n < 0) return DCG_ERR_FORMAT;
|
|
2546
|
+
written += n;
|
|
2547
|
+
}
|
|
2548
|
+
return written;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
size_t remaining = c_dcg_node_child_count(node);
|
|
2552
|
+
for (dcg_node* child = node->children; child; child = child->next_sibling) {
|
|
2553
|
+
remaining--;
|
|
2554
|
+
n = c_dcg_node_render_walk(child, stream, opts, child_prefix, remaining == 0, depth + 1);
|
|
2555
|
+
if (n < 0) return n;
|
|
2556
|
+
written += n;
|
|
2557
|
+
}
|
|
2558
|
+
return written;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/**
|
|
2562
|
+
* @brief Render a node and its subtree to a stream, one node per line.
|
|
2563
|
+
*
|
|
2564
|
+
* The root line carries no branch glyph; every descendant is prefixed with
|
|
2565
|
+
* the usual box-drawing (or ASCII) guide.
|
|
2566
|
+
*
|
|
2567
|
+
* @param node Node to render (typically the graph root).
|
|
2568
|
+
* @param stream Destination stream.
|
|
2569
|
+
* @param opts Renderer options (NULL = defaults).
|
|
2570
|
+
* @return Number of characters written, or DCG_ERR_INVALID_ARG.
|
|
2571
|
+
*/
|
|
2572
|
+
static inline int c_dcg_node_render(const dcg_node* node, FILE* stream, const dcg_render_opts* opts) {
|
|
2573
|
+
if (!node || !stream) return DCG_ERR_INVALID_ARG;
|
|
2574
|
+
|
|
2575
|
+
dcg_render_opts dflt;
|
|
2576
|
+
if (!opts) {
|
|
2577
|
+
c_dcg_render_opts_default(&dflt);
|
|
2578
|
+
opts = &dflt;
|
|
2579
|
+
}
|
|
2580
|
+
return c_dcg_node_render_walk(node, stream, opts, "", true, 0);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
/**
|
|
2584
|
+
* @brief Render a node and its subtree to stdout with the default options.
|
|
2585
|
+
*
|
|
2586
|
+
* @param node Node to render (NULL-safe).
|
|
2587
|
+
* @return Number of characters written, or DCG_ERR_INVALID_ARG.
|
|
2588
|
+
*/
|
|
2589
|
+
static inline int c_dcg_node_print(const dcg_node* node) {
|
|
2590
|
+
if (!node) return DCG_ERR_INVALID_ARG;
|
|
2591
|
+
return c_dcg_node_render(node, stdout, NULL);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* @brief Render a node and its subtree into a caller-provided buffer.
|
|
2596
|
+
*
|
|
2597
|
+
* Rendering goes through a fixed-size stream, so the buffer must be able to
|
|
2598
|
+
* hold the whole subtree: a buffer that runs out is reported as
|
|
2599
|
+
* DCG_ERR_FULL rather than a half-written tree, and the buffer is still
|
|
2600
|
+
* NUL-terminated.
|
|
2601
|
+
*
|
|
2602
|
+
* @param node Node to render.
|
|
2603
|
+
* @param out Destination buffer.
|
|
2604
|
+
* @param cap Capacity of out.
|
|
2605
|
+
* @param opts Renderer options (NULL = defaults).
|
|
2606
|
+
* @return Number of characters written (excluding NUL), or DCG_ERR_INVALID_ARG
|
|
2607
|
+
* / DCG_ERR_FULL / DCG_ERR_FORMAT.
|
|
2608
|
+
*/
|
|
2609
|
+
static inline int c_dcg_node_render_to_string(const dcg_node* node, char* out, size_t cap, const dcg_render_opts* opts) {
|
|
2610
|
+
if (!node || !out || cap == 0) return DCG_ERR_INVALID_ARG;
|
|
2611
|
+
|
|
2612
|
+
#if defined(_WIN32)
|
|
2613
|
+
/* fmemopen has no Windows counterpart, so the render goes through a
|
|
2614
|
+
* temporary file instead: the same stream renderer, into a sink that is
|
|
2615
|
+
* read back afterwards. tmpfile() opens in binary update mode, so ftell
|
|
2616
|
+
* counts the bytes written without a text-mode translation in the way,
|
|
2617
|
+
* and the caller's capacity is enforced once the render is done - a tree
|
|
2618
|
+
* that does not fit is reported as FULL, exactly as the fmemopen path
|
|
2619
|
+
* reports it, and never as a silent truncation. */
|
|
2620
|
+
FILE* stream = tmpfile();
|
|
2621
|
+
if (!stream) return DCG_ERR_FORMAT;
|
|
2622
|
+
|
|
2623
|
+
int written = c_dcg_node_render(node, stream, opts);
|
|
2624
|
+
(void) fflush(stream);
|
|
2625
|
+
long end = ftell(stream);
|
|
2626
|
+
|
|
2627
|
+
out[cap - 1] = '\0';
|
|
2628
|
+
if (written < 0 || end < 0 || (size_t) end >= cap) {
|
|
2629
|
+
(void) fclose(stream);
|
|
2630
|
+
return DCG_ERR_FULL;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
(void) fseek(stream, 0, SEEK_SET);
|
|
2634
|
+
size_t got = fread(out, 1, (size_t) end, stream);
|
|
2635
|
+
(void) fclose(stream);
|
|
2636
|
+
if (got != (size_t) end) return DCG_ERR_FORMAT;
|
|
2637
|
+
|
|
2638
|
+
out[end] = '\0';
|
|
2639
|
+
return written;
|
|
2640
|
+
#else
|
|
2641
|
+
FILE* stream = fmemopen(out, cap, "w");
|
|
2642
|
+
if (!stream) return DCG_ERR_FORMAT;
|
|
2643
|
+
|
|
2644
|
+
int written = c_dcg_node_render(node, stream, opts);
|
|
2645
|
+
long end = ftell(stream);
|
|
2646
|
+
(void) fflush(stream);
|
|
2647
|
+
(void) fclose(stream);
|
|
2648
|
+
|
|
2649
|
+
// Inside a fixed-size stream every failure is a buffer overflow: the
|
|
2650
|
+
// line formatter reports FULL, while a full buffer makes fprintf fail
|
|
2651
|
+
// outright. Both mean "the tree did not fit".
|
|
2652
|
+
out[cap - 1] = '\0';
|
|
2653
|
+
if (written < 0) return DCG_ERR_FULL;
|
|
2654
|
+
if ((end < 0) || ((size_t) end >= cap)) return DCG_ERR_FULL;
|
|
2655
|
+
|
|
2656
|
+
return written;
|
|
2657
|
+
#endif
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
#endif // C_DCG_BAKE_NODE_H
|