1use crate::seqstring::SeqString;
18use crate::stack::{Stack, pop, push};
19use crate::value::{MapKey, Value, VariantData};
20use std::collections::HashMap;
21
22#[derive(Clone)]
24pub(crate) struct SonConfig {
25 pub(crate) pretty: bool,
27 pub(crate) indent: usize,
29}
30
31impl Default for SonConfig {
32 fn default() -> Self {
33 Self {
34 pretty: false,
35 indent: 2,
36 }
37 }
38}
39
40impl SonConfig {
41 pub(crate) fn compact() -> Self {
43 Self::default()
44 }
45
46 pub(crate) fn pretty() -> Self {
48 Self {
49 pretty: true,
50 indent: 2,
51 }
52 }
53}
54
55pub(crate) fn value_to_son(value: &Value, config: &SonConfig) -> String {
57 let mut buf = String::new();
58 format_value(value, config, 0, &mut buf);
59 buf
60}
61
62fn format_value(value: &Value, config: &SonConfig, depth: usize, buf: &mut String) {
64 match value {
65 Value::Int(n) => {
66 buf.push_str(&n.to_string());
67 }
68 Value::Float(f) => {
69 let s = f.to_string();
70 buf.push_str(&s);
71 if !s.contains('.') && f.is_finite() {
73 buf.push_str(".0");
74 }
75 }
76 Value::Bool(b) => {
77 buf.push_str(if *b { "true" } else { "false" });
78 }
79 Value::String(s) => {
80 format_string(&s.as_str_lossy(), buf);
86 }
87 Value::Symbol(s) => {
88 buf.push(':');
89 buf.push_str(&s.as_str_lossy());
90 }
91 Value::Variant(v) => {
92 format_variant(v, config, depth, buf);
93 }
94 Value::Map(m) => {
95 format_map(m, config, depth, buf);
96 }
97 Value::Quotation { .. } => {
98 buf.push_str("<quotation>");
99 }
100 Value::Closure { .. } => {
101 buf.push_str("<closure>");
102 }
103 Value::Channel(_) => {
104 buf.push_str("<channel>");
105 }
106 Value::WeaveCtx { .. } => {
107 buf.push_str("<weave-ctx>");
108 }
109 }
110}
111
112fn format_string(s: &str, buf: &mut String) {
114 buf.push('"');
115 for c in s.chars() {
116 match c {
117 '"' => buf.push_str("\\\""),
118 '\\' => buf.push_str("\\\\"),
119 '\n' => buf.push_str("\\n"),
120 '\r' => buf.push_str("\\r"),
121 '\t' => buf.push_str("\\t"),
122 '\x08' => buf.push_str("\\b"),
123 '\x0C' => buf.push_str("\\f"),
124 c if c.is_control() => {
125 buf.push_str(&format!("\\u{:04x}", c as u32));
126 }
127 c => buf.push(c),
128 }
129 }
130 buf.push('"');
131}
132
133fn format_variant(v: &VariantData, config: &SonConfig, depth: usize, buf: &mut String) {
135 let is_list = v.tag.as_bytes() == b"List";
139
140 if is_list {
141 format_list(&v.fields, config, depth, buf);
142 } else {
143 buf.push(':');
145 buf.push_str(&v.tag.as_str_lossy());
146
147 let field_count = v.fields.len();
148
149 for field in v.fields.iter() {
150 let child_depth = child_indent(buf, config, depth);
151 format_value(field, config, child_depth, buf);
152 }
153 if config.pretty && !v.fields.is_empty() {
154 newline_at_indent(buf, depth, config);
155 }
156
157 buf.push_str(&format!(" wrap-{}", field_count));
158 }
159}
160
161fn format_list(fields: &[Value], config: &SonConfig, depth: usize, buf: &mut String) {
163 buf.push_str("list-of");
164
165 for field in fields.iter() {
166 let child_depth = child_indent(buf, config, depth);
167 format_value(field, config, child_depth, buf);
168 buf.push_str(" lv");
169 }
170}
171
172fn format_map(map: &HashMap<MapKey, Value>, config: &SonConfig, depth: usize, buf: &mut String) {
174 buf.push_str("map-of");
175
176 if map.is_empty() {
177 return;
178 }
179
180 let mut entries: Vec<_> = map.iter().collect();
182 entries.sort_by(|(k1, _), (k2, _)| {
183 let s1 = map_key_sort_string(k1);
184 let s2 = map_key_sort_string(k2);
185 s1.cmp(&s2)
186 });
187
188 for (key, value) in entries {
189 let child_depth = child_indent(buf, config, depth);
190 format_map_key(key, buf);
191 buf.push(' ');
192 format_value(value, config, child_depth, buf);
193 buf.push_str(" kv");
194 }
195}
196
197fn map_key_sort_string(key: &MapKey) -> String {
199 match key {
200 MapKey::Int(n) => format!("0_{:020}", n), MapKey::Bool(b) => format!("1_{}", b), MapKey::String(s) => format!("2_{}", s.as_str_lossy()), }
204}
205
206fn format_map_key(key: &MapKey, buf: &mut String) {
208 match key {
209 MapKey::Int(n) => buf.push_str(&n.to_string()),
210 MapKey::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
211 MapKey::String(s) => format_string(&s.as_str_lossy(), buf),
212 }
213}
214
215fn push_indent(buf: &mut String, depth: usize, indent_size: usize) {
217 for _ in 0..(depth * indent_size) {
218 buf.push(' ');
219 }
220}
221
222fn newline_at_indent(buf: &mut String, depth: usize, config: &SonConfig) {
224 buf.push('\n');
225 push_indent(buf, depth, config.indent);
226}
227
228fn child_indent(buf: &mut String, config: &SonConfig, depth: usize) -> usize {
232 if config.pretty {
233 newline_at_indent(buf, depth + 1, config);
234 depth + 1
235 } else {
236 buf.push(' ');
237 depth
238 }
239}
240
241#[unsafe(no_mangle)]
252pub unsafe extern "C" fn patch_seq_son_dump(stack: Stack) -> Stack {
253 unsafe { son_dump_impl(stack, false) }
254}
255
256#[unsafe(no_mangle)]
263pub unsafe extern "C" fn patch_seq_son_dump_pretty(stack: Stack) -> Stack {
264 unsafe { son_dump_impl(stack, true) }
265}
266
267unsafe fn son_dump_impl(stack: Stack, pretty: bool) -> Stack {
269 let (rest, value) = unsafe { pop(stack) };
270
271 let config = if pretty {
272 SonConfig::pretty()
273 } else {
274 SonConfig::compact()
275 };
276
277 let result = value_to_son(&value, &config);
278 let result_str = SeqString::from(result);
279
280 unsafe { push(rest, Value::String(result_str)) }
281}
282
283#[cfg(test)]
288mod tests {
289 use super::*;
290 use crate::seqstring::global_string;
291 use std::sync::Arc;
292
293 #[test]
294 fn test_int() {
295 let v = Value::Int(42);
296 assert_eq!(value_to_son(&v, &SonConfig::default()), "42");
297 }
298
299 #[test]
300 fn test_negative_int() {
301 let v = Value::Int(-123);
302 assert_eq!(value_to_son(&v, &SonConfig::default()), "-123");
303 }
304
305 #[test]
306 fn test_float() {
307 let v = Value::Float(2.5);
308 assert_eq!(value_to_son(&v, &SonConfig::default()), "2.5");
309 }
310
311 #[test]
312 fn test_float_whole_number() {
313 let v = Value::Float(42.0);
314 let s = value_to_son(&v, &SonConfig::default());
315 assert!(s.contains('.'), "Float should contain decimal point: {}", s);
316 }
317
318 #[test]
319 fn test_bool_true() {
320 let v = Value::Bool(true);
321 assert_eq!(value_to_son(&v, &SonConfig::default()), "true");
322 }
323
324 #[test]
325 fn test_bool_false() {
326 let v = Value::Bool(false);
327 assert_eq!(value_to_son(&v, &SonConfig::default()), "false");
328 }
329
330 #[test]
331 fn test_string_simple() {
332 let v = Value::String(global_string("hello".to_string()));
333 assert_eq!(value_to_son(&v, &SonConfig::default()), r#""hello""#);
334 }
335
336 #[test]
337 fn test_string_escaping() {
338 let v = Value::String(global_string("hello\nworld".to_string()));
339 assert_eq!(value_to_son(&v, &SonConfig::default()), r#""hello\nworld""#);
340 }
341
342 #[test]
343 fn test_string_quotes() {
344 let v = Value::String(global_string(r#"say "hi""#.to_string()));
345 assert_eq!(value_to_son(&v, &SonConfig::default()), r#""say \"hi\"""#);
346 }
347
348 #[test]
349 fn test_symbol() {
350 let v = Value::Symbol(global_string("my-symbol".to_string()));
351 assert_eq!(value_to_son(&v, &SonConfig::default()), ":my-symbol");
352 }
353
354 #[test]
355 fn test_empty_list() {
356 let list = Value::Variant(Arc::new(VariantData::new(
357 global_string("List".to_string()),
358 vec![],
359 )));
360 assert_eq!(value_to_son(&list, &SonConfig::default()), "list-of");
361 }
362
363 #[test]
364 fn test_list() {
365 let list = Value::Variant(Arc::new(VariantData::new(
366 global_string("List".to_string()),
367 vec![Value::Int(1), Value::Int(2), Value::Int(3)],
368 )));
369 assert_eq!(
370 value_to_son(&list, &SonConfig::default()),
371 "list-of 1 lv 2 lv 3 lv"
372 );
373 }
374
375 #[test]
376 fn test_list_pretty() {
377 let list = Value::Variant(Arc::new(VariantData::new(
378 global_string("List".to_string()),
379 vec![Value::Int(1), Value::Int(2)],
380 )));
381 let expected = "list-of\n 1 lv\n 2 lv";
382 assert_eq!(value_to_son(&list, &SonConfig::pretty()), expected);
383 }
384
385 #[test]
386 fn test_empty_map() {
387 let m: HashMap<MapKey, Value> = HashMap::new();
388 let v = Value::Map(Box::new(m));
389 assert_eq!(value_to_son(&v, &SonConfig::default()), "map-of");
390 }
391
392 #[test]
393 fn test_map() {
394 let mut m = HashMap::new();
395 m.insert(
396 MapKey::String(global_string("key".to_string())),
397 Value::Int(42),
398 );
399 let v = Value::Map(Box::new(m));
400 assert_eq!(
401 value_to_son(&v, &SonConfig::default()),
402 r#"map-of "key" 42 kv"#
403 );
404 }
405
406 #[test]
407 fn test_variant_no_fields() {
408 let v = Value::Variant(Arc::new(VariantData::new(
409 global_string("None".to_string()),
410 vec![],
411 )));
412 assert_eq!(value_to_son(&v, &SonConfig::default()), ":None wrap-0");
413 }
414
415 #[test]
416 fn test_variant_with_fields() {
417 let v = Value::Variant(Arc::new(VariantData::new(
418 global_string("Point".to_string()),
419 vec![Value::Int(10), Value::Int(20)],
420 )));
421 assert_eq!(
422 value_to_son(&v, &SonConfig::default()),
423 ":Point 10 20 wrap-2"
424 );
425 }
426
427 #[test]
428 fn test_variant_pretty() {
429 let v = Value::Variant(Arc::new(VariantData::new(
430 global_string("Point".to_string()),
431 vec![Value::Int(10), Value::Int(20)],
432 )));
433 let expected = ":Point\n 10\n 20\n wrap-2";
434 assert_eq!(value_to_son(&v, &SonConfig::pretty()), expected);
435 }
436
437 #[test]
438 fn test_nested_list_in_map() {
439 let list = Value::Variant(Arc::new(VariantData::new(
440 global_string("List".to_string()),
441 vec![Value::Int(1), Value::Int(2)],
442 )));
443 let mut m = HashMap::new();
444 m.insert(MapKey::String(global_string("items".to_string())), list);
445 let v = Value::Map(Box::new(m));
446 assert_eq!(
447 value_to_son(&v, &SonConfig::default()),
448 r#"map-of "items" list-of 1 lv 2 lv kv"#
449 );
450 }
451
452 #[test]
453 fn test_quotation() {
454 let v = Value::Quotation {
455 wrapper: 0,
456 impl_: 0,
457 };
458 assert_eq!(value_to_son(&v, &SonConfig::default()), "<quotation>");
459 }
460
461 #[test]
462 fn test_closure() {
463 let v = Value::Closure {
464 fn_ptr: 0,
465 env: Arc::new([]),
466 };
467 assert_eq!(value_to_son(&v, &SonConfig::default()), "<closure>");
468 }
469}