"[Function.length is] the number of formal parameters. This number
excludes the rest parameter and only includes parameters before
the first one with a default value." - MDN
To make processing tagged template literals easier, template literals
will now add one empty StringLiteral before and after each template
expression *if* there's no other string - e.g.:
`${foo}` -> "", foo, ""
`test${foo}${bar}test` -> "test", foo, "", bar, "test"
This also matches the behaviour of many other parsers.
This now matches the output of
Program
(Variables)
...
(Children)
...
or
FunctionDeclaration 'foo'
(Parameters)
...
(Body)
...
etc.
Also don't print each consequent statement index, it doesn't add any
value.
Adds fully functioning template literals. Because template literals
contain expressions, most of the work has to be done in the Lexer rather
than the Parser. And because of the complexity of template literals
(expressions, nesting, escapes, etc), the Lexer needs to have some
template-related state.
When entering a new template literal, a TemplateLiteralStart token is
emitted. When inside a literal, all text will be parsed up until a '${'
or '`' (or EOF, but that's a syntax error) is seen, and then a
TemplateLiteralExprStart token is emitted. At this point, the Lexer
proceeds as normal, however it keeps track of the number of opening
and closing curly braces it has seen in order to determine the close
of the expression. Once it finds a matching curly brace for the '${',
a TemplateLiteralExprEnd token is emitted and the state is updated
accordingly.
When the Lexer is inside of a template literal, but not an expression,
and sees a '`', this must be the closing grave: a TemplateLiteralEnd
token is emitted.
The state required to correctly parse template strings consists of a
vector (for nesting) of two pieces of information: whether or not we
are in a template expression (as opposed to a template string); and
the count of the number of unmatched open curly braces we have seen
(only applicable if the Lexer is currently in a template expression).
TODO: Add support for template literal newlines in the JS REPL (this will
cause a syntax error currently):
> `foo
> bar`
'foo
bar'
Adds the ability for function arguments to have default values. This
works for standard functions as well as arrow functions. Default values
are not printed in a <function>.toString() call, as nodes cannot print
their source string representation.
This commit introduces a way to get an object's own properties in the
correct order. The "correct order" for JS object properties is first all
array-like index properties (numeric keys) sorted by insertion order,
followed by all string properties sorted by insertion order.
Objects also now print correctly in the repl! Before this commit:
courage ~/js-tests $ js
> ({ foo: 1, bar: 2, baz: 3 })
{ bar: 2, foo: 1, baz: 3 }
After:
courage ~/js-tests $ js
> ({ foo: 1, bar: 2, baz: 3 })
{ foo: 1, bar: 2, baz: 3 }
This patch teaches UpdateExpression how to use a Reference. Some other
changes were necessary to keep tests working:
A Reference can now also refer to a local or global variable. This is
not fully aligned with the spec since we don't have a Record concept.
Expression nodes can now be asked to produce a Reference. We then use
this to implement the "delete" operator without downcasting the child
node to a MemberExpression manually.
Implement the syntax and behavor necessary to support array literals
such as [...[1, 2, 3]]. A type error is thrown if the target of the
spread operator does not evaluate to an array (though it should
eventually just check for an iterable).
Note that the spread token's name is TripleDot, since the '...' token is
used for two features: spread and rest. Calling it anything involving
'spread' or 'rest' would be a bit confusing.
It turns out "delete" is actually a unary op :)
This patch implements deletion of object properties, it doesn't yet
work for casually deleting properties from the global object.
When deleting a property from an object, we switch that object to
having a unique shape, no longer sharing shapes with others.
Once an object has a unique shape, it no longer needs to care about
shape transitions.
JS::Value already has the empty state ({} or Value() gives you one.)
Use this instead of wrapping Value in Optional in some places.
I've also added Value::value_or(Value) so you can easily provide a
fallback value when one is not present.
- Let undefined variables throw a ReferenceError by using
Identifier::execute() rather than doing variable lookup manually and
ASSERT()ing
- Coerce value to number rather than ASSERT()ing
- Make code DRY
- Add tests
A MarkedValueList is basically a Vector<JS::Value> that registers with
the Heap and makes sure that the stored values don't get GC'd.
Before this change, we were unsafely keeping Vector<JS::Value> in some
places, which is out-of-reach for the live reference finding logic
since Vector puts its elements on the heap by default.
We now pass all the JavaScript tests even when running with "js -g",
which does a GC on every heap allocation.
Everyone who constructs an Object must now pass a prototype object when
applicable. There's still a fair amount of code that passes something
fetched from the Interpreter, but this brings us closer to being able
to detach prototypes from Interpreter eventually.
Let's start moving towards native JS objects taking their prototype as
a constructor argument.
This will eventually allow us to move prototypes off of Interpreter and
into GlobalObject.
This patch replaces the old variable lookup logic with a new one based
on lexical environments.
This brings us closer to the way JavaScript is actually specced, and
also gives us some basic support for closures.
The interpreter's call stack frames now have a pointer to the lexical
environment for that frame. Each lexical environment can have a chain
of parent environments.
Before calling a Function, we first ask it to create_environment().
This gives us a new LexicalEnvironment for that function, which has the
function's lexical parent's environment as its parent. This allows
inner functions to access variables in their outer function:
function foo() { <-- LexicalEnvironment A
var x = 1;
function() { <-- LexicalEnvironment B (parent: A)
console.log(x);
}
}
If we return the result of a function expression from a function, that
new function object will keep a reference to its parent environment,
which is how we get closures. :^)
I'm pretty sure I didn't get everything right here, but it's a pretty
good start. This is quite a bit slower than before, but also correcter!
Since declarations are now hoisted and handled on scope entry, the job
of a VariableDeclaration becomes to actually initialize variables.
As such, we can remove the part where we insert variables into the
nearest relevant scope. Less work == more speed! :^)
"var" declarations are hoisted to the nearest function scope, while
"let" and "const" are hoisted to the nearest block scope.
This is done by the parser, which keeps two scope stacks, one stack
for the current var scope and one for the current let/const scope.
When the interpreter enters a scope, we walk all of the declarations
and insert them into the variable environment.
We don't support the temporal dead zone for let/const yet.
Many other parsers call it with this name.
Also Type can be confusing in this context since the DeclarationType is
not the type (number, string, etc.) of the variables that are being
declared by the VariableDeclaration.
The PropertyName class able to match a number or an array can only
accept positive numerical values. However, the computed_property_name
method sometimes returned negative values.
This commit also adds a basic object access test case.
This patch adds a new kind of JS::Value, the empty value.
It's what you get when you do JSValue() (or most commonly, {} in C++.)
An empty Value signifies the absence of a value, and should never be
visible to JavaScript itself. As of right now, it's used for array
holes and as a return value when an exception has been thrown and we
just want to unwind.
This patch is a bit of a mess as I had to fix a whole bunch of code
that was relying on JSValue() being undefined, etc.
Now that we have two separate storages for Object properties depending
on what kind of index they have, it's nice to have an abstraction that
still allows us to say "here's a property name".
We use PropertyName to always choose the optimal storage path directly
while interpreting the AST. :^)
This patch adds support in the parser and interpreter for this:
var a = 1, b = 2, c = a + b;
VariableDeclaration is now a sequence of VariableDeclarators. :^)
There is no such thing as a "undefined literal" in JS - undefined is
just a property on the global object with a value of undefined.
This is pretty similar to NaN.
var undefined = "foo"; is a perfectly fine AssignmentExpression :^)
Let's move towards using references over pointers in LibJS as well.
I had originally steered away from it because that's how I've seen
things done in other engines. But this is not the other engines. :^)
This adds Function::construct() for constructor function calls via `new`
keyword. NativeFunction doesn't have constructor behaviour by default,
ScriptFunction simply calls call() in construct()
Native functions now only get the Interpreter& as an argument. They can
then extract |this| along with any indexed arguments it wants from it.
This forces functions that want |this| to actually deal with calling
interpreter.this_value().to_object(), and dealing with the possibility
of a non-object |this|.
This is still not great but let's keep massaging it forward.
This operator walks the prototype chain of the RHS value and looks for
a "prototype" property with the same value as the prototype of the LHS.
This is pretty cool. :^)
NewExpression mostly piggybacks on the existing CallExpression. The big
difference is that "new" creates a new Object and passes it as |this|
to the callee.
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! :^)
- 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.
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.
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. :^)
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)
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, 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! :^)
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.