Skip to main content

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}