Skip to content

Commit

Permalink
Pull Request mozilla#1451: initial implementation of function rest pa…
Browse files Browse the repository at this point in the history
…rameter support
  • Loading branch information
rbri committed Feb 27, 2024
2 parents 6ec1085 + ee07a4a commit 1b2f540
Show file tree
Hide file tree
Showing 20 changed files with 589 additions and 65 deletions.
1 change: 1 addition & 0 deletions src/org/mozilla/javascript/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,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
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/Decompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,10 @@ else if (nextToken == Token.NAME) {
i = printSourceString(source, i + 1, false, result);
continue;

case Token.DOTDOTDOT:
result.append("...");
break;

default:
// If we don't know how to decompile it, raise an exception.
throw new RuntimeException("Token: " + Token.name(source.charAt(i)));
Expand Down
10 changes: 7 additions & 3 deletions src/org/mozilla/javascript/IRFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -2450,11 +2450,15 @@ Node decompileFunctionHeader(FunctionNode fn) {
decompiler.addToken(Token.LP);
}
List<AstNode> params = fn.getParams();
for (int i = 0; i < params.size(); i++) {
decompile(params.get(i));
if (i < params.size() - 1) {
int last = params.size() - 1;
for (int i = 0; i <= last; i++) {
if (i > 0) {
decompiler.addToken(Token.COMMA);
}
if (fn.hasRestParameter() && i == last) {
decompiler.addToken(Token.DOTDOTDOT);
}
decompile(params.get(i));
}
if (!noParen) {
decompiler.addToken(Token.RP);
Expand Down
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/InterpretedFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ protected int getLanguageVersion() {

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

Expand Down
34 changes: 32 additions & 2 deletions src/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,21 @@ void initializeArgs(
if (idata.itsFunctionType == FunctionNode.ARROW_FUNCTION) {
scope =
ScriptRuntime.createArrowFunctionActivation(
fnOrScript, scope, args, idata.isStrict);
fnOrScript,
cx,
scope,
args,
idata.isStrict,
idata.argsHasRest);
} else {
scope =
ScriptRuntime.createFunctionActivation(
fnOrScript, scope, args, idata.isStrict);
fnOrScript,
cx,
scope,
args,
idata.isStrict,
idata.argsHasRest);
}
}
} else {
Expand Down Expand Up @@ -188,6 +198,26 @@ void initializeArgs(
for (int i = definedArgs; i != idata.itsMaxVars; ++i) {
stack[i] = Undefined.instance;
}

if (idata.argsHasRest) {
Object[] vals;
int offset = idata.argCount - 1;
if (argCount >= idata.argCount) {
vals = new Object[argCount - offset];

argShift = argShift + offset;
for (int valsIdx = 0; valsIdx != vals.length; ++argShift, ++valsIdx) {
Object val = args[argShift];
if (val == UniqueTag.DOUBLE_MARK) {
val = ScriptRuntime.wrapNumber(argsDbl[argShift]);
}
vals[valsIdx] = val;
}
} else {
vals = ScriptRuntime.emptyArgs;
}
stack[offset] = cx.newArray(scope, vals);
}
}

CallFrame cloneFrozen() {
Expand Down
1 change: 1 addition & 0 deletions src/org/mozilla/javascript/InterpreterData.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ private void init() {
String[] argNames;
boolean[] argIsConst;
int argCount;
boolean argsHasRest;

int itsMaxCalleeArgs;

Expand Down
32 changes: 27 additions & 5 deletions src/org/mozilla/javascript/NativeCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ static void init(Scriptable scope, boolean sealed) {

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

setParentScope(scope);
Expand All @@ -44,10 +46,30 @@ 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) {
String name = function.getParamOrVarName(i);
Object 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) {
String name = function.getParamOrVarName(i);
Object val = i < args.length ? args[i] : Undefined.instance;
defineProperty(name, val, PERMANENT);
}
}
}

Expand Down
27 changes: 27 additions & 0 deletions src/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -787,10 +787,20 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
do {
int tt = peekToken();
if (tt == Token.RP) {
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

fnNode.putIntProp(Node.TRAILING_COMMA, 1);
break;
}
if (tt == Token.LB || tt == Token.LC) {
if (fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

AstNode expr = destructuringPrimaryExpr();
markDestructuring(expr);
fnNode.addParam(expr);
Expand All @@ -804,7 +814,24 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
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)) {
if (!wasRest && fnNode.hasRestParameter()) {
// Error: parameter after rest parameter
reportError("msg.parm.after.rest", ts.tokenBeg, ts.tokenEnd - ts.tokenBeg);
}

Name paramNameNode = createNameNode();
Comment jsdocNodeForName = getAndResetJsDoc();
if (jsdocNodeForName != null) {
Expand Down
70 changes: 65 additions & 5 deletions src/org/mozilla/javascript/ScriptRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,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 @@ -4001,23 +4028,56 @@ public static void initScript(
}

/**
* @deprecated Use {@link #createFunctionActivation(NativeFunction, Scriptable, Object[],
* boolean)} instead
* @deprecated Use {@link #createFunctionActivation(NativeFunction, Context, Scriptable,
* Object[], boolean, boolean)} instead
*/
@Deprecated
public static Scriptable createFunctionActivation(
NativeFunction funObj, Scriptable scope, Object[] args) {
return createFunctionActivation(funObj, scope, args, false);
return createFunctionActivation(
funObj, Context.getCurrentContext(), scope, args, false, false);
}

/**
* @deprecated Use {@link #createFunctionActivation(NativeFunction, Context, Scriptable,
* Object[], boolean, boolean)} instead
*/
@Deprecated
public static Scriptable createFunctionActivation(
NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) {
return new NativeCall(funObj, scope, args, false, isStrict);
return new NativeCall(
funObj, Context.getCurrentContext(), scope, args, false, isStrict, false);
}

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 Use {@link #createArrowFunctionActivation(NativeFunction, Context, Scriptable,
* Object[], boolean, boolean)} instead
*/
@Deprecated
public static Scriptable createArrowFunctionActivation(
NativeFunction funObj, Scriptable scope, Object[] args, boolean isStrict) {
return new NativeCall(funObj, scope, args, true, isStrict);
return new NativeCall(
funObj, Context.getCurrentContext(), scope, args, true, 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 void enterActivationFunction(Context cx, Scriptable scope) {
Expand Down
3 changes: 2 additions & 1 deletion src/org/mozilla/javascript/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ public static enum CommentType {
TEMPLATE_CHARS = 171, // template literal - literal section
TEMPLATE_LITERAL_SUBST = 172, // template literal - substitution
TAGGED_TEMPLATE_LITERAL = 173, // template literal - tagged/handler
LAST_TOKEN = 173;
DOTDOTDOT = 174, // spread/rest ...
LAST_TOKEN = 174;

/**
* Returns a name for the token. If Rhino is compiled with certain hardcoded debugging flags in
Expand Down
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/TokenStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,10 @@ && peekChar() == '!'
return Token.COLON;
case '.':
if (matchChar('.')) {
if (parser.compilerEnv.getLanguageVersion() >= Context.VERSION_1_8
&& matchChar('.')) {
return Token.DOTDOTDOT;
}
return Token.DOTDOT;
} else if (matchChar('(')) {
return Token.DOTQUERY;
Expand Down
10 changes: 10 additions & 0 deletions src/org/mozilla/javascript/ast/FunctionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static enum Form {
private Form functionForm = Form.FUNCTION;
private int lp = -1;
private int rp = -1;
private boolean hasRestParameter;

// codegen variables
private int functionType;
Expand Down Expand Up @@ -284,6 +285,15 @@ public void setIsES6Generator() {
isGenerator = true;
}

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

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

public void addResumptionPoint(Node target) {
if (generatorResumePoints == null) generatorResumePoints = new ArrayList<>();
generatorResumePoints.add(target);
Expand Down
4 changes: 4 additions & 0 deletions src/org/mozilla/javascript/ast/ScriptNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ public boolean[] getParamAndVarConst() {
return isConsts;
}

public boolean hasRestParameter() {
return false;
}

void addSymbol(Symbol symbol) {
if (variableNames != null) codeBug();
if (symbol.getDeclType() == Token.LP) {
Expand Down

0 comments on commit 1b2f540

Please sign in to comment.