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
    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

            
33
impl Value {
34
    /// The single runtime truth test: a value is false exactly when it is nil.
35
    #[must_use]
36
1169
    pub fn is_nil(&self) -> bool {
37
1169
        matches!(self, Value::Nil)
38
1169
    }
39

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

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

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

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

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

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

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

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

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

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

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

            
128
    #[test]
129
1
    fn only_nil_is_nil() {
130
1
        assert!(Value::Nil.is_nil());
131
1
        assert!(!Value::T.is_nil());
132
1
        assert!(!Value::Number(Fraction::from_integer(0)).is_nil());
133
1
        assert!(!Value::String(String::new()).is_nil());
134
1
        assert!(
135
1
            !Value::Struct {
136
1
                name: "test".to_string(),
137
1
                fields: vec![]
138
1
            }
139
1
            .is_nil()
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 bytes_are_never_nil() {
171
1
        assert!(!Value::Bytes(Vec::new()).is_nil());
172
1
        assert!(!Value::Bytes(vec![0]).is_nil());
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
}