summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibJS/AST.cpp22
-rw-r--r--Libraries/LibJS/AST.h15
-rw-r--r--Libraries/LibJS/Parser.cpp29
-rw-r--r--Libraries/LibJS/Parser.h2
-rw-r--r--Libraries/LibJS/Runtime/FunctionPrototype.cpp13
-rw-r--r--Libraries/LibJS/Runtime/ScriptFunction.cpp17
-rw-r--r--Libraries/LibJS/Runtime/ScriptFunction.h9
-rw-r--r--Libraries/LibJS/Tests/function-default-parameters.js112
8 files changed, 184 insertions, 35 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index ab91564887..6afdc51961 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -670,17 +670,27 @@ void NullLiteral::dump(int indent) const
void FunctionNode::dump(int indent, const char* class_name) const
{
- StringBuilder parameters_builder;
- parameters_builder.join(',', parameters());
-
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()) {
print_indent(indent + 1);
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);
printf("(Body)\n");
body().dump(indent + 2);
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index cfb74594d8..c41c0050d6 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -149,12 +149,17 @@ class Declaration : public Statement {
class FunctionNode {
public:
+ struct Parameter {
+ FlyString name;
+ RefPtr<Expression> default_value;
+ };
+
const FlyString& name() const { return m_name; }
const Statement& body() const { return *m_body; }
- const Vector<FlyString>& parameters() const { return m_parameters; };
+ const Vector<Parameter>& parameters() const { return m_parameters; };
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_body(move(body))
, m_parameters(move(parameters))
@@ -169,7 +174,7 @@ protected:
private:
FlyString m_name;
NonnullRefPtr<Statement> m_body;
- const Vector<FlyString> m_parameters;
+ const Vector<Parameter> m_parameters;
NonnullRefPtrVector<VariableDeclaration> m_variables;
};
@@ -179,7 +184,7 @@ class FunctionDeclaration final
public:
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))
{
}
@@ -196,7 +201,7 @@ class FunctionExpression final : public Expression
public:
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))
{
}
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index 05d2c81bb7..c73b267a1a 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -279,14 +279,19 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe
load_state();
};
- Vector<FlyString> parameters;
+ Vector<FunctionNode::Parameter> 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());
+ auto parameter_name = consume(TokenType::Identifier).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)) {
if (expect_parens) {
consume(TokenType::ParenClose);
@@ -775,10 +780,15 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(bool needs_function_
name = consume(TokenType::Identifier).value();
}
consume(TokenType::ParenOpen);
- Vector<FlyString> parameters;
+ Vector<FunctionNode::Parameter> parameters;
while (match(TokenType::Identifier)) {
- auto parameter = consume(TokenType::Identifier).value();
- parameters.append(parameter);
+ auto parameter_name = consume(TokenType::Identifier).value();
+ 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)) {
break;
}
@@ -1233,14 +1243,13 @@ void Parser::syntax_error(const String& message, size_t line, size_t column)
void Parser::save_state()
{
- m_saved_state = m_parser_state;
+ m_saved_state.append(m_parser_state);
}
void Parser::load_state()
{
- ASSERT(m_saved_state.has_value());
- m_parser_state = m_saved_state.value();
- m_saved_state.clear();
+ ASSERT(!m_saved_state.is_empty());
+ m_parser_state = m_saved_state.take_last();
}
}
diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h
index 248005defa..e11429399b 100644
--- a/Libraries/LibJS/Parser.h
+++ b/Libraries/LibJS/Parser.h
@@ -108,6 +108,6 @@ private:
};
ParserState m_parser_state;
- Optional<ParserState> m_saved_state;
+ Vector<ParserState> m_saved_state;
};
}
diff --git a/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Libraries/LibJS/Runtime/FunctionPrototype.cpp
index 7be81921c5..d6f5d3a5fa 100644
--- a/Libraries/LibJS/Runtime/FunctionPrototype.cpp
+++ b/Libraries/LibJS/Runtime/FunctionPrototype.cpp
@@ -136,9 +136,18 @@ Value FunctionPrototype::to_string(Interpreter& interpreter)
if (this_object->is_native_function() || this_object->is_bound_function()) {
function_body = String::format(" [%s]", this_object->class_name());
} else {
- auto& parameters = static_cast<ScriptFunction*>(this_object)->parameters();
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();
// FIXME: ASTNodes should be able to dump themselves to source strings - something like this:
// auto& body = static_cast<ScriptFunction*>(this_object)->body();
diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp
index 042ad99872..f138f3b71d 100644
--- a/Libraries/LibJS/Runtime/ScriptFunction.cpp
+++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp
@@ -46,12 +46,12 @@ static ScriptFunction* script_function_from(Interpreter& interpreter)
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());
}
-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)
, m_name(name)
, m_body(body)
@@ -77,7 +77,7 @@ LexicalEnvironment* ScriptFunction::create_environment()
{
HashMap<FlyString, Variable> variables;
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()) {
@@ -97,12 +97,15 @@ Value ScriptFunction::call(Interpreter& interpreter)
auto& argument_values = interpreter.call_frame().arguments;
ArgumentVector arguments;
for (size_t i = 0; i < m_parameters.size(); ++i) {
- auto name = parameters()[i];
+ auto parameter = parameters()[i];
auto value = js_undefined();
- if (i < argument_values.size())
+ if (i < argument_values.size() && !argument_values[i].is_undefined()) {
value = argument_values[i];
- arguments.append({ name, value });
- interpreter.current_environment()->set(name, { value, DeclarationKind::Var });
+ } else if (parameter.default_value) {
+ 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);
}
diff --git a/Libraries/LibJS/Runtime/ScriptFunction.h b/Libraries/LibJS/Runtime/ScriptFunction.h
index 23ccb3c323..24338972d5 100644
--- a/Libraries/LibJS/Runtime/ScriptFunction.h
+++ b/Libraries/LibJS/Runtime/ScriptFunction.h
@@ -26,19 +26,20 @@
#pragma once
+#include <LibJS/AST.h>
#include <LibJS/Runtime/Function.h>
namespace JS {
class ScriptFunction final : public Function {
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();
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 construct(Interpreter&) override;
@@ -56,7 +57,7 @@ private:
FlyString m_name;
NonnullRefPtr<Statement> m_body;
- const Vector<FlyString> m_parameters;
+ const Vector<FunctionNode::Parameter> m_parameters;
LexicalEnvironment* m_parent_environment { nullptr };
};
diff --git a/Libraries/LibJS/Tests/function-default-parameters.js b/Libraries/LibJS/Tests/function-default-parameters.js
new file mode 100644
index 0000000000..ab537928b0
--- /dev/null
+++ b/Libraries/LibJS/Tests/function-default-parameters.js
@@ -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);
+}