1use crate::numfmt;
9use crate::options::Options;
10use crate::quote::quote;
11use crate::value::{Ident, Key, Table, Value};
12use std::collections::HashMap;
13
14const KEYWORDS: &[&str] = &[
17 "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in",
18 "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while",
19];
20
21fn is_keyword(s: &str) -> bool {
22 KEYWORDS.contains(&s)
23}
24
25struct Serializer<'o> {
27 opts: &'o Options,
28 space: &'static str,
29 huge: bool,
30 maxl: usize,
31 maxlen: Option<i64>,
32 numformat: String,
33 iname: String,
34 comm: Option<usize>,
35 seen: HashMap<Ident, String>,
37 sref: Vec<String>,
39 syms: HashMap<String, usize>,
41 symn: usize,
42 fatal_value: Option<String>,
46}
47
48#[derive(Clone, Copy)]
54struct Frame<'a> {
55 name: Option<&'a Key>,
57 indent: Option<&'a str>,
59 insref: Option<&'a str>,
62 path: Option<&'a str>,
64 plainindex: bool,
66 level: usize,
68}
69
70impl<'o> Serializer<'o> {
71 fn new(opts: &'o Options) -> Self {
72 let name = opts.name.clone().unwrap_or_default();
73 let iname = format!("_{name}");
74 let space = if opts.compact { "" } else { " " };
75 Serializer {
76 space,
77 huge: !opts.nohuge,
78 maxl: opts.maxlevel.unwrap_or(usize::MAX),
79 maxlen: opts.maxlength,
80 numformat: opts
81 .numformat
82 .clone()
83 .unwrap_or_else(|| "%.17g".to_string()),
84 sref: vec![format!("local {iname}={{}}")],
85 iname,
86 comm: opts.comment,
87 seen: HashMap::new(),
88 syms: HashMap::new(),
89 symn: 0,
90 fatal_value: None,
91 opts,
92 }
93 }
94
95 fn gensym(&mut self, raw: &str) -> String {
99 let stripped: String = raw.chars().filter(|c| c.is_ascii_alphanumeric()).collect();
100 let remapped = self.remap_runs(&stripped);
101 format!("_{remapped}")
102 }
103
104 fn remap_runs(&mut self, s: &str) -> String {
107 let b = s.as_bytes();
108 let mut out = String::new();
109 let mut i = 0;
110 while i < b.len() {
111 if b[i].is_ascii_digit() {
112 let start = i;
114 i += 1;
115 let mut count = 0;
116 while i < b.len() && b[i].is_ascii_alphanumeric() {
117 i += 1;
118 count += 1;
119 }
120 let run = &s[start..i];
121 if count >= 1 {
122 let n = *self.syms.entry(run.to_string()).or_insert_with(|| {
123 self.symn += 1;
124 self.symn
125 });
126 out.push_str(&n.to_string());
127 } else {
128 out.push_str(run);
129 }
130 } else {
131 out.push(b[i] as char);
132 i += 1;
133 }
134 }
135 out
136 }
137
138 fn safestr(&self, v: &Value) -> String {
140 match v {
141 Value::Number(n) => {
142 if self.huge {
143 if n.is_infinite() {
144 return if *n > 0.0 {
145 "1/0 --[[math.huge]]".to_string()
146 } else {
147 "-1/0 --[[-math.huge]]".to_string()
148 };
149 }
150 if n.is_nan() {
151 return "0/0".to_string();
152 }
153 }
154 numfmt::format(&self.numformat, *n)
155 }
156 Value::Str(s) => quote(s),
157 Value::Bool(b) => b.to_string(),
158 Value::Nil => "nil".to_string(),
159 other => quote(tostring(other).as_bytes()),
162 }
163 }
164
165 fn comment(&self, text: &str, level: usize) -> String {
168 match self.comm {
169 Some(threshold) if level < threshold => format!(" --[[{text}]]"),
170 _ => String::new(),
171 }
172 }
173
174 fn safename(&self, path: Option<&str>, name: Option<&Key>) -> (String, String) {
176 let plain_name = match name {
177 Some(Key::Str(s)) => std::str::from_utf8(s)
178 .ok()
179 .filter(|n| is_plain_ident(n) && !is_keyword(n))
180 .map(str::to_string),
181 _ => None,
182 };
183 let (safe, is_plain) = match &plain_name {
184 Some(n) => (n.clone(), true),
185 None => {
186 let key_val = name.map_or(Value::Str(Vec::new()), Key::to_value);
187 (format!("[{}]", self.safestr(&key_val)), false)
188 }
189 };
190 let joiner = if is_plain && path.is_some() { "." } else { "" };
191 let full = format!("{}{}{}", path.unwrap_or(""), joiner, safe);
192 (full, safe)
193 }
194
195 fn val2str(&mut self, t: &Value, frame: Frame) -> String {
198 let level = frame.level;
199 let (spath, sname) = self.safename(frame.path, frame.name);
200 let tag = self.build_tag(frame.name, &sname, frame.plainindex);
201
202 if let Some(id) = t.ident() {
204 if let Some(prev) = self.seen.get(&id).cloned() {
205 self.sref
206 .push(format!("{spath}{}={}{prev}", self.space, self.space));
207 return format!("{tag}nil{}", self.comment("ref", level));
208 }
209 }
210
211 let replaced = self.apply_metamethods(t, frame.insref.unwrap_or(&spath));
215 let t = replaced.as_ref().unwrap_or(t);
216
217 match t {
218 Value::Table(tab) => self.table_to_str(tab, &tag, &spath, frame),
219 Value::Global(_) => {
220 if let Some(id) = t.ident() {
221 self.seen
222 .insert(id, frame.insref.unwrap_or(&spath).to_string());
223 }
224 format!("{tag}{}", self.globerr(t, level))
225 }
226 Value::Function(f) => {
227 if let Some(id) = t.ident() {
228 self.seen
229 .insert(id, frame.insref.unwrap_or(&spath).to_string());
230 }
231 if self.opts.nocode {
232 return format!(
233 "{tag}function() --[[..skipped..]] end{}",
234 self.comment("", level)
235 );
236 }
237 match &f.bytecode {
238 Some(code) => {
239 let quoted = quote(code);
240 format!(
241 "{tag}((loadstring or load)({quoted},'@serialized')){}",
242 self.comment("", level)
243 )
244 }
245 None => format!("{tag}{}", self.globerr(t, level)),
246 }
247 }
248 _ => format!("{tag}{}", self.safestr(t)),
249 }
250 }
251
252 fn build_tag(&self, name: Option<&Key>, sname: &str, plainindex: bool) -> String {
254 if plainindex {
255 match name {
256 Some(Key::Number(_)) => String::new(),
257 Some(k) => {
258 let raw = tostring(&k.to_value());
259 format!("{raw}{}={}", self.space, self.space)
260 }
261 None => String::new(),
262 }
263 } else if name.is_some() {
264 format!("{sname}{}={}", self.space, self.space)
265 } else {
266 String::new()
267 }
268 }
269
270 fn apply_metamethods(&mut self, t: &Value, seen_path: &str) -> Option<Value> {
275 let Value::Table(tab) = t else {
276 return None;
277 };
278 if self.opts.metatostring == Some(false) {
279 return None;
280 }
281 let ser = tab.serialize_meta();
282 let tos = tab.tostring_meta();
283 if ser.is_none() && tos.is_none() {
284 return None;
285 }
286 let result = ser
288 .and_then(|f| f().ok())
289 .or_else(|| tos.and_then(|f| f().ok()))?;
290 if let Some(id) = t.ident() {
291 self.seen.insert(id, seen_path.to_string());
292 }
293 Some(result)
294 }
295
296 fn globerr(&mut self, v: &Value, level: usize) -> String {
303 if let Value::Global(g) = v {
304 if !g.name.is_empty() {
305 return format!("{}{}", g.name, self.comment(&g.name, level));
306 }
307 }
308 let text = match v {
312 Value::Global(g) if g.name.is_empty() => format!("userdata: 0x{:012x}", g.id),
313 _ => tostring(v),
314 };
315 if !self.opts.fatal {
316 return self.safestr(&Value::Str(text.into_bytes()));
317 }
318 if self.fatal_value.is_none() {
320 self.fatal_value = Some(text.clone());
321 }
322 self.safestr(&Value::Str(text.into_bytes()))
325 }
326
327 fn table_to_str(&mut self, tab: &Table, tag: &str, spath: &str, frame: Frame) -> String {
328 let Frame {
329 indent,
330 insref,
331 level,
332 ..
333 } = frame;
334 if level >= self.maxl {
335 return format!("{tag}{{}}{}", self.comment("maxlvl", level));
336 }
337 let id = Ident::Table(tab.id());
338 self.seen.insert(id, insref.unwrap_or(spath).to_string());
339 if tab.is_empty() {
340 let text = tab_tostring(tab);
341 return format!("{tag}{{}}{}", self.comment(&text, level));
342 }
343 if let Some(ml) = self.maxlen {
344 if ml < 0 {
345 return format!("{tag}{{}}{}", self.comment("maxlen", level));
346 }
347 }
348
349 let entries = tab.entries();
350 if let Some((key, _)) = entries
351 .iter()
352 .find(|(key, _)| matches!(key, Key::Number(n) if n.is_nan()))
353 {
354 self.fatal_value = Some(tostring(&key.to_value()));
355 return String::new();
356 }
357 let border = tab.border();
358 let maxn = match self.opts.maxnum {
359 Some(m) => border.min(m),
360 None => border,
361 };
362
363 let index: HashMap<KeyId, usize> = entries
366 .iter()
367 .enumerate()
368 .filter_map(|(i, (k, _))| key_id(k).map(|id| (id, i)))
369 .collect();
370 let lookup = |key: &Key| -> Option<Value> {
371 key_id(key)
372 .and_then(|id| index.get(&id))
373 .map(|&i| entries[i].1.clone())
374 };
375
376 let mut o: Vec<Key> = (1..=maxn).map(|i| Key::Number(i as f64)).collect();
378 if self.opts.maxnum.is_none() || o.len() < self.opts.maxnum.unwrap() {
379 for (k, _) in &entries {
380 if !is_array_index(k, maxn) {
381 o.push(k.clone());
382 }
383 }
384 }
385 if let Some(m) = self.opts.maxnum {
386 if o.len() > m {
387 o.truncate(m);
388 }
389 }
390 if (self.opts.sortkeys || self.opts.sortfn.is_some()) && o.len() > maxn {
391 self.sort_keys(&mut o, &entries);
392 }
393 let sparse = self.opts.sparse && o.len() > maxn;
394
395 let seen_t = self.seen.get(&Ident::Table(tab.id())).cloned();
397
398 let mut out: Vec<String> = Vec::new();
399 for (n, key) in o.iter().enumerate() {
400 let idx = n + 1;
401 let value = lookup(key);
402 let plainindex = idx <= maxn && !sparse;
403
404 if self.should_skip(key, value.as_ref(), sparse) {
405 continue;
406 }
407
408 let is_complex = matches!(key, Key::Table(_) | Key::Function(_) | Key::Global(_));
409 if is_complex {
410 self.emit_complex_key(tab, key, value, indent);
411 } else {
412 let v = value.unwrap_or(Value::Nil);
413 let text = self.val2str(
414 &v,
415 Frame {
416 name: Some(key),
417 indent,
418 insref: None,
419 path: seen_t.as_deref(),
420 plainindex,
421 level: level + 1,
422 },
423 );
424 let len = text.len() as i64;
425 out.push(text);
426 if let Some(ml) = self.maxlen.as_mut() {
427 *ml -= len;
428 if *ml < 0 {
429 break;
430 }
431 }
432 }
433 }
434
435 self.assemble_table(tab, tag, indent, &out, level)
436 }
437
438 fn emit_complex_key(
441 &mut self,
442 tab: &Table,
443 key: &Key,
444 value: Option<Value>,
445 indent: Option<&str>,
446 ) {
447 let key_val = key.to_value();
448 let key_id = key_val.ident();
449 let key_seen = key_id.as_ref().and_then(|i| self.seen.get(i)).cloned();
450 let key_global = match key {
451 Key::Global(g) => Some(g.name.clone()),
452 _ => None,
453 };
454
455 if key_seen.is_none() && key_global.is_none() {
456 self.sref.push("placeholder".to_string());
457 let idx = self.sref.len() - 1;
458 let sym = self.gensym(&tostring(&key_val));
459 let iname = self.iname.clone();
460 let (sname, _) = self.safename(Some(&iname), Some(&Key::Str(sym.into_bytes())));
461 let stmt = self.val2str(
462 &key_val,
463 Frame {
464 name: Some(&Key::Str(sname.clone().into_bytes())),
465 indent,
466 insref: Some(&sname),
467 path: Some(&iname),
468 plainindex: true,
469 level: 0,
470 },
471 );
472 self.sref[idx] = stmt;
473 }
474
475 self.sref.push("placeholder".to_string());
476 let idx = self.sref.len() - 1;
477 let seen_t = self
478 .seen
479 .get(&Ident::Table(tab.id()))
480 .cloned()
481 .unwrap_or_default();
482 let key_seen_now = key_id.as_ref().and_then(|i| self.seen.get(i)).cloned();
486 let key_ref = key_seen_now
487 .or(key_global)
488 .unwrap_or_else(|| self.gensym(&tostring(&key_val)));
489 let path = format!("{seen_t}[{key_ref}]");
490 let value = value.unwrap_or(Value::Nil);
491 let val_seen = value.ident().and_then(|i| self.seen.get(&i)).cloned();
492 let rhs = match val_seen {
493 Some(p) => p,
494 None => self.val2str(
498 &value,
499 Frame {
500 name: None,
501 indent,
502 insref: Some(&path),
503 path: None,
504 plainindex: false,
505 level: 0,
506 },
507 ),
508 };
509 self.sref[idx] = format!("{path}{}={}{rhs}", self.space, self.space);
510 }
511
512 fn assemble_table(
514 &self,
515 tab: &Table,
516 tag: &str,
517 indent: Option<&str>,
518 out: &[String],
519 level: usize,
520 ) -> String {
521 let prefix = indent.map_or(String::new(), |ind| ind.repeat(level));
522 let head = match indent {
523 Some(ind) => format!("{{\n{prefix}{ind}"),
524 None => "{".to_string(),
525 };
526 let sep = match indent {
527 Some(ind) => format!(",\n{prefix}{ind}"),
528 None => format!(",{}", self.space),
529 };
530 let body = out.join(&sep);
531 let tail = match indent {
532 Some(_) => format!("\n{prefix}}}"),
533 None => "}".to_string(),
534 };
535 let table_text = match &self.opts.custom {
536 Some(f) => f(tag, &head, &body, &tail, level),
537 None => format!("{tag}{head}{body}{tail}"),
538 };
539 let text = tab_tostring(tab);
540 format!("{table_text}{}", self.comment(&text, level))
541 }
542
543 fn should_skip(&self, key: &Key, value: Option<&Value>, sparse: bool) -> bool {
545 if let Some(v) = value {
546 if let Some(ig) = &self.opts.valignore {
547 if let Some(id) = v.ident() {
548 if ig.contains(&id) {
549 return true;
550 }
551 }
552 }
553 if let Some(ig) = &self.opts.valignore_scalar {
554 if ig.iter().any(|x| x == v) {
555 return true;
556 }
557 }
558 }
559 if let Some(allow) = &self.opts.keyallow {
560 if !allow.iter().any(|k| k == key) {
561 return true;
562 }
563 }
564 if let Some(ignore) = &self.opts.keyignore {
565 if ignore.iter().any(|k| k == key) {
566 return true;
567 }
568 }
569 if let Some(tignore) = &self.opts.valtypeignore {
570 let tn = value.map_or("nil", type_name);
571 if tignore.contains(tn) {
572 return true;
573 }
574 }
575 if sparse && value.is_none() {
576 return true;
577 }
578 false
579 }
580
581 fn sort_keys(&self, o: &mut Vec<Key>, entries: &[(Key, Value)]) {
583 if let Some(f) = &self.opts.sortfn {
584 f(o, entries);
587 return;
588 }
589 let len = o.len();
594 o.sort_by_key(|a| sortkey(a, len));
595 }
596}
597
598fn tab_tostring(tab: &Table) -> String {
600 format!("table: 0x{:012x}", tab.id())
601}
602
603fn tostring(v: &Value) -> String {
606 match v {
607 Value::Nil => "nil".to_string(),
608 Value::Bool(b) => b.to_string(),
609 Value::Number(n) => {
610 if n.is_nan() {
611 "nan".to_string()
612 } else if n.is_infinite() {
613 if *n > 0.0 {
614 "inf".to_string()
615 } else {
616 "-inf".to_string()
617 }
618 } else {
619 numfmt::format("%.14g", *n)
620 }
621 }
622 Value::Str(s) => String::from_utf8_lossy(s).into_owned(),
623 Value::Table(t) => tab_tostring(t),
624 Value::Function(f) => format!("function: 0x{:012x}", f.id),
625 Value::Global(g) => g.name.clone(),
626 }
627}
628
629fn type_name(v: &Value) -> &'static str {
631 match v {
632 Value::Nil => "nil",
633 Value::Bool(_) => "boolean",
634 Value::Number(_) => "number",
635 Value::Str(_) => "string",
636 Value::Table(_) => "table",
637 Value::Function(_) => "function",
638 Value::Global(_) => "userdata",
639 }
640}
641
642fn is_plain_ident(s: &str) -> bool {
644 let mut chars = s.chars();
645 match chars.next() {
646 Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
647 _ => return false,
648 }
649 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
650}
651
652fn is_array_index(k: &Key, maxn: usize) -> bool {
654 match k {
655 Key::Number(x) => {
656 let f = *x;
657 f.fract() == 0.0 && f >= 1.0 && f <= maxn as f64
658 }
659 _ => false,
660 }
661}
662
663#[derive(PartialEq, Eq, Hash)]
671enum KeyId {
672 Bool(bool),
673 Number(u64),
674 Str(Vec<u8>),
675 Table(usize),
676 Function(usize),
677 Global(usize),
678}
679
680fn key_id(k: &Key) -> Option<KeyId> {
681 Some(match k {
682 Key::Bool(b) => KeyId::Bool(*b),
683 Key::Number(n) if n.is_nan() => return None,
684 Key::Number(n) => KeyId::Number(if *n == 0.0 { 0.0_f64 } else { *n }.to_bits()),
685 Key::Str(s) => KeyId::Str(s.clone()),
686 Key::Table(t) => KeyId::Table(t.id()),
687 Key::Function(f) => KeyId::Function(f.id),
688 Key::Global(g) => KeyId::Global(g.id),
689 })
690}
691
692fn sortkey(k: &Key, list_len: usize) -> String {
699 let on_filled_slot = match k {
700 Key::Number(x) => x.fract() == 0.0 && *x >= 1.0 && *x <= list_len as f64,
701 _ => false,
702 };
703 let prefix = if on_filled_slot {
704 "0"
705 } else {
706 match k {
707 Key::Number(_) => "a",
708 Key::Str(_) => "b",
709 _ => "z",
710 }
711 };
712 let body = pad_numbers(&key_tostring(k));
713 format!("{prefix}{body}")
714}
715
716fn pad_numbers(s: &str) -> String {
718 let mut out = String::new();
719 let b = s.as_bytes();
720 let mut i = 0;
721 while i < b.len() {
722 if b[i].is_ascii_digit() {
723 let start = i;
724 while i < b.len() && b[i].is_ascii_digit() {
725 i += 1;
726 }
727 let num = &s[start..i];
728 let val: u64 = num.parse().unwrap_or(0);
729 out.push_str(&format!("{val:012}"));
730 } else {
731 out.push(b[i] as char);
732 i += 1;
733 }
734 }
735 out
736}
737
738fn key_tostring(k: &Key) -> String {
740 tostring(&k.to_value())
741}
742
743pub fn serialize(t: &Value, opts: &Options) -> Result<String, SerError> {
753 let mut s = Serializer::new(opts);
754 let name = opts.name.clone();
755 let indent = opts.indent.clone();
756 let sepr = match &indent {
757 Some(_) => "\n".to_string(),
758 None => format!(";{}", s.space),
759 };
760 let top_name = name.as_ref().map(|n| Key::Str(n.clone().into_bytes()));
761 let body = s.val2str(
762 t,
763 Frame {
764 name: top_name.as_ref(),
765 indent: indent.as_deref(),
766 insref: None,
767 path: None,
768 plainindex: false,
769 level: 0,
770 },
771 );
772
773 if let Some(value) = s.fatal_value.take() {
774 return Err(SerError::NotSerializable(value));
775 }
776
777 let tail = if s.sref.len() > 1 {
778 format!("{}{sepr}", s.sref.join(&sepr))
779 } else {
780 String::new()
781 };
782 let warn = if opts.comment.is_some() && s.sref.len() > 1 {
783 format!(
784 "{}--[[incomplete output with shared/self-references skipped]]",
785 s.space
786 )
787 } else {
788 String::new()
789 };
790
791 let result = match name {
792 None => format!("{body}{warn}"),
793 Some(n) => format!("do local {body}{sepr}{tail}return {n}{sepr}end"),
794 };
795 Ok(result)
796}
797
798#[derive(Debug, PartialEq, Eq)]
800pub enum SerError {
801 NotSerializable(String),
804}
805
806impl std::fmt::Display for SerError {
807 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
808 match self {
809 SerError::NotSerializable(value) => write!(f, "Can't serialize {value}"),
810 }
811 }
812}
813
814impl std::error::Error for SerError {}
815
816#[cfg(test)]
817mod tests {
818 use super::SerError;
819 use crate::value::{Key, Table, Value};
820 use crate::{dump, line};
821
822 #[test]
823 fn nan_table_keys_return_error() {
824 let table = Table::new();
825 table.set(Key::Number(f64::NAN), Value::Number(7.0));
826
827 assert_eq!(
828 line(&Value::Table(table.clone())).unwrap_err(),
829 SerError::NotSerializable("nan".to_string())
830 );
831 assert_eq!(
832 dump(&Value::Table(table)).unwrap_err(),
833 SerError::NotSerializable("nan".to_string())
834 );
835 }
836}