Skip to content

Commit c7b9771

Browse files
committed
Implement Map and Set constructors with full API support
- Add JSMap and JSSet data structures to Value enum - Implement Map constructor, set/get/has/delete/clear/size/keys/values/entries methods - Implement Set constructor, add/has/delete/clear/size/values/keys/entries methods - Add global Map and Set constructors to environment - Update evaluation logic for property access (.size) and method calls - Add console.log, toString, valueOf, and FFI support for Map/Set - Add comprehensive test suite with 13 test cases
1 parent 43878fa commit c7b9771

File tree

13 files changed

+611
-0
lines changed

13 files changed

+611
-0
lines changed

examples/js.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ fn print_eval_result(result: &Value) {
6767
Value::Promise(_) => println!("[object Promise]"),
6868
Value::Symbol(_) => println!("[object Symbol]"),
6969
Value::BigInt(s) => println!("{s}"),
70+
Value::Map(_) => println!("[object Map]"),
71+
Value::Set(_) => println!("[object Set]"),
7072
}
7173
}
7274

src/core.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,18 @@ pub(crate) fn initialize_global_constructors(env: &JSObjectDataPtr) -> Result<()
489489
Rc::new(RefCell::new(Value::Function("Symbol".to_string()))),
490490
);
491491

492+
// Map constructor
493+
env_borrow.insert(
494+
PropertyKey::String("Map".to_string()),
495+
Rc::new(RefCell::new(Value::Function("Map".to_string()))),
496+
);
497+
498+
// Set constructor
499+
env_borrow.insert(
500+
PropertyKey::String("Set".to_string()),
501+
Rc::new(RefCell::new(Value::Function("Set".to_string()))),
502+
);
503+
492504
// Create a few well-known symbols and store them in the well-known symbol registry
493505
WELL_KNOWN_SYMBOLS.with(|wk| {
494506
let mut map = wk.borrow_mut();

src/core/eval.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,6 +2731,8 @@ fn evaluate_typeof(env: &JSObjectDataPtr, expr: &Expr) -> Result<Value, JSError>
27312731
Value::Property { .. } => "undefined",
27322732
Value::Promise(_) => "object",
27332733
Value::Symbol(_) => "symbol",
2734+
Value::Map(_) => "object",
2735+
Value::Set(_) => "object",
27342736
};
27352737
Ok(Value::String(utf8_to_utf16(type_str)))
27362738
}
@@ -3647,6 +3649,8 @@ fn evaluate_property(env: &JSObjectDataPtr, obj: &Expr, prop: &str) -> Result<Va
36473649
// coerce to a primitive wrapper or return undefined if not found. To
36483650
// keep things simple, return undefined for boolean properties.
36493651
Value::Boolean(_) => Ok(Value::Undefined),
3652+
Value::Map(map) if prop == "size" => Ok(Value::Number(map.borrow().entries.len() as f64)),
3653+
Value::Set(set) if prop == "size" => Ok(Value::Number(set.borrow().values.len() as f64)),
36503654
_ => Err(raise_eval_error!(format!("Property not found for prop={prop}"))),
36513655
}
36523656
}
@@ -3778,6 +3782,8 @@ fn evaluate_call(env: &JSObjectDataPtr, func_expr: &Expr, args: &[Expr]) -> Resu
37783782
// and Object.prototype functions act as fallbacks.
37793783
(Value::Symbol(sd), "toString") => crate::js_object::handle_to_string_method(&Value::Symbol(sd.clone()), args),
37803784
(Value::Symbol(sd), "valueOf") => crate::js_object::handle_value_of_method(&Value::Symbol(sd.clone()), args),
3785+
(Value::Map(map), method) => crate::js_map::handle_map_instance_method(&map, method, args, env),
3786+
(Value::Set(set), method) => crate::js_set::handle_set_instance_method(&set, method, args, env),
37813787
(Value::Object(obj_map), method) => {
37823788
// Object prototype methods are supplied on `Object.prototype`.
37833789
// Lookups will find user-defined (own) methods before inherited

src/core/ffi.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,8 @@ pub unsafe fn JS_Eval(_ctx: *mut JSContext, input: *const i8, input_len: usize,
943943
Ok(Value::Promise(_)) => JS_UNDEFINED, // For now
944944
Ok(Value::Symbol(_)) => JS_UNDEFINED, // For now
945945
Ok(Value::BigInt(_)) => JS_UNDEFINED,
946+
Ok(Value::Map(_)) => JS_UNDEFINED, // For now
947+
Ok(Value::Set(_)) => JS_UNDEFINED, // For now
946948
Err(_) => JS_UNDEFINED,
947949
}
948950
}

