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 let border = tab.border();
351 let maxn = match self.opts.maxnum {
352 Some(m) => border.min(m),
353 None => border,
354 };
355
356 let index: HashMap<KeyId, usize> = entries
359 .iter()
360 .enumerate()
361 .filter_map(|(i, (k, _))| key_id(k).map(|id| (id, i)))
362 .collect();
363 let lookup = |key: &Key| -> Option<Value> {
364 key_id(key)
365 .and_then(|id| index.get(&id))
366 .map(|&i| entries[i].1.clone())
367 };
368
369 let mut o: Vec<Key> = (1..=maxn).map(|i| Key::Number(i as f64)).collect();
371 if self.opts.maxnum.is_none() || o.len() < self.opts.maxnum.unwrap() {
372 for (k, _) in &entries {
373 if !is_array_index(k, maxn) {
374 o.push(k.clone());
375 }
376 }
377 }
378 if let Some(m) = self.opts.maxnum {
379 if o.len() > m {
380 o.truncate(m);
381 }
382 }
383 if (self.opts.sortkeys || self.opts.sortfn.is_some()) && o.len() > maxn {
384 self.sort_keys(&mut o, &entries);
385 }
386 let sparse = self.opts.sparse && o.len() > maxn;
387
388 let seen_t = self.seen.get(&Ident::Table(tab.id())).cloned();
390
391 let mut out: Vec<String> = Vec::new();
392 for (n, key) in o.iter().enumerate() {
393 let idx = n + 1;
394 let value = lookup(key);
395 let plainindex = idx <= maxn && !sparse;
396
397 if self.should_skip(key, value.as_ref(), sparse) {
398 continue;
399 }
400
401 let is_complex = matches!(key, Key::Table(_) | Key::Function(_) | Key::Global(_));
402 if is_complex {
403 self.emit_complex_key(tab, key, value, indent);
404 } else {
405 let v = value.unwrap_or(Value::Nil);
406 let text = self.val2str(
407 &v,
408 Frame {
409 name: Some(key),
410 indent,
411 insref: None,
412 path: seen_t.as_deref(),
413 plainindex,
414 level: level + 1,
415 },
416 );
417 let len = text.len() as i64;
418 out.push(text);
419 if let Some(ml) = self.maxlen.as_mut() {
420 *ml -= len;
421 if *ml < 0 {
422 break;
423 }
424 }
425 }
426 }
427
428 self.assemble_table(tab, tag, indent, &out, level)
429 }
430
431 fn emit_complex_key(
434 &mut self,
435 tab: &Table,
436 key: &Key,
437 value: Option<Value>,
438 indent: Option<&str>,
439 ) {
440 let key_val = key.to_value();
441 let key_id = key_val.ident();
442 let key_seen = key_id.as_ref().and_then(|i| self.seen.get(i)).cloned();
443 let key_global = match key {
444 Key::Global(g) => Some(g.name.clone()),
445 _ => None,
446 };
447
448 if key_seen.is_none() && key_global.is_none() {
449 self.sref.push("placeholder".to_string());
450 let idx = self.sref.len() - 1;
451 let sym = self.gensym(&tostring(&key_val));
452 let iname = self.iname.clone();
453 let (sname, _) = self.safename(Some(&iname), Some(&Key::Str(sym.into_bytes())));
454 let stmt = self.val2str(
455 &key_val,
456 Frame {
457 name: Some(&Key::Str(sname.clone().into_bytes())),
458 indent,
459 insref: Some(&sname),
460 path: Some(&iname),
461 plainindex: true,
462 level: 0,
463 },
464 );
465 self.sref[idx] = stmt;
466 }
467
468 self.sref.push("placeholder".to_string());
469 let idx = self.sref.len() - 1;
470 let seen_t = self
471 .seen
472 .get(&Ident::Table(tab.id()))
473 .cloned()
474 .unwrap_or_default();
475 let key_seen_now = key_id.as_ref().and_then(|i| self.seen.get(i)).cloned();
479 let key_ref = key_seen_now
480 .or(key_global)
481 .unwrap_or_else(|| self.gensym(&tostring(&key_val)));
482 let path = format!("{seen_t}[{key_ref}]");
483 let value = value.unwrap_or(Value::Nil);
484 let val_seen = value.ident().and_then(|i| self.seen.get(&i)).cloned();
485 let rhs = match val_seen {
486 Some(p) => p,
487 None => self.val2str(
491 &value,
492 Frame {
493 name: None,
494 indent,
495 insref: Some(&path),
496 path: None,
497 plainindex: false,
498 level: 0,
499 },
500 ),
501 };
502 self.sref[idx] = format!("{path}{}={}{rhs}", self.space, self.space);
503 }
504
505 fn assemble_table(
507 &self,
508 tab: &Table,
509 tag: &str,
510 indent: Option<&str>,
511 out: &[String],
512 level: usize,
513 ) -> String {
514 let prefix = indent.map_or(String::new(), |ind| ind.repeat(level));
515 let head = match indent {
516 Some(ind) => format!("{{\n{prefix}{ind}"),
517 None => "{".to_string(),
518 };
519 let sep = match indent {
520 Some(ind) => format!(",\n{prefix}{ind}"),
521 None => format!(",{}", self.space),
522 };
523 let body = out.join(&sep);
524 let tail = match indent {
525 Some(_) => format!("\n{prefix}}}"),
526 None => "}".to_string(),
527 };
528 let table_text = match &self.opts.custom {
529 Some(f) => f(tag, &head, &body, &tail, level),
530 None => format!("{tag}{head}{body}{tail}"),
531 };
532 let text = tab_tostring(tab);
533 format!("{table_text}{}", self.comment(&text, level))
534 }
535
536 fn should_skip(&self, key: &Key, value: Option<&Value>, sparse: bool) -> bool {
538 if let Some(v) = value {
539 if let Some(ig) = &self.opts.valignore {
540 if let Some(id) = v.ident() {
541 if ig.contains(&id) {
542 return true;
543 }
544 }
545 }
546 if let Some(ig) = &self.opts.valignore_scalar {
547 if ig.iter().any(|x| x == v) {
548 return true;
549 }
550 }
551 }
552 if let Some(allow) = &self.opts.keyallow {
553 if !allow.iter().any(|k| k == key) {
554 return true;
555 }
556 }
557 if let Some(ignore) = &self.opts.keyignore {
558 if ignore.iter().any(|k| k == key) {
559 return true;
560 }
561 }
562 if let Some(tignore) = &self.opts.valtypeignore {
563 let tn = value.map_or("nil", type_name);
564 if tignore.contains(tn) {
565 return true;
566 }
567 }
568 if sparse && value.is_none() {
569 return true;
570 }
571 false
572 }
573
574 fn sort_keys(&self, o: &mut Vec<Key>, entries: &[(Key, Value)]) {
576 if let Some(f) = &self.opts.sortfn {
577 f(o, entries);
580 return;
581 }
582 let len = o.len();
587 o.sort_by_key(|a| sortkey(a, len));
588 }
589}
590
591fn tab_tostring(tab: &Table) -> String {
593 format!("table: 0x{:012x}", tab.id())
594}
595
596fn tostring(v: &Value) -> String {
599 match v {
600 Value::Nil => "nil".to_string(),
601 Value::Bool(b) => b.to_string(),
602 Value::Number(n) => {
603 if n.is_nan() {
604 "nan".to_string()
605 } else if n.is_infinite() {
606 if *n > 0.0 {
607 "inf".to_string()
608 } else {
609 "-inf".to_string()
610 }
611 } else {
612 numfmt::format("%.14g", *n)
613 }
614 }
615 Value::Str(s) => String::from_utf8_lossy(s).into_owned(),
616 Value::Table(t) => tab_tostring(t),
617 Value::Function(f) => format!("function: 0x{:012x}", f.id),
618 Value::Global(g) => g.name.clone(),
619 }
620}
621
622fn type_name(v: &Value) -> &'static str {
624 match v {
625 Value::Nil => "nil",
626 Value::Bool(_) => "boolean",
627 Value::Number(_) => "number",
628 Value::Str(_) => "string",
629 Value::Table(_) => "table",
630 Value::Function(_) => "function",
631 Value::Global(_) => "userdata",
632 }
633}
634
635fn is_plain_ident(s: &str) -> bool {
637 let mut chars = s.chars();
638 match chars.next() {
639 Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
640 _ => return false,
641 }
642 chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
643}
644
645fn is_array_index(k: &Key, maxn: usize) -> bool {
647 match k {
648 Key::Number(x) => {
649 let f = *x;
650 f.fract() == 0.0 && f >= 1.0 && f <= maxn as f64
651 }
652 _ => false,
653 }
654}
655
656#[derive(PartialEq, Eq, Hash)]
664enum KeyId {
665 Bool(bool),
666 Number(u64),
667 Str(Vec<u8>),
668 Table(usize),
669 Function(usize),
670 Global(usize),
671}
672
673fn key_id(k: &Key) -> Option<KeyId> {
674 Some(match k {
675 Key::Bool(b) => KeyId::Bool(*b),
676 Key::Number(n) if n.is_nan() => return None,
677 Key::Number(n) => KeyId::Number(if *n == 0.0 { 0.0_f64 } else { *n }.to_bits()),
678 Key::Str(s) => KeyId::Str(s.clone()),
679 Key::Table(t) => KeyId::Table(t.id()),
680 Key::Function(f) => KeyId::Function(f.id),
681 Key::Global(g) => KeyId::Global(g.id),
682 })
683}
684
685fn sortkey(k: &Key, list_len: usize) -> String {
692 let on_filled_slot = match k {
693 Key::Number(x) => x.fract() == 0.0 && *x >= 1.0 && *x <= list_len as f64,
694 _ => false,
695 };
696 let prefix = if on_filled_slot {
697 "0"
698 } else {
699 match k {
700 Key::Number(_) => "a",
701 Key::Str(_) => "b",
702 _ => "z",
703 }
704 };
705 let body = pad_numbers(&key_tostring(k));
706 format!("{prefix}{body}")
707}
708
709fn pad_numbers(s: &str) -> String {
711 let mut out = String::new();
712 let b = s.as_bytes();
713 let mut i = 0;
714 while i < b.len() {
715 if b[i].is_ascii_digit() {
716 let start = i;
717 while i < b.len() && b[i].is_ascii_digit() {
718 i += 1;
719 }
720 let num = &s[start..i];
721 let val: u64 = num.parse().unwrap_or(0);
722 out.push_str(&format!("{val:012}"));
723 } else {
724 out.push(b[i] as char);
725 i += 1;
726 }
727 }
728 out
729}
730
731fn key_tostring(k: &Key) -> String {
733 tostring(&k.to_value())
734}
735
736pub fn serialize(t: &Value, opts: &Options) -> Result<String, SerError> {
746 let mut s = Serializer::new(opts);
747 let name = opts.name.clone();
748 let indent = opts.indent.clone();
749 let sepr = match &indent {
750 Some(_) => "\n".to_string(),
751 None => format!(";{}", s.space),
752 };
753 let top_name = name.as_ref().map(|n| Key::Str(n.clone().into_bytes()));
754 let body = s.val2str(
755 t,
756 Frame {
757 name: top_name.as_ref(),
758 indent: indent.as_deref(),
759 insref: None,
760 path: None,
761 plainindex: false,
762 level: 0,
763 },
764 );
765
766 if let Some(value) = s.fatal_value.take() {
767 return Err(SerError::NotSerializable(value));
768 }
769
770 let tail = if s.sref.len() > 1 {
771 format!("{}{sepr}", s.sref.join(&sepr))
772 } else {
773 String::new()
774 };
775 let warn = if opts.comment.is_some() && s.sref.len() > 1 {
776 format!(
777 "{}--[[incomplete output with shared/self-references skipped]]",
778 s.space
779 )
780 } else {
781 String::new()
782 };
783
784 let result = match name {
785 None => format!("{body}{warn}"),
786 Some(n) => format!("do local {body}{sepr}{tail}return {n}{sepr}end"),
787 };
788 Ok(result)
789}
790
791#[derive(Debug, PartialEq, Eq)]
793pub enum SerError {
794 NotSerializable(String),
797}
798
799impl std::fmt::Display for SerError {
800 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
801 match self {
802 SerError::NotSerializable(value) => write!(f, "Can't serialize {value}"),
803 }
804 }
805}
806
807impl std::error::Error for SerError {}