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