1use std::collections::{HashMap, HashSet};
4
5use xlog_core::{Result, ScalarType, XlogError};
6
7use crate::ast::{
8 Atom, BodyLiteral, Comparison, DomainDecl, PredColumn, PredDecl, Program, Query, Rule, Term,
9 TypeRef,
10};
11
12const LIST_ID_TYPE: ScalarType = ScalarType::U64;
13
14pub fn normalize_list_builtins(program: &Program) -> Result<Program> {
25 let mut normalizer = ListNormalizer::new(program)?;
26 normalizer.normalize_program(program)
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30enum ListOp {
31 Append,
32 Sort,
33 MSort,
34 ListToSet,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38enum LiteralKey {
39 U32(u32),
40 U64(u64),
41 I32(i32),
42 I64(i64),
43 F32(u32),
44 F64(u64),
45 Bool(bool),
46 Symbol(String),
47}
48
49#[derive(Debug, Clone)]
50struct ListDef {
51 id: u64,
52 elem_type: ScalarType,
53 items: Vec<Term>,
54 keys: Vec<LiteralKey>,
55}
56
57#[derive(Debug, Clone)]
58struct ListArg {
59 term: Term,
60 id: Option<u64>,
61 elem_type: ScalarType,
62 is_literal: bool,
63}
64
65#[derive(Debug, Clone, Copy)]
66struct ListColumn {
67 elem_type: ScalarType,
68}
69
70struct ListNormalizer {
71 list_columns: HashMap<(String, usize), ListColumn>,
72 lists_by_key: HashMap<(ScalarType, Vec<LiteralKey>), u64>,
73 lists: Vec<ListDef>,
74 next_list_id: u64,
75 helper_preds: HashSet<String>,
76 operation_rows: HashSet<(ListOp, ScalarType, Vec<u64>)>,
77 fresh_counter: usize,
78}
79
80impl ListNormalizer {
81 fn new(program: &Program) -> Result<Self> {
82 let domains: HashMap<String, ScalarType> = program
83 .domains
84 .iter()
85 .map(|DomainDecl { name, typ }| (name.clone(), *typ))
86 .collect();
87
88 let mut list_columns = HashMap::new();
89 for pred in &program.predicates {
90 let columns = if pred.columns.is_empty() {
91 pred.types
92 .iter()
93 .cloned()
94 .map(|typ| PredColumn { name: None, typ })
95 .collect()
96 } else {
97 pred.columns.clone()
98 };
99 for (idx, col) in columns.iter().enumerate() {
100 if let TypeRef::List(inner) = &col.typ {
101 let elem_type = Self::storage_type_for_type_ref(inner, &domains)?;
102 list_columns.insert((pred.name.clone(), idx), ListColumn { elem_type });
103 }
104 }
105 }
106
107 Ok(Self {
108 list_columns,
109 lists_by_key: HashMap::new(),
110 lists: Vec::new(),
111 next_list_id: 1,
112 helper_preds: HashSet::new(),
113 operation_rows: HashSet::new(),
114 fresh_counter: 0,
115 })
116 }
117
118 fn normalize_program(&mut self, program: &Program) -> Result<Program> {
119 let mut out = program.clone();
120 out.rules = program
121 .rules
122 .iter()
123 .map(|rule| self.normalize_rule(rule))
124 .collect::<Result<Vec<_>>>()?
125 .into_iter()
126 .flatten()
127 .collect();
128 out.constraints = program
129 .constraints
130 .iter()
131 .map(|constraint| {
132 let body = self.normalize_body(&constraint.body)?;
133 Ok(crate::ast::Constraint { body })
134 })
135 .collect::<Result<Vec<_>>>()?;
136 out.queries = program
137 .queries
138 .iter()
139 .map(|query| self.normalize_query(query))
140 .collect::<Result<Vec<_>>>()?;
141
142 self.append_helper_declarations_and_facts(&mut out)?;
143 Ok(out)
144 }
145
146 fn normalize_query(&mut self, query: &Query) -> Result<Query> {
147 let mut var_list_types = HashMap::new();
148 let atoms = self.expand_atom(&query.atom, false, &mut var_list_types)?;
149 let atom = atoms
150 .into_iter()
151 .next()
152 .unwrap_or_else(|| query.atom.clone());
153 Ok(Query { atom })
154 }
155
156 fn normalize_rule(&mut self, rule: &Rule) -> Result<Vec<Rule>> {
157 let mut var_list_types = HashMap::new();
158 let head = self.normalize_head_atom(&rule.head)?;
159 let body = self.normalize_body_with_env(&rule.body, &mut var_list_types)?;
160 Ok(vec![Rule { head, body }])
161 }
162
163 fn normalize_body(&mut self, body: &[BodyLiteral]) -> Result<Vec<BodyLiteral>> {
164 let mut var_list_types = HashMap::new();
165 self.normalize_body_with_env(body, &mut var_list_types)
166 }
167
168 fn normalize_body_with_env(
169 &mut self,
170 body: &[BodyLiteral],
171 var_list_types: &mut HashMap<String, ScalarType>,
172 ) -> Result<Vec<BodyLiteral>> {
173 let mut out = Vec::new();
174 for lit in body {
175 match lit {
176 BodyLiteral::Positive(atom) => {
177 for atom in self.expand_atom(atom, false, var_list_types)? {
178 out.push(BodyLiteral::Positive(atom));
179 }
180 }
181 BodyLiteral::Negated(atom) => {
182 let atoms = self.expand_atom(atom, true, var_list_types)?;
183 if atoms.len() != 1 {
184 return Err(list_error(
185 "negated list pattern expansion would require multiple atoms",
186 ));
187 }
188 out.push(BodyLiteral::Negated(atoms.into_iter().next().unwrap()));
189 }
190 BodyLiteral::Comparison(cmp) => {
191 out.push(BodyLiteral::Comparison(Comparison {
192 op: cmp.op,
193 left: self.normalize_value_term(&cmp.left, None)?,
194 right: self.normalize_value_term(&cmp.right, None)?,
195 }));
196 }
197 BodyLiteral::Epistemic(lit) => out.push(BodyLiteral::Epistemic(lit.clone())),
198 BodyLiteral::IsExpr(is_expr) => out.push(BodyLiteral::IsExpr(is_expr.clone())),
199 BodyLiteral::Univ(_) => {
200 return Err(list_error(
201 "univ literals must be normalized by the meta-term pass before list normalization",
202 ));
203 }
204 }
205 }
206 Ok(out)
207 }
208
209 fn normalize_head_atom(&mut self, atom: &Atom) -> Result<Atom> {
210 let mut terms = Vec::with_capacity(atom.terms.len());
211 for (idx, term) in atom.terms.iter().enumerate() {
212 let expected = self
213 .list_columns
214 .get(&(atom.predicate.clone(), idx))
215 .map(|col| col.elem_type);
216 terms.push(self.normalize_value_term(term, expected)?);
217 }
218 Ok(Atom {
219 predicate: atom.predicate.clone(),
220 terms,
221 })
222 }
223
224 fn expand_atom(
225 &mut self,
226 atom: &Atom,
227 negated: bool,
228 var_list_types: &mut HashMap<String, ScalarType>,
229 ) -> Result<Vec<Atom>> {
230 if let Some(atoms) = self.expand_list_builtin(atom, var_list_types)? {
231 return Ok(atoms);
232 }
233 if is_reserved_pair_helper(atom) {
234 return Err(list_error(
235 "pair helpers are reserved for meta-term normalization and are not implemented in list normalization",
236 ));
237 }
238
239 let mut terms = Vec::with_capacity(atom.terms.len());
240 let mut extra_atoms = Vec::new();
241 for (idx, term) in atom.terms.iter().enumerate() {
242 let expected = self
243 .list_columns
244 .get(&(atom.predicate.clone(), idx))
245 .map(|col| col.elem_type);
246 match term {
247 Term::Variable(name) => {
248 if let Some(elem_type) = expected {
249 var_list_types.insert(name.clone(), elem_type);
250 }
251 terms.push(term.clone());
252 }
253 Term::Cons { head, tail } => {
254 if negated {
255 return Err(list_error(
256 "cons patterns in negated atoms are unsupported before a positive binder",
257 ));
258 }
259 let elem_type = expected.ok_or_else(|| {
260 list_error("cons pattern requires a declared list<T> column")
261 })?;
262 let list_var = self.fresh_var("list_cons");
263 let head_term = self.normalize_scalar_term(head, Some(elem_type))?.1;
264 let tail_arg =
265 self.normalize_list_arg(tail, Some(elem_type), var_list_types)?;
266 if let Term::Variable(name) = &tail_arg.term {
267 var_list_types.insert(name.clone(), elem_type);
268 }
269 terms.push(Term::Variable(list_var.clone()));
270 extra_atoms.push(Atom {
271 predicate: cons_pred(elem_type),
272 terms: vec![Term::Variable(list_var), head_term, tail_arg.term],
273 });
274 }
275 _ => {
276 let normalized = self.normalize_value_term(term, expected)?;
277 terms.push(normalized);
278 }
279 }
280 }
281
282 let mut atoms = vec![Atom {
283 predicate: atom.predicate.clone(),
284 terms,
285 }];
286 atoms.extend(extra_atoms);
287 Ok(atoms)
288 }
289
290 fn expand_list_builtin(
291 &mut self,
292 atom: &Atom,
293 var_list_types: &mut HashMap<String, ScalarType>,
294 ) -> Result<Option<Vec<Atom>>> {
295 let pred = atom.predicate.as_str();
296 match pred {
297 "is_list" => {
298 require_arity(atom, 1)?;
299 let arg = self.normalize_list_arg(&atom.terms[0], None, var_list_types)?;
300 Ok(Some(vec![Atom {
301 predicate: len_pred().to_string(),
302 terms: vec![arg.term, Term::Anonymous],
303 }]))
304 }
305 "member" | "memberchk" => {
306 require_arity(atom, 2)?;
307 let list = self.normalize_list_arg(&atom.terms[1], None, var_list_types)?;
308 let value = self
309 .normalize_scalar_term(&atom.terms[0], Some(list.elem_type))?
310 .1;
311 Ok(Some(vec![Atom {
312 predicate: item_pred(list.elem_type),
313 terms: vec![list.term, Term::Anonymous, value],
314 }]))
315 }
316 "length" => {
317 require_arity(atom, 2)?;
318 let list = self.normalize_list_arg(&atom.terms[0], None, var_list_types)?;
319 let len = self
320 .normalize_scalar_term(&atom.terms[1], Some(ScalarType::U32))?
321 .1;
322 Ok(Some(vec![Atom {
323 predicate: len_pred().to_string(),
324 terms: vec![list.term, len],
325 }]))
326 }
327 "nth" => {
328 require_arity(atom, 3)?;
329 let idx = self
330 .normalize_scalar_term(&atom.terms[0], Some(ScalarType::U32))?
331 .1;
332 let list = self.normalize_list_arg(&atom.terms[1], None, var_list_types)?;
333 let value = self
334 .normalize_scalar_term(&atom.terms[2], Some(list.elem_type))?
335 .1;
336 Ok(Some(vec![Atom {
337 predicate: item_pred(list.elem_type),
338 terms: vec![list.term, idx, value],
339 }]))
340 }
341 "append" => {
342 require_arity(atom, 3)?;
343 if matches!(atom.terms[2], Term::List(_) | Term::Cons { .. })
344 && !matches!(atom.terms[0], Term::List(_) | Term::Cons { .. })
345 && !matches!(atom.terms[1], Term::List(_) | Term::Cons { .. })
346 {
347 return Err(list_error(
348 "unbounded append/3 generation is unsupported during list normalization; bind the first two lists to finite literals",
349 ));
350 }
351 let left = self.normalize_list_arg(&atom.terms[0], None, var_list_types)?;
352 let right =
353 self.normalize_list_arg(&atom.terms[1], Some(left.elem_type), var_list_types)?;
354 if left.elem_type != right.elem_type {
355 return Err(list_error(
356 "append/3 list arguments must have the same element type",
357 ));
358 }
359 let out =
360 self.normalize_list_arg(&atom.terms[2], Some(left.elem_type), var_list_types)?;
361 if !left.is_literal || !right.is_literal {
362 return Err(list_error(
363 "unbounded append/3 generation is unsupported during list normalization; bind the first two lists to finite literals",
364 ));
365 }
366 let left_id = left.id.expect("literal list has id");
367 let right_id = right.id.expect("literal list has id");
368 let out_id = match out.id {
369 Some(id) => id,
370 None => self.concat_lists(left_id, right_id)?,
371 };
372 self.operation_rows.insert((
373 ListOp::Append,
374 left.elem_type,
375 vec![left_id, right_id, out_id],
376 ));
377 if let Term::Variable(name) = &out.term {
378 var_list_types.insert(name.clone(), left.elem_type);
379 }
380 Ok(Some(vec![Atom {
381 predicate: append_pred(left.elem_type),
382 terms: vec![left.term, right.term, out.term],
383 }]))
384 }
385 "sort" | "msort" | "list_to_set" => {
386 require_arity(atom, 2)?;
387 let input = self.normalize_list_arg(&atom.terms[0], None, var_list_types)?;
388 if !input.is_literal {
389 return Err(list_error(
390 "sort/msort/list_to_set require a finite list literal during list normalization",
391 ));
392 }
393 let op = match pred {
394 "sort" => ListOp::Sort,
395 "msort" => ListOp::MSort,
396 "list_to_set" => ListOp::ListToSet,
397 _ => unreachable!(),
398 };
399 let out =
400 self.normalize_list_arg(&atom.terms[1], Some(input.elem_type), var_list_types)?;
401 let input_id = input.id.expect("literal list has id");
402 let out_id = match out.id {
403 Some(id) => id,
404 None => self.derived_ordered_list(input_id, op)?,
405 };
406 self.operation_rows
407 .insert((op, input.elem_type, vec![input_id, out_id]));
408 if let Term::Variable(name) = &out.term {
409 var_list_types.insert(name.clone(), input.elem_type);
410 }
411 let helper = match op {
412 ListOp::Sort => sort_pred(input.elem_type),
413 ListOp::MSort => msort_pred(input.elem_type),
414 ListOp::ListToSet => list_to_set_pred(input.elem_type),
415 ListOp::Append => unreachable!(),
416 };
417 Ok(Some(vec![Atom {
418 predicate: helper,
419 terms: vec![input.term, out.term],
420 }]))
421 }
422 _ => Ok(None),
423 }
424 }
425
426 fn normalize_value_term(
427 &mut self,
428 term: &Term,
429 expected_list_elem: Option<ScalarType>,
430 ) -> Result<Term> {
431 match term {
432 Term::List(_) => Ok(self
433 .normalize_list_arg(term, expected_list_elem, &mut HashMap::new())?
434 .term),
435 Term::Cons { head, tail } => {
436 let elem_type = expected_list_elem.ok_or_else(|| {
437 list_error("cons literal requires a declared list<T> context")
438 })?;
439 let mut items = vec![self.normalize_scalar_term(head, Some(elem_type))?.1];
440 let tail_arg =
441 self.normalize_list_arg(tail, Some(elem_type), &mut HashMap::new())?;
442 let tail_id = tail_arg
443 .id
444 .ok_or_else(|| list_error("cons literal tail must be a finite list literal"))?;
445 let tail_items = self.list_by_id(tail_id)?.items.clone();
446 items.extend(tail_items);
447 let id = self.register_list_terms(items, Some(elem_type))?;
448 Ok(Term::Integer(id as i64))
449 }
450 _ => Ok(term.clone()),
451 }
452 }
453
454 fn normalize_list_arg(
455 &mut self,
456 term: &Term,
457 expected_elem_type: Option<ScalarType>,
458 var_list_types: &mut HashMap<String, ScalarType>,
459 ) -> Result<ListArg> {
460 match term {
461 Term::List(items) => {
462 let id = self.register_list_terms(items.clone(), expected_elem_type)?;
463 let elem_type = self.list_by_id(id)?.elem_type;
464 Ok(ListArg {
465 term: Term::Integer(id as i64),
466 id: Some(id),
467 elem_type,
468 is_literal: true,
469 })
470 }
471 Term::Cons { .. } => {
472 let value = self.normalize_value_term(term, expected_elem_type)?;
473 let id = match value {
474 Term::Integer(id) if id >= 0 => id as u64,
475 _ => {
476 return Err(list_error(
477 "cons list argument must normalize to a finite list id",
478 ));
479 }
480 };
481 let elem_type = self.list_by_id(id)?.elem_type;
482 Ok(ListArg {
483 term: Term::Integer(id as i64),
484 id: Some(id),
485 elem_type,
486 is_literal: true,
487 })
488 }
489 Term::Integer(id) if *id > 0 => {
490 let elem_type = expected_elem_type
491 .or_else(|| self.list_by_id(*id as u64).ok().map(|list| list.elem_type))
492 .ok_or_else(|| list_error("list id requires a known list<T> type"))?;
493 Ok(ListArg {
494 term: term.clone(),
495 id: Some(*id as u64),
496 elem_type,
497 is_literal: false,
498 })
499 }
500 Term::Variable(name) => {
501 let elem_type = expected_elem_type
502 .or_else(|| var_list_types.get(name).copied())
503 .ok_or_else(|| {
504 list_error(
505 "list built-in requires a finite literal or known list<T> variable",
506 )
507 })?;
508 var_list_types.insert(name.clone(), elem_type);
509 Ok(ListArg {
510 term: term.clone(),
511 id: None,
512 elem_type,
513 is_literal: false,
514 })
515 }
516 Term::Anonymous => {
517 let elem_type = expected_elem_type.ok_or_else(|| {
518 list_error("anonymous list argument requires a known list<T> type")
519 })?;
520 Ok(ListArg {
521 term: term.clone(),
522 id: None,
523 elem_type,
524 is_literal: false,
525 })
526 }
527 _ => Err(list_error(
528 "expected finite list literal or known list<T> variable",
529 )),
530 }
531 }
532
533 fn normalize_scalar_term(
534 &mut self,
535 term: &Term,
536 expected: Option<ScalarType>,
537 ) -> Result<(ScalarType, Term)> {
538 match term {
539 Term::Variable(_) | Term::Anonymous => {
540 let typ = expected.ok_or_else(|| {
541 list_error("list element variable requires a known list<T> element type")
542 })?;
543 Ok((typ, term.clone()))
544 }
545 Term::Integer(i) => match expected {
546 Some(ScalarType::U32) => {
547 let value = u32::try_from(*i)
548 .map_err(|_| list_error("integer list element is out of range for u32"))?;
549 Ok((ScalarType::U32, Term::Integer(value as i64)))
550 }
551 Some(ScalarType::U64) => {
552 let value = u64::try_from(*i)
553 .map_err(|_| list_error("integer list element is out of range for u64"))?;
554 Ok((ScalarType::U64, Term::Integer(value as i64)))
555 }
556 Some(ScalarType::I32) => {
557 let value = i32::try_from(*i)
558 .map_err(|_| list_error("integer list element is out of range for i32"))?;
559 Ok((ScalarType::I32, Term::Integer(value as i64)))
560 }
561 Some(ScalarType::I64) | None if *i < 0 || *i > u32::MAX as i64 => {
562 Ok((ScalarType::I64, Term::Integer(*i)))
563 }
564 Some(ScalarType::I64) => Ok((ScalarType::I64, Term::Integer(*i))),
565 None => Ok((ScalarType::U32, Term::Integer(*i))),
566 Some(ScalarType::F32) => Ok((ScalarType::F32, Term::Float(*i as f64))),
567 Some(ScalarType::F64) => Ok((ScalarType::F64, Term::Float(*i as f64))),
568 Some(ScalarType::Bool) if *i == 0 || *i == 1 => {
569 Ok((ScalarType::Bool, Term::Integer(*i)))
570 }
571 Some(ScalarType::Bool) => Err(list_error("bool list elements must be 0 or 1")),
572 Some(ScalarType::Symbol) => {
573 Err(list_error("integer list element is not valid for symbol"))
574 }
575 },
576 Term::Float(f) => match expected {
577 Some(ScalarType::F32) => Ok((ScalarType::F32, Term::Float(*f))),
578 Some(ScalarType::F64) | None => Ok((ScalarType::F64, Term::Float(*f))),
579 Some(_) => Err(list_error(
580 "float list element is not valid for expected type",
581 )),
582 },
583 Term::String(s) => {
584 if expected.is_none() || expected == Some(ScalarType::Symbol) {
585 Ok((ScalarType::Symbol, Term::String(s.clone())))
586 } else {
587 Err(list_error(
588 "string list element is only valid for symbol lists",
589 ))
590 }
591 }
592 Term::Symbol(id) => {
593 if expected.is_none() || expected == Some(ScalarType::Symbol) {
594 Ok((ScalarType::Symbol, Term::Symbol(*id)))
595 } else {
596 Err(list_error(
597 "symbol list element is not valid for expected type",
598 ))
599 }
600 }
601 Term::List(_) | Term::Cons { .. } => {
602 let id = self
603 .normalize_list_arg(term, None, &mut HashMap::new())?
604 .id
605 .expect("literal list has id");
606 let typ = expected.unwrap_or(LIST_ID_TYPE);
607 if typ != LIST_ID_TYPE {
608 return Err(list_error(
609 "nested list element requires list<list<T>> or list<u64> context",
610 ));
611 }
612 Ok((LIST_ID_TYPE, Term::Integer(id as i64)))
613 }
614 Term::Compound { .. } | Term::PredRef(_) | Term::Aggregate(_) => Err(list_error(
615 "compound, predref, and aggregate list elements require meta-term normalization support",
616 )),
617 }
618 }
619
620 fn register_list_terms(
621 &mut self,
622 items: Vec<Term>,
623 expected_elem_type: Option<ScalarType>,
624 ) -> Result<u64> {
625 let mut elem_type = expected_elem_type;
626 let mut normalized = Vec::with_capacity(items.len());
627 let mut keys = Vec::with_capacity(items.len());
628
629 for item in &items {
630 let (item_type, term) = match self.normalize_scalar_term(item, elem_type) {
631 Ok(value) => value,
632 Err(err) if elem_type.is_some() => {
633 return Err(list_error(format!("heterogeneous list literal: {}", err)));
634 }
635 Err(err) => return Err(err),
636 };
637 if let Some(existing) = elem_type {
638 if existing != item_type {
639 return Err(list_error(
640 "heterogeneous list literals require a declared finite term type",
641 ));
642 }
643 } else {
644 elem_type = Some(item_type);
645 }
646 keys.push(literal_key(&term, item_type)?);
647 normalized.push(term);
648 }
649
650 let elem_type = elem_type.unwrap_or(LIST_ID_TYPE);
651 let key = (elem_type, keys.clone());
652 if let Some(id) = self.lists_by_key.get(&key) {
653 return Ok(*id);
654 }
655
656 let id = self.next_list_id;
657 self.next_list_id += 1;
658 self.lists_by_key.insert(key, id);
659 self.lists.push(ListDef {
660 id,
661 elem_type,
662 items: normalized,
663 keys,
664 });
665 Ok(id)
666 }
667
668 fn concat_lists(&mut self, left_id: u64, right_id: u64) -> Result<u64> {
669 let left = self.list_by_id(left_id)?.clone();
670 let right = self.list_by_id(right_id)?.clone();
671 if left.elem_type != right.elem_type {
672 return Err(list_error(
673 "append/3 list arguments must have the same element type",
674 ));
675 }
676 let mut items = left.items;
677 items.extend(right.items);
678 self.register_list_terms(items, Some(right.elem_type))
679 }
680
681 fn derived_ordered_list(&mut self, input_id: u64, op: ListOp) -> Result<u64> {
682 let input = self.list_by_id(input_id)?.clone();
683 let mut pairs: Vec<(LiteralKey, Term)> = input.keys.into_iter().zip(input.items).collect();
684 pairs.sort_by(|(a, _), (b, _)| format!("{a:?}").cmp(&format!("{b:?}")));
685 if matches!(op, ListOp::Sort | ListOp::ListToSet) {
686 pairs.dedup_by(|(a, _), (b, _)| a == b);
687 }
688 let items = pairs.into_iter().map(|(_, term)| term).collect();
689 self.register_list_terms(items, Some(input.elem_type))
690 }
691
692 fn append_helper_declarations_and_facts(&mut self, program: &mut Program) -> Result<()> {
693 self.ensure_tail_lists()?;
694
695 let mut helper_rules = Vec::new();
696 for list in self.lists.clone() {
697 self.helper_preds.insert(len_pred().to_string());
698 helper_rules.push(fact(
699 len_pred(),
700 vec![
701 Term::Integer(list.id as i64),
702 Term::Integer(list.items.len() as i64),
703 ],
704 ));
705
706 let item_name = item_pred(list.elem_type);
707 self.helper_preds.insert(item_name.clone());
708 for (idx, item) in list.items.iter().enumerate() {
709 helper_rules.push(fact(
710 &item_name,
711 vec![
712 Term::Integer(list.id as i64),
713 Term::Integer(idx as i64),
714 item.clone(),
715 ],
716 ));
717 }
718
719 if let Some((head, tail_id)) = self.cons_fact_parts(list.id)? {
720 let cons_name = cons_pred(list.elem_type);
721 self.helper_preds.insert(cons_name.clone());
722 helper_rules.push(fact(
723 &cons_name,
724 vec![
725 Term::Integer(list.id as i64),
726 head,
727 Term::Integer(tail_id as i64),
728 ],
729 ));
730 }
731 }
732
733 for (op, elem_type, ids) in self.operation_rows.clone() {
734 let pred = match op {
735 ListOp::Append => append_pred(elem_type),
736 ListOp::Sort => sort_pred(elem_type),
737 ListOp::MSort => msort_pred(elem_type),
738 ListOp::ListToSet => list_to_set_pred(elem_type),
739 };
740 self.helper_preds.insert(pred.clone());
741 helper_rules.push(fact(
742 &pred,
743 ids.into_iter().map(|id| Term::Integer(id as i64)).collect(),
744 ));
745 }
746
747 self.append_helper_pred_decls(program);
748 program.rules.extend(helper_rules);
749 Ok(())
750 }
751
752 fn ensure_tail_lists(&mut self) -> Result<()> {
753 loop {
754 let before = self.lists.len();
755 for list in self.lists.clone() {
756 if list.items.is_empty() {
757 continue;
758 }
759 self.register_list_terms(list.items[1..].to_vec(), Some(list.elem_type))?;
760 }
761 if self.lists.len() == before {
762 break;
763 }
764 }
765 Ok(())
766 }
767
768 fn cons_fact_parts(&self, list_id: u64) -> Result<Option<(Term, u64)>> {
769 let list = self.list_by_id(list_id)?;
770 let Some(head) = list.items.first() else {
771 return Ok(None);
772 };
773 let tail_key = (list.elem_type, list.keys[1..].to_vec());
774 let tail_id = self
775 .lists_by_key
776 .get(&tail_key)
777 .copied()
778 .ok_or_else(|| list_error("missing normalized list tail"))?;
779 Ok(Some((head.clone(), tail_id)))
780 }
781
782 fn append_helper_pred_decls(&self, program: &mut Program) {
783 let mut existing: HashSet<String> = program
784 .predicates
785 .iter()
786 .map(|pred| pred.name.clone())
787 .collect();
788
789 for pred in &self.helper_preds {
790 if existing.contains(pred) {
791 continue;
792 }
793 let columns = helper_columns(pred);
794 program.predicates.push(PredDecl {
795 name: pred.clone(),
796 types: columns.iter().map(|col| col.typ.clone()).collect(),
797 columns,
798 is_private: true,
799 });
800 existing.insert(pred.clone());
801 }
802 }
803
804 fn list_by_id(&self, id: u64) -> Result<&ListDef> {
805 self.lists
806 .iter()
807 .find(|list| list.id == id)
808 .ok_or_else(|| list_error("unknown normalized list id"))
809 }
810
811 fn fresh_var(&mut self, prefix: &str) -> String {
812 let var = format!(
813 "__XLOG_{}_{}",
814 prefix.to_ascii_uppercase(),
815 self.fresh_counter
816 );
817 self.fresh_counter += 1;
818 var
819 }
820
821 fn storage_type_for_type_ref(
822 typ: &TypeRef,
823 domains: &HashMap<String, ScalarType>,
824 ) -> Result<ScalarType> {
825 match typ {
826 TypeRef::Scalar(ty) => Ok(*ty),
827 TypeRef::Domain(name) => domains
828 .get(name)
829 .copied()
830 .ok_or_else(|| list_error(format!("unknown domain alias '{}' in list<T>", name))),
831 TypeRef::List(_) | TypeRef::Term | TypeRef::Compound | TypeRef::PredRef => {
832 Ok(LIST_ID_TYPE)
833 }
834 }
835 }
836}
837
838fn fact(pred: &str, terms: Vec<Term>) -> Rule {
839 Rule {
840 head: Atom {
841 predicate: pred.to_string(),
842 terms,
843 },
844 body: vec![],
845 }
846}
847
848fn require_arity(atom: &Atom, expected: usize) -> Result<()> {
849 if atom.terms.len() == expected {
850 Ok(())
851 } else {
852 Err(list_error(format!(
853 "{} expects {} arguments, got {}",
854 atom.predicate,
855 expected,
856 atom.terms.len()
857 )))
858 }
859}
860
861fn literal_key(term: &Term, typ: ScalarType) -> Result<LiteralKey> {
862 match (typ, term) {
863 (ScalarType::U32, Term::Integer(v)) => Ok(LiteralKey::U32(*v as u32)),
864 (ScalarType::U64, Term::Integer(v)) => Ok(LiteralKey::U64(*v as u64)),
865 (ScalarType::I32, Term::Integer(v)) => Ok(LiteralKey::I32(*v as i32)),
866 (ScalarType::I64, Term::Integer(v)) => Ok(LiteralKey::I64(*v)),
867 (ScalarType::F32, Term::Float(v)) => Ok(LiteralKey::F32((*v as f32).to_bits())),
868 (ScalarType::F64, Term::Float(v)) => Ok(LiteralKey::F64(v.to_bits())),
869 (ScalarType::Bool, Term::Integer(v)) => Ok(LiteralKey::Bool(*v != 0)),
870 (ScalarType::Symbol, Term::String(s)) => Ok(LiteralKey::Symbol(s.clone())),
871 (ScalarType::Symbol, Term::Symbol(id)) => {
872 Ok(LiteralKey::Symbol(xlog_core::symbol::resolve(*id)))
873 }
874 _ => Err(list_error(
875 "list literal element did not normalize to expected scalar type",
876 )),
877 }
878}
879
880fn helper_columns(pred: &str) -> Vec<PredColumn> {
881 let scalar = |name: &str, typ| PredColumn {
882 name: Some(name.to_string()),
883 typ: TypeRef::Scalar(typ),
884 };
885 if pred == len_pred() {
886 return vec![
887 scalar("list_id", LIST_ID_TYPE),
888 scalar("len", ScalarType::U32),
889 ];
890 }
891 if let Some(elem_type) = helper_elem_type(pred, "__xlog_list_item_") {
892 return vec![
893 scalar("list_id", LIST_ID_TYPE),
894 scalar("idx", ScalarType::U32),
895 scalar("value", elem_type),
896 ];
897 }
898 if let Some(elem_type) = helper_elem_type(pred, "__xlog_list_cons_") {
899 return vec![
900 scalar("list_id", LIST_ID_TYPE),
901 scalar("head", elem_type),
902 scalar("tail_id", LIST_ID_TYPE),
903 ];
904 }
905 if helper_elem_type(pred, "__xlog_list_append_").is_some() {
906 return vec![
907 scalar("left_id", LIST_ID_TYPE),
908 scalar("right_id", LIST_ID_TYPE),
909 scalar("out_id", LIST_ID_TYPE),
910 ];
911 }
912 vec![
913 scalar("input_id", LIST_ID_TYPE),
914 scalar("out_id", LIST_ID_TYPE),
915 ]
916}
917
918fn helper_elem_type(pred: &str, prefix: &str) -> Option<ScalarType> {
919 pred.strip_prefix(prefix).and_then(scalar_type_from_suffix)
920}
921
922fn scalar_type_from_suffix(s: &str) -> Option<ScalarType> {
923 match s {
924 "u32" => Some(ScalarType::U32),
925 "u64" => Some(ScalarType::U64),
926 "i32" => Some(ScalarType::I32),
927 "i64" => Some(ScalarType::I64),
928 "f32" => Some(ScalarType::F32),
929 "f64" => Some(ScalarType::F64),
930 "bool" => Some(ScalarType::Bool),
931 "symbol" => Some(ScalarType::Symbol),
932 _ => None,
933 }
934}
935
936fn scalar_suffix(typ: ScalarType) -> &'static str {
937 match typ {
938 ScalarType::U32 => "u32",
939 ScalarType::U64 => "u64",
940 ScalarType::I32 => "i32",
941 ScalarType::I64 => "i64",
942 ScalarType::F32 => "f32",
943 ScalarType::F64 => "f64",
944 ScalarType::Bool => "bool",
945 ScalarType::Symbol => "symbol",
946 }
947}
948
949fn len_pred() -> &'static str {
950 "__xlog_list_len"
951}
952
953fn item_pred(typ: ScalarType) -> String {
954 format!("__xlog_list_item_{}", scalar_suffix(typ))
955}
956
957fn cons_pred(typ: ScalarType) -> String {
958 format!("__xlog_list_cons_{}", scalar_suffix(typ))
959}
960
961fn append_pred(typ: ScalarType) -> String {
962 format!("__xlog_list_append_{}", scalar_suffix(typ))
963}
964
965fn sort_pred(typ: ScalarType) -> String {
966 format!("__xlog_list_sort_{}", scalar_suffix(typ))
967}
968
969fn msort_pred(typ: ScalarType) -> String {
970 format!("__xlog_list_msort_{}", scalar_suffix(typ))
971}
972
973fn list_to_set_pred(typ: ScalarType) -> String {
974 format!("__xlog_list_to_set_{}", scalar_suffix(typ))
975}
976
977fn is_reserved_pair_helper(atom: &Atom) -> bool {
978 matches!(
979 (atom.predicate.as_str(), atom.terms.len()),
980 ("pair", 3) | ("pairs", 2) | ("zip", 3) | ("zip_with_index", 2) | ("enumerate", 2)
981 )
982}
983
984fn list_error(message: impl Into<String>) -> XlogError {
985 XlogError::Compilation(format!("list normalization error: {}", message.into()))
986}