forma_ir/lib.rs
1//! Forma Module IR: binary format definitions and parsers.
2//!
3//! This crate provides the core IR types and parsing logic that can be compiled
4//! to both native Rust and WebAssembly targets.
5
6// README.md and SECURITY.md both promise "Zero `unsafe` blocks". This is what
7// makes that a compiler-enforced property of the shipped library rather than a
8// claim. It is crate-root rather than a `[lints]` entry because `[lints]`
9// covers every target in the package, and the allocation-counting harness in
10// `tests/list_scaling.rs` must implement `GlobalAlloc` — a separate crate, not
11// code that ships.
12#![forbid(unsafe_code)]
13#![allow(clippy::type_complexity)]
14
15/// On-disk FMIR layout: header, section table, opcodes, and the table entry
16/// types. See `docs/FMIR-FORMAT.md` for the stability contract.
17pub mod format;
18/// Bounds-checked parsing and one-shot structural validation of an FMIR file.
19pub mod parser;
20/// Runtime slot values and the dense `SlotData` a page handler populates.
21pub mod slot;
22/// The SSR walker: opcode stream plus slot values in, HTML out.
23pub mod walker;
24
25/// Human-readable text dump of a module, for debugging and diffing.
26#[cfg(feature = "dump")]
27pub mod dump;
28
29// Re-exports for convenience
30pub use format::{
31 IrError, IrHeader, IslandEntry, IslandTrigger, Opcode, PropsMode, SectionDescriptor,
32 SectionTable, SlotEntry, SlotSource, SlotType, HEADER_SIZE, IR_VERSION, MAGIC,
33 SECTION_TABLE_SIZE,
34};
35pub use parser::{IrModule, IslandTableParsed, SlotTable, StringTable};
36pub use slot::{json_to_slot_value, SlotData, SlotValue};
37pub use walker::{
38 walk_island, walk_island_with_limits, walk_to_html, walk_to_html_with_limits, WalkLimits,
39 DEFAULT_MAX_LIST_ITERATIONS, DEFAULT_MAX_OUTPUT_BYTES,
40};
41
42#[cfg(feature = "wasm")]
43use wasm_bindgen::prelude::*;
44
45/// Full page render: parse IR bytes + JSON slots → HTML string.
46///
47/// Panics on error (throws a JS exception in WASM — the client loader catches
48/// it and falls back to Phase 1). That set of errors now includes the walker's
49/// resource budgets ([`WalkLimits`]): an over-limit render throws here rather
50/// than aborting the wasm instance, and malformed control-flow lengths that
51/// used to render garbage now throw too. The client loader must have a
52/// rejection handler on this call — that is pre-existing behavior for parse
53/// errors, but the set of inputs that reach it is wider.
54#[cfg(feature = "wasm")]
55#[wasm_bindgen]
56pub fn render(ir_bytes: &[u8], slots_json: &str) -> String {
57 let module = parser::IrModule::parse(ir_bytes).unwrap_or_else(|e| panic!("IR parse: {e}"));
58 let slots =
59 slot::SlotData::from_json(slots_json, &module).unwrap_or_else(|e| panic!("slots: {e}"));
60 walker::walk_to_html(&module, &slots).unwrap_or_else(|e| panic!("walk: {e}"))
61}
62
63/// Fragment render: parse IR bytes + JSON slots → the island's ROOT element,
64/// carrying its full `data-forma-*` attribute set (including `data-forma-props`
65/// when the island has props). Swap it in with `replaceWith`/`outerHTML`, never
66/// `innerHTML` — that would discard the attributes the runtime selects on.
67///
68/// Panics on error (throws a JS exception in WASM — client loader catches and
69/// falls back). See [`render`] for the widened error set.
70#[cfg(feature = "wasm")]
71#[wasm_bindgen]
72pub fn render_island(ir_bytes: &[u8], slots_json: &str, island_id: u16) -> String {
73 let module = parser::IrModule::parse(ir_bytes).unwrap_or_else(|e| panic!("IR parse: {e}"));
74 let slots =
75 slot::SlotData::from_json(slots_json, &module).unwrap_or_else(|e| panic!("slots: {e}"));
76 walker::walk_island(&module, &slots, island_id).unwrap_or_else(|e| panic!("walk island: {e}"))
77}