Files
vectorcraft/crates/js/src/number_builtins.rs
T
dotCooCoo 02fffcd00c JavaScript: String, Number, RegExp and Date built-ins
A realm now has String, Number, RegExp and Date as ECMAScript 5.1
sections 15.5, 15.7, 15.9 and 15.10 define them, with Annex B's substr,
getYear, setYear and toGMTString.

- String: every ES5 prototype method; split, replace, match and search
  take strings and RegExps. Each new string's length is checked against
  the heap limit before it is built, and scans take fuel.
- Number: toString(radix), toFixed, toExponential and toPrecision
  format from the number's exact decimal value.
- RegExp: a backtracking matcher keeps its choice points and capture
  state on the heap, within the heap room left, and takes fuel for every
  step, so a catastrophic pattern stops at the fuel limit. Patterns
  nested past Options::max_depth give a SyntaxError. Flags g, i and m,
  lastIndex, exec, test and toString.
- Date: chrono converts days to calendar dates, shifting by whole
  400-year cycles past its years. Realm::set_clock and
  Realm::set_time_zone set the clock and local time (UTC by default).
  Without a hint, a Date converts to a primitive as a string.

cargo xtask test262 also runs the suite's built-ins tests for the four
and prints their counts.
2026-11-07 18:24:27 -08:00

128 lines
5.2 KiB
Rust

//! `Number` (ECMA-262 5.1 section 15.7): the constructor, its constants and the methods of
//! `Number.prototype`. The formats are in [`crate::number`].
use crate::builtins::{arg, constructor, data, method, native};
use crate::heap::{Class, NativeKind, Object};
use crate::interp::{ErrorType, Interp, JsResult};
use crate::number;
use crate::string_builtins::{reserve_units, to_integer};
use crate::value::{Str, Value};
pub(crate) fn install(interp: &mut Interp) -> JsResult<()> {
let proto = interp.intrinsics.number_proto;
let ctor = native(interp, "Number", number_ctor, 1, NativeKind::Plain, true)?;
constructor(interp, "Number", ctor, proto)?;
let constants = [
("MAX_VALUE", f64::MAX),
("MIN_VALUE", f64::from_bits(1)),
("NaN", f64::NAN),
("NEGATIVE_INFINITY", f64::NEG_INFINITY),
("POSITIVE_INFINITY", f64::INFINITY),
];
for (name, v) in constants {
data(interp, ctor, name, Value::Number(v), 0)?;
}
method(interp, proto, "toString", to_string, 1)?;
method(interp, proto, "toLocaleString", to_locale_string, 0)?;
method(interp, proto, "valueOf", value_of, 0)?;
method(interp, proto, "toFixed", to_fixed, 1)?;
method(interp, proto, "toExponential", to_exponential, 1)?;
method(interp, proto, "toPrecision", to_precision, 1)
}
/// `Number(value)` and `new Number(value)` (sections 15.7.1 and 15.7.2).
fn number_ctor(interp: &mut Interp, _: Value, args: &[Value], construct: bool) -> JsResult<Value> {
let n = match args.first() {
Some(v) => interp.to_number(v)?,
None => 0.0,
};
if !construct {
return Ok(Value::Number(n));
}
let proto = interp.intrinsics.number_proto;
Ok(Value::Object(interp.heap.alloc_object(Object::new(Class::Number(n), Some(proto)))))
}
/// The number a `Number.prototype` method works on: a number, or a Number object's number.
fn this_number(interp: &mut Interp, this: &Value, name: &str) -> JsResult<f64> {
match this {
Value::Number(n) => Ok(*n),
Value::Object(o) => match interp.heap.object(*o).map(|o| &o.class) {
Some(Class::Number(n)) => Ok(*n),
_ => Err(interp.type_error(&format!("Number.prototype.{name} was called on a value that is not a number"))),
},
_ => Err(interp.type_error(&format!("Number.prototype.{name} was called on a value that is not a number"))),
}
}
fn text(interp: &mut Interp, s: &str) -> JsResult<Value> {
reserve_units(interp, s.len())?;
Ok(Value::String(Str::from(s)))
}
/// `toString(radix)` (section 15.7.4.2).
fn to_string(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let x = this_number(interp, &this, "toString")?;
let radix = match arg(args, 0) {
Value::Undefined => 10.0,
r => to_integer(interp.to_number(&r)?),
};
if !(2.0..=36.0).contains(&radix) {
return Err(interp.throw(ErrorType::Range, "the radix must be between 2 and 36"));
}
let s = if radix == 10.0 { number::to_string(x) } else { number::to_radix_string(x, radix as u32) };
text(interp, &s)
}
fn to_locale_string(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
let x = this_number(interp, &this, "toLocaleString")?;
text(interp, &number::to_string(x))
}
fn value_of(interp: &mut Interp, this: Value, _: &[Value], _: bool) -> JsResult<Value> {
this_number(interp, &this, "valueOf").map(Value::Number)
}
/// `toFixed(fractionDigits)` (section 15.7.4.5).
fn to_fixed(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let x = this_number(interp, &this, "toFixed")?;
let f = to_integer(interp.to_number(&arg(args, 0))?);
if !(0.0..=20.0).contains(&f) {
return Err(interp.throw(ErrorType::Range, "toFixed() digits must be between 0 and 20"));
}
text(interp, &number::to_fixed(x, f as usize))
}
/// `toExponential(fractionDigits)` (section 15.7.4.6).
fn to_exponential(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let x = this_number(interp, &this, "toExponential")?;
let digits = arg(args, 0);
let f = to_integer(interp.to_number(&digits)?);
if !x.is_finite() {
return text(interp, &number::to_string(x));
}
let f = match digits {
Value::Undefined => None,
_ if !(0.0..=20.0).contains(&f) => return Err(interp.throw(ErrorType::Range, "toExponential() digits must be between 0 and 20")),
_ => Some(f as usize),
};
text(interp, &number::to_exponential(x, f))
}
/// `toPrecision(precision)` (section 15.7.4.7).
fn to_precision(interp: &mut Interp, this: Value, args: &[Value], _: bool) -> JsResult<Value> {
let x = this_number(interp, &this, "toPrecision")?;
let precision = arg(args, 0);
if matches!(precision, Value::Undefined) {
return text(interp, &number::to_string(x));
}
let p = to_integer(interp.to_number(&precision)?);
if !x.is_finite() {
return text(interp, &number::to_string(x));
}
if !(1.0..=21.0).contains(&p) {
return Err(interp.throw(ErrorType::Range, "toPrecision() argument must be between 1 and 21"));
}
text(interp, &number::to_precision(x, p as usize))
}