mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-11-13 22:27:12 +00:00
Make compiled JIT functions immutable
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::sync::Arc;
|
||||
|
||||
use jitexpr::ast::{Function, InferredTypeSet, UntypedExpr, infer_types};
|
||||
use jitexpr::compile::{CompiledFn, compile};
|
||||
use jitexpr::compile::{CompiledFn, CompiledFnCtx, compile};
|
||||
use jitexpr::types::{VarType, VariableValue};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
@@ -28,13 +29,14 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let variable_types: HashMap<&str, VarType> =
|
||||
std::iter::once(("my_col", VarType::F64)).collect();
|
||||
|
||||
let mut compiled_fn: CompiledFn = compile(&untyped_expr, &variable_types)?;
|
||||
let compiled_fn: Arc<CompiledFn> = compile(&untyped_expr, &variable_types)?;
|
||||
let mut compiled_fn_ctx = CompiledFnCtx::new(compiled_fn);
|
||||
|
||||
// We use a nullable wrapper around the value union to pass typed variables.
|
||||
// For present values, it is up to us to populate the correct union member.
|
||||
// Not doing so is UB.
|
||||
let input: Box<[VariableValue]> = vec![VariableValue::from(1.2f64)].into_boxed_slice();
|
||||
let output = unsafe { compiled_fn.call(&input[..]) };
|
||||
let output = unsafe { compiled_fn_ctx.call(&input[..]) };
|
||||
assert_eq!(unsafe { output.as_f64() }, Some(1.2f64 + 1.0f64));
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -11,8 +11,7 @@ use cranelift_module::{FuncId, Module, ModuleError, default_libcall_names};
|
||||
|
||||
use super::compiled_fn::JitEntry;
|
||||
use super::{
|
||||
CompileError, CompiledFn, LoweringContext, StringArena, TypedExpr, TypedExprAst, TypedLiteral,
|
||||
TypedVariable,
|
||||
CompileError, CompiledFn, LoweringContext, TypedExpr, TypedExprAst, TypedLiteral, TypedVariable,
|
||||
};
|
||||
use crate::ast::{InferredTypeSet, Literal, UntypedExpr};
|
||||
use crate::functions::{declare_native_functions, register_jit_symbols};
|
||||
@@ -316,7 +315,6 @@ impl LoweredFunction {
|
||||
entry,
|
||||
_module: module,
|
||||
inputs: input_vars,
|
||||
string_arena: StringArena::new(),
|
||||
_typed_expr: expression,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cranelift_jit::JITModule;
|
||||
|
||||
use super::{StringArena, TypedExpr, TypedVariable};
|
||||
@@ -13,7 +16,7 @@ compile_error!(
|
||||
|
||||
// On the supported targets, VariableValue's two eightbytes are returned in two
|
||||
// integer registers by the platform C ABI. The lifetime is selected by
|
||||
// CompiledFn::call so that it is bounded by both possible sources of strings.
|
||||
// CompiledFn::call so that it is bounded by all possible sources of strings.
|
||||
// This is a Rust-to-JIT boundary whose VariableValue layout is asserted in
|
||||
// types.rs, not an interface intended for C callers.
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
@@ -29,18 +32,78 @@ pub struct CompiledFn {
|
||||
pub(crate) _module: JITModule,
|
||||
/// Input slots in the exact order expected by [`CompiledFn::call`].
|
||||
pub inputs: Vec<TypedVariable>,
|
||||
pub(crate) string_arena: StringArena,
|
||||
// This AST owns the Arc-backed literals and regexes embedded in generated code.
|
||||
pub(crate) _typed_expr: Box<TypedExpr>,
|
||||
}
|
||||
|
||||
// `JITModule` is not `Sync` because it supports lazily looking up symbols through
|
||||
// interior mutability. A `CompiledFn` only retains a finalized module to keep its
|
||||
// executable allocation alive and never invokes those mutable APIs. Its entry
|
||||
// point and the immutable resources referenced by the generated code can be
|
||||
// called concurrently when each caller supplies a distinct `StringArena`.
|
||||
unsafe impl Sync for CompiledFn {}
|
||||
|
||||
impl CompiledFn {
|
||||
/// Returns the concrete result type selected during compilation.
|
||||
pub fn result_type(&self) -> VarType {
|
||||
self._typed_expr.return_type
|
||||
}
|
||||
|
||||
/// Evaluate the compiled expression.
|
||||
/// Creates an evaluation context with a private string arena.
|
||||
pub fn context(self: &Arc<Self>) -> CompiledFnCtx {
|
||||
CompiledFnCtx::new(Arc::clone(self))
|
||||
}
|
||||
|
||||
/// Evaluates the compiled expression using the supplied string arena.
|
||||
///
|
||||
/// The mutable arena borrow prevents another evaluation from clearing the
|
||||
/// arena while an arena-backed result from this call is still live.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `args` must follow [`CompiledFn::inputs`] exactly: every present slot must
|
||||
/// contain the union member corresponding to that variable's type. Absent
|
||||
/// slots must use [`VariableValue::none`], and any borrowed strings must
|
||||
/// remain alive for the duration of this call.
|
||||
///
|
||||
/// The result cannot outlive the compiled function, the string arena, or
|
||||
/// the passed arguments' lifetime.
|
||||
#[inline(always)]
|
||||
pub unsafe fn call<'args, 'compiled, 'arena, 'output>(
|
||||
&'compiled self,
|
||||
args: &[VariableValue<'args>],
|
||||
string_arena: &'arena mut StringArena,
|
||||
) -> VariableValue<'output>
|
||||
where
|
||||
'args: 'output,
|
||||
'compiled: 'output,
|
||||
'arena: 'output,
|
||||
{
|
||||
debug_assert_eq!(args.len(), self.inputs.len());
|
||||
let args: &[VariableValue<'output>] = args;
|
||||
let string_arena = &raw mut *string_arena;
|
||||
// SAFETY: Guaranteed by the caller. The input, compiled-function, and
|
||||
// arena lifetimes outlive the lifetime selected for the returned value.
|
||||
unsafe { (self.entry)(args.as_ptr(), string_arena) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-caller mutable state used to evaluate a shared [`CompiledFn`].
|
||||
pub struct CompiledFnCtx {
|
||||
compiled_fn: Arc<CompiledFn>,
|
||||
pub(crate) string_arena: StringArena,
|
||||
}
|
||||
|
||||
impl CompiledFnCtx {
|
||||
/// Creates an evaluation context for `compiled_fn`.
|
||||
pub fn new(compiled_fn: Arc<CompiledFn>) -> Self {
|
||||
Self {
|
||||
compiled_fn,
|
||||
string_arena: StringArena::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the compiled expression using this context's string arena.
|
||||
///
|
||||
/// The mutable borrow prevents another evaluation from clearing the string
|
||||
/// arena while an arena-backed result from this call is still live.
|
||||
@@ -52,22 +115,38 @@ impl CompiledFn {
|
||||
/// slots must use [`VariableValue::none`], and any borrowed strings must
|
||||
/// remain alive for the duration of this call.
|
||||
///
|
||||
/// The result cannot outlive the compiled function nor the passed
|
||||
/// arguments' lifetime.
|
||||
/// The result cannot outlive this context nor the passed arguments'
|
||||
/// lifetime.
|
||||
#[inline(always)]
|
||||
pub unsafe fn call<'args, 'compiled, 'output>(
|
||||
&'compiled mut self,
|
||||
pub unsafe fn call<'args, 'ctx, 'output>(
|
||||
&'ctx mut self,
|
||||
args: &[VariableValue<'args>],
|
||||
) -> VariableValue<'output>
|
||||
where
|
||||
'args: 'output,
|
||||
'compiled: 'output,
|
||||
'ctx: 'output,
|
||||
{
|
||||
debug_assert_eq!(args.len(), self.inputs.len());
|
||||
let args: &[VariableValue<'output>] = args;
|
||||
let string_arena = &raw mut self.string_arena;
|
||||
// SAFETY: Guaranteed by the caller. Both the input and compiled-function
|
||||
// lifetimes outlive the lifetime selected for the returned value.
|
||||
unsafe { (self.entry)(args.as_ptr(), string_arena) }
|
||||
// SAFETY: Guaranteed by the caller. The context owns both the compiled
|
||||
// function and arena for the lifetime selected for the returned value.
|
||||
unsafe { self.compiled_fn.call(args, &mut self.string_arena) }
|
||||
}
|
||||
|
||||
/// Returns the shared compiled expression owned by this context.
|
||||
pub fn compiled_fn(&self) -> &Arc<CompiledFn> {
|
||||
&self.compiled_fn
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<CompiledFn>> for CompiledFnCtx {
|
||||
fn from(compiled_fn: Arc<CompiledFn>) -> Self {
|
||||
Self::new(compiled_fn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CompiledFnCtx {
|
||||
type Target = CompiledFn;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.compiled_fn
|
||||
}
|
||||
}
|
||||
|
||||
+40
-12
@@ -6,9 +6,10 @@ mod typed_expr;
|
||||
mod typed_expr_serialize;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) use compile_fn_builder::CompileFnBuilder;
|
||||
pub use compiled_fn::CompiledFn;
|
||||
pub use compiled_fn::{CompiledFn, CompiledFnCtx};
|
||||
use cranelift::codegen::ir::{
|
||||
InstBuilder as _, MemFlagsData, Type, Value as CraneliftValue, types as cranelift_types,
|
||||
};
|
||||
@@ -16,7 +17,7 @@ use cranelift::frontend::FunctionBuilder;
|
||||
pub use error::CompileError;
|
||||
#[cfg(test)]
|
||||
pub(crate) use string_arena::STRING_ARENA_CAPACITY;
|
||||
pub(crate) use string_arena::StringArena;
|
||||
pub use string_arena::StringArena;
|
||||
pub use typed_expr::TypedVariable;
|
||||
pub(crate) use typed_expr::{TypedExpr, TypedExprAst, TypedLiteral};
|
||||
pub(crate) use typed_expr_serialize::{format_function_call, format_string_literal};
|
||||
@@ -25,13 +26,14 @@ use crate::ast::UntypedExpr;
|
||||
use crate::functions::NativeFunctions;
|
||||
use crate::types::{VarType, VariablePrimitiveOpt, VariableValue};
|
||||
|
||||
/// Compiles an expression into an immutable, shareable native function.
|
||||
pub fn compile(
|
||||
untyped_expr: &UntypedExpr,
|
||||
var_types: &HashMap<&str, VarType>,
|
||||
) -> Result<CompiledFn, CompileError> {
|
||||
) -> Result<Arc<CompiledFn>, CompileError> {
|
||||
let mut builder = CompileFnBuilder::new(var_types);
|
||||
let typed_expr = builder.build_typed_expr(untyped_expr)?;
|
||||
builder.compile_typed_expr(typed_expr)
|
||||
builder.compile_typed_expr(typed_expr).map(Arc::new)
|
||||
}
|
||||
|
||||
/// Applies concrete variable types and serializes the resulting typed expression.
|
||||
@@ -239,20 +241,46 @@ mod tests {
|
||||
fn test_compile_bool_variable() {
|
||||
let untyped_expr = UntypedExpr::variable("flag");
|
||||
let variable_types = HashMap::from([("flag", VarType::Bool)]);
|
||||
let mut compiled_fn = compile(&untyped_expr, &variable_types).unwrap();
|
||||
assert!(compiled_fn.string_arena.allocate(1).is_some());
|
||||
let compiled_fn = compile(&untyped_expr, &variable_types).unwrap();
|
||||
let mut string_arena = StringArena::new();
|
||||
assert!(string_arena.allocate(1).is_some());
|
||||
let input = [VariableValue::some(true)];
|
||||
let output = unsafe { compiled_fn.call(&input) };
|
||||
let output = unsafe { compiled_fn.call(&input, &mut string_arena) };
|
||||
|
||||
assert_eq!(unsafe { output.as_bool() }, Some(true));
|
||||
assert_eq!(compiled_fn.string_arena.used_bytes(), 1);
|
||||
assert_eq!(string_arena.used_bytes(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compiled_fn_is_send_and_sync() {
|
||||
fn assert_send_and_sync<T: Send + Sync>() {}
|
||||
|
||||
assert_send_and_sync::<CompiledFn>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_contexts_have_independent_string_arenas() {
|
||||
let untyped_expr = Function::Lower.call_untyped_expr(vec![UntypedExpr::variable("value")]);
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let compiled_fn = compile(&untyped_expr, &variable_types).unwrap();
|
||||
let mut first_ctx = compiled_fn.context();
|
||||
let mut second_ctx = CompiledFnCtx::from(compiled_fn);
|
||||
|
||||
let first = unsafe { first_ctx.call(&[VariableValue::some("FIRST")]) };
|
||||
assert_eq!(unsafe { first.as_str() }, Some("first"));
|
||||
assert_eq!(first_ctx.string_arena.used_bytes(), 5);
|
||||
|
||||
let second = unsafe { second_ctx.call(&[VariableValue::some("SECOND")]) };
|
||||
assert_eq!(unsafe { second.as_str() }, Some("second"));
|
||||
assert_eq!(second_ctx.string_arena.used_bytes(), 6);
|
||||
assert_eq!(first_ctx.string_arena.used_bytes(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_none_variable() {
|
||||
let untyped_expr = UntypedExpr::variable("value");
|
||||
let variable_types = HashMap::from([("value", VarType::U64)]);
|
||||
let mut compiled_fn = compile(&untyped_expr, &variable_types).unwrap();
|
||||
let mut compiled_fn = compile(&untyped_expr, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::none()];
|
||||
assert_eq!(compiled_fn.result_type(), VarType::U64);
|
||||
let output = unsafe { compiled_fn.call(&input) };
|
||||
@@ -263,7 +291,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_string_literal_keeps_backing_data_alive() {
|
||||
let untyped_expr = UntypedExpr::literal("hello");
|
||||
let mut compiled_fn = compile(&untyped_expr, &HashMap::new()).unwrap();
|
||||
let mut compiled_fn = compile(&untyped_expr, &HashMap::new()).unwrap().context();
|
||||
drop(untyped_expr);
|
||||
let output = unsafe { compiled_fn.call(&[]) };
|
||||
|
||||
@@ -274,7 +302,7 @@ mod tests {
|
||||
fn test_compile_returns_borrowed_string_variable_directly() {
|
||||
let untyped_expr = UntypedExpr::variable("value");
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled_fn = compile(&untyped_expr, &variable_types).unwrap();
|
||||
let mut compiled_fn = compile(&untyped_expr, &variable_types).unwrap().context();
|
||||
let value = String::from("hello from an input");
|
||||
let input = [VariableValue::some(value.as_str())];
|
||||
let output = unsafe { compiled_fn.call(&input) };
|
||||
@@ -287,7 +315,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_none_literal_returns_absent_value() {
|
||||
let untyped_expr = UntypedExpr::literal(crate::ast::Literal::None);
|
||||
let mut compiled_fn = compile(&untyped_expr, &HashMap::new()).unwrap();
|
||||
let mut compiled_fn = compile(&untyped_expr, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(compiled_fn.result_type(), VarType::None);
|
||||
let output = unsafe { compiled_fn.call(&[]) };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
pub(crate) const STRING_ARENA_CAPACITY: usize = 262_144;
|
||||
|
||||
/// Fixed-capacity storage for strings constructed while evaluating an expression.
|
||||
pub(crate) struct StringArena {
|
||||
pub struct StringArena {
|
||||
buffer: Box<[u8; STRING_ARENA_CAPACITY]>,
|
||||
cursor: usize,
|
||||
}
|
||||
@@ -9,7 +9,8 @@ pub(crate) struct StringArena {
|
||||
impl StringArena {
|
||||
pub(crate) const CURSOR_OFFSET: i32 = std::mem::offset_of!(StringArena, cursor) as i32;
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
/// Creates an empty string arena.
|
||||
pub fn new() -> Self {
|
||||
let buffer = vec![0; STRING_ARENA_CAPACITY].into_boxed_slice();
|
||||
let buffer = buffer
|
||||
.try_into()
|
||||
|
||||
@@ -192,7 +192,7 @@ mod tests {
|
||||
fn test_integer_edges_and_runtime_null() {
|
||||
let expression = deserialize("(ABS value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::I64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[VariableValue::some(-7i64)]).as_i64() },
|
||||
Some(7)
|
||||
@@ -211,7 +211,7 @@ mod tests {
|
||||
fn test_float_preserves_negative_zero_and_nan_bits() {
|
||||
let expression = deserialize("(ABS value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::F64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
let negative_zero = unsafe {
|
||||
compiled
|
||||
|
||||
@@ -294,7 +294,7 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
|
||||
assert_eq!(unsafe { output.as_u64() }, Some(9223372036854775808u64));
|
||||
@@ -325,7 +325,7 @@ mod tests {
|
||||
UntypedExpr::variable("myfield"),
|
||||
]);
|
||||
let variable_types = HashMap::from([("myfield", VarType::I64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(-8i64)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -342,7 +342,7 @@ mod tests {
|
||||
|
||||
for args in argument_orders {
|
||||
let expression = Function::Add.call_untyped_expr(args);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(41u64)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
assert_eq!(unsafe { output.as_u64() }, Some(42));
|
||||
@@ -356,7 +356,7 @@ mod tests {
|
||||
let expression = Function::Add
|
||||
.call_untyped_expr(vec![UntypedExpr::variable("myfield"), nested_literals]);
|
||||
let variable_types = HashMap::from([("myfield", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(39u64)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -371,7 +371,7 @@ mod tests {
|
||||
UntypedExpr::literal(0.5f64),
|
||||
]);
|
||||
let variable_types = HashMap::from([("myfield", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(10u64)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -383,7 +383,7 @@ mod tests {
|
||||
let expression = Function::Add
|
||||
.call_untyped_expr(vec![UntypedExpr::variable("x"), UntypedExpr::variable("y")]);
|
||||
let variable_types = HashMap::from([("x", VarType::U64), ("y", VarType::F64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(10u64), VariableValue::some(0.5f64)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -394,7 +394,7 @@ mod tests {
|
||||
fn test_compile_add_propagates_none_input() {
|
||||
let expression = crate::ast::deserialize(r#"(ADD x 0.5f64)"#).unwrap();
|
||||
let variable_types = HashMap::from([("x", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::none()];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
assert_eq!(unsafe { output.as_f64() }, None);
|
||||
@@ -407,7 +407,7 @@ mod tests {
|
||||
UntypedExpr::literal(0.5f64),
|
||||
]);
|
||||
let variable_types = HashMap::from([("x", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(u64::MAX)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -422,7 +422,7 @@ mod tests {
|
||||
UntypedExpr::literal(1u64),
|
||||
]);
|
||||
let variable_types = HashMap::from([("x", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(4i64)];
|
||||
assert_eq!(compiled.inputs.len(), 1);
|
||||
assert_eq!(compiled.inputs[0].variable_name.as_ref(), "x");
|
||||
@@ -441,7 +441,7 @@ mod tests {
|
||||
UntypedExpr::literal(1.2f64),
|
||||
]);
|
||||
let variable_types = HashMap::from([("x", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some(4u64)];
|
||||
assert_eq!(compiled.inputs.len(), 1);
|
||||
assert_eq!(compiled.inputs[0].variable_name.as_ref(), "x");
|
||||
@@ -460,7 +460,7 @@ mod tests {
|
||||
assert_eq!(typed_expr.return_type, VarType::I64);
|
||||
|
||||
let expression = Function::Add.call_untyped_expr(Vec::new());
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
assert_eq!(unsafe { output.as_i64() }, Some(0));
|
||||
}
|
||||
@@ -472,7 +472,7 @@ mod tests {
|
||||
let typed_expr = crate::typed_expr_from_str("(ADD 1.2f64 1u64)", &variable_types);
|
||||
assert_eq!(typed_expr.return_type, VarType::F64);
|
||||
let expression = Function::Add.call_untyped_expr(args);
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
assert_eq!(unsafe { output.as_f64() }, Some(2.2f64));
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no runtime inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -162,7 +162,7 @@ mod tests {
|
||||
fn test_runtime_null_propagation() {
|
||||
let expression = deserialize("(AND left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Bool), ("right", VarType::Bool)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<i64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable i64 value.
|
||||
unsafe { compiled.call(&[]).as_i64() }
|
||||
}
|
||||
@@ -193,7 +193,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null() {
|
||||
let expression = deserialize("(CEIL value)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::F64)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::F64)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable f64 argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//! written. Consequently `CONCAT(",", "false", "", "b")` is `"b"`, while the reversed values
|
||||
//! produce `"b,"`. Empty output is present, not null.
|
||||
//!
|
||||
//! Constructed bytes live in `CompiledFn`'s fixed-capacity call arena; arena exhaustion returns
|
||||
//! Constructed bytes live in the caller's fixed-capacity string arena; arena exhaustion returns
|
||||
//! null.
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -335,7 +335,7 @@ unsafe extern "C" fn string_concat(
|
||||
output_len = next_len;
|
||||
}
|
||||
|
||||
// SAFETY: CompiledFn exclusively owns and passes this arena for the duration of the call.
|
||||
// SAFETY: The caller exclusively borrows and passes this arena for the duration of the call.
|
||||
let Some(output_ptr) = (unsafe { &mut *string_arena }).allocate(output_len) else {
|
||||
return RawStr::none();
|
||||
};
|
||||
@@ -384,7 +384,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable strings.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -434,7 +434,7 @@ mod tests {
|
||||
fn test_runtime_null_is_strict() {
|
||||
let expression = deserialize(r#"(CONCAT ":" "true" left right)"#).unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
@@ -450,7 +450,7 @@ mod tests {
|
||||
fn test_nested_values_are_stable_and_arena_exhaustion_is_null() {
|
||||
let expression = deserialize(r#"(CONCAT ":" "false" (UPPER left) (LOWER right))"#).unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -156,7 +156,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_integer_inputs_use_floating_point_division() {
|
||||
let expression = deserialize("(DIVIDE 5i64 2i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
|
||||
assert_eq!(compiled.result_type(), VarType::F64);
|
||||
// SAFETY: The expression has no inputs and returns f64.
|
||||
@@ -167,7 +167,7 @@ mod tests {
|
||||
fn test_positive_and_negative_zero_divisors_return_none() {
|
||||
for expression in ["(DIVIDE 1f64 0f64)", "(DIVIDE 1f64 -0f64)"] {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns nullable f64.
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_f64() }, None);
|
||||
}
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_nan_divisor_remains_present() {
|
||||
let expression = deserialize("(DIVIDE 1f64 nanf64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
|
||||
// SAFETY: The expression has no inputs and returns f64.
|
||||
assert!(unsafe { compiled.call(&[]).as_f64() }.unwrap().is_nan());
|
||||
@@ -186,7 +186,7 @@ mod tests {
|
||||
fn test_runtime_null_propagation() {
|
||||
let expression = deserialize("(DIVIDE left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::I64), ("right", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
|
||||
@@ -297,7 +297,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> bool {
|
||||
let expression = ast::deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
|
||||
unsafe { output.as_bool() }.unwrap()
|
||||
@@ -305,7 +305,7 @@ mod tests {
|
||||
|
||||
fn eval_nullable(expression: &str) -> Option<bool> {
|
||||
let expression = ast::deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable boolean.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -352,7 +352,7 @@ mod tests {
|
||||
fn test_compile_signed_unsigned_comparison() {
|
||||
let expression = ast::deserialize("(EQ signed unsigned)").unwrap();
|
||||
let variable_types = HashMap::from([("signed", VarType::I64), ("unsigned", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
for (signed, unsigned, expected) in [(7i64, 7u64, true), (-1, u64::MAX, false)] {
|
||||
let input = [VariableValue::some(signed), VariableValue::some(unsigned)];
|
||||
@@ -365,7 +365,7 @@ mod tests {
|
||||
fn test_compile_float_integer_comparison_is_exact() {
|
||||
let expression = ast::deserialize("(EQ float integer)").unwrap();
|
||||
let variable_types = HashMap::from([("float", VarType::F64), ("integer", VarType::I64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let cases = [
|
||||
(1.0, 1, true),
|
||||
(1.2, 1, false),
|
||||
@@ -385,7 +385,7 @@ mod tests {
|
||||
fn test_compile_float_unsigned_comparison_is_exact() {
|
||||
let expression = ast::deserialize("(EQ float integer)").unwrap();
|
||||
let variable_types = HashMap::from([("float", VarType::F64), ("integer", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let cases = [
|
||||
(1.0, 1, true),
|
||||
(1.2, 1, false),
|
||||
@@ -405,7 +405,7 @@ mod tests {
|
||||
fn test_compile_string_equality_compares_contents() {
|
||||
let expression = ast::deserialize("(EQ left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let left_value = String::from("same contents");
|
||||
let right_value = String::from("same contents");
|
||||
let input = [
|
||||
@@ -439,7 +439,7 @@ mod tests {
|
||||
fn test_compile_runtime_none_equality() {
|
||||
let expression = ast::deserialize("(EQ left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::U64), ("right", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[VariableValue::none(), VariableValue::none()]) };
|
||||
assert_eq!(unsafe { output.as_bool() }, None);
|
||||
|
||||
@@ -451,7 +451,7 @@ mod tests {
|
||||
fn test_compile_none_literal_equals_absent_variable() {
|
||||
let expression = ast::deserialize("(EQ value none)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[VariableValue::none()]) };
|
||||
|
||||
assert_eq!(unsafe { output.as_bool() }, None);
|
||||
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<i64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable i64 value.
|
||||
unsafe { compiled.call(&[]).as_i64() }
|
||||
}
|
||||
@@ -193,7 +193,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null() {
|
||||
let expression = deserialize("(FLOOR value)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::F64)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::F64)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable f64 argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -82,7 +82,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -125,7 +125,7 @@ mod tests {
|
||||
|
||||
let expression = deserialize("(GT float integer)").unwrap();
|
||||
let variable_types = HashMap::from([("float", VarType::F64), ("integer", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
@@ -146,7 +146,7 @@ mod tests {
|
||||
|
||||
let expression = deserialize("(GT left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -129,7 +129,7 @@ mod tests {
|
||||
fn test_runtime_null_propagates() {
|
||||
let expression = deserialize("(GT_EQ left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
|
||||
@@ -159,11 +159,11 @@ mod tests {
|
||||
));
|
||||
for (expr, expected) in [("(IF true 7i64 9i64)", 7), ("(IF false 7i64 9i64)", 9)] {
|
||||
let expr = deserialize(expr).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_i64() }, Some(expected));
|
||||
}
|
||||
let expr = deserialize("(IF true \"yes\" \"no\")").unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_str() }, Some("yes"));
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ mod tests {
|
||||
("yes", VarType::I64),
|
||||
("no", VarType::I64),
|
||||
]);
|
||||
let mut compiled = compile(&expr, &types).unwrap();
|
||||
let mut compiled = compile(&expr, &types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -157,7 +157,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<f64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable f64 values.
|
||||
unsafe { compiled.call(&[]).as_f64() }
|
||||
}
|
||||
@@ -209,7 +209,7 @@ mod tests {
|
||||
fn test_runtime_null_propagates() {
|
||||
let expression = deserialize("(INT_MOD value modulus)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::I64), ("modulus", VarType::F64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -103,7 +103,7 @@ mod tests {
|
||||
|
||||
fn eval_without_args(expression: &str) -> (bool, Option<bool>) {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert!(compiled.inputs.is_empty());
|
||||
// SAFETY: The compiled expression has no inputs.
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
@@ -115,7 +115,7 @@ mod tests {
|
||||
fn eval_variable(var_type: VarType, input: VariableValue<'_>) -> (bool, Option<bool>) {
|
||||
let expression = deserialize("(IS_NOT_NULL value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", var_type)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
// SAFETY: `input` is constructed with the union member matching `var_type`
|
||||
// at each call site, or is absent and therefore has no active payload.
|
||||
let output = unsafe { compiled.call(&[input]) };
|
||||
|
||||
@@ -102,7 +102,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no runtime inputs and return booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -157,7 +157,7 @@ mod tests {
|
||||
fn test_runtime_absent_variable_is_true_and_present_variable_is_false() {
|
||||
let expression = deserialize("(IS_NULL value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::I64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
|
||||
@@ -133,7 +133,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable string.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -167,7 +167,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null() {
|
||||
let expression = deserialize("(LEFT value 2i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable string argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! than expanding to `i` followed by a combining dot. Null input returns null, while empty input
|
||||
//! remains present.
|
||||
//!
|
||||
//! Constructed bytes live in `CompiledFn`'s fixed-capacity call arena; arena exhaustion returns
|
||||
//! Constructed bytes live in the caller's fixed-capacity string arena; arena exhaustion returns
|
||||
//! null.
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -173,7 +173,7 @@ unsafe extern "C" fn string_lowercase(
|
||||
|
||||
// The input reference above is no longer live. This matters for nested
|
||||
// LOWER calls whose input points into an earlier, disjoint arena allocation.
|
||||
// SAFETY: CompiledFn exclusively owns and passes this arena for the call.
|
||||
// SAFETY: The caller exclusively borrows and passes this arena for the call.
|
||||
let Some(output_ptr) = (unsafe { &mut *string_arena }).allocate(output_len) else {
|
||||
return RawStr::none();
|
||||
};
|
||||
@@ -248,7 +248,7 @@ mod tests {
|
||||
fn test_compile_lowercases_unicode_without_mutating_input() {
|
||||
let expression = deserialize("(LOWER value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input_string = String::from("CAFÉ İSTANBUL");
|
||||
let input = [VariableValue::some(input_string.as_str())];
|
||||
|
||||
@@ -262,7 +262,7 @@ mod tests {
|
||||
fn test_compile_propagates_none_and_preserves_empty_string() {
|
||||
let expression = deserialize("(LOWER value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
let none = unsafe { compiled.call(&[VariableValue::none()]) };
|
||||
assert_eq!(unsafe { none.as_str() }, None);
|
||||
@@ -275,7 +275,7 @@ mod tests {
|
||||
fn test_nested_lower_uses_stable_arena_allocations() {
|
||||
let expression = deserialize("(LOWER (LOWER value))").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("HeLLo")];
|
||||
|
||||
assert_eq!(unsafe { compiled.call(&input).as_str() }, Some("hello"));
|
||||
@@ -285,7 +285,7 @@ mod tests {
|
||||
fn test_multiple_lower_calls_keep_previous_allocations_valid() {
|
||||
let expression = deserialize("(EQ (LOWER left) (LOWER right))").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("FiRsT"), VariableValue::some("SeCoNd")];
|
||||
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
@@ -298,7 +298,7 @@ mod tests {
|
||||
fn test_arena_exhaustion_returns_none_without_advancing_cursor() {
|
||||
let expression = deserialize("(LOWER value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let too_large = "A".repeat(STRING_ARENA_CAPACITY + 1);
|
||||
let input = [VariableValue::some(too_large.as_str())];
|
||||
|
||||
@@ -312,7 +312,7 @@ mod tests {
|
||||
fn test_arena_cursor_is_cleared_before_each_call() {
|
||||
let expression = deserialize("(LOWER value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let full = "A".repeat(STRING_ARENA_CAPACITY);
|
||||
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -130,7 +130,7 @@ mod tests {
|
||||
fn test_runtime_null_propagates() {
|
||||
let expression = deserialize("(LT left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::I64), ("right", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
|
||||
@@ -87,7 +87,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -129,7 +129,7 @@ mod tests {
|
||||
fn test_runtime_null_propagates() {
|
||||
let expression = deserialize("(LT_EQ left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::F64), ("right", VarType::I64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
|
||||
@@ -171,20 +171,20 @@ mod tests {
|
||||
})
|
||||
));
|
||||
let expression = deserialize("(MAX 7i64 -3i64 12i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_i64() }, Some(12));
|
||||
let expression = deserialize("(MAX 7u64 13u64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_u64() }, Some(13));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float_nan_and_null_behavior() {
|
||||
let expression = deserialize("(MAX nanf64 3f64 -2f64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_f64() }, Some(3.0));
|
||||
let expression = deserialize("(MAX nanf64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[]).as_f64() },
|
||||
Some(f64::NEG_INFINITY)
|
||||
@@ -194,7 +194,8 @@ mod tests {
|
||||
&expression,
|
||||
&HashMap::from([("left", VarType::F64), ("right", VarType::F64)]),
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -170,20 +170,20 @@ mod tests {
|
||||
));
|
||||
|
||||
let expression = deserialize("(MIN 7i64 -3i64 2i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_i64() }, Some(-3));
|
||||
let expression = deserialize("(MIN 7u64 3u64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_u64() }, Some(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float_nan_and_null_behavior() {
|
||||
let expression = deserialize("(MIN nanf64 3f64 -2f64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_f64() }, Some(-2.0));
|
||||
let expression = deserialize("(MIN nanf64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_f64() }, Some(f64::INFINITY));
|
||||
|
||||
let expression = deserialize("(MIN left right)").unwrap();
|
||||
@@ -191,7 +191,8 @@ mod tests {
|
||||
&expression,
|
||||
&HashMap::from([("left", VarType::I64), ("right", VarType::I64)]),
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -176,18 +176,18 @@ mod tests {
|
||||
#[test]
|
||||
fn test_signed_unsigned_and_float_multiplication() {
|
||||
let expression = deserialize("(MULTIPLY -7i64 3i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns i64.
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_i64() }, Some(-21));
|
||||
|
||||
let expression = deserialize("(MULTIPLY 9223372036854775808u64 2u64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::U64);
|
||||
// SAFETY: The expression has no inputs and returns u64; multiplication wraps.
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_u64() }, Some(0));
|
||||
|
||||
let expression = deserialize("(MULTIPLY 1.5f64 2f64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns f64.
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_f64() }, Some(3.0));
|
||||
}
|
||||
@@ -195,7 +195,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_nan_is_preserved() {
|
||||
let expression = deserialize("(MULTIPLY nanf64 2f64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
|
||||
// SAFETY: The expression has no inputs and returns f64.
|
||||
assert!(unsafe { compiled.call(&[]).as_f64() }.unwrap().is_nan());
|
||||
@@ -205,7 +205,7 @@ mod tests {
|
||||
fn test_runtime_null_propagation_and_integer_coercion() {
|
||||
let expression = deserialize("(MULTIPLY value 3i64)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::U64);
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
@@ -223,7 +223,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_time_none_propagates() {
|
||||
let expression = deserialize("(MULTIPLY none 2i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
|
||||
assert_eq!(compiled.result_type(), VarType::None);
|
||||
// SAFETY: The expression has no inputs and returns an absent value.
|
||||
|
||||
@@ -117,7 +117,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -157,7 +157,7 @@ mod tests {
|
||||
|
||||
let expression = deserialize("(NEQ left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -112,7 +112,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no runtime inputs and return booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -157,7 +157,7 @@ mod tests {
|
||||
|
||||
let expression = deserialize("(NOT value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Bool)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
|
||||
@@ -120,7 +120,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<bool> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no runtime inputs and return nullable booleans.
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
@@ -163,7 +163,7 @@ mod tests {
|
||||
fn test_runtime_null_handling() {
|
||||
let expression = deserialize("(OR left right)").unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Bool), ("right", VarType::Bool)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
|
||||
@@ -152,7 +152,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<f64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable f64 values.
|
||||
unsafe { compiled.call(&[]).as_f64() }
|
||||
}
|
||||
@@ -196,7 +196,7 @@ mod tests {
|
||||
fn test_runtime_null_propagates() {
|
||||
let expression = deserialize("(POW base exponent)").unwrap();
|
||||
let variable_types = HashMap::from([("base", VarType::I64), ("exponent", VarType::F64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -296,7 +296,7 @@ mod tests {
|
||||
let expression =
|
||||
ast::deserialize(r#"(REGEXP_EXTRACT message "([a-z]+)-(\\d+)" 1u64)"#).unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let haystack = "prefix user-123 suffix";
|
||||
let input = [VariableValue::some(haystack)];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
@@ -311,7 +311,7 @@ mod tests {
|
||||
let expression =
|
||||
ast::deserialize(r#"(REGEXP_EXTRACT message "([a-z]+)-(\\d+)" 2u64)"#).unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("user-123")];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -323,7 +323,7 @@ mod tests {
|
||||
let expression =
|
||||
ast::deserialize(r#"(REGEXP_EXTRACT message "([a-z]+)-(\\d+)" 0u64)"#).unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("no digits here")];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -334,7 +334,7 @@ mod tests {
|
||||
fn test_compile_propagates_none_haystack() {
|
||||
let expression = ast::deserialize(r#"(REGEXP_EXTRACT message "([a-z]+)" 0u64)"#).unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::none()];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -344,7 +344,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_propagates_compile_time_none_haystack() {
|
||||
let expression = ast::deserialize(r#"(REGEXP_EXTRACT missing "([a-z]+)" 0u64)"#).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::None);
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
|
||||
@@ -354,7 +354,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_distinguishes_empty_capture_from_none() {
|
||||
let expression = ast::deserialize(r#"(REGEXP_EXTRACT "b" "(a*)b" 1u64)"#).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
let output = unsafe { compiled.call(&[]) };
|
||||
|
||||
assert_eq!(unsafe { output.as_str() }, Some(""));
|
||||
@@ -364,7 +364,7 @@ mod tests {
|
||||
fn test_compile_omitted_group_defaults_to_full_match() {
|
||||
let expression = ast::deserialize(r#"(REGEXP_EXTRACT message "[a-z]+-\\d+")"#).unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("prefix user-123 suffix")];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -376,7 +376,7 @@ mod tests {
|
||||
let expression =
|
||||
ast::deserialize(r#"(REGEXP_EXTRACT message "[a-z]+-\\d+" 0u64)"#).unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("prefix user-123 suffix")];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
@@ -393,7 +393,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
let variable_types = HashMap::from([("message", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input = [VariableValue::some("id=user-123!")];
|
||||
let output = unsafe { compiled.call(&input) };
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ mod tests {
|
||||
use crate::types::VariableValue;
|
||||
fn eval(expr: &str) -> Option<bool> {
|
||||
let expr = deserialize(expr).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap().context();
|
||||
unsafe { compiled.call(&[]).as_bool() }
|
||||
}
|
||||
#[test]
|
||||
@@ -193,7 +193,9 @@ mod tests {
|
||||
assert_eq!(eval("(REGEXP_LIKE none \"x\")"), Some(false));
|
||||
assert_eq!(eval("(REGEXP_LIKE 123i64 \"123\")"), Some(false));
|
||||
let expr = deserialize("(REGEXP_LIKE value \"x\")").unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[VariableValue::none()]).as_bool() },
|
||||
Some(false)
|
||||
|
||||
@@ -140,7 +140,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable string.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -174,7 +174,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null() {
|
||||
let expression = deserialize("(RIGHT value 2i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[VariableValue::some("abc")]).as_str() },
|
||||
Some("bc")
|
||||
@@ -188,10 +190,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_utf8_boundary_break_returns_null() {
|
||||
let expression = deserialize(r#"(RIGHT "下北沢" 1i64)"#).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::default()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::default()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_str() }, None);
|
||||
let expression = deserialize(r#"(RIGHT "下北沢" 3i64)"#).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::default()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::default()).unwrap().context();
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_str() }, Some("沢"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,14 +381,14 @@ mod tests {
|
||||
|
||||
fn eval_i64(expression: &str) -> Option<i64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable i64 value.
|
||||
unsafe { compiled.call(&[]).as_i64() }
|
||||
}
|
||||
|
||||
fn eval_f64(expression: &str) -> Option<f64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable f64 value.
|
||||
unsafe { compiled.call(&[]).as_f64() }
|
||||
}
|
||||
@@ -467,7 +467,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null_and_integer_rounding() {
|
||||
let expression = deserialize("(ROUND value -1i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::I64)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::I64)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable i64 argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -240,7 +240,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable string.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -295,7 +295,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null() {
|
||||
let expression = deserialize("(SPLIT_AFTER value \".\")").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable string argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -239,7 +239,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable string.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -306,7 +306,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null() {
|
||||
let expression = deserialize("(SPLIT_BEFORE value \".\")").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable string argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -109,7 +109,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<f64> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable f64 value.
|
||||
unsafe { compiled.call(&[]).as_f64() }
|
||||
}
|
||||
@@ -160,7 +160,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null_and_negative_input() {
|
||||
let expression = deserialize("(SQRT value)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::I64)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::I64)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
|
||||
// SAFETY: The compiled expression expects one nullable i64 argument.
|
||||
assert_eq!(
|
||||
|
||||
@@ -228,7 +228,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: The expression has no inputs and returns a nullable string.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -271,7 +271,9 @@ mod tests {
|
||||
assert_eq!(eval("(SUBSTRING \"abc\" -1i64 1i64)"), None);
|
||||
|
||||
let expression = deserialize("(SUBSTRING value 0i64 2i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[VariableValue::some("abc")]).as_str() },
|
||||
Some("ab")
|
||||
|
||||
@@ -159,7 +159,7 @@ mod tests {
|
||||
use crate::types::VariableValue;
|
||||
fn eval(expr: &str) -> Option<i64> {
|
||||
let expr = deserialize(expr).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap().context();
|
||||
unsafe { compiled.call(&[]).as_i64() }
|
||||
}
|
||||
#[test]
|
||||
@@ -183,7 +183,8 @@ mod tests {
|
||||
&expr,
|
||||
&HashMap::from([("text", VarType::Str), ("needle", VarType::Str)]),
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -177,13 +177,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_signed_unsigned_and_float_subtraction() {
|
||||
let expression = deserialize("(SUBTRACT 7i64 10i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::I64);
|
||||
// SAFETY: The expression has no inputs and returns i64.
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_i64() }, Some(-3));
|
||||
|
||||
let expression = deserialize("(SUBTRACT 9223372036854775810u64 1u64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::U64);
|
||||
// SAFETY: The expression has no inputs and returns u64.
|
||||
assert_eq!(
|
||||
@@ -192,7 +192,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let expression = deserialize("(SUBTRACT 7.5f64 2f64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::F64);
|
||||
// SAFETY: The expression has no inputs and returns f64.
|
||||
assert_eq!(unsafe { compiled.call(&[]).as_f64() }, Some(5.5));
|
||||
@@ -202,7 +202,7 @@ mod tests {
|
||||
fn test_unsigned_subtraction_wraps() {
|
||||
let expression = deserialize("(SUBTRACT value 1u64)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
assert_eq!(
|
||||
@@ -215,7 +215,7 @@ mod tests {
|
||||
fn test_runtime_null_propagation_and_mixed_literal_coercion() {
|
||||
let expression = deserialize("(SUBTRACT value 1i64)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::U64)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(compiled.result_type(), VarType::U64);
|
||||
|
||||
// SAFETY: The input and output types match the compiled signature.
|
||||
@@ -233,7 +233,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_time_none_propagates() {
|
||||
let expression = deserialize("(SUBTRACT none 1i64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
|
||||
assert_eq!(compiled.result_type(), VarType::None);
|
||||
// SAFETY: The expression has no inputs and returns an absent value.
|
||||
@@ -243,7 +243,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_compile_time_i64() {
|
||||
let expression = deserialize("(SUBTRACT 1u64 10u64)").unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
|
||||
assert_eq!(compiled.result_type(), VarType::I64);
|
||||
// SAFETY: The expression has no inputs and returns an absent value.
|
||||
|
||||
@@ -82,7 +82,7 @@ mod tests {
|
||||
|
||||
fn eval(expression: &str) -> Option<String> {
|
||||
let expression = deserialize(expression).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expression, &HashMap::new()).unwrap().context();
|
||||
// SAFETY: These expressions have no inputs and return nullable strings.
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
@@ -125,7 +125,7 @@ mod tests {
|
||||
fn test_runtime_null_is_strict() {
|
||||
let expression = deserialize(r#"(TEXT_JOIN "" "false" left right)"#).unwrap();
|
||||
let variable_types = HashMap::from([("left", VarType::Str), ("right", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
compiled
|
||||
|
||||
@@ -237,7 +237,7 @@ mod tests {
|
||||
|
||||
fn eval(expr: &str) -> Option<String> {
|
||||
let expr = deserialize(expr).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::new()).unwrap().context();
|
||||
unsafe { compiled.call(&[]).as_str().map(str::to_owned) }
|
||||
}
|
||||
|
||||
@@ -270,7 +270,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_runtime_null_and_unicode() {
|
||||
let expr = deserialize("(TRIM value \"é\" \"both\")").unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::from([("value", VarType::Str)])).unwrap();
|
||||
let mut compiled = compile(&expr, &HashMap::from([("value", VarType::Str)]))
|
||||
.unwrap()
|
||||
.context();
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[VariableValue::some("éhelloé")]).as_str() },
|
||||
Some("hello")
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! into several (for example `ß` to `SS`) are not applied. Null input returns null, while an empty
|
||||
//! input returns a present empty string.
|
||||
//!
|
||||
//! Constructed bytes live in `CompiledFn`'s fixed-capacity call arena; arena exhaustion returns
|
||||
//! Constructed bytes live in the caller's fixed-capacity string arena; arena exhaustion returns
|
||||
//! null.
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -171,7 +171,7 @@ unsafe extern "C" fn string_uppercase(
|
||||
};
|
||||
|
||||
// The input borrow has ended. Nested calls may pass an earlier, disjoint arena allocation.
|
||||
// SAFETY: CompiledFn exclusively owns and passes this arena for the duration of the call.
|
||||
// SAFETY: The caller exclusively borrows and passes this arena for the duration of the call.
|
||||
let Some(output_ptr) = (unsafe { &mut *string_arena }).allocate(output_len) else {
|
||||
return RawStr::none();
|
||||
};
|
||||
@@ -245,7 +245,7 @@ mod tests {
|
||||
fn test_uppercases_unicode_without_full_case_expansion_or_mutation() {
|
||||
let expression = deserialize("(UPPER value)").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
let input_string = String::from("café Straße ı");
|
||||
|
||||
let output = unsafe { compiled.call(&[VariableValue::some(input_string.as_str())]) };
|
||||
@@ -258,7 +258,7 @@ mod tests {
|
||||
fn test_null_empty_nested_and_arena_exhaustion() {
|
||||
let expression = deserialize("(UPPER (UPPER value))").unwrap();
|
||||
let variable_types = HashMap::from([("value", VarType::Str)]);
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap();
|
||||
let mut compiled = compile(&expression, &variable_types).unwrap().context();
|
||||
|
||||
assert_eq!(
|
||||
unsafe { compiled.call(&[VariableValue::none()]).as_str() },
|
||||
|
||||
Reference in New Issue
Block a user