Skip to content

Releases: lustefaniak/cel-swift

cel-swift 0.1.1

Choose a tag to compare

@lustefaniak lustefaniak released this 04 Oct 20:40
5b304b5

cel-swift 0.1.1 is a performance release. Parsing and type checking now run at about cel-go's speed, and evaluation is up to twice as fast as in 0.1.0. The public API is the same as 0.1.0, and so is the conformance: cel-go v0.32.0 behaviour, tested against cel-spec v0.25.3.

Highlights

  • Parsing takes 0.43–0.48× as long as in 0.1.0 (1.1–1.3× cel-go, down from 2.3–2.9×). Each parse-tree node used to get a weak-reference side table. The parser no longer does dynamic exclusivity checks. On Darwin, long inputs run on the calling thread when it has enough stack.
  • Type checking takes 0.55–0.73× as long (0.8–1.1× cel-go). Types are looked up without being formatted into strings, expression ids are kept in a bitset, and a failed unification is undone instead of copying the type mapping. That copy made long expressions quadratic.
  • Evaluation takes 0.51–0.88× as long (1.2–2× cel-go, down from 1.7–3.1×). Interpreter nodes share a native base class, so calls through them need no runtime type lookup. Maps and variable bindings with up to 8 entries are searched without hashing.
  • Planning (Environment.program) takes 0.90–0.97× as long, and 0.64–0.76× for trivial expressions.
  • Fix: a CELSwift explanation of a condition that starts with a global function call, such as size(pr.labels) > 0, lost the function name.

Installation

.package(url: "https://github.com/lustefaniak/cel-swift.git", from: "0.1.1"),

The products are unchanged: CEL, CELExtensions, CELProtobuf, CELPolicy, CELTest, CELSwift, and the cel-swift command-line tool.

Conformance

cel-spec v0.25.3, checked mode. The other implementations' numbers come from the skip lists their own conformance runners keep:

Implementation Passing
cel-swift 0.1.1 2508 / 2508 (100%)
cel-go v0.32.0 2380 / 2508 (94.9%)
cel-cpp 2386 / 2508 (95.1%)
cel-rust 1283 / 2508 (51.2%)

Parse-only mode passes 2339 / 2339.

Platforms

These are the same as in 0.1.0:

  • Swift 6.0 or newer, language mode 6, strict concurrency.
  • macOS 13+, iOS 16+, and Linux with glibc or the static Linux SDK (musl).
  • CI tests Swift 6.0 to 6.3 on Linux and the latest Xcode on macOS, and builds for the iOS simulator.

Performance

Against cel-go on the same expressions (docs/performance.md), each phase takes this many times as long as cel-go's:

Phase 0.1.1 0.1.0
Parsing 1.1 to 1.3× 2 to 3×
Checking 0.8 to 1.1× 1.3 to 2×
Planning 2.6 to 3× about 3×
Evaluation 1.2 to 2× 1.6 to 3.3×

Each change was measured against the main it merged into, running both builds in alternation (tools/bench/bench.py --baseline-driver) on an Apple M1 Max. The new tools in tools/bench (allocs.py, profile.py) count allocations and summarise xctrace profiles.

Breaking changes

None. swift package diagnose-api-breaking-changes 0.1.0 reports no breaking changes in CEL, CELExtensions, CELPolicy, CELTest or CELProtobuf.

Known limitations

These are unchanged from 0.1.0:

  • The AST is not public yet, so there are no custom macros, optimizers or interpreter decorators.
  • No dynamic protobuf messages from runtime descriptor sets.
  • No proto conversion of checked expressions.
  • No textproto test suites.

Full changelog: CHANGELOG.md

cel-swift 0.1.0

Choose a tag to compare

@lustefaniak lustefaniak released this 01 Oct 20:35

The first release of cel-swift, a pure-Swift implementation of the Common Expression Language, ported from cel-go v0.32.0 and tested against cel-spec v0.25.3.

