Skip to content

Commit aeb3979

Browse files
committed
Implement Map and MapIterator
- Enable `js_map` module and initialize `Map` global. - Refactor `initialize_collection_from_iterable` to accept evaluated `Value`s. - Implement `Map` constructor, instance methods, and `size` getter. - Implement `MapIterator` for `keys()`, `values()`, and `entries()`. - Add native function call hooks in `eval.rs` to support Map methods and accessors. - Add `Expr::New` support for `Map`.
1 parent 9ce47ab commit aeb3979

File tree

5 files changed

+320
-74
lines changed

5 files changed

+320
-74
lines changed

js-scripts/map_tests.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
let map = new Map();
2+
console.log(map.size); // should be 0
3+
map.set('a', 1);
4+
console.log(map.size); // should be 1
5+
console.log(map.get('a')); // 1
6+
console.log(map.has('a')); // true
7+
console.log(map.has('b')); // false
8+
map.set('a', 2);
9+
console.log(map.get('a')); // 2
10+
11+
map.delete('a');
12+
console.log(map.size); // should be 0
13+
14+
let map2 = new Map();
15+
console.log(map2.size); // 0
16+
17+
var map3 = new Map([ ['x', 10], ['y', 20] ]);
18+
console.log("map3 size", map3.size); // 2
19+
console.log("map3 x", map3.get('x')); // 10
20+
console.log("map3 y", map3.get('y')); // 20
21+
console.log("map3 has x", map3.has('x')); // true
22+
23+
const iterator = map3.keys();
24+
console.log(iterator.next().value); // 'x'
25+
console.log(iterator.next().value); // 'y'
26+
console.log(iterator.next().done); // true
27+
28+
const entriesIterator = map3.entries();
29+
console.log(entriesIterator.next().value); // ['x', 10]
30+
console.log(entriesIterator.next().value); // ['y', 20]
31+
console.log(entriesIterator.next().done); // true
32+
33+
const valuesIterator = map3.values();
34+
console.log(valuesIterator.next().value); // 10
35+
console.log(valuesIterator.next().value); // 20
36+
console.log(valuesIterator.next().done); // true
37+
38+
console.log("All tests passed");
39+
40+
true

src/core/eval.rs

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,9 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
10291029

10301030
match func_val {
10311031
Value::Function(name) => {
1032+
if let Some(res) = call_native_function(mc, &name, this_val.clone(), &eval_args, env)? {
1033+
return Ok(res);
1034+
}
10321035
if name == "eval" {
10331036
let first_arg = eval_args.get(0).cloned().unwrap_or(Value::Undefined);
10341037
if let Value::String(script_str) = first_arg {
@@ -1178,6 +1181,33 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
11781181
let method = &name[6..];
11791182
Ok(handle_array_static_method(mc, method, &eval_args, env)?)
11801183
}
1184+
} else if name.starts_with("Map.") {
1185+
if let Some(method) = name.strip_prefix("Map.prototype.") {
1186+
let this_v = this_val.clone().unwrap_or(Value::Undefined);
1187+
if let Value::Object(obj) = this_v {
1188+
if let Some(map_val) = obj_get_key_value(&obj, &"__map__".into())? {
1189+
if let Value::Map(map_ptr) = &*map_val.borrow() {
1190+
Ok(crate::js_map::handle_map_instance_method(mc, map_ptr, method, &eval_args, env)?)
1191+
} else {
1192+
Err(EvalError::Js(raise_eval_error!(
1193+
"TypeError: Map.prototype method called on incompatible receiver"
1194+
)))
1195+
}
1196+
} else {
1197+
Err(EvalError::Js(raise_eval_error!(
1198+
"TypeError: Map.prototype method called on incompatible receiver"
1199+
)))
1200+
}
1201+
} else if let Value::Map(map_ptr) = this_v {
1202+
Ok(crate::js_map::handle_map_instance_method(mc, &map_ptr, method, &eval_args, env)?)
1203+
} else {
1204+
Err(EvalError::Js(raise_eval_error!(
1205+
"TypeError: Map.prototype method called on non-object receiver"
1206+
)))
1207+
}
1208+
} else {
1209+
Err(EvalError::Js(raise_eval_error!(format!("Unknown Map function: {}", name))))
1210+
}
11811211
} else {
11821212
Err(EvalError::Js(raise_eval_error!(format!("Unknown native function: {}", name))))
11831213
}
@@ -1438,6 +1468,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
14381468
return Ok(Value::Object(new_obj));
14391469
} else if name == &crate::unicode::utf8_to_utf16("Date") {
14401470
return Ok(crate::js_date::handle_date_constructor(mc, &eval_args, env)?);
1471+
} else if name == &crate::unicode::utf8_to_utf16("Map") {
1472+
return Ok(crate::js_map::handle_map_constructor(mc, &eval_args, env)?);
14411473
}
14421474
}
14431475
}
@@ -1689,7 +1721,7 @@ fn evaluate_function_expression<'gc>(
16891721

