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,692 @@
|
|
|
1
|
+
#ifndef C_DCG_BAKE_COLLECTIONS_H
|
|
2
|
+
#define C_DCG_BAKE_COLLECTIONS_H
|
|
3
|
+
|
|
4
|
+
#include <stdbool.h>
|
|
5
|
+
#include <stddef.h>
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
#include <stdio.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <sys/types.h>
|
|
10
|
+
|
|
11
|
+
#include <cbase/allocator_protocol/c_allocator_protocol.h>
|
|
12
|
+
#include <cbase/bytemap/c_bytemap.h>
|
|
13
|
+
|
|
14
|
+
#include <decision_graph/decision_tree/bake/c_logic_group.h>
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The collection family: the logic groups that HOLD values rather than compute
|
|
18
|
+
* them. One of them so far, the mapping:
|
|
19
|
+
*
|
|
20
|
+
* - a mapping is a logic group whose entries are keyed by name, held in a
|
|
21
|
+
* bytemap index over a flat value array. An entry either holds a value the
|
|
22
|
+
* mapping owns (a string is copied) or refers to a value the caller owns.
|
|
23
|
+
*
|
|
24
|
+
* That it is a GROUP and not a node is the whole point, and it is the capi's
|
|
25
|
+
* shape: its LogicMapping is a LogicGroup subclass, so what it maps is the
|
|
26
|
+
* store a build runs inside - a node built within the group reads an entry
|
|
27
|
+
* through an attribute access, and the read stays live because the store
|
|
28
|
+
* outlives the graph.
|
|
29
|
+
*
|
|
30
|
+
* A sequence and a generator belong beside it and are not ported: nothing
|
|
31
|
+
* needs them yet, and a family added later costs one struct and its own _free,
|
|
32
|
+
* exactly as this one did.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ========== Constants ==========
|
|
36
|
+
|
|
37
|
+
/** Slots a mapping is created with when the caller does not say. */
|
|
38
|
+
#ifndef DCG_MAPPING_DEFAULT_CAPACITY
|
|
39
|
+
#define DCG_MAPPING_DEFAULT_CAPACITY 8U
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @brief Compile-time switch: an entry keeps the type it was given.
|
|
44
|
+
*
|
|
45
|
+
* A store's reads are resolved ONCE - to the type the entry had when the read was
|
|
46
|
+
* first evaluated - and from then on they hold a reference that PROMISES that
|
|
47
|
+
* type. Retyping the entry under such a reference breaks the promise: a string
|
|
48
|
+
* reference over a double hands a pointer nothing allocated to whoever
|
|
49
|
+
* dereferences it.
|
|
50
|
+
*
|
|
51
|
+
* With this on (the default, and what a baked graph wants) a write of another
|
|
52
|
+
* type into an entry that already holds one is REFUSED, so no promise can be
|
|
53
|
+
* broken and the read's fast path needs no check of its own. Define it to 0 and
|
|
54
|
+
* a store is a pool a caller may retype freely - the behavior a store had before
|
|
55
|
+
* this switch - and the read rule pays for a check that resolves an entry which
|
|
56
|
+
* has changed type all over again.
|
|
57
|
+
*/
|
|
58
|
+
#ifndef DCG_MAPPING_IMMUTABLE_DTYPE
|
|
59
|
+
#define DCG_MAPPING_IMMUTABLE_DTYPE 1
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
// ========== Structs ==========
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @brief A mapping logic group: the base group plus a key index over its slots.
|
|
66
|
+
*
|
|
67
|
+
* Two structures, each doing what it is good at:
|
|
68
|
+
* - `idx_mapping` is a bytemap keyed by the entry name, holding the slot
|
|
69
|
+
* INDEX as its value. It stores an index rather than a pointer into
|
|
70
|
+
* `slots`, because `slots` moves when it grows.
|
|
71
|
+
* - `slots` is the value array itself: n_slots used of capacity, growing by
|
|
72
|
+
* doubling. Values are contiguous, so a baked lookup is a hash plus an
|
|
73
|
+
* array index - no pointer chasing.
|
|
74
|
+
*
|
|
75
|
+
* Both are owned by the group, in the two ways there are: the slot block is a
|
|
76
|
+
* nested block of the group - as is the name, as is every variable node built
|
|
77
|
+
* over the store - so the group's own free releases them, while the bytemap
|
|
78
|
+
* keeps its table in a block of its own (see c_bytemap_ex_init), which is why
|
|
79
|
+
* this family has a _free of its own: it hands the table back before the group
|
|
80
|
+
* block goes.
|
|
81
|
+
*
|
|
82
|
+
* The base group must stay the FIRST member: a dcg_mapping_lgroup* is
|
|
83
|
+
* therefore a valid dcg_logic_group*.
|
|
84
|
+
*/
|
|
85
|
+
typedef struct dcg_mapping_lgroup {
|
|
86
|
+
dcg_logic_group base; // The common group header. Must stay first.
|
|
87
|
+
bytemap idx_mapping; // Entry name -> slot index (stored as a uintptr_t).
|
|
88
|
+
dcg_var_t* slots; // OWNED - nested block of `capacity` values.
|
|
89
|
+
size_t n_slots; // Slots in use.
|
|
90
|
+
size_t capacity; // Slots the block has room for.
|
|
91
|
+
bool frozen; // Sealed: no entry may be created in it. Reading and writing what it holds are unaffected.
|
|
92
|
+
} dcg_mapping_lgroup;
|
|
93
|
+
|
|
94
|
+
// ========== Forward Declarations ==========
|
|
95
|
+
|
|
96
|
+
// Lifecycle
|
|
97
|
+
static inline dcg_mapping_lgroup* c_dcg_mapping_lgroup_new(const char* name, size_t capacity, allocator_protocol* allocator);
|
|
98
|
+
static inline void c_dcg_mapping_lgroup_dealloc(dcg_mapping_lgroup* lgroup);
|
|
99
|
+
static inline void c_dcg_mapping_lgroup_free(dcg_mapping_lgroup* lgroup);
|
|
100
|
+
static inline void c_dcg_mapping_lgroup_clear(dcg_mapping_lgroup* lgroup);
|
|
101
|
+
|
|
102
|
+
// Setter
|
|
103
|
+
static inline int c_dcg_mapping_lgroup_set(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, dcg_var_t* value);
|
|
104
|
+
static inline int c_dcg_mapping_lgroup_set_ref(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, dcg_var_t* value);
|
|
105
|
+
static inline int c_dcg_mapping_lgroup_set_ptr(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, void* value);
|
|
106
|
+
static inline int c_dcg_mapping_lgroup_set_double(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, double value);
|
|
107
|
+
static inline int c_dcg_mapping_lgroup_set_int(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, ssize_t value);
|
|
108
|
+
static inline int c_dcg_mapping_lgroup_set_offset(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, ssize_t value);
|
|
109
|
+
static inline int c_dcg_mapping_lgroup_set_bool(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, bool value);
|
|
110
|
+
|
|
111
|
+
// Getter
|
|
112
|
+
static inline int c_dcg_mapping_lgroup_get_var_idx(const dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, size_t* out_index);
|
|
113
|
+
static inline dcg_variable_node* c_dcg_mapping_lgroup_get_node(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, allocator_protocol* allocator);
|
|
114
|
+
|
|
115
|
+
// Evaluation - what a read of an entry evaluates to (see c_eval.h)
|
|
116
|
+
static inline int c_dcg_node_mapping_var_node_eval_hook(dcg_node* header, void* user_data);
|
|
117
|
+
|
|
118
|
+
// Internal helpers (exposed for reuse and testing - not part of the stable surface)
|
|
119
|
+
static inline dcg_var_t* c_dcg_mapping_lgroup_get_slot(const dcg_mapping_lgroup* lgroup, const char* key, size_t key_len);
|
|
120
|
+
static inline int c_dcg_mapping_lgroup_get_create_slot(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, const dcg_var_type* var_type, dcg_var_t** out);
|
|
121
|
+
|
|
122
|
+
// ========== Evaluation ==========
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @brief What a read of an entry evaluates to: the store's answer, once.
|
|
126
|
+
*
|
|
127
|
+
* The slot says which of the two states the read is in, and the store's promise
|
|
128
|
+
* is not part of it - a read of a frozen store resolves exactly like a read of a
|
|
129
|
+
* free one, and for the same reason: an entry either holds a value yet or it does
|
|
130
|
+
* not, and only the entry can say which:
|
|
131
|
+
*
|
|
132
|
+
* - VAR_TYPE_INFERRED (or VAR_TYPE_OFFSET, the same state in the other tag):
|
|
133
|
+
* unresolved. The slot holds the entry's offset, which the store handed out
|
|
134
|
+
* and never retires - it has no pop - so the entry is that many slots into the
|
|
135
|
+
* block. This is where the offset is spent, and the first evaluation is the
|
|
136
|
+
* last one that finds this state;
|
|
137
|
+
* - anything else: the reference the read resolved to. It is the fast path, and
|
|
138
|
+
* it is where every evaluation after the first lands. The promise that
|
|
139
|
+
* reference makes is kept by the STORE rather than checked here: an entry
|
|
140
|
+
* keeps its type (see DCG_MAPPING_IMMUTABLE_DTYPE), and only a store built
|
|
141
|
+
* with that guard off is one whose reads must resolve again.
|
|
142
|
+
*
|
|
143
|
+
* Called for a read the store BUILT. A variable built by hand, or one reading a
|
|
144
|
+
* group that is not a mapping, carries none of these states and is answered by
|
|
145
|
+
* the dispatch instead (see c_dcg_node_eval_default).
|
|
146
|
+
*
|
|
147
|
+
* @param header The read to evaluate.
|
|
148
|
+
* @param user_data Unused: the hooks share one signature.
|
|
149
|
+
* @return DCG_OK when the read is answered, DCG_ERR_UNBOUND when its entry holds
|
|
150
|
+
* nothing yet, or a DCG_ERR_* from the mint.
|
|
151
|
+
*/
|
|
152
|
+
static inline int c_dcg_node_mapping_var_node_eval_hook(dcg_node* header, void* user_data) {
|
|
153
|
+
(void) user_data;
|
|
154
|
+
|
|
155
|
+
dcg_var_t* slot = &header->out;
|
|
156
|
+
|
|
157
|
+
/* Unresolved, or already the reference the resolution left: the fast path is
|
|
158
|
+
* every evaluation after the first, and it is one tag test wide. */
|
|
159
|
+
dcg_var_type dtype = slot->dtype;
|
|
160
|
+
if (dtype != VAR_TYPE_INFERRED && dtype != VAR_TYPE_OFFSET) {
|
|
161
|
+
#if !DCG_MAPPING_IMMUTABLE_DTYPE
|
|
162
|
+
/* A store a caller may retype can break the promise its reads hold (see
|
|
163
|
+
* DCG_MAPPING_IMMUTABLE_DTYPE), so where that is allowed the promise is
|
|
164
|
+
* checked here - in the one place that knows the reference was taken to an
|
|
165
|
+
* entry of this store's block, one member back from the address it holds
|
|
166
|
+
* (see dcg_var_variant). An entry that is no longer what the tag names is
|
|
167
|
+
* resolved AGAIN, so the read follows the entry it names whatever type it
|
|
168
|
+
* has become. With the entry's type immutable there is nothing to check,
|
|
169
|
+
* and the fast path stays one test wide. */
|
|
170
|
+
dcg_var_t* entry = (dcg_var_t*) ((char*) slot->value.as_ref - offsetof(dcg_var_t, value));
|
|
171
|
+
if (c_dcg_var_ref_base(entry->dtype) != c_dcg_var_ref_base(dtype)) return c_dcg_var_init_ref(slot, entry);
|
|
172
|
+
#endif
|
|
173
|
+
return DCG_OK;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* The offset is the payload, read as the member it was written as and not
|
|
177
|
+
* through c_dcg_var_as_offset: that reader follows the base tag, and an
|
|
178
|
+
* INFERRED base is RESERVED, which no reader resolves - the tag that makes
|
|
179
|
+
* the slot inert is the same one that keeps the offset out of reach of it. */
|
|
180
|
+
dcg_mapping_lgroup* mapping = (dcg_mapping_lgroup*) ((dcg_variable_node*) header)->logic_group;
|
|
181
|
+
dcg_var_t* entry = &mapping->slots[slot->value.as_offset];
|
|
182
|
+
if (entry->dtype == VAR_TYPE_RESERVED) return DCG_ERR_UNBOUND; /* the entry holds nothing yet */
|
|
183
|
+
|
|
184
|
+
/* The resolution, and it happens once: the offset becomes a reference to the
|
|
185
|
+
* entry it named - the reference every later evaluation takes the fast path
|
|
186
|
+
* on, until the store is written another value into that entry. */
|
|
187
|
+
return c_dcg_var_init_ref(slot, entry);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ========== Lifecycle Methods ==========
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @brief Allocate a mapping group with room for `capacity` entries.
|
|
194
|
+
*
|
|
195
|
+
* The base group header is initialized first - a mapping is a group - and the
|
|
196
|
+
* entry index and slot block follow.
|
|
197
|
+
*
|
|
198
|
+
* @param name Group name to copy (may be NULL).
|
|
199
|
+
* @param capacity Initial slot capacity (clamped to DCG_MAPPING_DEFAULT_CAPACITY if 0).
|
|
200
|
+
* @param allocator Allocator for the block; NULL falls back to the plain heap.
|
|
201
|
+
* @return The group, or NULL on OOM.
|
|
202
|
+
*/
|
|
203
|
+
static inline dcg_mapping_lgroup* c_dcg_mapping_lgroup_new(const char* name, size_t capacity, allocator_protocol* allocator) {
|
|
204
|
+
if (capacity == 0) capacity = DCG_MAPPING_DEFAULT_CAPACITY;
|
|
205
|
+
|
|
206
|
+
dcg_mapping_lgroup* lgroup = (dcg_mapping_lgroup*) c_ap_alloc(sizeof(dcg_mapping_lgroup), allocator);
|
|
207
|
+
if (!lgroup) return NULL;
|
|
208
|
+
|
|
209
|
+
if (c_dcg_logic_group_init(&lgroup->base, DCG_LG_MAPPING, name) != DCG_OK) {
|
|
210
|
+
c_ap_free_owned(lgroup);
|
|
211
|
+
return NULL;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* One pointer-sized value slot per entry: the index of the value in `slots`. */
|
|
215
|
+
if (c_bytemap_ex_init(&lgroup->idx_mapping, capacity, sizeof(uintptr_t), c_ap_protocol_from_ptr(lgroup)) != BYTEMAP_OK) {
|
|
216
|
+
c_ap_free_owned(lgroup);
|
|
217
|
+
return NULL;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
lgroup->slots = (dcg_var_t*) c_ap_alloc_child(capacity * sizeof(dcg_var_t), NULL, lgroup);
|
|
221
|
+
if (!lgroup->slots) {
|
|
222
|
+
c_bytemap_ex_dealloc(&lgroup->idx_mapping); /* the table is a block of its own, not a child */
|
|
223
|
+
c_ap_free_owned(lgroup);
|
|
224
|
+
return NULL;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (size_t i = 0; i < capacity; i++) (void) c_dcg_var_init(&lgroup->slots[i]);
|
|
228
|
+
lgroup->n_slots = 0;
|
|
229
|
+
lgroup->capacity = capacity;
|
|
230
|
+
return lgroup;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @brief Tear down a mapping's contents, leaving the group buf alone.
|
|
235
|
+
*
|
|
236
|
+
* Two things go, in this order: the entry index, whose table is a block of its
|
|
237
|
+
* own and is handed back first (while the field that names it is still intact),
|
|
238
|
+
* and then the base group's half, which releases the name. The entry slots, the
|
|
239
|
+
* string copies of the entries and every variable node built over the store are
|
|
240
|
+
* nested blocks of the group block, so they go with it rather than here.
|
|
241
|
+
*
|
|
242
|
+
* @param lgroup Group to tear down (NULL-safe).
|
|
243
|
+
*/
|
|
244
|
+
static inline void c_dcg_mapping_lgroup_dealloc(dcg_mapping_lgroup* lgroup) {
|
|
245
|
+
if (!lgroup) return;
|
|
246
|
+
c_bytemap_ex_dealloc(&lgroup->idx_mapping); /* a table block of its own, not a child */
|
|
247
|
+
c_dcg_logic_group_dealloc(&lgroup->base);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @brief Tear down a mapping group and free its buf.
|
|
252
|
+
*
|
|
253
|
+
* The clean half first, then the block.
|
|
254
|
+
*
|
|
255
|
+
* @param lgroup Group to free (NULL-safe).
|
|
256
|
+
*/
|
|
257
|
+
static inline void c_dcg_mapping_lgroup_free(dcg_mapping_lgroup* lgroup) {
|
|
258
|
+
if (!lgroup) return;
|
|
259
|
+
c_dcg_mapping_lgroup_dealloc(lgroup);
|
|
260
|
+
c_ap_free_owned(lgroup);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @brief Empty a mapping's entries, keeping the store itself.
|
|
265
|
+
*
|
|
266
|
+
* Every entry goes and nothing else does: each slot in use is released in place
|
|
267
|
+
* and the index that names it is emptied, while the slots block, its capacity
|
|
268
|
+
* and the group around them stay exactly where they were. That is what makes
|
|
269
|
+
* this different from a free - entries are dropped, not the store.
|
|
270
|
+
*
|
|
271
|
+
* The slots are emptied IN PLACE on purpose. A read that has been evaluated
|
|
272
|
+
* holds the entry it resolved to - an offset into this block - so a clear that
|
|
273
|
+
* moved or released the block would leave every read in a graph pointing at
|
|
274
|
+
* freed storage. Emptied, the block still holds the address the read names, and
|
|
275
|
+
* the read reports what an unfilled entry reports rather than reading rubbish.
|
|
276
|
+
*
|
|
277
|
+
* A frozen store is not exempt: freezing seals the SHAPE, and this changes the
|
|
278
|
+
* contents the seal was never about. Anything that removes entries cannot be
|
|
279
|
+
* undone, so a caller that clears a store a graph is baked against has to fill
|
|
280
|
+
* the entries it needs again before evaluating.
|
|
281
|
+
*
|
|
282
|
+
* @param lgroup Group to empty (NULL-safe).
|
|
283
|
+
*/
|
|
284
|
+
static inline void c_dcg_mapping_lgroup_clear(dcg_mapping_lgroup* lgroup) {
|
|
285
|
+
if (!lgroup) return;
|
|
286
|
+
|
|
287
|
+
for (size_t i = 0; i < lgroup->n_slots; i++) c_dcg_var_dealloc(&lgroup->slots[i]);
|
|
288
|
+
c_bytemap_ex_clear(&lgroup->idx_mapping); /* the table block stays; only its entries go */
|
|
289
|
+
lgroup->n_slots = 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ========== Internal Helpers ==========
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @brief Get a slot: the one an entry occupies, or NULL when the key is not held.
|
|
296
|
+
*
|
|
297
|
+
* @param lgroup Group to read (NULL-safe).
|
|
298
|
+
* @param key Entry name.
|
|
299
|
+
* @param key_len Length of key.
|
|
300
|
+
* @return The live slot, or NULL.
|
|
301
|
+
*/
|
|
302
|
+
/**
|
|
303
|
+
* @brief Find the entry a key names, as the INDEX of its slot in the store.
|
|
304
|
+
*
|
|
305
|
+
* The index, not a pointer to the slot, and the difference is the whole reason
|
|
306
|
+
* this exists: `slots` is a block that GROWS, and growing it moves every entry
|
|
307
|
+
* in it. A pointer taken today is wrong the moment the store takes one more
|
|
308
|
+
* entry; an index is an offset into whatever block the store has now, so it
|
|
309
|
+
* outlives the growth - which is what lets a read built over an entry keep
|
|
310
|
+
* reading it after the store has grown around it.
|
|
311
|
+
*
|
|
312
|
+
* @param lgroup Group to read (NULL-safe).
|
|
313
|
+
* @param key Entry name.
|
|
314
|
+
* @param key_len Length of key.
|
|
315
|
+
* @param out_index Receives the index of the entry's slot.
|
|
316
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, or DCG_ERR_NOT_FOUND (no such entry).
|
|
317
|
+
*/
|
|
318
|
+
static inline int c_dcg_mapping_lgroup_get_var_idx(const dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, size_t* out_index) {
|
|
319
|
+
if (out_index) *out_index = 0;
|
|
320
|
+
if (!lgroup || !key || !out_index) return DCG_ERR_INVALID_ARG;
|
|
321
|
+
|
|
322
|
+
void* stored = NULL;
|
|
323
|
+
if (c_bytemap_get(&lgroup->idx_mapping, key, key_len, &stored) != BYTEMAP_OK) return DCG_ERR_NOT_FOUND;
|
|
324
|
+
|
|
325
|
+
size_t index = (size_t) (uintptr_t) stored;
|
|
326
|
+
if (index >= lgroup->n_slots) return DCG_ERR_NOT_FOUND;
|
|
327
|
+
|
|
328
|
+
*out_index = index;
|
|
329
|
+
return DCG_OK;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
static inline dcg_var_t* c_dcg_mapping_lgroup_get_slot(const dcg_mapping_lgroup* lgroup, const char* key, size_t key_len) {
|
|
333
|
+
size_t index = 0;
|
|
334
|
+
if (c_dcg_mapping_lgroup_get_var_idx(lgroup, key, key_len, &index) != DCG_OK) return NULL;
|
|
335
|
+
return (dcg_var_t*) &lgroup->slots[index]; /* the index came out of the table, so the block holds it */
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @brief Get or create a slot: the one an entry occupies, taking one when the
|
|
340
|
+
* key is new.
|
|
341
|
+
*
|
|
342
|
+
* One hash and one probe walk, whether the key is known or new: the index the
|
|
343
|
+
* mapping would hand out next is offered to the map as the default, and
|
|
344
|
+
* c_bytemap_ex_set_default either finds the key and hands back its own index or
|
|
345
|
+
* writes that default. A get followed by a set would hash the key twice and walk
|
|
346
|
+
* the table twice for the same answer.
|
|
347
|
+
*
|
|
348
|
+
* The slot block grows by doubling, and only when the key really was new; the
|
|
349
|
+
* bytemap holds the slot INDEX, so a move does not invalidate the index - which
|
|
350
|
+
* is why the index, rather than a pointer into the block, is what the map holds.
|
|
351
|
+
*
|
|
352
|
+
* A FROZEN group creates nothing NEW. This is the single point where an entry
|
|
353
|
+
* comes into being, so the check sits exactly there and every path - a setter, a
|
|
354
|
+
* reservation - inherits it, while an entry the group already holds is still
|
|
355
|
+
* served by both. It is the store's SHAPE that is sealed, not its contents, and
|
|
356
|
+
* the code says which way it went: DCG_ERR_BUSY for the store that said no,
|
|
357
|
+
* DCG_ERR_OOM for a machine that ran out.
|
|
358
|
+
*
|
|
359
|
+
* A NEW entry takes the type the caller names in `var_type`, or comes into
|
|
360
|
+
* being RESERVED when the caller names none - a slot with room for a value and
|
|
361
|
+
* no opinion yet about what it will hold, which is what a read of an entry that
|
|
362
|
+
* is not written yet needs. An entry that is ALREADY there keeps the type it
|
|
363
|
+
* has: the caller's type is checked against it in vigilant mode and the
|
|
364
|
+
* mismatch is reported - never asserted, since the store is not the side being
|
|
365
|
+
* contradicted - and RESERVED and INFERRED mean "no type claimed", so neither is
|
|
366
|
+
* ever a mismatch. What the entry holds is the truth either way.
|
|
367
|
+
*
|
|
368
|
+
* @param lgroup Group to modify.
|
|
369
|
+
* @param key Entry name.
|
|
370
|
+
* @param key_len Length of key.
|
|
371
|
+
* @param var_type Type the caller wants a NEW entry to have (NULL, RESERVED and
|
|
372
|
+
* INFERRED all mean "no type named"). Never written to.
|
|
373
|
+
* @param out Receives the slot: the live slot on DCG_OK, NULL on any failure.
|
|
374
|
+
* WHERE it is, the caller has: the block and the slot are both in
|
|
375
|
+
* front of it, and a subtraction is the index.
|
|
376
|
+
* @return DCG_OK; DCG_ERR_INVALID_ARG for a NULL argument; DCG_ERR_BUSY for a new
|
|
377
|
+
* key in a frozen group; DCG_ERR_OOM; DCG_ERR_RANGE for an index this
|
|
378
|
+
* mapping never handed out.
|
|
379
|
+
*/
|
|
380
|
+
static inline int c_dcg_mapping_lgroup_get_create_slot(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, const dcg_var_type* var_type, dcg_var_t** out) {
|
|
381
|
+
/* The out slot is cleared first, so a caller that ignores the code still
|
|
382
|
+
* cannot read a slot out of a call that did not take one. */
|
|
383
|
+
if (!out) return DCG_ERR_INVALID_ARG;
|
|
384
|
+
*out = NULL;
|
|
385
|
+
|
|
386
|
+
if (!lgroup || !key) return DCG_ERR_INVALID_ARG;
|
|
387
|
+
|
|
388
|
+
uintptr_t index = (uintptr_t) lgroup->n_slots;
|
|
389
|
+
char* stored = NULL;
|
|
390
|
+
size_t stored_len = 0;
|
|
391
|
+
|
|
392
|
+
if (c_bytemap_ex_set_default(&lgroup->idx_mapping, key, key_len, (const char*) &index, sizeof(index), 0, &stored, &stored_len) != BYTEMAP_OK) return DCG_ERR_OOM;
|
|
393
|
+
if (!stored || stored_len != sizeof(uintptr_t)) return DCG_ERR_OOM;
|
|
394
|
+
|
|
395
|
+
uintptr_t slot_index = 0;
|
|
396
|
+
memcpy(&slot_index, stored, sizeof(slot_index));
|
|
397
|
+
|
|
398
|
+
/* The key was new exactly when the map handed back the default, which is the
|
|
399
|
+
* index of a slot this block does not have yet. */
|
|
400
|
+
if (slot_index == lgroup->n_slots) {
|
|
401
|
+
if (lgroup->frozen) {
|
|
402
|
+
c_bytemap_pop(&lgroup->idx_mapping, key, key_len, NULL); /* take the index back */
|
|
403
|
+
return DCG_ERR_BUSY; /* sealed: nothing new appears here */
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (lgroup->n_slots == lgroup->capacity) {
|
|
407
|
+
size_t new_capacity = lgroup->capacity ? lgroup->capacity * 2 : DCG_MAPPING_DEFAULT_CAPACITY;
|
|
408
|
+
void* grown = c_ap_realloc(lgroup->slots, new_capacity * sizeof(dcg_var_t), NULL);
|
|
409
|
+
if (!grown) {
|
|
410
|
+
c_bytemap_pop(&lgroup->idx_mapping, key, key_len, NULL); /* take the index back */
|
|
411
|
+
return DCG_ERR_OOM;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
lgroup->slots = (dcg_var_t*) grown;
|
|
415
|
+
for (size_t i = lgroup->capacity; i < new_capacity; i++) (void) c_dcg_var_init(&lgroup->slots[i]);
|
|
416
|
+
lgroup->capacity = new_capacity;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
(void) c_dcg_var_init_reserved(&lgroup->slots[slot_index]); /* born holding nothing */
|
|
420
|
+
if (var_type && *var_type != VAR_TYPE_RESERVED && *var_type != VAR_TYPE_INFERRED) lgroup->slots[slot_index].dtype = *var_type;
|
|
421
|
+
|
|
422
|
+
lgroup->n_slots++;
|
|
423
|
+
}
|
|
424
|
+
else if (var_type && *var_type != VAR_TYPE_RESERVED && *var_type != VAR_TYPE_INFERRED) {
|
|
425
|
+
/* The entry is already there and the caller named a type for it: if it
|
|
426
|
+
* holds another one, the caller is working from a wrong idea of the
|
|
427
|
+
* store and is told so. Reported rather than refused - the slot is what
|
|
428
|
+
* the store has and it is handed back as it is. */
|
|
429
|
+
#if DCG_VIGILANT
|
|
430
|
+
if (lgroup->slots[slot_index].dtype != *var_type) {
|
|
431
|
+
(void) fprintf(
|
|
432
|
+
stderr, "[DCG] c_dcg_mapping_lgroup_get_create_slot: entry \"%.*s\" is %s, not the %s the caller named - the entry keeps what it holds\n", (int) key_len,
|
|
433
|
+
key, c_dcg_var_type_name(lgroup->slots[slot_index].dtype), c_dcg_var_type_name(*var_type)
|
|
434
|
+
);
|
|
435
|
+
(void) fflush(stderr);
|
|
436
|
+
}
|
|
437
|
+
#endif
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (slot_index >= lgroup->n_slots) return DCG_ERR_RANGE; /* not an index this mapping handed out */
|
|
441
|
+
*out = &lgroup->slots[slot_index];
|
|
442
|
+
return DCG_OK;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ========== Public APIs - Setters ==========
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* @brief Store the value another var holds under a key.
|
|
449
|
+
*
|
|
450
|
+
* The value is what is stored, not the var: a string is copied in, a scalar or
|
|
451
|
+
* pointer is taken as it is, and what the caller does with its own var
|
|
452
|
+
* afterwards is its own business. Use c_dcg_mapping_lgroup_set_ref() when the
|
|
453
|
+
* entry should follow that var instead.
|
|
454
|
+
*
|
|
455
|
+
* @param lgroup Group to modify.
|
|
456
|
+
* @param key Entry name.
|
|
457
|
+
* @param key_len Length of key (excluding any NUL).
|
|
458
|
+
* @param value Value to store (read now).
|
|
459
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_RANGE or DCG_ERR_OOM.
|
|
460
|
+
*/
|
|
461
|
+
static inline int c_dcg_mapping_lgroup_set(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, dcg_var_t* value) {
|
|
462
|
+
if (!lgroup || !key || !value) return DCG_ERR_INVALID_ARG;
|
|
463
|
+
|
|
464
|
+
dcg_var_t* slot = NULL;
|
|
465
|
+
int ret_code = c_dcg_mapping_lgroup_get_create_slot(lgroup, key, key_len, NULL, &slot);
|
|
466
|
+
if (ret_code != DCG_OK) return ret_code;
|
|
467
|
+
|
|
468
|
+
#if DCG_MAPPING_IMMUTABLE_DTYPE
|
|
469
|
+
/* An entry keeps the type it was given (see DCG_MAPPING_IMMUTABLE_DTYPE): the
|
|
470
|
+
* reads of a graph resolve to it once and hold a reference that promises it,
|
|
471
|
+
* so a write of another type is refused rather than taken. A reference counts
|
|
472
|
+
* as the type it points at, so a slot holding a value may be pointed at
|
|
473
|
+
* another slot holding the same one. */
|
|
474
|
+
if (slot->dtype != VAR_TYPE_RESERVED && c_dcg_var_ref_base(slot->dtype) != c_dcg_var_ref_base(value->dtype)) {
|
|
475
|
+
#if DCG_VIGILANT
|
|
476
|
+
(void) fprintf(
|
|
477
|
+
stderr, "[DCG] c_dcg_mapping_lgroup_set: entry \"%.*s\" holds a %s; a %s is not what it is - the entry keeps what it holds\n", (int) key_len, key,
|
|
478
|
+
c_dcg_var_type_name(slot->dtype), c_dcg_var_type_name(value->dtype)
|
|
479
|
+
);
|
|
480
|
+
(void) fflush(stderr);
|
|
481
|
+
#endif
|
|
482
|
+
return DCG_ERR_TYPE;
|
|
483
|
+
}
|
|
484
|
+
#endif
|
|
485
|
+
|
|
486
|
+
/* What the slot held goes first: a string value is a nested block of this
|
|
487
|
+
* group, and it is about to be replaced. */
|
|
488
|
+
if (slot->dtype == VAR_TYPE_STRING && slot->value.as_string) {
|
|
489
|
+
c_ap_free_owned((void*) slot->value.as_string);
|
|
490
|
+
(void) c_dcg_var_init(slot);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (value->dtype != VAR_TYPE_STRING) {
|
|
494
|
+
*slot = *value;
|
|
495
|
+
return DCG_OK;
|
|
496
|
+
}
|
|
497
|
+
if (!value->value.as_string) return c_dcg_var_init_string(slot, NULL);
|
|
498
|
+
|
|
499
|
+
/* The text is copied into a block nested under this group, so the caller's
|
|
500
|
+
* string can go away at once. */
|
|
501
|
+
size_t len = strlen(value->value.as_string);
|
|
502
|
+
char* copy = (char*) c_ap_alloc_child(len + 1, NULL, lgroup);
|
|
503
|
+
if (!copy) return DCG_ERR_OOM;
|
|
504
|
+
memcpy(copy, value->value.as_string, len + 1);
|
|
505
|
+
return c_dcg_var_init_string(slot, copy);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* @brief Store a REFERENCE to another var under a key.
|
|
510
|
+
*
|
|
511
|
+
* The entry reads what that var holds, at the moment it is read, for as long as
|
|
512
|
+
* the var outlives the mapping - which is how a mapping entry points at a
|
|
513
|
+
* caller's variable without copying it.
|
|
514
|
+
*
|
|
515
|
+
* @param lgroup Group to modify.
|
|
516
|
+
* @param key Entry name.
|
|
517
|
+
* @param key_len Length of key.
|
|
518
|
+
* @param value Value to refer to (must outlive the mapping).
|
|
519
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
|
|
520
|
+
*/
|
|
521
|
+
static inline int c_dcg_mapping_lgroup_set_ref(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, dcg_var_t* value) {
|
|
522
|
+
if (!lgroup || !key || !value) return DCG_ERR_INVALID_ARG;
|
|
523
|
+
|
|
524
|
+
dcg_var_t* slot = NULL;
|
|
525
|
+
int ret_code = c_dcg_mapping_lgroup_get_create_slot(lgroup, key, key_len, NULL, &slot);
|
|
526
|
+
if (ret_code != DCG_OK) return ret_code;
|
|
527
|
+
|
|
528
|
+
#if DCG_MAPPING_IMMUTABLE_DTYPE
|
|
529
|
+
/* An entry keeps the type it was given (see DCG_MAPPING_IMMUTABLE_DTYPE): the
|
|
530
|
+
* reads of a graph resolve to it once and hold a reference that promises it,
|
|
531
|
+
* so a write of another type is refused rather than taken. A reference counts
|
|
532
|
+
* as the type it points at, so a slot holding a value may be pointed at
|
|
533
|
+
* another slot holding the same one. */
|
|
534
|
+
if (slot->dtype != VAR_TYPE_RESERVED && c_dcg_var_ref_base(slot->dtype) != c_dcg_var_ref_base(value->dtype)) {
|
|
535
|
+
#if DCG_VIGILANT
|
|
536
|
+
(void) fprintf(
|
|
537
|
+
stderr, "[DCG] c_dcg_mapping_lgroup_set_ref: entry \"%.*s\" holds a %s; a %s is not what it is - the entry keeps what it holds\n", (int) key_len, key,
|
|
538
|
+
c_dcg_var_type_name(slot->dtype), c_dcg_var_type_name(value->dtype)
|
|
539
|
+
);
|
|
540
|
+
(void) fflush(stderr);
|
|
541
|
+
#endif
|
|
542
|
+
return DCG_ERR_TYPE;
|
|
543
|
+
}
|
|
544
|
+
#endif
|
|
545
|
+
|
|
546
|
+
if (slot->dtype == VAR_TYPE_STRING && slot->value.as_string) c_ap_free_owned((void*) slot->value.as_string);
|
|
547
|
+
return c_dcg_var_init_ref(slot, value);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* @brief Store a raw pointer under a key.
|
|
552
|
+
*
|
|
553
|
+
* @param lgroup Group to modify.
|
|
554
|
+
* @param key Entry name.
|
|
555
|
+
* @param key_len Length of key.
|
|
556
|
+
* @param value Pointer to store (not owned).
|
|
557
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
|
|
558
|
+
*/
|
|
559
|
+
static inline int c_dcg_mapping_lgroup_set_ptr(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, void* value) {
|
|
560
|
+
dcg_var_t packed;
|
|
561
|
+
(void) c_dcg_var_init_ptr(&packed, value);
|
|
562
|
+
return c_dcg_mapping_lgroup_set(lgroup, key, key_len, &packed);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* @brief Store a double under a key.
|
|
567
|
+
*
|
|
568
|
+
* @param lgroup Group to modify.
|
|
569
|
+
* @param key Entry name.
|
|
570
|
+
* @param key_len Length of key.
|
|
571
|
+
* @param value Value to store.
|
|
572
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
|
|
573
|
+
*/
|
|
574
|
+
static inline int c_dcg_mapping_lgroup_set_double(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, double value) {
|
|
575
|
+
dcg_var_t packed;
|
|
576
|
+
(void) c_dcg_var_init_double(&packed, value);
|
|
577
|
+
return c_dcg_mapping_lgroup_set(lgroup, key, key_len, &packed);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* @brief Store an integer under a key.
|
|
582
|
+
*
|
|
583
|
+
* @param lgroup Group to modify.
|
|
584
|
+
* @param key Entry name.
|
|
585
|
+
* @param key_len Length of key.
|
|
586
|
+
* @param value Value to store.
|
|
587
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
|
|
588
|
+
*/
|
|
589
|
+
static inline int c_dcg_mapping_lgroup_set_int(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, ssize_t value) {
|
|
590
|
+
dcg_var_t packed;
|
|
591
|
+
(void) c_dcg_var_init_int(&packed, value);
|
|
592
|
+
return c_dcg_mapping_lgroup_set(lgroup, key, key_len, &packed);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* @brief Store an offset under a key.
|
|
597
|
+
*
|
|
598
|
+
* @param lgroup Group to modify.
|
|
599
|
+
* @param key Entry name.
|
|
600
|
+
* @param key_len Length of key.
|
|
601
|
+
* @param value Value to store.
|
|
602
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
|
|
603
|
+
*/
|
|
604
|
+
static inline int c_dcg_mapping_lgroup_set_offset(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, ssize_t value) {
|
|
605
|
+
dcg_var_t packed;
|
|
606
|
+
(void) c_dcg_var_init_offset(&packed, value);
|
|
607
|
+
return c_dcg_mapping_lgroup_set(lgroup, key, key_len, &packed);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* @brief Store a boolean under a key.
|
|
612
|
+
*
|
|
613
|
+
* @param lgroup Group to modify.
|
|
614
|
+
* @param key Entry name.
|
|
615
|
+
* @param key_len Length of key.
|
|
616
|
+
* @param value Value to store.
|
|
617
|
+
* @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
|
|
618
|
+
*/
|
|
619
|
+
static inline int c_dcg_mapping_lgroup_set_bool(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, bool value) {
|
|
620
|
+
dcg_var_t packed;
|
|
621
|
+
(void) c_dcg_var_init_bool(&packed, value);
|
|
622
|
+
return c_dcg_mapping_lgroup_set(lgroup, key, key_len, &packed);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ========== Public APIs - Getters ==========
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* @brief A variable node reading an entry, or NULL when the key is not held.
|
|
629
|
+
*
|
|
630
|
+
* The node is allocated for the caller, which frees it with
|
|
631
|
+
* c_dcg_node_free_var(); its `logic_group` records this group and its `key` the
|
|
632
|
+
* name - and WHERE the entry is, it names in its own out slot.
|
|
633
|
+
*
|
|
634
|
+
* What the node is BORN with there is the same for every store: the entry's
|
|
635
|
+
* OFFSET, tagged VAR_TYPE_INFERRED - "the entry's type, whatever it is when you
|
|
636
|
+
* read me". What the read is resolved TO is not decided here and not by the
|
|
637
|
+
* store's `frozen`: it is decided by the entry, the first time the read is
|
|
638
|
+
* evaluated (see c_dcg_node_mapping_var_node_eval_hook). An entry a store holds
|
|
639
|
+
* nothing in, and an entry written after the read was built, are the same
|
|
640
|
+
* question with the same answer - and holding the read at a pointer into the
|
|
641
|
+
* block would answer a different one, because `slots` moves when the store
|
|
642
|
+
* grows, and a frozen store is a store that stops growing, not a store whose
|
|
643
|
+
* reads are already answered.
|
|
644
|
+
*
|
|
645
|
+
* From that resolution on, the slot is a reference INTO the store's block, so
|
|
646
|
+
* that block has to stay where it is. A store is not grown under a graph that
|
|
647
|
+
* reads it - the bake pass is what will hold it to that, and `frozen` is what a
|
|
648
|
+
* caller holds it to now.
|
|
649
|
+
*
|
|
650
|
+
* The repr is the attribute path the capi gives an AttrExpression -
|
|
651
|
+
* `group.key` - so a rendering names the entry the way the build did.
|
|
652
|
+
*
|
|
653
|
+
* The group is taken NON-const on purpose, and it is the one place in this
|
|
654
|
+
* file that does: the node this hands out reads the store live, long after the
|
|
655
|
+
* call returns, so the binding is to a store that is expected to move. Handing
|
|
656
|
+
* one out from a group the caller promised not to change would be a promise
|
|
657
|
+
* this call cannot keep on someone else's behalf.
|
|
658
|
+
*
|
|
659
|
+
* @param lgroup Group to read (NULL-safe).
|
|
660
|
+
* @param key Entry name.
|
|
661
|
+
* @param key_len Length of key.
|
|
662
|
+
* @param allocator Allocator for the node; NULL derives it from the group.
|
|
663
|
+
* @return The variable node, or NULL when the key is missing / on OOM.
|
|
664
|
+
*/
|
|
665
|
+
static inline dcg_variable_node* c_dcg_mapping_lgroup_get_node(dcg_mapping_lgroup* lgroup, const char* key, size_t key_len, allocator_protocol* allocator) {
|
|
666
|
+
dcg_var_t* slot = c_dcg_mapping_lgroup_get_slot(lgroup, key, key_len);
|
|
667
|
+
if (!slot) return NULL;
|
|
668
|
+
|
|
669
|
+
char repr[DCG_NODE_STRING_MAXLEN];
|
|
670
|
+
if (lgroup->base.name) (void) snprintf(repr, sizeof(repr), "%s.%.*s", lgroup->base.name, (int) key_len, key);
|
|
671
|
+
else (void) snprintf(repr, sizeof(repr), "%.*s", (int) key_len, key);
|
|
672
|
+
|
|
673
|
+
dcg_variable_node* node = c_dcg_node_new_var(repr, key, key_len, NULL, &lgroup->base, allocator);
|
|
674
|
+
if (!node) return NULL;
|
|
675
|
+
|
|
676
|
+
/* The read's beginning, and the same one for every store: where the entry is,
|
|
677
|
+
* and no type of its own. The offset is the payload, written as the member
|
|
678
|
+
* the rule reads it from. */
|
|
679
|
+
node->base.out.dtype = VAR_TYPE_INFERRED;
|
|
680
|
+
node->base.out.value.as_offset = (ssize_t) (slot - lgroup->slots);
|
|
681
|
+
|
|
682
|
+
/* A read made BY a store carries the store's own rule, in every build: what
|
|
683
|
+
* the entry holds now IS what the node reads, the store is what knows it, and
|
|
684
|
+
* the rule belongs to the read this constructor just made. A variable node
|
|
685
|
+
* built by hand - bound to a slot, with no store to look anything up in -
|
|
686
|
+
* carries no rule and is evaluated by the dispatch instead (see
|
|
687
|
+
* c_dcg_node_eval_default). */
|
|
688
|
+
node->base.eval_ctx.type_eval_fn = c_dcg_node_mapping_var_node_eval_hook;
|
|
689
|
+
return node;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
#endif // C_DCG_BAKE_COLLECTIONS_H
|