4.3 Loading Prolog source files
AllApplicationManualNameSummaryHelp

  • Documentation
    • Reference manual
      • Built-in Predicates
        • Loading Prolog source files
          • load_files/1
          • load_files/2
          • consult/1
          • ensure_loaded/1
          • include/1
          • require/1
          • encoding/1
          • make/0
          • library_directory/1
          • file_search_path/2
          • expand_file_search_path/2
          • prolog_file_type/2
          • source_file/1
          • source_file/2
          • source_file_property/2
          • exists_source/1
          • exists_source/2
          • unload_file/1
          • prolog_load_context/2
          • source_location/2
          • at_halt/1
          • cancel_halt/1
          • initialization/1
          • initialization/2
          • initialize/0
          • compiling/0
          • Conditional compilation and program transformation
            • term_expansion/2
            • expand_term/2
            • goal_expansion/2
            • expand_goal/2
            • compile_aux_clauses/1
            • dcg_translate_rule/2
            • var_property/2
            • Program transformation with source layout info
            • Conditional compilation
          • Reloading files, active code and threads
          • Quick load files
    • Packages

4.3.1 Conditional compilation and program transformation

ISO Prolog defines no way for program transformations such as macro expansion or conditional compilation. Expansion through term_expansion/2 and expand_term/2 can be seen as part of the de-facto standard. This mechanism can do arbitrary translation between valid Prolog terms read from the source file to Prolog terms handed to the compiler. As term_expansion/2 can return a list, the transformation does not need to be term-to-term.

Various Prolog dialects provide the analogous goal_expansion/2 and expand_goal/2 that allow for translation of individual body terms, freeing the user of the task to disassemble each clause.

term_expansion(+Term1, -Term2)
Dynamic and multifile predicate, normally not defined. When defined by the user all terms read during consulting are given to this predicate. If the predicate succeeds Prolog will assert Term2 in the database rather than the read term (Term1). Term2 may be a term of the form ?- Goal. or :- Goal. Goal is then treated as a directive. If Term2 is a list, all terms of the list are stored in the database or called (for directives). If Term2 is of the form below, the system will assert Clause and record the indicated source location with it:
’$source_location'(<File>, <Line>):<Clause>

When compiling a module (see chapter 6 and the directive module/2), expand_term/2 will first try term_expansion/2 in the module being compiled to allow for term expansion rules that are local to a module. If there is no local definition, or the local definition fails to translate the term, expand_term/2 will try term_expansion/2 in module user. For compatibility with SICStus and Quintus Prolog, this feature should not be used. See also expand_term/2, goal_expansion/2 and expand_goal/2.

It is possible to act on the beginning and end of a file by expanding the terms begin_of_file and end_of_file. The latter is supported by most Prolog systems that support term expansion as read_term/3 returns end_of_file on reaching the end of the input. Expanding begin_of_file may be used to initialise the compilation, for example base on the file name extension. It was added in SWI-Prolog 8.1.1.

The current macro-expansion mechanism originates from Prolog systems in the 1980s and 1990s. It has several flaws, (1) the hooks act globally (except for definitions in a module), (2) it is hard to deal with interactions between transformations, (3) macros can not be reused between modules using the normal module export/import protocol and (4) it is hard to make source code aware tools such as the graphical debugger act properly in the context of macro expansion. Several Prolog implementations have tried to implement better expansion mechanisms. None of these solve all problems and all are largely incompatible with our current macro expansion. Future versions may provide a new mechanism to solve these issues.

Controlled interaction is provided between macro expansion defined in a module and the user and system modules. Here, SWI-Prolog uses a pipeline where the result of local module expansion is the input for the expansion in user, which is the input for the expansion in system. See also section 6.10.

Scoping, i.e., make a rule defined in a module only active if this module is imported into the module being compiled, can be emulated by defining the macro globally in the user module and using prolog_load_context/2 and some logic to verify the macro expansion should apply. If (goal) expansion effectively defined inlining it is good practice to also define the predicate and have the macro expansion check that the predicate is in scope. Here is an example.

