summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibJS/AST.cpp9
-rw-r--r--Libraries/LibJS/Forward.h1
-rw-r--r--Libraries/LibJS/Interpreter.cpp62
-rw-r--r--Libraries/LibJS/Interpreter.h19
-rw-r--r--Libraries/LibJS/Makefile1
-rw-r--r--Libraries/LibJS/Runtime/Function.h1
-rw-r--r--Libraries/LibJS/Runtime/LexicalEnvironment.cpp64
-rw-r--r--Libraries/LibJS/Runtime/LexicalEnvironment.h64
-rw-r--r--Libraries/LibJS/Runtime/NativeFunction.h1
-rw-r--r--Libraries/LibJS/Runtime/ScriptFunction.cpp35
-rw-r--r--Libraries/LibJS/Runtime/ScriptFunction.h5
11 files changed, 228 insertions, 34 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index f6a2910bb2..ada01e8917 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -48,14 +48,14 @@ Value ScopeNode::execute(Interpreter& interpreter) const
Value FunctionDeclaration::execute(Interpreter& interpreter) const
{
- auto* function = interpreter.heap().allocate<ScriptFunction>(name(), body(), parameters());
+ auto* function = interpreter.heap().allocate<ScriptFunction>(name(), body(), parameters(), interpreter.current_environment());
interpreter.set_variable(name(), function);
return js_undefined();
}
Value FunctionExpression::execute(Interpreter& interpreter) const
{
- return interpreter.heap().allocate<ScriptFunction>(name(), body(), parameters());
+ return interpreter.heap().allocate<ScriptFunction>(name(), body(), parameters(), interpreter.current_environment());
}
Value ExpressionStatement::execute(Interpreter& interpreter) const
@@ -119,6 +119,7 @@ Value CallExpression::execute(Interpreter& interpreter) const
auto& call_frame = interpreter.push_call_frame();
call_frame.function_name = function.name();
call_frame.arguments = move(arguments);
+ call_frame.environment = function.create_environment();
Object* new_object = nullptr;
Value result;
@@ -134,11 +135,11 @@ Value CallExpression::execute(Interpreter& interpreter) const
result = function.call(interpreter);
}
+ interpreter.pop_call_frame();
+
if (interpreter.exception())
return {};
- interpreter.pop_call_frame();
-
if (is_new_expression()) {
if (result.is_object())
return result;
diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h
index 6de9b6ce1f..2330542171 100644
--- a/Libraries/LibJS/Forward.h
+++ b/Libraries/LibJS/Forward.h
@@ -61,6 +61,7 @@ class HandleImpl;
class Heap;
class HeapBlock;
class Interpreter;
+class LexicalEnvironment;
class PrimitiveString;
class ScopeNode;
class Shape;
diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp
index 22f8489d87..4dbd06bdf4 100644
--- a/Libraries/LibJS/Interpreter.cpp
+++ b/Libraries/LibJS/Interpreter.cpp
@@ -34,6 +34,7 @@
#include <LibJS/Runtime/ErrorPrototype.h>
#include <LibJS/Runtime/FunctionPrototype.h>
#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/NumberPrototype.h>
#include <LibJS/Runtime/Object.h>
@@ -66,6 +67,16 @@ Interpreter::~Interpreter()
Value Interpreter::run(const Statement& statement, ArgumentVector arguments, ScopeType scope_type)
{
+ if (statement.is_program()) {
+ if (m_call_stack.is_empty()) {
+ CallFrame global_call_fram;
+ global_call_fram.this_value = m_global_object;
+ global_call_fram.function_name = "(global execution context)";
+ global_call_fram.environment = heap().allocate<LexicalEnvironment>();
+ m_call_stack.append(move(global_call_fram));
+ }
+ }
+
if (!statement.is_scope_node())
return statement.execute(*this);
@@ -91,6 +102,11 @@ Value Interpreter::run(const Statement& statement, ArgumentVector arguments, Sco
void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector arguments, ScopeType scope_type)
{
+ if (scope_type == ScopeType::Function) {
+ m_scope_stack.append({ scope_type, scope_node, false });
+ return;
+ }
+
HashMap<FlyString, Variable> scope_variables_with_declaration_kind;
scope_variables_with_declaration_kind.ensure_capacity(16);
@@ -107,13 +123,30 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector argume
scope_variables_with_declaration_kind.set(argument.name, { argument.value, DeclarationKind::Var });
}
- m_scope_stack.append({ scope_type, scope_node, move(scope_variables_with_declaration_kind) });
+ bool pushed_lexical_environment = false;
+
+ if (scope_type != ScopeType::Function) {
+ // only a block, but maybe it has block-scoped variables!
+ if (!scope_variables_with_declaration_kind.is_empty()) {
+ auto* block_lexical_environment = heap().allocate<LexicalEnvironment>(move(scope_variables_with_declaration_kind), current_environment());
+ m_call_stack.last().environment = block_lexical_environment;
+ pushed_lexical_environment = true;
+ }
+ } else if (scope_type == ScopeType::Function) {
+ for (auto& it : scope_variables_with_declaration_kind) {
+ current_environment()->set(it.key, it.value);
+ }
+ }
+
+ m_scope_stack.append({ scope_type, scope_node, pushed_lexical_environment });
}
void Interpreter::exit_scope(const ScopeNode& scope_node)
{
while (!m_scope_stack.is_empty()) {
auto popped_scope = m_scope_stack.take_last();
+ if (popped_scope.pushed_environment)
+ m_call_stack.last().environment = m_call_stack.last().environment->parent();
if (popped_scope.scope_node.ptr() == &scope_node)
break;
}
@@ -125,17 +158,15 @@ void Interpreter::exit_scope(const ScopeNode& scope_node)
void Interpreter::set_variable(const FlyString& name, Value value, bool first_assignment)
{
- for (ssize_t i = m_scope_stack.size() - 1; i >= 0; --i) {
- auto& scope = m_scope_stack.at(i);
-
- auto possible_match = scope.variables.get(name);
+ for (auto* environment = current_environment(); environment; environment = environment->parent()) {
+ auto possible_match = environment->get(name);
if (possible_match.has_value()) {
if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) {
throw_exception<TypeError>("Assignment to constant variable");
return;
}
- scope.variables.set(move(name), { move(value), possible_match.value().declaration_kind });
+ environment->set(name, { value, possible_match.value().declaration_kind });
return;
}
}
@@ -145,13 +176,11 @@ void Interpreter::set_variable(const FlyString& name, Value value, bool first_as
Optional<Value> Interpreter::get_variable(const FlyString& name)
{
- for (ssize_t i = m_scope_stack.size() - 1; i >= 0; --i) {
- auto& scope = m_scope_stack.at(i);
- auto value = scope.variables.get(name);
- if (value.has_value())
- return value.value().value;
+ for (auto* environment = current_environment(); environment; environment = environment->parent()) {
+ auto possible_match = environment->get(name);
+ if (possible_match.has_value())
+ return possible_match.value().value;
}
-
return global_object().get(name);
}
@@ -169,13 +198,6 @@ void Interpreter::gather_roots(Badge<Heap>, HashTable<Cell*>& roots)
if (m_last_value.is_cell())
roots.set(m_last_value.as_cell());
- for (auto& scope : m_scope_stack) {
- for (auto& it : scope.variables) {
- if (it.value.value.is_cell())
- roots.set(it.value.value.as_cell());
- }
- }
-
for (auto& call_frame : m_call_stack) {
if (call_frame.this_value.is_cell())
roots.set(call_frame.this_value.as_cell());
@@ -183,6 +205,7 @@ void Interpreter::gather_roots(Badge<Heap>, HashTable<Cell*>& roots)
if (argument.is_cell())
roots.set(argument.as_cell());
}
+ roots.set(call_frame.environment);
}
}
@@ -192,6 +215,7 @@ Value Interpreter::call(Function* function, Value this_value, const Vector<Value
call_frame.function_name = function->name();
call_frame.this_value = this_value;
call_frame.arguments = arguments;
+ call_frame.environment = function->create_environment();
auto result = function->call(*this);
pop_call_frame();
return result;
diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h
index b25df3352a..4cd9a8fb45 100644
--- a/Libraries/LibJS/Interpreter.h
+++ b/Libraries/LibJS/Interpreter.h
@@ -33,6 +33,7 @@
#include <LibJS/Forward.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Exception.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
@@ -46,21 +47,17 @@ enum class ScopeType {
Continuable,
};
-struct Variable {
- Value value;
- DeclarationKind declaration_kind;
-};
-
struct ScopeFrame {
ScopeType type;
NonnullRefPtr<ScopeNode> scope_node;
- HashMap<FlyString, Variable> variables;
+ bool pushed_environment { false };
};
struct CallFrame {
FlyString function_name;
Value this_value;
Vector<Value> arguments;
+ LexicalEnvironment* environment { nullptr };
};
struct Argument {
@@ -106,12 +103,18 @@ public:
CallFrame& push_call_frame()
{
- m_call_stack.append({ {}, js_undefined(), {} });
+ m_call_stack.append({ {}, js_undefined(), {}, nullptr });
return m_call_stack.last();
}
void pop_call_frame() { m_call_stack.take_last(); }
const CallFrame& call_frame() { return m_call_stack.last(); }
- const Vector<CallFrame> call_stack() { return m_call_stack; }
+ const Vector<CallFrame>& call_stack() { return m_call_stack; }
+
+ void push_environment(LexicalEnvironment*);
+ void pop_environment();
+
+ const LexicalEnvironment* current_environment() const { return m_call_stack.last().environment; }
+ LexicalEnvironment* current_environment() { return m_call_stack.last().environment; }
size_t argument_count() const
{
diff --git a/Libraries/LibJS/Makefile b/Libraries/LibJS/Makefile
index e7c7cc0f70..3e924b8dac 100644
--- a/Libraries/LibJS/Makefile
+++ b/Libraries/LibJS/Makefile
@@ -25,6 +25,7 @@ OBJS = \
Runtime/FunctionConstructor.o \
Runtime/FunctionPrototype.o \
Runtime/GlobalObject.o \
+ Runtime/LexicalEnvironment.o \
Runtime/MathObject.o \
Runtime/NativeFunction.o \
Runtime/NativeProperty.o \
diff --git a/Libraries/LibJS/Runtime/Function.h b/Libraries/LibJS/Runtime/Function.h
index 242f671b9e..3465e02135 100644
--- a/Libraries/LibJS/Runtime/Function.h
+++ b/Libraries/LibJS/Runtime/Function.h
@@ -38,6 +38,7 @@ public:
virtual Value call(Interpreter&) = 0;
virtual Value construct(Interpreter&) = 0;
virtual const FlyString& name() const = 0;
+ virtual LexicalEnvironment* create_environment() = 0;
protected:
Function();
diff --git a/Libraries/LibJS/Runtime/LexicalEnvironment.cpp b/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
new file mode 100644
index 0000000000..ddf244bb68
--- /dev/null
+++ b/Libraries/LibJS/Runtime/LexicalEnvironment.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <LibJS/Runtime/LexicalEnvironment.h>
+
+namespace JS {
+
+LexicalEnvironment::LexicalEnvironment()
+{
+}
+
+LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent)
+ : m_parent(parent)
+ , m_variables(move(variables))
+{
+}
+
+LexicalEnvironment::~LexicalEnvironment()
+{
+}
+
+void LexicalEnvironment::visit_children(Visitor& visitor)
+{
+ Cell::visit_children(visitor);
+ if (m_parent)
+ visitor.visit(m_parent);
+ for (auto& it : m_variables)
+ visitor.visit(it.value.value);
+}
+
+Optional<Variable> LexicalEnvironment::get(const FlyString& name) const
+{
+ return m_variables.get(name);
+}
+
+void LexicalEnvironment::set(const FlyString& name, Variable variable)
+{
+ m_variables.set(name, variable);
+}
+
+}
diff --git a/Libraries/LibJS/Runtime/LexicalEnvironment.h b/Libraries/LibJS/Runtime/LexicalEnvironment.h
new file mode 100644
index 0000000000..c2cf3d8651
--- /dev/null
+++ b/Libraries/LibJS/Runtime/LexicalEnvironment.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <LibJS/Runtime/Cell.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+struct Variable {
+ Value value;
+ DeclarationKind declaration_kind;
+};
+
+class LexicalEnvironment final : public Cell {
+public:
+ LexicalEnvironment();
+ LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent);
+ virtual ~LexicalEnvironment() override;
+
+ LexicalEnvironment* parent() { return m_parent; }
+
+ Optional<Variable> get(const FlyString&) const;
+ void set(const FlyString&, Variable);
+
+ void clear();
+
+ const HashMap<FlyString, Variable>& variables() const { return m_variables; }
+
+private:
+ virtual const char* class_name() const override { return "LexicalEnvironment"; }
+ virtual void visit_children(Visitor&) override;
+
+ LexicalEnvironment* m_parent { nullptr };
+ HashMap<FlyString, Variable> m_variables;
+};
+
+}
diff --git a/Libraries/LibJS/Runtime/NativeFunction.h b/Libraries/LibJS/Runtime/NativeFunction.h
index d79fb38b04..ee03bafb47 100644
--- a/Libraries/LibJS/Runtime/NativeFunction.h
+++ b/Libraries/LibJS/Runtime/NativeFunction.h
@@ -49,6 +49,7 @@ protected:
private:
virtual bool is_native_function() const override { return true; }
virtual const char* class_name() const override { return "NativeFunction"; }
+ virtual LexicalEnvironment* create_environment() override final { return nullptr; }
FlyString m_name;
AK::Function<Value(Interpreter&)> m_native_function;
diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp
index 3560ea77b4..ab241fe331 100644
--- a/Libraries/LibJS/Runtime/ScriptFunction.cpp
+++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp
@@ -33,11 +33,17 @@
namespace JS {
-ScriptFunction::ScriptFunction(const FlyString& name, const Statement& body, Vector<FlyString> parameters)
+ScriptFunction::ScriptFunction(const FlyString& name, const Statement& body, Vector<FlyString> parameters, LexicalEnvironment* parent_environment)
: m_name(name)
, m_body(body)
, m_parameters(move(parameters))
+ , m_parent_environment(parent_environment)
{
+ HashMap<FlyString, Variable> variables;
+ for (auto& parameter : parameters) {
+ variables.set(parameter, {});
+ }
+
put("prototype", heap().allocate<Object>());
put_native_property("length", length_getter, length_setter);
}
@@ -46,6 +52,30 @@ ScriptFunction::~ScriptFunction()
{
}
+void ScriptFunction::visit_children(Visitor& visitor)
+{
+ Function::visit_children(visitor);
+ visitor.visit(m_parent_environment);
+}
+
+LexicalEnvironment* ScriptFunction::create_environment()
+{
+ HashMap<FlyString, Variable> variables;
+ for (auto& parameter : m_parameters) {
+ variables.set(parameter, { js_undefined(), DeclarationKind::Var });
+ }
+
+ if (body().is_scope_node()) {
+ for (auto& declaration : static_cast<const ScopeNode&>(body()).variables()) {
+ for (auto& declarator : declaration.declarations()) {
+ variables.set(declarator.id().string(), { js_undefined(), DeclarationKind::Var });
+ }
+ }
+ }
+
+ return heap().allocate<LexicalEnvironment>(move(variables), m_parent_environment);
+}
+
Value ScriptFunction::call(Interpreter& interpreter)
{
auto& argument_values = interpreter.call_frame().arguments;
@@ -55,7 +85,8 @@ Value ScriptFunction::call(Interpreter& interpreter)
auto value = js_undefined();
if (i < argument_values.size())
value = argument_values[i];
- arguments.append({ move(name), move(value) });
+ arguments.append({ name, value });
+ interpreter.current_environment()->set(name, { value, DeclarationKind::Var });
}
return interpreter.run(m_body, arguments, ScopeType::Function);
}
diff --git a/Libraries/LibJS/Runtime/ScriptFunction.h b/Libraries/LibJS/Runtime/ScriptFunction.h
index 18afbe131d..1e65faca41 100644
--- a/Libraries/LibJS/Runtime/ScriptFunction.h
+++ b/Libraries/LibJS/Runtime/ScriptFunction.h
@@ -32,7 +32,7 @@ namespace JS {
class ScriptFunction final : public Function {
public:
- ScriptFunction(const FlyString& name, const Statement& body, Vector<FlyString> parameters = {});
+ ScriptFunction(const FlyString& name, const Statement& body, Vector<FlyString> parameters, LexicalEnvironment* parent_environment);
virtual ~ScriptFunction();
const Statement& body() const { return m_body; }
@@ -46,6 +46,8 @@ public:
private:
virtual bool is_script_function() const final { return true; }
virtual const char* class_name() const override { return "ScriptFunction"; }
+ virtual LexicalEnvironment* create_environment() override;
+ virtual void visit_children(Visitor&) override;
static Value length_getter(Interpreter&);
static void length_setter(Interpreter&, Value);
@@ -53,6 +55,7 @@ private:
FlyString m_name;
NonnullRefPtr<Statement> m_body;
const Vector<FlyString> m_parameters;
+ LexicalEnvironment* m_parent_environment { nullptr };
};
}