Skip to main content

pipa/builtins/
process.rs

1use super::promise;
2use crate::host::HostFunction;
3use crate::object::function::JSFunction;
4use crate::object::object::JSObject;
5use crate::runtime::context::JSContext;
6use crate::value::JSValue;
7
8pub fn init_process_module(ctx: &mut JSContext) {
9    let specifier = "pipa:process";
10
11    ctx.register_builtin("process_exit", HostFunction::new("exit", 1, process_exit));
12    ctx.register_builtin("process_cwd", HostFunction::new("cwd", 0, process_cwd));
13    ctx.register_builtin("process_exec", HostFunction::new("exec", 2, process_exec));
14
15    let mut module = crate::runtime::module::Module::new(specifier.to_string(), String::new());
16
17    module.add_export("argv".to_string(), make_argv(ctx), false);
18    module.add_export(
19        "argc".to_string(),
20        JSValue::new_int(ctx.runtime().argv.len() as i64),
21        false,
22    );
23    module.add_export("env".to_string(), make_env(ctx), false);
24
25    let (cwd_fn, exit_fn, exec_fn) = make_functions(ctx);
26    module.add_export("cwd".to_string(), cwd_fn, false);
27    module.add_export("exit".to_string(), exit_fn, false);
28    module.add_export("exec".to_string(), exec_fn, false);
29
30    ctx.runtime_mut().module_registry_mut().register(module);
31}
32
33fn make_argv(ctx: &mut JSContext) -> JSValue {
34    let args: Vec<String> = ctx.runtime().argv.clone();
35    let mut arr = JSObject::new_array();
36    if let Some(proto_ptr) = ctx.get_array_prototype() {
37        arr.prototype = Some(proto_ptr);
38    }
39    arr.set(ctx.common_atoms.length, JSValue::new_int(args.len() as i64));
40
41    for (i, arg) in args.iter().enumerate() {
42        let val = JSValue::new_string(ctx.intern(arg));
43        let atom = ctx.intern(&i.to_string());
44        arr.set(atom, val);
45    }
46
47    let ptr = Box::into_raw(Box::new(arr)) as usize;
48    JSValue::new_object(ptr)
49}
50
51fn make_env(ctx: &mut JSContext) -> JSValue {
52    let mut obj = JSObject::new();
53    for (key, val) in std::env::vars() {
54        let key_atom = ctx.intern(&key);
55        obj.set(key_atom, JSValue::new_string(ctx.intern(&val)));
56    }
57    let ptr = Box::into_raw(Box::new(obj)) as usize;
58    JSValue::new_object(ptr)
59}
60
61fn make_functions(ctx: &mut JSContext) -> (JSValue, JSValue, JSValue) {
62    let cwd = make_function_value(ctx, "cwd", 0, "process_cwd");
63    let exit = make_function_value(ctx, "exit", 1, "process_exit");
64    let exec = make_function_value(ctx, "exec", 2, "process_exec");
65    (cwd, exit, exec)
66}
67
68fn make_function_value(ctx: &mut JSContext, name: &str, arity: u32, marker: &str) -> JSValue {
69    let mut func = JSFunction::new_builtin(ctx.intern(name), arity);
70    func.set_builtin_marker(ctx, marker);
71    let ptr = Box::into_raw(Box::new(func)) as usize;
72    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
73    JSValue::new_function(ptr)
74}
75
76fn process_exit(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
77    let code = if args.is_empty() || !args[0].is_int() {
78        0
79    } else {
80        args[0].get_int() as i32
81    };
82    std::process::exit(code);
83}
84
85fn process_cwd(ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
86    match std::env::current_dir() {
87        Ok(p) => JSValue::new_string(ctx.intern(&p.to_string_lossy().to_string())),
88        Err(e) => {
89            let s = format!("{}", e);
90            let msg = JSValue::new_string(ctx.intern(&s));
91            promise::create_rejected_promise(ctx, msg)
92        }
93    }
94}
95
96fn process_exec(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
97    let start_idx = if args.len() >= 3 { 1 } else { 0 };
98    let real_args = &args[start_idx..];
99
100    if real_args.is_empty() || !real_args[0].is_string() {
101        let msg = JSValue::new_string(ctx.intern("exec: command must be a string"));
102        return promise::create_rejected_promise(ctx, msg);
103    }
104
105    let command = ctx.get_atom_str(real_args[0].get_atom()).to_string();
106
107    let cmd_args: Vec<String> = parse_js_array(ctx, real_args.get(1));
108
109    let result = crate::runtime::process_task::run_command_sync(&command, &cmd_args);
110
111    let mut promise_obj = JSObject::new_promise();
112    if let Some(proto_ptr) = ctx.get_promise_prototype() {
113        promise_obj.prototype = Some(proto_ptr);
114    }
115    promise_obj.set(ctx.intern("__promise_state__"), JSValue::new_int(0));
116    promise_obj.set(ctx.intern("__promise_result__"), JSValue::undefined());
117    promise_obj.set(ctx.intern("__promise_reactions__"), JSValue::null());
118    let promise_ptr = Box::into_raw(Box::new(promise_obj)) as usize;
119    let promise_val = JSValue::new_object(promise_ptr);
120
121    if let Some(e) = result.error {
122        let msg = JSValue::new_string(ctx.intern(&e));
123        super::promise::reject_promise_with_value(ctx, promise_ptr, msg);
124    } else {
125        let mut result_obj = crate::object::object::JSObject::new();
126        result_obj.set(
127            ctx.intern("stdout"),
128            JSValue::new_string(ctx.intern(&String::from_utf8_lossy(&result.stdout))),
129        );
130        result_obj.set(
131            ctx.intern("stderr"),
132            JSValue::new_string(ctx.intern(&String::from_utf8_lossy(&result.stderr))),
133        );
134        result_obj.set(ctx.intern("code"), JSValue::new_int(result.code as i64));
135        result_obj.set(
136            ctx.intern("signal"),
137            if let Some(sig) = result.signal {
138                JSValue::new_int(sig as i64)
139            } else {
140                JSValue::null()
141            },
142        );
143        let result_ptr = Box::into_raw(Box::new(result_obj)) as usize;
144        super::promise::fulfill_promise_with_value(
145            ctx,
146            promise_ptr,
147            JSValue::new_object(result_ptr),
148        );
149    }
150
151    promise_val
152}
153
154fn parse_js_array(ctx: &mut JSContext, arg: Option<&JSValue>) -> Vec<String> {
155    let obj_val = match arg {
156        Some(v) if v.is_object() => v,
157        _ => return Vec::new(),
158    };
159    let obj = obj_val.as_object();
160
161    let len = if obj.is_dense_array() {
162        let arr_ptr = obj_val.get_ptr() as *const crate::object::array_obj::JSArrayObject;
163        unsafe { &*arr_ptr }.len()
164    } else {
165        let len_val = obj.get(ctx.intern("length")).unwrap_or(JSValue::new_int(0));
166        if len_val.is_int() {
167            len_val.get_int() as usize
168        } else {
169            0
170        }
171    };
172
173    let mut result = Vec::with_capacity(len);
174    for i in 0..len {
175        if obj.is_dense_array() {
176            let arr_ptr = obj_val.get_ptr() as *const crate::object::array_obj::JSArrayObject;
177            let arr = unsafe { &*arr_ptr };
178            if let Some(val) = arr.get(i) {
179                if val.is_string() {
180                    result.push(ctx.get_atom_str(val.get_atom()).to_string());
181                }
182            }
183        } else {
184            let atom = ctx.intern(&i.to_string());
185            if let Some(val) = obj.get(atom) {
186                if val.is_string() {
187                    result.push(ctx.get_atom_str(val.get_atom()).to_string());
188                }
189            }
190        }
191    }
192    result
193}