PyDecisionGraph 0.3.2.post1__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. decision_graph/__infra__.pxd +1 -0
  2. decision_graph/__init__.pxd +1 -0
  3. decision_graph/__init__.py +39 -0
  4. decision_graph/decision_tree/__init__.pxd +2 -0
  5. decision_graph/decision_tree/__init__.py +85 -0
  6. decision_graph/decision_tree/bake/__infra__.pxd +502 -0
  7. decision_graph/decision_tree/bake/__init__.pxd +502 -0
  8. decision_graph/decision_tree/bake/__init__.py +29 -0
  9. decision_graph/decision_tree/bake/c_action.cp315-win_amd64.pyd +0 -0
  10. decision_graph/decision_tree/bake/c_action.h +324 -0
  11. decision_graph/decision_tree/bake/c_action.pxd +54 -0
  12. decision_graph/decision_tree/bake/c_action.pyi +126 -0
  13. decision_graph/decision_tree/bake/c_action.pyx +65 -0
  14. decision_graph/decision_tree/bake/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  15. decision_graph/decision_tree/bake/c_allocator_protocol.pxd +24 -0
  16. decision_graph/decision_tree/bake/c_allocator_protocol.pyi +61 -0
  17. decision_graph/decision_tree/bake/c_allocator_protocol.pyx +109 -0
  18. decision_graph/decision_tree/bake/c_bake.cp315-win_amd64.pyd +0 -0
  19. decision_graph/decision_tree/bake/c_bake.h +629 -0
  20. decision_graph/decision_tree/bake/c_bake.pxd +52 -0
  21. decision_graph/decision_tree/bake/c_bake.pyi +151 -0
  22. decision_graph/decision_tree/bake/c_bake.pyx +142 -0
  23. decision_graph/decision_tree/bake/c_collections.cp315-win_amd64.pyd +0 -0
  24. decision_graph/decision_tree/bake/c_collections.h +692 -0
  25. decision_graph/decision_tree/bake/c_collections.pxd +52 -0
  26. decision_graph/decision_tree/bake/c_collections.pyi +193 -0
  27. decision_graph/decision_tree/bake/c_collections.pyx +161 -0
  28. decision_graph/decision_tree/bake/c_const.cp315-win_amd64.pyd +0 -0
  29. decision_graph/decision_tree/bake/c_const.h +395 -0
  30. decision_graph/decision_tree/bake/c_const.pxd +56 -0
  31. decision_graph/decision_tree/bake/c_const.pyi +305 -0
  32. decision_graph/decision_tree/bake/c_const.pyx +278 -0
  33. decision_graph/decision_tree/bake/c_edge.cp315-win_amd64.pyd +0 -0
  34. decision_graph/decision_tree/bake/c_edge.h +511 -0
  35. decision_graph/decision_tree/bake/c_edge.pxd +117 -0
  36. decision_graph/decision_tree/bake/c_edge.pyi +192 -0
  37. decision_graph/decision_tree/bake/c_edge.pyx +270 -0
  38. decision_graph/decision_tree/bake/c_eval.h +848 -0
  39. decision_graph/decision_tree/bake/c_eval.pxd +40 -0
  40. decision_graph/decision_tree/bake/c_expr.cp315-win_amd64.pyd +0 -0
  41. decision_graph/decision_tree/bake/c_expr.h +1900 -0
  42. decision_graph/decision_tree/bake/c_expr.pxd +129 -0
  43. decision_graph/decision_tree/bake/c_expr.pyi +280 -0
  44. decision_graph/decision_tree/bake/c_expr.pyx +272 -0
  45. decision_graph/decision_tree/bake/c_hierarchy.cp315-win_amd64.pyd +0 -0
  46. decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  47. decision_graph/decision_tree/bake/c_hierarchy.pxd +71 -0
  48. decision_graph/decision_tree/bake/c_hierarchy.pyi +409 -0
  49. decision_graph/decision_tree/bake/c_hierarchy.pyx +277 -0
  50. decision_graph/decision_tree/bake/c_logic_group.cp315-win_amd64.pyd +0 -0
  51. decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  52. decision_graph/decision_tree/bake/c_logic_group.pxd +162 -0
  53. decision_graph/decision_tree/bake/c_logic_group.pyi +264 -0
  54. decision_graph/decision_tree/bake/c_logic_group.pyx +308 -0
  55. decision_graph/decision_tree/bake/c_node.cp315-win_amd64.pyd +0 -0
  56. decision_graph/decision_tree/bake/c_node.h +2660 -0
  57. decision_graph/decision_tree/bake/c_node.pxd +381 -0
  58. decision_graph/decision_tree/bake/c_node.pyi +438 -0
  59. decision_graph/decision_tree/bake/c_node.pyx +639 -0
  60. decision_graph/decision_tree/bake/c_reconstruct.cp315-win_amd64.pyd +0 -0
  61. decision_graph/decision_tree/bake/c_reconstruct.pxd +63 -0
  62. decision_graph/decision_tree/bake/c_reconstruct.pyi +60 -0
  63. decision_graph/decision_tree/bake/c_reconstruct.pyx +252 -0
  64. decision_graph/decision_tree/bake/c_var.cp315-win_amd64.pyd +0 -0
  65. decision_graph/decision_tree/bake/c_var.h +2054 -0
  66. decision_graph/decision_tree/bake/c_var.pxd +205 -0
  67. decision_graph/decision_tree/bake/c_var.pyi +271 -0
  68. decision_graph/decision_tree/bake/c_var.pyx +228 -0
  69. decision_graph/decision_tree/capi/__infra__.pxd +33 -0
  70. decision_graph/decision_tree/capi/__init__.pxd +33 -0
  71. decision_graph/decision_tree/capi/__init__.py +57 -0
  72. decision_graph/decision_tree/capi/c_abc.cp315-win_amd64.pyd +0 -0
  73. decision_graph/decision_tree/capi/c_abc.pxd +246 -0
  74. decision_graph/decision_tree/capi/c_abc.pyi +770 -0
  75. decision_graph/decision_tree/capi/c_abc.pyx +1532 -0
  76. decision_graph/decision_tree/capi/c_collection.cp315-win_amd64.pyd +0 -0
  77. decision_graph/decision_tree/capi/c_collection.pxd +22 -0
  78. decision_graph/decision_tree/capi/c_collection.pyi +245 -0
  79. decision_graph/decision_tree/capi/c_collection.pyx +138 -0
  80. decision_graph/decision_tree/capi/c_node.cp315-win_amd64.pyd +0 -0
  81. decision_graph/decision_tree/capi/c_node.pxd +95 -0
  82. decision_graph/decision_tree/capi/c_node.pyi +492 -0
  83. decision_graph/decision_tree/capi/c_node.pyx +551 -0
  84. decision_graph/decision_tree/exc.py +189 -0
  85. decision_graph/decision_tree/native/__init__.py +57 -0
  86. decision_graph/decision_tree/native/abc.py +1167 -0
  87. decision_graph/decision_tree/native/collection.py +134 -0
  88. decision_graph/decision_tree/native/node.py +521 -0
  89. decision_graph/includes/decision_graph/decision_tree/bake/c_action.c +16320 -0
  90. decision_graph/includes/decision_graph/decision_tree/bake/c_action.h +324 -0
  91. decision_graph/includes/decision_graph/decision_tree/bake/c_allocator_protocol.c +12473 -0
  92. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.c +16485 -0
  93. decision_graph/includes/decision_graph/decision_tree/bake/c_bake.h +629 -0
  94. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.c +17381 -0
  95. decision_graph/includes/decision_graph/decision_tree/bake/c_collections.h +692 -0
  96. decision_graph/includes/decision_graph/decision_tree/bake/c_const.c +20815 -0
  97. decision_graph/includes/decision_graph/decision_tree/bake/c_const.h +395 -0
  98. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.c +20696 -0
  99. decision_graph/includes/decision_graph/decision_tree/bake/c_edge.h +511 -0
  100. decision_graph/includes/decision_graph/decision_tree/bake/c_eval.h +848 -0
  101. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.c +22065 -0
  102. decision_graph/includes/decision_graph/decision_tree/bake/c_expr.h +1900 -0
  103. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.c +23267 -0
  104. decision_graph/includes/decision_graph/decision_tree/bake/c_hierarchy.h +731 -0
  105. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.c +22434 -0
  106. decision_graph/includes/decision_graph/decision_tree/bake/c_logic_group.h +1226 -0
  107. decision_graph/includes/decision_graph/decision_tree/bake/c_node.c +28385 -0
  108. decision_graph/includes/decision_graph/decision_tree/bake/c_node.h +2660 -0
  109. decision_graph/includes/decision_graph/decision_tree/bake/c_reconstruct.c +15955 -0
  110. decision_graph/includes/decision_graph/decision_tree/bake/c_var.c +16881 -0
  111. decision_graph/includes/decision_graph/decision_tree/bake/c_var.h +2054 -0
  112. decision_graph/includes/decision_graph/decision_tree/capi/c_abc.c +59196 -0
  113. decision_graph/includes/decision_graph/decision_tree/capi/c_collection.c +19967 -0
  114. decision_graph/includes/decision_graph/decision_tree/capi/c_node.c +39229 -0
  115. decision_graph/logic_group/__init__.py +25 -0
  116. decision_graph/logic_group/base.py +100 -0
  117. decision_graph/logic_group/pending_request.py +384 -0
  118. decision_graph/webui/__init__.py +24 -0
  119. decision_graph/webui/bake/__init__.py +65 -0
  120. decision_graph/webui/bake/app.py +488 -0
  121. decision_graph/webui/bake/static/d3.v7.min.js +2 -0
  122. decision_graph/webui/bake/static/script.js +1440 -0
  123. decision_graph/webui/bake/static/style.css +920 -0
  124. decision_graph/webui/bake/templates/index.html +183 -0
  125. decision_graph/webui/bake/templates/offline.html +184 -0
  126. decision_graph/webui/capi/__init__.py +54 -0
  127. decision_graph/webui/capi/app.py +366 -0
  128. decision_graph/webui/capi/main.py +53 -0
  129. decision_graph/webui/capi/static/d3.v7.min.js +2 -0
  130. decision_graph/webui/capi/static/script.js +1026 -0
  131. decision_graph/webui/capi/static/style.css +656 -0
  132. decision_graph/webui/capi/templates/index.html +93 -0
  133. decision_graph/webui/capi/templates/offline.html +94 -0
  134. decision_graph/webui/native/__init__.py +54 -0
  135. decision_graph/webui/native/app.py +366 -0
  136. decision_graph/webui/native/main.py +53 -0
  137. decision_graph/webui/native/static/d3.v7.min.js +2 -0
  138. decision_graph/webui/native/static/script.js +1026 -0
  139. decision_graph/webui/native/static/style.css +656 -0
  140. decision_graph/webui/native/templates/index.html +93 -0
  141. decision_graph/webui/native/templates/offline.html +94 -0
  142. pydecisiongraph-0.3.2.post1.dist-info/METADATA +136 -0
  143. pydecisiongraph-0.3.2.post1.dist-info/RECORD +148 -0
  144. pydecisiongraph-0.3.2.post1.dist-info/WHEEL +5 -0
  145. pydecisiongraph-0.3.2.post1.dist-info/licenses/LICENSE +373 -0
  146. pydecisiongraph-0.3.2.post1.dist-info/top_level.txt +2 -0
  147. tests/bake_c_evaluator.cp315-win_amd64.pyd +0 -0
  148. tests/bake_eval_probe.cp315-win_amd64.pyd +0 -0
