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}