In the previous chapters, we have seen how to compile Prolog predicates into Bash scripts. However, the process involved manually compiling each predicate and its dependencies. While the inference-based test runner helped in automating the testing, the compilation process itself was not as seamless as it could be.
This chapter introduces the Compiler Driver, a new module that provides a truly recursive, “one-click” compilation experience.
Previously, to compile the ancestor/2 rule, we had to:
parent/2 facts into parent.sh.ancestor/2 rule into ancestor.sh.This works for simple examples, but in a large project with complex dependencies, it becomes tedious and error-prone.
The new compiler_driver.pl module solves this problem. It provides a single entry point, compile/3, that automatically finds and compiles all dependencies of a given predicate.
When you call compile/3 on a predicate:
ancestor/2, it will find the dependency on parent/2.Let’s see how this simplifies our ancestor/2 example.
Start SWI-Prolog and load the init.pl file as before. Then, load the new compiler_driver module.
?- ['education/init'].
true.
?- use_module(unifyweaver(core/compiler_driver)).
true.
ancestor/2Now, we can compile the ancestor/2 predicate with a single call. We will also load our family_tree.pl file to make our predicates available to the compiler.
?- ['education/family_tree'].
true.
?- compile(ancestor/2, [output_dir('education/output/recursive')], GeneratedScripts).
Notice the new output_dir option, which tells the compiler where to save the generated files. After the command finishes, the GeneratedScripts variable will contain a list of all the files that were created:
GeneratedScripts = ['education/output/recursive/parent.sh', 'education/output/recursive/ancestor.sh'].
As you can see, the compiler automatically found the parent/2 dependency and compiled it as well.
This new recursive compilation makes creating a test runner trivial. The GeneratedScripts variable gives us the exact list of files we need to source.
Here is a simple Prolog predicate that can generate a test runner for any compiled predicate:
generate_test_runner(Predicate, TestRunnerPath) :-
compile(Predicate, [output_dir('education/output/recursive')], GeneratedScripts),
open(TestRunnerPath, write, Stream),
write(Stream, '#!/bin/bash\n'),
forall(member(Script, GeneratedScripts),
format(Stream, 'source ~w\n', [Script])),
format(Stream, '\n# --- Add your tests here ---\n'),
format(Stream, 'ancestor abraham jacob && echo "PASS" || echo "FAIL"\n'),
close(Stream).
You could run this like so:
?- generate_test_runner(ancestor/2, 'education/output/recursive/test_ancestor.sh').
true.
Then, you can run the generated test script from your terminal:
$ bash education/output/recursive/test_ancestor.sh
The compiler driver represents a significant step forward in the usability of UnifyWeaver. By automatically handling dependencies, it makes the compilation process more robust, less error-prone, and much more seamless, truly living up to the goal of one-click compilation from a declarative source.
| ← Previous: Chapter 11: Test Runner Inference | 📖 Book 2: Bash Target | Next: Chapter 13: Partitioning and Parallel Execution → |