use ksl::{
BULTIN_FUNCTIONS,
Environment,
MODULE_NAME_ENV,
MODULE_PATH_ENV,
eval::{self, eval},
init_environment,
value::Value,
};
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();
}
}
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 view full help information.");
println!("Use `.exit` to exit the REPL.");
println!();
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 get 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_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);
let _ = new_env.remove(MODULE_NAME_ENV);
repl_env.extend(new_env);
repl_count += 1;
}
}
} else {
break;
}
}
}
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),
}
}
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!();
}
fn print_command_line_help_information() {
println!("Avaliable argument:");
println!("<file-path>: file to evaluate");
println!("-h: show this message");
}