use std::{error::Error, fmt};
use crate::{
follow::ChoiceInfo,
knot::{Address, AddressKind},
line::Variable,
node::Stack,
story::Choice,
};
use std::cmp::Ordering;
#[derive(Clone, Debug)]
pub enum InklingError {
Internal(InternalError),
InvalidAddress {
knot: String,
stitch: Option<String>,
},
InvalidChoice {
selection: usize,
presented_choices: Vec<Choice>,
},
InvalidVariable { name: String },
InvalidVariableComparison {
from: Variable,
to: Variable,
comparison: Ordering,
},
MadeChoiceWithoutChoice,
OutOfChoices { address: Address },
OutOfContent,
PrintInvalidVariable { name: String, value: Variable },
ResumeBeforeStart,
StartOnStoryInProgress,
VariableTypeChange { from: Variable, to: Variable },
}
#[derive(Clone, Debug)]
pub enum InternalError {
BadKnotStack(StackError),
CouldNotProcess(ProcessError),
IncorrectChoiceIndex {
selection: usize,
available_choices: Vec<ChoiceInfo>,
stack_index: usize,
stack: Stack,
},
IncorrectNodeStack(IncorrectNodeStackError),
UseOfVariableAsLocation { name: String },
UseOfUnvalidatedAddress { address: Address },
}
impl Error for InklingError {}
impl Error for InternalError {}
macro_rules! impl_from_error {
($for_type:ident; $([$variant:ident, $from_type:ident]),+) => {
$(
impl From<$from_type> for $for_type {
fn from(err: $from_type) -> Self {
$for_type::$variant(err)
}
}
)*
}
}
impl_from_error![
InklingError;
[Internal, InternalError]
];
impl_from_error![
InternalError;
[BadKnotStack, StackError],
[CouldNotProcess, ProcessError],
[IncorrectNodeStack, IncorrectNodeStackError]
];
impl From<StackError> for InklingError {
fn from(err: StackError) -> Self {
InklingError::Internal(InternalError::BadKnotStack(err))
}
}
impl fmt::Display for InklingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use InklingError::*;
match self {
Internal(err) => write!(f, "INTERNAL ERROR: {}", err),
InvalidAddress { knot, stitch } => match stitch {
Some(stitch_name) => write!(
f,
"Invalid address: knot '{}' does not contain a stitch named '{}'",
knot, stitch_name
),
None => write!(
f,
"Invalid address: story does not contain a knot name '{}'",
knot
),
},
InvalidChoice {
selection,
presented_choices,
} => write!(
f,
"Invalid selection of choice: selection was {} but number of choices was {} \
(maximum selection index is {})",
selection,
presented_choices.len(),
presented_choices.len() - 1
),
InvalidVariable { name } => write!(
f,
"Invalid variable: no variable with name '{}' exists in the story",
name
),
InvalidVariableComparison {
from,
to,
comparison,
} => {
let operator = match comparison {
Ordering::Equal => "==",
Ordering::Less => ">",
Ordering::Greater => "<",
};
write!(
f,
"Cannot compare variable of type '{}' to '{}' using the '{op}' operator \
(comparison was: '{:?} {op} {:?}')",
from.variant_string(),
to.variant_string(),
from,
to,
op = operator
)
}
MadeChoiceWithoutChoice => write!(
f,
"Tried to make a choice, but no choice is currently active. Call `resume` \
and assert that a branching choice is returned before calling this again."
),
OutOfChoices {
address: Address::Validated(AddressKind::Location { knot, stitch }),
} => write!(
f,
"Story reached a branching choice with no available choices to present \
or default choices to fall back on (knot: {}, stitch: {})",
knot, stitch
),
OutOfChoices { address } => write!(
f,
"Internal error: Tried to use a non-validated or non-location `Address` ('{:?}') \
when following a story",
address
),
OutOfContent => write!(f, "Story ran out of content before an end was reached"),
PrintInvalidVariable { name, value } => write!(
f,
"Cannot print variable '{}' which has value '{:?}': invalid type",
name, value
),
ResumeBeforeStart => write!(f, "Tried to resume a story that has not yet been started"),
StartOnStoryInProgress => {
write!(f, "Called `start` on a story that is already in progress")
}
VariableTypeChange { from, to } => write!(
f,
"Cannot assign a value of type '{}' to a variable of type '{}' \
(variables cannot change type)",
to.variant_string(),
from.variant_string()
),
}
}
}
impl fmt::Display for InternalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use IncorrectNodeStackError::*;
use InternalError::*;
use ProcessErrorKind::*;
use StackError::*;
match self {
BadKnotStack(err) => match err {
BadAddress {
address: Address::Validated(AddressKind::Location { knot, stitch }),
} => write!(
f,
"The currently set knot address (knot: {}, stitch: {}) does not \
actually represent a knot in the story",
knot, stitch
),
BadAddress { address } => write!(
f,
"Tried to used a non-validated or non-location `Address` ('{:?}') in \
a function",
address
),
NoLastChoices => write!(
f,
"Tried to follow with a choice but the last set of presented choices has \
not been saved"
),
NoRootKnot { knot_name } => write!(
f,
"After reading a set of knots, the root knot with name {} \
does not exist in the set",
knot_name
),
NoStack => write!(
f,
"There is no currently set knot or address to follow the story from"
),
},
CouldNotProcess(ProcessError { kind }) => match kind {
InvalidAlternativeIndex => write!(
f,
"When processing an alternative, an invalid index was used to pick an item"
),
InklingError(err) => write!(f, "{}", err),
},
IncorrectChoiceIndex {
selection,
ref available_choices,
stack_index,
ref stack,
} => write!(
f,
"Tried to resume after a choice was made but the chosen index does not exist \
in the set of choices. Somehow a faulty set of choices was created from this \
branch point and returned upwards, the stack is wrong, or the wrong set of \
choices was used elsewhere in the preparation of the choice list. \
Selection index: {}, number of branches: {} \
(node level: {}, stack: {:?})",
selection,
available_choices.len(),
stack_index,
stack
),
IncorrectNodeStack(err) => match err {
EmptyStack => write!(f, "Tried to advance through a knot with an empty stack"),
ExpectedBranchingPoint { stack_index, stack } => {
let item_number = stack[*stack_index];
write!(
f,
"While resuming a follow the stack found a regular line where \
it expected a branch point to nest deeper into. \
The stack has been corrupted. \
(stack level: {}, item number: {}, stack: {:?}",
stack_index, item_number, stack
)
}
MissingBranchIndex { stack_index, stack } => write!(
f,
"While resuming a follow the stack did not contain an index to \
select a branch with from a set of choices. The stack has been \
corrupted.
(stack level: {}, attempted index: {}, stack: {:?}",
stack_index,
stack_index + 1,
stack
),
OutOfBounds {
stack_index,
stack,
num_items,
} => write!(
f,
"Current stack has invalid index {} at node level {}: size of set is {} \
(stack: {:?})",
stack[*stack_index], stack_index, num_items, stack
),
},
UseOfVariableAsLocation { name } => write!(
f,
"Tried to use variable '{}' as a location in the story",
name
),
UseOfUnvalidatedAddress { address } => {
write!(f, "Tried to use unvalidated address '{:?}'", address)
}
}
}
}
#[derive(Clone, Debug)]
pub struct ProcessError {
pub kind: ProcessErrorKind,
}
impl From<InklingError> for ProcessError {
fn from(err: InklingError) -> Self {
ProcessError {
kind: ProcessErrorKind::InklingError(Box::new(err)),
}
}
}
#[derive(Clone, Debug)]
pub enum ProcessErrorKind {
InvalidAlternativeIndex,
InklingError(Box<InklingError>),
}
#[derive(Clone, Debug)]
pub enum StackError {
NoStack,
BadAddress { address: Address },
NoLastChoices,
NoRootKnot { knot_name: String },
}
#[derive(Clone, Debug)]
pub enum IncorrectNodeStackError {
EmptyStack,
ExpectedBranchingPoint { stack_index: usize, stack: Stack },
MissingBranchIndex { stack_index: usize, stack: Stack },
OutOfBounds {
stack_index: usize,
stack: Stack,
num_items: usize,
},
}