1use crate::host::{with_host, JsObj};
10use fusevm::Value;
11use indexmap::IndexMap;
12
13pub const METHODS: &[&str] = &[
15 "cwd",
16 "chdir",
17 "exit",
18 "hrtime",
19 "hrtime.bigint",
24 "uptime",
25 "memoryUsage",
26 "cpuUsage",
27 "umask",
28 "binding",
29 "emit",
30 "on",
31 "once",
32 "off",
33 "addListener",
34 "removeListener",
35 "removeAllListeners",
36 "listeners",
37 "emitWarning",
38 "kill",
39 "getuid",
40 "getgid",
41 "geteuid",
42 "getegid",
43 "getgroups",
44 "setuid",
45 "setgid",
46 "seteuid",
47 "setegid",
48 "setgroups",
49 "initgroups",
50 "ref",
51 "unref",
52 "abort",
53 "getActiveResourcesInfo",
54 "resourceUsage",
55 "threadCpuUsage",
56 "availableMemory",
57 "constrainedMemory",
58 "getBuiltinModule",
59 "openStdin",
60 "hasUncaughtExceptionCaptureCallback",
61 "setUncaughtExceptionCaptureCallback",
62 "addUncaughtExceptionCaptureCallback",
63 "execve",
64 "reallyExit",
65 "loadEnvFile",
66 "setSourceMapsEnabled",
67];
68
69thread_local! {
70 static UNCAUGHT_CAPTURE: std::cell::RefCell<Option<Value>> =
74 const { std::cell::RefCell::new(None) };
75}
76
77static TRACE_HINT_SHOWN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
80
81pub fn emit_warning(name: &str, code: Option<&str>, message: &str, detail: Option<&str>) {
86 let argv: Vec<String> = std::env::args().collect();
87 let flag = |f: &str| argv.iter().any(|a| a == f);
88 let is_deprecation = name == "DeprecationWarning";
89 if flag("--no-warnings") || (is_deprecation && flag("--no-deprecation")) {
90 return;
91 }
92 let trace = flag("--trace-warnings") || (is_deprecation && flag("--trace-deprecation"));
93
94 let mut msg = std::format!("(node:{}) ", std::process::id());
95 if let Some(c) = code {
96 msg.push_str(&std::format!("[{c}] "));
97 }
98 msg.push_str(&std::format!("{name}: {message}"));
99 if let Some(d) = detail {
100 msg.push_str(&std::format!("\n{d}"));
101 }
102 if !trace && !TRACE_HINT_SHOWN.swap(true, std::sync::atomic::Ordering::Relaxed) {
103 let trace_flag = if is_deprecation {
104 "--trace-deprecation"
105 } else {
106 "--trace-warnings"
107 };
108 msg.push_str(&std::format!(
109 "\n(Use `node {trace_flag} ...` to show where the warning was created)"
110 ));
111 }
112 eprintln!("{msg}");
113}
114
115pub fn emit_deprecation_warning(code: &str, message: &str) {
118 use std::cell::RefCell;
119 thread_local! {
120 static SEEN: RefCell<std::collections::HashSet<String>> =
121 RefCell::new(std::collections::HashSet::new());
122 }
123 let first = SEEN.with(|s| s.borrow_mut().insert(code.to_string()));
124 if first {
125 emit_warning("DeprecationWarning", Some(code), message, None);
126 }
127}
128
129pub fn signal_number(name: &str) -> Option<libc::c_int> {
137 Some(match name.to_uppercase().as_str() {
138 "SIGHUP" => libc::SIGHUP,
139 "SIGINT" => libc::SIGINT,
140 "SIGQUIT" => libc::SIGQUIT,
141 "SIGILL" => libc::SIGILL,
142 "SIGTRAP" => libc::SIGTRAP,
143 "SIGABRT" => libc::SIGABRT,
144 "SIGBUS" => libc::SIGBUS,
145 "SIGFPE" => libc::SIGFPE,
146 "SIGKILL" => libc::SIGKILL,
147 "SIGUSR1" => libc::SIGUSR1,
148 "SIGSEGV" => libc::SIGSEGV,
149 "SIGUSR2" => libc::SIGUSR2,
150 "SIGPIPE" => libc::SIGPIPE,
151 "SIGALRM" => libc::SIGALRM,
152 "SIGTERM" => libc::SIGTERM,
153 "SIGCHLD" => libc::SIGCHLD,
154 "SIGCONT" => libc::SIGCONT,
155 "SIGSTOP" => libc::SIGSTOP,
156 "SIGTSTP" => libc::SIGTSTP,
157 "SIGWINCH" => libc::SIGWINCH,
158 _ => return None,
159 })
160}
161
162fn emit_warning_args(args: &[Value]) {
164 let message = super::arg_str(args, 0);
165 let mut name = "Warning".to_string();
166 let mut code: Option<String> = None;
167 let mut detail: Option<String> = None;
168 match args.get(1) {
169 Some(v) if with_host(|h| matches!(h.get(v), Some(JsObj::Object(_)))) => {
170 let field = |k: &str| {
171 with_host(|h| match h.get(v) {
172 Some(JsObj::Object(p)) => {
173 p.get(k).filter(|x| !h.is_nullish(x)).map(|x| h.str_of(x))
174 }
175 _ => None,
176 })
177 };
178 if let Some(t) = field("type") {
179 name = t;
180 }
181 code = field("code");
182 detail = field("detail");
183 }
184 Some(_) => {
185 name = super::arg_str(args, 1);
186 code = args.get(2).map(|_| super::arg_str(args, 2));
187 }
188 None => {}
189 }
190 emit_warning(&name, code.as_deref(), &message, detail.as_deref());
191}
192
193fn memo(name: &str, make: impl FnOnce() -> Value) -> Value {
206 if let Some(v) = with_host(|h| h.builtin_static("process", name)) {
207 return v;
208 }
209 let v = make();
210 with_host(|h| h.set_builtin_static("process", name, v.clone()));
211 v
212}
213
214fn features() -> Value {
216 with_host(|h| {
217 let mut m = IndexMap::new();
218 for (k, v) in [
219 ("inspector", false),
220 ("debug", false),
221 ("uv", false),
222 ("ipv6", true),
223 ("tls_alpn", false),
224 ("tls_sni", false),
225 ("tls_ocsp", false),
226 ("tls", true),
227 ("openssl_is_boringssl", false),
228 ("cached_builtins", true),
229 ("require_module", true),
230 ("quic", false),
231 ] {
232 m.insert(k.to_string(), Value::Bool(v));
233 }
234 let ts = h.new_str("none");
236 m.insert("typescript".into(), ts);
237 h.new_object(m)
238 })
239}
240
241fn config() -> Value {
243 with_host(|h| {
244 let mut vars = IndexMap::new();
245 let arch = h.new_str(super::os::arch());
246 let plat = h.new_str(super::os::platform());
247 vars.insert("host_arch".to_string(), arch.clone());
248 vars.insert("target_arch".to_string(), arch);
249 vars.insert("node_shared".to_string(), Value::Bool(false));
250 vars.insert("node_use_openssl".to_string(), Value::Bool(false));
251 vars.insert("v8_enable_i18n_support".to_string(), Value::Bool(false));
252 vars.insert("node_platform".to_string(), plat);
253 let variables = h.new_object(vars);
254 let defaults = h.new_object(IndexMap::new());
255 let mut m = IndexMap::new();
256 m.insert("target_defaults".to_string(), defaults);
257 m.insert("variables".to_string(), variables);
258 h.new_object(m)
259 })
260}
261
262fn allowed_flags() -> Value {
264 let flags = [
265 "--enable-source-maps",
266 "--max-old-space-size",
267 "--no-warnings",
268 "--preserve-symlinks",
269 "--stack-trace-limit",
270 "--throw-deprecation",
271 "--trace-warnings",
272 "--unhandled-rejections",
273 "--zero-fill-buffers",
274 ];
275 let vals: Vec<Value> = flags.iter().map(|f| with_host(|h| h.new_str(*f))).collect();
276 let set = with_host(|h| {
277 h.alloc(crate::host::JsObj::Set {
278 entries: indexmap::IndexMap::new(),
279 weak: false,
280 })
281 });
282 for v in vals {
283 let _ = crate::host::call_method(&set, "add", vec![v]);
284 }
285 set
286}
287
288pub fn constant(name: &str) -> Option<Value> {
289 Some(match name {
290 "env" => memo("env", env_object),
291 "release" => memo("release", || {
298 with_host(|h| {
299 let mut m = IndexMap::new();
300 let name = h.new_str("node");
301 m.insert("name".into(), name);
302 h.new_object(m)
303 })
304 }),
305 "argv" => memo("argv", argv),
306 "argv0" => with_host(|h| h.new_str(exec_path())),
307 "execPath" => with_host(|h| h.new_str(exec_path())),
308 "execArgv" => memo("execArgv", exec_argv),
309 "platform" => with_host(|h| h.new_str(super::os::platform())),
310 "arch" => with_host(|h| h.new_str(super::os::arch())),
311 "pid" => Value::Float(std::process::id() as f64),
312 "ppid" => Value::Float(0.0),
313 "title" => with_host(|h| h.new_str("node")),
314 "version" => with_host(|h| h.new_str("v26.5.0")),
317 "versions" => memo("versions", versions),
318 "features" => memo("features", features),
324 "config" => memo("config", config),
328 "allowedNodeEnvironmentFlags" => memo("allowedNodeEnvironmentFlags", allowed_flags),
332 "stdout" => memo("stdout", || std_stream(1)),
333 "stderr" => memo("stderr", || std_stream(2)),
334 "stdin" => memo("stdin", || std_stream(0)),
335 "exitCode" => match with_host(|h| h.exit_code) {
338 Some(c) => Value::Float(c as f64),
339 None => Value::Undef,
340 },
341 _ => return None,
342 })
343}
344
345pub fn set_exit_code(val: &Value) -> Result<(), String> {
369 if matches!(val, Value::Undef) || with_host(|h| h.is_null(val)) {
370 with_host(|h| h.exit_code = None);
371 return Ok(());
372 }
373 let numeric = match with_host(|h| h.as_str(val)) {
375 Some(s) if !s.is_empty() => {
376 let n = with_host(|h| h.to_number(val));
377 if n.is_nan() {
378 None
379 } else {
380 Some(n)
381 }
382 }
383 Some(_) => None,
384 None => match val {
385 Value::Float(_) | Value::Int(_) => Some(with_host(|h| h.to_number(val))),
386 _ => None,
387 },
388 };
389 match numeric {
390 Some(n) if n.fract() == 0.0 && n.is_finite() => {
391 with_host(|h| h.exit_code = Some(n as i32));
392 Ok(())
393 }
394 Some(n) => Err(crate::host::coded_error(
395 "RangeError",
396 "ERR_OUT_OF_RANGE",
397 &format!(
398 "The value of \"code\" is out of range. It must be an integer. Received {}",
399 crate::host::fmt_number(n)
400 ),
401 )),
402 None => Err(crate::host::invalid_arg_type(
403 "code", "argument", "number", val,
404 )),
405 }
406}
407
408pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
409 Some(match method {
410 "@@stdinPump" => stdin_pump().map(|_| Value::Undef),
411 "cwd" => {
412 let d = std::env::current_dir()
413 .map(|p| p.to_string_lossy().into_owned())
414 .unwrap_or_default();
415 Ok(with_host(|h| h.new_str(d)))
416 }
417 "hrtime" => Ok(hrtime(args)),
421 "hrtime.bigint" => Ok(with_host(|h| {
423 let now = std::time::SystemTime::now()
424 .duration_since(std::time::UNIX_EPOCH)
425 .unwrap_or_default();
426 h.new_bigint(num_bigint::BigInt::from(now.as_nanos()))
427 })),
428 "uptime" => Ok(Value::Float(0.0)),
429 "memoryUsage" => Ok(memory_usage()),
430 "cpuUsage" => Ok(with_host(|h| {
431 let mut m = IndexMap::new();
432 m.insert("user".into(), Value::Float(0.0));
433 m.insert("system".into(), Value::Float(0.0));
434 h.new_object(m)
435 })),
436 "umask" => Ok(Value::Float(0.0)),
437 "binding" => Err(crate::host::type_error("process.binding is not supported")),
438 "on" | "once" | "addListener" => {
442 let (event, f) = (event_name(args), args.get(1).cloned());
443 if let Some(f) = f {
444 let once = method == "once";
445 with_host(|h| {
446 h.process_listeners
447 .entry(event)
448 .or_default()
449 .push(crate::host::ProcListener { f, once })
450 });
451 }
452 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
453 }
454 "off" | "removeListener" => {
455 let (event, f) = (event_name(args), args.get(1).cloned());
456 if let Some(f) = f {
457 with_host(|h| {
458 if let Some(l) = h.process_listeners.get_mut(&event) {
459 if let Some(i) = l.iter().position(|x| x.f == f) {
460 l.remove(i);
461 }
462 }
463 });
464 }
465 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
466 }
467 "removeAllListeners" => {
468 let event = event_name(args);
469 with_host(|h| {
470 if event.is_empty() {
471 h.process_listeners.clear();
472 } else {
473 h.process_listeners.shift_remove(&event);
474 }
475 });
476 Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into()))))
477 }
478 "listeners" => {
479 let event = event_name(args);
480 Ok(with_host(|h| {
481 let l = h
482 .process_listeners
483 .get(&event)
484 .map(|v| v.iter().map(|x| x.f.clone()).collect())
485 .unwrap_or_default();
486 h.new_array(l)
487 }))
488 }
489 "emit" => {
490 let event = event_name(args);
491 let rest: Vec<Value> = args.iter().skip(1).cloned().collect();
492 let listeners = with_host(|h| h.take_process_listeners(&event));
493 let any = !listeners.is_empty();
494 let mut r = Ok(Value::Bool(any));
495 for f in listeners {
496 if let Err(e) = crate::host::invoke(&f, rest.clone(), None) {
497 r = Err(e);
498 break;
499 }
500 }
501 r
502 }
503 "emitWarning" => {
504 emit_warning_args(args);
505 Ok(Value::Undef)
506 }
507 "exit" | "reallyExit" => {
529 if !args.is_empty() {
530 if let Err(e) = set_exit_code(&args[0]) {
531 return Some(Err(e));
532 }
533 }
534 let code = with_host(|h| h.exit_code).unwrap_or(0);
535 if let Err(e) = emit_exit_event(code) {
536 return Some(Err(e));
537 }
538 let code = with_host(|h| h.exit_code).unwrap_or(0);
540 use std::io::Write;
541 let _ = std::io::stdout().flush();
542 let _ = std::io::stderr().flush();
543 crate::cache::flush();
548 std::process::exit(code);
549 }
550 "chdir" => {
554 let dir = super::arg_str(args, 0);
555 std::env::set_current_dir(&dir)
556 .map(|()| Value::Undef)
557 .map_err(|e| {
562 let from = std::env::current_dir()
563 .map(|p| p.display().to_string())
564 .unwrap_or_default();
565 format!(
566 "Error: {}, chdir '{from}' -> '{dir}'",
567 crate::stdlib::fs::libuv_message(&e)
568 )
569 })
570 }
571 "kill" => {
575 let pid = with_host(|h| args.first().map(|v| h.to_number(v)).unwrap_or(0.0)) as i32;
576 let sig: Result<libc::c_int, String> = match args.get(1) {
577 Some(v) if !matches!(v, Value::Undef) => match with_host(|h| h.as_str(v)) {
578 Some(name) => signal_number(&name).ok_or(crate::host::coded_error(
579 "TypeError",
580 "ERR_UNKNOWN_SIGNAL",
581 &format!("Unknown signal: {name}"),
582 )),
583 None => Ok(with_host(|h| h.to_number(v)) as libc::c_int),
584 },
585 _ => Ok(libc::SIGTERM),
586 };
587 sig.and_then(|sig| {
588 if unsafe { libc::kill(pid, sig) } != 0 {
591 Err(format!("Error: {}", std::io::Error::last_os_error()))
592 } else {
593 Ok(Value::Undef)
594 }
595 })
596 }
597 "setSourceMapsEnabled" => Ok(Value::Undef),
601
602 "getuid" => Ok(Value::Float(unsafe { libc::getuid() } as f64)),
604 "geteuid" => Ok(Value::Float(unsafe { libc::geteuid() } as f64)),
605 "getgid" => Ok(Value::Float(unsafe { libc::getgid() } as f64)),
606 "getegid" => Ok(Value::Float(unsafe { libc::getegid() } as f64)),
607 "getgroups" => {
608 let groups = supplementary_groups();
609 Ok(with_host(|h| {
610 h.new_array(groups.into_iter().map(Value::Float).collect())
611 }))
612 }
613
614 "setuid" | "seteuid" | "setgid" | "setegid" => {
617 let id = super::arg_num(args, 0);
618 if id.is_finite() {
619 let id = id as u32;
620 unsafe {
622 match method {
623 "setuid" => libc::setuid(id),
624 "seteuid" => libc::seteuid(id),
625 "setgid" => libc::setgid(id),
626 _ => libc::setegid(id),
627 };
628 }
629 }
630 Ok(Value::Undef)
631 }
632 "setgroups" => {
633 let groups = gid_array(args.first());
634 unsafe {
636 libc::setgroups(groups.len() as _, groups.as_ptr());
637 }
638 Ok(Value::Undef)
639 }
640 "initgroups" => {
641 let user = super::arg_str(args, 0);
642 let extra = super::arg_num(args, 1);
643 if let Ok(c) = std::ffi::CString::new(user) {
644 let gid = if extra.is_finite() { extra as u32 } else { 0 };
645 unsafe {
647 libc::initgroups(c.as_ptr(), gid as _);
648 }
649 }
650 Ok(Value::Undef)
651 }
652
653 "ref" | "unref" => Ok(with_host(|h| h.alloc(JsObj::Builtin("process".into())))),
656 "abort" => std::process::abort(),
657 "getActiveResourcesInfo" => Ok(with_host(|h| h.new_array(Vec::new()))),
658 "resourceUsage" => Ok(resource_usage()),
659 "threadCpuUsage" => Ok(thread_cpu_usage()),
660 "availableMemory" | "constrainedMemory" => Ok(Value::Float(0.0)),
661 "getBuiltinModule" => {
662 let id = super::arg_str(args, 0);
663 let id = id.strip_prefix("node:").unwrap_or(&id);
664 match crate::stdlib::resolve(id) {
665 Some(ns) => Ok(with_host(|h| h.alloc(JsObj::Builtin(ns.to_string())))),
666 None => Ok(Value::Undef),
667 }
668 }
669 "openStdin" => Ok(std_stream(0)),
670
671 "hasUncaughtExceptionCaptureCallback" => {
672 Ok(Value::Bool(UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some())))
673 }
674 "setUncaughtExceptionCaptureCallback" => {
675 let cb = args.first().cloned().unwrap_or(Value::Undef);
676 let clear = matches!(cb, Value::Undef) || with_host(|h| h.is_null(&cb));
677 if clear {
678 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = None);
679 } else if UNCAUGHT_CAPTURE.with(|c| c.borrow().is_some()) {
680 return Some(Err(crate::host::type_error(
681 "`process.setUncaughtExceptionCaptureCallback()` was called \
682 while a capture callback was already active",
683 )));
684 } else {
685 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
686 }
687 Ok(Value::Undef)
688 }
689 "addUncaughtExceptionCaptureCallback" => {
690 let cb = args.first().cloned().unwrap_or(Value::Undef);
691 if !matches!(cb, Value::Undef) {
692 UNCAUGHT_CAPTURE.with(|c| *c.borrow_mut() = Some(cb));
693 }
694 Ok(Value::Undef)
695 }
696 "execve" => exec_ve(args),
697 "loadEnvFile" => load_env_file(&super::arg_str(args, 0)),
698 _ => return None,
699 })
700}
701
702fn env_object() -> Value {
704 with_host(|h| {
705 let mut m = IndexMap::new();
706 m.insert("@@envObject".into(), Value::Bool(true));
711 for (k, v) in std::env::vars() {
712 m.insert(k, h.new_str(v));
713 }
714 h.new_object(m)
715 })
716}
717
718static ARGV: std::sync::OnceLock<(Vec<String>, Vec<String>)> = std::sync::OnceLock::new();
724
725pub fn install_argv() {
733 let split = crate::cli::split_argv(std::env::args());
734 let mut argv = vec![exec_path()];
735 if let Some(s) = &split.script {
736 argv.push(if s == "-" {
738 s.clone()
739 } else {
740 super::path::resolve_one(s)
741 });
742 }
743 argv.extend(split.user);
744 let _ = ARGV.set((split.exec, argv));
745}
746
747fn argv() -> Value {
754 with_host(|h| {
755 let items: Vec<Value> = match ARGV.get() {
756 Some((_, argv)) => argv.iter().map(|a| h.new_str(a.clone())).collect(),
757 None => std::env::args().map(|a| h.new_str(a)).collect(),
758 };
759 h.new_array(items)
760 })
761}
762
763fn exec_argv() -> Value {
765 with_host(|h| {
766 let items: Vec<Value> = ARGV
767 .get()
768 .map(|(e, _)| e.iter().map(|a| h.new_str(a.clone())).collect())
769 .unwrap_or_default();
770 h.new_array(items)
771 })
772}
773
774fn exec_path() -> String {
775 std::env::current_exe()
776 .map(|p| p.to_string_lossy().into_owned())
777 .unwrap_or_else(|_| "node".into())
778}
779
780fn versions() -> Value {
782 with_host(|h| {
783 let mut m = IndexMap::new();
784 m.insert("node".into(), h.new_str("26.5.0"));
785 m.insert("v8".into(), h.new_str("0.0.0"));
786 h.new_object(m)
787 })
788}
789
790fn std_stream(fd: i32) -> Value {
794 with_host(|h| {
795 let mut m = IndexMap::new();
796 m.insert("@@native".into(), h.new_str("WriteStream"));
797 m.insert("fd".into(), Value::Float(fd as f64));
798 let is_tty = unsafe { libc::isatty(fd) == 1 };
800 if is_tty {
806 m.insert("isTTY".into(), Value::Bool(true));
807 }
808 m.insert("writable".into(), Value::Bool(fd != 0));
809 m.insert("readable".into(), Value::Bool(fd == 0));
810 if is_tty {
812 if let Some((cols, rows)) = super::tty::window_size(fd) {
813 m.insert("columns".into(), Value::Float(cols as f64));
814 m.insert("rows".into(), Value::Float(rows as f64));
815 }
816 }
817 h.new_object(m)
818 })
819}
820
821pub fn stream_instance_call(recv: &Value, method: &str, args: &[Value]) -> Result<Value, String> {
825 if stream_fd(recv) == 0.0 {
826 if let Some(r) = stdin_call(recv, method, args) {
827 return r;
828 }
829 }
830 match method {
831 "write" | "end" => {
832 let fd = with_host(|h| match h.get(recv) {
833 Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
834 _ => 1.0,
835 });
836 if method == "end" && args.first().map(|v| matches!(v, Value::Undef)) != Some(false) {
839 return Ok(Value::Bool(true));
840 }
841 let bytes = chunk_bytes(args)?;
842 with_host(|h| h.write_out_bytes(&bytes, fd == 2.0));
843 Ok(Value::Bool(true))
844 }
845 "on" | "once" | "addListener" | "prependListener" | "removeListener" | "off"
849 | "removeAllListeners" | "cork" | "uncork" | "setEncoding" | "resume" | "pause" => {
850 Ok(recv.clone())
851 }
852 "listenerCount" => Ok(Value::Float(0.0)),
853 "read" => Ok(with_host(|h| h.null())),
854 "pipe" => Ok(args.first().cloned().unwrap_or(Value::Undef)),
855 "cursorTo" | "moveCursor" | "clearLine" | "clearScreenDown" => {
858 let seq = tty_control(method, args);
859 write_fd(stream_fd(recv), seq.as_bytes());
860 Ok(Value::Bool(true))
861 }
862 "getWindowSize" => {
863 let (c, r) = super::tty::window_size(stream_fd(recv) as i32).unwrap_or((80, 24));
864 Ok(with_host(|h| {
865 h.new_array(vec![Value::Float(c as f64), Value::Float(r as f64)])
866 }))
867 }
868 "getColorDepth" => Ok(Value::Float(24.0)),
871 "hasColors" => Ok(Value::Bool(true)),
872 _ => Err(crate::host::type_error(&format!(
873 "{method} is not a function"
874 ))),
875 }
876}
877
878fn hrtime(args: &[Value]) -> Value {
879 let now = std::time::SystemTime::now()
880 .duration_since(std::time::UNIX_EPOCH)
881 .unwrap_or_default();
882 let (mut secs, mut nanos) = (now.as_secs() as f64, now.subsec_nanos() as f64);
883 if let Some(Value::Obj(_)) = args.first() {
885 if let Some(prev) = with_host(|h| match h.get(&args[0]) {
886 Some(JsObj::Array(a)) if a.len() == 2 => Some((h.to_number(&a[0]), h.to_number(&a[1]))),
887 _ => None,
888 }) {
889 secs -= prev.0;
890 nanos -= prev.1;
891 }
892 }
893 with_host(|h| h.new_array(vec![Value::Float(secs), Value::Float(nanos)]))
894}
895
896#[cfg(target_os = "macos")]
904fn resident_bytes() -> Option<u64> {
905 let mut info: libc::proc_taskinfo = unsafe { std::mem::zeroed() };
906 let size = std::mem::size_of::<libc::proc_taskinfo>() as libc::c_int;
907 let got = unsafe {
908 libc::proc_pidinfo(
909 std::process::id() as libc::c_int,
910 libc::PROC_PIDTASKINFO,
911 0,
912 (&mut info as *mut libc::proc_taskinfo).cast(),
913 size,
914 )
915 };
916 (got == size).then_some(info.pti_resident_size)
917}
918
919#[cfg(target_os = "linux")]
920fn resident_bytes() -> Option<u64> {
921 let statm = std::fs::read_to_string("/proc/self/statm").ok()?;
923 let pages: u64 = statm.split_whitespace().nth(1)?.parse().ok()?;
924 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
925 (page > 0).then(|| pages * page as u64)
926}
927
928#[cfg(not(any(target_os = "macos", target_os = "linux")))]
929fn resident_bytes() -> Option<u64> {
930 None
931}
932
933fn memory_usage() -> Value {
934 let rss = resident_bytes().unwrap_or(0) as f64;
935 with_host(|h| {
936 let mut m = IndexMap::new();
937 m.insert("rss".into(), Value::Float(rss));
938 for k in ["heapTotal", "heapUsed", "external", "arrayBuffers"] {
941 m.insert(k.into(), Value::Float(0.0));
942 }
943 h.new_object(m)
944 })
945}
946
947pub fn memory_usage_rss() -> Value {
949 Value::Float(resident_bytes().unwrap_or(0) as f64)
950}
951
952pub fn emit_exit_event(code: i32) -> Result<(), String> {
962 if with_host(|h| std::mem::replace(&mut h.exiting, true)) {
963 return Ok(());
964 }
965 let listeners = with_host(|h| h.take_process_listeners("exit"));
966 for f in listeners {
967 crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
968 }
969 Ok(())
970}
971
972pub fn emit_before_exit(code: i32) -> Result<bool, String> {
980 let listeners = with_host(|h| h.take_process_listeners("beforeExit"));
981 let any = !listeners.is_empty();
982 for f in listeners {
983 crate::host::invoke(&f, vec![Value::Float(code as f64)], None)?;
984 }
985 Ok(any)
986}
987
988fn chunk_bytes(args: &[Value]) -> Result<Vec<u8>, String> {
1004 let chunk = args.first().cloned().unwrap_or(Value::Undef);
1005 if with_host(|h| h.is_null(&chunk)) {
1006 return Err(crate::host::type_error(
1007 "May not write null values to stream",
1008 ));
1009 }
1010 if let Some(s) = with_host(|h| h.as_str(&chunk)) {
1011 let enc = match args.get(1) {
1012 Some(v) if !matches!(v, Value::Undef) => with_host(|h| h.str_of(v)),
1013 _ => "utf8".to_string(),
1014 };
1015 return Ok(super::buffer::decode_str(&s, &enc));
1016 }
1017 match super::native_tag(&chunk).as_deref() {
1018 Some("Buffer") | Some("TypedArray") | Some("DataView") => {
1019 Ok(super::buffer::bytes_like(&chunk).unwrap_or_default())
1020 }
1021 _ => Err(crate::host::type_error(&format!(
1022 "The \"chunk\" argument must be of type string or an instance of \
1023 Buffer, TypedArray, or DataView. Received {}",
1024 super::received_desc(&chunk)
1025 ))),
1026 }
1027}
1028
1029fn stream_fd(recv: &Value) -> f64 {
1031 with_host(|h| match h.get(recv) {
1032 Some(JsObj::Object(p)) => p.get("fd").map(|v| h.to_number(v)).unwrap_or(1.0),
1033 _ => 1.0,
1034 })
1035}
1036
1037fn write_fd(fd: f64, bytes: &[u8]) {
1040 let text = String::from_utf8_lossy(bytes).into_owned();
1041 with_host(|h| h.write_out(&text, fd == 2.0));
1042}
1043
1044fn tty_control(method: &str, args: &[Value]) -> String {
1046 match method {
1047 "cursorTo" => {
1049 let x = super::arg_num(args, 0);
1050 let y = super::arg_num(args, 1);
1051 let x = if x.is_finite() { x as i64 } else { 0 };
1052 if y.is_finite() {
1053 format!("\x1b[{};{}H", y as i64 + 1, x + 1)
1054 } else {
1055 format!("\x1b[{}G", x + 1)
1056 }
1057 }
1058 "moveCursor" => {
1060 let dx = super::arg_num(args, 0);
1061 let dy = super::arg_num(args, 1);
1062 let mut s = String::new();
1063 let dx = if dx.is_finite() { dx as i64 } else { 0 };
1064 let dy = if dy.is_finite() { dy as i64 } else { 0 };
1065 if dx > 0 {
1066 s.push_str(&format!("\x1b[{dx}C"));
1067 } else if dx < 0 {
1068 s.push_str(&format!("\x1b[{}D", -dx));
1069 }
1070 if dy > 0 {
1071 s.push_str(&format!("\x1b[{dy}B"));
1072 } else if dy < 0 {
1073 s.push_str(&format!("\x1b[{}A", -dy));
1074 }
1075 s
1076 }
1077 "clearLine" => match super::arg_num(args, 0) {
1079 d if d < 0.0 => "\x1b[1K".into(),
1080 d if d > 0.0 => "\x1b[0K".into(),
1081 _ => "\x1b[2K".into(),
1082 },
1083 _ => "\x1b[0J".into(),
1085 }
1086}
1087
1088fn supplementary_groups() -> Vec<f64> {
1090 unsafe {
1092 let n = libc::getgroups(0, std::ptr::null_mut());
1093 if n <= 0 {
1094 return Vec::new();
1095 }
1096 let mut buf = vec![0 as libc::gid_t; n as usize];
1097 let filled = libc::getgroups(n, buf.as_mut_ptr());
1098 if filled < 0 {
1099 return Vec::new();
1100 }
1101 buf.truncate(filled as usize);
1102 buf.into_iter().map(|g| g as f64).collect()
1103 }
1104}
1105
1106fn gid_array(v: Option<&Value>) -> Vec<libc::gid_t> {
1108 let Some(v) = v else { return Vec::new() };
1109 with_host(|h| match h.get(v) {
1110 Some(JsObj::Array(a)) => a.iter().map(|x| h.to_number(x) as libc::gid_t).collect(),
1111 _ => Vec::new(),
1112 })
1113}
1114
1115fn get_rusage() -> Option<libc::rusage> {
1117 unsafe {
1119 let mut ru: libc::rusage = std::mem::zeroed();
1120 (libc::getrusage(libc::RUSAGE_SELF, &mut ru) == 0).then_some(ru)
1121 }
1122}
1123
1124fn tv_micros(t: &libc::timeval) -> f64 {
1126 t.tv_sec as f64 * 1e6 + t.tv_usec as f64
1127}
1128
1129fn resource_usage() -> Value {
1131 let ru = get_rusage();
1132 with_host(|h| {
1133 let mut m = IndexMap::new();
1134 let (utime, stime) = ru
1135 .as_ref()
1136 .map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
1137 .unwrap_or((0.0, 0.0));
1138 m.insert("userCPUTime".into(), Value::Float(utime));
1139 m.insert("systemCPUTime".into(), Value::Float(stime));
1140 let fields = [
1141 ("maxRSS", ru.as_ref().map(|r| r.ru_maxrss)),
1142 ("sharedMemorySize", ru.as_ref().map(|r| r.ru_ixrss)),
1143 ("unsharedDataSize", ru.as_ref().map(|r| r.ru_idrss)),
1144 ("unsharedStackSize", ru.as_ref().map(|r| r.ru_isrss)),
1145 ("minorPageFault", ru.as_ref().map(|r| r.ru_minflt)),
1146 ("majorPageFault", ru.as_ref().map(|r| r.ru_majflt)),
1147 ("swappedOut", ru.as_ref().map(|r| r.ru_nswap)),
1148 ("fsRead", ru.as_ref().map(|r| r.ru_inblock)),
1149 ("fsWrite", ru.as_ref().map(|r| r.ru_oublock)),
1150 ("ipcSent", ru.as_ref().map(|r| r.ru_msgsnd)),
1151 ("ipcReceived", ru.as_ref().map(|r| r.ru_msgrcv)),
1152 ("signalsCount", ru.as_ref().map(|r| r.ru_nsignals)),
1153 ("voluntaryContextSwitches", ru.as_ref().map(|r| r.ru_nvcsw)),
1154 (
1155 "involuntaryContextSwitches",
1156 ru.as_ref().map(|r| r.ru_nivcsw),
1157 ),
1158 ];
1159 for (k, v) in fields {
1160 m.insert(k.into(), Value::Float(v.unwrap_or(0) as f64));
1161 }
1162 h.new_object(m)
1163 })
1164}
1165
1166fn thread_cpu_usage() -> Value {
1169 let (u, s) = get_rusage()
1170 .map(|r| (tv_micros(&r.ru_utime), tv_micros(&r.ru_stime)))
1171 .unwrap_or((0.0, 0.0));
1172 with_host(|h| {
1173 let mut m = IndexMap::new();
1174 m.insert("user".into(), Value::Float(u));
1175 m.insert("system".into(), Value::Float(s));
1176 h.new_object(m)
1177 })
1178}
1179
1180fn exec_ve(args: &[Value]) -> Result<Value, String> {
1183 use std::ffi::CString;
1184 let prog = CString::new(super::arg_str(args, 0))
1185 .map_err(|_| crate::host::type_error("process.execve: invalid file path"))?;
1186
1187 let argv_strs: Vec<String> = with_host(|h| match args.get(1).and_then(|v| h.get(v)) {
1188 Some(JsObj::Array(a)) => a.iter().map(|x| h.str_of(x)).collect(),
1189 _ => Vec::new(),
1190 });
1191 let env_strs: Vec<String> = {
1192 let from_arg = with_host(|h| match args.get(2).and_then(|v| h.get(v)) {
1193 Some(JsObj::Object(p)) => Some(
1194 p.iter()
1195 .map(|(k, v)| format!("{k}={}", h.str_of(v)))
1196 .collect::<Vec<_>>(),
1197 ),
1198 _ => None,
1199 });
1200 from_arg.unwrap_or_else(|| std::env::vars().map(|(k, v)| format!("{k}={v}")).collect())
1201 };
1202
1203 let to_c = |s: String| {
1204 CString::new(s).map_err(|_| crate::host::type_error("process.execve: NUL in argument"))
1205 };
1206 let argv_c: Vec<CString> = argv_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
1207 let env_c: Vec<CString> = env_strs.into_iter().map(to_c).collect::<Result<_, _>>()?;
1208
1209 let mut argv_p: Vec<*const libc::c_char> = argv_c.iter().map(|c| c.as_ptr()).collect();
1210 argv_p.push(std::ptr::null());
1211 let mut envp_p: Vec<*const libc::c_char> = env_c.iter().map(|c| c.as_ptr()).collect();
1212 envp_p.push(std::ptr::null());
1213
1214 unsafe {
1217 libc::execve(prog.as_ptr(), argv_p.as_ptr(), envp_p.as_ptr());
1218 }
1219 Err(crate::host::type_error(&format!(
1220 "process.execve failed: {}",
1221 std::io::Error::last_os_error()
1222 )))
1223}
1224
1225fn load_env_file(path: &str) -> Result<Value, String> {
1228 let path = if path.is_empty() { ".env" } else { path };
1229 let text =
1230 std::fs::read_to_string(path).map_err(|e| format!("Error: ENOENT: {e}, open '{path}'"))?;
1231 for line in text.lines() {
1232 let line = line.trim();
1233 if line.is_empty() || line.starts_with('#') {
1234 continue;
1235 }
1236 let line = line.strip_prefix("export ").unwrap_or(line);
1237 let Some((key, val)) = line.split_once('=') else {
1238 continue;
1239 };
1240 let key = key.trim();
1241 if key.is_empty() {
1242 continue;
1243 }
1244 let mut val = val.trim();
1245 if val.len() >= 2
1246 && ((val.starts_with('"') && val.ends_with('"'))
1247 || (val.starts_with('\'') && val.ends_with('\'')))
1248 {
1249 val = &val[1..val.len() - 1];
1250 }
1251 std::env::set_var(key, val);
1252 }
1253 Ok(Value::Undef)
1254}
1255
1256fn event_name(args: &[Value]) -> String {
1258 args.first()
1259 .map(|v| with_host(|h| h.str_of(v)))
1260 .unwrap_or_default()
1261}
1262
1263#[derive(Default)]
1275struct Stdin {
1276 listeners: Vec<(String, Value, bool)>,
1278 encoding: Option<String>,
1280 partial: Vec<u8>,
1282 pumping: bool,
1284 paused: bool,
1286 flowing: bool,
1287 readable_mode: bool,
1288 buffered: std::collections::VecDeque<Value>,
1290 waiters: std::collections::VecDeque<u32>,
1291 iterating: bool,
1292 ended: bool,
1293 pipe_dest: Option<Value>,
1295}
1296
1297thread_local! {
1298 static STDIN: std::cell::RefCell<Stdin> = std::cell::RefCell::new(Stdin::default());
1299}
1300
1301fn with_stdin<R>(f: impl FnOnce(&mut Stdin) -> R) -> R {
1302 STDIN.with(|s| f(&mut s.borrow_mut()))
1303}
1304
1305fn start_stdin() {
1307 let start = with_stdin(|s| {
1308 if s.pumping || s.paused || s.ended {
1309 return false;
1310 }
1311 s.pumping = true;
1312 true
1313 });
1314 if start {
1315 schedule_stdin_pump();
1316 }
1317}
1318
1319fn schedule_stdin_pump() {
1320 with_host(|h| {
1321 let cb = h.alloc(JsObj::Builtin("process.@@stdinPump".into()));
1322 h.add_timer(-1.0, cb, Vec::new(), None);
1323 });
1324}
1325
1326fn emit_stdin(recv: &Value, event: &str, args: Vec<Value>) -> Result<(), String> {
1327 let entries: Vec<(Value, bool)> = with_stdin(|s| {
1328 let found = s
1329 .listeners
1330 .iter()
1331 .filter(|(e, _, _)| e == event)
1332 .map(|(_, cb, once)| (cb.clone(), *once))
1333 .collect();
1334 s.listeners.retain(|(e, _, once)| !(e == event && *once));
1335 found
1336 });
1337 for (cb, _) in entries {
1338 crate::host::invoke(&cb, args.clone(), Some(recv.clone()))?;
1339 }
1340 Ok(())
1341}
1342
1343fn stdin_chunk(bytes: &[u8]) -> Option<Value> {
1347 let enc = with_stdin(|s| s.encoding.clone());
1348 let Some(enc) = enc else {
1349 return Some(super::buffer::from_bytes(bytes));
1350 };
1351 if enc != "utf8" && enc != "utf-8" {
1352 return Some(with_host(|h| {
1353 h.new_str(super::buffer::encode_bytes(bytes, &enc))
1354 }));
1355 }
1356 let mut all = with_stdin(|s| std::mem::take(&mut s.partial));
1357 all.extend_from_slice(bytes);
1358 let keep = incomplete_utf8_tail(&all);
1359 let tail = all.split_off(all.len() - keep);
1360 with_stdin(|s| s.partial = tail);
1361 if all.is_empty() {
1362 return None;
1363 }
1364 Some(with_host(|h| {
1365 h.new_str(String::from_utf8_lossy(&all).into_owned())
1366 }))
1367}
1368
1369fn incomplete_utf8_tail(b: &[u8]) -> usize {
1372 for back in 1..=3.min(b.len()) {
1373 let c = b[b.len() - back];
1374 if c & 0xC0 == 0x80 {
1375 continue;
1376 }
1377 let need = match c {
1378 0xC0..=0xDF => 2,
1379 0xE0..=0xEF => 3,
1380 0xF0..=0xF7 => 4,
1381 _ => 1,
1382 };
1383 return if need > back { back } else { 0 };
1384 }
1385 0
1386}
1387
1388fn stdin_pump() -> Result<(), String> {
1390 use std::io::Read;
1391 let recv = stdin_value();
1392 if with_stdin(|s| s.paused) {
1393 with_stdin(|s| s.pumping = false);
1394 return Ok(());
1395 }
1396 let mut buf = vec![0u8; 65536];
1397 let n = std::io::stdin().lock().read(&mut buf).unwrap_or(0);
1398 if n == 0 {
1399 return stdin_end(&recv);
1400 }
1401 schedule_stdin_pump();
1402 let Some(chunk) = stdin_chunk(&buf[..n]) else {
1403 return Ok(());
1404 };
1405 stdin_deliver(&recv, chunk)
1406}
1407
1408fn stdin_deliver(recv: &Value, chunk: Value) -> Result<(), String> {
1410 if let Some(dest) = with_stdin(|s| s.pipe_dest.clone()) {
1411 crate::host::call_method(&dest, "write", vec![chunk.clone()])?;
1412 }
1413 let (flowing, readable, waiter) = with_stdin(|s| {
1414 let waiter = if s.iterating {
1415 s.waiters.pop_front()
1416 } else {
1417 None
1418 };
1419 (s.flowing, s.readable_mode, waiter)
1420 });
1421 if let Some(id) = waiter {
1422 crate::host::resolve_promise_val(id, stdin_iter_result(chunk, false));
1423 return Ok(());
1424 }
1425 if flowing {
1426 emit_stdin(recv, "data", vec![chunk])?;
1427 } else if readable || with_stdin(|s| s.iterating) {
1428 with_stdin(|s| s.buffered.push_back(chunk));
1429 if readable {
1430 emit_stdin(recv, "readable", Vec::new())?;
1431 }
1432 }
1433 Ok(())
1434}
1435
1436fn stdin_end(recv: &Value) -> Result<(), String> {
1439 let (readable, waiters, dest) = with_stdin(|s| {
1440 s.ended = true;
1441 s.pumping = false;
1442 (
1443 s.readable_mode,
1444 std::mem::take(&mut s.waiters),
1445 s.pipe_dest.clone(),
1446 )
1447 });
1448 if readable {
1449 emit_stdin(recv, "readable", Vec::new())?;
1450 }
1451 for id in waiters {
1452 crate::host::resolve_promise_val(id, stdin_iter_result(Value::Undef, true));
1453 }
1454 if let Some(dest) = dest {
1456 let is_std = with_host(|h| match h.get(&dest) {
1457 Some(JsObj::Object(p)) => {
1458 p.get("@@native").map(|v| h.str_of(v)).as_deref() == Some("WriteStream")
1459 }
1460 _ => false,
1461 });
1462 if !is_std {
1463 crate::host::call_method(&dest, "end", Vec::new())?;
1464 }
1465 }
1466 emit_stdin(recv, "end", Vec::new())?;
1467 let target = recv.clone();
1468 with_host(|h| h.queue_micro_native(Box::new(move || emit_stdin(&target, "close", Vec::new()))));
1469 Ok(())
1470}
1471
1472fn stdin_iter_result(value: Value, done: bool) -> Value {
1473 with_host(|h| {
1474 let mut m = IndexMap::new();
1475 m.insert("value".into(), value);
1476 m.insert("done".into(), Value::Bool(done));
1477 h.new_object(m)
1478 })
1479}
1480
1481fn stdin_value() -> Value {
1483 constant("stdin").unwrap_or(Value::Undef)
1484}
1485
1486fn stdin_call(recv: &Value, method: &str, args: &[Value]) -> Option<Result<Value, String>> {
1489 let str_arg = |i: usize| with_host(|h| args.get(i).map(|v| h.str_of(v)).unwrap_or_default());
1490 Some(match method {
1491 "on" | "once" | "addListener" | "prependListener" => {
1492 let event = str_arg(0);
1493 if let Some(cb) = args.get(1).cloned() {
1494 with_stdin(|s| {
1495 let entry = (event.clone(), cb, method == "once");
1496 if method == "prependListener" {
1497 s.listeners.insert(0, entry);
1498 } else {
1499 s.listeners.push(entry);
1500 }
1501 match event.as_str() {
1502 "data" => s.flowing = true,
1503 "readable" => s.readable_mode = true,
1504 _ => {}
1505 }
1506 });
1507 if matches!(event.as_str(), "data" | "readable") {
1508 start_stdin();
1509 }
1510 }
1511 Ok(recv.clone())
1512 }
1513 "removeListener" | "off" => {
1514 let event = str_arg(0);
1515 if let Some(cb) = args.get(1) {
1516 with_stdin(|s| {
1517 let pos = s
1518 .listeners
1519 .iter()
1520 .position(|(e, f, _)| *e == event && with_host(|h| h.strict_eq(f, cb)));
1521 if let Some(i) = pos {
1522 s.listeners.remove(i);
1523 }
1524 });
1525 }
1526 Ok(recv.clone())
1527 }
1528 "removeAllListeners" => {
1529 let event = args
1530 .first()
1531 .filter(|v| !matches!(v, Value::Undef))
1532 .map(|_| str_arg(0));
1533 with_stdin(|s| match &event {
1534 Some(e) => s.listeners.retain(|(x, _, _)| x != e),
1535 None => s.listeners.clear(),
1536 });
1537 Ok(recv.clone())
1538 }
1539 "listenerCount" => {
1540 let event = str_arg(0);
1541 let n = with_stdin(|s| s.listeners.iter().filter(|(e, _, _)| *e == event).count());
1542 Ok(Value::Float(n as f64))
1543 }
1544 "setEncoding" => {
1545 let enc = match args.first() {
1546 Some(v) if !matches!(v, Value::Undef) => str_arg(0).to_ascii_lowercase(),
1547 _ => "utf8".to_string(),
1548 };
1549 with_stdin(|s| s.encoding = Some(enc));
1550 Ok(recv.clone())
1551 }
1552 "resume" => {
1553 with_stdin(|s| {
1554 s.paused = false;
1555 s.flowing = true;
1556 });
1557 start_stdin();
1558 Ok(recv.clone())
1559 }
1560 "pause" => {
1561 with_stdin(|s| s.paused = true);
1562 Ok(recv.clone())
1563 }
1564 "read" => {
1565 let chunk = with_stdin(|s| {
1566 let parts: Vec<Value> = s.buffered.drain(..).collect();
1567 (!parts.is_empty()).then_some(parts)
1568 });
1569 match chunk {
1570 None => Ok(with_host(|h| h.null())),
1571 Some(parts) if parts.len() == 1 => {
1572 Ok(parts.into_iter().next().unwrap_or(Value::Undef))
1573 }
1574 Some(parts) => {
1575 if with_stdin(|s| s.encoding.is_some()) {
1578 let joined: String =
1579 parts.iter().map(|p| with_host(|h| h.str_of(p))).collect();
1580 Ok(with_host(|h| h.new_str(joined)))
1581 } else {
1582 let bytes: Vec<u8> = parts
1583 .iter()
1584 .flat_map(|p| super::buffer::view_bytes(p).unwrap_or_default())
1585 .collect();
1586 Ok(super::buffer::from_bytes(&bytes))
1587 }
1588 }
1589 }
1590 }
1591 "pipe" => {
1592 let dest = args.first().cloned().unwrap_or(Value::Undef);
1593 with_stdin(|s| {
1594 s.pipe_dest = Some(dest.clone());
1595 s.flowing = true;
1596 });
1597 start_stdin();
1598 Ok(dest)
1599 }
1600 "@@asyncIterator" => {
1601 with_stdin(|s| s.iterating = true);
1602 start_stdin();
1603 Ok(with_host(|h| {
1604 let mut m = IndexMap::new();
1605 m.insert("@@native".into(), h.new_str("StdinIterator"));
1606 h.new_object(m)
1607 }))
1608 }
1609 _ => return None,
1610 })
1611}
1612
1613pub const STDIN_ITERATOR_METHODS: &[&str] = &["next", "return", "@@asyncIterator"];
1616
1617pub fn stdin_iterator_call(recv: &Value, method: &str) -> Result<Value, String> {
1618 match method {
1619 "@@asyncIterator" => Ok(recv.clone()),
1620 "next" => {
1621 let ready = with_stdin(|s| match s.buffered.pop_front() {
1622 Some(c) => Some(Some(c)),
1623 None if s.ended => Some(None),
1624 None => None,
1625 });
1626 match ready {
1627 Some(Some(c)) => crate::builtins::promise_resolve_pub(stdin_iter_result(c, false)),
1628 Some(None) => {
1629 crate::builtins::promise_resolve_pub(stdin_iter_result(Value::Undef, true))
1630 }
1631 None => {
1632 let (promise, id) = with_host(|h| {
1633 let p = h.new_promise();
1634 let id = h.promise_id(&p).unwrap_or(0);
1635 (p, id)
1636 });
1637 with_stdin(|s| s.waiters.push_back(id));
1638 Ok(promise)
1639 }
1640 }
1641 }
1642 "return" => {
1644 with_stdin(|s| {
1645 s.iterating = false;
1646 s.paused = true;
1647 });
1648 crate::builtins::promise_resolve_pub(stdin_iter_result(Value::Undef, true))
1649 }
1650 _ => Err(crate::host::type_error(&format!(
1651 "{method} is not a function"
1652 ))),
1653 }
1654}