diff --git a/Makefile b/Makefile index 109fcb2..aa012ce 100644 --- a/Makefile +++ b/Makefile @@ -75,15 +75,23 @@ ifdef CONFIG_ARM32 MQJS_BUILD_FLAGS=-m32 endif -PROGS=mqjs$(EXE) example$(EXE) +PROGS=mqjs$(EXE) example$(EXE) mqjsc$(EXE) TEST_PROGS=dtoa_test libm_test +LIB=libmquickjs.a -all: $(PROGS) +all: $(LIB) $(PROGS) -MQJS_OBJS=mqjs.o readline_tty.o readline.o mquickjs.o dtoa.o libm.o cutils.o +LIB_OBJS=mquickjs.o dtoa.o libm.o cutils.o mqjs_runtime.o readline.o readline_tty.o +MQJS_OBJS=mqjs.repl.o LIBS=-lm -mqjs$(EXE): $(MQJS_OBJS) +$(LIB): $(LIB_OBJS) + $(AR) rcs $@ $^ + +mqjs$(EXE): $(MQJS_OBJS) $(LIB) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +mqjsc$(EXE): mqjsc.o $(LIB) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) mquickjs.o: mquickjs_atom.h @@ -97,12 +105,16 @@ mquickjs_atom.h: mqjs_stdlib mqjs_stdlib.h: mqjs_stdlib ./mqjs_stdlib $(MQJS_BUILD_FLAGS) > $@ -mqjs.o: mqjs_stdlib.h +mqjs.repl.o: mqjs.c mqjs_stdlib.h + $(CC) $(CFLAGS) -DCONFIG_MQJS_REPL -c -o $@ mqjs.c + +mqjs_runtime.o: mqjs.c + $(CC) $(CFLAGS) -c -o $@ mqjs.c # C API example example.o: example_stdlib.h -example$(EXE): example.o mquickjs.o dtoa.o libm.o cutils.o +example$(EXE): example.o $(LIB) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) example_stdlib: example_stdlib.host.o mquickjs_build.host.o @@ -117,7 +129,7 @@ example_stdlib.h: example_stdlib %.host.o: %.c $(HOST_CC) $(HOST_CFLAGS) -c -o $@ $< -test: mqjs example +test: mqjs example mqjsc ./mqjs tests/test_closure.js ./mqjs tests/test_language.js ./mqjs tests/test_loop.js @@ -127,6 +139,7 @@ test: mqjs example # @sha256sum -c test_builtin.sha256 ./mqjs -b test_builtin.bin ./example tests/test_rect.js + ./tests/test_mqjsc.sh microbench: mqjs ./mqjs tests/microbench.js diff --git a/README.md b/README.md index 3e53b59..90c5427 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,27 @@ system. Use the option `--no-column` to remove the column number debug info (only line numbers are remaining) if you want to save some storage. +## Compiler + +The compiler is `mqjsc`. It can produce a standalone C program binary +executable that embeds the MicroQuickJS engine along with the +compiled bytecode. Usage: + +``` +usage: mqjsc [options] [file] +-h --help list options +-o FILE set the output filename (default = a.out) +-c only output C source file +-m32 force 32 bit bytecode output +``` + +Example: + +```sh +./mqjsc -o my_prog examples/mqjsc_example.js +./my_prog arg1 arg2 +``` + ## Stricter mode MQuickJS only supports a subset of JavaScript (mostly ES5). It is diff --git a/examples/mqjsc_example.js b/examples/mqjsc_example.js new file mode 100644 index 0000000..fcf596b --- /dev/null +++ b/examples/mqjsc_example.js @@ -0,0 +1,22 @@ +/* Example for mqjsc */ + +print("Welcome to MicroQuickJS Standalone!"); + +function greet(name) { + print("Hello, " + name + "!"); +} + +if (scriptArgs && scriptArgs.length > 0) { + for (var arg of scriptArgs) { + greet(arg); + } +} else { + greet("World"); +} + +print("Memory limit: 16 MB (default in generated C wrapper)"); +print("Current time: " + Date.now()); + +setTimeout(function() { + print("This message is printed after 500ms"); +}, 500); diff --git a/mqjs.c b/mqjs.c index 46ad953..3761604 100644 --- a/mqjs.c +++ b/mqjs.c @@ -1,5 +1,5 @@ /* - * Micro QuickJS REPL + * Micro QuickJS runtime functions * * Copyright (c) 2017-2025 Fabrice Bellard * Copyright (c) 2017-2025 Charlie Gordon @@ -38,15 +38,44 @@ #include "cutils.h" #include "readline_tty.h" #include "mquickjs.h" +#include "mqjs.h" -static uint8_t *load_file(const char *filename, int *plen); -static void dump_error(JSContext *ctx); +uint8_t *load_file(const char *filename, int *plen) +{ + FILE *f; + uint8_t *buf; + int buf_len; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + fseek(f, 0, SEEK_END); + buf_len = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(buf_len + 1); + if (!buf) { + fclose(f); + return NULL; + } + if (fread(buf, 1, buf_len, f) != buf_len) { + free(buf); + fclose(f); + return NULL; + } + buf[buf_len] = '\0'; + fclose(f); + if (plen) + *plen = buf_len; + return buf; +} -static JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { int i; JSValue v; - + for(i = 0; i < argc; i++) { if (i != 0) putchar(' '); @@ -65,21 +94,21 @@ static JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *ar return JS_UNDEFINED; } -static JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { JS_GC(ctx); return JS_UNDEFINED; } #if defined(__linux__) || defined(__APPLE__) -static int64_t get_time_ms(void) +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 -static int64_t get_time_ms(void) +int64_t get_time_ms(void) { struct timeval tv; gettimeofday(&tv, NULL); @@ -87,27 +116,27 @@ static int64_t get_time_ms(void) } #endif -static JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { struct timeval tv; gettimeofday(&tv, NULL); return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000)); } -static JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_NewInt64(ctx, get_time_ms()); } /* load a script */ -static JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { const char *filename; JSCStringBuf buf_str; uint8_t *buf; int buf_len; JSValue ret; - + filename = JS_ToCString(ctx, argv[0], &buf_str); if (!filename) return JS_EXCEPTION; @@ -118,23 +147,14 @@ static JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *arg return ret; } -/* timers */ -typedef struct { - BOOL allocated; - JSGCRef func; - int64_t timeout; /* in ms */ -} JSTimer; - -#define MAX_TIMERS 16 - static JSTimer js_timer_list[MAX_TIMERS]; -static JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { JSTimer *th; int delay, i; JSValue *pfunc; - + if (!JS_IsFunction(ctx, argv[0])) return JS_ThrowTypeError(ctx, "not a function"); if (JS_ToInt32(ctx, &delay, argv[1])) @@ -152,7 +172,7 @@ static JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValu return JS_ThrowInternalError(ctx, "too many timers"); } -static JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { int timer_id; JSTimer *th; @@ -169,7 +189,7 @@ static JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSVa return JS_UNDEFINED; } -static void run_timers(JSContext *ctx) +void run_timers(JSContext *ctx) { int64_t min_delay, delay, cur_time; BOOL has_timer; @@ -193,10 +213,10 @@ static void run_timers(JSContext *ctx) goto fail; JS_PushArg(ctx, th->func.val); /* func name */ JS_PushArg(ctx, JS_NULL); /* this */ - + JS_DeleteGCRef(ctx, &th->func); th->allocated = FALSE; - + ret = JS_Call(ctx, 0); if (JS_IsException(ret)) { fail: @@ -220,52 +240,14 @@ static void run_timers(JSContext *ctx) } } -#include "mqjs_stdlib.h" - -#define STYLE_DEFAULT COLOR_BRIGHT_GREEN -#define STYLE_COMMENT COLOR_WHITE -#define STYLE_STRING COLOR_BRIGHT_CYAN -#define STYLE_REGEX COLOR_CYAN -#define STYLE_NUMBER COLOR_GREEN -#define STYLE_KEYWORD COLOR_BRIGHT_WHITE -#define STYLE_FUNCTION COLOR_BRIGHT_YELLOW -#define STYLE_TYPE COLOR_BRIGHT_MAGENTA -#define STYLE_IDENTIFIER COLOR_BRIGHT_GREEN -#define STYLE_ERROR COLOR_RED -#define STYLE_RESULT COLOR_BRIGHT_WHITE -#define STYLE_ERROR_MSG COLOR_BRIGHT_RED - -static uint8_t *load_file(const char *filename, int *plen) -{ - FILE *f; - uint8_t *buf; - int buf_len; - - f = fopen(filename, "rb"); - if (!f) { - perror(filename); - exit(1); - } - fseek(f, 0, SEEK_END); - buf_len = ftell(f); - fseek(f, 0, SEEK_SET); - buf = malloc(buf_len + 1); - fread(buf, 1, buf_len, f); - buf[buf_len] = '\0'; - fclose(f); - if (plen) - *plen = buf_len; - return buf; -} - static int js_log_err_flag; -static void js_log_func(void *opaque, const void *buf, size_t buf_len) +void js_log_func(void *opaque, const void *buf, size_t buf_len) { fwrite(buf, 1, buf_len, js_log_err_flag ? stderr : stdout); } -static void dump_error(JSContext *ctx) +void dump_error(JSContext *ctx) { JSValue obj; obj = JS_GetException(ctx); @@ -276,6 +258,10 @@ static void dump_error(JSContext *ctx) fprintf(stderr, "%s\n", term_colors[COLOR_NONE]); } +#ifdef CONFIG_MQJS_REPL + +#include "mqjs_stdlib.h" + static int eval_buf(JSContext *ctx, const char *eval_str, const char *filename, BOOL is_repl, int parse_flags) { JSValue val; @@ -654,7 +640,7 @@ int main(int argc, const char **argv) case 'm': count *= 1024; /* fall thru */ - case 'k': +case 'k': count *= 1024; /* fall thru */ default: @@ -772,3 +758,4 @@ int main(int argc, const char **argv) free(mem_buf); return 1; } +#endif diff --git a/mqjs.h b/mqjs.h new file mode 100644 index 0000000..17b2269 --- /dev/null +++ b/mqjs.h @@ -0,0 +1,65 @@ +/* + * Micro QuickJS shared runtime header + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 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. + */ +#ifndef MQJS_H +#define MQJS_H + +#include "cutils.h" +#include "mquickjs.h" + +#define MAX_TIMERS 16 + +typedef struct { + BOOL allocated; + JSGCRef func; + int64_t timeout; /* in ms */ +} JSTimer; + +uint8_t *load_file(const char *filename, int *plen); +JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +int64_t get_time_ms(void); +JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +void run_timers(JSContext *ctx); +void js_log_func(void *opaque, const void *buf, size_t buf_len); +void dump_error(JSContext *ctx); + +#define STYLE_DEFAULT COLOR_BRIGHT_GREEN +#define STYLE_COMMENT COLOR_WHITE +#define STYLE_STRING COLOR_BRIGHT_CYAN +#define STYLE_REGEX COLOR_CYAN +#define STYLE_NUMBER COLOR_GREEN +#define STYLE_KEYWORD COLOR_BRIGHT_WHITE +#define STYLE_FUNCTION COLOR_BRIGHT_YELLOW +#define STYLE_TYPE COLOR_BRIGHT_MAGENTA +#define STYLE_IDENTIFIER COLOR_BRIGHT_GREEN +#define STYLE_ERROR COLOR_RED +#define STYLE_RESULT COLOR_BRIGHT_WHITE +#define STYLE_ERROR_MSG COLOR_BRIGHT_RED + +#endif /* MQJS_H */ diff --git a/mqjsc.c b/mqjsc.c new file mode 100644 index 0000000..3754f03 --- /dev/null +++ b/mqjsc.c @@ -0,0 +1,241 @@ +/* + * Micro QuickJS standalone compiler + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 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 +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "mquickjs.h" +#include "mqjs.h" +#include "mqjs_stdlib.h" + +static void help(void) +{ + printf("mqjsc version 1.0.0\n" + "usage: mqjsc [options] [file]\n" + "-h --help list options\n" + "-o FILE set the output filename (default = a.out)\n" + "-c only output C source file\n" + "-m32 force 32 bit bytecode output\n"); + exit(1); +} + +static const char main_c_template1[] = + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \"mquickjs.h\"\n" + "#include \"mqjs.h\"\n" + "#include \"mqjs_stdlib.h\"\n" + "\n" + "static uint8_t bc_data[] = {\n"; + +static const char main_c_template2[] = + "};\n" + "\n" + "int main(int argc, const char **argv)\n" + "{\n" + " size_t mem_size = 16 << 20;\n" + " uint8_t *mem_buf = malloc(mem_size);\n" + " JSContext *ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib);\n" + " JS_SetLogFunc(ctx, js_log_func);\n" + " JSValue val;\n" + " JSGCRef val_ref;\n" + " if (JS_RelocateBytecode(ctx, (uint8_t *)bc_data, sizeof(bc_data))) {\n" + " fprintf(stderr, \"Could not relocate bytecode\\n\");\n" + " return 1;\n" + " }\n" + " val = JS_LoadBytecode(ctx, bc_data);\n" + " if (JS_IsException(val)) {\n" + " dump_error(ctx);\n" + " return 1;\n" + " }\n" + " JS_PUSH_VALUE(ctx, val);\n" + " if (argc > 1) {\n" + " JSValue obj, arr;\n" + " JSGCRef arr_ref;\n" + " int i;\n" + " arr = JS_NewArray(ctx, argc - 1);\n" + " JS_AddGCRef(ctx, &arr_ref);\n" + " arr_ref.val = arr;\n" + " for(i = 1; i < argc; i++) {\n" + " JS_SetPropertyUint32(ctx, arr_ref.val, i - 1,\n" + " JS_NewString(ctx, argv[i]));\n" + " }\n" + " obj = JS_GetGlobalObject(ctx);\n" + " JS_SetPropertyStr(ctx, obj, \"scriptArgs\", arr_ref.val);\n" + " JS_DeleteGCRef(ctx, &arr_ref);\n" + " }\n" + " val = JS_Run(ctx, val_ref.val);\n" + " if (JS_IsException(val)) {\n" + " dump_error(ctx);\n" + " return 1;\n" + " }\n" + " run_timers(ctx);\n" + " JS_POP_VALUE(ctx, val);\n" + " JS_FreeContext(ctx);\n" + " free(mem_buf);\n" + " return 0;\n" + "}\n"; + +static void output_c_wrapper(FILE *f, const uint8_t *bc_buf, uint32_t bc_len) +{ + uint32_t i; + + fputs(main_c_template1, f); + for(i = 0; i < bc_len; i++) { + fprintf(f, " 0x%02x,", bc_buf[i]); + if ((i % 16) == 15) + fprintf(f, "\n"); + } + fputs(main_c_template2, f); +} + +int main(int argc, char **argv) +{ + const char *out_filename = "a.out"; + const char *filename = NULL; + int optind; + BOOL force_32bit = FALSE; + BOOL only_c = FALSE; + size_t mem_size = 16 << 20; + uint8_t *mem_buf; + JSContext *ctx; + char *eval_str; + JSValue val; + union { + JSBytecodeHeader hdr; +#if JSW == 8 + JSBytecodeHeader32 hdr32; +#endif + } hdr_buf; + int hdr_len; + const uint8_t *data_buf; + uint32_t data_len; + uint8_t *full_bc; + FILE *f; + char c_filename[1024]; + + optind = 1; + while (optind < argc && *argv[optind] == '-') { + const char *arg = argv[optind] + 1; + if (!strcmp(arg, "h") || !strcmp(arg, "-help") || !strcmp(arg, "?")) { + help(); + } else if (!strcmp(arg, "o")) { + optind++; + if (optind >= argc) help(); + out_filename = argv[optind++]; + } else if (!strcmp(arg, "m32")) { + force_32bit = TRUE; + optind++; + } else if (!strcmp(arg, "c")) { + only_c = TRUE; + optind++; + } else { + fprintf(stderr, "Unknown option: -%s\n", arg); + help(); + } + } + + if (optind >= argc) help(); + filename = argv[optind]; + + mem_buf = malloc(mem_size); + ctx = JS_NewContext2(mem_buf, mem_size, &js_stdlib, TRUE); + JS_SetLogFunc(ctx, js_log_func); + + eval_str = (char *)load_file(filename, NULL); + if (!eval_str) { + fprintf(stderr, "Could not load %s\n", filename); + exit(1); + } + + val = JS_Parse(ctx, eval_str, strlen(eval_str), filename, 0); + free(eval_str); + if (JS_IsException(val)) { + dump_error(ctx); + exit(1); + } + +#if JSW == 8 + if (force_32bit) { + if (JS_PrepareBytecode64to32(ctx, &hdr_buf.hdr32, &data_buf, &data_len, val)) { + fprintf(stderr, "Could not convert the bytecode from 64 to 32 bits\n"); + exit(1); + } + hdr_len = sizeof(JSBytecodeHeader32); + } else +#endif + { + JS_PrepareBytecode(ctx, &hdr_buf.hdr, &data_buf, &data_len, val); + JS_RelocateBytecode2(ctx, &hdr_buf.hdr, (uint8_t *)data_buf, data_len, 0, FALSE); + hdr_len = sizeof(JSBytecodeHeader); + } + + full_bc = malloc(hdr_len + data_len); + memcpy(full_bc, &hdr_buf, hdr_len); + memcpy(full_bc + hdr_len, data_buf, data_len); + + if (only_c) { + f = fopen(out_filename, "w"); + if (!f) { + perror(out_filename); + exit(1); + } + output_c_wrapper(f, full_bc, hdr_len + data_len); + fclose(f); + } else { + snprintf(c_filename, sizeof(c_filename), "%s.c", out_filename); + f = fopen(c_filename, "w"); + if (!f) { + perror(c_filename); + exit(1); + } + output_c_wrapper(f, full_bc, hdr_len + data_len); + fclose(f); + + char cmd[2048]; + char *path = getcwd(NULL, 0); + snprintf(cmd, sizeof(cmd), "gcc -O2 -I%s -o %s %s %s/libmquickjs.a -lm", path, out_filename, c_filename, path); + free(path); + // printf("%s\n", cmd); + if (system(cmd) != 0) { + fprintf(stderr, "Compilation failed\n"); + exit(1); + } + unlink(c_filename); + } + + free(full_bc); + JS_FreeContext(ctx); + free(mem_buf); + return 0; +} diff --git a/tests/test_mqjsc.sh b/tests/test_mqjsc.sh new file mode 100755 index 0000000..4cb7ccb --- /dev/null +++ b/tests/test_mqjsc.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Test mqjsc compiler + +set -e + +MQJSC="./mqjsc" + +# Test 1: Simple print +echo "print('hello');" > tests/test1.js +$MQJSC -o tests/test1 tests/test1.js +res=`./tests/test1` +if [ "$res" != "hello" ]; then + echo "Test 1 failed: $res" + exit 1 +fi +echo "Test 1 passed" + +# Test 2: scriptArgs +echo "print(scriptArgs.join(' '));" > tests/test2.js +$MQJSC -o tests/test2 tests/test2.js +res=`./tests/test2 arg1 arg2` +if [ "$res" != "arg1 arg2" ]; then + echo "Test 2 failed: $res" + exit 1 +fi +echo "Test 2 passed" + +# Test 3: timers +echo "setTimeout(function() { print('timeout'); }, 100);" > tests/test3.js +$MQJSC -o tests/test3 tests/test3.js +res=`./tests/test3` +if [ "$res" != "timeout" ]; then + echo "Test 3 failed: $res" + exit 1 +fi +echo "Test 3 passed" + +# Cleanup +rm -f tests/test1.js tests/test1 tests/test2.js tests/test2 tests/test3.js tests/test3