Compiler and Virtual Machine extensions
If you have programmed in an imperative programming language, you might have noticed the absence of an assignment operator in Motto. This is because the kernel of Motto is a purely Functional language. The Motto virtual machine has the capability to update a variable. It is just not exposed to the outside world. Before I show you how to expose it, let me explain why Motto cannot have an assignment operator based on its simple stack-based programming model.
A symbol bound to a value, is evaluated to that value:
> 100 a @ > a .s .c 100
As a Motto procedure always follow its arguments, we cannot have an
“assignment” procedure because
all it will see on the stack is the value, not the symbol. So we need
to add a facility to handle
this special case. We need a procedure that precedes its argument or
at least one of its arguments.
This can be accomplished by writing a compiler definition, i.e, a
procedure executed by the compiler.
This procedure will read the symbol from the input and emit virtual
machine instructions to assign
the previous value on the stack to that symbol. As this special
procedure is executed by the compiler, it has to be
written in the language the compiler itself is written. The Motto
compiler and virtual machine is
written in Scheme,
a dialect of Lisp.
Especially, we use a Scheme implementation called
Gambit-C
which has the ability to generate highly-optimized executable programs
for a variety of platforms.
Let us call our assignment procedure set and add its definition to
the Motto compiler:
> (cdef 'set (lambda (compiler)
(list (c-push (get-symbol (compiler-input compiler)))
(c-set))))cdef maps the Scheme symbol set to a Scheme procedure (a lambda form).
The Motto virtual machine consumes lists of instructions.
Here the compiler will read a symbol from the input stream and emit
the instruction to push
that symbol to the stack. This instruction is followed by a set
bytecode which will assign
the value already on the stack to the symbol:
> 140 set a > a .s .c 140
In fact, Motto comes pre-packaged with set and a few other useful
compiler definitions:
> 20 def b ;; defines a new variable. > 50 def a ;; re-defines the existing variable `a`. > a b + def c > c .s .c 70 > 100 c set > c .s .c 100
The use of def and set becomes more evident when we have to deal
with non-local variables:
> 100 def x > 200 def y > [x y .s .c] ! 100 200 > x y .s .c 100 200 > ;; In the following code block, `set` will update the global ;; variable `x`, while ;; `def` will declare a new local variable `y`. > [300 set x 500 def y x y .s .c 600 set y x y .s .c] ! 300 500 300 600 > ;; `x` was changed by the procedure, while `y` stays intact ;; as the procedure modified its local definition of `y`. > x y .s .c 300 200
There are compiler definitions to load and compile Motto source code files. For example, here is a simple Motto script – hello.m:
;; file: hello.m [hello world .s .c] def say-hello
This can be imported to the interpreter:
> import "hello.m" > say-hello hello, world
The interpreter will compile the script before executing it. The script can be explicitly compiled and stored in an object file for faster loading:
> compile "hello.m" > import "hello.mo" > say-hello hello, world
Another useful compiler definition is probe which can be used to
look-up a value in an
anonymous closure:
> 100 200 [def x def y []] ! dup probe x swap probe y .s .c 200 100
Compiler definitions can be used to optimize some computations by executing them at compile-time and inserting the result into bytecode. An example can be seen here. Most of the basic operations in Motto are compiler definitions. You can extend the compiler with your own definitions or change the behavior of existing definitions. For example, arithmetic procedures can be re-defined to make Motto do infix arithmetic:
> (cdef '+ (lambda (compiler)
(list (c-push (get-number (compiler-input compiler)))
(c-arith '+))))
> 10 + 20 .s .c
30Virtual Machine definitions
Just like the compiler, the virtual machine can also be extended with
definitions written in Scheme.
These definitions are executed at runtime, like normal Motto
procedures. A virtual machine definition
is a lambda expression that takes two arguments:
the current instance of the virtual machine and a reference to the
data stack. This lambda should
return a new data stack. A virtual machine definition is introduced
using the def Scheme procedure.
The following sample shows a VM definition that squares all numbers on
the stack. It first converts
the stack to a list and uses the Lisp map function to produce a list
of squares. This list is then converted
to a stack and returned. (Conversions between the data stack and
Scheme lists are not expensive,
because the current implementation of Motto uses a Scheme list as its
data stack!).
> (def 'sqr-all
(lambda (vm stack)
(list->stack (map (lambda (x) (* x 2))
(stack->list stack)))))
> 1 2 3 4 5 sqr-all
> .s
2 4 6 8 10