Skip to main content

lua_rs_hlua_shim/
any.rs

1//! The dynamically-typed Lua value enums exposed by `hlua`, reproduced with
2//! identical shape and derives so that consumer code which pattern-matches on
3//! the variants compiles unchanged.
4//!
5//! The free functions in this module marshal these values across the lua-rs
6//! stack using only the public `lua-vm` C-style API, so no `unsafe` is needed.
7
8use std::collections::HashMap;
9
10use lua_types::{LuaError as VmError, LuaType};
11use lua_vm::api;
12use lua_vm::state::LuaState;
13
14/// A byte string that may not be valid UTF-8, mirroring `hlua::AnyLuaString`.
15#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct AnyLuaString(pub Vec<u8>);
17
18/// Any Lua value, mirroring `hlua::AnyLuaValue` field-for-field.
19#[derive(Clone, Debug, PartialEq)]
20pub enum AnyLuaValue {
21    LuaString(String),
22    LuaAnyString(AnyLuaString),
23    LuaNumber(f64),
24    LuaBoolean(bool),
25    LuaArray(Vec<(AnyLuaValue, AnyLuaValue)>),
26    LuaNil,
27    LuaOther,
28}
29
30/// The hashable projection used for Lua table keys, mirroring
31/// `hlua::AnyHashableLuaValue`. Numbers narrow to `i32` exactly as upstream.
32#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub enum AnyHashableLuaValue {
34    LuaString(String),
35    LuaAnyString(AnyLuaString),
36    LuaNumber(i32),
37    LuaBoolean(bool),
38    LuaArray(Vec<(AnyHashableLuaValue, AnyHashableLuaValue)>),
39    LuaNil,
40    LuaOther,
41}
42
43/// Push an `AnyLuaValue` onto the top of the stack.
44pub(crate) fn push_any(state: &mut LuaState, value: &AnyLuaValue) -> Result<(), VmError> {
45    match value {
46        AnyLuaValue::LuaNil | AnyLuaValue::LuaOther => api::push_nil(state),
47        AnyLuaValue::LuaBoolean(b) => api::push_boolean(state, *b),
48        AnyLuaValue::LuaNumber(n) => api::push_number(state, *n),
49        AnyLuaValue::LuaString(s) => {
50            api::push_lstring(state, s.as_bytes())?;
51        }
52        AnyLuaValue::LuaAnyString(s) => {
53            api::push_lstring(state, &s.0)?;
54        }
55        AnyLuaValue::LuaArray(pairs) => {
56            state.create_table(0, pairs.len() as i32)?;
57            let table = api::get_top(state);
58            for (k, v) in pairs {
59                push_any(state, k)?;
60                push_any(state, v)?;
61                api::raw_set(state, table)?;
62            }
63        }
64    }
65    Ok(())
66}
67
68/// Push an `AnyHashableLuaValue` onto the top of the stack.
69pub(crate) fn push_hashable(
70    state: &mut LuaState,
71    value: &AnyHashableLuaValue,
72) -> Result<(), VmError> {
73    match value {
74        AnyHashableLuaValue::LuaNil | AnyHashableLuaValue::LuaOther => api::push_nil(state),
75        AnyHashableLuaValue::LuaBoolean(b) => api::push_boolean(state, *b),
76        AnyHashableLuaValue::LuaNumber(n) => api::push_integer(state, *n as i64),
77        AnyHashableLuaValue::LuaString(s) => {
78            api::push_lstring(state, s.as_bytes())?;
79        }
80        AnyHashableLuaValue::LuaAnyString(s) => {
81            api::push_lstring(state, &s.0)?;
82        }
83        AnyHashableLuaValue::LuaArray(pairs) => {
84            state.create_table(0, pairs.len() as i32)?;
85            let table = api::get_top(state);
86            for (k, v) in pairs {
87                push_hashable(state, k)?;
88                push_hashable(state, v)?;
89                api::raw_set(state, table)?;
90            }
91        }
92    }
93    Ok(())
94}
95
96/// Read the value at stack index `idx` as an `AnyLuaValue`.
97pub(crate) fn read_any(state: &mut LuaState, idx: i32) -> AnyLuaValue {
98    match api::lua_type_at(state, idx) {
99        LuaType::None | LuaType::Nil => AnyLuaValue::LuaNil,
100        LuaType::Boolean => AnyLuaValue::LuaBoolean(api::to_boolean(state, idx)),
101        LuaType::Number => {
102            let n = api::to_number_x(state, idx).expect("Number value must convert to f64");
103            AnyLuaValue::LuaNumber(n)
104        }
105        LuaType::String => read_string_value(state, idx),
106        LuaType::Table => AnyLuaValue::LuaArray(read_table_pairs(state, idx)),
107        _ => AnyLuaValue::LuaOther,
108    }
109}
110
111/// Read the value at stack index `idx` as an `AnyHashableLuaValue`.
112pub(crate) fn read_hashable(state: &mut LuaState, idx: i32) -> AnyHashableLuaValue {
113    match read_any(state, idx) {
114        AnyLuaValue::LuaNil => AnyHashableLuaValue::LuaNil,
115        AnyLuaValue::LuaBoolean(b) => AnyHashableLuaValue::LuaBoolean(b),
116        AnyLuaValue::LuaNumber(n) => AnyHashableLuaValue::LuaNumber(n as i32),
117        AnyLuaValue::LuaString(s) => AnyHashableLuaValue::LuaString(s),
118        AnyLuaValue::LuaAnyString(s) => AnyHashableLuaValue::LuaAnyString(s),
119        AnyLuaValue::LuaArray(_) | AnyLuaValue::LuaOther => AnyHashableLuaValue::LuaOther,
120    }
121}
122
123/// Read a string-typed stack slot, preserving raw bytes when not valid UTF-8.
124fn read_string_value(state: &mut LuaState, idx: i32) -> AnyLuaValue {
125    let bytes = string_bytes_at(state, idx).expect("String value must have bytes");
126    match String::from_utf8(bytes) {
127        Ok(s) => AnyLuaValue::LuaString(s),
128        Err(e) => AnyLuaValue::LuaAnyString(AnyLuaString(e.into_bytes())),
129    }
130}
131
132/// Borrow the bytes of a string-typed stack slot without numeric coercion.
133pub(crate) fn string_bytes_at(state: &mut LuaState, idx: i32) -> Option<Vec<u8>> {
134    let s = api::to_lua_string(state, idx).ok()??;
135    Some(s.as_bytes().to_vec())
136}
137
138/// Iterate a table at `idx` into key/value pairs, restoring the stack.
139fn read_table_pairs(state: &mut LuaState, idx: i32) -> Vec<(AnyLuaValue, AnyLuaValue)> {
140    let table = api::abs_index(state, idx);
141    let mut pairs = Vec::new();
142    api::push_nil(state);
143    while api::next(state, table).unwrap_or(false) {
144        let key = read_any(state, -2);
145        let value = read_any(state, -1);
146        pairs.push((key, value));
147        api::set_top(state, -2).ok();
148    }
149    pairs
150}
151
152/// Read a table at `idx` as an ordered list of its 1..n integer-keyed values.
153pub(crate) fn read_sequence(state: &mut LuaState, idx: i32) -> Vec<AnyLuaValue> {
154    let table = api::abs_index(state, idx);
155    let mut out = Vec::new();
156    let mut i = 1i64;
157    loop {
158        let t = match api::get_i(state, table, i) {
159            Ok(t) => t,
160            Err(_) => break,
161        };
162        if t == LuaType::Nil {
163            api::set_top(state, -2).ok();
164            break;
165        }
166        out.push(read_any(state, -1));
167        api::set_top(state, -2).ok();
168        i += 1;
169    }
170    out
171}
172
173/// Read a table at `idx` into a `HashMap` keyed by hashable Lua values.
174pub(crate) fn read_map(
175    state: &mut LuaState,
176    idx: i32,
177) -> HashMap<AnyHashableLuaValue, AnyLuaValue> {
178    let table = api::abs_index(state, idx);
179    let mut map = HashMap::new();
180    api::push_nil(state);
181    while api::next(state, table).unwrap_or(false) {
182        let key = read_hashable(state, -2);
183        let value = read_any(state, -1);
184        map.insert(key, value);
185        api::set_top(state, -2).ok();
186    }
187    map
188}