pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

use crate::lang::label::Label;
use crate::value::span::Span;

#[derive(Clone, PartialEq)]
pub(crate) enum Node {
    Space(Span),
    Comma(Span),
    Tag(Span, Vec<u8>),
    Bytes(Span, Vec<u8>),
    Label(Span, Label),
    List(Span, Vec<Node>),
    Map(Span, Vec<Node>),
    Pipe(Span, Vec<Node>),
    Paren(Span, Vec<Node>),
    Bracket(Span, Vec<Node>),
    And(Span, Vec<Node>),
    Or(Span, Vec<Node>),
    Thunk(Span, Vec<Node>),
    Semicolon(Span, Vec<Node>),
}

impl Default for Node {
    fn default() -> Self {
        Node::Bytes(Span::default(), Vec::default())
    }
}

impl core::fmt::Debug for Node {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let u8l = |n| String::from_utf8_lossy(n).into_owned();

        match self {
            Node::Space(s) => write!(f, "Node::Space({s:?})"),
            Node::Comma(s) => write!(f, "Node::Comma({s:?})"),
            Node::Tag(s, n) => write!(f, "Node::Tag({s:?}, {:?})", u8l(n)),
            Node::Bytes(s, n) => write!(f, "Node::Bytes({s:?}, {:?})", u8l(n)),
            Node::Label(s, n) => write!(f, "Node::Label({s:?}, \"{n:?}\")"),
            Node::List(s, n) => write!(f, "Node::List({s:?}, {n:?})"),
            Node::Map(s, n) => write!(f, "Node::Map({s:?}, {n:?})"),
            Node::Pipe(s, n) => write!(f, "Node::Pipe({s:?}, {n:?})"),
            Node::Paren(s, n) => write!(f, "Node::Paren({s:?}, {n:?})"),
            Node::Bracket(s, n) => write!(f, "Node::Bracket({s:?}, {n:?})"),
            Node::And(s, n) => write!(f, "Node::And({s:?}, {n:?})"),
            Node::Or(s, n) => write!(f, "Node::Or({s:?}, {n:?})"),
            Node::Semicolon(s, n) => write!(f, "Node::Semicolon({s:?}, {n:?})"),
            Node::Thunk(s, n) => write!(f, "Node::Thunk({s:?}, {n:?})"),
        }
    }
}

impl From<&Node> for Span {
    fn from(value: &Node) -> Self {
        match value {
            Node::Space(s) => *s,
            Node::Comma(s) => *s,
            Node::Tag(s, _) => *s,
            Node::Bytes(s, _) => *s,
            Node::Label(s, _) => *s,
            Node::List(s, _) => *s,
            Node::Map(s, _) => *s,
            Node::Pipe(s, _) => *s,
            Node::Paren(s, _) => *s,
            Node::Bracket(s, _) => *s,
            Node::And(s, _) => *s,
            Node::Or(s, _) => *s,
            Node::Thunk(s, _) => *s,
            Node::Semicolon(s, _) => *s,
        }
    }
}

impl Node {
    pub(crate) fn is_pipe(&self) -> bool {
        matches!(
            self,
            Node::Pipe(..) | Node::And(..) | Node::Or(..) | Node::Semicolon(..)
        )
    }

    pub(crate) fn list(&self) -> &[Self] {
        match self {
            Node::Space(..) => &[],
            Node::Comma(..) => &[],
            Node::Tag(..) => &[],
            Node::Bytes(..) => &[],
            Node::Label(..) => &[],
            Node::List(_, n) => n,
            Node::Map(_, n) => n,
            Node::Pipe(_, n) => n,
            Node::Paren(_, n) => n,
            Node::Bracket(_, n) => n,
            Node::And(_, n) => n,
            Node::Or(_, n) => n,
            Node::Semicolon(_, n) => n,
            Node::Thunk(_, n) => n,
        }
    }

    pub(crate) fn list_mut(&mut self) -> Option<&mut Vec<Self>> {
        match self {
            Node::Space(..) => None,
            Node::Comma(..) => None,
            Node::Tag(..) => None,
            Node::Bytes(..) => None,
            Node::Label(..) => None,
            Node::List(_, n) => Some(n),
            Node::Map(_, n) => Some(n),
            Node::Pipe(_, n) => Some(n),
            Node::Paren(_, n) => Some(n),
            Node::Bracket(_, n) => Some(n),
            Node::And(_, n) => Some(n),
            Node::Or(_, n) => Some(n),
            Node::Semicolon(_, n) => Some(n),
            Node::Thunk(_, n) => Some(n),
        }
    }

    pub(crate) fn into_list(self) -> Option<Vec<Self>> {
        match self {
            Node::Space(..) => None,
            Node::Comma(..) => None,
            Node::Tag(..) => None,
            Node::Bytes(..) => None,
            Node::Label(..) => None,
            Node::List(_, n) => Some(n),
            Node::Map(_, n) => Some(n),
            Node::Pipe(_, n) => Some(n),
            Node::Paren(_, n) => Some(n),
            Node::Bracket(_, n) => Some(n),
            Node::And(_, n) => Some(n),
            Node::Or(_, n) => Some(n),
            Node::Semicolon(_, n) => Some(n),
            Node::Thunk(_, n) => Some(n),
        }
    }

    pub(crate) fn as_label(&self) -> Option<&Label> {
        match self {
            Node::Label(_, label) => Some(label),
            _ => None,
        }
    }

    pub(crate) fn try_remove(&mut self, index: usize) -> Option<Node> {
        self.list_mut()
            .filter(|l| l.get(index).is_some())
            .map(|l| l.remove(index))
    }
}

pub(crate) fn pipe_from_nodes(mut nodes: Vec<Node>) -> Node {
    if nodes.len() != 1 {
        return Node::Pipe(span_from_nodes(&nodes), nodes);
    }

    match nodes.pop() {
        Some(node) if node.is_pipe() => node,
        Some(node) => Node::Pipe(span_from_nodes(&nodes), vec![node]),
        _ => Node::Pipe(span_from_nodes(&nodes), Vec::default()),
    }
}

fn span_from_nodes(nodes: &[Node]) -> Span {
    nodes
        .first()
        .map(Span::from)
        .zip(nodes.last().map(Span::from))
        .map(|(s0, s1)| Span::new(s0.lo(), s1.hi()))
        .unwrap_or_default()
}