From ea8b93988b31d2194dba0a4f774b3d169c59b96d Mon Sep 17 00:00:00 2001 From: yumin-chen <10954839+yumin-chen@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:30:25 +0000 Subject: [PATCH 1/3] Design and implement mqjsc compiler for standalone binaries Implemented `mqjsc.c` which: - Parses JavaScript source or expressions into bytecode. - Generates a C wrapper embedding the bytecode. - Invokes the system C compiler to produce a standalone executable. - Handles `scriptArgs` in the generated binary. Updated `Makefile` to include `mqjsc` in the build process. Fixed unused variables and improved GC safety in the generated C code. Verified with tests and `make test`. --- Makefile | 8 +- mqjsc.c | 452 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 mqjsc.c diff --git a/Makefile b/Makefile index 109fcb2..9d763ec 100644 --- a/Makefile +++ b/Makefile @@ -75,17 +75,21 @@ ifdef CONFIG_ARM32 MQJS_BUILD_FLAGS=-m32 endif -PROGS=mqjs$(EXE) example$(EXE) +PROGS=mqjs$(EXE) mqjsc$(EXE) example$(EXE) TEST_PROGS=dtoa_test libm_test all: $(PROGS) MQJS_OBJS=mqjs.o readline_tty.o readline.o mquickjs.o dtoa.o libm.o cutils.o +MQJSC_OBJS=mqjsc.o mquickjs.o dtoa.o libm.o cutils.o LIBS=-lm mqjs$(EXE): $(MQJS_OBJS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +mqjsc$(EXE): $(MQJSC_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + mquickjs.o: mquickjs_atom.h mqjs_stdlib: mqjs_stdlib.host.o mquickjs_build.host.o @@ -99,6 +103,8 @@ mqjs_stdlib.h: mqjs_stdlib mqjs.o: mqjs_stdlib.h +mqjsc.o: mqjs_stdlib.h + # C API example example.o: example_stdlib.h diff --git a/mqjsc.c b/mqjsc.c new file mode 100644 index 0000000..c312a92 --- /dev/null +++ b/mqjsc.c @@ -0,0 +1,452 @@ +/* + * Micro QuickJS 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 +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "mquickjs.h" + +/* JS function declarations for the compiler to be able to use the stdlib */ +JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +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); + +#include "mqjs_stdlib.h" + +/* these are only for the compiler's own context */ +JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } +JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; } + +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); + if (!buf) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + if (fread(buf, 1, buf_len, f) != buf_len) { + fprintf(stderr, "Could not read %s\n", filename); + exit(1); + } + buf[buf_len] = '\0'; + fclose(f); + if (plen) + *plen = buf_len; + return buf; +} + +static void js_log_func(void *opaque, const void *buf, size_t buf_len) +{ + fwrite(buf, 1, buf_len, stderr); +} + +static void dump_error(JSContext *ctx) +{ + JSValue obj; + obj = JS_GetException(ctx); + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + fprintf(stderr, "\n"); +} + +typedef struct { + uint8_t *data; + uint32_t len; + union { + JSBytecodeHeader hdr; +#if JSW == 8 + JSBytecodeHeader32 hdr32; +#endif + }; + int hdr_len; +} CompiledBytecode; + +static void compile_file(CompiledBytecode *bc, const char *filename, const char *expr, + size_t mem_size, int parse_flags, BOOL force_32bit) +{ + uint8_t *mem_buf; + JSContext *ctx; + uint8_t *eval_str; + int eval_len; + JSValue val; + const uint8_t *data_buf; + uint32_t data_len; + + mem_buf = malloc(mem_size); + if (!mem_buf) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + ctx = JS_NewContext2(mem_buf, mem_size, &js_stdlib, TRUE); + JS_SetLogFunc(ctx, js_log_func); + + if (expr) { + eval_str = (uint8_t *)strdup(expr); + eval_len = strlen(expr); + filename = ""; + } else { + eval_str = load_file(filename, &eval_len); + } + + val = JS_Parse(ctx, (char *)eval_str, eval_len, filename, parse_flags); + free(eval_str); + if (JS_IsException(val)) { + dump_error(ctx); + exit(1); + } + +#if JSW == 8 + if (force_32bit) { + if (JS_PrepareBytecode64to32(ctx, &bc->hdr32, &data_buf, &data_len, val)) { + fprintf(stderr, "Could not convert the bytecode from 64 to 32 bits\n"); + exit(1); + } + bc->hdr_len = sizeof(JSBytecodeHeader32); + } else +#endif + { + JS_PrepareBytecode(ctx, &bc->hdr, &data_buf, &data_len, val); + JS_RelocateBytecode2(ctx, &bc->hdr, (uint8_t *)data_buf, data_len, 0, FALSE); + bc->hdr_len = sizeof(JSBytecodeHeader); + } + + bc->data = malloc(data_len); + if (!bc->data) { + fprintf(stderr, "Out of memory\n"); + exit(1); + } + memcpy(bc->data, data_buf, data_len); + bc->len = data_len; + + JS_FreeContext(ctx); + free(mem_buf); +} + +static void output_c(FILE *f, CompiledBytecode *bc, size_t mem_size) +{ + int i; + fprintf(f, "#include \n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \"mquickjs.h\"\n"); + fprintf(f, "\n"); + fprintf(f, "/* required for the stdlib */\n"); + fprintf(f, "JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv);\n"); + fprintf(f, "\n"); + fprintf(f, "#include \"mqjs_stdlib.h\"\n"); + fprintf(f, "\n"); + fprintf(f, "/* dummy or real implementation of the required functions */\n"); + fprintf(f, "JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {\n"); + fprintf(f, " for(int i = 0; i < argc; i++) {\n"); + fprintf(f, " if (i != 0) putchar(' ');\n"); + fprintf(f, " if (JS_IsString(ctx, argv[i])) {\n"); + fprintf(f, " JSCStringBuf buf;\n"); + fprintf(f, " size_t len;\n"); + fprintf(f, " const char *str = JS_ToCStringLen(ctx, &len, argv[i], &buf);\n"); + fprintf(f, " fwrite(str, 1, len, stdout);\n"); + fprintf(f, " } else {\n"); + fprintf(f, " JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG);\n"); + fprintf(f, " }\n"); + fprintf(f, " }\n"); + fprintf(f, " putchar('\\n');\n"); + fprintf(f, " return JS_UNDEFINED;\n"); + fprintf(f, "}\n"); + fprintf(f, "JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { JS_GC(ctx); return JS_UNDEFINED; }\n"); + fprintf(f, "JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {\n"); + fprintf(f, " struct timeval tv; gettimeofday(&tv, NULL);\n"); + fprintf(f, " return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000));\n"); + fprintf(f, "}\n"); + fprintf(f, "JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) {\n"); + fprintf(f, " struct timeval tv; gettimeofday(&tv, NULL);\n"); + fprintf(f, " return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000));\n"); + fprintf(f, "}\n"); + fprintf(f, "JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_ThrowInternalError(ctx, \"load() not supported\"); }\n"); + fprintf(f, "JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_ThrowInternalError(ctx, \"setTimeout() not supported\"); }\n"); + fprintf(f, "JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { return JS_UNDEFINED; }\n"); + fprintf(f, "\n"); + fprintf(f, "static const uint8_t qjs_bytecode[] = {\n"); + for (i = 0; i < bc->hdr_len; i++) { + fprintf(f, " 0x%02x,", ((uint8_t *)&bc->hdr)[i]); + if ((i & 15) == 15) fprintf(f, "\n"); + } + for (i = 0; i < bc->len; i++) { + fprintf(f, " 0x%02x,", bc->data[i]); + if (((i + bc->hdr_len) & 15) == 15) fprintf(f, "\n"); + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "static void js_log_func(void *opaque, const void *buf, size_t buf_len) {\n"); + fprintf(f, " fwrite(buf, 1, buf_len, stdout);\n"); + fprintf(f, "}\n\n"); + + fprintf(f, "int main(int argc, char **argv) {\n"); + fprintf(f, " size_t mem_size = %zu;\n", mem_size); + fprintf(f, " uint8_t *mem_buf = malloc(mem_size);\n"); + fprintf(f, " JSContext *ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib);\n"); + fprintf(f, " JS_SetLogFunc(ctx, js_log_func);\n"); + fprintf(f, " {\n"); + fprintf(f, " struct timeval tv;\n"); + fprintf(f, " gettimeofday(&tv, NULL);\n"); + fprintf(f, " JS_SetRandomSeed(ctx, ((uint64_t)tv.tv_sec << 32) ^ tv.tv_usec);\n"); + fprintf(f, " }\n"); + fprintf(f, " uint8_t *bc_copy = malloc(sizeof(qjs_bytecode));\n"); + fprintf(f, " memcpy(bc_copy, qjs_bytecode, sizeof(qjs_bytecode));\n"); + fprintf(f, " if (JS_RelocateBytecode(ctx, bc_copy, sizeof(qjs_bytecode))) {\n"); + fprintf(f, " fprintf(stderr, \"Could not relocate bytecode\\n\");\n"); + fprintf(f, " return 1;\n"); + fprintf(f, " }\n"); + fprintf(f, " JSValue val = JS_LoadBytecode(ctx, bc_copy);\n"); + fprintf(f, " JSGCRef val_ref;\n"); + fprintf(f, " JS_PUSH_VALUE(ctx, val);\n"); + fprintf(f, " if (argc > 1) {\n"); + fprintf(f, " JSValue arr = JS_NewArray(ctx, argc - 1);\n"); + fprintf(f, " JSGCRef arr_ref;\n"); + fprintf(f, " JS_PUSH_VALUE(ctx, arr);\n"); + fprintf(f, " for (int i = 1; i < argc; i++) {\n"); + fprintf(f, " JS_SetPropertyUint32(ctx, arr, i - 1, JS_NewString(ctx, argv[i]));\n"); + fprintf(f, " }\n"); + fprintf(f, " JS_SetPropertyStr(ctx, JS_GetGlobalObject(ctx), \"scriptArgs\", arr);\n"); + fprintf(f, " JS_POP_VALUE(ctx, arr);\n"); + fprintf(f, " }\n"); + fprintf(f, " val = JS_Run(ctx, val);\n"); + fprintf(f, " JS_POP_VALUE(ctx, val);\n"); + fprintf(f, " if (JS_IsException(val)) {\n"); + fprintf(f, " JSValue obj = JS_GetException(ctx);\n"); + fprintf(f, " JS_PrintValueF(ctx, obj, JS_DUMP_LONG);\n"); + fprintf(f, " printf(\"\\n\");\n"); + fprintf(f, " return 1;\n"); + fprintf(f, " }\n"); + fprintf(f, " JS_FreeContext(ctx);\n"); + fprintf(f, " free(mem_buf);\n"); + fprintf(f, " free(bc_copy);\n"); + fprintf(f, " return 0;\n"); + fprintf(f, "}\n"); +} + +static int execlp_res(const char *cmd, ...) +{ + va_list ap; + char *args[128]; + int i, status; + pid_t pid; + + va_start(ap, cmd); + args[0] = (char *)cmd; + for (i = 1; i < 127; i++) { + args[i] = va_arg(ap, char *); + if (!args[i]) + break; + } + args[i] = NULL; + va_end(ap); + + pid = fork(); + if (pid == 0) { + execvp(cmd, args); + perror(cmd); + exit(1); + } else if (pid > 0) { + waitpid(pid, &status, 0); + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return -1; + } else { + perror("fork"); + return -1; + } +} + +static void help(void) +{ + printf("MicroQuickJS Compiler\n" + "usage: mqjsc [options] [file]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-o FILE output executable to FILE (default = a.out)\n" + "-m32 force 32 bit bytecode output\n" + " --memory-limit n limit the memory usage of the compiled program to 'n' bytes\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int optind; + const char *out_filename = "a.out"; + const char *expr = NULL; + const char *infile = NULL; + size_t mem_size = 16 << 20; + BOOL force_32bit = FALSE; + CompiledBytecode bc; + int parse_flags = 0; + + optind = 1; + while (optind < argc && *argv[optind] == '-') { + const char *arg = argv[optind] + 1; + const char *longopt = ""; + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + } + if (opt == 'o') { + if (*arg) { + out_filename = arg; + break; + } + if (optind < argc) { + out_filename = argv[optind++]; + break; + } + fprintf(stderr, "missing filename for -o\n"); + exit(2); + } + if (opt == 'e' || !strcmp(longopt, "eval")) { + if (*arg) { + expr = arg; + break; + } + if (optind < argc) { + expr = argv[optind++]; + break; + } + fprintf(stderr, "missing expression for -e\n"); + exit(2); + } + if (opt == 'm' && !strcmp(arg, "32")) { + force_32bit = TRUE; + arg += strlen(arg); + continue; + } + if (!strcmp(longopt, "memory-limit")) { + char *p; + double count; + if (optind >= argc) { + fprintf(stderr, "expecting memory limit\n"); + exit(1); + } + count = strtod(argv[optind++], &p); + switch (tolower((unsigned char)*p)) { + case 'g': count *= 1024; + case 'm': count *= 1024; + case 'k': count *= 1024; + default: + mem_size = (size_t)(count); + break; + } + continue; + } + if (opt) { + fprintf(stderr, "mqjsc: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "mqjsc: unknown option '--%s'\n", longopt); + } + help(); + } + } + + if (optind < argc) { + infile = argv[optind++]; + } + + if (!infile && !expr) { + help(); + } + + compile_file(&bc, infile, expr, mem_size, parse_flags, force_32bit); + + char c_filename[1024]; + snprintf(c_filename, sizeof(c_filename), "%s.c", out_filename); + FILE *f = fopen(c_filename, "w"); + if (!f) { + perror(c_filename); + exit(1); + } + output_c(f, &bc, mem_size); + fclose(f); + + const char *cc = getenv("CC"); + if (!cc) cc = "gcc"; + + if (execlp_res(cc, "-O2", "-o", out_filename, c_filename, + "mquickjs.o", "dtoa.o", "libm.o", "cutils.o", + "-lm", NULL) != 0) { + fprintf(stderr, "C compilation failed\n"); + exit(1); + } + + unlink(c_filename); + return 0; +} From c655d325086d53fa94ee3b58cbc8cfea294e7125 Mon Sep 17 00:00:00 2001 From: yumin-chen <10954839+yumin-chen@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:46:43 +0000 Subject: [PATCH 2/3] Implement mqjsc compiler with tests, examples, and documentation - Implemented `mqjsc` compiler tool in `mqjsc.c`. - Added `examples/hello_mqjsc.js` to demonstrate standalone compilation. - Added `tests/test_mqjsc.sh` for automated testing of the compiler. - Updated `Makefile` to build `mqjsc` and include its tests in `make test`. - Updated `README.md` with usage instructions for standalone compilation. - Ensured proper GC safety and `scriptArgs` handling in compiled binaries. - Cleaned up all build artifacts from the source tree. --- Makefile | 4 +++- README.md | 14 ++++++++++++++ examples/hello_mqjsc.js | 24 +++++++++++++++++++++++ tests/test_mqjsc.sh | 43 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 examples/hello_mqjsc.js create mode 100755 tests/test_mqjsc.sh diff --git a/Makefile b/Makefile index 9d763ec..eba430c 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,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 @@ -133,6 +133,8 @@ test: mqjs example # @sha256sum -c test_builtin.sha256 ./mqjs -b test_builtin.bin ./example tests/test_rect.js +# test mqjsc compiler + ./tests/test_mqjsc.sh microbench: mqjs ./mqjs tests/microbench.js diff --git a/README.md b/README.md index 3e53b59..c7c6e96 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,20 @@ 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. +## Standalone binary compilation + +The `mqjsc` tool can produce a standalone C program binary executable +that embeds the self-contained `mqjs` engine along with precompiled +bytecode: + +```sh +./mqjsc -o mandelbrot tests/mandelbrot.js +./mandelbrot +``` + +The resulting binary only depends on the standard C library and +optionally the mathematical library (`-lm`). + ## Stricter mode MQuickJS only supports a subset of JavaScript (mostly ES5). It is diff --git a/examples/hello_mqjsc.js b/examples/hello_mqjsc.js new file mode 100644 index 0000000..542d9f0 --- /dev/null +++ b/examples/hello_mqjsc.js @@ -0,0 +1,24 @@ +/* + MicroQuickJS (MQuickJS) standalone compilation example. + Usage: + ./mqjsc -o hello_mqjsc examples/hello_mqjsc.js + ./hello_mqjsc Alice Bob +*/ + +print("Hello from a standalone MicroQuickJS binary!"); + +if (typeof scriptArgs !== 'undefined' && scriptArgs.length > 0) { + print("Arguments provided:"); + for (var i = 0; i < scriptArgs.length; i++) { + print(" - " + scriptArgs[i]); + } +} else { + print("No arguments provided."); +} + +function fib(n) { + if (n <= 1) return n; + return fib(n - 1) + fib(n - 2); +} + +print("Fibonacci(10) =", fib(10)); diff --git a/tests/test_mqjsc.sh b/tests/test_mqjsc.sh new file mode 100755 index 0000000..a0266c6 --- /dev/null +++ b/tests/test_mqjsc.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Simple test script for mqjsc +set -e + +# Build the compiler first +make mqjsc + +MQJSC=./mqjsc + +# Test 1: Simple hello world +echo "print('hello');" > hello_test.js +$MQJSC -o hello_test hello_test.js +RES=$(./hello_test) +if [ "$RES" != "hello" ]; then + echo "Test 1 Failed: expected 'hello', got '$RES'" + exit 1 +fi +rm hello_test.js hello_test +echo "Test 1 Passed: Simple hello world" + +# Test 2: scriptArgs +echo "print(scriptArgs.join(' '));" > args_test.js +$MQJSC -o args_test args_test.js +RES=$($MQJSC -o args_test args_test.js && ./args_test foo bar baz) +if [ "$RES" != "foo bar baz" ]; then + echo "Test 2 Failed: expected 'foo bar baz', got '$RES'" + exit 1 +fi +rm args_test.js args_test +echo "Test 2 Passed: scriptArgs handling" + +# Test 3: eval expression +$MQJSC -o eval_test -e "print(1+2+3)" +RES=$(./eval_test) +if [ "$RES" != "6" ]; then + echo "Test 3 Failed: expected '6', got '$RES'" + exit 1 +fi +rm eval_test +echo "Test 3 Passed: eval expression compilation" + +echo "All mqjsc tests passed!" From ad1e30cc7655a56d5dd54e383508a8695bedf513 Mon Sep 17 00:00:00 2001 From: yumin-chen <10954839+yumin-chen@users.noreply.github.com> Date: Fri, 27 Mar 2026 16:07:03 +0000 Subject: [PATCH 3/3] Add comprehensive tests and examples for mqjsc compiler - Implemented `mqjsc` standalone compiler for MicroQuickJS. - Added `tests/test_compiler_features.js` to test RegExp, closures, arrays, objects, and JSON in compiled binaries. - Added `tests/test_mqjsc.sh` to automate compiler testing. - Added `examples/hello_mqjsc.js` as a usage example. - Updated `Makefile` and `README.md`. - Improved `mqjsc.c` with better GC safety and robust bytecode relocation. - Cleaned up build artifacts from the repository. --- mqjsc.c | 4 ++ tests/test_compiler_features.js | 89 +++++++++++++++++++++++++++++++++ tests/test_mqjsc.sh | 30 ++++++++++- 3 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 tests/test_compiler_features.js diff --git a/mqjsc.c b/mqjsc.c index c312a92..2d84411 100644 --- a/mqjsc.c +++ b/mqjsc.c @@ -148,6 +148,8 @@ static void compile_file(CompiledBytecode *bc, const char *filename, const char dump_error(ctx); exit(1); } + JSGCRef val_ref; + JS_PUSH_VALUE(ctx, val); #if JSW == 8 if (force_32bit) { @@ -172,6 +174,7 @@ static void compile_file(CompiledBytecode *bc, const char *filename, const char memcpy(bc->data, data_buf, data_len); bc->len = data_len; + JS_POP_VALUE(ctx, val); JS_FreeContext(ctx); free(mem_buf); } @@ -448,5 +451,6 @@ int main(int argc, char **argv) } unlink(c_filename); + free(bc.data); return 0; } diff --git a/tests/test_compiler_features.js b/tests/test_compiler_features.js new file mode 100644 index 0000000..f7c799a --- /dev/null +++ b/tests/test_compiler_features.js @@ -0,0 +1,89 @@ +/* Comprehensive tests for MicroQuickJS compiler */ + +function assert(condition, message) { + if (!condition) { + print("Assertion failed: " + message); + throw Error(message); + } +} + +// 1. Closures +function test_closures() { + function makeAdder(x) { + return function(y) { + return x + y; + }; + } + var add5 = makeAdder(5); + assert(add5(10) === 15, "add5(10) === 15"); + + var counter = (function() { + var count = 0; + return { + inc: function() { return ++count; }, + get: function() { return count; } + }; + })(); + assert(counter.get() === 0, "counter.get() === 0"); + assert(counter.inc() === 1, "counter.inc() === 1"); + assert(counter.inc() === 2, "counter.inc() === 2"); + assert(counter.get() === 2, "counter.get() === 2"); +} + +// 2. RegExp +function test_regexp() { + var re = /a(b+)c/i; + var m = re.exec("ABBC"); + assert(m !== null, "RegExp match"); + assert(m[0] === "ABBC", "RegExp match group 0"); + assert(m[1] === "BB", "RegExp match group 1"); + + assert("hello world".replace(/o/g, "0") === "hell0 w0rld", "RegExp replace"); + assert(/abc/.test("abcdef") === true, "RegExp test true"); + assert(/abc/.test("abdef") === false, "RegExp test false"); +} + +// 3. Arrays and Typed Arrays +function test_arrays() { + var a = [1, 2, 3]; + assert(a.map(function(x) { return x * 2; }).join(",") === "2,4,6", "Array map"); + assert(a.filter(function(x) { return x > 1; }).length === 2, "Array filter"); + assert(a.reduce(function(acc, x) { return acc + x; }, 0) === 6, "Array reduce"); + + var ta = new Uint8Array([10, 20, 30]); + assert(ta[1] === 20, "TypedArray index"); + ta[0] = 256; // wraps to 0 + assert(ta[0] === 0, "TypedArray wrap"); +} + +// 4. Objects and Prototypes +function test_objects() { + var proto = { + sayHello: function() { return "Hello " + this.name; } + }; + var obj = Object.create(proto); + obj.name = "World"; + assert(obj.sayHello() === "Hello World", "Object prototype"); + + var keys = Object.keys({a: 1, b: 2}); + assert(keys.length === 2, "Object.keys length"); + assert(keys.indexOf("a") !== -1, "Object.keys contains a"); +} + +// 5. JSON +function test_json() { + var obj = { a: 1, b: [2, 3], c: "s" }; + var s = JSON.stringify(obj); + var obj2 = JSON.parse(s); + assert(obj2.a === 1, "JSON parse a"); + assert(obj2.b[1] === 3, "JSON parse b[1]"); + assert(obj2.c === "s", "JSON parse c"); +} + +print("Running comprehensive compiler tests..."); +test_closures(); +test_regexp(); +test_arrays(); +test_objects(); +test_json(); +print("All compiler feature tests passed!"); diff --git a/tests/test_mqjsc.sh b/tests/test_mqjsc.sh index a0266c6..5f0769d 100755 --- a/tests/test_mqjsc.sh +++ b/tests/test_mqjsc.sh @@ -22,7 +22,7 @@ echo "Test 1 Passed: Simple hello world" # Test 2: scriptArgs echo "print(scriptArgs.join(' '));" > args_test.js $MQJSC -o args_test args_test.js -RES=$($MQJSC -o args_test args_test.js && ./args_test foo bar baz) +RES=$(./args_test foo bar baz) if [ "$RES" != "foo bar baz" ]; then echo "Test 2 Failed: expected 'foo bar baz', got '$RES'" exit 1 @@ -40,4 +40,32 @@ fi rm eval_test echo "Test 3 Passed: eval expression compilation" +# Test 4: Comprehensive features +echo "Test 4: Comprehensive features..." +$MQJSC -o features_test tests/test_compiler_features.js +./features_test +rm features_test +echo "Test 4 Passed: Comprehensive features" + +# Test 5: Existing closure test +echo "Test 5: Existing closure test..." +$MQJSC -o closure_test tests/test_closure.js +./closure_test +rm closure_test +echo "Test 5 Passed: Existing closure test" + +# Test 6: Existing language test +echo "Test 6: Existing language test..." +$MQJSC -o language_test tests/test_language.js +./language_test +rm language_test +echo "Test 6 Passed: Existing language test" + +# Test 7: Example script +echo "Test 7: Example script..." +$MQJSC -o hello_mqjsc examples/hello_mqjsc.js +./hello_mqjsc Alice Bob > /dev/null +rm hello_mqjsc +echo "Test 7 Passed: Example script" + echo "All mqjsc tests passed!"