summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md21
-rw-r--r--src/lapi.js22
-rw-r--r--src/lauxlib.js8
-rw-r--r--src/ldo.js2
-rw-r--r--src/ljstype.js10
-rw-r--r--src/lstrlib.js552
-rw-r--r--src/ltablib.js2
-rw-r--r--tests/lstrlib.js368
8 files changed, 952 insertions, 33 deletions
diff --git a/README.md b/README.md
index 6de8cab..791a885 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
@@ -95,24 +93,7 @@
- [x] Table
- [x] Math
- [x] utf8
- - [ ] String
- - [x] string.byte
- - [x] string.char
- - [x] string.dump
- - [x] string.format
- - [x] string.len
- - [x] string.lower
- - [x] string.rep
- - [x] string.reverse
- - [x] string.sub
- - [x] string.upper
- - [ ] string.find
- - [ ] string.gmatch
- - [ ] string.gsub
- - [ ] string.match
- - [ ] string.pack
- - [ ] string.packsize
- - [ ] string.unpack
+ - [x] String
- [ ] Package
- [ ] os
- [ ] io
diff --git a/src/lapi.js b/src/lapi.js
index a884a97..5872545 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 */
@@ -534,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}`;
@@ -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/lauxlib.js b/src/lauxlib.js
index 3ee04a4..ac7ab00 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;
@@ -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/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/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 56fcc11..cd9278d 100644
--- a/src/lstrlib.js
+++ b/src/lstrlib.js
@@ -10,7 +10,15 @@ 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);
+
+/*
+** 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;
// (sizeof(size_t) < sizeof(int) ? MAX_SIZET : (size_t)(INT_MAX))
const MAXSIZE = Number.MAX_SAFE_INTEGER;
@@ -165,9 +173,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(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));
+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 +823,545 @@ 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.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 sL_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(c, cl) {
+ let res;
+ switch (cl.toLowerCase()) {
+ case 'a' : res = isalpha(c); break;
+ case 'c' : res = iscntrl(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;
+ 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 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;
+ }
+ }
+};
+
+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] = ms.capture[level] ? ms.capture[level] : {};
+ 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, p + 2, CAP_POSITION);
+ else
+ s = start_capture(ms, s, p + 1, CAP_UNFINISHED);
+ break;
+ }
+ case ')': { /* end capture */
+ s = end_capture(ms, s, p + 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 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);
+ 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 (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 */
+ } 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_onecapture = function(ms, i, s, e) {
+ if (i >= ms.level) {
+ if (i === 0)
+ 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 {
+ 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.src.slice(ms.capture[i].init), l);
+ }
+};
+
+const push_captures = function(ms, s, e) {
+ 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);
+ 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 + 1);
+ 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 + 1); /* 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);
+ }
+ lapi.lua_pushnil(L); /* not found */
+ return 1;
+};
+
+const str_find = function(L) {
+ return str_find_aux(L, 1);
+};
+
+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 = 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) {
+ 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 = 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.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));
+ } 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_tolstring(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,
"dump": str_dump,
+ "find": str_find,
"format": str_format,
+ "gmatch": str_gmatch,
+ "gsub": str_gsub,
"len": str_len,
"lower": str_lower,
+ "match": str_match,
"pack": str_pack,
"packsize": str_packsize,
"rep": str_rep,
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))
diff --git a/tests/lstrlib.js b/tests/lstrlib.js
index 6364c7f..12df173 100644
--- a/tests/lstrlib.js
+++ b/tests/lstrlib.js
@@ -498,4 +498,372 @@ 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"
+ );
+});
+
+
+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"
+ );
+});
+
+
+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