1use std::fmt;
4
5#[derive(Debug, Clone)]
8pub enum Value {
9 String(String),
12
13 Integer(i64),
16
17 Decimal(f64),
20
21 Boolean(bool),
24
25 Array(Vec<Value>),
28
29 Range { lower: i64, upper: i64 },
32
33 TaggedString { tag: String, content: String },
36}
37
38impl Value {
39 pub fn to_gram_notation(&self) -> String {
110 match self {
111 Value::String(s) => format!("\"{}\"", escape_string(s)),
113 Value::Integer(i) => i.to_string(),
114 Value::Decimal(f) => format_decimal(*f),
115 Value::Boolean(b) => b.to_string(),
116 Value::Array(values) => {
117 let items: Vec<String> = values.iter().map(|v| v.to_gram_notation()).collect();
118 format!("[{}]", items.join(", "))
119 }
120 Value::Range { lower, upper } => format!("{}..{}", lower, upper),
121 Value::TaggedString { tag, content } => {
122 if tag.is_empty() {
123 format!("\"\"\"{}\"\"\"", content)
124 } else {
125 format!("\"\"\"{}{}\"\"\"", tag, content)
126 }
127 }
128 }
129 }
130
131 pub fn type_name(&self) -> &'static str {
133 match self {
134 Value::String(_) => "string",
135 Value::Integer(_) => "integer",
136 Value::Decimal(_) => "decimal",
137 Value::Boolean(_) => "boolean",
138 Value::Array(_) => "array",
139 Value::Range { .. } => "range",
140 Value::TaggedString { .. } => "tagged string",
141 }
142 }
143
144 }
146
147impl fmt::Display for Value {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "{}", self.to_gram_notation())
150 }
151}
152
153impl PartialEq for Value {
154 fn eq(&self, other: &Self) -> bool {
155 match (self, other) {
156 (Value::String(a), Value::String(b)) => a == b,
157 (Value::Integer(a), Value::Integer(b)) => a == b,
158 (Value::Decimal(a), Value::Decimal(b)) => {
159 (a - b).abs() < f64::EPSILON
161 }
162 (Value::Boolean(a), Value::Boolean(b)) => a == b,
163 (Value::Array(a), Value::Array(b)) => a == b,
164 (
165 Value::Range {
166 lower: l1,
167 upper: u1,
168 },
169 Value::Range {
170 lower: l2,
171 upper: u2,
172 },
173 ) => l1 == l2 && u1 == u2,
174 (
175 Value::TaggedString {
176 tag: t1,
177 content: c1,
178 },
179 Value::TaggedString {
180 tag: t2,
181 content: c2,
182 },
183 ) => t1 == t2 && c1 == c2,
184 _ => false,
185 }
186 }
187}
188
189pub(crate) fn escape_string(s: &str) -> String {
193 s.replace('\\', "\\\\")
194 .replace('"', "\\\"")
195 .replace('\n', "\\n")
196 .replace('\t', "\\t")
197 .replace('\r', "\\r")
198}
199
200pub(crate) fn format_decimal(f: f64) -> String {
202 if f.fract() == 0.0 && f.is_finite() {
203 format!("{:.1}", f) } else {
205 f.to_string()
206 }
207}
208
209#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_string_value_serialization() {
218 assert_eq!(
220 Value::String("hello".to_string()).to_gram_notation(),
221 "\"hello\""
222 );
223 assert_eq!(
224 Value::String("Hello World".to_string()).to_gram_notation(),
225 "\"Hello World\""
226 );
227 assert_eq!(Value::String("".to_string()).to_gram_notation(), "\"\"");
228 }
229
230 #[test]
231 fn test_integer_value_serialization() {
232 assert_eq!(Value::Integer(42).to_gram_notation(), "42");
233 assert_eq!(Value::Integer(-10).to_gram_notation(), "-10");
234 assert_eq!(Value::Integer(0).to_gram_notation(), "0");
235 }
236
237 #[test]
238 fn test_decimal_value_serialization() {
239 assert_eq!(Value::Decimal(3.14).to_gram_notation(), "3.14");
240 assert_eq!(Value::Decimal(0.0).to_gram_notation(), "0.0");
241 assert_eq!(Value::Decimal(-2.5).to_gram_notation(), "-2.5");
242 }
243
244 #[test]
245 fn test_boolean_value_serialization() {
246 assert_eq!(Value::Boolean(true).to_gram_notation(), "true");
247 assert_eq!(Value::Boolean(false).to_gram_notation(), "false");
248 }
249
250 #[test]
251 fn test_array_value_serialization() {
252 let v = Value::Array(vec![
253 Value::Integer(1),
254 Value::Integer(2),
255 Value::Integer(3),
256 ]);
257 assert_eq!(v.to_gram_notation(), "[1, 2, 3]");
258
259 let v = Value::Array(vec![
261 Value::String("rust".to_string()),
262 Value::Integer(42),
263 Value::Boolean(true),
264 ]);
265 assert_eq!(v.to_gram_notation(), "[\"rust\", 42, true]");
267
268 assert_eq!(Value::Array(vec![]).to_gram_notation(), "[]");
270 }
271
272 #[test]
273 fn test_range_value_serialization() {
274 let v = Value::Range {
275 lower: 1,
276 upper: 10,
277 };
278 assert_eq!(v.to_gram_notation(), "1..10");
279
280 let v = Value::Range {
281 lower: -5,
282 upper: 5,
283 };
284 assert_eq!(v.to_gram_notation(), "-5..5");
285 }
286
287 #[test]
288 fn test_tagged_string_serialization() {
289 let v = Value::TaggedString {
290 tag: "markdown".to_string(),
291 content: "# Heading".to_string(),
292 };
293 assert_eq!(v.to_gram_notation(), "\"\"\"markdown# Heading\"\"\"");
294
295 let v = Value::TaggedString {
296 tag: String::new(),
297 content: "Plain text".to_string(),
298 };
299 assert_eq!(v.to_gram_notation(), "\"\"\"Plain text\"\"\"");
300 }
301
302 #[test]
303 fn test_value_type_names() {
304 assert_eq!(Value::String("".to_string()).type_name(), "string");
305 assert_eq!(Value::Integer(0).type_name(), "integer");
306 assert_eq!(Value::Decimal(0.0).type_name(), "decimal");
307 assert_eq!(Value::Boolean(false).type_name(), "boolean");
308 assert_eq!(Value::Array(vec![]).type_name(), "array");
309 assert_eq!(Value::Range { lower: 0, upper: 0 }.type_name(), "range");
310 assert_eq!(
311 Value::TaggedString {
312 tag: String::new(),
313 content: String::new()
314 }
315 .type_name(),
316 "tagged string"
317 );
318 }
319
320 #[test]
321 fn test_value_equality() {
322 assert_eq!(Value::Integer(42), Value::Integer(42));
323 assert_ne!(Value::Integer(42), Value::Integer(43));
324 assert_ne!(Value::Integer(42), Value::String("42".to_string()));
325
326 assert_eq!(Value::Decimal(1.0), Value::Decimal(1.0));
328
329 assert_eq!(
331 Value::Array(vec![Value::Integer(1), Value::Integer(2)]),
332 Value::Array(vec![Value::Integer(1), Value::Integer(2)])
333 );
334 }
335
336 #[test]
337 fn test_escape_string() {
338 assert_eq!(escape_string("hello"), "hello");
339 assert_eq!(escape_string("hello\"world"), "hello\\\"world");
340 assert_eq!(escape_string("line1\nline2"), "line1\\nline2");
341 assert_eq!(escape_string("tab\there"), "tab\\there");
342 assert_eq!(escape_string("back\\slash"), "back\\\\slash");
343 }
344
345 #[test]
346 fn test_format_decimal() {
347 assert_eq!(format_decimal(3.14), "3.14");
348 assert_eq!(format_decimal(0.0), "0.0");
349 assert_eq!(format_decimal(1.0), "1.0");
350 assert_eq!(format_decimal(-2.5), "-2.5");
351 }
352}