Language Reference/Domains

From wiki.visual-prolog.com

Domains Sections

A domain section defines a set of domains in the current scope (see Interface, Class Declaration, and Class Implementation).

DomainsSection:
    domains DomainDefinition-dot-term-list-opt

Domain Definitions

A domain definition defines a named domain in the current scope.

DomainDefinition:
    DomainName FormalTypeParameterList-opt = DomainExpression

If the domain on the right hand side denotes an interface or a compound domain, then the defined domain is synonym (i.e. identical) to the type expression. Otherwise the defined domain becomes a subdomain of the domain denoted by the domain expression. Here a domain name DomainName should be a lower case identifier.

There are certain places where you must use a domain name rather than a type expression:

  • as a declaration of a formal argument type;
  • as a type of a constant or a fact variable;
  • as a type in a list domain.

Domain Expressions

A domain expression denotes a type in a domain definition:

DomainExpression: one of
    TypeName
    CompoundDomain
    ListDomain
    PredicateDomain
    IntegralDomain
    RealDomain
    TypeVariable
    ScopeTypeVariable
    TypeApplication

Type Expressions

The full range of DomainExpressions can only be used in a domain definition. TypeExpression is a subset of these expressions that are used in other many other contexts.

TypeExpression: one of
    TypeName
    ListDomain
    TypeVariable
    ScopeTypeVariable
    TypeApplication

Type Names

A type name is either an interface name or the name of a value domain. We use the term value domain to specify domains whose elements are immutable (unchangeable). Here we can say that objects, belonging to domains correspondent to interface names, have mutable state and terms of any other domains are immutable. So actually value types are everything except object types. A type name (obviously) denotes the type corresponding to the name of an existing domain.

TypeName: one of
    InterfaceName
    DomainName
    ClassQualifiedDomainName
InterfaceName:
    LowercaseIdentifier
DomainName:
    LowercaseIdentifier
ClassQualifiedDomainName:
    ClassName::DomainName
ClassName:
    LowercaseIdentifier

Here InterfaceName is an interface name, DomainName is a value domain name, and ClassName is a class name.

Example
domains
    newDomain1 = existingDomain.
    newDomain2 = myInterface.

In this example the domain name existingDomain and the interface name myInterface are used to define new domains.

Compound Domains

Compound domains (also known as algebraic data types) are used to represent lists, trees, and other tree structured values. In its simple forms compound domains are used to represent structures and enumeration values. Compound domains can have a recursive definition. They can also be mutually/indirectly recursive.

CompoundDomain:
   Alignment-opt FunctorAlternative-semicolon-sep-list
Alignment:
    align IntegralConstantExpression

Here IntegralConstantExpression is an expression, which must be compile time evaluated to an integral value.

A compound domain declaration declares a list of functor alternatives with optional alignment. Alignment must be 1, 2 or 4.

If a compound domain consists of one functor alternative, then it is considered as structure and has representation, which is binary compatible with the appropriate structure in language C.

FunctorAlternative: 
    FunctorName FunctorName ( FormalArgument-comma-sep-list-opt )

Here FunctorName is the name of a functor alternative it should be a lower case identifier.

FormalArgument:
    TypeExpression ArgumentName-opt

Here ArgumentName can be any upper case identifier. The compiler ignores it.

Compound domains have no subtype relations to any other domains.

If a domain is defined as being equal to a compound domain, then these two domains are synonym types rather than subtypes. Meaning that they are just two different names for the same type.

Example
domains
    t1 = empty(); cons(integer V, t1 Tail).

t1 is a compound domain with two alternatives. The first alternative is the null-ary functor empty(), while the second alternative is the two-ary functor cons, which takes an integer and a term of the domain t1 itself as arguments. So the domain t1 is recursively defined.

The following expressions are terms of the domain t1:

empty
cons(77, empty())
cons(33, cons(44, cons(55, empty())))

In the example above we used parenthesis after the null-ary function empty. Such parenthesis are optional in all situations, except in a domain definition consisting only of a single null-ary functor. In that case parenthesis are required to distinguish it from a synonym/subtype definition.

Example

t1 is a compound domain with a single null-ary functor, whereas t2 is defined to be synonym to t1.

domains 
    t1 = f(). 
    t2 = t1.
Example
domains 
    t1 = nil; subt2(t2). 
    t2 = hh(t1, t1).