@@ -0,0 +1,1226 @@
1
+ #ifndef C_DCG_BAKE_LOGIC_GROUP_H
2
+ #define C_DCG_BAKE_LOGIC_GROUP_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_action.h>
15
+ #include <decision_graph/decision_tree/bake/c_hierarchy.h>
16
+ #include <decision_graph/decision_tree/bake/c_node.h>
17
+
18
+ /*
19
+ * The logic groups: the scopes a graph is built inside, and the manager that
20
+ * keeps track of which one is open.
21
+ *
22
+ * A group is a NAME, A TYPE and a PARENT - nothing else. What makes it more
23
+ * than a record is that a build runs inside one: a node built
24
+ * while groups are open carries their names as labels (which is how the capi's
25
+ * list_labels() finds the nodes of a group), and breaking out of a group is
26
+ * the only way a branch stops where it stands and resumes outside.
27
+ *
28
+ * The manager (bake's LGM) holds the three stacks a build runs on:
29
+ *
30
+ * - the open groups, innermost last;
31
+ * - the nodes being built, innermost last - the active node is the one a
32
+ * branch belongs to right now;
33
+ * - the breakpoints raised and not yet connected back into the graph.
34
+ *
35
+ * It also holds the registry groups are looked up by name in, the shelved
36
+ * states a sub-graph build puts the stacks away in (see c_dcg_lgm_shelve), and
37
+ * the two mode flags a build switches on.
38
+ *
39
+ * What is NOT here is the runtime half of a break. In the capi, breaking while
40
+ * building (the inspection mode) is what this header ports - the breakpoint is
41
+ * raised into the graph and connected to whatever is entered next. Breaking
42
+ * while EVALUATING is a control-flow signal, and control flow is the
43
+ * evaluator's, so it lands with the evaluator and not before.
44
+ *
45
+ * Ownership, like everywhere else in bake, is explicit: the manager holds
46
+ * BORROWED pointers to groups and nodes. A registry entry does not own its
47
+ * group - a group outlives any number of lookups, and several variable nodes
48
+ * may read the same one - so whoever built the groups releases them.
49
+ */
50
+
51
+ // ========== Constants ==========
52
+
53
+ /** Groups a fresh manager's registry is created with. */
54
+ #ifndef DCG_LGM_DEFAULT_CAPACITY
55
+ #define DCG_LGM_DEFAULT_CAPACITY 16U
56
+ #endif
57
+
58
+ // ========== Structs ==========
59
+
60
+ /*
61
+ * Forward-declared because the manager holds blocks of them: the struct itself
62
+ * is private to this header, and only its pointer ever crosses out of it.
63
+ */
64
+ typedef struct dcg_lgm_state dcg_lgm_state;
65
+
66
+ /**
67
+ * @brief Type of a logic group.
68
+ *
69
+ * One flat set rather than a family tree like the node types: every group is
70
+ * the same struct - a name, a parent and a type - and a variant is
71
+ * the group that adds a store of its own beside them. The value is what a
72
+ * caller dispatches on and what a baked artifact records.
73
+ */
74
+ typedef enum dcg_logic_group_type {
75
+ DCG_LG_BASE = 0x0000, // The base group: metadata and nothing else.
76
+ DCG_LG_MAPPING = 0x0001 // A group whose entries live in a store of their own.
77
+ } dcg_logic_group_type;
78
+
79
+ /**
80
+ * @brief A logic group: a named scope a graph is built inside.
81
+ *
82
+ * It holds METADATA and nothing else - what the group IS, and where it sits,
83
+ * which is all a build needs to reach it and read it back. A group that has
84
+ * values to carry is a dcg_mapping_lgroup: the store lives there, beside this
85
+ * header, and a scope with no store of its own has none to leak.
86
+ *
87
+ * (The capi gives the base class a contexts dict as well, and keeps runtime
88
+ * Python objects in it. Bake has no such need - a value a graph reads is a slot
89
+ * a store owns, or a caller's own var - so the field is not ported rather than
90
+ * ported empty.)
91
+ *
92
+ * `name` is owned: it is copied into a block nested under the group. It is the
93
+ * group's identity - what a node's labels carry, and what the registry looks a
94
+ * group up by - so it is the caller's to give and the group's to keep.
95
+ *
96
+ * `parent` is NOT owned. It is set when the group is entered with another
97
+ * group already open (see c_dcg_lgm_enter_group), and it names a group that
98
+ * outlives this one.
99
+ *
100
+ * A dcg_mapping_lgroup embeds this struct as its FIRST member, so a
101
+ * dcg_mapping_lgroup* is a valid dcg_logic_group*.
102
+ */
103
+ typedef struct dcg_logic_group {
104
+ // === Meta ===
105
+ dcg_logic_group_type lgtype; // Group type.
106
+ const char* name; // Display and lookup name. // OWNED - a nested copy.
107
+ dcg_logic_group* parent; // The group this one is nested in, or NULL. // NOT owned.
108
+ } dcg_logic_group;
109
+
110
+ /**
111
+ * @brief The stacks a sub-graph build puts away, and the modes it ran under.
112
+ *
113
+ * One entry of the manager's shelf. The three stacks are WHOLE BLOCKS, not
114
+ * copies: shelving hands the live blocks over and gives the manager empty ones,
115
+ * so putting the state back is a swap of pointers rather than a copy of every
116
+ * frame.
117
+ */
118
+ struct dcg_lgm_state {
119
+ dcg_logic_group** groups; // OWNED - the group stack that was live.
120
+ size_t n_groups;
121
+ size_t groups_capacity;
122
+ dcg_node** nodes; // OWNED - the node stack that was live.
123
+ size_t n_nodes;
124
+ size_t nodes_capacity;
125
+ dcg_breakpoint_node** breakpoints; // OWNED - the breakpoint list that was live.
126
+ size_t n_breakpoints;
127
+ size_t breakpoints_capacity;
128
+ bool inspection_mode;
129
+ bool vigilant_mode;
130
+ };
131
+
132
+ /**
133
+ * @brief The group manager: the build state a graph is assembled under.
134
+ *
135
+ * A caller-owned struct (a local, a field of a builder, or a block of its own)
136
+ * whose blocks are allocator blocks; c_dcg_lgm_init() creates them and
137
+ * c_dcg_lgm_free() releases them. Everything it points at is borrowed: the
138
+ * groups in the registry and on the stacks, and the nodes on the node stack.
139
+ *
140
+ * It keeps the allocator it was initialized with, because it is NOT itself an
141
+ * allocator block: a manager on the caller's stack has no allocator header to
142
+ * read one back out of, which is the one thing a node can do and a manager
143
+ * cannot.
144
+ *
145
+ * The three stacks are arrays with the innermost frame LAST, so the active
146
+ * group is the last entry and a push is an append. (The capi keeps the
147
+ * innermost first and inserts at index 0; the two are equivalent, and an array
148
+ * that grows at its end is the one that needs no shifting.)
149
+ */
150
+ typedef struct dcg_logic_group_manager {
151
+ // === Allocator ===
152
+ allocator_protocol* allocator; // How this manager's blocks are made and grown.
153
+ // === Registry ===
154
+ bytemap registry; // Group name -> dcg_logic_group* (stored as a uintptr_t). BORROWED.
155
+ // === Open groups ===
156
+ dcg_logic_group** groups; // Innermost last. OWNED block.
157
+ size_t n_groups;
158
+ size_t groups_capacity;
159
+ // === Nodes being built ===
160
+ dcg_node** nodes; // Innermost last: the active node is the last entry. OWNED block.
161
+ size_t n_nodes;
162
+ size_t nodes_capacity;
163
+ // === Raised breakpoints ===
164
+ dcg_breakpoint_node** breakpoints; // Oldest first. OWNED block. BORROWED entries.
165
+ size_t n_breakpoints;
166
+ size_t breakpoints_capacity;
167
+ // === Shelved states ===
168
+ dcg_lgm_state* shelved; // Newest last. OWNED block.
169
+ size_t n_shelved;
170
+ size_t shelved_capacity;
171
+ // === Modes ===
172
+ bool inspection_mode; // Building a layout rather than evaluating it.
173
+ bool vigilant_mode; // Refuse what a lenient build would tolerate.
174
+ } dcg_logic_group_manager;
175
+
176
+ // ========== Forward Declarations ==========
177
+
178
+ // Utility Functions
179
+ static inline const char* c_dcg_logic_group_type_name(dcg_logic_group_type lgtype);
180
+
181
+ // Internal Helpers (exposed for reuse and testing - not part of the stable surface)
182
+ static inline void* c_dcg_lgm_reserve(void** block, size_t* capacity, size_t count, size_t element_size, allocator_protocol* allocator);
183
+ static inline int c_dcg_lgm_push_group(dcg_logic_group_manager* mgr, dcg_logic_group* group);
184
+ static inline int c_dcg_lgm_push_node(dcg_logic_group_manager* mgr, dcg_node* node);
185
+ static inline int c_dcg_lgm_push_breakpoint(dcg_logic_group_manager* mgr, dcg_breakpoint_node* breakpoint);
186
+
187
+ // Group Lifecycle
188
+ static inline int c_dcg_logic_group_init(dcg_logic_group* group, dcg_logic_group_type lgtype, const char* name);
189
+ static inline dcg_logic_group* c_dcg_logic_group_new(dcg_logic_group_type lgtype, const char* name, allocator_protocol* allocator);
190
+ static inline void c_dcg_logic_group_dealloc(dcg_logic_group* group);
191
+ static inline void c_dcg_logic_group_free(dcg_logic_group* group);
192
+
193
+ // Manager Lifecycle
194
+ static inline dcg_logic_group_manager* c_dcg_lgm_new(allocator_protocol* allocator);
195
+ static inline int c_dcg_lgm_init(dcg_logic_group_manager* mgr, allocator_protocol* allocator);
196
+ static inline void c_dcg_lgm_dealloc(dcg_logic_group_manager* mgr);
197
+ static inline void c_dcg_lgm_free(dcg_logic_group_manager* mgr);
198
+ static inline void c_dcg_lgm_clear(dcg_logic_group_manager* mgr);
199
+
200
+ // Registry
201
+ static inline int c_dcg_lgm_register(dcg_logic_group_manager* mgr, dcg_logic_group* group);
202
+ static inline dcg_logic_group* c_dcg_lgm_find(const dcg_logic_group_manager* mgr, const char* name, size_t name_len);
203
+
204
+ // Building Inside the Manager
205
+ static inline int c_dcg_lgm_enter_group(dcg_logic_group_manager* mgr, dcg_logic_group* group);
206
+ static inline int c_dcg_lgm_exit_group(dcg_logic_group_manager* mgr, dcg_logic_group* group);
207
+ static inline int c_dcg_lgm_enter_node(dcg_logic_group_manager* mgr, dcg_node* node);
208
+ static inline int c_dcg_lgm_connect_awaiting(dcg_logic_group_manager* mgr, dcg_node* node); // Internal helper: not part of the stable surface.
209
+ static inline int c_dcg_lgm_exit_node(dcg_logic_group_manager* mgr, dcg_node* node);
210
+ static inline int c_dcg_lgm_label_node(dcg_logic_group_manager* mgr, dcg_node* node);
211
+ static inline int c_dcg_lgm_install_breakpoint(dcg_logic_group_manager* mgr, dcg_breakpoint_node* breakpoint);
212
+ static inline int c_dcg_lgm_break_inspection(dcg_logic_group_manager* mgr, dcg_logic_group* group);
213
+
214
+ // Shelving
215
+ static inline int c_dcg_node_root_ctx_shelve(dcg_root_node* root, dcg_logic_group_manager* mgr);
216
+ static inline int c_dcg_node_root_ctx_unshelve(dcg_root_node* root, dcg_logic_group_manager* mgr);
217
+ static inline int c_dcg_lgm_shelve(dcg_logic_group_manager* mgr);
218
+ static inline int c_dcg_lgm_unshelve(dcg_logic_group_manager* mgr);
219
+
220
+ // Queries
221
+ static inline dcg_logic_group* c_dcg_lgm_active_group(const dcg_logic_group_manager* mgr);
222
+ static inline dcg_node* c_dcg_lgm_active_node(const dcg_logic_group_manager* mgr);
223
+ static inline size_t c_dcg_lgm_breakpoint_count(const dcg_logic_group_manager* mgr);
224
+
225
+ // Building: Nodes That Join As They Are Made
226
+ static inline int c_dcg_node_auto_connect(dcg_node* node, dcg_logic_group_manager* mgr);
227
+ static inline dcg_action_node* c_dcg_node_connect_new_action(dcg_node_type action_type, const char* repr, bool auto_connect, ssize_t sig, void* action_data, dcg_logic_group_manager* mgr, allocator_protocol* allocator);
228
+ static inline dcg_action_node* c_dcg_node_connect_new_action_trade(dcg_node_type action_type, bool auto_connect, dcg_logic_group_manager* mgr, allocator_protocol* allocator);
229
+ static inline dcg_action_node* c_dcg_node_connect_new_action_clear(bool auto_connect, dcg_logic_group_manager* mgr, allocator_protocol* allocator);
230
+ static inline dcg_node* c_dcg_node_connect_new_placeholder(bool auto_connect, dcg_logic_group_manager* mgr, allocator_protocol* allocator);
231
+
232
+ // ========== Utility Functions ==========
233
+
234
+ /**
235
+ * @brief Stable display name of a group type.
236
+ *
237
+ * @param lgtype Group type.
238
+ * @return Static string; "UNKNOWN" for an out-of-range type.
239
+ */
240
+ static inline const char* c_dcg_logic_group_type_name(dcg_logic_group_type lgtype) {
241
+ switch (lgtype) {
242
+ case DCG_LG_BASE:
243
+ return "BASE";
244
+ case DCG_LG_MAPPING:
245
+ return "MAPPING";
246
+ default:
247
+ return "UNKNOWN";
248
+ }
249
+ }
250
+
251
+ // ========== Internal Helpers ==========
252
+
253
+ /**
254
+ * @brief Make room for one more entry in a stack block, growing it by doubling.
255
+ *
256
+ * @param block Address of the block pointer (updated on growth).
257
+ * @param capacity Address of the capacity field (updated on growth).
258
+ * @param count Entries in use.
259
+ * @param element_size Size of one entry in bytes.
260
+ * @param allocator Allocator for the grown block (NULL falls back to the heap).
261
+ * @return The block, or NULL on OOM.
262
+ */
263
+ static inline void* c_dcg_lgm_reserve(void** block, size_t* capacity, size_t count, size_t element_size, allocator_protocol* allocator) {
264
+ if (count < *capacity) return *block;
265
+
266
+ size_t grown_capacity = *capacity ? *capacity * 2 : DCG_LGM_DEFAULT_CAPACITY;
267
+ void* grown = c_ap_realloc(*block, grown_capacity * element_size, allocator);
268
+ if (!grown) return NULL;
269
+
270
+ *block = grown;
271
+ *capacity = grown_capacity;
272
+ return grown;
273
+ }
274
+
275
+ /**
276
+ * @brief Push an open group, after setting its parent to the one below it.
277
+ *
278
+ * @param mgr Manager to modify.
279
+ * @param group Group being entered.
280
+ * @return DCG_OK or DCG_ERR_OOM.
281
+ */
282
+ static inline int c_dcg_lgm_push_group(dcg_logic_group_manager* mgr, dcg_logic_group* group) {
283
+ if (!c_dcg_lgm_reserve((void**) &mgr->groups, &mgr->groups_capacity, mgr->n_groups, sizeof(dcg_logic_group*), mgr->allocator)) return DCG_ERR_OOM;
284
+
285
+ group->parent = mgr->n_groups ? mgr->groups[mgr->n_groups - 1] : NULL;
286
+ mgr->groups[mgr->n_groups++] = group;
287
+ return DCG_OK;
288
+ }
289
+
290
+ /**
291
+ * @brief Push a node being built onto the active-node stack.
292
+ *
293
+ * @param mgr Manager to modify.
294
+ * @param node Node being entered.
295
+ * @return DCG_OK or DCG_ERR_OOM.
296
+ */
297
+ static inline int c_dcg_lgm_push_node(dcg_logic_group_manager* mgr, dcg_node* node) {
298
+ if (!c_dcg_lgm_reserve((void**) &mgr->nodes, &mgr->nodes_capacity, mgr->n_nodes, sizeof(dcg_node*), mgr->allocator)) return DCG_ERR_OOM;
299
+
300
+ mgr->nodes[mgr->n_nodes++] = node;
301
+ return DCG_OK;
302
+ }
303
+
304
+ /**
305
+ * @brief Push a raised breakpoint onto the pending list.
306
+ *
307
+ * @param mgr Manager to modify.
308
+ * @param breakpoint Breakpoint that breaks out of a group.
309
+ * @return DCG_OK or DCG_ERR_OOM.
310
+ */
311
+ static inline int c_dcg_lgm_push_breakpoint(dcg_logic_group_manager* mgr, dcg_breakpoint_node* breakpoint) {
312
+ if (!c_dcg_lgm_reserve((void**) &mgr->breakpoints, &mgr->breakpoints_capacity, mgr->n_breakpoints, sizeof(dcg_breakpoint_node*), mgr->allocator)) return DCG_ERR_OOM;
313
+
314
+ mgr->breakpoints[mgr->n_breakpoints++] = breakpoint;
315
+ return DCG_OK;
316
+ }
317
+
318
+ /**
319
+ * @brief Initialize a group buf, taking a copy of its name.
320
+ *
321
+ * The buf is populated from scratch, so a reused buf is safe. The name is the
322
+ * one block the base group owns, and it is nested under the group, so the
323
+ * group's free releases it.
324
+ *
325
+ * The buf must live inside an allocator block - the family constructors put it
326
+ * at the head of a larger one - because everything the group owns is nested
327
+ * under it.
328
+ *
329
+ * @param group Group buf to initialize.
330
+ * @param lgtype Group type.
331
+ * @param name Group name to copy (may be NULL).
332
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
333
+ */
334
+ static inline int c_dcg_logic_group_init(dcg_logic_group* group, dcg_logic_group_type lgtype, const char* name) {
335
+ if (!group) return DCG_ERR_INVALID_ARG;
336
+
337
+ memset(group, 0, sizeof(*group));
338
+ group->lgtype = lgtype;
339
+
340
+ if (name) {
341
+ size_t len = strlen(name);
342
+ char* copy = (char*) c_ap_alloc_child(len + 1, NULL, group);
343
+ if (!copy) return DCG_ERR_OOM;
344
+ memcpy(copy, name, len + 1);
345
+ group->name = copy;
346
+ }
347
+ return DCG_OK;
348
+ }
349
+
350
+ /**
351
+ * @brief Allocate a base group: metadata and no store.
352
+ *
353
+ * A scope with values to carry is a dcg_mapping_lgroup, whose constructor
354
+ * initializes this header and then builds its store beside it.
355
+ *
356
+ * @param lgtype Group type.
357
+ * @param name Group name to copy (may be NULL).
358
+ * @param allocator Allocator for the block; NULL falls back to the plain heap.
359
+ * @return The group, or NULL on OOM.
360
+ */
361
+ static inline dcg_logic_group* c_dcg_logic_group_new(dcg_logic_group_type lgtype, const char* name, allocator_protocol* allocator) {
362
+ dcg_logic_group* group = (dcg_logic_group*) c_ap_alloc(sizeof(dcg_logic_group), allocator);
363
+ if (!group) return NULL;
364
+
365
+ if (c_dcg_logic_group_init(group, lgtype, name) != DCG_OK) {
366
+ c_ap_free_owned(group);
367
+ return NULL;
368
+ }
369
+ return group;
370
+ }
371
+
372
+ /**
373
+ * @brief Tear down a group's contents, leaving the buf itself alone.
374
+ *
375
+ * The name is the one block the base group owns, and it is nested under the
376
+ * group, so this releases it and zeroes the buf. The block the group sits in
377
+ * is NOT touched - that is c_dcg_logic_group_free()'s half - which is what lets
378
+ * a group be embedded (a dcg_mapping_lgroup embeds one) be cleaned without its
379
+ * owner's block going with it.
380
+ *
381
+ * @param group Group to tear down (NULL-safe).
382
+ */
383
+ static inline void c_dcg_logic_group_dealloc(dcg_logic_group* group) {
384
+ if (!group) return;
385
+ if (group->name) c_ap_free_owned((void*) group->name);
386
+ memset(group, 0, sizeof(*group));
387
+ }
388
+
389
+ /**
390
+ * @brief Tear down a group and free its buf.
391
+ *
392
+ * The clean half first, then the block. A variant that adds a store of its own
393
+ * has the pair as well: a dcg_mapping_lgroup is released by
394
+ * c_dcg_mapping_lgroup_free(), which hands its own table back before reaching
395
+ * this one.
396
+ *
397
+ * @param group Group to free (NULL-safe).
398
+ */
399
+ static inline void c_dcg_logic_group_free(dcg_logic_group* group) {
400
+ if (!group) return;
401
+ c_dcg_logic_group_dealloc(group);
402
+ c_ap_free_owned(group);
403
+ }
404
+
405
+ // ========== Manager Lifecycle ==========
406
+
407
+ static inline dcg_logic_group_manager* c_dcg_lgm_new(allocator_protocol* allocator) {
408
+ dcg_logic_group_manager* mgr = c_ap_alloc(sizeof(dcg_logic_group_manager), allocator);
409
+ if (!mgr) return NULL;
410
+
411
+ int ret_code = c_dcg_lgm_init(mgr, allocator);
412
+ if (ret_code != DCG_OK) {
413
+ c_dcg_lgm_free(mgr);
414
+ return NULL;
415
+ }
416
+
417
+ return mgr;
418
+ }
419
+
420
+ /**
421
+ * @brief Initialize a manager: an empty registry and three empty stacks.
422
+ *
423
+ * @param mgr Manager to initialize.
424
+ * @param allocator Allocator for the manager's blocks; NULL uses the plain heap.
425
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_INVALID_BUF.
426
+ */
427
+ static inline int c_dcg_lgm_init(dcg_logic_group_manager* mgr, allocator_protocol* allocator) {
428
+ if (!mgr) return DCG_ERR_INVALID_ARG;
429
+
430
+ memset(mgr, 0, sizeof(*mgr));
431
+ mgr->allocator = allocator;
432
+
433
+ /* One pointer-sized slot per group: the group the name maps to. */
434
+ if (c_bytemap_ex_init(&mgr->registry, DCG_LGM_DEFAULT_CAPACITY, sizeof(uintptr_t), allocator) != BYTEMAP_OK) return DCG_ERR_INVALID_BUF;
435
+ return DCG_OK;
436
+ }
437
+
438
+ /**
439
+ * @brief Tear down a manager's contents, leaving the manager buf alone.
440
+ *
441
+ * The registry table and the stack blocks go; nothing they POINT at does.
442
+ * Groups and nodes are the caller's - the manager only borrows them - so a
443
+ * manager still holding frames is torn down cleanly, and a build that is
444
+ * abandoned, or one whose graph is freed by whoever owns it, leaves nothing
445
+ * behind here.
446
+ *
447
+ * The manager buf itself is NOT freed: a manager is usually the caller's own
448
+ * local, so this is the half that a stack-allocated one needs. A manager from
449
+ * c_dcg_lgm_new() is released with c_dcg_lgm_free(), which is this plus the
450
+ * block.
451
+ *
452
+ * @param mgr Manager to tear down (NULL-safe).
453
+ */
454
+ static inline void c_dcg_lgm_dealloc(dcg_logic_group_manager* mgr) {
455
+ if (!mgr) return;
456
+
457
+ c_bytemap_ex_dealloc(&mgr->registry);
458
+
459
+ for (size_t i = 0; i < mgr->n_shelved; i++) {
460
+ dcg_lgm_state* state = &mgr->shelved[i];
461
+ c_ap_free_owned(state->groups);
462
+ c_ap_free_owned(state->nodes);
463
+ c_ap_free_owned(state->breakpoints);
464
+ }
465
+
466
+ c_ap_free_owned(mgr->groups);
467
+ c_ap_free_owned(mgr->nodes);
468
+ c_ap_free_owned(mgr->breakpoints);
469
+ c_ap_free_owned(mgr->shelved);
470
+
471
+ memset(mgr, 0, sizeof(*mgr));
472
+ }
473
+
474
+ /**
475
+ * @brief Tear down a manager and free its buf.
476
+ *
477
+ * @param mgr Manager to free (NULL-safe).
478
+ */
479
+ static inline void c_dcg_lgm_free(dcg_logic_group_manager* mgr) {
480
+ if (!mgr) return;
481
+ c_dcg_lgm_dealloc(mgr);
482
+ c_ap_free_owned(mgr);
483
+ }
484
+
485
+ /**
486
+ * @brief Drop the registry and every open frame, keeping the manager usable.
487
+ *
488
+ * This is the capi's clear(): the caches and the stacks go, the modes stay.
489
+ *
490
+ * @param mgr Manager to clear (NULL-safe).
491
+ */
492
+ static inline void c_dcg_lgm_clear(dcg_logic_group_manager* mgr) {
493
+ if (!mgr) return;
494
+
495
+ c_bytemap_ex_clear(&mgr->registry);
496
+ mgr->n_groups = 0;
497
+ mgr->n_nodes = 0;
498
+ mgr->n_breakpoints = 0;
499
+ mgr->n_shelved = 0;
500
+ }
501
+
502
+ // ========== Registry ==========
503
+
504
+ /**
505
+ * @brief Register a group under its name, so a later build can find it again.
506
+ *
507
+ * The manager borrows the group: it does not own it and does not free it.
508
+ *
509
+ * @param mgr Manager to modify.
510
+ * @param group Group to register.
511
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_DUPLICATE when the name is taken.
512
+ */
513
+ static inline int c_dcg_lgm_register(dcg_logic_group_manager* mgr, dcg_logic_group* group) {
514
+ if (!mgr || !group || !group->name) return DCG_ERR_INVALID_ARG;
515
+ if (c_bytemap_contains(&mgr->registry, group->name, strlen(group->name)) == BYTEMAP_OK) return DCG_ERR_DUPLICATE;
516
+
517
+ if (c_bytemap_set(&mgr->registry, group->name, strlen(group->name), (void*) group, NULL) != BYTEMAP_OK) return DCG_ERR_OOM;
518
+ return DCG_OK;
519
+ }
520
+
521
+ /**
522
+ * @brief Look a group up by name.
523
+ *
524
+ * @param mgr Manager to read (NULL-safe).
525
+ * @param name Group name.
526
+ * @param name_len Length of the name.
527
+ * @return The group, or NULL when the name is not registered.
528
+ */
529
+ static inline dcg_logic_group* c_dcg_lgm_find(const dcg_logic_group_manager* mgr, const char* name, size_t name_len) {
530
+ if (!mgr || !name) return NULL;
531
+
532
+ dcg_logic_group* found = NULL;
533
+ if (c_bytemap_get(&mgr->registry, name, name_len, (void**) &found) != BYTEMAP_OK) return NULL;
534
+ return found;
535
+ }
536
+
537
+ // ========== Building Inside the Manager ==========
538
+
539
+ /**
540
+ * @brief Enter a group: it becomes the innermost open one.
541
+ *
542
+ * The group takes the group below it as its parent - the capi's rule, and the
543
+ * reason a nested group can be named by walking up the stack.
544
+ *
545
+ * @param mgr Manager to modify.
546
+ * @param group Group being entered.
547
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
548
+ */
549
+ static inline int c_dcg_lgm_enter_group(dcg_logic_group_manager* mgr, dcg_logic_group* group) {
550
+ if (!mgr || !group) return DCG_ERR_INVALID_ARG;
551
+ return c_dcg_lgm_push_group(mgr, group);
552
+ }
553
+
554
+ /**
555
+ * @brief Leave a group, retiring its breakpoints to be connected later.
556
+ *
557
+ * Leaving is what makes a break real: every breakpoint raised from this group
558
+ * is marked as AWAITING CONNECTION here, and the next node entered picks it up
559
+ * (see c_dcg_lgm_connect_awaiting). A breakpoint that is still awaiting is not
560
+ * an inspection point yet - the branch it resumes into does not exist.
561
+ *
562
+ * Pass the group that is actually open, or NULL to leave the innermost one.
563
+ *
564
+ * @param mgr Manager to modify.
565
+ * @param group Group being left, or NULL for the innermost.
566
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_BUSY when another group is open.
567
+ */
568
+ static inline int c_dcg_lgm_exit_group(dcg_logic_group_manager* mgr, dcg_logic_group* group) {
569
+ if (!mgr) return DCG_ERR_INVALID_ARG;
570
+ if (!mgr->n_groups) return DCG_ERR_NOT_FOUND;
571
+
572
+ dcg_logic_group* current = mgr->groups[mgr->n_groups - 1];
573
+ if (group && current != group) return DCG_ERR_BUSY;
574
+
575
+ for (size_t i = 0; i < mgr->n_breakpoints; i++) {
576
+ if (mgr->breakpoints[i]->break_from == current) mgr->breakpoints[i]->await_connection = true;
577
+ }
578
+
579
+ mgr->n_groups--;
580
+ return DCG_OK;
581
+ }
582
+
583
+ /**
584
+ * @brief Connect every breakpoint that is waiting, then take the node as active.
585
+ *
586
+ * The two halves of entering a node, in the capi's order:
587
+ *
588
+ * - a breakpoint that was waiting is CONNECTED to this node: the node becomes
589
+ * its single child, which is what turns "break out of that group" into
590
+ * "resume here". A connected breakpoint is retired from the pending list.
591
+ * The node now has a parent, so the step below adopts it rather than
592
+ * linking it fresh - the breakpoint goes on naming it (see
593
+ * c_dcg_node_replace_shared).
594
+ * - the node then fills the active node's placeholder slot - the branch a
595
+ * build reserves for whatever comes next - and becomes the active node.
596
+ *
597
+ * An action node is refused: an action is a leaf, so there is nothing to build
598
+ * inside one.
599
+ *
600
+ * @param mgr Manager to modify.
601
+ * @param node Node being entered.
602
+ * @return DCG_OK, or a DCG_ERR_* code: DCG_ERR_INVALID_ARG (NULL),
603
+ * DCG_ERR_TYPE (an action node), DCG_ERR_UNRESOLVED (the active node has
604
+ * no free edge), DCG_ERR_OOM, or the first failure a connection hit.
605
+ */
606
+ static inline int c_dcg_lgm_enter_node(dcg_logic_group_manager* mgr, dcg_node* node) {
607
+ if (!mgr || !node) return DCG_ERR_INVALID_ARG;
608
+
609
+ /*
610
+ * The node's own half of the enter runs first, because it is the node's type
611
+ * that decides what entering it means - reserving the branches a build will
612
+ * fill, or refusing because there is nothing inside a leaf to build in.
613
+ *
614
+ * The exit half is uniform for every type and its body needs the action
615
+ * family's constructors, which live above the header that declares the pair.
616
+ * So it is installed here, where both are visible, rather than by the
617
+ * constructor that armed the enter.
618
+ */
619
+ if (!node->ctx_ops.exit_fn) node->ctx_ops.exit_fn = c_dcg_node_ctx_exit_closed;
620
+
621
+ int ret = node->ctx_ops.enter_fn(node, mgr);
622
+ if (ret != DCG_OK) {
623
+ (void) fprintf(
624
+ stderr, "c_dcg_lgm_enter_node: the context enter of type %d failed with err code: %d\n",
625
+ (int) node->ntype, ret
626
+ );
627
+ return ret;
628
+ }
629
+
630
+ /*
631
+ * A node entered inside a group is a member of it, and the label is how it
632
+ * says so. This is the entering's business, not the constructor's: a node
633
+ * built outside any group belongs to none, and a root that does not inherit
634
+ * its contexts takes its own before the names of the groups around it could
635
+ * still mean anything - which is why this runs here, with the outer groups
636
+ * still standing, rather than at construction.
637
+ */
638
+ ret = c_dcg_lgm_label_node(mgr, node);
639
+ if (ret < 0) return ret;
640
+
641
+ ret = c_dcg_lgm_connect_awaiting(mgr, node);
642
+ if (ret != DCG_OK) return ret;
643
+
644
+ /*
645
+ * A breakpoint entered is ALREADY in the graph, so there is nothing to place.
646
+ *
647
+ * It got there when the break was made - the arm it took is where it stands -
648
+ * and entering it is the build going INSIDE it, not adding it. Placing it
649
+ * again would link it a second time, into whatever arm the node below
650
+ * happens to have open, and the link refuses that: a child that already has
651
+ * a parent is DCG_ERR_BUSY. The capi makes the same distinction by its
652
+ * breakpoint's enter never calling the manager's enter at all - it pushes
653
+ * the node and stops - which is what the skip below amounts to.
654
+ *
655
+ * Everything after this block still runs: the node is pushed, so it is the
656
+ * active node the build inside it fills.
657
+ */
658
+ bool already_placed = (node->ntype == DCG_NODE_BREAKPOINT);
659
+
660
+ /* A breakpoint that took the node over is its parent now, and that is what
661
+ * makes the placement below a join rather than a plain link. */
662
+ bool adopted = node->parent != NULL;
663
+
664
+ if (mgr->n_nodes && !already_placed) {
665
+ dcg_node* active = mgr->nodes[mgr->n_nodes - 1];
666
+ dcg_node* placeholder = c_dcg_node_get_placeholder(active);
667
+ if (!placeholder) return DCG_ERR_UNRESOLVED;
668
+
669
+ ret = adopted ? c_dcg_node_replace_shared(placeholder, node) : c_dcg_node_replace(placeholder, node);
670
+ if (ret != DCG_OK) return ret;
671
+
672
+ c_dcg_node_free_generic(placeholder); /* displaced: the slot is the new node's now */
673
+ }
674
+
675
+ ret = c_dcg_lgm_push_node(mgr, node);
676
+ if (ret != DCG_OK) return ret;
677
+
678
+ /*
679
+ * A root opens a context of its own. The outer build - which the link above
680
+ * just placed the root into - goes on the shelf, and the root, now the only
681
+ * thing in a fresh context, is made its active node. That is why a root is
682
+ * entered twice: once into the graph it is being built in, once into the
683
+ * context it opens.
684
+ */
685
+ if (node->ntype == DCG_NODE_ROOT) {
686
+ ret = c_dcg_node_root_ctx_shelve((dcg_root_node*) node, mgr);
687
+ if (ret != DCG_OK) {
688
+ (void) fprintf(stderr, "c_dcg_lgm_enter_node: shelving for the root failed with err code: %d\n", ret);
689
+ return ret;
690
+ }
691
+ ret = c_dcg_lgm_push_node(mgr, node);
692
+ }
693
+ return ret;
694
+ }
695
+
696
+ /**
697
+ * @brief Connect the awaiting breakpoints to a node and retire them.
698
+ *
699
+ * Walking the list in place, keeping the entries that are not awaiting and the
700
+ * ones a connection refused. A refused connection stays queued rather than
701
+ * being dropped: it is still waiting for a node it can attach to, and the
702
+ * failure is reported to the caller either way.
703
+ *
704
+ * @param mgr Manager to modify.
705
+ * @param node Node the breakpoints resume into.
706
+ * @return DCG_OK, or the first failure a connection hit.
707
+ */
708
+ static inline int c_dcg_lgm_connect_awaiting(dcg_logic_group_manager* mgr, dcg_node* node) {
709
+ if (!mgr || !node) return DCG_ERR_INVALID_ARG;
710
+
711
+ int error = DCG_OK;
712
+ size_t kept = 0;
713
+
714
+ for (size_t i = 0; i < mgr->n_breakpoints; i++) {
715
+ dcg_breakpoint_node* breakpoint = mgr->breakpoints[i];
716
+
717
+ /*
718
+ * A breakpoint cannot resume into itself, and this is asked BEFORE the
719
+ * waiting check rather than after it. When the node being entered IS the
720
+ * queued breakpoint, the build is going INSIDE it rather than resuming
721
+ * into it - the arm it opened is what the build fills - and entering it
722
+ * has already taken the waiting flag off, so a later test would read it
723
+ * as "not waiting", keep it in the queue and never drop it here.
724
+ *
725
+ * Both at once would give the arm away twice, so this one is the build's
726
+ * now and leaves the queue.
727
+ */
728
+ if (breakpoint == (dcg_breakpoint_node*) node) {
729
+ breakpoint->await_connection = false;
730
+ continue;
731
+ }
732
+
733
+ if (!breakpoint->await_connection) {
734
+ mgr->breakpoints[kept++] = breakpoint;
735
+ continue;
736
+ }
737
+
738
+ int ret = c_dcg_node_append(&breakpoint->base, node, DCG_NO_CONDITION);
739
+ if (ret != DCG_OK) {
740
+ if (error == DCG_OK) error = ret;
741
+ mgr->breakpoints[kept++] = breakpoint;
742
+ continue;
743
+ }
744
+ breakpoint->await_connection = false;
745
+ }
746
+
747
+ mgr->n_breakpoints = kept;
748
+ return error;
749
+ }
750
+
751
+ /**
752
+ * @brief Leave the innermost node being built.
753
+ *
754
+ * @param mgr Manager to modify.
755
+ * @param node Node being left.
756
+ * @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_NOT_FOUND (no node is open), or
757
+ * DCG_ERR_BUSY when another node is the active one.
758
+ */
759
+ static inline int c_dcg_lgm_exit_node(dcg_logic_group_manager* mgr, dcg_node* node) {
760
+ if (!mgr || !node) return DCG_ERR_INVALID_ARG;
761
+ if (!mgr->n_nodes) return DCG_ERR_NOT_FOUND;
762
+ if (mgr->nodes[mgr->n_nodes - 1] != node) return DCG_ERR_BUSY;
763
+
764
+ /* The node's own half first, while it is still the one being built in: fill
765
+ * the branches it never got, then retire the stand-ins they left. */
766
+ int ret = node->ctx_ops.exit_fn(node, mgr);
767
+ if (ret != DCG_OK) {
768
+ (void) fprintf(
769
+ stderr, "c_dcg_lgm_exit_node: the context exit of type %d failed with err code: %d\n",
770
+ (int) node->ntype, ret
771
+ );
772
+ return ret;
773
+ }
774
+
775
+ mgr->n_nodes--;
776
+
777
+ /*
778
+ * Leaving the context a root opened hands the outer build back. It comes
779
+ * off the shelf with the root still on top of ITS node stack - the root was
780
+ * placed there before the shelve - so it is popped a second time, from
781
+ * there, and the build that entered the root is the active node again.
782
+ */
783
+ if (node->ntype == DCG_NODE_ROOT) {
784
+ ret = c_dcg_node_root_ctx_unshelve((dcg_root_node*) node, mgr);
785
+ if (ret != DCG_OK) {
786
+ (void) fprintf(stderr, "c_dcg_lgm_exit_node: unshelving for the root failed with err code: %d\n", ret);
787
+ return ret;
788
+ }
789
+ if (mgr->n_nodes && mgr->nodes[mgr->n_nodes - 1] == node) mgr->n_nodes--;
790
+ }
791
+ return DCG_OK;
792
+ }
793
+
794
+ /**
795
+ * @brief Label a node with the name of every open group.
796
+ *
797
+ * The capi does this when a node is CONSTRUCTED, which is the rule this
798
+ * port keeps: a node belongs to the groups that were open around it, and that
799
+ * does not change when one of them is left. So a builder calls this once per
800
+ * node, right after creating it - and it is deliberately not done by
801
+ * c_dcg_lgm_enter_node(), which runs on every entry and would relabel a node
802
+ * that is merely being descended into.
803
+ *
804
+ * A name already on the node is skipped rather than refused, so calling this
805
+ * twice is harmless.
806
+ *
807
+ * @param mgr Manager to read.
808
+ * @param node Node to label.
809
+ * @return The number of labels added, or a DCG_ERR_* code.
810
+ */
811
+ static inline int c_dcg_lgm_label_node(dcg_logic_group_manager* mgr, dcg_node* node) {
812
+ if (!mgr || !node) return DCG_ERR_INVALID_ARG;
813
+
814
+ int added = 0;
815
+ for (size_t i = 0; i < mgr->n_groups; i++) {
816
+ const char* name = mgr->groups[i]->name;
817
+ if (!name || c_dcg_node_has_label(node, name)) continue;
818
+
819
+ int ret = c_dcg_node_add_label(node, name);
820
+ if (ret != DCG_OK) return ret;
821
+ added++;
822
+ }
823
+ return added;
824
+ }
825
+
826
+ /**
827
+ * @brief Install a breakpoint that has already been built into the active node.
828
+ *
829
+ * The placing half of a break, split out from the making of one so that a caller
830
+ * which has a breakpoint of its own can put THAT one in - the node a wrapper
831
+ * already holds, rather than a second block of the same kind that the caller
832
+ * would have no handle on. One block, one wrapper: a break the caller can name
833
+ * afterwards has to be the block that landed.
834
+ *
835
+ * What it does is the active node's placeholder slot swapped for the breakpoint,
836
+ * and the breakpoint queued. The queue is what joins it: when the group is left
837
+ * the breakpoint starts waiting for a node to resume into, and the next node
838
+ * entered outside it is connected by ``c_dcg_lgm_connect_awaiting``, which walks
839
+ * that queue and nothing else - so a breakpoint that is placed but not queued is
840
+ * in the graph and never resumes.
841
+ *
842
+ * The queue's room is taken BEFORE the graph is touched, because a push can only
843
+ * fail for want of room: an OOM afterwards would leave the breakpoint placed and
844
+ * unqueued, which is the one outcome this has to avoid. Taken first, a failure
845
+ * changes nothing at all and the caller still owns what it handed in.
846
+ *
847
+ * With no active node there is nothing to break out of, and the call is a no-op
848
+ * - the capi's rule, kept because a builder that breaks at the top of a graph is
849
+ * not making an error, it is making an empty branch.
850
+ *
851
+ * @param mgr Manager to modify.
852
+ * @param breakpoint Breakpoint to place and queue. On DCG_OK it belongs to the
853
+ * graph; on any error it was not placed and is still the
854
+ * caller's to release.
855
+ * @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_UNRESOLVED (the active node has
856
+ * no slot to break out of) or DCG_ERR_OOM.
857
+ */
858
+ static inline int c_dcg_lgm_install_breakpoint(dcg_logic_group_manager* mgr, dcg_breakpoint_node* breakpoint) {
859
+ if (!mgr || !breakpoint) return DCG_ERR_INVALID_ARG;
860
+ if (!mgr->n_nodes) return DCG_OK; /* no active node: a break affects nothing */
861
+
862
+ dcg_node* active = mgr->nodes[mgr->n_nodes - 1];
863
+ dcg_node* placeholder = c_dcg_node_get_placeholder(active);
864
+ if (!placeholder) return DCG_ERR_UNRESOLVED;
865
+
866
+ if (!c_dcg_lgm_reserve((void**) &mgr->breakpoints, &mgr->breakpoints_capacity, mgr->n_breakpoints, sizeof(dcg_breakpoint_node*), mgr->allocator)) {
867
+ return DCG_ERR_OOM;
868
+ }
869
+
870
+ int ret = c_dcg_node_replace(placeholder, &breakpoint->base);
871
+ if (ret != DCG_OK) return ret;
872
+
873
+ c_dcg_node_free_generic(placeholder); /* displaced: the slot is the breakpoint's now */
874
+
875
+ (void) c_dcg_lgm_push_breakpoint(mgr, breakpoint); /* the room is taken above, so this cannot fail */
876
+ return DCG_OK;
877
+ }
878
+
879
+ /**
880
+ * @brief Break out of a group while a graph is being built.
881
+ *
882
+ * The inspection half of a break, and the only half there is so far: make a
883
+ * breakpoint naming the group, and install it - see
884
+ * ``c_dcg_lgm_install_breakpoint`` for what the placing involves and why the
885
+ * queue's room is taken before the graph is.
886
+ *
887
+ * @param mgr Manager to modify.
888
+ * @param group Group being broken out of.
889
+ * @return DCG_OK, DCG_ERR_INVALID_ARG, DCG_ERR_UNRESOLVED (the active node has
890
+ * no slot to break out of) or DCG_ERR_OOM.
891
+ */
892
+ static inline int c_dcg_lgm_break_inspection(dcg_logic_group_manager* mgr, dcg_logic_group* group) {
893
+ if (!mgr || !group) return DCG_ERR_INVALID_ARG;
894
+ if (!mgr->n_nodes) return DCG_OK; /* no active node: a break affects nothing */
895
+
896
+ dcg_node* active = mgr->nodes[mgr->n_nodes - 1];
897
+ dcg_breakpoint_node* breakpoint = c_dcg_node_new_breakpoint(group, NULL, c_ap_protocol_from_ptr(active));
898
+ if (!breakpoint) return DCG_ERR_OOM;
899
+
900
+ int ret = c_dcg_lgm_install_breakpoint(mgr, breakpoint);
901
+ if (ret != DCG_OK) {
902
+ /* The install changes nothing when it fails, so the block it was handed
903
+ * is still unplaced and still ours. */
904
+ c_dcg_node_free_breakpoint(breakpoint);
905
+ return ret;
906
+ }
907
+ return DCG_OK;
908
+ }
909
+
910
+ // ========== Shelving ==========
911
+
912
+ /**
913
+ * @brief Shelve for a root's entry: put the outer build's stacks away.
914
+ *
915
+ * A root starts a context of its own, so whatever the outer build had open goes
916
+ * on the shelf. `inherit_contexts` - the root's own field, and the only place
917
+ * it is read - decides whether the GROUPS travel with it: set, the active group
918
+ * stack is carried across and the subgraph is built inside the groups the root
919
+ * was entered from; unset, the capi's default, the groups are shelved too and
920
+ * the subgraph starts with none.
921
+ *
922
+ * Either way the manager is left in inspection mode, which is what marks a
923
+ * build laying a graph out rather than evaluating one.
924
+ *
925
+ * @param root The root being entered.
926
+ * @param mgr Manager holding the build.
927
+ * @return DCG_OK, or a DCG_ERR_* code.
928
+ */
929
+ static inline int c_dcg_node_root_ctx_shelve(dcg_root_node* root, dcg_logic_group_manager* mgr) {
930
+ if (!root || !mgr) return DCG_ERR_INVALID_ARG;
931
+
932
+ if (root->inherit_contexts) {
933
+ /* The groups travel WITH the root, so the shelf does not take them: the
934
+ * manager's stack is saved, the shelf runs - which hands its own copy of
935
+ * the stack to the shelf and leaves the manager empty - and the saved
936
+ * one goes back. Both now name the same block, which is what makes it
937
+ * inherited rather than shelved; the unshelve drops the manager's copy
938
+ * before restoring so it cannot free the stack it is handing back. */
939
+ dcg_logic_group** groups = mgr->groups;
940
+ size_t n_groups = mgr->n_groups;
941
+ size_t groups_capacity = mgr->groups_capacity;
942
+
943
+ int ret = c_dcg_lgm_shelve(mgr);
944
+ if (ret != DCG_OK) return ret;
945
+
946
+ mgr->groups = groups;
947
+ mgr->n_groups = n_groups;
948
+ mgr->groups_capacity = groups_capacity;
949
+ }
950
+ else {
951
+ int ret = c_dcg_lgm_shelve(mgr);
952
+ if (ret != DCG_OK) return ret;
953
+ }
954
+
955
+ mgr->inspection_mode = true;
956
+ return DCG_OK;
957
+ }
958
+
959
+ /**
960
+ * @brief Unshelve for a root's exit: bring the outer build back.
961
+ *
962
+ * With `inherit_contexts` set, the groups standing on the manager are the ones
963
+ * that travelled WITH the root rather than the ones the shelf is holding, so
964
+ * they are dropped first - otherwise the unshelve would free the inherited
965
+ * stack while putting it back.
966
+ *
967
+ * @param root The root being left.
968
+ * @param mgr Manager holding the build.
969
+ * @return DCG_OK, or a DCG_ERR_* code.
970
+ */
971
+ static inline int c_dcg_node_root_ctx_unshelve(dcg_root_node* root, dcg_logic_group_manager* mgr) {
972
+ if (!root || !mgr) return DCG_ERR_INVALID_ARG;
973
+
974
+ if (root->inherit_contexts) {
975
+ mgr->groups = NULL;
976
+ mgr->n_groups = 0;
977
+ mgr->groups_capacity = 0;
978
+ }
979
+
980
+ return c_dcg_lgm_unshelve(mgr);
981
+ }
982
+
983
+ /**
984
+ * @brief Put the open stacks away and start from an empty state.
985
+ *
986
+ * What a sub-graph build needs: the frames of the build around it are handed
987
+ * over whole - not copied - and the manager goes back to holding nothing open.
988
+ * c_dcg_lgm_unshelve() puts the most recently shelved state back, so shelving
989
+ * nests.
990
+ *
991
+ * The modes travel with the state, as they do in the capi.
992
+ *
993
+ * @param mgr Manager to modify.
994
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_OOM.
995
+ */
996
+ static inline int c_dcg_lgm_shelve(dcg_logic_group_manager* mgr) {
997
+ if (!mgr) return DCG_ERR_INVALID_ARG;
998
+
999
+ if (!c_dcg_lgm_reserve((void**) &mgr->shelved, &mgr->shelved_capacity, mgr->n_shelved, sizeof(dcg_lgm_state), mgr->allocator)) return DCG_ERR_OOM;
1000
+
1001
+ dcg_lgm_state* state = &mgr->shelved[mgr->n_shelved++];
1002
+ memset(state, 0, sizeof(*state));
1003
+
1004
+ /* The blocks move, they are not copied: the manager is left with empty
1005
+ * stacks and the sizes it needs are the ones the arrays already have. */
1006
+ state->groups = mgr->groups;
1007
+ state->n_groups = mgr->n_groups;
1008
+ state->groups_capacity = mgr->groups_capacity;
1009
+ state->nodes = mgr->nodes;
1010
+ state->n_nodes = mgr->n_nodes;
1011
+ state->nodes_capacity = mgr->nodes_capacity;
1012
+ state->breakpoints = mgr->breakpoints;
1013
+ state->n_breakpoints = mgr->n_breakpoints;
1014
+ state->breakpoints_capacity = mgr->breakpoints_capacity;
1015
+ state->inspection_mode = mgr->inspection_mode;
1016
+ state->vigilant_mode = mgr->vigilant_mode;
1017
+
1018
+ mgr->groups = NULL;
1019
+ mgr->n_groups = 0;
1020
+ mgr->groups_capacity = 0;
1021
+ mgr->nodes = NULL;
1022
+ mgr->n_nodes = 0;
1023
+ mgr->nodes_capacity = 0;
1024
+ mgr->breakpoints = NULL;
1025
+ mgr->n_breakpoints = 0;
1026
+ mgr->breakpoints_capacity = 0;
1027
+ return DCG_OK;
1028
+ }
1029
+
1030
+ /**
1031
+ * @brief Put the most recently shelved state back, stacks and modes alike.
1032
+ *
1033
+ * The stacks the manager is holding when this is called are the sub-build's,
1034
+ * and this call is what ends it: their blocks go with it. Shelving handed the
1035
+ * outer frames to the shelf, so anything on the manager now was opened inside
1036
+ * the sub-build - and a frame is bookkeeping, not ownership (the nodes it
1037
+ * names belong to the graph), so one that is still open is dropped without
1038
+ * leaking anything. That is the capi's behaviour too: it rebinds its lists and
1039
+ * lets the discarded ones be collected.
1040
+ *
1041
+ * @param mgr Manager to modify.
1042
+ * @return DCG_OK, DCG_ERR_INVALID_ARG or DCG_ERR_NOT_FOUND when nothing is shelved.
1043
+ */
1044
+ static inline int c_dcg_lgm_unshelve(dcg_logic_group_manager* mgr) {
1045
+ if (!mgr) return DCG_ERR_INVALID_ARG;
1046
+ if (!mgr->n_shelved) return DCG_ERR_NOT_FOUND;
1047
+
1048
+ dcg_lgm_state* state = &mgr->shelved[--mgr->n_shelved];
1049
+
1050
+ c_ap_free_owned(mgr->groups);
1051
+ c_ap_free_owned(mgr->nodes);
1052
+ c_ap_free_owned(mgr->breakpoints);
1053
+
1054
+ mgr->groups = state->groups;
1055
+ mgr->n_groups = state->n_groups;
1056
+ mgr->groups_capacity = state->groups_capacity;
1057
+ mgr->nodes = state->nodes;
1058
+ mgr->n_nodes = state->n_nodes;
1059
+ mgr->nodes_capacity = state->nodes_capacity;
1060
+ mgr->breakpoints = state->breakpoints;
1061
+ mgr->n_breakpoints = state->n_breakpoints;
1062
+ mgr->breakpoints_capacity = state->breakpoints_capacity;
1063
+ mgr->inspection_mode = state->inspection_mode;
1064
+ mgr->vigilant_mode = state->vigilant_mode;
1065
+
1066
+ memset(state, 0, sizeof(*state));
1067
+ return DCG_OK;
1068
+ }
1069
+
1070
+ // ========== Queries ==========
1071
+
1072
+ /**
1073
+ * @brief The innermost open group, or NULL when none is open.
1074
+ *
1075
+ * @param mgr Manager to read (NULL-safe).
1076
+ * @return The active group, or NULL.
1077
+ */
1078
+ static inline dcg_logic_group* c_dcg_lgm_active_group(const dcg_logic_group_manager* mgr) {
1079
+ if (!mgr || !mgr->n_groups) return NULL;
1080
+ return mgr->groups[mgr->n_groups - 1];
1081
+ }
1082
+
1083
+ /**
1084
+ * @brief The innermost node being built, or NULL when none is open.
1085
+ *
1086
+ * @param mgr Manager to read (NULL-safe).
1087
+ * @return The active node, or NULL.
1088
+ */
1089
+ static inline dcg_node* c_dcg_lgm_active_node(const dcg_logic_group_manager* mgr) {
1090
+ if (!mgr || !mgr->n_nodes) return NULL;
1091
+ return mgr->nodes[mgr->n_nodes - 1];
1092
+ }
1093
+
1094
+ /**
1095
+ * @brief Breakpoints raised and not yet connected into the graph.
1096
+ *
1097
+ * @param mgr Manager to read (NULL-safe).
1098
+ * @return The number of pending breakpoints.
1099
+ */
1100
+ static inline size_t c_dcg_lgm_breakpoint_count(const dcg_logic_group_manager* mgr) {
1101
+ if (!mgr) return 0;
1102
+ return mgr->n_breakpoints;
1103
+ }
1104
+
1105
+ // ========== Building: Nodes That Join As They Are Made ==========
1106
+
1107
+ /**
1108
+ * @brief Connect a node to the arm the active node reserved for it.
1109
+ *
1110
+ * What "auto-connect" means, and the reason it is a function here rather than
1111
+ * inside the constructors in c_action.h: joining needs to know which node is
1112
+ * being built, and only the manager knows that. The node takes the placeholder
1113
+ * the active node left open - the newest one, so a node built inside a block
1114
+ * lands in the arm the block reserved for it.
1115
+ *
1116
+ * A node built with no build in progress is not an error: it is a standalone
1117
+ * node that has not been placed yet, and it stays that way.
1118
+ *
1119
+ * @param node Node to join (must be parentless).
1120
+ * @param mgr Manager holding the build (NULL means no build).
1121
+ * @return DCG_OK, or a DCG_ERR_* code.
1122
+ */
1123
+ static inline int c_dcg_node_auto_connect(dcg_node* node, dcg_logic_group_manager* mgr) {
1124
+ if (!node) return DCG_ERR_INVALID_ARG;
1125
+ if (!mgr) return DCG_OK; /* no manager, no build: nothing to join */
1126
+
1127
+ dcg_node* active = c_dcg_lgm_active_node(mgr);
1128
+ if (!active) return DCG_OK; /* a build outside any node: still nothing to join */
1129
+
1130
+ dcg_node* placeholder = c_dcg_node_get_placeholder(active);
1131
+ if (!placeholder) return DCG_ERR_OOM;
1132
+
1133
+ return c_dcg_node_replace(placeholder, node);
1134
+ }
1135
+
1136
+ /**
1137
+ * @brief Allocate an action leaf of an explicit type and join it as it is made.
1138
+ *
1139
+ * The plain constructor in c_action.h builds the node; this one places it, and
1140
+ * only does so when the caller asked for it and there is a build to join.
1141
+ *
1142
+ * @param action_type An action type.
1143
+ * @param repr Display text to copy (may be NULL).
1144
+ * @param auto_connect Whether to join the active node now.
1145
+ * @param sig The signal to carry (+1 long, -1 short, 0 for the rest).
1146
+ * @param action_data The caller's payload (not owned; may be NULL).
1147
+ * @param mgr Manager holding the build (NULL means no build).
1148
+ * @param allocator Allocator for the block; NULL falls back to the plain heap.
1149
+ * @return The node, or NULL on OOM / an invalid type / a refused join.
1150
+ */
1151
+ static inline dcg_action_node* c_dcg_node_connect_new_action(dcg_node_type action_type, const char* repr, bool auto_connect, ssize_t sig, void* action_data, dcg_logic_group_manager* mgr, allocator_protocol* allocator) {
1152
+ dcg_action_node* node = c_dcg_node_new_action(action_type, repr, sig, action_data, allocator);
1153
+ if (!node) return NULL;
1154
+
1155
+ node->auto_connect = auto_connect;
1156
+
1157
+ if (auto_connect && c_dcg_node_auto_connect(&node->base, mgr) != DCG_OK) {
1158
+ c_dcg_node_free_action(node);
1159
+ return NULL;
1160
+ }
1161
+ return node;
1162
+ }
1163
+
1164
+ /**
1165
+ * @brief Allocate a trade action of an explicit type and join it as it is made.
1166
+ *
1167
+ * @param action_type DCG_NODE_LONGACTION, DCG_NODE_SHORTACTION or DCG_NODE_CANCELACTION.
1168
+ * @param auto_connect Whether to join the active node now.
1169
+ * @param mgr Manager holding the build (NULL means no build).
1170
+ * @param allocator Allocator for the block; NULL falls back to the plain heap.
1171
+ * @return The node, or NULL on OOM / a type that does not trade / a refused join.
1172
+ */
1173
+ static inline dcg_action_node* c_dcg_node_connect_new_action_trade(dcg_node_type action_type, bool auto_connect, dcg_logic_group_manager* mgr, allocator_protocol* allocator) {
1174
+ dcg_action_node* node = c_dcg_node_new_action_trade(action_type, allocator);
1175
+ if (!node) return NULL;
1176
+
1177
+ node->auto_connect = auto_connect;
1178
+
1179
+ if (auto_connect && c_dcg_node_auto_connect(&node->base, mgr) != DCG_OK) {
1180
+ c_dcg_node_free_action(node);
1181
+ return NULL;
1182
+ }
1183
+ return node;
1184
+ }
1185
+
1186
+ /**
1187
+ * @brief Allocate a position-flattening action and join it as it is made.
1188
+ *
1189
+ * @param auto_connect Whether to join the active node now.
1190
+ * @param mgr Manager holding the build (NULL means no build).
1191
+ * @param allocator Allocator for the block; NULL falls back to the plain heap.
1192
+ * @return The node, or NULL on OOM / a refused join.
1193
+ */
1194
+ static inline dcg_action_node* c_dcg_node_connect_new_action_clear(bool auto_connect, dcg_logic_group_manager* mgr, allocator_protocol* allocator) {
1195
+ dcg_action_node* node = c_dcg_node_new_action_clear(allocator);
1196
+ if (!node) return NULL;
1197
+
1198
+ node->auto_connect = auto_connect;
1199
+
1200
+ if (auto_connect && c_dcg_node_auto_connect(&node->base, mgr) != DCG_OK) {
1201
+ c_dcg_node_free_action(node);
1202
+ return NULL;
1203
+ }
1204
+ return node;
1205
+ }
1206
+
1207
+ /**
1208
+ * @brief Allocate an auto-generated placeholder and join it as it is made.
1209
+ *
1210
+ * @param auto_connect Whether to join the active node now.
1211
+ * @param mgr Manager holding the build (NULL means no build).
1212
+ * @param allocator Allocator for the block; NULL falls back to the plain heap.
1213
+ * @return The node, or NULL on OOM / a refused join.
1214
+ */
1215
+ static inline dcg_node* c_dcg_node_connect_new_placeholder(bool auto_connect, dcg_logic_group_manager* mgr, allocator_protocol* allocator) {
1216
+ dcg_node* node = c_dcg_node_new_placeholder(allocator);
1217
+ if (!node) return NULL;
1218
+
1219
+ if (auto_connect && c_dcg_node_auto_connect(node, mgr) != DCG_OK) {
1220
+ c_dcg_node_free(node);
1221
+ return NULL;
1222
+ }
1223
+ return node;
1224
+ }
1225
+
1226
+ #endif // C_DCG_BAKE_LOGIC_GROUP_H