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 .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"