selene-db-gql 1.3.0

ISO/IEC 39075:2024 GQL parser, planner, optimizer, and executor for selene-db.
Documentation
//! Expression pair-to-AST builders.

mod call;
mod literal;
mod predicate;
mod type_name;

use pest::iterators::Pair;

use crate::{
    ast::{BinaryOp, GqlType, Literal, SourceSpan, UnaryOp, ValueExpr},
    error::ParserError,
};

use super::{
    Rule, build_typed_param_ref, db_string_pair, db_string_param, first_child, span,
    unexpected_pair,
};

pub(super) fn build_value_expr(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    match pair.as_rule() {
        Rule::expr => build_value_expr(first_child(pair)?),
        Rule::or_expr => build_left_assoc(pair, Rule::or_kw, |_| BinaryOp::Or),
        Rule::xor_expr => build_left_assoc(pair, Rule::xor_kw, |_| BinaryOp::Xor),
        Rule::and_expr => build_left_assoc(pair, Rule::and_kw, |_| BinaryOp::And),
        Rule::not_expr => build_not_expr(pair),
        Rule::is_expr => build_is_expr(pair),
        Rule::comparison => build_left_assoc(pair, Rule::comp_op, build_comparison_op),
        Rule::concat => build_repeated_same_op(pair, BinaryOp::Concat),
        Rule::addition => build_left_assoc(pair, Rule::add_op, build_add_op),
        Rule::multiplication => build_left_assoc(pair, Rule::mul_op, build_mul_op),
        Rule::unary => build_unary(pair),
        Rule::postfix => build_postfix(pair),
        Rule::primary => build_value_expr(first_child(pair)?),
        Rule::literal => literal::build_literal_expr(pair),
        Rule::list_lit => literal::build_list_lit(pair),
        Rule::record_constructor => build_record_constructor(pair),
        Rule::var_ref => Ok(ValueExpr::Variable {
            name: db_string_pair(pair)?,
            span: source_span,
        }),
        Rule::param_ref => Ok(ValueExpr::Parameter {
            name: db_string_param(pair)?,
            declared_type: None,
            span: source_span,
        }),
        Rule::typed_param_ref => {
            let (name, declared_type, span) = build_typed_param_ref(pair)?;
            Ok(ValueExpr::Parameter {
                name,
                declared_type,
                span,
            })
        }
        Rule::function_call => call::build_function_call(pair),
        Rule::elements_function => call::build_elements_function(pair),
        Rule::current_datetime_function => call::build_current_datetime_function(pair),
        Rule::duration_between_expr => call::build_duration_between_expr(pair),
        Rule::duration_lit => literal::build_literal_child_expr(pair),
        Rule::scalar_keyword_function_call => call::build_scalar_keyword_function_call(pair),
        Rule::normalize_expr => call::build_normalize_expr(pair),
        Rule::aggregate_expr => call::build_aggregate_expr(first_child(pair)?),
        Rule::paren_expr => build_value_expr(first_child(pair)?),
        Rule::all_different_expr => {
            call::build_expr_list_predicate(pair, call::PredicateKind::AllDifferent)
        }
        Rule::same_expr => call::build_expr_list_predicate(pair, call::PredicateKind::Same),
        Rule::property_exists_expr => call::build_property_exists(pair),
        Rule::exists_expr => call::build_exists(pair),
        Rule::value_subquery_expr => call::build_value_subquery(pair),
        Rule::case_expr => call::build_case_expr(first_child(pair)?),
        Rule::simple_case | Rule::searched_case => call::build_case_expr(pair),
        Rule::cast_expr => build_cast_expr(pair),
        Rule::labels_expr => call::build_labels_function(pair),
        Rule::path_constructor => call::build_path_constructor(pair),
        Rule::trim_expr => call::build_trim_expr(pair),
        _ => Err(unexpected_pair(pair, "expected value expression")),
    }
}

pub(super) fn build_type_name(pair: Pair<'_, Rule>) -> Result<GqlType, ParserError> {
    type_name::build_type_name(pair)
}

