/common-expression-language

Primary LanguageRustApache License 2.0Apache-2.0

Learning Rust by playing with the Common Expression Language.

Plan

  • Write a very basic CEL Lexer/Parser in Rust (using Chumsky)
  • Evaluate the parsed CEL AST by implementing a basic Treewalk interpreter
  • Write a basic CLI for evaluating CEL expressions with data loaded from stdin or a JSON/YAML file.
  • Create rust packages for the Lexer/Parser and Interpreter
  • See how conformant we can get to the CEL spec, parse the test data and evaluate the expressions.
  • Wrap for Python

Extensions

  • Use a Pratt parser once now that Chumsky supports it (zesterer/chumsky#464)
  • Possibly write a bytecode compiler for a simple stack based VM...
  • Research and try the existing Rust implementations of CEL.
  • Benchmark the Cloud Custodian Python-CEL implementation against the Rust ones.
  • Use an existing Lexer like Logos

Execution

cargo run -- --expression="-(3.0 + 5.0) * 2.0e0 - -1.0"

Which outputs something like:

AST:
Arithmetic(Arithmetic(Unary(Neg, Arithmetic(Atom(Float(3.0)), Add, Atom(Float(5.0)))), Multiply, Atom(Float(2.0))), Subtract, Unary(Neg, Atom(Float(1.0))))

Evaluating program
NumericCelType(Float(-15.0))

Note while the parser is close to complete I'm not intending to implement a full interpreter with all built-in functions.

The interpreter has basic support for strings, numbers, bools, lists, maps, variables, and binary and unary operators.

cargo run -- --expression="size('hello world') > 5u"

Outputs:

AST:
Relation(Member(Ident("size"), FunctionCall([Atom(String("hello world"))])), GreaterThan, Atom(UInt(5)))

Evaluating program
Bool(true)

Context

To add context from a JSON file use --input with either a filename or - to read from stdin:

echo "{\"foo\":\"bar\"}" | cargo run -- --expression="foo" --input -

Outputs:

Parsed context data: Object {"foo": String("bar")}
AST:
Ident("foo")

Evaluating program
String("bar")

Python Bindings

A Python package rustycel using PyO3 in python_bindings offers a simple evaluate function from Python:

>>> import rustycel
>>> rustycel.evaluate("1u + 4u")
5

Note only a few primitive types are mapped back to Python native types.

References

Existing CEL Implementations

  • cel-go - Reference implementation of CEL by Google in Go.
  • cel-python - Python implementation of CEL by Cloud Custodian as used in Netchecks.
  • clarkmcc/cel-rust (Maintained fork of orf/cel-rust.) Implements separate crates for parser (using lalrpop) and interpreter (straightforward treewalk interpreter).
  • thesayyn/cel-rust (Incomplete). WASM target with online demo. Parser uses lalrpop.

Test Data

https://github.com/google/cel-spec/blob/master/tests/simple/testdata/basic.textproto

Real parsers using Chumsky