200 lines
5.1 KiB
Rust
200 lines
5.1 KiB
Rust
use std::{
|
|
fs::File,
|
|
io::{self, BufReader},
|
|
path::{Path, PathBuf},
|
|
process::ExitCode,
|
|
str::FromStr,
|
|
};
|
|
|
|
use clap::Parser;
|
|
use lysp::{
|
|
compile::{CompileError, CompileOptions, ParseError},
|
|
error::{EvalError, MachineErrorKind},
|
|
read::{InteractiveReader, ModuleReader, read},
|
|
util::Either,
|
|
vm::{env::Environment, machine::Machine, prelude, value::Value},
|
|
};
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
#[error("{0}")]
|
|
enum Error {
|
|
Machine(#[from] MachineErrorKind),
|
|
Eval(#[from] EvalError),
|
|
Io(#[from] io::Error),
|
|
Compile(#[from] CompileError),
|
|
#[error("Error already printed")]
|
|
Printed,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
pub enum Trace {
|
|
Compile,
|
|
Execute,
|
|
}
|
|
|
|
impl FromStr for Trace {
|
|
type Err = String;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"compile" => Ok(Self::Compile),
|
|
"execute" => Ok(Self::Execute),
|
|
_ => Err(format!("Unknown trace flag: {s:?}")),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Parser)]
|
|
struct Args {
|
|
#[clap(
|
|
short = 'T',
|
|
long = "trace",
|
|
help = "Enable tracing of execution/compilation steps"
|
|
)]
|
|
trace: Vec<Trace>,
|
|
module: Option<PathBuf>,
|
|
}
|
|
|
|
fn print_syntax_errors(errors: &[ParseError]) {
|
|
if errors.len() > 1 {
|
|
eprintln!("Syntax errors:");
|
|
} else {
|
|
eprintln!("Syntax error:");
|
|
}
|
|
eprintln!();
|
|
for error in errors {
|
|
let ParseError { input, error } = error;
|
|
eprintln!(" * In expression:");
|
|
eprintln!();
|
|
eprintln!(" {input}");
|
|
eprintln!();
|
|
eprintln!(" :: {error}");
|
|
}
|
|
}
|
|
|
|
fn handle_eval_error(value: Option<&Value>, input: EvalError) -> Error {
|
|
match input {
|
|
EvalError::Machine(error) => {
|
|
if let Some(value) = value {
|
|
eprintln!("Error in expression:");
|
|
eprintln!();
|
|
eprintln!(" {value}:");
|
|
eprintln!();
|
|
}
|
|
eprintln!(":: {}", error.error);
|
|
eprintln!();
|
|
if let Some(ip) = error.ip.as_ref() {
|
|
ip.module.dump(Some(ip.address), 8);
|
|
}
|
|
}
|
|
EvalError::Compile(CompileError::Parse(errors)) => {
|
|
print_syntax_errors(&errors);
|
|
}
|
|
error => {
|
|
if let Some(value) = value {
|
|
eprintln!("Error in expression:");
|
|
eprintln!();
|
|
eprintln!(" {value}:");
|
|
eprintln!();
|
|
}
|
|
eprintln!(":: {error}");
|
|
}
|
|
}
|
|
Error::Printed
|
|
}
|
|
|
|
fn handle_module_error(input: Either<EvalError, Vec<ParseError>>) -> Error {
|
|
match input {
|
|
Either::Left(error) => handle_eval_error(None, error),
|
|
Either::Right(errors) => {
|
|
print_syntax_errors(&errors);
|
|
Error::Printed
|
|
}
|
|
}
|
|
}
|
|
|
|
fn eval(
|
|
options: &CompileOptions,
|
|
vm: &mut Machine,
|
|
env: &mut Environment,
|
|
value: Value,
|
|
) -> Option<Value> {
|
|
let result = vm.eval_value(options.clone(), env, value.clone());
|
|
match result {
|
|
Ok(r) => Some(r),
|
|
Err(error) => {
|
|
handle_eval_error(Some(&value), error);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_interactive(
|
|
compile_options: &CompileOptions,
|
|
vm: &mut Machine,
|
|
env: &mut Environment,
|
|
) -> Result<(), Error> {
|
|
let mut reader = InteractiveReader::new("> ", ">> ");
|
|
|
|
loop {
|
|
let value = match read(&mut reader, vm, env) {
|
|
Ok(Some(value)) => value,
|
|
Ok(None) => break,
|
|
Err(error) => {
|
|
eprintln!("{error}");
|
|
continue;
|
|
}
|
|
};
|
|
if let Some(value) = eval(compile_options, vm, env, value) {
|
|
println!("== {value}");
|
|
} else {
|
|
reader.reset();
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn run_module<P: AsRef<Path>>(
|
|
compile_options: &CompileOptions,
|
|
vm: &mut Machine,
|
|
env: &mut Environment,
|
|
path: P,
|
|
) -> Result<(), Error> {
|
|
let path = path.as_ref();
|
|
let reader = BufReader::new(File::open(path)?);
|
|
let module_reader = ModuleReader::new(reader);
|
|
let module = match module_reader.compile(compile_options, env) {
|
|
Ok(module) => module,
|
|
Err(error) => return Err(handle_module_error(error)),
|
|
};
|
|
|
|
match vm.eval_module(env, module) {
|
|
Ok(_) => Ok(()),
|
|
Err(error) => Err(handle_eval_error(None, error)),
|
|
}
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
let args = Args::parse();
|
|
let mut vm = Machine::default();
|
|
let compile_options = CompileOptions {
|
|
trace_compile: args.trace.contains(&Trace::Compile),
|
|
};
|
|
vm.trace_instructions = args.trace.contains(&Trace::Execute);
|
|
let mut env = Environment::default();
|
|
prelude::load(&mut env);
|
|
let result = match args.module.as_ref() {
|
|
Some(module) => run_module(&compile_options, &mut vm, &mut env, module),
|
|
None => run_interactive(&compile_options, &mut vm, &mut env),
|
|
};
|
|
match result {
|
|
Ok(()) => ExitCode::SUCCESS,
|
|
Err(Error::Printed) => ExitCode::FAILURE,
|
|
Err(error) => {
|
|
eprintln!("{error}");
|
|
ExitCode::FAILURE
|
|
}
|
|
}
|
|
}
|