Created
June 23, 2021 20:24
-
-
Save awesomekling/2c8910f58b5e23a7d364b1e4b0d0dbb5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp | |
index f85376baa..0382f1fba 100644 | |
--- a/Userland/Libraries/LibJS/AST.cpp | |
+++ b/Userland/Libraries/LibJS/AST.cpp | |
@@ -63,6 +63,67 @@ String ASTNode::class_name() const | |
return demangle(typeid(*this).name()).substring(4); | |
} | |
+// 13.3.8.1 Runtime Semantics: ArgumentListEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation | |
+Optional<MarkedValueList> argument_list_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<CallExpression::Argument> const& arguments) | |
+{ | |
+ // FIXME: This is somewhat ad-hoc. | |
+ | |
+ auto& vm = global_object.vm(); | |
+ MarkedValueList arg_list(vm.heap()); | |
+ arg_list.ensure_capacity(arguments.size()); | |
+ | |
+ for (auto& argument : arguments) { | |
+ auto value = argument.value->execute(interpreter, global_object); | |
+ if (vm.exception()) | |
+ return {}; | |
+ if (argument.is_spread) { | |
+ get_iterator_values(global_object, value, [&](Value iterator_value) { | |
+ if (vm.exception()) | |
+ return IterationDecision::Break; | |
+ arg_list.append(iterator_value); | |
+ return IterationDecision::Continue; | |
+ }); | |
+ if (vm.exception()) | |
+ return {}; | |
+ } else { | |
+ arg_list.append(value); | |
+ } | |
+ } | |
+ return arg_list; | |
+} | |
+ | |
+// 13.3.6.2 EvaluateCall ( func, ref, arguments, tailPosition ), https://tc39.es/ecma262/#sec-evaluatecall | |
+Value evaluate_call(Interpreter& interpreter, GlobalObject& global_object, Value function_value, Reference* reference, Vector<CallExpression::Argument> const& arguments) | |
+{ | |
+ auto& vm = interpreter.vm(); | |
+ | |
+ Value this_value; | |
+ if (reference) { | |
+ if (reference->is_property_reference()) { | |
+ this_value = reference->get_this_value(); | |
+ } else { | |
+ this_value = reference->base_environment().with_base_object(); | |
+ } | |
+ } else { | |
+ this_value = js_undefined(); | |
+ } | |
+ | |
+ auto arg_list = argument_list_evaluation(interpreter, global_object, arguments); | |
+ if (!arg_list.has_value()) | |
+ return {}; | |
+ | |
+ if (!function_value.is_object()) { | |
+ interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAnObject, reference ? reference->name() : String("Value")); | |
+ return {}; | |
+ } | |
+ if (!function_value.as_object().is_function()) { | |
+ interpreter.vm().throw_exception<TypeError>(global_object, ErrorType::NotAFunction, reference ? reference->name() : String("Value")); | |
+ return {}; | |
+ } | |
+ | |
+ return vm.call(function_value.as_function(), this_value, arg_list.release_value()); | |
+} | |
+ | |
static void update_function_name(Value value, FlyString const& name) | |
{ | |
if (!value.is_function()) | |
@@ -81,18 +142,6 @@ static String get_function_name(GlobalObject& global_object, Value value) | |
return value.to_string(global_object); | |
} | |
-Value ScopeNode::execute(Interpreter& interpreter, GlobalObject& global_object) const | |
-{ | |
- InterpreterNodeScope node_scope { interpreter, *this }; | |
- return interpreter.execute_statement(global_object, *this); | |
-} | |
- | |
-Value Program::execute(Interpreter& interpreter, GlobalObject& global_object) const | |
-{ | |
- InterpreterNodeScope node_scope { interpreter, *this }; | |
- return interpreter.execute_statement(global_object, *this, ScopeType::Block); | |
-} | |
- | |
Value FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject&) const | |
{ | |
InterpreterNodeScope node_scope { interpreter, *this }; | |
@@ -141,7 +190,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete | |
auto property_name = member_expression.computed_property_name(interpreter, global_object); | |
if (!property_name.is_valid()) | |
return {}; | |
- auto reference = Reference(super_base, property_name); | |
+ auto reference = Reference { super_base, property_name, super_base }; | |
callee = reference.get(global_object); | |
if (vm.exception()) | |
return {}; | |
@@ -173,6 +222,15 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj | |
{ | |
InterpreterNodeScope node_scope { interpreter, *this }; | |
auto& vm = interpreter.vm(); | |
+ | |
+ auto reference = m_callee->to_reference(interpreter, global_object); | |
+ auto func = reference.get(global_object); | |
+ | |
+ if (vm.exception()) | |
+ return {}; | |
+ | |
+ return evaluate_call(interpreter, global_object, func, &reference, m_arguments); | |
+#if 0 | |
auto [this_value, callee] = compute_this_and_callee(interpreter, global_object); | |
if (vm.exception()) | |
return {}; | |
@@ -259,6 +317,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj | |
return new_object; | |
} | |
return result; | |
+#endif | |
} | |
Value YieldExpression::execute(Interpreter&, GlobalObject&) const | |
@@ -286,10 +345,10 @@ Value IfStatement::execute(Interpreter& interpreter, GlobalObject& global_object | |
return {}; | |
if (predicate_result.to_boolean()) | |
- return interpreter.execute_statement(global_object, *m_consequent); | |
+ return m_consequent->execute(interpreter, global_object); | |
if (m_alternate) | |
- return interpreter.execute_statement(global_object, *m_alternate); | |
+ return m_alternate->execute(interpreter, global_object); | |
return js_undefined(); | |
} | |
@@ -310,7 +369,7 @@ Value WithStatement::execute(Interpreter& interpreter, GlobalObject& global_obje | |
auto* object_environment_record = new_object_environment(*object, true, interpreter.vm().call_frame().lexical_environment); | |
TemporaryChange<EnvironmentRecord*> scope_change(interpreter.vm().call_frame().lexical_environment, object_environment_record); | |
- return interpreter.execute_statement(global_object, m_body).value_or(js_undefined()); | |
+ return m_body->execute(interpreter, global_object).value_or(js_undefined()); | |
} | |
Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const | |
@@ -324,7 +383,7 @@ Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_obj | |
return {}; | |
if (!test_result.to_boolean()) | |
break; | |
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); | |
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value); | |
if (interpreter.exception()) | |
return {}; | |
if (interpreter.vm().should_unwind()) { | |
@@ -350,7 +409,7 @@ Value DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& global_o | |
for (;;) { | |
if (interpreter.exception()) | |
return {}; | |
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); | |
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value); | |
if (interpreter.exception()) | |
return {}; | |
if (interpreter.vm().should_unwind()) { | |
@@ -406,7 +465,7 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec | |
return {}; | |
if (!test_result.to_boolean()) | |
break; | |
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); | |
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value); | |
if (interpreter.exception()) | |
return {}; | |
if (interpreter.vm().should_unwind()) { | |
@@ -427,7 +486,7 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec | |
} | |
} else { | |
while (true) { | |
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); | |
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value); | |
if (interpreter.exception()) | |
return {}; | |
if (interpreter.vm().should_unwind()) { | |
@@ -496,10 +555,10 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj | |
while (object) { | |
auto property_names = object->get_enumerable_own_property_names(Object::PropertyKind::Key); | |
for (auto& value : property_names) { | |
- interpreter.vm().assign(target, value, global_object, has_declaration); | |
+ interpreter.vm().assign(target, value, global_object); | |
if (interpreter.exception()) | |
return {}; | |
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); | |
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value); | |
if (interpreter.exception()) | |
return {}; | |
if (interpreter.vm().should_unwind()) { | |
@@ -541,8 +600,8 @@ Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_obj | |
return {}; | |
get_iterator_values(global_object, rhs_result, [&](Value value) { | |
- interpreter.vm().assign(target, value, global_object, has_declaration); | |
- last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); | |
+ interpreter.vm().assign(target, value, global_object); | |
+ last_value = m_body->execute(interpreter, global_object).value_or(last_value); | |
if (interpreter.exception()) | |
return IterationDecision::Break; | |
if (interpreter.vm().should_unwind()) { | |
@@ -668,9 +727,9 @@ Reference Expression::to_reference(Interpreter&, GlobalObject&) const | |
return {}; | |
} | |
-Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) const | |
+Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject& global_object) const | |
{ | |
- return interpreter.vm().get_reference(string()); | |
+ return interpreter.vm().resolve_binding(global_object, string()); | |
} | |
Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& global_object) const | |
@@ -681,7 +740,10 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& | |
auto property_name = computed_property_name(interpreter, global_object); | |
if (!property_name.is_valid()) | |
return {}; | |
- return { object_value, property_name }; | |
+ require_object_coercible(global_object, object_value); | |
+ if (interpreter.exception()) | |
+ return {}; | |
+ return Reference { object_value, property_name, object_value }; | |
} | |
Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const | |
@@ -699,13 +761,12 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob | |
Value lhs_result; | |
if (m_op == UnaryOp::Typeof && is<Identifier>(*m_lhs)) { | |
auto reference = m_lhs->to_reference(interpreter, global_object); | |
- if (interpreter.exception()) { | |
+ if (interpreter.exception()) | |
return {}; | |
- } | |
- // FIXME: standard recommends checking with is_unresolvable but it ALWAYS return false here | |
- if (reference.is_local_variable() || reference.is_global_variable()) { | |
- const auto& name = reference.name(); | |
- lhs_result = interpreter.vm().get_variable(name.to_string(), global_object).value_or(js_undefined()); | |
+ if (reference.is_unresolvable()) { | |
+ lhs_result = js_undefined(); | |
+ } else { | |
+ lhs_result = reference.get(global_object); | |
if (interpreter.exception()) | |
return {}; | |
} | |
@@ -1318,13 +1379,10 @@ Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object) | |
{ | |
InterpreterNodeScope node_scope { interpreter, *this }; | |
- auto value = interpreter.vm().get_variable(string(), global_object); | |
- if (value.is_empty()) { | |
- if (!interpreter.exception()) | |
- interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string()); | |
+ auto reference = interpreter.vm().resolve_binding(global_object, string()); | |
+ if (interpreter.exception()) | |
return {}; | |
- } | |
- return value; | |
+ return reference.get(global_object); | |
} | |
void Identifier::dump(int indent) const | |
@@ -1461,11 +1519,6 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob | |
return {}; | |
} | |
- if (reference.is_unresolvable()) { | |
- interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::InvalidLeftHandAssignment); | |
- return {}; | |
- } | |
- | |
reference.put(global_object, rhs_result); | |
if (interpreter.exception()) | |
return {}; | |
@@ -1600,20 +1653,32 @@ Value VariableDeclaration::execute(Interpreter& interpreter, GlobalObject& globa | |
{ | |
InterpreterNodeScope node_scope { interpreter, *this }; | |
+ auto& vm = interpreter.vm(); | |
+ | |
for (auto& declarator : m_declarations) { | |
if (auto* init = declarator.init()) { | |
- auto initializer_result = init->execute(interpreter, global_object); | |
- if (interpreter.exception()) | |
- return {}; | |
declarator.target().visit( | |
[&](NonnullRefPtr<Identifier> const& id) { | |
- auto variable_name = id->string(); | |
+ auto binding_id = id->string(); | |
+ | |
+ auto lhs = vm.resolve_binding(global_object, binding_id); | |
+ if (vm.exception()) | |
+ return; | |
+ | |
+ auto rhs = init->execute(interpreter, global_object); | |
+ if (vm.exception()) | |
+ return; | |
+ | |
if (is<ClassExpression>(*init)) | |
- update_function_name(initializer_result, variable_name); | |
- interpreter.vm().set_variable(variable_name, initializer_result, global_object, true); | |
+ update_function_name(rhs, binding_id); | |
+ | |
+ lhs.put(global_object, rhs); | |
}, | |
[&](NonnullRefPtr<BindingPattern> const& pattern) { | |
- interpreter.vm().assign(pattern, initializer_result, global_object, true); | |
+ auto initializer_result = init->execute(interpreter, global_object); | |
+ if (vm.exception()) | |
+ return; | |
+ vm.assign(pattern, initializer_result, global_object); | |
}); | |
} | |
} | |
@@ -2042,16 +2107,24 @@ Value TryStatement::execute(Interpreter& interpreter, GlobalObject& global_objec | |
{ | |
InterpreterNodeScope node_scope { interpreter, *this }; | |
+ auto& vm = interpreter.vm(); | |
+ | |
auto result = interpreter.execute_statement(global_object, m_block, ScopeType::Try); | |
if (auto* exception = interpreter.exception()) { | |
if (m_handler) { | |
- interpreter.vm().clear_exception(); | |
+ vm.clear_exception(); | |
+ | |
+ auto* old_environment = vm.lexical_environment(); | |
+ auto* catch_environment = new_declarative_environment(*old_environment); | |
+ | |
+ catch_environment->create_mutable_binding(global_object, m_handler->parameter(), false); | |
+ TemporaryChange change(vm.call_frame().lexical_environment, static_cast<EnvironmentRecord*>(catch_environment)); | |
+ | |
+ initialize_bound_name(global_object, m_handler->parameter(), exception->value(), catch_environment); | |
+ if (vm.exception()) | |
+ return {}; | |
- HashMap<FlyString, Variable> parameters; | |
- parameters.set(m_handler->parameter(), Variable { exception->value(), DeclarationKind::Var }); | |
- auto* catch_scope = interpreter.heap().allocate<DeclarativeEnvironmentRecord>(global_object, move(parameters), interpreter.vm().call_frame().lexical_environment); | |
- TemporaryChange<EnvironmentRecord*> scope_change(interpreter.vm().call_frame().lexical_environment, catch_scope); | |
- result = interpreter.execute_statement(global_object, m_handler->body()); | |
+ result = m_handler->body().execute(interpreter, global_object); | |
} | |
} | |
@@ -2265,4 +2338,35 @@ void ScopeNode::add_functions(NonnullRefPtrVector<FunctionDeclaration> functions | |
m_functions.extend(move(functions)); | |
} | |
+Value Program::execute(Interpreter&, GlobalObject&) const | |
+{ | |
+ // NOTE: This is implemented by Interpreter::run() at the moment. | |
+ VERIFY_NOT_REACHED(); | |
+} | |
+ | |
+// 14.2.2 RS: Evaluation, https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation | |
+Value BlockStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const | |
+{ | |
+ if (children().is_empty()) { | |
+ // Block : { } | |
+ return {}; | |
+ } | |
+ | |
+ auto& vm = interpreter.vm(); | |
+ | |
+ // Block : { StatementList } | |
+ auto* old_env = vm.call_frame().lexical_environment; | |
+ auto* block_env = new_declarative_environment(*old_env); | |
+ if (interpreter.exception()) | |
+ return {}; | |
+ block_declaration_instantiation(*this, *block_env); | |
+ | |
+ vm.call_frame().lexical_environment = block_env; | |
+ | |
+ auto block_value = interpreter.evaluate_statement_list(global_object, *this, children(), ScopeType::Block); | |
+ | |
+ vm.call_frame().lexical_environment = old_env; | |
+ return block_value; | |
+} | |
+ | |
} | |
diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h | |
index 457a20e8c..6c19917aa 100644 | |
--- a/Userland/Libraries/LibJS/AST.h | |
+++ b/Userland/Libraries/LibJS/AST.h | |
@@ -139,7 +139,6 @@ public: | |
} | |
NonnullRefPtrVector<Statement> const& children() const { return m_children; } | |
- virtual Value execute(Interpreter&, GlobalObject&) const override; | |
virtual void dump(int indent) const override; | |
virtual void generate_bytecode(Bytecode::Generator&) const override; | |
@@ -186,6 +185,8 @@ public: | |
: ScopeNode(move(source_range)) | |
{ | |
} | |
+ | |
+ Value execute(Interpreter&, GlobalObject&) const override; | |
}; | |
class Expression : public ASTNode { | |
diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp | |
index aedbe5028..cd120dd75 100644 | |
--- a/Userland/Libraries/LibJS/Bytecode/Op.cpp | |
+++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp | |
@@ -16,6 +16,7 @@ | |
#include <LibJS/Runtime/EnvironmentRecord.h> | |
#include <LibJS/Runtime/GlobalObject.h> | |
#include <LibJS/Runtime/IteratorOperations.h> | |
+#include <LibJS/Runtime/Reference.h> | |
#include <LibJS/Runtime/RegExpObject.h> | |
#include <LibJS/Runtime/ScriptFunction.h> | |
#include <LibJS/Runtime/Value.h> | |
@@ -215,12 +216,18 @@ void ConcatString::execute_impl(Bytecode::Interpreter& interpreter) const | |
void GetVariable::execute_impl(Bytecode::Interpreter& interpreter) const | |
{ | |
- interpreter.accumulator() = interpreter.vm().get_variable(interpreter.current_executable().get_string(m_identifier), interpreter.global_object()); | |
+ auto reference = interpreter.vm().resolve_binding(interpreter.global_object(), interpreter.current_executable().get_string(m_identifier)); | |
+ if (interpreter.vm().exception()) | |
+ return; | |
+ interpreter.accumulator() = reference.get(interpreter.global_object()); | |
} | |
void SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const | |
{ | |
- interpreter.vm().set_variable(interpreter.current_executable().get_string(m_identifier), interpreter.accumulator(), interpreter.global_object()); | |
+ auto reference = interpreter.vm().resolve_binding(interpreter.global_object(), interpreter.current_executable().get_string(m_identifier)); | |
+ if (interpreter.vm().exception()) | |
+ return; | |
+ reference.put(interpreter.global_object(), interpreter.accumulator()); | |
} | |
void GetById::execute_impl(Bytecode::Interpreter& interpreter) const | |
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h | |
index 5e1ba35db..fa5f41b1f 100644 | |
--- a/Userland/Libraries/LibJS/Forward.h | |
+++ b/Userland/Libraries/LibJS/Forward.h | |
@@ -127,6 +127,7 @@ class Error; | |
class ErrorType; | |
class Exception; | |
class Expression; | |
+class FunctionDeclaration; | |
class FunctionEnvironmentRecord; | |
class FunctionNode; | |
class GlobalEnvironmentRecord; | |
@@ -140,6 +141,7 @@ class NativeFunction; | |
class NativeProperty; | |
class ObjectEnvironmentRecord; | |
class PrimitiveString; | |
+class Program; | |
class PromiseReaction; | |
class PromiseReactionJob; | |
class PromiseResolveThenableJob; | |
diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp | |
index b70671b78..5479f7ef2 100644 | |
--- a/Userland/Libraries/LibJS/Interpreter.cpp | |
+++ b/Userland/Libraries/LibJS/Interpreter.cpp | |
@@ -9,6 +9,7 @@ | |
#include <AK/StringBuilder.h> | |
#include <LibJS/AST.h> | |
#include <LibJS/Interpreter.h> | |
+#include <LibJS/Runtime/AbstractOperations.h> | |
#include <LibJS/Runtime/FunctionEnvironmentRecord.h> | |
#include <LibJS/Runtime/GlobalEnvironmentRecord.h> | |
#include <LibJS/Runtime/GlobalObject.h> | |
@@ -39,6 +40,9 @@ Interpreter::~Interpreter() | |
void Interpreter::run(GlobalObject& global_object, const Program& program) | |
{ | |
+ // FIXME: This is an ad-hoc implementation of 16.1.6 and should be rewritten per the specification. | |
+ // 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation | |
+ | |
auto& vm = this->vm(); | |
VERIFY(!vm.exception()); | |
@@ -56,8 +60,11 @@ void Interpreter::run(GlobalObject& global_object, const Program& program) | |
VERIFY(!vm.exception()); | |
global_call_frame.is_strict_mode = program.is_strict_mode(); | |
vm.push_call_frame(global_call_frame, global_object); | |
+ | |
+ global_declaration_instantiation(program, global_object.environment_record()); | |
+ | |
VERIFY(!vm.exception()); | |
- auto value = program.execute(*this, global_object); | |
+ auto value = evaluate_statement_list(global_object, program, program.children(), ScopeType::Block); | |
vm.set_last_value(Badge<Interpreter> {}, value.value_or(js_undefined())); | |
vm.pop_call_frame(); | |
@@ -164,21 +171,15 @@ void Interpreter::push_scope(ScopeFrame frame) | |
m_scope_stack.append(move(frame)); | |
} | |
-Value Interpreter::execute_statement(GlobalObject& global_object, const Statement& statement, ScopeType scope_type) | |
+Value Interpreter::evaluate_statement_list(GlobalObject& global_object, ScopeNode const& scope_node, NonnullRefPtrVector<Statement> const& statement_list, ScopeType scope_type) | |
{ | |
- if (!is<ScopeNode>(statement)) | |
- return statement.execute(*this, global_object); | |
- | |
- auto& block = static_cast<const ScopeNode&>(statement); | |
- enter_scope(block, scope_type, global_object); | |
- | |
Value last_value; | |
- for (auto& node : block.children()) { | |
- auto value = node.execute(*this, global_object); | |
+ for (auto& statement : statement_list) { | |
+ auto value = statement.execute(*this, global_object); | |
if (!value.is_empty()) | |
last_value = value; | |
if (vm().should_unwind()) { | |
- if (!block.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, block.label())) | |
+ if (!scope_node.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, scope_node.label())) | |
vm().stop_unwind(); | |
break; | |
} | |
@@ -193,11 +194,23 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen | |
if (vm().unwind_until() == scope_type) | |
vm().stop_unwind(); | |
- exit_scope(block); | |
- | |
return last_value; | |
} | |
+Value Interpreter::execute_statement(GlobalObject& global_object, const Statement& statement, ScopeType scope_type) | |
+{ | |
+ if (!is<ScopeNode>(statement)) | |
+ return statement.execute(*this, global_object); | |
+ | |
+ auto& scope_node = static_cast<const ScopeNode&>(statement); | |
+ enter_scope(scope_node, scope_type, global_object); | |
+ | |
+ auto value = evaluate_statement_list(global_object, scope_node, scope_node.children(), scope_type); | |
+ | |
+ exit_scope(scope_node); | |
+ return value; | |
+} | |
+ | |
FunctionEnvironmentRecord* Interpreter::current_function_environment_record() | |
{ | |
VERIFY(is<FunctionEnvironmentRecord>(vm().call_frame().lexical_environment)); | |
diff --git a/Userland/Libraries/LibJS/Interpreter.h b/Userland/Libraries/LibJS/Interpreter.h | |
index a342fc8b1..8db143fe0 100644 | |
--- a/Userland/Libraries/LibJS/Interpreter.h | |
+++ b/Userland/Libraries/LibJS/Interpreter.h | |
@@ -80,6 +80,7 @@ public: | |
const ExecutingASTNodeChain* executing_ast_node_chain() const { return m_ast_node_chain; } | |
Value execute_statement(GlobalObject&, const Statement&, ScopeType = ScopeType::Block); | |
+ Value evaluate_statement_list(GlobalObject&, ScopeNode const&, NonnullRefPtrVector<Statement> const&, ScopeType); | |
private: | |
explicit Interpreter(VM&); | |
diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp | |
index 1865212e0..50bf6a58a 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp | |
@@ -8,6 +8,7 @@ | |
#include <AK/Function.h> | |
#include <AK/Result.h> | |
#include <AK/TemporaryChange.h> | |
+#include <LibJS/AST.h> | |
#include <LibJS/Interpreter.h> | |
#include <LibJS/Parser.h> | |
#include <LibJS/Runtime/AbstractOperations.h> | |
@@ -22,6 +23,8 @@ | |
#include <LibJS/Runtime/ObjectEnvironmentRecord.h> | |
#include <LibJS/Runtime/PropertyName.h> | |
#include <LibJS/Runtime/ProxyObject.h> | |
+#include <LibJS/Runtime/Reference.h> | |
+#include <LibJS/Runtime/ScriptFunction.h> | |
namespace JS { | |
@@ -178,7 +181,7 @@ ObjectEnvironmentRecord* new_object_environment(Object& object, bool is_with_env | |
if (!is_with_environment) { | |
TODO(); | |
} | |
- return global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, object, environment_record); | |
+ return global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, object, is_with_environment, environment_record); | |
} | |
// 9.4.3 GetThisEnvironment ( ), https://tc39.es/ecma262/#sec-getthisenvironment | |
@@ -228,4 +231,177 @@ Value perform_eval(Value x, GlobalObject& caller_realm, CallerMode strict_caller | |
return interpreter.execute_statement(caller_realm, program).value_or(js_undefined()); | |
} | |
+// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference | |
+Reference get_identifier_reference(GlobalObject& global_object, EnvironmentRecord* environment_record, FlyString const& name, bool strict) | |
+{ | |
+ if (!environment_record) { | |
+ // FIXME: "Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }." | |
+ // (The FIXME here is to return this exact reference as specified instead of just an empty Reference {}) | |
+ return Reference { Reference::BaseType::Unresolvable, name, strict }; | |
+ } | |
+ | |
+ bool exists = environment_record->has_binding(name); | |
+ if (exists) | |
+ return Reference { *environment_record, name, strict }; | |
+ | |
+ return get_identifier_reference(global_object, environment_record->outer_environment(), name, strict); | |
+} | |
+ | |
+// 14.2.3 BlockDeclarationInstantiation ( code, environment ), https://tc39.es/ecma262/#sec-blockdeclarationinstantiation | |
+void block_declaration_instantiation(ScopeNode const& code, DeclarativeEnvironmentRecord& environment) | |
+{ | |
+ auto& global_object = environment.global_object(); | |
+ auto& vm = global_object.vm(); | |
+ | |
+ for (auto& declaration : code.variables()) { | |
+ bool is_constant_declaration = declaration.declaration_kind() == DeclarationKind::Const; | |
+ auto create_binding = [&](auto& name) { | |
+ if (is_constant_declaration) { | |
+ environment.create_immutable_binding(global_object, name, true); | |
+ } else { | |
+ // FIXME: "NOTE: This step is replaced in section B.3.3.6." | |
+ environment.create_mutable_binding(global_object, name, false); | |
+ } | |
+ }; | |
+ for (auto& declarator : declaration.declarations()) { | |
+ declarator.target().visit( | |
+ [&](NonnullRefPtr<Identifier> const& id) { | |
+ create_binding(id->string()); | |
+ }, | |
+ [&](NonnullRefPtr<BindingPattern> const& binding) { | |
+ binding->for_each_bound_name([&](const auto& name) { | |
+ create_binding(name); | |
+ }); | |
+ }); | |
+ } | |
+ if (vm.exception()) | |
+ return; | |
+ } | |
+ | |
+ for (auto& declaration : code.functions()) { | |
+ auto& function_name = declaration.name(); | |
+ auto* function_object = instantiate_function_object(declaration, environment); | |
+ if (vm.exception()) | |
+ return; | |
+ | |
+ // FIXME: "NOTE: This step is replaced in section B.3.3.6." | |
+ environment.initialize_binding(global_object, function_name, function_object); | |
+ if (vm.exception()) | |
+ return; | |
+ } | |
+} | |
+ | |
+// 15.2.4 Runtime Semantics: InstantiateOrdinaryFunctionObject, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionobject | |
+Function* instantiate_ordinary_function_object(FunctionDeclaration const& declaration, EnvironmentRecord& scope) | |
+{ | |
+ // FIXME: This is very ad-hoc. | |
+ auto& global_object = scope.global_object(); | |
+ auto& vm = global_object.vm(); | |
+ return ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), vm.lexical_environment(), declaration.kind(), declaration.is_strict_mode() || vm.in_strict_mode(), declaration.is_arrow_function()); | |
+} | |
+ | |
+// 8.5.1 Runtime Semantics: InstantiateFunctionObject, https://tc39.es/ecma262/#sec-runtime-semantics-instantiatefunctionobject | |
+Function* instantiate_function_object(FunctionDeclaration const& declaration, EnvironmentRecord& scope) | |
+{ | |
+ // FIXME: Support the other function kinds. | |
+ return instantiate_ordinary_function_object(declaration, scope); | |
+} | |
+ | |
+// 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation | |
+void global_declaration_instantiation(Program const& script, GlobalEnvironmentRecord& environment) | |
+{ | |
+ auto& global_object = environment.global_object(); | |
+ auto& vm = global_object.vm(); | |
+ | |
+ // FIXME: We want to iterate lexNames and varNames separately here, but they are not separate.. | |
+ | |
+ HashTable<FlyString> declared_function_names; | |
+ NonnullRefPtrVector<FunctionDeclaration> functions_to_initialize; | |
+ | |
+ for (auto& declaration : script.functions()) { | |
+ if (!declared_function_names.contains(declaration.name())) { | |
+ if (!environment.can_declare_global_function(declaration.name())) { | |
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString); | |
+ return; | |
+ } | |
+ | |
+ declared_function_names.set(declaration.name()); | |
+ functions_to_initialize.prepend(declaration); | |
+ } | |
+ } | |
+ | |
+ HashTable<FlyString> declared_var_names; | |
+ for (auto& declaration : script.variables()) { | |
+ auto do_var_name = [&](auto& name) { | |
+ if (!declared_var_names.contains(name)) { | |
+ if (!environment.can_declare_global_var(name)) { | |
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString); | |
+ return; | |
+ } | |
+ declared_var_names.set(name); | |
+ } | |
+ }; | |
+ for (auto& declarator : declaration.declarations()) { | |
+ declarator.target().visit( | |
+ [&](NonnullRefPtr<Identifier> const& id) { do_var_name(id->string()); }, | |
+ [&](NonnullRefPtr<BindingPattern> const& binding) { binding->for_each_bound_name([&](auto& name) { do_var_name(name); }); }); | |
+ if (vm.exception()) | |
+ return; | |
+ } | |
+ } | |
+ | |
+ for (auto& declaration : script.variables()) { | |
+ bool is_constant_declaration = declaration.declaration_kind() == DeclarationKind::Const; | |
+ auto create_binding = [&](auto& name) { | |
+ if (is_constant_declaration) { | |
+ environment.create_immutable_binding(global_object, name, true); | |
+ } else { | |
+ environment.create_mutable_binding(global_object, name, false); | |
+ } | |
+ }; | |
+ for (auto& declarator : declaration.declarations()) { | |
+ declarator.target().visit( | |
+ [&](NonnullRefPtr<Identifier> const& id) { | |
+ create_binding(id->string()); | |
+ }, | |
+ [&](NonnullRefPtr<BindingPattern> const& binding) { | |
+ binding->for_each_bound_name([&](const auto& name) { | |
+ create_binding(name); | |
+ }); | |
+ }); | |
+ } | |
+ if (vm.exception()) | |
+ return; | |
+ } | |
+ | |
+ for (auto& declaration : functions_to_initialize) { | |
+ auto& function_name = declaration.name(); | |
+ auto* function_object = instantiate_function_object(declaration, environment); | |
+ if (vm.exception()) | |
+ return; | |
+ | |
+ environment.create_global_function_binding(function_name, function_object, false); | |
+ if (vm.exception()) | |
+ return; | |
+ } | |
+ | |
+ for (auto& name : declared_var_names) { | |
+ environment.create_global_var_binding(name, false); | |
+ if (vm.exception()) | |
+ return; | |
+ } | |
+} | |
+ | |
+// 8.5.2.1 InitializeBoundName ( name, value, environment ), https://tc39.es/ecma262/#sec-initializeboundname | |
+void initialize_bound_name(GlobalObject& global_object, FlyString const& name, Value value, EnvironmentRecord* environment) | |
+{ | |
+ if (environment) { | |
+ environment->initialize_binding(global_object, name, value); | |
+ return; | |
+ } | |
+ | |
+ auto lhs = global_object.vm().resolve_binding(global_object, name); | |
+ lhs.put(global_object, value); | |
+} | |
+ | |
} | |
diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h | |
index 1e00d90cb..502f7a8c6 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h | |
+++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h | |
@@ -24,6 +24,12 @@ MarkedValueList create_list_from_array_like(GlobalObject&, Value, AK::Function<R | |
Function* species_constructor(GlobalObject&, Object const&, Function& default_constructor); | |
GlobalObject* get_function_realm(GlobalObject&, Function const&); | |
Object* get_prototype_from_constructor(GlobalObject&, Function const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)()); | |
+Reference get_identifier_reference(GlobalObject&, EnvironmentRecord*, FlyString const&, bool strict); | |
+void block_declaration_instantiation(ScopeNode const&, DeclarativeEnvironmentRecord&); | |
+Function* instantiate_function_object(FunctionDeclaration const&, EnvironmentRecord&); | |
+Function* instantiate_ordinary_function_object(FunctionDeclaration const&, EnvironmentRecord&); | |
+void global_declaration_instantiation(Program const&, GlobalEnvironmentRecord&); | |
+void initialize_bound_name(GlobalObject&, FlyString const& name, Value, EnvironmentRecord*); | |
enum class CallerMode { | |
Strict, | |
diff --git a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp | |
index 54a48871c..39cc38ba5 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.cpp | |
@@ -65,11 +65,6 @@ void DeclarativeEnvironmentRecord::put_into_environment_record(FlyString const& | |
m_variables.set(name, variable); | |
} | |
-bool DeclarativeEnvironmentRecord::delete_from_environment_record(FlyString const& name) | |
-{ | |
- return m_variables.remove(name); | |
-} | |
- | |
// 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n | |
bool DeclarativeEnvironmentRecord::has_binding(FlyString const& name) const | |
{ | |
diff --git a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h | |
index e82a1b334..5c29f86e8 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h | |
+++ b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironmentRecord.h | |
@@ -42,7 +42,6 @@ public: | |
// ^EnvironmentRecord | |
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override; | |
virtual void put_into_environment_record(FlyString const&, Variable) override; | |
- virtual bool delete_from_environment_record(FlyString const&) override; | |
HashMap<FlyString, Variable> const& variables() const { return m_variables; } | |
diff --git a/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h | |
index f4a13278b..2be94c6ca 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h | |
+++ b/Userland/Libraries/LibJS/Runtime/EnvironmentRecord.h | |
@@ -29,11 +29,12 @@ public: | |
virtual Optional<Variable> get_from_environment_record(FlyString const&) const = 0; | |
virtual void put_into_environment_record(FlyString const&, Variable) = 0; | |
- virtual bool delete_from_environment_record(FlyString const&) = 0; | |
virtual bool has_this_binding() const { return false; } | |
virtual Value get_this_binding(GlobalObject&) const { return {}; } | |
+ virtual Object* with_base_object() const { return nullptr; } | |
+ | |
virtual bool has_binding([[maybe_unused]] FlyString const& name) const { return false; } | |
virtual void create_mutable_binding(GlobalObject&, [[maybe_unused]] FlyString const& name, [[maybe_unused]] bool can_be_deleted) { } | |
virtual void create_immutable_binding(GlobalObject&, [[maybe_unused]] FlyString const& name, [[maybe_unused]] bool strict) { } | |
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp | |
index ee866636e..bbae11dbf 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.cpp | |
@@ -14,9 +14,8 @@ namespace JS { | |
GlobalEnvironmentRecord::GlobalEnvironmentRecord(GlobalObject& global_object) | |
: EnvironmentRecord(nullptr) | |
- , m_global_object(global_object) | |
{ | |
- m_object_record = global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, global_object, nullptr); | |
+ m_object_record = global_object.heap().allocate<ObjectEnvironmentRecord>(global_object, global_object, false, nullptr); | |
m_declarative_record = global_object.heap().allocate<DeclarativeEnvironmentRecord>(global_object); | |
} | |
@@ -39,20 +38,14 @@ void GlobalEnvironmentRecord::put_into_environment_record(FlyString const& name, | |
m_object_record->put_into_environment_record(name, variable); | |
} | |
-bool GlobalEnvironmentRecord::delete_from_environment_record(FlyString const& name) | |
-{ | |
- // FIXME: This should be a "composite" of the object record and the declarative record. | |
- return object_record().delete_from_environment_record(name); | |
-} | |
- | |
Value GlobalEnvironmentRecord::get_this_binding(GlobalObject&) const | |
{ | |
- return &m_global_object; | |
+ return &global_object(); | |
} | |
Value GlobalEnvironmentRecord::global_this_value() const | |
{ | |
- return &m_global_object; | |
+ return &global_object(); | |
} | |
// 9.1.1.4.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n | |
@@ -144,7 +137,7 @@ bool GlobalEnvironmentRecord::has_lexical_declaration(FlyString const& name) con | |
// 9.1.1.4.14 HasRestrictedGlobalProperty ( N ), https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty | |
bool GlobalEnvironmentRecord::has_restricted_global_property(FlyString const& name) const | |
{ | |
- auto existing_prop = m_global_object.get_own_property_descriptor(name); | |
+ auto existing_prop = global_object().get_own_property_descriptor(name); | |
if (!existing_prop.has_value() || existing_prop.value().value.is_undefined()) | |
return false; | |
if (existing_prop.value().attributes.is_configurable()) | |
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h | |
index 093a1ba57..ed968a33d 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h | |
+++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironmentRecord.h | |
@@ -18,7 +18,6 @@ public: | |
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override; | |
virtual void put_into_environment_record(FlyString const&, Variable) override; | |
- virtual bool delete_from_environment_record(FlyString const&) override; | |
virtual bool has_this_binding() const final { return true; } | |
virtual Value get_this_binding(GlobalObject&) const final; | |
@@ -50,8 +49,6 @@ private: | |
virtual bool is_global_environment_record() const override { return true; } | |
virtual void visit_edges(Visitor&) override; | |
- GlobalObject& m_global_object; | |
- | |
ObjectEnvironmentRecord* m_object_record { nullptr }; | |
DeclarativeEnvironmentRecord* m_declarative_record { nullptr }; | |
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp | |
index 521734060..e06db34dc 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp | |
@@ -99,7 +99,7 @@ void GlobalObject::initialize_global_object() | |
m_object_prototype = heap().allocate_without_global_object<ObjectPrototype>(*this); | |
m_function_prototype = heap().allocate_without_global_object<FunctionPrototype>(*this); | |
- m_environment_record = heap().allocate_without_global_object<GlobalEnvironmentRecord>(*this); | |
+ m_environment_record = heap().allocate<GlobalEnvironmentRecord>(*this, *this); | |
m_new_object_shape = vm.heap().allocate_without_global_object<Shape>(*this); | |
m_new_object_shape->set_prototype_without_transition(m_object_prototype); | |
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp | |
index 2be830ea7..b50e5cc43 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.cpp | |
@@ -10,9 +10,10 @@ | |
namespace JS { | |
-ObjectEnvironmentRecord::ObjectEnvironmentRecord(Object& object, EnvironmentRecord* parent_scope) | |
+ObjectEnvironmentRecord::ObjectEnvironmentRecord(Object& object, bool is_with_environment, EnvironmentRecord* parent_scope) | |
: EnvironmentRecord(parent_scope) | |
, m_object(object) | |
+ , m_with_environment(is_with_environment) | |
{ | |
} | |
@@ -35,11 +36,6 @@ void ObjectEnvironmentRecord::put_into_environment_record(FlyString const& name, | |
m_object.put(name, variable.value); | |
} | |
-bool ObjectEnvironmentRecord::delete_from_environment_record(FlyString const& name) | |
-{ | |
- return m_object.delete_property(name); | |
-} | |
- | |
// 9.1.1.2.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n | |
bool ObjectEnvironmentRecord::has_binding(FlyString const& name) const | |
{ | |
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h | |
index f9780f0fb..2deebbc7c 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h | |
+++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironmentRecord.h | |
@@ -14,11 +14,10 @@ class ObjectEnvironmentRecord : public EnvironmentRecord { | |
JS_ENVIRONMENT_RECORD(ObjectEnvironmentRecord, EnvironmentRecord); | |
public: | |
- ObjectEnvironmentRecord(Object&, EnvironmentRecord* parent_scope); | |
+ ObjectEnvironmentRecord(Object&, bool is_with_environment, EnvironmentRecord* parent_scope); | |
virtual Optional<Variable> get_from_environment_record(FlyString const&) const override; | |
virtual void put_into_environment_record(FlyString const&, Variable) override; | |
- virtual bool delete_from_environment_record(FlyString const&) override; | |
virtual bool has_binding(FlyString const& name) const override; | |
virtual void create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted) override; | |
@@ -28,12 +27,23 @@ public: | |
virtual Value get_binding_value(GlobalObject&, FlyString const& name, bool strict) override; | |
virtual bool delete_binding(GlobalObject&, FlyString const& name) override; | |
+ // 9.1.1.2.10 WithBaseObject ( ), https://tc39.es/ecma262/#sec-object-environment-records-withbaseobject | |
+ virtual Object* with_base_object() const override | |
+ { | |
+ if (is_with_environment()) | |
+ return &m_object; | |
+ return nullptr; | |
+ } | |
+ | |
+ bool is_with_environment() const { return m_with_environment; } | |
+ | |
Object& object() { return m_object; } | |
private: | |
virtual void visit_edges(Visitor&) override; | |
Object& m_object; | |
+ bool m_with_environment { false }; | |
}; | |
} | |
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp | |
index 2bc6b466f..1f4a4ce17 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/Reference.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp | |
@@ -1,9 +1,10 @@ | |
/* | |
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> | |
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> | |
* | |
* SPDX-License-Identifier: BSD-2-Clause | |
*/ | |
+#include <LibJS/AST.h> | |
#include <LibJS/Runtime/Error.h> | |
#include <LibJS/Runtime/GlobalObject.h> | |
#include <LibJS/Runtime/Reference.h> | |
@@ -15,39 +16,28 @@ void Reference::put(GlobalObject& global_object, Value value) | |
auto& vm = global_object.vm(); | |
if (is_unresolvable()) { | |
- throw_reference_error(global_object); | |
- return; | |
- } | |
- | |
- if (is_local_variable() || is_global_variable()) { | |
- if (is_local_variable()) | |
- vm.set_variable(m_name.to_string(), value, global_object); | |
- else | |
- global_object.put(m_name, value); | |
- return; | |
- } | |
- | |
- auto base = this->base(); | |
- | |
- if (!base.is_object() && vm.in_strict_mode()) { | |
- if (base.is_nullish()) | |
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects()); | |
- else | |
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferencePrimitiveSetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.typeof(), base.to_string_without_side_effects()); | |
+ if (m_strict) { | |
+ throw_reference_error(global_object); | |
+ return; | |
+ } | |
+ global_object.put(m_name, value); | |
return; | |
} | |
- if (base.is_nullish()) { | |
- // This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead. | |
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects()); | |
+ if (is_property_reference()) { | |
+ auto* base_obj = m_base_value.to_object(global_object); | |
+ if (!base_obj) | |
+ return; | |
+ bool succeeded = base_obj->put(m_name, value); | |
+ if (!succeeded && m_strict) { | |
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects()); | |
+ return; | |
+ } | |
return; | |
} | |
- auto* object = base.to_object(global_object); | |
- if (!object) | |
- return; | |
- | |
- object->put(m_name, value); | |
+ VERIFY(m_base_type == BaseType::EnvironmentRecord); | |
+ m_base_environment_record->set_mutable_binding(global_object, m_name.as_string(), value, m_strict); | |
} | |
void Reference::throw_reference_error(GlobalObject& global_object) | |
@@ -61,69 +51,46 @@ void Reference::throw_reference_error(GlobalObject& global_object) | |
Value Reference::get(GlobalObject& global_object) | |
{ | |
- auto& vm = global_object.vm(); | |
- | |
if (is_unresolvable()) { | |
throw_reference_error(global_object); | |
return {}; | |
} | |
- if (is_local_variable() || is_global_variable()) { | |
- Value value; | |
- if (is_local_variable()) | |
- value = vm.get_variable(m_name.to_string(), global_object); | |
- else | |
- value = global_object.get(m_name); | |
- if (vm.exception()) | |
+ if (is_property_reference()) { | |
+ auto* base_obj = m_base_value.to_object(global_object); | |
+ if (!base_obj) | |
return {}; | |
- if (value.is_empty()) { | |
- throw_reference_error(global_object); | |
- return {}; | |
- } | |
- return value; | |
- } | |
- | |
- auto base = this->base(); | |
- | |
- if (base.is_nullish()) { | |
- // This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead. | |
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishGetProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects()); | |
- return {}; | |
+ return base_obj->get(m_name).value_or(js_undefined()); | |
} | |
- auto* object = base.to_object(global_object); | |
- if (!object) | |
- return {}; | |
- | |
- return object->get(m_name).value_or(js_undefined()); | |
+ VERIFY(m_base_type == BaseType::EnvironmentRecord); | |
+ return m_base_environment_record->get_binding_value(global_object, m_name.as_string(), m_strict); | |
} | |
+// 13.5.1.2 RS: Evaluation (UnaryExpression : delete UnaryExpression), https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation | |
bool Reference::delete_(GlobalObject& global_object) | |
{ | |
- if (is_unresolvable()) | |
+ if (is_unresolvable()) { | |
+ VERIFY(!m_strict); | |
return true; | |
- | |
- auto& vm = global_object.vm(); | |
- | |
- if (is_local_variable() || is_global_variable()) { | |
- if (is_local_variable()) | |
- return vm.delete_variable(m_name.to_string()); | |
- else | |
- return global_object.delete_property(m_name); | |
} | |
- auto base = this->base(); | |
+ auto& vm = global_object.vm(); | |
- if (base.is_nullish()) { | |
- // This will always fail the to_object() call below, let's throw the TypeError ourselves with a nice message instead. | |
- vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), base.to_string_without_side_effects()); | |
- return false; | |
+ if (is_property_reference()) { | |
+ auto* base_obj = m_base_value.to_object(global_object); | |
+ if (!base_obj) | |
+ return false; | |
+ bool succeeded = base_obj->delete_property(m_name); | |
+ if (!succeeded && m_strict) { | |
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects()); | |
+ return false; | |
+ } | |
+ return succeeded; | |
} | |
- auto* object = base.to_object(global_object); | |
- VERIFY(object); | |
- | |
- return object->delete_property(m_name); | |
+ VERIFY(m_base_type == BaseType::EnvironmentRecord); | |
+ return m_base_environment_record->delete_binding(global_object, m_name.as_string()); | |
} | |
} | |
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h | |
index 864d7855c..7ce40b957 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/Reference.h | |
+++ b/Userland/Libraries/LibJS/Runtime/Reference.h | |
@@ -1,5 +1,5 @@ | |
/* | |
- * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> | |
+ * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> | |
* | |
* SPDX-License-Identifier: BSD-2-Clause | |
*/ | |
@@ -7,6 +7,7 @@ | |
#pragma once | |
#include <AK/String.h> | |
+#include <LibJS/Runtime/EnvironmentRecord.h> | |
#include <LibJS/Runtime/PropertyName.h> | |
#include <LibJS/Runtime/Value.h> | |
@@ -14,55 +15,84 @@ namespace JS { | |
class Reference { | |
public: | |
+ enum class BaseType : u8 { | |
+ Unresolvable, | |
+ Value, | |
+ EnvironmentRecord, | |
+ }; | |
+ | |
Reference() { } | |
- Reference(Value base, const PropertyName& name, bool strict = false) | |
- : m_base(base) | |
+ Reference(BaseType type, PropertyName const& name, bool strict) | |
+ : m_base_type(type) | |
, m_name(name) | |
, m_strict(strict) | |
{ | |
} | |
- enum LocalVariableTag { LocalVariable }; | |
- Reference(LocalVariableTag, const FlyString& name, bool strict = false) | |
- : m_base(js_null()) | |
+ Reference(Value base, PropertyName const& name, Value this_value, bool strict = false) | |
+ : m_base_type(BaseType::Value) | |
+ , m_base_value(base) | |
, m_name(name) | |
+ , m_this_value(this_value) | |
, m_strict(strict) | |
- , m_local_variable(true) | |
{ | |
+ if (base.is_nullish()) { | |
+ m_base_value = {}; | |
+ m_name = {}; | |
+ } | |
} | |
- enum GlobalVariableTag { GlobalVariable }; | |
- Reference(GlobalVariableTag, const FlyString& name, bool strict = false) | |
- : m_base(js_null()) | |
- , m_name(name) | |
+ Reference(EnvironmentRecord& base, FlyString const& referenced_name, bool strict = false) | |
+ : m_base_type(BaseType::EnvironmentRecord) | |
+ , m_base_environment_record(&base) | |
+ , m_name(referenced_name) | |
, m_strict(strict) | |
- , m_global_variable(true) | |
{ | |
} | |
- Value base() const { return m_base; } | |
- const PropertyName& name() const { return m_name; } | |
- bool is_strict() const { return m_strict; } | |
+ Value base() const | |
+ { | |
+ VERIFY(m_base_type == BaseType::Value); | |
+ return m_base_value; | |
+ } | |
- bool is_unresolvable() const { return m_base.is_empty(); } | |
- bool is_property() const | |
+ EnvironmentRecord& base_environment() const | |
{ | |
- return m_base.is_object() || has_primitive_base(); | |
+ VERIFY(m_base_type == BaseType::EnvironmentRecord); | |
+ return *m_base_environment_record; | |
} | |
- bool has_primitive_base() const | |
+ PropertyName const& name() const { return m_name; } | |
+ bool is_strict() const { return m_strict; } | |
+ | |
+ // 6.2.4.2 IsUnresolvableReference ( V ), https://tc39.es/ecma262/#sec-isunresolvablereference | |
+ bool is_unresolvable() const { return m_base_type == BaseType::Unresolvable; } | |
+ | |
+ // 6.2.4.1 IsPropertyReference ( V ), https://tc39.es/ecma262/#sec-ispropertyreference | |
+ bool is_property_reference() const | |
{ | |
- return m_base.is_boolean() || m_base.is_string() || m_base.is_number(); | |
+ if (is_unresolvable()) | |
+ return false; | |
+ if (m_base_type == BaseType::EnvironmentRecord) | |
+ return false; | |
+ if (m_base_value.is_boolean() || m_base_value.is_string() || m_base_value.is_symbol() || m_base_value.is_bigint() || m_base_value.is_number() || m_base_value.is_object()) | |
+ return true; | |
+ return false; | |
} | |
- bool is_local_variable() const | |
+ // 6.2.4.7 GetThisValue ( V ), https://tc39.es/ecma262/#sec-getthisvalue | |
+ Value get_this_value() const | |
{ | |
- return m_local_variable; | |
+ VERIFY(is_property_reference()); | |
+ if (is_super_reference()) | |
+ return m_this_value; | |
+ return m_base_value; | |
} | |
- bool is_global_variable() const | |
+ // 6.2.4.3 IsSuperReference ( V ), https://tc39.es/ecma262/#sec-issuperreference | |
+ bool is_super_reference() const | |
{ | |
- return m_global_variable; | |
+ return !m_this_value.is_empty(); | |
} | |
void put(GlobalObject&, Value); | |
@@ -72,11 +102,14 @@ public: | |
private: | |
void throw_reference_error(GlobalObject&); | |
- Value m_base; | |
+ BaseType m_base_type { BaseType::Unresolvable }; | |
+ union { | |
+ Value m_base_value; | |
+ EnvironmentRecord* m_base_environment_record; | |
+ }; | |
PropertyName m_name; | |
+ Value m_this_value; | |
bool m_strict { false }; | |
- bool m_local_variable { false }; | |
- bool m_global_variable { false }; | |
}; | |
} | |
diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp | |
index 9f657e8ad..d020dfd35 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp | |
@@ -166,7 +166,7 @@ Value ScriptFunction::execute_function_body() | |
if (i >= call_frame_args.size()) | |
call_frame_args.resize(i + 1); | |
call_frame_args[i] = argument_value; | |
- vm.assign(param, argument_value, global_object(), true, vm.lexical_environment()); | |
+ vm.assign(param, argument_value, global_object()); | |
}); | |
if (vm.exception()) | |
diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp | |
index e805bcba5..583109919 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/VM.cpp | |
+++ b/Userland/Libraries/LibJS/Runtime/VM.cpp | |
@@ -11,7 +11,6 @@ | |
#include <LibJS/Interpreter.h> | |
#include <LibJS/Runtime/AbstractOperations.h> | |
#include <LibJS/Runtime/Array.h> | |
-#include <LibJS/Runtime/Error.h> | |
#include <LibJS/Runtime/FinalizationRegistry.h> | |
#include <LibJS/Runtime/FunctionEnvironmentRecord.h> | |
#include <LibJS/Runtime/GlobalEnvironmentRecord.h> | |
@@ -131,74 +130,26 @@ Symbol* VM::get_global_symbol(const String& description) | |
return new_global_symbol; | |
} | |
-void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope) | |
+void VM::set_variable(FlyString const& name, Value value, GlobalObject& global_object) | |
{ | |
- Optional<Variable> possible_match; | |
- if (!specific_scope && m_call_stack.size()) { | |
- for (auto* environment_record = lexical_environment(); environment_record; environment_record = environment_record->outer_environment()) { | |
- possible_match = environment_record->get_from_environment_record(name); | |
- if (possible_match.has_value()) { | |
- specific_scope = environment_record; | |
- break; | |
- } | |
- } | |
- } | |
- | |
- if (specific_scope && possible_match.has_value()) { | |
- if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { | |
- throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst); | |
- return; | |
- } | |
- | |
- specific_scope->put_into_environment_record(name, { value, possible_match.value().declaration_kind }); | |
- return; | |
- } | |
- | |
- if (specific_scope) { | |
- specific_scope->put_into_environment_record(name, { value, DeclarationKind::Var }); | |
- return; | |
- } | |
- | |
- global_object.put(name, value); | |
-} | |
- | |
-bool VM::delete_variable(FlyString const& name) | |
-{ | |
- EnvironmentRecord* specific_scope = nullptr; | |
- Optional<Variable> possible_match; | |
- if (!m_call_stack.is_empty()) { | |
- for (auto* environment_record = lexical_environment(); environment_record; environment_record = environment_record->outer_environment()) { | |
- possible_match = environment_record->get_from_environment_record(name); | |
- if (possible_match.has_value()) { | |
- specific_scope = environment_record; | |
- break; | |
- } | |
- } | |
- } | |
- | |
- if (!possible_match.has_value()) | |
- return false; | |
- if (possible_match.value().declaration_kind == DeclarationKind::Const) | |
- return false; | |
- | |
- VERIFY(specific_scope); | |
- return specific_scope->delete_from_environment_record(name); | |
+ auto reference = resolve_binding(global_object, name); | |
+ return reference.put(global_object, value); | |
} | |
-void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope) | |
+void VM::assign(FlyString const& target, Value value, GlobalObject& global_object) | |
{ | |
- set_variable(target, move(value), global_object, first_assignment, specific_scope); | |
+ set_variable(target, value, global_object); | |
} | |
-void VM::assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope) | |
+void VM::assign(Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> const& target, Value value, GlobalObject& global_object) | |
{ | |
if (auto id_ptr = target.get_pointer<NonnullRefPtr<Identifier>>()) | |
- return assign((*id_ptr)->string(), move(value), global_object, first_assignment, specific_scope); | |
+ return assign((*id_ptr)->string(), value, global_object); | |
- assign(target.get<NonnullRefPtr<BindingPattern>>(), move(value), global_object, first_assignment, specific_scope); | |
+ assign(target.get<NonnullRefPtr<BindingPattern>>(), value, global_object); | |
} | |
-void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, GlobalObject& global_object, bool first_assignment, EnvironmentRecord* specific_scope) | |
+void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, GlobalObject& global_object) | |
{ | |
auto& binding = *target; | |
@@ -267,10 +218,10 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global | |
entry.alias.visit( | |
[&](Empty) {}, | |
[&](NonnullRefPtr<Identifier> const& identifier) { | |
- set_variable(identifier->string(), value, global_object, first_assignment, specific_scope); | |
+ set_variable(identifier->string(), value, global_object); | |
}, | |
[&](NonnullRefPtr<BindingPattern> const& pattern) { | |
- assign(pattern, value, global_object, first_assignment, specific_scope); | |
+ assign(pattern, value, global_object); | |
}); | |
if (entry.is_rest) | |
@@ -335,15 +286,15 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global | |
property.alias.visit( | |
[&](Empty) { | |
- set_variable(assignment_name.to_string(), value_to_assign, global_object, first_assignment, specific_scope); | |
+ set_variable(assignment_name.to_string(), value_to_assign, global_object); | |
}, | |
[&](NonnullRefPtr<Identifier> const& identifier) { | |
VERIFY(!property.is_rest); | |
- set_variable(identifier->string(), value_to_assign, global_object, first_assignment, specific_scope); | |
+ set_variable(identifier->string(), value_to_assign, global_object); | |
}, | |
[&](NonnullRefPtr<BindingPattern> const& pattern) { | |
VERIFY(!property.is_rest); | |
- assign(pattern, value_to_assign, global_object, first_assignment, specific_scope); | |
+ assign(pattern, value_to_assign, global_object); | |
}); | |
if (property.is_rest) | |
@@ -356,6 +307,9 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global | |
Value VM::get_variable(const FlyString& name, GlobalObject& global_object) | |
{ | |
+ if (m_call_stack.is_empty()) | |
+ return global_object.get(name); | |
+ | |
if (!m_call_stack.is_empty()) { | |
if (name == names.arguments.as_string() && !call_frame().callee.is_empty()) { | |
// HACK: Special handling for the name "arguments": | |
@@ -375,29 +329,25 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object) | |
} | |
return call_frame().arguments_object; | |
} | |
+ } | |
- for (auto* environment_record = lexical_environment(); environment_record; environment_record = environment_record->outer_environment()) { | |
- auto possible_match = environment_record->get_from_environment_record(name); | |
- if (exception()) | |
- return {}; | |
- if (possible_match.has_value()) | |
- return possible_match.value().value; | |
- } | |
+ auto reference = resolve_binding(global_object, name); | |
+ if (reference.is_unresolvable()) { | |
+ if (m_underscore_is_last_value && name == "_"sv) | |
+ return m_last_value; | |
} | |
- auto value = global_object.get(name); | |
- if (m_underscore_is_last_value && name == "_" && value.is_empty()) | |
- return m_last_value; | |
- return value; | |
+ return reference.get(global_object); | |
} | |
-Reference VM::get_reference(const FlyString& name) | |
+Reference VM::resolve_binding(GlobalObject& global_object, FlyString const& name, EnvironmentRecord* environment) | |
{ | |
- for (auto* environment_record = lexical_environment(); environment_record && environment_record->outer_environment(); environment_record = environment_record->outer_environment()) { | |
- auto possible_match = environment_record->get_from_environment_record(name); | |
- if (possible_match.has_value()) | |
- return { Reference::LocalVariable, name }; | |
- } | |
- return { Reference::GlobalVariable, name }; | |
+ if (!environment) | |
+ environment = lexical_environment(); | |
+ | |
+ // FIXME: 3. If the code matching the syntactic production that is being evaluated is contained in strict mode code, let strict be true; else let strict be false. | |
+ bool strict = in_strict_mode(); | |
+ | |
+ return get_identifier_reference(global_object, environment, name, strict); | |
} | |
Value VM::construct(Function& function, Function& new_target, Optional<MarkedValueList> arguments) | |
diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h | |
index 44c893825..b4d4ec5b1 100644 | |
--- a/Userland/Libraries/LibJS/Runtime/VM.h | |
+++ b/Userland/Libraries/LibJS/Runtime/VM.h | |
@@ -195,14 +195,13 @@ public: | |
ScopeType unwind_until() const { return m_unwind_until; } | |
FlyString unwind_until_label() const { return m_unwind_until_label; } | |
- Value get_variable(const FlyString& name, GlobalObject&); | |
- void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr); | |
- bool delete_variable(FlyString const& name); | |
- void assign(const Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>>& target, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr); | |
- void assign(const FlyString& target, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr); | |
- void assign(const NonnullRefPtr<BindingPattern>& target, Value, GlobalObject&, bool first_assignment = false, EnvironmentRecord* specific_scope = nullptr); | |
- | |
- Reference get_reference(const FlyString& name); | |
+ Value get_variable(FlyString const& name, GlobalObject&); | |
+ void set_variable(FlyString const& name, Value, GlobalObject&); | |
+ void assign(Variant<NonnullRefPtr<Identifier>, NonnullRefPtr<BindingPattern>> const& target, Value, GlobalObject&); | |
+ void assign(FlyString const& target, Value, GlobalObject&); | |
+ void assign(NonnullRefPtr<BindingPattern> const& target, Value, GlobalObject&); | |
+ | |
+ Reference resolve_binding(GlobalObject&, FlyString const& name, EnvironmentRecord* = nullptr); | |
template<typename T, typename... Args> | |
void throw_exception(GlobalObject& global_object, Args&&... args) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment