1use crate::core::{
12 env::{Env, INTERNED_SYMBOLS},
13 gc::{Context, Rt},
14 object::{NIL, Object, ObjectType},
15};
16use std::collections::HashMap;
17use std::fmt::{self, Display, Write};
18use std::path::Path;
19
20type ObjId = u32;
22
23enum DumpedObject {
26 Nil,
27 Int(i64),
28 Float(f64),
29 String(std::string::String),
30 ByteString(Vec<u8>),
31 Symbol {
32 name: std::string::String,
33 interned: bool,
34 },
35 Cons {
36 car: ObjId,
37 cdr: ObjId,
38 },
39 Vec(Vec<ObjId>),
40 ByteFn {
41 args: u64,
42 depth: usize,
43 codes: Vec<u8>,
44 consts: Vec<ObjId>,
45 },
46 Subr(std::string::String),
50 Record(Vec<ObjId>),
51 HashTable(Vec<(ObjId, ObjId)>),
52 Buffer,
53 CharTable,
54 BigInt(std::string::String),
55 ChannelSender,
56 ChannelReceiver,
57}
58
59struct DumpedSymbol {
61 name: std::string::String,
62 sym_id: ObjId,
63 func_id: Option<ObjId>,
64}
65
66struct DumpedBinding {
68 sym_id: ObjId,
69 val_id: ObjId,
70}
71
72struct DumpState {
73 seen: HashMap<usize, ObjId>,
75 objects: Vec<DumpedObject>,
76 symbols: Vec<DumpedSymbol>,
77 env: Vec<DumpedBinding>,
78}
79
80impl Display for DumpedObject {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 Self::Nil => write!(f, "nil"),
84 Self::Int(x) => write!(f, "int {x}"),
85 Self::Float(x) => write!(f, "float {x}"),
86 Self::String(s) => write!(f, "string \"{}\"", escape_str(s)),
87 Self::ByteString(bytes) => {
88 write!(f, "bytestring #")?;
89 for b in bytes {
90 write!(f, "{b:02x}")?;
91 }
92 Ok(())
93 }
94 Self::Symbol { name, interned } => {
95 write!(f, "symbol \"{}\" interned={interned}", escape_str(name))
96 }
97 Self::Cons { car, cdr } => write!(f, "cons car=@{car} cdr=@{cdr}"),
98 Self::Vec(elems) => {
99 write!(f, "vec [")?;
100 fmt_id_list(f, elems)?;
101 write!(f, "]")
102 }
103 Self::ByteFn { args, depth, codes, consts } => {
104 write!(f, "bytefn args={args} depth={depth} codes=#")?;
105 for b in codes {
106 write!(f, "{b:02x}")?;
107 }
108 write!(f, " consts=[")?;
109 fmt_id_list(f, consts)?;
110 write!(f, "]")
111 }
112 Self::Subr(name) => write!(f, "subr \"{}\"", escape_str(name)),
113 Self::Record(elems) => {
114 write!(f, "record [")?;
115 fmt_id_list(f, elems)?;
116 write!(f, "]")
117 }
118 Self::HashTable(entries) => {
119 write!(f, "hashtable [")?;
120 for (i, (k, v)) in entries.iter().enumerate() {
121 if i > 0 {
122 write!(f, " ")?;
123 }
124 write!(f, "{k}:{v}")?;
125 }
126 write!(f, "]")
127 }
128 Self::Buffer => write!(f, "buffer <opaque>"),
129 Self::CharTable => write!(f, "chartable <opaque>"),
130 Self::BigInt(s) => write!(f, "bigint {s}"),
131 Self::ChannelSender => write!(f, "channel-sender <opaque>"),
132 Self::ChannelReceiver => write!(f, "channel-receiver <opaque>"),
133 }
134 }
135}
136
137fn fmt_id_list(f: &mut fmt::Formatter<'_>, ids: &[ObjId]) -> fmt::Result {
138 for (i, id) in ids.iter().enumerate() {
139 if i > 0 {
140 write!(f, " ")?;
141 }
142 write!(f, "@{id}")?;
143 }
144 Ok(())
145}
146
147impl DumpState {
148 fn new() -> Self {
149 Self { seen: HashMap::new(), objects: Vec::new(), symbols: Vec::new(), env: Vec::new() }
150 }
151
152 fn dump_object(&mut self, obj: Object) -> ObjId {
155 let addr = obj.into_raw().addr();
156
157 if obj.ptr_eq(NIL) {
159 if !self.seen.contains_key(&addr) {
160 let id = self.alloc_id(DumpedObject::Nil);
161 self.seen.insert(addr, id);
162 }
163 return self.seen[&addr];
164 }
165
166 if let Some(&id) = self.seen.get(&addr) {
168 return id;
169 }
170
171 let id = self.alloc_id(DumpedObject::Nil);
175 self.seen.insert(addr, id);
176
177 let dumped = match obj.untag() {
178 ObjectType::Int(x) => DumpedObject::Int(x),
179 ObjectType::Float(x) => DumpedObject::Float(**x),
180 ObjectType::String(s) => DumpedObject::String(s.inner().to_owned()),
181 ObjectType::ByteString(s) => DumpedObject::ByteString(s.to_vec()),
182 ObjectType::Symbol(sym) => {
183 DumpedObject::Symbol { name: sym.name().to_owned(), interned: sym.interned() }
184 }
185 ObjectType::Cons(cons) => {
186 let car = self.dump_object(cons.car());
187 let cdr = self.dump_object(cons.cdr());
188 DumpedObject::Cons { car, cdr }
189 }
190 ObjectType::Vec(vec) => {
191 let elems = vec.iter().map(|cell| self.dump_object(cell.get())).collect();
192 DumpedObject::Vec(elems)
193 }
194 ObjectType::ByteFn(bf) => {
195 let consts = bf.consts().iter().map(|c| self.dump_object(*c)).collect();
198 DumpedObject::ByteFn {
199 args: bf.args.into_arg_spec(),
200 depth: bf.depth,
201 codes: bf.codes().to_vec(),
202 consts,
203 }
204 }
205 ObjectType::SubrFn(subr) => DumpedObject::Subr(subr.name.to_owned()),
206 ObjectType::HashTable(ht) => {
207 let entries: Vec<(ObjId, ObjId)> = (0..ht.len())
208 .filter_map(|i| ht.get_index(i))
209 .map(|(k, v)| (self.dump_object(k), self.dump_object(v)))
210 .collect();
211 DumpedObject::HashTable(entries)
212 }
213 ObjectType::Record(rec) => {
214 let elems = rec.iter().map(|cell| self.dump_object(cell.get())).collect();
215 DumpedObject::Record(elems)
216 }
217 ObjectType::Buffer(_) => DumpedObject::Buffer,
218 ObjectType::CharTable(_) => DumpedObject::CharTable,
219 ObjectType::BigInt(n) => DumpedObject::BigInt(n.to_string()),
220 ObjectType::ChannelSender(_) => DumpedObject::ChannelSender,
221 ObjectType::ChannelReceiver(_) => DumpedObject::ChannelReceiver,
222 };
223
224 self.objects[id as usize] = dumped;
226 id
227 }
228
229 fn alloc_id(&mut self, placeholder: DumpedObject) -> ObjId {
230 let id = self.objects.len() as ObjId;
231 self.objects.push(placeholder);
232 id
233 }
234
235 fn dump_symbols(&mut self, cx: &Context) {
237 let map = INTERNED_SYMBOLS.lock().unwrap();
238 for (name, sym) in map.iter() {
239 let sym_obj: Object = sym.into();
240 let sym_id = self.dump_object(sym_obj);
241 let func_id = if sym.has_func() {
242 let func = sym.func(cx).unwrap();
243 Some(self.dump_object(func.as_obj()))
244 } else {
245 None
246 };
247 self.symbols.push(DumpedSymbol { name: name.to_owned(), sym_id, func_id });
248 }
249 }
250
251 fn dump_env(&mut self, env: &Rt<Env>, _cx: &Context) {
253 for (sym_slot, val_slot) in env.vars.iter() {
254 let sym_obj: Object = (**sym_slot).into();
257 let val: Object = **val_slot;
258 let sym_id = self.dump_object(sym_obj);
259 let val_id = self.dump_object(val);
260 self.env.push(DumpedBinding { sym_id, val_id });
261 }
262 }
263
264 fn into_output(self) -> std::string::String {
265 let mut out = std::string::String::new();
266 writeln!(out, ".HEADER").unwrap();
267 writeln!(out, " version 1").unwrap();
268 writeln!(out, " objects {}", self.objects.len()).unwrap();
269 writeln!(out).unwrap();
270
271 writeln!(out, ".OBJECTS").unwrap();
272 for (id, obj) in self.objects.iter().enumerate() {
273 writeln!(out, " @{id} {obj}").unwrap();
274 }
275 writeln!(out).unwrap();
276
277 writeln!(out, ".SYMBOLS").unwrap();
278 for sym in &self.symbols {
279 let escaped = escape_str(&sym.name);
280 match sym.func_id {
281 Some(fid) => writeln!(out, " \"{escaped}\" -> @{} func=@{fid}", sym.sym_id),
282 None => writeln!(out, " \"{escaped}\" -> @{}", sym.sym_id),
283 }
284 .unwrap();
285 }
286 writeln!(out).unwrap();
287
288 writeln!(out, ".ENV").unwrap();
289 for b in &self.env {
290 writeln!(out, " @{} = @{}", b.sym_id, b.val_id).unwrap();
291 }
292 out
293 }
294}
295
296fn escape_str(s: &str) -> std::string::String {
297 let mut out = std::string::String::with_capacity(s.len());
298 for c in s.chars() {
299 match c {
300 '"' => out.push_str("\\\""),
301 '\\' => out.push_str("\\\\"),
302 '\n' => out.push_str("\\n"),
303 '\r' => out.push_str("\\r"),
304 '\t' => out.push_str("\\t"),
305 c => out.push(c),
306 }
307 }
308 out
309}
310
311pub(crate) fn dump_to_file(path: &Path, env: &Rt<Env>, cx: &Context) -> Result<(), std::io::Error> {
313 let mut state = DumpState::new();
314 state.dump_symbols(cx);
315 state.dump_env(env, cx);
316 let output = state.into_output();
317 std::fs::write(path, output)
318}
319
320fn subr_registry() -> HashMap<&'static str, &'static crate::core::object::SubrFn> {
325 use crate::core::env::sym;
326 let mut map = HashMap::new();
327 for subr in sym::SUBR_DEFS.iter() {
328 map.insert(subr.name, *subr);
329 }
330 map
331}
332
333struct DumpFile {
337 objects: Vec<DumpedObject>,
338 symbols: Vec<DumpedSymbol>,
339 env: Vec<DumpedBinding>,
340}
341
342fn parse_dump(input: &str) -> Result<DumpFile, String> {
343 let mut objects: Vec<Option<DumpedObject>> = Vec::new();
344 let mut symbols = Vec::new();
345 let mut env = Vec::new();
346 let mut section = "";
347
348 for line in input.lines() {
349 let line = line.trim();
350 if line.is_empty() {
351 continue;
352 }
353 if line.starts_with('.') {
354 section = match line {
355 ".HEADER" => "header",
356 ".OBJECTS" => "objects",
357 ".SYMBOLS" => "symbols",
358 ".ENV" => "env",
359 _ => return Err(format!("unknown section: {line}")),
360 };
361 continue;
362 }
363 match section {
364 "header" => {} "objects" => {
366 let (id, obj) = parse_object_line(line)?;
367 if id as usize >= objects.len() {
369 objects.resize_with(id as usize + 1, || None);
370 }
371 objects[id as usize] = Some(obj);
372 }
373 "symbols" => symbols.push(parse_symbol_line(line)?),
374 "env" => env.push(parse_binding_line(line)?),
375 _ => {}
376 }
377 }
378
379 let objects = objects
380 .into_iter()
381 .enumerate()
382 .map(|(i, o)| o.ok_or_else(|| format!("missing object @{i}")))
383 .collect::<Result<Vec<_>, _>>()?;
384
385 Ok(DumpFile { objects, symbols, env })
386}
387
388fn parse_object_line(line: &str) -> Result<(ObjId, DumpedObject), String> {
390 let line = line.strip_prefix('@').ok_or("expected @")?;
391 let (id_str, rest) = line.split_once(' ').ok_or("expected space after id")?;
392 let id: ObjId = id_str.parse().map_err(|e| format!("bad id: {e}"))?;
393
394 let obj = if rest == "nil" {
395 DumpedObject::Nil
396 } else if let Some(val) = rest.strip_prefix("int ") {
397 DumpedObject::Int(val.parse().map_err(|e| format!("bad int: {e}"))?)
398 } else if let Some(val) = rest.strip_prefix("float ") {
399 DumpedObject::Float(val.parse().map_err(|e| format!("bad float: {e}"))?)
400 } else if let Some(val) = rest.strip_prefix("string ") {
401 DumpedObject::String(parse_quoted_string(val)?)
402 } else if let Some(val) = rest.strip_prefix("bytestring #") {
403 DumpedObject::ByteString(parse_hex_bytes(val)?)
404 } else if let Some(val) = rest.strip_prefix("symbol ") {
405 let (name, rest) = parse_quoted_string_rest(val)?;
406 let interned = rest
407 .strip_prefix(" interned=")
408 .ok_or("expected interned=")?
409 .parse()
410 .map_err(|e| format!("bad bool: {e}"))?;
411 DumpedObject::Symbol { name, interned }
412 } else if let Some(val) = rest.strip_prefix("cons ") {
413 let car = parse_field_ref(val, "car=@")?;
414 let cdr_start = val.find("cdr=@").ok_or("expected cdr=")?;
415 let cdr = parse_ref(&val[cdr_start + "cdr=".len()..])?;
416 DumpedObject::Cons { car, cdr }
417 } else if let Some(val) = rest.strip_prefix("vec [") {
418 let inner = val.strip_suffix(']').ok_or("expected ]")?;
419 DumpedObject::Vec(parse_ref_list(inner)?)
420 } else if let Some(val) = rest.strip_prefix("bytefn ") {
421 parse_bytefn(val)?
422 } else if let Some(val) = rest.strip_prefix("subr ") {
423 DumpedObject::Subr(parse_quoted_string(val)?)
424 } else if let Some(val) = rest.strip_prefix("record [") {
425 let inner = val.strip_suffix(']').ok_or("expected ]")?;
426 DumpedObject::Record(parse_ref_list(inner)?)
427 } else if let Some(val) = rest.strip_prefix("bigint ") {
428 DumpedObject::BigInt(val.to_owned())
429 } else if let Some(val) = rest.strip_prefix("hashtable [") {
430 let inner = val.strip_suffix(']').ok_or("expected ]")?;
431 let entries = if inner.trim().is_empty() {
432 Vec::new()
433 } else {
434 inner
435 .split_whitespace()
436 .map(|pair| {
437 let (k, v) = pair.split_once(':').ok_or("expected k:v in hashtable")?;
438 Ok((
439 k.trim_start_matches('@').parse::<ObjId>().map_err(|e| e.to_string())?,
440 v.trim_start_matches('@').parse::<ObjId>().map_err(|e| e.to_string())?,
441 ))
442 })
443 .collect::<Result<Vec<_>, String>>()?
444 };
445 DumpedObject::HashTable(entries)
446 } else if rest.starts_with("buffer") {
447 DumpedObject::Buffer
448 } else if rest.starts_with("chartable") {
449 DumpedObject::CharTable
450 } else if rest.starts_with("channel-sender") {
451 DumpedObject::ChannelSender
452 } else if rest.starts_with("channel-receiver") {
453 DumpedObject::ChannelReceiver
454 } else {
455 return Err(format!("unknown object type: {rest}"));
456 };
457
458 Ok((id, obj))
459}
460
461fn parse_bytefn(val: &str) -> Result<DumpedObject, String> {
462 let args_str = extract_field(val, "args=", ' ')?;
464 let args: u64 = args_str.parse().map_err(|e| format!("bad args: {e}"))?;
465 let depth_str = extract_field(val, "depth=", ' ')?;
466 let depth: usize = depth_str.parse().map_err(|e| format!("bad depth: {e}"))?;
467 let codes_start = val.find("codes=#").ok_or("expected codes=#")? + 7;
468 let codes_end = val[codes_start..].find(' ').map_or(val.len(), |i| codes_start + i);
469 let codes = parse_hex_bytes(&val[codes_start..codes_end])?;
470 let consts_start = val.find("consts=[").ok_or("expected consts=[")? + 8;
471 let consts_end = val[consts_start..].find(']').ok_or("expected ]")? + consts_start;
472 let consts = parse_ref_list(&val[consts_start..consts_end])?;
473 Ok(DumpedObject::ByteFn { args, depth, codes, consts })
474}
475
476fn parse_quoted_string(s: &str) -> Result<std::string::String, String> {
478 parse_quoted_string_rest(s).map(|(s, _)| s)
479}
480
481fn parse_quoted_string_rest(s: &str) -> Result<(std::string::String, &str), String> {
483 let s = s.strip_prefix('"').ok_or("expected opening quote")?;
484 let mut out = std::string::String::new();
485 let mut chars = s.char_indices();
486 while let Some((i, c)) = chars.next() {
487 match c {
488 '"' => return Ok((out, &s[i + 1..])),
489 '\\' => match chars.next() {
490 Some((_, 'n')) => out.push('\n'),
491 Some((_, 'r')) => out.push('\r'),
492 Some((_, 't')) => out.push('\t'),
493 Some((_, '\\')) => out.push('\\'),
494 Some((_, '"')) => out.push('"'),
495 Some((_, c)) => {
496 out.push('\\');
497 out.push(c);
498 }
499 None => return Err("unexpected end of string".into()),
500 },
501 c => out.push(c),
502 }
503 }
504 Err("unterminated string".into())
505}
506
507fn parse_hex_bytes(s: &str) -> Result<Vec<u8>, String> {
508 (0..s.len())
509 .step_by(2)
510 .map(|i| {
511 u8::from_str_radix(s.get(i..i + 2).ok_or("odd hex length")?, 16)
512 .map_err(|e| format!("bad hex: {e}"))
513 })
514 .collect()
515}
516
517fn parse_ref(s: &str) -> Result<ObjId, String> {
519 let s = s.trim();
520 let digits = s.strip_prefix('@').ok_or_else(|| format!("expected @, got: {s}"))?;
521 let end = digits.find(|c: char| !c.is_ascii_digit()).unwrap_or(digits.len());
523 digits[..end].parse().map_err(|e| format!("bad ref: {e}"))
524}
525
526fn parse_field_ref(s: &str, prefix: &str) -> Result<ObjId, String> {
528 let start = s.find(prefix).ok_or_else(|| format!("expected {prefix}"))?;
529 parse_ref(&s[start + prefix.len() - 1..]) }
531
532fn parse_ref_list(s: &str) -> Result<Vec<ObjId>, String> {
534 let s = s.trim();
535 if s.is_empty() {
536 return Ok(Vec::new());
537 }
538 s.split_whitespace().map(parse_ref).collect()
539}
540
541fn extract_field<'a>(s: &'a str, key: &str, delim: char) -> Result<&'a str, String> {
543 let start = s.find(key).ok_or_else(|| format!("expected {key}"))? + key.len();
544 let end = s[start..].find(delim).map_or(s.len(), |i| start + i);
545 Ok(&s[start..end])
546}
547
548fn parse_symbol_line(line: &str) -> Result<DumpedSymbol, String> {
550 let (name, rest) = parse_quoted_string_rest(line)?;
551 let rest = rest.strip_prefix(" -> ").ok_or("expected -> ")?;
552 let sym_id = parse_ref(rest)?;
553 let func_id = if let Some(fpos) = rest.find("func=@") {
554 Some(parse_ref(&rest[fpos + "func=".len()..])?)
555 } else {
556 None
557 };
558 Ok(DumpedSymbol { name, sym_id, func_id })
559}
560
561fn parse_binding_line(line: &str) -> Result<DumpedBinding, String> {
563 let (left, right) = line.split_once(" = ").ok_or("expected = ")?;
564 Ok(DumpedBinding { sym_id: parse_ref(left)?, val_id: parse_ref(right)? })
565}
566
567use crate::core::{
585 cons::Cons,
586 env::intern,
587 object::{FnArgs, HashTable, IntoObject, Symbol},
588};
589use rune_core::hashmap::IndexMap;
590
591pub(crate) fn load_dump(path: &Path, env: &mut Rt<Env>, cx: &mut Context) -> Result<(), String> {
592 let input = std::fs::read_to_string(path).map_err(|e| format!("read error: {e}"))?;
593 let dump = parse_dump(&input)?;
594 let subrs = subr_registry();
595
596 let mut table: Vec<Object> = Vec::with_capacity(dump.objects.len());
598
599 for obj in &dump.objects {
600 let live: Object = match obj {
601 DumpedObject::Nil => NIL,
602 DumpedObject::Int(x) => cx.add(*x),
603 DumpedObject::Float(x) => cx.add(*x),
604 DumpedObject::String(s) => cx.add(s.as_str()),
605 DumpedObject::ByteString(b) => cx.add(b.clone()),
606 DumpedObject::Symbol { name, interned } => {
607 if *interned {
608 let sym = intern(name, cx);
611 let obj: Object = sym.into();
612 obj
613 } else {
614 let sym = Symbol::new_uninterned(name, cx);
615 let obj: Object = sym.into();
616 obj
617 }
618 }
619 DumpedObject::Cons { .. } => {
621 let cons = Cons::new(NIL, NIL, cx);
622 let obj: Object = cons.into();
623 obj
624 }
625 DumpedObject::Vec(elems) => {
627 let nils: Vec<Object> = vec![NIL; elems.len()];
628 cx.add(nils)
629 }
630 DumpedObject::ByteFn { args, depth, codes, consts } => {
631 let nils: Vec<Object> = vec![NIL; consts.len()];
633 let const_vec: crate::core::object::Gc<&crate::core::object::LispVec> =
634 nils.into_obj(cx);
635 let fn_args = FnArgs::from_arg_spec(*args as i64)
636 .map_err(|e| format!("bad arg spec: {e}"))?;
637 let bytefn = unsafe {
638 crate::core::object::ByteFn::make(codes, const_vec.untag(), fn_args, *depth)
639 };
640 let obj: Object = bytefn.into_obj(cx).into();
641 obj
642 }
643 DumpedObject::Subr(name) => {
644 let subr =
646 subrs.get(name.as_str()).ok_or_else(|| format!("unknown subr: {name}"))?;
647 let obj: Object = (*subr).into();
648 obj
649 }
650 DumpedObject::Record(elems) => {
651 let nils: Vec<Object> = vec![NIL; elems.len()];
652 let mut gvec = cx.vec_with_capacity(nils.len());
653 gvec.extend_from_slice(&nils);
654 let builder = crate::core::object::RecordBuilder(gvec);
655 let obj: Object = builder.into_obj(cx).into();
656 obj
657 }
658 DumpedObject::BigInt(s) => {
659 let n: num_bigint::BigInt = s.parse().map_err(|e| format!("bad bigint: {e}"))?;
660 cx.add(n)
661 }
662 DumpedObject::HashTable(_entries) => {
664 let ht: HashTable = IndexMap::default();
665 let obj: Object = ht.into_obj(cx).into();
666 obj
667 }
668 DumpedObject::Buffer
670 | DumpedObject::CharTable
671 | DumpedObject::ChannelSender
672 | DumpedObject::ChannelReceiver => NIL,
673 };
674 table.push(live);
675 }
676
677 for (id, obj) in dump.objects.iter().enumerate() {
679 match obj {
680 DumpedObject::Cons { car, cdr } => {
681 let cons = table[id].untag();
682 if let ObjectType::Cons(c) = cons {
683 c.set_car(table[*car as usize]).map_err(|e| e.to_string())?;
685 c.set_cdr(table[*cdr as usize]).map_err(|e| e.to_string())?;
686 }
687 }
688 DumpedObject::Vec(elems) => {
689 if let ObjectType::Vec(v) = table[id].untag() {
690 let cells = v.try_mut().map_err(|e| e.to_string())?;
691 for (i, elem_id) in elems.iter().enumerate() {
692 cells[i].set(table[*elem_id as usize]);
693 }
694 }
695 }
696 DumpedObject::ByteFn { consts, .. } => {
697 if let ObjectType::ByteFn(bf) = table[id].untag() {
698 let cells = bf.consts_mut().map_err(|e| e.to_string())?;
701 for (i, c_id) in consts.iter().enumerate() {
702 cells[i].set(table[*c_id as usize]);
703 }
704 }
705 }
706 DumpedObject::Record(elems) => {
707 if let ObjectType::Record(r) = table[id].untag() {
708 let cells = r.try_mut().map_err(|e| e.to_string())?;
709 for (i, elem_id) in elems.iter().enumerate() {
710 cells[i].set(table[*elem_id as usize]);
711 }
712 }
713 }
714 DumpedObject::HashTable(entries) => {
715 if let ObjectType::HashTable(ht) = table[id].untag() {
716 for (k_id, v_id) in entries {
717 ht.insert(table[*k_id as usize], table[*v_id as usize]);
718 }
719 }
720 }
721
722 DumpedObject::Nil
724 | DumpedObject::Int(_)
725 | DumpedObject::Float(_)
726 | DumpedObject::String(_)
727 | DumpedObject::ByteString(_)
728 | DumpedObject::Symbol { .. }
729 | DumpedObject::Subr(_)
730 | DumpedObject::BigInt(_) => {}
731
732 DumpedObject::Buffer
735 | DumpedObject::CharTable
736 | DumpedObject::ChannelSender
737 | DumpedObject::ChannelReceiver => {}
738 }
739 }
740
741 {
746 let map = INTERNED_SYMBOLS.lock().unwrap();
747 for sym_entry in &dump.symbols {
748 if let Some(func_id) = sym_entry.func_id {
749 let func_obj = table[func_id as usize];
750 if let ObjectType::SubrFn(_) = func_obj.untag() {
754 continue;
755 }
756 if let Some(sym) = map.get(&sym_entry.name) {
757 let func: crate::core::object::Function =
758 unsafe { crate::core::object::Gc::from_raw(func_obj.into_raw()) };
759 map.set_func(sym, func).map_err(|e| e.to_string())?;
761 }
762 }
763 }
764 }
765
766 for binding in &dump.env {
768 let sym_obj = table[binding.sym_id as usize];
769 let val_obj = table[binding.val_id as usize];
770 if let ObjectType::Symbol(sym) = sym_obj.untag() {
771 env.vars.insert(sym, val_obj);
772 }
773 }
774
775 Ok(())
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781
782 #[test]
784 fn test_escape_roundtrip_plain() {
785 let s = "hello world";
786 assert_eq!(parse_quoted_string(&format!("\"{}\"", escape_str(s))).unwrap(), s);
787 }
788
789 #[test]
790 fn test_escape_roundtrip_empty() {
791 assert_eq!(parse_quoted_string(&format!("\"{}\"", escape_str(""))).unwrap(), "");
792 }
793
794 #[test]
795 fn test_parse_quoted_string_no_opening_quote() {
796 assert!(parse_quoted_string("no quote").is_err());
797 }
798
799 #[test]
800 fn test_parse_quoted_string_unterminated() {
801 assert!(parse_quoted_string("\"unterminated").is_err());
802 }
803
804 #[test]
806 fn test_parse_hex_bytes_valid() {
807 assert_eq!(parse_hex_bytes("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
808 }
809
810 #[test]
811 fn test_parse_hex_bytes_empty() {
812 assert_eq!(parse_hex_bytes("").unwrap(), Vec::<u8>::new());
813 }
814
815 #[test]
816 fn test_parse_hex_bytes_odd_length() {
817 assert!(parse_hex_bytes("abc").is_err());
818 }
819
820 #[test]
822 fn test_parse_ref_valid() {
823 assert_eq!(parse_ref("@42").unwrap(), 42);
824 assert_eq!(parse_ref("@0").unwrap(), 0);
825 assert_eq!(parse_ref(" @7 ").unwrap(), 7);
826 }
827
828 #[test]
829 fn test_parse_ref_no_at() {
830 assert!(parse_ref("42").is_err());
831 }
832
833 #[test]
835 fn test_parse_ref_list() {
836 assert_eq!(parse_ref_list("@1 @2 @3").unwrap(), vec![1, 2, 3]);
837 assert_eq!(parse_ref_list("").unwrap(), Vec::<ObjId>::new());
838 assert_eq!(parse_ref_list(" ").unwrap(), Vec::<ObjId>::new());
839 }
840
841 #[test]
843 fn test_parse_nil() {
844 let (id, obj) = parse_object_line("@0 nil").unwrap();
845 assert_eq!(id, 0);
846 assert!(matches!(obj, DumpedObject::Nil));
847 }
848
849 #[test]
850 fn test_parse_int() {
851 let (id, obj) = parse_object_line("@1 int 42").unwrap();
852 assert_eq!(id, 1);
853 assert!(matches!(obj, DumpedObject::Int(42)));
854 }
855
856 #[test]
857 fn test_parse_negative_int() {
858 let (_, obj) = parse_object_line("@2 int -7").unwrap();
859 assert!(matches!(obj, DumpedObject::Int(-7)));
860 }
861
862 #[test]
863 #[allow(clippy::approx_constant)]
864 fn test_parse_float() {
865 let (_, obj) = parse_object_line("@3 float 3.14").unwrap();
866 if let DumpedObject::Float(v) = obj {
867 assert!((v - 3.14).abs() < f64::EPSILON);
868 } else {
869 panic!("expected Float");
870 }
871 }
872
873 #[test]
874 fn test_parse_string() {
875 let (_, obj) = parse_object_line(r#"@4 string "hello""#).unwrap();
876 assert!(matches!(obj, DumpedObject::String(s) if s == "hello"));
877 }
878
879 #[test]
880 fn test_parse_string_with_escapes() {
881 let (_, obj) = parse_object_line(r#"@5 string "line\none""#).unwrap();
882 assert!(matches!(obj, DumpedObject::String(s) if s == "line\none"));
883 }
884
885 #[test]
886 fn test_parse_bytestring() {
887 let (_, obj) = parse_object_line("@6 bytestring #ff00ab").unwrap();
888 assert!(matches!(obj, DumpedObject::ByteString(b) if b == vec![0xff, 0x00, 0xab]));
889 }
890
891 #[test]
892 fn test_parse_symbol() {
893 let (_, obj) = parse_object_line(r#"@7 symbol "foo" interned=true"#).unwrap();
894 assert!(
895 matches!(obj, DumpedObject::Symbol { name, interned } if name == "foo" && interned)
896 );
897 }
898
899 #[test]
900 fn test_parse_cons() {
901 let (_, obj) = parse_object_line("@8 cons car=@1 cdr=@2").unwrap();
902 assert!(matches!(obj, DumpedObject::Cons { car: 1, cdr: 2 }));
903 }
904
905 #[test]
906 fn test_parse_vec() {
907 let (_, obj) = parse_object_line("@9 vec [@1 @2 @3]").unwrap();
908 assert!(matches!(obj, DumpedObject::Vec(v) if v == vec![1, 2, 3]));
909 }
910
911 #[test]
912 fn test_parse_vec_empty() {
913 let (_, obj) = parse_object_line("@10 vec []").unwrap();
914 assert!(matches!(obj, DumpedObject::Vec(v) if v.is_empty()));
915 }
916
917 #[test]
918 fn test_parse_subr() {
919 let (_, obj) = parse_object_line(r#"@11 subr "car""#).unwrap();
920 assert!(matches!(obj, DumpedObject::Subr(s) if s == "car"));
921 }
922
923 #[test]
924 fn test_parse_record() {
925 let (_, obj) = parse_object_line("@12 record [@5 @6]").unwrap();
926 assert!(matches!(obj, DumpedObject::Record(v) if v == vec![5, 6]));
927 }
928
929 #[test]
930 fn test_parse_hashtable() {
931 let (_, obj) = parse_object_line("@13 hashtable [1:2 3:4]").unwrap();
932 assert!(matches!(obj, DumpedObject::HashTable(e) if e == vec![(1,2),(3,4)]));
933 }
934
935 #[test]
936 fn test_parse_hashtable_empty() {
937 let (_, obj) = parse_object_line("@14 hashtable []").unwrap();
938 assert!(matches!(obj, DumpedObject::HashTable(e) if e.is_empty()));
939 }
940
941 #[test]
942 fn test_parse_bigint() {
943 let (_, obj) = parse_object_line("@15 bigint 99999999999999999999").unwrap();
944 assert!(matches!(obj, DumpedObject::BigInt(s) if s == "99999999999999999999"));
945 }
946
947 #[test]
948 fn test_parse_opaque_types() {
949 assert!(matches!(
950 parse_object_line("@16 buffer <opaque>").unwrap().1,
951 DumpedObject::Buffer
952 ));
953 assert!(matches!(
954 parse_object_line("@17 chartable <opaque>").unwrap().1,
955 DumpedObject::CharTable
956 ));
957 assert!(matches!(
958 parse_object_line("@18 channel-sender <opaque>").unwrap().1,
959 DumpedObject::ChannelSender
960 ));
961 assert!(matches!(
962 parse_object_line("@19 channel-receiver <opaque>").unwrap().1,
963 DumpedObject::ChannelReceiver
964 ));
965 }
966
967 #[test]
968 fn test_parse_bytefn() {
969 let (_, obj) =
970 parse_object_line("@20 bytefn args=0 depth=5 codes=#0102 consts=[@1 @2]").unwrap();
971 if let DumpedObject::ByteFn { args, depth, codes, consts } = obj {
972 assert_eq!(args, 0);
973 assert_eq!(depth, 5);
974 assert_eq!(codes, vec![0x01, 0x02]);
975 assert_eq!(consts, vec![1, 2]);
976 } else {
977 panic!("expected ByteFn");
978 }
979 }
980
981 #[test]
982 fn test_parse_object_line_unknown_type() {
983 assert!(parse_object_line("@0 foobar xyz").is_err());
984 }
985
986 fn display_roundtrip(id: ObjId, obj: &DumpedObject) -> DumpedObject {
989 let line = format!("@{id} {obj}");
990 parse_object_line(&line).unwrap().1
991 }
992
993 #[test]
994 #[allow(clippy::approx_constant)]
995 fn test_display_roundtrip_leaf_types() {
996 let cases: Vec<(ObjId, DumpedObject)> = vec![
997 (0, DumpedObject::Nil),
998 (1, DumpedObject::Int(-42)),
999 (2, DumpedObject::Float(2.718)),
1000 (3, DumpedObject::String("hello \"world\"\nnewline".into())),
1001 (4, DumpedObject::ByteString(vec![0xca, 0xfe])),
1002 (5, DumpedObject::Symbol { name: "my-sym".into(), interned: false }),
1003 (6, DumpedObject::Subr("car".into())),
1004 (7, DumpedObject::BigInt("123456789012345678901234567890".into())),
1005 ];
1006 for (id, original) in &cases {
1007 let recovered = display_roundtrip(*id, original);
1008 assert_eq!(format!("{recovered}"), format!("{original}"), "mismatch for @{id}");
1009 }
1010 }
1011
1012 #[test]
1013 fn test_display_roundtrip_compound_types() {
1014 let cases: Vec<(ObjId, DumpedObject)> = vec![
1015 (0, DumpedObject::Cons { car: 1, cdr: 2 }),
1016 (1, DumpedObject::Vec(vec![3, 4, 5])),
1017 (2, DumpedObject::Record(vec![6, 7])),
1018 (3, DumpedObject::HashTable(vec![(8, 9), (10, 11)])),
1019 (
1020 4,
1021 DumpedObject::ByteFn {
1022 args: 257,
1023 depth: 3,
1024 codes: vec![0xab, 0xcd],
1025 consts: vec![0, 1],
1026 },
1027 ),
1028 ];
1029 for (id, original) in &cases {
1030 let recovered = display_roundtrip(*id, original);
1031 assert_eq!(format!("{recovered}"), format!("{original}"), "mismatch for @{id}");
1032 }
1033 }
1034
1035 #[test]
1036 fn test_parse_symbol_line_no_func() {
1037 let sym = parse_symbol_line(r#""my-var" -> @5"#).unwrap();
1038 assert_eq!(sym.name, "my-var");
1039 assert_eq!(sym.sym_id, 5);
1040 assert_eq!(sym.func_id, None);
1041 }
1042
1043 #[test]
1044 fn test_parse_symbol_line_with_func() {
1045 let sym = parse_symbol_line(r#""my-fn" -> @3 func=@7"#).unwrap();
1046 assert_eq!(sym.name, "my-fn");
1047 assert_eq!(sym.sym_id, 3);
1048 assert_eq!(sym.func_id, Some(7));
1049 }
1050
1051 #[test]
1052 fn test_parse_binding_line() {
1053 let b = parse_binding_line("@10 = @20").unwrap();
1054 assert_eq!(b.sym_id, 10);
1055 assert_eq!(b.val_id, 20);
1056 }
1057
1058 #[test]
1061 fn test_parse_dump_minimal() {
1062 let input = "\
1063.HEADER
1064 version 1
1065 objects 2
1066
1067.OBJECTS
1068 @0 nil
1069 @1 int 42
1070
1071.SYMBOLS
1072 \"nil\" -> @0
1073
1074.ENV
1075 @0 = @1
1076";
1077 let dump = parse_dump(input).unwrap();
1078 assert_eq!(dump.objects.len(), 2);
1079 assert!(matches!(dump.objects[0], DumpedObject::Nil));
1080 assert!(matches!(dump.objects[1], DumpedObject::Int(42)));
1081 assert_eq!(dump.symbols.len(), 1);
1082 assert_eq!(dump.symbols[0].name, "nil");
1083 assert_eq!(dump.env.len(), 1);
1084 assert_eq!(dump.env[0].sym_id, 0);
1085 assert_eq!(dump.env[0].val_id, 1);
1086 }
1087
1088 #[test]
1089 fn test_parse_dump_unknown_section() {
1090 let input = ".UNKNOWN\n";
1091 assert!(parse_dump(input).is_err());
1092 }
1093
1094 #[test]
1095 fn test_parse_dump_missing_object() {
1096 let input = "\
1098.HEADER
1099 version 1
1100 objects 3
1101
1102.OBJECTS
1103 @0 nil
1104 @2 int 1
1105
1106.SYMBOLS
1107
1108.ENV
1109";
1110 assert!(parse_dump(input).is_err());
1111 }
1112
1113 #[test]
1116 fn test_dump_state_roundtrip() {
1117 let mut state = DumpState::new();
1118 state.objects.push(DumpedObject::Nil);
1119 state.objects.push(DumpedObject::Int(99));
1120 state.objects.push(DumpedObject::String("test".into()));
1121 state.objects.push(DumpedObject::Cons { car: 1, cdr: 2 });
1122 state.symbols.push(DumpedSymbol { name: "x".into(), sym_id: 1, func_id: None });
1123 state
1124 .symbols
1125 .push(DumpedSymbol { name: "f".into(), sym_id: 2, func_id: Some(3) });
1126 state.env.push(DumpedBinding { sym_id: 1, val_id: 0 });
1127
1128 let output = state.into_output();
1129 let dump = parse_dump(&output).unwrap();
1130
1131 assert_eq!(dump.objects.len(), 4);
1132 assert!(matches!(dump.objects[0], DumpedObject::Nil));
1133 assert!(matches!(dump.objects[1], DumpedObject::Int(99)));
1134 assert!(matches!(dump.objects[2], DumpedObject::String(ref s) if s == "test"));
1135 assert!(matches!(dump.objects[3], DumpedObject::Cons { car: 1, cdr: 2 }));
1136 assert_eq!(dump.symbols.len(), 2);
1137 assert_eq!(dump.symbols[0].name, "x");
1138 assert_eq!(dump.symbols[0].func_id, None);
1139 assert_eq!(dump.symbols[1].name, "f");
1140 assert_eq!(dump.symbols[1].func_id, Some(3));
1141 assert_eq!(dump.env.len(), 1);
1142 assert_eq!(dump.env[0].sym_id, 1);
1143 assert_eq!(dump.env[0].val_id, 0);
1144 }
1145
1146 #[test]
1147 #[allow(clippy::approx_constant)]
1148 fn test_parse_sample_dump_file() {
1149 let input = r#".HEADER
1150 version 1
1151 objects 7
1152
1153.OBJECTS
1154 @0 nil
1155 @1 subr "elt"
1156 @2 symbol "bare-symbol" interned=true
1157 @3 subr "bare-symbol"
1158 @4 int 42
1159 @5 float 3.14
1160 @6 string "hello world"
1161
1162.SYMBOLS
1163 "nil" -> @0
1164 "elt" -> @0 func=@1
1165 "bare-symbol" -> @2 func=@3
1166
1167.ENV
1168 @2 = @4
1169"#;
1170 let dump = parse_dump(input).unwrap();
1171
1172 assert_eq!(dump.objects.len(), 7);
1173 assert!(matches!(dump.objects[0], DumpedObject::Nil));
1174 assert!(matches!(dump.objects[1], DumpedObject::Subr(ref s) if s == "elt"));
1175 assert!(
1176 matches!(dump.objects[2], DumpedObject::Symbol { ref name, interned: true } if name == "bare-symbol")
1177 );
1178 assert!(matches!(dump.objects[3], DumpedObject::Subr(ref s) if s == "bare-symbol"));
1179 assert!(matches!(dump.objects[4], DumpedObject::Int(42)));
1180 if let DumpedObject::Float(v) = dump.objects[5] {
1181 assert!((v - 3.14).abs() < f64::EPSILON);
1182 } else {
1183 panic!("expected Float");
1184 }
1185 assert!(matches!(dump.objects[6], DumpedObject::String(ref s) if s == "hello world"));
1186
1187 assert_eq!(dump.symbols.len(), 3);
1188 assert_eq!(dump.symbols[0].name, "nil");
1189 assert_eq!(dump.symbols[0].sym_id, 0);
1190 assert_eq!(dump.symbols[0].func_id, None);
1191 assert_eq!(dump.symbols[1].name, "elt");
1192 assert_eq!(dump.symbols[1].sym_id, 0);
1193 assert_eq!(dump.symbols[1].func_id, Some(1));
1194 assert_eq!(dump.symbols[2].name, "bare-symbol");
1195 assert_eq!(dump.symbols[2].sym_id, 2);
1196 assert_eq!(dump.symbols[2].func_id, Some(3));
1197
1198 assert_eq!(dump.env.len(), 1);
1199 assert_eq!(dump.env[0].sym_id, 2);
1200 assert_eq!(dump.env[0].val_id, 4);
1201 }
1202}