diff options
-rw-r--r-- | Libraries/LibJS/AST.cpp | 9 | ||||
-rw-r--r-- | Libraries/LibJS/Forward.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.cpp | 62 | ||||
-rw-r--r-- | Libraries/LibJS/Interpreter.h | 19 | ||||
-rw-r--r-- | Libraries/LibJS/Makefile | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Function.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/LexicalEnvironment.cpp | 64 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/LexicalEnvironment.h | 64 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/NativeFunction.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ScriptFunction.cpp | 35 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ScriptFunction.h | 5 |
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 }; }; } |