1use std::collections::HashMap;
4
5use brink_format::{DefinitionId, StoryData};
6
7use crate::error::RuntimeError;
8use crate::program::{
9 ExternalFnEntry, GlobalSlot, LinkedContainer, ListDefEntry, ListItemEntry, PathTarget, Program,
10};
11
12#[expect(clippy::cast_possible_truncation, clippy::too_many_lines)]
18pub fn link(
19 data: &StoryData,
20) -> Result<(Program, Vec<Vec<brink_format::LineEntry>>), RuntimeError> {
21 let mut container_map = HashMap::with_capacity(data.containers.len());
22
23 for (i, cdef) in data.containers.iter().enumerate() {
24 let idx = i as u32;
25 container_map.insert(cdef.id, idx);
26 }
27
28 let mut scope_table_map: HashMap<DefinitionId, u32> =
30 HashMap::with_capacity(data.line_tables.len());
31 let mut line_tables: Vec<Vec<brink_format::LineEntry>> =
32 Vec::with_capacity(data.line_tables.len());
33 let mut scope_ids: Vec<DefinitionId> = Vec::with_capacity(data.line_tables.len());
34 for lt in &data.line_tables {
35 let idx = line_tables.len() as u32;
36 scope_table_map.insert(lt.scope_id, idx);
37 scope_ids.push(lt.scope_id);
38 line_tables.push(lt.lines.clone());
39 }
40
41 let mut containers = Vec::with_capacity(data.containers.len());
43 for cdef in &data.containers {
44 let scope_table_idx = scope_table_map.get(&cdef.scope_id).copied().unwrap_or(0);
45 containers.push(LinkedContainer {
46 id: cdef.id,
47 bytecode: cdef.bytecode.clone(),
48 counting_flags: cdef.counting_flags,
49 path_hash: cdef.path_hash,
50 param_count: cdef.param_count,
51 scope_table_idx,
52 });
53 }
54
55 let mut globals = Vec::with_capacity(data.variables.len());
57 let mut global_map = HashMap::with_capacity(data.variables.len());
58 for (i, gvar) in data.variables.iter().enumerate() {
59 let idx = i as u32;
60 global_map.insert(gvar.id, idx);
61 globals.push(GlobalSlot {
62 id: gvar.id,
63 name: gvar.name,
64 default: gvar.default_value.clone(),
65 });
66 }
67
68 let mut address_map = HashMap::with_capacity(data.containers.len() + data.addresses.len());
71 for (i, cdef) in data.containers.iter().enumerate() {
72 address_map.insert(cdef.id, (i as u32, 0usize));
73 }
74 for addr in &data.addresses {
76 let container_idx = container_map
77 .get(&addr.container_id)
78 .copied()
79 .ok_or(RuntimeError::UnresolvedDefinition(addr.container_id))?;
80 address_map.insert(addr.id, (container_idx, addr.byte_offset as usize));
81 }
82
83 if data.containers.is_empty() {
85 return Err(RuntimeError::NoRootContainer);
86 }
87 let root_idx = 0;
88
89 let name_table = data.name_table.clone();
90
91 let mut list_item_map = HashMap::with_capacity(data.list_items.len());
93 for li in &data.list_items {
94 list_item_map.insert(
95 li.id,
96 ListItemEntry {
97 name: li.name,
98 ordinal: li.ordinal,
99 origin: li.origin,
100 },
101 );
102 }
103
104 let mut list_defs = Vec::with_capacity(data.list_defs.len());
106 let mut list_def_map = HashMap::with_capacity(data.list_defs.len());
107 for ldef in &data.list_defs {
108 let idx = list_defs.len();
109 let mut items: Vec<_> = data
111 .list_items
112 .iter()
113 .filter(|li| li.origin == ldef.id)
114 .collect();
115 items.sort_by_key(|li| li.ordinal);
116 let item_ids: Vec<_> = items.iter().map(|li| li.id).collect();
117
118 list_def_map.insert(ldef.id, idx);
119 list_defs.push(ListDefEntry {
120 name: ldef.name,
121 items: item_ids,
122 });
123 }
124
125 let list_literals = data.list_literals.clone();
127
128 let mut external_fns = HashMap::with_capacity(data.externals.len());
130 for ext in &data.externals {
131 external_fns.insert(
132 ext.id,
133 ExternalFnEntry {
134 name: ext.name,
135 fallback: ext.fallback,
136 },
137 );
138 }
139
140 let mut address_by_path: HashMap<String, PathTarget> = HashMap::new();
152 if data.address_paths.is_empty() {
153 address_by_path.reserve(data.containers.len());
154 for (i, cdef) in data.containers.iter().enumerate() {
155 if let Some(name_id) = cdef.name {
156 let name = data.name_table[name_id.0 as usize].clone();
157 address_by_path.insert(
158 name,
159 PathTarget {
160 id: cdef.id,
161 container_idx: i as u32,
162 byte_offset: 0,
163 },
164 );
165 }
166 }
167 } else {
168 address_by_path.reserve(data.address_paths.len());
169 for ap in &data.address_paths {
170 if let Some(&(idx, offset)) = address_map.get(&ap.target) {
173 let name = data.name_table[ap.path.0 as usize].clone();
174 address_by_path.insert(
175 name,
176 PathTarget {
177 id: ap.target,
178 container_idx: idx,
179 byte_offset: offset,
180 },
181 );
182 }
183 }
184 }
185
186 let program = Program {
187 containers,
188 address_map,
189 scope_ids,
190 source_checksum: data.source_checksum,
191 globals,
192 global_map,
193 name_table,
194 address_by_path,
195 root_idx,
196 list_literals,
197 list_item_map,
198 list_defs,
199 list_def_map,
200 external_fns,
201 };
202 Ok((program, line_tables))
203}