1use crate::cell::*;
10use crate::machine::Machine;
11use plg_shared::atom::ATOM_NIL;
12
13pub struct RenderedSolution {
16 pub bindings: Vec<(String, String, String)>,
18}
19
20pub fn capture_solution(m: &Machine) -> RenderedSolution {
22 let mut vars: Vec<_> = m.query_vars.iter().collect();
23 vars.sort_by(|a, b| a.0.cmp(&b.0));
24 let bindings = vars
25 .into_iter()
26 .filter(|(name, _)| name != "_")
27 .map(|(name, idx)| {
28 let w = m.deref(make_ref(*idx));
29 (name.clone(), term_to_json(m, w), term_to_string(m, w))
30 })
31 .collect();
32 RenderedSolution { bindings }
33}
34
35pub fn json_escape(s: &str) -> String {
37 let mut out = String::with_capacity(s.len() + 2);
38 for c in s.chars() {
39 match c {
40 '"' => out.push_str("\\\""),
41 '\\' => out.push_str("\\\\"),
42 '\n' => out.push_str("\\n"),
43 '\r' => out.push_str("\\r"),
44 '\t' => out.push_str("\\t"),
45 '\u{08}' => out.push_str("\\b"),
46 '\u{0c}' => out.push_str("\\f"),
47 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
48 c => out.push(c),
49 }
50 }
51 out
52}
53
54fn fmt_float(f: f64) -> String {
58 if f.is_finite() && f.fract() == 0.0 && f.abs() < 1e15 {
59 format!("{f:.1}")
60 } else {
61 format!("{f}")
62 }
63}
64
65pub fn term_to_json(m: &Machine, w: Word) -> String {
66 term_to_json_v(m, w, &mut Vec::new())
67}
68
69fn term_to_json_v(m: &Machine, w: Word, visiting: &mut Vec<usize>) -> String {
75 let w = m.deref(w);
76 match tag_of(w) {
77 TAG_ATOM => format!("\"{}\"", json_escape(m.atoms.resolve(atom_id(w)))),
78 TAG_INT => int_value(w).to_string(),
79 TAG_BIG => (m.heap[payload(w) as usize] as i64).to_string(),
80 TAG_FLT => fmt_float(f64::from_bits(m.heap[payload(w) as usize])),
81 TAG_REF => format!("\"_{}\"", payload(w)),
82 TAG_STR => {
83 let idx = payload(w) as usize;
84 if visiting.contains(&idx) {
85 return format!("\"_{idx}\""); }
87 visiting.push(idx);
88 let (f, n) = unpack_functor(m.heap[idx]);
89 let args: Vec<String> = (0..n as usize)
90 .map(|i| term_to_json_v(m, m.heap[idx + 1 + i], visiting))
91 .collect();
92 visiting.pop();
93 format!(
95 "{{\"args\":[{}],\"functor\":\"{}\"}}",
96 args.join(","),
97 json_escape(m.atoms.resolve(f))
98 )
99 }
100 TAG_LST => {
101 let idx = payload(w) as usize;
102 if visiting.contains(&idx) {
103 return format!("\"_{idx}\"");
104 }
105 visiting.push(idx);
106 let (elements, tail) = collect_list_v(m, w, visiting);
107 let items: Vec<String> = elements
108 .iter()
109 .map(|e| term_to_json_v(m, *e, visiting))
110 .collect();
111 let out = match tail {
112 None => format!("[{}]", items.join(",")),
113 Some(t) => format!(
115 "{{\"list\":[{}],\"tail\":{}}}",
116 items.join(","),
117 term_to_json_v(m, t, visiting)
118 ),
119 };
120 visiting.pop();
121 out
122 }
123 _ => unreachable!("bad tag"),
124 }
125}
126
127const INFIX: &[&str] = &[
129 "+", "-", "*", "/", "mod", "is", "=", "\\=", "<", ">", "=<", ">=", "=:=", "=\\=",
130];
131
132pub fn term_to_string(m: &Machine, w: Word) -> String {
133 term_to_string_v(m, w, &mut Vec::new())
134}
135
136fn term_to_string_v(m: &Machine, w: Word, visiting: &mut Vec<usize>) -> String {
137 let w = m.deref(w);
138 match tag_of(w) {
139 TAG_ATOM => m.atoms.resolve(atom_id(w)).to_string(),
140 TAG_INT => int_value(w).to_string(),
141 TAG_BIG => (m.heap[payload(w) as usize] as i64).to_string(),
142 TAG_FLT => format!("{}", f64::from_bits(m.heap[payload(w) as usize])),
143 TAG_REF => format!("_{}", payload(w)),
144 TAG_STR => {
145 let idx = payload(w) as usize;
146 if visiting.contains(&idx) {
147 return format!("_{idx}"); }
149 visiting.push(idx);
150 let (f, n) = unpack_functor(m.heap[idx]);
151 let name = m.atoms.resolve(f).to_string();
152 let out = if n == 2 && INFIX.contains(&name.as_str()) {
153 format!(
154 "{} {} {}",
155 term_to_string_v(m, m.heap[idx + 1], visiting),
156 name,
157 term_to_string_v(m, m.heap[idx + 2], visiting)
158 )
159 } else {
160 let args: Vec<String> = (0..n as usize)
161 .map(|i| term_to_string_v(m, m.heap[idx + 1 + i], visiting))
162 .collect();
163 format!("{}({})", name, args.join(", "))
164 };
165 visiting.pop();
166 out
167 }
168 TAG_LST => {
169 let idx = payload(w) as usize;
170 if visiting.contains(&idx) {
171 return format!("_{idx}");
172 }
173 visiting.push(idx);
174 let (elements, tail) = collect_list_v(m, w, visiting);
175 let items: Vec<String> = elements
176 .iter()
177 .map(|e| term_to_string_v(m, *e, visiting))
178 .collect();
179 let out = match tail {
180 None => format!("[{}]", items.join(", ")),
181 Some(t) => format!(
182 "[{}|{}]",
183 items.join(", "),
184 term_to_string_v(m, t, visiting)
185 ),
186 };
187 visiting.pop();
188 out
189 }
190 _ => unreachable!("bad tag"),
191 }
192}
193
194pub fn format_term(m: &Machine, w: Word, out: &mut String) {
198 format_term_v(m, w, out, &mut Vec::new())
199}
200
201fn format_term_v(m: &Machine, w: Word, out: &mut String, visiting: &mut Vec<usize>) {
202 let w = m.deref(w);
203 match tag_of(w) {
204 TAG_ATOM => out.push_str(m.atoms.resolve(atom_id(w))),
205 TAG_INT => out.push_str(&int_value(w).to_string()),
206 TAG_BIG => out.push_str(&(m.heap[payload(w) as usize] as i64).to_string()),
207 TAG_FLT => out.push_str(&f64::from_bits(m.heap[payload(w) as usize]).to_string()),
208 TAG_REF => {
209 out.push('_');
210 out.push_str(&payload(w).to_string());
211 }
212 TAG_STR => {
213 let idx = payload(w) as usize;
214 if visiting.contains(&idx) {
215 out.push('_');
216 out.push_str(&idx.to_string());
217 return;
218 }
219 visiting.push(idx);
220 let (f, n) = unpack_functor(m.heap[idx]);
221 out.push_str(m.atoms.resolve(f));
222 out.push('(');
223 for i in 0..n as usize {
224 if i > 0 {
225 out.push_str(", ");
226 }
227 format_term_v(m, m.heap[idx + 1 + i], out, visiting);
228 }
229 out.push(')');
230 visiting.pop();
231 }
232 TAG_LST => {
233 let idx = payload(w) as usize;
234 if visiting.contains(&idx) {
235 out.push('_');
236 out.push_str(&idx.to_string());
237 return;
238 }
239 visiting.push(idx);
240 out.push('[');
241 let (elements, tail) = collect_list_v(m, w, visiting);
242 for (i, e) in elements.iter().enumerate() {
243 if i > 0 {
244 out.push_str(", ");
245 }
246 format_term_v(m, *e, out, visiting);
247 }
248 if let Some(t) = tail {
249 out.push('|');
250 format_term_v(m, t, out, visiting);
251 }
252 out.push(']');
253 visiting.pop();
254 }
255 _ => unreachable!("bad tag"),
256 }
257}
258
259fn collect_list_v(m: &Machine, w: Word, visiting: &[usize]) -> (Vec<Word>, Option<Word>) {
264 let mut elements = Vec::new();
265 let mut cur = m.deref(w);
266 let mut seen: Vec<usize> = Vec::new();
267 loop {
268 match tag_of(cur) {
269 TAG_LST => {
270 let idx = payload(cur) as usize;
271 if seen.contains(&idx) || (visiting.contains(&idx) && !elements.is_empty()) {
272 return (elements, Some(cur));
273 }
274 seen.push(idx);
275 elements.push(m.heap[idx]);
276 cur = m.deref(m.heap[idx + 1]);
277 }
278 TAG_ATOM if atom_id(cur) == ATOM_NIL => return (elements, None),
279 _ => return (elements, Some(cur)),
280 }
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use plg_shared::StringInterner;
288
289 fn machine() -> Box<Machine> {
290 let mut atoms = StringInterner::new();
291 atoms.intern("foo");
292 atoms.intern("bar");
293 Machine::new(atoms, Vec::new())
294 }
295
296 #[test]
297 fn json_escape_matches_serde() {
298 assert_eq!(json_escape("a\"b\\c\nd"), "a\\\"b\\\\c\\nd");
299 assert_eq!(json_escape("\u{01}"), "\\u0001");
300 }
301
302 #[test]
303 fn atoms_ints_render() {
304 let m = machine();
305 let foo = m.atoms.lookup("foo").unwrap();
306 assert_eq!(term_to_json(&m, make_atom(foo)), "\"foo\"");
307 assert_eq!(term_to_json(&m, make_int(-7)), "-7");
308 assert_eq!(term_to_string(&m, make_int(-7)), "-7");
309 }
310
311 #[test]
312 fn compound_renders_sorted_keys() {
313 let mut m = machine();
314 let foo = m.atoms.lookup("foo").unwrap();
315 let bar = m.atoms.lookup("bar").unwrap();
316 let idx = m.heap.len();
317 m.heap.push(pack_functor(foo, 2));
318 m.heap.push(make_atom(bar));
319 m.heap.push(make_int(1));
320 let w = make(TAG_STR, idx as u64);
321 assert_eq!(
322 term_to_json(&m, w),
323 "{\"args\":[\"bar\",1],\"functor\":\"foo\"}"
324 );
325 assert_eq!(term_to_string(&m, w), "foo(bar, 1)");
326 }
327
328 #[test]
329 fn proper_and_partial_lists() {
330 let mut m = machine();
331 let nil = make_atom(ATOM_NIL);
332 let i2 = m.heap.len();
333 m.heap.push(make_int(2));
334 m.heap.push(nil);
335 let l2 = make(TAG_LST, i2 as u64);
336 let i1 = m.heap.len();
337 m.heap.push(make_int(1));
338 m.heap.push(l2);
339 let l1 = make(TAG_LST, i1 as u64);
340 assert_eq!(term_to_json(&m, l1), "[1,2]");
341 assert_eq!(term_to_string(&m, l1), "[1, 2]");
342
343 let v = m.new_var();
344 let ip = m.heap.len();
345 m.heap.push(make_int(1));
346 m.heap.push(v);
347 let lp = make(TAG_LST, ip as u64);
348 let json = term_to_json(&m, lp);
349 assert!(json.starts_with("{\"list\":[1],\"tail\":\"_"), "{json}");
350 assert!(term_to_string(&m, lp).starts_with("[1|_"));
351 }
352}