mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-09 22:35:56 +05:45
* 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>
83 lines
3.1 KiB
Swift
83 lines
3.1 KiB
Swift
//
|
|
// LogicOperators.swift
|
|
//
|
|
// Created by Antonio Pallares.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
extension RulesEngine {
|
|
|
|
/// Logic operators: `!`, `!!`, `and`, `or`, `if`.
|
|
///
|
|
/// `and`, `or`, and `if` short-circuit and therefore can't use the eager
|
|
/// `evalArgs` helpers — they evaluate their arguments one at a time.
|
|
enum LogicOperators {
|
|
|
|
/// `{"!": x}` — boolean negation. Coerces to bool first per JSON Logic
|
|
/// truthiness rules.
|
|
static func opNot(args: Value, vars: Scope) throws -> Value {
|
|
let value = try Operators.firstArgEvaluated(args, vars: vars)
|
|
return .bool(!value.isTruthy)
|
|
}
|
|
|
|
/// `{"!!": x}` — boolean cast. Spec: equivalent to `!!x` in JS.
|
|
static func opNotNot(args: Value, vars: Scope) throws -> Value {
|
|
let value = try Operators.firstArgEvaluated(args, vars: vars)
|
|
return .bool(value.isTruthy)
|
|
}
|
|
|
|
/// `{"and": [a, b, c]}` — short-circuit AND. Returns the first falsy
|
|
/// value or, if all are truthy, the last value (matches JS / JSON Logic:
|
|
/// `and` returns the actual value, not a coerced boolean). Empty input
|
|
/// returns `.undefined` (json-logic-js reduces an empty `and` to
|
|
/// `undefined`, which is falsy but `!== null`).
|
|
static func opAnd(args: Value, vars: Scope) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
var last: Value = .undefined
|
|
for item in items {
|
|
last = try Evaluator.evaluateValue(item, vars: vars)
|
|
if !last.isTruthy {
|
|
return last
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
/// `{"or": [a, b, c]}` — short-circuit OR. Returns the first truthy
|
|
/// value or, if all are falsy, the last value. Empty input returns
|
|
/// `.undefined` (json-logic-js reduces an empty `or` to `undefined`,
|
|
/// which is falsy but `!== null`).
|
|
static func opOr(args: Value, vars: Scope) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
var last: Value = .undefined
|
|
for item in items {
|
|
last = try Evaluator.evaluateValue(item, vars: vars)
|
|
if last.isTruthy {
|
|
return last
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
/// `{"if": [cond, then, else]}` — also supports chained
|
|
/// `[c1, t1, c2, t2, ..., else]` (think `else if`). Without an `else`
|
|
/// clause and with no truthy condition, returns `null`.
|
|
static func opIf(args: Value, vars: Scope) throws -> Value {
|
|
let items = Operators.argsAsList(args)
|
|
var index = 0
|
|
while index + 1 < items.count {
|
|
let condition = try Evaluator.evaluateValue(items[index], vars: vars)
|
|
if condition.isTruthy {
|
|
return try Evaluator.evaluateValue(items[index + 1], vars: vars)
|
|
}
|
|
index += 2
|
|
}
|
|
if index < items.count {
|
|
return try Evaluator.evaluateValue(items[index], vars: vars)
|
|
}
|
|
return .null
|
|
}
|
|
}
|
|
}
|