PyDecisionGraph 0.3.2.post1__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. decision_graph/__infra__.pxd +1 -0
  2. decision_graph/__init__.pxd +1 -0
  3. decision_graph/__init__.py +39 -0
  4. decision_graph/decision_tree/__init__.pxd +2 -0
  5. decision_graph/decision_tree/__init__.py +85 -0
  6. decision_graph/decision_tree/bake/__infra__.pxd +502 -0
  7. decision_graph/decision_tree/bake/__init__.pxd +502 -0
  8. decision_graph/decision_tree/bake/__init__.py +29 -0
  9. decision_graph/decision_tree/bake/c_action.cp315-win_amd64.pyd +0 -0
  10. decision_graph/decision_tree/bake/c_action.h +324 -0
  11. decision_graph/decision_tree/bake/c_action.pxd +54 -0
  12. decision_graph/decision_tree/bake/c_action.pyi +126 -0
  13. decision_graph/decision_tree/bake/c_action.pyx +65 -0
  14. decision_graph/decision_tree/bake/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  15. decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
  16. decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
  17. decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
  18. decision_graph/decision_tree/bake/c_bake.cp315-win_amd64.pyd +0 -0
  19. decision_graph/decision_tree/bake/c_bake.h +629 -0
  20. decision_graph/decision_tree/bake/c_bake.pxd +52 -0
  21. decision_graph/decision_tree/bake/c_bake.pyi +151 -0
  22. decision_graph/decision_tree/bake/c_bake.pyx +142 -0
  23. decision_graph/decision_tree/bake/c_collections.cp315-win_amd64.pyd +0 -0
  24. decision_graph/decision_tree/bake/c_collections.h +692 -0
  25. decision_graph/decision_tree/bake/c_collections.pxd +52 -0
  26. decision_graph/decision_tree/bake/c_collections.pyi +193 -0
  27. decision_graph/decision_tree/bake/c_collections.pyx +161 -0
  28. decision_graph/decision_tree/bake/c_const.cp315-win_amd64.pyd +0 -0
  29. decision_graph/decision_tree/bake/c_const.h +395 -0
  30. decision_graph/decision_tree/bake/c_const.pxd +56 -0
  31. decision_graph/decision_tree/bake/c_const.pyi +305 -0
  32. decision_graph/decision_tree/bake/c_const.pyx +278 -0
  33. decision_graph/decision_tree/bake/c_edge.cp315-win_amd64.pyd +0 -0
  34. decision_graph/decision_tree/bake/c_edge.h +511 -0
  35. decision_graph/decision_tree/bake/c_edge.pxd +117 -0
  36. decision_graph/decision_tree/bake/c_edge.pyi +192 -0
  37. decision_graph/decision_tree/bake/c_edge.pyx +270 -0
  38. decision_graph/decision_tree/bake/c_eval.h +848 -0
  39. decision_graph/decision_tree/bake/c_eval.pxd +40 -0
  40. decision_graph/decision_tree/bake/c_expr.cp315-win_amd64.pyd +0 -0
  41. decision_graph/decision_tree/bake/c_expr.h +1900 -0
  42. decision_graph/decision_tree/bake/c_expr.pxd +129 -0
  43. decision_graph/decision_tree/bake/c_expr.pyi +280 -0
  44. decision_graph/decision_tree/bake/c_expr.pyx +272 -0
  45. decision_graph/decision_tree/bake/c_hierarchy.cp315-win_amd64.pyd +0 -0
  46. decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  47. decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
  48. decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
  49. decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
  50. decision_graph/decision_tree/bake/c_logic_group.cp315-win_amd64.pyd +0 -0
  51. decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  52. decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
  53. decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
  54. decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
  55. decision_graph/decision_tree/bake/c_node.cp315-win_amd64.pyd +0 -0
  56. decision_graph/decision_tree/bake/c_node.h +2660 -0
  57. decision_graph/decision_tree/bake/c_node.pxd +381 -0
  58. decision_graph/decision_tree/bake/c_node.pyi +438 -0
  59. decision_graph/decision_tree/bake/c_node.pyx +639 -0
  60. decision_graph/decision_tree/bake/c_reconstruct.cp315-win_amd64.pyd +0 -0
  61. decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
  62. decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
  63. decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
  64. decision_graph/decision_tree/bake/c_var.cp315-win_amd64.pyd +0 -0
  65. decision_graph/decision_tree/bake/c_var.h +2054 -0
  66. decision_graph/decision_tree/bake/c_var.pxd +205 -0
  67. decision_graph/decision_tree/bake/c_var.pyi +271 -0
  68. decision_graph/decision_tree/bake/c_var.pyx +228 -0
  69. decision_graph/decision_tree/capi/__infra__.pxd +33 -0
  70. decision_graph/decision_tree/capi/__init__.pxd +33 -0
  71. decision_graph/decision_tree/capi/__init__.py +57 -0
  72. decision_graph/decision_tree/capi/c_abc.cp315-win_amd64.pyd +0 -0
  73. decision_graph/decision_tree/capi/c_abc.pxd +246 -0
  74. decision_graph/decision_tree/capi/c_abc.pyi +770 -0
  75. decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
  76. decision_graph/decision_tree/capi/c_collection.cp315-win_amd64.pyd +0 -0
  77. decision_graph/decision_tree/capi/c_collection.pxd +22 -0
  78. decision_graph/decision_tree/capi/c_collection.pyi +245 -0
  79. decision_graph/decision_tree/capi/c_collection.pyx +138 -0
  80. decision_graph/decision_tree/capi/c_node.cp315-win_amd64.pyd +0 -0
  81. decision_graph/decision_tree/capi/c_node.pxd +95 -0
  82. decision_graph/decision_tree/capi/c_node.pyi +492 -0
  83. decision_graph/decision_tree/capi/c_node.pyx +551 -0
  84. decision_graph/decision_tree/exc.py +189 -0
  85. decision_graph/decision_tree/native/__init__.py +57 -0
  86. decision_graph/decision_tree/native/abc.py +1167 -0
  87. decision_graph/decision_tree/native/collection.py +134 -0
  88. decision_graph/decision_tree/native/node.py +521 -0
  89. decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
  90. decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
  91. decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
  92. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
  93. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
  94. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
  95. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
  96. decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
  97. decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
  98. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
  99. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
  100. decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
  101. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
  102. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
  103. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
  104. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  105. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
  106. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  107. decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
  108. decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
  109. decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
  110. decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
  111. decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
  112. decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
  113. decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
  114. decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
  115. decision_graph/logic_group/__init__.py +25 -0
  116. decision_graph/logic_group/base.py +100 -0
  117. decision_graph/logic_group/pending_request.py +384 -0
  118. decision_graph/webui/__init__.py +24 -0
  119. decision_graph/webui/bake/__init__.py +65 -0
  120. decision_graph/webui/bake/app.py +488 -0
  121. decision_graph/webui/bake/static/d3.v7.min.js +2 -0
  122. decision_graph/webui/bake/static/script.js +1440 -0
  123. decision_graph/webui/bake/static/style.css +920 -0
  124. decision_graph/webui/bake/templates/index.html +183 -0
  125. decision_graph/webui/bake/templates/offline.html +184 -0
  126. decision_graph/webui/capi/__init__.py +54 -0
  127. decision_graph/webui/capi/app.py +366 -0
  128. decision_graph/webui/capi/main.py +53 -0
  129. decision_graph/webui/capi/static/d3.v7.min.js +2 -0
  130. decision_graph/webui/capi/static/script.js +1026 -0
  131. decision_graph/webui/capi/static/style.css +656 -0
  132. decision_graph/webui/capi/templates/index.html +93 -0
  133. decision_graph/webui/capi/templates/offline.html +94 -0
  134. decision_graph/webui/native/__init__.py +54 -0
  135. decision_graph/webui/native/app.py +366 -0
  136. decision_graph/webui/native/main.py +53 -0
  137. decision_graph/webui/native/static/d3.v7.min.js +2 -0
  138. decision_graph/webui/native/static/script.js +1026 -0
  139. decision_graph/webui/native/static/style.css +656 -0
  140. decision_graph/webui/native/templates/index.html +93 -0
  141. decision_graph/webui/native/templates/offline.html +94 -0
  142. pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
  143. pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
  144. pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
  145. pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
  146. pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
  147. tests/bake_c_evaluator.cp315-win_amd64.pyd +0 -0
  148. tests/bake_eval_probe.cp315-win_amd64.pyd +0 -0
