seuil 0.1.1

A complete, safe JSONata implementation in Rust — JSON query, transform, and expression evaluation
Documentation
//! Abstract syntax tree types for JSONata expressions.

use std::hash::{Hash, Hasher};
use std::ops::Deref;

use regress::Regex;

use crate::Span;

// ---------------------------------------------------------------------------
// Regex literal (ECMAScript-compatible via regress)
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
pub struct RegexLiteral {
    regex: Regex,
    pattern: String,
}

impl RegexLiteral {
    pub fn new(
        pattern: &str,
        case_insensitive: bool,
        multi_line: bool,
    ) -> Result<Self, regress::Error> {
        let mut flags = String::new();
        if case_insensitive {
            flags.push('i');
        }
        if multi_line {
            flags.push('m');
        }
        let regex = Regex::with_flags(pattern, flags.as_str())?;
        Ok(Self {
            regex,
            pattern: pattern.to_string(),
        })
    }

    pub fn is_match(&self, text: &str) -> bool {
        self.regex.find(text).is_some()
    }

    pub fn as_pattern(&self) -> &str {
        &self.pattern
    }

    pub fn get_regex(&self) -> &Regex {
        &self.regex
    }
}

impl Deref for RegexLiteral {
    type Target = Regex;
    fn deref(&self) -> &Self::Target {
        &self.regex
    }
}

impl PartialEq for RegexLiteral {
    fn eq(&self, other: &Self) -> bool {
        self.pattern == other.pattern
    }
}

impl Eq for RegexLiteral {}

impl Hash for RegexLiteral {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.pattern.hash(state);
    }
}

// ---------------------------------------------------------------------------
// Object constructor: Vec of (key_expr, value_expr) pairs
// ---------------------------------------------------------------------------

pub type Object = Vec<(Ast, Ast)>;

// ---------------------------------------------------------------------------
// Sort terms: Vec of (expr, is_descending)
// ---------------------------------------------------------------------------

pub type SortTerms = Vec<(Ast, bool)>;

// ---------------------------------------------------------------------------
// Operators
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
pub enum UnaryOp {
    Minus(Box<Ast>),
    ArrayConstructor(Vec<Ast>),
    ObjectConstructor(Object),
}

#[derive(Debug, PartialEq, Clone)]
pub enum BinaryOp {
    Add,
    Subtract,
    Multiply,
    Divide,
    Modulus,
    Equal,
    NotEqual,
    LessThan,
    GreaterThan,
    LessThanEqual,
    GreaterThanEqual,
    Concat,
    And,
    Or,
    In,
    Map,
    Range,
    FocusBind,
    IndexBind,
    Predicate,
    Apply,
    Bind,
}

impl std::fmt::Display for BinaryOp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(match *self {
            BinaryOp::Add => "+",
            BinaryOp::Subtract => "-",
            BinaryOp::Multiply => "*",
            BinaryOp::Divide => "/",
            BinaryOp::Modulus => "%",
            BinaryOp::Equal => "=",
            BinaryOp::NotEqual => "!=",
            BinaryOp::LessThan => "<",
            BinaryOp::GreaterThan => ">",
            BinaryOp::LessThanEqual => "<=",
            BinaryOp::GreaterThanEqual => ">=",
            BinaryOp::Concat => "&",
            BinaryOp::And => "and",
            BinaryOp::Or => "or",
            BinaryOp::In => "in",
            BinaryOp::Map => ".",
            BinaryOp::Range => "..",
            BinaryOp::FocusBind => "@",
            BinaryOp::IndexBind => "#",
            BinaryOp::Predicate => "[]",
            BinaryOp::Apply => "~>",
            BinaryOp::Bind => ":=",
        })
    }
}

// ---------------------------------------------------------------------------
// AST node kinds
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
pub enum AstKind {
    Empty,
    Null,
    Bool(bool),
    String(String),
    Number(f64),
    Regex(Box<RegexLiteral>),
    Name(String),
    Var(String),
    Unary(UnaryOp),
    Binary(BinaryOp, Box<Ast>, Box<Ast>),
    GroupBy(Box<Ast>, Object),
    OrderBy(Box<Ast>, SortTerms),
    Block(Vec<Ast>),
    Wildcard,
    Descendent,
    Parent,
    Function {
        name: String,
        proc: Box<Ast>,
        args: Vec<Ast>,
        is_partial: bool,
    },
    PartialArg,
    Lambda {
        name: String,
        args: Vec<Ast>,
        body: Box<Ast>,
        thunk: bool,
    },
    Ternary {
        cond: Box<Ast>,
        truthy: Box<Ast>,
        falsy: Option<Box<Ast>>,
    },
    Transform {
        pattern: Box<Ast>,
        update: Box<Ast>,
        delete: Option<Box<Ast>>,
    },

    // Generated by AST post-processing
    Path(Vec<Ast>),
    Filter(Box<Ast>),
    Sort(SortTerms),
    Index(String),
}

// ---------------------------------------------------------------------------
// AST node
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
pub struct Ast {
    pub kind: AstKind,

    /// Span in the original source that introduced this node.
    pub span: Span,

    pub keep_array: bool,
    pub cons_array: bool,
    pub keep_singleton_array: bool,

    /// Optional group-by expression.
    pub group_by: Option<(Span, Object)>,

    /// Optional list of predicate expressions.
    pub predicates: Option<Vec<Ast>>,

    /// Optional list of evaluation stages (filtering, indexing) for path steps.
    pub stages: Option<Vec<Ast>>,

    /// Whether this step produces tuple bindings (for @ and # binds, parent resolution).
    pub tuple: bool,

    /// Variable to bind the index of a step to.
    pub index: Option<String>,

    /// Variable to bind the context of a step to.
    pub focus: Option<String>,

    /// Whether this step seeks the parent context (for % operator).
    pub seeking_parent: bool,
}

impl Default for Ast {
    fn default() -> Ast {
        Ast::new(AstKind::Empty, Span::at(0))
    }
}

impl Ast {
    pub fn new(kind: AstKind, span: Span) -> Self {
        Self {
            kind,
            span,
            keep_array: false,
            cons_array: false,
            keep_singleton_array: false,
            group_by: None,
            predicates: None,
            stages: None,
            tuple: false,
            index: None,
            focus: None,
            seeking_parent: false,
        }
    }
}

/// Checks that brackets are balanced in a picture string (used by datetime formatting).
pub fn check_balanced_brackets(expr: &str) -> Result<(), String> {
    let mut bracket_count: i32 = 0;
    let mut i = 0;
    let chars: Vec<char> = expr.chars().collect();

    while i < chars.len() {
        let ch = chars[i];
        if ch == '[' {
            if i + 1 < chars.len() && chars[i + 1] == '[' {
                i += 1; // escaped [[
            } else {
                bracket_count += 1;
            }
        } else if ch == ']' {
            if i + 1 < chars.len() && chars[i + 1] == ']' {
                i += 1; // escaped ]]
            } else {
                if bracket_count == 0 {
                    return Err(format!("Unbalanced closing bracket found in: {expr}"));
                }
                bracket_count -= 1;
            }
        }
        i += 1;
    }

    if bracket_count != 0 {
        return Err(format!(
            "No matching closing bracket ']' in expression: {expr}"
        ));
    }

    Ok(())
}