1
use num_rational::Ratio;
2
use uuid::Uuid;
3

            
4
pub type Fraction = Ratio<i64>;
5

            
6
#[derive(Debug, Clone, PartialEq)]
7
pub 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

            
33
impl Value {
34
    #[must_use]
35
789
    pub fn is_truthy(&self) -> bool {
36
789
        !matches!(self, Value::Nil | Value::Bool(false))
37
789
    }
38

            
39
    #[must_use]
40
569
    pub fn type_name(&self) -> &'static str {
41
569
        match self {
42
71
            Value::Nil => "nil",
43
71
            Value::Bool(_) => "bool",
44
71
            Value::Number(_) => "number",
45
71
            Value::String(_) => "string",
46
71
            Value::Symbol(_) => "symbol",
47
1
            Value::Bytes(_) => "bytes",
48
71
            Value::Pair(_) => "pair",
49
71
            Value::Vector(_) => "vector",
50
71
            Value::Closure(_) => "closure",
51
            Value::Struct { .. } => "struct",
52
            Value::Commodity { .. } => "commodity",
53
        }
54
569
    }
55

            
56
    #[must_use]
57
2
    pub fn as_bytes(&self) -> Option<&[u8]> {
58
2
        match self {
59
1
            Value::Bytes(b) => Some(b),
60
1
            _ => None,
61
        }
62
2
    }
63

            
64
    #[must_use]
65
1
    pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
66
1
        Value::Bytes(b.into())
67
1
    }
68
}
69

            
70
#[derive(Debug, Clone, PartialEq)]
71
pub struct Pair {
72
    pub car: Value,
73
    pub cdr: Value,
74
}
75

            
76
impl Pair {
77
    #[must_use]
78
163952
    pub fn new(car: Value, cdr: Value) -> Self {
79
163952
        Self { car, cdr }
80
163952
    }
81

            
82
    #[must_use]
83
163810
    pub fn cons(car: Value, cdr: Value) -> Value {
84
163810
        Value::Pair(Box::new(Self::new(car, cdr)))
85
163810
    }
86
}
87

            
88
#[derive(Debug, Clone, PartialEq)]
89
pub struct Closure {
90
    pub code_id: u32,
91
    pub env: Vec<Value>,
92
}
93

            
94
impl Closure {
95
    #[must_use]
96
285
    pub fn new(code_id: u32, env: Vec<Value>) -> Self {
97
285
        Self { code_id, env }
98
285
    }
99
}
100

            
101
#[must_use]
102
42034
pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
103
42034
    let mut result = Vec::new();
104
    loop {
105
390932
        match val {
106
41891
            Value::Nil => return Some(result),
107
348898
            Value::Pair(pair) => {
108
348898
                result.push(pair.car.clone());
109
348898
                val = &pair.cdr;
110
348898
            }
111
143
            _ => return None,
112
        }
113
    }
114
42034
}
115

            
116
#[must_use]
117
17396
pub fn vec_to_list(vec: Vec<Value>) -> Value {
118
17396
    vec.into_iter()
119
17396
        .rev()
120
154144
        .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
121
17396
}
122

            
123
#[cfg(test)]
124
mod tests {
125
    use super::*;
126

            
127
    #[test]
128
1
    fn test_value_truthy() {
129
1
        assert!(!Value::Nil.is_truthy());
130
1
        assert!(!Value::Bool(false).is_truthy());
131
1
        assert!(Value::Bool(true).is_truthy());
132
1
        assert!(Value::Number(Fraction::from_integer(0)).is_truthy());
133
1
        assert!(Value::String(String::new()).is_truthy());
134
1
        assert!(
135
1
            Value::Struct {
136
1
                name: "test".to_string(),
137
1
                fields: vec![]
138
1
            }
139
1
            .is_truthy()
140
        );
141
1
    }
142

            
143
    #[test]
144
1
    fn test_list_conversion() {
145
1
        let list = Pair::cons(
146
1
            Value::Number(Fraction::from_integer(1)),
147
1
            Pair::cons(
148
1
                Value::Number(Fraction::from_integer(2)),
149
1
                Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
150
            ),
151
        );
152

            
153
1
        let vec = list_to_vec(&list).unwrap();
154
1
        assert_eq!(vec.len(), 3);
155

            
156
1
        let back = vec_to_list(vec);
157
1
        assert_eq!(back, list);
158
1
    }
159

            
160
    #[test]
161
1
    fn test_improper_list() {
162
1
        let improper = Pair::cons(
163
1
            Value::Number(Fraction::from_integer(1)),
164
1
            Value::Number(Fraction::from_integer(2)),
165
        );
166
1
        assert!(list_to_vec(&improper).is_none());
167
1
    }
168

            
169
    #[test]
170
1
    fn test_bytes_truthy() {
171
1
        assert!(Value::Bytes(Vec::new()).is_truthy());
172
1
        assert!(Value::Bytes(vec![0]).is_truthy());
173
1
    }
174

            
175
    #[test]
176
1
    fn test_bytes_type_name() {
177
1
        assert_eq!(Value::Bytes(vec![1, 2, 3]).type_name(), "bytes");
178
1
    }
179

            
180
    #[test]
181
1
    fn test_bytes_accessors() {
182
1
        let v = Value::from_bytes(vec![0xCA, 0xFE]);
183
1
        assert_eq!(v.as_bytes(), Some(&[0xCA, 0xFE][..]));
184
1
        assert_eq!(Value::Nil.as_bytes(), None);
185
1
    }
186
}