1use crate::host::{is_callable, with_host, JsObj};
27use fusevm::Value;
28use indexmap::IndexMap;
29use std::collections::{HashMap, VecDeque};
30use std::io::{self, Write};
31
32pub const METHODS: &[&str] = &[
33 "createInterface",
34 "clearLine",
35 "clearScreenDown",
36 "cursorTo",
37 "moveCursor",
38 "emitKeypressEvents",
39];
40
41pub const INTERFACE_METHODS: &[&str] = &[
44 "question",
45 "write",
46 "close",
47 "pause",
48 "resume",
49 "prompt",
50 "setPrompt",
51 "getPrompt",
52 "on",
53 "once",
54 "addListener",
55 "prependListener",
56 "removeListener",
57 "off",
58 "removeAllListeners",
59 "@@asyncIterator",
60];
61
62pub const PROMISES_METHODS: &[&str] = &["createInterface"];
65
66pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
67 if method.starts_with("@@") {
68 return internal_call(method, args);
69 }
70 Some(match method {
71 "createInterface" => Ok(create_interface(args, false)),
72 "cursorTo" => {
76 let x = super::arg_num(args, 1);
77 let y = args.get(2).filter(|v| !matches!(v, Value::Undef));
78 let seq = match y {
79 Some(yv) => format!(
80 "\x1b[{};{}H",
81 with_host(|h| h.to_number(yv)) as i64 + 1,
82 x as i64 + 1
83 ),
84 None => format!("\x1b[{}G", x as i64 + 1),
85 };
86 write_stdout(&seq);
87 Ok(Value::Bool(true))
88 }
89 "moveCursor" => {
90 let dx = super::arg_num(args, 1) as i64;
91 let dy = super::arg_num(args, 2) as i64;
92 let mut seq = String::new();
93 if dx > 0 {
94 seq.push_str(&format!("\x1b[{dx}C"));
95 } else if dx < 0 {
96 seq.push_str(&format!("\x1b[{}D", -dx));
97 }
98 if dy > 0 {
99 seq.push_str(&format!("\x1b[{dy}B"));
100 } else if dy < 0 {
101 seq.push_str(&format!("\x1b[{}A", -dy));
102 }
103 write_stdout(&seq);
104 Ok(Value::Bool(true))
105 }
106 "clearLine" => {
107 let dir = super::arg_num(args, 1);
108 let seq = if dir < 0.0 {
110 "\x1b[1K"
111 } else if dir > 0.0 {
112 "\x1b[0K"
113 } else {
114 "\x1b[2K"
115 };
116 write_stdout(seq);
117 Ok(Value::Bool(true))
118 }
119 "clearScreenDown" => {
120 write_stdout("\x1b[0J");
121 Ok(Value::Bool(true))
122 }
123 "emitKeypressEvents" => Ok(Value::Undef),
128 _ => return None,
129 })
130}
131
132pub fn construct(args: &[Value]) -> Result<Value, String> {
136 Ok(create_interface(args, false))
137}
138
139pub fn constant(name: &str) -> Option<Value> {
143 match name {
144 "Interface" => Some(with_host(|h| h.alloc(JsObj::Builtin("Interface".into())))),
145 _ => None,
146 }
147}
148
149pub fn promises_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
152 match method {
153 "createInterface" => Some(Ok(create_interface(args, true))),
154 _ => None,
155 }
156}
157
158fn create_interface(args: &[Value], promises: bool) -> Value {
159 let id = NEXT_ID.with(|n| {
160 let id = n.get();
161 n.set(id + 1);
162 id
163 });
164 let (input, output) = match args.first() {
166 Some(o) if opt_prop(o, "input").is_some() => (
167 opt_prop(o, "input").unwrap_or(Value::Undef),
168 opt_prop(o, "output").unwrap_or(Value::Undef),
169 ),
170 _ => (
171 args.first().cloned().unwrap_or(Value::Undef),
172 args.get(1).cloned().unwrap_or(Value::Undef),
173 ),
174 };
175 let iface = with_host(|h| {
176 let listeners = h.new_object(IndexMap::new());
177 let prompt = h.new_str("> ");
178 let mut m = IndexMap::new();
179 m.insert("@@native".into(), h.new_str("Interface"));
180 m.insert("@@input".into(), input.clone());
181 m.insert("@@output".into(), output);
182 m.insert("@@prompt".into(), prompt);
183 m.insert("@@listeners".into(), listeners);
184 m.insert("@@rlid".into(), Value::Float(id as f64));
185 if promises {
186 let flag = h.new_str("1");
187 m.insert("@@promises".into(), flag);
188 }
189 h.new_object(m)
190 });
191 if matches!(input, Value::Obj(_)) && !is_stdin(&input) {
192 attach_stream(&iface, &input);
193 }
194 iface
195}
196
197pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
199 match method {
200 "question" => {
203 let query = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
204 write_stdout(&query);
205 if with_state(recv, |s| s.flowing) {
208 if read_hidden(recv, "@@promises") == "1" {
209 let (promise, id) = with_host(|h| {
210 let p = h.new_promise();
211 let id = h.promise_id(&p).unwrap_or(0);
212 (p, id)
213 });
214 with_state(recv, |s| s.question = Some(QuestionReply::Promise(id)));
215 return Ok(promise);
216 }
217 let cb = args
218 .iter()
219 .rev()
220 .find(|v| with_host(|h| is_callable(h, v)))
221 .cloned();
222 if let Some(cb) = cb {
223 with_state(recv, |s| s.question = Some(QuestionReply::Callback(cb)));
224 }
225 return Ok(Value::Undef);
226 }
227 let line = read_line();
228 if read_hidden(recv, "@@promises") == "1" {
232 let line_val = with_host(|h| h.new_str(line));
233 return crate::builtins::promise_resolve_pub(line_val);
234 }
235 let cb = args
240 .iter()
241 .rev()
242 .find(|v| with_host(|h| is_callable(h, v)))
243 .cloned();
244 if let Some(cb) = cb {
245 let line_val = with_host(|h| h.new_str(line));
246 crate::host::invoke(&cb, vec![line_val], None)?;
247 }
248 Ok(Value::Undef)
249 }
250 "write" => {
251 let data = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
252 write_output(recv, &data);
253 Ok(Value::Undef)
254 }
255 "prompt" => {
256 let p = read_hidden(recv, "@@prompt");
257 write_output(recv, &p);
258 Ok(Value::Undef)
259 }
260 "setPrompt" => {
261 let p = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
262 with_host(|h| {
263 let pv = h.new_str(p);
264 if let Some(JsObj::Object(m)) = h.get_mut(recv) {
265 m.insert("@@prompt".into(), pv);
266 }
267 });
268 Ok(Value::Undef)
269 }
270 "getPrompt" => {
274 let prompt = read_hidden(recv, "@@prompt");
275 Ok(with_host(|h| h.new_str(prompt)))
276 }
277 "on" | "once" | "addListener" | "prependListener" => {
280 if let (Some(ev), Some(cb)) = (args.first(), args.get(1)) {
281 let event = with_host(|h| h.str_of(ev));
282 store_listener(
283 recv,
284 &event,
285 cb.clone(),
286 method == "once",
287 method == "prependListener",
288 );
289 if event == "line" || event == "close" {
290 ensure_flowing(recv);
291 }
292 }
293 Ok(recv.clone())
294 }
295 "removeListener" | "off" => {
296 if let (Some(ev), Some(cb)) = (args.first(), args.get(1)) {
297 let event = with_host(|h| h.str_of(ev));
298 remove_listener(recv, &event, cb);
299 }
300 Ok(recv.clone())
301 }
302 "removeAllListeners" => {
303 let event = args
304 .first()
305 .filter(|v| !matches!(v, Value::Undef))
306 .map(|v| with_host(|h| h.str_of(v)));
307 clear_listeners(recv, event.as_deref());
308 Ok(recv.clone())
309 }
310 "close" => {
311 close(recv)?;
312 Ok(Value::Undef)
313 }
314 "@@asyncIterator" => Ok(async_iterator(recv)),
315 "pause" | "resume" => Ok(recv.clone()),
318 _ => Err(crate::host::type_error(&format!(
319 "{method} is not a function"
320 ))),
321 }
322}
323
324fn read_hidden(recv: &Value, key: &str) -> String {
326 with_host(|h| match h.get(recv) {
327 Some(JsObj::Object(p)) => p.get(key).map(|v| h.str_of(v)).unwrap_or_default(),
328 _ => String::new(),
329 })
330}
331
332fn listeners_obj(recv: &Value) -> Option<Value> {
334 opt_prop(recv, "@@listeners")
335}
336
337fn store_listener(recv: &Value, event: &str, cb: Value, once: bool, prepend: bool) {
340 let Some(listeners) = listeners_obj(recv) else {
341 return;
342 };
343 with_host(|h| {
344 let entry = h.new_array(vec![cb, Value::Bool(once)]);
345 let arr = match h.get(&listeners) {
346 Some(JsObj::Object(p)) => p.get(event).cloned(),
347 _ => None,
348 };
349 let arr = arr.filter(|a| matches!(h.get(a), Some(JsObj::Array(_))));
350 match arr {
351 Some(a) => {
352 if let Some(JsObj::Array(items)) = h.get_mut(&a) {
353 if prepend {
354 items.insert(0, entry);
355 } else {
356 items.push(entry);
357 }
358 }
359 }
360 None => {
361 let a = h.new_array(vec![entry]);
362 if let Some(JsObj::Object(p)) = h.get_mut(&listeners) {
363 p.insert(event.to_string(), a);
364 }
365 }
366 }
367 });
368}
369
370fn listener_entries(recv: &Value, event: &str) -> Vec<(Value, bool)> {
372 let Some(listeners) = listeners_obj(recv) else {
373 return Vec::new();
374 };
375 with_host(|h| {
376 let arr = match h.get(&listeners) {
377 Some(JsObj::Object(p)) => p.get(event).cloned(),
378 _ => None,
379 };
380 let Some(Some(JsObj::Array(items))) = arr.map(|a| h.get(&a).cloned()) else {
381 return Vec::new();
382 };
383 items
384 .iter()
385 .filter_map(|e| match h.get(e) {
386 Some(JsObj::Array(pair)) if pair.len() == 2 => {
387 Some((pair[0].clone(), h.truthy(&pair[1])))
388 }
389 _ => None,
390 })
391 .collect()
392 })
393}
394
395fn remove_listener(recv: &Value, event: &str, cb: &Value) {
397 let Some(listeners) = listeners_obj(recv) else {
398 return;
399 };
400 with_host(|h| {
401 let arr = match h.get(&listeners) {
402 Some(JsObj::Object(p)) => p.get(event).cloned(),
403 _ => None,
404 };
405 let Some(arr) = arr else { return };
406 let pos = match h.get(&arr) {
407 Some(JsObj::Array(items)) => items.iter().position(|e| match h.get(e) {
408 Some(JsObj::Array(pair)) => pair.first().is_some_and(|f| h.strict_eq(f, cb)),
409 _ => false,
410 }),
411 _ => None,
412 };
413 if let (Some(i), Some(JsObj::Array(items))) = (pos, h.get_mut(&arr)) {
414 items.remove(i);
415 }
416 });
417}
418
419fn clear_listeners(recv: &Value, event: Option<&str>) {
421 let Some(listeners) = listeners_obj(recv) else {
422 return;
423 };
424 with_host(|h| {
425 if let Some(JsObj::Object(p)) = h.get_mut(&listeners) {
426 match event {
427 Some(e) => {
428 p.shift_remove(e);
429 }
430 None => p.clear(),
431 }
432 }
433 });
434}
435
436fn read_line() -> String {
438 let mut line = String::new();
439 let _ = io::stdin().read_line(&mut line);
440 while line.ends_with('\n') || line.ends_with('\r') {
441 line.pop();
442 }
443 line
444}
445
446fn write_stdout(s: &str) {
449 let mut out = io::stdout();
450 let _ = out.write_all(s.as_bytes());
451 let _ = out.flush();
452}
453
454fn write_output(recv: &Value, s: &str) {
463 let out = opt_prop(recv, "@@output").unwrap_or(Value::Undef);
464 if matches!(out, Value::Obj(_)) {
465 let payload = with_host(|h| h.new_str(s.to_string()));
466 if crate::host::call_method(&out, "write", vec![payload]).is_ok() {
467 return;
468 }
469 }
470 write_stdout(s);
471}
472
473fn opt_prop(v: &Value, key: &str) -> Option<Value> {
475 with_host(|h| match h.get(v) {
476 Some(JsObj::Object(p)) => p.get(key).cloned(),
477 _ => None,
478 })
479}
480
481#[derive(Default)]
491struct LineState {
492 pending: Vec<u8>,
494 after_cr: bool,
497 to_emit: VecDeque<String>,
499 emitting: bool,
501 ended: bool,
503 closed: bool,
504 flowing: bool,
507 iterating: bool,
510 buffered: VecDeque<String>,
511 waiters: VecDeque<u32>,
512 question: Option<QuestionReply>,
515}
516
517enum QuestionReply {
518 Callback(Value),
519 Promise(u32),
520}
521
522thread_local! {
523 static LINES: std::cell::RefCell<HashMap<u64, LineState>> =
524 std::cell::RefCell::new(HashMap::new());
525 static NEXT_ID: std::cell::Cell<u64> = const { std::cell::Cell::new(1) };
526}
527
528fn rl_id(recv: &Value) -> u64 {
529 opt_prop(recv, "@@rlid")
530 .map(|v| with_host(|h| h.to_number(&v)) as u64)
531 .unwrap_or(0)
532}
533
534fn with_state<R>(recv: &Value, f: impl FnOnce(&mut LineState) -> R) -> R {
535 let id = rl_id(recv);
536 LINES.with(|m| f(m.borrow_mut().entry(id).or_default()))
537}
538
539fn is_stdin(input: &Value) -> bool {
541 with_host(|h| match h.get(input) {
542 Some(JsObj::Object(p)) => {
543 p.get("@@native").map(|v| h.str_of(v)).as_deref() == Some("WriteStream")
544 && p.get("fd").map(|v| h.to_number(v)) == Some(0.0)
545 }
546 _ => false,
547 })
548}
549
550fn ensure_flowing(recv: &Value) {
552 let input = opt_prop(recv, "@@input").unwrap_or(Value::Undef);
553 if !is_stdin(&input) {
554 return;
555 }
556 let start = with_state(recv, |s| {
557 !std::mem::replace(&mut s.flowing, true) && !s.closed
558 });
559 if start {
560 attach_stream(recv, &input);
561 }
562}
563
564fn on_data(recv: &Value, bytes: &[u8]) -> Result<(), String> {
567 if with_state(recv, |s| s.closed) {
569 return Ok(());
570 }
571 let start = with_state(recv, |s| {
572 let mut i = 0;
573 if s.after_cr && bytes.first() == Some(&b'\n') {
574 i = 1;
575 }
576 s.after_cr = false;
577 while i < bytes.len() {
578 match bytes[i] {
579 b'\n' => {
580 let line = String::from_utf8_lossy(&s.pending).into_owned();
581 s.pending.clear();
582 s.to_emit.push_back(line);
583 }
584 b'\r' => {
585 let line = String::from_utf8_lossy(&s.pending).into_owned();
586 s.pending.clear();
587 s.to_emit.push_back(line);
588 match bytes.get(i + 1) {
589 Some(b'\n') => i += 1,
590 None => s.after_cr = true,
591 _ => {}
592 }
593 }
594 b => s.pending.push(b),
595 }
596 i += 1;
597 }
598 !s.to_emit.is_empty() && !std::mem::replace(&mut s.emitting, true)
599 });
600 if start {
601 emit_lines(recv)?;
602 }
603 Ok(())
604}
605
606fn on_end(recv: &Value) -> Result<(), String> {
608 let (start, close_now) = with_state(recv, |s| {
609 s.ended = true;
610 if !s.pending.is_empty() {
611 let line = String::from_utf8_lossy(&s.pending).into_owned();
612 s.pending.clear();
613 s.to_emit.push_back(line);
614 }
615 let start = !s.to_emit.is_empty() && !std::mem::replace(&mut s.emitting, true);
616 (start, s.to_emit.is_empty() && !s.emitting)
617 });
618 if start {
619 emit_lines(recv)?;
620 } else if close_now {
621 close(recv)?;
622 }
623 Ok(())
624}
625
626fn emit_lines(recv: &Value) -> Result<(), String> {
631 while let Some(line) = with_state(recv, |s| s.to_emit.pop_front()) {
632 if let Err(e) = deliver(recv, line) {
633 with_state(recv, |s| s.emitting = false);
634 return Err(e);
635 }
636 }
637 let close_now = with_state(recv, |s| {
638 s.emitting = false;
639 s.ended
640 });
641 if close_now {
642 close(recv)?;
643 }
644 Ok(())
645}
646
647fn deliver(recv: &Value, line: String) -> Result<(), String> {
650 match with_state(recv, |s| s.question.take()) {
651 Some(QuestionReply::Callback(cb)) => {
652 let v = with_host(|h| h.new_str(line));
653 crate::host::invoke(&cb, vec![v], None)?;
654 return Ok(());
655 }
656 Some(QuestionReply::Promise(id)) => {
657 let v = with_host(|h| h.new_str(line));
658 crate::host::resolve_promise_val(id, v);
659 return Ok(());
660 }
661 None => {}
662 }
663 let v = with_host(|h| h.new_str(line.clone()));
664 emit(recv, "line", vec![v.clone()])?;
665 let waiter = with_state(recv, |s| {
666 if !s.iterating {
667 return None;
668 }
669 let w = s.waiters.pop_front();
670 if w.is_none() {
671 s.buffered.push_back(line);
672 }
673 w
674 });
675 if let Some(id) = waiter {
676 crate::host::resolve_promise_val(id, iter_result(v, false));
677 }
678 Ok(())
679}
680
681fn close(recv: &Value) -> Result<(), String> {
684 let waiters = with_state(recv, |s| {
685 if std::mem::replace(&mut s.closed, true) {
686 return None;
687 }
688 Some(std::mem::take(&mut s.waiters))
689 });
690 let Some(waiters) = waiters else {
691 return Ok(());
692 };
693 let input = opt_prop(recv, "@@input").unwrap_or(Value::Undef);
696 if is_stdin(&input) {
697 crate::host::call_method(&input, "pause", Vec::new())?;
698 }
699 emit(recv, "close", Vec::new())?;
700 for id in waiters {
701 crate::host::resolve_promise_val(id, iter_result(Value::Undef, true));
702 }
703 Ok(())
704}
705
706fn emit(recv: &Value, event: &str, args: Vec<Value>) -> Result<(), String> {
708 let entries = listener_entries(recv, event);
709 for (cb, once) in entries {
710 if once {
711 remove_listener(recv, event, &cb);
712 }
713 crate::host::invoke(&cb, args.clone(), Some(recv.clone()))?;
714 }
715 Ok(())
716}
717
718fn iter_result(value: Value, done: bool) -> Value {
720 with_host(|h| {
721 let mut m = IndexMap::new();
722 m.insert("value".into(), value);
723 m.insert("done".into(), Value::Bool(done));
724 h.new_object(m)
725 })
726}
727
728fn internal_call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
731 let recv = args.first().cloned().unwrap_or(Value::Undef);
732 let r = match method {
733 "@@feed" => {
734 let chunk = args.get(1).cloned().unwrap_or(Value::Undef);
735 let bytes = super::buffer::view_bytes(&chunk)
736 .unwrap_or_else(|| with_host(|h| h.str_of(&chunk)).into_bytes());
737 on_data(&recv, &bytes)
738 }
739 "@@end" => on_end(&recv),
740 "@@close" => close(&recv),
741 _ => return None,
742 };
743 Some(r.map(|_| Value::Undef))
744}
745
746fn attach_stream(recv: &Value, input: &Value) {
749 with_state(recv, |s| s.flowing = true);
750 let has_on = crate::builtins::get_property(input, "on")
751 .map(|f| with_host(|h| is_callable(h, &f)))
752 .unwrap_or(false);
753 if !has_on {
754 return;
755 }
756 for (event, internal) in [("data", "readline.@@feed"), ("end", "readline.@@end")] {
757 let (name, cb) = with_host(|h| {
758 let target = h.alloc(JsObj::Builtin(internal.into()));
759 let undef = Value::Undef;
760 let cb = h.alloc(JsObj::BoundFunc {
761 target,
762 this: undef,
763 args: vec![recv.clone()],
764 });
765 (h.new_str(event), cb)
766 });
767 let _ = crate::host::call_method(input, "on", vec![name, cb]);
768 }
769}
770
771pub const ITERATOR_METHODS: &[&str] = &["next", "return", "@@asyncIterator"];
775
776fn async_iterator(recv: &Value) -> Value {
777 with_state(recv, |s| s.iterating = true);
778 ensure_flowing(recv);
779 with_host(|h| {
780 let mut m = IndexMap::new();
781 m.insert("@@native".into(), h.new_str("ReadlineIterator"));
782 m.insert("@@iface".into(), recv.clone());
783 h.new_object(m)
784 })
785}
786
787pub fn iterator_call(recv: &Value, method: &str) -> Result<Value, String> {
788 let iface = opt_prop(recv, "@@iface").unwrap_or(Value::Undef);
789 match method {
790 "@@asyncIterator" => Ok(recv.clone()),
791 "next" => {
792 let ready = with_state(&iface, |s| match s.buffered.pop_front() {
793 Some(line) => Some(Some(line)),
794 None if s.closed => Some(None),
795 None => None,
796 });
797 match ready {
798 Some(Some(line)) => {
799 let v = with_host(|h| h.new_str(line));
800 crate::builtins::promise_resolve_pub(iter_result(v, false))
801 }
802 Some(None) => crate::builtins::promise_resolve_pub(iter_result(Value::Undef, true)),
803 None => {
804 let (promise, id) = with_host(|h| {
805 let p = h.new_promise();
806 let id = h.promise_id(&p).unwrap_or(0);
807 (p, id)
808 });
809 with_state(&iface, |s| s.waiters.push_back(id));
810 Ok(promise)
811 }
812 }
813 }
814 "return" => {
817 with_host(|h| {
818 let cb = h.alloc(JsObj::Builtin("readline.@@close".into()));
819 h.queue_nexttick(cb, vec![iface.clone()]);
820 });
821 crate::builtins::promise_resolve_pub(iter_result(Value::Undef, true))
822 }
823 _ => Err(crate::host::type_error(&format!(
824 "{method} is not a function"
825 ))),
826 }
827}