diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-11-09 22:52:21 +0200 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-11-10 08:48:27 +0000 |
commit | 46dabf02ecf0abb432643c6e6db37ce4ae279585 (patch) | |
tree | ae4bc4c1d12790f21e6e436f032d327854842c4f | |
parent | 681787de7656a31f4f010f93b8319e444b7014fa (diff) | |
download | serenity-46dabf02ecf0abb432643c6e6db37ce4ae279585.zip |
LibJS: Add support for await expressions
-rw-r--r-- | Userland/Libraries/LibJS/AST.cpp | 23 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/AST.h | 15 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Parser.cpp | 32 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Parser.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Completion.cpp | 98 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Completion.h | 2 |
7 files changed, 169 insertions, 4 deletions
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 8158063e93..574cd455e8 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -28,6 +28,8 @@ #include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/ObjectEnvironment.h> #include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/PromiseConstructor.h> +#include <LibJS/Runtime/PromiseReaction.h> #include <LibJS/Runtime/Reference.h> #include <LibJS/Runtime/RegExpObject.h> #include <LibJS/Runtime/Shape.h> @@ -393,6 +395,21 @@ Value YieldExpression::execute(Interpreter&, GlobalObject&) const VERIFY_NOT_REACHED(); } +// 15.8.5 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-async-function-definitions-runtime-semantics-evaluation +Value AwaitExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const +{ + InterpreterNodeScope node_scope { interpreter, *this }; + + // 1. Let exprRef be the result of evaluating UnaryExpression. + // 2. Let value be ? GetValue(exprRef). + auto value = m_argument->execute(interpreter, global_object); + if (interpreter.exception()) + return {}; + + // 3. Return ? Await(value). + return TRY_OR_DISCARD(await(global_object, value)); +} + Value ReturnStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; @@ -1973,6 +1990,12 @@ void YieldExpression::dump(int indent) const argument()->dump(indent + 1); } +void AwaitExpression::dump(int indent) const +{ + ASTNode::dump(indent); + m_argument->dump(indent + 1); +} + void ReturnStatement::dump(int indent) const { ASTNode::dump(indent); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index c8f0e213f6..ff0b14e4a8 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -565,6 +565,21 @@ private: bool m_is_yield_from { false }; }; +class AwaitExpression final : public Expression { +public: + explicit AwaitExpression(SourceRange source_range, NonnullRefPtr<Expression> argument) + : Expression(source_range) + , m_argument(move(argument)) + { + } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + virtual void dump(int indent) const override; + +private: + NonnullRefPtr<Expression> m_argument; +}; + class ReturnStatement final : public Statement { public: explicit ReturnStatement(SourceRange source_range, RefPtr<Expression> argument) diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 607863fa85..e79b43f680 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -49,6 +49,7 @@ set(SOURCES Runtime/BooleanObject.cpp Runtime/BooleanPrototype.cpp Runtime/BoundFunction.cpp + Runtime/Completion.cpp Runtime/ConsoleObject.cpp Runtime/DataView.cpp Runtime/DataViewConstructor.cpp diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 11a2a85467..730207be93 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -3,6 +3,7 @@ * Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021, David Tuin <davidot@serenityos.org> * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -420,6 +421,7 @@ Associativity Parser::operator_associativity(TokenType type) const case TokenType::Typeof: case TokenType::Void: case TokenType::Delete: + case TokenType::Await: case TokenType::Ampersand: case TokenType::Caret: case TokenType::Pipe: @@ -1042,7 +1044,8 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ TemporaryChange function_context_rollback(m_state.in_function_context, false); TemporaryChange generator_function_context_rollback(m_state.in_generator_function_context, false); TemporaryChange async_function_context_rollback(m_state.in_async_function_context, false); - TemporaryChange in_class_field_initializer_rollback(m_state.in_class_field_initializer, true); + TemporaryChange class_field_initializer_rollback(m_state.in_class_field_initializer, true); + TemporaryChange class_static_init_block_rollback(m_state.in_class_static_init_block, true); ScopePusher static_init_scope = ScopePusher::static_init_block_scope(*this, *static_init_block); parse_statement_list(static_init_block); @@ -1253,6 +1256,10 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() if (!m_state.in_generator_function_context) goto read_as_identifier; return { parse_yield_expression(), false }; + case TokenType::Await: + if (!m_state.in_async_function_context) + goto read_as_identifier; + return { parse_await_expression() }; case TokenType::PrivateIdentifier: VERIFY(next_token().type() == TokenType::In); if (!is_private_identifier_valid()) @@ -1934,6 +1941,7 @@ RefPtr<BindingPattern> Parser::synthesize_binding_pattern(Expression const& expr parser.m_state.in_continue_context = m_state.in_continue_context; parser.m_state.string_legacy_octal_escape_sequence_in_scope = m_state.string_legacy_octal_escape_sequence_in_scope; parser.m_state.in_class_field_initializer = m_state.in_class_field_initializer; + parser.m_state.in_class_static_init_block = m_state.in_class_static_init_block; auto result = parser.parse_binding_pattern(AllowDuplicates::Yes, AllowMemberExpressions::Yes); if (parser.has_errors()) @@ -2085,6 +2093,22 @@ NonnullRefPtr<YieldExpression> Parser::parse_yield_expression() return create_ast_node<YieldExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(argument), yield_from); } +NonnullRefPtr<AwaitExpression> Parser::parse_await_expression() +{ + auto rule_start = push_start(); + + if (m_state.in_formal_parameter_context) + syntax_error("'Await' expression is not allowed in formal parameters of an async function"); + + consume(TokenType::Await); + + auto precedence = g_operator_precedence.get(TokenType::Await); + auto associativity = operator_associativity(TokenType::Await); + auto argument = parse_expression(precedence, associativity); + + return create_ast_node<AwaitExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(argument)); +} + NonnullRefPtr<ReturnStatement> Parser::parse_return_statement() { auto rule_start = push_start(); @@ -3393,14 +3417,14 @@ bool Parser::match_identifier() const if (m_state.current_token.value() == "yield"sv) return !m_state.strict_mode && !m_state.in_generator_function_context; if (m_state.current_token.value() == "await"sv) - return m_program_type != Program::Type::Module && !m_state.in_async_function_context; + return m_program_type != Program::Type::Module && !m_state.in_async_function_context && !m_state.in_class_static_init_block; return true; } return m_state.current_token.type() == TokenType::Identifier || m_state.current_token.type() == TokenType::Async || (m_state.current_token.type() == TokenType::Let && !m_state.strict_mode) - || (m_state.current_token.type() == TokenType::Await && m_program_type != Program::Type::Module && !m_state.in_async_function_context) + || (m_state.current_token.type() == TokenType::Await && m_program_type != Program::Type::Module && !m_state.in_async_function_context && !m_state.in_class_static_init_block) || (m_state.current_token.type() == TokenType::Yield && !m_state.in_generator_function_context && !m_state.strict_mode); // See note in Parser::parse_identifier(). } @@ -3482,7 +3506,7 @@ Token Parser::consume_identifier() } if (match(TokenType::Await)) { - if (m_program_type == Program::Type::Module || m_state.in_async_function_context) + if (m_program_type == Program::Type::Module || m_state.in_async_function_context || m_state.in_class_static_init_block) syntax_error("Identifier must not be a reserved word in modules ('await')"); return consume(); } diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 3bcd52b626..42c52ea939 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -106,6 +106,7 @@ public: NonnullRefPtr<ClassDeclaration> parse_class_declaration(); NonnullRefPtr<ClassExpression> parse_class_expression(bool expect_class_name); NonnullRefPtr<YieldExpression> parse_yield_expression(); + NonnullRefPtr<AwaitExpression> parse_await_expression(); NonnullRefPtr<Expression> parse_property_key(); NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity); NonnullRefPtr<Identifier> parse_identifier(); @@ -258,6 +259,7 @@ private: bool in_continue_context { false }; bool string_legacy_octal_escape_sequence_in_scope { false }; bool in_class_field_initializer { false }; + bool in_class_static_init_block { false }; bool function_might_need_arguments_object { false }; ParserState(Lexer, Program::Type); diff --git a/Userland/Libraries/LibJS/Runtime/Completion.cpp b/Userland/Libraries/LibJS/Runtime/Completion.cpp new file mode 100644 index 0000000000..fd70754bbc --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Completion.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/TypeCasts.h> +#include <LibJS/Runtime/Completion.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/PromiseConstructor.h> +#include <LibJS/Runtime/PromiseReaction.h> +#include <LibJS/Runtime/VM.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +// 6.2.3.1 Await, https://tc39.es/ecma262/#await +ThrowCompletionOr<Value> await(GlobalObject& global_object, Value value) +{ + auto& vm = global_object.vm(); + + // 1. Let asyncContext be the running execution context. + auto& async_context = vm.running_execution_context(); + + // 2. Let promise be ? PromiseResolve(%Promise%, value). + auto* promise = TRY(promise_resolve(global_object, *global_object.promise_constructor(), value)); + + bool success = false; + Value result; + // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: + auto fulfilled_closure = [&async_context, &success, &result](VM& vm, GlobalObject& global_object) -> ThrowCompletionOr<Value> { + VERIFY(!vm.argument(0).is_undefined()); + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // FIXME: We don't have this concept yet. + + // NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead + success = true; + result = vm.argument(0); + + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + vm.push_execution_context(async_context, global_object); + + // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // FIXME: We don't have this concept yet. + + // f. Return undefined. + return js_undefined(); + }; + + // 4. Let onFulfilled be ! CreateBuiltinFunction(fulfilledClosure, 1, "", « »). + auto on_fulfilled = NativeFunction::create(global_object, "", move(fulfilled_closure)); + + // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: + auto rejected_closure = [&async_context, &success, &result](VM& vm, GlobalObject& global_object) -> ThrowCompletionOr<Value> { + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // FIXME: We don't have this concept yet. + + // NOTE: Since we don't support context suspension, we exfiltrate the result to await()'s scope instead + success = false; + result = vm.argument(0); + + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + vm.push_execution_context(async_context, global_object); + + // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // FIXME: We don't have this concept yet. + + // f. Return undefined. + return js_undefined(); + }; + + // 6. Let onRejected be ! CreateBuiltinFunction(rejectedClosure, 1, "", « »). + auto on_rejected = NativeFunction::create(global_object, "", move(rejected_closure)); + + // 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected). + verify_cast<Promise>(promise)->perform_then(on_fulfilled, on_rejected, {}); + + // 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. + vm.pop_execution_context(); + + // 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available. + // 10. Return. + // 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext. + // FIXME: Since we don't support context suspension, we synchronously execute the promise + vm.run_queued_promise_jobs(); + + if (success) + return result; + else + return throw_completion(result); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Completion.h b/Userland/Libraries/LibJS/Runtime/Completion.h index 1486aba275..c6cefab011 100644 --- a/Userland/Libraries/LibJS/Runtime/Completion.h +++ b/Userland/Libraries/LibJS/Runtime/Completion.h @@ -165,6 +165,8 @@ public: using ThrowCompletionOr<Empty>::ThrowCompletionOr; }; +ThrowCompletionOr<Value> await(GlobalObject&, Value); + // 6.2.3.2 NormalCompletion ( value ), https://tc39.es/ecma262/#sec-normalcompletion inline Completion normal_completion(Optional<Value> value) { |