1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! Errors from parsing knots and stitches.
use std::{error::Error, fmt};
use crate::error::{
parse::line::LineError,
utils::{write_line_information, MetaData},
};
#[derive(Debug)]
/// Errors from parsing a single knot from lines.
pub struct KnotError {
/// Information about the line at which the knot starts.
pub knot_meta_data: MetaData,
/// Set of errors that were encountered while parsing the knot.
pub line_errors: Vec<KnotErrorKind>,
}
#[derive(Debug)]
/// Error from parsing a `Knot` or `Stitch` in a story.
pub enum KnotErrorKind {
/// Duplicate knot name was found in a story.
DuplicateKnotName {
/// Name of duplicate stitch.
name: String,
/// Information about the origin of the line of the original knot with this name.
prev_meta_data: MetaData,
},
/// Duplicate stitch name was found in a knot.
DuplicateStitchName {
/// Name of duplicate stitch.
name: String,
/// Name of knot that contains the stitches.
knot_name: String,
/// Information about the origin of the line that caused this error.
meta_data: MetaData,
/// Information about the origin of the line of the original stitch with this name.
prev_meta_data: MetaData,
},
/// Knot has no content.
EmptyKnot,
/// Stitch in knot has no content.
EmptyStitch {
/// Name of stitch, if it is named.
name: Option<String>,
/// Information about the origin of the line that caused this error.
meta_data: MetaData,
},
/// Could not parse a name for knot or stitch.
InvalidName {
/// String that could not be parsed into a name.
line: String,
/// Kind of error.
kind: KnotNameError,
/// Information about the origin of the line that caused this error.
meta_data: MetaData,
},
/// Could not parse a line inside a not.
LineError(LineError),
}
#[derive(Clone, Debug)]
/// Invalid knot or stitch name.
pub enum KnotNameError {
/// Knot name contains an invalid character.
ContainsInvalidCharacter(char),
/// Knot name contains a whitespace character.
ContainsWhitespace,
/// No name existed to read for the knot.
Empty,
/// Name was a reserved keyword.
ReservedKeyword { keyword: String },
}
impl Error for KnotError {}
impl Error for KnotNameError {}
impl Error for KnotErrorKind {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self {
KnotErrorKind::InvalidName { kind, .. } => Some(kind),
KnotErrorKind::LineError(err) => Some(err),
_ => None,
}
}
}
impl_from_error![
KnotErrorKind;
[LineError, LineError]
];
/// Get a string with all errors from parsing a `Knot`.
pub(crate) fn write_knot_error<W: fmt::Write>(buffer: &mut W, error: &KnotError) -> fmt::Result {
for line_error in &error.line_errors {
match line_error {
// All error kinds except these carry their own `MetaData` to use
KnotErrorKind::EmptyKnot => {
write_line_information(buffer, &error.knot_meta_data)?;
}
KnotErrorKind::DuplicateKnotName { .. } => {
write_line_information(buffer, &error.knot_meta_data)?;
}
_ => (),
}
write!(buffer, "{}\n", line_error)?;
}
Ok(())
}
impl fmt::Display for KnotError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} error(s) from parsing knot starting at line {}",
self.line_errors.len(),
self.knot_meta_data.line_index + 1,
)
}
}
impl fmt::Display for KnotErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use KnotErrorKind::*;
match self {
DuplicateKnotName {
name,
prev_meta_data,
} => write!(
f,
"encountered another knot with name '{}' in the story (previous at {})",
name, prev_meta_data
),
DuplicateStitchName {
name,
knot_name,
meta_data,
prev_meta_data,
} => {
write_line_information(f, meta_data)?;
write!(
f,
"encountered another stitch with name '{}' in knot '{}' (previous at {})",
name, knot_name, prev_meta_data
)
}
EmptyKnot => write!(f, "knot has no content"),
EmptyStitch {
name: Some(name),
meta_data,
} => {
write_line_information(f, meta_data)?;
write!(f, "named stitch '{}' has no content", name)
}
EmptyStitch {
name: None,
meta_data,
} => {
write_line_information(f, meta_data)?;
write!(f, "root stitch has no content",)
}
InvalidName {
kind, meta_data, ..
} => {
write_line_information(f, meta_data)?;
write!(f, "could not read knot or stitch name: {}", kind)
}
LineError(err) => write!(f, "{}", err),
}
}
}
impl fmt::Display for KnotNameError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use KnotNameError::*;
match self {
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
),
Empty => write!(f, "no name after knot or stitch marker"),
ReservedKeyword { ref keyword } => write!(
f,
"knot or stitch name may not be reserved keyword '{}'",
keyword.to_lowercase()
),
}
}
}