src/core/value.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ use crate::{
99
raise_type_error,
1010
};
1111

12+
#[derive(Clone, Debug)]
13+
pub struct JSMap {
14+
pub entries: Vec<(Value, Value)>, // key-value pairs
15+
}
16+
17+
#[derive(Clone, Debug)]
18+
pub struct JSSet {
19+
pub values: Vec<Value>,
20+
}
21+
1222
pub type JSObjectDataPtr = Rc<RefCell<JSObjectData>>;
1323

1424
#[derive(Clone, Default)]
@@ -94,6 +104,8 @@ pub enum Value {
94104
},
95105
Promise(Rc<RefCell<JSPromise>>), // Promise object
96106
Symbol(Rc<SymbolData>), // Symbol primitive with description
107+
Map(Rc<RefCell<JSMap>>), // Map object
108+
Set(Rc<RefCell<JSSet>>), // Set object
97109
}
98110

99111
impl std::fmt::Debug for Value {
@@ -114,6 +126,8 @@ impl std::fmt::Debug for Value {
114126
Value::Property { .. } => write!(f, "Property"),
115127
Value::Promise(p) => write!(f, "Promise({:p})", Rc::as_ptr(p)),
116128
Value::Symbol(_) => write!(f, "Symbol"),
129+
Value::Map(m) => write!(f, "Map({:p})", Rc::as_ptr(m)),
130+
Value::Set(s) => write!(f, "Set({:p})", Rc::as_ptr(s)),
117131
}
118132
}
119133
}
@@ -149,6 +163,8 @@ pub fn is_truthy(val: &Value) -> bool {
149163
Value::Property { .. } => true,
150164
Value::Promise(_) => true,
151165
Value::Symbol(_) => true,
166+
Value::Map(_) => true,
167+
Value::Set(_) => true,
152168
}
153169
}
154170

@@ -187,6 +203,8 @@ pub fn value_to_string(val: &Value) -> String {
187203
Some(d) => format!("Symbol({})", d),
188204
None => "Symbol()".to_string(),
189205
},
206+
Value::Map(_) => "[object Map]".to_string(),
207+
Value::Set(_) => "[object Set]".to_string(),
190208
}
191209
}
192210

@@ -281,6 +299,8 @@ pub fn value_to_sort_string(val: &Value) -> String {
281299
Value::Property { .. } => "[property]".to_string(),
282300
Value::Promise(_) => "[object Promise]".to_string(),
283301
Value::Symbol(_) => "[object Symbol]".to_string(),
302+
Value::Map(_) => "[object Map]".to_string(),
303+
Value::Set(_) => "[object Set]".to_string(),
284304
}
285305
}
286306

