1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-02 22:02:12 +00:00

LibJS: Add function default arguments

Adds the ability for function arguments to have default values. This
works for standard functions as well as arrow functions. Default values
are not printed in a <function>.toString() call, as nodes cannot print
their source string representation.
This commit is contained in:
Matthew Olsson 2020-05-02 11:46:39 -07:00 committed by Andreas Kling
parent 751f813f6a
commit 5e66f1900b
8 changed files with 184 additions and 35 deletions

View file

@ -670,17 +670,27 @@ void NullLiteral::dump(int indent) const
void FunctionNode::dump(int indent, const char* class_name) const void FunctionNode::dump(int indent, const char* class_name) const
{ {
StringBuilder parameters_builder;
parameters_builder.join(',', parameters());
print_indent(indent); print_indent(indent);
printf("%s '%s(%s)'\n", class_name, name().characters(), parameters_builder.build().characters()); printf("%s '%s'\n", class_name, name().characters());
if (!m_parameters.is_empty()) {
print_indent(indent + 1);
printf("(Parameters)\n");
for (auto& parameter : m_parameters) {
print_indent(indent + 2);
printf("%s\n", parameter.name.characters());
if (parameter.default_value) {
parameter.default_value->dump(indent + 3);
}
}
}
if (!m_variables.is_empty()) { if (!m_variables.is_empty()) {
print_indent(indent + 1); print_indent(indent + 1);
printf("(Variables)\n"); printf("(Variables)\n");
for (auto& variable : m_variables)
variable.dump(indent + 2);
} }
for (auto& variable : m_variables)
variable.dump(indent + 2);
print_indent(indent + 1); print_indent(indent + 1);
printf("(Body)\n"); printf("(Body)\n");
body().dump(indent + 2); body().dump(indent + 2);

View file

@ -149,12 +149,17 @@ class Declaration : public Statement {
class FunctionNode { class FunctionNode {
public: public:
struct Parameter {
FlyString name;
RefPtr<Expression> default_value;
};
const FlyString& name() const { return m_name; } const FlyString& name() const { return m_name; }
const Statement& body() const { return *m_body; } const Statement& body() const { return *m_body; }
const Vector<FlyString>& parameters() const { return m_parameters; }; const Vector<Parameter>& parameters() const { return m_parameters; };
protected: protected:
FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<FlyString> parameters, NonnullRefPtrVector<VariableDeclaration> variables) FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, NonnullRefPtrVector<VariableDeclaration> variables)
: m_name(name) : m_name(name)
, m_body(move(body)) , m_body(move(body))
, m_parameters(move(parameters)) , m_parameters(move(parameters))
@ -169,7 +174,7 @@ protected:
private: private:
FlyString m_name; FlyString m_name;
NonnullRefPtr<Statement> m_body; NonnullRefPtr<Statement> m_body;
const Vector<FlyString> m_parameters; const Vector<Parameter> m_parameters;
NonnullRefPtrVector<VariableDeclaration> m_variables; NonnullRefPtrVector<VariableDeclaration> m_variables;
}; };
@ -179,7 +184,7 @@ class FunctionDeclaration final
public: public:
static bool must_have_name() { return true; } static bool must_have_name() { return true; }
FunctionDeclaration(const FlyString& name, NonnullRefPtr<Statement> body, Vector<FlyString> parameters, NonnullRefPtrVector<VariableDeclaration> variables) FunctionDeclaration(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, NonnullRefPtrVector<VariableDeclaration> variables)
: FunctionNode(name, move(body), move(parameters), move(variables)) : FunctionNode(name, move(body), move(parameters), move(variables))
{ {
} }
@ -196,7 +201,7 @@ class FunctionExpression final : public Expression
public: public:
static bool must_have_name() { return false; } static bool must_have_name() { return false; }
FunctionExpression(const FlyString& name, NonnullRefPtr<Statement> body, Vector<FlyString> parameters, NonnullRefPtrVector<VariableDeclaration> variables) FunctionExpression(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, NonnullRefPtrVector<VariableDeclaration> variables)
: FunctionNode(name, move(body), move(parameters), move(variables)) : FunctionNode(name, move(body), move(parameters), move(variables))
{ {
} }

View file

@ -279,14 +279,19 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
load_state(); load_state();
}; };
Vector<FlyString> parameters; Vector<FunctionNode::Parameter> parameters;
bool parse_failed = false; bool parse_failed = false;
while (true) { while (true) {
if (match(TokenType::Comma)) { if (match(TokenType::Comma)) {
consume(TokenType::Comma); consume(TokenType::Comma);
} else if (match(TokenType::Identifier)) { } else if (match(TokenType::Identifier)) {
auto token = consume(TokenType::Identifier); auto parameter_name = consume(TokenType::Identifier).value();
parameters.append(token.value()); RefPtr<Expression> default_value;
if (match(TokenType::Equals)) {
consume(TokenType::Equals);
default_value = parse_expression(0);
}
parameters.append({ parameter_name, default_value });
} else if (match(TokenType::ParenClose)) { } else if (match(TokenType::ParenClose)) {
if (expect_parens) { if (expect_parens) {
consume(TokenType::ParenClose); consume(TokenType::ParenClose);
@ -775,10 +780,15 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_
name = consume(TokenType::Identifier).value(); name = consume(TokenType::Identifier).value();
} }
consume(TokenType::ParenOpen); consume(TokenType::ParenOpen);
Vector<FlyString> parameters; Vector<FunctionNode::Parameter> parameters;
while (match(TokenType::Identifier)) { while (match(TokenType::Identifier)) {
auto parameter = consume(TokenType::Identifier).value(); auto parameter_name = consume(TokenType::Identifier).value();
parameters.append(parameter); RefPtr<Expression> default_value;
if (match(TokenType::Equals)) {
consume(TokenType::Equals);
default_value = parse_expression(0);
}
parameters.append({ parameter_name, default_value });
if (match(TokenType::ParenClose)) { if (match(TokenType::ParenClose)) {
break; break;
} }
@ -1233,14 +1243,13 @@ void Parser::syntax_error(const String& message, size_t line, size_t column)
void Parser::save_state() void Parser::save_state()
{ {
m_saved_state = m_parser_state; m_saved_state.append(m_parser_state);
} }
void Parser::load_state() void Parser::load_state()
{ {
ASSERT(m_saved_state.has_value()); ASSERT(!m_saved_state.is_empty());
m_parser_state = m_saved_state.value(); m_parser_state = m_saved_state.take_last();
m_saved_state.clear();
} }
} }

View file

@ -108,6 +108,6 @@ private:
}; };
ParserState m_parser_state; ParserState m_parser_state;
Optional<ParserState> m_saved_state; Vector<ParserState> m_saved_state;
}; };
} }

