Often, a program that runs successfully under control of the interpreter cannot be compiled without some modifications. This is because the interpreter handles many of the "bookkeeping" tasks that you must handle yourself when you compile the program. In a compiled program, these bookkeeping tasks are achieved through the use of code declarations.
A few differences between Arity/Prolog32 compiled and interpreted code include:
- A compiled programs must have a main entry point. The main entry point is specified through the definition and declaration of the main/0 predicate.
- If you want to specify an action to be taken if the user types Ctrl-Break while the application is running, you define and declare the restart/0 predicate.
- Compiled code may contain embedded C expressions. The principal use for embedded C expressions is the ability to call functions that are written in C (or in other languages with compatible calling conventions).
- Compiled code provides a means of encapsulating functionality within source modules and within DLLs. The model for encapsulation of interpreted code is very different; You can use code stored in different databases and use the Arity/Prolog32 workspace and world predicates to manage accessibilty.
Compiled code directives include the following:
- module: The ability to explicitly name the compiled object file, with additional features associated with the module that contains the definition of main/0.
- public: The declaration of compiled code entry points.
- visible: The declaration of interpreted code entry points called directly by compiled code.
- extrn: The declaration of accessibilty of code and their calling convention. The extrn directives may be used in place of public directives.
- dllmodule: The association of a name for a DLL with its constituent compiled modules.
- mode: The optional declaration of how a compiled predicate's arguments will be instantiated and the predicate's determinacy.
- model: The memory model used for embedded C compilation.
- cseg and dseg: The default segments for embedded C code and data.
- pack: The assertion that structure packing should be used for embedded C data.
- name: The naming convention to be applied to embedded C code and data.
Related to compiled code directives are directives that apply to compiled and consulted Prolog source code described in the Source code processing directives section of the book. These directives are:
- include: The ability to insert the contents of a file in the stream of compiled or consulted source code.
- macro: The definition of expansion rules to be applied to source code.
- Conditionals: The ability to enable or suppress the use of a portion of source file.
- op: The declaration of operators that affect the reading and writing of Prolog terms, including Prolog code.
In addition to these directives are those described in the Embedded C declarations section of the book.
Each source file is compiled to create an object module (.obj). The name of the compiled object file is by default is the root of the source file name concatenated with the '.obj' extension..
There are two ways that the object module name may be explicitly specified, overriding the default. The first is to override using a module declaration within the source code. The second, which can override the first, is to specify a module name as an option to the APC32 compiler.
The format for the module declaration is as follows:
In addition, the Name for the module, regardless of the method it is specified determines the name of the default database for the application for the module that contains the definition of the main/0 predicate. This database name may be overriden by an environment parameter, either in the startup C code for the application or in an environment file.
Public declarations are used to allow a predicate to be called from code outside of the module where the predicate has been defined. A public declaration can take one of the following forms:
:- public Name/Arity. % more than one predicate may be made public with a single declaration :- public Name/Arity, Name/Arity, ..., Name/Arity. % all predicates in the module may be made public :- public all.
As an alternative to using the public declaration, if the compiler detects an extrn declaration for a predicate defined in the module, it will create a public declaration for the predicate. This can be extremely useful for a complex application that contains an include file of all possible extrn predicates supplied in the application. Each module would include the file, thereby declaring public the predicates contained in the module that are available for common use and receiving the appropriate extrn declarations for common predicates not defined in the module.
Many applications execute both compiled and interpreted code. Just as applications use a public declaration in compiled code to make a predicate accessible to compiled code in another compiled code, applications use a visible declaration to make compiled code accessible to interpreted code.
The visible declaration can take one of the following forms:
:- visible Name/Arity. % more than one predicate may be made public with a single declaration :- visible Name/Arity, Name/Arity, ..., Name/Arity. % all public predicates in this module are made visible :- visible public. % all predicates in this module are made visible :- visible all. % all Arity/Prolog32 evaluable predicates are visible :- visible arity_builtins.
There is an important circumstance where visible declarations are required even if your application does not have interpreted code. If the compiled code of your program uses call/1, not/1, setof/3, bagof/3, findall/3, stdin/2, stdout/2, or stdinout/3 with goals that are compiled predicates or goals that are Arity/Prolog32 evaluable predicates, then you must declare those predicates to be visible. The reason for this is that these predicates always attempt to execute interpreted code. Any compiled code is unavailable to the interpreter unless declared visible.
An extrn declaration asserts that a predicate should be accessible even if it was defined in another module (in the case of compiled code) or if it was defined in interpreted code.
The visible declaration can take one of the following forms:
:- extrn Name/Arity. % more than one predicate may be made public with a single declaration :- extrn Name/Arity, Name/Arity, ..., Name/Arity. % Suffix determines the kind of extrn :- extrn Name/Arity:Suffix. % Suffix can apply to a number of predicates :- extrn (Name/Arity, Name/Arity, ..., Name/Arity):Suffix.
The extrn declaration can have the effect of being a public declaration. If a module contains the definition of a predicate and an extrn declaration for that predicate, then it is made public.
Suffixes determine the calling convention of the declared predicate. The default calling convention (used when no suffix is present) is prolog. The types of Suffix are:
The prolog calling convention indicates that the extrn predicate is "near" to the caller, meaning one of:
- The extrn predicate is within the same compiled file (and thus the extrn declaration is unneeded and ignored);
- The extrn predicate's module and this module are not in DLLs (both are in the linked .EXE); or
- The extern predicate's module and this module are in the same DLL.
The interp calling convention indicates that the predicate's definition is in interpreted code (is located in the database). You do not need to explicitly declare a predicate as extrn interp if the only way the code is called is by using call/1, not/1, setof/3, bagof/3, findall/3, stdin/2, stdout/2, or stdinout/3. However, it is good documentation practice to declare all extrn interp predicates.
The import calling convention designates that the extrn predicate is in a DLL different from the caller's DLL, or that the extrn predicate is in a DLL and the caller is in the linked .EXE.
The module suffix explicitly names the module where the extrn predicate is located. This declaration is preferable over the use of the prolog and import suffixes because it promotes a better source code documentation style. It is typically used in combination with module and dllmodule declarations and the use of include files. An extended example is provided below.
The eval, evalpost, and lib suffixes are used to designate that the called predicate is included as a built-in evaluable predicate. These suffixes are generally not used except by the developers of the Arity/Prolog32 tools.
The dllmodule/2 declaration associates a name for a DLL with the Prolog modules that are to be included in the DLL. The use of the dllmodule declaration can simplify your tracking of the organization of your application, particularly when used within an include file.
The form of the dllmodule/2 declaration is:
An extended example is provided below.
Prolog is an unusual language in that you can write predicates that work differently depending on whether an argument is instantiated or uninstantiated. For example, the functor/3 predicate has three arguments which in normal use correspond upon success to a Term, an Atom, and the number of arguments (Arity). If the Term argument on call is instantiated to a structure, then Atom and Arity will be unified with the name of the principal functor and its arity, respectively. If, on the other hand, the Term argument is uninstantianted on call, then the prediate expects that the Atom and Arity arguments will be instantiated to a string and an integer between 0 and 255, respectively. If so, then functor/3 will succeed and will unify Term with the most general term with the principal functor Atom/N.
This behavior of many Prolog predicates seems "directional" and, to some Prolog beginners is a source of some confusion. However, it can be readily understood both procedurally (as the functor/3 predicate above was explained) or declaratively by recognizing that each Prolog predicate expresses a true relationship whenever it succeeds in operating on the arguments as provided upon call or redo.
There are many Prolog predicates that work in both directions, as functor/3 does. However, there are many that do not. The sort predicate, for instance, will not scramble a sorted list. This predicate, to name just one, works in only one direction.
A predicate can use mode declarations to instruct the compiler that only one use of the predicate is allowed. As a result, the compiler does not have to produce as much object code. In some cases, the resulting object code is faster. Mode declarations can also influence the program's use of memory at runtime.
In the mode declaration, you give the predicate name and a parenthesized list of argument types – one for each argument. The argument types can contain any of the following symbols:
A plus sign states that the argument must be instantiated when the predicate is called.
A minus sign states that the argument must be uninstantiated when the predicate is called.
A question mark states that the argument may be either instantiated or uninstantiated when the predicate is called.
This symbol indicates that the value for the argument must be uninstantiated when the predicate is called, and must be instantiated when the predicate is completed.
This symbol indicates that the value for the argument can be instantiated or uninstantiated when the predicate is called, but must be instantiated when the predicate is completed.
For example, a predicate named inorout that can both accept and return an argument value can be declared like this:
:- mode in_or_out(?,?).
In addition, a mode declaration may indicate whether a predicate is deterministic (can succeed at most once) or non-deterministic. Non-deterministic mode is the default. The indication of determinacy for the above example is as follows:
:- mode in_or_out(?, ?):det.
The use of 'nondet' in the place of 'det' would indicate nondeterminacy, the default.
Once a mode declaration is made, you must be careful to use the predicate in the way that you have declared. You must not supply an uninstantiated variable for an argument that is declared with the plus sign and you must always supply an uninstantiated variable for an argument that is declared with the minus sign.
You should always write and test your code without mode declarations. Then if your program behaves differently after the mode declarations are added, check that you are using the predicates in the way that you have declared. You can easily tell the compiler to recompile your file ignoring mode declarations by using the -m compiler switch.
It is a good documentation style to use mode declarations, even for interpreted code that does not exploit mode declarations for any efficiency gains. However, it is common practice to either comment out the mode declarations or to use the -m compiler switch.
The model declaration can be used to specify the memory model used in compilation. There are two forms of the model directive:
:- model(Model). :- model(Model,Specifier).
The Model argument can be small, compact, medium, or large. If your program does not include a model directive, then the small model is used. The Specifier can be one of the following:
Assumes DS = SS, SS==DS (default).
Assumes DS \= SS, DS is reloaded on function entry
Assumes DS \= SS, DS is not reloaded on function entry
The following are example model directives:
:- model(large). :- model(compact,d). :- model(medium,w).
If you are calling Pascal routines, use either the medium or large model. If you are using embedded C specifically for compiled arithmetic rather than for calling C, Pascal, or Fortran routines, use the small model.
You can use the cseg and dseg declarations to specify the default segments for the C code and data in your Prolog module. The format for these declarations are:
Places C code in segment CodeSegment
Places initialized data in segment DataSegment. Uninitialized data is placed in the segment DataSegmentBSS. In addition, the segments are placed in the group DataSegmentGROUP.
If the segments are not specified, the C code is placed in segment _TEXT. Initialized data is placed in the segment DATA and unitialized data is placed within the segment BSS. These segments are placed in with the DGROUP.
If your program uses structures, data storage is assigned to the structures according to the data type sizes of the members of the structure. In the case of structures that contain byte data, the structure members may be padded by the system with a filler byte to ensure that each member is aligned on a word boundary. You can bypass this automatic byte padding through the use of the pack declaration. The format for the pack declaration is:
When the pack directive is used, storage allocation is packed in a method similar to that used by Microsoft C.
Note: The pack directive is similar to the /Zp directive used with the Microsoft C compiler.
The names directive is used to cause a naming convention to be applied to all C routines in embedded C expressions and declarations. The format of the name directive is:
Where Type may be one of the following atoms:
The Pascal naming convention which causes all routine names to be in uppercase will be used.
The C naming convention to add an underscore to the beginning of routine names will be used.
No translations will be applied to the names which will be accepted as written.
Note: if you explicitly include a cdecl or pascal specifier that naming convention will override the one specified with the names directive.