Browse Source

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)
Roberto Ierusalimschy 6 years ago
parent
commit
cf71a5ddc7
8 changed files with 87 additions and 51 deletions
  1. 22 9
      lapi.c
  2. 2 1
      lfunc.c
  3. 7 8
      lopcodes.h
  4. 4 2
      lstrlib.c
  5. 5 2
      ltests.c
  6. 32 22
      manual/manual.of
  7. 14 7
      testes/api.lua
  8. 1 0
      testes/strings.lua

+ 22 - 9
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)) {
   switch (ttype(o)) {
     case LUA_TUSERDATA: return getudatamem(uvalue(o));
     case LUA_TUSERDATA: return getudatamem(uvalue(o));
     case LUA_TLIGHTUSERDATA: return pvalue(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) {
 LUA_API lua_State *lua_tothread (lua_State *L, int idx) {
   const TValue *o = index2value(L, idx);
   const TValue *o = index2value(L, idx);
   return (!ttisthread(o)) ? NULL : thvalue(o);
   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) {
 LUA_API const void *lua_topointer (lua_State *L, int idx) {
   const TValue *o = index2value(L, idx);
   const TValue *o = index2value(L, idx);
   switch (ttypetag(o)) {
   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_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;
+    }
   }
   }
 }
 }
 
 

+ 2 - 1
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? */
     if (prepclosingmethod(L, uv, &G(L)->nilvalue))  /* something to call? */
       callclose(L, NULL);  /* call closing method */
       callclose(L, NULL);  /* call closing method */
     else if (!ttisnil(uv)) {  /* non-closable non-nil value? */
     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 = "?";
       if (vname == NULL) vname = "?";
       luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
       luaG_runerror(L, "attempt to close non-closable variable '%s'", vname);
     }
     }

+ 7 - 8
lopcodes.h

@@ -90,7 +90,6 @@ enum OpMode {iABC, iABx, iAsBx, iAx, isJ};  /* basic instruction formats */
 #define MAXARG_B	((1<<SIZE_B)-1)
 #define MAXARG_B	((1<<SIZE_B)-1)
 #define MAXARG_C	((1<<SIZE_C)-1)
 #define MAXARG_C	((1<<SIZE_C)-1)
 #define OFFSET_sC	(MAXARG_C >> 1)
 #define OFFSET_sC	(MAXARG_C >> 1)
-#define MAXARG_Cx	((1<<(SIZE_C + 1))-1)
 
 
 
 
 /* creates a mask with 'n' 1 bits at position 'p' */
 /* 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_BORK,/*	A B C	R(A) := R(B) | K(C):integer			*/
 OP_BXORK,/*	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_ADD,/*	A B C	R(A) := R(B) + R(C)				*/
 OP_SUB,/*	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_GEI,/*	A sB	if ((R(A) >= sB) ~= k) then pc++		*/
 
 
 OP_TEST,/*	A 	if (not R(A) == 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_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))		*/
 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;
 } OpCode;
 
 
 
 
-#define NUM_OPCODES	(cast_int(OP_EXTRAARG) + 1)
+#define NUM_OPCODES	((int)(OP_EXTRAARG) + 1)
 
 
 
 
 
 
 /*===========================================================================
 /*===========================================================================
   Notes:
   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
   (*) In OP_VARARG, if (C == 0) then use actual number of varargs and
   set top (like in OP_CALL with C == 0).
   set top (like in OP_CALL with C == 0).

+ 4 - 2
lstrlib.c

@@ -181,7 +181,7 @@ static int str_byte (lua_State *L) {
   size_t pose = getendpos(L, 3, pi, l);
   size_t pose = getendpos(L, 3, pi, l);
   int n, i;
   int n, i;
   if (posi > pose) return 0;  /* empty interval; return no values */
   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");
     return luaL_error(L, "string slice too long");
   n = (int)(pose -  posi) + 1;
   n = (int)(pose -  posi) + 1;
   luaL_checkstack(L, n, "string slice too long");
   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 */
       char *buff = luaL_prepbuffsize(&b, MAX_ITEM);  /* to put formatted item */
       int nb = 0;  /* number of bytes in added item */
       int nb = 0;  /* number of bytes in added item */
       if (++arg > top)
       if (++arg > top)