Highlights

  • 100% cel-spec conformance: all 2508 tests pass in checked mode and all 2339 in parse-only mode, with an empty skip list. That includes the strong-enum sections cel-go and cel-cpp skip.
  • The whole of cel-go's runtime: parser, type checker, interpreter, cost estimation and limits, partial evaluation with unknowns and residual expressions, constant folding and inlining.
  • cel-go's extension libraries: strings, lists, math, sets, encoders, network, regex, bindings, two-variable comprehensions and protos, with their versions and cost estimators.
  • Policies and test suites: cel-go's YAML policy format, environment configs and tests.yaml runner, with the same results as cel-go's celtest.
  • Swift types on both ends (CELSwift): Codable facts and results, CEL schemas derived from Swift types, typed Swift functions, and typed programs that check their outputs when they load and explain their results.
  • A zero-dependency core: CEL depends on nothing. Protobuf and YAML live in their own products.

Installation

.package(url: "https://github.com/lustefaniak/cel-swift.git", from: "0.1.0"),
Product Contents Dependencies
CEL parser, checker, interpreter, standard library, cost, partial evaluation, optimizers none
CELExtensions cel-go's extension libraries CEL
CELProtobuf protobuf messages, enums and well-known types as CEL values, generated by protoc-gen-cel-swift CEL, swift-protobuf
CELPolicy cel-go's YAML policy format and environment configs CEL, CELExtensions, Yams
CELTest cel-go's tests.yaml suites CELPolicy
CELSwift Codable facts and results, typed functions, typed programs and policies with explanations CEL, CELPolicy

The package also builds the cel-swift command-line tool: eval, check, parse, repl and policy test.

import CEL

let env = try Environment(.variable("user", .map(key: .string, value: .dyn)))
let program = try env.program(env.compile("user.age >= 18 && user.country in ['PL', 'DK']"))
let result = try program.evaluate(["user": ["age": 30, "country": "PL"]])
print(result.value)   // true

Conformance

cel-spec v0.25.3, checked mode. The other implementations' numbers come from the skip lists their own conformance runners keep:

Implementation Passing
cel-swift 0.1.0 2508 / 2508 (100%)
cel-go v0.32.0 2380 / 2508 (94.9%)
cel-cpp 2386 / 2508 (95.1%)
cel-rust 1283 / 2508 (51.2%)

Every test cel-go or cel-cpp passes, cel-swift passes too. Where cel-spec and cel-go disagree, cel-swift follows the spec and cel-cpp, and each difference is recorded in docs/divergences.md. A differential suite also compares values, errors, types and costs with cel-go on generated expressions; it runs nightly in CI.

Platforms

  • Swift 6.0 or newer, language mode 6, strict concurrency.
  • macOS 13+, iOS 16+, and Linux with glibc or the static Linux SDK (musl).
  • CI tests Swift 6.0, 6.1, 6.2 and 6.3 on Linux and the latest Xcode on macOS, and builds for the iOS simulator.
  • On Linux, IANA time zone names need the system tz database (tzdata, plus tzdata-legacy for the old US/... names).

Performance

Against cel-go on the same expressions (docs/performance.md), each phase takes this many times as long as cel-go's:

Phase Time vs cel-go
Evaluation 1.6 to 3.3×
Checking 1.3 to 2×
Parsing 2 to 3×
Planning about 3×

Concurrent parses on one Environment share a prediction cache and scale across threads.

Untrusted input

Expressions from untrusted sources always terminate, and estimateCost, the cost limit, a time limit and task cancellation bound the work. Policy YAML and environment configs are hardened against alias expansion, deep nesting and huge duplicate-key sets: these give errors instead of crashing or using memory out of proportion.

Known limitations

  • The AST is not public yet, so there are no custom macros, optimizers or interpreter decorators.
  • No dynamic protobuf messages from runtime descriptor sets.
  • No proto conversion of checked expressions.
  • No textproto test suites.

docs/decisions.md records the API decisions behind this release.

Full changelog: CHANGELOG.md