Skip to main content

serializer/machine/
machine_types.rs

1//! RKYV-compatible types for machine format serialization
2//!
3//! Uses a flattened arena-based approach to avoid recursive types.
4//! All nested values are stored in a flat Vec with indices.
5
6use rkyv::{Archive, Deserialize, Serialize};
7
8/// RKYV-compatible document with flattened value arena
9#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
10#[rkyv(derive(Debug))]
11pub struct MachineDocument {
12    /// Top-level context entries mapped to value-arena indexes.
13    pub context: Vec<(String, usize)>,
14    /// Named reference values preserved from the source document.
15    pub refs: Vec<(String, String)>,
16    /// Tabular sections keyed by compact section id.
17    pub sections: Vec<(char, MachineSection)>,
18    /// Human-readable section names keyed by compact section id.
19    pub section_names: Vec<(char, String)>,
20    /// Original document entry ordering for context and section records.
21    pub entry_order: Vec<MachineEntryRef>,
22    /// Flat storage for every nested value referenced by indexes.
23    pub value_arena: Vec<MachineValue>,
24}
25
26/// Reference to a top-level document entry in its original order.
27#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
28#[rkyv(derive(Debug))]
29pub enum MachineEntryRef {
30    /// Context entry identified by key.
31    Context(String),
32    /// Section entry identified by compact section id.
33    Section(char),
34}
35
36/// RKYV-compatible table section that stores cells as value-arena indexes.
37#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
38#[rkyv(derive(Debug))]
39pub struct MachineSection {
40    /// Column names for each row.
41    pub schema: Vec<String>,
42    /// Rows of value-arena indexes, one index per cell.
43    pub rows: Vec<Vec<usize>>,
44}
45
46/// Non-recursive value type - arrays/objects store indices
47#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
48#[rkyv(derive(Debug))]
49pub enum MachineValue {
50    /// String value stored inline in the arena.
51    Str(String),
52    /// Floating-point numeric value.
53    Num(f64),
54    /// Boolean value.
55    Bool(bool),
56    /// Null value.
57    Null,
58    /// Array represented by value-arena indexes.
59    Arr(Vec<usize>),
60    /// Object fields mapped to value-arena indexes.
61    Obj(Vec<(String, usize)>),
62    /// Reference name that can be resolved by higher-level consumers.
63    Ref(String),
64}
65
66impl From<&crate::llm::types::DxDocument> for MachineDocument {
67    fn from(doc: &crate::llm::types::DxDocument) -> Self {
68        use crate::llm::types::EntryRef;
69
70        let mut value_arena = Vec::new();
71        let mut context = Vec::new();
72
73        // Convert context values
74        for (k, v) in &doc.context {
75            let idx = add_value_to_arena(v, &mut value_arena);
76            context.push((k.clone(), idx));
77        }
78
79        // Convert sections
80        let sections: Vec<(char, MachineSection)> = doc
81            .sections
82            .iter()
83            .map(|(k, v)| {
84                let schema = v.schema.clone();
85                let rows: Vec<Vec<usize>> = v
86                    .rows
87                    .iter()
88                    .map(|row| {
89                        row.iter()
90                            .map(|cell| add_value_to_arena(cell, &mut value_arena))
91                            .collect()
92                    })
93                    .collect();
94                (*k, MachineSection { schema, rows })
95            })
96            .collect();
97
98        Self {
99            context,
100            refs: doc
101                .refs
102                .iter()
103                .map(|(k, v)| (k.clone(), v.clone()))
104                .collect(),
105            sections,
106            section_names: doc
107                .section_names
108                .iter()
109                .map(|(k, v)| (*k, v.clone()))
110                .collect(),
111            entry_order: doc
112                .entry_order
113                .iter()
114                .map(|e| match e {
115                    EntryRef::Context(s) => MachineEntryRef::Context(s.clone()),
116                    EntryRef::Section(c) => MachineEntryRef::Section(*c),
117                })
118                .collect(),
119            value_arena,
120        }
121    }
122}
123
124fn add_value_to_arena(v: &crate::llm::types::DxLlmValue, arena: &mut Vec<MachineValue>) -> usize {
125    use crate::llm::types::DxLlmValue;
126
127    let idx = arena.len();
128    arena.push(MachineValue::Null);
129    let machine_value = match v {
130        DxLlmValue::Str(s) => MachineValue::Str(s.clone()),
131        DxLlmValue::Num(n) => MachineValue::Num(*n),
132        DxLlmValue::Bool(b) => MachineValue::Bool(*b),
133        DxLlmValue::Null => MachineValue::Null,
134        DxLlmValue::Arr(items) => {
135            let indices: Vec<usize> = items
136                .iter()
137                .map(|item| add_value_to_arena(item, arena))
138                .collect();
139            MachineValue::Arr(indices)
140        }
141        DxLlmValue::Obj(fields) => {
142            let pairs: Vec<(String, usize)> = fields
143                .iter()
144                .map(|(k, v)| (k.clone(), add_value_to_arena(v, arena)))
145                .collect();
146            MachineValue::Obj(pairs)
147        }
148        DxLlmValue::Ref(r) => MachineValue::Ref(r.clone()),
149    };
150    arena[idx] = machine_value;
151    idx
152}
153
154impl From<&MachineDocument> for crate::llm::types::DxDocument {
155    fn from(m: &MachineDocument) -> Self {
156        use crate::llm::types::{DxDocument, DxSection, EntryRef};
157
158        let mut doc = DxDocument::new();
159
160        // Convert context
161        for (k, idx) in &m.context {
162            let value = get_value_from_arena(*idx, &m.value_arena);
163            doc.context.insert(k.clone(), value);
164        }
165
166        doc.refs = m.refs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
167
168        // Convert sections
169        for (k, section) in &m.sections {
170            let mut dx_section = DxSection::new(section.schema.clone());
171            for row_indices in &section.rows {
172                let row: Vec<_> = row_indices
173                    .iter()
174                    .map(|idx| get_value_from_arena(*idx, &m.value_arena))
175                    .collect();
176                dx_section.rows.push(row);
177            }
178            doc.sections.insert(*k, dx_section);
179        }
180
181        doc.section_names = m
182            .section_names
183            .iter()
184            .map(|(k, v)| (*k, v.clone()))
185            .collect();
186        doc.entry_order = m
187            .entry_order
188            .iter()
189            .map(|e| match e {
190                MachineEntryRef::Context(s) => EntryRef::Context(s.clone()),
191                MachineEntryRef::Section(c) => EntryRef::Section(*c),
192            })
193            .collect();
194        doc
195    }
196}
197
198fn get_value_from_arena(idx: usize, arena: &[MachineValue]) -> crate::llm::types::DxLlmValue {
199    use crate::llm::types::DxLlmValue;
200    use indexmap::IndexMap;
201
202    match &arena[idx] {
203        MachineValue::Str(s) => DxLlmValue::Str(s.clone()),
204        MachineValue::Num(n) => DxLlmValue::Num(*n),
205        MachineValue::Bool(b) => DxLlmValue::Bool(*b),
206        MachineValue::Null => DxLlmValue::Null,
207        MachineValue::Arr(indices) => {
208            let items: Vec<DxLlmValue> = indices
209                .iter()
210                .map(|i| get_value_from_arena(*i, arena))
211                .collect();
212            DxLlmValue::Arr(items)
213        }
214        MachineValue::Obj(pairs) => {
215            let mut fields = IndexMap::new();
216            for (k, i) in pairs {
217                fields.insert(k.clone(), get_value_from_arena(*i, arena));
218            }
219            DxLlmValue::Obj(fields)
220        }
221        MachineValue::Ref(r) => DxLlmValue::Ref(r.clone()),
222    }
223}