t1 is a compound domain with two alternatives. The first alternative is the null-ary functor nil, while the second alternative is the unary functor subt2, which takes a term of the domain t2 as argument. t2 is a compound domain with one alternative the functor hh, which takes two t1 terms as argument. So the domains t1 and t2 are mutually recursive.

The following are terms in the domain t1:

nil 
subt2(hh(nil, nil)) 
subt2(hh(subt2(hh(nil, nil)), nil)) 
consg(hh(nil, g(hh(nil, nil)))) 
subt2(hh(subt2(hh(nil, nil)), subt2(hh(nil, nil))))

List Domains

List domains represent sequences of values of a certain domain. Thus, all elements in a T list must be of type T.

ListDomain:
    TypeExpression *

T* is the type of lists of T elements.

The following syntax is used for lists:

ListExpression:
    [ Term-comma-sep-list-opt ]
    [ Term-comma-sep-list | Tail ]
Tail:
    Term

Here Tail is a term which should have a value of the ListDomain type. Each Term should be of typeName type.

Actually, lists are just compound domains with two functors: [] denoting the empty list and the mix-fix functor [HD|TL] denoting the list with head HD and tail TL. The head must be of the underlying element type, whereas the tail must be a list of relevant type.

Lists are however syntactically sugared.

[E1, E2, ..., En | L ] is shorthand for [E1 | [ E2 | [ ...[ En | L ]...] ] ]

[E1, E2, ..., En] is shorthand for [E1, E2, ..., En | [] ], which in turn is shorthand for [E1 | [ E2 | [ ...[ En | [] ]...] ] ].

Predicate Domains

Values of a predicate domain are predicates with the same "signature", i.e. the same argument and return types, the same flow pattern and the same (or stronger) predicate mode.

A predicate that returns a value is called a function, whereas a predicate that does not return a value is sometimes called an ordinary predicate, to stress that it is not a function.

PredicateDomain:
   ( FormalArgument-comma-sep-list-opt ) ReturnArgument-opt 
   PredicateModeAndFlow-list-opt CallingConvention-opt
FormalArgument:
   TypeExpression VariableName-opt
   Ellipsis
ReturnArgument:
    -> FormalArgument
VariableName:
   UpperCaseIdentifier

Predicate domains can have Ellipsis argument as the last FormalArgument in the FormalArgument-comma-sep-list.

Predicate domains can have AnonymousIdentifier as a predicateArgumentType to specify that the argument can be of any type.

Currently, predicate domains with ellipsis can only be used in predicate declarations.

Predicate domains that are used in domain definitions can at most state one flow.

PredicateModeAndFlow:
     PredicateMode-opt
     FlowPattern-list-opt
Predicate Mode

The specified predicate mode applies for each member of a flow pattern list following it.

PredicateMode: one of
   erroneous 
   failure 
   procedure 
   determ 
   multi 
   nondeterm

Predicate modes can be described by the following sets:

erroneous = {} 
failure = {Fail} 
procedure = {Succeed} 
determ = {Fail, Succeed} 
multi = {Succeed, BacktrackPoint} 
nondeterm = {Fail, Succeed, BacktrackPoint}

If Fail is in the set it means that the predicate can fail. If succeed is in the set it means that the predicate can succeed. If BacktrackPoint is in the set it means that the predicate can return with an active backtrack point in it.

If such a set, say failure, is a subset of another set, say nondeterm, then we say that the mode is stronger than the other, i.e. failure is stronger than nondeterm.


A predicate domain actually contain all predicates (with correct type and flow), which have the mode specified or a stronger mode.

It is illegal to state a predicate mode for constructors, they always have the procedure mode.

Omitting of a predicate mode means procedure.

Flow Pattern

The flow pattern defines the input/output direction of the arguments, which in combination with functor domains can be structures with parts of a single argument being input and other parts of the same argument being output.

A flow pattern consists of a sequence of flows, each flow corresponds to an argument (fist flow to first argument, etc).

FlowPattern:
     ( Flow-comma-sep-list-opt ) AnyFlow
Flow: one of
    i
    o 
    FunctorFlow
    ListFlow
    Ellipsis

Ellipsis flow must match an ellipsis argument and can therefore be only the last flow in the flow pattern.

Ellipsis:
    ...

A functor flow FunctorFlow states a functor and flows of each of the components of that flow. The functor must of course be in the domain of the corresponding argument.

FunctorFlow:
    FunctorName ( Flow-comma-sep-list-opt )

