use crate::{
ERR_SYMBOL,
Environment,
OK_SYMBOL,
eval::{apply::eval_apply, eval},
value::Value,
};
pub(crate) fn set_env(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if args.len() != 2 {
return Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::SetEnv]: ",
"Expected 2 parameters, but {} were passed."
),
args.len()
)));
}
let name_val = eval_apply(&args[0], env.clone())?;
let content_val = eval_apply(&args[1], env)?;
match (name_val, content_val) {
(Value::String(name), Value::String(val)) => {
unsafe { std::env::set_var(name.as_ref(), val.as_ref()) };
Ok(Value::Unit)
}
(Value::String(_), other) | (other, _) => Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::SetEnv]: Unexpected value type: `{}`.",
other
))),
}
}
pub(crate) fn get_env(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if args.len() != 1 {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::GetEnv]: Expected 1 parameter, but {} were passed.",
args.len()
)));
}
match eval_apply(&args[0], env)? {
Value::String(name) => match std::env::var(name.as_ref()) {
Ok(val) => Ok(Value::String(std::sync::Arc::from(val))),
Err(e) => Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::GetEnv]: Failed to get environment variable `{}`: {}.",
name, e
))),
},
other => Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::GetEnv]: Expected a string, but got: `{}`.",
other
))),
}
}
pub(crate) fn evial(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if args.len() != 1 {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Evial]: Expected 1 parameter, but {} were passed.",
args.len()
)));
}
match eval_apply(&args[0], env.clone())? {
Value::String(source) => eval(source.as_ref(), env),
other => Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::Evial]: Expected a valid KSL code, but got: `{}`.",
other
))),
}
}
pub(crate) fn run_shell(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if args.len() != 2 {
return Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::RunShell]: Expected 2 parameters, but {} were passed.",
args.len()
)));
}
let cmd_val = eval_apply(&args[0], env.clone())?;
let params_val = eval_apply(&args[1], env)?;
match (cmd_val, params_val) {
(Value::String(command), Value::List(params)) => {
let processed_args: Vec<String> = params
.iter()
.map(|v| match v {
Value::Atom(a) => format!("-{}", a),
Value::String(s) => s.to_string(),
Value::Number(n) => n.to_string(),
_ => String::new(),
})
.collect();
let mut child = std::process::Command::new(command.as_ref())
.args(processed_args)
.spawn()
.map_err(|e| {
std::sync::Arc::from(format!(
"Error[ksl::builtin::RunShell]: Execution failed: {}.",
e
))
})?;
if child
.wait()
.map_err(|e| {
std::sync::Arc::from(format!(
"Error[ksl::builtin::RunShell]: Execution failed: {}.",
e
))
})?
.success()
{
Ok(OK_SYMBOL.clone())
} else {
Ok(ERR_SYMBOL.clone())
}
}
(Value::String(_), other) | (other, _) => Err(std::sync::Arc::from(format!(
"Error[ksl::builtin::RunShell]: Unexpected argument types: `{}`.",
other
))),
}
}
macro_rules! build_key_pool {
($($name:ident),* $(,)?) => {
struct KeyPool {
$($name: std::sync::Arc<str>),*
}
impl KeyPool {
fn new() -> Self {
Self {
$($name: std::sync::Arc::from(stringify!($name))),*
}
}
}
};
}
build_key_pool! {
key_left, key_right, key_up, key_down, key_enter,
key_escape, ey_backspace, key_home, key_end,
key_tab, key_backtab, key_del, key_insert,
key_pageup, key_pagedown, key_unknown,
}
struct KeyManager {
named_keys: KeyPool,
ascii: [std::sync::Arc<str>; 128],
}
static KMGR: std::sync::LazyLock<KeyManager> = std::sync::LazyLock::new(|| {
let ascii = (0..128u8)
.map(|b| std::sync::Arc::from((b as char).to_string()))
.collect::<Vec<_>>()
.try_into()
.unwrap();
KeyManager {
named_keys: KeyPool::new(),
ascii,
}
});
pub(crate) fn get_char(args: &[Value], _env: Environment) -> Result<Value, std::sync::Arc<str>> {
let pool = &KMGR.named_keys;
let ascii = &KMGR.ascii;
if args.is_empty() {
match console::Term::stdout().read_key() {
Ok(key) => Ok(match key {
console::Key::ArrowLeft => Value::Atom(pool.key_left.clone()),
console::Key::ArrowRight => Value::Atom(pool.key_right.clone()),
console::Key::ArrowUp => Value::Atom(pool.key_up.clone()),
console::Key::ArrowDown => Value::Atom(pool.key_down.clone()),
console::Key::Enter => Value::Atom(pool.key_enter.clone()),
console::Key::Escape => Value::Atom(pool.key_escape.clone()),
console::Key::Backspace => Value::Atom(pool.ey_backspace.clone()),
console::Key::Home => Value::Atom(pool.key_home.clone()),
console::Key::End => Value::Atom(pool.key_end.clone()),
console::Key::Tab => Value::Atom(pool.key_tab.clone()),
console::Key::BackTab => Value::Atom(pool.key_backtab.clone()),
console::Key::Del => Value::Atom(pool.key_del.clone()),
console::Key::Insert => Value::Atom(pool.key_insert.clone()),
console::Key::PageUp => Value::Atom(pool.key_pageup.clone()),
console::Key::PageDown => Value::Atom(pool.key_pagedown.clone()),
console::Key::Char(ch) if (ch as u32) < 128 => Value::String(ascii[ch as usize].clone()),
console::Key::Char(ch) => Value::String(std::sync::Arc::from(ch.to_string())),
_ => Value::Atom(pool.key_unknown.clone()),
}),
Err(e) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::GetChar]: ",
"Failed to read a char with: `{}`."
),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::GetChar]: ",
"Expected no parameters, but {} were passed."
),
args.len()
)))
}
}
pub(crate) fn write_term(args: &[Value], _env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [text] = args {
match text {
Value::String(s) => {
let unescaped = match unescaper::unescape(s) {
Ok(raw) => raw,
Err(e) => {
return Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::WriteTerm]: ",
"Failed to unescape `{}` with: `{}`."
),
s, e
)));
}
};
match std::io::Write::write_all(&mut console::Term::stdout(), unescaped.as_bytes()) {
Ok(()) => Ok(Value::Unit),
Err(e) => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::WriteTerm]: ",
"Failed to write to console with: `{}`."
),
e
))),
}
}
e => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::WriteTerm]: ",
"Expected a string, but got: `{}`."
),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::WriteTerm]: ",
"Expected 1 parameter, but {} were passed."
),
args.len()
)))
}
}
pub(crate) fn exit(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
if let [code] = args {
match eval_apply(code, env)? {
Value::Number(c) => std::process::exit(c.trunc() as i32),
e => Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Exit]: ",
"Expected a number as exit code, but got: `{}`."
),
e
))),
}
} else {
Err(std::sync::Arc::from(format!(
concat!(
"Error[ksl::builtin::Exit]: ",
"Expected 1 parameter, but {} were passed."
),
args.len()
)))
}
}