1use anyhow::{Result, anyhow};
61use ast::Arena;
62use fxhash::FxHashSet;
63use mangle_analysis::{LoweringContext, Planner, Program, StratifiedProgram, rewrite_unit};
64use mangle_ast as ast;
65use mangle_codegen::{Codegen, WasmImportsBackend};
66use mangle_interpreter::{Interpreter, Store};
67use mangle_ir::{Inst, InstId, Ir};
68use mangle_parse::Parser;
69
70pub fn compile<'a>(source: &str, arena: &'a Arena) -> Result<(Ir, StratifiedProgram<'a>)> {
81 compile_units(&[source], arena)
82}
83
84pub fn compile_units<'a>(sources: &[&str], arena: &'a Arena) -> Result<(Ir, StratifiedProgram<'a>)> {
93 let mut all_decls: Vec<&'a ast::Decl<'a>> = Vec::new();
95 let mut all_clauses: Vec<&'a ast::Clause<'a>> = Vec::new();
96
97 for (i, source) in sources.iter().enumerate() {
98 let label = format!("source_{}", i);
99 let mut parser = Parser::new(arena, source.as_bytes(), arena.alloc_str(&label));
100 parser.next_token().map_err(|e| anyhow!(e))?;
101 let unit = parser.parse_unit()?;
102
103 let rewritten = rewrite_unit(arena, unit);
104 all_decls.extend_from_slice(rewritten.decls);
105 all_clauses.extend_from_slice(rewritten.clauses);
106 }
107
108 let merged_unit = ast::Unit {
110 decls: arena.alloc_slice_copy(&all_decls),
111 clauses: arena.alloc_slice_copy(&all_clauses),
112 };
113 let unit = &merged_unit;
114
115 let mut program = Program::new(arena);
116 let mut all_preds = FxHashSet::default();
117 let mut idb_preds = FxHashSet::default();
118
119 for clause in unit.clauses {
120 program.add_clause(arena, clause);
121 idb_preds.insert(clause.head.sym);
122 all_preds.insert(clause.head.sym);
123 for premise in clause.premises {
124 match premise {
125 ast::Term::Atom(atom) => { all_preds.insert(atom.sym); }
126 ast::Term::NegAtom(atom) => { all_preds.insert(atom.sym); }
127 ast::Term::TemporalAtom(atom, _) => { all_preds.insert(atom.sym); }
128 _ => {}
129 }
130 }
131 }
132
133 for pred in all_preds {
134 if !idb_preds.contains(&pred) {
135 program.ext_preds.push(pred);
136 }
137 }
138
139 let stratified = program.stratify().map_err(|e| anyhow!(e))?;
140
141 let ctx = LoweringContext::new(arena);
142 let ir = ctx.lower_unit(unit);
143
144 Ok((ir, stratified))
145}
146
147pub fn compile_to_wasm(
153 ir: &mut Ir,
154 stratified: &StratifiedProgram,
155) -> mangle_codegen::CompiledModule {
156 let mut codegen = Codegen::new_with_stratified(ir, stratified, WasmImportsBackend);
157 codegen.generate()
158}
159
160pub fn execute<'a>(
170 ir: &'a mut Ir,
171 stratified: &StratifiedProgram<'a>,
172 store: Box<dyn Store + 'a>,
173) -> Result<Interpreter<'a>> {
174 let arena = stratified.arena();
175
176 let mut strata_plans = Vec::new();
178
179 for stratum in stratified.strata() {
180 let mut stratum_pred_names = FxHashSet::default();
181 for pred in &stratum {
182 if let Some(name) = arena.predicate_name(*pred) {
183 stratum_pred_names.insert(name);
184 }
185 }
186
187 let mut rule_ids = Vec::new();
189 for (i, inst) in ir.insts.iter().enumerate() {
190 if let Inst::Rule { head, .. } = inst
191 && let Inst::Atom { predicate, .. } = ir.get(*head)
192 {
193 let head_name = ir.resolve_name(*predicate);
194 if stratum_pred_names.contains(head_name) {
195 rule_ids.push(InstId::new(i));
196 }
197 }
198 }
199
200 if rule_ids.is_empty() {
201 strata_plans.push(None);
202 continue;
203 }
204
205 let mut is_recursive = false;
207 for &rule_id in &rule_ids {
208 if let Inst::Rule { premises, .. } = ir.get(rule_id) {
209 for &premise in premises {
210 if let Inst::Atom { predicate, .. } = ir.get(premise) {
211 let pred_name = ir.resolve_name(*predicate);
212 if stratum_pred_names.contains(pred_name) {
213 is_recursive = true;
214 break;
215 }
216 }
217 }
218 }
219 if is_recursive {
220 break;
221 }
222 }
223
224 if !is_recursive {
225 let mut ops = Vec::new();
226 for rule_id in rule_ids {
227 let planner = Planner::new(ir);
228 ops.push(planner.plan_rule(rule_id)?);
229 }
230 strata_plans.push(Some(StratumPlan::NonRecursive(ops)));
231 } else {
232 let mut initial_ops = Vec::new();
233 for &rule_id in &rule_ids {
234 let planner = Planner::new(ir);
235 initial_ops.push(planner.plan_rule(rule_id)?);
236 }
237
238 let mut delta_plans = Vec::new();
239 for &rule_id in &rule_ids {
240 let premises = if let Inst::Rule { premises, .. } = ir.get(rule_id) {
241 premises.clone()
242 } else {
243 continue;
244 };
245
246 for &premise in &premises {
247 let (predicate, pred_name) =
248 if let Inst::Atom { predicate, .. } = ir.get(premise) {
249 (*predicate, ir.resolve_name(*predicate).to_string())
250 } else {
251 continue;
252 };
253
254 if stratum_pred_names.contains(pred_name.as_str()) {
255 let planner = Planner::new(ir).with_delta(predicate);
256 delta_plans.push(planner.plan_rule(rule_id)?);
257 }
258 }
259 }
260 strata_plans.push(Some(StratumPlan::Recursive {
261 initial_ops,
262 delta_plans,
263 }));
264 }
265 }
266
267 let temporal_pred_names: Vec<String> = ir
269 .temporal_predicates
270 .iter()
271 .map(|name_id| ir.resolve_name(*name_id).to_string())
272 .collect();
273
274 let mut interpreter = Interpreter::new(ir, store);
276
277 for pred in stratified.extensional_preds() {
279 if let Some(name) = arena.predicate_name(pred) {
280 interpreter.store_mut().create_relation(name);
281 }
282 }
283
284 for plan in strata_plans {
285 match plan {
286 Some(StratumPlan::NonRecursive(ops)) => {
287 for op in ops {
288 interpreter.execute(&op)?;
289 }
290 }
291 Some(StratumPlan::Recursive {
292 initial_ops,
293 delta_plans,
294 }) => {
295 for op in initial_ops {
296 interpreter.execute(&op)?;
297 }
298 interpreter.store_mut().merge_deltas();
299
300 loop {
301 let mut changes = 0;
302 for op in &delta_plans {
303 changes += interpreter.execute(op)?;
304 }
305 if changes == 0 {
306 break;
307 }
308 interpreter.store_mut().merge_deltas();
309 }
310 for name in &temporal_pred_names {
312 interpreter.store_mut().coalesce_temporal(name);
313 }
314 }
315 None => {}
316 }
317 interpreter.store_mut().merge_deltas();
318 for name in &temporal_pred_names {
319 interpreter.store_mut().coalesce_temporal(name);
320 }
321 }
322
323 Ok(interpreter)
324}
325
326enum StratumPlan {
327 NonRecursive(Vec<mangle_ir::physical::Op>),
328 Recursive {
329 initial_ops: Vec<mangle_ir::physical::Op>,
330 delta_plans: Vec<mangle_ir::physical::Op>,
331 },
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use mangle_interpreter::{MemStore, Value};
338
339 #[test]
340 fn test_driver_e2e() -> Result<()> {
341 let arena = Arena::new_with_global_interner();
342 let source = r#"
343 p(1).
344 p(2).
345 q(X) :- p(X).
346 "#;
347
348 let (mut ir, stratified) = compile(source, &arena)?;
349 let store = Box::new(MemStore::new());
350 let interpreter = execute(&mut ir, &stratified, store)?;
351
352 let facts: Vec<_> = interpreter
354 .store()
355 .scan("q")
356 .expect("relation q not found")
357 .collect();
358 assert!(!facts.is_empty(), "relation q not found");
359
360 let mut values: Vec<i64> = facts
361 .iter()
362 .map(|t| match t[0] {
363 Value::Number(n) => n,
364 _ => panic!("expected number"),
365 })
366 .collect();
367 values.sort();
368
369 assert_eq!(values, vec![1, 2]);
370
371 Ok(())
372 }
373
374 #[test]
375 fn test_driver_e2e_with_package() -> Result<()> {
376 let arena = Arena::new_with_global_interner();
377 let source = r#"
378 Package pkg!
379 p(1).
380 q(X) :- p(X).
381 "#;
382
383 let (mut ir, stratified) = compile(source, &arena)?;
384 let store = Box::new(MemStore::new());
385 let interpreter = execute(&mut ir, &stratified, store)?;
386
387 let facts: Vec<_> = interpreter
389 .store()
390 .scan("pkg.q")
391 .expect("relation pkg.q not found")
392 .collect();
393 assert!(!facts.is_empty(), "relation pkg.q not found");
394
395 let values: Vec<i64> = facts
396 .iter()
397 .map(|t| match t[0] {
398 Value::Number(n) => n,
399 _ => panic!("expected number"),
400 })
401 .collect();
402 assert_eq!(values, vec![1]);
403
404 Ok(())
405 }
406
407 #[test]
408 fn test_driver_let_transform() -> Result<()> {
409 let arena = Arena::new_with_global_interner();
410 let source = r#"
411 p(1).
412 p(2).
413 q(Y) :- p(X) |> let Y = fn:plus(X, 10).
414 "#;
415
416 let (mut ir, stratified) = compile(source, &arena)?;
417 let store = Box::new(MemStore::new());
418 let interpreter = execute(&mut ir, &stratified, store)?;
419
420 let facts: Vec<_> = interpreter
421 .store()
422 .scan("q")
423 .expect("relation q not found")
424 .collect();
425 let mut values: Vec<i64> = facts
426 .iter()
427 .map(|t| match t[0] {
428 Value::Number(n) => n,
429 _ => panic!("expected number"),
430 })
431 .collect();
432 values.sort();
433
434 assert_eq!(values, vec![11, 12]);
435 Ok(())
436 }
437
438 #[test]
439 fn test_driver_aggregation() -> Result<()> {
440 let arena = Arena::new_with_global_interner();
441 let source = r#"
442 p(1, 10).
443 p(1, 20).
444 p(2, 30).
445 q(K, S) :- p(K, V) |> do fn:group_by(K); let S = fn:sum(V).
446 "#;
447
448 let (mut ir, stratified) = compile(source, &arena)?;
449 let store = Box::new(MemStore::new());
450 let interpreter = execute(&mut ir, &stratified, store)?;
451
452 let facts: Vec<_> = interpreter
453 .store()
454 .scan("q")
455 .expect("relation q not found")
456 .collect();
457 let mut results: Vec<(i64, i64)> = facts
458 .iter()
459 .map(|t| {
460 if let (Value::Number(k), Value::Number(s)) = (&t[0], &t[1]) {
461 (*k, *s)
462 } else {
463 panic!("expected numbers");
464 }
465 })
466 .collect();
467 results.sort();
468
469 assert_eq!(results, vec![(1, 30), (2, 30)]);
470 Ok(())
471 }
472
473 #[test]
474 fn test_driver_aggregation_count() -> Result<()> {
475 let arena = Arena::new_with_global_interner();
476 let source = r#"
477 p(1, 10).
478 p(1, 20).
479 p(2, 30).
480 q(K, C) :- p(K, V) |> do fn:group_by(K); let C = fn:count(V).
481 "#;
482
483 let (mut ir, stratified) = compile(source, &arena)?;
484 let store = Box::new(MemStore::new());
485 let interpreter = execute(&mut ir, &stratified, store)?;
486
487 let facts: Vec<_> = interpreter
488 .store()
489 .scan("q")
490 .expect("relation q not found")
491 .collect();
492 let mut results: Vec<(i64, i64)> = facts
493 .iter()
494 .map(|t| {
495 if let (Value::Number(k), Value::Number(c)) = (&t[0], &t[1]) {
496 (*k, *c)
497 } else {
498 panic!("expected numbers");
499 }
500 })
501 .collect();
502 results.sort();
503
504 assert_eq!(results, vec![(1, 2), (2, 1)]);
505 Ok(())
506 }
507
508 #[test]
509 fn test_driver_reachability() -> Result<()> {
510 let arena = Arena::new_with_global_interner();
511 let source = r#"
512 edge(1, 2).
513 edge(2, 3).
514 edge(3, 4).
515 edge(4, 5).
516 reachable(X, Y) :- edge(X, Y).
517 reachable(X, Z) :- reachable(X, Y), edge(Y, Z).
518 "#;
519
520 let (mut ir, stratified) = compile(source, &arena)?;
521 let store = Box::new(MemStore::new());
522 let interpreter = execute(&mut ir, &stratified, store)?;
523
524 let facts: Vec<_> = interpreter
525 .store()
526 .scan("reachable")
527 .expect("reachable relation not found")
528 .collect();
529 assert_eq!(facts.len(), 10); let mut reachable_from_1: Vec<i64> = facts
532 .iter()
533 .filter(|t| t[0] == Value::Number(1))
534 .map(|t| match t[1] {
535 Value::Number(n) => n,
536 _ => panic!("expected number"),
537 })
538 .collect();
539 reachable_from_1.sort();
540 assert_eq!(reachable_from_1, vec![2, 3, 4, 5]);
541
542 Ok(())
543 }
544
545 #[test]
546 fn test_name_constants() -> Result<()> {
547 let arena = Arena::new_with_global_interner();
548 let source = r#"
549 role(/role/admin).
550 role(/role/user).
551 role(/role/application).
552 "#;
553
554 let (mut ir, stratified) = compile(source, &arena)?;
555 let store = Box::new(MemStore::new());
556 let interpreter = execute(&mut ir, &stratified, store)?;
557
558 let facts: Vec<_> = interpreter
559 .store()
560 .scan("role")
561 .expect("relation role not found")
562 .collect();
563 assert_eq!(facts.len(), 3);
564
565 let mut names: Vec<String> = facts
566 .iter()
567 .map(|t| match &t[0] {
568 Value::String(s) => s.clone(),
569 _ => panic!("expected string"),
570 })
571 .collect();
572 names.sort();
573 assert_eq!(
574 names,
575 vec!["/role/admin", "/role/application", "/role/user"]
576 );
577
578 Ok(())
579 }
580
581 #[test]
582 fn test_inequality() -> Result<()> {
583 let arena = Arena::new_with_global_interner();
584 let source = r#"
588 role("admin").
589 role("user").
590 role("application").
591 non_app_role(R) :- role(R), R != "application".
592 "#;
593
594 let (mut ir, stratified) = compile(source, &arena)?;
595 let store = Box::new(MemStore::new());
596 let interpreter = execute(&mut ir, &stratified, store)?;
597
598 let facts: Vec<_> = interpreter
599 .store()
600 .scan("non_app_role")
601 .expect("relation non_app_role not found")
602 .collect();
603 assert_eq!(facts.len(), 2);
604
605 let mut names: Vec<String> = facts
606 .iter()
607 .map(|t| match &t[0] {
608 Value::String(s) => s.clone(),
609 _ => panic!("expected string"),
610 })
611 .collect();
612 names.sort();
613 assert_eq!(names, vec!["admin", "user"]);
614
615 Ok(())
616 }
617
618 #[test]
619 fn test_negation() -> Result<()> {
620 let arena = Arena::new_with_global_interner();
621 let source = r#"
622 service("web").
623 service("api").
624 service("db").
625 has_dep("web").
626 has_dep("api").
627 no_dep(S) :- service(S), !has_dep(S).
628 "#;
629
630 let (mut ir, stratified) = compile(source, &arena)?;
631 let store = Box::new(MemStore::new());
632 let interpreter = execute(&mut ir, &stratified, store)?;
633
634 let facts: Vec<_> = interpreter
635 .store()
636 .scan("no_dep")
637 .expect("relation no_dep not found")
638 .collect();
639 assert_eq!(facts.len(), 1);
640 assert_eq!(facts[0][0], Value::String("db".to_string()));
641
642 Ok(())
643 }
644
645 #[test]
646 fn test_combined_name_ineq_negation() -> Result<()> {
647 let arena = Arena::new_with_global_interner();
649 let source = r#"
650 container("web", /status/running).
651 container("api", /status/running).
652 container("db", /status/stopped).
653 depends_on("web", "db").
654 depends_on("api", "db").
655
656 running(Name) :- container(Name, /status/running).
657 stopped(Name) :- container(Name, /status/stopped).
658 has_running_dep(Name) :- depends_on(Name, Dep), running(Dep).
659 needs_attention(Name) :- depends_on(Name, Dep), stopped(Dep).
660 independent(Name) :- running(Name), !has_running_dep(Name).
661 "#;
662
663 let (mut ir, stratified) = compile(source, &arena)?;
664 let store = Box::new(MemStore::new());
665 let interpreter = execute(&mut ir, &stratified, store)?;
666
667 let running: Vec<_> = interpreter
669 .store()
670 .scan("running")
671 .expect("relation running not found")
672 .collect();
673 assert_eq!(running.len(), 2);
674
675 let stopped: Vec<_> = interpreter
677 .store()
678 .scan("stopped")
679 .expect("relation stopped not found")
680 .collect();
681 assert_eq!(stopped.len(), 1);
682 assert_eq!(stopped[0][0], Value::String("db".to_string()));
683
684 let needs_attention: Vec<_> = interpreter
686 .store()
687 .scan("needs_attention")
688 .expect("relation needs_attention not found")
689 .collect();
690 assert_eq!(needs_attention.len(), 2);
691
692 let independent: Vec<_> = interpreter
695 .store()
696 .scan("independent")
697 .expect("relation independent not found")
698 .collect();
699 assert_eq!(independent.len(), 2);
700
701 Ok(())
702 }
703
704 #[test]
705 fn test_join_with_constants_in_second_atom() -> Result<()> {
706 let arena = Arena::new_with_global_interner();
710 let source = r#"
711 p("a", "x").
712 q("a", "y").
713 test(E) :- p(E, "x"), q(E, "y").
714 "#;
715
716 let (mut ir, stratified) = compile(source, &arena)?;
717 let store = Box::new(MemStore::new());
718 let interpreter = execute(&mut ir, &stratified, store)?;
719
720 let facts: Vec<_> = interpreter
721 .store()
722 .scan("test")
723 .expect("relation test not found")
724 .collect();
725
726 assert_eq!(facts.len(), 1, "expected 1 result, got {:?}", facts);
727 assert_eq!(facts[0][0], Value::String("a".to_string()));
728
729 Ok(())
730 }
731
732 #[test]
733 fn test_join_constant_only_in_second_atom() -> Result<()> {
734 let arena = Arena::new_with_global_interner();
736 let source = r#"
737 p("a", "x").
738 q("a", "y").
739 test(E, V) :- p(E, V), q(E, "y").
740 "#;
741
742 let (mut ir, stratified) = compile(source, &arena)?;
743 let store = Box::new(MemStore::new());
744 let interpreter = execute(&mut ir, &stratified, store)?;
745
746 let facts: Vec<_> = interpreter
747 .store()
748 .scan("test")
749 .expect("relation test not found")
750 .collect();
751
752 assert_eq!(facts.len(), 1, "expected 1 result, got {:?}", facts);
753 assert_eq!(facts[0][0], Value::String("a".to_string()));
754 assert_eq!(facts[0][1], Value::String("x".to_string()));
755
756 Ok(())
757 }
758
759 #[test]
760 fn test_compile_units_package_use() -> Result<()> {
761 let arena = Arena::new_with_global_interner();
762
763 let schema = r#"
764 Package config_schema !
765 Decl server_port(Port).
766 Decl programs_dir(Path).
767 "#;
768
769 let config = r#"
770 Use config_schema !
771 config_schema.server_port(8090).
772 config_schema.programs_dir("/programs").
773 "#;
774
775 let (mut ir, stratified) = compile_units(&[schema, config], &arena)?;
776 let store = Box::new(MemStore::new());
777 let interpreter = execute(&mut ir, &stratified, store)?;
778
779 let port_facts: Vec<_> = interpreter
781 .store()
782 .scan("config_schema.server_port")
783 .expect("relation config_schema.server_port not found")
784 .collect();
785 assert_eq!(port_facts.len(), 1);
786 assert_eq!(port_facts[0][0], Value::Number(8090));
787
788 let dir_facts: Vec<_> = interpreter
789 .store()
790 .scan("config_schema.programs_dir")
791 .expect("relation config_schema.programs_dir not found")
792 .collect();
793 assert_eq!(dir_facts.len(), 1);
794 assert_eq!(dir_facts[0][0], Value::String("/programs".to_string()));
795
796 Ok(())
797 }
798
799 #[test]
800 fn test_less_than_comparison() -> Result<()> {
801 let arena = Arena::new_with_global_interner();
802 let source = r#"
803 num(8.1). num(42). num(99.5).
804 big(X) :- num(X), 85 < X .
805 "#;
806
807 let (mut ir, stratified) = compile(source, &arena)?;
808 let store = Box::new(MemStore::new());
809 let interpreter = execute(&mut ir, &stratified, store)?;
810
811 let facts: Vec<_> = interpreter
812 .store()
813 .scan("big")
814 .expect("relation big not found")
815 .collect();
816 assert_eq!(facts.len(), 1, "expected 1 result, got {:?}", facts);
817 assert_eq!(facts[0][0], Value::Float(99.5));
818
819 Ok(())
820 }
821
822 #[test]
823 fn test_less_equal_comparison() -> Result<()> {
824 let arena = Arena::new_with_global_interner();
825 let source = r#"
826 num(10). num(50). num(85). num(99).
827 up_to_85(X) :- num(X), X <= 85 .
828 "#;
829
830 let (mut ir, stratified) = compile(source, &arena)?;
831 let store = Box::new(MemStore::new());
832 let interpreter = execute(&mut ir, &stratified, store)?;
833
834 let facts: Vec<_> = interpreter
835 .store()
836 .scan("up_to_85")
837 .expect("relation up_to_85 not found")
838 .collect();
839 assert_eq!(facts.len(), 3, "expected 3 results, got {:?}", facts);
840
841 Ok(())
842 }
843
844 #[test]
845 fn test_compile_to_wasm() -> Result<()> {
846 let arena = Arena::new_with_global_interner();
847 let source = r#"
848 p(1).
849 q(X) :- p(X).
850 "#;
851
852 let (mut ir, stratified) = compile(source, &arena)?;
853 let compiled = compile_to_wasm(&mut ir, &stratified);
854
855 assert!(!compiled.wasm.is_empty());
857 assert_eq!(&compiled.wasm[0..4], b"\0asm"); Ok(())
860 }
861
862 #[test]
863 fn test_greater_than_comparison() -> Result<()> {
864 let arena = Arena::new_with_global_interner();
865 let source = r#"
866 num(10). num(50). num(85). num(99).
867 big(X) :- num(X), X > 50 .
868 "#;
869
870 let (mut ir, stratified) = compile(source, &arena)?;
871 let store = Box::new(MemStore::new());
872 let interpreter = execute(&mut ir, &stratified, store)?;
873
874 let facts: Vec<_> = interpreter
875 .store()
876 .scan("big")
877 .expect("relation big not found")
878 .collect();
879 assert_eq!(facts.len(), 2, "expected 2 results, got {:?}", facts);
880
881 let mut values: Vec<i64> = facts
882 .iter()
883 .map(|t| match t[0] {
884 Value::Number(n) => n,
885 _ => panic!("expected number"),
886 })
887 .collect();
888 values.sort();
889 assert_eq!(values, vec![85, 99]);
890
891 Ok(())
892 }
893
894 #[test]
895 fn test_greater_equal_comparison() -> Result<()> {
896 let arena = Arena::new_with_global_interner();
897 let source = r#"
898 num(10). num(50). num(85). num(99).
899 at_least_85(X) :- num(X), X >= 85 .
900 "#;
901
902 let (mut ir, stratified) = compile(source, &arena)?;
903 let store = Box::new(MemStore::new());
904 let interpreter = execute(&mut ir, &stratified, store)?;
905
906 let facts: Vec<_> = interpreter
907 .store()
908 .scan("at_least_85")
909 .expect("relation at_least_85 not found")
910 .collect();
911 assert_eq!(facts.len(), 2, "expected 2 results, got {:?}", facts);
912
913 Ok(())
914 }
915
916 #[test]
917 fn test_variadic_arithmetic() -> Result<()> {
918 let arena = Arena::new_with_global_interner();
919 let source = r#"
920 p(1).
921 p(2).
922 q(Y) :- p(X) |> let Y = fn:plus(X, 10, 100).
923 "#;
924
925 let (mut ir, stratified) = compile(source, &arena)?;
926 let store = Box::new(MemStore::new());
927 let interpreter = execute(&mut ir, &stratified, store)?;
928
929 let facts: Vec<_> = interpreter
930 .store()
931 .scan("q")
932 .expect("relation q not found")
933 .collect();
934 let mut values: Vec<i64> = facts
935 .iter()
936 .map(|t| match t[0] {
937 Value::Number(n) => n,
938 _ => panic!("expected number"),
939 })
940 .collect();
941 values.sort();
942 assert_eq!(values, vec![111, 112]);
943 Ok(())
944 }
945
946 #[test]
947 fn test_unary_minus() -> Result<()> {
948 let arena = Arena::new_with_global_interner();
949 let source = r#"
950 p(5).
951 q(Y) :- p(X) |> let Y = fn:minus(X).
952 "#;
953
954 let (mut ir, stratified) = compile(source, &arena)?;
955 let store = Box::new(MemStore::new());
956 let interpreter = execute(&mut ir, &stratified, store)?;
957
958 let facts: Vec<_> = interpreter
959 .store()
960 .scan("q")
961 .expect("relation q not found")
962 .collect();
963 assert_eq!(facts.len(), 1);
964 assert_eq!(facts[0][0], Value::Number(-5));
965 Ok(())
966 }
967
968 #[test]
969 fn test_string_concat() -> Result<()> {
970 let arena = Arena::new_with_global_interner();
971 let source = r#"
972 p("hello", "world").
973 q(R) :- p(A, B) |> let R = fn:string:concat(A, " ", B).
974 "#;
975
976 let (mut ir, stratified) = compile(source, &arena)?;
977 let store = Box::new(MemStore::new());
978 let interpreter = execute(&mut ir, &stratified, store)?;
979
980 let facts: Vec<_> = interpreter
981 .store()
982 .scan("q")
983 .expect("relation q not found")
984 .collect();
985 assert_eq!(facts.len(), 1);
986 assert_eq!(facts[0][0], Value::String("hello world".to_string()));
987 Ok(())
988 }
989
990 #[test]
991 fn test_string_replace() -> Result<()> {
992 let arena = Arena::new_with_global_interner();
993 let source = r#"
994 p("foo-bar-baz").
995 q(R) :- p(S) |> let R = fn:string:replace(S, "-", "_", -1).
996 "#;
997
998 let (mut ir, stratified) = compile(source, &arena)?;
999 let store = Box::new(MemStore::new());
1000 let interpreter = execute(&mut ir, &stratified, store)?;
1001
1002 let facts: Vec<_> = interpreter
1003 .store()
1004 .scan("q")
1005 .expect("relation q not found")
1006 .collect();
1007 assert_eq!(facts.len(), 1);
1008 assert_eq!(facts[0][0], Value::String("foo_bar_baz".to_string()));
1009 Ok(())
1010 }
1011
1012 #[test]
1013 fn test_number_to_string() -> Result<()> {
1014 let arena = Arena::new_with_global_interner();
1015 let source = r#"
1016 p(42).
1017 q(R) :- p(X) |> let R = fn:number:to_string(X).
1018 "#;
1019
1020 let (mut ir, stratified) = compile(source, &arena)?;
1021 let store = Box::new(MemStore::new());
1022 let interpreter = execute(&mut ir, &stratified, store)?;
1023
1024 let facts: Vec<_> = interpreter
1025 .store()
1026 .scan("q")
1027 .expect("relation q not found")
1028 .collect();
1029 assert_eq!(facts.len(), 1);
1030 assert_eq!(facts[0][0], Value::String("42".to_string()));
1031 Ok(())
1032 }
1033
1034 #[test]
1035 fn test_float64_to_string() -> Result<()> {
1036 let arena = Arena::new_with_global_interner();
1037 let source = r#"
1038 p(3.14).
1039 q(R) :- p(X) |> let R = fn:float64:to_string(X).
1040 "#;
1041
1042 let (mut ir, stratified) = compile(source, &arena)?;
1043 let store = Box::new(MemStore::new());
1044 let interpreter = execute(&mut ir, &stratified, store)?;
1045
1046 let facts: Vec<_> = interpreter
1047 .store()
1048 .scan("q")
1049 .expect("relation q not found")
1050 .collect();
1051 assert_eq!(facts.len(), 1);
1052 assert_eq!(facts[0][0], Value::String("3.14".to_string()));
1053 Ok(())
1054 }
1055
1056 #[test]
1057 fn test_float_promotion_in_sqrt() -> Result<()> {
1058 let arena = Arena::new_with_global_interner();
1059 let source = r#"
1060 p(16).
1061 q(R) :- p(X) |> let R = fn:sqrt(X).
1062 "#;
1063
1064 let (mut ir, stratified) = compile(source, &arena)?;
1065 let store = Box::new(MemStore::new());
1066 let interpreter = execute(&mut ir, &stratified, store)?;
1067
1068 let facts: Vec<_> = interpreter
1069 .store()
1070 .scan("q")
1071 .expect("relation q not found")
1072 .collect();
1073 assert_eq!(facts.len(), 1);
1074 assert_eq!(facts[0][0], Value::Float(4.0));
1075 Ok(())
1076 }
1077
1078 #[test]
1079 fn test_negative_number_literals() -> Result<()> {
1080 let arena = Arena::new_with_global_interner();
1081 let source = r#"
1082 temp(-10).
1083 temp(5).
1084 temp(20).
1085 below_zero(X) :- temp(X), X < 0 .
1086 offset(X, Y) :- temp(X) |> let Y = fn:float:plus(X, -0.5).
1087 "#;
1088
1089 let (mut ir, stratified) = compile(source, &arena)?;
1090 let store = Box::new(MemStore::new());
1091 let interpreter = execute(&mut ir, &stratified, store)?;
1092
1093 let facts: Vec<_> = interpreter
1094 .store()
1095 .scan("below_zero")
1096 .expect("relation below_zero not found")
1097 .collect();
1098 assert_eq!(facts.len(), 1);
1099 assert_eq!(facts[0][0], Value::Number(-10));
1100
1101 let offset_facts: Vec<_> = interpreter
1102 .store()
1103 .scan("offset")
1104 .expect("relation offset not found")
1105 .collect();
1106 assert_eq!(offset_facts.len(), 3);
1107
1108 Ok(())
1109 }
1110
1111 #[test]
1112 fn test_builtin_string_predicates() -> Result<()> {
1113 let arena = Arena::new_with_global_interner();
1114 let source = r#"
1115 path("/api/users").
1116 path("/api/posts").
1117 path("/home").
1118 path("/api/users/admin").
1119 api_path(P) :- path(P), :string:starts_with(P, "/api").
1120 users_path(P) :- path(P), :string:contains(P, "users").
1121 html_path(P) :- path(P), :string:ends_with(P, "admin").
1122 "#;
1123
1124 let (mut ir, stratified) = compile(source, &arena)?;
1125 let store = Box::new(MemStore::new());
1126 let interpreter = execute(&mut ir, &stratified, store)?;
1127
1128 let api_facts: Vec<_> = interpreter
1129 .store()
1130 .scan("api_path")
1131 .expect("relation api_path not found")
1132 .collect();
1133 assert_eq!(api_facts.len(), 3, "api_path: {:?}", api_facts);
1134
1135 let users_facts: Vec<_> = interpreter
1136 .store()
1137 .scan("users_path")
1138 .expect("relation users_path not found")
1139 .collect();
1140 assert_eq!(users_facts.len(), 2, "users_path: {:?}", users_facts);
1141
1142 let html_facts: Vec<_> = interpreter
1143 .store()
1144 .scan("html_path")
1145 .expect("relation html_path not found")
1146 .collect();
1147 assert_eq!(html_facts.len(), 1, "html_path: {:?}", html_facts);
1148
1149 Ok(())
1150 }
1151
1152 #[test]
1153 fn test_match_prefix() -> Result<()> {
1154 let arena = Arena::new_with_global_interner();
1155 let source = r#"
1156 name("/role/admin").
1157 name("/role").
1158 name("/other").
1159 under_role(N) :- name(N), :match_prefix(N, "/role").
1160 "#;
1161
1162 let (mut ir, stratified) = compile(source, &arena)?;
1163 let store = Box::new(MemStore::new());
1164 let interpreter = execute(&mut ir, &stratified, store)?;
1165
1166 let facts: Vec<_> = interpreter
1167 .store()
1168 .scan("under_role")
1169 .expect("relation under_role not found")
1170 .collect();
1171 assert_eq!(facts.len(), 1, "under_role: {:?}", facts);
1173 assert_eq!(facts[0][0], Value::String("/role/admin".to_string()));
1174
1175 Ok(())
1176 }
1177
1178 #[test]
1179 fn test_timestamp_literals() -> Result<()> {
1180 let arena = Arena::new_with_global_interner();
1181 let source = r#"
1182 event(2024-01-15T10:30:00Z).
1183 event(2024-06-01T00:00:00Z).
1184 has_event(X) :- event(X).
1185 "#;
1186
1187 let (mut ir, stratified) = compile(source, &arena)?;
1188 let store = Box::new(MemStore::new());
1189 let interpreter = execute(&mut ir, &stratified, store)?;
1190
1191 let facts: Vec<_> = interpreter
1192 .store()
1193 .scan("has_event")
1194 .expect("relation has_event not found")
1195 .collect();
1196 assert_eq!(facts.len(), 2);
1197 for fact in &facts {
1199 assert!(matches!(fact[0], Value::Time(_)), "expected Time, got {:?}", fact[0]);
1200 }
1201 Ok(())
1202 }
1203
1204 #[test]
1205 fn test_duration_literals() -> Result<()> {
1206 let arena = Arena::new_with_global_interner();
1207 let source = r#"
1208 timeout(30s).
1209 timeout(500ms).
1210 has_timeout(X) :- timeout(X).
1211 "#;
1212
1213 let (mut ir, stratified) = compile(source, &arena)?;
1214 let store = Box::new(MemStore::new());
1215 let interpreter = execute(&mut ir, &stratified, store)?;
1216
1217 let facts: Vec<_> = interpreter
1218 .store()
1219 .scan("has_timeout")
1220 .expect("relation has_timeout not found")
1221 .collect();
1222 assert_eq!(facts.len(), 2);
1223 for fact in &facts {
1224 assert!(matches!(fact[0], Value::Duration(_)), "expected Duration, got {:?}", fact[0]);
1225 }
1226 Ok(())
1227 }
1228
1229 #[test]
1230 fn test_time_arithmetic() -> Result<()> {
1231 let arena = Arena::new_with_global_interner();
1232 let source = r#"
1233 start(2024-01-15T10:00:00Z).
1234 result(Y) :- start(X) |> let Y = fn:time:add(X, 1h).
1235 "#;
1236
1237 let (mut ir, stratified) = compile(source, &arena)?;
1238 let store = Box::new(MemStore::new());
1239 let interpreter = execute(&mut ir, &stratified, store)?;
1240
1241 let facts: Vec<_> = interpreter
1242 .store()
1243 .scan("result")
1244 .expect("relation result not found")
1245 .collect();
1246 assert_eq!(facts.len(), 1);
1247 match &facts[0][0] {
1249 Value::Time(nanos) => {
1250 let expected = 1705276800_000_000_000i64 + (11 * 3600) * 1_000_000_000;
1251 assert_eq!(*nanos, expected, "time should be 2024-01-15T11:00:00Z");
1252 }
1253 v => panic!("expected Time, got {v:?}"),
1254 }
1255 Ok(())
1256 }
1257
1258 #[test]
1259 fn test_time_sub() -> Result<()> {
1260 let arena = Arena::new_with_global_interner();
1261 let source = r#"
1262 pair(2024-01-15T12:00:00Z, 2024-01-15T10:00:00Z).
1263 diff(D) :- pair(A, B) |> let D = fn:time:sub(A, B).
1264 "#;
1265
1266 let (mut ir, stratified) = compile(source, &arena)?;
1267 let store = Box::new(MemStore::new());
1268 let interpreter = execute(&mut ir, &stratified, store)?;
1269
1270 let facts: Vec<_> = interpreter
1271 .store()
1272 .scan("diff")
1273 .expect("relation diff not found")
1274 .collect();
1275 assert_eq!(facts.len(), 1);
1276 match &facts[0][0] {
1277 Value::Duration(nanos) => {
1278 assert_eq!(*nanos, 2 * 3600 * 1_000_000_000, "diff should be 2h");
1279 }
1280 v => panic!("expected Duration, got {v:?}"),
1281 }
1282 Ok(())
1283 }
1284
1285 #[test]
1286 fn test_time_components() -> Result<()> {
1287 let arena = Arena::new_with_global_interner();
1288 let source = r#"
1289 ts(2024-06-15T14:30:45Z).
1290 year_of(Y) :- ts(T) |> let Y = fn:time:year(T).
1291 month_of(M) :- ts(T) |> let M = fn:time:month(T).
1292 day_of(D) :- ts(T) |> let D = fn:time:day(T).
1293 hour_of(H) :- ts(T) |> let H = fn:time:hour(T).
1294 minute_of(Min) :- ts(T) |> let Min = fn:time:minute(T).
1295 second_of(S) :- ts(T) |> let S = fn:time:second(T).
1296 "#;
1297
1298 let (mut ir, stratified) = compile(source, &arena)?;
1299 let store = Box::new(MemStore::new());
1300 let interpreter = execute(&mut ir, &stratified, store)?;
1301
1302 let check = |rel: &str, expected: i64| {
1303 let facts: Vec<_> = interpreter.store().scan(rel).expect(rel).collect();
1304 assert_eq!(facts.len(), 1, "{rel}");
1305 assert_eq!(facts[0][0], Value::Number(expected), "{rel}");
1306 };
1307 check("year_of", 2024);
1308 check("month_of", 6);
1309 check("day_of", 15);
1310 check("hour_of", 14);
1311 check("minute_of", 30);
1312 check("second_of", 45);
1313 Ok(())
1314 }
1315
1316 #[test]
1317 fn test_duration_components() -> Result<()> {
1318 let arena = Arena::new_with_global_interner();
1319 let source = r#"
1320 dur(90s).
1321 dur_seconds(S) :- dur(D) |> let S = fn:duration:seconds(D).
1322 dur_nanos(N) :- dur(D) |> let N = fn:duration:nanos(D).
1323 "#;
1324
1325 let (mut ir, stratified) = compile(source, &arena)?;
1326 let store = Box::new(MemStore::new());
1327 let interpreter = execute(&mut ir, &stratified, store)?;
1328
1329 let secs: Vec<_> = interpreter.store().scan("dur_seconds").expect("dur_seconds").collect();
1330 assert_eq!(secs.len(), 1);
1331 assert_eq!(secs[0][0], Value::Float(90.0));
1332
1333 let nanos: Vec<_> = interpreter.store().scan("dur_nanos").expect("dur_nanos").collect();
1334 assert_eq!(nanos.len(), 1);
1335 assert_eq!(nanos[0][0], Value::Number(90_000_000_000));
1336 Ok(())
1337 }
1338
1339 #[test]
1340 fn test_time_comparison_predicates() -> Result<()> {
1341 let arena = Arena::new_with_global_interner();
1342 let source = r#"
1343 event(2024-01-01T00:00:00Z).
1344 event(2024-06-15T00:00:00Z).
1345 event(2024-12-31T00:00:00Z).
1346 recent(T) :- event(T), :time:gt(T, 2024-06-01T00:00:00Z).
1347 "#;
1348
1349 let (mut ir, stratified) = compile(source, &arena)?;
1350 let store = Box::new(MemStore::new());
1351 let interpreter = execute(&mut ir, &stratified, store)?;
1352
1353 let facts: Vec<_> = interpreter
1354 .store()
1355 .scan("recent")
1356 .expect("relation recent not found")
1357 .collect();
1358 assert_eq!(facts.len(), 2, "recent: {:?}", facts);
1359 Ok(())
1360 }
1361
1362 #[test]
1363 fn test_date_only_timestamp() -> Result<()> {
1364 let arena = Arena::new_with_global_interner();
1365 let source = r#"
1366 d(2024-01-15).
1367 has(X) :- d(X).
1368 "#;
1369
1370 let (mut ir, stratified) = compile(source, &arena)?;
1371 let store = Box::new(MemStore::new());
1372 let interpreter = execute(&mut ir, &stratified, store)?;
1373
1374 let facts: Vec<_> = interpreter
1375 .store()
1376 .scan("has")
1377 .expect("relation has not found")
1378 .collect();
1379 assert_eq!(facts.len(), 1);
1380 match &facts[0][0] {
1381 Value::Time(nanos) => {
1382 assert_eq!(*nanos, 1705276800_000_000_000);
1384 }
1385 v => panic!("expected Time, got {v:?}"),
1386 }
1387 Ok(())
1388 }
1389
1390 #[test]
1391 fn test_time_trunc() -> Result<()> {
1392 let arena = Arena::new_with_global_interner();
1393 let source = r#"
1394 ts(2024-06-15T14:30:45Z).
1395 truncated(Y) :- ts(T) |> let Y = fn:time:trunc(T, /hour).
1396 "#;
1397
1398 let (mut ir, stratified) = compile(source, &arena)?;
1399 let store = Box::new(MemStore::new());
1400 let interpreter = execute(&mut ir, &stratified, store)?;
1401
1402 let facts: Vec<_> = interpreter
1403 .store()
1404 .scan("truncated")
1405 .expect("relation truncated not found")
1406 .collect();
1407 assert_eq!(facts.len(), 1);
1408 match &facts[0][0] {
1409 Value::Time(nanos) => {
1410 let display = format!("{}", Value::Time(*nanos));
1412 assert_eq!(display, "2024-06-15T14:00:00Z");
1413 }
1414 v => panic!("expected Time, got {v:?}"),
1415 }
1416 Ok(())
1417 }
1418
1419 #[test]
1420 fn test_duration_arithmetic() -> Result<()> {
1421 let arena = Arena::new_with_global_interner();
1422 let source = r#"
1423 d(1h, 30m).
1424 total(T) :- d(A, B) |> let T = fn:duration:add(A, B).
1425 doubled(T) :- d(A, _) |> let T = fn:duration:mult(A, 2).
1426 "#;
1427
1428 let (mut ir, stratified) = compile(source, &arena)?;
1429 let store = Box::new(MemStore::new());
1430 let interpreter = execute(&mut ir, &stratified, store)?;
1431
1432 let facts: Vec<_> = interpreter
1433 .store()
1434 .scan("total")
1435 .expect("relation total not found")
1436 .collect();
1437 assert_eq!(facts.len(), 1);
1438 match &facts[0][0] {
1439 Value::Duration(nanos) => {
1440 assert_eq!(*nanos, 90 * 60 * 1_000_000_000, "1h + 30m = 90m");
1441 }
1442 v => panic!("expected Duration, got {v:?}"),
1443 }
1444
1445 let facts: Vec<_> = interpreter
1446 .store()
1447 .scan("doubled")
1448 .expect("relation doubled not found")
1449 .collect();
1450 assert_eq!(facts.len(), 1);
1451 match &facts[0][0] {
1452 Value::Duration(nanos) => {
1453 assert_eq!(*nanos, 2 * 3600 * 1_000_000_000, "2 * 1h = 2h");
1454 }
1455 v => panic!("expected Duration, got {v:?}"),
1456 }
1457 Ok(())
1458 }
1459
1460 #[test]
1461 fn test_list_construction() -> Result<()> {
1462 let arena = Arena::new_with_global_interner();
1463 let source = r#"
1464 data(1). data(2). data(3).
1465 result(L) :- data(X) |> let L = fn:list(X).
1466 "#;
1467 let (mut ir, stratified) = compile(source, &arena)?;
1468 let store = Box::new(MemStore::new());
1469 let interpreter = execute(&mut ir, &stratified, store)?;
1470
1471 let facts: Vec<_> = interpreter
1472 .store()
1473 .scan("result")
1474 .expect("relation result not found")
1475 .collect();
1476 assert_eq!(facts.len(), 3);
1478 for fact in &facts {
1479 match &fact[0] {
1480 Value::Compound(_, elems) => assert_eq!(elems.len(), 1),
1481 v => panic!("expected Compound, got {v:?}"),
1482 }
1483 }
1484 Ok(())
1485 }
1486
1487 #[test]
1488 fn test_list_literal_syntax() -> Result<()> {
1489 let arena = Arena::new_with_global_interner();
1490 let source = r#"
1492 result([1, 2, 3]).
1493 "#;
1494 let (mut ir, stratified) = compile(source, &arena)?;
1495 let store = Box::new(MemStore::new());
1496 let interpreter = execute(&mut ir, &stratified, store)?;
1497
1498 let facts: Vec<_> = interpreter
1499 .store()
1500 .scan("result")
1501 .expect("relation result not found")
1502 .collect();
1503 assert_eq!(facts.len(), 1);
1504 match &facts[0][0] {
1505 Value::Compound(_, elems) => {
1506 assert_eq!(elems.len(), 3);
1507 assert_eq!(elems[0], Value::Number(1));
1508 assert_eq!(elems[1], Value::Number(2));
1509 assert_eq!(elems[2], Value::Number(3));
1510 }
1511 v => panic!("expected Compound, got {v:?}"),
1512 }
1513 Ok(())
1514 }
1515
1516 #[test]
1517 fn test_struct_construction() -> Result<()> {
1518 let arena = Arena::new_with_global_interner();
1519 let source = r#"
1520 result({/name: "alice", /age: 30}).
1521 "#;
1522 let (mut ir, stratified) = compile(source, &arena)?;
1523 let store = Box::new(MemStore::new());
1524 let interpreter = execute(&mut ir, &stratified, store)?;
1525
1526 let facts: Vec<_> = interpreter
1527 .store()
1528 .scan("result")
1529 .expect("relation result not found")
1530 .collect();
1531 assert_eq!(facts.len(), 1);
1532 match &facts[0][0] {
1533 Value::Compound(_, elems) => {
1534 assert_eq!(elems.len(), 4);
1536 assert_eq!(elems[0], Value::String("/name".to_string()));
1537 assert_eq!(elems[1], Value::String("alice".to_string()));
1538 assert_eq!(elems[2], Value::String("/age".to_string()));
1539 assert_eq!(elems[3], Value::Number(30));
1540 }
1541 v => panic!("expected Compound, got {v:?}"),
1542 }
1543 Ok(())
1544 }
1545
1546 #[test]
1547 fn test_pair_construction() -> Result<()> {
1548 let arena = Arena::new_with_global_interner();
1549 let source = r#"
1550 data("key", 42).
1551 result(P) :- data(K, V) |> let P = fn:pair(K, V).
1552 "#;
1553 let (mut ir, stratified) = compile(source, &arena)?;
1554 let store = Box::new(MemStore::new());
1555 let interpreter = execute(&mut ir, &stratified, store)?;
1556
1557 let facts: Vec<_> = interpreter
1558 .store()
1559 .scan("result")
1560 .expect("relation result not found")
1561 .collect();
1562 assert_eq!(facts.len(), 1);
1563 match &facts[0][0] {
1564 Value::Compound(_, elems) => {
1565 assert_eq!(elems.len(), 2);
1566 assert_eq!(elems[0], Value::String("key".to_string()));
1567 assert_eq!(elems[1], Value::Number(42));
1568 }
1569 v => panic!("expected Compound, got {v:?}"),
1570 }
1571 Ok(())
1572 }
1573
1574 #[test]
1575 fn test_list_get_and_len() -> Result<()> {
1576 let arena = Arena::new_with_global_interner();
1577 let source = r#"
1578 data([10, 20, 30]).
1579 first(F) :- data(L) |> let F = fn:list:get(L, 0).
1580 length(N) :- data(L) |> let N = fn:len(L).
1581 "#;
1582 let (mut ir, stratified) = compile(source, &arena)?;
1583 let store = Box::new(MemStore::new());
1584 let interpreter = execute(&mut ir, &stratified, store)?;
1585
1586 let facts: Vec<_> = interpreter
1587 .store()
1588 .scan("first")
1589 .expect("relation first not found")
1590 .collect();
1591 assert_eq!(facts.len(), 1);
1592 assert_eq!(facts[0][0], Value::Number(10));
1593
1594 let facts: Vec<_> = interpreter
1595 .store()
1596 .scan("length")
1597 .expect("relation length not found")
1598 .collect();
1599 assert_eq!(facts.len(), 1);
1600 assert_eq!(facts[0][0], Value::Number(3));
1601 Ok(())
1602 }
1603
1604 #[test]
1605 fn test_struct_get() -> Result<()> {
1606 let arena = Arena::new_with_global_interner();
1607 let source = r#"
1608 data({/name: "alice", /age: 30}).
1609 name(N) :- data(S) |> let N = fn:struct:get(S, /name).
1610 age(A) :- data(S) |> let A = fn:struct:get(S, /age).
1611 "#;
1612 let (mut ir, stratified) = compile(source, &arena)?;
1613 let store = Box::new(MemStore::new());
1614 let interpreter = execute(&mut ir, &stratified, store)?;
1615
1616 let facts: Vec<_> = interpreter
1617 .store()
1618 .scan("name")
1619 .expect("relation name not found")
1620 .collect();
1621 assert_eq!(facts.len(), 1);
1622 assert_eq!(facts[0][0], Value::String("alice".to_string()));
1623
1624 let facts: Vec<_> = interpreter
1625 .store()
1626 .scan("age")
1627 .expect("relation age not found")
1628 .collect();
1629 assert_eq!(facts.len(), 1);
1630 assert_eq!(facts[0][0], Value::Number(30));
1631 Ok(())
1632 }
1633
1634 #[test]
1635 fn test_pair_accessors() -> Result<()> {
1636 let arena = Arena::new_with_global_interner();
1637 let source = r#"
1639 data(42, "hello").
1640 pair_data(P) :- data(A, B) |> let P = fn:pair(A, B).
1641 result_first(F) :- pair_data(P) |> let F = fn:pair:first(P).
1642 result_second(S) :- pair_data(P) |> let S = fn:pair:second(P).
1643 "#;
1644 let (mut ir, stratified) = compile(source, &arena)?;
1645 let store = Box::new(MemStore::new());
1646 let interpreter = execute(&mut ir, &stratified, store)?;
1647
1648 let facts: Vec<_> = interpreter
1649 .store()
1650 .scan("result_first")
1651 .expect("relation result_first not found")
1652 .collect();
1653 assert_eq!(facts.len(), 1);
1654 assert_eq!(facts[0][0], Value::Number(42));
1655
1656 let facts: Vec<_> = interpreter
1657 .store()
1658 .scan("result_second")
1659 .expect("relation result_second not found")
1660 .collect();
1661 assert_eq!(facts.len(), 1);
1662 assert_eq!(facts[0][0], Value::String("hello".to_string()));
1663 Ok(())
1664 }
1665
1666 #[test]
1667 fn test_map_operations() -> Result<()> {
1668 let arena = Arena::new_with_global_interner();
1669 let source = r#"
1670 data([/a: 10, /b: 20]).
1671 val_a(V) :- data(M) |> let V = fn:map:get(M, /a).
1672 val_b(V) :- data(M) |> let V = fn:map:get(M, /b).
1673 nkeys(N) :- data(M) |> let N = fn:map:len(M).
1674 "#;
1675 let (mut ir, stratified) = compile(source, &arena)?;
1676 let store = Box::new(MemStore::new());
1677 let interpreter = execute(&mut ir, &stratified, store)?;
1678
1679 let facts: Vec<_> = interpreter
1680 .store()
1681 .scan("val_a")
1682 .expect("relation val_a not found")
1683 .collect();
1684 assert_eq!(facts.len(), 1);
1685 assert_eq!(facts[0][0], Value::Number(10));
1686
1687 let facts: Vec<_> = interpreter
1688 .store()
1689 .scan("val_b")
1690 .expect("relation val_b not found")
1691 .collect();
1692 assert_eq!(facts.len(), 1);
1693 assert_eq!(facts[0][0], Value::Number(20));
1694
1695 let facts: Vec<_> = interpreter
1696 .store()
1697 .scan("nkeys")
1698 .expect("relation nkeys not found")
1699 .collect();
1700 assert_eq!(facts.len(), 1);
1701 assert_eq!(facts[0][0], Value::Number(2));
1702 Ok(())
1703 }
1704
1705 #[test]
1706 fn test_nested_compound() -> Result<()> {
1707 let arena = Arena::new_with_global_interner();
1708 let source = r#"
1710 data("a", 1).
1711 data("b", 2).
1712 pairs(P) :- data(K, V) |> let P = fn:pair(K, V).
1713 first_key(K) :- pairs(P) |> let K = fn:pair:first(P).
1714 "#;
1715 let (mut ir, stratified) = compile(source, &arena)?;
1716 let store = Box::new(MemStore::new());
1717 let interpreter = execute(&mut ir, &stratified, store)?;
1718
1719 let facts: Vec<_> = interpreter
1720 .store()
1721 .scan("first_key")
1722 .expect("relation first_key not found")
1723 .collect();
1724 assert_eq!(facts.len(), 2);
1725 let mut keys: Vec<String> = facts
1726 .iter()
1727 .map(|t| match &t[0] {
1728 Value::String(s) => s.clone(),
1729 v => panic!("expected string, got {v:?}"),
1730 })
1731 .collect();
1732 keys.sort();
1733 assert_eq!(keys, vec!["a", "b"]);
1734 Ok(())
1735 }
1736
1737 #[test]
1743 fn test_temporal_point_facts() -> Result<()> {
1744 let arena = Arena::new_with_global_interner();
1745 let source = r#"
1746 Decl link(X, Y) temporal bound [/name, /name].
1747 link(/a, /b)@[2024-01-01].
1748 link(/a, /c)@[2024-01-02].
1749 "#;
1750
1751 let (mut ir, stratified) = compile(source, &arena)?;
1752 let store = Box::new(MemStore::new());
1753 let interpreter = execute(&mut ir, &stratified, store)?;
1754
1755 let facts: Vec<_> = interpreter
1757 .store()
1758 .scan("link")
1759 .expect("relation link not found")
1760 .collect();
1761 assert_eq!(facts.len(), 2, "expected 2 temporal link facts, got {:?}", facts);
1762
1763 for fact in &facts {
1765 assert_eq!(fact.len(), 4, "temporal fact should have 4 columns, got {:?}", fact);
1766 }
1767
1768 Ok(())
1769 }
1770
1771 #[test]
1773 fn test_temporal_simple_rule() -> Result<()> {
1774 let arena = Arena::new_with_global_interner();
1775 let source = r#"
1776 Decl link(X, Y) temporal bound [/name, /name].
1777 Decl reachable(X, Y) temporal bound [/name, /name].
1778 link(/a, /b)@[2024-01-01].
1779 reachable(X, Y)@[T] :- link(X, Y)@[T].
1780 "#;
1781
1782 let (mut ir, stratified) = compile(source, &arena)?;
1783 let store = Box::new(MemStore::new());
1784 let interpreter = execute(&mut ir, &stratified, store)?;
1785
1786 let facts: Vec<_> = interpreter
1787 .store()
1788 .scan("reachable")
1789 .expect("relation reachable not found")
1790 .collect();
1791 assert_eq!(facts.len(), 1, "expected 1 reachable fact, got {:?}", facts);
1792 assert_eq!(facts[0].len(), 4); assert_eq!(facts[0][0], Value::String("/a".to_string()));
1794 assert_eq!(facts[0][1], Value::String("/b".to_string()));
1795
1796 Ok(())
1797 }
1798
1799 #[test]
1802 fn test_temporal_graph_points() -> Result<()> {
1803 let arena = Arena::new_with_global_interner();
1804 let source = r#"
1805 Decl link(X, Y) temporal bound [/name, /name].
1806 Decl reachable(X, Y) temporal bound [/name, /name].
1807
1808 link(/a, /b)@[2024-01-01].
1809 link(/b, /c)@[2024-01-01].
1810
1811 link(/a, /c)@[2024-01-02].
1812 link(/c, /d)@[2024-01-02].
1813
1814 reachable(X, Y)@[T] :- link(X, Y)@[T].
1815 reachable(X, Z)@[T] :- reachable(X, Y)@[T], link(Y, Z)@[T].
1816 "#;
1817
1818 let (mut ir, stratified) = compile(source, &arena)?;
1819
1820 let store = Box::new(MemStore::new());
1821 let interpreter = execute(&mut ir, &stratified, store)?;
1822
1823 let facts: Vec<_> = interpreter
1824 .store()
1825 .scan("reachable")
1826 .expect("relation reachable not found")
1827 .collect();
1828
1829 let t1: i64 = 1704067200_000_000_000; let t2: i64 = 1704153600_000_000_000; let mut results: Vec<(String, String, i64)> = facts
1834 .iter()
1835 .map(|f| {
1836 let from = match &f[0] {
1837 Value::String(n) => n.clone(),
1838 v => panic!("expected name, got {v:?}"),
1839 };
1840 let to = match &f[1] {
1841 Value::String(n) => n.clone(),
1842 v => panic!("expected name, got {v:?}"),
1843 };
1844 let time = match &f[2] {
1845 Value::Time(t) => *t,
1846 v => panic!("expected time, got {v:?}"),
1847 };
1848 (from, to, time)
1849 })
1850 .collect();
1851 results.sort();
1852
1853 let expected = vec![
1857 ("/a".to_string(), "/b".to_string(), t1),
1858 ("/a".to_string(), "/c".to_string(), t1),
1859 ("/a".to_string(), "/c".to_string(), t2),
1860 ("/a".to_string(), "/d".to_string(), t2),
1861 ("/b".to_string(), "/c".to_string(), t1),
1862 ("/c".to_string(), "/d".to_string(), t2),
1863 ];
1864 assert_eq!(results, expected, "temporal graph reachability mismatch");
1865
1866 Ok(())
1867 }
1868
1869 #[test]
1871 fn test_temporal_coalescing() -> Result<()> {
1872 let arena = Arena::new_with_global_interner();
1873 let source = r#"
1875 Decl link(X, Y) temporal bound [/name, /name].
1876 Decl reach(X, Y) temporal bound [/name, /name].
1877 link(/a, /b)@[2024-01-01, 2024-01-05].
1878 link(/a, /b)@[2024-01-03, 2024-01-10].
1879 reach(X, Y)@[S, E] :- link(X, Y)@[S, E].
1880 "#;
1881
1882 let (mut ir, stratified) = compile(source, &arena)?;
1883 let store = Box::new(MemStore::new());
1884 let interpreter = execute(&mut ir, &stratified, store)?;
1885
1886 let facts: Vec<_> = interpreter
1887 .store()
1888 .scan("reach")
1889 .expect("relation reach not found")
1890 .collect();
1891
1892 assert_eq!(facts.len(), 1, "expected 1 coalesced fact, got {:?}", facts);
1895 assert_eq!(facts[0][0], Value::String("/a".to_string()));
1896 assert_eq!(facts[0][1], Value::String("/b".to_string()));
1897
1898 let start = match &facts[0][2] {
1900 Value::Time(t) => *t,
1901 v => panic!("expected time, got {v:?}"),
1902 };
1903 let end = match &facts[0][3] {
1904 Value::Time(t) => *t,
1905 v => panic!("expected time, got {v:?}"),
1906 };
1907 let jan1: i64 = 1704067200_000_000_000;
1908 let jan10: i64 = 1704844800_000_000_000;
1909 assert_eq!(start, jan1, "start should be Jan 1");
1910 assert_eq!(end, jan10, "end should be Jan 10");
1911
1912 Ok(())
1913 }
1914
1915 #[test]
1921 fn test_temporal_coalesce_three_overlapping() -> Result<()> {
1922 let arena = Arena::new_with_global_interner();
1923 let source = r#"
1924 Decl status(X) temporal bound [/name].
1925 status(/active)@[2024-01-01, 2024-01-15].
1926 status(/active)@[2024-01-10, 2024-01-25].
1927 status(/active)@[2024-01-20, 2024-01-31].
1928 "#;
1929
1930 let (mut ir, stratified) = compile(source, &arena)?;
1931 let store = Box::new(MemStore::new());
1932 let interpreter = execute(&mut ir, &stratified, store)?;
1933
1934 let facts: Vec<_> = interpreter
1935 .store()
1936 .scan("status")
1937 .expect("relation status not found")
1938 .collect();
1939 assert_eq!(facts.len(), 1, "expected 1 coalesced fact, got {:?}", facts);
1941 assert_eq!(facts[0][0], Value::String("/active".to_string()));
1942
1943 Ok(())
1944 }
1945
1946 #[test]
1949 fn test_temporal_reachability_mixed_timestamps() -> Result<()> {
1950 let arena = Arena::new_with_global_interner();
1951 let source = r#"
1952 Decl link(X, Y) temporal bound [/name, /name].
1953 Decl reachable(X, Y) temporal bound [/name, /name].
1954
1955 link(/a, /b)@[2024-01-01].
1956 link(/b, /c)@[2024-01-01].
1957 link(/c, /d)@[2024-01-02].
1958
1959 reachable(X, Y)@[T] :- link(X, Y)@[T].
1960 reachable(X, Z)@[T] :- reachable(X, Y)@[T], link(Y, Z)@[T].
1961 "#;
1962
1963 let (mut ir, stratified) = compile(source, &arena)?;
1964 let store = Box::new(MemStore::new());
1965 let interpreter = execute(&mut ir, &stratified, store)?;
1966
1967 let facts: Vec<_> = interpreter
1968 .store()
1969 .scan("reachable")
1970 .expect("relation reachable not found")
1971 .collect();
1972
1973 let mut pairs: Vec<(String, String)> = facts
1975 .iter()
1976 .map(|f| {
1977 let from = match &f[0] { Value::String(s) => s.clone(), v => panic!("{v:?}") };
1978 let to = match &f[1] { Value::String(s) => s.clone(), v => panic!("{v:?}") };
1979 (from, to)
1980 })
1981 .collect();
1982 pairs.sort();
1983 pairs.dedup();
1984
1985 let expected = vec![
1990 ("/a".to_string(), "/b".to_string()),
1991 ("/a".to_string(), "/c".to_string()),
1992 ("/b".to_string(), "/c".to_string()),
1993 ("/c".to_string(), "/d".to_string()),
1994 ];
1995 assert_eq!(pairs, expected, "temporal reachability mismatch");
1996
1997 Ok(())
1998 }
1999
2000 #[test]
2003 fn test_temporal_graph_intervals() -> Result<()> {
2004 let arena = Arena::new_with_global_interner();
2005 let source = r#"
2006 Decl link(X, Y) temporal bound [/name, /name].
2007 Decl reachable(X, Y) temporal bound [/name, /name].
2008
2009 link(/a, /b)@[2024-01-01, 2024-01-10].
2010 link(/b, /c)@[2024-01-05, 2024-01-15].
2011 link(/c, /d)@[2024-01-12, 2024-01-20].
2012
2013 reachable(X, Y)@[S, E] :- link(X, Y)@[S, E].
2014
2015 reachable(X, Z)@[S1, E1] :-
2016 reachable(X, Y)@[S1, E1], link(Y, Z)@[S2, E2],
2017 :time:ge(S1, S2), :time:le(E1, E2), :time:le(S1, E1).
2018
2019 reachable(X, Z)@[S1, E2] :-
2020 reachable(X, Y)@[S1, E1], link(Y, Z)@[S2, E2],
2021 :time:ge(S1, S2), :time:lt(E2, E1), :time:le(S1, E2).
2022
2023 reachable(X, Z)@[S2, E1] :-
2024 reachable(X, Y)@[S1, E1], link(Y, Z)@[S2, E2],
2025 :time:gt(S2, S1), :time:le(E1, E2), :time:le(S2, E1).
2026
2027 reachable(X, Z)@[S2, E2] :-
2028 reachable(X, Y)@[S1, E1], link(Y, Z)@[S2, E2],
2029 :time:gt(S2, S1), :time:lt(E2, E1), :time:le(S2, E2).
2030 "#;
2031
2032 let (mut ir, stratified) = compile(source, &arena)?;
2033 let store = Box::new(MemStore::new());
2034 let interpreter = execute(&mut ir, &stratified, store)?;
2035
2036 let facts: Vec<_> = interpreter
2037 .store()
2038 .scan("reachable")
2039 .expect("relation reachable not found")
2040 .collect();
2041
2042 let mut pairs: Vec<(String, String)> = facts
2044 .iter()
2045 .map(|f| {
2046 let from = match &f[0] { Value::String(s) => s.clone(), v => panic!("{v:?}") };
2047 let to = match &f[1] { Value::String(s) => s.clone(), v => panic!("{v:?}") };
2048 (from, to)
2049 })
2050 .collect();
2051 pairs.sort();
2052 pairs.dedup();
2053
2054 let expected = vec![
2060 ("/a".to_string(), "/b".to_string()),
2061 ("/a".to_string(), "/c".to_string()),
2062 ("/b".to_string(), "/c".to_string()),
2063 ("/b".to_string(), "/d".to_string()),
2064 ("/c".to_string(), "/d".to_string()),
2065 ];
2066 assert_eq!(pairs, expected, "interval intersection reachability mismatch");
2067
2068 for f in &facts {
2070 let from = match &f[0] { Value::String(s) => s.as_str(), _ => "" };
2071 let to = match &f[1] { Value::String(s) => s.as_str(), _ => "" };
2072 let start = match &f[2] { Value::Time(t) => *t, _ => 0 };
2073 let end = match &f[3] { Value::Time(t) => *t, _ => 0 };
2074
2075 if from == "/a" && to == "/c" {
2076 let jan5: i64 = 1704412800_000_000_000;
2078 let jan10: i64 = 1704844800_000_000_000;
2079 assert_eq!(start, jan5, "a->c start should be Jan 5");
2080 assert_eq!(end, jan10, "a->c end should be Jan 10");
2081 }
2082 if from == "/b" && to == "/d" {
2083 let jan12: i64 = 1705017600_000_000_000;
2085 let jan15: i64 = 1705276800_000_000_000;
2086 assert_eq!(start, jan12, "b->d start should be Jan 12");
2087 assert_eq!(end, jan15, "b->d end should be Jan 15");
2088 }
2089 }
2090
2091 Ok(())
2092 }
2093
2094 #[test]
2099 fn test_temporal_sequence_detection() -> Result<()> {
2100 let arena = Arena::new_with_global_interner();
2101 let source = r#"
2105 Decl event_a(Name) temporal bound [/name].
2106 Decl event_b(Name) temporal bound [/name].
2107
2108 event_a(/u1)@[2024-01-01T10:00:00Z].
2109 event_b(/u1)@[2024-01-01T10:05:00Z].
2110
2111 event_a(/u2)@[2024-01-01T10:00:00Z].
2112 event_b(/u2)@[2024-01-01T10:15:00Z].
2113
2114 seq_pair(U, Diff) :-
2115 event_b(U)@[Tb],
2116 event_a(U)@[Ta],
2117 :time:lt(Ta, Tb)
2118 |> let Diff = fn:time:sub(Tb, Ta).
2119
2120 match_seq(U) :-
2121 seq_pair(U, Diff),
2122 :duration:le(Diff, 600000000000).
2123 "#;
2124
2125 let (mut ir, stratified) = compile(source, &arena)?;
2126 let store = Box::new(MemStore::new());
2127 let interpreter = execute(&mut ir, &stratified, store)?;
2128
2129 let facts: Vec<_> = interpreter
2130 .store()
2131 .scan("match_seq")
2132 .expect("relation match_seq not found")
2133 .collect();
2134
2135 assert_eq!(facts.len(), 1, "expected 1 match, got {:?}", facts);
2137 assert_eq!(facts[0][0], Value::String("/u1".to_string()));
2138
2139 Ok(())
2140 }
2141
2142 #[test]
2144 fn test_temporal_backward_compat_reachability() -> Result<()> {
2145 let arena = Arena::new_with_global_interner();
2146 let source = r#"
2147 edge(/a, /b).
2148 edge(/b, /c).
2149 edge(/c, /d).
2150 path(X, Y) :- edge(X, Y).
2151 path(X, Z) :- edge(X, Y), path(Y, Z).
2152 "#;
2153
2154 let (mut ir, stratified) = compile(source, &arena)?;
2155 let store = Box::new(MemStore::new());
2156 let interpreter = execute(&mut ir, &stratified, store)?;
2157
2158 let facts: Vec<_> = interpreter
2159 .store()
2160 .scan("path")
2161 .expect("relation path not found")
2162 .collect();
2163
2164 let mut pairs: Vec<(String, String)> = facts
2165 .iter()
2166 .map(|f| {
2167 let from = match &f[0] { Value::String(s) => s.clone(), v => panic!("{v:?}") };
2168 let to = match &f[1] { Value::String(s) => s.clone(), v => panic!("{v:?}") };
2169 (from, to)
2170 })
2171 .collect();
2172 pairs.sort();
2173
2174 let expected = vec![
2175 ("/a".to_string(), "/b".to_string()),
2176 ("/a".to_string(), "/c".to_string()),
2177 ("/a".to_string(), "/d".to_string()),
2178 ("/b".to_string(), "/c".to_string()),
2179 ("/b".to_string(), "/d".to_string()),
2180 ("/c".to_string(), "/d".to_string()),
2181 ];
2182 assert_eq!(pairs, expected);
2183
2184 Ok(())
2185 }
2186
2187 #[test]
2189 fn test_temporal_backward_compat_negation() -> Result<()> {
2190 let arena = Arena::new_with_global_interner();
2191 let source = r#"
2192 all(/a). all(/b). all(/c).
2193 excluded(/a).
2194 included(X) :- all(X), !excluded(X).
2195 "#;
2196
2197 let (mut ir, stratified) = compile(source, &arena)?;
2198 let store = Box::new(MemStore::new());
2199 let interpreter = execute(&mut ir, &stratified, store)?;
2200
2201 let facts: Vec<_> = interpreter
2202 .store()
2203 .scan("included")
2204 .expect("relation included not found")
2205 .collect();
2206
2207 let mut values: Vec<String> = facts
2208 .iter()
2209 .map(|f| match &f[0] { Value::String(s) => s.clone(), v => panic!("{v:?}") })
2210 .collect();
2211 values.sort();
2212 assert_eq!(values, vec!["/b", "/c"]);
2213
2214 Ok(())
2215 }
2216}