use std::error::Error;
use std::fmt::{Display, Formatter, Result as FResult};
use std::path::PathBuf;
use brane_ast::func_id::FunctionId;
use brane_ast::{DataType, MergeStrategy};
use console::style;
use enum_debug::EnumDebug as _;
use specifications::data::DataName;
use specifications::version::Version;
use crate::pc::ProgramCounter;
fn prettyprint_err_instr(pc: ProgramCounter, instr: Option<usize>, err: &dyn Error) {
eprintln!(
"{}: {}: {}",
style(format!("{}{}", pc, if let Some(instr) = instr { format!(":{instr}") } else { String::new() })).bold(),
style("error").red().bold(),
err
);
}
fn prettyprint_err(pc: ProgramCounter, err: &dyn Error) {
eprintln!("{}: {}: {}", style(format!("{pc}")).bold(), style("error").red().bold(), err);
}
pub trait ReturnEdge {
type Ret;
fn to(self, pc: ProgramCounter) -> Result<Self::Ret, VmError>;
fn to_instr(self, pc: ProgramCounter, instr: usize) -> Result<Self::Ret, VmError>;
}
impl<T> ReturnEdge for Result<T, StackError> {
type Ret = T;
fn to(self, pc: ProgramCounter) -> Result<Self::Ret, VmError> {
match self {
Ok(val) => Ok(val),
Err(err) => Err(VmError::StackError { pc, instr: None, err }),
}
}
fn to_instr(self, pc: ProgramCounter, instr: usize) -> Result<Self::Ret, VmError> {
match self {
Ok(val) => Ok(val),
Err(err) => Err(VmError::StackError { pc, instr: Some(instr), err }),
}
}
}
#[derive(Debug)]
pub enum ValueError {
JsonError { err: serde_json::Error },
CastError { got: DataType, target: DataType },
}
impl Display for ValueError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use ValueError::*;
match self {
JsonError { err } => write!(f, "Cannot parse the given JSON value to a Value: {err}"),
CastError { got, target } => write!(f, "Cannot cast a value of type {got} to {target}"),
}
}
}
impl Error for ValueError {}
#[derive(Debug)]
pub enum StackError {
StackOverflowError { size: usize },
}
impl Display for StackError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use StackError::*;
match self {
StackOverflowError { size } => write!(f, "Stack overflow occurred (has space for {size} values)"),
}
}
}
impl Error for StackError {}
#[derive(Debug)]
pub enum FrameStackError {
EmptyError,
OverflowError { size: usize },
UndeclaredVariable { name: String },
DuplicateDeclaration { name: String },
UndeclaredUndeclaration { name: String },
UninitializedVariable { name: String },
VarTypeError { name: String, got: DataType, expected: DataType },
VariableNotInScope { name: String },
}
impl Display for FrameStackError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use FrameStackError::*;
match self {
EmptyError => write!(f, "Frame stack empty"),
OverflowError { size } => write!(f, "Frame stack overflow occurred (has space for {size} frames/nested calls)"),
UndeclaredVariable { name } => write!(f, "Undeclared variable '{name}'"),
DuplicateDeclaration { name } => write!(f, "Cannot declare variable '{name}' if it is already declared"),
UndeclaredUndeclaration { name } => write!(f, "Cannot undeclare variable '{name}' that was never declared"),
UninitializedVariable { name } => write!(f, "Uninitialized variable '{name}'"),
VarTypeError { name, got, expected } => write!(f, "Cannot assign value of type {got} to variable '{name}' of type {expected}"),
VariableNotInScope { name } => write!(f, "Variable '{name}' is declared but not currently in scope"),
}
}
}
impl Error for FrameStackError {}
#[derive(Debug)]
pub enum VarRegError {
DuplicateDeclaration { id: usize, old_name: String, old_type: DataType, new_name: String, new_type: DataType },
UndeclaredVariable { id: usize },
UninitializedVariable { id: usize },
}
impl Display for VarRegError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use VarRegError::*;
match self {
DuplicateDeclaration { id, old_name, old_type, new_name, new_type } => {
write!(f, "Variable {id} was already declared before (old '{old_name}: {old_type}', new '{new_name}: {new_type}')")
},
UndeclaredVariable { id } => write!(f, "Variable {id} was not declared"),
UninitializedVariable { id } => write!(f, "Variable {id} was not initialized"),
}
}
}
impl Error for VarRegError {}
#[derive(Debug)]
pub enum VmError {
GlobalStateError { err: Box<dyn Send + Sync + Error> },
UnknownFunction { func: FunctionId },
PcOutOfBounds { func: FunctionId, edges: usize, got: usize },
EmptyStackError { pc: ProgramCounter, instr: Option<usize>, expected: DataType },
StackTypeError { pc: ProgramCounter, instr: Option<usize>, got: DataType, expected: DataType },
StackLhsRhsTypeError { pc: ProgramCounter, instr: usize, got: (DataType, DataType), expected: DataType },
ArrayTypeError { pc: ProgramCounter, instr: usize, got: DataType, expected: DataType },
InstanceTypeError { pc: ProgramCounter, instr: usize, class: String, field: String, got: DataType, expected: DataType },
CastError { pc: ProgramCounter, instr: usize, err: ValueError },
ArrIdxOutOfBoundsError { pc: ProgramCounter, instr: usize, got: i64, max: usize },
ProjUnknownFieldError { pc: ProgramCounter, instr: usize, class: String, field: String },
VarDecError { pc: ProgramCounter, instr: usize, err: FrameStackError },
VarUndecError { pc: ProgramCounter, instr: usize, err: FrameStackError },
VarGetError { pc: ProgramCounter, instr: usize, err: FrameStackError },
VarSetError { pc: ProgramCounter, instr: usize, err: FrameStackError },
SpawnError { pc: ProgramCounter, err: tokio::task::JoinError },
BranchTypeError { pc: ProgramCounter, branch: usize, got: DataType, expected: DataType },
IllegalBranchType { pc: ProgramCounter, branch: usize, merge: MergeStrategy, got: DataType, expected: DataType },
FunctionTypeError { pc: ProgramCounter, name: String, arg: usize, got: DataType, expected: DataType },
UnresolvedLocation { pc: ProgramCounter, name: String },
UnknownInput { pc: ProgramCounter, task: String, name: DataName },
UnplannedInput { pc: ProgramCounter, task: String, name: DataName },
FrameStackPushError { pc: ProgramCounter, err: FrameStackError },
FrameStackPopError { pc: ProgramCounter, err: FrameStackError },
ReturnTypeError { pc: ProgramCounter, got: DataType, expected: DataType },
TaskTypeError { pc: ProgramCounter, name: String, arg: usize, got: DataType, expected: DataType },
UnknownData { pc: ProgramCounter, name: String },
UnknownResult { pc: ProgramCounter, name: String },
UnknownPackage { pc: ProgramCounter, name: String, version: Version },
ArgumentsSerializeError { pc: ProgramCounter, err: serde_json::Error },
StackError { pc: ProgramCounter, instr: Option<usize>, err: StackError },
Custom { pc: ProgramCounter, err: Box<dyn Send + Sync + Error> },
}
impl VmError {
#[inline]
pub fn prettyprint(&self) {
use VmError::*;
match self {
GlobalStateError { .. } => eprintln!("{self}"),
UnknownFunction { .. } => eprintln!("{self}"),
PcOutOfBounds { .. } => eprintln!("{self}"),
EmptyStackError { pc, instr, .. } => prettyprint_err_instr(*pc, *instr, self),
StackTypeError { pc, instr, .. } => prettyprint_err_instr(*pc, *instr, self),
StackLhsRhsTypeError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
ArrayTypeError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
InstanceTypeError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
CastError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
ArrIdxOutOfBoundsError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
ProjUnknownFieldError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
VarDecError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
VarUndecError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
VarGetError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
VarSetError { pc, instr, .. } => prettyprint_err_instr(*pc, Some(*instr), self),
SpawnError { pc, .. } => prettyprint_err(*pc, self),
BranchTypeError { pc, .. } => prettyprint_err(*pc, self),
IllegalBranchType { pc, .. } => prettyprint_err(*pc, self),
FunctionTypeError { pc, .. } => prettyprint_err(*pc, self),
UnresolvedLocation { pc, .. } => prettyprint_err(*pc, self),
UnknownInput { pc, .. } => prettyprint_err(*pc, self),
UnplannedInput { pc, .. } => prettyprint_err(*pc, self),
FrameStackPushError { pc, .. } => prettyprint_err(*pc, self),
FrameStackPopError { pc, .. } => prettyprint_err(*pc, self),
ReturnTypeError { pc, .. } => prettyprint_err(*pc, self),
TaskTypeError { pc, .. } => prettyprint_err(*pc, self),
UnknownData { pc, .. } => prettyprint_err(*pc, self),
UnknownResult { pc, .. } => prettyprint_err(*pc, self),
UnknownPackage { pc, .. } => prettyprint_err(*pc, self),
ArgumentsSerializeError { pc, .. } => prettyprint_err(*pc, self),
StackError { pc, instr, .. } => prettyprint_err_instr(*pc, *instr, self),
Custom { pc, .. } => prettyprint_err(*pc, self),
}
}
}
impl Display for VmError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use VmError::*;
match self {
GlobalStateError { err } => write!(f, "Could not create custom state: {err}"),
UnknownFunction { func } => write!(f, "Unknown function {func}"),
PcOutOfBounds { func, edges, got } => write!(f, "Edge index {got} is out-of-bounds for function {func} with {edges} edges"),
EmptyStackError { expected, .. } => write!(f, "Expected a value of type {expected} on the stack, but stack was empty"),
StackTypeError { got, expected, .. } => write!(f, "Expected a value of type {expected} on the stack, but got a value of type {got}"),
StackLhsRhsTypeError { got, expected, .. } => write!(
f,
"Expected a lefthand-side and righthand-side of (the same) {} type on the stack, but got types {} and {}, respectively (remember \
that rhs is on top)",
expected, got.0, got.1
),
ArrayTypeError { got, expected, .. } => {
write!(f, "Expected an array element of type {expected} on the stack, but got a value of type {got}")
},
InstanceTypeError { class, field, got, expected, .. } => {
write!(f, "Expected field '{field}' of class '{class}' to have type {expected}, but found type {got}")
},
CastError { err, .. } => write!(f, "Failed to cast top value on the stack: {err}"),
ArrIdxOutOfBoundsError { got, max, .. } => write!(f, "Index {got} is out-of-bounds for an array of length {max}"),
ProjUnknownFieldError { class, field, .. } => write!(f, "Class '{class}' has not field '{field}'"),
VarDecError { err, .. } => write!(f, "Could not declare variable: {err}"),
VarUndecError { err, .. } => write!(f, "Could not undeclare variable: {err}"),
VarGetError { err, .. } => write!(f, "Could not get variable: {err}"),
VarSetError { err, .. } => write!(f, "Could not set variable: {err}"),
SpawnError { err, .. } => write!(f, "Failed to spawn new thread: {err}"),
BranchTypeError { branch, got, expected, .. } => {
write!(f, "Branch {branch} in parallel statement did not return value of type {expected}; got {got} instead")
},
IllegalBranchType { branch, merge, got, expected, .. } => write!(
f,
"Branch {branch} returned a value of type {got}, but the current merge strategy ({merge:?}) requires values of {expected} type"
),
FunctionTypeError { name, arg, got, expected, .. } => {
write!(f, "Argument {arg} for function '{name}' has incorrect type: expected {expected}, got {got}")
},
UnresolvedLocation { name, .. } => write!(f, "Cannot call task '{name}' because it has no resolved location."),
UnknownInput { task, name, .. } => write!(f, "{} '{}' is not a possible input for task '{}'", name.variant(), name.name(), task),
UnplannedInput { task, name, .. } => write!(f, "{} '{}' as input for task '{}' is not yet planned", name.variant(), name.name(), task),
FrameStackPushError { err, .. } => write!(f, "Failed to push to frame stack: {err}"),
FrameStackPopError { err, .. } => write!(f, "Failed to pop from frame stack: {err}"),
ReturnTypeError { got, expected, .. } => write!(f, "Got incorrect return type for function: expected {expected}, got {got}"),
TaskTypeError { name, arg, got, expected, .. } => {
write!(f, "Task '{name}' expected argument {arg} to be of type {expected}, but got {got}")
},
UnknownData { name, .. } => write!(f, "Encountered unknown dataset '{name}'"),
UnknownResult { name, .. } => write!(f, "Encountered unknown result '{name}'"),
UnknownPackage { name, version, .. } => write!(
f,
"Unknown package with name '{}'{}",
name,
if !version.is_latest() { format!(" and version {version}") } else { String::new() }
),
ArgumentsSerializeError { err, .. } => write!(f, "Could not serialize task arguments: {err}"),
StackError { err, .. } => write!(f, "{err}"),
Custom { err, .. } => write!(f, "{err}"),
}
}
}
impl Error for VmError {}
#[derive(Debug)]
pub enum LocalVmError {
Base64DecodeError { name: String, raw: String, err: base64::DecodeError },
Utf8DecodeError { name: String, err: std::string::FromUtf8Error },
JsonDecodeError { name: String, raw: String, err: serde_json::Error },
DataNotAvailable { name: String, loc: String },
IllegalDataPath { name: String, path: PathBuf, err: std::io::Error },
ColonInDataPath { name: String, path: PathBuf },
TransferNotSupported,
}
impl Display for LocalVmError {
#[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use LocalVmError::*;
match self {
Base64DecodeError { name, raw, err } => write!(f, "Could not decode result '{raw}' from task '{name}' as Base64: {err}"),
Utf8DecodeError { name, err } => write!(f, "Could not decode base64-decoded result from task '{name}' as UTF-8: {err}"),
JsonDecodeError { name, raw, err } => write!(f, "Could not decode result '{raw}' from task '{name}' as JSON: {err}"),
DataNotAvailable { name, loc } => write!(f, "Dataset '{name}' is not available on the local location '{loc}'"),
IllegalDataPath { name, path, err } => write!(f, "Invalid path '{}' to dataset '{}': {}", path.display(), name, err),
ColonInDataPath { name, path } => {
write!(f, "Encountered colon (:) in path '{}' to dataset '{}'; provide another path without", path.display(), name)
},
TransferNotSupported => write!(f, "Transfers are not supported in the LocalVm"),
}
}
}
impl Error for LocalVmError {}
#[derive(Debug)]
pub enum DummyVmError {
ExecError { err: VmError },
}
impl Display for DummyVmError {
fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
use DummyVmError::*;
match self {
ExecError { err } => write!(f, "Failed to execute workflow: {err}"),
}
}
}
impl Error for DummyVmError {}