Skip to main content

texform_core/
document.rs

1//! Public, fallible DOM layer over the internal panic-contract [`Ast`].
2//!
3//! [`Document`] is the single public, editable tree entry point. It wraps an
4//! internal [`crate::ast::Ast`], exposes read access through [`NodeRef`]
5//! handles, and edits through fallible methods returning [`EditError`] -- no
6//! panic from the `Ast` layer ever reaches a `Document` caller on
7//! user-input-driven paths.
8//!
9//! # Structural validity vs semantic completeness
10//!
11//! The wrapped `Ast` is always structurally valid. Whether the tree contains
12//! [`crate::ast::Node::Error`] placeholders is a separate, O(1) property
13//! exposed by [`Document::has_errors`]. A `Document` produced from a partial
14//! parse (containing `Error` nodes) is read-only.
15
16use std::collections::{HashMap, HashSet};
17use std::sync::atomic::{AtomicU64, Ordering};
18
19use slotmap::SecondaryMap;
20use texform_interface::syntax_node::{self, SyntaxNode};
21
22pub use crate::ast::NodeKind;
23use crate::ast::{
24    Argument, ArgumentKind, ArgumentSlot, ArgumentValue, Ast, ContentMode, Delimiter, GroupKind,
25    Node, NodeId as RawNodeId, Slot,
26};
27use crate::parse::Span;
28use crate::serialize::{SerializeError, SerializeOptions, serialize, serialize_with};
29
30/// Process-wide unique identity for a [`Document`].
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub struct DocumentId(u64);
33
34static NEXT_DOCUMENT_ID: AtomicU64 = AtomicU64::new(1);
35
36fn next_document_id() -> DocumentId {
37    DocumentId(NEXT_DOCUMENT_ID.fetch_add(1, Ordering::Relaxed))
38}
39
40/// Public node handle.
41///
42/// This is intentionally not the raw arena key: it carries the owning
43/// [`DocumentId`] so core can reject cross-document node mixing before touching
44/// the arena. Users can copy it but cannot construct one directly.
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46pub struct NodeId {
47    document: DocumentId,
48    raw: RawNodeId,
49}
50
51impl NodeId {
52    fn new(document: DocumentId, raw: RawNodeId) -> Self {
53        Self { document, raw }
54    }
55
56    fn raw(self) -> RawNodeId {
57        self.raw
58    }
59
60    fn document(self) -> DocumentId {
61        self.document
62    }
63}
64
65/// Error from [`Document::from_syntax`].
66#[derive(Clone, Debug, PartialEq, Eq)]
67#[non_exhaustive]
68pub enum FromSyntaxError {
69    /// The provided `SyntaxNode` was not a `Root`.
70    NotARoot,
71    /// A `Prime` node had `count == 0`.
72    InvalidPrimeCount,
73    /// A `Prime` node appeared in text-mode content.
74    PrimeInTextMode,
75}
76
77impl std::fmt::Display for FromSyntaxError {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            FromSyntaxError::NotARoot => f.write_str("from_syntax expects a SyntaxNode::Root"),
81            FromSyntaxError::InvalidPrimeCount => {
82                f.write_str("Prime count must be greater than zero")
83            }
84            FromSyntaxError::PrimeInTextMode => {
85                f.write_str("Prime nodes are only valid in math mode")
86            }
87        }
88    }
89}
90
91impl std::error::Error for FromSyntaxError {}
92
93/// Fallible editing error from [`Document`] mutation APIs.
94#[derive(Clone, Debug, PartialEq, Eq)]
95#[non_exhaustive]
96pub enum EditError {
97    NodeNotFound,
98    ReadOnlyDocument,
99    CannotEditRoot,
100    NotAContainer,
101    SlotShapeMismatch { expected: &'static str },
102    WouldCreateCycle,
103    IndexOutOfBounds,
104    DuplicateChild,
105    ExpectedDetachedRoot,
106    ForeignNode,
107}
108
109impl std::fmt::Display for EditError {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            EditError::NodeNotFound => f.write_str("node not found"),
113            EditError::ReadOnlyDocument => f.write_str("document is read-only"),
114            EditError::CannotEditRoot => f.write_str("cannot edit the root node"),
115            EditError::NotAContainer => f.write_str("node is not a container"),
116            EditError::SlotShapeMismatch { expected } => {
117                write!(f, "slot shape mismatch: expected {expected}")
118            }
119            EditError::WouldCreateCycle => f.write_str("edit would create a cycle"),
120            EditError::IndexOutOfBounds => f.write_str("index out of bounds"),
121            EditError::DuplicateChild => f.write_str("node cannot appear more than once"),
122            EditError::ExpectedDetachedRoot => f.write_str("expected a detached root"),
123            EditError::ForeignNode => f.write_str("node belongs to a different document"),
124        }
125    }
126}
127
128impl std::error::Error for EditError {}
129
130/// Write-side command/environment argument value.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum ArgValue {
133    Math(NodeId),
134    Text(NodeId),
135    Delimiter(DelimiterValue),
136    CSName(String),
137    Dimension(String),
138    Integer(String),
139    KeyVal(String),
140    Column(String),
141    Boolean(bool),
142}
143
144impl ArgValue {
145    pub fn math(id: NodeId) -> Self {
146        Self::Math(id)
147    }
148
149    pub fn text(id: NodeId) -> Self {
150        Self::Text(id)
151    }
152
153    pub fn delimiter(delimiter: DelimiterValue) -> Self {
154        Self::Delimiter(delimiter)
155    }
156
157    pub fn cs_name(value: impl Into<String>) -> Self {
158        Self::CSName(value.into())
159    }
160
161    pub fn dimension(value: impl Into<String>) -> Self {
162        Self::Dimension(value.into())
163    }
164
165    pub fn integer(value: impl Into<String>) -> Self {
166        Self::Integer(value.into())
167    }
168
169    pub fn key_val(value: impl Into<String>) -> Self {
170        Self::KeyVal(value.into())
171    }
172
173    pub fn column(value: impl Into<String>) -> Self {
174        Self::Column(value.into())
175    }
176
177    pub fn boolean(value: bool) -> Self {
178        Self::Boolean(value)
179    }
180}
181
182/// Public write-side delimiter value.
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub enum DelimiterValue {
185    None,
186    Char(char),
187    Control(String),
188}
189
190impl DelimiterValue {
191    fn into_ast(self) -> Delimiter {
192        match self {
193            DelimiterValue::None => Delimiter::None,
194            DelimiterValue::Char(ch) => Delimiter::Char(ch),
195            DelimiterValue::Control(name) => Delimiter::Control(name),
196        }
197    }
198}
199
200/// Public, fallible, editable DOM over an internal [`Ast`].
201pub struct Document {
202    ast: Ast,
203    spans: SecondaryMap<RawNodeId, Span>,
204    has_errors: bool,
205    id: DocumentId,
206}
207
208impl Document {
209    /// Create an empty document containing only an empty math-mode root.
210    pub fn new() -> Self {
211        Self::with_mode(ContentMode::Math)
212    }
213
214    /// Like [`Document::new`] but with an explicit root content mode.
215    pub fn with_mode(mode: ContentMode) -> Self {
216        Document {
217            ast: Ast::with_root_mode(mode),
218            spans: SecondaryMap::new(),
219            has_errors: false,
220            id: next_document_id(),
221        }
222    }
223
224    /// Build a document from a parsed syntax tree.
225    pub fn from_syntax(node: &SyntaxNode) -> Result<Document, FromSyntaxError> {
226        Self::validate_syntax(node, None, true)?;
227        let ast = Ast::from_syntax_root(node);
228        let has_errors = ast
229            .find(ast.root(), |node| matches!(node, Node::Error { .. }))
230            .is_some();
231        Ok(Document {
232            ast,
233            spans: SecondaryMap::new(),
234            has_errors,
235            id: next_document_id(),
236        })
237    }
238
239    /// Internal: build from syntax plus parser path spans.
240    #[allow(dead_code)]
241    pub(crate) fn from_syntax_with_spans(
242        node: &SyntaxNode,
243        path_spans: &[(String, Span)],
244    ) -> Result<Document, FromSyntaxError> {
245        let mut doc = Document::from_syntax(node)?;
246        let lookup: HashMap<&str, &Span> = path_spans
247            .iter()
248            .map(|(path, span)| (path.as_str(), span))
249            .collect();
250        let mut spans = SecondaryMap::new();
251        Self::assign_spans(&doc.ast, node, doc.ast.root(), "root", &lookup, &mut spans);
252        doc.spans = spans;
253        Ok(doc)
254    }
255
256    /// Process-wide unique id of this document.
257    pub fn id(&self) -> DocumentId {
258        self.id
259    }
260
261    /// Root node handle.
262    pub fn root(&self) -> NodeRef<'_> {
263        NodeRef {
264            doc: self,
265            raw: self.ast.root(),
266        }
267    }
268
269    /// Return a read-only handle for a public id.
270    pub fn node(&self, id: NodeId) -> Result<NodeRef<'_>, EditError> {
271        let raw = self.check_node_owner(id)?;
272        Ok(NodeRef { doc: self, raw })
273    }
274
275    /// `true` when the tree contains one or more `Error` nodes.
276    pub fn has_errors(&self) -> bool {
277        self.has_errors
278    }
279
280    /// Iterate every `Error` node in the tree.
281    pub fn errors(&self) -> impl Iterator<Item = NodeRef<'_>> + '_ {
282        self.ast
283            .find_all(self.ast.root(), |node| matches!(node, Node::Error { .. }))
284            .into_iter()
285            .map(move |raw| NodeRef { doc: self, raw })
286    }
287
288    /// `true` when this document is read-only.
289    pub fn is_read_only(&self) -> bool {
290        self.has_errors
291    }
292
293    /// Find the first matching node under `start`, including `start`.
294    pub fn find<'a>(
295        &'a self,
296        start: NodeRef<'a>,
297        pred: impl Fn(NodeRef<'a>) -> bool + 'a,
298    ) -> Option<NodeRef<'a>> {
299        if start.doc.id != self.id || !self.ast.contains(start.raw) {
300            return None;
301        }
302        self.ast
303            .find_all(start.raw, |_| true)
304            .into_iter()
305            .map(|raw| NodeRef { doc: self, raw })
306            .find(|node| pred(*node))
307    }
308
309    /// Collect matching nodes under `start`, including `start`.
310    pub fn find_all<'a>(
311        &'a self,
312        start: NodeRef<'a>,
313        pred: impl Fn(NodeRef<'a>) -> bool + 'a,
314    ) -> impl Iterator<Item = NodeRef<'a>> + 'a {
315        let raws = if start.doc.id == self.id && self.ast.contains(start.raw) {
316            self.ast.find_all(start.raw, |_| true)
317        } else {
318            Vec::new()
319        };
320        raws.into_iter().filter_map(move |raw| {
321            let node = NodeRef { doc: self, raw };
322            pred(node).then_some(node)
323        })
324    }
325
326    /// Find commands by name.
327    pub fn find_commands<'a>(&'a self, name: &'a str) -> impl Iterator<Item = NodeRef<'a>> + 'a {
328        self.find_all(self.root(), move |node| node.is_command(name))
329    }
330
331    /// Find environments by name.
332    pub fn find_environments<'a>(
333        &'a self,
334        name: &'a str,
335    ) -> impl Iterator<Item = NodeRef<'a>> + 'a {
336        self.find_all(self.root(), move |node| node.env_name() == Some(name))
337    }
338
339    /// Convert this document back into a lossless [`SyntaxNode`] tree.
340    pub fn to_syntax(&self) -> SyntaxNode {
341        self.ast.to_syntax_root()
342    }
343
344    /// Internal bridge for the `texform` facade engine integration.
345    ///
346    /// This is not a stable public editing API. It bypasses fallible document
347    /// editing and must only be called after a `!has_errors()` gate.
348    #[doc(hidden)]
349    pub fn __texform_engine_ast_mut(&mut self) -> &mut Ast {
350        &mut self.ast
351    }
352
353    /// Serialize to LaTeX using the default canonical style.
354    pub fn to_latex(&self) -> Result<String, SerializeError> {
355        Ok(serialize(&self.ast))
356    }
357
358    /// Serialize to LaTeX with explicit style options.
359    pub fn to_latex_with(&self, options: &SerializeOptions) -> Result<String, SerializeError> {
360        Ok(serialize_with(&self.ast, options))
361    }
362
363    /// Create a detached character node.
364    pub fn create_char(&mut self, c: char) -> Result<NodeId, EditError> {
365        self.create_node(Node::Char(c))
366    }
367
368    /// Create a detached text node.
369    pub fn create_text(&mut self, s: impl Into<String>) -> Result<NodeId, EditError> {
370        self.create_node(Node::Text(s.into()))
371    }
372
373    /// Create a detached active `~` space node.
374    pub fn create_active_space(&mut self) -> Result<NodeId, EditError> {
375        self.create_node(Node::ActiveSpace)
376    }
377
378    /// Create a detached group node.
379    pub fn create_group(&mut self, mode: ContentMode) -> Result<NodeId, EditError> {
380        self.create_node(Node::Group {
381            children: Vec::new(),
382            kind: GroupKind::Explicit,
383            mode,
384        })
385    }
386
387    /// Create a detached command node.
388    pub fn create_command(
389        &mut self,
390        name: impl Into<String>,
391        args: Vec<ArgValue>,
392    ) -> Result<NodeId, EditError> {
393        self.check_writable()?;
394        let args = self.build_arg_slots(args)?;
395        self.create_node(Node::Command {
396            name: name.into(),
397            args,
398            known: false,
399        })
400    }
401
402    /// Create a detached declarative command node.
403    pub fn create_declarative(
404        &mut self,
405        name: impl Into<String>,
406        args: Vec<ArgValue>,
407    ) -> Result<NodeId, EditError> {
408        self.check_writable()?;
409        let args = self.build_arg_slots(args)?;
410        self.create_node(Node::Declarative {
411            name: name.into(),
412            args,
413        })
414    }
415
416    /// Create a detached environment node.
417    pub fn create_environment(
418        &mut self,
419        name: impl Into<String>,
420        args: Vec<ArgValue>,
421        body: NodeId,
422    ) -> Result<NodeId, EditError> {
423        self.check_writable()?;
424        let body = self.check_node_owner(body)?;
425        self.check_detached(body)?;
426        if !matches!(self.ast.node_opt(body), Some(Node::Group { .. })) {
427            return Err(EditError::SlotShapeMismatch { expected: "group" });
428        }
429        let args = self.build_arg_slots(args)?;
430        self.create_node(Node::Environment {
431            name: name.into(),
432            args,
433            known: false,
434            body,
435        })
436    }
437
438    /// Append a detached node to a root/group container.
439    pub fn append_child(&mut self, parent: NodeId, child: NodeId) -> Result<(), EditError> {
440        self.check_writable()?;
441        self.insert_child_at(parent, self.child_len(parent)?, child)
442    }
443
444    /// Insert a detached node before an attached group child.
445    pub fn insert_before(&mut self, anchor: NodeId, new: NodeId) -> Result<(), EditError> {
446        self.check_writable()?;
447        let (parent, index) = self.group_child_position(anchor)?;
448        self.insert_child_at(parent, index, new)
449    }
450
451    /// Insert a detached node after an attached group child.
452    pub fn insert_after(&mut self, anchor: NodeId, new: NodeId) -> Result<(), EditError> {
453        self.check_writable()?;
454        let (parent, index) = self.group_child_position(anchor)?;
455        self.insert_child_at(parent, index + 1, new)
456    }
457
458    /// Insert a detached node at `index` in a root/group container.
459    pub fn insert_child(
460        &mut self,
461        parent: NodeId,
462        index: usize,
463        child: NodeId,
464    ) -> Result<(), EditError> {
465        self.check_writable()?;
466        self.insert_child_at(parent, index, child)
467    }
468
469    /// Detach an attached group child and return it as a detached root.
470    pub fn extract(&mut self, id: NodeId) -> Result<NodeId, EditError> {
471        self.check_writable()?;
472        let raw = self.check_node_owner(id)?;
473        if raw == self.ast.root() {
474            return Err(EditError::CannotEditRoot);
475        }
476        if !matches!(self.ast.slot(raw), Some(Slot::GroupChild(_))) {
477            return Err(EditError::SlotShapeMismatch {
478                expected: "group child",
479            });
480        }
481        Ok(NodeId::new(self.id, self.ast.detach(raw)))
482    }
483
484    /// Remove an attached group child and its subtree.
485    pub fn remove(&mut self, id: NodeId) -> Result<(), EditError> {
486        self.check_writable()?;
487        let raw = self.extract(id)?.raw();
488        self.ast.remove_detached(raw);
489        Ok(())
490    }
491
492    /// Replace `target` with a detached `replacement`.
493    pub fn replace_with(&mut self, target: NodeId, replacement: NodeId) -> Result<(), EditError> {
494        self.check_writable()?;
495        let target = self.check_node_owner(target)?;
496        let replacement = self.check_node_owner(replacement)?;
497        if target == self.ast.root() {
498            return Err(EditError::CannotEditRoot);
499        }
500        self.check_detached(replacement)?;
501        self.check_no_cycle(replacement, target)?;
502        match self.ast.slot(target) {
503            Some(Slot::GroupChild(index)) => {
504                let parent = self.ast.parent_id(target).ok_or(EditError::NodeNotFound)?;
505                let detached = self.ast.detach(target);
506                self.ast.insert_child(parent, index, replacement);
507                self.ast.remove_detached(detached);
508                Ok(())
509            }
510            Some(_) => {
511                let slot = self.ast.slot(target).ok_or(EditError::NodeNotFound)?;
512                self.check_slot_shape(slot, replacement)?;
513                self.ast.replace_content_child(target, replacement);
514                if self.ast.contains(target)
515                    && self.ast.parent_id(target).is_none()
516                    && target != self.ast.root()
517                {
518                    self.ast.remove_detached(target);
519                }
520                Ok(())
521            }
522            None => Err(EditError::NodeNotFound),
523        }
524    }
525
526    /// Remove all direct children from a root/group container.
527    pub fn clear(&mut self, container: NodeId) -> Result<(), EditError> {
528        self.check_writable()?;
529        let raw = self.check_node_owner(container)?;
530        self.check_container(raw)?;
531        let len = self.ast.children(raw).len();
532        let removed = self.ast.detach_children_range(raw, 0..len);
533        for child in removed {
534            self.ast.remove_detached(child);
535        }
536        Ok(())
537    }
538
539    /// Set the name of a command/infix/declarative/environment node.
540    pub fn set_command_name(
541        &mut self,
542        id: NodeId,
543        name: impl Into<String>,
544    ) -> Result<(), EditError> {
545        self.check_writable()?;
546        let raw = self.check_node_owner(id)?;
547        match self.ast.node_opt_mut(raw) {
548            Some(Node::Command { name: current, .. })
549            | Some(Node::Infix { name: current, .. })
550            | Some(Node::Declarative { name: current, .. })
551            | Some(Node::Environment { name: current, .. }) => {
552                *current = name.into();
553                Ok(())
554            }
555            Some(_) => Err(EditError::SlotShapeMismatch {
556                expected: "command-like node",
557            }),
558            None => Err(EditError::NodeNotFound),
559        }
560    }
561
562    /// Set the payload of a text node.
563    pub fn set_text(&mut self, id: NodeId, s: impl Into<String>) -> Result<(), EditError> {
564        self.check_writable()?;
565        let raw = self.check_node_owner(id)?;
566        match self.ast.node_opt_mut(raw) {
567            Some(Node::Text(text)) => {
568                *text = s.into();
569                Ok(())
570            }
571            Some(_) => Err(EditError::SlotShapeMismatch {
572                expected: "text node",
573            }),
574            None => Err(EditError::NodeNotFound),
575        }
576    }
577
578    /// Set the character of a char node.
579    pub fn set_char(&mut self, id: NodeId, c: char) -> Result<(), EditError> {
580        self.check_writable()?;
581        let raw = self.check_node_owner(id)?;
582        match self.ast.node_opt_mut(raw) {
583            Some(Node::Char(ch)) => {
584                *ch = c;
585                Ok(())
586            }
587            Some(_) => Err(EditError::SlotShapeMismatch {
588                expected: "char node",
589            }),
590            None => Err(EditError::NodeNotFound),
591        }
592    }
593
594    /// Replace the value at argument slot `index` of a command-like node.
595    pub fn set_arg(&mut self, id: NodeId, index: usize, value: ArgValue) -> Result<(), EditError> {
596        self.check_writable()?;
597        let raw = self.check_node_owner(id)?;
598        let kind = match self.ast.node_opt(raw) {
599            Some(
600                Node::Command { args, .. }
601                | Node::Infix { args, .. }
602                | Node::Declarative { args, .. }
603                | Node::Environment { args, .. },
604            ) => {
605                let Some(slot) = args.get(index) else {
606                    return Err(EditError::IndexOutOfBounds);
607                };
608                let Some(argument) = slot else {
609                    return Err(EditError::SlotShapeMismatch {
610                        expected: "filled argument slot",
611                    });
612                };
613                argument.kind.clone()
614            }
615            Some(_) => {
616                return Err(EditError::SlotShapeMismatch {
617                    expected: "command-like node",
618                });
619            }
620            None => return Err(EditError::NodeNotFound),
621        };
622        let value = self.build_arg_value_for_kind(value, &kind)?;
623        self.replace_single_arg_value(raw, index, value)
624    }
625
626    /// Wrap a group-child target with a detached root/group wrapper.
627    pub fn wrap(&mut self, target: NodeId, wrapper: NodeId) -> Result<NodeId, EditError> {
628        self.check_writable()?;
629        let target = self.check_node_owner(target)?;
630        let wrapper = self.check_node_owner(wrapper)?;
631        if target == self.ast.root() {
632            return Err(EditError::CannotEditRoot);
633        }
634        self.check_container(wrapper)?;
635        self.check_detached(wrapper)?;
636        let (parent, index) = match self.ast.slot(target) {
637            Some(Slot::GroupChild(index)) => (
638                self.ast.parent_id(target).ok_or(EditError::NodeNotFound)?,
639                index,
640            ),
641            Some(_) => {
642                return Err(EditError::SlotShapeMismatch {
643                    expected: "group child",
644                });
645            }
646            None => return Err(EditError::NodeNotFound),
647        };
648        self.check_no_cycle(wrapper, parent)?;
649        let target = self.ast.detach(target);
650        self.ast.insert_child(parent, index, wrapper);
651        self.ast.append_child(wrapper, target);
652        Ok(NodeId::new(self.id, wrapper))
653    }
654
655    /// Remove a group-child group and splice its children into the parent.
656    pub fn unwrap(&mut self, group: NodeId) -> Result<Vec<NodeId>, EditError> {
657        self.check_writable()?;
658        let group = self.check_node_owner(group)?;
659        if group == self.ast.root() {
660            return Err(EditError::CannotEditRoot);
661        }
662        if !matches!(self.ast.node_opt(group), Some(Node::Group { .. })) {
663            return Err(EditError::SlotShapeMismatch { expected: "group" });
664        }
665        let (parent, index) = match self.ast.slot(group) {
666            Some(Slot::GroupChild(index)) => (
667                self.ast.parent_id(group).ok_or(EditError::NodeNotFound)?,
668                index,
669            ),
670            Some(_) => {
671                return Err(EditError::SlotShapeMismatch {
672                    expected: "group child",
673                });
674            }
675            None => return Err(EditError::NodeNotFound),
676        };
677        let count = self.ast.children(group).len();
678        let children = self.ast.detach_children_range(group, 0..count);
679        let detached_group = self.ast.detach(group);
680        self.ast.remove_detached(detached_group);
681        for (offset, child) in children.iter().copied().enumerate() {
682            self.ast.insert_child(parent, index + offset, child);
683        }
684        Ok(children
685            .into_iter()
686            .map(|raw| NodeId::new(self.id, raw))
687            .collect())
688    }
689
690    fn create_node(&mut self, node: Node) -> Result<NodeId, EditError> {
691        self.check_writable()?;
692        self.check_unique_direct_children(&node)?;
693        Ok(NodeId::new(self.id, self.ast.new_node(node)))
694    }
695
696    fn child_len(&self, parent: NodeId) -> Result<usize, EditError> {
697        let raw = self.check_node_owner(parent)?;
698        self.check_container(raw)?;
699        Ok(self.ast.children(raw).len())
700    }
701
702    fn check_writable(&self) -> Result<(), EditError> {
703        if self.has_errors {
704            Err(EditError::ReadOnlyDocument)
705        } else {
706            Ok(())
707        }
708    }
709
710    fn check_node_owner(&self, id: NodeId) -> Result<RawNodeId, EditError> {
711        if id.document() != self.id {
712            return Err(EditError::ForeignNode);
713        }
714        let raw = id.raw();
715        if self.ast.contains(raw) {
716            Ok(raw)
717        } else {
718            Err(EditError::NodeNotFound)
719        }
720    }
721
722    fn check_container(&self, id: RawNodeId) -> Result<(), EditError> {
723        match self.ast.node_opt(id) {
724            Some(Node::Root { .. }) | Some(Node::Group { .. }) => Ok(()),
725            Some(_) => Err(EditError::NotAContainer),
726            None => Err(EditError::NodeNotFound),
727        }
728    }
729
730    fn check_detached(&self, id: RawNodeId) -> Result<(), EditError> {
731        if !self.ast.contains(id) {
732            return Err(EditError::NodeNotFound);
733        }
734        if id == self.ast.root() {
735            return Err(EditError::CannotEditRoot);
736        }
737        if self.ast.parent_id(id).is_some() || !self.ast.is_detached_root(id) {
738            return Err(EditError::ExpectedDetachedRoot);
739        }
740        Ok(())
741    }
742
743    fn check_no_cycle(&self, child: RawNodeId, new_parent: RawNodeId) -> Result<(), EditError> {
744        if self.ast.subtree_contains_node(child, new_parent) {
745            Err(EditError::WouldCreateCycle)
746        } else {
747            Ok(())
748        }
749    }
750
751    fn check_slot_shape(&self, slot: Slot, child: RawNodeId) -> Result<(), EditError> {
752        if matches!(slot, Slot::EnvBody)
753            && !matches!(self.ast.node_opt(child), Some(Node::Group { .. }))
754        {
755            Err(EditError::SlotShapeMismatch { expected: "group" })
756        } else {
757            Ok(())
758        }
759    }
760
761    fn check_unique_direct_children(&self, node: &Node) -> Result<(), EditError> {
762        let mut seen = HashSet::new();
763        for child in Self::direct_children(node) {
764            if !seen.insert(child) {
765                return Err(EditError::DuplicateChild);
766            }
767        }
768        Ok(())
769    }
770
771    fn direct_children(node: &Node) -> Vec<RawNodeId> {
772        let mut children = Vec::new();
773        match node {
774            Node::Root {
775                children: group_children,
776                ..
777            }
778            | Node::Group {
779                children: group_children,
780                ..
781            } => children.extend(group_children.iter().copied()),
782            Node::Command { args, .. } | Node::Declarative { args, .. } => {
783                Self::push_arg_children(args, &mut children);
784            }
785            Node::Infix {
786                args, left, right, ..
787            } => {
788                children.push(*left);
789                Self::push_arg_children(args, &mut children);
790                children.push(*right);
791            }
792            Node::Environment { args, body, .. } => {
793                Self::push_arg_children(args, &mut children);
794                children.push(*body);
795            }
796            Node::Scripted {
797                base,
798                subscript,
799                superscript,
800            } => {
801                children.push(*base);
802                children.extend(*subscript);
803                children.extend(*superscript);
804            }
805            Node::Prime { .. }
806            | Node::Text(_)
807            | Node::Char(_)
808            | Node::ActiveSpace
809            | Node::Error { .. } => {}
810        }
811        children
812    }
813
814    fn validate_syntax(
815        node: &SyntaxNode,
816        current_mode: Option<ContentMode>,
817        is_top_level: bool,
818    ) -> Result<(), FromSyntaxError> {
819        if is_top_level && !matches!(node, SyntaxNode::Root { .. }) {
820            return Err(FromSyntaxError::NotARoot);
821        }
822
823        match node {
824            SyntaxNode::Root { mode, children } => {
825                if !is_top_level {
826                    return Err(FromSyntaxError::NotARoot);
827                }
828                for child in children {
829                    Self::validate_syntax(child, Some(*mode), false)?;
830                }
831            }
832            SyntaxNode::Group { mode, children, .. } => {
833                for child in children {
834                    Self::validate_syntax(child, Some(*mode), false)?;
835                }
836            }
837            SyntaxNode::Command { args, .. } | SyntaxNode::Declarative { args, .. } => {
838                Self::validate_syntax_args(args)?;
839            }
840            SyntaxNode::Infix {
841                args, left, right, ..
842            } => {
843                Self::validate_syntax_args(args)?;
844                Self::validate_syntax(left, current_mode, false)?;
845                Self::validate_syntax(right, current_mode, false)?;
846            }
847            SyntaxNode::Environment { args, body, .. } => {
848                Self::validate_syntax_args(args)?;
849                Self::validate_syntax(body, None, false)?;
850            }
851            SyntaxNode::Scripted {
852                base,
853                subscript,
854                superscript,
855            } => {
856                Self::validate_syntax(base, current_mode, false)?;
857                if let Some(subscript) = subscript {
858                    Self::validate_syntax(subscript, current_mode, false)?;
859                }
860                if let Some(superscript) = superscript {
861                    Self::validate_syntax(superscript, current_mode, false)?;
862                }
863            }
864            SyntaxNode::Prime { count } => {
865                if *count == 0 {
866                    return Err(FromSyntaxError::InvalidPrimeCount);
867                }
868                if current_mode != Some(ContentMode::Math) {
869                    return Err(FromSyntaxError::PrimeInTextMode);
870                }
871            }
872            SyntaxNode::Error { .. }
873            | SyntaxNode::Text(_)
874            | SyntaxNode::Char(_)
875            | SyntaxNode::ActiveSpace => {}
876        }
877
878        Ok(())
879    }
880
881    fn validate_syntax_args(args: &[syntax_node::ArgumentSlot]) -> Result<(), FromSyntaxError> {
882        for arg in args.iter().flatten() {
883            match &arg.value {
884                syntax_node::ArgumentValue::MathContent(node) => {
885                    Self::validate_syntax(node, Some(ContentMode::Math), false)?;
886                }
887                syntax_node::ArgumentValue::TextContent(node) => {
888                    Self::validate_syntax(node, Some(ContentMode::Text), false)?;
889                }
890                _ => {}
891            }
892        }
893        Ok(())
894    }
895
896    fn push_arg_children(args: &[ArgumentSlot], out: &mut Vec<RawNodeId>) {
897        for arg in args.iter().flatten() {
898            match &arg.value {
899                ArgumentValue::MathContent(child) | ArgumentValue::TextContent(child) => {
900                    out.push(*child);
901                }
902                _ => {}
903            }
904        }
905    }
906
907    fn group_child_position(&self, anchor: NodeId) -> Result<(NodeId, usize), EditError> {
908        let raw = self.check_node_owner(anchor)?;
909        match self.ast.slot(raw) {
910            Some(Slot::GroupChild(index)) => {
911                let parent = self.ast.parent_id(raw).ok_or(EditError::NodeNotFound)?;
912                Ok((NodeId::new(self.id, parent), index))
913            }
914            Some(_) => Err(EditError::SlotShapeMismatch {
915                expected: "group child",
916            }),
917            None => Err(EditError::NodeNotFound),
918        }
919    }
920
921    fn insert_child_at(
922        &mut self,
923        parent: NodeId,
924        index: usize,
925        child: NodeId,
926    ) -> Result<(), EditError> {
927        self.check_writable()?;
928        let parent = self.check_node_owner(parent)?;
929        let child = self.check_node_owner(child)?;
930        self.check_container(parent)?;
931        self.check_detached(child)?;
932        self.check_no_cycle(child, parent)?;
933        if index > self.ast.children(parent).len() {
934            return Err(EditError::IndexOutOfBounds);
935        }
936        self.ast.insert_child(parent, index, child);
937        Ok(())
938    }
939
940    fn build_arg_slots(&mut self, args: Vec<ArgValue>) -> Result<Vec<ArgumentSlot>, EditError> {
941        args.into_iter()
942            .map(|arg| self.build_arg_slot(arg))
943            .collect()
944    }
945
946    fn build_arg_slot(&mut self, value: ArgValue) -> Result<ArgumentSlot, EditError> {
947        let (kind, value) = match value {
948            ArgValue::Math(id) => {
949                let raw = self.check_node_owner(id)?;
950                self.check_detached(raw)?;
951                (ArgumentKind::Mandatory, ArgumentValue::MathContent(raw))
952            }
953            ArgValue::Text(id) => {
954                let raw = self.check_node_owner(id)?;
955                self.check_detached(raw)?;
956                (ArgumentKind::Mandatory, ArgumentValue::TextContent(raw))
957            }
958            ArgValue::Delimiter(d) => (
959                ArgumentKind::Mandatory,
960                ArgumentValue::Delimiter(d.into_ast()),
961            ),
962            ArgValue::CSName(s) => (ArgumentKind::Mandatory, ArgumentValue::CSName(s)),
963            ArgValue::Dimension(s) => (ArgumentKind::Mandatory, ArgumentValue::Dimension(s)),
964            ArgValue::Integer(s) => (ArgumentKind::Mandatory, ArgumentValue::Integer(s)),
965            ArgValue::KeyVal(s) => (ArgumentKind::Mandatory, ArgumentValue::KeyVal(s)),
966            ArgValue::Column(s) => (ArgumentKind::Mandatory, ArgumentValue::Column(s)),
967            ArgValue::Boolean(b) => (ArgumentKind::Star, ArgumentValue::Boolean(b)),
968        };
969        Ok(Some(Argument { kind, value }))
970    }
971
972    fn build_arg_value_for_kind(
973        &self,
974        value: ArgValue,
975        kind: &ArgumentKind,
976    ) -> Result<ArgumentValue, EditError> {
977        match (kind, value) {
978            (ArgumentKind::Star, ArgValue::Boolean(value)) => Ok(ArgumentValue::Boolean(value)),
979            (ArgumentKind::Star, _) => Err(EditError::SlotShapeMismatch {
980                expected: "boolean value",
981            }),
982            (_, ArgValue::Boolean(_)) => Err(EditError::SlotShapeMismatch {
983                expected: "non-boolean argument value",
984            }),
985            (_, ArgValue::Math(id)) => {
986                let raw = self.check_node_owner(id)?;
987                self.check_detached(raw)?;
988                Ok(ArgumentValue::MathContent(raw))
989            }
990            (_, ArgValue::Text(id)) => {
991                let raw = self.check_node_owner(id)?;
992                self.check_detached(raw)?;
993                Ok(ArgumentValue::TextContent(raw))
994            }
995            (_, ArgValue::Delimiter(delimiter)) => {
996                Ok(ArgumentValue::Delimiter(delimiter.into_ast()))
997            }
998            (_, ArgValue::CSName(value)) => Ok(ArgumentValue::CSName(value)),
999            (_, ArgValue::Dimension(value)) => Ok(ArgumentValue::Dimension(value)),
1000            (_, ArgValue::Integer(value)) => Ok(ArgumentValue::Integer(value)),
1001            (_, ArgValue::KeyVal(value)) => Ok(ArgumentValue::KeyVal(value)),
1002            (_, ArgValue::Column(value)) => Ok(ArgumentValue::Column(value)),
1003        }
1004    }
1005
1006    fn replace_single_arg_value(
1007        &mut self,
1008        id: RawNodeId,
1009        index: usize,
1010        value: ArgumentValue,
1011    ) -> Result<(), EditError> {
1012        let old_content = self
1013            .ast
1014            .arg_slots(id)
1015            .get(index)
1016            .and_then(|slot| slot.as_ref())
1017            .and_then(|arg| match &arg.value {
1018                ArgumentValue::MathContent(content) | ArgumentValue::TextContent(content) => {
1019                    Some(*content)
1020                }
1021                _ => None,
1022            });
1023
1024        let mut node = self.ast.node(id).clone();
1025        match &mut node {
1026            Node::Command { args, .. }
1027            | Node::Infix { args, .. }
1028            | Node::Declarative { args, .. }
1029            | Node::Environment { args, .. } => {
1030                let Some(Some(argument)) = args.get_mut(index) else {
1031                    return Err(EditError::SlotShapeMismatch {
1032                        expected: "filled argument slot",
1033                    });
1034                };
1035                argument.value = value;
1036            }
1037            _ => {
1038                return Err(EditError::SlotShapeMismatch {
1039                    expected: "command-like node",
1040                });
1041            }
1042        }
1043        self.ast.replace_node(id, node);
1044
1045        if let Some(old) = old_content
1046            && self.ast.contains(old)
1047            && self.ast.parent_id(old).is_none()
1048            && old != self.ast.root()
1049        {
1050            self.ast.remove_detached(old);
1051        }
1052        Ok(())
1053    }
1054
1055    /// Export the parse-time span side table as `(path, span)` pairs.
1056    ///
1057    /// Paths follow the parser's tree-path scheme rooted at `root`:
1058    /// `.child.N` for container children, `.arg.N.content` for content-carrying
1059    /// argument slots, `.left` / `.right` for infix operands, `.body` for
1060    /// environment bodies, and `.base` / `.sub` / `.sup` for script slots.
1061    /// Nodes without a recorded span (e.g. created by edits, or any node of a
1062    /// document built without parser spans) are omitted. Spans reflect the
1063    /// original parse and are not updated by document edits.
1064    pub fn node_spans(&self) -> Vec<(String, Span)> {
1065        let mut out = Vec::new();
1066        self.collect_node_spans(self.ast.root(), "root", &mut out);
1067        out
1068    }
1069
1070    fn collect_node_spans(&self, id: RawNodeId, path: &str, out: &mut Vec<(String, Span)>) {
1071        if let Some(span) = self.spans.get(id) {
1072            out.push((path.to_string(), span.clone()));
1073        }
1074        match self.ast.node(id) {
1075            Node::Root { children, .. } | Node::Group { children, .. } => {
1076                for (index, child) in children.iter().enumerate() {
1077                    self.collect_node_spans(*child, &format!("{path}.child.{index}"), out);
1078                }
1079            }
1080            Node::Command { args, .. } | Node::Declarative { args, .. } => {
1081                self.collect_arg_node_spans(args, path, out);
1082            }
1083            Node::Infix {
1084                args, left, right, ..
1085            } => {
1086                self.collect_node_spans(*left, &format!("{path}.left"), out);
1087                self.collect_arg_node_spans(args, path, out);
1088                self.collect_node_spans(*right, &format!("{path}.right"), out);
1089            }
1090            Node::Environment { args, body, .. } => {
1091                self.collect_arg_node_spans(args, path, out);
1092                self.collect_node_spans(*body, &format!("{path}.body"), out);
1093            }
1094            Node::Scripted {
1095                base,
1096                subscript,
1097                superscript,
1098            } => {
1099                self.collect_node_spans(*base, &format!("{path}.base"), out);
1100                if let Some(sub) = subscript {
1101                    self.collect_node_spans(*sub, &format!("{path}.sub"), out);
1102                }
1103                if let Some(sup) = superscript {
1104                    self.collect_node_spans(*sup, &format!("{path}.sup"), out);
1105                }
1106            }
1107            _ => {}
1108        }
1109    }
1110
1111    fn collect_arg_node_spans(
1112        &self,
1113        args: &[ArgumentSlot],
1114        path: &str,
1115        out: &mut Vec<(String, Span)>,
1116    ) {
1117        for (index, slot) in args.iter().enumerate() {
1118            let Some(arg) = slot else { continue };
1119            if let ArgumentValue::MathContent(id) | ArgumentValue::TextContent(id) = &arg.value {
1120                self.collect_node_spans(*id, &format!("{path}.arg.{index}.content"), out);
1121            }
1122        }
1123    }
1124
1125    #[allow(dead_code)]
1126    fn assign_spans(
1127        ast: &Ast,
1128        syntax: &SyntaxNode,
1129        id: RawNodeId,
1130        path: &str,
1131        lookup: &HashMap<&str, &Span>,
1132        out: &mut SecondaryMap<RawNodeId, Span>,
1133    ) {
1134        if let Some(span) = lookup.get(path) {
1135            out.insert(id, (*span).clone());
1136        }
1137
1138        match (syntax, ast.node(id)) {
1139            (
1140                SyntaxNode::Root {
1141                    children: syntax_children,
1142                    ..
1143                },
1144                Node::Root { children, .. },
1145            )
1146            | (
1147                SyntaxNode::Group {
1148                    children: syntax_children,
1149                    ..
1150                },
1151                Node::Group { children, .. },
1152            ) => {
1153                for (index, (syntax_child, ast_child)) in
1154                    syntax_children.iter().zip(children.iter()).enumerate()
1155                {
1156                    Self::assign_spans(
1157                        ast,
1158                        syntax_child,
1159                        *ast_child,
1160                        &format!("{path}.child.{index}"),
1161                        lookup,
1162                        out,
1163                    );
1164                }
1165            }
1166            (
1167                SyntaxNode::Command {
1168                    args: syntax_args, ..
1169                },
1170                Node::Command { args: ast_args, .. },
1171            )
1172            | (
1173                SyntaxNode::Declarative {
1174                    args: syntax_args, ..
1175                },
1176                Node::Declarative { args: ast_args, .. },
1177            ) => Self::assign_arg_spans(ast, syntax_args, ast_args, path, lookup, out),
1178            (
1179                SyntaxNode::Infix {
1180                    args: syntax_args,
1181                    left: syntax_left,
1182                    right: syntax_right,
1183                    ..
1184                },
1185                Node::Infix {
1186                    args: ast_args,
1187                    left,
1188                    right,
1189                    ..
1190                },
1191            ) => {
1192                Self::assign_spans(
1193                    ast,
1194                    syntax_left,
1195                    *left,
1196                    &format!("{path}.left"),
1197                    lookup,
1198                    out,
1199                );
1200                Self::assign_arg_spans(ast, syntax_args, ast_args, path, lookup, out);
1201                Self::assign_spans(
1202                    ast,
1203                    syntax_right,
1204                    *right,
1205                    &format!("{path}.right"),
1206                    lookup,
1207                    out,
1208                );
1209            }
1210            (
1211                SyntaxNode::Environment {
1212                    args: syntax_args,
1213                    body: syntax_body,
1214                    ..
1215                },
1216                Node::Environment {
1217                    args: ast_args,
1218                    body,
1219                    ..
1220                },
1221            ) => {
1222                Self::assign_arg_spans(ast, syntax_args, ast_args, path, lookup, out);
1223                Self::assign_spans(
1224                    ast,
1225                    syntax_body,
1226                    *body,
1227                    &format!("{path}.body"),
1228                    lookup,
1229                    out,
1230                );
1231            }
1232            (
1233                SyntaxNode::Scripted {
1234                    base: syntax_base,
1235                    subscript: syntax_subscript,
1236                    superscript: syntax_superscript,
1237                },
1238                Node::Scripted {
1239                    base,
1240                    subscript,
1241                    superscript,
1242                },
1243            ) => {
1244                Self::assign_spans(
1245                    ast,
1246                    syntax_base,
1247                    *base,
1248                    &format!("{path}.base"),
1249                    lookup,
1250                    out,
1251                );
1252                if let (Some(syntax), Some(ast_id)) = (syntax_subscript, subscript) {
1253                    Self::assign_spans(ast, syntax, *ast_id, &format!("{path}.sub"), lookup, out);
1254                }
1255                if let (Some(syntax), Some(ast_id)) = (syntax_superscript, superscript) {
1256                    Self::assign_spans(ast, syntax, *ast_id, &format!("{path}.sup"), lookup, out);
1257                }
1258            }
1259            _ => {}
1260        }
1261    }
1262
1263    #[allow(dead_code)]
1264    fn assign_arg_spans(
1265        ast: &Ast,
1266        syntax_args: &[syntax_node::ArgumentSlot],
1267        ast_args: &[ArgumentSlot],
1268        path: &str,
1269        lookup: &HashMap<&str, &Span>,
1270        out: &mut SecondaryMap<RawNodeId, Span>,
1271    ) {
1272        for (index, (syntax_slot, ast_slot)) in syntax_args.iter().zip(ast_args.iter()).enumerate()
1273        {
1274            let (Some(syntax_arg), Some(ast_arg)) = (syntax_slot, ast_slot) else {
1275                continue;
1276            };
1277            let content_path = format!("{path}.arg.{index}.content");
1278            if let (
1279                syntax_node::ArgumentValue::MathContent(syntax_node)
1280                | syntax_node::ArgumentValue::TextContent(syntax_node),
1281                ArgumentValue::MathContent(ast_id) | ArgumentValue::TextContent(ast_id),
1282            ) = (&syntax_arg.value, &ast_arg.value)
1283            {
1284                Self::assign_spans(ast, syntax_node, *ast_id, &content_path, lookup, out);
1285            }
1286        }
1287    }
1288}
1289
1290impl Default for Document {
1291    fn default() -> Self {
1292        Self::new()
1293    }
1294}
1295
1296impl Clone for Document {
1297    fn clone(&self) -> Self {
1298        Document {
1299            ast: self.ast.clone(),
1300            spans: self.spans.clone(),
1301            has_errors: self.has_errors,
1302            id: next_document_id(),
1303        }
1304    }
1305}
1306
1307impl std::fmt::Debug for Document {
1308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1309        f.debug_struct("Document")
1310            .field("id", &self.id)
1311            .field("root", &self.ast.root())
1312            .field("has_errors", &self.has_errors)
1313            .finish()
1314    }
1315}
1316
1317impl std::fmt::Display for Document {
1318    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1319        let latex = self.to_latex().map_err(|_| std::fmt::Error)?;
1320        f.write_str(&latex)
1321    }
1322}
1323
1324/// Read-only borrowed handle to a node within a [`Document`].
1325#[derive(Clone, Copy)]
1326pub struct NodeRef<'a> {
1327    doc: &'a Document,
1328    raw: RawNodeId,
1329}
1330
1331impl std::fmt::Debug for NodeRef<'_> {
1332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1333        f.debug_struct("NodeRef").field("id", &self.id()).finish()
1334    }
1335}
1336
1337impl<'a> NodeRef<'a> {
1338    /// Opaque public handle for this node.
1339    pub fn id(&self) -> NodeId {
1340        NodeId::new(self.doc.id, self.raw)
1341    }
1342
1343    /// Lightweight node discriminant.
1344    pub fn kind(&self) -> NodeKind {
1345        self.doc.ast.kind(self.raw)
1346    }
1347
1348    /// `true` when this is a command named `name`.
1349    pub fn is_command(&self, name: &str) -> bool {
1350        matches!(self.node(), Node::Command { name: current, .. } if current == name)
1351    }
1352
1353    /// `true` when this is a char node equal to `c`.
1354    pub fn is_char(&self, c: char) -> bool {
1355        matches!(self.node(), Node::Char(ch) if *ch == c)
1356    }
1357
1358    /// `true` when this is an `Error` placeholder.
1359    pub fn is_error(&self) -> bool {
1360        matches!(self.node(), Node::Error { .. })
1361    }
1362
1363    /// Parent handle, or `None` for the root / a detached node.
1364    pub fn parent(&self) -> Option<NodeRef<'a>> {
1365        self.doc
1366            .ast
1367            .parent_id(self.raw)
1368            .map(|raw| self.sibling(raw))
1369    }
1370
1371    /// Direct children (root/group only; other kinds yield an empty iterator).
1372    pub fn children(&self) -> impl Iterator<Item = NodeRef<'a>> + 'a {
1373        let doc = self.doc;
1374        doc.ast
1375            .children(self.raw)
1376            .to_vec()
1377            .into_iter()
1378            .map(move |raw| NodeRef { doc, raw })
1379    }
1380
1381    /// Next sibling when attached as a group child.
1382    pub fn next_sibling(&self) -> Option<NodeRef<'a>> {
1383        self.doc
1384            .ast
1385            .next_sibling(self.raw)
1386            .map(|raw| self.sibling(raw))
1387    }
1388
1389    /// Previous sibling when attached as a group child.
1390    pub fn prev_sibling(&self) -> Option<NodeRef<'a>> {
1391        self.doc
1392            .ast
1393            .prev_sibling(self.raw)
1394            .map(|raw| self.sibling(raw))
1395    }
1396
1397    /// Ancestors from immediate parent up to the root.
1398    pub fn ancestors(&self) -> impl Iterator<Item = NodeRef<'a>> + 'a {
1399        let doc = self.doc;
1400        let mut current = doc.ast.parent_id(self.raw);
1401        std::iter::from_fn(move || {
1402            let raw = current?;
1403            current = doc.ast.parent_id(raw);
1404            Some(NodeRef { doc, raw })
1405        })
1406    }
1407
1408    /// All descendants in depth-first order, excluding self.
1409    pub fn descendants(&self) -> impl Iterator<Item = NodeRef<'a>> + 'a {
1410        let doc = self.doc;
1411        let start = self.raw;
1412        doc.ast
1413            .find_all(start, |_| true)
1414            .into_iter()
1415            .filter(move |raw| *raw != start)
1416            .map(move |raw| NodeRef { doc, raw })
1417    }
1418
1419    /// Command/infix/declarative name without leading backslash.
1420    pub fn command_name(&self) -> Option<&'a str> {
1421        match self.node() {
1422            Node::Command { name, .. }
1423            | Node::Infix { name, .. }
1424            | Node::Declarative { name, .. } => Some(name),
1425            _ => None,
1426        }
1427    }
1428
1429    /// Environment name without `begin`/`end`.
1430    pub fn env_name(&self) -> Option<&'a str> {
1431        match self.node() {
1432            Node::Environment { name, .. } => Some(name),
1433            _ => None,
1434        }
1435    }
1436
1437    /// Text payload for a `Text` node.
1438    pub fn text(&self) -> Option<&'a str> {
1439        match self.node() {
1440            Node::Text(text) => Some(text),
1441            _ => None,
1442        }
1443    }
1444
1445    /// Character for a `Char` node.
1446    pub fn char(&self) -> Option<char> {
1447        match self.node() {
1448            Node::Char(ch) => Some(*ch),
1449            _ => None,
1450        }
1451    }
1452
1453    /// Prime mark count for a `Prime` node.
1454    pub fn prime_count(&self) -> Option<usize> {
1455        match self.node() {
1456            Node::Prime { count } => Some(*count),
1457            _ => None,
1458        }
1459    }
1460
1461    /// Error message + snippet for an `Error` node.
1462    pub fn error_parts(&self) -> Option<(&'a str, &'a str)> {
1463        match self.node() {
1464            Node::Error { message, snippet } => Some((message, snippet)),
1465            _ => None,
1466        }
1467    }
1468
1469    /// Content mode for root/group nodes.
1470    pub fn content_mode(&self) -> Option<ContentMode> {
1471        match self.node() {
1472            Node::Root { mode, .. } | Node::Group { mode, .. } => Some(*mode),
1473            _ => None,
1474        }
1475    }
1476
1477    /// Group kind for group nodes.
1478    pub fn group_kind(&self) -> Option<GroupKindRef<'a>> {
1479        match self.node() {
1480            Node::Group { kind, .. } => Some(self.group_kind_ref(kind)),
1481            _ => None,
1482        }
1483    }
1484
1485    /// Number of argument slots on a command-like node.
1486    pub fn arg_count(&self) -> usize {
1487        self.doc.ast.arg_slots(self.raw).len()
1488    }
1489
1490    /// Argument at `index`.
1491    pub fn arg(&self, index: usize) -> Option<ArgRef<'a>> {
1492        let arg = self.doc.ast.arg_slots(self.raw).get(index)?.as_ref()?;
1493        Some(self.arg_ref(arg))
1494    }
1495
1496    /// All argument slots.
1497    pub fn arg_slots(&self) -> impl Iterator<Item = Option<ArgRef<'a>>> + 'a {
1498        let this = *self;
1499        (0..self.arg_count()).map(move |index| this.arg(index))
1500    }
1501
1502    /// Scripted base.
1503    pub fn script_base(&self) -> Option<NodeRef<'a>> {
1504        match self.node() {
1505            Node::Scripted { base, .. } => Some(self.sibling(*base)),
1506            _ => None,
1507        }
1508    }
1509
1510    /// Scripted subscript.
1511    pub fn subscript(&self) -> Option<NodeRef<'a>> {
1512        match self.node() {
1513            Node::Scripted { subscript, .. } => subscript.map(|raw| self.sibling(raw)),
1514            _ => None,
1515        }
1516    }
1517
1518    /// Scripted superscript.
1519    pub fn superscript(&self) -> Option<NodeRef<'a>> {
1520        match self.node() {
1521            Node::Scripted { superscript, .. } => superscript.map(|raw| self.sibling(raw)),
1522            _ => None,
1523        }
1524    }
1525
1526    /// Infix left operand.
1527    pub fn infix_left(&self) -> Option<NodeRef<'a>> {
1528        match self.node() {
1529            Node::Infix { left, .. } => Some(self.sibling(*left)),
1530            _ => None,
1531        }
1532    }
1533
1534    /// Infix right operand.
1535    pub fn infix_right(&self) -> Option<NodeRef<'a>> {
1536        match self.node() {
1537            Node::Infix { right, .. } => Some(self.sibling(*right)),
1538            _ => None,
1539        }
1540    }
1541
1542    /// Environment body group.
1543    pub fn env_body(&self) -> Option<NodeRef<'a>> {
1544        match self.node() {
1545            Node::Environment { body, .. } => Some(self.sibling(*body)),
1546            _ => None,
1547        }
1548    }
1549
1550    /// Parse-time byte span, if known.
1551    pub fn span(&self) -> Option<Span> {
1552        self.doc.spans.get(self.raw).cloned()
1553    }
1554
1555    fn node(&self) -> &'a Node {
1556        self.doc.ast.node(self.raw)
1557    }
1558
1559    fn sibling(&self, raw: RawNodeId) -> NodeRef<'a> {
1560        NodeRef { doc: self.doc, raw }
1561    }
1562
1563    fn arg_ref(&self, arg: &'a Argument) -> ArgRef<'a> {
1564        match &arg.value {
1565            ArgumentValue::MathContent(id) => ArgRef::Math(self.sibling(*id)),
1566            ArgumentValue::TextContent(id) => ArgRef::Text(self.sibling(*id)),
1567            ArgumentValue::Delimiter(delimiter) => {
1568                ArgRef::Delimiter(Self::delimiter_ref(delimiter))
1569            }
1570            ArgumentValue::CSName(value) => ArgRef::CSName(value),
1571            ArgumentValue::Dimension(value) => ArgRef::Dimension(value),
1572            ArgumentValue::Integer(value) => ArgRef::Integer(value),
1573            ArgumentValue::KeyVal(value) => ArgRef::KeyVal(value),
1574            ArgumentValue::Column(value) => ArgRef::Column(value),
1575            ArgumentValue::Boolean(value) => ArgRef::Boolean(*value),
1576        }
1577    }
1578
1579    fn group_kind_ref(&self, kind: &'a GroupKind) -> GroupKindRef<'a> {
1580        match kind {
1581            GroupKind::Explicit => GroupKindRef::Explicit,
1582            GroupKind::Implicit => GroupKindRef::Implicit,
1583            GroupKind::Delimited { left, right } => GroupKindRef::Delimited {
1584                left: Self::delimiter_ref(left),
1585                right: Self::delimiter_ref(right),
1586            },
1587            GroupKind::InlineMath => GroupKindRef::InlineMath,
1588        }
1589    }
1590
1591    fn delimiter_ref(delimiter: &'a Delimiter) -> DelimiterRef<'a> {
1592        match delimiter {
1593            Delimiter::None => DelimiterRef::None,
1594            Delimiter::Char(ch) => DelimiterRef::Char(*ch),
1595            Delimiter::Control(name) => DelimiterRef::Control(name),
1596        }
1597    }
1598}
1599
1600#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1601pub enum DelimiterRef<'a> {
1602    None,
1603    Char(char),
1604    Control(&'a str),
1605}
1606
1607#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1608pub enum GroupKindRef<'a> {
1609    Explicit,
1610    Implicit,
1611    Delimited {
1612        left: DelimiterRef<'a>,
1613        right: DelimiterRef<'a>,
1614    },
1615    InlineMath,
1616}
1617
1618/// Read-side view of one command/environment argument value.
1619#[derive(Clone, Copy, Debug)]
1620pub enum ArgRef<'a> {
1621    Math(NodeRef<'a>),
1622    Text(NodeRef<'a>),
1623    Delimiter(DelimiterRef<'a>),
1624    CSName(&'a str),
1625    Dimension(&'a str),
1626    Integer(&'a str),
1627    KeyVal(&'a str),
1628    Column(&'a str),
1629    Boolean(bool),
1630}
1631
1632impl<'a> ArgRef<'a> {
1633    pub fn as_node(self) -> Option<NodeRef<'a>> {
1634        match self {
1635            ArgRef::Math(node) | ArgRef::Text(node) => Some(node),
1636            _ => None,
1637        }
1638    }
1639}
1640
1641#[cfg(test)]
1642impl Document {
1643    fn from_ast_for_test(build: impl FnOnce(&mut Ast)) -> Self {
1644        let mut ast = Ast::new();
1645        build(&mut ast);
1646        ast.assert_invariants();
1647        Document {
1648            ast,
1649            spans: SecondaryMap::new(),
1650            has_errors: false,
1651            id: next_document_id(),
1652        }
1653    }
1654
1655    fn from_ast_with_errors_for_test(build: impl FnOnce(&mut Ast)) -> Self {
1656        let mut ast = Ast::new();
1657        build(&mut ast);
1658        ast.assert_invariants();
1659        let has_errors = ast
1660            .find(ast.root(), |node| matches!(node, Node::Error { .. }))
1661            .is_some();
1662        Document {
1663            ast,
1664            spans: SecondaryMap::new(),
1665            has_errors,
1666            id: next_document_id(),
1667        }
1668    }
1669}
1670
1671#[cfg(test)]
1672mod tests {
1673    use super::*;
1674
1675    #[test]
1676    fn new_document_is_empty_and_editable() {
1677        let doc = Document::new();
1678        assert_eq!(doc.root().kind(), NodeKind::Root);
1679        assert!(!doc.has_errors());
1680        assert!(!doc.is_read_only());
1681        assert_eq!(doc.errors().count(), 0);
1682    }
1683
1684    #[test]
1685    fn document_ids_are_unique_across_construction_and_clone() {
1686        let a = Document::new();
1687        let b = Document::new();
1688        assert_ne!(a.id(), b.id());
1689
1690        let c = a.clone();
1691        assert_ne!(a.id(), c.id());
1692    }
1693
1694    #[test]
1695    fn node_ref_reads_synthesized_chars() {
1696        let doc = Document::from_ast_for_test(|ast| {
1697            let a = ast.new_node(Node::Char('a'));
1698            let b = ast.new_node(Node::Char('b'));
1699            ast.append_child(ast.root(), a);
1700            ast.append_child(ast.root(), b);
1701        });
1702
1703        let mut children = doc.root().children();
1704        let first = children.next().unwrap();
1705        let second = children.next().unwrap();
1706        assert!(first.is_char('a'));
1707        assert!(second.is_char('b'));
1708        assert_eq!(first.next_sibling().map(|n| n.id()), Some(second.id()));
1709        assert_eq!(second.prev_sibling().map(|n| n.id()), Some(first.id()));
1710        assert_eq!(doc.root().children().count(), 2);
1711    }
1712
1713    #[test]
1714    fn find_all_collects_matching_nodes() {
1715        let doc = Document::from_ast_for_test(|ast| {
1716            let a = ast.new_node(Node::Char('a'));
1717            let group = ast.new_node(Node::Group {
1718                children: vec![a],
1719                kind: crate::ast::GroupKind::Explicit,
1720                mode: ContentMode::Math,
1721            });
1722            let b = ast.new_node(Node::Char('b'));
1723            ast.append_child(ast.root(), group);
1724            ast.append_child(ast.root(), b);
1725        });
1726
1727        let chars: Vec<_> = doc
1728            .find_all(doc.root(), |node| node.char().is_some())
1729            .filter_map(|node| node.char())
1730            .collect();
1731        assert_eq!(chars, vec!['a', 'b']);
1732    }
1733
1734    #[test]
1735    fn edit_error_implements_display_and_error() {
1736        fn assert_error<E: std::error::Error>() {}
1737        assert_error::<EditError>();
1738
1739        assert_eq!(
1740            EditError::CannotEditRoot.to_string(),
1741            "cannot edit the root node"
1742        );
1743        assert_eq!(
1744            EditError::SlotShapeMismatch { expected: "group" }.to_string(),
1745            "slot shape mismatch: expected group"
1746        );
1747    }
1748
1749    #[test]
1750    fn create_and_append_children() {
1751        let mut doc = Document::new();
1752        let a = doc.create_char('a').unwrap();
1753        let b = doc.create_char('b').unwrap();
1754        let root = doc.root().id();
1755
1756        doc.append_child(root, a).unwrap();
1757        doc.append_child(root, b).unwrap();
1758
1759        let chars: Vec<_> = doc
1760            .root()
1761            .children()
1762            .filter_map(|node| node.char())
1763            .collect();
1764        assert_eq!(chars, vec!['a', 'b']);
1765    }
1766
1767    #[test]
1768    fn insert_before_and_after() {
1769        let mut doc = Document::new();
1770        let a = doc.create_char('a').unwrap();
1771        let c = doc.create_char('c').unwrap();
1772        let b = doc.create_char('b').unwrap();
1773        let d = doc.create_char('d').unwrap();
1774        let root = doc.root().id();
1775        doc.append_child(root, a).unwrap();
1776        doc.append_child(root, c).unwrap();
1777
1778        doc.insert_before(c, b).unwrap();
1779        doc.insert_after(c, d).unwrap();
1780
1781        let chars: Vec<_> = doc
1782            .root()
1783            .children()
1784            .filter_map(|node| node.char())
1785            .collect();
1786        assert_eq!(chars, vec!['a', 'b', 'c', 'd']);
1787    }
1788
1789    #[test]
1790    fn remove_and_extract() {
1791        let mut doc = Document::new();
1792        let a = doc.create_char('a').unwrap();
1793        let b = doc.create_char('b').unwrap();
1794        let root = doc.root().id();
1795        doc.append_child(root, a).unwrap();
1796        doc.append_child(root, b).unwrap();
1797
1798        let extracted = doc.extract(a).unwrap();
1799        assert_eq!(extracted, a);
1800        doc.remove(b).unwrap();
1801
1802        assert_eq!(doc.root().children().count(), 0);
1803        doc.append_child(root, extracted).unwrap();
1804        assert_eq!(
1805            doc.root().children().next().and_then(|node| node.char()),
1806            Some('a')
1807        );
1808    }
1809
1810    #[test]
1811    fn replace_with_swaps_node() {
1812        let mut doc = Document::new();
1813        let a = doc.create_char('a').unwrap();
1814        let b = doc.create_char('b').unwrap();
1815        let root = doc.root().id();
1816        doc.append_child(root, a).unwrap();
1817
1818        doc.replace_with(a, b).unwrap();
1819
1820        assert_eq!(
1821            doc.root().children().next().and_then(|node| node.char()),
1822            Some('b')
1823        );
1824    }
1825
1826    #[test]
1827    fn set_text_and_char_and_command_name() {
1828        let mut doc = Document::new();
1829        let text = doc.create_text("old").unwrap();
1830        let ch = doc.create_char('a').unwrap();
1831        let cmd = doc.create_command("alpha", Vec::new()).unwrap();
1832
1833        doc.set_text(text, "new").unwrap();
1834        doc.set_char(ch, 'b').unwrap();
1835        doc.set_command_name(cmd, "beta").unwrap();
1836
1837        assert_eq!(doc.node(text).unwrap().text(), Some("new"));
1838        assert_eq!(doc.node(ch).unwrap().char(), Some('b'));
1839        assert_eq!(doc.node(cmd).unwrap().command_name(), Some("beta"));
1840    }
1841
1842    #[test]
1843    fn cannot_edit_root() {
1844        let mut doc = Document::new();
1845        let root = doc.root().id();
1846        assert_eq!(doc.remove(root), Err(EditError::CannotEditRoot));
1847        assert_eq!(doc.extract(root), Err(EditError::CannotEditRoot));
1848    }
1849
1850    #[test]
1851    fn append_to_non_container_fails() {
1852        let mut doc = Document::new();
1853        let parent = doc.create_char('a').unwrap();
1854        let child = doc.create_char('b').unwrap();
1855
1856        assert_eq!(
1857            doc.append_child(parent, child),
1858            Err(EditError::NotAContainer)
1859        );
1860    }
1861
1862    #[test]
1863    fn create_command_with_args_and_read_back() {
1864        let mut doc = Document::new();
1865        let numerator = doc.create_char('a').unwrap();
1866        let denominator = doc.create_char('b').unwrap();
1867        let frac = doc
1868            .create_command(
1869                "frac",
1870                vec![ArgValue::math(numerator), ArgValue::math(denominator)],
1871            )
1872            .unwrap();
1873
1874        let node = doc.node(frac).unwrap();
1875        assert_eq!(node.command_name(), Some("frac"));
1876        assert_eq!(node.arg_count(), 2);
1877        assert_eq!(
1878            node.arg(0)
1879                .and_then(|arg| arg.as_node())
1880                .and_then(|node| node.char()),
1881            Some('a')
1882        );
1883        assert_eq!(
1884            node.arg(1)
1885                .and_then(|arg| arg.as_node())
1886                .and_then(|node| node.char()),
1887            Some('b')
1888        );
1889    }
1890
1891    #[test]
1892    fn duplicate_child_is_rejected() {
1893        let mut doc = Document::new();
1894        let child = doc.create_char('x').unwrap();
1895
1896        assert_eq!(
1897            doc.create_command("dup", vec![ArgValue::math(child), ArgValue::math(child)]),
1898            Err(EditError::DuplicateChild)
1899        );
1900    }
1901
1902    #[test]
1903    fn set_arg_replaces_content() {
1904        let mut doc = Document::new();
1905        let old = doc.create_char('a').unwrap();
1906        let cmd = doc
1907            .create_command("sqrt", vec![ArgValue::math(old)])
1908            .unwrap();
1909        let new = doc.create_char('b').unwrap();
1910
1911        doc.set_arg(cmd, 0, ArgValue::math(new)).unwrap();
1912
1913        let node = doc.node(cmd).unwrap();
1914        assert_eq!(
1915            node.arg(0)
1916                .and_then(|arg| arg.as_node())
1917                .and_then(|node| node.char()),
1918            Some('b')
1919        );
1920    }
1921
1922    #[test]
1923    fn set_arg_preserves_optional_slot_kind() {
1924        use texform_interface::syntax_node::{
1925            Argument as SyntaxArgument, ArgumentKind as SyntaxArgumentKind,
1926            ArgumentValue as SyntaxArgumentValue, ContentMode as M, SyntaxNode,
1927        };
1928
1929        let syntax = SyntaxNode::Root {
1930            mode: M::Math,
1931            children: vec![SyntaxNode::Command {
1932                name: "sqrt".to_string(),
1933                args: vec![Some(SyntaxArgument {
1934                    kind: SyntaxArgumentKind::Optional,
1935                    value: SyntaxArgumentValue::MathContent(SyntaxNode::Char('a')),
1936                })],
1937                known: true,
1938            }],
1939        };
1940        let mut doc = Document::from_syntax(&syntax).unwrap();
1941        let command = doc.root().children().next().unwrap().id();
1942        let replacement = doc.create_char('b').unwrap();
1943
1944        doc.set_arg(command, 0, ArgValue::math(replacement))
1945            .unwrap();
1946
1947        assert_eq!(doc.to_latex().unwrap(), r"\sqrt [ b ]");
1948    }
1949
1950    #[test]
1951    fn set_arg_preserves_star_boolean_slot_kind() {
1952        use texform_interface::syntax_node::{
1953            Argument as SyntaxArgument, ArgumentKind as SyntaxArgumentKind,
1954            ArgumentValue as SyntaxArgumentValue, ContentMode as M, SyntaxNode,
1955        };
1956
1957        let syntax = SyntaxNode::Root {
1958            mode: M::Math,
1959            children: vec![SyntaxNode::Command {
1960                name: "operatorname".to_string(),
1961                args: vec![
1962                    Some(SyntaxArgument {
1963                        kind: SyntaxArgumentKind::Star,
1964                        value: SyntaxArgumentValue::Boolean(false),
1965                    }),
1966                    Some(SyntaxArgument {
1967                        kind: SyntaxArgumentKind::Mandatory,
1968                        value: SyntaxArgumentValue::MathContent(SyntaxNode::Char('x')),
1969                    }),
1970                ],
1971                known: true,
1972            }],
1973        };
1974        let mut doc = Document::from_syntax(&syntax).unwrap();
1975        let command = doc.root().children().next().unwrap().id();
1976
1977        doc.set_arg(command, 0, ArgValue::boolean(true)).unwrap();
1978
1979        assert_eq!(doc.to_latex().unwrap(), r"\operatorname* { x }");
1980    }
1981
1982    #[test]
1983    fn read_only_editing_checks_precede_invalid_node_checks() {
1984        let mut read_only = Document::from_ast_with_errors_for_test(|ast| {
1985            let err = ast.new_node(Node::Error {
1986                message: "bad".to_string(),
1987                snippet: "x".to_string(),
1988            });
1989            ast.append_child(ast.root(), err);
1990        });
1991        let mut other = Document::new();
1992        let foreign = other.create_char('x').unwrap();
1993
1994        assert_eq!(
1995            read_only.append_child(foreign, foreign),
1996            Err(EditError::ReadOnlyDocument)
1997        );
1998        assert_eq!(
1999            read_only.insert_before(foreign, foreign),
2000            Err(EditError::ReadOnlyDocument)
2001        );
2002    }
2003
2004    #[test]
2005    fn wrap_moves_target_inside_wrapper() {
2006        let mut doc = Document::new();
2007        let a = doc.create_char('a').unwrap();
2008        let wrapper = doc.create_group(ContentMode::Math).unwrap();
2009        let root = doc.root().id();
2010        doc.append_child(root, a).unwrap();
2011
2012        let wrapped = doc.wrap(a, wrapper).unwrap();
2013
2014        let group = doc.root().children().next().unwrap();
2015        assert_eq!(group.id(), wrapped);
2016        assert_eq!(
2017            group.children().next().and_then(|node| node.char()),
2018            Some('a')
2019        );
2020    }
2021
2022    #[test]
2023    fn unwrap_splices_group_children_into_parent() {
2024        let mut doc = Document::new();
2025        let group = doc.create_group(ContentMode::Math).unwrap();
2026        let a = doc.create_char('a').unwrap();
2027        let b = doc.create_char('b').unwrap();
2028        doc.append_child(group, a).unwrap();
2029        doc.append_child(group, b).unwrap();
2030        let root = doc.root().id();
2031        doc.append_child(root, group).unwrap();
2032
2033        let children = doc.unwrap(group).unwrap();
2034
2035        assert_eq!(children.len(), 2);
2036        let chars: Vec<_> = doc
2037            .root()
2038            .children()
2039            .filter_map(|node| node.char())
2040            .collect();
2041        assert_eq!(chars, vec!['a', 'b']);
2042    }
2043
2044    #[test]
2045    fn error_tree_is_read_only() {
2046        let doc = Document::from_ast_with_errors_for_test(|ast| {
2047            let err = ast.new_node(Node::Error {
2048                message: "bad".to_string(),
2049                snippet: "x".to_string(),
2050            });
2051            ast.append_child(ast.root(), err);
2052        });
2053        assert!(doc.has_errors());
2054        assert!(doc.is_read_only());
2055        assert_eq!(doc.errors().count(), 1);
2056
2057        let mut doc = doc;
2058        assert_eq!(doc.create_char('z'), Err(EditError::ReadOnlyDocument));
2059    }
2060
2061    #[test]
2062    fn from_syntax_round_trips_clean_tree() {
2063        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2064
2065        let syntax = SyntaxNode::Root {
2066            mode: M::Math,
2067            children: vec![SyntaxNode::Char('a'), SyntaxNode::Char('b')],
2068        };
2069        let doc = Document::from_syntax(&syntax).unwrap();
2070        assert!(!doc.has_errors());
2071
2072        assert_eq!(doc.to_syntax(), syntax);
2073    }
2074
2075    #[test]
2076    fn from_syntax_rejects_zero_count_prime() {
2077        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2078
2079        let syntax = SyntaxNode::Root {
2080            mode: M::Math,
2081            children: vec![SyntaxNode::Prime { count: 0 }],
2082        };
2083
2084        assert_eq!(
2085            Document::from_syntax(&syntax).expect_err("expected invalid prime count"),
2086            FromSyntaxError::InvalidPrimeCount
2087        );
2088    }
2089
2090    #[test]
2091    fn from_syntax_rejects_text_mode_prime() {
2092        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2093
2094        let syntax = SyntaxNode::Root {
2095            mode: M::Text,
2096            children: vec![SyntaxNode::Prime { count: 1 }],
2097        };
2098
2099        assert_eq!(
2100            Document::from_syntax(&syntax).expect_err("expected text-mode prime rejection"),
2101            FromSyntaxError::PrimeInTextMode
2102        );
2103    }
2104
2105    #[test]
2106    fn from_syntax_rejects_text_mode_scripted_prime() {
2107        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2108
2109        let syntax = SyntaxNode::Root {
2110            mode: M::Text,
2111            children: vec![SyntaxNode::Scripted {
2112                base: Box::new(SyntaxNode::Prime { count: 1 }),
2113                subscript: None,
2114                superscript: None,
2115            }],
2116        };
2117
2118        assert_eq!(
2119            Document::from_syntax(&syntax)
2120                .expect_err("expected text-mode scripted prime rejection"),
2121            FromSyntaxError::PrimeInTextMode
2122        );
2123    }
2124
2125    #[test]
2126    fn from_syntax_rejects_text_content_scripted_prime() {
2127        use texform_interface::syntax_node::{
2128            Argument, ArgumentKind, ArgumentValue, ContentMode as M, SyntaxNode,
2129        };
2130
2131        let syntax = SyntaxNode::Root {
2132            mode: M::Math,
2133            children: vec![SyntaxNode::Command {
2134                name: "text".to_string(),
2135                args: vec![Some(Argument {
2136                    kind: ArgumentKind::Mandatory,
2137                    value: ArgumentValue::TextContent(SyntaxNode::Scripted {
2138                        base: Box::new(SyntaxNode::Prime { count: 1 }),
2139                        subscript: None,
2140                        superscript: None,
2141                    }),
2142                })],
2143                known: true,
2144            }],
2145        };
2146
2147        assert_eq!(
2148            Document::from_syntax(&syntax)
2149                .expect_err("expected text-content scripted prime rejection"),
2150            FromSyntaxError::PrimeInTextMode
2151        );
2152    }
2153
2154    #[test]
2155    fn from_syntax_marks_error_tree_read_only() {
2156        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2157
2158        let syntax = SyntaxNode::Root {
2159            mode: M::Math,
2160            children: vec![SyntaxNode::Error {
2161                message: "bad".to_string(),
2162                snippet: "x".to_string(),
2163            }],
2164        };
2165        let doc = Document::from_syntax(&syntax).unwrap();
2166        assert!(doc.has_errors());
2167        assert!(doc.is_read_only());
2168    }
2169
2170    #[test]
2171    fn from_syntax_alone_has_no_spans() {
2172        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2173
2174        let syntax = SyntaxNode::Root {
2175            mode: M::Math,
2176            children: vec![SyntaxNode::Char('a')],
2177        };
2178        let doc = Document::from_syntax(&syntax).unwrap();
2179        assert_eq!(doc.root().children().next().unwrap().span(), None);
2180    }
2181
2182    #[test]
2183    fn span_mapping_aligns_paths_to_node_ids() {
2184        use texform_interface::syntax_node::{ContentMode as M, SyntaxNode};
2185
2186        let syntax = SyntaxNode::Root {
2187            mode: M::Math,
2188            children: vec![SyntaxNode::Char('a'), SyntaxNode::Char('b')],
2189        };
2190        let path_spans = vec![
2191            ("root".to_string(), Span { start: 0, end: 2 }),
2192            ("root.child.0".to_string(), Span { start: 0, end: 1 }),
2193            ("root.child.1".to_string(), Span { start: 1, end: 2 }),
2194        ];
2195
2196        let doc = Document::from_syntax_with_spans(&syntax, &path_spans).unwrap();
2197        let mut kids = doc.root().children();
2198        let a = kids.next().unwrap();
2199        let b = kids.next().unwrap();
2200        assert_eq!(a.span(), Some(Span { start: 0, end: 1 }));
2201        assert_eq!(b.span(), Some(Span { start: 1, end: 2 }));
2202        assert_eq!(doc.root().span(), Some(Span { start: 0, end: 2 }));
2203    }
2204
2205    #[test]
2206    fn to_latex_default_and_with_options() {
2207        use crate::serialize::SerializeOptions;
2208
2209        let mut doc = Document::new();
2210        let a = doc.create_char('a').unwrap();
2211        let b = doc.create_char('b').unwrap();
2212        let root = doc.root().id();
2213        doc.append_child(root, a).unwrap();
2214        doc.append_child(root, b).unwrap();
2215
2216        assert_eq!(doc.to_latex().unwrap(), "a b");
2217        assert_eq!(format!("{doc}"), "a b");
2218
2219        let opts = SerializeOptions::default();
2220        assert_eq!(doc.to_latex_with(&opts).unwrap(), "a b");
2221    }
2222}