Skip to content

Commit 1d0e670

Browse files
wip: Pretty display error info
1 parent c43fd58 commit 1d0e670

File tree

2 files changed

+76
-18
lines changed

2 files changed

+76
-18
lines changed

keyvalues-parser/src/error.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,24 @@ impl std::error::Error for RenderError {}
3535
/// An alias for `Result` with an [`Error`]
3636
pub type ParseResult<T> = std::result::Result<T, ParseError>;
3737

38+
#[derive(Clone, Debug)]
39+
pub struct ParseError {
40+
pub kind: ParseErrorKind,
41+
pub span: Span,
42+
line: String,
43+
}
44+
45+
impl fmt::Display for ParseError {
46+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47+
todo!();
48+
}
49+
}
50+
51+
impl std::error::Error for ParseError {}
52+
3853
/// Errors encountered while parsing VDF text
3954
#[derive(Clone, Debug, PartialEq, Eq)]
40-
pub enum ParseError {
55+
pub enum ParseErrorKind {
4156
LingeringBytes,
4257
InvalidMacro,
4358
MissingTopLevelPair,
@@ -48,7 +63,7 @@ pub enum ParseError {
4863
InvalidComment,
4964
}
5065

51-
impl fmt::Display for ParseError {
66+
impl fmt::Display for ParseErrorKind {
5267
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5368
match self {
5469
Self::LingeringBytes => f.write_str("Bytes remained after parsed pair"),
@@ -65,4 +80,20 @@ impl fmt::Display for ParseError {
6580
}
6681
}
6782

68-
impl std::error::Error for ParseError {}
83+
#[derive(Clone, Debug)]
84+
pub enum Span {
85+
Single(usize),
86+
Run { index: usize, len: usize },
87+
}
88+
89+
impl Span {
90+
pub(crate) fn run_with_len(index: usize, len: usize) -> Self {
91+
Span::Run { index, len }
92+
}
93+
}
94+
95+
impl From<usize> for Span {
96+
fn from(index: usize) -> Self {
97+
Self::Single(index)
98+
}
99+
}

keyvalues-parser/src/text/parse.rs

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{borrow::Cow, str::Chars};
22

33
use crate::{
4-
error::{ParseError, ParseResult},
4+
error::{ParseError, ParseErrorKind, ParseResult, Span},
55
Key, Obj, PartialVdf, Value, Vdf,
66
};
77

@@ -51,7 +51,7 @@ pub fn parse_(s: &str, escape_chars: bool) -> ParseResult<PartialVdf> {
5151
eat_comments_whitespace_and_newlines(&mut chars)?;
5252

5353
if chars.peek().is_some() {
54-
Err(ParseError::LingeringBytes)
54+
Err(chars.err(ParseErrorKind::LingeringBytes, 0.into()))
5555
} else {
5656
Ok(PartialVdf { bases, key, value })
5757
}
@@ -79,7 +79,7 @@ fn parse_maybe_macro<'text>(
7979
}
8080

8181
if !eat_whitespace(chars) {
82-
return Err(ParseError::InvalidMacro);
82+
return Err(chars.err(ParseErrorKind::InvalidMacro, 0.into()));
8383
}
8484

8585
let macro_ = parse_quoted_raw_or_unquoted_string(chars)?;
@@ -90,7 +90,7 @@ fn parse_maybe_macro<'text>(
9090
if eat_newlines(chars) {
9191
Ok(Some(()))
9292
} else {
93-
Err(ParseError::MissingTopLevelPair)
93+
Err(chars.err(ParseErrorKind::MissingTopLevelPair, 0.into()))
9494
}
9595
}
9696

@@ -108,17 +108,24 @@ fn parse_quoted_raw_or_unquoted_string<'text>(
108108
fn parse_quoted_raw_string<'text>(chars: &mut CharIter<'text>) -> ParseResult<&'text str> {
109109
assert!(chars.ensure_next('"'));
110110
let start_idx = chars.index();
111-
while chars.next().ok_or(ParseError::EoiParsingString)? != '"' {}
111+
while chars
112+
.next()
113+
.ok_or_else(|| chars.err(ParseErrorKind::EoiParsingString, 0.into()))?
114+
!= '"'
115+
{}
112116
let end_idx = chars.index() - '"'.len_utf8();
113117
Ok(&chars.original_str()[start_idx..end_idx])
114118
}
115119

116120
fn parse_unquoted_string<'text>(chars: &mut CharIter<'text>) -> ParseResult<&'text str> {
117121
let start_idx = chars.index();
118122

