We were interpreting "undefined" as a variable lookup failure in some
cases and throwing a ReferenceError exception instead of treating it
as the valid value "undefined".
This patch wraps the result of variable lookup in Optional<>, which
allows us to only throw ReferenceError when lookup actually fails.
You can now throw an expression to the nearest catcher! :^)
To support throwing arbitrary values, I added an Exception class that
sits as a wrapper around whatever is thrown. In the future it will be
a logical place to store a call stack.
You can now throw exceptions by calling Interpreter::throw_exception().
Anyone who calls ASTNode::execute() needs to check afterwards if the
Interpreter now has an exception(), and if so, stop what they're doing
and simply return.
When catching an exception, we'll first execute the CatchClause node
if present. After that, we'll execute the finalizer block if present.
This is unlikely to be completely correct, but it's a start! :^)
We can now handle scripts with if/else in LibJS. Most of the changes
are about fixing IfStatement to store the consequent and alternate node
as Statements.
Interpreter now also runs Statements, rather than running ScopeNodes.
When the Heap is going down, it's our last chance to run destructors,
so add a separate collector mode where we simply skip over the marking
phase and go directly to sweeping. This causes everything to get swept
and all live cells get destroyed.
This way, valgrind reports 0 leaks on exit. :^)
Object now has virtual get_own_property() and put_own_property() member
functions that can be overridden to provide custom behavior.
We use these virtuals to move Array-specific access behavior to Array.
- move() the property map when constructing ObjectExpression instead of
making a copy.
- Use key+value iterators to traverse the property map in the execute()
and dump() functions.
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!");
});