1use alloc::string::String;
4use alloc::vec::Vec;
5
6use brink_format::{DefinitionId, NameId, StoryData};
7
8use crate::collections::{Map as HashMap, map_with_capacity};
9use crate::error::RuntimeError;
10use crate::program::{
11 ExternalFnEntry, GlobalSlot, LinkedContainer, ListDefEntry, ListItemEntry, PathTarget, Program,
12 StructShapeEntry,
13};
14
15fn resolve_name(data: &StoryData, name_id: NameId) -> Result<String, RuntimeError> {
21 data.name_table
22 .get(name_id.0 as usize)
23 .cloned()
24 .ok_or(RuntimeError::InvalidNameId(name_id.0))
25}
26
27#[expect(clippy::cast_possible_truncation, clippy::too_many_lines)]
33pub fn link(
34 data: &StoryData,
35) -> Result<(Program, Vec<Vec<brink_format::LineEntry>>), RuntimeError> {
36 let mut container_map = map_with_capacity(data.containers.len());
37
38 for (i, cdef) in data.containers.iter().enumerate() {
39 let idx = i as u32;
40 container_map.insert(cdef.id, idx);
41 }
42
43 let mut scope_table_map: HashMap<DefinitionId, u32> = map_with_capacity(data.line_tables.len());
45 let mut line_tables: Vec<Vec<brink_format::LineEntry>> =
46 Vec::with_capacity(data.line_tables.len());
47 let mut scope_ids: Vec<DefinitionId> = Vec::with_capacity(data.line_tables.len());
48 for lt in &data.line_tables {
49 let idx = line_tables.len() as u32;
50 scope_table_map.insert(lt.scope_id, idx);
51 scope_ids.push(lt.scope_id);
52 line_tables.push(lt.lines.clone());
53 }
54
55 let mut containers = Vec::with_capacity(data.containers.len());
57 for cdef in &data.containers {
58 let scope_table_idx = scope_table_map.get(&cdef.scope_id).copied().unwrap_or(0);
59 containers.push(LinkedContainer {
60 id: cdef.id,
61 bytecode: cdef.bytecode.clone(),
62 counting_flags: cdef.counting_flags,
63 path_hash: cdef.path_hash,
64 param_count: cdef.param_count,
65 params: cdef.params.clone(),
66 scope_table_idx,
67 });
68 }
69
70 let mut globals = Vec::with_capacity(data.variables.len());
72 let mut global_map = map_with_capacity(data.variables.len());
73 for (i, gvar) in data.variables.iter().enumerate() {
74 let idx = i as u32;
75 global_map.insert(gvar.id, idx);
76 globals.push(GlobalSlot {
77 id: gvar.id,
78 name: gvar.name,
79 default: gvar.default_value.clone(),
80 local: gvar.local,
81 });
82 }
83
84 let mut address_map = map_with_capacity(data.containers.len() + data.addresses.len());
87 for (i, cdef) in data.containers.iter().enumerate() {
88 address_map.insert(cdef.id, (i as u32, 0usize));
89 }
90 for addr in &data.addresses {
92 let container_idx = container_map
93 .get(&addr.container_id)
94 .copied()
95 .ok_or(RuntimeError::UnresolvedDefinition(addr.container_id))?;
96 address_map.insert(addr.id, (container_idx, addr.byte_offset as usize));
97 }
98
99 if data.containers.is_empty() {
101 return Err(RuntimeError::NoRootContainer);
102 }
103 let root_idx = 0;
104
105 let name_table = data.name_table.clone();
106
107 let mut list_item_map = map_with_capacity(data.list_items.len());
109 for li in &data.list_items {
110 list_item_map.insert(
111 li.id,
112 ListItemEntry {
113 name: li.name,
114 ordinal: li.ordinal,
115 origin: li.origin,
116 },
117 );
118 }
119
120 let mut list_defs = Vec::with_capacity(data.list_defs.len());
122 let mut list_def_map = map_with_capacity(data.list_defs.len());
123 for ldef in &data.list_defs {
124 let idx = list_defs.len();
125 let mut items: Vec<_> = data
127 .list_items
128 .iter()
129 .filter(|li| li.origin == ldef.id)
130 .collect();
131 items.sort_by_key(|li| li.ordinal);
132 let item_ids: Vec<_> = items.iter().map(|li| li.id).collect();
133
134 list_def_map.insert(ldef.id, idx);
135 list_defs.push(ListDefEntry {
136 name: ldef.name,
137 items: item_ids,
138 });
139 }
140
141 let list_literals = data.list_literals.clone();
143
144 let literal_pool = data.literal_pool.clone();
146
147 let mut struct_shapes: Vec<StructShapeEntry> = Vec::with_capacity(data.struct_shapes.len());
152 for shape in &data.struct_shapes {
153 let idx = shape.id.0 as usize;
154 if struct_shapes.len() <= idx {
155 struct_shapes.resize_with(idx + 1, || StructShapeEntry {
156 name: NameId(0),
157 fields: Vec::new(),
158 });
159 }
160 struct_shapes[idx] = StructShapeEntry {
161 name: shape.name,
162 fields: shape.fields.clone(),
163 };
164 }
165
166 let mut external_fns = map_with_capacity(data.externals.len());
168 for ext in &data.externals {
169 external_fns.insert(
170 ext.id,
171 ExternalFnEntry {
172 name: ext.name,
173 fallback: ext.fallback,
174 },
175 );
176 }
177
178 let mut address_by_path: HashMap<String, PathTarget> = HashMap::new();
190 if data.address_paths.is_empty() {
191 #[cfg(feature = "std")]
193 address_by_path.reserve(data.containers.len());
194 for (i, cdef) in data.containers.iter().enumerate() {
195 if let Some(name_id) = cdef.name {
196 let name = resolve_name(data, name_id)?;
197 address_by_path.insert(
198 name,
199 PathTarget {
200 id: cdef.id,
201 container_idx: i as u32,
202 byte_offset: 0,
203 },
204 );
205 }
206 }
207 } else {
208 #[cfg(feature = "std")]
210 address_by_path.reserve(data.address_paths.len());
211 for ap in &data.address_paths {
212 if let Some(&(idx, offset)) = address_map.get(&ap.target) {
215 let name = resolve_name(data, ap.path)?;
216 address_by_path.insert(
217 name,
218 PathTarget {
219 id: ap.target,
220 container_idx: idx,
221 byte_offset: offset,
222 },
223 );
224 }
225 }
226 }
227
228 let mut local_scope_defaults: Vec<(String, DefinitionId)> = Vec::new();
231 for cdef in data.containers.iter().filter(|c| c.local) {
232 if let Some(n) = cdef.name {
233 local_scope_defaults.push((resolve_name(data, n)?, cdef.id));
234 }
235 }
236 local_scope_defaults.sort();
237
238 let mut private_defs: Vec<DefinitionId> = data.private_defs.clone();
243 private_defs.sort_by_key(|d| d.to_raw());
244
245 let mut alias_table = data.alias_table.clone();
250 alias_table.sort_unstable();
251
252 let program = Program {
253 containers,
254 address_map,
255 scope_ids,
256 source_checksum: data.source_checksum,
257 globals,
258 global_map,
259 name_table,
260 address_by_path,
261 root_idx,
262 list_literals,
263 literal_pool,
264 list_item_map,
265 list_defs,
266 list_def_map,
267 external_fns,
268 local_scope_defaults,
269 struct_shapes,
270 private_defs,
271 alias_table,
272 };
273 Ok((program, line_tables))
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 fn story_with_out_of_range_address_path_name() -> StoryData {
287 let mut data = brink_compiler::compile("main.ink", |_p| {
288 Ok("=== knot ===\nHello.\n-> END\n".to_owned())
289 })
290 .unwrap()
291 .data;
292 assert!(
293 !data.address_paths.is_empty(),
294 "compiler output should carry an address_paths table"
295 );
296 data.address_paths[0].path = NameId(u16::MAX);
297 data
298 }
299
300 #[test]
301 fn link_rejects_out_of_range_address_path_name_id() {
302 let data = story_with_out_of_range_address_path_name();
303 let result = link(&data);
304 assert!(
305 matches!(result, Err(RuntimeError::InvalidNameId(id)) if id == u16::MAX),
306 "out-of-range NameId must not link"
307 );
308 }
309}