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(state: &mut LuaState, value: &AnyHashableLuaValue) -> Result<(), VmError> {
70    match value {
71        AnyHashableLuaValue::LuaNil | AnyHashableLuaValue::LuaOther => api::push_nil(state),
72        AnyHashableLuaValue::LuaBoolean(b) => api::push_boolean(state, *b),
73        AnyHashableLuaValue::LuaNumber(n) => api::push_integer(state, *n as i64),
74        AnyHashableLuaValue::LuaString(s) => {
75            api::push_lstring(state, s.as_bytes())?;
76        }
77        AnyHashableLuaValue::LuaAnyString(s) => {
78            api::push_lstring(state, &s.0)?;
79        }
80        AnyHashableLuaValue::LuaArray(pairs) => {
81            state.create_table(0, pairs.len() as i32)?;
82            let table = api::get_top(state);
83            for (k, v) in pairs {
84                push_hashable(state, k)?;
85                push_hashable(state, v)?;
86                api::raw_set(state, table)?;
87            }
88        }
89    }
90    Ok(())
91}
92
93/// Read the value at stack index `idx` as an `AnyLuaValue`.
94pub(crate) fn read_any(state: &mut LuaState, idx: i32) -> AnyLuaValue {
95    match api::lua_type_at(state, idx) {
96        LuaType::None | LuaType::Nil => AnyLuaValue::LuaNil,
97        LuaType::Boolean => AnyLuaValue::LuaBoolean(api::to_boolean(state, idx)),
98        LuaType::Number => {
99            let n = api::to_number_x(state, idx).expect("Number value must convert to f64");
100            AnyLuaValue::LuaNumber(n)
101        }
102        LuaType::String => read_string_value(state, idx),
103        LuaType::Table => AnyLuaValue::LuaArray(read_table_pairs(state, idx)),
104        _ => AnyLuaValue::LuaOther,
105    }
106}
107
108/// Read the value at stack index `idx` as an `AnyHashableLuaValue`.
109pub(crate) fn read_hashable(state: &mut LuaState, idx: i32) -> AnyHashableLuaValue {
110    match read_any(state, idx) {
111        AnyLuaValue::LuaNil => AnyHashableLuaValue::LuaNil,
112        AnyLuaValue::LuaBoolean(b) => AnyHashableLuaValue::LuaBoolean(b),
113        AnyLuaValue::LuaNumber(n) => AnyHashableLuaValue::LuaNumber(n as i32),
114        AnyLuaValue::LuaString(s) => AnyHashableLuaValue::LuaString(s),
115        AnyLuaValue::LuaAnyString(s) => AnyHashableLuaValue::LuaAnyString(s),
116        AnyLuaValue::LuaArray(_) | AnyLuaValue::LuaOther => AnyHashableLuaValue::LuaOther,
117    }
118}
119
120/// Read a string-typed stack slot, preserving raw bytes when not valid UTF-8.
121fn read_string_value(state: &mut LuaState, idx: i32) -> AnyLuaValue {
122    let bytes = string_bytes_at(state, idx).expect("String value must have bytes");
123    match String::from_utf8(bytes) {
124        Ok(s) => AnyLuaValue::LuaString(s),
125        Err(e) => AnyLuaValue::LuaAnyString(AnyLuaString(e.into_bytes())),
126    }
127}
128
129/// Borrow the bytes of a string-typed stack slot without numeric coercion.
130pub(crate) fn string_bytes_at(state: &mut LuaState, idx: i32) -> Option<Vec<u8>> {
131    let s = api::to_lua_string(state, idx).ok()??;
132    Some(s.as_bytes().to_vec())
133}
134
135/// Iterate a table at `idx` into key/value pairs, restoring the stack.
136fn read_table_pairs(state: &mut LuaState, idx: i32) -> Vec<(AnyLuaValue, AnyLuaValue)> {
137    let table = api::abs_index(state, idx);
138    let mut pairs = Vec::new();
139    api::push_nil(state);
140    while api::next(state, table).unwrap_or(false) {
141        let key = read_any(state, -2);
142        let value = read_any(state, -1);
143        pairs.push((key, value));
144        api::set_top(state, -2).ok();
145    }
146    pairs
147}
148
149/// Read a table at `idx` as an ordered list of its 1..n integer-keyed values.
150pub(crate) fn read_sequence(state: &mut LuaState, idx: i32) -> Vec<AnyLuaValue> {
151    let table = api::abs_index(state, idx);
152    let mut out = Vec::new();
153    let mut i = 1i64;
154    loop {
155        let t = match api::get_i(state, table, i) {
156            Ok(t) => t,
157            Err(_) => break,
158        };
159        if t == LuaType::Nil {
160            api::set_top(state, -2).ok();
161            break;
162        }
163        out.push(read_any(state, -1));
164        api::set_top(state, -2).ok();
165        i += 1;
166    }
167    out
168}
169
170/// Read a table at `idx` into a `HashMap` keyed by hashable Lua values.
171pub(crate) fn read_map(
172    state: &mut LuaState,
173    idx: i32,
174) -> HashMap<AnyHashableLuaValue, AnyLuaValue> {
175    let table = api::abs_index(state, idx);
176    let mut map = HashMap::new();
177    api::push_nil(state);
178    while api::next(state, table).unwrap_or(false) {
179        let key = read_hashable(state, -2);
180        let value = read_any(state, -1);
181        map.insert(key, value);
182        api::set_top(state, -2).ok();
183    }
184    map
185}