Skip to content

Commit

Permalink
initial implementation of function rest parameter support (arrow func…
Browse files Browse the repository at this point in the history
…tions are not supported as of now)
  • Loading branch information
rbri committed Feb 26, 2024
1 parent 6ec1085 commit 2d2693e
Show file tree
Hide file tree
Showing 18 changed files with 397 additions and 25 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
20 changes: 20 additions & 0 deletions src/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,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
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
27 changes: 27 additions & 0 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
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
40 changes: 28 additions & 12 deletions src/org/mozilla/javascript/optimizer/BodyCodegen.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,34 @@ private void generatePrologue() {
int parmCount = scriptOrFn.getParamCount();
if (parmCount > 0 && !inDirectCallFunction) {
// Set up args array
// check length of arguments, pad if need be
cfw.addALoad(argsLocal);
cfw.add(ByteCode.ARRAYLENGTH);
cfw.addPush(parmCount);
int label = cfw.acquireLabel();
cfw.add(ByteCode.IF_ICMPGE, label);
cfw.addALoad(argsLocal);
cfw.addPush(parmCount);
addScriptRuntimeInvoke(
"padArguments", "([Ljava/lang/Object;I" + ")[Ljava/lang/Object;");
cfw.addAStore(argsLocal);
cfw.markLabel(label);
if (scriptOrFn.hasRestParameter()) {
cfw.addALoad(contextLocal);
cfw.addALoad(variableObjectLocal);
cfw.addALoad(argsLocal);
cfw.addPush(parmCount);
addScriptRuntimeInvoke(
"padAndRestArguments",
"("
+ "Lorg/mozilla/javascript/Context;"
+ "Lorg/mozilla/javascript/Scriptable;"
+ "[Ljava/lang/Object;"
+ "I"
+ ")[Ljava/lang/Object;");
cfw.addAStore(argsLocal);
} else {
// check length of arguments, pad if need be
cfw.addALoad(argsLocal);
cfw.add(ByteCode.ARRAYLENGTH);
cfw.addPush(parmCount);
int label = cfw.acquireLabel();
cfw.add(ByteCode.IF_ICMPGE, label);
cfw.addALoad(argsLocal);
cfw.addPush(parmCount);
addScriptRuntimeInvoke(
"padArguments", "([Ljava/lang/Object;I)[Ljava/lang/Object;");
cfw.addAStore(argsLocal);
cfw.markLabel(label);
}
}

int paramCount = fnCurrent.fnode.getParamCount();
Expand Down
19 changes: 17 additions & 2 deletions src/org/mozilla/javascript/optimizer/Codegen.java
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded
final int Do_getEncodedSource = 4;
final int Do_getParamOrVarConst = 5;
final int Do_isGeneratorFunction = 6;
final int SWITCH_COUNT = 7;
final int Do_hasRestParameter = 7;
final int SWITCH_COUNT = 8;

for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) {
if (methodIndex == Do_getEncodedSource && encodedSource == null) {
Expand Down Expand Up @@ -786,6 +787,10 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded
methodLocals = 1; // Only this
cfw.startMethod("isGeneratorFunction", "()Z", ACC_PROTECTED);
break;
case Do_hasRestParameter:
methodLocals = 1; // Only this
cfw.startMethod("hasRestParameter", "()Z", ACC_PUBLIC);
break;
default:
throw Kit.codeBug();
}
Expand Down Expand Up @@ -830,7 +835,11 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded

case Do_getParamCount:
// Push number of defined parameters
cfw.addPush(n.getParamCount());
if (n.hasRestParameter()) {
cfw.addPush(n.getParamCount() - 1);
} else {
cfw.addPush(n.getParamCount());
}
cfw.add(ByteCode.IRETURN);
break;

Expand Down Expand Up @@ -920,6 +929,12 @@ private void generateNativeFunctionOverrides(ClassFileWriter cfw, String encoded
cfw.add(ByteCode.IRETURN);
break;

case Do_hasRestParameter:
// Push boolean of defined hasRestParameter
cfw.addPush(n.hasRestParameter());
cfw.add(ByteCode.IRETURN);
break;

case Do_getEncodedSource:
// Push number encoded source start and end
// to prepare for encodedSource.substring(start, end)
Expand Down
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/resources/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ msg.no.parm =\
msg.no.paren.after.parms =\
missing ) after formal parameters

msg.parm.after.rest =\
parameter after rest parameter

msg.no.brace.body =\
missing '{' before function body

Expand Down
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/resources/Messages_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ msg.no.parm =\
msg.no.paren.after.parms =\
il manque '')'' apr\u00E8s les param\u00E8tres formels

msg.parm.after.rest =\
parameter after rest parameter

msg.no.brace.body =\
il manque ''{'' avant le corps d''une fonction

Expand Down
Loading

0 comments on commit 2d2693e

Please sign in to comment.