Skip to main content

rexlang_engine/
error.rs

1use std::path::PathBuf;
2use std::process::ExitStatus;
3
4use rexlang_ast::expr::Symbol;
5use rexlang_lexer::LexicalError;
6use rexlang_parser::error::ParserErr;
7use rexlang_typesystem::TypeError;
8use rexlang_util::OutOfGas;
9
10use crate::libraries::LibraryId;
11
12#[derive(Debug)]
13pub enum LibraryError {
14    NotFound {
15        library_name: String,
16    },
17    NoBaseDirectory,
18    ImportEscapesRoot,
19    EmptyLibraryPath,
20    StatePoisoned,
21    CyclicImport {
22        id: LibraryId,
23    },
24    InvalidIncludeRoot {
25        path: PathBuf,
26        source: std::io::Error,
27    },
28    InvalidLibraryPath {
29        path: PathBuf,
30        source: std::io::Error,
31    },
32    ReadFailed {
33        path: PathBuf,
34        source: std::io::Error,
35    },
36    NotUtf8 {
37        kind: &'static str,
38        path: PathBuf,
39        source: std::string::FromUtf8Error,
40    },
41    NotUtf8Remote {
42        url: String,
43        source: std::string::FromUtf8Error,
44    },
45    ShaMismatchStdlib {
46        library: String,
47        expected: String,
48        actual: String,
49    },
50    ShaMismatchPath {
51        kind: &'static str,
52        path: PathBuf,
53        expected: String,
54        actual: String,
55    },
56    MissingExport {
57        library: Symbol,
58        export: Symbol,
59    },
60    DuplicateImportedName {
61        name: Symbol,
62    },
63    ImportNameConflictsWithLocal {
64        library: Symbol,
65        name: Symbol,
66    },
67    Lex {
68        source: LexicalError,
69    },
70    LexInLibrary {
71        library: LibraryId,
72        source: LexicalError,
73    },
74    Parse {
75        errors: Vec<ParserErr>,
76    },
77    ParseInLibrary {
78        library: LibraryId,
79        errors: Vec<ParserErr>,
80    },
81    TopLevelExprInLibrary {
82        library: LibraryId,
83    },
84    InvalidGithubImport {
85        url: String,
86    },
87    UnpinnedGithubImport {
88        url: String,
89    },
90    CurlFailed {
91        source: std::io::Error,
92    },
93    CurlNonZeroExit {
94        url: String,
95        status: ExitStatus,
96    },
97}
98
99impl std::fmt::Display for LibraryError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            LibraryError::NotFound { library_name } => {
103                write!(f, "library not found: {library_name}")
104            }
105            LibraryError::NoBaseDirectory => {
106                write!(f, "cannot resolve local import without a base directory")
107            }
108            LibraryError::ImportEscapesRoot => write!(f, "import path escapes filesystem root"),
109            LibraryError::EmptyLibraryPath => write!(f, "empty library path"),
110            LibraryError::StatePoisoned => write!(f, "library state poisoned"),
111            LibraryError::CyclicImport { id } => write!(f, "cyclic library import: {id}"),
112            LibraryError::InvalidIncludeRoot { path, source } => {
113                write!(f, "invalid include root `{}`: {source}", path.display())
114            }
115            LibraryError::InvalidLibraryPath { path, source } => {
116                write!(f, "invalid library path `{}`: {source}", path.display())
117            }
118            LibraryError::ReadFailed { path, source } => {
119                write!(f, "failed to read library `{}`: {source}", path.display())
120            }
121            LibraryError::NotUtf8 { kind, path, source } => {
122                write!(
123                    f,
124                    "{kind} library `{}` was not utf-8: {source}",
125                    path.display()
126                )
127            }
128            LibraryError::NotUtf8Remote { url, source } => {
129                write!(f, "remote library `{url}` was not utf-8: {source}")
130            }
131            LibraryError::ShaMismatchStdlib {
132                library,
133                expected,
134                actual,
135            } => write!(
136                f,
137                "sha mismatch for `{library}`: expected #{expected}, got #{actual}"
138            ),
139            LibraryError::ShaMismatchPath {
140                kind,
141                path,
142                expected,
143                actual,
144            } => write!(
145                f,
146                "{kind} import sha mismatch for {}: expected #{expected}, got #{actual}",
147                path.display()
148            ),
149            LibraryError::MissingExport { library, export } => {
150                write!(f, "library `{library}` does not export `{export}`")
151            }
152            LibraryError::DuplicateImportedName { name } => {
153                write!(f, "duplicate imported name `{name}`")
154            }
155            LibraryError::ImportNameConflictsWithLocal { library, name } => {
156                write!(
157                    f,
158                    "imported name `{name}` from library `{library}` conflicts with local declaration"
159                )
160            }
161            LibraryError::Lex { source } => write!(f, "lex error: {source}"),
162            LibraryError::LexInLibrary { library, source } => {
163                write!(f, "lex error in library {library}: {source}")
164            }
165            LibraryError::Parse { errors } => {
166                write!(f, "parse error:")?;
167                for err in errors {
168                    write!(f, "\n  {err}")?;
169                }
170                Ok(())
171            }
172            LibraryError::ParseInLibrary { library, errors } => {
173                write!(f, "parse error in library {library}:")?;
174                for err in errors {
175                    write!(f, "\n  {err}")?;
176                }
177                Ok(())
178            }
179            LibraryError::TopLevelExprInLibrary { library } => {
180                write!(
181                    f,
182                    "library {library} cannot contain a top-level expression; library files must be declaration-only"
183                )
184            }
185            LibraryError::InvalidGithubImport { url } => write!(
186                f,
187                "github import must be `https://github.com/<owner>/<repo>/<path>.rex#<sha>` (got {url})"
188            ),
189            LibraryError::UnpinnedGithubImport { url } => {
190                write!(f, "github import must be pinned: add `#<sha>` (got {url})")
191            }
192            LibraryError::CurlFailed { source } => write!(f, "failed to run curl: {source}"),
193            LibraryError::CurlNonZeroExit { url, status } => {
194                write!(f, "failed to fetch {url} (curl exit {status})")
195            }
196        }
197    }
198}
199
200impl std::error::Error for LibraryError {
201    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
202        match self {
203            LibraryError::InvalidIncludeRoot { source, .. } => Some(source),
204            LibraryError::InvalidLibraryPath { source, .. } => Some(source),
205            LibraryError::ReadFailed { source, .. } => Some(source),
206            LibraryError::NotUtf8 { source, .. } => Some(source),
207            LibraryError::NotUtf8Remote { source, .. } => Some(source),
208            LibraryError::Lex { source } => Some(source),
209            LibraryError::LexInLibrary { source, .. } => Some(source),
210            LibraryError::CurlFailed { source } => Some(source),
211            _ => None,
212        }
213    }
214}
215
216#[derive(Debug, thiserror::Error)]
217pub enum EngineError {
218    #[error("unknown variable `{0}`")]
219    UnknownVar(Symbol),
220    #[error("value is not callable: {0}")]
221    NotCallable(String),
222    #[error("native `{name}` expected {expected} args, got {got}")]
223    NativeArity {
224        name: Symbol,
225        expected: usize,
226        got: usize,
227    },
228    #[error("expected {expected}, got {got}")]
229    NativeType { expected: String, got: String },
230    #[error("pattern match failure")]
231    MatchFailure,
232    #[error("expected boolean, got {0}")]
233    ExpectedBool(String),
234    #[error("type error: {0}")]
235    Type(#[from] TypeError),
236    #[error("ambiguous overload for `{name}`")]
237    AmbiguousOverload { name: Symbol },
238    #[error("no native implementation for `{name}` with type {typ}")]
239    MissingImpl { name: Symbol, typ: String },
240    #[error("ambiguous native implementation for `{name}` with type {typ}")]
241    AmbiguousImpl { name: Symbol, typ: String },
242    #[error("duplicate native implementation for `{name}` with type {typ}")]
243    DuplicateImpl { name: Symbol, typ: String },
244    #[error("no type class instance for `{class}` with type {typ}")]
245    MissingTypeclassImpl { class: Symbol, typ: String },
246    #[error("ambiguous type class instance for `{class}` with type {typ}")]
247    AmbiguousTypeclassImpl { class: Symbol, typ: String },
248    #[error("duplicate type class instance for `{class}` with type {typ}")]
249    DuplicateTypeclassImpl { class: Symbol, typ: String },
250    #[error("injected `{name}` has incompatible type {typ}")]
251    InvalidInjection { name: Symbol, typ: String },
252    #[error("unknown type for value in `{0}`")]
253    UnknownType(Symbol),
254    #[error("unknown field `{field}` on {value}")]
255    UnknownField { field: Symbol, value: String },
256    #[error("unsupported expression")]
257    UnsupportedExpr,
258    #[error("empty sequence")]
259    EmptySequence,
260    #[error("index {index} out of bounds in `{name}` (len {len})")]
261    IndexOutOfBounds {
262        name: Symbol,
263        index: i32,
264        len: usize,
265    },
266    #[error("internal error: {0}")]
267    Internal(String),
268    #[error(transparent)]
269    Library(#[from] Box<LibraryError>),
270    #[error("cancelled")]
271    Cancelled,
272    #[error("{0}")]
273    OutOfGas(#[from] OutOfGas),
274    #[error("{0}")]
275    Custom(String),
276    #[error("Evaluation suspended")]
277    Suspended,
278}
279
280impl From<LibraryError> for EngineError {
281    fn from(err: LibraryError) -> Self {
282        EngineError::Library(Box::new(err))
283    }
284}
285
286impl From<&str> for EngineError {
287    fn from(msg: &str) -> Self {
288        EngineError::Custom(msg.to_string())
289    }
290}
291
292impl From<String> for EngineError {
293    fn from(msg: String) -> Self {
294        EngineError::Custom(msg)
295    }
296}