| /* |
| ** $Id: lvm.c,v 1.189 2001/06/28 14:48:44 roberto Exp roberto $ |
| ** Lua virtual machine |
| ** See Copyright Notice in lua.h |
| */ |
| |
| |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #define LUA_PRIVATE |
| #include "lua.h" |
| |
| #include "lapi.h" |
| #include "ldebug.h" |
| #include "ldo.h" |
| #include "lfunc.h" |
| #include "lgc.h" |
| #include "lobject.h" |
| #include "lopcodes.h" |
| #include "lstate.h" |
| #include "lstring.h" |
| #include "ltable.h" |
| #include "ltm.h" |
| #include "lvm.h" |
| |
| |
| |
| static void luaV_checkGC (lua_State *L, StkId top) { |
| if (G(L)->nblocks >= G(L)->GCthreshold) { |
| StkId temp = L->top; |
| L->top = top; |
| luaC_collectgarbage(L); |
| L->top = temp; /* restore old top position */ |
| } |
| } |
| |
| |
| const TObject *luaV_tonumber (const TObject *obj, TObject *n) { |
| lua_Number num; |
| if (ttype(obj) == LUA_TNUMBER) return obj; |
| if (ttype(obj) == LUA_TSTRING && luaO_str2d(svalue(obj), &num)) { |
| setnvalue(n, num); |
| return n; |
| } |
| else |
| return NULL; |
| } |
| |
| |
| int luaV_tostring (lua_State *L, TObject *obj) { |
| if (ttype(obj) != LUA_TNUMBER) |
| return 1; |
| else { |
| l_char s[32]; /* 16 digits, sign, point and \0 (+ some extra...) */ |
| lua_number2str(s, nvalue(obj)); /* convert `s' to number */ |
| setsvalue(obj, luaS_new(L, s)); |
| return 0; |
| } |
| } |
| |
| |
| static void traceexec (lua_State *L, lua_Hook linehook) { |
| CallInfo *ci = L->ci; |
| int *lineinfo = ci_func(ci)->f.l->lineinfo; |
| int pc = (*ci->pc - ci_func(ci)->f.l->code) - 1; |
| int newline; |
| if (pc == 0) { /* may be first time? */ |
| ci->line = 1; |
| ci->refi = 0; |
| ci->lastpc = pc+1; /* make sure it will call linehook */ |
| } |
| newline = luaG_getline(lineinfo, pc, ci->line, &ci->refi); |
| /* calls linehook when enters a new line or jumps back (loop) */ |
| if (newline != ci->line || pc <= ci->lastpc) { |
| ci->line = newline; |
| luaD_lineHook(L, newline, linehook); |
| } |
| ci->lastpc = pc; |
| } |
| |
| |
| static Closure *luaV_closure (lua_State *L, int nelems) { |
| Closure *c = luaF_newclosure(L, nelems); |
| L->top -= nelems; |
| while (nelems--) |
| setobj(&c->upvalue[nelems], L->top+nelems); |
| setclvalue(L->top, c); |
| incr_top; |
| return c; |
| } |
| |
| |
| void luaV_Cclosure (lua_State *L, lua_CFunction c, int nelems) { |
| Closure *cl = luaV_closure(L, nelems); |
| cl->f.c = c; |
| cl->isC = 1; |
| } |
| |
| |
| void luaV_Lclosure (lua_State *L, Proto *l, int nelems) { |
| Closure *cl = luaV_closure(L, nelems); |
| cl->f.l = l; |
| cl->isC = 0; |
| } |
| |
| |
| /* maximum stack used by a call to a tag method (func + args) */ |
| #define MAXSTACK_TM 4 |
| |
| static StkId callTM (lua_State *L, Closure *f, const l_char *fmt, ...) { |
| va_list argp; |
| StkId base = L->top; |
| lua_assert(strlen(fmt)+1 <= MAXSTACK_TM); |
| luaD_checkstack(L, MAXSTACK_TM); |
| va_start(argp, fmt); |
| setclvalue(L->top, f); /* push function */ |
| L->top++; |
| while (*fmt) { |
| if (*fmt++ == l_c('o')) { |
| setobj(L->top, va_arg(argp, TObject *)); |
| } |
| else { |
| lua_assert(*(fmt-1) == l_c('s')); |
| setsvalue(L->top, va_arg(argp, TString *)); |
| } |
| L->top++; |
| } |
| luaD_call(L, base); |
| va_end(argp); |
| return base; |
| } |
| |
| |
| #define setTM(L, base) (L->top = (base)) |
| |
| static void setTMresult (lua_State *L, TObject *result, StkId base) { |
| if (L->top == base) { /* are there valid results? */ |
| setnilvalue(result); /* function had no results */ |
| } |
| else { |
| setobj(result, base); /* get first result */ |
| } |
| L->top = base; /* restore top */ |
| } |
| |
| |
| /* |
| ** Function to index a table. |
| ** Receives the table at `t' and the key at the `key'. |
| ** leaves the result at `res'. |
| */ |
| void luaV_gettable (lua_State *L, StkId t, TObject *key, StkId res) { |
| Closure *tm; |
| if (ttype(t) == LUA_TTABLE) { /* `t' is a table? */ |
| int tg = hvalue(t)->htag; |
| if (tg == LUA_TTABLE || /* with default tag? */ |
| (tm = luaT_gettm(G(L), tg, TM_GETTABLE)) == NULL) { /* or no TM? */ |
| const TObject *h = luaH_get(hvalue(t), key); /* do a primitive get */ |
| /* result is no nil or there is no `index' tag method? */ |
| if (ttype(h) != LUA_TNIL || /* no nil? */ |
| ((tm=luaT_gettm(G(L), tg, TM_INDEX)) == NULL)) { /* or no index TM? */ |
| setobj(res, h); /* default get */ |
| return; |
| } |
| } |
| /* else will call the tag method */ |
| } else { /* not a table; try a `gettable' tag method */ |
| tm = luaT_gettmbyObj(G(L), t, TM_GETTABLE); |
| if (tm == NULL) /* no tag method? */ |
| luaG_typeerror(L, t, l_s("index")); |
| } |
| setTMresult(L, res, callTM(L, tm, l_s("oo"), t, key)); |
| } |
| |
| |
| |
| /* |
| ** Receives table at `t', key at `key' and value at `val'. |
| */ |
| void luaV_settable (lua_State *L, StkId t, TObject *key, StkId val) { |
| Closure *tm; |
| if (ttype(t) == LUA_TTABLE) { /* `t' is a table? */ |
| int tg = hvalue(t)->htag; |
| if (hvalue(t)->htag == LUA_TTABLE || /* with default tag? */ |
| (tm = luaT_gettm(G(L), tg, TM_SETTABLE)) == NULL) { /* or no TM? */ |
| setobj(luaH_set(L, hvalue(t), key), val); /* do a primitive set */ |
| return; |
| } |
| /* else will call the tag method */ |
| } else { /* not a table; try a `settable' tag method */ |
| tm = luaT_gettmbyObj(G(L), t, TM_SETTABLE); |
| if (tm == NULL) /* no tag method? */ |
| luaG_typeerror(L, t, l_s("index")); |
| } |
| setTM(L, callTM(L, tm, l_s("ooo"), t, key, val)); |
| } |
| |
| |
| void luaV_getglobal (lua_State *L, TString *name, StkId res) { |
| const TObject *value = luaH_getstr(L->gt, name); |
| Closure *tm; |
| if (!HAS_TM_GETGLOBAL(L, ttype(value)) || /* is there a tag method? */ |
| (tm = luaT_gettmbyObj(G(L), value, TM_GETGLOBAL)) == NULL) { |
| setobj(res, value); /* default behavior */ |
| } |
| else |
| setTMresult(L, res, callTM(L, tm, l_s("so"), name, value)); |
| } |
| |
| |
| void luaV_setglobal (lua_State *L, TString *name, StkId val) { |
| TObject *oldvalue = luaH_setstr(L, L->gt, name); |
| Closure *tm; |
| if (!HAS_TM_SETGLOBAL(L, ttype(oldvalue)) || /* no tag methods? */ |
| (tm = luaT_gettmbyObj(G(L), oldvalue, TM_SETGLOBAL)) == NULL) { |
| setobj(oldvalue, val); /* raw set */ |
| } |
| else |
| setTM(L, callTM(L, tm, l_s("soo"), name, oldvalue, val)); |
| } |
| |
| |
| static int call_binTM (lua_State *L, const TObject *p1, const TObject *p2, |
| TObject *res, TMS event) { |
| Closure *tm = luaT_gettmbyObj(G(L), p1, event); /* try first operand */ |
| if (tm == NULL) { |
| tm = luaT_gettmbyObj(G(L), p2, event); /* try second operand */ |
| if (tm == NULL) { |
| tm = luaT_gettm(G(L), 0, event); /* try a `global' method */ |
| if (tm == NULL) |
| return 0; /* no tag method */ |
| } |
| } |
| setTMresult(L, res, callTM(L, tm, l_s("oo"), p1, p2)); |
| return 1; |
| } |
| |
| |
| static void call_arith (lua_State *L, StkId p1, TObject *p2, |
| StkId res, TMS event) { |
| if (!call_binTM(L, p1, p2, res, event)) |
| luaG_aritherror(L, p1, p2); |
| } |
| |
| |
| static int luaV_strlessthan (const TString *ls, const TString *rs) { |
| const l_char *l = getstr(ls); |
| size_t ll = ls->tsv.len; |
| const l_char *r = getstr(rs); |
| size_t lr = rs->tsv.len; |
| for (;;) { |
| int temp = strcoll(l, r); |
| if (temp != 0) return (temp < 0); |
| else { /* strings are equal up to a `\0' */ |
| size_t len = strlen(l); /* index of first `\0' in both strings */ |
| if (len == lr) /* r is finished? */ |
| return 0; /* l is equal or greater than r */ |
| else if (len == ll) /* l is finished? */ |
| return 1; /* l is smaller than r (because r is not finished) */ |
| /* both strings longer than `len'; go on comparing (after the `\0') */ |
| len++; |
| l += len; ll -= len; r += len; lr -= len; |
| } |
| } |
| } |
| |
| |
| int luaV_lessthan (lua_State *L, const TObject *l, const TObject *r) { |
| if (ttype(l) == LUA_TNUMBER && ttype(r) == LUA_TNUMBER) |
| return (nvalue(l) < nvalue(r)); |
| else if (ttype(l) == LUA_TSTRING && ttype(r) == LUA_TSTRING) |
| return luaV_strlessthan(tsvalue(l), tsvalue(r)); |
| else { /* try TM */ |
| if (!call_binTM(L, l, r, L->top, TM_LT)) |
| luaG_ordererror(L, l, r); |
| return (ttype(L->top) != LUA_TNIL); |
| } |
| } |
| |
| |
| void luaV_strconc (lua_State *L, int total, StkId top) { |
| luaV_checkGC(L, top); |
| do { |
| int n = 2; /* number of elements handled in this pass (at least 2) */ |
| if (tostring(L, top-2) || tostring(L, top-1)) { |
| if (!call_binTM(L, top-2, top-1, top-2, TM_CONCAT)) |
| luaG_concaterror(L, top-2, top-1); |
| } else if (tsvalue(top-1)->tsv.len > 0) { /* if len=0, do nothing */ |
| /* at least two string values; get as many as possible */ |
| lu_mem tl = (lu_mem)tsvalue(top-1)->tsv.len + |
| (lu_mem)tsvalue(top-2)->tsv.len; |
| l_char *buffer; |
| int i; |
| while (n < total && !tostring(L, top-n-1)) { /* collect total length */ |
| tl += tsvalue(top-n-1)->tsv.len; |
| n++; |
| } |
| if (tl > MAX_SIZET) luaD_error(L, l_s("string size overflow")); |
| buffer = luaO_openspace(L, tl, l_char); |
| tl = 0; |
| for (i=n; i>0; i--) { /* concat all strings */ |
| size_t l = tsvalue(top-i)->tsv.len; |
| memcpy(buffer+tl, svalue(top-i), l); |
| tl += l; |
| } |
| setsvalue(top-n, luaS_newlstr(L, buffer, tl)); |
| } |
| total -= n-1; /* got `n' strings to create 1 new */ |
| top -= n-1; |
| } while (total > 1); /* repeat until only 1 result left */ |
| } |
| |
| |
| static void luaV_pack (lua_State *L, StkId firstelem) { |
| int i; |
| Hash *htab = luaH_new(L, 0); |
| TObject *n; |
| for (i=0; firstelem+i<L->top; i++) |
| setobj(luaH_setnum(L, htab, i+1), firstelem+i); |
| /* store counter in field `n' */ |
| n = luaH_setstr(L, htab, luaS_newliteral(L, l_s("n"))); |
| setnvalue(n, i); |
| L->top = firstelem; /* remove elements from the stack */ |
| sethvalue(L->top, htab); |
| incr_top; |
| } |
| |
| |
| static void adjust_varargs (lua_State *L, StkId base, int nfixargs) { |
| int nvararg = (L->top-base) - nfixargs; |
| StkId firstvar = base + nfixargs; /* position of first vararg */ |
| if (nvararg < 0) { |
| luaD_checkstack(L, -nvararg); |
| luaD_adjusttop(L, firstvar); |
| } |
| luaV_pack(L, firstvar); |
| } |
| |
| |
| |
| /* |
| ** some macros for common tasks in `luaV_execute' |
| */ |
| |
| #define runtime_check(L, c) { if (!(c)) return L->top; } |
| |
| #define RA(i) (base+GETARG_A(i)) |
| #define RB(i) (base+GETARG_B(i)) |
| #define RC(i) (base+GETARG_C(i)) |
| #define RKC(i) ((GETARG_C(i) < MAXSTACK) ? \ |
| base+GETARG_C(i) : \ |
| tf->k+GETARG_C(i)-MAXSTACK) |
| #define KBc(i) (tf->k+GETARG_Bc(i)) |
| |
| #define Arith(op, optm) { \ |
| const TObject *b = RB(i); const TObject *c = RKC(i); \ |
| TObject tempb, tempc; \ |
| if ((ttype(b) == LUA_TNUMBER || (b = luaV_tonumber(b, &tempb)) != NULL) && \ |
| (ttype(c) == LUA_TNUMBER || (c = luaV_tonumber(c, &tempc)) != NULL)) { \ |
| setnvalue(ra, nvalue(b) op nvalue(c)); \ |
| } else \ |
| call_arith(L, RB(i), RKC(i), ra, optm); \ |
| } |
| |
| |
| #define dojump(pc, i) ((pc) += GETARG_sBc(i)) |
| |
| /* |
| ** Executes the given Lua function. Parameters are between [base,top). |
| ** Returns n such that the the results are between [n,top). |
| */ |
| StkId luaV_execute (lua_State *L, const Closure *cl, StkId base) { |
| const Proto *const tf = cl->f.l; |
| const Instruction *pc; |
| lua_Hook linehook; |
| if (tf->is_vararg) /* varargs? */ |
| adjust_varargs(L, base, tf->numparams); |
| if (base > L->stack_last - tf->maxstacksize) |
| luaD_stackerror(L); |
| luaD_adjusttop(L, base + tf->maxstacksize); |
| pc = tf->code; |
| L->ci->pc = &pc; |
| linehook = L->linehook; |
| /* main loop of interpreter */ |
| for (;;) { |
| const Instruction i = *pc++; |
| const StkId ra = RA(i); |
| if (linehook) |
| traceexec(L, linehook); |
| switch (GET_OPCODE(i)) { |
| case OP_MOVE: { |
| setobj(ra, RB(i)); |
| break; |
| } |
| case OP_LOADK: { |
| setobj(ra, KBc(i)); |
| break; |
| } |
| case OP_LOADINT: { |
| setnvalue(ra, (lua_Number)GETARG_sBc(i)); |
| break; |
| } |
| case OP_LOADUPVAL: { |
| setobj(ra, cl->upvalue+GETARG_Bc(i)); |
| break; |
| } |
| case OP_LOADNIL: { |
| TObject *rb = RB(i); |
| do { |
| setnilvalue(rb--); |
| } while (rb >= ra); |
| break; |
| } |
| case OP_GETGLOBAL: { |
| lua_assert(ttype(KBc(i)) == LUA_TSTRING); |
| luaV_getglobal(L, tsvalue(KBc(i)), ra); |
| break; |
| } |
| case OP_GETTABLE: { |
| luaV_gettable(L, RB(i), RKC(i), ra); |
| break; |
| } |
| case OP_SETGLOBAL: { |
| lua_assert(ttype(KBc(i)) == LUA_TSTRING); |
| luaV_setglobal(L, tsvalue(KBc(i)), ra); |
| break; |
| } |
| case OP_SETTABLE: { |
| luaV_settable(L, RB(i), RKC(i), ra); |
| break; |
| } |
| case OP_NEWTABLE: { |
| sethvalue(ra, luaH_new(L, GETARG_Bc(i))); |
| luaV_checkGC(L, ra+1); |
| break; |
| } |
| case OP_SELF: { |
| StkId rb = RB(i); |
| setobj(ra+1, rb); |
| luaV_gettable(L, rb, RKC(i), ra); |
| break; |
| } |
| case OP_ADD: { |
| Arith( + , TM_ADD); |
| break; |
| } |
| case OP_SUB: { |
| Arith( - , TM_SUB); |
| break; |
| } |
| case OP_MUL: { |
| Arith( * , TM_MUL); |
| break; |
| } |
| case OP_DIV: { |
| Arith( / , TM_DIV); |
| break; |
| } |
| case OP_POW: { |
| call_arith(L, RB(i), RKC(i), ra, TM_POW); |
| break; |
| } |
| case OP_UNM: { |
| const TObject *rb = RB(i); |
| if (ttype(rb) == LUA_TNUMBER || (rb=luaV_tonumber(rb, ra)) != NULL) { |
| setnvalue(ra, -nvalue(rb)); |
| } |
| else { |
| TObject temp; |
| setnilvalue(&temp); |
| call_arith(L, RB(i), &temp, ra, TM_UNM); |
| } |
| break; |
| } |
| case OP_NOT: { |
| if (ttype(RB(i)) == LUA_TNIL) { |
| setnvalue(ra, 1); |
| } else { |
| setnilvalue(ra); |
| } |
| break; |
| } |
| case OP_CONCAT: { |
| StkId top = RC(i)+1; |
| StkId rb = RB(i); |
| luaV_strconc(L, top-rb, top); |
| setobj(ra, rb); |
| break; |
| } |
| case OP_CJMP: |
| case OP_JMP: { |
| dojump(pc, i); |
| break; |
| } |
| case OP_TESTEQ: { |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (luaO_equalObj(ra, RKC(i))) dojump(pc, *pc); |
| pc++; |
| break; |
| } |
| case OP_TESTNE: { |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (!luaO_equalObj(ra, RKC(i))) dojump(pc, *pc); |
| pc++; |
| break; |
| } |
| case OP_TESTLT: { |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (luaV_lessthan(L, ra, RKC(i))) dojump(pc, *pc); |
| pc++; |
| break; |
| } |
| case OP_TESTLE: { /* b <= c === !(c<b) */ |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (!luaV_lessthan(L, RKC(i), ra)) dojump(pc, *pc); |
| pc++; |
| break; |
| } |
| case OP_TESTGT: { /* b > c === (c<b) */ |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (luaV_lessthan(L, RKC(i), ra)) dojump(pc, *pc); |
| pc++; |
| break; |
| } |
| case OP_TESTGE: { /* b >= c === !(b<c) */ |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (!luaV_lessthan(L, ra, RKC(i))) dojump(pc, *pc); |
| pc++; |
| break; |
| } |
| case OP_TESTT: { |
| StkId rb = RB(i); |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (ttype(rb) != LUA_TNIL) { |
| setobj(ra, rb); |
| dojump(pc, *pc); |
| } |
| pc++; |
| break; |
| } |
| case OP_TESTF: { |
| lua_assert(GET_OPCODE(*pc) == OP_CJMP); |
| if (ttype(RB(i)) == LUA_TNIL) { |
| setnilvalue(ra); |
| dojump(pc, *pc); |
| } |
| pc++; |
| break; |
| } |
| case OP_NILJMP: { |
| setnilvalue(ra); |
| pc++; |
| break; |
| } |
| case OP_CALL: { |
| int c; |
| int b = GETARG_B(i); |
| if (b != NO_REG) |
| L->top = ra+b+1; |
| luaD_call(L, ra); |
| c = GETARG_C(i); |
| if (c != NO_REG) { |
| while (L->top < ra+c) setnilvalue(L->top++); |
| L->top = base + tf->maxstacksize; |
| } |
| break; |
| } |
| case OP_RETURN: { |
| int b = GETARG_B(i); |
| if (b != NO_REG) |
| L->top = ra+b; |
| return ra; |
| } |
| case OP_FORPREP: { |
| if (luaV_tonumber(ra, ra) == NULL) |
| luaD_error(L, l_s("`for' initial value must be a number")); |
| if (luaV_tonumber(ra+1, ra+1) == NULL) |
| luaD_error(L, l_s("`for' limit must be a number")); |
| if (luaV_tonumber(ra+2, ra+2) == NULL) |
| luaD_error(L, l_s("`for' step must be a number")); |
| /* decrement index (to be incremented) */ |
| chgnvalue(ra, nvalue(ra) - nvalue(ra+2)); |
| pc += -GETARG_sBc(i); /* `jump' to loop end (delta is negated here) */ |
| /* store in `ra+1' total number of repetitions */ |
| chgnvalue(ra+1, (nvalue(ra+1)-nvalue(ra))/nvalue(ra+2)); |
| /* go through */ |
| } |
| case OP_FORLOOP: { |
| runtime_check(L, ttype(ra+1) == LUA_TNUMBER && |
| ttype(ra+2) == LUA_TNUMBER); |
| if (ttype(ra) != LUA_TNUMBER) |
| luaD_error(L, l_s("`for' index must be a number")); |
| chgnvalue(ra+1, nvalue(ra+1) - 1); /* decrement counter */ |
| if (nvalue(ra+1) >= 0) { |
| chgnvalue(ra, nvalue(ra) + nvalue(ra+2)); /* increment index */ |
| dojump(pc, i); /* repeat loop */ |
| } |
| break; |
| } |
| case OP_TFORPREP: { |
| if (ttype(ra) != LUA_TTABLE) |
| luaD_error(L, l_s("`for' table must be a table")); |
| setnvalue(ra+1, -1); /* initial index */ |
| setnilvalue(ra+2); |
| setnilvalue(ra+3); |
| pc += -GETARG_sBc(i); /* `jump' to loop end (delta is negated here) */ |
| /* go through */ |
| } |
| case OP_TFORLOOP: { |
| Hash *t; |
| int n; |
| runtime_check(L, ttype(ra) == LUA_TTABLE && |
| ttype(ra+1) == LUA_TNUMBER); |
| t = hvalue(ra); |
| n = (int)nvalue(ra+1); |
| n = luaH_nexti(t, n); |
| if (n != -1) { /* repeat loop? */ |
| Node *node = node(t, n); |
| setnvalue(ra+1, n); /* index */ |
| setobj(ra+2, key(node)); |
| setobj(ra+3, val(node)); |
| dojump(pc, i); /* repeat loop */ |
| } |
| break; |
| } |
| case OP_SETLIST: |
| case OP_SETLISTO: { |
| int bc; |
| int n; |
| Hash *h; |
| runtime_check(L, ttype(ra) == LUA_TTABLE); |
| h = hvalue(ra); |
| bc = GETARG_Bc(i); |
| if (GET_OPCODE(i) == OP_SETLIST) |
| n = (bc&(LFIELDS_PER_FLUSH-1)) + 1; |
| else |
| n = L->top - ra - 1; |
| bc &= ~(LFIELDS_PER_FLUSH-1); /* bc = bc - bc%FPF */ |
| for (; n > 0; n--) |
| setobj(luaH_setnum(L, h, bc+n), ra+n); |
| break; |
| } |
| case OP_CLOSURE: { |
| Proto *p = tf->p[GETARG_Bc(i)]; |
| int nup = p->nupvalues; |
| luaV_checkGC(L, ra+nup); |
| L->top = ra+nup; |
| luaV_Lclosure(L, p, nup); |
| L->top = base + tf->maxstacksize; |
| break; |
| } |
| } |
| } |
| } |
| |