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,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
|