At any point in the program’s execution, there is a lexical and a dynamic scope. Both of the scopes are first-class objects in Latitude. The scope objects are stored in register memory.
A lexical scope object’s parent is the enclosing lexical scope. In most cases, this is the lexical scope of the method or module in which the current scope was defined initially. A dynamic scope object’s parent is the caller’s dynamic scope. A scope object’s parent can be modified freely. Doing so will not change the internal call stack, so, for example, changing a dynamic scope object’s parent does not change the caller’s dynamic scope; it merely changes what dynamic variables are inherited into the current scope.
Scope objects should always be traditional objects. The consequences
are undefined if a scope object is made to be part of an inheritance
hierarchy that does not contain Object
.
At the top-level, there is a global scope object. This global scope
object has a global
slot that contains itself.
The act of looking up a slot in the associative array of an object is simply referred to as directly retrieving a slot from the object.
When an identifier is preceded by an expression, the expression is evaluated, and the symbol corresponding to the identifier is fully retrieved from the result of the expression. If the identifier is not preceded by an expression, the object on which the lookup is performed is one of the two scope objects, depending on the identifier.
$
), then the lookup
is performed on the current dynamic scope object.There is a slot on every traditional object that is named meta
. This
is referred to as that object’s meta. An object’s meta defines several
properties about that object’s behavior. For most ordinary
programming, the meta object should not need to be modified, but
changing certain meta properties may be advantageous for those who are
interested in changing properties of the language. Slots on the meta
object can be modified to change these behaviors, but new slots should
not be added by the user.
Meta objects should always be traditional objects, and they should
always have exactly the slots defined on them that Object
’s meta has
defined. As such, if a user wishes to modify a particular object’s
meta, it makes logical sense to clone the parent’s meta object and
then modify the clone.
Currently, only the current lexical scope’s meta object is used. However, the meta object may be expanded in the future to have more effects.
A full retrieval, or full lookup, of a slot is a more sophisticated action than a direct retrieval. A full retrieval is often simply called a retrieval, as it is the more common case than direct retrieval.
When fully retrieving a slot corresponding to an identifier from an object, that object is first checked for direct retrieval. If a slot corresponding to the given identifier exists in the current object, then that slot is the result of the full lookup. If it does not exist, the same identifier is sought on the parent object, and then on its parent, and so on until the slot is found or a cycle in parents is detected. If the slot is found in this process, then that slot is the result of the full lookup.
If seeking the slot by looking in successive parents fails, then a
slot with the name missing
is looked up, using the same
rules. First, missing
is sought on the current object, then on the
parent, then the parent’s parent, and so on until the slot is found or
a cycle is detected. If the missing
slot is found, it is called with
the name of the original slot as an argument. The result of this call
is the result of the full lookup.
If neither the slot nor missing
exists, then the current lexical
scope’s meta object is used. A slot called missed
is sought on the
lexical scope’s meta object, then on its parent, and so on until the
slot is found or a cycle is detected. If such a slot is found, it is
called with no arguments, and its result is used as the result of the
full lookup.
missed
on the current lexical meta object should always exist. If
all of the above attempts fail, then the behavior is undefined.
Objects, usually but not necessarily methods, can be called. A call is always performed with a caller and a called object, as well as a possibly empty argument list. When an object is called, several steps are performed. First, the arguments are evaluated in order and placed in register memory.
Next, the slot by the name of closure
is directly retrieved from the
called object. If closure
does not exist or if the object is not an
evaluating object, then the called object is the return value. If the
object is an evaluating object and has closure
, the call continues.
The current dynamic scope is cloned to create a new dynamic scope for
the call. The closure (obtained from the closure
slot of the
evaluating object) is cloned to create a new lexical scope for the
call. Then several variables are bound in the new scopes.
self
is bound to the caller. again
is bound to the called
object. caller
is bound to the caller’s lexical scope. These
bindings all occur in the newly created lexical scope object.All of the bindings created at this time are assign-protected and
delete-protected. Next, the arguments are bound to variables. Note
that the arguments are not protected and can be reassigned or
deleted freely by the programmer. The first argument is bound to $1
,
the second to $2
, and so on. If there are ten or more arguments, the
latter arguments will be bound to multi-digit variable names, such as
$10
or even $100
.
[TODO: Perhaps implementations should be allowed to restrict the argument count.]
Finally, the body of the method is evaluated in order, in the context of the new lexical and dynamic scopes. The final statement in the method determines its return value. If the method is empty, the return value is the special nil object.
When arguments in an argument list are evaluated, they are evaluated in applicative order. That is, the first argument is evaluated first, then the second, and so on until all the arguments have been evaluated. As they are evaluated, the accumulating results are stored in register memory.
Note that a short argument list can either be an argument list enclosed in parentheses, a single chain expression enclosed in parentheses, or a literal expression. In the latter two cases, the argument list is considered to contain one element: the chain expression or the literal expression.
A chain consists of a series of terms which are either identifiers or identifiers followed by argument lists. In some circumstances, a chain is allowed to be empty, but in others it may be required to have at least some contents.
Chains are always evaluated left-to-right. Each term of the chain has access to the evaluation of the entire chain to the left of it.
The first term of a chain (and only the first term) is allowed to be a quasi-literal expression. In this case, the first term is evaluated as a quasi-literal expression, not according to the chain rules.
An empty chain evaluates to itself: an empty chain. Note that this is a special syntactic construct that is not equivalent to any Latitude object; in particular, an empty chain does not evaluate to the nil object.
When a term of a chain is encountered, the name is fully retrieved as a slot in the object returned by the left-hand-side chain. If the left-hand-side chain is empty, the slot is looked up in an object determined by the rules of Slot Lookup above. In either case, the resulting slot’s object is called with the argument list supplied. If no argument list is supplied, the object is called with an empty argument list. The caller is considered to be the left-hand-side chain’s object, or the appropriate scope object.
There are several kinds of statements, which will be enumerated here. A statement that consists only of a quasi-literal expression will be evaluated as a quasi-literal expression. The other statement types are detailed below.
A method invocation consists of a chain followed by a name, followed
optionally by either a short argument list or a full argument list. In
the latter case, the full argument list must be preceded by a colon
(:
). The chain is evaluated, then the name is fully retrieved as a
slot on the chain object. The result of the lookup is then called with
the argument list as arguments. The return value from the method is
the return value of the invocation.
During the course of evaluating a method invocation, the evaluated chain object should be stored in register memory.
An assignment operation consists of a chain, then a name, then either
a colon-equals (:=
) or a double-colon-equals (::=
), followed by
another statement. The chain is evaluated, then the right-hand-side
statement. Then the slot with the given name on the chain object is
set to the value indicated by the right-hand-side statement. If the
slot does not exist, it is created. If it exists in a parent object,
it is overriden in the child only. If it exists in the chain object
itself, it is modified to the new value.
In the colon-equals case, the return value of the assignment operation
is the return value of the right-hand-side, after being assigned to
the new slot. In the double-colon-equals case, after the assignment is
made, the slot with the special name ::
is fully retrieved from the
object returned by the right-hand-side statement. This slot is then
called, with the right-hand-side object as the caller and with the
name to which it was assigned as the only argument. The name will be
passed as a symbol object. The return value of the ::
call is the
return value of the assignment in this case.
During the course of evaluating an assignment operation, the evaluated chain object should be stored in register memory.
An assignment method invocation consists of a chain, followed by a
name, then optionally a short argument list, then an equal-sign (=
),
and then a single statement. First, the chain is evaluated. Then the
right-hand-side statement is appended to the end of the argument
list. Then the name has an equal-sign appended to it, and the slot
with the new name is fully retrieved from the chain object. That
object is then called with the modified argument list, using the chain
object as the caller. The return value of the statement will be result
of the call.
Specifically, a statement of the form
className methodName (a, b, c) = d.
will behave equivalently to
className methodName= (a, b, c, d).
During the course of evaluating an assignment operation, the evaluated chain object should be stored in register memory.
A bind operation consists of a chain, followed by a name, then the
special token <-
, and finally a single statement. First, the chain
is evaluated. Then the name (as a symbol object) and the
right-hand-side are combined to create a 2-element argument list. The
slot with the name <-
is fully retrieved from the chain object. If
the chain is empty, then the slot is fully retrieved from the lexical
scope instead. The resulting object is called with the 2-element
argument list, using the chain object or the lexical scope as the
caller. The return value of the statement will be the result of the
call.
[up]
[prev - Chapter 3 - Object Model]
[next - Chapter 5 - Literals]