use crate::{Ptr, prelude::*};
use koto_bytecode::{Chunk, ModuleLoaderError};
use koto_parser::format_source_excerpt;
use std::{error, fmt, time::Duration};
use thiserror::Error;
#[derive(Error, Clone)]
#[allow(missing_docs)]
pub enum ErrorKind {
#[error("{0}")]
StringError(String),
#[error("{}", display_thrown_value(thrown_value, vm.as_deref()))]
KotoError {
thrown_value: KValue,
vm: Option<Box<KotoVm>>,
},
#[error("execution timed out (the limit of {} seconds was reached)", .0.as_secs_f64())]
Timeout(Duration),
#[error("unable to borrow an object that is already mutably borrowed")]
UnableToBorrowObject,
#[error(
"Unexpected arguments.\n Expected: {expected}\n Provided: |{}|",
value_types_as_string(unexpected)
)]
UnexpectedArguments {
expected: String,
unexpected: Vec<KValue>,
},
#[error("insufficient arguments ({actual}, expected {expected})")]
InsufficientArguments { expected: u8, actual: u8 },
#[error("too many arguments ({actual}, expected {expected})")]
TooManyArguments { expected: u8, actual: u8 },
#[error("expected {expected}, found {}", unexpected.type_as_string())]
UnexpectedType {
expected: String,
unexpected: KValue,
},
#[error("expected {expected}, found {unexpected}")]
UnexpectedObjectType {
expected: &'static str,
unexpected: KString,
},
#[error("{fn_name} is unimplemented for {object_type}")]
Unimplemented {
fn_name: &'static str,
object_type: KString,
},
#[error("unable to perform operation '{op}' with '{}' and '{}'", lhs.type_as_string(), rhs.type_as_string())]
InvalidBinaryOp {
lhs: KValue,
rhs: KValue,
op: BinaryOp,
},
#[error("empty call stack")]
EmptyCallStack,
#[error("missing sequence builder")]
MissingSequenceBuilder,
#[error("missing string builder")]
MissingStringBuilder,
#[error("this operation is unsupported on this platform")]
UnsupportedPlatform,
#[error(
"an unexpected error occurred, please report this as a bug at\nhttps://github.com/koto-lang/koto/issues"
)]
UnexpectedError,
#[error(transparent)]
CompileError(#[from] ModuleLoaderError),
}
fn display_thrown_value(value: &KValue, vm: Option<&KotoVm>) -> String {
if let Some(vm) = vm {
let mut display_context = DisplayContext::with_vm(vm);
if value.display(&mut display_context).is_ok() {
return display_context.result();
}
}
"Unable to display error message".into()
}
impl fmt::Debug for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
#[derive(Clone, Debug)]
pub struct Error {
pub error: ErrorKind,
pub context: Vec<String>,
pub trace: Vec<InstructionFrame>,
}
impl Error {
pub fn new(error: ErrorKind) -> Self {
Self {
error,
context: Vec::new(),
trace: Vec::new(),
}
}
pub fn with_error_frame(error: ErrorKind, error_frame: InstructionFrame) -> Self {
Self {
error,
context: Vec::new(),
trace: vec![error_frame],
}
}
#[must_use]
pub fn with_context(mut self, prefix: String) -> Self {
self.context.push(prefix);
self
}
pub fn is_indentation_error(&self) -> bool {
match &self.error {
ErrorKind::CompileError(error) => error.is_indentation_error(),
_ => false,
}
}
pub fn is_unimplemented_error(&self) -> bool {
matches!(&self.error, ErrorKind::Unimplemented { .. })
}
pub(crate) fn from_koto_value(thrown_value: KValue) -> Self {
Self::new(ErrorKind::KotoError {
thrown_value,
vm: None, })
}
pub(crate) fn extend_trace(&mut self, instruction_frame: InstructionFrame) {
self.trace.push(instruction_frame);
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)?;
for context in self.context.iter() {
write!(f, " ({context})")?;
}
for InstructionFrame { chunk, instruction } in self.trace.iter() {
write!(f, "\n--- ")?;
if let Some(span) = chunk.debug_info.get_source_span(*instruction) {
f.write_str(&format_source_excerpt(
&chunk.debug_info.source,
&span,
chunk.path.as_deref(),
))?;
continue;
}
write!(
f,
"Runtime error at instruction {} in chunk {:p}",
instruction, &chunk
)?
}
Ok(())
}
}
impl error::Error for Error {}
impl From<String> for Error {
fn from(error: String) -> Self {
Self::new(ErrorKind::StringError(error))
}
}
impl From<&str> for Error {
fn from(error: &str) -> Self {
Self::new(ErrorKind::StringError(error.into()))
}
}
impl<T> From<T> for Error
where
T: Into<ErrorKind>,
{
fn from(error: T) -> Self {
Self::new(error.into())
}
}
#[derive(Clone, Debug)]
#[allow(missing_docs)]
pub struct InstructionFrame {
pub chunk: Ptr<Chunk>,
pub instruction: u32,
}
pub type Result<T> = std::result::Result<T, Error>;
#[macro_export]
macro_rules! runtime_error {
($error:literal) => {
Err($crate::Error::from(format!($error)))
};
($error:expr) => {
Err($crate::Error::from($error))
};
($error:literal, $($y:expr),+ $(,)?) => {
Err($crate::Error::from(format!($error, $($y),+)))
};
}
pub fn unexpected_type<T>(expected_str: &str, unexpected: &KValue) -> Result<T> {
runtime_error!(ErrorKind::UnexpectedType {
expected: expected_str.into(),
unexpected: unexpected.clone(),
})
}
pub fn unexpected_args<T>(expected_str: &str, arguments: &[KValue]) -> Result<T> {
runtime_error!(ErrorKind::UnexpectedArguments {
expected: expected_str.into(),
unexpected: arguments.into(),
})
}
pub fn unexpected_args_after_instance<T>(
expected_str: &str,
instance: &KValue,
arguments: &[KValue],
) -> Result<T> {
let unexpected = std::iter::once(instance.clone())
.chain(arguments.iter().cloned())
.collect();
runtime_error!(ErrorKind::UnexpectedArguments {
expected: expected_str.into(),
unexpected,
})
}
fn value_types_as_string(values: &[KValue]) -> String {
match values {
[] => "".to_string(),
[single_value] => single_value.type_as_string().to_string(),
_ => {
let mut result = String::new();
let mut first = true;
for value in values {
if !first {
result.push_str(", ");
}
first = false;
result.push_str(&value.type_as_string());
}
result
}
}
}