pub struct Program { /* private fields */ }Expand description
Implementations§
Source§impl Program
impl Program
Sourcepub fn container_path(&self, idx: u32) -> Option<&str>
pub fn container_path(&self, idx: u32) -> Option<&str>
The knot or knot.stitch path a container names, if it names one.
Exact: an anonymous container (a choice body, gather, sequence
branch) is None — the save format relies on that to write such a
container’s visit entry without a path. For “where is this
container” see Program::scope_path.
Sourcepub fn scope_path(&self, idx: u32) -> Option<&str>
pub fn scope_path(&self, idx: u32) -> Option<&str>
The knot or knot.stitch a container sits in: its own path when it
names one, otherwise its lexical scope’s — a choice body, gather, or
sequence branch reports the knot/stitch that holds it (after
choose, the frame holds only the chosen branch, and the story is
still in that knot). None for the root scope and anything directly
under it. The runtime’s own vocabulary for “where am I” — see
Story::current_path.
Sourcepub fn resolve_address(&self, id: DefinitionId) -> Option<(u32, usize)>
pub fn resolve_address(&self, id: DefinitionId) -> Option<(u32, usize)>
Resolve a definition ID to (container_idx, byte_offset).
Promoted from #[cfg(feature = "testing")] to real public API by
W2 (#3295): with Self::definition_id_for_path it is the
name-based half of source→program addressing (“break on
tavern.order”), which the wasm bridge composes as
resolve_path_address. A pure lookup over the container table —
nothing here touches the VM hot path, so the step_once promotion
warning (docs/debugger-spec.md §1.4) does not apply.
Sourcepub fn resolve_debug_position(
&self,
position: DebugPosition,
) -> Option<DebugSourceLocation>
pub fn resolve_debug_position( &self, position: DebugPosition, ) -> Option<DebugSourceLocation>
The program→source resolver (D9, issue #3187; wire encoding: D6,
docs/debugger-spec.md §2.2). Resolves a runtime execution position
— crate::DebugPosition, as reported by
crate::DebugSnapshot::position/crate::DebugFrame::position
(D4, #3182) — to the source range it was compiled from, via this
program’s DebugInfo section.
None when:
- no
DebugInfosection is present (a release-exported or--debug-info-less compile — §1.2 ship policy: this is the expected, non-error case for most builds, not a fault); container_idxis out of range for the section’s container table (defensive — should not happen for a position this sameProgramproduced);offsetis before the container’s first recorded entry (the section’s coverage guarantee, §2.2, means this should not happen for a real instruction boundary either, but a reader must not panic on an adversarial/malformed position).
The returned range’s file is None for the reserved synthetic
sentinel file (index 0, §2.5) — a compiler-synthesized construct
with no author source to point at — and Some(path) (project-root-
relative) otherwise. This is exactly the path/span pair the
studio’s source Location space needs (docs/studio-shell-spec.md
§6.1) — a caller’s program resolver wraps this method and returns
{ kind: "source", file, span: { start: range_start, end: range_start + range_len } }.
Entries within a container are sorted ascending by
bytecode_offset and cover the container’s full address range with
no gaps (§2.2), so a floor lookup — the last entry whose
bytecode_offset is <= offset — always names the instruction’s
own statement, matching how a running VM’s offset (the next
instruction to execute, always itself a decoded instruction
boundary) lines up against entries recorded at instruction
boundaries during codegen’s own walk.
Source§impl Program
impl Program
Sourcepub fn resolve_debug_line(
&self,
position: DebugPosition,
) -> Option<ResolvedDebugLine<'_>>
pub fn resolve_debug_line( &self, position: DebugPosition, ) -> Option<ResolvedDebugLine<'_>>
The file:line — plus the covering entry’s exact byte range — of a
bytecode position (W6/#3299). The line places the execution
highlight’s band and the paused chip; the RANGE rides along so
finer-than-line consumers need no new seam: expression-level
entries (D1’s unflagged rows, once #3183’s Expr provenance
lands), instruction stepping in the editor, and the one
mid-line case that exists TODAY — a step-out lands at the call
site, not a line start (docs/debugger-spec.md §4’s finish
semantics).
0-based line (UIs showing 1-based convert at their edge). None
when the position doesn’t resolve, resolves to the synthetic
sentinel, or that file carries no line index.
Sourcepub fn line_at(&self, file: &str, byte: u32) -> Option<u32>
pub fn line_at(&self, file: &str, byte: u32) -> Option<u32>
0-based line containing byte in file (#3264) — the public form
of the lookup Self::debug_line_key uses internally. None when
the file is unknown or carries no line index.
Sourcepub fn resolve_source_range(
&self,
file: &str,
start: u32,
end: u32,
) -> Option<DebugPosition>
pub fn resolve_source_range( &self, file: &str, start: u32, end: u32, ) -> Option<DebugPosition>
The inverse of Self::resolve_debug_position (D9/#3187): the
program address to break on for a span of source text — issue
#3246, the half a breakpoint gutter needs. BreakpointSet is keyed
by (container_idx, offset); an editor speaks in source. This maps
the latter to the former.
§Why a byte range and not a line number
The DebugInfo section records byte ranges, and a Program
holds neither source text nor a line table — so it physically
cannot turn “line 7” into bytes. That conversion belongs where the
source already lives (the editor, the CLI’s own file read), which
also keeps the UTF-8/UTF-16 question out of the runtime entirely.
The caller passes the half-open byte range [start, end) it
considers “the line” (or a selection, or any span), and this answers
where to break within it.
§Which candidate wins
Every entry in every container whose file is file and whose
range_start lies in [start, end) is a candidate. The winner is
the minimum by (range_start, container_idx, bytecode_offset):
range_startfirst — the textually earliest construct in the span, which is what “break on this line” means to a person. Note this is deliberately not “lowestcontainer_idx”: containers are independent bytecode streams with no execution order between them, so ordering by container index would be arbitrary dressed up as a rule.- then
container_idx, thenbytecode_offset— pure tie-breaking, so a given span always yields the same address rather than whichever entry iteration happened to reach first (CLAUDE.md: determinism matters).
§None is a real answer, not a failure
Returns None when the span contains no executable code at all — a
comment, a blank line, a line whose code folded away — and when the
artifact carries no DebugInfo or names no such file. Callers
must surface that: a gutter has to refuse to arm visibly,
because a breakpoint that silently never hits is worse than no
breakpoint.
Entries whose file is the reserved synthetic sentinel (§2.5) never match, since no author-facing path names it.
Sourcepub fn has_debug_info(&self) -> bool
pub fn has_debug_info(&self) -> bool
Whether this program carries a DebugInfo section at all (#3248).
Every other debug accessor returns None for two very different
reasons — “this artifact was compiled without --debug-info” and
“that particular position/line has nothing on it” — and a debugger
front-end must tell a user which. Reporting “that line has no
executable code” for a story compiled without the flag sends the
author hunting a bug in their source when the fix is a compiler
flag. This is the cheap discriminator that keeps that message
honest; it says nothing about whether any particular lookup will
succeed.
Sourcepub fn resolve_source_line(
&self,
file: &str,
line: u32,
) -> Option<DebugPosition>
pub fn resolve_source_line( &self, file: &str, line: u32, ) -> Option<DebugPosition>
The program address to break on for a line of source, with no
source text required (#3261) — the DebugInfo file table carries a
per-file line index, so the engine can answer file:line directly.
line is 0-based. Every UI that shows 1-based line numbers
converts at its own edge; keeping the engine 0-based means the
fencepost lives in exactly one place per consumer instead of being
re-decided here.
This is the shape a remote debugger frontend needs — DAP’s
setBreakpoints is file + line, and an adapter may hold no source
at all. It is a thin wrapper over
Self::resolve_source_range: the line index turns the line into
its half-open byte span, and the same “textually earliest construct
wins” rule picks the address.
None when the file is unknown, carries no line index (compiled
before this data existed, or with no source text supplied), the line
is past the end of the file, or the line holds no executable code —
a comment, a blank, a line whose code folded away. Callers must
surface that: a gutter has to refuse to arm visibly, because a
breakpoint that silently never hits is worse than no breakpoint.
Sourcepub fn line_span(&self, file: &str, line: u32) -> Option<(u32, u32)>
pub fn line_span(&self, file: &str, line: u32) -> Option<(u32, u32)>
The half-open byte span [start, end) of a 0-based line in file,
from the DebugInfo file table’s line index (#3261). None when the
file is unknown, has no line index, or the line is past its end.
The last line runs to the end of the file, which the index does not
record — so it is represented as u32::MAX, an end bound no real
range_start can reach. That is deliberate rather than clamping to
a length the section does not carry.
Sourcepub fn source_matches(&self, file: &str, text: &str) -> Option<bool>
pub fn source_matches(&self, file: &str, text: &str) -> Option<bool>
Whether text is byte-identical to the source file was compiled
from, by the DebugInfo file table’s source_hash (#3261).
The problem this exists for: both debug resolvers happily answer questions about source they were never built from. Author types, the recompile is still debounced, the gutter asks about the current buffer against the previous program — and gets a confidently wrong address rather than an error. That applies to byte ranges every bit as much as to line numbers; offsets shift on every inserted character.
Per-file on purpose: one dirty file degrades debugging in that file alone, where a whole-program checksum degrades everything.
None — “cannot tell” — when the artifact carries no DebugInfo,
names no such file, or recorded no hash (compiled without source
text). Deliberately tri-state rather than defaulting to false:
“unknown” and “stale” call for different handling, and collapsing
them would make every hash-less artifact look permanently stale.
A change detector, not a proof — see brink_format::content_hash.
Sourcepub fn container_bytecode(&self, idx: u32) -> &[u8] ⓘ
pub fn container_bytecode(&self, idx: u32) -> &[u8] ⓘ
Get a container’s bytecode by index.
Sourcepub fn container_count(&self) -> u32
pub fn container_count(&self) -> u32
Number of containers. Promoted from #[cfg(feature = "testing")]
to real public API for the structural-transcript re-render road
(RULED 2026-08-30): a transcript saved against an older compile can
carry container indices this program no longer has, and the caller
must be able to bounds-filter them before scope_table_idx would
panic.
Sourcepub fn source_checksum(&self) -> u32
pub fn source_checksum(&self) -> u32
CRC-32 checksum from the source .inkb, used for transcript validation.
Sourcepub fn name_checked(&self, id: NameId) -> Option<&str>
pub fn name_checked(&self, id: NameId) -> Option<&str>
Look up a name by id, returning None if the id is out of range. Used
by function-value rehydration (T1c, #700): a closure loaded from a save
produced against a different compile can carry a NameId that no
longer indexes this program’s table — treated as a mismatch (fault),
never a panic.
Public (T1d, docs/t1d-spec.md §6): the same “index by id, None if
out of range” contract a host needs to resolve a brink_format::Value::Handle’s
kind to its manifest-declared name — e.g. for dev-tooling display or
a host-side capability check. bevy-brink re-exports Program
(decision 2026-07-10), so this is reachable from engine code without a
direct brink-runtime dependency.
Sourcepub fn name_id(&self, name: &str) -> Option<NameId>
pub fn name_id(&self, name: &str) -> Option<NameId>
Reverse of name_checked: look up the
NameId a string interns to in this program’s name table, if any.
Public (T1d-3, docs/t1d-spec.md §4): a host minting a
brink_format::Value::Handle from a binding (e.g. spawn_timer()
returning a fresh Handle<Timer>) needs the compiled program’s
NameId for the manifest-declared kind name ("Timer") to build the
token — the wire form carries only the interned id, never the string.
None means this compile never interned that name (e.g. no
Handle<Timer>-typed signature or annotation anywhere in the source
graph), so no token of that kind can be minted against this program.
Linear scan, same cost class as global_index.
Sourcepub fn find_address(&self, path: &str) -> Option<(u32, usize)>
pub fn find_address(&self, path: &str) -> Option<(u32, usize)>
Resolve a qualified ink path to its (container_idx, byte_offset).
Supports knot names (intro), qualified stitches (knot.stitch), and,
for programs compiled by brink-compiler, author labels
(knot.label, knot.stitch.label). Programs without the compiler’s
address_paths table (legacy .inkb or converter output) resolve
knot/stitch scope paths only. Use this to spawn flows at named entry
points:
use brink_runtime::FlowInstance;
if let Some((idx, _)) = program.find_address("intro_scene") {
let (flow, ctx) = FlowInstance::new_at(program, idx);
}Sourcepub fn definition_id_for_path(&self, path: &str) -> Option<DefinitionId>
pub fn definition_id_for_path(&self, path: &str) -> Option<DefinitionId>
Public wrapper on find_path_target: resolve
a qualified ink path (same grammar as find_address)
to the DefinitionId of its target. Used by hosts that need the id
itself — e.g. bevy-brink’s wake-condition purity check (issue #995),
which looks the id up in the story’s EffectRows table to inspect a
FlowSleep condition’s effect row before admitting it into the wake
contract.
Sourcepub fn global_defaults(&self) -> Vec<Value>
pub fn global_defaults(&self) -> Vec<Value>
Build the initial globals vector from slot defaults.
Sourcepub fn global_index(&self, name: &str) -> Option<u32>
pub fn global_index(&self, name: &str) -> Option<u32>
Find the global variable slot index for a variable name, if declared.
Used by host-facing variable get/set (Story::variable/set_variable).
Sourcepub fn global_slot(&self, id: DefinitionId) -> Option<u32>
pub fn global_slot(&self, id: DefinitionId) -> Option<u32>
Resolve a global cell’s DefinitionId to its slot index — the
numbering ContextAccess::set_global and
Self::global_index use.
Public because effect rows (brink_format::DirectEffects::reads /
writes) name global cells by DefinitionId while the runtime’s
world writes are keyed by slot: a host consuming rows for scheduling
(bevy-brink’s row-directed wake dirtying, issue #1146) needs exactly
this bridge. None for an id this program declares no global for
(a stale row, a VAR removed by a story patch).
Sourcepub fn global_name(&self, idx: u32) -> Option<&str>
pub fn global_name(&self, idx: u32) -> Option<&str>
Resolve a global slot index to its variable name.
Sourcepub fn has_local_defaults(&self) -> bool
pub fn has_local_defaults(&self) -> bool
Whether the compiler marked anything flow-private. When false
(all existing unannotated ink), policy resolution keeps its
all-World fast path.
Public so the bevy host (bevy-brink’s batch driver) can guard
against batching a #@local-annotated story: batch mode routes only
the shared World, never a flow’s private FlowLocal, so a story
carrying compiled flow-private defaults must stay on the serial API
(docs/effects-spec.md §12; bevy-brink #925).
Sourcepub fn global_count(&self) -> u32
pub fn global_count(&self) -> u32
Number of global variable slots.
Sourcepub fn global_var_name(&self, id: DefinitionId) -> Option<&str>
pub fn global_var_name(&self, id: DefinitionId) -> Option<&str>
Variable name for a global’s defining DefinitionId (e.g. a
VariablePointer target, or a T1e projection’s root cell). pub
(not pub(crate)) since brink-web’s program-model/speculation
disassembly needs it to render a projection’s root name at the wasm
boundary, the same way divert_target_path already resolves a
divert’s DefinitionId for that consumer.
Sourcepub fn list_members(&self, list: &ListValue) -> Vec<ListMember>
pub fn list_members(&self, list: &ListValue) -> Vec<ListMember>
Resolve the active members of a list value for host-facing display: each member’s origin list name, unqualified item name, and ordinal. Sorted the same way in-story list stringification orders them (ordinal, then origin name) so the two presentations agree.
Sourcepub fn divert_target_path(&self, id: DefinitionId) -> Option<String>
pub fn divert_target_path(&self, id: DefinitionId) -> Option<String>
The qualified knot/stitch path a DefinitionId names, if it resolves
to a named scope entry (offset-0 in address_by_path) — the
destination of a Value::DivertTarget for host-facing display.
Deterministic on collision: shortest path, then lexicographically
smallest, independent of the map’s iteration order (mirrors
debug::NameResolver’s reverse lookup).