pub mod access;
pub mod collections;
pub mod context;
pub mod expr;
pub mod functions;
pub mod helpers;
pub mod literals;
pub mod operators;
use std::collections::HashMap;
use anyhow::Context;
use cel::{common::ast::Expr, parser::Parser};
pub use context::ExtensionKey;
use context::{CompilerContext, CompilerEnv};
use ferricel_types::{extensions::ExtensionDecl, functions::RuntimeFunction};
use walrus::{FunctionBuilder, FunctionId, ModuleConfig, ValType};
const RUNTIME_BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/runtime.wasm"));
use crate::schema::ProtoSchema;
pub struct Builder {
proto_descriptor: Option<Vec<u8>>,
container: Option<String>,
logger: slog::Logger,
extensions: Vec<ExtensionDecl>,
}
impl Builder {
pub fn new() -> Self {
Self {
proto_descriptor: None,
container: None,
logger: slog::Logger::root(slog::Discard, slog::o!()),
extensions: vec![],
}
}
pub fn with_logger(mut self, logger: slog::Logger) -> Self {
self.logger = logger;
self
}
pub fn with_proto_descriptor(mut self, bytes: Vec<u8>) -> Result<Self, anyhow::Error> {
ProtoSchema::from_descriptor_set(&bytes)?;
self.proto_descriptor = Some(bytes);
Ok(self)
}
pub fn with_container(mut self, container: impl Into<String>) -> Self {
self.container = Some(container.into());
self
}
pub fn with_extension(mut self, decl: ExtensionDecl) -> Self {
self.extensions.push(decl);
self
}
pub fn build(self) -> Compiler {
let schema = self.proto_descriptor.as_ref().map(|b| {
ProtoSchema::from_descriptor_set(b).expect("proto descriptor already validated")
});
Compiler {
schema,
container: self.container,
logger: self.logger,
extensions: self.extensions,
}
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
pub struct Compiler {
schema: Option<ProtoSchema>,
container: Option<String>,
logger: slog::Logger,
extensions: Vec<ExtensionDecl>,
}
impl Compiler {
pub fn compile(&self, cel_code: &str) -> Result<Vec<u8>, anyhow::Error> {
let mut module = ModuleConfig::new().parse(RUNTIME_BYTES)?;
let mut functions = HashMap::new();
for func in RuntimeFunction::iter() {
let id = module.exports.get_func(func.name()).with_context(|| {
format!(
"Runtime function '{}' not found in module exports",
func.name()
)
})?;
functions.insert(func, id);
if !func.is_exported() {
module.exports.remove(func.name())?;
}
}
let env = CompilerEnv { functions };
let root_ast = Parser::new()
.enable_optional_syntax(true)
.parse(cel_code)
.map_err(|e| anyhow::anyhow!("Parse error: {:?}", e))?;
let ctx = CompilerContext::new(
self.schema.clone(),
self.container.clone(),
self.logger.clone(),
&self.extensions,
);
let evaluate_id = build_evaluate_function(&mut module, &env, &ctx, &root_ast.expr)?;
module.exports.add("evaluate", evaluate_id);
let evaluate_proto_id =
build_evaluate_proto_function(&mut module, &env, &ctx, &root_ast.expr)?;
module.exports.add("evaluate_proto", evaluate_proto_id);
walrus::passes::gc::run(&mut module);
Ok(module.emit_wasm())
}
}
fn build_evaluate_function(
module: &mut walrus::Module,
env: &CompilerEnv,
ctx: &CompilerContext,
expr: &Expr,
) -> Result<FunctionId, anyhow::Error> {
let mut func = FunctionBuilder::new(&mut module.types, &[ValType::I64], &[ValType::I64]);
let bindings_encoded_arg = module.locals.add(ValType::I64);
let mut body = func.func_body();
body.local_get(bindings_encoded_arg)
.call(env.get(RuntimeFunction::DeserializeJson))
.call(env.get(RuntimeFunction::InitBindings));
expr::compile_expr(expr, &mut body, env, ctx, module)?;
body.call(env.get(RuntimeFunction::SerializeResult));
Ok(func.finish(vec![bindings_encoded_arg], &mut module.funcs))
}
fn build_evaluate_proto_function(
module: &mut walrus::Module,
env: &CompilerEnv,
ctx: &CompilerContext,
expr: &Expr,
) -> Result<FunctionId, anyhow::Error> {
let mut func = FunctionBuilder::new(&mut module.types, &[ValType::I64], &[ValType::I64]);
let bindings_encoded_arg = module.locals.add(ValType::I64);
let mut body = func.func_body();
body.local_get(bindings_encoded_arg)
.call(env.get(RuntimeFunction::DeserializeProto))
.call(env.get(RuntimeFunction::InitBindings));
expr::compile_expr(expr, &mut body, env, ctx, module)?;
body.call(env.get(RuntimeFunction::SerializeResult));
Ok(func.finish(vec![bindings_encoded_arg], &mut module.funcs))
}