hamelin_lib 0.21.9

Core library for Hamelin query language
Documentation
use std::cmp::Ordering;

use once_cell::sync::OnceCell;

use super::{
    expression::{Expression, ExpressionKind},
    ops::{BinaryOp, UnaryPostfixOp, UnaryPrefixOp},
};

type PrecedenceMatcher = fn(&ExpressionKind) -> bool;

static PRECEDENCE: OnceCell<Vec<PrecedenceMatcher>> = OnceCell::new();

/// The position occupied by a child expression within its parent operator.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChildPosition {
    Left,
    Right,
    Operand,
}

/// Whether `child` needs parentheses when printed in `position` beneath
/// `parent`.
///
/// At equal precedence, the child's position decides whether the parser would
/// reconstruct the same AST. In particular, the right child of a
/// left-associative binary operator must retain its parentheses.
pub fn needs_parentheses(
    parent: &ExpressionKind,
    child: &Expression,
    position: ChildPosition,
) -> bool {
    let Some(parent_precedence) = precedence(parent) else {
        return false;
    };
    let Some(child_precedence) = precedence(&child.kind) else {
        return false;
    };

    match child_precedence.cmp(&parent_precedence) {
        Ordering::Less => false,
        Ordering::Greater => true,
        Ordering::Equal => equal_precedence_child(parent) != Some(position),
    }
}

/// Return the precedence of an operator-like expression.
///
/// `None` means the expression is self-delimiting: a literal, reference,
/// function call, or another form whose surrounding syntax already closes it.
fn precedence(expression: &ExpressionKind) -> Option<usize> {
    PRECEDENCE
        .get_or_init(|| {
            vec![
                // | operator=(MINUS | PLUS) expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::UnaryPrefixOperator(unary)
                            if matches!(
                                unary.operator,
                                UnaryPrefixOp::Minus | UnaryPrefixOp::Plus
                            )
                    )
                },
                // | expression '@' ...
                // | expression truncation_unit
                // | expression DOT simpleIdentifier
                // | expression LBRACKET expression RBRACKET
                //
                // These closed postfix forms can be chained without
                // parentheses, so they deliberately share one entry.
                |expression| match expression {
                    ExpressionKind::TsTrunc(_)
                    | ExpressionKind::FieldLookup(_)
                    | ExpressionKind::IndexAccess(_) => true,
                    ExpressionKind::BinaryOperator(binary) => binary.operator == BinaryOp::Dot,
                    _ => false,
                },
                // | expression (ASTERISK | SLASH | PERCENT) expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::BinaryOperator(binary)
                            if matches!(
                                binary.operator,
                                BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo
                            )
                    )
                },
                // | expression (PLUS | MINUS) expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::BinaryOperator(binary)
                            if matches!(binary.operator, BinaryOp::Add | BinaryOp::Subtract)
                    )
                },
                // | operator=(RANGE_INCLUSIVE | RANGE) expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::UnaryPrefixOperator(unary)
                            if matches!(
                                unary.operator,
                                UnaryPrefixOp::RangeInclusive | UnaryPrefixOp::Range
                            )
                    )
                },
                // | expression operator=RANGE
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::UnaryPostfixOperator(unary)
                            if unary.operator == UnaryPostfixOp::Range
                    )
                },
                // | expression (RANGE_INCLUSIVE | RANGE) expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::BinaryOperator(binary)
                            if matches!(
                                binary.operator,
                                BinaryOp::RangeInclusive | BinaryOp::Range
                            )
                    )
                },
                // | expression AS hamelintype
                |expression| match expression {
                    ExpressionKind::Cast(_) => true,
                    ExpressionKind::BinaryOperator(binary) => binary.operator == BinaryOp::As,
                    _ => false,
                },
                // | expression comparison_operator expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::BinaryOperator(binary)
                            if matches!(
                                binary.operator,
                                BinaryOp::Equal
                                    | BinaryOp::NotEqual
                                    | BinaryOp::LessThan
                                    | BinaryOp::LessThanOrEqual
                                    | BinaryOp::GreaterThan
                                    | BinaryOp::GreaterThanOrEqual
                                    | BinaryOp::Is
                                    | BinaryOp::IsNot
                                    | BinaryOp::In
                                    | BinaryOp::NotIn
                            )
                    )
                },
                // | NOT expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::UnaryPrefixOperator(unary)
                            if unary.operator == UnaryPrefixOp::Not
                    )
                },
                // | expression AND expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::BinaryOperator(binary)
                            if binary.operator == BinaryOp::And
                    )
                },
                // | expression OR expression
                |expression| {
                    matches!(
                        expression,
                        ExpressionKind::BinaryOperator(binary)
                            if binary.operator == BinaryOp::Or
                    )
                },
                // | expression COLON expression
                |expression| match expression {
                    ExpressionKind::PairLiteral(_) => true,
                    ExpressionKind::BinaryOperator(binary) => binary.operator == BinaryOp::Colon,
                    _ => false,
                },
                // | lambdaParams ARROW expression
                |expression| matches!(expression, ExpressionKind::Lambda(_)),
            ]
        })
        .iter()
        .enumerate()
        .find_map(|(index, matches)| matches(expression).then_some(index))
}

/// Return the child position that naturally retains the same AST when its
/// precedence equals its parent's precedence.
fn equal_precedence_child(parent: &ExpressionKind) -> Option<ChildPosition> {
    match parent {
        ExpressionKind::UnaryPrefixOperator(_)
        | ExpressionKind::UnaryPostfixOperator(_)
        | ExpressionKind::TsTrunc(_)
        | ExpressionKind::FieldLookup(_)
        | ExpressionKind::IndexAccess(_)
        | ExpressionKind::Cast(_) => Some(ChildPosition::Operand),
        ExpressionKind::BinaryOperator(_) | ExpressionKind::PairLiteral(_) => {
            Some(ChildPosition::Left)
        }
        ExpressionKind::Lambda(_) => Some(ChildPosition::Right),
        ExpressionKind::IntLiteral(_)
        | ExpressionKind::DecimalLiteral(_)
        | ExpressionKind::ScientificLiteral(_)
        | ExpressionKind::DoubleLiteral(_)
        | ExpressionKind::BooleanLiteral(_)
        | ExpressionKind::StringLiteral(_)
        | ExpressionKind::BinaryLiteral(_)
        | ExpressionKind::NullLiteral(_)
        | ExpressionKind::ArrayLiteral(_)
        | ExpressionKind::TupleLiteral(_)
        | ExpressionKind::StructLiteral(_)
        | ExpressionKind::FieldReference(_)
        | ExpressionKind::TemplateParameter(_)
        | ExpressionKind::FunctionCall(_)
        | ExpressionKind::IntervalLiteral(_)
        | ExpressionKind::RowsLiteral(_)
        | ExpressionKind::UnboundRangeLiteral(_)
        | ExpressionKind::Error(_) => None,
    }
}