Directives

Directives control the operation of the Felix compiler.

Directive Statements

include directive

The include directive causes the compiler to ensure the specified file is parsed, and ensure the abstract syntax tree generated by parsing is prepended to the current AST being generated by the parser.

See Include Directive for details.

open directive

The open directive injects the set of public names of the specified class or library specialised to the given types, in a shadow lookup scope just underneath the primary scope in which the open directive is written.

Symbols in shadow scopes are hidden by definitions in the primary scope and are not exported as as public members of the current primary class.

class X { fun f(x:int)=> x * x; }
class Y { open X; fun g(y:int) => f x; }
class Z {
  open Y;
  fun g(y:int) => y + 1; // hides Y::g of int
  fun h(k:int) => X::f k; // X::f not visible without qualification
}
open X, Y, Z;
println$ f 1; // X::f
println$ Y::g 1; // resolve X::g, Y::g ambiguity

An example with specialisations:

class P[T] { fun h(x:T) => x,x; }
open[U] P[U];
println$ f (f (f 1));

inherit directive

Syntax

namespace_stmt := "inherit" stvarlist squalified_name ";"

Description

The inherit directive injects the set of public names of the specified class or library specialised to the given types, into the current primary scope, as if they were defined there.

Injected symbols may clash with each other and definitions in the primary scope. Clashes with non-function symbols lead to a fatal compiler error because the compiler cannot construct a suitable entry in the symbol table. Clashes with function symbols lead to ambiguity errors only when the function name is used and overload resolution performed. Such clashes can be resolved by using a qualified name.

class X { fun f(x:int) => x * x; }
class Y { inherit X; fun g(x:int) => f x; }
println$ Y::f 1; // Y::f aka X::f

rename directive

Syntax

namespace_stmt := "rename" sdeclname "=" squalified_name ";"
namespace_stmt := "rename" "fun" sdeclname "=" squalified_name ";"

Decription

The rename directive can be is used to inject a single name into the current scope defining it by another name, either from the current scope, or some other scope. The name can either be a non-function name, a function name, or a class name. The name can be polymorphic and the defining expression can be specialised.

class X {
  fun f(x:int) => x * x;
  fun f(x:double) => x * x;
}
class Y {
  rename fun g = X::f;
  fun h(x:int) => g x;
}
println$ Y::g 1; // X::f of int

use directive

Syntax

namespace_stmt := "use" sname "=" squalified_name ";"
namespace_stmt := "use" squalified_name ";"

Description

The use directive injects a single symbol or set of function signatures into the current scope, as if it were defined there. It is a special shortcut version of the rename directive used when the injected name is the same as the source name.

library directive

Syntax

namespace_stmt := "library" sname "=" ? scompound
namespace_stmt := "open" "library" sname "=" ? scompound

Description

The library directive constructs an part of an extensible scope. Multiple library directives can be given for the same name. Libraries can therefore be defined in multiple files, whereas classes must be specified in a single file.

Libraries cannot be polymorphic and serve only to provide a qualified name prefix for names.

library X { fun f(x:int) => x * x; }
...
library X { fun g(x:int) => x + 1; }

class directive

Syntax

namespace_stmt := "class" sdeclname ";"

Description

The class directive specifies the rest of the current file should be considered as a class definition. The directive is syntactic sugar for the standard class definition, the entire purpose is to allow easier indentation of the text.

Qualified Names

Names can be qualified by the class of library in which to lookup the name. This can be used to resolve ambiguities, or, to find a symbol if the class or library containing the name is not open. Opening classes or libraries causes namespace pollution, which is especially problematic if the open is in the top level (global or root) scope and is generally reserved for core algebras.

Export directive

Syntax

stmt := "export" "requires" srequirements ";"

cbind_stmt := "export" "fun" ssuffixed_name "as" sstring ";"

cbind_stmt := "export" "cfun" ssuffixed_name "as" sstring ";"

cbind_stmt := "export" "proc" ssuffixed_name "as" sstring ";"

cbind_stmt := "export" "cproc" ssuffixed_name "as" sstring ";"

cbind_stmt := "export" "struct" ssuffixed_name "as" sstring ";"