:- module(m1, [double/2]).

double(X, D) :- D is X*2.

user:goal_expansion(double(X,D), D is X*2) :-
    prolog_load_context(module, M),
    predicate_property(M:double(_,_), imported_from(m1)).

For term expansion that is not related to a specific predicate we can define a sentinel predicate rather than using the goal predicate and check it is imported into the current module to verify that the module that defines the expansion is imported into the current compilation context.

expand_term(+Term1, -Term2)
This predicate is normally called by the compiler on terms read from the input to perform preprocessing. It consists of four steps, where each step processes the output of the previous step.

  1. Test conditional compilation directives and translate all input to [] if we are in a‘false branch' of the conditional compilation. See section 4.3.1.2.

  2. Call term_expansion/2. This predicate is first tried in the module that is being compiled and then in modules from which this module inherits according to default_module/2. The output of the expansion in a module is used as input for the next module. Using the default setup and when compiling a normal application module M, this implies expansion is executed in M, user and finally in system. Library modules inherit directly from system and can thus not be re-interpreted by term expansion rules in user.

  3. Call DCG expansion (dcg_translate_rule/2).

  4. Call expand_goal/2 on each body term that appears in the output of the previous steps.
goal_expansion(+Goal1, -Goal2)
Like term_expansion/2, goal_expansion/2 provides for macro expansion of Prolog source code. Between expand_term/2 and the actual compilation, the body of clauses analysed and the goals are handed to expand_goal/2, which uses the goal_expansion/2 hook to do user-defined expansion.

The predicate goal_expansion/2 is first called in the module that is being compiled, and then follows the module inheritance path as defined by default_module/2, i.e., by default user and system. If Goal is of the form Module:Goal where Module is instantiated, goal_expansion/2 is called on Goal using rules from module Module followed by default modules for Module.

Only goals appearing in the body of clauses when reading a source file are expanded using this mechanism, and only if they appear literally in the clause, or as an argument to a defined meta-predicate that is annotated using‘0' (see meta_predicate/1). Other cases need a real predicate definition.

The expansion hook can use prolog_load_context/2 to obtain information about the context in which the goal is expanded such as the module, variable names or the encapsulating term.

expand_goal(+Goal1, -Goal2)
This predicate is normally called by the compiler to perform preprocessing using goal_expansion/2. The predicate computes a fixed-point by applying transformations until there are no more changes. If optimisation is enabled (see -O and optimise), expand_goal/2 simplifies the result by removing unneeded calls to true/0 and fail/0 as well as trivially unreachable branches.

If goal_expansion/2 wraps a goal as in the example below the system still reaches fixed-point as it prevents re-expanding the expanded term while recursing. It does re-enable expansion on the arguments of the expanded goal as illustrated in t2/1 in the example.59After discussion with Peter Ludemann and Paulo Moura on the forum.

:- meta_predicate run(0).

may_not_fail(test(_)).
may_not_fail(run(_)).

goal_expansion(G, (G *-> true ; error(goal_failed(G),_))) :-
    may_not_fail(G).

t1(X) :- test(X).
t2(X) :- run(run(X)).

Is expanded into

t1(X) :-
    (   test(X)
    *-> true
    ;   error(goal_failed(test(X)), _)
    ).

t2(X) :-
    (   run((run(X)*->true;error(goal_failed(run(X)), _)))
    *-> true
    ;   error(goal_failed(run(run(X))), _)
    ).

Note that goal expansion should not bind any variables in the clause. Doing so may impact the semantics of the clause if the variable is also used elsewhere. In the general case this is not verified. It is verified for \+/1 and ;/2, resulting in an exception.

compile_aux_clauses(+Clauses)
Compile clauses on behalf of goal_expansion/2. This predicate compiles the argument clauses into static predicates, associating the predicates with the current file but avoids changing the notion of current predicate and therefore discontiguous warnings.

