feat: 支持 Number 对象及成员方法并修复属性溢出崩溃问题
This commit is contained in:
@@ -66,6 +66,15 @@ pub struct StringMethods {
|
|||||||
pub search: *const (),
|
pub search: *const (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct NumberMethods {
|
||||||
|
pub is_nan: *const (),
|
||||||
|
pub is_finite: *const (),
|
||||||
|
pub parse_int: *const (),
|
||||||
|
pub parse_float: *const (),
|
||||||
|
pub to_string: *const (),
|
||||||
|
pub to_fixed: *const (),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BuiltinWorld {
|
pub struct BuiltinWorld {
|
||||||
pub object_proto: P<JsObject>,
|
pub object_proto: P<JsObject>,
|
||||||
pub array_proto: P<JsObject>,
|
pub array_proto: P<JsObject>,
|
||||||
@@ -557,6 +566,61 @@ impl BuiltinWorld {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_number_methods(
|
||||||
|
&self,
|
||||||
|
methods: &NumberMethods,
|
||||||
|
string_forge: &StringForge,
|
||||||
|
shape_forge: &ShapeForge,
|
||||||
|
) {
|
||||||
|
let ctor_ptr = P::as_ptr(&self.number_constructor) as *mut JsObject;
|
||||||
|
let ctor = unsafe { &mut *ctor_ptr };
|
||||||
|
let proto_ptr = P::as_ptr(&self.number_proto) as *mut JsObject;
|
||||||
|
let proto = unsafe { &mut *proto_ptr };
|
||||||
|
|
||||||
|
let _ = Self::bind_method(ctor, shape_forge, string_forge, "isNaN", methods.is_nan, 1);
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
ctor,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"isFinite",
|
||||||
|
methods.is_finite,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
ctor,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"parseInt",
|
||||||
|
methods.parse_int,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
ctor,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"parseFloat",
|
||||||
|
methods.parse_float,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
proto,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"toString",
|
||||||
|
methods.to_string,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
let _ = Self::bind_method(
|
||||||
|
proto,
|
||||||
|
shape_forge,
|
||||||
|
string_forge,
|
||||||
|
"toFixed",
|
||||||
|
methods.to_fixed,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bind_method(
|
pub fn bind_method(
|
||||||
proto: &mut JsObject,
|
proto: &mut JsObject,
|
||||||
shape_forge: &ShapeForge,
|
shape_forge: &ShapeForge,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod array;
|
pub mod array;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod global;
|
pub mod global;
|
||||||
|
pub mod number;
|
||||||
pub mod object;
|
pub mod object;
|
||||||
pub mod string;
|
pub mod string;
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
|
use crate::coercion;
|
||||||
|
use crate::native::NativeResult;
|
||||||
|
use crate::vm::Vm;
|
||||||
|
|
||||||
|
pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let n = if args.len() > 1 {
|
||||||
|
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref())
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||||
|
Ok(JsValue::int(n as i32))
|
||||||
|
} else {
|
||||||
|
Ok(JsValue::float(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_is_nan(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Ok(JsValue::bool(false));
|
||||||
|
}
|
||||||
|
let val = vm.reg(args[1]);
|
||||||
|
if val.is_double() {
|
||||||
|
Ok(JsValue::bool(val.as_double().is_nan()))
|
||||||
|
} else {
|
||||||
|
Ok(JsValue::bool(false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_is_finite(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Ok(JsValue::bool(false));
|
||||||
|
}
|
||||||
|
let val = vm.reg(args[1]);
|
||||||
|
if val.is_int() {
|
||||||
|
return Ok(JsValue::bool(true));
|
||||||
|
}
|
||||||
|
if val.is_double() {
|
||||||
|
return Ok(JsValue::bool(val.as_double().is_finite()));
|
||||||
|
}
|
||||||
|
let n = coercion::to_number(val, vm.kernel().string_forge().as_ref());
|
||||||
|
Ok(JsValue::bool(n.is_finite()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Ok(JsValue::float(f64::NAN));
|
||||||
|
}
|
||||||
|
let sf = vm.kernel().string_forge().as_ref();
|
||||||
|
let s = coercion::to_string(sf, vm.reg(args[1]));
|
||||||
|
let s = s.trim();
|
||||||
|
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok(JsValue::float(f64::NAN));
|
||||||
|
}
|
||||||
|
|
||||||
|
let radix = if args.len() > 2 {
|
||||||
|
let r = coercion::to_number(vm.reg(args[2]), sf) as i32;
|
||||||
|
if r == 0 {
|
||||||
|
10
|
||||||
|
} else {
|
||||||
|
r.clamp(2, 36)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
10
|
||||||
|
};
|
||||||
|
|
||||||
|
let (rest, hex) = if s.starts_with("0x") || s.starts_with("0X") {
|
||||||
|
if radix == 16 || radix == 0 || (args.len() <= 2) {
|
||||||
|
(s[2..].to_string(), true)
|
||||||
|
} else {
|
||||||
|
(s.to_string(), false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(s.to_string(), false)
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual_radix = if hex { 16u32 } else { radix as u32 };
|
||||||
|
|
||||||
|
if let Ok(n) = i32::from_str_radix(&rest, actual_radix) {
|
||||||
|
return Ok(JsValue::int(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(JsValue::float(f64::NAN))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_parse_float(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Ok(JsValue::float(f64::NAN));
|
||||||
|
}
|
||||||
|
let sf = vm.kernel().string_forge().as_ref();
|
||||||
|
let s = coercion::to_string(sf, vm.reg(args[1]));
|
||||||
|
let s = s.trim();
|
||||||
|
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok(JsValue::float(f64::NAN));
|
||||||
|
}
|
||||||
|
|
||||||
|
match fast_float::parse::<f64, _>(&s) {
|
||||||
|
Ok(v) => Ok(JsValue::float(v)),
|
||||||
|
Err(_) => Ok(JsValue::float(f64::NAN)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let n = coercion::to_number(vm.reg(args[0]), vm.kernel().string_forge().as_ref());
|
||||||
|
let radix = if args.len() > 1 {
|
||||||
|
let r = coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as u32;
|
||||||
|
r.clamp(2, 36)
|
||||||
|
} else {
|
||||||
|
10u32
|
||||||
|
};
|
||||||
|
|
||||||
|
if radix == 10 {
|
||||||
|
if n.is_nan() {
|
||||||
|
return Ok(vm.intern("NaN"));
|
||||||
|
}
|
||||||
|
if n.is_infinite() {
|
||||||
|
if n.is_sign_positive() {
|
||||||
|
return Ok(vm.intern("Infinity"));
|
||||||
|
}
|
||||||
|
return Ok(vm.intern("-Infinity"));
|
||||||
|
}
|
||||||
|
if n.fract() == 0.0 && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||||
|
return Ok(vm.intern(&(n as i64).to_string()));
|
||||||
|
}
|
||||||
|
let mut buf = ryu::Buffer::new();
|
||||||
|
Ok(vm.intern(buf.format(n)))
|
||||||
|
} else {
|
||||||
|
if n.is_nan() {
|
||||||
|
return Ok(vm.intern("NaN"));
|
||||||
|
}
|
||||||
|
let nn = n as i64;
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut value = nn.abs();
|
||||||
|
if value == 0 {
|
||||||
|
result.push('0');
|
||||||
|
} else {
|
||||||
|
let chars = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
|
let mut digits = Vec::new();
|
||||||
|
while value > 0 {
|
||||||
|
digits.push(chars.as_bytes()[(value % radix as i64) as usize] as char);
|
||||||
|
value /= radix as i64;
|
||||||
|
}
|
||||||
|
for ch in digits.iter().rev() {
|
||||||
|
result.push(*ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nn < 0 {
|
||||||
|
result.insert(0, '-');
|
||||||
|
}
|
||||||
|
Ok(vm.intern(&result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_to_fixed(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let n = coercion::to_number(vm.reg(args[0]), vm.kernel().string_forge().as_ref());
|
||||||
|
let digits = if args.len() > 1 {
|
||||||
|
coercion::to_number(vm.reg(args[1]), vm.kernel().string_forge().as_ref()) as usize
|
||||||
|
} else {
|
||||||
|
0usize
|
||||||
|
}
|
||||||
|
.min(20);
|
||||||
|
|
||||||
|
let formatted = format!("{:.digits$}", n);
|
||||||
|
Ok(vm.intern(&formatted))
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ use oxide_compiler::opcode::{self, OpCode};
|
|||||||
|
|
||||||
use crate::coercion;
|
use crate::coercion;
|
||||||
use crate::native::NativeFn;
|
use crate::native::NativeFn;
|
||||||
use oxide_kernel::builtin::{ArrayMethods, ErrorMethods, ObjectMethods, StringMethods};
|
use oxide_kernel::builtin::{
|
||||||
|
ArrayMethods, ErrorMethods, NumberMethods, ObjectMethods, StringMethods,
|
||||||
|
};
|
||||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||||
use oxide_kernel::prop_forge::PropTemplate;
|
use oxide_kernel::prop_forge::PropTemplate;
|
||||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||||
@@ -65,8 +67,7 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
|||||||
let cur_count = global.prop_count();
|
let cur_count = global.prop_count();
|
||||||
global.set_shape_id(obj_shape);
|
global.set_shape_id(obj_shape);
|
||||||
global.set_prop_count(cur_count + 1);
|
global.set_prop_count(cur_count + 1);
|
||||||
let bump = bumpalo::Bump::new();
|
global.set_prop_expand_heap(cur_count, obj_val);
|
||||||
global.set_prop_expand(cur_count, obj_val, &bump);
|
|
||||||
|
|
||||||
let _array_methods = ArrayMethods {
|
let _array_methods = ArrayMethods {
|
||||||
push: crate::builtins::array::array_push as *const (),
|
push: crate::builtins::array::array_push as *const (),
|
||||||
@@ -102,8 +103,7 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
|||||||
let cur_count = global.prop_count();
|
let cur_count = global.prop_count();
|
||||||
global.set_shape_id(arr_shape);
|
global.set_shape_id(arr_shape);
|
||||||
global.set_prop_count(cur_count + 1);
|
global.set_prop_count(cur_count + 1);
|
||||||
let bump2 = bumpalo::Bump::new();
|
global.set_prop_expand_heap(cur_count, arr_val);
|
||||||
global.set_prop_expand(cur_count, arr_val, &bump2);
|
|
||||||
global.bump_generation();
|
global.bump_generation();
|
||||||
|
|
||||||
let error_methods = ErrorMethods {
|
let error_methods = ErrorMethods {
|
||||||
@@ -128,8 +128,7 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
|||||||
let cur_count = global.prop_count();
|
let cur_count = global.prop_count();
|
||||||
global.set_shape_id(err_shape);
|
global.set_shape_id(err_shape);
|
||||||
global.set_prop_count(cur_count + 1);
|
global.set_prop_count(cur_count + 1);
|
||||||
let bump3 = bumpalo::Bump::new();
|
global.set_prop_expand_heap(cur_count, err_val);
|
||||||
global.set_prop_expand(cur_count, err_val, &bump3);
|
|
||||||
global.bump_generation();
|
global.bump_generation();
|
||||||
|
|
||||||
let string_methods = StringMethods {
|
let string_methods = StringMethods {
|
||||||
@@ -167,9 +166,61 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
|||||||
let cur_count = global.prop_count();
|
let cur_count = global.prop_count();
|
||||||
global.set_shape_id(str_shape);
|
global.set_shape_id(str_shape);
|
||||||
global.set_prop_count(cur_count + 1);
|
global.set_prop_count(cur_count + 1);
|
||||||
let bump4 = bumpalo::Bump::new();
|
global.set_prop_expand_heap(cur_count, str_val);
|
||||||
global.set_prop_expand(cur_count, str_val, &bump4);
|
|
||||||
global.bump_generation();
|
global.bump_generation();
|
||||||
|
|
||||||
|
let number_methods = NumberMethods {
|
||||||
|
is_nan: crate::builtins::number::number_is_nan as *const (),
|
||||||
|
is_finite: crate::builtins::number::number_is_finite as *const (),
|
||||||
|
parse_int: crate::builtins::number::number_parse_int as *const (),
|
||||||
|
parse_float: crate::builtins::number::number_parse_float as *const (),
|
||||||
|
to_string: crate::builtins::number::number_to_string as *const (),
|
||||||
|
to_fixed: crate::builtins::number::number_to_fixed as *const (),
|
||||||
|
};
|
||||||
|
kernel.builtin_world().bind_number_methods(
|
||||||
|
&number_methods,
|
||||||
|
kernel.string_forge().as_ref(),
|
||||||
|
kernel.shape_forge().as_ref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let si_num = kernel.string_forge().intern("Number").0;
|
||||||
|
let num_shape = kernel.shape_forge().make_shape(global.shape_id(), si_num);
|
||||||
|
let num_val = JsValue::from_js_object(
|
||||||
|
kernel.builtin_world().number_constructor.as_ptr() as *mut JsObject
|
||||||
|
);
|
||||||
|
let cur_count = global.prop_count();
|
||||||
|
global.set_shape_id(num_shape);
|
||||||
|
global.set_prop_count(cur_count + 1);
|
||||||
|
global.set_prop_expand_heap(cur_count, num_val);
|
||||||
|
global.bump_generation();
|
||||||
|
|
||||||
|
{
|
||||||
|
let num_ctor_ptr = kernel.builtin_world().number_constructor.as_ptr() as *mut JsObject;
|
||||||
|
let num_ctor = unsafe { &mut *num_ctor_ptr };
|
||||||
|
num_ctor.set_native_fn(Some(
|
||||||
|
crate::builtins::number::number_constructor as *const (),
|
||||||
|
));
|
||||||
|
num_ctor.set_native_arg_count(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pi_fn = crate::builtins::number::number_parse_int as *const ();
|
||||||
|
let pf_fn = crate::builtins::number::number_parse_float as *const ();
|
||||||
|
let _ = oxide_kernel::builtin::BuiltinWorld::bind_method(
|
||||||
|
global,
|
||||||
|
kernel.shape_forge().as_ref(),
|
||||||
|
kernel.string_forge().as_ref(),
|
||||||
|
"parseInt",
|
||||||
|
pi_fn,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let _ = oxide_kernel::builtin::BuiltinWorld::bind_method(
|
||||||
|
global,
|
||||||
|
kernel.shape_forge().as_ref(),
|
||||||
|
kernel.string_forge().as_ref(),
|
||||||
|
"parseFloat",
|
||||||
|
pf_fn,
|
||||||
|
1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vm {
|
impl Vm {
|
||||||
@@ -605,6 +656,17 @@ impl Vm {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if val.is_int() || val.is_double() {
|
||||||
|
let proto_ptr =
|
||||||
|
self.kernel.builtin_world().number_proto.as_ptr() as *mut JsObject;
|
||||||
|
let proto = unsafe { &*proto_ptr };
|
||||||
|
let prop_name_si = self.regs[b].as_string_index();
|
||||||
|
if let Some(resolved) = self.resolve_property(proto, prop_name_si) {
|
||||||
|
self.regs[a] = resolved;
|
||||||
|
self.pc += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
return Err("IC_GET_PROP on non-object".into());
|
return Err("IC_GET_PROP on non-object".into());
|
||||||
}
|
}
|
||||||
let obj = unsafe { &*obj_ptr };
|
let obj = unsafe { &*obj_ptr };
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
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 number_is_nan_with_nan() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "Number.isNaN(NaN)").unwrap();
|
||||||
|
assert!(result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_is_nan_with_number() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "Number.isNaN(42)").unwrap();
|
||||||
|
assert!(!result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_is_finite_with_number() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "Number.isFinite(42)").unwrap();
|
||||||
|
assert!(result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_is_finite_with_infinity() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "Number.isFinite(1/0)").unwrap();
|
||||||
|
assert!(!result.as_bool());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_int_decimal() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "parseInt('42')").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_int_hex() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "parseInt('0xFF')").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_float_decimal() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "parseFloat('3.14')").unwrap();
|
||||||
|
assert!((result.as_double() - 3.14).abs() < 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_float_invalid() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "parseFloat('abc')").unwrap();
|
||||||
|
assert!(result.as_double().is_nan());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_constructor() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "Number('42')").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_string_of_number() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "var n = 42; n.toString()").unwrap();
|
||||||
|
let s = vm
|
||||||
|
.kernel()
|
||||||
|
.string_forge()
|
||||||
|
.lookup(result.as_string_index())
|
||||||
|
.unwrap_or_default();
|
||||||
|
assert_eq!(s, "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_fixed_of_number() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
let result = eval(&mut vm, "var n = 3.14159; n.toFixed(2)").unwrap();
|
||||||
|
let s = vm
|
||||||
|
.kernel()
|
||||||
|
.string_forge()
|
||||||
|
.lookup(result.as_string_index())
|
||||||
|
.unwrap_or_default();
|
||||||
|
assert_eq!(s, "3.14");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user