Files
Antonio PallaresandCursor 2deacfd190 Add Scope plumbing and custom operator extension point (#7433)
* Add Scope plumbing and custom operator extension point

Thread evaluation scope through the rules engine and delegate unknown
operators to an empty CustomOperators dispatcher, without changing behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix project.pbxproj: restore RewardedAdTrackingMetadata entries

The RulesEngine registration edit accidentally dropped four
RewardedAdTrackingMetadata.swift entries from the Xcode project.
Restore them and normalize CustomOperators identifiers to 24 hex chars.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Promote firstArgEvaluated to shared Operators helpers

Move the unary-arg helper from LogicOperators to Operators so custom
operators can use the same json-logic-js spread convention.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address review comments on custom operator extension point

Drop the cross-engine registration note from CustomOperators, and restore
the single-line var extra-args warning that was reflowed without changing
its text.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 08:04:21 +00:00

53 lines
2.0 KiB
Swift

//
// MinMaxOperators.swift
//
// Created by Antonio Pallares.
//
import Foundation
extension RulesEngine {
/// Numeric extrema: `min`, `max`.
///
/// Variadic over a flat list of values (`{"max": [v1, v2, ...]}` /
/// `{"min": [v1, v2, ...]}`), matching JS `Math.max` / `Math.min`. Each
/// operand goes through `Value.asNumber` (JS `Number()`), so
/// single-element arrays bridge to a number via `toString`
/// (`Math.max(1, [2]) === 2`); multi-element arrays, objects, and
/// unparseable strings become `Double.nan` and poison the result.
///
/// Empty input mirrors `Math.max()` / `Math.min()`: `max` → `-∞`,
/// `min` → `+∞`.
enum MinMaxOperators {
/// `{"max": [v1, v2, ...]}` — variadic numeric maximum.
static func opMax(args: Value, vars: Scope) throws -> Value {
try reduceExtremum(args, vars: vars, empty: -.infinity) { Swift.max($0, $1) }
}
/// `{"min": [v1, v2, ...]}` — variadic numeric minimum.
static func opMin(args: Value, vars: Scope) throws -> Value {
try reduceExtremum(args, vars: vars, empty: .infinity) { Swift.min($0, $1) }
}
/// Evaluate each operand to a `Double` (non-numeric → `nan`) and fold
/// it into a single extremum in one pass. `empty` is the no-operand
/// result (`Math.max()` → `-∞`, `Math.min()` → `+∞`); any `nan`
/// operand poisons the accumulator, mirroring `Math.max` / `Math.min`.
private static func reduceExtremum(
_ args: Value,
vars: Scope,
empty: Double,
combine: (Double, Double) -> Double
) throws -> Value {
let result = try Operators.evalArgs(args, vars: vars).reduce(empty) { accumulator, value in
let number = value.asNumber ?? .nan
guard !accumulator.isNaN, !number.isNaN else { return .nan }
return combine(accumulator, number)
}
return .float(result)
}
}
}