Skip to content

Commit 098c77e

Browse files
committed
Refactor engine with feature-gated OS and STD modules
- Configure 'os' and 'std' Cargo features for optional standard library support. - Move 'js_std.rs', 'sprintf.rs', and 'tmpfile.rs' into 'src/js_std/' module structure. - Apply conditional compilation guards across 'core', 'eval', and 'lib' modules. - Update 'eval.rs' to dispatch 'os.*' and 'std.*' calls only when features are enabled. - Add 'run-js' Cargo alias for convenient example execution with features. - Enforce feature requirements for the 'js' example in Cargo.toml.
1 parent da61a44 commit 098c77e

File tree

12 files changed

+174
-53
lines changed

12 files changed

+174
-53
lines changed

.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[alias]
2+
run-js = "run --example js --features=os,std --"

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ readme = "README.md"
1212
keywords = ["javascript", "engine", "interpreter", "runtime"]
1313
categories = ["development-tools", "parsing"]
1414

15+
[features]
16+
# default = ["os", "std"]
17+
os = []
18+
std = []
19+
1520
[dependencies]
1621
chrono = { version = "0.4.42", features = ["serde"] }
1722
gc-arena = { git = "https://github.com/kyren/gc-arena.git", rev = "40c2710" }
@@ -39,6 +44,7 @@ rustyline = "17.0.2"
3944
[[example]]
4045
name = "js"
4146
path = "examples/js.rs"
47+
required-features = ["os", "std"]
4248

4349
[[bench]]
4450
name = "promise_benchmarks"

