File size: 3,431 Bytes
3afc977
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! 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<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
        }
    }
}