regast-syntax 0.1.0

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

use serde_json::{Value, json};

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

impl Pattern {
    #[must_use]
    pub fn to_json(&self, shape: JsonShape) -> Value {
        match shape {
            JsonShape::Tree => json!({
                "schema_version": 1,
                "pattern": self.source(),
                "shape": "tree",
                "root": run_visitor(self, TreeJson::default()),
            }),
            JsonShape::Arena => json!({
                "schema_version": 1,
                "pattern": self.source(),
                "shape": "arena",
                "root": self.root_id().0,
                "nodes": run_visitor(self, ArenaJson::default()),
            }),
        }
    }
}

fn run_visitor<V>(pattern: &Pattern, visitor: V) -> V::Output
where
    V: Visitor<Err = Infallible>,
{
    match visit(pattern, visitor) {
        Ok(output) => output,
        Err(error) => match error {},
    }
}

#[derive(Default)]
struct TreeJson {
    stack: Vec<Value>,
    root: Option<Value>,
}

impl Visitor for TreeJson {
    type Output = Value;
    type Err = Infallible;

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

    fn leave(&mut self, _node: Cursor<'_>) -> Result<(), Self::Err> {
        let completed = self.stack.pop().expect("enter precedes leave");
        if let Some(parent) = self.stack.last_mut() {
            let object = parent.as_object_mut().expect("node JSON is an object");
            object
                .entry("children")
                .or_insert_with(|| Value::Array(Vec::new()))
                .as_array_mut()
                .expect("children is an array")
                .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"))
    }
}

#[derive(Default)]
struct ArenaJson {
    nodes: Vec<Value>,
}

impl Visitor for ArenaJson {
    type Output = Vec<Value>;
    type Err = Infallible;

    fn enter(&mut self, node: Cursor<'_>) -> Result<Flow, Self::Err> {
        self.nodes.push(json!({
            "id": node.id().0,
            "span": [node.span().start, node.span().end],
            "parent": node.parent().map(|parent| parent.id().0),
            "kind": kind_name(node.kind()),
            "children": node.children().map(|child| child.id().0).collect::<Vec<_>>(),
            "text": node.text(),
        }));
        Ok(Flow::Continue)
    }

    fn finish(mut self) -> Result<Self::Output, Self::Err> {
        self.nodes.sort_by_key(|node| node["id"].as_u64());
        Ok(self.nodes)
    }
}

fn node_json(cursor: Cursor<'_>) -> Value {
    let mut value = json!({
        "id": cursor.id().0,
        "kind": kind_name(cursor.kind()),
        "span": [cursor.span().start, cursor.span().end],
        "text": cursor.text(),
    });
    let object = value.as_object_mut().expect("object literal");
    match cursor.kind() {
        AstKind::Literal { c, escaped } => {
            object.insert("c".into(), json!(c));
            object.insert("escaped".into(), json!(escaped));
        }
        AstKind::Class { negated, set, .. } => {
            object.insert("negated".into(), json!(negated));
            object.insert("set".into(), json!(set));
        }
        AstKind::Group { kind, .. } => add_group_fields(object, kind),
        AstKind::Repeat { kind, greedy, .. } => {
            object.insert("rep".into(), json!(rep_name(*kind)));
            object.insert("greedy".into(), json!(greedy));
        }
        AstKind::Anchor { kind } => {
            object.insert("anchor".into(), json!(kind));
        }
        AstKind::Empty | AstKind::Dot | AstKind::Alt { .. } | AstKind::Concat { .. } => {}
    }
    value
}

fn add_group_fields(object: &mut serde_json::Map<String, Value>, kind: &GroupKind) {
    match kind {
        GroupKind::Capture { index, name } => {
            object.insert("capture".into(), json!(index));
            object.insert("name".into(), json!(name));
        }
        GroupKind::NonCapture => {
            object.insert("capture".into(), Value::Null);
        }
    }
}