mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-24 02:12:09 -05:00
LibJS: Add support for arrow functions
This commit is contained in:
parent
f90da71d28
commit
098f1cd0ca
5 changed files with 144 additions and 1 deletions
|
@ -86,6 +86,7 @@ Lexer::Lexer(StringView source)
|
|||
}
|
||||
|
||||
if (s_two_char_tokens.is_empty()) {
|
||||
s_two_char_tokens.set("=>", TokenType::Arrow);
|
||||
s_two_char_tokens.set("+=", TokenType::PlusEquals);
|
||||
s_two_char_tokens.set("-=", TokenType::MinusEquals);
|
||||
s_two_char_tokens.set("*=", TokenType::AsteriskEquals);
|
||||
|
|
|
@ -223,6 +223,78 @@ NonnullRefPtr<Statement> Parser::parse_statement()
|
|||
return statement;
|
||||
}
|
||||
|
||||
RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expect_parens)
|
||||
{
|
||||
save_state();
|
||||
|
||||
Vector<FlyString> parameters;
|
||||
bool parse_failed = false;
|
||||
while (true) {
|
||||
if (match(TokenType::Comma)) {
|
||||
consume(TokenType::Comma);
|
||||
} else if (match(TokenType::Identifier)) {
|
||||
auto token = consume(TokenType::Identifier);
|
||||
parameters.append(token.value());
|
||||
} else if (match(TokenType::ParenClose)) {
|
||||
if (expect_parens) {
|
||||
consume(TokenType::ParenClose);
|
||||
if (match(TokenType::Arrow)) {
|
||||
consume(TokenType::Arrow);
|
||||
} else {
|
||||
parse_failed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
parse_failed = true;
|
||||
break;
|
||||
} else if (match(TokenType::Arrow)) {
|
||||
if (!expect_parens) {
|
||||
consume(TokenType::Arrow);
|
||||
break;
|
||||
}
|
||||
parse_failed = true;
|
||||
break;
|
||||
} else {
|
||||
parse_failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parse_failed) {
|
||||
load_state();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto function_body_result = [this]() -> RefPtr<BlockStatement> {
|
||||
if (match(TokenType::CurlyOpen)) {
|
||||
// Parse a function body with statements
|
||||
return parse_block_statement();
|
||||
}
|
||||
if (match_expression()) {
|
||||
// Parse a function body which returns a single expression
|
||||
|
||||
// FIXME: We synthesize a block with a return statement
|
||||
// for arrow function bodies which are a single expression.
|
||||
// Esprima generates a single "ArrowFunctionExpression"
|
||||
// with a "body" property.
|
||||
auto return_expression = parse_expression(0);
|
||||
auto return_block = create_ast_node<BlockStatement>();
|
||||
return_block->append<ReturnStatement>(move(return_expression));
|
||||
return return_block;
|
||||
}
|
||||
// Invalid arrow function body
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (!function_body_result.is_null()) {
|
||||
auto body = function_body_result.release_nonnull();
|
||||
return create_ast_node<FunctionExpression>("", move(body), move(parameters));
|
||||
}
|
||||
|
||||
load_state();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
||||
{
|
||||
if (match_unary_prefixed_expression())
|
||||
|
@ -231,12 +303,23 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression()
|
|||
switch (m_parser_state.m_current_token.type()) {
|
||||
case TokenType::ParenOpen: {
|
||||
consume(TokenType::ParenOpen);
|
||||
if (match(TokenType::ParenClose) || match(TokenType::Identifier)) {
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(true);
|
||||
if (!arrow_function_result.is_null()) {
|
||||
return arrow_function_result.release_nonnull();
|
||||
}
|
||||
}
|
||||
auto expression = parse_expression(0);
|
||||
consume(TokenType::ParenClose);
|
||||
return expression;
|
||||
}
|
||||
case TokenType::Identifier:
|
||||
case TokenType::Identifier: {
|
||||
auto arrow_function_result = try_parse_arrow_function_expression(false);
|
||||
if (!arrow_function_result.is_null()) {
|
||||
return arrow_function_result.release_nonnull();
|
||||
}
|
||||
return create_ast_node<Identifier>(consume().value());
|
||||
}
|
||||
case TokenType::NumericLiteral:
|
||||
return create_ast_node<NumericLiteral>(consume().double_value());
|
||||
case TokenType::BoolLiteral:
|
||||
|
|
|
@ -67,6 +67,7 @@ public:
|
|||
NonnullRefPtr<Expression> parse_secondary_expression(NonnullRefPtr<Expression>, int min_precedence, Associativity associate = Associativity::Right);
|
||||
NonnullRefPtr<CallExpression> parse_call_expression(NonnullRefPtr<Expression>);
|
||||
NonnullRefPtr<NewExpression> parse_new_expression();
|
||||
RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens);
|
||||
|
||||
bool has_errors() const { return m_parser_state.m_has_errors; }
|
||||
|
||||
|
|
57
Libraries/LibJS/Tests/arrow-functions.js
Normal file
57
Libraries/LibJS/Tests/arrow-functions.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
function assert(x) { if (!x) throw 1; }
|
||||
try {
|
||||
let getNumber = () => 42;
|
||||
assert(getNumber() === 42);
|
||||
|
||||
let add = (a, b) => a + b;
|
||||
assert(add(2, 3) === 5);
|
||||
|
||||
const addBlock = (a, b) => {
|
||||
let res = a + b;
|
||||
return res;
|
||||
};
|
||||
assert(addBlock(5, 4) === 9);
|
||||
|
||||
const makeObject = (a, b) => ({ a, b });
|
||||
const obj = makeObject(33, 44);
|
||||
assert(typeof obj === "object");
|
||||
assert(obj.a === 33);
|
||||
assert(obj.b === 44);
|
||||
|
||||
let returnUndefined = () => {};
|
||||
assert(typeof returnUndefined() === "undefined");
|
||||
|
||||
const makeArray = (a, b) => [a, b];
|
||||
const array = makeArray("3", { foo: 4 });
|
||||
assert(array[0] === "3");
|
||||
assert(array[1].foo === 4);
|
||||
|
||||
let square = x => x * x;
|
||||
assert(square(3) === 9);
|
||||
|
||||
let squareBlock = x => {
|
||||
return x * x;
|
||||
};
|
||||
assert(squareBlock(4) === 16);
|
||||
|
||||
const message = (who => "Hello " + who)("friends!");
|
||||
assert(message === "Hello friends!");
|
||||
|
||||
const sum = ((x, y, z) => x + y + z)(1, 2, 3);
|
||||
assert(sum === 6);
|
||||
|
||||
const product = ((x, y, z) => {
|
||||
let res = x * y * z;
|
||||
return res;
|
||||
})(5, 4, 2);
|
||||
assert(product === 40);
|
||||
|
||||
const half = (x => {
|
||||
return x / 2;
|
||||
})(10);
|
||||
assert(half === 5);
|
||||
|
||||
console.log("PASS");
|
||||
} catch {
|
||||
console.log("FAIL");
|
||||
}
|
|
@ -34,6 +34,7 @@ namespace JS {
|
|||
#define ENUMERATE_JS_TOKENS \
|
||||
__ENUMERATE_JS_TOKEN(Ampersand) \
|
||||
__ENUMERATE_JS_TOKEN(AmpersandEquals) \
|
||||
__ENUMERATE_JS_TOKEN(Arrow) \
|
||||
__ENUMERATE_JS_TOKEN(Asterisk) \
|
||||
__ENUMERATE_JS_TOKEN(AsteriskAsteriskEquals) \
|
||||
__ENUMERATE_JS_TOKEN(AsteriskEquals) \
|
||||
|
|
Loading…
Add table
Reference in a new issue