зеркало из https://github.com/mozilla/pjs.git
Integration of VariableTable into ScriptOrFnNode to avoid the need to have a separated wrapper class around ObjArray/ObjToIntMap
This commit is contained in:
Родитель
46b7643242
Коммит
39dbbb0550
|
@ -107,7 +107,7 @@ public class FunctionNode extends ScriptOrFnNode {
|
||||||
if (fn.getFunctionType() == FUNCTION_EXPRESSION_STATEMENT) {
|
if (fn.getFunctionType() == FUNCTION_EXPRESSION_STATEMENT) {
|
||||||
String name = fn.getFunctionName();
|
String name = fn.getFunctionName();
|
||||||
if (name != null && name.length() != 0) {
|
if (name != null && name.length() != 0) {
|
||||||
removeParameterOrVar(name);
|
removeParamOrVar(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ public class FunctionNode extends ScriptOrFnNode {
|
||||||
Node stmts = getLastChild();
|
Node stmts = getLastChild();
|
||||||
if (getFunctionType() == FUNCTION_EXPRESSION) {
|
if (getFunctionType() == FUNCTION_EXPRESSION) {
|
||||||
String name = getFunctionName();
|
String name = getFunctionName();
|
||||||
if (name != null && name.length() != 0 && !hasParameterOrVar(name))
|
if (name != null && name.length() != 0 && !hasParamOrVar(name))
|
||||||
{
|
{
|
||||||
// A function expression needs to have its name as a
|
// A function expression needs to have its name as a
|
||||||
// variable (if it isn't already allocated as a variable).
|
// variable (if it isn't already allocated as a variable).
|
||||||
|
|
|
@ -218,7 +218,7 @@ public class Interpreter {
|
||||||
itsData.itsDoubleTable = tmp;
|
itsData.itsDoubleTable = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
itsData.itsMaxVars = scriptOrFn.getParameterAndVarCount();
|
itsData.itsMaxVars = scriptOrFn.getParamAndVarCount();
|
||||||
// itsMaxFrameArray: interpret method needs this amount for its
|
// itsMaxFrameArray: interpret method needs this amount for its
|
||||||
// stack and sDbl arrays
|
// stack and sDbl arrays
|
||||||
itsData.itsMaxFrameArray = itsData.itsMaxVars
|
itsData.itsMaxFrameArray = itsData.itsMaxVars
|
||||||
|
@ -226,8 +226,8 @@ public class Interpreter {
|
||||||
+ itsData.itsMaxTryDepth
|
+ itsData.itsMaxTryDepth
|
||||||
+ itsData.itsMaxStack;
|
+ itsData.itsMaxStack;
|
||||||
|
|
||||||
itsData.argNames = scriptOrFn.getParameterAndVarNames();
|
itsData.argNames = scriptOrFn.getParamAndVarNames();
|
||||||
itsData.argCount = scriptOrFn.getParameterCount();
|
itsData.argCount = scriptOrFn.getParamCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int updateLineNumber(Node node, int iCodeTop) {
|
private int updateLineNumber(Node node, int iCodeTop) {
|
||||||
|
@ -674,7 +674,7 @@ public class Interpreter {
|
||||||
// use typeofname if an activation frame exists
|
// use typeofname if an activation frame exists
|
||||||
// since the vars all exist there instead of in jregs
|
// since the vars all exist there instead of in jregs
|
||||||
if (itsInFunctionFlag && !itsData.itsNeedsActivation)
|
if (itsInFunctionFlag && !itsData.itsNeedsActivation)
|
||||||
index = scriptOrFn.getParameterOrVarIndex(name);
|
index = scriptOrFn.getParamOrVarIndex(name);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
iCodeTop = addByte(TokenStream.TYPEOFNAME, iCodeTop);
|
iCodeTop = addByte(TokenStream.TYPEOFNAME, iCodeTop);
|
||||||
iCodeTop = addString(name, iCodeTop);
|
iCodeTop = addString(name, iCodeTop);
|
||||||
|
@ -724,7 +724,7 @@ public class Interpreter {
|
||||||
iCodeTop);
|
iCodeTop);
|
||||||
itsStackDepth--;
|
itsStackDepth--;
|
||||||
} else {
|
} else {
|
||||||
int i = scriptOrFn.getParameterOrVarIndex(name);
|
int i = scriptOrFn.getParamOrVarIndex(name);
|
||||||
iCodeTop = addByte(type == TokenStream.INC
|
iCodeTop = addByte(type == TokenStream.INC
|
||||||
? TokenStream.VARINC
|
? TokenStream.VARINC
|
||||||
: TokenStream.VARDEC,
|
: TokenStream.VARDEC,
|
||||||
|
@ -930,7 +930,7 @@ public class Interpreter {
|
||||||
iCodeTop = addByte(TokenStream.GETPROP, iCodeTop);
|
iCodeTop = addByte(TokenStream.GETPROP, iCodeTop);
|
||||||
itsStackDepth--;
|
itsStackDepth--;
|
||||||
} else {
|
} else {
|
||||||
int index = scriptOrFn.getParameterOrVarIndex(name);
|
int index = scriptOrFn.getParamOrVarIndex(name);
|
||||||
iCodeTop = addByte(TokenStream.GETVAR, iCodeTop);
|
iCodeTop = addByte(TokenStream.GETVAR, iCodeTop);
|
||||||
iCodeTop = addByte(index, iCodeTop);
|
iCodeTop = addByte(index, iCodeTop);
|
||||||
itsStackDepth++;
|
itsStackDepth++;
|
||||||
|
@ -949,7 +949,7 @@ public class Interpreter {
|
||||||
String name = child.getString();
|
String name = child.getString();
|
||||||
child = child.getNext();
|
child = child.getNext();
|
||||||
iCodeTop = generateICode(child, iCodeTop);
|
iCodeTop = generateICode(child, iCodeTop);
|
||||||
int index = scriptOrFn.getParameterOrVarIndex(name);
|
int index = scriptOrFn.getParamOrVarIndex(name);
|
||||||
iCodeTop = addByte(TokenStream.SETVAR, iCodeTop);
|
iCodeTop = addByte(TokenStream.SETVAR, iCodeTop);
|
||||||
iCodeTop = addByte(index, iCodeTop);
|
iCodeTop = addByte(index, iCodeTop);
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,7 +305,7 @@ public class JavaAdapter extends ScriptableObject {
|
||||||
ScriptableObject.getProperty(p, "length"));
|
ScriptableObject.getProperty(p, "length"));
|
||||||
} else if (f instanceof FunctionNode) {
|
} else if (f instanceof FunctionNode) {
|
||||||
// This is used only by optimizer/Codegen
|
// This is used only by optimizer/Codegen
|
||||||
length = ((FunctionNode)f).getParameterCount();
|
length = ((FunctionNode)f).getParamCount();
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,7 +376,7 @@ public class NodeTransformer {
|
||||||
// use of "arguments" requires an activation object.
|
// use of "arguments" requires an activation object.
|
||||||
((FunctionNode) tree).setRequiresActivation(true);
|
((FunctionNode) tree).setRequiresActivation(true);
|
||||||
}
|
}
|
||||||
if (tree.hasParameterOrVar(name)) {
|
if (tree.hasParamOrVar(name)) {
|
||||||
if (type == TokenStream.SETNAME) {
|
if (type == TokenStream.SETNAME) {
|
||||||
node.setType(TokenStream.SETVAR);
|
node.setType(TokenStream.SETVAR);
|
||||||
bind.setType(TokenStream.STRING);
|
bind.setType(TokenStream.STRING);
|
||||||
|
@ -416,7 +416,7 @@ public class NodeTransformer {
|
||||||
// Use of "arguments" requires an activation object.
|
// Use of "arguments" requires an activation object.
|
||||||
((FunctionNode) tree).setRequiresActivation(true);
|
((FunctionNode) tree).setRequiresActivation(true);
|
||||||
}
|
}
|
||||||
if (tree.hasParameterOrVar(name)) {
|
if (tree.hasParamOrVar(name)) {
|
||||||
node.setType(TokenStream.GETVAR);
|
node.setType(TokenStream.GETVAR);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -458,7 +458,7 @@ public class NodeTransformer {
|
||||||
boolean addGetThis = false;
|
boolean addGetThis = false;
|
||||||
if (left.getType() == TokenStream.NAME) {
|
if (left.getType() == TokenStream.NAME) {
|
||||||
String name = left.getString();
|
String name = left.getString();
|
||||||
if (inFunction && tree.hasParameterOrVar(name)
|
if (inFunction && tree.hasParamOrVar(name)
|
||||||
&& !inWithStatement())
|
&& !inWithStatement())
|
||||||
{
|
{
|
||||||
// call to a var. Transform to Call(GetVar("a"), b, c)
|
// call to a var. Transform to Call(GetVar("a"), b, c)
|
||||||
|
|
|
@ -261,11 +261,11 @@ class Parser {
|
||||||
first = false;
|
first = false;
|
||||||
mustMatchToken(ts, ts.NAME, "msg.no.parm");
|
mustMatchToken(ts, ts.NAME, "msg.no.parm");
|
||||||
String s = ts.getString();
|
String s = ts.getString();
|
||||||
if (fnNode.hasParameterOrVar(s)) {
|
if (fnNode.hasParamOrVar(s)) {
|
||||||
Object[] msgArgs = { s };
|
Object[] msgArgs = { s };
|
||||||
ts.reportCurrentLineWarning("msg.dup.parms", msgArgs);
|
ts.reportCurrentLineWarning("msg.dup.parms", msgArgs);
|
||||||
}
|
}
|
||||||
fnNode.addParameter(s);
|
fnNode.addParam(s);
|
||||||
sourceAddString(ts.NAME, s);
|
sourceAddString(ts.NAME, s);
|
||||||
} while (ts.matchToken(ts.COMMA));
|
} while (ts.matchToken(ts.COMMA));
|
||||||
|
|
||||||
|
|
|
@ -117,48 +117,65 @@ public class ScriptOrFnNode extends Node {
|
||||||
return regexps.size() / 2 - 1;
|
return regexps.size() / 2 - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean hasParameterOrVar(String name) {
|
public final boolean hasParamOrVar(String name) {
|
||||||
if (variableTable == null) { return false; }
|
return itsVariableNames.has(name);
|
||||||
return variableTable.hasVariable(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getParameterOrVarIndex(String name) {
|
public final int getParamOrVarIndex(String name) {
|
||||||
if (variableTable == null) { return -1; }
|
return itsVariableNames.get(name, -1);
|
||||||
return variableTable.getOrdinal(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final String getParameterOrVarName(int index) {
|
public final String getParamOrVarName(int index) {
|
||||||
return variableTable.getVariable(index);
|
return (String)itsVariables.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getParameterCount() {
|
public final int getParamCount() {
|
||||||
if (variableTable == null) { return 0; }
|
return varStart;
|
||||||
return variableTable.getParameterCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getParameterAndVarCount() {
|
public final int getParamAndVarCount() {
|
||||||
if (variableTable == null) { return 0; }
|
return itsVariables.size();
|
||||||
return variableTable.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final String[] getParameterAndVarNames() {
|
public final String[] getParamAndVarNames() {
|
||||||
if (variableTable == null) { return new String[0]; }
|
String[] array = new String[itsVariables.size()];
|
||||||
return variableTable.getAllVariables();
|
itsVariables.toArray(array);
|
||||||
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void addParameter(String name) {
|
public final void addParam(String name) {
|
||||||
if (variableTable == null) { variableTable = new VariableTable(); }
|
// Check addparam is not called after addLocal
|
||||||
variableTable.addParameter(name);
|
if (varStart != itsVariables.size()) Context.codeBug();
|
||||||
|
// Allow non-unique parameter names: use the last occurrence
|
||||||
|
int index = varStart++;
|
||||||
|
itsVariables.add(name);
|
||||||
|
itsVariableNames.put(name, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void addVar(String name) {
|
public final void addVar(String name) {
|
||||||
if (variableTable == null) { variableTable = new VariableTable(); }
|
int vIndex = itsVariableNames.get(name, -1);
|
||||||
variableTable.addLocal(name);
|
if (vIndex != -1) {
|
||||||
|
// There's already a variable or parameter with this name.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = itsVariables.size();
|
||||||
|
itsVariables.add(name);
|
||||||
|
itsVariableNames.put(name, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void removeParameterOrVar(String name) {
|
public final void removeParamOrVar(String name) {
|
||||||
if (variableTable == null) { return; }
|
int i = itsVariableNames.get(name, -1);
|
||||||
variableTable.removeLocal(name);
|
if (i != -1) {
|
||||||
|
itsVariables.remove(i);
|
||||||
|
itsVariableNames.remove(name);
|
||||||
|
ObjToIntMap.Iterator iter = itsVariableNames.newIterator();
|
||||||
|
for (iter.start(); !iter.done(); iter.next()) {
|
||||||
|
int v = iter.getValue();
|
||||||
|
if (v > i) {
|
||||||
|
iter.setValue(v - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final int getLocalCount() { return localCount; }
|
public final int getLocalCount() { return localCount; }
|
||||||
|
@ -174,9 +191,19 @@ public class ScriptOrFnNode extends Node {
|
||||||
private String sourceName;
|
private String sourceName;
|
||||||
private int baseLineno = -1;
|
private int baseLineno = -1;
|
||||||
private int endLineno = -1;
|
private int endLineno = -1;
|
||||||
|
|
||||||
private ObjArray functions;
|
private ObjArray functions;
|
||||||
|
|
||||||
private ObjArray regexps;
|
private ObjArray regexps;
|
||||||
private VariableTable variableTable;
|
|
||||||
|
// a list of the formal parameters and local variables
|
||||||
|
private ObjArray itsVariables = new ObjArray();
|
||||||
|
|
||||||
|
// mapping from name to index in list
|
||||||
|
private ObjToIntMap itsVariableNames = new ObjToIntMap(11);
|
||||||
|
|
||||||
|
private int varStart; // index in list of first variable
|
||||||
|
|
||||||
private int localCount;
|
private int localCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,7 +333,7 @@ public class Codegen extends Interpreter {
|
||||||
+ "Ljava/lang/Object;",
|
+ "Ljava/lang/Object;",
|
||||||
flags);
|
flags);
|
||||||
|
|
||||||
int argCount = fnCurrent.getParameterCount();
|
int argCount = fnCurrent.getParamCount();
|
||||||
int firstLocal = (4 + argCount * 3) + 1;
|
int firstLocal = (4 + argCount * 3) + 1;
|
||||||
|
|
||||||
aload((short)0); // this
|
aload((short)0); // this
|
||||||
|
@ -429,7 +429,7 @@ public class Codegen extends Interpreter {
|
||||||
addByteCode(ByteCode.ALOAD_1);
|
addByteCode(ByteCode.ALOAD_1);
|
||||||
addByteCode(ByteCode.ALOAD_2);
|
addByteCode(ByteCode.ALOAD_2);
|
||||||
addByteCode(ByteCode.ALOAD_3);
|
addByteCode(ByteCode.ALOAD_3);
|
||||||
for (int i = 0; i < scriptOrFn.getParameterCount(); i++) {
|
for (int i = 0; i < scriptOrFn.getParamCount(); i++) {
|
||||||
push(i);
|
push(i);
|
||||||
addByteCode(ByteCode.ALOAD, 4);
|
addByteCode(ByteCode.ALOAD, 4);
|
||||||
addByteCode(ByteCode.ARRAYLENGTH);
|
addByteCode(ByteCode.ARRAYLENGTH);
|
||||||
|
@ -465,7 +465,7 @@ public class Codegen extends Interpreter {
|
||||||
if (!fnCurrent.getParameterNumberContext()) {
|
if (!fnCurrent.getParameterNumberContext()) {
|
||||||
// make sure that all parameters are objects
|
// make sure that all parameters are objects
|
||||||
itsForcedObjectParameters = true;
|
itsForcedObjectParameters = true;
|
||||||
for (int i = 0; i < fnCurrent.getParameterCount(); i++) {
|
for (int i = 0; i < fnCurrent.getParamCount(); i++) {
|
||||||
OptLocalVariable lVar = fnCurrent.getVar(i);
|
OptLocalVariable lVar = fnCurrent.getVar(i);
|
||||||
aload(lVar.getJRegister());
|
aload(lVar.getJRegister());
|
||||||
classFile.add(ByteCode.GETSTATIC,
|
classFile.add(ByteCode.GETSTATIC,
|
||||||
|
@ -482,7 +482,7 @@ public class Codegen extends Interpreter {
|
||||||
markLabel(isObjectLabel);
|
markLabel(isObjectLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
generatePrologue(cx, true, scriptOrFn.getParameterCount());
|
generatePrologue(cx, true, scriptOrFn.getParamCount());
|
||||||
} else {
|
} else {
|
||||||
startNewMethod("call",
|
startNewMethod("call",
|
||||||
"(Lorg/mozilla/javascript/Context;" +
|
"(Lorg/mozilla/javascript/Context;" +
|
||||||
|
@ -544,7 +544,7 @@ public class Codegen extends Interpreter {
|
||||||
// 2 is reserved for parentScope
|
// 2 is reserved for parentScope
|
||||||
// 3 is reserved for script 'this'
|
// 3 is reserved for script 'this'
|
||||||
short jReg = 4;
|
short jReg = 4;
|
||||||
int parameterCount = fnCurrent.getParameterCount();
|
int parameterCount = fnCurrent.getParamCount();
|
||||||
for (int i = 0; i < parameterCount; i++) {
|
for (int i = 0; i < parameterCount; i++) {
|
||||||
OptLocalVariable lVar = fnCurrent.getVar(i);
|
OptLocalVariable lVar = fnCurrent.getVar(i);
|
||||||
lVar.assignJRegister(jReg);
|
lVar.assignJRegister(jReg);
|
||||||
|
@ -1145,7 +1145,7 @@ public class Codegen extends Interpreter {
|
||||||
"functionName", "Ljava/lang/String;");
|
"functionName", "Ljava/lang/String;");
|
||||||
}
|
}
|
||||||
|
|
||||||
int N = scriptOrFn.getParameterAndVarCount();
|
int N = scriptOrFn.getParamAndVarCount();
|
||||||
if (N != 0) {
|
if (N != 0) {
|
||||||
setNonTrivialInit(methodName);
|
setNonTrivialInit(methodName);
|
||||||
push(N);
|
push(N);
|
||||||
|
@ -1153,7 +1153,7 @@ public class Codegen extends Interpreter {
|
||||||
for (int i = 0; i != N; i++) {
|
for (int i = 0; i != N; i++) {
|
||||||
addByteCode(ByteCode.DUP);
|
addByteCode(ByteCode.DUP);
|
||||||
push(i);
|
push(i);
|
||||||
push(scriptOrFn.getParameterOrVarName(i));
|
push(scriptOrFn.getParamOrVarName(i));
|
||||||
addByteCode(ByteCode.AASTORE);
|
addByteCode(ByteCode.AASTORE);
|
||||||
}
|
}
|
||||||
addByteCode(ByteCode.ALOAD_0);
|
addByteCode(ByteCode.ALOAD_0);
|
||||||
|
@ -1163,7 +1163,7 @@ public class Codegen extends Interpreter {
|
||||||
"argNames", "[Ljava/lang/String;");
|
"argNames", "[Ljava/lang/String;");
|
||||||
}
|
}
|
||||||
|
|
||||||
int parmCount = scriptOrFn.getParameterCount();
|
int parmCount = scriptOrFn.getParamCount();
|
||||||
if (parmCount != 0) {
|
if (parmCount != 0) {
|
||||||
setNonTrivialInit(methodName);
|
setNonTrivialInit(methodName);
|
||||||
addByteCode(ByteCode.ALOAD_0);
|
addByteCode(ByteCode.ALOAD_0);
|
||||||
|
@ -1373,7 +1373,7 @@ public class Codegen extends Interpreter {
|
||||||
!((OptFunctionNode)scriptOrFn).requiresActivation();
|
!((OptFunctionNode)scriptOrFn).requiresActivation();
|
||||||
if (hasVarsInRegs) {
|
if (hasVarsInRegs) {
|
||||||
// No need to create activation. Pad arguments if need be.
|
// No need to create activation. Pad arguments if need be.
|
||||||
int parmCount = scriptOrFn.getParameterCount();
|
int parmCount = scriptOrFn.getParamCount();
|
||||||
if (inFunction && parmCount > 0 && directParameterCount < 0) {
|
if (inFunction && parmCount > 0 && directParameterCount < 0) {
|
||||||
// Set up args array
|
// Set up args array
|
||||||
// check length of arguments, pad if need be
|
// check length of arguments, pad if need be
|
||||||
|
|
|
@ -48,17 +48,17 @@ class OptFunctionNode extends FunctionNode {
|
||||||
|
|
||||||
protected void finishParsing(IRFactory irFactory) {
|
protected void finishParsing(IRFactory irFactory) {
|
||||||
super.finishParsing(irFactory);
|
super.finishParsing(irFactory);
|
||||||
int N = getParameterAndVarCount();
|
int N = getParamAndVarCount();
|
||||||
int parameterCount = getParameterCount();
|
int parameterCount = getParamCount();
|
||||||
optVars = new OptLocalVariable[N];
|
optVars = new OptLocalVariable[N];
|
||||||
for (int i = 0; i != N; ++i) {
|
for (int i = 0; i != N; ++i) {
|
||||||
String name = getParameterOrVarName(i);
|
String name = getParamOrVarName(i);
|
||||||
optVars[i] = new OptLocalVariable(name, i < parameterCount);
|
optVars[i] = new OptLocalVariable(name, i < parameterCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getDirectCallParameterSignature() {
|
String getDirectCallParameterSignature() {
|
||||||
int pCount = getParameterCount();
|
int pCount = getParamCount();
|
||||||
switch (pCount) {
|
switch (pCount) {
|
||||||
case 0: return ZERO_PARAM_SIG;
|
case 0: return ZERO_PARAM_SIG;
|
||||||
case 1: return ONE_PARAM_SIG;
|
case 1: return ONE_PARAM_SIG;
|
||||||
|
@ -129,7 +129,7 @@ class OptFunctionNode extends FunctionNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
OptLocalVariable getVar(String name) {
|
OptLocalVariable getVar(String name) {
|
||||||
int index = getParameterOrVarIndex(name);
|
int index = getParamOrVarIndex(name);
|
||||||
if (index < 0) { return null; }
|
if (index < 0) { return null; }
|
||||||
return optVars[index];
|
return optVars[index];
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ class OptTransformer extends NodeTransformer {
|
||||||
OptFunctionNode theFunction
|
OptFunctionNode theFunction
|
||||||
= (OptFunctionNode)theFnClassNameList.get(targetName);
|
= (OptFunctionNode)theFnClassNameList.get(targetName);
|
||||||
if (theFunction != null) {
|
if (theFunction != null) {
|
||||||
int N = theFunction.getParameterCount();
|
int N = theFunction.getParamCount();
|
||||||
// Refuse to directCall any function with more
|
// Refuse to directCall any function with more
|
||||||
// than 32 parameters - prevent code explosion
|
// than 32 parameters - prevent code explosion
|
||||||
// for wacky test cases
|
// for wacky test cases
|
||||||
|
|
Загрузка…
Ссылка в новой задаче