use std::{fmt, io, ops::RangeInclusive, rc::Rc}; use crate::{ compile::CompileError, vm::{ Value, instruction::{Instruction, InstructionDecodeError}, value::{BytecodeFunction, IdentifierValue}, }, }; #[derive(Debug, thiserror::Error)] #[error("{error}")] pub struct MachineErrorAt { // TODO ip where the error occured pub error: MachineError, pub location: Option, } #[derive(Debug)] pub struct MachineErrorLocation { pub function: Rc, pub offset: usize, } #[derive(Debug, PartialEq, thiserror::Error)] #[error("expected {expected}, got {got}")] pub struct ValueConversionError { pub expected: String, pub got: Value, } #[derive(Debug, thiserror::Error)] pub enum ReadError { #[error("{0}")] Lexical(nom::Err, nom::error::Error>), #[error("{0}")] Io(io::Error), } #[derive(Debug, PartialEq)] pub struct ArgumentCountError { pub function: Rc, pub expected_range: RangeInclusive, pub actual: usize, } #[derive(Debug, PartialEq, thiserror::Error)] pub enum MachineError { // VM itself #[error("instruction pointer is undefined")] InstructionPointerUndefined, #[error("instruction pointer is out of bounds")] InstructionPointerOutOfBounds, #[error("instruction fetch failed")] InstructionFetch, #[error("instruction decode error: {0}")] InstructionDecode(#[from] InstructionDecodeError), #[error("data stack overflowed")] DataStackOverflow, #[error("data stack underflowed")] DataStackUnderflow, #[error("call stack overflowed")] CallStackOverflow, #[error("call stack underflowed")] CallStackUnderflow, #[error("undefined upvalue reference")] UndefinedUpvalueReference, #[error("undefined local reference")] UndefinedLocalReference, #[error("undefined constant reference")] UndefinedConstantReference, #[error("unbound identifier reference: {0}")] UnboundIdentifier(IdentifierValue), #[error("invalid {0} argument: {1}")] InvalidInstructionArgument(Instruction, ValueConversionError), #[error("invalid branch target: {0}{1:+}")] InvalidBranchTarget(usize, isize), #[error("GET_TEMP with an empty temp register")] TempRegisterEmpty, #[error("{0}")] ArgumentCount(ArgumentCountError), // Syntax+evaluation // #[error("evaluation error: {0}")] // EvaluationError(EvalError), #[error("invalid argument count")] InvalidArgumentCount, #[error("aborted: {0}")] Abort(Rc), #[error("value conversion error: {0}")] ValueConversion(#[from] ValueConversionError), #[error("syntax error: {0}")] Read(ReadError), #[error("compile error: {0}")] Compile(#[from] CompileError), } impl MachineError { pub fn at(self, location: Option) -> MachineErrorAt { MachineErrorAt { error: self, location, } } pub fn at_unknown(self) -> MachineErrorAt { self.at(None) } } impl MachineErrorAt { pub fn at_unknown(error: MachineError) -> Self { Self::at(error, None) } pub fn at(error: MachineError, location: Option) -> Self { Self { error, location } } } impl MachineErrorLocation { pub fn disassemble_chunk(&self, address: usize, before: usize, after: usize, arrow: bool) { self.function.disassemble(address, before, after, arrow); } } impl fmt::Display for MachineErrorLocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:p}+{}", self.function, self.offset) } } impl PartialEq for ReadError { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Lexical(a), Self::Lexical(b)) => a == b, (Self::Io(a), Self::Io(b)) => a.raw_os_error() == b.raw_os_error(), _ => false, } } } impl fmt::Display for ArgumentCountError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let min_arity = *self.expected_range.start(); let max_arity = *self.expected_range.end(); let one_argument = min_arity == max_arity; let too_few = self.actual < min_arity; write!( f, "too {} arguments for function {}: expected ", if too_few { "few" } else { "many" }, self.function )?; if one_argument { write!(f, "{min_arity}")?; write!(f, " argument")?; if min_arity != 1 { write!(f, "s")?; } } else { write!(f, "{min_arity}-{max_arity} arguments")?; } write!(f, ", got {}", self.actual) } }