Browse Source

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.)
pull/22/head
Roberto Ierusalimschy 6 years ago
parent
commit
3da34a5fa7
  1. 144
      lobject.c
  2. 9
      ltests.c
  3. 7
      luaconf.h
  4. 61
      testes/strings.lua

144
lobject.c

@ -364,85 +364,154 @@ 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++;
}
/*
** 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));
}

9
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);

7
luaconf.h

@ -578,13 +578,6 @@
#endif
/*
@@ 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.

61
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')

Loading…
Cancel
Save