From af91ef07eb2a20fae0ed1fd4f6714020cb790e20 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 17 Mar 2017 13:53:43 +0100 Subject: string.pack --- src/lstrlib.js | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) (limited to 'src') diff --git a/src/lstrlib.js b/src/lstrlib.js index 02feff6..89d1f7f 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -12,6 +12,10 @@ const CT = lua.constant_types; const L_ESC = '%'.charCodeAt(0); +// (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) +const MAXSIZE = Number.MAX_SAFE_INTEGER; + + /* translate a relative string position: negative means back from end */ const posrelat = function(pos, len) { if (pos >= 0) return pos; @@ -350,6 +354,256 @@ const str_format = function(L) { return 1; }; +/* value used for padding */ +const LUAL_PACKPADBYTE = 0x00; + +/* maximum size for the binary representation of an integer */ +const MAXINTSIZE = 16; + +const SZINT = 8; // Size of lua_Integer + +/* number of bits in a character */ +const NB = 8; + +/* mask for one character (NB 1's) */ +const MC = ((1 << NB) - 1) + +/* +** information to pack/unpack stuff +*/ +class Header { + constructor(L) { + this.L = L; + this.islittle = true; + this.maxalign = 1; + } +} + +/* +** options for pack/unpack +*/ +const KOption = { + Kint: 0, /* signed integers */ + Kuint: 1, /* unsigned integers */ + Kfloat: 2, /* floating-point numbers */ + Kchar: 3, /* fixed-length strings */ + Kstring: 4, /* strings with prefixed length */ + Kzstr: 5, /* zero-terminated strings */ + Kpadding: 6, /* padding */ + Kpaddalign: 7, /* padding for alignment */ + Knop: 8 /* no-op (configuration or spaces) */ +}; + +const digit = function(c) { + return '0'.charCodeAt(0) <= c && c <= '9'.charCodeAt(0); +}; + +const getnum = function(fmt, df) { + if (!digit(fmt)) /* no number? */ + return df; /* return default value */ + else { + let a = 0; + do { + a = a * 10 + ((fmt = fmt.slice(1))[0] - '0'.charCodeAt(0)); + } while (digit(fmt[0]) && a <= (MAXSIZE - 9)/10); + return a; + } +}; + +/* +** Read an integer numeral and raises an error if it is larger +** than the maximum size for integers. +*/ +const getnumlimit = function(h, fmt, df) { + let sz = getnum(fmt, df); + if (sz > MAXINTSIZE || sz <= 0) + lauxlib.luaL_error(h.L, `integral size (${sz}) out of limits [1,${MAXINTSIZE}]`); + return sz; +}; + +/* +** Read and classify next option. 'size' is filled with option's size. +*/ +const getoption = function(h, fmt) { + let r = { + opt: NaN, + size: NaN + }; + + r.opt = (fmt = fmt.slice(1))[0]; + r.size = 0; /* default */ + switch (r.opt) { + case 'b': r.size = 1; r.opt = KOption.Kint; return r; // sizeof(char): 1 + case 'B': r.size = 1; r.opt = KOption.Kuint; return r; + case 'h': r.size = 2; r.opt = KOption.Kint; return r; // sizeof(short): 2 + case 'H': r.size = 2; r.opt = KOption.Kuint; return r; + case 'l': r.size = 8; r.opt = KOption.Kint; return r; // sizeof(long): 8 + case 'L': r.size = 8; r.opt = KOption.Kuint; return r; + case 'j': r.size = 8; r.opt = KOption.Kint; return r; // sizeof(lua_Integer): 8 + case 'J': r.size = 8; r.opt = KOption.Kuint; return r; + case 'T': r.size = 8; r.opt = KOption.Kuint; return r; // sizeof(size_t): 8 + case 'f': r.size = 4; r.opt = KOption.Kfloat; return r; // sizeof(float): 4 + case 'd': r.size = 8; r.opt = KOption.Kfloat; return r; // sizeof(double): 8 + case 'n': r.size = 8; r.opt = KOption.Kfloat; return r; // sizeof(lua_Number): 8 + case 'i': r.size = getnumlimit(h, fmt, 4); r.opt = KOption.Kint; return r; // sizeof(int): 4 + case 'I': r.size = getnumlimit(h, fmt, 4); r.opt = KOption.Kuint; return r; + case 's': r.size = getnumlimit(h, fmt, 8); r.opt = KOption.Kstring; return r; + } + + r.opt = KOption.Knop; + return r; +}; + +/* +** Read, classify, and fill other details about the next option. +** 'psize' is filled with option's size, 'notoalign' with its +** alignment requirements. +** Local variable 'size' gets the size to be aligned. (Kpadal option +** always gets its full alignment, other options are limited by +** the maximum alignment ('maxalign'). Kchar option needs no alignment +** despite its size. +*/ +const getdetails = function(h, totalsize, fmt) { + let r = { + opt: NaN, + size: NaN, + ntoalign: NaN + }; + + let opt = getoption(h, fmt); + r.size = opt.size; + r.opt = opt.opt; + let align = r.size; /* usually, alignment follows size */ + if (opt === KOption.Kpaddalign) { /* 'X' gets alignment from following option */ + if (fmt[0] === 0) + lauxlib.luaL_argerror(h.L, 1, "invalid next option for option 'X'"); + else { + let o = getoption(h, fmt); + align = o.size; + o = o.opt; + if (o === KOption.Kchar || align === 0) + lauxlib.luaL_argerror(h.L, 1, "invalid next option for option 'X'"); + } + } + if (align <= 1 || opt === KOption.Kchar) /* need no alignment? */ + r.ntoalign = 0; + else { + if (align > h.maxalign) /* enforce maximum alignment */ + align = h.maxalign; + if ((align & (align -1)) !== 0) /* is 'align' not a power of 2? */ + lauxlib.luaL_argerror(h.L, 1, "format asks for alignment not power of 2"); + r.ntoalign = (align - (totalsize & (align - 1))) & (align - 1); + } + return r; +}; + +/* +** Pack integer 'n' with 'size' bytes and 'islittle' endianness. +** The final 'if' handles the case when 'size' is larger than +** the size of a Lua integer, correcting the extra sign-extension +** bytes if necessary (by default they would be zeros). +*/ +const packint = function(b, n, islittle, size, neg) { + let buff = new Array(size); + + buff[islittle ? 0 : size - 1] = n & MC; /* first byte */ + for (let i = 1; i < size; i++) { + n >>= NB; + buff[islittle ? i : size - 1 - i] = n & MC; + } + if (neg && size > SZINT) { /* negative number need sign extension? */ + for (let i = SZINT; i < size; i++) /* correct extra bytes */ + buff[islittle ? i : size - 1 - i] = MC; + } + b.concat(buff); /* add result to buffer */ +}; + +const packnum = function(b, n, islittle, size) { + let dv = new DataView(new ArrayBuffer(size)); + dv.setFloat64(0, n, islittle); + + for (let i = 0; i < 8; i++) + b.push(dv.getUint8(i, islittle)); +}; + +const str_pack = function(L) { + let b = []; + let h = new Header(); + let fmt = lauxlib.luaL_checkstring(L, 1); /* format string */ + let arg = 1; /* current argument to pack */ + let totalsize = 0; /* accumulate total size of result */ + lapi.lua_pushnil(L); /* mark to separate arguments from string buffer */ + while (fmt.length > 0) { + let details = getdetails(h, totalsize, fmt); + let opt = details.opt; + let size = details.size; + let ntoalign = details.ntoalign; + totalsize += ntoalign + size; + while (ntoalign-- > 0) + b.push(LUAL_PACKPADBYTE); /* fill alignment */ + arg++; + switch (opt) { + case KOption.Kint: { /* signed integers */ + let n = lauxlib.luaL_checkinteger(L, arg); + if (size < SZINT) { /* need overflow check? */ + let lim = 1 << (size * 8) - 1; + lauxlib.luaL_argcheck(L, -lim <= n && n < lim, arg, "integer overflow"); + } + packint(b, n, h.islittle, size, n < 0); + break; + } + case KOption.Kuint: { /* unsigned integers */ + let n = lauxlib.luaL_checkinteger(L, arg); + if (size < SZINT) + lauxlib.luaL_argcheck(L, n < (1 << (size * NB)), arg, "unsigned overflow"); + packint(b, n, h.islittle, size, false); + break; + } + case KOption.Kfloat: { /* floating-point options */ + let n = lauxlib.luaL_checknumber(L, arg); /* get argument */ + packnum(b, n, h.islittle, size); + break; + } + case KOption.Kchar: { /* fixed-size string */ + let s = lauxlib.luaL_checkstring(L, arg); + s = L.stack[lapi.index2addr_(L, arg)].value; + let len = s.value.length; + lauxlib.luaL_argcheck(L, len <= size, arg, "string long than given size"); + b.concat(s.value); /* add string */ + while (len++ < size) /* pad extra space */ + b.push(LUAL_PACKPADBYTE); + break; + } + case KOption.Kstring: { /* strings with length count */ + let s = lauxlib.luaL_checkstring(L, arg); + s = L.stack[lapi.index2addr_(L, arg)].value; + let len = s.value.length; + lauxlib.luaL_argcheck(L, size >= NB || len < (1 << size * NB), arg, "string length does not fit in given size"); + packint(b, len, h.islittle, size, 0); /* pack length */ + b.concat(s.value); + totalsize += len; + break; + } + case KOption.Kzstr: { /* zero-terminated string */ + let s = lauxlib.luaL_checkstring(L, arg); + s = L.stack[lapi.index2addr_(L, arg)].value; + let len = s.value.length; + lauxlib.luaL_argcheck(L, s.value.length === String.fromCharCode(...s.value).length, arg, "strings contains zeros"); + b.concat(s.value); + b.push(0); /* add zero at the end */ + totalsize += len + 1; + break; + } + case KOption.Kpadding: b.push(LUAL_PACKPADBYTE); + case KOption.Kpaddalign: case KOption.Knop: + arg--; /* undo increment */ + break; + } + } + L.stack[L.top++] = new lobject.TValue(CT.LUA_TLNGSTR, b); // We don't want lua > js > lua string conversion here + return 1; +}; + const str_reverse = function(L) { lapi.lua_pushstring(L, lauxlib.luaL_checkstring(L, 1).split("").reverse().join("")); return 1; @@ -401,6 +655,7 @@ const strlib = { "format": str_format, "len": str_len, "lower": str_lower, + "pack": str_pack, "rep": str_rep, "reverse": str_reverse, "sub": str_sub, -- cgit v1.2.3-70-g09d2 From 2db30e01b8c5fb3186b99ed122eb0763b15ee095 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 17 Mar 2017 15:03:11 +0100 Subject: string.unpack --- src/lstrlib.js | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lstrlib.js b/src/lstrlib.js index 89d1f7f..d6c642a 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -366,7 +366,7 @@ const SZINT = 8; // Size of lua_Integer const NB = 8; /* mask for one character (NB 1's) */ -const MC = ((1 << NB) - 1) +const MC = ((1 << NB) - 1); /* ** information to pack/unpack stuff @@ -648,6 +648,104 @@ const str_byte = function(L) { return n; }; +/* +** Unpack an integer with 'size' bytes and 'islittle' endianness. +** If size is smaller than the size of a Lua integer and integer +** is signed, must do sign extension (propagating the sign to the +** higher bits); if size is larger than the size of a Lua integer, +** it must check the unread bytes to see whether they do not cause an +** overflow. +*/ +const unpackint = function(L, str, islittle, size, issigned) { + let res = 0; + let limit = size <= SZINT ? size : SZINT; + for (let i = limit - 1; i >= 0; i--) { + res <<= NB; + res |= str[islittle ? i : size - 1 - i]; + } + if (size < SZINT) { /* real size smaller than lua_Integer? */ + if (issigned) { /* needs sign extension? */ + let mask = 1 << (size * NB - 1); + res = ((res ^ mask) - mask); /* do sign extension */ + } + } else if (size > SZINT) { /* must check unread bytes */ + let mask = issigned || res >= 0 ? 0 : MC; + for (let i = limit; i < size; i++) { + if (str[islittle ? i : size - 1 - i] !== mask) + lauxlib.luaL_error(L, `${size}-byte integer does not fit into Lua Integer`); + } + } + return res; +}; + +const unpacknum = function(L, b, islittle, size) { + assert(b.length >= size); + + let dv = new DataView(new ArrayBuffer(size)); + b.forEach((e, i) => dv.setUint8(i, e, islittle)); + + return dv.getFloat64(0, islittle); +}; + +const str_unpack = function(L) { + let h = new Header(L); + let fmt = lauxlib.luaL_checkstring(L, 1); + let data = lauxlib.luaL_checkstring(L, 2); + data = L.stack[lapi.index2addr_(L, 2)]; + let ld = data.length; + let pos = posrelat(lauxlib.luaL_optinteger(L, 3, 1), ld) - 1; + let n = 0; /* number of results */ + lauxlib.luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); + while (fmt.length > 0) { + let details = getdetails(h, pos, fmt); + let opt = details.opt; + let size = details.size; + let ntoalign = details.ntoalign; + if (ntoalign + size > ~pos || pos + ntoalign + size > ld) + lauxlib.luaL_argerror(L, 2, "data string too short"); + pos += ntoalign; /* skip alignment */ + /* stack space for item + next position */ + lauxlib.luaL_checkstack(L, 2, "too many results"); + n++; + switch (opt) { + case KOption.Kint: + case KOption.Kuint: { + let res = unpackint(L, data.slice(pos), h.islittle, size, opt === KOption.Kint); + lapi.lua_pushinteger(L, res); + break; + } + case KOption.Kfloat: { + let res = unpacknum(L, data.slice(pos), h.islittle, size); + lapi.lua_pushnumber(L, res); + break; + } + case KOption.Kchar: { + lapi.lua_pushstring(L, data.slice(pos, pos + size)); + break; + } + case KOption.Kstring: { + let len = unpackint(L, data.slice(pos), h.islittle, size, 0); + lauxlib.luaL_argcheck(L, pos + len + size <= ld, 2, "data string too short"); + lapi.lua_pushstring(L, data.slice(pos + size, pos + size + len)); + pos += len; /* skip string */ + break; + } + case KOption.Kzstr: { + let len = data.slice(pos).indexOf(0); + lapi.lua_pushstring(L, data.slice(pos, pos + len)); + pos += len + 1; /* skip string plus final '\0' */ + break; + } + case KOption.Kpaddalign: case KOption.Kpadding: case KOption.Knop: + n--; /* undo increment */ + break; + } + pos += size; + } + lapi.lua_pushinteger(L, pos + 1); /* next position */ + return n + 1; +}; + const strlib = { "byte": str_byte, "char": str_char, @@ -659,6 +757,7 @@ const strlib = { "rep": str_rep, "reverse": str_reverse, "sub": str_sub, + "unpack": str_unpack, "upper": str_upper }; -- cgit v1.2.3-70-g09d2 From aea4af6bafafd0aa166e41ead5ce90b530e3ac0a Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 17 Mar 2017 15:08:28 +0100 Subject: string.packsize --- src/lstrlib.js | 70 ++++++++++++++++++++++++++++++++++++++++++++------------ tests/lstrlib.js | 34 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/lstrlib.js b/src/lstrlib.js index d6c642a..030e063 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -368,6 +368,8 @@ const NB = 8; /* mask for one character (NB 1's) */ const MC = ((1 << NB) - 1); +const MAXALIGN = 8; + /* ** information to pack/unpack stuff */ @@ -448,6 +450,22 @@ const getoption = function(h, fmt) { case 'i': r.size = getnumlimit(h, fmt, 4); r.opt = KOption.Kint; return r; // sizeof(int): 4 case 'I': r.size = getnumlimit(h, fmt, 4); r.opt = KOption.Kuint; return r; case 's': r.size = getnumlimit(h, fmt, 8); r.opt = KOption.Kstring; return r; + case 'c': { + r.size = getnum(fmt, -1); + if (r.size === -1) + lauxlib.luaL_error(h.L, "missing size for format option 'c'"); + r.opt = KOption.Kchar; + return r; + } + case 'z': r.opt = KOption.Kzstr; return r; + case 'x': r.size = 1; r.opt = KOption.Kpadding; return r; + case 'X': r.opt = KOption.Kpaddalign; return r; + case ' ': break; + case '<': h.islittle = true; break; + case '>': h.islittle = false; break; + case '=': h.islittle = true; break; + case '!': h.maxalign = getnumlimit(h, fmt, MAXALIGN); break; + default: lauxlib.luaL_error(h.L, `invalid format option '${r.opt}'`); } r.opt = KOption.Knop; @@ -529,7 +547,7 @@ const packnum = function(b, n, islittle, size) { const str_pack = function(L) { let b = []; let h = new Header(); - let fmt = lauxlib.luaL_checkstring(L, 1); /* format string */ + let fmt = lauxlib.luaL_checkstring(L, 1).split(''); /* format string */ let arg = 1; /* current argument to pack */ let totalsize = 0; /* accumulate total size of result */ lapi.lua_pushnil(L); /* mark to separate arguments from string buffer */ @@ -648,6 +666,29 @@ const str_byte = function(L) { return n; }; +const str_packsize = function(L) { + let h = new Header(L); + let fmt = lauxlib.luaL_checkstring(L, 1).split(''); + let totalsize = 0; /* accumulate total size of result */ + while (fmt.length > 0) { + let details = getdetails(h, totalsize, fmt); + let opt = details.opt; + let size = details.size; + let ntoalign = details.ntoalign; + size += ntoalign; /* total space used by option */ + lauxlib.luaL_argcheck(L, totalsize <= MAXSIZE - size - 1, "format result too large"); + totalsize += size; + switch (opt) { + case KOption.Kstring: /* strings with length count */ + case KOption.Kzstr: /* zero-terminated string */ + lauxlib.luaL_argerror(L, 1, "variable-length format"); + default: break; + } + } + lapi.lua_pushinteger(L, totalsize); + return 1; +}; + /* ** Unpack an integer with 'size' bytes and 'islittle' endianness. ** If size is smaller than the size of a Lua integer and integer @@ -689,7 +730,7 @@ const unpacknum = function(L, b, islittle, size) { const str_unpack = function(L) { let h = new Header(L); - let fmt = lauxlib.luaL_checkstring(L, 1); + let fmt = lauxlib.luaL_checkstring(L, 1).split(''); let data = lauxlib.luaL_checkstring(L, 2); data = L.stack[lapi.index2addr_(L, 2)]; let ld = data.length; @@ -747,18 +788,19 @@ const str_unpack = function(L) { }; const strlib = { - "byte": str_byte, - "char": str_char, - "dump": str_dump, - "format": str_format, - "len": str_len, - "lower": str_lower, - "pack": str_pack, - "rep": str_rep, - "reverse": str_reverse, - "sub": str_sub, - "unpack": str_unpack, - "upper": str_upper + "byte": str_byte, + "char": str_char, + "dump": str_dump, + "format": str_format, + "len": str_len, + "lower": str_lower, + "pack": str_pack, + "packsize": str_packsize, + "rep": str_rep, + "reverse": str_reverse, + "sub": str_sub, + "unpack": str_unpack, + "upper": str_upper }; const createmetatable = function(L) { diff --git a/tests/lstrlib.js b/tests/lstrlib.js index 23cd786..4185f04 100644 --- a/tests/lstrlib.js +++ b/tests/lstrlib.js @@ -459,4 +459,38 @@ test('string.dump', function (t) { "hello1212.5", "Correct element(s) on the stack" ); +}); + + +test('string.pack/unpack/packsize', function (t) { + let luaCode = ` + local s1, n, s2 = "hello", 2, "you" + local packed = string.pack("zjz", s1, n, s2) + local us1, un, us2 = string.unpack("zjz", packed) + return s1 == us1 and n == un and s2 == us2 + `, L; + + t.plan(3); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -1), + "FFFFFFF", + "Correct element(s) on the stack" + ); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 6d684333cbbf92ce940b0f10b126197bb2e04b93 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Fri, 17 Mar 2017 16:06:10 +0100 Subject: string.pack/unpack/packsize test --- sizes | Bin 8656 -> 0 bytes sizes.c | 9 -------- src/lstrlib.js | 64 ++++++++++++++++++++++++++++++++++++------------------- tests/lstrlib.js | 17 +++++++++------ 4 files changed, 53 insertions(+), 37 deletions(-) delete mode 100755 sizes delete mode 100644 sizes.c (limited to 'src') diff --git a/sizes b/sizes deleted file mode 100755 index eabd657..0000000 Binary files a/sizes and /dev/null differ diff --git a/sizes.c b/sizes.c deleted file mode 100644 index 647c169..0000000 --- a/sizes.c +++ /dev/null @@ -1,9 +0,0 @@ -#include - -int main(void) { - - printf("sizeof(char): %lu\nsizeof(short): %lu\nsizeof(long): %lu\nsizeof(size_t): %lu\nsizeof(float): %lu\nsizeof(double): %lu\nsizeof(int): %lu\n", - sizeof(char), sizeof(short), sizeof(long), sizeof(size_t), sizeof(float), sizeof(double), sizeof(int)); - - return 0; -} \ No newline at end of file diff --git a/src/lstrlib.js b/src/lstrlib.js index 030e063..56fcc11 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -397,17 +397,18 @@ const KOption = { }; const digit = function(c) { - return '0'.charCodeAt(0) <= c && c <= '9'.charCodeAt(0); + return '0'.charCodeAt(0) <= c.charCodeAt(0) && c.charCodeAt(0) <= '9'.charCodeAt(0); }; const getnum = function(fmt, df) { - if (!digit(fmt)) /* no number? */ + if (!digit(fmt.s[0])) /* no number? */ return df; /* return default value */ else { let a = 0; do { - a = a * 10 + ((fmt = fmt.slice(1))[0] - '0'.charCodeAt(0)); - } while (digit(fmt[0]) && a <= (MAXSIZE - 9)/10); + a = a * 10 + (fmt.s[0].charCodeAt(0) - '0'.charCodeAt(0)); + fmt.s = fmt.s.slice(1); + } while (digit(fmt.s[0]) && a <= (MAXSIZE - 9)/10); return a; } }; @@ -432,7 +433,8 @@ const getoption = function(h, fmt) { size: NaN }; - r.opt = (fmt = fmt.slice(1))[0]; + r.opt = fmt.s[0]; + fmt.s = fmt.s.slice(1); r.size = 0; /* default */ switch (r.opt) { case 'b': r.size = 1; r.opt = KOption.Kint; return r; // sizeof(char): 1 @@ -492,8 +494,8 @@ const getdetails = function(h, totalsize, fmt) { r.size = opt.size; r.opt = opt.opt; let align = r.size; /* usually, alignment follows size */ - if (opt === KOption.Kpaddalign) { /* 'X' gets alignment from following option */ - if (fmt[0] === 0) + if (r.opt === KOption.Kpaddalign) { /* 'X' gets alignment from following option */ + if (fmt.s[0] === 0) lauxlib.luaL_argerror(h.L, 1, "invalid next option for option 'X'"); else { let o = getoption(h, fmt); @@ -503,7 +505,7 @@ const getdetails = function(h, totalsize, fmt) { lauxlib.luaL_argerror(h.L, 1, "invalid next option for option 'X'"); } } - if (align <= 1 || opt === KOption.Kchar) /* need no alignment? */ + if (align <= 1 || r.opt === KOption.Kchar) /* need no alignment? */ r.ntoalign = 0; else { if (align > h.maxalign) /* enforce maximum alignment */ @@ -533,7 +535,7 @@ const packint = function(b, n, islittle, size, neg) { for (let i = SZINT; i < size; i++) /* correct extra bytes */ buff[islittle ? i : size - 1 - i] = MC; } - b.concat(buff); /* add result to buffer */ + b.push(...buff); /* add result to buffer */ }; const packnum = function(b, n, islittle, size) { @@ -546,12 +548,17 @@ const packnum = function(b, n, islittle, size) { const str_pack = function(L) { let b = []; - let h = new Header(); + let h = new Header(L); let fmt = lauxlib.luaL_checkstring(L, 1).split(''); /* format string */ + fmt.push('\0'); // Add \0 to avoid overflow + fmt = { + s: fmt, + off: 0 + }; let arg = 1; /* current argument to pack */ let totalsize = 0; /* accumulate total size of result */ lapi.lua_pushnil(L); /* mark to separate arguments from string buffer */ - while (fmt.length > 0) { + while (fmt.s.length - 1 > 0) { let details = getdetails(h, totalsize, fmt); let opt = details.opt; let size = details.size; @@ -584,10 +591,10 @@ const str_pack = function(L) { } case KOption.Kchar: { /* fixed-size string */ let s = lauxlib.luaL_checkstring(L, arg); - s = L.stack[lapi.index2addr_(L, arg)].value; + s = L.stack[lapi.index2addr_(L, arg)]; let len = s.value.length; lauxlib.luaL_argcheck(L, len <= size, arg, "string long than given size"); - b.concat(s.value); /* add string */ + b.push(...s.value); /* add string */ while (len++ < size) /* pad extra space */ b.push(LUAL_PACKPADBYTE); break; @@ -598,7 +605,7 @@ const str_pack = function(L) { let len = s.value.length; lauxlib.luaL_argcheck(L, size >= NB || len < (1 << size * NB), arg, "string length does not fit in given size"); packint(b, len, h.islittle, size, 0); /* pack length */ - b.concat(s.value); + b.push(...s.value); totalsize += len; break; } @@ -607,7 +614,7 @@ const str_pack = function(L) { s = L.stack[lapi.index2addr_(L, arg)].value; let len = s.value.length; lauxlib.luaL_argcheck(L, s.value.length === String.fromCharCode(...s.value).length, arg, "strings contains zeros"); - b.concat(s.value); + b.push(...s.value); b.push(0); /* add zero at the end */ totalsize += len + 1; break; @@ -669,8 +676,13 @@ const str_byte = function(L) { const str_packsize = function(L) { let h = new Header(L); let fmt = lauxlib.luaL_checkstring(L, 1).split(''); + fmt.push('\0'); // Add \0 to avoid overflow + fmt = { + s: fmt, + off: 0 + }; let totalsize = 0; /* accumulate total size of result */ - while (fmt.length > 0) { + while (fmt.s.length - 1 > 0) { let details = getdetails(h, totalsize, fmt); let opt = details.opt; let size = details.size; @@ -731,18 +743,23 @@ const unpacknum = function(L, b, islittle, size) { const str_unpack = function(L) { let h = new Header(L); let fmt = lauxlib.luaL_checkstring(L, 1).split(''); + fmt.push('\0'); // Add \0 to avoid overflow + fmt = { + s: fmt, + off: 0 + }; let data = lauxlib.luaL_checkstring(L, 2); - data = L.stack[lapi.index2addr_(L, 2)]; + data = L.stack[lapi.index2addr_(L, 2)].value; let ld = data.length; let pos = posrelat(lauxlib.luaL_optinteger(L, 3, 1), ld) - 1; let n = 0; /* number of results */ lauxlib.luaL_argcheck(L, pos <= ld, 3, "initial position out of string"); - while (fmt.length > 0) { + while (fmt.s.length - 1 > 0) { let details = getdetails(h, pos, fmt); let opt = details.opt; let size = details.size; let ntoalign = details.ntoalign; - if (ntoalign + size > ~pos || pos + ntoalign + size > ld) + if (/*ntoalign + size > ~pos ||*/ pos + ntoalign + size > ld) lauxlib.luaL_argerror(L, 2, "data string too short"); pos += ntoalign; /* skip alignment */ /* stack space for item + next position */ @@ -761,19 +778,22 @@ const str_unpack = function(L) { break; } case KOption.Kchar: { - lapi.lua_pushstring(L, data.slice(pos, pos + size)); + // lapi.lua_pushstring(L, data.slice(pos, pos + size)); + L.stack[L.top++] = new lobject.TValue(CT.LUA_TLNGSTR, data.slice(pos, pos + size)); break; } case KOption.Kstring: { let len = unpackint(L, data.slice(pos), h.islittle, size, 0); lauxlib.luaL_argcheck(L, pos + len + size <= ld, 2, "data string too short"); - lapi.lua_pushstring(L, data.slice(pos + size, pos + size + len)); + // lapi.lua_pushstring(L, data.slice(pos + size, pos + size + len)); + L.stack[L.top++] = new lobject.TValue(CT.LUA_TLNGSTR, data.slice(pos + size, pos + size + len)); pos += len; /* skip string */ break; } case KOption.Kzstr: { let len = data.slice(pos).indexOf(0); - lapi.lua_pushstring(L, data.slice(pos, pos + len)); + // lapi.lua_pushstring(L, data.slice(pos, pos + len)); + L.stack[L.top++] = new lobject.TValue(CT.LUA_TLNGSTR, data.slice(pos, pos + len)); pos += len + 1; /* skip string plus final '\0' */ break; } diff --git a/tests/lstrlib.js b/tests/lstrlib.js index 4185f04..6364c7f 100644 --- a/tests/lstrlib.js +++ b/tests/lstrlib.js @@ -465,12 +465,12 @@ test('string.dump', function (t) { test('string.pack/unpack/packsize', function (t) { let luaCode = ` local s1, n, s2 = "hello", 2, "you" - local packed = string.pack("zjz", s1, n, s2) - local us1, un, us2 = string.unpack("zjz", packed) - return s1 == us1 and n == un and s2 == us2 + local packed = string.pack("c5jc3", s1, n, s2) + local us1, un, us2 = string.unpack("c5jc3", packed) + return string.packsize("c5jc3"), s1 == us1 and n == un and s2 == us2 `, L; - t.plan(3); + t.plan(4); t.doesNotThrow(function () { @@ -489,8 +489,13 @@ test('string.pack/unpack/packsize', function (t) { }, "Lua program ran without error"); t.strictEqual( - lapi.lua_tostring(L, -1), - "FFFFFFF", + lapi.lua_tointeger(L, -2), + 16, + "Correct element(s) on the stack" + ); + + t.ok( + lapi.lua_toboolean(L, -1), "Correct element(s) on the stack" ); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 16612bb93644e83afde482b75725fe1ce902d02a Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 20 Mar 2017 08:25:04 +0100 Subject: string.match, string.find --- src/lstrlib.js | 400 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 399 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/lstrlib.js b/src/lstrlib.js index 56fcc11..64870d6 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -15,6 +15,12 @@ const L_ESC = '%'.charCodeAt(0); // (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) const MAXSIZE = Number.MAX_SAFE_INTEGER; +/* +** maximum number of captures that a pattern can do during +** pattern-matching. This limit is arbitrary, but must fit in +** an unsigned char. +*/ +const LUA_MAXCAPTURES = 32; /* translate a relative string position: negative means back from end */ const posrelat = function(pos, len) { @@ -165,9 +171,17 @@ const FLAGS = ["-", "+", " ", "#", "0"].map(e => e.charCodeAt(0)); */ const MAX_FORMAT = 32; +// TODO: locale ? +const isalpha = e => /^[a-zA-Z]$/.test(e.charAt(0)); const isdigit = e => "0".charCodeAt(0) <= e && e <= "9".charCodeAt(0); - const iscntrl = e => (0x00 <= e && e <= 0x1f) || e === 0x7f; +const isgraph = e => e.charCodeAt(0) > 32 && e.charCodeAt < 127; // TODO: Will only work for ASCII +const islower = e => /^(?![A-Z]).*$/.test(e.charAt(0)); +const isupper = e => /^(?![a-z]).*$/.test(e.charAt(0)); +const isalnum = e => /^[a-zA-Z0-9]$/.test(e.charAt(0)); +const ispunct = e => isgraph(e) && !isalnum(e); +const isspace = e => /^\s$/.test(e.charAt(0)); +const isxdigit = e => /^[0-9A-Fa-f]$/.test(e.charAt(0)); const addquoted = function(b, s) { lauxlib.luaL_addchar(b, '"'); @@ -807,13 +821,397 @@ const str_unpack = function(L) { return n + 1; }; +const CAP_UNFINISHED = -1; +const CAP_POSITION = -2; +const MAXCCALLS = 200; +const SPECIALS = ["^", "$", "*", "+", "?", ".", "(", "[", "%", "-"]; + +class MatchState { + constructor(L) { + this.src = null; /* unmodified source string */ + this.src_init = null; /* init of source string */ + this.src_end = null; /* end ('\0') of source string */ + this.p = null; /* unmodified pattern string */ + this.p_end = null; /* end ('\0') of pattern */ + this.L = L; + this.matchdepth = NaN; /* control for recursive depth */ + this.level = NaN; /* total number of captures (finished or unfinished) */ + this.captures = []; + } +} + +const classend = function(ms, p) { + switch(ms.p.charAt(p++)) { + case L_ESC: { + if (p === ms.p_end) + lauxlib.luaL_error(ms.L, "malformed pattern (ends with '%')"); + return p + 1; + } + case '[': { + if (ms.p.charAt(p) === '^') p++; + do { /* look for a ']' */ + if (p === ms.p_end) + lauxlib.luaL_error(ms.L, "malformed pattern (missing ']')"); + if (ms.p.charAt(p++) === L_ESC && p < ms.p_end) + p++; /* skip escapes (e.g. '%]') */ + } while (ms.p.charAt(p) !== ']'); + return p + 1; + } + default: { + return p; + } + } +}; + +const match_class = function(ms, c, cl) { + let res; + switch (cl.toLowerCase()) { + case 'a' : res = isalpha(c); break; + case 'c' : res = iscntrl(c); break; + case 'd' : res = isdigit(c); break; + case 'g' : res = isgraph(c); break; + case 'l' : res = islower(c); break; + case 'p' : res = ispunct(c); break; + case 's' : res = isspace(c); break; + case 'u' : res = isupper(c); break; + case 'w' : res = isalnum(c); break; + case 'x' : res = isxdigit(c); break; + case 'z' : res = (c.charCodeAt(0) === 0); break; /* deprecated option */ + default: return (cl === c); + } + return (islower(cl) ? res : !res); +}; + +const matchbracketclass = function(ms, c, p, ec) { + let sig = true; + if (ms.p.charAt(p + 1) === '^') { + sig = false; + p++; /* skip the '^' */ + } + while (++p < ec) { + if (ms.p.charAt(p) === L_ESC) { + p++; + if (match_class(c, ms.p.charAt(p))) + return sig; + } else if (ms.p.charAt(p + 1) === '-' && p + 2 < ec) { + p += 2; + if (ms.p.charAt(p - 2) <= c.charCodeAt(0) && c.charCodeAt(0) <= ms.p.charAt(p)) + return sig; + } else if (ms.p.charAt(p) === c) return sig; + } + return !sig; +}; + +const singlematch = function(ms, s, p, ep) { + if (s >= ms.src_end) + return false; + else { + let c = ms.src.charAt(s); + switch (ms.p.charAt(p)) { + case '.': return true; /* matches any char */ + case L_ESC: return match_class(c, ms.p.charAt(p + 1)); + case '[': return matchbracketclass(ms, c, p, ep - 1); + default: return ms.p.charAt(p) === c; + } + } +}; + +const matchbalance = function(ms, s, p) { + if (p >= ms.p_end - 1) + lauxlib.luaL_error(ms.L, "malformed pattern (missing arguments to '%b'"); + if (ms.src.charAt(s) !== ms.p.charAt(p)) + return null; + else { + let b = ms.p.charAt(p); + let e = ms.p.charAt(p + 1); + let cont = 1; + while (++s < ms.src_end) { + if (ms.src.charAt(s) === e) { + if (--cont === 0) return s + 1; + } + else if (s === b) cont++; + } + } + return null; /* string ends out of balance */ +}; + +const max_expand = function(ms, s, p, ep) { + let i = 0; /* counts maximum expand for item */ + while (singlematch(ms, s + i, p, ep)) + i++; + /* keeps trying to match with the maximum repetitions */ + while (i >= 0) { + let res = match(ms, s + i, ep + 1); + if (res) return res; + i--; /* else didn't match; reduce 1 repetition to try again */ + } + return null; +}; + +const min_expand = function(ms, s, p, ep) { + for (;;) { + let res = match(ms, s, ep + 1); + if (res !== null) + return res; + else if (singlematch(ms, s, p, ep)) + s++; /* try with one more repetition */ + else return null; + } +}; + +const start_capture = function(ms, s, p, what) { + let level = ms.level; + if (level >= LUA_MAXCAPTURES) lauxlib.luaL_error(ms.L, "too many captures"); + ms.capture[level].init = s; + ms.capture[level].len = what; + ms.level = level + 1; + let res; + if ((res = match(ms, s, p)) === null) /* match failed? */ + ms.level--; /* undo capture */ + return res; +}; + +const end_capture = function(ms, s, p) { + let l = capture_to_close(ms); + ms.capture[l].len = s - ms.capture[l].init; /* close capture */ + let res; + if ((res = match(ms, s, p)) === null) /* match failed? */ + ms.capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +}; + +const match_capture = function(ms, s, l) { + l = check_capture(ms, l); + let len = ms.capture[l].len; + if (ms.src_end >= len && ms.src.slice(ms.capture[l].init, ms.capture[l].init + len) === ms.src.slice(s, s + len)) + return s+len; + else return null; +}; + +const match = function(ms, s, p) { + let gotodefault = false; + let gotoinit = true; + + if (ms.matchdepth-- === 0) + lauxlib.luaL_error(ms.L, "pattern too complex"); + + while (gotoinit || gotodefault) { + gotoinit = false; + if (p !== ms.p_end) { /* end of pattern? */ + switch (gotodefault ? 'x' : ms.p.charAt(p)) { + case '(': { /* start capture */ + if (ms.p.charAt(p + 1) === ')') /* position capture? */ + s = start_capture(ms, s, 2, CAP_POSITION); + else + s = start_capture(ms, s, 1, CAP_UNFINISHED); + break; + } + case ')': { /* end capture */ + s = end_capture(ms, s, 1); + break; + } + case '$': { + if (p + 1 !== ms.p_end) { /* is the '$' the last char in pattern? */ + gotodefault = true; /* no; go to default */ + break; + } + s = ms.src.slice(s).length === 0 ? s : null; /* check end of string */ + break; + } + case L_ESC: { /* escaped sequences not in the format class[*+?-]? */ + switch (ms.p.charAt(p + 1)) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, p + 2); + if (s !== null) { + p = p.slice(4); + gotoinit = true; + } + break; + } + case 'f': { + p += 2; + if (ms.p.charAt(p) !== '[') + lauxlib.luaL_error(ms.L, `missing '[' after '%f' in pattern`); + let ep = classend(ms, p); /* points to what is next */ + let previous = s === ms.src_init ? '\0' : ms.s.charAt(s - 1); + if (!matchbracketclass(ms, previous, p, ep - 1) && matchbracketclass(ms, ms.src.charAt(s), p, ep - 1)) { + p = ep; gotoinit = true; break; + } + s = null; /* match failed */ + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': { /* capture results (%0-%9)? */ + s = match_capture(ms, s, ms.p.charAt(p + 1)); + if (s !== null) { + p += 2; gotoinit = true; + } + break; + } + default: gotodefault = true; + } + break; + } + default: { /* pattern class plus optional suffix */ + gotodefault = false; + let ep = classend(ms, p); /* points to optional suffix */ + /* does not match at least once? */ + if (!singlematch(ms, s, p, ep)) { + if (ep.charAt(0) === '*' || ep.charAt(0) === '?' || ep.charAt(0) === '-') { /* accept empty? */ + p = ep + 1; gotoinit = true; break; + } else /* '+' or no suffix */ + s = null; /* fail */ + } else { /* matched once */ + switch (ms.p.charAt(ep)) { /* handle optional suffix */ + case '?': { /* optional */ + let res; + if ((res = match(ms, s + 1, ep + 1)) !== null) + s = res; + else { + p = ep + 1; gotoinit = true; + } + break; + } + case '+': /* 1 or more repetitions */ + s++; /* 1 match already done */ + case '*': /* 0 or more repetitions */ + s = max_expand(ms, s, p, ep); + break; + case '-': /* 0 or more repetitions (minimum) */ + s = min_expand(ms, s, p, ep); + break; + default: /* no suffix */ + s++; p = ep; gotoinit = true; + } + } + break; + } + } + } + } + ms.matchdepth++; + return s; +}; + +const push_onecatpure = function(ms, i, s, e) { + if (i >= ms.level) { + if (i === 0) + lapi.lua_pushlstring(ms.L, s, e); /* add whole match */ + else + lauxlib.luaL_error(ms.L, `invalid capture index %${i + 1}`); + } else { + let l = ms.capture[i].len; + if (l === CAP_UNFINISHED) lauxlib.luaL_error(ms.L, "unfinished capture"); + if (l === CAP_POSITION) + lapi.lua_pushinteger(ms.L, ms.src_init + 1); + else + lapi.lua_pushlstring(ms.L, ms.capture[i].init, l); + } +}; + +const push_captures = function(ms, s, e) { + let nlevels = ms.level === 0 && s ? 1 : ms.level; + lauxlib.luaL_checkstack(ms.L, nlevels, "too many catpures"); + for (let i = 0; i < nlevels; i++) + push_onecatpure(ms, i, s, e); + return nlevels; /* number of strings pushed */ +}; + +const nospecials = function(p, l) { + let upto = 0; + do { + let special = false; + let supto = p.slice(upto); + for (let i = 0; i < SPECIALS.length; i++) { + if (supto.indexOf(SPECIALS[i]) > -1) { + special = true; + break; + } + } + + if (special) + return false; /* pattern has a special character */ + upto = upto + 1; /* may have more after \0 */ + } while (upto <= l); + return true; /* no special chars found */ +}; + +const prepstate = function(ms, L, s, ls, p, lp) { + ms.L = L; + ms.matchdepth = MAXCCALLS; + ms.src = s; + ms.src_init = 0; + ms.src_end = ls; + ms.p = p; + ms.p_end = lp; +}; + +const reprepstate = function(ms) { + ms.level = 0; + assert(ms.matchdepth === MAXCCALLS); +}; + +const str_find_aux = function(L, find) { + let s = lauxlib.luaL_checkstring(L, 1); + let p = lauxlib.luaL_checkstring(L, 2); + let ls = s.length; + let lp = p.length; + let init = posrelat(lauxlib.luaL_optinteger(L, 3, 1), ls); + if (init < 1) init = 1; + else if (init > ls + 1) { /* start after string's end? */ + lauxlib.lua_pushnil(L); /* cannot find anything */ + return 1; + } + /* explicit request or no special characters? */ + if (find && (lapi.lua_toboolean(L, 4) || nospecials(p, lp))) { + /* do a plain search */ + let f = s.indexOf(p); + if (f > -1) { + lapi.lua_pushinteger(L, f); + lapi.lua_pushinteger(L, f + lp); + return 2; + } + } else { + let ms = new MatchState(L); + let s1 = init - 1; + let anchor = s.charAt(0) === '^'; + if (anchor) { + p = p.slice(1); lp--; /* skip anchor character */ + } + prepstate(ms, L, s, ls, p, lp); + do { + let res; + reprepstate(ms); + if ((res = match(ms, s1, 0)) !== null) { + if (find) { + lapi.lua_pushinteger(L, s1); /* start */ + lapi.lua_pushinteger(L, res); /* end */ + return push_captures(ms, null, 0) + 2; + } else + return push_captures(ms, s1, res); + } + } while (s1++ < ms.src_end && !anchor); + } +}; + +const str_find = function(L) { + return str_find_aux(L, 1); +}; + +const str_match = function(L) { + return str_find_aux(L, 1); +}; + const strlib = { "byte": str_byte, "char": str_char, "dump": str_dump, + "find": str_find, "format": str_format, "len": str_len, "lower": str_lower, + "match": str_match, "pack": str_pack, "packsize": str_packsize, "rep": str_rep, -- cgit v1.2.3-70-g09d2 From 645437b8e0846635f886520bb679a79ec1849c09 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 20 Mar 2017 14:21:21 +0100 Subject: string.match/find test --- src/lstrlib.js | 52 +++++++++++++++-------- tests/lstrlib.js | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/lstrlib.js b/src/lstrlib.js index 64870d6..cedb403 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -10,7 +10,8 @@ const lua = require('./lua.js'); const luaconf = require('./luaconf.js'); const CT = lua.constant_types; -const L_ESC = '%'.charCodeAt(0); +const sL_ESC = '%'; +const L_ESC = sL_ESC.charCodeAt(0); // (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX)) const MAXSIZE = Number.MAX_SAFE_INTEGER; @@ -836,13 +837,27 @@ class MatchState { this.L = L; this.matchdepth = NaN; /* control for recursive depth */ this.level = NaN; /* total number of captures (finished or unfinished) */ - this.captures = []; + this.capture = []; } } +const check_capture = function(ms, l) { + l = String.fromCharCode(l - '1'.charCodeAt(0)); + if (l < 0 || l >= ms.level || ms.capture[l].len === CAP_UNFINISHED) + return lauxlib.luaL_error(ms.L, `invalid capture index %${l + 1}`); + return l; +}; + +const capture_to_close = function(ms) { + let level = ms.level; + for (level--; level >= 0; level--) + if (ms.capture[level].len === CAP_UNFINISHED) return level; + return lauxlib.luaL_error(ms.L, "invalid pattern capture"); +}; + const classend = function(ms, p) { switch(ms.p.charAt(p++)) { - case L_ESC: { + case sL_ESC: { if (p === ms.p_end) lauxlib.luaL_error(ms.L, "malformed pattern (ends with '%')"); return p + 1; @@ -863,12 +878,12 @@ const classend = function(ms, p) { } }; -const match_class = function(ms, c, cl) { +const match_class = function(c, cl) { let res; switch (cl.toLowerCase()) { case 'a' : res = isalpha(c); break; case 'c' : res = iscntrl(c); break; - case 'd' : res = isdigit(c); break; + case 'd' : res = isdigit(c.charCodeAt(0)); break; case 'g' : res = isgraph(c); break; case 'l' : res = islower(c); break; case 'p' : res = ispunct(c); break; @@ -909,7 +924,7 @@ const singlematch = function(ms, s, p, ep) { let c = ms.src.charAt(s); switch (ms.p.charAt(p)) { case '.': return true; /* matches any char */ - case L_ESC: return match_class(c, ms.p.charAt(p + 1)); + case sL_ESC: return match_class(c, ms.p.charAt(p + 1)); case '[': return matchbracketclass(ms, c, p, ep - 1); default: return ms.p.charAt(p) === c; } @@ -962,6 +977,7 @@ const min_expand = function(ms, s, p, ep) { const start_capture = function(ms, s, p, what) { let level = ms.level; if (level >= LUA_MAXCAPTURES) lauxlib.luaL_error(ms.L, "too many captures"); + ms.capture[level] = ms.capture[level] ? ms.capture[level] : {}; ms.capture[level].init = s; ms.capture[level].len = what; ms.level = level + 1; @@ -1001,13 +1017,13 @@ const match = function(ms, s, p) { switch (gotodefault ? 'x' : ms.p.charAt(p)) { case '(': { /* start capture */ if (ms.p.charAt(p + 1) === ')') /* position capture? */ - s = start_capture(ms, s, 2, CAP_POSITION); + s = start_capture(ms, s, p + 2, CAP_POSITION); else - s = start_capture(ms, s, 1, CAP_UNFINISHED); + s = start_capture(ms, s, p + 1, CAP_UNFINISHED); break; } case ')': { /* end capture */ - s = end_capture(ms, s, 1); + s = end_capture(ms, s, p + 1); break; } case '$': { @@ -1018,7 +1034,7 @@ const match = function(ms, s, p) { s = ms.src.slice(s).length === 0 ? s : null; /* check end of string */ break; } - case L_ESC: { /* escaped sequences not in the format class[*+?-]? */ + case sL_ESC: { /* escaped sequences not in the format class[*+?-]? */ switch (ms.p.charAt(p + 1)) { case 'b': { /* balanced string? */ s = matchbalance(ms, s, p + 2); @@ -1058,7 +1074,7 @@ const match = function(ms, s, p) { let ep = classend(ms, p); /* points to optional suffix */ /* does not match at least once? */ if (!singlematch(ms, s, p, ep)) { - if (ep.charAt(0) === '*' || ep.charAt(0) === '?' || ep.charAt(0) === '-') { /* accept empty? */ + if (ms.p.charAt(ep) === '*' || ms.p.charAt(ep) === '?' || ms.p.charAt(ep) === '-') { /* accept empty? */ p = ep + 1; gotoinit = true; break; } else /* '+' or no suffix */ s = null; /* fail */ @@ -1094,7 +1110,7 @@ const match = function(ms, s, p) { return s; }; -const push_onecatpure = function(ms, i, s, e) { +const push_onecapture = function(ms, i, s, e) { if (i >= ms.level) { if (i === 0) lapi.lua_pushlstring(ms.L, s, e); /* add whole match */ @@ -1106,7 +1122,7 @@ const push_onecatpure = function(ms, i, s, e) { if (l === CAP_POSITION) lapi.lua_pushinteger(ms.L, ms.src_init + 1); else - lapi.lua_pushlstring(ms.L, ms.capture[i].init, l); + lapi.lua_pushlstring(ms.L, ms.src.slice(ms.capture[i].init), l); } }; @@ -1114,7 +1130,7 @@ const push_captures = function(ms, s, e) { let nlevels = ms.level === 0 && s ? 1 : ms.level; lauxlib.luaL_checkstack(ms.L, nlevels, "too many catpures"); for (let i = 0; i < nlevels; i++) - push_onecatpure(ms, i, s, e); + push_onecapture(ms, i, s, e); return nlevels; /* number of strings pushed */ }; @@ -1168,7 +1184,7 @@ const str_find_aux = function(L, find) { /* do a plain search */ let f = s.indexOf(p); if (f > -1) { - lapi.lua_pushinteger(L, f); + lapi.lua_pushinteger(L, f + 1); lapi.lua_pushinteger(L, f + lp); return 2; } @@ -1185,7 +1201,7 @@ const str_find_aux = function(L, find) { reprepstate(ms); if ((res = match(ms, s1, 0)) !== null) { if (find) { - lapi.lua_pushinteger(L, s1); /* start */ + lapi.lua_pushinteger(L, s1 + 1); /* start */ lapi.lua_pushinteger(L, res); /* end */ return push_captures(ms, null, 0) + 2; } else @@ -1193,6 +1209,8 @@ const str_find_aux = function(L, find) { } } while (s1++ < ms.src_end && !anchor); } + lapi.lua_pushnil(L); /* not found */ + return 1; }; const str_find = function(L) { @@ -1200,7 +1218,7 @@ const str_find = function(L) { }; const str_match = function(L) { - return str_find_aux(L, 1); + return str_find_aux(L, 0); }; const strlib = { diff --git a/tests/lstrlib.js b/tests/lstrlib.js index 6364c7f..aac3851 100644 --- a/tests/lstrlib.js +++ b/tests/lstrlib.js @@ -498,4 +498,127 @@ test('string.pack/unpack/packsize', function (t) { lapi.lua_toboolean(L, -1), "Correct element(s) on the stack" ); +}); + + +test('string.find without pattern', function (t) { + let luaCode = ` + return string.find("hello to you", " to ") + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tointeger(L, -2), + 6, + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -1), + 9, + "Correct element(s) on the stack" + ); +}); + + +test('string.match', function (t) { + let luaCode = ` + return string.match("foo: 123 bar: 456", "(%a+):%s*(%d+)") + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "foo", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tostring(L, -1), + "123", + "Correct element(s) on the stack" + ); +}); + + +test('string.find', function (t) { + let luaCode = ` + return string.find("foo: 123 bar: 456", "(%a+):%s*(%d+)") + `, L; + + t.plan(6); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tointeger(L, -4), + 1, + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -3), + 8, + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "foo", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tostring(L, -1), + "123", + "Correct element(s) on the stack" + ); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 5f5ba74ace8a956ae48a9f0b182a157052c2546e Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Mon, 20 Mar 2017 16:15:36 +0100 Subject: string.gsub --- README.md | 14 ++++++-------- src/lapi.js | 20 ++++++++++++++++++++ src/lstrlib.js | 40 ++++++++++++++++++++++++++++++++++++++++ tests/lstrlib.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 6de8cab..2d1e35a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ - [ ] lua_islightuserdata - [ ] lua_isthread - [ ] lua_isuserdata - - [ ] lua_newuserdata - [ ] lua_pcallk - [ ] lua_pushfstring - [ ] lua_pushlightuserdata @@ -57,7 +56,6 @@ - [ ] lua_setlocal - [ ] lua_setuservalue - [ ] lua_tocfunction - - [ ] lua_touserdata - [ ] lua_upvalueid - [ ] lua_upvaluejoin - [ ] Auxiliary library @@ -99,20 +97,20 @@ - [x] string.byte - [x] string.char - [x] string.dump + - [x] string.find - [x] string.format + - [x] string.gsub - [x] string.len - [x] string.lower + - [x] string.match + - [x] string.pack + - [x] string.packsize - [x] string.rep - [x] string.reverse - [x] string.sub + - [x] string.unpack - [x] string.upper - - [ ] string.find - [ ] string.gmatch - - [ ] string.gsub - - [ ] string.match - - [ ] string.pack - - [ ] string.packsize - - [ ] string.unpack - [ ] Package - [ ] os - [ ] io diff --git a/src/lapi.js b/src/lapi.js index a884a97..6840ef9 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -428,6 +428,14 @@ const lua_createtable = function(L, narray, nrec) { assert(L.top <= L.ci.top, "stack overflow"); }; +const lua_newuserdata = function(L, u) { + L.stack[L.top++] = new lobject.TValue(CT.LUA_TUSERDATA, u); + + assert(L.top <= L.ci.top, "stack overflow"); + + return L.stack[L.top - 1].value; +}; + const aux_upvalue = function(fi, n) { switch(fi.ttype()) { case CT.LUAT_TCCL: { /* C closure */ @@ -578,6 +586,16 @@ const lua_tonumber = function(L, idx) { return lvm.tonumber(index2addr(L, idx)); }; +const lua_touserdata = function(L, idx) { + let o = index2addr(L, idx); + switch (o.ttnov()) { + case CT.LUA_TUSERDATA: + case CT.LUA_TLIGHTUSERDATA: + return o.value; + default: return null; + } +}; + const lua_tothread = function(L, idx) { let o = index2addr(L, idx); return o.ttisthread() ? o.value : null; @@ -879,6 +897,7 @@ module.exports.lua_istable = lua_istable; module.exports.lua_len = lua_len; module.exports.lua_load = lua_load; module.exports.lua_newtable = lua_newtable; +module.exports.lua_newuserdata = lua_newuserdata; module.exports.lua_next = lua_next; module.exports.lua_pcall = lua_pcall; module.exports.lua_pcallk = lua_pcallk; @@ -924,6 +943,7 @@ module.exports.lua_tonumber = lua_tonumber; module.exports.lua_topointer = lua_topointer; module.exports.lua_tostring = lua_tostring; module.exports.lua_tothread = lua_tothread; +module.exports.lua_touserdata = lua_touserdata; module.exports.lua_type = lua_type; module.exports.lua_typename = lua_typename; module.exports.lua_version = lua_version; diff --git a/src/lstrlib.js b/src/lstrlib.js index cedb403..9caa3cc 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -1221,12 +1221,52 @@ const str_match = function(L) { return str_find_aux(L, 0); }; +/* state for 'gmatch' */ +class GMatchState { + constructor() { + this.src = NaN; /* current position */ + this.p = NaN; /* pattern */ + this.lastmatch = NaN; /* end of last match */ + this.ms = new MatchState(); /* match state */ + } +} + +const gmatch_aux = function(L) { + let gm = lapi.lua_touserdata(L, lua.lua_upvalueindex(3)); + gm.ms.L = L; + for (let src = 0; src < gm.ms.src_end; src++) { + reprepstate(gm.ms); + let e; + if ((e = match(gm.ms, src, gm.p)) !== null && e !== gm.lastmatch) { + gm.src = gm.lastmatch = e; + return push_captures(gm.ms, src, e); + } + } + return 0; /* not found */ +}; + +const str_gmatch = function(L) { + let s = lauxlib.luaL_checkstring(L, 1); + let p = lauxlib.luaL_checkstring(L, 2); + let ls = s.length; + let lp = p.length; + lapi.lua_settop(L, 2); /* keep them on closure to avoid being collected */ + let gm = lapi.lua_newuserdata(L, new GMatchState()); + prepstate(gm.ms, L, s, ls, p, lp); + gm.src = s; + gm.p = p; + gm.lastmatch = null; + lapi.lua_pushcclosure(L, gmatch_aux, 3); + return 1; +}; + const strlib = { "byte": str_byte, "char": str_char, "dump": str_dump, "find": str_find, "format": str_format, + "gmatch": str_gmatch, "len": str_len, "lower": str_lower, "match": str_match, diff --git a/tests/lstrlib.js b/tests/lstrlib.js index aac3851..ce77bdc 100644 --- a/tests/lstrlib.js +++ b/tests/lstrlib.js @@ -621,4 +621,60 @@ test('string.find', function (t) { "123", "Correct element(s) on the stack" ); +}); + + +test('string.gmatch', function (t) { + let luaCode = ` + local s = "hello world from Lua" + local t = {} + + for w in string.gmatch(s, "%a+") do + table.insert(t, w) + end + + return table.unpack(t) + `, L; + + t.plan(6); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -4), + "hello", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tostring(L, -3), + "world", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "from", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tostring(L, -1), + "Lua", + "Correct element(s) on the stack" + ); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 2cc93b924a21030fcd6f5dc96f287bbf13f987f4 Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 21 Mar 2017 07:33:40 +0100 Subject: string.gmatch --- README.md | 19 +---------- src/lauxlib.js | 4 +-- src/ldo.js | 2 +- src/lstrlib.js | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/ltablib.js | 2 +- 5 files changed, 101 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 2d1e35a..791a885 100644 --- a/README.md +++ b/README.md @@ -93,24 +93,7 @@ - [x] Table - [x] Math - [x] utf8 - - [ ] String - - [x] string.byte - - [x] string.char - - [x] string.dump - - [x] string.find - - [x] string.format - - [x] string.gsub - - [x] string.len - - [x] string.lower - - [x] string.match - - [x] string.pack - - [x] string.packsize - - [x] string.rep - - [x] string.reverse - - [x] string.sub - - [x] string.unpack - - [x] string.upper - - [ ] string.gmatch + - [x] String - [ ] Package - [ ] os - [ ] io diff --git a/src/lauxlib.js b/src/lauxlib.js index 3ee04a4..879d081 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -215,8 +215,8 @@ const luaL_buffinitsize = function(L, B, sz) { return B; }; -const luaL_addlstring = function(B, s) { - B.b += s; +const luaL_addlstring = function(B, s, l) { + B.b += s.slice(0, l); }; const luaL_addstring = luaL_addlstring; diff --git a/src/ldo.js b/src/ldo.js index 7df4c62..a9f442a 100644 --- a/src/ldo.js +++ b/src/ldo.js @@ -143,7 +143,7 @@ const moveresults = function(L, firstResult, res, nres, wanted) { break; case 1: { if (nres === 0) - firstResult = nil; + L.stack[firstResult] = nil; L.stack[res] = L.stack[firstResult]; break; } diff --git a/src/lstrlib.js b/src/lstrlib.js index 9caa3cc..dc3845b 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -1113,7 +1113,7 @@ const match = function(ms, s, p) { const push_onecapture = function(ms, i, s, e) { if (i >= ms.level) { if (i === 0) - lapi.lua_pushlstring(ms.L, s, e); /* add whole match */ + lapi.lua_pushlstring(ms.L, ms.src.slice(s), e - s); /* add whole match */ else lauxlib.luaL_error(ms.L, `invalid capture index %${i + 1}`); } else { @@ -1127,7 +1127,7 @@ const push_onecapture = function(ms, i, s, e) { }; const push_captures = function(ms, s, e) { - let nlevels = ms.level === 0 && s ? 1 : ms.level; + let nlevels = ms.level === 0 && ms.src.slice(s) ? 1 : ms.level; lauxlib.luaL_checkstack(ms.L, nlevels, "too many catpures"); for (let i = 0; i < nlevels; i++) push_onecapture(ms, i, s, e); @@ -1234,7 +1234,7 @@ class GMatchState { const gmatch_aux = function(L) { let gm = lapi.lua_touserdata(L, lua.lua_upvalueindex(3)); gm.ms.L = L; - for (let src = 0; src < gm.ms.src_end; src++) { + for (let src = gm.src; src < gm.ms.src_end; src++) { reprepstate(gm.ms); let e; if ((e = match(gm.ms, src, gm.p)) !== null && e !== gm.lastmatch) { @@ -1253,13 +1253,103 @@ const str_gmatch = function(L) { lapi.lua_settop(L, 2); /* keep them on closure to avoid being collected */ let gm = lapi.lua_newuserdata(L, new GMatchState()); prepstate(gm.ms, L, s, ls, p, lp); - gm.src = s; - gm.p = p; + gm.src = 0; + gm.p = 0; gm.lastmatch = null; lapi.lua_pushcclosure(L, gmatch_aux, 3); return 1; }; +const add_s = function(ms, b, s, e) { + let L = ms.L; + let news = lapi.lua_tostring(L, 3); + let l = news.length; + for (let i = 0; i < l; i++) { + if (news.charAt(i) !== sL_ESC) + lauxlib.luaL_addchar(b, news.charAt(i)); + else { + i++; /* skip ESC */ + if (!isdigit(news.charAt(i))) { + if (news.charAt(i) !== sL_ESC) + lauxlib.luaL_error(L, `invalid use of '${sL_ESC}' in replacement string`); + lauxlib.luaL_addchar(b, news.charAt(i)); + } else if (news.charAt(i) === '0') + lauxlib.luaL_addlstring(b, ms.src.slice(s), e - s); + else { + push_onecapture(ms, news.charCodeAt(i) - '1'.charCodeAt(0), s, e); + lauxlib.luaL_tostring(L, -1); + lapi.lua_remove(L, -2); /* remove original value */ + lauxlib.luaL_addvalue(b); /* add capture to accumulated result */ + } + } + } +}; + +const add_value = function(ms, b, s, e, tr) { + let L = ms.L; + switch (tr) { + case CT.LUA_TFUNCTION: { + lapi.lua_pushvalue(L, 3); + let n = push_captures(ms, s, e); + lapi.lua_call(L, n, 1); + break; + } + case CT.LUA_TTABLE: { + push_onecapture(ms, 0, s, e); + lapi.lua_gettable(L, 3); + break; + } + default: { /* LUA_TNUMBER or LUA_TSTRING */ + add_s(ms, b, s, e); + return; + } + } + if (!lapi.lua_toboolean(L, -1)) { /* nil or false? */ + lapi.lua_pop(L, 1); + lapi.lua_pushlstring(L, s, e - s); /* keep original text */ + } else if (!lapi.lua_isstring(L, -1)) + lauxlib.luaL_error(L, `invalid replacement value (a ${lauxlib.luaL_typename(L, -1)})`); + lauxlib.luaL_addvalue(b); /* add result to accumulator */ +}; + +const str_gsub = function(L) { + let src = lauxlib.luaL_checkstring(L, 1); /* subject */ + let srcl = src.length; + let p = lauxlib.luaL_checkstring(L, 2); /* pattern */ + let lp = p.length; + let lastmatch = null; /* end of last match */ + let tr = lapi.lua_type(L, 3); /* replacement type */ + let max_s = lauxlib.luaL_optinteger(L, 4, srcl + 1); /* max replacements */ + let anchor = p.charAt(0) === '^'; + let n = 0; /* replacement count */ + let ms = new MatchState(L); + let b = new lauxlib.luaL_Buffer(L); + lauxlib.luaL_argcheck(L, tr === CT.LUA_TNUMBER || tr === CT.LUA_TSTRING || tr === CT.LUA_TFUNCTION || tr === CT.LUA_TTABLE, 3, + "string/function/table expected"); + lauxlib.luaL_buffinit(L, b); + if (anchor) { + p = p.slice(1); lp--; /* skip anchor character */ + } + prepstate(ms, L, src, srcl, p, lp); + src = 0; p = 0; + while (n < max_s) { + let e; + reprepstate(ms); + if ((e = match(ms, src, p)) !== null && e !== lastmatch) { /* match? */ + n++; + add_value(ms, b, src, e, tr); /* add replacement to buffer */ + src = lastmatch = e; + } else if (src < ms.src_end) /* otherwise, skip one character */ + lauxlib.luaL_addchar(b, ms.src.charAt(src++)); + else break; /* end of subject */ + if (anchor) break; + } + lauxlib.luaL_addlstring(b, ms.src.slice(src), ms.src_end - src); + lauxlib.luaL_pushresult(b); + lapi.lua_pushinteger(L, n); /* number of substitutions */ + return 2; +}; + const strlib = { "byte": str_byte, "char": str_char, @@ -1267,6 +1357,7 @@ const strlib = { "find": str_find, "format": str_format, "gmatch": str_gmatch, + "gsub": str_gsub, "len": str_len, "lower": str_lower, "match": str_match, diff --git a/src/ltablib.js b/src/ltablib.js index 10d66eb..20fdf95 100644 --- a/src/ltablib.js +++ b/src/ltablib.js @@ -168,7 +168,7 @@ const pack = function(L) { const unpack = function(L) { let i = lauxlib.luaL_optinteger(L, 2, 1); - let e = lauxlib.luaL_opt(L, lauxlib.luaL_checkinteger, 3, lapi.lua_len(L, 1)); + let e = lauxlib.luaL_opt(L, lauxlib.luaL_checkinteger, 3, lauxlib.luaL_len(L, 1)); if (i > e) return 0; /* empty range */ let n = e - i; /* number of elements minus 1 (avoid overflows) */ if (n >= Number.MAX_SAFE_INTEGER || !lapi.lua_checkstack(L, ++n)) -- cgit v1.2.3-70-g09d2 From 66292a6a85cf22cae31520ea4d08c76f544e338a Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Tue, 21 Mar 2017 08:38:41 +0100 Subject: string.gsub tests --- src/lapi.js | 2 +- src/lauxlib.js | 4 +- src/ljstype.js | 10 +-- src/lstrlib.js | 6 +- tests/lstrlib.js | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 201 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/lapi.js b/src/lapi.js index 6840ef9..5872545 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -542,7 +542,7 @@ const lua_toboolean = function(L, idx) { const lua_tolstring = function(L, idx) { let o = index2addr(L, idx); - if (!o.ttisstring() && !o.ttisnumber()) + if ((!o.ttisstring() && !o.ttisnumber())) return null; return o.ttisstring() ? o.jsstring() : `${o.value}`; diff --git a/src/lauxlib.js b/src/lauxlib.js index 879d081..ac7ab00 100644 --- a/src/lauxlib.js +++ b/src/lauxlib.js @@ -299,10 +299,12 @@ const luaL_tolstring = function(L, idx) { } else { switch(lapi.lua_type(L, idx)) { case CT.LUA_TNUMBER: - case CT.LUA_TSTRING: case CT.LUA_TBOOLEAN: lapi.lua_pushstring(L, `${lapi.index2addr(L, idx).value}`); break; + case CT.LUA_TSTRING: + lapi.lua_pushstring(L, lapi.index2addr(L, idx).jsstring()); + break; case CT.LUA_TNIL: lapi.lua_pushstring(L, `nil`); break; diff --git a/src/ljstype.js b/src/ljstype.js index ef4be1d..7de9d9a 100644 --- a/src/ljstype.js +++ b/src/ljstype.js @@ -3,23 +3,23 @@ const assert = require('assert'); const lisdigit = function(c) { - return /^\d$/.test(c.charAt(0)); + return typeof c === 'string' && /^\d$/.test(c.charAt(0)); }; const lisxdigit = function(c) { - return /^[0-9a-fA-F]$/.test(c.charAt(0)); + return typeof c === 'string' && /^[0-9a-fA-F]$/.test(c.charAt(0)); }; const lisspace = function(c) { - return /^\s$/.test(c.charAt(0)); + return typeof c === 'string' && /^\s$/.test(c.charAt(0)); }; const lislalpha = function(c) { - return /^[_a-zA-Z]$/.test(c.charAt(0)); + return typeof c === 'string' && /^[_a-zA-Z]$/.test(c.charAt(0)); }; const lislalnum = function(c) { - return /^[_a-zA-Z0-9]$/.test(c.charAt(0)); + return typeof c === 'string' && /^[_a-zA-Z0-9]$/.test(c.charAt(0)); }; module.exports.lisdigit = lisdigit; diff --git a/src/lstrlib.js b/src/lstrlib.js index dc3845b..3872cb2 100644 --- a/src/lstrlib.js +++ b/src/lstrlib.js @@ -176,7 +176,7 @@ const MAX_FORMAT = 32; const isalpha = e => /^[a-zA-Z]$/.test(e.charAt(0)); const isdigit = e => "0".charCodeAt(0) <= e && e <= "9".charCodeAt(0); const iscntrl = e => (0x00 <= e && e <= 0x1f) || e === 0x7f; -const isgraph = e => e.charCodeAt(0) > 32 && e.charCodeAt < 127; // TODO: Will only work for ASCII +const isgraph = e => e.charCodeAt(0) > 32 && e.charCodeAt(0) < 127; // TODO: Will only work for ASCII const islower = e => /^(?![A-Z]).*$/.test(e.charAt(0)); const isupper = e => /^(?![a-z]).*$/.test(e.charAt(0)); const isalnum = e => /^[a-zA-Z0-9]$/.test(e.charAt(0)); @@ -1269,7 +1269,7 @@ const add_s = function(ms, b, s, e) { lauxlib.luaL_addchar(b, news.charAt(i)); else { i++; /* skip ESC */ - if (!isdigit(news.charAt(i))) { + if (!isdigit(news.charCodeAt(i))) { if (news.charAt(i) !== sL_ESC) lauxlib.luaL_error(L, `invalid use of '${sL_ESC}' in replacement string`); lauxlib.luaL_addchar(b, news.charAt(i)); @@ -1277,7 +1277,7 @@ const add_s = function(ms, b, s, e) { lauxlib.luaL_addlstring(b, ms.src.slice(s), e - s); else { push_onecapture(ms, news.charCodeAt(i) - '1'.charCodeAt(0), s, e); - lauxlib.luaL_tostring(L, -1); + lauxlib.luaL_tolstring(L, -1); lapi.lua_remove(L, -2); /* remove original value */ lauxlib.luaL_addvalue(b); /* add capture to accumulated result */ } diff --git a/tests/lstrlib.js b/tests/lstrlib.js index ce77bdc..12df173 100644 --- a/tests/lstrlib.js +++ b/tests/lstrlib.js @@ -677,4 +677,193 @@ test('string.gmatch', function (t) { "Lua", "Correct element(s) on the stack" ); +}); + + +test('string.gsub', function (t) { + let luaCode = ` + return string.gsub("hello world", "(%w+)", "%1 %1") + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "hello hello world world", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -1), + 2, + "Correct element(s) on the stack" + ); +}); + + +test('string.gsub (number)', function (t) { + let luaCode = ` + return string.gsub("hello world", "%w+", "%0 %0", 1) + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "hello hello world", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -1), + 1, + "Correct element(s) on the stack" + ); +}); + + +test('string.gsub (pattern)', function (t) { + let luaCode = ` + return string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "world hello Lua from", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -1), + 2, + "Correct element(s) on the stack" + ); +}); + + +test('string.gsub (function)', function (t) { + let luaCode = ` + return string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) + return load(s)() + end) + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "4+5 = 9", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -1), + 1, + "Correct element(s) on the stack" + ); +}); + + + +test('string.gsub (table)', function (t) { + let luaCode = ` + local t = {name="lua", version="5.3"} + return string.gsub("$name-$version.tar.gz", "%$(%w+)", t) + `, L; + + t.plan(4); + + t.doesNotThrow(function () { + + L = lauxlib.luaL_newstate(); + + linit.luaL_openlibs(L); + + lauxlib.luaL_loadstring(L, luaCode); + + }, "Lua program loaded without error"); + + t.doesNotThrow(function () { + + lapi.lua_call(L, 0, -1); + + }, "Lua program ran without error"); + + t.strictEqual( + lapi.lua_tostring(L, -2), + "lua-5.3.tar.gz", + "Correct element(s) on the stack" + ); + + t.strictEqual( + lapi.lua_tointeger(L, -1), + 2, + "Correct element(s) on the stack" + ); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2