Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
matrix:
rust: [stable, "1.85"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
Expand All @@ -27,7 +27,7 @@ jobs:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
Expand All @@ -37,7 +37,7 @@ jobs:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
Expand All @@ -47,7 +47,7 @@ jobs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- run: cargo doc --no-deps
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
contents: read
id-token: write # required to mint the crates.io OIDC token
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0 # merge-base needs real history

Expand Down
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

All notable changes to this crate are documented here.

## 0.1.3

- Replaced em-dashes and en-dashes in rustdoc comments and the changelog with plain ASCII hyphens, so generated documentation renders consistently.
- Updated CI and publish workflows from `actions/checkout@v4` to `actions/checkout@v7` (Node 24 runtime). The crate still has no external Rust dependencies.

## 0.1.2

- Fixed PC-relative branch resolution for backward branches. The IFPS VM adds the branch delta to the current position in wrapping `Cardinal` arithmetic, so the compiler encodes a backward jump as a large unsigned delta. `Instruction::branch_target()` / `branch_targets()` resolved `condgoto` / `condnotgoto` with a checked add and therefore returned `None` for every backward conditional branch — losing the back edge of every loop — while the disassembly display saturated and printed `0xffffffff`.
- Fixed PC-relative branch resolution for backward branches. The IFPS VM adds the branch delta to the current position in wrapping `Cardinal` arithmetic, so the compiler encodes a backward jump as a large unsigned delta. `Instruction::branch_target()` / `branch_targets()` resolved `condgoto` / `condnotgoto` with a checked add and therefore returned `None` for every backward conditional branch - losing the back edge of every loop - while the disassembly display saturated and printed `0xffffffff`.
- Unified the four separate copies of the branch-delta arithmetic (two in `bytecode`, two in `disasm`) onto a single `relative_target` helper, so `goto` and `condgoto` can no longer disagree about the same wire delta.
- Fixed the coordinate system of absolute branch targets. `flaggoto` and the four `PushExceptionHandler` regions store *procedure-local* positions, but relative branch targets and `Instruction::offset` are IFPS-blob offsets, so a listing mixed two coordinate systems and handler targets did not resolve against the listing they appeared in for any proc but the first. Disassembly output now rebases them onto the proc's `bytecode_offset`, and new `ProcDisasm::branch_target()` / `ProcDisasm::branch_targets()` return every form in blob coordinates. The `Instruction`-level helpers keep their raw behaviour and now document the caveat.

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pascalscript"
version = "0.1.2"
version = "0.1.3"
edition = "2024"
rust-version = "1.85"
description = "Read-only parser + disassembler for the RemObjects PascalScript III binary container format (IFPS)"
Expand Down
12 changes: 6 additions & 6 deletions src/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Attribute-block walker — `ReadAttributes` in
//! Attribute-block walker - `ReadAttributes` in
//! `uPSRuntime.pas:2333-2532`.
//!
//! Build-21+ attaches an inline attribute block at the end of
//! every type and proc entry (vars do not carry attributes — see
//! every type and proc entry (vars do not carry attributes - see
//! `LoadVars`, `uPSRuntime.pas:2936-2989`). The block layout is:
//!
//! ```text
Expand All @@ -15,7 +15,7 @@
//! payload: per-BaseType (see `AttributeFieldValue`)
//! ```
//!
//! Reading an attribute requires the type table parsed so far —
//! Reading an attribute requires the type table parsed so far -
//! the per-field payload's size depends on the referenced type's
//! `BaseType` (and its body, in the `Set` case).

Expand All @@ -36,7 +36,7 @@ pub type AttributeFieldValue<'a> = Literal<'a>;
/// One parsed attribute.
#[derive(Clone, Debug)]
pub struct Attribute<'a> {
/// ASCII identifier — the attribute "name" the script
/// ASCII identifier - the attribute "name" the script
/// declared, e.g. `Description`.
pub name: &'a [u8],
/// Field values in declaration order.
Expand All @@ -46,7 +46,7 @@ pub struct Attribute<'a> {
/// One field of an [`Attribute`].
#[derive(Clone, Debug)]
pub struct AttributeField<'a> {
/// Index into the type table — describes the field's type.
/// Index into the type table - describes the field's type.
pub type_no: u32,
/// Decoded wire value.
pub value: Literal<'a>,
Expand All @@ -57,7 +57,7 @@ const MAX_NAME_LEN: u32 = 0x4000_0000;
/// Parses the attribute block at `reader`'s cursor into a vec of
/// [`Attribute`]s.
///
/// `types` is the type table parsed so far — including the
/// `types` is the type table parsed so far - including the
/// current entry, since upstream pushes the type onto `FTypes`
/// before reading its attribute block (`uPSRuntime.pas:2789-2798`).
/// Empty blocks (count == 0) return `Ok(vec![])`.
Expand Down
22 changes: 11 additions & 11 deletions src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
#[derive(Clone, Debug, PartialEq)]
pub struct Instruction<'a> {
/// Offset of this instruction's leading byte within the
/// IFPS blob — same coordinate system as
/// IFPS blob - same coordinate system as
/// [`crate::InternalProc::bytecode_offset`].
/// Useful when chasing PC-relative branches: a `Goto`
/// instruction's `offset` is relative to the byte
Expand All @@ -41,7 +41,7 @@ impl Instruction<'_> {
/// setup opcodes carry multiple targets, so callers that need all of
/// them should use [`Self::branch_targets`].
///
/// Carries the same coordinate caveat as [`Self::branch_targets`] — prefer
/// Carries the same coordinate caveat as [`Self::branch_targets`] - prefer
/// [`ProcDisasm::branch_target`], which normalizes every form to IFPS-blob
/// coordinates.
pub fn branch_target(&self) -> Option<u32> {
Expand All @@ -57,7 +57,7 @@ impl Instruction<'_> {
///
/// - Relative branches (`goto`, `condgoto`, `condnotgoto`, the pop-and-goto
/// forms) resolve against [`Self::next_offset`], which is an IFPS-blob
/// offset — so these come back **blob-absolute**. A delta is
/// offset - so these come back **blob-absolute**. A delta is
/// coordinate-independent, so this is exact.
/// - `flaggoto` and the four `PushExceptionHandler` regions store an
/// *absolute* position, and IFPS positions are **procedure-local** (the VM
Expand Down Expand Up @@ -106,7 +106,7 @@ impl Instruction<'_> {
/// pop-and-goto forms) encodes the same thing: a four-byte little-endian delta
/// added to the position after the instruction. The VM performs that addition
/// on Delphi `Cardinal`s (`CurrentPosition := CurrentPosition + NewPosition`),
/// which wraps — so the compiler emits a **backward** branch as a large
/// which wraps - so the compiler emits a **backward** branch as a large
/// unsigned delta that wraps back around. Modelling the addition as
/// checked or saturating therefore loses exactly the backward branches, i.e.
/// every loop.
Expand All @@ -116,7 +116,7 @@ impl Instruction<'_> {
/// both reach this function as a raw `u32` delta.
///
/// The result can land outside the owning procedure when the input is
/// malformed — IFPS producers are untrusted. Validating that is the caller's
/// malformed - IFPS producers are untrusted. Validating that is the caller's
/// job (the VM itself bounds-checks the new position against the proc length),
/// which is why this returns a plain `u32` rather than an `Option`.
pub(crate) fn relative_target(base: u32, delta: u32) -> u32 {
Expand All @@ -130,7 +130,7 @@ pub struct ProcDisasm<'a> {
/// disassembled proc.
pub proc_index: u32,
/// Byte offset where the proc's bytecode starts inside the
/// IFPS blob — copied verbatim from
/// IFPS blob - copied verbatim from
/// [`crate::InternalProc::bytecode_offset`] for
/// cross-reference.
pub bytecode_offset: u32,
Expand All @@ -152,7 +152,7 @@ impl ProcDisasm<'_> {
}

/// Returns every control-flow target `instruction` carries, all of them in
/// **IFPS-blob coordinates** — the same coordinate system as
/// **IFPS-blob coordinates** - the same coordinate system as
/// [`Instruction::offset`], so a target can be compared directly against
/// the instruction stream.
///
Expand Down Expand Up @@ -181,14 +181,14 @@ impl ProcDisasm<'_> {
/// `blob` is the entire IFPS byte buffer; `bytecode_offset` and
/// `bytecode_len` are the proc's window into that buffer
/// (validated up-front by [`crate::proc::parse_proc`]).
/// `types` is the parsed type table — needed for typed-literal
/// `types` is the parsed type table - needed for typed-literal
/// operand payloads.
///
/// # Errors
///
/// - [`Error::BytecodeOutOfRange`] when the window falls outside
/// `blob`.
/// - Any error from the per-opcode decoder — bad opcode byte,
/// - Any error from the per-opcode decoder - bad opcode byte,
/// malformed operand, truncated payload.
pub(crate) fn disassemble_proc<'a>(
blob: &'a [u8],
Expand Down Expand Up @@ -267,7 +267,7 @@ mod tests {
}

/// A backward conditional branch is encoded as a wrapping `Cardinal`
/// delta, exactly like the unconditional `goto` — resolving it with
/// delta, exactly like the unconditional `goto` - resolving it with
/// checked or saturating arithmetic loses every loop in the program.
#[test]
fn backward_conditional_branch_wraps_to_a_target_behind_the_instruction() {
Expand Down Expand Up @@ -306,7 +306,7 @@ mod tests {
assert_eq!(unconditional.branch_target(), conditional.branch_target());
}

/// `flaggoto` carries an absolute position, not a delta — and on the bare
/// `flaggoto` carries an absolute position, not a delta - and on the bare
/// instruction it comes back procedure-local, as stored.
#[test]
fn flag_goto_target_is_absolute() {
Expand Down
8 changes: 4 additions & 4 deletions src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{

/// Parsed IFPS blob.
///
/// Borrows from the original byte slice — names, decls, and
/// Borrows from the original byte slice - names, decls, and
/// bytecode regions all reference the original buffer rather
/// than allocating.
#[derive(Clone, Debug)]
Expand All @@ -38,7 +38,7 @@ impl<'a> Container<'a> {
/// # Errors
///
/// Returns whatever [`Header::parse`] or any of the per-table
/// walkers (types, procs, vars) surface — see [`Error`] for
/// walkers (types, procs, vars) surface - see [`Error`] for
/// the full set.
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let header = Header::parse(bytes)?;
Expand Down Expand Up @@ -151,8 +151,8 @@ impl<'a> Container<'a> {
DisasmDisplay::new(self, disasm)
}

/// Returns a [`ContainerSummary`] — single-line
/// `fmt::Display`-ready triage view (`IFPS build N — A
/// Returns a [`ContainerSummary`] - single-line
/// `fmt::Display`-ready triage view (`IFPS build N - A
/// types, B procs (X internal / Y external), C vars, main=…`).
pub fn display_summary(&self) -> ContainerSummary<'a, '_> {
ContainerSummary::new(self)
Expand Down
18 changes: 9 additions & 9 deletions src/disasm.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Symbolic disassembly — render a [`ProcDisasm`] as text with
//! Symbolic disassembly - render a [`ProcDisasm`] as text with
//! operand indices resolved against the container's symbol
//! tables.
//!
Expand All @@ -10,12 +10,12 @@
//! offsets via [`Instruction::next_offset`]; absolute branches
//! (`FlagGoto`, the exception-handler regions) are stored
//! procedure-locally and are rebased onto the proc's
//! `bytecode_offset`, so every offset in a listing — the leading
//! instruction offset and every printed target alike — is in one
//! `bytecode_offset`, so every offset in a listing - the leading
//! instruction offset and every printed target alike - is in one
//! coordinate system.
//!
//! Control-flow reconstruction (loop / if-else detection from
//! goto patterns) is deferred — the raw target offsets in the
//! goto patterns) is deferred - the raw target offsets in the
//! output are sufficient for tracing manually, and the
//! basic-block analysis required for structural recovery is its
//! own substantial layer.
Expand All @@ -31,7 +31,7 @@ use crate::{
proc::{Proc, ProcKind},
};

/// One-line summary of a [`Container`] — counts of types, procs
/// One-line summary of a [`Container`] - counts of types, procs
/// (split into internal vs. external), vars, plus the wire build
/// number. Suitable for triage / log output.
///
Expand Down Expand Up @@ -72,7 +72,7 @@ impl fmt::Display for ContainerSummary<'_, '_> {
};
write!(
f,
"IFPS build {} — {} types, {} procs ({} internal / {} external), {} vars, {main_proc}",
"IFPS build {} - {} types, {} procs ({} internal / {} external), {} vars, {main_proc}",
header.build_no,
header.type_count,
header.proc_count,
Expand All @@ -87,7 +87,7 @@ impl fmt::Display for ContainerSummary<'_, '_> {
/// [`Container`] it came from for symbol resolution.
///
/// Construct via [`Container::display`] or directly via
/// [`Self::new`]. Implements [`fmt::Display`] — drop into
/// [`Self::new`]. Implements [`fmt::Display`] - drop into
/// `println!`, `format!`, or any other formatting context.
#[derive(Clone, Copy)]
pub struct DisasmDisplay<'a, 'c> {
Expand Down Expand Up @@ -146,8 +146,8 @@ fn format_proc_self_label(proc: &Proc<'_>) -> String {
///
/// `bytecode_offset` is the owning proc's base in the IFPS blob. It is needed
/// because `flaggoto` and the exception-handler regions store *procedure-local*
/// absolute positions, whereas the leading `inst.offset` on every line — and
/// every relative branch target — is blob-absolute. Printing them as stored
/// absolute positions, whereas the leading `inst.offset` on every line - and
/// every relative branch target - is blob-absolute. Printing them as stored
/// would make a handler target unresolvable against the very listing it sits in
/// for every proc but the first.
fn write_instruction(
Expand Down
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::fmt;
#[non_exhaustive]
pub enum Error {
/// `bytes` were too short to contain the field named by
/// `what` — typically the 28-byte header, or a fixed payload
/// `what` - typically the 28-byte header, or a fixed payload
/// past the header.
Truncated {
/// Static label for the truncated structure.
Expand Down Expand Up @@ -60,7 +60,7 @@ pub enum Error {
length: u32,
},
/// An integer overflow occurred while computing a buffer
/// offset or size — the input contains a value too large to
/// offset or size - the input contains a value too large to
/// represent on this platform.
Overflow {
/// Static label identifying the overflowing computation.
Expand Down
10 changes: 5 additions & 5 deletions src/header.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! `TPSHeader` — the fixed 28-byte preamble of every IFPS blob.
//! `TPSHeader` - the fixed 28-byte preamble of every IFPS blob.
//!
//! Wire layout (`uPSRuntime.pas:1204-1212`):
//!
Expand All @@ -16,12 +16,12 @@
//!
//! The `ImportTableSize` field is included for forward parity with
//! the upstream layout but `LoadData` (`uPSRuntime.pas:3030`) does
//! not actually walk an import table — externals are stored
//! not actually walk an import table - externals are stored
//! inline on the proc table, with their `Flags & 1` bit set.

use crate::{error::Error, reader::Reader};

/// Canonical magic byte sequence — first 4 bytes of every IFPS
/// Canonical magic byte sequence - first 4 bytes of every IFPS
/// blob. Mirrors [`PS_VALID_HEADER`].
pub const IFPS_MAGIC: [u8; 4] = *b"IFPS";

Expand All @@ -43,7 +43,7 @@ pub const PS_CURRENT_BUILD_NO: u32 = 23;
/// <> InvalidVal`).
pub const INVALID_VAL: u32 = u32::MAX;

/// Total header size in bytes — `SizeOf(TPSHeader)` in upstream.
/// Total header size in bytes - `SizeOf(TPSHeader)` in upstream.
pub const HEADER_SIZE: usize = 28;

/// Parsed [`TPSHeader`](https://github.com/remobjects/pascalscript)
Expand All @@ -66,7 +66,7 @@ pub struct Header {
pub main_proc_no: u32,
/// Trailing `ImportTableSize` field. Retained for layout
/// parity but has no functional role at the supported build
/// range — the proc table carries import data inline.
/// range - the proc table carries import data inline.
pub import_table_size: u32,
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
//! RemObjects PascalScript is a Pascal-syntax scripting library
//! widely embedded in Delphi applications (Inno Setup's `[Code]`
//! sections being one of the most prominent users). This crate
//! parses the on-disk binary form — header, type / proc / var
//! / attribute tables, and the bytecode stream — into typed
//! parses the on-disk binary form - header, type / proc / var
//! / attribute tables, and the bytecode stream - into typed
//! Rust views, plus surfaces a symbolic disassembly via
//! [`Container::display`].
//!
Expand Down
Loading