Note that in some cases multiple expansions of similar goals can share the same compiled auxiliary predicate. In such cases, the implementation of goal_expansion/2 can use predicate_property/2 using the property defined to test whether the predicate is already defined in the current context.

dcg_translate_rule(+In, -Out)
This predicate performs the translation of a term Head-->Body into a normal Prolog clause. Normally this functionality should be accessed using expand_term/2.
var_property(+Var, ?Property)
True when Property is a property of Var. These properties are available during goal- and term-expansion. Defined properties are below. Future versions are likely to provide more properties, such as whether the variable is referenced in the remainder of the term. See also goal_expansion/2.
fresh(Bool)
Bool has the value true if the variable is guaranteed to be unbound at entry of the goal, otherwise its value is false. This implies that the variable first appears in this goal or a previous appearance was in a negation (\+/1) or a different branch of a disjunction.
singleton(Bool)
Bool has the value true if the variable is a syntactic singleton in the term it appears in. Note that this tests that the variable appears exactly once in the term being expanded without making any claim on the syntax of the variable. Variables that appear only once in multiple branches are not singletons according to this property. Future implementations may improve on that.
name(Name)
True when variable appears with the given name in the source.

4.3.1.1 Program transformation with source layout info

This sections documents extended versions of the program transformation predicates that also transform the source layout information. Extended layout information is currently processed, but unused. Future versions will use for the following enhancements:

  • More precise locations of warnings and errors
  • More reliable setting of breakpoints
  • More reliable source layout information in the graphical debugger.
expand_goal(+Goal1, ?Layout1, -Goal2, -Layout2)
goal_expansion(+Goal1, ?Layout1, -Goal2, -Layout2)
expand_term(+Term1, ?Layout1, -Term2, -Layout2)
term_expansion(+Term1, ?Layout1, -Term2, -Layout2)
dcg_translate_rule(+In, ?LayoutIn, -Out, -LayoutOut)
These versions are called before their 2-argument counterparts. The input layout term is either a variable (if no layout information is available) or a term carrying detailed layout information as returned by the subterm_positions of read_term/2. The output layout should be a variable if no layout information can be computed for the expansion; a sub-term can also be a variable to indicate “don't know''.

4.3.1.2 Conditional compilation

Conditional compilation builds on the same principle as term_expansion/2, goal_expansion/2 and the expansion of grammar rules to compile sections of the source code conditionally. One of the reasons for introducing conditional compilation is to simplify writing portable code. See section C for more information. Here is a simple example:

:- if(\+source_exports(library(lists), suffix/2)).

suffix(Suffix, List) :-
        append(_, Suffix, List).

:- endif.

Note that these directives can only appear as separate terms in the input. SWI-Prolog accomodates syntax extensions under conditional compilation by silently ignoring syntax errors when in the false branch. This allow, for example, for the code below. With rational number support 1r3 denotes the rational number 1/3 while without it is a syntax error. Note that this only works properly if (1) the syntax error still allows to re-synchronize on the full stop of the invalid clause and (2) the subsequent conditional compilation directive is valid.

:- if(current_prolog_flag(bounded, false)).
one_third(1r3).
:- endif.

Typical usage scenarios include:

  • Load different libraries on different dialects.
  • Define a predicate if it is missing as a system predicate.
  • Realise totally different implementations for a particular part of the code due to different capabilities.
  • Realise different configuration options for your software.
:- if(:Goal)
Compile subsequent code only if Goal succeeds. For enhanced portability, Goal is processed by expand_goal/2 before execution. If an error occurs, the error is printed and processing proceeds as if Goal has failed.
:- elif(:Goal)
Equivalent to :- else. :-if(Goal). ... :- endif. In a sequence as below, the section below the first matching elif is processed. If no test succeeds, the else branch is processed.
:- if(test1).
section_1.
:- elif(test2).
section_2.
:- elif(test3).
section_3.
:- else.
section_else.
:- endif.
:- else
Start‘else' branch.
:- endif
End of conditional compilation.