From cf71a5ddc742692fad813f89f1c9ef53e1ffde0f Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 13 Mar 2019 13:16:53 -0300 Subject: [PATCH] Details Several small improvements (code style, warnings, comments, more tests), in particular: - 'lua_topointer' extended to handle strings - raises an error in 'string.format("%10q")' ('%q' with modifiers) - in the manual for 'string.format', the term "option" replaced by "conversion specifier" (the term used by the C standard) --- lapi.c | 31 ++++++++++++++++++-------- lfunc.c | 3 ++- lopcodes.h | 15 ++++++------- lstrlib.c | 6 ++++-- ltests.c | 7 ++++-- manual/manual.of | 54 +++++++++++++++++++++++++++------------------- testes/api.lua | 21 ++++++++++++------ testes/strings.lua | 1 + 8 files changed, 87 insertions(+), 51 deletions(-) diff --git a/lapi.c b/lapi.c index 4026497e..66d75649 100644 --- a/lapi.c +++ b/lapi.c @@ -414,8 +414,7 @@ LUA_API lua_CFunction lua_tocfunction (lua_State *L, int idx) { } -LUA_API void *lua_touserdata (lua_State *L, int idx) { - const TValue *o = index2value(L, idx); +static void *touserdata (const TValue *o) { switch (ttype(o)) { case LUA_TUSERDATA: return getudatamem(uvalue(o)); case LUA_TLIGHTUSERDATA: return pvalue(o); @@ -424,23 +423,37 @@ LUA_API void *lua_touserdata (lua_State *L, int idx) { } +LUA_API void *lua_touserdata (lua_State *L, int idx) { + const TValue *o = index2value(L, idx); + return touserdata(o); +} + + LUA_API lua_State *lua_tothread (lua_State *L, int idx) { const TValue *o = index2value(L, idx); return (!ttisthread(o)) ? NULL : thvalue(o); } +/* +** Returns a pointer to the internal representation of an object. +** Note that ANSI C does not allow the conversion of a pointer to +** function to a 'void*', so the conversion here goes through +** a 'size_t'. (As the returned pointer is only informative, this +** conversion should not be a problem.) +*/ LUA_API const void *lua_topointer (lua_State *L, int idx) { const TValue *o = index2value(L, idx); switch (ttypetag(o)) { - case LUA_TTABLE: return hvalue(o); - case LUA_TLCL: return clLvalue(o); - case LUA_TCCL: return clCvalue(o); case LUA_TLCF: return cast_voidp(cast_sizet(fvalue(o))); - case LUA_TTHREAD: return thvalue(o); - case LUA_TUSERDATA: return getudatamem(uvalue(o)); - case LUA_TLIGHTUSERDATA: return pvalue(o); - default: return NULL; + case LUA_TUSERDATA: case LUA_TLIGHTUSERDATA: + return touserdata(o); + default: { + if (iscollectable(o)) + return gcvalue(o); + else + return NULL; + } } } diff --git a/lfunc.c b/lfunc.c index 362b798c..3e044b65 100644 --- a/lfunc.c +++ b/lfunc.c @@ -138,7 +138,8 @@ static int callclosemth (lua_State *L, TValue *uv, StkId level, int status) { if (prepclosingmethod(L, uv, &G(L)->nilvalue)) /* something to call? */ callclose(L, NULL); /* call closing method */ else if (!ttisnil(uv)) { /* non-closable non-nil value? */ - const char *vname = luaG_findlocal(L, L->ci, level - L->ci->func, NULL); + int idx = cast_int(level - L->ci->func); + const char *vname = luaG_findlocal(L, L->ci, idx, NULL); if (vname == NULL) vname = "?"; luaG_runerror(L, "attempt to close non-closable variable '%s'", vname); } diff --git a/lopcodes.h b/lopcodes.h index d7403caf..3e100259 100644 --- a/lopcodes.h +++ b/lopcodes.h @@ -90,7 +90,6 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ}; /* basic instruction formats */ #define MAXARG_B ((1<> 1) -#define MAXARG_Cx ((1<<(SIZE_C + 1))-1) /* creates a mask with 'n' 1 bits at position 'p' */ @@ -233,8 +232,8 @@ OP_BANDK,/* A B C R(A) := R(B) & K(C):integer */ OP_BORK,/* A B C R(A) := R(B) | K(C):integer */ OP_BXORK,/* A B C R(A) := R(B) ~ K(C):integer */ -OP_SHRI,/* A B C R(A) := R(B) >> C */ -OP_SHLI,/* A B C R(A) := C << R(B) */ +OP_SHRI,/* A B sC R(A) := R(B) >> C */ +OP_SHLI,/* A B sC R(A) := C << R(B) */ OP_ADD,/* A B C R(A) := R(B) + R(C) */ OP_SUB,/* A B C R(A) := R(B) - R(C) */ @@ -272,7 +271,7 @@ OP_GTI,/* A sB if ((R(A) > sB) ~= k) then pc++ */ OP_GEI,/* A sB if ((R(A) >= sB) ~= k) then pc++ */ OP_TEST,/* A if (not R(A) == k) then pc++ */ -OP_TESTSET,/* A B if (not R(B) == k) then R(A) := R(B) else pc++ */ +OP_TESTSET,/* A B if (not R(B) == k) then pc++ else R(A) := R(B) */ OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */ OP_TAILCALL,/* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ @@ -305,15 +304,15 @@ OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */ } OpCode; -#define NUM_OPCODES (cast_int(OP_EXTRAARG) + 1) +#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1) /*=========================================================================== Notes: - (*) In OP_CALL, if (B == 0) then B = top. If (C == 0), then 'top' is - set to last_result+1, so next open instruction (OP_CALL, OP_RETURN*, - OP_SETLIST) may use 'top'. + (*) In OP_CALL, if (B == 0) then B = top - A. If (C == 0), then + 'top' is set to last_result+1, so next open instruction (OP_CALL, + OP_RETURN*, OP_SETLIST) may use 'top'. (*) In OP_VARARG, if (C == 0) then use actual number of varargs and set top (like in OP_CALL with C == 0). diff --git a/lstrlib.c b/lstrlib.c index 41ebc523..ab4258e5 100644 --- a/lstrlib.c +++ b/lstrlib.c @@ -181,7 +181,7 @@ static int str_byte (lua_State *L) { size_t pose = getendpos(L, 3, pi, l); int n, i; if (posi > pose) return 0; /* empty interval; return no values */ - if (pose - posi >= INT_MAX) /* arithmetic overflow? */ + if (pose - posi >= (size_t)INT_MAX) /* arithmetic overflow? */ return luaL_error(L, "string slice too long"); n = (int)(pose - posi) + 1; luaL_checkstack(L, n, "string slice too long"); @@ -1159,7 +1159,7 @@ static int str_format (lua_State *L) { char *buff = luaL_prepbuffsize(&b, MAX_ITEM); /* to put formatted item */ int nb = 0; /* number of bytes in added item */ if (++arg > top) - luaL_argerror(L, arg, "no value"); + return luaL_argerror(L, arg, "no value"); strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { @@ -1186,6 +1186,8 @@ static int str_format (lua_State *L) { break; } case 'q': { + if (form[2] != '\0') /* modifiers? */ + return luaL_error(L, "specifier '%%q' cannot have modifiers"); addliteral(L, &b, arg); break; } diff --git a/ltests.c b/ltests.c index 36a974ae..23375382 100644 --- a/ltests.c +++ b/ltests.c @@ -164,7 +164,7 @@ typedef union Header { Memcontrol l_memcontrol = - {0L, 0L, 0L, 0L, (~0L), {0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L}}; + {0UL, 0UL, 0UL, 0UL, (~0UL), {0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL}}; static void freeblock (Memcontrol *mc, Header *block) { @@ -1596,7 +1596,10 @@ static struct X { int x; } x; lua_pushnumber(L1, lua_tonumber(L1, getindex)); } else if EQ("topointer") { - lua_pushnumber(L1, cast_sizet(lua_topointer(L1, getindex))); + lua_pushlightuserdata(L1, cast_voidp(lua_topointer(L1, getindex))); + } + else if EQ("touserdata") { + lua_pushlightuserdata(L1, lua_touserdata(L1, getindex)); } else if EQ("tostring") { const char *s = lua_tostring(L1, getindex); diff --git a/manual/manual.of b/manual/manual.of index 421d04de..9c8ab033 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -143,7 +143,7 @@ that is, @x{arrays} that can have as indices not only numbers, but any Lua value except @nil and @x{NaN}. (@emphx{Not a Number} is a special floating-point value used by the @x{IEEE 754} standard to represent -undefined or unrepresentable numerical results, such as @T{0/0}.) +undefined numerical results, such as @T{0/0}.) Tables can be @emph{heterogeneous}; that is, they can contain values of all types (except @nil). Any key with value @nil is not considered part of the table. @@ -670,8 +670,8 @@ are called when the garbage collector detects that the corresponding table or userdata is unreachable. Finalizers allow you to coordinate Lua's garbage collection with external resource management -(such as closing files, network or database connections, -or freeing your own memory). +such as closing files, network or database connections, +or freeing your own memory. For an object (table or userdata) to be finalized when collected, you must @emph{mark} it for finalization. @@ -1323,11 +1323,12 @@ labels in Lua are considered statements too: } A label is visible in the entire block where it is defined, -except -inside nested blocks where a label with the same name is defined and -inside nested functions. +except inside nested functions. A goto may jump to any visible label as long as it does not enter into the scope of a local variable. +A label should not be declared +where a label with the same name is visible, +even if this other label has been declared in an enclosing block. Labels and empty statements are called @def{void statements}, as they perform no actions. @@ -1537,7 +1538,7 @@ goes out of scope, including normal block termination, exiting its block by @Rw{break}/@Rw{goto}/@Rw{return}, or exiting by an error. -Here, to \emph{close} a value means +Here, to @emph{close} a value means to call its @idx{__close} metamethod. If the value is @nil, it is ignored; otherwise, @@ -4236,7 +4237,7 @@ indicates whether the operation succeeded. Converts the value at the given index to a generic @N{C pointer} (@T{void*}). -The value can be a userdata, a table, a thread, or a function; +The value can be a userdata, a table, a thread, a string, or a function; otherwise, @id{lua_topointer} returns @id{NULL}. Different objects will give different pointers. There is no way to convert the pointer back to its original value. @@ -6712,8 +6713,10 @@ to save space. Functions with upvalues have only their number of upvalues saved. When (re)loaded, -those upvalues receive fresh instances containing @nil. -(You can use the debug library to serialize +those upvalues receive fresh instances. +(See the @Lid{load} function for details about +how these upvalues are initialized. +You can use the debug library to serialize and reload the upvalues of a function in a way adequate to your needs.) @@ -6747,12 +6750,12 @@ after the two indices. Returns a formatted version of its variable number of arguments following the description given in its first argument (which must be a string). The format string follows the same rules as the @ANSI{sprintf}. -The only differences are that the options/modifiers +The only differences are that the conversion specifiers and modifiers @T{*}, @id{h}, @id{L}, @id{l}, @id{n}, and @id{p} are not supported -and that there is an extra option, @id{q}. +and that there is an extra specifier, @id{q}. -The @id{q} option formats booleans, nil, numbers, and strings +The specifier @id{q} formats booleans, nil, numbers, and strings in a way that the result is a valid constant in Lua source code. Booleans and nil are written in the obvious way (@id{true}, @id{false}, @id{nil}). @@ -6770,22 +6773,23 @@ may produce the string: "a string with \"quotes\" and \ new line" } +This specifier does not support modifiers (flags, width, length). -Options +The conversion specifiers @id{A}, @id{a}, @id{E}, @id{e}, @id{f}, @id{G}, and @id{g} all expect a number as argument. -Options @id{c}, @id{d}, +The specifiers @id{c}, @id{d}, @id{i}, @id{o}, @id{u}, @id{X}, and @id{x} expect an integer. When Lua is compiled with a C89 compiler, -options @id{A} and @id{a} (hexadecimal floats) -do not support any modifier (flags, width, length). +the specifiers @id{A} and @id{a} (hexadecimal floats) +do not support modifiers. -Option @id{s} expects a string; +The specifier @id{s} expects a string; if its argument is not a string, it is converted to one following the same rules of @Lid{tostring}. -If the option has any modifier (flags, width, length), -the string argument should not contain @x{embedded zeros}. +If the specifier has any modifier, +the corresponding string argument should not contain @x{embedded zeros}. } @@ -8009,8 +8013,8 @@ or there is any input from some special files } } -For the last two cases, @id{size} -specifies the size of the buffer, in bytes. +For the last two cases, +@id{size} is a hint for the size of the buffer, in bytes. The default is an appropriate size. } @@ -8698,6 +8702,12 @@ When a coroutine finishes with an error, its stack is unwound (to run any pending closing methods). } +@item{ +A label for a @Rw{goto} cannot be declared where a label with the same +name is visible, even if this other label is declared in an enclosing +block. +} + } } diff --git a/testes/api.lua b/testes/api.lua index 9904dadf..d034ea80 100644 --- a/testes/api.lua +++ b/testes/api.lua @@ -332,6 +332,7 @@ function to (s, x, n) return T.testC(string.format("%s %d; return 1", s, n), x) end +local null = T.pushuserdata(0) local hfunc = string.gmatch("", "") -- a "heavy C function" (with upvalues) assert(debug.getupvalue(hfunc, 1)) assert(to("tostring", {}) == nil) @@ -349,13 +350,19 @@ assert(to("tonumber", {}) == 0) assert(to("tonumber", "12") == 12) assert(to("tonumber", "s2") == 0) assert(to("tonumber", 1, 20) == 0) -assert(to("topointer", 10) == 0) -assert(to("topointer", true) == 0) -assert(to("topointer", T.pushuserdata(20)) == 20) -assert(to("topointer", io.read) ~= 0) -- light C function -assert(to("topointer", hfunc) ~= 0) -- "heavy" C function -assert(to("topointer", function () end) ~= 0) -- Lua function -assert(to("topointer", io.stdin) ~= 0) -- full userdata +assert(to("topointer", 10) == null) +assert(to("topointer", true) == null) +assert(to("topointer", nil) == null) +assert(to("topointer", "abc") ~= null) +assert(to("topointer", string.rep("x", 10)) == + to("topointer", string.rep("x", 10))) -- short strings +assert(to("topointer", string.rep("x", 300)) ~= + to("topointer", string.rep("x", 300))) -- long strings +assert(to("topointer", T.pushuserdata(20)) ~= null) +assert(to("topointer", io.read) ~= null) -- light C function +assert(to("topointer", hfunc) ~= null) -- "heavy" C function +assert(to("topointer", function () end) ~= null) -- Lua function +assert(to("topointer", io.stdin) ~= null) -- full userdata assert(to("func2num", 20) == 0) assert(to("func2num", T.pushuserdata(10)) == 0) assert(to("func2num", io.read) ~= 0) -- light C function diff --git a/testes/strings.lua b/testes/strings.lua index 88480924..da53a87e 100644 --- a/testes/strings.lua +++ b/testes/strings.lua @@ -199,6 +199,7 @@ end assert(string.format("\0%s\0", "\0\0\1") == "\0\0\0\1\0") checkerror("contains zeros", string.format, "%10s", "\0") +checkerror("cannot have modifiers", string.format, "%10q", "1") -- format x tostring assert(string.format("%s %s", nil, true) == "nil true")