Spaces:
Sleeping
Sleeping
| //! Typed value interchange for program I/O. | |
| //! | |
| //! Inputs and outputs of generated Lua functions are restricted to a small, | |
| //! JSON-serializable, deterministic value space. Anything outside it (functions, | |
| //! userdata, non-sequence tables, NaN/Inf) is treated as non-observable and | |
| //! causes the sample to be filtered out upstream. | |
| use mlua::{Lua, Value as LuaValue}; | |
| use serde::{Deserialize, Serialize}; | |
| pub enum LValue { | |
| Nil, | |
| Bool(bool), | |
| Int(i64), | |
| Num(f64), | |
| Str(String), | |
| List(Vec<LValue>), | |
| } | |
| impl LValue { | |
| /// Convert a Rust-side value into a Lua value inside `lua`. | |
| pub fn to_lua(&self, lua: &Lua) -> mlua::Result<LuaValue> { | |
| Ok(match self { | |
| LValue::Nil => LuaValue::Nil, | |
| LValue::Bool(b) => LuaValue::Boolean(*b), | |
| LValue::Int(i) => LuaValue::Integer(*i), | |
| LValue::Num(n) => LuaValue::Number(*n), | |
| LValue::Str(s) => LuaValue::String(lua.create_string(s)?), | |
| LValue::List(items) => { | |
| let t = lua.create_table()?; | |
| for (idx, item) in items.iter().enumerate() { | |
| t.set(idx as i64 + 1, item.to_lua(lua)?)?; | |
| } | |
| LuaValue::Table(t) | |
| } | |
| }) | |
| } | |
| /// Best-effort conversion from a Lua value. Returns None when the value is | |
| /// outside the observable space (function/userdata/thread, non-sequence | |
| /// table, or a non-finite number). | |
| pub fn from_lua(v: &LuaValue) -> Option<LValue> { | |
| match v { | |
| LuaValue::Nil => Some(LValue::Nil), | |
| LuaValue::Boolean(b) => Some(LValue::Bool(*b)), | |
| LuaValue::Integer(i) => Some(LValue::Int(*i)), | |
| LuaValue::Number(n) => { | |
| if n.is_finite() { | |
| Some(LValue::Num(*n)) | |
| } else { | |
| None | |
| } | |
| } | |
| LuaValue::String(s) => { | |
| let st = s.to_str().ok()?; | |
| // Cap length so a runaway string.rep is treated as non-observable | |
| // rather than ballooning the dataset. | |
| if st.len() > 4096 { | |
| return None; | |
| } | |
| Some(LValue::Str(st.to_owned())) | |
| } | |
| LuaValue::Table(t) => { | |
| // Only accept array-like (sequence) tables: keys 1..=n, no holes, | |
| // no extra keys. Bounded length to avoid pathological outputs. | |
| let len = t.raw_len(); | |
| if len > 256 { | |
| return None; | |
| } | |
| let mut items = Vec::with_capacity(len); | |
| for i in 1..=len { | |
| let elem: LuaValue = t.raw_get(i as i64).ok()?; | |
| items.push(LValue::from_lua(&elem)?); | |
| } | |
| // Reject tables with non-sequence keys (e.g. string keys, holes). | |
| let mut count = 0usize; | |
| for pair in t.clone().pairs::<LuaValue, LuaValue>() { | |
| let (_k, _v) = pair.ok()?; | |
| count += 1; | |
| } | |
| if count != len { | |
| return None; | |
| } | |
| Some(LValue::List(items)) | |
| } | |
| _ => None, // function, userdata, thread, lightuserdata, error | |
| } | |
| } | |
| } | |