Files
lysp/src/main.rs
T
2026-05-08 17:16:32 +03:00

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
}
}
}