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 Sandbox,
40 Limit,
42 Internal,
43}
44
45impl ErrorKind {
46 pub fn label(self) -> &'static str {
47 match self {
48 ErrorKind::Parse => "parse error",
49 ErrorKind::Rank => "rank error",
50 ErrorKind::Length => "length error",
51 ErrorKind::Shape => "shape error",
52 ErrorKind::Domain => "domain error",
53 ErrorKind::Type => "type error",
54 ErrorKind::Value => "value error",
55 ErrorKind::NotYet => "not supported yet",
56 ErrorKind::Language => "not in the language",
57 ErrorKind::Sandbox => "closed by the sandbox",
58 ErrorKind::Limit => "limit error",
59 ErrorKind::Internal => "internal error",
60 }
61 }
62}
63
64#[derive(Clone, Debug)]
65pub struct Error {
66 pub kind: ErrorKind,
67 pub msg: String,
68 pub span: Option<Span>,
69 pub notes: Vec<String>,
70}
71
72pub type Result<T> = std::result::Result<T, Error>;
73
74impl Error {
75 pub fn new(kind: ErrorKind, msg: impl Into<String>, span: Option<Span>) -> Self {
76 Error { kind, msg: msg.into(), span, notes: Vec::new() }
77 }
78
79 pub fn parse(msg: impl Into<String>, span: Span) -> Self {
80 Self::new(ErrorKind::Parse, msg, Some(span))
81 }
82
83 pub fn not_yet(what: impl fmt::Display, span: Span) -> Self {
84 Self::new(ErrorKind::NotYet, format!("{what} is not supported yet"), Some(span))
85 }
86
87 pub fn language(msg: impl Into<String>, span: Span) -> Self {
88 Self::new(ErrorKind::Language, msg, Some(span))
89 }
90
91 pub fn sandbox(msg: impl Into<String>, span: Span) -> Self {
95 Self::new(ErrorKind::Sandbox, msg, Some(span))
96 }
97
98 pub fn domain(msg: impl Into<String>, span: Span) -> Self {
99 Self::new(ErrorKind::Domain, msg, Some(span))
100 }
101
102 pub fn internal(msg: impl Into<String>) -> Self {
103 Self::new(ErrorKind::Internal, msg, None)
104 }
105
106 pub fn note(mut self, note: impl Into<String>) -> Self {
107 self.notes.push(note.into());
108 self
109 }
110
111 pub fn render(&self, src: &str) -> String {
113 let mut out = format!("{}: {}", self.kind.label(), self.msg);
114 if let Some(span) = self.span && let Some((line, col_start, col_len)) = locate(src, span) {
115 out.push_str("\n ");
116 out.push_str(line);
117 out.push_str("\n ");
118 out.push_str(&" ".repeat(col_start));
119 out.push_str(&"^".repeat(col_len.max(1)));
120 }
121 for n in &self.notes {
122 out.push_str("\nnote: ");
123 out.push_str(n);
124 }
125 out
126 }
127}
128
129impl fmt::Display for Error {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(f, "{}: {}", self.kind.label(), self.msg)?;
132 for n in &self.notes {
133 write!(f, "\nnote: {n}")?;
134 }
135 Ok(())
136 }
137}
138
139impl std::error::Error for Error {}
140
141fn locate(src: &str, span: Span) -> Option<(&str, usize, usize)> {
144 if span.start > src.len() || !src.is_char_boundary(span.start) {
147 return None;
148 }
149 let line_start = src[..span.start].rfind('\n').map(|i| i + 1).unwrap_or(0);
150 let line_end = src[span.start..].find('\n').map(|i| span.start + i).unwrap_or(src.len());
151 let line = &src[line_start..line_end];
152 let col_start = src[line_start..span.start].chars().count();
153 let span_end = span.end.min(line_end).max(span.start);
154 let col_len = if src.is_char_boundary(span_end) {
155 src[span.start..span_end].chars().count()
156 } else {
157 1
158 };
159 Some((line, col_start, col_len))
160}