mod builtin;
pub mod eval;
mod token;
pub mod value;
pub use builtin::{FALSE_SYMBOL, TRUE_SYMBOL};
use crate::{
builtin::{BULTIN_FUNCTIONS, MODULE_NAME_ENV, MODULE_PATH_ENV},
value::Value,
};
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
}
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)
}
}
const INIT_READ_BUFFER_SIZE: usize = 1024;
pub fn repl() {
let mut repl_env = init_environment();
let mut read_buffer = String::with_capacity(INIT_READ_BUFFER_SIZE);
let mut repl_count: usize = 1;
let mut is_bracket_matched = false;
let mut is_brace_matched = false;
let mut is_quote_matched = false;
println!("ksl repl");
println!("use `.help` to see other support actions");
println!("use `.exit` to close repl\n");
loop {
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();
}
}
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;
}
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
}
is_bracket_matched = false;
is_brace_matched = false;
is_quote_matched = false;
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);
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!();
}