fn build_cast_expr(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut value_pair: Option<Pair<'_, Rule>> = None;
    let mut type_pair: Option<Pair<'_, Rule>> = None;
    for child in pair.into_inner() {
        match child.as_rule() {
            Rule::expr => value_pair = Some(child),
            Rule::type_name => type_pair = Some(child),
            _ => {}
        }
    }
    let value_pair = value_pair.ok_or_else(|| {
        ParserError::syntax("CAST is missing source expression", source_span, None)
    })?;
    let type_pair = type_pair
        .ok_or_else(|| ParserError::syntax("CAST is missing target type", source_span, None))?;
    let value = build_value_expr(value_pair)?;
    let target_type = build_type_name(type_pair)?;
    Ok(ValueExpr::Cast {
        value: Box::new(value),
        target_type: Box::new(target_type),
        span: source_span,
    })
}

/// Decode a `string_lit` pair into raw text plus its source spelling class.
pub(super) fn decode_string_text_with_kind(
    pair: &Pair<'_, Rule>,
) -> Result<(String, crate::ast::CharacterStringLiteralKind), ParserError> {
    literal::decode_string_text_with_kind(pair)
}

fn build_left_assoc(
    pair: Pair<'_, Rule>,
    op_rule: Rule,
    op_from_pair: fn(&Pair<'_, Rule>) -> BinaryOp,
) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut children = pair.into_inner();
    let first = children
        .next()
        .ok_or_else(|| ParserError::syntax("expression is empty", source_span, None))?;
    let mut value = build_value_expr(first)?;

    while let Some(op_pair) = children.next() {
        if op_pair.as_rule() != op_rule {
            return Err(unexpected_pair(op_pair, "expected binary operator"));
        }
        let rhs_pair = children.next().ok_or_else(|| {
            ParserError::syntax(
                "binary operator is missing right operand",
                source_span,
                None,
            )
        })?;
        let rhs = build_value_expr(rhs_pair)?;
        value = binary(value, op_from_pair(&op_pair), rhs);
    }

    Ok(value)
}

fn build_repeated_same_op(pair: Pair<'_, Rule>, op: BinaryOp) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut children = pair.into_inner();
    let first = children
        .next()
        .ok_or_else(|| ParserError::syntax("expression is empty", source_span, None))?;
    let mut value = build_value_expr(first)?;

    for rhs_pair in children {
        value = binary(value, op, build_value_expr(rhs_pair)?);
    }

    Ok(value)
}

fn binary(lhs: ValueExpr, op: BinaryOp, rhs: ValueExpr) -> ValueExpr {
    let span = SourceSpan::merge(lhs.span(), rhs.span());
    ValueExpr::BinaryOp {
        op,
        lhs: Box::new(lhs),
        rhs: Box::new(rhs),
        span,
    }
}

fn build_comparison_op(pair: &Pair<'_, Rule>) -> BinaryOp {
    match pair.as_str() {
        "=" => BinaryOp::Eq,
        "<>" => BinaryOp::Ne,
        "<" => BinaryOp::Lt,
        "<=" => BinaryOp::Le,
        ">" => BinaryOp::Gt,
        ">=" => BinaryOp::Ge,
        _ => BinaryOp::Eq,
    }
}

fn build_add_op(pair: &Pair<'_, Rule>) -> BinaryOp {
    match pair.as_str() {
        "-" => BinaryOp::Sub,
        _ => BinaryOp::Add,
    }
}

fn build_mul_op(pair: &Pair<'_, Rule>) -> BinaryOp {
    match pair.as_str() {
        "/" => BinaryOp::Div,
        _ => BinaryOp::Mul,
    }
}

fn build_not_expr(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut children = pair.into_inner();
    let Some(first) = children.next() else {
        return Err(ParserError::syntax(
            "expression is empty",
            source_span,
            None,
        ));
    };

    if first.as_rule() == Rule::not_kw {
        let operand_pair = children.next().ok_or_else(|| {
            ParserError::syntax("NOT operator is missing operand", source_span, None)
        })?;
        return Ok(ValueExpr::UnaryOp {
            op: UnaryOp::Not,
            operand: Box::new(build_value_expr(operand_pair)?),
            span: source_span,
        });
    }

    build_value_expr(first)
}

