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}