Skip to main content

nomiscript/runtime/
value.rs

1use num_rational::Ratio;
2use uuid::Uuid;
3
4pub type Fraction = Ratio<i64>;
5
6#[derive(Debug, Clone, PartialEq)]
7pub enum Value {
8    Nil,
9    T,
10    Number(Fraction),
11    String(String),
12    Symbol(String),
13    Bytes(Vec<u8>),
14    Pair(Box<Pair>),
15    Vector(Vec<Value>),
16    Closure(Closure),
17    Struct {
18        name: String,
19        fields: Vec<Value>,
20    },
21    /// Commodity-bearing amount: rational value tagged with the
22    /// originating commodity entity id. Distinct from `Number` so the
23    /// type system refuses cross-strata arithmetic (`Commodity + Ratio`,
24    /// `Commodity + Commodity` with different commodity_ids) at compile
25    /// or runtime. Conversion between commodities goes through the
26    /// Prices table via `(convert-commodity ...)`.
27    Commodity {
28        amount: Fraction,
29        commodity_id: Uuid,
30    },
31}
32
33impl Value {
34    /// The single runtime truth test: a value is false exactly when it is nil.
35    #[must_use]
36    pub fn is_nil(&self) -> bool {
37        matches!(self, Value::Nil)
38    }
39
40    #[must_use]
41    pub fn type_name(&self) -> &'static str {
42        match self {
43            Value::Nil => "nil",
44            Value::T => "boolean",
45            Value::Number(_) => "number",
46            Value::String(_) => "string",
47            Value::Symbol(_) => "symbol",
48            Value::Bytes(_) => "bytes",
49            Value::Pair(_) => "pair",
50            Value::Vector(_) => "vector",
51            Value::Closure(_) => "closure",
52            Value::Struct { .. } => "struct",
53            Value::Commodity { .. } => "commodity",
54        }
55    }
56
57    #[must_use]
58    pub fn as_bytes(&self) -> Option<&[u8]> {
59        match self {
60            Value::Bytes(b) => Some(b),
61            _ => None,
62        }
63    }
64
65    #[must_use]
66    pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
67        Value::Bytes(b.into())
68    }
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub struct Pair {
73    pub car: Value,
74    pub cdr: Value,
75}
76
77impl Pair {
78    #[must_use]
79    pub fn new(car: Value, cdr: Value) -> Self {
80        Self { car, cdr }
81    }
82
83    #[must_use]
84    pub fn cons(car: Value, cdr: Value) -> Value {
85        Value::Pair(Box::new(Self::new(car, cdr)))
86    }
87}
88
89#[derive(Debug, Clone, PartialEq)]
90pub struct Closure {
91    pub code_id: u32,
92    pub env: Vec<Value>,
93}
94
95impl Closure {
96    #[must_use]
97    pub fn new(code_id: u32, env: Vec<Value>) -> Self {
98        Self { code_id, env }
99    }
100}
101
102#[must_use]
103pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
104    let mut result = Vec::new();
105    loop {
106        match val {
107            Value::Nil => return Some(result),
108            Value::Pair(pair) => {
109                result.push(pair.car.clone());
110                val = &pair.cdr;
111            }
112            _ => return None,
113        }
114    }
115}
116
117#[must_use]
118pub fn vec_to_list(vec: Vec<Value>) -> Value {
119    vec.into_iter()
120        .rev()
121        .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn only_nil_is_nil() {
130        assert!(Value::Nil.is_nil());
131        assert!(!Value::T.is_nil());
132        assert!(!Value::Number(Fraction::from_integer(0)).is_nil());
133        assert!(!Value::String(String::new()).is_nil());
134        assert!(
135            !Value::Struct {
136                name: "test".to_string(),
137                fields: vec![]
138            }
139            .is_nil()
140        );
141    }
142
143    #[test]
144    fn test_list_conversion() {
145        let list = Pair::cons(
146            Value::Number(Fraction::from_integer(1)),
147            Pair::cons(
148                Value::Number(Fraction::from_integer(2)),
149                Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
150            ),
151        );
152
153        let vec = list_to_vec(&list).unwrap();
154        assert_eq!(vec.len(), 3);
155
156        let back = vec_to_list(vec);
157        assert_eq!(back, list);
158    }
159
160    #[test]
161    fn test_improper_list() {
162        let improper = Pair::cons(
163            Value::Number(Fraction::from_integer(1)),
164            Value::Number(Fraction::from_integer(2)),
165        );
166        assert!(list_to_vec(&improper).is_none());
167    }
168
169    #[test]
170    fn bytes_are_never_nil() {
171        assert!(!Value::Bytes(Vec::new()).is_nil());
172        assert!(!Value::Bytes(vec![0]).is_nil());
173    }
174
175    #[test]
176    fn test_bytes_type_name() {
177        assert_eq!(Value::Bytes(vec![1, 2, 3]).type_name(), "bytes");
178    }
179
180    #[test]
181    fn test_bytes_accessors() {
182        let v = Value::from_bytes(vec![0xCA, 0xFE]);
183        assert_eq!(v.as_bytes(), Some(&[0xCA, 0xFE][..]));
184        assert_eq!(Value::Nil.as_bytes(), None);
185    }
186}