To make sure that everyone has the same instance of StringPrototype,
hang a global prototype off of the Interpreter that can be fetched
when constructing new StringObjects.
We now evaluate for loops in their own scope if their init statement is
a lexical declaration.
Evaluating for loops in their own scope allow us to obtain expected
behaviour, which means for example, that the block-scoped variables
declared in a for statement will be limited to the scope of the for
loop's body and statement and not to that of the current scope (i.e the
one where the for statement was made)
Previously, we were allowing the redeclaration of a variable with `let`
or `const` if it was declared initially using `var`, we should not
tolerate any form of variable redeclaration using let/const.
This can be used to implement arbitrary functionality, callable from
JavaScript.
To make this work, I had to change the way CallExpression passes
arguments to the callee. Instead of a HashMap<String, Value>, we now
pass an ordered list of Argument { String name; Value value; }.
This patch includes a native "print(argument)" function. :^)
This also tightens the means of redeclaration of a variable by proxy,
since we now have a way of knowing how a variable was initially
declared, we can check if it was declared using `let` or `const` and
not tolerate redeclaration like we did previously.
Previously objects were the only heap
allocated value. Now there are also strings.
This replaces a usage of is_object with is_cell.
Without this change strings could be garbage
collected while still being used in an active scope.
Previously, we were assuming all declared variables were bound to a
block scope, now, with the addition of declaration types, we can bind
a variable to a block scope using `let`, or a function scope (the scope
of the inner-most enclosing function of a `var` declaration) using
`var`.
It's now possible to assign expressions to variables. The variables are
put into the current scope of the interpreter.
Variable lookup follows the scope chain, ending in the global object.
Objects can now be allocated via the interpreter's heap. Objects that
are allocated in this way will need to be provably reachable from at
least one of the known object graph roots.
The roots are currently determined by Heap::collect_roots().
Anything that wants be collectable garbage should inherit from Cell,
the fundamental atom of the GC heap.
This is pretty neat! :^)
I always tell people to start building things by working on the thing
that seems the most interesting right now. The most interesting thing
here was an AST + simple interpreter, so that's where we start!
There is no lexer or parser yet, we build an AST directly and then
execute it in the interpreter, producing a return value.
This seems like the start of something interesting. :^)