Skip to content

Commit

Permalink
initial implementation of function rest parameter support
Browse files Browse the repository at this point in the history
  • Loading branch information
ZZZank committed Oct 11, 2024
1 parent 3b21b0b commit 1198630
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ private void generateICodeFromTree(Node tree) {
itsData.argNames = scriptOrFn.getParamAndVarNames();
itsData.argIsConst = scriptOrFn.getParamAndVarConst();
itsData.argCount = scriptOrFn.getParamCount();
itsData.argsHasRest = scriptOrFn.hasRestParameter();

itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd();
Expand Down
17 changes: 13 additions & 4 deletions common/src/main/java/dev/latvian/mods/rhino/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import dev.latvian.mods.rhino.ast.WhileLoop;
import dev.latvian.mods.rhino.ast.WithStatement;
import dev.latvian.mods.rhino.ast.Yield;
import lombok.val;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -1933,13 +1934,21 @@ boolean isDestructuring(Node n) {
Node decompileFunctionHeader(FunctionNode fn) {
Node mexpr = null;
if (fn.getFunctionName() != null) {
// decompiler.addName(fn.getName());
} else if (fn.getMemberExprNode() != null) {
mexpr = transform(fn.getMemberExprNode());
}
boolean isArrow = fn.getFunctionType() == FunctionNode.ARROW_FUNCTION;
boolean noParen = isArrow && fn.getLp() == -1;
List<AstNode> params = fn.getParams();
for (AstNode param : params) {
val isArrow = fn.getFunctionType() == FunctionNode.ARROW_FUNCTION;
val noParen = isArrow && fn.getLp() == -1;
val params = fn.getParams();
for (int i = 0; i < params.size(); i++) {
// if (i > 0) {
// decompiler.addToken(Token.COMMA);
// }
// if (fn.hasRestParameter() && i == last) {
// decompiler.addToken(Token.DOTDOTDOT);
// }
val param = params.get(i);
decompile(param);
}
return mexpr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ public Object resumeGenerator(Context cx, Scriptable scope, int operation, Objec

@Override
protected int getParamCount() {
return idata.argCount;
}
return idata.argsHasRest ? idata.argCount - 1 : idata.argCount;
}

@Override
protected int getParamAndVarCount() {
Expand Down
4 changes: 2 additions & 2 deletions common/src/main/java/dev/latvian/mods/rhino/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ void initializeArgs(Context cx, Scriptable callerScope, Object[] args, double[]

if (useActivation) {
if (idata.itsFunctionType == FunctionNode.ARROW_FUNCTION) {
scope = ScriptRuntime.createArrowFunctionActivation(fnOrScript, scope, args, idata.isStrict);
scope = ScriptRuntime.createArrowFunctionActivation(fnOrScript, cx, scope, args, idata.isStrict, idata.argsHasRest);
} else {
scope = ScriptRuntime.createFunctionActivation(fnOrScript, scope, args, idata.isStrict);
scope = ScriptRuntime.createFunctionActivation(fnOrScript, cx, scope, args, idata.isStrict, idata.argsHasRest);
}
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ final class InterpreterData implements Serializable {
static final int INITIAL_MAX_ICODE_LENGTH = 1024;
static final int INITIAL_STRINGTABLE_SIZE = 64;
static final int INITIAL_NUMBERTABLE_SIZE = 64;
boolean argsHasRest;

InterpreterData(String sourceFile, String encodedSource, boolean isStrict) {
this.itsSourceFile = sourceFile;
Expand Down
39 changes: 33 additions & 6 deletions common/src/main/java/dev/latvian/mods/rhino/NativeCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package dev.latvian.mods.rhino;

import lombok.val;

/**
* This class implements the activation object.
* <p>
Expand All @@ -27,7 +29,15 @@ static void init(Scriptable scope, boolean sealed) {
NativeCall() {
}

NativeCall(NativeFunction function, Scriptable scope, Object[] args, boolean isArrow, boolean isStrict) {
NativeCall(
NativeFunction function,
Context cx,
Scriptable scope,
Object[] args,
boolean isArrow,
boolean isStrict,
boolean argsHasRest
) {
this.function = function;

setParentScope(scope);
Expand All @@ -40,10 +50,27 @@ static void init(Scriptable scope, boolean sealed) {
int paramAndVarCount = function.getParamAndVarCount();
int paramCount = function.getParamCount();
if (paramAndVarCount != 0) {
for (int i = 0; i < paramCount; ++i) {
String name = function.getParamOrVarName(i);
Object val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
if (argsHasRest) {
Object[] vals;
if (args.length >= paramCount) {
vals = new Object[args.length - paramCount];
System.arraycopy(args, paramCount, vals, 0, args.length - paramCount);
} else {
vals = ScriptRuntime.emptyArgs;
}

for (int i = 0; i < paramCount; ++i) {
val name = function.getParamOrVarName(i);
val val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
}
defineProperty(function.getParamOrVarName(paramCount), cx.newArray(scope, vals), PERMANENT);
} else {
for (int i = 0; i < paramCount; ++i) {
val name = function.getParamOrVarName(i);
val val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
}
}
}

Expand All @@ -56,7 +83,7 @@ static void init(Scriptable scope, boolean sealed) {

if (paramAndVarCount != 0) {
for (int i = paramCount; i < paramAndVarCount; ++i) {
String name = function.getParamOrVarName(i);
val name = function.getParamOrVarName(i);
if (!super.has(name, this)) {
if (function.getParamOrVarConst(i)) {
defineProperty(name, Undefined.instance, CONST);
Expand Down
33 changes: 28 additions & 5 deletions common/src/main/java/dev/latvian/mods/rhino/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import dev.latvian.mods.rhino.ast.*;
import dev.latvian.mods.rhino.ast.Symbol;
import lombok.val;

import java.io.IOException;
import java.util.ArrayList;
Expand Down Expand Up @@ -637,7 +638,12 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
do {
int tt = peekToken();
if (tt == Token.LB || tt == Token.LC) {
AstNode expr = destructuringPrimaryExpr();
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

val expr = destructuringPrimaryExpr();
markDestructuring(expr);
fnNode.addParam(expr);
// Destructuring assignment for parameters: add a dummy
Expand All @@ -646,18 +652,35 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
if (destructuring == null) {
destructuring = new HashMap<>();
}
String pname = currentScriptOrFn.getNextTempName();
val pname = currentScriptOrFn.getNextTempName();
defineSymbol(Token.LP, pname, false);
destructuring.put(pname, expr);
} else {
boolean wasRest = false;
if (tt == Token.DOTDOTDOT) {
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

fnNode.setHasRestParameter(true);
wasRest = true;
consumeToken();
}

if (mustMatchToken(Token.NAME, "msg.no.parm", true)) {
Name paramNameNode = createNameNode();
Comment jsdocNodeForName = getAndResetJsDoc();
if (!wasRest && fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

val paramNameNode = createNameNode();
val jsdocNodeForName = getAndResetJsDoc();
if (jsdocNodeForName != null) {
paramNameNode.setJsDocNode(jsdocNodeForName);
}
fnNode.addParam(paramNameNode);
String paramName = ts.getString();
val paramName = ts.getString();
defineSymbol(Token.LP, paramName);
if (this.inUseStrictDirective) {
if ("eval".equals(paramName) || "arguments".equals(paramName)) {
Expand Down
59 changes: 55 additions & 4 deletions common/src/main/java/dev/latvian/mods/rhino/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,33 @@ public static Object[] padArguments(Object[] args, int count) {
return result;
}

/**
* Helper function for builtin objects that use the varargs form. ECMA function formal arguments
* are undefined if not supplied; this function pads the argument array out to the expected
* length, if necessary. Also, the rest parameter array construction is done here.
*/
public static Object[] padAndRestArguments(
Context cx, Scriptable scope, Object[] args, int argCount) {
Object[] result = new Object[argCount];
int paramCount = argCount - 1;
if (args.length < paramCount) {
System.arraycopy(args, 0, result, 0, args.length);
Arrays.fill(result, args.length, paramCount, Undefined.instance);
} else {
System.arraycopy(args, 0, result, 0, paramCount);
}

Object[] restValues;
if (args.length > paramCount) {
restValues = new Object[args.length - paramCount];
System.arraycopy(args, paramCount, restValues, 0, restValues.length);
} else {
restValues = ScriptRuntime.emptyArgs;
}
result[paramCount] = cx.newArray(scope, restValues);
return result;
}

public static String escapeString(String s) {
return escapeString(s, '"');
}
Expand Down Expand Up @@ -2915,12 +2942,36 @@ public static void initScript(NativeFunction funObj, Scriptable thisObj, Context
}
}

public static Scriptable createFunctionActivation(NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) {
return new NativeCall(funObj, scope, args, false, isStrict);
public static Scriptable createFunctionActivation(
NativeFunction funObj,
Context cx,
Scriptable scope,
Object[] args,
boolean isStrict,
boolean argsHasRest
) {
return new NativeCall(funObj, cx, scope, args, false, isStrict, argsHasRest);
}

@Deprecated
public static Scriptable createFunctionActivation(NativeFunction funObj, Context cx, Scriptable scope, Object[] args, boolean isStrict) {
return createFunctionActivation(funObj, cx, scope, args, isStrict, false);
}

public static Scriptable createArrowFunctionActivation(
NativeFunction funObj,
Context cx,
Scriptable scope,
Object[] args,
boolean isStrict,
boolean argsHasRest
) {
return new NativeCall(funObj, cx, scope, args, true, isStrict, argsHasRest);
}

public static Scriptable createArrowFunctionActivation(NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) {
return new NativeCall(funObj, scope, args, true, isStrict);
@Deprecated
public static Scriptable createArrowFunctionActivation(NativeFunction funObj, Context cx, Scriptable scope, Object[] args, boolean isStrict) {
return createArrowFunctionActivation(funObj, cx, scope, args, true, isStrict);
}

public static void enterActivationFunction(Context cx, Scriptable scope) {
Expand Down
3 changes: 2 additions & 1 deletion common/src/main/java/dev/latvian/mods/rhino/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,9 @@ enum CommentType {
int TEMPLATE_CHARS = TEMPLATE_LITERAL + 1; // template literal - literal section
int TEMPLATE_LITERAL_SUBST = TEMPLATE_CHARS + 1; // template literal - substitution
int TAGGED_TEMPLATE_LITERAL = TEMPLATE_LITERAL_SUBST + 1; // template literal - tagged/handler
int DOTDOTDOT = 174; // spread/rest ...

int LAST_TOKEN = TAGGED_TEMPLATE_LITERAL;
int LAST_TOKEN = DOTDOTDOT;


/**
Expand Down
3 changes: 3 additions & 0 deletions common/src/main/java/dev/latvian/mods/rhino/TokenStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,9 @@ final int getToken() throws IOException {
case ':':
return Token.COLON;
case '.':
if (matchChar('.') && matchChar('.')) {
return Token.DOTDOTDOT;
}
return Token.DOT;

case '|':
Expand Down
10 changes: 10 additions & 0 deletions common/src/main/java/dev/latvian/mods/rhino/ast/FunctionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class FunctionNode extends ScriptNode {
public static final int FUNCTION_EXPRESSION = 2;
public static final int FUNCTION_EXPRESSION_STATEMENT = 3;
public static final int ARROW_FUNCTION = 4;
private boolean hasRestParameter;

public enum Form {
FUNCTION, GETTER, SETTER, METHOD
Expand Down Expand Up @@ -397,6 +398,15 @@ public AstNode getMemberExprNode() {
return memberExprNode;
}

@Override
public boolean hasRestParameter() {
return hasRestParameter;
}

public void setHasRestParameter(boolean hasRestParameter) {
this.hasRestParameter = hasRestParameter;
}

/**
* Visits this node, the function name node if supplied,
* the parameters, and the body. If there is a member-expr node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,10 @@ public boolean isInStrictMode() {
return inStrictMode;
}

public boolean hasRestParameter() {
return false;
}

@Override
public void visit(NodeVisitor v) {
if (v.visit(this)) {
Expand Down
Loading

0 comments on commit 1198630

Please sign in to comment.