Skip to main content

lua_types/
error.rs

1//! `LuaError` and its canonical constructors. PORT_STRATEGY §3.7, PORTING.md §6.
2
3use crate::status::LuaStatus;
4use crate::value::LuaValue;
5use std::fmt;
6
7/// Internal control-flow payload used by the standalone CLI to implement
8/// `os.exit` without making Lua protected calls catch it as an ordinary error.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct LuaExit(pub i32);
11
12/// The Lua error type. Carries a `LuaValue` payload because Lua errors can
13/// be any value (typically a string).
14#[derive(Debug, Clone)]
15pub enum LuaError {
16    Runtime(LuaValue),
17    Syntax(LuaValue),
18    Memory,
19    Error,
20    Yield,
21    File,
22    Gc,
23}
24
25impl LuaError {
26    // ── Generic message constructors ─────────────────────────────────────
27    pub fn runtime(args: fmt::Arguments<'_>) -> Self {
28        LuaError::Runtime(LuaValue::Str(crate::gc::GcRef::new(
29            crate::string::LuaString::from_bytes(format!("{}", args).into_bytes()),
30        )))
31    }
32    pub fn syntax(args: fmt::Arguments<'_>) -> Self {
33        LuaError::Syntax(LuaValue::Str(crate::gc::GcRef::new(
34            crate::string::LuaString::from_bytes(format!("{}", args).into_bytes()),
35        )))
36    }
37    pub fn syntax_at(args: fmt::Arguments<'_>, source: &[u8], line: i32) -> Self {
38        LuaError::Syntax(LuaValue::Str(crate::gc::GcRef::new(
39            crate::string::LuaString::from_bytes(
40                format!("{}:{}: {}", String::from_utf8_lossy(source), line, args).into_bytes()
41            ),
42        )))
43    }
44    pub fn syntax_raw(msg: &[u8]) -> Self {
45        LuaError::Syntax(LuaValue::Str(crate::gc::GcRef::new(
46            crate::string::LuaString::from_bytes(msg.to_vec()),
47        )))
48    }
49
50    // ── Standard-shape constructors ──────────────────────────────────────
51    pub fn type_error(v: &LuaValue, op: &str) -> Self {
52        LuaError::runtime(format_args!("attempt to {} a {} value", op, v.type_name()))
53    }
54    pub fn call_error(v: &LuaValue) -> Self {
55        Self::type_error(v, "call")
56    }
57    pub fn concat_error(p1: &LuaValue, p2: &LuaValue) -> Self {
58        let bad = if matches!(p1, LuaValue::Str(_) | LuaValue::Int(_) | LuaValue::Float(_)) { p2 } else { p1 };
59        LuaError::runtime(format_args!("attempt to concatenate a {} value", bad.type_name()))
60    }
61    pub fn arith_error(p1: &LuaValue, p2: &LuaValue, _msg: &str) -> Self {
62        let bad = if matches!(p1, LuaValue::Int(_) | LuaValue::Float(_)) { p2 } else { p1 };
63        LuaError::runtime(format_args!("attempt to perform arithmetic on a {} value", bad.type_name()))
64    }
65    pub fn int_overflow(_p1: &LuaValue, _p2: &LuaValue) -> Self {
66        LuaError::runtime(format_args!("number has no integer representation"))
67    }
68    pub fn order_error(p1: &LuaValue, p2: &LuaValue) -> Self {
69        LuaError::runtime(format_args!("attempt to compare {} with {}", p1.type_name(), p2.type_name()))
70    }
71    pub fn for_error(v: &LuaValue, what: &str) -> Self {
72        LuaError::runtime(format_args!("bad 'for' {} (number expected, got {})", what, v.type_name()))
73    }
74    pub fn arg_error(narg: i32, msg: &str) -> Self {
75        LuaError::runtime(format_args!("bad argument #{} ({})", narg, msg))
76    }
77    pub fn type_arg_error(narg: i32, expected: &str, got: &LuaValue) -> Self {
78        LuaError::runtime(format_args!("bad argument #{} ({} expected, got {})", narg, expected, got.type_name()))
79    }
80
81    // ── Pass-through constructors ────────────────────────────────────────
82    pub fn from_value(v: LuaValue) -> Self {
83        // Special-case: the global "not enough memory" string becomes Memory.
84        // The real impl checks ptr equality against G(L)->memerrmsg.
85        LuaError::Runtime(v)
86    }
87    pub fn with_status(status: LuaStatus) -> Self {
88        match status {
89            LuaStatus::Ok        => LuaError::Error,
90            LuaStatus::Yield     => LuaError::Yield,
91            LuaStatus::ErrRun    => LuaError::Runtime(LuaValue::Nil),
92            LuaStatus::ErrSyntax => LuaError::Syntax(LuaValue::Nil),
93            LuaStatus::ErrMem    => LuaError::Memory,
94            LuaStatus::ErrErr    => LuaError::Error,
95            LuaStatus::ErrFile   => LuaError::File,
96            LuaStatus::ErrGc     => LuaError::Gc,
97        }
98    }
99
100    pub fn to_status(&self) -> LuaStatus {
101        match self {
102            LuaError::Runtime(_) => LuaStatus::ErrRun,
103            LuaError::Syntax(_)  => LuaStatus::ErrSyntax,
104            LuaError::Memory     => LuaStatus::ErrMem,
105            LuaError::Error      => LuaStatus::ErrErr,
106            LuaError::Yield      => LuaStatus::Yield,
107            LuaError::File       => LuaStatus::ErrFile,
108            LuaError::Gc         => LuaStatus::ErrGc,
109        }
110    }
111
112    pub fn into_value(self) -> LuaValue {
113        match self {
114            LuaError::Runtime(v) | LuaError::Syntax(v) => v,
115            _ => LuaValue::Nil,
116        }
117    }
118}
119
120impl fmt::Display for LuaError {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(f, "{:?}", self)
123    }
124}
125impl std::error::Error for LuaError {}
126
127// ──────────────────────────────────────────────────────────────────────────────
128// PORT STATUS
129//   source:        n/a (Rust-native error enum; no C analogue)
130//   target_crate:  lua-types
131//   confidence:    high
132//   todos:         0
133//   port_notes:    0
134//   unsafe_blocks: 0
135//   notes:         LuaError + supporting variants. Pure Rust idiom (Result<T, LuaError>) replacing
136//                  C's setjmp/longjmp error propagation. No direct C-source mapping.
137// ──────────────────────────────────────────────────────────────────────────────