Skip to main content

sva_ast/
diag.rs

1// Concern: declares the parser-level refusal code registry, ByteSpan and Diag | Non-concern: directory-level Refusal wrapping (refusal.rs), raising one | IO: (DiagCode, ByteSpan, message) -> Diag
2
3use std::fmt;
4
5/// A half-open byte range into one file's content; always lands on char boundaries.
6#[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}