16901722
fn get_property_with_accessors<'gc>(
16911723
mc: &MutationContext<'gc>,
1692-
_env: &JSObjectDataPtr<'gc>,
1724+
env: &JSObjectDataPtr<'gc>,
16931725
obj: &JSObjectDataPtr<'gc>,
16941726
key: &PropertyKey<'gc>,
16951727
) -> Result<Value<'gc>, EvalError<'gc>> {
@@ -1698,14 +1730,14 @@ fn get_property_with_accessors<'gc>(
16981730
match val {
16991731
Value::Property { getter, value, .. } => {
17001732
if let Some(g) = getter {
1701-
return call_accessor(mc, obj, &*g);
1733+
return call_accessor(mc, env, obj, &*g);
17021734
}
17031735
if let Some(v) = value {
17041736
return Ok(v.borrow().clone());
17051737
}
17061738
Ok(Value::Undefined)
17071739
}
1708-
Value::Getter(..) => call_accessor(mc, obj, &val),
1740+
Value::Getter(..) => call_accessor(mc, env, obj, &val),
17091741
_ => Ok(val),
17101742
}
17111743
} else {
@@ -1750,12 +1782,74 @@ fn set_property_with_accessors<'gc>(
17501782
}
17511783
}
17521784

1785+
fn call_native_function<'gc>(
1786+
mc: &MutationContext<'gc>,
1787+
name: &str,
1788+
this_val: Option<Value<'gc>>,
1789+
args: &[Value<'gc>],
1790+
env: &JSObjectDataPtr<'gc>,
1791+
) -> Result<Option<Value<'gc>>, EvalError<'gc>> {
1792+
if name == "MapIterator.prototype.next" {
1793+
let this_v = this_val.clone().unwrap_or(Value::Undefined);
1794+
if let Value::Object(obj) = this_v {
1795+
return Ok(Some(crate::js_map::handle_map_iterator_next(mc, &obj, env).map_err(EvalError::Js)?));
1796+
} else {
1797+
return Err(EvalError::Js(raise_eval_error!(
1798+
"TypeError: MapIterator.prototype.next called on non-object"
1799+
)));
1800+
}
1801+
}
1802+
1803+
if name.starts_with("Map.") {
1804+
if let Some(method) = name.strip_prefix("Map.prototype.") {
1805+
let this_v = this_val.clone().unwrap_or(Value::Undefined);
1806+
if let Value::Object(obj) = this_v {
1807+
if let Some(map_val) = crate::core::obj_get_key_value(&obj, &"__map__".into()).map_err(EvalError::Js)? {
1808+
if let Value::Map(map_ptr) = &*map_val.borrow() {
1809+
return Ok(Some(
1810+
crate::js_map::handle_map_instance_method(mc, map_ptr, method, args, env).map_err(EvalError::Js)?,
1811+
));
1812+
} else {
1813+
return Err(EvalError::Js(raise_eval_error!(
1814+
"TypeError: Map.prototype method called on incompatible receiver"
1815+
)));
1816+
}
1817+
} else {
1818+
return Err(EvalError::Js(raise_eval_error!(
1819+
"TypeError: Map.prototype method called on incompatible receiver"
1820+
)));
1821+
}
1822+
} else if let Value::Map(map_ptr) = this_v {
1823+
return Ok(Some(
1824+
crate::js_map::handle_map_instance_method(mc, &map_ptr, method, args, env).map_err(EvalError::Js)?,
1825+
));
1826+
} else {
1827+
return Err(EvalError::Js(raise_eval_error!(
1828+
"TypeError: Map.prototype method called on non-object receiver"
1829+
)));
1830+
}
1831+
}
1832+
}
1833+
Ok(None)
1834+
}
1835+
17531836
fn call_accessor<'gc>(
17541837
mc: &MutationContext<'gc>,
1838+
env: &JSObjectDataPtr<'gc>,
17551839
receiver: &JSObjectDataPtr<'gc>,
17561840
accessor: &Value<'gc>,
17571841
) -> Result<Value<'gc>, EvalError<'gc>> {
17581842
match accessor {
1843+
Value::Function(name) => {
1844+
if let Some(res) = call_native_function(mc, name, Some(Value::Object(*receiver)), &[], env)? {
1845+
Ok(res)
1846+
} else {
1847+
Err(EvalError::Js(crate::raise_type_error!(format!(
1848+
"Accessor function {} not supported",
1849+
name
1850+
))))
1851+
}
1852+
}
17591853
Value::Getter(body, captured_env, _) => {
17601854
let call_env = crate::core::new_js_object_data(mc);
17611855
call_env.borrow_mut(mc).prototype = Some(*captured_env);

src/core/mod.rs

Lines changed: 4 additions & 14 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_json::initialize_json;
7+
use crate::js_map::initialize_map;
78
use crate::js_math::initialize_math;
89
use crate::js_number::initialize_number_module;
910
use crate::js_regexp::initialize_regexp;
@@ -84,6 +85,7 @@ pub fn initialize_global_constructors<'gc>(mc: &MutationContext<'gc>, env: &JSOb
8485
initialize_date(mc, env)?;
8586
initialize_bigint(mc, env)?;
8687
initialize_json(mc, env)?;
88+
initialize_map(mc, env)?;
8789

8890
env_set(mc, env, "undefined", Value::Undefined)?;
8991
env_set(mc, env, "NaN", Value::Number(f64::NAN))?;
@@ -208,14 +210,7 @@ pub fn set_internal_prototype_from_constructor<'gc>(
208210

209211
// Helper to initialize a collection from an iterable argument.
210212
// Used by Map, Set, WeakMap, WeakSet constructors.
211-
#[allow(dead_code)]
212-
pub fn initialize_collection_from_iterable<'gc, F>(
213-
mc: &MutationContext<'gc>,
214-
args: &[Expr],
215-
env: &JSObjectDataPtr<'gc>,
216-
constructor_name: &str,
217-
mut process_item: F,
218-
) -> Result<(), JSError>
213+
pub fn initialize_collection_from_iterable<'gc, F>(args: &[Value<'gc>], constructor_name: &str, mut process_item: F) -> Result<(), JSError>
219214
where
220215
F: FnMut(Value<'gc>) -> Result<(), JSError>,
221216
{
@@ -226,12 +221,7 @@ where
226221
let msg = format!("{constructor_name} constructor takes at most one argument",);
227222
return Err(raise_eval_error!(msg));
228223
}
229-
let iterable = evaluate_expr(mc, env, &args[0]).map_err(|e| match e {
230-
crate::core::js_error::EvalError::Js(e) => e,
231-
crate::core::js_error::EvalError::Throw(val, _, _) => {
232-
crate::raise_eval_error!(format!("Uncaught exception: {:?}", val))
233-
}
234-
})?;
224+
let iterable = args[0].clone();
235225
match iterable {
236226
Value::Object(obj) => {
237227
let mut i = 0;

0 commit comments

Comments
 (0)