Source code processing directives

Arity/Prolog32 provides directives that are accepted by consult/1, reconsult/1 and the compiler for file inclusion, macro expansion, conditional processing, and operator definition.

Include directives

:- include(File).

Input is continued from the specified File if found along the INCLUDE path and will return to the current file when done. The File argument is specified as the textual name of the file and may contain the path name. For example:

:- include('c:\PROLOG\myApp.inc').
:- include('myOtherApp.inc').

Using include files is often very helpful in complex applications that contain multiple source modules, particularly if you are creating DLLs. You should consider creating a single master include file that contains all of the compiler directives and source code processing directives. If this single include file is too large or complex, then you should consider having a single include file that loads other include files.

As an example of the style we advocate, suppose we are building a DLL with multiple modules called 'kneedeep'. A single source code module might be structured as follows:

% ****************************************************************************
%
%    KDTest.ARI
%            === Code for testing my structures
%
%    Revision Log at End of File --
%     Initial Rev: Jan 24, 2013 [PLG]
%
%    Copyright (C) 2013 Headspace Sprockets, LLC.  -- COMPANY CONFIDENTIAL
%
%    All rights reserved. This document may not be copied in any
%    form, whatsoever, without the express written permission of
%    Headspace Sprockets, LLC.
%
% ****************************************************************************

:- define const_MODULE = kdTest.
:- define const_DLL = kneedeep.
:- include('kneedeep.inc').

%* kdt_Test(+Type1, +Type2):det.
%* kdt_Type2ID(+, -/+):det.

%%%% my code goes here %%%%

The file KneeDeepVis.ari is used to ensure that all extrn predicates from all of the modules in the DLL will be made visible. In full, it looks like this:

% ***************************************************************************
%
%    KneeDeep:(KneeDeepVis.ARi) -- Visibility
%
%     Initial Rev: Jan 24, 2013 [PLG]
%
%    Copyright (C) 2013 Headspace Sprockets, LLC.  -- COMPANY CONFIDENTIAL
%
%    All rights reserved. This document may not be copied in any
%    form, whatsoever, without the express written permission of
%    Headspace Sprockets, LLC.
%%
% ***************************************************************************

:- define const_DLL = kneedeep.
:- define const_MODULE = kneedeep.
:- define const_VISIBLES.
:- include('kneedeep.Inc').

The file 'kneedeep.inc' contains the directives needed to build the DLL. In part, it would look like this:

% operators
:- op(1200, xfx, ':=').
:- op(800, xfy, '&').

% interpreted code I want to directly call.  Defined in kdInterp.ari
:- extrn atdl_create/1:interp.
:- extrn atdl_close/0:interp.
:- extrn atdl_handle/1:interp.

% ***************************************************************************
% KDTest.ARI
% ***************************************************************************

:- define preds_KNEEDEEP_KDTEST = (
    kdu_Test/2,                  %* kdu_Test(+Type1, +Type2):det.
    kdu_Type2ID/2                %* kdu_Type2ID(+, -/+):det.

).

%%%% other module defines of all preds go here

% ***************************************************************************
% Here are the dllmodule, module, and visible directives
% ***************************************************************************

:- dllmodule(
    kneedeep,
    [
    kneedeep,
     kdtest, kdatdl, avmTree, json
    ]
).

:- extrn preds_KNEEDEEP_KDTEST            :module(kdTest).
:- extrn preds_KNEEDEEP_KDATDL            :module(kdatdl).
:- extrn preds_KNEEDEEP_AVMTREE           :module(avmtree).
:- extrn preds_KNEEDEEP_JSON              :module(json).

:- if( (defined(const_VISIBLES)) ).
:- visible preds_KNEEDEEP_KDTEST.
:- visible preds_KNEEDEEP_KDATDL.
:- visible preds_KNEEDEEP_AVMTREE.
:- visible preds_KNEEDEEP_JSON.
:- endif.




Macro definition directives
:- define Macro = Define.

Any term unifying with Macro read later in the file will be replaced with the term Define.

Macros are often used to create symbolic values, for example:

:- define sizeof_CHAR        = 1.
:- define sizeof_SHORT       = 2.
:- define sizeof_LONG        = 4.
:- define sizeof_DOUBLE      = 8.



Some useful extensions to common control constructs can be created using macros, for example:

:- define for_each(Var, List, Do) = ( l_Member(List,Var), Do, fail ; true).
:- define foreach(Var, List, Do)  = ( l_Member(List,Var), Do, fail ; true).
:- define ifnot(A,B)              = ifthenelse(A, true, B).
:- define if(A,B)                 = ifthen(A, B).
:- define if(A,B,C)               = ifthenelse(A, B, C).
:- define ifbool(A,Ret)           = ifthenelse(A,Ret=bTRUE,Ret=bFALSE).
:- define try_once(X)             = if(X, true).
:- define on_fail(X)              = (true; X, fail).
:- define Macro is Define.

Any term unifying with Macro will be replaced with the value resulting from the evaluation of the arithmetic expression Define.

