1
use num_rational::Ratio;
2

            
3
pub type Fraction = Ratio<i64>;
4

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

            
18
impl Value {
19
    #[must_use]
20
380
    pub fn is_truthy(&self) -> bool {
21
380
        !matches!(self, Value::Nil | Value::Bool(false))
22
380
    }
23

            
24
    #[must_use]
25
272
    pub fn type_name(&self) -> &'static str {
26
272
        match self {
27
34
            Value::Nil => "nil",
28
34
            Value::Bool(_) => "bool",
29
34
            Value::Number(_) => "number",
30
34
            Value::String(_) => "string",
31
34
            Value::Symbol(_) => "symbol",
32
34
            Value::Pair(_) => "pair",
33
34
            Value::Vector(_) => "vector",
34
34
            Value::Closure(_) => "closure",
35
            Value::Struct { .. } => "struct",
36
        }
37
272
    }
38
}
39

            
40
#[derive(Debug, Clone, PartialEq)]
41
pub struct Pair {
42
    pub car: Value,
43
    pub cdr: Value,
44
}
45

            
46
impl Pair {
47
    #[must_use]
48
38291
    pub fn new(car: Value, cdr: Value) -> Self {
49
38291
        Self { car, cdr }
50
38291
    }
51

            
52
    #[must_use]
53
38223
    pub fn cons(car: Value, cdr: Value) -> Value {
54
38223
        Value::Pair(Box::new(Self::new(car, cdr)))
55
38223
    }
56
}
57

            
58
#[derive(Debug, Clone, PartialEq)]
59
pub struct Closure {
60
    pub code_id: u32,
61
    pub env: Vec<Value>,
62
}
63

            
64
impl Closure {
65
    #[must_use]
66
136
    pub fn new(code_id: u32, env: Vec<Value>) -> Self {
67
136
        Self { code_id, env }
68
136
    }
69
}
70

            
71
#[must_use]
72
308
pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
73
308
    let mut result = Vec::new();
74
    loop {
75
34856
        match val {
76
239
            Value::Nil => return Some(result),
77
34548
            Value::Pair(pair) => {
78
34548
                result.push(pair.car.clone());
79
34548
                val = &pair.cdr;
80
34548
            }
81
69
            _ => return None,
82
        }
83
    }
84
308
}
85

            
86
#[must_use]
87
171
pub fn vec_to_list(vec: Vec<Value>) -> Value {
88
171
    vec.into_iter()
89
171
        .rev()
90
34275
        .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
91
171
}
92

            
93
#[cfg(test)]
94
mod tests {
95
    use super::*;
96

            
97
    #[test]
98
1
    fn test_value_truthy() {
99
1
        assert!(!Value::Nil.is_truthy());
100
1
        assert!(!Value::Bool(false).is_truthy());
101
1
        assert!(Value::Bool(true).is_truthy());
102
1
        assert!(Value::Number(Fraction::from_integer(0)).is_truthy());
103
1
        assert!(Value::String(String::new()).is_truthy());
104
1
        assert!(
105
1
            Value::Struct {
106
1
                name: "test".to_string(),
107
1
                fields: vec![]
108
1
            }
109
1
            .is_truthy()
110
        );
111
1
    }
112

            
113
    #[test]
114
1
    fn test_list_conversion() {
115
1
        let list = Pair::cons(
116
1
            Value::Number(Fraction::from_integer(1)),
117
1
            Pair::cons(
118
1
                Value::Number(Fraction::from_integer(2)),
119
1
                Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
120
            ),
121
        );
122

            
123
1
        let vec = list_to_vec(&list).unwrap();
124
1
        assert_eq!(vec.len(), 3);
125

            
126
1
        let back = vec_to_list(vec);
127
1
        assert_eq!(back, list);
128
1
    }
129

            
130
    #[test]
131
1
    fn test_improper_list() {
132
1
        let improper = Pair::cons(
133
1
            Value::Number(Fraction::from_integer(1)),
134
1
            Value::Number(Fraction::from_integer(2)),
135
        );
136
1
        assert!(list_to_vec(&improper).is_none());
137
1
    }
138
}