ExpandCollapseNext Index

+ 1.1 Program structure

A Felix program consists of a nominated root parse unit and the transitive closure of units with respect to inclusion.

The behaviour of this system consists of the action of the initialisation code in each unit, performed in sequence within a given unit, with the order of action between units unspecified.

+ 1.1.1 Parse Unit

A parse unit is a file augmented by prefixing specified import files to the front. These consist of a suite of grammar files defining the syntax and other files defining macros.

By convention import files have the extension .flxh.

With this augmentation all parse units in a program are independently parsed to produce an list of statements represented as abstract syntax, denoted an AST (even though it is a list of trees, not a single tree).

+ 1.1.2 AST

The program consists of the concatenation of the ASTs of each parse unit, resulting in a single AST, which is then translated to a C++ translation unit by the compiler.

+ 1.2 Grammar

The Felix grammar is part of the library. It is notionally prefixed to each file to be processed prior to any import files to specify the syntax with which the file is to be parsed and translated to an AST.

The grammar uses an augmented BNF like syntax with parse actions specified in R5RS Scheme.

The resulting S-expressions are translated to an intermediate form and then into an internal AST structure.

The parser is a scannerless GLR parser with significant extensions.

+ 1.2.1 Grammar syntax

Not written yet. Browse the grammar directory for examples.

+ 1.3 Modules

Every Felix program is encapsulated in a module with the name being a mangle of the basename of the root unit. The mangling replaces characters in the filename with other characters so that the module name is a valid ISO C identifier.

+ 1.3.1 Special procedure flx_main

A program module may contain at most one top level procedure named flx_main. After initialisation code suspends or terminates, this procedure is invoked if it exists. It is the analogue of main in C++ however it is rarely used: side-effects of the root units initialisation code are typically used instead.

+ 1.3.2 Libraries

In Felix a library is a root unit together with its transitive closure with respect to inclusion, which does not contain a top level flx_main.

A program unit can be augmented by a set of libraries which are then considered as if included, but without an include directive being present.

+ 1.4 Lexicology

All Felix files are considered to be UTF-8 encoded Unicode.

Felix uses a scannerless parser, there are no keywords.

+ 1.4.1 Identifiers

A plain identifier starts with a letter or underscore, then consists of a sequence of letters, digits, apostrophy, has no more than one apostrophy or dash in a row, except at the end no dash is allowed, and any number of apostrophies.

  Ab_cd1  a' b-x

Identifies starting with underscore are reserved for the implementation.

A letter may be any Unicode character designated for use in an identifier by the ISO C++ standard. In practice, all high bit set octets are allowed.

A TeX identifier starts with a slosh and is followed by a sequence of letters.

TeX Symbols

+ 1.4.2 Integer Literals

Reference

An plain integer literal consists of a sequence of digits, optionally separated by underscores. Each separating underscore must be between digits.

A prefixed integer literal is a plain integer literal or a plain integer literal prefixed by a radix specifier. The radix specifier is a zero followed by one of the letters bBoOdDxX for binary, octal, decimal or hex.

An underscore is permitted after the prefix.

The radix is the one specified by the prefix or decimal by default.

The digits of an integer consist of those permitted by the radix: 01 for binary, 01234567 for octal, 0123456789 for decimal, 0123456789abcdefABCDEF for hex.

Note there are no negative integer literals.

A type suffix may be added to the end of a prefixed integer to designate a literal of a particular integer type, it has the form of an upper or lower case letter or pair of letters usually combined with a prefix or suffix u or U to designate an unsigned variant of the type.

Signed integers are expected to be two's complement with one more negative value that positive value. Bitwise and, or, exclusive or, and complement operations do not apply with signed types.

The effect of overflow on signed types is unspecified.

Unsigned types use the standard representation. Bitwise operations may be applied to unsigned types. Basic arithmetic operations on unsigned types are all well defined as the result of the operation mathematically modulo the maximum value of the type plus one.

The maximum value of an unsigned type is one less than two raised to the power of the number of bits in the type. The number of bits is 8, 16, 32, or 64 or 128 for all unsigned types.

Note that integers starting with 0 are decimal not octal as in C.

A table of suffices and the types they signify follows in lower case.

Suffix  Type      C type              Description
-------------------------------------------------------------------
i8      int8      int8_t              8 bit signed integer
i16     int16     int16_t             16 bit signed integer
i32     int32     int32_t             32 bit signed integer
i64     int64     int64_t             64 bit signed integer

u8      uint8     uint8_t             8 bit unsigned integer
u16     uint16    uint16_t            16 bit unsigned integer
u32     uint32    uint32_t            32 bit unsigned integer
u64     uint64    uint64_t            64 bit unsigned integer

t       tiny      signed char         C++ signed char used an integer
s       short     short               C short
i       int       int                 C int
l       long      long                C long
ll v    vlong     long long           very long: C long long


ut tu   utiny     unsigned char       unsigned tiny: C++ unsigned char used as an integer
us su   ushort    unsigned short      C unsigned short
u       uint      unsigned            C unsigned int
ul lu   ulong     unsigned long       C unsigned long
ull uv  uvlong    unsigned long long  C unsigned longlong

uz zu   size      size_t              array size
j       intmax    intmax_t            largest integer type
uj ju   uintmax   uintmax_t           largest unsigned integer type
p       intptr    intptr_t            pointer considered as an integer
up pu   uintptr   uintptr_t           pointer considered as an unsigned integer
d       ptrdiff   ptrdiff_t           signed distance between pointers 
ud      uptrdiff  uptrdiff_t          unsigned distance between pointers

Note that all these types are distinct unlike C and C++. The types designated are not the complete set of available integer like types since not all have literal representations.

Note the suffices do not entirely agree with C.

+ 1.4.3 Floating point Literals

Reference

Floating point literals follow ISO C89, except that underscores are allowed between digits, and a a digit is required both before and after the decimal point if it is present.

The mantissa may be decimal, or hex, a hex mantissa uses a leading 0x or 0X prefix optionally followed by an underscore.

The exponent may designate a power of 10 using E or e, or a power of 2, using P or p.

A suffix may be F,f,D,d, L or l, designating floating type, double precision floating type, or long double precision floating type.

  123.4
  123_456.78
  12.6E-5L
  0xAf.bE6f
  12.7p35

+ 1.4.4 String literals

Reference

