1use std::fmt;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub struct ByteSpan {
8 pub start: usize,
9 pub end: usize,
10}
11
12impl ByteSpan {
13 pub const fn new(start: usize, end: usize) -> ByteSpan {
14 ByteSpan { start, end }
15 }
16
17 pub const fn at(offset: usize) -> ByteSpan {
18 ByteSpan {
19 start: offset,
20 end: offset,
21 }
22 }
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum DiagCode {
27 UnexpectedChar,
28 InvalidNumber,
29 UnexpectedToken,
30 UnexpectedEof,
31 UnclosedParen,
32 UnbalancedParen,
33 EmptyExpression,
34 RecursionLimit,
35 BadArity,
36 MultilineBody,
37 BadScore,
38 BadLane,
39 BadScale,
40 BadTempo,
41 DanglingRef,
42 RefAboveRoot,
43 RefCycle,
44 Io,
45 BadArrangementArg,
46 BadDefault,
47 BareTime,
48 NotNodeContent,
49 NonIntegerIndex,
50 UnwrittenSeries,
51 MisplacedPath,
52 MissingRecording,
53}
54
55impl DiagCode {
56 #[cfg(test)]
57 const ALL: &'static [DiagCode] = &[
58 DiagCode::UnexpectedChar,
59 DiagCode::InvalidNumber,
60 DiagCode::UnexpectedToken,
61 DiagCode::UnexpectedEof,
62 DiagCode::UnclosedParen,
63 DiagCode::UnbalancedParen,
64 DiagCode::EmptyExpression,
65 DiagCode::RecursionLimit,
66 DiagCode::BadArity,
67 DiagCode::MultilineBody,
68 DiagCode::BadScore,
69 DiagCode::BadLane,
70 DiagCode::BadScale,
71 DiagCode::BadTempo,
72 DiagCode::DanglingRef,
73 DiagCode::RefAboveRoot,
74 DiagCode::RefCycle,
75 DiagCode::Io,
76 DiagCode::BadArrangementArg,
77 DiagCode::BadDefault,
78 DiagCode::BareTime,
79 DiagCode::NotNodeContent,
80 DiagCode::NonIntegerIndex,
81 DiagCode::UnwrittenSeries,
82 DiagCode::MisplacedPath,
83 DiagCode::MissingRecording,
84 ];
85
86 pub fn code_str(self) -> &'static str {
87 match self {
88 DiagCode::UnexpectedChar => "unexpected-char",
89 DiagCode::InvalidNumber => "invalid-number",
90 DiagCode::UnexpectedToken => "unexpected-token",
91 DiagCode::UnexpectedEof => "unexpected-eof",
92 DiagCode::UnclosedParen => "unclosed-paren",
93 DiagCode::UnbalancedParen => "unbalanced-paren",
94 DiagCode::EmptyExpression => "empty-expression",
95 DiagCode::RecursionLimit => "recursion-limit",
96 DiagCode::BadArity => "bad-arity",
97 DiagCode::MultilineBody => "multiline-body",
98 DiagCode::BadScore => "bad-score",
99 DiagCode::BadLane => "bad-lane",
100 DiagCode::BadScale => "bad-scale",
101 DiagCode::BadTempo => "bad-tempo",
102 DiagCode::DanglingRef => "dangling-ref",
103 DiagCode::RefAboveRoot => "ref-above-root",
104 DiagCode::RefCycle => "ref-cycle",
105 DiagCode::Io => "io",
106 DiagCode::BadArrangementArg => "bad-arrangement-arg",
107 DiagCode::BadDefault => "bad-default",
108 DiagCode::BareTime => "bare-time",
109 DiagCode::NotNodeContent => "not-node-content",
110 DiagCode::NonIntegerIndex => "non-integer-index",
111 DiagCode::UnwrittenSeries => "unwritten-series",
112 DiagCode::MisplacedPath => "misplaced-path",
113 DiagCode::MissingRecording => "missing-recording",
114 }
115 }
116}
117
118#[derive(Clone, Debug, PartialEq, Eq)]
119pub struct Diag {
120 pub code: DiagCode,
121 pub span: ByteSpan,
122 pub message: String,
123}
124
125impl Diag {
126 pub fn new(code: DiagCode, span: ByteSpan, message: impl Into<String>) -> Diag {
127 Diag {
128 code,
129 span,
130 message: message.into(),
131 }
132 }
133}
134
135impl fmt::Display for Diag {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 write!(
138 f,
139 "error[{}]: {} (at bytes {}..{})",
140 self.code.code_str(),
141 self.message,
142 self.span.start,
143 self.span.end,
144 )
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn registry_codes_are_unique_kebab_case() {
154 let mut codes: Vec<&str> = DiagCode::ALL.iter().map(|c| c.code_str()).collect();
155 let before = codes.len();
156 codes.sort_unstable();
157 codes.dedup();
158 assert_eq!(before, codes.len(), "code strings must be unique");
159 for c in DiagCode::ALL {
160 let s = c.code_str();
161 assert!(
162 s.chars().all(|ch| ch.is_ascii_lowercase() || ch == '-'),
163 "kebab-case only: {s:?}"
164 );
165 }
166 }
167
168 #[test]
169 fn display_is_located_and_ascii() {
170 let d = Diag::new(DiagCode::UnexpectedEof, ByteSpan::new(1, 4), "oops");
171 let s = d.to_string();
172 assert!(s.is_ascii());
173 assert!(s.contains("unexpected-eof"));
174 assert!(s.contains("1..4"));
175 }
176}