A functor flow declaration cannot contain ellipsis flow.

List flows are just like functor flows, but with the same syntactic sugaring as the list domain.

ListFlow:
    [ Flow-comma-sep-list-opt ListFlowTail-opt]
ListFlowTail:
    | Flow

A list flow cannot contain ellipsis flow.

When declaring a predicate the flow can be omitted. Inside an implementation (i.e. for a local predicate) the needed flows are derived from the usages of the predicate. Inside an interface or a class declaration (i.e. for a public predicate) omitting flows means that all arguments are input.

The special flow pattern anyflow can be stated only in declarations of local predicates (i.e. in predicate declarations inside the implementation of a class). It means that the exact flow pattern(s) will be evaluated during the compilation.

Example
domains
    pp1 = (integer Argument1).

pp1 is a predicate domain. The predicates that have type pp1 takes one integer argument. Since no flow-pattern is stated the argument is input, and since no predicate mode is mentioned the predicates are procedure.

Example
domains
    pp2 = (integer Argument1) -> integer ReturnType.

Predicates of type pp2 take one integer argument and returns a value of type integer. Therefore, pp2 is actually a function domain and the predicates that have type pp2 are actually functions. Since no flow-pattern is stated the argument is input and since no predicate mode is mentioned the predicates are procedure.

Example
predicates
    ppp : (integer Argument1, integer Argument2) determ (o,i) (i,o) nondeterm (o,o).

The predicate ppp takes two integer arguments. It exists in three flow variants: (o,i) and (i,o), which are determ, and (o,o), which is nondeterm.

Calling Convention

The calling convention determines how arguments, etc. are passed to the predicate, it also determines how the link name is derived from a predicate name.

CallingConvention:
     language CallingConventionKind
CallingConventionKind: one of
     c thiscall stdcall apicall prolog

If a calling convention is not stated, then the prolog convention is assumed. The prolog calling convention is the standard convention used for Prolog predicates.

The calling convention c follows the C/C++ standard calling convention. The link name of a predicate is created from the predicate name by adding a leading underscore (_).

The calling convention thiscall follows the C++ standard calling convention for virtual functions. This calling convention uses the c link name strategy but sometimes it may use the different argument and stack handling rules. Calling convention thiscall can be applied to the object predicates only.

The calling convention stdcall uses the c link name strategy but it uses the different argument and stack handling rules. The following table shows the implementation of stdcall calling convention:

Feature Implementation
Argument-passing order Right to left.
Argument-passing convention By value, unless a compound domain term is passed. So it cannot be used to predicates with variable number of arguments.
Stack-maintenance responsibility Called predicate pops its own arguments from the stack.
Name-decoration convention An underscore (_) is prefixed to the predicate name.
Case-translation convention No case translation of the predicate name is performed.


The calling convention apicall uses the same argument and stack handling rules as stdcall, but for convenience to call MS Windows API functions apicall uses the naming conventions that are used by most MS Windows API functions. According to apicall naming conventions the link name of a predicate is constructed as follows:

  • an leading underscore (_) is prefixed to the predicate name;
  • the predicate name in which the first letter is changed in to a capital letter;
  • the 'A', the 'W' or nothing is suffixed, if the arguments and the return type indicate an ANSI, Unicode or neutral predicate, respectively;
  • the '@' is suffixed;
  • the number of bytes pushed on the call stack is suffixed.
Example
predicates
     predicateName : (integer, string) language apicall

The argument types of this predicate indicates that it is a Unicode predicate (as string is the domain of Unicode strings). An integer and a string each occupies 4 bytes on the call stack and, therefore, the link name becomes:

_PredicateNameW@8

If apicall is used together with the "as" construction the name stated in the "as" construction is decorated in the same manner.

apicall can only be used directly in a predicate declaration, not in a predicate domain definition. In predicate domain definitions stdcall, must be used instead. A predicate declared with apicall calling convention cannot have clauses and it also cannot be resolved externally without explicit DLL name.

The following table compares implementations of c, apicall, and stdcall calling conventions (the prolog calling convention has the special implementation, which is not discussed here):

