Skip to main content

bock_interp/
error.rs

1//! Runtime error type for the Bock interpreter.
2
3use thiserror::Error;
4
5use crate::Value;
6
7/// An error that can occur during Bock program evaluation.
8///
9/// Three variants (`Return`, `Break`, `Continue`) are control-flow signals
10/// rather than true errors; they are propagated up through `eval_expr` and
11/// caught at the appropriate statement handler (function body, loop body).
12#[derive(Debug, Error, PartialEq)]
13pub enum RuntimeError {
14    // ── Type errors ───────────────────────────────────────────────────────
15    #[error("type error: {0}")]
16    TypeError(String),
17
18    // ── Variable / name errors ────────────────────────────────────────────
19    #[error("undefined variable: {name}")]
20    UndefinedVariable { name: String },
21
22    // ── Arithmetic errors ─────────────────────────────────────────────────
23    #[error("division by zero")]
24    DivisionByZero,
25
26    #[error("integer overflow")]
27    IntOverflow,
28
29    // ── Collection errors ─────────────────────────────────────────────────
30    #[error("index out of bounds: index {index} for length {len}")]
31    IndexOutOfBounds { index: i64, len: usize },
32
33    /// DQ30 (§18.3 / §10.5): an in-place `List` mutator
34    /// (`remove_at`/`insert`/`set`) was called with an out-of-bounds index —
35    /// a violated index contract, surfaced as a runtime abort. The message is
36    /// the normalized cross-target form `List.<op>: index <i> out of bounds
37    /// (len <n>)`, matching the synthesized js/ts/python/go aborts (R11
38    /// interpreter parity).
39    #[error("List.{op}: index {index} out of bounds (len {len})")]
40    ListIndexAbort {
41        op: &'static str,
42        index: i64,
43        len: usize,
44    },
45
46    #[error("field not found: {field} on {type_name}")]
47    FieldNotFound { field: String, type_name: String },
48
49    // ── Call errors ───────────────────────────────────────────────────────
50    #[error("not callable: {value}")]
51    NotCallable { value: String },
52
53    #[error("arity mismatch: expected {expected} args, got {got}")]
54    ArityMismatch { expected: usize, got: usize },
55
56    // ── Error propagation (`?`) ───────────────────────────────────────────
57    /// Raised when `?` is applied to `None` or `Err(e)` (§7.10). Carries the
58    /// **whole propagating value** — `Value::Optional(None)` or
59    /// `Value::Result(Err(e))` — and is caught at the enclosing
60    /// **function-call boundary** (`call_closure` / `run_method_body`), where
61    /// it becomes the function's return value: the caller observes a normal
62    /// `Result`/`Optional`, exactly as the compiled targets early-return it.
63    /// Like `Return`/`Break`/`Continue`, this is a control-flow signal, not a
64    /// true error; it only surfaces as a runtime error if `?` executes outside
65    /// any function boundary (e.g. a top-level REPL expression).
66    #[error("propagated error: {0}")]
67    Propagated(Box<Value>),
68
69    // ── Literal parse errors ──────────────────────────────────────────────
70    #[error("integer literal parse failed: {0}")]
71    IntParseFailed(String),
72
73    #[error("float literal parse failed: {0}")]
74    FloatParseFailed(String),
75
76    // ── Control-flow signals ──────────────────────────────────────────────
77    /// Carries the return value from a `return` expression.
78    #[error("return")]
79    Return(Box<Value>),
80
81    /// Carries an optional `break` value from a `break` expression.
82    #[error("break")]
83    Break(Option<Box<Value>>),
84
85    /// Signals `continue` inside a loop.
86    #[error("continue")]
87    Continue,
88
89    // ── Misc ──────────────────────────────────────────────────────────────
90    #[error("unreachable code reached")]
91    Unreachable,
92
93    #[error("match failed: no arm matched the scrutinee")]
94    MatchFailed,
95
96    #[error("no handler for effect `{effect}`: provide a handler via a `handling` block, module-level `handle`, or project config")]
97    NoEffectHandler { effect: String },
98
99    #[error("not implemented: {0}")]
100    NotImplemented(String),
101
102    // ── Test assertion errors ─────────────────────────────────────────────
103    /// Raised when a test assertion (e.g., `expect(x).to_equal(y)`) fails.
104    #[error("assertion failed: {0}")]
105    AssertionFailed(String),
106}