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 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 {
28 amount: Fraction,
29 commodity_id: Uuid,
30 },
31}
32
33impl Value {
34 #[must_use]
36 pub fn is_nil(&self) -> bool {
37 matches!(self, Value::Nil)
38 }
39
40 #[must_use]
41 pub fn type_name(&self) -> &'static str {
42 match self {
43 Value::Nil => "nil",
44 Value::T => "boolean",
45 Value::Number(_) => "number",
46 Value::String(_) => "string",
47 Value::Symbol(_) => "symbol",
48 Value::Bytes(_) => "bytes",
49 Value::Pair(_) => "pair",
50 Value::Vector(_) => "vector",
51 Value::Closure(_) => "closure",
52 Value::Struct { .. } => "struct",
53 Value::Commodity { .. } => "commodity",
54 }
55 }
56
57 #[must_use]
58 pub fn as_bytes(&self) -> Option<&[u8]> {
59 match self {
60 Value::Bytes(b) => Some(b),
61 _ => None,
62 }
63 }
64
65 #[must_use]
66 pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
67 Value::Bytes(b.into())
68 }
69}
70
71#[derive(Debug, Clone, PartialEq)]
72pub struct Pair {
73 pub car: Value,
74 pub cdr: Value,
75}
76
77impl Pair {
78 #[must_use]
79 pub fn new(car: Value, cdr: Value) -> Self {
80 Self { car, cdr }
81 }
82
83 #[must_use]
84 pub fn cons(car: Value, cdr: Value) -> Value {
85 Value::Pair(Box::new(Self::new(car, cdr)))
86 }
87}
88
89#[derive(Debug, Clone, PartialEq)]
90pub struct Closure {
91 pub code_id: u32,
92 pub env: Vec<Value>,
93}
94
95impl Closure {
96 #[must_use]
97 pub fn new(code_id: u32, env: Vec<Value>) -> Self {
98 Self { code_id, env }
99 }
100}
101
102#[must_use]
103pub fn list_to_vec(mut val: &Value) -> Option<Vec<Value>> {
104 let mut result = Vec::new();
105 loop {
106 match val {
107 Value::Nil => return Some(result),
108 Value::Pair(pair) => {
109 result.push(pair.car.clone());
110 val = &pair.cdr;
111 }
112 _ => return None,
113 }
114 }
115}
116
117#[must_use]
118pub fn vec_to_list(vec: Vec<Value>) -> Value {
119 vec.into_iter()
120 .rev()
121 .fold(Value::Nil, |acc, v| Pair::cons(v, acc))
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn only_nil_is_nil() {
130 assert!(Value::Nil.is_nil());
131 assert!(!Value::T.is_nil());
132 assert!(!Value::Number(Fraction::from_integer(0)).is_nil());
133 assert!(!Value::String(String::new()).is_nil());
134 assert!(
135 !Value::Struct {
136 name: "test".to_string(),
137 fields: vec![]
138 }
139 .is_nil()
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 bytes_are_never_nil() {
171 assert!(!Value::Bytes(Vec::new()).is_nil());
172 assert!(!Value::Bytes(vec![0]).is_nil());
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}