1use serde::{de, Deserialize, Deserializer, Serialize};
4
5use crate::model::{Deco, FracStyle, Kind, Mark, MatrixEnv, Node, NodeId, SeqId, Symbol, Tree, Variant};
6
7pub const DOCUMENT_VERSION: u32 = 1;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Document {
21 #[serde(deserialize_with = "deserialize_version")]
22 version: u32,
23 pub root: Vec<NodeDoc>,
25}
26
27impl Document {
28 pub fn new(root: Vec<NodeDoc>) -> Self {
30 Self {
31 version: DOCUMENT_VERSION,
32 root,
33 }
34 }
35
36 pub fn version(&self) -> u32 {
38 self.version
39 }
40
41 pub fn root(&self) -> &[NodeDoc] {
43 &self.root
44 }
45
46 pub fn len(&self) -> usize {
48 self.root.len()
49 }
50
51 pub fn is_empty(&self) -> bool {
53 self.root.is_empty()
54 }
55
56 pub fn into_root(self) -> Vec<NodeDoc> {
58 self.root
59 }
60}
61
62impl Default for Document {
63 fn default() -> Self {
64 Self::new(Vec::new())
65 }
66}
67
68fn deserialize_version<'de, D>(deserializer: D) -> Result<u32, D::Error>
69where
70 D: Deserializer<'de>,
71{
72 let version = u32::deserialize(deserializer)?;
73 if version == DOCUMENT_VERSION {
74 Ok(version)
75 } else {
76 Err(de::Error::custom(format_args!(
77 "unsupported document version {version}; expected {DOCUMENT_VERSION}"
78 )))
79 }
80}
81
82pub type DocFragment = Vec<NodeDoc>;
84
85macro_rules! node_kinds {
87 (
88 $(
89 $variant:ident {
90 $( $field:ident : $mode:ident $ty:ty ),* $(,)?
91 }
92 ),* $(,)?
93 ) => {
94 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96 #[serde(tag = "type", content = "data", rename_all = "snake_case")]
97 pub enum NodeDoc {
98 Atom(Symbol),
100 $(
101 $variant {
103 $( $field : node_kinds!(@doc_ty $mode $ty) ),*
105 },
106 )*
107 Matrix {
109 env: MatrixEnv,
111 rows: Vec<Vec<Vec<NodeDoc>>>,
113 },
114 }
115
116 impl Tree {
117 fn node_to_doc(&self, node: NodeId) -> NodeDoc {
118 match self.kind(node).expect("live node") {
119 Kind::Atom(s) => NodeDoc::Atom(s.clone()),
120 $(
121 Kind::$variant { $( $field ),* } => NodeDoc::$variant {
122 $( $field : node_kinds!(@to_doc self $mode $field) ),*
123 },
124 )*
125 Kind::Matrix { env, rows } => NodeDoc::Matrix {
126 env: *env,
127 rows: rows
128 .iter()
129 .map(|row| row.iter().map(|&c| self.seq_to_doc(c)).collect())
130 .collect(),
131 },
132 }
133 }
134
135 fn doc_kind(&mut self, d: &NodeDoc) -> Kind {
136 match d {
137 NodeDoc::Atom(s) => Kind::Atom(s.clone()),
138 $(
139 NodeDoc::$variant { $( $field ),* } => Kind::$variant {
140 $( $field : node_kinds!(@from_doc self $mode $field) ),*
141 },
142 )*
143 NodeDoc::Matrix { env, rows } => {
144 let mut grid = Vec::with_capacity(rows.len());
145 for row in rows {
146 let mut r = Vec::with_capacity(row.len());
147 for cell in row {
148 r.push(self.doc_seq(cell));
149 }
150 grid.push(r);
151 }
152 Kind::Matrix { env: *env, rows: grid }
153 }
154 }
155 }
156 }
157 };
158
159 (@doc_ty seq $ty:ty) => { Vec<NodeDoc> };
161 (@doc_ty opt_seq $ty:ty) => { Option<Vec<NodeDoc>> };
162 (@doc_ty copy $ty:ty) => { $ty };
163 (@doc_ty clone $ty:ty) => { $ty };
164
165 (@to_doc $self:ident seq $f:ident) => { $self.seq_to_doc(*$f) };
167 (@to_doc $self:ident opt_seq $f:ident) => { $self.opt_seq_to_doc(*$f) };
168 (@to_doc $self:ident copy $f:ident) => { *$f };
169 (@to_doc $self:ident clone $f:ident) => { $f.clone() };
170
171 (@from_doc $self:ident seq $f:ident) => { $self.doc_seq($f) };
173 (@from_doc $self:ident opt_seq $f:ident) => { $self.doc_opt_seq($f) };
174 (@from_doc $self:ident copy $f:ident) => { *$f };
175 (@from_doc $self:ident clone $f:ident) => { $f.clone() };
176}
177
178node_kinds! {
179 Frac {
180 num: seq SeqId,
181 den: seq SeqId,
182 style: copy FracStyle,
183 },
184 Script {
185 base: seq SeqId,
186 sub: opt_seq SeqId,
187 sup: opt_seq SeqId,
188 },
189 BigOp {
190 op: clone Symbol,
191 lower: seq SeqId,
192 upper: seq SeqId,
193 },
194 Sqrt {
195 index: seq SeqId,
196 radicand: seq SeqId,
197 },
198 Delim {
199 open: copy char,
200 close: copy char,
201 body: seq SeqId,
202 },
203 Accent {
204 mark: copy Mark,
205 base: seq SeqId,
206 },
207 UnderOver {
208 base: seq SeqId,
209 over: opt_seq SeqId,
210 under: opt_seq SeqId,
211 over_deco: copy Deco,
212 under_deco: copy Deco,
213 },
214 Styled {
215 variant: copy Variant,
216 content: seq SeqId,
217 },
218}
219
220impl Tree {
221 pub fn to_doc(&self) -> Document {
223 Document::new(self.seq_to_doc(self.root()))
224 }
225
226 fn seq_to_doc(&self, seq: SeqId) -> Vec<NodeDoc> {
227 self.items(seq).iter().map(|&n| self.node_to_doc(n)).collect()
228 }
229
230 fn opt_seq_to_doc(&self, seq: Option<SeqId>) -> Option<Vec<NodeDoc>> {
231 seq.map(|s| self.seq_to_doc(s))
232 }
233
234 pub fn from_doc(doc: &Document) -> Self {
236 let mut t = Tree::new();
237 let root = t.root();
238 for d in doc.root() {
239 t.push_doc_node(root, d);
240 }
241 t
242 }
243
244 pub fn insert_fragment(&mut self, at: crate::model::Cursor, frag: &DocFragment) -> crate::model::Cursor {
246 let mut idx = at.index;
247 for d in frag {
248 let kind = self.doc_kind(d);
249 let node = self.nodes.insert(Node {
250 parent: at.seq,
251 kind,
252 });
253 for s in self.child_seqs(node) {
254 if let Some(sq) = self.seqs.get_mut(s) {
255 sq.parent = Some(node);
256 }
257 }
258 if let Some(sq) = self.seqs.get_mut(at.seq) {
259 let i = idx.min(sq.items.len());
260 sq.items.insert(i, node);
261 }
262 idx += 1;
263 }
264 crate::model::Cursor {
265 seq: at.seq,
266 index: idx,
267 }
268 }
269
270 fn push_doc_node(&mut self, seq: SeqId, d: &NodeDoc) {
271 let kind = self.doc_kind(d);
272 let node = self.nodes.insert(Node { parent: seq, kind });
273 for s in self.child_seqs(node) {
274 if let Some(sq) = self.seqs.get_mut(s) {
275 sq.parent = Some(node);
276 }
277 }
278 if let Some(sq) = self.seqs.get_mut(seq) {
279 sq.items.push(node);
280 }
281 }
282
283 fn doc_seq(&mut self, docs: &[NodeDoc]) -> SeqId {
284 let seq = self.alloc_seq(None);
285 for d in docs {
286 self.push_doc_node(seq, d);
287 }
288 seq
289 }
290
291 fn doc_opt_seq(&mut self, docs: &Option<Vec<NodeDoc>>) -> Option<SeqId> {
292 match docs {
293 Some(d) => Some(self.doc_seq(d)),
294 None => None,
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use crate::model::{Cursor, MathClass};
303
304 fn atom(c: &str) -> Symbol {
305 Symbol {
306 latex: c.into(),
307 class: MathClass::Ord,
308 }
309 }
310
311 #[test]
312 fn doc_roundtrip() {
313 let mut t = Tree::new();
314 let root = t.root();
315 let c = t.insert_fraction(Cursor { seq: root, index: 0 }, FracStyle::Bar, None);
316 let c = t.insert_atom(c, atom("a")); let _ = c;
318 let frac = t.items(root)[0];
320 let den = t.child_seqs(frac)[1];
321 t.insert_atom(Cursor { seq: den, index: 0 }, atom("b"));
322
323 let d1 = t.to_doc();
324 let json = serde_json::to_string(&d1).unwrap();
325 let decoded = serde_json::from_str(&json).unwrap();
326 let t2 = Tree::from_doc(&decoded);
327 let d2 = t2.to_doc();
328 assert_eq!(d1, d2);
329 }
330
331 #[test]
333 fn doc_roundtrip_all_kinds() {
334 use crate::model::{Mark, MatrixEnv, ScriptSlot, UnderOverSpec, Variant};
335 let mut t = Tree::new();
336 let root = t.root();
337 let end = |t: &Tree| Cursor { seq: root, index: t.len(root) };
338
339 t.insert_atom(Cursor { seq: root, index: 0 }, atom("a"));
341 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
342 t.insert_atom(c, atom("2"));
343 let c = t.insert_fraction(end(&t), FracStyle::Bar, None);
345 t.insert_atom(c, atom("x"));
346 let c = t.insert_sqrt(end(&t), None);
348 t.insert_atom(c, atom("y"));
349 t.insert_big_op(end(&t), Symbol { latex: "\\sum".into(), class: MathClass::Op });
351 let c = t.insert_delimiters(end(&t), '(', ')', None);
353 t.insert_atom(c, atom("z"));
354 let c = t.insert_accent(end(&t), Mark::Hat, None);
356 t.insert_atom(c, atom("b"));
357 let c = t.insert_styled(end(&t), Variant::Bold, None);
359 t.insert_atom(c, atom("c"));
360 let spec = UnderOverSpec { over: true, under: false, over_deco: Deco::Brace, under_deco: Deco::None };
362 let c = t.insert_under_over(end(&t), spec, None);
363 t.insert_atom(c, atom("d"));
364 let c = t.insert_matrix(end(&t), MatrixEnv::Pmatrix, 2, 2);
366 t.insert_atom(c, atom("e"));
367
368 let d1 = t.to_doc();
369 let json = serde_json::to_string(&d1).unwrap();
370 let decoded = serde_json::from_str(&json).unwrap();
371 let t2 = Tree::from_doc(&decoded);
372 let d2 = t2.to_doc();
373 assert_eq!(d1, d2);
374 }
375
376 #[test]
378 fn doc_serde_field_order_is_canonical() {
379 let pos = |s: &str, key: &str| s.find(&format!("\"{key}\"")).unwrap_or_else(|| panic!("missing {key} in {s}"));
380
381 let bigop = NodeDoc::BigOp {
382 op: Symbol { latex: "\\sum".into(), class: MathClass::Op },
383 lower: vec![],
384 upper: vec![],
385 };
386 let s = serde_json::to_string(&bigop).unwrap();
387 assert!(pos(&s, "op") < pos(&s, "lower") && pos(&s, "lower") < pos(&s, "upper"), "BigOp order: {s}");
388
389 let uo = NodeDoc::UnderOver {
390 base: vec![],
391 over: Some(vec![]), under: None,
393 over_deco: Deco::Brace,
394 under_deco: Deco::None,
395 };
396 let s = serde_json::to_string(&uo).unwrap();
397 assert!(
398 pos(&s, "base") < pos(&s, "over")
399 && pos(&s, "over") < pos(&s, "under")
400 && pos(&s, "under") < pos(&s, "over_deco")
401 && pos(&s, "over_deco") < pos(&s, "under_deco"),
402 "UnderOver order: {s}"
403 );
404 let back: NodeDoc = serde_json::from_str(&s).unwrap();
406 assert_eq!(back, uo);
407 }
408
409 #[test]
410 fn document_json_has_a_stable_shape() {
411 let doc = Document::new(vec![NodeDoc::Atom(atom("x"))]);
412
413 let json = serde_json::to_string(&doc).unwrap();
414
415 assert_eq!(
416 json,
417 r#"{"version":1,"root":[{"type":"atom","data":{"latex":"x","class":"ord"}}]}"#
418 );
419 }
420
421 #[test]
422 fn unknown_document_version_is_rejected() {
423 let error = serde_json::from_str::<Document>(r#"{"version":2,"root":[]}"#)
424 .unwrap_err()
425 .to_string();
426
427 assert!(error.contains("unsupported document version 2"), "{error}");
428 }
429}