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}