src/core/eval.rs

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::js_array::handle_array_static_method;
55
use crate::js_bigint::bigint_constructor;
66
use crate::js_date::{handle_date_method, handle_date_static_method};
77
use crate::js_number::{handle_number_instance_method, handle_number_prototype_method, handle_number_static_method, number_constructor};
8-
use crate::js_os::handle_os_method;
98
use crate::js_string::{string_from_char_code, string_from_code_point, string_raw};
109
use crate::{
1110
JSError, JSErrorKind, PropertyKey, Value,
@@ -678,6 +677,58 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
678677
evaluate_expr(mc, env, else_expr)
679678
}
680679
}
680+
Expr::Object(properties) => {
681+
let obj = crate::core::new_js_object_data(mc);
682+
if let Some(obj_val) = env_get(env, "Object") {
683+
if let Value::Object(obj_ctor) = &*obj_val.borrow() {
684+
if let Some(proto_val) = obj_get_key_value(obj_ctor, &"prototype".into())? {
685+
if let Value::Object(proto) = &*proto_val.borrow() {
686+
obj.borrow_mut(mc).prototype = Some(*proto);
687+
}
688+
}
689+
}
690+
}
691+
692+
for (key_expr, val_expr, is_computed) in properties {
693+
let key_val = evaluate_expr(mc, env, key_expr)?;
694+
let val = evaluate_expr(mc, env, val_expr)?;
695+
696+
let key_str = match key_val {
697+
Value::String(s) => utf16_to_utf8(&s),
698+
Value::Number(n) => n.to_string(),
699+
Value::Boolean(b) => b.to_string(),
700+
Value::BigInt(b) => b.to_string(),
701+
Value::Undefined => "undefined".to_string(),
702+
Value::Null => "null".to_string(),
703+
_ => "object".to_string(),
704+
};
705+
obj_set_key_value(mc, &obj, &PropertyKey::from(key_str), val)?;
706+
}
707+
Ok(Value::Object(obj))
708+
}
709+
Expr::Array(elements) => {
710+
let arr_obj = crate::core::new_js_object_data(mc);
711+
if let Some(arr_val) = env_get(env, "Array") {
712+
if let Value::Object(arr_ctor) = &*arr_val.borrow() {
713+
if let Some(proto_val) = obj_get_key_value(arr_ctor, &"prototype".into())? {
714+
if let Value::Object(proto) = &*proto_val.borrow() {
715+
arr_obj.borrow_mut(mc).prototype = Some(*proto);
716+
}
717+
}
718+
}
719+
}
720+
721+
let mut len = 0;
722+
for (i, elem_opt) in elements.iter().enumerate() {
723+
if let Some(elem) = elem_opt {
724+
let val = evaluate_expr(mc, env, elem)?;
725+
obj_set_key_value(mc, &arr_obj, &i.to_string().into(), val)?;
726+
}
727+
len = i + 1;
728+
}
729+
obj_set_key_value(mc, &arr_obj, &"length".into(), Value::Number(len as f64))?;
730+
Ok(Value::Object(arr_obj))
731+
}
681732
Expr::Function(name, params, body) => {
682733
let mut body_clone = body.clone();
683734
Ok(evaluate_function_expression(mc, env, name.clone(), params, &mut body_clone)?)
@@ -729,12 +780,50 @@ pub fn evaluate_expr<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
729780
}
730781
} else if let Some(method_name) = name.strip_prefix("console.") {
731782
crate::js_console::handle_console_method(mc, method_name, &eval_args, env)
732-
} else if let Some(method) = name.strip_prefix("os.path.") {
733-
let this_val = this_val.clone().unwrap_or(Value::Object(*env));
734-
Ok(handle_os_method(mc, this_val, method, &eval_args, env).map_err(EvalError::Js)?)
735783
} else if let Some(method) = name.strip_prefix("os.") {
736-
let this_val = this_val.clone().unwrap_or(Value::Object(*env));
737-
Ok(handle_os_method(mc, this_val, method, &eval_args, env).map_err(EvalError::Js)?)
784+
#[cfg(feature = "os")]
785+
{
786+
let this_val = this_val.clone().unwrap_or(Value::Object(*env));
787+
Ok(crate::js_os::handle_os_method(mc, this_val, method, &eval_args, env).map_err(EvalError::Js)?)
788+
}
789+
#[cfg(not(feature = "os"))]
790+
{
791+
Err(EvalError::Js(raise_eval_error!(
792+
"os module not enabled. Recompile with --features os"
793+
)))
794+
}
795+
} else if let Some(method) = name.strip_prefix("std.") {
796+
#[cfg(feature = "std")]
797+
{
798+
match method {
799+
"sprintf" => Ok(crate::js_std::sprintf::handle_sprintf_call(&eval_args).map_err(EvalError::Js)?),
800+
"tmpfile" => Ok(crate::js_std::tmpfile::create_tmpfile(mc).map_err(EvalError::Js)?),
801+
_ => Err(EvalError::Js(raise_eval_error!(format!("std method '{}' not implemented", method)))),
802+
}
803+
}
804+
#[cfg(not(feature = "std"))]
805+
{
806+
Err(EvalError::Js(raise_eval_error!(
807+
"std module not enabled. Recompile with --features std"
808+
)))
809+
}
810+
} else if let Some(method) = name.strip_prefix("tmp.") {
811+
#[cfg(feature = "std")]
812+
{
813+
if let Some(Value::Object(this_obj)) = this_val {
814+
Ok(crate::js_std::tmpfile::handle_file_method(&this_obj, method, &eval_args).map_err(EvalError::Js)?)
815+
} else {
816+
Err(EvalError::Js(raise_eval_error!(
817+
"TypeError: tmp method called on incompatible receiver"
818+
)))
819+
}
820+
}
821+
#[cfg(not(feature = "std"))]
822+
{
823+
Err(EvalError::Js(raise_eval_error!(
824+
"std module (tmpfile) not enabled. Recompile with --features std"
825+
)))
826+
}
738827
} else if let Some(method) = name.strip_prefix("BigInt.prototype.") {
739828
let this_v = this_val.clone().unwrap_or(Value::Undefined);
740829
Ok(crate::js_bigint::handle_bigint_object_method(this_v, method, &eval_args).map_err(EvalError::Js)?)

src/core.rs renamed to src/core/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::js_console::initialize_console_object;
55
use crate::js_date::initialize_date;
66
use crate::js_math::initialize_math;
77
use crate::js_number::initialize_number_module;
8-
use crate::js_os::initialize_os_module;
98
use crate::js_regexp::initialize_regexp;
109
use crate::js_string::initialize_string;
1110
use crate::raise_eval_error;
@@ -75,14 +74,19 @@ pub fn initialize_global_constructors<'gc>(mc: &MutationContext<'gc>, env: &JSOb
7574
initialize_regexp(mc, env)?;
7675
// Initialize Date constructor and prototype
7776
initialize_date(mc, env)?;
78-
initialize_os_module(mc, env)?;
7977
initialize_bigint(mc, env)?;
8078

8179
env_set(mc, env, "undefined", Value::Undefined)?;
8280
env_set(mc, env, "NaN", Value::Number(f64::NAN))?;
8381
env_set(mc, env, "Infinity", Value::Number(f64::INFINITY))?;
8482
env_set(mc, env, "eval", Value::Function("eval".to_string()))?;
8583

84+
#[cfg(feature = "os")]
85+
crate::js_os::initialize_os_module(mc, env)?;
86+
87+
#[cfg(feature = "std")]
88+
crate::js_std::initialize_std_module(mc, env)?;
89+
8690
Ok(())
8791
}
8892

