ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::shell
//!
//! Built-in functions about shell operations.
//!
//! - SetEnv
//! - GetEnv
//! - Evial
//! - RunShell
//! - GetChar
//! - WriteTerm
//! - Exit

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
        ))),
    }
}

/// macros for easy build key pool
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,
}

/// pool object
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()
        )))
    }
}