I thought I would just share how I do my daily development with SWI Prolog at the moment. I am following the guidelines on project structure and to that end I have a
load.pl file that contains the code that sets up my REPL environment and it’s now very useful and feels like an old pair of worn comfy slippers.
Extending the file search paths
Once you get to a certain size of project you’ll probably want to create a
./src folder to better arrange your files. For reasons I won’t explain here, SWI Prolog looks for test files in the same folder as the source file, so for example the file
foo.pl has its tests in the file
foo.plt. T for test I guess.
This is the first time I have done this so my solution may not be the most elegant but it works. First we need to inform the compiler that the predicate
user:file_search_path is one that is allowed to be defined across multiple files
:- multifile user:file_search_path/2. :- prolog_load_context(directory, Dir), asserta(user:file_search_path(myapp, Dir)), format(atom(S), '~s/src', [Dir]), asserta(user:file_search_path(myapp, S)), format(atom(A), '~s/src/ast', [Dir]), asserta(user:file_search_path(myapp, A)), format(atom(T), '~s/src/tokeniser', [Dir]), asserta(user:file_search_path(myapp, T)), format(atom(C), '~s/src/coder', [Dir]), asserta(user:file_search_path(myapp, C)).
:- is basically a “compile time” expression of a predicate. Code that runs when the file is loaded not when the actual application gets to run later. What it does as associate the alias
myapp with these folder relative to the project root:
./src/ ./src/ast ./src/tokeniser ./src/coder
Firing it off
And next, we have these lines:-
Once a file has been loaded and compiled, if there is a registered initialisation goal (
setup, in this case) then it is called. What I do here is ask that all test files be loaded, set the current REPL module to be the user module and finally open all my most used source files.
Notice that the typical format is:
and this will then search all the paths registered above looking for a source file defines the module
foo, typically the file
foo.pl, but not necessarily so and the first line of that file must be:
(Looks like Erlang doesn’t it…I wonder why?! :O )
The next thing encountered is more compile time directives that cause the required modules to be loaded into the engine. Because this happens at compile time and the previous
setup runs at run time, this means that the call to
load_test_files() also loads in any file it can find with the same name as a loaded source file but that has the file extension
.plt. This is very slick because there is a command,
make that automatically reloads any files changed on disk and then re-runs any unit tests affected by those changed files.
In the built-in editor, there is a shortcut to run make and this leads to a very nice way of working. You open a normal terminal, start a session, open the files, then as you plough your way through the day all you do is:
Ctrl-X s % saves the current file Ctrl-c m % pokes the REPL to reload and run tests
And immediately you can see if any tests failed. It’s really nice actually!
Once yo get into the swing of it you start to realise that you can use Prolog to extend the capabilities of the REPL both transparently and easily. The following are some things that I now find I can’t live without on the project that this code is from (a mother of a transpiler).
So simple but very useful at times especially after running an AST run and wanting the see it all on the page.
All it does is send standard ANSII escape sequences to cause the terminal to clear and “home” the cursor.
Abbreviated stock commands.
Call me lazy but typing
help and apropos gets tiring after a while so I just shortened them!
Finding stuff in files
This one just shells a
find command. Sure it’s easy to drop out with
Ctrl-z, run any command and then type
fg but this way I don’t have to be bothered.
I absolutely litter files with
%TODO: notices any time something pops into my head, even if the thought and the current file are in no way related. I just type it out and forget it. Then later on I can issue the
todo command and see what needs doing next.
edit/1 is your friend.
It’s taken me a while but I now know that once your source files are loaded, you can forget them! If you want to edit a particular piece of code, say a predicate called
frobulator then you don’t need to know the file it lives in because the REPL already does, all you do is pass the name of the predicate,
If there is only one file then it just opens in the editor and Voila, off you go. If however it was a multlifile predicate, or there is more than one file that matches, you might see something like this:
?- edit(ast). Please select item to edit: 1 <loaded file> 'src/ast/ast.pl':1 2 ast:ast/2 'src/ast/ast.pl':70 3 ast:ast/3 'src/ast/ast.pl':74 4 <loaded file> 'src/ast/ast.plt' Your choice?
All you do is press the number on the left and again, the relevant file just appears in the editor on the correct line. Note that 2 and 3 above would be the two implementations of that predicate.
I will definitely be writing a lot more about Prolog on the coming months as I think that for a language first launched in 1972 it has some mind bendingly powerful features that should be shared!