hamelin_lib 0.9.3

Core library for Hamelin query language
Documentation
use std::{
    fmt::{Display, Formatter},
    ops::RangeInclusive,
};

use antlr_rust::parser_rule_context::ParserRuleContext;

use crate::{
    antlr::interval,
    func::def::SpecialPosition,
    sql::expression::{
        literal::{
            BooleanLiteral, DecimalLiteral, IntegerLiteral, NullLiteral, ScientificLiteral,
            StringLiteral,
        },
        SQLExpression,
    },
    types::{decimal_type::Decimal, Type, BOOLEAN, DOUBLE, INT, STRING, UNKNOWN},
    write_utils::Indent,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExpressionTranslation {
    pub sql: SQLExpression,
    pub typ: Type,
    pub span: Option<RangeInclusive<usize>>,
    pub special: Option<SpecialPosition>,
    pub nested_special: Vec<(SpecialPosition, RangeInclusive<usize>)>,
}

impl ExpressionTranslation {
    pub fn with_defaults(typ: Type, sql: SQLExpression) -> Self {
        Self {
            sql,
            typ,
            span: None,
            special: None,
            nested_special: Vec::new(),
        }
    }

    pub fn with_span<'a, T>(mut self, tree: &T) -> Self
    where
        T: ParserRuleContext<'a>,
    {
        self.span = Some(interval(tree));
        self
    }

    pub fn with_special(mut self, position: SpecialPosition) -> Self {
        self.special = Some(position);
        self
    }

    pub fn with_nested_special(
        mut self,
        position: SpecialPosition,
        span: RangeInclusive<usize>,
    ) -> Self {
        self.nested_special.push((position, span));
        self
    }
}

impl Display for ExpressionTranslation {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Translation yields type:\n{}\n\ninto sql:\n", self.typ,)?;
        self.sql.fmt_indented(f, Indent::default().nested())
    }
}

impl Default for ExpressionTranslation {
    fn default() -> Self {
        Self {
            sql: NullLiteral::default().into(),
            typ: UNKNOWN,
            span: None,
            special: None,
            nested_special: Vec::new(),
        }
    }
}

impl TryFrom<crate::tree::ast::expression::Expression> for ExpressionTranslation {
    type Error = anyhow::Error;

    /// Convert an AST Expression to an ExpressionTranslation.
    /// Only supports literal expressions.
    fn try_from(expr: crate::tree::ast::expression::Expression) -> anyhow::Result<Self> {
        use crate::tree::ast::expression::ExpressionKind;

        match expr.kind {
            ExpressionKind::BooleanLiteral(lit) => Ok(Self::with_defaults(
                BOOLEAN,
                BooleanLiteral { value: lit.value }.into(),
            )),
            ExpressionKind::IntLiteral(lit) => Ok(Self::with_defaults(
                INT,
                IntegerLiteral::from_int(lit.int).into(),
            )),
            ExpressionKind::StringLiteral(lit) => Ok(Self::with_defaults(
                STRING,
                StringLiteral::new(&lit.value).into(),
            )),
            ExpressionKind::NullLiteral(_) => {
                Ok(Self::with_defaults(UNKNOWN, NullLiteral::default().into()))
            }
            ExpressionKind::ScientificLiteral(lit) => Ok(Self::with_defaults(
                DOUBLE,
                ScientificLiteral::new(&format!("{:e}", lit.value)).into(),
            )),
            ExpressionKind::DoubleLiteral(lit) => Ok(Self::with_defaults(
                DOUBLE,
                ScientificLiteral::new(&format!("{:e}", lit.value)).into(),
            )),
            ExpressionKind::DecimalLiteral(lit) => {
                // Convert from unscaled_value/scale representation to string
                let mut s = lit.unscaled_value.to_string();
                let negative = s.starts_with('-');
                if negative {
                    s = s[1..].to_string();
                }
                // Pad with leading zeros if needed
                while s.len() <= lit.scale as usize {
                    s = format!("0{}", s);
                }
                let decimal_pos = s.len() - lit.scale as usize;
                s.insert(decimal_pos, '.');
                if negative {
                    s = format!("-{}", s);
                }
                let decimal_type = Decimal::new(lit.precision as i32, lit.scale as i32)?;
                Ok(Self::with_defaults(
                    Type::Decimal(decimal_type),
                    DecimalLiteral::new(&s).into(),
                ))
            }
            other => anyhow::bail!(
                "Expression to ExpressionTranslation only supports literals, got: {other:?}",
            ),
        }
    }
}