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    Bool(bool),
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    #[must_use]
35    pub fn is_truthy(&self) -> bool {
36        !matches!(self, Value::Nil | Value::Bool(false))
37    }
38
39    #[must_use]
40    pub fn type_name(&self) -> &'static str {
41        match self {
42            Value::Nil => "nil",
43            Value::Bool(_) => "bool",
44            Value::Number(_) => "number",
45            Value::String(_) => "string",
46            Value::Symbol(_) => "symbol",
47            Value::Bytes(_) => "bytes",
48            Value::Pair(_) => "pair",
49            Value::Vector(_) => "vector",
50            Value::Closure(_) => "closure",
51            Value::Struct { .. } => "struct",
52            Value::Commodity { .. } => "commodity",
53        }
54    }
55
56    #[must_use]
57    pub fn as_bytes(&self) -> Option<&[u8]> {
58        match self {
59            Value::Bytes(b) => Some(b),
60            _ => None,
61        }
62    }
63
64    #[must_use]
65    pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
66        Value::Bytes(b.into())
67    }
68}
69
70#[derive(Debug, Clone, PartialEq)]
71pub struct Pair {
72    pub car: Value,
73    pub cdr: Value,
74}
75
76impl Pair {
77    #[must_use]
78    pub fn new(car: Value, cdr: Value) -> Self {
79        Self { car, cdr }
80    }
81
82    #[must_use]
83    pub fn cons(car: Value, cdr: Value) -> Value {
84        Value::Pair(Box::new(Self::new(car, cdr)))
85    }
86}
87
88#[derive(Debug, Clone, PartialEq)]
89pub struct Closure {
90    pub code_id: u32,
91    pub env: Vec<Value>,
92}
93
94impl Closure {
95    #[must_use]
96    pub fn new(code_id: u32, env: Vec<Value>) -> Self {
97        Self { code_id, env }
98    }
99}
100
101#[must_use]
102pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
103    let mut result = Vec::new();
104    loop {
105        match val {
106            Value::Nil => return Some(result),
107            Value::Pair(pair) => {
108                result.push(pair.car.clone());
109                val = &pair.cdr;
110            }
111            _ => return None,
112        }
113    }
114}
115
116#[must_use]
117pub fn vec_to_list(vec: Vec<Value>) -> Value {
118    vec.into_iter()
119        .rev()
120        .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_value_truthy() {
129        assert!(!Value::Nil.is_truthy());
130        assert!(!Value::Bool(false).is_truthy());
131        assert!(Value::Bool(true).is_truthy());
132        assert!(Value::Number(Fraction::from_integer(0)).is_truthy());
133        assert!(Value::String(String::new()).is_truthy());
134        assert!(
135            Value::Struct {
136                name: "test".to_string(),
137                fields: vec![]
138            }
139            .is_truthy()
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 test_bytes_truthy() {
171        assert!(Value::Bytes(Vec::new()).is_truthy());
172        assert!(Value::Bytes(vec![0]).is_truthy());
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}