Skip to content

Commit da61a44

Browse files
committed
Integrate JS Number module, implementation details and evaluation logic
- Initialize Number module and register global constructor in `src/core.rs` - Implement Number constructor with support for various types (String, Boolean, Null, etc.) in `src/js_number.rs` - Add support for `new Number()` object creation - Implement Number static methods (e.g. isFinite, isNaN) and prototype methods (e.g. toString, valueOf) - Refactor `js_number` logic to split static/instance handling and extract prototype method dispatch - Add `Expr::TypeOf` evaluation support in `src/core/eval.rs` - Fix evaluation of `Expr::Null` and `Expr::Undefined` - Update `Expr::Call` to route Number methods correctly - Improve `Value` enum debug formatting in `src/core/value.rs` - Add `js-scripts/test_number_integration.js` for comprehensive testing
1 parent a2c02ae commit da61a44

File tree

6 files changed

+215
-78
lines changed

6 files changed

+215
-78
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Test Number constants
2+
console.log("=== Testing Number constants... ===");
3+
console.log("MAX_VALUE:", Number.MAX_VALUE);
4+
console.log("MIN_VALUE:", Number.MIN_VALUE);
5+
console.log("NaN:", Number.NaN);
6+
console.log("POSITIVE_INFINITY:", Number.POSITIVE_INFINITY);
7+
console.log("NEGATIVE_INFINITY:", Number.NEGATIVE_INFINITY);
8+
console.log("EPSILON:", Number.EPSILON);
9+
console.log("MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER);
10+
console.log("MIN_SAFE_INTEGER:", Number.MIN_SAFE_INTEGER);
11+
12+
// Test Number constructor as function
13+
console.log("=== Testing Number() constructor as function... ===");
14+
console.log("Number(123): " + Number(123));
15+
console.log("Number('123'): " + Number('123'));
16+
console.log("Number(' 123 '): " + Number(' 123 '));
17+
console.log("Number(true): " + Number(true));
18+
console.log("Number(false): " + Number(false));
19+
// Note: In standard JS, Number(null) is 0, Number(undefined) is NaN.
20+
// Checking current implementation behavior:
21+
console.log("Number(null): " + Number(null));
22+
console.log("Number(undefined): " + Number(undefined));
23+
24+
// Test new Number()
25+
console.log("=== Testing new Number()... ===");
26+
var n = new Number(123);
27+
console.log("typeof new Number(123): " + typeof n);
28+
console.log("valueOf: " + n.valueOf());
29+
console.log("toString: " + n.toString());
30+
31+
var n2 = new Number("456");
32+
console.log("new Number('456') valueOf: " + n2.valueOf());
33+
34+
// Test static methods
35+
console.log("=== Testing Static methods... ===");
36+
console.log("Number.isNaN(NaN): " + Number.isNaN(NaN));
37+
console.log("Number.isNaN(123): " + Number.isNaN(123));
38+
console.log("Number.isFinite(123): " + Number.isFinite(123));
39+
console.log("Number.isFinite(Infinity): " + Number.isFinite(Infinity));
40+
console.log("Number.isInteger(123): " + Number.isInteger(123));
41+
console.log("Number.isInteger(123.45): " + Number.isInteger(123.45));
42+
console.log("Number.isSafeInteger(9007199254740991): " + Number.isSafeInteger(9007199254740991));
43+
console.log("Number.isSafeInteger(9007199254740992): " + Number.isSafeInteger(9007199254740992));
44+
45+
console.log("Number.parseFloat('123.45'): " + Number.parseFloat('123.45'));
46+
console.log("Number.parseInt('123', 10): " + Number.parseInt('123', 10));
47+
console.log("Number.parseInt('101', 2): " + Number.parseInt('101', 2));
48+
49+
// Test instance methods
50+
console.log("=== Testing Instance methods... ===");
51+
var num = 123.456;
52+
console.log("num = 123.456");
53+
console.log("num.toFixed(2): " + num.toFixed(2));
54+
console.log("num.toExponential(1): " + num.toExponential(1));
55+
console.log("num.toPrecision(4): " + num.toPrecision(4));
56+
57+
console.log("(123).toString(): " + (123).toString());
58+
59+
try {
60+
console.log("Number.prototype.toFixed.call('not a number')");
61+
// Function.prototype.call might not be implemented yet.
62+
if (Number.prototype.toFixed.call) {
63+
Number.prototype.toFixed.call('not a number');
64+
} else {
65+
console.log("Skipping call() test as Function.prototype.call is not implemented.");
66+
}
67+
} catch (e) {
68+
console.log("Caught expected error: " + e.name + ": " + e.message);
69+
}

src/core.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::js_bigint::initialize_bigint;
44
use crate::js_console::initialize_console_object;
55
use crate::js_date::initialize_date;
66
use crate::js_math::initialize_math;
7+
use crate::js_number::initialize_number_module;
78
use crate::js_os::initialize_os_module;
89
use crate::js_regexp::initialize_regexp;
910
use crate::js_string::initialize_string;
@@ -66,6 +67,8 @@ pub fn initialize_global_constructors<'gc>(mc: &MutationContext<'gc>, env: &JSOb
6667
let console_obj = initialize_console_object(mc)?;
6768
env_set(mc, env, "console", Value::Object(console_obj))?;
6869

70+
initialize_number_module(mc, env)?;
71+
6972
initialize_math(mc, env)?;
7073
initialize_string(mc, env)?;
7174
initialize_array(mc, env)?;

src/core/eval.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::core::{Gc, GcCell, MutationContext};
44
use crate::js_array::handle_array_static_method;
55
use crate::js_bigint::bigint_constructor;
66
use crate::js_date::{handle_date_method, handle_date_static_method};
7+
use crate::js_number::{handle_number_instance_method, handle_number_prototype_method, handle_number_static_method, number_constructor};
78
use crate::js_os::handle_os_method;
89
use crate::js_string::{string_from_char_code, string_from_code_point, string_raw};
910
use crate::{
@@ -503,6 +504,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
503504
Expr::Number(n) => Ok(Value::Number(*n)),
504505
Expr::StringLit(s) => Ok(Value::String(s.clone())),
505506
Expr::Boolean(b) => Ok(Value::Boolean(*b)),
507+
Expr::Null => Ok(Value::Null),
508+
Expr::Undefined => Ok(Value::Undefined),
506509
Expr::Var(name, _, _) => Ok(evaluate_var(mc, env, name)?),
507510
Expr::Assign(target, value_expr) => {
508511
let val = evaluate_expr(mc, env, value_expr)?;
@@ -737,6 +740,10 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
737740
Ok(crate::js_bigint::handle_bigint_object_method(this_v, method, &eval_args).map_err(EvalError::Js)?)
738741
} else if let Some(method) = name.strip_prefix("BigInt.") {
739742
Ok(crate::js_bigint::handle_bigint_static_method(method, &eval_args, env).map_err(EvalError::Js)?)
743+
} else if let Some(method) = name.strip_prefix("Number.prototype.") {
744+
Ok(handle_number_prototype_method(this_val.clone(), method, &eval_args).map_err(EvalError::Js)?)
745+
} else if let Some(method) = name.strip_prefix("Number.") {
746+
Ok(handle_number_static_method(method, &eval_args).map_err(EvalError::Js)?)
740747
} else if let Some(method) = name.strip_prefix("Math.") {
741748
Ok(handle_math_call(mc, method, &eval_args, env).map_err(EvalError::Js)?)
742749
} else if let Some(method) = name.strip_prefix("Date.prototype.") {
@@ -877,6 +884,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
877884
Value::String(name) => {
878885
if name == &crate::unicode::utf8_to_utf16("String") {
879886
Ok(crate::js_string::string_constructor(mc, &eval_args, env)?)
887+
} else if name == &crate::unicode::utf8_to_utf16("Number") {
888+
Ok(number_constructor(&eval_args, env).map_err(EvalError::Js)?)
880889
} else if name == &crate::unicode::utf8_to_utf16("BigInt") {
881890
Ok(bigint_constructor(&eval_args, env)?)
882891
} else {
@@ -933,6 +942,21 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
933942
};
934943

935944
return Ok(crate::core::js_error::create_error(mc, prototype, msg)?);
945+
} else if name == &crate::unicode::utf8_to_utf16("Number") {
946+
let val = match number_constructor(&eval_args, env).map_err(EvalError::Js)? {
947+
Value::Number(n) => n,
948+
_ => f64::NAN,
949+
};
950+
let new_obj = crate::core::new_js_object_data(mc);
951+
obj_set_key_value(mc, &new_obj, &"__value__".into(), Value::Number(val))?;
952+
953+
if let Some(proto_val) = obj_get_key_value(&obj, &"prototype".into())?
954+
&& let Value::Object(proto_obj) = &*proto_val.borrow()
955+
{
956+
new_obj.borrow_mut(mc).prototype = Some(*proto_obj);
957+
}
958+
959+
return Ok(Value::Object(new_obj));
936960
} else if name == &crate::unicode::utf8_to_utf16("Date") {
937961
return Ok(crate::js_date::handle_date_constructor(mc, &eval_args, env)?);
938962
}
@@ -1023,6 +1047,50 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
10231047
Err(EvalError::Js(raise_eval_error!("Unary Negation only for numbers")))
10241048
}
10251049
}
1050+
Expr::TypeOf(expr) => {
1051+
// typeof handles ReferenceError for undeclared variables
1052+
let val_result = evaluate_expr(mc, env, expr);
1053+
let val = match val_result {
1054+
Ok(v) => v,
1055+
Err(e) => {
1056+
// Check if it is a ReferenceError (simplistic check for now, assuming EvalError could be it)
1057+
// Ideally we check if the error kind is ReferenceError.
1058+
// For now, if evaluation fails, return undefined (as string "undefined")
1059+
// This covers `typeof nonExistentVar` -> "undefined"
1060+
Value::Undefined
1061+
}
1062+
};
1063+
1064+
let type_str = match val {
1065+
Value::Number(_) => "number",
1066+
Value::String(_) => "string",
1067+
Value::Boolean(_) => "boolean",
1068+
Value::Undefined | Value::Uninitialized => "undefined",
1069+
Value::Null => "object",
1070+
Value::Symbol(_) => "symbol",
1071+
Value::BigInt(_) => "bigint",
1072+
Value::Function(_)
1073+
| Value::Closure(_)
1074+
| Value::AsyncClosure(_)
1075+
| Value::GeneratorFunction(..)
1076+
| Value::ClassDefinition(_) => "function",
1077+
Value::Object(obj) => {
1078+
if obj_get_key_value(&obj, &"__closure__".into()).unwrap_or(None).is_some() {
1079+
"function"
1080+
} else if let Some(is_ctor) = obj_get_key_value(&obj, &"__is_constructor".into()).unwrap_or(None) {
1081+
if matches!(*is_ctor.borrow(), Value::Boolean(true)) {
1082+
"function"
1083+
} else {
1084+
"object"
1085+
}
1086+
} else {
1087+
"object"
1088+
}
1089+
}
1090+
_ => "undefined",
1091+
};
1092+
Ok(Value::String(utf8_to_utf16(type_str)))
1093+
}
10261094
_ => Ok(Value::Undefined),
10271095
}
10281096
}

src/core/value.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@ impl<'gc> std::fmt::Debug for Value<'gc> {
441441
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442442
match self {
443443
Value::Number(n) => write!(f, "Number({})", n),
444+
Value::String(s) => write!(f, "String({:?})", utf16_to_utf8(s)),
445+
Value::Boolean(b) => write!(f, "Boolean({})", b),
446+
Value::Null => write!(f, "Null"),
447+
Value::Undefined => write!(f, "Undefined"),
448+
Value::Object(_) => write!(f, "Object"),
449+
Value::Function(s) => write!(f, "Function({})", s),
444450
_ => write!(f, "[value]"),
445451
}
446452
}

0 commit comments

Comments
 (0)