Quoting - how functions are made
The behavior of the input parser depends on an integer counter, the quote level. If the quote level is zero, each identifier is resolved in the current scope and acted upon (i.e. either pushed to the stack or executed depending on the execution mode of the name). If, however, the quote level is non-zero, only those names which have the quoting execution mode associated get executed. For all other names, a new function is instead created and pushed to the stack. This function will upon execution look up the name and only then act upon it. This allows easy construction of complex function objects.
In particular the functions { and } have quoting execution mode and are hence always executed directly when encountered.
The { function puts a function-quote-begin marker on the stack and increases the quote level by one. A later } then collects
all stack contents up to the function-quote-begin marker from the stack and creates assembly instructions from them. These
instructions will esentially execute the collected functions. After that } decrements the quote level and checks for zero.
If zero has been reached, a new function object is created from the assembled instructions and the current scope and pushed
to the stack. If the quote level is still non-zero after }, a new function is instead created which will upon execution
create a new function object from the instructions and the then current scope.
An example might make everything a little clearer:
1 1 add dump # instant execution of `add`
0000000000000002
{ 1 1 add } dump # a function object is created
<function: 00006000005D5600>
{ 1 1 add } * dump
0000000000000002
The function quoted returns the current quote level and can be very useful for creating macros. For now however, let's just
use it to make the example a little clearer.
{ "===" dump quoted dump _ dump } /debug defq # define a helper function showing current quote level and stack top
# note that this function has quote execution mode
1 debug 1 debug add debug
"==="
0000000000000000
0000000000000001
"==="
0000000000000000
0000000000000001
"==="
0000000000000000
0000000000000002
{ 1 debug 1 debug add debug } debug
"==="
0000000000000001 # inside the { the quote level is 1
0000000000000001 # constants are still pushed literally
"==="
0000000000000001
0000000000000001
"==="
0000000000000001
<function: 000060000062DEA0> # but the add has been pushed as a function
"==="
0000000000000000
<function: 00006000005CE300> # this is the resulting function object
{ { 1 1 debug } debug } * dump
"==="
0000000000000002 # the nested { resulted in a quote level of 2
0000000000000001
"==="
0000000000000001
<function: 00006000006A4250> # this is the output of debug, showing a function object 6A4250...
<function: 00006000006A6EE0> # which is different from the one ultimately representing { 1 1 }
Why are the two function objects in the last example different? Consider repeated executions of { { 1 1 } }. Each invokation of the outer
function should result in a new inner function object. Hence it is not pushed as a literal, but a function object is created (for inclusion
in the outer braces) which will create a new inner function object on each execution. In principle, these implicitely created function
objects can be assigned to variables and executed like all others.
{ 3 } =*value # define a function always returning 3
{ } =*f # predefine a function variable
{ _ =f } /get defq # grab a function object into f
{ value get } -- # f now contains the function resolving value
value dump
0000000000000003
<
{ 5 } /value deff # redefine value, always return 5
f dump
0000000000000005 # notice how the new value is being used
>
value dump # and the global one again gets resolved
0000000000000003
How instructions are assembled
How exactly does } assemble instructions? First it searches the stack for the topmost function-quote-begin marker. From there on towards
the stack top, each element is considered in turn. If it is a literal (i.e. integer, string or float) an instruction is created pushing
the same literal. If it is a function object, an instruction is created calling that function (if necessary switching the current scope to
the captured scope of the function beforehand and back after execution).
Additionally, } adds a function header and footer to the instruction sequence. The header creates a new scope for the duration of the
execution, while the footer switches back to the earlier scope again. This effectively results in local variable semantics for newly
defined variables.
What a function object contains
Each function object can refer to * its instruction sequence * its captured scope * its expected input and output arguments
When a function starts executing, the current scope is switched to the captured scope. Afterwards the instruction sequence will create a new scope as a child of this now current scope. At the end of the function this child scope is exited. Afterwards the calling function will switch the scope back to where it was before execution began.
The function }' and }" allow construction of function objects with less scoping effects. This can be useful to create functions with
unusal effects with respect to scopes. While }' creates a function which captures the enclosing scope but does not create a new child
scope, }" neither captures the scope nor creates a new child. In effect the contents of }' will execute in the parent scope of the
function definition while the contents of }" will execute in the scope of the calling site.
<
{ == }' /set deff # capture enclosing scope and execute == within it
{ == }" /SET deff # capture nothing
> ==s
s keys dump
[
"set"
"SET"
]
0 /foo s .set # execute set, i.e. execute == in the scope where { == } was written
s keys dump
[
"set"
"SET"
"foo"
]
0 /bar s .SET # execute SET, i.e. execute == in the current scope
s keys dump
[
"set"
"SET"
"foo"
]
bar dump # bar has been defined in the global scope
0000000000000000
}_
The }_ function is a macro defined in terms of }. It first creates a function just as }. Afterwards it also consumes the stack top
element and combines it with the just created function object into a new function object which pushes the captured stack element before
executing the created function.
{ { add }_ } /makeAdder deff
5 makeAdder /addFive deff
3 addFive dump
0000000000000008
Understanding the source code of }_ in compiler/standerd.ey is actually a very worthwile exercise for understanding the quoting rules
of elymas.