Generaly we follow Python here. Felix allows strings to be delimited by; single quotes ', double quotes ", triped single quotes ''' or tripled double quotes """.

The single quote forms must be on a single line.

The triple quoted forms may span lines, and include embedded newline characters.

These forms all allows embedded escape codes. These are:

\a  -  7 : bell
\b  -  8 : backspace
\t  -  9 : horizontal tab
\n  - 10 : linefeed, newline
\r  - 13 : carriage return
\v  - 11 : vertical tab
\f  - 12 :form feed
\e  - 27 : escape
\\  - \  : slosh
\"  - "  : double quote
\'  - '  : single quote
\   - 32 : space
\xFF - hexadecimal character code
\o7 \o77 \o777 -- octal character code (stops on count of 3 or non-octal character)
\d9 \d99 \d999 -- decimal character code (stops on count of 3 or non-decimal character)
\uFFFF - utf8 encoding of specified hex value
\UFFFFFFFF - utf8 encoding of specified hex value

+ 1.4.4.1 Raw strings

A prefix "r" or "R" on a double quoted string or triple double quoted string suppresses escape processing,

this is called a raw string literal. NOTE: single quoted string cannot be used!

+ 1.4.4.2 Null terminated strings

A prefix of "c" or "C" specifies a C NTBS (Nul terminated byte string) be generated instead of a C++ string. Such a string has type +char rather than string.

+ 1.4.4.3 Perl interpolation strings

A literal prefixed by "q" or "Q" is a Perl interpolation string. Such strings are actually functions. Each occurrence of $(varname) in the string is replaced at run time by the value "str varname". The type of the variable must provide an overload of "str" which returns a C++ string for this to work.

+ 1.4.4.4 C format strings

A literal prefixed by a "f" or "F" is a C format string.

Such strings are actually functions.

The string contains code such as "%d" or other supported C format specifiers.

Variable field width specifiers "*" are not permitted.

The additional format specification %S is supported and requires a Felix string argument.

Such functions accept a tuple of values like this:

  f"%d-%S" (42, "Hello")

If vsnprintf is available on the local platform it is used to provide an implementation which cannot overrun. If it is not, vsprintf is used instead with a 1000 character buffer.

The argument types and code types are fully checked for type safety.

+ 1.4.4.5 Special identifiers

The special literal with a "n" or "N" prefix is a way to encode an arbitrary sequence of characters as an identifer in a context where the parser might interpret it otherwise. It can be used, for example, to define special characters as functions. For example:

  typedef fun n"@" (T:TYPE) : TYPE => cptr[T];

+ 1.4.5 Include Directive

An include directive has the syntax:

  include "filename";

where the filename is a Unix relative filename, may not have an extension, and may not begin with or contain .. (two dots).

If the filename begins with ./ then the balance of the name is relative, a sibling of the including file, otherwise the name is searched for on an include path.

In either case, a search succeeds when it finds a file with the appropriate base path in the search directory with extension .flx or {.fdoc}. If both files exist the most recently changed one is used. If the time stamps are the same the choice is unspecified.

+ 1.5 Macro processing

Syntax

Semantics

+ 1.5.1 Macro val

The macro val statement is used to specify an identifier should be replaced by the defining expression wherever it occurs in an expression, type expression, or pattern.

  macro val WIN32 = true;
  macro val hitchhiker;
  macro val a,b,c = 1,2,3;

+ 1.5.2 Macro for

This statement allows a list of statements to be repeated with a sequence of replacements.

  forall name in 1,2,3 do
    println$ name;
  done

+ 1.5.3 Constant folding and conditional compilation

Reference

Felix provides two core kinds of constant folding: folding of arithmetic, boolean, and string values, and deletion of code, either statements or expressions, which would become unreachable due to certain value of conditionals.

Basic operations on integer literals, namely addition, subtraction, negation, multiplication, division, and remainder are folded.

Strings are concatenated.

Boolean and, or, exclusive or, and negation, are evaluated.

False branches of if/then/else/endif expression and match expressions are eliminated.

False branches of if/do/elif/else/done are also eliminated.

By this mechanism of constant folding and elimination, Felix provides conditional compilation without the need for special constructions.

+ 1.6 General lookup

By default Felix looks up symbols in nested scopes, starting with all symbols in the current scope and proceeding through its containing scope outwards until the outermost scope is reached.

Symbols are visible in the whole of a scope, both before and after their introduction.

A symbol lookup may properly find either a single non-function symbol, which is final, or a set of function symbols.

If the kind of symbol being sought is a function symbol, overload resolution is performed on the set of function signatures found in a scope. If a best match is found, that is final. If no match is found the search continues in the next outermost scope.

All other cases are in error.

+ 1.7 Classes

Syntax

The top level Felix module can contain submodules which are specified by a non-polymorphic class statement:

  class classname { ... }

The effect is to produce a qualified name to be used outside the class:

  class classname { proc f () {} }
  classname::f (); 

Classes may be nested.

A class may contain private definitions:

  class X {
    private var a = 1;
  }
  // X::a will fail, since a is private to the class X

A private definition is visible within the scope of the class but not outside it.

A class must be specified within a single file.

Classes are not extensible, a definition of a class with the same name in the same scope is not permitted.

The body of a class forms a nested scope. Within a class all symbols defined in the class are visible, along with all those visible in the enclosing context.

The reserved name root may be used as a prefix for the top level module:

  var x = 1;
  class A { var x = root::x; }

+ 1.8 Lookup control directives

+ 1.8.1 Open directive

The simple open directive may be used to make the symbols defined in a class visible in the scope containing the open directive.

  class X { var x = 1; }
  open X;
  println$ x;

Names made visible by an open directive live in a weak scope under the current scope. Names in the weak scope may be hidden by definitions in the current scope without error.

  class X { var x = 1; }
  open X;
  var x = 2;
  println$ x; // prints 2

The open directive is not transitive. The names it makes visible are only visible in the scope in which the open directive is written.

+ 1.8.2 Inherit directive

The inherit directive allows all of the public symbols of a class to be included in another scope as if they were defined in that scope. This means such names inherited into a class can be accessed by qualification with the inheriting class name, and will be visible if that class is opened.

Inheriting is transtitive.

If a name is inherited it will clash with a local definition.

  class A { var a = 1; }
  class B { inherit A; }
  println$ B::a;

+ 1.8.3 Rename directive

This directive is can be used to inherit a single symbol into a scope, possibly with a new name, and also to add an alias for a name in the current scope.

When applied to a function name all functions with that name are renamed.

  class A { 
    var a = 1; 
    proc f() {} 
    proc f(x:int) {} 
  }
  
  class B { 
    rename a = A::a;
    rename fun f = A::f;
  }

The new name injected by a rename may be polymorphic:

  class A { proc f[T] () {} }
  class B { rename g[T] = A::f[T]; } 
  

+ 1.8.4 Use directive

This is a short form of the rename directive:

  class A { var a = 1; }
  class B { use A::a; use b = A::a; }

It cannot be applied to functions. The first form is equivalent to

  use a = A::a;

Unlike the rename directive the new name cannot be polymorphic and is limited to a simple identifier.

+ 1.8.5 Export directives

The export directives make the exported symbol a root of the symbol graph.

The functional export and forces it to be place in the generated code as an extern "C" symbol with the given name:

  export fun f of (int) as "myf";
  export cfun f of (int) as "myf";
  export proc f of (int) as "myf";
  export cproc f of (int) as "myf";

Functions are exported by generating a wrapper around the Felix function. If the function is exported as fun or proc the C function generated requires a pointer to the thread frame as the first argument, if the cfun or cproc forms are used, the wrapper will not require the thread frame.

In the latter case, the Felix function must not require the thread frame.

A type may also be exported:

  export type ( mystruct ) as "MyStruct";

This causes a C typedef to be emitted making the name MyStruct an alias to the Felix type. This is useful because Felix types can have unpredictable mangled names.

The word export optionally followed by a string may also be used as a prefix for any Felix function, generator, or procedure definition. If the string is omitted is taken as the symbol name. The effect is the same as if an export statement has been written.

+ 1.9 Variable Definitions

Syntax

A definition is a statement which defines a name, but does no cause any observable behavior, or, a class statement, or, a var or val statement. The latter two exceptions define a name but may also have associated behaviour.

+ 1.9.1 The var statement

The var statement is used to introduce a variable name and potential executable behaviour. It has one of three basic forms:

  var x : int = 1;
  var y : int;
  var z = 1;

The first form specifies the type and an initialising expression which must be of the specified type.

The second form specifies a variable of the given type without an explicit initialiser, however the variable will be initialised anyhow with the default contructor for the underlying C++ type, although that constructor may be trivial.

The third form does not specify the type, it will be deduced from the initialiser.

If the initialiser has observable behaviour it will be observed if at all, when control passes through the variable statement.

If the variable introduced by the var statement is not used, the variable and its initaliser will be elided and any observable behaviour will be lost.

To be used means to have its address taken in a used expression, to occur in a used expression. A used expression is one which initialises a used variable, or, is an argument to function or generator in a used expression, or an argument to a procedure through which control passes.

In other words, the variable is used if the behaviour of the program appears to depend on its value or its address.

The library procedure C_hack::ignore ensures the compiler believes a variable is used:

  var x = expr;
  C_hack::ignore x;

so that any side effects of expr will be seen. In general the argument to any primitive function, generator or procedure will be considered used if its containing entity is also considered used. In general this means there is a possible execution path from a root procedure of the program.

A variable may have its address taken:

  var x = 1;
  var px = &x;

it may be assigned a new value directly or indirectly:

  x = 2;
  px <- 3;
  *px = 4;

A variable is said to name an object, not a value. This basically means it is associated with the address of a typed storage location.

+ 1.9.1.1 Multiple variables

Multipls variables can be defined at once:

  var m = 1,2;
  var a,b = 1,2;
  var c,d = m;

With this syntax, no type annotation may be given.

+ 1.9.2 The val statement.

A val statement defines a name for an expression.

  val x : int = 1;
  val z = 1;

The value associated with a val symbol may be computed at any time between its definition and its use, and may differ between uses, if the initialising expression depends on variable state, such as a variable or call to a generator.

It is not an error to create such a dependence since either the value may, in fact, not change, or the change may not be significant.

Nevertheless the user must be warned to take care with the indeterminate evaluation time and use a var when there is any doubt.

Since a val simply names an expression, it is associated with a value not an object and cannot be addressed or assigned to. However this does NOT mean its value cannot change:

  for var i in 0 upto 9 do
    val x = i;
    println$ x;
  done

In this example, x isn't mutable but it does take on all the values 0 to 9 in succession. This is just a most obvious case: a less obvious one:

  var i = 0;
  val x = i;
  println$ x;
  ++i;
  println$ x;

which is clearly just an expansion of the the first two iteration of the previously given for loop. However in this case there is no assurance x will change after i is incremented because the compiler is free to replace any val definition with a var definition.

+ 1.9.2.1 Multiple values

Multipls values can be defined at once:

  val m = 1,2;
  val a,b = 1,2;
  val c,d = m;

With this syntax, no type annotation may be given.

+ 1.10 Functions

Syntax

+ 1.10.1 Functions

A felix function definition takes one of three basic forms:

  fun f (x:int) = { var y = x + x; return y + 1; }
  fun g (x:int) => x + x + 1;
  fun h : int -> int = | x => x + x + 1;

The first form is the most general, the body of the function contains executable statements and the result is returned by a return statement.

The second form is equivalent to a function in the first form whose body returns the RHS expression.

The third form specifies the function type then the body of a pattern match. It is equivalent to

  fun h (a:int) = { return match a with | x => x + x + 1 endmatch; }

The first two forms also allow the return type to be specified:

  fun f (x:int) : int = { var y = x + x; return y + 1; }
  fun g (x:int) :int => x + x + 1;

Functions may not have side effects.

All these function have a type:

  D -> C

where D is the domain and C is the codomain: both would be int in the examples.

A function can be applied by the normal forward notation using juxtaposition or what is whimsically known as operator whitespace, or in reverse notation using operator dot:

  f x
  x.f

Such applications are equivalent. Both operators are left associative. Operator dot binds more tightly than whitespace so that

  f x.g    // means
  f (g x)

A special notation is used for application to the unit tuple:

  #zero // means
  zero ()

The intention is intended to suggest a constant since a pure function with unit argument must always return the same value.

This hash operator binds more tightly than operator dot so

  #a.b // means
  (#a).b

+ 1.10.2 Pre- and post-conditions

A function using one of the first two forms may have pre-conditions, post-conditions, or both:

  fun f1 (x:int when x > 0) => x + x + 1;
  fun f2 (x:int) expect result > 1 => x + x + 1;
  fun f3 (x:int when x > 0) expect result > 1 => x + x + 1;
  fun f4 (x:int when x > 0) : int expect result > 1 => x + x + 1;

Pre- and pos-conditions are usually treated as boolean assertions which are checked at run time. The compiler may occasionally be able to prove a pre- or post-condition must hold and elide it.

The special identifier result is used to indicate the return value of the function.

+ 1.10.3 Higher order functions

A function may be written like

  fun hof (x:int) (y:int) : int = { return x + y; }
  fun hof (x:int) (y:int) => x + y;

These are called higher order functions of arity 2. They have the type

  int -> int -> int   // or equivalently
  int -> (int -> int) //since -> is right associative.

They are equivalent to

  fun hof (x:int) : int -> int = 
  {
    fun inner (y:int) : int => x + y;
    return inner;
  }

that is, a function which returns another function.

Such a function can be applied like

  hof 1 2 // or equivalently
  (hof 1) 2

since whitespace application is left associative.

+ 1.10.4 Procedures

A function which returns control but no value is called a procedure. Procedures may have side effects.

  fun show (x:int) : 0 = { println x; }
  proc show (x:int) { println x; }
  proc show (x:int) => println x;

The second form is a more convenient notation. The type 0 is also called void and denotes a type with no values.

A procedure may return with a simple return statement:

  proc show (x:int) { println x; return; }

however one is assumed at the end of the procedure body .

Procedures can also have pre- and post-conditions.

A procedure may be called like an application, however it must be a whole statement since expressions of type void may not occur interior to an expression.

  show 1;
  1.show;

If a procedure accepts the unit argument, it may be elided:

  proc f () =>  show 1;
  f; // equivalent to
  f ();

+ 1.10.5 Generators

A generator is a special kind of function which is allowed to have side effects. It is defined similarly to a function, but using the binder gen instead of fun:

  var seqno = 1;
  gen seq () { var result = seqno; ++seqno; return result; }

When a generator is directly applied in an expression, the application is replaced by a fresh variable and the generator application is lifted out and assigned to the variable. For example:

  fun twice (x:int) => x + x;
  println$ twice #seq;

will always print an even number because it is equivalent to

  var tmp = #seq;
  println$ twice tmp;

Therefore even if twice is inlined we end up with the argument to println being tmp+tmp and not #seq+#seq which would print an odd number.

+ 1.10.5.1 Yielding Generators

A generator may contain a yield statement:

  gen fresh() {
    var x = 1;
    while x < 10 do
      yield x;
      ++x;
    done
    return x;
  }

In order to use such a yielding generator, a closure of the generator must be stored in a variable. Then the generator may be called repeatedly.

  var g = fresh;
  for i in 1 upto 20 do 
    println$ i,#g;
  done

This will print pairs (1,1), (2,2) up to (10,10) then print (11,10), (12,10) up to (20,10).

The yield statement returns a value such that a subsequent call to a closure of the generator will resume execution after the yield statement. Therefore yield is a kind of cross between a return and a subroutine call.

If a generator executes a return statement, that is equivalent to yielding a value and setting the resume point back to the return statement, in other words return expr; is equivalent to

  while true do yield expr; done

Yielding generators should not be called directly because they will always start at the beginning with a fresh copy of any local variables used to maintain state.

Function closures differ from generator closures in that the closures is cloned before every application to ensure that the initial state is fresh.

Yielding generators are primarily intended to implement iterators, that is, to provide lazy lists or streams.

+ 1.10.6 Constructors

Felix provides a special notation which allows an identifier naming a type to return a value of that type:

  typedef cart = dcomplex;
  typedef polar = dcomplex;
  ctor cart (x:double, y:double) => dcomplex (x,y);
  ctor polar (r: double, theta: double) => 
    dcomplex (r * sin theta, r * cos theta)
  ;
  var z = cart (20.0,15.0) + polar (25.8, 0.7 * pi);

The constructions are equivalent to

  fun _ctor_cart (x:double, y:double) : cart => dcomplex (x,y);
  fun _ctor_polar (r: double, theta: double): polar => 
    dcomplex (r * sin theta, r * cos theta)
  ;

When a type with a simple name is applied to a value, Felix tries to find a function with that name prefixed by _ctor_ instead.

Note that Felix generates a constructor for struct and cstruct types automatically with argument type the product of the types of the structure fields.

+ 1.10.7 Special function apply

When Felix finds and application

    f a

where f is a value of type F which is not a function (or C function) type, Felix looks instead for a function named apply with argument of type:

  F * A

where A is the type of a. For example

  fun apply (x:string, y:string) => x + y; // concat
  var x = "hello " "world"; // apply a string to a string

+ 1.10.8 Objects

Felix provides an object system with syntax based on Java, and technology based on CLOS.

An object is a record of function closures, closed over the local scope of a constructor function that returns the record.

  interface person_t {
    get_name: 1 -> string; 
    set_age: int -> 0; 
    set_job : string -> 0;
    get_job : 1 -> string;
  }
  
  object person (name:string, var age:int) implements person_t =
  {
    var job = "unknown";
    method fun get_name () => name;
    method proc set_age (x:int) { age = x; }
    method fun get_job () => job;
    method proc set_job (x:string) {  job = x; }
  }
  
  var john = person ("John", 42);
  println$ #(john.name) + " is " + #(john.age).str;

The entity person is a function which when called with its argument of name and age returns a record of type person_t consisting of closures of the functions and procedures marked as method in its definition.

Since functions hide their local variables the object state is hidden and can only be accessed using the methods.

The implements clause is optional.

Objects provide an excellent way for a dynamically loaded shared library to export a set of functions, only the object function needs to be exported so it has a C name which can be linked to with dlopen.

+ 1.11 Type constructors

Syntax

+ 1.11.1 typedef

The typedef statement is used to define an alias for a type. It does not create a new type.

  typedef Int = int;

+ 1.11.2 Tuples

Tuple types are well known: a tuple is just a Cartesian Product with components identified by position, starting at 0. The n-ary type combinator is infix * and the n-ary value constructor is infix ,:

  val tup : int * string * double = 1, "Hello", 4.2;

The 0-ary tuple type is denoted 1 or unit with sole value ():

  val u : unit = ();

The 1-ary tuple of type T component value v is identified with the type T and has value v.

The individual components of a tuple may be accessed by a projection function. Felix uses an integer literal to denote this function.

  var x = 1,"Hello";
  assert 0 x == 1; assert x.0 == 1;
  assert 1 x == "Hello"; assert x.1 == "Hello";

[There should be a way to name this function without application to a tuple!]

A pointer to a tuple is also in itself a tuple, namely the tuple of pointers to the individual components. This means if a tuple is addressable, so are the components.

  var x = 1, "Hello";
  val px = &x;
  val pi = px.0; pi <-42;
  val ps = px.1; ps <-"World";
  assert x.0 == 42;
  assert x.1 == "World";

In particular note:

  var x = 1, "Hello";
  &x.0 <- 42;

because the precedences make the grouping (&x).0.

You cannot take the address of a tuple component because a projection of a value is a value.

Assignment to components of tuples stored in variables is supported but only to one level, for general access you must take a pointer and use the store-at-address operator <-.

The projections of a tuple can also be written in an expanded form so that they may stand alone as functions:

  var first = proj 0 of (int * string);
  var a = 1, "Hello";
  var one = a . first; 
  var two = a . proj 1 of (int * string);

+ 1.11.3 Records

A record is similar to a tuple except the components are named and considered unordered.

  typedef xy = (x:int, y:int);
  var r : xy = (x=1,y=2);
  println$ r.x, r.y;
  var px = &r.x; // means (&r).x
  px <- 42;
  println$ r.x, r.y; // r.x == 42 now

The field names are projection functions and can be applied to a record value to extract the nominated component, or applied to a pointer to a record to find a pointer to the nominated component.

The notation (may be changed soon)

  var prjx = x of (xy);
  var prjpx = x of (&xy);

can be used to refer to a projection in isolation, and a pointer projection in isolation, that is, as unapplied first class functions.

+ 1.11.3.1 Interfaces

An interface is a special notation for a record type all of whose fields are functions or procedures.

  interface fred {
    f: int -> int;
    g: int -> 0; // procedure
  }
  
  // equivalent to
  typedef fred = (f: int -> int, g: int -> 0);

The primary use is for specifying the type of a Java like object.

+ 1.11.4 Structs

A struct is a a nominally typed record, that is, it must be defined, and each definition specifies a distinct type.

  struct S { x:int; y:int; };
  var s : S = S (1,2);
  println$ s.x,s.y;

A struct value can be constructued using the structure name as a function and passing a tuple of values corresponding by position to the fields of the struct.

A struct constructor can be used a first class function.

The field names are projection functions and can be applied to a struct value to extract the nominated component, or applied to a pointer to a struct to find a pointer to the nominated component.

The notation (may be changed soon)

  var prjx = x of (S);
  var prjpx = x of (&S);

can be used to refer to a projection in isolation, and a pointer projection in isolation, that is, as unapplied first class functions.

A struct may also contain function and procedure definitions:

  struct A { 
    x:int;
    y:int;
    fun get2x => 2 * self.x;
    fun get2y () => 2 * self.y;
    proc diag (d:int) { self.x <- d; self.y <-d; }
  };

These functions are precisely equivalent to:

  fun get2x (self:A) => 2 * self.x;
  proc diag (self: &A) (d:int) { self.x <-d; self.y <-d; }

Note that for a function self is a value, for a procedure self is a pointer.

Because of these definitions, we can form object closures over a struct:

  var a = A(1,2);
  var g2y = a. get2y;
  var di = a . diag;

Note we can't form a closure for get2x without an explicit wrapper, i.e. eta-expansion.

+ 1.11.5 Sums

Sum types are the dual of tuples. They represent a sequence of possible cases, potentially with arguments. Case indicies are 0 origin. Sum variables are decode with a match which may also extract an argument value:

  typedef num = int + long + double;
  var x = (case 1 of num) 53L;
  println$ 
    match x with
    | case 0 (i) => "int " + i.str
    | case 1 (l) => "long" + l.str
    | case 2 (d) => "double " + d.str
    endmatch
  ;

+ 1.11.5.1 Unit sum

There is a family of special sum types equivalent to:

    2 = 1 + 1
    3 = 1 + 1 + 1
    4 = 1 + 1 + 1

Recall type 1, or unit, is the type of the empty tuple. The type 2 is also known as bool, and represents two cases where false is an alias for case 0 of 2 and true is an alias for case 1 of 2.

The type 0 or void, is the type of no values.

These types are called unit sums because they're a sum of a certain number of units.

Note carefully that:

    x + (y + z), (x + y) + z, x + y + z

are three distinct types because operator + is not associative.

+ 1.11.6 union

A union is the dual of a struct. It is a nominally typed version of a sum. Here for example is a list of integers:

  union intlist {
    iEmpty ;
    iCons of int * intlist;
  };

This alternative syntax is more commonly used and comes from ML family:

  union intlist = 
    | iEmpty 
    | iCons of int * intlist
  ;

The fields of a union type are injections or type constructors. In effect they cast their argument to the type of the union, thus unifying heterogenous types into a single type.

Pattern matches are used to decode unions.

  var x = iEmpty;
  x = iCons (1, x);
  x = iCons (2, x); // list of two integers
  
  fun istr (x:intlist) =>
    match x with
    | #iEmpty => "end"
    | iCons (i, tail) => i.str + "," + istr tail
    endmatch
  ;

The first variant represents an empty list. The second variant says that a pair consisting of an int and a list can be considered as a list by applying the type constructor iCons to it.

Unlike product types, a sum may directly contain itself. This is because sum types are represented by pointers.

+ 1.11.6.1 enum

A restricted kind of union, being a nominally typed version of a unit sum.

  enum colour { red, green, blue }; // same as
  enum colour = red, green, blue; // same as
  union colour = red | green | blue;

The tag value of an enum can be set:

  enum wsize = w8=8, w16=16, w32=32, w64=64;

+ 1.11.6.2 caseno operator

The caseno operator can find the tag value of any sum type, the anonymous sum, union, enum or variant as an integer.

  assert caseno w16 == 16;
  assert caseno (case 1 of 2) == 1;

+ 1.11.7 variant

Wariants the sum type which are the dual of records. They used named injections like unions but are structurally typed.

  typedef vars = union { Int of int ; Float of float; };

+ 1.11.8 Array

Felix has various kinds of array. The term is abused and sometimes refers to the abstract concept, and sometimes the statically typed fixed length array described here.

An array is nothing but a tuple all of whose elements have the same type. It is convenient to use an exponential operator with a unit sum index to provide a compact notation:

  int ^ 3 // array of 3 integers equivalent to
  int * int * int

Therefore the value:

  var a3 = 1,2,3;

is, in fact, an array. As for tuples an integer literal applied to an array value returns a component, however for arrays, an expression may be used as well:

  var i = 1;
  var y = a3 . i;
  var z = a3 . proj i of (int^3);

The last form is equivalent to

  var z = a . 
    match i with
    | 0 => proj 0 of (int^3)
    | 1 => proj 1 of (int^3)
    | 2 => proj 2 of (int^3)
    | _ => throw error
    endmatch
  ;

in other words there is a run time array bounds check equivalent to a match failure. Note that of course the actual generated code is optimised!

A run time check can be avoided by using the correct type of index:

  var i = case 1 of 3;
  var z = a . i; // no run time check

+ 1.11.8.1 Multi-arrays

Whilst we introduced the exponential notation

    B ^ J

as a mere shorthand, where J is a unit sum, in fact Felix allows the index to be any compact linear type.

A compact linear type is any combination of sums, products, and exponentials of unit sums. The type

   3 * 4 * 5

for example is compact linear, and therefore Felix allows the array type

  typedef matrix = double ^ (3 * 4 * 5)

Although this looks like the type

  typedef array3 = double  ^ 3 ^ 4 ^ 5

as suggested by the usual index laws, the latter is an array size 5 of arrays size 4 of arrays size 3 which can be used like:

  var a : array3;
  var z = a . case 1 of 5 . case 1 of 4 . case 1 of 3;

where you will note that the projections are applied in the reverse order to the indices. On the other hand to use the first form we have instead:

  var m : matrix;
  var z = a . (case 1 of 3, case 1 of 4, case 1 of 5);

The exponent here is a value of a compact linear type. It is a single tuple value! It is called a multi-index when applied to an array.

The advantage of this type is that there is an obvious encoding of the values shown in this psuedo code:

    i * 3 * 4 + j * 3 + k

which is nothing more than a positional number notation where the base varies with position. That encoding clearly associates with the compact linear value as integer in the range 0 to 59, or, alternatively, an value of type 60. In other words the type is linear and compact.

Since clearly, given an integer in range 0 through 59 and this type we can decode the integer into a tuple, being the positional representation of the integer in this weird coding scheme, the type is clearly isomorphic to the subrange of integer.

Therefore Felix allows you to coerce an integer to a compact linear type with a run time check, and convert a compact linear type to an integer or a unitsum:

  var i : int = ((case 1 of 3, case 1 of 4, case 1 of 5) :>> int);
  var j : 60 = ((case 1 of 3, case 1 of 4, case 1 of 5) :>> 60);
  var clt : 3 * 4 * 5 = (16 :>> 3 * 4 * 5);

Because we can do this we can now write a loop over a matrix with a single iterator:

  for i in 60 do
    println$ m . (i :>> 3 * 4 * 5);
  done

This is an advanced topic which will require an extensive explanation beyond the scope of this summary. However we will not that this facility provides a very high level feature known as polyadic array programming. In short this means that one may write routines which work on matrices of arbitrary dimension. You can of course do this in C by doing your own index calculations at run time and using casts, however Felix does these calculations for you based on the type so they're always correct.

+ 1.12 Meta-typing

Felix provides some facilities for meta-typing.

+ 1.12.1 typedef fun

The notation

  typedef fun diag (T:TYPE):TYPE=> T * T;
  var x: diag int = 1,2;

defines a type function (or functor). Given a type T, this function returns the type for a pair of T's. The identifier TYPE denotes the kind which is a category of all types.

Applications of type functions must be resolved during binding, since the result may influence overloading.

+ 1.12.2 typematch

Felix has a facility to inspect and decode types at compile time.

  typedef T = int * long;
  var x: 
    typematch T with
    | A * B => A
    | _ => int
    endmatch
    = 1
  ;

As with type functions, type matches must be resolved during binding. If a type match fails, an error is issued a compilation halted. The wildcard type pattern _ matches any type.

The real power of the type match comes when combined with a type function:

  typedef fun promote (T:TYPE): TYPE =>
    typematch T with
    | #tiny => int
    | #short => int
    | #int => int
    | #long => long
    | #vlong => vlong
    endmatch
  ;

This functor does integral promotions of signed integer types corresponding to ISO C rules.

+ 1.12.3 type sets

TBD

+ 1.13 Abstract types

Felix provides abstract types as demonstrated in this example.

  class Rat {
    type rat = new (num:int, den:int);
    ctor rat (x:int, y:int) =>  
      let d = gcd (x,y) in
      _make_rat (num=x/d, den-y/d)
    ;
  
    fun + (a:rat, b:rat) => 
      let a = _repr_ a in
      let b = _repr_ b in
      _make_rat ( 
        a.num * b.den + b.num * a.den,
        a.den * b.den
      )
    ;
  }

Here, the abstract type rat is represented by a record of two integers, num and den, but this type is hidden.

Inside the class Rat the operator _make_rat casts the implementation value to an abstract value, and the operator _repr_ casts the abstract value to its implementation.

These casts cannot be used outside the class, thereby hiding the implementation outside the class.

+ 1.14 Polymorphism

TBD

+ 1.15 Expressions

Syntax

Expressions are listed in approximate order of precedence, starting with the weakest binding.

We will often exhibit expressions in the form

  var x = expr;

so as to present a complete statement. The x is of no significance.

+ 1.15.1 Chain forms

+ 1.15.1.1 Pattern let

The traditional let binding of ML. The syntax is

  var x = let pattern = expr1 in expr2; // equivalent to
  var x = match expr1 with pattern => expr2 endmatch

  var x = let a = 1 in a + 1; // equivalent to
  var x = match 1 with a => a + 1 endmatch

+ 1.15.1.2 Function let

A let form which makes a function available in the expression

  var x = 
    let fun f(y:int)=> y + 1 in 
    f 42
  ;

A variant on the terminated match which allows a second match to be chained onto the last branch without any endmatch.

  var y = list (1,2);
  var x = 
    match y with
    | #Empty => "Empty"
    | _ =>
    match y with 
    | h ! Empty => h.str
    | _ =>
    match y with 
    | h1 ! h2 ! Empty => h1.str + "," + h2.str
  ;
  
  println$ x;
  

+ 1.15.1.3 conditional chain

A variant on the terminated if/then/elif/else allowing chaining.

  var x = 
    if c1 then r1 
    elif c2 then r2 else
    if c3 then r3 else
    r4
  ;

+ 1.15.2 Alternate conditional chain

  var x = n / d unless d == 0 then 0;

+ 1.15.3 Dollar application

A right associative low precedence forward apply operator taken from Haskell.

  var x =  str$ rev$ list$ 1,2,3;

+ 1.15.4 Pipe application

A left associative low precedence reverse apply operator taken from C#.

  var x =  1,2,3 |> list |> rev |> str;

+ 1.15.5 Tuple cons constructor

A right associative cons operator for tuples. Allows concatenating an element to the head/front/left end of a tuple. Can also be used in a pattern match to recursively decode a tuple like a list.

  var x =  3,4;
  var y = 1 ,, 2 ,, x; // 1,2,3,4

+ 1.15.6 N-ary tuple constructor

The is a non-associative n-ary tuple constructor consists of a sequence of expressions separated by commas.

  var x = 1,2,3,4;

+ 1.15.7 Logical implication

An operator for function implies.

  var x = false implies true;

+ 1.15.8 Logical disjunction

A chaining operator for function lor.

  var x = true or false;

+ 1.15.9 Logical conjunction

A chaining operator for function land.

  var x = true and false;

+ 1.15.10 Logical negation

A bool operator for function lnot.

+ 1.15.11 Comparisons

Non-associative.

  var x =
    a < b or 
    a > b or
    a <= b or
    a >= b or
    a == b or
    a != b or
    1 in list(1,2,3)
    1 \(\in\) list (1,2,3)
  ;

+ 1.15.12 Name temporary

Allows a subexpression to be named as a val by default or a var.

  var x = a +  (f y as z) + z; // equivalent to
  val z = f y; var x = a + z + z;
  
  var x = a +  (f y as var k) + k; // equivalent to
  var k = f y; var x = a + k + k;

Note that the var for ensures the subexpression is eagerly evaluated, before the containing expression.

+ 1.15.13 Schannel pipe operators

Used to flow data through schannels from the source on the left to the sink on the right via processing units in between.

  spawn_fthread$ source |-> filter |-> enhancer |-> sink;

This variant uses an iterator to stream data out of a data structure:

  spawn_fthread$ list (1,2,3) >-> sink;

+ 1.15.14 Right Arrows

Right associative arrow operators.

List cons operator.

  var x = 1 ! 2 ! list (3,4);

Function types (type language only):

    D -> C // Felix function
    D --> C // C function pointer

+ 1.15.15 Case literals

The case tag is only used in pattern matches. The sum or union type isn't required because it can be deduced from the match argument.

  var a = match a with Some v  => v | #None => 0;

The case constructor with integer caseno has two uses.

It creates a value of a sum type with no arguments:

  var x = case 1 of 2; // aka true

or it is a function for a sum type variant with an argument:

  var x = (case 1 of int + double) 4.2;

A case literal with a name instead of an integer constructs a variant instead:

  typedef maybe = union { No; Yes of int; };
  var x = (case Yes of maybe) 42;

The tuple projection function names a tuple projection:

  typedef triple = int * long * string;
  var snd = proj 1 of triple;
  var y: int = snd (1, 2L, "3");

+ 1.15.16 Bitwise or

Left associative.

  var x = q \| b;

+ 1.15.17 Bitwise exclusive or

Left associative.

  var x = q \^ b;

+ 1.15.18 Bitwise and

Left associative.

  var x = q \& b;

+ 1.15.19 Bitwise shifts

Left associative.

  var x = a << b; // left shift
  var x = a >> b; // right shift

+ 1.15.20 Addition

Chain operator. Non-associative for types. Left associative for expressions.

  var x = a + b + c;

+ 1.15.21 Subtraction

Left associative.

  var x = a - b;

+ 1.15.22 Multiplication

Chain operator. Non-associative for types. Left associative for expressions.

  var x = a * b;

+ 1.15.23 Division operators

Left associative. Not carefully: higher precedence that multiplication, unlike C!!

  var x = a * b / c * d; // means
  var x = a * (b / c) * d;
  
  var x = a * b % c * d; // means
  var x = a * (b % c) * d;

+ 1.15.24 Prefix operators

  var x = !a;
  var x = -a; // negation
  var x = ~a; // bitwise complement

+ 1.15.25 Fortran exponentiation

Infix ** is special syntax for function pow. The left operand binds more tightly than ** but the right operand binds as for prefixed operators or more tightly. Observe than that:

  var x = -a**-b;  // means
  var x = -(a**(-b));

preserving the usual mathematical syntax.

+ 1.15.26 Felix exponentiation

Left associative. The right operand binds as deref operator or more tightly. Used for array notation in the type language.

  var x = a ^ ix;

+ 1.15.27 Function composition

Standard math notation. Left associative. Same precedence as exponentiation. Spelled \circ.

  var x = f \(\circ\) g;

+ 1.15.28 Dereference

For function deref.

  var x = *p;

For builtin dereference operator:

  var x = _deref p;

Note these usually have the same meaning however the function deref can be overloaded. If the overloaded definition is not to be circular it may use _deref when dereferencing pointers.

+ 1.15.28.1 Operator new

Copies a value onto the heap and returns a pointer.

  var px = new 42;

+ 1.15.29 Whitespace application

Operator whitespace is used for applications.

+ 1.15.29.1 General

  var x = sin y;

+ 1.15.29.2 Caseno operator

Returns the integer tag value of the value of an anonymous sum, union, or variant type.

  var x = caseno true; // 1
  var x = caseno (Some 43); // 1

+ 1.15.29.3 Likelyhood

Indicaties if a bool valued expression is likely or unlikely to be true. Used to generate the corresponding gcc optimisation hints, if available.

  if likely (c) goto restart;
  if unlikely (d) goto loopend;

+ 1.15.30 Coercion operator

left associative. The right operand is a type.

  var x = 1L :>> int; // cast

+ 1.15.31 Suffixed name

The most general form of a name:

  var x = qualified::name of int;

Used to name functions, with the right operand specifying the function argument type.

+ 1.15.32 Factors

+ 1.15.32.1 Subscript

Used for array and string subscripting. Calls function subscript. For strings, returns a character. If the subscript is out of range after adjustment of negative index, returns char 0 and thus cannot fail.

  var x = a . [ i ]; // i'th element

+ 1.15.32.2 Subsstring

Calls function substring. Negative indices may be used to offset from end, i.e. -1 is the index of the last element. Out of range indices (past the end or before the start, after adjustment of negative indices) are clipped back to the end or start respectively.

  var x = a . [ first to past]; 
    // past is one past the last element refered to

+ 1.15.32.3 Copyfrom

Calls function copyfrom. Copies from designated index to end. Supports negative indices and range clipping for strings.

  var x = a . [to past]; // from the first

+ 1.15.32.4 Copyto

Calls function copyto. Supports negative indices and range clipping for strings.

  var x = a . [to past]; // from the first

+ 1.15.32.5 Reverse application

Left associative.

  var x = y .f; // means
  var x = f y;

+ 1.15.32.6 Reverse application with deref

Left associative

  var x = p *. k; // means
  var x = (*p) . k;

+ 1.15.32.7 Reverse application with addressing

Left associative

  var x = v &. k; // means
  var x = (&v) . k;

+ 1.15.32.8 Unit application

Prefix operator applies argument to empty tuple.

  var x = #f; // means
  var x = f ();

+ 1.15.32.9 Addressing

Finds the pointer address of a variable. Means pointer to in type language.

  var x : int = 1;
  var px : &int = &x; 
    // address of x
    // type pointer to int

+ 1.15.32.10 C pointer

Used in type language for pointer to type or NULL.

  var px : @char =  malloc (42);

Note that this symbol is also used in fdoc as a markup indicator. Please keep out of column 1, do not follow with a left brace.

+ 1.15.32.11 Label address

Used to find the machine address in the code text of a label. Used with computed goto instruction.

  proc f (a: int) {
    var target: LABEL = 
      if a < 0 then label_address neg
      elif a > 0 then label_address pos
      else label_address zer
    ;
    goto-indirect target;
    pos:> println$ "pos"; return;
    neg:> println$ "neg"; return;
    zer:> println$ "zer"; return;
  }

+ 1.15.32.12 Macro freezer

Used to disable macro expansion of a symbol.

  macro val fred = joe;
  var x = fred + noexpand fred; // means
  var x = joe + fred;

+ 1.15.32.13 Pattern variable

Notation v Used in patterns to designate a val variable to be bound in the pattern matching.

  var x = 
    match y with 
    | Some v => "Some " + v.str 
    | #None => "None";
  ;

+ 1.15.32.14 Parser argument

Notation n for some integer n. In user defined syntax designates the n'th term of a syntax production.

+ 1.15.33 Qualified name

A name in Felix has the form:

    class1 :: nested1 :: ... :: identifier [ type1, type2, ... ]

where the qualifiers and/or type list may be elided. This is the same as C++ except we use [] instead of <> for template argument types.

+ 1.16 Atoms

+ 1.16.1 Record expression

  var x = (name="Hello", age=42);

+ 1.16.2 Alternate record expression

  var x = 
    struct {
      var name = "Hello";
      var age = 42;
    }
  ;

+ 1.16.3 Variant type

Denotes a variant type.

  var x : 
    union {
      Cart of double * double;
      Polar of double * double;
    }
  ;

+ 1.16.4 Wildcard pattern

Used in a pattern match, matches anything.

    var x = match a with _ => "anything";

+ 1.16.5 Ellipsis

Used only in C bindings to denote varags.

    fun f: int * ... -> int;

+ 1.16.6 Truth constants

    false // alias for case 0 of 2
    true  // alias for case 1 of 2

+ 1.16.7 callback expression

??

  callback [ x ]

+ 1.16.8 Lazy expression

Function of unit.

  var f = { expr };
  var x = f ();

+ 1.16.9 Sequencing

Function dependent on final expression.

  var x = ( var y = 1; var z = y + y; z + 1 ); // equivalent to
  var x = #{ var y = 1; var z = y + y; return z + 1; };

+ 1.16.10 Procedure of unit.

  var p = { println$ "Hello"; } // procedure
  p (); 
  
  var f = { var y = 1; return y + y; }; // function
  var x = f ();

+ 1.16.11 Grouping

Parentheses are used for grouping.

  var x = (1 + 2) * 3;

+ 1.16.12 Object extension

  var x = extend a,b with c end;

+ 1.16.13 Conditional expression

  var x =
    if c1 then a elif c2 then b else c endif
  ;

+ 1.17 Executable statements

+ 1.17.1 Assignment

+ 1.17.2 The goto statement and label prefix

Felix statements may be prefixed by a label to which control may be transfered by a goto statement:

  alabel:>
    dosomething;
    goto alabel;

The label must be visible from the goto statement.

There are two kinds of gotos. A local goto is a jump to a label in the same scope as the goto statement.

A non-local goto is a jump to any other visible label.

Non-local transfers of control may cross procedure boundaries. They may not cross function or generator boundaries.

The procedure or function containing the label must be active at the time of the control transfer.

A non-local goto may be wrapped in a procedure closure and passed to a procedure from which the goto target is not visible.

  proc doit (err: 1 -> 0) { e; }
  
  proc outer () {
    proc handler () { goto error; }
    doit (handler);
    return;
  
    error:> println$ error;
  }

This is a valid way to handle errors. the code is correct because outer is active at the time that handler performs the control transfer.

+ 1.17.2.1 halt

Stops the program with a diagnostic.

  halt "Program complete";

+ 1.17.2.2 try/catch/entry

The try/catch construction may only be user to wrap calls to C++ primitives, so as to catch exceptions.

  proc mythrow 1 = "throw 0;";
  try
     mythrow;
  catch (x:int) =>
     println$ "Caughht integer " + x.str;
  endtry

+ 1.17.2.3 goto-indirect/label_address

The label-address operator captures the address of code at a nominated label.

The address has type LABEL and can be stored in a variable.

Provided the activation record of the procedure containing the label remains live, a subsequent @{goto-indirect) can be used to jump to that location.

  proc demo (selector:int) {
    var pos : LABEL = 
      if selector == 1 
      then label_address lab1
      else label_address lab2
      endif
    ;
    goto-indirect selector;
  lab1:>
    println$ "Lab1"; return;
  lab2:>
    println$ "Lab2"; return;
  }

+ 1.17.2.4 Exchange of control

Built on top of label adressing and indirect gotos, the branch-and-link instruction is conceptually the most fundamental control instruction. The library implementation is in

  inline proc branch-and-link (target:&LABEL, save:&LABEL)
  {
    save <- label_address next;
    goto-indirect *target;
    next:>
  }

A good example is here, which shows an example of coroutines.

+ 1.17.3 match/endmatch

The form:

  match expr with
  | pattern1 => stmts1
  | pattern2 => stmts2
  ...
  endmatch

is an extension of the C switch statement. The patterns are composed of these forms:

  (v1, v2, ... )          // tuple match
  h!t                     // list match
  h,,t                    // tuple cons
  Ctor                      // const union or variant match
  Ctor v                   // nonconst union or variant match
  (fld1=f1, fld2=f2, ...)  // record match
  pat as v                 // assign variable to matched subexpression
  pat when expr             // guarded match
  pat1 | pat2               // match either pattern
  999                       // integer match
  "str"                     // string match 
  lit1 .. lit2              // range match
  _                         // wildcard match

The guarded match only matches the pattern if the guard expression is true.

  match x with
  | (x,y) when y != 0 => ...
  endmatch

The tuple as list cons match is a form of row polymorphism where the first element of a tuple and the remaining elements considered as a tuple are matched.

A good example of this is found in the library here which allows printing a tuple of arbitrary number of components, indeed, this facility was implemented precisely to allow this definition in the library.

Record matches succeed with any record containing a superset of the specified fields.

As well as integer and string matches, a literal of any type with an equality and inequality operator can be matched. In addition, if there is a less than or equal operator <= an inclusive range match can be specified.

+ 1.17.4 if/goto

The conditional goto is an abbreviation for the more verbose conditional:

  if c goto lab; // equivalent to
  if c do goto lab; done

+ 1.17.4.1 if/return

The conditional return is an abbreviation for the more verbose conditional:

  if c return; // equivalent to
  if c do return; done

+ 1.17.4.2 if/call

The conditional call is an abbreviation for the more verbose conditional:

  if c call f x; // equivalent to
  if c do call f x; done

+ 1.17.5 if/do/elif/else/done

The procedural conditional branch is used to select a control path based on a boolean expression.

The else and elif clauses are optional.

  if c1 do 
    stmt1;
    stmt2;
  elif c2 do
    stmt3;
    stmt4;
  else
    stmt5;
    stmt6;
  done

The elif clause saves writing a nested conditional. The above is equivalent to:

  if c1 do 
    stmt1;
    stmt2;
  else 
    if c2 do
      stmt3;
      stmt4;
    else
      stmt5;
      stmt6;
    done
  done

One or more statements may be givn in the selected control path.

A simple conditional is an abbreviation for a statement match:

  if c do stmt1; stmt2; else stmt3; stmt4; done
  // is equivalent to
  match c with
  | true => stmt1; stmt2; 
  | false => stmt3; stmt4;
  endmatch;

+ 1.17.6 call

The call statement is used to invoke a procedure.

  proc p(x:int) { println$ x; }
  call p 1;

The word call may be elided in a simple call:

  p 1;

If the argument is of unit type; that is, it is the empty tuple, then the tuple may also be elided in a simple call:

  proc f() { println$ "Hi"; }
  call f (); // is equivalent to
  f(); // is equivalent to
  f;

+ 1.17.7 procedure return

The procedural return is used to return control from a procedure to its caller.

A return is not required at the end of a procedure where control would otherwise appear to drop through, a return is assumed:

  proc f() { println$ 1; }
  // equivalent to
  proc f() { println$ 1; return; }

+ 1.17.7.1 return from

The return from statement allows control to be returned from an enclosing procedure, provided that procedure is active.

  proc outer () {
    proc inner () {
       println$ "Inner";
       return from outer;
    }
    inner;
    println$ "Never executed";
  }

+ 1.17.7.2 jump

The procedural jump is an abbreviation for the more verbose sequence:

  jump procedure arg; // is equivalent to
  call procedure arg;
  return;

+ 1.17.8 function return

The functional return statement returns a value from a function.

  fun f () : int = {
    return 1;
  }

Control may not fall through the end of a function.

+ 1.17.8.1 yield

The yield statement returns a value from a generator whilst retaining the current location so that execution may be resumed at the point after the yield.

For this to work a closure of the generator must be stored in a variable which is subsequently applied.

  gen counter () = { 
    var x = 0;
  next_integer:>
    yield x;
    ++x;
    goto next_integer;
  }
  
  var counter1 = counter;
  var zero = counter1 ();
  var one = counter1 ();
  println$ zero, one;

+ 1.17.9 spawn_fthread

Reference

The spawn_fthread library function invokes the corresponding service call to schedule the initial continuation of a procedure taking a unit argument as an fthread (fibre).

The spawned fthread begins executing immediately. If coutrol returns before yielding by a synchronous channel operation, the action is equivalent to calling the procedure.

Otherwise the spawned fthread is suspended when the first write, or the first unmatched read operation occurs.

+ 1.17.9.1 read/write/broadcast schannel

+ 1.17.10 spawn_pthread

Reference

+ 1.17.10.1 read/write pchannel

+ 1.17.10.2 exchange

+ 1.17.11 loops

Reference

Felix has some low level and high level loop constructions.

The low level for, while, and repeat loops are equivalent to loops implemented with gotos.

The bodies of do loops do not constitute a scope, therefore any symbol defined in such a body is also visible in the surrounding code.

Low level loops may be labelled with a loop label which is used to allow break, continue, and redo statements to exit from any containing loop.

  outer:for var i in 0 upto 9 do
     inner: for var j in 0 upto 9 do
       println$ i,j;
       if i == j do break inner; done
       if i * j > 60 do break outer; done
     done
  done

+ 1.17.11.1 redo

The redo statement causes control to jump to the start of the specified loop without incrementing the control variable.

+ 1.17.11.2 break

The break statement causes control to jump past the end of the specified loop, terminating iteration.

+ 1.17.11.3 continue

The continue statement causes the control variable to be incremented and tests and the next iteration commenced or the loop terminated.

+ 1.17.11.4 for/in/upto/downto/do/done

A basic loop with an inclusive range.

  // up
  for var ti:int in 0 upto 9 do println$ ti; done
  for var i in 0 upto 9 do println$ i; done
  for i in  0 upto 9 do println$ i; done
  
  // down
  for var tj:int in 9 downto 0 do println$ j; done
  for var j in 9 downto 0 do println$ j; done
  for j in  0 upto 9 do println$ j; done

The start and end expressions must be of the same type.

If the control variable is defined in the loop with a type annotation, that type must agree with the control variable.

The type must support comparison with the equality operator == the less than or equals operator <= and increment with the pre increment procedure ++.

For loops over unsigned types cannot handle the empty case. For loops over signed types cannot span the whole range of the type.

The loop logic takes care to ensure the control variable is not incremented (resp. decremented) past the end (resp.start) value.

+ 1.17.11.5 while/do/done

The while loop executes the body repeatedly whilst the control condition is true at the start of the loop body.

  var i = 0;
  while i < 10 do println$ i; ++i; done

+ 1.17.11.6 until loop

The until loop executes the loop body repeatedly until the control condition is false at the start of the loop, it is equivalent o a while loop with a negated condition.

  var i = 0;
  until i == 9 do println$ i; ++i; done

+ 1.17.11.7 for/match/done

TBD

+ 1.17.11.8 loop

TBD

+ 1.17.12 Assertions

+ 1.17.13 assert

Ad hoc assertion throws an assertion exception if its argument is false.

  assert x > 0;

+ 1.17.13.1 axiom

An axiom is a relationship between functions, typically polymorphic, which is required to hold.

  axiom squares (x:double) => x * x >= 0;
  class addition[T]
  {
    virtual add : T * T -> T;
    virtual == : T * T -> bool;
  
    axiom assoc (x:T, y:T, z:T) : 
      add (add (x,y),z) == add (x, add (y,z))
    ;
  }

In a class, an axiom is a specification constraining implementations of virtual function in instances.

Axioms are restricted to first order logic, that is, they may be polymorphic, but the universal quantification implied is always at the head.

Existential quantification can be provided in a constructive logic by actually constructing the requisite variable.

Second order logic, with quantifiers internal to the logic term, are not supported.

+ 1.17.13.2 lemma

A lemma is similar to an axiom, except that is it easily derivable from axioms; in particular, a reasonable automatic theorem prover should be able to derived it.

+ 1.17.13.3 theorem

A theorem is similar to a lemma, except that it is too hard to expect an automatic theorem prover to be able to derive it without hints or assistance.

There is currently no standard way to prove such hints.

+ 1.17.13.4 reduce

A reduce statement specifies a term reduction and is logically equivalent to an axiom, lemma, or theorem, however it acts as an instruction to the compiler to attempt to actually apply the axiom.

The compiler may apply the axiom, but it may miss opportunities for application.

The set of reductions must be coherent and terminal, that is, after a finite number of reductions the final term must be unique and irreducible.

Application of reduction is extremely expensive and they should be used lightly.

  reduce revrev[T] (x: list[T]) : rev (rev x) => x;

+ 1.17.13.5 invariant

An invariant is an assertion which must hold on the state variables of an object, at the point after construction of the state is completed by the constructor function and just before the record of method closures is returned, and, at the start and end of every method invocation.

The invariant need not hold during execution of a method.

Felix inserts the a check on the invariant into the constructor function and into the post conditions of every procedure or generator method.

  object f(var x:int, var y:int) =
  {
     invariant y >= 0;
     method proc set_y (newy: int) => y = newy;
  }

+ 1.17.14 code

The code statement inserts C++ code literally into the current Felix code.

The code must be one or more C++ statements.

  code 'cout << "hello";';

+ 1.17.14.1 noreturn code

Similar to code, however noreturn code never returns.

  noreturn code "throw 1;";

+ 1.17.15 Service call

The service call statement calls the Felix system kernel to perform a specified operation.

It is equivalent to an OS kernel call.

The available operations include:

    union svc_req_t =
    /*0*/ | svc_yield
    /*1*/ | svc_get_fthread         of &fthread    // CHANGED LAYOUT
    /*2*/ | svc_read                of address
    /*3*/ | svc_general             of &address    // CHANGED LAYOUT
    /*4*/ | svc_reserved1
    /*5*/ | svc_spawn_pthread       of fthread
    /*6*/ | svc_spawn_detached      of fthread
    /*7*/ | svc_sread               of _schannel * &gcaddress
    /*8*/ | svc_swrite              of _schannel * &gcaddress
    /*9*/ | svc_kill                of fthread
    /*10*/ | svc_reserved2
    /*11*/ | svc_multi_swrite       of _schannel * &gcaddress 
    /*12*/ | svc_schedule_detached  of fthread
    ;

These operations are typically related to coroutine or thread scheduling. However svc_general is an unspecified operation, which is typically used to invoke the asynchronous I/O subsystem.

Service calls can only be issued from flat code, that is, from procedures, since they call the system by returning control, the system must reside exactly one return address up the machine stack at the point a service call is executed.

+ 1.17.16 with/do/done

The with/do/done statement is use to define temporary variables which are accessible only in the do/done body of the statement.

It is the statement equivalent of the let expression.

  var x = 1;
  with var x = 2; do println$ x; done
  assert x == 1;

+ 1.17.17 do/done

The do/done statement has no semantics and merely acts as a way to make a sequence of statements appear as a single statement to the parser.

Jumps into do/done groups are therefore allowed, and any labels defined in a do/done group are visible in the enclosing context.

Any variables, functions, or other symbols defined in a do/done group are visible in the enclosing context.

  do something; done

+ 1.17.18 begin/end

The begin/end statement creates an anonymous procedure and then calls it. It therefore appears as a single statement to the parser, but it simulates a block as would be used in C. It is exactly equivalent to a brace enclosed procedure called by a terminating semi-colon.

  begin
    var x = 1;
  end
  // equivalent to
  {
    var x = 1;
  };

+ 1.18 C bindings

Felix is specifically designed to provide almost seamless integration with C and C++.

In particular, Felix and C++ can share types and functions, typically without executable glue.

However Felix has a stronger and stricter type system than C++ and a much better syntax, so binding specifications which lift C++ entities into Felix typically require some static glue.

+ 1.18.1 Type bindings

In general, Felix requires all primitive types to be first class, that is, they must be default initialisable, copy constructible, assignable, and destructible. Assignment to a default initialised variable must have the same semantics as copy construction.

It is recommended C++ objects provide move constructors as Felix generated code uses pass by value extensively.

The Felix type system does not support C++ references in general, you should use pointers instead.

However, there is a special lvalue annotation for C++ functions returning lvalues that allows them to appear on the LHS of an assignment. Only primitives can be marked lvalue.

The Felix type system does not support either const or volatile. This has no impact when passing arguments to C++ functions. However it may be necessary to cast a pointer returned from a primitive function in order for the generated code to type check.

+ 1.18.2 Expression bindings

TBD

+ 1.18.3 Function bindings

TBD

+ 1.18.4 Floating insertions

TBD

+ 1.18.5 Package requirements

TBD

+ 1.19 Domain Specific Sublanguages

+ 1.19.1 Regexps

+ 1.19.2 Pipelines

+ 1.19.2.1 Synchronouse pipelines

+ 1.19.2.2 Asynchronouse pipelines

+ 1.19.2.3 Json

TBD

+ 1.19.2.4 Sqlite3

TBD