From 2e5b595a2e04fe72555a565af4aae43560946473 Mon Sep 17 00:00:00 2001
From: Benoit Giannangeli <giann008@gmail.com>
Date: Wed, 29 Mar 2017 11:57:43 +0200
Subject: Never use js strings internally

---
 src/lapi.js     |  48 ++++++++++++++++++----
 src/lauxlib.js  |  74 +++++++++++++++++----------------
 src/lbaselib.js |  43 +++++++++----------
 src/lcode.js    |   6 +--
 src/lcorolib.js |   5 ++-
 src/ldebug.js   |  60 +++++++++++++--------------
 src/ldo.js      |   8 ++--
 src/ljstype.js  |  12 +++---
 src/llex.js     | 125 ++++++++++++++++++++++++++------------------------------
 src/lmathlib.js |  20 ++++-----
 src/lobject.js  |  84 ++++++++++++++++++-------------------
 src/lparser.js  |  70 +++++++++++++++----------------
 src/ltablib.js  |  25 ++++++------
 src/ltm.js      |  90 ++++++++++++++--------------------------
 src/lua.js      |   2 +-
 src/lutf8lib.js |  24 +++++------
 16 files changed, 348 insertions(+), 348 deletions(-)

(limited to 'src')

diff --git a/src/lapi.js b/src/lapi.js
index 8cabf12..6e1471d 100644
--- a/src/lapi.js
+++ b/src/lapi.js
@@ -218,11 +218,11 @@ const lua_pushinteger = function(L, n) {
     assert(L.top <= L.ci.top, "stack overflow");
 };
 
