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}