nomiscript/runtime/
value.rs1use 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 {
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}