Keyword Stack cleanup Predicate name case-translation Link predicate name decoration convention
c Calling predicate pops the arguments from the stack. None. An underscore (_) is prefixed to the predicate name.
thiscall Calling predicate pops the arguments from the stack except the implicit This argument which is passed in the register. None. c link name strategy is used.
stdcall Called predicate pops its own arguments from the stack. None. An underscore (_) is prefixed to the predicate name.
apicall Called predicate pops its own arguments from the stack. The first letter of the predicate name is changed to the capital letter. An underscore (_) is prefixed to the name. The first letter is changed to the upper case. The 'A', the 'W' or nothing is suffixed. The sign @ is suffixed. The (decimal) number of bytes in the argument list is suffixed.

Visual Prolog notion of predicate domains covers both class and object members. Class members are handled straight forward, but the handling of object members requires attention. The invocation of an object predicate will get "back" in the context of the object to which the member belongs.

Example Assume the following declarations:
interface actionEventSource
domains
     actionListener = (actionEventSource Source) procedure (i).
predicates
     addActionListener : (actionListener Listener) procedure (i).
     ... end interface

Also assume a class button_class which supports the actionEventSource. The event is sent when the button is pressed. In myDialog_class class, which implements a dialog, I create a button and I want to listen to its action events, so that I can react on button presses:

implement myDialog_class 
clauses
    new() :-
        OkButton = button_class::new(...),
        OkButton:addActionListener(onOk),
        ...
facts
    okPressed : () determ.
predicates
    onOk : actionListener.
clauses
    onOk(Source) :-
        assert(okPressed()).
end implement

The important thing about the example is that onOk is an object member and that, when the button is pressed, the invocation of the registered onOk will bring us back in the object that owns onOk. This means that we have access to the object fact okPressed, so that we can assert it.

Format Strings

A formal parameter to a predicate can be marked as format string using the attribute formatString. The format string can contain ordinary characters which are printed without modification, and format fields, that % begins with the percent '%' sign. If the percent sign is followed % by some unknown character (not the format specifier) - then this character will be printed without modifications. To output a % character you must write %% in the format string.

predicates
     writef : (string Format [formatstring], ...).

The format fields specification is:

[-][0][width][.precision][type]

All fields are optional.

[-] Hyphen indicates that the field is to be left justified; right justified is the default. Having no effect when width value is not set, or the number of characters in the actual value is greater than width value.

[0] Zero before width means for values that zeros will be added until the minimum width is reached. If 0(zero) and -(hyphen) appear, the 0 is ignored

[width] Positive decimal number specifying a minimum field size. If the number of characters in the actual value is less than width value - then the required number of space ' ' characters will be added before the value (or after it, if '-' field was set). No changes occurs if number of characters in the actual value is greater than the width value.

[.precision] The point '.' with the following unsigned decimal number can specify either the precision of a floating-point image or the maximum number of characters to be printed from a string.

[type] Specifies other format then the default for the given. For example, in the type field, you can give a specifier that says an integer will be formatted as an unsigned. The possible values are:

f Format real's in fixed-decimal notation (such as 123.4 or 0.004321). This is the default for real's.
e Format real's in exponential notation (such as 1.234e+002 or 4.321e-003).
g Format real's in the shortest of f and e format, but always in e format if exponent of the value is less than -4 or greater than or equal to the precision. Trailing zeros are truncated.
d or D Format as a signed decimal number.
u or U Format as an unsigned integer.
x or X Format as a hexadecimal number.
o or O Format as an octal number.
c Format as a char.
B Format as the Visual Prolog binary type.
R Format as a database reference number.
p Format as the presented value.
P Format as a procedure parameter.
s Format as a string.

Integral Domains

Integral domains are used for representing integral numbers. They are divided in two main categories for signed and unsigned numbers. Integral domains can also have different representation size. The predefined domains integer and unsigned represent signed and unsigned numbers with natural representation length for the processor architecture (i.e. 32bit on a 32bit machine, etc).

IntegralDomain:
     DomainName-opt IntegralDomainProperties

If a DomainName is stated in front of the IntegralDomainProperties, then this domain must itself be an integral domain and the resulting domain will be child-type (i.e. subtype) of this domain. In that case IntegralDomainProperties may not violate the possibility of being a subtype, i.e. the range cannot be extended and the size cannot be changed.

IntegralDomainProperties:
    IntegralSizeDescription IntegralRangeDescription-opt
    IntegralRangeDescription IntegralSizeDescription-opt
 
IntegralSizeDescription:
    bitsize DomainSize
 
DomainSize: 
    IntegralConstantExpression

An integral size description declares the size DomainSize of the integral domain, measured in bits. The compiler implement such representation to the integral domain, which has no less than the specified number of bits. The value of DomainSize should be positive and no greater than the maximal value supported by the compiler.

