hamelin_lib 0.21.4

Core library for Hamelin query language
Documentation
use std::fmt::{self, Display, Formatter};
use std::rc::Rc;

use super::node::Span;
use crate::antlr::hamelinparser::{
    PipelineAltContextAttrs, PipelineContextAll, PipelineEOFContextAttrs,
};
use crate::antlr::interval;
use crate::antlr::resilient_parse_pipeline;
use crate::err::TranslationErrors;
use crate::tree::ast::context::{FromCst, ParseContext};
use crate::tree::ast::{ParseWithContext, ParseWithErrors, TypeCheck};
use crate::tree::typed_ast::context::StatementTranslationContext;
use crate::tree::typed_ast::pipeline::TypedPipeline;
use crate::write_utils::pad;
use std::ops::RangeInclusive;
use std::sync::Arc;

use super::command::{Command, CommandKind, SelectCommand};
use super::expression::Expression;
use super::node::Spannable;

/// A pipeline is a sequence of commands that transform data
#[derive(Debug, Clone)]
pub struct Pipeline {
    pub span: Span,
    pub commands: Vec<Arc<Command>>,
}

impl ParseWithErrors for Pipeline {
    fn parse_with_errors(input: impl Into<String>) -> (Self, TranslationErrors) {
        Self::parse_with_context(input.into(), ParseContext::new())
    }
}

impl ParseWithContext for Pipeline {
    fn parse_with_context(input: String, mut ctx: ParseContext) -> (Self, TranslationErrors) {
        match resilient_parse_pipeline(input) {
            Ok((csteof, parse_errors)) => match csteof.pipeline() {
                Some(cst) => {
                    let ast = Self::from_cst_with_context(cst, &mut ctx);
                    let mut errors = ctx.take_errors();
                    errors.extend(parse_errors);
                    (ast, errors)
                }
                None => {
                    let errors = ctx.take_errors();
                    let mut all_errors = errors;
                    all_errors.extend(parse_errors);
                    (
                        Self {
                            span: Span::NONE,
                            commands: vec![],
                        },
                        all_errors,
                    )
                }
            },
            Err(e) => {
                let error = ctx
                    .error("fatal error initializing parser")
                    .with_source_boxed(e.into())
                    .emit();
                (
                    Self {
                        span: Span::NONE,
                        commands: vec![],
                    },
                    (*error).clone().single(),
                )
            }
        }
    }
}

impl Spannable for Pipeline {
    fn span(&self) -> Option<RangeInclusive<usize>> {
        self.span.to_range()
    }
}

impl Pipeline {
    /// Set the span for this pipeline
    pub fn at(mut self, span: Span) -> Self {
        self.span = span;
        self
    }
}

impl PartialEq for Pipeline {
    fn eq(&self, other: &Self) -> bool {
        // Compare only the semantic content (commands), ignoring CST
        self.commands == other.commands
    }
}

impl From<Expression> for Pipeline {
    fn from(expr: Expression) -> Self {
        use super::clause::Assignment;
        use super::identifier::{ParsedIdentifier, SimpleIdentifier};

        // Expression-only queries always use "result" as the column name
        let identifier: ParsedIdentifier = SimpleIdentifier::new("result").into();

        let command = Command {
            span: Span::NONE,
            kind: CommandKind::Select(SelectCommand {
                clauses: vec![Arc::new(Assignment::from_parts(identifier, expr).into())],
            }),
        };

        Pipeline {
            span: Span::NONE,
            commands: vec![Arc::new(command)],
        }
    }
}

impl FromCst<Rc<PipelineContextAll<'static>>> for Pipeline {
    fn from_cst_with_context(cst: Rc<PipelineContextAll<'static>>, ctx: &mut ParseContext) -> Self {
        // Convert each command context to a Command
        let commands: Vec<Arc<Command>> = match cst.as_ref() {
            PipelineContextAll::PipelineAltContext(pctx) => pctx
                .command_all()
                .into_iter()
                .map(|cmd_cst| Arc::new(Command::from_cst_with_context(cmd_cst.clone(), ctx)))
                .collect(),
            PipelineContextAll::Error(_) => vec![],
        };

        Pipeline {
            span: interval(cst.as_ref()).into(),
            commands,
        }
    }
}

// ============================================================================
// Display / fmt_indented implementations
// ============================================================================

impl Pipeline {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: usize) -> fmt::Result {
        for (i, cmd) in self.commands.iter().enumerate() {
            if i > 0 {
                writeln!(f)?;
                pad(f, indentation)?;
                write!(f, "| ")?;
            }
            // Commands render at indentation + 2 (past "| " or at the start for first)
            let cmd_indent = if i == 0 { indentation } else { indentation + 2 };
            cmd.fmt_indented(f, cmd_indent)?;
        }
        Ok(())
    }
}

impl Display for Pipeline {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.fmt_indented(f, 0)
    }
}

impl TypeCheck for Pipeline {
    type Output = TypedPipeline;

    fn type_check_with_context(
        ast: Arc<Self>,
        ctx: &mut StatementTranslationContext,
    ) -> Self::Output {
        TypedPipeline::from_ast_with_context(ast, ctx)
    }
}