src/js_class.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ pub(crate) fn evaluate_new(env: &JSObjectDataPtr, constructor: &Expr, args: &[Ex
168168
"Promise" => {
169169
return crate::js_promise::handle_promise_constructor(args, env);
170170
}
171+
"Map" => return crate::js_map::handle_map_constructor(args, env),
172+
"Set" => return crate::js_set::handle_set_constructor(args, env),
171173
"MockIntlConstructor" => {
172174
// Handle mock Intl constructor for testing
173175
let locale_arg = if !args.is_empty() {
@@ -664,6 +666,8 @@ pub(crate) fn handle_string_constructor(args: &[Expr], env: &JSObjectDataPtr) ->
664666
Value::Promise(_) => utf8_to_utf16("[object Promise]"),
665667
Value::Symbol(_) => utf8_to_utf16("[object Symbol]"),
666668
Value::BigInt(s) => utf8_to_utf16(&s),
669+
Value::Map(_) => utf8_to_utf16("[object Map]"),
670+
Value::Set(_) => utf8_to_utf16("[object Set]"),
667671
}
668672
};
669673

src/js_console.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ pub fn handle_console_method(method: &str, args: &[Expr], env: &JSObjectDataPtr)
105105
}
106106
Value::Promise(_) => print!("[object Promise]"),
107107
Value::Symbol(_) => print!("[object Symbol]"),
108+
Value::Map(_) => print!("[object Map]"),
109+
Value::Set(_) => print!("[object Set]"),
108110
}
109111
if i < count - 1 {
110112
print!(" ");

src/js_function.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ pub fn handle_global_function(func_name: &str, args: &[Expr], env: &JSObjectData
7777
None => Ok(Value::String(utf8_to_utf16("Symbol()"))),
7878
},
7979
Value::BigInt(s) => Ok(Value::String(utf8_to_utf16(&s))),
80+
Value::Map(_) => Ok(Value::String(utf8_to_utf16("[object Map]"))),
81+
Value::Set(_) => Ok(Value::String(utf8_to_utf16("[object Set]"))),
8082
}
8183
} else {
8284
Ok(Value::String(Vec::new())) // String() with no args returns empty string

src/js_map.rs

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
use crate::{
2+
core::{Expr, JSObjectData, JSObjectDataPtr, PropertyKey, Value, evaluate_expr, obj_set_value},
3+
error::JSError,
4+
raise_eval_error,
5+
};
6+
use std::cell::RefCell;
7+
use std::rc::Rc;
8+
9+
use crate::core::JSMap;
10+
11+
/// Handle Map constructor calls
12+
pub(crate) fn handle_map_constructor(args: &[Expr], env: &JSObjectDataPtr) -> Result<Value, JSError> {
13+
let map = Rc::new(RefCell::new(JSMap { entries: Vec::new() }));
14+
15+
if !args.is_empty() {
16+
if args.len() == 1 {
17+
// Map(iterable)
18+
let iterable = evaluate_expr(env, &args[0])?;
19+
match iterable {
20+
Value::Object(obj) => {
21+
// Try to iterate over the object
22+
// For now, assume it's an array-like or has entries
23+
// TODO: Implement proper iteration protocol
24+
let mut i = 0;
25+
loop {
26+
let key = format!("{}", i);
27+
if let Some(entry_val) = obj_get_value(&obj, &key.into())? {
28+
let entry = entry_val.borrow().clone();
29+
if let Value::Object(entry_obj) = entry
30+
&& let (Some(key_val), Some(value_val)) =
31+
(obj_get_value(&entry_obj, &"0".into())?, obj_get_value(&entry_obj, &"1".into())?)
32+
{
33+
map.borrow_mut()
34+
.entries
35+
.push((key_val.borrow().clone(), value_val.borrow().clone()));
36+
}
37+
} else {
38+
break;
39+
}
40+
i += 1;
41+
}
42+
}
43+
_ => {
44+
return Err(raise_eval_error!("Map constructor requires an iterable"));
45+
}
46+
}
47+
} else {
48+
return Err(raise_eval_error!("Map constructor takes at most one argument"));
49+
}
50+
}
51+
52+
Ok(Value::Map(map))
53+
}
54+
55+
/// Handle Map instance method calls
56+
pub(crate) fn handle_map_instance_method(
57+
map: &Rc<RefCell<JSMap>>,
58+
method: &str,
59+
args: &[Expr],
60+
env: &JSObjectDataPtr,
61+
) -> Result<Value, JSError> {
62+
match method {
63+
"set" => {
64+
if args.len() != 2 {
65+
return Err(raise_eval_error!("Map.prototype.set requires exactly two arguments"));
66+
}
67+
let key = evaluate_expr(env, &args[0])?;
68+
let value = evaluate_expr(env, &args[1])?;
69+
70+
// Remove existing entry with same key
71+
map.borrow_mut().entries.retain(|(k, _)| !values_equal(k, &key));
72+
// Add new entry
73+
map.borrow_mut().entries.push((key, value));
74+
75+
Ok(Value::Map(map.clone()))
76+
}
77+
"get" => {
78+
if args.len() != 1 {
79+
return Err(raise_eval_error!("Map.prototype.get requires exactly one argument"));
80+
}
81+
let key = evaluate_expr(env, &args[0])?;
82+
83+
for (k, v) in &map.borrow().entries {
84+
if values_equal(k, &key) {
85+
return Ok(v.clone());
86+
}
87+
}
88+
Ok(Value::Undefined)
89+
}
90+
"has" => {
91+
if args.len() != 1 {
92+
return Err(raise_eval_error!("Map.prototype.has requires exactly one argument"));
93+
}
94+
let key = evaluate_expr(env, &args[0])?;
95+
96+
let has_key = map.borrow().entries.iter().any(|(k, _)| values_equal(k, &key));
97+
Ok(Value::Boolean(has_key))
98+
}
99+
"delete" => {
100+
if args.len() != 1 {
101+
return Err(raise_eval_error!("Map.prototype.delete requires exactly one argument"));
102+
}
103+
let key = evaluate_expr(env, &args[0])?;
104+
105+
let initial_len = map.borrow().entries.len();
106+
map.borrow_mut().entries.retain(|(k, _)| !values_equal(k, &key));
107+
let deleted = map.borrow().entries.len() < initial_len;
108+
109+
Ok(Value::Boolean(deleted))
110+
}
111+
"clear" => {
112+
if !args.is_empty() {
113+
return Err(raise_eval_error!("Map.prototype.clear takes no arguments"));
114+
}
115+
map.borrow_mut().entries.clear();
116+
Ok(Value::Undefined)
117+
}
118+
"size" => {
119+
if !args.is_empty() {
120+
return Err(raise_eval_error!("Map.prototype.size is a getter"));
121+
}
122+
Ok(Value::Number(map.borrow().entries.len() as f64))
123+
}
124+
"keys" => {
125+
if !args.is_empty() {
126+
return Err(raise_eval_error!("Map.prototype.keys takes no arguments"));
127+
}
128+
// Create an array of keys
129+
let keys_array = Rc::new(RefCell::new(JSObjectData::new()));
130+
for (i, (key, _)) in map.borrow().entries.iter().enumerate() {
131+
obj_set_value(&keys_array, &i.to_string().into(), key.clone())?;
132+
}
133+
// Set length
134+
obj_set_value(&keys_array, &"length".into(), Value::Number(map.borrow().entries.len() as f64))?;
135+
Ok(Value::Object(keys_array))
136+
}
137+
"values" => {
138+
if !args.is_empty() {
139+
return Err(raise_eval_error!("Map.prototype.values takes no arguments"));
140+
}
141+
// Create an array of values
142+
let values_array = Rc::new(RefCell::new(JSObjectData::new()));
143+
for (i, (_, value)) in map.borrow().entries.iter().enumerate() {
144+
obj_set_value(&values_array, &i.to_string().into(), value.clone())?;
145+
}
146+
// Set length
147+
obj_set_value(&values_array, &"length".into(), Value::Number(map.borrow().entries.len() as f64))?;
148+
Ok(Value::Object(values_array))
149+
}
150+
"entries" => {
151+
if !args.is_empty() {
152+
return Err(raise_eval_error!("Map.prototype.entries takes no arguments"));
153+
}
154+
// Create an array of [key, value] pairs
155+
let entries_array = Rc::new(RefCell::new(JSObjectData::new()));
156+
for (i, (key, value)) in map.borrow().entries.iter().enumerate() {
157+
let entry_array = Rc::new(RefCell::new(JSObjectData::new()));
158+
obj_set_value(&entry_array, &"0".into(), key.clone())?;
159+
obj_set_value(&entry_array, &"1".into(), value.clone())?;
160+
obj_set_value(&entry_array, &"length".into(), Value::Number(2.0))?;
161+
obj_set_value(&entries_array, &i.to_string().into(), Value::Object(entry_array))?;
162+
}
163+
// Set length
164+
obj_set_value(&entries_array, &"length".into(), Value::Number(map.borrow().entries.len() as f64))?;
165+
Ok(Value::Object(entries_array))
166+
}
167+
_ => Err(raise_eval_error!(format!("Map.prototype.{} is not implemented", method))),
168+
}
169+
}
170+
171+
// Helper function to compare two values for equality (simplified version)
172+
fn values_equal(a: &Value, b: &Value) -> bool {
173+
match (a, b) {
174+
(Value::Number(na), Value::Number(nb)) => na == nb,
175+
(Value::String(sa), Value::String(sb)) => sa == sb,
176+
(Value::Boolean(ba), Value::Boolean(bb)) => ba == bb,
177+
(Value::Undefined, Value::Undefined) => true,
178+
(Value::Symbol(sa), Value::Symbol(sb)) => Rc::ptr_eq(sa, sb),
179+
_ => false, // For objects, we use reference equality
180+
}
181+
}
182+
183+
// Helper function to get object property value
184+
fn obj_get_value(js_obj: &JSObjectDataPtr, key: &PropertyKey) -> Result<Option<Rc<RefCell<Value>>>, JSError> {
185+
let mut current: Option<JSObjectDataPtr> = Some(js_obj.clone());
186+
while let Some(cur) = current {
187+
if let Some(val) = cur.borrow().properties.get(key) {
188+
return Ok(Some(val.clone()));
189+
}
190+
current = cur.borrow().prototype.clone();
191+
}
192+
Ok(None)
193+
}

src/js_object.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<
293293
Value::Property { .. } => "Property",
294294
Value::Promise(_) => "Promise",
295295
Value::Symbol(_) => "Symbol",
296+
Value::Map(_) => "Map",
297+
Value::Set(_) => "Set",
296298
},
297299
args.len()
298300
)));
@@ -363,6 +365,8 @@ pub(crate) fn handle_to_string_method(obj_val: &Value, args: &[Expr]) -> Result<
363365
let desc_str = symbol_data.description.as_deref().unwrap_or("");
364366
Ok(Value::String(utf8_to_utf16(&format!("Symbol({})", desc_str))))
365367
}
368+
Value::Map(_) => Ok(Value::String(utf8_to_utf16("[object Map]"))),
369+
Value::Set(_) => Ok(Value::String(utf8_to_utf16("[object Set]"))),
366370
}
367371
}
368372

@@ -385,6 +389,8 @@ pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr]) -> Result<V
385389
&Value::Promise(_) => "Promise",
386390
Value::BigInt(_) => "BigInt",
387391
Value::Symbol(_) => "Symbol",
392+
Value::Map(_) => "Map",
393+
Value::Set(_) => "Set",
388394
},
389395
args.len()
390396
)));
@@ -421,5 +427,7 @@ pub(crate) fn handle_value_of_method(obj_val: &Value, args: &[Expr]) -> Result<V
421427
}),
422428
Value::Promise(promise) => Ok(Value::Promise(promise.clone())),
423429
Value::Symbol(symbol_data) => Ok(Value::Symbol(symbol_data.clone())),
430+
Value::Map(map) => Ok(Value::Map(map.clone())),
431+
Value::Set(set) => Ok(Value::Set(set.clone())),
424432
}
425433
}

0 commit comments

Comments
 (0)