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
//! The error for when reading or building fails
use std::fmt;
/// The reason for the failure, and where it is in the original string
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
/// A description of what happened
pub message: String,
/// The character position counted from the start of the original string (starting at 0)
pub position: usize,
}
impl Error {
pub fn new(message: impl Into<String>, position: usize) -> Error {
Error {message: message.into(), position}
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "{} ({} 文字目)", self.message, self.position + 1)
}
}
impl std::error::Error for Error {}
/// Converts a character position to a line and a column. Both count from 1
pub fn place(source: &[char], position: usize) -> (usize, usize) {
let mut line = 1;
let mut column = 1;
for character in source.iter().take(position) {
if *character == '\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
(line, column)
}
/// Restates the position of the error as a line and a column
pub fn describe(source: &[char], error: &Error) -> String {
let (line, column) = place(source, error.position);
format!("{line} 行 {column} 桁: {}", error.message)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_position_becomes_a_line_and_a_column() {
let source: Vec<char> = "abc\ndef".chars().collect();
assert_eq!(place(&source, 0), (1, 1));
assert_eq!(place(&source, 4), (2, 1));
assert_eq!(place(&source, 6), (2, 3));
}
}