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}
32
33/// Metadata for a single interpolation slot in a template line.
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct SlotInfo {
36    pub index: u8,
37    pub name: String,
38}
39
40/// Source location of a line in the original `.ink` file.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct SourceLocation {
43    pub file: String,
44    pub range_start: u32,
45    pub range_end: u32,
46}
47
48/// One entry in a container's line table.
49#[derive(Debug, Clone, PartialEq)]
50pub struct LineEntry {
51    pub content: LineContent,
52    pub flags: crate::LineFlags,
53    pub source_hash: u64,
54    pub audio_ref: Option<String>,
55    pub slot_info: Vec<SlotInfo>,
56    pub source_location: Option<SourceLocation>,
57}
58
59/// A locale line entry — content + optional audio, no source metadata.
60#[derive(Debug, Clone, PartialEq)]
61pub struct LocaleLineEntry {
62    pub content: LineContent,
63    pub audio_ref: Option<String>,
64}
65
66/// A per-scope locale line table.
67#[derive(Debug, Clone, PartialEq)]
68pub struct LocaleScopeTable {
69    pub scope_id: DefinitionId,
70    pub lines: Vec<LocaleLineEntry>,
71}
72
73/// Complete locale overlay data from a `.inkl` file.
74#[derive(Debug, Clone, PartialEq)]
75pub struct LocaleData {
76    pub locale_tag: String,
77    pub base_checksum: u32,
78    pub line_tables: Vec<LocaleScopeTable>,
79}
80
81/// Per-scope line table, stored separately from [`ContainerDef`] for
82/// locale overlay swapping (`.inkl`).
83///
84/// All containers within a lexical scope (knot, stitch, or root) share one
85/// `ScopeLineTable`. `EmitLine(idx)` indices are scope-relative.
86#[derive(Debug, Clone, PartialEq)]
87pub struct ScopeLineTable {
88    pub scope_id: DefinitionId,
89    pub lines: Vec<LineEntry>,
90}
91
92/// A global variable definition.
93#[derive(Debug, Clone, PartialEq)]
94pub struct GlobalVarDef {
95    pub id: DefinitionId,
96    pub name: NameId,
97    pub value_type: ValueType,
98    pub default_value: Value,
99    pub mutable: bool,
100}
101
102/// A list (enum-like set) definition.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct ListDef {
105    pub id: DefinitionId,
106    pub name: NameId,
107    /// `(item_name, ordinal)` pairs in declaration order.
108    pub items: Vec<(NameId, i32)>,
109}
110
111/// A single list item definition.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct ListItemDef {
114    pub id: DefinitionId,
115    pub origin: DefinitionId,
116    pub ordinal: i32,
117    pub name: NameId,
118}
119
120/// An address pointing to a specific byte offset within a container.
121///
122/// Addresses are used for divert targets, visit tracking, and any definition
123/// that maps to a position within a container. A "primary" address has
124/// `byte_offset == 0` and the same `id` as its `container_id`, functioning
125/// like the old `Container` tag. Intra-container addresses have non-zero
126/// offsets and distinct IDs.
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct AddressDef {
129    pub id: DefinitionId,
130    pub container_id: DefinitionId,
131    pub byte_offset: u32,
132}
133
134/// Maps a qualified author path (e.g. `knot`, `knot.stitch`, `knot.label`,
135/// `knot.stitch.label`) to the [`DefinitionId`] it addresses.
136///
137/// This is the source of truth for path → address lookup
138/// ([`Program::find_address`](../../brink_runtime/struct.Program.html#method.find_address)):
139/// the linker resolves each `target` through its address map. The compiler
140/// emits one entry per scope container (knot/stitch) and per author-labeled
141/// gather/choice. `path` indexes the name table; `target` is the addressed
142/// container/label id.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub struct AddressPath {
145    pub path: NameId,
146    pub target: DefinitionId,
147}
148
149/// Compute a deterministic hash of line content text.
150///
151/// Used by both the compiler codegen and the converter to populate
152/// [`LineEntry::source_hash`]. The hash detects when source text has
153/// changed across builds, enabling the regeneration workflow in the
154/// internationalization pipeline.
155pub fn content_hash(text: &str) -> u64 {
156    use std::hash::{Hash, Hasher};
157    let mut hasher = std::collections::hash_map::DefaultHasher::new();
158    text.hash(&mut hasher);
159    hasher.finish()
160}
161
162/// An externally-bound function definition.
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub struct ExternalFnDef {
165    pub id: DefinitionId,
166    pub name: NameId,
167    pub arg_count: u8,
168    pub fallback: Option<DefinitionId>,
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174
175    #[test]
176    fn content_hash_deterministic() {
177        let a = content_hash("Hello, world!");
178        let b = content_hash("Hello, world!");
179        assert_eq!(a, b);
180    }
181
182    #[test]
183    fn content_hash_non_zero_for_non_empty() {
184        assert_ne!(content_hash("some text"), 0);
185        assert_ne!(content_hash("x"), 0);
186    }
187
188    #[test]
189    fn content_hash_differs_for_different_input() {
190        assert_ne!(content_hash("hello"), content_hash("world"));
191    }
192}