If integral size description is omitted, then it will become the same as the parent domain. If there is no parent domain, it will become the natural size for the processor.

IntegralRangeDescription:
    [ MinimalBoundary-opt .. MaximalBoundary-opt ]
 
MinimalBoundary:
    IntegralConstantExpression
 
MaximalBoundary: 
    IntegralConstantExpression

An integral range description declares the minimal MinimalBoundary and the maximal MaximalBoundary limits for the integral domain. If a limit is omitted, then the range of the parent domain is used. If there is no parent domain, then the DomainSize is used to determine respectively maximum or minimum value.

Notice that the specified minimum value should not exceed the specified maximum value. That is:

MinimalBoundary <= MaximalBoundary

Also the minimal MinimalBoundary and the maximal MaximalBoundary limits should satisfy the limits implied by the specified bit size bitsize.

The domain bit size DomainSize value and values of the minimal MinimalBoundary and the maximal MaximalBoundary limits must be calculated while compiling time.

Real Domains

Real domains are used to represent numbers with fractional parts (i.e. floating point numbers). Real domains can be used to represent very large and very small numbers. The built-in domain real have the natural precision for the processor architecture (or the precision given by the compiler).

RealDomain:
     DomainName-opt RealDomainProperties

If a DomainName is stated in front of the RealDomainProperties, then this domain must itself be a real domain and the resulting domain will be a subtype of this domain. In that case RealDomainProperties may not violate the possibility of being a subtype, i.e. the range cannot be extended and the precision cannot be increased.

RealDomainProperties: one of
    RealPrecisionDescription RealRangeDescription-opt
    RealRangeDescription RealPrecisionDescription
RealPrecisionDescription:
    digits IntegralConstantExpression

The real precision description declares precision of the real domain, measured in number of decimal digits. If precision is omitted then it will become the same as for the parent domain. If there is no parent domain, then it will be the natural precision for the processor or given by the compiler (in Visual Prolog v.6 the compiler limit is 15 digits). Precision have an upper and a lower limits given by the compiler, if the precisions larger than that limit is used the numbers will only obtain the processor (compiler) specified precision anyway.

RealRangeDescription:
    [ MinimalRealBoundary-opt .. MaximalRealBoundary-opt ]
 
MinimalRealBoundary:
    RealConstantExpression
 
MaximalRealBoundary: 
    RealConstantExpression

Here RealConstantExpression is an expression, which must be compile time evaluated to a floating point value. That is the real domain precision and limits must be calculated while compiling time.

The real range description declares minimal and maximal limits for the real domain. If a limit is omitted then it will be the same as for the parent domain. If there is no parent domain then the largest possible range for the precision will be used.

Notice that the specified minimum value should not exceed the specified maximum value. That is:

MinimalBoundary <= MaximalBoundary

Generic Domains

This section contains the formal syntax for generic domains, for a more complete introduction to generics please see the tutorial Objects and Polymorphism and the section Generic Interfaces and Classes.

FormalTypeParameterList:
     TypeVariable-comma-sep-list-opt

A formalTypeParameterList is a list of typeVariables

TypeVariable:
    UpperCaseIdentifier

A TypeVariable is an upper case identifier. In a domain declaration the type variable must be bound in the FormalTypeParameterList on the left hand side of the domain definition. In a predicate declaration all free type variables are implicitly bound and scoped to that predicate declaration.

TypeApplication:
     TypeName {TypeExpression-comma-sep-list-opt }

A TypeApplication is the application of a typeName to a list of types. The type name must be generic and the number of formal type parameters must match the number of type expressions.

Universal and Root Types

Visual Prolog uses some internal types, called root types and universal types.

Universal Types

A number literal like 1 does not have any particular type, it can be used as a value of any type that contains 1, including real types.

We say that 1 have a universal type. Having a universal type means that it have any type, which can represent its value.

Arithmetic operations also return universal types.

Root Types

Arithmetic operations are very liberal with their operand requirements: You can add integers of any integer domain with each other.

We say that arithmetic operands takes root types as arguments. The integer root type is super-type of any integer type (regardless that it is not mentioned in their declarations). Hence any integer type can be converted to the integer root type, and, since the arithmetic operations exist for the root types, it means one of them will work on any integer domains.

The actual number of root types and which operands exist is a matter of library facilities, and outside the scope of this document to describe.