mirror of
https://github.com/lua/lua
synced 2024-11-25 22:29:39 +03:00
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.)
This commit is contained in:
parent
20b161e285
commit
3da34a5fa7
144
lobject.c
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
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);
|
||||
|
@ -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.
|
||||
|
@ -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…
Reference in New Issue
Block a user