From 2977fe002407b6f86efa4ba5216f567082f33e45 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 8 May 2017 12:32:08 +1000 Subject: Move string functions to centralised lstring.js --- src/lapi.js | 13 +++++++------ src/lcode.js | 3 ++- src/ldo.js | 7 ++++--- src/llex.js | 9 +++++---- src/lobject.js | 3 ++- src/lparser.js | 24 +++++++++++++++--------- src/lstate.js | 12 ------------ src/lstring.js | 33 +++++++++++++++++++++++++++++++++ src/ltable.js | 10 +++------- src/ltm.js | 3 ++- src/lundump.js | 40 ++++++++++++++-------------------------- src/lvm.js | 5 +++-- 12 files changed, 90 insertions(+), 72 deletions(-) create mode 100644 src/lstring.js diff --git a/src/lapi.js b/src/lapi.js index aa8cf04..fa6da20 100644 --- a/src/lapi.js +++ b/src/lapi.js @@ -10,6 +10,7 @@ const lfunc = require('./lfunc.js'); const llex = require('./llex.js'); const lobject = require('./lobject.js'); const lstate = require('./lstate.js'); +const lstring = require('./lstring.js'); const ltm = require('./ltm.js'); const luaconf = require('./luaconf.js'); const lvm = require('./lvm.js'); @@ -216,7 +217,7 @@ 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(defs.to_luastring("", true)) : new TValue(CT.LUA_TLNGSTR, s.slice(0, len)); + let ts = new TValue(CT.LUA_TLNGSTR, lstring.luaS_bless(L, s.slice(0, len))); L.stack[L.top++] = ts; assert(L.top <= L.ci.top, "stack overflow"); @@ -230,7 +231,7 @@ const lua_pushstring = function (L, s) { 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.stack[L.top] = new TValue(CT.LUA_TLNGSTR, lstring.luaS_new(L, s)); } L.top++; @@ -255,7 +256,7 @@ const lua_pushliteral = function (L, s) { if (s === undefined || s === null) L.stack[L.top] = new TValue(CT.LUA_TNIL, null); else { - let ts = L.l_G.intern(defs.to_luastring(s)); + let ts = new TValue(CT.LUA_TLNGSTR, lstring.luaS_newliteral(L, s)); L.stack[L.top] = ts; } @@ -330,7 +331,7 @@ const lua_pushglobaltable = function(L) { const auxsetstr = function(L, t, k) { assert(Array.isArray(k), "key must be an array of bytes"); - let str = L.l_G.intern(k); + let str = new TValue(CT.LUA_TLNGSTR, lstring.luaS_new(L, k)); assert(1 < L.top - L.ci.funcOff, "not enough elements in the stack"); @@ -429,7 +430,7 @@ const lua_rawsetp = function(L, idx, p) { const auxgetstr = function(L, t, k) { assert(Array.isArray(k), "key must be an array of bytes"); - let str = L.l_G.intern(k); + let str = new TValue(CT.LUA_TLNGSTR, lstring.luaS_new(L, k)); L.stack[L.top++] = str; assert(L.top <= L.ci.top, "stack overflow"); @@ -952,7 +953,7 @@ const lua_concat = function(L, n) { if (n >= 2) lvm.luaV_concat(L, n); else if (n === 0) { - L.stack[L.top++] = L.l_G.intern(defs.to_luastring("", true)); + L.stack[L.top++] = new TValue(CT.LUA_TLNGSTR, lstring.luaS_newliteral(L, [])); assert(L.top <= L.ci.top, "stack overflow"); } }; diff --git a/src/lcode.js b/src/lcode.js index fac7d37..814498c 100644 --- a/src/lcode.js +++ b/src/lcode.js @@ -7,6 +7,7 @@ const llex = require('./llex.js'); const lobject = require('./lobject.js'); const lopcode = require('./lopcodes.js'); const lparser = require('./lparser.js'); +const lstring = require('./lstring.js'); const ltable = require('./ltable.js'); require('./lstate.js'); /* XXX: if this isn't here then things break on require("ltm") */ const ltm = require('./ltm.js'); @@ -514,7 +515,7 @@ const luaK_stringK = function(fs, s) { */ const luaK_intK = function(fs, n) { /* FIXME: shouldn't use string as key. need to use pointer */ - let k = new TValue(CT.LUA_TLNGSTR, defs.to_luastring(`${n}`)); + let k = new TValue(CT.LUA_TLNGSTR, lstring.luaS_bless(fs.L, defs.to_luastring(`${n}`))); let o = new TValue(CT.LUA_TNUMINT, n); return addk(fs, k, o); }; diff --git a/src/ldo.js b/src/ldo.js index 5865f96..39ba4dc 100644 --- a/src/ldo.js +++ b/src/ldo.js @@ -13,6 +13,7 @@ const llimit = require('./llimit.js'); const lobject = require('./lobject.js'); const lparser = require('./lparser.js'); const lstate = require('./lstate.js'); +const lstring = require('./lstring.js'); const ltm = require('./ltm.js'); const lvm = require('./lvm.js'); const CT = defs.constant_types; @@ -23,11 +24,11 @@ const TValue = lobject.TValue; const seterrorobj = function(L, errcode, oldtop) { switch (errcode) { case TS.LUA_ERRMEM: { - L.stack[oldtop] = L.l_G.intern(defs.to_luastring("not enough memory", true)); + L.stack[oldtop] = new TValue(CT.LUA_TLNGSTR, lstring.luaS_newliteral(L, "not enough memory")); break; } case TS.LUA_ERRERR: { - L.stack[oldtop] = L.l_G.intern(defs.to_luastring("error in error handling", true)); + L.stack[oldtop] = new TValue(CT.LUA_TLNGSTR, lstring.luaS_newliteral(L, "error in error handling")); break; } default: { @@ -401,7 +402,7 @@ const recover = function(L, status) { */ const resume_error = function(L, msg, narg) { L.top -= narg; /* remove args from the stack */ - L.stack[L.top++] = L.l_G.intern(defs.to_luastring(msg)); /* push error message */ + L.stack[L.top++] = new TValue(CT.LUA_TLNGSTR, lstring.luaS_newliteral(L, msg)); /* push error message */ assert(L.top <= L.ci.top, "stack overflow"); return TS.LUA_ERRRUN; }; diff --git a/src/llex.js b/src/llex.js index 5591054..e690ea2 100644 --- a/src/llex.js +++ b/src/llex.js @@ -7,6 +7,7 @@ const ldebug = require('./ldebug.js'); const ldo = require('./ldo.js'); const ljstype = require('./ljstype'); const lobject = require('./lobject'); +const lstring = require('./lstring.js'); const llimit = require('./llimit.js'); const TS = defs.thread_status; const char = defs.char; @@ -231,7 +232,7 @@ const luaX_setinput = function(L, ls, z, source, firstchar) { ls.linenumber = 1; ls.lastline = 1; ls.source = source; - ls.envn = defs.to_luastring("_ENV", true); + ls.envn = lstring.luaS_newliteral(L, "_ENV"); }; const check_next1 = function(ls, c) { @@ -368,7 +369,7 @@ const read_long_string = function(ls, seminfo, sep) { } if (seminfo) - seminfo.ts = ls.buff.buffer.slice(2 + sep, 2 + sep - 2 * (2 + sep)); + seminfo.ts = lstring.luaS_bless(ls.L, ls.buff.buffer.slice(2 + sep, 2 + sep - 2 * (2 + sep))); }; const esccheck = function(ls, c, msg) { @@ -493,7 +494,7 @@ const read_string = function(ls, del, seminfo) { } save_and_next(ls); /* skip delimiter */ - seminfo.ts = ls.buff.buffer.slice(1, ls.buff.n-1); + seminfo.ts = lstring.luaS_bless(ls.L, ls.buff.buffer.slice(1, ls.buff.n-1)); }; const isreserved = function(w) { @@ -605,7 +606,7 @@ const llex = function(ls, seminfo) { save_and_next(ls); } while (ljstype.lislalnum(ls.current)); - let ts = ls.buff.buffer; + let ts = lstring.luaS_new(ls.L, ls.buff.buffer); seminfo.ts = ts; let kidx = luaX_tokens.slice(0, 22).indexOf(defs.to_jsstring(ts)); if (kidx >= 0) /* reserved word? */ diff --git a/src/lobject.js b/src/lobject.js index cea9975..905eed1 100644 --- a/src/lobject.js +++ b/src/lobject.js @@ -6,6 +6,7 @@ const assert = require('assert'); const defs = require('./defs.js'); const ljstype = require('./ljstype.js'); const ldebug = require('./ldebug.js'); +const lstring = require('./lstring.js'); const luaconf = require('./luaconf.js'); const lvm = require('./lvm.js'); const llimit = require('./llimit.js'); @@ -418,7 +419,7 @@ const luaO_utf8esc = function(x) { }; const pushstr = function(L, str) { - L.stack[L.top++] = L.l_G.intern(str); + L.stack[L.top++] = new TValue(CT.LUA_TLNGSTR, lstring.luaS_new(L, str)); }; const luaO_pushvfstring = function(L, fmt, argp) { diff --git a/src/lparser.js b/src/lparser.js index 566576a..c90ff01 100644 --- a/src/lparser.js +++ b/src/lparser.js @@ -9,6 +9,7 @@ const llex = require('./llex.js'); const llimit = require('./llimit.js'); const lobject = require('./lobject.js'); const lopcode = require('./lopcodes.js'); +const lstring = require('./lstring.js'); const ltable = require('./ltable.js'); const BinOpr = lcode.BinOpr; const OpCodesI = lopcode.OpCodesI; @@ -24,6 +25,11 @@ const hasmultret = function(k) { return k === expkind.VCALL || k === expkind.VVARARG; }; +const eqstr = function(a, b) { + /* TODO: use plain equality as strings are cached */ + return lstring.luaS_eqlngstr(a, b); +}; + class BlockCnt { constructor() { this.previous = null; /* chain */ @@ -274,7 +280,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.join() === name.join()) + if (eqstr(up[i].name, name)) return i; } return -1; /* not found */ @@ -293,7 +299,7 @@ const newupvalue = function(fs, name, v) { const searchvar = function(fs, n) { for (let i = fs.nactvar - 1; i >= 0; i--) { - if (n.join() === getlocvar(fs, i).varname.join()) + if (eqstr(n, getlocvar(fs, i).varname)) return i; } @@ -385,7 +391,7 @@ const closegoto = function(ls, g, label) { let fs = ls.fs; let gl = ls.dyd.gt; let gt = gl.arr[g]; - assert(gt.name.join() === label.name.join()); + assert(eqstr(gt.name, label.name)); if (gt.nactvar < label.nactvar) { let vname = getlocvar(fs, gt.nactvar).varname; let msg = lobject.luaO_pushfstring(ls.L, defs.to_luastring(" at line %d jumps into the scope of local '%s'"), @@ -409,7 +415,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.join() === gt.name.join()) { /* correct label? */ + if (eqstr(lb.name, gt.name)) { /* 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 */ @@ -438,7 +444,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.join() === lb.name.join()) + if (eqstr(gl.arr[i].name, lb.name)) closegoto(ls, i, lb); else i++; @@ -483,7 +489,7 @@ const enterblock = function(fs, bl, isloop) { ** create a label named 'break' to resolve break statements */ const breaklabel = function(ls) { - let n = defs.to_luastring("break", true); + let n = lstring.luaS_newliteral(ls.L, "break"); let l = newlabelentry(ls, ls.dyd.label, n, 0, ls.fs.pc); findgotos(ls, ls.dyd.label.arr[l]); }; @@ -1146,7 +1152,7 @@ const gotostat = function(ls, pc) { label = str_checkname(ls); else { llex.luaX_next(ls); /* skip break */ - label = defs.to_luastring("break", true); + label = lstring.luaS_newliteral(ls.L, "break"); } let g = newlabelentry(ls, ls.dyd.gt, label, line, pc); findlabel(ls, g); /* close it if label already defined */ @@ -1155,7 +1161,7 @@ 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.join() === ll.arr[i].name.join()) { + if (eqstr(label, ll.arr[i].name)) { semerror(fs.ls, defs.to_luastring(`label '${label.jsstring()}' already defined on line ${ll.arr[i].line}`)); } } @@ -1554,7 +1560,7 @@ const luaY_parser = function(L, z, buff, dyd, name, firstchar) { lexstate.h = new TValue(defs.CT.LUA_TTABLE, ltable.luaH_new(L)); /* create table for scanner */ L.stack[L.top++] = lexstate.h; funcstate.f = cl.p = new Proto(L); - funcstate.f.source = name; + funcstate.f.source = lstring.luaS_new(L, name); lexstate.buff = buff; lexstate.dyd = dyd; dyd.actvar.n = dyd.gt.n = dyd.label.n = 0; diff --git a/src/lstate.js b/src/lstate.js index fee308c..f2e17cd 100644 --- a/src/lstate.js +++ b/src/lstate.js @@ -71,18 +71,6 @@ class global_State { this.mt = new Array(LUA_NUMTAGS); } - intern(stringArray) { - // TODO: when weak value maps are available - // let key = stringArray.map(e => `${e}|`).join(''); - - // if (!this.strt.has(key)) - // this.strt.set(key, new lobject.TValue(CT.LUA_TLNGSTR, stringArray)); - - // return this.strt.get(key); - - return new lobject.TValue(CT.LUA_TLNGSTR, stringArray); - } - } diff --git a/src/lstring.js b/src/lstring.js new file mode 100644 index 0000000..34f988e --- /dev/null +++ b/src/lstring.js @@ -0,0 +1,33 @@ +"use strict"; + +const defs = require('./defs.js'); + +const luaS_eqlngstr = function(a, b) { + return a == b || (a.length == b.length && a.join() == b.join()); +}; + +/* converts strings (arrays) to a consistent map key */ +const luaS_hash = function(str) { + return str.map(e => `${e}|`).join(''); +}; + +/* variant that takes ownership of array */ +const luaS_bless = function(L, str) { + return str; +}; + +/* makes a copy */ +const luaS_new = function(L, str) { + return luaS_bless(L, str.slice(0)); +}; + +/* takes a js string */ +const luaS_newliteral = function(L, str) { + return luaS_bless(L, defs.to_luastring(str)); +}; + +module.exports.luaS_eqlngstr = luaS_eqlngstr; +module.exports.luaS_hash = luaS_hash; +module.exports.luaS_bless = luaS_bless; +module.exports.luaS_new = luaS_new; +module.exports.luaS_newliteral = luaS_newliteral; diff --git a/src/ltable.js b/src/ltable.js index 428ee5e..de4c686 100644 --- a/src/ltable.js +++ b/src/ltable.js @@ -6,13 +6,9 @@ const assert = require('assert'); const defs = require('./defs.js'); const ldebug = require('./ldebug.js'); const lobject = require('./lobject.js'); +const lstring = require('./lstring.js'); const CT = defs.constant_types; -/* converts strings (arrays) to a consistent map key */ -const hashstr = function(str) { - return str.map(e => `${e}|`).join(''); -}; - const table_hash = function(key) { switch(key.type) { case CT.LUA_TBOOLEAN: @@ -28,7 +24,7 @@ const table_hash = function(key) { return key.value; case CT.LUA_TSHRSTR: case CT.LUA_TLNGSTR: - return hashstr(key.value); + return lstring.luaS_hash(key.value); default: throw new Error("unknown key type: " + key.type); } @@ -58,7 +54,7 @@ const luaH_getint = function(t, key) { const luaH_getstr = function(t, key) { assert(Array.isArray(key)); - return getgeneric(t, hashstr(key)); + return getgeneric(t, lstring.luaS_hash(key)); }; const luaH_get = function(t, key) { diff --git a/src/ltm.js b/src/ltm.js index 4bf37fe..3647308 100644 --- a/src/ltm.js +++ b/src/ltm.js @@ -7,6 +7,7 @@ const defs = require('./defs.js'); const lobject = require('./lobject.js'); const ldo = require('./ldo.js'); const lstate = require('./lstate.js'); +const lstring = require('./lstring.js'); const ltable = require('./ltable.js'); const ldebug = require('./ldebug.js'); const lvm = require('./lvm.js'); @@ -61,7 +62,7 @@ const ttypename = function(t) { const luaT_init = function(L) { L.l_G.tmname = []; for (let event in TMS) - L.l_G.tmname.push(L.l_G.intern(TMS[event])); + L.l_G.tmname.push(new lobject.TValue(CT.LUA_TLNGSTR, lstring.luaS_new(L, TMS[event]))); }; /* diff --git a/src/lundump.js b/src/lundump.js index 1570588..3e56a84 100644 --- a/src/lundump.js +++ b/src/lundump.js @@ -7,6 +7,7 @@ const defs = require('./defs.js'); const lfunc = require('./lfunc.js'); const lobject = require('./lobject.js'); const lopcodes = require('./lopcodes.js'); +const lstring = require('./lstring.js'); const llex = require('./llex.js'); let LUAC_DATA = [0x19, 0x93, defs.char["\r"], defs.char["\n"], 0x1a, defs.char["\n"]]; @@ -36,6 +37,7 @@ class BytecodeParser { read(size) { let buffer = this.buffer.read(size); + assert(Array.isArray(buffer)); if (buffer.length < size) this.error("truncated"); return buffer; } @@ -74,8 +76,8 @@ class BytecodeParser { return this.dv.getFloat64(0, true); } - read8bitString(n) { - let size = typeof n !== 'undefined' ? n : Math.max(this.readByte() - 1, 0); + readString() { + let size = Math.max(this.readByte() - 1, 0); if (size + 1 === 0xFF) size = this.readSize_t() - 1; @@ -84,25 +86,7 @@ class BytecodeParser { return null; } - let string = []; - - for (let i = 0; i < size; i++) - string.push(this.readByte()); - - return string; - } - - readString(n) { - let size = typeof n !== 'undefined' ? n : Math.max(this.readByte() - 1, 0); - - if (size + 1 === 0xFF) - size = this.readSize_t() - 1; - - if (size === 0) { - return null; - } - - return this.read(size); + return lstring.luaS_new(this.L, this.read(size)); } /* creates a mask with 'n' 1 bits at position 'p' */ @@ -176,7 +160,7 @@ class BytecodeParser { break; case defs.CT.LUA_TSHRSTR: case defs.CT.LUA_TLNGSTR: - f.k.push(this.L.l_G.intern(this.read8bitString())); + f.k.push(new lobject.TValue(defs.CT.LUA_TLNGSTR, this.readString())); break; default: this.error(`unrecognized constant '${t}'`); @@ -229,9 +213,14 @@ class BytecodeParser { this.readDebug(f); } + checkliteral(s, msg) { + let buff = this.read(s.length); + if (buff.join() !== s.join()) + this.error(msg); + } + checkHeader() { - if (this.readString(3).join() !== defs.to_luastring(defs.LUA_SIGNATURE.substring(1)).join()) /* 1st char already checked */ - this.error("bad LUA_SIGNATURE, expected 'Lua'"); + this.checkliteral(defs.to_luastring(defs.LUA_SIGNATURE.substring(1)), "bad LUA_SIGNATURE, expected 'Lua'"); /* 1st char already checked */ if (this.readByte() !== 0x53) this.error("bad Lua version, expected 5.3"); @@ -239,8 +228,7 @@ class BytecodeParser { if (this.readByte() !== 0) this.error("supports only official PUC-Rio implementation"); - if (this.readString(6).join() !== LUAC_DATA.join()) - this.error("bytecode corrupted"); + this.checkliteral(LUAC_DATA, "bytecode corrupted"); this.intSize = this.readByte(); this.size_tSize = this.readByte(); diff --git a/src/lvm.js b/src/lvm.js index ef5d370..8bfef40 100644 --- a/src/lvm.js +++ b/src/lvm.js @@ -9,6 +9,7 @@ const luaconf = require('./luaconf.js'); const lobject = require('./lobject.js'); const lfunc = require('./lfunc.js'); const lstate = require('./lstate.js'); +const lstring = require('./lstring.js'); const llimit = require('./llimit.js'); const ldo = require('./ldo.js'); const ltm = require('./ltm.js'); @@ -991,7 +992,7 @@ const tostring = function(L, i) { if (o.ttisstring()) return true; if (o.ttisnumber() && !isNaN(o.value)) { - L.stack[i] = L.l_G.intern(defs.to_luastring(`${o.value}`)); + L.stack[i] = new lobject.TValue(CT.LUA_TLNGSTR, lstring.luaS_bless(L, defs.to_luastring(`${o.value}`))); return true; } @@ -1029,7 +1030,7 @@ const luaV_concat = function(L, total) { ts = ts.concat(L.stack[top - i].value); } - L.stack[top - n] = L.l_G.intern(ts); + L.stack[top - n] = new lobject.TValue(CT.LUA_TLNGSTR, lstring.luaS_bless(L, ts)); } total -= n - 1; /* got 'n' strings to create 1 new */ L.top -= n - 1; /* popped 'n' strings and pushed one */ -- cgit v1.2.3-70-g09d2