Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 75 additions & 4 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,7 @@ where
.split(|c| *c == b'\n')
.nth(line_idx)
.expect("valid line number");
let (content_left, _) = content.split_at(col_idx);

writeln!(f, "parse error at line {line_num}, column {col_num}")?;
// |
Expand All @@ -1383,8 +1384,16 @@ where
write!(f, " ")?;
}
write!(f, " | ")?;
for _ in 0..col_idx {
write!(f, " ")?;
// Add the same amount of tabs as in the content line up to the `col_num`,
// so the `^` aligns correctly.
for byte in content_left {
// Tabs must be at the same position as in the content to match correctly,
// as they are variable width...
if *byte == b'\t' {
write!(f, "\t")?;
} else {
write!(f, " ")?;
}
}
// The span will be empty at eof, so we need to make sure we always print at least
// one `^`
Expand All @@ -1395,9 +1404,19 @@ where
writeln!(f)?;
} else {
let content = input;
let (content_left, _) = content.split_at(span_start);

writeln!(f, "{}", String::from_utf8_lossy(content))?;
for _ in 0..span_start {
write!(f, " ")?;
// Add the same amount of tabs as in the content up to the `span_start`,
// so the `^` aligns correctly.
for byte in content_left {
// Tabs must be at the same position as in the content to match correctly,
// as they are variable width...
if *byte == b'\t' {
write!(f, "\t")?;
} else {
write!(f, " ")?;
}
}
// The span will be empty at eof, so we need to make sure we always print at least
// one `^`
Expand Down Expand Up @@ -1509,6 +1528,58 @@ mod test_parse_error {
failed to parse starting at: Z123";
assert_eq!(error.to_string(), expected);
}

#[test]
fn single_line_tabs() {
let mut input = "\t0xA123\t0xZ123\t";
let start = input.checkpoint();
for _ in 0..10 {
let _ = input.next_token().unwrap();
}
let inner = InputError::at(input);
let error = ParseError::new(input, start, inner);
let expected = "\
\t0xA123\t0xZ123\t
\t \t ^
failed to parse starting at: Z123\t";
assert_eq!(error.to_string(), expected);
}

#[test]
fn multiple_lines() {
let mut input = "0xA123\n0xZ123";
let start = input.checkpoint();
for _ in 0..9 {
let _ = input.next_token().unwrap();
}
let inner = InputError::at(input);
let error = ParseError::new(input, start, inner);
let expected = "\
parse error at line 2, column 3
|
2 | 0xZ123
| ^
failed to parse starting at: Z123";
assert_eq!(error.to_string(), expected);
}

#[test]
fn multiple_lines_tabs() {
let mut input = "\t0xA123\t0xA123\t\n\t0xA123\t0xZ123\t";
let start = input.checkpoint();
for _ in 0..26 {
let _ = input.next_token().unwrap();
}
let inner = InputError::at(input);
let error = ParseError::new(input, start, inner);
let expected = "\
parse error at line 2, column 11
|
2 | \t0xA123\t0xZ123\t
| \t \t ^
failed to parse starting at: Z123\t";
assert_eq!(error.to_string(), expected);
}
}

#[cfg(test)]
Expand Down