Skip to main content

rusterix/vm/
value.rs

1use crate::value::Value;
2use std::ops::{Add, Div, Mul, Neg, Sub};
3use vek::Vec3;
4
5#[derive(Clone, Debug, PartialEq)]
6pub struct VMValue {
7    pub x: f32,
8    pub y: f32,
9    pub z: f32,
10    pub string: Option<String>,
11}
12
13impl VMValue {
14    pub fn new(x: f32, y: f32, z: f32) -> Self {
15        Self {
16            x,
17            y,
18            z,
19            string: None,
20        }
21    }
22
23    /// Construct with numeric components and an optional string payload.
24    pub fn new_with_string<S: Into<String>>(x: f32, y: f32, z: f32, s: S) -> Self {
25        Self {
26            x,
27            y,
28            z,
29            string: Some(s.into()),
30        }
31    }
32
33    pub fn broadcast(v: f32) -> Self {
34        Self {
35            x: v,
36            y: v,
37            z: v,
38            string: None,
39        }
40    }
41
42    pub fn zero() -> Self {
43        Self::broadcast(0.0)
44    }
45
46    pub fn from_bool(v: bool) -> Self {
47        Self::broadcast(if v { 1.0 } else { 0.0 })
48    }
49
50    pub fn from_i32(v: i32) -> Self {
51        Self::broadcast(v as f32)
52    }
53
54    pub fn from_f32(v: f32) -> Self {
55        Self::broadcast(v)
56    }
57
58    pub fn from_u32(v: u32) -> Self {
59        Self::broadcast(v as f32)
60    }
61
62    /// Generic helper leveraging `Into<VMValue>` implementations.
63    pub fn from<T: Into<VMValue>>(v: T) -> Self {
64        v.into()
65    }
66
67    pub fn from_vec3(v: Vec3<f32>) -> Self {
68        Self {
69            x: v.x,
70            y: v.y,
71            z: v.z,
72            string: None,
73        }
74    }
75
76    pub fn to_vec3(&self) -> Vec3<f32> {
77        Vec3::new(self.x, self.y, self.z)
78    }
79
80    pub fn from_string<S: Into<String>>(s: S) -> Self {
81        Self {
82            x: 0.0,
83            y: 0.0,
84            z: 0.0,
85            string: Some(s.into()),
86        }
87    }
88
89    pub fn as_string(&self) -> Option<&str> {
90        self.string.as_deref()
91    }
92
93    pub fn from_value(value: &Value) -> Self {
94        match value {
95            Value::NoValue => VMValue::zero(),
96            Value::Bool(b) => VMValue::broadcast(if *b { 1.0 } else { 0.0 }),
97            Value::Int(i) => VMValue::broadcast(*i as f32),
98            Value::UInt(i) => VMValue::broadcast(*i as f32),
99            Value::Int64(i) => VMValue::broadcast(*i as f32),
100            Value::Float(f) => VMValue::broadcast(*f),
101            Value::Vec2(v) => VMValue::new(v[0], v[1], 0.0),
102            Value::Vec3(v) => VMValue::new(v[0], v[1], v[2]),
103            Value::Vec4(v) => VMValue::new(v[0], v[1], v[2]),
104            Value::Str(s) => VMValue::from_string(s.clone()),
105            _ => VMValue::zero(),
106        }
107    }
108
109    /// Convert into a generic runtime Value.
110    pub fn to_value(&self) -> Value {
111        if let Some(s) = self.as_string() {
112            Value::Str(s.to_string())
113        } else if self.x == self.y && self.x == self.z {
114            Value::Float(self.x)
115        } else {
116            Value::Vec3([self.x, self.y, self.z])
117        }
118    }
119
120    /// Convert into a Value using an optional type hint and/or inline string tag (e.g. "bool").
121    pub fn to_value_with_hint(&self, hint: Option<&Value>) -> Value {
122        // String payload can act as an explicit type hint.
123        if let Some(s) = self.as_string() {
124            let s_trim = s.trim();
125            // Support legacy tagged strings like "bool:true"
126            if let Some(tagged) = Self::from_type_tagged_str(s_trim) {
127                return tagged;
128            }
129            match s_trim.to_ascii_lowercase().as_str() {
130                "bool" => return Value::Bool(self.to_bool()),
131                "int" => return Value::Int(self.x as i32),
132                "uint" => return Value::UInt(self.x.max(0.0) as u32),
133                "i64" | "int64" => return Value::Int64(self.x as i64),
134                "float" | "f32" => return Value::Float(self.x),
135                "vec2" => return Value::Vec2([self.x, self.y]),
136                "vec3" => return Value::Vec3([self.x, self.y, self.z]),
137                "str" | "string" => {
138                    return Value::Str(Self::to_string_lossy_components(self.x, self.y, self.z));
139                }
140                _ => {}
141            }
142        }
143
144        match hint {
145            Some(Value::Bool(_)) => Value::Bool(self.to_bool()),
146            Some(Value::Int(_)) => Value::Int(self.x as i32),
147            Some(Value::UInt(_)) => Value::UInt(self.x.max(0.0) as u32),
148            Some(Value::Int64(_)) => Value::Int64(self.x as i64),
149            Some(Value::Float(_)) => Value::Float(self.x),
150            Some(Value::Vec2(_)) => Value::Vec2([self.x, self.y]),
151            Some(Value::Vec3(_)) => Value::Vec3([self.x, self.y, self.z]),
152            Some(Value::Vec4(_)) => Value::Vec4([self.x, self.y, self.z, 0.0]),
153            Some(Value::Str(_)) => Value::Str(
154                self.as_string()
155                    .map(|s| s.to_string())
156                    .unwrap_or_else(|| format!("{}", self.x)),
157            ),
158            Some(Value::StrArray(_)) => Value::StrArray(vec![
159                self.as_string()
160                    .map(|s| s.to_string())
161                    .unwrap_or_else(|| format!("{}", self.x)),
162            ]),
163            _ => {
164                // Fallback: infer from string payload, then numbers.
165                if let Some(s) = self.as_string() {
166                    if let Some(b) = Self::parse_bool_str(s) {
167                        return Value::Bool(b);
168                    }
169                    if let Ok(i) = s.parse::<i32>() {
170                        return Value::Int(i);
171                    }
172                    if let Ok(f) = s.parse::<f32>() {
173                        return Value::Float(f);
174                    }
175                    return Value::Str(s.to_string());
176                }
177                if self.x == self.y && self.x == self.z {
178                    Value::Float(self.x)
179                } else {
180                    Value::Vec3([self.x, self.y, self.z])
181                }
182            }
183        }
184    }
185
186    pub fn to_bool(&self) -> bool {
187        if let Some(s) = self.as_string() {
188            if let Some(b) = Self::parse_bool_str(s) {
189                return b;
190            }
191        }
192        // Numeric fallback: nonzero -> true
193        self.x != 0.0 || self.y != 0.0 || self.z != 0.0
194    }
195
196    pub fn is_truthy(&self) -> bool {
197        if let Some(s) = &self.string {
198            !s.is_empty()
199        } else {
200            self.x != 0.0 || self.y != 0.0 || self.z != 0.0
201        }
202    }
203
204    fn parse_bool_str(s: &str) -> Option<bool> {
205        match s.trim().to_ascii_lowercase().as_str() {
206            "true" | "1" | "yes" | "on" => Some(true),
207            "false" | "0" | "no" | "off" => Some(false),
208            _ => None,
209        }
210    }
211
212    fn from_type_tagged_str(s: &str) -> Option<Value> {
213        let (tag, rest) = s.split_once(':')?;
214        let tag = tag.trim().to_ascii_lowercase();
215        let rest = rest.trim();
216
217        match tag.as_str() {
218            "bool" => Self::parse_bool_str(rest).map(Value::Bool),
219            "int" => rest.parse::<i32>().ok().map(Value::Int),
220            "uint" => rest.parse::<u32>().ok().map(Value::UInt),
221            "i64" | "int64" => rest.parse::<i64>().ok().map(Value::Int64),
222            "float" | "f32" => rest.parse::<f32>().ok().map(Value::Float),
223            "vec2" => parse_vec(rest, 2).map(|v| Value::Vec2([v[0], v[1]])),
224            "vec3" => parse_vec(rest, 3).map(|v| Value::Vec3([v[0], v[1], v[2]])),
225            "str" | "string" => Some(Value::Str(rest.to_string())),
226            _ => None,
227        }
228    }
229
230    pub fn magnitude(&self) -> f32 {
231        self.to_vec3().magnitude()
232    }
233
234    pub fn map<F: Fn(f32) -> f32>(&self, f: F) -> Self {
235        VMValue::new(f(self.x), f(self.y), f(self.z))
236    }
237
238    pub fn map2<F: Fn(f32, f32) -> f32>(&self, other: VMValue, f: F) -> Self {
239        VMValue::new(f(self.x, other.x), f(self.y, other.y), f(self.z, other.z))
240    }
241
242    pub fn dot(&self, other: VMValue) -> f32 {
243        self.to_vec3().dot(other.to_vec3())
244    }
245
246    pub fn cross(&self, other: VMValue) -> Self {
247        VMValue::from_vec3(self.to_vec3().cross(other.to_vec3()))
248    }
249
250    fn format_scalar(v: f32) -> String {
251        if v.fract() == 0.0 {
252            format!("{:.0}", v)
253        } else {
254            v.to_string()
255        }
256    }
257
258    fn to_string_lossy_components(x: f32, y: f32, z: f32) -> String {
259        if x == y && y == z {
260            Self::format_scalar(x)
261        } else {
262            format!("{},{},{}", x, y, z)
263        }
264    }
265
266    fn _to_string_lossy(&self) -> String {
267        Self::to_string_lossy_components(self.x, self.y, self.z)
268    }
269}
270
271impl std::fmt::Display for VMValue {
272    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
273        if let Some(s) = &self.string {
274            let tag = s.trim();
275            let tag_l = tag.to_ascii_lowercase();
276
277            if let Some(val) = Self::from_type_tagged_str(tag) {
278                return write!(f, "{}", format_value_brief(&val));
279            }
280
281            return match tag_l.as_str() {
282                "bool" => write!(f, "{}", self.to_bool()),
283                "int" => write!(f, "{}", self.x as i32),
284                "uint" => write!(f, "{}", self.x.max(0.0) as u32),
285                "i64" | "int64" => write!(f, "{}", self.x as i64),
286                "float" | "f32" => write!(f, "{}", self.x),
287                "vec2" => write!(f, "[{}, {}]", self.x, self.y),
288                "vec3" => write!(f, "[{}, {}, {}]", self.x, self.y, self.z),
289                "vec4" => write!(f, "[{}, {}, {}, 0]", self.x, self.y, self.z),
290                "str" | "string" => {
291                    write!(
292                        f,
293                        "{}",
294                        Self::to_string_lossy_components(self.x, self.y, self.z)
295                    )
296                }
297                _ => write!(f, "{}", s),
298            };
299        }
300
301        if self.x == self.y && self.x == self.z {
302            write!(f, "{}", self.x)
303        } else {
304            write!(f, "[{}, {}, {}]", self.x, self.y, self.z)
305        }
306    }
307}
308
309fn format_value_brief(v: &Value) -> String {
310    match v {
311        Value::Bool(b) => b.to_string(),
312        Value::Int(i) => i.to_string(),
313        Value::UInt(u) => u.to_string(),
314        Value::Int64(i) => i.to_string(),
315        Value::Float(f) => f.to_string(),
316        Value::Vec2(v) => format!("[{}, {}]", v[0], v[1]),
317        Value::Vec3(v) => format!("[{}, {}, {}]", v[0], v[1], v[2]),
318        Value::Vec4(v) => format!("[{}, {}, {}, {}]", v[0], v[1], v[2], v[3]),
319        Value::Str(s) => s.clone(),
320        _ => format!("{:?}", v),
321    }
322}
323
324impl Add for VMValue {
325    type Output = VMValue;
326
327    fn add(self, rhs: VMValue) -> Self::Output {
328        let (ax, ay, az) = (self.x, self.y, self.z);
329        let (bx, by, bz) = (rhs.x, rhs.y, rhs.z);
330        match (self.string, rhs.string) {
331            (Some(a), Some(b)) => VMValue::from_string(format!("{a}{b}")),
332            (Some(a), None) => {
333                let b_str = VMValue::to_string_lossy_components(bx, by, bz);
334                VMValue::from_string(format!("{a}{b_str}"))
335            }
336            (None, Some(b)) => {
337                let a_str = VMValue::to_string_lossy_components(ax, ay, az);
338                VMValue::from_string(format!("{a_str}{b}"))
339            }
340            _ => VMValue::new(ax + bx, ay + by, az + bz),
341        }
342    }
343}
344
345impl Sub for VMValue {
346    type Output = VMValue;
347
348    fn sub(self, rhs: VMValue) -> Self::Output {
349        VMValue::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
350    }
351}
352
353impl Mul for VMValue {
354    type Output = VMValue;
355
356    fn mul(self, rhs: VMValue) -> Self::Output {
357        VMValue::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z)
358    }
359}
360
361impl Div for VMValue {
362    type Output = VMValue;
363
364    fn div(self, rhs: VMValue) -> Self::Output {
365        VMValue::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z)
366    }
367}
368
369impl Neg for VMValue {
370    type Output = VMValue;
371
372    fn neg(self) -> Self::Output {
373        VMValue::new(-self.x, -self.y, -self.z)
374    }
375}
376
377impl From<bool> for VMValue {
378    fn from(v: bool) -> Self {
379        VMValue::from_bool(v)
380    }
381}
382
383impl From<i32> for VMValue {
384    fn from(v: i32) -> Self {
385        VMValue::from_i32(v)
386    }
387}
388
389impl From<u32> for VMValue {
390    fn from(v: u32) -> Self {
391        VMValue::from_u32(v)
392    }
393}
394
395impl From<f32> for VMValue {
396    fn from(v: f32) -> Self {
397        VMValue::from_f32(v)
398    }
399}
400
401impl From<String> for VMValue {
402    fn from(s: String) -> Self {
403        VMValue::from_string(s)
404    }
405}
406
407impl From<&str> for VMValue {
408    fn from(s: &str) -> Self {
409        VMValue::from_string(s)
410    }
411}
412
413impl From<Value> for VMValue {
414    fn from(v: Value) -> Self {
415        VMValue::from_value(&v)
416    }
417}
418
419impl From<Vec3<f32>> for VMValue {
420    fn from(v: Vec3<f32>) -> Self {
421        VMValue::from_vec3(v)
422    }
423}
424
425fn parse_vec(s: &str, expected: usize) -> Option<Vec<f32>> {
426    let vals: Vec<f32> = s
427        .split(',')
428        .filter_map(|p| p.trim().parse::<f32>().ok())
429        .collect();
430    if vals.len() == expected {
431        Some(vals)
432    } else {
433        None
434    }
435}