Embedding

This guide describes the steps required for embedding Logtalk and Logtalk applications.

Embedding the compiler and runtime

The Logtalk 3.x compiler and runtime are implemented using both Prolog and Logtalk source files. When embedding Logtalk, usually to generate a new top level interpreter or an executable that includes it, we first need to save the intermediate Prolog files generated by the compilation of the Logtalk source files. An alternative, available in some backend Prolog compilers such as SICStus Prolog, SWI-Prolog, and YAP is to create a saved state after loading Logtalk and a Logtalk application. We explore here the first option.

After starting Logtalk using your favorite backend Prolog compiler, type the query:

| ?- logtalk_compile([
        core(expanding),
        core(monitoring),
        core(forwarding),
        core(user),
        core(logtalk),
        core(core_messages)
     ], [
        optimize(on),
        scratch_directory('$HOME/collect')
     ]).

Assuming, for example, that you’re using a POSIX system and GNU Prolog as the backend compiler, we need to copy the adapter and default paths files to the directory where we’re collecting all the files ($HOME/collect in our example):

$ cp "$LOGTALKHOME"/adapters/gnu.pl "$HOME"/collect/gnu.pl
$ cp "$LOGTALKHOME"/paths/paths_core.pl "$HOME"/collect/paths_core.pl
$ cp "$LOGTALKHOME"/core/core.pl "$HOME"/collect/core.pl

Now that we have all the necessary files in one place, its a good idea to take a look to the files in the integration directory for the chosen backend Prolog compiler and check if some changes to the core files are advised. For example, in the case of GNU Prolog, we can edit the $HOME/collect/core.pl file and add the line :- built_in. to the top to give all the predicates implementing the Logtalk compiler and runtime built-in status. After this step, the details to embed the collected Prolog files that implement Logtalk depend on the backend Prolog compiler.

As the paths.pl and core.pl files contain initialization goals, the order of embedding of the files is not arbitrary. The initialization goals in the paths.pl file must be called before the initialization goals in the core.pl file. But the order that the GNU Prolog compiler calls the initialization goals is operating-system dependent (see its documentation and the adapters/NOTES.md file for details). Moreover, the files containing the pre-compiled built-in entities, expanding_*_lgt.pl, monitoring_*_lgt.pl, forwarding_*_lgt.pl, user_*_lgt.pl, logtalk_*_lgt.pl, and core_messages_*_lgt.pl must precede, in this order, the core.pl file. Note that we use regular expressions for the files that contain the pre-compiled built-in entities as their names, depending on the backend Prolog compiler, may include a directory hash, which is used to avoid file names conflicts.

Continuing to use GNU Prolog for our tutorial, we can generate a new top-level executable file by typing (ignore the suspicious predicate warnings about {}/1):

$ cd "$HOME"/collect
$ gplc -o logtalk gnu.pl expanding*_lgt.pl monitoring*_lgt.pl forwarding*_lgt.pl user*_lgt.pl logtalk*_lgt.pl core_messages*_lgt.pl core.pl paths.pl

In this case, the paths.pl file is listed after the core.pl file due to gplc oddly collecting all initialization goals in a stack before calling.

The created logtalk executable can now be moved to a suitable place or distributed to its users. Note that the executable will still look by default for a settings.lgt file in the $LOGTALK_STARTUP_DIRECTORY directory (or the startup directory as returned by the Prolog system when the LOGTALK_STARTUP_DIRECTORY is not available) and in the $LOGTALKUSER directory. If necessary, you can turn off loading of settings files by editing the adapter file and changing the value of the settings_file read-only flag to deny.

Embedding an application

To embed not only Logtalk but also a Logtalk application, we also need to collect the intermediate Prolog files generated by the compilation of the Logtalk source files. Using now SWI-Prolog as an example, assume that we want to generate a single QLF file (SWI-Prolog Quick Load Format file) that includes both Logtalk and our application. The steps are similar (and can be easily automated using e.g. a shell script). Assuming a loader.lgt file that loads all our application files, we can type the query:

| ?- set_logtalk_flag(source_data, off),
     set_logtalk_flag(optimize, on), 
     set_logtalk_flag(clean, off),
     set_logtalk_flag(context_switching_calls, deny),
     set_logtalk_flag(scratch_directory, '$HOME/collect_app_files'),
     logtalk_load(loader, [clean(on)]).

Turning off the source_data flag minimizes the size of the generated code (assuming our application doesn’t require the extended set of source data for reflective computations). Setting the context_switching_calls flag to deny ensures that the <</2 debugging control construct cannot be used to break encapsulation in the final version of our application. As we don’t want to collect an intermediate Prolog file for the loader file itself, we load it using the option clean(on). This assumes, of course, that the loader files don’t do anything else than loading the application files (as recommended).

Your application may use libraries with their own loader files. If so, these files should be deleted before proceeding:

$ rm -f "$HOME"/collect_app_files/*loader*

In alternative, the embedding scripts (see below) provide an option to compile (i.e. expand) library alias paths in paths and settings files. This option allows, in general, the safe inclusion of the application loader files.

Now that we have all necessary application files in the $HOME/collect_app_files/ directory, we can concatenate them in a single Prolog file (on POSIX system we can use the cat utility in an automation script), say, app_resources.pl :

$ cat $(ls -t $HOME/collect_app_files/*.pl) > app_resources.pl

But for the Logtalk own files you cannot use the same trick as the order (show above) is important. Not a problem as there are only nine files to concatenate into, say, a logtalk_resources.pl file:

$ cd "$HOME"/collect
$ cp "$LOGTALKHOME"/adapters/swi.pl .
$ cp "$LOGTALKHOME"/paths/paths_core.pl .
$ cp "$LOGTALKHOME"/core/core.pl .
$ cat swi.pl paths_core.pl expanding_*_lgt.pl monitoring_*_lgt.pl forwarding_*_lgt.pl user_*_lgt.pl logtalk_*_lgt.pl core_messages_*_lgt.pl core.pl > logtalk_resources.pl

We just need an intermediate step before generating the .qlf file:

$ cat logtalk_resources.pl ../collect_app_files/app_resources.pl > resources.pl

With all the code in a single file, we can now compiled it into a .qlf file:

$ swipl -q -g "qcompile(resources)" -t halt

These steps are just a guideline. You may want e.g. to add a settings file, set other flags for compiling the final version of your application, define additional library paths, or include required foreign language resources.

Embedding scripts

Most of the embedding steps described above can easily be automated. For example embedding scripts for selected backend Prolog compilers, see:

https://github.com/LogtalkDotOrg/logtalk3/blob/master/scripts/embedding/

For embedding details for specific backend Prolog compilers see those compilers documentation and the following notes:

https://github.com/LogtalkDotOrg/logtalk3/blob/master/adapters/NOTES.md

If you’re using Windows, a possible solution to run these scripts is to either install the Windows Subsystem for Linux (WSL) or install Git for Windows, which includes a Bash shell implementation (see also the testing guide for Bash configuration details).