1use plg_shared::{StringInterner, Term};
18
19#[derive(Debug, Clone)]
23pub struct ThrownError {
24 pub term: Term,
25 pub uncatchable: bool,
26}
27
28impl ThrownError {
29 pub fn from_prolog(err: PrologError, interner: &mut StringInterner) -> Self {
32 let uncatchable = err.is_uncatchable();
33 let term = err.to_term(interner);
34 ThrownError { term, uncatchable }
35 }
36
37 pub fn from_term(term: Term) -> Self {
39 ThrownError {
40 term,
41 uncatchable: false,
42 }
43 }
44}
45
46#[derive(Debug, Clone)]
48pub enum PrologError {
49 Instantiation { context: String },
51 Type {
54 expected_type: &'static str,
55 culprit: Term,
56 context: String,
57 },
58 Existence {
61 object_type: &'static str,
62 culprit: Term,
63 context: String,
64 },
65 Domain {
67 expected_domain: &'static str,
68 culprit: Term,
69 context: String,
70 },
71 Evaluation {
73 kind: &'static str, context: String,
75 },
76 Permission {
78 operation: &'static str,
79 permission_type: &'static str,
80 culprit: Term,
81 context: String,
82 },
83 Representation {
85 flag: &'static str, context: String,
87 },
88 Resource {
92 kind: &'static str, context: String,
94 },
95 Syntax { context: String },
97}
98
99impl PrologError {
100 pub fn is_uncatchable(&self) -> bool {
105 matches!(self, PrologError::Resource { kind: "steps", .. })
106 }
107
108 pub fn to_term(&self, interner: &mut StringInterner) -> Term {
113 let formal = self.formal_term(interner);
114 let context_atom = interner.intern(self.context());
115 let error_functor = interner.intern("error");
116 Term::Compound {
117 functor: error_functor,
118 args: vec![formal, Term::Atom(context_atom)],
119 }
120 }
121
122 pub fn context(&self) -> &str {
125 match self {
126 PrologError::Instantiation { context }
127 | PrologError::Type { context, .. }
128 | PrologError::Existence { context, .. }
129 | PrologError::Domain { context, .. }
130 | PrologError::Evaluation { context, .. }
131 | PrologError::Permission { context, .. }
132 | PrologError::Representation { context, .. }
133 | PrologError::Resource { context, .. }
134 | PrologError::Syntax { context } => context,
135 }
136 }
137
138 fn formal_term(&self, interner: &mut StringInterner) -> Term {
139 match self {
140 PrologError::Instantiation { .. } => Term::Atom(interner.intern("instantiation_error")),
141 PrologError::Type {
142 expected_type,
143 culprit,
144 ..
145 } => Term::Compound {
146 functor: interner.intern("type_error"),
147 args: vec![Term::Atom(interner.intern(expected_type)), culprit.clone()],
148 },
149 PrologError::Existence {
150 object_type,
151 culprit,
152 ..
153 } => Term::Compound {
154 functor: interner.intern("existence_error"),
155 args: vec![Term::Atom(interner.intern(object_type)), culprit.clone()],
156 },
157 PrologError::Domain {
158 expected_domain,
159 culprit,
160 ..
161 } => Term::Compound {
162 functor: interner.intern("domain_error"),
163 args: vec![
164 Term::Atom(interner.intern(expected_domain)),
165 culprit.clone(),
166 ],
167 },
168 PrologError::Evaluation { kind, .. } => Term::Compound {
169 functor: interner.intern("evaluation_error"),
170 args: vec![Term::Atom(interner.intern(kind))],
171 },
172 PrologError::Permission {
173 operation,
174 permission_type,
175 culprit,
176 ..
177 } => Term::Compound {
178 functor: interner.intern("permission_error"),
179 args: vec![
180 Term::Atom(interner.intern(operation)),
181 Term::Atom(interner.intern(permission_type)),
182 culprit.clone(),
183 ],
184 },
185 PrologError::Representation { flag, .. } => Term::Compound {
186 functor: interner.intern("representation_error"),
187 args: vec![Term::Atom(interner.intern(flag))],
188 },
189 PrologError::Resource { kind, .. } => Term::Compound {
190 functor: interner.intern("resource_error"),
191 args: vec![Term::Atom(interner.intern(kind))],
192 },
193 PrologError::Syntax { context } => Term::Compound {
194 functor: interner.intern("syntax_error"),
195 args: vec![Term::Atom(interner.intern(context))],
196 },
197 }
198 }
199
200 pub fn to_display(&self, interner: &mut StringInterner) -> String {
203 let term = self.to_term(interner);
204 let mut out = String::new();
205 format_term(&term, interner, &mut out);
206 out
207 }
208}
209
210pub fn format_term(term: &Term, interner: &StringInterner, out: &mut String) {
214 match term {
215 Term::Atom(id) => out.push_str(interner.resolve(*id)),
216 Term::Var(id) => {
217 out.push('_');
218 out.push_str(&id.to_string());
219 }
220 Term::Integer(n) => out.push_str(&n.to_string()),
221 Term::Float(f) => out.push_str(&f.to_string()),
222 Term::Compound { functor, args } => {
223 out.push_str(interner.resolve(*functor));
224 out.push('(');
225 for (i, a) in args.iter().enumerate() {
226 if i > 0 {
227 out.push_str(", ");
228 }
229 format_term(a, interner, out);
230 }
231 out.push(')');
232 }
233 Term::List { head, tail } => {
234 out.push('[');
235 format_term(head, interner, out);
236 let mut cur = tail.as_ref();
237 loop {
238 match cur {
239 Term::List { head, tail } => {
240 out.push_str(", ");
241 format_term(head, interner, out);
242 cur = tail;
243 }
244 Term::Atom(id) if interner.resolve(*id) == "[]" => break,
245 other => {
246 out.push('|');
247 format_term(other, interner, out);
248 break;
249 }
250 }
251 }
252 out.push(']');
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn instantiation_error_term_shape() {
263 let mut interner = StringInterner::new();
264 let err = PrologError::Instantiation {
265 context: "X must be bound".into(),
266 };
267 let term = err.to_term(&mut interner);
268 match term {
269 Term::Compound { functor, args } => {
270 assert_eq!(interner.resolve(functor), "error");
271 assert_eq!(args.len(), 2);
272 match &args[0] {
273 Term::Atom(id) => assert_eq!(interner.resolve(*id), "instantiation_error"),
274 _ => panic!("expected atom formal term"),
275 }
276 }
277 _ => panic!("expected compound error/2"),
278 }
279 }
280
281 #[test]
282 fn type_error_term_shape() {
283 let mut interner = StringInterner::new();
284 let foo = interner.intern("foo");
285 let err = PrologError::Type {
286 expected_type: "integer",
287 culprit: Term::Atom(foo),
288 context: "arithmetic on non-number".into(),
289 };
290 let term = err.to_term(&mut interner);
291 let Term::Compound { args, .. } = term else {
293 panic!("expected compound");
294 };
295 let Term::Compound {
296 functor: type_functor,
297 args: type_args,
298 } = &args[0]
299 else {
300 panic!("expected formal compound");
301 };
302 assert_eq!(interner.resolve(*type_functor), "type_error");
303 assert_eq!(type_args.len(), 2);
304 assert!(matches!(type_args[1], Term::Atom(id) if interner.resolve(id) == "foo"));
305 }
306
307 #[test]
308 fn existence_error_indicator() {
309 let mut interner = StringInterner::new();
310 let f = interner.intern("frobnicate");
311 let slash = interner.intern("/");
312 let indicator = Term::Compound {
313 functor: slash,
314 args: vec![Term::Atom(f), Term::Integer(2)],
315 };
316 let err = PrologError::Existence {
317 object_type: "procedure",
318 culprit: indicator,
319 context: "frobnicate/2 is undefined".into(),
320 };
321 let display = err.to_display(&mut interner);
322 assert!(
323 display.contains("existence_error(procedure, /(frobnicate, 2))"),
324 "got: {display}"
325 );
326 assert!(display.contains("frobnicate/2 is undefined"));
327 }
328
329 #[test]
330 fn evaluation_error_zero_divisor_renders() {
331 let mut interner = StringInterner::new();
332 let err = PrologError::Evaluation {
333 kind: "zero_divisor",
334 context: "Division by zero".into(),
335 };
336 let display = err.to_display(&mut interner);
337 assert!(
338 display.contains("evaluation_error(zero_divisor)"),
339 "got: {display}"
340 );
341 assert!(display.contains("zero"));
342 }
343
344 #[test]
345 fn resource_steps_is_uncatchable() {
346 let err = PrologError::Resource {
347 kind: "steps",
348 context: "step limit exceeded".into(),
349 };
350 assert!(err.is_uncatchable());
351 }
352
353 #[test]
354 fn other_errors_are_catchable() {
355 let err = PrologError::Type {
356 expected_type: "integer",
357 culprit: Term::Integer(0),
358 context: String::new(),
359 };
360 assert!(!err.is_uncatchable());
361 }
362}