ksl 0.1.5

KSL core library and interpreter
Documentation
mod builtin;
pub mod eval;
mod token;
pub mod value;

/// true and false symbol for atom
pub use builtin::{FALSE_SYMBOL, TRUE_SYMBOL};

use crate::{
    builtin::{BULTIN_FUNCTIONS, MODULE_NAME_ENV, MODULE_PATH_ENV},
    value::Value,
};

/// the environment
pub type Environment = std::collections::HashMap<String, Value>;

pub fn init_environment() -> Environment {
    let mut env = Environment::new();
    for &func in BULTIN_FUNCTIONS.iter() {
        let _ = env.insert(String::from(func), Value::Builtin(func));
    }
    let _ = env.insert(String::from(MODULE_PATH_ENV), Value::List(Vec::new()));
    env
}

/// check whether two numbers are equal
pub(crate) fn is_number_eq(n1: f64, n2: f64) -> bool { (n1 - n2).abs() <= 1.9073486328125e-6 }

pub(crate) fn expand_tilde(path: String) -> Option<String> {
    if path.starts_with('~') {
        Some(
            (match std::env::var(if cfg!(target_os = "windows") {
                "USERPROFILE"
            } else {
                "HOME"
            }) {
                Ok(p) => p,
                Err(_) => {
                    eprintln!(concat!(
                        "Error[ksl::eval::eval_apply]: ",
                        "Unable to retrieve user home directory path."
                    ));
                    return None;
                }
            }) + &path[1..],
        )
    } else {
        Some(path)
    }
}

/// initial size of read buffer
const INIT_READ_BUFFER_SIZE: usize = 1024;

/// read-eval-print-loop for ksl
pub fn repl() {
    // init repl environment
    let mut repl_env = init_environment();
    // buffer for readin
    let mut read_buffer = String::with_capacity(INIT_READ_BUFFER_SIZE);
    // in and out count
    let mut repl_count: usize = 1;
    // check whether brackets are matched
    let mut is_bracket_matched = false;
    // check whether braces are matched
    let mut is_brace_matched = false;
    // check whether double quotes are matched
    let mut is_quote_matched = false;
    // print basic help
    println!("ksl repl");
    println!("use `.help` to see other support actions");
    println!("use `.exit` to close repl\n");
    // main loop
    loop {
        // show repl prompt
        print!("In[{}]: ", repl_count);
        match std::io::Write::flush(&mut std::io::stdout()) {
            Ok(()) => (),
            Err(_) => {
                eprintln!(concat!(
                    "Error[ksl::repl]: ",
                    "Failed to refresh REPL output buffer."
                ));
                std::process::abort();
            }
        }
        // get user input
        read_buffer.clear();
        while !is_bracket_matched || !is_brace_matched || !is_quote_matched {
            let mut temp_buffer = String::with_capacity(INIT_READ_BUFFER_SIZE);
            match std::io::stdin().read_line(&mut temp_buffer) {
                Ok(_) => (),
                Err(_) => {
                    eprintln!(concat!("Error[ksl::repl]: ", "Failed to read user input."));
                    std::process::abort();
                }
            }
            if temp_buffer.trim().is_empty() {
                continue;
            }
            // check brackets and quotes
            read_buffer.extend(temp_buffer.chars());
            let mut is_string = false;
            let ((bkleft, bkright), (bleft, bright), quote_count): (
                (usize, usize),
                (usize, usize),
                usize,
            ) = read_buffer.chars().fold(((0, 0), (0, 0), 0), |acc, e| {
                if e == '[' && !is_string {
                    ((acc.0.0 + 1, acc.0.1), acc.1, acc.2)
                } else if e == ']' && !is_string {
                    ((acc.0.0, acc.0.1 + 1), acc.1, acc.2)
                } else if e == '{' && !is_string {
                    (acc.0, (acc.1.0 + 1, acc.1.1), acc.2)
                } else if e == '}' && !is_string {
                    (acc.0, (acc.1.0, acc.1.1 + 1), acc.2)
                } else if e == '"' {
                    is_string = !is_string;
                    (acc.0, acc.1, acc.2 + 1)
                } else {
                    acc
                }
            });
            is_bracket_matched = bkleft == bkright;
            is_brace_matched = bleft == bright;
            is_quote_matched = quote_count & 1 == 0
        }
        // reset input state
        is_bracket_matched = false;
        is_brace_matched = false;
        is_quote_matched = false;
        // eval user input
        read_buffer = read_buffer.trim().to_string();
        if let Some(is_need_eval) = try_handle_actions(&read_buffer, &repl_env) {
            if is_need_eval {
                if let Some((result, mut new_env)) = eval::eval(&read_buffer, &repl_env) {
                    println!("Out[{}]: {:?}", repl_count, result);
                    // repl cannot be a module
                    let _ = new_env.remove(MODULE_NAME_ENV);
                    repl_env.extend(new_env);
                    repl_count += 1;
                }
            }
        } else {
            break;
        }
    }
}

fn try_handle_actions(input: &str, env: &Environment) -> Option<bool> {
    match input {
        ".exit" => None,
        ".help" => {
            print_help_info();
            Some(false)
        }
        ".env" => {
            println!();
            println!("current env:");
            println!(
                "{{{}}}",
                env.iter()
                    .map(|(k, v)| if BULTIN_FUNCTIONS.contains(&k.as_str()) {
                        format!("<<{}>>", k)
                    } else if k == MODULE_PATH_ENV || k == MODULE_NAME_ENV {
                        String::new()
                    } else {
                        format!("<{}, {}>", k, v)
                    })
                    .filter(|p| !p.is_empty())
                    .collect::<Vec<String>>()
                    .join(", ")
            );
            println!();
            Some(false)
        }
        _ => Some(true),
    }
}

fn print_help_info() {
    println!();
    println!(".exit: close repl");
    println!(".help: show this message");
    println!(".env: show current environment");
    println!();
}