1use crate::host::{with_host, JsObj};
4use fusevm::Value;
5use indexmap::IndexMap;
6
7pub const METHODS: &[&str] = &[
8 "format",
9 "formatWithOptions",
10 "inspect",
11 "deprecate",
12 "inherits",
13 "types",
14 "types.isMap",
15 "types.isSet",
16 "types.isPromise",
17 "types.isDate",
18 "types.isRegExp",
19 "types.isNativeError",
20 "types.isAsyncFunction",
21 "isDeepStrictEqual",
22 "isArray",
23 "debuglog",
24 "stripVTControlCharacters",
25 "toUSVString",
26 "getSystemErrorName",
27 "getSystemErrorMessage",
28 "getSystemErrorMap",
29 "styleText",
30 "parseArgs",
31 "promisify",
32 "callbackify",
33 "parseEnv",
34 "debug",
35];
36
37pub fn constant(name: &str) -> Option<Value> {
40 match name {
41 "types" => Some(with_host(|h| h.alloc(JsObj::Builtin("util/types".into())))),
42 "TextEncoder" | "TextDecoder" | "MIMEType" | "MIMEParams" => {
43 Some(with_host(|h| h.alloc(JsObj::Builtin(name.into()))))
44 }
45 _ => None,
46 }
47}
48
49pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
50 if let Some(pred) = method.strip_prefix("types.") {
51 return Some(Ok(Value::Bool(type_predicate(pred, args.first()))));
52 }
53 Some(match method {
54 "format" => {
55 let s = format(args);
58 Ok(with_host(|h| h.new_str(s)))
59 }
60 "formatWithOptions" => {
64 let s = format(args.get(1..).unwrap_or(&[]));
65 Ok(with_host(|h| h.new_str(s)))
66 }
67 "inspect" => {
68 let depth = match args.get(1) {
70 Some(opts) => match crate::builtins::get_property(opts, "depth") {
71 Ok(Value::Undef) => 2,
72 Ok(v) if with_host(|h| h.is_null(&v)) => usize::MAX,
73 Ok(v) => {
74 let n = with_host(|h| h.to_number(&v));
75 if n.is_finite() && n >= 0.0 {
76 n as usize
77 } else {
78 usize::MAX
79 }
80 }
81 Err(_) => 2,
82 },
83 None => 2,
84 };
85 crate::host::set_inspect_max_depth(depth);
86 let out = with_host(|h| {
87 let s = h.inspect(&args.first().cloned().unwrap_or(Value::Undef));
88 h.new_str(s)
89 });
90 crate::host::set_inspect_max_depth(2);
91 Ok(out)
92 }
93 "deprecate" => Ok(args.first().cloned().unwrap_or(Value::Undef)),
97 "inherits" => Ok(inherits(args)),
101 "types" => Ok(Value::Undef),
104 "isDeepStrictEqual" => Ok(Value::Bool(super::assert::deep_equal(
105 &args.first().cloned().unwrap_or(Value::Undef),
106 &args.get(1).cloned().unwrap_or(Value::Undef),
107 true,
108 ))),
109 "isArray" => Ok(Value::Bool(with_host(|h| {
111 matches!(
112 h.get(&args.first().cloned().unwrap_or(Value::Undef)),
113 Some(JsObj::Array(_))
114 )
115 }))),
116 "stripVTControlCharacters" => {
118 let stripped = strip_vt(&super::arg_str(args, 0));
119 Ok(with_host(|h| h.new_str(stripped)))
120 }
121 "toUSVString" => Ok(with_host(|h| {
125 let s = h.str_of(&args.first().cloned().unwrap_or(Value::Undef));
126 h.new_str(s)
127 })),
128 "getSystemErrorName" => {
129 let e = errno_of(super::arg_num(args, 0));
130 let name = errno_name(e)
131 .map(str::to_string)
132 .unwrap_or_else(|| format!("Unknown system error {e}"));
133 Ok(with_host(|h| h.new_str(name)))
134 }
135 "getSystemErrorMessage" => {
136 let e = errno_of(super::arg_num(args, 0));
137 let msg = errno_message(e)
138 .map(str::to_string)
139 .unwrap_or_else(|| format!("Unknown system error {e}"));
140 Ok(with_host(|h| h.new_str(msg)))
141 }
142 "getSystemErrorMap" => Ok(system_error_map()),
143 "styleText" => return Some(style_text(args)),
144 "parseArgs" => return Some(parse_args(args)),
145 "debuglog" => return Some(debuglog(args)),
146 "promisify" => return Some(promisify(args)),
147 "callbackify" => return Some(callbackify(args)),
148 "parseEnv" => Ok(parse_env(&super::arg_str(args, 0))),
150 "debug" => return Some(debuglog(args)),
152 _ => return None,
153 })
154}
155
156fn inherits(args: &[Value]) -> Value {
158 let ctor = args.first().cloned().unwrap_or(Value::Undef);
159 let sup = args.get(1).cloned().unwrap_or(Value::Undef);
160 with_host(|h| {
161 let sup_proto = h.fn_prop(&sup, "prototype").unwrap_or_else(|| {
164 let mut props = indexmap::IndexMap::new();
165 props.insert("constructor".to_string(), sup.clone());
166 let p = h.new_object(props);
167 h.set_fn_prop(&sup, "prototype", p.clone());
168 p
169 });
170 let ctor_proto = h.fn_prop(&ctor, "prototype").unwrap_or_else(|| {
172 let mut props = indexmap::IndexMap::new();
173 props.insert("constructor".to_string(), ctor.clone());
174 let p = h.new_object(props);
175 h.set_fn_prop(&ctor, "prototype", p.clone());
176 p
177 });
178 h.set_proto(&ctor_proto, sup_proto);
179 h.set_fn_prop(&ctor, "super_", sup);
180 });
181 Value::Undef
182}
183
184fn type_predicate(pred: &str, v: Option<&Value>) -> bool {
185 let Some(v) = v else { return false };
186 with_host(|h| match pred {
187 "isMap" => matches!(h.get(v), Some(JsObj::Map { weak: false, .. })),
188 "isSet" => matches!(h.get(v), Some(JsObj::Set { weak: false, .. })),
189 "isPromise" => matches!(h.get(v), Some(JsObj::Promise { .. })),
190 _ => false,
191 })
192}
193
194fn fmt_directive_number(n: f64) -> String {
198 if n == 0.0 && n.is_sign_negative() {
199 return "-0".into();
200 }
201 crate::host::fmt_number(n)
202}
203
204fn coerce_via(parser: &str, arg: &Value) -> f64 {
208 crate::builtins::call_builtin_function(parser, vec![arg.clone()])
209 .ok()
210 .map(|v| with_host(|h| h.to_number(&v)))
211 .unwrap_or(f64::NAN)
212}
213
214pub fn format(args: &[Value]) -> String {
217 if args.is_empty() {
218 return String::new();
219 }
220 if args.len() == 1 {
223 return with_host(|h| h.console_format(&args[0]));
224 }
225 let fmt = with_host(|h| h.str_of(&args[0]));
226 if !matches!(args[0], Value::Str(_))
228 && !with_host(|h| matches!(h.get(&args[0]), Some(JsObj::Str(_))))
229 {
230 return with_host(|h| {
231 args.iter()
232 .map(|a| h.console_format(a))
233 .collect::<Vec<_>>()
234 .join(" ")
235 });
236 }
237
238 let mut out = String::new();
239 let mut ai = 1usize;
240 let mut chars = fmt.chars().peekable();
241 while let Some(c) = chars.next() {
242 if c != '%' {
243 out.push(c);
244 continue;
245 }
246 let Some(&spec) = chars.peek() else {
247 out.push('%');
248 break;
249 };
250 if spec == '%' {
251 out.push('%');
252 chars.next();
253 continue;
254 }
255 if !matches!(spec, 's' | 'd' | 'i' | 'f' | 'j' | 'o' | 'O' | 'c') || ai >= args.len() {
256 out.push('%');
257 continue;
258 }
259 chars.next();
260 let arg = &args[ai];
261 ai += 1;
262 match spec {
263 's' => {
265 let s = with_host(|h| match h.get(arg) {
266 Some(JsObj::BigInt(b)) => format!("{b}n"),
267 _ => h.str_of(arg),
268 });
269 out.push_str(&s);
270 }
271 'd' | 'i' => {
280 let big = with_host(|h| match h.get(arg) {
281 Some(JsObj::BigInt(b)) => Some(format!("{b}n")),
282 _ => None,
283 });
284 match big {
285 Some(s) => out.push_str(&s),
286 None if spec == 'd' => {
287 out.push_str(&fmt_directive_number(with_host(|h| h.to_number(arg))))
288 }
289 None => out.push_str(&fmt_directive_number(coerce_via("parseInt", arg))),
290 }
291 }
292 'f' => out.push_str(&fmt_directive_number(coerce_via("parseFloat", arg))),
293 'j' => {
294 let s = crate::builtins::call_builtin_function("JSON.stringify", vec![arg.clone()])
295 .ok()
296 .map(|v| with_host(|h| h.str_of(&v)))
297 .unwrap_or_else(|| "undefined".into());
298 out.push_str(&s);
299 }
300 'o' | 'O' => out.push_str(&with_host(|h| h.inspect(arg))),
301 'c' => {} _ => {}
303 }
304 }
305 for a in &args[ai..] {
307 out.push(' ');
308 out.push_str(&with_host(|h| h.console_format(a)));
309 }
310 out
311}
312
313fn run_completion(src: &str) -> Result<Value, String> {
323 crate::eval_in_global_scope(src)
324}
325
326const PROMISIFY_SRC: &str = "(function(original){\n\
327 return function(){\n\
328 var self = this;\n\
329 var args = Array.prototype.slice.call(arguments);\n\
330 return new Promise(function(resolve, reject){\n\
331 args.push(function(err, value){ if (err) reject(err); else resolve(value); });\n\
332 original.apply(self, args);\n\
333 });\n\
334 };\n\
335})";
336
337const CALLBACKIFY_SRC: &str = "(function(original){\n\
338 return function(){\n\
339 var self = this;\n\
340 var args = Array.prototype.slice.call(arguments);\n\
341 var cb = args.pop();\n\
342 Promise.resolve(original.apply(self, args)).then(\n\
343 function(value){ cb.call(self, null, value); },\n\
344 function(err){ cb.call(self, err || new Error('Promise was rejected with a falsy value')); }\n\
345 );\n\
346 };\n\
347})";
348
349fn promisify(args: &[Value]) -> Result<Value, String> {
352 let orig = args.first().cloned().unwrap_or(Value::Undef);
353 if !with_host(|h| crate::host::is_callable(h, &orig)) {
354 return Err(std::format!(
355 "TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of \
356 type function. Received {}",
357 super::received_desc(&orig)
358 ));
359 }
360 let factory = run_completion(PROMISIFY_SRC)?;
361 crate::host::invoke(&factory, vec![orig], None)
362}
363
364fn callbackify(args: &[Value]) -> Result<Value, String> {
367 let orig = args.first().cloned().unwrap_or(Value::Undef);
368 if !with_host(|h| crate::host::is_callable(h, &orig)) {
369 return Err(std::format!(
370 "TypeError [ERR_INVALID_ARG_TYPE]: The \"original\" argument must be of \
371 type function. Received {}",
372 super::received_desc(&orig)
373 ));
374 }
375 let factory = run_completion(CALLBACKIFY_SRC)?;
376 crate::host::invoke(&factory, vec![orig], None)
377}
378
379const DEBUGLOG_ENABLED_SRC: &str = "(function(prefix){\n\
382 var util = require('util');\n\
383 return function(){\n\
384 console.error(prefix + ' ' + util.format.apply(null, arguments));\n\
385 };\n\
386})";
387
388fn debuglog(args: &[Value]) -> Result<Value, String> {
391 let section = super::arg_str(args, 0);
392 if debuglog_enabled(§ion) {
393 let prefix = format!("{} {}:", section.to_uppercase(), std::process::id());
394 let factory = run_completion(DEBUGLOG_ENABLED_SRC)?;
395 let pfx = with_host(|h| h.new_str(prefix));
396 crate::host::invoke(&factory, vec![pfx], None)
397 } else {
398 run_completion("(function(){})")
399 }
400}
401
402fn debuglog_enabled(section: &str) -> bool {
405 let Ok(env) = std::env::var("NODE_DEBUG") else {
406 return false;
407 };
408 let sec = section.to_uppercase();
409 env.split(|c: char| c == ',' || c.is_whitespace())
410 .filter(|s| !s.is_empty())
411 .any(|pat| {
412 let pat = pat.to_uppercase();
413 if pat.contains('*') {
414 wildcard_match(&pat, &sec)
415 } else {
416 pat == sec
417 }
418 })
419}
420
421fn wildcard_match(pat: &str, s: &str) -> bool {
423 let parts: Vec<&str> = pat.split('*').collect();
424 if parts.len() == 1 {
425 return pat == s;
426 }
427 let mut pos = 0usize;
428 for (i, part) in parts.iter().enumerate() {
429 if part.is_empty() {
430 continue;
431 }
432 if i == 0 {
433 if !s[pos..].starts_with(part) {
434 return false;
435 }
436 pos += part.len();
437 } else if i == parts.len() - 1 {
438 return s[pos..].ends_with(part);
439 } else if let Some(idx) = s[pos..].find(part) {
440 pos += idx + part.len();
441 } else {
442 return false;
443 }
444 }
445 true
446}
447
448fn strip_vt(s: &str) -> String {
453 let mut out = String::with_capacity(s.len());
454 let mut chars = s.chars().peekable();
455 while let Some(c) = chars.next() {
456 if c == '\u{9b}' {
457 while let Some(&n) = chars.peek() {
459 chars.next();
460 if ('\u{40}'..='\u{7e}').contains(&n) {
461 break;
462 }
463 }
464 continue;
465 }
466 if c != '\u{1b}' {
467 out.push(c);
468 continue;
469 }
470 match chars.peek() {
471 Some('[') => {
472 chars.next();
473 while let Some(&n) = chars.peek() {
474 chars.next();
475 if ('\u{40}'..='\u{7e}').contains(&n) {
476 break;
477 }
478 }
479 }
480 Some(']') => {
481 chars.next();
482 while let Some(&n) = chars.peek() {
483 if n == '\u{7}' {
484 chars.next();
485 break;
486 }
487 if n == '\u{1b}' {
488 chars.next();
489 if chars.peek() == Some(&'\\') {
490 chars.next();
491 }
492 break;
493 }
494 chars.next();
495 }
496 }
497 Some(_) => {
498 chars.next();
499 }
500 None => {}
501 }
502 }
503 out
504}
505
506fn style_names(v: &Value) -> Vec<String> {
510 with_host(|h| match h.get(v) {
511 Some(JsObj::Array(items)) => items.iter().map(|x| h.str_of(x)).collect(),
512 _ => vec![h.str_of(v)],
513 })
514}
515
516pub(crate) const STYLES: &[(&str, u16, u16)] = &[
527 ("reset", 0, 0),
528 ("bold", 1, 22),
529 ("dim", 2, 22),
530 ("italic", 3, 23),
531 ("underline", 4, 24),
532 ("blink", 5, 25),
533 ("inverse", 7, 27),
534 ("hidden", 8, 28),
535 ("strikethrough", 9, 29),
536 ("doubleunderline", 21, 24),
537 ("black", 30, 39),
538 ("red", 31, 39),
539 ("green", 32, 39),
540 ("yellow", 33, 39),
541 ("blue", 34, 39),
542 ("magenta", 35, 39),
543 ("cyan", 36, 39),
544 ("white", 37, 39),
545 ("bgBlack", 40, 49),
546 ("bgRed", 41, 49),
547 ("bgGreen", 42, 49),
548 ("bgYellow", 43, 49),
549 ("bgBlue", 44, 49),
550 ("bgMagenta", 45, 49),
551 ("bgCyan", 46, 49),
552 ("bgWhite", 47, 49),
553 ("framed", 51, 54),
554 ("overlined", 53, 55),
555 ("gray", 90, 39),
556 ("redBright", 91, 39),
557 ("greenBright", 92, 39),
558 ("yellowBright", 93, 39),
559 ("blueBright", 94, 39),
560 ("magentaBright", 95, 39),
561 ("cyanBright", 96, 39),
562 ("whiteBright", 97, 39),
563 ("bgGray", 100, 49),
564 ("bgRedBright", 101, 49),
565 ("bgGreenBright", 102, 49),
566 ("bgYellowBright", 103, 49),
567 ("bgBlueBright", 104, 49),
568 ("bgMagentaBright", 105, 49),
569 ("bgCyanBright", 106, 49),
570 ("bgWhiteBright", 107, 49),
571];
572
573pub(crate) const STYLE_ALIASES: &[(&str, u16, u16)] = &[
576 ("grey", 90, 39),
577 ("blackBright", 90, 39),
578 ("bgGrey", 100, 49),
579 ("bgBlackBright", 100, 49),
580 ("faint", 2, 22),
581 ("crossedout", 9, 29),
582 ("strikeThrough", 9, 29),
583 ("crossedOut", 9, 29),
584 ("conceal", 8, 28),
585 ("swapColors", 7, 27),
586 ("swapcolors", 7, 27),
587 ("doubleUnderline", 21, 24),
588];
589
590fn style_codes(name: &str) -> Option<(u16, u16)> {
592 STYLES
593 .iter()
594 .chain(STYLE_ALIASES.iter())
595 .find(|(n, _, _)| *n == name)
596 .map(|(_, o, c)| (*o, *c))
597}
598
599fn style_text(args: &[Value]) -> Result<Value, String> {
602 let fmt = args.first().cloned().unwrap_or(Value::Undef);
603 let names = style_names(&fmt);
604 let mut result = super::arg_str(args, 1);
605 for name in &names {
606 if name == "none" {
607 continue;
608 }
609 let (open, close) = style_codes(name).ok_or_else(|| {
610 let listed: Vec<String> = STYLES
613 .iter()
614 .chain(STYLE_ALIASES.iter())
615 .map(|(n, _, _)| format!("'{n}'"))
616 .collect();
617 crate::host::coded_error(
618 "TypeError",
619 "ERR_INVALID_ARG_VALUE",
620 &format!(
621 "The argument 'format' must be one of: {}. Received '{name}'",
622 listed.join(", ")
623 ),
624 )
625 })?;
626 result = format!("\u{1b}[{open}m{result}\u{1b}[{close}m");
627 }
628 Ok(with_host(|h| h.new_str(result)))
629}
630
631const ERRNO_TABLE: &[(&str, i32, &str)] = &[
639 ("E2BIG", libc::E2BIG, "argument list too long"),
640 ("EACCES", libc::EACCES, "permission denied"),
641 ("EADDRINUSE", libc::EADDRINUSE, "address already in use"),
642 (
643 "EADDRNOTAVAIL",
644 libc::EADDRNOTAVAIL,
645 "address not available",
646 ),
647 (
648 "EAFNOSUPPORT",
649 libc::EAFNOSUPPORT,
650 "address family not supported",
651 ),
652 ("EAGAIN", libc::EAGAIN, "resource temporarily unavailable"),
653 ("EALREADY", libc::EALREADY, "connection already in progress"),
654 ("EBADF", libc::EBADF, "bad file descriptor"),
655 ("EBUSY", libc::EBUSY, "resource busy or locked"),
656 ("ECANCELED", libc::ECANCELED, "operation canceled"),
657 (
658 "ECONNABORTED",
659 libc::ECONNABORTED,
660 "software caused connection abort",
661 ),
662 ("ECONNREFUSED", libc::ECONNREFUSED, "connection refused"),
663 ("ECONNRESET", libc::ECONNRESET, "connection reset by peer"),
664 (
665 "EDESTADDRREQ",
666 libc::EDESTADDRREQ,
667 "destination address required",
668 ),
669 ("EEXIST", libc::EEXIST, "file already exists"),
670 (
671 "EFAULT",
672 libc::EFAULT,
673 "bad address in system call argument",
674 ),
675 ("EFBIG", libc::EFBIG, "file too large"),
676 ("EHOSTDOWN", libc::EHOSTDOWN, "host is down"),
677 ("EHOSTUNREACH", libc::EHOSTUNREACH, "host is unreachable"),
678 ("EINTR", libc::EINTR, "interrupted system call"),
679 ("EINVAL", libc::EINVAL, "invalid argument"),
680 ("EIO", libc::EIO, "i/o error"),
681 ("EISCONN", libc::EISCONN, "socket is already connected"),
682 ("EISDIR", libc::EISDIR, "illegal operation on a directory"),
683 ("ELOOP", libc::ELOOP, "too many symbolic links encountered"),
684 ("EMFILE", libc::EMFILE, "too many open files"),
685 ("EMLINK", libc::EMLINK, "too many links"),
686 ("EMSGSIZE", libc::EMSGSIZE, "message too long"),
687 ("ENAMETOOLONG", libc::ENAMETOOLONG, "name too long"),
688 ("ENETDOWN", libc::ENETDOWN, "network is down"),
689 ("ENETUNREACH", libc::ENETUNREACH, "network is unreachable"),
690 ("ENFILE", libc::ENFILE, "file table overflow"),
691 ("ENOBUFS", libc::ENOBUFS, "no buffer space available"),
692 ("ENODEV", libc::ENODEV, "no such device"),
693 ("ENOENT", libc::ENOENT, "no such file or directory"),
694 ("ENOMEM", libc::ENOMEM, "not enough memory"),
695 ("ENOPROTOOPT", libc::ENOPROTOOPT, "protocol not available"),
696 ("ENOSPC", libc::ENOSPC, "no space left on device"),
697 ("ENOSYS", libc::ENOSYS, "function not implemented"),
698 ("ENOTCONN", libc::ENOTCONN, "socket is not connected"),
699 ("ENOTDIR", libc::ENOTDIR, "not a directory"),
700 ("ENOTEMPTY", libc::ENOTEMPTY, "directory not empty"),
701 ("ENOTSOCK", libc::ENOTSOCK, "socket operation on non-socket"),
702 ("ENXIO", libc::ENXIO, "no such device or address"),
703 (
704 "EOPNOTSUPP",
705 libc::EOPNOTSUPP,
706 "operation not supported on socket",
707 ),
708 (
709 "EOVERFLOW",
710 libc::EOVERFLOW,
711 "value too large for defined data type",
712 ),
713 ("EPERM", libc::EPERM, "operation not permitted"),
714 ("EPIPE", libc::EPIPE, "broken pipe"),
715 ("EPROTO", libc::EPROTO, "protocol error"),
716 (
717 "EPROTONOSUPPORT",
718 libc::EPROTONOSUPPORT,
719 "protocol not supported",
720 ),
721 (
722 "EPROTOTYPE",
723 libc::EPROTOTYPE,
724 "protocol wrong type for socket",
725 ),
726 ("ERANGE", libc::ERANGE, "result too large"),
727 ("EROFS", libc::EROFS, "read-only file system"),
728 (
729 "ESHUTDOWN",
730 libc::ESHUTDOWN,
731 "cannot send after transport endpoint shutdown",
732 ),
733 ("ESPIPE", libc::ESPIPE, "invalid seek"),
734 ("ESRCH", libc::ESRCH, "no such process"),
735 ("ETIMEDOUT", libc::ETIMEDOUT, "connection timed out"),
736 ("ETXTBSY", libc::ETXTBSY, "text file is busy"),
737 ("EXDEV", libc::EXDEV, "cross-device link not permitted"),
738];
739
740fn errno_of(err: f64) -> i32 {
743 if err < 0.0 {
744 (-err) as i32
745 } else {
746 err as i32
747 }
748}
749
750fn errno_name(e: i32) -> Option<&'static str> {
751 ERRNO_TABLE
752 .iter()
753 .find(|(_, code, _)| *code == e)
754 .map(|(n, _, _)| *n)
755}
756
757fn errno_message(e: i32) -> Option<&'static str> {
758 ERRNO_TABLE
759 .iter()
760 .find(|(_, code, _)| *code == e)
761 .map(|(_, _, m)| *m)
762}
763
764fn system_error_map() -> Value {
766 with_host(|h| {
767 let mut entries = indexmap::IndexMap::new();
768 for (name, code, msg) in ERRNO_TABLE {
769 let key_val = Value::Float(-(*code as f64));
770 let name_v = h.new_str(*name);
771 let msg_v = h.new_str(*msg);
772 let pair = h.new_array(vec![name_v, msg_v]);
773 let key = crate::host::map_key(h, &key_val);
774 entries.insert(key, (key_val, pair));
775 }
776 h.alloc(JsObj::Map {
777 entries,
778 weak: false,
779 })
780 })
781}
782
783enum Slot {
787 Bool(bool),
788 Str(String),
789 ListBool(Vec<bool>),
790 ListStr(Vec<String>),
791}
792
793struct OptCfg {
795 long: String,
796 is_string: bool,
797 multiple: bool,
798 short: Option<String>,
799 default: Option<Value>,
800}
801
802fn parse_args(config_args: &[Value]) -> Result<Value, String> {
805 let config = config_args.first().cloned().unwrap_or(Value::Undef);
806 let tokens = read_arg_tokens(&config);
807 let strict = read_bool_prop(&config, "strict", true);
808 let allow_positionals = read_bool_prop(&config, "allowPositionals", false);
809 let allow_negative = read_bool_prop(&config, "allowNegative", false);
810 let opts = read_options(&config);
811
812 let lookup_long = |name: &str| opts.iter().find(|o| o.long == name);
813 let lookup_short = |c: &str| opts.iter().find(|o| o.short.as_deref() == Some(c));
814 let is_bool_long = |name: &str| opts.iter().any(|o| o.long == name && !o.is_string);
815
816 let mut values: IndexMap<String, Slot> = IndexMap::new();
817 let mut positionals: Vec<String> = Vec::new();
818
819 let mut i = 0usize;
820 while i < tokens.len() {
821 let tok = tokens[i].clone();
822 if tok == "--" {
823 for t in &tokens[i + 1..] {
824 positionals.push(t.clone());
825 }
826 break;
827 }
828 if let Some(rest) = tok.strip_prefix("--") {
829 let (raw_name, inline) = match rest.split_once('=') {
830 Some((n, v)) => (n.to_string(), Some(v.to_string())),
831 None => (rest.to_string(), None),
832 };
833 let (name, negate) = match raw_name.strip_prefix("no-") {
835 Some(base) if allow_negative && is_bool_long(base) => (base.to_string(), true),
836 _ => (raw_name, false),
837 };
838 match lookup_long(&name) {
839 None if strict => {
840 return Err(format!(
841 "TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '--{name}'"
842 ))
843 }
844 None => {
845 store(&mut values, &name, Slot::Bool(true), false);
847 }
848 Some(cfg) if cfg.is_string => {
849 let val = match inline {
850 Some(v) => v,
851 None => {
852 i += 1;
853 tokens.get(i).cloned().ok_or_else(|| {
854 format!(
855 "TypeError [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
856 Option '--{name} <value>' argument missing"
857 )
858 })?
859 }
860 };
861 store(&mut values, &cfg.long, Slot::Str(val), cfg.multiple);
862 }
863 Some(cfg) => {
864 if inline.is_some() && strict {
865 return Err(format!(
866 "TypeError [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
867 Option '--{}' does not take an argument",
868 cfg.long
869 ));
870 }
871 store(&mut values, &cfg.long, Slot::Bool(!negate), cfg.multiple);
872 }
873 }
874 } else if tok.len() > 1 && tok.starts_with('-') {
875 let chars: Vec<char> = tok[1..].chars().collect();
876 let mut ci = 0usize;
877 while ci < chars.len() {
878 let short = chars[ci].to_string();
879 match lookup_short(&short) {
880 None if strict => {
881 return Err(format!(
882 "TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '-{short}'"
883 ))
884 }
885 None => {
886 store(&mut values, &short, Slot::Bool(true), false);
887 ci += 1;
888 }
889 Some(cfg) if cfg.is_string => {
890 let remainder: String = chars[ci + 1..].iter().collect();
891 let val = if !remainder.is_empty() {
892 remainder
893 } else {
894 i += 1;
895 tokens.get(i).cloned().ok_or_else(|| {
896 format!(
897 "TypeError [ERR_PARSE_ARGS_INVALID_OPTION_VALUE]: \
898 Option '-{short}, --{} <value>' argument missing",
899 cfg.long
900 )
901 })?
902 };
903 store(&mut values, &cfg.long, Slot::Str(val), cfg.multiple);
904 break;
905 }
906 Some(cfg) => {
907 store(&mut values, &cfg.long, Slot::Bool(true), cfg.multiple);
908 ci += 1;
909 }
910 }
911 }
912 } else {
913 if !allow_positionals && strict {
914 return Err(format!(
915 "TypeError [ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL]: \
916 Unexpected argument '{tok}'. This command does not take positional arguments"
917 ));
918 }
919 positionals.push(tok);
920 }
921 i += 1;
922 }
923
924 for cfg in &opts {
926 if !values.contains_key(&cfg.long) {
927 if let Some(def) = &cfg.default {
928 store_default(&mut values, cfg, def.clone());
929 }
930 }
931 }
932
933 Ok(build_parse_result(values, positionals))
934}
935
936fn store(values: &mut IndexMap<String, Slot>, name: &str, slot: Slot, multiple: bool) {
938 if !multiple {
939 values.insert(name.to_string(), slot);
940 return;
941 }
942 match values.get_mut(name) {
943 Some(Slot::ListBool(v)) => {
944 if let Slot::Bool(b) = slot {
945 v.push(b);
946 }
947 }
948 Some(Slot::ListStr(v)) => {
949 if let Slot::Str(s) = slot {
950 v.push(s);
951 }
952 }
953 _ => {
954 let init = match slot {
955 Slot::Bool(b) => Slot::ListBool(vec![b]),
956 Slot::Str(s) => Slot::ListStr(vec![s]),
957 other => other,
958 };
959 values.insert(name.to_string(), init);
960 }
961 }
962}
963
964fn store_default(values: &mut IndexMap<String, Slot>, cfg: &OptCfg, def: Value) {
966 let slot = with_host(|h| match h.get(&def) {
969 Some(JsObj::Array(items)) => {
970 if cfg.is_string {
971 Slot::ListStr(items.iter().map(|v| h.str_of(v)).collect())
972 } else {
973 Slot::ListBool(items.iter().map(|v| h.truthy(v)).collect())
974 }
975 }
976 _ if cfg.is_string => Slot::Str(h.str_of(&def)),
977 _ => Slot::Bool(h.truthy(&def)),
978 });
979 values.insert(cfg.long.clone(), slot);
980}
981
982fn build_parse_result(values: IndexMap<String, Slot>, positionals: Vec<String>) -> Value {
984 with_host(|h| {
985 let mut vobj = IndexMap::new();
986 for (k, slot) in values {
987 let v = match slot {
988 Slot::Bool(b) => Value::Bool(b),
989 Slot::Str(s) => h.new_str(s),
990 Slot::ListBool(items) => {
991 let arr = items.into_iter().map(Value::Bool).collect();
992 h.new_array(arr)
993 }
994 Slot::ListStr(items) => {
995 let arr = items.into_iter().map(|s| h.new_str(s)).collect();
996 h.new_array(arr)
997 }
998 };
999 vobj.insert(k, v);
1000 }
1001 let values_v = h.new_object(vobj);
1002 let pos: Vec<Value> = positionals.into_iter().map(|s| h.new_str(s)).collect();
1003 let positionals_v = h.new_array(pos);
1004 let mut out = IndexMap::new();
1005 out.insert("values".to_string(), values_v);
1006 out.insert("positionals".to_string(), positionals_v);
1007 h.new_object(out)
1008 })
1009}
1010
1011fn read_arg_tokens(config: &Value) -> Vec<String> {
1013 let arr = crate::builtins::get_property(config, "args").unwrap_or(Value::Undef);
1014 let from_config = with_host(|h| match h.get(&arr) {
1015 Some(JsObj::Array(items)) => Some(items.iter().map(|v| h.str_of(v)).collect::<Vec<_>>()),
1016 _ => None,
1017 });
1018 from_config.unwrap_or_else(|| std::env::args().skip(2).collect())
1019}
1020
1021fn read_bool_prop(config: &Value, name: &str, default: bool) -> bool {
1023 match crate::builtins::get_property(config, name) {
1024 Ok(Value::Undef) | Err(_) => default,
1025 Ok(v) => with_host(|h| h.truthy(&v)),
1026 }
1027}
1028
1029fn read_options(config: &Value) -> Vec<OptCfg> {
1031 let options = crate::builtins::get_property(config, "options").unwrap_or(Value::Undef);
1032 let keys: Vec<String> = with_host(|h| match h.get(&options) {
1033 Some(JsObj::Object(m)) => m.keys().filter(|k| !k.starts_with("@@")).cloned().collect(),
1034 _ => Vec::new(),
1035 });
1036 keys.into_iter()
1037 .map(|long| {
1038 let spec = crate::builtins::get_property(&options, &long).unwrap_or(Value::Undef);
1039 let type_str = crate::builtins::get_property(&spec, "type")
1040 .ok()
1041 .map(|v| with_host(|h| h.str_of(&v)))
1042 .unwrap_or_default();
1043 let short = match crate::builtins::get_property(&spec, "short") {
1044 Ok(Value::Undef) | Err(_) => None,
1045 Ok(v) => Some(with_host(|h| h.str_of(&v))),
1046 };
1047 let multiple = read_bool_prop(&spec, "multiple", false);
1048 let default = match crate::builtins::get_property(&spec, "default") {
1049 Ok(Value::Undef) | Err(_) => None,
1050 Ok(v) => Some(v),
1051 };
1052 OptCfg {
1053 long,
1054 is_string: type_str == "string",
1055 multiple,
1056 short,
1057 default,
1058 }
1059 })
1060 .collect()
1061}
1062
1063fn env_trim(s: &str) -> &str {
1073 s.trim_matches(|c: char| c == ' ' || c == '\t' || c == '\n')
1074}
1075
1076fn parse_env(input: &str) -> Value {
1078 let lines = input.replace('\r', "");
1079 let mut pairs: IndexMap<String, String> = IndexMap::new();
1080 let mut content: &str = env_trim(&lines);
1081
1082 while !content.is_empty() {
1083 let first = content.as_bytes()[0];
1084 if first == b'\n' || first == b'#' {
1086 match content.find('\n') {
1087 Some(nl) => content = &content[nl + 1..],
1088 None => content = "",
1089 }
1090 continue;
1091 }
1092 let Some(eq_or_nl) = content.find(['=', '\n']) else {
1094 break;
1095 };
1096 if content.as_bytes()[eq_or_nl] == b'\n' {
1097 content = env_trim(&content[eq_or_nl + 1..]);
1098 continue;
1099 }
1100 let mut key = env_trim(&content[..eq_or_nl]);
1102 content = &content[eq_or_nl + 1..];
1103 if content.is_empty() || content.as_bytes()[0] == b'\n' {
1105 pairs.insert(key.to_string(), String::new());
1106 continue;
1107 }
1108 content = env_trim(content);
1109 if key.is_empty() {
1111 continue;
1112 }
1113 if let Some(rest) = key.strip_prefix("export ") {
1115 key = env_trim(rest);
1116 }
1117 if content.is_empty() {
1118 pairs.insert(key.to_string(), String::new());
1119 break;
1120 }
1121 let vfirst = content.as_bytes()[0];
1122 if vfirst == b'"' {
1124 if let Some(rel) = content[1..].find('"') {
1125 let closing = rel + 1;
1126 let value = content[1..closing].replace("\\n", "\n");
1127 pairs.insert(key.to_string(), value);
1128 match content[closing + 1..].find('\n') {
1129 Some(nl) => content = &content[closing + 1 + nl + 1..],
1130 None => content = "",
1131 }
1132 continue;
1133 }
1134 }
1136 if vfirst == b'\'' || vfirst == b'"' || vfirst == b'`' {
1138 match content[1..].find(vfirst as char) {
1139 None => match content.find('\n') {
1140 Some(nl) => {
1141 pairs.insert(key.to_string(), content[..nl].to_string());
1142 content = &content[nl + 1..];
1143 }
1144 None => {
1145 pairs.insert(key.to_string(), content.to_string());
1146 break;
1147 }
1148 },
1149 Some(rel) => {
1150 let closing = rel + 1;
1151 pairs.insert(key.to_string(), content[1..closing].to_string());
1152 match content[closing + 1..].find('\n') {
1153 Some(nl) => content = &content[closing + 1 + nl + 1..],
1154 None => content = "",
1155 }
1156 continue;
1157 }
1158 }
1159 } else {
1160 let (raw, next) = match content.find('\n') {
1162 Some(nl) => (&content[..nl], &content[nl + 1..]),
1163 None => (content, ""),
1164 };
1165 let value = match raw.find('#') {
1166 Some(h) => &raw[..h],
1167 None => raw,
1168 };
1169 pairs.insert(key.to_string(), env_trim(value).to_string());
1170 content = next;
1171 }
1172 content = env_trim(content);
1173 }
1174
1175 pairs.sort_keys();
1176 with_host(|h| {
1177 let mut m = IndexMap::new();
1178 for (k, v) in pairs {
1179 let val = h.new_str(v);
1180 m.insert(k, val);
1181 }
1182 h.new_object(m)
1183 })
1184}
1185
1186fn is_token_char(c: char) -> bool {
1195 c.is_ascii_alphanumeric()
1196 || matches!(
1197 c,
1198 '!' | '#'
1199 | '$'
1200 | '%'
1201 | '&'
1202 | '\''
1203 | '*'
1204 | '+'
1205 | '-'
1206 | '.'
1207 | '^'
1208 | '_'
1209 | '`'
1210 | '|'
1211 | '~'
1212 )
1213}
1214
1215fn is_quoted_string_char(c: char) -> bool {
1218 c == '\t' || ('\u{20}'..='\u{7e}').contains(&c) || ('\u{80}'..='\u{ff}').contains(&c)
1219}
1220
1221fn is_http_ws(c: char) -> bool {
1223 matches!(c, '\r' | '\n' | '\t' | ' ')
1224}
1225
1226fn ascii_lower(s: &str) -> String {
1229 s.to_ascii_lowercase()
1230}
1231
1232fn mime_syntax_err(part: &str, s: &str, index: Option<usize>) -> String {
1235 match index {
1236 Some(i) => format!(
1237 "TypeError [ERR_INVALID_MIME_SYNTAX]: The MIME syntax for a {part} in \"{s}\" is invalid at {i}"
1238 ),
1239 None => format!(
1240 "TypeError [ERR_INVALID_MIME_SYNTAX]: The MIME syntax for a {part} in \"{s}\" is invalid"
1241 ),
1242 }
1243}
1244
1245fn parse_type_and_subtype(s: &str) -> Result<(String, String, String), String> {
1248 let chars: Vec<char> = s.chars().collect();
1249 let n = chars.len();
1250 let mut pos = 0;
1252 while pos < n && is_http_ws(chars[pos]) {
1253 pos += 1;
1254 }
1255 let type_end = (pos..n).find(|&i| chars[i] == '/');
1257 let trimmed_type: String = match type_end {
1258 Some(e) => chars[pos..e].iter().collect(),
1259 None => chars[pos..].iter().collect(),
1260 };
1261 let type_invalid = trimmed_type.chars().position(|c| !is_token_char(c));
1262 if trimmed_type.is_empty() || type_invalid.is_some() || type_end.is_none() {
1263 return Err(mime_syntax_err("type", s, type_invalid));
1264 }
1265 let type_end = type_end.unwrap();
1266 pos = type_end + 1;
1267 let mime_type = ascii_lower(&trimmed_type);
1268 let sub_end = (pos..n).find(|&i| chars[i] == ';');
1270 let raw_subtype: &[char] = match sub_end {
1271 Some(e) => &chars[pos..e],
1272 None => &chars[pos..],
1273 };
1274 let mut new_pos = pos + raw_subtype.len();
1275 if sub_end.is_some() {
1276 new_pos += 1;
1277 }
1278 let mut end = raw_subtype.len();
1280 while end > 0 && is_http_ws(raw_subtype[end - 1]) {
1281 end -= 1;
1282 }
1283 let trimmed_subtype: String = raw_subtype[..end].iter().collect();
1284 let sub_invalid = trimmed_subtype.chars().position(|c| !is_token_char(c));
1285 if trimmed_subtype.is_empty() || sub_invalid.is_some() {
1286 return Err(mime_syntax_err("subtype", s, sub_invalid));
1287 }
1288 let subtype = ascii_lower(&trimmed_subtype);
1289 let params: String = chars[new_pos.min(n)..].iter().collect();
1290 Ok((mime_type, subtype, params))
1291}
1292
1293fn scan_quoted(chars: &[char], start: usize) -> (usize, bool, bool) {
1296 let n = chars.len();
1297 let mut i = start;
1298 let mut lone_backslash = false;
1299 let mut closing_quote = false;
1300 while i < n {
1301 match chars[i] {
1302 '\\' => {
1303 if i + 1 >= n {
1304 lone_backslash = true;
1305 i += 1;
1306 break;
1307 }
1308 i += 2;
1309 }
1310 '"' => {
1311 closing_quote = true;
1312 i += 1;
1313 break;
1314 }
1315 _ => i += 1,
1316 }
1317 }
1318 (i - start, lone_backslash, closing_quote)
1319}
1320
1321fn remove_backslashes(s: &[char]) -> String {
1323 let n = s.len();
1324 if n == 0 {
1325 return String::new();
1326 }
1327 let mut ret = String::new();
1328 let mut i = 0usize;
1329 while i < n - 1 {
1330 if s[i] == '\\' {
1331 i += 1;
1332 ret.push(s[i]);
1333 } else {
1334 ret.push(s[i]);
1335 }
1336 i += 1;
1337 }
1338 if i == n - 1 {
1339 ret.push(s[i]);
1340 }
1341 ret
1342}
1343
1344fn parse_mime_params(s: &str) -> Vec<(String, String)> {
1346 let chars: Vec<char> = s.chars().collect();
1347 let n = chars.len();
1348 let mut end_of_source = n;
1350 while end_of_source > 0 && is_http_ws(chars[end_of_source - 1]) {
1351 end_of_source -= 1;
1352 }
1353 let mut out: Vec<(String, String)> = Vec::new();
1354 let mut position = 0usize;
1355 while position < end_of_source {
1356 while position < n && is_http_ws(chars[position]) {
1358 position += 1;
1359 }
1360 let mut after = position;
1362 while after < n && chars[after] != ';' && chars[after] != '=' {
1363 after += 1;
1364 }
1365 let name = ascii_lower(&chars[position..after].iter().collect::<String>());
1366 position = after;
1367 if position < end_of_source {
1368 let ch = chars[position];
1369 position += 1;
1370 if ch == ';' {
1372 continue;
1373 }
1374 }
1375 if position >= end_of_source {
1376 break;
1377 }
1378 let value = if chars[position] == '"' {
1379 position += 1;
1381 let (matched_len, lone_backslash, closing_quote) = scan_quoted(&chars, position);
1382 let matched = &chars[position..position + matched_len];
1383 position += matched_len;
1384 let inside: &[char] = if lone_backslash || closing_quote {
1385 &matched[..matched.len().saturating_sub(1)]
1386 } else {
1387 matched
1388 };
1389 let mut v = remove_backslashes(inside);
1390 if lone_backslash {
1391 v.push('\\');
1392 }
1393 v
1394 } else {
1395 let value_end = (position..n).find(|&i| chars[i] == ';').unwrap_or(n);
1397 let raw = &chars[position..value_end];
1398 position += raw.len();
1399 let mut end = raw.len();
1400 while end > 0 && is_http_ws(raw[end - 1]) {
1401 end -= 1;
1402 }
1403 let trimmed: String = raw[..end].iter().collect();
1404 if trimmed.is_empty() {
1405 continue;
1408 }
1409 trimmed
1410 };
1411 let name_ok = !name.is_empty() && name.chars().all(is_token_char);
1413 let value_ok = value.chars().all(is_quoted_string_char);
1414 if name_ok && value_ok && !out.iter().any(|(k, _)| *k == name) {
1415 out.push((name, value));
1416 }
1417 position += 1;
1418 }
1419 out
1420}
1421
1422fn encode_param_value(value: &str) -> String {
1425 if value.is_empty() {
1426 return "\"\"".to_string();
1427 }
1428 if value.chars().all(is_token_char) {
1429 return value.to_string();
1430 }
1431 let mut escaped = String::with_capacity(value.len() + 2);
1432 for c in value.chars() {
1433 if c == '"' || c == '\\' {
1434 escaped.push('\\');
1435 }
1436 escaped.push(c);
1437 }
1438 format!("\"{escaped}\"")
1439}
1440
1441fn serialize_mime_params(pairs: &[(String, String)]) -> String {
1443 let mut ret = String::new();
1444 for (k, v) in pairs {
1445 if !ret.is_empty() {
1446 ret.push(';');
1447 }
1448 ret.push_str(k);
1449 ret.push('=');
1450 ret.push_str(&encode_param_value(v));
1451 }
1452 ret
1453}
1454
1455pub const MIME_PARAMS_METHODS: &[&str] = &[
1458 "get",
1459 "set",
1460 "has",
1461 "delete",
1462 "entries",
1463 "keys",
1464 "values",
1465 "toString",
1466 "toJSON",
1467 "@@iterator",
1468];
1469
1470pub const MIME_TYPE_METHODS: &[&str] = &["toString", "toJSON"];
1472
1473fn make_mime_params(pairs: &[(String, String)]) -> Value {
1475 with_host(|h| {
1476 let items: Vec<Value> = pairs
1477 .iter()
1478 .map(|(k, v)| {
1479 let kv = vec![h.new_str(k.clone()), h.new_str(v.clone())];
1480 h.new_array(kv)
1481 })
1482 .collect();
1483 let arr = h.new_array(items);
1484 let mut m = IndexMap::new();
1485 m.insert("@@native".into(), h.new_str("MIMEParams"));
1486 m.insert("@@pairs".into(), arr);
1487 h.new_object(m)
1488 })
1489}
1490
1491fn mime_pairs_of(recv: &Value) -> Vec<(String, String)> {
1493 with_host(|h| {
1494 let items: Vec<Value> = match h.get(recv) {
1495 Some(JsObj::Object(p)) => match p.get("@@pairs").and_then(|a| h.get(a)) {
1496 Some(JsObj::Array(items)) => items.clone(),
1497 _ => Vec::new(),
1498 },
1499 _ => Vec::new(),
1500 };
1501 items
1502 .iter()
1503 .map(|it| match h.get(it) {
1504 Some(JsObj::Array(kv)) => {
1505 let kv = kv.clone();
1506 let k = kv.first().map(|x| h.str_of(x)).unwrap_or_default();
1507 let v = kv.get(1).map(|x| h.str_of(x)).unwrap_or_default();
1508 (k, v)
1509 }
1510 _ => (h.str_of(it), String::new()),
1511 })
1512 .collect()
1513 })
1514}
1515
1516fn set_mime_pairs(recv: &Value, pairs: &[(String, String)]) {
1518 with_host(|h| {
1519 let items: Vec<Value> = pairs
1520 .iter()
1521 .map(|(k, v)| {
1522 let kv = vec![h.new_str(k.clone()), h.new_str(v.clone())];
1523 h.new_array(kv)
1524 })
1525 .collect();
1526 let arr = h.new_array(items);
1527 if let Some(JsObj::Object(p)) = h.get_mut(recv) {
1528 p.insert("@@pairs".into(), arr);
1529 }
1530 });
1531}
1532
1533pub fn construct_mime_params(_args: &[Value]) -> Result<Value, String> {
1536 Ok(make_mime_params(&[]))
1537}
1538
1539pub fn mime_params_instance_call(
1541 recv: &Value,
1542 method: &str,
1543 args: &[Value],
1544) -> Result<Value, String> {
1545 match method {
1546 "get" => {
1547 let name = super::arg_str(args, 0);
1548 match mime_pairs_of(recv).into_iter().find(|(k, _)| *k == name) {
1549 Some((_, v)) => Ok(with_host(|h| h.new_str(v))),
1550 None => Ok(with_host(|h| h.null())),
1551 }
1552 }
1553 "has" => {
1554 let name = super::arg_str(args, 0);
1555 Ok(Value::Bool(
1556 mime_pairs_of(recv).iter().any(|(k, _)| *k == name),
1557 ))
1558 }
1559 "set" => {
1560 let name = super::arg_str(args, 0);
1561 let value = super::arg_str(args, 1);
1562 if let Some(i) = name.chars().position(|c| !is_token_char(c)) {
1563 return Err(mime_syntax_err("parameter name", &name, Some(i)));
1564 }
1565 if name.is_empty() {
1566 return Err(mime_syntax_err("parameter name", &name, None));
1567 }
1568 if let Some(i) = value.chars().position(|c| !is_quoted_string_char(c)) {
1569 return Err(mime_syntax_err("parameter value", &value, Some(i)));
1570 }
1571 let mut pairs = mime_pairs_of(recv);
1572 match pairs.iter_mut().find(|(k, _)| *k == name) {
1573 Some(slot) => slot.1 = value,
1574 None => pairs.push((name, value)),
1575 }
1576 set_mime_pairs(recv, &pairs);
1577 Ok(Value::Undef)
1578 }
1579 "delete" => {
1580 let name = super::arg_str(args, 0);
1581 let mut pairs = mime_pairs_of(recv);
1582 pairs.retain(|(k, _)| *k != name);
1583 set_mime_pairs(recv, &pairs);
1584 Ok(Value::Undef)
1585 }
1586 "keys" => {
1587 let pairs = mime_pairs_of(recv);
1588 Ok(with_host(|h| {
1589 let items = pairs.into_iter().map(|(k, _)| h.new_str(k)).collect();
1590 h.alloc(JsObj::Iter { items, idx: 0 })
1591 }))
1592 }
1593 "values" => {
1594 let pairs = mime_pairs_of(recv);
1595 Ok(with_host(|h| {
1596 let items = pairs.into_iter().map(|(_, v)| h.new_str(v)).collect();
1597 h.alloc(JsObj::Iter { items, idx: 0 })
1598 }))
1599 }
1600 "entries" | "@@iterator" => {
1601 let pairs = mime_pairs_of(recv);
1602 Ok(with_host(|h| {
1603 let items = pairs
1604 .into_iter()
1605 .map(|(k, v)| {
1606 let kv = vec![h.new_str(k), h.new_str(v)];
1607 h.new_array(kv)
1608 })
1609 .collect();
1610 h.alloc(JsObj::Iter { items, idx: 0 })
1611 }))
1612 }
1613 "toString" | "toJSON" => {
1614 let s = serialize_mime_params(&mime_pairs_of(recv));
1615 Ok(with_host(|h| h.new_str(s)))
1616 }
1617 _ => Err(crate::host::type_error(&format!(
1618 "mimeParams.{method} is not a function"
1619 ))),
1620 }
1621}
1622
1623pub fn construct_mime_type(args: &[Value]) -> Result<Value, String> {
1625 let input = with_host(|h| h.str_of(&args.first().cloned().unwrap_or(Value::Undef)));
1628 let (mime_type, subtype, params_str) = parse_type_and_subtype(&input)?;
1629 let essence = format!("{mime_type}/{subtype}");
1630 let params = make_mime_params(&parse_mime_params(¶ms_str));
1632 Ok(with_host(|h| {
1633 let mut m = IndexMap::new();
1634 m.insert("@@native".into(), h.new_str("MIMEType"));
1635 m.insert("type".into(), h.new_str(mime_type));
1636 m.insert("subtype".into(), h.new_str(subtype));
1637 m.insert("essence".into(), h.new_str(essence));
1638 m.insert("params".into(), params);
1639 h.new_object(m)
1640 }))
1641}
1642
1643pub fn mime_type_instance_call(
1647 recv: &Value,
1648 method: &str,
1649 _args: &[Value],
1650) -> Result<Value, String> {
1651 match method {
1652 "toString" | "toJSON" => {
1653 let (essence, params) = with_host(|h| match h.get(recv) {
1655 Some(JsObj::Object(p)) => (
1656 p.get("essence").map(|x| h.str_of(x)).unwrap_or_default(),
1657 p.get("params").cloned().unwrap_or(Value::Undef),
1658 ),
1659 _ => (String::new(), Value::Undef),
1660 });
1661 let param_str = serialize_mime_params(&mime_pairs_of(¶ms));
1662 let out = if param_str.is_empty() {
1663 essence
1664 } else {
1665 format!("{essence};{param_str}")
1666 };
1667 Ok(with_host(|h| h.new_str(out)))
1668 }
1669 _ => Err(crate::host::type_error(&format!(
1670 "mimeType.{method} is not a function"
1671 ))),
1672 }
1673}