### Setter semantics

Dusk OS defines the immediate word "to" along with what is called "setter
semantics". Any word, not only values, can have a setter routine. That setter is
activated when "to" is called right before invoking it. This works at compile
time too. For values, "to" is the exact equivalent to "!", but more efficient
because we don't push and pop a destination address to stack. For other words,
it might have another meaning. For example, [drv/pc/ioport] uses setter
semantics to compile x86 OUT instructions.

To implement a setter, you use "to?," like this:

  :> ."getting!" ;
  :> ."setting!" ;
  code foo findselector
    litn to?, ifz, swap i) @, then exit,
  foo \ prints "getting!"
  to foo \ prints "setting!"
  : bar foo to foo ;
  bar \ prints "getting!setting!"

## value

These semantics allow the concept of "value", which is like a "variable", but
calling the word automatically dereferences the address, which saves a whole
bunch of "@" calls. When you need to write to a value, use "to".

Values are compatible with Big Moustache [lib/bm] and can be used with the ">"
modifier.

## Local variables

It's a common pattern, to avoid PS juggling, to place an element on RS (Return
Stack) and recall that element with r@ or r>. It works well, but unfortunately,
this only works with a single element. Another drawback is that this element can
be "buried" by a pattern (a loop for example) that needs to place something on
RS. Example:

  : print100times ( n -- ) >r 100 0 do r@ . spc> loop rdrop ;

is not going to work as expected because of the loop's usage of a RS slot.

A common workaround to this is to declare a "private" value near the word and
store temporary values in there.

  0 value myval
  : print100times ( n -- ) to myval 100 0 do myval . spc> loop ;

It works too, but because those values are statically allocated, this can't be
done in words that require recursion. Also, it's a bit verbose.

Local variables are there to the rescue! There are 4 of them: V1 V2 V3 V4. They
access RS like r@ does, from a point of reference, which is often call "stack
frame pointer" in other computing environment. It's where RSP was when the word
began its execution. We keep track of RS movement at compile time through the
"[rcnt]" variable, which allow us to compile the proper RS-relative accesses.

V1 is the "deepest" variable, V4 is the "shallowest". For example, let's imagine
this word:

  : foo 1 >r 2 >r 3 >r 4 >r V1 . 2rdrop 2rdrop ;

This snippet will print "1" because at this point, RS looks like this, stack
growing left:

  4    3    2    1    ...
  R+0  R+4  R+8  R+12

Unlike words using static variables, words using variables can recurse just
fine. They are also impervious to being "buried" because they remember their
"slot" at compile time and adjust their compilation accordingly. For example,
the code below:

  : foo 1 >r 2 >r 3 >r 4 >r 10 0 do V1 . loop 2rdrop 2rdrop ;

will print "1" 10 times even though "do..loop" pushes 2 elements to RS.

When using local variables, you are responsible for pushing and popping to/from
RS.

Local variables obey setter semantics and are fully accessible to moustaches
[lib/bm]. More examples:

  : foo ( a b c -- ) >r >r >r V1 . spc> V2 . spc> V3 . 2rdrop rdrop ;

  1 2 3 foo \ prints "3 2 1"

  : inc ( a -- a+1 ) >r +{>V1 !#1} V1 rdrop ;
  42 inc . \ prints 43

  \ this works too
  : inc5 ( a -- a+5 ) >r 5 0 do +{>V1 !#1} loop V1 rdrop ;
  42 inc5 . \ prints 47

### Manual [rcnt] adjustments

The "R counter" that determines local variable slots at compile time is
oblivious to conditional code or loops. It's not common to have code that
conditionally maintain separate RS levels (they always need to stay balanced, of
course), but it can happen. For example, in early "exit" paths, we often have to
include a few "rdrop" before the "exit" call. If you have a fancy word that
returns a variable number of arguments, you might end up with >r and r> in
loops. All of this messes up the "R counter". You can manually adjust it through
the [rcnt] variable. For example, if you want your next ">r" to push to V1, you
would precede it with "[ 0 [rcnt] ! ]". Example case:

  : foo ( n -- )
    dup >r 42 = if rdrop ."answer to universe" exit then
    \ The conditional "rdrop" messed with [rcnt], we need manual adjustment
    [ -4 [rcnt] ! ]
    V1 . rdrop ;

## getsetter,

When implementing a typical setter words, you often end up with a findselector
word that has to support many possibilities:

* scry
* interpret-time get
* interpret-time set
* compile-time get
* compile-time set

If you follow the instructions above, that's quite some boilerplate you have to
carry around. To reduce this boilerplate, we have "getsetter,", which has this
signature:

  ( settercompilerxt setterxt gettercompilerxt getterxt scryval -- )

You supply it with the correct values and it generates an immediate findselector
for you, which will yield the correct values depending on values SCRYING, to?
and FINDCOMPILER. For example, here's the implementation of "value":

  : _do@, dup, m) @, ;
  : _do!, m) !, drop, ;
  : value here# n"VALU" , swap , ( valu )
    code> swap dup 4+ litn ['] _do!, bbr,
    code> swap dup 4+ _do!, exit,
    code> swap dup 4+ litn ['] _do@, bbr,
    code> swap dup 4+ _do@, exit,
    getsetter, ;

The first code> is the setter compiler xt, which is what happens when we invoke
the value with "to" activated and during compilation. It will do, at *compile
time*, the equivalent of "valueaddr m) !, drop,".

The second code> is the same thing, but for interpret time. So instead of
compiling "valueaddr m) !, drop,", it calls a word that has compiled this very
code already.

The other two code> are the like the first, but for when "to" is not activated.

Finally, on top of PS, we end up with a pointer to the *tagged* [doc/usage/tag]
address of our value, which allows units like [lib/bm] to do introspection on
that value and compile moustaches for it.
