本文测试基于Google的CEL-GP CodeLab课程,按照所有语言的入门规则,先从一个"hello world"开始。
func exercise1() { fmt.Println("=== Exercise 1: Hello World ===\n") env, err := cel.NewEnv() if err != nil { glog.Exitf("Error creating CEL environment: %v", err) } }
CEL应用程序根据环境来评估表达式,使用env,err := cel.NewEnv()
来配置标准环境。
可以通过cel.EnvOption来订制环境,这些选项能够禁用宏、声明自定义变量和函数等。
一个标注的CEl环境支持语言规范[^1] 当中定义的所有类型、运算符、函数和宏。
当配置好环境之后,就可以解析和检查表达式。
// Compile, eval, profit! func exercise1() { fmt.Println("=== Exercise 1: Hello World ===\n") // 创建标准环境 env, err := cel.NewEnv() if err != nil { glog.Exitf("Error creating CEL environment: %v", err) } // 检查表达式是否编译,返回AST、Issues (Issues定义用于检查解析和检查调用的错误详细信息的方法。) ast,iss := env.Parse(`"Hello, World!"`) // 是否存在语法错误 if iss.Err() != nil { glog.Exitf("Error parsing expression: %v", iss.Err()) } // 检查表达式的正确性 checked,iss := env.Check(ast) // 检查是否存在语法错误 if iss.Err() != nil { glog.Exitf("Error checking expression: %v", iss.Err()) } // 检查表达式结果类型是否为String if !proto.Equal(checked.ResultType(),decls.String) { glog.Exitf("Error: expected result type to be string, got %v", checked.ResultType()) } // TODO }
env.Parse
和env.Check
返回的iss对象的值可能是错误的问题列表,如果iss.Err()
不为nil
,那说明表达式的语法或者语义有错误,程序无法继续运行。当表达式格式良好时,这两个调用的结果是一个可执行的cel.Ast
在这个codelab中解析和检查阶段被打包到当前文件的compile()方法中,它可以用于协助练习其余部分。
// compile将根据给定的表达式expr进行解析和检查 // ‘env’确定表达式执行的环境 // ‘exprType’ 匹配输入表达式结果的ResultType func compile(env *cel.Env, expr string, exprType *exprpb.Type) *cel.Ast { ast, iss := env.Compile(expr) if iss.Err() != nil { glog.Exit(iss.Err()) } if !proto.Equal(ast.ResultType(), exprType) { glog.Exitf( "Got %v, wanted %v result type", ast.ResultType(), exprType) } fmt.Printf("%s\n\n", strings.ReplaceAll(expr, "\t", " ")) return ast }
一单表达式被解析并检查通过返回一个cel.Ast
,代表它可以转换成一个可以求值的表达式程序,其参数绑定和求值模式可以使用cel.ProgramOption
选项进行自定义。请注意,也可以使用cel.CheckedExprToAst
或celParsedExprToAst
函数从proto
中读取cel.Ast
一旦设计好了cel.Program
,就可以通过调用Eval
对表达式进行求值,求值结果将包含结果、求值详情、错误状态等信息。
func exercise1() { fmt.Println("=== Exercise 1: Hello World ===\n") // 创建标准环境 env, err := cel.NewEnv() if err != nil { glog.Exitf("Error creating CEL environment: %v", err) } // 检查表达式是否编译,返回AST、Issues (Issues定义用于检查解析和检查调用的错误详细信息的方法。) ast,iss := env.Parse(`"Hello, World!"`) // 是否存在语法错误 if iss.Err() != nil { glog.Exitf("Error parsing expression: %v", iss.Err()) } // 检查表达式的正确性 checked,iss := env.Check(ast) // 检查是否存在语法错误 if iss.Err() != nil { glog.Exitf("Error checking expression: %v", iss.Err()) } // 检查表达式结果类型是否为String if !proto.Equal(checked.ResultType(),decls.String) { glog.Exitf("Error: expected result type to be string, got %v", checked.ResultType()) } // 程序生成一个AST的可计算实例 program, err := env.Program(checked) if err != nil { glog.Exitf("Error creating program: %v", err) } // 在没有任何附加参数的情况下执行程序 out,_,err := eval(program, cel.NoVars()) if err != nil { glog.Exitf("Error evaluating expression: %v", err) } // 打印结果 fmt.Printf("Result: %v\n", out) }
run it.
输出:
=== Exercise 1: Hello World === ------ input ------ (interpreter.emptyActivation) ------ result ------ value: Hello, World! (types.String) Result: Hello, World!
大多数CEL应用程序会声明可以在表达式中引用的变量。变量的声明指定了名称和类型。变量的类型可以是CEL内置的类型、协议缓冲区的知名类型,或者任何protobuf消息类型,只要它的描述符也被提供给CEL。
在下面的例子当中,compile
和eval
helpers方法被作为evn.Parse()/env.Check()/program.Eval()
的替身而包含。这些辅助函数是用来打印练习的输入和输出的。
在此开始使用exercise2的代码
[^1]语言规范: https://github.com/google/cel-spec/blob/master/doc/langdef.md