Skip to content

Commit a2c02ae

Browse files
committed
Implement BigInt support and primitive prototype lookup
- src/js_bigint.rs: Add BigInt implementation with constructor, static methods, and instance methods. - src/core/eval.rs: Add mechanisms to access properties on primitive values via prototype lookup. - src/core/eval.rs: Route BigInt constructor and method calls in the evaluator. - src/core.rs: Register BigInt global object during initialization. - src/core/value.rs: Add to_primitive helper function. - src/lib.rs: Enable js_bigint module.
1 parent d2a3693 commit a2c02ae

File tree

6 files changed

+261
-72
lines changed

6 files changed

+261
-72
lines changed

js-scripts/bigint_test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
try {
2+
console.log("Starting BigInt tests...");
3+
4+
// 1. Constructor
5+
var b1 = BigInt(123);
6+
var b2 = BigInt("900719925474099100");
7+
console.log("b1 created: " + b1.toString());
8+
console.log("b2 created: " + b2.toString());
9+
10+
if (b1.toString() !== "123") throw "b1 value mismatch";
11+
if (b2.toString() !== "900719925474099100") throw "b2 value mismatch";
12+
13+
// 2. Methods
14+
console.log("Testing asIntN...");
15+
// 64-bit signed int of b2
16+
var b3 = BigInt.asIntN(64, b2);
17+
console.log("BigInt.asIntN(64, b2) = " + b3.toString());
18+
19+
console.log("Testing asUintN...");
20+
var b4 = BigInt.asUintN(64, b2);
21+
console.log("BigInt.asUintN(64, b2) = " + b4.toString());
22+
23+
console.log("Testing valueOf...");
24+
var v1 = b1.valueOf();
25+
console.log("b1.valueOf() = " + v1.toString());
26+
console.log("Type of b1.valueOf() = " + (typeof v1));
27+
console.log("Type of b1 = " + (typeof b1));
28+
29+
console.log("BigInt tests finished successfully.");
30+
31+
} catch (e) {
32+
console.log("Test Failed: " + e);
33+
if (e.stack) console.log(e.stack);
34+
}

src/core.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::error::JSError;
22
use crate::js_array::initialize_array;
3+
use crate::js_bigint::initialize_bigint;
34
use crate::js_console::initialize_console_object;
45
use crate::js_date::initialize_date;
56
use crate::js_math::initialize_math;
@@ -72,6 +73,7 @@ pub fn initialize_global_constructors<'gc>(mc: &MutationContext<'gc>, env: &JSOb
7273
// Initialize Date constructor and prototype
7374
initialize_date(mc, env)?;
7475
initialize_os_module(mc, env)?;
76+
initialize_bigint(mc, env)?;
7577

7678
env_set(mc, env, "undefined", Value::Undefined)?;
7779
env_set(mc, env, "NaN", Value::Number(f64::NAN))?;

src/core/eval.rs

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use crate::core::{Gc, GcCell, MutationContext};
44
use crate::js_array::handle_array_static_method;
5+
use crate::js_bigint::bigint_constructor;
56
use crate::js_date::{handle_date_method, handle_date_static_method};
67
use crate::js_os::handle_os_method;
78
use crate::js_string::{string_from_char_code, string_from_code_point, string_raw};
@@ -469,6 +470,34 @@ fn refresh_error_by_additional_stack_frame<'gc>(
469470
e
470471
}
471472

