diff --git a/src/intrinsic/math.rs b/src/intrinsic/math.rs index df4fb79..8d34b90 100644 --- a/src/intrinsic/math.rs +++ b/src/intrinsic/math.rs @@ -17,6 +17,48 @@ macro_rules! as_math_fn { }; } +macro_rules! as_math_fn2 { + ($name: ident) => { + crate::vm::bytecode::NamedFn2 { + name: stringify!($name), + func: |_: Value, v: Value, w: Value| match (v, w) { + (crate::Value::Number(v), crate::Value::Number(w)) => { + crate::intrinsic::math::$name(v, w).map(Into::into) + } + (v, crate::Value::Number(_)) => crate::vm::Result::Err( + crate::vm::QueryExecutionError::InvalidArgType(stringify!($name), v), + ), + (_, w) => crate::vm::Result::Err(crate::vm::QueryExecutionError::InvalidArgType( + stringify!($name), + w, + )), + }, + } + }; +} + +macro_rules! as_math_fn3 { + ($name: ident) => { + crate::vm::bytecode::NamedFn3 { + name: stringify!($name), + func: |_: Value, v: Value, w: Value, x: Value| match (v, w, x) { + (crate::Value::Number(v), crate::Value::Number(w), crate::Value::Number(x)) => { + crate::intrinsic::math::$name(v, w, x).map(Into::into) + } + (v, crate::Value::Number(_), crate::Value::Number(_)) => crate::vm::Result::Err( + crate::vm::QueryExecutionError::InvalidArgType(stringify!($name), v), + ), + (_, w, crate::Value::Number(_)) => crate::vm::Result::Err( + crate::vm::QueryExecutionError::InvalidArgType(stringify!($name), w), + ), + (_, _, x) => crate::vm::Result::Err( + crate::vm::QueryExecutionError::InvalidArgType(stringify!($name), x), + ), + }, + } + }; +} + pub(crate) fn nan(_: Value) -> Result { Ok(Number::nan().into()) } @@ -37,6 +79,10 @@ pub(crate) fn is_infinite(v: Number) -> Result { Ok(v.is_infinite()) } +pub(crate) fn exp10(v: Number) -> Result { + Ok(Number::from(10).powf(v)) +} + macro_rules! pub_math_fn { ($($name: ident),*) => { $( @@ -47,4 +93,31 @@ macro_rules! pub_math_fn { }; } -pub_math_fn!(floor, sqrt, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh); +pub_math_fn!( + floor, round, ceil, trunc, abs, sqrt, cbrt, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, + asinh, acosh, atanh, exp, exp2, exp_m1, ln, log2, log10 +); + +pub(crate) fn fmax(v: Number, w: Number) -> Result { + Ok(Float::max(v, w)) +} + +pub(crate) fn fmin(v: Number, w: Number) -> Result { + Ok(Float::min(v, w)) +} + +macro_rules! pub_math_fn2 { + ($($name: ident),*) => { + $( + pub(crate) fn $name(v: Number, w: Number) -> Result { + Ok(v.$name(w)) + } + )* + }; +} + +pub_math_fn2!(copysign, atan2, hypot, powf); + +pub(crate) fn fma(v: Number, w: Number, x: Number) -> Result { + Ok(v.mul_add(w, x)) +} diff --git a/src/intrinsic/mod.rs b/src/intrinsic/mod.rs index 0ab0dea..8665768 100644 --- a/src/intrinsic/mod.rs +++ b/src/intrinsic/mod.rs @@ -16,7 +16,7 @@ use crate::{ compile::compiler::{ArgType, FunctionIdentifier}, util::make_owned, vm::{ - bytecode::{NamedFn0, NamedFn1, NamedFn2}, + bytecode::{NamedFn0, NamedFn1, NamedFn2, NamedFn3}, error::Result, ByteCode, QueryExecutionError, }, @@ -62,7 +62,12 @@ static INTRINSICS0: phf::Map<&'static str, NamedFn0> = phf_map! { "isnormal" => as_math_fn!(is_normal), "isinfinite" => as_math_fn!(is_infinite), "floor" => as_math_fn!(floor), + "round" => as_math_fn!(round), + "ceil" => as_math_fn!(ceil), + "trunc" => as_math_fn!(trunc), + "fabs" => as_math_fn!(abs), "sqrt" => as_math_fn!(sqrt), + "cbrt" => as_math_fn!(cbrt), "sin" => as_math_fn!(sin), "cos" => as_math_fn!(cos), "tan" => as_math_fn!(tan), @@ -75,6 +80,13 @@ static INTRINSICS0: phf::Map<&'static str, NamedFn0> = phf_map! { "asinh" => as_math_fn!(asinh), "acosh" => as_math_fn!(acosh), "atanh" => as_math_fn!(atanh), + "exp" => as_math_fn!(exp), + "exp2" => as_math_fn!(exp2), + "exp10" => as_math_fn!(exp10), + "expm1" => as_math_fn!(exp_m1), + "log" => as_math_fn!(ln), + "log2" => as_math_fn!(log2), + "log10" => as_math_fn!(log10), }; static INTRINSICS1: phf::Map<&'static str, NamedFn1> = phf_map! { "error" => NamedFn1 { name: "error", func: error1 }, @@ -101,6 +113,16 @@ static INTRINSICS1: phf::Map<&'static str, NamedFn1> = phf_map! { static INTRINSICS2: phf::Map<&'static str, NamedFn2> = phf_map! { "setpath" => NamedFn2 { name: "setpath", func: path::set_path }, "__split_match_impl" => NamedFn2 { name: "__split_match_impl", func: regex::split_match_impl }, + + "fmax" => as_math_fn2!(fmax), + "fmin" => as_math_fn2!(fmin), + "copysign" => as_math_fn2!(copysign), + "atan2" => as_math_fn2!(atan2), + "hypot" => as_math_fn2!(hypot), + "pow" => as_math_fn2!(powf), +}; +static INTRINSICS3: phf::Map<&'static str, NamedFn3> = phf_map! { + "fma" => as_math_fn3!(fma), }; pub(crate) fn lookup_intrinsic_fn( @@ -123,6 +145,13 @@ pub(crate) fn lookup_intrinsic_fn( vec![ArgType::Value, ArgType::Value], ) }) + } else if *n_args == 3 { + INTRINSICS3.get(&ident.0).cloned().map(|f| { + ( + ByteCode::Intrinsic3(f), + vec![ArgType::Value, ArgType::Value, ArgType::Value], + ) + }) } else { None } diff --git a/src/vm/bytecode.rs b/src/vm/bytecode.rs index 2d1a39b..7f77639 100644 --- a/src/vm/bytecode.rs +++ b/src/vm/bytecode.rs @@ -17,6 +17,7 @@ pub(crate) struct ClosureAddress(pub(crate) Address); pub type NamedFn0 = NamedFunction Result>; pub type NamedFn1 = NamedFunction Result>; pub type NamedFn2 = NamedFunction Result>; +pub type NamedFn3 = NamedFunction Result>; impl Debug for NamedFunction { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -189,6 +190,12 @@ pub(crate) enum ByteCode { /// # Panics /// Panics if the stack had less than 3 elements, or the invoked function panicked. Intrinsic2(NamedFn2), + /// Pops a value `arg3` from the stack, pops another value `arg2` from the stack, pops another value `arg1` from the stack, + /// and another value `context` from the stack, and invokes the function with the arg `context, arg1, arg2, arg3`, + /// and pushes the resulting value to the stack. + /// # Panics + /// Panics if the stack had less than 4 elements, or the invoked function panicked. + Intrinsic3(NamedFn3), } #[derive(Debug, Clone)] diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 605ef96..1713b96 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -971,6 +971,24 @@ fn run_code( Err(e) => err = Some(e), } } + Intrinsic3(NamedFunction { name, func }) => { + let arg3 = state.pop(); + let arg2 = state.pop(); + let arg1 = state.pop(); + let context = state.pop(); + log::trace!( + "Calling function {} with context {:?} and arg1 {:?} and arg2 {:?} and arg3 {:?}", + name, + context, + arg1, + arg2, + arg3 + ); + match func(context, arg1, arg2, arg3) { + Ok(value) => state.push(value), + Err(e) => err = Some(e), + } + } } state.pc.next(); } diff --git a/tests/hand_written/math.rs b/tests/hand_written/math.rs index d711114..f8a0e92 100644 --- a/tests/hand_written/math.rs +++ b/tests/hand_written/math.rs @@ -16,6 +16,37 @@ test!( "# ); +test!( + floor_round_ceil_trunc_fabs_functions, + r#" + map(floor), map(round), map(ceil), map(trunc), map(fabs) + "#, + r#" + [-3.5,-2.1,-1,-0.3,0,0.3,0.7,1,1.2,2.8,3.5] + "#, + r#" + [-4,-3,-1,-1,0,0,0,1,1,2,3] + [-4,-2,-1,-0,0,0,1,1,1,3,4] + [-3,-2,-1,-0,0,1,1,1,2,3,4] + [-3,-2,-1,-0,0,0,0,1,1,2,3] + [3.5,2.1,1,0.3,0,0.3,0.7,1,1.2,2.8,3.5] + "# +); + +test!( + sqrt_cbrt_functions, + r#" + map(sqrt | select(isnan | not)), map(cbrt) | map(. * 1000000000 | floor / 1000000000) + "#, + r#" + [-8,-3.375,0,0.125,1,4,8,40.96,64] + "#, + r#" + [0,0.35355339,1,2,2.828427124,6.4,8] + [-2,-1.5,0,0.5,1,1.587401051,2,3.447095504,4] + "# +); + test!( trigonometric_functions, r#" @@ -75,3 +106,82 @@ test!( [0,0.346573590,0.804718956] "# ); + +test!( + exponential_functions, + r#" + map(exp), map(exp2), map(exp10), map(expm1) | map(. * 1000000000 | floor / 1000000000) + "#, + r#" + [-1,-0.5,0,0.5,1,1.5,2] + "#, + r#" + [0.367879441,0.606530659,1,1.64872127,2.718281828,4.48168907,7.389056098] + [0.5,0.707106781,1,1.414213562,2,2.828427124,4] + [0.1,0.316227766,1,3.16227766,10,31.622776601,100] + [-0.632120559,-0.393469341,0,0.64872127,1.718281828,3.48168907,6.389056098] + "# +); + +test!( + logarithmic_functions, + r#" + map(log), map(log2), map(log10) | map(. * 1000000000 | floor / 1000000000) + "#, + r#" + [0.1,0.5,1,2,3,10] + "#, + r#" + [-2.302585093,-0.693147181,0,0.69314718,1.098612288,2.302585092] + [-3.321928095,-1,0,1,1.5849625,3.321928094] + [-1,-0.301029996,0,0.301029995,0.477121254,1] + "# +); + +test!( + fmax_fmin_copysign_functions, + r#" + [fmax(0.1,0.3; 0.2,0.4)], + [fmin(0.1,0.3; 0.2,0.4)], + [copysign(0.1,-0.3; 0.2,-0.4)] + "#, + r#" + null + "#, + r#" + [0.2,0.4,0.3,0.4] + [0.1,0.1,0.2,0.3] + [0.1,-0.1,0.3,-0.3] + "# +); + +test!( + atan2_hypot_powf_functions, + r#" + [atan2(0.1,0.3; 0.2,0.4)], + [hypot(0.1,0.3; 0.2,0.4)], + [pow(0.1,0.3; 0.2,0.4)] | + map(. * 1000000000 | floor / 1000000000) + "#, + r#" + null + "#, + r#" + [0.463647609,0.244978663,0.982793723,0.643501108] + [0.223606797,0.412310562,0.360555127,0.5] + [0.630957344,0.39810717,0.786003085,0.61780085] + "# +); + +test!( + fma_function, + r#" + [fma(1.5,2.5; 3.5,4.5; 5.25,6.75)] + "#, + r#" + null + "#, + r#" + [10.5,12,12,13.5,14,15.5,16.5,18] + "# +);