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 local: gvar.local,
66 });
67 }
68
69 let mut address_map = HashMap::with_capacity(data.containers.len() + data.addresses.len());
72 for (i, cdef) in data.containers.iter().enumerate() {
73 address_map.insert(cdef.id, (i as u32, 0usize));
74 }
75 for addr in &data.addresses {
77 let container_idx = container_map
78 .get(&addr.container_id)
79 .copied()
80 .ok_or(RuntimeError::UnresolvedDefinition(addr.container_id))?;
81 address_map.insert(addr.id, (container_idx, addr.byte_offset as usize));
82 }
83
84 if data.containers.is_empty() {
86 return Err(RuntimeError::NoRootContainer);
87 }
88 let root_idx = 0;
89
90 let name_table = data.name_table.clone();
91
92 let mut list_item_map = HashMap::with_capacity(data.list_items.len());
94 for li in &data.list_items {
95 list_item_map.insert(
96 li.id,
97 ListItemEntry {
98 name: li.name,
99 ordinal: li.ordinal,
100 origin: li.origin,
101 },
102 );
103 }
104
105 let mut list_defs = Vec::with_capacity(data.list_defs.len());
107 let mut list_def_map = HashMap::with_capacity(data.list_defs.len());
108 for ldef in &data.list_defs {
109 let idx = list_defs.len();
110 let mut items: Vec<_> = data
112 .list_items
113 .iter()
114 .filter(|li| li.origin == ldef.id)
115 .collect();
116 items.sort_by_key(|li| li.ordinal);
117 let item_ids: Vec<_> = items.iter().map(|li| li.id).collect();
118
119 list_def_map.insert(ldef.id, idx);
120 list_defs.push(ListDefEntry {
121 name: ldef.name,
122 items: item_ids,
123 });
124 }
125
126 let list_literals = data.list_literals.clone();
128
129 let mut external_fns = HashMap::with_capacity(data.externals.len());
131 for ext in &data.externals {
132 external_fns.insert(
133 ext.id,
134 ExternalFnEntry {
135 name: ext.name,
136 fallback: ext.fallback,
137 },
138 );
139 }
140
141 let mut address_by_path: HashMap<String, PathTarget> = HashMap::new();
153 if data.address_paths.is_empty() {
154 address_by_path.reserve(data.containers.len());
155 for (i, cdef) in data.containers.iter().enumerate() {
156 if let Some(name_id) = cdef.name {
157 let name = data.name_table[name_id.0 as usize].clone();
158 address_by_path.insert(
159 name,
160 PathTarget {
161 id: cdef.id,
162 container_idx: i as u32,
163 byte_offset: 0,
164 },
165 );
166 }
167 }
168 } else {
169 address_by_path.reserve(data.address_paths.len());
170 for ap in &data.address_paths {
171 if let Some(&(idx, offset)) = address_map.get(&ap.target) {
174 let name = data.name_table[ap.path.0 as usize].clone();
175 address_by_path.insert(
176 name,
177 PathTarget {
178 id: ap.target,
179 container_idx: idx,
180 byte_offset: offset,
181 },
182 );
183 }
184 }
185 }
186
187 let mut local_scope_defaults: Vec<(String, DefinitionId)> = data
190 .containers
191 .iter()
192 .filter(|c| c.local)
193 .filter_map(|c| {
194 c.name
195 .map(|n| (data.name_table[n.0 as usize].clone(), c.id))
196 })
197 .collect();
198 local_scope_defaults.sort();
199
200 let program = Program {
201 containers,
202 address_map,
203 scope_ids,
204 source_checksum: data.source_checksum,
205 globals,
206 global_map,
207 name_table,
208 address_by_path,
209 root_idx,
210 list_literals,
211 list_item_map,
212 list_defs,
213 list_def_map,
214 external_fns,
215 local_scope_defaults,
216 };
217 Ok((program, line_tables))
218}