pascalscript/container.rs
1//! Top-level driver: parse an entire IFPS blob into typed views.
2//!
3//! Walk order matches `TPSExec.LoadData`
4//! (`uPSRuntime.pas:2991-3033`): header, then types, then procs,
5//! then vars, then validate `MainProcNo`. Build-21+ attribute
6//! blocks are recorded as opaque byte slices on each entry and
7//! decoded structurally via [`crate::attribute`].
8
9use crate::{
10 attribute,
11 bytecode::{ProcDisasm, disassemble_proc},
12 disasm::{ContainerSummary, DisasmDisplay},
13 error::Error,
14 header::{HEADER_SIZE, Header},
15 proc::{Proc, ProcKind, has_attributes, parse_proc},
16 reader::Reader,
17 ty::{Type, parse_type},
18 var::{Var, parse_var},
19};
20
21/// Parsed IFPS blob.
22///
23/// Borrows from the original byte slice — names, decls, and
24/// bytecode regions all reference the original buffer rather
25/// than allocating.
26#[derive(Clone, Debug)]
27pub struct Container<'a> {
28 bytes: &'a [u8],
29 header: Header,
30 types: Vec<Type<'a>>,
31 procs: Vec<Proc<'a>>,
32 vars: Vec<Var<'a>>,
33}
34
35impl<'a> Container<'a> {
36 /// Parses an IFPS blob from `bytes`.
37 ///
38 /// # Errors
39 ///
40 /// Returns whatever [`Header::parse`] or any of the per-table
41 /// walkers (types, procs, vars) surface — see [`Error`] for
42 /// the full set.
43 pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
44 let header = Header::parse(bytes)?;
45 let mut reader = Reader::new(bytes);
46 // Position the cursor past the fixed header.
47 reader.skip(HEADER_SIZE, "header skip")?;
48
49 let mut types: Vec<Type<'a>> = Vec::with_capacity(header.type_count as usize);
50 for _ in 0..header.type_count {
51 let so_far = u32::try_from(types.len()).unwrap_or(u32::MAX);
52 // Match upstream `LoadTypes` push-then-attr order:
53 // the type goes onto the table BEFORE its attribute
54 // block reads, so the block can reference any type
55 // 0..=N (including itself).
56 types.push(parse_type(&mut reader, so_far)?);
57 if header.build_no >= 21 {
58 let attrs = attribute::parse_block(&mut reader, &types)?;
59 if let Some(last) = types.last_mut() {
60 last.attributes = attrs;
61 }
62 }
63 }
64
65 let mut procs: Vec<Proc<'a>> = Vec::with_capacity(header.proc_count as usize);
66 for _ in 0..header.proc_count {
67 let proc = parse_proc(&mut reader, bytes.len())?;
68 let needs_attrs = has_attributes(proc.flags_raw);
69 procs.push(proc);
70 if needs_attrs {
71 let attrs = attribute::parse_block(&mut reader, &types)?;
72 if let Some(last) = procs.last_mut() {
73 last.attributes = attrs;
74 }
75 }
76 }
77
78 let mut vars: Vec<Var<'a>> = Vec::with_capacity(header.var_count as usize);
79 for _ in 0..header.var_count {
80 vars.push(parse_var(&mut reader, header.type_count)?);
81 }
82
83 // Mirror upstream: MainProcNo must be either InvalidVal
84 // or a valid proc index.
85 if !header.has_no_main_proc() && (header.main_proc_no as usize) >= procs.len() {
86 return Err(Error::Overflow {
87 what: "MainProcNo references missing proc",
88 });
89 }
90
91 Ok(Self {
92 bytes,
93 header,
94 types,
95 procs,
96 vars,
97 })
98 }
99
100 /// Returns the parsed fixed header.
101 pub fn header(&self) -> Header {
102 self.header
103 }
104
105 /// Returns the original blob bytes.
106 pub fn bytes(&self) -> &'a [u8] {
107 self.bytes
108 }
109
110 /// Returns the parsed type table in declaration order.
111 pub fn types(&self) -> &[Type<'a>] {
112 &self.types
113 }
114
115 /// Returns the parsed proc table in declaration order.
116 pub fn procs(&self) -> &[Proc<'a>] {
117 &self.procs
118 }
119
120 /// Returns the parsed var table in declaration order.
121 pub fn vars(&self) -> &[Var<'a>] {
122 &self.vars
123 }
124
125 /// Returns the entry-point proc, or `None` when the blob has
126 /// no main proc (`MainProcNo == u32::MAX`).
127 pub fn main_proc(&self) -> Option<&Proc<'a>> {
128 if self.header.has_no_main_proc() {
129 return None;
130 }
131 self.procs.get(self.header.main_proc_no as usize)
132 }
133
134 /// Wraps `disasm` in a [`DisasmDisplay`] bound to this
135 /// container's symbol tables, ready for `format!` /
136 /// `println!`. Convenience over constructing the wrapper
137 /// manually via [`DisasmDisplay::new`].
138 pub fn display<'c>(&'c self, disasm: &'c ProcDisasm<'a>) -> DisasmDisplay<'a, 'c> {
139 DisasmDisplay::new(self, disasm)
140 }
141
142 /// Returns a [`ContainerSummary`] — single-line
143 /// `fmt::Display`-ready triage view (`IFPS build N — A
144 /// types, B procs (X internal / Y external), C vars, main=…`).
145 pub fn display_summary(&self) -> ContainerSummary<'a, '_> {
146 ContainerSummary::new(self)
147 }
148
149 /// Disassembles the bytecode body of the proc at
150 /// `proc_index`.
151 ///
152 /// Returns `Ok(None)` when the requested proc is external
153 /// (no bytecode body to walk). Returns `Ok(Some(_))` with the
154 /// fully decoded instruction stream when the proc is
155 /// internal. The returned [`ProcDisasm`] borrows from the
156 /// container's IFPS blob.
157 ///
158 /// # Errors
159 ///
160 /// - [`Error::TypeIndexOutOfRange`] when `proc_index >=
161 /// procs.len()`.
162 /// - [`Error::BytecodeOutOfRange`] /
163 /// [`Error::UnknownBaseType`] / [`Error::Truncated`] when
164 /// an instruction can't be decoded.
165 pub fn disassemble(&self, proc_index: u32) -> Result<Option<ProcDisasm<'a>>, Error> {
166 let count = u32::try_from(self.procs.len()).unwrap_or(u32::MAX);
167 let proc = self
168 .procs
169 .get(proc_index as usize)
170 .ok_or(Error::TypeIndexOutOfRange {
171 index: proc_index,
172 count,
173 })?;
174 let internal = match &proc.kind {
175 ProcKind::Internal(int) => int,
176 ProcKind::External(_) => return Ok(None),
177 };
178 let disasm = disassemble_proc(
179 self.bytes,
180 proc_index,
181 internal.bytecode_offset,
182 internal.bytecode_len,
183 &self.types,
184 )?;
185 Ok(Some(disasm))
186 }
187}