Skip to content

Commit 7f67d71

Browse files
committed
Implement ES6 export parsing and evaluation
- Implement parse_export_statement and Token::Export support in parser - Add StatementKind::Export evaluation logic in eval.rs - Implement export_value helper to bind exports to module objects - Implement StatementKind::Import with proper module base path resolution - Implement LogicalAnd and LogicalOr operators in evaluate_expr - Enhance load_module to support relative paths using __script_name
1 parent a17a9ab commit 7f67d71

File tree

12 files changed

+559
-43
lines changed

12 files changed

+559
-43
lines changed

js-scripts/es6_module_export.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use strict";
2+
3+
// Test module
4+
const PI = 3.14159;
5+
const E = 2.71828;
6+
7+
function add(a, b) {
8+
return a + b;
9+
}
10+
11+
function multiply(a, b) {
12+
return a * b;
13+
}
14+
15+
export { PI, E, add };
16+
export default multiply;

js-scripts/es6_module_test_01.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
3+
// import("math").then(module => {
4+
// return module.PI + module.E;
5+
// })
6+
7+
import { PI, E } from "math";
8+
import identity from "math";
9+
console.log(PI + E);
10+
console.log(identity);
11+
console.log(typeof identity);
12+
console.log(identity(PI + E));
13+
14+
true;

js-scripts/es6_module_test_02.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"use strict";
2+
3+
import * as os from "os";
4+
import {log} from "console";
5+
6+
// Call some OS functions
7+
let cwd = os.getcwd();
8+
let pid = os.getpid();
9+
let ppid = os.getppid();
10+
11+
// Call some path functions
12+
let basename = os.path.basename("/home/user/test.js");
13+
let dirname = os.path.dirname("/home/user/test.js");
14+
let joined = os.path.join("home", "user", "test.js");
15+
16+
// Log the results
17+
log("Current working directory:", cwd);
18+
log("Process ID:", pid);
19+
log("Parent Process ID:", ppid);
20+
console.log("Basename of /home/user/test.js:", basename);
21+
console.log("Dirname of /home/user/test.js:", dirname);
22+
console.log("Joined path:", joined);
23+
24+
// Return a success value
25+
42

js-scripts/es6_module_test_03.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use strict";
2+
3+
import { PI, E, add } from "./es6_module_export.js";
4+
import multiply from "./es6_module_export.js";
5+
6+
// Test imported values
7+
console.log("PI:", PI);
8+
console.log("E:", E);
9+
console.log("add(3, 4):", add(3, 4));
10+
console.log("multiply(3, 4):", multiply(3, 4));
11+
12+
// Verify values
13+
let pi_ok = Math.abs(PI - 3.14159) < 0.0001;
14+
let e_ok = Math.abs(E - 2.71828) < 0.0001;
15+
let add_ok = add(3, 4) === 7;
16+
let multiply_ok = multiply(3, 4) === 12;
17+
18+
return pi_ok && e_ok && add_ok && multiply_ok;

src/core/eval.rs

