Skip to main content

brink_runtime/
error.rs

1//! Runtime error types.
2
3use brink_format::{DecodeError, DefinitionId};
4
5/// Errors that can occur during story linking or execution.
6#[derive(Debug, thiserror::Error)]
7pub enum RuntimeError {
8    #[error("bytecode decode error: {0}")]
9    Decode(#[from] DecodeError),
10
11    #[error("unresolved definition: {0}")]
12    UnresolvedDefinition(DefinitionId),
13
14    #[error("no root container found")]
15    NoRootContainer,
16
17    #[error("value stack underflow")]
18    StackUnderflow,
19
20    #[error("call stack underflow")]
21    CallStackUnderflow,
22
23    #[error("container stack underflow")]
24    ContainerStackUnderflow,
25
26    #[error("invalid choice index: {index} (available: {available})")]
27    InvalidChoiceIndex { index: usize, available: usize },
28
29    #[error("not waiting for choice")]
30    NotWaitingForChoice,
31
32    #[error("story has ended")]
33    StoryEnded,
34
35    #[error("unresolved global: {0}")]
36    UnresolvedGlobal(DefinitionId),
37
38    #[error("type error: {0}")]
39    TypeError(String),
40
41    #[error("division by zero")]
42    DivisionByZero,
43
44    #[error("unimplemented opcode: {0}")]
45    Unimplemented(String),
46
47    #[error("unresolved external function call: {0}")]
48    UnresolvedExternalCall(DefinitionId),
49
50    #[error("output capture underflow (no checkpoint)")]
51    CaptureUnderflow,
52
53    #[error("unknown flow: {0}")]
54    UnknownFlow(String),
55
56    #[error("flow already exists: {0}")]
57    FlowAlreadyExists(String),
58
59    #[error("ran out of content. Do you need a '-> DONE' or '-> END'?")]
60    RanOutOfContent,
61
62    #[error("step limit exceeded ({0} steps)")]
63    StepLimitExceeded(u64),
64
65    #[error("line limit exceeded ({0} lines in a single turn)")]
66    LineLimitExceeded(usize),
67
68    #[error("locale checksum mismatch: expected {expected:#010x}, got {actual:#010x}")]
69    LocaleChecksumMismatch { expected: u32, actual: u32 },
70
71    #[error("locale scope not in base program: {0}")]
72    LocaleScopeNotInBase(DefinitionId),
73
74    #[error("locale missing scope required by strict mode: {0}")]
75    LocaleScopeMissing(DefinitionId),
76
77    #[error(
78        "function evaluation yielded (a function called from the engine cannot present choices or end the story)"
79    )]
80    FunctionYielded,
81
82    #[error("no function evaluation in progress")]
83    NotEvaluatingFunction,
84
85    #[error("a function evaluation is already in progress on this flow")]
86    AlreadyEvaluatingFunction,
87
88    /// `call_function` was given a name that resolves to no function/knot.
89    #[error("function not found: {0}")]
90    FunctionNotFound(String),
91
92    /// A function evaluated via the synchronous `call_function` path called an
93    /// external whose handler deferred (`Pending`) — it can't be resolved in a
94    /// one-shot synchronous call.
95    #[error("external '{0}' is async; cannot resolve during a synchronous call_function")]
96    AsyncExternalInCall(String),
97
98    /// `choose_path_string` was given a path that resolves to no knot,
99    /// stitch, or label.
100    #[error("no knot or stitch found at path '{0}'")]
101    UnknownPath(String),
102
103    /// `choose_path_string` was called while the flow is parked on an
104    /// unresolved external call. A pending host call cannot be silently
105    /// abandoned — resolve it (or reset the story) before jumping.
106    #[error(
107        "cannot jump to '{path}': the flow is parked on unresolved external '{external}' — \
108         resolve it before jumping"
109    )]
110    JumpWhileAwaitingExternal { path: String, external: String },
111}