@@ -0,0 +1,848 @@
1
+ #ifndef C_DCG_BAKE_EVAL_H
2
+ #define C_DCG_BAKE_EVAL_H
3
+
4
+ #include <math.h>
5
+ #include <stdbool.h>
6
+ #include <stddef.h>
7
+ #include <stdint.h>
8
+ #include <stdio.h>
9
+ #include <string.h>
10
+
11
+ #include <cbase/allocator_protocol/c_allocator_protocol.h>
12
+
13
+ #include <decision_graph/decision_tree/bake/c_const.h>
14
+ #include <decision_graph/decision_tree/bake/c_edge.h>
15
+ #include <decision_graph/decision_tree/bake/c_expr.h>
16
+ #include <decision_graph/decision_tree/bake/c_hierarchy.h>
17
+ #include <decision_graph/decision_tree/bake/c_node.h>
18
+ #include <decision_graph/decision_tree/bake/c_var.h>
19
+
20
+ /*
21
+ * THE EVALUATION PROTOCOL - the half of the layer that makes a graph decide.
22
+ *
23
+ * A build says what the graph IS; an evaluation says what it DOES. The walk
24
+ * goes from the top down, updating one node's out slot at a time, and comes to
25
+ * rest on a single leaf of the whole tree. The nodes off that path keep the
26
+ * values they had: nothing is evaluated that the walk did not reach.
27
+ *
28
+ * ONE NODE, in order - the three hooks a node carries, around the value it
29
+ * produces:
30
+ *
31
+ * 1. pre_eval_fn, if the node has one. It may fail, and a failure stops the
32
+ * node where it stands: what it refused to set up is not set up.
33
+ * 2. eval_fn - the node's own way of producing a value, if a caller installed
34
+ * one. Without one the value comes from the node's TYPE, and for an
35
+ * operator node from its OP CODE as well: an operator node is given the
36
+ * rule of its own operator when the operator is set (see c_expr.h), and
37
+ * that rule IS its type_eval_fn - so the value is reached through the rule
38
+ * the node carries, either at bake time (DCG_EVAL_DIRECT_HOOKS, which
39
+ * installs a family's rule as the node is built) or by the dispatch that
40
+ * finds it from the type (c_dcg_node_eval_default) - the same functions
41
+ * either way. A type with no rule of its own - a call, whose callee is
42
+ * composed into display text and stored nowhere - reports DCG_ERR_TYPE
43
+ * rather than producing a value nothing can read.
44
+ * 3. post_eval_fn, if the node has one. By here the slot holds the value, so
45
+ * a hook that runs at this point reacts to it rather than producing it.
46
+ *
47
+ * What the protocol does NOT do is hide a hook's failure: a hook that raises
48
+ * comes back as DCG_ERR_HOOK, and everything after it in the node is skipped.
49
+ * A hook is a function pointer, so the layer never calls one that was never
50
+ * installed - there is no dummy to dispatch on.
51
+ *
52
+ * THE BOOKKEEPING, on the node's own context, once the three stages are done:
53
+ * which stages completed and WHAT produced the value (dcg_eval_stage bits, the
54
+ * producer among them), what the node ended with (err_code), the run that wrote
55
+ * the value (eval_seq_id), how deep the node sat (depth) and how many times it
56
+ * has been evaluated (visits). The node is then
57
+ * announced with DCG_NODE_EVENT_EVALUATED, carrying the run's id - so a
58
+ * listener hears which run produced the value it is looking at.
59
+ *
60
+ * THE WALK, after the node's own value stands:
61
+ *
62
+ * 4. the value selects the edge. The node's children are tried in the order
63
+ * they are stored - which is the order they are read, the true arm before
64
+ * its fallback - and the first whose condition MATCHES the value is taken.
65
+ * An ELSE arm is held back and taken only when nothing else matched, so a
66
+ * fallback cannot shadow a branch written after it (see
67
+ * c_dcg_condition_matches).
68
+ * 5. the walk descends into that child, one level deeper, and starts again at
69
+ * step 1. A node with no children is a leaf and ends the walk; a node
70
+ * whose children all declined is DCG_ERR_NO_MATCH.
71
+ *
72
+ * A node carrying DCG_EVAL_FLAG_SKIP_CHILDREN ends the walk where it stands, and
73
+ * one carrying DCG_EVAL_FLAG_BREAKPOINT is an inspection point: recorded, and
74
+ * passed through to what it is connected to when there is anything below it. A
75
+ * node carrying DCG_EVAL_FLAG_CACHED keeps the value it already holds - its eval
76
+ * hook is not called - which is how a caller reuses a value it computed itself.
77
+ *
78
+ * THE FOUR WAYS IN, in the order they are built on each other:
79
+ *
80
+ * - c_dcg_node_eval(): ONE node, and nothing else. No bookkeeping is written
81
+ * and no child is reached, so the value it produces stays in the node's slot.
82
+ * - c_dcg_node_dryrun(): the same, asked as a question - the value goes to a
83
+ * slot the caller lends and the node is left exactly as it was found.
84
+ * - c_dcg_node_eval_graph(): a walk from wherever it is given, recording the
85
+ * nodes it reached into a path the caller lends it.
86
+ * - c_dcg_root_node_eval(): the same walk from a root, recorded into the root's
87
+ * OWN eval path - the record a caller reads back to see how the decision was
88
+ * reached.
89
+ *
90
+ * The header is C only: it is above every family (it includes them) and nothing
91
+ * includes it. A Cython module that binds it does so with a textual
92
+ * `cdef extern from`, so it creates no pxd edge and no cycle - see DEPENDENCY.md
93
+ * section 3.2.
94
+ */
95
+
96
+ /** Entries the eval path starts with; it doubles as the walk grows it. */
97
+ #ifndef DCG_EVAL_PATH_INITIAL_CAPACITY
98
+ #define DCG_EVAL_PATH_INITIAL_CAPACITY 16U
99
+ #endif
100
+
101
+ // ========== Constants ==========
102
+
103
+ /* The stage mask a node records its evaluation in - and the producer bits that
104
+ * ride in it - are declared in c_node.h, where the context that carries them is:
105
+ * this header is above that one and fills the mask (see c_dcg_node_eval_hooks). */
106
+
107
+ // ========== Structs ==========
108
+
109
+ /**
110
+ * @brief The state of one walk - what every node of a run shares.
111
+ *
112
+ * It is what the evaluator hangs off `dcg_node_eval_ctx.run` while a node is
113
+ * being evaluated, so a hook can ask about the run it is part of rather than
114
+ * only about its own node: what has been reached so far, how deep the walk is,
115
+ * and whether anything has failed. It lives on the walker's stack for the
116
+ * length of the walk, and no node keeps a pointer to it beyond its own visit.
117
+ */
118
+ typedef struct dcg_eval_run {
119
+ uint64_t seq_id; // The run's id, stamped on every node it visits.
120
+ dcg_ret_code code; // DCG_OK so far, or the first failure.
121
+ dcg_node* failed; // The node that reported that failure.
122
+ dcg_node* leaf; // The node the walk came to rest on.
123
+ size_t visited; // Nodes whose evaluation completed.
124
+ size_t depth; // The deepest level the walk reached.
125
+ bool inplace; // Whether a value produced under it is kept.
126
+ } dcg_eval_run;
127
+
128
+ // ========== Forward Declarations ==========
129
+
130
+ // The path record
131
+ static inline dcg_node_eval_path* c_dcg_node_eval_path_new(size_t capacity, allocator_protocol* allocator);
132
+ static inline void c_dcg_node_eval_path_free(dcg_node_eval_path* path);
133
+ static inline void c_dcg_eval_path_reset(dcg_node_eval_path* path);
134
+ static inline int c_dcg_eval_path_append(dcg_node_eval_path* path, dcg_node* node);
135
+ static inline int c_dcg_root_node_eval_path_reserve(dcg_root_node* root, size_t capacity);
136
+ static inline int c_dcg_root_node_eval_path_append(dcg_root_node* root, dcg_node* node);
137
+
138
+ // The built-in evaluation
139
+ static inline int c_dcg_node_eval_default(dcg_node* node);
140
+
141
+ // The node's own evaluation
142
+ static inline int c_dcg_node_eval_hooks(dcg_node* node);
143
+ static inline int c_dcg_node_eval(dcg_node* node);
144
+ static inline int c_dcg_node_dryrun(dcg_node* node, dcg_var_t* out);
145
+
146
+ // The walk
147
+ static inline uint64_t c_dcg_eval_gen_seq_id(const dcg_node* node);
148
+ static inline dcg_node* c_dcg_eval_select_child(const dcg_node* node);
149
+ static inline int c_dcg_node_eval_visit(dcg_node* node, dcg_eval_run* run, size_t depth, dcg_root_node* root, dcg_node_eval_path* path);
150
+ static inline void c_dcg_eval_path_outcome(dcg_root_node* root, dcg_node_eval_path* path, const dcg_eval_run* run, dcg_node* leaf, dcg_node* failed);
151
+ static inline int c_dcg_eval_walk(dcg_node* node, dcg_root_node* root, dcg_node_eval_path* path);
152
+ static inline int c_dcg_node_eval_graph(dcg_node* node, dcg_node_eval_path* path);
153
+ static inline int c_dcg_root_node_eval(dcg_root_node* root);
154
+
155
+ // ========== The Path Record ==========
156
+
157
+ /**
158
+ * @brief Empty a path record, keeping the room it has.
159
+ *
160
+ * The entries go, the two blocks and the room stay: a caller that evaluates
161
+ * repeatedly reuses the record rather than allocating for every walk, and a path
162
+ * is emptied before every walk because what it holds belongs to the last one.
163
+ *
164
+ * @param path Path to empty (NULL-safe).
165
+ */
166
+ static inline void c_dcg_eval_path_reset(dcg_node_eval_path* path) {
167
+ if (!path) return;
168
+ path->n_nodes = 0;
169
+ path->code = DCG_OK;
170
+ path->leaf = NULL;
171
+ path->failed = NULL;
172
+ path->seq_id = 0;
173
+ }
174
+
175
+ /**
176
+ * @brief Allocate a record of its own, with room for `capacity` entries.
177
+ *
178
+ * The way to record a walk that is not a root's: the record is one block, the
179
+ * two arrays are blocks nested under it, and c_dcg_node_eval_path_free()
180
+ * releases all three in one walk - so a record can be a local, a field of a
181
+ * caller's own struct, or a piece of a larger one, and it never belongs to the
182
+ * graph it describes.
183
+ *
184
+ * What to ask for is what the walk can need, and that is knowable before it
185
+ * runs: a walk visits one node per LEVEL and never two at the same level, so
186
+ * while it may stop earlier, it cannot go deeper than the graph does -
187
+ * c_dcg_node_height(node) + 1 is the most entries any walk from `node` can use.
188
+ * A graph that grows between walks can outgrow the room; the record then keeps
189
+ * its END and counts what it lost (see c_dcg_eval_path_append).
190
+ *
191
+ * @param capacity Entries to make room for; 0 takes the default.
192
+ * @param allocator Allocator for the block; NULL falls back to the plain heap.
193
+ * @return The record, or NULL on OOM.
194
+ */
195
+ static inline dcg_node_eval_path* c_dcg_node_eval_path_new(size_t capacity, allocator_protocol* allocator) {
196
+ if (capacity == 0) capacity = DCG_EVAL_PATH_INITIAL_CAPACITY;
197
+
198
+ dcg_node_eval_path* path = (dcg_node_eval_path*) c_ap_alloc(sizeof(dcg_node_eval_path), allocator);
199
+ if (!path) return NULL;
200
+
201
+ path->capacity = capacity;
202
+ path->node = (dcg_node**) c_ap_alloc_child(capacity * sizeof(dcg_node*), allocator, path);
203
+ path->eval_val = (dcg_var_t*) c_ap_alloc_child(capacity * sizeof(dcg_var_t), allocator, path);
204
+ if (!path->node || !path->eval_val) {
205
+ c_ap_free_owned(path); /* the arrays are its children: one free releases whatever was made */
206
+ return NULL;
207
+ }
208
+
209
+ c_dcg_eval_path_reset(path);
210
+ return path;
211
+ }
212
+
213
+ /**
214
+ * @brief Release a record of its own, and its arrays with it.
215
+ *
216
+ * Only for a record from c_dcg_node_eval_path_new(). The root's embedded record
217
+ * is not freed - it is not a block, it is a field of the root, and the root's
218
+ * own free releases what it holds.
219
+ *
220
+ * @param path Record to free (NULL-safe).
221
+ */
222
+ static inline void c_dcg_node_eval_path_free(dcg_node_eval_path* path) {
223
+ if (!path) return;
224
+ c_ap_free_owned(path);
225
+ }
226
+
227
+ /**
228
+ * @brief Record one visited node and the value it stood for.
229
+ *
230
+ * The value is what the node's slot READS AS, not a copy of the slot itself: a
231
+ * read's slot is a live reference, and a record that stored the reference would
232
+ * report whatever the store holds at the moment it is read back rather than what
233
+ * the node evaluated to (see c_dcg_var_snapshot). The snapshot is shallow - a
234
+ * payload the node owns is borrowed, not copied - so it is valid for as long as
235
+ * the node is not evaluated again.
236
+ *
237
+ * A record GROWS: a walk as deep as the graph can make one still records every
238
+ * node of it, because a record that silently lost its first entries would be a
239
+ * record of a different walk. Room is the caller's to size (a walk can never
240
+ * visit more than c_dcg_node_height(node) + 1 nodes, which is what
241
+ * c_dcg_node_eval_path_new() and the root's own record ask for), and a record
242
+ * that needs more simply takes it.
243
+ *
244
+ * @param path Record to add to (must have room made for it).
245
+ * @param node Node that was visited.
246
+ * @return DCG_OK, DCG_ERR_INVALID_ARG (NULL path or node, or a record that was
247
+ * never given room), or DCG_ERR_OOM.
248
+ */
249
+ static inline int c_dcg_eval_path_append(dcg_node_eval_path* path, dcg_node* node) {
250
+ if (!path || !node) return DCG_ERR_INVALID_ARG;
251
+ if (!path->node || !path->eval_val) return DCG_ERR_INVALID_ARG;
252
+
253
+ if (path->n_nodes == path->capacity) {
254
+ size_t grown = path->capacity ? path->capacity * 2 : DCG_EVAL_PATH_INITIAL_CAPACITY;
255
+
256
+ /* Each array is grown with the allocator read from the very block being
257
+ * grown, and it has to be that way: a realloc MOVES a block - it takes a
258
+ * new one and frees the old - so an allocator read from the first array
259
+ * is into freed memory by the time the second one is grown. The block a
260
+ * call is about to grow is alive at the call, and realloc reads the
261
+ * allocator before it frees anything. */
262
+ dcg_node** grown_nodes = (dcg_node**) c_ap_realloc(path->node, grown * sizeof(dcg_node*), c_ap_protocol_from_ptr(path->node));
263
+ if (!grown_nodes) return DCG_ERR_OOM;
264
+ path->node = grown_nodes;
265
+
266
+ dcg_var_t* grown_values = (dcg_var_t*) c_ap_realloc(path->eval_val, grown * sizeof(dcg_var_t), c_ap_protocol_from_ptr(path->eval_val));
267
+ if (!grown_values) return DCG_ERR_OOM;
268
+ path->eval_val = grown_values;
269
+
270
+ path->capacity = grown;
271
+ }
272
+
273
+ path->node[path->n_nodes] = node;
274
+ c_dcg_var_snapshot(&path->eval_val[path->n_nodes], &node->out);
275
+ path->n_nodes++;
276
+ return DCG_OK;
277
+ }
278
+
279
+ /**
280
+ * @brief Make the room a root's own record needs, once, before anything is in it.
281
+ *
282
+ * A root's record is EMBEDDED in the root, not a block of its own, so its
283
+ * arrays are nested under the root and released with it. The room is made once -
284
+ * a record that has room is left as it is - and sized for what a walk can need:
285
+ * one node per LEVEL, and no walk goes deeper than the graph it starts from, so
286
+ * the graph's height plus the node the walk starts from is the most entries any
287
+ * walk from this root can use.
288
+ *
289
+ * Making it is a thing of its own because a walk is not the only caller: a walk
290
+ * makes it on the way in (a record with no room is one it would have to grow),
291
+ * and a bake makes it ahead of the walk, so that the evaluation a hot loop runs
292
+ * allocates nothing at all (see c_bake.h).
293
+ *
294
+ * @param root Root whose record to make room for.
295
+ * @param capacity Entries to make room for; 0 asks for the graph's own height,
296
+ * plus the node a walk from it starts at.
297
+ * @return DCG_OK, DCG_ERR_INVALID_ARG, or DCG_ERR_OOM when the room could not be
298
+ * made.
299
+ */
300
+ static inline int c_dcg_root_node_eval_path_reserve(dcg_root_node* root, size_t capacity) {
301
+ if (!root) return DCG_ERR_INVALID_ARG;
302
+
303
+ dcg_node_eval_path* path = &root->eval_path;
304
+ if (path->node && path->eval_val) return DCG_OK; /* it already has room */
305
+
306
+ if (capacity == 0) capacity = c_dcg_node_height(&root->base) + 1;
307
+
308
+ allocator_protocol* allocator = c_ap_protocol_from_ptr(root);
309
+ path->node = (dcg_node**) c_ap_alloc_child(capacity * sizeof(dcg_node*), allocator, root);
310
+ path->eval_val = (dcg_var_t*) c_ap_alloc_child(capacity * sizeof(dcg_var_t), allocator, root);
311
+ if (!path->node || !path->eval_val) {
312
+ if (path->node) c_ap_free_owned(path->node); /* the root outlives this: release what was made */
313
+ path->node = NULL;
314
+ path->eval_val = NULL;
315
+ return DCG_ERR_OOM;
316
+ }
317
+ path->capacity = capacity;
318
+ return DCG_OK;
319
+ }
320
+
321
+ /**
322
+ * @brief Record one visited node into a root's own record - its blocks, its room.
323
+ *
324
+ * The variant a root's walk appends through: the room is made if the record has
325
+ * none, and the node is then added to it (c_dcg_root_node_eval_path_reserve,
326
+ * c_dcg_eval_path_append).
327
+ *
328
+ * @param root Root whose record to add to.
329
+ * @param node Node that was visited.
330
+ * @return DCG_OK, DCG_ERR_INVALID_ARG, or DCG_ERR_OOM when the room could not be
331
+ * made.
332
+ */
333
+ static inline int c_dcg_root_node_eval_path_append(dcg_root_node* root, dcg_node* node) {
334
+ if (!root || !node) return DCG_ERR_INVALID_ARG;
335
+
336
+ int ret_code = c_dcg_root_node_eval_path_reserve(root, 0);
337
+ if (ret_code != DCG_OK) return ret_code;
338
+
339
+ return c_dcg_eval_path_append(&root->eval_path, node);
340
+ }
341
+
342
+ // ========== The Built-in Evaluation ==========
343
+
344
+ /**
345
+ * @brief The built-in evaluation of a node: the rule its type gives it.
346
+ *
347
+ * Most of the layer needs no evaluation at all, because what a node is worth is
348
+ * settled when the node is BUILT - and saying so here is what keeps an
349
+ * evaluation cheap:
350
+ *
351
+ * - a LITERAL's slot is its value, written when it was built;
352
+ * - a READ's slot is a reference to its store entry - or, before the read has
353
+ * ever been evaluated, the entry's OFFSET, which the evaluation spends
354
+ * (c_dcg_node_mapping_var_node_eval_hook);
355
+ * - an ACTION's slot is the node itself, written at birth;
356
+ * - a ROOT's slot is true and a BREAKPOINT passes through, both as they were
357
+ * built.
358
+ *
359
+ * So there is exactly ONE rule left to run: an OPERATOR node's, which produces
360
+ * its own operands and applies its operator to them (see c_expr.h).
361
+ *
362
+ * This function is that dispatch, and it is what a node built by a TU with
363
+ * DCG_EVAL_DIRECT_HOOKS off reaches at every evaluation; with it on, the rules
364
+ * a node does have are installed as its own hook at bake time and this is not
365
+ * reached for them. Either way the rule that runs is the same function: for an
366
+ * operator node the two ways in are its own rule ON the node (injected) and
367
+ * c_dcg_node_expr_eval (found from the operator), and both land on the same one.
368
+ *
369
+ * @param node Node to evaluate.
370
+ * @return DCG_OK, or a DCG_ERR_* code.
371
+ */
372
+ static inline int c_dcg_node_eval_default(dcg_node* node) {
373
+ if (!node) return DCG_ERR_INVALID_ARG;
374
+
375
+ switch ((int) node->ntype & DCG_NODE_FAMILY_MASK) {
376
+ case DCG_NODE_INPUT: {
377
+ /* A literal is its value and a read is a reference to its entry: only
378
+ * the read has anything to say at run time, and only the read with no
379
+ * store to ask (a store's read brings its own rule). */
380
+ if (node->ntype != DCG_NODE_VARIABLE) return DCG_OK;
381
+
382
+ /* A read a STORE made never arrives here: it is built with the store's
383
+ * own rule on it (see c_collections.h). What is left is a read of no
384
+ * store - one bound by hand to another node's slot - and it has only
385
+ * that slot to answer with: it either says what it reflects, or it
386
+ * says nothing has landed behind it yet, or it is not a reference at
387
+ * all and the node is malformed. */
388
+ dcg_variable_node* var = (dcg_variable_node*) node;
389
+ dcg_var_t* out = &var->base.out;
390
+ if (!c_dcg_var_is_ref(out->dtype)) return c_dcg_var_is_null(out) ? DCG_ERR_UNBOUND : DCG_ERR_TYPE;
391
+ return c_dcg_var_ref_base(out->dtype) == VAR_TYPE_RESERVED ? DCG_ERR_UNBOUND : DCG_OK;
392
+ }
393
+
394
+ case DCG_NODE_OP:
395
+ /* The family's own entry, which finds the rule from the node's
396
+ * operator and its arity (c_expr.h). A build that injects its rules
397
+ * (DCG_EVAL_DIRECT_HOOKS) never arrives here - its operator nodes
398
+ * carry theirs - and both ways land on the same rule. */
399
+ return c_dcg_node_expr_eval(node, NULL);
400
+
401
+ case DCG_NODE_ACTION:
402
+ case DCG_NODE_SPECIAL:
403
+ /* Settled at birth: an action is its own value, a root is true, a
404
+ * breakpoint passes through. */
405
+ return DCG_OK;
406
+
407
+ default:
408
+ break;
409
+ }
410
+
411
+ (void) fprintf(stderr, "c_dcg_node_eval_default: no evaluation for node type %s (0x%04x) at %p\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node);
412
+ return DCG_ERR_TYPE;
413
+ }
414
+
415
+ // ========== The Node's Own Evaluation ==========
416
+
417
+ /**
418
+ * @brief Run a node's three stages, recording on the node how far they got.
419
+ *
420
+ * This is the protocol body: the pre hook, then the eval hook or the built-in
421
+ * rule, then the post hook, each one skipped when the node carries no hook of
422
+ * that kind. What it completed - and which of the three producers got the value
423
+ * there - is written onto the node's own context as it goes, because that record
424
+ * is about the node and belongs on it: a caller asking why a node failed reads
425
+ * the stage it stopped at off the node, not off a return it had to keep.
426
+ *
427
+ * @param node Node to evaluate.
428
+ * @return DCG_OK, or the code that stopped it.
429
+ */
430
+ static inline int c_dcg_node_eval_hooks(dcg_node* node) {
431
+ if (!node) return DCG_ERR_INVALID_ARG;
432
+
433
+ dcg_node_eval_ctx* ctx = &node->eval_ctx;
434
+ uint32_t done = DCG_EVAL_STAGE_NONE; /* the mask as it is built, before it is recorded */
435
+ int ret_code;
436
+
437
+ /* The record of this evaluation starts empty, and is written onto the node as
438
+ * it progresses rather than handed back to a caller: what ran, and what
439
+ * produced the value, is state about the node - and a failure leaves the
440
+ * stage it stopped at where anyone asking about the node can read it. */
441
+ ctx->stage = DCG_EVAL_STAGE_NONE;
442
+
443
+ if (ctx->pre_eval_fn) {
444
+ ret_code = ctx->pre_eval_fn(node, ctx->user_data);
445
+ if (ctx->flags & DCG_EVAL_FLAG_TRACE) {
446
+ (void) fprintf(stderr, "[eval] pre %s (0x%04x) %p -> %s\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node, c_dcg_ret_code_name(ret_code));
447
+ }
448
+ if (ret_code != DCG_OK) {
449
+ ctx->stage = done;
450
+ return ret_code;
451
+ }
452
+ }
453
+ done |= DCG_EVAL_STAGE_PRE_EVAL;
454
+
455
+ if (ctx->flags & DCG_EVAL_FLAG_CACHED) {
456
+ /* The node holds a value a caller computed itself; the slot stands as
457
+ * it is and the eval hook is not called. */
458
+ }
459
+ else if (ctx->eval_fn) {
460
+ done |= DCG_EVAL_STAGE_HOOK;
461
+ ret_code = ctx->eval_fn(node, ctx->user_data);
462
+ if (ctx->flags & DCG_EVAL_FLAG_TRACE) {
463
+ (void) fprintf(stderr, "[eval] eval %s (0x%04x) %p -> %s\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node, c_dcg_ret_code_name(ret_code));
464
+ }
465
+ if (ret_code != DCG_OK) {
466
+ ctx->stage = done;
467
+ return ret_code;
468
+ }
469
+ }
470
+ else if (ctx->type_eval_fn) {
471
+ /* The rule the node's type gave it, installed as the node was built: a
472
+ * caller's hook is an override of it, so this is only reached when there
473
+ * is none (see DCG_EVAL_DIRECT_HOOKS). */
474
+ done |= DCG_EVAL_STAGE_TYPE_RULE;
475
+ ret_code = ctx->type_eval_fn(node, NULL);
476
+ if (ctx->flags & DCG_EVAL_FLAG_TRACE) {
477
+ (void) fprintf(stderr, "[eval] type %s (0x%04x) %p -> %s\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node, c_dcg_ret_code_name(ret_code));
478
+ }
479
+ if (ret_code != DCG_OK) {
480
+ ctx->stage = done;
481
+ return ret_code;
482
+ }
483
+ }
484
+ else {
485
+ done |= DCG_EVAL_STAGE_BUILTIN;
486
+ ret_code = c_dcg_node_eval_default(node);
487
+ if (ctx->flags & DCG_EVAL_FLAG_TRACE) {
488
+ (void) fprintf(stderr, "[eval] def %s (0x%04x) %p -> %s\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node, c_dcg_ret_code_name(ret_code));
489
+ }
490
+ if (ret_code != DCG_OK) {
491
+ ctx->stage = done;
492
+ return ret_code;
493
+ }
494
+ }
495
+ done |= DCG_EVAL_STAGE_EVAL;
496
+
497
+ if (ctx->post_eval_fn) {
498
+ ret_code = ctx->post_eval_fn(node, ctx->user_data);
499
+ if (ctx->flags & DCG_EVAL_FLAG_TRACE) {
500
+ (void) fprintf(stderr, "[eval] post %s (0x%04x) %p -> %s\n", c_dcg_node_type_name(node->ntype), (unsigned) node->ntype, (const void*) node, c_dcg_ret_code_name(ret_code));
501
+ }
502
+ if (ret_code != DCG_OK) {
503
+ ctx->stage = done;
504
+ return ret_code;
505
+ }
506
+ }
507
+ done |= DCG_EVAL_STAGE_POST_EVAL | DCG_EVAL_STAGE_DONE;
508
+
509
+ ctx->stage = done;
510
+ return DCG_OK;
511
+ }
512
+
513
+ /**
514
+ * @brief Evaluate one node and nothing else, keeping the value it produces.
515
+ *
516
+ * No child is reached and nothing about a RUN is written: the node's sequence
517
+ * id, depth and visit count are left exactly as they were, so one node can be
518
+ * evaluated between two walks without disturbing them. What the node IS asked
519
+ * for is its hooks, so a hook that has side effects has them either way - and
520
+ * what the evaluation leaves behind is its own outcome, the stage it failed at
521
+ * and the code it ended with, because those are about this evaluation and a
522
+ * caller told "it failed" is owed the stage it failed at.
523
+ *
524
+ * The run pointer is lent to the node for the duration and given back
525
+ * afterwards, so a hook reading `eval_ctx.run` sees a run - a minimal one, with
526
+ * no id and no walk behind it - instead of whatever the last real walk left
527
+ * there.
528
+ *
529
+ * @param node Node to evaluate.
530
+ * @return DCG_OK, or the code that stopped it.
531
+ */
532
+ static inline int c_dcg_node_eval(dcg_node* node) {
533
+ if (!node) return DCG_ERR_INVALID_ARG;
534
+
535
+ dcg_eval_run lent;
536
+ memset(&lent, 0, sizeof(lent));
537
+ lent.inplace = true;
538
+
539
+ dcg_node_eval_ctx* ctx = &node->eval_ctx;
540
+ void* previous_run = ctx->run;
541
+
542
+ ctx->run = &lent;
543
+ int ret_code = c_dcg_node_eval_hooks(node);
544
+ ctx->run = previous_run;
545
+
546
+ /* What the node's OWN evaluation did is written onto it by the protocol
547
+ * itself - the stage it got through and the code it ended with, stage
548
+ * included - because those are about this evaluation, and a caller that is
549
+ * told "it failed" is owed the stage it failed at. The state that belongs to
550
+ * a RUN stays as it was: this is not one, so it neither counts a visit, nor
551
+ * claims the depth, nor stamps a run id on the node. */
552
+ ctx->err_code = ret_code;
553
+
554
+ return ret_code;
555
+ }
556
+
557
+ /**
558
+ * @brief Ask one node what it WOULD evaluate to - the value out, the node as it
559
+ * was.
560
+ *
561
+ * Everything c_dcg_node_eval() does, and then the node is put back the way it
562
+ * was found: what the evaluation produced goes to the caller's slot instead of
563
+ * staying in the node's, and the node's own slot is restored - so a node a walk
564
+ * has already given a value to can be asked again without that value being
565
+ * disturbed, and a node under which a subtree is about to be walked can be
566
+ * asked what it says first.
567
+ *
568
+ * The slot IS the answer, and the value moves into it whole: a payload the
569
+ * produced value owns goes with it, and nothing is copied. A value the caller
570
+ * does not take (`out` NULL) is released here instead, so a question never
571
+ * leaks the answer to it. What the caller's slot must not be is a slot that
572
+ * owns something: what it held is overwritten.
573
+ *
574
+ * The node's slot is then put back as it stood - the value that was there
575
+ * before anything ran - which is safe because a rule writes OVER the slot it is
576
+ * given (a kernel re-inits it) and never releases what it held.
577
+ *
578
+ * What is written onto the node is its own outcome, the stage it got through
579
+ * and the code it ended with, exactly as c_dcg_node_eval() writes it; nothing
580
+ * about a RUN is, since this is not one.
581
+ *
582
+ * @param node Node to evaluate.
583
+ * @param out Receives the value the node produced (may be NULL: the value is
584
+ * then released and only the outcome is reported).
585
+ * @return DCG_OK, or the code that stopped it.
586
+ */
587
+ static inline int c_dcg_node_dryrun(dcg_node* node, dcg_var_t* out) {
588
+ if (!node) return DCG_ERR_INVALID_ARG;
589
+
590
+ dcg_eval_run lent;
591
+ memset(&lent, 0, sizeof(lent));
592
+ lent.inplace = false; /* the value leaves the node instead of staying in it */
593
+
594
+ dcg_node_eval_ctx* ctx = &node->eval_ctx;
595
+ void* previous_run = ctx->run;
596
+ dcg_var_t previous_value = node->out;
597
+
598
+ ctx->run = &lent;
599
+ int ret_code = c_dcg_node_eval_hooks(node);
600
+ ctx->run = previous_run;
601
+
602
+ ctx->err_code = ret_code;
603
+
604
+ if (out) *out = node->out; /* the answer goes to the caller ... */
605
+ else c_dcg_var_dealloc(&node->out);
606
+
607
+ node->out = previous_value; /* ... and the node keeps what it came in with */
608
+ return ret_code;
609
+ }
610
+
611
+ // ========== The Walk ==========
612
+
613
+ /**
614
+ * @brief Mint the id of a run.
615
+ *
616
+ * The id is what a node's `eval_seq_id` carries afterwards and what the
617
+ * EVALUATED event is announced with, so a listener can tell whose value it is
618
+ * looking at. It is derived from the top node's identity and a counter, which
619
+ * makes it distinct between the runs of one process - the pid is part of the
620
+ * mix, but two processes are not expected to compare their ids.
621
+ *
622
+ * @param node Top of the walk (may be NULL: the id is then just the counter).
623
+ * @return The run's id.
624
+ */
625
+ static inline uint64_t c_dcg_eval_gen_seq_id(const dcg_node* node) {
626
+ static uint64_t counter = 0;
627
+ counter++;
628
+ return c_dcg_node_gen_seq_id(node) ^ (counter * 0x9E3779B97F4A7C15ULL);
629
+ }
630
+
631
+ /**
632
+ * @brief Which child the node's value selects - the edge the walk follows.
633
+ *
634
+ * The children are tried in stored order, which is the order the arms were
635
+ * written and read: the true arm before its fallback. An ELSE arm is held back
636
+ * and taken only when nothing else matched, so a fallback does not shadow a
637
+ * branch that was written after it.
638
+ *
639
+ * @param node Node whose value selects the edge.
640
+ * @return The child to descend into, or NULL when none was selected.
641
+ */
642
+ static inline dcg_node* c_dcg_eval_select_child(const dcg_node* node) {
643
+ if (!node) return NULL;
644
+
645
+ dcg_node* fallback = NULL;
646
+ for (dcg_node* child = node->children; child; child = child->next_sibling) {
647
+ if (c_dcg_condition_is_else(child->condition_to_parent)) {
648
+ if (!fallback) fallback = child;
649
+ continue;
650
+ }
651
+ if (c_dcg_condition_matches(child->condition_to_parent, &node->out)) return child;
652
+ }
653
+ return fallback;
654
+ }
655
+
656
+ /**
657
+ * @brief Evaluate one node as part of a walk: the protocol and its bookkeeping.
658
+ *
659
+ * The three stages run (c_dcg_node_eval_hooks), and then what happened is
660
+ * written onto the node: which stages completed, what it ended with, the run it
661
+ * belongs to, how deep it sat and that it has been visited once more. The node
662
+ * is announced afterwards with DCG_NODE_EVENT_EVALUATED - carrying the run's id
663
+ * as the event's sequence id - but only when its evaluation completed, because
664
+ * an event named "evaluated" about a node that produced no value is a lie a
665
+ * listener would act on. A node that failed is left with the code in its
666
+ * context, which is what a listener asking about it can read.
667
+ *
668
+ * The node is recorded in the path when one is lent, whether the evaluation
669
+ * succeeded or not: the record of how a decision was reached has to be able to
670
+ * show where it stopped being reached.
671
+ *
672
+ * @param node Node to evaluate.
673
+ * @param run The walk's state.
674
+ * @param depth How deep the node sits.
675
+ * @param root The root a walk started from, when it started from one - the
676
+ * record is then the root's own (may be NULL).
677
+ * @param path Record to add the node to, when it is not a root's (may be NULL).
678
+ * @return DCG_OK, or the code that stopped the node.
679
+ */
680
+ static inline int c_dcg_node_eval_visit(dcg_node* node, dcg_eval_run* run, size_t depth, dcg_root_node* root, dcg_node_eval_path* path) {
681
+ if (!node || !run) return DCG_ERR_INVALID_ARG;
682
+
683
+ dcg_node_eval_ctx* ctx = &node->eval_ctx;
684
+ void* previous_run = ctx->run;
685
+
686
+ ctx->run = run;
687
+ int ret_code = c_dcg_node_eval_hooks(node);
688
+ ctx->run = previous_run;
689
+
690
+ ctx->err_code = ret_code;
691
+ ctx->eval_seq_id = run->seq_id;
692
+ ctx->depth = depth;
693
+ ctx->visits++;
694
+
695
+ if (root) (void) c_dcg_root_node_eval_path_append(root, node);
696
+ else if (path) (void) c_dcg_eval_path_append(path, node);
697
+
698
+ if (ret_code == DCG_OK) {
699
+ run->visited++;
700
+ if (depth > run->depth) run->depth = depth;
701
+ c_dcg_node_invoke_callbacks(node, DCG_NODE_EVENT_EVALUATED, node, run->seq_id);
702
+ }
703
+ else if (run->code == DCG_OK) {
704
+ run->code = ret_code;
705
+ run->failed = node;
706
+ }
707
+
708
+ return ret_code;
709
+ }
710
+
711
+ /**
712
+ * @brief The outcome of a walk, written where the record lives.
713
+ *
714
+ * A walk reports through two records at most - the root's own and the one a
715
+ * caller lent - and they are written with the same code so neither can be the
716
+ * one that forgot to say what happened.
717
+ *
718
+ * @param root Root whose record to write (may be NULL).
719
+ * @param path Record to write (may be NULL).
720
+ * @param run The walk's state.
721
+ * @param leaf The node the walk came to rest on (NULL when it did not land).
722
+ * @param failed The node that failed (NULL when none did).
723
+ */
724
+ static inline void c_dcg_eval_path_outcome(dcg_root_node* root, dcg_node_eval_path* path, const dcg_eval_run* run, dcg_node* leaf, dcg_node* failed) {
725
+ dcg_ret_code code = failed ? run->code : DCG_OK;
726
+ dcg_node* landed = failed ? NULL : leaf;
727
+
728
+ if (root) {
729
+ root->eval_path.code = code;
730
+ root->eval_path.leaf = landed;
731
+ root->eval_path.failed = failed;
732
+ root->eval_path.seq_id = run->seq_id;
733
+ }
734
+ if (path) {
735
+ path->code = code;
736
+ path->leaf = landed;
737
+ path->failed = failed;
738
+ path->seq_id = run->seq_id;
739
+ }
740
+ }
741
+
742
+ /**
743
+ * @brief Walk a graph from a node down to a leaf, evaluating as it goes.
744
+ *
745
+ * The walk is a loop, not a recursion: every step replaces the node with the
746
+ * child its value selected, so a graph as deep as a build can make one costs no
747
+ * stack. It stops at a leaf, at a node that asks not to be descended below
748
+ * (DCG_EVAL_FLAG_SKIP_CHILDREN), at a breakpoint with nothing under it, or at
749
+ * the first failure - and it reports where it stopped through `path` and through
750
+ * the code it returns.
751
+ *
752
+ * The path is reset first: the record belongs to this walk and to no other. The
753
+ * nodes visited are added to it in order, so the last entry is the node the walk
754
+ * came to rest on. The outcome goes with them - `code`, `leaf`, `failed` and the
755
+ * run's `seq_id` - and a walk that did not land leaves `leaf` NULL, so "which
756
+ * node did it land on?" is never answered with the node it broke at.
757
+ *
758
+ * @param node Node to start from - a root, or any node whose graph is to be
759
+ * evaluated from there down.
760
+ * @param path Record to fill (may be NULL). Its owner receives the blocks.
761
+ * @return DCG_OK when a leaf was reached, or the DCG_ERR_* that stopped the walk
762
+ * (DCG_ERR_NO_MATCH included: a branching node whose value selected none
763
+ * of its children and which has no else arm).
764
+ */
765
+ static inline int c_dcg_eval_walk(dcg_node* node, dcg_root_node* root, dcg_node_eval_path* path) {
766
+ if (!node) return DCG_ERR_INVALID_ARG;
767
+
768
+ /* A record with no room was never made, so a walk asked to fill one is
769
+ * refused before it starts: walking and then reporting a value with no
770
+ * record behind it is the one answer a caller asking for a record cannot
771
+ * use. A walk asked for NO record runs all the same. */
772
+ if (path && (!path->node || !path->eval_val)) return DCG_ERR_INVALID_ARG;
773
+
774
+ if (root) c_dcg_eval_path_reset(&root->eval_path);
775
+ if (path) c_dcg_eval_path_reset(path);
776
+
777
+ dcg_eval_run run;
778
+ memset(&run, 0, sizeof(run));
779
+ run.seq_id = c_dcg_eval_gen_seq_id(node);
780
+ run.code = DCG_OK;
781
+ run.inplace = true;
782
+
783
+ size_t current_depth = 0;
784
+ dcg_node* current = node;
785
+
786
+ while (current) {
787
+ int ret_code = c_dcg_node_eval_visit(current, &run, current_depth, root, path);
788
+ if (ret_code != DCG_OK) {
789
+ c_dcg_eval_path_outcome(root, path, &run, current, current);
790
+ return ret_code;
791
+ }
792
+
793
+ if (current->eval_ctx.flags & DCG_EVAL_FLAG_SKIP_CHILDREN) break;
794
+
795
+ /* A node with no children is where the walk ends - a breakpoint that was
796
+ * never connected included: there is nothing below it to resume into. */
797
+ if (!current->children) break;
798
+
799
+ dcg_node* child = c_dcg_eval_select_child(current);
800
+ if (!child) {
801
+ run.code = DCG_ERR_NO_MATCH;
802
+ c_dcg_eval_path_outcome(root, path, &run, current, current);
803
+ return DCG_ERR_NO_MATCH;
804
+ }
805
+
806
+ current = child;
807
+ current_depth++;
808
+ }
809
+
810
+ run.leaf = current;
811
+ c_dcg_eval_path_outcome(root, path, &run, current, NULL);
812
+ return DCG_OK;
813
+ }
814
+
815
+ /**
816
+ * @brief Walk a graph from a node down to a leaf, recording into a caller's path.
817
+ *
818
+ * @param node Node to start from - any node whose graph is to be evaluated from
819
+ * there down.
820
+ * @param path Record to fill (may be NULL: a walk asked for no record runs all
821
+ * the same).
822
+ * @return DCG_OK when a leaf was reached, or the DCG_ERR_* that stopped the walk
823
+ * (DCG_ERR_NO_MATCH included: a branching node whose value selected none
824
+ * of its children and which has no else arm).
825
+ */
826
+ static inline int c_dcg_node_eval_graph(dcg_node* node, dcg_node_eval_path* path) {
827
+ if (!node) return DCG_ERR_INVALID_ARG;
828
+ return c_dcg_eval_walk(node, NULL, path);
829
+ }
830
+
831
+ /**
832
+ * @brief Evaluate a graph from its root, recording into the root's own path.
833
+ *
834
+ * The root's eval path is the record of how the last walk reached its decision,
835
+ * so it is emptied and refilled rather than lent to a caller: a root keeps the
836
+ * one record of itself, and every walk replaces it. The record is the root's own
837
+ * - embedded in it, its blocks nested under it - which is why the walk is given
838
+ * the root and not the record.
839
+ *
840
+ * @param root Root to evaluate.
841
+ * @return DCG_OK, or the DCG_ERR_* that stopped the walk.
842
+ */
843
+ static inline int c_dcg_root_node_eval(dcg_root_node* root) {
844
+ if (!root) return DCG_ERR_INVALID_ARG;
845
+ return c_dcg_eval_walk(&root->base, root, NULL);
846
+ }
847
+
848
+ #endif // C_DCG_BAKE_EVAL_H