rssn_advanced/parser/
error.rs1use core::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Span {
13 pub offset: usize,
15 pub len: usize,
17 pub line: u32,
19 pub col: u32,
21}
22
23impl Span {
24 #[must_use]
27 pub fn from_offset(source: &str, offset: usize, len: usize) -> Self {
28 let clamped = offset.min(source.len());
29 let prefix = &source.as_bytes()[..clamped];
30 let mut line: u32 = 1;
31 let mut last_newline: usize = 0;
32 for (i, &b) in prefix.iter().enumerate() {
33 if b == b'\n' {
34 line = line.saturating_add(1);
35 last_newline = i + 1;
36 }
37 }
38 #[allow(clippy::cast_possible_truncation)]
40 let col = (clamped - last_newline) as u32 + 1;
41 Self {
42 offset,
43 len,
44 line,
45 col,
46 }
47 }
48}
49
50impl fmt::Display for Span {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "{}:{}", self.line, self.col)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct ParseError {
59 pub message: String,
61 pub span: Span,
63}
64
65impl fmt::Display for ParseError {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 write!(f, "Parse error at {}: {}", self.span, self.message)
68 }
69}
70
71impl std::error::Error for ParseError {}
72
73#[doc(hidden)]
75#[cold]
76#[track_caller]
77#[inline(never)]
78#[must_use]
79pub fn cold_parse_error_unexpected_eof(span: Span) -> ParseError {
80 ParseError {
81 message: "Unexpected end of input; expected closing ')'".to_owned(),
82 span,
83 }
84}
85
86#[doc(hidden)]
88#[cold]
89#[track_caller]
90#[inline(never)]
91#[must_use]
92pub fn cold_parse_error_unexpected_token(span: Span, bad: char) -> ParseError {
93 ParseError {
94 message: format!("Unexpected character {bad:?}; expected a number, variable, or '('"),
95 span,
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn span_at_start_of_input() {
105 let s = Span::from_offset("hello", 0, 1);
106 assert_eq!(s.line, 1);
107 assert_eq!(s.col, 1);
108 }
109
110 #[test]
111 fn span_after_newline() {
112 let src = "a + b\nc + d\nbad";
113 let off = src.find("bad").expect("find");
115 let s = Span::from_offset(src, off, 3);
116 assert_eq!(s.line, 3);
117 assert_eq!(s.col, 1);
118 }
119
120 #[test]
121 fn span_offset_past_end_clamps() {
122 let s = Span::from_offset("xy", 999, 1);
123 assert_eq!(s.line, 1);
124 assert_eq!(s.col, 3); }
126}