summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibJS
diff options
context:
space:
mode:
authorIdan Horowitz <idan.horowitz@gmail.com>2021-11-09 20:39:22 +0200
committerLinus Groh <mail@linusgroh.de>2021-11-10 08:48:27 +0000
commit681787de7656a31f4f010f93b8319e444b7014fa (patch)
tree0f4b2fdb817ee1213413498b655054c34bc11c99 /Userland/Libraries/LibJS
parent05c3320da366ba27560e77c010feffdc9afc4b72 (diff)
downloadserenity-681787de7656a31f4f010f93b8319e444b7014fa.zip
LibJS: Add support for async functions
This commit adds support for the most bare bones version of async functions, support for async generator functions, async arrow functions and await expressions are TODO.
Diffstat (limited to 'Userland/Libraries/LibJS')
-rw-r--r--Userland/Libraries/LibJS/AST.cpp2
-rw-r--r--Userland/Libraries/LibJS/AST.h1
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt2
-rw-r--r--Userland/Libraries/LibJS/Forward.h1
-rw-r--r--Userland/Libraries/LibJS/Lexer.cpp1
-rw-r--r--Userland/Libraries/LibJS/Parser.cpp126
-rw-r--r--Userland/Libraries/LibJS/Parser.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp60
-rw-r--r--Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h29
-rw-r--r--Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp26
-rw-r--r--Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h22
-rw-r--r--Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp128
-rw-r--r--Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h5
-rw-r--r--Userland/Libraries/LibJS/Runtime/ExecutionContext.h24
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp3
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.cpp2
-rw-r--r--Userland/Libraries/LibJS/Token.cpp1
17 files changed, 399 insertions, 36 deletions
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp
index f4387762af..8158063e93 100644
--- a/Userland/Libraries/LibJS/AST.cpp
+++ b/Userland/Libraries/LibJS/AST.cpp
@@ -1921,7 +1921,7 @@ void BindingPattern::dump(int indent) const
void FunctionNode::dump(int indent, String const& class_name) const
{
print_indent(indent);
- outln("{}{} '{}'", class_name, m_kind == FunctionKind::Generator ? "*" : "", name());
+ outln("{}{}{} '{}'", class_name, m_kind == FunctionKind::Async ? " async" : "", m_kind == FunctionKind::Generator ? "*" : "", name());
if (m_contains_direct_call_to_eval) {
print_indent(indent + 1);
outln("\033[31;1m(direct eval)\033[0m");
diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h
index b8e7036385..c8f0e213f6 100644
--- a/Userland/Libraries/LibJS/AST.h
+++ b/Userland/Libraries/LibJS/AST.h
@@ -36,6 +36,7 @@ class VariableDeclaration;
enum class FunctionKind {
Generator,
Regular,
+ Async,
};
template<class T, class... Args>
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt
index 3b1c4218c2..607863fa85 100644
--- a/Userland/Libraries/LibJS/CMakeLists.txt
+++ b/Userland/Libraries/LibJS/CMakeLists.txt
@@ -38,6 +38,8 @@ set(SOURCES
Runtime/ArrayIterator.cpp
Runtime/ArrayIteratorPrototype.cpp
Runtime/ArrayPrototype.cpp
+ Runtime/AsyncFunctionConstructor.cpp
+ Runtime/AsyncFunctionPrototype.cpp
Runtime/AtomicsObject.cpp
Runtime/BigInt.cpp
Runtime/BigIntConstructor.cpp
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
index f2a374aed4..c589c601ed 100644
--- a/Userland/Libraries/LibJS/Forward.h
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -17,6 +17,7 @@
__JS_ENUMERATE(AggregateError, aggregate_error, AggregateErrorPrototype, AggregateErrorConstructor, void) \
__JS_ENUMERATE(Array, array, ArrayPrototype, ArrayConstructor, void) \
__JS_ENUMERATE(ArrayBuffer, array_buffer, ArrayBufferPrototype, ArrayBufferConstructor, void) \
+ __JS_ENUMERATE(AsyncFunction, async_function, AsyncFunctionPrototype, AsyncFunctionConstructor, void) \
__JS_ENUMERATE(BigIntObject, bigint, BigIntPrototype, BigIntConstructor, void) \
__JS_ENUMERATE(BooleanObject, boolean, BooleanPrototype, BooleanConstructor, void) \
__JS_ENUMERATE(DataView, data_view, DataViewPrototype, DataViewConstructor, void) \
diff --git a/Userland/Libraries/LibJS/Lexer.cpp b/Userland/Libraries/LibJS/Lexer.cpp
index b46f5daa83..907e4920dd 100644
--- a/Userland/Libraries/LibJS/Lexer.cpp
+++ b/Userland/Libraries/LibJS/Lexer.cpp
@@ -30,6 +30,7 @@ Lexer::Lexer(StringView source, StringView filename, size_t line_number, size_t
, m_parsed_identifiers(adopt_ref(*new ParsedIdentifiers))
{
if (s_keywords.is_empty()) {
+ s_keywords.set("async", TokenType::Async);
s_keywords.set("await", TokenType::Await);
s_keywords.set("break", TokenType::Break);
s_keywords.set("case", TokenType::Case);
diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp
index f2f275dd44..11a2a85467 100644
--- a/Userland/Libraries/LibJS/Parser.cpp
+++ b/Userland/Libraries/LibJS/Parser.cpp
@@ -502,6 +502,8 @@ NonnullRefPtr<Program> Parser::parse_program(bool starts_in_strict_mode)
NonnullRefPtr<Declaration> Parser::parse_declaration()
{
auto rule_start = push_start();
+ if (m_state.current_token.type() == TokenType::Async && next_token().type() == TokenType::Function)
+ return parse_function_node<FunctionDeclaration>();
switch (m_state.current_token.type()) {
case TokenType::Class:
return parse_class_declaration();
@@ -572,7 +574,7 @@ NonnullRefPtr<Statement> Parser::parse_statement(AllowLabelledFunction allow_lab
return result.release_nonnull();
}
if (match_expression()) {
- if (match(TokenType::Function) || match(TokenType::Class))
+ if (match(TokenType::Function) || (match(TokenType::Async) && next_token().type() == TokenType::Function) || match(TokenType::Class))
syntax_error(String::formatted("{} declaration not allowed in single-statement context", m_state.current_token.name()));
if (match(TokenType::Let) && next_token().type() == TokenType::BracketOpen)
syntax_error(String::formatted("let followed by [ is not allowed in single-statement context"));
@@ -748,7 +750,7 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all
return {};
}
- if (m_state.current_token.value() == "await"sv && m_program_type == Program::Type::Module) {
+ if (m_state.current_token.value() == "await"sv && (m_program_type == Program::Type::Module || m_state.in_async_function_context)) {
return {};
}
@@ -792,6 +794,8 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all
m_state.current_scope_pusher->add_declaration(function_declaration);
if (function_declaration->kind() == FunctionKind::Generator)
syntax_error("Generator functions cannot be defined in labelled statements");
+ if (function_declaration->kind() == FunctionKind::Async)
+ syntax_error("Async functions cannot be defined in labelled statements");
labelled_statement = move(function_declaration);
} else {
@@ -899,6 +903,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
bool is_static = false;
bool is_constructor = false;
bool is_generator = false;
+ bool is_async = false;
auto method_kind = ClassMethod::Kind::Method;
if (match(TokenType::Semicolon)) {
@@ -906,6 +911,11 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
continue;
}
+ if (match(TokenType::Async)) {
+ consume();
+ is_async = true;
+ }
+
if (match(TokenType::Asterisk)) {
consume();
is_generator = true;
@@ -913,10 +923,14 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
StringView name;
if (match_property_key() || match(TokenType::PrivateIdentifier)) {
- if (!is_generator && m_state.current_token.original_value() == "static"sv) {
+ if (!is_generator && !is_async && m_state.current_token.original_value() == "static"sv) {
if (match(TokenType::Identifier)) {
consume();
is_static = true;
+ if (match(TokenType::Async)) {
+ consume();
+ is_async = true;
+ }
if (match(TokenType::Asterisk)) {
consume();
is_generator = true;
@@ -995,12 +1009,17 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
// It is a Syntax Error if PropName of MethodDefinition is "prototype".
if (is_static && name == "prototype"sv)
syntax_error("Classes may not have a static property named 'prototype'");
- } else if ((match(TokenType::ParenOpen) || match(TokenType::Equals)) && (is_static || method_kind != ClassMethod::Kind::Method)) {
+ } else if ((match(TokenType::ParenOpen) || match(TokenType::Equals)) && (is_static || is_async || method_kind != ClassMethod::Kind::Method)) {
switch (method_kind) {
case ClassMethod::Kind::Method:
- VERIFY(is_static);
- name = "static";
- is_static = false;
+ if (is_async) {
+ name = "async";
+ is_async = false;
+ } else {
+ VERIFY(is_static);
+ name = "static";
+ is_static = false;
+ }
break;
case ClassMethod::Kind::Getter:
name = "get";
@@ -1022,6 +1041,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
TemporaryChange continue_context_rollback(m_state.in_continue_context, false);
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);
ScopePusher static_init_scope = ScopePusher::static_init_block_scope(*this, *static_init_block);
@@ -1042,6 +1062,8 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
syntax_error("Classes may not have more than one constructor");
if (is_generator)
syntax_error("Class constructor may not be a generator");
+ if (is_async)
+ syntax_error("Class constructor may not be async");
is_constructor = true;
}
@@ -1057,6 +1079,8 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
parse_options |= FunctionNodeParseOptions::IsSetterFunction;
if (is_generator)
parse_options |= FunctionNodeParseOptions::IsGeneratorFunction;
+ if (is_async)
+ parse_options |= FunctionNodeParseOptions::IsAsyncFunction;
auto function = parse_function_node<FunctionExpression>(parse_options);
if (is_constructor) {
constructor = move(function);
@@ -1065,7 +1089,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
} else {
syntax_error("No key for class method");
}
- } else if (is_generator) {
+ } else if (is_generator || is_async) {
expected("ParenOpen");
consume();
} else if (property_key.is_null()) {
@@ -1156,6 +1180,8 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
auto& function = static_cast<FunctionExpression&>(*expression);
if (function.kind() == FunctionKind::Generator && function.name() == "yield"sv)
syntax_error("function is not allowed to be called 'yield' in this context", function.source_range().start);
+ if (function.kind() == FunctionKind::Async && function.name() == "await"sv)
+ syntax_error("function is not allowed to be called 'await' in this context", function.source_range().start);
}
return { move(expression) };
}
@@ -1170,7 +1196,7 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
syntax_error("'super' keyword unexpected here");
return { create_ast_node<SuperExpression>({ m_state.current_token.filename(), rule_start.position(), position() }) };
case TokenType::EscapedKeyword:
- if (m_state.strict_mode || (m_state.current_token.value() != "let"sv && (m_state.in_generator_function_context || m_state.current_token.value() != "yield"sv)))
+ if (m_state.strict_mode || (m_state.current_token.value() != "let"sv && (m_state.in_generator_function_context || m_state.current_token.value() != "yield"sv) && (m_state.in_async_function_context || m_state.current_token.value() != "await"sv)))
syntax_error("Keyword must not contain escaped characters");
[[fallthrough]];
case TokenType::Identifier: {
@@ -1201,6 +1227,10 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
return { create_ast_node<NullLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }) };
case TokenType::CurlyOpen:
return { parse_object_expression() };
+ case TokenType::Async:
+ if (next_token().type() != TokenType::Function)
+ goto read_as_identifier;
+ [[fallthrough]];
case TokenType::Function:
return { parse_function_node<FunctionExpression>() };
case TokenType::BracketOpen:
@@ -1409,10 +1439,14 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
consume();
property_type = ObjectProperty::Type::KeyValue;
property_name = parse_property_key();
- function_kind = FunctionKind ::Generator;
+ function_kind = FunctionKind::Generator;
} else if (match_identifier()) {
auto identifier = consume();
- if (identifier.original_value() == "get"sv && match_property_key()) {
+ if (identifier.original_value() == "async" && match_property_key()) {
+ property_type = ObjectProperty::Type::KeyValue;
+ property_name = parse_property_key();
+ function_kind = FunctionKind::Async;
+ } else if (identifier.original_value() == "get"sv && match_property_key()) {
property_type = ObjectProperty::Type::Getter;
property_name = parse_property_key();
} else if (identifier.original_value() == "set"sv && match_property_key()) {
@@ -1451,6 +1485,8 @@ NonnullRefPtr<ObjectExpression> Parser::parse_object_expression()
parse_options |= FunctionNodeParseOptions::IsSetterFunction;
if (function_kind == FunctionKind::Generator)
parse_options |= FunctionNodeParseOptions::IsGeneratorFunction;
+ if (function_kind == FunctionKind::Async)
+ parse_options |= FunctionNodeParseOptions::IsAsyncFunction;
auto function = parse_function_node<FunctionExpression>(parse_options);
properties.append(create_ast_node<ObjectProperty>({ m_state.current_token.filename(), rule_start.position(), position() }, *property_name, function, property_type, true));
} else if (match(TokenType::Colon)) {
@@ -1892,6 +1928,7 @@ RefPtr<BindingPattern> Parser::synthesize_binding_pattern(Expression const& expr
parser.m_state.in_function_context = m_state.in_function_context;
parser.m_state.in_formal_parameter_context = m_state.in_formal_parameter_context;
parser.m_state.in_generator_function_context = m_state.in_generator_function_context;
+ parser.m_state.in_async_function_context = m_state.in_async_function_context;
parser.m_state.in_arrow_function_context = m_state.in_arrow_function_context;
parser.m_state.in_break_context = m_state.in_break_context;
parser.m_state.in_continue_context = m_state.in_continue_context;
@@ -2116,6 +2153,9 @@ NonnullRefPtr<FunctionBody> Parser::parse_function_body(Vector<FunctionDeclarati
if (function_kind == FunctionKind::Generator && parameter_name == "yield"sv)
syntax_error("Parameter name 'yield' not allowed in this context");
+ if (function_kind == FunctionKind::Async && parameter_name == "await"sv)
+ syntax_error("Parameter name 'await' not allowed in this context");
+
for (auto& previous_name : parameter_names) {
if (previous_name == parameter_name) {
syntax_error(String::formatted("Duplicate parameter '{}' not allowed in strict mode", parameter_name));
@@ -2129,6 +2169,9 @@ NonnullRefPtr<FunctionBody> Parser::parse_function_body(Vector<FunctionDeclarati
if (function_kind == FunctionKind::Generator && bound_name == "yield"sv)
syntax_error("Parameter name 'yield' not allowed in this context");
+ if (function_kind == FunctionKind::Async && bound_name == "await"sv)
+ syntax_error("Parameter name 'await' not allowed in this context");
+
for (auto& previous_name : parameter_names) {
if (previous_name == bound_name) {
syntax_error(String::formatted("Duplicate parameter '{}' not allowed in strict mode", bound_name));
@@ -2173,16 +2216,27 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
TemporaryChange might_need_arguments_object_rollback(m_state.function_might_need_arguments_object, false);
constexpr auto is_function_expression = IsSame<FunctionNodeType, FunctionExpression>;
- auto function_kind = (parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0 ? FunctionKind::Generator : FunctionKind::Regular;
+ FunctionKind function_kind;
+ if ((parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0 && (parse_options & FunctionNodeParseOptions::IsAsyncFunction) != 0)
+ TODO();
+ else if ((parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0)
+ function_kind = FunctionKind::Generator;
+ else if ((parse_options & FunctionNodeParseOptions::IsAsyncFunction) != 0)
+ function_kind = FunctionKind::Async;
+ else
+ function_kind = FunctionKind::Regular;
String name;
if (parse_options & FunctionNodeParseOptions::CheckForFunctionAndName) {
+ if (function_kind == FunctionKind::Regular && match(TokenType::Async) && !next_token().trivia_contains_line_terminator()) {
+ function_kind = FunctionKind::Async;
+ consume(TokenType::Async);
+ parse_options = parse_options | FunctionNodeParseOptions::IsAsyncFunction;
+ }
consume(TokenType::Function);
- if (function_kind == FunctionKind::Regular) {
- function_kind = match(TokenType::Asterisk) ? FunctionKind::Generator : FunctionKind::Regular;
- if (function_kind == FunctionKind::Generator) {
- consume(TokenType::Asterisk);
- parse_options = parse_options | FunctionNodeParseOptions::IsGeneratorFunction;
- }
+ if (function_kind == FunctionKind::Regular && match(TokenType::Asterisk)) {
+ function_kind = FunctionKind::Generator;
+ consume(TokenType::Asterisk);
+ parse_options = parse_options | FunctionNodeParseOptions::IsGeneratorFunction;
}
if (FunctionNodeType::must_have_name() || match_identifier())
@@ -2193,6 +2247,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
check_identifier_name_for_assignment_validity(name);
}
TemporaryChange generator_change(m_state.in_generator_function_context, function_kind == FunctionKind::Generator);
+ TemporaryChange async_change(m_state.in_async_function_context, function_kind == FunctionKind::Async);
consume(TokenType::ParenOpen);
i32 function_length = -1;
@@ -2560,6 +2615,13 @@ NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool for_l
target = create_ast_node<Identifier>(
{ m_state.current_token.filename(), rule_start.position(), position() },
consume().value());
+ } else if (!m_state.in_async_function_context && match(TokenType::Async)) {
+ if (m_program_type == Program::Type::Module)
+ syntax_error("Identifier must not be a reserved word in modules ('async')");
+
+ target = create_ast_node<Identifier>(
+ { m_state.current_token.filename(), rule_start.position(), position() },
+ consume().value());
}
if (target.has<Empty>()) {
@@ -2913,7 +2975,7 @@ NonnullRefPtr<CatchClause> Parser::parse_catch_clause()
if (match(TokenType::ParenOpen)) {
should_expect_parameter = true;
consume();
- if (match_identifier_name() && (!match(TokenType::Yield) || !m_state.in_generator_function_context))
+ if (match_identifier_name() && (!match(TokenType::Yield) || !m_state.in_generator_function_context) && (!match(TokenType::Async) || !m_state.in_async_function_context))
parameter = consume().value();
else
pattern_parameter = parse_binding_pattern(AllowDuplicates::No, AllowMemberExpressions::No);
@@ -2978,6 +3040,8 @@ NonnullRefPtr<IfStatement> Parser::parse_if_statement()
auto& function_declaration = static_cast<FunctionDeclaration const&>(*declaration);
if (function_declaration.kind() == FunctionKind::Generator)
syntax_error("Generator functions can only be declared in top-level or within a block");
+ if (function_declaration.kind() == FunctionKind::Async)
+ syntax_error("Async functions can only be declared in top-level or within a block");
block->append(move(declaration));
return block;
};
@@ -3162,6 +3226,7 @@ bool Parser::match_expression() const
|| type == TokenType::BracketOpen
|| type == TokenType::ParenOpen
|| type == TokenType::Function
+ || type == TokenType::Async
|| type == TokenType::This
|| type == TokenType::Super
|| type == TokenType::RegexLiteral
@@ -3280,7 +3345,8 @@ bool Parser::match_declaration() const
return type == TokenType::Function
|| type == TokenType::Class
|| type == TokenType::Const
- || type == TokenType::Let;
+ || type == TokenType::Let
+ || (type == TokenType::Async && next_token().type() == TokenType::Function);
}
Token Parser::next_token() const
@@ -3327,13 +3393,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;
+ return m_program_type != Program::Type::Module && !m_state.in_async_function_context;
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.current_token.type() == TokenType::Await && m_program_type != Program::Type::Module && !m_state.in_async_function_context)
|| (m_state.current_token.type() == TokenType::Yield && !m_state.in_generator_function_context && !m_state.strict_mode); // See note in Parser::parse_identifier().
}
@@ -3414,6 +3481,15 @@ Token Parser::consume_identifier()
return consume();
}
+ if (match(TokenType::Await)) {
+ if (m_program_type == Program::Type::Module || m_state.in_async_function_context)
+ syntax_error("Identifier must not be a reserved word in modules ('await')");
+ return consume();
+ }
+
+ if (match(TokenType::Async))
+ return consume();
+
expected("Identifier");
return consume();
}
@@ -3453,6 +3529,9 @@ Token Parser::consume_identifier_reference()
return consume();
}
+ if (match(TokenType::Async))
+ return consume();
+
expected(Token::name(TokenType::Identifier));
return consume();
}
@@ -3722,11 +3801,10 @@ NonnullRefPtr<ExportStatement> Parser::parse_export_statement(Program& program)
auto class_expression = parse_class_expression(false);
local_name = class_expression->name();
expression = move(class_expression);
- } else if (match(TokenType::Function)) {
+ } else if (match(TokenType::Function) || (match(TokenType::Async) && next_token().type() == TokenType::Function)) {
auto func_expr = parse_function_node<FunctionExpression>();
local_name = func_expr->name();
expression = move(func_expr);
- // TODO: Allow async function
} else if (match_expression()) {
expression = parse_expression(2);
consume_or_insert_semicolon();
diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h
index ba932ca374..3bcd52b626 100644
--- a/Userland/Libraries/LibJS/Parser.h
+++ b/Userland/Libraries/LibJS/Parser.h
@@ -31,6 +31,7 @@ struct FunctionNodeParseOptions {
IsSetterFunction = 1 << 4,
IsArrowFunction = 1 << 5,
IsGeneratorFunction = 1 << 6,
+ IsAsyncFunction = 1 << 7,
};
};
@@ -251,6 +252,7 @@ private:
bool in_function_context { false };
bool in_formal_parameter_context { false };
bool in_generator_function_context { false };
+ bool in_async_function_context { false };
bool in_arrow_function_context { false };
bool in_break_context { false };
bool in_continue_context { false };
diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp
new file mode 100644
index 0000000000..f069c6545e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/AsyncFunctionConstructor.h>
+#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
+#include <LibJS/Runtime/FunctionConstructor.h>
+#include <LibJS/Runtime/FunctionObject.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+AsyncFunctionConstructor::AsyncFunctionConstructor(GlobalObject& global_object)
+ : NativeFunction(vm().names.AsyncFunction.as_string(), *global_object.function_prototype())
+{
+}
+
+void AsyncFunctionConstructor::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ NativeFunction::initialize(global_object);
+
+ // 27.7.2.2 AsyncFunction.prototype, https://tc39.es/ecma262/#sec-async-function-constructor-prototype
+ define_direct_property(vm.names.prototype, global_object.async_function_prototype(), 0);
+
+ define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
+}
+
+// 27.7.1.1 AsyncFunction ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-async-function-constructor-arguments
+ThrowCompletionOr<Value> AsyncFunctionConstructor::call()
+{
+ return TRY(construct(*this));
+}
+
+// 27.7.1.1 AsyncFunction ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-async-function-constructor-arguments
+ThrowCompletionOr<Object*> AsyncFunctionConstructor::construct(FunctionObject& new_target)
+{
+ auto& vm = this->vm();
+ auto function = TRY(FunctionConstructor::create_dynamic_function_node(global_object(), new_target, FunctionKind::Async));
+
+ OwnPtr<Interpreter> local_interpreter;
+ Interpreter* interpreter = vm.interpreter_if_exists();
+
+ if (!interpreter) {
+ local_interpreter = Interpreter::create_with_existing_realm(*realm());
+ interpreter = local_interpreter.ptr();
+ }
+
+ VM::InterpreterExecutionScope scope(*interpreter);
+ auto result = function->execute(*interpreter, global_object());
+ if (auto* exception = vm.exception())
+ return throw_completion(exception->value());
+ VERIFY(result.is_object() && is<ECMAScriptFunctionObject>(result.as_object()));
+ return &result.as_object();
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h
new file mode 100644
index 0000000000..9258740675
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/AST.h>
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class AsyncFunctionConstructor final : public NativeFunction {
+ JS_OBJECT(AsyncFunctionConstructor, NativeFunction);
+
+public:
+ explicit AsyncFunctionConstructor(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~AsyncFunctionConstructor() override = default;
+
+ virtual ThrowCompletionOr<Value> call() override;
+ virtual ThrowCompletionOr<Object*> construct(FunctionObject& new_target) override;
+
+private:
+ virtual bool has_constructor() const override { return true; }
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp
new file mode 100644
index 0000000000..ad3a7ba896
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AsyncFunctionPrototype.h>
+#include <LibJS/Runtime/GlobalObject.h>
+
+namespace JS {
+
+AsyncFunctionPrototype::AsyncFunctionPrototype(GlobalObject& global_object)
+ : Object(*global_object.function_prototype())
+{
+}
+
+void AsyncFunctionPrototype::initialize(GlobalObject& global_object)
+{
+ auto& vm = this->vm();
+ Object::initialize(global_object);
+
+ // 27.7.3.2 AsyncFunction.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-async-function-prototype-properties-toStringTag
+ define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, vm.names.AsyncFunction.as_string()), Attribute::Configurable);
+}
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h
new file mode 100644
index 0000000000..ac8d91654e
--- /dev/null
+++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS {
+
+class AsyncFunctionPrototype final : public Object {
+ JS_OBJECT(AsyncFunctionPrototype, Object);
+
+public:
+ explicit AsyncFunctionPrototype(GlobalObject&);
+ virtual void initialize(GlobalObject&) override;
+ virtual ~AsyncFunctionPrototype() override = default;
+};
+
+}
diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
index 735cd32d8a..e3b02dfc7e 100644
--- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
@@ -15,11 +15,14 @@
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/FunctionEnvironment.h>
#include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/GeneratorObjectPrototype.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/NativeFunction.h>
+#include <LibJS/Runtime/PromiseConstructor.h>
+#include <LibJS/Runtime/PromiseReaction.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
@@ -34,6 +37,9 @@ ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_
case FunctionKind::Generator:
prototype = global_object.generator_function_prototype();
break;
+ case FunctionKind::Async:
+ prototype = global_object.async_function_prototype();
+ break;
}
return global_object.heap().allocate<ECMAScriptFunctionObject>(global_object, move(name), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, *prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function);
}
@@ -96,6 +102,8 @@ void ECMAScriptFunctionObject::initialize(GlobalObject& global_object)
// prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
set_prototype(global_object.generator_object_prototype());
break;
+ case FunctionKind::Async:
+ break;
}
define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
}
@@ -659,6 +667,84 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(ExecutionContext& callee_
MUST(verify_cast<FunctionEnvironment>(local_env)->bind_this_value(global_object(), this_value));
}
+// 27.7.5.1 AsyncFunctionStart ( promiseCapability, asyncFunctionBody ), https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
+void ECMAScriptFunctionObject::async_function_start(PromiseCapability const& promise_capability)
+{
+ auto& vm = this->vm();
+
+ // 1. Let runningContext be the running execution context.
+ auto& running_context = vm.running_execution_context();
+
+ // 2. Let asyncContext be a copy of runningContext.
+ auto async_context = running_context.copy();
+
+ // 3. NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context.
+
+ // 4. Perform ! AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext).
+ async_block_start(promise_capability, async_context);
+}
+
+// 27.7.5.2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ), https://tc39.es/ecma262/#sec-asyncblockstart
+void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promise_capability, ExecutionContext& async_context)
+{
+ auto& vm = this->vm();
+
+ // 1. Assert: promiseCapability is a PromiseCapability Record.
+
+ // 2. Let runningContext be the running execution context.
+ auto& running_context = vm.running_execution_context();
+
+ // 3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
+ auto* execution_steps = NativeFunction::create(global_object(), "", [async_body = m_ecmascript_code, &promise_capability](auto& vm, auto& global_object) -> ThrowCompletionOr<Value> {
+ // a. Let result be the result of evaluating asyncBody.
+ auto result = async_body->execute(vm.interpreter(), global_object);
+
+ // b. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
+
+ // c. 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();
+
+ // NOTE: Running the AST node should eventually return a completion.
+ // Until it does, we assume "return" and include the undefined fallback from the call site.
+ // d. If result.[[Type]] is normal, then
+ if (false) {
+ // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
+ MUST(call(global_object, promise_capability.resolve, js_undefined(), js_undefined()));
+ }
+ // e. Else if result.[[Type]] is return, then
+ else if (!vm.exception()) {
+ // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
+ MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value_or(js_undefined())));
+ }
+ // f. Else,
+ else {
+ // i. Assert: result.[[Type]] is throw.
+
+ // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
+ auto reason = vm.exception()->value();
+ vm.clear_exception();
+ vm.stop_unwind();
+ MUST(call(global_object, promise_capability.reject, js_undefined(), reason));
+ }
+ // g. Return.
+ return js_undefined();
+ });
+
+ // 4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
+ vm.push_execution_context(async_context, global_object());
+
+ // 5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
+ auto result = vm.call(*execution_steps, async_context.this_value.is_empty() ? js_undefined() : async_context.this_value);
+
+ // 6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
+ VERIFY(&vm.running_execution_context() == &running_context);
+
+ // 7. Assert: result is a normal completion with a value of undefined. The possible sources of completion values are Await or, if the async function doesn't await anything, step 3.g above.
+ VERIFY(result.has_value() && result.value().is_undefined());
+
+ // 8. Return.
+}
+
// 10.2.1.4 OrdinaryCallEvaluateBody ( F, argumentsList ), https://tc39.es/ecma262/#sec-ordinarycallevaluatebody
Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
{
@@ -666,6 +752,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
auto* bytecode_interpreter = Bytecode::Interpreter::current();
if (bytecode_interpreter) {
+ if (m_kind == FunctionKind::Async)
+ return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Async function execution in Bytecode interpreter");
// FIXME: pass something to evaluate default arguments with
TRY(function_declaration_instantiation(nullptr));
if (!m_bytecode_executable.has_value()) {
@@ -690,8 +778,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
return normal_completion(TRY(GeneratorObject::create(global_object(), result, this, vm.running_execution_context().lexical_environment, bytecode_interpreter->snapshot_frame())));
} else {
- if (m_kind != FunctionKind::Regular)
- return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Non regular function execution in AST interpreter");
+ if (m_kind == FunctionKind::Generator)
+ return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Generator function execution in AST interpreter");
OwnPtr<Interpreter> local_interpreter;
Interpreter* ast_interpreter = vm.interpreter_if_exists();
@@ -702,14 +790,36 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
VM::InterpreterExecutionScope scope(*ast_interpreter);
- TRY(function_declaration_instantiation(ast_interpreter));
+ if (m_kind == FunctionKind::Regular) {
+ TRY(function_declaration_instantiation(ast_interpreter));
- auto result = m_ecmascript_code->execute(*ast_interpreter, global_object());
- if (auto* exception = vm.exception())
- return throw_completion(exception->value());
- // NOTE: Running the AST node should eventually return a completion.
- // Until it does, we assume "return" and include the undefined fallback from the call site.
- return { Completion::Type::Return, result.value_or(js_undefined()), {} };
+ auto result = m_ecmascript_code->execute(*ast_interpreter, global_object());
+ if (auto* exception = vm.exception())
+ return throw_completion(exception->value());
+ // NOTE: Running the AST node should eventually return a completion.
+ // Until it does, we assume "return" and include the undefined fallback from the call site.
+ return { Completion::Type::Return, result.value_or(js_undefined()), {} };
+ } else if (m_kind == FunctionKind::Async) {
+ // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
+ auto promise_capability = MUST(new_promise_capability(global_object(), global_object().promise_constructor()));
+
+ // 2. Let declResult be FunctionDeclarationInstantiation(functionObject, argumentsList).
+ auto declaration_result = function_declaration_instantiation(ast_interpreter);
+
+ // 3. If declResult is not an abrupt completion, then
+ if (!declaration_result.is_throw_completion() || !declaration_result.throw_completion().is_abrupt()) {
+ // a. Perform ! AsyncFunctionStart(promiseCapability, FunctionBody).
+ async_function_start(promise_capability);
+ }
+ // 4. Else,
+ else {
+ // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »).
+ MUST(call(global_object(), promise_capability.reject, js_undefined(), declaration_result.throw_completion().value()));
+ }
+
+ // 5. Return Completion { [[Type]]: return, [[Value]]: promiseCapability.[[Promise]], [[Target]]: empty }.
+ return Completion { Completion::Type::Return, promise_capability.promise, {} };
+ }
}
VERIFY_NOT_REACHED();
}
diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h
index dfe121f844..158775d398 100644
--- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h
+++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h
@@ -73,7 +73,7 @@ public:
bool has_simple_parameter_list() const { return m_has_simple_parameter_list; }
// Equivalent to absence of [[Construct]]
- virtual bool has_constructor() const override { return !(m_is_arrow_function || m_kind == FunctionKind::Generator); }
+ virtual bool has_constructor() const override { return m_kind == FunctionKind::Regular && !m_is_arrow_function; }
protected:
virtual bool is_strict_mode() const final { return m_strict; }
@@ -87,6 +87,9 @@ private:
void prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target);
void ordinary_call_bind_this(ExecutionContext&, Value this_argument);
+ void async_function_start(PromiseCapability const&);
+ void async_block_start(PromiseCapability const&, ExecutionContext&);
+
ThrowCompletionOr<void> function_declaration_instantiation(Interpreter*);
// Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects
diff --git a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h
index 344205b917..5e0ce04953 100644
--- a/Userland/Libraries/LibJS/Runtime/ExecutionContext.h
+++ b/Userland/Libraries/LibJS/Runtime/ExecutionContext.h
@@ -22,6 +22,30 @@ struct ExecutionContext {
{
}
+ [[nodiscard]] ExecutionContext copy() const
+ {
+ ExecutionContext copy { arguments.copy() };
+
+ copy.function = function;
+ copy.realm = realm;
+ copy.lexical_environment = lexical_environment;
+ copy.variable_environment = variable_environment;
+ copy.private_environment = private_environment;
+ copy.current_node = current_node;
+ copy.function_name = function_name;
+ copy.this_value = this_value;
+ copy.is_strict_mode = is_strict_mode;
+
+ return copy;
+ }
+
+private:
+ explicit ExecutionContext(MarkedValueList existing_arguments)
+ : arguments(move(existing_arguments))
+ {
+ }
+
+public:
FunctionObject* function { nullptr }; // [[Function]]
Realm* realm { nullptr }; // [[Realm]]
Environment* lexical_environment { nullptr }; // [[LexicalEnvironment]]
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp
index c5405a13f1..a39eae0703 100644
--- a/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/FunctionConstructor.cpp
@@ -54,7 +54,8 @@ ThrowCompletionOr<RefPtr<FunctionExpression>> FunctionConstructor::create_dynami
body_source = TRY(vm.argument(vm.argument_count() - 1).to_string(global_object));
}
auto is_generator = kind == FunctionKind::Generator;
- auto source = String::formatted("function{} anonymous({}\n) {{\n{}\n}}", is_generator ? "*" : "", parameters_source, body_source);
+ auto is_async = kind == FunctionKind::Async;
+ auto source = String::formatted("{}function{} anonymous({}\n) {{\n{}\n}}", is_async ? "async " : "", is_generator ? "*" : "", parameters_source, body_source);
auto parser = Parser(Lexer(source));
auto function = parser.parse_function_node<FunctionExpression>();
if (parser.has_errors()) {
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
index ae4ced6548..8a4647c7f0 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
@@ -20,6 +20,8 @@
#include <LibJS/Runtime/ArrayConstructor.h>
#include <LibJS/Runtime/ArrayIteratorPrototype.h>
#include <LibJS/Runtime/ArrayPrototype.h>
+#include <LibJS/Runtime/AsyncFunctionConstructor.h>
+#include <LibJS/Runtime/AsyncFunctionPrototype.h>
#include <LibJS/Runtime/AtomicsObject.h>
#include <LibJS/Runtime/BigIntConstructor.h>
#include <LibJS/Runtime/BigIntPrototype.h>
diff --git a/Userland/Libraries/LibJS/Token.cpp b/Userland/Libraries/LibJS/Token.cpp
index 5a268d2808..94fb0c593b 100644
--- a/Userland/Libraries/LibJS/Token.cpp
+++ b/Userland/Libraries/LibJS/Token.cpp
@@ -224,6 +224,7 @@ bool Token::is_identifier_name() const
return m_type == TokenType::Identifier
|| m_type == TokenType::EscapedKeyword
|| m_type == TokenType::Await
+ || m_type == TokenType::Async
|| m_type == TokenType::BoolLiteral
|| m_type == TokenType::Break
|| m_type == TokenType::Case