Browse Source

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.
Matthew Olsson 5 years ago
parent
commit
5e66f1900b

+ 16 - 6
Libraries/LibJS/AST.cpp

@@ -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);

+ 10 - 5
Libraries/LibJS/AST.h

@@ -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))
     {
     {
     }
     }

+ 19 - 10
Libraries/LibJS/Parser.cpp

@@ -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);
-            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)) {
         } 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();
-        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)) {
         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());
-    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();
 }
 }
 
 
 }
 }

+ 1 - 1
Libraries/LibJS/Parser.h

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

+ 11 - 2
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()) {
     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();

+ 10 - 7
Libraries/LibJS/Runtime/ScriptFunction.cpp

@@ -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 });
-        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);
     return interpreter.run(m_body, arguments, ScopeType::Function);
 }
 }

+ 5 - 4
Libraries/LibJS/Runtime/ScriptFunction.h

@@ -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 };
 };
 };
 
 

+ 112 - 0
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);
+}