1use std::collections::HashMap;
4
5use brink_format::{CountingFlags, DefinitionId, ListValue, NameId, Value};
6
7pub struct Program {
12 pub(crate) containers: Vec<LinkedContainer>,
13 pub(crate) address_map: HashMap<DefinitionId, (u32, usize)>,
16 pub(crate) scope_ids: Vec<DefinitionId>,
19 pub(crate) source_checksum: u32,
21 pub(crate) globals: Vec<GlobalSlot>,
22 pub(crate) global_map: HashMap<DefinitionId, u32>,
23 pub(crate) name_table: Vec<String>,
24 pub(crate) address_by_path: HashMap<String, PathTarget>,
29 pub(crate) root_idx: u32,
30 pub(crate) list_literals: Vec<ListValue>,
32 pub(crate) list_item_map: HashMap<DefinitionId, ListItemEntry>,
34 pub(crate) list_defs: Vec<ListDefEntry>,
36 pub(crate) list_def_map: HashMap<DefinitionId, usize>,
38 pub(crate) external_fns: HashMap<DefinitionId, ExternalFnEntry>,
40}
41
42pub(crate) struct LinkedContainer {
43 pub id: DefinitionId,
44 pub bytecode: Vec<u8>,
45 pub counting_flags: CountingFlags,
46 pub path_hash: i32,
47 pub param_count: u8,
49 pub scope_table_idx: u32,
51}
52
53pub(crate) struct GlobalSlot {
54 #[expect(dead_code, reason = "needed for save/load serialization and debugging")]
55 pub id: DefinitionId,
56 pub name: NameId,
57 pub default: Value,
58}
59
60pub(crate) struct ListItemEntry {
62 pub name: NameId,
63 pub ordinal: i32,
64 pub origin: DefinitionId,
65}
66
67pub(crate) struct ListDefEntry {
69 pub name: NameId,
70 pub items: Vec<DefinitionId>,
72}
73
74pub(crate) struct ExternalFnEntry {
76 pub name: NameId,
77 pub fallback: Option<DefinitionId>,
78}
79
80#[derive(Debug, Clone, Copy)]
84pub(crate) struct PathTarget {
85 pub id: DefinitionId,
86 pub container_idx: u32,
87 pub byte_offset: usize,
88}
89
90impl Program {
91 pub(crate) fn resolve_target(&self, id: DefinitionId) -> Option<(u32, usize)> {
93 self.address_map.get(&id).copied()
94 }
95
96 #[cfg(feature = "testing")]
98 pub fn resolve_address(&self, id: DefinitionId) -> Option<(u32, usize)> {
99 self.resolve_target(id)
100 }
101
102 pub(crate) fn container(&self, idx: u32) -> &LinkedContainer {
104 &self.containers[idx as usize]
105 }
106
107 #[cfg(feature = "testing")]
109 pub fn container_bytecode(&self, idx: u32) -> &[u8] {
110 &self.containers[idx as usize].bytecode
111 }
112
113 #[cfg(feature = "testing")]
115 #[expect(
116 clippy::cast_possible_truncation,
117 reason = "container count fits in u32"
118 )]
119 pub fn container_count(&self) -> u32 {
120 self.containers.len() as u32
121 }
122
123 pub fn source_checksum(&self) -> u32 {
125 self.source_checksum
126 }
127
128 pub(crate) fn scope_table_idx(&self, container_idx: u32) -> u32 {
130 self.containers[container_idx as usize].scope_table_idx
131 }
132
133 pub(crate) fn name(&self, id: NameId) -> &str {
135 &self.name_table[id.0 as usize]
136 }
137
138 pub(crate) fn resolve_global(&self, id: DefinitionId) -> Option<u32> {
140 self.global_map.get(&id).copied()
141 }
142
143 pub(crate) fn root_idx(&self) -> u32 {
145 self.root_idx
146 }
147
148 #[must_use]
163 pub fn find_address(&self, path: &str) -> Option<(u32, usize)> {
164 self.address_by_path
165 .get(path)
166 .map(|t| (t.container_idx, t.byte_offset))
167 }
168
169 pub(crate) fn find_path_target(&self, path: &str) -> Option<DefinitionId> {
174 self.address_by_path.get(path).map(|t| t.id)
175 }
176
177 pub(crate) fn path_param_count(&self, path: &str) -> Option<u8> {
182 self.address_by_path
183 .get(path)
184 .map(|t| self.containers[t.container_idx as usize].param_count)
185 }
186
187 pub fn global_defaults(&self) -> Vec<Value> {
189 self.globals.iter().map(|s| s.default.clone()).collect()
190 }
191
192 #[expect(clippy::cast_possible_truncation, reason = "global count fits in u32")]
195 pub fn global_index(&self, name: &str) -> Option<u32> {
196 self.globals
197 .iter()
198 .position(|slot| self.name(slot.name) == name)
199 .map(|i| i as u32)
200 }
201
202 pub(crate) fn list_literal(&self, idx: u16) -> &ListValue {
204 &self.list_literals[idx as usize]
205 }
206
207 pub(crate) fn list_item(&self, id: DefinitionId) -> Option<&ListItemEntry> {
209 self.list_item_map.get(&id)
210 }
211
212 pub(crate) fn list_def(&self, id: DefinitionId) -> Option<&ListDefEntry> {
214 self.list_def_map.get(&id).map(|&idx| &self.list_defs[idx])
215 }
216
217 pub(crate) fn list_def_by_name(&self, name: &str) -> Option<&ListDefEntry> {
219 self.list_defs
220 .iter()
221 .find(|def| self.name(def.name) == name)
222 }
223
224 pub(crate) fn external_fn(&self, id: DefinitionId) -> Option<&ExternalFnEntry> {
226 self.external_fns.get(&id)
227 }
228
229 pub fn global_name(&self, idx: u32) -> Option<&str> {
237 self.globals
238 .get(idx as usize)
239 .map(|slot| self.name(slot.name))
240 }
241
242 #[expect(clippy::cast_possible_truncation, reason = "global count fits in u32")]
244 pub fn global_count(&self) -> u32 {
245 self.globals.len() as u32
246 }
247
248 pub(crate) fn global_slot_name(&self, idx: usize) -> Option<&str> {
252 self.globals.get(idx).map(|slot| self.name(slot.name))
253 }
254
255 pub(crate) fn global_var_name(&self, id: DefinitionId) -> Option<&str> {
258 let slot = self.resolve_global(id)?;
259 self.global_slot_name(slot as usize)
260 }
261
262 pub(crate) fn list_item_name(&self, id: DefinitionId) -> Option<&str> {
264 self.list_item(id).map(|item| self.name(item.name))
265 }
266}
267
268#[cfg(test)]
269mod find_address_tests {
270 use super::*;
271
272 fn make_program_with_named_containers(names: &[&str]) -> Program {
273 let mut address_by_path = HashMap::new();
277 for (i, name) in names.iter().enumerate() {
278 #[expect(clippy::cast_possible_truncation, reason = "test fixture")]
279 address_by_path.insert(
280 (*name).to_string(),
281 PathTarget {
282 id: DefinitionId::new(brink_format::DefinitionTag::Address, i as u64),
283 container_idx: i as u32,
284 byte_offset: 0,
285 },
286 );
287 }
288 Program {
289 containers: Vec::new(),
290 address_map: HashMap::new(),
291 scope_ids: Vec::new(),
292 source_checksum: 0,
293 globals: Vec::new(),
294 global_map: HashMap::new(),
295 name_table: Vec::new(),
296 address_by_path,
297 root_idx: 0,
298 list_literals: Vec::new(),
299 list_item_map: HashMap::new(),
300 list_defs: Vec::new(),
301 list_def_map: HashMap::new(),
302 external_fns: HashMap::new(),
303 }
304 }
305
306 #[test]
307 fn finds_known_knot() {
308 let program = make_program_with_named_containers(&["intro", "outro"]);
309 assert_eq!(program.find_address("intro"), Some((0, 0)));
310 assert_eq!(program.find_address("outro"), Some((1, 0)));
311 }
312
313 #[test]
314 fn returns_none_for_unknown_knot() {
315 let program = make_program_with_named_containers(&["intro"]);
316 assert_eq!(program.find_address("nope"), None);
317 }
318
319 #[test]
320 fn empty_program_returns_none() {
321 let program = make_program_with_named_containers(&[]);
322 assert_eq!(program.find_address("anything"), None);
323 }
324}