ebisp/interpreter: Use closed-over environment as basis for lambda calls
* Instead of extending our caller Scope with a frame containing
the arguments for the new function, create a new Scope from the
environment stored in the lambda and extend that with the new
frame.
* This is a MAJOR semantic change to the language. Because the
caller environment is not passed to the callee, we end up with
lexical (instead of dynamic) scope (issue #338). Because we use
the environment stored in the lambda, we end up with closures
(issue #620). And because the environment mutates its value cells,
the closures are mutable as well.
* ((lambda (x) (defun foo () (set x (+ x 1)))) 0) now creates a
global function called "foo" that returns successive integers from
1 each time it's called. And doesn't leak unreclaimable memory
each time it's called.
* First, by mutating existing CONSes ("value cells") when we set
a value in a scope with an existing binding, we prevent runaway
allocation of un-collectable garbage. If we simply shadow the
existing binding then it can't be reclaimed until the new binding
is discarded, and at global scope that means that it can't be
reclaimed until the global scope is destroyed.
* Second, by preserving the "spine" of the environment list, we
can "close over" that environment by taking a reference to the
"spine", and can view all subsequent changes (outside of a new
binding being added by push_scope_frame()).
* There seems to be an impossible case in set_scope_value_impl():
scope.expr is initialized to a CONS and only possibly changed to
something else by pop_scope_frame(), which is only used in a pair
with push_scope_frame(), thus having no net effect on the type.
So how can it ever not be a CONS?
ebisp/: Use ATOM_LAMBDA instead of bare "lambda" forms
* First off, this gives us faster lambda_p(). No more parsing
list structure to determine if we have a lambda each time we call
a function, it becomes a simple type-test.
* Second, this now gives us a single mechanism for creating
lambdas, making it easier to modify the structure of a lambda in
the future (such as for storing a closure environment).
* Third, this is a semantic change to the language: We can no
longer construct a valid lambda using calls to (list), (quote), or
(quasiquote); they must always be created by (defun), (lambda), or
(λ).
ebisp/: Create ATOM_LAMBDA as a wrapper around an args_list and body
* This gives us a typed ATOM, with structure known to the GC,
that can contain the information currently associated with a
lambda.
* This is a lead-in to faster lambda_p(), to closures, and to
lexical scope.
* As a minor note, due to lambdas becoming closures in the
future, ATOM_LAMBDAs have "identity"; that is, they are only equal
to each other if they are the same object in memory.
ebisp/std: Validate arglist to defun in the same way as for lambda
* One one level, this is simply for parallelism, but on another
it covers one particular failure mode (a malformed arglist for a
named function) in a slightly cleaner manner.
ebisp/gc: Prevent stack blow-out in gc_traverse_expr() on cyclic structure
* If we manage to construct a cyclic list structure and then
attempt to run a GC while any part of the cycle is still "live",
gc_traverse_expr() was attempting to traverse the entire cycle to
the end before marking any given cell in the cycle as having been
visited.
* Mark cells as being visited before attempting to visit the
values to which they refer, causing gc_traverse_expr() to return
without further processing when it encounters a cycle.
* This might-or-might-not be issue #578. There's not enough
context in the issue description for me to be certain.