aboutsummaryrefslogtreecommitdiff
path: root/doc/scopes.md
blob: f9d92e0d9e4db7e8fc7a6b6a41c650505d4569d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
Scopes
======

A scope maps (variable) names to values. Each such binding also specifies how and when the referenced
object gets executed and what assumptions the optimizer may make about later values of the same name.
To create a mapping from a name to a value, various functions starting with `def` exist. Some of them
are aliased to `==` and similar functions.

They allow specifying four different execution modes:
* _v_alue: Upon dereference, the object is placed on top of the stack.
* _f_unction: Upon dereference, the object is executed (equivalently the object is placed on top of the stack, then `*` is executed).
* _m_ember: Upon dereference, the containing scope is pushed on the stack, then the object is executed.
* _q_uoting: The object is executed as soon as the name is encountered in the input stream, even if the parser is currently in quote
             mode.

They also allow specifying four different optimization guarantees:
* _s_tatic: The name always resides at the same scope slot. Scope slots are assigned deterministically. If the same set of variables
            is always declared in scopes encountered by a certain piece of code, then this piece of code can savely assume static
            names. This also guarantees that the execution mode of this name is always the same.
* _t_ype constant: The name always refers to the same type of object. If the variable held an integer once, it is required
                   to always hold an integer and so on. Arrays and functions are only type constant if they keep the same
                   function signature (i.e. same nesting depth in case of arrays).
* _c_onstant: The referenced object stays identical forever. This implies static and type constant.
* _d_eep constant: The referenced object and all objects reached through it (i.e. submembers in case of a scope) stay identical forever.
                   This implies constant.

No optimization guarantees can be specified for quoting execution mode, as optimization is not applied in this parsing stage. To allow `defq` from within nested scopes, `defq` itself has quoting execution mode.

The resulting function names are the concatenation of `def`, the desired execution mode character (`v`, `f`, `m`, `q`) and the
desired optimization guarantee (none, `s`, `t`, `st`, `c`, `d`).

This scheme results in 19 different functions. All of these functions take from the stack a name (on top of stack) and a value
to associate with the name.

    5 /five defv
    { "hi" dump } /greet deffst
    42 "ANSWER" defvd

Some of these functions are aliased, because they appear particularly useful:

* `==?` aliases `defv`, i.e. value definition without optimization guarantees
* `==` aliases `defvst`, i.e. value definition with static and type constness
* `==:` aliases `defvc`, i.e. value definition with constness
* `=*?` aliases `deff`, i.e. executable definition without optimization guarantees
* `=*` aliases `deffst`, i.e. executable definition with static and type constness
* `=*:` aliases `deffc`, i.e. executable definition with constness

The value associated with a name can be updated using the `=` function. It takes a name to update and the new value from the stack.

    0 ==i
    i 1 add =i
    i dump
    0000000000000001


Scope objects on the stack
--------------------------

There is always a current scope. This is the scope where lookup happens during code parsing and this is where the `def` function
family puts values. The current scope object can also be put on the stack using the `scope` function. All scope objects but the
global one have a single parent scope where lookup continues if a name can not be resolved in the scope itself.

The current scope can also be switched using `<` and `>`. `<` takes the current scope as the parent of a new scope which then
becomes current. `>` pushes the current scope to the stack and makes its parent the new current scope. This allows construction
of structured datatypes. To this end, the `.` function takes a name and a scope object from the stack and resolves the name in
the given scope object.

    <
      1 ==one
    > _ dump
    <scope: 00006000005DDAA0>
        .one dump
    0000000000000001

Function objects created by a `{`, `}` pair remember the scope they have been created in. Upon execution, they create a new
scope object which has this remembered scope as its parent. In effect, this results in closure semantics for function objects.

    <
      0 ==i
      { i dump i 1 add =i }
    > -- /dumpAndIncrement deffst
    dumpAndIncrement
    0000000000000000
    dumpAndIncrement
    0000000000000001
    dumpAndIncrement
    0000000000000002

Sometimes it's useful to assign a different parent pointer than the current scope to a new scope object. This can be
achieved by the `>'` function. It behaves like `>` but takes the parent pointer of the new object from the stack.

    <
      0 ==i
    > ==parent
    <
      { i dump i 1 add =i }
      parent
    >' -- /dumpAndIncrement deffst
    dumpAndIncrement
    0000000000000000
    dumpAndIncrement
    0000000000000001
    dumpAndIncrement
    0000000000000002

As `>'` only assigns the parent pointer when the scope stops being the current scope, before its execution the parent
was set as usual, i.e. the current scope before `<`. This allows for interesting possibilities. Note that names in
quoted mode are only resolved during first execution.


Missing members
---------------

If a scope member is accessed, which is not available, the program will call `die` and be dead. Unless, that is,
that the scope happens to have `#.`, `#.|`, or `#.=` defined, which are used to dynamically handle missing members
on `.` (or quoted identifiers), on `|` and on `=` respectively. To fully fake missing members, there also exists the
possibility to declare `#.?` which will be invoked by `.?` when the member does not exist.

    <
      { ==memberName "#. called with: " memberName cat dump } "#." deffd
      { ==memberName "#.| called with: " memberName cat dump } "#.|" deffd
      { ==memberName "#.? called with: " memberName cat dump } "#.?" deffd
      { ==memberName ==value "#.= called with: " memberName cat dump value dump } "#.=" deffd
    > ==s
    s .hello_world
    "#. called with: hello_world"
    < { "value" =random_name } s >' -- *
    "#.= called with: random_name"
    "value"