You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
975 lines
31 KiB
975 lines
31 KiB
4 years ago
|
Index: testes/all.lua
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- testes/all.lua (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ testes/all.lua (date 1594850137246)
|
||
|
@@ -184,6 +184,7 @@
|
||
|
dofile('bitwise.lua')
|
||
|
assert(dofile('verybig.lua', true) == 10); collectgarbage()
|
||
|
dofile('files.lua')
|
||
|
+dofile('defer.lua')
|
||
|
|
||
|
if #msgs > 0 then
|
||
|
print("\ntests not performed:")
|
||
|
Index: ldo.h
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- ldo.h (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ ldo.h (date 1594850124117)
|
||
|
@@ -53,6 +53,7 @@
|
||
|
|
||
|
LUAI_FUNC l_noret luaD_throw (lua_State *L, int errcode);
|
||
|
LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
|
||
|
+LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);
|
||
|
|
||
|
#endif
|
||
|
|
||
|
Index: ldo.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- ldo.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ ldo.c (date 1594850137277)
|
||
|
@@ -88,7 +88,7 @@
|
||
|
};
|
||
|
|
||
|
|
||
|
-static void seterrorobj (lua_State *L, int errcode, StkId oldtop) {
|
||
|
+void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {
|
||
|
switch (errcode) {
|
||
|
case LUA_ERRMEM: { /* memory error? */
|
||
|
setsvalue2s(L, oldtop, G(L)->memerrmsg); /* reuse preregistered msg. */
|
||
|
@@ -98,6 +98,10 @@
|
||
|
setsvalue2s(L, oldtop, luaS_newliteral(L, "error in error handling"));
|
||
|
break;
|
||
|
}
|
||
|
+ case CLOSEPROTECT: {
|
||
|
+ setnilvalue(oldtop); /* no error message */
|
||
|
+ break;
|
||
|
+ }
|
||
|
default: {
|
||
|
setobjs2s(L, oldtop, L->top - 1); /* error message on current top */
|
||
|
break;
|
||
|
@@ -114,6 +118,7 @@
|
||
|
}
|
||
|
else { /* thread has no error handler */
|
||
|
global_State *g = G(L);
|
||
|
+ errcode = luaF_close(L, L->stack, errcode); /* close all upvalues */
|
||
|
L->status = cast_byte(errcode); /* mark it as dead */
|
||
|
if (g->mainthread->errorJmp) { /* main thread has a handler? */
|
||
|
setobjs2s(L, g->mainthread->top++, L->top - 1); /* copy error obj. */
|
||
|
@@ -121,7 +126,7 @@
|
||
|
}
|
||
|
else { /* no handler at all; abort */
|
||
|
if (g->panic) { /* panic function? */
|
||
|
- seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */
|
||
|
+ luaD_seterrorobj(L, errcode, L->top); /* assume EXTRA_STACK */
|
||
|
if (L->ci->top < L->top)
|
||
|
L->ci->top = L->top; /* pushing msg. can break this invariant */
|
||
|
lua_unlock(L);
|
||
|
@@ -584,8 +589,8 @@
|
||
|
if (ci == NULL) return 0; /* no recovery point */
|
||
|
/* "finish" luaD_pcall */
|
||
|
oldtop = restorestack(L, ci->extra);
|
||
|
- luaF_close(L, oldtop);
|
||
|
- seterrorobj(L, status, oldtop);
|
||
|
+ luaF_close(L, oldtop, status);
|
||
|
+ luaD_seterrorobj(L, status, oldtop);
|
||
|
L->ci = ci;
|
||
|
L->allowhook = getoah(ci->callstatus); /* restore original 'allowhook' */
|
||
|
L->nny = 0; /* should be zero to be yieldable */
|
||
|
@@ -662,19 +667,17 @@
|
||
|
L->nny = 0; /* allow yields */
|
||
|
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);
|
||
|
status = luaD_rawrunprotected(L, resume, &nargs);
|
||
|
- if (status == -1) /* error calling 'lua_resume'? */
|
||
|
- status = LUA_ERRRUN;
|
||
|
- else { /* continue running after recoverable errors */
|
||
|
- while (errorstatus(status) && recover(L, status)) {
|
||
|
- /* unroll continuation */
|
||
|
- status = luaD_rawrunprotected(L, unroll, &status);
|
||
|
- }
|
||
|
- if (errorstatus(status)) { /* unrecoverable error? */
|
||
|
- L->status = cast_byte(status); /* mark thread as 'dead' */
|
||
|
- seterrorobj(L, status, L->top); /* push error message */
|
||
|
- L->ci->top = L->top;
|
||
|
- }
|
||
|
- else lua_assert(status == L->status); /* normal end or yield */
|
||
|
+ /* continue running after recoverable errors */
|
||
|
+ while (errorstatus(status) && recover(L, status)) {
|
||
|
+ /* unroll continuation */
|
||
|
+ status = luaD_rawrunprotected(L, unroll, &status);
|
||
|
+ }
|
||
|
+ if (!errorstatus(status))
|
||
|
+ lua_assert(status == L->status); /* normal end or yield */
|
||
|
+ else { /* unrecoverable error */
|
||
|
+ L->status = cast_byte(status); /* mark thread as 'dead' */
|
||
|
+ luaD_seterrorobj(L, status, L->top); /* push error message */
|
||
|
+ L->ci->top = L->top;
|
||
|
}
|
||
|
L->nny = oldnny; /* restore 'nny' */
|
||
|
L->nCcalls--;
|
||
|
@@ -729,11 +732,12 @@
|
||
|
status = luaD_rawrunprotected(L, func, u);
|
||
|
if (status != LUA_OK) { /* an error occurred? */
|
||
|
StkId oldtop = restorestack(L, old_top);
|
||
|
- luaF_close(L, oldtop); /* close possible pending closures */
|
||
|
- seterrorobj(L, status, oldtop);
|
||
|
L->ci = old_ci;
|
||
|
L->allowhook = old_allowhooks;
|
||
|
L->nny = old_nny;
|
||
|
+ status = luaF_close(L, oldtop, status); /* close possible pending closures */
|
||
|
+ oldtop = restorestack(L, old_top);
|
||
|
+ luaD_seterrorobj(L, status, oldtop);
|
||
|
luaD_shrinkstack(L);
|
||
|
}
|
||
|
L->errfunc = old_errfunc;
|
||
|
Index: lfunc.h
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lfunc.h (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lfunc.h (date 1594850137293)
|
||
|
@@ -34,7 +34,8 @@
|
||
|
*/
|
||
|
struct UpVal {
|
||
|
TValue *v; /* points to stack or to its own value */
|
||
|
- lu_mem refcount; /* reference counter */
|
||
|
+ unsigned int refcount; /* reference counter */
|
||
|
+ unsigned int flags; /* Used to mark deferred values */
|
||
|
union {
|
||
|
struct { /* (when open) */
|
||
|
UpVal *next; /* linked list */
|
||
|
@@ -46,13 +47,22 @@
|
||
|
|
||
|
#define upisopen(up) ((up)->v != &(up)->u.value)
|
||
|
|
||
|
+/*
|
||
|
+** Special "status" for 'luaF_close'
|
||
|
+*/
|
||
|
+
|
||
|
+/* close upvalues without running their closing methods */
|
||
|
+#define NOCLOSINGMETH (-1)
|
||
|
+
|
||
|
+/* close upvalues running all closing methods in protected mode */
|
||
|
+#define CLOSEPROTECT (-2)
|
||
|
|
||
|
LUAI_FUNC Proto *luaF_newproto (lua_State *L);
|
||
|
LUAI_FUNC CClosure *luaF_newCclosure (lua_State *L, int nelems);
|
||
|
LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nelems);
|
||
|
LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
|
||
|
LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
|
||
|
-LUAI_FUNC void luaF_close (lua_State *L, StkId level);
|
||
|
+LUAI_FUNC int luaF_close (lua_State *L, StkId level, int status);
|
||
|
LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
|
||
|
LUAI_FUNC const char *luaF_getlocalname (const Proto *func, int local_number,
|
||
|
int pc);
|
||
|
Index: lstate.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lstate.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lstate.c (date 1594850137347)
|
||
|
@@ -241,7 +241,7 @@
|
||
|
|
||
|
static void close_state (lua_State *L) {
|
||
|
global_State *g = G(L);
|
||
|
- luaF_close(L, L->stack); /* close all upvalues for this thread */
|
||
|
+ luaF_close(L, L->stack, CLOSEPROTECT); /* close all upvalues for this thread */
|
||
|
luaC_freeallobjects(L); /* collect all objects */
|
||
|
if (g->version) /* closing a fully built state? */
|
||
|
luai_userstateclose(L);
|
||
|
@@ -284,13 +284,33 @@
|
||
|
|
||
|
void luaE_freethread (lua_State *L, lua_State *L1) {
|
||
|
LX *l = fromstate(L1);
|
||
|
- luaF_close(L1, L1->stack); /* close all upvalues for this thread */
|
||
|
+ luaF_close(L1, L1->stack, NOCLOSINGMETH); /* close all upvalues for this thread */
|
||
|
lua_assert(L1->openupval == NULL);
|
||
|
luai_userstatefree(L, L1);
|
||
|
freestack(L1);
|
||
|
luaM_free(L, l);
|
||
|
}
|
||
|
|
||
|
+int lua_resetthread (lua_State *L) {
|
||
|
+ CallInfo *ci;
|
||
|
+ int status;
|
||
|
+ lua_lock(L);
|
||
|
+ L->ci = ci = &L->base_ci; /* unwind CallInfo list */
|
||
|
+ setnilvalue(L->stack); /* 'function' entry for basic 'ci' */
|
||
|
+ ci->func = L->stack;
|
||
|
+ ci->callstatus = 0;
|
||
|
+ status = luaF_close(L, L->stack, CLOSEPROTECT);
|
||
|
+ if (status != CLOSEPROTECT) /* real errors? */
|
||
|
+ luaD_seterrorobj(L, status, L->stack + 1);
|
||
|
+ else {
|
||
|
+ status = LUA_OK;
|
||
|
+ L->top = L->stack + 1;
|
||
|
+ }
|
||
|
+ ci->top = L->top + LUA_MINSTACK;
|
||
|
+ L->status = status;
|
||
|
+ lua_unlock(L);
|
||
|
+ return status;
|
||
|
+}
|
||
|
|
||
|
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
|
||
|
int i;
|
||
|
Index: lfunc.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lfunc.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lfunc.c (date 1594850137293)
|
||
|
@@ -14,6 +14,7 @@
|
||
|
|
||
|
#include "lua.h"
|
||
|
|
||
|
+#include "ldo.h"
|
||
|
#include "lfunc.h"
|
||
|
#include "lgc.h"
|
||
|
#include "lmem.h"
|
||
|
@@ -61,13 +62,15 @@
|
||
|
lua_assert(isintwups(L) || L->openupval == NULL);
|
||
|
while (*pp != NULL && (p = *pp)->v >= level) {
|
||
|
lua_assert(upisopen(p));
|
||
|
- if (p->v == level) /* found a corresponding upvalue? */
|
||
|
- return p; /* return it */
|
||
|
+ if (p->v == level && !p->flags) /* found a corresponding upvalue that is not a deferred value? */ {
|
||
|
+ return p; /* return it */
|
||
|
+ }
|
||
|
pp = &p->u.open.next;
|
||
|
}
|
||
|
/* not found: create a new upvalue */
|
||
|
uv = luaM_new(L, UpVal);
|
||
|
uv->refcount = 0;
|
||
|
+ uv->flags = 0;
|
||
|
uv->u.open.next = *pp; /* link it to list of open upvalues */
|
||
|
uv->u.open.touched = 1;
|
||
|
*pp = uv;
|
||
|
@@ -79,20 +82,84 @@
|
||
|
return uv;
|
||
|
}
|
||
|
|
||
|
+static void calldeferred(lua_State *L, void *ud) {
|
||
|
+ UNUSED(ud);
|
||
|
+ luaD_callnoyield(L, L->top - 2, 0);
|
||
|
+}
|
||
|
+
|
||
|
+/*
|
||
|
+** Prepare deferred function plus its arguments for object 'obj' with
|
||
|
+** error message 'err'. (This function assumes EXTRA_STACK.)
|
||
|
+*/
|
||
|
+static int preparetocall(lua_State *L, TValue *func, TValue *err) {
|
||
|
+ StkId top = L->top;
|
||
|
+ setobj2s(L, top, func); /* will call deferred function */
|
||
|
+ if (err) {
|
||
|
+ setobj2s(L, top + 1, err); /* and error msg. as 1st argument */
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ setnilvalue(top + 1);
|
||
|
+ }
|
||
|
+ L->top = top + 2; /* add function and arguments */
|
||
|
+ return 1;
|
||
|
+}
|
||
|
|
||
|
-void luaF_close (lua_State *L, StkId level) {
|
||
|
+/*
|
||
|
+** Prepare and call a deferred function. If status is OK, code is still
|
||
|
+** inside the original protected call, and so any error will be handled
|
||
|
+** there. Otherwise, a previous error already activated the original
|
||
|
+** protected call, and so the call to the deferred method must be
|
||
|
+** protected here. (A status == -1 behaves like a previous
|
||
|
+** error, to also run the closing method in protected mode).
|
||
|
+** If status is OK, the call to the deferred method will be pushed
|
||
|
+** at the top of the stack. Otherwise, values are pushed after
|
||
|
+** the 'level' of the upvalue containing deferred function, as everything after
|
||
|
+** that won't be used again.
|
||
|
+*/
|
||
|
+static int calldeferredfunction(lua_State *L, StkId level, int status) {
|
||
|
+ TValue *uv = level; /* value being closed */
|
||
|
+ if (status == LUA_OK) {
|
||
|
+ preparetocall(L, uv, NULL); /* something to call? */
|
||
|
+ calldeferred(L, NULL); /* call closing method */
|
||
|
+ }
|
||
|
+ else { /* must close the object in protected mode */
|
||
|
+ ptrdiff_t oldtop;
|
||
|
+ level++; /* space for error message */
|
||
|
+ oldtop = savestack(L, level + 1); /* top will be after that */
|
||
|
+ luaD_seterrorobj(L, status, level); /* set error message */
|
||
|
+ preparetocall(L, uv, level);
|
||
|
+ int newstatus = luaD_pcall(L, calldeferred, NULL, oldtop, 0);
|
||
|
+ if (newstatus != LUA_OK && status == CLOSEPROTECT) /* first error? */
|
||
|
+ status = newstatus; /* this will be the new error */
|
||
|
+ else {
|
||
|
+ /* leave original error (or nil) on top */
|
||
|
+ L->top = restorestack(L, oldtop);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+int luaF_close (lua_State *L, StkId level, int status) {
|
||
|
UpVal *uv;
|
||
|
while (L->openupval != NULL && (uv = L->openupval)->v >= level) {
|
||
|
lua_assert(upisopen(uv));
|
||
|
L->openupval = uv->u.open.next; /* remove from 'open' list */
|
||
|
- if (uv->refcount == 0) /* no references? */
|
||
|
- luaM_free(L, uv); /* free upvalue */
|
||
|
+ if (uv->refcount == 0) { /* no references? */
|
||
|
+ UpVal uv1 = *uv; /* copy the upvalue as we will free it below */
|
||
|
+ luaM_free(L, uv); /* free upvalue before invoking any deferred functions */
|
||
|
+ if (status != NOCLOSINGMETH && uv1.flags && ttisfunction(uv1.v)) {
|
||
|
+ ptrdiff_t levelrel = savestack(L, level);
|
||
|
+ status = calldeferredfunction(L, uv1.v, status);
|
||
|
+ level = restorestack(L, levelrel);
|
||
|
+ }
|
||
|
+ }
|
||
|
else {
|
||
|
setobj(L, &uv->u.value, uv->v); /* move value to upvalue slot */
|
||
|
uv->v = &uv->u.value; /* now current value lives here */
|
||
|
luaC_upvalbarrier(L, uv);
|
||
|
}
|
||
|
}
|
||
|
+ return status;
|
||
|
}
|
||
|
|
||
|
|
||
|
Index: llex.h
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- llex.h (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ llex.h (date 1594850124176)
|
||
|
@@ -27,7 +27,7 @@
|
||
|
/* terminal symbols denoted by reserved words */
|
||
|
TK_AND = FIRST_RESERVED, TK_BREAK,
|
||
|
TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,
|
||
|
- TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
|
||
|
+ TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_DEFER, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
|
||
|
TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,
|
||
|
/* other terminal symbols */
|
||
|
TK_IDIV, TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE,
|
||
|
Index: lparser.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lparser.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lparser.c (date 1594850137347)
|
||
|
@@ -52,6 +52,7 @@
|
||
|
lu_byte nactvar; /* # active locals outside the block */
|
||
|
lu_byte upval; /* true if some variable in the block is an upvalue */
|
||
|
lu_byte isloop; /* true if 'block' is a loop */
|
||
|
+ lu_byte insidetbc; /* true if inside the scope of a defer stmt (i.e. defer closure var) */
|
||
|
} BlockCnt;
|
||
|
|
||
|
|
||
|
@@ -442,6 +443,7 @@
|
||
|
bl->firstlabel = fs->ls->dyd->label.n;
|
||
|
bl->firstgoto = fs->ls->dyd->gt.n;
|
||
|
bl->upval = 0;
|
||
|
+ bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc);
|
||
|
bl->previous = fs->bl;
|
||
|
fs->bl = bl;
|
||
|
lua_assert(fs->freereg == fs->nactvar);
|
||
|
@@ -519,10 +521,17 @@
|
||
|
** so that, if it invokes the GC, the GC knows which registers
|
||
|
** are in use at that time.
|
||
|
*/
|
||
|
-static void codeclosure (LexState *ls, expdesc *v) {
|
||
|
+static void codeclosure (LexState *ls, expdesc *v, int deferred) {
|
||
|
FuncState *fs = ls->fs->prev;
|
||
|
+ int pc = -1;
|
||
|
+ if (deferred) {
|
||
|
+ pc = luaK_codeABC(fs, OP_DEFER, 0, 0, 0);
|
||
|
+ }
|
||
|
init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np - 1));
|
||
|
luaK_exp2nextreg(fs, v); /* fix it at the last register */
|
||
|
+ if (deferred) {
|
||
|
+ SETARG_A(fs->f->code[pc], v->u.info);
|
||
|
+ }
|
||
|
}
|
||
|
|
||
|
|
||
|
@@ -780,24 +789,26 @@
|
||
|
}
|
||
|
|
||
|
|
||
|
-static void body (LexState *ls, expdesc *e, int ismethod, int line) {
|
||
|
+static void body (LexState *ls, expdesc *e, int ismethod, int line, int deferred) {
|
||
|
/* body -> '(' parlist ')' block END */
|
||
|
FuncState new_fs;
|
||
|
BlockCnt bl;
|
||
|
new_fs.f = addprototype(ls);
|
||
|
new_fs.f->linedefined = line;
|
||
|
open_func(ls, &new_fs, &bl);
|
||
|
- checknext(ls, '(');
|
||
|
- if (ismethod) {
|
||
|
- new_localvarliteral(ls, "self"); /* create 'self' parameter */
|
||
|
- adjustlocalvars(ls, 1);
|
||
|
- }
|
||
|
- parlist(ls);
|
||
|
- checknext(ls, ')');
|
||
|
+ if (!deferred) {
|
||
|
+ checknext(ls, '(');
|
||
|
+ if (ismethod) {
|
||
|
+ new_localvarliteral(ls, "self"); /* create 'self' parameter */
|
||
|
+ adjustlocalvars(ls, 1);
|
||
|
+ }
|
||
|
+ parlist(ls);
|
||
|
+ checknext(ls, ')');
|
||
|
+ }
|
||
|
statlist(ls);
|
||
|
new_fs.f->lastlinedefined = ls->linenumber;
|
||
|
check_match(ls, TK_END, TK_FUNCTION, line);
|
||
|
- codeclosure(ls, e);
|
||
|
+ codeclosure(ls, e, deferred);
|
||
|
close_func(ls);
|
||
|
}
|
||
|
|
||
|
@@ -972,7 +983,7 @@
|
||
|
}
|
||
|
case TK_FUNCTION: {
|
||
|
luaX_next(ls);
|
||
|
- body(ls, v, 0, ls->linenumber);
|
||
|
+ body(ls, v, 0, ls->linenumber, 0);
|
||
|
return;
|
||
|
}
|
||
|
default: {
|
||
|
@@ -1429,12 +1440,19 @@
|
||
|
}
|
||
|
|
||
|
|
||
|
-static void localfunc (LexState *ls) {
|
||
|
+static void localfunc (LexState *ls, int defer) {
|
||
|
expdesc b;
|
||
|
FuncState *fs = ls->fs;
|
||
|
- new_localvar(ls, str_checkname(ls)); /* new local variable */
|
||
|
+ if (defer) {
|
||
|
+ static const char funcname[] = "(deferred function)";
|
||
|
+ new_localvar(ls, luaX_newstring(ls, funcname, sizeof funcname-1)); /* new local variable */
|
||
|
+ markupval(fs, fs->nactvar);
|
||
|
+ fs->bl->insidetbc = 1; /* in the scope of a defer closure variable */
|
||
|
+ } else {
|
||
|
+ new_localvar(ls, str_checkname(ls)); /* new local variable */
|
||
|
+ }
|
||
|
adjustlocalvars(ls, 1); /* enter its scope */
|
||
|
- body(ls, &b, 0, ls->linenumber); /* function created in next register */
|
||
|
+ body(ls, &b, 0, ls->linenumber, defer); /* function created in next register */
|
||
|
/* debug information will only see the variable after this point! */
|
||
|
getlocvar(fs, b.u.info)->startpc = fs->pc;
|
||
|
}
|
||
|
@@ -1480,7 +1498,7 @@
|
||
|
expdesc v, b;
|
||
|
luaX_next(ls); /* skip FUNCTION */
|
||
|
ismethod = funcname(ls, &v);
|
||
|
- body(ls, &b, ismethod, line);
|
||
|
+ body(ls, &b, ismethod, line, 0);
|
||
|
luaK_storevar(ls->fs, &v, &b);
|
||
|
luaK_fixline(ls->fs, line); /* definition "happens" in the first line */
|
||
|
}
|
||
|
@@ -1513,7 +1531,7 @@
|
||
|
nret = explist(ls, &e); /* optional return values */
|
||
|
if (hasmultret(e.k)) {
|
||
|
luaK_setmultret(fs, &e);
|
||
|
- if (e.k == VCALL && nret == 1) { /* tail call? */
|
||
|
+ if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */
|
||
|
SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);
|
||
|
lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar);
|
||
|
}
|
||
|
@@ -1572,10 +1590,15 @@
|
||
|
case TK_LOCAL: { /* stat -> localstat */
|
||
|
luaX_next(ls); /* skip LOCAL */
|
||
|
if (testnext(ls, TK_FUNCTION)) /* local function? */
|
||
|
- localfunc(ls);
|
||
|
+ localfunc(ls, 0);
|
||
|
else
|
||
|
localstat(ls);
|
||
|
break;
|
||
|
+ }
|
||
|
+ case TK_DEFER: { /* stat -> deferstat */
|
||
|
+ luaX_next(ls); /* skip DEFER */
|
||
|
+ localfunc(ls, 1);
|
||
|
+ break;
|
||
|
}
|
||
|
case TK_DBCOLON: { /* stat -> label */
|
||
|
luaX_next(ls); /* skip double colon */
|
||
|
Index: llex.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- llex.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ llex.c (date 1594850124157)
|
||
|
@@ -40,7 +40,7 @@
|
||
|
static const char *const luaX_tokens [] = {
|
||
|
"and", "break", "do", "else", "elseif",
|
||
|
"end", "false", "for", "function", "goto", "if",
|
||
|
- "in", "local", "nil", "not", "or", "repeat",
|
||
|
+ "in", "local", "defer", "nil", "not", "or", "repeat",
|
||
|
"return", "then", "true", "until", "while",
|
||
|
"//", "..", "...", "==", ">=", "<=", "~=",
|
||
|
"<<", ">>", "::", "<eof>",
|
||
|
Index: testes/defer.lua
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- testes/defer.lua (date 1594849634130)
|
||
|
+++ testes/defer.lua (date 1594849634130)
|
||
|
@@ -0,0 +1,313 @@
|
||
|
+-- ================================================================
|
||
|
+-- Following section is an extract from the code.lua test
|
||
|
+-- These functions test bytecode generation, and also provide
|
||
|
+-- helper routines that we use later on in other test cases
|
||
|
+
|
||
|
+-- testing opcodes
|
||
|
+function check (f, ...)
|
||
|
+ if not T then
|
||
|
+ return true
|
||
|
+ end
|
||
|
+ local arg = {...}
|
||
|
+ local c = T.listcode(f)
|
||
|
+ for i=1, #arg do
|
||
|
+ --print(arg[i], c[i])
|
||
|
+ opcodes_coverage[arg[i]] = opcodes_coverage[arg[i]]+1
|
||
|
+ assert(string.find(c[i], '- '..arg[i]..' *[AB][xs]?=%d'))
|
||
|
+ end
|
||
|
+ assert(c[#arg+2] == nil)
|
||
|
+end
|
||
|
+
|
||
|
+-- Test defer statement
|
||
|
+do
|
||
|
+ local y = 0
|
||
|
+ local function x()
|
||
|
+ defer y = y + 1 end
|
||
|
+ defer y = y + 1 end
|
||
|
+ end
|
||
|
+ check(x, 'DEFER', 'CLOSURE', 'DEFER', 'CLOSURE', 'RETURN')
|
||
|
+ x()
|
||
|
+ assert(y == 2)
|
||
|
+ print 'Test 1 OK'
|
||
|
+end
|
||
|
+
|
||
|
+-- Test defer statement
|
||
|
+do
|
||
|
+ local y = 0
|
||
|
+ local function x()
|
||
|
+ defer y = y + 1 end
|
||
|
+ error('raise error')
|
||
|
+ defer y = y + 2 end -- will not be called
|
||
|
+ end
|
||
|
+ pcall(x)
|
||
|
+ assert(y == 1)
|
||
|
+ print 'Test 2 OK'
|
||
|
+end
|
||
|
+
|
||
|
+-- Test defer statement
|
||
|
+do
|
||
|
+ local y = 0
|
||
|
+ local function x()
|
||
|
+ defer y = y + 1 end
|
||
|
+ defer y = y + 2; error('err') end
|
||
|
+ defer y = y + 3 end
|
||
|
+ end
|
||
|
+ pcall(x)
|
||
|
+ assert(y == 6)
|
||
|
+ print 'Test 3 OK'
|
||
|
+end
|
||
|
+
|
||
|
+-- Test defer statement in tailcalls
|
||
|
+do
|
||
|
+ local y = 0
|
||
|
+ local function x (n)
|
||
|
+ defer y = y + 1 end
|
||
|
+ if n > 0 then return x(n - 1) end
|
||
|
+ end
|
||
|
+ pcall(x, 3)
|
||
|
+ assert(y == 4)
|
||
|
+ print 'Test 4 OK'
|
||
|
+end
|
||
|
+
|
||
|
+-- Simulate a test of resource closure with defer
|
||
|
+do
|
||
|
+ local y = 0
|
||
|
+ local z = { count = 0 }
|
||
|
+ z.__index = z;
|
||
|
+ function z:new()
|
||
|
+ local object = {}
|
||
|
+ setmetatable(object, z)
|
||
|
+ return object
|
||
|
+ end
|
||
|
+ function z:open(arg)
|
||
|
+ if (arg) then
|
||
|
+ z.count = z.count + 1
|
||
|
+ return
|
||
|
+ end
|
||
|
+ y = 1
|
||
|
+ error('error opening')
|
||
|
+ end
|
||
|
+ function z.close()
|
||
|
+ z.count = z.count - 1
|
||
|
+ end
|
||
|
+ local function x(arg)
|
||
|
+ local f = z:new()
|
||
|
+ f:open(arg)
|
||
|
+ assert(z.count == 1)
|
||
|
+ defer f:close() end
|
||
|
+ end
|
||
|
+ x('filename')
|
||
|
+ assert(y == 0)
|
||
|
+ assert(z.count == 0)
|
||
|
+ pcall(x, false)
|
||
|
+ assert(z.count == 0)
|
||
|
+ assert(y == 1)
|
||
|
+ print 'Test 5 OK'
|
||
|
+end
|
||
|
+
|
||
|
+--- Test stack reallocation in defer statement
|
||
|
+do
|
||
|
+ local function x(a) if a <= 0 then return else x(a-1) end end
|
||
|
+ local y = 1000
|
||
|
+ local function z(...)
|
||
|
+ -- recursive call to make stack
|
||
|
+ defer x(y) end
|
||
|
+ return ...
|
||
|
+ end
|
||
|
+ do
|
||
|
+ local a,b,c = z(1,2,3)
|
||
|
+ assert(a == 1 and b == 2 and c == 3)
|
||
|
+ a,b,c = z(3,2,1)
|
||
|
+ assert(a == 3 and b == 2 and c == 1)
|
||
|
+ end
|
||
|
+ print 'Test 6 OK'
|
||
|
+end
|
||
|
+
|
||
|
+-- Adapted from Lua 5.4
|
||
|
+local function stack(n) n = ((n == 0) or stack(n - 1)) end
|
||
|
+
|
||
|
+local function func2close (f, x, y)
|
||
|
+ local obj = setmetatable({}, {__close = f})
|
||
|
+ if x then
|
||
|
+ return x, obj, y
|
||
|
+ else
|
||
|
+ return obj
|
||
|
+ end
|
||
|
+end
|
||
|
+
|
||
|
+do
|
||
|
+ local function t()
|
||
|
+ local a = {}
|
||
|
+ do
|
||
|
+ local b = false -- not to be closed
|
||
|
+ -- x is <close>
|
||
|
+ local x = setmetatable({"x"}, {__close = function (self)
|
||
|
+ a[#a + 1] = self[1] end})
|
||
|
+ defer getmetatable(x).__close(x) end
|
||
|
+ -- y is <close>
|
||
|
+ local w, y, z = func2close(function (self, err)
|
||
|
+ assert(err == nil); a[#a + 1] = "y"
|
||
|
+ end, 10, 20)
|
||
|
+ defer getmetatable(y).__close(y) end
|
||
|
+ local c = nil -- not to be closed
|
||
|
+ a[#a + 1] = "in"
|
||
|
+ assert(w == 10 and z == 20)
|
||
|
+ end
|
||
|
+ a[#a + 1] = "out"
|
||
|
+ assert(a[1] == "in" and a[2] == "y" and a[3] == "x" and a[4] == "out")
|
||
|
+ end
|
||
|
+ t()
|
||
|
+ print 'Test 7 OK'
|
||
|
+end
|
||
|
+
|
||
|
+do
|
||
|
+ local function t()
|
||
|
+ local X = false
|
||
|
+
|
||
|
+ local x, closescope = func2close(function () stack(10); X = true end, 100)
|
||
|
+ assert(x == 100); x = 101; -- 'x' is not read-only
|
||
|
+
|
||
|
+ -- closing functions do not corrupt returning values
|
||
|
+ local function foo (x)
|
||
|
+ local _ = closescope
|
||
|
+ defer getmetatable(_).__close(_) end
|
||
|
+ return x, X, 23
|
||
|
+ end
|
||
|
+
|
||
|
+ local a, b, c = foo(1.5)
|
||
|
+ assert(a == 1.5 and b == false and c == 23 and X == true)
|
||
|
+
|
||
|
+ X = false
|
||
|
+ foo = function (x)
|
||
|
+ local _ = closescope
|
||
|
+ defer getmetatable(_).__close(_) end
|
||
|
+ local y = 15
|
||
|
+ return y
|
||
|
+ end
|
||
|
+
|
||
|
+ assert(foo() == 15 and X == true)
|
||
|
+
|
||
|
+ X = false
|
||
|
+ foo = function ()
|
||
|
+ local x = closescope
|
||
|
+ defer getmetatable(x).__close(x) end
|
||
|
+ return x
|
||
|
+ end
|
||
|
+
|
||
|
+ assert(foo() == closescope and X == true)
|
||
|
+ end
|
||
|
+ t()
|
||
|
+ print 'Test 8 OK'
|
||
|
+end
|
||
|
+
|
||
|
+do
|
||
|
+ local function t()
|
||
|
+ -- calls cannot be tail in the scope of to-be-closed variables
|
||
|
+ local X, Y
|
||
|
+ local function foo ()
|
||
|
+ local _ = func2close(function () Y = 10 end)
|
||
|
+ defer getmetatable(_).__close(_) end
|
||
|
+ assert(X == true and Y == nil) -- 'X' not closed yet
|
||
|
+ return 1,2,3
|
||
|
+ end
|
||
|
+
|
||
|
+ local function bar ()
|
||
|
+ local _ = func2close(function () X = false end)
|
||
|
+ defer getmetatable(_).__close(_) end
|
||
|
+ X = true
|
||
|
+ do
|
||
|
+ return foo() -- not a tail call!
|
||
|
+ end
|
||
|
+ end
|
||
|
+
|
||
|
+ local a, b, c, d = bar()
|
||
|
+ assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil)
|
||
|
+ return foo, bar
|
||
|
+ end
|
||
|
+ local f,b = t()
|
||
|
+ print 'Test 9 OK'
|
||
|
+end
|
||
|
+
|
||
|
+do
|
||
|
+ local function t()
|
||
|
+ -- an error in a wrapped coroutine closes variables
|
||
|
+ local x = false
|
||
|
+ local y = false
|
||
|
+ local co = coroutine.wrap(function ()
|
||
|
+ local xv = func2close(function () x = true end)
|
||
|
+ defer getmetatable(xv).__close(xv) end
|
||
|
+ do
|
||
|
+ local yv = func2close(function () y = true end)
|
||
|
+ defer getmetatable(yv).__close(yv) end
|
||
|
+ coroutine.yield(100) -- yield doesn't close variable
|
||
|
+ end
|
||
|
+ coroutine.yield(200) -- yield doesn't close variable
|
||
|
+ error(23) -- error does
|
||
|
+ end)
|
||
|
+
|
||
|
+ local b = co()
|
||
|
+ assert(b == 100 and not x and not y)
|
||
|
+ b = co()
|
||
|
+ assert(b == 200 and not x and y)
|
||
|
+ local a, b = pcall(co)
|
||
|
+ assert(not a and b == 23 and x and y)
|
||
|
+ end
|
||
|
+ t()
|
||
|
+ print 'Test 10 OK'
|
||
|
+end
|
||
|
+
|
||
|
+-- a suspended coroutine should not close its variables when collected
|
||
|
+do
|
||
|
+ function t()
|
||
|
+ local co
|
||
|
+ co = coroutine.wrap(function()
|
||
|
+ -- should not run
|
||
|
+ local x = func2close(function () os.exit(false) end)
|
||
|
+ defer getmetatable(x).__close(x) end
|
||
|
+ co = nil
|
||
|
+ coroutine.yield()
|
||
|
+ end)
|
||
|
+ co() -- start coroutine
|
||
|
+ assert(co == nil) -- eventually it will be collected
|
||
|
+ collectgarbage()
|
||
|
+ end
|
||
|
+ t()
|
||
|
+ print 'Test 11 OK'
|
||
|
+end
|
||
|
+
|
||
|
+do
|
||
|
+ local function t()
|
||
|
+ -- error in a wrapped coroutine raising errors when closing a variable
|
||
|
+ local x = 0
|
||
|
+ local co = coroutine.wrap(function ()
|
||
|
+ local xx = func2close(function () x = x + 1; error("@YYY") end)
|
||
|
+ defer getmetatable(xx).__close(xx) end
|
||
|
+ local xv = func2close(function () x = x + 1; error("@XXX") end)
|
||
|
+ defer getmetatable(xv).__close(xv) end
|
||
|
+ coroutine.yield(100)
|
||
|
+ error(200)
|
||
|
+ end)
|
||
|
+ assert(co() == 100); assert(x == 0)
|
||
|
+ local st, msg = pcall(co); assert(x == 2)
|
||
|
+ assert(not st and msg == 200) -- should get first error raised
|
||
|
+
|
||
|
+ local x = 0
|
||
|
+ local y = 0
|
||
|
+ co = coroutine.wrap(function ()
|
||
|
+ local xx = func2close(function () y = y + 1; error("YYY") end)
|
||
|
+ defer getmetatable(xx).__close(xx) end
|
||
|
+ local xv = func2close(function () x = x + 1; error("XXX") end)
|
||
|
+ defer getmetatable(xv).__close(xv) end
|
||
|
+ coroutine.yield(100)
|
||
|
+ return 200
|
||
|
+ end)
|
||
|
+ assert(co() == 100); assert(x == 0)
|
||
|
+ local st, msg = pcall(co)
|
||
|
+ assert(not st and string.find(msg, "%w+%.%w+:%d+: XXX"))
|
||
|
+ assert(x == 1 and y == 1)
|
||
|
+ end
|
||
|
+ t()
|
||
|
+ print 'Test 12 OK'
|
||
|
+end
|
||
|
+
|
||
|
+print 'OK'
|
||
|
Index: lopcodes.h
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lopcodes.h (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lopcodes.h (date 1594850124198)
|
||
|
@@ -230,11 +230,13 @@
|
||
|
|
||
|
OP_VARARG,/* A B R(A), R(A+1), ..., R(A+B-2) = vararg */
|
||
|
|
||
|
-OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
|
||
|
+OP_EXTRAARG,/* Ax extra (larger) argument for previous opcode */
|
||
|
+OP_DEFER /* A mark variable A "deferred" */
|
||
|
+
|
||
|
} OpCode;
|
||
|
|
||
|
|
||
|
-#define NUM_OPCODES (cast(int, OP_EXTRAARG) + 1)
|
||
|
+#define NUM_OPCODES (cast(int, OP_DEFER) + 1)
|
||
|
|
||
|
|
||
|
|
||
|
Index: lvm.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lvm.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lvm.c (date 1594850137378)
|
||
|
@@ -737,7 +737,7 @@
|
||
|
/* execute a jump instruction */
|
||
|
#define dojump(ci,i,e) \
|
||
|
{ int a = GETARG_A(i); \
|
||
|
- if (a != 0) luaF_close(L, ci->u.l.base + a - 1); \
|
||
|
+ if (a != 0) Protect(luaF_close(L, ci->u.l.base + a - 1, LUA_OK)); \
|
||
|
ci->u.l.savedpc += GETARG_sBx(i) + e; }
|
||
|
|
||
|
/* for test instructions, execute the jump instruction that follows it */
|
||
|
@@ -1159,7 +1159,7 @@
|
||
|
StkId lim = nci->u.l.base + getproto(nfunc)->numparams;
|
||
|
int aux;
|
||
|
/* close all upvalues from previous call */
|
||
|
- if (cl->p->sizep > 0) luaF_close(L, oci->u.l.base);
|
||
|
+ if (cl->p->sizep > 0) Protect(luaF_close(L, oci->u.l.base, NOCLOSINGMETH));
|
||
|
/* move new frame into old one */
|
||
|
for (aux = 0; nfunc + aux < lim; aux++)
|
||
|
setobjs2s(L, ofunc + aux, nfunc + aux);
|
||
|
@@ -1175,7 +1175,10 @@
|
||
|
}
|
||
|
vmcase(OP_RETURN) {
|
||
|
int b = GETARG_B(i);
|
||
|
- if (cl->p->sizep > 0) luaF_close(L, base);
|
||
|
+ if (cl->p->sizep > 0) {
|
||
|
+ Protect(luaF_close(L, base, LUA_OK));
|
||
|
+ ra = RA(i);
|
||
|
+ }
|
||
|
b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra)));
|
||
|
if (ci->callstatus & CIST_FRESH) /* local 'ci' still from callee */
|
||
|
return; /* external invocation: return */
|
||
|
@@ -1313,6 +1316,12 @@
|
||
|
vmcase(OP_EXTRAARG) {
|
||
|
lua_assert(0);
|
||
|
vmbreak;
|
||
|
+ }
|
||
|
+ vmcase(OP_DEFER) {
|
||
|
+ UpVal *up = luaF_findupval(L, ra); /* create new upvalue */
|
||
|
+ up->flags = 1; /* mark it as deferred */
|
||
|
+ setnilvalue(ra); /* initialize it with nil */
|
||
|
+ vmbreak;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
Index: lcorolib.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lcorolib.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lcorolib.c (date 1594850137262)
|
||
|
@@ -75,8 +75,11 @@
|
||
|
lua_State *co = lua_tothread(L, lua_upvalueindex(1));
|
||
|
int r = auxresume(L, co, lua_gettop(L));
|
||
|
if (r < 0) {
|
||
|
+ int stat = lua_status(co);
|
||
|
+ if (stat != LUA_OK && stat != LUA_YIELD)
|
||
|
+ lua_resetthread(co); /* close variables in case of errors */
|
||
|
if (lua_type(L, -1) == LUA_TSTRING) { /* error object is a string? */
|
||
|
- luaL_where(L, 1); /* add extra info */
|
||
|
+ luaL_where(L, 1); /* add extra info, if available */
|
||
|
lua_insert(L, -2);
|
||
|
lua_concat(L, 2);
|
||
|
}
|
||
|
Index: lua.h
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lua.h (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lua.h (date 1594850137362)
|
||
|
@@ -144,6 +144,7 @@
|
||
|
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
|
||
|
LUA_API void (lua_close) (lua_State *L);
|
||
|
LUA_API lua_State *(lua_newthread) (lua_State *L);
|
||
|
+LUA_API int (lua_resetthread) (lua_State *L);
|
||
|
|
||
|
LUA_API lua_CFunction (lua_atpanic) (lua_State *L, lua_CFunction panicf);
|
||
|
|
||
|
Index: lopcodes.c
|
||
|
IDEA additional info:
|
||
|
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
|
||
|
<+>UTF-8
|
||
|
===================================================================
|
||
|
--- lopcodes.c (revision e7411fab800e2cfa810a1ba296356532eabdde40)
|
||
|
+++ lopcodes.c (date 1594850124178)
|
||
|
@@ -65,6 +65,7 @@
|
||
|
"CLOSURE",
|
||
|
"VARARG",
|
||
|
"EXTRAARG",
|
||
|
+ "DEFER",
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
@@ -119,6 +120,7 @@
|
||
|
,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */
|
||
|
,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */
|
||
|
,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */
|
||
|
- ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */
|
||
|
+ ,opmode(0, 0, OpArgU, OpArgU, iAx) /* OP_EXTRAARG */
|
||
|
+ ,opmode(0, 1, OpArgN, OpArgN, iABC) /* OP_DEFER */
|
||
|
};
|
||
|
|