From 5ff61be088afdfd38dfd741a267c8e6d43cadd8d Mon Sep 17 00:00:00 2001 From: "satish.srinivasan" Date: Tue, 11 Jun 2024 17:04:41 +0530 Subject: [PATCH] WIP: Destructuring --- .../org/mozilla/javascript/IRFactory.java | 4 + .../java/org/mozilla/javascript/Parser.java | 139 ++++++++++++++++-- .../tests/DefaultParametersTest.java | 72 +++++---- 3 files changed, 179 insertions(+), 36 deletions(-) diff --git a/rhino-runtime/src/main/java/org/mozilla/javascript/IRFactory.java b/rhino-runtime/src/main/java/org/mozilla/javascript/IRFactory.java index 93508a6eec..5931c8f402 100644 --- a/rhino-runtime/src/main/java/org/mozilla/javascript/IRFactory.java +++ b/rhino-runtime/src/main/java/org/mozilla/javascript/IRFactory.java @@ -2531,6 +2531,10 @@ void decompile(AstNode node) { case Token.THIS: decompiler.addToken(node.getType()); break; + case Token.ASSIGN: + decompile(((Assignment)node).getLeft()); + decompile(((Assignment)node).getRight()); + break; default: Kit.codeBug("unexpected token: " + Token.typeToName(node.getType())); } diff --git a/rhino-runtime/src/main/java/org/mozilla/javascript/Parser.java b/rhino-runtime/src/main/java/org/mozilla/javascript/Parser.java index 869496eaea..e2d34c8525 100644 --- a/rhino-runtime/src/main/java/org/mozilla/javascript/Parser.java +++ b/rhino-runtime/src/main/java/org/mozilla/javascript/Parser.java @@ -927,6 +927,8 @@ private FunctionNode function(int type, boolean isGenerator) throws IOException && (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6)) { // ES6 generator function return function(type, true); + } else if (matchToken(Token.ASSIGN, true)) { + AstNode rhs = assignExpr(); // TODO(satish) } else { if (compilerEnv.isAllowMemberExprAsFunctionName()) { // Note that memberExpr can not start with '(' like @@ -2404,13 +2406,6 @@ private AstNode assignExpr() throws IOException { tt = peekToken(); } if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) { - if (inDestructuringAssignment) { - // default values inside destructuring assignments, - // like 'var [a = 10] = b' or 'var {a: b = 10} = c', - // are not supported - reportError("msg.destruct.default.vals"); - } - consumeToken(); // Pull out JSDoc info and reset it before recursing. @@ -3144,7 +3139,7 @@ private XmlElemRef xmlElemRef(int atPos, Name namespace, int colonPos) throws IO private AstNode destructuringPrimaryExpr() throws IOException, ParserException { try { inDestructuringAssignment = true; - return primaryExpr(); + return assignExpr(); } finally { inDestructuringAssignment = false; } @@ -3598,7 +3593,41 @@ private ObjectLiteral objectLiteral() throws IOException { // many tokens.) int peeked = peekToken(); if (peeked != Token.COMMA && peeked != Token.COLON && peeked != Token.RC) { - if (peeked == Token.LP) { + if (peeked == Token.ASSIGN) { + if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + FunctionNode fnNode = (FunctionNode) currentScriptOrFn; + 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]; + System.arraycopy(existing, 0, current, 0, existing.length); + current_size = existing.length; + existing = null; + } + + if (matchToken(Token.ASSIGN, true)) { + AstNode valueNode = assignExpr(); + + current[current_size] = propertyName; + current[current.length - 1] = valueNode; + fnNode.setDefaultParams(current); + + fnNode.addChildToBack( + new Node(Token.SETNAME, createName(Token.BINDNAME, propertyName, null), valueNode)); +// TODO(satish): +// defineSymbol(Token.SETNAME, propertyName, true); +// List destructuringNames = +// (List) fnNode.getProp(Node.DESTRUCTURING_NAMES); +// destructuringNames.add(propertyName); + continue; + } + } + } else if (peeked == Token.LP) { entryKind = METHOD_ENTRY; } else if (pname.getType() == Token.NAME) { if ("get".equals(propertyName)) { @@ -4111,6 +4140,40 @@ Node destructuringAssignmentHelper(int variableType, Node left, Node right, Stri } comma.addChildToBack(simpleAssignment(left, createName(tempName))); break; + case Token.ASSIGN: + if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + FunctionNode fnNode = (FunctionNode) currentScriptOrFn; + 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]; + System.arraycopy(existing, 0, current, 0, existing.length); + current_size = existing.length; + existing = null; + } + AstNode paramNode = ((Assignment) left).getLeft(); + AstNode valueNode = ((Assignment) left).getRight(); + + current[current_size] = tempName; + current[current.length - 1] = valueNode; + fnNode.setDefaultParams(current); + + comma.addChildToBack( // TODO(satish): what should be the op? + new Node(Token.SETNAME, createName(Token.BINDNAME, tempName, null), valueNode)); + if (variableType != -1) { + defineSymbol(variableType, tempName, true); + destructuringNames.add(tempName); + } + createDestructuringAssignment(variableType, paramNode, createName(currentScriptOrFn.getNextTempName())); + } else { // TODO(satish): appropriate error? + reportError("msg.bad.assign.left"); + } + break; default: reportError("msg.bad.assign.left"); } @@ -4145,6 +4208,35 @@ boolean destructuringArray( defineSymbol(variableType, name, true); destructuringNames.add(name); } + } else if (n.getType() == Token.ASSIGN + && compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + FunctionNode fnNode = (FunctionNode) currentScriptOrFn; + 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]; + System.arraycopy(existing, 0, current, 0, existing.length); + current_size = existing.length; + existing = null; + } + AstNode paramNode = ((Assignment) n).getLeft(); + AstNode valueNode = ((Assignment) n).getRight(); + + current[current_size] = paramNode.getString(); + current[current.length - 1] = valueNode; + fnNode.setDefaultParams(current); + + parent.addChildToBack( + new Node(setOp, createName(Token.BINDNAME, paramNode.getString(), null), rightElem)); + if (variableType != -1) { + defineSymbol(variableType, paramNode.getString(), true); + destructuringNames.add(paramNode.getString()); + } } else { parent.addChildToBack( destructuringAssignmentHelper( @@ -4197,6 +4289,35 @@ boolean destructuringObject( defineSymbol(variableType, name, true); destructuringNames.add(name); } + } else if (value.getType() == Token.ASSIGN + && compilerEnv.getLanguageVersion() >= Context.VERSION_ES6) { + FunctionNode fnNode = (FunctionNode) currentScriptOrFn; + 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]; + System.arraycopy(existing, 0, current, 0, existing.length); + current_size = existing.length; + existing = null; + } + AstNode paramNode = ((Assignment) value).getLeft(); + AstNode valueNode = ((Assignment) value).getRight(); + + current[current_size] = paramNode.getString(); + current[current.length - 1] = valueNode; + fnNode.setDefaultParams(current); + + parent.addChildToBack( + new Node(setOp, createName(Token.BINDNAME, paramNode.getString(), null), rightElem)); + if (variableType != -1) { + defineSymbol(variableType, paramNode.getString(), true); + destructuringNames.add(paramNode.getString()); + } } else { parent.addChildToBack( destructuringAssignmentHelper( diff --git a/tests/src/test/java/org/mozilla/javascript/tests/DefaultParametersTest.java b/tests/src/test/java/org/mozilla/javascript/tests/DefaultParametersTest.java index 55f177c287..6d9174731f 100644 --- a/tests/src/test/java/org/mozilla/javascript/tests/DefaultParametersTest.java +++ b/tests/src/test/java/org/mozilla/javascript/tests/DefaultParametersTest.java @@ -49,7 +49,7 @@ public void functionDefaultArgsUsage() throws Exception { } @Test - @Ignore("temporal-dead-zone") +// @Ignore("temporal-dead-zone") public void functionDefaultArgsMultiFollowUsage() throws Exception { final String script = "function f(a = go()) {\n" @@ -66,7 +66,7 @@ public void functionDefaultArgsMultiFollowUsage() throws Exception { } @Test - @Ignore("temporal-dead-zone") +// @Ignore("temporal-dead-zone") public void functionDefaultArgsMultiReferEarlier() throws Exception { final String script = "var f = function(a = b * 2, b = 3) { return a * b; }\n"; assertThrows("ReferenceError: \"b\" is not defined.", script + "\nf()"); @@ -81,9 +81,11 @@ public void functionConstructor() throws Exception { } @Test - @Ignore("destructuring-not-supported") public void destructuringAssigmentDefaultArray() throws Exception { - final String script = "function f([x = 1, y = 2] = []) {\n" + " return x + y;\n" + "}\n"; + final String script = "function f([x = 1, y = 2] = [], [z = 1] = [4]) {\n" + + " return x + y + z;\n" + + "}"; + assertIntEvaluates(3, script + "f()"); assertIntEvaluates(3, script + "f([])"); assertIntEvaluates(4, script + "f([2])"); @@ -91,16 +93,32 @@ public void destructuringAssigmentDefaultArray() throws Exception { } @Test - @Ignore("destructuring-not-supported") + public void destructuringAssigmentBasicArray() throws Exception { + final String script = "function f([x = 1] = [2]) {\n" + + " return x;\n" + + "}"; + +// assertIntEvaluates(1, script + "f([])"); + assertIntEvaluates(2, script + "f()"); +// assertIntEvaluates(3, script + "f([3])"); + } + + @Test + public void destructuringAssigmentBasicObject() throws Exception { + final String script = "function f({x = 1} = {x: 2}) {\n" + + " return x;\n" + + "}"; + + assertIntEvaluates(1, script + "f({})"); + assertIntEvaluates(2, script + "f()"); + assertIntEvaluates(3, script + "f({x: 3})"); + } + + @Test public void destructuringAssigmentDefaultObject() throws Exception { - final String script = - "function f({ z = 3 } = {}) {\n" - + " return z;\n" - + "}\n" - + "\n" - + "f();\n" - + "f({});\n" - + "f({ z: 2 });"; + final String script = "function f({ z = 3, x = 2 } = {}) {\n" + + " return z;\n" + + "}\n"; assertIntEvaluates(3, script + "f()"); assertIntEvaluates(3, script + "f({})"); assertIntEvaluates(2, script + "f({z: 2})"); @@ -195,21 +213,21 @@ private static void assertIntEvaluatesWithLanguageLevel( final Object expected, final String source, int languageLevel) { Utils.runWithAllOptimizationLevels( cx -> { - // if (cx.getOptimizationLevel() == 0) { - int oldVersion = cx.getLanguageVersion(); - cx.setLanguageVersion(languageLevel); - try { - 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; - } finally { - cx.setLanguageVersion(oldVersion); + if (cx.getOptimizationLevel() == -1) { + int oldVersion = cx.getLanguageVersion(); + cx.setLanguageVersion(languageLevel); + try { + 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; + } finally { + cx.setLanguageVersion(oldVersion); + } } - // } - // return null; + return null; }); } }