119-
match chars.next().ok_or(ParseError::EoiParsingString)? {
123+
match chars
124+
.next()
125+
.ok_or_else(|| chars.err(ParseErrorKind::EoiParsingString, 0.into()))?
126+
{
120127
'"' | '{' | '}' | ' ' | '\t' | '\r' | '\n' => {
121-
return Err(ParseError::ExpectedUnquotedString)
128+
return Err(chars.err(ParseErrorKind::ExpectedUnquotedString, 0.into()))
122129
}
123130
_ => {}
124131
}
@@ -162,7 +169,9 @@ fn parse_quoted_string<'text>(
162169

163170
let start_idx = chars.index();
164171
loop {
165-
let peeked = chars.peek().ok_or(ParseError::EoiParsingString)?;
172+
let peeked = chars
173+
.peek()
174+
.ok_or_else(|| chars.err(ParseErrorKind::EoiParsingString, 0.into()))?;
166175
// We only care about potential escaped characters if `escape_chars` is set. Otherwise we
167176
// only break on " for a quoted string
168177
if peeked == '"' || (peeked == '\\' && escape_chars) {
@@ -175,23 +184,32 @@ fn parse_quoted_string<'text>(
175184
let text_chunk = &chars.original_str()[start_idx..end_idx];
176185
// If our string contains escaped characters then it has to be a `Cow::Owned` otherwise it can
177186
// be `Cow::Borrowed`
178-
let key = if chars.peek().ok_or(ParseError::EoiParsingString)? == '"' {
187+
let key = if chars
188+
.peek()
189+
.ok_or_else(|| chars.err(ParseErrorKind::EoiParsingString, 0.into()))?
190+
== '"'
191+
{
179192
assert!(chars.ensure_next('"'));
180193
Cow::Borrowed(text_chunk)
181194
} else {
182195
assert!(chars.peek().unwrap() == '\\');
183196
let mut escaped = text_chunk.to_owned();
184197
loop {
185-
let ch = chars.next().ok_or(ParseError::InvalidEscapedCharacter)?;
198+
let ch = chars
199+
.next()
200+
.ok_or_else(|| chars.err(ParseErrorKind::InvalidEscapedCharacter, 0.into()))?;
186201
match ch {
187202
'"' => break Cow::Owned(escaped),
188-
'\\' => match chars.next().ok_or(ParseError::InvalidEscapedCharacter)? {
203+
'\\' => match chars
204+
.next()
205+
.ok_or_else(|| chars.err(ParseErrorKind::InvalidEscapedCharacter, 0.into()))?
206+
{
189207
'n' => escaped.push('\n'),
190208
'r' => escaped.push('\r'),
191209
't' => escaped.push('\t'),
192210
'\\' => escaped.push('\\'),
193211
'\"' => escaped.push('\"'),
194-
_ => return Err(ParseError::InvalidEscapedCharacter),
212+
_ => return Err(chars.err(ParseErrorKind::InvalidEscapedCharacter, 0.into())),
195213
},
196214
regular => escaped.push(regular),
197215
}
@@ -224,7 +242,11 @@ fn parse_obj<'text>(chars: &mut CharIter<'text>, escape_chars: bool) -> ParseRes
224242

225243
let mut obj = Obj::new();
226244

227-
while chars.peek().ok_or(ParseError::EoiParsingMap)? != '}' {
245+
while chars
246+
.peek()
247+
.ok_or_else(|| chars.err(ParseErrorKind::EoiParsingMap, 0.into()))?
248+
!= '}'
249+
{
228250
let Vdf { key, value } = parse_pair(chars, escape_chars)?;
229251
obj.0.entry(key).or_default().push(value);
230252

@@ -273,13 +295,13 @@ fn eat_comments(chars: &mut CharIter<'_>) -> ParseResult<bool> {
273295
chars.unwrap_next();
274296
match chars.next() {
275297
Some('\n') => break,
276-
_ => return Err(ParseError::InvalidComment),
298+
_ => return Err(chars.err(ParseErrorKind::InvalidComment, 0.into())),
277299
}
278300
}
279301
None | Some('\n') => break,
280302
// Various control characters
281303
Some('\u{00}'..='\u{08}' | '\u{0A}'..='\u{1F}' | '\u{7F}') => {
282-
return Err(ParseError::InvalidComment);
304+
return Err(chars.err(ParseErrorKind::InvalidComment, 0.into()));
283305
}
284306
_ => _ = chars.unwrap_next(),
285307
}
@@ -403,6 +425,11 @@ impl<'text> CharIter<'text> {
403425
}
404426
arr
405427
}
428+
429+
#[must_use]
430+
fn err(&self, kind: ParseErrorKind, span: Span) -> ParseError {
431+
todo!();
432+
}
406433
}
407434

408435
impl Iterator for CharIter<'_> {

0 commit comments

Comments
 (0)