Last updated : Tue, 02 Apr 2024
This documentation is mainly issued from the document by Yves Caseau "Introduction to the CLAIRE Programming Language version 3.3". It has been included in the source files of XL CLAIRE such to be used with the XL code documentation generator. In this documentation XL specific stuffs are denoted by an [XL] mark.
categories Introduction What is CLAIRE ? |
Design |
CLAIRE is a high-level, portable, functional and object-oriented language with advanced rule processing capabilities. It is intended to allow the programmer to express complex algorithms with fewer lines and in an elegant and readable manner.
To provide a high degree of expressivity, CLAIRE uses :
To achieve its goal of readability, CLAIRE uses :
What is CLAIRE ? | categories Introduction Design |
Features |
CLAIRE was designed for advanced applications that involve complex data modeling, rule processing and problem solving. CLAIRE was meant to be used in a C++ environment, either as a satellite (linking CLAIRE programs to C++ programs is straightforward) or as an upper layer (importing C++ programs is also easy). The key set of features that distinguishes CLAIRE from other programming languages has been dictated by our experience in solving complex optimization problems. Of particular interest are two features that distinguish CLAIRE from procedural languages such as C++ or Java :
Design | categories Introduction Features |
Inspirations |
CLAIRE provides automatic memory allocation/de-allocation, which would have prevented an easy implementation as a C++ library. Also, set-oriented programming is much easier with a set-oriented language like CLAIRE than with libraries. CLAIRE is about ten years old and the current version reaches a new level of maturity.
CLAIRE is a high-level language that can be used as a complete development language, since it is a general purpose language, but also as a pre-processor to C++ or Java, since a CLAIRE program can be naturally translated into a C++ program (We continue to use C++ as our target language of choice, but the reader may now substitute Java to C++ in the rest of this document). CLAIRE is a set-oriented language in the sense that sets are first-class objects, typing is based on sets and control structures for manipulating sets are parts of the language kernel. Similarly, CLAIRE makes manipulating lists easy since lists are also first-class objects. Sets and lists may be typed to provide a more robust and expressive framework. CLAIRE can also be seen as a functional programming language, with full support for lambda abstraction, where functions can be passed as parameters and returned as values, and with powerful parametric polymorphism.
CLAIRE is an object-oriented language with single inheritance. As in SMALLTALK, everything that exists in CLAIRE is an object. Each object belongs to a unique class and has a unique identity. Classes are the corner stones of the language, from which methods (procedures), slots and tables (relations) are defined. Classes belong themselves to a single inheritance hierarchy. However, classes may be grouped using set union operators, and these unions may be used in most places where a class would be used, which offers an alternative to multiple inheritance. In a way similar to Modula-3, CLAIRE is a modular language that provides recursively embedded modules with associated namespaces. Module decomposition can either be parallel to the class organization (mimicking C++ encapsulation) or orthogonal (e.g., encapsulating one service among multiple classes).
CLAIRE is a typed language, with full inclusion polymorphism. This implies that one can use CLAIRE with a variety of type disciplines ranging from weak typing in a manner that is close to SMALLTALK up to a more rigid manner close to C++. This flexibility is useful to capture programming styles ranging from prototyping to production code development. The more typing information available, the more CLAIRE's compiler will behave like a statically typed language compiler. This is achieved with a rich type system, based on sets, that goes beyond types in C++. This type system provides functional types (second-order types) similar to ML, parametric types associated to parametric classes and many useful type constructors such as unions or intervals. Therefore, the same type system supports the naive user who simply wishes to use classes as types and the utility library developer who needs a powerful interface description language.
[XL] Starting with XL CLAIRE, CLAIRE is intended to cover various aspects of web oriented application (running in a CGI like environment), that is to serve dynamic content over the web. The Wcl syntax is introduced as new method of printing, in a similar way to printf but closer to the HTML syntax. The development of a web oriented agent would however require the module Wcl, not included in the standard XL CLAIRE distribution.
Features | categories Introduction Inspirations |
Primitives Integers and Floats |
As the reader will notice, CLAIRE draws its inspiration from a large number of existing languages. A non-exhaustive list would include SMALLTALK for the object-oriented aspects, SETL for the set programming aspects, OPS5 for the production rules, LISP for the reflection and the functional programming aspects, ML for the polymorphism and C for the general programming philosophy. As far as its ancestors are concerned, CLAIRE is very much influenced by LORE, a language developed in the mid 80s for knowledge representation. It was also influenced by LAURE but is much smaller and does not retain the original features of LAURE such as constraints or deductive rules. CLAIRE is also closer to C in its spirit and its syntax than LAURE was.
Introduction Inspirations |
categories Primitives Integers and Floats |
Dates and Times |
Both floats and integers are CLAIRE primitives. integers are represented using 30 bits (which is required for the OID model) and are always signed. Floats are represented as C double precision floating point numbers.
Arithmetic between integers and float can be handled using conversion method integer! and float! :
1 + 2 -> 3 1 + 2. -> error 1 + integer!(2.) -> 3 float!(1) + 2. -> 3. |
Integers and Floats | categories Primitives Dates and Times |
Chars |
Dates and times are represented using floats containing an UNIX C time that is the time in seconds since the Epoch (00:00:00 UTC, January 1, 1970). The use of float is required since CLAIRE integer are coded on 30 bits and times on 32 bits.
Internally, CLAIRE always handles dates represented in UTC. Times are referenced on the Epoch such the arithmetic between date and time can be made with the standard float arithmetic.
Two kind of timer are also supported, one that account time in the process time (time_set/time_get) and one that count in real time (timer!/elapsed).
Dates and Times | categories Primitives Chars |
Strings |
In CLAIRE chars are true object (i.e. not primitive) that hold an 8 bit value, one can obtain this value with integer!(c:char) and make a char from an integer i with char!(i:integer).
[XL] Starting with XL CLAIRE the internal 8 bit value is stored as an unsigned char (vs. signed char in CLAIRE 3) such the composition char!(integer!()) is actually the identity for all possible existing char in the system. If their is no char that hold a given value n then char!(n) will produce an error.
Chars | categories Primitives Strings |
Objects, Classes and Slots Objects and Entities |
In CLAIRE strings are represented using a C char*, that is a sequence of 8 bit characters. The length of a string is computed by the general method length @ string :
length("toto") -> 4 |
[XL] Starting with XL CLAIRE strings may contain null char under certain restriction. Indeed XL CLAIRE makes difference between strings that are dynamically allocated and strings that are are statically compiled :
s0 :: "a static string" // would compile as a static string s1 :: ("a string" /+ string!('<0')>/+ "another string") // dynamically built string |
In the above example s0 would be compiled statically, that is the string content would be allocated outside claire memory (e.g. in the data space of the program). In addition s1 is allocated dynamically and stored in a chunk of the claire memory. The important difference between these two string is the handling of their length :
Notice that inside the interpreter all strings are dynamically allocated which can give different behavior between the interpreted and the compiled code, for instance :
s :: "a<0b">// dangerous !!! static string that contain a null
(printf("~S |
In the interpreter this code would print 3 whereas the compiled code would print 1. Indeed s would be compiled statically (outside claire memory) and its length would handled by strlen.
As a general rule, any method that returns a new string returns a dynamic string. This is especially true for the port interface (fread and friends) which is particularly convenient to handle binary streams without having to deal with the null.
Primitives Strings |
categories Objects, Classes and Slots Objects and Entities |
Classes |
A program in CLAIRE is a collection of entities (everything in CLAIRE is an entity). Some entities are pre-defined, we call them primitive entities, and some others may be created when writing a program, we call them objects. The set (a class) of all entities is called any and the set (a class also) of all objects is called object.
[XL] In XL CLAIRE port are not imported entity but implemented an extensible class vs. primitive (see port).
Primitive entities consist of integers, floats, symbols, strings and functions. The most common operations on them are already built in, but you can add yours. You may also add your own entity classes using the import mechanism.
Objects can be seen as "records", with named fields (called slots) and unique identifiers. Two objects are distinct even if they represent the same record. The data record structure and the associated slot names are represented by a class. An object is uniquely an instance of a class, which describes the record structure (ordered list of slots). CLAIRE comes with a collection of structures (classes) as well as with a collection of objects (instances).
Definition : A class is a generator of objects, which are called its instances. Classes are organized into an inclusion hierarchy (a tree), so a class can also be seen as an extensible set of objects, which is the set of instances of the class itself and all its subclasses. A class has one unique father in the inclusion hierarchy (also called the inheritance hierarchy), called its superclass. It is a subclass of its superclass. |
Each entity in CLAIRE belongs to a special class called its owner, which is the smallest class to which the entity belongs. The owner relationship is the extension to any of the traditional isa relationship between objects and classes, which implies that for any object x, x.isa = owner(x).
Thus the focus on entities in CLAIRE can be summarized as follows: everything is an entity, but not everything is an object. An entity is described by its owner class, like an object, but objects are "instantiated" from their classes and new instances can be made, while entities are (virtually) already there and their associated (primitive) classes don't need to be instantiated. A corollary is that the list of instances for a primitive class is never available.
Objects and Entities | categories Objects, Classes and Slots Classes |
Free-able objects |
Classes are organized into a tree, each class being the subclass of another one, called its superclass. This relation of being a subclass (inheritance) corresponds to set inclusion: each class denotes a subset of its superclass. So, in order to identify instances of a class as objects of its superclass, there has to be some correspondence between the structures of both classes: all slots of a class must be present in all its subclasses. Subclasses are said to inherit the structure (slots) of their superclass (while refining it with other slots). The root of the class tree is the class any since it is the set of all entities. Formally, a class is defined by its superclass and a list of additional slots. Two types of classes can be created: those whose instances will have a name and those whose instances will be unnamed. Named objects must inherit (not directly, but they must be descendents) of the class thing. A named object is an object that has a name, which is a symbol that is used to designate the object and to print it. A named object is usually created with the x :: C() syntax but can also be created with new(C, name).
Each slot is given as <name>:<range>=<default>. The range is a type and the optional default value is an object which type is included in <range>. The range must be defined before it is used, thus recursive class definitions use a forward definition principle (e.g., person).
person <: thing // forward definition person <: thing(age:integer = 0, father:person) woman <: person // another forward definition man <: person(wife:woman) woman <: person(husband:man) child <: person(school:string) complex <: object(re:float, im:float) |
A class inherits from all the slots of its super-classes, so they need not be recalled in the definition of the class. For instance, here, the class child contains the slots age and father, because it inherited them from person.
A default value is used to place in the object slot during the instantiation (creation of a new instance) if no explicit value is supplied. The default value must belong to the range and will trigger rules or inverses in the same way an explicit value would. The only exception is the "unknown" value, which represents the absence of value. unknown is used when no default value is given (the default default value). Note that the default value is a real entity that is shared by all instances and not an expression that would be evaluated for each instantiation. The proper management of default values, or their absence through unknown, is a key feature of CLAIRE.
From a set-oriented perspective, a class is the set union of all the instances of its descendents (itself, its subclasses, the subclasses of its subclasses, etc.). In some cases, it may be useful to "freeze" the data representation at some point: for this, two mechanisms are offered: abstract and final. First, a class c can be declared to have no instances with abstract(c) such as in the following :
abstract(person) |
An abstract class is not an empty set, it contains the instances of its descendents. Second, a class can also be declared to have no more new descendents using final as follows :
final(colors) |
It is a good practice to declare final classes that are leaves in the class hierarchy and that are not meant to receive subclasses in the future. This will enable further optimizations from the compiler. A class can be declared to instantiate ephemeral objects, in which case its extension (the list of its instances) is not kept. An important consequence is that ephemeral objects may be garbage collected when they are no longer used. For this behavior, the class must be declared with ephemeral or inherit from ephemeral_object :
action <: object(on:any, performed_by:object) ephemeral(action) action <: ephemeral_object(on:any, performed_by:object) |
A class definition can be executed only once, even if it is left unchanged. On the other hand, CLAIRE supports the notion of a class forward definition. A forward definition contains no slots and no parentheses. It simply tells the position of the class in the class hierarchy. A forward definition must be followed by a complete definition (with the same parent class !) before the class can be instantiated. Attempts to instantiate a class that has been defined only with a forward definition will produce an error. A forward definition is necessary in the case of recursive class definitions. Here is a simple example :
parent <: thing child <: thing(father:parent) parent <: thing(son:child) |
Although the father of a child is a parent (in the previous example), creating an instance of child does not create an implicit instance of parent that would be stored in the father slot. Once an instance of child is created, it is your responsibility to fill out the relevant slots of the objects. There exists a way to perform this task automatically, using the close method. This method is the CLAIRE equivalent to the notion of a constructor(in a C++ or Java sense). CLAIRE does not support class constructors since its instantiation control structure may be seen as a generic constructor for all classes. However, there are cases when additional operations must be performed on a newly created object. To take this into account, the close method is called automatically when an instantiation is done if a relevant definition is found. Remember that the close method must always return the newly create object, since the result of the instantiation is the result of the close method. Here is an example that shows how to create a parent for each new child object :
close(x:child) -> (x.father := parent(), x) |
Slots can be mono- or multi-valued. A multi-valued slot contains multiple values that are represented by a list (ordered) or a set (without duplicates). CLAIRE assumes by default that a slot with range list or set is multi-valued. However, the multi-valuation is defined at the property level. This is logical, since the difference between a mono-valued and a multi-valued slot only occurs when inversion or rules are concerned, which are both defined at the property level. This means that CLAIRE cannot accept slots for two classes with the same name and different multi-valuation status. For instance, the following program will cause an error :
A <: thing(x:set[integer]) // forces CLAIRE to consider x as multi-valued B <: thing(x:stack[integer]) // conflict: x cannot be multi-valued |
On the other hand, it is possible to explicitly tell CLAIRE that a slot with range list or set is mono-valued, as in the following correct example :
A <: thing(x:set[integer]) x.multivalued? := false // x is from A U B -> (set[integer] U stack[integer]) B <: thing(x:stack[integer]) |
It is sometimes advisable to set up manually the multi-valuation status of the property before creating the slots, in order to make sure that this status cannot be forced by the creation of another class with a mono-valued slot with the same name (this could happen within a many-authors project who share a namespace). This is achieved simply by creating the property explicitly :
x :: property(multivalued? = true) // creates the property // ... whatever happens will not change x's multi-valuation B <: thing(x:set[integer]) // safe definition of a multi-valued slot |
Classes | categories Objects, Classes and Slots Free-able objects |
parametric class |
Starting with XL CLAIRE, a class can inherit from freeable_object that are special kind of ephemeral objects regarding the Garbage collector (GC). We can define two GC callbacks (prefree! and free!) for such object that will be called when the GC attempt to free an object giving a chance for such object to perform a cleanup operation :
As an illustration we could define the following long_double class that import from C++ the 'long double' data type :
long_double* <: import() long_double <: freeable_object(value:long_double*) (c_interface(long_double*, "long double*")) close(self:long_double) : long_double -> (self.value := externC("(long double*)::malloc(sizeof(long double))", long_double*), self) free!(self:long_double) : void -> externC("::free(self->value)") |
Free-able objects | categories Objects, Classes and Slots parametric class |
Calls and Slot Access |
A class can be parameterized by a subset of its slots. This means that subsets of the class that are defined by the value of their parameters can be used as types. This feature is useful to describe parallel structures that only differ by a few points: parametrization helps describing the common kernel, provides a unified treatment and avoids redundancy.
A parameterized class is defined by giving the list of slot names into brackets. Parameters can be inherited slots, and include necessarily inherited parameters.
stack[of] <: object(of:type, content:list[any], index:integer = 0) complex[re,im] <: object(re:float = 0.0, im:float = 0.0) |
CLAIRE includes a type system that contains parametric class selections. For instance, the set of real numbers can be defined as a subset of complex with the additional constraint that the imaginary part is 0.0. This is expressed in CLAIRE as follows :
complex[re:float, im:{0.0}] |
In the previous example with stacks, parametric sub-types can be used to designate typed stacks. We can either specify the precise range of the stack (i.e., the value of the of parameter) or say that the range must be a sub-type of another type. For instance, the set of stacks with range integer and the set of stacks which contain integers are respectively :
stack[of:{integer}] stack[of:subtype[integer]] |
parametric class | categories Objects, Classes and Slots Calls and Slot Access |
Updates |
Calls are the basic building blocks of a CLAIRE program. A call is a polymorphic function call (a message) with the usual syntax : a selector followed by a list of arguments between parentheses. A call is used to invoke a method.
When the selector is an operation, such as +, -, %, etc... (% denotes set membership) an infix syntax is allowed (with explicit precedence rules) :
eval(x), f(x,y,z), x.price, y.name |
If a slot is read before being defined (its value being unknown), an error is raised. This only occurs if the default value is unknown. To read a slot that may not be defined, one must use the get(r:property,x:object) method :
John.father // may provoke an error if John.father is unknown get(father,john) // may return unknown |
When the selector is an operation, such as +,-,%,etc... (% denotes set membership) an infix syntax is allowed (with explicit precedence rules). Hence the following expressions are valid :
1 + 2 1 + 2 * 3 |
Note that new operations may be defined. This syntax extends to boolean operations (and:& and or:|). However, the evaluation follows the usual semantic for boolean expression (e.g., (x & y) does not evaluate y if x evaluates to false) :
(x = 1) & ((y = 2) | (y > 2)) & (z = 3) |
The values that are combined with and/or do not need to be boolean values (although boolean expressions always return the boolean values true or false). Following a philosophy borrowed from LISP, all values are assimilated to true, except for false, empty lists and empty sets. The special treatment for the empty lists and the empty sets yields a simpler programming style when dealing with lists or sets. Notice that in CLAIRE 3.0, contrary to previous releases, there are many empty lists since empty lists can be typed (list<integer>(), list<string>(), ... are all different).
A dynamic functional call where the selector is evaluated can be obtained using the call method. For instance, call(+,1,2) is equivalent to +(1,2) and call(show,x) is equivalent to show(x). The difference is that the first parameter to call can be any expression. This is the key for writing parametric methods using the inline capabilities of CLAIRE. This also means that using call is not a safe way to force dynamic binding, this should be done using the property abstract. An abstract property is a property that can be re-defined at any time and, therefore, relies on dynamic binding. Notice that call takes a variable number of arguments. A similar method named apply can be used to apply a property to an explicit list of arguments.
Since the use of call is somehow tedious, CLAIRE supports the use of variables (local or global) as selectors in a function call and re-introduce the call implicitly. For instance :
compose(f:property, g:property, x:any) : any => f(g(x)) |
is equivalent to :
compose(f:property, g:property, x:any) => call(f, call(g,x)) |
Calls and Slot Access | categories Objects, Classes and Slots Updates |
Lists, Sets and Instructions Lists, Sets and Tuples |
Assigning a value to a variable is always done with the operator :=. This applies to local variables but also to the slots of an object. The value returned by the assignment is always the value that was assigned :
x.age := 10, John.father := mary |
When the assignment depends on the former value of the variable, an implicit syntax ":op" can be used to combine the previous value with a new one using the operation op. This can be done with any (built-in or user-defined) operation (an operation is a function with arity 2 that has been explicitly declared as an operation) :
x.age :+ 1, John.friends :add mary, x.price :min 100 |
Note that the use of :op is pure syntactical sugar: x.A :op y is equivalent to x.A := (x.A op y). The receiving expression should not, therefore, contain side-effects as in the dangerous following example :
A(x :+ 1) :+ 1 |
Objects, Classes and Slots Updates |
categories Lists, Sets and Instructions Lists, Sets and Tuples |
Blocks |
CLAIRE provides two easy means of manipulating collections of objects: sets and lists. Lists are ordered, possibly heterogeneous, collections. To create a list, one must use the list(...) instruction : it admits any number of arguments and returns the list of its arguments. Each argument to the list(...) constructor is evaluated.
list(a, b, c, d) list(1, 2 + 3), list() |
Sets are collections without order and without duplicates. Sets are created similarly with the set(...) constructor :
set(a,b,c) set(1,2 + 3) |
The major novelty in CLAIRE 3.2 is the fact that lists or sets may be typed. This means that each bag (set or list) may have a type slot named of, which contains a type to which all members of the list must belong. This type is optional, as is illustrated by the previous examples, where no typing was given for the lists or sets. To designate a type for a new list or a new set, we use a slightly different syntax :
list<thing>(a,b,c,d) list<integer>(1,2 + 3) list<float>() set<thing>(a,b,c) set<integer>(1, 2 + 3) |
Typing a list or a set is a way to ensure that adding new values to them will not violate typing assumptions, which could happen in earlier versions of CLAIRE. Insertion is now always a destructive operation (add(l,x) returns the list l, that has been augmented with the value x at its end).
Since typing is mandatory in order to assume type-safe updates onto a list or a set, if no type is provided, CLAIRE will forbid any future update: the list or the set is then a "read-only" structure. This is the major novelty in CLAIRE 3.2: there is a difference between:
list(a,b,c,d) set(1,2 + 3) list{i | i in (1 .. 2)} |
which are read-only structures, and :
list<thing>(a, b) set<integer>(1, 2 + 3) list<integer>{i | i in (1 .. 2)} |
which are structures that can be updated.
List or set types can be arbitrarily complex, to represent complex list types such as list of lists of integers. However, it is recommended to use a global constant to represent a complex type that is used as a list type, as follows :
MyList :: list<integer> set<MyList>(list<integer>(1), list<integer>(2, 3)) |
Constant sets are valid CLAIRE types and can be built using the following syntax :
{a,b,c,d} {3, 8} |
The expressions inside a constant set expression are not evaluated and should be primitive entities, such as integers or strings, named objects or global constants. Constant sets are constant, which means that inserting a new value is forbidden and will provoke an error.
A set can also be formed by selection. The result can either be a set with {x in a | P(x)}, or a list with list{x in a | P(x)}, when one wants to preserve the order of a and keep the duplicates if a was a list. Similarly, one may decide to create a typed or an un-typed list or set, by adding the additional type information between angular brackets. For instance, here are two samples with and without typing :
{x in class | (thing % x.ancestors)} list{x in (0 .. 14) | x mod 2 = 0} set<class>{x in class | (thing % x.ancestors)} list<integer>{x in (0 .. 14) | x mod 2 = 0} |
When does one need to add typing information to a list or a set ? A type is needed when new insertions need to be made, for instance when the list or set is meant to be stored in an object's slot which is itself typed.
Also, the imageof a set via a function can be formed. Here again, the result can either be a set with {f(x)|x in a} or a list with list{f(x) | x in a}, when one wants to preserve the order of a and the duplicates :
{(x ^ 2) | x in (0 .. 10)} list<integer>{size(x.slots) | x in class} |
For example, we have the traditional average_salary method :
average_salary(s:set[man]) : float -> (sum(list{m.sal | m in s}) / size(s)) |
Last, two usual constructions are offered in CLAIRE to check a boolean expression universally (forall) or existentially (exists). A member of a set that satisfies a condition can be extracted (a non-deterministic choice) using the some construct: some(x in a | f(x)). For instance, we can write :
exists(x in (1 .. 10) | x > 2) // returns true some(x in (1 .. 10) | x > 2) // returns 3 in most implementations exists(x in class | length(x.ancestors) > 10) |
The difference between exists and some is that the first always returns a boolean, whereas the second returns one of the objects that satisfy the condition (if there exists one) and unknown otherwise. It is very often used in conjunction with when, as in the following example :
when x := some(x in man | rich?(x)) in (borrow_from(x,1000), ...) else printf("There is no one from whom to borrow!") |
Conversely, the boolean expression forall(x in a | f(x)) returns true if and only if f(x) is true for all members of the set a. The two following examples returns false (because of 1):
forall(x in (1 .. 10) | x > 2) forall(x in (1 .. n) | exists(y in (1 .. x) | y * y > x)) |
Definition : A list is an ordered collection of objects that is organized into an extensible array, with an indexed access to its members. A list may contain duplicates, which are multiple occurrence of the same object. A set is a collection of objects without duplicates and without any user-defined order. The existence of a system-dependent order is language-dependent and should not be abused. The concept of bag in CLAIRE is the unifier between lists and sets : a collection of objects with possible duplicates and without order. |
A read-only (untyped) list can also be thought as tuples of values. For upward compatibility reasons, the expression tuple(a1,...,an) is equivalent to list(a1,...,an) :
tuple(1,2,3), tuple(1,2.0,"this is heterogeneous") |
Since it is a read-only list, a tuple cannot be changed once it is created, neither through addition of a new member (using the method add) or through the exchange of a given member (using the nth= method). CLAIRE offers an associated data type. For instance, the following expressions are true :
tuple(1,2,3) % tuple(integer,integer,integer) tuple(1,2,3) % tuple(0 .. 1, 0 .. 10, 0 .. 100) tuple(1,2.0,"this is heterogeneous") % tuple(any,any,any) |
Typed tuples are used to return multiple values from a method. Because a tuple is a bag, it supports membership, iteration and indexed access operations. However, there is yet another data structure in CLAIRE for homogeneous arrays of fixed length, called arrays. Arrays are similar to lists but their size is fixed once they are created and they must be assigned a subtype (a type for the members of the array) that cannot change. Because of these strong constraints, CLAIRE can provide an implementation that is more efficient (memory usage and access time) than the implementation of bags. However, the use of arrays is considered an advanced feature of CLAIRE since everything that is done with an array may also be done with a list.
Lists, Sets and Tuples | categories Lists, Sets and Instructions Blocks |
Conditionals |
Parentheses can be used to group a sequence of instructions into one. In this case, the returned value is the value of the last instruction :
(x := 3, x := 5) |
Parentheses can also be used to explicitly build an expression. In the case of boolean evaluation (for example in an if), any expression is considered as true except false, empty sets and empty lists :
(1 + 2) * 3 if (x = 2 & l) |
Local variables can be introduced in a block with the let construct. These variables can be typed, but it is not mandatory (CLAIRE will use type inference to provide with a reasonable type). On the other hand, unlike languages such as C++, you always must provide an initialization value when you define a variable. A let instruction contains a sequence of variable definitions and, following the in keyword, a body (another instruction). The scope of the local variable is exactly that body and the value of the let instruction is the value returned by this body.
let x := 1, y := 3 in (z := x + y, y := 0) |
Notice that CLAIRE uses := to represent assignment and = to represent equality. The compiler will issue a warning if a statement (x = y) is used where an assignment was probably meant (this is the case when the value of the assignment is not needed, such as in x := 1, y = 3, z := 4).
The value of local variables can be changed with the same syntax as an update to an object: the syntax :op is allowed for all operations op :
x := x + 1, x :+ 1, x :/ 2, x :^ 2 |
The name of a local variable can be any identifier, including the name of an existing object or variable. In that case, the new variable overrides the older definition within the scope of the let. While this may prove useful in a few cases, it should be used sparingly since it yields to code that is hard to read. A rule of thumb is to avoid mixing the name of variables and the name of properties since it often produces errors that are hard to catch (the property cannot be accessed any more once a variable with the same name is defined). The control structure when is a special form of let, which only evaluates the body if the value of the local variable (unique) is not unknown (otherwise, the returned value is unknown). This is convenient to use slots that are not necessarily defined as in the following example :
when f := get(father,x)
in printf("his father is ~S |
The default behavior when the value is unknown can be specified using the else keyword. The statement following the else keyword will be evaluated and its value will be returned when the value of the local variable is unknown :
when f := get(father,x)
in printf("his father is ~S |
Local variables can also be introduced as a pattern, that is a tuple of variables. In that case, the initial value must be a tuple of the right length. For instance, one could write :
let (x, y, z) := tuple(1, 2, 3) in x + y + z |
The tuple of variable is simply introduced as a sequence of variables surrounded by two parentheses. The most common use of this form is to assign the multiple values returned by a function with range tuple, as we shall see in the next section. If we suppose that f is a method that returns a tuple with arity 2, then the two following forms are equivalent:
let (x1,x2) := f() in ... let l := f(), x1 := l[1], x2 := l[2] in ... |
[XL] In XL CLAIRE, as a syntactical shortcut, we can define in a single let statement both tuple assigment and normal variable assigment as in :
let (x1,x2) := f(), x3 := g() in ... |
Tuples of variables can also be assigned directly within a block as in the following example :
(x1, x2) := tuple(x2, x1) |
Although this mostly used for assigning the result of tuple-valued functions without any useless allocation, it is interesting to note that the previous example will be compiled into a nice value-exchange interaction without any allocation (the compiler is smart enough to determine that the list "list(x2,x1)" is not used as such).
The key principle of lexical variables is that they are local to the "let" in which they are defined. CLAIRE supports another similar type of block, which is called a temporary slot assignment. The idea is to change the value of a slot but only locally, within a given expression. This is done as follows:
let x.r := y in e |
changes the value of r(x) to y, executes e and then restore r(x) to its previous value. It is strictly equivalent to
let old_v := x.r in (x.r := y, let result := e in (x.r := old_v, result)) |
CLAIRE provides automatic type inference for variables that are defined in a let so that explicit typing is not necessary in most of the cases. Here are a few rules to help you decide if you need to add an explicit type to your variable or even cast a special type for the value that is assigned to the variable :
let x := (y as t2) in ... |
Blocks | categories Lists, Sets and Instructions Conditionals |
Loops |
if statements have the usual syntax (if <test> x else y) with implicit nestings (else if). The <test> expression is evaluated and the instruction x is evaluated if the value is different from false, nil or {}. Otherwise, the instruction y is evaluated, or the default value false is returned if no else part was provided.
if (x = 1) x := f(x,y) else if (x > 1) x := g(x,y) else (x := 3, f(x,y)) if (let y := 3 in x + y > 4 / x) print(x) |
If statements must be inside a block, which means that if they are not inside a sequence surrounded by parenthesis they must be themselves surrounded by parenthesis (thus forming a block).
case is a set-based switch instruction: CLAIRE tests the branching sets one after another, executes the instruction associated with the first set that contains the object and exits the case instruction without any further testing. Hence, the default branch is associated with the set any. As for an if, the returned value is nil if no branch of the case is relevant :
case x ({1} x + 1, {2,3} x + 2, any x + 3)
case x (integer (x := 3, print(x)),
any error("~I is no good |
Note that the compiler will not accept a modification of the variable that is not consistent with the branch of the case (such as case x ({1} x := 2)). The expression on which the switching is performed is usually a variable, but can be any expression. However, it should not produce any side effect since it will be evaluated many times.
Starting with CLAIRE 3.3, only boolean expressions should be used in the <test> expression of a conditional statement. The implicit coercion of any expression into a Boolean is still supported, but should not be used any longer. The compiler will issue a warning if a non-boolean expression is used in an If.
Conditionals | categories Lists, Sets and Instructions Loops |
Instantiation |
CLAIRE supports two types of loops: iteration and conditional loops (while and until). Iteration is uniquely performed with the for statement, it can be performed on any collection :
for x in (1 .. 3) a[x] := a[x + 3]
for x in list{x in class | size(x.ancestors) >= 4}
printf("~S |
A collection here is taken in a very general sense, i.e., an object that can be seen as a set through the enumeration method set!. This includes all CLAIRE types but is not restricted since this method can be defined on new user classes that inherit from the collection root. For instance, set!(n:integer) returns the subset of (0 .. 29) that is represented by the integer n taken as a bit-vector. To tell CLAIRE that her new class is a collection, the user must define it as a subclass of collection. If x is a collection, then :
for z in x ... (z % x) |
are supported. When defining a new subclass of collection, the methods set! and % must be defined for this new class, and it is also advisable to define size and iterate to get compiler speed-ups (if size is not defined, an implicit call to set! is made). Other collection handling methods, such as add, delete, etc may be defined freely if needed.
Notice that it is possible that the expression being evaluated inside the loop modifies the set itself, such as in :
for x in {y in S | P(y)} P(x) := false |
Because the CLAIRE compiler tries to optimize iteration using lazy evaluation, there is no guarantee about the result of the previous statement. In this case, it is necessary to use an explicit copy as follows :
for x in copy({y in S | P(y)}) P(x) := false |
The iteration control structure plays a major role in CLAIRE. It is possible to optimize its behavior by telling CLAIRE how to iterate a new subclass (C) of collection. This is done through adding a new restriction of the property iterate for this class C, which tells how to apply a given expression to all members of an instance of C. This may avoid the explicit construction of the equivalent set which is performed through the set! method.
Conditional loops are also standard (the exiting condition is executed before each loop in a while and after each loop in a until),
while (x > 0) x :+ 1 until (x = 12) x :+ 1 while not(i = size(l)) (l[i] := 1, i :+ 1) |
The value of a loop is false. However, loops can be exited with the break(x) instruction, in which case the return value is the value of x :
for x in class (if (x % subtype[integer]) break(x)) |
There is one restriction with the use of break: it cannot be used to escape from a try ... catch block. This situation will provoke an error at compile-time.
Loops | categories Lists, Sets and Instructions Instantiation |
Exception Handling |
Instantiation is the mechanism of creating a new object of a given class; instantiation is done by using the class as a selector and by giving a list of "<slot> = <value>" pairs as arguments :
complex(re = 0.0, im = 1.0) person(age = 0, father = john) |
Recall that the list of instances of a given class is only kept for non-ephemeral classes (a class is ephemeral if has been declared as such or if it inherits from the ephemeral_object class). The creation of a new instance of a class yields to a function call to the method close. Objects with a name are represented by the class thing, hence descendents of thing (classes that inherit from thing) can be given a name with the definition operation ::. These named objects can later be accessed with their name, while objects with no name offer no handle to manipulate them after their creation outside of their block (objects with no name are usually attached to a local variable with a let whenever any other operation other than the creation itself is needed) :
paul :: person(age = 10, father = peter) |
Notice that the identifier used as the name of an object is a constant that cannot be changed. Thus, it is different from creating a global variable that would contain an object as in :
aGoodGuy:person :: person(age = 10, father = peter) |
Additionally, there is a simpler way of instantiating parameterized classes by dropping the slot names. All values of the parameter slots must be provided in the exact order that was used to declare the list of parameters. For instance, we could use :
complex(0.0,1.0), stack(integer) |
The previously mentioned instantiation form only applies to a parameterized class. It is possible to instantiate a class that is given as a parameter (say, the variable v) using the new method. New(v) creates an instance of the class v and new(v,s) creates a named instance of the class v (assumed to be a subclass of thing) with the name s.
Instantiation | categories Lists, Sets and Instructions Exception Handling |
array |
Exceptions are a useful feature of software development: they are used to describe an exceptional or wrong behavior of a block. Exception can be raised, to signal this behavior and are caught by exception handlers that surround the code where the exceptional behavior happened. Exceptions are CLAIRE objects (a descendent from the class exception) and can contain information in slots. The class exception is an "ephemeral" class, so the list of instances is not kept. In fact, raising an exception e is achieved by creating an instance of the class e. Then, the method close is called: the normal flow of execution is aborted and the control is passed to the previously set dynamic handler. A handler is created with the following instruction :
try <expression> catch <class> <expression> |
For instance we could write :
try 1 / x catch any (printf("1/~A does not exists", x), 0) |
A handler "try e catch c f", associated with a class c, will catch all exceptions that may occur during the evaluation of e as long as they belong to c. Otherwise the exception will be passed to the previous dynamic handler (and so on). When a handler "catches" an exception, it evaluates the "f" part and its value is returned. The last exception that was raised can be accessed directly with the exception!() method. Also, as noticed previously, the body of a handler cannot contain a break statement that refers to a loop defined outside the handler.
The most common exceptions are errors and there is a standard way to create an error in CLAIRE using the error(s:string, l:listargs) instruction. This instruction creates an error object that will be printed using the string s and the arguments in l, as in a printf statement. Here are a few examples :
error("stop here") error("the value of price(~S) is ~S !", x, price(x)) |
Another very useful type of exception is contradiction. CLAIRE provides a class contradiction and a method contradiction!() for creating new contradictions. This is very commonly used for hypothetical reasoning with forms like :
try (choice(), // create a new world ...) // performs an update that may cause a contradiction catch contradiction (backtrack(), // return to previous world ...) |
In fact, this is such a common pattern that CLAIRE provides a special instruction, branch(x), which evaluates an expression inside a temporary world and returns a boolean value, while detecting possible contradiction. The statement branch(x) is equivalent to :
try (choice(), if x true else (backtrack(), false)) catch contradiction (backtrack(), false) |
If we want to find a value for the slot x.r among a set x.possible that does not cause a contradiction (through rule propagation) we can simply write :
when y := some(y in x.possible | branch(x.r = y)) in x.r := y else contradiction!() |
Exception Handling | categories Lists, Sets and Instructions array |
Methods and Types Methods |
An array can be seen as a fixed-size list, with a member type (the slot name is of), which tells the type of all the members of the array. Because of the fixed size, the compiler is able to generate faster code than when using lists, so lists should be used when the collection shrinks and grows, and an array may be used otherwise. This is especially true for arrays of floats, which are handled in a special (and efficient) way by the compiler.
Arrays are simpler than lists, and only a few operations are supported. Therefore, more complex operations such as append often require a cast to list (list!). An array is created explicitly with the make_array property :
let l := make_array(10,float,0.0) in l[1] := l[3] + l[4] |
Operations on arrays include copying, casting a bag into an array (array!), defeasible update on arrays using store, and returning the length of the array with length. An array can also be made from a list using array!, which is necessary to create arrays that contain complex objects (such as arrays of arrays).
Lists, Sets and Instructions array |
categories Methods and Types Methods |
Types |
A method is the definition of a property for a given signature. A method is defined by the following pattern : a selector (the name of the property represented by the method), a list of typed parameters (the list of their types forms the domain of the method), a range expression and a body (an expression or a let statement introduced by -> or =>).
<selector>(<typed parameters>) : <range>opt < -> | => > <body>
fact(n:{0}) : integer -> 1
fact(n:integer) : integer -> (n * fact(n - 1))
print_test() : void
-> (print("Hello"),
print("world |
Definition : A signature is a Cartesian product of types that always contains the extension of the function. More precisely, a signature A1* A2* ... * An, also represented as list(A1, ...,An) or A1* A2* ... * An-1 -> An, is associated to a method definition f(...) : An -> ... for two purposes: it says that the definition of the property f is only valid for input arguments (x1, x2, ..., xn-1) in A1* A2* ... * An-1 and it says that the result of f(x1, x2, ..., xn-1) must belong to An. The property f is also called an overloaded function and a method m is called its restriction to A1* A2* ... * An-1. |
If two methods have intersecting signatures and the property is called on objects in both signatures, the definition of the method with the smaller domain is taken into account. If the two domains have a non-empty intersection but are not comparable, a warning is issued and the result is implementation-dependent. The set of methods that apply for a given class or return results in another can be found conveniently with methods.
The range declaration can only be omitted if the range is void. In particular, this is convenient when using the interpreter :
loadMM() -> (begin(my_module), load("f1"), load("f2"), end(my_module)) |
If the range is void (unspecified), the result cannot be used inside another expression (a type-checking error will be detected at compilation). A method's range must be declared void if it does not return a value (for instance, if its last statement is, recursively, a call to another method with range void). It is important not to mix restrictions with void range with other regular methods that do return a value, since the compiler will generate an error when compiling a call unless it can guarantee that the void methods will not be used.
The default range was changed to void in the version 3.3 of CLAIRE, in an effort to encourage proper typing of methods: "no range" means that the method does not return a value. This is an important change when migrating code from earlier versions of CLAIRE. CLAIRE supports methods with a variable number of arguments using the listargs keyword. The arguments are put in a list, which is passed to the (unique) argument of type listargs. For instance, if we define :
[f(x:integer,y:listargs) -> x + size(y)] |
A call f(1,2,3,4) will produce the binding x = 1 and y = list(2,3,4) and will return 4.
CLAIRE also supports functions that return multiple values using tuples. If you need a function that returns n values v1,v2,...,vn of respective types t1,t2,...,tn, you simply declare its range as tuple(t1,t2,...,tn) and return tuple(v1,v2,...,vn) in the body of the function. For instance the following method returns the maximum value of a list and the "regret" which is the difference between the best and the second-best value :
[my_max(l:list[integer]) : tuple(integer,integer) -> let x1 := 1000000000, x2 := 1000000000 in (for y in l (if (y < x1) (x2 := x1, x1 := y) else if (y < x2) x2 := y), tuple(x1,x2))] |
The tuple produced by a tuple-valued method can be used in any way, but the preferred way is to use a tuple-assignment in a let. For instance, here is how we would use the max2 method :
let (a,b) := my_max(list{f(i) | i in (1 .. 10)}) in ... |
Each time you use a tuple-assignment for a tuple-method, the compiler uses an optimization technique to use the tuple virtually without any allocation. This makes using tuple-valued methods a safe and elegant programming technique.
The body of a method is either a CLAIRE expression (the most common case) or an external (C++) function. In the first case, the method can be seen as defined by a lambda abstraction. This lambda can be created directly through the following :
lambda[(<typed parameters>), <body>] |
Defining a method with an external function is the standard way to import a C/C++ function in CLAIRE. This is done with the function!(...) constructor, as in the following :
f(x:integer,y:integer) -> function!(my_version_of_f) cos(x:float) -> function!(cos_for_claire) |
It is important to notice that in CLAIRE, methods can have at most 20 parameters. Methods with 40 or more parameters that exist in some C++ libraries are very hard to maintain. It is advised to use parameter objects in this situation.
CLAIRE also provides inline methods, that are defined using the => keyword before the body instead of ->. An inline method behaves exactly like a regular method. The only difference is that the compiler will use in-line substitution in its generated code instead of a function call when it seems more appropriate. Inline methods can be seen as polymorphic macros, and are quite powerful because of the combination of parametric function calls (using call(...)) and parametric iteration (using for). Let us consider the two following examples, where subtype[integer] is the type of everything that represents a set of integers :
sum(s:subtype[integer]) : integer => let x := 0 in (for y in s x :+ y, x) min(s:subtype[integer], f:property) : integer => let x := 0, empty := true in (for y in s (if empty (x := y, empty := false) else if call(f,y,x) x := y), x) |
For each call to these methods, the compiler performs the substitution and optimizes the result. For instance, the optimized code generated for sum({x.age | x in person}) and for min({x in 1 .. 10 | f(x) > 0}, >) will be :
let x := 0 in (for %v in person.instances let y := %v.age in x :+ y, x) let x := 0, empty := true, y := 1, max := 10 in (while (y <= max) (if (f(y) > 0) (if empty (x := y, empty := false) else if (y > x) x := y), y :+ 1), x) |
Notice that, in these two cases, the construction of temporary sets is totally avoided. The combined use of inline methods and functional parameters provides an easy way to produce generic algorithms that can be instantiated as follows :
mymin(l:list[integer]) : integer -> min(l, my_order) |
The code generated for the definition of mymin @ list[integer] will use a direct call to my_order (with static binding) and the efficient iteration pattern for lists, because min is an inline method. In that case, the previous definition of min may be seen as a pattern of algorithms.
For upward compatibility reasons (from release 1.0), CLAIRE still supports the use of external brackets around method definitions. The brackets are there to represent boxes around methods (and are pretty-printed as such with advanced printing tools). For instance, one can write :
[mymin(l:list[integer]) : integer -> min(l, my_order)] |
Brackets have been found useful by some users because one can search for the definition of the method m by looking for occurrences of '[mmm'. They also transform a method definition into a closed syntactical unit that may be easier to manipulate (e.g., cut-and-paste).
When a new property is created, it is most often implicitly with the definition of a new method or a new slot, although a direct instantiation is possible. Each property has an extensibility status that may be one of :
The compiler will automatically change the status from undefined to closed, unless the status is forced with the abstract declaration :
abstract(p) |
Conversely, the final declaration :
final(p) |
may be used to force the status to closed, in the interpreted mode. Note that these two declarations have obviously an impact on performance: an open property will be compiled with the systematic used of dynamic calls, which ensures the extensibility of the compiled code, but at a price. On the contrary, a final property will enable the compiler to use as much static binding as possible, yielding faster call executions. Notice that the interface(p) declaration has been introduced to support dynamic dispatch in a efficient manner, as long as the property is uniform.
Methods | categories Methods and Types Types |
Polymorphism |
CLAIRE uses an extended type system that is built on top of the set of classes. Like a class, a type denotes a set of objects, but it can be much more precise than a class. Since methods are attached to types (by their signature), this allows attaching methods to complex sets of objects.
Definition : A (data) type is an expression that represents a set of objects. Types offer a finer-granularity partition of the object world than classes. They are used to describe objects (range of slots), variables and methods (through their signatures). An object that belongs to a type will always belong to the set represented by the type. |
Any class (even parameterized) is a type. A parameterized class type is obtained by filtering a subset of the class parameters with other types to which the parameters must belong. For instance, we saw previously that complex[im:{0.0}] is a parametrized type that represent the real number subset of the complex number class. This also applies to typed lists or sets which use the of parameter. For instance, list[of:{integer}] is the set of list whose of parameter is precisely integer. Since these are common patterns, CLAIRE offers two shortcuts for parameterized type expressions. First, it accepts the expression C[p = v] as a shortcut for C[p:{v}]. Second, it accepts the expression C<X> as a shortcut for C[of = X]. This applies to any class with a type-valued parameter named of; Thus, stack<integer> is the set of stacks whose parameter "of" is exactly integer, whereas stack[of:subtype[integer]] is the set of stacks whose parameter (a type) is a subset of integer.
Finite constant sets of objects can also be used as types. For example, {john, jack, mary} and {1,4,9} are types. Intervals can be used as types; the only kind of intervals supported by CLAIRE 3.0 is integer intervals. Types may also formed using the two intersection (^) and union (U) operations. For example, integer U float denotes the set of numbers and (1 .. 100) ^ (-2 .. 5) denotes the intersection of both integer intervals, i.e. (1 .. 5).
Subtypes are also as type expressions. First, because types are also objects, CLAIRE introduces subtype[t] to represent the set of all type expressions that are included in t. This type can be intersected with any other type, but there are two cases which are more useful than other, namely subtypes of the list and set classes. Thus, CLAIRE uses set[t] as a shortcut for set ^ subtype[t] and list[t] as a shortcut for list ^ subtype[t]. Because of the semantics of lists, one may see that list[t] is the union of two kinds of lists :
Therefore, there is a clear difference between
Obviously, we have list<t> <= list[t]. When should you use one or the other form of typed lists or sets ?
Classes are sorted with the inheritance order. This order can be extended to types with the same intuitive meaning that a type t1 is a subtype of a type t2 if the set represented by t1 is a subset of that represented by t2. The relation "t1 is a subtype of a type t2" is noted t1 <= t2. This order supports the introduction of the " subtype " constructor: subtype[t] is the type of all types that are less than t.
Types | categories Methods and Types Polymorphism |
Escaping Types |
In addition to the traditional "objet-oriented" polymorphism, CLAIRE also offers two forms of parametric polymorphism, which can be skipped by a novice reader.
(1) There often exists a relation between the types of the arguments of a method. Capturing such a relation is made possible in CLAIRE through the notion of an "extended signature". For instance, if we want to define the operation "push" on a stack, we would like to check that the argument y that is being pushed on the stack s belongs to the type of(s), that we know to be a parameter of s. The value of this parameter can be introduced as a variable and reused for the typing of the remaining variables (or the range) as follows :
push(s:stack<X>, y:X) -> (s.content :add y, s.index :+ 1) |
The declaration s:stack<X> introduced X as a type variable with value s.of, since stack[of] was defined as a parameterized class. Using X in y:X simply means that y must belong to the type s.of. Such intermediate type variables must be free identifiers (the symbol is not used as the name of an object) and must be introduced with the following template :
<class>[pi=vi, ...] |
The use of type variables in the signature can be compared to pattern matching. The first step is to bind the type variable. If (p = V) is used in c[ ...] instead of p:t, it means that we do not put any restriction on the parameter p but that we want to bind its value to V for further use. Note that this is only interesting if the value of the parameter is a type itself. Once a type variable V is defined, it can be used to form a pattern (called a <type with var> in the CLAIRE syntax) as follows:
<type with var> = <type> | <var> | {<var>} | tuple(<type with var>seq+) | <class>[< <var>:<type with var> | <var>=<var> >seq+] |
(2) The second advanced typing feature of CLAIRE is designed to capture the fine relationship between the type of the output result and the types of the input arguments. When such a relationship can be described with a CLAIRE expression e(x1,...,xn), where x1, ..., xn are the types of the input parameters, CLAIRE allows to substitute type[e] to the range declaration. It means that the result of the evaluation of the method should belong to e(t1,...,tn) for any types t1,...,tn that contain the input parameters.
For instance, the identity function is known to return a result of the same type as its input argument (by definition !). Therefore, it can be described in CLAIRE as follows :
id(x:any) : type[x] -> x |
In the expression that we introduce with the type[e] construct, we can use the types of the input variables directly through the variables themselves. For instance, in the "type[x]" definition of the id example, the "x" refers to the type of the input variable. Notice that the types of the input variables are not uniquely defined. Therefore, the user must ensure that her "prediction" e of the output type is valid for any valid types t1, ..., tn of the input arguments.
The expression e may use the extra type variables that were introduced earlier. For instance, we could define the "top" method for stacks as follows :
top(s:stack<X>) : type[X] -> s.content[s.index] |
The "second-order type" e (second-order means that we type the method, which is a function on objects, with another function on types) is built using the basic CLAIRE operators on types such as U, ^ and some useful operations such as "member". If c is a type, member(c) is the minimal type that contains all possible members of c. For instance, member({c}) = c by definition. This is useful to describe the range of the enumeration method set!. This method returns a set, whose members belong to the input class c by definition. Thus, we know that they must belong to the type member(X) for any type X to whom c belongs (cf. definition of member). This translates into the following CLAIRE definition :
set!(c:class) : type[set[member(c)]] -> c.instances |
For instance, if c belongs to subtype[B] then set!(c) belongs to set[B].
To summarize, here is a more precise description of the syntax for defining a method :
<function>(<vi>:<ti>i E (1 .. n)) : <range> -> <exp> |
Each type ti for the variable vi is an "extended type" that may use type variables introduced by the previous extended types t1, t2 ... ti-1 . An extended type is defined as follows :
<et> = <class> | <set> | <var> | (<et> ^ | U <et>) | (<integer> .. <integer>) | set[<et>] | list[<et>] | <et>[] | tuple(<et>seq) | <class>[< <var>:<et> | <var>=< <var> | <const> > >seq+] |
The <range> expression is either a regular type or a "second order type", which is a CLAIRE expression e introduced with the type[e] syntactical construct :
<range> = <type> | type[<expression>] |
Polymorphism | categories Methods and Types Escaping Types |
Selectors, Properties and Operations |
There are two ways to escape type checking in CLAIRE. The first one is casting, which means giving an explicit type to an expression. The syntax is quite explicit :
<cast> = (<expression> as <type>) |
This will tell the compiler that <expression> should be considered as having type <type>. Casting is ignored by the interpreter and should only be used as a compiler optimization. There is, however, one convenient exception to this rule, which is the casting into a list parametric type. When an untyped list is casted into a typed list, the value of its of parameter is actually modified by the interpreter, once the correct typing of all members has been verified. For instance, the two following expressions are equivalent :
list<integer>(1,2,3,4) list(1,2,3,4) as list<integer> |
The second type escaping mechanism is the non-polymorphic method call, where we tell what method we want to use by forcing the type of the first argument. This is equivalent to the supermessage passing facilities of many object-oriented languages.
<super> = <selector>@<type>(<exp>seq) |
The instruction f@c(...) will force CLAIRE to use the method that it would use for f(...) if the first argument was of type c (CLAIRE only checks that this first argument actually belongs to c).
A language is type-safe if the compiler can use type inference to check all type constraints (ranges) at compile-time and ensure that there will be no type checking errors at run-time. CLAIRE is not type-safe because it admits expressions for which type inference is not possible such as read(p) + read(p). On the other hand, most expressions in CLAIRE may be statically type-checked and the CLAIRE compiler uses this property to generate code that is very similar to what would be produced with a C++ compiler. A major difference between CLAIRE 3.0 and earlier versions is the fact that lists may be explicitly typed, which removes the problems that could happen earlier with dynamic types. Lists and sets subtypes support inclusion polymorphism, which means that if A is a subtype of B, list[A] is a subtype of list[B]; for instance list[(0 .. 1)] <= list[integer]. Thus only read operations can be statically type-checked w.r.t. such type information. On the other hand, array subtypes, as well as list or set parametric subtypes, are monomorphic, since A[] is not the set of arrays which contain members of A, but the set of arrays whose member type (the of slot) contains the value A. Thus if A is different from B, A[] is not comparable with B[], and list<A> is not comparable with list<B>. This enables the static type-checking of read and write operations on lists. The fact that CLAIRE supports all styles of type disciplines is granted by the combination of a rich dynamic type system coupled with a powerful type inference mechanism within the compiler, and is a key feature of CLAIRE.
Escaping Types | categories Methods and Types Selectors, Properties and Operations |
Iterations |
As we said previously, CLAIRE supports two syntaxes for using selectors, f(...) and (.... f ....). The choice only exists when the associated methods have exactly two arguments. The ability to be used with an infix syntax is attached to the property f :
f :: operation() |
Once f has been declared as an operation, CLAIRE will check that it is used as such subsequently. Restrictions of f can then be defined with the usual syntax :
f(x:integer, y:integer) : ... |
Note that declaring f as an operation can only be done when no restriction of f is known. If the first appearance of f is in the declaration of a method, f is considered as a normal selector and its status cannot be changed thereafter. Each operation is an object (inherits from property) with a precedence slot that is used by the reader to produce the proper syntax tree from expressions without parentheses.
gcd :: operation(precedence = precedence(/)) 12 + 3 gcd 4 // same as 12 + (3 gcd 4) |
So far we have assumed that any method definition is allowed, provided that inheritance conflict may cause warning. Once a property is compiled, CLAIRE uses a more restrictive approach since only new methods that have an empty intersection with existing methods (for a given property) are allowed. This allows the compiler to generate efficient code. It is possible to keep the "open" status of a property when it is compiled through the abstract declaration.
abstract(f) |
Such a statement will force CLAIRE to consider f as an "abstract" parameter of the program that can be changed at any time. In that case, any re-definition of f (any new method) will be allowed. When defining a property parameter, one should keep in mind that another user may redefine the behavior of the property freely in the future.
It is sometimes useful to model a system with redundant information. This can be done by considering pairs of relations inverse one of another. In this case the system maintains the soundness of the database by propagating updates on one of the relations onto the other. For example if husband is a relation from the class man onto the class woman and wife a relation from woman to man, if moreover husband and wife have been declared inverse one of another, each modification (addition or retrieval of information) on the relation husband will be propagated onto wife. For example husband(mary) := john will automatically generate the update wife(john) := mary. Syntactically, relations are declared inverses one of another with the declaration :
inverse(husband) := wife |
This can be done for any relation: slots and tables. Inverses introduce an important distinction between multi-valued relations and mono-valued relations. A relation is multi-valued in CLAIRE when its range is a subset of bag (i.e. a set or a list). In that case the slot multivalued? of the relation is set to true and the set associated with an object x is supposed to be the set of values associated with x through the relation.
This has the following impact on inversion. If r and s are two mono-valued relations inverse one of another, we have the following equivalence :
s(x) = y <=> r(y) = x |
In addition, the range of r needs to be included in the domain of s and conversely. The meaning of inversion is different if r is multi-valued since the inverse declaration now means :
s(x) = y <=> x E r(y) |
Two multi-valued relations can indeed be declared inverses one of another. For example, if parents and children are two relations from person to set[person] and if inverse(children) = parents, then :
children(x) = {y in person | x % parents(y)} |
Modifications to the inverse relation are triggered by updates (with :=) and creations of objects (with filled slots). Since the explicit inverse of a relation is activated only upon modifications to the database (it is not retroactive), one should always set the declaration of an inverse as soon as the relation itself is declared, before the relation is applied on objects. This will ensure the soundness of the database. To escape the triggering of updates to inverse relations, the solution is to fill the relation with the method put instead of :=. For example, the following declaration :
let john := person() in (put(wife,john,mary), john) |
does the same as :
john :: person(wife = mary) |
without triggering the update husband(mary) := john.
Selectors, Properties and Operations | categories Methods and Types Iterations |
Tables, Rules and Hypothetical Reasoning Tables |
We just saw that CLAIRE will produce in-line substitution for some methods. This is especially powerful when combined with parametric function calls (using call(...)) taking advantage of the fact that CLAIRE is a functional language. There is another form of code substitution supported by CLAIRE that is also extremely useful, namely the iteration of set data structure.
Any object s that understands the set! method can be iterated over. That means that the construction for x in s e(x) can be used. The actual iteration over the set represented by s is done by constructing explicitly the set extension. However, there often exists a way to iterate the set structure without constructing the set extension. The simplest example is the integer interval structure that is iterated with a while loop and a counter.
It is possible to define iteration in CLAIRE through code substitution. This is done by defining a new inline restriction of the property iterate, with signature (x:X,v:Variable,e:any). The principle is that CLAIRE will replace any occurrence of (for v in x e) by the body of the inline method as soon as the type of the expression x matches with X (v is assumed to be a free variable in the expression e). For instance, here is the definition of iterate over integer intervals :
iterate(x:interval[min:integer,max:integer],v:Variable,e:any) => let v := min(x), %max := max(x) in (while (v <= %max) (e, v :+ 1)) |
Here is a more interesting example. We can define hash tables as follows. A table is defined with a list (of size 2n - 3, which is the largest size for which a chunk of size 2n is allocated), which is full of "unknown" except for these objects that belong to the set. Each object is inserted at the next available place in the table, starting at a point given by the hashing function (a generic hashing function provided by CLAIRE: hash) :
htable <: object(count:integer = 0, index:integer = 4, arg:list<any> = list<any>()) set!(x:htable) : set -> {y in x.arg | known?(y)} insert(x:htable, y:any) -> let l := x.arg in (if (x.count >= length(l) / 2) (x.arg := make_list(^2(x.index - 3), unknown), x.index :+ 1, x.count := 0, for z in {y in l | known?(y)} insert(x,z), insert(x,y)) else let i := hash(l,y) in (until (l[i] = unknown | l[i] = y) (if (i = length(l)) i := 1 else i :+ 1), if (l[i] = unknown) (x.count :+ 1, l[i] := y))) |
Note that CLAIRE provides a few other functions for hashing that would allow an even simpler, though less self-contained, solution. To iterate over such hash tables without computing set!(x) we define :
iterate(s:htable, v:Variable, e:any) => (for v in s.arg (if known?(v) e)) |
Thus, CLAIRE will replace :
let s:htable := ... in sum(s) |
by :
let s:htable := ... in (let x := 0 in (for v in s.arg (if known?(v) x :+ v), x)) |
The use of iterate will only be taken into account in the compiled code unless one uses oload, which calls the optimizer for each new method. iterate is a convenient way to extend the set of CLAIRE data structure that represent sets with the optimal efficiency. Notice that, for a compiled program, we could have defined set! as follows (this definition would be valid for any new type of set) :
set!(s:htable) -> {x | x in s} |
When defining a restriction of iterate, one must not forget the handling of values returned by a break statement. In most cases, the code produce by iterate is itself a loop (a for or a while), thus this handling is implicit. However, there may be multiples loops, or the final value may be distinct from the loop itself, in which case an explicit handling is necessary. Here is an example taken from class iteration :
iterate(x:class, v:Variable, e:any) : any => (for %v_1 in x.descendents let %v_2 := (for v in %v_1.instances e) // catch inner break in (if %v_2 break(%v_2))) // transmit the value |
Notice that it is always possible to introduce a loop to handle breaks if none are present; we may replace the expression e by :
while true (e, break(nil)) |
Last, we need to address the issue of parametric polymorphism, or how to define new kinds of type sets. The previous example of hash-sets is incomplete, because it only describes generic hash-sets that may contain any element. If we want to introduce typed hash-sets, we need to follow these three steps. First we add a type parameter to the htable class :
htable[of] <: object(of:type = any, count:integer = 0, ...) |
Second, we use a parametric signature to use the type parameter appropriately :
insert(x:htable<X>, y:X) -> ... |
Last, we need to tell the compiler that an instance from htable[X] only contains objects from X. This is accomplished by extending the member function which is used by the compiler to find a valid type for all members of a given set. If x is a type, member(x) is a valid type for any y that will belong to a set s of type x. If T is a new type of sets, we may introduce a method member(x :T, t :type) that tells how to compute member(t) if t is included in T. For instance, here is a valid definition for our htable example :
member(x:htable,t:type) -> member(t @ of) |
This last part may be difficult to grasp (do not worry, this is an advanced feature). First, recall that if t is a type and p a property, (t @ p) is a valid type for x.p when x is of type t. Suppose that we now have an expression e, with type t1, that represents a htable (thus t1 <= htable). When the compiler calls member(t1), the previous method is invoked (x is bound to a system-dependent value that should not be used and t is bound to t1). The first step is to compute (t1 @ of), which is a type that contains all possible values for y.of, where y is a possible result of evaluating e. Thus, member(t1 @ of) is a type that contains all possible values of y, since they must belong to y.of by construction. This type is, therefore, used by the compiler as the type of the element variable v inside the loop generated by iterate.
Methods and Types Iterations |
categories Tables, Rules and Hypothetical Reasoning Tables |
Rules |
Named arrays, called tables, can be defined in CLAIRE with the following syntax :
<name>[var:<domain>] : <type> := <expression(var)> |
The <type> is the range of the table and <expression> is an expression that is used to fill the table. This expression may either be a constant or a function of the variables of the table (i.e., an expression in which the variables appear). If the expression is a constant, it is implicitly considered as a default value, the domain of the table may thus be infinite. If the default expression is a function, then the table is filled when it is created, so the domain needs to be finite. When one wants to represent incomplete information, one should fill this spot with the value unknown. For instance, we can define :
square[x:(0 .. 20)] : integer := (x * x) creator[x:class] : string := "who created that class" maximum[x:set[0 .. 10]] : integer := (if x min(x,> @ integer) else 0) |
Tables can be accessed through square brackets and can be modified with assignment expressions like for local variables :
square[1], square[2] := 4, square[4] :+ 5 |
We can also define two-dimensional tables such as :
distance[x:tuple(city,city)] : integer := 0 cost[x:tuple(1 .. 10, 1 .. 10)] : integer := 0 |
The proper way to use such a table is distance[list(denver,miami)] but CLAIRE also supports distance[denver,miami]. CLAIRE also supports a more straightforward declaration such as :
cost[x:(1 .. 10), y:(1 .. 10)] : integer := 0 |
Last, tables can be defined in an unamed fashion through the method make_table, such unamed can be collected by the GC :
let square := make_table((1 .. 10), integer, 0) in (for n in (1 .. 10)) square[n] := n * n, ...) |
Tables | categories Tables, Rules and Hypothetical Reasoning Rules |
Hypothetical Reasoning |
A rule in CLAIRE is made by associating an event condition to an expression. The rule is attached to a set of free variables of given types: each time that an event that matches the condition becomes occurs for a given binding of the variables (i.e., association of one value to each variable), the expression will be evaluated with this binding. The interest of rules is to attach an expression not to a functional call (as with methods) but to an event, with a binding that is more flexible (many rules can be combined for one event) and more incremental.
Definition : A rule is an object that binds a condition to an action, called its conclusion. Each time the condition becomes true for a set of objects because of a new event, the conclusion is executed. The condition is expressed as a logic formula on one or more free variables that represent objects to which the rule applies. The conclusion is a CLAIRE expression that uses the same free variables. An event is an update on these objects, either the change of a slot or a table value, or the instantiation of a class. A rule condition is checked if and only if an event has occurred. |
A novelty in CLAIRE 3.0 is the introduction of event logic. There are two events that can be matched precisely: the update of a slot or a table, and the instantiation of a class. CLAIRE 3.2 use expressions called event pattern to specify which kind of events the rule is associated with. For instance, the expression x.r := y is an event expression that says both that x.r = y and that the last event is actually the update of x.r from a previous value. More precisely, here are the events that are supported :
Note that an update of the type x.r :delete y (resp. a[x] :delete y), where r is a slot of x (resp. a is a table), will never be considered as an event if r is multi-valued. However, one can always replace this declaration by x.r := delete(x.r, y) which is an event, but which costs a memory allocation for the creation of the new x.r.
In addition, a new event pattern was introduced in CLAIRE 3.0 to capture the transition from an old to a new value. This is achieved with the expression x.r := (z <- y) which says that the last event is the update of x.r from z to y. For instance, here is the event expression that states that x.salary crossed the 100000 limit :
x.salary := (y <- z) & y < 100000 & z >= 100000 |
In CLAIRE 3.2 we introduce the notion of a "pure" event. If a property p has no restrictions, then p(x,y) represents a virtual call to p with parameters x and y. This event may be used in a rule in a way similar to x.p := y, with the difference that it does not correspond to an update. Virtual events are very generic since one of the parameter may be arbitrarily complex (a list, a set, a tuple ...). The event filter associated to a virtual event is simply the expression "p(x,y)". To create such an event, one simply calls p(x,y), once a rule using such an event has been defined. As a matter of fact, the definition of a rule using p(x,y) as an event pattern will provoke the creation of a generic method p that creates the event.
Virtual event may be used for many purposes. The creation of a virtual event requires no time nor memory; thus, it is a convenient technique to capture state transition in your object system. For instance, we can create an event signaling the instantiation of a class as follows :
instantiation :: property(domain = myClass, range = string)
[close(x:MyClass) : MyClass -> instantiation(x,date!(1)), x]
controlRule() :: rule(instantiation(x,s) => printf("--- create ~S at ~A |
To define a rule, we must indeed define :
Here is a classical transitive closure example :
r1() :: rule(x.friends :add y => for z in y.friend x.friends :add z) |
Rules are named (for easier debugging) and can use any CLAIRE expression as a conclusion, using the event parameters as variables. Rule triggering can be traced using trace(if_write). Notice that a rule definition in CLAIRE 3.2 has no parameters; rules with parameters require the presence of the ClaireRules library, which is no longer available.
For instance, let us define the following rule to fill the table fib with the Fibonacci sequence :
r3() :: rule(y := fib[x] & x % (0 .. 100) => when z := get(fib,x - 1) in fib[x + 1] := y + z) (fib[0] := 1, fib[1] := 1) |
Rules | categories Tables, Rules and Hypothetical Reasoning Hypothetical Reasoning |
I/O, Modules and System Interface Communication ports |
In addition to rules, CLAIRE also provides the ability to do some hypothetical reasoning. It is indeed possible to make hypotheses on part of the knowledge (the database of relations) of CLAIRE, and to change them whenever we come to a dead-end. This possibility to store successive versions of the database and to come back to a previous one is called the world mechanism (each version is called a world). The slots or tables x on which hypothetical reasoning will be done need to be specified with the declaration store(x). For instance :
store(age,friends,fib) <=> store(age), store(friends), store(fib) |
Each time we ask CLAIRE to create a new world, CLAIRE saves the status of tables and slots declared with the store command. Worlds are represented with numbers, and creating a new world is done with choice(). Returning to the previous world is done with backtrack(). Returning to a previous world n is done with backtrack(n). Worlds are organized into a stack (sorry, you cannot explore two worlds at the same time) so that save/restore operations are very fast. The current world that is being used can be found with world?(), which returns an integer.
Definition : A world is a virtual copy of the defeasible part of the object database. The object database (set of slots, tables and global variables) is divided into the defeasible part and the stable part using the store declaration. Defeasible means that updates performed to a defeasible relation or variable can be undone later; r is defeasible if store(r) has been declared. Creating a world (choice) means storing the current status of the defeasible database (a delta-storage using the previous world as a reference). Returning to the previous world (backtrack) is just restoring the defeasible database to its previously stored state. |
In addition, you may accept the hypothetical changes that you made within a world while removing the world and keeping the changes. This is done with the commit methods. commit() decreases the world counter by one, while keeping the updates that were made in the current world. It can be seen as a collapse of the current world and the previous world. commit(n) repeats commit() until the current world is n. Notice that this "collapse" will simply make the updates that were made in the current world (n) look like they were made in the previous world (n - 1); thus, these updates are still defeasible.
Defeasible updates are fairly optimized in CLAIRE, with an emphasis on minimal book-keeping to ensure better performance. Roughly speaking, CLAIRE stores a pair of pointers for each defeasible update in the world stack. There are (rare) cases where it may be interesting to record more information to avoid overloading the trailing stack. For instance, trailing information is added to the stack for each update even if the current world has not changed. This strategy is actually faster than using a more sophisticated book-keeping, but may yield a world stack overflow.
For instance, here is a simple program that solves the n queens problem (the problem is the following: how many queens can one place on a chessboard so that none are in situation of chess, given that a queen can move vertically, horizontally and diagonally in both ways ?) :
column[n:(1 .. 8)] : (1 .. 8) := unknown possible[x:(1 .. 8), y:(1 .. 8)] : boolean := true store(column, possible) r1() :: rule(column[x] := z => for y in ((1 .. 8) but x) possible[y,z] := false) r2() :: rule(column[x] := z => let d := x + z in for y in (max(1,d - 8) .. min(d - 1, 8)) possible[y,d - y] := false ) r3() :: rule(column[x] := z => let d := z - x in for y in (max(1,1 - d) .. min(8,8 - d)) possible[y,y + d] := false) queens(n:(0 .. 8)) : boolean -> (if (n = 0) true else exists(p in (1 .. 8) | (possible[n,p] & branch((column[n] := p, queens(n - 1)))))) (queens(8)) |
In this program queens(n) returns true if it is possible to place n queens. Obviously there can be at most one queen per line, so the purpose is to find a column for each queen in each line : this is represented by the column table. So, we have eight levels of decision in this problem (finding a line for each of the eight queens). The search tree (these imbricated choices) is represented by the stack of the recursive calls to the method queens. At each level of the tree, each time a decision is made (an affectation to the table column), a new world is created, so that we can backtrack (go back to previous decision level) if this hypothesis (this branch of the tree) leads to a failure.
Note that the table possible, which tells us whether the nth queen can be set on the pth line, is filled by means of rules triggered by column (declared event) and that both possible and column are declared store so that the decisions taken in worlds that have been left are undone (this avoids to keep track of decisions taken under hypotheses that have been dismissed since).
Updates on lists can also be "stored" on worlds so that they become defeasible. Instead of using the nth= method, one can use the method store(l,x,v,b) that places the value v in l[x] and stores the update if b is true. In this case, a return to a previous world will restore the previous value of l[x]. If the boolean value is always true, the shorter form store(l,x,y) may be used. Here is a typical use of store :
store(l,n,y,l[n] != y) |
This is often necessary for tables with range list or set. For instance, consider the following :
A[i:(1 .. 10)] : tuple(integer,integer,integer) := list<integer>(0,0,0) (let l := A[x] in (l[1] := 3, l[3] := 3)) |
even if store(A) is declared, the manipulation on l will not be recorded by the world mechanism. You would need to write :
A[x] := list(3, A[x][2], 3) |
Using store, you can use the original (and more space-efficient) pattern and write :
(let l := A[x] in (store(l,1,3), store(l,3,3))) |
There is another problem with the previous definition. The expression given as a default in a table definition is evaluated only once and the value is stored. Thus the same list<integer>(0,0,0) will be used for all A[x]. In this case, which is a default value that will support side-effects, it is better to introduce an explicit initialization of the table :
(for i in (1 .. 10) A[i] := list<integer>(0,0,0)) |
There are two operations that are supported in a defeasible manner: direct replacement of the ithelement of l with y (using store(l,i,y)) and adding a new element at the end of the list (using store(l,y)). All other operations, such as nth+ or nth- are not defeasible. The addition of a new element is interesting because it either returns a new list or perform a defeasible side-effect. Therefore, one must also make sure that the assignment of the value of store(l,x) is also made in a defeasible manner (e.g., placing the value in a defeasible global variable). To perform an operation like nth+ or delete on a list in a defeasible manner, one usually needs to use an explicit copy (to protect the original list) and store the result using a defeasible update (cf. the second update in the next example).
It is also important to notice that the management of defeasible updates is done at the relation level and not the object level. Suppose that we have the following :
C1 <: object(a:list<any>, b:integer) C2 <: thing(c:C1) store(c,a) P :: C1() P.c := C2(a = list<any>(1,2,3) , b = 0) // defeasible but the C2 object remains P.c.a := delete(copy(P.c.a), 2) // this is defeasible P.c.b := 2 // not defeasible |
The first two updates are defeasible but the third is not, because store(b) has not been declared. It is also possible to make a defeasible update on a regular property using put_store. It is worth noticing that hypothetical reasoning.
Tables, Rules and Hypothetical Reasoning Hypothetical Reasoning |
categories I/O, Modules and System Interface Communication ports |
Printing |
In XL CLAIRE, the entire port interface has been rewritten such port is now the root class for an extensible hierarchy of communication interface (In CLAIRE 3, ports are based on a C++ import). We define two sorts of port :
Definition : A device is a communication port that is connected to a physical port like a file or a socket that can be handled through a chain of filter |
Definition : A filter is a communication port that may modify, buffer or look at a data read or written from a device. |
Given this sorts, we define the descriptor device as a wrapper for UNIX descriptor which handles read, write (read_port and write_port interface) and close (close_port interface) operations in a unified way for each derived class (disk_file, socket, pipe). At startup, 3 global variables named stdin, stdout and stderr are created to hold the standard input, output, and error devices respectively (UNIX descriptors 0,1,2 on most system).
Languages often provide these standard ports in a buffered way, that is system calls read(2) or write(2) are made by chunks. So XL CLAIRE comes with two kind of filter, the buffer (as created by buffer!) that perform read (or write) once for each read (or written) chunk of a given size and the line_buffer (as created by line_buffer!) that perform write calls once for each written line. Depending on how the program was launched, the standard output may be a terminal or something else (e.g. pipe). In the later case we'll always provide stdout as a buffer but when it is found that the output is a terminal device, which is often shared by multiple processes, we'll provide stdout as a line_buffer. On the other hand the standard error port is always provided unbuffered, such that in case of crash we avoid data miss that could be hold by a buffer.
To avoid problems of synchronization between reading and writing, it is sometimes useful to ensure that the buffer of a given port is empty. This is done by the command flush(p:port). flush(p) will perform all printing instructions for the port p that are waiting in the associated buffer (flush_port interface).
A (buffered) file is opened with fopen(s:string,m:string) where s is the file path and m the opening mode ("r": read, "w": write, "a": append). For instance :
inefficient_show_size(filepath:string) : void ->
let f := fopen(filepath, "r"),
content := fread(f)
in printf("File ~A has ~S bytes |
An other provided interface is the ability to make a port from a string and vice versa. In XL CLAIRE we call that blob (based on device), the internal data representing the string is a chunk of memory allocated dynamically outside CLAIRE memory (CLAIRE 3 port! interface is supported for compatibility). Blob can be made in various ways with blob! including blob!(s:string) that would initialize the internal data with the string s :
let b := blob!("toto") in assert(fread(b) = "toto") let b := blob!() in (fwrite("toto",b), assert(fread(b) = "toto")) |
Communication ports | categories I/O, Modules and System Interface Printing |
WCL syntax |
There are several ways of printing in CLAIRE. Any entity may be printed with the function print. When print is called for an object that does not inherit from thing (an object without a name), it calls the method self_print of which you can define new restrictions whenever you define new classes. If self_print was called on an object x owned by a class toto for which no applicable restriction could be found, it would print <toto>.
In the case of bags (sets or lists), strings, symbols or characters, the standard method is princ. It formats its argument in a somewhat nicer way than print. For example :
print("john") prints "john" princ("john") prints john |
Finally, there also exists a printf macro as in C. Its first argument is a string with possible occurrences of the control patterns ~S, ~I and ~A. The macro requires as many arguments as there are "tilde patterns" in the string, and pairs in order of appearance arguments together with tildes. These control patterns do not refer to the type of the corresponding argument but to the way you want it to be printed. The macro will call print for each argument associated with a ~S form, princ for each associated with a ~A form and will print the result of the evaluation of the argument for each ~I form. A mnemonic is A for alphanumeric, S for standard and I for instruction. Hence the command :
printf("~S is ~A and here is what we know |
will be expanded into :
(print(john), princ(" is "), princ(23),
princ(" and here is what we know |
Output may also be directed to a file or another device instead of the screen, using a port. A port is an object bound to a physical device, a memory buffer or a file. The method use_as_output is meant to select the port on which the output will be written. Following is an example :
let p := fopen("agenda-2006", "w") in (use_as_output(p), write(agenda), fclose(p)) |
[XL] In XL CLAIRE printf construction can take a port argument and would perform a local output rediction to the supplied port :
printf(my_port, "~S is ~A and here is what we know |
will be expanded into :
let old := use_as_output(my_port)
in (print(john), princ(" is "), princ(23),
princ(" and here is what we know |
CLAIRE also offers a simple method to redirect the output towards a string port. Two methods are needed to do this: print_in_string and end_of_string. print_in_string() starts redirecting all printing statements towards the string being built. end_of_string() returns the string formed by all the printing done between these two instructions. You can only use print_in_string with one output string at a time; more complex uses require the creation of multiple string ports.
All trace statements will be directed to this port. A trace statement is either obtained implicitly through tracing a method or a rule, or explicitly with the trace statement. the statement trace(n, <string>, <args> ...) is equivalent to printf(<string>, <args> ..) with two differences: the string is printed only if the verbosity level verbose() is higher than n and the output port is ctrace(). The following lines are equivalent :
trace(0, "assigning ~S with ~S", x, y) //[0] assigning ~S with ~S // x, y (if (verbose() >= 0) printf(ctrace(), "assigning ~S with ~S", x, y)) |
[XL] In XL CLAIRE however, trace instructions are bound to a module such one can specify a per module verbose policy : a module m has a slot m.verbose that can take the values :
Printing | categories I/O, Modules and System Interface WCL syntax |
Reading |
XL CLAIRE comes with a new printing facility called WCL syntax standing for Web CLaire syntax due to its design originally meant for web oriented applications with generation of dynamic content. WCL syntax draws its inspiration from HTML and the ability to embed CLAIRE code in such language. It comes as a printing alternative to printf and also perform inline substitution. For instance, here is a simple WCL fragment and its printf equivalent :
?>Hello world<? <=> printf("Hello world") |
A WCL fragment is introduced with the keyword ?> which is the beginning of a static string terminated by the corresponding keyword <? substituted at read time by a call to princ. As a convenience a WCL fragment may not be delimited with a coma as shown in the following equivalent forms :
?>toto<? princ("titi") ?>tata<? ?>toto<? , princ("titi"), ?>tata<? ?>toto<? princ("titi"), ?>tata<? ?>toto<? , princ("titi") ?>tata<? princ("tototititata") |
As in a printf construction a WCL fragment can invoke a method take an argument, this is achieved by postfixing the selector of the call to the <? keyword as in :
?>toto<?princ "titi" <=> princ("tototiti") |
As a notation shortcut, three special forms are allowed for the postfixed selector :
?>toto<?= <exp> <=> princ("toto"), echo(<exp>) ?>toto<?== <exp> <=> princ("toto"), self_html(<exp>) ?>toto<?oid <exp> <=> princ("toto"), princ(Core/Oid(<exp>)) |
Where echo and self_html and both extensible. The last form (oid) may be used to produce a unique printed value for the given expression <exp>. By default echo behave like princ for primitive objects (integers, floats, chars and strings) and self_print for other objects. self_html behave like echo but also perform conversion for special HTML entities :
As a summary, a WCL fragment as the following syntax :
<wcl fragment> = < ?> <text> <? < <= | == | oid | property> <exp> >opt > |
As a last sample here is a code that would generate an HTML table with 10 rows and ten columns where each cell contains its coordinates in the table :
?><table><? for y in (1 .. 10) ( ?><tr><? for x in (1 .. 10) ( ?><td><?= x ?>,<?= y)) ?></table><? |
WCL syntax | categories I/O, Modules and System Interface Reading |
Symbols |
Files can be read one expression at a time : read(p:port) reads the next CLAIRE expression on the port p or, in a single step, load(s:string) reads the file associated to the string s and evaluates it. It returns true when no problem occurred while loading the file and false otherwise. A variant of this method is the method sload(s:string) which does the same thing but prints the expression read and the result of their evaluation. Another variant is the method oload(s:string) which does the same thing but substitute an optimized form to each method's body. This may hinder the inspection of the code at the toplevel, but it will increase the efficiency of the interpreter.
Files may contain comments. A comment is anything that follows a // until the end of the line. When reading, the CLAIRE reader will ignore comments (they will not be read and hence not evaluated). For instance :
x :+ 1, // increments x by 1 |
To insure compatibility with earlier versions, CLAIRE also recognizes lines that begin with ';' as comments. Conversely, CLAIRE also support the C syntax for block comments: anything between /* and */ will be taken as a comment. Comments in CLAIRE may become active comments that behave like trace statements if they begin with [<level>] (see appendix B). The global variable NeedComment may be turned to true (it is false by default) to tell the reader to place any comment found before the definition of a class or a method in the comment slot of the associated CLAIRE object.
The second type of special instructions are immediate conditionals. An immediate conditional is defined with the same syntax as a regular conditional but with a #if instead of an if :
#if <test> <expression> < else <expression> >opt |
When the reader finds such an expression, it evaluates the test. If the value is true, then the reader behaves as if it had read the first expression, otherwise it behaves as if it had read the second expression (or nothing if there is no else). This is useful for implementing variants (such as debugging versions). For instance :
#if debug printf("the value of x is ~S", x) |
Note that the expression can be a block (within parentheses) which is necessary to place a definition (like a rule definition) inside a #if. Last, there exists another pre-processing directive for loading a file within a file: #include(s) loads the file as if it was included in the file in which the #include is read.
There are a few differences between CLAIRE and C++ or Java parsing that need to be underlined :
foo (1,2,3) // this is not correct, one must write foo(1,2,3) |
x + (y * z) // instead of x+y*z which is taken as (one) variable name |
Reading | categories I/O, Modules and System Interface Symbols |
Modules |
Identifiers are represented in CLAIRE with entities called symbols. Each identifier is represented by a symbol. A symbol is defined by a namespace (where the identifier belongs), a name (the sequence of character from the unqualified symbol) and a status. The status of a symbol is either private or export (the default status). When the status of an identifier is private, the reader will not recognize the qualified form from a module that is not a sub-module of that of the identifier. Therefore, the only way to read the identifier is inside its own namespace. When the status of the identifier is export, the qualified form gives access to the designated object, if the sharing declarations of namespaces have been properly set.
Each symbol may be bound to the object that it designates. The object becomes the value of the symbol. The name of the object must be the symbol itself. In addition, the symbol collects another piece of useful information: the module where its value (the named object) is defined. If the symbol is defined locally (through a private or export definition), this definition module is the same as the owner of the symbol itself. If the symbol is shared (if it was defined as an identifier of an above module), this module is a subpart of the owner of the symbol.
CLAIRE now supports a simple syntax for creating symbols directly, in addition to the various methods provided in the API. Symbols can be entered in the same way that they are printed, by indicating the module (namespace) to which the symbol belongs and the associated string, separated by a '/' :
<claire symbol> = <module>/<string> |
Symbols | categories I/O, Modules and System Interface Modules |
Global Variables and Constants |
Organizing software into modules is a key aspect of software engineering : modules separate different features as well as different levels of abstraction for a given task. To avoid messy designs and to encourage modular programming, programs can be structured into modules, which all have their own identifiers and may hide them to other modules. A module is thus a namespace that can be visible or hidden for other modules. CLAIRE supports multiple namespaces, organized into a hierarchy similar to the unix file system. The root of the hierarchy is the module CLAIRE, which is implicit. A module is defined as a usual CLAIRE object with two important slots: part_of which contains the name of the father module, and a slot uses which gives the list of all modules that can be used inside the new module. For instance :
interface :: module(part_of = library, uses = list(claire)) |
defines interface as a new sub-module to the library module that uses the module claire (which implies using all the modules). All module names belong to the claire namespace (they are shared) for the sake of simplicity.
Definition : A module is a CLAIRE object that represents a namespace. A namespace is a set of identifiers : each identifier (a symbol representing the name of an object) belongs to one unique namespace, but is visible in many namespaces. Namespaces allow the use of the same name for two different objects in two different modules. Modules are organized into a visibility hierarchy so that each symbol defined in a module m is visible in modules that are children of m. |
Identifiers always belong to the namespace in which they are created (claire by default). The instruction module!() returns the module currently opened. To change to a new module, one may use begin(m:module) and end(m:module). The instruction begin(m) makes m the current module. Each newly created identifier (symbol) will belong to the module m, until end(m) resumes to the original module. For instance, we may define :
begin(interface) window <: object(...) end(interface) |
This creates the identifier interface/window. Each identifier needs to be preceded by its module, unless it belongs to the current module or one of its descendent, or unless it is private (cf. visibility rules). We call the short form "window" the unqualified identifier and the long one "interface/window" the qualified identifier.
The visibility rules among name spaces are as follows :
Any identifier can be made private when it is defined by prefixing it with private/. For instance, we could have written :
begin(interface) window <: object(...) private/temporary <: window(...) end(interface) |
The declaration private/temporary makes "temporary" a private identifier that cannot be accessed outside the module interface (or one of its descendents). The declaration claire/window makes window an identifier from the claire module (thus it is visible everywhere), which is allowed since claire belongs to the list of usable modules for interface.
In practice, there is almost always a set of files that we want to associate with a module, which means that we want to load into the module's namespace. CLAIRE allows an explicit representation of this through the slots made_of and source. made_of(m) is the list of files (described as strings) that we want to associate with the module and source(m) is the common directory (also described as a string). The benefits are the load/sload methods that provide automatic loading of the module's files into the right namespace and the module compiling features. CLAIRE expects the set of file names to be different from module names, otherwise a confusion may occur at compile time.
A last important slot of a module is uses, a list of other modules that the new module is allowed to use. This list has two purposes, that only exist at compile time. The first one is to restrict the importation of symbols from other modules. A module is considered a legal import if it included itself in this uses list, or, recursively, if its father module is legal or if the module is legal for one of the modules in this list. An attempt to read a symbol m/s from a module that is not a legal import will provoke a compiler error. Second, this list is used by the compiler to find out which binaries should be included in the link script that is produced by the compiler.
Modules | categories I/O, Modules and System Interface Global Variables and Constants |
Command line handling |
CLAIRE offers the possibility to define global variables; they are named objects from the following class :
global_variable <: thing(range:type, value:any) |
For instance, one can create the following :
tata :: global_variable(range = integer, value = 12) |
However, there is a shorthand notation :
tata:integer :: 12 |
Notice that, contrary to languages such as C++, you always must provide an initialization value when you define a global variable (it may be the unknown value). Variables can be used anywhere, following the visibility rules of their identifiers. Their value can be changed directly with the same syntax as local variables.
tata := 12, tata :+ 1, tata :- 10 |
The value that is assigned to a global variable must always belong to its range, with the exception of the unknown value, which is allowed. If a variable is re-defined, the new value replaces the old one, with the exception still of unknown, which does not replace the previous value. This is useful for defining flags, which are global_variables that are set from the outside (e.g., by the compiler). One could write, for instance,
talk:boolean :: unknown (#if talk printf(...)) |
The value of talk may be set to false before running the program to avoid loading the printf statements. When the value does not change, it is simpler to just assign a value to an identifier. For instance :
toto :: 13 |
binds the value 13 to the identifier toto. This is useful to define aliases, especially when we use imported objects from other modules. For instance, if we use Logic/Algebra/exp, we may want to define a simpler alias with :
exp :: Logic/Algebra/exp |
The value assigned to a global variable may be made part of a world definition and thus restored by backtracking using choice/backtrack. It is simply required to declare the variable as a defeasible (i.e., backtrack -able) variable using the store declaration as for a relation :
store(tata) (tata := 1, choice(), tata := 2, backtrack(), assert(tata = 1)) |
Global Variables and Constants | categories I/O, Modules and System Interface Command line handling |
Serialization |
In XL CLAIRE, additionally to the main @ list redefinition, we have a command line processor that make a module able to define a particular set of option responder and associated command line documentation through 2 handlers. For instance the Reader implement the option -f used to load a file from the command line. The Reader implements :
[option_usage(opt:{"-f", "-ef"}) : tuple(string, string, string) -> tuple("Load file", "{-f | -ef} <file:path> ...", "Load the CLAIRE file(s) <file>. The given path may contain an extension " /+ "assuming .cl by default. When the <-f> option is used, the file is " /+ "assumed to contain CLAIRE definitions (variables, class, methods) whereas " /+ " <-ef> attempts to read a file made of CLAIRE expression.")] [option_respond(opt:{"-f", "-ef"}, l:list) : void -> if not(l) invalid_option_argument(), if (not(isfile?(l[1])) & not(isfile?(l[1] /+ ".cl"))) error("~A cannot be opened", l[1]), while (l & (isfile?(l[1]) | isfile?(l[1] /+ ".cl"))) let path := l[1] in (l << 1, case opt ({"-f"} load(path), {"-ef"} eload(path)))] |
It comes with this implementation that when an executable is linked with a set of module the whole option set defined by these module is implicitly supported by the executable. For instance, when an executable is linked with the Reader module is support the -f option.
Additionally to this two handlers, a module can define a single restriction of option_parsed that will be called once the full command line has been parsed. With such an handler, a module can perform a job that depends on multiple independent option responder (that would have initialize global flags). For instance :
*myopt*:boolean := false [option_respond(opt:{"myopt"}, l:list) : void -> *myopt* := true] [option_parsed() : void -> if *myopt* ... else ...] |
Restrictions of option_usage are used by the command line help which is handled with the particular option -h in the Core module :
{-h | -help} +[<m> | <option> | <index>] |
Which would provide the help for all options defined by a module <m> or for a particular option <option> or for the option having the index <index> in the option index as generated by option -h when used without arguments.
Command line handling | categories I/O, Modules and System Interface Serialization |
Platform Miscellaneous |
XL CLAIRE distribution comes with the module Serialize that provide generic mechanism (using CLAIRE reflection) for writing/reading CLAIRE data structures to/from a communication port. The ability to convert a data structure to a binary stream can be used for Inter Process Communication (IPC) to exchange objects over a network or to store on the hard disk a set of object that belong to a user session. For instance :
b :: blob!() (serialize(b, 12), serialize(b, list(12))) (assert(unserialize(b) = 12) (assert(unserialize(b) = list(12)) |
The example above illustrates the simple case when the serialized data represent a primitive object (an integer and a list with an integer), sometimes we need to serialize a tree of objects having relations one to each other in a single step and we would like the serialize process to be recursive :
A <: ephemeral_object() B <: ephemeral_object(a:A) b :: blob!() (serialize(b, false, B(a = A()))) (assert(known?(a, unserialize(b)))) |
We use a serialize restriction that take a top? flag that tell whether the serialization should be simple (when top? is true) or recursive (when top? is false). By default serialize in non-recursive :
serialize(b, x) <=> serialize(b, false, x) |
When used recursively, serialize will traverse the object relation such to serialize related objects. As such it would serialize the whole database but it doesn't because named objects (class, property...) are not serialized. When a named object is serialized we only save its symbol. It comes the important rule that the process that unserialize a data stream should have in its database all named objects referenced by the data stream (e.g. classes have to be defined in both serializer and unserializer process). The recursion rules are as follow :
Notice that an inverse relation that should exists between a unserialized object and the object database is built during unserialization. Also notice that when an object is unserialized the close constructor is called so that if you serialize an exception, the unserialize process will throw the error (which can be used as an event). Here is a sample method that applies a property with a list of argument (like apply) but does it in a child process. The result is transmitted between the child and the parent through a pipe using serialization facility :
[fork_apply(p:property, l:list) : any -> let s := socketpair() // create a pipe in (if forker?() // parent process : (fclose(s[2]), let x := (try unserialize(s[1]) // read the result on the pipe catch any exception!()), // and catch the error if any st := waitpid(forked()) // wait for child termination in (if (st[1] != WEXITED & st[3] != 0) error("fork_apply(~S, ~S) failed with ~S", p, l, st), fclose(s[1]), case x (exception close(x)), x)) else // child process : (fclose(s[1]), try serialize(s[2], apply(p, l)) // actually apply catch any serialize(s[2], exception!()), exit(0), 0))] |
When using serialize over a network one can use the same serialize_context object for the life time of the connection. The context object associates named objects with IDs and the reuse of the same context for multiple serialize operation will be more efficient than creating a new context for each serialized data, for instance :
let c := client!("host.domain.com", 10000), ctx := serialize_context!() in (while (...) (serialize(c, ctx, ...), ...)) |
I/O, Modules and System Interface Serialization |
categories Platform Miscellaneous |
Environment variables |
Miscellaneous | categories Platform Environment variables |
Process handling |
Additionally to the CLAIRE getenv method, XL CLAIRE provides some tools that make possible to change an environment variable (setenv) or iterate over the whole environment of the process (environ/maxenv) as in :
(for i in (1 .. maxenv())
let (v, val) := explode_wildcard(environ(i), "*=*")
in (assert(getenv(v) = val),
printf("~A = ~S |
Note that setenv and
Environment variables | categories Platform Process handling |
File system |
Process handling | categories Platform File system |
Signal Handling |
XL CLAIRE comes with a set of method used for the file system management. These methods are used to :
File system | categories Platform Signal Handling |
Using |
Starting with XL CLAIRE we can install an Interrupt Service Routine written in CLAIRE for a particular signal. Signals, in XL CLAIRE, are not true asynchronous event since they are always caught by the Kernel as a first step and redistributed to the meta code from an appropriate execution point (necessary to preserve memory integrity) : the meta handling is always performed in the program execution flow (i.e. not in the stack of the signal handler) so that the code of the handler may be arbitrary complex.
The signal interface is very similar to C interface, we use signal(sig:signal_handler, p:property) to install a signal handler :
user_interrupt() : void -> error("User interrupt") (signal(SIGUSR1, user_interrupt)) (raise(SIGUSR1)) // would throw the error above |
Notice that in XL CLAIRE after the delivery of a signal the signal is always re-installed. To uninstall a handler one should use the special property SIG_DFL which correspond to a default handling :
(signal(SIGUSR1, SIG_DFL)) |
We could also ignore a signal using the special property SIG_IGN :
(signal(SIGUSR1, SIG_IGN)) |
Last, XL CLAIRE comes with another special property SIG_EXT that is set by default to a signal already installed at the time of CLAIRE initialization (as may be done by a library linked with CLAIRE).
However, SIGINT is handled in a particular way (SIGINT is on most UNIX raised with the keyboard when the user types ^C - Control + C). The SIGINT handler, when set to SIG_DFL (default), throws a user interrupt error such to abort the current computation and come back to the interpreter. If, additionally, the debugger is active, SIGINT would behave as a breakpoint.
Using | categories Using XL Claire Debugging |
XL claire comes with a whole new debugger (since v3.3.39) that is far more robust and powerful than its previous implementation. The new strategy is to produce an instrumented meta code that calls the debugger handlers, as such interpreted and compiled code are homogeneously handled with the debugger. The debugger interface is handled by the toplevel and is highly inspired by the popular GNU debugger (gdb). The debugger is activated by the -debug command line option which would :
When an unhandled exception is caught by the toplevel a special debug loop is entered providing debugging interface to the user additionaly to the standard toplevel features, namely :
Additionally the user may control a program execution step by step (stepper) either from an initial command or by interrupting the execution of a command. A program may be interrupted using ^C which would however stop the interpreter under Win32.
XL Claire comes with a debugger that let the user control and step during a program execution and locate the place where an exception was thrown. The debugger interface is inspired by gdb (the popular GNU debugger) and provide backtrace, source file listing, frame inspection including local variables.
In order to be debugged some code have to be instrumented which is done by specifying the -debug option on the command line. Since the instrumentation is performed at the meta level the debugger would handle interpreted and compiled code homogeneously.
categories driving optimizations |
categories | File system | Core global variable |
*fs* is a global variable that is set to a string that represent the file separator (system dependent). Usually "/" on UNIX and "< "> on windows.
categories | File system | Core global variable |
*ps* is a global variable that is set to a string that represent the path separator (system dependent). Usually ":" on UNIX and ";" on windows.
categories | Integers and Floats | Language constant |
This global variable is the greatest supported integer in CLAIRE. This is a 30 bit value (integers in CLAIRE are coded on 30 bits). This value can be used in code that require a big integer value as in the following sample :
my_min(l:list[integer]) : integer -> let minima := MAX_INTEGER in (for i in l (if (i < minima) minima := i), minima) |
categories | Lists, Sets and Tuples | Core constant |
nil is the empty list instance.
categories | Communication ports | [XL] Core constant |
compatibility with <ycs>, one should use blob! instead.
categories | Communication ports | [XL] Core global variable |
the standard error output port
categories | Communication ports | [XL] Core global variable |
the standard input port
categories | Communication ports | [XL] Core global variable |
the standard output port
categories | Classes | normal dispatch | Kernel interface |
close interface is used to define constructors. Each time a new object x is created close(x) is call. The only case where the close interface isn't called is when the object is created by the special mClaire/new! allocator. close should always return a valid object (its argument most of time).
ACCOUNT_ID:integer := 0 account <: ephemeral_object(account_id:integer) close(self:account) : account -> (self.account_id := ACCOUNT_ID, ACCOUNT_ID :+ 1, self) |
categories | Communication ports | fast dispatch | [XL] Core interface |
close_port(self:my_port) : void -> ... |
categories | Communication ports | fast dispatch | [XL] Core interface |
interface to check the end-of-file condition on a given port :
eof_port?(self:my_port) : boolean -> ... |
categories | Communication ports | fast dispatch | [XL] Core interface |
interface to flush pending buffered data
flush_port(self:my_port) : void -> ... |
categories | Free-able objects | normal dispatch | [XL] Kernel interface |
In a similar way to prefree! interface free! is called by the GC before a freeable_object is actually freed. It is a good place, for instance, to free externally allocated memory. An object x given to the free! handler should be handled with a lot of care :
categories | Lists, Sets and Tuples | normal dispatch | Kernel interface |
nth can be redefined on user class domain meant to have indexed access. The reader converts the x[y] notation into a call to nth(x,y) that at evaluation will call the redefined restriction :
hash_table <: ephemeral_object(data:list[any]) nth(x:hash_table, x:any) : any -> x.data[hash_value(x) and \xFFF] |
categories | Lists, Sets and Tuples | normal dispatch | Kernel interface |
nth= can be redefined on user class domain meant to have indexed access. The reader converts the x[y] := z notation into a call to nth(x,y,z) that at evaluation will call the redefined restriction :
hash_table <: ephemeral_object(data:list[any]) nth=(x:hash_table, x:any, y:any) : void -> x.data[hash_value(x) and \xFFF] := y |
categories | Process handling | normal dispatch | [XL] Core interface |
fork, when called in CLAIRE applies all existing on_fork with the domain void. the on_fork callback is applied just before the fork, that is the parent process only. For instance :
on_fork() : void -> printf("attemp to fork... |
categories | Process handling | normal dispatch | [XL] Core interface |
after a fork, CLAIRE applies all existing on_forked restrictions that have the domain boolean. the on_forked callback is applied with true from the child and false from the parent process. For instance :
on_forked(parent?:boolean) : void ->
printf("just forked (~I) |
categories | Command line handling | normal dispatch | [XL] Core interface |
A module can define a single option_parsed restriction that will be called by the command line option parser at startup as soon as the full command line is parsed. It will be used inside a module to perform an operation that rely on multiple options (for which option_respond as already been called):
option_parsed() : void -> ... |
categories | Command line handling | normal dispatch | [XL] Core interface |
A module can define multiple option_respond restrictions that will be called by the command line option parser at startup. option_respond will take two arguments:
option_respond(opt:{"-opt"}, l:list[string]) : void -> ... |
It is up to the handler to remove used arguments from the given list.
categories | Command line handling | normal dispatch | [XL] Core interface |
A module can define multiple option_usage restrictions that will be called by the command line option parser when the command line help is invoked. option_usage takes a single argument that is a constant set that contain names of multiple related options. The handler should return of tuple of 3 strings :
option_usage(opt:{"-opt"}) : tuple(string, string, string) -> tuple("short description", "-opt <f:file>", "long description on how to use option -opt we may use <f> to reference the argument f it can also span multiple line if needed") |
The usage should be written following a few guidelines such the command line help can produce a description with a nice appearance :
categories | Free-able objects | normal dispatch | [XL] Kernel interface |
The prefree! interface is called by the GC before a freeable_object is actually freed. It give a chance to terminate inter-object operations that may need a last synchronization step (like the flush of a filter). Since an object x given to the handler prefree! is about to be freed you should never create reference on x in the scope of the handler (i.e. don't add x to a list, don't set a global var to x).
categories | Communication ports | fast dispatch | [XL] Core interface |
interface to read a chunk of data :
read_port(self:my_port, buf:data*, len:integer) : integer -> ... |
the returned value should be the amount of bytes that were actually read
categories | Printing | normal dispatch | Kernel interface |
self_print interface is used to redefine the way an entity is printed by the standard print method. self_print can be redefined for each new created class, for instance :
account <: ephemeral_object(account_id:integer) self_print(self:account) : void -> printf("<account with id ~S>", self.account_id) |
categories | Communication ports | fast dispatch | [XL] Core interface |
interface to unget a chunk of data :
unget_port(self:my_port, buf:data*, len:integer) : void -> ... |
categories | Communication ports | fast dispatch | [XL] Core interface |
interface to write a chunk of data
write_port(self:my_port, buf:data*, len:integer) : integer -> ... |
the returned value should be the amount of bytes that were actualy written
categories | Communication ports | ephemeral | [XL] Core class |
blob | data : char* alloc_length : Integers and Floats read_index : Integers and Floats write_index : Integers and Floats |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
blob is a port interface for strings, data are stored in a chunk of memory allocated outside claire memory.
categories | Communication ports | ephemeral | [XL] Core class |
buffer | mClaire/buffer_length : Integers and Floats pending_r : blob pending_w : blob |
filter | dev : device target : Communication ports close_target? : boolean |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
a buffer filter performs a single read/write call on its target for each data raw of a given size (buffer_length). It comes as replacement of the C FILE* buffering capability.
categories | Communication ports | ephemeral | [XL] Core class |
byte_counter | written_bytes : Integers and Floats read_bytes : Integers and Floats |
filter | dev : device target : Communication ports close_target? : boolean |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
byte_counter is a pure filter that count the number of bytes transfered in both directions
categories | Communication ports | abstract | [XL] Core class |
char* | - |
import | - |
primitive | - |
read_port and write_port operate on a given buffer of a given size. these buffers are char* imported from C, this way we have an interface similar to read(2) and write(2)
categories | Communication ports | ephemeral | [XL] Core class |
descriptor | fd : Integers and Floats eof_reached? : boolean |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
UNIX file descriptor wrapping. we do not use C stream capabilities (FILE*) but the descriptor itself, which make the API relying on system calls read(2) and write(2), stream interface is provided by filters (buffer)
categories | Communication ports | ephemeral | [XL] Core class |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
physical port like a descriptor or a blob that may be handled through a chain of filter
categories | Communication ports | ephemeral | [XL] Core class |
disk_file | file_path : Strings locked? : boolean mode : Integers and Floats |
descriptor | fd : Integers and Floats eof_reached? : boolean |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
disk_file is the interface for file located on the hard drive
categories | Communication ports | ephemeral | [XL] Core class |
filter | dev : device target : Communication ports close_target? : boolean |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
port that may analyze and/or transform and/or collect read and/or written data like a buffer, a line counter or even a MIME decoder or any kind embedded protocols.
categories | Signal Handling | open | [XL] Core class |
itimer | timerno : Integers and Floats |
UNIX timer interface can handle 3 kind of timers:
categories | Communication ports | ephemeral | [XL] Core class |
line_buffer | pending : blob |
filter | dev : device target : Communication ports close_target? : boolean |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
line_buffer is a write filter that collects written data until a new line character. each line is flushed with a single write on the target port. It is mainly used for terminal output such to prevent line overlap when multiple process share the same terminal output or when a trace file is shared by multiple process.
categories | Communication ports | ephemeral | [XL] Core class |
line_counter | written_lines : Integers and Floats line_offset : Integers and Floats prev_line_offset : Integers and Floats read_lines : Integers and Floats |
filter | dev : device target : Communication ports close_target? : boolean |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
line_counter is a pure filter that count the number of lines transfered in both directions
categories | Communication ports | ephemeral | [XL] Core class |
listener | - |
socket | address : Strings tcpport : Integers and Floats |
descriptor | fd : Integers and Floats eof_reached? : boolean |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
the interface for listening sockets. such socket are used with accept to handle new connections.
categories | Communication ports | ephemeral | [XL] Core class |
pipe | - |
descriptor | fd : Integers and Floats eof_reached? : boolean |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
interface for UNIX pipes
categories | Process handling | open | [XL] Core class |
process_status | - |
A process status is returned by waitpid and tell how a child process has exited:
categories | Signal Handling | open | [XL] Core class |
signal_handler | signo : Integers and Floats handler : Selectors, Properties and Operations |
In claire signals are named objects. signal handling is system dependent and a signal_handler instance may not correspond to a supported signal in which case its value signo will be set to -1.
categories | Communication ports | ephemeral | [XL] Core class |
socket | address : Strings tcpport : Integers and Floats |
descriptor | fd : Integers and Floats eof_reached? : boolean |
device | filters : list[filter] |
Communication ports | firstc : Integers and Floats closed? : boolean |
freeable_object | mClaire/freeme? : boolean |
Objects and Entities | isa : Classes |
the interface for UNIX stream oriented sockets, sockets are two way communication ports connected to a remote client.
categories | Objects and Entities | normal dispatch | operation | Core method |
this is the negation of (x = y).
categories | Types | normal dispatch | operation | Kernel method |
(x % y) returns (x E y) for any entity x and any abstract set y. An abstract set is an object that represents a set, which is a type or a list.
categories | Integers and Floats | inline | operation | Kernel method |
returns the product of two integers
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns the product of two floats
categories | Integers and Floats | normal dispatch | operation | Core method |
returns the sum of two floats
categories | Integers and Floats | normal dispatch | operation | Core method |
returns the sum of two integers
categories | Communication ports | normal dispatch | operation | [XL] Core method |
translate the char* pointer self with an offset of n bytes
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns the opposite of x (i.e. -1.0 * x)
categories | Integers and Floats | inline | operation | Kernel method |
returns the opposite of x
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns the difference of two integers
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns the difference of two floats
categories | Types | normal dispatch | operation | Kernel method |
x .. y returns the interval interval (x .. y).
categories | Strings | normal dispatch | operation | Kernel method |
s1 / s2 returns the concatenation of two path components. it is equivalent two s1 /+ *fs* /+ s2 where *fs* is the file separator ('/' under UNIX and '<'>under win32)
"toto" / "titi" -> "toto/titi". |
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns the division of two floats
categories | Integers and Floats | inline | operation | Kernel method |
returns the division of two integers
categories | Strings | normal dispatch | operation | Kernel method |
s1 /+ s2 returns a new string that is the concatenation of the two strings.
"toto" /+ "titi" -> "tototiti". |
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
l1 /+ l2 returns a new list that is the concatenation of the two lists.
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
x /+ y returns a new list that is the concatenation of the two bag contents.
categories | Strings | normal dispatch | operation | [XL] Core method |
s1 /- s2 returns the concatenation of two path components where the last path component of s1 has been removed. For instance
"toto/tata" /- "titi" -> "toto/titi". |
categories | Integers and Floats | inline | operation | Kernel method |
returns true if x is lower than y
categories | Chars | normal dispatch | operation | Kernel method |
returns true if ASCII code of x is lower than ASCII code of y
categories | Strings | normal dispatch | operation | Kernel method |
returns true if x is lower than y according to the lexicographic order induced by the ASCII order on characters.
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns true if x is lower than y
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
(l << n) left-shifts the list l by n units, which means that the n first members of the list are removed. This is a method with a side-effect since the returned value is the original list, which has been modified.
categories | Integers and Floats | normal dispatch | operation | Kernel method |
(x << n) is the result of shifting the integer x seen as a bitvector to the left by n positions.
categories | Integers and Floats | inline | operation | Kernel method |
returns true if x is lower or equal to y
categories | Types | normal dispatch | operation | Kernel method |
The order on types is the inclusion: (x <= y) returns true if all members of type x are members of type y.
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns true if x is lower or equal to y
categories | Chars | normal dispatch | operation | Kernel method |
returns true if ASCII code of x is lower or equal to ASCII code of y
categories | Strings | normal dispatch | operation | Kernel method |
returns true if x is lower or equal to y according to the lexicographic order induced by the ASCII order on characters.
categories | Objects and Entities | normal dispatch | operation | Kernel method |
(x = y) returns true if x is equal to y and nil otherwise. Equality is defined as identity for all entities except strings, lists and sets. For lists, sets and strings, equality is defined recursively as follows: x and y are equal if they are of same size n and if x[i] is equal to y[i] for all i in (1 .. n).
categories | Types | normal dispatch | operation | Core method |
returns true if x and y denote the same type. For example =type?(boolean, {true, false}) returns true because defined(boolean) was declared after the two instances true and false were created, so the system knows that no other instances of boolean may ever be created in the future. This equality is stronger than set equality in the sense that the system answers true if it knows that the answer will hold everafter.
categories | Integers and Floats | inline | operation | Kernel method |
returns true if x is greater than y
categories | Integers and Floats | normal dispatch | operation | Kernel method |
returns true if x is greater than y
categories | Strings | normal dispatch | operation | Kernel method |
returns true if x is greater than y according to the lexicographic order induced by the ASCII order on characters.
categories | Chars | normal dispatch | operation | Kernel method |
returns true if ASCII code of x is greater than ASCII code of y
categories | Integers and Floats | inline | operation | Kernel method |
returns true if x is greater or equal to y
categories | Chars | normal dispatch | operation | Kernel method |
returns true if ASCII code of x is greater or equal to ASCII code of y
categories | Integers and Floats | inline | operation | Kernel method |
returns true if x is greater or equal to y
categories | Strings | normal dispatch | operation | Kernel method |
returns true if x is greater or equal to y according to the lexicographic order induced by the ASCII order on characters.
categories | Integers and Floats | normal dispatch | operation | Core method |
(x >> n) is the result of shifting the integer x seen as a bitvector to the right by n positions.
categories | Integers and Floats | normal dispatch | operation | Kernel method |
(x ^ y) returns the y exponent of x.
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
(s1 ^ s2) returns the intersection of the two sets s1 and s2 that is the set of entities that belong to both s1 and s2. Other internal restrictions of the property ^ exist, where ^ denotes the intersection (it is used for the type lattice).
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
(l ^ y) skips the y first members of the list l. If the integer y is bigger than the length of the list l, the result is the empty list, otherwise it is the sublist starting at the y + 1 position in l (up to the end).
categories | Integers and Floats | inline | operation | Kernel method |
(x ^ y) returns the y exponent of x. y must be a positive integer, otherwise an error is raised.
categories | Integers and Floats | inline | Kernel method |
^2(x) returns the 2 exponent of x
categories | Classes | normal dispatch | Kernel method |
declares a class as an abtract class (without instances)
categories | Integers and Floats | normal dispatch | Core method |
acos(self) computes the principal value of the arc cosine of self in the range [0, pi].
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
add(s,x) adds x to the list l. The returned value is the list obtained by appending (x) to l.
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
add(s,x) adds x to the set s. The returned value is the set s U {x}. This method may modify the set s but not necessarily.
categories | Classes | normal dispatch | operation | Kernel method |
add(self,x,y) is equivalent to self(x) :add y (This form is interesting when one wants to write such an expression for a variable self)
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
add*(l1,l2) returns the concatenated list l1 . l2, but it is destructive: it uses l1 as the data structure on which to perform the concatenation. Hence, the original l1 is no longer available after the method add* has been called
categories | Strings | normal dispatch | [XL] Kernel method |
alpha?(s) returns true if for all char c in s alpha?(c) is true.
categories | Chars | normal dispatch | [XL] Kernel method |
alpha?(c) returns true when the char c is in the range ('a' .. 'z', 'A' .. 'Z').
categories | Integers and Floats | normal dispatch | operation | Core method |
and(x,y) returns the bitwise intersection of two integers (seen as bitvectors).
categories | Calls and Slot Access | normal dispatch | Core method |
apply(p,l) is equivalent to a function call where the selector is p and the argument list is l. For instance the following expressions are equivalent :
apply(+, list(1,2)) -> 3 1 + 2 -> 3 call(+, 1, 2) -> 3 |
categories | Calls and Slot Access | normal dispatch | Core method |
apply(self,%l) applies the lambda expression self to the argument list %l.
categories | Calls and Slot Access | normal dispatch | Core method |
apply(m,lx) applies the method m to the argument list lx. For instance the following expressions are equivalent :
apply(+ @ integer, list(1, 3)) -> 4 1 + 3 -> 4 |
categories | Calls and Slot Access | normal dispatch | Core method |
apply(self,ls,l) applies the function self to the argument list l, where ls is the list of sort of the arguments and the result (i.e. length(ls) = length(l) + 1). For instance, if self is the external function that defines + @ integer :
apply(f, list(integer,integer,integer), list(1,2)) -> 1 + 2 |
categories | array | normal dispatch | Kernel method |
creates a copy of the bag x that is represented as an array. The member type must be given as a parameter t and an error will occur if a member of the bag does not belong to t..
categories | Integers and Floats | normal dispatch | Core method |
asin(self) computes the principal value of the arc sine of self in the range [-pi/2, +pi/2].
categories | Integers and Floats | normal dispatch | Core method |
atan(self) returns the principal value of the arc tangent of self in the range [-pi/2, +pi/2].
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
backtrack() pops the current world and returns to the previous one.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
backtrack(n) returns to the world numbered with n, and pops all the intermediary worlds.
categories | Modules | normal dispatch | Kernel method |
Sets the current namespace to the module m.
categories | Integers and Floats | normal dispatch | [XL] Kernel method |
bin!(i) returns a string representation of the integer i in the binary basis (the length of the string is always 32). The string can only contain '0' and '1' chars
bin!(0) -> "0000000000000000000000000000000" bin!(1) -> "0000000000000000000000000000001" bin!(12) -> "0000000000000000000000000001100" |
categories | Communication ports | normal dispatch | [XL] Core method |
allocate an empty blob
categories | Communication ports | normal dispatch | [XL] Core method |
allocate a blob that is filled with all data that can be read on p
categories | Communication ports | normal dispatch | [XL] Core method |
allocate a blob with the string self has the initial data
categories | Communication ports | normal dispatch | [XL] Core method |
allocate a blob that can receive at least n bytes (i.e. writing more than n bytes may cause further allocation)
categories | Communication ports | normal dispatch | [XL] Core method |
allocate a blob that is a copy of the given blob
categories | Communication ports | normal dispatch | [XL] Core method |
perform a back-quote expansion of the system command s. subexpressions enclosed by '`' are substituted by the output generated by the corresponding command ex: bexpand("`ls`") returns the result of an ls command
categories | Communication ports | normal dispatch | [XL] Core method |
buffer! creates a buffer filter on the port self with an internal buffer bufsize bytes long.
categories | Calls and Slot Access | normal dispatch | Core method |
call(p, x1, x2, ..., xn) is equivalent to apply(p, list(x1 ,x2 , ... , xn)).
categories | Lists, Sets and Tuples | normal dispatch | Core method |
Classical LISP methods that return the head of the list (e.g. l[1]).
car(list(1,2,3)) -> 1 car(list(3,2,1)) -> 3 |
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
cast!(s, t) sets the member type of bag s to t. This method should be used carefully since their is no verification made to assert that all elements from the list actually belongs to the supplied type.
cast!(list(1,2,3), integer) -> list<integer>(1,2,3) |
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
Classical LISP methods that return the tail of the list (e.g. the list l starting at its second element ).
cdr(list(1,2,3)) -> (2, 3) cdr(list(3,2,1)) -> (2, 1) |
categories | Integers and Floats | normal dispatch | Kernel method |
char!(n) returns the character which ASCII code is n.
char!(65) -> 'A' |
categories | Communication ports | normal dispatch | [XL] Core method |
acquire the memory address of a string.
categories | File system | normal dispatch | [XL] Kernel method |
chmod(s, m) changes the mode of the file or directory s to m.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
choice() creates a new world and steps into it.
categories | Miscellaneous | normal dispatch | [XL] Core method |
chroot(dirname) causes dirname to become the root directory, that is, the starting point for path searches of pathnames beginning with '/'.
This call is restricted to the super-user.
categories | Classes | normal dispatch | Core method |
class!(x) returns the intersection of all classes y such that x <= y that is the best class approximation (Such an intersection always exists since classes are organized in a lattice). Hence, if c is a class class!(c) = c.
categories | Communication ports | normal dispatch | [XL] Core method |
creates a UNIX domain connected socket on the UNIX domain server at file addr
categories | Communication ports | normal dispatch | [XL] Core method |
creates a connected socket at the address "addr:p", addr may be a numeric IP or a server name and p is the TCP port on which the connection should be made. For instance we could implement a simple HTTP GET :
httpget(addr:string, f:string) : string ->
let c := client!(addr, 80)
in (printf(c, "GET /~A HTTP/1.0 |
categories | Communication ports | normal dispatch | [XL] Core method |
tell that the filter should close its target when it is itself closed (cascading close)
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
color() returns the current color mode that can be on of :
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
color(c) sets the color mode to c and returns the previous color mode
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
color_princ(s) prints the string s on the current output (cout()). The string s may contain color sequences that will be printed as color modifiers according to the current color mode.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
The method commit() returns to the previous world but carries the updates that were made within the current world; these updates now belong to the previous world and another call to backtrack() would undo them.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
commit(n) returns to the world numbered with n through successive applications of commit().
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
This traditional method appends x at the beginning of l and returns the constructed list.
cons(1, list(2,3)) -> (1,2,3) |
categories | Hypothetical Reasoning | normal dispatch | Core method |
contradiction!() throws a contradiction. contradiction!() uses a unique instance of the contradiction exception class such to save a little memory.
categories | Classes | normal dispatch | Kernel method |
copy(x) returns a duplicate of the object x. It is not recursive : slots of the copied object are shared with that of the original one.
categories | Strings | normal dispatch | Kernel method |
The copy of a string is ... a copy of a string!
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
The copy of a bag (a set or a list) returns a fresh set or list with the same elements
categories | array | normal dispatch | Kernel method |
The copy of an array returns a fresh array with the same elements
categories | Integers and Floats | normal dispatch | Core method |
cos(self) computes the cosine of self (measured in radians).
categories | Integers and Floats | normal dispatch | Core method |
cosh(self) computes the hyperbolic cosine of self.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
date_add(d, c, i) returns the date d incremented by i, where the unit of the increment depends on c :
categories | Communication ports | normal dispatch | [XL] Kernel method |
decode64(pr,pw) reads pr until EOF. read data is assumed to be encoded in base 64, the decoded data is written on pw.
categories | Lists, Sets and Tuples | normal dispatch | operation | Kernel method |
delete(p, x, y) is equivalent to p(x) :delete y. This is a destructive method in the sense that it modifies its input argument unless the result is nil (There is only one empty list). The proper way to use delete, therefore, is either destructive (l :delete x) or non-destructive (delete(copy(l), x)).
categories | Classes | normal dispatch | operation | Kernel method |
delete(s, x) returns s if x is not in the list (resp. set) s without x otherwise.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
diff_time(d1, d2) returns the difference d1 - d2 in seconds.
categories | Lists, Sets and Tuples | normal dispatch | Core method |
difference(s, t) returns the difference set s - t, that is the set of all elements of s which are not elements of t.
categories | Strings | normal dispatch | [XL] Kernel method |
digit?(s) returns true if for all c in s digit?(c) is true.
categories | Chars | normal dispatch | [XL] Kernel method |
digit?(c) returns true if c is in the interval ('0' .. '9').
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
ding() prints on stdout the "bell" char (ASCII 0x7).
categories | Integers and Floats | normal dispatch | Core method |
divide?(x,y) returns true if y is a multiple of x.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
elapsed(t) returns the time in millisecond (accounted in real time) that has elapsed since the call to timer!()
categories | Communication ports | normal dispatch | [XL] Kernel method |
encode64(pr,pw,line_length) reads pr until EOF. read data are encoded in base 64 and then written on the port pw. line_length specify the length at which carriage return are inserted.
categories | Modules | normal dispatch | Kernel method |
Pop the current namespace and restore the namespace that was active before the call to begin(m).
categories | Printing | normal dispatch | Core method |
end_of_string() returns the string containing everything that has been printed since the last call to print_in_string().
categories | File system | normal dispatch | [XL] Kernel method |
entries(s) returns the list of entries (files and sub-directories names) located in the directory s.
categories | File system | normal dispatch | [XL] Kernel method |
entries(s, w) returns the subset of entries (files and sub-directories names) located in the directory s that match the wildcard w (according to match_wilcard?). entries(s) is equivalent to entries(s, "*").
categories | Environment variables | normal dispatch | [XL] Kernel method |
environ(i) returns the ithenvironment variable (1 based index) in the form "var=value".
categories | Communication ports | normal dispatch | [XL] Core method |
check whether the end-of-file condition has been reached
categories | Classes | normal dispatch | Kernel method |
declares a class as ephemeral: the member set is not maintained (ephemeral_object)
categories | Tables | normal dispatch | Core method |
erase(a) removes all value pairs contained in the table. This means that, on one hand, the value a[x] becomes unknown for each object x, and also that any references to an object from the table's domain or an associated value is lost, which may be useful to allow for complete garbage collection.
categories | Classes | normal dispatch | Core method |
erase(p, x) removes the value associated to x with the property p. The default value, or the unknown value, is placed in the slot x.p, and the inverse if updated (if any).
categories | Strings | normal dispatch | [XL] Kernel method |
escape(s) encode the string s according to ISO-8859-1.
categories | Classes | normal dispatch | Kernel method |
exception!() returns the last exception that was raised.
categories | Miscellaneous | normal dispatch | Core method |
exit(n) stops CLAIRE and returns to the hosting system the value n. What can happen next is platform-dependent.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
explode(t) explodes the calendar time t in integers. The returned value is a tuple made of :
categories | Strings | normal dispatch | [XL] Kernel method |
explode(s, sep) returns the list of substrings of s that are separated by sep. When sep is found at the beginning of the string s an empty match is added to the result list unlike if sep was found at the end of s. Last, if sep isn't found in s the result is a list that contain s as unique element. For instance :
explode("CLAIRE--CLAIRE","--") -> list<string>("CLAIRE","CLAIRE") explode(";titi;toto",";") -> list<string>("","titi","toto") explode(";titi;toto;",";") -> list<string>("","titi","toto") explode("titi",";") -> list<string>("titi") |
categories | Strings | normal dispatch | [XL] Kernel method |
like match_wildcard?, explode_wildcard(s, w) matches the string s against the wildcard w. It return the list of matches or nil if the string s doesn't match. A match is made for each '*' and each sequence of '?'. For instance :
explode_wildcard("CLAIRE", "C*RE") -> list<string>("LAI") explode_wildcard("CLAIRE", "C*TE") -> nil explode_wildcard("CLAIRE", "C???RE") -> list<string>("LAI") explode_wildcard("CLAIRE", "C???TE") -> nil |
categories | File system | normal dispatch | [XL] Kernel method |
faccessed(s) returns the time when the file s was last accessed (e.g. the time of the last read operation)
categories | Integers and Floats | normal dispatch | Core method |
factor?(x,y) returns true if x is a multiple of y.
categories | File system | normal dispatch | [XL] Kernel method |
fchanged(s) returns the time when the status of file s was last changed (the time of the last write operation or the time of the last fmode call or the time of the last unlink...)
categories | File system | normal dispatch | [XL] Kernel method |
fcopy(s1,s2) copies the file s1 to a file named s2, if the copy is successful then the mode of s2 (according to fmode) is set to the same mode as s1.
categories | Communication ports | normal dispatch | [XL] Core method |
filter! should be used by filter constructors, it ensures the good shape of the inner relations (i.e. between the filter and its device)
categories | Classes | normal dispatch | Kernel method |
declares a class as totally defined in the hierarchy: no new subclasses can be added.
categories | Types | normal dispatch | Kernel method |
final(c) forbids the user to create any subclass of the class c. If c is a constant class, this is taken as a "diet" compiling directive.
categories | Strings | normal dispatch | [XL] Kernel method |
find(s,x) returns the position in s where the first occurrence of x is found and 0 otherwise.
find("toto","to") -> 1 find("toto","oto") -> 2 find("toto","ti") -> 0 |
categories | Strings | normal dispatch | [XL] Kernel method |
find(s,x,from) returns the position in s starting at from where the first occurrence of x is found and 0 otherwise.
find("toto","to",2) -> 3 find("toto","oto",2) -> 2 find("toto","ti", 2) -> 0 |
categories | Types | normal dispatch | Core method |
finite?(self) returns true if the type self represents a finite set. Set iteration (with the for loop) can only be done over finite sets.
categories | Strings | normal dispatch | Kernel method |
transforms a string into a float.
categories | Integers and Floats | normal dispatch | Kernel method |
transforms an integer into a float.
categories | Communication ports | normal dispatch | Core method |
flush pending buffers in the filter chain such pending data are actually written on the device
categories | Communication ports | normal dispatch | Core method |
for compatibility with <ycs> mainly used for stdin
categories | File system | normal dispatch | [XL] Kernel method |
fmode(s) returns the mode of the file s.
categories | File system | normal dispatch | [XL] Kernel method |
fmodified(s) returns the time when the file s was last modified (e.g. the time of the last write operation)
categories | Communication ports | normal dispatch | [XL] Core method |
open a file on disk. the returned port is buffered. This is a lib C like fopen API (see man for details). note: unlike in C lib there is no support for the 'b' mode (binary) i.e. always open in binary mode. For instance here is a simple file copy method :
file_copy(src:string, cpy:string) : void -> let fsrc := fopen(src, "r"), fcpy := fopen(cpy,"w") in (freadwrite(fsrc, fcpy), fclose(fsrc), fclose(fcpy)) |
Notice this method does not properly set the mode of the copied file as done by fcopy.
categories | Process handling | normal dispatch | [XL] Core method |
fork is the standard UNIX interface to create new processes. fork creates a child process that is an image of the current process that only differs by the returned value which is:
under win32 it produce a system error.
categories | Printing | normal dispatch | Core method |
This method does the same thing as printf, except that there are always two arguments, thus the arguments must be replaced by an explicit list. Unlike with the printf construction ~I cannot be used in a format. Last the string s may contain color escapes.
categories | Communication ports | normal dispatch | [XL] Kernel method |
read all data on self until eof
categories | Communication ports | normal dispatch | [XL] Kernel method |
read inside an existing string, this overwrites the string content and may modify the string length if an eof condition is reached on self. the amount of queried bytes is the length of the input string
categories | Communication ports | normal dispatch | [XL] Kernel method |
read a string of n bytes on self the length of the returned string may be lower than n if an eof condition is reached on self
categories | Communication ports | normal dispatch | [XL] Kernel method |
freadline(p) read a single line on the port p. The end-on-line marker is either CRLF, CR or LF and is not part of the returned line.
categories | Communication ports | normal dispatch | [XL] Kernel method |
Equivalent to freadline(p, sep, true)[1]
categories | Communication ports | normal dispatch | [XL] Kernel method |
Equivalent to freadline(p, seps, true) For instance :
parse_line_of_floats(p:port) : list[float] ->
let l := list[float]
in (while not(eof?(p))
let (data, sep) := freadline(p, {',', ';', ' |
categories | Communication ports | normal dispatch | [XL] Kernel method |
Equivalent to freadline(p, list(sep), sensitive?) but without support for escaping.
categories | Communication ports | normal dispatch | [XL] Kernel method |
read a raw data from p until a separator in seps matches. If sensitive? is false the separator matching is made insensitively. esc is an escape char that is used while reading the raw data so that the char that follows cannot be part of a match. The return value is a tuple made of :
categories | Communication ports | normal dispatch | [XL] Kernel method |
Equivalent to freadline(p, list(sep), sensitive?, esc)[1]
categories | Communication ports | normal dispatch | [XL] Kernel method |
read all data from src until eof and write it on trgt without performing any allocation. returns the amount of byte transfered.
categories | Communication ports | normal dispatch | [XL] Kernel method |
read up-to len bytes from src and write them on trgt without performing any allocation. returns the amount of bytes actually transferred (may be lower than len if an eof condition is reached on src)
categories | File system | normal dispatch | [XL] Kernel method |
fsize(s) returns the size in bytes of the file s. The returned value is a float and not an integer that could overflow for big files.
categories | Communication ports | normal dispatch | [XL] Core method |
skip n bytes from self (dummy read) without performing any allocation and return the amount of bytes actually skipped (may be lower than len if an eof condition is reached)
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall(l,x) applies a lambda with one argument
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall(m,x) applies a method with one argument
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall(l,x,y) applies a lambda with two argument
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall(m,x,y) applies a method with two argument
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall(l,x,y) applies a lambda with three argument
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall provides an easy interface with external (C++) functions. funcall(f, s1, x, s) applies an external function to an argument of sort s1. The sort of the returned value must be passed as an argument. Notice that the last argument is the sort of the result, and that giving an erroneous sort argument will likely produce a fatal error.
categories | Calls and Slot Access | normal dispatch | Kernel method |
funcall(m,x,y) applies a method with three argument
categories | Calls and Slot Access | normal dispatch | Kernel method |
like funcall(f, s1, x, s) but for external functions with two arguments.
categories | Calls and Slot Access | normal dispatch | Kernel method |
like funcall(f, s1, x, s) but for external functions with three arguments.
categories | Communication ports | normal dispatch | [XL] Kernel method |
writes a raw string on the port p
categories | Miscellaneous | normal dispatch | Core method |
gc() forces a garbage collection to take place.
categories | Symbols | normal dispatch | Kernel method |
gensym() generates randomly a new symbol.
categories | Symbols | normal dispatch | Kernel method |
gensym(s) generates randomly a new symbol prefixed by s.
categories | Classes | normal dispatch | Kernel method |
get(self,x) is equivalent to self(x), but without any verification on unknown.
categories | array | normal dispatch | Kernel method |
get(a, x) returns the lowest i such that a[i] = x (if no such i exists, 0 is returned).
categories | Classes | normal dispatch | Kernel method |
get(s,x) returns the value of x associated with slot s
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
get(l, x) returns the lowest i such that l[i] = x (if no such i exists, 0 is returned).
categories | Strings | normal dispatch | Kernel method |
get(l, x) returns the lowest i such that s[i] = c (if no such i exists, 0 is returned).
categories | Communication ports | normal dispatch | [XL] Kernel method |
returns the current reading index
categories | Symbols | normal dispatch | Core method |
returns the object whose name (in the default claire namespace) corresponds to the string s.
categories | Symbols | normal dispatch | Core method |
returns the object whose name (search from the namespace m) corresponds to the string s.
categories | Communication ports | normal dispatch | [XL] Core method |
read a single char on self reading an EOF char does not mean that the end-of-file is reached, one should use eof? @ port to check the eof condition
categories | Environment variables | normal dispatch | Core method |
getenv(s) returns the value of the environment variable s if it exists and an empty string otherwise.
categories | Communication ports | normal dispatch | [XL] Core method |
returns the name of the host running CLAIRE
categories | Signal Handling | normal dispatch | [XL] Core method |
getitimer returns the current values for timer it (i.e. the interval and the value).
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
getlocale(s) returns the current locale for the given category cat (see setlocale).
categories | Process handling | normal dispatch | [XL] Kernel method |
getpid() returns the process id of the calling process
categories | Process handling | normal dispatch | [XL] Kernel method |
getppid() returns the parent process id of the calling process
categories | Process handling | normal dispatch | [XL] Kernel method |
getuid() returns the real user ID of the calling process. Under windows this method always returns 0
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
hash(l, x) returns an integer between 1 and length(l) that is obtained through generic hashing. To obtain the best dispersion, one may use a list of size 2i-3. This function can be used to implement hash tables in CLAIRE; it is the basis of the table implementation.
categories | Integers and Floats | normal dispatch | [XL] Kernel method |
hex!(i) returns a string representation of the integer i in the hexadecimal basis (the length of the string is always 8). The string can only contain chars in the range ('0' .. '9') U ('A' .. 'F').
hex!(0) -> "00000000" hex!(12) -> "0000000C" hex!(255) -> "000000FF" hex!(256) -> "00000100" |
categories | driving optimizations | normal dispatch | Core method |
Id(x) returns x. Id has a special behavior when compiled which makes it useful. The argument is evaluated before being compiled. The intended use is with global variables: the compiler uses the actual value of the variable instead of a reference to the global variable. This is very convenient to introduce parameters that are defined outside the module that is being compiled. This is also used to tell compiler that an iteration should make explicit use of all iterations rule that may apply to some subclasses of the set expression being iterated.
categories | Types | normal dispatch | operation | Core method |
inherit?(self, ens) returns (self % ancestors(ens)).
categories | Chars | normal dispatch | Kernel method |
integer!(c) returns the ASCII code of c
categories | Symbols | normal dispatch | Kernel method |
integer!(s) returns a unique index associated to a symbol s
categories | Strings | normal dispatch | Kernel method |
integer!(s) returns the integer denoted by the string s if s is a string formed by a sign and a succession of digits.
categories | Integers and Floats | normal dispatch | Kernel method |
integer!(f) returns the lower integer approximation of f
categories | Integers and Floats | normal dispatch | Kernel method |
integer!(l) returns the integer represented by the bitvector l, i.e. the sum of all 2i for i in l.
categories | Methods | normal dispatch | Language method |
Activate fast dispatch on the property p, that is dynamic calls should be optimized. p is meant to be a uniform property such the optimization can take place.
categories | Methods | normal dispatch | Language method |
This new method (in CLAIRE 3.1) is used to associate the interface status to a property or a set of properties. Within a class (through the use of interface(c, p1, ...)), this means that a member method will be generated for the C++ class associated to c. Note that this definition requires the presence of a method pi @ C for each property pi. In CLAIRE 3.1, a union (c1 U c2 ... U c3) can be used instead of a class, which is an elegant way to factor the interface declaration for c1, ... cn.
categories | Selectors, Properties and Operations | normal dispatch | Kernel method |
r.inverse contains the inverse relation of r. If the range of r inherits from bag then r is considered multi-valued by default. If r and its inverse are mono-valued then if r(x) = y then inverse(r)(y) = x. If they are multi-valued, then inverse(r)(y) returns the set (resp. list) of all x such that (y % r(x)).
categories | File system | normal dispatch | [XL] Kernel method |
Tells if s represents an existing directory entry.
categories | Environment variables | normal dispatch | [XL] Kernel method |
isenv?(v) tests the existence of the environment variable v.
categories | File system | normal dispatch | [XL] Kernel method |
Tells if s represents an existing file entry.
categories | Classes | normal dispatch | Kernel method |
kill(x) is used to remove an object from the database of the language. kill(x) does it properly, removing the object from all the relation network but without deallocating.
categories | Classes | normal dispatch | Kernel method |
kill(x) is used to remove an object from the database of the language. kill(x) does it properly, removing the object from all the relation network but without deallocating.
categories | Process handling | normal dispatch | [XL] Kernel method |
kill(p) terminates the process identified by the process id p. It first give a chance to the process to exit gracefully upon receipt of a SIGTERM signal. If the process still runs after a short timeout then a SIGKILL signal is send to the process. The caller should have necessary permissions.
categories | Signal Handling | normal dispatch | [XL] Kernel method |
send the signal sig to the process with pid p.
categories | Classes | normal dispatch | Core method |
kill!(x) is more brutal than kill and deallocates without any checking.
categories | Objects and Entities | normal dispatch | Core method |
The general method known? simply returns true whenever the object exists in the database (i.e. false is returned if x is unknown vs. unknown?).
categories | Classes | normal dispatch | Core method |
known?(p, x) is equivalent to get(p, x) != unknown
categories | Lists, Sets and Tuples | normal dispatch | Core method |
last(l) returns l[length(l)]
categories | Strings | normal dispatch | [XL] Kernel method |
left(s,i) is equivalent to substring(s, 1, i).
left("123456", 2) -> "12" left("6", 2) -> "6" |
categories | array | normal dispatch | Kernel method |
returns the length of an array
categories | Strings | normal dispatch | Kernel method |
returns the length of a string
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
returns the length of a list. The length of a list is not its size !
categories | Communication ports | normal dispatch | [XL] Kernel method |
return the total amount of bytes contained in the blob, string! @ blob will return a string with that length
categories | Communication ports | normal dispatch | [XL] Core method |
line_buffer! creates a new line_buffer filter on the port self.
categories | Communication ports | normal dispatch | [XL] Core method |
since socket are bi-channel communication ports a simple close (i.e. the two channel at once) may cause the client to miss information already sent on the socket but still unsent from the underlying system point of view. This is the purpose of the linger that will ensure that what is sent is actually received: first, the write channel is closed then we wait for the read channel to be closed by the remote part. For sanity this operation has a timeout of 3 seconds
categories | File system | normal dispatch | [XL] Kernel method |
creates a hard link for the file s1 with name s2.
categories | array | normal dispatch | Kernel method |
list!(a) transforms a into a list.
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
list!(s) transforms s into a list. The order of the elements in the list can be anything.
categories | Integers and Floats | normal dispatch | Core method |
log(x) returns the value of the natural logarithm of argument x.
categories | Strings | normal dispatch | [XL] Kernel method |
lower(s) returns a copy of s where all uppercase letters are converted to lowercase.
categories | Strings | normal dispatch | [XL] Kernel method |
lower?(s) returns true if for all char c in s lower?(c) is true.
categories | Chars | normal dispatch | [XL] Kernel method |
lower?(c) returns true if c is not in ('A' .. 'Z').
categories | Strings | normal dispatch | [XL] Kernel method |
ltrim(s) (say left trim) returns a copy of s where heading blank chars (' ', '\n', '\t', '\r') have been removed.
ltrim("toto") -> "toto"
ltrim(" toto ") -> "toto "
ltrim(" |
categories | array | normal dispatch | Core method |
returns an array of length n filled with x. The parameter t is the member_type of the array, thus x must belong to t, as well as any future value that will be put in the array.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
make_date(s) parses the string s and tries to create a date with it according to RFC 1123
categories | Dates and Times | normal dispatch | [XL] Kernel method |
make_date(D,M,Y,h,m,s) returns the date D/M/Y h:m:s where months are one based index (M = 1 is January).
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
returns a list of length n filled with x (e.g., make_list(3, 0) = list(0, 0, 0)).
categories | Lists, Sets and Tuples | normal dispatch | [XL] Kernel method |
returns an empty list with type member t. n gives the size of the allocated content (i.e. adding n element won't cause further allocation).
categories | Integers and Floats | normal dispatch | Core method |
make_set(x) returns the set of bit index that are set in the integer x seen as a bitvector.
categories | Symbols | normal dispatch | Kernel method |
make_string(s) returns a string denoting the same identifier. If s is given in the qualified form (module/identifier), than the result will contain the name of the module ("module/identifier").
categories | Strings | normal dispatch | Kernel method |
make_string(i, c) returns a string of length i filled with the character c.
categories | Tables | normal dispatch | Kernel method |
returns a table with a domain of type d and a range t. The parameter x is the default value, thus x must belong to t, as well as any future value that will be put in the table.
[XL] In XL CLAIRE, tables created by make_table are seen as ephemeral, thus collectable by the GC.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
make_time(s) parses the string s and tries to create a time with it
categories | Dates and Times | normal dispatch | [XL] Kernel method |
returns the time constructed with the given hours, minutes and seconds
categories | Strings | normal dispatch | [XL] Kernel method |
match_wildcard?(s, w) returns true if the string s matches the wildcard w. The wildcard is a string that understand special chars :
For instance :
match_wildcard?("CLAIRE", "C*RE") -> true match_wildcard?("CLAIRE", "C*TE") -> false match_wildcard?("CLAIRE", "C???RE") -> true match_wildcard?("CLAIRE", "C???TE") -> false |
categories | Lists, Sets and Tuples | normal dispatch | operation | Core method |
max(f,self) return the element of self that has the greatest value according to the ordering method f. For instance :
max(< @ integer, list(1,2,3,2,1)) -> 3 |
categories | Integers and Floats | normal dispatch | operation | Core method |
returns the greatest integer
categories | Integers and Floats | normal dispatch | operation | Core method |
returns the greatest float
categories | Environment variables | normal dispatch | [XL] Kernel method |
maxenv() returns the index of the last environment variable. For instance :
for i in (1 .. maxenv()) printf("~A |
printf the process environment in the same way as the env shell command.
categories | Types | normal dispatch | Core method |
member(x) returns the type of all instances of type x, assuming that x is a CLAIRE type which contains objects y such that other objects z can belong to. If this is the case, member(x) is a valid type for all such z, otherwise the returned value is the empty set. For instance, if x is list[integer], all instances of x are lists that contain integers, and all members of these lists are integers. Therefore, member(list[integer]) is integer.
categories | array | normal dispatch | Kernel method |
member_type(x) returns the type of all members of the array x. Therefore, member(a) = member_type(a) for an array a.
categories | Strings | normal dispatch | [XL] Kernel method |
decode the string s that is assumed to be mime encoded (see RFC2047).
categories | Strings | normal dispatch | [XL] Kernel method |
return a mime encoded representation of s (see RFC2047).
categories | Lists, Sets and Tuples | normal dispatch | operation | Core method |
min(f,self) return the element of self that has the lowest value according to the ordering method f. For instance :
min(< @ integer, list(1,2,3,2,1)) -> 1 |
categories | Integers and Floats | normal dispatch | operation | Core method |
returns the lowest float
categories | Integers and Floats | normal dispatch | operation | Core method |
returns the lowest integer
categories | File system | normal dispatch | [XL] Kernel method |
mkdir(s) is equivalent to mkdir(s, <777)>
categories | File system | normal dispatch | [XL] Kernel method |
mkdir(s, m) creates a new directory s with mode m.
categories | Integers and Floats | normal dispatch | operation | Kernel method |
mod(x,y) is the rest of the Euclidean division of x by y (modulo).
categories | Modules | normal dispatch | Kernel method |
module!() returns the current namespace (i.e. module).
categories | Modules | normal dispatch | Kernel method |
module!(r) returns the module where the restriction r was created.
categories | Classes | normal dispatch | Core method |
new(self) creates an instance of the class self
categories | Classes | normal dispatch | Core method |
new(self, %nom) creates a named instance of the class v (assumed to be a subclass of thing) with the name self
categories | Objects and Entities | normal dispatch | Core method |
not(self) returns false for all entity except false, nil, the empty set and the empty list.
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
nth(l,i) return the ithelement of the bag l. nth(l,i) is equivalent to l[i].
categories | Strings | inline | Kernel method |
nth(s,i) returns the ithcharacter of the string s. nth(s,i) is equivalent to s[i].
categories | array | inline | Kernel method |
nth(a,i) returns the ithelement of the array a. nth(a,i) is equivalent to a[i].
categories | Integers and Floats | inline | Kernel method |
nth(n,i) returns true if the ithdigit of n in base 2 is 1. nth(n,i) is equivalent to n[i].
categories | Communication ports | normal dispatch | [XL] Kernel method |
categories | Tables | normal dispatch | Kernel method |
nth(t,x) returns the element of t that as the key x. nth(t,x) is equivalent to t[x].
categories | Communication ports | normal dispatch | [XL] Kernel method |
get the char at position n in self (n is a 1 based index).
categories | Tables | normal dispatch | Kernel method |
nth(t,x) returns the element of t that as the key tuple(x, y). nth(t,x) is equivalent to t[x,y], t[tuple(x,y)] and nth(t,tuple(x,y)).
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
nth+(l,i,x) inserts element x at the ithposition in the bag l. By extension, i may be length(l) + 1, in which case x is inserted at the end of l.
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
nth-(l,i) removes the ithelement of the bag l. By extension, i may be length(l) + 1, in which case x is inserted at the end of l.
categories | Tables | normal dispatch | Kernel method |
nth=(t,x,y) sets the item of t with key x to y. nth=(t,x,y) is equivalent to t[x] := y.
categories | Strings | inline | Kernel method |
nth=(s,i,c) sets the ithcharacter of the string s to c. nth=(s,i,c) is equivalent to s[i] := c.
categories | Communication ports | normal dispatch | [XL] Kernel method |
sets the nthchar in self with c (n is a 1 based index).
categories | Communication ports | normal dispatch | [XL] Kernel method |
categories | array | normal dispatch | Kernel method |
nth=(a,i,x) replace the ithelement of the array a by x. nth(a,i,x) is equivalent to a[i] := x.
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
nth=(l,i,x) replace the ithelement of the bag l by x. nth(l,i,x) is equivalent to l[i] := x.
categories | Tables | normal dispatch | Kernel method |
nth=(t,x1,x2,y) sets the item t with key tuple(x1,x2) to y nth=(t,x1,x2,y) is equivalent to t[x1,x2] := y.
categories | Strings | normal dispatch | [XL] Kernel method |
occurrence(s, z) returns the number of times when the substring z appears in s.
occurrence("toto", "to") -> 2 occurrence("toto", "tot") -> 1 occurrence("toto", "t1") -> 0 |
categories | Integers and Floats | normal dispatch | operation | Core method |
or(x, y) returns the bitwise union of two integers (seen as bitvectors).
categories | Objects and Entities | normal dispatch | Core method |
owner(self) returns the class from which the object is an instance. If x is an object, then owner(x) = isa(x) = the unique class c such that x % instances(c).
owner(12) -> integer owner("12") -> string owner(integer) -> class owner(integer U string) -> Union |
categories | Communication ports | normal dispatch | [XL] Core method |
create a pair of unidirectional pipe connected to each other. The first one is intended for read and the second one for write. note: some system may return bi-directional pipes...
categories | Communication ports | normal dispatch | [XL] Core method |
popen creates a new process with a redirected input ("r") or output (w). popen may be a two way communication device, this is system dependent
categories | Hypothetical Reasoning | normal dispatch | [XL] Kernel method |
XL CLAIRE provides a convenient way to (pre)allocate defeasible lists in a class prototypes. Starting with CLAIRE 3, the content of a defeasible list have to be allocated prior to perform updates on it. prealloc_list(t,n) allocates an empty list for a class prototype that will automatically initialize a slot with a preallocated list of n elements and initially empty :
lesson <: ephemeral_object( students:list[student] = prealloc_list(student, 100)) (store(students)) lesson!() : lesson -> lesson() |
notice that the list owned by the class prototype is actually empty and that it becomes allocated only once it is copied (as done by instantiation process).
categories | Hypothetical Reasoning | normal dispatch | [XL] Kernel method |
preallocate a set in a similar way to prealloc_list.
categories | Printing | normal dispatch | Kernel method |
princ(s) prints the string s on the current output (cout()).
categories | Printing | normal dispatch | Kernel method |
princ(c) prints the char c on the current output (cout()).
categories | Printing | normal dispatch | Kernel method |
equivalent to print(n)
categories | Printing | normal dispatch | Kernel method |
prints the content of the bag l on the current output (cout()). elements are separated by a comma ','.
categories | Printing | normal dispatch | [XL] Kernel method |
princ(s,i,j) is equivalent to princ(substring(s,i,j)) but without the allocation of a temporary string
categories | Printing | normal dispatch | Kernel method |
prints the entity x (x can be anything) on the current output (cout()).
categories | Printing | normal dispatch | Core method |
print_in_string() opens a new output port that will be stored as a string. The user is given access to the string at the end of the transcription, when the call to end_of_string() returns this string. print_in_string may be used recursively.
categories | Symbols | normal dispatch | Kernel method |
put(s,x) binds the symbol s to the object x.
categories | Tables | normal dispatch | Kernel method |
put(t,x,y) is equivalent to t[x] := y but does not trigger the rules associated to a. Besides, this operation is performed without any type-checking.
categories | Classes | normal dispatch | Kernel method |
put(p,x,y) is equivalent to p(x) := y but does not trigger the rules associated to p (seen as a relation) or the inverse of p. Besides, this operation is performed without any type-checking.
categories | Hypothetical Reasoning | normal dispatch | Core method |
put_store(r,x,v,b) is equivalent to put(r,x,v) but also stores this update in the current world if b is true. The difference with the use of the statement store(p) is that put_store allows the user to control precisely which update should be backtracked.
categories | Communication ports | normal dispatch | Core method |
writes a single byte on p
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
pwd() is used to obtain the path of the current working directory. this directory may be changed with setcwd.
categories | Signal Handling | normal dispatch | [XL] Core method |
raise the given signal to the calling process
categories | Integers and Floats | normal dispatch | Kernel method |
random(n) returns an integer in (0 .. n-1), supposedly with uniform probability.
categories | Integers and Floats | normal dispatch | [XL] Kernel method |
random!() resets the seed with the current UNIX time value for the random number generator process.
categories | Integers and Floats | normal dispatch | Kernel method |
random!(n) resets the seed for the random number generator process.
categories | Classes | normal dispatch | Core method |
read(self,x) is strictly equivalent to self(x) : it reads the value and raises an exception if it is unknown.
categories | Communication ports | normal dispatch | [XL] Core method |
read!(self) put the descriptor self in the selection set used for the select? operation
categories | Communication ports | normal dispatch | [XL] Core method |
readable?(self) returns true when self has been selected (see select?) and that a read operation is guaranteed to succeed.
categories | Miscellaneous | normal dispatch | Core method |
returns a release number of your CLAIRE system (<release>.<version>.<revision>).
categories | Communication ports | normal dispatch | [XL] Core method |
reopen a file for read, the current offset of the reopened file is moved at the same place if a buffer is present on the filter chain then it contents and index are restored
categories | Strings | normal dispatch | [XL] Kernel method |
returns a copy of the string src where all occurrence of s have been replaced by rep.
replace("toto", "to", "ti") -> "titi" replace("tototiti", "to", "") -> "titi" replace("totitoti", "tot", "t") -> "titi" |
categories | Strings | normal dispatch | [XL] Kernel method |
find(s,x) returns the position in s where the last occurrence of x is found and 0 otherwise.
rfind("toto","to") -> 3 rfind("toto","tot") -> 1 rfind("toto","ti") -> 0 |
categories | Strings | normal dispatch | [XL] Kernel method |
find(s,x,from) returns the position in s where the last occurrence of x is found before the position from and 0 otherwise.
rfind("toto","to", 4) -> 3 rfind("toto","to", 3) -> 3 rfind("toto","to", 2) -> 1 rfind("toto","ti", 4) -> 0 |
categories | Strings | normal dispatch | [XL] Kernel method |
right(s,i) is equivalent to substring(s, length(s) - i + 1, length(s)).
right("123456", 2) -> "56" right("6", 2) -> "6" |
categories | File system | normal dispatch | [XL] Kernel method |
rmdir(s) removes the directory s. The directory have to be empty and the calling process must have sufficient permissions.
categories | Lists, Sets and Tuples | normal dispatch | Core method |
removes the last element of the list self.
categories | Strings | normal dispatch | [XL] Kernel method |
rtrim(s) (say right trim) returns a copy of s where tailing blank chars (' ', '\n', '\t', '\r') have been removed.
rtrim("toto") -> "toto"
rtrim(" toto ") -> " toto"
rtrim(" |
categories | driving optimizations | normal dispatch | Optimize method |
safe(x) is semantically equivalent to x and is ignored by the interpreter (x = safe(x)). On the other hand, this tells the compiler that the expression x must be compiled with the safe setting of the optimizing options. This is useful when a complete program requires high optimization settings for performance reasons but you still want to ensure that (say) overflow errors will be detected. A typical use would be :
try safe(x * y) catch error MAX_INTEGER |
to implement a bounded multiplication that can be placed in an optimize module.
categories | Communication ports | normal dispatch | [XL] Core method |
select?() waits for selected descriptors for a write or read condition to occur. This method blocks until a condition occur and will always return true as soon as a condition is satisfied. selected descriptors are the one for which read! or write! has been called first. In order to test whether a condition was satisfied on a selected descriptor one should use readable? or writeable?.
categories | Communication ports | normal dispatch | [XL] Core method |
select?(ms) is the non-blocking version of select?. It returns true whenever a read or write condition is satisfied on a selected descriptor before a timeout of ms milliseconds could occur. If the timeout is reached before a condition could occur then select?(ms) returns false.
When true is returned a read (resp. write) operation is guaranteed to succeed on a descriptor for which readable? (resp. writable?) returns true. If the descriptor is a listener socket and readable? returns true then accept is guaranteed to succeed.
get_one_char(c:socket) : string -> (read!(c), if (select?(10) & readable?(c)) fread(c, 1) // always succeed else "") |
categories | Serialization | normal dispatch | [XL] Serialize method |
serialize(p, self) is equivalent to serialize(p, true, self)
categories | Serialization | normal dispatch | [XL] Serialize method |
When top? is true serialize(p,top?,self) writes on p a binary representation of self. When top? is false serialize(p,top?,self) writes on p the object tree starting at self (that is the object self and related objects). The data written on p can then be handle with unserialize to build back the object tree.
categories | Communication ports | normal dispatch | [XL] Core method |
creates a INET domain listener socket listening on the port p. For instance here is a basic HTTP server :
http_server(n:integer) : void ->
let s := server!(n)
in (while true
let c := accept(s)
in serve_client(c))
serve_client(c:socket) : void ->
(printf(c, "Hello world |
categories | Communication ports | normal dispatch | [XL] Core method |
creates a UNIX domain listener socket. addr is the absolutepath (i.e. must start with '/') to a file that will be used by the subsystem as listener descriptor.
categories | Communication ports | normal dispatch | [XL] Core method |
creates a INET domain listener socket listening on the port p with an internal queue of qlen connections.
categories | Communication ports | normal dispatch | [XL] Kernel method |
sets the current reading index
categories | Communication ports | normal dispatch | [XL] Core method |
sets the amount of bytes contained in the blob, when 0 is given a new chunk is reallocated for the internal data
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
setcwd(s) set the current working directory to the path s. this path can then be obtained with pwd.
categories | Environment variables | normal dispatch | [XL] Kernel method |
setenv(s) adds or updates an environment variable defined by s = "var=value".
categories | Signal Handling | normal dispatch | [XL] Core method |
setitimer sets a timer to the specified interval / value (in milliseconds). If value is non-zero, it indicates the time to the next timer expiration (at reload). If interval is non-zero, it specifies a value to be used in reloading value when the timer expires. Setting interval/value to 0/0 disables a timer. Setting interval to 0 causes a timer to be disabled after its next expiration (assuming value is non-zero). For instance we could implement a timeout as follow :
timeout() : void -> error("Time out !") (signal(SIGALRM, timeout)) (setitimer(ITIMER_REAL, 0, 10000)) |
which would raise a time out exception after 10 seconds of (real time) processing.
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
setlocale(cat, s) is a wrapper for the C lib setlocale. It sets the notion of natural language formatting style for particular sets of routines. The cat argument defines the category of the routine setlocale applies (see man). cat is one of the following :
categories | Miscellaneous | normal dispatch | Core method |
Passes the command s to the operating system (the shell) and returns the exit status of the command execution.
categories | Strings | normal dispatch | Kernel method |
The method shrink truncates the string s so that its length becomes n. This is a true side-effect and the value returned is always the same as the input.
let s := "123" in (shrink(s, 2), assert(length(s) = 2)) |
categories | Lists, Sets and Tuples | normal dispatch | Kernel method |
The method shrink truncates the list l so that its length becomes n. This is a true side-effect and the value returned is always the same as the input. As a consequence, shrink(l, 0) returns an empty list that is different from the canonical empty list nil.
let l := list<integer>(1,2,3) in (shrink(l, 2), assert(length(l) = 2)) |
categories | Signal Handling | normal dispatch | [XL] Core method |
sigblock adds a set of signal to the process signal mask.
categories | Signal Handling | normal dispatch | [XL] Core method |
Install a signal handler for the given signal_handler. A restriction p should exists with the domain void. signal returns the old property associated with the signal handler. One may use special values for the handler p:
in a special way when assigned to SIG_DFL: the USER INTERRUPT. In CLAIRE signals are synchronously distributed to the meta code such to keep CLAIRE memory in a good shape. That is, there is a delay between the kernel routine that catches the signal and the execution of the meta handler.
categories | Signal Handling | normal dispatch | [XL] Core method |
sigpending returns the set of signals that are currently blocked from delivery, i.e. the process has been signaled but the delivery is blocked until the signal is unblocked (sigunblock).
categories | Signal Handling | normal dispatch | [XL] Core method |
sigprocmask returns the set of signal that are part of the process signal mask, that is the set of signal that the subsystem blocks from delivery.
categories | Signal Handling | normal dispatch | [XL] Core method |
sigsetmask sets the set of signal for the process signal mask, that is the set of signal that the subsystem blocks from delivery.
categories | Signal Handling | normal dispatch | [XL] Core method |
sigsuspend temporarily changes the process signal mask to the set sigs, and then wait for a signal to arrive; on return the previous set of masked signals is restored. The signal mask set is usually empty to indicate that all signals are to be unblocked for the duration of the call.
categories | Signal Handling | normal dispatch | [XL] Core method |
sigunblock removes a set of signal to the process signal mask.
categories | Integers and Floats | normal dispatch | Core method |
sin(self) computes the sine of self (measured in radians).
categories | Integers and Floats | normal dispatch | Core method |
sinh(self) computes the hyperbolic sine.
categories | Miscellaneous | normal dispatch | [XL] Kernel method |
sleep(t) suspends the execution of the process for t ms.
categories | Communication ports | normal dispatch | [XL] Core method |
return a pair of inter-connected socket
categories | Lists, Sets and Tuples | normal dispatch | Core method |
this method sorts the list self according to the ordering method f
categories | Chars | normal dispatch | [XL] Kernel method |
space?(c) returns true if c is in {' ', '\t', '\r', '\n'}.
space?(' ') -> true
space?(' |
categories | Strings | normal dispatch | [XL] Kernel method |
space?(s) returns true if for all c in s c is in {' ', '\t', '\r', '\n'}.
space?(" ") -> true
space?("
categories | Integers and Floats | normal dispatch | Core method |
returns the square root of x. Returns an irrelevant value when x is strictly negative.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(v) declares the global variable v as defeasible (using the world mechanism).
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(r1, r2, ...) declares the relations (properties or arrays) as defeasible (using the world mechanism)
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(a, n, v, b) is equivalent to a[n] := v but also stores this update in the current world if b is true. As a syntactical convenience, the argument b may be omitted if it is true. Note that there is a similar method for properties called put_store.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
store(l, n, v, b) is equivalent to l[n] := v but also stores this update in the current world if b is true. As a syntactical convenience, the argument b may be omitted if it is true. Note that there is a similar method for properties called put_store.
categories | Dates and Times | normal dispatch | [XL] Kernel method |
strftime(f,d) is a wrapper for the C lib strftime. f may contain formatters introduced by the '%' (see man).
categories | Integers and Floats | normal dispatch | Kernel method |
make a new string from an integer n.
categories | Symbols | normal dispatch | Kernel method |
string! converts a symbol into a string. For example if toto is a symbol string!(toto) returns "toto". Unlike make_string, it returns the unqualified form :
string!(Francois/agenda) -> "agenda" whereas make_string(Francois/agenda) -> "Francois/agenda" |
categories | Communication ports | normal dispatch | [XL] Kernel method |
string! converts a blob in a string
categories | Chars | normal dispatch | [XL] Kernel method |
make a new string from a a char self. The returned string always has of length of 1. One could use make_string to create a string of a given length filled with a given char.
categories | Integers and Floats | normal dispatch | Kernel method |
make a new string from a float x.
categories | Communication ports | normal dispatch | [XL] Kernel method |
make a new string of length len from a char*
categories | Strings | normal dispatch | Kernel method |
substring(s,i,j) returns the substring of s starting at the ithcharacter and ending at the j th. For example :
substring("CLAIRE", 3, 4) -> "AI" |
If i is negative, the empty string is returned and if j is out of bounds (j > length(s)), then the system takes j = length(s).
categories | Strings | normal dispatch | Kernel method |
substring(s1,s2,b) returns i if s2 is a subsequence of s1, starting at s1's ithcharacter. The boolean b is there to allow case-sensitiveness or not (identify 'a' and 'A' or not). In case s2 cannot be identified with any subsequence of s1, the returned value is 0.
categories | Communication ports | normal dispatch | [XL] Kernel method |
returns a substring of the internal data considered, i and j are 1 base indexed
categories | Symbols | normal dispatch | Kernel method |
symbol!(s) returns the symbol associated to s in the claire module. A new symbol is created if no such symbol exists.
categories | Symbols | normal dispatch | Kernel method |
symbol!(s,m) returns the symbol associated to s in the module m . A new symbol is created if no such symbol exists.
categories | File system | normal dispatch | [XL] Kernel method |
creates a symbolic link for the file s1 with name s2.
categories | Integers and Floats | normal dispatch | Core method |
tan(self) computes the tangent of self (measured in radians).
categories | Integers and Floats | normal dispatch | Core method |
tanh(self) computes the hyperbolic tangent of self.
categories | Dates and Times | normal dispatch | Core method |
time_get() stops a clock started by time_set() it and returns the elapsed time in milliseconds.
categories | Dates and Times | normal dispatch | Core method |
time_set() starts a clock that will have to be stopped with time_get().
categories | Dates and Times | normal dispatch | Core method |
time_show pretty prints the current value of a timer started by time_set().
categories | Dates and Times | normal dispatch | [XL] Kernel method |
timer!() return the current date time in second with millisecond granularity. That is a real time clock is started, the returned value may be used with elapsed or uptime to get the time elapsed since the timer! call.
categories | Strings | normal dispatch | [XL] Kernel method |
trim(s) returns a copy of s where heading and tailing blank chars (' ', '\n', '\t', '\r') have been removed, one may use ltrim or rtrim that operate on left/right only.
trim("toto") -> "toto"
trim(" toto ") -> "toto"
trim(" |
categories | Lists, Sets and Tuples | normal dispatch | Core method |
categories | Strings | normal dispatch | [XL] Kernel method |
unescape(s) decode the string s assumed encoded according to ISO-8859-1.
categories | Communication ports | normal dispatch | [XL] Core method |
unget a string such the next char that can be read is c
categories | Communication ports | normal dispatch | [XL] Core method |
unget a string such the next data that can be read is s
categories | Miscellaneous | normal dispatch | [XL] Core method |
unix?() tell if the underlying system is a UNIX (vs. windows).
categories | Objects and Entities | normal dispatch | Core method |
The general method unknown? simply returns true if the entity is unknown in the database (vs. known?).
categories | Classes | normal dispatch | Core method |
unknown?(p, x) is equivalent to get(p, x) = unknown
categories | File system | normal dispatch | [XL] Kernel method |
unlink(s) removes the file s from the disk. note that the the file may still exists after the call since an other process may have it already open...
categories | Communication ports | normal dispatch | [XL] Kernel method |
unlink the UNIX domain socket file associated with the given listener socket
categories | Serialization | normal dispatch | [XL] Serialize method |
unserialize(p) creates an object tree from a serialized data stream (as done with serialize). The calling process should have a correct reading environment, that is the same class definition that was used by the process that serialized the data.
categories | Strings | normal dispatch | [XL] Kernel method |
returns a copy of the string s where all lowercase letters have been converted to uppercase.
upper("toto") -> "TOTO" upper("a 1 % @ B") -> "A 1 % @ B" |
categories | Strings | normal dispatch | [XL] Kernel method |
upper?(s) returns false if it exists a char c in s such c is a lowercase letter
upper?("toto") -> false upper?("123") -> true upper?("1 % @ B") -> true |
categories | Dates and Times | normal dispatch | [XL] Kernel method |
uptime(x) prints on the current output a human readable version of the time elapsed (accounted in real time) since timer! with millisecond granularity.
categories | Strings | normal dispatch | [XL] Kernel method |
url_decode(s) returns a new string that is the decoded version of s according to RFC 1738.
url_decode("toto") -> "toto" url_decode("toto+titi%3Dtata") -> "toto titi=tata" |
categories | Strings | normal dispatch | [XL] Kernel method |
url_encode(s) returns a new string that is a representation of s encoded according to RFC 1738.
url_encode("toto") -> "toto" url_encode("toto titi=tata") -> "toto+titi%3Dtata" |
categories | Communication ports | normal dispatch | Kernel method |
use_as_output(p) changes the value of the current output (the port where all print instructions will be sent AKA cout()) to p. It returns the previous port that was used as output which can thus be saved and possibly restored later.
categories | Process handling | normal dispatch | [XL] Core method |
waitpid(p) is equivalent to waitpid(p, true)
categories | Process handling | normal dispatch | [XL] Core method |
waitpid is a the UNIX interface to get status of child process. The parameter p specifies the set of child processes to wait for:
block?, when true, tell that the call should block until a waited child terminates.
categories | Hypothetical Reasoning | normal dispatch | Kernel method |
world?() returns the index of the current world.
categories | Communication ports | normal dispatch | [XL] Core method |
writable?(self) returns true when self has been selected (see select?) and that a write operation is guaranteed to succeed.
categories | Classes | normal dispatch | Core method |
This method is used to store a value in a slot of an object. write(p, x, y) is equivalent to p(x) := y.
categories | Communication ports | normal dispatch | [XL] Core method |
write!(self) put the descriptor self in the selection set used for the select? operation