cbind_stmt := "export" "union" ssuffixed_name "as" sstring ";"

cbind_stmt := "export" "type" "(" sexpr ")" "as" sstring ";"

stmt := "export" "python" "fun" ssuffixed_name "as" sstring ";" =>#

The export directive tell the compiler to export a symbol with a special name. The export directive can also be used as an adjective.

See also export adjective

export python directive

The export python directive tells the compiler the function is part of a Python module. It has no effect on the function itself, however it causes the compiler to generate a Python module table containing the function in the output. Felix generates module tables for Python 3. To work correctly the function must have arguments and return types compliant with Python C API.

Adjectival directives

A function, generator, procedure or type definition may be prefixed with an adjectival directive that provides instructions for its use or properties.

inline adjective

A function or procedure definition can be qualified by the adjective inline to tell the compiler to inline direct applications or calls.

Recursive functions or procedures cannot be inlined, it is an error to specify inline for them. [Currently ignored].

An inline function will not be inlined if it is invoked via a closure.

noinline adjective

The noinline adjective on a function or procedure definition tells the compiler not to inline it.

The inline and noinline directives are not optimisation hints, they are mandatory requirements with semantic impact. This is because Felix has indeterminate evaluation strategy and may choose to eagerly or lazily evaluate arguments. Indirect calls or direct calls to recursive function cannot be inlined and necessarily use eager evaluation. Inlined calls generally use lazy evaluation.

pure adjective

The pure adjective tells the compiler the programmer thinks the function is pure; that is, it is dependent only on its parameters. Dependence on invariant symbols outside the definition is permitted, however invoking an impure function or procedure is not.

The compiler will examine the function to try to determine if it is pure. If the compiler can prove it is not pure, the compilation will be aborted with an error message, otherwise the function will be taken as pure. [Purity violation is not currently implemented]

impure adjective

The impure adjective tells the compiler to treat the function as impure, whether it is actually impure or not.

Purity helps enable certain optimisations. For functions, purity ensures referential transparency.

total adjective

The total adjective tells the compiler the function or procedure will work correctly with all arguments of the correct type, that is, that there are no pre-conditions.

Felix provides a way to specify pre-conditions, but not all pre-conditions can or should be specified, and pre-conditions can and usually are omitted.

partial adjective

The partial adjective tells the compiler the function may fail with some correctly typed arguments, that is, that the function may have pre-conditions.

If pre-conditions are given along with the partial adjective it should indicate the pre-conditions are not complete.

strict adjective

The strict adjective tells the compiler that if an argument expression is evaluated lazily and fails, then the function would have failed anyhow.

Some functions require lazy evaluation. For example consider:

fun myif(c:bool, t:int, f:int) =>
  if c then t else f
 ;
 var y = 0;
 var x = myif(y==0, 1, 1/y);

This code will crash if the third argument to myif is evaluated before the function is called, even though the final result does not depend on it it. However if the application is inlined the resulting expression:

if y==0 then 1 else 1/y endif

will not crash becuase the else branch is not taken. Indeed in the example the compiler may optimise the code to just 1 because it knows y==0 must be true and the nasty division by zero is not only not executed, it isn’t even present in the code.

The strict adjective tells the compiler it is safe to eagerly evaluate the function application: if the evaluation of the argument would fail, then the function would fail even with lazy evaluation, for example because the argument is always are required.

Felix assumes functions are strict. Even if this is not the case, the function may still work correctly on the arguments for which it applied.

nonstrict adjective

This tells the programmer the function is not strict in one or more arguments. It has no effect on the compiler, which continues to assume the function is, in fact strict. Rather, it tells the programmer to be careful to call the function with arguments for which eager and lazy evaluation would produce the same result.

If this is not possible the programmer must change the argument type to accept a closure and evaluate the argument on demand, thereby enforcing lazy evaluation.

method adjective

The method adjective may only be used in an object and tells the compiler a closure of the function over the objects internal state must be included in the record value returned as the value of a field named after the function name.

virtual adjective