View file

@ -136,9 +136,18 @@ Value FunctionPrototype::to_string(Interpreter& interpreter)
if (this_object->is_native_function() || this_object->is_bound_function()) { if (this_object->is_native_function() || this_object->is_bound_function()) {
function_body = String::format(" [%s]", this_object->class_name()); function_body = String::format(" [%s]", this_object->class_name());
} else { } else {
auto& parameters = static_cast<ScriptFunction*>(this_object)->parameters();
StringBuilder parameters_builder; StringBuilder parameters_builder;
parameters_builder.join(", ", parameters); auto first = true;
for (auto& parameter : static_cast<ScriptFunction*>(this_object)->parameters()) {
if (!first)
parameters_builder.append(", ");
first = false;
parameters_builder.append(parameter.name);
if (parameter.default_value) {
// FIXME: See note below
parameters_builder.append(" = TODO");
}
}
function_parameters = parameters_builder.build(); function_parameters = parameters_builder.build();
// FIXME: ASTNodes should be able to dump themselves to source strings - something like this: // FIXME: ASTNodes should be able to dump themselves to source strings - something like this:
// auto& body = static_cast<ScriptFunction*>(this_object)->body(); // auto& body = static_cast<ScriptFunction*>(this_object)->body();

View file

@ -46,12 +46,12 @@ static ScriptFunction* script_function_from(Interpreter& interpreter)
return static_cast<ScriptFunction*>(this_object); return static_cast<ScriptFunction*>(this_object);
} }
ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FlyString> parameters, LexicalEnvironment* parent_environment) ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment)
{ {
return global_object.heap().allocate<ScriptFunction>(name, body, move(parameters), parent_environment, *global_object.function_prototype()); return global_object.heap().allocate<ScriptFunction>(name, body, move(parameters), parent_environment, *global_object.function_prototype());
} }
ScriptFunction::ScriptFunction(const FlyString& name, const Statement& body, Vector<FlyString> parameters, LexicalEnvironment* parent_environment, Object& prototype) ScriptFunction::ScriptFunction(const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment, Object& prototype)
: Function(prototype) : Function(prototype)
, m_name(name) , m_name(name)
, m_body(body) , m_body(body)
@ -77,7 +77,7 @@ LexicalEnvironment* ScriptFunction::create_environment()
{ {
HashMap<FlyString, Variable> variables; HashMap<FlyString, Variable> variables;
for (auto& parameter : m_parameters) { for (auto& parameter : m_parameters) {
variables.set(parameter, { js_undefined(), DeclarationKind::Var }); variables.set(parameter.name, { js_undefined(), DeclarationKind::Var });
} }
if (body().is_scope_node()) { if (body().is_scope_node()) {
@ -97,12 +97,15 @@ Value ScriptFunction::call(Interpreter& interpreter)
auto& argument_values = interpreter.call_frame().arguments; auto& argument_values = interpreter.call_frame().arguments;
ArgumentVector arguments; ArgumentVector arguments;
for (size_t i = 0; i < m_parameters.size(); ++i) { for (size_t i = 0; i < m_parameters.size(); ++i) {
auto name = parameters()[i]; auto parameter = parameters()[i];
auto value = js_undefined(); auto value = js_undefined();
if (i < argument_values.size()) if (i < argument_values.size() && !argument_values[i].is_undefined()) {
value = argument_values[i]; value = argument_values[i];
arguments.append({ name, value }); } else if (parameter.default_value) {
interpreter.current_environment()->set(name, { value, DeclarationKind::Var }); value = parameter.default_value->execute(interpreter);
}
arguments.append({ parameter.name, value });
interpreter.current_environment()->set(parameter.name, { value, DeclarationKind::Var });
} }
return interpreter.run(m_body, arguments, ScopeType::Function); return interpreter.run(m_body, arguments, ScopeType::Function);
} }

View file

@ -26,19 +26,20 @@
#pragma once #pragma once
#include <LibJS/AST.h>
#include <LibJS/Runtime/Function.h> #include <LibJS/Runtime/Function.h>
namespace JS { namespace JS {
class ScriptFunction final : public Function { class ScriptFunction final : public Function {
public: public:
static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FlyString> parameters, LexicalEnvironment* parent_environment); static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment);
ScriptFunction(const FlyString& name, const Statement& body, Vector<FlyString> parameters, LexicalEnvironment* parent_environment, Object& prototype); ScriptFunction(const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, LexicalEnvironment* parent_environment, Object& prototype);
virtual ~ScriptFunction(); virtual ~ScriptFunction();
const Statement& body() const { return m_body; } const Statement& body() const { return m_body; }
const Vector<FlyString>& parameters() const { return m_parameters; }; const Vector<FunctionNode::Parameter>& parameters() const { return m_parameters; };
virtual Value call(Interpreter&) override; virtual Value call(Interpreter&) override;
virtual Value construct(Interpreter&) override; virtual Value construct(Interpreter&) override;
@ -56,7 +57,7 @@ private:
FlyString m_name; FlyString m_name;
NonnullRefPtr<Statement> m_body; NonnullRefPtr<Statement> m_body;
const Vector<FlyString> m_parameters; const Vector<FunctionNode::Parameter> m_parameters;
LexicalEnvironment* m_parent_environment { nullptr }; LexicalEnvironment* m_parent_environment { nullptr };
}; };

View file

@ -0,0 +1,112 @@
load("test-common.js");
try {
function func1(a, b = 1) {
return a + b;
}
const arrowFunc1 = (a, b = 1) => a + b;
assert(func1(4, 5) === 9);
assert(func1(4) === 5);
assert(func1(4, undefined) === 5);
assert(Number.isNaN(func1()));
assert(arrowFunc1(4, 5) === 9);
assert(arrowFunc1(4) === 5);
assert(arrowFunc1(4, undefined) === 5);
assert(Number.isNaN(arrowFunc1()));
function func2(a = 6) {
return typeof a;
}
const arrowFunc2 = (a = 6) => typeof a;
assert(func2() === "number");
assert(func2(5) === "number");
assert(func2(undefined) === "number");
assert(func2(false) === "boolean");
assert(func2(null) === "object");
assert(func2({}) === "object");
assert(arrowFunc2() === "number");
assert(arrowFunc2(5) === "number");
assert(arrowFunc2(undefined) === "number");
assert(arrowFunc2(false) === "boolean");
assert(arrowFunc2(null) === "object");
assert(arrowFunc2({}) === "object");
function func3(a = 5, b) {
return a + b;
}
const arrowFunc3 = (a = 5, b) => a + b;
assert(func3(4, 5) === 9);
assert(func3(undefined, 4) === 9);
assert(Number.isNaN(func3()));
assert(arrowFunc3(4, 5) === 9);
assert(arrowFunc3(undefined, 4) === 9);
assert(Number.isNaN(arrowFunc3()));
function func4(a, b = a) {
return a + b;
}
const arrowFunc4 = (a, b = a) => a + b;
assert(func4(4, 5) === 9);
assert(func4(4) === 8);
assert(func4("hf") === "hfhf");
assert(func4(true) === 2);
assert(Number.isNaN(func4()));
assert(arrowFunc4(4, 5) === 9);
assert(arrowFunc4(4) === 8);
assert(arrowFunc4("hf") === "hfhf");
assert(arrowFunc4(true) === 2);
assert(Number.isNaN(arrowFunc4()));
function func5(a = function() { return 5; }) {
return a();
}
const arrowFunc5 = (a = function() { return 5; }) => a();
assert(func5() === 5);
assert(func5(function() { return 10; }) === 10);
assert(func5(() => 10) === 10);
assert(arrowFunc5() === 5);
assert(arrowFunc5(function() { return 10; }) === 10);
assert(arrowFunc5(() => 10) === 10);
function func6(a = () => 5) {
return a();
}
const arrowFunc6 = (a = () => 5) => a();
assert(func6() === 5);
assert(func6(function() { return 10; }) === 10);
assert(func6(() => 10) === 10);
assert(arrowFunc6() === 5);
assert(arrowFunc6(function() { return 10; }) === 10);
assert(arrowFunc6(() => 10) === 10);
function func7(a = { foo: "bar" }) {
return a.foo;
}
const arrowFunc7 = (a = { foo: "bar" }) => a.foo
assert(func7() === "bar");
assert(func7({ foo: "baz" }) === "baz");
assert(arrowFunc7() === "bar");
assert(arrowFunc7({ foo: "baz" }) === "baz");
console.log("PASS");
} catch (e) {
console.log("FAIL: " + e);
}