use std::collections::HashMap;
use std::sync::Arc as Rc;
use crate::nan_value::{Arena, NanValue, NanValueConvert};
use crate::value::{RuntimeError, Value};
pub fn register(global: &mut HashMap<String, Value>) {
let mut members = HashMap::new();
for method in &[
"enableRawMode",
"disableRawMode",
"clear",
"moveTo",
"print",
"setColor",
"resetColor",
"readKey",
"size",
"hideCursor",
"showCursor",
"flush",
] {
members.insert(
method.to_string(),
Value::Builtin(format!("Terminal.{}", method)),
);
}
global.insert(
"Terminal".to_string(),
Value::Namespace {
name: "Terminal".to_string(),
members,
},
);
}
pub const DECLARED_EFFECTS: &[&str] = &[
"Terminal.enableRawMode",
"Terminal.disableRawMode",
"Terminal.clear",
"Terminal.moveTo",
"Terminal.print",
"Terminal.setColor",
"Terminal.resetColor",
"Terminal.readKey",
"Terminal.size",
"Terminal.hideCursor",
"Terminal.showCursor",
"Terminal.flush",
];
pub fn effects(name: &str) -> &'static [&'static str] {
match name {
"Terminal.enableRawMode" => &["Terminal.enableRawMode"],
"Terminal.disableRawMode" => &["Terminal.disableRawMode"],
"Terminal.clear" => &["Terminal.clear"],
"Terminal.moveTo" => &["Terminal.moveTo"],
"Terminal.print" => &["Terminal.print"],
"Terminal.setColor" => &["Terminal.setColor"],
"Terminal.resetColor" => &["Terminal.resetColor"],
"Terminal.readKey" => &["Terminal.readKey"],
"Terminal.size" => &["Terminal.size"],
"Terminal.hideCursor" => &["Terminal.hideCursor"],
"Terminal.showCursor" => &["Terminal.showCursor"],
"Terminal.flush" => &["Terminal.flush"],
_ => &[],
}
}
pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
match name {
"Terminal.enableRawMode" => Some(no_args(name, args, || {
aver_rt::terminal_enable_raw_mode().map(|()| Value::Unit)
})),
"Terminal.disableRawMode" => Some(no_args(name, args, || {
aver_rt::terminal_disable_raw_mode().map(|()| Value::Unit)
})),
"Terminal.clear" => Some(no_args(name, args, || {
aver_rt::terminal_clear().map(|()| Value::Unit)
})),
"Terminal.moveTo" => Some(move_to(args)),
"Terminal.print" => Some(print(args)),
"Terminal.setColor" => Some(set_color(args)),
"Terminal.resetColor" => Some(no_args(name, args, || {
aver_rt::terminal_reset_color().map(|()| Value::Unit)
})),
"Terminal.readKey" => Some(no_args(name, args, || {
Ok(match aver_rt::terminal_read_key() {
Some(key) => Value::Some(Box::new(Value::Str(key))),
None => Value::None,
})
})),
"Terminal.size" => Some(no_args(name, args, || {
aver_rt::terminal_size().map(|(w, h)| Value::Record {
type_name: "Terminal.Size".to_string(),
fields: vec![
("width".to_string(), Value::Int(w)),
("height".to_string(), Value::Int(h)),
]
.into(),
})
})),
"Terminal.hideCursor" => Some(no_args(name, args, || {
aver_rt::terminal_hide_cursor().map(|()| Value::Unit)
})),
"Terminal.showCursor" => Some(no_args(name, args, || {
aver_rt::terminal_show_cursor().map(|()| Value::Unit)
})),
"Terminal.flush" => Some(no_args(name, args, || {
aver_rt::terminal_flush().map(|()| Value::Unit)
})),
_ => None,
}
}
fn no_args<F>(name: &str, args: &[Value], f: F) -> Result<Value, RuntimeError>
where
F: FnOnce() -> Result<Value, String>,
{
if !args.is_empty() {
return Err(RuntimeError::Error(format!(
"{}() takes 0 arguments, got {}",
name,
args.len()
)));
}
f().map_err(RuntimeError::Error)
}
fn move_to(args: &[Value]) -> Result<Value, RuntimeError> {
let [x_val, y_val] = args else {
return Err(RuntimeError::Error(format!(
"Terminal.moveTo() takes 2 arguments (x, y), got {}",
args.len()
)));
};
let Value::Int(x) = x_val else {
return Err(RuntimeError::Error(
"Terminal.moveTo: x must be an Int".to_string(),
));
};
let Value::Int(y) = y_val else {
return Err(RuntimeError::Error(
"Terminal.moveTo: y must be an Int".to_string(),
));
};
aver_rt::terminal_move_to(*x, *y).map_err(RuntimeError::Error)?;
Ok(Value::Unit)
}
fn print(args: &[Value]) -> Result<Value, RuntimeError> {
let [s_val] = args else {
return Err(RuntimeError::Error(format!(
"Terminal.print() takes 1 argument, got {}",
args.len()
)));
};
let s = crate::value::aver_display(s_val).unwrap_or_default();
aver_rt::terminal_print(&s).map_err(RuntimeError::Error)?;
Ok(Value::Unit)
}
fn set_color(args: &[Value]) -> Result<Value, RuntimeError> {
let [c_val] = args else {
return Err(RuntimeError::Error(format!(
"Terminal.setColor() takes 1 argument, got {}",
args.len()
)));
};
let Value::Str(color) = c_val else {
return Err(RuntimeError::Error(
"Terminal.setColor: argument must be a String".to_string(),
));
};
aver_rt::terminal_set_color(color).map_err(RuntimeError::Error)?;
Ok(Value::Unit)
}
pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
let methods = &[
"enableRawMode",
"disableRawMode",
"clear",
"moveTo",
"print",
"setColor",
"resetColor",
"readKey",
"size",
"hideCursor",
"showCursor",
"flush",
];
let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
for method in methods {
let idx = arena.push_builtin(&format!("Terminal.{}", method));
members.push((Rc::from(*method), NanValue::new_builtin(idx)));
}
let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
name: Rc::from("Terminal"),
members,
});
global.insert("Terminal".to_string(), NanValue::new_namespace(ns_idx));
}
pub fn call_nv(
name: &str,
args: &[NanValue],
arena: &mut Arena,
) -> Option<Result<NanValue, RuntimeError>> {
if !name.starts_with("Terminal.") {
return None;
}
let old_args: Vec<Value> = args.iter().map(|nv| nv.to_value(arena)).collect();
let result = call(name, &old_args)?;
Some(result.map(|v| NanValue::from_value(&v, arena)))
}