1.1 Fundamentals
This tutorial will take you through the Felix system from the ground up. To proceed you will need to know some C and C++. Felix is a C++ code generator and our viewpoint here will be to explain how it generates C++ code.
In the examples below some of the names used seem a little strange: that's because we wish to avoid creating a clash with the library code.
So lets get started. Most languages which can interface to C have a thing called an FFI or Foreign Function Interface.
Felix is tightly integrated with C++ and does not requires such a crude mechanism. Lets see how it works!
1.1.1 Lifting types
The first thing we need to do is to lift some types out of C:
// A bool type. type mybool = "bool"; // An integer type. type myint = "int"; // A string type. type mystring = "::std::string" requires header " " ;
In the first two cases we're defining the Felix
types mybool
and myint
to be the C++ types bool
and int
respectively. That's pretty easy!
In the third case we're defining the Felix
type mystring
to be the C++ string
type {::std::string}. However to make sure
that string
is defined in the generated C++
code we have to emit
#include <string>
in the header file Felix generates. The requires
clause given with the type says to do this if,
and only if, the mystring
type is actually used.
If it's used we need the header, if it isn't we don't
want to slow the C++ compilation down by making
it process a header that's not needed.
1.1.2 Defining values.
So we now have some types, and we need to say how get some values. We can do this like so:
const myfalse : mybool = "false"; const mytrue : mybool = "true"; const zero : myint = "0"; const one : myint = "1"; const two : myint = "2"; const empty : mystring = '::std::string("")'; const white : mystring = '::std::string(" ")'; const hello : mystring = '::std::string("hello")'; const world : mystring = '::std::string("world")';
You will note that for the strings we have to put the double quotes {"} expected by C++, so for the specification we use single quotes {'}, Felix accepts both.
1.1.3 Defining functional operators
Now to get anywhere we need to do calculations. So we will define a few simple calculations.
fun myand : mybool * mybool -> mybool = "$1&&$2"; fun myor : mybool * mybool -> mybool = "$1||$2"; fun mynot : mybool -> mybool = "!$1"; fun myadd : myint * myint -> myint = "$1+$2"; fun mymul : myint * myint -> myint = "$1*$2"; fun myeq : myint * myint -> mybool = "$1==$2"; fun mycat : mystring * mystring -> mystring = "$1+$2";
It should be clear that {$1} refers to the first argument and {$2} refers to the second argument, and that the infix type operator {*} is used to separate the arguments, and the infix arrow {->} is used to specify the result type of the function. A specification like:
f: R * R -> R
is standard mathematics except that we use {*} instead of x
for cartesian products.
1.1.4 Defining commands
It's ok to do calculations but we need to have some way to at least output the results. We will use procedures to implement some commands.
proc myprint : myint = "::std::cout<<$1;" requires header " " ; proc myendl : 1 = "::std::cout<<::std::endl;" requires header " " ;
It should be clear how these procedures work. You should note that a procedure must be one or more C++ statements, so the trailing semicolon {;} within the quotes is required here. Procedure bindings don't specify a return type like functions because procedures don't return any values.
There is one piece of magic here: myendl
appears to accept an argument of type 1
.
This type is also called unit and
is built in to Felix. There is only one value
of this type:
()
which is why the type is called 1
.
In Felix, all functions and procedures
have one argument, and {()} is the argument
we have to use where the corresponding C++
function or procedure has no arguments.
1.1.5 Overloading
You probably know you can do overloading in C++, and you can do it in Felix too. Here's an example:
proc myprint : mystring = "::std::cout<<$1;" requires header " " ; proc myprint : mybool= '::std::cout<<($1??"mytrue":"myfalse");' requires header " " ;
Here, we defined two more overloads of myprint
so we can now
print values of type mybool
, myint
and mystring
with
the same command.
You will also note that the requires
clause is used here
on these commands to ensure that we get the include file
required to define {::std::cout} and {::std::endl} as well
as {::std::operator <<}.
Finally, note the trick notation for the C++ conditional using {??} instead of {?}. This is because {?} has a special meaning which we'll learn later.
1.1.6 A test program
Now we have enough tools to do some testing. So lets get going!
// Say "hello world" myprint (mycat (mycat (hello, white), world)); myendl (); // show 1 + 2, should be 3 myprint (myadd (one, two)); myendl(); // basic axiom check myprint (myeq (myadd (one, two), myadd (two, one))); myendl ();
Of course we expect these results:
hello world 3 mytrue