From 482d3d8b47c0a6de1cc492ce7d84e6454c429cfa Mon Sep 17 00:00:00 2001 From: "satish.srinivasan" Date: Tue, 28 May 2024 12:19:05 +0530 Subject: [PATCH] FEAT: Support default arguments in functions #WIP --- src/org/mozilla/javascript/IRFactory.java | 26 +++++++++++++ src/org/mozilla/javascript/Parser.java | 26 ++++++++++++- .../mozilla/javascript/ast/FunctionNode.java | 10 +++++ .../javascript/tests/FunctionTest.java | 37 +++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/org/mozilla/javascript/IRFactory.java b/src/org/mozilla/javascript/IRFactory.java index de26e57a19..6c9d0594c0 100644 --- a/src/org/mozilla/javascript/IRFactory.java +++ b/src/org/mozilla/javascript/IRFactory.java @@ -641,6 +641,32 @@ private Node transformFunction(FunctionNode fn) { decompiler.addToken(Token.EOL); } + /* Process default parameters */ + if (fn.getDefaultParams() != null) { + Object[] defaultParams = fn.getDefaultParams(); + for (int i = defaultParams.length - 1; i > 0; ) { + AstNode rhs = (AstNode) defaultParams[i]; + String name = (String) defaultParams[i - 1]; + /* TODO: default params - error handling */ + body.addChildToFront( + createIf( + createBinary( + Token.SHEQ, + new Name(fn.getPosition(), name), + new Name(fn.getPosition(), "undefined")), + new Node( + Token.EXPR_VOID, + createAssignment( + Token.ASSIGN, + new Name(fn.getPosition(), name), + transform(rhs)), + body.getLineno()), + null, + body.getLineno())); + i -= 2; + } + } + if (destructuring != null) { body.addChildToFront(new Node(Token.EXPR_VOID, destructuring, lineno)); } diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index 8a31a212a4..5ea39165fc 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -848,6 +848,29 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { addError("msg.dup.param.strict", paramName); paramNames.add(paramName); } + if (matchToken(Token.ASSIGN, true)) { + /* TODO: Same for arrowFunctionParams below. Refactor into a helper. */ + AstNode rhs = assignExpr(); + Object[] existing = fnNode.getDefaultParams(); + Object[] current; + int current_size = 0; + if (existing == null) { + existing = new Object[2]; + fnNode.setDefaultParams(existing); + current = existing; + } else { + current = + new Object + [existing.length + + 2]; /* TODO: A more flexible structure? */ + System.arraycopy(existing, 0, current, 0, existing.length); + current_size = existing.length; + existing = null; + } + current[current_size] = paramName; + current[current.length - 1] = rhs; + fnNode.setDefaultParams(current); + } } else { fnNode.addParam(makeErrorNode()); } @@ -1046,7 +1069,8 @@ private void arrowFunctionParams( FunctionNode fnNode, AstNode params, Map destructuring, - Set paramNames) { + Set paramNames) + throws IOException { if (params instanceof ArrayLiteral || params instanceof ObjectLiteral) { markDestructuring(params); fnNode.addParam(params); diff --git a/src/org/mozilla/javascript/ast/FunctionNode.java b/src/org/mozilla/javascript/ast/FunctionNode.java index 7c5d07d6e8..c08aab2e2f 100644 --- a/src/org/mozilla/javascript/ast/FunctionNode.java +++ b/src/org/mozilla/javascript/ast/FunctionNode.java @@ -82,6 +82,16 @@ public static enum Form { private int rp = -1; private boolean hasRestParameter; + public Object[] getDefaultParams() { + return defaultParams; + } + + public void setDefaultParams(Object[] defaultParams) { + this.defaultParams = defaultParams; + } + + Object[] defaultParams; + // codegen variables private int functionType; private boolean needsActivation; diff --git a/testsrc/org/mozilla/javascript/tests/FunctionTest.java b/testsrc/org/mozilla/javascript/tests/FunctionTest.java index 6edf137ec7..c1b0e44a35 100644 --- a/testsrc/org/mozilla/javascript/tests/FunctionTest.java +++ b/testsrc/org/mozilla/javascript/tests/FunctionTest.java @@ -95,6 +95,31 @@ public void secondFunctionWithSameNameStrict() throws Exception { assertEvaluates("functionfunc(){result+=norm(func);}outer", script); } + @Test + public void functionDefaultArgsBasic() throws Exception { + final String script = "function foo(a = 2) {" + " return a;" + "}"; + assertIntEvaluates(32, script + "\nfoo(32)"); + assertIntEvaluates(2, script + "\nfoo()"); + assertIntEvaluates(2, script + "\nfoo(undefined)"); + } + + @Test + public void functionDefaultArgsMulti() throws Exception { + final String script = "function foo(a = 2, b = 23) {" + " return a + b;" + "}"; + assertIntEvaluates(55, script + "\nfoo(32)"); + assertIntEvaluates(25, script + "\nfoo()"); + assertIntEvaluates(34, script + "\nfoo(32, 2)"); + assertIntEvaluates(34, script + "\nfoo(undefined, undefined)"); + } + + @Test + public void functionDefaultArgsUsage() throws Exception { + final String script = "function foo(a = 2, b = a * 2) {" + " return a + b;" + "}"; + assertIntEvaluates(96, script + "\nfoo(32)"); + assertIntEvaluates(6, script + "\nfoo()"); + assertIntEvaluates(34, script + "\nfoo(32, 2)"); + } + @Test public void functioNamesExceptionsStrict() throws Exception { final String script = @@ -149,4 +174,16 @@ private static void assertEvaluates(final Object expected, final String source) return null; }); } + + private static void assertIntEvaluates(final Object expected, final String source) { + Utils.runWithAllOptimizationLevels( + cx -> { + final Scriptable scope = cx.initStandardObjects(); + final Object rep = cx.evaluateString(scope, source, "test.js", 0, null); + if (rep instanceof Double) + assertEquals((int) expected, ((Double) rep).intValue()); + else assertEquals(expected, rep); + return null; + }); + } }