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 if let ast::Term::Atom(atom) = premise {
125 all_preds.insert(atom.sym);
126 } else if let ast::Term::NegAtom(atom) = premise {
127 all_preds.insert(atom.sym);
128 }
129 }
130 }
131
132 for pred in all_preds {
133 if !idb_preds.contains(&pred) {
134 program.ext_preds.push(pred);
135 }
136 }
137
138 let stratified = program.stratify().map_err(|e| anyhow!(e))?;
139
140 let ctx = LoweringContext::new(arena);
141 let ir = ctx.lower_unit(unit);
142
143 Ok((ir, stratified))
144}
145
146pub fn compile_to_wasm(ir: &mut Ir, stratified: &StratifiedProgram) -> Vec<u8> {
151 let mut codegen = Codegen::new_with_stratified(ir, stratified, WasmImportsBackend);
152 codegen.generate()
153}
154
155pub fn execute<'a>(
165 ir: &'a mut Ir,
166 stratified: &StratifiedProgram<'a>,
167 store: Box<dyn Store + 'a>,
168) -> Result<Interpreter<'a>> {
169 let arena = stratified.arena();
170
171 let mut strata_plans = Vec::new();
173
174 for stratum in stratified.strata() {
175 let mut stratum_pred_names = FxHashSet::default();
176 for pred in &stratum {
177 if let Some(name) = arena.predicate_name(*pred) {
178 stratum_pred_names.insert(name);
179 }
180 }
181
182 let mut rule_ids = Vec::new();
184 for (i, inst) in ir.insts.iter().enumerate() {
185 if let Inst::Rule { head, .. } = inst
186 && let Inst::Atom { predicate, .. } = ir.get(*head)
187 {
188 let head_name = ir.resolve_name(*predicate);
189 if stratum_pred_names.contains(head_name) {
190 rule_ids.push(InstId::new(i));
191 }
192 }
193 }
194
195 if rule_ids.is_empty() {
196 strata_plans.push(None);
197 continue;
198 }
199
200 let mut is_recursive = false;
202 for &rule_id in &rule_ids {
203 if let Inst::Rule { premises, .. } = ir.get(rule_id) {
204 for &premise in premises {
205 if let Inst::Atom { predicate, .. } = ir.get(premise) {
206 let pred_name = ir.resolve_name(*predicate);
207 if stratum_pred_names.contains(pred_name) {
208 is_recursive = true;
209 break;
210 }
211 }
212 }
213 }
214 if is_recursive {
215 break;
216 }
217 }
218
219 if !is_recursive {
220 let mut ops = Vec::new();
221 for rule_id in rule_ids {
222 let planner = Planner::new(ir);
223 ops.push(planner.plan_rule(rule_id)?);
224 }
225 strata_plans.push(Some(StratumPlan::NonRecursive(ops)));
226 } else {
227 let mut initial_ops = Vec::new();
228 for &rule_id in &rule_ids {
229 let planner = Planner::new(ir);
230 initial_ops.push(planner.plan_rule(rule_id)?);
231 }
232
233 let mut delta_plans = Vec::new();
234 for &rule_id in &rule_ids {
235 let premises = if let Inst::Rule { premises, .. } = ir.get(rule_id) {
236 premises.clone()
237 } else {
238 continue;
239 };
240
241 for &premise in &premises {
242 let (predicate, pred_name) =
243 if let Inst::Atom { predicate, .. } = ir.get(premise) {
244 (*predicate, ir.resolve_name(*predicate).to_string())
245 } else {
246 continue;
247 };
248
249 if stratum_pred_names.contains(pred_name.as_str()) {
250 let planner = Planner::new(ir).with_delta(predicate);
251 delta_plans.push(planner.plan_rule(rule_id)?);
252 }
253 }
254 }
255 strata_plans.push(Some(StratumPlan::Recursive {
256 initial_ops,
257 delta_plans,
258 }));
259 }
260 }
261
262 let mut interpreter = Interpreter::new(ir, store);
264
265 for pred in stratified.extensional_preds() {
267 if let Some(name) = arena.predicate_name(pred) {
268 interpreter.store_mut().create_relation(name);
269 }
270 }
271
272 for plan in strata_plans {
273 match plan {
274 Some(StratumPlan::NonRecursive(ops)) => {
275 for op in ops {
276 interpreter.execute(&op)?;
277 }
278 }
279 Some(StratumPlan::Recursive {
280 initial_ops,
281 delta_plans,
282 }) => {
283 for op in initial_ops {
284 interpreter.execute(&op)?;
285 }
286 interpreter.store_mut().merge_deltas();
287
288 loop {
289 let mut changes = 0;
290 for op in &delta_plans {
291 changes += interpreter.execute(op)?;
292 }
293 if changes == 0 {
294 break;
295 }
296 interpreter.store_mut().merge_deltas();
297 }
298 }
299 None => {}
300 }
301 interpreter.store_mut().merge_deltas();
302 }
303
304 Ok(interpreter)
305}
306
307enum StratumPlan {
308 NonRecursive(Vec<mangle_ir::physical::Op>),
309 Recursive {
310 initial_ops: Vec<mangle_ir::physical::Op>,
311 delta_plans: Vec<mangle_ir::physical::Op>,
312 },
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use mangle_interpreter::{MemStore, Value};
319
320 #[test]
321 fn test_driver_e2e() -> Result<()> {
322 let arena = Arena::new_with_global_interner();
323 let source = r#"
324 p(1).
325 p(2).
326 q(X) :- p(X).
327 "#;
328
329 let (mut ir, stratified) = compile(source, &arena)?;
330 let store = Box::new(MemStore::new());
331 let interpreter = execute(&mut ir, &stratified, store)?;
332
333 let facts: Vec<_> = interpreter
335 .store()
336 .scan("q")
337 .expect("relation q not found")
338 .collect();
339 assert!(!facts.is_empty(), "relation q not found");
340
341 let mut values: Vec<i64> = facts
342 .iter()
343 .map(|t| match t[0] {
344 Value::Number(n) => n,
345 _ => panic!("expected number"),
346 })
347 .collect();
348 values.sort();
349
350 assert_eq!(values, vec![1, 2]);
351
352 Ok(())
353 }
354
355 #[test]
356 fn test_driver_e2e_with_package() -> Result<()> {
357 let arena = Arena::new_with_global_interner();
358 let source = r#"
359 Package pkg!
360 p(1).
361 q(X) :- p(X).
362 "#;
363
364 let (mut ir, stratified) = compile(source, &arena)?;
365 let store = Box::new(MemStore::new());
366 let interpreter = execute(&mut ir, &stratified, store)?;
367
368 let facts: Vec<_> = interpreter
370 .store()
371 .scan("pkg.q")
372 .expect("relation pkg.q not found")
373 .collect();
374 assert!(!facts.is_empty(), "relation pkg.q not found");
375
376 let values: Vec<i64> = facts
377 .iter()
378 .map(|t| match t[0] {
379 Value::Number(n) => n,
380 _ => panic!("expected number"),
381 })
382 .collect();
383 assert_eq!(values, vec![1]);
384
385 Ok(())
386 }
387
388 #[test]
389 fn test_driver_let_transform() -> Result<()> {
390 let arena = Arena::new_with_global_interner();
391 let source = r#"
392 p(1).
393 p(2).
394 q(Y) :- p(X) |> let Y = fn:plus(X, 10).
395 "#;
396
397 let (mut ir, stratified) = compile(source, &arena)?;
398 let store = Box::new(MemStore::new());
399 let interpreter = execute(&mut ir, &stratified, store)?;
400
401 let facts: Vec<_> = interpreter
402 .store()
403 .scan("q")
404 .expect("relation q not found")
405 .collect();
406 let mut values: Vec<i64> = facts
407 .iter()
408 .map(|t| match t[0] {
409 Value::Number(n) => n,
410 _ => panic!("expected number"),
411 })
412 .collect();
413 values.sort();
414
415 assert_eq!(values, vec![11, 12]);
416 Ok(())
417 }
418
419 #[test]
420 fn test_driver_aggregation() -> Result<()> {
421 let arena = Arena::new_with_global_interner();
422 let source = r#"
423 p(1, 10).
424 p(1, 20).
425 p(2, 30).
426 q(K, S) :- p(K, V) |> do fn:group_by(K); let S = fn:sum(V).
427 "#;
428
429 let (mut ir, stratified) = compile(source, &arena)?;
430 let store = Box::new(MemStore::new());
431 let interpreter = execute(&mut ir, &stratified, store)?;
432
433 let facts: Vec<_> = interpreter
434 .store()
435 .scan("q")
436 .expect("relation q not found")
437 .collect();
438 let mut results: Vec<(i64, i64)> = facts
439 .iter()
440 .map(|t| {
441 if let (Value::Number(k), Value::Number(s)) = (&t[0], &t[1]) {
442 (*k, *s)
443 } else {
444 panic!("expected numbers");
445 }
446 })
447 .collect();
448 results.sort();
449
450 assert_eq!(results, vec![(1, 30), (2, 30)]);
451 Ok(())
452 }
453
454 #[test]
455 fn test_driver_aggregation_count() -> Result<()> {
456 let arena = Arena::new_with_global_interner();
457 let source = r#"
458 p(1, 10).
459 p(1, 20).
460 p(2, 30).
461 q(K, C) :- p(K, V) |> do fn:group_by(K); let C = fn:count(V).
462 "#;
463
464 let (mut ir, stratified) = compile(source, &arena)?;
465 let store = Box::new(MemStore::new());
466 let interpreter = execute(&mut ir, &stratified, store)?;
467
468 let facts: Vec<_> = interpreter
469 .store()
470 .scan("q")
471 .expect("relation q not found")
472 .collect();
473 let mut results: Vec<(i64, i64)> = facts
474 .iter()
475 .map(|t| {
476 if let (Value::Number(k), Value::Number(c)) = (&t[0], &t[1]) {
477 (*k, *c)
478 } else {
479 panic!("expected numbers");
480 }
481 })
482 .collect();
483 results.sort();
484
485 assert_eq!(results, vec![(1, 2), (2, 1)]);
486 Ok(())
487 }
488
489 #[test]
490 fn test_driver_reachability() -> Result<()> {
491 let arena = Arena::new_with_global_interner();
492 let source = r#"
493 edge(1, 2).
494 edge(2, 3).
495 edge(3, 4).
496 edge(4, 5).
497 reachable(X, Y) :- edge(X, Y).
498 reachable(X, Z) :- reachable(X, Y), edge(Y, Z).
499 "#;
500
501 let (mut ir, stratified) = compile(source, &arena)?;
502 let store = Box::new(MemStore::new());
503 let interpreter = execute(&mut ir, &stratified, store)?;
504
505 let facts: Vec<_> = interpreter
506 .store()
507 .scan("reachable")
508 .expect("reachable relation not found")
509 .collect();
510 assert_eq!(facts.len(), 10); let mut reachable_from_1: Vec<i64> = facts
513 .iter()
514 .filter(|t| t[0] == Value::Number(1))
515 .map(|t| match t[1] {
516 Value::Number(n) => n,
517 _ => panic!("expected number"),
518 })
519 .collect();
520 reachable_from_1.sort();
521 assert_eq!(reachable_from_1, vec![2, 3, 4, 5]);
522
523 Ok(())
524 }
525
526 #[test]
527 fn test_name_constants() -> Result<()> {
528 let arena = Arena::new_with_global_interner();
529 let source = r#"
530 role(/role/admin).
531 role(/role/user).
532 role(/role/application).
533 "#;
534
535 let (mut ir, stratified) = compile(source, &arena)?;
536 let store = Box::new(MemStore::new());
537 let interpreter = execute(&mut ir, &stratified, store)?;
538
539 let facts: Vec<_> = interpreter
540 .store()
541 .scan("role")
542 .expect("relation role not found")
543 .collect();
544 assert_eq!(facts.len(), 3);
545
546 let mut names: Vec<String> = facts
547 .iter()
548 .map(|t| match &t[0] {
549 Value::String(s) => s.clone(),
550 _ => panic!("expected string"),
551 })
552 .collect();
553 names.sort();
554 assert_eq!(
555 names,
556 vec!["/role/admin", "/role/application", "/role/user"]
557 );
558
559 Ok(())
560 }
561
562 #[test]
563 fn test_inequality() -> Result<()> {
564 let arena = Arena::new_with_global_interner();
565 let source = r#"
569 role("admin").
570 role("user").
571 role("application").
572 non_app_role(R) :- role(R), R != "application".
573 "#;
574
575 let (mut ir, stratified) = compile(source, &arena)?;
576 let store = Box::new(MemStore::new());
577 let interpreter = execute(&mut ir, &stratified, store)?;
578
579 let facts: Vec<_> = interpreter
580 .store()
581 .scan("non_app_role")
582 .expect("relation non_app_role not found")
583 .collect();
584 assert_eq!(facts.len(), 2);
585
586 let mut names: Vec<String> = facts
587 .iter()
588 .map(|t| match &t[0] {
589 Value::String(s) => s.clone(),
590 _ => panic!("expected string"),
591 })
592 .collect();
593 names.sort();
594 assert_eq!(names, vec!["admin", "user"]);
595
596 Ok(())
597 }
598
599 #[test]
600 fn test_negation() -> Result<()> {
601 let arena = Arena::new_with_global_interner();
602 let source = r#"
603 service("web").
604 service("api").
605 service("db").
606 has_dep("web").
607 has_dep("api").
608 no_dep(S) :- service(S), !has_dep(S).
609 "#;
610
611 let (mut ir, stratified) = compile(source, &arena)?;
612 let store = Box::new(MemStore::new());
613 let interpreter = execute(&mut ir, &stratified, store)?;
614
615 let facts: Vec<_> = interpreter
616 .store()
617 .scan("no_dep")
618 .expect("relation no_dep not found")
619 .collect();
620 assert_eq!(facts.len(), 1);
621 assert_eq!(facts[0][0], Value::String("db".to_string()));
622
623 Ok(())
624 }
625
626 #[test]
627 fn test_combined_name_ineq_negation() -> Result<()> {
628 let arena = Arena::new_with_global_interner();
630 let source = r#"
631 container("web", /status/running).
632 container("api", /status/running).
633 container("db", /status/stopped).
634 depends_on("web", "db").
635 depends_on("api", "db").
636
637 running(Name) :- container(Name, /status/running).
638 stopped(Name) :- container(Name, /status/stopped).
639 has_running_dep(Name) :- depends_on(Name, Dep), running(Dep).
640 needs_attention(Name) :- depends_on(Name, Dep), stopped(Dep).
641 independent(Name) :- running(Name), !has_running_dep(Name).
642 "#;
643
644 let (mut ir, stratified) = compile(source, &arena)?;
645 let store = Box::new(MemStore::new());
646 let interpreter = execute(&mut ir, &stratified, store)?;
647
648 let running: Vec<_> = interpreter
650 .store()
651 .scan("running")
652 .expect("relation running not found")
653 .collect();
654 assert_eq!(running.len(), 2);
655
656 let stopped: Vec<_> = interpreter
658 .store()
659 .scan("stopped")
660 .expect("relation stopped not found")
661 .collect();
662 assert_eq!(stopped.len(), 1);
663 assert_eq!(stopped[0][0], Value::String("db".to_string()));
664
665 let needs_attention: Vec<_> = interpreter
667 .store()
668 .scan("needs_attention")
669 .expect("relation needs_attention not found")
670 .collect();
671 assert_eq!(needs_attention.len(), 2);
672
673 let independent: Vec<_> = interpreter
676 .store()
677 .scan("independent")
678 .expect("relation independent not found")
679 .collect();
680 assert_eq!(independent.len(), 2);
681
682 Ok(())
683 }
684
685 #[test]
686 fn test_join_with_constants_in_second_atom() -> Result<()> {
687 let arena = Arena::new_with_global_interner();
691 let source = r#"
692 p("a", "x").
693 q("a", "y").
694 test(E) :- p(E, "x"), q(E, "y").
695 "#;
696
697 let (mut ir, stratified) = compile(source, &arena)?;
698 let store = Box::new(MemStore::new());
699 let interpreter = execute(&mut ir, &stratified, store)?;
700
701 let facts: Vec<_> = interpreter
702 .store()
703 .scan("test")
704 .expect("relation test not found")
705 .collect();
706
707 assert_eq!(facts.len(), 1, "expected 1 result, got {:?}", facts);
708 assert_eq!(facts[0][0], Value::String("a".to_string()));
709
710 Ok(())
711 }
712
713 #[test]
714 fn test_join_constant_only_in_second_atom() -> Result<()> {
715 let arena = Arena::new_with_global_interner();
717 let source = r#"
718 p("a", "x").
719 q("a", "y").
720 test(E, V) :- p(E, V), q(E, "y").
721 "#;
722
723 let (mut ir, stratified) = compile(source, &arena)?;
724 let store = Box::new(MemStore::new());
725 let interpreter = execute(&mut ir, &stratified, store)?;
726
727 let facts: Vec<_> = interpreter
728 .store()
729 .scan("test")
730 .expect("relation test not found")
731 .collect();
732
733 assert_eq!(facts.len(), 1, "expected 1 result, got {:?}", facts);
734 assert_eq!(facts[0][0], Value::String("a".to_string()));
735 assert_eq!(facts[0][1], Value::String("x".to_string()));
736
737 Ok(())
738 }
739
740 #[test]
741 fn test_compile_units_package_use() -> Result<()> {
742 let arena = Arena::new_with_global_interner();
743
744 let schema = r#"
745 Package config_schema !
746 Decl server_port(Port).
747 Decl programs_dir(Path).
748 "#;
749
750 let config = r#"
751 Use config_schema !
752 config_schema.server_port(8090).
753 config_schema.programs_dir("/programs").
754 "#;
755
756 let (mut ir, stratified) = compile_units(&[schema, config], &arena)?;
757 let store = Box::new(MemStore::new());
758 let interpreter = execute(&mut ir, &stratified, store)?;
759
760 let port_facts: Vec<_> = interpreter
762 .store()
763 .scan("config_schema.server_port")
764 .expect("relation config_schema.server_port not found")
765 .collect();
766 assert_eq!(port_facts.len(), 1);
767 assert_eq!(port_facts[0][0], Value::Number(8090));
768
769 let dir_facts: Vec<_> = interpreter
770 .store()
771 .scan("config_schema.programs_dir")
772 .expect("relation config_schema.programs_dir not found")
773 .collect();
774 assert_eq!(dir_facts.len(), 1);
775 assert_eq!(dir_facts[0][0], Value::String("/programs".to_string()));
776
777 Ok(())
778 }
779
780 #[test]
781 fn test_compile_to_wasm() -> Result<()> {
782 let arena = Arena::new_with_global_interner();
783 let source = r#"
784 p(1).
785 q(X) :- p(X).
786 "#;
787
788 let (mut ir, stratified) = compile(source, &arena)?;
789 let wasm_bytes = compile_to_wasm(&mut ir, &stratified);
790
791 assert!(!wasm_bytes.is_empty());
793 assert_eq!(&wasm_bytes[0..4], b"\0asm"); Ok(())
796 }
797}