-const lua_pushlstring = function(L, s, len) { // TODO: embedded \0
-    assert(typeof s === "string");
+const lua_pushlstring = function(L, s, len) {
+    assert(Array.isArray(s), "lua_pushlstring expects array of byte");
     assert(typeof len === "number");
 
-    let ts = len === 0 ? L.l_G.intern(lua.to_luastring("")) : L.l_G.intern(lua.to_luastring(s.substr(0, len)));
+    let ts = len === 0 ? L.l_G.intern(lua.to_luastring("")) : new TValue(CT.LUA_TLNGSTR, s.slice(0, len));
     L.stack[L.top++] = ts;
 
     assert(L.top <= L.ci.top, "stack overflow");
@@ -231,7 +231,24 @@ const lua_pushlstring = function(L, s, len) { // TODO: embedded \0
 };
 
 const lua_pushstring = function (L, s) {
-    if (typeof s !== "string")
+    assert(Array.isArray(s), "lua_pushstring expects array of byte");
+
+    if (s === undefined || s === null)
+        L.stack[L.top] = new TValue(CT.LUA_TNIL, null);
+    else {
+        L.stack[L.top] = new TValue(CT.LUA_TLNGSTR, s);
+    }
+
+    L.top++;
+    assert(L.top <= L.ci.top, "stack overflow");
+
+    return s;
+};
+
+const lua_pushliteral = function (L, s) {
+    assert(typeof s === "string", "lua_pushliteral expects a JS string");
+
+    if (s === undefined || s === null)
         L.stack[L.top] = new TValue(CT.LUA_TNIL, null);
     else {
         let ts = L.l_G.intern(lua.to_luastring(s));
@@ -244,8 +261,6 @@ const lua_pushstring = function (L, s) {
     return s;
 };
 
-const lua_pushliteral = lua_pushstring;
-
 const lua_pushcclosure = function(L, fn, n) {
     assert(typeof fn === "function");
     assert(typeof n === "number");
@@ -315,7 +330,9 @@ const lua_pushglobaltable = function(L) {
 ** t[k] = value at the top of the stack (where 'k' is a string)
 */
 const auxsetstr = function(L, t, k) {
-    let str = L.l_G.intern(lua.to_luastring(k));
+    assert(Array.isArray(k), "key must be an array of bytes");
+
+    let str = L.l_G.intern(k);
 
     assert(1 < L.top - L.ci.funcOff, "not enough elements in the stack");
 
@@ -392,7 +409,9 @@ const lua_rawset = function(L, idx) {
 */
 
 const auxgetstr = function(L, t, k) {
-    let str = L.l_G.intern(lua.to_luastring(k));
+    assert(Array.isArray(k), "key must be an array of bytes");
+
+    let str = L.l_G.intern(k);
     let slot = t.__index(t, k);
     if (t.ttistable() && !slot.ttisnil()) {
         L.stack[L.top++] = slot;
@@ -553,11 +572,22 @@ const lua_tolstring = function(L, idx) {
     if ((!o.ttisstring() && !o.ttisnumber()))
         return null;
 
-    return o.ttisstring() ? o.jsstring() : `${o.value}`;
+    return o.ttisstring() ? o.value : lua.to_luastring(`${o.value}`);
 };
 
 const lua_tostring =  lua_tolstring;
 
+const lua_toljsstring = function(L, idx) {
+    let o = index2addr(L, idx);
+
+    if ((!o.ttisstring() && !o.ttisnumber()))
+        return null;
+
+    return o.ttisstring() ? o.jsstring() : `${o.value}`;
+};
+
+const lua_tojsstring =  lua_toljsstring;
+
 // Convert a string on the stack to a dataview, because lua_tostring will perform utf-8 to utf-16 conversion
 const lua_todataview = function(L, idx) {
     let o = index2addr(L, idx);
diff --git a/src/lauxlib.js b/src/lauxlib.js
index 8bda842..f60bd8c 100644
--- a/src/lauxlib.js
+++ b/src/lauxlib.js
@@ -58,7 +58,7 @@ const pushglobalfuncname = function(L, ar) {
     lapi.lua_getfield(L, lua.LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
     if (findfield(L, top + 1, 2)) {
         let name = lapi.lua_tostring(L, -1);
-        if (name.startsWith("_G.")) {
+        if (name.jsstring().startsWith("_G.")) {
             lapi.lua_pushstring(L, name.slice(3));  /* name start with '_G.'? */
             lapi.lua_remove(L, -2);  /* name start with '_G.'? */
         }
@@ -71,61 +71,65 @@ const pushglobalfuncname = function(L, ar) {
 };
 
 const panic = function(L) {
-    throw new Error(`PANIC: unprotected error in call to Lua API (${lapi.lua_tostring(L, -1)})`);
+    throw new Error(`PANIC: unprotected error in call to Lua API (${lapi.lua_tojsstring(L, -1)})`);
 };
 
 const luaL_argerror = function(L, arg, extramsg) {
     let ar = new lua.lua_Debug();
 
     if (!ldebug.lua_getstack(L, 0, ar))  /* no stack frame? */
-        return luaL_error(L, 'bad argument #%d (%s)', arg, extramsg);
+        return luaL_error(L, lua.to_luastring(`bad argument #${arg} (${lobject.jsstring(extramsg)})`));
 
     ldebug.lua_getinfo(L, 'n', ar);
 
     if (ar.namewhat === 'method') {
         arg--;  /* do not count 'self' */
         if (arg === 0)  /* error is in the self argument itself? */
-            return luaL_error(L, "calling '%s' on  bad self (%s)", ar.name, extramsg);
+            return luaL_error(L, lua.to_luastring(`calling '${lobject.jsstring(ar.name)}' on  bad self (${lobject.jsstring(extramsg)})`));
     }
 
     if (ar.name === null)
         ar.name = pushglobalfuncname(L, ar) ? lapi.lua_tostring(L, -1) : "?";
 
-    return luaL_error(L, `bad argument #${arg} to '${ar.name}' (${extramsg})`);
+    return luaL_error(L, lua.to_luastring(`bad argument #${arg} to '${lobject.jsstring(ar.name)}' (${lobject.jsstring(extramsg)})`));
 };
 
 const typeerror = function(L, arg, tname) {
     let typearg;
-    if (luaL_getmetafield(L, arg, "__name") === CT.LUA_TSTRING)
+    if (luaL_getmetafield(L, arg, lua.to_luastring("__name")) === CT.LUA_TSTRING)
         typearg = lapi.lua_tostring(L, -1);
     else if (lapi.lua_type(L, arg) === CT.LUA_TLIGHTUSERDATA)
-        typearg = "light userdata";
+        typearg = lua.to_luastring("light userdata");
     else
         typearg = luaL_typename(L, arg);
 
-    let msg = lapi.lua_pushstring(L, `${tname} expected, got ${typearg}`);
+    let msg = lapi.lua_pushstring(L, lua.to_luastring(`${lobject.jsstring(tname)} expected, got ${lobject.jsstring(typearg)}`));
     return luaL_argerror(L, arg, msg);
 };
 
 const luaL_where = function(L, level) {
     let ar = new lua.lua_Debug();
     if (ldebug.lua_getstack(L, level, ar)) {
-        ldebug.lua_getinfo(L, "Sl", ar);
+        ldebug.lua_getinfo(L, lua.to_luastring("Sl"), ar);
         if (ar.currentline > 0) {
-            lapi.lua_pushstring(L, `${ar.short_src}:${ar.currentline}:`);
+            lapi.lua_pushstring(L, lua.to_luastring(`${lobject.jsstring(ar.short_src)}:${ar.currentline}:`));
             return;
         }
     }
-    lapi.lua_pushstring(L, "");
+    lapi.lua_pushstring(L, []);
 };
 
 const luaL_error = function(L, fmt, ...args) {
     let i = 0;
 
+    fmt = lobject.jsstring(fmt);
     // TODO: bypassing lua_pushvstring for now
-    lapi.lua_pushstring(L, fmt.replace(/(^%[sfIpdcU]|([^%])%[sfIpdcU])/g, function (m, p1, p2, off) {
+    fmt = fmt.replace(/(^%[sfIpdcU]|([^%])%[sfIpdcU])/g, function (m, p1, p2, off) {
         return p2 ? p2 + args[i++] : args[i++];
-    }));
+    });
+    fmt = lua.to_luastring(fmt);
+
+    lapi.lua_pushstring(L, fmt);
 
     return lapi.lua_error(L);
 };
@@ -151,7 +155,7 @@ const luaL_argcheck = function(L, cond, arg, extramsg) {
 
 const luaL_checkany = function(L, arg) {
     if (lapi.lua_type(L, arg) === CT.LUA_TNONE)
-        luaL_argerror(L, arg, "value expected");
+        luaL_argerror(L, arg, lua.to_luastring("value expected"));
 };
 
 const luaL_checktype = function(L, arg, t) {
@@ -165,7 +169,7 @@ const luaL_checkstring = function(L, n) {
 
 const luaL_checklstring = function(L, arg) {
     let s = lapi.lua_tolstring(L, arg);
-    if (typeof s !== "string") tag_error(L, arg, CT.LUA_TSTRING);
+    if (s === null || s === undefined) tag_error(L, arg, CT.LUA_TSTRING);
     return s;
 };
 
@@ -179,7 +183,7 @@ const luaL_optstring = luaL_optlstring;
 
 const interror = function(L, arg) {
     if (lapi.lua_isnumber(L, arg))
-        luaL_argerror(L, arg, "number has no integer representation");
+        luaL_argerror(L, arg, lua.to_luastring("number has no integer representation"));
     else
         tag_error(L, arg, CT.LUA_TNUMBER);
 };
@@ -208,7 +212,7 @@ const luaL_prepbuffsize = function(B, sz) {
 
 const luaL_buffinit = function(L, B) {
     B.L = L;
-    B.b = "";
+    B.b = [];
 };
 
 const luaL_buffinitsize = function(L, B, sz) {
@@ -217,7 +221,7 @@ const luaL_buffinitsize = function(L, B, sz) {
 };
 
 const luaL_addlstring = function(B, s, l) {
-    B.b += s.slice(0, l);
+    B.b = B.b.concat(s.slice(0, l));
 };
 
 const luaL_addstring = luaL_addlstring;
@@ -228,7 +232,7 @@ const luaL_pushresult = function(B) {
 };
 
 const luaL_addchar = function(B, c) {
-    B.b += c;
+    B.b.push(c);
 };
 
 const luaL_addvalue = function(B) {
@@ -301,18 +305,18 @@ const luaL_tolstring = function(L, idx) {
         switch(lapi.lua_type(L, idx)) {
             case CT.LUA_TNUMBER:
             case CT.LUA_TBOOLEAN:
-                lapi.lua_pushstring(L, `${lapi.index2addr(L, idx).value}`);
+                lapi.lua_pushstring(L, lua.to_luastring(`${lapi.index2addr(L, idx).value}`));
                 break;
             case CT.LUA_TSTRING:
-                lapi.lua_pushstring(L, lapi.index2addr(L, idx).jsstring());
+                lapi.lua_pushstring(L, lapi.index2addr(L, idx).value);
                 break;
             case CT.LUA_TNIL:
-                lapi.lua_pushstring(L, `nil`);
+                lapi.lua_pushstring(L, lua.to_luastring(`nil`));
                 break;
             default:
-                let tt = luaL_getmetafield(L, idx, "__name");
+                let tt = luaL_getmetafield(L, idx, lua.to_luastring("__name"));
                 let kind = tt === CT.LUA_TSTRING ? lapi.lua_tostring(L, -1) : luaL_typename(L, idx);
-                lapi.lua_pushstring(L, `${kind}: 0x${lapi.index2addr(L, -1).id.toString(16)}`);
+                lapi.lua_pushstring(L, lua.to_luastring(`${lobject.jsstring(kind)}: 0x${lapi.index2addr(L, -1).id.toString(16)}`));
                 if (tt !== CT.LUA_TNIL)
                     lapi.lua_remove(L, -2);
                 break;
@@ -369,12 +373,12 @@ const luaL_getsubtable = function(L, idx, fname) {
 ** Returns with only the table at the stack.
 */
 const luaL_setfuncs = function(L, l, nup) {
-    luaL_checkstack(L, nup, "too many upvalues");
+    luaL_checkstack(L, nup, lua.to_luastring("too many upvalues"));
     for (let lib in l) {  /* fill the table with given functions */
         for (let i = 0; i < nup; i++)  /* copy upvalues to the top */
             lapi.lua_pushvalue(L, -nup);
         lapi.lua_pushcclosure(L, l[lib], nup);  /* closure with those upvalues */
-        lapi.lua_setfield(L, -(nup + 2), lib);
+        lapi.lua_setfield(L, -(nup + 2), lua.to_luastring(lib));
     }
     lapi.lua_pop(L, nup);  /* remove upvalues */
 };
@@ -389,9 +393,9 @@ const luaL_setfuncs = function(L, l, nup) {
 const luaL_checkstack = function(L, space, msg) {
     if (!lapi.lua_checkstack(L, space)) {
         if (msg)
-            luaL_error(L, `stack overflow (${msg})`);
+            luaL_error(L, lua.to_luastring(`stack overflow (${lobject.jsstring(msg)})`));
         else
-            luaL_error(L, 'stack overflow');
+            luaL_error(L, lua.to_luastring('stack overflow'));
     }
 };
 
@@ -437,14 +441,14 @@ if (typeof require === "function") {
                 lf.pos += bytes;
             }
             if (bytes > 0)
-                return lf.binary ? toDataView(lf.buff) : lobject.jsstring(lf.buff, 0, bytes); // TODO: Here reading utf8 only
+                return lf.binary ? toDataView(lf.buff) : lf.buff;
             else return null;
         };
 
         const errfile = function(L, what, fnameindex, error) {
             let serr = error.message;
             let filename = lapi.lua_tostring(L, fnameindex).slice(1);
-            lapi.lua_pushstring(L, `cannot ${what} ${filename}: ${serr}`);
+            lapi.lua_pushstring(L, lua.to_luastring(`cannot ${lobject.jsstring(what)} ${lobject.jsstring(filename)}: ${lobject.jsstring(serr)}`));
             lapi.lua_remove(L, fnameindex);
             return lua.thread_status.LUA_ERRFILE;
         };
@@ -457,12 +461,12 @@ if (typeof require === "function") {
         };
 
         const skipBOM = function(lf) {
-            let p = "\xEF\xBB\xBF";  /* UTF-8 BOM mark */
+            let p = [0XEF, 0XBB, 0XBF];  /* UTF-8 BOM mark */
             lf.n = 0;
             let c;
             do {
                 c = getc(lf);
-                if (c === null || c !== p.charCodeAt(0)) return c;
+                if (c === null || c !== p[0]) return c;
                 p = p.slice(1);
                 lf.buff[lf.n++] = c;  /* to be read by the parser */
             } while (p.length > 0);
@@ -504,11 +508,11 @@ if (typeof require === "function") {
                 lapi.lua_pushliteral(L, "=stdin");
                 lf.f = process.stdin.fd;
             } else {
-                lapi.lua_pushstring(L, `@${filename}`);
+                lapi.lua_pushliteral(L, `@${filename}`);
                 try {
                     lf.f = fs.openSync(filename, "r");
                 } catch (e) {
-                    return errfile(L, "open", fnameindex, e);
+                    return errfile(L, lua.to_luastring("open"), fnameindex, e);
                 }
             }
 
@@ -527,7 +531,7 @@ if (typeof require === "function") {
                 return status;
             } catch (err) {
                 lapi.lua_settop(L, fnameindex);  /* ignore results from 'lua_load' */
-                return errfile(L, "read", fnameindex);
+                return errfile(L, lua.to_luastring("read"), fnameindex);
             }
         };
 
diff --git a/src/lbaselib.js b/src/lbaselib.js
index a6f2ce8..8420672 100644
--- a/src/lbaselib.js
+++ b/src/lbaselib.js
@@ -5,6 +5,7 @@ const assert  = require('assert');
 const lua     = require('./lua.js');
 const lapi    = require('./lapi.js');
 const lauxlib = require('./lauxlib.js');
+const lobject = require('./lobject.js');
 const CT      = lua.constant_types;
 const TS      = lua.thread_status;
 
@@ -12,20 +13,20 @@ const luaB_print = function(L) {
     let n = lapi.lua_gettop(L); /* number of arguments */
     let str = "";
 
-    lapi.lua_getglobal(L, "tostring");
+    lapi.lua_getglobal(L, lua.to_luastring("tostring"));
     for (let i = 1; i <= n; i++) {
         lapi.lua_pushvalue(L, -1);  /* function to be called */
         lapi.lua_pushvalue(L, i);  /* value to print */
         lapi.lua_call(L, 1, 1);
         let s = lapi.lua_tolstring(L, -1);
         if (s === null)
-            return lauxlib.luaL_error(L, "'tostring' must return a string to 'print'");
-        if (i > 1) s = `\t${s}`;
-        str = `${str}${s}`;
+            return lauxlib.luaL_error(L, lua.to_luastring("'tostring' must return a string to 'print'"));
+        if (i > 1) s = ["\t".charCodeAt(0)].concat(s);
+        str = str.concat(s);
         lapi.lua_pop(L, 1);
     }
 
-    console.log(str);
+    console.log(lobject.jsstring(str));
     return 0;
 };
 
@@ -42,16 +43,16 @@ const luaB_getmetatable = function(L) {
         lapi.lua_pushnil(L);
         return 1;  /* no metatable */
     }
-    lauxlib.luaL_getmetafield(L, 1, "__metatable");
+    lauxlib.luaL_getmetafield(L, 1, lua.to_luastring("__metatable"));
     return 1;  /* returns either __metatable field (if present) or metatable */
 };
 
 const luaB_setmetatable = function(L) {
     let t = lapi.lua_type(L, 2);
     lauxlib.luaL_checktype(L, 1, CT.LUA_TTABLE);
-    lauxlib.luaL_argcheck(L, t === CT.LUA_TNIL || t === CT.LUA_TTABLE, 2, "nil or table expected");
-    if (lauxlib.luaL_getmetafield(L, 1, "__metatable") !== CT.LUA_TNIL)
-        return lauxlib.luaL_error(L, "cannot change a protected metatable");
+    lauxlib.luaL_argcheck(L, t === CT.LUA_TNIL || t === CT.LUA_TTABLE, 2, lua.to_luastring("nil or table expected"));
+    if (lauxlib.luaL_getmetafield(L, 1, lua.to_luastring("__metatable")) !== CT.LUA_TNIL)
+        return lauxlib.luaL_error(L, lua.to_luastring("cannot change a protected metatable"));
     lapi.lua_settop(L, 2);
     lapi.lua_setmetatable(L, 1);
     return 1;
@@ -66,7 +67,7 @@ const luaB_rawequal = function(L) {
 
 const luaB_rawlen = function(L) {
     let t = lapi.lua_type(L, 1);
-    lauxlib.luaL_argcheck(L, t === CT.LUA_TTABLE || t === CT.LUA_TSTRING, 1, "table or string expected");
+    lauxlib.luaL_argcheck(L, t === CT.LUA_TTABLE || t === CT.LUA_TSTRING, 1, lua.to_luastring("table or string expected"));
     lapi.lua_pushinteger(L, lapi.lua_rawlen(L, 1));
     return 1;
 };
@@ -90,7 +91,7 @@ const luaB_rawset = function(L) {
 
 const luaB_type = function(L) {
     let t = lapi.lua_type(L, 1);
-    lauxlib.luaL_argcheck(L, t !== CT.LUA_TNONE, 1, "value expected");
+    lauxlib.luaL_argcheck(L, t !== CT.LUA_TNONE, 1, lua.to_luastring("value expected"));
     lapi.lua_pushstring(L, lapi.lua_typename(L, t));
     return 1;
 };
@@ -121,7 +122,7 @@ const luaB_next = function(L) {
 };
 
 const luaB_pairs = function(L) {
-    return pairsmeta(L, "__pairs", 0, luaB_next);
+    return pairsmeta(L, lua.to_luastring("__pairs"), 0, luaB_next);
 };
 
 /*
@@ -163,8 +164,8 @@ const luaB_tonumber = function(L) {
         let base = lauxlib.luaL_checkinteger(L, 2);
         lauxlib.luaL_checktype(L, 1, CT.LUA_TSTRING);  /* no numbers as strings */
         let s = lapi.lua_tostring(L, 1);
-        lauxlib.luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");
-        let n = parseInt(s, base);
+        lauxlib.luaL_argcheck(L, 2 <= base && base <= 36, 2, lua.to_luastring("base out of range"));
+        let n = parseInt(lobject.jsstring(s), base);
         if (!isNaN(n)) {
             lapi.lua_pushinteger(L, n);
             return 1;
@@ -207,7 +208,7 @@ const luaB_select = function(L) {
         let i = lauxlib.luaL_checkinteger(L, 1);
         if (i < 0) i = n + i;
         else if (i > n) i = n;
-        lauxlib.luaL_argcheck(L, 1 <= i, 1, "index out of range");
+        lauxlib.luaL_argcheck(L, 1 <= i, 1, lua.to_luastring("index out of range"));
         return n - i;
     }
 };
@@ -281,28 +282,28 @@ const RESERVEDSLOT = 5;
 ** reserved slot inside the stack.
 */
 const generic_reader = function(L, ud) {
-    lauxlib.luaL_checkstack(L, 2, "too many nested functions");
+    lauxlib.luaL_checkstack(L, 2, lua.to_luastring("too many nested functions"));
     lapi.lua_pushvalue(L, 1);  /* get function */
     lapi.lua_call(L, 0, 1);  /* call it */
     if (lapi.lua_isnil(L, -1)) {
         lapi.lua_pop(L, 1);  /* pop result */
         return null;
     } else if (!lapi.lua_isstring(L, -1))
-        lauxlib.luaL_error(L, "reader function must return a string");
+        lauxlib.luaL_error(L, lua.to_luastring("reader function must return a string"));
     lapi.lua_replace(L, RESERVEDSLOT);  /* save string in reserved slot */
     return lapi.lua_tostring(L, RESERVEDSLOT);
 };
 
 const luaB_load = function(L) {
     let s = lapi.lua_tostring(L, 1);
-    let mode = lauxlib.luaL_optstring(L, 3, "bt");
+    let mode = lauxlib.luaL_optstring(L, 3, lua.to_luastring("bt"));
     let env = !lapi.lua_isnone(L, 4) ? 4 : 0;  /* 'env' index or 0 if no 'env' */
     let status;
     if (s !== null) {  /* loading a string? */
         let chunkname = lauxlib.luaL_optstring(L, 2, s);
         status = lauxlib.luaL_loadbufferx(L, s, chunkname, mode);
     } else {  /* loading from a reader function */
-        let chunkname = lauxlib.luaL_optstring(L, 2, "=(load)");
+        let chunkname = lauxlib.luaL_optstring(L, 2, lua.to_luastring("=(load)"));
         lauxlib.luaL_checktype(L, 1, CT.LUA_TFUNCTION);
         lapi.lua_settop(L, RESERVEDSLOT);  /* create reserved slot */
         status = lapi.lua_load(L, generic_reader, null, chunkname, mode);
@@ -375,10 +376,10 @@ const luaopen_base = function(L) {
     lauxlib.luaL_setfuncs(L, base_funcs, 0);
     /* set global _G */
     lapi.lua_pushvalue(L, -1);
-    lapi.lua_setfield(L, -2, "_G");
+    lapi.lua_setfield(L, -2, lua.to_luastring("_G"));
     /* set global _VERSION */
     lapi.lua_pushliteral(L, lua.LUA_VERSION);
-    lapi.lua_setfield(L, -2, "_VERSION");
+    lapi.lua_setfield(L, -2, lua.to_luastring("_VERSION"));
     return 1;
 };
 
diff --git a/src/lcode.js b/src/lcode.js
index c0ca1ff..8b4a57c 100644
--- a/src/lcode.js
+++ b/src/lcode.js
@@ -183,7 +183,7 @@ const fixjump = function(fs, pc, dest) {
     let offset = dest - (pc + 1);
     assert(dest !== NO_JUMP);
     if (Math.abs(offset) > lopcode.MAXARG_sBx)
-        llex.luaX_syntaxerror(fs.ls, "control structure too long");
+        llex.luaX_syntaxerror(fs.ls, lua.to_luastring("control structure too long"));
     lopcode.SETARG_sBx(jmp, offset);
 };
 
@@ -425,7 +425,7 @@ const luaK_checkstack = function(fs, n) {
     let newstack = fs.freereg + n;
     if (newstack > fs.f.maxstacksize) {
         if (newstack >= MAXREGS)
-            llex.luaX_syntaxerror(fs.ls, "function or expression needs to many registers");
+            llex.luaX_syntaxerror(fs.ls, lua.to_luastring("function or expression needs to many registers"));
         fs.f.maxstacksize = newstack;
     }
 };
@@ -1277,4 +1277,4 @@ module.exports.luaK_setmultret    = luaK_setmultret;
 module.exports.luaK_setoneret     = luaK_setoneret;
 module.exports.luaK_setreturns    = luaK_setreturns;
 module.exports.luaK_storevar      = luaK_storevar;
-module.exports.luaK_stringK       = luaK_stringK;
\ No newline at end of file
+module.exports.luaK_stringK       = luaK_stringK;
diff --git a/src/lcorolib.js b/src/lcorolib.js
index b2d0de8..58d061c 100644
--- a/src/lcorolib.js
+++ b/src/lcorolib.js
@@ -8,12 +8,13 @@ const lauxlib = require('./lauxlib.js');
 const lstate  = require('./lstate.js');
 const ldo     = require('./ldo.js');
 const ldebug  = require('./ldebug.js');
+const lobject = require('./lobject.js');
 const CT      = lua.constant_types;
 const TS      = lua.thread_status;
 
 const getco = function(L) {
     let co = lapi.lua_tothread(L, 1);
-    lauxlib.luaL_argcheck(L, co, 1, "thread expected");
+    lauxlib.luaL_argcheck(L, co, 1, lua.to_luastring("thread expected"));
     return co;
 };
 
@@ -146,4 +147,4 @@ const luaopen_coroutine = function(L) {
     return 1;
 };
 
-module.exports.luaopen_coroutine = luaopen_coroutine;
\ No newline at end of file
+module.exports.luaopen_coroutine = luaopen_coroutine;
diff --git a/src/ldebug.js b/src/ldebug.js
index 7c892a5..507e764 100644
--- a/src/ldebug.js
+++ b/src/ldebug.js
@@ -55,22 +55,22 @@ const lua_getstack = function(L, level, ar) {
 const upvalname = function(p, uv) {
     assert(uv < p.upvalues.length);
     let s = p.upvalues[uv].name;
-    if (s === null) return "?";
+    if (s === null) return "?".charCodeAt(0);
     return s;
 };
 
 const funcinfo = function(ar, cl) {
     if (cl === null || cl.type === CT.LUA_TCCL) {
-        ar.source = "=[JS]";
+        ar.source = lua.to_luastring("=[JS]");
         ar.linedefined = -1;
         ar.lastlinedefined = -1;
         ar.what = "J";
     } else {
         let p = cl.p;
-        ar.source = p.source ? p.source : "=?";
+        ar.source = p.source ? p.source : lua.to_luastring("=?");
         ar.linedefined = p.linedefined;
         ar.lastlinedefined = p.lastlinedefined;
-        ar.what = ar.linedefined === 0 ? "main" : "Lua";
+        ar.what = ar.linedefined === 0 ? lua.to_luastring("main") : lua.to_luastring("Lua");
     }
 
     ar.short_src = lobject.luaO_chunkid(ar.source, luaconf.LUA_IDSIZE);
@@ -99,8 +99,8 @@ const getfuncname = function(L, ci) {
     if (ci === null)
         return null;
     else if (ci.callstatus & lstate.CIST_FIN) {  /* is this a finalizer? */
-        r.name = "__gc";
-        r.funcname = "metamethod";  /* report it as such */
+        r.name = lua.to_luastring("__gc");
+        r.funcname = lua.to_luastring("metamethod");  /* report it as such */
         return r;
     }
     /* calling function is a known Lua function? */
@@ -112,7 +112,7 @@ const getfuncname = function(L, ci) {
 const auxgetinfo = function(L, what, ar, f, ci) {
     let status = 1;
     for (; what.length > 0; what = what.slice(1)) {
-        switch (what[0]) {
+        switch (String.fromCharCode(what[0])) {
             case 'S': {
                 funcinfo(ar, f);
                 break;
@@ -139,7 +139,7 @@ const auxgetinfo = function(L, what, ar, f, ci) {
             case 'n': {
                 ar.namewhat = getfuncname(L, ci, ar.name);
                 if (ar.namewhat === null) {
-                    ar.namewhat = "";
+                    ar.namewhat = [];
                     ar.name = null;
                 }
                 break;
@@ -157,7 +157,7 @@ const auxgetinfo = function(L, what, ar, f, ci) {
 const lua_getinfo = function(L, what, ar) {
     let status, cl, ci, func, funcOff;
     swapextra(L);
-    if (what[0] === '>') {
+    if (what[0] === '>'.charCodeAt(0)) {
         ci = null;
         funcOff = L.top - 1;
         func = L.stack[funcOff];
@@ -173,13 +173,13 @@ const lua_getinfo = function(L, what, ar) {
 
     cl = func.ttisclosure() ? func : null;
     status = auxgetinfo(L, what, ar, cl, ci);
-    if (what.indexOf('f') >= 0) {
+    if (what.indexOf('f'.charCodeAt(0)) >= 0) {
         L.stack[L.top++] = func;
         assert(L.top <= L.ci.top, "stack overflow");
     }
 
     swapextra(L);
-    if (what.indexOf('L') >= 0)
+    if (what.indexOf('L'.charCodeAt(0)) >= 0)
         collectvalidlines(L, cl);
 
     return status;
@@ -200,7 +200,7 @@ const kname = function(p, pc, c) {
         /* else no reasonable name found */
     } else {  /* 'c' is a register */
         let what = getobjname(p, pc, c); /* search for 'c' */
-        if (what && what.name[0] === 'c') {
+        if (what && what.name[0] === 'c'.charCodeAt(0)) {
             return what;
         }
         /* else no reasonable name found */
@@ -271,7 +271,7 @@ const getobjname = function(p, lastpc, reg) {
     };
 
     if (r.name) {  /* is a local? */
-        r.funcname = "local";
+        r.funcname = lua.to_luastring("local");
         return r;
     }
 
@@ -293,12 +293,12 @@ const getobjname = function(p, lastpc, reg) {
                 let t = i.B;  /* table index */
                 let vn = op === 'OP_GETTABLE' ? lfunc.luaF_getlocalname(p, t + 1, pc) : upvalname(p, t);
                 r.name = kname(p, pc, k);
-                r.funcname = vn && vn === "_ENV" ? "global" : "field";
+                r.funcname = vn && vn === lua.to_luastring("_ENV") ? lua.to_luastring("global") : lua.to_luastring("field");
                 return r;
             }
             case 'OP_GETUPVAL': {
                 r.name = upvalname(p, i.B);
-                r.funcname = "upvalue";
+                r.funcname = lua.to_luastring("upvalue");
                 return r;
             }
             case 'OP_LOADK':
@@ -306,7 +306,7 @@ const getobjname = function(p, lastpc, reg) {
                 let b = op === 'OP_LOADK' ? i.Bx : p.code[pc + 1].Ax;
                 if (p.k[b].ttisstring()) {
                     r.name = p.k[b].value;
-                    r.funcname = "constant";
+                    r.funcname = lua.to_luastring("constant");
                     return r;
                 }
                 break;
@@ -314,7 +314,7 @@ const getobjname = function(p, lastpc, reg) {
             case 'OP_SELF': {
                 let k = i.C;
                 r.name = kname(p, pc, k);
-                r.funcname = "method";
+                r.funcname = lua.to_luastring("method");
                 return r;
             }
             default: break;
@@ -343,7 +343,7 @@ const funcnamefromcode = function(L, ci) {
 
     if (ci.callstatus & lstate.CIST_HOOKED) {
         r.name = "?";
-        r.funcname = "hook";
+        r.funcname = lua.to_luastring("hook");
         return r;
     }
 
@@ -352,8 +352,8 @@ const funcnamefromcode = function(L, ci) {
         case 'OP_TAILCALL':
             return getobjname(p, pc, i.A);  /* get function name */
         case 'OP_TFORCALL':
-            r.name = "for iterator";
-            r.funcname = "for iterator";
+            r.name = lua.to_luastring("for iterator");
+            r.funcname = lua.to_luastring("for iterator");
             return r;
         /* other instructions can do calls through metamethods */
         case 'OP_SELF':
@@ -389,7 +389,7 @@ const funcnamefromcode = function(L, ci) {
     }
 
     r.name = L.l_G.tmname[tm].jsstring();
-    r.funcname = "metamethod";
+    r.funcname = lua.to_luastring("metamethod");
     return r;
 };
 
@@ -413,7 +413,7 @@ const getupvalname = function(L, ci, o, name) {
         if (c.upvals[i].val(L) === o) {
             return {
                 name: upvalname(c.p, i),
-                funcname: 'upvalue'
+                funcname: lua.to_luastring('upvalue')
             };
         }
     }
@@ -431,17 +431,17 @@ const varinfo = function(L, o) {
             kind = getobjname(ci.func.p, ci.pcOff, stkid - ci.u.l.base);
     }
 
-    return kind ? ` (${kind.funcname} '${kind.name}')` : ``;
+    return lua.to_luastring(kind ? ` (${lobject.jsstring(kind.funcname)} '${lobject.jsstring(kind.name)}')` : ``);
 };
 
 const luaG_typeerror = function(L, o, op) {
     let t = ltm.luaT_objtypename(L, o);
-    luaG_runerror(L, `attempt to ${op} a ${t} value${varinfo(L, o)}`);
+    luaG_runerror(L, lua.to_luastring(`attempt to ${lobject.jsstring(op)} a ${lobject.jsstring(t)} value${lobject.jsstring(varinfo(L, o))}`));
 };
 
 const luaG_concaterror = function(L, p1, p2) {
     if (p1.ttisstring() || p1.ttisnumber()) p1 = p2;
-    luaG_typeerror(L, p1, 'concatenate');
+    luaG_typeerror(L, p1, lua.to_luastring('concatenate'));
 };
 
 /*
@@ -458,18 +458,18 @@ const luaG_ordererror = function(L, p1, p2) {
     let t1 = ltm.luaT_objtypename(L, p1);
     let t2 = ltm.luaT_objtypename(L, p2);
     if (t1 === t2)
-        luaG_runerror(L, `attempt to compare two ${t1} values`);
+        luaG_runerror(L, lua.to_luastring(`attempt to compare two ${lobject.jsstring(t1)} values`));
     else
-        luaG_runerror(L, `attempt to compare ${t1} with ${t2}`);
+        luaG_runerror(L, lua.to_luastring(`attempt to compare ${lobject.jsstring(t1)} with ${lobject.jsstring(t2)}`));
 };
 
 /* add src:line information to 'msg' */
 const luaG_addinfo = function(L, msg, src, line) {
-    let buff = '?';
+    let buff = ['?'.charCodeAt(0)];
     if (src)
         buff = lobject.luaO_chunkid(src, luaconf.LUA_IDSIZE);
 
-    L.stack[L.top++] = L.l_G.intern(lua.to_luastring(`${buff}:${line}: ${msg}`)); // We don't need to check for overflow here
+    L.stack[L.top++] = L.l_G.intern(lua.to_luastring(`${lobject.jsstring(buff)}:${line}: ${lobject.jsstring(msg)}`));
 
     return L.stack[L.top - 1];
 };
@@ -500,7 +500,7 @@ const luaG_tointerror = function(L, p1, p2) {
     let temp = lvm.tointeger(p1);
     if (temp === false)
         p2 = p1;
-    luaG_runerror(L, `number${varinfo(L, p2)} has no integer representation`);
+    luaG_runerror(L, lua.to_luastring(`number${lobject.jsstring(varinfo(L, p2))} has no integer representation`));
 };
 
 module.exports.lua_getstack     = lua_getstack;
diff --git a/src/ldo.js b/src/ldo.js
index 50d455e..54d1ac0 100644
--- a/src/ldo.js
+++ b/src/ldo.js
@@ -448,9 +448,9 @@ const lua_yieldk = function(L, nresults, ctx, k) {
 
     if (L.nny > 0) {
         if (L !== L.l_G.mainthread)
-            ldebug.luaG_runerror(L, "attempt to yield across a JS-call boundary");
+            ldebug.luaG_runerror(L, lua.to_luastring("attempt to yield across a JS-call boundary"));
         else
-            ldebug.luaG_runerror(L, "attempt to yield from outside a coroutine");
+            ldebug.luaG_runerror(L, lua.to_luastring("attempt to yield from outside a coroutine"));
     }
 
     L.status = TS.LUA_YIELD;
@@ -519,8 +519,8 @@ class SParser {
 }
 
 const checkmode = function(L, mode, x) {
-    if (mode && mode.indexOf(x.charAt(0)) === -1) {
-        lapi.lua_pushstring(L, `attempt to load a ${x} chunk (mode is '${mode}')`);
+    if (mode && mode.indexOf(x.charCodeAt(0)) === -1) {
+        lapi.lua_pushstring(L, lua.to_luastring(`attempt to load a ${lobject.jsstring(x)} chunk (mode is '${mode}')`));
         luaD_throw(L, TS.LUA_ERRSYNTAX);
     }
 };
diff --git a/src/ljstype.js b/src/ljstype.js
index 7de9d9a..6f371bb 100644
--- a/src/ljstype.js
+++ b/src/ljstype.js
@@ -3,27 +3,27 @@
 const assert = require('assert');
 
 const lisdigit = function(c) {
-    return typeof c === 'string' && /^\d$/.test(c.charAt(0));
+    return /^\d$/.test(String.fromCharCode(c));
 };
 
 const lisxdigit = function(c) {
-    return typeof c === 'string' && /^[0-9a-fA-F]$/.test(c.charAt(0));
+    return /^[0-9a-fA-F]$/.test(String.fromCharCode(c));
 };
 
 const lisspace = function(c) {
-    return typeof c === 'string' && /^\s$/.test(c.charAt(0));
+    return /^\s$/.test(String.fromCharCode(c));
 };
 
 const lislalpha = function(c) {
-    return typeof c === 'string' && /^[_a-zA-Z]$/.test(c.charAt(0));
+    return /^[_a-zA-Z]$/.test(String.fromCharCode(c));
 };
 
 const lislalnum = function(c) {
-    return typeof c === 'string' && /^[_a-zA-Z0-9]$/.test(c.charAt(0));
+    return /^[_a-zA-Z0-9]$/.test(String.fromCharCode(c));
 };
 
 module.exports.lisdigit   = lisdigit;
 module.exports.lislalnum  = lislalnum;
 module.exports.lislalpha  = lislalpha;
 module.exports.lisspace   = lisspace;
-module.exports.lisxdigit  = lisxdigit;
\ No newline at end of file
+module.exports.lisxdigit  = lisxdigit;
diff --git a/src/llex.js b/src/llex.js
index 2d34b1f..9b81cd7 100644
--- a/src/llex.js
+++ b/src/llex.js
@@ -81,7 +81,7 @@ class MBuffer {
         this.reader = reader ? reader : null;
 
         if (!this.reader) {
-            this.buffer = typeof data === "string" ? data.split('') : (data ? data : []);
+            this.buffer = typeof data === "string" ? lua.to_luastring(data) : (data ? data : []);
             this.n = this.buffer instanceof DataView ? this.buffer.byteLength : this.buffer.length;
             this.off = 0;
         }
@@ -97,7 +97,7 @@ class MBuffer {
     fill() {
         if (this.reader) {
             this.buffer = this.reader(this.L, this.data);
-            this.buffer = typeof this.buffer === "string" ? this.buffer.split('') : this.buffer;
+            this.buffer = typeof this.buffer === "string" ? lua.to_luastring(this.buffer) : this.buffer;
             if (this.buffer === null)
                 return -1;
             this.n = this.buffer instanceof DataView ? this.buffer.byteLength - 1 : this.buffer.length - 1;
@@ -150,25 +150,25 @@ const save = function(ls, c) {
     let b = ls.buff;
     if (b.n + 1 > b.buffer.length) {
         if (b.buffer.length >= Number.MAX_SAFE_INTEGER/2)
-            lexerror(ls, "lexical element too long", 0);
+            lexerror(ls, lua.to_luastring("lexical element too long"), 0);
     }
     b.buffer[b.n++] = c < 0 ? 255 + c + 1 : c;
 };
 
 const luaX_token2str = function(ls, token) {
     if (typeof token === "string" || token < FIRST_RESERVED) {  /* single-byte symbols? */
-        return `'${typeof token === "string" ? token : String.fromCharCode(token)}'`;
+        return lua.to_luastring(`'${typeof token === "string" ? token : lobject.jsstring(token)}'`);
     } else {
         let s = luaX_tokens[token - FIRST_RESERVED];
         if (token < R.TK_EOS)  /* fixed format (symbols and reserved words)? */
-            return `'${s}'`;
+            return lua.to_luastring(`'${s}'`);
         else  /* names, strings, and numerals */
-            return s;
+            return lua.to_luastring(s);
     }
 };
 
 const currIsNewline = function(ls) {
-    return ls.current === '\n' || ls.current === '\r';
+    return ls.current === '\n'.charCodeAt(0) || ls.current === '\r'.charCodeAt(0);
 };
 
 const next = function(ls) {
@@ -191,7 +191,7 @@ const inclinenumber = function(ls) {
     if (currIsNewline(ls) && ls.current !== old)
         next(ls);  /* skip '\n\r' or '\r\n' */
     if (++ls.linenumber >= Number.MAX_SAFE_INTEGER)
-        lexerror(ls, "chunk has too many lines", 0);
+        lexerror(ls, lua.to_luastring("chunk has too many lines"), 0);
 };
 
 const luaX_setinput = function(L, ls, z, source, firstchar) {
@@ -222,7 +222,7 @@ const luaX_setinput = function(L, ls, z, source, firstchar) {
 };
 
 const check_next1 = function(ls, c) {
-    if (ls.current === c) {
+    if (ls.current === c.charCodeAt(0)) {
         next(ls);
         return true;
     }
@@ -235,7 +235,7 @@ const check_next1 = function(ls, c) {
 ** saves it
 */
 const check_next2 = function(ls, set) {
-    if (ls.current === set.charAt(0) || ls.current === set.charAt(1)) {
+    if (ls.current === set.charAt(0).charCodeAt(0) || ls.current === set.charAt(1).charCodeAt(0)) {
         save_and_next(ls);
         return true;
     }
@@ -261,11 +261,11 @@ const read_numeral = function(ls, seminfo) {
         else break;
     }
 
-    save(ls, '\0');
+    save(ls, 0);
 
     let obj = lobject.luaO_str2num(ls.buff.buffer);
     if (obj === false)  /* format error? */
-        lexerror(ls, "malformed number", R.TK_FLT);
+        lexerror(ls, lua.to_luastring("malformed number"), R.TK_FLT);
     if (obj.ttisinteger()) {
         seminfo.i = obj.value;
         return R.TK_INT;
@@ -280,8 +280,8 @@ const txtToken = function(ls, token) {
     switch (token) {
         case R.TK_NAME: case R.TK_STRING:
         case R.TK_FLT: case R.TK_INT:
-            save(ls, '\0');
-            return `'${ls.buff.buffer.join('')}'`;
+            save(ls, 0);
+            return lua.to_luastring(`'${lobject.jsstring(ls.buff.buffer)}'`);
         default:
             return luaX_token2str(ls, token);
     }
@@ -290,12 +290,12 @@ const txtToken = function(ls, token) {
 const lexerror = function(ls, msg, token) {
     msg = ldebug.luaG_addinfo(ls.L, msg, ls.source, ls.linenumber);
     if (token)
-        lapi.lua_pushstring(ls.L, `${msg instanceof TValue ? msg.value : msg} near ${txtToken(ls, token)}`);
+        lapi.lua_pushstring(ls.L, lua.to_luastring(`${msg instanceof TValue ? msg.jsstring() : msg} near ${lobject.jsstring(txtToken(ls, token))}`));
     ldo.luaD_throw(ls.L, TS.LUA_ERRSYNTAX);
 };
 
 const luaX_syntaxerror = function(ls, msg) {
-    msg = msg instanceof TValue ? msg.value : msg;
+    msg = msg instanceof TValue ? msg.value : lua.to_luastring(msg);
     lexerror(ls, msg, ls.t.token);
 };
 
@@ -307,9 +307,9 @@ const luaX_syntaxerror = function(ls, msg) {
 const skip_sep = function(ls) {
     let count = 0;
     let s = ls.current;
-    assert(s === '[' || s === ']');
+    assert(s === '['.charCodeAt(0) || s === ']'.charCodeAt(0));
     save_and_next(ls);
-    while (ls.current === '=') {
+    while (ls.current === '='.charCodeAt(0)) {
         save_and_next(ls);
         count++;
     }
@@ -329,18 +329,18 @@ const read_long_string = function(ls, seminfo, sep) {
             case -1: {  /* error */
                 let what = seminfo ? "string" : "comment";
                 let msg = `unfinished long ${what} (starting at line ${line})`;
-                lexerror(ls, msg, R.TK_EOS);
+                lexerror(ls, lua.to_luastring(msg), R.TK_EOS);
                 break;
             }
-            case ']': {
+            case ']'.charCodeAt(0): {
                 if (skip_sep(ls) === sep) {
                     save_and_next(ls);  /* skip 2nd ']' */
                     skip = true;
                 }
                 break;
             }
-            case '\n': case '\r': {
-                save(ls, '\n');
+            case '\n'.charCodeAt(0): case '\r'.charCodeAt(0): {
+                save(ls, '\n'.charCodeAt(0));
                 inclinenumber(ls);
                 if (!seminfo) {
                     ls.buff.n = 0;
@@ -358,11 +358,7 @@ const read_long_string = function(ls, seminfo, sep) {
     if (seminfo)
         seminfo.ts = new TValue(
             CT.LUA_TLNGSTR,
-            lua.to_luastring(
-                ls.buff.buffer
-                    .slice(2 + sep, 2 + sep - 2 * (2 + sep))
-                    .join('')
-            )
+            ls.buff.buffer.slice(2 + sep, 2 + sep - 2 * (2 + sep))
         );
 };
 
@@ -376,7 +372,7 @@ const esccheck = function(ls, c, msg) {
 
 const gethexa = function(ls) {
     save_and_next(ls);
-    esccheck(ls, ljstype.lisxdigit(ls.current), "hexadecimal digit expected");
+    esccheck(ls, ljstype.lisxdigit(ls.current), lua.to_luastring("hexadecimal digit expected"));
     return lobject.luaO_hexavalue(ls.current);
 };
 
@@ -390,17 +386,17 @@ const readhexaesc = function(ls) {
 const readutf8desc = function(ls) {
     let i = 4;  /* chars to be removed: '\', 'u', '{', and first digit */
     save_and_next(ls);  /* skip 'u' */
-    esccheck(ls, ls.current === '{', "missing '{'");
+    esccheck(ls, ls.current === '{'.charCodeAt(0), lua.to_luastring("missing '{'"));
     let r = gethexa(ls);  /* must have at least one digit */
 
     save_and_next(ls);
     while (ljstype.lisxdigit(ls.current)) {
         i++;
         r = (r << 4) + lobject.luaO_hexavalue(ls.current);
-        esccheck(ls, r <= 0x10FFFF, "UTF-8 value too large");
+        esccheck(ls, r <= 0x10FFFF, lua.to_luastring("UTF-8 value too large"));
         save_and_next(ls);
     }
-    esccheck(ls, ls.current === '}', "missing '}'");
+    esccheck(ls, ls.current === '}'.charCodeAt(0), lua.to_luastring("missing '}'"));
     next(ls);  /* skip '}' */
     ls.buff.n -= i;  /* remove saved chars from buffer */
     return r;
@@ -420,7 +416,7 @@ const readdecesc = function(ls) {
         r = 10 * r + parseInt(ls.current);
         save_and_next(ls);
     }
-    esccheck(ls, r <= 255, "decimal escape too large");
+    esccheck(ls, r <= 255, lua.to_luastring("decimal escape too large"));
     ls.buff.n -= i;  /* remove read digits from buffer */
     return r;
 };
@@ -431,29 +427,29 @@ const read_string = function(ls, del, seminfo) {
     while (ls.current !== del) {
         switch (ls.current) {
             case -1:
-                lexerror(ls, "unfinished string", R.TK_EOS);
+                lexerror(ls, lua.to_luastring("unfinished string"), R.TK_EOS);
                 break;
-            case '\n':
-            case '\r':
-                lexerror(ls, "unfinished string", R.TK_STRING);
+            case '\n'.charCodeAt(0):
+            case '\r'.charCodeAt(0):
+                lexerror(ls, lua.to_luastring("unfinished string"), R.TK_STRING);
                 break;
-            case '\\': {  /* escape sequences */
+            case '\\'.charCodeAt(0): {  /* escape sequences */
                 save_and_next(ls);  /* keep '\\' for error messages */
                 let will;
                 let c;
                 switch(ls.current) {
-                    case 'a': c = '\a'; will = 'read_save'; break;
-                    case 'b': c = '\b'; will = 'read_save'; break;
-                    case 'f': c = '\f'; will = 'read_save'; break;
-                    case 'n': c = '\n'; will = 'read_save'; break;
-                    case 'r': c = '\r'; will = 'read_save'; break;
-                    case 't': c = '\t'; will = 'read_save'; break;
-                    case 'v': c = '\v'; will = 'read_save'; break;
+                    case 'a': c = '\a'.charCodeAt(0); will = 'read_save'; break;
+                    case 'b': c = '\b'.charCodeAt(0); will = 'read_save'; break;
+                    case 'f': c = '\f'.charCodeAt(0); will = 'read_save'; break;
+                    case 'n': c = '\n'.charCodeAt(0); will = 'read_save'; break;
+                    case 'r': c = '\r'.charCodeAt(0); will = 'read_save'; break;
+                    case 't': c = '\t'.charCodeAt(0); will = 'read_save'; break;
+                    case 'v': c = '\v'.charCodeAt(0); will = 'read_save'; break;
                     case 'x': c = readhexaesc(ls); will = 'read_save'; break;
                     case 'u': utf8esc(ls); will = 'no_save'; break;
-                    case '\n': case '\r':
+                    case '\n'.charCodeAt(0): case '\r'.charCodeAt(0):
                         inclinenumber(ls); c = '\n'; will = 'only_save'; break;
-                    case '\\': case '\"': case '\'':
+                    case '\\'.charCodeAt(0): case '\"'.charCodeAt(0): case '\''.charCodeAt(0):
                         c = ls.current; will = 'read_save'; break;
                     case -1: will = 'no_save'; break;  /* will raise an error next loop */
                     case 'z': {  /* zap following span of spaces */
@@ -466,7 +462,7 @@ const read_string = function(ls, del, seminfo) {
                         will = 'no_save'; break;
                     }
                     default: {
-                        esccheck(ls, ljstype.lisdigit(ls.current), "invalid escape sequence");
+                        esccheck(ls, ljstype.lisdigit(ls.current), lua.to_luastring("invalid escape sequence"));
                         c = readdecesc(ls);  /* digital escape '\ddd' */
                         will = 'only_save'; break;
                     }
@@ -490,10 +486,7 @@ const read_string = function(ls, del, seminfo) {
 
     seminfo.ts = new TValue(
         CT.LUA_TLNGSTR,
-        ls.buff.buffer
-            .slice(1, ls.buff.n-1)
-            .map(e => typeof e === "string" ? lua.to_luastring(e) : [e])
-            .reduce((acc, e) => acc = acc.concat(e), [])  /* Hex value must not be converted */
+        ls.buff.buffer.slice(1, ls.buff.n-1)
     );
 };
 
@@ -507,20 +500,20 @@ const llex = function(ls, seminfo) {
 
     for (;;) {
         switch (ls.current) {
-            case '\n': case '\r': {  /* line breaks */
+            case '\n'.charCodeAt(0): case '\r'.charCodeAt(0): {  /* line breaks */
                 inclinenumber(ls);
                 break;
             }
-            case ' ': case '\f': case '\t': case '\v': {  /* spaces */
+            case ' '.charCodeAt(0): case '\f'.charCodeAt(0): case '\t'.charCodeAt(0): case '\v'.charCodeAt(0): {  /* spaces */
                 next(ls);
                 break;
             }
-            case '-': {  /* '-' or '--' (comment) */
+            case '-'.charCodeAt(0): {  /* '-' or '--' (comment) */
                 next(ls);
-                if (ls.current !== '-') return '-';
+                if (ls.current !== '-'.charCodeAt(0)) return '-';
                 /* else is a comment */
                 next(ls);
-                if (ls.current === '[') {  /* long comment? */
+                if (ls.current === '['.charCodeAt(0)) {  /* long comment? */
                     let sep = skip_sep(ls);
                     ls.buff.n = 0;  /* 'skip_sep' may dirty the buffer */
                     ls.buff.buffer = [];
@@ -537,52 +530,52 @@ const llex = function(ls, seminfo) {
                     next(ls);  /* skip until end of line (or end of file) */
                 break;
             }
-            case '[': {  /* long string or simply '[' */
+            case '['.charCodeAt(0): {  /* long string or simply '[' */
                 let sep = skip_sep(ls);
                 if (sep >= 0) {
                     read_long_string(ls, seminfo, sep);
                     return R.TK_STRING;
                 } else if (sep !== -1)  /* '[=...' missing second bracket */
-                    lexerror(ls, "invalid long string delimiter", R.TK_STRING);
+                    lexerror(ls, lua.to_luastring("invalid long string delimiter"), R.TK_STRING);
                 return '[';
             }
-            case '=': {
+            case '='.charCodeAt(0): {
                 next(ls);
                 if (check_next1(ls, '=')) return R.TK_EQ;
                 else return '=';
             }
-            case '<': {
+            case '<'.charCodeAt(0): {
                 next(ls);
                 if (check_next1(ls, '=')) return R.TK_LE;
                 else if (check_next1(ls, '<')) return R.TK_SHL;
                 else return '<';
             }
-            case '>': {
+            case '>'.charCodeAt(0): {
                 next(ls);
                 if (check_next1(ls, '=')) return R.TK_GE;
                 else if (check_next1(ls, '>')) return R.TK_SHR;
                 else return '>';
             }
-            case '/': {
+            case '/'.charCodeAt(0): {
                 next(ls);
                 if (check_next1(ls, '/')) return R.TK_IDIV;
                 else return '/';
             }
-            case '~': {
+            case '~'.charCodeAt(0): {
                 next(ls);
                 if (check_next1(ls, '=')) return R.TK_NE;
                 else return '~';
             }
-            case ':': {
+            case ':'.charCodeAt(0): {
                 next(ls);
                 if (check_next1(ls, ':')) return R.TK_DBCOLON;
                 else return ':';
             }
-            case '"': case '\'': {  /* short literal strings */
+            case '"'.charCodeAt(0): case '\''.charCodeAt(0): {  /* short literal strings */
                 read_string(ls, ls.current, seminfo);
                 return R.TK_STRING;
             }
-            case '.': {  /* '.', '..', '...', or number */
+            case '.'.charCodeAt(0): {  /* '.', '..', '...', or number */
                 save_and_next(ls);
                 if (check_next1(ls, '.')) {
                     if (check_next1(ls, '.'))
diff --git a/src/lmathlib.js b/src/lmathlib.js
index 7553dfd..500331b 100644
--- a/src/lmathlib.js
+++ b/src/lmathlib.js
@@ -37,13 +37,13 @@ const math_random = function(L) {
             up = lauxlib.luaL_checkinteger(L, 2);
             break;
         }
-        default: return lauxlib.luaL_error(L, "wrong number of arguments");
+        default: return lauxlib.luaL_error(L, lua.to_luastring("wrong number of arguments"));
     }
 
     /* random integer in the interval [low, up] */
-    lauxlib.luaL_argcheck(L, low <= up, 1, "interval is empty");
+    lauxlib.luaL_argcheck(L, low <= up, 1, lua.to_luastring("interval is empty"));
     lauxlib.luaL_argcheck(L, low >= 0 || up <= Number.MAX_SAFE_INTEGER + low, 1,
-            "interval too large");
+            lua.to_luastring("interval too large"));
 
     r *= (up - low) + 1;
     lapi.lua_pushinteger(L, r + low);
@@ -173,7 +173,7 @@ const math_rad = function(L) {
 const math_min = function(L) {
     let n = lapi.lua_gettop(L);  /* number of arguments */
     let imin = 1;  /* index of current minimum value */
-    lauxlib.luaL_argcheck(L, n >= 1, 1, "value expected");
+    lauxlib.luaL_argcheck(L, n >= 1, 1, lua.to_luastring("value expected"));
     for (let i = 2; i <= n; i++){
         if (lapi.lua_compare(L, i, imin, lua.LUA_OPLT))
             imin = i;
@@ -185,7 +185,7 @@ const math_min = function(L) {
 const math_max = function(L) {
     let n = lapi.lua_gettop(L);  /* number of arguments */
     let imax = 1;  /* index of current minimum value */
-    lauxlib.luaL_argcheck(L, n >= 1, 1, "value expected");
+    lauxlib.luaL_argcheck(L, n >= 1, 1, lua.to_luastring("value expected"));
     for (let i = 2; i <= n; i++){
         if (lapi.lua_compare(L, imax, i, lua.LUA_OPLT))
             imax = i;
@@ -211,7 +211,7 @@ const math_fmod = function(L) {
     if (lapi.lua_isinteger(L, 1) && lapi.lua_isinteger(L, 2)) {
         let d = lapi.lua_tointeger(L, 2);
         if (Math.abs(d) + 1 <= 1) {
-            lauxlib.luaL_argcheck(L, d !== 0, 2, "zero");
+            lauxlib.luaL_argcheck(L, d !== 0, 2, lua.to_luastring("zero"));
             lapi.lua_pushinteger(L, 0);
         } else
             lapi.lua_pushinteger(L, lapi.lua_tointeger(L, 1) % d);
@@ -265,13 +265,13 @@ const mathlib = {
 const luaopen_math = function(L) {
     lauxlib.luaL_newlib(L, mathlib);
     lapi.lua_pushnumber(L, Math.PI);
-    lapi.lua_setfield(L, -2, "pi");
+    lapi.lua_setfield(L, -2, lua.to_luastring("pi"));
     lapi.lua_pushnumber(L, Number.MAX_VALUE);
-    lapi.lua_setfield(L, -2, "huge");
+    lapi.lua_setfield(L, -2, lua.to_luastring("huge"));
     lapi.lua_pushinteger(L, Number.MAX_SAFE_INTEGER);
-    lapi.lua_setfield(L, -2, "maxinteger");
+    lapi.lua_setfield(L, -2, lua.to_luastring("maxinteger"));
     lapi.lua_pushinteger(L, Number.MIN_SAFE_INTEGER);
-    lapi.lua_setfield(L, -2, "mininteger");
+    lapi.lua_setfield(L, -2, lua.to_luastring("mininteger"));
     return 1;
 };
 
diff --git a/src/lobject.js b/src/lobject.js
index 966bdd8..812ac6a 100644
--- a/src/lobject.js
+++ b/src/lobject.js
@@ -278,49 +278,49 @@ class LocVar {
     }
 }
 
-const RETS = "...";
-const PRE  = "[string \"";
-const POS  = "\"]";
+const RETS = lua.to_luastring("...");
+const PRE  = lua.to_luastring("[string \"");
+const POS  = lua.to_luastring("\"]");
 
 const luaO_chunkid = function(source, bufflen) {
-    source = source instanceof TValue ? source.jsstring() : source;
+    source = source instanceof TValue ? source.value : source;
     bufflen = bufflen instanceof TValue ? bufflen.value : bufflen;
     let l = source.length;
-    let out = "";
-    if (source.charAt(0) === '=') {  /* 'literal' source */
+    let out = [];
+    if (source[0] === '='.charCodeAt(0)) {  /* 'literal' source */
         if (l < bufflen)  /* small enough? */
-            out = `${source.slice(1)}`;
+            out = source.slice(1);
         else {  /* truncate it */
-            out += `${source.slice(1, bufflen)}`;
+            out = out.concat(source.slice(1, bufflen));
         }
     } else if (source.charAt(0) === '@') {  /* file name */
         if (l <= bufflen)  /* small enough? */
-            out = `${source.slice(1)}`;
+            out = source.slice(1);
         else {  /* add '...' before rest of name */
             bufflen -= RETS.length;
-            out = `${RETS}${source.slice(1, l - bufflen)}`;
+            out = RETS.concat(source.slice(1, l - bufflen));
         }
     } else {  /* string; format as [string "source"] */
         let nli = source.indexOf('\n');  /* find first new line (if any) */
         let nl = nli ? source.slice(nli) : null;
-        out = `${PRE}`;  /* add prefix */
+        out = PRE;  /* add prefix */
         bufflen -= PRE.length + RETS.length + POS.length + 1;  /* save space for prefix+suffix+'\0' */
         if (l < bufflen && nl === null) {  /* small one-line source? */
-            out += `${source}`;  /* keep it */
+            out = out.conat(source);  /* keep it */
         } else {
             if (nl !== null) l = nl.length - source.length;  /* stop at first newline */
             if (l > bufflen) l = bufflen;
-            out += `${source}${RETS}`;
+            out = out.concat(source).concat(RETS);
         }
-        out += POS;
+        out = out.concat(POS);
     }
 
     return out;
 };
 
 const luaO_hexavalue = function(c) {
-    if (ljstype.lisdigit(c)) return c.charCodeAt(0) - '0'.charCodeAt(0);
-    else return (c.toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0)) + 10;
+    if (ljstype.lisdigit(c)) return c - '0'.charCodeAt(0);
+    else return (String.fromCharCode(c).toLowerCase().charCodeAt(0) - 'a'.charCodeAt(0)) + 10;
 };
 
 const UTF8BUFFSZ = 8;
@@ -329,15 +329,15 @@ const luaO_utf8desc = function(buff, x) {
     let n = 1;  /* number of bytes put in buffer (backwards) */
     assert(x <= 0x10FFFF);
     if (x < 0x80)  /* ascii? */
-        buff[UTF8BUFFSZ - 1] = String.fromCharCode(x);
+        buff[UTF8BUFFSZ - 1] = x;
     else {  /* need continuation bytes */
         let mfb = 0x3f;  /* maximum that fits in first byte */
         do {
-            buff[UTF8BUFFSZ - (n++)] = String.fromCharCode(0x80 | (x & 0x3f));
+            buff[UTF8BUFFSZ - (n++)] = 0x80 | (x & 0x3f);
             x >>= 6;  /* remove added bits */
             mfb >>= 1;  /* now there is one less bit available in first byte */
         } while (x > mfb);  /* still needs continuation byte? */
-        buff[UTF8BUFFSZ - n] = String.fromCharCode((~mfb << 1) | x);  /* add first byte */
+        buff[UTF8BUFFSZ - n] = (~mfb << 1) | x;  /* add first byte */
     }
     return n;
 };
@@ -383,19 +383,19 @@ const lua_strx2number = function(s) {
     let neg;  /* 1 if number is negative */
     let hasdot = false;  /* true after seen a dot */
 
-    while (ljstype.lisspace(s.charAt(0))) s = s.slice(1);  /* skip initial spaces */
+    while (ljstype.lisspace(s)) s = s.slice(1);  /* skip initial spaces */
 
-    neg = s.charAt(0) === '-';  /* check signal */
-    s = neg || s.charAt(0) === '+' ? s.slice(1) : s;  /* skip sign if one */
-    if (!(s.charAt(0) === '0' && (s.charAt(1) === 'x' || s.charAt(1) === 'X')))  /* check '0x' */
+    neg = s[0] === '-'.charCodeAt(0);  /* check signal */
+    s = neg || s[0] === '+'.charCodeAt(0) ? s.slice(1) : s;  /* skip sign if one */
+    if (!(s[0] === '0' && (s[1] === 'x'.charCodeAt(0) || s[1] === 'X'.charCodeAt(0))))  /* check '0x' */
         return 0.0;  /* invalid format (no '0x') */
 
     for (s = s.slice(2); ; s = s.slice(1)) {  /* skip '0x' and read numeral */
-        if (s.charAt(0) === dot) {
+        if (s[0] === dot) {
             if (hasdot) break;  /* second dot? stop loop */
             else hasdot = true;
-        } else if (ljstype.lisxdigit(s.charAt(0))) {
-            if (sigdig === 0 && s.charAt(0) === '0')  /* non-significant digit (zero)? */
+        } else if (ljstype.lisxdigit(s[0])) {
+            if (sigdig === 0 && s[0] === '0'.charCodeAt(0))  /* non-significant digit (zero)? */
                 nosigdig++;
             else if (++sigdig <= MAXSIGDIG)  /* can read it without overflow? */
                 r = (r * 16) + luaO_hexavalue(s);
@@ -407,35 +407,35 @@ const lua_strx2number = function(s) {
     if (nosigdig + sigdig === 0)  /* no digits? */
         return 0.0;  /* invalid format */
     e *= 4;  /* each digit multiplies/divides value by 2^4 */
-    if (s.charAt(0) === 'p' || s.charAt(0) === 'P') {  /* exponent part? */
+    if (s[0] === 'p'.charCodeAt(0) || s[0] === 'P'.charCodeAt(0)) {  /* exponent part? */
         let exp1 = 0;  /* exponent value */
         let neg1;  /* exponent signal */
         s = s.slice(1);  /* skip 'p' */
-        neg1 = s.charAt(0) === '-';  /* check signal */
-        s = neg1 || s.charAt(0) === '+' ? s.slice(1) : s;  /* skip sign if one */
-        if (!ljstype.lisdigit(s.charAt(0)))
+        neg1 = s[0] === '-'.charCodeAt(0);  /* check signal */
+        s = neg1 || s[0] === '+'.charCodeAt(0) ? s.slice(1) : s;  /* skip sign if one */
+        if (!ljstype.lisdigit(s[0]))
             return 0.0;  /* invalid; must have at least one digit */
-        while (ljstype.lisdigit(s.charAt(0))) {  /* read exponent */
-            exp1 = exp1 * 10 + s.charCodeAt(0) - '0'.charCodeAt(0);
+        while (ljstype.lisdigit(s[0])) {  /* read exponent */
+            exp1 = exp1 * 10 + s - '0'.charCodeAt(0);
             s = s.slice(1);
         }
         if (neg1) exp1 = -exp1;
         e += exp1;
     }
     if (neg) r = -r;
-    return s.trim().search(/s/) < 0 ? ldexp(r, e) : null;  /* Only valid if nothing left is s*/
+    return jsstring(s).trim().search(/s/) < 0 ? ldexp(r, e) : null;  /* Only valid if nothing left is s*/
 };
 
 const l_str2dloc = function(s, mode) {
-    let flt = mode === 'x' ? lua_strx2number(s) : parseFloat(s);
+    let flt = mode === 'x' ? lua_strx2number(s) : parseFloat(jsstring(s));
     return !isNaN(flt) ? flt : null;  /* OK if no trailing characters */
 };
 
 const l_str2d = function(s) {
-    let pidx = /[.xXnN]/g.exec(s);
+    let pidx = /[.xXnN]/g.exec(String.fromCharCode(...s));
     pidx = pidx ? pidx.index : null;
     let pmode = pidx ? s[pidx] : null;
-    let mode = pmode ? pmode.toLowerCase() : 0;
+    let mode = pmode ? String.fromCharCode(pmode).toLowerCase() : 0;
     if (mode === 'n')  /* reject 'inf' and 'nan' */
         return null;
     let end = l_str2dloc(s, mode);  /* try to convert */
@@ -454,12 +454,12 @@ const l_str2int = function(s) {
     let neg;
 
     while (ljstype.lisspace(s[0])) s = s.slice(1);  /* skip initial spaces */
-    neg = s[0] === '-';
+    neg = s[0] === '-'.charCodeAt(0);
 
-    if (neg || s[0] === '+')
+    if (neg || s[0] === '+'.charCodeAt(0))
         s = s.slice(1);
 
-    if (s[0] === '0' && (s[1] === 'x' || s[1] === 'X')) {  /* hex? */
+    if (s[0] === '0'.charCodeAt(0) && (s[1] === 'x'.charCodeAt(0) || s[1] === 'X'.charCodeAt(0))) {  /* hex? */
         s = s.slice(2);  /* skip '0x' */
 
         for (; ljstype.lisxdigit(s[0]); s = s.slice(1)) {
@@ -468,7 +468,7 @@ const l_str2int = function(s) {
         }
     } else {  /* decimal */
         for (; ljstype.lisdigit(s[0]); s = s.slice(1)) {
-            let d = parseInt(s[0]);
+            let d = s[0] - '0'.charCodeAt(0);
             if (a >= MAXBY10 && (a > MAXBY10 || d > MAXLASTD + neg))  /* overflow? */
                 return null;  /* do not accept it (as integer) */
             a = a * 10 + d;
@@ -478,7 +478,7 @@ const l_str2int = function(s) {
 
     while (ljstype.lisspace(s[0])) s = s.slice(1);  /* skip trailing spaces */
 
-    if (empty || s[0] !== "\0") return null;  /* something wrong in the numeral */
+    if (empty || s[0] !== 0) return null;  /* something wrong in the numeral */
     else {
         return neg ? -a : a;
     }
@@ -490,7 +490,7 @@ const luaO_str2num = function(s) {
     if (s2i !== null) {   /* try as an integer */
         return new TValue(CT.LUA_TNUMINT, s2i);
     } else {   /* else try as a float */
-        s2i = l_str2d(s.join(''));
+        s2i = l_str2d(s);
 
         if (s2i !== null) {
             return new TValue(CT.LUA_TNUMFLT, s2i);
diff --git a/src/lparser.js b/src/lparser.js
index 1806bbe..01b9a08 100644
--- a/src/lparser.js
+++ b/src/lparser.js
@@ -159,7 +159,7 @@ const semerror = function(ls, msg) {
 };
 
 const error_expected = function(ls, token) {
-    llex.luaX_syntaxerror(ls, `${llex.luaX_token2str(ls, token)} expected`);
+    llex.luaX_syntaxerror(ls, lua.to_luastring(`${lobject.jsstring(llex.luaX_token2str(ls, token))} expected`));
 };
 
 const errorlimit = function(fs, limit, what) {
@@ -167,7 +167,7 @@ const errorlimit = function(fs, limit, what) {
     let line = fs.f.linedefined;
     let where = (line === 0) ? "main function" : `function at line ${line}`;
     let msg = `too many ${what} (limit is ${limit}) in ${where}`;
-    llex.luaX_syntaxerror(fs.ls, msg);
+    llex.luaX_syntaxerror(fs.ls, lua.to_luastring(msg));
 };
 
 const checklimit = function(fs, v, l, what) {
@@ -204,7 +204,7 @@ const check_match = function(ls, what, who, where) {
             error_expected(ls, what);
         else
             llex.luaX_syntaxerror(ls,
-                `${llex.luaX_token2str(ls, what)} expected (to close ${llex.luaX_token2str(ls, who)} at line ${where}`);
+                lua.to_luastring(`${lobject.jsstring(llex.luaX_token2str(ls, what))} expected (to close ${lobject.jsstring(llex.luaX_token2str(ls, who))} at line ${where}`));
     }
 };
 
@@ -241,7 +241,7 @@ const new_localvar = function(ls, name) {
     let fs = ls.fs;
     let dyd = ls.dyd;
     let reg = registerlocalvar(ls, name);
-    checklimit(fs, dyd.actvar.n + 1 - fs.firstlocal, MAXVARS, "local variables");
+    checklimit(fs, dyd.actvar.n + 1 - fs.firstlocal, MAXVARS, lua.to_luastring("local variables"));
     dyd.actvar.arr[dyd.actvar.n] = new Vardesc();
     dyd.actvar.arr[dyd.actvar.n].idx = reg;
     dyd.actvar.n++;
@@ -273,7 +273,7 @@ const removevars = function(fs, tolevel) {
 const searchupvalue = function(fs, name) {
     let up = fs.f.upvalues;
     for (let i = 0; i < fs.nups; i++) {
-        if (up[i].name.jsstring() === name.jsstring())
+        if (up[i].name.join() === name.join())
             return i;
     }
     return -1;  /* not found */
@@ -281,7 +281,7 @@ const searchupvalue = function(fs, name) {
 
 const newupvalue = function(fs, name, v) {
     let f = fs.f;
-    checklimit(fs, fs.nups + 1, lfunc.MAXUPVAL, "upvalues");
+    checklimit(fs, fs.nups + 1, lfunc.MAXUPVAL, lua.to_luastring("upvalues"));
     f.upvalues[fs.nups] = new UpVal(fs.ls.L);
     f.upvalues[fs.nups].instack = v.k === expkind.VLOCAL;
     f.upvalues[fs.nups].idx = v.u.info;
@@ -291,7 +291,7 @@ const newupvalue = function(fs, name, v) {
 
 const searchvar = function(fs, n) {
     for (let i = fs.nactvar - 1; i >= 0; i--) {
-        if (n.jsstring() === getlocvar(fs, i).varname.jsstring())
+        if (n.join() === getlocvar(fs, i).varname.join())
             return i;
     }
 
@@ -372,7 +372,7 @@ const adjust_assign = function(ls, nvars, nexps, e) {
 const enterlevel = function(ls) {
     let L = ls.L;
     ++L.nCcalls;
-    checklimit(ls.fs, L.nCcalls, llimit.LUAI_MAXCCALLS, "JS levels");
+    checklimit(ls.fs, L.nCcalls, llimit.LUAI_MAXCCALLS, lua.to_luastring("JS levels"));
 };
 
 const leavelevel = function(ls) {
@@ -383,10 +383,10 @@ const closegoto = function(ls, g, label) {
     let fs = ls.fs;
     let gl = ls.dyd.gt;
     let gt = gl.arr[g];
-    assert(gt.name.jsstring() === label.name.jsstring());
+    assert(gt.name.value.join() === label.name.value.join());
     if (gt.nactvar < label.nactvar) {
         let vname = getlocvar(fs, gt.nactvar).varname;
-        semerror(ls, `<goto ${gt.name.value}> at line ${gt.line} jumps into the scope of local '${vname.value}'`);
+        semerror(ls, lua.to_luastring(`<goto ${gt.name.jsstring()}> at line ${gt.line} jumps into the scope of local '${vname.jsstring()}'`));
     }
     lcode.luaK_patchlist(fs, gt.pc, label.pc);
     /* remove goto from pending list */
@@ -405,7 +405,7 @@ const findlabel = function(ls, g) {
     /* check labels in current block for a match */
     for (let i = bl.firstlabel; i < dyd.label.n; i++) {
         let lb = dyd.label.arr[i];
-        if (lb.name.jsstring() === gt.name.jsstring()) {  /* correct label? */
+        if (lb.name.value.join() === gt.name.value.join()) {  /* correct label? */
             if (gt.nactvar > lb.nactvar && (bl.upval || dyd.label.n > bl.firstlabel))
                 lcode.luaK_patchclose(ls.fs, gt.pc, lb.nactvar);
             closegoto(ls, g, lb);  /* close it */
@@ -434,7 +434,7 @@ const findgotos = function(ls, lb) {
     let gl = ls.dyd.gt;
     let i = ls.fs.bl.firstgoto;
     while (i < gl.n) {
-        if (gl.arr[i].name.jsstring() === lb.name.jsstring())
+        if (gl.arr[i].name.value.join() === lb.name.value.join())
             closegoto(ls, i, lb);
         else
             i++;
@@ -490,9 +490,9 @@ const breaklabel = function(ls) {
 */
 const undefgoto = function(ls, gt) {
     const msg = llex.isreserved(gt.name.value)
-                ? `<${gt.name.value}> at line ${gt.line} not inside a loop`
-                : `no visible label '${gt.name.value}' for <goto> at line ${gt.line}`;
-    semerror(ls, msg);
+                ? `<${gt.name.jsstring()}> at line ${gt.line} not inside a loop`
+                : `no visible label '${gt.name.jsstring()}' for <goto> at line ${gt.line}`;
+    semerror(ls, lua.to_luastring(msg));
 };
 
 /*
@@ -638,7 +638,7 @@ const recfield = function(ls, cc) {
     let val = new expdesc();
 
     if (ls.t.token === R.TK_NAME) {
-        checklimit(fs, cc.nh, Number.MAX_SAFE_INTEGER, "items in a constructor");
+        checklimit(fs, cc.nh, Number.MAX_SAFE_INTEGER, lua.to_luastring("items in a constructor"));
         checkname(ls, key);
     } else  /* ls->t.token === '[' */
         yindex(ls, key);
@@ -676,7 +676,7 @@ const lastlistfield = function(fs, cc) {
 const listfield = function(ls, cc) {
     /* listfield -> exp */
     expr(ls, cc.v);
-    checklimit(ls.fs, cc.na, Number.MAX_SAFE_INTEGER, "items in a constructor");
+    checklimit(ls.fs, cc.na, Number.MAX_SAFE_INTEGER, lua.to_luastring("items in a constructor"));
     cc.na++;
     cc.tostore++;
 };
@@ -748,7 +748,7 @@ const parlist = function(ls) {
                     f.is_vararg = 1;  /* declared vararg */
                     break;
                 }
-                default: llex.luaX_syntaxerror(ls, "<name> or '...' expected");
+                default: llex.luaX_syntaxerror(ls, lua.to_luastring("<name> or '...' expected"));
             }
         } while(!f.is_vararg && testnext(ls, ','));
     }
@@ -766,7 +766,7 @@ const body = function(ls, e, ismethod, line) {
     open_func(ls, new_fs, bl);
     checknext(ls, '(');
     if (ismethod) {
-        new_localvarliteral(ls, "self");  /* create 'self' parameter */
+        new_localvarliteral(ls, lua.to_luastring("self"));  /* create 'self' parameter */
         adjustlocalvars(ls, 1);
     }
     parlist(ls);
@@ -815,7 +815,7 @@ const funcargs = function(ls, f, line) {
             break;
         }
         default: {
-            llex.luaX_syntaxerror(ls, "function arguments expected");
+            llex.luaX_syntaxerror(ls, lua.to_luastring("function arguments expected"));
         }
     }
     assert(f.k === expkind.VNONRELOC);
@@ -855,7 +855,7 @@ const primaryexp = function(ls, v) {
             return;
         }
         default: {
-            llex.luaX_syntaxerror(ls, "unexpected symbol");
+            llex.luaX_syntaxerror(ls, lua.to_luastring("unexpected symbol"));
         }
     }
 };
@@ -929,7 +929,7 @@ const simpleexp = function(ls, v) {
         }
         case R.TK_DOTS: {  /* vararg */
             let fs = ls.fs;
-            check_condition(ls, fs.f.is_vararg, "cannot use '...' outside a vararg function");
+            check_condition(ls, fs.f.is_vararg, lua.to_luastring("cannot use '...' outside a vararg function"));
             init_exp(v, expkind.VVARARG, lcode.luaK_codeABC(fs, OpCodesI.OP_VARARG, 0, 1, 0));
             break;
         }
@@ -1101,14 +1101,14 @@ const check_conflict = function(ls, lh, v) {
 
 const assignment = function(ls, lh, nvars) {
     let e = new expdesc();
-    check_condition(ls, vkisvar(lh.v.k), "syntax error");
+    check_condition(ls, vkisvar(lh.v.k), lua.to_luastring("syntax error"));
     if (testnext(ls, ',')) {  /* assignment -> ',' suffixedexp assignment */
         let nv = new LHS_assign();
         nv.prev = lh;
         suffixedexp(ls, nv.v);
         if (nv.v.k !== expkind.VINDEXED)
             check_conflict(ls, lh, nv.v);
-        checklimit(ls.fs, nvars + ls.L.nCcalls, llimit.LUAI_MAXCCALLS, "JS levels");
+        checklimit(ls.fs, nvars + ls.L.nCcalls, llimit.LUAI_MAXCCALLS, lua.to_luastring("JS levels"));
         assignment(ls, nv, nvars + 1);
     } else {  /* assignment -> '=' explist */
         checknext(ls, '=');
@@ -1150,8 +1150,8 @@ const gotostat = function(ls, pc) {
 /* check for repeated labels on the same block */
 const checkrepeated = function(fs, ll, label) {
     for (let i = fs.bl.firstlabel; i < ll.n; i++) {
-        if (label.jsstring() === ll.arr[i].name.jsstring()) {
-            semerror(fs.ls, `label '${label}' already defined on line ${ll.arr[i].line}`);
+        if (label.value.join() === ll.arr[i].name.value.join()) {
+            semerror(fs.ls, lua.to_luastring(`label '${label.jsstring()}' already defined on line ${ll.arr[i].line}`));
         }
     }
 };
@@ -1252,9 +1252,9 @@ const fornum = function(ls, varname, line) {
     /* fornum -> NAME = exp1,exp1[,exp1] forbody */
     let fs = ls.fs;
     let base = fs.freereg;
-    new_localvarliteral(ls, "(for index)");
-    new_localvarliteral(ls, "(for limit)");
-    new_localvarliteral(ls, "(for step)");
+    new_localvarliteral(ls, lua.to_luastring("(for index)"));
+    new_localvarliteral(ls, lua.to_luastring("(for limit)"));
+    new_localvarliteral(ls, lua.to_luastring("(for step)"));
     new_localvar(ls, varname);
     checknext(ls, '=');
     exp1(ls);  /* initial value */
@@ -1276,9 +1276,9 @@ const forlist = function(ls, indexname) {
     let nvars = 4;  /* gen, state, control, plus at least one declared var */
     let base = fs.freereg;
     /* create control variables */
-    new_localvarliteral(ls, "(for generator)");
-    new_localvarliteral(ls, "(for state)");
-    new_localvarliteral(ls, "(for control)");
+    new_localvarliteral(ls, lua.to_luastring("(for generator)"));
+    new_localvarliteral(ls, lua.to_luastring("(for state)"));
+    new_localvarliteral(ls, lua.to_luastring("(for control)"));
     /* create declared variables */
     new_localvar(ls, indexname);
     while (testnext(ls, ',')) {
@@ -1302,7 +1302,7 @@ const forstat = function(ls, line) {
     switch (ls.t.token) {
         case '=': fornum(ls, varname, line); break;
         case ',': case R.TK_IN: forlist(ls, varname); break;
-        default: llex.luaX_syntaxerror(ls, "'=' or 'in' expected");
+        default: llex.luaX_syntaxerror(ls, lua.to_luastring("'=' or 'in' expected"));
     }
     check_match(ls, R.TK_END, R.TK_FOR, line);
     leaveblock(fs);  /* loop scope ('break' jumps to this point) */
@@ -1418,7 +1418,7 @@ const exprstat= function(ls) {
         assignment(ls, v, 1);
     }
     else {  /* stat -> func */
-        check_condition(ls, v.v.k === expkind.VCALL, "syntax error");
+        check_condition(ls, v.v.k === expkind.VCALL, lua.to_luastring("syntax error"));
         lopcode.SETARG_C(lcode.getinstruction(fs, v.v), 1);  /* call statement uses no results */
     }
 };
@@ -1565,4 +1565,4 @@ module.exports.Dyndata     = Dyndata;
 module.exports.expkind     = expkind;
 module.exports.expdesc     = expdesc;
 module.exports.luaY_parser = luaY_parser;
-module.exports.vkisinreg   = vkisinreg;
\ No newline at end of file
+module.exports.vkisinreg   = vkisinreg;
diff --git a/src/ltablib.js b/src/ltablib.js
index 20fdf95..c45f7e5 100644
--- a/src/ltablib.js
+++ b/src/ltablib.js
@@ -9,6 +9,7 @@ const lstate  = require('./lstate.js');
 const ldo     = require('./ldo.js');
 const ldebug  = require('./ldebug.js');
 const llimit  = require('./llimit.js');
+const lobject = require('./lobject.js');
 const CT      = lua.constant_types;
 const TS      = lua.thread_status;
 
@@ -35,9 +36,9 @@ const checktab = function(L, arg, what) {
     if (lapi.lua_type(L, arg) !== CT.LUA_TTABLE) {  /* is it not a table? */
         let n = 1;
         if (lapi.lua_getmetatable(L, arg) &&  /* must have metatable */
-            (!(what & TAB_R) || checkfield(L, "__index", ++n)) &&
-            (!(what & TAB_W) || checkfield(L, "__newindex", ++n)) &&
-            (!(what & TAB_L) || checkfield(L, "__len", ++n))) {
+            (!(what & TAB_R) || checkfield(L, lua.to_luastring("__index"), ++n)) &&
+            (!(what & TAB_W) || checkfield(L, lua.to_luastring("__newindex"), ++n)) &&
+            (!(what & TAB_L) || checkfield(L, lua.to_luastring("__len"), ++n))) {
             lapi.lua_pop(L, n);  /* pop metatable and tested metamethods */
         }
         else
@@ -53,7 +54,7 @@ const aux_getn = function(L, n, w) {
 const addfield = function(L, b, i) {
     lapi.lua_geti(L, 1, i);
     if (!lapi.lua_isstring(L, -1))
-        lauxlib.luaL_error(L, `invalid value (${lauxlib.luaL_typename(L, -1)}) at index ${i} in table for 'concat'`);
+        lauxlib.luaL_error(L, lua.to_luastring(`invalid value (${lobject.jsstring(lauxlib.luaL_typename(L, -1))}) at index ${i} in table for 'concat'`));
 
     lauxlib.luaL_addvalue(b);
 };
@@ -67,7 +68,7 @@ const tinsert = function(L) {
             break;
         case 3: {
             pos = lauxlib.luaL_checkinteger(L, 2);  /* 2nd argument is the position */
-            lauxlib.luaL_argcheck(L, 1 <= pos && pos <= e, 2, "position out of bounds");
+            lauxlib.luaL_argcheck(L, 1 <= pos && pos <= e, 2, lua.to_luastring("position out of bounds"));
             for (let i = e; i > pos; i--) {  /* move up elements */
                 lapi.lua_geti(L, 1, i - 1);
                 lapi.lua_seti(L, 1, i);  /* t[i] = t[i - 1] */
@@ -75,7 +76,7 @@ const tinsert = function(L) {
             break;
         }
         default: {
-            return lauxlib.luaL_error(L, "wrong number of arguments to 'insert'");
+            return lauxlib.luaL_error(L, lua.to_luastring("wrong number of arguments to 'insert'"));
         }
     }
 
@@ -87,7 +88,7 @@ const tremove = function(L) {
     let size = aux_getn(L, 1, TAB_RW);
     let pos = lauxlib.luaL_optinteger(L, 2, size);
     if (pos !== size)  /* validate 'pos' if given */
-        lauxlib.luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, "position out of bounds");
+        lauxlib.luaL_argcheck(L, 1 <= pos && pos <= size + 1, 1, lua.to_luastring("position out of bounds"));
     lapi.lua_geti(L, 1, pos);  /* result = t[pos] */
     for (; pos < size; pos++) {
         lapi.lua_geti(L, 1, pos + 1);
@@ -112,9 +113,9 @@ const tmove = function(L) {
     checktab(L, 1, TAB_R);
     checktab(L, tt, TAB_W);
     if (e >= f) {  /* otherwise, nothing to move */
-        lauxlib.luaL_argcheck(L, f > 0 || e < llimit.LUA_MAXINTEGER + f, 3, "too many elements to move");
+        lauxlib.luaL_argcheck(L, f > 0 || e < llimit.LUA_MAXINTEGER + f, 3, lua.to_luastring("too many elements to move"));
         let n = e - f + 1;  /* number of elements to move */
-        lauxlib.luaL_argcheck(L, t <= llimit.LUA_MAXINTEGER - n + 1, 4, "destination wrap around");
+        lauxlib.luaL_argcheck(L, t <= llimit.LUA_MAXINTEGER - n + 1, 4, lua.to_luastring("destination wrap around"));
 
         if (t > e || t <= f || (tt !== 1 && lapi.lua_compare(L, 1, tt, lua.LUA_OPEQ) !== 1)) {
             for (let i = 0; i < n; i++) {
@@ -172,7 +173,7 @@ const unpack = function(L) {
     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))
-        return lauxlib.luaL_error(L, "too many results to unpack");
+        return lauxlib.luaL_error(L, lua.to_luastring("too many results to unpack"));
     for (; i < e; i++)  /* push arg[i..e - 1] (to avoid overflows) */
         lapi.lua_geti(L, 1, i);
     lapi.lua_geti(L, 1, e);  /* push last element */
@@ -213,7 +214,7 @@ const auxsort = function(L) {
 const sort = function(L) {
     let n = aux_getn(L, 1, TAB_RW);
     if (n > 1) {  /* non-trivial interval? */
-        lauxlib.luaL_argcheck(L, n < Number.MAX_SAFE_INTEGER, 1, "array too big");
+        lauxlib.luaL_argcheck(L, n < Number.MAX_SAFE_INTEGER, 1, lua.to_luastring("array too big"));
         if (!lapi.lua_isnoneornil(L, 2))  /* is there a 2nd argument? */
             lauxlib.luaL_checktype(L, 2, CT.LUA_TFUNCTION);  /* must be a function */
         lapi.lua_settop(L, 2);  /* make sure there are two arguments */
@@ -237,4 +238,4 @@ const luaopen_table = function(L) {
     return 1;
 };
 
-module.exports.luaopen_table = luaopen_table;
\ No newline at end of file
+module.exports.luaopen_table = luaopen_table;
diff --git a/src/ltm.js b/src/ltm.js
index 7ef772f..d2df961 100644
--- a/src/ltm.js
+++ b/src/ltm.js
@@ -15,57 +15,30 @@ const CT      = lua.constant_types;
 
 
 const TMS = {
-    TM_INDEX:    "__index",
-    TM_NEWINDEX: "__newindex",
-    TM_GC:       "__gc",
-    TM_MODE:     "__mode",
-    TM_LEN:      "__len",
-    TM_EQ:       "__eq",  /* last tag method with fast access */
-    TM_ADD:      "__add",
-    TM_SUB:      "__sub",
-    TM_MUL:      "__mul",
-    TM_MOD:      "__mod",
-    TM_POW:      "__pow",
-    TM_DIV:      "__div",
-    TM_IDIV:     "__idiv",
-    TM_BAND:     "__band",
-    TM_BOR:      "__bor",
-    TM_BXOR:     "__bxor",
-    TM_SHL:      "__shl",
-    TM_SHR:      "__shr",
-    TM_UNM:      "__unm",
-    TM_BNOT:     "__bnot",
-    TM_LT:       "__lt",
-    TM_LE:       "__le",
-    TM_CONCAT:   "__concat",
-    TM_CALL:     "__call"
-};
-
-const TMS8 = {
-    TM_INDEX:    lua.to_luastring(TMS.TM_INDEX),
-    TM_NEWINDEX: lua.to_luastring(TMS.TM_NEWINDEX),
-    TM_GC:       lua.to_luastring(TMS.TM_GC),
-    TM_MODE:     lua.to_luastring(TMS.TM_MODE),
-    TM_LEN:      lua.to_luastring(TMS.TM_LEN),
-    TM_EQ:       lua.to_luastring(TMS.TM_EQ),  /* last tag method with fast access */
-    TM_ADD:      lua.to_luastring(TMS.TM_ADD),
-    TM_SUB:      lua.to_luastring(TMS.TM_SUB),
-    TM_MUL:      lua.to_luastring(TMS.TM_MUL),
-    TM_MOD:      lua.to_luastring(TMS.TM_MOD),
-    TM_POW:      lua.to_luastring(TMS.TM_POW),
-    TM_DIV:      lua.to_luastring(TMS.TM_DIV),
-    TM_IDIV:     lua.to_luastring(TMS.TM_IDIV),
-    TM_BAND:     lua.to_luastring(TMS.TM_BAND),
-    TM_BOR:      lua.to_luastring(TMS.TM_BOR),
-    TM_BXOR:     lua.to_luastring(TMS.TM_BXOR),
-    TM_SHL:      lua.to_luastring(TMS.TM_SHL),
-    TM_SHR:      lua.to_luastring(TMS.TM_SHR),
-    TM_UNM:      lua.to_luastring(TMS.TM_UNM),
-    TM_BNOT:     lua.to_luastring(TMS.TM_BNOT),
-    TM_LT:       lua.to_luastring(TMS.TM_LT),
-    TM_LE:       lua.to_luastring(TMS.TM_LE),
-    TM_CONCAT:   lua.to_luastring(TMS.TM_CONCAT),
-    TM_CALL:     lua.to_luastring(TMS.TM_CALL)
+    TM_INDEX:    lua.to_luastring("__index"),
+    TM_NEWINDEX: lua.to_luastring("__newindex"),
+    TM_GC:       lua.to_luastring("__gc"),
+    TM_MODE:     lua.to_luastring("__mode"),
+    TM_LEN:      lua.to_luastring("__len"),
+    TM_EQ:       lua.to_luastring("__eq"),  /* last tag method with fast access */
+    TM_ADD:      lua.to_luastring("__add"),
+    TM_SUB:      lua.to_luastring("__sub"),
+    TM_MUL:      lua.to_luastring("__mul"),
+    TM_MOD:      lua.to_luastring("__mod"),
+    TM_POW:      lua.to_luastring("__pow"),
+    TM_DIV:      lua.to_luastring("__div"),
+    TM_IDIV:     lua.to_luastring("__idiv"),
+    TM_BAND:     lua.to_luastring("__band"),
+    TM_BOR:      lua.to_luastring("__bor"),
+    TM_BXOR:     lua.to_luastring("__bxor"),
+    TM_SHL:      lua.to_luastring("__shl"),
+    TM_SHR:      lua.to_luastring("__shr"),
+    TM_UNM:      lua.to_luastring("__unm"),
+    TM_BNOT:     lua.to_luastring("__bnot"),
+    TM_LT:       lua.to_luastring("__lt"),
+    TM_LE:       lua.to_luastring("__le"),
+    TM_CONCAT:   lua.to_luastring("__concat"),
+    TM_CALL:     lua.to_luastring("__call")
 };
 
 const luaT_typenames_ = [
@@ -88,10 +61,8 @@ const ttypename = function(t) {
 
 const luaT_init = function(L) {
     L.l_G.tmname = [];
-    for (let event in TMS) {
-        let name = lua.to_luastring(TMS[event]);
-        L.l_G.tmname.push(L.l_G.intern(name)); // Strings are already interned by JS
-    }
+    for (let event in TMS)
+        L.l_G.tmname.push(L.l_G.intern(TMS[event]));
 };
 
 /*
@@ -99,9 +70,8 @@ const luaT_init = function(L) {
 ** with metatable, use their '__name' metafield, if present.
 */
 const luaT_objtypename = function(L, o) {
-    if ((o.ttistable() && o.metatable !== null)
-        || (o.ttisfulluserdata() && o.metatable !== null)) {
-        let name = o.__index(o, '__name');
+    if ((o.ttistable() && o.metatable !== null) || (o.ttisfulluserdata() && o.metatable !== null)) {
+        let name = o.__index(o, lua.to_luastring('__name'));
         if (name.ttisstring())
             return name.jsstring();
     }
@@ -153,10 +123,10 @@ const luaT_trybinTM = function(L, p1, p2, res, event) {
                 if (n1 !== false && n2 !== false)
                     ldebug.luaG_tointerror(L, p1, p2);
                 else
-                    ldebug.luaG_opinterror(L, p1, p2, "perform bitwise operation on");
+                    ldebug.luaG_opinterror(L, p1, p2, lua.to_luastring("perform bitwise operation on"));
             }
             default:
-                ldebug.luaG_opinterror(L, p1, p2, "perform arithmetic on");
+                ldebug.luaG_opinterror(L, p1, p2, lua.to_luastring("perform arithmetic on"));
         }
     }
 };
diff --git a/src/lua.js b/src/lua.js
index b1faccd..4710ded 100644
--- a/src/lua.js
+++ b/src/lua.js
@@ -240,4 +240,4 @@ module.exports.lua_Debug               = lua_Debug;
 module.exports.lua_upvalueindex        = lua_upvalueindex;
 module.exports.print_version           = print_version;
 module.exports.thread_status           = thread_status;
-module.exports.to_luastring            = to_luastring;
\ No newline at end of file
+module.exports.to_luastring            = to_luastring;
diff --git a/src/lutf8lib.js b/src/lutf8lib.js
index 053fb56..1df6096 100644
--- a/src/lutf8lib.js
+++ b/src/lutf8lib.js
@@ -91,8 +91,8 @@ const utflen = function(L) {
     let posi = u_posrelat(lauxlib.luaL_optinteger(L, 2, 1), s.length);
     let posj = u_posrelat(lauxlib.luaL_optinteger(L, 3, -1), s.length);
 
-    lauxlib.luaL_argcheck(L, 1 <= posi && --posi <= s.length, 2, "initial position out of string");
-    lauxlib.luaL_argcheck(L, --posj < s.length, 3, "final position out of string");
+    lauxlib.luaL_argcheck(L, 1 <= posi && --posi <= s.length, 2, lua.to_luastring("initial position out of string"));
+    lauxlib.luaL_argcheck(L, --posj < s.length, 3, lua.to_luastring("final position out of string"));
 
     lapi.lua_pushinteger(L, s.slice(posi, posj + 1).length);
     return 1;
@@ -100,7 +100,7 @@ const utflen = function(L) {
 
 const pushutfchar = function(L, arg) {
     let code = lauxlib.luaL_checkinteger(L, arg);
-    lauxlib.luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, "value out of range");
+    lauxlib.luaL_argcheck(L, 0 <= code && code <= MAXUNICODE, arg, lua.to_luastring("value out of range"));
     lapi.lua_pushstring(L, `${String.fromCharCode(code)}`);
 };
 
@@ -134,14 +134,14 @@ const byteoffset = function(L) {
     let posi = n >= 0 ? 1 : s.length + 1;
     posi = u_posrelat(lauxlib.luaL_optinteger(L, 3, posi), s.length);
 
-    lauxlib.luaL_argcheck(L, 1 <= posi && --posi <= s.length, 3, "position ot ouf range");
+    lauxlib.luaL_argcheck(L, 1 <= posi && --posi <= s.length, 3, lua.to_luastring("position ot ouf range"));
 
     if (n === 0) {
         /* find beginning of current byte sequence */
         while (posi > 0 && iscont(s[posi])) posi--;
     } else {
         if (iscont(s[posi]))
-            lauxlib.luaL_error(L, "initial position is a continuation byte");
+            lauxlib.luaL_error(L, lua.to_luastring("initial position is a continuation byte"));
 
         if (n < 0) {
             while (n < 0 && posi > 0) {  /* move back */
@@ -179,19 +179,19 @@ const codepoint = function(L) {
     let posi = u_posrelat(lauxlib.luaL_optinteger(L, 2, 1), s.length);
     let pose = u_posrelat(lauxlib.luaL_optinteger(L, 3, posi), s.length);
 
-    lauxlib.luaL_argcheck(L, posi >= 1, 2, "out of range");
-    lauxlib.luaL_argcheck(L, pose <= s.length, 3, "out of range");
+    lauxlib.luaL_argcheck(L, posi >= 1, 2, lua.to_luastring("out of range"));
+    lauxlib.luaL_argcheck(L, pose <= s.length, 3, lua.to_luastring("out of range"));
 
     if (posi > pose) return 0;  /* empty interval; return no values */
     if (pose - posi >= Number.MAX_SAFE_INTEGER)
-        return lauxlib.luaL_error(L, "string slice too long");
+        return lauxlib.luaL_error(L, lua.to_luastring("string slice too long"));
     let n = (pose - posi) + 1;
-    lauxlib.luaL_checkstack(L, n, "string slice too long");
+    lauxlib.luaL_checkstack(L, n, lua.to_luastring("string slice too long"));
     n = 0;
     for (s = s.slice(posi - 1); n < pose - posi;) {
         let dec = utf8_decode(s);
         if (dec === null)
-            return lauxlib.luaL_error(L, "invalid UTF-8 code");
+            return lauxlib.luaL_error(L, lua.to_luastring("invalid UTF-8 code"));
         s = dec.string;
         let code = dec.code;
         lapi.lua_pushinteger(L, code);
@@ -220,7 +220,7 @@ const iter_aux = function(L) {
         let code = dec ? dec.code : null;
         let next = dec ? dec.string : null;
         if (next === null || iscont(next[0]))
-            return lauxlib.luaL_error(L, "invalid UTF-8 code");
+            return lauxlib.luaL_error(L, lua.to_luastring("invalid UTF-8 code"));
         lapi.lua_pushinteger(L, n + 1);
         lapi.lua_pushinteger(L, code);
         return 2;
@@ -253,4 +253,4 @@ const luaopen_utf8 = function(L) {
     return 1;
 };
 
-module.exports.luaopen_utf8 = luaopen_utf8;
\ No newline at end of file
+module.exports.luaopen_utf8 = luaopen_utf8;
-- 
cgit v1.2.3-70-g09d2