C expressions within Prolog clauses
C expressions
Arity/Prolog32 extends the notion of a Prolog goal to include a very special form of evaluable predicate. A C expression may only be used in compiled code and it typically requires the declaration of C data and functions. A C expression is denoted by enclosing it in curly braces ( { ... } ). Note that this is an entirely different use of curly braces than is used for DCGs. There is no ambiguity because the principal functor of a DCG is '–>' and not the neck of ordinary Prolog rules (':-').
A simple example of a C expression that calls a function that creates a C string from a Prolog atom, complete with declarations follows:
... various Prolog declarations ...
:- c.
extern unsigned long ulNRlips;
extern char achNRtime[10];
#prolog
nrevbch(Length, Iters) :-
do_nrev(Length, Iters, Lips, Time),
float_text(Time, TimeAtom, fixed(2)),
{
Lips:long,
ulNRlips = Lips;
'MakeCString'(achNRtime, sizeof(achNRtime), &TimeAtom)
}.
... additional Prolog code ...
Declaring variables within embedded C expressions
There are three types of variables used in C expressions embedded within Prolog clauses:
Global C variables
A global C variable declaration appears within the :- c specifier and the :- prolog specifier.
Local C variables
A local C variable declaration appears within an embedded C expression.
Prolog variables
Prolog variables may appear throughout your Prolog program. Prolog variables appearing within an embedded C expression may or may not be declared. If Prolog variables appearing in an embedded C expression are not declared, they are assumed to be signed long integers.
When a variable is declared within an embedded C expression, it must be declared as one of the simple data types listed in table 15.1 or it can be declared as a type defined using a typedef declaration. A declaration within an embedded C expression has one of the following formats:
variable:type
(variable1, variable2, ... variableN):type
Any number of declarations can appear within an embedded C expression. For example, the following declares the variables x and Y as long integers and the value z as a double:
{ (x,Y):long,
z:double,
z = x * Y }
Note: both C and Prolog variables can be declared in this way. All C variables local to the expression must be declared either as global C variables or local C variables. If Prolog variables used in an expression are not declared, they are assumed to be signed long integers.
Two predefined data types are allowed in embedded C expressions that apply only to Prolog variables: '#ref" and '#atom'. Variables declared with these types need to be atoms or database reference numbers, when used within embedded C expressions. Atoms will be converted into short values and database reference numbers will convert to long integers.The following is an example use of these types:
X :-
Atom1 = 'atom',
recordz(Atom1,Atom1,Ref1),
{
(Atom1,Atom2):'#atom',
(Ref1,Ref2):'#ref',
C_atom:short,
C_ref:long,
C_atom = Atom1,
Atom2 is C_atom,
C_ref = Ref1,
Ref2 is C_ref
},
instance(Ref2, Atom2).
Assignment expressions
There are two formats of assignment expressions within embedded C expressions. The is expression is used for unifying Prolog variables with the results of an expression, the = expression is used for assigning values to C variables or C storage locations:
{ Prolog variable is expression }
{ lvalue = expression }
An lvalue is a variable or expression which refers to a particular memory location. Prolog variables may appear in an lvalue expression when they are valid pointers to C storage locations. For instance, the following are all valid expressions:
X is (array .. [1] .. [3])
a = (*lptr) = l1*12
X is &(array [1] .. [3])
(*X) = a*12
Note: The = operator used within embedded C expressions is used to assign a value to a C variable and is not equivalent to the =/2 predicate used to unify Prolog terms. Also, note in the second example that it is possible to assign more than one value in a statement.
Prolog variables used on the right-hand side of an expression or used in lvalue expressions must be instantiated to a value or can be cast into a value that corresponds to the variables given type. If not, a run-time error will occur.
When the intermediate result of an expression involves two or more data types, data type conversion is performed according to the C language convention. All values in the expression will be converted to the value with the largest range of values as shown in table 15.1. Thus, if an expression involves a long, a short and a char, then all of the values will be converted to longs before the expression is calculated.
Elements of an expression
An embedded C expression can be made up of any of a number of elements. The expression can include both Prolog and C variables. Names of C objects may appear as strings or valid Prolog atoms. The following is a list of elements that can be included in a valid expression.
Constants
Any numeric constant value.
Prolog variables
Any valid Prolog variable.
C variables
Any C variable declared with a C declaration.
Unary operators
Any of: + - \ not * sizeof(Arg) &. The unary operators may appear as:
Unary_op Expression
Unary_op(Expression)
Binary operators
Any of: + - * / mod << >> /\ \/ +/ and or. The binary operators may appear as:
Expression Operator Expression
Operator(Expression, Expression)
Relational operators
Any of: > < >= =< =:= =\=
Assignment expression
An expression involving either the is operator to assign a value to a Prolog variable, or the = operator that assigns a value to a C lvalue.
Cast expression
Casting a variable or expression to a defined Type has the following format:
Type(Expression)
where Type is any of the simple data types in table 15.1 or a type name defined through the use of a typedef declaration.
Function call
A call to a C function has the following format:
name(Arg1,...,Argn)
name(void)
When calling a C function using a pointer, you must use the funcall functor:
funcall(Name,Arg1,Arg2,...)
For example, the C function call (*fpointer)(3,4,5) should be specified as:
{...,funcall(*fpointer,3,4,5),...}
Array element
An element of an array is specified using a double dot (..) to separate array elements from the array name.
For example, the C array element array1[3][5] is specified as:
array1..[3]..[5]
Structure or union member
A member of a structure or union is specified using a double dot (..), instead of the C convention of a single dot.
For example:
date..yearweight..ounces
Pointer to a structure or union member
A pointer to a structure or union member is specified with an arrow (->) as it is in C.
For example:
(date -> year)
(weight -> ounces)
Note: Because the -> operator has a high precedence it is usually necessary to enclose these expressions in parenthesis.
Control constructs
Several control constructs are supported by the embedded C compiler for controlling execution based on test expressions. In all cases, a test expression is assumed to be true if it evaluates to a non-zero value and false if it evaluates to a value of zero. The supported control constructs are:
ifthenelse(Test, ExpTrue, ExpFail)
If Test is true, then ExpTrue is evaluated and the result is returned, otherwise ExpFail is evaluated and returned. ExpTrue and ExpFail must have values of the same type.
case(Test,[Val1 -> Expr1, ..., Valn -> Exprn])
case(Test,[Val1 -> Expr1, ..., Valn -> Exprn | ExprDefault ])
Test is evaluated and a short value is returned and compared with each value (Val1 thru Valn). If one of the values is the same, then the corresponding expression is evaluated and the result is returned as a long. If no value matches then ExprDefault is evaluated if it is specified, otherwise the value 0 is returned.
do_while(Exp,Test)
Exp is evaluated until Test evaluates as false. Exp is evaluated before the first evaluation of Test.
while_do(Test,Exp)
As long as Test evaluates to true, Exp will be evaluated.
do_until(Exp,Test)
Exp is evaluated until Test is evaluated as true. Exp is evaluated before the first evaluation of Test.
until_do(Test,Exp)
As long as Test is evaluates to false, Exp will be evaluated.
An expression can contain any valid combination of the elements described above. Note that C string constants are not allowed in embedded C expressions.
Operators
Arity/Prolog32 includes a full set of operators for use in embedded C expressions, which closely correspond to the operators found in the C language. The following table shows the Arity/Prolog32 representation of comparable C operators.
Operator | C | Arity/Prolog32 |
---|---|---|
sequential evaluation | , | , |
logical not | ! X | not X |
pointer to structure or union member | -> | -> |
logical and | X && Y | X and Y |
logical or | X || Y | X or Y |
bitwise or | X | Y | X \ / Y |
bitwise exclusive or | X ^ Y | X \+/ Y |
bitwise and | X & Y | X / \ Y |
assignment | X = Y | X = Y |
equality | X == Y | X =:= Y |
not equality | X != Y | X =\= Y |
greater than or equal | X >= Y | X >= Y |
less than or equal | X <= Y | X =< Y |
greater than | X > Y | X > Y |
less than | X < Y | X < Y |
right shift | >> | >> |
left shift | << | << |
unary minus | - X | -X |
remainder X | % Y | X mod Y |
bitwise negation | ~X | (X) |
address of | &X | &X |
indirection | *X | *X |
membership | X.Y | X..Y |
type cast | (Type) X | Type(X) |
size of object | sizeof(X) | sizeof(X) |
Note that the precedence of the following operators differ between Prolog and C: logical not, = (assignment) , -> (pointer to structure/union member).
Failing from an embedded C expression
The APC$SUCCFAIL function is provided to allow you to fail from an embedded C expression. The definition of this predicate would be:
int far pascal APC$SUCCFAIL(int value);
It is not necessary to provide this function declaration in your code as it is already built into the compiler. If the integer argument value is non-zero, then the function returns the value, otherwise the embedded C section which contains the function call will fail. For example, if you had a boolean C variable data_exists defined to mean that there is data if the variable is set to true, then you could assign either 'it exists' or 'it is missing' to a Prolog variable with the following predicate:
any_c_data('it exists') :-
{ 'APC$SUCCFAIL'(data_exists) },
!.
any_c_data('it is missing').
Compiled Arithmetic and the arith directive
Unlike the Prolog is/2 predicate that is always interpreted (in case a variable in the expression is actually an expression), the embedded C is expression actually performs compiled arithmetic. This is possible because the types of all of the variables in the expression are known. You can use compiled arithmetic instead of the is/2 predicate by replacing each occurrence of the is/2 predicate with an embedded C expression where each variable is typed and the expression is provided. For instance, you would replace the following goals:
Var is (3*Float1) mod(4*Long1)
Var is integer(Long1 mod Long2)
with the following expressions:
{ (Var,Float1):double, Long1:long; Var is (3*Float1) mod (4*Long1) }
{ Var is Long1 mod Long2 }
Note: In the first expression the variable Var needs to be declared as a double, otherwise it would be assumed to be a long and the result of the expression would have been cast to a long.
In the second expression, all of the Prolog variables are assumed to be C long values by default. When all of the variables in an is/2 goal (including the result) are the same integer type, you can specify that compiled arithmetic be used in place of the is/2 goal. The arith directiveindicates what type of compiled arithmetic is substituted for is/2 goals within Prolog predicates. The format for this directive is
:-arith(Type)
where Type can be one of the following:
interp
All is expressions outside of embedded C expressions are interpreted. This is the default action if no arith declaration is specified.
double
All is expressions outside of embedded C expressions are compiled. Constants and variables within the expressions are assumed to be double precision floats.
long
All is expressions outside of embedded C expressions are compiled. Constants and variables within the expressions are assumed to be long integers.
short
All is expressions outside of embedded C expressions are compiled. Constants and variables within the expression are assumed to be short integers.
More than one arith directive may appear within the Prolog code. An arith directive applies to all clauses that follow it up to the next arith directive. Note, however, that arith directive cannot be placed within a clause.