diff --git a/Cargo.lock b/Cargo.lock index fe86550..d75fb51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -590,6 +590,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" name = "haku" version = "0.1.0" dependencies = [ + "libm", "log", "tiny-skia", ] diff --git a/crates/haku/Cargo.toml b/crates/haku/Cargo.toml index 6c71582..a0dafab 100644 --- a/crates/haku/Cargo.toml +++ b/crates/haku/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] log.workspace = true tiny-skia = { version = "0.11.4", default-features = false, features = ["no-std-float"] } +libm = "0.2.8" [features] default = [] diff --git a/crates/haku/src/system.rs b/crates/haku/src/system.rs index 1beacce..fe79735 100644 --- a/crates/haku/src/system.rs +++ b/crates/haku/src/system.rs @@ -106,7 +106,7 @@ impl Display for ChunkError { impl Error for ChunkError {} pub mod fns { - use alloc::vec::Vec; + use alloc::{format, vec::Vec}; use crate::{ value::{Fill, List, Ref, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4}, @@ -123,6 +123,36 @@ pub mod fns { 0x03 Binary "/" => div, 0x04 Unary "-" => neg, + 0x10 Nary "floor" => floorf, + 0x11 Nary "ceil" => ceilf, + 0x12 Nary "round" => roundf, + 0x13 Nary "abs" => fabsf, + 0x14 Nary "mod" => fmodf, + 0x15 Nary "pow" => powf, + 0x16 Nary "sqrt" => sqrtf, + 0x17 Nary "cbrt" => cbrtf, + 0x18 Nary "exp" => expf, + 0x19 Nary "exp2" => exp2f, + 0x1A Nary "ln" => logf, + 0x1B Nary "log2" => log2f, + 0x1C Nary "log10" => log10f, + 0x1D Nary "hypot" => hypotf, + 0x1E Nary "sin" => sinf, + 0x1F Nary "cos" => cosf, + 0x20 Nary "tan" => tanf, + 0x21 Nary "asin" => asinf, + 0x22 Nary "acos" => acosf, + 0x23 Nary "atan" => atanf, + 0x24 Nary "atan2" => atan2f, + 0x25 Nary "expMinus1" => expm1f, + 0x26 Nary "ln1Plus" => log1pf, + 0x27 Nary "sinh" => sinhf, + 0x28 Nary "cosh" => coshf, + 0x29 Nary "tanh" => tanhf, + 0x2A Nary "asinh" => asinhf, + 0x2B Nary "acosh" => acoshf, + 0x2C Nary "atanh" => atanhf, + 0x40 Unary "!" => not, 0x41 Binary "==" => eq, 0x42 Binary "!=" => neq, @@ -185,6 +215,85 @@ pub mod fns { Ok(Value::Number(-x)) } + #[inline(never)] + fn math1(vm: &mut Vm, args: FnArgs, name: &str, f: fn(f32) -> f32) -> Result { + if args.num() != 1 { + return Err( + vm.create_exception(format!("`{name}` expects a single argument ({name} x)")) + ); + } + + let x = args + .get(vm, 0) + .to_number() + .ok_or_else(|| vm.create_exception(format!("`{name}` argument must be a number")))?; + Ok(Value::Number(f(x))) + } + + #[inline(never)] + fn math2( + vm: &mut Vm, + args: FnArgs, + name: &str, + f: fn(f32, f32) -> f32, + ) -> Result { + if args.num() != 2 { + return Err(vm.create_exception(format!("`{name}` expects two arguments ({name} x y)"))); + } + + let x = args + .get(vm, 0) + .to_number() + .ok_or_else(|| vm.create_exception(format!("`{name}` arguments must be numbers")))?; + let y = args + .get(vm, 1) + .to_number() + .ok_or_else(|| vm.create_exception(format!("`{name}` arguments must be numbers")))?; + Ok(Value::Number(f(x, y))) + } + + macro_rules! math_fns { + ($($arity:tt $sysname:tt $name:tt),* $(,)?) => { + $( + pub fn $name(vm: &mut Vm, args: FnArgs) -> Result { + $arity(vm, args, $sysname, libm::$name) + } + )* + } + } + + math_fns! { + math1 "floor" floorf, + math1 "ceil" ceilf, + math1 "round" roundf, + math1 "abs" fabsf, + math2 "mod" fmodf, + math2 "pow" powf, + math1 "sqrt" sqrtf, + math1 "cbrt" cbrtf, + math1 "exp" expf, + math1 "exp2" exp2f, + math1 "ln" logf, + math1 "log2" log2f, + math1 "log10" log10f, + math2 "hypot" hypotf, + math1 "sin" sinf, + math1 "cos" cosf, + math1 "tan" tanf, + math1 "asin" asinf, + math1 "acos" acosf, + math1 "atan" atanf, + math2 "atan2" atan2f, + math1 "expMinus1" expm1f, + math1 "ln1Plus" log1pf, + math1 "sinh" sinhf, + math1 "cosh" coshf, + math1 "tanh" tanhf, + math1 "asinh" asinhf, + math1 "acosh" acoshf, + math1 "atanh" atanhf, + } + pub fn not(vm: &mut Vm, args: FnArgs) -> Result { let value = args.get(vm, 0); Ok(Value::from(value.is_falsy()))