summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIdan Horowitz <idan.horowitz@gmail.com>2021-11-09 22:52:21 +0200
committerLinus Groh <mail@linusgroh.de>2021-11-10 08:48:27 +0000
commit46dabf02ecf0abb432643c6e6db37ce4ae279585 (patch)
treeae4bc4c1d12790f21e6e436f032d327854842c4f
parent681787de7656a31f4f010f93b8319e444b7014fa (diff)
downloadserenity-46dabf02ecf0abb432643c6e6db37ce4ae279585.zip
LibJS: Add support for await expressions
-rw-r--r--Userland/Libraries/LibJS/AST.cpp23
-rw-r--r--Userland/Libraries/LibJS/AST.h15
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibJS/Parser.cpp32
-rw-r--r--Userland/Libraries/LibJS/Parser.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Completion.cpp98
-rw-r--r--Userland/Libraries/LibJS/Runtime/Completion.h2
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)
{