diff --git a/lobject.c b/lobject.c index 67c37124..123f0e57 100644 --- a/lobject.c +++ b/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)); } diff --git a/ltests.c b/ltests.c index 40de2292..7d441d1a 100644 --- 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 index 76a61616..019f2eb6 100644 --- a/luaconf.h +++ b/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. diff --git a/testes/strings.lua b/testes/strings.lua index 8bcbb391..66c1176d 100644 --- 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')