Skip to main content

luna_core/vm/
error.rs

1//! Runtime errors: a Lua error is an arbitrary Lua value (usually a
2//! string). Propagated as `Result<_, LuaError>` through the interpreter;
3//! `pcall` catches it at the native boundary. Traceback capture lands with
4//! the debug interfaces (P05).
5
6use crate::runtime::Value;
7use crate::runtime::table::TableError;
8use std::fmt;
9
10/// A Lua error: an arbitrary Lua value (almost always a string) plus
11/// classification metadata recorded on the [`Vm`](crate::vm::Vm) side.
12///
13/// `LuaError` itself is `Copy` (16 bytes) — embedders matching on the
14/// error value do so via `e.0`. Richer error context (kind, source,
15/// traceback) lives on the Vm and is accessed through:
16///
17/// - [`Vm::error_text`](crate::vm::Vm::error_text) — formatted message
18/// - [`Vm::error_kind`](crate::vm::Vm::error_kind) — [`LuaErrorKind`] classification
19/// - [`Vm::error_source`](crate::vm::Vm::error_source) — `(source_name, line)` of the most recent error
20/// - [`Vm::take_error_traceback`](crate::vm::Vm::take_error_traceback) — formatted traceback
21///
22/// ```
23/// use luna_core::vm::{Vm, LuaError, LuaErrorKind};
24/// use luna_core::version::LuaVersion;
25///
26/// let mut vm = Vm::sandbox(LuaVersion::Lua55).open_base().build();
27/// let err: LuaError = vm.eval("syntax error here").unwrap_err();
28/// // The error value is a string (PUC convention):
29/// assert!(err.0.try_as_str().is_some());
30/// // Vm-side metadata records the classification:
31/// assert_eq!(vm.error_kind(), LuaErrorKind::Syntax);
32/// ```
33#[derive(Clone, Copy, Debug)]
34pub struct LuaError(pub Value);
35
36impl LuaError {
37    /// Construct a `LuaError` carrying `Value::Nil` (the cheap default
38    /// for trait conversions that don't have a `&mut Vm` to intern a
39    /// rich message with). Callers with a Vm typically use
40    /// `LuaError::message`.
41    pub fn nil() -> LuaError {
42        LuaError(Value::Nil)
43    }
44
45    /// Construct a `LuaError` from a `Value` directly.
46    pub fn new(v: Value) -> LuaError {
47        LuaError(v)
48    }
49}
50
51impl fmt::Display for LuaError {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        match self.0 {
54            Value::Str(s) => match std::str::from_utf8(s.as_bytes()) {
55                Ok(t) => f.write_str(t),
56                Err(_) => write!(f, "{}", String::from_utf8_lossy(s.as_bytes())),
57            },
58            Value::Nil => f.write_str("(nil error)"),
59            other => write!(f, "(error object is a {} value)", other.type_name()),
60        }
61    }
62}
63
64impl std::error::Error for LuaError {
65    /// Lua errors do not carry a chained Rust-side `source` — the
66    /// causal chain (`__index` failure → metamethod error → top-level
67    /// failure) is captured in the `traceback` accessible via
68    /// `Vm::take_error_traceback`.
69    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
70        None
71    }
72}
73
74impl From<TableError> for LuaError {
75    /// Lift a `TableError` into a `LuaError` carrying `Value::Nil`.
76    /// Heap-free so it composes with `?` in functions that don't have
77    /// a `&mut Vm`. The classification is later refined when the error
78    /// crosses a Vm boundary (caller can set
79    /// [`Vm::set_error_kind`](crate::vm::Vm::set_error_kind) if needed).
80    fn from(_: TableError) -> LuaError {
81        LuaError(Value::Nil)
82    }
83}
84
85/// Classification of the most recent error raised on a Vm.
86///
87/// Embedders switch on this to decide whether to retry (`InstrBudget`,
88/// `MemoryCap`), report (`Runtime`, `Syntax`), or treat as fatal
89/// (`Native`, `OutOfMemory`). Default is [`LuaErrorKind::Runtime`].
90#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
91pub enum LuaErrorKind {
92    /// Generic Lua runtime error (`error(...)`, type errors, missing
93    /// global, etc.). The default classification.
94    #[default]
95    Runtime,
96    /// Source did not parse (lexer or parser rejected the input).
97    Syntax,
98    /// `vm.set_instr_budget(Some(N))` budget exhausted mid-call.
99    InstrBudget,
100    /// `vm.set_memory_cap(Some(N))` cap exceeded during allocation.
101    MemoryCap,
102    /// A `NativeFn` callback returned `Err(LuaError)` (host-side error).
103    Native,
104    /// Allocation failed (typically only on cap exhaustion in luna).
105    OutOfMemory,
106    /// `error` raised at a type boundary (e.g. arithmetic on a table).
107    Type,
108}
109
110impl fmt::Display for LuaErrorKind {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let s = match self {
113            LuaErrorKind::Runtime => "runtime",
114            LuaErrorKind::Syntax => "syntax",
115            LuaErrorKind::InstrBudget => "instr-budget",
116            LuaErrorKind::MemoryCap => "memory-cap",
117            LuaErrorKind::Native => "native",
118            LuaErrorKind::OutOfMemory => "out-of-memory",
119            LuaErrorKind::Type => "type",
120        };
121        f.write_str(s)
122    }
123}