This format for define directives could be used to do some common calculations, for example:

:- define circleArea(Radius) is pi*Radius*Radius.

This is used as, for example:

?- X = circleArea(2).
X = 12.566370614
yes
:- define FunctorMacro/ArityMacro =.. Functor(ArgMacro1, ..., ArgMacroN).

The univ macro define is a short hand that can be used to assign to macros the functor, arity and argument positions of a term.

For example, if you were to define an employee record with the following define:

:- define empFUNCTOR/empARITY =.. emp(empLAST,empFIRST,empSALARY,empPHONE,empSTART).

This define is a short hand for the following macro definitions:

:- define( empFUNCTOR = emp ).
:- define( empARITY = 5 ).
:- define( empLAST = 1 ).
:- define( empFIRST = 2 ).
:- define( empSALARY = 3 ).
:- define( empPHONE = 4 ).
:- define( empSTART = 5).

You can now use these macros in your program to test that you have the correct structure or examine an argument. For example, you could write a general predicate to return information from either a company or an employee record:

get_info(Record, Attribute, Value) :-
    functor(Record, Functor, _),
    attribute_arg(Attribute, Functor, Arg),
    arg(Arg, Record, Value).

% Employee Attributes
attribute_arg('first name', empFUNCTOR, empFIRST) :- !.
attribute_arg('last name', empFUNCTOR, empLAST) :- !.
attribute_arg('salary', empFUNCTOR, empSALARY) :- !.
attribute_arg('phone number', empFUNCTOR, empPHONE) :- !.

% Company Attributes
attribute_arg('revenues', compFUNCTOR, compREVENUE) :- !.
attribute_arg('profits', compFUNCTOR, compPROFITS) :- !.
attribute_arg('phone number', compFUNCTOR, compPHONE) :- !.

Notice that the same attribute 'phone number' will return different arguments depending on whether the record was for an employee or for a company. You would find this mechanism extremely convenient if you occasionally need to add a new argument, for instance if you started a pension fund and wished to add an argument to employee records to keep track of their contributions.

In addition, there is the builtin macro =..(Functor, Arity, Args) that can be used to create a structure. When encountered in your program, this macro will be replaced with the structure specified by the Functor, Arity and Args. For example, you could have the following predicate to add a new employee record when you only have the employees first and last names:

add_employee(Last,First) :-
    recordb(employees, Last,
        =..(empFUNCTOR, empARITY, [empLAST=Last, empFIRST=First])).

The =../3 macro will create the employee structure when this clause is read. Arguments that are not supplied will be variables.

Conditional directives

Conditional directives can be used to enable or suppress the compilation or consultation of parts of a source file based on the success or failure of a provided goal, respectively. Conditional directives in Arity/Prolog32 are analogous to conditional directives in C.

:- if(Goal).
:- elif(Goal).
:- endif.
:- else.

Each if/1 directive must be matched by an endif/0 directive but there is no limit to the depth of nesting allowed. Each if/1 directive may be followed by any number of elif/1 directives, but at most only one else/0 directive is allowed, appearing as the last alternative before its associated endif/0.

The Goal used in if/1 and elif/1 directives are composed of conjunctions, disjunctions, and the following predicates:

not(G)

succeeds if G fails

defined(Term)

succeeds if Term has been defined as a macro

X = Y

succeeds if X and Y unify

X \= Y

succeeds if X and Y do not unify.

X =:= Y

succeeds if X and Y evaluate to the same value

X =/= Y

succeeds if X and Y do not evaluate to the same value

X < Y

succeeds if X evaluates to a value less than the evaluated value of Y

X > Y

succeeds if X evaluates to a value greater than the evaluated value of Y

X =< Y

succeeds if X evaluates to a value less than or equal to the evaluated value of Y

X >= Y

succeeds if X evaluates to a value greater than or equal to the evaluated value of Y

Operator directives

The op/3 predicate specifies the position, precedence, and associativity of an operator. It is common for the op/3 predicate to be called within your executing code to change the set of operator definitions in force dynamically. You may also invoke the operator as a directive in your compiled and consulted code.

:- op(P ,A, O).

The operator O with precedence P and associativity A is defined taking effect immediately.

The use of the op/3 predicate as a directive within compliled code applies to the source module that it appears within (from the point it appears) and also will be used in the initialization phase of program execution . This use of the op/3 directive in one compiled module does not affect other module compilation; For consistency and good program documentation practice it is common to place op/3 directives in include files.

The use of the op/3 predicate as a directive within consulted code applies to the source module that it appears within (from the point it appears) and also from that point forward in the currently executing program.

Two variants of the op/3 directive may be used within compiled code but not within consulted code. These variants correspond to the duration that the directive is in effect:

:- op(P, A, O) : compiletime.

The operator is in effect only during the duration of the compilation of the module.

:- op(P, A, O) : runtime.

The operator is in effect only at during program execution.

The compile time and runtime variants of the op/3 directive are not recommended. They are expected to be deprecated in the future.