Skip to main content

nomiscript/runtime/
value.rs

1use num_rational::Ratio;
2
3pub type Fraction = Ratio<i64>;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Value {
7    Nil,
8    Bool(bool),
9    Number(Fraction),
10    String(String),
11    Symbol(String),
12    Pair(Box<Pair>),
13    Vector(Vec<Value>),
14    Closure(Closure),
15    Struct { name: String, fields: Vec<Value> },
16}
17
18impl Value {
19    #[must_use]
20    pub fn is_truthy(&self) -> bool {
21        !matches!(self, Value::Nil | Value::Bool(false))
22    }
23
24    #[must_use]
25    pub fn type_name(&self) -> &'static str {
26        match self {
27            Value::Nil => "nil",
28            Value::Bool(_) => "bool",
29            Value::Number(_) => "number",
30            Value::String(_) => "string",
31            Value::Symbol(_) => "symbol",
32            Value::Pair(_) => "pair",
33            Value::Vector(_) => "vector",
34            Value::Closure(_) => "closure",
35            Value::Struct { .. } => "struct",
36        }
37    }
38}
39
40#[derive(Debug, Clone, PartialEq)]
41pub struct Pair {
42    pub car: Value,
43    pub cdr: Value,
44}
45
46impl Pair {
47    #[must_use]
48    pub fn new(car: Value, cdr: Value) -> Self {
49        Self { car, cdr }
50    }
51
52    #[must_use]
53    pub fn cons(car: Value, cdr: Value) -> Value {
54        Value::Pair(Box::new(Self::new(car, cdr)))
55    }
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub struct Closure {
60    pub code_id: u32,
61    pub env: Vec<Value>,
62}
63
64impl Closure {
65    #[must_use]
66    pub fn new(code_id: u32, env: Vec<Value>) -> Self {
67        Self { code_id, env }
68    }
69}
70
71#[must_use]
72pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
73    let mut result = Vec::new();
74    loop {
75        match val {
76            Value::Nil => return Some(result),
77            Value::Pair(pair) => {
78                result.push(pair.car.clone());
79                val = &pair.cdr;
80            }
81            _ => return None,
82        }
83    }
84}
85
86#[must_use]
87pub fn vec_to_list(vec: Vec<Value>) -> Value {
88    vec.into_iter()
89        .rev()
90        .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_value_truthy() {
99        assert!(!Value::Nil.is_truthy());
100        assert!(!Value::Bool(false).is_truthy());
101        assert!(Value::Bool(true).is_truthy());
102        assert!(Value::Number(Fraction::from_integer(0)).is_truthy());
103        assert!(Value::String(String::new()).is_truthy());
104        assert!(
105            Value::Struct {
106                name: "test".to_string(),
107                fields: vec![]
108            }
109            .is_truthy()
110        );
111    }
112
113    #[test]
114    fn test_list_conversion() {
115        let list = Pair::cons(
116            Value::Number(Fraction::from_integer(1)),
117            Pair::cons(
118                Value::Number(Fraction::from_integer(2)),
119                Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
120            ),
121        );
122
123        let vec = list_to_vec(&list).unwrap();
124        assert_eq!(vec.len(), 3);
125
126        let back = vec_to_list(vec);
127        assert_eq!(back, list);
128    }
129
130    #[test]
131    fn test_improper_list() {
132        let improper = Pair::cons(
133            Value::Number(Fraction::from_integer(1)),
134            Value::Number(Fraction::from_integer(2)),
135        );
136        assert!(list_to_vec(&improper).is_none());
137    }
138}