use myslip::parse::parsetree::parse_to_ast; use myslip::sexp::{SExp, SExp::*, SLeaf::Nil, util::scons}; use myslip::r#type::Type::NilType; use std::{io, io::Write, io::Read, env}; use std::fs::File; fn read_bind_files( file_names: Vec ) -> Result, String> { let mut res = vec![]; for name in file_names { let mut file = File::open(name).map_err(|e| e.to_string())?; let mut contents = String::new(); file.read_to_string(&mut contents) .map_err(|e| e.to_string())?; let exps = parse_to_ast(&contents)?.parts(); for exp in exps { if exp.clone().check_let().is_some() { res.push(exp); } else { return Err(format!("'{}' isn't a declaration", exp)); } } } Ok(res) } fn read_file_to_execute( file_name: String ) -> Result { let mut file = File::open(file_name) .map_err(|e| e.to_string())?; let mut contents = String::new(); file.read_to_string(&mut contents) .map_err(|e| e.to_string())?; match parse_to_ast(&contents) { Ok(SCons(a, b)) if *b == Atom(Nil) => Ok(*a), t => t } } fn main() { let mut file_to_execute: Option = None; let mut bind_files: Vec = vec!["stdlib.slip".to_string()]; let mut next_is_load = false; let mut no_stdlib = false; let mut babysteps = false; let mut help = false; for arg in env::args() { if next_is_load { bind_files.push(arg); next_is_load = false; continue; } match arg.as_str() { s if s.ends_with("myslip") => continue, "-l" | "--load" => next_is_load = true, "--no-stdlib" => no_stdlib = true, "--babysteps" => babysteps = true, "--help" | "-h" | "-?" => help = true, s => match file_to_execute { Some(n) => { println!("Error: can't execute both '{}' and '{}', please specify just one file. See '--help' for correct syntax.", s, n); return; }, None => file_to_execute = Some(s.to_string()), } } } if help { println!("{}", String::from("") + "myslip [options] [file]\n" + "\n" + "If [file] is given, executes the s-expression contained by\n" + "the file. Otherwise starts the myslip repl. Note that the\n" + "file stdlib.slip needs to be in the current working\n" + "directory or the `--no-stdlib` option must be used.\n" + "\n" + "Options:\n" + "\n" + " --load [defs] | -l [defs] loads declarations from file\n" + " named [defs] before execution\n" + "\n" + " --no-stdlib stops myslip from loading the\n" + " standard library at startup\n" + "\n" + " --babysteps makes the repl evaluate\n" + " s-expressions one step at a\n" + " time, displaying the result of\n" + " each one on a separate line\n" + "\n" + " --help | -h | -? displays this help message\n" ); return; } if next_is_load { println!( "Error: '--load' can't be the last element of the argument list. See '--help' for correct syntax." ); return; } if no_stdlib { bind_files.remove(0); } let binds = match read_bind_files(bind_files) { Ok(x) => x, Err(e) => { println!( "Error reading declarations from files: {}", e.to_string() ); return; } }; match file_to_execute { Some(name) => { let exp = match read_file_to_execute(name) { Ok(e) => e, Err(e) => { println!("Error reading source file: {e}"); return; } }; execute_expression(&binds, exp, false); }, None => { match repl(binds, babysteps) { Ok(()) => println!("bye :)"), Err(e) => println!("Error: {}", e), } } } } fn prompt_line(stdin: &io::Stdin, stdout: &mut io::Stdout) -> Result<(bool, String), io::Error> { let mut line = String::new(); print!("> "); match stdout.flush() { Ok(_) => (), Err(_) => println!("Enter s-expression:"), }; let code = stdin.read_line(&mut line)?; Ok((code == 0, line)) } fn execute_expression(binds: &Vec, mut exp: SExp, babysteps: bool) { for bind in binds.into_iter().rev() { exp = scons(bind.clone(), exp); } let ty = match exp .type_check() .map_err(|e| e.to_string()) { Ok(t) => t, Err(e) => { println!("Type error: {}", e); return; }, }; if babysteps { if exp.is_value() { println!("{} : {}", exp, ty); } while !exp.is_value() { exp = match exp.step() { Ok(t) => t, Err(e) => { println!("Runtime error: {}", e); return; }, }; println!("{} : {}", exp, ty); } } else { let result = match exp.multistep() { Ok(t) => t, Err(e) => { println!("Runtime error: {}", e); return; }, }; println!("{} : {}", result, ty); } } fn repl(mut binds: Vec, babysteps: bool) -> Result<(), io::Error> { let stdin = io::stdin(); let mut stdout = io::stdout(); loop { let (eof, input) = prompt_line(&stdin, &mut stdout)?; if eof || input == "exit\n" { break; } let orig_expression = match parse_to_ast(&input) { Ok(SCons(a, b)) if *b == Atom(Nil) && *a != Atom(Nil) => *a, Ok(Atom(Nil)) => scons(Nil, Nil), Ok(t) => t, Err(e) => { println!("Syntax error: {}", e); continue; }, }; if let Some((name, _value)) = orig_expression.clone().check_let() { let mut expr = scons(orig_expression.clone(), scons(Nil, Nil)); for i in 1..=binds.len() { expr = scons(binds[binds.len() - i].clone(), expr); } match expr.type_check() { Ok(NilType) => { binds.push(orig_expression); println!("{name} saved"); continue; }, Err(e) => { println!("Type error: {}", e); continue; }, Ok(_) => (), } } execute_expression(&binds, orig_expression, babysteps); } Ok(()) }