Language Reference/Domains
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.