regast-syntax 0.1.0

Lossless syntax tree for the regast regular expression engine
Documentation
use std::convert::Infallible;

use super::rep_name;
use crate::{AstKind, Cursor, Flow, Pattern, Visitor, visit};

impl Pattern {
    #[must_use]
    pub fn to_sexpr(&self) -> String {
        match visit(self, SExpr::default()) {
            Ok(output) => output,
            Err(error) => match error {},
        }
    }
}

#[derive(Default)]
struct SExpr {
    stack: Vec<SExprFrame>,
    root: Option<String>,
}

struct SExprFrame {
    head: String,
    children: Vec<String>,
}

impl Visitor for SExpr {
    type Output = String;
    type Err = Infallible;

    fn enter(&mut self, node: Cursor<'_>) -> Result<Flow, Self::Err> {
        self.stack.push(SExprFrame {
            head: node_head(node),
            children: Vec::new(),
        });
        Ok(Flow::Continue)
    }

    fn leave(&mut self, _node: Cursor<'_>) -> Result<(), Self::Err> {
        let frame = self.stack.pop().expect("enter precedes leave");
        let completed = if frame.children.is_empty() {
            format!("({})", frame.head)
        } else {
            format!("({} {})", frame.head, frame.children.join(" "))
        };
        if let Some(parent) = self.stack.last_mut() {
            parent.children.push(completed);
        } else {
            self.root = Some(completed);
        }
        Ok(())
    }

    fn finish(self) -> Result<Self::Output, Self::Err> {
        Ok(self.root.expect("pattern always has a root"))
    }
}

fn node_head(node: Cursor<'_>) -> String {
    match node.kind() {
        AstKind::Empty => "empty".into(),
        AstKind::Anchor { kind } => format!("anchor {kind:?}"),
        AstKind::Literal { c, .. } => format!("literal {c:?}"),
        AstKind::Dot => "dot".into(),
        AstKind::Class { .. } => format!("class {:?}", node.text()),
        AstKind::Group { .. } => "group".into(),
        AstKind::Alt { .. } => "alt".into(),
        AstKind::Concat { .. } => "concat".into(),
        AstKind::Repeat { kind, greedy, .. } => format!(
            "repeat {} {}",
            rep_name(*kind),
            if *greedy { "greedy" } else { "lazy" }
        ),
    }
}