use crate::host::{with_host, JsObj};
use fusevm::Value;
use indexmap::IndexMap;
use std::io::{self, Write};
pub const METHODS: &[&str] = &["start", "isValidSyntax"];
pub const REPLSERVER_METHODS: &[&str] = &[
"close",
"on",
"once",
"addListener",
"prependListener",
"removeListener",
"off",
"removeAllListeners",
"write",
"setPrompt",
"displayPrompt",
"defineCommand",
"clearBufferedCommand",
"pause",
"resume",
];
pub fn call(method: &str, args: &[Value]) -> Option<Result<Value, String>> {
Some(match method {
"start" => {
let opts = args.first().cloned().unwrap_or(Value::Undef);
crate::repl::run();
Ok(new_repl_server(&opts))
}
"isValidSyntax" => Ok(Value::Bool(
crate::compile(&super::arg_str(args, 0)).is_ok(),
)),
_ => return None,
})
}
pub fn construct(name: &str, args: &[Value]) -> Result<Value, String> {
match name {
"REPLServer" => Ok(new_repl_server(
&args.first().cloned().unwrap_or(Value::Undef),
)),
"Recoverable" => {
let inner = args.first().cloned().unwrap_or(Value::Undef);
let msg = with_host(|h| h.str_of(&inner));
let err =
crate::builtins::construct_builtin("Error", vec![with_host(|h| h.new_str(msg))])?;
with_host(|h| {
if let Some(JsObj::Object(p)) = h.get_mut(&err) {
p.insert("err".into(), inner);
}
});
Ok(err)
}
_ => Err(crate::host::type_error(&format!(
"repl.{name} is not a constructor"
))),
}
}
pub fn constant(name: &str) -> Option<Value> {
match name {
"REPLServer" | "Recoverable" => Some(with_host(|h| h.alloc(JsObj::Builtin(name.into())))),
"writer" => Some(with_host(|h| {
h.alloc(JsObj::Builtin("util.inspect".into()))
})),
_ => None,
}
}
fn new_repl_server(opts: &Value) -> Value {
let prompt = opt_str(opts, "prompt").unwrap_or_else(|| "> ".to_string());
let input = opt_prop(opts, "input").unwrap_or(Value::Undef);
let output = opt_prop(opts, "output").unwrap_or(Value::Undef);
let use_colors = opt_prop(opts, "useColors").unwrap_or(Value::Bool(true));
with_host(|h| {
let listeners = h.new_object(IndexMap::new());
let mut m = IndexMap::new();
m.insert("@@native".into(), h.new_str("REPLServer"));
let p = h.new_str(prompt);
m.insert("@@prompt".into(), p);
m.insert("input".into(), input);
m.insert("output".into(), output);
m.insert("useColors".into(), use_colors);
m.insert("@@listeners".into(), listeners);
h.new_object(m)
})
}
pub fn instance_call(recv: &Value, method: &str, args: Vec<Value>) -> Result<Value, String> {
match method {
"close" => {
emit(recv, "exit", &[])?;
Ok(Value::Undef)
}
"on" | "once" | "addListener" | "prependListener" => {
if let (Some(ev), Some(cb)) = (args.first(), args.get(1)) {
let event = with_host(|h| h.str_of(ev));
store_listener(recv, &event, cb.clone());
}
Ok(recv.clone())
}
"removeListener" | "off" | "removeAllListeners" => Ok(recv.clone()),
"write" => {
let data = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
let mut out = io::stdout();
let _ = out.write_all(data.as_bytes());
let _ = out.flush();
Ok(Value::Undef)
}
"setPrompt" => {
let p = with_host(|h| args.first().map(|v| h.str_of(v)).unwrap_or_default());
with_host(|h| {
let pv = h.new_str(p);
if let Some(JsObj::Object(m)) = h.get_mut(recv) {
m.insert("@@prompt".into(), pv);
}
});
Ok(recv.clone())
}
"displayPrompt" | "clearBufferedCommand" | "pause" | "resume" => Ok(recv.clone()),
"defineCommand" => Ok(recv.clone()),
_ => Err(crate::host::type_error(&format!(
"{method} is not a function"
))),
}
}
fn emit(recv: &Value, event: &str, cb_args: &[Value]) -> Result<(), String> {
let listeners = with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get("@@listeners").cloned(),
_ => None,
});
let Some(listeners) = listeners else {
return Ok(());
};
let cbs: Vec<Value> = with_host(|h| match h.get(&listeners) {
Some(JsObj::Object(p)) => match p.get(event).map(|a| h.get(a)) {
Some(Some(JsObj::Array(items))) => items.clone(),
_ => Vec::new(),
},
_ => Vec::new(),
});
for cb in cbs {
crate::host::invoke(&cb, cb_args.to_vec(), None)?;
}
Ok(())
}
fn store_listener(recv: &Value, event: &str, cb: Value) {
let listeners = with_host(|h| match h.get(recv) {
Some(JsObj::Object(p)) => p.get("@@listeners").cloned(),
_ => None,
});
let Some(listeners) = listeners else { return };
with_host(|h| {
let arr = match h.get(&listeners) {
Some(JsObj::Object(p)) => p.get(event).cloned(),
_ => None,
};
let arr = arr.filter(|a| matches!(h.get(a), Some(JsObj::Array(_))));
match arr {
Some(a) => {
if let Some(JsObj::Array(items)) = h.get_mut(&a) {
items.push(cb);
}
}
None => {
let a = h.new_array(vec![cb]);
if let Some(JsObj::Object(p)) = h.get_mut(&listeners) {
p.insert(event.to_string(), a);
}
}
}
});
}
fn opt_prop(v: &Value, key: &str) -> Option<Value> {
with_host(|h| match h.get(v) {
Some(JsObj::Object(p)) => p.get(key).cloned(),
_ => None,
})
}
fn opt_str(v: &Value, key: &str) -> Option<String> {
with_host(|h| match h.get(v) {
Some(JsObj::Object(p)) => p.get(key).map(|pv| h.str_of(pv)),
_ => None,
})
}