This file explains how the interpretor uses standard lower-level data types
to support functions and function contexts.
The term 'function', as used by programmers, might mean one of three
different things:
- a 'function body'
- a compile time entity -- the source code
- a 'function value'
-
a run time entity -- what is passed for a function argument
and what gets called at run time. These include the context that the function
will run in.
- a 'function activation'
-
a run time entity -- the temporary data needed during a specific
execution of the function
Global Context
A global context is stored in a vector of length 3 with the following fields:
0. name (string)
1. variables (hash_table: name (string) => Mini_obj)
2. parent global context (vector)
Function Body
Since function bodies are not function values, they may not be called (directly).
(see Function Values, below).
A function body is stored in a vector of length 13 with the following
fields:
0. name (string)
1. literal_table (vector)
2. global context (vector)
3. required #args (integer) (space required in far for args)
4. rest args? (nil or 't')
5. argument regions (vector of integer, each code is: 4 * unnesting-level +
0. global
1. ret
2. first_arg (self)
3. local_far
6. local names (vector of strings -- locals and min args)
7. bytecodes (string)
8. nested functions (vector of function bodies (vectors))
9. min #args (integer) (min args to pass in call)
10. #ret values (integer)
11. keyword table (maps keyword arg name to region) (hash: symbol =>
integer)
12. number of local variables before first argument (integer)
Top-level function bodies have an 14th field:
13. compiler function (function value?)
Function Values
Function values are built-in types and may not be extended by the programmer.
All function values must support a 'prepare_send' operation and a 'store_arg'
operation. In addition, non-member function values must support a 'prepare_call'
operation.
The built-in function values are:
-
standard_fun (for non-member functions)
-
contains function body being called
-
member_fun
-
contains operation name being called
-
does not support 'prepare_call'.
-
curried_fun (includes a function value and a set of positional argument
values)
-
used for obj.mthd_name by looking up the function and using the obj as
the first argument.
-
used for local functions by having the parent FAR as the first argument.
-
can be used to curry arguments to member functions or standard functions.
-
can curry arguments at any position.
- composed function (makes a function out of two other function values by
returning f(g(x)) when called -- i.e., this is f o g).
- contains the two function values being composed
- may be used as a part of a coroutine chain by giving it a coroutine
function as its second function and a standard or member function as its
first function.
- coroutine function (this represents a started coroutine to the
coroutine's caller).
- each time the caller calls this object, the object remembers the next
place to resume the coroutine at.
- the started coroutine also needs a coroutine function object that
represents the caller to it. This object will remember where to resume the
caller each time.
- thus, both the caller and the started coroutine jump back and forth by
making standard function calls on these special coroutine function values.
- a coroutine function value is what a coroutine returns when you
start it
- the procedure to start a coroutine is to first create a coroutine
function to represent yourself to the coroutine (with the mkcor_head
opcode) and then to pass this to the coroutine body (using either a
standard function value or a member function value). The coroutine body
will create and return a new coroutine function object to represent itself
to you (with the mkcor_fun opcode).
- coroutine functions are linked together so that chains of coroutines
may be treated as a single coroutine (or a single function). This is a
simplified form of unix pipes (without multi-tasking or buffering).
- to form a coroutine chain, start the last coroutine in the chain first.
Then use the coroutine function it returns to start the next to last
coroutine, etc. Make calls to the final coroutine function returned (the
one for the first coroutine in the chain) to operate the whole chain.
- may be used in conjunction with compose function to add a standard
function or member function onto a coroutine chain. These can then be used
to start another coroutine, adding a coroutine to the front of the composed
function.
- a started coroutine may be used anywhere a function is expected.
FAR (Function Activation Record):
A far is a vector with its local variables and arguments as elements. All
fars use the following reserved indexes:
0. for the function body
1. for the return continuation (continuation)
2. for the return continuation offset (integer)
3. for the free wait list
4. for the free std_cont list
5. for the number of positional arguments passed (including self or parent
far)
6. for the keyword argument names passed (vector of symbols)
The local variables start at index 7, followed by the positional argument
values, and finally the keyword argument values. The theory is that there
are always a fixed number of local variables, but there may be a variable
number of positional arguments and keyword arguments.
Continuation
A continuation is a built-in type. It takes care of wait counts and far
scheduling. It also knows where to allocate return data and where to store
the pointer to the return data.
There are two kinds of continuations: standard continuations and argument
continuations.
The standard continuations are allocated and reused by the code as it
runs. Each far keeps a list of free standard continuations available for
reuse. These are linked together through 'next' pointers in the
continuation
objects. The list head pointers are kept in index 4 of the far vector.
The continuation objects point to a wait object to do the actual wait
processing.
Wait
A Wait is a built-in type. It takes care of wait counts and far scheduling.
Waits are allocated and reused by the interpretor code as it runs. Each
far keeps a list of free waits available for reuse. These are linked
together through 'next' pointers in the wait objects. The list head pointer
is kept in index 3 of the far vector.