This function is ultimately supposed to be generic and allow any |this|
that has a length property, but for now it only works on our own Array
object type.
I'm not completely thrilled about Object::get() and Object::put() doing
special-case stuff for arrays, and we should probably come up with a
better abstraction for it.
But at least it works for now, which is really nice. :^)
This is pretty naive, we just walk up the prototype chain and call any
NativeProperty setter that we find. If we don't find one, we put/set
the value as an own property of the object itself.
FunctionExpression is mostly like FunctionDeclaration, except the name
is optional. Share the parsing logic in parse_function_node<NodeType>.
This allows us to do nice things like:
document.addEventListener("DOMContentLoaded", function() {
alert("Hello friends!");
});
This is pretty heavy and unoptimized, but it will do the trick for now.
Basically, Heap now has a HashTable<HandleImpl*> and you can call
JS::make_handle(T*) to construct a Handle<T> that guarantees that the
pointee will always survive GC until the Handle<T> is destroyed.
Now that Interpreter keeps all arguments in the CallFrame stack, we can
just pass a const-reference to the CallFrame's argument vector to each
function handler (instead of copying it.)
This patch adds a CallFrame stack to Interpreter, which keeps track of
the "this" value and all argument values passed in function calls.
Interpreter::gather_roots() scans the call stack, making sure that all
argument values get marked. :^)
We now scan the stack and CPU registers for potential pointers into the
GC heap, and include any valid Cell pointers in the set of roots.
This works pretty well but we'll also need to solve marking of things
passed to native functions, since those are currently in Vector<Value>
and the Vector storage is on the heap (not scanned.)
This commits makes effort towards tolerating some of javascript's quirks
when it comes to its type system, note that the interpreter's way of
handling type coercion is still not mature at all, for example, we still
have to implement NaN instead of just crashing when trying to parse a
string and failing.
Instead of every NativeFunction callback having to ask the Interpreter
for the current "this" value and then converting it to an Object etc,
just pass "this" as an Object* directly.
This patch adds NativeProperty, which can be used to implement object
properties that have C++ getters and/or setters.
Use this to move String.prototype.length to its correct place. :^)
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.
This patch adds String.prototype.charAt() to demonstrate that prototype
property lookup works, and that you can call a prototype function on an
object, and it will do what you expect. :^)
Object will now traverse up the prototype chain when doing a get().
When a function is called on an object, that object will now also be
the "this" value inside the function. This stuff is probably not very
correct, but we will improve things as we go! :^)
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 patch makes all HeapBlock allocations aligned to their block size,
enabling us to find the HeapBlock* for a given Cell* by simply masking
bits off of the cell address.
Use this to make a simple Heap& getter for Cell, which lets us avoid
plumbing the Heap& everywhere.
Both types of functions are now Function and implement calling via:
virtual Value call(Interpreter&, Vector<Value> arguments);
This removes the need for CallExpression::execute() to care about which
kind of function it's calling. :^)
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.
Remove the need to construct a full Value during parsing. This means
we don't have to worry about plumbing the heap into the parser.
The Literal ASTNode now has a bunch of subclasses that synthesize a
Value on demand.
This adds a basic Javascript lexer and parser. It can parse the
currently existing demo programs. More work needs to be done to
turn it into a complete parser than can parse arbitrary JS Code.
The lexer outputs tokens with preceeding whitespace and comments
in the trivia member. This should allow us to generate the exact
source code by concatenating the generated tokens.
The parser is written in a way that it always returns a complete
syntax tree. Error conditions are represented as nodes in the
tree. This simplifies the code and allows it to be used as an
early stage parser, e.g for parsing JS documents in an IDE while
editing the source code.:
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`.
The above snippet is a MemberExpression that necessitates the implicit
construction of a StringObject wrapper around a PrimitiveString.
We then do a property lookup (a "get") on the StringObject, where we
find the "length" property. This is pretty neat! :^)
Don't visit cells that are already marked. This prevents the marking
phase from looping forever when two cells refer to each other.
Also do the marking directly from the CellVisitor, removing another
unnecessary phase of the collector. :^)
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.
Do note that when it comes to evaluating binary expressions, we are
asserting in multiple contexts that the values we're operating on are
numbers, we should probably handle other value types to be more tolerant
in the future, since for example, adding a number and a string, in
which case the number is converted to a string implicitly which is then
concatenated, although ugly, is valid javascript.
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! :^)
If statements execute a certain action or an alternative one depending
on whether the tested condition is true or false, this commit helps
establish basic control flow capabilities in the AST.
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. :^)