-        luaL_argerror(L, arg, "no value");
+        return luaL_argerror(L, arg, "no value");
       strfrmt = scanformat(L, strfrmt, form);
       strfrmt = scanformat(L, strfrmt, form);
       switch (*strfrmt++) {
       switch (*strfrmt++) {
         case 'c': {
         case 'c': {
@@ -1186,6 +1186,8 @@ static int str_format (lua_State *L) {
           break;
           break;
         }
         }
         case 'q': {
         case 'q': {
+          if (form[2] != '\0')  /* modifiers? */
+            return luaL_error(L, "specifier '%%q' cannot have modifiers");
           addliteral(L, &b, arg);
           addliteral(L, &b, arg);
           break;
           break;
         }
         }

+ 5 - 2
ltests.c

@@ -164,7 +164,7 @@ typedef union Header {
 
 
 
 
 Memcontrol l_memcontrol =
 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) {
 static void freeblock (Memcontrol *mc, Header *block) {
@@ -1596,7 +1596,10 @@ static struct X { int x; } x;
       lua_pushnumber(L1, lua_tonumber(L1, getindex));
       lua_pushnumber(L1, lua_tonumber(L1, getindex));
     }
     }
     else if EQ("topointer") {
     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") {
     else if EQ("tostring") {
       const char *s = lua_tostring(L1, getindex);
       const char *s = lua_tostring(L1, getindex);

+ 32 - 22
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}.
 but any Lua value except @nil and @x{NaN}.
 (@emphx{Not a Number} is a special floating-point value
 (@emphx{Not a Number} is a special floating-point value
 used by the @x{IEEE 754} standard to represent
 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};
 Tables can be @emph{heterogeneous};
 that is, they can contain values of all types (except @nil).
 that is, they can contain values of all types (except @nil).
 Any key with value @nil is not considered part of the table.
 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.
 corresponding table or userdata is unreachable.
 Finalizers allow you to coordinate Lua's garbage collection
 Finalizers allow you to coordinate Lua's garbage collection
 with external resource management
 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,
 For an object (table or userdata) to be finalized when collected,
 you must @emph{mark} it for finalization.
 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,
 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
 A goto may jump to any visible label as long as it does not
 enter into the scope of a local variable.
 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},
 Labels and empty statements are called @def{void statements},
 as they perform no actions.
 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},
 exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
 or exiting by an error.
 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.
 to call its @idx{__close} metamethod.
 If the value is @nil, it is ignored;
 If the value is @nil, it is ignored;
 otherwise,
 otherwise,
@@ -4236,7 +4237,7 @@ indicates whether the operation succeeded.
 
 
 Converts the value at the given index to a generic
 Converts the value at the given index to a generic
 @N{C pointer} (@T{void*}).
 @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}.
 otherwise, @id{lua_topointer} returns @id{NULL}.
 Different objects will give different pointers.
 Different objects will give different pointers.
 There is no way to convert the pointer back to its original value.
 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.
 Functions with upvalues have only their number of upvalues saved.
 When (re)loaded,
 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
 and reload the upvalues of a function
 in a way adequate to your needs.)
 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
 Returns a formatted version of its variable number of arguments
 following the description given in its first argument (which must be a string).
 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 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},
 @T{*}, @id{h}, @id{L}, @id{l}, @id{n},
 and @id{p} are not supported
 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.
 in a way that the result is a valid constant in Lua source code.
 Booleans and nil are written in the obvious way
 Booleans and nil are written in the obvious way
 (@id{true}, @id{false}, @id{nil}).
 (@id{true}, @id{false}, @id{nil}).
@@ -6770,22 +6773,23 @@ may produce the string:
 "a string with \"quotes\" and \
 "a string with \"quotes\" and \
  new line"
  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{A}, @id{a}, @id{E}, @id{e}, @id{f},
 @id{G}, and @id{g} all expect a number as argument.
 @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}
 @id{i}, @id{o}, @id{u}, @id{X}, and @id{x}
 expect an integer.
 expect an integer.
 When Lua is compiled with a C89 compiler,
 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,
 if its argument is not a string,
 it is converted to one following the same rules of @Lid{tostring}.
 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.
 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).
 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.
+}
+
 }
 }
 
 
 }
 }

+ 14 - 7
testes/api.lua

@@ -332,6 +332,7 @@ function to (s, x, n)
   return T.testC(string.format("%s %d; return 1", s, n), x)
   return T.testC(string.format("%s %d; return 1", s, n), x)
 end
 end
 
 
+local null = T.pushuserdata(0)
 local hfunc = string.gmatch("", "")    -- a "heavy C function" (with upvalues)
 local hfunc = string.gmatch("", "")    -- a "heavy C function" (with upvalues)
 assert(debug.getupvalue(hfunc, 1))
 assert(debug.getupvalue(hfunc, 1))
 assert(to("tostring", {}) == nil)
 assert(to("tostring", {}) == nil)
@@ -349,13 +350,19 @@ assert(to("tonumber", {}) == 0)
 assert(to("tonumber", "12") == 12)
 assert(to("tonumber", "12") == 12)
 assert(to("tonumber", "s2") == 0)
 assert(to("tonumber", "s2") == 0)
 assert(to("tonumber", 1, 20) == 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", 20) == 0)
 assert(to("func2num", T.pushuserdata(10)) == 0)
 assert(to("func2num", T.pushuserdata(10)) == 0)
 assert(to("func2num", io.read) ~= 0)     -- light C function
 assert(to("func2num", io.read) ~= 0)     -- light C function

+ 1 - 0
testes/strings.lua

@@ -199,6 +199,7 @@ end
 
 
 assert(string.format("\0%s\0", "\0\0\1") == "\0\0\0\1\0")
 assert(string.format("\0%s\0", "\0\0\1") == "\0\0\0\1\0")
 checkerror("contains zeros", string.format, "%10s", "\0")
 checkerror("contains zeros", string.format, "%10s", "\0")
+checkerror("cannot have modifiers", string.format, "%10q", "1")
 
 
 -- format x tostring
 -- format x tostring
 assert(string.format("%s %s", nil, true) == "nil true")
 assert(string.format("%s %s", nil, true) == "nil true")