mirror of https://github.com/boa-dev/boa.git
Browse Source
* Feature(Number): Tests for the Test Driven Development! refs #34 * Feature: Skeleton code for Number refs #34 * Feature: Most of Number() is complete Includes: - make_number() - call_number() - Number().toExponential() - Number().toFixed() - Number().toLocaleString() (ish) - Number().toString() - Number().valueOf() - create_constructor() - init() Missing: - Number().toPrecision() refs #34 * Feature(Number): test compatability with const -> var I don't have all the context on _why_ but all of the tests for `Number` started failing. Upon investigation it was becuase `const` stopped acting the way I expected. Switching my test cases from using `const` to using `var` variable declarations made everything work again. I don't know enough about JS to know if this is a bug or expected behavior. Based on changes to other tests, it is know behavior. Refs #34 * Changelog: Number() object. Includes some clippy fixes. Fixes #34pull/212/head
Elijah Caine M. Voigt
5 years ago
committed by
Jason Williams
8 changed files with 426 additions and 15 deletions
@ -0,0 +1,382 @@
|
||||
use crate::{ |
||||
builtins::{ |
||||
function::NativeFunctionData, |
||||
object::{Object, ObjectKind, PROTOTYPE}, |
||||
value::{to_value, ResultValue, Value, ValueData}, |
||||
}, |
||||
exec::Interpreter, |
||||
}; |
||||
use std::{borrow::Borrow, f64, ops::Deref}; |
||||
|
||||
/// Helper function: to_number(value: &Value) -> Value
|
||||
///
|
||||
/// Converts a Value to a Number.
|
||||
fn to_number(value: &Value) -> Value { |
||||
match *value.deref().borrow() { |
||||
ValueData::Boolean(b) => { |
||||
if b { |
||||
to_value(1) |
||||
} else { |
||||
to_value(0) |
||||
} |
||||
} |
||||
ValueData::Function(_) | ValueData::Undefined => to_value(f64::NAN), |
||||
ValueData::Integer(i) => to_value(f64::from(i)), |
||||
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), |
||||
ValueData::Null => to_value(0), |
||||
ValueData::Number(n) => to_value(n), |
||||
ValueData::String(ref s) => match s.parse::<f64>() { |
||||
Ok(n) => to_value(n), |
||||
Err(_) => to_value(f64::NAN), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
/// Helper function: num_to_exponential(n: f64) -> String
|
||||
///
|
||||
/// Formats a float as a ES6-style exponential number string.
|
||||
fn num_to_exponential(n: f64) -> String { |
||||
match n.abs() { |
||||
x if x > 1.0 => format!("{:e}", n).replace("e", "e+"), |
||||
x if x == 0.0 => format!("{:e}", n).replace("e", "e+"), |
||||
_ => format!("{:e}", n), |
||||
} |
||||
} |
||||
|
||||
/// Number(arg)
|
||||
///
|
||||
/// Create a new number [[Construct]]
|
||||
pub fn make_number(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
let data = match args.get(0) { |
||||
Some(ref value) => to_number(value), |
||||
None => to_number(&to_value(0)), |
||||
}; |
||||
this.set_internal_slot("NumberData", data); |
||||
Ok(this.clone()) |
||||
} |
||||
|
||||
/// Number()
|
||||
///
|
||||
/// https://tc39.es/ecma262/#sec-number-constructor-number-value
|
||||
pub fn call_number(_this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
let data = match args.get(0) { |
||||
Some(ref value) => to_number(value), |
||||
None => to_number(&to_value(0)), |
||||
}; |
||||
Ok(data) |
||||
} |
||||
|
||||
/// Number().toExponential()
|
||||
///
|
||||
/// https://tc39.es/ecma262/#sec-number.prototype.toexponential
|
||||
pub fn to_exponential(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
let this_num = to_number(this).to_num(); |
||||
let this_str_num = num_to_exponential(this_num); |
||||
Ok(to_value(this_str_num)) |
||||
} |
||||
|
||||
/// https://tc39.es/ecma262/#sec-number.prototype.tofixed
|
||||
pub fn to_fixed(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
let this_num = to_number(this).to_num(); |
||||
let precision = match args.get(0) { |
||||
Some(n) => match n.to_int() { |
||||
x if x > 0 => n.to_int() as usize, |
||||
_ => 0, |
||||
}, |
||||
None => 0, |
||||
}; |
||||
let this_fixed_num = format!("{:.*}", precision, this_num); |
||||
Ok(to_value(this_fixed_num)) |
||||
} |
||||
|
||||
/// Number().toLocaleString()
|
||||
///
|
||||
/// https://tc39.es/ecma262/#sec-number.prototype.tolocalestring
|
||||
///
|
||||
/// Note that while this technically conforms to the Ecma standard, it does no actual
|
||||
/// internationalization logic.
|
||||
pub fn to_locale_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
let this_num = to_number(this).to_num(); |
||||
let this_str_num = format!("{}", this_num); |
||||
Ok(to_value(this_str_num)) |
||||
} |
||||
|
||||
/// Number().toPrecision(p)
|
||||
///
|
||||
/// https://tc39.es/ecma262/#sec-number.prototype.toprecision
|
||||
pub fn to_precision(this: &Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
println!("Number::to_precision()"); |
||||
let this_num = to_number(this); |
||||
let _num_str_len = format!("{}", this_num.to_num()).len(); |
||||
let _precision = match args.get(0) { |
||||
Some(n) => match n.to_int() { |
||||
x if x > 0 => n.to_int() as usize, |
||||
_ => 0, |
||||
}, |
||||
None => 0, |
||||
}; |
||||
// TODO: Implement toPrecision
|
||||
unimplemented!(); |
||||
} |
||||
|
||||
/// Number().toString()
|
||||
///
|
||||
/// https://tc39.es/ecma262/#sec-number.prototype.tostring
|
||||
pub fn to_string(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
Ok(to_value(format!("{}", to_number(this).to_num()))) |
||||
} |
||||
|
||||
/// Number().valueOf()
|
||||
///
|
||||
/// https://tc39.es/ecma262/#sec-number.prototype.valueof
|
||||
pub fn value_of(this: &Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { |
||||
Ok(to_number(this)) |
||||
} |
||||
|
||||
/// Create a new `Number` object
|
||||
pub fn create_constructor(global: &Value) -> Value { |
||||
let mut number_constructor = Object::default(); |
||||
number_constructor.kind = ObjectKind::Function; |
||||
|
||||
number_constructor.set_internal_method("construct", make_number); |
||||
number_constructor.set_internal_method("call", call_number); |
||||
|
||||
let number_prototype = ValueData::new_obj(Some(global)); |
||||
|
||||
number_prototype.set_internal_slot("NumberData", to_value(0)); |
||||
|
||||
number_prototype.set_field_slice( |
||||
"toExponential", |
||||
to_value(to_exponential as NativeFunctionData), |
||||
); |
||||
number_prototype.set_field_slice("toFixed", to_value(to_fixed as NativeFunctionData)); |
||||
number_prototype.set_field_slice( |
||||
"toLocaleString", |
||||
to_value(to_locale_string as NativeFunctionData), |
||||
); |
||||
number_prototype.set_field_slice("toPrecision", to_value(to_precision as NativeFunctionData)); |
||||
number_prototype.set_field_slice("toString", to_value(to_string as NativeFunctionData)); |
||||
number_prototype.set_field_slice("valueOf", to_value(value_of as NativeFunctionData)); |
||||
|
||||
let number = to_value(number_constructor); |
||||
number_prototype.set_field_slice("constructor", number.clone()); |
||||
number.set_field_slice(PROTOTYPE, number_prototype); |
||||
number |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
mod tests { |
||||
use super::*; |
||||
use crate::{builtins::value::ValueData, exec::Executor, forward, forward_val, realm::Realm}; |
||||
use std::f64; |
||||
|
||||
#[test] |
||||
fn check_number_constructor_is_function() { |
||||
let global = ValueData::new_obj(None); |
||||
let number_constructor = create_constructor(&global); |
||||
assert_eq!(number_constructor.is_function(), true); |
||||
} |
||||
|
||||
#[test] |
||||
fn call_number() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
let init = r#" |
||||
var default_zero = Number(); |
||||
var int_one = Number(1); |
||||
var float_two = Number(2.1); |
||||
var str_three = Number('3.2'); |
||||
var bool_one = Number(true); |
||||
var bool_zero = Number(false); |
||||
var invalid_nan = Number("I am not a number"); |
||||
var from_exp = Number("2.34e+2"); |
||||
"#; |
||||
|
||||
forward(&mut engine, init); |
||||
let default_zero = forward_val(&mut engine, "default_zero").unwrap(); |
||||
let int_one = forward_val(&mut engine, "int_one").unwrap(); |
||||
let float_two = forward_val(&mut engine, "float_two").unwrap(); |
||||
let str_three = forward_val(&mut engine, "str_three").unwrap(); |
||||
let bool_one = forward_val(&mut engine, "bool_one").unwrap(); |
||||
let bool_zero = forward_val(&mut engine, "bool_zero").unwrap(); |
||||
let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap(); |
||||
let from_exp = forward_val(&mut engine, "from_exp").unwrap(); |
||||
|
||||
assert_eq!(default_zero.to_num(), f64::from(0)); |
||||
assert_eq!(int_one.to_num(), f64::from(1)); |
||||
assert_eq!(float_two.to_num(), f64::from(2.1)); |
||||
assert_eq!(str_three.to_num(), f64::from(3.2)); |
||||
assert_eq!(bool_one.to_num(), f64::from(1)); |
||||
assert!(invalid_nan.to_num().is_nan()); |
||||
assert_eq!(bool_zero.to_num(), f64::from(0)); |
||||
assert_eq!(from_exp.to_num(), f64::from(234)); |
||||
} |
||||
|
||||
#[test] |
||||
fn to_exponential() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
let init = r#" |
||||
var default_exp = Number().toExponential(); |
||||
var int_exp = Number(5).toExponential(); |
||||
var float_exp = Number(1.234).toExponential(); |
||||
var big_exp = Number(1234).toExponential(); |
||||
var nan_exp = Number("I am also not a number").toExponential(); |
||||
var noop_exp = Number("1.23e+2").toExponential(); |
||||
"#; |
||||
|
||||
forward(&mut engine, init); |
||||
let default_exp = forward(&mut engine, "default_exp"); |
||||
let int_exp = forward(&mut engine, "int_exp"); |
||||
let float_exp = forward(&mut engine, "float_exp"); |
||||
let big_exp = forward(&mut engine, "big_exp"); |
||||
let nan_exp = forward(&mut engine, "nan_exp"); |
||||
let noop_exp = forward(&mut engine, "noop_exp"); |
||||
|
||||
assert_eq!(default_exp, String::from("0e+0")); |
||||
assert_eq!(int_exp, String::from("5e+0")); |
||||
assert_eq!(float_exp, String::from("1.234e+0")); |
||||
assert_eq!(big_exp, String::from("1.234e+3")); |
||||
assert_eq!(nan_exp, String::from("NaN")); |
||||
assert_eq!(noop_exp, String::from("1.23e+2")); |
||||
} |
||||
|
||||
#[test] |
||||
fn to_fixed() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
let init = r#" |
||||
var default_fixed = Number().toFixed(); |
||||
var pos_fixed = Number("3.456e+4").toFixed(); |
||||
var neg_fixed = Number("3.456e-4").toFixed(); |
||||
var noop_fixed = Number(5).toFixed(); |
||||
var nan_fixed = Number("I am not a number").toFixed(); |
||||
"#; |
||||
|
||||
forward(&mut engine, init); |
||||
let default_fixed = forward(&mut engine, "default_fixed"); |
||||
let pos_fixed = forward(&mut engine, "pos_fixed"); |
||||
let neg_fixed = forward(&mut engine, "neg_fixed"); |
||||
let noop_fixed = forward(&mut engine, "noop_fixed"); |
||||
let nan_fixed = forward(&mut engine, "nan_fixed"); |
||||
|
||||
assert_eq!(default_fixed, String::from("0")); |
||||
assert_eq!(pos_fixed, String::from("34560")); |
||||
assert_eq!(neg_fixed, String::from("0")); |
||||
assert_eq!(noop_fixed, String::from("5")); |
||||
assert_eq!(nan_fixed, String::from("NaN")); |
||||
} |
||||
|
||||
#[test] |
||||
fn to_locale_string() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
let init = r#" |
||||
var default_locale = Number().toLocaleString(); |
||||
var small_locale = Number(5).toLocaleString(); |
||||
var big_locale = Number("345600").toLocaleString(); |
||||
var neg_locale = Number(-25).toLocaleString(); |
||||
"#; |
||||
|
||||
// TODO: We don't actually do any locale checking here
|
||||
// To honor the spec we should print numbers according to user locale.
|
||||
|
||||
forward(&mut engine, init); |
||||
let default_locale = forward(&mut engine, "default_locale"); |
||||
let small_locale = forward(&mut engine, "small_locale"); |
||||
let big_locale = forward(&mut engine, "big_locale"); |
||||
let neg_locale = forward(&mut engine, "neg_locale"); |
||||
|
||||
assert_eq!(default_locale, String::from("0")); |
||||
assert_eq!(small_locale, String::from("5")); |
||||
assert_eq!(big_locale, String::from("345600")); |
||||
assert_eq!(neg_locale, String::from("-25")); |
||||
} |
||||
|
||||
#[test] |
||||
#[ignore] |
||||
fn to_precision() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
let init = r#" |
||||
var default_precision = Number().toPrecision(); |
||||
var low_precision = Number(123456789).toPrecision(1); |
||||
var more_precision = Number(123456789).toPrecision(4); |
||||
var exact_precision = Number(123456789).toPrecision(9); |
||||
var over_precision = Number(123456789).toPrecision(50); |
||||
var neg_precision = Number(-123456789).toPrecision(4); |
||||
"#; |
||||
|
||||
forward(&mut engine, init); |
||||
let default_precision = forward(&mut engine, "default_precision"); |
||||
let low_precision = forward(&mut engine, "low_precision"); |
||||
let more_precision = forward(&mut engine, "more_precision"); |
||||
let exact_precision = forward(&mut engine, "exact_precision"); |
||||
let over_precision = forward(&mut engine, "over_precision"); |
||||
let neg_precision = forward(&mut engine, "neg_precision"); |
||||
|
||||
assert_eq!(default_precision, String::from("0")); |
||||
assert_eq!(low_precision, String::from("1e+8")); |
||||
assert_eq!(more_precision, String::from("1.235e+8")); |
||||
assert_eq!(exact_precision, String::from("123456789")); |
||||
assert_eq!( |
||||
over_precision, |
||||
String::from("123456789.00000000000000000000000000000000000000000") |
||||
); |
||||
assert_eq!(neg_precision, String::from("-1.235e+8")); |
||||
} |
||||
|
||||
#[test] |
||||
fn to_string() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
let init = r#" |
||||
var default_string = Number().toString(); |
||||
var int_string = Number(123).toString(); |
||||
var float_string = Number(1.234).toString(); |
||||
var exp_string = Number("1.2e+4").toString(); |
||||
var neg_string = Number(-1.2).toString(); |
||||
"#; |
||||
|
||||
forward(&mut engine, init); |
||||
let default_string = forward(&mut engine, "default_string"); |
||||
let int_string = forward(&mut engine, "int_string"); |
||||
let float_string = forward(&mut engine, "float_string"); |
||||
let exp_string = forward(&mut engine, "exp_string"); |
||||
let neg_string = forward(&mut engine, "neg_string"); |
||||
|
||||
assert_eq!(default_string, String::from("0")); |
||||
assert_eq!(int_string, String::from("123")); |
||||
assert_eq!(float_string, String::from("1.234")); |
||||
assert_eq!(exp_string, String::from("12000")); |
||||
assert_eq!(neg_string, String::from("-1.2")); |
||||
} |
||||
|
||||
#[test] |
||||
fn value_of() { |
||||
let realm = Realm::create(); |
||||
let mut engine = Executor::new(realm); |
||||
// TODO: In addition to parsing numbers from strings, parse them bare As of October 2019
|
||||
// the parser does not understand scientific e.g., Xe+Y or -Xe-Y notation.
|
||||
let init = r#" |
||||
var default_val = Number().valueOf(); |
||||
var int_val = Number("123").valueOf(); |
||||
var float_val = Number(1.234).valueOf(); |
||||
var exp_val = Number("1.2e+4").valueOf() |
||||
var neg_val = Number("-1.2e+4").valueOf() |
||||
"#; |
||||
|
||||
forward(&mut engine, init); |
||||
let default_val = forward_val(&mut engine, "default_val").unwrap(); |
||||
let int_val = forward_val(&mut engine, "int_val").unwrap(); |
||||
let float_val = forward_val(&mut engine, "float_val").unwrap(); |
||||
let exp_val = forward_val(&mut engine, "exp_val").unwrap(); |
||||
let neg_val = forward_val(&mut engine, "neg_val").unwrap(); |
||||
|
||||
assert_eq!(default_val.to_num(), f64::from(0)); |
||||
assert_eq!(int_val.to_num(), f64::from(123)); |
||||
assert_eq!(float_val.to_num(), f64::from(1.234)); |
||||
assert_eq!(exp_val.to_num(), f64::from(12000)); |
||||
assert_eq!(neg_val.to_num(), f64::from(-12000)); |
||||
} |
||||
} |
Loading…
Reference in new issue