The ability to express and evaluate constraints, policies and conditions using a machine-readable language is not something new, the roots lie back in the Advanced Boolean Expression Language which was used in hardware verification.
Below is a sample program for describing the half-adder
module my_first_circuit;
title 'ee200 assignment 1'
EE200XY device 'XC4003E';
" input pins
A, B pin 3, 5;
" output pins
SUM, Carry_out pin 15, 18 istype 'com';
equations
SUM = (A & !B) # (!A & B) ;
Carry_out = A & B;
end my_first_circuit;
evolving to software in the Java ecosystem as an Expression Language
<c:if test="${sessionScope.cart.numberOfItems > 0}">
...
</c:if>
notice how "${sessionScope.cart.numberOfItems > 0}"
expression is represented as a string that allows the user to evaluate a condition depending on the value of numberOfItems
in cart
object from the session.
Common Expression Language is one such attempt from Google to write a language-agnostic expression language and evaluation engine.
A sample rule from cel-spec
account.balance >= transaction.withdrawal
|| (account.overdraftProtection
&& account.overdraftLimit >= transaction.withdrawal - account.balance)
CEL allows us to specify expressions and provides libraries to evaluate these expressions. There are two implementations as of today cel-go and cel-cpp
CEL support has been integrated into Kubernetes at webhook validation, match condition and authorization. This has sparked my interest in learning how CEL is implemented and works internally. This series is not primarily focused on writing CEL rules in Kubernetes but on how CEL itself is implemented.
So let’s write the hello world equivalent of CEL and evaluate a simple expression, we are trying to compare an input number and check if it is equal to 42 or not.
number == 42
If you are not familiar with 42 and the Ultimate Question of Life, the Universe, and Everything. Check out this short video
Prepare the go module
mkdir meaningoflife && cd ./meaningoflife && go mod init hitchhiker
go: creating new go.mod: module hitchhiker
Add main.go
package main
import (
"fmt"
"github.com/google/cel-go/cel"
"log"
)
func main() {
// define an environment with
// a number variable of integer type
// for your program
env, err := cel.NewEnv(cel.Variable("number", cel.IntType))
if err != nil {
log.Fatalln(err)
}
// compile the expression
// this step as expected generates
// Abstract Syntax Tree for the parsed expression
ast, iss := env.Compile(`number == 42`)
if iss.Err() != nil {
log.Fatalln(iss.Err())
}
// generate a program using a given AST
prg, err := env.Program(ast)
if err != nil {
log.Fatalln(err)
}
// prepare data for your evaluation
evalInput := map[string]interface{}{
"number": 69,
}
// perform evaluation and capture results
out, _, err := prg.Eval(evalInput)
if err != nil {
log.Fatalln(err)
}
fmt.Println(fmt.Sprintf("Is %d the answer for the Ultimate Question of Life, the Universe, and Everything? %v\n", evalInput["number"], out))
}
Build and Run
go mod tidy
go: finding module for package github.com/google/cel-go/cel
go: found github.com/google/cel-go/cel in github.com/google/cel-go v0.22.1
go build && ./hitchhiker
Is 69 the answer for the Ultimate Question of Life, the Universe, and Everything? false
Modify evalInput number to 42
// prepare data for your evaluation
evalInput := map[string]interface{}{
"number": 42,
}
build and run again
go build && ./hitchhiker
Is 42 the answer for the Ultimate Question of Life, the Universe, and Everything? true
In the next article we will explore cel-spec and different properties of Common Expression Language and why turning incompleteness is an advertised aspect of this language.