Revamp of 'lua_pushfstring' / 'luaO_pushvfstring'

The function 'luaO_pushvfstring' now uses an internal buffer to
concatenate small strings, instead of pushing all pieces on the
stack. This avoids the creation of several small Lua strings for each
piece of the result. (For instance, a format like "n: '%d'" used to
create three intermediate strings: "n: '", the numeral, and "'".
Now it creates none.)
diff --git a/lobject.c b/lobject.c
index 67c3712..123f0e5 100644
--- a/lobject.c
+++ b/lobject.c
@@ -364,25 +364,44 @@
 
 
 /*
-** Convert a number object to a string
+** Convert a number object to a string, adding it to a buffer
 */
-void luaO_tostring (lua_State *L, TValue *obj) {
-  char buff[MAXNUMBER2STR];
+static size_t tostringbuff (TValue *obj, char *buff) {
   size_t len;
   lua_assert(ttisnumber(obj));
   if (ttisinteger(obj))
-    len = lua_integer2str(buff, sizeof(buff), ivalue(obj));
+    len = lua_integer2str(buff, MAXNUMBER2STR, ivalue(obj));
   else {
-    len = lua_number2str(buff, sizeof(buff), fltvalue(obj));
+    len = lua_number2str(buff, MAXNUMBER2STR, fltvalue(obj));
     if (buff[strspn(buff, "-0123456789")] == '\0') {  /* looks like an int? */
       buff[len++] = lua_getlocaledecpoint();
       buff[len++] = '0';  /* adds '.0' to result */
     }
   }
+  return len;
+}
+
+
+/*
+** Convert a number object to a Lua string, replacing the value at 'obj'
+*/
+void luaO_tostring (lua_State *L, TValue *obj) {
+  char buff[MAXNUMBER2STR];
+  size_t len = tostringbuff(obj, buff);
   setsvalue(L, obj, luaS_newlstr(L, buff, len));
 }
 
 
+/* size for buffer used by 'luaO_pushvfstring' */
+#define BUFVFS		400
+
+/* buffer used by 'luaO_pushvfstring' */
+typedef struct BuffFS {
+  int blen;  /* length of partial string in 'buff' */
+  char buff[BUFVFS];  /* holds last part of the result */
+} BuffFS;
+
+
 static void pushstr (lua_State *L, const char *str, size_t l) {
   setsvalue2s(L, L->top, luaS_newlstr(L, str, l));
   L->top++;
@@ -390,59 +409,109 @@
 
 
 /*
+** empty the buffer into the stack
+*/
+static void clearbuff (lua_State *L, BuffFS *buff) {
+  pushstr(L, buff->buff, buff->blen);  /* push buffer */
+  buff->blen = 0;  /* buffer now is empty */
+}
+
+
+/*
+** Add 'str' to the buffer. It buffer has no enough space,
+** empty the buffer. If string is still larger than the buffer,
+** push the string directly to the stack. Return number of items
+** pushed.
+*/
+static int addstr2buff (lua_State *L, BuffFS *buff, const char *str,
+                                                    size_t slen) {
+  int pushed = 0;  /* number of items pushed to the stack */
+  lua_assert(buff->blen <= BUFVFS);
+  if (slen > BUFVFS - cast_sizet(buff->blen)) {  /* string does not fit? */
+    clearbuff(L, buff);
+    pushed = 1;
+    if (slen >= BUFVFS) {  /* string still does not fit into buffer? */
+      pushstr(L, str, slen);  /* push string */
+      return 2;
+    }
+  }
+  memcpy(buff->buff + buff->blen, str, slen);  /* add string to buffer */
+  buff->blen += slen;
+  return pushed;
+}
+
+
+/*
+** Add a number to the buffer; return number of strings pushed into
+** the stack. (At most one, to free buffer space.)
+*/
+static int addnum2buff (lua_State *L, BuffFS *buff, TValue *num) {
+  char numbuff[MAXNUMBER2STR];
+  size_t len = tostringbuff(num, numbuff);  /* format number into 'numbuff' */
+  return addstr2buff(L, buff, numbuff, len);
+}
+
+
+/*
 ** this function handles only '%d', '%c', '%f', '%p', and '%s'
    conventional formats, plus Lua-specific '%I' and '%U'
 */
 const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) {
-  int n = 0;  /* number of strings in the stack to concatenate */
-  const char *e;  /* points to next conversion specifier */
+  BuffFS buff;  /* holds last part of the result */
+  int pushed = 0;  /* number of strings in the stack to concatenate */
+  const char *e;  /* points to next '%' */
+  buff.blen = 0;
   while ((e = strchr(fmt, '%')) != NULL) {
-    pushstr(L, fmt, e - fmt);  /* string up to conversion specifier */
-    switch (*(e+1)) {
+    pushed += addstr2buff(L, &buff, fmt, e - fmt);  /* add 'fmt' up to '%' */
+    switch (*(e + 1)) {  /* conversion specifier */
       case 's': {  /* zero-terminated string */
         const char *s = va_arg(argp, char *);
         if (s == NULL) s = "(null)";
-        pushstr(L, s, strlen(s));
+        pushed += addstr2buff(L, &buff, s, strlen(s));
         break;
       }
       case 'c': {  /* an 'int' as a character */
-        char buff = cast_char(va_arg(argp, int));
-        if (lisprint(cast_uchar(buff)))
-          pushstr(L, &buff, 1);
-        else  /* non-printable character; print its code */
-          luaO_pushfstring(L, "<\\%d>", cast_uchar(buff));
+        /* if non-printable character, print its code */
+        char bf[10];
+        int c = va_arg(argp, int);
+        int l = (lisprint(c)) ? l_sprintf(bf, sizeof(bf), "%c", c)
+                              : l_sprintf(bf, sizeof(bf), "<\\%u>", c);
+        pushed += addstr2buff(L, &buff, bf, l);
         break;
       }
       case 'd': {  /* an 'int' */
-        setivalue(s2v(L->top), va_arg(argp, int));
-        goto top2str;
+        TValue num;
+        setivalue(&num, va_arg(argp, int));
+        pushed += addnum2buff(L, &buff, &num);
+        break;
       }
       case 'I': {  /* a 'lua_Integer' */
-        setivalue(s2v(L->top), cast(lua_Integer, va_arg(argp, l_uacInt)));
-        goto top2str;
+        TValue num;
+        setivalue(&num, cast(lua_Integer, va_arg(argp, l_uacInt)));
+        pushed += addnum2buff(L, &buff, &num);
+        break;
       }
       case 'f': {  /* a 'lua_Number' */
-        setfltvalue(s2v(L->top), cast_num(va_arg(argp, l_uacNumber)));
-      top2str:  /* convert the top element to a string */
-        L->top++;
-        luaO_tostring(L, s2v(L->top - 1));
+        TValue num;
+        setfltvalue(&num, cast_num(va_arg(argp, l_uacNumber)));
+        pushed += addnum2buff(L, &buff, &num);
         break;
       }
       case 'p': {  /* a pointer */
-        char buff[4*sizeof(void *) + 8]; /* should be enough space for a '%p' */
+        char bf[3 * sizeof(void*) + 8]; /* should be enough space for '%p' */
         void *p = va_arg(argp, void *);
-        int l = lua_pointer2str(buff, sizeof(buff), p);
-        pushstr(L, buff, l);
+        int l = l_sprintf(bf, sizeof(bf), "%p", p);
+        pushed += addstr2buff(L, &buff, bf, l);
         break;
       }
       case 'U': {  /* a 'long' as a UTF-8 sequence */
-        char buff[UTF8BUFFSZ];
-        int l = luaO_utf8esc(buff, va_arg(argp, long));
-        pushstr(L, buff + UTF8BUFFSZ - l, l);
+        char bf[UTF8BUFFSZ];
+        int l = luaO_utf8esc(bf, va_arg(argp, long));
+        pushed += addstr2buff(L, &buff, bf + UTF8BUFFSZ - l, l);
         break;
       }
       case '%': {
-        pushstr(L, "%", 1);
+        pushed += addstr2buff(L, &buff, "%", 1);
         break;
       }
       default: {
@@ -450,15 +519,16 @@
                          *(e + 1));
       }
     }
-    n += 2;
-    if (L->top + 2 > L->stack_last) {  /* no free stack space? */
-      luaV_concat(L, n);
-      n = 1;
+    if (pushed > 1 && L->top + 2 > L->stack_last) {  /* no free stack space? */
+      luaV_concat(L, pushed);  /* join all partial results into one */
+      pushed = 1;
     }
-    fmt = e + 2;
+    fmt = e + 2;  /* skip '%' and the specifier */
   }
-  pushstr(L, fmt, strlen(fmt));
-  if (n > 0) luaV_concat(L, n + 1);
+  pushed += addstr2buff(L, &buff, fmt, strlen(fmt));  /* rest of 'fmt' */
+  clearbuff(L, &buff);  /* empty buffer into the stack */
+  if (pushed > 0)
+    luaV_concat(L, pushed + 1);  /* join all partial results */
   return svalue(s2v(L->top - 1));
 }
 
diff --git a/ltests.c b/ltests.c
index 40de229..7d441d1 100644
--- a/ltests.c
+++ b/ltests.c
@@ -1481,6 +1481,15 @@
     else if EQ("pushvalue") {
       lua_pushvalue(L1, getindex);
     }
+    else if EQ("pushfstringI") {
+      lua_pushfstring(L1, lua_tostring(L, -2), (int)lua_tointeger(L, -1));
+    }
+    else if EQ("pushfstringS") {
+      lua_pushfstring(L1, lua_tostring(L, -2), lua_tostring(L, -1));
+    }
+    else if EQ("pushfstringP") {
+      lua_pushfstring(L1, lua_tostring(L, -2), lua_topointer(L, -1));
+    }
     else if EQ("rawgeti") {
       int t = getindex;
       lua_rawgeti(L1, t, getnum);
diff --git a/luaconf.h b/luaconf.h
index 76a6161..019f2eb 100644
--- a/luaconf.h
+++ b/luaconf.h
@@ -579,13 +579,6 @@
 
 
 /*
-@@ lua_pointer2str converts a pointer to a readable string in a
-** non-specified way.
-*/
-#define lua_pointer2str(buff,sz,p)	l_sprintf(buff,sz,"%p",p)
-
-
-/*
 @@ lua_number2strx converts a float to a hexadecimal numeric string.
 ** In C99, 'sprintf' (with format specifiers '%a'/'%A') does that.
 ** Otherwise, you can leave 'lua_number2strx' undefined and Lua will
diff --git a/testes/strings.lua b/testes/strings.lua
index 8bcbb39..66c1176 100644
--- a/testes/strings.lua
+++ b/testes/strings.lua
@@ -400,5 +400,66 @@
   assert(co() == "2")
 end
 
+
+if T==nil then
+  (Message or print)
+     ("\n >>> testC not active: skipping 'pushfstring' tests <<<\n")
+else
+
+  print"testing 'pushfstring'"
+
+  -- formats %U, %f, %I already tested elsewhere
+
+  local blen = 400    -- internal buffer length in 'luaO_pushfstring'
+
+  local function callpfs (op, fmt, n)
+    local x = {T.testC("pushfstring" .. op .. "; return *", fmt, n)}
+    -- stack has code, 'fmt', 'n', and result from operation
+    assert(#x == 4)  -- make sure nothing else was left in the stack
+    return x[4]
+  end
+
+  local function testpfs (op, fmt, n)
+    assert(callpfs(op, fmt, n) == string.format(fmt, n))
+  end
+
+  testpfs("I", "", 0)
+  testpfs("I", string.rep("a", blen - 1), 0)
+  testpfs("I", string.rep("a", blen), 0)
+  testpfs("I", string.rep("a", blen + 1), 0)
+
+  local str = string.rep("ab", blen) .. "%d" .. string.rep("d", blen / 2)
+  testpfs("I", str, 2^14)
+  testpfs("I", str, -2^15)
+
+  str = "%d" .. string.rep("cd", blen)
+  testpfs("I", str, 2^14)
+  testpfs("I", str, -2^15)
+
+  str = string.rep("c", blen - 2) .. "%d"
+  testpfs("I", str, 2^14)
+  testpfs("I", str, -2^15)
+
+  for l = 12, 14 do
+    local str1 = string.rep("a", l)
+    for i = 0, 500, 13 do
+      for j = 0, 500, 13 do
+        str = string.rep("a", i) .. "%s" .. string.rep("d", j)
+        testpfs("S", str, str1)
+        testpfs("S", str, str)
+      end
+    end
+  end
+
+  str = "abc %c def"
+  testpfs("I", str, string.byte("A"))
+  -- non-printable character
+  assert(callpfs("I", str, 255) == "abc <\\255> def")
+
+  str = string.rep("a", blen - 1) .. "%p" .. string.rep("cd", blen)
+  testpfs("P", str, {})
+end
+
+
 print('OK')