feat: 支持 Math 对象及核心成员方法
This commit is contained in:
@@ -98,6 +98,7 @@ pub struct BuiltinWorld {
|
||||
pub syntax_error_proto: P<JsObject>,
|
||||
pub uri_error_proto: P<JsObject>,
|
||||
pub eval_error_proto: P<JsObject>,
|
||||
pub math_object: P<JsObject>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -40,6 +40,7 @@ pub struct Vm {
|
||||
interned_strings: Vec<u32>,
|
||||
pub epoch: Epoch,
|
||||
pub object_prototype: P<JsObject>,
|
||||
pub math_rng_state: u64,
|
||||
}
|
||||
|
||||
pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
||||
@@ -221,6 +222,256 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
||||
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<OxideKernel> {
|
||||
&self.kernel
|
||||
}
|
||||
|
||||
@@ -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<JsValue, String> {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user