SAPI - Repl

REPL stands for Read, Execute, Print, Loop.

There is a crude REPL system present in src/main/commands/repl.c. Itself is nothing more than a call to the repl() function in the src/components/repl/repl.c file. Note that the REPL is not 100% functional yet!

The repl works differently than other SAPIs, as we cannot simply run the VM. Once reason for this, is that there is nothing to run. A user must input some saffire code (read), which must be executed (execute), its output (if any) printed (print) and we must repeat this (loop).

So instead, we do some tricky things.

First, we use libedit to give the user some decent edit functionality, history etc. Then, we initialize a Saffire Parser structure (normally used only during lexing and parsing). In this case, we create manually such a structure, and among other things, set the yyexec function to point to repl_exec and the yyparse function to repl_readline().

We initialize the vm through vm_init() with runmode VM_RUNMODE_REPL. We create a dummy bytecode file, which mimics an empty file, and create a stackframe for it.

Instead of calling vm_execute() with the given stackframe, we actually will be calling the parser function called yyparse(). This is a automatically generated function from our lexx and bison files. Where normally yyparse() will just read the source code from a file, since we changed the yyparse property of the SaffireParser structure to repl_readline(), it will call this function instead.

So instead of reading a line from a file, it will instead read a line from the console. Once a line has been entered by the user, yyparse() will continue with lexing and tokenizing.

This takes care of the read part of the repl. But how about executing?

This depends on what a user has entered. For instance, when a user has entered the following statement:

 a = <enter>

That can be lexed, and parsed, but it will not match a complete statement. We are still missing something. At that point, nothing will be executed, but the yyparse() will ask the user for more lines in order to complete the statement. (not only will it ask for more data, it will also tell you so by changing the users prompt from > to ...>.

So how does Saffire know that we have a “finished” statement?

This can be found in /src/components/compiler/saffire.y file. On a few places, you will see in the rules the yy_exec() call. This happens on a few places:

  • In the “saffire” rule, which is the main rule indicating a complete read of a file.
  • When an “import” line is finished.
  • when a “top-statement” is found.

A top-statement could be either an interface, a class definition (thus everything within class foo { ... } is one statement, including all methods and such, or simple statements outside classes like a=1; or if (a = 1) { b = 3}.

This also concludes that a REPL is not really useful for anything but some simple few-line tests.

As soon as saffire.y triggers a yy_exec, it will call repl_exec function (as this is configured in our SaffireParser structure. What it does is simply find the given AST (or actually, a small part of the AST, but since a branch of an AST is itself an AST too, this works quite well). It will convert this AST to assembler/bytecode.

Big TODO

However, it will not call the VM yet, but only dumps the compiled bytecode to the output. So it will give you an idea on what it would have done.