Implement correct semantics of storage class (global, var, let) for
destructuring assignment. git-svn-id: svn://10.0.0.236/trunk@232109 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -517,48 +517,18 @@ final class IRFactory
|
||||
|
||||
return loop;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true iff "n" is an array literal of length 2. This is
|
||||
* surprisingly hard to determine due to the "skip_indexes" property.
|
||||
*/
|
||||
private boolean isArrayLitOfLengthTwo(Node n) {
|
||||
int[] skipIndexes = (int[])n.getProp(Node.SKIP_INDEXES_PROP);
|
||||
if (n.getFirstChild() != null && n.getLastChild() != null &&
|
||||
n.getFirstChild().getNext() == n.getLastChild() &&
|
||||
skipIndexes == null)
|
||||
{
|
||||
// two elements in tree, no skipped indexes
|
||||
return true;
|
||||
}
|
||||
if (n.getFirstChild() != null && n.getFirstChild() == n.getLastChild() &&
|
||||
skipIndexes != null && skipIndexes.length == 1 &&
|
||||
skipIndexes[0] < 2)
|
||||
{
|
||||
// one element in tree, one skipped index
|
||||
return true;
|
||||
}
|
||||
if (n.getFirstChild() == null &&
|
||||
skipIndexes != null && skipIndexes.length == 2 &&
|
||||
skipIndexes[0] == 0 && skipIndexes[1] == 1)
|
||||
{
|
||||
// no elements in tree, two skipped indices
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For .. In
|
||||
*
|
||||
*/
|
||||
Node createForIn(Node loop, Node lhs, Node obj, Node body,
|
||||
Node createForIn(int declType, Node loop, Node lhs, Node obj, Node body,
|
||||
boolean isForEach)
|
||||
{
|
||||
int type = lhs.getType();
|
||||
|
||||
int destructuring = -1;
|
||||
int destructuringLen = 0;
|
||||
Node lvalue;
|
||||
boolean destructuring = false;
|
||||
int type = lhs.getType();
|
||||
if (type == Token.VAR || type == Token.LET) {
|
||||
/*
|
||||
* check that there was only one variable given.
|
||||
@@ -570,18 +540,20 @@ final class IRFactory
|
||||
if (lhs.getFirstChild() != lastChild) {
|
||||
parser.reportError("msg.mult.index");
|
||||
}
|
||||
lvalue = Node.newString(Token.NAME, lastChild.getString());
|
||||
} else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) {
|
||||
// destructuring assignment is only allowed in for each or
|
||||
// with an array type of length 2 (to hold key and value)
|
||||
if (!isForEach && (type == Token.OBJECTLIT ||
|
||||
!isArrayLitOfLengthTwo(lhs)))
|
||||
if (lastChild.getType() == Token.ARRAYLIT ||
|
||||
lastChild.getType() == Token.OBJECTLIT)
|
||||
{
|
||||
parser.reportError("msg.bad.for.in.lhs");
|
||||
return obj;
|
||||
type = destructuring = lastChild.getType();
|
||||
lvalue = lastChild;
|
||||
destructuringLen = lastChild.getIntProp(
|
||||
Node.DESTRUCTURING_ARRAY_LENGTH, 0);
|
||||
} else {
|
||||
lvalue = Node.newString(Token.NAME, lastChild.getString());
|
||||
}
|
||||
lvalue = lhs;
|
||||
destructuring = true;
|
||||
} else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) {
|
||||
destructuring = type;
|
||||
lvalue = lhs;
|
||||
destructuringLen = lhs.getIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, 0);
|
||||
} else {
|
||||
lvalue = makeReference(lhs);
|
||||
if (lvalue == null) {
|
||||
@@ -591,9 +563,9 @@ final class IRFactory
|
||||
}
|
||||
|
||||
Node localBlock = new Node(Token.LOCAL_BLOCK);
|
||||
int initType = (isForEach) ? Token.ENUM_INIT_VALUES :
|
||||
(destructuring) ? Token.ENUM_INIT_ARRAY :
|
||||
Token.ENUM_INIT_KEYS;
|
||||
int initType = (isForEach) ? Token.ENUM_INIT_VALUES :
|
||||
(destructuring != -1) ? Token.ENUM_INIT_ARRAY :
|
||||
Token.ENUM_INIT_KEYS;
|
||||
Node init = new Node(initType, obj);
|
||||
init.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
|
||||
Node cond = new Node(Token.ENUM_NEXT);
|
||||
@@ -603,8 +575,16 @@ final class IRFactory
|
||||
|
||||
Node newBody = new Node(Token.BLOCK);
|
||||
Node assign;
|
||||
if (destructuring) {
|
||||
assign = createAssignment(Token.ASSIGN, lvalue, id);
|
||||
if (destructuring != -1) {
|
||||
assign = createDestructuringAssignment(declType, lvalue, id);
|
||||
int len = assign.getIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, 0);
|
||||
if (!isForEach && (destructuring == Token.OBJECTLIT ||
|
||||
destructuringLen!= 2))
|
||||
{
|
||||
// destructuring assignment is only allowed in for..each or
|
||||
// with an array type of length 2 (to hold key and value)
|
||||
parser.reportError("msg.bad.for.in.destruct");
|
||||
}
|
||||
} else {
|
||||
assign = simpleAssignment(lvalue, id);
|
||||
}
|
||||
@@ -832,7 +812,7 @@ final class IRFactory
|
||||
return result;
|
||||
}
|
||||
|
||||
Node createArrayLiteral(ObjArray elems, int skipCount)
|
||||
Node createArrayLiteral(ObjArray elems, int skipCount, int destructuringLen)
|
||||
{
|
||||
int length = elems.size();
|
||||
int[] skipIndexes = null;
|
||||
@@ -852,6 +832,7 @@ final class IRFactory
|
||||
if (skipCount != 0) {
|
||||
array.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
|
||||
}
|
||||
array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, destructuringLen);
|
||||
return array;
|
||||
}
|
||||
|
||||
@@ -1333,7 +1314,7 @@ final class IRFactory
|
||||
parser.reportError("msg.bad.destruct.op");
|
||||
return right;
|
||||
}
|
||||
return createDestructuringAssignment(left, right);
|
||||
return createDestructuringAssignment(-1, left, right);
|
||||
}
|
||||
parser.reportError("msg.bad.assign.left");
|
||||
return right;
|
||||
@@ -1395,23 +1376,25 @@ final class IRFactory
|
||||
* as an array or object literal and a right hand side expression,
|
||||
* rewrite as a series of assignments to the variables defined in
|
||||
* left from property accesses to the expression on the right.
|
||||
* @param type declaration type: Token.VAR or Token.LET or -1
|
||||
* @param left array or object literal containing NAME nodes for
|
||||
* variables to assign
|
||||
* @param right expression to assign from
|
||||
* @return expression that performs a series of assignments to
|
||||
* the variables defined in left
|
||||
*/
|
||||
Node createDestructuringAssignment(Node left, Node right)
|
||||
Node createDestructuringAssignment(int type, Node left, Node right)
|
||||
{
|
||||
String tempName = parser.currentScriptOrFn.getNextTempName();
|
||||
Node result = destructuringAssignmentHelper(left, right, tempName);
|
||||
Node result = destructuringAssignmentHelper(type, left, right,
|
||||
tempName);
|
||||
Node comma = result.getLastChild();
|
||||
comma.addChildToBack(createName(tempName));
|
||||
return result;
|
||||
}
|
||||
|
||||
private Node destructuringAssignmentHelper(Node left, Node right,
|
||||
String tempName)
|
||||
private Node destructuringAssignmentHelper(int variableType, Node left,
|
||||
Node right, String tempName)
|
||||
{
|
||||
Node result = createScopeNode(Token.LETEXPR,
|
||||
parser.getCurrentLineNumber());
|
||||
@@ -1420,75 +1403,88 @@ final class IRFactory
|
||||
try {
|
||||
parser.pushScope(result);
|
||||
parser.defineSymbol(Token.LET, tempName);
|
||||
Node comma = new Node(Token.COMMA);
|
||||
result.addChildToBack(comma);
|
||||
boolean empty = true;
|
||||
int type = left.getType();
|
||||
if (type == Token.ARRAYLIT) {
|
||||
int index = 0;
|
||||
int[] skipIndices = (int[])left.getProp(Node.SKIP_INDEXES_PROP);
|
||||
int skip = 0;
|
||||
for (Node n = left.getFirstChild(); n != null; n = n.getNext())
|
||||
{
|
||||
if (skipIndices != null) {
|
||||
while (skip < skipIndices.length &&
|
||||
skipIndices[skip] == index) {
|
||||
skip++;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
Node rightElem = new Node(Token.GETELEM,
|
||||
createName(tempName),
|
||||
createNumber(index));
|
||||
if (n.getType() == Token.NAME) {
|
||||
comma.addChildToBack(new Node(Token.SETNAME,
|
||||
createName(Token.BINDNAME, n.getString(), null),
|
||||
rightElem));
|
||||
} else {
|
||||
comma.addChildToBack(
|
||||
destructuringAssignmentHelper(n, rightElem,
|
||||
parser.currentScriptOrFn.getNextTempName()));
|
||||
}
|
||||
index++;
|
||||
empty = false;
|
||||
}
|
||||
} else if (type == Token.OBJECTLIT) {
|
||||
int index = 0;
|
||||
Object[] propertyIds = (Object[])
|
||||
left.getProp(Node.OBJECT_IDS_PROP);
|
||||
for (Node n = left.getFirstChild(); n != null; n = n.getNext())
|
||||
{
|
||||
Object id = propertyIds[index];
|
||||
Node rightElem = id instanceof String
|
||||
? new Node(Token.GETPROP,
|
||||
createName(tempName),
|
||||
createString((String)id))
|
||||
: new Node(Token.GETELEM,
|
||||
createName(tempName),
|
||||
createNumber(((Number)id).intValue()));
|
||||
if (n.getType() == Token.NAME) {
|
||||
comma.addChildToBack(new Node(Token.SETNAME,
|
||||
createName(Token.BINDNAME, n.getString(), null),
|
||||
rightElem));
|
||||
} else {
|
||||
comma.addChildToBack(
|
||||
destructuringAssignmentHelper(n, rightElem,
|
||||
parser.currentScriptOrFn.getNextTempName()));
|
||||
}
|
||||
index++;
|
||||
empty = false;
|
||||
}
|
||||
} else {
|
||||
parser.reportError("msg.bad.assign.left");
|
||||
}
|
||||
if (empty) {
|
||||
// Don't want a COMMA node with no children. Just add a zero.
|
||||
comma.addChildToBack(createNumber(0));
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
parser.popScope();
|
||||
}
|
||||
Node comma = new Node(Token.COMMA);
|
||||
result.addChildToBack(comma);
|
||||
boolean empty = true;
|
||||
int type = left.getType();
|
||||
if (type == Token.ARRAYLIT) {
|
||||
int index = 0;
|
||||
int[] skipIndices = (int[])left.getProp(Node.SKIP_INDEXES_PROP);
|
||||
int skip = 0;
|
||||
Node n = left.getFirstChild();
|
||||
for (;;) {
|
||||
if (skipIndices != null) {
|
||||
while (skip < skipIndices.length &&
|
||||
skipIndices[skip] == index) {
|
||||
skip++;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
if (n == null)
|
||||
break;
|
||||
Node rightElem = new Node(Token.GETELEM,
|
||||
createName(tempName),
|
||||
createNumber(index));
|
||||
if (n.getType() == Token.NAME) {
|
||||
String name = n.getString();
|
||||
comma.addChildToBack(new Node(Token.SETNAME,
|
||||
createName(Token.BINDNAME, name, null),
|
||||
rightElem));
|
||||
if (variableType != -1) {
|
||||
parser.defineSymbol(variableType, name);
|
||||
}
|
||||
} else {
|
||||
comma.addChildToBack(
|
||||
destructuringAssignmentHelper(variableType, n,
|
||||
rightElem,
|
||||
parser.currentScriptOrFn.getNextTempName()));
|
||||
}
|
||||
index++;
|
||||
empty = false;
|
||||
n = n.getNext();
|
||||
}
|
||||
} else if (type == Token.OBJECTLIT) {
|
||||
int index = 0;
|
||||
Object[] propertyIds = (Object[])
|
||||
left.getProp(Node.OBJECT_IDS_PROP);
|
||||
for (Node n = left.getFirstChild(); n != null; n = n.getNext())
|
||||
{
|
||||
Object id = propertyIds[index];
|
||||
Node rightElem = id instanceof String
|
||||
? new Node(Token.GETPROP,
|
||||
createName(tempName),
|
||||
createString((String)id))
|
||||
: new Node(Token.GETELEM,
|
||||
createName(tempName),
|
||||
createNumber(((Number)id).intValue()));
|
||||
if (n.getType() == Token.NAME) {
|
||||
String name = n.getString();
|
||||
comma.addChildToBack(new Node(Token.SETNAME,
|
||||
createName(Token.BINDNAME, name, null),
|
||||
rightElem));
|
||||
if (variableType != -1) {
|
||||
parser.defineSymbol(variableType, name);
|
||||
}
|
||||
} else {
|
||||
comma.addChildToBack(
|
||||
destructuringAssignmentHelper(variableType, n,
|
||||
rightElem,
|
||||
parser.currentScriptOrFn.getNextTempName()));
|
||||
}
|
||||
index++;
|
||||
empty = false;
|
||||
}
|
||||
} else {
|
||||
parser.reportError("msg.bad.assign.left");
|
||||
}
|
||||
if (empty) {
|
||||
// Don't want a COMMA node with no children. Just add a zero.
|
||||
comma.addChildToBack(createNumber(0));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Node createUseLocal(Node localBlock)
|
||||
|
||||
@@ -86,7 +86,8 @@ public class Node
|
||||
CONTROL_BLOCK_PROP = 18, // flags a control block that can drop off
|
||||
PARENTHESIZED_PROP = 19, // expression is parenthesized
|
||||
GENERATOR_END_PROP = 20,
|
||||
LAST_PROP = 20;
|
||||
DESTRUCTURING_ARRAY_LENGTH = 21,
|
||||
LAST_PROP = 21;
|
||||
|
||||
// values of ISNUMBER_PROP to specify
|
||||
// which of the children are Number types
|
||||
@@ -534,6 +535,8 @@ public class Node
|
||||
case CONTROL_BLOCK_PROP: return "control_block_prop";
|
||||
case PARENTHESIZED_PROP: return "parenthesized_prop";
|
||||
case GENERATOR_END_PROP: return "generator_end";
|
||||
case DESTRUCTURING_ARRAY_LENGTH:
|
||||
return "destructuring_array_length";
|
||||
|
||||
default: Kit.codeBug();
|
||||
}
|
||||
|
||||
@@ -305,17 +305,23 @@ public class NodeTransformer
|
||||
// Move cursor to next before createAssignment gets chance
|
||||
// to change n.next
|
||||
Node n = cursor;
|
||||
if (n.getType() != Token.NAME) Kit.codeBug();
|
||||
cursor = cursor.getNext();
|
||||
if (!n.hasChildren())
|
||||
continue;
|
||||
Node init = n.getFirstChild();
|
||||
n.removeChild(init);
|
||||
n.setType(Token.BINDNAME);
|
||||
n = new Node(type == Token.CONST ?
|
||||
Token.SETCONST :
|
||||
Token.SETNAME,
|
||||
n, init);
|
||||
if (n.getType() == Token.NAME) {
|
||||
if (!n.hasChildren())
|
||||
continue;
|
||||
Node init = n.getFirstChild();
|
||||
n.removeChild(init);
|
||||
n.setType(Token.BINDNAME);
|
||||
n = new Node(type == Token.CONST ?
|
||||
Token.SETCONST :
|
||||
Token.SETNAME,
|
||||
n, init);
|
||||
} else {
|
||||
// May be a destructuring assignment already transformed
|
||||
// to a LETEXPR
|
||||
if (n.getType() != Token.LETEXPR)
|
||||
throw Kit.codeBug();
|
||||
}
|
||||
Node pop = new Node(Token.EXPR_VOID, n, node.getLineno());
|
||||
result.addChildToBack(pop);
|
||||
}
|
||||
|
||||
@@ -873,6 +873,7 @@ public class Parser
|
||||
Node cond; // Node cond is also object in 'foo in object'
|
||||
Node incr = null;
|
||||
Node body;
|
||||
int declType = -1;
|
||||
|
||||
// See if this is a for each () instead of just a for ()
|
||||
if (matchToken(Token.NAME)) {
|
||||
@@ -895,6 +896,7 @@ public class Parser
|
||||
consumeToken(); // consume the token
|
||||
decompiler.addToken(tt);
|
||||
init = variables(true, true, tt);
|
||||
declType = tt;
|
||||
}
|
||||
else {
|
||||
init = expr(true);
|
||||
@@ -933,7 +935,8 @@ public class Parser
|
||||
if (incr == null) {
|
||||
// cond could be null if 'in obj' got eaten
|
||||
// by the init node.
|
||||
pn = nf.createForIn(loop, init, cond, body, isForEach);
|
||||
pn = nf.createForIn(declType, loop, init, cond, body,
|
||||
isForEach);
|
||||
} else {
|
||||
pn = nf.createFor(loop, init, cond, incr, body);
|
||||
}
|
||||
@@ -1367,50 +1370,67 @@ public class Parser
|
||||
{
|
||||
Node result = nf.createVariables(inStatement ? declType : Token.COMMA,
|
||||
ts.getLineno());
|
||||
|
||||
Node destructuringInit = null;
|
||||
boolean first = true;
|
||||
for (;;) {
|
||||
Node destructuring = null;
|
||||
String s = null;
|
||||
int tt = peekToken();
|
||||
if (tt == Token.LB || tt == Token.LC) {
|
||||
// Destructuring assignment, e.g., var [a,b] = ...
|
||||
Node n = expr(inFor);
|
||||
return inStatement && !inFor
|
||||
? new Node(Token.EXPR_VOID, n, ts.getLineno())
|
||||
: n;
|
||||
destructuring = primaryExpr();
|
||||
} else {
|
||||
// Simple variable name
|
||||
mustMatchToken(Token.NAME, "msg.bad.var");
|
||||
s = ts.getString();
|
||||
|
||||
if (!first)
|
||||
decompiler.addToken(Token.COMMA);
|
||||
first = false;
|
||||
|
||||
decompiler.addName(s);
|
||||
defineSymbol(declType, s);
|
||||
}
|
||||
mustMatchToken(Token.NAME, "msg.bad.var");
|
||||
String s = ts.getString();
|
||||
|
||||
if (!first)
|
||||
decompiler.addToken(Token.COMMA);
|
||||
first = false;
|
||||
|
||||
decompiler.addName(s);
|
||||
defineSymbol(declType, s);
|
||||
|
||||
|
||||
Node init = null;
|
||||
if (matchToken(Token.ASSIGN)) {
|
||||
decompiler.addToken(Token.ASSIGN);
|
||||
init = assignExpr(inFor);
|
||||
}
|
||||
|
||||
if (inStatement) {
|
||||
Node name = nf.createName(s);
|
||||
if (init != null)
|
||||
nf.addChildToBack(name, init);
|
||||
nf.addChildToBack(result, name);
|
||||
} else if (init != null) {
|
||||
Node string = nf.createString(s);
|
||||
string.setScope(currentScope);
|
||||
nf.addChildToBack(result,
|
||||
nf.createBinary(Token.SETVAR, string, init));
|
||||
|
||||
if (destructuring != null) {
|
||||
if (init == null) {
|
||||
if (!inFor)
|
||||
reportError("msg.destruct.assign.no.init");
|
||||
nf.addChildToBack(result, destructuring);
|
||||
} else {
|
||||
if (destructuringInit == null) {
|
||||
destructuringInit = new Node(Token.COMMA);
|
||||
}
|
||||
nf.addChildToBack(result,
|
||||
nf.createDestructuringAssignment(declType,
|
||||
destructuring, init));
|
||||
}
|
||||
} else {
|
||||
if (inStatement) {
|
||||
Node name = nf.createName(s);
|
||||
if (init != null)
|
||||
nf.addChildToBack(name, init);
|
||||
nf.addChildToBack(result, name);
|
||||
} else if (init != null) {
|
||||
Node string = nf.createString(s);
|
||||
string.setScope(currentScope);
|
||||
nf.addChildToBack(result,
|
||||
nf.createBinary(Token.SETVAR, string, init));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!matchToken(Token.COMMA))
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private Node let(boolean isStatement)
|
||||
throws IOException, ParserException
|
||||
@@ -2203,7 +2223,8 @@ public class Parser
|
||||
|
||||
Node loop = enterLoop(null, true);
|
||||
try {
|
||||
return nf.createForIn(loop, init, iterator, body, isForEach);
|
||||
return nf.createForIn(Token.LET, loop, init, iterator, body,
|
||||
isForEach);
|
||||
} finally {
|
||||
exitLoop(false);
|
||||
}
|
||||
@@ -2225,6 +2246,7 @@ public class Parser
|
||||
case Token.LB: {
|
||||
ObjArray elems = new ObjArray();
|
||||
int skipCount = 0;
|
||||
int destructuringLen = 0;
|
||||
decompiler.addToken(Token.LB);
|
||||
boolean after_lb_or_comma = true;
|
||||
for (;;) {
|
||||
@@ -2242,6 +2264,13 @@ public class Parser
|
||||
} else if (tt == Token.RB) {
|
||||
consumeToken();
|
||||
decompiler.addToken(Token.RB);
|
||||
// for ([a,] in obj) is legal, but for ([a] in obj) is
|
||||
// not since we have both key and value supplied. The
|
||||
// trick is that [a,] and [a] are equivalent in other
|
||||
// array literal contexts. So we calculate a special
|
||||
// length value just for destructuring assignment.
|
||||
destructuringLen = elems.size() +
|
||||
(after_lb_or_comma ? 1 : 0);
|
||||
break;
|
||||
} else if (skipCount == 0 && elems.size() == 1 &&
|
||||
tt == Token.FOR)
|
||||
@@ -2276,7 +2305,7 @@ public class Parser
|
||||
after_lb_or_comma = false;
|
||||
}
|
||||
}
|
||||
return nf.createArrayLiteral(elems, skipCount);
|
||||
return nf.createArrayLiteral(elems, skipCount, destructuringLen);
|
||||
}
|
||||
|
||||
case Token.LC: {
|
||||
|
||||
@@ -114,6 +114,10 @@ msg.bad.for.in.lhs =\
|
||||
msg.mult.index =\
|
||||
Only one variable allowed in for..in loop.
|
||||
|
||||
msg.bad.for.in.destruct =\
|
||||
Left hand side of for..in loop must be an array of length 2 to accept \
|
||||
key/value pair.
|
||||
|
||||
msg.cant.convert =\
|
||||
Can''t convert to type "{0}".
|
||||
|
||||
@@ -482,6 +486,9 @@ msg.equal.as.assign =\
|
||||
msg.var.hides.arg =\
|
||||
Variable {0} hides argument
|
||||
|
||||
msg.destruct.assign.no.init =\
|
||||
Missing = in destructuring declaration
|
||||
|
||||
# ScriptRuntime
|
||||
msg.no.properties =\
|
||||
{0} has no properties.
|
||||
|
||||
@@ -93,7 +93,7 @@ function test()
|
||||
|
||||
// syntax error in js17
|
||||
var iter2 = gen(list2);
|
||||
expect = 'SyntaxError: invalid for/in left-hand side';
|
||||
expect = /SyntaxError: (invalid for.in left-hand side|Left hand side of for..in loop must be an array of length 2 to accept key.value pair.)/;
|
||||
actual = '';
|
||||
|
||||
try
|
||||
@@ -108,7 +108,7 @@ function test()
|
||||
actual = ex + '';
|
||||
}
|
||||
|
||||
reportCompare(expect, actual, summary + ': 4');
|
||||
reportMatch(expect, actual, summary + ': 4');
|
||||
|
||||
exitFunc ('test');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user