Skip to main content

jay/
error.rs

1//! Error type carrying a position in the user's source expression.
2
3use std::fmt;
4
5/// Byte range into the display source of a compiled program.
6#[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/// Broad class of a failure. `NotYet` and `Language` are deliberately
23/// distinct: the former is a promise, the latter is a property of J/APL.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum ErrorKind {
26    Parse,
27    Rank,
28    Length,
29    Shape,
30    Domain,
31    Type,
32    Value,
33    /// Present in the language, not implemented yet.
34    NotYet,
35    /// Absent from the language itself; will never exist.
36    Language,
37    /// Present in the language and closed by libjay's sandbox: the host
38    /// policy, not a property of J or APL, and not a queue position.
39    Sandbox,
40    /// Larger than libjay will allocate.
41    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    /// A feature the language has and libjay's sandbox does not open. The
92    /// message says what the feature would reach; the kind's label says who
93    /// closed it.
94    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    /// Render with a caret line pointing into `src` (the display source).
112    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
141/// Find the source line containing `span` and the span's position in it,
142/// measured in characters (for caret alignment).
143fn locate(src: &str, span: Span) -> Option<(&str, usize, usize)> {
144    // A span that does not land on this source has no caret to draw; the
145    // message still stands on its own.
146    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}