use std::collections::HashMap;
use brink_format::{DefinitionId, StoryData};
use crate::error::RuntimeError;
use crate::program::{
ExternalFnEntry, GlobalSlot, LinkedContainer, ListDefEntry, ListItemEntry, PathTarget, Program,
};
#[expect(clippy::cast_possible_truncation, clippy::too_many_lines)]
pub fn link(
data: &StoryData,
) -> Result<(Program, Vec<Vec<brink_format::LineEntry>>), RuntimeError> {
let mut container_map = HashMap::with_capacity(data.containers.len());
for (i, cdef) in data.containers.iter().enumerate() {
let idx = i as u32;
container_map.insert(cdef.id, idx);
}
let mut scope_table_map: HashMap<DefinitionId, u32> =
HashMap::with_capacity(data.line_tables.len());
let mut line_tables: Vec<Vec<brink_format::LineEntry>> =
Vec::with_capacity(data.line_tables.len());
let mut scope_ids: Vec<DefinitionId> = Vec::with_capacity(data.line_tables.len());
for lt in &data.line_tables {
let idx = line_tables.len() as u32;
scope_table_map.insert(lt.scope_id, idx);
scope_ids.push(lt.scope_id);
line_tables.push(lt.lines.clone());
}
let mut containers = Vec::with_capacity(data.containers.len());
for cdef in &data.containers {
let scope_table_idx = scope_table_map.get(&cdef.scope_id).copied().unwrap_or(0);
containers.push(LinkedContainer {
id: cdef.id,
bytecode: cdef.bytecode.clone(),
counting_flags: cdef.counting_flags,
path_hash: cdef.path_hash,
param_count: cdef.param_count,
scope_table_idx,
});
}
let mut globals = Vec::with_capacity(data.variables.len());
let mut global_map = HashMap::with_capacity(data.variables.len());
for (i, gvar) in data.variables.iter().enumerate() {
let idx = i as u32;
global_map.insert(gvar.id, idx);
globals.push(GlobalSlot {
id: gvar.id,
name: gvar.name,
default: gvar.default_value.clone(),
});
}
let mut address_map = HashMap::with_capacity(data.containers.len() + data.addresses.len());
for (i, cdef) in data.containers.iter().enumerate() {
address_map.insert(cdef.id, (i as u32, 0usize));
}
for addr in &data.addresses {
let container_idx = container_map
.get(&addr.container_id)
.copied()
.ok_or(RuntimeError::UnresolvedDefinition(addr.container_id))?;
address_map.insert(addr.id, (container_idx, addr.byte_offset as usize));
}
if data.containers.is_empty() {
return Err(RuntimeError::NoRootContainer);
}
let root_idx = 0;
let name_table = data.name_table.clone();
let mut list_item_map = HashMap::with_capacity(data.list_items.len());
for li in &data.list_items {
list_item_map.insert(
li.id,
ListItemEntry {
name: li.name,
ordinal: li.ordinal,
origin: li.origin,
},
);
}
let mut list_defs = Vec::with_capacity(data.list_defs.len());
let mut list_def_map = HashMap::with_capacity(data.list_defs.len());
for ldef in &data.list_defs {
let idx = list_defs.len();
let mut items: Vec<_> = data
.list_items
.iter()
.filter(|li| li.origin == ldef.id)
.collect();
items.sort_by_key(|li| li.ordinal);
let item_ids: Vec<_> = items.iter().map(|li| li.id).collect();
list_def_map.insert(ldef.id, idx);
list_defs.push(ListDefEntry {
name: ldef.name,
items: item_ids,
});
}
let list_literals = data.list_literals.clone();
let mut external_fns = HashMap::with_capacity(data.externals.len());
for ext in &data.externals {
external_fns.insert(
ext.id,
ExternalFnEntry {
name: ext.name,
fallback: ext.fallback,
},
);
}
let mut address_by_path: HashMap<String, PathTarget> = HashMap::new();
if data.address_paths.is_empty() {
address_by_path.reserve(data.containers.len());
for (i, cdef) in data.containers.iter().enumerate() {
if let Some(name_id) = cdef.name {
let name = data.name_table[name_id.0 as usize].clone();
address_by_path.insert(
name,
PathTarget {
id: cdef.id,
container_idx: i as u32,
byte_offset: 0,
},
);
}
}
} else {
address_by_path.reserve(data.address_paths.len());
for ap in &data.address_paths {
if let Some(&(idx, offset)) = address_map.get(&ap.target) {
let name = data.name_table[ap.path.0 as usize].clone();
address_by_path.insert(
name,
PathTarget {
id: ap.target,
container_idx: idx,
byte_offset: offset,
},
);
}
}
}
let program = Program {
containers,
address_map,
scope_ids,
source_checksum: data.source_checksum,
globals,
global_map,
name_table,
address_by_path,
root_idx,
list_literals,
list_item_map,
list_defs,
list_def_map,
external_fns,
};
Ok((program, line_tables))
}