nomiscript/runtime/
value.rs1use num_rational::Ratio;
2
3pub type Fraction = Ratio<i64>;
4
5#[derive(Debug, Clone, PartialEq)]
6pub 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
18impl Value {
19 #[must_use]
20 pub fn is_truthy(&self) -> bool {
21 !matches!(self, Value::Nil | Value::Bool(false))
22 }
23
24 #[must_use]
25 pub fn type_name(&self) -> &'static str {
26 match self {
27 Value::Nil => "nil",
28 Value::Bool(_) => "bool",
29 Value::Number(_) => "number",
30 Value::String(_) => "string",
31 Value::Symbol(_) => "symbol",
32 Value::Pair(_) => "pair",
33 Value::Vector(_) => "vector",
34 Value::Closure(_) => "closure",
35 Value::Struct { .. } => "struct",
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq)]
41pub struct Pair {
42 pub car: Value,
43 pub cdr: Value,
44}
45
46impl Pair {
47 #[must_use]
48 pub fn new(car: Value, cdr: Value) -> Self {
49 Self { car, cdr }
50 }
51
52 #[must_use]
53 pub fn cons(car: Value, cdr: Value) -> Value {
54 Value::Pair(Box::new(Self::new(car, cdr)))
55 }
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub struct Closure {
60 pub code_id: u32,
61 pub env: Vec<Value>,
62}
63
64impl Closure {
65 #[must_use]
66 pub fn new(code_id: u32, env: Vec<Value>) -> Self {
67 Self { code_id, env }
68 }
69}
70
71#[must_use]
72pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
73 let mut result = Vec::new();
74 loop {
75 match val {
76 Value::Nil => return Some(result),
77 Value::Pair(pair) => {
78 result.push(pair.car.clone());
79 val = &pair.cdr;
80 }
81 _ => return None,
82 }
83 }
84}
85
86#[must_use]
87pub fn vec_to_list(vec: Vec<Value>) -> Value {
88 vec.into_iter()
89 .rev()
90 .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_value_truthy() {
99 assert!(!Value::Nil.is_truthy());
100 assert!(!Value::Bool(false).is_truthy());
101 assert!(Value::Bool(true).is_truthy());
102 assert!(Value::Number(Fraction::from_integer(0)).is_truthy());
103 assert!(Value::String(String::new()).is_truthy());
104 assert!(
105 Value::Struct {
106 name: "test".to_string(),
107 fields: vec![]
108 }
109 .is_truthy()
110 );
111 }
112
113 #[test]
114 fn test_list_conversion() {
115 let list = Pair::cons(
116 Value::Number(Fraction::from_integer(1)),
117 Pair::cons(
118 Value::Number(Fraction::from_integer(2)),
119 Pair::cons(Value::Number(Fraction::from_integer(3)), Value::Nil),
120 ),
121 );
122
123 let vec = list_to_vec(&list).unwrap();
124 assert_eq!(vec.len(), 3);
125
126 let back = vec_to_list(vec);
127 assert_eq!(back, list);
128 }
129
130 #[test]
131 fn test_improper_list() {
132 let improper = Pair::cons(
133 Value::Number(Fraction::from_integer(1)),
134 Value::Number(Fraction::from_integer(2)),
135 );
136 assert!(list_to_vec(&improper).is_none());
137 }
138}