tom 0.0.1

Yet another format-preserving TOML parser/manipulator.
Documentation
use std::cmp;

use {
    TomlDoc, Symbol, TextUnit, TextRange, ChunkedText,
    tree::{NodeId, TreeData},
};

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct CstNode(pub(crate) NodeId);

pub enum CstNodeKind<'a> {
    Leaf(&'a str),
    Internal(CstChildren<'a>),
}

impl CstNode {
    pub fn symbol(self, doc: &TomlDoc) -> Symbol {
        *match self.0.data(&doc.tree) {
            TreeData::Internal(s) => s,
            TreeData::Leaf((s, _)) => s,
        }
    }

    pub fn range(self, doc: &TomlDoc) -> TextRange {
        assert!(
            !doc.edit_in_progress,
            "range info is unavailable during edit"
        );
        doc.data[self.0.to_idx()].range
    }

    pub fn kind(self, doc: &TomlDoc) -> CstNodeKind {
        match self.0.data(&doc.tree) {
            TreeData::Leaf((_, idx)) => CstNodeKind::Leaf(doc.intern.resolve(*idx)),
            TreeData::Internal(_) => CstNodeKind::Internal(self.children(doc)),
        }
    }

    pub fn is_leaf(self, doc: &TomlDoc) -> bool {
        match self.kind(doc) {
            CstNodeKind::Leaf(_) => true,
            CstNodeKind::Internal(_) => false,
        }
    }

    pub fn parent(self, doc: &TomlDoc) -> Option<CstNode> {
        self.0.parent(&doc.tree).map(CstNode)
    }

    pub fn children(self, doc: &TomlDoc) -> CstChildren {
        CstChildren { doc, node: self }
    }

    pub fn next_sibling(self, doc: &TomlDoc) -> Option<CstNode> {
        self.0.next_sibling(&doc.tree).map(CstNode)
    }

    pub fn prev_sibling(self, doc: &TomlDoc) -> Option<CstNode> {
        self.0.prev_sibling(&doc.tree).map(CstNode)
    }

    pub fn get_text(self, doc: &TomlDoc) -> String {
        self.chunked_text(doc).to_string()
    }

    pub(crate) fn chunked_text<'a>(self, doc: &'a TomlDoc) -> impl ChunkedText + 'a {
        struct Chunks<'a> {
            root: CstNode,
            doc: &'a TomlDoc,
        }

        impl<'a> Chunks<'a> {
            fn go<F: FnMut(&str) -> Result<(), T>, T>(
                &self,
                node: CstNode,
                f: &mut F,
            ) -> Result<(), T> {
                match node.kind(self.doc) {
                    CstNodeKind::Leaf(text) => f(text)?,
                    CstNodeKind::Internal(children) => {
                        for child in children {
                            self.go(child, f)?;
                        }
                    }
                }
                Ok(())
            }
        }

        impl<'a> ChunkedText for Chunks<'a> {
            fn for_each_chunk<F: FnMut(&str) -> Result<(), T>, T>(
                &self,
                mut f: F,
            ) -> Result<(), T> {
                self.go(self.root, &mut f)
            }
        }

        Chunks { root: self, doc }
    }

    pub(crate) fn chunked_substring<'a>(
        self,
        doc: &'a TomlDoc,
        range: TextRange,
    ) -> impl ChunkedText + 'a {
        assert!(
            !doc.edit_in_progress,
            "range info is unavailable during edit"
        );

        struct Chunks<'a> {
            root: CstNode,
            doc: &'a TomlDoc,
            range: TextRange,
        }

        impl<'a> Chunks<'a> {
            fn go<F: FnMut(&str) -> Result<(), T>, T>(
                &self,
                node: CstNode,
                f: &mut F,
            ) -> Result<(), T> {
                let node_range = node.range(self.doc);
                let rel_range = match intersect(self.range, node_range) {
                    None => return Ok(()),
                    Some(range) => relative_range(node_range.start(), range),
                };
                match node.kind(self.doc) {
                    CstNodeKind::Leaf(text) => f(&text[rel_range])?,
                    CstNodeKind::Internal(children) => {
                        for child in children {
                            self.go(child, f)?;
                        }
                    }
                }
                Ok(())
            }
        }

        impl<'a> ChunkedText for Chunks<'a> {
            fn for_each_chunk<F: FnMut(&str) -> Result<(), T>, T>(
                &self,
                mut f: F,
            ) -> Result<(), T> {
                self.go(self.root, &mut f)
            }
        }
        Chunks {
            root: self,
            doc,
            range,
        }
    }

    pub fn debug(self, doc: &TomlDoc) -> String {
        if doc.edit_in_progress {
            format!("{}@[??:??)", self.symbol(doc).name())
        } else {
            format!("{}@{:?}", self.symbol(doc).name(), self.range(doc))
        }
    }
}

#[derive(Clone, Copy)]
pub struct CstChildren<'a> {
    doc: &'a TomlDoc,
    node: CstNode,
}

impl<'a> CstChildren<'a> {
    pub fn first(self) -> Option<CstNode> {
        self.node.0.first_child(&self.doc.tree).map(CstNode)
    }
    pub fn last(self) -> Option<CstNode> {
        self.node.0.last_child(&self.doc.tree).map(CstNode)
    }
    pub fn iter(self) -> CstChildrenIter<'a> {
        CstChildrenIter {
            doc: self.doc,
            curr: self.first(),
        }
    }
    pub fn rev(self) -> RevCstChildrenIter<'a> {
        RevCstChildrenIter {
            doc: self.doc,
            curr: self.last(),
        }
    }
}

impl<'a> IntoIterator for CstChildren<'a> {
    type Item = CstNode;
    type IntoIter = CstChildrenIter<'a>;
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

#[derive(Clone)]
pub struct CstChildrenIter<'a> {
    pub(crate) doc: &'a TomlDoc,
    curr: Option<CstNode>,
}

impl<'a> Iterator for CstChildrenIter<'a> {
    type Item = CstNode;
    fn next(&mut self) -> Option<CstNode> {
        self.curr.map(|node| {
            self.curr = node.next_sibling(self.doc);
            node
        })
    }
}

#[derive(Clone)]
pub struct RevCstChildrenIter<'a> {
    doc: &'a TomlDoc,
    curr: Option<CstNode>,
}

impl<'a> Iterator for RevCstChildrenIter<'a> {
    type Item = CstNode;
    fn next(&mut self) -> Option<CstNode> {
        self.curr.map(|node| {
            self.curr = node.prev_sibling(self.doc);
            node
        })
    }
}

fn intersect(r1: TextRange, r2: TextRange) -> Option<TextRange> {
    let start = cmp::max(r1.start(), r2.start());
    let end = cmp::min(r1.end(), r2.end());
    if end > start {
        Some(TextRange::from_to(start, end))
    } else {
        None
    }
}

fn relative_range(offset: TextUnit, range: TextRange) -> TextRange {
    TextRange::from_to(range.start() - offset, range.end() - offset)
}