hamelin_lib 0.23.2

Core library for Hamelin query language
Documentation
//! Literal expression builders
//!
//! This module provides builders for creating various types of literal expressions.

use crate::antlr::strip_numeric_separators;
use crate::tree::ast::expression::{
    BooleanLiteral, DecimalLiteral, DoubleLiteral, Expression, IntLiteral, IntervalLiteral,
    IntervalUnit, NullLiteral, StringLiteral,
};
use crate::tree::ast::node::Span;

use super::{ExpressionBuilder, IntoExpressionBuilder};

/// Builder for integer literals
#[derive(Debug)]
pub struct IntLiteralBuilder {
    value: i64,
}

impl IntLiteralBuilder {
    pub fn new(value: i64) -> Self {
        Self { value }
    }
}

impl ExpressionBuilder for IntLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: IntLiteral { int: self.value }.into(),
        }
    }
}

impl IntoExpressionBuilder for IntLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

/// Builder for double/float literals
#[derive(Debug)]
pub struct DoubleLiteralBuilder {
    value: f64,
}

impl DoubleLiteralBuilder {
    pub fn new(value: f64) -> Self {
        Self { value }
    }
}

impl ExpressionBuilder for DoubleLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: DoubleLiteral { value: self.value }.into(),
        }
    }
}

impl IntoExpressionBuilder for DoubleLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

/// Builder for decimal literals
///
/// Provides functionality to create decimal literals either from string representations
/// or from raw components (unscaled value, precision, scale).
#[derive(Debug)]
pub struct DecimalLiteralBuilder {
    unscaled_value: i128,
    precision: u32,
    scale: u32,
}

impl DecimalLiteralBuilder {
    pub fn new(unscaled_value: i128, precision: u32, scale: u32) -> Self {
        Self {
            unscaled_value,
            precision,
            scale,
        }
    }

    /// Create a decimal from a string representation like "3.14"
    ///
    /// This method automatically determines the precision and scale from the string format.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use hamelin_lib::tree::builder::DecimalLiteralBuilder;
    ///
    /// let decimal = DecimalLiteralBuilder::from_string("123.45").unwrap();
    /// // Creates a decimal with unscaled_value=12345, precision=5, scale=2
    /// ```
    pub fn from_string(s: &str) -> Result<Self, String> {
        let s = strip_numeric_separators(s);
        let decimal_pos = s.find('.').unwrap_or(s.len());
        let integer_part = &s[..decimal_pos];
        let fractional_part = if decimal_pos < s.len() {
            &s[decimal_pos + 1..]
        } else {
            ""
        };

        let scale = fractional_part.len() as u32;
        // Calculate precision by counting digits only, excluding the negative sign
        let integer_digit_count = if integer_part.starts_with('-') {
            integer_part.len() - 1 // Exclude the negative sign
        } else {
            integer_part.len()
        };
        let precision = (integer_digit_count + scale as usize) as u32;

        // Remove the decimal point and parse as integer
        let unscaled_str = format!("{}{}", integer_part, fractional_part);
        let unscaled_value = unscaled_str
            .parse::<i128>()
            .map_err(|e| format!("Invalid decimal format: {}", e))?;

        Ok(Self::new(unscaled_value, precision, scale))
    }
}

impl ExpressionBuilder for DecimalLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: DecimalLiteral {
                unscaled_value: self.unscaled_value,
                precision: self.precision,
                scale: self.scale,
            }
            .into(),
        }
    }
}

impl IntoExpressionBuilder for DecimalLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

/// Builder for string literals
#[derive(Debug)]
pub struct StringLiteralBuilder {
    value: String,
}

impl StringLiteralBuilder {
    pub fn new(value: impl Into<String>) -> Self {
        Self {
            value: value.into(),
        }
    }
}

impl ExpressionBuilder for StringLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: StringLiteral {
                value: self.value.clone(),
            }
            .into(),
        }
    }
}

impl IntoExpressionBuilder for StringLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

/// Builder for boolean literals
#[derive(Debug)]
pub struct BoolLiteralBuilder {
    value: bool,
}

impl BoolLiteralBuilder {
    pub fn new(value: bool) -> Self {
        Self { value }
    }
}

impl ExpressionBuilder for BoolLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: BooleanLiteral { value: self.value }.into(),
        }
    }
}

impl IntoExpressionBuilder for BoolLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

/// Builder for null literal
#[derive(Debug)]
pub struct NullLiteralBuilder;

impl NullLiteralBuilder {
    pub fn new() -> Self {
        Self
    }
}

impl IntoExpressionBuilder for NullLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

/// Builder for interval literals
#[derive(Debug)]
pub struct IntervalLiteralBuilder {
    value: i64,
    unit: IntervalUnit,
}

impl IntervalLiteralBuilder {
    pub fn new(value: i64, unit: IntervalUnit) -> Self {
        Self { value, unit }
    }
}

impl ExpressionBuilder for IntervalLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: IntervalLiteral {
                value: self.value,
                unit: self.unit.clone(),
            }
            .into(),
        }
    }
}

impl IntoExpressionBuilder for IntervalLiteralBuilder {
    fn into_expression_builder(self) -> Box<dyn ExpressionBuilder> {
        Box::new(self)
    }
}

impl Default for NullLiteralBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl ExpressionBuilder for NullLiteralBuilder {
    fn build(&self) -> Expression {
        Expression {
            span: Span::NONE,
            kind: NullLiteral.into(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;
    use rstest::rstest;

    #[rstest]
    #[case("123.45", 12345, 5, 2, "positive decimal")]
    #[case("-123.45", -12345, 5, 2, "negative decimal - should exclude negative sign from precision")]
    #[case("42", 42, 2, 0, "positive integer")]
    #[case("-42", -42, 2, 0, "negative integer - should exclude negative sign from precision")]
    #[case("1000", 1000, 4, 0, "integer with zero scale")]
    #[case("0.123456789", 123456789, 10, 9, "high precision decimal")]
    #[case("-0.123456789", -123456789, 10, 9, "negative high precision decimal")]
    #[case("0", 0, 1, 0, "zero")]
    #[case("-0", 0, 1, 0, "negative zero")]
    #[case("1.0", 10, 2, 1, "decimal with trailing zero")]
    fn test_decimal_from_string(
        #[case] input: &str,
        #[case] expected_unscaled: i128,
        #[case] expected_precision: u32,
        #[case] expected_scale: u32,
        #[case] description: &str,
    ) {
        let decimal = DecimalLiteralBuilder::from_string(input).unwrap();

        assert_eq!(
            decimal.unscaled_value, expected_unscaled,
            "Failed for {}: unscaled value mismatch",
            description
        );
        assert_eq!(
            decimal.precision, expected_precision,
            "Failed for {}: precision mismatch",
            description
        );
        assert_eq!(
            decimal.scale, expected_scale,
            "Failed for {}: scale mismatch",
            description
        );
    }

    #[rstest]
    #[case("")]
    #[case("abc")]
    #[case("1.2.3")]
    #[case("--123")]
    fn test_decimal_from_string_invalid(#[case] input: &str) {
        let result = DecimalLiteralBuilder::from_string(input);
        assert!(result.is_err(), "Expected error for input: {}", input);
    }
}