//! 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}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "k", content = "v")] pub enum LValue { Nil, Bool(bool), Int(i64), Num(f64), Str(String), List(Vec), } impl LValue { /// Convert a Rust-side value into a Lua value inside `lua`. pub fn to_lua(&self, lua: &Lua) -> mlua::Result { 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 { 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::() { let (_k, _v) = pair.ok()?; count += 1; } if count != len { return None; } Some(LValue::List(items)) } _ => None, // function, userdata, thread, lightuserdata, error } } }