473+
fn get_primitive_prototype_property<'gc>(
474+
mc: &MutationContext<'gc>,
475+
env: &JSObjectDataPtr<'gc>,
476+
obj_val: &Value<'gc>,
477+
key: &PropertyKey<'gc>,
478+
) -> Result<Value<'gc>, EvalError<'gc>> {
479+
let proto_name = match obj_val {
480+
Value::BigInt(_) => "BigInt",
481+
Value::Number(_) => "Number",
482+
Value::String(_) => "String",
483+
Value::Boolean(_) => "Boolean",
484+
_ => return Ok(Value::Undefined),
485+
};
486+
487+
if let Ok(ctor) = evaluate_var(mc, env, proto_name) {
488+
if let Value::Object(ctor_obj) = ctor {
489+
if let Some(proto_ref) = obj_get_key_value(&ctor_obj, &"prototype".into())? {
490+
if let Value::Object(proto) = &*proto_ref.borrow() {
491+
if let Some(val) = obj_get_key_value(proto, key)? {
492+
return Ok(val.borrow().clone());
493+
}
494+
}
495+
}
496+
}
497+
}
498+
Ok(Value::Undefined)
499+
}
500+
472501
pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>, expr: &Expr) -> Result<Value<'gc>, EvalError<'gc>> {
473502
match expr {
474503
Expr::Number(n) => Ok(Value::Number(*n)),
@@ -663,7 +692,7 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
663692
} else if matches!(obj_val, Value::Undefined | Value::Null) {
664693
return Err(EvalError::Js(raise_eval_error!("Cannot read properties of null or undefined")));
665694
} else {
666-
Value::Undefined
695+
get_primitive_prototype_property(mc, env, &obj_val, &key.as_str().into())?
667696
};
668697
(f_val, Some(obj_val))
669698
}
@@ -703,6 +732,11 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
703732
} else if let Some(method) = name.strip_prefix("os.") {
704733
let this_val = this_val.clone().unwrap_or(Value::Object(*env));
705734
Ok(handle_os_method(mc, this_val, method, &eval_args, env).map_err(EvalError::Js)?)
735+
} else if let Some(method) = name.strip_prefix("BigInt.prototype.") {
736+
let this_v = this_val.clone().unwrap_or(Value::Undefined);
737+
Ok(crate::js_bigint::handle_bigint_object_method(this_v, method, &eval_args).map_err(EvalError::Js)?)
738+
} else if let Some(method) = name.strip_prefix("BigInt.") {
739+
Ok(crate::js_bigint::handle_bigint_static_method(method, &eval_args, env).map_err(EvalError::Js)?)
706740
} else if let Some(method) = name.strip_prefix("Math.") {
707741
Ok(handle_math_call(mc, method, &eval_args, env).map_err(EvalError::Js)?)
708742
} else if let Some(method) = name.strip_prefix("Date.prototype.") {
@@ -843,6 +877,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
843877
Value::String(name) => {
844878
if name == &crate::unicode::utf8_to_utf16("String") {
845879
Ok(crate::js_string::string_constructor(mc, &eval_args, env)?)
880+
} else if name == &crate::unicode::utf8_to_utf16("BigInt") {
881+
Ok(bigint_constructor(&eval_args, env)?)
846882
} else {
847883
Err(EvalError::Js(raise_eval_error!("Not a function")))
848884
}
@@ -911,39 +947,44 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
911947
}
912948
Expr::Property(obj_expr, key) => {
913949
let obj_val = evaluate_expr(mc, env, obj_expr)?;
914-
if let Value::Object(obj) = obj_val {
915-
if let Some(val) = obj_get_key_value(&obj, &key.as_str().into())? {
916-
Ok(val.borrow().clone())
950+
951+
if let Value::Object(obj) = &obj_val {
952+
if let Some(val) = obj_get_key_value(obj, &key.as_str().into())? {
953+
return Ok(val.borrow().clone());
917954
} else {
918-
Ok(Value::Undefined)
955+
return Ok(Value::Undefined);
919956
}
920-
} else if matches!(obj_val, Value::Undefined | Value::Null) {
921-
Err(EvalError::Js(raise_eval_error!("Cannot read properties of null or undefined")))
922-
} else {
923-
Ok(Value::Undefined)
924957
}
958+
959+
if matches!(obj_val, Value::Undefined | Value::Null) {
960+
return Err(EvalError::Js(raise_eval_error!("Cannot read properties of null or undefined")));
961+
}
962+
963+
get_primitive_prototype_property(mc, env, &obj_val, &key.as_str().into())
925964
}
926965
Expr::Index(obj_expr, key_expr) => {
927966
let obj_val = evaluate_expr(mc, env, obj_expr)?;
928967
let key_val = evaluate_expr(mc, env, key_expr)?;
929968

930-
if let Value::Object(obj) = obj_val {
931-
let key = match key_val {
932-
Value::String(s) => PropertyKey::String(utf16_to_utf8(&s)),
933-
Value::Number(n) => PropertyKey::String(n.to_string()),
934-
_ => PropertyKey::String(value_to_string(&key_val)),
935-
};
969+
let key = match key_val {
970+
Value::String(s) => PropertyKey::String(utf16_to_utf8(&s)),
971+
Value::Number(n) => PropertyKey::String(n.to_string()),
972+
_ => PropertyKey::String(value_to_string(&key_val)),
973+
};
936974

937-
if let Some(val) = obj_get_key_value(&obj, &key)? {
938-
Ok(val.borrow().clone())
975+
if let Value::Object(obj) = &obj_val {
976+
if let Some(val) = obj_get_key_value(obj, &key)? {
977+
return Ok(val.borrow().clone());
939978
} else {
940-
Ok(Value::Undefined)
979+
return Ok(Value::Undefined);
941980
}
942-
} else if matches!(obj_val, Value::Undefined | Value::Null) {
943-
Err(EvalError::Js(raise_eval_error!("Cannot read properties of null or undefined")))
944-
} else {
945-
Ok(Value::Undefined)
946981
}
982+
983+
if matches!(obj_val, Value::Undefined | Value::Null) {
984+
return Err(EvalError::Js(raise_eval_error!("Cannot read properties of null or undefined")));
985+
}
986+
987+
get_primitive_prototype_property(mc, env, &obj_val, &key)
947988
}
948989
Expr::TemplateString(parts) => {
949990
let mut result = Vec::new();

src/core/value.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,74 @@ impl<'gc> std::fmt::Debug for Value<'gc> {
445445
}
446446
}
447447
}
448+
// Helper: perform ToPrimitive coercion with a given hint ('string', 'number', 'default')
449+
pub fn to_primitive<'gc>(val: &Value<'gc>, _hint: &str, _env: &JSObjectDataPtr<'gc>) -> Result<Value<'gc>, JSError> {
450+
match val {
451+
Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::Undefined | Value::Null | Value::Symbol(_) => Ok(val.clone()),
452+
Value::Object(_object) => {
453+
/*
454+
// Prefer explicit [Symbol.toPrimitive] if present and callable
455+
if let Some(tp_sym) = get_well_known_symbol_rc("toPrimitive") {
456+
let key = PropertyKey::Symbol(tp_sym.clone());
457+
if let Some(method_rc) = obj_get_key_value(object, &key)? {
458+
let method_val = method_rc.borrow().clone();
459+
// Accept direct closures or function-objects that wrap a closure
460+
if let Some((params, body, captured_env)) = extract_closure_from_value(&method_val) {
461+
// Pass hint as first param if the function declares params
462+
let args = vec![Value::String(utf8_to_utf16(hint))];
463+
let func_env = prepare_function_call_env(
464+
Some(&captured_env),
465+
Some(Value::Object(object.clone())),
466+
Some(&params),
467+
&args,
468+
None,
469+
None,
470+
)?;
471+
let result = evaluate_statements(&func_env, &body)?;
472+
match result {
473+
Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_) | Value::Symbol(_) => {
474+
return Ok(result);
475+
}
476+
_ => {
477+
return Err(raise_type_error!("[Symbol.toPrimitive] must return a primitive"));
478+
}
479+
}
480+
} else {
481+
// Not a closure/minimally supported callable - fall through to default algorithm
482+
}
483+
}
484+
}
485+
486+
// Default algorithm: order depends on hint
487+
if hint == "string" {
488+
// toString -> valueOf
489+
let to_s = crate::js_object::handle_to_string_method(&Value::Object(object.clone()), &[], env)?;
490+
if matches!(to_s, Value::String(_) | Value::Number(_) | Value::Boolean(_) | Value::BigInt(_)) {
491+
return Ok(to_s);
492+
}
493+
let val_of = crate::js_object::handle_value_of_method(&Value::Object(object.clone()), &[], env)?;
494+
if matches!(val_of, Value::String(_) | Value::Number(_) | Value::Boolean(_) | Value::BigInt(_)) {
495+
return Ok(val_of);
496+
}
497+
} else {
498+
// number or default: valueOf -> toString
499+
let val_of = crate::js_object::handle_value_of_method(&Value::Object(object.clone()), &[], env)?;
500+
if matches!(val_of, Value::Number(_) | Value::String(_) | Value::Boolean(_) | Value::BigInt(_)) {
501+
return Ok(val_of);
502+
}
503+
let to_s = crate::js_object::handle_to_string_method(&Value::Object(object.clone()), &[], env)?;
504+
if matches!(to_s, Value::String(_) | Value::Number(_) | Value::Boolean(_) | Value::BigInt(_)) {
505+
return Ok(to_s);
506+
}
507+
}
508+
509+
Err(raise_type_error!("Cannot convert object to primitive"))
510+
// */
511+
todo!("to_primitive not yet implemented for objects");
512+
}
513+
_ => Ok(val.clone()),
514+
}
515+
}
448516

449517
pub fn value_to_string<'gc>(val: &Value<'gc>) -> String {
450518
match val {

0 commit comments

Comments
 (0)