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 { .. } => Term::Atom(interner.intern("syntax_error")),
194 }
195 }
196
197 pub fn to_display(&self, interner: &mut StringInterner) -> String {
200 let term = self.to_term(interner);
201 let mut out = String::new();
202 format_term(&term, interner, &mut out);
203 out
204 }
205}
206
207pub fn format_term(term: &Term, interner: &StringInterner, out: &mut String) {
211 match term {
212 Term::Atom(id) => out.push_str(interner.resolve(*id)),
213 Term::Var(id) => {
214 out.push('_');
215 out.push_str(&id.to_string());
216 }
217 Term::Integer(n) => out.push_str(&n.to_string()),
218 Term::Float(f) => out.push_str(&f.to_string()),
219 Term::Compound { functor, args } => {
220 out.push_str(interner.resolve(*functor));
221 out.push('(');
222 for (i, a) in args.iter().enumerate() {
223 if i > 0 {
224 out.push_str(", ");
225 }
226 format_term(a, interner, out);
227 }
228 out.push(')');
229 }
230 Term::List { head, tail } => {
231 out.push('[');
232 format_term(head, interner, out);
233 let mut cur = tail.as_ref();
234 loop {
235 match cur {
236 Term::List { head, tail } => {
237 out.push_str(", ");
238 format_term(head, interner, out);
239 cur = tail;
240 }
241 Term::Atom(id) if interner.resolve(*id) == "[]" => break,
242 other => {
243 out.push('|');
244 format_term(other, interner, out);
245 break;
246 }
247 }
248 }
249 out.push(']');
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn instantiation_error_term_shape() {
260 let mut interner = StringInterner::new();
261 let err = PrologError::Instantiation {
262 context: "X must be bound".into(),
263 };
264 let term = err.to_term(&mut interner);
265 match term {
266 Term::Compound { functor, args } => {
267 assert_eq!(interner.resolve(functor), "error");
268 assert_eq!(args.len(), 2);
269 match &args[0] {
270 Term::Atom(id) => assert_eq!(interner.resolve(*id), "instantiation_error"),
271 _ => panic!("expected atom formal term"),
272 }
273 }
274 _ => panic!("expected compound error/2"),
275 }
276 }
277
278 #[test]
279 fn type_error_term_shape() {
280 let mut interner = StringInterner::new();
281 let foo = interner.intern("foo");
282 let err = PrologError::Type {
283 expected_type: "integer",
284 culprit: Term::Atom(foo),
285 context: "arithmetic on non-number".into(),
286 };
287 let term = err.to_term(&mut interner);
288 let Term::Compound { args, .. } = term else {
290 panic!("expected compound");
291 };
292 let Term::Compound {
293 functor: type_functor,
294 args: type_args,
295 } = &args[0]
296 else {
297 panic!("expected formal compound");
298 };
299 assert_eq!(interner.resolve(*type_functor), "type_error");
300 assert_eq!(type_args.len(), 2);
301 assert!(matches!(type_args[1], Term::Atom(id) if interner.resolve(id) == "foo"));
302 }
303
304 #[test]
305 fn existence_error_indicator() {
306 let mut interner = StringInterner::new();
307 let f = interner.intern("frobnicate");
308 let slash = interner.intern("/");
309 let indicator = Term::Compound {
310 functor: slash,
311 args: vec![Term::Atom(f), Term::Integer(2)],
312 };
313 let err = PrologError::Existence {
314 object_type: "procedure",
315 culprit: indicator,
316 context: "frobnicate/2 is undefined".into(),
317 };
318 let display = err.to_display(&mut interner);
319 assert!(
320 display.contains("existence_error(procedure, /(frobnicate, 2))"),
321 "got: {display}"
322 );
323 assert!(display.contains("frobnicate/2 is undefined"));
324 }
325
326 #[test]
327 fn evaluation_error_zero_divisor_renders() {
328 let mut interner = StringInterner::new();
329 let err = PrologError::Evaluation {
330 kind: "zero_divisor",
331 context: "Division by zero".into(),
332 };
333 let display = err.to_display(&mut interner);
334 assert!(
335 display.contains("evaluation_error(zero_divisor)"),
336 "got: {display}"
337 );
338 assert!(display.contains("zero"));
339 }
340
341 #[test]
342 fn resource_steps_is_uncatchable() {
343 let err = PrologError::Resource {
344 kind: "steps",
345 context: "step limit exceeded".into(),
346 };
347 assert!(err.is_uncatchable());
348 }
349
350 #[test]
351 fn other_errors_are_catchable() {
352 let err = PrologError::Type {
353 expected_type: "integer",
354 culprit: Term::Integer(0),
355 context: String::new(),
356 };
357 assert!(!err.is_uncatchable());
358 }
359}