1use crate::cell::*;
9use crate::copyterm::copy_to_buf;
10use crate::machine::{Machine, RtError};
11use crate::render::format_term;
12
13pub fn set_formal(m: &mut Machine, formal: Word, context: &str, uncatchable: bool) {
16 let mark = m.heap.len().min(payload_start(formal, m));
17 let ctx_atom = make_atom(m.atoms.intern(context));
18 let err_f = m.atoms.intern("error");
19 let idx = m.heap.len();
20 m.heap.push(pack_functor(err_f, 2));
21 m.heap.push(formal);
22 m.heap.push(ctx_atom);
23 let ball_word = make(TAG_STR, idx as u64);
24 let ball = copy_to_buf(m, ball_word);
25 let mut message = String::new();
26 format_term(m, ball_word, &mut message);
27 if let Some((file, line, col)) = m.site_location(m.error_site) {
37 use std::fmt::Write as _;
38 let _ = write!(message, " at {file}:{line}:{col}");
39 }
40 m.heap.truncate(mark.max(idx)); m.error = Some(RtError {
42 ball,
43 message,
44 uncatchable,
45 });
46}
47
48fn payload_start(w: Word, m: &Machine) -> usize {
50 match tag_of(w) {
51 TAG_STR | TAG_LST | TAG_FLT | TAG_BIG => payload(w) as usize,
52 _ => m.heap.len(),
53 }
54}
55
56pub fn instantiation(m: &mut Machine, context: &str) {
58 let f = make_atom(m.atoms.intern("instantiation_error"));
59 set_formal(m, f, context, false);
60}
61
62pub fn type_error(m: &mut Machine, expected: &str, culprit: Word, context: &str) {
64 let te = m.atoms.intern("type_error");
65 let ty = make_atom(m.atoms.intern(expected));
66 let idx = m.heap.len();
67 m.heap.push(pack_functor(te, 2));
68 m.heap.push(ty);
69 m.heap.push(culprit);
70 set_formal(m, make(TAG_STR, idx as u64), context, false);
71}
72
73pub fn domain_error(m: &mut Machine, domain: &str, culprit: Word, context: &str) {
75 let de = m.atoms.intern("domain_error");
76 let d = make_atom(m.atoms.intern(domain));
77 let idx = m.heap.len();
78 m.heap.push(pack_functor(de, 2));
79 m.heap.push(d);
80 m.heap.push(culprit);
81 set_formal(m, make(TAG_STR, idx as u64), context, false);
82}
83
84pub fn evaluation(m: &mut Machine, kind: &str, context: &str) {
86 let ee = m.atoms.intern("evaluation_error");
87 let k = make_atom(m.atoms.intern(kind));
88 let idx = m.heap.len();
89 m.heap.push(pack_functor(ee, 1));
90 m.heap.push(k);
91 set_formal(m, make(TAG_STR, idx as u64), context, false);
92}
93
94pub fn resource(m: &mut Machine, kind: &str, context: &str, uncatchable: bool) {
96 let re = m.atoms.intern("resource_error");
97 let k = make_atom(m.atoms.intern(kind));
98 let idx = m.heap.len();
99 m.heap.push(pack_functor(re, 1));
100 m.heap.push(k);
101 set_formal(m, make(TAG_STR, idx as u64), context, uncatchable);
102}
103
104pub fn existence_procedure(m: &mut Machine, name: &str, arity: u32) {
108 let ee = m.atoms.intern("existence_error");
109 let proc = make_atom(m.atoms.intern("procedure"));
110 let slash = m.atoms.intern("/");
111 let name_atom = make_atom(m.atoms.intern(name));
112 let pi = m.heap.len();
113 m.heap.push(pack_functor(slash, 2));
114 m.heap.push(name_atom);
115 m.heap.push(make_int(arity as i64));
116 let idx = m.heap.len();
117 m.heap.push(pack_functor(ee, 2));
118 m.heap.push(proc);
119 m.heap.push(make(TAG_STR, pi as u64));
120 let context = format!("Undefined procedure: {name}/{arity}");
121 set_formal(m, make(TAG_STR, idx as u64), &context, false);
122}
123
124pub fn throw_term(m: &mut Machine, ball_word: Word) {
127 let ball_word = m.deref(ball_word);
128 if tag_of(ball_word) == TAG_REF {
129 instantiation(m, "throw/1 requires a bound argument");
131 return;
132 }
133 let ball = copy_to_buf(m, ball_word);
134 let mut message = String::new();
135 format_term(m, ball_word, &mut message);
136 m.error = Some(RtError {
137 ball,
138 message,
139 uncatchable: false,
140 });
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use plg_shared::StringInterner;
147
148 fn machine() -> Box<Machine> {
149 Machine::new(StringInterner::new(), Vec::new())
150 }
151
152 #[test]
153 fn existence_message_format() {
154 let mut m = machine();
155 existence_procedure(&mut m, "nosuch", 1);
156 assert_eq!(
157 m.error.as_ref().unwrap().message,
158 "error(existence_error(procedure, /(nosuch, 1)), Undefined procedure: nosuch/1)"
159 );
160 }
161
162 #[test]
163 fn evaluation_message_format() {
164 let mut m = machine();
165 evaluation(
166 &mut m,
167 "zero_divisor",
168 "Division by zero (integer division)",
169 );
170 assert_eq!(
171 m.error.as_ref().unwrap().message,
172 "error(evaluation_error(zero_divisor), Division by zero (integer division))"
173 );
174 }
175
176 #[test]
177 fn existence_suffix_appears_when_site_resolves() {
178 use crate::machine::SrcLoc;
179 let mut m = machine();
180 m.set_provenance(
181 vec![SrcLoc {
182 file: 0,
183 line: 12,
184 col: 7,
185 }],
186 vec!["examples/family.pl".to_string()],
187 );
188 m.error_site = 0;
189 existence_procedure(&mut m, "foo", 1);
190 assert_eq!(
192 m.error.as_ref().unwrap().message,
193 "error(existence_error(procedure, /(foo, 1)), Undefined procedure: foo/1) \
194 at examples/family.pl:12:7"
195 );
196 }
197
198 #[test]
199 fn ball_survives_heap_truncation() {
200 let mut m = machine();
201 let heap_mark = m.heap.len();
202 existence_procedure(&mut m, "gone", 2);
203 m.heap.truncate(heap_mark); let err = m.error.take().unwrap();
205 let w = crate::copyterm::restore_from_buf(&mut m, &err.ball);
206 let mut s = String::new();
207 format_term(&m, w, &mut s);
208 assert!(s.starts_with("error(existence_error(procedure"), "{s}");
209 }
210}