Files

593 lines
18 KiB
JavaScript

"use strict";
const {
LUA_TNIL,
LUA_TTABLE,
lua_close,
lua_createtable,
lua_getfield,
lua_isboolean,
lua_isnoneornil,
lua_pop,
lua_pushboolean,
lua_pushfstring,
lua_pushinteger,
lua_pushliteral,
lua_pushnil,
lua_pushnumber,
lua_pushstring,
lua_setfield,
lua_settop,
lua_toboolean,
lua_tointegerx
} = require('./lua.js');
const {
luaL_Buffer,
luaL_addchar,
luaL_addstring,
// luaL_argcheck,
luaL_argerror,
luaL_buffinit,
luaL_checkinteger,
luaL_checkoption,
luaL_checkstring,
luaL_checktype,
luaL_error,
luaL_execresult,
luaL_fileresult,
luaL_newlib,
luaL_optinteger,
luaL_optlstring,
luaL_optstring,
luaL_pushresult
} = require('./lauxlib.js');
const {
luastring_eq,
to_jsstring,
to_luastring
} = require("./fengaricore.js");
/* options for ANSI C 89 (only 1-char options) */
// const L_STRFTIMEC89 = to_luastring("aAbBcdHIjmMpSUwWxXyYZ%");
// const LUA_STRFTIMEOPTIONS = L_STRFTIMEC89;
/* options for ISO C 99 and POSIX */
// const L_STRFTIMEC99 = to_luastring("aAbBcCdDeFgGhHIjmMnprRStTuUVwWxXyYzZ%||EcECExEXEyEYOdOeOHOIOmOMOSOuOUOVOwOWOy"); /* two-char options */
// const LUA_STRFTIMEOPTIONS = L_STRFTIMEC99;
/* options for Windows */
// const L_STRFTIMEWIN = to_luastring("aAbBcdHIjmMpSUwWxXyYzZ%||#c#x#d#H#I#j#m#M#S#U#w#W#y#Y"); /* two-char options */
// const LUA_STRFTIMEOPTIONS = L_STRFTIMEWIN;
/* options for our own strftime implementation
- should be superset of C89 options for compat
- missing from C99:
- ISO 8601 week specifiers: gGV
- > single char specifiers
- beyond C99:
- %k: TZ extension: space-padded 24-hour
- %l: TZ extension: space-padded 12-hour
- %P: GNU extension: lower-case am/pm
*/
const LUA_STRFTIMEOPTIONS = to_luastring("aAbBcCdDeFhHIjklmMnpPrRStTuUwWxXyYzZ%");
const setfield = function(L, key, value) {
lua_pushinteger(L, value);
lua_setfield(L, -2, to_luastring(key, true));
};
const setallfields = function(L, time, utc) {
setfield(L, "sec", utc ? time.getUTCSeconds() : time.getSeconds());
setfield(L, "min", utc ? time.getUTCMinutes() : time.getMinutes());
setfield(L, "hour", utc ? time.getUTCHours() : time.getHours());
setfield(L, "day", utc ? time.getUTCDate() : time.getDate());
setfield(L, "month", (utc ? time.getUTCMonth() : time.getMonth()) + 1);
setfield(L, "year", utc ? time.getUTCFullYear() : time.getFullYear());
setfield(L, "wday", (utc ? time.getUTCDay() : time.getDay()) + 1);
setfield(L, "yday", Math.floor((time - (new Date(time.getFullYear(), 0, 0 /* shortcut to correct day by one */))) / 86400000));
// setboolfield(L, "isdst", time.get);
};
const L_MAXDATEFIELD = (Number.MAX_SAFE_INTEGER / 2);
const getfield = function(L, key, d, delta) {
let t = lua_getfield(L, -1, to_luastring(key, true)); /* get field and its type */
let res = lua_tointegerx(L, -1);
if (res === false) { /* field is not an integer? */
if (t !== LUA_TNIL) /* some other value? */
return luaL_error(L, to_luastring("field '%s' is not an integer"), key);
else if (d < 0) /* absent field; no default? */
return luaL_error(L, to_luastring("field '%s' missing in date table"), key);
res = d;
}
else {
if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD))
return luaL_error(L, to_luastring("field '%s' is out-of-bound"), key);
res -= delta;
}
lua_pop(L, 1);
return res;
};
const locale = {
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ].map((s) => to_luastring(s)),
shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((s) => to_luastring(s)),
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"].map((s) => to_luastring(s)),
shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].map((s) => to_luastring(s)),
AM: to_luastring("AM"),
PM: to_luastring("PM"),
am: to_luastring("am"),
pm: to_luastring("pm"),
formats: {
c: to_luastring("%a %b %e %H:%M:%S %Y"),
D: to_luastring("%m/%d/%y"),
F: to_luastring("%Y-%m-%d"),
R: to_luastring("%H:%M"),
r: to_luastring("%I:%M:%S %p"),
T: to_luastring("%H:%M:%S"),
X: to_luastring("%T"),
x: to_luastring("%D")
}
};
const week_number = function(date, start_of_week) {
// This works by shifting the weekday back by one day if we
// are treating Monday as the first day of the week.
let weekday = date.getDay();
if (start_of_week === 'monday') {
if (weekday === 0) // Sunday
weekday = 6;
else
weekday--;
}
let yday = (date - new Date(date.getFullYear(), 0, 1)) / 86400000;
return Math.floor((yday + 7 - weekday) / 7);
};
const push_pad_2 = function(b, n, pad) {
if (n < 10)
luaL_addchar(b, pad);
luaL_addstring(b, to_luastring(String(n)));
};
const strftime = function(L, b, s, date) {
let i = 0;
while (i < s.length) {
if (s[i] !== 37 /* % */) { /* not a conversion specifier? */
luaL_addchar(b, s[i++]);
} else {
i++; /* skip '%' */
let len = checkoption(L, s, i);
/* each `case` has an example output above it for the UTC epoch */
switch(s[i]) {
// '%'
case 37 /* % */:
luaL_addchar(b, 37);
break;
// 'Thursday'
case 65 /* A */:
luaL_addstring(b, locale.days[date.getDay()]);
break;
// 'January'
case 66 /* B */:
luaL_addstring(b, locale.months[date.getMonth()]);
break;
// '19'
case 67 /* C */:
push_pad_2(b, Math.floor(date.getFullYear() / 100), 48 /* 0 */);
break;
// '01/01/70'
case 68 /* D */:
strftime(L, b, locale.formats.D, date);
break;
// '1970-01-01'
case 70 /* F */:
strftime(L, b, locale.formats.F, date);
break;
// '00'
case 72 /* H */:
push_pad_2(b, date.getHours(), 48 /* 0 */);
break;
// '12'
case 73 /* I */:
push_pad_2(b, (date.getHours() + 11) % 12 + 1, 48 /* 0 */);
break;
// '00'
case 77 /* M */:
push_pad_2(b, date.getMinutes(), 48 /* 0 */);
break;
// 'am'
case 80 /* P */:
luaL_addstring(b, date.getHours() < 12 ? locale.am : locale.pm);
break;
// '00:00'
case 82 /* R */:
strftime(L, b, locale.formats.R, date);
break;
// '00'
case 83 /* S */:
push_pad_2(b, date.getSeconds(), 48 /* 0 */);
break;
// '00:00:00'
case 84 /* T */:
strftime(L, b, locale.formats.T, date);
break;
// '00'
case 85 /* U */:
push_pad_2(b, week_number(date, "sunday"), 48 /* 0 */);
break;
// '00'
case 87 /* W */:
push_pad_2(b, week_number(date, "monday"), 48 /* 0 */);
break;
// '16:00:00'
case 88 /* X */:
strftime(L, b, locale.formats.X, date);
break;
// '1970'
case 89 /* Y */:
luaL_addstring(b, to_luastring(String(date.getFullYear())));
break;
// 'GMT'
case 90 /* Z */: {
let tzString = date.toString().match(/\(([\w\s]+)\)/);
if (tzString)
luaL_addstring(b, to_luastring(tzString[1]));
break;
}
// 'Thu'
case 97 /* a */:
luaL_addstring(b, locale.shortDays[date.getDay()]);
break;
// 'Jan'
case 98 /* b */:
case 104 /* h */:
luaL_addstring(b, locale.shortMonths[date.getMonth()]);
break;
// ''
case 99 /* c */:
strftime(L, b, locale.formats.c, date);
break;
// '01'
case 100 /* d */:
push_pad_2(b, date.getDate(), 48 /* 0 */);
break;
// ' 1'
case 101 /* e */:
push_pad_2(b, date.getDate(), 32 /* space */);
break;
// '000'
case 106 /* j */: {
let yday = Math.floor((date - new Date(date.getFullYear(), 0, 1)) / 86400000);
if (yday < 100) {
if (yday < 10)
luaL_addchar(b, 48 /* 0 */);
luaL_addchar(b, 48 /* 0 */);
}
luaL_addstring(b, to_luastring(String(yday)));
break;
}
// ' 0'
case 107 /* k */:
push_pad_2(b, date.getHours(), 32 /* space */);
break;
// '12'
case 108 /* l */:
push_pad_2(b, (date.getHours() + 11) % 12 + 1, 32 /* space */);
break;
// '01'
case 109 /* m */:
push_pad_2(b, date.getMonth() + 1, 48 /* 0 */);
break;
// '\n'
case 110 /* n */:
luaL_addchar(b, 10);
break;
// 'AM'
case 112 /* p */:
luaL_addstring(b, date.getHours() < 12 ? locale.AM : locale.PM);
break;
// '12:00:00 AM'
case 114 /* r */:
strftime(L, b, locale.formats.r, date);
break;
// '0'
case 115 /* s */:
luaL_addstring(b, to_luastring(String(Math.floor(date / 1000))));
break;
// '\t'
case 116 /* t */:
luaL_addchar(b, 8);
break;
// '4'
case 117 /* u */: {
let day = date.getDay();
luaL_addstring(b, to_luastring(String(day === 0 ? 7 : day)));
break;
}
// '4'
case 119 /* w */:
luaL_addstring(b, to_luastring(String(date.getDay())));
break;
// '12/31/69'
case 120 /* x */:
strftime(L, b, locale.formats.x, date);
break;
// '70'
case 121 /* y */:
push_pad_2(b, date.getFullYear() % 100, 48 /* 0 */);
break;
// '+0000'
case 122 /* z */: {
let off = date.getTimezoneOffset();
if (off > 0) {
luaL_addchar(b, 45 /* - */);
} else {
off = -off;
luaL_addchar(b, 43 /* + */);
}
push_pad_2(b, Math.floor(off/60), 48 /* 0 */);
push_pad_2(b, off % 60, 48 /* 0 */);
break;
}
}
i += len;
}
}
};
const checkoption = function(L, conv, i) {
let option = LUA_STRFTIMEOPTIONS;
let o = 0;
let oplen = 1; /* length of options being checked */
for (; o < option.length && oplen <= (conv.length - i); o += oplen) {
if (option[o] === '|'.charCodeAt(0)) /* next block? */
oplen++; /* will check options with next length (+1) */
else if (luastring_eq(conv.subarray(i, i+oplen), option.subarray(o, o+oplen))) { /* match? */
return oplen; /* return length */
}
}
luaL_argerror(L, 1,
lua_pushfstring(L, to_luastring("invalid conversion specifier '%%%s'"), conv));
};
/* maximum size for an individual 'strftime' item */
// const SIZETIMEFMT = 250;
const os_date = function(L) {
let s = luaL_optlstring(L, 1, "%c");
let stm = lua_isnoneornil(L, 2) ? new Date() : new Date(l_checktime(L, 2) * 1000);
let utc = false;
let i = 0;
if (s[i] === '!'.charCodeAt(0)) { /* UTC? */
utc = true;
i++; /* skip '!' */
}
if (s[i] === "*".charCodeAt(0) && s[i+1] === "t".charCodeAt(0)) {
lua_createtable(L, 0, 9); /* 9 = number of fields */
setallfields(L, stm, utc);
} else {
let cc = new Uint8Array(4);
cc[0] = "%".charCodeAt(0);
let b = new luaL_Buffer();
luaL_buffinit(L, b);
strftime(L, b, s, stm);
luaL_pushresult(b);
}
return 1;
};
const os_time = function(L) {
let t;
if (lua_isnoneornil(L, 1)) /* called without args? */
t = new Date(); /* get current time */
else {
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 1); /* make sure table is at the top */
t = new Date(
getfield(L, "year", -1, 0),
getfield(L, "month", -1, 1),
getfield(L, "day", -1, 0),
getfield(L, "hour", 12, 0),
getfield(L, "min", 0, 0),
getfield(L, "sec", 0, 0)
);
setallfields(L, t);
}
lua_pushinteger(L, Math.floor(t / 1000));
return 1;
};
const l_checktime = function(L, arg) {
let t = luaL_checkinteger(L, arg);
// luaL_argcheck(L, t, arg, "time out-of-bounds");
return t;
};
const os_difftime = function(L) {
let t1 = l_checktime(L, 1);
let t2 = l_checktime(L, 2);
lua_pushnumber(L, t1 - t2);
return 1;
};
const catnames = ["all", "collate", "ctype", "monetary", "numeric", "time"].map((lc) => to_luastring(lc));
const C = to_luastring("C");
const POSIX = to_luastring("POSIX");
const os_setlocale = function(L) {
const l = luaL_optstring(L, 1, null);
luaL_checkoption(L, 2, "all", catnames);
/* It is not possible to set the JS-VM wide locale, so we say that we only
know the C locale. The "POSIX" locale is defined in
IEEE Std 1003.1-2017 Section 7.2 as equivalent to "C" */
lua_pushstring(L, (
l === null /* passing nil returns the current locale; which is "C" */
|| l.length == 0 /* empty string resets to the default locale; which is "C" */
|| luastring_eq(l, C) /* user passed "C" */
|| luastring_eq(l, POSIX) /* user passed "POSIX", equivalent to "C" */
) ? C : null);
return 1;
};
const syslib = {
"date": os_date,
"difftime": os_difftime,
"setlocale": os_setlocale,
"time": os_time
};
if (typeof process === "undefined") {
syslib.clock = function(L) {
lua_pushnumber(L, performance.now()/1000);
return 1;
};
} else {
/* Only with Node */
const fs = require('fs');
const tmp = require('tmp');
const child_process = require('child_process');
syslib.exit = function(L) {
let status;
if (lua_isboolean(L, 1))
status = (lua_toboolean(L, 1) ? 0 : 1);
else
status = luaL_optinteger(L, 1, 0);
if (lua_toboolean(L, 2))
lua_close(L);
if (L) process.exit(status); /* 'if' to avoid warnings for unreachable 'return' */
return 0;
};
syslib.getenv = function(L) {
let key = luaL_checkstring(L, 1);
key = to_jsstring(key); /* https://github.com/nodejs/node/issues/16961 */
if (Object.prototype.hasOwnProperty.call(process.env, key)) {
lua_pushliteral(L, process.env[key]);
} else {
lua_pushnil(L);
}
return 1;
};
syslib.clock = function(L) {
lua_pushnumber(L, process.uptime());
return 1;
};
const lua_tmpname = function() {
return tmp.tmpNameSync();
};
syslib.remove = function(L) {
let filename = luaL_checkstring(L, 1);
try {
fs.unlinkSync(filename);
} catch (e) {
if (e.code === 'EISDIR') {
try {
fs.rmdirSync(filename);
} catch (e) {
return luaL_fileresult(L, false, filename, e);
}
} else {
return luaL_fileresult(L, false, filename, e);
}
}
return luaL_fileresult(L, true);
};
syslib.rename = function(L) {
let fromname = luaL_checkstring(L, 1);
let toname = luaL_checkstring(L, 2);
try {
fs.renameSync(fromname, toname);
} catch (e) {
return luaL_fileresult(L, false, false, e);
}
return luaL_fileresult(L, true);
};
syslib.tmpname = function(L) {
let name = lua_tmpname();
if (!name)
return luaL_error(L, to_luastring("unable to generate a unique filename"));
lua_pushstring(L, to_luastring(name));
return 1;
};
syslib.execute = function(L) {
let cmd = luaL_optstring(L, 1, null);
if (cmd !== null) {
cmd = to_jsstring(cmd);
try {
child_process.execSync(
cmd,
{
stdio: [process.stdin, process.stdout, process.stderr]
}
);
} catch (e) {
return luaL_execresult(L, e);
}
return luaL_execresult(L, null);
} else {
/* Assume a shell is available.
If it's good enough for musl it's good enough for us.
http://git.musl-libc.org/cgit/musl/tree/src/process/system.c?id=ac45692a53a1b8d2ede329d91652d43c1fb5dc8d#n22
*/
lua_pushboolean(L, 1);
return 1;
}
};
}
const luaopen_os = function(L) {
luaL_newlib(L, syslib);
return 1;
};
module.exports.luaopen_os = luaopen_os;