diff --git a/interpreter/src/context.rs b/interpreter/src/context.rs index 5a48c47..52122a4 100644 --- a/interpreter/src/context.rs +++ b/interpreter/src/context.rs @@ -40,6 +40,25 @@ pub enum Context<'a> { } impl<'a> Context<'a> { + pub fn serialize_variable( + &mut self, + name: S, + value: V, + ) -> Result<(), Box> + where + S: Into, + V: serde::Serialize, + { + match self { + Context::Root { variables, .. } => { + variables.insert(name.into(), crate::ser::to_value(value)?); + } + Context::Child { variables, .. } => { + variables.insert(name.into(), crate::ser::to_value(value)?); + } + } + Ok(()) + } pub fn add_variable( &mut self, name: S, diff --git a/interpreter/src/external.rs b/interpreter/src/external.rs new file mode 100644 index 0000000..e95dff0 --- /dev/null +++ b/interpreter/src/external.rs @@ -0,0 +1,327 @@ +use std::{ + any::TypeId, + cmp::Ordering, + fmt::Debug, + sync::{atomic::AtomicUsize, Arc}, +}; + +use crate::{ + objects::{TryIntoValue, ValueType}, + ExecutionError, ResolveResult, Value, +}; + +#[allow(unused_variables)] +pub trait AsValue: Send + Sync { + #[inline] + fn value_field(&self, name: &str) -> ResolveResult { + Err(ExecutionError::NoSuchKey(Arc::new(name.to_string()))) + } + + #[inline] + fn value_index(&self, index: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedIndexExternal( + index.clone(), + std::any::type_name::(), + )) + } + + #[inline] + fn to_value(&self, hint: ValueType) -> Option { + None + } + + /// Called when other [`Value`] is added to this type. + #[inline] + fn value_add(&self, second: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "+", + std::any::type_name::(), + "Value", + )) + } + + /// Called when this type is added to other [`Value`]. + /// + /// Override if addition is non-commutative. + #[inline] + fn value_add_other(&self, first: &Value) -> ResolveResult { + AsValue::value_add(self, first).map_err(|err| match err { + ExecutionError::UnsupportedBinaryOperatorExternal(op, a, b) => { + ExecutionError::UnsupportedBinaryOperatorExternal(op, b, a) + } + other => other, + }) + } + + /// Called when other [`Value`] is subtracted from this type. + #[inline] + fn value_sub(&self, second: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "-", + std::any::type_name::(), + "Value", + )) + } + + /// Called when this type is subtracted from other [`Value`]. + /// + /// Override if subtraction is non-commutative. + #[inline] + fn value_sub_other(&self, first: &Value) -> ResolveResult { + AsValue::value_sub(self, first).map_err(|err| match err { + ExecutionError::UnsupportedBinaryOperatorExternal(op, a, b) => { + ExecutionError::UnsupportedBinaryOperatorExternal(op, b, a) + } + other => other, + }) + } + + /// Called when this type is multiplied with other [`Value`]. + #[inline] + fn value_mul(&self, second: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "*", + std::any::type_name::(), + "Value", + )) + } + + /// Called when other [`Value`] is multiplied with this type. + /// + /// Override if multiplication is non-commutative. + #[inline] + fn value_mul_other(&self, first: &Value) -> ResolveResult { + AsValue::value_mul(self, first).map_err(|err| match err { + ExecutionError::UnsupportedBinaryOperatorExternal(op, a, b) => { + ExecutionError::UnsupportedBinaryOperatorExternal(op, b, a) + } + other => other, + }) + } + + /// Called when other [`Value`] is divided by this type. + #[inline] + fn value_div(&self, second: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "/", + std::any::type_name::(), + "Value", + )) + } + + /// Called when this type divided by other [`Value`]. + #[inline] + fn value_div_other(&self, first: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "/", + "Value", + std::any::type_name::(), + )) + } + + /// Called when modulo of other [`Value`] is applied to this type. + #[inline] + fn value_rem(&self, second: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "%", + std::any::type_name::(), + "Value", + )) + } + + /// Called when modulo of this type is applied to other [`Value`]. + #[inline] + fn value_rem_other(&self, first: &Value) -> ResolveResult { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "%", + "Value", + std::any::type_name::(), + )) + } + + /// Called when this type is compared to other [`Value`]. + #[inline] + fn value_cmp(&self, second: &Value) -> Result { + Err(ExecutionError::UnsupportedBinaryOperatorExternal( + "cmp", + std::any::type_name::(), + "Value", + )) + } + + /// Called when this type is checked for equality to other [`Value`]. + #[inline] + fn value_eq(&self, second: &Value) -> bool { + false + } +} + +impl TryIntoValue for V { + type Error = Infalliable; + + fn try_into_value(self) -> Result { + unsafe { Ok(Value::External(ExternalValue::new(self))) } + } +} + +/// External value is a wrapper for external types that aren't directly +/// supported by supported CEL primitives. +#[derive(Clone)] +pub struct ExternalValue { + inner: Arc, +} + +struct Data { + r#type: TypeId, + name: &'static str, + data: *mut (), + drop_fn: DropFn, + as_value_vtable: Option<*const ()>, +} + +impl ExternalValue { + /// Constructs a new opaque `ExternalValue`. + /// + /// Opaque external values act like [_abstract types_] as documented in + /// specification. + /// + /// # Safety + /// + /// Type `T` must be kept alive for as long as returned `ExternalValue` + /// exists. + /// + /// [_abstract types_]: + /// https://github.com/google/cel-spec/blob/master/doc/langdef.md#abstract-types + pub unsafe fn new_opaque(value: T) -> Self + where + T: Sized + Send + Sync + 'static, + { + Self { + inner: Arc::new(Data { + r#type: TypeId::of::(), + name: std::any::type_name::(), + data: Box::leak(Box::new(value)) as *mut T as *mut (), + drop_fn: drop_fn::(), + as_value_vtable: None, + }), + } + } + + /// Constructs a new non-opaque `ExternalValue`. + /// + /// Non-opaque external values behave like type safe + /// [`Map`][crate::objects::Map]s that allow operator specialization and + /// function implementations to check their underlying types. + /// + /// # Safety + /// + /// Type `T` must be kept alive for as long as returned `ExternalValue` + /// exists. + pub unsafe fn new(value: T) -> Self + where + T: AsValue + Sized + Send + Sync + 'static, + { + let as_value_vtable = unsafe { + let value = &value as &dyn AsValue; + let (_, vtable): (*const (), *const ()) = std::mem::transmute(value); + Some(vtable) + }; + + Self { + inner: Arc::new(Data { + r#type: TypeId::of::(), + name: std::any::type_name::(), + data: Box::leak(Box::new(value)) as *mut T as *mut (), + drop_fn: drop_fn::(), + as_value_vtable, + }), + } + } + + /// Returns a reference to contained value if the contained type is `T`. + pub fn as_ref(&self) -> Option<&T> + where + T: 'static + Sized + Send + Sync, + { + if self.inner.r#type != TypeId::of::() { + return None; + } + let data = self.inner.data as *const T; + Some(unsafe { data.as_ref().unwrap() }) + } + + /// Returns a reference to contained value if the contained type is `T`, or + /// returns an [`UnexpectedType`][ExecutionError::UnexpectedType] error. + pub fn as_ref_or_error(&self) -> Result<&T, ExecutionError> + where + T: 'static + Sized + Send + Sync, + { + match self.as_ref() { + Some(it) => Ok(it), + None => Err(ExecutionError::UnexpectedType { + got: format!("external({})", self.inner.name), + want: format!("external({})", std::any::type_name::()), + }), + } + } + + /// Returns `true` if contained type is opaque, i.e. doesn't implement + /// [`AsValue`]. + pub fn is_opaque(&self) -> bool { + !self.inner.as_value_vtable.is_some() + } + + /// Returns [`&dyn AsValue`][AsValue] reference to contained value if it's + /// not opaque. + pub fn as_value(&self) -> Option<&dyn AsValue> { + let vtable = self.inner.as_value_vtable?; + Some(unsafe { std::mem::transmute((self.inner.data, vtable)) }) + } + + /// Returns [`&dyn AsValue`][AsValue] reference to contained value, or + /// panics if it's opaque. + pub fn as_value_expect(&self) -> &dyn AsValue { + self.as_value().expect("expected external value to be non non-opaque") + } + + /// Returns [`&dyn AsValue`][AsValue] reference to contained value, assuming + /// it's not opaque. + /// + /// # Safety + /// + /// Calling this function is unsafe if `ExternalValue` was constructed from + /// opaque type. + pub unsafe fn as_value_unchecked(&self) -> &dyn AsValue { + let vtable = unsafe { + // SAFETY: This operation is unsafe which is reflected in function + // signature. + self.inner.as_value_vtable.unwrap_unchecked() + }; + unsafe { std::mem::transmute((self.inner.data, vtable)) } + } +} + +impl Debug for ExternalValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Requires specialization#31844 to show useful information + f.write_str("ExternalValue") + } +} + +impl Drop for Data { + fn drop(&mut self) { + (self.drop_fn)(self.data); + } +} + +type DropFn = fn(*mut ()); +const fn drop_fn() -> DropFn { + if core::mem::needs_drop::() { + |ptr: *mut ()| unsafe { core::ptr::drop_in_place(ptr as *mut T) } + } else { + |_: *mut ()| {} + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Infalliable {} diff --git a/interpreter/src/functions.rs b/interpreter/src/functions.rs index 69f48d2..7fe9ec8 100644 --- a/interpreter/src/functions.rs +++ b/interpreter/src/functions.rs @@ -140,85 +140,211 @@ pub fn contains(This(this): This, arg: Value) -> Result { .into()) } -// Performs a type conversion on the target. The following conversions are currently -// supported: -// * `string` - Returns a copy of the target string. -// * `timestamp` - Returns the timestamp in RFC3339 format. -// * `duration` - Returns the duration in a string formatted like "72h3m0.5s". -// * `int` - Returns the integer value of the target. -// * `uint` - Returns the unsigned integer value of the target. -// * `float` - Returns the float value of the target. -// * `bytes` - Converts bytes to string using from_utf8_lossy. +macro_rules! return_external_conversion { + ($v: ident as $ty: ident) => {{ + let $v = unsafe { $v.as_value_unchecked() }; + if let Some(it) = $v.to_value(ValueType::$ty) { + if matches!(it, Value::$ty(_)) { + return Ok(it); + } + } + }}; +} + +/// Converts provided value into a [`string`][Value::String]. +/// +/// # Supported Conversions +/// +/// - [`string`][Value::String] - will be returned as is. +/// - [`timestamp`][Value::Timestamp] - will be formatted into RFC3339 format. +/// - [`duration`][Value::Duration] - will be formatted to string representation +/// (e.g. "72h3m0.5s"). +/// - [`int`][Value::Int] - will be formatted into a string. +/// - [`uint`][Value::UInt] - will be formatted into a string. +/// - [`float`][Value::Float] - will be formatted into a string. +/// - [`bytes`][Value::Bytes] - will be converted to string using +/// [`from_utf8_lossy`][String::from_utf8_lossy]. +/// - [`external`][Value::External] - will be converted to +/// [`string`][Value::String] if it's not opaque (i.e. implements +/// [`AsValue`][crate::external::AsValue]), and value returned by +/// [`to_value(ValueType::String)`][crate::external::AsValue::to_value] is +/// [`string`][Value::String]. [`AsValue`][crate::external::AsValue]) and the +/// returned value is a [`string`][Value::String]. pub fn string(ftx: &FunctionContext, This(this): This) -> Result { - Ok(match this { - Value::String(v) => Value::String(v.clone()), - Value::Timestamp(t) => Value::String(t.to_rfc3339().into()), - Value::Duration(v) => Value::String(format_duration(&v).into()), - Value::Int(v) => Value::String(v.to_string().into()), - Value::UInt(v) => Value::String(v.to_string().into()), - Value::Float(v) => Value::String(v.to_string().into()), - Value::Bytes(v) => Value::String(Arc::new(String::from_utf8_lossy(v.as_slice()).into())), - v => return Err(ftx.error(format!("cannot convert {:?} to string", v))), - }) + match &this { + Value::String(v) => return Ok(Value::String(v.clone())), + Value::Timestamp(t) => return Ok(Value::String(t.to_rfc3339().into())), + Value::Duration(v) => return Ok(Value::String(format_duration(&v).into())), + Value::Int(v) => return Ok(Value::String(v.to_string().into())), + Value::UInt(v) => return Ok(Value::String(v.to_string().into())), + Value::Float(v) => return Ok(Value::String(v.to_string().into())), + Value::Bytes(v) => { + return Ok(Value::String(Arc::new( + String::from_utf8_lossy(v.as_slice()).into(), + ))) + } + Value::External(v) if !v.is_opaque() => { + return_external_conversion!(v as String); + } + _ => {} + } + + Err(ftx.error(format!("cannot convert {:?} to string", this))) } -pub fn bytes(value: Arc) -> Result { - Ok(Value::Bytes(value.as_bytes().to_vec().into())) +/// Converts provided value into [`bytes`][Value::Bytes]. +/// +/// # Supported Conversions +/// +/// - [`bytes`][Value::Bytes] - will be returned as is. +/// - [`string`][Value::String] - will return UTF8 bytes. +/// - [`external`][Value::External] - will be converted to +/// [`bytes`][Value::Bytes] if it's not opaque (i.e. implements +/// [`AsValue`][crate::external::AsValue]), and value returned by +/// [`to_value(ValueType::Bytes)`][crate::external::AsValue::to_value] is +/// [`bytes`][Value::Bytes]. +pub fn bytes(Arguments(args): Arguments) -> Result { + if args.len() > 1 { + return Err(ExecutionError::InvalidArgumentCount { + expected: 1, + actual: args.len(), + }); + } + let value = match args.get(0) { + Some(it) => it, + None => return Err(ExecutionError::MissingArgumentOrTarget), + }; + match value { + Value::Bytes(v) => return Ok(Value::Bytes(v.clone())), + Value::String(v) => return Ok(Value::Bytes(v.as_bytes().to_vec().into())), + Value::External(v) if !v.is_opaque() => { + return_external_conversion!(v as Bytes); + } + _ => {} + } + + Err(ExecutionError::UnexpectedType { + got: value.type_of().to_string(), + want: "`string` or `external` convertible to `bytes`".to_string(), + }) } -// Performs a type conversion on the target. +/// Converts provided value into [`float`][Value::Float]. +/// +/// # Supported Conversions +/// +/// - [`string`][Value::String] - will be parsed as `f64`. +/// - [`float`][Value::Float] - will be returned as is. +/// - [`int`][Value::Int] - will be cast into a float. +/// - [`uint`][Value::UInt] - will be cast into a float. +/// - [`external`][Value::External] - will be converted to +/// [`float`][Value::Float] if it's not opaque (i.e. implements +/// [`AsValue`][crate::external::AsValue]), and value returned by +/// [`to_value(ValueType::Float)`][crate::external::AsValue::to_value] is +/// a [`float`][Value::Float]. pub fn double(ftx: &FunctionContext, This(this): This) -> Result { - Ok(match this { - Value::String(v) => v - .parse::() - .map(Value::Float) - .map_err(|e| ftx.error(format!("string parse error: {e}")))?, - Value::Float(v) => Value::Float(v), - Value::Int(v) => Value::Float(v as f64), - Value::UInt(v) => Value::Float(v as f64), - v => return Err(ftx.error(format!("cannot convert {:?} to double", v))), - }) + match &this { + Value::Float(v) => return Ok(Value::Float(*v)), + Value::String(v) => { + return v + .parse::() + .map(Value::Float) + .map_err(|e| ftx.error(format!("string parse error: {e}"))) + } + Value::Int(v) => return Ok(Value::Float(*v as f64)), + Value::UInt(v) => return Ok(Value::Float(*v as f64)), + Value::External(v) if !v.is_opaque() => { + return_external_conversion!(v as Float); + } + _ => {} + } + + Err(ftx.error(format!("cannot convert {:?} to float", this))) } -// Performs a type conversion on the target. +/// Converts provided value into [`uint`][Value::UInt]. +/// +/// # Supported Conversions +/// +/// - [`string`][Value::String] - will be parsed as `u64`. +/// - [`float`][Value::Float] - will be cast if there's no overflow. +/// - [`int`][Value::Int] - will be cast if there's no overflow. +/// - [`uint`][Value::UInt] - will be returned as is. +/// - [`external`][Value::External] - will be converted to [`uint`][Value::UInt] +/// if it's not opaque (i.e. implements +/// [`AsValue`][crate::external::AsValue]), and value returned by +/// [`to_value(ValueType::Float)`][crate::external::AsValue::to_value] is +/// an [`uint`][Value::UInt]. pub fn uint(ftx: &FunctionContext, This(this): This) -> Result { - Ok(match this { - Value::String(v) => v - .parse::() - .map(Value::UInt) - .map_err(|e| ftx.error(format!("string parse error: {e}")))?, + match &this { + Value::UInt(v) => return Ok(Value::UInt(*v)), + Value::String(v) => { + return v + .parse::() + .map(Value::UInt) + .map_err(|e| ftx.error(format!("string parse error: {e}"))) + } Value::Float(v) => { - if v > u64::MAX as f64 || v < u64::MIN as f64 { + if *v > u64::MAX as f64 || *v < u64::MIN as f64 { return Err(ftx.error("unsigned integer overflow")); } - Value::UInt(v as u64) + return Ok(Value::UInt(*v as u64)); } - Value::Int(v) => Value::UInt( - v.try_into() - .map_err(|_| ftx.error("unsigned integer overflow"))?, - ), - Value::UInt(v) => Value::UInt(v), - v => return Err(ftx.error(format!("cannot convert {:?} to uint", v))), - }) + Value::Int(v) => { + return (*v) + .try_into() + .map(Value::UInt) + .map_err(|_| ftx.error("unsigned integer overflow")) + } + Value::External(v) if !v.is_opaque() => { + return_external_conversion!(v as UInt); + } + _ => {} + } + + Err(ftx.error(format!("cannot convert {:?} to uint", this))) } -// Performs a type conversion on the target. +/// Converts provided value into [`int`][Value::Int]. +/// +/// # Supported Conversions +/// +/// - [`string`][Value::String] - will be parsed as `i64`. +/// - [`float`][Value::Float] - will be cast if there's no overflow. +/// - [`int`][Value::Int] - will be returned as is. +/// - [`uint`][Value::UInt] - will be cast if there's no overflow. +/// - [`external`][Value::External] - Will be converted to [`int`][Value::Int] +/// if it's not opaque (i.e. implements +/// [`AsValue`][crate::external::AsValue]), and value returned by +/// [`to_value(ValueType::Int)`][crate::external::AsValue::to_value] is +/// an [`int`][Value::Int]. pub fn int(ftx: &FunctionContext, This(this): This) -> Result { - Ok(match this { - Value::String(v) => v - .parse::() - .map(Value::Int) - .map_err(|e| ftx.error(format!("string parse error: {e}")))?, + match &this { + Value::Int(v) => return Ok(Value::Int(*v)), + Value::String(v) => { + return v + .parse::() + .map(Value::Int) + .map_err(|e| ftx.error(format!("string parse error: {e}"))) + } Value::Float(v) => { - if v > i64::MAX as f64 || v < i64::MIN as f64 { + if *v > i64::MAX as f64 || *v < i64::MIN as f64 { return Err(ftx.error("integer overflow")); } - Value::Int(v as i64) + return Ok(Value::Int(*v as i64)); } - Value::Int(v) => Value::Int(v), - Value::UInt(v) => Value::Int(v.try_into().map_err(|_| ftx.error("integer overflow"))?), - v => return Err(ftx.error(format!("cannot convert {:?} to int", v))), - }) + Value::UInt(v) => { + return TryInto::::try_into(*v) + .map(Value::Int) + .map_err(|_| ftx.error("integer overflow")) + } + Value::External(v) if !v.is_opaque() => { + return_external_conversion!(v as Int); + } + _ => {} + } + + Err(ftx.error(format!("cannot convert {:?} to int", this))) } /// Returns true if a string starts with another string. @@ -485,12 +611,21 @@ pub fn exists_one( } } -/// Duration parses the provided argument into a [`Value::Duration`] value. -/// The argument must be string, and must be in the format of a duration. See -/// the [`parse_duration`] documentation for more information on the supported -/// formats. +/// Duration converts the provided argument into a +/// [`duration`][Value::Duration]. +/// +/// # Supported Conversions +/// +/// - [`duration`][Value::Duration] - will be returned as is. +/// - [`string`][Value::String] - will be parsed with [`parse_duration`]. +/// - [`external`][Value::External] - will be converted to bytes if it's not +/// opaque (i.e. implements [`AsValue`][crate::external::AsValue]), and value +/// returned by +/// [`to_value(ValueType::Duration)`][crate::external::AsValue::to_value] is a +/// [`duration`][Value::Duration]. +/// +/// # Parsing Result Examples /// -/// # Examples /// - `1h` parses as 1 hour /// - `1.5h` parses as 1 hour and 30 minutes /// - `1h30m` parses as 1 hour and 30 minutes @@ -499,17 +634,74 @@ pub fn exists_one( /// - `1.5ms` parses as 1 millisecond and 500 microseconds /// - `1ns` parses as 1 nanosecond /// - `1.5ns` parses as 1 nanosecond (sub-nanosecond durations not supported) -pub fn duration(value: Arc) -> Result { - Ok(Value::Duration(_duration(value.as_str())?)) +pub fn duration(Arguments(args): Arguments) -> Result { + if args.len() > 1 { + return Err(ExecutionError::InvalidArgumentCount { + expected: 1, + actual: args.len(), + }); + } + let value = match args.get(0) { + Some(it) => it, + None => return Err(ExecutionError::MissingArgumentOrTarget), + }; + match value { + Value::Duration(value) => return Ok(Value::Duration(*value)), + Value::String(value) => return Ok(Value::Duration(_duration(value.as_str())?)), + Value::External(external) if !external.is_opaque() => { + return_external_conversion!(external as Duration); + } + _ => {} + } + + Err(ExecutionError::UnexpectedType { + got: value.type_of().to_string(), + want: "`string` or `external` convertible to `duration`".to_string(), + }) } -/// Timestamp parses the provided argument into a [`Value::Timestamp`] value. -/// The -pub fn timestamp(value: Arc) -> Result { - Ok(Value::Timestamp( - DateTime::parse_from_rfc3339(value.as_str()) - .map_err(|e| ExecutionError::function_error("timestamp", e.to_string().as_str()))?, - )) +/// Timestamp converts the provided argument into a +/// [`timestamp`][Value::Timestamp]. +/// +/// # Supported Conversions +/// +/// - [`timestamp`][Value::Timestamp] - will be returned as is. +/// - [`string`][Value::String] - will be parsed as RFC3339 formatted timestamp. +/// - [`external`][Value::External] - will be converted to +/// [`timestamp`][Value::Timestamp] if it's not opaque (i.e. implements +/// [`AsValue`][crate::external::AsValue]), and value returned by +/// [`to_value(ValueType::Timestamp)`][crate::external::AsValue::to_value] is +/// a [`timestamp`][Value::Timestamp]. +pub fn timestamp(Arguments(args): Arguments) -> Result { + if args.len() > 1 { + return Err(ExecutionError::InvalidArgumentCount { + expected: 1, + actual: args.len(), + }); + } + let value = match args.get(0) { + Some(it) => it, + None => return Err(ExecutionError::MissingArgumentOrTarget), + }; + match value { + Value::Timestamp(value) => return Ok(Value::Timestamp(*value)), + Value::String(value) => { + return Ok(Value::Timestamp( + DateTime::parse_from_rfc3339(value.as_str()).map_err(|e| { + ExecutionError::function_error("timestamp", e.to_string().as_str()) + })?, + )) + } + Value::External(external) if !external.is_opaque() => { + return_external_conversion!(external as Timestamp); + } + _ => {} + } + + Err(ExecutionError::UnexpectedType { + got: value.type_of().to_string(), + want: "`string` or `external` convertible to `timestamp`".to_string(), + }) } pub fn max(Arguments(args): Arguments) -> Result { diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index f4cc648..cb7943f 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -14,6 +14,7 @@ pub use functions::FunctionContext; pub use objects::{ResolveResult, Value}; mod duration; pub mod functions; +pub mod external; mod magic; pub mod objects; mod resolvers; @@ -73,6 +74,10 @@ pub enum ExecutionError { /// where it's unsupported, for example list + map. #[error("Unsupported binary operator '{0}': {1:?}, {2:?}")] UnsupportedBinaryOperator(&'static str, Value, Value), + /// Indicates that an unsupported binary operator was applied on an external + /// value + #[error("Unsupported binary operator: '{1}' {0} '{2}'")] + UnsupportedBinaryOperatorExternal(&'static str, &'static str, &'static str), /// Indicates that an unsupported type was used to index a map #[error("Cannot use value as map index: {0:?}")] UnsupportedMapIndex(Value), @@ -82,6 +87,9 @@ pub enum ExecutionError { /// Indicates that an unsupported type was used to index a list #[error("Cannot use value {0:?} to index {1:?}")] UnsupportedIndex(Value, Value), + /// Indicates that an unsupported type was used to index an external type + #[error("Cannot use value {0:?} to index {1}")] + UnsupportedIndexExternal(Value, &'static str), /// Indicates that a function call occurred without an [`Expression::Ident`] /// as the function identifier. #[error("Unsupported function call identifier type: {0:?}")] diff --git a/interpreter/src/objects.rs b/interpreter/src/objects.rs index 4b81a08..c783fd6 100644 --- a/interpreter/src/objects.rs +++ b/interpreter/src/objects.rs @@ -1,8 +1,7 @@ -use crate::context::Context; use crate::functions::FunctionContext; -use crate::ser::SerializationError; +use crate::ExecutionError; use crate::ExecutionError::NoSuchKey; -use crate::{to_value, ExecutionError}; +use crate::{context::Context, external::ExternalValue}; use cel_parser::{ArithmeticOp, Atom, Expression, Member, RelationOp, UnaryOp}; use chrono::{DateTime, Duration, FixedOffset}; use core::ops; @@ -146,13 +145,6 @@ pub trait TryIntoValue { fn try_into_value(self) -> Result; } -impl TryIntoValue for T { - type Error = SerializationError; - fn try_into_value(self) -> Result { - to_value(self) - } -} - impl TryIntoValue for Value { type Error = Infallible; fn try_into_value(self) -> Result { @@ -166,6 +158,7 @@ pub enum Value { Map(Map), Function(Arc, Option>), + External(ExternalValue), // Atoms Int(i64), @@ -183,6 +176,7 @@ pub enum ValueType { List, Map, Function, + External, Int, UInt, Float, @@ -200,6 +194,7 @@ impl Display for ValueType { ValueType::List => write!(f, "list"), ValueType::Map => write!(f, "map"), ValueType::Function => write!(f, "function"), + ValueType::External => write!(f, "external"), ValueType::Int => write!(f, "int"), ValueType::UInt => write!(f, "uint"), ValueType::Float => write!(f, "float"), @@ -219,6 +214,7 @@ impl Value { Value::List(_) => ValueType::List, Value::Map(_) => ValueType::Map, Value::Function(_, _) => ValueType::Function, + Value::External(_) => ValueType::External, Value::Int(_) => ValueType::Int, Value::UInt(_) => ValueType::UInt, Value::Float(_) => ValueType::Float, @@ -275,6 +271,16 @@ impl PartialEq for Value { (Value::UInt(a), Value::Float(b)) => (*a as f64) == *b, (Value::Float(a), Value::Int(b)) => *a == (*b as f64), (Value::Float(a), Value::UInt(b)) => *a == (*b as f64), + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_eq(b) + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_eq(a) + } (_, _) => false, } } @@ -312,6 +318,18 @@ impl PartialOrd for Value { (Value::UInt(a), Value::Float(b)) => (*a as f64).partial_cmp(b), (Value::Float(a), Value::Int(b)) => a.partial_cmp(&(*b as f64)), (Value::Float(a), Value::UInt(b)) => a.partial_cmp(&(*b as f64)), + + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_cmp(b).ok() + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_cmp(a).map(|ord| ord.reverse()).ok() + } + _ => None, } } @@ -606,6 +624,11 @@ impl<'a> Value { .into(), (Value::Map(_), index) => Err(ExecutionError::UnsupportedMapIndex(index)), (Value::List(_), index) => Err(ExecutionError::UnsupportedListIndex(index)), + (Value::External(external), index) if !external.is_opaque() => { + let external = external + .as_value_expect(); + external.value_index(&index) + } (value, index) => Err(ExecutionError::UnsupportedIndex(value, index)), } } @@ -617,6 +640,11 @@ impl<'a> Value { // a property on self, or a method on self. let child = match self { Value::Map(ref m) => m.map.get(&name.clone().into()).cloned(), + Value::External(ref external) if !external.is_opaque() => { + let external = external + .as_value_expect(); + return external.value_field(name.as_str()); + } _ => None, }; @@ -647,6 +675,16 @@ impl<'a> Value { Value::Duration(v) => v.num_nanoseconds().map(|n| n != 0).unwrap_or(false), Value::Timestamp(v) => v.timestamp_nanos_opt().unwrap_or_default() > 0, Value::Function(_, _) => false, + Value::External(external) if !external.is_opaque() => { + let external = external + .as_value_expect(); + match external.to_value(ValueType::Bool) { + Some(Value::Bool(result)) => result, + Some(other) => other.to_bool(), + None => false, + } + } + Value::External(_) => false, } } } @@ -705,6 +743,18 @@ impl ops::Add for Value { (Value::Duration(l), Value::Duration(r)) => Value::Duration(l + r).into(), (Value::Timestamp(l), Value::Duration(r)) => Value::Timestamp(l + r).into(), (Value::Duration(l), Value::Timestamp(r)) => Value::Timestamp(r + l).into(), + + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_add(&b) + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_add_other(&a) + } + (left, right) => Err(ExecutionError::UnsupportedBinaryOperator( "add", left, right, )), @@ -731,6 +781,18 @@ impl ops::Sub for Value { (Value::Duration(l), Value::Duration(r)) => Value::Duration(l - r).into(), (Value::Timestamp(l), Value::Duration(r)) => Value::Timestamp(l - r).into(), (Value::Timestamp(l), Value::Timestamp(r)) => Value::Duration(l - r).into(), + + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_sub(&b) + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_sub_other(&a) + } + (left, right) => Err(ExecutionError::UnsupportedBinaryOperator( "sub", left, right, )), @@ -754,6 +816,17 @@ impl ops::Div for Value { (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 / r).into(), (Value::Float(l), Value::UInt(r)) => Value::Float(l / r as f64).into(), + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_div(&b) + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_div_other(&a) + } + (left, right) => Err(ExecutionError::UnsupportedBinaryOperator( "div", left, right, )), @@ -777,6 +850,17 @@ impl ops::Mul for Value { (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 * r).into(), (Value::Float(l), Value::UInt(r)) => Value::Float(l * r as f64).into(), + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_mul(&b) + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_mul_other(&a) + } + (left, right) => Err(ExecutionError::UnsupportedBinaryOperator( "mul", left, right, )), @@ -800,6 +884,17 @@ impl ops::Rem for Value { (Value::UInt(l), Value::Float(r)) => Value::Float(l as f64 % r).into(), (Value::Float(l), Value::UInt(r)) => Value::Float(l % r as f64).into(), + (Value::External(a), b) if !a.is_opaque() => { + let a = a + .as_value_expect(); + a.value_rem(&b) + } + (a, Value::External(b)) if !b.is_opaque() => { + let b = b + .as_value_expect(); + b.value_rem_other(&a) + } + (left, right) => Err(ExecutionError::UnsupportedBinaryOperator( "rem", left, right, )),