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