Skip to content

Commit 227fa2f

Browse files
committed
Refactor native function signatures and implement object accessors
- Change native helper functions (Global, Object, etc.) to accept `&[Value]` instead of `&[Expr]`. - Centralize argument evaluation in the main interpreter loop. - Implement `Expr::Getter` and `Expr::Setter` evaluation in `src/core/eval.rs`. - Update accessor invocation logic to handle Function objects by extracting `__closure__`. - Enable `js_function` module in `src/lib.rs`.
1 parent d2c49e5 commit 227fa2f

File tree

9 files changed

+550
-343
lines changed

9 files changed

+550
-343
lines changed

js-scripts/object_tests_03.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use strict";
2+
13
function assert(condition, message) {
24
if (!condition) {
35
throw new Error(message);
@@ -77,8 +79,10 @@ function assert(condition, message) {
7779

7880
const rand = new Person("Rand McKinnon", 33, "M");
7981
const ken = new Person("Ken Jones", 39, "M");
82+
console.log("ken:", ken);
8083

8184
const car1 = new Car("Eagle", "Talon TSi", 1993, rand);
85+
console.log("car1:", car1);
8286
const car2 = new Car("Nissan", "300ZX", 1992, ken);
8387

8488
console.log(car2.owner.name);
@@ -166,7 +170,7 @@ function assert(condition, message) {
166170
console.log("=== Testing difference between dot and bracket notation ===");
167171

168172
const myObj = {};
169-
str = "myString";
173+
let str = "myString";
170174
myObj[str] = "This key is in variable str";
171175

172176
console.log(myObj.str); // undefined
@@ -442,3 +446,5 @@ function assert(condition, message) {
442446
console.log(anotherFruit); // { name: "grape" }; not { name: "apple" }
443447
assert(anotherFruit.name === "grape", 'anotherFruit.name should be "grape"');
444448
}
449+
450+
true;

src/core/eval.rs

Lines changed: 172 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,8 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
11461146
let key_val_res = evaluate_expr(mc, env, key_expr)?;
11471147

11481148
let key = match key_val_res {
1149+
Value::String(s) => PropertyKey::String(utf16_to_utf8(&s)),
1150+
Value::Number(n) => PropertyKey::String(n.to_string()),
11491151
Value::Symbol(s) => PropertyKey::Symbol(s),
11501152
_ => PropertyKey::from(value_to_string(&key_val_res)),
11511153
};
@@ -1704,15 +1706,19 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
17041706
if let Some(method) = name.strip_prefix("Object.prototype.") {
17051707
let this_v = this_val.clone().unwrap_or(Value::Undefined);
17061708
match method {
1707-
"valueOf" => Ok(crate::js_object::handle_value_of_method(mc, &this_v, args, env).map_err(EvalError::Js)?),
1708-
"toString" => Ok(crate::js_object::handle_to_string_method(mc, &this_v, args, env).map_err(EvalError::Js)?),
1709+
"valueOf" => {
1710+
Ok(crate::js_object::handle_value_of_method(mc, &this_v, &eval_args, env).map_err(EvalError::Js)?)
1711+
}
1712+
"toString" => {
1713+
Ok(crate::js_object::handle_to_string_method(mc, &this_v, &eval_args, env).map_err(EvalError::Js)?)
1714+
}
17091715
"toLocaleString" => {
1710-
Ok(crate::js_object::handle_to_string_method(mc, &this_v, args, env).map_err(EvalError::Js)?)
1716+
Ok(crate::js_object::handle_to_string_method(mc, &this_v, &eval_args, env).map_err(EvalError::Js)?)
17111717
}
17121718
"hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable" => {
17131719
// Need object wrapper
17141720
if let Value::Object(o) = this_v {
1715-
let res_opt = crate::js_object::handle_object_prototype_builtin(mc, &name, &o, args, env)
1721+
let res_opt = crate::js_object::handle_object_prototype_builtin(mc, &name, &o, &eval_args, env)
17161722
.map_err(EvalError::Js)?;
17171723
Ok(res_opt.unwrap_or(Value::Undefined))
17181724
} else {
@@ -1725,7 +1731,7 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
17251731
}
17261732
} else {
17271733
let method = &name[7..];
1728-
Ok(crate::js_object::handle_object_method(mc, method, args, env).map_err(EvalError::Js)?)
1734+
Ok(crate::js_object::handle_object_method(mc, method, &eval_args, env).map_err(EvalError::Js)?)
17291735
}
17301736
} else if name.starts_with("Array.") {
17311737
if let Some(method) = name.strip_prefix("Array.prototype.") {
@@ -2078,11 +2084,10 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
20782084
}
20792085
}
20802086
let mut body_clone = cl.body.clone();
2081-
let result = evaluate_statements(mc, &call_env, &mut body_clone)?;
2082-
if let Value::Object(_) = result {
2083-
Ok(result)
2084-
} else {
2085-
Ok(Value::Object(instance))
2087+
match evaluate_statements_with_labels(mc, &call_env, &mut body_clone, &[], &[])? {
2088+
ControlFlow::Return(Value::Object(obj)) => Ok(Value::Object(obj)),
2089+
ControlFlow::Throw(val, line, col) => Err(EvalError::Throw(val, line, col)),
2090+
_ => Ok(Value::Object(instance)),
20862091
}
20872092
}
20882093
_ => Err(EvalError::Js(raise_eval_error!("Not a constructor"))),
@@ -2330,6 +2335,143 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
23302335
Ok(crate::js_class::evaluate_super_method(mc, env, prop, &eval_args).map_err(EvalError::Js)?)
23312336
}
23322337
Expr::Super => Ok(crate::js_class::evaluate_super(mc, env).map_err(EvalError::Js)?),
2338+
Expr::OptionalProperty(lhs, prop) => {
2339+
let left_val = evaluate_expr(mc, env, lhs)?;
2340+
if left_val.is_null_or_undefined() {
2341+
Ok(Value::Undefined)
2342+
} else {
2343+
if let Value::Object(obj) = &left_val {
2344+
if let Some(val_rc) = crate::core::obj_get_key_value(&obj, &prop.into()).map_err(EvalError::Js)? {
2345+
Ok(val_rc.borrow().clone())
2346+
} else {
2347+
Ok(Value::Undefined)
2348+
}
2349+
} else {
2350+
get_primitive_prototype_property(mc, env, &left_val, &prop.into())
2351+
}
2352+
}
2353+
}
2354+
Expr::OptionalIndex(lhs, index_expr) => {
2355+
let left_val = evaluate_expr(mc, env, lhs)?;
2356+
if left_val.is_null_or_undefined() {
2357+
Ok(Value::Undefined)
2358+
} else {
2359+
let index_val = evaluate_expr(mc, env, index_expr)?;
2360+
let prop_key = match index_val {
2361+
Value::Symbol(s) => crate::core::PropertyKey::Symbol(s),
2362+
Value::String(s) => crate::core::PropertyKey::String(crate::unicode::utf16_to_utf8(&s)),
2363+
val => {
2364+
let s = match val {
2365+
Value::Number(n) => n.to_string(),
2366+
Value::Boolean(b) => b.to_string(),
2367+
Value::Undefined => "undefined".to_string(),
2368+
Value::Null => "null".to_string(),
2369+
_ => crate::core::value_to_string(&val),
2370+
};
2371+
crate::core::PropertyKey::String(s)
2372+
}
2373+
};
2374+
if let Value::Object(obj) = &left_val {
2375+
if let Some(val_rc) = crate::core::obj_get_key_value(&obj, &prop_key).map_err(EvalError::Js)? {
2376+
Ok(val_rc.borrow().clone())
2377+
} else {
2378+
Ok(Value::Undefined)
2379+
}
2380+
} else {
2381+
get_primitive_prototype_property(mc, env, &left_val, &prop_key)
2382+
}
2383+
}
2384+
}
2385+
Expr::OptionalCall(lhs, args) => {
2386+
let left_val = evaluate_expr(mc, env, lhs)?;
2387+
if left_val.is_null_or_undefined() {
2388+
Ok(Value::Undefined)
2389+
} else {
2390+
let mut eval_args = Vec::new();
2391+
for arg in args {
2392+
eval_args.push(evaluate_expr(mc, env, arg)?);
2393+
}
2394+
match left_val {
2395+
Value::Function(name) => {
2396+
crate::js_function::handle_global_function(mc, &name, &eval_args, &env.clone()).map_err(EvalError::Js)
2397+
}
2398+
Value::Closure(c) => call_closure(mc, &c, None, &eval_args, env),
2399+
_ => Err(EvalError::Js(crate::raise_type_error!("OptionalCall target is not a function"))),
2400+
}
2401+
}
2402+
}
2403+
Expr::Delete(target) => match &**target {
2404+
Expr::Property(obj_expr, key) => {
2405+
let obj_val = evaluate_expr(mc, env, obj_expr)?;
2406+
if let Value::Object(obj) = obj_val {
2407+
let key_val = PropertyKey::from(key.to_string());
2408+
let removed = obj.borrow_mut(mc).properties.shift_remove(&key_val).is_some();
2409+
Ok(Value::Boolean(removed))
2410+
} else {
2411+
Ok(Value::Boolean(true))
2412+
}
2413+
}
2414+
Expr::Index(obj_expr, key_expr) => {
2415+
let obj_val = evaluate_expr(mc, env, obj_expr)?;
2416+
let key_val_res = evaluate_expr(mc, env, key_expr)?;
2417+
let key = match key_val_res {
2418+
Value::String(s) => PropertyKey::String(utf16_to_utf8(&s)),
2419+
Value::Number(n) => PropertyKey::String(n.to_string()),
2420+
Value::Symbol(s) => PropertyKey::Symbol(s),
2421+
_ => PropertyKey::from(value_to_string(&key_val_res)),
2422+
};
2423+
if let Value::Object(obj) = obj_val {
2424+
let removed = obj.borrow_mut(mc).properties.shift_remove(&key).is_some();
2425+
Ok(Value::Boolean(removed))
2426+
} else {
2427+
Ok(Value::Boolean(true))
2428+
}
2429+
}
2430+
Expr::Var(_name, _, _) => Ok(Value::Boolean(false)),
2431+
_ => Ok(Value::Boolean(true)),
2432+
},
2433+
Expr::Getter(func_expr) => {
2434+
let val = evaluate_expr(mc, env, func_expr)?;
2435+
let closure = match &val {
2436+
Value::Object(obj) => {
2437+
let c_val = obj_get_key_value(obj, &"__closure__".into())?;
2438+
if let Some(c_ptr) = c_val {
2439+
let c_ref = c_ptr.borrow();
2440+
if let Value::Closure(c) = &*c_ref {
2441+
*c
2442+
} else {
2443+
panic!("Getter function missing __closure__ (not a closure)");
2444+
}
2445+
} else {
2446+
panic!("Getter function missing __closure__");
2447+
}
2448+
}
2449+
Value::Closure(c) => *c,
2450+
_ => panic!("Expr::Getter evaluated to invalid value: {:?}", val),
2451+
};
2452+
Ok(Value::Getter(closure.body.clone(), closure.env, None))
2453+
}
2454+
Expr::Setter(func_expr) => {
2455+
let val = evaluate_expr(mc, env, func_expr)?;
2456+
let closure = match &val {
2457+
Value::Object(obj) => {
2458+
let c_val = obj_get_key_value(obj, &"__closure__".into())?;
2459+
if let Some(c_ptr) = c_val {
2460+
let c_ref = c_ptr.borrow();
2461+
if let Value::Closure(c) = &*c_ref {
2462+
*c
2463+
} else {
2464+
panic!("Setter function missing __closure__ (not a closure)");
2465+
}
2466+
} else {
2467+
panic!("Setter function missing __closure__");
2468+
}
2469+
}
2470+
Value::Closure(c) => *c,
2471+
_ => panic!("Expr::Setter evaluated to invalid value: {:?}", val),
2472+
};
2473+
Ok(Value::Setter(closure.params.clone(), closure.body.clone(), closure.env, None))
2474+
}
23332475
_ => todo!("{expr:?}"),
23342476
}
23352477
}
@@ -2605,6 +2747,16 @@ fn call_accessor<'gc>(
26052747
let mut body_clone = cl_data.body.clone();
26062748
evaluate_statements(mc, &call_env, &mut body_clone)
26072749
}
2750+
Value::Object(obj) => {
2751+
// Check for __closure__
2752+
let cl_val_opt = crate::core::obj_get_key_value(obj, &"__closure__".into()).map_err(EvalError::Js)?;
2753+
if let Some(cl_val) = cl_val_opt {
2754+
if let Value::Closure(cl) = &*cl_val.borrow() {
2755+
return call_accessor(mc, env, receiver, &Value::Closure(*cl));
2756+
}
2757+
}
2758+
Err(EvalError::Js(crate::raise_type_error!("Accessor is not a function")))
2759+
}
26082760
_ => Err(EvalError::Js(crate::raise_type_error!("Accessor is not a function"))),
26092761
}
26102762
}
@@ -2632,6 +2784,16 @@ fn call_setter<'gc>(
26322784
let mut body_clone = cl_data.body.clone();
26332785
evaluate_statements(mc, &call_env, &mut body_clone).map(|_| ())
26342786
}
2787+
Value::Object(obj) => {
2788+
// Check for __closure__
2789+
let cl_val_opt = crate::core::obj_get_key_value(obj, &"__closure__".into()).map_err(EvalError::Js)?;
2790+
if let Some(cl_val) = cl_val_opt {
2791+
if let Value::Closure(cl) = &*cl_val.borrow() {
2792+
return call_setter(mc, receiver, &Value::Closure(*cl), val);
2793+
}
2794+
}
2795+
Err(EvalError::Js(crate::raise_type_error!("Setter is not a function")))
2796+
}
26352797
_ => Err(EvalError::Js(crate::raise_type_error!("Setter is not a function"))),
26362798
}
26372799
}

