Skip to main content

brink_format/
definition.rs

1use crate::counting::CountingFlags;
2use crate::id::{DefinitionId, NameId};
3use crate::line::LineContent;
4use crate::value::{Value, ValueType};
5
6/// A compiled container (knot, stitch, gather, or anonymous flow block).
7#[derive(Debug, Clone, PartialEq)]
8pub struct ContainerDef {
9    pub id: DefinitionId,
10    /// The lexical scope this container belongs to.
11    /// For scope containers (root, knot, stitch): `scope_id == id`.
12    /// For child containers (gather, choice target, sequence, etc.): `scope_id` is
13    /// the enclosing scope's `DefinitionId`.
14    pub scope_id: DefinitionId,
15    /// Human-readable name for scope-owning containers (root, knot, stitch).
16    /// `None` for child containers.
17    pub name: Option<NameId>,
18    pub bytecode: Vec<u8>,
19    pub counting_flags: CountingFlags,
20    /// Sum of char values from the container's ink path string.
21    /// Used to seed the RNG for shuffle sequences.
22    pub path_hash: i32,
23    /// Number of parameters this container declares (a parameterized knot,
24    /// stitch, or function — e.g. `=== call(action, present) ===` has 2). The
25    /// container's prologue binds them with that many leading `DeclareTemp`s.
26    /// `0` for the vast majority of containers. Lets the runtime arity-check a
27    /// host-directed entry (`choose_path_string_with_args`) or `call_function`.
28    /// The converter reference pipeline leaves this `0` (inklecate's JSON does
29    /// not expose it); only the brink compiler populates the true count.
30    pub param_count: u8,
31    /// Compiled scope default: `true` for a flow-private (`#@local`) knot or
32    /// stitch. Only ever set on scope-owning containers; subtree coverage of
33    /// interior containers is resolved by the runtime at policy resolution
34    /// (`docs/directive-annotations-spec.md`). The converter always emits
35    /// `false` (inklecate has no flow-private concept).
36    pub local: bool,
37}
38
39/// Metadata for a single interpolation slot in a template line.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct SlotInfo {
42    pub index: u8,
43    pub name: String,
44}
45
46/// Source location of a line in the original `.ink` file.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct SourceLocation {
49    pub file: String,
50    pub range_start: u32,
51    pub range_end: u32,
52}
53
54/// One entry in a container's line table.
55#[derive(Debug, Clone, PartialEq)]
56pub struct LineEntry {
57    pub content: LineContent,
58    pub flags: crate::LineFlags,
59    pub source_hash: u64,
60    pub audio_ref: Option<String>,
61    pub slot_info: Vec<SlotInfo>,
62    pub source_location: Option<SourceLocation>,
63}
64
65/// A locale line entry — content + optional audio, no source metadata.
66#[derive(Debug, Clone, PartialEq)]
67pub struct LocaleLineEntry {
68    pub content: LineContent,
69    pub audio_ref: Option<String>,
70}
71
72/// A per-scope locale line table.
73#[derive(Debug, Clone, PartialEq)]
74pub struct LocaleScopeTable {
75    pub scope_id: DefinitionId,
76    pub lines: Vec<LocaleLineEntry>,
77}
78
79/// Complete locale overlay data from a `.inkl` file.
80#[derive(Debug, Clone, PartialEq)]
81pub struct LocaleData {
82    pub locale_tag: String,
83    pub base_checksum: u32,
84    pub line_tables: Vec<LocaleScopeTable>,
85}
86
87/// Per-scope line table, stored separately from [`ContainerDef`] for
88/// locale overlay swapping (`.inkl`).
89///
90/// All containers within a lexical scope (knot, stitch, or root) share one
91/// `ScopeLineTable`. `EmitLine(idx)` indices are scope-relative.
92#[derive(Debug, Clone, PartialEq)]
93pub struct ScopeLineTable {
94    pub scope_id: DefinitionId,
95    pub lines: Vec<LineEntry>,
96}
97
98/// A global variable definition.
99#[derive(Debug, Clone, PartialEq)]
100pub struct GlobalVarDef {
101    pub id: DefinitionId,
102    pub name: NameId,
103    pub value_type: ValueType,
104    pub default_value: Value,
105    pub mutable: bool,
106    /// Compiled scope default: `true` for a flow-private (`#@local`)
107    /// variable, `false` for ordinary shared state. Consumed by the runtime
108    /// as the base layer of `WorldPolicy` resolution
109    /// (`docs/directive-annotations-spec.md`).
110    pub local: bool,
111}
112
113/// A list (enum-like set) definition.
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub struct ListDef {
116    pub id: DefinitionId,
117    pub name: NameId,
118    /// `(item_name, ordinal)` pairs in declaration order.
119    pub items: Vec<(NameId, i32)>,
120}
121
122/// A single list item definition.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct ListItemDef {
125    pub id: DefinitionId,
126    pub origin: DefinitionId,
127    pub ordinal: i32,
128    pub name: NameId,
129}
130
131/// An address pointing to a specific byte offset within a container.
132///
133/// Addresses are used for divert targets, visit tracking, and any definition
134/// that maps to a position within a container. A "primary" address has
135/// `byte_offset == 0` and the same `id` as its `container_id`, functioning
136/// like the old `Container` tag. Intra-container addresses have non-zero
137/// offsets and distinct IDs.
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct AddressDef {
140    pub id: DefinitionId,
141    pub container_id: DefinitionId,
142    pub byte_offset: u32,
143}
144
145/// Maps a qualified author path (e.g. `knot`, `knot.stitch`, `knot.label`,
146/// `knot.stitch.label`) to the [`DefinitionId`] it addresses.
147///
148/// This is the source of truth for path → address lookup
149/// ([`Program::find_address`](../../brink_runtime/struct.Program.html#method.find_address)):
150/// the linker resolves each `target` through its address map. The compiler
151/// emits one entry per scope container (knot/stitch) and per author-labeled
152/// gather/choice. `path` indexes the name table; `target` is the addressed
153/// container/label id.
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub struct AddressPath {
156    pub path: NameId,
157    pub target: DefinitionId,
158}
159
160/// Compute a deterministic hash of line content text.
161///
162/// Used by both the compiler codegen and the converter to populate
163/// [`LineEntry::source_hash`]. The hash detects when source text has
164/// changed across builds, enabling the regeneration workflow in the
165/// internationalization pipeline.
166pub fn content_hash(text: &str) -> u64 {
167    use std::hash::{Hash, Hasher};
168    let mut hasher = std::collections::hash_map::DefaultHasher::new();
169    text.hash(&mut hasher);
170    hasher.finish()
171}
172
173/// An externally-bound function definition.
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub struct ExternalFnDef {
176    pub id: DefinitionId,
177    pub name: NameId,
178    pub arg_count: u8,
179    pub fallback: Option<DefinitionId>,
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn content_hash_deterministic() {
188        let a = content_hash("Hello, world!");
189        let b = content_hash("Hello, world!");
190        assert_eq!(a, b);
191    }
192
193    #[test]
194    fn content_hash_non_zero_for_non_empty() {
195        assert_ne!(content_hash("some text"), 0);
196        assert_ne!(content_hash("x"), 0);
197    }
198
199    #[test]
200    fn content_hash_differs_for_different_input() {
201        assert_ne!(content_hash("hello"), content_hash("world"));
202    }
203}