use std::{error::Error, fmt};
use crate::{
consts::{CHOICE_MARKER, STICKY_CHOICE_MARKER},
story::Address,
};
#[derive(Debug)]
pub enum ParseError {
Empty,
KnotError(KnotError),
LineError(LineParsingError),
InvalidAddress(InvalidAddressError),
}
#[derive(Clone, Debug)]
pub enum InvalidAddressError {
BadFormat { line: String },
UnknownCurrentAddress { address: Address },
UnknownKnot { knot_name: String },
UnknownStitch {
knot_name: String,
stitch_name: String,
},
ValidatedWithUnvalidatedAddress {
needle: String,
current_address: Address,
},
}
#[derive(Debug)]
pub enum KnotError {
Empty,
InvalidName { line: String, kind: KnotNameError },
LineError(LineParsingError),
}
#[derive(Clone, Debug)]
pub struct LineParsingError {
pub line: String,
pub kind: LineErrorKind,
}
impl LineParsingError {
pub fn from_kind<T: Into<String>>(line: T, kind: LineErrorKind) -> Self {
LineParsingError {
line: line.into(),
kind,
}
}
}
impl Error for ParseError {}
impl Error for InvalidAddressError {}
impl Error for KnotError {}
impl Error for LineParsingError {}
impl_from_error![
ParseError;
[InvalidAddress, InvalidAddressError],
[KnotError, KnotError],
[LineError, LineParsingError]
];
impl_from_error![
KnotError;
[LineError, LineParsingError]
];
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ParseError::*;
match self {
Empty => write!(f, "Tried to read from an empty file or string"),
InvalidAddress(err) => write!(f, "{}", err),
KnotError(err) => write!(f, "{}", err),
LineError(err) => write!(f, "{}", err),
}
}
}
impl fmt::Display for InvalidAddressError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use InvalidAddressError::*;
write!(f, "Encountered an invalid address: ")?;
match self {
BadFormat { line } => write!(f, "address was incorrectly formatted ('{}')", line),
UnknownCurrentAddress { address } => write!(
f,
"during validation an address '{:?}' that is not in the system was used as
a current address",
address
),
UnknownKnot { knot_name } => {
write!(f, "no knot with name '{}' in the story", knot_name)
}
UnknownStitch {
knot_name,
stitch_name,
} => write!(
f,
"no stitch with name '{}' in knot '{}'",
stitch_name, knot_name
),
ValidatedWithUnvalidatedAddress {
needle,
current_address,
} => write!(
f,
"during validating the raw address '{}' an unvalidated address '{:?}' was used",
needle, current_address
),
}
}
}
impl fmt::Display for KnotError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use KnotError::Empty as EmptyKnot;
use KnotError::*;
use KnotNameError::Empty as EmptyKnotName;
use KnotNameError::*;
write!(f, "Could not parse a knot: ")?;
match self {
EmptyKnot => write!(f, "knot has no name"),
InvalidName { line, kind } => {
write!(f, "could not read knot name: ")?;
match kind {
ContainsWhitespace => {
write!(
f,
"name contains whitespace characters: only alphanumeric \
and underline characters are allowed"
)?;
}
ContainsInvalidCharacter(c) => {
write!(
f,
"name contains invalid character '{}': only alphanumeric \
and underline characters are allowed",
c
)?;
}
EmptyKnotName => {
write!(f, "knot marker without a knot name was found")?;
}
NoNamePresent => {
write!(f, "knot or stitch has no name where one is expected")?;
}
}
write!(f, " (line: {})", line)
}
LineError(err) => write!(f, "{}", err),
}
}
}
impl fmt::Display for LineParsingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LineErrorKind::*;
match self.kind {
EmptyDivert => write!(f, "Encountered a divert statement with no address",),
ExpectedEndOfLine { ref tail } => write!(
f,
"Expected no more content after a divert statement address but found '{}'",
tail
),
ExpectedLogic { ref line } => write!(
f,
"Could not parse a conditional logic statement '{}'",
line
),
ExpectedNumber { ref value } => write!(f, "Could not parse a number from '{}'", value),
FoundTunnel => write!(
f,
"Found multiple divert markers in a line. In the `Ink` language this indicates \
a `tunnel` for the story to pass through, but these are not yet implemented \
in `inkling`."
),
InvalidAddress { ref address } => write!(
f,
"Found an invalid address to knot, stitch or variable '{}': \
contains invalid characters",
address
),
StickyAndNonSticky => write!(
f,
"Encountered a line which has both non-sticky ('{}') and sticky ('{}') \
choice markers. This is not allowed.",
CHOICE_MARKER, STICKY_CHOICE_MARKER
),
UnmatchedBraces => write!(f, "Line has unmatched curly '{{}}' braces"),
UnmatchedBrackets => write!(f, "Choice line has unmatched square '[]' brackets"),
}?;
write!(f, " (line: {}", &self.line)
}
}
#[derive(Clone, Debug)]
pub enum KnotNameError {
ContainsInvalidCharacter(char),
ContainsWhitespace,
Empty,
NoNamePresent,
}
#[derive(Clone, Debug)]
pub enum LineErrorKind {
EmptyDivert,
ExpectedEndOfLine { tail: String },
ExpectedLogic { line: String },
ExpectedNumber { value: String },
FoundTunnel,
InvalidAddress { address: String },
StickyAndNonSticky,
UnmatchedBraces,
UnmatchedBrackets,
}