The virtual adjective can only be used in a class and tells the compiler the function, procedure, or type may be overriden in an instance. A virtual function must be defined in an instance if, and only if, it is actually used, and, it is not defined in the class.

export adjective

The export adjective is equivalent to an export directive specifying the function or type, providing the C name the same as the Felix nae.

See also Export directive

private adjective

The private adjective tells the compiler that the symbol being defined is private to the current class and should not be exported. Each class has a symbol table with two indices: the public index and the private index.

The private index maps names to definitions of all symbols defined in the class, whereas the public index omits symbols marked private.

Helper functions should be marked private as they are not intended to be used by the client of the class. Types intended only for internal as implementation details should also be marked private.

Note only the public access to the name of a private type is hidden: the type itself is still visible. For example a public function can return a value of a private type. The client can still name the type. For example:

class X {
  private typedef t = int;
  fun f () : t => 1;
}
var x = X::f();
typedef u = typeof x;
var z = x + x;

The client now has a name u for the type, even though they do not know it is an int. Also the calculation of z is legitimate, even though it depends on the type of x being an int. Therefore, private hides only the name of a definition.

Similarly and more obviously, a client cannot directly call a private function but they can call a closure of it a public function returns.

pod adjective

The pod adjective tells the compiler a type lifted from C is a plain old datatype. This means that it has a trivial destructor, it tells the compiler to omit the pointer to the destructor in a generated RTTI object, and this ensure the garbage collector will not waste time invoking the destructor when it doesn’t do anything anyhow.

Note that in Felix all data types must be first class which means they must by copyable, movable, and assignable (unless marked incomplete).

incomplete adjective

The incomplete adjective tells the compiler a type is not first class, and that expressions of the type may not be used. However pointers to such types may be used. Such pointers, however, cannot be dereferenced so the type acts as a phantom to separate these pointers from each other by type.

The incomplete adjective only makes sense on a C type binding.

uncopyable adjective

The uncopyable adjective tells the compiler a value of the type cannot be copied or assigned. They can, however, be constructed and destroyed.

This adjective has no semantics at the moment but is intended to tell the garbage collector the type may not be used in a copyable arena. Copying collectors work by having two arenas, and copy, compactly, objects from one arena into a fresh arena, then delete the old arena, in order to perform their function (that is, they do not collect and dispose of garbage but collect and retain reachable objects instead).

When allocating an object, the copyability attribute is passed as a boolean flag to the C++ operator new, so it can choose to place the object in an uncopyable space, whilst other objects are placed in a copyable space. The intention is to allow copyable objects to be compacted by moving them together in an arena, improving performance and freeing up larger blocks of free space. However the current Felix gc does not do compaction and the flag is ignored.

_gc_pointer adjective

The _gc_pointer adjective tells the compiler a type lifted from C is actually a pointer to an object managed gy the garbage collector. The type must be a pointer. The effect is to include storage locations of this type in the table of offsets of pointers in the RTTI object for any type containing an _gc_pointer, so that the garbage collector can trace it.

_gc_type T adjective

The _gc_type T adjective must be used in conjunction with the _gc_pointer adjective and tells the compiler the actual type pointed at is T.

The effect is that during code generation a C binding specification which requires a pointer to an RTTI object will provide one pointing to a T rather than the expected type. Here is an example:

private incomplete type RE2_ = "::re2::RE2";
_gc_pointer _gc_type RE2_ type RE2 = "::re2::RE2*";
gen _ctor_RE2 : string -> RE2 = "new (*PTF gcp, @0, false) RE2($1)";

We bind the private type RE2_ to the C type RE2. This is the type of a Google RE2 regular expression object.

It’s private so the public cannot allocate it. Instead we use the type RE2 which is a pointer, and thus copyable. Because it is a pointer we have to specify _gc_pointer.

Now, the constructor _ctor_RE2 takes a string and returns a Felix RE2 (C type RE2*) which is a pointer to a heap allocated object of type _RE2 (C type RE2).

The constructor does the allocation, so it must provde the shape of the RE2_ object, and this is what the specification _gc_type RE2_ does. This allows the notation @0 to refer to the shape of RE2_ instead of RE2 which it would normally.