Skip to main content

lua_rs_hlua_shim/
lib.rs

1//! A drop-in subset of the `hlua` 0.4 embedding API, backed by the pure-Rust
2//! lua-rs VM instead of the C library.
3//!
4//! The goal is source compatibility: a consumer that does
5//! `use lua_rs_hlua_shim as hlua;` should compile and run against lua-rs without
6//! touching its own code. The implemented surface is the part exercised by
7//! `authoscope` — `Lua`, `functionN`, `AnyLuaValue` and friends, `LuaFunction`,
8//! and `StringInLua`. It is intentionally not the whole hlua API.
9//!
10//! Sandboxing note: lua-rs's runtime opens the full standard library, whereas
11//! hlua consumers typically open only `string`. This is acceptable for a
12//! compatibility spike but widens the sandbox; restricting the opened libraries
13//! is tracked as future work.
14
15mod any;
16mod func;
17
18use std::cell::Cell;
19use std::marker::PhantomData;
20
21use lua_stdlib::auxlib::load_buffer;
22use lua_stdlib::init::open_libs;
23use lua_types::closure::LuaLClosure;
24use lua_types::gc::GcRef;
25use lua_types::upval::UpVal;
26use lua_types::value::LuaValue;
27use lua_vm::api;
28use lua_vm::state::{new_state, LuaState};
29
30pub use any::{AnyHashableLuaValue, AnyLuaString, AnyLuaValue};
31pub use func::{
32    function0, function1, function2, function3, function4, function5, function6, FnHandle,
33    LuaFunction, StringInLua,
34};
35
36use func::{registry_remove, Adapter, FromLuaGlobal, SetValue};
37
38/// Error returned by `Lua::execute` and `LuaFunction::call_with_args`.
39///
40/// Carries only an owned message so the type is `Send + Sync + 'static` and
41/// flows through `anyhow`'s `?` the way `hlua::LuaError` does for consumers.
42#[derive(Debug, Clone)]
43pub struct LuaError {
44    message: String,
45}
46
47impl LuaError {
48    pub(crate) fn from_vm(err: lua_types::LuaError) -> Self {
49        LuaError {
50            message: format!("{err}"),
51        }
52    }
53}
54
55impl std::fmt::Display for LuaError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.write_str(&self.message)
58    }
59}
60
61impl std::error::Error for LuaError {}
62
63/// A Lua state with the standard libraries installed.
64///
65/// The `'lua` parameter mirrors hlua's signature so consumer code that writes
66/// `Lua<'a>` keeps compiling; the shim itself owns its state outright.
67pub struct Lua<'lua> {
68    state: LuaState,
69    owned_closures: Vec<usize>,
70    _marker: PhantomData<&'lua ()>,
71}
72
73impl Default for Lua<'_> {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79impl<'lua> Lua<'lua> {
80    /// Create a new Lua state with the standard libraries installed. Panics
81    /// only on allocation failure, matching the effective behaviour of
82    /// `hlua::Lua::new()`.
83    pub fn new() -> Lua<'lua> {
84        let mut state = new_state().expect("lua-rs state allocation failed");
85        state.global_mut().parser_hook = Some(parser_hook);
86        open_libs(&mut state).expect("opening lua-rs standard libraries failed");
87        Lua {
88            state,
89            owned_closures: Vec::new(),
90            _marker: PhantomData,
91        }
92    }
93
94    /// Open the string library. lua-rs already opens the full standard library
95    /// at construction, so this is a no-op kept for API compatibility.
96    pub fn open_string(&mut self) {}
97
98    /// Load and run a chunk of Lua source.
99    pub fn execute<T: FromExec>(&mut self, code: &str) -> Result<T, LuaError> {
100        let status = load_buffer(&mut self.state, code.as_bytes(), b"=(load)")
101            .map_err(LuaError::from_vm)?;
102        if status != 0 {
103            let err = self.state.pop();
104            return Err(LuaError::from_vm(lua_types::LuaError::from_value(err)));
105        }
106        api::pcall_k(&mut self.state, 0, 0, 0, 0, None).map_err(LuaError::from_vm)?;
107        T::from_exec(self)
108    }
109
110    /// Set a global. Accepts the `functionN` wrappers from this crate.
111    pub fn set<V: SetValue>(&mut self, name: &str, value: V) {
112        value.set_into(self, name);
113    }
114
115    /// Read a global by name. The value type is chosen by the caller, e.g.
116    /// `lua.get::<StringInLua<_>, _>("descr")`.
117    pub fn get<'l, V>(&'l mut self, name: &str) -> Option<V>
118    where
119        V: FromLuaGlobal<'l>,
120    {
121        V::from_lua_global(self, name)
122    }
123
124    pub(crate) fn state_mut(&mut self) -> &mut LuaState {
125        &mut self.state
126    }
127
128    /// Register a hosted closure as a global named `name`. The adapter index is
129    /// recorded so it is freed when this `Lua` is dropped.
130    pub(crate) fn install_closure(&mut self, name: &str, adapter: Adapter) {
131        let index = func::registry_insert(adapter);
132        self.owned_closures.push(index);
133        let state = &mut self.state;
134        api::push_integer(state, index as i64);
135        api::push_cclosure(state, func::trampoline, 1).expect("push_cclosure failed");
136        api::set_global(state, name.as_bytes()).expect("set_global failed");
137    }
138}
139
140impl Drop for Lua<'_> {
141    fn drop(&mut self) {
142        for &index in &self.owned_closures {
143            registry_remove(index);
144        }
145    }
146}
147
148/// The result type produced by [`Lua::execute`]. Only `()` is supported, which
149/// is all `authoscope` requires.
150pub trait FromExec: Sized {
151    fn from_exec(lua: &mut Lua<'_>) -> Result<Self, LuaError>;
152}
153
154impl FromExec for () {
155    fn from_exec(_lua: &mut Lua<'_>) -> Result<Self, LuaError> {
156        Ok(())
157    }
158}
159
160/// Compile a chunk to a Lua closure. Installed on the state so `load_buffer`
161/// has a front end; this is the same bootstrap `lua-rs-runtime` performs,
162/// inlined here so the shim depends only on published crates.
163fn parser_hook(
164    state: &mut LuaState,
165    source: &[u8],
166    name: &[u8],
167    firstchar: i32,
168) -> Result<GcRef<LuaLClosure>, lua_types::LuaError> {
169    let proto = lua_parse::parse(state, lua_parse::DynData::default(), source, name, firstchar)?;
170    let nupvals = proto.upvalues.len();
171    let mut upvals = Vec::with_capacity(nupvals);
172    for _ in 0..nupvals {
173        upvals.push(Cell::new(GcRef::new(UpVal::closed(LuaValue::Nil))));
174    }
175    Ok(GcRef::new(LuaLClosure {
176        proto: GcRef::new(*proto),
177        upvals,
178    }))
179}