fn build_is_expr(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut children = pair.into_inner();
    let operand_pair = children
        .next()
        .ok_or_else(|| ParserError::syntax("IS expression is empty", source_span, None))?;
    let operand = build_value_expr(operand_pair)?;
    match children.next() {
        Some(suffix) => predicate::apply_is_suffix(operand, suffix, source_span),
        None => Ok(operand),
    }
}

fn build_unary(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut children = pair.into_inner();
    let first = children
        .next()
        .ok_or_else(|| ParserError::syntax("expression is empty", source_span, None))?;

    if first.as_rule() != Rule::sign_op {
        return build_value_expr(first);
    }

    let is_negative = first.as_str() == "-";
    let operand_pair = children.next().ok_or_else(|| {
        ParserError::syntax("unary operator is missing operand", source_span, None)
    })?;
    let operand = build_value_expr(operand_pair)?;

    match (is_negative, operand) {
        (false, value @ ValueExpr::Literal(Literal::Integer(_, _)))
        | (false, value @ ValueExpr::Literal(Literal::RadixInteger(_, _, _)))
        | (false, value @ ValueExpr::Literal(Literal::Decimal(_, _, _)))
        | (false, value @ ValueExpr::Literal(Literal::Float(_, _, _))) => {
            Ok(literal::with_numeric_span(value, source_span))
        }
        (true, ValueExpr::Literal(Literal::Integer(value, _))) => {
            let signed = value.checked_neg().ok_or_else(|| {
                ParserError::syntax(
                    "integer literal overflows i64 after negation",
                    source_span,
                    Some("integer literals must fit in i64".into()),
                )
            })?;
            Ok(ValueExpr::Literal(Literal::Integer(signed, source_span)))
        }
        (true, ValueExpr::Literal(Literal::RadixInteger(value, _, kind))) => {
            let signed = value.checked_neg().ok_or_else(|| {
                ParserError::syntax(
                    "integer literal overflows i64 after negation",
                    source_span,
                    Some("integer literals must fit in i64".into()),
                )
            })?;
            Ok(ValueExpr::Literal(Literal::RadixInteger(
                signed,
                source_span,
                kind,
            )))
        }
        (true, ValueExpr::Literal(Literal::Float(value, _, kind))) => Ok(ValueExpr::Literal(
            Literal::Float(-value, source_span, kind),
        )),
        (true, ValueExpr::Literal(Literal::Decimal(value, _, kind))) => Ok(ValueExpr::Literal(
            Literal::Decimal(-value, source_span, kind),
        )),
        (false, value) => Ok(value),
        (true, value) => Ok(ValueExpr::UnaryOp {
            op: UnaryOp::Negate,
            operand: Box::new(value),
            span: source_span,
        }),
    }
}

fn build_postfix(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let mut children = pair.into_inner();
    let first = children
        .next()
        .ok_or_else(|| ParserError::syntax("postfix expression is empty", source_span, None))?;
    let mut value = build_value_expr(first)?;

    for op_pair in children {
        let op_span = span(&op_pair);
        let op_child = first_child(op_pair)?;
        match op_child.as_rule() {
            Rule::prop_access => {
                let prop = first_child(op_child)?;
                let previous_span = value.span();
                value = ValueExpr::PropertyAccess {
                    target: Box::new(value),
                    key: db_string_pair(prop)?,
                    span: SourceSpan::merge(previous_span, op_span),
                };
            }
            _ => return Err(unexpected_pair(op_child, "expected postfix operator")),
        }
    }

    Ok(value)
}

fn build_record_constructor(pair: Pair<'_, Rule>) -> Result<ValueExpr, ParserError> {
    let source_span = span(&pair);
    let fields = pair
        .into_inner()
        .filter(|child| child.as_rule() == Rule::record_field)
        .map(|field| {
            let field_span = span(&field);
            let mut children = field.into_inner();
            let key_pair = children.next().ok_or_else(|| {
                ParserError::syntax("record field is missing name", field_span, None)
            })?;
            let value_pair = children.next().ok_or_else(|| {
                ParserError::syntax("record field is missing value", field_span, None)
            })?;
            Ok((db_string_pair(key_pair)?, build_value_expr(value_pair)?))
        })
        .collect::<Result<Vec<_>, ParserError>>()?;
    Ok(ValueExpr::RecordLiteral {
        fields,
        span: source_span,
    })
}