src/core/parser.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,9 +1651,9 @@ fn parse_primary(tokens: &[TokenData], index: &mut usize, allow_call: bool) -> R
16511651

16521652
// Parse key
16531653
let mut is_shorthand_candidate = false;
1654-
let key_expr = if let Some(Token::Identifier(name)) = tokens.first().map(|t| t.token.clone()) {
1654+
let key_expr = if let Some(Token::Identifier(name)) = tokens.get(*index).map(|t| t.token.clone()) {
16551655
// Check for concise method: Identifier + (
1656-
if !is_getter && !is_setter && tokens.len() >= 2 && matches!(tokens[1].token, Token::LParen) {
1656+
if !is_getter && !is_setter && tokens.len() > *index + 1 && matches!(tokens[*index + 1].token, Token::LParen) {
16571657
// Concise method
16581658
*index += 1; // consume name
16591659
*index += 1; // consume (
@@ -1693,16 +1693,16 @@ fn parse_primary(tokens: &[TokenData], index: &mut usize, allow_call: bool) -> R
16931693
is_shorthand_candidate = true;
16941694
*index += 1;
16951695
Expr::StringLit(crate::unicode::utf8_to_utf16(&name))
1696-
} else if let Some(Token::Number(n)) = tokens.first().map(|t| t.token.clone()) {
1696+
} else if let Some(Token::Number(n)) = tokens.get(*index).map(|t| t.token.clone()) {
16971697
// Numeric property keys are allowed in object literals (they become strings)
16981698
*index += 1;
16991699
// Format as integer if whole number, otherwise use default representation
17001700
let s = if n.fract() == 0.0 { format!("{}", n as i64) } else { n.to_string() };
17011701
Expr::StringLit(crate::unicode::utf8_to_utf16(&s))
1702-
} else if let Some(Token::StringLit(s)) = tokens.first().map(|t| t.token.clone()) {
1702+
} else if let Some(Token::StringLit(s)) = tokens.get(*index).map(|t| t.token.clone()) {
17031703
*index += 1;
17041704
Expr::StringLit(s)
1705-
} else if let Some(Token::Default) = tokens.first().map(|t| t.token.clone()) {
1705+
} else if let Some(Token::Default) = tokens.get(*index).map(|t| t.token.clone()) {
17061706
// allow the reserved word `default` as an object property key
17071707
*index += 1;
17081708
Expr::StringLit(crate::unicode::utf8_to_utf16("default"))

src/js_function.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ pub fn handle_global_function<'gc>(
9393
// Handle functions that expect unevaluated expressions
9494
match func_name {
9595
"import" => return dynamic_import_function(mc, args, env),
96-
"std.sprintf" => return crate::sprintf::handle_sprintf_call(mc, env, args),
9796
"Function" => return function_constructor(mc, args, env),
9897
"new" => return evaluate_new_expression(mc, args, env),
9998
"eval" => return evalute_eval_function(mc, args, env),

src/js_module.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,18 @@ pub fn load_module<'gc>(mc: &MutationContext<'gc>, module_name: &str, base_path:
3737
let log_func = Value::Function("console.log".to_string());
3838
obj_set_key_value(mc, &module_exports, &"log".into(), log_func)?;
3939
} else if module_name == "std" {
40-
let std_obj = crate::js_std::make_std_object(mc)?;
41-
return Ok(Value::Object(std_obj));
40+
#[cfg(feature = "std")]
41+
{
42+
let std_obj = crate::js_std::make_std_object(mc)?;
43+
return Ok(Value::Object(std_obj));
44+
}
45+
#[cfg(not(feature = "std"))]
46+
return Err(raise_eval_error!("Module 'std' is not built-in (feature disabled)."));
4247
} else if module_name == "os" {
43-
let os_obj = crate::js_os::make_os_object(mc)?;
44-
return Ok(Value::Object(os_obj));
48+
// Removed hardcoded os module. os functionality should be injected.
49+
return Err(raise_eval_error!(
50+
"Module 'os' is not built-in. Please provide it via host environment."
51+
));
4552
} else {
4653
// Try to load as a file
4754
match load_module_from_file(mc, module_name, base_path) {

src/js_os.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![cfg(feature = "os")]
2+
13
use crate::core::MutationContext;
24
use std::collections::HashMap;
35
use std::fs::File;
@@ -325,7 +327,7 @@ pub(crate) fn handle_os_method<'gc>(
325327
// If this object looks like the `os.path` module
326328
if get_own_property(&object, &"join".into()).is_some() {
327329
match method {
328-
"join" => {
330+
"join" | "path.join" => {
329331
let mut result = String::new();
330332
for (i, val) in args.iter().enumerate() {
331333
let part = match val {

src/js_std.rs renamed to src/js_std/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#![cfg(feature = "std")]
2+
3+
pub(crate) mod sprintf;
4+
pub(crate) mod tmpfile;
5+
6+
use crate::core::MutationContext;
17
use crate::core::{JSObjectDataPtr, Value, new_js_object_data, obj_set_key_value};
28
use crate::error::JSError;
39

@@ -7,6 +13,13 @@ fn utf8_to_utf16_local(s: &str) -> Vec<u16> {
713
s.encode_utf16().collect()
814
}
915

16+
pub fn initialize_std_module<'gc>(mc: &MutationContext<'gc>, global_obj: &JSObjectDataPtr<'gc>) -> Result<(), JSError> {
17+
let std_obj = make_std_object(mc)?;
18+
// Optionally expose it globally, or just rely on module system import
19+
obj_set_key_value(mc, global_obj, &"std".into(), Value::Object(std_obj))?;
20+
Ok(())
21+
}
22+
1023
pub fn make_std_object<'gc>(mc: &MutationContext<'gc>) -> Result<JSObjectDataPtr<'gc>, JSError> {
1124
let obj = new_js_object_data(mc);
1225
obj_set_key_value(mc, &obj, &"sprintf".into(), Value::Function("std.sprintf".to_string()))?;

src/sprintf.rs renamed to src/js_std/sprintf.rs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
1-
use crate::core::JSObjectDataPtr;
2-
use crate::core::{Collect, Gc, GcCell, GcPtr, MutationContext, Trace};
3-
use crate::core::{Expr, Value, evaluate_expr};
1+
#![cfg(feature = "std")]
2+
3+
use crate::core::Value;
44
use crate::error::JSError;
55
use crate::unicode::{utf8_to_utf16, utf16_to_utf8};
66

7-
pub(crate) fn handle_sprintf_call<'gc>(
8-
mc: &MutationContext<'gc>,
9-
env: &JSObjectDataPtr<'gc>,
10-
args: &[Expr],
11-
) -> Result<Value<'gc>, JSError> {
7+
pub(crate) fn handle_sprintf_call<'gc>(args: &[Value<'gc>]) -> Result<Value<'gc>, JSError> {
128
log::trace!("handle_sprintf_call called with {} args", args.len());
139
if args.is_empty() {
1410
return Ok(Value::String(utf8_to_utf16("")));
1511
}
16-
let format_val = evaluate_expr(mc, env, &args[0])?;
12+
// args[0] is format string
13+
let format_val = &args[0];
1714
let format_str = match format_val {
1815
Value::String(s) => utf16_to_utf8(&s),
1916
_ => {
2017
return Err(raise_eval_error!("sprintf format must be a string"));
2118
}
2219
};
23-
let result = sprintf_impl(mc, env, &format_str, &args[1..])?;
20+
let result = sprintf_impl(&format_str, &args[1..])?;
2421
Ok(Value::String(utf8_to_utf16(&result)))
2522
}
2623

27-
pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>, format: &str, args: &[Expr]) -> Result<String, JSError> {
24+
pub fn sprintf_impl<'gc>(format: &str, args: &[Value<'gc>]) -> Result<String, JSError> {
2825
let mut result = String::new();
2926
let mut arg_index = 0;
3027
let mut chars = format.chars().peekable();
@@ -60,9 +57,9 @@ pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
6057
if ch == '*' {
6158
chars.next();
6259
if arg_index < args.len() {
63-
let width_val = evaluate_expr(mc, env, &args[arg_index])?;
60+
let width_val = &args[arg_index];
6461
if let Value::Number(n) = width_val {
65-
width = Some(n as usize);
62+
width = Some(*n as usize);
6663
}
6764
arg_index += 1;
6865
}
@@ -89,9 +86,9 @@ pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
8986
if ch == '*' {
9087
chars.next();
9188
if arg_index < args.len() {
92-
let prec_val = evaluate_expr(mc, env, &args[arg_index])?;
89+
let prec_val = &args[arg_index];
9390
if let Value::Number(n) = prec_val {
94-
precision = Some(n as usize);
91+
precision = Some(*n as usize);
9592
}
9693
arg_index += 1;
9794
}
@@ -127,15 +124,15 @@ pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
127124
}
128125

129126
// Get the argument value
130-
let arg_val = evaluate_expr(mc, env, &args[arg_index])?;
127+
let arg_val = &args[arg_index];
131128

132129
match specifier {
133130
'd' | 'i' => {
134131
// Integer
135132
let val = match arg_val {
136-
Value::Number(n) => n as i64,
133+
Value::Number(n) => *n as i64,
137134
Value::Boolean(b) => {
138-
if b {
135+
if *b {
139136
1
140137
} else {
141138
0
@@ -157,7 +154,7 @@ pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
157154
'x' | 'X' => {
158155
// Hexadecimal
159156
let val = match arg_val {
160-
Value::Number(n) => n as i64,
157+
Value::Number(n) => *n as i64,
161158
_ => 0,
162159
};
163160
let formatted = if length_modifier == "l" {
@@ -174,7 +171,7 @@ pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
174171
'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
175172
// Float
176173
let val = match arg_val {
177-
Value::Number(n) => n,
174+
Value::Number(n) => *n,
178175
_ => 0.0,
179176
};
180177
let formatted = if let Some(w) = width {
@@ -208,7 +205,7 @@ pub fn sprintf_impl<'gc>(mc: &MutationContext<'gc>, env: &JSObjectDataPtr<'gc>,
208205
'c' => {
209206
// Character
210207
let val = match arg_val {
211-
Value::Number(n) => n as u8 as char,
208+
Value::Number(n) => *n as u8 as char,
212209
_ => '?',
213210
};
214211
result.push(val);

0 commit comments

Comments
 (0)