Lines changed: 210 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use crate::js_string::{string_from_char_code, string_from_code_point, string_raw
99
use crate::{
1010
JSError, JSErrorKind, PropertyKey, Value,
1111
core::{
12-
BinaryOp, ClosureData, DestructuringElement, EvalError, Expr, JSObjectDataPtr, ObjectDestructuringElement, Statement,
13-
StatementKind, create_error, env_get, env_set, env_set_recursive, is_error, new_js_object_data, obj_get_key_value,
14-
obj_set_key_value, value_to_string,
12+
BinaryOp, ClosureData, DestructuringElement, EvalError, ExportSpecifier, Expr, ImportSpecifier, JSObjectDataPtr,
13+
ObjectDestructuringElement, Statement, StatementKind, create_error, env_get, env_set, env_set_recursive, is_error,
14+
new_js_object_data, obj_get_key_value, obj_set_key_value, value_to_string,
1515
},
1616
js_math::handle_math_call,
1717
raise_eval_error, raise_reference_error,
@@ -169,6 +169,22 @@ fn hoist_declarations<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>
169169
StatementKind::Class(name, ..) => {
170170
env_set(mc, env, name, Value::Uninitialized)?;
171171
}
172+
StatementKind::Import(specifiers, _) => {
173+
for spec in specifiers {
174+
match spec {
175+
ImportSpecifier::Default(name) => {
176+
env_set(mc, env, name, Value::Uninitialized)?;
177+
}
178+
ImportSpecifier::Named(name, alias) => {
179+
let binding_name = alias.as_ref().unwrap_or(name);
180+
env_set(mc, env, binding_name, Value::Uninitialized)?;
181+
}
182+
ImportSpecifier::Namespace(name) => {
183+
env_set(mc, env, name, Value::Uninitialized)?;
184+
}
185+
}
186+
}
187+
}
172188
StatementKind::LetDestructuringArray(pattern, _) | StatementKind::ConstDestructuringArray(pattern, _) => {
173189
let mut names = Vec::new();
174190
collect_names_from_destructuring(pattern, &mut names);
@@ -280,6 +296,123 @@ fn eval_res<'gc>(
280296
*last_value = Value::Undefined;
281297
Ok(None)
282298
}
299+
StatementKind::Import(specifiers, source) => {
300+
// Try to deduce base path from env or use current dir
301+
let base_path = if let Some(cell) = env_get(env, "__script_name") {
302+
if let Value::String(s) = cell.borrow().clone() {
303+
Some(crate::unicode::utf16_to_utf8(&s))
304+
} else {
305+
None
306+
}
307+
} else {
308+
None
309+
};
310+
311+
let exports = crate::js_module::load_module(mc, source, base_path.as_deref())
312+
.map_err(|e| EvalError::Throw(Value::String(utf8_to_utf16(&e.message())), Some(stmt.line), Some(stmt.column)))?;
313+
314+
if let Value::Object(exports_obj) = exports {
315+
for spec in specifiers {
316+
match spec {
317+
ImportSpecifier::Named(name, alias) => {
318+
let binding_name = alias.as_ref().unwrap_or(name);
319+
320+
let val_ptr_res = obj_get_key_value(&exports_obj, &name.into());
321+
let val = if let Ok(Some(cell)) = val_ptr_res {
322+
cell.borrow().clone()
323+
} else {
324+
Value::Undefined
325+
};
326+
env_set(mc, env, binding_name, val)?;
327+
}
328+
ImportSpecifier::Default(name) => {
329+
let val_ptr_res = obj_get_key_value(&exports_obj, &"default".into());
330+
let val = if let Ok(Some(cell)) = val_ptr_res {
331+
cell.borrow().clone()
332+
} else {
333+
Value::Undefined
334+
};
335+
env_set(mc, env, name, val)?;
336+
}
337+
ImportSpecifier::Namespace(name) => {
338+
env_set(mc, env, name, Value::Object(exports_obj))?;
339+
}
340+
}
341+
}
342+
}
343+
*last_value = Value::Undefined;
344+
Ok(None)
345+
}
346+
StatementKind::Export(specifiers, inner_stmt) => {
347+
// 1. Evaluate inner statement if present, to bind variables in current env
348+
if let Some(stmt) = inner_stmt {
349+
// Recursively evaluate inner statement
350+
// Note: inner_stmt is a Box<Statement>. We need to call eval_res or evaluate_statements on it.
351+
// Since evaluate_statements expects a slice, we can wrap it.
352+
let mut stmts = vec![*stmt.clone()];
353+
match evaluate_statements(mc, env, &mut stmts) {
354+
Ok(_) => {} // Declarations are hoisted or executed, binding should be in env
355+
Err(e) => return Err(e),
356+
}
357+
358+
// If inner stmt was a declaration, we need to export the declared names.
359+
// For now, we handle named exports via specifiers only for `export { ... }`.
360+
// For `export var x = 1`, the parser should have produced specifiers?
361+
// My parser implementation for export var/function didn't produce specifiers, just inner_stmt.
362+
// So we need to look at inner_stmt kind to determine what to export.
363+
364+
match &stmt.kind {
365+
StatementKind::Var(decls) | StatementKind::Let(decls) => {
366+
for (name, _) in decls {
367+
if let Some(cell) = env_get(env, name) {
368+
let val = cell.borrow().clone();
369+
crate::core::eval::export_value(mc, env, name, val)?;
370+
}
371+
}
372+
}
373+
StatementKind::Const(decls) => {
374+
for (name, _) in decls {
375+
if let Some(cell) = env_get(env, name) {
376+
let val = cell.borrow().clone();
377+
crate::core::eval::export_value(mc, env, name, val)?;
378+
}
379+
}
380+
}
381+
StatementKind::FunctionDeclaration(name, _, _, _) => {
382+
if let Some(cell) = env_get(env, name) {
383+
let val = cell.borrow().clone();
384+
crate::core::eval::export_value(mc, env, name, val)?;
385+
}
386+
}
387+
_ => {}
388+
}
389+
}
390+
391+
// 2. Handle explicit specifiers
392+
for spec in specifiers {
393+
match spec {
394+
crate::core::statement::ExportSpecifier::Named(name, alias) => {
395+
// export { name as alias }
396+
// value should be in env
397+
if let Some(cell) = env_get(env, name) {
398+
let val = cell.borrow().clone();
399+
let export_name = alias.as_ref().unwrap_or(name);
400+
crate::core::eval::export_value(mc, env, export_name, val)?;
401+
} else {
402+
return Err(EvalError::Js(raise_reference_error!(format!("{} is not defined", name))));
403+
}
404+
}
405+
crate::core::statement::ExportSpecifier::Default(expr) => {
406+
// export default expr
407+
let val = evaluate_expr(mc, env, expr)?;
408+
crate::core::eval::export_value(mc, env, "default", val)?;
409+
}
410+
}
411+
}
412+
413+
*last_value = Value::Undefined;
414+
Ok(None)
415+
}
283416
StatementKind::Return(expr_opt) => {
284417
let val = if let Some(expr) = expr_opt {
285418
match evaluate_expr(mc, env, expr) {
@@ -442,6 +575,28 @@ fn eval_res<'gc>(
442575
}
443576
}
444577

578+
pub fn export_value<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>, name: &str, val: Value<'gc>) -> Result<(), EvalError<'gc>> {
579+
if let Some(exports_cell) = env_get(env, "exports") {
580+
let exports = exports_cell.borrow().clone();
581+
if let Value::Object(exports_obj) = exports {
582+
obj_set_key_value(mc, &exports_obj, &name.into(), val).map_err(|e| EvalError::Js(e))?;
583+
return Ok(());
584+
}
585+
}
586+
587+
if let Some(module_cell) = env_get(env, "module") {
588+
let module = module_cell.borrow().clone();
589+
if let Value::Object(module_obj) = module {
590+
if let Ok(Some(exports_val)) = obj_get_key_value(&module_obj, &"exports".into()) {
591+
if let Value::Object(exports_obj) = &*exports_val.borrow() {
592+
obj_set_key_value(mc, exports_obj, &name.into(), val).map_err(|e| EvalError::Js(e))?;
593+
}
594+
}
595+
}
596+
}
597+
Ok(())
598+
}
599+
445600
fn refresh_error_by_additional_stack_frame<'gc>(
446601
mc: &MutationContext<'gc>,
447602
env: &JSObjectDataPtr<'gc>,
@@ -987,7 +1142,24 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
9871142
Err(EvalError::Js(raise_eval_error!("Not a function")))
9881143
}
9891144
}
990-
_ => Err(EvalError::Js(raise_eval_error!("Not a function"))),
1145+
Value::Closure(cl) => {
1146+
let call_env = crate::core::new_js_object_data(mc);
1147+
call_env.borrow_mut(mc).prototype = Some(cl.env);
1148+
call_env.borrow_mut(mc).is_function_scope = true;
1149+
1150+
for (i, param) in cl.params.iter().enumerate() {
1151+
if let DestructuringElement::Variable(name, _) = param {
1152+
let arg_val = eval_args.get(i).cloned().unwrap_or(Value::Undefined);
1153+
env_set(mc, &call_env, name, arg_val)?;
1154+
}
1155+
}
1156+
let mut body_clone = cl.body.clone();
1157+
match evaluate_statements(mc, &call_env, &mut body_clone) {
1158+
Ok(v) => Ok(v),
1159+
Err(e) => Err(e),
1160+
}
1161+
}
1162+
_ => Err(EvalError::Js(raise_eval_error!(format!("Value {func_val:?} is not callable yet")))),
9911163
}
9921164
}
9931165
Expr::New(ctor, args) => {
@@ -1180,6 +1352,40 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
11801352
};
11811353
Ok(Value::String(utf8_to_utf16(type_str)))
11821354
}
1355+
Expr::LogicalAnd(left, right) => {
1356+
let lhs = evaluate_expr(mc, env, left)?;
1357+
let is_truthy = match &lhs {
1358+
Value::Boolean(b) => *b,
1359+
Value::Number(n) => *n != 0.0 && !n.is_nan(),
1360+
Value::String(s) => !s.is_empty(),
1361+
Value::Null | Value::Undefined => false,
1362+
Value::Object(_)
1363+
| Value::Function(_)
1364+
| Value::Closure(_)
1365+
| Value::AsyncClosure(_)
1366+
| Value::GeneratorFunction(..)
1367+
| Value::ClassDefinition(_) => true,
1368+
_ => false,
1369+
};
1370+
if !is_truthy { Ok(lhs) } else { evaluate_expr(mc, env, right) }
1371+
}
1372+
Expr::LogicalOr(left, right) => {
1373+
let lhs = evaluate_expr(mc, env, left)?;
1374+
let is_truthy = match &lhs {
1375+
Value::Boolean(b) => *b,
1376+
Value::Number(n) => *n != 0.0 && !n.is_nan(),
1377+
Value::String(s) => !s.is_empty(),
1378+
Value::Null | Value::Undefined => false,
1379+
Value::Object(_)
1380+
| Value::Function(_)
1381+
| Value::Closure(_)
1382+
| Value::AsyncClosure(_)
1383+
| Value::GeneratorFunction(..)
1384+
| Value::ClassDefinition(_) => true,
1385+
_ => false,
1386+
};
1387+
if is_truthy { Ok(lhs) } else { evaluate_expr(mc, env, right) }
1388+
}
11831389
_ => Ok(Value::Undefined),
11841390
}
11851391
}

src/core/gc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ pub fn trace_expr<'gc, T: GcTrace<'gc>>(context: &mut T, expr: &Expr) {
3737
trace_expr(context, arg);
3838
}
3939
}
40+
Expr::DynamicImport(a) => {
41+
trace_expr(context, a);
42+
}
4043
Expr::Function(_, _, body) => {
4144
for stmt in body {
4245
trace_stmt(context, stmt);

src/core/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,10 @@ where
120120
env_set(mc, &root.global_env, "globalThis", Value::Object(root.global_env))?;
121121
if let Some(p) = script_path.as_ref() {
122122
let p_str = p.as_ref().to_string_lossy().to_string();
123+
// Store __filename
123124
obj_set_key_value(mc, &root.global_env, &"__filename".into(), Value::String(utf8_to_utf16(&p_str)))?;
125+
// Store __script_name for import resolution
126+
obj_set_key_value(mc, &root.global_env, &"__script_name".into(), Value::String(utf8_to_utf16(&p_str)))?;
124127
}
125128
match evaluate_statements(mc, &root.global_env, &mut statements) {
126129
Ok(result) => Ok(value_to_string(&result)),

0 commit comments

Comments
 (0)