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 scope_table_idx,
51 });
52 }
53
54 let mut globals = Vec::with_capacity(data.variables.len());
56 let mut global_map = HashMap::with_capacity(data.variables.len());
57 for (i, gvar) in data.variables.iter().enumerate() {
58 let idx = i as u32;
59 global_map.insert(gvar.id, idx);
60 globals.push(GlobalSlot {
61 id: gvar.id,
62 name: gvar.name,
63 default: gvar.default_value.clone(),
64 });
65 }
66
67 let mut address_map = HashMap::with_capacity(data.containers.len() + data.addresses.len());
70 for (i, cdef) in data.containers.iter().enumerate() {
71 address_map.insert(cdef.id, (i as u32, 0usize));
72 }
73 for addr in &data.addresses {
75 let container_idx = container_map
76 .get(&addr.container_id)
77 .copied()
78 .ok_or(RuntimeError::UnresolvedDefinition(addr.container_id))?;
79 address_map.insert(addr.id, (container_idx, addr.byte_offset as usize));
80 }
81
82 if data.containers.is_empty() {
84 return Err(RuntimeError::NoRootContainer);
85 }
86 let root_idx = 0;
87
88 let name_table = data.name_table.clone();
89
90 let mut list_item_map = HashMap::with_capacity(data.list_items.len());
92 for li in &data.list_items {
93 list_item_map.insert(
94 li.id,
95 ListItemEntry {
96 name: li.name,
97 ordinal: li.ordinal,
98 origin: li.origin,
99 },
100 );
101 }
102
103 let mut list_defs = Vec::with_capacity(data.list_defs.len());
105 let mut list_def_map = HashMap::with_capacity(data.list_defs.len());
106 for ldef in &data.list_defs {
107 let idx = list_defs.len();
108 let mut items: Vec<_> = data
110 .list_items
111 .iter()
112 .filter(|li| li.origin == ldef.id)
113 .collect();
114 items.sort_by_key(|li| li.ordinal);
115 let item_ids: Vec<_> = items.iter().map(|li| li.id).collect();
116
117 list_def_map.insert(ldef.id, idx);
118 list_defs.push(ListDefEntry {
119 name: ldef.name,
120 items: item_ids,
121 });
122 }
123
124 let list_literals = data.list_literals.clone();
126
127 let mut external_fns = HashMap::with_capacity(data.externals.len());
129 for ext in &data.externals {
130 external_fns.insert(
131 ext.id,
132 ExternalFnEntry {
133 name: ext.name,
134 fallback: ext.fallback,
135 },
136 );
137 }
138
139 let mut address_by_path: HashMap<String, PathTarget> = HashMap::new();
151 if data.address_paths.is_empty() {
152 address_by_path.reserve(data.containers.len());
153 for (i, cdef) in data.containers.iter().enumerate() {
154 if let Some(name_id) = cdef.name {
155 let name = data.name_table[name_id.0 as usize].clone();
156 address_by_path.insert(
157 name,
158 PathTarget {
159 id: cdef.id,
160 container_idx: i as u32,
161 byte_offset: 0,
162 },
163 );
164 }
165 }
166 } else {
167 address_by_path.reserve(data.address_paths.len());
168 for ap in &data.address_paths {
169 if let Some(&(idx, offset)) = address_map.get(&ap.target) {
172 let name = data.name_table[ap.path.0 as usize].clone();
173 address_by_path.insert(
174 name,
175 PathTarget {
176 id: ap.target,
177 container_idx: idx,
178 byte_offset: offset,
179 },
180 );
181 }
182 }
183 }
184
185 let program = Program {
186 containers,
187 address_map,
188 scope_ids,
189 source_checksum: data.source_checksum,
190 globals,
191 global_map,
192 name_table,
193 address_by_path,
194 root_idx,
195 list_literals,
196 list_item_map,
197 list_defs,
198 list_def_map,
199 external_fns,
200 };
201 Ok((program, line_tables))
202}