Unification of size representation in OP_NEWTABLE and OP_SETLIST
Opcodes OP_NEWTABLE and OP_SETLIST use the same representation to store the size of the array part of a table. This new representation can go up to 2^33 (8 + 25 bits).
This commit is contained in:
40
lcode.c
40
lcode.c
@@ -372,7 +372,7 @@ static void removelastinstruction (FuncState *fs) {
|
|||||||
** Emit instruction 'i', checking for array sizes and saving also its
|
** Emit instruction 'i', checking for array sizes and saving also its
|
||||||
** line information. Return 'i' position.
|
** line information. Return 'i' position.
|
||||||
*/
|
*/
|
||||||
static int luaK_code (FuncState *fs, Instruction i) {
|
int luaK_code (FuncState *fs, Instruction i) {
|
||||||
Proto *f = fs->f;
|
Proto *f = fs->f;
|
||||||
/* put new instruction in code array */
|
/* put new instruction in code array */
|
||||||
luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction,
|
luaM_growvector(fs->ls->L, f->code, fs->pc, f->sizecode, Instruction,
|
||||||
@@ -430,7 +430,7 @@ static int codesJ (FuncState *fs, OpCode o, int sj, int k) {
|
|||||||
/*
|
/*
|
||||||
** Emit an "extra argument" instruction (format 'iAx')
|
** Emit an "extra argument" instruction (format 'iAx')
|
||||||
*/
|
*/
|
||||||
int luaK_codeextraarg (FuncState *fs, int a) {
|
static int codeextraarg (FuncState *fs, int a) {
|
||||||
lua_assert(a <= MAXARG_Ax);
|
lua_assert(a <= MAXARG_Ax);
|
||||||
return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a));
|
return luaK_code(fs, CREATE_Ax(OP_EXTRAARG, a));
|
||||||
}
|
}
|
||||||
@@ -446,7 +446,7 @@ static int luaK_codek (FuncState *fs, int reg, int k) {
|
|||||||
return luaK_codeABx(fs, OP_LOADK, reg, k);
|
return luaK_codeABx(fs, OP_LOADK, reg, k);
|
||||||
else {
|
else {
|
||||||
int p = luaK_codeABx(fs, OP_LOADKX, reg, 0);
|
int p = luaK_codeABx(fs, OP_LOADKX, reg, 0);
|
||||||
luaK_codeextraarg(fs, k);
|
codeextraarg(fs, k);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1672,6 +1672,22 @@ void luaK_fixline (FuncState *fs, int line) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void luaK_settablesize (FuncState *fs, int pc, int ra, int rc, int rb) {
|
||||||
|
Instruction *inst = &fs->f->code[pc];
|
||||||
|
int extra = 0;
|
||||||
|
int k = 0;
|
||||||
|
if (rb != 0)
|
||||||
|
rb = luaO_ceillog2(rb) + 1; /* hash size */
|
||||||
|
if (rc > MAXARG_C) { /* does it need the extra argument? */
|
||||||
|
extra = rc / (MAXARG_C + 1);
|
||||||
|
rc %= (MAXARG_C + 1);
|
||||||
|
k = 1;
|
||||||
|
}
|
||||||
|
*inst = CREATE_ABCk(OP_NEWTABLE, ra, rb, rc, k);
|
||||||
|
*(inst + 1) = CREATE_Ax(OP_EXTRAARG, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Emit a SETLIST instruction.
|
** Emit a SETLIST instruction.
|
||||||
** 'base' is register that keeps table;
|
** 'base' is register that keeps table;
|
||||||
@@ -1680,17 +1696,17 @@ void luaK_fixline (FuncState *fs, int line) {
|
|||||||
** table (or LUA_MULTRET to add up to stack top).
|
** table (or LUA_MULTRET to add up to stack top).
|
||||||
*/
|
*/
|
||||||
void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) {
|
void luaK_setlist (FuncState *fs, int base, int nelems, int tostore) {
|
||||||
int c = (nelems - 1)/LFIELDS_PER_FLUSH + 1;
|
|
||||||
int b = (tostore == LUA_MULTRET) ? 0 : tostore;
|
|
||||||
lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH);
|
lua_assert(tostore != 0 && tostore <= LFIELDS_PER_FLUSH);
|
||||||
if (c <= MAXARG_C)
|
if (tostore == LUA_MULTRET)
|
||||||
luaK_codeABC(fs, OP_SETLIST, base, b, c);
|
tostore = 0;
|
||||||
else if (c <= MAXARG_Ax) {
|
if (nelems <= MAXARG_C)
|
||||||
luaK_codeABC(fs, OP_SETLIST, base, b, 0);
|
luaK_codeABC(fs, OP_SETLIST, base, tostore, nelems);
|
||||||
luaK_codeextraarg(fs, c);
|
else {
|
||||||
|
int extra = nelems / (MAXARG_C + 1);
|
||||||
|
nelems %= (MAXARG_C + 1);
|
||||||
|
luaK_codeABCk(fs, OP_SETLIST, base, tostore, nelems, 1);
|
||||||
|
codeextraarg(fs, extra);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
luaX_syntaxerror(fs->ls, "constructor too long");
|
|
||||||
fs->freereg = base + 1; /* free registers with list values */
|
fs->freereg = base + 1; /* free registers with list values */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
lcode.h
4
lcode.h
@@ -51,11 +51,11 @@ typedef enum UnOpr { OPR_MINUS, OPR_BNOT, OPR_NOT, OPR_LEN, OPR_NOUNOPR } UnOpr;
|
|||||||
|
|
||||||
#define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t)
|
#define luaK_jumpto(fs,t) luaK_patchlist(fs, luaK_jump(fs), t)
|
||||||
|
|
||||||
|
LUAI_FUNC int luaK_code (FuncState *fs, Instruction i);
|
||||||
LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx);
|
LUAI_FUNC int luaK_codeABx (FuncState *fs, OpCode o, int A, unsigned int Bx);
|
||||||
LUAI_FUNC int luaK_codeAsBx (FuncState *fs, OpCode o, int A, int Bx);
|
LUAI_FUNC int luaK_codeAsBx (FuncState *fs, OpCode o, int A, int Bx);
|
||||||
LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A,
|
LUAI_FUNC int luaK_codeABCk (FuncState *fs, OpCode o, int A,
|
||||||
int B, int C, int k);
|
int B, int C, int k);
|
||||||
LUAI_FUNC int luaK_codeextraarg (FuncState *fs, int a);
|
|
||||||
LUAI_FUNC int luaK_isKint (expdesc *e);
|
LUAI_FUNC int luaK_isKint (expdesc *e);
|
||||||
LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v);
|
LUAI_FUNC int luaK_exp2const (FuncState *fs, const expdesc *e, TValue *v);
|
||||||
LUAI_FUNC void luaK_fixline (FuncState *fs, int line);
|
LUAI_FUNC void luaK_fixline (FuncState *fs, int line);
|
||||||
@@ -87,6 +87,8 @@ LUAI_FUNC void luaK_prefix (FuncState *fs, UnOpr op, expdesc *v, int line);
|
|||||||
LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v);
|
LUAI_FUNC void luaK_infix (FuncState *fs, BinOpr op, expdesc *v);
|
||||||
LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1,
|
LUAI_FUNC void luaK_posfix (FuncState *fs, BinOpr op, expdesc *v1,
|
||||||
expdesc *v2, int line);
|
expdesc *v2, int line);
|
||||||
|
LUAI_FUNC void luaK_settablesize (FuncState *fs, int pc,
|
||||||
|
int ra, int rb, int rc);
|
||||||
LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore);
|
LUAI_FUNC void luaK_setlist (FuncState *fs, int base, int nelems, int tostore);
|
||||||
LUAI_FUNC void luaK_finish (FuncState *fs);
|
LUAI_FUNC void luaK_finish (FuncState *fs);
|
||||||
LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg);
|
LUAI_FUNC l_noret luaK_semerror (LexState *ls, const char *msg);
|
||||||
|
|||||||
23
lopcodes.h
23
lopcodes.h
@@ -214,7 +214,7 @@ OP_SETTABLE,/* A B C R(A)[R(B)] := RK(C) */
|
|||||||
OP_SETI,/* A B C R(A)[B] := RK(C) */
|
OP_SETI,/* A B C R(A)[B] := RK(C) */
|
||||||
OP_SETFIELD,/* A B C R(A)[K(B):string] := RK(C) */
|
OP_SETFIELD,/* A B C R(A)[K(B):string] := RK(C) */
|
||||||
|
|
||||||
OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */
|
OP_NEWTABLE,/* A B C R(A) := {} */
|
||||||
|
|
||||||
OP_SELF,/* A B C R(A+1) := R(B); R(A) := R(B)[RK(C):string] */
|
OP_SELF,/* A B C R(A+1) := R(B); R(A) := R(B)[RK(C):string] */
|
||||||
|
|
||||||
@@ -321,12 +321,17 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
|
|||||||
|
|
||||||
(*) In OP_RETURN, if (B == 0) then return up to 'top'.
|
(*) In OP_RETURN, if (B == 0) then return up to 'top'.
|
||||||
|
|
||||||
(*) In OP_SETLIST, if (B == 0) then real B = 'top'; if (C == 0) then
|
(*) In OP_LOADKX and OP_NEWTABLE, the next instruction is always
|
||||||
next 'instruction' is EXTRAARG(real C).
|
|
||||||
|
|
||||||
(*) In OP_LOADKX and OP_NEWTABLE, the next 'instruction' is always
|
|
||||||
EXTRAARG.
|
EXTRAARG.
|
||||||
|
|
||||||
|
(*) In OP_SETLIST, if (B == 0) then real B = 'top'; if k, then
|
||||||
|
real C = EXTRAARG _ C (the bits of EXTRAARG concatenated with the
|
||||||
|
bits of C).
|
||||||
|
|
||||||
|
(*) In OP_NEWTABLE, B is log2 of the hash size (which is always a
|
||||||
|
power of 2) plus 1, or zero for size zero. If not k, the array size
|
||||||
|
is C. Otherwise, the array size is EXTRAARG _ C.
|
||||||
|
|
||||||
(*) For comparisons, k specifies what condition the test should accept
|
(*) For comparisons, k specifies what condition the test should accept
|
||||||
(true or false).
|
(true or false).
|
||||||
|
|
||||||
@@ -375,12 +380,4 @@ LUAI_DDEC(const lu_byte luaP_opmodes[NUM_OPCODES];)
|
|||||||
/* number of list items to accumulate before a SETLIST instruction */
|
/* number of list items to accumulate before a SETLIST instruction */
|
||||||
#define LFIELDS_PER_FLUSH 50
|
#define LFIELDS_PER_FLUSH 50
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
** In OP_NEWTABLE, array sizes smaller than LIMTABSZ are represented
|
|
||||||
** directly in R(B). Otherwise, array size is given by
|
|
||||||
** (R(B) - LIMTABSZ) + EXTRAARG * LFIELDS_PER_FLUSH
|
|
||||||
*/
|
|
||||||
#define LIMTABSZ (MAXARG_B - LFIELDS_PER_FLUSH)
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
29
lparser.c
29
lparser.c
@@ -815,7 +815,7 @@ typedef struct ConsControl {
|
|||||||
expdesc v; /* last list item read */
|
expdesc v; /* last list item read */
|
||||||
expdesc *t; /* table descriptor */
|
expdesc *t; /* table descriptor */
|
||||||
int nh; /* total number of 'record' elements */
|
int nh; /* total number of 'record' elements */
|
||||||
int na; /* total number of array elements */
|
int na; /* number of array elements already stored */
|
||||||
int tostore; /* number of array elements pending to be stored */
|
int tostore; /* number of array elements pending to be stored */
|
||||||
} ConsControl;
|
} ConsControl;
|
||||||
|
|
||||||
@@ -847,6 +847,7 @@ static void closelistfield (FuncState *fs, ConsControl *cc) {
|
|||||||
cc->v.k = VVOID;
|
cc->v.k = VVOID;
|
||||||
if (cc->tostore == LFIELDS_PER_FLUSH) {
|
if (cc->tostore == LFIELDS_PER_FLUSH) {
|
||||||
luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */
|
luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore); /* flush */
|
||||||
|
cc->na += cc->tostore;
|
||||||
cc->tostore = 0; /* no more items pending */
|
cc->tostore = 0; /* no more items pending */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,13 +865,13 @@ static void lastlistfield (FuncState *fs, ConsControl *cc) {
|
|||||||
luaK_exp2nextreg(fs, &cc->v);
|
luaK_exp2nextreg(fs, &cc->v);
|
||||||
luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore);
|
luaK_setlist(fs, cc->t->u.info, cc->na, cc->tostore);
|
||||||
}
|
}
|
||||||
|
cc->na += cc->tostore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void listfield (LexState *ls, ConsControl *cc) {
|
static void listfield (LexState *ls, ConsControl *cc) {
|
||||||
/* listfield -> exp */
|
/* listfield -> exp */
|
||||||
expr(ls, &cc->v);
|
expr(ls, &cc->v);
|
||||||
cc->na++;
|
|
||||||
cc->tostore++;
|
cc->tostore++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -897,22 +898,6 @@ static void field (LexState *ls, ConsControl *cc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void settablesize (FuncState *fs, ConsControl *cc, int pc) {
|
|
||||||
Instruction *inst = &fs->f->code[pc];
|
|
||||||
int rc = (cc->nh == 0) ? 0 : luaO_ceillog2(cc->nh) + 1;
|
|
||||||
int rb = cc->na;
|
|
||||||
int extra = 0;
|
|
||||||
if (rb >= LIMTABSZ) {
|
|
||||||
extra = rb / LFIELDS_PER_FLUSH;
|
|
||||||
rb = rb % LFIELDS_PER_FLUSH + LIMTABSZ;
|
|
||||||
checklimit(fs, extra, MAXARG_Ax, "items in a constructor");
|
|
||||||
}
|
|
||||||
SETARG_C(*inst, rc); /* set initial table size */
|
|
||||||
SETARG_B(*inst, rb); /* set initial array size */
|
|
||||||
SETARG_Ax(*(inst + 1), extra);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void constructor (LexState *ls, expdesc *t) {
|
static void constructor (LexState *ls, expdesc *t) {
|
||||||
/* constructor -> '{' [ field { sep field } [sep] ] '}'
|
/* constructor -> '{' [ field { sep field } [sep] ] '}'
|
||||||
sep -> ',' | ';' */
|
sep -> ',' | ';' */
|
||||||
@@ -920,12 +905,12 @@ static void constructor (LexState *ls, expdesc *t) {
|
|||||||
int line = ls->linenumber;
|
int line = ls->linenumber;
|
||||||
int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0);
|
int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0);
|
||||||
ConsControl cc;
|
ConsControl cc;
|
||||||
luaK_codeextraarg(fs, 0);
|
luaK_code(fs, 0); /* space for extra arg. */
|
||||||
cc.na = cc.nh = cc.tostore = 0;
|
cc.na = cc.nh = cc.tostore = 0;
|
||||||
cc.t = t;
|
cc.t = t;
|
||||||
init_exp(t, VRELOC, pc);
|
init_exp(t, VNONRELOC, fs->freereg); /* table will be at stack top */
|
||||||
|
luaK_reserveregs(fs, 1);
|
||||||
init_exp(&cc.v, VVOID, 0); /* no value (yet) */
|
init_exp(&cc.v, VVOID, 0); /* no value (yet) */
|
||||||
luaK_exp2nextreg(ls->fs, t); /* fix it at stack top */
|
|
||||||
checknext(ls, '{');
|
checknext(ls, '{');
|
||||||
do {
|
do {
|
||||||
lua_assert(cc.v.k == VVOID || cc.tostore > 0);
|
lua_assert(cc.v.k == VVOID || cc.tostore > 0);
|
||||||
@@ -935,7 +920,7 @@ static void constructor (LexState *ls, expdesc *t) {
|
|||||||
} while (testnext(ls, ',') || testnext(ls, ';'));
|
} while (testnext(ls, ',') || testnext(ls, ';'));
|
||||||
check_match(ls, '}', '{', line);
|
check_match(ls, '}', '{', line);
|
||||||
lastlistfield(fs, &cc);
|
lastlistfield(fs, &cc);
|
||||||
settablesize(fs, &cc, pc);
|
luaK_settablesize(fs, pc, t->u.info, cc.na, cc.nh);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* }====================================================================== */
|
/* }====================================================================== */
|
||||||
|
|||||||
28
lvm.c
28
lvm.c
@@ -1247,18 +1247,19 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
|
|||||||
vmbreak;
|
vmbreak;
|
||||||
}
|
}
|
||||||
vmcase(OP_NEWTABLE) {
|
vmcase(OP_NEWTABLE) {
|
||||||
int b = GETARG_B(i);
|
int b = GETARG_B(i); /* log2(hash size) + 1 */
|
||||||
int c = GETARG_C(i);
|
int c = GETARG_C(i); /* array size */
|
||||||
Table *t;
|
Table *t;
|
||||||
c = (c == 0) ? 0 : 1 << (c - 1); /* size is 2^c */
|
if (b > 0)
|
||||||
if (b >= LIMTABSZ)
|
b = 1 << (b - 1); /* size is 2^(b - 1) */
|
||||||
b += LFIELDS_PER_FLUSH * GETARG_Ax(*pc) - LIMTABSZ;
|
if (TESTARG_k(i))
|
||||||
|
c += GETARG_Ax(*pc) * (MAXARG_C + 1);
|
||||||
pc++; /* skip extra argument */
|
pc++; /* skip extra argument */
|
||||||
L->top = ci->top; /* correct top in case of GC */
|
L->top = ci->top; /* correct top in case of GC */
|
||||||
t = luaH_new(L); /* memory allocation */
|
t = luaH_new(L); /* memory allocation */
|
||||||
sethvalue2s(L, ra, t);
|
sethvalue2s(L, ra, t);
|
||||||
if (b != 0 || c != 0)
|
if (b != 0 || c != 0)
|
||||||
luaH_resize(L, t, b, c); /* idem */
|
luaH_resize(L, t, c, b); /* idem */
|
||||||
checkGC(L, ra + 1);
|
checkGC(L, ra + 1);
|
||||||
vmbreak;
|
vmbreak;
|
||||||
}
|
}
|
||||||
@@ -1763,18 +1764,17 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
|
|||||||
}
|
}
|
||||||
vmcase(OP_SETLIST) {
|
vmcase(OP_SETLIST) {
|
||||||
int n = GETARG_B(i);
|
int n = GETARG_B(i);
|
||||||
int c = GETARG_C(i);
|
unsigned int last = GETARG_C(i);
|
||||||
unsigned int last;
|
Table *h = hvalue(s2v(ra));
|
||||||
Table *h;
|
|
||||||
if (n == 0)
|
if (n == 0)
|
||||||
n = cast_int(L->top - ra) - 1;
|
n = cast_int(L->top - ra) - 1; /* get up to the top */
|
||||||
else
|
else
|
||||||
L->top = ci->top; /* correct top in case of GC */
|
L->top = ci->top; /* correct top in case of GC */
|
||||||
if (c == 0) {
|
last += n;
|
||||||
c = GETARG_Ax(*pc); pc++;
|
if (TESTARG_k(i)) {
|
||||||
|
last += GETARG_Ax(*pc) * (MAXARG_C + 1);
|
||||||
|
pc++;
|
||||||
}
|
}
|
||||||
h = hvalue(s2v(ra));
|
|
||||||
last = ((c-1)*LFIELDS_PER_FLUSH) + n;
|
|
||||||
if (last > luaH_realasize(h)) /* needs more space? */
|
if (last > luaH_realasize(h)) /* needs more space? */
|
||||||
luaH_resizearray(L, h, last); /* preallocate it at once */
|
luaH_resizearray(L, h, last); /* preallocate it at once */
|
||||||
for (; n > 0; n--) {
|
for (; n > 0; n--) {
|
||||||
|
|||||||
@@ -80,15 +80,23 @@ local sizes = {0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17,
|
|||||||
|
|
||||||
for _, sa in ipairs(sizes) do -- 'sa' is size of the array part
|
for _, sa in ipairs(sizes) do -- 'sa' is size of the array part
|
||||||
local arr = {"return {"}
|
local arr = {"return {"}
|
||||||
-- array part
|
for i = 1, sa do arr[1 + i] = "1," end -- build array part
|
||||||
for i = 1, sa do arr[1 + i] = "1," end
|
|
||||||
for _, sh in ipairs(sizes) do -- 'sh' is size of the hash part
|
for _, sh in ipairs(sizes) do -- 'sh' is size of the hash part
|
||||||
for j = 1, sh do -- hash part
|
for j = 1, sh do -- build hash part
|
||||||
arr[1 + sa + j] = string.format('k%x=%d,', j, j)
|
arr[1 + sa + j] = string.format('k%x=%d,', j, j)
|
||||||
end
|
end
|
||||||
arr[1 + sa + sh + 1] = "}"
|
arr[1 + sa + sh + 1] = "}"
|
||||||
local prog = table.concat(arr)
|
local prog = table.concat(arr)
|
||||||
local t = assert(load(prog))()
|
local f = assert(load(prog))
|
||||||
|
f() -- call once to ensure stack space
|
||||||
|
-- make sure table is not resized after being created
|
||||||
|
if sa == 0 or sh == 0 then
|
||||||
|
T.alloccount(2); -- header + array or hash part
|
||||||
|
else
|
||||||
|
T.alloccount(3); -- header + array part + hash part
|
||||||
|
end
|
||||||
|
local t = f()
|
||||||
|
T.alloccount();
|
||||||
assert(#t == sa)
|
assert(#t == sa)
|
||||||
check(t, sa, mp2(sh))
|
check(t, sa, mp2(sh))
|
||||||
end
|
end
|
||||||
@@ -99,12 +107,12 @@ end
|
|||||||
local a = {}
|
local a = {}
|
||||||
for i=1,sizes[#sizes] do a[i] = i end -- build auxiliary table
|
for i=1,sizes[#sizes] do a[i] = i end -- build auxiliary table
|
||||||
for k in ipairs(sizes) do
|
for k in ipairs(sizes) do
|
||||||
local a = {table.unpack(a,1,k)}
|
local t = {table.unpack(a,1,k)}
|
||||||
assert(#a == k)
|
assert(#t == k)
|
||||||
check(a, k, 0)
|
check(t, k, 0)
|
||||||
a = {1,2,3,table.unpack(a,1,k)}
|
t = {1,2,3,table.unpack(a,1,k)}
|
||||||
check(a, k+3, 0)
|
check(t, k+3, 0)
|
||||||
assert(#a == k + 3)
|
assert(#t == k + 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user