src/core/parser.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,14 +2084,14 @@ fn parse_primary(tokens: &[TokenData], index: &mut usize, allow_call: bool) -> R
20842084
// regular property named 'get'/'set' (e.g. `set: function(...)`) with
20852085
// the getter/setter syntax.
20862086
// Recognize getter/setter signatures including computed keys
2087-
let is_getter = if tokens.len() >= 2 && matches!(tokens[*index].token, Token::Identifier(ref id) if id == "get") {
2088-
if matches!(tokens[1].token, Token::Identifier(_) | Token::StringLit(_)) {
2089-
tokens.len() >= 3 && matches!(tokens[2].token, Token::LParen)
2090-
} else if matches!(tokens[1].token, Token::LBracket) {
2087+
let is_getter = if tokens.len() > *index + 1 && matches!(tokens[*index].token, Token::Identifier(ref id) if id == "get") {
2088+
if matches!(tokens[*index + 1].token, Token::Identifier(_) | Token::StringLit(_)) {
2089+
tokens.len() > *index + 2 && matches!(tokens[*index + 2].token, Token::LParen)
2090+
} else if matches!(tokens[*index + 1].token, Token::LBracket) {
20912091
// find matching RBracket and ensure '(' follows
20922092
let mut depth = 0i32;
20932093
let mut idx_after = None;
2094-
for (i, t) in tokens.iter().enumerate().skip(1) {
2094+
for (i, t) in tokens.iter().enumerate().skip(*index + 1) {
20952095
match &t.token {
20962096
Token::LBracket => depth += 1,
20972097
Token::RBracket => {
@@ -2116,13 +2116,13 @@ fn parse_primary(tokens: &[TokenData], index: &mut usize, allow_call: bool) -> R
21162116
false
21172117
};
21182118

2119-
let is_setter = if tokens.len() >= 2 && matches!(tokens[*index].token, Token::Identifier(ref id) if id == "set") {
2120-
if matches!(tokens[1].token, Token::Identifier(_) | Token::StringLit(_)) {
2121-
tokens.len() >= 3 && matches!(tokens[2].token, Token::LParen)
2122-
} else if matches!(tokens[1].token, Token::LBracket) {
2119+
let is_setter = if tokens.len() > *index + 1 && matches!(tokens[*index].token, Token::Identifier(ref id) if id == "set") {
2120+
if matches!(tokens[*index + 1].token, Token::Identifier(_) | Token::StringLit(_)) {
2121+
tokens.len() > *index + 2 && matches!(tokens[*index + 2].token, Token::LParen)
2122+
} else if matches!(tokens[*index + 1].token, Token::LBracket) {
21232123
let mut depth = 0i32;
21242124
let mut idx_after = None;
2125-
for (i, t) in tokens.iter().enumerate().skip(1) {
2125+
for (i, t) in tokens.iter().enumerate().skip(*index + 1) {
21262126
match &t.token {
21272127
Token::LBracket => depth += 1,
21282128
Token::RBracket => {

src/core/value.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,12 @@ pub enum Value<'gc> {
354354
Uninitialized,
355355
}
356356

357+
impl Value<'_> {
358+
pub fn is_null_or_undefined(&self) -> bool {
359+
matches!(self, Value::Null | Value::Undefined)
360+
}
361+
}
362+
357363
impl From<f64> for Value<'_> {
358364
fn from(n: f64) -> Self {
359365
Value::Number(n)

0 commit comments

Comments
 (0)