From e2d58932058172e227cfeb0974ae286b0422277c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Sun, 7 Jun 2026 11:07:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20Math=20=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=8F=8A=E6=A0=B8=E5=BF=83=E6=88=90=E5=91=98=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/builtin.rs | 4 + crates/oxide_vm/src/builtins/math.rs | 167 ++++++++++++ crates/oxide_vm/src/builtins/mod.rs | 1 + crates/oxide_vm/src/vm.rs | 270 +++++++++++++++++++ crates/oxide_vm/tests/builtins_math_tests.rs | 117 ++++++++ 5 files changed, 559 insertions(+) create mode 100644 crates/oxide_vm/src/builtins/math.rs create mode 100644 crates/oxide_vm/tests/builtins_math_tests.rs diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 52cd1e4..d6bb5ae 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -98,6 +98,7 @@ pub struct BuiltinWorld { pub syntax_error_proto: P, pub uri_error_proto: P, pub eval_error_proto: P, + pub math_object: P, } fn intern_label(string_forge: &StringForge, label: &str) -> u32 { @@ -216,6 +217,8 @@ impl BuiltinWorld { let uri_error_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, error_proto_val)); let eval_error_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, error_proto_val)); + let math_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + Self { object_proto, array_proto, @@ -239,6 +242,7 @@ impl BuiltinWorld { syntax_error_proto, uri_error_proto, eval_error_proto, + math_object, } } diff --git a/crates/oxide_vm/src/builtins/math.rs b/crates/oxide_vm/src/builtins/math.rs new file mode 100644 index 0000000..86fa8d3 --- /dev/null +++ b/crates/oxide_vm/src/builtins/math.rs @@ -0,0 +1,167 @@ +use oxide_types::value::JsValue; + +use crate::coercion; +use crate::native::NativeResult; +use crate::vm::Vm; + +fn num(vm: &Vm, reg: u8) -> f64 { + coercion::to_number(vm.reg(reg), vm.kernel().string_forge().as_ref()) +} + +fn arg1(vm: &Vm, args: &[u8]) -> f64 { + if args.len() < 2 { + f64::NAN + } else { + num(vm, args[1]) + } +} + +fn arg2(vm: &Vm, args: &[u8]) -> (f64, f64) { + ( + if args.len() > 1 { + num(vm, args[1]) + } else { + f64::NAN + }, + if args.len() > 2 { + num(vm, args[2]) + } else { + f64::NAN + }, + ) +} + +pub fn math_abs(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { + return Ok(JsValue::float(f64::NAN)); + } + let x = vm.reg(args[1]); + if x.is_int() { + Ok(JsValue::int(x.as_int().abs())) + } else { + Ok(JsValue::float(num(vm, args[1]).abs())) + } +} + +macro_rules! math_unary { + ($name:ident, $op:ident) => { + pub fn $name(vm: &mut Vm, args: &[u8]) -> NativeResult { + Ok(JsValue::float(arg1(vm, args).$op())) + } + }; +} + +math_unary!(math_acos, acos); +math_unary!(math_acosh, acosh); +math_unary!(math_asin, asin); +math_unary!(math_asinh, asinh); +math_unary!(math_atan, atan); +math_unary!(math_atanh, atanh); +math_unary!(math_cbrt, cbrt); +math_unary!(math_ceil, ceil); +math_unary!(math_cos, cos); +math_unary!(math_cosh, cosh); +math_unary!(math_exp, exp); +math_unary!(math_expm1, exp_m1); +math_unary!(math_floor, floor); +math_unary!(math_log, ln); +math_unary!(math_log10, log10); +math_unary!(math_log1p, ln_1p); +math_unary!(math_log2, log2); +math_unary!(math_sin, sin); +math_unary!(math_sinh, sinh); +math_unary!(math_sqrt, sqrt); +math_unary!(math_tan, tan); +math_unary!(math_tanh, tanh); +math_unary!(math_trunc, trunc); + +pub fn math_atan2(vm: &mut Vm, args: &[u8]) -> NativeResult { + let (a, b) = arg2(vm, args); + Ok(JsValue::float(a.atan2(b))) +} + +pub fn math_round(vm: &mut Vm, args: &[u8]) -> NativeResult { + let x = arg1(vm, args); + let r = if x < 0.0 { + (x - 0.5).ceil() + } else { + (x + 0.5).floor() + }; + Ok(JsValue::float(r)) +} + +pub fn math_sign(vm: &mut Vm, args: &[u8]) -> NativeResult { + let x = arg1(vm, args); + if x.is_nan() { + Ok(JsValue::float(f64::NAN)) + } else if x > 0.0 { + Ok(JsValue::int(1)) + } else if x < 0.0 { + Ok(JsValue::int(-1)) + } else { + Ok(JsValue::float(x)) + } +} + +pub fn math_clz32(vm: &mut Vm, args: &[u8]) -> NativeResult { + let n = arg1(vm, args) as u32; + Ok(JsValue::int(n.leading_zeros() as i32)) +} + +pub fn math_fround(vm: &mut Vm, args: &[u8]) -> NativeResult { + Ok(JsValue::float(arg1(vm, args) as f32 as f64)) +} + +pub fn math_hypot(vm: &mut Vm, args: &[u8]) -> NativeResult { + let (a, b) = arg2(vm, args); + Ok(JsValue::float(a.hypot(b))) +} + +pub fn math_imul(vm: &mut Vm, args: &[u8]) -> NativeResult { + let (a, b) = arg2(vm, args); + Ok(JsValue::int((a as i32).wrapping_mul(b as i32))) +} + +pub fn math_pow(vm: &mut Vm, args: &[u8]) -> NativeResult { + let (a, b) = arg2(vm, args); + Ok(JsValue::float(a.powf(b))) +} + +pub fn math_max(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { + return Ok(JsValue::float(f64::NEG_INFINITY)); + } + let mut m = f64::NEG_INFINITY; + for &r in args.iter().skip(1) { + let x = num(vm, r); + if x.is_nan() { + return Ok(JsValue::float(f64::NAN)); + } + if x > m { + m = x; + } + } + Ok(JsValue::float(m)) +} + +pub fn math_min(vm: &mut Vm, args: &[u8]) -> NativeResult { + if args.len() < 2 { + return Ok(JsValue::float(f64::INFINITY)); + } + let mut m = f64::INFINITY; + for &r in args.iter().skip(1) { + let x = num(vm, r); + if x.is_nan() { + return Ok(JsValue::float(f64::NAN)); + } + if x < m { + m = x; + } + } + Ok(JsValue::float(m)) +} + +pub fn math_random(vm: &mut Vm, _args: &[u8]) -> NativeResult { + vm.step_rng(); + Ok(JsValue::float(vm.math_rng_value())) +} diff --git a/crates/oxide_vm/src/builtins/mod.rs b/crates/oxide_vm/src/builtins/mod.rs index 9abc4fd..fea4dc4 100644 --- a/crates/oxide_vm/src/builtins/mod.rs +++ b/crates/oxide_vm/src/builtins/mod.rs @@ -1,6 +1,7 @@ pub mod array; pub mod error; pub mod global; +pub mod math; pub mod number; pub mod object; pub mod string; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 1cd36f6..4af509c 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -40,6 +40,7 @@ pub struct Vm { interned_strings: Vec, pub epoch: Epoch, pub object_prototype: P, + pub math_rng_state: u64, } pub fn init_kernel_builtins(kernel: &Arc) { @@ -221,6 +222,256 @@ pub fn init_kernel_builtins(kernel: &Arc) { pf_fn, 1, ); + + let math_ptr = kernel.builtin_world().math_object.as_ptr() as *mut JsObject; + let math = unsafe { &mut *math_ptr }; + let sf = kernel.string_forge().as_ref(); + let sh = kernel.shape_forge().as_ref(); + + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "abs", + crate::builtins::math::math_abs as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "acos", + crate::builtins::math::math_acos as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "asin", + crate::builtins::math::math_asin as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "atan", + crate::builtins::math::math_atan as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "atan2", + crate::builtins::math::math_atan2 as *const (), + 2, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "cbrt", + crate::builtins::math::math_cbrt as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "ceil", + crate::builtins::math::math_ceil as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "cos", + crate::builtins::math::math_cos as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "cosh", + crate::builtins::math::math_cosh as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "exp", + crate::builtins::math::math_exp as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "floor", + crate::builtins::math::math_floor as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "hypot", + crate::builtins::math::math_hypot as *const (), + 2, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "imul", + crate::builtins::math::math_imul as *const (), + 2, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "log", + crate::builtins::math::math_log as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "log10", + crate::builtins::math::math_log10 as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "log2", + crate::builtins::math::math_log2 as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "max", + crate::builtins::math::math_max as *const (), + 2, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "min", + crate::builtins::math::math_min as *const (), + 2, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "pow", + crate::builtins::math::math_pow as *const (), + 2, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "random", + crate::builtins::math::math_random as *const (), + 0, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "round", + crate::builtins::math::math_round as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "sign", + crate::builtins::math::math_sign as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "sin", + crate::builtins::math::math_sin as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "sinh", + crate::builtins::math::math_sinh as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "sqrt", + crate::builtins::math::math_sqrt as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "tan", + crate::builtins::math::math_tan as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "tanh", + crate::builtins::math::math_tanh as *const (), + 1, + ); + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method( + math, + sh, + sf, + "trunc", + crate::builtins::math::math_trunc as *const (), + 1, + ); + + let pi_val = JsValue::float(std::f64::consts::PI); + { + let si_pi = sf.intern("PI").0; + let sh_pi = sh.make_shape(math.shape_id(), si_pi); + let cur = math.prop_count(); + math.set_shape_id(sh_pi); + math.set_prop_count(cur + 1); + math.set_prop_expand_heap(cur, pi_val); + } + + let si_m = kernel.string_forge().intern("Math").0; + let m_shape = kernel.shape_forge().make_shape(global.shape_id(), si_m); + let m_val = + JsValue::from_js_object(kernel.builtin_world().math_object.as_ptr() as *mut JsObject); + let cur_count = global.prop_count(); + global.set_shape_id(m_shape); + global.set_prop_count(cur_count + 1); + global.set_prop_expand_heap(cur_count, m_val); + global.bump_generation(); } impl Vm { @@ -238,6 +489,7 @@ impl Vm { interned_strings: Vec::new(), epoch: Epoch::new(), object_prototype: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + math_rng_state: 0, } } @@ -253,9 +505,27 @@ impl Vm { interned_strings: Vec::new(), epoch: Epoch::new(), object_prototype: P::clone(&kernel.builtin_world().object_proto), + math_rng_state: 0, } } + pub fn step_rng(&mut self) { + if self.math_rng_state == 0 { + self.math_rng_state = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_nanos() as u64; + } + self.math_rng_state = self + .math_rng_state + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + } + + pub fn math_rng_value(&self) -> f64 { + (self.math_rng_state >> 33) as f64 / (1u64 << 31) as f64 + } + pub fn kernel(&self) -> &Arc { &self.kernel } diff --git a/crates/oxide_vm/tests/builtins_math_tests.rs b/crates/oxide_vm/tests/builtins_math_tests.rs new file mode 100644 index 0000000..a09ab9e --- /dev/null +++ b/crates/oxide_vm/tests/builtins_math_tests.rs @@ -0,0 +1,117 @@ +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = + oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new() + .compile(&program) + .map_err(|e| format!("Compile error: {}", e))?; + vm.run(&module) +} + +#[test] +fn math_abs_negative() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.abs(-42)").unwrap(); + assert!((result.as_double() - 42.0).abs() < 0.0001); +} + +#[test] +fn math_sqrt() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.sqrt(16)").unwrap(); + assert!((result.as_double() - 4.0).abs() < 0.0001); +} + +#[test] +fn math_pow() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.pow(2, 10)").unwrap(); + assert!((result.as_double() - 1024.0).abs() < 0.0001); +} + +#[test] +fn math_ceil() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.ceil(3.14)").unwrap(); + assert!((result.as_double() - 4.0).abs() < 0.0001); +} + +#[test] +fn math_floor() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.floor(3.14)").unwrap(); + assert!((result.as_double() - 3.0).abs() < 0.0001); +} + +#[test] +fn math_round() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.round(3.6)").unwrap(); + assert!((result.as_double() - 4.0).abs() < 0.0001); +} + +#[test] +fn math_max() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.max(1, 5)").unwrap(); + assert!((result.as_double() - 5.0).abs() < 0.0001); +} + +#[test] +fn math_min() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.min(10, 5)").unwrap(); + assert!((result.as_double() - 5.0).abs() < 0.0001); +} + +#[test] +fn math_sin() { + let mut vm = Vm::new(); + eval(&mut vm, "Math.sin(0)").unwrap(); +} + +#[test] +fn math_cos() { + let mut vm = Vm::new(); + eval(&mut vm, "Math.cos(0)").unwrap(); +} + +#[test] +fn math_random_in_range() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.random()").unwrap(); + assert!(result.as_double() >= 0.0); + assert!(result.as_double() < 1.0); +} + +#[test] +fn math_pi() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.PI").unwrap(); + assert!((result.as_double() - std::f64::consts::PI).abs() < 0.0001); +} + +#[test] +fn math_sign_positive() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.sign(42)").unwrap(); + assert_eq!(result.as_int(), 1); +} + +#[test] +fn math_sign_negative() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.sign(-42)").unwrap(); + assert_eq!(result.as_int(), -1); +} + +#[test] +fn math_trunc() { + let mut vm = Vm::new(); + let result = eval(&mut vm, "Math.trunc(3.9)").unwrap(); + assert!((result.as_double() - 3.0).abs() < 0.0001); +}