lua

A copy of the Lua development repository
Log | Files | Refs | README

commit 3da34a5fa70a51f0cf06d677a4f07b470693260c
parent 20b161e2859837e4f7fb1c19440ad7efe1588f1f
Author: Roberto Ierusalimschy <roberto@inf.puc-rio.br>
Date:   Wed, 24 Apr 2019 14:00:53 -0300

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.)

Diffstat:
Mlobject.c | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mltests.c | 9+++++++++
Mluaconf.h | 7-------
Mtestes/strings.lua | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 177 insertions(+), 44 deletions(-)

diff --git a/lobject.c b/lobject.c @@ -364,25 +364,44 @@ int luaO_utf8esc (char *buff, unsigned long x) { /* -** 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 @@ static void pushstr (lua_State *L, const char *str, size_t l) { /* +** 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 @@ const char *luaO_pushvfstring (lua_State *L, const char *fmt, va_list argp) { *(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 @@ -1481,6 +1481,15 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { 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 @@ -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 @@ -400,5 +400,66 @@ do 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')