1826 lines
52 KiB
C
Executable File
1826 lines
52 KiB
C
Executable File
/*
|
|
* QuickJS C library
|
|
*
|
|
* Copyright (c) 2017-2019 Fabrice Bellard
|
|
* Copyright (c) 2017-2019 Charlie Gordon
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#if defined(_WIN32)
|
|
#include <windows.h>
|
|
#include <conio.h>
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <termios.h>
|
|
#include <sys/ioctl.h>
|
|
#if defined(__APPLE__)
|
|
typedef sig_t sighandler_t;
|
|
#endif
|
|
#endif
|
|
|
|
#include "cutils.h"
|
|
#include "list.h"
|
|
#include "quickjs-libc.h"
|
|
|
|
static void js_std_dbuf_init(JSContext *ctx, DynBuf *s)
|
|
{
|
|
dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt);
|
|
}
|
|
|
|
/* TODO:
|
|
- add exec() wrapper
|
|
- add minimal VT100 emulation for win32
|
|
- add socket calls
|
|
*/
|
|
|
|
typedef struct {
|
|
struct list_head link;
|
|
int fd;
|
|
JSValue rw_func[2];
|
|
} JSOSRWHandler;
|
|
|
|
typedef struct {
|
|
struct list_head link;
|
|
int sig_num;
|
|
JSValue func;
|
|
} JSOSSignalHandler;
|
|
|
|
typedef struct {
|
|
struct list_head link;
|
|
BOOL has_object;
|
|
int64_t timeout;
|
|
JSValue func;
|
|
} JSOSTimer;
|
|
|
|
/* initialize the lists so js_std_free_handlers() can always be called */
|
|
static struct list_head os_rw_handlers = LIST_HEAD_INIT(os_rw_handlers);
|
|
static struct list_head os_signal_handlers = LIST_HEAD_INIT(os_signal_handlers);
|
|
static struct list_head os_timers = LIST_HEAD_INIT(os_timers);
|
|
static uint64_t os_pending_signals;
|
|
static int eval_script_recurse;
|
|
static int (*os_poll_func)(JSContext *ctx);
|
|
|
|
static JSValue js_printf_internal(JSContext *ctx,
|
|
int argc, JSValueConst *argv, FILE *fp)
|
|
{
|
|
char fmtbuf[32];
|
|
uint8_t cbuf[UTF8_CHAR_LEN_MAX+1];
|
|
JSValue res;
|
|
DynBuf dbuf;
|
|
const char *fmt_str;
|
|
const uint8_t *fmt, *fmt_end;
|
|
const uint8_t *p;
|
|
char *q;
|
|
int i, c, len;
|
|
int32_t int32_arg;
|
|
int64_t int64_arg;
|
|
double double_arg;
|
|
const char *string_arg;
|
|
enum { PART_FLAGS, PART_WIDTH, PART_DOT, PART_PREC, PART_MODIFIER } part;
|
|
int modsize;
|
|
/* Use indirect call to dbuf_printf to prevent gcc warning */
|
|
int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf;
|
|
|
|
js_std_dbuf_init(ctx, &dbuf);
|
|
|
|
if (argc > 0) {
|
|
fmt_str = JS_ToCStringLen(ctx, &len, argv[0], FALSE);
|
|
if (!fmt_str)
|
|
goto fail;
|
|
|
|
i = 1;
|
|
fmt = (const uint8_t *)fmt_str;
|
|
fmt_end = fmt + len;
|
|
while (fmt < fmt_end) {
|
|
for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++)
|
|
continue;
|
|
dbuf_put(&dbuf, p, fmt - p);
|
|
if (fmt >= fmt_end)
|
|
break;
|
|
q = fmtbuf;
|
|
*q++ = *fmt++; /* copy '%' */
|
|
part = PART_FLAGS;
|
|
modsize = 0;
|
|
for (;;) {
|
|
if (q >= fmtbuf + sizeof(fmtbuf) - 1)
|
|
goto invalid;
|
|
|
|
c = *fmt++;
|
|
*q++ = c;
|
|
*q = '\0';
|
|
|
|
switch (c) {
|
|
case '1': case '2': case '3':
|
|
case '4': case '5': case '6':
|
|
case '7': case '8': case '9':
|
|
if (part != PART_PREC) {
|
|
if (part <= PART_WIDTH)
|
|
part = PART_WIDTH;
|
|
else
|
|
goto invalid;
|
|
}
|
|
continue;
|
|
|
|
case '0': case '#': case '+': case '-': case ' ': case '\'':
|
|
if (part > PART_FLAGS)
|
|
goto invalid;
|
|
continue;
|
|
|
|
case '.':
|
|
if (part > PART_DOT)
|
|
goto invalid;
|
|
part = PART_DOT;
|
|
continue;
|
|
|
|
case '*':
|
|
if (part < PART_WIDTH)
|
|
part = PART_DOT;
|
|
else if (part == PART_DOT)
|
|
part = PART_MODIFIER;
|
|
else
|
|
goto invalid;
|
|
|
|
if (i >= argc)
|
|
goto missing;
|
|
|
|
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
|
|
goto fail;
|
|
q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg);
|
|
continue;
|
|
|
|
case 'h':
|
|
if (modsize != 0 && modsize != -1)
|
|
goto invalid;
|
|
modsize--;
|
|
part = PART_MODIFIER;
|
|
continue;
|
|
case 'l':
|
|
q--;
|
|
if (modsize != 0 && modsize != 1)
|
|
goto invalid;
|
|
modsize++;
|
|
part = PART_MODIFIER;
|
|
continue;
|
|
|
|
case 'c':
|
|
if (i >= argc)
|
|
goto missing;
|
|
if (JS_IsString(argv[i])) {
|
|
string_arg = JS_ToCString(ctx, argv[i++]);
|
|
if (!string_arg)
|
|
goto fail;
|
|
int32_arg = unicode_from_utf8((uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p);
|
|
JS_FreeCString(ctx, string_arg);
|
|
} else {
|
|
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
|
|
goto fail;
|
|
}
|
|
/* handle utf-8 encoding explicitly */
|
|
if ((unsigned)int32_arg > 0x10FFFF)
|
|
int32_arg = 0xFFFD;
|
|
/* ignore conversion flags, width and precision */
|
|
len = unicode_to_utf8(cbuf, int32_arg);
|
|
dbuf_put(&dbuf, cbuf, len);
|
|
break;
|
|
|
|
case 'd':
|
|
case 'i':
|
|
case 'o':
|
|
case 'u':
|
|
case 'x':
|
|
case 'X':
|
|
if (i >= argc)
|
|
goto missing;
|
|
if (modsize > 0) {
|
|
if (JS_ToInt64(ctx, &int64_arg, argv[i++]))
|
|
goto fail;
|
|
q[1] = q[-1];
|
|
q[-1] = q[0] = 'l';
|
|
q[2] = '\0';
|
|
dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg);
|
|
} else {
|
|
if (JS_ToInt32(ctx, &int32_arg, argv[i++]))
|
|
goto fail;
|
|
dbuf_printf_fun(&dbuf, fmtbuf, int32_arg);
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
if (i >= argc)
|
|
goto missing;
|
|
string_arg = JS_ToCString(ctx, argv[i++]);
|
|
if (!string_arg)
|
|
goto fail;
|
|
dbuf_printf_fun(&dbuf, fmtbuf, string_arg);
|
|
JS_FreeCString(ctx, string_arg);
|
|
break;
|
|
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
case 'a':
|
|
case 'E':
|
|
case 'F':
|
|
case 'G':
|
|
case 'A':
|
|
if (i >= argc)
|
|
goto missing;
|
|
if (JS_ToFloat64(ctx, &double_arg, argv[i++]))
|
|
goto fail;
|
|
dbuf_printf_fun(&dbuf, fmtbuf, double_arg);
|
|
break;
|
|
|
|
case '%':
|
|
dbuf_putc(&dbuf, '%');
|
|
break;
|
|
|
|
default:
|
|
/* XXX: should support an extension mechanism */
|
|
invalid:
|
|
JS_ThrowTypeError(ctx, "invalid conversion specifier in format string");
|
|
goto fail;
|
|
missing:
|
|
JS_ThrowReferenceError(ctx, "missing argument for conversion specifier");
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
JS_FreeCString(ctx, fmt_str);
|
|
}
|
|
if (dbuf.error) {
|
|
res = JS_ThrowOutOfMemory(ctx);
|
|
} else {
|
|
if (fp) {
|
|
len = fwrite(dbuf.buf, 1, dbuf.size, fp);
|
|
res = JS_NewInt32(ctx, len);
|
|
} else {
|
|
res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size);
|
|
}
|
|
}
|
|
dbuf_free(&dbuf);
|
|
return res;
|
|
|
|
fail:
|
|
dbuf_free(&dbuf);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename)
|
|
{
|
|
FILE *f;
|
|
uint8_t *buf;
|
|
size_t buf_len;
|
|
|
|
f = fopen(filename, "rb");
|
|
if (!f)
|
|
return NULL;
|
|
fseek(f, 0, SEEK_END);
|
|
buf_len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
if (ctx)
|
|
buf = js_malloc(ctx, buf_len + 1);
|
|
else
|
|
buf = malloc(buf_len + 1);
|
|
fread(buf, 1, buf_len, f);
|
|
buf[buf_len] = '\0';
|
|
fclose(f);
|
|
*pbuf_len = buf_len;
|
|
return buf;
|
|
}
|
|
|
|
/* load and evaluate a file */
|
|
static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
uint8_t *buf;
|
|
const char *filename;
|
|
JSValue ret;
|
|
size_t buf_len;
|
|
|
|
filename = JS_ToCString(ctx, argv[0]);
|
|
if (!filename)
|
|
return JS_EXCEPTION;
|
|
buf = js_load_file(ctx, &buf_len, filename);
|
|
if (!buf) {
|
|
JS_ThrowReferenceError(ctx, "could not load '%s'", filename);
|
|
JS_FreeCString(ctx, filename);
|
|
return JS_EXCEPTION;
|
|
}
|
|
ret = JS_Eval(ctx, (char *)buf, buf_len, filename,
|
|
JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_SHEBANG);
|
|
js_free(ctx, buf);
|
|
JS_FreeCString(ctx, filename);
|
|
return ret;
|
|
}
|
|
|
|
typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx,
|
|
const char *module_name);
|
|
|
|
|
|
#if defined(_WIN32)
|
|
static JSModuleDef *js_module_loader_so(JSContext *ctx,
|
|
const char *module_name)
|
|
{
|
|
JS_ThrowReferenceError(ctx, "shared library modules are not supported yet");
|
|
return NULL;
|
|
}
|
|
#else
|
|
static JSModuleDef *js_module_loader_so(JSContext *ctx,
|
|
const char *module_name)
|
|
{
|
|
JSModuleDef *m;
|
|
void *hd;
|
|
JSInitModuleFunc *init;
|
|
char *filename;
|
|
|
|
if (!strchr(module_name, '/')) {
|
|
/* must add a '/' so that the DLL is not searched in the
|
|
system library paths */
|
|
filename = js_malloc(ctx, strlen(module_name) + 2 + 1);
|
|
if (!filename)
|
|
return NULL;
|
|
strcpy(filename, "./");
|
|
strcpy(filename + 2, module_name);
|
|
} else {
|
|
filename = (char *)module_name;
|
|
}
|
|
|
|
/* C module */
|
|
hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
|
|
if (filename != module_name)
|
|
js_free(ctx, filename);
|
|
if (!hd) {
|
|
JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library",
|
|
module_name);
|
|
goto fail;
|
|
}
|
|
|
|
init = dlsym(hd, "js_init_module");
|
|
if (!init) {
|
|
JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found",
|
|
module_name);
|
|
goto fail;
|
|
}
|
|
|
|
m = init(ctx, module_name);
|
|
if (!m) {
|
|
JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error",
|
|
module_name);
|
|
fail:
|
|
if (hd)
|
|
dlclose(hd);
|
|
return NULL;
|
|
}
|
|
return m;
|
|
}
|
|
#endif /* !_WIN32 */
|
|
|
|
JSModuleDef *js_module_loader(JSContext *ctx,
|
|
const char *module_name, void *opaque)
|
|
{
|
|
JSModuleDef *m;
|
|
|
|
if (has_suffix(module_name, ".so")) {
|
|
m = js_module_loader_so(ctx, module_name);
|
|
} else {
|
|
size_t buf_len;
|
|
uint8_t *buf;
|
|
JSValue func_val;
|
|
|
|
buf = js_load_file(ctx, &buf_len, module_name);
|
|
if (!buf) {
|
|
JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
|
|
module_name);
|
|
return NULL;
|
|
}
|
|
|
|
/* compile the module */
|
|
func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
|
|
JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
js_free(ctx, buf);
|
|
if (JS_IsException(func_val))
|
|
return NULL;
|
|
/* the module is already referenced, so we must free it */
|
|
m = JS_VALUE_GET_PTR(func_val);
|
|
JS_FreeValue(ctx, func_val);
|
|
}
|
|
return m;
|
|
}
|
|
|
|
static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int status;
|
|
if (JS_ToInt32(ctx, &status, argv[0]))
|
|
status = -1;
|
|
exit(status);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
const char *name, *str;
|
|
name = JS_ToCString(ctx, argv[0]);
|
|
if (!name)
|
|
return JS_EXCEPTION;
|
|
str = getenv(name);
|
|
JS_FreeCString(ctx, name);
|
|
if (!str)
|
|
return JS_UNDEFINED;
|
|
else
|
|
return JS_NewString(ctx, str);
|
|
}
|
|
|
|
static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
JS_RunGC(JS_GetRuntime(ctx));
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static int interrupt_handler(JSRuntime *rt, void *opaque)
|
|
{
|
|
return (os_pending_signals >> SIGINT) & 1;
|
|
}
|
|
|
|
static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
const char *str;
|
|
int len;
|
|
JSValue ret;
|
|
str = JS_ToCStringLen(ctx, &len, argv[0], FALSE);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
if (++eval_script_recurse == 1) {
|
|
/* install the interrupt handler */
|
|
JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL);
|
|
}
|
|
ret = JS_Eval(ctx, str, len, "<evalScript>", JS_EVAL_TYPE_GLOBAL);
|
|
if (--eval_script_recurse == 0) {
|
|
/* remove the interrupt handler */
|
|
JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL);
|
|
os_pending_signals &= ~((uint64_t)1 << SIGINT);
|
|
/* convert the uncatchable "interrupted" error into a normal error
|
|
so that it can be caught by the REPL */
|
|
if (JS_IsException(ret))
|
|
JS_ResetUncatchableError(ctx);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static JSClassID js_std_file_class_id;
|
|
|
|
typedef struct {
|
|
FILE *f;
|
|
BOOL close_in_finalizer;
|
|
} JSSTDFile;
|
|
|
|
static void js_std_file_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id);
|
|
if (s) {
|
|
if (s->f && s->close_in_finalizer)
|
|
fclose(s->f);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
static JSValue js_new_std_error(JSContext *ctx, int err)
|
|
{
|
|
JSValue obj;
|
|
/* XXX: could add a specific Error prototype */
|
|
obj = JS_NewError(ctx);
|
|
JS_DefinePropertyValueStr(ctx, obj, "message",
|
|
JS_NewString(ctx, strerror(err)),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
JS_DefinePropertyValueStr(ctx, obj, "errno",
|
|
JS_NewInt32(ctx, err),
|
|
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_std_error_constructor(JSContext *ctx, JSValueConst new_target,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int err;
|
|
if (JS_ToInt32(ctx, &err, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return js_new_std_error(ctx, err);
|
|
}
|
|
|
|
static JSValue js_std_error_strerror(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int err;
|
|
if (JS_ToInt32(ctx, &err, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return JS_NewString(ctx, strerror(err));
|
|
}
|
|
|
|
static JSValue js_std_throw_errno(JSContext *ctx, int err)
|
|
{
|
|
JSValue obj;
|
|
obj = js_new_std_error(ctx, err);
|
|
if (JS_IsException(obj))
|
|
obj = JS_NULL;
|
|
return JS_Throw(ctx, obj);
|
|
}
|
|
|
|
static JSValue js_new_std_file(JSContext *ctx, FILE *f, BOOL close_in_finalizer)
|
|
{
|
|
JSSTDFile *s;
|
|
JSValue obj;
|
|
obj = JS_NewObjectClass(ctx, js_std_file_class_id);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
s->close_in_finalizer = close_in_finalizer;
|
|
s->f = f;
|
|
JS_SetOpaque(obj, s);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_std_open(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
const char *filename, *mode = NULL;
|
|
FILE *f;
|
|
|
|
filename = JS_ToCString(ctx, argv[0]);
|
|
if (!filename)
|
|
goto fail;
|
|
mode = JS_ToCString(ctx, argv[1]);
|
|
if (!mode)
|
|
goto fail;
|
|
if (mode[strspn(mode, "rwa+b")] != '\0') {
|
|
js_std_throw_errno(ctx, EINVAL);
|
|
goto fail;
|
|
}
|
|
|
|
f = fopen(filename, mode);
|
|
JS_FreeCString(ctx, filename);
|
|
JS_FreeCString(ctx, mode);
|
|
if (!f)
|
|
return js_std_throw_errno(ctx, errno);
|
|
return js_new_std_file(ctx, f, TRUE);
|
|
fail:
|
|
JS_FreeCString(ctx, filename);
|
|
JS_FreeCString(ctx, mode);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f;
|
|
f = tmpfile();
|
|
if (!f)
|
|
return js_std_throw_errno(ctx, errno);
|
|
return js_new_std_file(ctx, f, TRUE);
|
|
}
|
|
|
|
static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
return js_printf_internal(ctx, argc, argv, NULL);
|
|
}
|
|
|
|
static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
return js_printf_internal(ctx, argc, argv, stdout);
|
|
}
|
|
|
|
static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj)
|
|
{
|
|
JSSTDFile *s = JS_GetOpaque2(ctx, obj, js_std_file_class_id);
|
|
if (!s)
|
|
return NULL;
|
|
if (!s->f) {
|
|
js_std_throw_errno(ctx, EBADF);
|
|
return NULL;
|
|
}
|
|
return s->f;
|
|
}
|
|
|
|
static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
FILE *f;
|
|
int i;
|
|
const char *str;
|
|
|
|
if (magic == 0) {
|
|
f = stdout;
|
|
} else {
|
|
f = js_std_file_get(ctx, this_val);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
for(i = 0; i < argc; i++) {
|
|
str = JS_ToCString(ctx, argv[i]);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
fputs(str, f);
|
|
JS_FreeCString(ctx, str);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
JSSTDFile *s = JS_GetOpaque2(ctx, this_val, js_std_file_class_id);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
if (!s->f)
|
|
return js_std_throw_errno(ctx, EBADF);
|
|
fclose(s->f);
|
|
s->f = NULL;
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
return js_printf_internal(ctx, argc, argv, f);
|
|
}
|
|
|
|
static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
fflush(f);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
int64_t pos;
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
#if defined(__linux__)
|
|
pos = ftello(f);
|
|
#else
|
|
pos = ftell(f);
|
|
#endif
|
|
return JS_NewInt64(ctx, pos);
|
|
}
|
|
|
|
static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
int64_t pos;
|
|
int whence, ret;
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt64(ctx, &pos, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &whence, argv[1]))
|
|
return JS_EXCEPTION;
|
|
#if defined(__linux__)
|
|
ret = fseeko(f, pos, whence);
|
|
#else
|
|
ret = fseek(f, pos, whence);
|
|
#endif
|
|
if (ret < 0)
|
|
return js_std_throw_errno(ctx, EBADF);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(ctx, feof(f));
|
|
}
|
|
|
|
static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
return JS_NewInt32(ctx, fileno(f));
|
|
}
|
|
|
|
static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
uint64_t pos, len;
|
|
size_t size, ret;
|
|
uint8_t *buf;
|
|
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
if (JS_ToIndex(ctx, &pos, argv[1]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToIndex(ctx, &len, argv[2]))
|
|
return JS_EXCEPTION;
|
|
buf = JS_GetArrayBuffer(ctx, &size, argv[0]);
|
|
if (!buf)
|
|
return JS_EXCEPTION;
|
|
if (pos + len > size)
|
|
return JS_ThrowRangeError(ctx, "read/write array buffer overflow");
|
|
if (magic)
|
|
ret = fwrite(buf + pos, 1, len, f);
|
|
else
|
|
ret = fread(buf + pos, 1, len, f);
|
|
return JS_NewInt64(ctx, ret);
|
|
}
|
|
|
|
/* XXX: could use less memory and go faster */
|
|
static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
int c;
|
|
DynBuf dbuf;
|
|
JSValue obj;
|
|
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
|
|
js_std_dbuf_init(ctx, &dbuf);
|
|
for(;;) {
|
|
c = fgetc(f);
|
|
if (c == EOF) {
|
|
if (dbuf.size == 0) {
|
|
/* EOF */
|
|
dbuf_free(&dbuf);
|
|
return JS_NULL;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (c == '\n')
|
|
break;
|
|
if (dbuf_putc(&dbuf, c)) {
|
|
dbuf_free(&dbuf);
|
|
return JS_ThrowOutOfMemory(ctx);
|
|
}
|
|
}
|
|
obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size);
|
|
dbuf_free(&dbuf);
|
|
return obj;
|
|
}
|
|
|
|
/* XXX: could use less memory and go faster */
|
|
static JSValue js_std_file_readAsString(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
int c;
|
|
DynBuf dbuf;
|
|
JSValue obj;
|
|
uint64_t max_size64;
|
|
size_t max_size;
|
|
JSValueConst max_size_val;
|
|
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
|
|
if (argc >= 1)
|
|
max_size_val = argv[0];
|
|
else
|
|
max_size_val = JS_UNDEFINED;
|
|
max_size = (size_t)-1;
|
|
if (!JS_IsUndefined(max_size_val)) {
|
|
if (JS_ToIndex(ctx, &max_size64, max_size_val))
|
|
return JS_EXCEPTION;
|
|
if (max_size64 < max_size)
|
|
max_size = max_size64;
|
|
}
|
|
|
|
js_std_dbuf_init(ctx, &dbuf);
|
|
while (max_size != 0) {
|
|
c = fgetc(f);
|
|
if (c == EOF)
|
|
break;
|
|
if (dbuf_putc(&dbuf, c)) {
|
|
dbuf_free(&dbuf);
|
|
return JS_EXCEPTION;
|
|
}
|
|
max_size--;
|
|
}
|
|
obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size);
|
|
dbuf_free(&dbuf);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
return JS_NewInt32(ctx, fgetc(f));
|
|
}
|
|
|
|
static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
FILE *f = js_std_file_get(ctx, this_val);
|
|
int c;
|
|
if (!f)
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &c, argv[0]))
|
|
return JS_EXCEPTION;
|
|
c = fputc(c, f);
|
|
return JS_NewInt32(ctx, c);
|
|
}
|
|
|
|
static JSClassDef js_std_file_class = {
|
|
"FILE",
|
|
.finalizer = js_std_file_finalizer,
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_std_funcs[] = {
|
|
JS_CFUNC_DEF("exit", 1, js_std_exit ),
|
|
JS_CFUNC_DEF("gc", 0, js_std_gc ),
|
|
JS_CFUNC_DEF("evalScript", 1, js_evalScript ),
|
|
JS_CFUNC_DEF("loadScript", 1, js_loadScript ),
|
|
JS_CFUNC_DEF("getenv", 1, js_std_getenv ),
|
|
|
|
/* FILE I/O */
|
|
JS_CFUNC_DEF("open", 2, js_std_open ),
|
|
JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ),
|
|
JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ),
|
|
JS_CFUNC_DEF("printf", 1, js_std_printf ),
|
|
JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ),
|
|
JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ),
|
|
JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ),
|
|
JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ),
|
|
|
|
/* setenv, ... */
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_std_error_funcs[] = {
|
|
JS_CFUNC_DEF("strerror", 1, js_std_error_strerror ),
|
|
/* various errno values */
|
|
#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE )
|
|
DEF(EINVAL),
|
|
DEF(EIO),
|
|
DEF(EACCES),
|
|
DEF(EEXIST),
|
|
DEF(ENOSPC),
|
|
DEF(ENOSYS),
|
|
DEF(EBUSY),
|
|
DEF(ENOENT),
|
|
DEF(EPERM),
|
|
DEF(EPIPE),
|
|
DEF(EBADF),
|
|
#undef DEF
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_std_file_proto_funcs[] = {
|
|
JS_CFUNC_DEF("close", 0, js_std_file_close ),
|
|
JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ),
|
|
JS_CFUNC_DEF("printf", 1, js_std_file_printf ),
|
|
JS_CFUNC_DEF("flush", 0, js_std_file_flush ),
|
|
JS_CFUNC_DEF("tell", 0, js_std_file_tell ),
|
|
JS_CFUNC_DEF("seek", 2, js_std_file_seek ),
|
|
JS_CFUNC_DEF("eof", 0, js_std_file_eof ),
|
|
JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ),
|
|
JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ),
|
|
JS_CFUNC_DEF("getline", 0, js_std_file_getline ),
|
|
JS_CFUNC_DEF("readAsString", 0, js_std_file_readAsString ),
|
|
JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ),
|
|
JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ),
|
|
/* setvbuf, ferror, clearerr, ... */
|
|
};
|
|
|
|
static int js_std_init(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
JSValue proto, obj;
|
|
|
|
/* FILE class */
|
|
/* the class ID is created once */
|
|
JS_NewClassID(&js_std_file_class_id);
|
|
/* the class is created once per runtime */
|
|
JS_NewClass(JS_GetRuntime(ctx), js_std_file_class_id, &js_std_file_class);
|
|
proto = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs,
|
|
countof(js_std_file_proto_funcs));
|
|
JS_SetClassProto(ctx, js_std_file_class_id, proto);
|
|
|
|
JS_SetModuleExportList(ctx, m, js_std_funcs,
|
|
countof(js_std_funcs));
|
|
JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE));
|
|
JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE));
|
|
JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE));
|
|
|
|
obj = JS_NewCFunction2(ctx, js_std_error_constructor,
|
|
"Error", 1, JS_CFUNC_constructor, 0);
|
|
JS_SetPropertyFunctionList(ctx, obj, js_std_error_funcs,
|
|
countof(js_std_error_funcs));
|
|
JS_SetModuleExport(ctx, m, "Error", obj);
|
|
|
|
/* global object */
|
|
JS_SetModuleExport(ctx, m, "global", JS_GetGlobalObject(ctx));
|
|
return 0;
|
|
}
|
|
|
|
JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name)
|
|
{
|
|
JSModuleDef *m;
|
|
m = JS_NewCModule(ctx, module_name, js_std_init);
|
|
if (!m)
|
|
return NULL;
|
|
JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs));
|
|
JS_AddModuleExport(ctx, m, "in");
|
|
JS_AddModuleExport(ctx, m, "out");
|
|
JS_AddModuleExport(ctx, m, "err");
|
|
JS_AddModuleExport(ctx, m, "global");
|
|
JS_AddModuleExport(ctx, m, "Error");
|
|
return m;
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* 'os' object */
|
|
|
|
static JSValue js_os_return(JSContext *ctx, ssize_t ret)
|
|
{
|
|
if (ret < 0)
|
|
ret = -errno;
|
|
return JS_NewInt64(ctx, ret);
|
|
}
|
|
|
|
static JSValue js_os_open(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
const char *filename;
|
|
int flags, mode, ret;
|
|
|
|
filename = JS_ToCString(ctx, argv[0]);
|
|
if (!filename)
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &flags, argv[1]))
|
|
goto fail;
|
|
if (argc >= 3 && !JS_IsUndefined(argv[2])) {
|
|
if (JS_ToInt32(ctx, &mode, argv[2])) {
|
|
fail:
|
|
JS_FreeCString(ctx, filename);
|
|
return JS_EXCEPTION;
|
|
}
|
|
} else {
|
|
mode = 0666;
|
|
}
|
|
#if defined(_WIN32)
|
|
/* force binary mode by default */
|
|
if (!(flags & O_TEXT))
|
|
flags |= O_BINARY;
|
|
#endif
|
|
ret = open(filename, flags, mode);
|
|
JS_FreeCString(ctx, filename);
|
|
return js_os_return(ctx, ret);
|
|
}
|
|
|
|
static JSValue js_os_close(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int fd, ret;
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
ret = close(fd);
|
|
return js_os_return(ctx, ret);
|
|
}
|
|
|
|
static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int fd, whence, ret;
|
|
int64_t pos;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt64(ctx, &pos, argv[1]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToInt32(ctx, &whence, argv[2]))
|
|
return JS_EXCEPTION;
|
|
ret = lseek(fd, pos, whence);
|
|
return js_os_return(ctx, ret);
|
|
}
|
|
|
|
static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
int fd;
|
|
uint64_t pos, len;
|
|
size_t size;
|
|
ssize_t ret;
|
|
uint8_t *buf;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToIndex(ctx, &pos, argv[2]))
|
|
return JS_EXCEPTION;
|
|
if (JS_ToIndex(ctx, &len, argv[3]))
|
|
return JS_EXCEPTION;
|
|
buf = JS_GetArrayBuffer(ctx, &size, argv[1]);
|
|
if (!buf)
|
|
return JS_EXCEPTION;
|
|
if (pos + len > size)
|
|
return JS_ThrowRangeError(ctx, "read/write array buffer overflow");
|
|
if (magic)
|
|
ret = write(fd, buf + pos, len);
|
|
else
|
|
ret = read(fd, buf + pos, len);
|
|
return js_os_return(ctx, ret);
|
|
}
|
|
|
|
static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int fd;
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
return JS_NewBool(ctx, isatty(fd) == 1);
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int fd;
|
|
HANDLE handle;
|
|
CONSOLE_SCREEN_BUFFER_INFO info;
|
|
JSValue obj;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
handle = (HANDLE)_get_osfhandle(fd);
|
|
|
|
if (!GetConsoleScreenBufferInfo(handle, &info))
|
|
return JS_NULL;
|
|
obj = JS_NewArray(ctx);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E);
|
|
JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int fd;
|
|
HANDLE handle;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
handle = (HANDLE)_get_osfhandle(fd);
|
|
|
|
SetConsoleMode(handle, ENABLE_WINDOW_INPUT);
|
|
return JS_UNDEFINED;
|
|
}
|
|
#else
|
|
static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int fd;
|
|
struct winsize ws;
|
|
JSValue obj;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (ioctl(fd, TIOCGWINSZ, &ws) == 0 &&
|
|
ws.ws_col >= 4 && ws.ws_row >= 4) {
|
|
obj = JS_NewArray(ctx);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E);
|
|
JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E);
|
|
return obj;
|
|
} else {
|
|
return JS_NULL;
|
|
}
|
|
}
|
|
|
|
static struct termios oldtty;
|
|
|
|
static void term_exit(void)
|
|
{
|
|
tcsetattr(0, TCSANOW, &oldtty);
|
|
}
|
|
|
|
/* XXX: should add a way to go back to normal mode */
|
|
static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
struct termios tty;
|
|
int fd;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
|
|
memset(&tty, 0, sizeof(tty));
|
|
tcgetattr(fd, &tty);
|
|
oldtty = tty;
|
|
|
|
tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
|
|
|INLCR|IGNCR|ICRNL|IXON);
|
|
tty.c_oflag |= OPOST;
|
|
tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
|
|
tty.c_cflag &= ~(CSIZE|PARENB);
|
|
tty.c_cflag |= CS8;
|
|
tty.c_cc[VMIN] = 1;
|
|
tty.c_cc[VTIME] = 0;
|
|
|
|
tcsetattr(fd, TCSANOW, &tty);
|
|
|
|
atexit(term_exit);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
#endif /* !_WIN32 */
|
|
|
|
static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
const char *filename;
|
|
int ret;
|
|
|
|
filename = JS_ToCString(ctx, argv[0]);
|
|
if (!filename)
|
|
return JS_EXCEPTION;
|
|
ret = remove(filename);
|
|
JS_FreeCString(ctx, filename);
|
|
return js_os_return(ctx, ret);
|
|
}
|
|
|
|
static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
const char *oldpath, *newpath;
|
|
int ret;
|
|
|
|
oldpath = JS_ToCString(ctx, argv[0]);
|
|
if (!oldpath)
|
|
return JS_EXCEPTION;
|
|
newpath = JS_ToCString(ctx, argv[1]);
|
|
if (!newpath) {
|
|
JS_FreeCString(ctx, oldpath);
|
|
return JS_EXCEPTION;
|
|
}
|
|
ret = rename(oldpath, newpath);
|
|
JS_FreeCString(ctx, oldpath);
|
|
JS_FreeCString(ctx, newpath);
|
|
return js_os_return(ctx, ret);
|
|
}
|
|
|
|
static JSOSRWHandler *find_rh(int fd)
|
|
{
|
|
JSOSRWHandler *rh;
|
|
struct list_head *el;
|
|
list_for_each(el, &os_rw_handlers) {
|
|
rh = list_entry(el, JSOSRWHandler, link);
|
|
if (rh->fd == fd)
|
|
return rh;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh)
|
|
{
|
|
int i;
|
|
list_del(&rh->link);
|
|
for(i = 0; i < 2; i++) {
|
|
JS_FreeValueRT(rt, rh->rw_func[i]);
|
|
}
|
|
js_free_rt(rt, rh);
|
|
}
|
|
|
|
static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSOSRWHandler *rh;
|
|
int fd;
|
|
JSValueConst func;
|
|
|
|
if (JS_ToInt32(ctx, &fd, argv[0]))
|
|
return JS_EXCEPTION;
|
|
func = argv[1];
|
|
if (JS_IsNull(func)) {
|
|
rh = find_rh(fd);
|
|
if (rh) {
|
|
JS_FreeValue(ctx, rh->rw_func[magic]);
|
|
rh->rw_func[magic] = JS_NULL;
|
|
if (JS_IsNull(rh->rw_func[0]) &&
|
|
JS_IsNull(rh->rw_func[1])) {
|
|
/* remove the entry */
|
|
free_rw_handler(JS_GetRuntime(ctx), rh);
|
|
}
|
|
}
|
|
} else {
|
|
if (!JS_IsFunction(ctx, func))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
rh = find_rh(fd);
|
|
if (!rh) {
|
|
rh = js_mallocz(ctx, sizeof(*rh));
|
|
if (!rh)
|
|
return JS_EXCEPTION;
|
|
rh->fd = fd;
|
|
rh->rw_func[0] = JS_NULL;
|
|
rh->rw_func[1] = JS_NULL;
|
|
list_add_tail(&rh->link, &os_rw_handlers);
|
|
}
|
|
JS_FreeValue(ctx, rh->rw_func[magic]);
|
|
rh->rw_func[magic] = JS_DupValue(ctx, func);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSOSSignalHandler *find_sh(int sig_num)
|
|
{
|
|
JSOSSignalHandler *sh;
|
|
struct list_head *el;
|
|
list_for_each(el, &os_signal_handlers) {
|
|
sh = list_entry(el, JSOSSignalHandler, link);
|
|
if (sh->sig_num == sig_num)
|
|
return sh;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh)
|
|
{
|
|
list_del(&sh->link);
|
|
JS_FreeValueRT(rt, sh->func);
|
|
js_free_rt(rt, sh);
|
|
}
|
|
|
|
static void os_signal_handler(int sig_num)
|
|
{
|
|
os_pending_signals |= ((uint64_t)1 << sig_num);
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
typedef void (*sighandler_t)(int sig_num);
|
|
#endif
|
|
|
|
static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
JSOSSignalHandler *sh;
|
|
uint32_t sig_num;
|
|
JSValueConst func;
|
|
sighandler_t handler;
|
|
|
|
if (JS_ToUint32(ctx, &sig_num, argv[0]))
|
|
return JS_EXCEPTION;
|
|
if (sig_num >= 64)
|
|
return JS_ThrowRangeError(ctx, "invalid signal number");
|
|
func = argv[1];
|
|
/* func = null: SIG_DFL, func = undefined, SIG_IGN */
|
|
if (JS_IsNull(func) || JS_IsUndefined(func)) {
|
|
sh = find_sh(sig_num);
|
|
if (sh) {
|
|
free_sh(JS_GetRuntime(ctx), sh);
|
|
}
|
|
if (JS_IsNull(func))
|
|
handler = SIG_DFL;
|
|
else
|
|
handler = SIG_IGN;
|
|
signal(sig_num, handler);
|
|
} else {
|
|
if (!JS_IsFunction(ctx, func))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
sh = find_sh(sig_num);
|
|
if (!sh) {
|
|
sh = js_mallocz(ctx, sizeof(*sh));
|
|
if (!sh)
|
|
return JS_EXCEPTION;
|
|
sh->sig_num = sig_num;
|
|
list_add_tail(&sh->link, &os_signal_handlers);
|
|
}
|
|
JS_FreeValue(ctx, sh->func);
|
|
sh->func = JS_DupValue(ctx, func);
|
|
signal(sig_num, os_signal_handler);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
static int64_t get_time_ms(void)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000);
|
|
}
|
|
#else
|
|
/* more portable, but does not work if the date is updated */
|
|
static int64_t get_time_ms(void)
|
|
{
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000);
|
|
}
|
|
#endif
|
|
|
|
static void unlink_timer(JSRuntime *rt, JSOSTimer *th)
|
|
{
|
|
if (th->link.prev) {
|
|
list_del(&th->link);
|
|
th->link.prev = th->link.next = NULL;
|
|
}
|
|
}
|
|
|
|
static void free_timer(JSRuntime *rt, JSOSTimer *th)
|
|
{
|
|
JS_FreeValueRT(rt, th->func);
|
|
js_free_rt(rt, th);
|
|
}
|
|
|
|
static JSClassID js_os_timer_class_id;
|
|
|
|
static void js_os_timer_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
|
|
if (th) {
|
|
th->has_object = FALSE;
|
|
if (!th->link.prev)
|
|
free_timer(rt, th);
|
|
}
|
|
}
|
|
|
|
static void js_os_timer_mark(JSRuntime *rt, JSValueConst val,
|
|
JS_MarkFunc *mark_func)
|
|
{
|
|
JSOSTimer *th = JS_GetOpaque(val, js_os_timer_class_id);
|
|
if (th) {
|
|
JS_MarkValue(rt, th->func, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int64_t delay;
|
|
JSValueConst func;
|
|
JSOSTimer *th;
|
|
JSValue obj;
|
|
|
|
if (JS_ToInt64(ctx, &delay, argv[0]))
|
|
return JS_EXCEPTION;
|
|
func = argv[1];
|
|
if (!JS_IsFunction(ctx, func))
|
|
return JS_ThrowTypeError(ctx, "not a function");
|
|
obj = JS_NewObjectClass(ctx, js_os_timer_class_id);
|
|
if (JS_IsException(obj))
|
|
return obj;
|
|
th = js_mallocz(ctx, sizeof(*th));
|
|
th->has_object = TRUE;
|
|
if (!th) {
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
th->timeout = get_time_ms() + delay;
|
|
th->func = JS_DupValue(ctx, func);
|
|
list_add_tail(&th->link, &os_timers);
|
|
JS_SetOpaque(obj, th);
|
|
return obj;
|
|
}
|
|
|
|
static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
JSOSTimer *th = JS_GetOpaque2(ctx, argv[0], js_os_timer_class_id);
|
|
if (!th)
|
|
return JS_EXCEPTION;
|
|
unlink_timer(JS_GetRuntime(ctx), th);
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSClassDef js_os_timer_class = {
|
|
"OSTimer",
|
|
.finalizer = js_os_timer_finalizer,
|
|
.gc_mark = js_os_timer_mark,
|
|
};
|
|
|
|
static void call_handler(JSContext *ctx, JSValueConst func)
|
|
{
|
|
JSValue ret;
|
|
ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL);
|
|
if (JS_IsException(ret))
|
|
js_std_dump_error(ctx);
|
|
JS_FreeValue(ctx, ret);
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
|
|
static int js_os_poll(JSContext *ctx)
|
|
{
|
|
int min_delay, console_fd;
|
|
int64_t cur_time, delay;
|
|
JSOSRWHandler *rh;
|
|
struct list_head *el;
|
|
|
|
/* XXX: handle signals if useful */
|
|
|
|
if (list_empty(&os_rw_handlers) && list_empty(&os_timers))
|
|
return -1; /* no more events */
|
|
|
|
/* XXX: only timers and basic console input are supported */
|
|
if (!list_empty(&os_timers)) {
|
|
cur_time = get_time_ms();
|
|
min_delay = 10000;
|
|
list_for_each(el, &os_timers) {
|
|
JSOSTimer *th = list_entry(el, JSOSTimer, link);
|
|
delay = th->timeout - cur_time;
|
|
if (delay <= 0) {
|
|
JSValue func;
|
|
/* the timer expired */
|
|
func = th->func;
|
|
th->func = JS_UNDEFINED;
|
|
unlink_timer(JS_GetRuntime(ctx), th);
|
|
if (!th->has_object)
|
|
free_timer(JS_GetRuntime(ctx), th);
|
|
call_handler(ctx, func);
|
|
JS_FreeValue(ctx, func);
|
|
return 0;
|
|
} else if (delay < min_delay) {
|
|
min_delay = delay;
|
|
}
|
|
}
|
|
} else {
|
|
min_delay = -1;
|
|
}
|
|
|
|
console_fd = -1;
|
|
list_for_each(el, &os_rw_handlers) {
|
|
rh = list_entry(el, JSOSRWHandler, link);
|
|
if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) {
|
|
console_fd = rh->fd;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (console_fd >= 0) {
|
|
DWORD ti, ret;
|
|
HANDLE handle;
|
|
if (min_delay == -1)
|
|
ti = INFINITE;
|
|
else
|
|
ti = min_delay;
|
|
handle = (HANDLE)_get_osfhandle(console_fd);
|
|
ret = WaitForSingleObject(handle, ti);
|
|
if (ret == WAIT_OBJECT_0) {
|
|
list_for_each(el, &os_rw_handlers) {
|
|
rh = list_entry(el, JSOSRWHandler, link);
|
|
if (rh->fd == console_fd && !JS_IsNull(rh->rw_func[0])) {
|
|
call_handler(ctx, rh->rw_func[0]);
|
|
/* must stop because the list may have been modified */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Sleep(min_delay);
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
static int js_os_poll(JSContext *ctx)
|
|
{
|
|
int ret, fd_max, min_delay;
|
|
int64_t cur_time, delay;
|
|
fd_set rfds, wfds;
|
|
JSOSRWHandler *rh;
|
|
struct list_head *el;
|
|
struct timeval tv, *tvp;
|
|
|
|
if (unlikely(os_pending_signals != 0)) {
|
|
JSOSSignalHandler *sh;
|
|
uint64_t mask;
|
|
|
|
list_for_each(el, &os_signal_handlers) {
|
|
sh = list_entry(el, JSOSSignalHandler, link);
|
|
mask = (uint64_t)1 << sh->sig_num;
|
|
if (os_pending_signals & mask) {
|
|
os_pending_signals &= ~mask;
|
|
call_handler(ctx, sh->func);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (list_empty(&os_rw_handlers) && list_empty(&os_timers))
|
|
return -1; /* no more events */
|
|
|
|
if (!list_empty(&os_timers)) {
|
|
cur_time = get_time_ms();
|
|
min_delay = 10000;
|
|
list_for_each(el, &os_timers) {
|
|
JSOSTimer *th = list_entry(el, JSOSTimer, link);
|
|
delay = th->timeout - cur_time;
|
|
if (delay <= 0) {
|
|
JSValue func;
|
|
/* the timer expired */
|
|
func = th->func;
|
|
th->func = JS_UNDEFINED;
|
|
unlink_timer(JS_GetRuntime(ctx), th);
|
|
if (!th->has_object)
|
|
free_timer(JS_GetRuntime(ctx), th);
|
|
call_handler(ctx, func);
|
|
JS_FreeValue(ctx, func);
|
|
return 0;
|
|
} else if (delay < min_delay) {
|
|
min_delay = delay;
|
|
}
|
|
}
|
|
tv.tv_sec = min_delay / 1000;
|
|
tv.tv_usec = (min_delay % 1000) * 1000;
|
|
tvp = &tv;
|
|
} else {
|
|
tvp = NULL;
|
|
}
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_ZERO(&wfds);
|
|
fd_max = -1;
|
|
list_for_each(el, &os_rw_handlers) {
|
|
rh = list_entry(el, JSOSRWHandler, link);
|
|
fd_max = max_int(fd_max, rh->fd);
|
|
if (!JS_IsNull(rh->rw_func[0]))
|
|
FD_SET(rh->fd, &rfds);
|
|
if (!JS_IsNull(rh->rw_func[1]))
|
|
FD_SET(rh->fd, &wfds);
|
|
}
|
|
|
|
ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp);
|
|
if (ret > 0) {
|
|
list_for_each(el, &os_rw_handlers) {
|
|
rh = list_entry(el, JSOSRWHandler, link);
|
|
if (!JS_IsNull(rh->rw_func[0]) &&
|
|
FD_ISSET(rh->fd, &rfds)) {
|
|
call_handler(ctx, rh->rw_func[0]);
|
|
/* must stop because the list may have been modified */
|
|
break;
|
|
}
|
|
if (!JS_IsNull(rh->rw_func[1])) {
|
|
FD_SET(rh->fd, &wfds);
|
|
call_handler(ctx, rh->rw_func[1]);
|
|
/* must stop because the list may have been modified */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* !_WIN32 */
|
|
|
|
#if defined(_WIN32)
|
|
#define OS_PLATFORM "win32"
|
|
#elif defined(__APPLE__)
|
|
#define OS_PLATFORM "darwin"
|
|
#elif defined(EMSCRIPTEN)
|
|
#define OS_PLATFORM "js"
|
|
#else
|
|
#define OS_PLATFORM "linux"
|
|
#endif
|
|
|
|
#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE )
|
|
|
|
static const JSCFunctionListEntry js_os_funcs[] = {
|
|
JS_CFUNC_DEF("open", 2, js_os_open ),
|
|
OS_FLAG(O_RDONLY),
|
|
OS_FLAG(O_WRONLY),
|
|
OS_FLAG(O_RDWR),
|
|
OS_FLAG(O_APPEND),
|
|
OS_FLAG(O_CREAT),
|
|
OS_FLAG(O_EXCL),
|
|
OS_FLAG(O_TRUNC),
|
|
#if defined(_WIN32)
|
|
OS_FLAG(O_BINARY),
|
|
OS_FLAG(O_TEXT),
|
|
#endif
|
|
JS_CFUNC_DEF("close", 1, js_os_close ),
|
|
JS_CFUNC_DEF("seek", 3, js_os_seek ),
|
|
JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ),
|
|
JS_CFUNC_DEF("isatty", 1, js_os_isatty ),
|
|
JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ),
|
|
JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ),
|
|
JS_CFUNC_DEF("remove", 1, js_os_remove ),
|
|
JS_CFUNC_DEF("rename", 2, js_os_rename ),
|
|
JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ),
|
|
JS_CFUNC_DEF("signal", 2, js_os_signal ),
|
|
OS_FLAG(SIGINT),
|
|
OS_FLAG(SIGABRT),
|
|
OS_FLAG(SIGFPE),
|
|
OS_FLAG(SIGILL),
|
|
OS_FLAG(SIGSEGV),
|
|
OS_FLAG(SIGTERM),
|
|
JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ),
|
|
JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ),
|
|
JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ),
|
|
/* stat, readlink, opendir, closedir, ... */
|
|
};
|
|
|
|
static int js_os_init(JSContext *ctx, JSModuleDef *m)
|
|
{
|
|
os_poll_func = js_os_poll;
|
|
|
|
/* OSTimer class */
|
|
JS_NewClassID(&js_os_timer_class_id);
|
|
JS_NewClass(JS_GetRuntime(ctx), js_os_timer_class_id, &js_os_timer_class);
|
|
|
|
return JS_SetModuleExportList(ctx, m, js_os_funcs,
|
|
countof(js_os_funcs));
|
|
}
|
|
|
|
JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name)
|
|
{
|
|
JSModuleDef *m;
|
|
m = JS_NewCModule(ctx, module_name, js_os_init);
|
|
if (!m)
|
|
return NULL;
|
|
JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs));
|
|
return m;
|
|
}
|
|
|
|
/**********************************************************/
|
|
|
|
static JSValue js_print(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv)
|
|
{
|
|
int i;
|
|
const char *str;
|
|
|
|
for(i = 0; i < argc; i++) {
|
|
if (i != 0)
|
|
putchar(' ');
|
|
str = JS_ToCString(ctx, argv[i]);
|
|
if (!str)
|
|
return JS_EXCEPTION;
|
|
fputs(str, stdout);
|
|
JS_FreeCString(ctx, str);
|
|
}
|
|
putchar('\n');
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
void js_std_add_helpers(JSContext *ctx, int argc, char **argv)
|
|
{
|
|
JSValue global_obj, console, args;
|
|
int i;
|
|
|
|
/* XXX: should these global definitions be enumerable? */
|
|
global_obj = JS_GetGlobalObject(ctx);
|
|
|
|
console = JS_NewObject(ctx);
|
|
JS_SetPropertyStr(ctx, console, "log",
|
|
JS_NewCFunction(ctx, js_print, "log", 1));
|
|
JS_SetPropertyStr(ctx, global_obj, "console", console);
|
|
|
|
/* same methods as the mozilla JS shell */
|
|
args = JS_NewArray(ctx);
|
|
for(i = 0; i < argc; i++) {
|
|
JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i]));
|
|
}
|
|
JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args);
|
|
|
|
JS_SetPropertyStr(ctx, global_obj, "print",
|
|
JS_NewCFunction(ctx, js_print, "print", 1));
|
|
JS_SetPropertyStr(ctx, global_obj, "__loadScript",
|
|
JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1));
|
|
|
|
JS_FreeValue(ctx, global_obj);
|
|
|
|
/* XXX: not multi-context */
|
|
init_list_head(&os_rw_handlers);
|
|
init_list_head(&os_signal_handlers);
|
|
init_list_head(&os_timers);
|
|
os_pending_signals = 0;
|
|
}
|
|
|
|
void js_std_free_handlers(JSRuntime *rt)
|
|
{
|
|
struct list_head *el, *el1;
|
|
|
|
list_for_each_safe(el, el1, &os_rw_handlers) {
|
|
JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link);
|
|
free_rw_handler(rt, rh);
|
|
}
|
|
|
|
list_for_each_safe(el, el1, &os_signal_handlers) {
|
|
JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link);
|
|
free_sh(rt, sh);
|
|
}
|
|
|
|
list_for_each_safe(el, el1, &os_timers) {
|
|
JSOSTimer *th = list_entry(el, JSOSTimer, link);
|
|
unlink_timer(rt, th);
|
|
if (!th->has_object)
|
|
free_timer(rt, th);
|
|
}
|
|
}
|
|
|
|
void js_std_dump_error(JSContext *ctx)
|
|
{
|
|
JSValue exception_val, val;
|
|
const char *stack;
|
|
BOOL is_error;
|
|
|
|
exception_val = JS_GetException(ctx);
|
|
is_error = JS_IsError(ctx, exception_val);
|
|
if (!is_error)
|
|
printf("Throw: ");
|
|
js_print(ctx, JS_NULL, 1, (JSValueConst *)&exception_val);
|
|
if (is_error) {
|
|
val = JS_GetPropertyStr(ctx, exception_val, "stack");
|
|
if (!JS_IsUndefined(val)) {
|
|
stack = JS_ToCString(ctx, val);
|
|
printf("%s\n", stack);
|
|
JS_FreeCString(ctx, stack);
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
}
|
|
JS_FreeValue(ctx, exception_val);
|
|
}
|
|
|
|
/* main loop which calls the user JS callbacks */
|
|
void js_std_loop(JSContext *ctx)
|
|
{
|
|
JSContext *ctx1;
|
|
int err;
|
|
|
|
for(;;) {
|
|
/* execute the pending jobs */
|
|
for(;;) {
|
|
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
|
|
if (err <= 0) {
|
|
if (err < 0) {
|
|
js_std_dump_error(ctx1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!os_poll_func || os_poll_func(ctx))
|
|
break;
|
|
}
|
|
}
|
|
|
|
void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len,
|
|
int flags)
|
|
{
|
|
JSValue val;
|
|
val = JS_EvalBinary(ctx, buf, buf_len, flags);
|
|
if (JS_IsException(val)) {
|
|
js_std_dump_error(ctx);
|
|
exit(1);
|
|
}
|
|
JS_FreeValue(ctx, val);
|
|
}
|