1use std::fmt;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct Span {
8 pub start: usize,
9 pub end: usize,
10}
11
12impl Span {
13 pub fn new(start: usize, end: usize) -> Self {
14 Span { start, end }
15 }
16
17 pub fn merge(a: Span, b: Span) -> Span {
18 Span { start: a.start.min(b.start), end: a.end.max(b.end) }
19 }
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum ErrorKind {
26 Parse,
27 Rank,
28 Length,
29 Shape,
30 Domain,
31 Type,
32 Value,
33 NotYet,
35 Language,
37 Limit,
39 Internal,
40}
41
42impl ErrorKind {
43 pub fn label(self) -> &'static str {
44 match self {
45 ErrorKind::Parse => "parse error",
46 ErrorKind::Rank => "rank error",
47 ErrorKind::Length => "length error",
48 ErrorKind::Shape => "shape error",
49 ErrorKind::Domain => "domain error",
50 ErrorKind::Type => "type error",
51 ErrorKind::Value => "value error",
52 ErrorKind::NotYet => "not supported yet",
53 ErrorKind::Language => "not in the language",
54 ErrorKind::Limit => "limit error",
55 ErrorKind::Internal => "internal error",
56 }
57 }
58}
59
60#[derive(Clone, Debug)]
61pub struct Error {
62 pub kind: ErrorKind,
63 pub msg: String,
64 pub span: Option<Span>,
65 pub notes: Vec<String>,
66}
67
68pub type Result<T> = std::result::Result<T, Error>;
69
70impl Error {
71 pub fn new(kind: ErrorKind, msg: impl Into<String>, span: Option<Span>) -> Self {
72 Error { kind, msg: msg.into(), span, notes: Vec::new() }
73 }
74
75 pub fn parse(msg: impl Into<String>, span: Span) -> Self {
76 Self::new(ErrorKind::Parse, msg, Some(span))
77 }
78
79 pub fn not_yet(what: impl fmt::Display, span: Span) -> Self {
80 Self::new(ErrorKind::NotYet, format!("{what} is not supported yet"), Some(span))
81 }
82
83 pub fn language(msg: impl Into<String>, span: Span) -> Self {
84 Self::new(ErrorKind::Language, msg, Some(span))
85 }
86
87 pub fn domain(msg: impl Into<String>, span: Span) -> Self {
88 Self::new(ErrorKind::Domain, msg, Some(span))
89 }
90
91 pub fn internal(msg: impl Into<String>) -> Self {
92 Self::new(ErrorKind::Internal, msg, None)
93 }
94
95 pub fn note(mut self, note: impl Into<String>) -> Self {
96 self.notes.push(note.into());
97 self
98 }
99
100 pub fn render(&self, src: &str) -> String {
102 let mut out = format!("{}: {}", self.kind.label(), self.msg);
103 if let Some(span) = self.span {
104 if let Some((line, col_start, col_len)) = locate(src, span) {
105 out.push_str("\n ");
106 out.push_str(line);
107 out.push_str("\n ");
108 out.push_str(&" ".repeat(col_start));
109 out.push_str(&"^".repeat(col_len.max(1)));
110 }
111 }
112 for n in &self.notes {
113 out.push_str("\nnote: ");
114 out.push_str(n);
115 }
116 out
117 }
118}
119
120impl fmt::Display for Error {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 write!(f, "{}: {}", self.kind.label(), self.msg)?;
123 for n in &self.notes {
124 write!(f, "\nnote: {n}")?;
125 }
126 Ok(())
127 }
128}
129
130impl std::error::Error for Error {}
131
132fn locate(src: &str, span: Span) -> Option<(&str, usize, usize)> {
135 if span.start > src.len() || !src.is_char_boundary(span.start) {
138 return None;
139 }
140 let line_start = src[..span.start].rfind('\n').map(|i| i + 1).unwrap_or(0);
141 let line_end = src[span.start..].find('\n').map(|i| span.start + i).unwrap_or(src.len());
142 let line = &src[line_start..line_end];
143 let col_start = src[line_start..span.start].chars().count();
144 let span_end = span.end.min(line_end).max(span.start);
145 let col_len = if src.is_char_boundary(span_end) {
146 src[span.start..span_end].chars().count()
147 } else {
148 1
149 };
150 Some((line, col_start, col_len))
151}