product_farm_farmscript/
compiler.rs

1//! Compiler from FarmScript AST to JSON Logic
2//!
3//! Translates FarmScript expressions into JSON Logic format
4//! that can be executed by the rule engine.
5
6use crate::ast::*;
7use serde_json::{json, Value as JsonValue};
8use thiserror::Error;
9
10/// Compilation error
11#[derive(Debug, Error, Clone)]
12pub enum CompileError {
13    #[error("Unsupported expression: {0}")]
14    Unsupported(String),
15
16    #[error("Invalid lambda: {0}")]
17    InvalidLambda(String),
18
19    #[error("Parse error: {0}")]
20    ParseError(String),
21}
22
23impl From<crate::parser::ParseError> for CompileError {
24    fn from(e: crate::parser::ParseError) -> Self {
25        CompileError::ParseError(e.to_string())
26    }
27}
28
29/// Compilation options
30#[derive(Debug, Clone, Default)]
31pub struct CompileOptions {
32    /// Use strict equality (===) by default for == operator
33    pub strict_equality_default: bool,
34    /// Emit safe division as custom operations
35    pub emit_safe_division: bool,
36}
37
38/// The FarmScript to JSON Logic compiler
39#[allow(dead_code)]
40pub struct Compiler {
41    options: CompileOptions,
42}
43
44impl Compiler {
45    /// Create a new compiler with options
46    pub fn new(options: CompileOptions) -> Self {
47        Self { options }
48    }
49
50    /// Create a compiler with default options
51    pub fn default_options() -> Self {
52        Self::new(CompileOptions::default())
53    }
54
55    /// Compile an expression to JSON Logic
56    pub fn compile(&self, expr: &Expr) -> Result<JsonValue, CompileError> {
57        match expr {
58            Expr::Literal(lit) => self.compile_literal(lit),
59            Expr::Var(var) => self.compile_var(var),
60            Expr::Binary(binary) => self.compile_binary(binary),
61            Expr::Unary(unary) => self.compile_unary(unary),
62            Expr::Postfix(postfix) => self.compile_postfix(postfix),
63            Expr::If(if_expr) => self.compile_if(if_expr),
64            Expr::Let(let_expr) => self.compile_let(let_expr),
65            Expr::Call(call) => self.compile_call(call),
66            Expr::MethodCall(method_call) => self.compile_method_call(method_call),
67            Expr::Property(prop) => self.compile_property(prop),
68            Expr::Index(idx) => self.compile_index(idx),
69            Expr::Array(items) => self.compile_array(items),
70            Expr::Lambda(lambda) => self.compile_lambda(lambda),
71            Expr::Query(query) => self.compile_query(query),
72            Expr::Template(parts) => self.compile_template(parts),
73            Expr::NullCoalesce(a, b) => self.compile_null_coalesce(a, b),
74        }
75    }
76
77    fn compile_literal(&self, lit: &Literal) -> Result<JsonValue, CompileError> {
78        Ok(match lit {
79            Literal::Null => JsonValue::Null,
80            Literal::Bool(b) => JsonValue::Bool(*b),
81            Literal::Integer(n) => json!(*n),
82            Literal::Float(n) => json!(*n),
83            Literal::String(s) => JsonValue::String(s.clone()),
84        })
85    }
86
87    fn compile_var(&self, var: &VarExpr) -> Result<JsonValue, CompileError> {
88        // Handle path-style variables by removing leading /
89        let path = if var.name.starts_with('/') {
90            &var.name[1..] // Remove leading slash for JSON Logic
91        } else {
92            &var.name
93        };
94
95        // Convert /path/to/var to path.to.var for JSON Logic dot notation
96        let normalized_path = path.replace('/', ".");
97
98        Ok(json!({ "var": normalized_path }))
99    }
100
101    fn compile_binary(&self, binary: &BinaryExpr) -> Result<JsonValue, CompileError> {
102        // Handle and/or specially to flatten nested expressions
103        match binary.op {
104            BinaryOp::And => return self.compile_logical_chain(&binary.left, &binary.right, "and"),
105            BinaryOp::Or => return self.compile_logical_chain(&binary.left, &binary.right, "or"),
106            _ => {}
107        }
108
109        let left = self.compile(&binary.left)?;
110        let right = self.compile(&binary.right)?;
111
112        let op = match binary.op {
113            BinaryOp::Add => "+",
114            BinaryOp::Sub => "-",
115            BinaryOp::Mul => "*",
116            BinaryOp::Div => "/",
117            BinaryOp::Mod => "%",
118            BinaryOp::Pow => {
119                // JSON Logic doesn't have power, emit as custom operation
120                return Ok(json!({ "pow": [left, right] }));
121            }
122            BinaryOp::SafeDivZero => {
123                // Safe division returning 0: if b == 0 then 0 else a / b
124                return Ok(json!({
125                    "if": [
126                        { "==": [right.clone(), 0] },
127                        0,
128                        { "/": [left, right] }
129                    ]
130                }));
131            }
132            BinaryOp::SafeDivNull => {
133                // Safe division returning null: if b == 0 then null else a / b
134                return Ok(json!({
135                    "if": [
136                        { "==": [right.clone(), 0] },
137                        null,
138                        { "/": [left, right] }
139                    ]
140                }));
141            }
142            BinaryOp::Eq => "==",
143            BinaryOp::StrictEq => "===",
144            BinaryOp::NotEq => "!=",
145            BinaryOp::StrictNotEq => "!==",
146            BinaryOp::Lt => "<",
147            BinaryOp::Gt => ">",
148            BinaryOp::LtEq => "<=",
149            BinaryOp::GtEq => ">=",
150            BinaryOp::And | BinaryOp::Or => unreachable!(), // Handled above
151            BinaryOp::In => "in",
152        };
153
154        Ok(json!({ op: [left, right] }))
155    }
156
157    /// Compile logical and/or chains into flat arrays
158    /// e.g., `a and b and c` becomes `{"and": [a, b, c]}` instead of nested
159    fn compile_logical_chain(&self, left: &Expr, right: &Expr, op: &str) -> Result<JsonValue, CompileError> {
160        let target_op = if op == "and" { BinaryOp::And } else { BinaryOp::Or };
161
162        let mut operands = Vec::new();
163        self.collect_logical_operands(left, target_op, &mut operands)?;
164        self.collect_logical_operands(right, target_op, &mut operands)?;
165
166        Ok(json!({ op: operands }))
167    }
168
169    /// Recursively collect operands for a logical chain
170    fn collect_logical_operands(&self, expr: &Expr, target_op: BinaryOp, operands: &mut Vec<JsonValue>) -> Result<(), CompileError> {
171        if let Expr::Binary(binary) = expr {
172            if binary.op == target_op {
173                // Same operator - flatten
174                self.collect_logical_operands(&binary.left, target_op, operands)?;
175                self.collect_logical_operands(&binary.right, target_op, operands)?;
176                return Ok(());
177            }
178        }
179        // Different expression - compile and add to list
180        operands.push(self.compile(expr)?);
181        Ok(())
182    }
183
184    fn compile_unary(&self, unary: &UnaryExpr) -> Result<JsonValue, CompileError> {
185        let expr = self.compile(&unary.expr)?;
186
187        match unary.op {
188            UnaryOp::Not => Ok(json!({ "!": expr })),
189            UnaryOp::Neg => Ok(json!({ "-": [expr] })),
190            UnaryOp::Plus => Ok(expr), // No-op
191        }
192    }
193
194    fn compile_postfix(&self, postfix: &PostfixExpr) -> Result<JsonValue, CompileError> {
195        let expr = self.compile(&postfix.expr)?;
196
197        match postfix.op {
198            PostfixOp::Truthy => Ok(json!({ "!!": expr })),
199        }
200    }
201
202    fn compile_if(&self, if_expr: &IfExpr) -> Result<JsonValue, CompileError> {
203        let mut args = Vec::new();
204
205        // Main condition and then branch
206        args.push(self.compile(&if_expr.condition)?);
207        args.push(self.compile(&if_expr.then_branch)?);
208
209        // Else-if chains
210        for (cond, then) in &if_expr.else_ifs {
211            args.push(self.compile(cond)?);
212            args.push(self.compile(then)?);
213        }
214
215        // Final else branch
216        if let Some(ref else_branch) = if_expr.else_branch {
217            args.push(self.compile(else_branch)?);
218        }
219
220        Ok(json!({ "if": args }))
221    }
222
223    fn compile_let(&self, let_expr: &LetExpr) -> Result<JsonValue, CompileError> {
224        // JSON Logic doesn't have let bindings, so we inline the value
225        // by substituting variable references in the body
226        let compiled_value = self.compile(&let_expr.value)?;
227        self.compile_with_substitution(&let_expr.body, &let_expr.name, &compiled_value)
228    }
229
230    /// Compile an expression with variable substitution
231    fn compile_with_substitution(&self, expr: &Expr, var_name: &str, replacement: &JsonValue) -> Result<JsonValue, CompileError> {
232        match expr {
233            Expr::Var(v) if v.name == var_name => Ok(replacement.clone()),
234            Expr::Var(v) => self.compile_var(v),
235            Expr::Literal(lit) => self.compile_literal(lit),
236            Expr::Binary(b) => {
237                // Handle and/or specially to flatten
238                match b.op {
239                    BinaryOp::And => {
240                        let mut operands = Vec::new();
241                        self.collect_logical_operands_with_sub(&b.left, BinaryOp::And, &mut operands, var_name, replacement)?;
242                        self.collect_logical_operands_with_sub(&b.right, BinaryOp::And, &mut operands, var_name, replacement)?;
243                        return Ok(json!({ "and": operands }));
244                    }
245                    BinaryOp::Or => {
246                        let mut operands = Vec::new();
247                        self.collect_logical_operands_with_sub(&b.left, BinaryOp::Or, &mut operands, var_name, replacement)?;
248                        self.collect_logical_operands_with_sub(&b.right, BinaryOp::Or, &mut operands, var_name, replacement)?;
249                        return Ok(json!({ "or": operands }));
250                    }
251                    _ => {}
252                }
253
254                let left = self.compile_with_substitution(&b.left, var_name, replacement)?;
255                let right = self.compile_with_substitution(&b.right, var_name, replacement)?;
256                let op = match b.op {
257                    BinaryOp::Add => "+",
258                    BinaryOp::Sub => "-",
259                    BinaryOp::Mul => "*",
260                    BinaryOp::Div => "/",
261                    BinaryOp::Mod => "%",
262                    BinaryOp::Eq => "==",
263                    BinaryOp::StrictEq => "===",
264                    BinaryOp::NotEq => "!=",
265                    BinaryOp::StrictNotEq => "!==",
266                    BinaryOp::Lt => "<",
267                    BinaryOp::Gt => ">",
268                    BinaryOp::LtEq => "<=",
269                    BinaryOp::GtEq => ">=",
270                    BinaryOp::In => "in",
271                    BinaryOp::Pow => return Ok(json!({ "pow": [left, right] })),
272                    BinaryOp::SafeDivZero => {
273                        return Ok(json!({
274                            "if": [{ "==": [right.clone(), 0] }, 0, { "/": [left, right] }]
275                        }));
276                    }
277                    BinaryOp::SafeDivNull => {
278                        return Ok(json!({
279                            "if": [{ "==": [right.clone(), 0] }, null, { "/": [left, right] }]
280                        }));
281                    }
282                    BinaryOp::And | BinaryOp::Or => unreachable!(),
283                };
284                Ok(json!({ op: [left, right] }))
285            }
286            Expr::Unary(u) => {
287                let inner = self.compile_with_substitution(&u.expr, var_name, replacement)?;
288                match u.op {
289                    UnaryOp::Not => Ok(json!({ "!": inner })),
290                    UnaryOp::Neg => Ok(json!({ "-": [inner] })),
291                    UnaryOp::Plus => Ok(inner),
292                }
293            }
294            Expr::Call(c) => {
295                let args: Vec<JsonValue> = c.args.iter()
296                    .map(|a| self.compile_with_substitution(a, var_name, replacement))
297                    .collect::<Result<_, _>>()?;
298                // Delegate to regular call compilation with pre-compiled args
299                self.compile_call_with_args(&c.function, args)
300            }
301            Expr::If(if_expr) => {
302                let mut args = Vec::new();
303                args.push(self.compile_with_substitution(&if_expr.condition, var_name, replacement)?);
304                args.push(self.compile_with_substitution(&if_expr.then_branch, var_name, replacement)?);
305                for (cond, then) in &if_expr.else_ifs {
306                    args.push(self.compile_with_substitution(cond, var_name, replacement)?);
307                    args.push(self.compile_with_substitution(then, var_name, replacement)?);
308                }
309                if let Some(ref else_branch) = if_expr.else_branch {
310                    args.push(self.compile_with_substitution(else_branch, var_name, replacement)?);
311                }
312                Ok(json!({ "if": args }))
313            }
314            Expr::Array(items) => {
315                let compiled: Vec<JsonValue> = items.iter()
316                    .map(|i| self.compile_with_substitution(i, var_name, replacement))
317                    .collect::<Result<_, _>>()?;
318                Ok(JsonValue::Array(compiled))
319            }
320            // For other expressions, fall back to regular compilation
321            _ => self.compile(expr),
322        }
323    }
324
325    /// Collect logical operands with substitution
326    fn collect_logical_operands_with_sub(
327        &self,
328        expr: &Expr,
329        target_op: BinaryOp,
330        operands: &mut Vec<JsonValue>,
331        var_name: &str,
332        replacement: &JsonValue,
333    ) -> Result<(), CompileError> {
334        if let Expr::Binary(binary) = expr {
335            if binary.op == target_op {
336                self.collect_logical_operands_with_sub(&binary.left, target_op, operands, var_name, replacement)?;
337                self.collect_logical_operands_with_sub(&binary.right, target_op, operands, var_name, replacement)?;
338                return Ok(());
339            }
340        }
341        operands.push(self.compile_with_substitution(expr, var_name, replacement)?);
342        Ok(())
343    }
344
345    /// Compile a function call with pre-compiled arguments
346    fn compile_call_with_args(&self, function: &str, args: Vec<JsonValue>) -> Result<JsonValue, CompileError> {
347        match function {
348            "min" => Ok(json!({ "min": args })),
349            "max" => Ok(json!({ "max": args })),
350            "sum" => Ok(json!({ "+": args })),
351            "abs" => {
352                if args.len() != 1 {
353                    return Err(CompileError::Unsupported("abs requires 1 argument".into()));
354                }
355                let x = &args[0];
356                Ok(json!({ "if": [{ "<": [x, 0] }, { "-": [0, x] }, x] }))
357            }
358            "clamp" => {
359                if args.len() != 3 {
360                    return Err(CompileError::Unsupported("clamp requires 3 arguments (min, max, value)".into()));
361                }
362                Ok(json!({ "max": [args[0].clone(), { "min": [args[1].clone(), args[2].clone()] }] }))
363            }
364            "floor" => Ok(json!({ "floor": args })),
365            "ceil" => Ok(json!({ "ceil": args })),
366            "round" => Ok(json!({ "round": args })),
367            "sqrt" => Ok(json!({ "sqrt": args })),
368            "pow" => Ok(json!({ "pow": args })),
369            "len" | "length" | "count" => Ok(json!({ "count": args })),
370            "substr" | "substring" => Ok(json!({ "substr": args })),
371            "cat" | "concat" => Ok(json!({ "cat": args })),
372            "upper" => Ok(json!({ "upper": args })),
373            "lower" => Ok(json!({ "lower": args })),
374            "merge" => Ok(json!({ "merge": args })),
375            other => Ok(json!({ other: args })),
376        }
377    }
378
379    fn compile_call(&self, call: &CallExpr) -> Result<JsonValue, CompileError> {
380        let args: Vec<JsonValue> = call.args.iter()
381            .map(|a| self.compile(a))
382            .collect::<Result<_, _>>()?;
383
384        // Map function names to JSON Logic operations
385        match call.function.as_str() {
386            // Aggregation functions
387            "min" => Ok(json!({ "min": args })),
388            "max" => Ok(json!({ "max": args })),
389            "sum" => {
390                // JSON Logic + takes variadic args
391                Ok(json!({ "+": args }))
392            }
393
394            // Math functions
395            "abs" => {
396                if args.len() != 1 {
397                    return Err(CompileError::Unsupported("abs requires 1 argument".into()));
398                }
399                // abs(x) = if x < 0 then -x else x
400                let x = &args[0];
401                Ok(json!({
402                    "if": [
403                        { "<": [x, 0] },
404                        { "-": [0, x] },
405                        x
406                    ]
407                }))
408            }
409            "round" => {
410                // JSON Logic doesn't have round, emit custom operation
411                Ok(json!({ "round": args }))
412            }
413            "floor" => Ok(json!({ "floor": args })),
414            "ceil" => Ok(json!({ "ceil": args })),
415            "pow" => {
416                if args.len() != 2 {
417                    return Err(CompileError::Unsupported("pow requires 2 arguments".into()));
418                }
419                Ok(json!({ "pow": args }))
420            }
421            "sqrt" => {
422                if args.len() != 1 {
423                    return Err(CompileError::Unsupported("sqrt requires 1 argument".into()));
424                }
425                Ok(json!({ "sqrt": args }))
426            }
427
428            // Clamp: clamp(min, max, value)
429            "clamp" => {
430                if args.len() != 3 {
431                    return Err(CompileError::Unsupported("clamp requires 3 arguments (min, max, value)".into()));
432                }
433                let min_val = &args[0];
434                let max_val = &args[1];
435                let value = &args[2];
436                // max(min_val, min(max_val, value))
437                Ok(json!({
438                    "max": [
439                        min_val,
440                        { "min": [max_val, value] }
441                    ]
442                }))
443            }
444
445            // Safe division
446            "safe_div" => {
447                if args.len() < 2 || args.len() > 3 {
448                    return Err(CompileError::Unsupported("safe_div requires 2-3 arguments".into()));
449                }
450                let a = &args[0];
451                let b = &args[1];
452                let default_val = if args.len() > 2 {
453                    args[2].clone()
454                } else {
455                    json!(0)
456                };
457                Ok(json!({
458                    "if": [
459                        { "==": [b, 0] },
460                        default_val,
461                        { "/": [a, b] }
462                    ]
463                }))
464            }
465
466            // String functions
467            "cat" => Ok(json!({ "cat": args })),
468            "substr" => Ok(json!({ "substr": args })),
469            "len" | "length" => {
470                if args.len() != 1 {
471                    return Err(CompileError::Unsupported("len requires 1 argument".into()));
472                }
473                // JSON Logic doesn't have len, use custom operation
474                Ok(json!({ "len": args }))
475            }
476
477            // Array functions
478            "merge" => Ok(json!({ "merge": args })),
479            "count" => {
480                if args.len() != 1 {
481                    return Err(CompileError::Unsupported("count requires 1 argument".into()));
482                }
483                Ok(json!({ "count": args }))
484            }
485
486            // Data operations
487            "missing" => Ok(json!({ "missing": args })),
488            "missing_some" => {
489                if args.len() != 2 {
490                    return Err(CompileError::Unsupported("missing_some requires 2 arguments".into()));
491                }
492                Ok(json!({ "missing_some": args }))
493            }
494
495            // Debug
496            "log" => {
497                if args.len() != 1 {
498                    return Err(CompileError::Unsupported("log requires 1 argument".into()));
499                }
500                Ok(json!({ "log": args[0] }))
501            }
502
503            // Contains
504            "contains" => {
505                if args.len() != 2 {
506                    return Err(CompileError::Unsupported("contains requires 2 arguments".into()));
507                }
508                // contains(haystack, needle) = in(needle, haystack)
509                Ok(json!({ "in": [&args[1], &args[0]] }))
510            }
511
512            // Unknown function - emit as custom operation
513            other => Ok(json!({ other: args })),
514        }
515    }
516
517    fn compile_method_call(&self, method: &MethodCallExpr) -> Result<JsonValue, CompileError> {
518        let object = self.compile(&method.object)?;
519        let args: Vec<JsonValue> = method.args.iter()
520            .map(|a| self.compile(a))
521            .collect::<Result<_, _>>()?;
522
523        match method.method.as_str() {
524            // Array methods
525            "map" => {
526                if args.len() != 1 {
527                    return Err(CompileError::Unsupported("map requires 1 argument".into()));
528                }
529                Ok(json!({ "map": [object, args[0]] }))
530            }
531            "filter" => {
532                if args.len() != 1 {
533                    return Err(CompileError::Unsupported("filter requires 1 argument".into()));
534                }
535                Ok(json!({ "filter": [object, args[0]] }))
536            }
537            "reduce" => {
538                if args.len() != 2 {
539                    return Err(CompileError::Unsupported("reduce requires 2 arguments (initial, reducer)".into()));
540                }
541                Ok(json!({ "reduce": [object, args[1], args[0]] }))
542            }
543            "all" => {
544                if args.len() != 1 {
545                    return Err(CompileError::Unsupported("all requires 1 argument".into()));
546                }
547                Ok(json!({ "all": [object, args[0]] }))
548            }
549            "some" => {
550                if args.len() != 1 {
551                    return Err(CompileError::Unsupported("some requires 1 argument".into()));
552                }
553                Ok(json!({ "some": [object, args[0]] }))
554            }
555            "none" => {
556                if args.len() != 1 {
557                    return Err(CompileError::Unsupported("none requires 1 argument".into()));
558                }
559                Ok(json!({ "none": [object, args[0]] }))
560            }
561            "contains" => {
562                if args.len() != 1 {
563                    return Err(CompileError::Unsupported("contains requires 1 argument".into()));
564                }
565                // obj.contains(x) = in(x, obj)
566                Ok(json!({ "in": [args[0], object] }))
567            }
568
569            // String methods
570            "substr" => {
571                let mut substr_args = vec![object];
572                substr_args.extend(args);
573                Ok(json!({ "substr": substr_args }))
574            }
575            "length" | "len" => {
576                Ok(json!({ "len": [object] }))
577            }
578
579            // Unknown method
580            other => {
581                let mut all_args = vec![object];
582                all_args.extend(args);
583                Ok(json!({ other: all_args }))
584            }
585        }
586    }
587
588    fn compile_property(&self, prop: &PropertyExpr) -> Result<JsonValue, CompileError> {
589        // For nested property access, we need to build the path
590        // obj.prop becomes {"var": "obj.prop"}
591        let path = self.build_property_path(&prop.object, &prop.property)?;
592        Ok(json!({ "var": path }))
593    }
594
595    fn build_property_path(&self, object: &Expr, property: &str) -> Result<String, CompileError> {
596        match object {
597            Expr::Var(v) => {
598                let base = if v.name.starts_with('/') {
599                    v.name[1..].replace('/', ".")
600                } else {
601                    v.name.clone()
602                };
603                Ok(format!("{}.{}", base, property))
604            }
605            Expr::Property(p) => {
606                let base = self.build_property_path(&p.object, &p.property)?;
607                Ok(format!("{}.{}", base, property))
608            }
609            _ => {
610                // For complex expressions, we can't use simple var access
611                // Fall back to a computed approach (not standard JSON Logic)
612                Err(CompileError::Unsupported(
613                    "Complex property access not yet supported".into()
614                ))
615            }
616        }
617    }
618
619    fn compile_index(&self, idx: &IndexExpr) -> Result<JsonValue, CompileError> {
620        // arr[idx] in JSON Logic is tricky
621        // We need to use var with computed index
622        // For simple cases: arr[0] -> {"var": "arr.0"}
623        match (&idx.object, &idx.index) {
624            (Expr::Var(v), Expr::Literal(Literal::Integer(n))) => {
625                let path = if v.name.starts_with('/') {
626                    v.name[1..].replace('/', ".")
627                } else {
628                    v.name.clone()
629                };
630                Ok(json!({ "var": format!("{}.{}", path, n) }))
631            }
632            _ => {
633                // For dynamic indexing, emit a custom operation
634                let arr = self.compile(&idx.object)?;
635                let index = self.compile(&idx.index)?;
636                Ok(json!({ "index": [arr, index] }))
637            }
638        }
639    }
640
641    fn compile_array(&self, items: &[Expr]) -> Result<JsonValue, CompileError> {
642        let compiled: Vec<JsonValue> = items.iter()
643            .map(|i| self.compile(i))
644            .collect::<Result<_, _>>()?;
645        Ok(JsonValue::Array(compiled))
646    }
647
648    fn compile_lambda(&self, lambda: &LambdaExpr) -> Result<JsonValue, CompileError> {
649        // Lambdas in FarmScript are used with array methods
650        // In JSON Logic, the lambda body references "current" element via {"var": ""}
651        // We need to transform variable references accordingly
652
653        // For single-param lambdas, the param becomes {"var": ""}
654        // For multi-param (reduce), we have "current" and "accumulator"
655
656        if lambda.params.len() == 1 {
657            // Replace references to the single param with {"var": ""}
658            let body = self.transform_lambda_body(&lambda.body, &lambda.params[0], "")?;
659            Ok(body)
660        } else if lambda.params.len() == 2 {
661            // Reduce lambda: (acc, cur) => ...
662            // acc -> {"var": "accumulator"}, cur -> {"var": "current"}
663            let body = self.transform_reduce_lambda(&lambda.body, &lambda.params[0], &lambda.params[1])?;
664            Ok(body)
665        } else {
666            Err(CompileError::InvalidLambda(
667                "Lambdas must have 1 or 2 parameters".into()
668            ))
669        }
670    }
671
672    fn transform_lambda_body(&self, expr: &Expr, param: &str, replacement: &str) -> Result<JsonValue, CompileError> {
673        match expr {
674            Expr::Var(v) if v.name == param => {
675                Ok(json!({ "var": replacement }))
676            }
677            Expr::Var(v) => {
678                // Keep other variables as-is
679                self.compile_var(v)
680            }
681            Expr::Literal(lit) => self.compile_literal(lit),
682            Expr::Binary(b) => {
683                let left = self.transform_lambda_body(&b.left, param, replacement)?;
684                let right = self.transform_lambda_body(&b.right, param, replacement)?;
685                let op = match b.op {
686                    BinaryOp::Add => "+",
687                    BinaryOp::Sub => "-",
688                    BinaryOp::Mul => "*",
689                    BinaryOp::Div => "/",
690                    BinaryOp::Mod => "%",
691                    BinaryOp::Eq => "==",
692                    BinaryOp::StrictEq => "===",
693                    BinaryOp::NotEq => "!=",
694                    BinaryOp::StrictNotEq => "!==",
695                    BinaryOp::Lt => "<",
696                    BinaryOp::Gt => ">",
697                    BinaryOp::LtEq => "<=",
698                    BinaryOp::GtEq => ">=",
699                    BinaryOp::And => "and",
700                    BinaryOp::Or => "or",
701                    BinaryOp::In => "in",
702                    BinaryOp::Pow => "pow",
703                    BinaryOp::SafeDivZero | BinaryOp::SafeDivNull => {
704                        return self.compile_binary(&BinaryExpr {
705                            left: b.left.clone(),
706                            op: b.op,
707                            right: b.right.clone(),
708                            span: b.span,
709                        });
710                    }
711                };
712                Ok(json!({ op: [left, right] }))
713            }
714            Expr::Unary(u) => {
715                let inner = self.transform_lambda_body(&u.expr, param, replacement)?;
716                match u.op {
717                    UnaryOp::Not => Ok(json!({ "!": inner })),
718                    UnaryOp::Neg => Ok(json!({ "-": [inner] })),
719                    UnaryOp::Plus => Ok(inner),
720                }
721            }
722            Expr::Call(c) => {
723                let args: Vec<JsonValue> = c.args.iter()
724                    .map(|a| self.transform_lambda_body(a, param, replacement))
725                    .collect::<Result<_, _>>()?;
726                match c.function.as_str() {
727                    "min" => Ok(json!({ "min": args })),
728                    "max" => Ok(json!({ "max": args })),
729                    other => Ok(json!({ other: args })),
730                }
731            }
732            _ => {
733                // For other expressions, fall back to regular compilation
734                // This might not correctly transform all nested param references
735                self.compile(expr)
736            }
737        }
738    }
739
740    fn transform_reduce_lambda(&self, expr: &Expr, acc_param: &str, cur_param: &str) -> Result<JsonValue, CompileError> {
741        match expr {
742            Expr::Var(v) if v.name == acc_param => {
743                Ok(json!({ "var": "accumulator" }))
744            }
745            Expr::Var(v) if v.name == cur_param => {
746                Ok(json!({ "var": "current" }))
747            }
748            Expr::Var(v) => {
749                self.compile_var(v)
750            }
751            Expr::Literal(lit) => self.compile_literal(lit),
752            Expr::Binary(b) => {
753                let left = self.transform_reduce_lambda(&b.left, acc_param, cur_param)?;
754                let right = self.transform_reduce_lambda(&b.right, acc_param, cur_param)?;
755                let op = match b.op {
756                    BinaryOp::Add => "+",
757                    BinaryOp::Sub => "-",
758                    BinaryOp::Mul => "*",
759                    BinaryOp::Div => "/",
760                    BinaryOp::Mod => "%",
761                    BinaryOp::Eq => "==",
762                    BinaryOp::StrictEq => "===",
763                    BinaryOp::NotEq => "!=",
764                    BinaryOp::StrictNotEq => "!==",
765                    BinaryOp::Lt => "<",
766                    BinaryOp::Gt => ">",
767                    BinaryOp::LtEq => "<=",
768                    BinaryOp::GtEq => ">=",
769                    BinaryOp::And => "and",
770                    BinaryOp::Or => "or",
771                    BinaryOp::In => "in",
772                    BinaryOp::Pow => "pow",
773                    _ => return self.compile(expr),
774                };
775                Ok(json!({ op: [left, right] }))
776            }
777            _ => self.compile(expr),
778        }
779    }
780
781    fn compile_query(&self, query: &QueryExpr) -> Result<JsonValue, CompileError> {
782        // SQL-like: from items where cond select expr
783        // Translates to: map(filter(items, cond), expr)
784
785        let source = self.compile(&query.source)?;
786        let projection = self.compile(&query.projection)?;
787
788        if let Some(ref filter) = query.filter {
789            let filter_compiled = self.compile(filter)?;
790            // filter then map
791            Ok(json!({
792                "map": [
793                    { "filter": [source, filter_compiled] },
794                    projection
795                ]
796            }))
797        } else {
798            // just map
799            Ok(json!({ "map": [source, projection] }))
800        }
801    }
802
803    fn compile_template(&self, parts: &[TemplateExpr]) -> Result<JsonValue, CompileError> {
804        // Template string: `Hello {name}!`
805        // Translates to: {"cat": ["Hello ", {"var": "name"}, "!"]}
806
807        let compiled_parts: Vec<JsonValue> = parts.iter()
808            .map(|part| match part {
809                TemplateExpr::Literal(s) => Ok(JsonValue::String(s.clone())),
810                TemplateExpr::Expr(e) => self.compile(e),
811            })
812            .collect::<Result<_, _>>()?;
813
814        Ok(json!({ "cat": compiled_parts }))
815    }
816
817    fn compile_null_coalesce(&self, a: &Expr, b: &Expr) -> Result<JsonValue, CompileError> {
818        // a ?? b = if a != null then a else b
819        let a_compiled = self.compile(a)?;
820        let b_compiled = self.compile(b)?;
821
822        Ok(json!({
823            "if": [
824                { "!=": [a_compiled.clone(), null] },
825                a_compiled,
826                b_compiled
827            ]
828        }))
829    }
830}
831
832#[cfg(test)]
833mod tests {
834    use super::*;
835    use crate::{Lexer, Parser};
836
837    fn compile(source: &str) -> Result<JsonValue, CompileError> {
838        let lexer = Lexer::new(source);
839        let mut parser = Parser::new(lexer);
840        let ast = parser.parse()?;
841        let compiler = Compiler::default_options();
842        compiler.compile(&ast)
843    }
844
845    #[test]
846    fn test_simple_comparison() {
847        let result = compile("x < 10").unwrap();
848        assert_eq!(result, json!({"<": [{"var": "x"}, 10]}));
849    }
850
851    #[test]
852    fn test_and_expression() {
853        let result = compile("a and b").unwrap();
854        assert_eq!(result, json!({"and": [{"var": "a"}, {"var": "b"}]}));
855    }
856
857    #[test]
858    fn test_and_chain_flattening() {
859        // BODMAS: Chained and/or should flatten to a single array
860        let result = compile("a and b and c and d").unwrap();
861        assert_eq!(
862            result,
863            json!({"and": [{"var": "a"}, {"var": "b"}, {"var": "c"}, {"var": "d"}]})
864        );
865    }
866
867    #[test]
868    fn test_or_chain_flattening() {
869        let result = compile("a or b or c").unwrap();
870        assert_eq!(
871            result,
872            json!({"or": [{"var": "a"}, {"var": "b"}, {"var": "c"}]})
873        );
874    }
875
876    #[test]
877    fn test_mixed_and_or_no_flatten() {
878        // Different operators should NOT flatten together
879        let result = compile("a and b or c").unwrap();
880        // 'or' has lower precedence than 'and', so: (a and b) or c
881        assert_eq!(
882            result,
883            json!({"or": [{"and": [{"var": "a"}, {"var": "b"}]}, {"var": "c"}]})
884        );
885    }
886
887    #[test]
888    fn test_brackets_precedence() {
889        // Brackets should override precedence (BODMAS)
890        let result = compile("a and (b or c)").unwrap();
891        assert_eq!(
892            result,
893            json!({"and": [{"var": "a"}, {"or": [{"var": "b"}, {"var": "c"}]}]})
894        );
895    }
896
897    #[test]
898    fn test_let_expression() {
899        // let x = 5 in x * 2 should substitute x with 5
900        let result = compile("let x = 5 in x * 2").unwrap();
901        assert_eq!(result, json!({"*": [5, 2]}));
902    }
903
904    #[test]
905    fn test_let_expression_with_var() {
906        // let factor = /multiplier in factor * 10
907        let result = compile("let factor = /multiplier in factor * 10").unwrap();
908        assert_eq!(result, json!({"*": [{"var": "multiplier"}, 10]}));
909    }
910
911    #[test]
912    fn test_complex_expression() {
913        let result = compile("alert_acknowledged and time_since_alert_secs < 120").unwrap();
914        assert_eq!(
915            result,
916            json!({
917                "and": [
918                    {"var": "alert_acknowledged"},
919                    {"<": [{"var": "time_since_alert_secs"}, 120]}
920                ]
921            })
922        );
923    }
924
925    #[test]
926    fn test_clamp() {
927        let result = compile("clamp(0, 100, raw_score)").unwrap();
928        assert_eq!(
929            result,
930            json!({
931                "max": [
932                    0,
933                    {"min": [100, {"var": "raw_score"}]}
934                ]
935            })
936        );
937    }
938
939    #[test]
940    fn test_if_then_else() {
941        let result = compile("if x > 0 then 1 else 0").unwrap();
942        assert_eq!(
943            result,
944            json!({
945                "if": [
946                    {">": [{"var": "x"}, 0]},
947                    1,
948                    0
949                ]
950            })
951        );
952    }
953
954    #[test]
955    fn test_path_variable() {
956        let result = compile("/users/count").unwrap();
957        assert_eq!(result, json!({"var": "users.count"}));
958    }
959
960    #[test]
961    fn test_safe_division() {
962        let result = compile("a /? b").unwrap();
963        assert_eq!(
964            result,
965            json!({
966                "if": [
967                    {"==": [{"var": "b"}, 0]},
968                    0,
969                    {"/": [{"var": "a"}, {"var": "b"}]}
970                ]
971            })
972        );
973    }
974
975    #[test]
976    fn test_safe_div_function() {
977        let result = compile("safe_div(a, b, 0)").unwrap();
978        assert_eq!(
979            result,
980            json!({
981                "if": [
982                    {"==": [{"var": "b"}, 0]},
983                    0,
984                    {"/": [{"var": "a"}, {"var": "b"}]}
985                ]
986            })
987        );
988    }
989
990    #[test]
991    fn test_in_operator() {
992        let result = compile("x in [1, 2, 3]").unwrap();
993        assert_eq!(
994            result,
995            json!({"in": [{"var": "x"}, [1, 2, 3]]})
996        );
997    }
998
999    #[test]
1000    fn test_method_call() {
1001        let result = compile("items.filter(x => x > 0)").unwrap();
1002        assert_eq!(
1003            result,
1004            json!({
1005                "filter": [
1006                    {"var": "items"},
1007                    {">": [{"var": ""}, 0]}
1008                ]
1009            })
1010        );
1011    }
1012
1013    #[test]
1014    fn test_contains_method() {
1015        let result = compile("[1, 2, 3].contains(x)").unwrap();
1016        assert_eq!(
1017            result,
1018            json!({"in": [{"var": "x"}, [1, 2, 3]]})
1019        );
1020    }
1021
1022    #[test]
1023    fn test_truthy_postfix() {
1024        let result = compile("x?").unwrap();
1025        assert_eq!(result, json!({"!!": {"var": "x"}}));
1026    }
1027
1028    #[test]
1029    fn test_equality_synonyms() {
1030        // All should compile to ===
1031        let sources = ["a === b", "a is b", "a eq b", "a equals b"];
1032        for source in sources {
1033            let result = compile(source).unwrap();
1034            assert_eq!(
1035                result,
1036                json!({"===": [{"var": "a"}, {"var": "b"}]}),
1037                "Failed for: {}", source
1038            );
1039        }
1040    }
1041
1042    #[test]
1043    fn test_nested_property() {
1044        let result = compile("user.address.city").unwrap();
1045        assert_eq!(result, json!({"var": "user.address.city"}));
1046    }
1047
1048    #[test]
1049    fn test_null_coalesce() {
1050        let result = compile("a ?? b").unwrap();
1051        assert_eq!(
1052            result,
1053            json!({
1054                "if": [
1055                    {"!=": [{"var": "a"}, null]},
1056                    {"var": "a"},
1057                    {"var": "b"}
1058                ]
1059            })
1060        );
1061    }
1062}