ksl 0.1.7

KSL core library and interpreter
Documentation
//! # KSL interpreter

use ksl::{
    BULTIN_FUNCTIONS,
    Environment,
    MODULE_NAME_ENV,
    MODULE_PATH_ENV,
    eval::{self, eval},
    init_environment,
    value::Value,
};

/// KSL main function.
fn main() {
    let args = std::env::args().collect::<Vec<String>>();
    if args.len() == 1 {
        repl();
    } else if args.len() == 2 {
        let param = &args[1];
        match param.trim() {
            "-h" => print_command_line_help_information(),
            file_path => {
                match std::fs::File::options()
                    .read(true)
                    .open(file_path)
                    .and_then(|f| {
                        let mut buffer = String::new();
                        std::io::Read::read_to_string(&mut std::io::BufReader::new(f), &mut buffer).map(|_| buffer)
                    })
                    .map(|source| eval(&source, &init_environment()))
                {
                    Ok(Some((Value::Unit, _))) => (),
                    Ok(Some((result, _))) => println!("{}", result),
                    _ => {
                        eprintln!("Failed to evaluate `{}`.", file_path);
                    }
                }
            }
        }
    } else {
        eprintln!("Invalid arguments: `{:?}`.", args);
        print_command_line_help_information();
    }
}

/// Initial read buffer size.
const INIT_READ_BUFFER_SIZE: usize = 1024;

/// KSL REPL function definitions.
pub fn repl() {
    // Get the initial REPL environment.
    let mut repl_env = init_environment();
    // Get the input buffer.
    let mut read_buffer = String::with_capacity(INIT_READ_BUFFER_SIZE);
    // REPL interaction count.
    let mut repl_count: usize = 1;
    // Flag used to check if paired symbols are closed.
    let mut is_bracket_matched = false;
    let mut is_brace_matched = false;
    let mut is_quote_matched = false;
    // Print simple help information.
    println!("KSL REPL");
    println!("Use `.help` to view full help information.");
    println!("Use `.exit` to exit the REPL.");
    println!();
    // REPL main loop.
    loop {
        // Prints the REPL input 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 text.
        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 get user input."));
                    std::process::abort();
                }
            }
            if temp_buffer.trim().is_empty() {
                continue;
            }
            read_buffer.extend(temp_buffer.chars());
            // Check if paired symbols are closed.
            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 detection state.
        is_bracket_matched = false;
        is_brace_matched = false;
        is_quote_matched = false;
        // Evaluate user input.
        read_buffer = read_buffer.trim().to_string();
        if let Some(is_need_eval) = try_handle_user_command(&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);
                    // Remove module information from the REPL.
                    let _ = new_env.remove(MODULE_NAME_ENV);
                    repl_env.extend(new_env);
                    repl_count += 1;
                }
            }
        } else {
            break;
        }
    }
}

/// Handle user special commands.
fn try_handle_user_command(input: &str, env: &Environment) -> Option<bool> {
    match input {
        ".exit" => None,
        ".help" => {
            print_help_information();
            Some(false)
        }
        ".env" => {
            println!();
            println!("Current environment:");
            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),
    }
}

/// Print help information.
fn print_help_information() {
    println!();
    println!(".help: Display this information.");
    println!(".env: Output all bindings in the current environment.");
    println!(".exit: Exit the REPL.");
    println!();
}

/// Print command-line help information.
fn print_command_line_help_information() {
    println!("Avaliable argument:");
    println!("<file-path>: file to evaluate");
    println!("-h:  show this message");
}