texlang/vm/
serde.rs

1//! Serialization and deserialization of VMs
2//!
3//! Texcraft's documentation website has a
4//!     [dedicated page about serializing and deserializing VMs](https://texcraft.dev/texlang/11-serde.html).
5//! The documentation here is for reference.
6//!
7//! ## Serialization
8//!
9//! If the state type `S` implements [`::serde::Serialize`] then
10//!     the Texlang VM `vm::V<S>` satisfies the [`::serde::Serialize`] trait too.
11//! VMs can thus be serialized using the standard Serde infrastructure.
12//!
13//! ## Deserialization
14//!
15//! If the state `S` implements [`::serde::Deserialize`] and
16//!     the Texlang trait [`super::HasDefaultBuiltInCommands`],
17//!     the Texlang VM `vm::V<S>` satisfies the [`::serde::Deserialize`] trait too.
18//!
19//! If the state doesn't implement the Texlang trait, deserialization is slightly more complicated
20//!     because the set of built-in primitives needs to be provided at deserialization time.
21//! This is because the built-in primitives which are regular Rust functions,
22//!     and it is not possible to fully serialize and deserialize Rust functions.
23//! Deserialization of VMs is thus a two-step process:
24//!
25//! 1. Deserialize the bytes to a [`DeserializedVM`] type.
26//!
27//! 2. Invoke [`finish_deserialization`] with the deserialized VM
28//!    and a map of built-in commands in order to recover the regular Texlang VM.
29//!
30//! The Texlang VM has a [`deserialize_with_built_in_commands` convenience method](super::VM::deserialize_with_built_in_commands)
31//!     which performs both of these steps at once.
32
33use crate::*;
34use serde::{Deserialize, Deserializer, Serialize};
35use std::collections::HashMap;
36
37#[derive(Serialize)]
38struct SerializableVM<'a, S> {
39    state: &'a S,
40    commands_map: &'a command::Map<S>,
41    internal: &'a vm::Internal<S>,
42    save_stack: Vec<variable::SerializableSaveStackElement<'a>>,
43}
44
45impl<'a, S> SerializableVM<'a, S> {
46    fn new(vm: &'a vm::VM<S>) -> Self {
47        let variable_key_to_built_in = vm.commands_map.getters_key_to_built_in();
48        Self {
49            state: &vm.state,
50            commands_map: &vm.commands_map,
51            internal: &vm.internal,
52            save_stack: vm
53                .internal
54                .save_stack
55                .iter()
56                .map(|element| element.serializable(&variable_key_to_built_in))
57                .collect(),
58        }
59    }
60}
61
62impl<State: serde::Serialize> Serialize for vm::VM<State> {
63    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64    where
65        S: serde::Serializer,
66    {
67        let serializable_vm = SerializableVM::new(self);
68        serializable_vm.serialize(serializer)
69    }
70}
71
72/// A VM that has been deserialized.
73///
74/// In order to recover a regular [Texlang `VM`](super::VM) it is necessary to
75///     call the [`finish_deserialization`] function with the relevant built-in commands.
76#[derive(Deserialize)]
77pub struct DeserializedVM<'a, S> {
78    state: S,
79    commands_map: command::map::SerializableMap<'a>,
80    internal: vm::Internal<S>,
81    save_stack: Vec<variable::SerializableSaveStackElement<'a>>,
82}
83
84/// Finish the deserialization of a VM.
85///
86/// This function accepts a [`DeserializedVM`] and a collection of built-in commands
87///     and returns a regular [Texlang `VM`](super::VM).
88pub fn finish_deserialization<S>(
89    #[allow(clippy::boxed_local)] mut deserialized: Box<DeserializedVM<'_, S>>,
90    built_in_commands: HashMap<&str, command::BuiltIn<S>>,
91) -> vm::VM<S> {
92    let built_in_commands = built_in_commands
93        .into_iter()
94        .map(|(key, value)| {
95            let cs_name = deserialized.internal.cs_name_interner.get_or_intern(key);
96            (cs_name, value)
97        })
98        .collect();
99    deserialized.internal.save_stack = deserialized
100        .save_stack
101        .into_iter()
102        .map(|element| element.finish_deserialization(&built_in_commands))
103        .collect();
104    let commands_map = deserialized
105        .commands_map
106        .finish_deserialization(built_in_commands, &deserialized.internal.cs_name_interner);
107    vm::VM {
108        state: deserialized.state,
109        commands_map,
110        working_directory: match std::env::current_dir() {
111            Ok(path_buf) => Some(path_buf),
112            Err(err) => {
113                println!("failed to determine the working directory: {err}");
114                None
115            }
116        },
117        internal: deserialized.internal,
118    }
119}
120
121pub(super) fn deserialize<'de, D: Deserializer<'de>, S: serde::Deserialize<'de>>(
122    deserializer: D,
123    built_in_commands: HashMap<&str, command::BuiltIn<S>>,
124) -> Result<vm::VM<S>, D::Error> {
125    let deserialized_vm: Box<DeserializedVM<S>> = Deserialize::deserialize(deserializer)?;
126    Ok(finish_deserialization(deserialized_vm, built_in_commands))
127}