From b53f4b7190be5863a798308bdbf5aa2ac8b4d192 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sat, 14 Feb 2026 18:26:56 -0500 Subject: [PATCH 01/16] CLAUDE --- CLAUDE.md | 171 ++++++++++++++++++------------------------------------ 1 file changed, 58 insertions(+), 113 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6a850c5c..02406d06 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,143 +1,88 @@ -# quickjs-emscripten +# CLAUDE.md -## Package Manager +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -Use `corepack yarn` to run yarn commands, e.g.: +## Project Overview -- `corepack yarn install` -- `corepack yarn build` -- `corepack yarn build:ts` +quickjs-emscripten provides JavaScript/TypeScript bindings for QuickJS (a JavaScript interpreter) compiled to WebAssembly. It enables safe evaluation of untrusted JavaScript in browsers, Node.js, Deno, Bun, and Cloudflare Workers. -## Building Variants - -Variants are WASM/JS builds of QuickJS with different configurations. They are generated by `scripts/prepareVariants.ts`. - -To build a variant: - -```bash -cd packages/variant-quickjs- -make # builds the C code with emscripten -corepack yarn build:ts # builds the TypeScript wrapper -``` - -## Emscripten - -- Most variants use the default emscripten version (defined in `scripts/prepareVariants.ts` as `DEFAULT_EMSCRIPTEN_VERSION`) -- The asmjs variant uses an older emscripten version (`ASMJS_EMSCRIPTEN_VERSION`) to avoid newer browser APIs -- Emscripten runs via Docker when the local version doesn't match; see `scripts/emcc.sh` - -## Testing - -Run all tests: - -```bash -corepack yarn test -``` - -Run tests for a specific variant (e.g., quickjs-ng only): +## Common Commands ```bash -cd packages/quickjs-emscripten -npx vitest run -t "quickjs-ng" +yarn check # Run all checks (build, format, tests, type-checking, linting) +yarn build # Full build: codegen + packages + docs +yarn build:codegen # Generate variant packages from templates +yarn build:packages # Build all variant packages in parallel +yarn test # Run all tests +yarn test:fast # Fast tests only (skip async variants) +yarn test:slow # Tests with memory leak detection +yarn lint # ESLint +yarn lint --fix # Auto-fix lint issues +yarn prettier # Format with Prettier +yarn doc # Generate TypeDoc documentation ``` -Other test filters: - -- `-t "RELEASE_SYNC"` - only release sync variants -- `-t "DEBUG"` - only debug variants -- `-t "QuickJSContext"` - only sync context tests +## Architecture -## Git +### Package Structure -- Never use `git commit --amend` - always create new commits +The monorepo contains these key packages in `packages/`: -## Key Files +1. **@jitl/quickjs-ffi-types** - Low-level FFI types defining the C interface. No WebAssembly linkage. -- `scripts/prepareVariants.ts` - Generates all variant packages from templates -- `scripts/generate.ts` - Generates FFI bindings and symbols -- `templates/Variant.mk` - Makefile template for variants -- `c/interface.c` - C interface to QuickJS exposed to JavaScript +2. **quickjs-emscripten-core** - High-level TypeScript abstractions (QuickJSContext, QuickJSRuntime, QuickJSWASMModule, Lifetime, Scope). Does NOT include WebAssembly - callers must provide a variant. -## QuickJS C API Tips +3. **quickjs-emscripten** - Main user-facing package. Combines core with pre-built WASM variants. Entry point: `getQuickJS()`. -### Value Ownership +4. **Variant packages** (28 total) - Pre-compiled WebAssembly modules with different configurations: + - Naming: `variant-{BASE}-{FORMAT}-{OPTIMIZATION}-{MODE}` + - BASE: `quickjs` or `quickjs-ng` + - FORMAT: `wasmfile` or `singlefile` + - OPTIMIZATION: `release` or `debug` + - MODE: `sync` or `asyncify` -QuickJS has strict ownership semantics. Functions either "consume" (take ownership of) or "borrow" values: +### C/WASM Layer -**Functions that CONSUME values (caller must NOT free afterward):** +- **c/interface.c** - Main C wrapper around QuickJS. Functions prefixed `QTS_` are exported to JavaScript via FFI. +- **vendor/quickjs/** - Official QuickJS C library +- **vendor/quickjs-ng/** - QuickJS-ng fork +- **templates/Variant.mk** - Makefile template for building variants -- `JS_DefinePropertyValue` - consumes `val` -- `JS_DefinePropertyValueStr` - consumes `val` -- `JS_DefinePropertyValueUint32` - consumes `val` -- `JS_SetPropertyValue` - consumes `val` -- `JS_SetPropertyStr` - consumes `val` -- `JS_Throw` - consumes the error value +### Code Generation -**Functions that DUP values internally (caller SHOULD free afterward):** +- `scripts/prepareVariants.ts` - Generates variant packages from templates +- `scripts/generate.ts` - Generates FFI bindings from C function signatures +- `scripts/emcc.sh` - Emscripten wrapper with Docker fallback -- `JS_NewCFunctionData` - calls `JS_DupValue` on data values, so free your reference after -- `JS_SetProperty` - dups the value +## Key Concepts -**Common double-free bug pattern:** +### Memory Management -```c -// WRONG - double free! -JSValue val = JS_NewString(ctx, "hello"); -JS_DefinePropertyValueStr(ctx, obj, "name", val, JS_PROP_CONFIGURABLE); -JS_FreeValue(ctx, val); // BUG: val was already consumed! +Handles to QuickJS values must be manually disposed with `.dispose()`. The library supports: +- Explicit `.dispose()` calls +- `using` statement (Stage 3 proposal) +- `Scope` class for batch disposal +- `Lifetime.consume(fn)` for use-then-dispose pattern -// CORRECT -JSValue val = JS_NewString(ctx, "hello"); -JS_DefinePropertyValueStr(ctx, obj, "name", val, JS_PROP_CONFIGURABLE); -// No JS_FreeValue needed - value is consumed -``` +### Asyncify -### quickjs vs quickjs-ng Differences +Special build variants use Emscripten's ASYNCIFY transform to allow synchronous QuickJS code to call async host functions. Comes with 2x size overhead. Only one async operation can suspend at a time per module. -Some functions have different signatures between bellard/quickjs and quickjs-ng: +### Build Variants -```c -// bellard/quickjs - class ID is global -JS_NewClassID(&class_id); +- RELEASE_SYNC - Default production variant +- RELEASE_ASYNC - Production with asyncify +- DEBUG_SYNC - Development with memory leak detection +- DEBUG_ASYNC - Development with asyncify -// quickjs-ng - class ID is per-runtime -JS_NewClassID(rt, &class_id); -``` - -Use `#ifdef QTS_USE_QUICKJS_NG` for compatibility: - -```c -#ifdef QTS_USE_QUICKJS_NG - JS_NewClassID(rt, &class_id); -#else - JS_NewClassID(&class_id); -#endif -``` - -### Class Registration - -- `JS_NewClassID` allocates a new class ID (only call once globally or per-runtime for ng) -- `JS_NewClass` registers the class definition with a runtime -- `JS_IsRegisteredClass` checks if a class is already registered with a runtime -- Class prototypes default to `JS_NULL` for new classes - set with `JS_SetClassProto` if needed - -### Disposal Order - -When disposing resources, order matters for finalizers: +## Testing -```typescript -// CORRECT: Free runtime first so GC finalizers can call back to JS -const rt = new Lifetime(ffi.QTS_NewRuntime(), undefined, (rt_ptr) => { - ffi.QTS_FreeRuntime(rt_ptr); // 1. Free runtime - runs GC finalizers - callbacks.deleteRuntime(rt_ptr); // 2. Then delete callbacks -}); -``` +Main test suite: `packages/quickjs-emscripten/src/quickjs.test.ts` -### GC and Prevent Corruption Assertions +Tests use Vitest. The DEBUG_SYNC variant enables memory leak detection - use `TestQuickJSWASMModule.assertNoMemoryAllocated()` to verify handles are disposed. -If you see assertions like: -- `Assertion failed: i != 0, at: quickjs.c, JS_FreeAtomStruct` - atom hash corruption (often double-free) -- `Assertion failed: list_empty(&rt->gc_obj_list)` - objects leaked -- `Assertion failed: p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT` - memory corruption +## Build Requirements -These usually indicate memory management bugs: double-frees, use-after-free, or missing frees. +- Node.js 16+ +- Yarn 4.0.2 (workspaces) +- Emscripten 3.1.65 (or Docker fallback via `scripts/emcc.sh`) From 18d4b82bacfc4092386f645e42e7337e53bdb183 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sat, 14 Feb 2026 18:34:37 -0500 Subject: [PATCH 02/16] Add vendor scripts for mquickjs Co-Authored-By: Claude Opus 4.5 --- package.json | 6 +++++- vendor/mquickjs-patches/.gitkeep | 0 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 vendor/mquickjs-patches/.gitkeep diff --git a/package.json b/package.json index d182c9bd..c598ae0b 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,11 @@ "vendor:quickjs:add": "ACTION=add yarn vendor:quickjs:subtree", "vendor:quickjs:patch": "git apply vendor/quickjs-patches/*", "vendor:ng:pull": "./scripts/vendor-quickjs-ng.sh", - "vendor:update": "yarn vendor:quickjs:pull && yarn vendor:ng:pull", + "vendor:mquickjs:subtree": "git subtree $ACTION --prefix=vendor/mquickjs --squash git@github.com:bellard/mquickjs.git main", + "vendor:mquickjs:pull": "ACTION=pull yarn vendor:mquickjs:subtree && yarn vendor:mquickjs:patch", + "vendor:mquickjs:add": "ACTION=add yarn vendor:mquickjs:subtree", + "vendor:mquickjs:patch": "git apply vendor/mquickjs-patches/*", + "vendor:update": "yarn vendor:quickjs:pull && yarn vendor:ng:pull && yarn vendor:mquickjs:pull" "smoketest-node": "yarn tarball && ./scripts/smoketest-node-minimal.ts && ./scripts/smoketest-node.ts", "smoketest-cra": "yarn tarball && ./scripts/smoketest-create-react-app.ts", "smoketest-vite": "yarn tarball && ./scripts/smoketest-vite.ts", diff --git a/vendor/mquickjs-patches/.gitkeep b/vendor/mquickjs-patches/.gitkeep new file mode 100644 index 00000000..e69de29b From 1c8f86e6a42944a8ad46f409aa3384d009d5fbce Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sat, 14 Feb 2026 18:34:55 -0500 Subject: [PATCH 03/16] Squashed 'vendor/mquickjs/' content from commit ee50431 git-subtree-dir: vendor/mquickjs git-subtree-split: ee50431eac9b14b99f722b537ec4cac0c8dd75ab --- Changelog | 1 + Makefile | 152 + cutils.c | 178 + cutils.h | 355 + dtoa.c | 1620 ++++ dtoa.h | 83 + example.c | 287 + example_stdlib.c | 36 + libm.c | 2260 +++++ libm.h | 46 + list.h | 99 + mqjs.c | 774 ++ mqjs_stdlib.c | 402 + mquickjs.c | 18324 +++++++++++++++++++++++++++++++++++++++ mquickjs.h | 382 + mquickjs_build.c | 932 ++ mquickjs_build.h | 97 + mquickjs_opcode.h | 264 + mquickjs_priv.h | 268 + readline.c | 742 ++ readline.h | 98 + readline_tty.c | 246 + readline_tty.h | 29 + softfp_template.h | 970 +++ softfp_template_icvt.h | 172 + tests/mandelbrot.js | 39 + tests/microbench.js | 1137 +++ tests/test_builtin.js | 875 ++ tests/test_closure.js | 106 + tests/test_language.js | 355 + tests/test_loop.js | 395 + tests/test_rect.js | 68 + 32 files changed, 31792 insertions(+) create mode 100644 Changelog create mode 100644 Makefile create mode 100644 cutils.c create mode 100644 cutils.h create mode 100644 dtoa.c create mode 100644 dtoa.h create mode 100644 example.c create mode 100644 example_stdlib.c create mode 100644 libm.c create mode 100644 libm.h create mode 100644 list.h create mode 100644 mqjs.c create mode 100644 mqjs_stdlib.c create mode 100644 mquickjs.c create mode 100644 mquickjs.h create mode 100644 mquickjs_build.c create mode 100644 mquickjs_build.h create mode 100644 mquickjs_opcode.h create mode 100644 mquickjs_priv.h create mode 100644 readline.c create mode 100644 readline.h create mode 100644 readline_tty.c create mode 100644 readline_tty.h create mode 100644 softfp_template.h create mode 100644 softfp_template_icvt.h create mode 100644 tests/mandelbrot.js create mode 100644 tests/microbench.js create mode 100644 tests/test_builtin.js create mode 100644 tests/test_closure.js create mode 100644 tests/test_language.js create mode 100644 tests/test_loop.js create mode 100644 tests/test_rect.js diff --git a/Changelog b/Changelog new file mode 100644 index 00000000..7d056202 --- /dev/null +++ b/Changelog @@ -0,0 +1 @@ +2025-12-22: First public version diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..109fcb27 --- /dev/null +++ b/Makefile @@ -0,0 +1,152 @@ +#CONFIG_PROFILE=y +#CONFIG_X86_32=y +#CONFIG_ARM32=y +#CONFIG_WIN32=y +#CONFIG_SOFTFLOAT=y +#CONFIG_ASAN=y +#CONFIG_GPROF=y +CONFIG_SMALL=y +# consider warnings as errors (for development) +#CONFIG_WERROR=y + +ifdef CONFIG_ARM32 +CROSS_PREFIX=arm-linux-gnu- +endif + +ifdef CONFIG_WIN32 + ifdef CONFIG_X86_32 + CROSS_PREFIX?=i686-w64-mingw32- + else + CROSS_PREFIX?=x86_64-w64-mingw32- + endif + EXE=.exe +else + CROSS_PREFIX?= + EXE= +endif + +HOST_CC=gcc +CC=$(CROSS_PREFIX)gcc +CFLAGS=-Wall -g -MMD -D_GNU_SOURCE -fno-math-errno -fno-trapping-math +HOST_CFLAGS=-Wall -g -MMD -D_GNU_SOURCE -fno-math-errno -fno-trapping-math +ifdef CONFIG_WERROR +CFLAGS+=-Werror +HOST_CFLAGS+=-Werror +endif +ifdef CONFIG_ARM32 +CFLAGS+=-mthumb +endif +ifdef CONFIG_SMALL +CFLAGS+=-Os +else +CFLAGS+=-O2 +endif +#CFLAGS+=-fstack-usage +ifdef CONFIG_SOFTFLOAT +CFLAGS+=-msoft-float +CFLAGS+=-DUSE_SOFTFLOAT +endif # CONFIG_SOFTFLOAT +HOST_CFLAGS+=-O2 +LDFLAGS=-g +HOST_LDFLAGS=-g +ifdef CONFIG_GPROF +CFLAGS+=-p +LDFLAGS+=-p +endif +ifdef CONFIG_ASAN +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer +endif +ifdef CONFIG_X86_32 +CFLAGS+=-m32 +LDFLAGS+=-m32 +endif +ifdef CONFIG_PROFILE +CFLAGS+=-p +LDFLAGS+=-p +endif + +# when cross compiling from a 64 bit system to a 32 bit system, force +# a 32 bit output +ifdef CONFIG_X86_32 +MQJS_BUILD_FLAGS=-m32 +endif +ifdef CONFIG_ARM32 +MQJS_BUILD_FLAGS=-m32 +endif + +PROGS=mqjs$(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 +LIBS=-lm + +mqjs$(EXE): $(MQJS_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +mquickjs.o: mquickjs_atom.h + +mqjs_stdlib: mqjs_stdlib.host.o mquickjs_build.host.o + $(HOST_CC) $(HOST_LDFLAGS) -o $@ $^ + +mquickjs_atom.h: mqjs_stdlib + ./mqjs_stdlib -a $(MQJS_BUILD_FLAGS) > $@ + +mqjs_stdlib.h: mqjs_stdlib + ./mqjs_stdlib $(MQJS_BUILD_FLAGS) > $@ + +mqjs.o: mqjs_stdlib.h + +# C API example +example.o: example_stdlib.h + +example$(EXE): example.o mquickjs.o dtoa.o libm.o cutils.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +example_stdlib: example_stdlib.host.o mquickjs_build.host.o + $(HOST_CC) $(HOST_LDFLAGS) -o $@ $^ + +example_stdlib.h: example_stdlib + ./example_stdlib $(MQJS_BUILD_FLAGS) > $@ + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.host.o: %.c + $(HOST_CC) $(HOST_CFLAGS) -c -o $@ $< + +test: mqjs example + ./mqjs tests/test_closure.js + ./mqjs tests/test_language.js + ./mqjs tests/test_loop.js + ./mqjs tests/test_builtin.js +# test bytecode generation and loading + ./mqjs -o test_builtin.bin tests/test_builtin.js +# @sha256sum -c test_builtin.sha256 + ./mqjs -b test_builtin.bin + ./example tests/test_rect.js + +microbench: mqjs + ./mqjs tests/microbench.js + +octane: mqjs + ./mqjs --memory-limit 256M tests/octane/run.js + +size: mqjs + size mqjs mqjs.o readline.o cutils.o dtoa.o libm.o mquickjs.o + +dtoa_test: tests/dtoa_test.o dtoa.o cutils.o tests/gay-fixed.o tests/gay-precision.o tests/gay-shortest.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +libm_test: tests/libm_test.o libm.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +rempio2_test: tests/rempio2_test.o libm.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +clean: + rm -f *.o *.d *~ tests/*.o tests/*.d tests/*~ test_builtin.bin mqjs_stdlib mqjs_stdlib.h mquickjs_build_atoms mquickjs_atom.h mqjs_example example_stdlib example_stdlib.h $(PROGS) $(TEST_PROGS) + +-include $(wildcard *.d) diff --git a/cutils.c b/cutils.c new file mode 100644 index 00000000..57a71bde --- /dev/null +++ b/cutils.c @@ -0,0 +1,178 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 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 "cutils.h" + +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +int has_suffix(const char *str, const char *suffix) +{ + size_t len = strlen(str); + size_t slen = strlen(suffix); + return (len >= slen && !memcmp(str + len - slen, suffix, slen)); +} + +size_t __unicode_to_utf8(uint8_t *buf, unsigned int c) +{ + uint8_t *q = buf; + + if (c < 0x800) { + *q++ = (c >> 6) | 0xc0; + } else { + if (c < 0x10000) { + *q++ = (c >> 12) | 0xe0; + } else { + if (c < 0x00200000) { + *q++ = (c >> 18) | 0xf0; + } else { + return 0; + } + *q++ = ((c >> 12) & 0x3f) | 0x80; + } + *q++ = ((c >> 6) & 0x3f) | 0x80; + } + *q++ = (c & 0x3f) | 0x80; + return q - buf; +} + +int __unicode_from_utf8(const uint8_t *p, size_t max_len, size_t *plen) +{ + size_t len = 1; + int c; + + c = p[0]; + if (c < 0xc0) { + goto fail; + } else if (c < 0xe0) { + if (unlikely(max_len < 2 || (p[1] & 0xc0) != 0x80)) + goto fail; + c = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f); + len = 2; + if (unlikely(c < 0x80)) + goto fail; + } else if (c < 0xf0) { + if (unlikely(max_len < 2 || (p[1] & 0xc0) != 0x80)) + goto fail; + if (unlikely(max_len < 3 || (p[2] & 0xc0) != 0x80)) { + len = 2; + goto fail; + } + c = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + len = 3; + if (unlikely(c < 0x800)) + goto fail; + } else if (c < 0xf8) { + if (unlikely(max_len < 2 || (p[1] & 0xc0) != 0x80)) + goto fail; + if (unlikely(max_len < 3 || (p[2] & 0xc0) != 0x80)) { + len = 2; + goto fail; + } + if (unlikely(max_len < 4 || (p[3] & 0xc0) != 0x80)) { + len = 3; + goto fail; + } + c = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f); + len = 4; + /* We explicitly accept surrogate pairs */ + if (unlikely(c < 0x10000 || c > 0x10ffff)) + goto fail; + } else { + fail: + *plen = len; + return -1; + } + *plen = len; + return c; +} + +int __utf8_get(const uint8_t *p, size_t *plen) +{ + size_t len; + int c; + + c = p[0]; + if (c < 0xc0) { + len = 1; + } else if (c < 0xe0) { + c = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f); + len = 2; + } else if (c < 0xf0) { + c = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + len = 3; + } else if (c < 0xf8) { + c = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f); + len = 4; + } else { + len = 1; + } + *plen = len; + return c; +} diff --git a/cutils.h b/cutils.h new file mode 100644 index 00000000..c0dd0011 --- /dev/null +++ b/cutils.h @@ -0,0 +1,355 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 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 CUTILS_H +#define CUTILS_H + +#include +#include + +/* set if CPU is big endian */ +#undef WORDS_BIGENDIAN + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define force_inline inline __attribute__((always_inline)) +#define no_inline __attribute__((noinline)) +#define __maybe_unused __attribute__((unused)) + +#define xglue(x, y) x ## y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#endif + +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) + +typedef int BOOL; + +#ifndef FALSE +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); +int strstart(const char *str, const char *val, const char **ptr); +int has_suffix(const char *str, const char *suffix); + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline size_t max_size_t(size_t a, size_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline size_t min_size_t(size_t a, size_t b) +{ + if (a < b) + return a; + else + return b; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ + return __builtin_clz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ + return __builtin_clzll(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ + return __builtin_ctz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ + return __builtin_ctzll(a); +} + +struct __attribute__((packed)) packed_u64 { + uint64_t v; +}; + +struct __attribute__((packed)) packed_u32 { + uint32_t v; +}; + +struct __attribute__((packed)) packed_u16 { + uint16_t v; +}; + +static inline uint64_t get_u64(const uint8_t *tab) +{ + return ((const struct packed_u64 *)tab)->v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + return (int64_t)((const struct packed_u64 *)tab)->v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + ((struct packed_u64 *)tab)->v = val; +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + return ((const struct packed_u32 *)tab)->v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + return (int32_t)((const struct packed_u32 *)tab)->v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + ((struct packed_u32 *)tab)->v = val; +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + return ((const struct packed_u16 *)tab)->v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + return (int16_t)((const struct packed_u16 *)tab)->v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + ((struct packed_u16 *)tab)->v = val; +} + +static inline uint32_t get_u8(const uint8_t *tab) +{ + return *tab; +} + +static inline int32_t get_i8(const uint8_t *tab) +{ + return (int8_t)*tab; +} + +static inline void put_u8(uint8_t *tab, uint8_t val) +{ + *tab = val; +} + +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} + +static inline uint64_t bswap64(uint64_t v) +{ + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} + +static inline uint32_t get_be32(const uint8_t *d) +{ + return bswap32(get_u32(d)); +} + +static inline void put_be32(uint8_t *d, uint32_t v) +{ + put_u32(d, bswap32(v)); +} + +#define UTF8_CHAR_LEN_MAX 4 + +size_t __unicode_to_utf8(uint8_t *buf, unsigned int c); +int __unicode_from_utf8(const uint8_t *p, size_t max_len, size_t *plen); +int __utf8_get(const uint8_t *p, size_t *plen); + +/* Note: at most 21 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes + are output. */ +static inline size_t unicode_to_utf8(uint8_t *buf, unsigned int c) +{ + if (c < 0x80) { + buf[0] = c; + return 1; + } else { + return __unicode_to_utf8(buf, c); + } +} + +/* return -1 in case of error. Surrogates are accepted. max_len must + be >= 1. *plen is set in case of error and always >= 1. */ +static inline int unicode_from_utf8(const uint8_t *buf, size_t max_len, size_t *plen) +{ + if (buf[0] < 0x80) { + *plen = 1; + return buf[0]; + } else { + return __unicode_from_utf8(buf, max_len, plen); + } +} + +/* Warning: no error checking is done so the UTF-8 encoding must be + validated before. */ +static force_inline int utf8_get(const uint8_t *buf, size_t *plen) +{ + if (likely(buf[0] < 0x80)) { + *plen = 1; + return buf[0]; + } else { + return __utf8_get(buf, plen); + } +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static inline uint64_t float64_as_uint64(double d) +{ + union { + double d; + uint64_t u64; + } u; + u.d = d; + return u.u64; +} + +static inline double uint64_as_float64(uint64_t u64) +{ + union { + double d; + uint64_t u64; + } u; + u.u64 = u64; + return u.d; +} + +typedef union { + uint32_t u32; + float f; +} f32_union; + +static inline uint32_t float_as_uint(float f) +{ + f32_union u; + u.f = f; + return u.u32; +} + +static inline float uint_as_float(uint32_t v) +{ + f32_union u; + u.u32 = v; + return u.f; +} + +#endif /* CUTILS_H */ diff --git a/dtoa.c b/dtoa.c new file mode 100644 index 00000000..604f3f0c --- /dev/null +++ b/dtoa.c @@ -0,0 +1,1620 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024-2025 Fabrice Bellard + * + * 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 "cutils.h" +#include "dtoa.h" + +/* + TODO: + - test n_digits=101 instead of 100 + - simplify subnormal handling + - reduce max memory usage + - free format: could add shortcut if exact result + - use 64 bit limb_t when possible + - use another algorithm for free format dtoa in base 10 (ryu ?) +*/ + +#define USE_POW5_TABLE +/* use fast path to print small integers in free format */ +#define USE_FAST_INT + +#define LIMB_LOG2_BITS 5 + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; + +#define LIMB_DIGITS 9 + +#define JS_RADIX_MAX 36 + +#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ +#define MANT_LEN_MAX 18 /* < 36^100 */ + +typedef intptr_t mp_size_t; + +/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */ +typedef struct { + int len; /* >= 1 */ + limb_t tab[]; +} mpb_t; + +static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) +{ + size_t i; + limb_t k, a; + + k=b; + for(i=0;i> LIMB_BITS; + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is between + 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < + 2^shift. */ +static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t low) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + tab_r[i] = (a << shift) | l; + l = (a >> (LIMB_BITS - shift)); + } + return l; +} + +static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r, limb_t b_inv, int shift) +{ + slimb_t i; + + if (shift != 0) { + r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); + } + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + r >>= shift; + return r; +} + +static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) +{ + int i; + + printf("%s= 0x", str); + for(i = a->len - 1; i >= 0; i--) { + printf("%08x", a->tab[i]); + if (i != 0) + printf("_"); + } + printf("\n"); +} + +static void mpb_renorm(mpb_t *r) +{ + while (r->len > 1 && r->tab[r->len - 1] == 0) + r->len--; +} + +#ifdef USE_POW5_TABLE +static const uint32_t pow5_table[17] = { + 0x00000005, 0x00000019, 0x0000007d, 0x00000271, + 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, + 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, + 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, + 0xa2bc2ec5, +}; + +static const uint8_t pow5h_table[4] = { + 0x00000001, 0x00000007, 0x00000023, 0x000000b1, +}; + +static const uint32_t pow5_inv_table[13] = { + 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, + 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, + 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, + 0xc25c2684, +}; +#endif + +/* return a^b */ +static uint64_t pow_ui(uint32_t a, uint32_t b) +{ + int i, n_bits; + uint64_t r; + if (b == 0) + return 1; + if (b == 1) + return a; +#ifdef USE_POW5_TABLE + if ((a == 5 || a == 10) && b <= 17) { + r = pow5_table[b - 1]; + if (b >= 14) { + r |= (uint64_t)pow5h_table[b - 14] << 32; + } + if (a == 10) + r <<= b; + return r; + } +#endif + r = a; + n_bits = 32 - clz32(b); + for(i = n_bits - 2; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + return r; +} + +static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) +{ + uint32_t r_inv, r; + int shift; +#ifdef USE_POW5_TABLE + if (a == 5 && b >= 1 && b <= 13) { + r = pow5_table[b - 1]; + shift = clz32(r); + r <<= shift; + r_inv = pow5_inv_table[b - 1]; + } else +#endif + { + r = pow_ui(a, b); + shift = clz32(r); + r <<= shift; + r_inv = udiv1norm_init(r); + } + *pshift = shift; + *pr_inv = r_inv; + return r; +} + +enum { + JS_RNDN, /* round to nearest, ties to even */ + JS_RNDNA, /* round to nearest, ties away from zero */ + JS_RNDZ, +}; + +static int mpb_get_bit(const mpb_t *r, int k) +{ + int l; + + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + if (l >= r->len) + return 0; + else + return (r->tab[l] >> k) & 1; +} + +/* compute round(r / 2^shift). 'shift' can be negative */ +static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) +{ + int l, i; + + if (shift == 0) + return; + if (shift < 0) { + shift = -shift; + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (shift != 0) { + r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0); + r->len++; + mpb_renorm(r); + } + if (l > 0) { + for(i = r->len - 1; i >= 0; i--) + r->tab[i + l] = r->tab[i]; + for(i = 0; i < l; i++) + r->tab[i] = 0; + r->len += l; + } + } else { + limb_t bit1, bit2; + int k, add_one; + + switch(rnd_mode) { + default: + case JS_RNDZ: + add_one = 0; + break; + case JS_RNDN: + case JS_RNDNA: + bit1 = mpb_get_bit(r, shift - 1); + if (bit1) { + if (rnd_mode == JS_RNDNA) { + bit2 = 1; + } else { + /* bit2 = oring of all the bits after bit1 */ + bit2 = 0; + if (shift >= 2) { + k = shift - 1; + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + for(i = 0; i < min_int(l, r->len); i++) + bit2 |= r->tab[i]; + if (l < r->len) + bit2 |= r->tab[l] & (((limb_t)1 << k) - 1); + } + } + if (bit2) { + add_one = 1; + } else { + /* round to even */ + add_one = mpb_get_bit(r, shift); + } + } else { + add_one = 0; + } + break; + } + + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (l >= r->len) { + r->len = 1; + r->tab[0] = add_one; + } else { + if (l > 0) { + r->len -= l; + for(i = 0; i < r->len; i++) + r->tab[i] = r->tab[i + l]; + } + if (shift != 0) { + mp_shr(r->tab, r->tab, r->len, shift, 0); + mpb_renorm(r); + } + if (add_one) { + limb_t a; + a = mp_add_ui(r->tab, 1, r->len); + if (a) + r->tab[r->len++] = a; + } + } + } +} + +/* return -1, 0 or 1 */ +static int mpb_cmp(const mpb_t *a, const mpb_t *b) +{ + mp_size_t i; + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + for(i = a->len - 1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + return -1; + else + return 1; + } + } + return 0; +} + +static void mpb_set_u64(mpb_t *r, uint64_t m) +{ +#if LIMB_BITS == 64 + r->tab[0] = m; + r->len = 1; +#else + r->tab[0] = m; + r->tab[1] = m >> LIMB_BITS; + if (r->tab[1] == 0) + r->len = 1; + else + r->len = 2; +#endif +} + +static uint64_t mpb_get_u64(mpb_t *r) +{ +#if LIMB_BITS == 64 + return r->tab[0]; +#else + if (r->len == 1) { + return r->tab[0]; + } else { + return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS); + } +#endif +} + +/* floor_log2() = position of the first non zero bit or -1 if zero. */ +static int mpb_floor_log2(mpb_t *a) +{ + limb_t v; + v = a->tab[a->len - 1]; + if (v == 0) + return -1; + else + return a->len * LIMB_BITS - 1 - clz32(v); +} + +#define MUL_LOG2_RADIX_BASE_LOG2 24 + +/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ +static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = { + 0x000000, 0xa1849d, 0x000000, 0x6e40d2, + 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, + 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, + 0x433d00, 0x418677, 0x000000, 0x3ea16b, + 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, + 0x39680b, 0x3897b3, 0x37d5af, 0x372069, + 0x367686, 0x35d6df, 0x354072, 0x34b261, + 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, + 0x3251dd, 0x31e8d6, 0x318465, +}; + +/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ +static int mul_log2_radix(int a, int radix) +{ + int radix_bits, mult; + + if ((radix & (radix - 1)) == 0) { + /* if the radix is a power of two better to do it exactly */ + radix_bits = 31 - clz32(radix); + if (a < 0) + a -= radix_bits - 1; + return a / radix_bits; + } else { + mult = mul_log2_radix_table[radix - 2]; + return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; + } +} + +#if 0 +static void build_mul_log2_radix_table(void) +{ + int base, radix, mult, col, base_log2; + + base_log2 = 24; + base = 1 << base_log2; + col = 0; + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) + mult = 0; + else + mult = lrint((double)base / log2(radix)); + printf("0x%06x, ", mult); + if (++col == 4) { + printf("\n"); + col = 0; + } + } + printf("\n"); +} + +static void mul_log2_radix_test(void) +{ + int radix, i, ref, r; + + for(radix = 2; radix <= 36; radix++) { + for(i = -2048; i <= 2047; i++) { + ref = (int)floor((double)i / log2(radix)); + r = mul_log2_radix(i, radix); + if (ref != r) { + printf("ERROR: radix=%d i=%d r=%d ref=%d\n", + radix, i, r, ref); + exit(1); + } + } + } + if (0) + build_mul_log2_radix_table(); +} +#endif + +static void u32toa_len(char *buf, uint32_t n, size_t len) +{ + int digit, i; + for(i = len - 1; i >= 0; i--) { + digit = n % 10; + n = n / 10; + buf[i] = digit + '0'; + } +} + +/* for power of 2 radixes. len >= 1 */ +static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } +} + +/* len >= 1. 2 <= radix <= 36 */ +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ +#if LIMB_BITS == 32 + u32toa_len(buf, n, len); +#else + /* XXX: optimize */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } +#endif + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } + } +} + +size_t u32toa(char *buf, uint32_t n) +{ + char buf1[10], *q; + size_t len; + + q = buf1 + sizeof(buf1); + do { + *--q = n % 10 + '0'; + n /= 10; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; +} + +size_t i32toa(char *buf, int32_t n) +{ + if (n >= 0) { + return u32toa(buf, n); + } else { + buf[0] = '-'; + return u32toa(buf + 1, -(uint32_t)n) + 1; + } +} + +#ifdef USE_FAST_INT +size_t u64toa(char *buf, uint64_t n) +{ + if (n < 0x100000000) { + return u32toa(buf, n); + } else { + uint64_t n1; + char *q = buf; + uint32_t n2; + + n1 = n / 1000000000; + n %= 1000000000; + if (n1 >= 0x100000000) { + n2 = n1 / 1000000000; + n1 = n1 % 1000000000; + /* at most two digits */ + if (n2 >= 10) { + *q++ = n2 / 10 + '0'; + n2 %= 10; + } + *q++ = n2 + '0'; + u32toa_len(q, n1, 9); + q += 9; + } else { + q += u32toa(q, n1); + } + u32toa_len(q, n, 9); + q += 9; + return q - buf; + } +} + +size_t i64toa(char *buf, int64_t n) +{ + if (n >= 0) { + return u64toa(buf, n); + } else { + buf[0] = '-'; + return u64toa(buf + 1, -(uint64_t)n) + 1; + } +} + +/* XXX: only tested for 1 <= n < 2^53 */ +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) +{ + int radix_bits, l; + if (likely(radix == 10)) + return u64toa(buf, n); + if ((radix & (radix - 1)) == 0) { + radix_bits = 31 - clz32(radix); + if (n == 0) + l = 1; + else + l = (64 - clz64(n) + radix_bits - 1) / radix_bits; + u64toa_bin_len(buf, n, radix_bits, l); + return l; + } else { + char buf1[41], *q; /* maximum length for radix = 3 */ + size_t len; + int digit; + q = buf1 + sizeof(buf1); + do { + digit = n % radix; + n /= radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + *--q = digit; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; + } +} + +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) +{ + if (n >= 0) { + return u64toa_radix(buf, n, radix); + } else { + buf[0] = '-'; + return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; + } +} +#endif /* USE_FAST_INT */ + +static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +/* XXX: remove the table ? */ +static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = { + 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* we limit the maximum number of significant digits for atod to about + 128 bits of precision for non power of two bases. The only + requirement for Javascript is at least 20 digits in base 10. For + power of two bases, we do an exact rounding in all the cases. */ +static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = { + 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, +}; + +/* if abs(d) >= B^max_exponent, it is an overflow */ +static const int16_t max_exponent[JS_RADIX_MAX - 1] = { + 1024, 647, 512, 442, 397, 365, 342, 324, + 309, 297, 286, 277, 269, 263, 256, 251, + 246, 242, 237, 234, 230, 227, 224, 221, + 218, 216, 214, 211, 209, 207, 205, 203, + 202, 200, 199, +}; + +/* if abs(d) <= B^min_exponent, it is an underflow */ +static const int16_t min_exponent[JS_RADIX_MAX - 1] = { +-1075, -679, -538, -463, -416, -383, -359, -340, + -324, -311, -300, -291, -283, -276, -269, -263, + -258, -254, -249, -245, -242, -238, -235, -232, + -229, -227, -224, -222, -220, -217, -215, -214, + -212, -210, -208, +}; + +#if 0 +void build_tables(void) +{ + int r, j, radix, n, col, i; + + /* radix_base_table */ + for(radix = 2; radix <= 36; radix++) { + r = 1; + for(j = 0; j < digits_per_limb_table[radix - 2]; j++) { + r *= radix; + } + printf(" 0x%08x,", r); + if ((radix % 4) == 1) + printf("\n"); + } + printf("\n"); + + /* dtoa_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + /* Note: over estimated when the radix is a power of two */ + printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); + } + printf("\n"); + + /* atod_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) { + /* 64 bits is more than enough */ + n = (int)floor(64.0 / log2(radix)); + } else { + n = (int)floor(128.0 / log2(radix)); + } + printf(" %d,", n); + } + printf("\n"); + + printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)ceil(1024 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)floor(-1075 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const uint32_t pow5_table[16] = {\n"); + col = 0; + for(i = 2; i <= 17; i++) { + r = 1; + for(j = 0; j < i; j++) { + r *= 5; + } + printf("0x%08x, ", r); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + /* high part */ + printf("static const uint8_t pow5h_table[4] = {\n"); + col = 0; + for(i = 14; i <= 17; i++) { + uint64_t r1; + r1 = 1; + for(j = 0; j < i; j++) { + r1 *= 5; + } + printf("0x%08x, ", (uint32_t)(r1 >> 32)); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); +} +#endif + +/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, + the dot is not displayed. 'a' is modified. */ +static int output_digits(char *buf, + mpb_t *a, int radix, int n_digits1, + int dot_pos) +{ + int n_digits, digits_per_limb, radix_bits, n, len; + + n_digits = n_digits1; + if ((radix & (radix - 1)) == 0) { + /* radix = 2^radix_bits */ + radix_bits = 31 - clz32(radix); + } else { + radix_bits = 0; + } + digits_per_limb = digits_per_limb_table[radix - 2]; + if (radix_bits != 0) { + for(;;) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n); + if (n_digits == 0) + break; + mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); + } + } else { + limb_t r; + while (n_digits != 0) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0); + mpb_renorm(a); + limb_to_a(buf + n_digits, r, radix, n); + } + } + + /* add the dot */ + len = n_digits1; + if (dot_pos != n_digits1) { + memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); + buf[dot_pos] = '.'; + len++; + } + return len; +} + +/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * + 2^-e_offset. 'f' can be negative. */ +static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, BOOL is_int, int e) +{ + int e_offset, d, n, n0; + + e_offset = -f * radix_shift; + if (radix1 != 1) { + d = digits_per_limb_table[radix1 - 2]; + if (f >= 0) { + limb_t h, b; + + b = 0; + n0 = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui(radix1, n); + n0 = n; + } + h = mp_mul1(a->tab, a->tab, a->len, b, 0); + if (h != 0) { + a->tab[a->len++] = h; + } + f -= n; + } + } else { + int extra_bits, l, shift; + limb_t r, rem, b, b_inv; + + f = -f; + l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ + e_offset += l * LIMB_BITS; + if (!is_int) { + /* at least 'e' bits are needed in the final result for rounding */ + extra_bits = max_int(e - mpb_floor_log2(a), 0); + } else { + /* at least two extra bits are needed in the final result + for rounding */ + extra_bits = max_int(2 + e - e_offset, 0); + } + e_offset += extra_bits; + mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); + + b = 0; + b_inv = 0; + shift = 0; + n0 = 0; + rem = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui_inv(&b_inv, &shift, radix1, n); + n0 = n; + } + r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); + rem |= r; + mpb_renorm(a); + f -= n; + } + /* if the remainder is non zero, use it for rounding */ + a->tab[0] |= (rem != 0); + } + } + return e_offset; +} + +/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ +static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, + int rnd_mode) +{ + int e_offset; + + mpb_set_u64(tmp1, m); + e_offset = mul_pow(tmp1, radix1, radix_shift, f, TRUE, e); + mpb_shr_round(tmp1, -e + e_offset, rnd_mode); +} + +/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ +static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) +{ + int e; + uint64_t m; + + if (a->tab[0] == 0 && a->len == 1) { + /* zero result */ + m = 0; + e = 0; /* don't care */ + } else { + int prec, prec1, e_min; + e = mpb_floor_log2(a) + 1 - e_offset; + prec1 = 53; + e_min = -1021; + if (e < e_min) { + /* subnormal result or zero */ + prec = prec1 - (e_min - e); + } else { + prec = prec1; + } + mpb_shr_round(a, e + e_offset - prec, rnd_mode); + m = mpb_get_u64(a); + m <<= (53 - prec); + /* mantissa overflow due to rounding */ + if (m >= (uint64_t)1 << 53) { + m >>= 1; + e++; + } + } + *pe = e; + return m; +} + +/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 + <= m < 2^53 or m = 0. + 'a' is modified. */ +static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, + int radix1, int radix_shift, int f, int rnd_mode) +{ + int e_offset; + + e_offset = mul_pow(a, radix1, radix_shift, f, FALSE, 55); + return round_to_d(pe, a, e_offset, rnd_mode); +} + +#ifdef JS_DTOA_DUMP_STATS +static int out_len_count[17]; + +void js_dtoa_dump_stats(void) +{ + int i, sum; + sum = 0; + for(i = 0; i < 17; i++) + sum += out_len_count[i]; + for(i = 0; i < 17; i++) { + printf("%2d %8d %5.2f%%\n", + i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100); + } +} +#endif + +/* return a maximum bound of the string length. The bound depends on + 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED + is enabled. */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags) +{ + int fmt = flags & JS_DTOA_FORMAT_MASK; + int n, e; + uint64_t a; + + if (fmt != JS_DTOA_FORMAT_FRAC) { + if (fmt == JS_DTOA_FORMAT_FREE) { + n = dtoa_max_digits_table[radix - 2]; + } else { + n = n_digits; + } + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { + /* no exponential */ + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + e -= 1023; + /* XXX: adjust */ + n += 10 + abs(mul_log2_radix(e - 1, radix)); + } + } else { + /* extra: sign, 1 dot and exponent "e-1000" */ + n += 1 + 1 + 6; + } + } else { + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + /* high bound for the integer part */ + e -= 1023; + /* x < 2^(e + 1) */ + if (e < 0) { + n = 1; + } else { + n = 2 + mul_log2_radix(e - 1, radix); + } + /* sign, extra digit, 1 dot */ + n += 1 + 1 + 1 + n_digits; + } + } + return max_int(n, 9); /* also include NaN and [-]Infinity */ +} + +#if defined(__SANITIZE_ADDRESS__) && 0 +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + return malloc(size); +} +static void dtoa_free(void *ptr) +{ + free(ptr); +} +#else +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + void *ret; + ret = *pptr; + *pptr += (size + 7) / 8; + return ret; +} + +static void dtoa_free(void *ptr) +{ +} +#endif + +/* return the length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem) +{ + uint64_t a, m, *mptr = tmp_mem->mem; + int e, sgn, l, E, P, i, E_max, radix1, radix_shift; + char *q; + mpb_t *tmp1, *mant_max; + int fmt = flags & JS_DTOA_FORMAT_MASK; + + tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0])); + + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & 0x7ff; + m = a & (((uint64_t)1 << 52) - 1); + q = buf; + if (e == 0x7ff) { + if (m == 0) { + if (sgn) + *q++ = '-'; + memcpy(q, "Infinity", 8); + q += 8; + } else { + memcpy(q, "NaN", 3); + q += 3; + } + goto done; + } else if (e == 0) { + if (m == 0) { + tmp1->len = 1; + tmp1->tab[0] = 0; + E = 1; + if (fmt == JS_DTOA_FORMAT_FREE) + P = 1; + else if (fmt == JS_DTOA_FORMAT_FRAC) + P = n_digits + 1; + else + P = n_digits; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* remove the bias */ + e -= 1022; + /* d = 2^(e-53)*m */ + // printf("m=0x%016" PRIx64 " e=%d\n", m, e); +#ifdef USE_FAST_INT + if (fmt == JS_DTOA_FORMAT_FREE && + e >= 1 && e <= 53 && + (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && + (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { + m >>= 53 - e; + /* 'm' is never zero */ + q += u64toa_radix(q, m, radix); + goto done; + } +#endif + + /* this choice of E implies F=round(x*B^(P-E) is such as: + B^(P-1) <= F < 2.B^P. */ + E = 1 + mul_log2_radix(e - 1, radix); + + if (fmt == JS_DTOA_FORMAT_FREE) { + int P_max, E0, e1, E_found, P_found; + uint64_t m1, mant_found, mant, mant_max1; + /* P_max is guaranteed to work by construction */ + P_max = dtoa_max_digits_table[radix - 2]; + E0 = E; + E_found = 0; + P_found = 0; + mant_found = 0; + /* find the minimum number of digits by successive tries */ + P = P_max; /* P_max is guaranteed to work */ + for(;;) { + /* mant_max always fits on 64 bits */ + mant_max1 = pow_ui(radix, P); + /* compute the mantissa in base B */ + E = E0; + for(;;) { + /* XXX: add inexact flag */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); + mant = mpb_get_u64(tmp1); + if (mant < mant_max1) + break; + E++; /* at most one iteration is possible */ + } + /* remove useless trailing zero digits */ + while ((mant % radix) == 0) { + mant /= radix; + P--; + } + /* guaranteed to work for P = P_max */ + if (P_found == 0) + goto prec_found; + /* convert back to base 2 */ + mpb_set_u64(tmp1, mant); + m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); + // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); + /* Note: (m, e) is never zero here, so the exponent for m1 + = 0 does not matter */ + if (m1 == m && e1 == e) { + prec_found: + P_found = P; + E_found = E; + mant_found = mant; + if (P == 1) + break; + P--; /* try lower exponent */ + } else { + break; + } + } + P = P_found; + E = E_found; + mpb_set_u64(tmp1, mant_found); +#ifdef JS_DTOA_DUMP_STATS + if (radix == 10) { + out_len_count[P - 1]++; + } +#endif + } else if (fmt == JS_DTOA_FORMAT_FRAC) { + int len; + + assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); + /* P = max_int(E, 1) + n_digits; */ + /* frac is rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); + + /* we add one extra digit on the left and remove it if needed + to avoid testing if the result is < radix^P */ + len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, + max_int(E + 1, 1)); + if (q[0] == '0' && len >= 2 && q[1] != '.') { + len--; + memmove(q, q + 1, len); + } + q += len; + goto done; + } else { + int pow_shift; + assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); + P = n_digits; + /* mant_max = radix^P */ + mant_max->len = 1; + mant_max->tab[0] = 1; + pow_shift = mul_pow(mant_max, radix1, radix_shift, P, FALSE, 0); + mpb_shr_round(mant_max, pow_shift, JS_RNDZ); + + for(;;) { + /* fixed and frac are rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); + if (mpb_cmp(tmp1, mant_max) < 0) + break; + E++; /* at most one iteration is possible */ + } + } + output: + if (fmt == JS_DTOA_FORMAT_FIXED) + E_max = n_digits; + else + E_max = dtoa_max_digits_table[radix - 2] + 4; + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || + ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { + q += output_digits(q, tmp1, radix, P, 1); + E--; + if (radix == 10) { + *q++ = 'e'; + } else if (radix1 == 1 && radix_shift <= 4) { + E *= radix_shift; + *q++ = 'p'; + } else { + *q++ = '@'; + } + if (E < 0) { + *q++ = '-'; + E = -E; + } else { + *q++ = '+'; + } + q += u32toa(q, E); + } else if (E <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -E; i++) + *q++ = '0'; + q += output_digits(q, tmp1, radix, P, P); + } else { + q += output_digits(q, tmp1, radix, P, min_int(P, E)); + for(i = 0; i < E - P; i++) + *q++ = '0'; + } + done: + *q = '\0'; + dtoa_free(mant_max); + dtoa_free(tmp1); + return q - buf; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ +static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) +{ + int i; + if (r->tab[0] == 0 && r->len == 1) { + r->tab[0] = a; + } else { + if (radix_base == 0) { + for(i = r->len; i >= 0; i--) { + r->tab[i + 1] = r->tab[i]; + } + r->tab[0] = a; + } else { + r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len, + radix_base, a); + } + r->len++; + mpb_renorm(r); + } +} + +/* XXX: add fast path for small integers */ +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem) +{ + uint64_t *mptr = tmp_mem->mem; + const char *p, *p_start; + limb_t cur_limb, radix_base, extra_digits; + int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; + int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; + mpb_t *tmp0; + double dval; + BOOL is_bin_exp, is_zero, expn_overflow; + uint64_t m, a; + + tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0])); + /* optional separator between digits */ + sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start = p; + } else if (p[0] == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + if (!(flags & JS_ATOD_INT_ONLY) && strstart(p, "Infinity", &p)) + goto overflow; + } + if (radix == 0) + radix = 10; + + cur_limb = 0; + expn_offset = 0; + digit_count = 0; + limb_digit_count = 0; + max_digits = atod_max_digits_table[radix - 2]; + digits_per_limb = digits_per_limb_table[radix - 2]; + radix_base = radix_base_table[radix - 2]; + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + if (radix1 == 1) { + /* radix = 2^radix_bits */ + radix_bits = radix_shift; + } else { + radix_bits = 0; + } + tmp0->len = 1; + tmp0->tab[0] = 0; + extra_digits = 0; + pos = 0; + dot_pos = -1; + /* skip leading zeros */ + for(;;) { + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && p[1] == '0') + p++; + if (*p != '0') + break; + p++; + pos++; + } + + sig_pos = pos; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && to_digit(p[1]) < radix) + p++; + c = to_digit(*p); + if (c >= radix) + break; + p++; + pos++; + if (digit_count < max_digits) { + /* XXX: could be faster when radix_bits != 0 */ + cur_limb = cur_limb * radix + c; + limb_digit_count++; + if (limb_digit_count == digits_per_limb) { + mpb_mul1_base(tmp0, radix_base, cur_limb); + cur_limb = 0; + limb_digit_count = 0; + } + digit_count++; + } else { + extra_digits |= c; + } + } + if (limb_digit_count != 0) { + mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); + } + if (digit_count == 0) { + is_zero = TRUE; + expn_offset = 0; + } else { + is_zero = FALSE; + if (dot_pos < 0) + dot_pos = pos; + expn_offset = sig_pos + digit_count - dot_pos; + } + + /* Use the extra digits for rounding if the base is a power of + two. Otherwise they are just truncated. */ + if (radix_bits != 0 && extra_digits != 0) { + tmp0->tab[0] |= 1; + } + + /* parse the exponent, if any */ + expn = 0; + expn_overflow = FALSE; + is_bin_exp = FALSE; + if (!(flags & JS_ATOD_INT_ONLY) && + ((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + BOOL exp_is_neg; + int c; + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = 0; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = 1; + p++; + } + c = to_digit(*p); + if (c >= 10) + goto fail; /* XXX: could stop before the exponent part */ + expn = c; + p++; + for(;;) { + if (*p == sep && to_digit(p[1]) < 10) + p++; + c = to_digit(*p); + if (c >= 10) + break; + if (!expn_overflow) { + if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { + expn_overflow = TRUE; + } else { + expn = expn * 10 + c; + } + } + p++; + } + if (exp_is_neg) + expn = -expn; + /* if zero result, the exponent can be arbitrarily large */ + if (!is_zero && expn_overflow) { + if (exp_is_neg) + a = 0; + else + a = (uint64_t)0x7ff << 52; /* infinity */ + goto done; + } + } + + if (p == p_start) + goto fail; + + if (is_zero) { + a = 0; + } else { + int expn1; + if (radix_bits != 0) { + if (!is_bin_exp) + expn *= radix_bits; + expn -= expn_offset * radix_bits; + expn1 = expn + digit_count * radix_bits; + if (expn1 >= 1024 + radix_bits) + goto overflow; + else if (expn1 <= -1075) + goto underflow; + m = round_to_d(&e, tmp0, -expn, JS_RNDN); + } else { + expn -= expn_offset; + expn1 = expn + digit_count; + if (expn1 >= max_exponent[radix - 2] + 1) + goto overflow; + else if (expn1 <= min_exponent[radix - 2]) + goto underflow; + m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); + } + if (m == 0) { + underflow: + a = 0; + } else if (e > 1024) { + overflow: + /* overflow */ + a = (uint64_t)0x7ff << 52; + } else if (e < -1073) { + /* underflow */ + /* XXX: check rounding */ + a = 0; + } else if (e < -1021) { + /* subnormal */ + a = m >> (-e - 1021); + } else { + a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); + } + } + done: + a |= (uint64_t)is_neg << 63; + dval = uint64_as_float64(a); + done1: + if (pnext) + *pnext = p; + dtoa_free(tmp0); + return dval; + fail: + dval = NAN; + goto done1; +} diff --git a/dtoa.h b/dtoa.h new file mode 100644 index 00000000..91b025b4 --- /dev/null +++ b/dtoa.h @@ -0,0 +1,83 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024-2025 Fabrice Bellard + * + * 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. + */ + +//#define JS_DTOA_DUMP_STATS + +/* maximum number of digits for fixed and frac formats */ +#define JS_DTOA_MAX_DIGITS 101 + +/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ +/* use as many digits as necessary */ +#define JS_DTOA_FORMAT_FREE (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ +#define JS_DTOA_FORMAT_FIXED (1 << 0) +/* force fractional format: [-]dd.dd with n_digits fractional digits. + 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ +#define JS_DTOA_FORMAT_FRAC (2 << 0) +#define JS_DTOA_FORMAT_MASK (3 << 0) + +/* select exponential notation either in fixed or free format */ +#define JS_DTOA_EXP_AUTO (0 << 2) +#define JS_DTOA_EXP_ENABLED (1 << 2) +#define JS_DTOA_EXP_DISABLED (2 << 2) +#define JS_DTOA_EXP_MASK (3 << 2) + +#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ + +/* only accepts integers (no dot, no exponent) */ +#define JS_ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) +/* accept _ between digits as a digit separator */ +#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) + +typedef struct { + uint64_t mem[37]; +} JSDTOATempMem; + +typedef struct { + uint64_t mem[27]; +} JSATODTempMem; + +/* return a maximum bound of the string length */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags); +/* return the string length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem); +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem); + +#ifdef JS_DTOA_DUMP_STATS +void js_dtoa_dump_stats(void); +#endif + +/* additional exported functions */ +size_t u32toa(char *buf, uint32_t n); +size_t i32toa(char *buf, int32_t n); +size_t u64toa(char *buf, uint64_t n); +size_t i64toa(char *buf, int64_t n); +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); diff --git a/example.c b/example.c new file mode 100644 index 00000000..5385ede1 --- /dev/null +++ b/example.c @@ -0,0 +1,287 @@ +/* + * Micro QuickJS C API example + * + * 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 "cutils.h" +#include "mquickjs.h" + +#define JS_CLASS_RECTANGLE (JS_CLASS_USER + 0) +#define JS_CLASS_FILLED_RECTANGLE (JS_CLASS_USER + 1) +/* total number of classes */ +#define JS_CLASS_COUNT (JS_CLASS_USER + 2) + +#define JS_CFUNCTION_rectangle_closure_test (JS_CFUNCTION_USER + 0) + +typedef struct { + int x; + int y; +} RectangleData; + +typedef struct { + RectangleData parent; + int color; +} FilledRectangleData; + +static JSValue js_rectangle_constructor(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + JSValue obj; + RectangleData *d; + + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + argc &= ~FRAME_CF_CTOR; + obj = JS_NewObjectClassUser(ctx, JS_CLASS_RECTANGLE); + d = malloc(sizeof(*d)); + JS_SetOpaque(ctx, obj, d); + if (JS_ToInt32(ctx, &d->x, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &d->y, argv[1])) + return JS_EXCEPTION; + return obj; +} + +static void js_rectangle_finalizer(JSContext *ctx, void *opaque) +{ + RectangleData *d = opaque; + free(d); +} + +static JSValue js_rectangle_get_x(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + RectangleData *d; + int class_id = JS_GetClassID(ctx, *this_val); + if (class_id != JS_CLASS_RECTANGLE && class_id != JS_CLASS_FILLED_RECTANGLE) + return JS_ThrowTypeError(ctx, "expecting Rectangle class"); + d = JS_GetOpaque(ctx, *this_val); + return JS_NewInt32(ctx, d->x); +} + +static JSValue js_rectangle_get_y(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + RectangleData *d; + int class_id = JS_GetClassID(ctx, *this_val); + if (class_id != JS_CLASS_RECTANGLE && class_id != JS_CLASS_FILLED_RECTANGLE) + return JS_ThrowTypeError(ctx, "expecting Rectangle class"); + d = JS_GetOpaque(ctx, *this_val); + return JS_NewInt32(ctx, d->y); +} + +static JSValue js_rectangle_closure_test(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv, JSValue params) +{ + return params; +} + +/* C closure test */ +static JSValue js_rectangle_getClosure(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + return JS_NewCFunctionParams(ctx, JS_CFUNCTION_rectangle_closure_test, argv[0]); +} + +/* example to call a JS function. parameters: function to call, parameter */ +static JSValue js_rectangle_call(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + if (JS_StackCheck(ctx, 3)) + return JS_EXCEPTION; + JS_PushArg(ctx, argv[1]); /* parameter */ + JS_PushArg(ctx, argv[0]); /* func name */ + JS_PushArg(ctx, JS_NULL); /* this */ + return JS_Call(ctx, 1); /* single parameter */ +} + +static JSValue js_filled_rectangle_constructor(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + JSGCRef obj_ref; + JSValue *obj; + FilledRectangleData *d; + + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + obj = JS_PushGCRef(ctx, &obj_ref); + + argc &= ~FRAME_CF_CTOR; + *obj = JS_NewObjectClassUser(ctx, JS_CLASS_FILLED_RECTANGLE); + d = malloc(sizeof(*d)); + JS_SetOpaque(ctx, *obj, d); + if (JS_ToInt32(ctx, &d->parent.x, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &d->parent.y, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &d->color, argv[2])) + return JS_EXCEPTION; + JS_PopGCRef(ctx, &obj_ref); + return *obj; +} + +static void js_filled_rectangle_finalizer(JSContext *ctx, void *opaque) +{ + FilledRectangleData *d = opaque; + free(d); +} + +static JSValue js_filled_rectangle_get_color(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + FilledRectangleData *d; + if (JS_GetClassID(ctx, *this_val) != JS_CLASS_FILLED_RECTANGLE) + return JS_ThrowTypeError(ctx, "expecting FilledRectangle class"); + d = JS_GetOpaque(ctx, *this_val); + return JS_NewInt32(ctx, d->color); +} + +static 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(' '); + v = argv[i]; + if (JS_IsString(ctx, v)) { + JSCStringBuf buf; + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v, &buf); + fwrite(str, 1, len, stdout); + } else { + JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); + } + } + putchar('\n'); + 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 +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 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) +{ + return JS_NewInt64(ctx, get_time_ms()); +} + +#include "example_stdlib.h" + +static void js_log_func(void *opaque, const void *buf, size_t buf_len) +{ + fwrite(buf, 1, buf_len, stdout); +} + +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; +} + +int main(int argc, const char **argv) +{ + size_t mem_size; + int buf_len; + uint8_t *mem_buf, *buf; + JSContext *ctx; + const char *filename; + JSValue val; + + if (argc < 2) { + printf("usage: example script.js\n"); + exit(1); + } + + filename = argv[1]; + + mem_size = 65536; + mem_buf = malloc(mem_size); + ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); + JS_SetLogFunc(ctx, js_log_func); + + buf = load_file(filename, &buf_len); + val = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + if (JS_IsException(val)) { + JSValue obj; + obj = JS_GetException(ctx); + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + printf("\n"); + exit(1); + } + + JS_FreeContext(ctx); + free(mem_buf); + return 0; +} diff --git a/example_stdlib.c b/example_stdlib.c new file mode 100644 index 00000000..82d49434 --- /dev/null +++ b/example_stdlib.c @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "mquickjs_build.h" + +/* simple class example */ + +static const JSPropDef js_rectangle_proto[] = { + JS_CGETSET_DEF("x", js_rectangle_get_x, NULL ), + JS_CGETSET_DEF("y", js_rectangle_get_y, NULL ), + JS_PROP_END, +}; + +static const JSPropDef js_rectangle[] = { + JS_CFUNC_DEF("getClosure", 1, js_rectangle_getClosure ), + JS_CFUNC_DEF("call", 2, js_rectangle_call ), + JS_PROP_END, +}; + +static const JSClassDef js_rectangle_class = + JS_CLASS_DEF("Rectangle", 2, js_rectangle_constructor, JS_CLASS_RECTANGLE, js_rectangle, js_rectangle_proto, NULL, js_rectangle_finalizer); + +static const JSPropDef js_filled_rectangle_proto[] = { + JS_CGETSET_DEF("color", js_filled_rectangle_get_color, NULL ), + JS_PROP_END, +}; + +/* inherit from Rectangle */ +static const JSClassDef js_filled_rectangle_class = + JS_CLASS_DEF("FilledRectangle", 3, js_filled_rectangle_constructor, JS_CLASS_FILLED_RECTANGLE, NULL, js_filled_rectangle_proto, &js_rectangle_class, js_filled_rectangle_finalizer); + +/* include the full standard library too */ + +#define CONFIG_CLASS_EXAMPLE +#include "mqjs_stdlib.c" diff --git a/libm.c b/libm.c new file mode 100644 index 00000000..bff8ed1c --- /dev/null +++ b/libm.c @@ -0,0 +1,2260 @@ +/* + * Tiny Math Library + * + * Copyright (c) 2024 Fabrice Bellard + * + * 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. + */ +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +#include +#include +#include +#define NDEBUG +#include + +#include "cutils.h" +#include "libm.h" + +/* define to enable softfloat support */ +//#define USE_SOFTFLOAT +/* use less code for tan() but currently less precise */ +#define USE_TAN_SHORTCUT + +/* + TODO: + - smaller scalbn implementation ? + - add all ES6 math functions +*/ +/* + tc32: + - base: size libm+libgcc: 21368 + - size libm+libgcc: 11832 + + x86: + - size libm softfp: 18510 + - size libm hardfp: 10051 + + TODO: + - unify i32 bit and i64 bit conversions + - unify comparisons operations +*/ + +typedef enum { + RM_RNE, /* Round to Nearest, ties to Even */ + RM_RTZ, /* Round towards Zero */ + RM_RDN, /* Round Down (must be even) */ + RM_RUP, /* Round Up (must be odd) */ + RM_RMM, /* Round to Nearest, ties to Max Magnitude */ + RM_RMMUP, /* only for rint_sf64(): round to nearest, ties to +inf (must be odd) */ +} RoundingModeEnum; + +#define FFLAG_INVALID_OP (1 << 4) +#define FFLAG_DIVIDE_ZERO (1 << 3) +#define FFLAG_OVERFLOW (1 << 2) +#define FFLAG_UNDERFLOW (1 << 1) +#define FFLAG_INEXACT (1 << 0) + +typedef enum { + FMINMAX_PROP, /* min(1, qNaN/sNaN) -> qNaN */ + FMINMAX_IEEE754_2008, /* min(1, qNaN) -> 1, min(1, sNaN) -> qNaN */ + FMINMAX_IEEE754_201X, /* min(1, qNaN/sNaN) -> 1 */ +} SoftFPMinMaxTypeEnum; + +typedef uint32_t sfloat32; +typedef uint64_t sfloat64; + +#define F_STATIC static __maybe_unused +#define F_USE_FFLAGS 0 + +#define F_SIZE 32 +#define F_NORMALIZE_ONLY +#include "softfp_template.h" + +#define F_SIZE 64 +#include "softfp_template.h" + +#ifdef USE_SOFTFLOAT + +/* wrappers */ +double __adddf3(double a, double b) +{ + return uint64_as_float64(add_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +double __subdf3(double a, double b) +{ + return uint64_as_float64(sub_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +double __muldf3(double a, double b) +{ + return uint64_as_float64(mul_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +double __divdf3(double a, double b) +{ + return uint64_as_float64(div_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +/* comparisons */ + +int __eqdf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + return ret; +} + +/* NaN: return 0 */ +int __nedf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + if (unlikely(ret == 2)) + return 0; + else + return ret; +} + +int __ledf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + return ret; +} + +int __ltdf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + return ret; +} + +int __gedf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + if (unlikely(ret == 2)) + return -1; + else + return ret; +} + +int __gtdf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + if (unlikely(ret == 2)) + return -1; + else + return ret; +} + +int __unorddf2(double a, double b) +{ + return isnan_sf64(float64_as_uint64(a)) || + isnan_sf64(float64_as_uint64(b)); +} + +/* conversions */ +double __floatsidf(int32_t a) +{ + return uint64_as_float64(cvt_i32_sf64(a, RM_RNE)); +} + +double __floatdidf(int64_t a) +{ + return uint64_as_float64(cvt_i64_sf64(a, RM_RNE)); +} + +double __floatunsidf(unsigned int a) +{ + return uint64_as_float64(cvt_u32_sf64(a, RM_RNE)); +} + +int32_t __fixdfsi(double a) +{ + return cvt_sf64_i32(float64_as_uint64(a), RM_RTZ); +} + +double __extendsfdf2(float a) +{ + return uint64_as_float64(cvt_sf32_sf64(float_as_uint(a))); +} + +float __truncdfsf2(double a) +{ + return uint_as_float(cvt_sf64_sf32(float64_as_uint64(a), RM_RNE)); +} + +double js_sqrt(double a) +{ + return uint64_as_float64(sqrt_sf64(float64_as_uint64(a), RM_RNE)); +} + +#if defined(__tc32__) +/* XXX: check */ +int __fpclassifyd(double a) +{ + uint64_t u = float64_as_uint64(a); + uint32_t h = u >> 32; + uint32_t l = u; + + h &= 0x7fffffff; + if (h >= 0x7ff00000) { + if (h == 0x7ff00000 && l == 0) + return FP_INFINITE; + else + return FP_NAN; + } else if (h < 0x00100000) { + if (h == 0 && l == 0) + return FP_ZERO; + else + return FP_SUBNORMAL; + } else { + return FP_NORMAL; + } +} +#endif + +#endif /* USE_SOFTFLOAT */ + +int32_t js_lrint(double a) +{ + return cvt_sf64_i32(float64_as_uint64(a), RM_RNE); +} + +double js_fmod(double a, double b) +{ + return uint64_as_float64(fmod_sf64(float64_as_uint64(a), float64_as_uint64(b))); +} + +/* supported rounding modes: RM_UP, RM_DN, RM_RTZ, RM_RMMUP, RM_RMM */ +static double rint_sf64(double a, RoundingModeEnum rm) +{ + uint64_t u = float64_as_uint64(a); + uint64_t frac_mask, one, m, addend; + int e; + unsigned int s; + + e = ((u >> 52) & 0x7ff) - 0x3ff; + s = u >> 63; + if (e < 0) { + m = u & (((uint64_t)1 << 52) - 1); + if (e == -0x3ff && m == 0) { + /* zero: nothing to do */ + } else { + /* abs(a) < 1 */ + s = u >> 63; + one = (uint64_t)0x3ff << 52; + u = 0; + switch(rm) { + case RM_RUP: + case RM_RDN: + if (s ^ (rm & 1)) + u = one; + break; + default: + case RM_RMM: + case RM_RMMUP: + if (e == -1 && (m != 0 || (m == 0 && (!s || rm == RM_RMM)))) + u = one; + break; + case RM_RTZ: + break; + } + u |= (uint64_t)s << 63; + } + } else if (e < 52) { + one = (uint64_t)1 << (52 - e); + frac_mask = one - 1; + addend = 0; + switch(rm) { + case RM_RMMUP: + addend = (one >> 1) - s; + break; + default: + case RM_RMM: + addend = (one >> 1); + break; + case RM_RTZ: + break; + case RM_RUP: + case RM_RDN: + if (s ^ (rm & 1)) + addend = one - 1; + break; + } + u += addend; + u &= ~frac_mask; /* truncate to an integer */ + } + /* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */ + return uint64_as_float64(u); +} + +double js_floor(double x) +{ + return rint_sf64(x, RM_RDN); +} + +double js_ceil(double x) +{ + return rint_sf64(x, RM_RUP); +} + +double js_trunc(double x) +{ + return rint_sf64(x, RM_RTZ); +} + +double js_round_inf(double x) +{ + return rint_sf64(x, RM_RMMUP); +} + +double js_fabs(double x) +{ + uint64_t a = float64_as_uint64(x); + return uint64_as_float64(a & 0x7fffffffffffffff); +} + +/************************************************************/ +/* libm */ + +#define EXTRACT_WORDS(ix0,ix1,d) \ + do { \ + uint64_t __u = float64_as_uint64(d); \ + (ix0) = (uint32_t)(__u >> 32); \ + (ix1) = (uint32_t)__u; \ + } while (0) + +static uint32_t get_high_word(double d) +{ + return float64_as_uint64(d) >> 32; +} + +static double set_high_word(double d, uint32_t h) +{ + uint64_t u = float64_as_uint64(d); + u = (u & 0xffffffff) | ((uint64_t)h << 32); + return uint64_as_float64(u); +} + +static uint32_t get_low_word(double d) +{ + return float64_as_uint64(d); +} + +/* set the low 32 bits to zero */ +static double zero_low(double x) +{ + uint64_t u = float64_as_uint64(x); + u &= 0xffffffff00000000; + return uint64_as_float64(u); +} + +static double float64_from_u32(uint32_t h, uint32_t l) +{ + return uint64_as_float64(((uint64_t)h << 32) | l); +} + +static const double zero = 0.0; +static const double one = 1.0; +static const double half = 5.00000000000000000000e-01; +static const double tiny = 1.0e-300; +static const double huge = 1.0e300; + +/* @(#)s_scalbn.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * scalbn (double x, int n) + * scalbn(x,n) returns x* 2**n computed by exponent + * manipulation rather than by actually performing an + * exponentiation or a multiplication. + */ + +static const double + two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ + twom54 = 5.55111512312578270212e-17; /* 0x3C900000, 0x00000000 */ + +double js_scalbn(double x, int n) +{ + int k,hx,lx; + EXTRACT_WORDS(hx, lx, x); + k = (hx&0x7ff00000)>>20; /* extract exponent */ + if (k==0) { /* 0 or subnormal x */ + if ((lx|(hx&0x7fffffff))==0) return x; /* +-0 */ + x *= two54; + hx = get_high_word(x); + k = ((hx&0x7ff00000)>>20) - 54; + if (n< -50000) return tiny*x; /*underflow*/ + } + if (k==0x7ff) return x+x; /* NaN or Inf */ + k = k+n; + if (k > 0x7fe) return huge*copysign(huge,x); /* overflow */ + if (k > 0) /* normal result */ + {x = set_high_word(x, (hx&0x800fffff)|(k<<20)); return x;} + if (k <= -54) { + if (n > 50000) /* in case integer overflow in n+k */ + return huge*copysign(huge,x); /*overflow*/ + else + return tiny*copysign(tiny,x); /*underflow*/ + } + k += 54; /* subnormal result */ + x = set_high_word(x, (hx&0x800fffff)|(k<<20)); + return x*twom54; +} + +#ifndef USE_SOFTFLOAT +/* @(#)e_sqrt.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_sqrt(x) + * Return correctly rounded sqrt. + * ------------------------------------------ + * | Use the hardware sqrt if you have one | + * ------------------------------------------ + * Method: + * Bit by bit method using integer arithmetic. (Slow, but portable) + * 1. Normalization + * Scale x to y in [1,4) with even powers of 2: + * find an integer k such that 1 <= (y=x*2^(2k)) < 4, then + * sqrt(x) = 2^k * sqrt(y) + * 2. Bit by bit computation + * Let q = sqrt(y) truncated to i bit after binary point (q = 1), + * i 0 + * i+1 2 + * s = 2*q , and y = 2 * ( y - q ). (1) + * i i i i + * + * To compute q from q , one checks whether + * i+1 i + * + * -(i+1) 2 + * (q + 2 ) <= y. (2) + * i + * -(i+1) + * If (2) is false, then q = q ; otherwise q = q + 2 . + * i+1 i i+1 i + * + * With some algebraic manipulation, it is not difficult to see + * that (2) is equivalent to + * -(i+1) + * s + 2 <= y (3) + * i i + * + * The advantage of (3) is that s and y can be computed by + * i i + * the following recurrence formula: + * if (3) is false + * + * s = s , y = y ; (4) + * i+1 i i+1 i + * + * otherwise, + * -i -(i+1) + * s = s + 2 , y = y - s - 2 (5) + * i+1 i i+1 i i + * + * One may easily use induction to prove (4) and (5). + * Note. Since the left hand side of (3) contain only i+2 bits, + * it does not necessary to do a full (53-bit) comparison + * in (3). + * 3. Final rounding + * After generating the 53 bits result, we compute one more bit. + * Together with the remainder, we can decide whether the + * result is exact, bigger than 1/2ulp, or less than 1/2ulp + * (it will never equal to 1/2ulp). + * The rounding mode can be detected by checking whether + * huge + tiny is equal to huge, and whether huge - tiny is + * equal to huge for some floating point number "huge" and "tiny". + * + * Special cases: + * sqrt(+-0) = +-0 ... exact + * sqrt(inf) = inf + * sqrt(-ve) = NaN ... with invalid signal + * sqrt(NaN) = NaN ... with invalid signal for signaling NaN + * + * Other methods : see the appended file at the end of the program below. + *--------------- + */ + +#if defined(__aarch64__) || defined(__x86_64__) || defined(__i386__) +/* hardware sqrt is available */ +double js_sqrt(double x) +{ + return sqrt(x); +} +#else +double js_sqrt(double x) +{ + double z; + int sign = (int)0x80000000; + unsigned r,t1,s1,ix1,q1; + int ix0,s0,q,m,t,i; + + EXTRACT_WORDS(ix0, ix1, x); + + /* take care of Inf and NaN */ + if((ix0&0x7ff00000)==0x7ff00000) { + return x*x+x; /* sqrt(NaN)=NaN, sqrt(+inf)=+inf + sqrt(-inf)=sNaN */ + } + /* take care of zero */ + if(ix0<=0) { + if(((ix0&(~sign))|ix1)==0) return x;/* sqrt(+-0) = +-0 */ + else if(ix0<0) + return (x-x)/(x-x); /* sqrt(-ve) = sNaN */ + } + /* normalize x */ + m = (ix0>>20); + if(m==0) { /* subnormal x */ + while(ix0==0) { + m -= 21; + ix0 |= (ix1>>11); ix1 <<= 21; + } + for(i=0;(ix0&0x00100000)==0;i++) ix0<<=1; + m -= i-1; + ix0 |= (ix1>>(32-i)); + ix1 <<= i; + } + m -= 1023; /* unbias exponent */ + ix0 = (ix0&0x000fffff)|0x00100000; + if(m&1){ /* odd m, double x to make it even */ + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + } + m >>= 1; /* m = [m/2] */ + + /* generate sqrt(x) bit by bit */ + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + q = q1 = s0 = s1 = 0; /* [q,q1] = sqrt(x) */ + r = 0x00200000; /* r = moving bit from right to left */ + + while(r!=0) { + t = s0+r; + if(t<=ix0) { + s0 = t+r; + ix0 -= t; + q += r; + } + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + r>>=1; + } + + r = sign; + while(r!=0) { + t1 = s1+r; + t = s0; + if((t>31); + ix1 += ix1; + r>>=1; + } + + /* use floating add to find out rounding direction */ + if((ix0|ix1)!=0) { + z = one-tiny; /* trigger inexact flag */ + if (z>=one) { + z = one+tiny; + if (q1==(unsigned)0xffffffff) { q1=0; q += 1;} + else if (z>one) { + if (q1==(unsigned)0xfffffffe) q+=1; + q1+=2; + } else + q1 += (q1&1); + } + } + ix0 = (q>>1)+0x3fe00000; + ix1 = q1>>1; + if ((q&1)==1) ix1 |= sign; + ix0 += (m <<20); + return float64_from_u32(ix0, ix1); +} +#endif /* !hardware sqrt */ +#endif /* USE_SOFTFLOAT */ + +/* to have smaller code */ +/* n >= 1 */ +/* return sum(x^i*coefs[i] with i = 0 ... n - 1 and n >= 1 using + Horner algorithm. */ +static double eval_poly(double x, const double *coefs, int n) +{ + double r; + int i; + r = coefs[n - 1]; + for(i = n - 2; i >= 0; i--) { + r = r * x + coefs[i]; + } + return r; +} + +/* @(#)k_sin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __kernel_sin( x, y, iy) + * kernel sin function on [-pi/4, pi/4], pi/4 ~ 0.7854 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * Input iy indicates whether y is 0. (if iy=0, y assume to be 0). + * + * Algorithm + * 1. Since sin(-x) = -sin(x), we need only to consider positive x. + * 2. if x < 2^-27 (hx<0x3e400000 0), return x with inexact if x!=0. + * 3. sin(x) is approximated by a polynomial of degree 13 on + * [0,pi/4] + * 3 13 + * sin(x) ~ x + S1*x + ... + S6*x + * where + * + * |sin(x) 2 4 6 8 10 12 | -58 + * |----- - (1+S1*x +S2*x +S3*x +S4*x +S5*x +S6*x )| <= 2 + * | x | + * + * 4. sin(x+y) = sin(x) + sin'(x')*y + * ~ sin(x) + (1-x*x/2)*y + * For better accuracy, let + * 3 2 2 2 2 + * r = x *(S2+x *(S3+x *(S4+x *(S5+x *S6)))) + * then 3 2 + * sin(x) = x + (S1*x + (x *(r-y/2)+y)) + */ + +static const double +S1 = -1.66666666666666324348e-01; /* 0xBFC55555, 0x55555549 */ +static const double S_tab[] = { + /* S2 */ 8.33333333332248946124e-03, /* 0x3F811111, 0x1110F8A6 */ + /* S3 */ -1.98412698298579493134e-04, /* 0xBF2A01A0, 0x19C161D5 */ + /* S4 */ 2.75573137070700676789e-06, /* 0x3EC71DE3, 0x57B1FE7D */ + /* S5 */ -2.50507602534068634195e-08, /* 0xBE5AE5E6, 0x8A2B9CEB */ + /* S6 */ 1.58969099521155010221e-10, /* 0x3DE5D93A, 0x5ACFD57C */ +}; + +/* iy=0 if y is zero */ +static double __kernel_sin(double x, double y, int iy) +{ + double z,r,v; + int ix; + ix = get_high_word(x)&0x7fffffff; /* high word of x */ + if(ix<0x3e400000) /* |x| < 2**-27 */ + {if((int)x==0) return x;} /* generate inexact */ + z = x*x; + v = z*x; + r = eval_poly(z, S_tab, 5); + if(iy==0) return x+v*(S1+z*r); + else return x-((z*(half*y-v*r)-y)-v*S1); +} + + +/* @(#)k_cos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __kernel_cos( x, y ) + * kernel cos function on [-pi/4, pi/4], pi/4 ~ 0.785398164 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * + * Algorithm + * 1. Since cos(-x) = cos(x), we need only to consider positive x. + * 2. if x < 2^-27 (hx<0x3e400000 0), return 1 with inexact if x!=0. + * 3. cos(x) is approximated by a polynomial of degree 14 on + * [0,pi/4] + * 4 14 + * cos(x) ~ 1 - x*x/2 + C1*x + ... + C6*x + * where the remez error is + * + * | 2 4 6 8 10 12 14 | -58 + * |cos(x)-(1-.5*x +C1*x +C2*x +C3*x +C4*x +C5*x +C6*x )| <= 2 + * | | + * + * 4 6 8 10 12 14 + * 4. let r = C1*x +C2*x +C3*x +C4*x +C5*x +C6*x , then + * cos(x) = 1 - x*x/2 + r + * since cos(x+y) ~ cos(x) - sin(x)*y + * ~ cos(x) - x*y, + * a correction term is necessary in cos(x) and hence + * cos(x+y) = 1 - (x*x/2 - (r - x*y)) + * For better accuracy when x > 0.3, let qx = |x|/4 with + * the last 32 bits mask off, and if x > 0.78125, let qx = 0.28125. + * Then + * cos(x+y) = (1-qx) - ((x*x/2-qx) - (r-x*y)). + * Note that 1-qx and (x*x/2-qx) is EXACT here, and the + * magnitude of the latter is at least a quarter of x*x/2, + * thus, reducing the rounding error in the subtraction. + */ + +static const double C_tab[] = { + /* C1 */ 4.16666666666666019037e-02, /* 0x3FA55555, 0x5555554C */ + /* C2 */ -1.38888888888741095749e-03, /* 0xBF56C16C, 0x16C15177 */ + /* C3 */ 2.48015872894767294178e-05, /* 0x3EFA01A0, 0x19CB1590 */ + /* C4 */ -2.75573143513906633035e-07, /* 0xBE927E4F, 0x809C52AD */ + /* C5 */ 2.08757232129817482790e-09, /* 0x3E21EE9E, 0xBDB4B1C4 */ + /* C6 */ -1.13596475577881948265e-11, /* 0xBDA8FAE9, 0xBE8838D4 */ +}; + +static double __kernel_cos(double x, double y) +{ + double a,hz,z,r,qx; + int ix; + ix = get_high_word(x)&0x7fffffff; /* ix = |x|'s high word*/ + if(ix<0x3e400000) { /* if x < 2**27 */ + if(((int)x)==0) return one; /* generate inexact */ + } + z = x*x; + r = z * eval_poly(z, C_tab, 6); + if(ix < 0x3FD33333) /* if |x| < 0.3 */ + return one - (0.5*z - (z*r - x*y)); + else { + if(ix > 0x3fe90000) { /* x > 0.78125 */ + qx = 0.28125; + } else { + qx = float64_from_u32(ix-0x00200000, 0); /* x/4 */ + } + hz = 0.5*z-qx; + a = one-qx; + return a - (hz - (z*r-x*y)); + } +} + +/* rem_pio2 */ + +#define T_LEN 19 + +/* T[i] = floor(2^(64*(T_LEN - i))/2pi) mod 2^64 */ +static const uint64_t T[T_LEN] = { + 0x1580cc11bf1edaea, + 0x9afed7ec47e35742, + 0xcf41ce7de294a4ba, + 0x5d49eeb1faf97c5e, + 0xd3d18fd9a797fa8b, + 0xdb4d9fb3c9f2c26d, + 0xfbcbc462d6829b47, + 0xc7fe25fff7816603, + 0x272117e2ef7e4a0e, + 0x4e64758e60d4ce7d, + 0x3a671c09ad17df90, + 0xba208d7d4baed121, + 0x3f877ac72c4a69cf, + 0x01924bba82746487, + 0x6dc91b8e909374b8, + 0x7f9458eaf7aef158, + 0x36d8a5664f10e410, + 0x7f09d5f47d4d3770, + 0x28be60db9391054a, /* high part */ +}; + +/* PIO2[i] = floor(2^(64*(2 - i))*PI/4) mod 2^64 */ +static const uint64_t PIO4[2] = { + 0xc4c6628b80dc1cd1, + 0xc90fdaa22168c234, +}; + +static uint64_t get_u64_at_bit(const uint64_t *tab, uint32_t tab_len, + uint32_t pos) +{ + uint64_t v; + uint32_t p = pos / 64; + int shift = pos % 64; + v = tab[p] >> shift; + if (shift != 0 && (p + 1) < tab_len) + v |= tab[p + 1] << (64 - shift); + return v; +} + +/* return n = round(x/(pi/2)) (only low 2 bits are valid) and + (y[0], y[1]) = x - (pi/2) * n. + 'x' must be finite and such as abs(x) >= PI/4. + The initial algorithm comes from the CORE-MATH project. +*/ +static int rem_pio2_large(double x, double *y) +{ + uint64_t m; + int e, sgn, n, rnd, j, i, y_sgn; + uint64_t c[2], d[3]; + uint64_t r0, r1; + uint32_t carry, carry1; + + m = float64_as_uint64(x); + sgn = m >> 63; + e = (m >> 52) & 0x7ff; + /* 1022 <= e <= 2047 */ + m = (m & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + + /* multiply m by T[j:j+192] */ + j = T_LEN * 64 - (e - 1075) - 192; + /* 53 <= j <= 1077 */ + // printf("m=0x%016" PRIx64 " e=%d j=%d\n", m, e, j); + for(i = 0; i < 3; i++) { + d[i] = get_u64_at_bit(T, T_LEN, j + i * 64); + } + r1 = mul_u64(&r0, m, d[0]); + c[0] = r1; + r1 = mul_u64(&r0, m, d[1]); + c[0] += r0; + carry = c[0] < r0; + c[1] = r1 + carry; + mul_u64(&r0, m, d[2]); + c[1] += r0; + + // printf("c0=%016" PRIx64 " %016" PRIx64 "\n", c[1], c[0]); + + /* n = round(c[1]/2^62) */ + n = c[1] >> 62; + rnd = (c[1] >> 61) & 1; + n += rnd; + /* c = c * 4 - n */ + c[1] = (c[1] << 2) | (c[0] >> 62); + c[0] = (c[0] << 2); + y_sgn = sgn; + if (rnd) { + /* 'y' sign change */ + y_sgn ^= 1; + c[0] = ~c[0]; + c[1] = ~c[1]; + if (++c[0] == 0) + c[1]++; + } + // printf("c1=%016" PRIx64 " %016" PRIx64 " n=%d sgn=%d\n", c[1], c[0], n, sgn); + + /* c = c * (PI/2) (high 128 bits of the product) */ + r1 = mul_u64(&r0, c[0], PIO4[1]); + d[0] = r0; + d[1] = r1; + + r1 = mul_u64(&r0, c[1], PIO4[0]); + d[0] += r0; + carry = d[0] < r0; + d[1] += r1; + carry1 = d[1] < r1; + d[1] += carry; + carry1 |= (d[1] < carry); + d[2] = carry1; + + r1 = mul_u64(&r0, c[1], PIO4[1]); + d[1] += r0; + carry = d[1] < r0; + d[2] += r1 + carry; + + /* convert d to two float64 */ + // printf("d=%016" PRIx64 " %016" PRIx64 "\n", d[2], d[1]); + if (d[2] == 0) { + /* should never happen (see ARGUMENT REDUCTION FOR HUGE + ARGUMENTS: Good to the Last Bit, K. C. Ng and the members + of the FP group of SunPro */ + y[0] = y[1] = 0; + } else { + uint64_t m0, m1; + int e1; + + e = clz64(d[2]); + d[2] = (d[2] << e) | (d[1] >> (64 - e)); + d[1] = (d[1] << e); + // printf("d=%016" PRIx64 " %016" PRIx64 " e=%d\n", d[2], d[1], e); + m0 = (d[2] >> 11) & (((uint64_t)1 << 52) - 1); + m1 = ((d[2] & 0x7ff) << 42) | (d[1] >> (64 - 42)); + y[0] = uint64_as_float64(((uint64_t)y_sgn << 63) | + ((uint64_t)(1023 - e) << 52) | + m0); + if (m1 == 0) { + y[1] = 0; + } else { + e1 = clz64(m1) - 11; + m1 = (m1 << e1) & (((uint64_t)1 << 52) - 1); + y[1] = uint64_as_float64(((uint64_t)y_sgn << 63) | + ((uint64_t)(1023 - e - 53 - e1) << 52) | + m1); + } + } + if (sgn) + n = -n; + return n; +} + +#ifdef USE_SOFTFLOAT +/* when using softfloat, the FP reduction should be not much faster + than the generic one */ +int js_rem_pio2(double x, double *y) +{ + int ix,hx; + + hx = get_high_word(x); /* high word of x */ + ix = hx&0x7fffffff; + if(ix<=0x3fe921fb) { + /* |x| ~<= pi/4 , no need for reduction */ + y[0] = x; + y[1] = 0; + return 0; + } + /* + * all other (large) arguments + */ + if(ix>=0x7ff00000) { /* x is inf or NaN */ + y[0]=y[1]=x-x; + return 0; + } + + return rem_pio2_large(x, y); +} +#else +/* + * invpio2: 53 bits of 2/pi + * pio2_1: first 33 bit of pi/2 + * pio2_1t: pi/2 - pio2_1 + * pio2_2: second 33 bit of pi/2 + * pio2_2t: pi/2 - (pio2_1+pio2_2) + * pio2_3: third 33 bit of pi/2 + * pio2_3t: pi/2 - (pio2_1+pio2_2+pio2_3) + */ + +static const double +invpio2 = 6.36619772367581382433e-01; /* 0x3FE45F30, 0x6DC9C883 */ +static const double pio2_tab[3] = { + /* pio2_1 */ 1.57079632673412561417e+00, /* 0x3FF921FB, 0x54400000 */ + /* pio2_2 */ 6.07710050630396597660e-11, /* 0x3DD0B461, 0x1A600000 */ + /* pio2_3 */ 2.02226624871116645580e-21, /* 0x3BA3198A, 0x2E000000 */ +}; +static const double pio2_t_tab[3] = { + /* pio2_1t */ 6.07710050650619224932e-11, /* 0x3DD0B461, 0x1A626331 */ + /* pio2_2t */ 2.02226624879595063154e-21, /* 0x3BA3198A, 0x2E037073 */ + /* pio2_3t */ 8.47842766036889956997e-32, /* 0x397B839A, 0x252049C1 */ +}; +static uint8_t rem_pio2_emax[2] = { 16, 49 }; + +int js_rem_pio2(double x, double *y) +{ + double w,t,r,fn; + int i,j,n,ix,hx,it; + + hx = get_high_word(x); /* high word of x */ + ix = hx&0x7fffffff; + if(ix<=0x3fe921fb) { + /* |x| ~<= pi/4 , no need for reduction */ + y[0] = x; + y[1] = 0; + return 0; + } + if(ix<=0x413921fb) { /* |x| ~<= 2^19*(pi/2), medium size */ + t = fabs(x); + if (ix<0x4002d97c) { + /* |x| < 3pi/4, special case with n=+-1 */ + n = 1; + fn = 1; + } else { + n = (int) (t*invpio2+half); + fn = (double)n; + } + + it = 0; + for(;;) { + /* 1st round good to 85 bit */ + /* 2nd iteration needed, good to 118 */ + /* 3rd iteration need, 151 bits acc */ + r = t-fn*pio2_tab[it]; + w = fn*pio2_t_tab[it]; + y[0] = r-w; + j = ix>>20; + i = j-(((get_high_word(y[0]))>>20)&0x7ff); + if (it == 2 || i <= rem_pio2_emax[it]) + break; + t = r; + it++; + } + y[1] = (r-y[0])-w; + if (hx<0) { + y[0] = -y[0]; + y[1] = -y[1]; + return -n; + } else { + return n; + } + } + /* + * all other (large) arguments + */ + if(ix>=0x7ff00000) { /* x is inf or NaN */ + y[0]=y[1]=x-x; + return 0; + } + + return rem_pio2_large(x, y); +} +#endif /* !USE_SOFTFLOAT */ + +/* @(#)s_sin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* sin(x) + * Return sine function of x. + * + * kernel function: + * __kernel_sin ... sine function on [-pi/4,pi/4] + * __kernel_cos ... cose function on [-pi/4,pi/4] + * __ieee754_rem_pio2 ... argument reduction routine + * + * Method. + * Let S,C and T denote the sin, cos and tan respectively on + * [-PI/4, +PI/4]. Reduce the argument x to y1+y2 = x-k*pi/2 + * in [-pi/4 , +pi/4], and let n = k mod 4. + * We have + * + * n sin(x) cos(x) tan(x) + * ---------------------------------------------------------- + * 0 S C T + * 1 C -S -1/T + * 2 -S -C T + * 3 -C S -1/T + * ---------------------------------------------------------- + * + * Special cases: + * Let trig be any of sin, cos, or tan. + * trig(+-INF) is NaN, with signals; + * trig(NaN) is that NaN; + * + * Accuracy: + * TRIG(x) returns trig(x) nearly rounded + */ + +/* flag = 0: sin() + flag = 1: cos() + flag = 3: tan() +*/ +static double js_sin_cos(double x, int flag) +{ + double y[2], z, s, c; + int ix; + uint32_t n; + + /* High word of x. */ + ix = get_high_word(x); + + /* sin(Inf or NaN) is NaN */ + if (ix>=0x7ff00000) + return x-x; + + n = js_rem_pio2(x,y); + s = c = 0; /* avoid warning */ + if (flag == 3 || (n & 1) == flag) { + s = __kernel_sin(y[0],y[1],1); + if (flag != 3) + goto done; + } + if (flag == 3 || (n & 1) != flag) { + c = __kernel_cos(y[0],y[1]); + if (flag != 3) { + s = c; + goto done; + } + } + if (n & 1) + z = -c / s; + else + z = s / c; + return z; +done: + if ((n + flag) & 2) + s = -s; + return s; +} + +double js_sin(double x) +{ + return js_sin_cos(x, 0); +} + +double js_cos(double x) +{ + return js_sin_cos(x, 1); +} + +#ifdef USE_TAN_SHORTCUT +double js_tan(double x) +{ + return js_sin_cos(x, 3); +} +#endif + +#ifndef USE_TAN_SHORTCUT +/* + * ==================================================== + * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* INDENT OFF */ +/* __kernel_tan( x, y, k ) + * kernel tan function on [-pi/4, pi/4], pi/4 ~ 0.7854 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * Input k indicates whether tan (if k = 1) or -1/tan (if k = -1) is returned. + * + * Algorithm + * 1. Since tan(-x) = -tan(x), we need only to consider positive x. + * 2. if x < 2^-28 (hx<0x3e300000 0), return x with inexact if x!=0. + * 3. tan(x) is approximated by a odd polynomial of degree 27 on + * [0,0.67434] + * 3 27 + * tan(x) ~ x + T1*x + ... + T13*x + * where + * + * |tan(x) 2 4 26 | -59.2 + * |----- - (1+T1*x +T2*x +.... +T13*x )| <= 2 + * | x | + * + * Note: tan(x+y) = tan(x) + tan'(x)*y + * ~ tan(x) + (1+x*x)*y + * Therefore, for better accuracy in computing tan(x+y), let + * 3 2 2 2 2 + * r = x *(T2+x *(T3+x *(...+x *(T12+x *T13)))) + * then + * 3 2 + * tan(x+y) = x + (T1*x + (x *(r+y)+y)) + * + * 4. For x in [0.67434,pi/4], let y = pi/4 - x, then + * tan(x) = tan(pi/4-y) = (1-tan(y))/(1+tan(y)) + * = 1 - 2*(tan(y) - (tan(y)^2)/(1+tan(y))) + */ + +static const double T0 = 3.33333333333334091986e-01; /* 3FD55555, 55555563 */ +static const double T_even[] = { + 5.39682539762260521377e-02, /* 3FABA1BA, 1BB341FE */ + 8.86323982359930005737e-03, /* 3F8226E3, E96E8493 */ + 1.45620945432529025516e-03, /* 3F57DBC8, FEE08315 */ + 2.46463134818469906812e-04, /* 3F3026F7, 1A8D1068 */ + 7.14072491382608190305e-05, /* 3F12B80F, 32F0A7E9 */ + 2.59073051863633712884e-05, /* 3EFB2A70, 74BF7AD4 */ +}; +static const double T_odd[] = { + 1.33333333333201242699e-01, /* 3FC11111, 1110FE7A */ + 2.18694882948595424599e-02, /* 3F9664F4, 8406D637 */ + 3.59207910759131235356e-03, /* 3F6D6D22, C9560328 */ + 5.88041240820264096874e-04, /* 3F4344D8, F2F26501 */ + 7.81794442939557092300e-05, /* 3F147E88, A03792A6 */ + -1.85586374855275456654e-05, /* BEF375CB, DB605373 */ +}; +static const double pio4 = 7.85398163397448278999e-01, /* 3FE921FB, 54442D18 */ + pio4lo = 3.06161699786838301793e-17; /* 3C81A626, 33145C07 */ + +/* compute -1 / (x+y) carefully */ +static double minus_inv(double x, double y) +{ + double a, t, z, v, s, w; + + w = x + y; + z = zero_low(w); + v = y - (z - x); + a = -one / w; + t = zero_low(a); + s = one + t * z; + return t + a * (s + t * v); +} + +static double +__kernel_tan(double x, double y, int iy) { + double z, r, v, w, s; + int ix, hx; + + hx = get_high_word(x); /* high word of x */ + ix = hx & 0x7fffffff; /* high word of |x| */ + if (ix < 0x3e300000) { /* x < 2**-28 */ + if ((int) x == 0) { /* generate inexact */ + if (((ix | get_low_word(x)) | (iy + 1)) == 0) + return one / fabs(x); + else { + if (iy == 1) + return x; + else + return minus_inv(x, y); + } + } + } + if (ix >= 0x3FE59428) { /* |x| >= 0.6744 */ + if (hx < 0) { + x = -x; + y = -y; + } + z = pio4 - x; + w = pio4lo - y; + x = z + w; + y = 0.0; + } + z = x * x; + w = z * z; + /* + * Break x^5*(T[1]+x^2*T[2]+...) into + * x^5(T[1]+x^4*T[3]+...+x^20*T[11]) + + * x^5(x^2*(T[2]+x^4*T[4]+...+x^22*[T12])) + */ + r = eval_poly(w, T_odd, 6); + v = z * eval_poly(w, T_even, 6); + s = z * x; + r = y + z * (s * (r + v) + y); + r += T0 * s; + w = x + r; + if (ix >= 0x3FE59428) { + v = (double) iy; + return (double) (1 - ((hx >> 30) & 2)) * + (v - 2.0 * (x - (w * w / (w + v) - r))); + } + if (iy == 1) { + return w; + } else { + /* + * if allow error up to 2 ulp, simply return + * -1.0 / (x+r) here + */ + return minus_inv(x, r); + } +} + +/* @(#)s_tan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* tan(x) + * Return tangent function of x. + * + * kernel function: + * __kernel_tan ... tangent function on [-pi/4,pi/4] + * __ieee754_rem_pio2 ... argument reduction routine + * + * Method. + * Let S,C and T denote the sin, cos and tan respectively on + * [-PI/4, +PI/4]. Reduce the argument x to y1+y2 = x-k*pi/2 + * in [-pi/4 , +pi/4], and let n = k mod 4. + * We have + * + * n sin(x) cos(x) tan(x) + * ---------------------------------------------------------- + * 0 S C T + * 1 C -S -1/T + * 2 -S -C T + * 3 -C S -1/T + * ---------------------------------------------------------- + * + * Special cases: + * Let trig be any of sin, cos, or tan. + * trig(+-INF) is NaN, with signals; + * trig(NaN) is that NaN; + * + * Accuracy: + * TRIG(x) returns trig(x) nearly rounded + */ + +double js_tan(double x) +{ + double y[2],z=0.0; + int n, ix; + + /* High word of x. */ + ix = get_high_word(x); + + /* |x| ~< pi/4 */ + ix &= 0x7fffffff; + if(ix <= 0x3fe921fb) return __kernel_tan(x,z,1); + + /* tan(Inf or NaN) is NaN */ + else if (ix>=0x7ff00000) return x-x; /* NaN */ + + /* argument reduction needed */ + else { + n = js_rem_pio2(x,y); + return __kernel_tan(y[0],y[1],1-((n&1)<<1)); /* 1 -- n even + -1 -- n odd */ + } +} +#endif + +/* @(#)e_asin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_asin(x) + * Method : + * Since asin(x) = x + x^3/6 + x^5*3/40 + x^7*15/336 + ... + * we approximate asin(x) on [0,0.5] by + * asin(x) = x + x*x^2*R(x^2) + * where + * R(x^2) is a rational approximation of (asin(x)-x)/x^3 + * and its remez error is bounded by + * |(asin(x)-x)/x^3 - R(x^2)| < 2^(-58.75) + * + * For x in [0.5,1] + * asin(x) = pi/2-2*asin(sqrt((1-x)/2)) + * Let y = (1-x), z = y/2, s := sqrt(z), and pio2_hi+pio2_lo=pi/2; + * then for x>0.98 + * asin(x) = pi/2 - 2*(s+s*z*R(z)) + * = pio2_hi - (2*(s+s*z*R(z)) - pio2_lo) + * For x<=0.98, let pio4_hi = pio2_hi/2, then + * f = hi part of s; + * c = sqrt(z) - f = (z-f*f)/(s+f) ...f+c=sqrt(z) + * and + * asin(x) = pi/2 - 2*(s+s*z*R(z)) + * = pio4_hi+(pio4-2s)-(2s*z*R(z)-pio2_lo) + * = pio4_hi+(pio4-2f)-(2s*z*R(z)-(pio2_lo+2c)) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + */ + + +static const double +pio2_hi = 1.57079632679489655800e+00, /* 0x3FF921FB, 0x54442D18 */ + pio2_lo = 6.12323399573676603587e-17, /* 0x3C91A626, 0x33145C07 */ + pio4_hi = 7.85398163397448278999e-01; /* 0x3FE921FB, 0x54442D18 */ +/* coefficient for R(x^2) */ +static const double pS[] = { + /* pS0 */ 1.66666666666666657415e-01, /* 0x3FC55555, 0x55555555 */ + /* pS1 */-3.25565818622400915405e-01, /* 0xBFD4D612, 0x03EB6F7D */ + /* pS2 */ 2.01212532134862925881e-01, /* 0x3FC9C155, 0x0E884455 */ + /* pS3 */ -4.00555345006794114027e-02, /* 0xBFA48228, 0xB5688F3B */ + /* pS4 */ 7.91534994289814532176e-04, /* 0x3F49EFE0, 0x7501B288 */ + /* pS5 */ 3.47933107596021167570e-05, /* 0x3F023DE1, 0x0DFDF709 */ +}; + +static const double qS[] = { + /* qS1 */ -2.40339491173441421878e+00, /* 0xC0033A27, 0x1C8A2D4B */ + /* qS2 */ 2.02094576023350569471e+00, /* 0x40002AE5, 0x9C598AC8 */ + /* qS3 */ -6.88283971605453293030e-01, /* 0xBFE6066C, 0x1B8D0159 */ + /* qS4 */ 7.70381505559019352791e-02, /* 0x3FB3B8C5, 0xB12E9282 */ +}; + +static double R(double t) +{ + double p, q, w; + p = t * eval_poly(t, pS, 6); + q = one + t * eval_poly(t, qS, 4); + w = p/q; + return w; +} + +double js_asin(double x) +{ + double t,w,p,q,c,r,s; + int hx,ix; + hx = get_high_word(x); + ix = hx&0x7fffffff; + if(ix>= 0x3ff00000) { /* |x|>= 1 */ + if(((ix-0x3ff00000)|get_low_word(x))==0) + /* asin(1)=+-pi/2 with inexact */ + return x*pio2_hi+x*pio2_lo; + return (x-x)/(x-x); /* asin(|x|>1) is NaN */ + } else if (ix<0x3fe00000) { /* |x|<0.5 */ + if(ix<0x3e400000) { /* if |x| < 2**-27 */ + if(huge+x>one) return x;/* return x with inexact if x!=0*/ + } else { + t = x*x; + w = R(t); + return x+x*w; + } + } + /* 1> |x|>= 0.5 */ + w = one-fabs(x); + t = w*0.5; + r = R(t); + s = js_sqrt(t); + if(ix>=0x3FEF3333) { /* if |x| > 0.975 */ + w = r; + t = pio2_hi-(2.0*(s+s*w)-pio2_lo); + } else { + w = zero_low(s); + c = (t-w*w)/(s+w); + p = 2.0*s*r-(pio2_lo-2.0*c); + q = pio4_hi-2.0*w; + t = pio4_hi-(p-q); + } + if(hx>0) return t; else return -t; +} + + +/* @(#)e_acos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_acos(x) + * Method : + * acos(x) = pi/2 - asin(x) + * acos(-x) = pi/2 + asin(x) + * For |x|<=0.5 + * acos(x) = pi/2 - (x + x*x^2*R(x^2)) (see asin.c) + * For x>0.5 + * acos(x) = pi/2 - (pi/2 - 2asin(sqrt((1-x)/2))) + * = 2asin(sqrt((1-x)/2)) + * = 2s + 2s*z*R(z) ...z=(1-x)/2, s=sqrt(z) + * = 2f + (2c + 2s*z*R(z)) + * where f=hi part of s, and c = (z-f*f)/(s+f) is the correction term + * for f so that f+c ~ sqrt(z). + * For x<-0.5 + * acos(x) = pi - 2asin(sqrt((1-|x|)/2)) + * = pi - 0.5*(s+s*z*R(z)), where z=(1-|x|)/2,s=sqrt(z) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + * Function needed: sqrt + */ + +static const double +pi = 3.14159265358979311600e+00; /* 0x400921FB, 0x54442D18 */ + +double js_acos(double x) +{ + double z,r,w,s,c,df; + int hx,ix; + hx = get_high_word(x); + ix = hx&0x7fffffff; + if(ix>=0x3ff00000) { /* |x| >= 1 */ + if(((ix-0x3ff00000)|get_low_word(x))==0) { /* |x|==1 */ + if(hx>0) return 0.0; /* acos(1) = 0 */ + else return pi+2.0*pio2_lo; /* acos(-1)= pi */ + } + return (x-x)/(x-x); /* acos(|x|>1) is NaN */ + } + if(ix<0x3fe00000) { /* |x| < 0.5 */ + if(ix<=0x3c600000) return pio2_hi+pio2_lo;/*if|x|<2**-57*/ + z = x*x; + r = R(z); + return pio2_hi - (x - (pio2_lo-x*r)); + } else { + z = (one-fabs(x))*0.5; + r = R(z); + s = js_sqrt(z); + if (hx<0) { /* x < -0.5 */ + w = r*s-pio2_lo; + return pi - 2.0*(s+w); + } else { /* x > 0.5 */ + df = zero_low(s); + c = (z-df*df)/(s+df); + w = r*s+c; + return 2.0*(df+w); + } + } +} + + +/* @(#)s_atan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* atan(x) + * Method + * 1. Reduce x to positive by atan(x) = -atan(-x). + * 2. According to the integer k=4t+0.25 chopped, t=x, the argument + * is further reduced to one of the following intervals and the + * arctangent of t is evaluated by the corresponding formula: + * + * [0,7/16] atan(x) = t-t^3*(a1+t^2*(a2+...(a10+t^2*a11)...) + * [7/16,11/16] atan(x) = atan(1/2) + atan( (t-0.5)/(1+t/2) ) + * [11/16.19/16] atan(x) = atan( 1 ) + atan( (t-1)/(1+t) ) + * [19/16,39/16] atan(x) = atan(3/2) + atan( (t-1.5)/(1+1.5t) ) + * [39/16,INF] atan(x) = atan(INF) + atan( -1/t ) + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double atanhi[] = { + 4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */ + 7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */ + 9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */ + 1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */ +}; + +static const double atanlo[] = { + 2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */ + 3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */ + 1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */ + 6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */ +}; + +static const double aT_even[] = { + 3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */ + 1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */ + 9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */ + 6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */ + 4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */ + 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ +}; +static const double aT_odd[] = { + -1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */ + -1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */ + -7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */ + -5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */ + -3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */ +}; + +double js_atan(double x) +{ + double w,s1,s2,z; + int ix,hx,id; + + hx = get_high_word(x); + ix = hx&0x7fffffff; + if(ix>=0x44100000) { /* if |x| >= 2^66 */ + if(ix>0x7ff00000|| + (ix==0x7ff00000&&(get_low_word(x)!=0))) + return x+x; /* NaN */ + if(hx>0) return atanhi[3]+atanlo[3]; + else return -atanhi[3]-atanlo[3]; + } if (ix < 0x3fdc0000) { /* |x| < 0.4375 */ + if (ix < 0x3e200000) { /* |x| < 2^-29 */ + if(huge+x>one) return x; /* raise inexact */ + } + id = -1; + } else { + x = fabs(x); + if (ix < 0x3ff30000) { /* |x| < 1.1875 */ + if (ix < 0x3fe60000) { /* 7/16 <=|x|<11/16 */ + id = 0; x = (2.0*x-one)/(2.0+x); + } else { /* 11/16<=|x|< 19/16 */ + id = 1; x = (x-one)/(x+one); + } + } else { + if (ix < 0x40038000) { /* |x| < 2.4375 */ + id = 2; x = (x-1.5)/(one+1.5*x); + } else { /* 2.4375 <= |x| < 2^66 */ + id = 3; x = -1.0/x; + } + }} + /* end of argument reduction */ + z = x*x; + w = z*z; + /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ + s1 = z*eval_poly(w, aT_even, 6); + s2 = w*eval_poly(w, aT_odd, 5); + if (id<0) return x - x*(s1+s2); + else { + z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x); + return (hx<0)? -z:z; + } +} + +/* @(#)e_atan2.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_atan2(y,x) + * Method : + * 1. Reduce y to positive by atan2(y,x)=-atan2(-y,x). + * 2. Reduce x to positive by (if x and y are unexceptional): + * ARG (x+iy) = arctan(y/x) ... if x > 0, + * ARG (x+iy) = pi - arctan[y/(-x)] ... if x < 0, + * + * Special cases: + * + * ATAN2((anything), NaN ) is NaN; + * ATAN2(NAN , (anything) ) is NaN; + * ATAN2(+-0, +(anything but NaN)) is +-0 ; + * ATAN2(+-0, -(anything but NaN)) is +-pi ; + * ATAN2(+-(anything but 0 and NaN), 0) is +-pi/2; + * ATAN2(+-(anything but INF and NaN), +INF) is +-0 ; + * ATAN2(+-(anything but INF and NaN), -INF) is +-pi; + * ATAN2(+-INF,+INF ) is +-pi/4 ; + * ATAN2(+-INF,-INF ) is +-3pi/4; + * ATAN2(+-INF, (anything but,0,NaN, and INF)) is +-pi/2; + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +pi_o_4 = 7.8539816339744827900E-01, /* 0x3FE921FB, 0x54442D18 */ +pi_o_2 = 1.5707963267948965580E+00, /* 0x3FF921FB, 0x54442D18 */ +pi_lo = 1.2246467991473531772E-16; /* 0x3CA1A626, 0x33145C07 */ + +double js_atan2(double y, double x) +{ + double z; + int k,m,hx,hy,ix,iy; + unsigned lx,ly; + + EXTRACT_WORDS(hx, lx, x); + EXTRACT_WORDS(hy, ly, y); + ix = hx&0x7fffffff; + iy = hy&0x7fffffff; + if(((ix|((lx|-lx)>>31))>0x7ff00000)|| + ((iy|((ly|-ly)>>31))>0x7ff00000)) /* x or y is NaN */ + return x+y; + if(((hx-0x3ff00000)|lx)==0) + return js_atan(y); /* x=1.0 */ + m = ((hy>>31)&1)|((hx>>30)&2); /* 2*sign(x)+sign(y) */ + + /* when y = 0 */ + if((iy|ly)==0) { + z = 0; + goto done; + } + /* when x = 0 */ + if((ix|lx)==0) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* when x is INF */ + if(ix==0x7ff00000) { + if(iy==0x7ff00000) { + z = pi_o_4; + } else { + z = 0; + } + goto done; + } + /* when y is INF */ + if(iy==0x7ff00000) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* compute y/x */ + k = (iy-ix)>>20; + if(k > 60) { + z=pi_o_2+0.5*pi_lo; /* |y/x| > 2**60 */ + } else if(hx<0&&k<-60) { + z=0.0; /* |y|/x < -2**60 */ + } else { + z=js_atan(fabs(y/x)); /* safe to do y/x */ + } + done: + switch (m) { + case 0: + return z ; /* atan(+,+) */ + case 1: + z = set_high_word(z, get_high_word(z) ^ 0x80000000); + return z ; /* atan(-,+) */ + case 2: + return pi-(z-pi_lo);/* atan(+,-) */ + default: /* case 3 */ + return (z-pi_lo)-pi;/* atan(-,-) */ + } +} + +/* @(#)e_exp.c 1.6 04/04/22 */ +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_exp(x) + * Returns the exponential of x. + * + * Method + * 1. Argument reduction: + * Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658. + * Given x, find r and integer k such that + * + * x = k*ln2 + r, |r| <= 0.5*ln2. + * + * Here r will be represented as r = hi-lo for better + * accuracy. + * + * 2. Approximation of exp(r) by a special rational function on + * the interval [0,0.34658]: + * Write + * R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... + * We use a special Remes algorithm on [0,0.34658] to generate + * a polynomial of degree 5 to approximate R. The maximum error + * of this polynomial approximation is bounded by 2**-59. In + * other words, + * R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5 + * (where z=r*r, and the values of P1 to P5 are listed below) + * and + * | 5 | -59 + * | 2.0+P1*z+...+P5*z - R(z) | <= 2 + * | | + * The computation of exp(r) thus becomes + * 2*r + * exp(r) = 1 + ------- + * R - r + * r*R1(r) + * = 1 + r + ----------- (for better accuracy) + * 2 - R1(r) + * where + * 2 4 10 + * R1(r) = r - (P1*r + P2*r + ... + P5*r ). + * + * 3. Scale back to obtain exp(x): + * From step 1, we have + * exp(x) = 2^k * exp(r) + * + * Special cases: + * exp(INF) is INF, exp(NaN) is NaN; + * exp(-INF) is 0, and + * for finite argument, only exp(0)=1 is exact. + * + * Accuracy: + * according to an error analysis, the error is always less than + * 1 ulp (unit in the last place). + * + * Misc. info. + * For IEEE double + * if x > 7.09782712893383973096e+02 then exp(x) overflow + * if x < -7.45133219101941108420e+02 then exp(x) underflow + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +two = 2.0, +halF[2] = {0.5,-0.5,}, +twom1000= 9.33263618503218878990e-302, /* 2**-1000=0x01700000,0*/ +o_threshold= 7.09782712893383973096e+02, /* 0x40862E42, 0xFEFA39EF */ +u_threshold= -7.45133219101941108420e+02, /* 0xc0874910, 0xD52D3051 */ +ln2HI[2] ={ 6.93147180369123816490e-01, /* 0x3fe62e42, 0xfee00000 */ + -6.93147180369123816490e-01,},/* 0xbfe62e42, 0xfee00000 */ +ln2LO[2] ={ 1.90821492927058770002e-10, /* 0x3dea39ef, 0x35793c76 */ + -1.90821492927058770002e-10,},/* 0xbdea39ef, 0x35793c76 */ + invln2 = 1.44269504088896338700e+00; /* 0x3ff71547, 0x652b82fe */ +static const double P[] = { + /* P1 */ 1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */ + /* P2 */ -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */ + /* P3 */ 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */ + /* P4 */ -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */ + /* P5 */ 4.13813679705723846039e-08, /* 0x3E663769, 0x72BEA4D0 */ +}; + +/* compute exp(z+w)*2^n */ +static double kernel_exp(double z, double w, double lo, double hi, int n) +{ + int j; + double t, t1, r; + t = z*z; + t1 = z - t*eval_poly(t, P, 5); + r = (z*t1)/(t1-two) - (w+z*w); + z = one-((lo + r)-hi); + j = get_high_word(z); + j += (n<<20); + if((j>>20)<=0) + z = js_scalbn(z,n); /* subnormal output */ + else + z = set_high_word(z, get_high_word(z) + (n<<20)); + return z; +} + +double js_exp(double x) +{ + double hi,lo,t; + int k,xsb; + unsigned hx; + + hx = get_high_word(x); /* high word of x */ + xsb = (hx>>31)&1; /* sign bit of x */ + hx &= 0x7fffffff; /* high word of |x| */ + + /* filter out non-finite argument */ + if(hx >= 0x40862E42) { /* if |x|>=709.78... */ + if(hx>=0x7ff00000) { + if(((hx&0xfffff)|get_low_word(x))!=0) + return x+x; /* NaN */ + else return (xsb==0)? x:0.0; /* exp(+-inf)={inf,0} */ + } + if(x > o_threshold) return huge*huge; /* overflow */ + if(x < u_threshold) return twom1000*twom1000; /* underflow */ + } + + /* argument reduction */ + if(hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ + if(hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ + hi = x-ln2HI[xsb]; lo=ln2LO[xsb]; k = 1-xsb-xsb; + } else { + k = (int)(invln2*x+halF[xsb]); + t = k; + hi = x - t*ln2HI[0]; /* t*ln2HI is exact here */ + lo = t*ln2LO[0]; + } + x = hi - lo; + } + else if(hx < 0x3e300000) { /* when |x|<2**-28 */ + if(huge+x>one) return one+x;/* trigger inexact */ + k = 0; /* avoid warning */ + } + else k = 0; + + /* x is now in primary range */ + if (k == 0) { + lo = 0; + hi = x; + } + return kernel_exp(x, 0, lo, hi, k); +} + + +/* @(#)e_pow.c 1.5 04/04/22 SMI */ +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_pow(x,y) return x**y + * + * n + * Method: Let x = 2 * (1+f) + * 1. Compute and return log2(x) in two pieces: + * log2(x) = w1 + w2, + * where w1 has 53-24 = 29 bit trailing zeros. + * 2. Perform y*log2(x) = n+y' by simulating multi-precision + * arithmetic, where |y'|<=0.5. + * 3. Return x**y = 2**n*exp(y'*log2) + * + * Special cases: + * 1. (anything) ** 0 is 1 + * 2. (anything) ** 1 is itself + * 3. (anything) ** NAN is NAN + * 4. NAN ** (anything except 0) is NAN + * 5. +-(|x| > 1) ** +INF is +INF + * 6. +-(|x| > 1) ** -INF is +0 + * 7. +-(|x| < 1) ** +INF is +0 + * 8. +-(|x| < 1) ** -INF is +INF + * 9. +-1 ** +-INF is NAN + * 10. +0 ** (+anything except 0, NAN) is +0 + * 11. -0 ** (+anything except 0, NAN, odd integer) is +0 + * 12. +0 ** (-anything except 0, NAN) is +INF + * 13. -0 ** (-anything except 0, NAN, odd integer) is +INF + * 14. -0 ** (odd integer) = -( +0 ** (odd integer) ) + * 15. +INF ** (+anything except 0,NAN) is +INF + * 16. +INF ** (-anything except 0,NAN) is +0 + * 17. -INF ** (anything) = -0 ** (-anything) + * 18. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer) + * 19. (-anything except 0 and inf) ** (non-integer) is NAN + * + * Accuracy: + * pow(x,y) returns x**y nearly rounded. In particular + * pow(integer,integer) + * always returns the correct integer provided it is + * representable. + * + * Constants : + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +bp[] = {1.0, 1.5,}, +dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */ +dp_l[] = { 0.0, 1.35003920212974897128e-08,}, /* 0x3E4CFDEB, 0x43CFD006 */ +two53 = 9007199254740992.0, /* 0x43400000, 0x00000000 */ + /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ +lg2 = 6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */ +lg2_h = 6.93147182464599609375e-01, /* 0x3FE62E43, 0x00000000 */ +lg2_l = -1.90465429995776804525e-09, /* 0xBE205C61, 0x0CA86C39 */ +ovt = 8.0085662595372944372e-0017, /* -(1024-log2(ovfl+.5ulp)) */ +cp = 9.61796693925975554329e-01, /* 0x3FEEC709, 0xDC3A03FD =2/(3ln2) */ +cp_h = 9.61796700954437255859e-01, /* 0x3FEEC709, 0xE0000000 =(float)cp */ +cp_l = -7.02846165095275826516e-09, /* 0xBE3E2FE0, 0x145B01F5 =tail of cp_h*/ +ivln2 = 1.44269504088896338700e+00, /* 0x3FF71547, 0x652B82FE =1/ln2 */ +ivln2_h = 1.44269502162933349609e+00, /* 0x3FF71547, 0x60000000 =24b 1/ln2*/ +ivln2_l = 1.92596299112661746887e-08, /* 0x3E54AE0B, 0xF85DDF44 =1/ln2 tail*/ +ivlg10b2 = 0.3010299956639812, /* 0x3fd34413509f79ff 1/log2(10) */ +ivlg10b2_h = 0.30102992057800293, /* 0x3fd3441300000000 1/log2(10) high */ +ivlg10b2_l = 7.508597826552624e-8; /* 0x3e7427de7fbcc47c 1/log2(10) low */ + +static const double L_tab[] = { + /* L1 */ 5.99999999999994648725e-01, /* 0x3FE33333, 0x33333303 */ + /* L2 */ 4.28571428578550184252e-01, /* 0x3FDB6DB6, 0xDB6FABFF */ + /* L3 */ 3.33333329818377432918e-01, /* 0x3FD55555, 0x518F264D */ + /* L4 */ 2.72728123808534006489e-01, /* 0x3FD17460, 0xA91D4101 */ + /* L5 */ 2.30660745775561754067e-01, /* 0x3FCD864A, 0x93C9DB65 */ + /* L6 */ 2.06975017800338417784e-01, /* 0x3FCA7E28, 0x4A454EEF */ +}; + +/* compute (t1, t2) = log2(ax). is_small_ax is true if abs(ax)<= 2**-20 */ +static void kernel_log2(double *pt1, double *pt2, double ax) +{ + double t, u, v, t1, t2, r; + int n, j, ix, k; + double ss, s2, s_h, s_l, t_h, t_l, p_l, p_h, z_h, z_l; + + n = 0; + ix = get_high_word(ax); + /* take care subnormal number */ + if(ix<0x00100000) + {ax *= two53; n -= 53; ix = get_high_word(ax); } + n += ((ix)>>20)-0x3ff; + j = ix&0x000fffff; + /* determine interval */ + ix = j|0x3ff00000; /* normalize ix */ + if(j<=0x3988E) k=0; /* |x|>1)|0x20000000)+0x00080000+(k<<18)); + t_l = ax - (t_h-bp[k]); + s_l = v*((u-s_h*t_h)-s_h*t_l); + /* compute log(ax) */ + s2 = ss*ss; + r = s2*s2*eval_poly(s2, L_tab, 6); + r += s_l*(s_h+ss); + s2 = s_h*s_h; + t_h = zero_low(3.0+s2+r); + t_l = r-((t_h-3.0)-s2); + /* u+v = ss*(1+...) */ + u = s_h*t_h; + v = s_l*t_h+t_l*ss; + /* 2/(3log2)*(ss+...) */ + p_h = zero_low(u+v); + p_l = v-(p_h-u); + z_h = cp_h*p_h; /* cp_h+cp_l = 2/(3*log2) */ + z_l = cp_l*p_h+p_l*cp+dp_l[k]; + /* log2(ax) = (ss+..)*2/(3*log2) = n + dp_h + z_h + z_l */ + t = (double)n; + t1 = zero_low(((z_h+z_l)+dp_h[k])+t); + t2 = z_l-(((t1-t)-dp_h[k])-z_h); + + *pt1 = t1; + *pt2 = t2; +} + +/* flag = 0: log2() + flag = 1: log() + flag = 2: log10() +*/ +static double js_log_internal(double x, int flag) +{ + double p_h, p_l, t, u, v; + int hx, lx; + + EXTRACT_WORDS(hx, lx, x); + if (hx <= 0) { + if (((hx&0x7fffffff)|lx)==0) + return -INFINITY; /* log(+-0)=-inf */ + if (hx<0) + return NAN; /* log(-#) = NaN */ + } else if (hx >= 0x7ff00000) { + /* log(inf) = inf, log(nan) = nan */ + return x+x; + } + kernel_log2(&p_h, &p_l, x); + + t = p_h + p_l; + if (flag == 0) { + return t; + } else { + t = zero_low(t); + if (flag == 1) { + /* multiply (p_l+p_h) by lg2 */ + u = t*lg2_h; + v = (p_l-(t-p_h))*lg2+t*lg2_l; + } else { + /* mutiply (p_l+p_h) by 1/log2(10) */ + u = t*ivlg10b2_h; + v = (p_l-(t-p_h))*ivlg10b2+t*ivlg10b2_l; + } + return u+v; + } +} + +double js_log2(double x) +{ + return js_log_internal(x, 0); +} + +double js_log(double x) +{ + return js_log_internal(x, 1); +} + +double js_log10(double x) +{ + return js_log_internal(x, 2); +} + +double js_pow(double x, double y) +{ + double z,ax,p_h,p_l; + double y1,t1,t2,s,t,u,v,w; + int i,j,k,yisint,n; + int hx,hy,ix,iy; + unsigned lx,ly; + + EXTRACT_WORDS(hx, lx, x); + EXTRACT_WORDS(hy, ly, y); + ix = hx&0x7fffffff; iy = hy&0x7fffffff; + + /* y==zero: x**0 = 1 */ + if((iy|ly)==0) return one; + + /* +-NaN return x+y */ + if(ix > 0x7ff00000 || ((ix==0x7ff00000)&&(lx!=0)) || + iy > 0x7ff00000 || ((iy==0x7ff00000)&&(ly!=0))) + return x+y; + + /* determine if y is an odd int when x < 0 + * yisint = 0 ... y is not an integer + * yisint = 1 ... y is an odd int + * yisint = 2 ... y is an even int + */ + yisint = 0; + if(hx<0) { + if(iy>=0x43400000) yisint = 2; /* even integer y */ + else if(iy>=0x3ff00000) { + k = (iy>>20)-0x3ff; /* exponent */ + if(k>20) { + j = ly>>(52-k); + if((j<<(52-k))==ly) yisint = 2-(j&1); + } else if(ly==0) { + j = iy>>(20-k); + if((j<<(20-k))==iy) yisint = 2-(j&1); + } + } + } + + /* special value of y */ + if(ly==0) { + if (iy==0x7ff00000) { /* y is +-inf */ + if(((ix-0x3ff00000)|lx)==0) + return y - y; /* inf**+-1 is NaN */ + else if (ix >= 0x3ff00000)/* (|x|>1)**+-inf = inf,0 */ + return (hy>=0)? y: zero; + else /* (|x|<1)**-,+inf = inf,0 */ + return (hy<0)?-y: zero; + } + if(iy==0x3ff00000) { /* y is +-1 */ + if(hy<0) return one/x; else return x; + } + if(hy==0x40000000) return x*x; /* y is 2 */ + if(hy==0x3fe00000) { /* y is 0.5 */ + if(hx>=0) /* x >= +0 */ + return js_sqrt(x); + } + } + + ax = fabs(x); + /* special value of x */ + if(lx==0) { + if(ix==0x7ff00000||ix==0||ix==0x3ff00000){ + z = ax; /*x is +-0,+-inf,+-1*/ + if(hy<0) z = one/z; /* z = (1/|x|) */ + if(hx<0) { + if(((ix-0x3ff00000)|yisint)==0) { + z = (z-z)/(z-z); /* (-1)**non-int is NaN */ + } else if(yisint==1) + z = -z; /* (x<0)**odd = -(|x|**odd) */ + } + return z; + } + } + + n = (hx>>31)+1; + + /* (x<0)**(non-int) is NaN */ + if((n|yisint)==0) return (x-x)/(x-x); + + s = one; /* s (sign of result -ve**odd) = -1 else = 1 */ + if((n|(yisint-1))==0) s = -one;/* (-ve)**(odd int) */ + + /* |y| is huge */ + if(iy>0x41e00000) { /* if |y| > 2**31 */ + if(iy>0x43f00000){ /* if |y| > 2**64, must o/uflow */ + if(ix<=0x3fefffff) return (hy<0)? huge*huge:tiny*tiny; + if(ix>=0x3ff00000) return (hy>0)? huge*huge:tiny*tiny; + } + /* over/underflow if x is not close to one */ + if(ix<0x3fefffff) return (hy<0)? s*huge*huge:s*tiny*tiny; + if(ix>0x3ff00000) return (hy>0)? s*huge*huge:s*tiny*tiny; + t = ax-one; /* t has 20 trailing zeros */ + w = (t*t)*(0.5-t*(0.3333333333333333333333-t*0.25)); + u = ivln2_h*t; /* ivln2_h has 21 sig. bits */ + v = t*ivln2_l-w*ivln2; + t1 = zero_low(u+v); + t2 = v-(t1-u); + } else { + kernel_log2(&t1, &t2, ax); + } + /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */ + y1 = zero_low(y); + p_l = (y-y1)*t1+y*t2; + p_h = y1*t1; + z = p_l+p_h; + EXTRACT_WORDS(j, i, z); + if (j>=0x40900000) { /* z >= 1024 */ + if(((j-0x40900000)|i)!=0) /* if z > 1024 */ + return s*huge*huge; /* overflow */ + else { + if(p_l+ovt>z-p_h) return s*huge*huge; /* overflow */ + } + } else if((j&0x7fffffff)>=0x4090cc00 ) { /* z <= -1075 */ + if(((j-0xc090cc00)|i)!=0) /* z < -1075 */ + return s*tiny*tiny; /* underflow */ + else { + if(p_l<=z-p_h) return s*tiny*tiny; /* underflow */ + } + } + /* + * compute 2**(p_h+p_l) + */ + i = j&0x7fffffff; + k = (i>>20)-0x3ff; + n = 0; + if(i>0x3fe00000) { /* if |z| > 0.5, set n = [z+0.5] */ + n = j+(0x00100000>>(k+1)); + k = ((n&0x7fffffff)>>20)-0x3ff; /* new k for n */ + t = zero; + t = set_high_word(t, n&~(0x000fffff>>k)); + n = ((n&0x000fffff)|0x00100000)>>(20-k); + if(j<0) n = -n; + p_h -= t; + } + /* multiply (p_l+p_h) by lg2 */ + t = zero_low(p_l+p_h); + u = t*lg2_h; + v = (p_l-(t-p_h))*lg2+t*lg2_l; + z = u+v; + w = v-(z-u); + return s * kernel_exp(z, w, 0, z, n); +} + diff --git a/libm.h b/libm.h new file mode 100644 index 00000000..17e94e0e --- /dev/null +++ b/libm.h @@ -0,0 +1,46 @@ +/* + * Tiny Math Library + * + * Copyright (c) 2024 Fabrice Bellard + * + * 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. + */ +double js_scalbn(double x, int n); +double js_floor(double x); +double js_ceil(double x); +double js_trunc(double x); +double js_round_inf(double a); +double js_fabs(double x); +double js_sqrt(double x); +int32_t js_lrint(double a); +double js_fmod(double x, double y); +double js_sin(double x); +double js_cos(double x); +double js_tan(double x); +double js_acos(double x); +double js_asin(double x); +double js_atan(double x); +double js_atan2(double y, double x); +double js_exp(double x); +double js_log(double x); +double js_log2(double x); +double js_log10(double x); +double js_pow(double x, double y); +/* exported only for tests */ +int js_rem_pio2(double x, double *y); diff --git a/list.h b/list.h new file mode 100644 index 00000000..5c182340 --- /dev/null +++ b/list.h @@ -0,0 +1,99 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * 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 LIST_H +#define LIST_H + +#ifndef NULL +#include +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) container_of(el, type, member) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#endif /* LIST_H */ diff --git a/mqjs.c b/mqjs.c new file mode 100644 index 00000000..46ad9536 --- /dev/null +++ b/mqjs.c @@ -0,0 +1,774 @@ +/* + * Micro QuickJS REPL + * + * 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 "cutils.h" +#include "readline_tty.h" +#include "mquickjs.h" + +static uint8_t *load_file(const char *filename, int *plen); +static void dump_error(JSContext *ctx); + +static 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(' '); + v = argv[i]; + if (JS_IsString(ctx, v)) { + JSCStringBuf buf; + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v, &buf); + fwrite(str, 1, len, stdout); + } else { + JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); + } + } + putchar('\n'); + return JS_UNDEFINED; +} + +static 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) +{ + 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) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} +#endif + +static 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) +{ + return JS_NewInt64(ctx, get_time_ms()); +} + +/* load a script */ +static 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; + buf = load_file(filename, &buf_len); + + ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + 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) +{ + 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])) + return JS_EXCEPTION; + for(i = 0; i < MAX_TIMERS; i++) { + th = &js_timer_list[i]; + if (!th->allocated) { + pfunc = JS_AddGCRef(ctx, &th->func); + *pfunc = argv[0]; + th->timeout = get_time_ms() + delay; + th->allocated = TRUE; + return JS_NewInt32(ctx, i); + } + } + return JS_ThrowInternalError(ctx, "too many timers"); +} + +static JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + int timer_id; + JSTimer *th; + + if (JS_ToInt32(ctx, &timer_id, argv[0])) + return JS_EXCEPTION; + if (timer_id >= 0 && timer_id < MAX_TIMERS) { + th = &js_timer_list[timer_id]; + if (th->allocated) { + JS_DeleteGCRef(ctx, &th->func); + th->allocated = FALSE; + } + } + return JS_UNDEFINED; +} + +static void run_timers(JSContext *ctx) +{ + int64_t min_delay, delay, cur_time; + BOOL has_timer; + int i; + JSTimer *th; + struct timespec ts; + + for(;;) { + min_delay = 1000; + cur_time = get_time_ms(); + has_timer = FALSE; + for(i = 0; i < MAX_TIMERS; i++) { + th = &js_timer_list[i]; + if (th->allocated) { + has_timer = TRUE; + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue ret; + /* the timer expired */ + if (JS_StackCheck(ctx, 2)) + 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: + dump_error(ctx); + exit(1); + } + min_delay = 0; + break; + } else if (delay < min_delay) { + min_delay = delay; + } + } + } + if (!has_timer) + break; + if (min_delay > 0) { + ts.tv_sec = min_delay / 1000; + ts.tv_nsec = (min_delay % 1000) * 1000000; + nanosleep(&ts, NULL); + } + } +} + +#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) +{ + fwrite(buf, 1, buf_len, js_log_err_flag ? stderr : stdout); +} + +static void dump_error(JSContext *ctx) +{ + JSValue obj; + obj = JS_GetException(ctx); + fprintf(stderr, "%s", term_colors[STYLE_ERROR_MSG]); + js_log_err_flag++; + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + js_log_err_flag--; + fprintf(stderr, "%s\n", term_colors[COLOR_NONE]); +} + +static int eval_buf(JSContext *ctx, const char *eval_str, const char *filename, BOOL is_repl, int parse_flags) +{ + JSValue val; + int flags; + + flags = parse_flags; + if (is_repl) + flags |= JS_EVAL_RETVAL | JS_EVAL_REPL; + val = JS_Parse(ctx, eval_str, strlen(eval_str), filename, flags); + if (JS_IsException(val)) + goto exception; + + val = JS_Run(ctx, val); + if (JS_IsException(val)) { + exception: + dump_error(ctx); + return 1; + } else { + if (is_repl) { + printf("%s", term_colors[STYLE_RESULT]); + JS_PrintValueF(ctx, val, JS_DUMP_LONG); + printf("%s\n", term_colors[COLOR_NONE]); + } + return 0; + } +} + +static int eval_file(JSContext *ctx, const char *filename, + int argc, const char **argv, int parse_flags, + BOOL allow_bytecode) +{ + uint8_t *buf; + int ret, buf_len; + JSValue val; + + buf = load_file(filename, &buf_len); + if (allow_bytecode && JS_IsBytecode(buf, buf_len)) { + if (JS_RelocateBytecode(ctx, buf, buf_len)) { + fprintf(stderr, "Could not relocate bytecode\n"); + exit(1); + } + val = JS_LoadBytecode(ctx, buf); + } else { + val = JS_Parse(ctx, (char *)buf, buf_len, filename, parse_flags); + } + if (JS_IsException(val)) + goto exception; + + if (argc > 0) { + JSValue obj, arr; + JSGCRef arr_ref, val_ref; + int i; + + JS_PUSH_VALUE(ctx, val); + /* must be defined after JS_LoadBytecode() */ + arr = JS_NewArray(ctx, argc); + JS_PUSH_VALUE(ctx, arr); + for(i = 0; i < argc; i++) { + JS_SetPropertyUint32(ctx, arr_ref.val, i, + JS_NewString(ctx, argv[i])); + } + JS_POP_VALUE(ctx, arr); + obj = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(ctx, obj, "scriptArgs", arr); + JS_POP_VALUE(ctx, val); + } + + + val = JS_Run(ctx, val); + if (JS_IsException(val)) { + exception: + dump_error(ctx); + ret = 1; + } else { + ret = 0; + } + free(buf); + return ret; +} + +static void compile_file(const char *filename, const char *outfilename, + size_t mem_size, int dump_memory, int parse_flags, BOOL force_32bit) +{ + 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; + FILE *f; + + /* When compiling to a file, the actual content of the stdlib does + not matter because the generated bytecode does not depend on + it. We still need it so that the atoms for the parsing are + defined. The JSContext must be discarded once the compilation + is done. */ + 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); + + val = JS_Parse(ctx, eval_str, strlen(eval_str), filename, parse_flags); + free(eval_str); + if (JS_IsException(val)) { + dump_error(ctx); + return; + } + +#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); + + if (dump_memory) + JS_DumpMemory(ctx, (dump_memory >= 2)); + + /* Relocate to zero to have a deterministic + output. JS_DumpMemory() cannot work once the heap is relocated, + so we relocate after it. */ + JS_RelocateBytecode2(ctx, &hdr_buf.hdr, (uint8_t *)data_buf, data_len, 0, FALSE); + hdr_len = sizeof(JSBytecodeHeader); + } + f = fopen(outfilename, "wb"); + if (!f) { + perror(outfilename); + exit(1); + } + fwrite(&hdr_buf, 1, hdr_len, f); + fwrite(data_buf, 1, data_len, f); + fclose(f); + + JS_FreeContext(ctx); + free(mem_buf); +} + +/* repl */ + +static ReadlineState readline_state; +static uint8_t readline_cmd_buf[256]; +static uint8_t readline_kill_buf[256]; +static char readline_history[512]; + +void readline_find_completion(const char *cmdline) +{ +} + +static BOOL is_word(int c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + c == '_' || c == '$'; +} + +static const char js_keywords[] = + "break|case|catch|continue|debugger|default|delete|do|" + "else|finally|for|function|if|in|instanceof|new|" + "return|switch|this|throw|try|typeof|while|with|" + "class|const|enum|import|export|extends|super|" + "implements|interface|let|package|private|protected|" + "public|static|yield|" + "undefined|null|true|false|Infinity|NaN|" + "eval|arguments|" + "await|"; + +static const char js_types[] = "void|var|"; + +static BOOL find_keyword(const char *buf, size_t buf_len, const char *dict) +{ + const char *r, *p = dict; + while (*p != '\0') { + r = strchr(p, '|'); + if (!r) + break; + if ((r - p) == buf_len && !memcmp(buf, p, buf_len)) + return TRUE; + p = r + 1; + } + return FALSE; +} + +/* return the color for the character at position 'pos' and the number + of characters of the same color */ +static int term_get_color(int *plen, const char *buf, int pos, int buf_len) +{ + int c, color, pos1, len; + + c = buf[pos]; + if (c == '"' || c == '\'') { + pos1 = pos + 1; + for(;;) { + if (buf[pos1] == '\0' || buf[pos1] == c) + break; + if (buf[pos1] == '\\' && buf[pos1 + 1] != '\0') + pos1 += 2; + else + pos1++; + } + if (buf[pos1] != '\0') + pos1++; + len = pos1 - pos; + color = STYLE_STRING; + } else if (c == '/' && buf[pos + 1] == '*') { + pos1 = pos + 2; + while (buf[pos1] != '\0' && + !(buf[pos1] == '*' && buf[pos1 + 1] == '/')) { + pos1++; + } + if (buf[pos1] != '\0') + pos1 += 2; + len = pos1 - pos; + color = STYLE_COMMENT; + } else if ((c >= '0' && c <= '9') || c == '.') { + pos1 = pos + 1; + while (is_word(buf[pos1])) + pos1++; + len = pos1 - pos; + color = STYLE_NUMBER; + } else if (is_word(c)) { + pos1 = pos + 1; + while (is_word(buf[pos1])) + pos1++; + len = pos1 - pos; + if (find_keyword(buf + pos, len, js_keywords)) { + color = STYLE_KEYWORD; + } else { + while (buf[pos1] == ' ') + pos1++; + if (buf[pos1] == '(') { + color = STYLE_FUNCTION; + } else { + if (find_keyword(buf + pos, len, js_types)) { + color = STYLE_TYPE; + } else { + color = STYLE_IDENTIFIER; + } + } + } + } else { + color = STYLE_DEFAULT; + len = 1; + } + *plen = len; + return color; +} + +static int js_interrupt_handler(JSContext *ctx, void *opaque) +{ + return readline_is_interrupted(); +} + +static void repl_run(JSContext *ctx) +{ + ReadlineState *s = &readline_state; + const char *cmd; + + s->term_width = readline_tty_init(); + s->term_cmd_buf = readline_cmd_buf; + s->term_kill_buf = readline_kill_buf; + s->term_cmd_buf_size = sizeof(readline_cmd_buf); + s->term_history = readline_history; + s->term_history_buf_size = sizeof(readline_history); + s->get_color = term_get_color; + + JS_SetInterruptHandler(ctx, js_interrupt_handler); + + for(;;) { + cmd = readline_tty(&readline_state, "mqjs > ", FALSE); + if (!cmd) + break; + eval_buf(ctx, cmd, "", TRUE, 0); + run_timers(ctx); + } +} + +static void help(void) +{ + printf("MicroQuickJS" "\n" + "usage: mqjs [options] [file [args]]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-i --interactive go to interactive mode\n" + "-I --include file include an additional file\n" + "-d --dump dump the memory usage stats\n" + " --memory-limit n limit the memory usage to 'n' bytes\n" + "--no-column no column number in debug information\n" + "-o FILE save the bytecode to FILE\n" + "-m32 force 32 bit bytecode output (use with -o)\n" + "-b --allow-bytecode allow bytecode in input file\n"); + exit(1); +} + +int main(int argc, const char **argv) +{ + int optind; + size_t mem_size; + int dump_memory = 0; + int interactive = 0; + const char *expr = NULL; + const char *out_filename = NULL; + const char *include_list[32]; + int include_count = 0; + uint8_t *mem_buf; + JSContext *ctx; + int i, parse_flags; + BOOL force_32bit, allow_bytecode; + + mem_size = 16 << 20; + dump_memory = 0; + parse_flags = 0; + force_32bit = FALSE; + allow_bytecode = FALSE; + + /* cannot use getopt because we want to pass the command line to + the script */ + optind = 1; + while (optind < argc && *argv[optind] == '-') { + const char *arg = argv[optind] + 1; + const char *longopt = ""; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + 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 (!strcmp(longopt, "memory-limit")) { + char *p; + double count; + if (optind >= argc) { + fprintf(stderr, "expecting memory limit"); + exit(1); + } + count = strtod(argv[optind++], &p); + switch (tolower((unsigned char)*p)) { + case 'g': + count *= 1024; + /* fall thru */ + case 'm': + count *= 1024; + /* fall thru */ + case 'k': + count *= 1024; + /* fall thru */ + default: + mem_size = (size_t)(count); + break; + } + continue; + } + if (opt == 'd' || !strcmp(longopt, "dump")) { + dump_memory++; + continue; + } + if (opt == 'i' || !strcmp(longopt, "interactive")) { + interactive++; + continue; + } + 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 == 'I' || !strcmp(longopt, "include")) { + if (optind >= argc) { + fprintf(stderr, "expecting filename"); + exit(1); + } + if (include_count >= countof(include_list)) { + fprintf(stderr, "too many included files"); + exit(1); + } + include_list[include_count++] = argv[optind++]; + continue; + } + if (!strcmp(longopt, "no-column")) { + parse_flags |= JS_EVAL_STRIP_COL; + continue; + } + if (opt == 'm' && !strcmp(arg, "32")) { + /* XXX: using a long option is not consistent here */ + force_32bit = TRUE; + arg += strlen(arg); + continue; + } + if (opt == 'b' || !strcmp(longopt, "allow-bytecode")) { + allow_bytecode = TRUE; + continue; + } + if (opt) { + fprintf(stderr, "qjs: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); + } + help(); + } + } + + if (out_filename) { + if (optind >= argc) { + fprintf(stderr, "expecting input filename\n"); + exit(1); + } + compile_file(argv[optind], out_filename, mem_size, dump_memory, + parse_flags, force_32bit); + } else { + mem_buf = malloc(mem_size); + ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); + JS_SetLogFunc(ctx, js_log_func); + { + struct timeval tv; + gettimeofday(&tv, NULL); + JS_SetRandomSeed(ctx, ((uint64_t)tv.tv_sec << 32) ^ tv.tv_usec); + } + + for(i = 0; i < include_count; i++) { + if (eval_file(ctx, include_list[i], 0, NULL, + parse_flags, allow_bytecode)) { + goto fail; + } + } + + if (expr) { + if (eval_buf(ctx, expr, "", FALSE, parse_flags | JS_EVAL_REPL)) + goto fail; + } else if (optind >= argc) { + interactive = 1; + } else { + if (eval_file(ctx, argv[optind], argc - optind, argv + optind, + parse_flags, allow_bytecode)) { + goto fail; + } + } + + if (interactive) { + repl_run(ctx); + } else { + run_timers(ctx); + } + + if (dump_memory) + JS_DumpMemory(ctx, (dump_memory >= 2)); + + JS_FreeContext(ctx); + free(mem_buf); + } + return 0; + fail: + JS_FreeContext(ctx); + free(mem_buf); + return 1; +} diff --git a/mqjs_stdlib.c b/mqjs_stdlib.c new file mode 100644 index 00000000..0a30833c --- /dev/null +++ b/mqjs_stdlib.c @@ -0,0 +1,402 @@ +/* + * Micro QuickJS REPL library + * + * 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 "mquickjs_build.h" + +/* defined in mqjs_example.c */ +//#define CONFIG_CLASS_EXAMPLE + +static const JSPropDef js_object_proto[] = { + JS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty), + JS_CFUNC_DEF("toString", 0, js_object_toString), + JS_PROP_END, +}; + +static const JSPropDef js_object[] = { + JS_CFUNC_DEF("defineProperty", 3, js_object_defineProperty), + JS_CFUNC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf), + JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf), + JS_CFUNC_DEF("create", 2, js_object_create), + JS_CFUNC_DEF("keys", 1, js_object_keys), + JS_PROP_END, +}; + +static const JSClassDef js_object_class = + JS_CLASS_DEF("Object", 1, js_object_constructor, JS_CLASS_OBJECT, + js_object, js_object_proto, NULL, NULL); + +static const JSPropDef js_function_proto[] = { + JS_CGETSET_DEF("prototype", js_function_get_prototype, js_function_set_prototype ), + JS_CFUNC_DEF("call", 1, js_function_call ), + JS_CFUNC_DEF("apply", 2, js_function_apply ), + JS_CFUNC_DEF("bind", 1, js_function_bind ), + JS_CFUNC_DEF("toString", 0, js_function_toString ), + JS_CGETSET_MAGIC_DEF("length", js_function_get_length_name, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("name", js_function_get_length_name, NULL, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_function_class = + JS_CLASS_DEF("Function", 1, js_function_constructor, JS_CLASS_CLOSURE, NULL, js_function_proto, NULL, NULL); + +static const JSPropDef js_number_proto[] = { + JS_CFUNC_DEF("toExponential", 1, js_number_toExponential ), + JS_CFUNC_DEF("toFixed", 1, js_number_toFixed ), + JS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision ), + JS_CFUNC_DEF("toString", 1, js_number_toString ), + JS_PROP_END, +}; + +static const JSPropDef js_number[] = { + JS_CFUNC_DEF("parseInt", 2, js_number_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_number_parseFloat ), + JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ), + JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ), + JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), + JS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0 ), + JS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0 ), + JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0 ), /* ES6 */ + JS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0 ), /* ES6 */ + JS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0 ), /* ES6 */ + JS_PROP_END, +}; + +static const JSClassDef js_number_class = + JS_CLASS_DEF("Number", 1, js_number_constructor, JS_CLASS_NUMBER, js_number, js_number_proto, NULL, NULL); + +static const JSClassDef js_boolean_class = + JS_CLASS_DEF("Boolean", 1, js_boolean_constructor, JS_CLASS_BOOLEAN, NULL, NULL, NULL, NULL); + +static const JSPropDef js_string_proto[] = { + JS_CGETSET_DEF("length", js_string_get_length, js_string_set_length ), + JS_CFUNC_MAGIC_DEF("charAt", 1, js_string_charAt, magic_charAt ), + JS_CFUNC_MAGIC_DEF("charCodeAt", 1, js_string_charAt, magic_charCodeAt ), + JS_CFUNC_MAGIC_DEF("codePointAt", 1, js_string_charAt, magic_codePointAt ), + JS_CFUNC_DEF("slice", 2, js_string_slice ), + JS_CFUNC_DEF("substring", 2, js_string_substring ), + JS_CFUNC_DEF("concat", 1, js_string_concat ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), + JS_CFUNC_DEF("match", 1, js_string_match ), + JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ), + JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ), + JS_CFUNC_DEF("search", 1, js_string_search ), + JS_CFUNC_DEF("split", 2, js_string_split ), + JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), + JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), + JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ), + JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ), + JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ), + JS_CFUNC_DEF("toString", 0, js_string_toString ), + JS_CFUNC_DEF("repeat", 1, js_string_repeat ), + JS_PROP_END, +}; + +static const JSPropDef js_string[] = { + JS_CFUNC_MAGIC_DEF("fromCharCode", 1, js_string_fromCharCode, 0 ), + JS_CFUNC_MAGIC_DEF("fromCodePoint", 1, js_string_fromCharCode, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_string_class = + JS_CLASS_DEF("String", 1, js_string_constructor, JS_CLASS_STRING, js_string, js_string_proto, NULL, NULL); + +static const JSPropDef js_array_proto[] = { + JS_CFUNC_DEF("concat", 1, js_array_concat ), + JS_CGETSET_DEF("length", js_array_get_length, js_array_set_length ), + JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ), + JS_CFUNC_DEF("pop", 0, js_array_pop ), + JS_CFUNC_DEF("join", 1, js_array_join ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_DEF("reverse", 0, js_array_reverse ), + JS_CFUNC_DEF("shift", 0, js_array_shift ), + JS_CFUNC_DEF("slice", 2, js_array_slice ), + JS_CFUNC_DEF("splice", 2, js_array_splice ), + JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_array_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_array_indexOf, 1 ), + JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, js_special_every ), + JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, js_special_some ), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, js_special_forEach ), + JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, js_special_map ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, js_special_filter ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, js_special_reduce ), + JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, js_special_reduceRight ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, js_special_reduce ), + JS_CFUNC_DEF("sort", 1, js_array_sort ), + JS_PROP_END, +}; + +static const JSPropDef js_array[] = { + JS_CFUNC_DEF("isArray", 1, js_array_isArray ), + JS_PROP_END, +}; + +static const JSClassDef js_array_class = + JS_CLASS_DEF("Array", 1, js_array_constructor, JS_CLASS_ARRAY, js_array, js_array_proto, NULL, NULL); + +static const JSPropDef js_error_proto[] = { + JS_CFUNC_DEF("toString", 0, js_error_toString ), + JS_PROP_STRING_DEF("name", "Error", 0 ), + JS_CGETSET_MAGIC_DEF("message", js_error_get_message, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("stack", js_error_get_message, NULL, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_error_class = + JS_CLASS_MAGIC_DEF("Error", 1, js_error_constructor, JS_CLASS_ERROR, NULL, js_error_proto, NULL, NULL); + +#define ERROR_DEF(cname, name, class_id) \ + static const JSPropDef js_ ## cname ## _proto[] = { \ + JS_PROP_STRING_DEF("name", name, 0 ), \ + JS_PROP_END, \ + }; \ + static const JSClassDef js_ ## cname ## _class = \ + JS_CLASS_MAGIC_DEF(name, 1, js_error_constructor, class_id, NULL, js_ ## cname ## _proto, &js_error_class, NULL); + +ERROR_DEF(eval_error, "EvalError", JS_CLASS_EVAL_ERROR) +ERROR_DEF(range_error, "RangeError", JS_CLASS_RANGE_ERROR) +ERROR_DEF(reference_error, "ReferenceError", JS_CLASS_REFERENCE_ERROR) +ERROR_DEF(syntax_error, "SyntaxError", JS_CLASS_SYNTAX_ERROR) +ERROR_DEF(type_error, "TypeError", JS_CLASS_TYPE_ERROR) +ERROR_DEF(uri_error, "URIError", JS_CLASS_URI_ERROR) +ERROR_DEF(internal_error, "InternalError", JS_CLASS_INTERNAL_ERROR) + +static const JSPropDef js_math[] = { + JS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0 ), + JS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1 ), + JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ), + JS_CFUNC_SPECIAL_DEF("abs", 1, f_f, js_fabs ), + JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, js_floor ), + JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, js_ceil ), + JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_round_inf ), + JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, js_sqrt ), + + JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ), + JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ), + JS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0 ), + JS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0 ), + JS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0 ), + JS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0 ), + JS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0 ), + JS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0 ), + + JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, js_sin ), + JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, js_cos ), + JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, js_tan ), + JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, js_asin ), + JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, js_acos ), + JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, js_atan ), + JS_CFUNC_DEF("atan2", 2, js_math_atan2 ), + JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, js_exp ), + JS_CFUNC_SPECIAL_DEF("log", 1, f_f, js_log ), + JS_CFUNC_DEF("pow", 2, js_math_pow ), + JS_CFUNC_DEF("random", 0, js_math_random ), + + /* some ES6 functions */ + JS_CFUNC_DEF("imul", 2, js_math_imul ), + JS_CFUNC_DEF("clz32", 1, js_math_clz32 ), + JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ), + JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, js_trunc ), + JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, js_log2 ), + JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, js_log10 ), + + JS_PROP_END, +}; + +static const JSClassDef js_math_obj = + JS_OBJECT_DEF("Math", js_math); + +static const JSPropDef js_json[] = { + JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("stringify", 3, js_json_stringify ), + JS_PROP_END, +}; + +static const JSClassDef js_json_obj = + JS_OBJECT_DEF("JSON", js_json); + +/* typed arrays */ +static const JSPropDef js_array_buffer_proto[] = { + JS_CGETSET_DEF("byteLength", js_array_buffer_get_byteLength, NULL ), + JS_PROP_END, +}; + +static const JSClassDef js_array_buffer_class = + JS_CLASS_DEF("ArrayBuffer", 1, js_array_buffer_constructor, JS_CLASS_ARRAY_BUFFER, NULL, js_array_buffer_proto, NULL, NULL); + +static const JSPropDef js_typed_array_base_proto[] = { + JS_CGETSET_MAGIC_DEF("length", js_typed_array_get_length, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_length, NULL, 1 ), + JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_length, NULL, 2 ), + JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_length, NULL, 3 ), + JS_CFUNC_DEF("join", 1, js_array_join ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ), + JS_CFUNC_DEF("set", 1, js_typed_array_set ), + JS_PROP_END, +}; + +static const JSClassDef js_typed_array_base_class = + JS_CLASS_DEF("TypedArray", 0, js_typed_array_base_constructor, JS_CLASS_TYPED_ARRAY, NULL, js_typed_array_base_proto, NULL, NULL); + +#define TA_DEF(name, class_name, bpe)\ +static const JSPropDef js_ ## name [] = {\ + JS_PROP_DOUBLE_DEF("BYTES_PER_ELEMENT", bpe, 0),\ + JS_PROP_END,\ +};\ +static const JSPropDef js_ ## name ## _proto[] = {\ + JS_PROP_DOUBLE_DEF("BYTES_PER_ELEMENT", bpe, 0),\ + JS_PROP_END,\ +};\ +static const JSClassDef js_ ## name ## _class =\ + JS_CLASS_MAGIC_DEF(#name, 3, js_typed_array_constructor, class_name, js_ ## name, js_ ## name ## _proto, &js_typed_array_base_class, NULL); + +TA_DEF(Uint8ClampedArray, JS_CLASS_UINT8C_ARRAY, 1) +TA_DEF(Int8Array, JS_CLASS_INT8_ARRAY, 1) +TA_DEF(Uint8Array, JS_CLASS_UINT8_ARRAY, 1) +TA_DEF(Int16Array, JS_CLASS_INT16_ARRAY, 2) +TA_DEF(Uint16Array, JS_CLASS_UINT16_ARRAY, 2) +TA_DEF(Int32Array, JS_CLASS_INT32_ARRAY, 4) +TA_DEF(Uint32Array, JS_CLASS_UINT32_ARRAY, 4) +TA_DEF(Float32Array, JS_CLASS_FLOAT32_ARRAY, 4) +TA_DEF(Float64Array, JS_CLASS_FLOAT64_ARRAY, 8) + +/* regexp */ + +static const JSPropDef js_regexp_proto[] = { + JS_CGETSET_DEF("lastIndex", js_regexp_get_lastIndex, js_regexp_set_lastIndex ), + JS_CGETSET_DEF("source", js_regexp_get_source, NULL ), + JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ), + JS_CFUNC_MAGIC_DEF("exec", 1, js_regexp_exec, 0 ), + JS_CFUNC_MAGIC_DEF("test", 1, js_regexp_exec, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_regexp_class = + JS_CLASS_DEF("RegExp", 2, js_regexp_constructor, JS_CLASS_REGEXP, NULL, js_regexp_proto, NULL, NULL); + +/* other objects */ + +static const JSPropDef js_date[] = { + JS_CFUNC_DEF("now", 0, js_date_now), + JS_PROP_END, +}; + +static const JSClassDef js_date_class = + JS_CLASS_DEF("Date", 7, js_date_constructor, JS_CLASS_DATE, js_date, NULL, NULL, NULL); + +static const JSPropDef js_console[] = { + JS_CFUNC_DEF("log", 1, js_print), + JS_PROP_END, +}; + +static const JSClassDef js_console_obj = + JS_OBJECT_DEF("Console", js_console); + +static const JSPropDef js_performance[] = { + JS_CFUNC_DEF("now", 0, js_performance_now), + JS_PROP_END, +}; +static const JSClassDef js_performance_obj = + JS_OBJECT_DEF("Performance", js_performance); + +static const JSPropDef js_global_object[] = { + JS_PROP_CLASS_DEF("Object", &js_object_class), + JS_PROP_CLASS_DEF("Function", &js_function_class), + JS_PROP_CLASS_DEF("Number", &js_number_class), + JS_PROP_CLASS_DEF("Boolean", &js_boolean_class), + JS_PROP_CLASS_DEF("String", &js_string_class), + JS_PROP_CLASS_DEF("Array", &js_array_class), + JS_PROP_CLASS_DEF("Math", &js_math_obj), + JS_PROP_CLASS_DEF("Date", &js_date_class), + JS_PROP_CLASS_DEF("JSON", &js_json_obj), + JS_PROP_CLASS_DEF("RegExp", &js_regexp_class), + + JS_PROP_CLASS_DEF("Error", &js_error_class), + JS_PROP_CLASS_DEF("EvalError", &js_eval_error_class), + JS_PROP_CLASS_DEF("RangeError", &js_range_error_class), + JS_PROP_CLASS_DEF("ReferenceError", &js_reference_error_class), + JS_PROP_CLASS_DEF("SyntaxError", &js_syntax_error_class), + JS_PROP_CLASS_DEF("TypeError", &js_type_error_class), + JS_PROP_CLASS_DEF("URIError", &js_uri_error_class), + JS_PROP_CLASS_DEF("InternalError", &js_internal_error_class), + + JS_PROP_CLASS_DEF("ArrayBuffer", &js_array_buffer_class), + JS_PROP_CLASS_DEF("Uint8ClampedArray", &js_Uint8ClampedArray_class), + JS_PROP_CLASS_DEF("Int8Array", &js_Int8Array_class), + JS_PROP_CLASS_DEF("Uint8Array", &js_Uint8Array_class), + JS_PROP_CLASS_DEF("Int16Array", &js_Int16Array_class), + JS_PROP_CLASS_DEF("Uint16Array", &js_Uint16Array_class), + JS_PROP_CLASS_DEF("Int32Array", &js_Int32Array_class), + JS_PROP_CLASS_DEF("Uint32Array", &js_Uint32Array_class), + JS_PROP_CLASS_DEF("Float32Array", &js_Float32Array_class), + JS_PROP_CLASS_DEF("Float64Array", &js_Float64Array_class), + + JS_CFUNC_DEF("parseInt", 2, js_number_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_number_parseFloat ), + JS_CFUNC_DEF("eval", 1, js_global_eval), + JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ), + JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ), + + JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), + JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), + JS_PROP_UNDEFINED_DEF("undefined", 0 ), + /* Note: null is expanded as the global object in js_global_object[] */ + JS_PROP_NULL_DEF("globalThis", 0 ), + + JS_PROP_CLASS_DEF("console", &js_console_obj), + JS_PROP_CLASS_DEF("performance", &js_performance_obj), + JS_CFUNC_DEF("print", 1, js_print), +#ifdef CONFIG_CLASS_EXAMPLE + JS_PROP_CLASS_DEF("Rectangle", &js_rectangle_class), + JS_PROP_CLASS_DEF("FilledRectangle", &js_filled_rectangle_class), +#else + JS_CFUNC_DEF("gc", 0, js_gc), + JS_CFUNC_DEF("load", 1, js_load), + JS_CFUNC_DEF("setTimeout", 2, js_setTimeout), + JS_CFUNC_DEF("clearTimeout", 1, js_clearTimeout), +#endif + JS_PROP_END, +}; + +/* Additional C function declarations (only useful for C + closures). They are always defined first. */ +static const JSPropDef js_c_function_decl[] = { + /* must come first if "bind" is defined */ + JS_CFUNC_SPECIAL_DEF("bound", 0, generic_params, js_function_bound ), +#ifdef CONFIG_CLASS_EXAMPLE + JS_CFUNC_SPECIAL_DEF("rectangle_closure_test", 0, generic_params, js_rectangle_closure_test ), +#endif + JS_PROP_END, +}; + +int main(int argc, char **argv) +{ + return build_atoms("js_stdlib", js_global_object, js_c_function_decl, argc, argv); +} diff --git a/mquickjs.c b/mquickjs.c new file mode 100644 index 00000000..a950f3c3 --- /dev/null +++ b/mquickjs.c @@ -0,0 +1,18324 @@ +/* + * Micro QuickJS Javascript Engine + * + * 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 "dtoa.h" +#include "mquickjs_priv.h" + +/* + TODO: + - regexp: better error position info + - use a specific MTAG for short functions instead of an immediate value + - use hash table for atoms + - set the length accessors as non configurable so that the + 'get_length' instruction optimizations are always safe. + - memory: + - fix stack_bottom logic + - launch gc at regular intervals + - only launch compaction when needed (handle free blocks in malloc()) + - avoid pass to rehash the properties + - ensure no undefined bytes (e.g. at end of JSString) in + saved bytecode ? + - reduced memory usage: + - reduce JSFunctionBytecode size (remove source_pos) + - do not explicitly store function names for get/set/bound + - use JSSTDLibraryDef fields instead of copying them to JSContext ? +*/ + +#define __exception __attribute__((warn_unused_result)) + +#define JS_STACK_SLACK 16 /* additional free space on the stack */ +/* min free size in bytes between heap_free and the bottom of the stack */ +#define JS_MIN_FREE_SIZE 512 +/* minimum free size in bytes to create the out of memory object */ +#define JS_MIN_CRITICAL_FREE_SIZE (JS_MIN_FREE_SIZE - 256) +#define JS_MAX_LOCAL_VARS 65535 +#define JS_MAX_FUNC_STACK_SIZE 65535 +#define JS_MAX_ARGC 65535 +/* maximum number of recursing JS_Call() */ +#define JS_MAX_CALL_RECURSE 8 + + +#define JS_VALUE_IS_BOTH_INT(a, b) ((((a) | (b)) & 1) == 0) +#define JS_VALUE_IS_BOTH_SHORT_FLOAT(a, b) (((((a) - JS_TAG_SHORT_FLOAT) | ((b) - JS_TAG_SHORT_FLOAT)) & 7) == 0) + +static __maybe_unused const char *js_mtag_name[JS_MTAG_COUNT] = { + "free", + "object", + "float64", + "string", + "func_bytecode", + "value_array", + "byte_array", + "varref", +}; + +/* function call flags (max 31 bits) */ +#define FRAME_CF_ARGC_MASK 0xffff +/* FRAME_CF_CTOR */ +#define FRAME_CF_POP_RET (1 << 17) /* pop the return value */ +#define FRAME_CF_PC_ADD1 (1 << 18) /* increment the PC by 1 instead of 3 */ + +#define JS_MB_PAD(n) (JSW * 8 - (n)) + +typedef struct { + JS_MB_HEADER; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS); +} JSMemBlockHeader; + +typedef struct { + JS_MB_HEADER; + /* in JSWords excluding the header. Free blocks of JSW bytes + are only generated by js_shrink() and may not be always + compacted */ + JSWord size: JS_MB_PAD(JS_MTAG_BITS); +} JSFreeBlock; + +#if JSW == 8 +#define JS_STRING_LEN_MAX 0x7ffffffe +#else +#define JS_STRING_LEN_MAX ((1 << (32 - JS_MTAG_BITS - 3)) - 1) +#endif + +typedef struct { + JS_MB_HEADER; + JSWord is_unique: 1; + JSWord is_ascii: 1; + /* true if the string content represents a number, only meaningful + is is_unique = true */ + JSWord is_numeric: 1; + JSWord len: JS_MB_PAD(JS_MTAG_BITS + 3); + uint8_t buf[]; +} JSString; + +typedef struct { + JSWord string_buf[sizeof(JSString) / sizeof(JSWord)]; /* for JSString */ + uint8_t buf[5]; +} JSStringCharBuf; + +#define JS_BYTE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1) + +typedef struct { + JS_MB_HEADER; + JSWord size: JS_MB_PAD(JS_MTAG_BITS); + uint8_t buf[]; +} JSByteArray; + +#define JS_VALUE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1) + +typedef struct { + JS_MB_HEADER; + JSWord size: JS_MB_PAD(JS_MTAG_BITS); + JSValue arr[]; +} JSValueArray; + +typedef struct JSVarRef { + JS_MB_HEADER; + JSWord is_detached : 1; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 1); + union { + JSValue value; /* is_detached = true */ + struct { + JSValue next; /* is_detached = false: JS_NULL or JSVarRef, + must be at the same address as 'value' */ + JSValue *pvalue; + }; + } u; +} JSVarRef; + +typedef struct { + JS_MB_HEADER; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS); +#ifdef JS_PTR64 + struct { + double dval; + } u; +#else + /* unaligned 64 bit access in 32-bit mode */ + struct __attribute__((packed)) { + double dval; + } u; +#endif +} JSFloat64; + +typedef struct JSROMClass { + JS_MB_HEADER; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS); + JSValue props; + int32_t ctor_idx; /* -1 if defining a normal object */ + JSValue proto_props; + JSValue parent_class; /* JSROMClass or JS_NULL */ +} JSROMClass; + +#define N_ROM_ATOM_TABLES_MAX 2 + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +#define JS_STRING_POS_CACHE_SIZE 2 +#define JS_STRING_POS_CACHE_MIN_LEN 16 + +typedef enum { + POS_TYPE_UTF8, + POS_TYPE_UTF16, +} StringPosTypeEnum; + +typedef struct { + JSValue str; /* JS_NULL or weak reference to a JSString. It + contains at least JS_STRING_POS_CACHE_MIN_LEN + bytes and is a non ascii string */ + uint32_t str_pos[2]; /* 0 = UTF-8 pos (in bytes), 1 = UTF-16 pos */ +} JSStringPosCacheEntry; + +struct JSContext { + /* memory map: + Stack + Free area + Heap + JSContext + */ + uint8_t *heap_base; + uint8_t *heap_free; /* first free area */ + uint8_t *stack_top; + JSValue *stack_bottom; /* sp must always be higher than stack_bottom */ + JSValue *sp; /* current stack pointer */ + JSValue *fp; /* current frame pointer, stack_top if none */ + uint32_t min_free_size; /* min free size between heap_free and the + bottom of the stack */ + BOOL in_out_of_memory : 8; /* != 0 if generating the out of memory object */ + uint8_t n_rom_atom_tables; + uint8_t string_pos_cache_counter; /* used for string_pos_cache[] update */ + uint16_t class_count; /* number of classes including user classes */ + int16_t interrupt_counter; + BOOL current_exception_is_uncatchable : 8; + struct JSParseState *parse_state; /* != NULL during JS_Eval() */ + int unique_strings_len; + int js_call_rec_count; /* number of recursing JS_Call() */ + JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ + JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ + const JSWord *atom_table; /* constant atom table */ + /* 'n_rom_atom_tables' atom tables from code loaded from rom */ + const JSValueArray *rom_atom_tables[N_ROM_ATOM_TABLES_MAX]; + const JSCFunctionDef *c_function_table; + const JSCFinalizer *c_finalizer_table; + uint64_t random_state; + JSInterruptHandler *interrupt_handler; + JSWriteFunc *write_func; /* for the various dump functions */ + void *opaque; + JSValue *class_obj; /* same as class_proto + class_count */ + JSStringPosCacheEntry string_pos_cache[JS_STRING_POS_CACHE_SIZE]; + + /* must only contain JSValue from this point (see JS_GC()) */ + JSValue unique_strings; /* JSValueArray of sorted strings or JS_NULL */ + + JSValue current_exception; /* currently pending exception, must + come after unique_strings */ +#ifdef DEBUG_GC + JSValue dummy_block; /* dummy memory block near the start of the memory */ +#endif + JSValue empty_props; /* empty prop list, for objects with no properties */ + JSValue global_obj; + JSValue minus_zero; /* minus zero float64 value */ + JSValue class_proto[]; /* prototype for each class (class_count + element, then class_count elements for + class_obj */ +}; + +typedef enum { + JS_VARREF_KIND_ARG, /* var_idx is an argument of the parent function */ + JS_VARREF_KIND_VAR, /* var_idx is a local variable of the parent function */ + JS_VARREF_KIND_VAR_REF, /* var_idx is a var ref of the parent function */ + JS_VARREF_KIND_GLOBAL, /* to debug */ +} JSVarRefKindEnum; + +typedef struct JSObject JSObject; + +typedef struct { + /* string, short integer or JS_UNINITIALIZED if no property. If + the last property is uninitialized, hash_next = 2 * + first_free. */ + JSValue key; + /* JS_PROP_GETSET: JSValueArray of two elements + JS_PROP_VARREF: JSVarRef */ + JSValue value; + /* XXX: when JSW = 8, could use 32 bits for hash_next (faster) */ + uint32_t hash_next : 30; /* low bit at zero */ + uint32_t prop_type : 2; +} JSProperty; + +typedef struct { + JSValue func_bytecode; /* JSFunctionBytecode */ + JSValue var_refs[]; /* JSValueArray */ +} JSClosureData; + +typedef struct { + uint32_t idx; + JSValue params; /* optional associated parameters */ +} JSCFunctionData; + +typedef struct { + JSValue tab; /* JS_NULL or JSValueArray */ + uint32_t len; /* maximum value: 2^30-1 */ +} JSArrayData; + +typedef struct { + JSValue message; /* string or JS_NULL */ + JSValue stack; /* string or JS_NULL */ +} JSErrorData; + +typedef struct { + JSValue byte_buffer; /* JSByteBuffer */ +} JSArrayBuffer; + +typedef struct { + JSValue buffer; /* corresponding array buffer */ + uint32_t len; /* in elements */ + uint32_t offset; /* in elements */ +} JSTypedArray; + +typedef struct { + JSValue source; + JSValue byte_code; + int last_index; +} JSRegExp; + +typedef struct { + void *opaque; +} JSObjectUserData; + +struct JSObject { + JS_MB_HEADER; + JSWord class_id: 8; + JSWord extra_size: JS_MB_PAD(JS_MTAG_BITS + 8); /* object additional size, in JSValue */ + + JSValue proto; /* JSObject or JS_NULL */ + /* JSValueArray. structure: + prop_count (number of properties excluding deleted ones) + hash_mask (= hash_size - 1) + hash_table[hash_size] (0 = end of list or offset in array) + JSProperty props[] + */ + JSValue props; + /* number of additional fields depends on the object */ + union { + JSClosureData closure; + JSCFunctionData cfunc; + JSArrayData array; + JSErrorData error; + JSArrayBuffer array_buffer; + JSTypedArray typed_array; + JSRegExp regexp; + JSObjectUserData user; + } u; +}; + +typedef struct JSFunctionBytecode { + JS_MB_HEADER; + JSWord has_arguments : 1; /* only used during parsing */ + JSWord has_local_func_name : 1; /* only used during parsing */ + JSWord has_column : 1; /* column debug info is present */ + /* during parse: variable index + 1 of hoisted function, 0 otherwise */ + JSWord arg_count : 16; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 3 + 16); + + JSValue func_name; /* JS_NULL if anonymous function */ + JSValue byte_code; /* JS_NULL if the function is not parsed yet */ + JSValue cpool; /* constant pool */ + JSValue vars; /* only for debug */ + JSValue ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */ + uint16_t stack_size; /* maximum stack size */ + uint16_t ext_vars_len; /* XXX: only used during parsing */ + JSValue filename; /* filename in which the function is defined */ + JSValue pc2line; /* JSByteArray or JS_NULL if not initialized */ + uint32_t source_pos; /* only used during parsing (XXX: shrink) */ +} JSFunctionBytecode; + +static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size); +static int get_mblock_size(const void *ptr); +static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size); +static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size); +static void build_backtrace(JSContext *ctx, JSValue error_obj, + const char *filename, int line_num, int col_num, int skip_level); +static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val); +static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size); +static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params, + JSValue params); +static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val); +static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto); +static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size); +static JSValueArray *js_alloc_props(JSContext *ctx, int n); + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "mquickjs_opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +typedef enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "mquickjs_opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, +} OPCodeEnum; + +typedef struct { +#ifdef DUMP_BYTECODE + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_pusch items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static __maybe_unused const JSOpCode opcode_info[OP_COUNT] = { +#define FMT(f) +#ifdef DUMP_BYTECODE +#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, +#endif +#include "mquickjs_opcode.h" +#undef DEF +#undef FMT +}; + +#include "mquickjs_atom.h" + +JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref) +{ + ref->prev = ctx->top_gc_ref; + ctx->top_gc_ref = ref; + ref->val = JS_UNDEFINED; + return &ref->val; +} + +JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref) +{ + ctx->top_gc_ref = ref->prev; + return ref->val; +} + +JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref) +{ + ref->prev = ctx->last_gc_ref; + ctx->last_gc_ref = ref; + ref->val = JS_UNDEFINED; + return &ref->val; +} + +void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref) +{ + JSGCRef **pref, *ref1; + pref = &ctx->last_gc_ref; + for(;;) { + ref1 = *pref; + if (ref1 == NULL) + abort(); + if (ref1 == ref) { + *pref = ref1->prev; + break; + } + pref = &ref1->prev; + } +} + +#undef JS_PUSH_VALUE +#undef JS_POP_VALUE + +#define JS_PUSH_VALUE(ctx, v) do { \ + v ## _ref.prev = ctx->top_gc_ref; \ + ctx->top_gc_ref = &v ## _ref; \ + v ## _ref.val = v; \ + } while (0) + +#define JS_POP_VALUE(ctx, v) do { \ + v = v ## _ref.val; \ + ctx->top_gc_ref = v ## _ref.prev; \ + } while (0) + +static JSValue js_get_atom(JSContext *ctx, int a) +{ + return JS_VALUE_FROM_PTR(&ctx->atom_table[a]); +} + +static force_inline JSValue JS_NewTailCall(int val) +{ + return JS_VALUE_MAKE_SPECIAL(JS_TAG_EXCEPTION, JS_EX_CALL + val); +} + +static inline JS_BOOL JS_IsExceptionOrTailCall(JSValue v) +{ + return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_EXCEPTION; +} + +static int js_get_mtag(void *ptr) +{ + return ((JSMemBlockHeader *)ptr)->mtag; +} + +static int check_free_mem(JSContext *ctx, JSValue *stack_bottom, uint32_t size) +{ +#ifdef DEBUG_GC + assert(ctx->sp >= stack_bottom); + /* don't start the GC before dummy_block is allocated */ + if (JS_IsPtr(ctx->dummy_block)) { + JS_GC(ctx); + } +#endif + if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) { + JS_GC(ctx); + if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + } + return 0; +} + +/* check that 'len' values can be pushed on the stack. Return 0 if OK, + -1 if not enough space. May trigger a GC(). */ +int JS_StackCheck(JSContext *ctx, uint32_t len) +{ + JSValue *new_stack_bottom; + + len += JS_STACK_SLACK; + new_stack_bottom = ctx->sp - len; + if (check_free_mem(ctx, new_stack_bottom, len * sizeof(JSValue))) + return -1; + ctx->stack_bottom = new_stack_bottom; + return 0; +} + +static void *js_malloc(JSContext *ctx, uint32_t size, int mtag) +{ + JSMemBlockHeader *p; + + if (size == 0) + return NULL; + size = (size + JSW - 1) & ~(JSW - 1); + + if (check_free_mem(ctx, ctx->stack_bottom, size)) + return NULL; + + p = (JSMemBlockHeader *)ctx->heap_free; + ctx->heap_free += size; + + p->mtag = mtag; + p->gc_mark = 0; + p->dummy = 0; + return p; +} + +static void *js_mallocz(JSContext *ctx, uint32_t size, int mtag) +{ + uint8_t *ptr; + ptr = js_malloc(ctx, size, mtag); + if (!ptr) + return NULL; + if (size > sizeof(uint32_t)) { + memset(ptr + sizeof(uint32_t), 0, size - sizeof(uint32_t)); + } + return ptr; +} + +/* currently only free the last element */ +static void js_free(JSContext *ctx, void *ptr) +{ + uint8_t *ptr1; + if (!ptr) + return; + ptr1 = ptr; + ptr1 += get_mblock_size(ptr1); + if (ptr1 == ctx->heap_free) + ctx->heap_free = ptr; +} + +/* 'size' is in bytes and must be multiple of JSW and > 0 */ +static void set_free_block(void *ptr, uint32_t size) +{ + JSFreeBlock *p; + p = (JSFreeBlock *)ptr; + p->mtag = JS_MTAG_FREE; + p->gc_mark = 0; + p->size = (size - sizeof(JSFreeBlock)) / sizeof(JSWord); +} + +/* 'ptr' must be != NULL. new_size must be less or equal to the + current block size. */ +static void *js_shrink(JSContext *ctx, void *ptr, uint32_t new_size) +{ + uint32_t old_size; + uint32_t diff; + + new_size = (new_size + (JSW - 1)) & ~(JSW - 1); + + if (new_size == 0) { + js_free(ctx, ptr); + return NULL; + } + old_size = get_mblock_size(ptr); + assert(new_size <= old_size); + diff = old_size - new_size; + if (diff == 0) + return ptr; + set_free_block((uint8_t *)ptr + new_size, diff); + /* add a new free block after 'ptr' */ + return ptr; +} + +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + ctx->current_exception = obj; + ctx->current_exception_is_uncatchable = FALSE; + return JS_EXCEPTION; +} + +/* return the byte length. 'buf' must contain UTF8_CHAR_LEN_MAX + 1 bytes */ +static int get_short_string(uint8_t *buf, JSValue val) +{ + int len; + len = unicode_to_utf8(buf, JS_VALUE_GET_SPECIAL_VALUE(val)); + buf[len] = '\0'; + return len; +} + +/* printf utility */ + +#define PF_ZERO_PAD (1 << 0) /* 0 */ +#define PF_ALT_FORM (1 << 1) /* # */ +#define PF_MARK_POS (1 << 2) /* + */ +#define PF_LEFT_ADJ (1 << 3) /* - */ +#define PF_PAD_POS (1 << 4) /* ' ' */ +#define PF_INT64 (1 << 5) /* l/ll */ + +static BOOL is_digit(int c) +{ + return (c >= '0' && c <= '9'); +} + +/* pad with chars 'c' */ +static void pad(JSWriteFunc *write_func, void *opaque, char c, + int width, int len) +{ + char buf[16]; + int l; + if (len >= width) + return; + width -= len; + memset(buf, c, min_int(sizeof(buf), width)); + while (width != 0) { + l = min_int(width, sizeof(buf)); + write_func(opaque, buf, l); + width -= l; + } +} + +/* The 'o' format can be used to print a JSValue. Only short int, + bool, null, undefined and string types are supported. */ +static void js_vprintf(JSWriteFunc *write_func, void *opaque, const char *fmt, va_list ap) +{ + const char *p; + int width, prec, flags, c; + char tmp_buf[32], *buf; + size_t len; + + while (*fmt != '\0') { + p = fmt; + while (*fmt != '%' && *fmt != '\0') + fmt++; + if (fmt > p) + write_func(opaque, p, fmt - p); + if (*fmt == '\0') + break; + fmt++; + /* get the flags */ + flags = 0; + for(;;) { + c = *fmt; + if (c == '0') { + flags |= PF_ZERO_PAD; + } else if (c == '#') { + flags |= PF_ALT_FORM; + } else if (c == '+') { + flags |= PF_MARK_POS; + } else if (c == '-') { + flags |= PF_LEFT_ADJ; + } else if (c == ' ') { + flags |= PF_MARK_POS; + } else { + break; + } + fmt++; + } + width = 0; + if (*fmt == '*') { + width = va_arg(ap, int); + } else { + while (is_digit(*fmt)) { + width = width * 10 + *fmt - '0'; + fmt++; + } + } + prec = 0; + if (*fmt == '.') { + fmt++; + if (*fmt == '*') { + prec = va_arg(ap, int); + } else { + while (is_digit(*fmt)) { + prec = prec * 10 + *fmt - '0'; + fmt++; + } + } + } + /* modifiers */ + for(;;) { + c = *fmt; + if (c == 'l') { + if (sizeof(long) == sizeof(int64_t) || fmt[-1] == 'l') + flags |= PF_INT64; + } else + if (c == 'z' || c == 't') { + if (sizeof(size_t) == sizeof(uint64_t)) + flags |= PF_INT64; + } else { + break; + } + fmt++; + } + + c = *fmt++; + /* XXX: not complete, just enough for our needs */ + buf = tmp_buf; + len = 0; + switch(c) { + case '%': + write_func(opaque, fmt - 1, 1); + break; + case 'c': + buf[0] = va_arg(ap, int); + len = 1; + flags &= ~PF_ZERO_PAD; + break; + case 's': + buf = va_arg(ap, char *); + if (!buf) + buf = "null"; + len = strlen(buf); + flags &= ~PF_ZERO_PAD; + break; + case 'd': + if (flags & PF_INT64) + len = i64toa(buf, va_arg(ap, int64_t)); + else + len = i32toa(buf, va_arg(ap, int32_t)); + break; + case 'u': + if (flags & PF_INT64) + len = u64toa(buf, va_arg(ap, uint64_t)); + else + len = u32toa(buf, va_arg(ap, uint32_t)); + break; + case 'x': + if (flags & PF_INT64) + len = u64toa_radix(buf, va_arg(ap, uint64_t), 16); + else + len = u64toa_radix(buf, va_arg(ap, uint32_t), 16); + break; + case 'p': + buf[0] = '0'; + buf[1] = 'x'; + len = u64toa_radix(buf + 2, (uintptr_t)va_arg(ap, void *), 16); + len += 2; + break; + case 'o': + { + JSValue val = (flags & PF_INT64) ? va_arg(ap, uint64_t) : va_arg(ap, uint32_t); + if (JS_IsInt(val)) { + len = i32toa(buf, JS_VALUE_GET_INT(val)); + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + /* XXX: print it */ + buf = "[short_float]"; + goto do_strlen; + } else +#endif + if (!JS_IsPtr(val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + buf = "null"; + goto do_strlen; + case JS_TAG_UNDEFINED: + buf = "undefined"; + goto do_strlen; + case JS_TAG_UNINITIALIZED: + buf = "uninitialized"; + goto do_strlen; + case JS_TAG_BOOL: + buf = JS_VALUE_GET_SPECIAL_VALUE(val) ? "true" : "false"; + goto do_strlen; + case JS_TAG_STRING_CHAR: + len = get_short_string((uint8_t *)buf, val); + break; + default: + buf = "[tag]"; + goto do_strlen; + } + } else { + void *ptr = JS_VALUE_TO_PTR(val); + int mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_STRING: + { + JSString *p = ptr; + buf = (char *)p->buf; + len = p->len; + } + break; + default: + buf = "[mtag]"; + do_strlen: + len = strlen(buf); + break; + } + } + /* remove the trailing '\n' if any (used in error output) */ + if ((flags & PF_ALT_FORM) && len > 0 && buf[len - 1] == '\n') + len--; + flags &= ~PF_ZERO_PAD; + } + break; + default: + goto error; + } + if (flags & PF_ZERO_PAD) { + /* XXX: incorrect with prefix */ + pad(write_func, opaque, '0', width, len); + } else { + if (!(flags & PF_LEFT_ADJ)) + pad(write_func, opaque, ' ', width, len); + } + write_func(opaque, buf, len); + if (flags & PF_LEFT_ADJ) + pad(write_func, opaque, ' ', width, len); + } + return; + error: + return; +} + +/* used for the debug output */ +static void __js_printf_like(2, 3) js_printf(JSContext *ctx, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + js_vprintf(ctx->write_func, ctx->opaque, fmt, ap); + va_end(ap); +} + +static __maybe_unused void js_putchar(JSContext *ctx, uint8_t c) +{ + ctx->write_func(ctx->opaque, &c, 1); +} + +typedef struct { + char *ptr; + char *buf_end; + int len; +} SNPrintfState; + +static void snprintf_write_func(void *opaque, const void *buf, size_t buf_len) +{ + SNPrintfState *s = opaque; + size_t l; + s->len += buf_len; + l = min_size_t(buf_len, s->buf_end - s->ptr); + if (l != 0) { + memcpy(s->ptr, buf, l); + s->ptr += l; + } +} + +static int js_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) +{ + SNPrintfState ss, *s = &ss; + s->ptr = buf; + s->buf_end = buf + max_size_t(buf_size, 1) - 1; + s->len = 0; + js_vprintf(snprintf_write_func, s, fmt, ap); + if (buf_size > 0) + *s->ptr = '\0'; + return s->len; +} + +static int __maybe_unused __js_printf_like(3, 4) js_snprintf(char *buf, size_t buf_size, const char *fmt, ...) +{ + va_list ap; + int ret; + va_start(ap, fmt); + ret = js_vsnprintf(buf, buf_size, fmt, ap); + va_end(ap); + return ret; +} + +JSValue __js_printf_like(3, 4) JS_ThrowError(JSContext *ctx, JSObjectClassEnum error_num, + const char *fmt, ...) +{ + JSObject *p; + va_list ap; + char buf[128]; + JSValue msg, error_obj; + JSGCRef msg_ref, error_obj_ref; + + va_start(ap, fmt); + js_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + msg = JS_NewString(ctx, buf); + + JS_PUSH_VALUE(ctx, msg); + error_obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[error_num], JS_CLASS_ERROR, + sizeof(JSErrorData)); + JS_POP_VALUE(ctx, msg); + if (JS_IsException(error_obj)) + return error_obj; + + p = JS_VALUE_TO_PTR(error_obj); + p->u.error.message = msg; + p->u.error.stack = JS_NULL; + + /* in case of syntax error, the backtrace is added later */ + if (error_num != JS_CLASS_SYNTAX_ERROR) { + JS_PUSH_VALUE(ctx, error_obj); + build_backtrace(ctx, error_obj, NULL, 0, 0, 0); + JS_POP_VALUE(ctx, error_obj); + } + + return JS_Throw(ctx, error_obj); +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSValue val; + if (ctx->in_out_of_memory) + return JS_Throw(ctx, JS_NULL); + ctx->in_out_of_memory = TRUE; + ctx->min_free_size = JS_MIN_CRITICAL_FREE_SIZE; + val = JS_ThrowInternalError(ctx, "out of memory"); + ctx->in_out_of_memory = FALSE; + ctx->min_free_size = JS_MIN_FREE_SIZE; + return val; +} + +#define JS_SHORTINT_MIN (-(1 << 30)) +#define JS_SHORTINT_MAX ((1 << 30) - 1) + +#ifdef JS_USE_SHORT_FLOAT + +#define JS_FLOAT64_VALUE_EXP_MIN (1023 - 127) +#define JS_FLOAT64_VALUE_ADDEND ((uint64_t)(JS_FLOAT64_VALUE_EXP_MIN - (JS_TAG_SHORT_FLOAT << 8)) << 52) + +/* 1 <= n <= 63 */ +static inline uint64_t rotl64(uint64_t a, int n) +{ + return (a << n) | (a >> (64 - n)); +} + +static double js_get_short_float(JSValue v) +{ + return uint64_as_float64(rotl64(v, 60) + JS_FLOAT64_VALUE_ADDEND); +} + +static JSValue js_to_short_float(double d) +{ + return rotl64(float64_as_uint64(d) - JS_FLOAT64_VALUE_ADDEND, 4); +} + +#endif /* JS_USE_SHORT_FLOAT */ + +static JSValue js_alloc_float64(JSContext *ctx, double d) +{ + JSFloat64 *f; + f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64); + if (!f) + return JS_EXCEPTION; + f->u.dval = d; + return JS_VALUE_FROM_PTR(f); +} + +/* create a new float64 value which is known not to be a short integer */ +static JSValue __JS_NewFloat64(JSContext *ctx, double d) +{ + if (float64_as_uint64(d) == 0x8000000000000000) { + /* minus zero often happens, so it is worth having a constant + value */ + return ctx->minus_zero; + } else +#ifdef JS_USE_SHORT_FLOAT + /* Note: this test is false for NaN */ + if (fabs(d) >= 0x1p-127 && fabs(d) <= 0x1p+128) { + return js_to_short_float(d); + } else +#endif + { + return js_alloc_float64(ctx, d); + } +} + +static inline JSValue JS_NewShortInt(int32_t val) +{ + return JS_TAG_INT + (val << 1); +} + +#if defined(USE_SOFTFLOAT) +JSValue JS_NewFloat64(JSContext *ctx, double d) +{ + uint64_t a, m; + int e, b, shift; + JSValue v; + + a = float64_as_uint64(d); + if (a == 0) { + v = JS_NewShortInt(0); + } else { + e = (a >> 52) & 0x7ff; + if (e >= 1023 && e <= 1023 + 30 - 1) { + m = (a & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + shift = 52 - (e - 1023); + /* test if exact integer */ + if ((m & (((uint64_t)1 << shift) - 1)) != 0) + goto not_int; + b = m >> shift; + if (a >> 63) + b = -b; + v = JS_NewShortInt(b); + } else if (a == 0xc1d0000000000000) { + v = JS_NewShortInt(-(1 << 30)); + } else { + not_int: + v = __JS_NewFloat64(ctx, d); + } + } + return v; +} +#else +JSValue JS_NewFloat64(JSContext *ctx, double d) +{ + int32_t val; + if (d >= JS_SHORTINT_MIN && d <= JS_SHORTINT_MAX) { + val = (int32_t)d; + /* -0 cannot be represented as integer, so we compare the bit + representation */ + if (float64_as_uint64(d) == float64_as_uint64((double)val)) + return JS_NewShortInt(val); + } + return __JS_NewFloat64(ctx, d); +} +#endif + +static inline BOOL int64_is_short_int(int64_t val) +{ + return val >= JS_SHORTINT_MIN && val <= JS_SHORTINT_MAX; +} + +JSValue JS_NewInt64(JSContext *ctx, int64_t val) +{ + JSValue v; + if (likely(int64_is_short_int(val))) { + v = JS_NewShortInt(val); + } else { + v = __JS_NewFloat64(ctx, val); + } + return v; +} + +JSValue JS_NewInt32(JSContext *ctx, int32_t val) +{ + return JS_NewInt64(ctx, val); +} + +JSValue JS_NewUint32(JSContext *ctx, uint32_t val) +{ + return JS_NewInt64(ctx, val); +} + +static BOOL JS_IsPrimitive(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return JS_VALUE_GET_SPECIAL_TAG(val) != JS_TAG_SHORT_FUNC; + } else { + return (js_get_mtag(JS_VALUE_TO_PTR(val)) != JS_MTAG_OBJECT); + } +} + +/* Note: short functions are not considered as objects by this function */ +static BOOL JS_IsObject(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return FALSE; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT); + } +} + +/* return -1 if not an object */ +int JS_GetClassID(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return -1; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + if (p->mtag != JS_MTAG_OBJECT) + return -1; + else + return p->class_id; + } +} + +void JS_SetOpaque(JSContext *ctx, JSValue val, void *opaque) +{ + JSObject *p; + assert(JS_IsPtr(val)); + p = JS_VALUE_TO_PTR(val); + assert(p->mtag == JS_MTAG_OBJECT); + assert(p->class_id >= JS_CLASS_USER); + p->u.user.opaque = opaque; +} + +void *JS_GetOpaque(JSContext *ctx, JSValue val) +{ + JSObject *p; + assert(JS_IsPtr(val)); + p = JS_VALUE_TO_PTR(val); + assert(p->mtag == JS_MTAG_OBJECT); + assert(p->class_id >= JS_CLASS_USER); + return p->u.user.opaque; +} + +static JSObject *js_get_object_class(JSContext *ctx, JSValue val, int class_id) +{ + if (!JS_IsPtr(val)) { + return NULL; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + if (p->mtag != JS_MTAG_OBJECT || p->class_id != class_id) + return NULL; + else + return p; + } +} + +BOOL JS_IsFunction(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_SHORT_FUNC; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT && + (p->class_id == JS_CLASS_CLOSURE || + p->class_id == JS_CLASS_C_FUNCTION)); + } +} + +static BOOL JS_IsFunctionObject(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return FALSE; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT && + (p->class_id == JS_CLASS_CLOSURE || + p->class_id == JS_CLASS_C_FUNCTION)); + } +} + +BOOL JS_IsError(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return FALSE; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT && p->class_id == JS_CLASS_ERROR); + } +} + +static force_inline BOOL JS_IsIntOrShortFloat(JSValue val) +{ +#ifdef JS_USE_SHORT_FLOAT + return JS_IsInt(val) || JS_IsShortFloat(val); +#else + return JS_IsInt(val); +#endif +} + +BOOL JS_IsNumber(JSContext *ctx, JSValue val) +{ + if (JS_IsIntOrShortFloat(val)) { + return TRUE; + } else if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + return (js_get_mtag(ptr) == JS_MTAG_FLOAT64); + } else { + return FALSE; + } +} + +BOOL JS_IsString(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR; + } else { + void *ptr = JS_VALUE_TO_PTR(val); + return (js_get_mtag(ptr) == JS_MTAG_STRING); + } +} + +static JSString *js_alloc_string(JSContext *ctx, uint32_t buf_len) +{ + JSString *p; + + if (buf_len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(ctx, "string too long"); + return NULL; + } + p = js_malloc(ctx, sizeof(JSString) + buf_len + 1, JS_MTAG_STRING); + if (!p) + return NULL; + p->is_unique = FALSE; + p->is_ascii = FALSE; + p->is_numeric = FALSE; + p->len = buf_len; + p->buf[buf_len] = '\0'; + return p; +} + +/* 0 <= c <= 0x10ffff */ +static inline JSValue JS_NewStringChar(uint32_t c) +{ + return JS_VALUE_MAKE_SPECIAL(JS_TAG_STRING_CHAR, c); +} + +static force_inline int utf8_char_len(int c) +{ + int l; + if (c < 0x80) { + l = 1; + } else if (c < 0xc0) { + l = 1; + } else if (c < 0xe0) { + l = 2; + } else if (c < 0xf0) { + l = 3; + } else if (c < 0xf8) { + l = 4; + } else { + l = 1; + } + return l; +} + +static BOOL is_ascii_string(const char *buf, size_t len) +{ + size_t i; + for(i = 0; i < len; i++) { + if ((uint8_t)buf[i] > 0x7f) + return FALSE; + } + return TRUE; +} + +static JSString *get_string_ptr(JSContext *ctx, JSStringCharBuf *buf, + JSValue val) +{ + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + JSString *p = (JSString *)buf; + p->is_unique = FALSE; + p->is_ascii = JS_VALUE_GET_SPECIAL_VALUE(val) <= 0x7f; + p->len = get_short_string(p->buf, val); + return p; + } else { + return JS_VALUE_TO_PTR(val); + } +} + +static JSValue js_sub_string_utf8(JSContext *ctx, JSValue val, + uint32_t start0, uint32_t end0) +{ + JSString *p, *p1; + int len, start, end, c; + BOOL start_surrogate, end_surrogate; + JSStringCharBuf buf; + JSGCRef val_ref; + const uint8_t *ptr; + size_t clen; + + if (end0 - start0 == 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } + start_surrogate = start0 & 1; + end_surrogate = end0 & 1; + start = start0 >> 1; + end = end0 >> 1; + len = end - start; + p1 = get_string_ptr(ctx, &buf, val); + ptr = p1->buf; + if (!start_surrogate && !end_surrogate && utf8_char_len(ptr[start]) == len) { + c = utf8_get(ptr + start, &clen); + return JS_NewStringChar(c); + } + + JS_PUSH_VALUE(ctx, val); + p = js_alloc_string(ctx, len - start_surrogate + (end_surrogate ? 3 : 0)); + JS_POP_VALUE(ctx, val); + if (!p) + return JS_EXCEPTION; + p1 = get_string_ptr(ctx, &buf, val); + ptr = p1->buf; + if (unlikely(start_surrogate || end_surrogate)) { + uint8_t *q = p->buf; + p->is_ascii = FALSE; + if (start_surrogate) { + c = utf8_get(ptr + start, &clen); + c = 0xdc00 + ((c - 0x10000) & 0x3ff); /* right surrogate */ + q += unicode_to_utf8(q, c); + start += 4; + } + memcpy(q, ptr + start, end - start); + q += end - start; + if (end_surrogate) { + c = utf8_get(ptr + end, &clen); + c = 0xd800 + ((c - 0x10000) >> 10); /* left surrogate */ + q += unicode_to_utf8(q, c); + } + assert((q - p->buf) == p->len); + } else { + p->is_ascii = p1->is_ascii ? TRUE : is_ascii_string((const char *)(ptr + start), len); + memcpy(p->buf, ptr + start, len); + } + return JS_VALUE_FROM_PTR(p); +} + +/* Warning: the string must be a valid WTF-8 string (= UTF-8 + + unpaired surrogates). */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t len) +{ + JSString *p; + + if (len == 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } else { + if (utf8_char_len(buf[0]) == len) { + size_t clen; + int c; + c = utf8_get((const uint8_t *)buf, &clen); + return JS_NewStringChar(c); + } + } + p = js_alloc_string(ctx, len); + if (!p) + return JS_EXCEPTION; + p->is_ascii = is_ascii_string((const char *)buf, len); + memcpy(p->buf, buf, len); + return JS_VALUE_FROM_PTR(p); +} + +/* Warning: the string must be a valid UTF-8 string. */ +JSValue JS_NewString(JSContext *ctx, const char *buf) +{ + return JS_NewStringLen(ctx, buf, strlen(buf)); +} + +/* the byte array must be zero terminated. */ +static JSValue js_byte_array_to_string(JSContext *ctx, JSValue val, int len, BOOL is_ascii) +{ + JSByteArray *arr = JS_VALUE_TO_PTR(val); + JSString *p; + + assert(len + 1 <= arr->size); + if (len == 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } else if (utf8_char_len(arr->buf[0]) == len) { + size_t clen; + return JS_NewStringChar(utf8_get(arr->buf, &clen)); + } else { + js_shrink_byte_array(ctx, &val, len + 1); + p = (JSString *)arr; + p->mtag = JS_MTAG_STRING; + p->is_ascii = is_ascii; + p->is_unique = FALSE; + p->is_numeric = FALSE; + p->len = len; + return val; + } +} + +/* in bytes */ +static __maybe_unused int js_string_byte_len(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + int c = JS_VALUE_GET_SPECIAL_VALUE(val); + if (c < 0x80) + return 1; + else if (c < 0x800) + return 2; + else if (c < 0x10000) + return 3; + else + return 4; + } else { + JSString *p = JS_VALUE_TO_PTR(val); + return p->len; + } +} + +/* assuming that utf8_next() returns 4, validate the corresponding UTF-8 sequence */ +static BOOL is_valid_len4_utf8(const uint8_t *buf) +{ + return (((buf[0] & 0xf) << 6) | (buf[1] & 0x3f)) >= 0x10; +} + +static __maybe_unused void dump_string_pos_cache(JSContext *ctx) +{ + int i; + JSStringPosCacheEntry *ce; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) { + ce = &ctx->string_pos_cache[i]; + printf("%d: ", i); + if (ce->str == JS_NULL) { + printf("\n"); + } else { + JSString *p = JS_VALUE_TO_PTR(ce->str); + printf(" utf8_pos=%u/%u utf16_pos=%u\n", + ce->str_pos[POS_TYPE_UTF8], (int)p->len, ce->str_pos[POS_TYPE_UTF16]); + } + } +} + +/* an UTF-8 position is the byte position multiplied by 2. One is + added when the corresponding UTF-16 character represents the right + surrogate if the code is >= 0x10000. +*/ +static uint32_t js_string_convert_pos(JSContext *ctx, JSValue val, uint32_t pos, + StringPosTypeEnum pos_type) +{ + JSStringCharBuf buf; + JSString *p; + size_t i, clen, len, start; + uint32_t d_min, d, j; + JSStringPosCacheEntry *ce, *ce1; + uint32_t surrogate_flag, has_surrogate, limit; + int ce_idx; + + p = get_string_ptr(ctx, &buf, val); + len = p->len; + if (p->is_ascii) { + if (pos_type == POS_TYPE_UTF8) + return min_int(len, pos / 2); + else + return min_int(len, pos) * 2; + } + + if (pos_type == POS_TYPE_UTF8) { + has_surrogate = pos & 1; + pos >>= 1; + } else { + has_surrogate = 0; + } + + ce = NULL; + if (len < JS_STRING_POS_CACHE_MIN_LEN) { + j = 0; + i = 0; + goto uncached; + } + + d_min = pos; + for(ce_idx = 0; ce_idx < JS_STRING_POS_CACHE_SIZE; ce_idx++) { + ce1 = &ctx->string_pos_cache[ce_idx]; + if (ce1->str == val) { + d = ce1->str_pos[pos_type]; + d = d >= pos ? d - pos : pos - d; + if (d < d_min) { + d_min = d; + ce = ce1; + } + } + } + if (!ce) { + /* "random" replacement */ + ce = &ctx->string_pos_cache[ctx->string_pos_cache_counter]; + if (++ctx->string_pos_cache_counter == JS_STRING_POS_CACHE_SIZE) + ctx->string_pos_cache_counter = 0; + ce->str = val; + ce->str_pos[POS_TYPE_UTF8] = 0; + ce->str_pos[POS_TYPE_UTF16] = 0; + } + + i = ce->str_pos[POS_TYPE_UTF8]; + j = ce->str_pos[POS_TYPE_UTF16]; + if (ce->str_pos[pos_type] <= pos) { + uncached: + surrogate_flag = 0; + if (pos_type == POS_TYPE_UTF8) { + limit = INT32_MAX; + len = pos; + } else { + limit = pos; + } + for(; i < len; i += clen) { + if (j == limit) + break; + clen = utf8_char_len(p->buf[i]); + if (clen == 4 && is_valid_len4_utf8(p->buf + i)) { + if ((j + 1) == limit) { + surrogate_flag = 1; + break; + } + j += 2; + } else { + j++; + } + } + } else { + surrogate_flag = 0; + if (pos_type == POS_TYPE_UTF8) { + start = pos; + limit = INT32_MAX; + } else { + limit = pos; + start = 0; + } + while (i > start) { + size_t i0 = i; + i--; + while ((p->buf[i] & 0xc0) == 0x80) + i--; + clen = i0 - i; + if (clen == 4 && is_valid_len4_utf8(p->buf + i)) { + j -= 2; + if ((j + 1) == limit) { + surrogate_flag = 1; + break; + } + } else { + j--; + } + if (j == limit) + break; + } + } + if (ce) { + ce->str_pos[POS_TYPE_UTF8] = i; + ce->str_pos[POS_TYPE_UTF16] = j; + } + if (pos_type == POS_TYPE_UTF8) + return j + has_surrogate; + else + return i * 2 + surrogate_flag; +} + +static uint32_t js_string_utf16_to_utf8_pos(JSContext *ctx, JSValue val, uint32_t utf16_pos) +{ + return js_string_convert_pos(ctx, val, utf16_pos, POS_TYPE_UTF16); +} + +static uint32_t js_string_utf8_to_utf16_pos(JSContext *ctx, JSValue val, uint32_t utf8_pos) +{ + return js_string_convert_pos(ctx, val, utf8_pos, POS_TYPE_UTF8); +} + +/* Testing the third byte is not needed as the UTF-8 encoding must be + correct */ +static BOOL is_utf8_left_surrogate(const uint8_t *p) +{ + return p[0] == 0xed && (p[1] >= 0xa0 && p[1] <= 0xaf); +} + +static BOOL is_utf8_right_surrogate(const uint8_t *p) +{ + return p[0] == 0xed && (p[1] >= 0xb0 && p[1] <= 0xbf); +} + +typedef struct { + JSGCRef buffer_ref; /* string, JSByteBuffer or JS_EXCEPTION */ + int len; /* current string length (in bytes) */ + BOOL is_ascii; +} StringBuffer; + +/* return 0 if OK, -1 in case of exception (exception possible if len > 0) */ +static int string_buffer_push(JSContext *ctx, StringBuffer *s, int len) +{ + s->len = 0; + s->is_ascii = TRUE; + if (len > 0) { + JSByteArray *arr; + arr = js_alloc_byte_array(ctx, len); + if (!arr) + return -1; + s->buffer_ref.val = JS_VALUE_FROM_PTR(arr); + } else { + s->buffer_ref.val = js_get_atom(ctx, JS_ATOM_empty); + } + s->buffer_ref.prev = ctx->top_gc_ref; + ctx->top_gc_ref = &s->buffer_ref; + return 0; +} + +/* val2 must be a string. Return 0 if OK, -1 in case of exception */ +static int string_buffer_concat_str(JSContext *ctx, StringBuffer *s, JSValue val2) +{ + JSStringCharBuf buf1, buf2; + JSByteArray *arr; + JSString *p1, *p2; + int len, len1, len2; + JSValue val1; + uint8_t *q; + + if (JS_IsException(s->buffer_ref.val)) + return -1; + p2 = get_string_ptr(ctx, &buf2, val2); + len2 = p2->len; + if (len2 == 0) + return 0; + if (JS_IsString(ctx, s->buffer_ref.val)) { + p1 = get_string_ptr(ctx, &buf1, s->buffer_ref.val); + len1 = p1->len; + if (len1 == 0) { + /* empty string in buffer: just keep 'val2' */ + s->buffer_ref.val = val2; + return 0; + } + arr = NULL; + val1 = s->buffer_ref.val; + s->buffer_ref.val = JS_NULL; + } else { + arr = JS_VALUE_TO_PTR(s->buffer_ref.val); + len1 = s->len; + val1 = JS_NULL; + } + + len = len1 + len2; + if (len > JS_STRING_LEN_MAX) { + s->buffer_ref.val = JS_ThrowInternalError(ctx, "string too long"); + return -1; + } + + if (!arr || (len + 1) > arr->size) { + JSGCRef val1_ref, val2_ref; + + JS_PUSH_VALUE(ctx, val1); + JS_PUSH_VALUE(ctx, val2); + s->buffer_ref.val = js_resize_byte_array(ctx, s->buffer_ref.val, len + 1); + JS_POP_VALUE(ctx, val2); + JS_POP_VALUE(ctx, val1); + if (JS_IsException(s->buffer_ref.val)) + return -1; + arr = JS_VALUE_TO_PTR(s->buffer_ref.val); + if (val1 != JS_NULL) { + p1 = get_string_ptr(ctx, &buf1, val1); + s->is_ascii = p1->is_ascii; + memcpy(arr->buf, p1->buf, len1); + } + p2 = get_string_ptr(ctx, &buf2, val2); + } + + q = arr->buf + len1; + if (len2 >= 3 && unlikely(is_utf8_right_surrogate(p2->buf)) && + len1 >= 3 && is_utf8_left_surrogate(q - 3)) { + size_t clen; + int c; + /* contract the two surrogates to 4 bytes */ + c = (utf8_get(q - 3, &clen) & 0x3ff) << 10; + c |= (utf8_get(p2->buf, &clen) & 0x3ff); + c += 0x10000; + len -= 2; + len2 -= 3; + q -= 3; + q += unicode_to_utf8(q, c); + s->is_ascii = FALSE; + } + memcpy(q, p2->buf + p2->len - len2, len2); + s->len = len; + s->is_ascii &= p2->is_ascii; + return 0; +} + +/* 'str' must be a string */ +static int string_buffer_concat_utf8(JSContext *ctx, StringBuffer *s, JSValue str, + uint32_t start, uint32_t end) +{ + JSValue val2; + + if (end <= start) + return 0; + /* XXX: avoid explicitly constructing the substring */ + val2 = js_sub_string_utf8(ctx, str, start, end); + if (JS_IsException(val2)) { + s->buffer_ref.val = JS_EXCEPTION; + return -1; + } + return string_buffer_concat_str(ctx, s, val2); +} + +static int string_buffer_concat_utf16(JSContext *ctx, StringBuffer *s, JSValue str, + uint32_t start, uint32_t end) +{ + uint32_t start_utf8, end_utf8; + if (end <= start) + return 0; + start_utf8 = js_string_utf16_to_utf8_pos(ctx, str, start); + end_utf8 = js_string_utf16_to_utf8_pos(ctx, str, end); + return string_buffer_concat_utf8(ctx, s, str, start_utf8, end_utf8); +} + +static int string_buffer_concat(JSContext *ctx, StringBuffer *s, JSValue val2) +{ + val2 = JS_ToString(ctx, val2); + if (JS_IsException(val2)) { + s->buffer_ref.val = JS_EXCEPTION; + return -1; + } + return string_buffer_concat_str(ctx, s, val2); +} + +/* XXX: could optimize */ +static int string_buffer_putc(JSContext *ctx, StringBuffer *s, int c) +{ + return string_buffer_concat_str(ctx, s, JS_NewStringChar(c)); +} + +static int string_buffer_puts(JSContext *ctx, StringBuffer *s, const char *str) +{ + JSValue val; + + /* XXX: avoid this allocation */ + val = JS_NewString(ctx, str); + if (JS_IsException(val)) + return -1; + return string_buffer_concat_str(ctx, s, val); +} + +static JSValue string_buffer_pop(JSContext *ctx, StringBuffer *s) +{ + JSValue res; + if (JS_IsException(s->buffer_ref.val) || + JS_IsString(ctx, s->buffer_ref.val)) { + res = s->buffer_ref.val; + } else { + if (s->len != 0) { + /* add the trailing '\0' */ + JSByteArray *arr = JS_VALUE_TO_PTR(s->buffer_ref.val); + arr->buf[s->len] = '\0'; + } + res = js_byte_array_to_string(ctx, s->buffer_ref.val, s->len, s->is_ascii); + } + ctx->top_gc_ref = s->buffer_ref.prev; + return res; +} + +/* val1 and val2 must be strings or exception */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue val1, JSValue val2) +{ + StringBuffer b_s, *b = &b_s; + + if (JS_IsException(val1) || + JS_IsException(val2)) + return JS_EXCEPTION; + + string_buffer_push(ctx, b, 0); + string_buffer_concat_str(ctx, b, val1); /* no memory allocation */ + string_buffer_concat_str(ctx, b, val2); + return string_buffer_pop(ctx, b); +} + +static BOOL js_string_eq(JSContext *ctx, JSValue val1, JSValue val2) +{ + JSStringCharBuf buf1, buf2; + JSString *p1, *p2; + + p1 = get_string_ptr(ctx, &buf1, val1); + p2 = get_string_ptr(ctx, &buf2, val2); + if (p1->len != p2->len) + return FALSE; + return !memcmp(p1->buf, p2->buf, p1->len); +} + +/* Return the unicode character containing the byte at position + 'i'. Return -1 in case of error. */ +static int string_get_cp(const uint8_t *p) +{ + size_t clen; + while ((*p & 0xc0) == 0x80) + p--; + return utf8_get(p, &clen); +} + +static int js_string_compare(JSContext *ctx, JSValue val1, JSValue val2) +{ + JSStringCharBuf buf1, buf2; + int len, i, res; + JSString *p1, *p2; + + p1 = get_string_ptr(ctx, &buf1, val1); + p2 = get_string_ptr(ctx, &buf2, val2); + len = min_int(p1->len, p2->len); + for(i = 0; i < len; i++) { + if (p1->buf[i] != p2->buf[i]) + break; + } + if (i != len) { + int c1, c2; + /* if valid UTF-8, the strings cannot be equal at this point */ + /* Note: UTF-16 does not preserve unicode order like UTF-8 */ + c1 = string_get_cp(p1->buf + i); + c2 = string_get_cp(p2->buf + i); + if ((c1 < 0x10000 && c2 < 0x10000) || + (c1 >= 0x10000 && c2 >= 0x10000)) { + if (c1 < c2) + res = -1; + else + res = 1; + } else if (c1 < 0x10000) { + /* p1 < p2 if same first UTF-16 char */ + c2 = 0xd800 + ((c2 - 0x10000) >> 10); + if (c1 <= c2) + res = -1; + else + res = 1; + } else { + /* p1 > p2 if same first UTF-16 char */ + c1 = 0xd800 + ((c1 - 0x10000) >> 10); + if (c1 < c2) + res = -1; + else + res = 1; + } + } else { + if (p1->len == p2->len) + res = 0; + else if (p1->len < p2->len) + res = -1; + else + res = 1; + } + return res; +} + +/* return the string length in UTF16 characters. 'val' must be a + string char or a string */ +static int js_string_len(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + return JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1; + } else { + JSString *p; + p = JS_VALUE_TO_PTR(val); + if (p->is_ascii) + return p->len; + else + return js_string_utf8_to_utf16_pos(ctx, val, p->len * 2); + } +} + +/* return the UTF-16 code or the unicode character at a given UTF-8 + position or -1 if outside the string */ +static int string_getcp(JSContext *ctx, JSValue str, uint32_t utf16_pos, BOOL is_codepoint) +{ + JSString *p; + JSStringCharBuf buf; + uint32_t surrogate_flag, c, utf8_pos; + size_t clen; + + utf8_pos = js_string_utf16_to_utf8_pos(ctx, str, utf16_pos); + surrogate_flag = utf8_pos & 1; + utf8_pos >>= 1; + p = get_string_ptr(ctx, &buf, str); + if (utf8_pos >= p->len) + return -1; + c = utf8_get(p->buf + utf8_pos, &clen); + if (c < 0x10000 || (!surrogate_flag && is_codepoint)) { + return c; + } else { + c -= 0x10000; + if (!surrogate_flag) + return 0xd800 + (c >> 10); /* left surrogate */ + else + return 0xdc00 + (c & 0x3ff); /* right surrogate */ + } +} + +static int string_getc(JSContext *ctx, JSValue str, uint32_t utf16_pos) +{ + return string_getcp(ctx, str, utf16_pos, FALSE); +} + +/* precondition: 0 <= start <= end <= string length */ +static JSValue js_sub_string(JSContext *ctx, JSValue val, int start, int end) +{ + uint32_t start_utf8, end_utf8; + + if (end <= start) + return js_get_atom(ctx, JS_ATOM_empty); + start_utf8 = js_string_utf16_to_utf8_pos(ctx, val, start); + end_utf8 = js_string_utf16_to_utf8_pos(ctx, val, end); + return js_sub_string_utf8(ctx, val, start_utf8, end_utf8); +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return TRUE if the property 'val' represents a numeric property. -1 + is returned in case of exception. 'val' must be a string. It is + assumed that NaN and infinities have already been handled. */ +static int js_is_numeric_string(JSContext *ctx, JSValue val) +{ + int c, len; + double d; + const char *r, *q; + JSString *p; + JSByteArray *tmp_arr; + JSGCRef val_ref; + char buf[32]; /* enough for js_dtoa() */ + + p = JS_VALUE_TO_PTR(val); + /* the fast case is when the string is not a number */ + if (p->len == 0 || !p->is_ascii) + return FALSE; + q = (const char *)p->buf; + c = *q; + if (c == '-') { + if (p->len == 1) + return FALSE; + q++; + c = *q; + } + if (!is_num(c)) + return FALSE; + + JS_PUSH_VALUE(ctx, val); + tmp_arr = js_alloc_byte_array(ctx, max_int(sizeof(JSATODTempMem), + sizeof(JSDTOATempMem))); + JS_POP_VALUE(ctx, val); + if (!tmp_arr) + return -1; + p = JS_VALUE_TO_PTR(val); + d = js_atod((char *)p->buf, &r, 10, 0, (JSATODTempMem *)tmp_arr->buf); + if ((r - (char *)p->buf) != p->len) { + js_free(ctx, tmp_arr); + return FALSE; + } + len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE, (JSDTOATempMem *)tmp_arr->buf); + js_free(ctx, tmp_arr); + return (p->len == len && !memcmp(buf, p->buf, len)); +} + +/* return JS_NULL if not found */ +static JSValue find_atom(JSContext *ctx, int *pidx, const JSValueArray *arr, int len, JSValue val) +{ + int a, b, m, r; + JSValue val1; + + a = 0; + b = len - 1; + while (a <= b) { + m = (a + b) >> 1; + val1 = arr->arr[m]; + r = js_string_compare(ctx, val, val1); + if (r == 0) { + /* found */ + *pidx = m; + return val1; + } else if (r < 0) { + b = m - 1; + } else { + a = m + 1; + } + } + *pidx = a; + return JS_NULL; +} + +/* if 'val' is not a string, it is returned */ +/* XXX: use hash table */ +static JSValue JS_MakeUniqueString(JSContext *ctx, JSValue val) +{ + JSString *p; + int a, is_numeric, i; + JSValueArray *arr; + const JSValueArray *arr1; + JSValue val1, new_tab; + JSGCRef val_ref; + + if (!JS_IsPtr(val)) + return val; + p = JS_VALUE_TO_PTR(val); + if (p->mtag != JS_MTAG_STRING || p->is_unique) + return val; + + /* not unique: find it in the ROM or RAM sorted unique string table */ + for(i = 0; i < ctx->n_rom_atom_tables; i++) { + arr1 = ctx->rom_atom_tables[i]; + if (arr1) { + val1 = find_atom(ctx, &a, arr1, arr1->size, val); + if (!JS_IsNull(val1)) + return val1; + } + } + + arr = JS_VALUE_TO_PTR( ctx->unique_strings); + val1 = find_atom(ctx, &a, arr, ctx->unique_strings_len, val); + if (!JS_IsNull(val1)) + return val1; + + JS_PUSH_VALUE(ctx, val); + is_numeric = js_is_numeric_string(ctx, val); + JS_POP_VALUE(ctx, val); + if (is_numeric < 0) + return JS_EXCEPTION; + + /* not found: add it in the table */ + JS_PUSH_VALUE(ctx, val); + new_tab = js_resize_value_array(ctx, ctx->unique_strings, + ctx->unique_strings_len + 1); + JS_POP_VALUE(ctx, val); + if (JS_IsException(new_tab)) + return JS_EXCEPTION; + ctx->unique_strings = new_tab; + arr = JS_VALUE_TO_PTR( ctx->unique_strings); + memmove(&arr->arr[a + 1], &arr->arr[a], + sizeof(arr->arr[0]) * (ctx->unique_strings_len - a)); + arr->arr[a] = val; + p = JS_VALUE_TO_PTR(val); + p->is_unique = TRUE; + p->is_numeric = is_numeric; + ctx->unique_strings_len++; + return val; +} + +static int JS_ToBool(JSContext *ctx, JSValue val) +{ + if (JS_IsInt(val)) { + return JS_VALUE_GET_INT(val) != 0; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + double d; + d = js_get_short_float(val); + return !isnan(d) && d != 0; + } else +#endif + if (!JS_IsPtr(val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_SPECIAL_VALUE(val); + case JS_TAG_SHORT_FUNC: + case JS_TAG_STRING_CHAR: + return TRUE; + default: + return FALSE; + } + } else { + JSMemBlockHeader *h = JS_VALUE_TO_PTR(val); + switch(h->mtag) { + case JS_MTAG_STRING: + { + JSString *p = (JSString *)h; + return p->len != 0; + } + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = (JSFloat64 *)h; + return !isnan(p->u.dval) && p->u.dval != 0; + } + default: + case JS_MTAG_OBJECT: + return TRUE; + } + } +} + +/* plen can be NULL. No memory allocation is done if 'val' already is + a string. */ +const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val, + JSCStringBuf *buf) +{ + const char *p; + int len; + + val = JS_ToString(ctx, val); + if (JS_IsException(val)) + return NULL; + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + len = get_short_string(buf->buf, val); + p = (const char *)buf->buf; + } else { + JSString *r; + r = JS_VALUE_TO_PTR(val); + p = (const char *)r->buf; + len = r->len; + } + if (plen) + *plen = len; + return p; +} + +const char *JS_ToCString(JSContext *ctx, JSValue val, JSCStringBuf *buf) +{ + return JS_ToCStringLen(ctx, NULL, val, buf); +} + +JSValue JS_GetException(JSContext *ctx) +{ + JSValue obj; + obj = ctx->current_exception; + ctx->current_exception = JS_UNDEFINED; + return obj; +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val) +{ + if (val == JS_NULL || val == JS_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +/* 'val' must be a string. return TRUE if the string represents a + short integer */ +static inline BOOL is_num_string(JSContext *ctx, int32_t *pval, JSValue val) +{ + JSStringCharBuf buf; + uint32_t n; + uint64_t n64; + JSString *p1; + int c, is_neg; + const uint8_t *p, *p_end; + + p1 = get_string_ptr(ctx, &buf, val); + if (p1->len == 0 || p1->len > 11 || !p1->is_ascii) + return FALSE; + p = p1->buf; + p_end = p + p1->len; + c = *p++; + is_neg = 0; + if (c == '-') { + if (p >= p_end) + return FALSE; + is_neg = 1; + c = *p++; + } + if (!is_num(c)) + return FALSE; + if (c == '0') { + if (p != p_end || is_neg) + return FALSE; + n = 0; + } else { + n = c - '0'; + while (p < p_end) { + c = *p++; + if (!is_num(c)) + return FALSE; + /* XXX: simplify ? */ + n64 = (uint64_t)n * 10 + (c - '0'); + if (n64 > (JS_SHORTINT_MAX + is_neg)) + return FALSE; + n = n64; + } + if (is_neg) + n = -n; + } + *pval = n; + return TRUE; +} + +/* return TRUE if the property 'val' represent a numeric property. It + is assumed that the shortint case has been tested before */ +static BOOL JS_IsNumericProperty(JSContext *ctx, JSValue val) +{ + JSString *p; + if (!JS_IsPtr(val)) + return FALSE; /* JS_TAG_STRING_CHAR */ + p = JS_VALUE_TO_PTR(val); + return p->is_numeric; +} + +static JSValueArray *js_alloc_value_array(JSContext *ctx, int init_base, int new_size) +{ + JSValueArray *arr; + int i; + + if (new_size > JS_VALUE_ARRAY_SIZE_MAX) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + arr = js_malloc(ctx, sizeof(JSValueArray) + new_size * sizeof(JSValue), JS_MTAG_VALUE_ARRAY); + if (!arr) + return NULL; + arr->size = new_size; + for(i = init_base; i < new_size; i++) + arr->arr[i] = JS_UNDEFINED; + return arr; +} + +/* val can be JS_NULL (zero size). 'prop_base' is non zero only when + * resizing the property arrays so that the property array has a size + * which is a multiple of 3 */ +static JSValue js_resize_value_array2(JSContext *ctx, JSValue val, int new_size, int prop_base) +{ + JSValueArray *slots, *new_slots; + int old_size, new_size1; + JSGCRef val_ref; + + if (val == JS_NULL) { + slots = NULL; + old_size = 0; + } else { + slots = JS_VALUE_TO_PTR(val); + old_size = slots->size; + } + if (unlikely(new_size > old_size)) { + new_size1 = old_size + old_size / 2; + if (new_size1 > new_size) { + new_size = new_size1; + /* ensure that the property array has a size which is a + * multiple of 3 */ + if (prop_base != 0) { + int align = (new_size - prop_base) % 3; + if (align != 0) + new_size += 3 - align; + } + } + new_size = max_int(new_size, old_size + old_size / 2); + JS_PUSH_VALUE(ctx, val); + new_slots = js_alloc_value_array(ctx, old_size, new_size); + JS_POP_VALUE(ctx, val); + if (!new_slots) + return JS_EXCEPTION; + if (old_size > 0) { + slots = JS_VALUE_TO_PTR(val); + memcpy(new_slots->arr, slots->arr, old_size * sizeof(JSValue)); + } + val = JS_VALUE_FROM_PTR(new_slots); + } + return val; +} + +static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size) +{ + return js_resize_value_array2(ctx, val, new_size, 0); +} + +/* no allocation is done */ +static void js_shrink_value_array(JSContext *ctx, JSValue *pval, int new_size) +{ + JSValueArray *arr; + if (*pval == JS_NULL) + return; + arr = JS_VALUE_TO_PTR(*pval); + assert(new_size <= arr->size); + if (new_size == 0) { + js_free(ctx, arr); + *pval = JS_NULL; + } else { + arr = js_shrink(ctx, arr, sizeof(JSValueArray) + new_size * sizeof(JSValue)); + arr->size = new_size; + } +} + +static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size) +{ + JSByteArray *arr; + + if (size > JS_BYTE_ARRAY_SIZE_MAX) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + arr = js_malloc(ctx, sizeof(JSByteArray) + size, JS_MTAG_BYTE_ARRAY); + if (!arr) + return NULL; + arr->size = size; + return arr; +} + +static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size) +{ + JSByteArray *arr, *new_arr; + int old_size; + JSGCRef val_ref; + + if (val == JS_NULL) { + arr = NULL; + old_size = 0; + } else { + arr = JS_VALUE_TO_PTR(val); + old_size = arr->size; + } + if (unlikely(new_size > old_size)) { + new_size = max_int(new_size, old_size + old_size / 2); + JS_PUSH_VALUE(ctx, val); + new_arr = js_alloc_byte_array(ctx, new_size); + JS_POP_VALUE(ctx, val); + if (!new_arr) + return JS_EXCEPTION; + if (old_size > 0) { + arr = JS_VALUE_TO_PTR(val); + memcpy(new_arr->buf, arr->buf, old_size); + } + val = JS_VALUE_FROM_PTR(new_arr); + } + return val; +} + +static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size) +{ + JSByteArray *arr; + if (*pval == JS_NULL) + return; + arr = JS_VALUE_TO_PTR(*pval); + assert(new_size <= arr->size); + if (new_size == 0) { + js_free(ctx, arr); + *pval = JS_NULL; + } else { + arr = js_shrink(ctx, arr, sizeof(JSByteArray) + new_size); + arr->size = new_size; + } +} + +/* extra_size is in bytes */ +static JSObject *JS_NewObjectProtoClass1(JSContext *ctx, JSValue proto, + int class_id, int extra_size) +{ + JSObject *p; + JSGCRef proto_ref; + extra_size = (unsigned)(extra_size + JSW - 1) / JSW; + JS_PUSH_VALUE(ctx, proto); + p = js_malloc(ctx, offsetof(JSObject, u) + extra_size * JSW, JS_MTAG_OBJECT); + JS_POP_VALUE(ctx, proto); + if (!p) + return NULL; + p->class_id = class_id; + p->extra_size = extra_size; + p->proto = proto; + p->props = ctx->empty_props; + return p; +} + +static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size) +{ + JSObject *p; + p = JS_NewObjectProtoClass1(ctx, proto, class_id, extra_size); + if (!p) + return JS_EXCEPTION; + else + return JS_VALUE_FROM_PTR(p); +} + +static JSValue JS_NewObjectClass(JSContext *ctx, int class_id, int extra_size) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id, extra_size); +} + +JSValue JS_NewObjectClassUser(JSContext *ctx, int class_id) +{ + JSObject *p; + assert(class_id >= JS_CLASS_USER); + p = JS_NewObjectProtoClass1(ctx, ctx->class_proto[class_id], class_id, + sizeof(JSObjectUserData)); + if (!p) + return JS_EXCEPTION; + p->u.user.opaque = NULL; + return JS_VALUE_FROM_PTR(p); +} + +JSValue JS_NewObject(JSContext *ctx) +{ + return JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0); +} + +/* same as JS_NewObject() but preallocate for 'n' properties */ +JSValue JS_NewObjectPrealloc(JSContext *ctx, int n) +{ + JSValue obj; + JSValueArray *arr; + JSObject *p; + JSGCRef obj_ref; + + obj = JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0); + if (JS_IsException(obj) || n <= 0) + return obj; + JS_PUSH_VALUE(ctx, obj); + arr = js_alloc_props(ctx, n); + JS_POP_VALUE(ctx, obj); + if (!arr) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(obj); + p->props = JS_VALUE_FROM_PTR(arr); + return obj; +} + +JSValue JS_NewArray(JSContext *ctx, int initial_len) +{ + JSObject *p; + JSValue val; + JSGCRef val_ref; + + val = JS_NewObjectClass(ctx, JS_CLASS_ARRAY, sizeof(JSArrayData)); + if (JS_IsException(val)) + return val; + p = JS_VALUE_TO_PTR(val); + p->u.array.tab = JS_NULL; + p->u.array.len = 0; + if (initial_len > 0) { + JSValueArray *arr; + JS_PUSH_VALUE(ctx, val); + arr = js_alloc_value_array(ctx, 0, initial_len); + JS_POP_VALUE(ctx, val); + if (!arr) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(val); + p->u.array.tab = JS_VALUE_FROM_PTR(arr); + p->u.array.len = initial_len; + } + return val; +} + +static inline uint32_t hash_prop(JSValue prop) +{ + return (prop / JSW) ^ (prop % JSW); /* XXX: improve */ +} + +/* return NULL if not found */ +static force_inline JSProperty *find_own_property_inlined(JSContext *ctx, + JSObject *p, JSValue prop) +{ + JSValueArray *arr; + JSProperty *pr; + uint32_t hash_mask, h, idx; + + arr = JS_VALUE_TO_PTR(p->props); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + h = hash_prop(prop) & hash_mask; + idx = arr->arr[2 + h]; /* JSValue, hence idx * 2 */ + while (idx != 0) { + pr = (JSProperty *)((uint8_t *)arr->arr + idx * (sizeof(JSValue) / 2)); + if (pr->key == prop) + return pr; + idx = pr->hash_next; /* JSValue, hence idx * 2 */ + } + return NULL; +} + +static inline JSProperty *find_own_property(JSContext *ctx, + JSObject *p, JSValue prop) +{ + return find_own_property_inlined(ctx, p, prop); +} + +static JSValue get_special_prop(JSContext *ctx, JSValue val) +{ + int idx; + /* 'prototype' or 'constructor' property in ROM */ + idx = JS_VALUE_GET_INT(val); + if (idx >= 0) + return ctx->class_proto[idx]; + else + return ctx->class_obj[-idx - 1]; +} + +/* return the value or: + - exception + - tail call : returned in case of getter and handle_getset = + true. The function is put on the stack +*/ +static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj, JSValue prop, + BOOL allow_tail_call) +{ + JSObject *p; + JSValue proto; + JSProperty *pr; + + if (unlikely(!JS_IsPtr(obj))) { + if (JS_IsIntOrShortFloat(obj)) { + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(obj)) { + case JS_TAG_BOOL: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]); + break; + case JS_TAG_SHORT_FUNC: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]); + break; + case JS_TAG_STRING_CHAR: + goto string_proto; + case JS_TAG_NULL: + return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of undefined", prop); + default: + goto no_prop; + } + } + } else { + p = JS_VALUE_TO_PTR(obj); + } + if (unlikely(p->mtag != JS_MTAG_OBJECT)) { + switch(p->mtag) { + case JS_MTAG_FLOAT64: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + break; + case JS_MTAG_STRING: + string_proto: + { + if (JS_IsInt(prop)) { + JSValue ret; + ret = js_string_charAt(ctx, &obj, 1, &prop, magic_internalAt); + if (!JS_IsUndefined(ret)) + return ret; + } + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]); + } + break; + default: + no_prop: + return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of value", prop); + } + } + + for(;;) { + if (p->class_id == JS_CLASS_ARRAY) { + if (JS_IsInt(prop)) { + uint32_t idx = JS_VALUE_GET_INT(prop); + if (idx < p->u.array.len) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + return arr->arr[idx]; + } + } else if (JS_IsNumericProperty(ctx, prop)) { + return JS_UNDEFINED; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + if (JS_IsInt(prop)) { + uint32_t idx = JS_VALUE_GET_INT(prop); + JSObject *pbuffer; + JSByteArray *arr; + if (idx < p->u.typed_array.len) { + idx += p->u.typed_array.offset; + pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer); + switch(p->class_id) { + default: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + return JS_NewShortInt(*((uint8_t *)arr->buf + idx)); + case JS_CLASS_INT8_ARRAY: + return JS_NewShortInt(*((int8_t *)arr->buf + idx)); + case JS_CLASS_INT16_ARRAY: + return JS_NewShortInt(*((int16_t *)arr->buf + idx)); + case JS_CLASS_UINT16_ARRAY: + return JS_NewShortInt(*((uint16_t *)arr->buf + idx)); + case JS_CLASS_INT32_ARRAY: + return JS_NewInt32(ctx, *((int32_t *)arr->buf + idx)); + case JS_CLASS_UINT32_ARRAY: + return JS_NewUint32(ctx, *((uint32_t *)arr->buf + idx)); + case JS_CLASS_FLOAT32_ARRAY: + return JS_NewFloat64(ctx, *((float *)arr->buf + idx)); + case JS_CLASS_FLOAT64_ARRAY: + return JS_NewFloat64(ctx, *((double *)arr->buf + idx)); + } + } + } else if (JS_IsNumericProperty(ctx, prop)) { + return JS_UNDEFINED; + } + } + + pr = find_own_property(ctx, p, prop); + if (pr) { + if (likely(pr->prop_type == JS_PROP_NORMAL)) { + return pr->value; + } else if (pr->prop_type == JS_PROP_VARREF) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + /* always detached */ + return pv->u.value; + } else if (pr->prop_type == JS_PROP_SPECIAL) { + return get_special_prop(ctx, pr->value); + } else { + JSValueArray *arr = JS_VALUE_TO_PTR(pr->value); + JSValue getter = arr->arr[0]; + if (getter == JS_UNDEFINED) + return JS_UNDEFINED; + if (allow_tail_call) { + /* It is assumed 'this_obj' is on the stack and + that the stack has some slack to add one element. */ + ctx->sp[-1] = ctx->sp[0]; + ctx->sp[0] = getter; + ctx->sp--; + return JS_NewTailCall(0); + } else { + JSGCRef getter_ref, obj_ref; + int err; + JS_PUSH_VALUE(ctx, getter); + JS_PUSH_VALUE(ctx, obj); + err = JS_StackCheck(ctx, 2); + JS_POP_VALUE(ctx, obj); + JS_POP_VALUE(ctx, getter); + if (err) + return JS_EXCEPTION; + JS_PushArg(ctx, getter); + JS_PushArg(ctx, obj); + return JS_Call(ctx, 0); + } + } + } + /* look in the prototype */ + proto = p->proto; + if (proto == JS_NULL) + break; + p = JS_VALUE_TO_PTR(proto); + } + return JS_UNDEFINED; +} + +static JSValue JS_GetProperty(JSContext *ctx, JSValue obj, JSValue prop) +{ + return JS_GetPropertyInternal(ctx, obj, prop, FALSE); +} + +JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, const char *str) +{ + JSValue prop; + JSGCRef this_obj_ref; + + JS_PUSH_VALUE(ctx, this_obj); + prop = JS_NewString(ctx, str); + if (!JS_IsException(prop)) { + prop = JS_ToPropertyKey(ctx, prop); + } + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(prop)) + return prop; + return JS_GetProperty(ctx, this_obj, prop); +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue obj, uint32_t idx) +{ + if (idx > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array index"); + return JS_GetProperty(ctx, obj, JS_NewInt32(ctx, idx)); +} + +static BOOL JS_HasProperty(JSContext *ctx, JSValue obj, JSValue prop) +{ + JSObject *p; + JSProperty *pr; + + if (!JS_IsPtr(obj)) + return FALSE; + p = JS_VALUE_TO_PTR(obj); + if (p->mtag != JS_MTAG_OBJECT) + return FALSE; + for(;;) { + pr = find_own_property(ctx, p, prop); + if (pr) + return TRUE; + obj = p->proto; + if (obj == JS_NULL) + break; + p = JS_VALUE_TO_PTR(obj); + } + return FALSE; +} + +static int get_prop_hash_size_log2(int prop_count) +{ + /* XXX: adjust ? */ + if (prop_count <= 1) + return 0; + else + return (32 - clz32(prop_count - 1)) - 1; +} + +/* allocate 'n' properties, assuming n >= 1 */ +static JSValueArray *js_alloc_props(JSContext *ctx, int n) +{ + int hash_size_log2, hash_mask, size, i, first_free; + JSValueArray *arr; + JSProperty *pr; + + hash_size_log2 = get_prop_hash_size_log2(n); + hash_mask = (1 << hash_size_log2) - 1; + first_free = 2 + hash_mask + 1; + size = first_free + 3 * n; + arr = js_alloc_value_array(ctx, 0, size); + if (!arr) + return NULL; + arr->arr[0] = JS_NewShortInt(0); /* no property is allocated yet */ + arr->arr[1] = JS_NewShortInt(hash_mask); + for(i = 0; i <= hash_mask; i++) + arr->arr[2 + i] = 0; + pr = NULL; /* avoid warning */ + for(i = 0; i < n; i++) { + pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i]; + pr->key = JS_UNINITIALIZED; + } + /* last property */ + pr->hash_next = first_free << 1; + return arr; +} + +static void js_rehash_props(JSContext *ctx, JSObject *p, BOOL gc_rehash) +{ + JSValueArray *arr; + int prop_count, hash_mask, h, idx, i, j; + JSProperty *pr; + + arr = JS_VALUE_TO_PTR(p->props); + if (JS_IS_ROM_PTR(ctx, arr)) + return; + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + if (hash_mask == 0 && gc_rehash) + return; /* no need to rehash if single hash entry */ + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + for(i = 0; i <= hash_mask; i++) { + arr->arr[2 + i] = JS_NewShortInt(0); + } + for(i = 0, j = 0; j < prop_count; i++) { + idx = 2 + (hash_mask + 1) + 3 * i; + pr = (JSProperty *)&arr->arr[idx]; + if (pr->key != JS_UNINITIALIZED) { + h = hash_prop(pr->key) & hash_mask; + pr->hash_next = arr->arr[2 + h]; + arr->arr[2 + h] = JS_NewShortInt(idx); + j++; + } + } +} + +/* Compact the properties. No memory allocation is done */ +static void js_compact_props(JSContext *ctx, JSObject *p) +{ + JSValueArray *arr; + int prop_count, hash_mask, i, j, hash_size_log2; + int new_size, new_hash_mask; + JSProperty *pr, *pr1; + + arr = JS_VALUE_TO_PTR(p->props); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + + /* no property */ + if (prop_count == 0) { + if (p->props != ctx->empty_props) { + //js_free(ctx, p->props); + p->props = ctx->empty_props; + } + return; + } + + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + hash_size_log2 = get_prop_hash_size_log2(prop_count); + new_hash_mask = min_int(hash_mask, (1 << hash_size_log2) - 1); + new_size = 2 + new_hash_mask + 1 + 3 * prop_count; + if (new_size >= arr->size) + return; /* nothing to do */ + // printf("compact_props: new_size=%d size=%d hash=%d\n", new_size, arr->size, new_hash_mask); + + arr->arr[1] = JS_NewShortInt(new_hash_mask); + + /* move the properties, skipping the deleted ones */ + for(i = 0, j = 0; j < prop_count; i++) { + pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i]; + if (pr->key != JS_UNINITIALIZED) { + pr1 = (JSProperty *)&arr->arr[2 + (new_hash_mask + 1) + 3 * j]; + *pr1 = *pr; + j++; + } + } + + js_shrink_value_array(ctx, &p->props, new_size); + + js_rehash_props(ctx, p, FALSE); +} + +/* if the existing properties are in ROM, copy them to RAM. Return non zero if error */ +static int js_update_props(JSContext *ctx, JSValue obj) +{ + JSObject *p; + JSValueArray *arr, *arr1; + JSGCRef obj_ref; + int i, idx, prop_count, hash_mask; + JSProperty *pr; + + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->props); + if (!JS_IS_ROM_PTR(ctx, arr)) + return 0; + JS_PUSH_VALUE(ctx, obj); + arr1 = js_alloc_value_array(ctx, 0, arr->size); + JS_POP_VALUE(ctx, obj); + if (!arr1) + return -1; + /* no rehashing is needed because all the atoms are in ROM */ + memcpy(arr1->arr, arr->arr, arr->size * sizeof(JSValue)); + prop_count = JS_VALUE_GET_INT(arr1->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr1->arr[1]); + /* no deleted properties in ROM */ + assert(arr1->size == 2 + (hash_mask + 1) + 3 * prop_count); + /* convert JS_PROP_SPECIAL properties ("prototype" and "constructor") */ + for(i = 0; i < prop_count; i++) { + idx = 2 + (hash_mask + 1) + 3 * i; + pr = (JSProperty *)&arr1->arr[idx]; + if (pr->prop_type == JS_PROP_SPECIAL) { + pr->value = get_special_prop(ctx, pr->value); + pr->prop_type = JS_PROP_NORMAL; + } + } + + p = JS_VALUE_TO_PTR(obj); + p->props = JS_VALUE_FROM_PTR(arr1); + return 0; +} + +/* compute 'first_free' in a property list */ +static int get_first_free(JSValueArray *arr) +{ + JSProperty *pr1; + int first_free; + + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + if (pr1->key == JS_UNINITIALIZED) + first_free = pr1->hash_next >> 1; + else + first_free = arr->size; + return first_free; +} + +/* It is assumed that the property does not already exists. */ +static JSProperty *js_create_property(JSContext *ctx, JSValue obj, + JSValue prop) +{ + JSObject *p; + JSValueArray *arr; + int prop_count, hash_mask, new_size, h, first_free, new_hash_mask; + JSProperty *pr, *pr1; + JSValue new_props; + JSGCRef obj_ref, prop_ref; + + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->props); + + // JS_DumpValue(ctx, "create", prop); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + /* extend the array if no space left (this single test is valid + even if the property list is empty) */ + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + if (pr1->key != JS_UNINITIALIZED) { + if (p->props == ctx->empty_props) { + /* XXX: remove and move empty_props to ROM */ + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + arr = js_alloc_props(ctx, 1); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!arr) + return NULL; + p = JS_VALUE_TO_PTR(obj); + p->props = JS_VALUE_FROM_PTR(arr); + first_free = 3; + } else { + first_free = arr->size; + new_size = first_free + 3; + new_hash_mask = hash_mask; + if ((prop_count + 1) > 2 * (hash_mask + 1)) { + /* resize the hash table if too many properties */ + new_hash_mask = 2 * (hash_mask + 1) - 1; + new_size += new_hash_mask - hash_mask; + } + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + // printf("resize_props: new_size=%d hash=%d %d\n", new_size, new_hash_mask, hash_mask); + new_props = js_resize_value_array2(ctx, p->props, new_size, 2 + new_hash_mask + 1); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(new_props)) + return NULL; + p = JS_VALUE_TO_PTR(obj); + p->props = new_props; + arr = JS_VALUE_TO_PTR(p->props); + if (new_hash_mask != hash_mask) { + /* rebuild the hash table */ + memmove(&arr->arr[2 + (new_hash_mask + 1)], + &arr->arr[2 + (hash_mask + 1)], + (first_free - (2 + hash_mask + 1)) * sizeof(JSValue)); + first_free += new_hash_mask - hash_mask; + hash_mask = new_hash_mask; + arr->arr[1] = JS_NewShortInt(hash_mask); + js_rehash_props(ctx, p, FALSE); + } + } + /* ensure the last element is marked as uninitialized to store 'first_free' */ + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + pr1->key = JS_UNINITIALIZED; + } else { + first_free = pr1->hash_next >> 1; + } + + pr = (JSProperty *)&arr->arr[first_free]; + pr->key = prop; + pr->value = JS_UNDEFINED; + pr->prop_type = JS_PROP_NORMAL; + h = hash_prop(prop) & hash_mask; + pr->hash_next = arr->arr[2 + h]; + arr->arr[2 + h] = JS_NewShortInt(first_free); + arr->arr[0] = JS_NewShortInt(prop_count + 1); + /* update first_free */ + first_free += 3; + if (first_free < arr->size) { + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + pr1->hash_next = first_free << 1; + } + + return pr; +} + +/* don't do property lookup if not present */ +#define JS_DEF_PROP_LOOKUP (1 << 0) +/* return the raw property value */ +#define JS_DEF_PROP_RET_VAL (1 << 1) +#define JS_DEF_PROP_HAS_VALUE (1 << 2) +#define JS_DEF_PROP_HAS_GET (1 << 3) +#define JS_DEF_PROP_HAS_SET (1 << 4) + +/* XXX: handle arrays and typed arrays */ +static JSValue JS_DefinePropertyInternal(JSContext *ctx, JSValue obj, + JSValue prop, JSValue val, + JSValue setter, int flags) +{ + JSProperty *pr; + JSValueArray *arr; + JSGCRef obj_ref, prop_ref, val_ref, setter_ref; + int ret, prop_type; + + /* move to RAM if needed */ + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + ret = js_update_props(ctx, obj); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (ret) + return JS_EXCEPTION; + + if (flags & JS_DEF_PROP_LOOKUP) { + pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop); + if (pr) { + if (flags & JS_DEF_PROP_HAS_VALUE) { + if (pr->prop_type == JS_PROP_NORMAL) { + pr->value = val; + } else if (pr->prop_type == JS_PROP_VARREF) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + pv->u.value = val; + } else { + goto error_modify; + } + } else if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) { + if (pr->prop_type != JS_PROP_GETSET) { + error_modify: + return JS_ThrowTypeError(ctx, "cannot modify getter/setter/value kind"); + } + arr = JS_VALUE_TO_PTR(pr->value); + if (unlikely(JS_IS_ROM_PTR(ctx, arr))) { + /* move to RAM */ + JSValueArray *arr2; + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + arr2 = js_alloc_value_array(ctx, 0, 2); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!arr2) + return JS_EXCEPTION; + pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop); + arr = JS_VALUE_TO_PTR(pr->value); + arr2->arr[0] = arr->arr[0]; + arr2->arr[1] = arr->arr[1]; + pr->value = JS_VALUE_FROM_PTR(arr2); + arr = arr2; + } + if (flags & JS_DEF_PROP_HAS_GET) + arr->arr[0] = val; + if (flags & JS_DEF_PROP_HAS_SET) + arr->arr[1] = setter; + } + goto done; + } + } + + if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) { + prop_type = JS_PROP_GETSET; + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + arr = js_alloc_value_array(ctx, 0, 2); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!arr) + return JS_EXCEPTION; + arr->arr[0] = val; + arr->arr[1] = setter; + val = JS_VALUE_FROM_PTR(arr); + } else if (obj == ctx->global_obj) { + JSVarRef *pv; + + prop_type = JS_PROP_VARREF; + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + pv = js_malloc(ctx, sizeof(JSVarRef) - sizeof(JSValue), JS_MTAG_VARREF); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!pv) + return JS_EXCEPTION; + pv->is_detached = TRUE; + pv->u.value = val; + val = JS_VALUE_FROM_PTR(pv); + } else { + prop_type = JS_PROP_NORMAL; + } + JS_PUSH_VALUE(ctx, val); + pr = js_create_property(ctx, obj, prop); + JS_POP_VALUE(ctx, val); + if (!pr) + return JS_EXCEPTION; + pr->prop_type = prop_type; + pr->value = val; + done: + if (flags & JS_DEF_PROP_RET_VAL) { + return pr->value; + } else { + return JS_UNDEFINED; + } +} + +static JSValue JS_DefinePropertyValue(JSContext *ctx, JSValue obj, + JSValue prop, JSValue val) +{ + return JS_DefinePropertyInternal(ctx, obj, prop, val, JS_NULL, + JS_DEF_PROP_LOOKUP | JS_DEF_PROP_HAS_VALUE); +} + +static JSValue JS_DefinePropertyGetSet(JSContext *ctx, JSValue obj, + JSValue prop, JSValue getter, + JSValue setter, int flags) +{ + return JS_DefinePropertyInternal(ctx, obj, prop, getter, setter, + JS_DEF_PROP_LOOKUP | flags); +} + +/* return a JSVarRef or an exception. */ +static JSValue add_global_var(JSContext *ctx, JSValue prop, BOOL define_flag) +{ + JSObject *p; + JSProperty *pr; + + p = JS_VALUE_TO_PTR(ctx->global_obj); + pr = find_own_property(ctx, p, prop); + if (pr) { + if (pr->prop_type != JS_PROP_VARREF) + return JS_ThrowReferenceError(ctx, "global variable '%"JSValue_PRI"' must be a reference", prop); + if (define_flag) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + /* define the variable if needed */ + if (pv->u.value == JS_UNINITIALIZED) + pv->u.value = JS_UNDEFINED; + } + return pr->value; + } + return JS_DefinePropertyInternal(ctx, ctx->global_obj, prop, + define_flag ? JS_UNDEFINED : JS_UNINITIALIZED, JS_NULL, + JS_DEF_PROP_RET_VAL | JS_DEF_PROP_HAS_VALUE); +} + +/* return JS_UNDEFINED in the normal case. Otherwise: + - exception + - tail call : returned in case of getter and handle_getset = + true. The function is put on the stack +*/ +static JSValue JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj, + JSValue prop, JSValue val, + BOOL allow_tail_call) +{ + JSValue proto; + JSObject *p; + JSProperty *pr; + BOOL is_obj; + + if (unlikely(!JS_IsPtr(this_obj))) { + is_obj = FALSE; + if (JS_IsIntOrShortFloat(this_obj)) { + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + goto prototype_lookup; + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(this_obj)) { + case JS_TAG_BOOL: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]); + goto prototype_lookup; + case JS_TAG_SHORT_FUNC: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]); + goto prototype_lookup; + case JS_TAG_STRING_CHAR: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]); + goto prototype_lookup; + case JS_TAG_NULL: + return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of undefined", prop); + default: + goto no_prop; + } + } + } else { + is_obj = TRUE; + p = JS_VALUE_TO_PTR(this_obj); + } + if (unlikely(p->mtag != JS_MTAG_OBJECT)) { + is_obj = FALSE; + switch(p->mtag) { + case JS_MTAG_FLOAT64: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + goto prototype_lookup; + case JS_MTAG_STRING: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]); + goto prototype_lookup; + default: + no_prop: + return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of value", prop); + } + } + + /* search if the property is already present */ + if (p->class_id == JS_CLASS_ARRAY) { + if (JS_IsInt(prop)) { + JSValueArray *arr; + uint32_t idx = JS_VALUE_GET_INT(prop); + /* not standard: we refuse to add properties to object + except at the last position */ + if (idx < p->u.array.len) { + arr = JS_VALUE_TO_PTR(p->u.array.tab); + arr->arr[idx] = val; + return JS_UNDEFINED; + } else if (idx == p->u.array.len) { + JSValue new_tab; + JSGCRef this_obj_ref, val_ref; + + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, val); + new_tab = js_resize_value_array(ctx, p->u.array.tab, idx + 1); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(new_tab)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(this_obj); + p->u.array.tab = new_tab; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + arr->arr[idx] = val; + p->u.array.len++; + return JS_UNDEFINED; + } else { + goto invalid_array_subscript; + } + } else if (JS_IsNumericProperty(ctx, prop)) { + goto invalid_array_subscript; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + if (JS_IsInt(prop)) { + uint32_t idx = JS_VALUE_GET_INT(prop); + int v, conv_ret; + double d; + JSObject *pbuffer; + JSByteArray *arr; + JSGCRef val_ref, this_obj_ref; + + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, val); + switch(p->class_id) { + case JS_CLASS_UINT8C_ARRAY: + conv_ret = JS_ToUint8Clamp(ctx, &v, val); + break; + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + conv_ret = JS_ToNumber(ctx, &d, val); + break; + default: + conv_ret = JS_ToInt32(ctx, &v, val); + break; + } + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, this_obj); + if (conv_ret) + return JS_EXCEPTION; + + p = JS_VALUE_TO_PTR(this_obj); + if (idx >= p->u.typed_array.len) + goto invalid_array_subscript; + idx += p->u.typed_array.offset; + pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer); + switch(p->class_id) { + default: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + *((uint8_t *)arr->buf + idx) = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + *((uint16_t *)arr->buf + idx) = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + *((uint32_t *)arr->buf + idx) = v; + break; + case JS_CLASS_FLOAT32_ARRAY: + *((float *)arr->buf + idx) = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + *((double *)arr->buf + idx) = d; + break; + } + return JS_UNDEFINED; + } else if (JS_IsNumericProperty(ctx, prop)) { + invalid_array_subscript: + return JS_ThrowTypeError(ctx, "invalid array subscript"); + } + } + + redo: + pr = find_own_property(ctx, p, prop); + if (pr) { + if (likely(pr->prop_type == JS_PROP_NORMAL)) { + if (unlikely(JS_IS_ROM_PTR(ctx, pr))) + goto convert_to_ram; + pr->value = val; + return JS_UNDEFINED; + } else if (pr->prop_type == JS_PROP_VARREF) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + /* always detached */ + pv->u.value = val; + return JS_UNDEFINED; + } else if (pr->prop_type == JS_PROP_SPECIAL) { + JSGCRef val_ref, prop_ref, this_obj_ref; + int err; + convert_to_ram: + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + err = js_update_props(ctx, this_obj); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, this_obj); + if (err) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(this_obj); + goto redo; + } else { + goto getset; + } + } + + /* search in the prototype chain (getter/setters) */ + for(;;) { + proto = p->proto; + if (proto == JS_NULL) + break; + p = JS_VALUE_TO_PTR(proto); + prototype_lookup: + pr = find_own_property(ctx, p, prop); + if (pr) { + if (unlikely(pr->prop_type == JS_PROP_GETSET)) { + JSValueArray *arr; + JSValue setter; + getset: + arr = JS_VALUE_TO_PTR(pr->value); + setter = arr->arr[1]; + if (allow_tail_call) { + /* It is assumed "this_obj" already is on the stack + and that the stack has some slack to add one + element. */ + ctx->sp[-2] = ctx->sp[0]; + ctx->sp[-1] = setter; + ctx->sp[0] = val; + ctx->sp -= 2; + return JS_NewTailCall(1 | FRAME_CF_POP_RET); + } else { + JSGCRef val_ref, setter_ref, this_obj_ref; + int err; + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + JS_PUSH_VALUE(ctx, this_obj); + err = JS_StackCheck(ctx, 3); + JS_POP_VALUE(ctx, this_obj); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + if (err) + return JS_EXCEPTION; + JS_PushArg(ctx, val); + JS_PushArg(ctx, setter); + JS_PushArg(ctx, this_obj); + return JS_Call(ctx, 1); + } + } else { + /* stop prototype chain lookup */ + break; + } + } + } + + /* add the property in the object */ + if (!is_obj) + return JS_ThrowTypeErrorNotAnObject(ctx); + return JS_DefinePropertyInternal(ctx, this_obj, prop, val, JS_UNDEFINED, + JS_DEF_PROP_HAS_VALUE); +} + +JSValue JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *str, JSValue val) +{ + JSValue prop; + JSGCRef this_obj_ref, val_ref; + + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, val); + prop = JS_NewString(ctx, str); + if (!JS_IsException(prop)) { + prop = JS_ToPropertyKey(ctx, prop); + } + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(prop)) + return prop; + return JS_SetPropertyInternal(ctx, this_obj, prop, val, FALSE); +} + +JSValue JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val) +{ + if (idx > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array index"); + return JS_SetPropertyInternal(ctx, this_obj, JS_NewShortInt(idx), val, FALSE); +} + +/* return JS_FALSE, JS_TRUE or JS_EXCEPTION. Return false only if the + property is not configurable which is never the case here. */ +static JSValue JS_DeleteProperty(JSContext *ctx, JSValue this_obj, + JSValue prop) +{ + JSObject *p; + JSProperty *pr, *pr1; + JSValueArray *arr; + int h, idx, hash_mask, last_idx, prop_count, first_free; + JSGCRef this_obj_ref; + + JS_PUSH_VALUE(ctx, this_obj); + prop = JS_ToPropertyKey(ctx, prop); + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(prop)) + return prop; + + /* XXX: check return value */ + if (!JS_IsPtr(this_obj)) + return JS_TRUE; + p = JS_VALUE_TO_PTR(this_obj); + if (p->mtag != JS_MTAG_OBJECT) + return JS_TRUE; + + arr = JS_VALUE_TO_PTR(p->props); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + h = hash_prop(prop) & hash_mask; + idx = JS_VALUE_GET_INT(arr->arr[2 + h]); + last_idx = -1; + while (idx != 0) { + pr = (JSProperty *)(arr->arr + idx); + if (pr->key == prop) { + if (JS_IS_ROM_PTR(ctx, arr)) { + JSGCRef this_obj_ref; + int ret; + JS_PUSH_VALUE(ctx, this_obj); + ret = js_update_props(ctx, this_obj); + JS_POP_VALUE(ctx, this_obj); + if (ret) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(this_obj); + arr = JS_VALUE_TO_PTR(p->props); + pr = (JSProperty *)(arr->arr + idx); + } + /* found: remove it */ + if (last_idx >= 0) { + JSProperty *lpr = (JSProperty *)(arr->arr + last_idx); + lpr->hash_next = pr->hash_next; + } else { + arr->arr[2 + h] = pr->hash_next; + } + first_free = get_first_free(arr); + + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + arr->arr[0] = JS_NewShortInt(prop_count - 1); + pr->prop_type = JS_PROP_NORMAL; + pr->key = JS_UNINITIALIZED; + pr->value = JS_UNDEFINED; + + /* update first_free if needed */ + while (first_free > 2 + hash_mask + 1) { + pr1 = (JSProperty *)&arr->arr[first_free - 3]; + if (pr1->key != JS_UNINITIALIZED) + break; + first_free -= 3; + } + + /* update first_free */ + if (first_free < arr->size) { + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + pr1->hash_next = first_free << 1; + } + + /* compact the properties if needed */ + if ((2 + hash_mask + 1 + 3 * prop_count) < arr->size / 2) + js_compact_props(ctx, p); + return JS_TRUE; + } + last_idx = idx; + idx = pr->hash_next >> 1; + } + /* not found */ + return JS_TRUE; +} + +static JSValue stdlib_init_class(JSContext *ctx, const JSROMClass *class_def) +{ + JSValue obj, proto, parent_class, parent_proto; + JSGCRef parent_class_ref; + JSObject *p; + int ctor_idx = class_def->ctor_idx; + + if (ctor_idx >= 0) { + int class_id = ctx->c_function_table[ctor_idx].magic; + obj = ctx->class_obj[class_id]; + if (!JS_IsNull(obj)) + return obj; /* already defined */ + + /* initialize the parent class if necessary */ + if (!JS_IsNull(class_def->parent_class)) { + JSROMClass *parent_class_def = JS_VALUE_TO_PTR(class_def->parent_class); + int parent_class_id; + parent_class = stdlib_init_class(ctx, parent_class_def); + parent_class_id = ctx->c_function_table[parent_class_def->ctor_idx].magic; + parent_proto = ctx->class_proto[parent_class_id]; + } else { + parent_class = JS_NULL; + parent_proto = ctx->class_proto[JS_CLASS_OBJECT]; + } + /* initialize the prototype before. It is already defined only + for Object and Function */ + proto = ctx->class_proto[class_id]; + if (JS_IsNull(proto)) { + JS_PUSH_VALUE(ctx, parent_class); + proto = JS_NewObjectProtoClass(ctx, parent_proto, JS_CLASS_OBJECT, 0); + JS_POP_VALUE(ctx, parent_class); + ctx->class_proto[class_id] = proto; + } + p = JS_VALUE_TO_PTR(proto); + if (!JS_IsNull(class_def->proto_props)) + p->props = class_def->proto_props; + + if (JS_IsNull(parent_class)) + parent_class = ctx->class_proto[JS_CLASS_CLOSURE]; + obj = js_new_c_function_proto(ctx, ctor_idx, parent_class, FALSE, JS_NULL); + ctx->class_obj[class_id] = obj; + } else { + /* normal object */ + obj = JS_NewObject(ctx); + } + p = JS_VALUE_TO_PTR(obj); + if (!JS_IsNull(class_def->props)) { + /* set the properties from the ROM. They are copied to RAM + when modified */ + p->props = class_def->props; + } + return obj; +} + +static void stdlib_init(JSContext *ctx, const JSValueArray *arr) +{ + JSValue name, val; + int i; + + for(i = 0; i < arr->size; i += 2) { + name = arr->arr[i]; + val = arr->arr[i + 1]; + if (JS_IsObject(ctx, val)) { + val = stdlib_init_class(ctx, JS_VALUE_TO_PTR(val)); + } else if (val == JS_NULL) { + val = ctx->global_obj; + } + JS_DefinePropertyInternal(ctx, ctx->global_obj, name, + val, JS_NULL, + JS_DEF_PROP_HAS_VALUE); + } +} + +static void dummy_write_func(void *opaque, const void *buf, size_t buf_len) +{ + // fwrite(buf, 1, buf_len, stdout); +} + +/* if prepare_compilation is true, the context will be used to compile + to a binary file. It is not expected to be used in the embedded + version */ +JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, BOOL prepare_compilation) +{ + JSContext *ctx; + JSValueArray *arr; + int i, mem_align; + +#ifdef JS_PTR64 + mem_align = 8; +#else + mem_align = 4; +#endif + mem_size = mem_size & ~(mem_align - 1); + assert(mem_size >= 1024); + assert(((uintptr_t)mem_start & (mem_align - 1)) == 0); + + ctx = mem_start; + memset(ctx, 0, sizeof(*ctx)); + ctx->class_count = stdlib_def->class_count; + ctx->class_obj = ctx->class_proto + ctx->class_count; + ctx->heap_base = (void *)(ctx->class_proto + 2 * ctx->class_count); + ctx->heap_free = ctx->heap_base; + ctx->stack_top = mem_start + mem_size; + ctx->sp = (JSValue *)ctx->stack_top; + ctx->stack_bottom = ctx->sp; + ctx->fp = ctx->sp; + ctx->min_free_size = JS_MIN_FREE_SIZE; +#ifdef DEBUG_GC + ctx->dummy_block = JS_NULL; + ctx->unique_strings = JS_NULL; +#endif + ctx->random_state = 1; + ctx->write_func = dummy_write_func; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) + ctx->string_pos_cache[i].str = JS_NULL; + + if (prepare_compilation) { + int atom_table_len; + JSValueArray *arr, *arr1; + uint8_t *ptr; + + /* for compilation, no stdlib is needed. Only the atoms + corresponding to JS_ATOM_x are needed and they are stored + in RAM. */ + /* copy the atoms to a fixed location at the beginning of the + heap */ + ctx->atom_table = (JSWord *)ctx->heap_free; + atom_table_len = stdlib_def->sorted_atoms_offset; + memcpy(ctx->heap_free, stdlib_def->stdlib_table, + atom_table_len * sizeof(JSWord)); + ctx->heap_free += atom_table_len * sizeof(JSWord); + + /* allocate the sorted atom table and populate it */ + arr1 = (JSValueArray *)(stdlib_def->stdlib_table + atom_table_len); + arr = js_alloc_value_array(ctx, 0, arr1->size); + ctx->unique_strings = JS_VALUE_FROM_PTR(arr); + for(i = 0; i < arr1->size; i++) { + ptr = JS_VALUE_TO_PTR(arr1->arr[i]); + ptr = ptr - (uint8_t *)stdlib_def->stdlib_table + + (uint8_t *)ctx->atom_table; + arr->arr[i] = JS_VALUE_FROM_PTR(ptr); + } + ctx->unique_strings_len = arr1->size; + } else { + ctx->atom_table = stdlib_def->stdlib_table; + ctx->rom_atom_tables[0] = (JSValueArray *)(stdlib_def->stdlib_table + + stdlib_def->sorted_atoms_offset); + ctx->n_rom_atom_tables = 1; + ctx->c_function_table = stdlib_def->c_function_table; + ctx->c_finalizer_table = stdlib_def->c_finalizer_table; + ctx->unique_strings = JS_NULL; + ctx->unique_strings_len = 0; + } + + + ctx->current_exception = JS_UNDEFINED; +#ifdef DEBUG_GC + /* set the dummy block at the start of the memory */ + { + JSByteArray *barr; + barr = js_alloc_byte_array(ctx, (min_int(mem_size / 2, 1 << 17)) & ~(JSW - 1)); + ctx->dummy_block = JS_VALUE_FROM_PTR(barr); + } +#endif + + arr = js_alloc_value_array(ctx, 0, 3); + arr->arr[0] = JS_NewShortInt(0); /* prop_count */ + arr->arr[1] = JS_NewShortInt(0); /* hash_mark */ + arr->arr[2] = JS_NewShortInt(0); /* hash_table[1] */ + ctx->empty_props = JS_VALUE_FROM_PTR(arr); + for(i = 0; i < ctx->class_count; i++) + ctx->class_proto[i] = JS_NULL; + for(i = 0; i < ctx->class_count; i++) + ctx->class_obj[i] = JS_NULL; + /* must be done first so that the prototype of Object.prototype is + JS_NULL */ + ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObject(ctx); + /* must be done for proper function init */ + ctx->class_proto[JS_CLASS_CLOSURE] = JS_NewObject(ctx); + + ctx->global_obj = JS_NewObject(ctx); + ctx->minus_zero = js_alloc_float64(ctx, -0.0); /* XXX: use a ROM value instead */ + + if (!prepare_compilation) { + stdlib_init(ctx, (JSValueArray *)(stdlib_def->stdlib_table + stdlib_def->global_object_offset)); + } + + return ctx; +} + +JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def) +{ + return JS_NewContext2(mem_start, mem_size, stdlib_def, FALSE); +} + +void JS_FreeContext(JSContext *ctx) +{ + uint8_t *ptr; + int size; + JSObject *p; + + /* call the user C finalizers */ + /* XXX: could disable it when prepare_compilation = true */ + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + p = (JSObject *)ptr; + if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER && + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) { + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque); + } + ptr += size; + } +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->opaque = opaque; +} + +void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler) +{ + ctx->interrupt_handler = interrupt_handler; +} + +void JS_SetLogFunc(JSContext *ctx, JSWriteFunc *write_func) +{ + ctx->write_func = write_func; +} + +void JS_SetRandomSeed(JSContext *ctx, uint64_t seed) +{ + ctx->random_state = seed; +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return ctx->global_obj; +} + +static JSValue get_var_ref(JSContext *ctx, JSValue *pfirst_var_ref, JSValue *pval) +{ + JSValue val; + JSVarRef *p; + + val = *pfirst_var_ref; + for(;;) { + if (val == JS_NULL) + break; + p = JS_VALUE_TO_PTR(val); + assert(!p->is_detached); + if (p->u.pvalue == pval) + return val; + val = p->u.next; + } + + p = js_malloc(ctx, sizeof(JSVarRef), JS_MTAG_VARREF); + if (!p) + return JS_EXCEPTION; + p->is_detached = FALSE; + p->u.pvalue = pval; + p->u.next = *pfirst_var_ref; + val = JS_VALUE_FROM_PTR(p); + *pfirst_var_ref = val; + return val; +} + +#define FRAME_OFFSET_ARG0 4 +#define FRAME_OFFSET_FUNC_OBJ 3 +#define FRAME_OFFSET_THIS_OBJ 2 +#define FRAME_OFFSET_CALL_FLAGS 1 +#define FRAME_OFFSET_SAVED_FP 0 +#define FRAME_OFFSET_CUR_PC (-1) /* current pc_offset */ +#define FRAME_OFFSET_FIRST_VARREF (-2) +#define FRAME_OFFSET_VAR0 (-3) + +/* stack layout: + + padded_args (padded_argc - argc) + args (argc) + func_obj fp[3] + this_obj fp[2] + call_flags (int) fp[1] + saved_fp (int) fp[0] + cur_pc (int) fp[-1] + first_var_ref (val) fp[-2] + vars (var_count) + temp stack (pointed by sp) +*/ + +#define SP_TO_VALUE(ctx, fp) JS_NewShortInt((uint8_t *)(fp) - (uint8_t *)ctx) +#define VALUE_TO_SP(ctx, val) (void *)((uint8_t *)ctx + JS_VALUE_GET_INT(val)) + +/* buf_end points to the end of the buffer (after the final '\0') */ +static __js_printf_like(3, 4) void cprintf(char **pp, char *buf_end, const char *fmt, ...) +{ + char *p; + va_list ap; + + p = *pp; + if ((p + 1) >= buf_end) + return; + va_start(ap, fmt); + js_vsnprintf(p, buf_end - p, fmt, ap); + va_end(ap); + p += strlen(p); + *pp = p; +} + +static JSValue reloc_c_func_name(JSContext *ctx, JSValue val) +{ + return val; +} + +/* no memory allocation is done */ +/* XXX: handle bound functions */ +static JSValue js_function_get_length_name1(JSContext *ctx, JSValue *this_val, + int is_name, JSFunctionBytecode **pb) +{ + int short_func_idx; + const JSCFunctionDef *fd; + JSValue ret; + + if (!JS_IsPtr(*this_val)) { + if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC) + goto fail; + short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(*this_val); + goto short_func; + } else { + JSObject *p = JS_VALUE_TO_PTR(*this_val); + JSFunctionBytecode *b; + if (p->mtag != JS_MTAG_OBJECT) + goto fail; + if (p->class_id == JS_CLASS_CLOSURE) { + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + if (is_name) { + /* XXX: directly set func_name to the empty string ? */ + if (b->func_name == JS_NULL) + ret = js_get_atom(ctx, JS_ATOM_empty); + else + ret = b->func_name; + } else { + ret = JS_NewShortInt(b->arg_count); + } + *pb = b; + return ret; + } else if (p->class_id == JS_CLASS_C_FUNCTION) { + short_func_idx = p->u.cfunc.idx; + short_func: + fd = &ctx->c_function_table[short_func_idx]; + if (is_name) { + ret = reloc_c_func_name(ctx, fd->name); + } else { + ret = JS_NewShortInt(fd->arg_count); + } + *pb = NULL; + return ret; + } else { + fail: + *pb = NULL; + return JS_NULL; + } + } +} + +static uint32_t get_bit(const uint8_t *buf, uint32_t index) +{ + return (buf[index >> 3] >> (7 - (index & 7))) & 1; +} + +static uint32_t get_bits_slow(const uint8_t *buf, uint32_t index, int n) +{ + int i; + uint32_t val; + val = 0; + for(i = 0; i < n; i++) + val |= get_bit(buf, index + i) << (n - 1 - i); + return val; +} + +static uint32_t get_bits(const uint8_t *buf, uint32_t buf_len, + uint32_t index, int n) +{ + uint32_t val, pos; + + pos = index >> 3; + if (unlikely(n > 25 || (pos + 3) >= buf_len)) { + /* slow case */ + return get_bits_slow(buf, index, n); + } else { + /* fast case */ + val = get_be32(buf + pos); + return (val >> (32 - (index & 7) - n)) & ((1 << n) - 1); + } +} + +static uint32_t get_ugolomb(const uint8_t *buf, uint32_t buf_len, + uint32_t *pindex) +{ + uint32_t index = *pindex; + int i; + uint32_t v; + + i = 0; + for(;;) { + if (get_bit(buf, index++)) + break; + i++; + if (i == 32) { + /* error */ + *pindex = index; + return 0xffffffff; + } + } + if (i == 0) { + v = 0; + } else { + v = ((1 << i) | get_bits(buf, buf_len, index, i)) - 1; + index += i; + } + *pindex = index; + // printf("get_ugolomb: v=%d\n", v); + return v; +} + +static int32_t get_sgolomb(const uint8_t *buf, uint32_t buf_len, + uint32_t *pindex) +{ + uint32_t val; + val = get_ugolomb(buf, buf_len, pindex); + return (val >> 1) ^ -(val & 1); +} + +static int get_pc2line_hoisted_code_len(const uint8_t *buf, size_t buf_len) +{ + size_t i = buf_len; + int v = 0; + while (i > 0) { + i--; + v = (v << 7) | (buf[i] & 0x7f); + if ((buf[i] & 0x80) == 0) + break; + } + return v; +} + +/* line_num, col_num and index are updated */ +static void get_pc2line(int *pline_num, int *pcol_num, const uint8_t *buf, + uint32_t buf_len, uint32_t *pindex, BOOL has_column) +{ + int line_delta, line_num, col_num, col_delta; + + line_num = *pline_num; + col_num = *pcol_num; + + line_delta = get_sgolomb(buf, buf_len, pindex); + line_num += line_delta; + if (has_column) { + if (line_delta == 0) { + col_delta = get_sgolomb(buf, buf_len, pindex); + col_num += col_delta; + } else { + col_num = get_ugolomb(buf, buf_len, pindex) + 1; + } + } else { + col_num = 0; + } + *pline_num = line_num; + *pcol_num = col_num; +} + +/* return 0 if line/col number info */ +static int find_line_col(int *pcol_num, JSFunctionBytecode *b, uint32_t pc) +{ + JSByteArray *arr, *pc2line; + int pos, op, line_num, col_num; + uint32_t pc2line_pos; + + if (b->pc2line == JS_NULL) + goto fail; + arr = JS_VALUE_TO_PTR(b->byte_code); + pc2line = JS_VALUE_TO_PTR(b->pc2line); + + /* skip the hoisted code */ + pos = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size); + if (pc < pos) + pc = pos; + pc2line_pos = 0; + line_num = 1; + col_num = 1; + while (pos < arr->size) { + get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size, + &pc2line_pos, b->has_column); + if (pos == pc) { + *pcol_num = col_num; + return line_num; + } + op = arr->buf[pos]; + pos += opcode_info[op].size; + } + fail: + *pcol_num = 0; + return 0; +} + +static const char *get_func_name(JSContext *ctx, JSValue func_obj, + JSCStringBuf *str_buf, JSFunctionBytecode **pb) +{ + JSValue val; + val = js_function_get_length_name1(ctx, &func_obj, 1, pb); + if (JS_IsNull(val)) + return NULL; + return JS_ToCString(ctx, val, str_buf); +} + +static void build_backtrace(JSContext *ctx, JSValue error_obj, + const char *filename, int line_num, int col_num, int skip_level) +{ + JSObject *p1; + char buf[128], *p, *buf_end, *line_start; + const char *str; + JSValue *fp, stack_str; + JSCStringBuf str_buf; + JSFunctionBytecode *b; + int level; + JSGCRef error_obj_ref; + + if (!JS_IsError(ctx, error_obj)) + return; + p = buf; + buf_end = buf + sizeof(buf); + p[0] = '\0'; + if (filename) { + cprintf(&p, buf_end, " at %s:%d:%d\n", filename, line_num, col_num); + } + fp = ctx->fp; + level = 0; + while (fp != (JSValue *)ctx->stack_top && level < 10) { + if (skip_level != 0) { + skip_level--; + } else { + line_start = p; + str = get_func_name(ctx, fp[FRAME_OFFSET_FUNC_OBJ], &str_buf, &b); + if (!str) + str = ""; + cprintf(&p, buf_end, " at %s", str); + if (b) { + int pc, line_num, col_num; + const char *filename; + filename = JS_ToCString(ctx, b->filename, &str_buf); + pc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]) - 1; + line_num = find_line_col(&col_num, b, pc); + cprintf(&p, buf_end, " (%s", filename); + if (line_num != 0) { + cprintf(&p, buf_end, ":%d", line_num); + if (col_num != 0) + cprintf(&p, buf_end, ":%d", col_num); + } + cprintf(&p, buf_end, ")"); + } else { + cprintf(&p, buf_end, " (native)"); + } + cprintf(&p, buf_end, "\n"); + /* if truncated line, remove it and stop */ + if ((p + 1) >= buf_end) { + *line_start = '\0'; + break; + } + level++; + } + fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]); + } + + JS_PUSH_VALUE(ctx, error_obj); + stack_str = JS_NewString(ctx, buf); + JS_POP_VALUE(ctx, error_obj); + p1 = JS_VALUE_TO_PTR(error_obj); + p1->u.error.stack = stack_str; +} + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE HINT_NUMBER + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint) +{ + int i, atom; + JSValue method, ret; + JSGCRef val_ref, method_ref; + + if (JS_IsPrimitive(ctx, val)) + return val; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + atom = JS_ATOM_toString; + } else { + atom = JS_ATOM_valueOf; + } + JS_PUSH_VALUE(ctx, val); + method = JS_GetProperty(ctx, val, js_get_atom(ctx, atom)); + JS_POP_VALUE(ctx, val); + if (JS_IsException(method)) + return method; + if (JS_IsFunction(ctx, method)) { + int err; + JS_PUSH_VALUE(ctx, method); + JS_PUSH_VALUE(ctx, val); + err = JS_StackCheck(ctx, 2); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, method); + if (err) + return JS_EXCEPTION; + + JS_PushArg(ctx, method); + JS_PushArg(ctx, val); + JS_PUSH_VALUE(ctx, val); + ret = JS_Call(ctx, 0); + JS_POP_VALUE(ctx, val); + if (JS_IsException(ret)) + return ret; + if (!JS_IsObject(ctx, ret)) + return ret; + } + } + return JS_ThrowTypeError(ctx, "toPrimitive"); +} + +/* return a string or an exception */ +static JSValue js_dtoa2(JSContext *ctx, double d, int radix, int n_digits, int flags) +{ + int len_max, len; + JSValue str; + JSGCRef str_ref; + JSByteArray *tmp_arr, *p; + + len_max = js_dtoa_max_len(d, radix, n_digits, flags); + p = js_alloc_byte_array(ctx, len_max + 1); + if (!p) + return JS_EXCEPTION; + /* allocate the temporary buffer */ + str = JS_VALUE_FROM_PTR(p); + JS_PUSH_VALUE(ctx, str); + tmp_arr = js_alloc_byte_array(ctx, sizeof(JSDTOATempMem)); + JS_POP_VALUE(ctx, str); + if (!tmp_arr) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(str); + /* Note: tmp_arr->buf is always 32 bit aligned */ + len = js_dtoa((char *)p->buf, d, radix, n_digits, flags, (JSDTOATempMem *)tmp_arr->buf); + js_free(ctx, tmp_arr); + return js_byte_array_to_string(ctx, str, len, TRUE); +} + +JSValue JS_ToString(JSContext *ctx, JSValue val) +{ + char buf[128]; + int atom; + const char *str; + + redo: + if (JS_IsInt(val)) { + int len; + len = i32toa(buf, JS_VALUE_GET_INT(val)); + buf[len] = '\0'; + goto ret_buf; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + return js_dtoa2(ctx, js_get_short_float(val), 10, 0, JS_DTOA_FORMAT_FREE); + } else +#endif + if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + int mtag = js_get_mtag(ptr); + switch(mtag) { + case JS_MTAG_OBJECT: + to_primitive: + val = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val)) + return val; + goto redo; + case JS_MTAG_STRING: + return val; + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = ptr; + return js_dtoa2(ctx, p->u.dval, 10, 0, JS_DTOA_FORMAT_FREE); + } + default: + js_snprintf(buf, sizeof(buf), "[mtag %d]", mtag); + goto ret_buf; + } + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + atom = JS_ATOM_null; + goto ret_atom; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + goto ret_atom; + case JS_TAG_BOOL: + if (JS_VALUE_GET_SPECIAL_VALUE(val)) + atom = JS_ATOM_true; + else + atom = JS_ATOM_false; + ret_atom: + return js_get_atom(ctx, atom); + case JS_TAG_STRING_CHAR: + return val; + case JS_TAG_SHORT_FUNC: + goto to_primitive; + default: + str = "?"; + goto ret_str; + ret_buf: + str = buf; + ret_str: + return JS_NewString(ctx, str); + } + } +} + +/* return either a unique string or an integer. Strings representing + a short integer are converted to short integer */ +static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val) +{ + int32_t n; + if (JS_IsInt(val)) + return val; + val = JS_ToString(ctx, val); + if (JS_IsException(val)) + return val; + if (is_num_string(ctx, &n, val)) + return JS_NewShortInt(n); + else + return JS_MakeUniqueString(ctx, val); +} + +static int skip_spaces(const char *p1) +{ + const char *p = p1; + int c; + for(;;) { + c = *p; + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + p++; + } + return p - p1; +} + +/* JS_ToString() specific behaviors */ +#define JS_ATOD_TOSTRING (1 << 8) + +/* 'val' must be a string */ +static int js_atod1(JSContext *ctx, double *pres, JSValue val, + int radix, int flags) +{ + JSString *p; + JSByteArray *tmp_arr; + double d; + JSGCRef val_ref; + const char *p1; + + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + int c = JS_VALUE_GET_SPECIAL_VALUE(val); + if (c >= '0' && c <= '9') { + *pres = c - '0'; + } else { + *pres = NAN; + } + return 0; + } + + JS_PUSH_VALUE(ctx, val); + tmp_arr = js_alloc_byte_array(ctx, sizeof(JSATODTempMem)); + JS_POP_VALUE(ctx, val); + if (!tmp_arr) { + *pres = NAN; + return -1; + } + p = JS_VALUE_TO_PTR(val); + p1 = (char *)p->buf; + p1 += skip_spaces(p1); + if ((p1 - (char *)p->buf) == p->len) { + if (flags & JS_ATOD_TOSTRING) + d = 0; + else + d = NAN; + goto done; + } + d = js_atod(p1, &p1, radix, flags, (JSATODTempMem *)tmp_arr->buf); + js_free(ctx, tmp_arr); + if (flags & JS_ATOD_TOSTRING) { + p1 += skip_spaces(p1); + if ((p1 - (char *)p->buf) < p->len) + d = NAN; + } + done: + *pres = d; + return 0; +} + +/* Note: can fail due to memory allocation even if primitive type */ +int JS_ToNumber(JSContext *ctx, double *pres, JSValue val) +{ + redo: + if (JS_IsInt(val)) { + *pres = (double)JS_VALUE_GET_INT(val); + return 0; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + *pres = js_get_short_float(val); + return 0; + } else +#endif + if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + switch(js_get_mtag(ptr)) { + case JS_MTAG_STRING: + goto atod; + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = ptr; + *pres = p->u.dval; + return 0; + } + case JS_MTAG_OBJECT: + val = JS_ToPrimitive(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) { + *pres = NAN; + return -1; + } + goto redo; + default: + *pres = NAN; + return 0; + } + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + case JS_TAG_BOOL: + *pres = (double)JS_VALUE_GET_SPECIAL_VALUE(val); + return 0; + case JS_TAG_UNDEFINED: + *pres = NAN; + return 0; + case JS_TAG_STRING_CHAR: + atod: + return js_atod1(ctx, pres, val, 0, + JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_TOSTRING); + default: + *pres = NAN; + return 0; + } + } +} + +static int JS_ToInt32Internal(JSContext *ctx, int *pres, JSValue val, BOOL sat_flag) +{ + int32_t ret; + double d; + + if (JS_IsInt(val)) { + ret = JS_VALUE_GET_INT(val); + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + d = js_get_short_float(val); + goto handle_float64; + } else +#endif + if (JS_IsPtr(val)) { + uint64_t u; + int e; + + handle_number: + if (JS_ToNumber(ctx, &d, val)) { + *pres = 0; + return -1; + } +#ifdef JS_USE_SHORT_FLOAT + handle_float64: +#endif + u = float64_as_uint64(d); + e = (u >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (!sat_flag) { + if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u >> 63) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } else { + if (e == 2047 && (u & (((uint64_t)1 << 52) - 1)) != 0) { + /* nan */ + ret = 0; + } else { + /* take the sign into account */ + if (u >> 63) + ret = 0x80000000; + else + ret = 0x7fffffff; + } + } + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_SPECIAL_VALUE(val); + break; + default: + goto handle_number; + } + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int *pres, JSValue val) +{ + return JS_ToInt32Internal(ctx, pres, val, FALSE); +} + +int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Internal(ctx, (int *)pres, val, FALSE); +} + +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val) +{ + return JS_ToInt32Internal(ctx, pres, val, TRUE); +} + +static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32Sat(ctx, pres, val); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) + *pres = min; + } else { + if (*pres > max) + *pres = max; + } + } + return res; +} + +static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val) +{ + int32_t ret; + double d; + + if (JS_IsInt(val)) { + ret = JS_VALUE_GET_INT(val); + if (ret < 0) + ret = 0; + else if (ret > 255) + ret = 255; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + d = js_get_short_float(val); + goto handle_float64; + } else +#endif + if (JS_IsPtr(val)) { + handle_number: + if (JS_ToNumber(ctx, &d, val)) { + *pres = 0; + return -1; + } +#ifdef JS_USE_SHORT_FLOAT + handle_float64: +#endif + if (d < 0 || isnan(d)) + ret = 0; + else if (d > 255) + ret = 255; + else + ret = js_lrint(d); + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_SPECIAL_VALUE(val); + break; + default: + goto handle_number; + } + } + *pres = ret; + return 0; +} + +static int js_get_length32(JSContext *ctx, uint32_t *pres, JSValue obj) +{ + JSValue len_val; + len_val = JS_GetProperty(ctx, obj, js_get_atom(ctx, JS_ATOM_length)); + if (JS_IsException(len_val)) { + *pres = 0; + return -1; + } + return JS_ToUint32(ctx, pres, len_val); +} + +static no_inline JSValue js_add_slow(JSContext *ctx) +{ + JSValue *op1, *op2; + + op1 = &ctx->sp[1]; + op2 = &ctx->sp[0]; + *op1 = JS_ToPrimitive(ctx, *op1, HINT_NONE); + if (JS_IsException(*op1)) + return JS_EXCEPTION; + *op2 = JS_ToPrimitive(ctx, *op2, HINT_NONE); + if (JS_IsException(*op2)) + return JS_EXCEPTION; + if (JS_IsString(ctx, *op1) || JS_IsString(ctx, *op2)) { + *op1 = JS_ToString(ctx, *op1); + if (JS_IsException(*op1)) + return JS_EXCEPTION; + *op2 = JS_ToString(ctx, *op2); + if (JS_IsException(*op2)) + return JS_EXCEPTION; + return JS_ConcatString(ctx, *op1, *op2); + } else { + double d1, d2, r; + /* cannot fail */ + if (JS_ToNumber(ctx, &d1, *op1)) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &d2, *op2)) + return JS_EXCEPTION; + r = d1 + d2; + return JS_NewFloat64(ctx, r); + } +} + +static no_inline JSValue js_binary_arith_slow(JSContext *ctx, OPCodeEnum op) +{ + double d1, d2, r; + + if (JS_ToNumber(ctx, &d1, ctx->sp[1])) + return JS_EXCEPTION; + + if (JS_ToNumber(ctx, &d2, ctx->sp[0])) + return JS_EXCEPTION; + + switch(op) { + case OP_sub: + r = d1 - d2; + break; + case OP_mul: + r = d1 * d2; + break; + case OP_div: + r = d1 / d2; + break; + case OP_mod: + r = js_fmod(d1, d2); + break; + case OP_pow: + r = js_pow(d1, d2); + break; + default: + abort(); + } + return JS_NewFloat64(ctx, r); +} + +static no_inline JSValue js_unary_arith_slow(JSContext *ctx, OPCodeEnum op) +{ + double d; + + if (JS_ToNumber(ctx, &d, ctx->sp[0])) + return JS_EXCEPTION; + + switch(op) { + case OP_inc: + d++; + break; + case OP_dec: + d--; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + return JS_NewFloat64(ctx, d); +} + +/* specific case necessary for correct return value semantics */ +static no_inline JSValue js_post_inc_slow(JSContext *ctx, OPCodeEnum op) +{ + JSValue val; + double d, r; + + if (JS_ToNumber(ctx, &d, ctx->sp[0])) + return JS_EXCEPTION; + r = d + 2 * (op - OP_post_dec) - 1; + val = JS_NewFloat64(ctx, d); + if (JS_IsException(val)) + return val; + ctx->sp[0] = val; + return JS_NewFloat64(ctx, r); +} + +static no_inline JSValue js_binary_logic_slow(JSContext *ctx, OPCodeEnum op) +{ + uint32_t v1, v2, r; + + if (JS_ToUint32(ctx, &v1, ctx->sp[1])) + return JS_EXCEPTION; + if (JS_ToUint32(ctx, &v2, ctx->sp[0])) + return JS_EXCEPTION; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_shr: + r = v1 >> (v2 & 0x1f); + return JS_NewUint32(ctx, r); + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + return JS_NewInt32(ctx, r); +} + +static no_inline JSValue js_not_slow(JSContext *ctx) +{ + uint32_t r; + + if (JS_ToUint32(ctx, &r, ctx->sp[0])) + return JS_EXCEPTION; + return JS_NewInt32(ctx, ~r); +} + +static no_inline JSValue js_relational_slow(JSContext *ctx, OPCodeEnum op) +{ + JSValue *op1, *op2; + int res; + double d1, d2; + + op1 = &ctx->sp[1]; + op2 = &ctx->sp[0]; + *op1 = JS_ToPrimitive(ctx, *op1, HINT_NUMBER); + if (JS_IsException(*op1)) + return JS_EXCEPTION; + *op2 = JS_ToPrimitive(ctx, *op2, HINT_NUMBER); + if (JS_IsException(*op2)) + return JS_EXCEPTION; + if (JS_IsString(ctx, *op1) && JS_IsString(ctx, *op2)) { + res = js_string_compare(ctx, *op1, *op2); + switch(op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + case OP_gte: + res = (res >= 0); + break; + } + } else { + if (JS_ToNumber(ctx, &d1, *op1)) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &d2, *op2)) + return JS_EXCEPTION; + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } + } + return JS_NewBool(res); +} + +static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) +{ + BOOL res; + + if (JS_IsNumber(ctx, op1)) { + if (!JS_IsNumber(ctx, op2)) { + res = FALSE; + } else { + double d1, d2; + /* cannot fail */ + JS_ToNumber(ctx, &d1, op1); + JS_ToNumber(ctx, &d2, op2); + res = (d1 == d2); /* if NaN return false */ + } + } else if (JS_IsString(ctx, op1)) { + if (!JS_IsString(ctx, op2)) { + res = FALSE; + } else { + res = js_string_eq(ctx, op1, op2); + } + } else { + /* special value or object */ + res = (op1 == op2); + } + return res; +} + +static JSValue js_strict_eq_slow(JSContext *ctx, BOOL is_neq) +{ + BOOL res; + res = js_strict_eq(ctx, ctx->sp[1], ctx->sp[0]); + return JS_NewBool(res ^ is_neq); +} + +enum { + /* special tags to simplify the comparison */ + JS_ETAG_NUMBER = JS_TAG_SPECIAL | (8 << 2), + JS_ETAG_STRING = JS_TAG_SPECIAL | (9 << 2), + JS_ETAG_OBJECT = JS_TAG_SPECIAL | (10 << 2), +}; + +static int js_eq_get_type(JSContext *ctx, JSValue val) +{ + if (JS_IsIntOrShortFloat(val)) { + return JS_ETAG_NUMBER; + } else if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + switch(js_get_mtag(ptr)) { + case JS_MTAG_FLOAT64: + return JS_ETAG_NUMBER; + case JS_MTAG_STRING: + return JS_ETAG_STRING; + default: + case JS_MTAG_OBJECT: + return JS_ETAG_OBJECT; + } + } else { + int tag = JS_VALUE_GET_SPECIAL_TAG(val); + switch(tag) { + case JS_TAG_STRING_CHAR: + return JS_ETAG_STRING; + case JS_TAG_SHORT_FUNC: + return JS_ETAG_OBJECT; + default: + return tag; + } + } +} + +static no_inline JSValue js_eq_slow(JSContext *ctx, BOOL is_neq) +{ + JSValue op1, op2; + int tag1, tag2; + BOOL res; + + redo: + op1 = ctx->sp[1]; + op2 = ctx->sp[0]; + tag1 = js_eq_get_type(ctx, op1); + tag2 = js_eq_get_type(ctx, op2); + if (tag1 == tag2) { + res = js_strict_eq(ctx, op1, op2); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = TRUE; + } else if ((tag1 == JS_ETAG_STRING && tag2 == JS_ETAG_NUMBER) || + (tag2 == JS_ETAG_STRING && tag1 == JS_ETAG_NUMBER)) { + double d1; + double d2; + if (JS_ToNumber(ctx, &d1, ctx->sp[1])) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &d2, ctx->sp[0])) + return JS_EXCEPTION; + res = (d1 == d2); + } else if (tag1 == JS_TAG_BOOL) { + ctx->sp[1] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + ctx->sp[0] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op2)); + goto redo; + } else if (tag1 == JS_ETAG_OBJECT && + (tag2 == JS_ETAG_NUMBER || tag2 == JS_ETAG_STRING)) { + ctx->sp[1] = JS_ToPrimitive(ctx, op1, HINT_NONE); + if (JS_IsException(ctx->sp[1])) + return JS_EXCEPTION; + goto redo; + } else if (tag2 == JS_ETAG_OBJECT && + (tag1 == JS_ETAG_NUMBER || tag1 == JS_ETAG_STRING)) { + ctx->sp[0] = JS_ToPrimitive(ctx, op2, HINT_NONE); + if (JS_IsException(ctx->sp[0])) + return JS_EXCEPTION; + goto redo; + } else { + res = FALSE; + } + return JS_NewBool(res ^ is_neq); +} + +static JSValue js_operator_in(JSContext *ctx) +{ + JSValue prop; + int res; + + if (js_eq_get_type(ctx, ctx->sp[0]) != JS_ETAG_OBJECT) + return JS_ThrowTypeError(ctx, "invalid 'in' operand"); + prop = JS_ToPropertyKey(ctx, ctx->sp[1]); + if (JS_IsException(prop)) + return prop; + res = JS_HasProperty(ctx, ctx->sp[0], prop); + return JS_NewBool(res); +} + +static JSValue js_operator_instanceof(JSContext *ctx) +{ + JSValue op1, op2, proto; + JSObject *p; + + op1 = ctx->sp[1]; + op2 = ctx->sp[0]; + if (!JS_IsFunctionObject(ctx, op2)) + return JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + proto = JS_GetProperty(ctx, op2, js_get_atom(ctx, JS_ATOM_prototype)); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(ctx, op1)) + return JS_NewBool(FALSE); + p = JS_VALUE_TO_PTR(op1); + for(;;) { + if (p->proto == JS_NULL) + return JS_NewBool(FALSE); + if (p->proto == proto) + return JS_NewBool(TRUE); + p = JS_VALUE_TO_PTR(p->proto); + } + return JS_NewBool(FALSE); +} + +static JSValue js_operator_typeof(JSContext *ctx, JSValue val) +{ + int tag, atom; + tag = js_eq_get_type(ctx, val); + switch(tag) { + case JS_ETAG_NUMBER: + atom = JS_ATOM_number; + break; + case JS_ETAG_STRING: + atom = JS_ATOM_string; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_ETAG_OBJECT: + if (JS_IsFunction(ctx, val)) + atom = JS_ATOM_function; + else + atom = JS_ATOM_object; + break; + case JS_TAG_NULL: + atom = JS_ATOM_object; + break; + default: + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + } + return js_get_atom(ctx, atom); +} + +static void js_reverse_val(JSValue *tab, int n) +{ + int i; + JSValue tmp; + + for(i = 0; i < n / 2; i++) { + tmp = tab[i]; + tab[i] = tab[n - 1 - i]; + tab[n - 1 - i] = tmp; + } +} + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, JSValue *fp) +{ + JSFunctionBytecode *b; + JSObject *p; + JSGCRef bfunc_ref, closure_ref; + JSValueArray *ext_vars; + JSValue closure; + int ext_vars_len; + + b = JS_VALUE_TO_PTR(bfunc); + if (b->ext_vars != JS_NULL) { + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + ext_vars_len = ext_vars->size / 2; + } else { + ext_vars_len = 0; + } + + JS_PUSH_VALUE(ctx, bfunc); + closure = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_CLOSURE], JS_CLASS_CLOSURE, + sizeof(JSClosureData) + ext_vars_len * sizeof(JSValue)); + JS_POP_VALUE(ctx, bfunc); + if (JS_IsException(closure)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(closure); + p->u.closure.func_bytecode = bfunc; + + if (ext_vars_len > 0) { + JSValue *pfirst_var_ref, val; + int i, var_idx, var_kind, decl; + + /* initialize the var_refs in case of exception */ + memset(p->u.closure.var_refs, 0, sizeof(JSValue) * ext_vars_len); + if (fp) { + pfirst_var_ref = &fp[FRAME_OFFSET_FIRST_VARREF]; + } else { + pfirst_var_ref = NULL; /* not used */ + } + for(i = 0; i < ext_vars_len; i++) { + b = JS_VALUE_TO_PTR(bfunc); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]); + var_kind = decl >> 16; + var_idx = decl & 0xffff; + JS_PUSH_VALUE(ctx, bfunc); + JS_PUSH_VALUE(ctx, closure); + switch(var_kind) { + case JS_VARREF_KIND_ARG: + val = get_var_ref(ctx, pfirst_var_ref, + &fp[FRAME_OFFSET_ARG0 + var_idx]); + break; + case JS_VARREF_KIND_VAR: + val = get_var_ref(ctx, pfirst_var_ref, + &fp[FRAME_OFFSET_VAR0 - var_idx]); + break; + case JS_VARREF_KIND_VAR_REF: + { + JSObject *p; + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + val = p->u.closure.var_refs[var_idx]; + } + break; + case JS_VARREF_KIND_GLOBAL: + /* only for eval code */ + val = add_global_var(ctx, ext_vars->arr[2 * i], (var_idx != 0)); + break; + default: + abort(); + } + JS_POP_VALUE(ctx, closure); + JS_POP_VALUE(ctx, bfunc); + if (JS_IsException(val)) + return val; + p = JS_VALUE_TO_PTR(closure); + p->u.closure.var_refs[i] = val; + } + } + return closure; +} + +static JSValue js_for_of_start(JSContext *ctx, BOOL is_for_in) +{ + JSValueArray *arr; + + if (is_for_in) { + /* XXX: not spec compliant and slow. We return only the own + object keys. */ + ctx->sp[0] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]); + if (JS_IsException(ctx->sp[0])) + return JS_EXCEPTION; + } + + if (!js_get_object_class(ctx, ctx->sp[0], JS_CLASS_ARRAY)) + return JS_ThrowTypeError(ctx, "unsupported type in for...of"); + + arr = js_alloc_value_array(ctx, 0, 2); + if (!arr) + return JS_EXCEPTION; + arr->arr[0] = ctx->sp[0]; + arr->arr[1] = JS_NewShortInt(0); + return JS_VALUE_FROM_PTR(arr); +} + +static JSValue js_for_of_next(JSContext *ctx) +{ + JSValueArray *arr, *arr1; + JSObject *p; + int pos; + + arr = JS_VALUE_TO_PTR(ctx->sp[0]); + pos = JS_VALUE_GET_INT(arr->arr[1]); + p = JS_VALUE_TO_PTR(arr->arr[0]); + if (pos >= p->u.array.len) { + ctx->sp[-2] = JS_TRUE; + ctx->sp[-1] = JS_UNDEFINED; + } else { + ctx->sp[-2] = JS_FALSE; + arr1 = JS_VALUE_TO_PTR(p->u.array.tab); + ctx->sp[-1] = arr1->arr[pos]; + arr->arr[1] = JS_NewShortInt(pos + 1); + } + return JS_UNDEFINED; +} + +static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params, + JSValue params) +{ + JSObject *p; + JSGCRef params_ref; + + JS_PUSH_VALUE(ctx, params); + p = JS_NewObjectProtoClass1(ctx, proto, JS_CLASS_C_FUNCTION, + sizeof(JSCFunctionData) - (!has_params ? sizeof(JSValue) : 0)); + JS_POP_VALUE(ctx, params); + if (!p) + return JS_EXCEPTION; + p->u.cfunc.idx = func_idx; + if (has_params) + p->u.cfunc.params = params; + return JS_VALUE_FROM_PTR(p); +} + +JSValue JS_NewCFunctionParams(JSContext *ctx, int func_idx, JSValue params) +{ + return js_new_c_function_proto(ctx, func_idx, ctx->class_proto[JS_CLASS_CLOSURE], TRUE, params); +} + +static JSValue js_call_constructor_start(JSContext *ctx, JSValue func) +{ + JSValue proto; + proto = JS_GetProperty(ctx, func, js_get_atom(ctx, JS_ATOM_prototype)); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(ctx, proto)) + proto = ctx->class_proto[JS_CLASS_OBJECT]; + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0); +} + +#define SAVE() do { \ + fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf); \ + ctx->sp = sp; \ + ctx->fp = fp; \ + } while (0) + +/* only need to restore PC */ +#define RESTORE() do { \ + b = JS_VALUE_TO_PTR(((JSObject *)JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]))->u.closure.func_bytecode); \ + pc = ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf + JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]); \ + } while (0) + +static JSValue __js_poll_interrupt(JSContext *ctx) +{ + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (ctx->interrupt_handler && ctx->interrupt_handler(ctx, ctx->opaque)) { + JS_ThrowInternalError(ctx, "interrupted"); + ctx->current_exception_is_uncatchable = TRUE; + return JS_EXCEPTION; + } + return JS_UNDEFINED; +} + +/* handle user interruption */ +#define POLL_INTERRUPT() do { \ + if (unlikely(--ctx->interrupt_counter <= 0)) { \ + SAVE(); \ + val = __js_poll_interrupt(ctx); \ + RESTORE(); \ + if (JS_IsException(val)) \ + goto exception; \ + } \ + } while(0) + +/* must use JS_StackCheck() before using it */ +void JS_PushArg(JSContext *ctx, JSValue val) +{ +#ifdef DEBUG_GC + assert((ctx->sp - 1) >= ctx->stack_bottom); +#endif + *--ctx->sp = val; +} + +/* Usage: + if (JS_StackCheck(ctx, n + 2)) ... + JS_PushArg(ctx, arg[n - 1]); + ... + JS_PushArg(ctx, arg[0]); + JS_PushArg(ctx, func); + JS_PushArg(ctx, this_obj); + res = JS_Call(ctx, n); +*/ +JSValue JS_Call(JSContext *ctx, int call_flags) +{ + JSValue *fp, *sp, val = JS_UNDEFINED, *initial_fp; + uint8_t *pc; + /* temporary variables */ + int opcode = OP_invalid, i; + JSFunctionBytecode *b; +#ifdef JS_USE_SHORT_FLOAT + double dr; +#endif + + if (ctx->js_call_rec_count >= JS_MAX_CALL_RECURSE) + return JS_ThrowInternalError(ctx, "C stack overflow"); + ctx->js_call_rec_count++; + + sp = ctx->sp; + fp = ctx->fp; + initial_fp = fp; + b = NULL; + pc = NULL; + goto function_call; + +#define CASE(op) case op +#define DEFAULT default +#define BREAK break + + for(;;) { + opcode = *pc++; +#ifdef DUMP_EXEC + { + JSByteArray *arr; + arr = JS_VALUE_TO_PTR(b->byte_code); + js_printf(ctx, " sp=%d\n", (int)(sp - fp)); + js_printf(ctx, "%4d: %s\n", (int)(pc - arr->buf - 1), + opcode_info[opcode].name); + } +#endif + switch(opcode) { + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *--sp = JS_NewShortInt(opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *--sp = JS_NewShortInt(get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *--sp = JS_NewShortInt(get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_value): + *--sp = get_u32(pc); + pc += 4; + BREAK; + CASE(OP_push_const): + { + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + *--sp = cpool->arr[get_u16(pc)]; + pc += 2; + } + BREAK; + CASE(OP_undefined): + *--sp = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *--sp = JS_NULL; + BREAK; + CASE(OP_push_this): + *--sp = fp[FRAME_OFFSET_THIS_OBJ]; + BREAK; + CASE(OP_push_false): + *--sp = JS_FALSE; + BREAK; + CASE(OP_push_true): + *--sp = JS_TRUE; + BREAK; + CASE(OP_object): + { + int n = get_u16(pc); + SAVE(); + val = JS_NewObjectPrealloc(ctx, n); + RESTORE(); + if (JS_IsException(val)) + goto exception; + *--sp = val; + pc += 2; + } + BREAK; + CASE(OP_regexp): + { + JSObject *p; + SAVE(); + val = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp)); + RESTORE(); + if (JS_IsException(val)) + goto exception; + p = JS_VALUE_TO_PTR(val); + p->u.regexp.source = sp[1]; + p->u.regexp.byte_code = sp[0]; + p->u.regexp.last_index = 0; + sp[1] = val; + sp++; + } + BREAK; + CASE(OP_array_from): + { + JSObject *p; + JSValueArray *arr; + int i, argc; + + argc = get_u16(pc); + SAVE(); + val = JS_NewArray(ctx, argc); + RESTORE(); + if (JS_IsException(val)) + goto exception; + pc += 2; + p = JS_VALUE_TO_PTR(val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < argc; i++) { + arr->arr[i] = sp[argc - 1 - i]; + } + sp += argc; + *--sp = val; + } + BREAK; + CASE(OP_this_func): + *--sp = fp[FRAME_OFFSET_FUNC_OBJ]; + BREAK; + CASE(OP_arguments): + { + JSObject *p; + JSValueArray *arr; + int i, argc; + + argc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]) & FRAME_CF_ARGC_MASK; + SAVE(); + val = JS_NewArray(ctx, argc); + RESTORE(); + if (JS_IsException(val)) + goto exception; + p = JS_VALUE_TO_PTR(val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < argc; i++) { + arr->arr[i] = fp[FRAME_OFFSET_ARG0 + i]; + } + *--sp = val; + } + BREAK; + CASE(OP_new_target): + call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]); + if (call_flags & FRAME_CF_CTOR) { + *--sp = fp[FRAME_OFFSET_FUNC_OBJ]; + } else { + *--sp = JS_UNDEFINED; + } + BREAK; + CASE(OP_drop): + sp++; + BREAK; + CASE(OP_nip): + sp[1] = sp[0]; + sp++; + BREAK; + CASE(OP_dup): + sp--; + sp[0] = sp[1]; + BREAK; + CASE(OP_dup2): + sp -= 2; + sp[0] = sp[2]; + sp[1] = sp[3]; + BREAK; + CASE(OP_insert2): + sp[-1] = sp[0]; + sp[0] = sp[1]; + sp[1] = sp[-1]; + sp--; + BREAK; + CASE(OP_insert3): + sp[-1] = sp[0]; + sp[0] = sp[1]; + sp[1] = sp[2]; + sp[2] = sp[-1]; + sp--; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[1]; + sp[1] = sp[2]; + sp[2] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[2]; + sp[2] = sp[1]; + sp[1] = sp[0]; + sp[0] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[1]; + sp[1] = sp[2]; + sp[2] = sp[3]; + sp[3] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[1]; + sp[1] = sp[0]; + sp[0] = tmp; + } + BREAK; + + CASE(OP_fclosure): + { + int idx; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + idx = get_u16(pc); + SAVE(); + val = js_closure(ctx, cpool->arr[idx], fp); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + pc += 2; + *--sp = val; + } + BREAK; + + CASE(OP_call_constructor): + call_flags = get_u16(pc) | FRAME_CF_CTOR; + goto global_function_call; + CASE(OP_call): + call_flags = get_u16(pc); + global_function_call: + js_reverse_val(sp, (call_flags & FRAME_CF_ARGC_MASK) + 1); + *--sp = JS_UNDEFINED; + goto generic_function_call; + CASE(OP_call_method): + { + int n, argc, short_func_idx; + JSValue func_obj; + JSObject *p; + JSByteArray *byte_code; + + call_flags = get_u16(pc); + + n = (call_flags & FRAME_CF_ARGC_MASK) + 2; + js_reverse_val(sp, n); + + generic_function_call: + POLL_INTERRUPT(); + byte_code = JS_VALUE_TO_PTR(b->byte_code); + /* save pc + 1 of the current call */ + fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - byte_code->buf); + function_call: + *--sp = JS_NewShortInt(call_flags); + *--sp = SP_TO_VALUE(ctx, fp); + + func_obj = sp[FRAME_OFFSET_FUNC_OBJ]; +#if defined(DUMP_EXEC) + JS_DumpValue(ctx, "calling", func_obj); +#endif + if (!JS_IsPtr(func_obj)) { + if (JS_VALUE_GET_SPECIAL_TAG(func_obj) != JS_TAG_SHORT_FUNC) + goto not_a_function; + short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(func_obj); + p = NULL; + goto c_function; + } else { + p = JS_VALUE_TO_PTR(func_obj); + if (p->mtag != JS_MTAG_OBJECT) + goto not_a_function; + if (p->class_id == JS_CLASS_C_FUNCTION) { + const JSCFunctionDef *fd; + int pushed_argc; + short_func_idx = p->u.cfunc.idx; + c_function: + fd = &ctx->c_function_table[short_func_idx]; + /* add undefined arguments if the caller did not + provide enough arguments */ + call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]); + if ((call_flags & FRAME_CF_CTOR) && + (fd->def_type != JS_CFUNC_constructor && + fd->def_type != JS_CFUNC_constructor_magic)) { + sp += 2; /* go back to the caller frame */ + ctx->sp = sp; + ctx->fp = fp; + val = JS_ThrowTypeError(ctx, "not a constructor"); + goto call_exception; + } + + argc = call_flags & FRAME_CF_ARGC_MASK; + /* JS_StackCheck may trigger a gc */ + ctx->sp = sp; + ctx->fp = fp; + n = JS_StackCheck(ctx, max_int(fd->arg_count - argc, 0)); + if (n) { + sp += 2; /* go back to the caller frame */ + val = JS_EXCEPTION; + goto call_exception; + } + pushed_argc = argc; + if (fd->arg_count > argc) { + n = fd->arg_count - argc; + sp -= n; + for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++) + sp[i] = sp[i + n]; + for(i = 0; i < n; i++) + sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED; + pushed_argc = fd->arg_count; + } + fp = sp; + ctx->sp = sp; + ctx->fp = fp; + switch(fd->def_type) { + case JS_CFUNC_generic: + case JS_CFUNC_constructor: + val = fd->func.generic(ctx, &fp[FRAME_OFFSET_THIS_OBJ], + call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK), + fp + FRAME_OFFSET_ARG0); + break; + case JS_CFUNC_generic_magic: + case JS_CFUNC_constructor_magic: + val = fd->func.generic_magic(ctx, &fp[FRAME_OFFSET_THIS_OBJ], + call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK), + fp + FRAME_OFFSET_ARG0, fd->magic); + break; + case JS_CFUNC_generic_params: + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + val = fd->func.generic_params(ctx, &fp[FRAME_OFFSET_THIS_OBJ], + call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK), + fp + FRAME_OFFSET_ARG0, p->u.cfunc.params); + break; + case JS_CFUNC_f_f: + { + double d; + if (JS_ToNumber(ctx, &d, fp[FRAME_OFFSET_ARG0])) { + val = JS_EXCEPTION; + } else { + d = fd->func.f_f(d); + } + val = JS_NewFloat64(ctx, d); + } + break; + default: + assert(0); + } + if (JS_IsExceptionOrTailCall(val) && + JS_VALUE_GET_SPECIAL_VALUE(val) >= JS_EX_CALL) { + JSValue *fp1, *sp1; + /* tail call: equivalent to calling the + function after the C function */ + /* XXX: handle the call flags of the caller ? */ + call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL; + sp = ctx->sp; + /* pop the frame */ + fp1 = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]); + /* move the new arguments at the correct stack position */ + argc = (call_flags & FRAME_CF_ARGC_MASK) + 2; + sp1 = fp + FRAME_OFFSET_ARG0 + pushed_argc - argc; + memmove(sp1, sp, sizeof(*sp) * (argc)); + sp = sp1; + fp = fp1; + goto function_call; + } else { + sp = fp + FRAME_OFFSET_ARG0 + pushed_argc; + goto return_call; + } + } else if (p->class_id == JS_CLASS_CLOSURE) { + int n_vars; + call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]); + if (call_flags & FRAME_CF_CTOR) { + ctx->sp = sp; + ctx->fp = fp; + /* Note: can recurse at this point */ + val = js_call_constructor_start(ctx, func_obj); + if (JS_IsException(val)) + goto call_exception; + sp[FRAME_OFFSET_THIS_OBJ] = val; + func_obj = sp[FRAME_OFFSET_FUNC_OBJ]; + p = JS_VALUE_TO_PTR(func_obj); + } + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + if (b->vars != JS_NULL) { + JSValueArray *vars = JS_VALUE_TO_PTR(b->vars); + n_vars = vars->size - b->arg_count; + } else { + n_vars = 0; + } + argc = call_flags & FRAME_CF_ARGC_MASK; + /* JS_StackCheck may trigger a gc */ + ctx->sp = sp; + ctx->fp = fp; + n = JS_StackCheck(ctx, max_int(b->arg_count - argc, 0) + 2 + n_vars + + b->stack_size); + if (n) { + val = JS_EXCEPTION; + goto call_exception; + } + func_obj = sp[FRAME_OFFSET_FUNC_OBJ]; + p = JS_VALUE_TO_PTR(func_obj); + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + /* add undefined arguments if the caller did not + provide enough arguments */ + if (unlikely(b->arg_count > argc)) { + n = b->arg_count - argc; + sp -= n; + for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++) + sp[i] = sp[i + n]; + for(i = 0; i < n; i++) + sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED; + } + fp = sp; + *--sp = JS_NewShortInt(0); /* FRAME_OFFSET_CUR_PC */ + *--sp = JS_NULL; /* FRAME_OFFSET_FIRST_VARREF */ + sp -= n_vars; + for(i = 0; i < n_vars; i++) + sp[i] = JS_UNDEFINED; + byte_code = JS_VALUE_TO_PTR(b->byte_code); + pc = byte_code->buf; + } else { + not_a_function: + sp += 2; /* go back to the caller frame */ + ctx->sp = sp; + ctx->fp = fp; + val = JS_ThrowTypeError(ctx, "not a function"); + call_exception: + if (!pc) { + goto done; + } else { + RESTORE(); + goto exception; + } + } + } + } + BREAK; + + exception: + /* 'val' must contain the exception */ + { + JSValue *stack_top, val2; + JSValueArray *vars; + int v; + /* exception before entering in the first function ? + (XXX: remove this test) */ + if (!pc) + goto done; + v = JS_VALUE_GET_SPECIAL_VALUE(val); + if (v >= JS_EX_CALL) { + /* tail call */ + call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL; + /* the opcode has only one byte, hence the PC must + be updated accordingly after the function + returns */ + if (opcode == OP_get_length || + opcode == OP_get_length2 || + opcode == OP_get_array_el || + opcode == OP_get_array_el2 || + opcode == OP_put_array_el) { + call_flags |= FRAME_CF_PC_ADD1; + } + // js_printf(ctx, "tail call: 0x%x\n", call_flags); + goto generic_function_call; + } + /* XXX: start gc in case of JS_EXCEPTION_MEM */ + stack_top = fp + FRAME_OFFSET_VAR0 + 1; + if (b->vars != JS_NULL) { + vars = JS_VALUE_TO_PTR(b->vars); + stack_top -= (vars->size - b->arg_count); + } + if (ctx->current_exception_is_uncatchable) { + sp = stack_top; + } else { + while (sp < stack_top) { + val2 = *sp++; + if (JS_VALUE_GET_SPECIAL_TAG(val2) == JS_TAG_CATCH_OFFSET) { + JSByteArray *byte_code; + /* exception caught by a 'catch' in the + current function */ + *--sp = ctx->current_exception; + ctx->current_exception = JS_NULL; + byte_code = JS_VALUE_TO_PTR(b->byte_code); + pc = byte_code->buf + JS_VALUE_GET_SPECIAL_VALUE(val2); + goto restart; + } + } + } + } + goto generic_return; + + CASE(OP_return_undef): + val = JS_UNDEFINED; + goto generic_return; + + CASE(OP_return): + val = sp[0]; + generic_return: + { + JSObject *p; + int argc, pc_offset; + JSValue val2; + JSVarRef *pv; + JSByteArray *byte_code; + + /* detach the variable references */ + val2 = fp[FRAME_OFFSET_FIRST_VARREF]; + while (val2 != JS_NULL) { + pv = JS_VALUE_TO_PTR(val2); + val2 = pv->u.next; + assert(!pv->is_detached); + pv->u.value = *pv->u.pvalue; + pv->is_detached = TRUE; + /* shrink 'pv' */ + set_free_block((uint8_t *)pv + sizeof(JSVarRef) - sizeof(JSValue), sizeof(JSValue)); + } + + call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]); + if (unlikely(call_flags & FRAME_CF_CTOR)) { + if (!JS_IsException(val) && !JS_IsObject(ctx, val)) { + val = fp[FRAME_OFFSET_THIS_OBJ]; + } + } + argc = call_flags & FRAME_CF_ARGC_MASK; + argc = max_int(argc, b->arg_count); + sp = fp + FRAME_OFFSET_ARG0 + argc; + return_call: + call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]); + /* XXX: restore stack_bottom to reduce memory usage */ + fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]); + if (fp == initial_fp) + goto done; + pc_offset = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]); + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + byte_code = JS_VALUE_TO_PTR(b->byte_code); + pc = byte_code->buf + pc_offset; + /* now we are in the calling function */ + if (JS_IsException(val)) + goto exception; + if (!(call_flags & FRAME_CF_POP_RET)) + *--sp = val; + /* Note: if variable size call, can add a flag in call_flags */ + if (!(call_flags & FRAME_CF_PC_ADD1)) + pc += 2; /* skip the call arg or get_field/put_field arg */ + } + BREAK; + + CASE(OP_catch): + { + int32_t diff; + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + diff = get_u32(pc); + *--sp = JS_VALUE_MAKE_SPECIAL(JS_TAG_CATCH_OFFSET, pc + diff - byte_code->buf); + pc += 4; + } + BREAK; + CASE(OP_throw): + val = *sp++; + SAVE(); + val = JS_Throw(ctx, val); + RESTORE(); + goto exception; + CASE(OP_gosub): + { + int32_t diff; + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + diff = get_u32(pc); + *--sp = JS_NewShortInt(pc + 4 - byte_code->buf); + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + uint32_t pos; + if (unlikely(!JS_IsInt(sp[0]))) + goto ret_fail; + pos = JS_VALUE_GET_INT(sp[0]); + if (unlikely(pos >= byte_code->size)) { + ret_fail: + SAVE(); + val = JS_ThrowInternalError(ctx, "invalid ret value"); + RESTORE(); + goto exception; + } + sp++; + pc = byte_code->buf + pos; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + *--sp = fp[FRAME_OFFSET_VAR0 - idx]; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + fp[FRAME_OFFSET_VAR0 - idx] = sp[0]; + sp++; + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + *--sp = fp[FRAME_OFFSET_ARG0 + idx]; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + fp[FRAME_OFFSET_ARG0 + idx] = sp[0]; + sp++; + } + BREAK; + + CASE(OP_get_loc0): *--sp = fp[FRAME_OFFSET_VAR0 - 0]; BREAK; + CASE(OP_get_loc1): *--sp = fp[FRAME_OFFSET_VAR0 - 1]; BREAK; + CASE(OP_get_loc2): *--sp = fp[FRAME_OFFSET_VAR0 - 2]; BREAK; + CASE(OP_get_loc3): *--sp = fp[FRAME_OFFSET_VAR0 - 3]; BREAK; + CASE(OP_get_loc8): *--sp = fp[FRAME_OFFSET_VAR0 - *pc++]; BREAK; + + CASE(OP_put_loc0): fp[FRAME_OFFSET_VAR0 - 0] = *sp++; BREAK; + CASE(OP_put_loc1): fp[FRAME_OFFSET_VAR0 - 1] = *sp++; BREAK; + CASE(OP_put_loc2): fp[FRAME_OFFSET_VAR0 - 2] = *sp++; BREAK; + CASE(OP_put_loc3): fp[FRAME_OFFSET_VAR0 - 3] = *sp++; BREAK; + CASE(OP_put_loc8): fp[FRAME_OFFSET_VAR0 - *pc++] = *sp++; BREAK; + + CASE(OP_get_arg0): *--sp = fp[FRAME_OFFSET_ARG0 + 0]; BREAK; + CASE(OP_get_arg1): *--sp = fp[FRAME_OFFSET_ARG0 + 1]; BREAK; + CASE(OP_get_arg2): *--sp = fp[FRAME_OFFSET_ARG0 + 2]; BREAK; + CASE(OP_get_arg3): *--sp = fp[FRAME_OFFSET_ARG0 + 3]; BREAK; + + CASE(OP_put_arg0): fp[FRAME_OFFSET_ARG0 + 0] = *sp++; BREAK; + CASE(OP_put_arg1): fp[FRAME_OFFSET_ARG0 + 1] = *sp++; BREAK; + CASE(OP_put_arg2): fp[FRAME_OFFSET_ARG0 + 2] = *sp++; BREAK; + CASE(OP_put_arg3): fp[FRAME_OFFSET_ARG0 + 3] = *sp++; BREAK; + + CASE(OP_get_var_ref): + CASE(OP_get_var_ref_nocheck): + { + int idx; + JSObject *p; + JSVarRef *pv; + idx = get_u16(pc); + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]); + if (pv->is_detached) + val = pv->u.value; + else + val = *pv->u.pvalue; + if (unlikely(val == JS_TAG_UNINITIALIZED) && + opcode == OP_get_var_ref) { + JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + SAVE(); + val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]); + RESTORE(); + goto exception; + } + pc += 2; + *--sp = val; + } + BREAK; + CASE(OP_put_var_ref): + CASE(OP_put_var_ref_nocheck): + { + int idx; + JSObject *p; + JSVarRef *pv; + JSValue *pval; + idx = get_u16(pc); + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]); + if (pv->is_detached) + pval = &pv->u.value; + else + pval = pv->u.pvalue; + if (unlikely(*pval == JS_TAG_UNINITIALIZED) && + opcode == OP_put_var_ref) { + JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + SAVE(); + val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]); + RESTORE(); + goto exception; + } + *pval = *sp++; + pc += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + POLL_INTERRUPT(); + BREAK; + CASE(OP_if_false): + CASE(OP_if_true): + { + int res; + + pc += 4; + + res = JS_ToBool(ctx, *sp++); + if (res ^ (OP_if_true - opcode)) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + POLL_INTERRUPT(); + } + BREAK; + + CASE(OP_lnot): + { + int res; + res = JS_ToBool(ctx, sp[0]); + sp[0] = JS_NewBool(!res); + } + BREAK; + + CASE(OP_get_field2): + sp--; + sp[0] = sp[1]; + goto get_field_common; + CASE(OP_get_field): + get_field_common: + { + int idx; + JSValue prop, obj; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + idx = get_u16(pc); + prop = cpool->arr[idx]; + obj = sp[0]; + if (likely(JS_IsPtr(obj))) { + /* fast case */ + JSObject *p = JS_VALUE_TO_PTR(obj); + JSProperty *pr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto get_field_slow; + for(;;) { + /* no array check is necessary because 'prop' is + guaranteed not to be a numeric property */ + /* XXX: slow due to short ints */ + pr = find_own_property_inlined(ctx, p, prop); + if (pr) { + if (unlikely(pr->prop_type != JS_PROP_NORMAL)) { + /* sp[0] is this_obj, obj is the current + object */ + goto get_field_slow; + } else { + val = pr->value; + break; + } + } + obj = p->proto; + if (obj == JS_NULL) { + val = JS_UNDEFINED; + break; + } + p = JS_VALUE_TO_PTR(obj); + } + } else { + get_field_slow: + SAVE(); + val = JS_GetPropertyInternal(ctx, obj, prop, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + } + pc += 2; + sp[0] = val; + } + BREAK; + + CASE(OP_get_length2): + sp--; + sp[0] = sp[1]; + goto get_length_common; + + CASE(OP_get_length): + get_length_common: + { + JSValue obj; + obj = sp[0]; + if (likely(JS_IsPtr(obj))) { + /* fast case */ + JSObject *p = JS_VALUE_TO_PTR(obj); + if (p->mtag == JS_MTAG_OBJECT) { + if (p->class_id == JS_CLASS_ARRAY) { + if (unlikely(p->proto != ctx->class_proto[JS_CLASS_ARRAY] || + p->props != ctx->empty_props)) + goto get_length_slow; + val = JS_NewShortInt(p->u.array.len); + } else { + goto get_length_slow; + } + } else if (p->mtag == JS_MTAG_STRING) { + JSString *ps = (JSString *)p; + if (likely(ps->is_ascii)) + val = JS_NewShortInt(ps->len); + else + val = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, obj, ps->len * 2)); + } else { + goto get_length_slow; + } + } else if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + val = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1); + } else { + get_length_slow: + SAVE(); + val = JS_GetPropertyInternal(ctx, obj, js_get_atom(ctx, JS_ATOM_length), TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + } + sp[0] = val; + } + BREAK; + + CASE(OP_put_field): + { + int idx; + JSValue prop, obj; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + + idx = get_u16(pc); + prop = cpool->arr[idx]; + obj = sp[1]; + if (likely(JS_IsPtr(obj))) { + /* fast case */ + JSObject *p = JS_VALUE_TO_PTR(obj); + JSProperty *pr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto put_field_slow; + /* no array check is necessary because 'prop' is + guaranteed not to be a numeric property */ + /* XXX: slow due to short ints */ + pr = find_own_property_inlined(ctx, p, prop); + if (unlikely(!pr)) + goto put_field_slow; + if (unlikely(pr->prop_type != JS_PROP_NORMAL)) + goto put_field_slow; + /* XXX: slow */ + if (unlikely(JS_IS_ROM_PTR(ctx, pr))) + goto put_field_slow; + pr->value = sp[0]; + sp += 2; + } else { + put_field_slow: + val = *sp++; + SAVE(); + val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + sp++; + } + pc += 2; + } + BREAK; + + CASE(OP_get_array_el2): + val = sp[0]; + sp[0] = sp[1]; + goto get_array_el_common; + CASE(OP_get_array_el): + val = sp[0]; + sp++; + get_array_el_common: + { + JSValue prop = val, obj; + obj = sp[0]; + if (JS_IsPtr(obj) && JS_IsInt(prop)) { + /* fast case with array */ + /* XXX: optimize typed arrays too ? */ + JSObject *p = JS_VALUE_TO_PTR(obj); + uint32_t idx; + JSValueArray *arr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto get_array_el_slow; + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto get_array_el_slow; + idx = JS_VALUE_GET_INT(prop); + if (unlikely(idx >= p->u.array.len)) + goto get_array_el_slow; + + arr = JS_VALUE_TO_PTR(p->u.array.tab); + val = arr->arr[idx]; + } else { + get_array_el_slow: + SAVE(); + prop = JS_ToPropertyKey(ctx, prop); + RESTORE(); + if (JS_IsException(prop)) { + val = prop; + goto exception; + } + SAVE(); + val = JS_GetPropertyInternal(ctx, sp[0], prop, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + } + sp[0] = val; + } + BREAK; + + CASE(OP_put_array_el): + { + JSValue prop, obj; + obj = sp[2]; + prop = sp[1]; + if (JS_IsPtr(obj) && JS_IsInt(prop)) { + /* fast case with array */ + /* XXX: optimize typed arrays too ? */ + JSObject *p = JS_VALUE_TO_PTR(obj); + uint32_t idx; + JSValueArray *arr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto put_array_el_slow; + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto put_array_el_slow; + idx = JS_VALUE_GET_INT(prop); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (unlikely(idx >= p->u.array.len)) { + if (idx == p->u.array.len && + p->u.array.tab != JS_NULL && + idx < arr->size) { + arr->arr[idx] = sp[0]; + p->u.array.len = idx + 1; + } else { + goto put_array_el_slow; + } + } else { + arr->arr[idx] = sp[0]; + } + sp += 3; + } else { + put_array_el_slow: + SAVE(); + sp[1] = JS_ToPropertyKey(ctx, sp[1]); + RESTORE(); + if (JS_IsException(sp[1])) { + val = sp[1]; + goto exception; + } + val = *sp++; + prop = *sp++; + SAVE(); + val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + sp++; + } + } + BREAK; + + CASE(OP_define_field): + CASE(OP_define_getter): + CASE(OP_define_setter): + { + int idx; + JSValue prop; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + + idx = get_u16(pc); + prop = cpool->arr[idx]; + + SAVE(); + if (opcode == OP_define_field) { + val = JS_DefinePropertyValue(ctx, sp[1], prop, sp[0]); + } else if (opcode == OP_define_getter) + val = JS_DefinePropertyGetSet(ctx, sp[1], prop, sp[0], JS_UNDEFINED, JS_DEF_PROP_HAS_GET); + else + val = JS_DefinePropertyGetSet(ctx, sp[1], prop, JS_UNDEFINED, sp[0], JS_DEF_PROP_HAS_SET); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + pc += 2; + sp++; + } + BREAK; + + CASE(OP_set_proto): + { + if (JS_IsObject(ctx, sp[0]) || JS_IsNull(sp[0])) { + SAVE(); + val = js_set_prototype_internal(ctx, sp[1], sp[0]); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + } + sp++; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int r; + if (unlikely(__builtin_add_overflow((int)op1, (int)op2, &r))) + goto add_slow; + sp[1] = (uint32_t)r; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) { + double d1, d2; + d1 = js_get_short_float(op1); + d2 = js_get_short_float(op2); + dr = d1 + d2; + sp++; + goto float_result; + } else +#endif + { + add_slow: + SAVE(); + val = js_add_slow(ctx); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + } + sp++; + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int r; + if (unlikely(__builtin_sub_overflow((int)op1, (int)op2, &r))) + goto binary_arith_slow; + sp[1] = (uint32_t)r; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) { + double d1, d2; + d1 = js_get_short_float(op1); + d2 = js_get_short_float(op2); + dr = d1 - d2; + sp++; + goto float_result; + } else +#endif + { + goto binary_arith_slow; + } + sp++; + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + int64_t r; + v1 = (int)op1; + v2 = (int)op2 >> 1; + r = (int64_t)v1 * (int64_t)v2; + if (unlikely(r != (int)r)) { +#if defined(JS_USE_SHORT_FLOAT) + dr = (double)(r >> 1); + sp++; + goto float_result; +#else + goto binary_arith_slow; +#endif + } + /* -0 case */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + sp[1] = ctx->minus_zero; + } else { + sp[1] = (uint32_t)r; + } + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) { + double d1, d2; + d1 = js_get_short_float(op1); + d2 = js_get_short_float(op2); + dr = d1 * d2; + sp++; + goto float_result; + } else +#endif + { + goto binary_arith_slow; + } + sp++; + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + SAVE(); + val = JS_NewFloat64(ctx, (double)v1 / (double)v2); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + sp++; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[1] = JS_NewShortInt(r); + sp++; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + SAVE(); + val = js_binary_arith_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_plus): + { + JSValue op1; + op1 = sp[0]; + if (JS_IsIntOrShortFloat(op1) || + (JS_IsPtr(op1) && js_get_mtag(JS_VALUE_TO_PTR(op1)) == JS_MTAG_FLOAT64)) { + } else { + goto unary_arith_slow; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = op1; + if (v1 == 0) { + sp[0] = ctx->minus_zero; + } else if (v1 == INT32_MIN) { +#if defined(JS_USE_SHORT_FLOAT) + dr = -(double)JS_SHORTINT_MIN; + goto float_result; +#else + goto unary_arith_slow; +#endif + } else { + sp[0] = -v1; + } + } else +#if defined(JS_USE_SHORT_FLOAT) + if (JS_IsShortFloat(op1)) { + dr = -js_get_short_float(op1); + float_result: + /* for efficiency, we don't try to store it as a short integer */ + if (likely(fabs(dr) >= 0x1p-127 && fabs(dr) <= 0x1p+128)) { + val = js_to_short_float(dr); + } else if (dr == 0.0) { + if (float64_as_uint64(dr) != 0) { + /* minus zero often happens, so it is worth having a constant + value */ + val = ctx->minus_zero; + } else { + /* XXX: could have a short float + representation for zero and minus zero + so that the float fast case is still + used when they happen */ + val = JS_NewShortInt(0); + } + } else { + /* slow case: need to allocate it */ + SAVE(); + val = js_alloc_float64(ctx, dr); + RESTORE(); + if (JS_IsException(val)) + goto exception; + } + sp[0] = val; + } else +#endif + { + goto unary_arith_slow; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = JS_VALUE_GET_INT(op1); + if (unlikely(v1 == JS_SHORTINT_MAX)) + goto unary_arith_slow; + sp[0] = JS_NewShortInt(v1 + 1); + } else { + goto unary_arith_slow; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = JS_VALUE_GET_INT(op1); + if (unlikely(v1 == JS_SHORTINT_MIN)) + goto unary_arith_slow; + sp[0] = JS_NewShortInt(v1 - 1); + } else { + unary_arith_slow: + SAVE(); + val = js_unary_arith_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[0] = val; + } + } + BREAK; + CASE(OP_post_inc): + CASE(OP_post_dec): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = JS_VALUE_GET_INT(op1) + 2 * (opcode - OP_post_dec) - 1; + if (v1 < JS_SHORTINT_MIN || v1 > JS_SHORTINT_MAX) + goto slow_post_inc_dec; + val = JS_NewShortInt(v1); + } else { + slow_post_inc_dec: + SAVE(); + val = js_post_inc_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + } + *--sp = val; + } + BREAK; + + CASE(OP_not): + { + JSValue op1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + sp[0] = (~op1) & (~1); + } else { + SAVE(); + val = js_not_slow(ctx); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[0] = val; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t r; + r = JS_VALUE_GET_INT(op1) << (JS_VALUE_GET_INT(op2) & 0x1f); + if (unlikely(r < JS_SHORTINT_MIN || r > JS_SHORTINT_MAX)) { +#if defined(JS_USE_SHORT_FLOAT) + dr = (double)r; + sp++; + goto float_result; +#else + goto binary_logic_slow; +#endif + } + sp[1] = JS_NewShortInt(r); + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t r; + r = (uint32_t)JS_VALUE_GET_INT(op1) >> + ((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f); + if (unlikely(r > JS_SHORTINT_MAX)) { +#if defined(JS_USE_SHORT_FLOAT) + dr = (double)r; + sp++; + goto float_result; +#else + goto binary_logic_slow; +#endif + } + sp[1] = JS_NewShortInt(r); + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = ((int)op1 >> ((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f)) & ~1; + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = op1 & op2; + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = op1 | op2; + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = op1 ^ op2; + sp++; + } else { + binary_logic_slow: + SAVE(); + val = js_binary_logic_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + sp++; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[1]; \ + op2 = sp[0]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[1] = JS_NewBool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp++; \ + } else { \ + SAVE(); \ + val = slow_call; \ + RESTORE(); \ + if (JS_IsException(val)) \ + goto exception; \ + sp[1] = val; \ + sp++; \ + } \ + } \ + BREAK; + + OP_CMP(OP_lt, <, js_relational_slow(ctx, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, 1)); + CASE(OP_in): + SAVE(); + val = js_operator_in(ctx); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_instanceof): + SAVE(); + val = js_operator_instanceof(ctx); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_typeof): + SAVE(); + val = js_operator_typeof(ctx, sp[0]); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + BREAK; + CASE(OP_delete): + SAVE(); + val = JS_DeleteProperty(ctx, sp[1], sp[0]); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_for_in_start): + CASE(OP_for_of_start): + SAVE(); + val = js_for_of_start(ctx, (opcode == OP_for_in_start)); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + BREAK; + CASE(OP_for_of_next): + SAVE(); + val = js_for_of_next(ctx); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp -= 2; + BREAK; + default: + { + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + SAVE(); + val = JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - byte_code->buf - 1), opcode); + RESTORE(); + } + goto exception; + } + restart: ; + } /* switch */ + done: + ctx->sp = sp; + ctx->fp = fp; + ctx->js_call_rec_count--; + return val; +} + +#undef SAVE +#undef RESTORE + +static inline int is_ident_first(int c) +{ + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_' || c == '$'; +} + +static inline int is_ident_next(int c) +{ + return is_ident_first(c) || is_num(c); +} + +/**********************************************************************/ +/* dump utilities */ + +#ifdef JS_DUMP + +static void js_dump_array(JSContext *ctx, JSValueArray *arr, int len) +{ + int i; + + js_printf(ctx, "[ "); + for(i = 0; i < len; i++) { + if (i != 0) + js_printf(ctx, ", "); + JS_PrintValue(ctx, arr->arr[i]); + } + js_printf(ctx, " ]"); +} + +/* put constructors into a separate table */ +/* XXX: improve by using a table */ +static JSValue js_find_class_name(JSContext *ctx, int class_id) +{ + const JSCFunctionDef *fd; + fd = ctx->c_function_table; + while ((fd->def_type != JS_CFUNC_constructor_magic && + fd->def_type != JS_CFUNC_constructor) || + fd->magic != class_id) { + fd++; + } + return reloc_c_func_name(ctx, fd->name); +} + +static void js_dump_float64(JSContext *ctx, double d) +{ + char buf[32]; + JSDTOATempMem tmp_mem; /* XXX: potentially large stack size */ + js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &tmp_mem); + js_printf(ctx, "%s", buf); +} + +static void dump_regexp(JSContext *ctx, JSObject *p); + +static void js_dump_error(JSContext *ctx, JSObject *p) +{ + JSObject *p1; + JSProperty *pr; + JSValue name; + + /* find the error name without side effect */ + p1 = p; + if (p->proto != JS_NULL) + p1 = JS_VALUE_TO_PTR(p->proto); + pr = find_own_property(ctx, p1, js_get_atom(ctx, JS_ATOM_name)); + if (!pr || !JS_IsString(ctx, pr->value)) + name = js_get_atom(ctx, JS_ATOM_Error); + else + name = pr->value; + js_printf(ctx, "%" JSValue_PRI, name); + if (p->u.error.message != JS_NULL) { + js_printf(ctx, ": %" JSValue_PRI, p->u.error.message); + } + if (p->u.error.stack != JS_NULL) { + /* remove the trailing '\n' if any */ + js_printf(ctx, "\n%#" JSValue_PRI, p->u.error.stack); + } +} + +static void js_dump_object(JSContext *ctx, JSObject *p, int flags) +{ + if (flags & JS_DUMP_LONG) { + switch(p->class_id) { + case JS_CLASS_CLOSURE: + { + JSFunctionBytecode *b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + js_printf(ctx, "function "); + JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + } + break; + case JS_CLASS_C_FUNCTION: + js_printf(ctx, "function "); + JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[p->u.cfunc.idx].name), JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + break; + case JS_CLASS_ERROR: + js_dump_error(ctx, p); + break; + case JS_CLASS_REGEXP: + dump_regexp(ctx, p); + break; + default: + case JS_CLASS_ARRAY: + case JS_CLASS_OBJECT: + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + int i, idx; + uint32_t v; + double d; + JSObject *pbuffer; + JSByteArray *arr; + JS_PrintValueF(ctx, js_find_class_name(ctx, p->class_id), + JS_DUMP_NOQUOTE); + js_printf(ctx, "([ "); + pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer); + for(i = 0; i < p->u.typed_array.len; i++) { + if (i != 0) + js_printf(ctx, ", "); + idx = i + p->u.typed_array.offset; + switch(p->class_id) { + default: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + v = *((uint8_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_INT8_ARRAY: + v = *((int8_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_INT16_ARRAY: + v = *((int16_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_UINT16_ARRAY: + v = *((uint16_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_INT32_ARRAY: + v = *((int32_t *)arr->buf + idx); + ta_i32: + js_printf(ctx, "%d", v); + break; + case JS_CLASS_UINT32_ARRAY: + v = *((uint32_t *)arr->buf + idx); + js_printf(ctx, "%u", v); + break; + case JS_CLASS_FLOAT32_ARRAY: + d = *((float *)arr->buf + idx); + goto ta_d; + case JS_CLASS_FLOAT64_ARRAY: + d = *((double *)arr->buf + idx); + ta_d: + js_dump_float64(ctx, d); + break; + } + } + js_printf(ctx, " ])"); + } else { + int i, j, prop_count, hash_mask; + JSProperty *pr; + JSValueArray *arr; + BOOL is_first = TRUE; + + arr = JS_VALUE_TO_PTR(p->props); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + if (p->class_id == JS_CLASS_ARRAY) { + JSValueArray *tab = JS_VALUE_TO_PTR(p->u.array.tab); + js_printf(ctx, "[ "); + for(i = 0; i < p->u.array.len; i++) { + if (!is_first) + js_printf(ctx, ", "); + JS_PrintValue(ctx, tab->arr[i]); + is_first = FALSE; + } + } else { + if (p->class_id != JS_CLASS_OBJECT) { + JSValue class_name = js_find_class_name(ctx, p->class_id); + if (!JS_IsNull(class_name)) + JS_PrintValueF(ctx, class_name, JS_DUMP_NOQUOTE); + js_putchar(ctx, ' '); + } + js_printf(ctx, "{ "); + } + for(i = 0, j = 0; j < prop_count; i++) { + pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i]; + if (pr->key != JS_UNINITIALIZED) { + if (!is_first) + js_printf(ctx, ", "); + JS_PrintValueF(ctx, pr->key, JS_DUMP_NOQUOTE); + js_printf(ctx, ": "); + if (!(flags & JS_DUMP_RAW) && pr->prop_type == JS_PROP_SPECIAL) { + JS_PrintValue(ctx, get_special_prop(ctx, pr->value)); + } else { + JS_PrintValue(ctx, pr->value); + } + is_first = FALSE; + j++; + } + } + js_printf(ctx, " %c", + p->class_id == JS_CLASS_ARRAY ? ']' : '}'); + } + break; + } + } else { + const char *str; + if (p->class_id == JS_CLASS_ARRAY) + str = "Array"; + else if (p->class_id == JS_CLASS_ERROR) + str = "Error"; + else if (p->class_id == JS_CLASS_CLOSURE || + p->class_id == JS_CLASS_C_FUNCTION) { + str = "Function"; + } else { + str = "Object"; + } + js_printf(ctx, "[object %s]", str); + } +} + +static void dump_string(JSContext *ctx, int sep, const uint8_t *buf, size_t len, + int flags) +{ + BOOL use_quote; + const uint8_t *p, *p_end; + size_t i, clen; + int c; + + use_quote = TRUE; + if (flags & JS_DUMP_NOQUOTE) { + if (len >= 1 && is_ident_first(buf[0])) { + for(i = 1; i < len; i++) { + if (!is_ident_next(buf[i])) + goto need_quote; + } + use_quote = FALSE; + } + need_quote: ; + } + + if (!(flags & JS_DUMP_RAW)) + sep = '"'; + if (use_quote) + js_putchar(ctx, sep); + p = buf; + p_end = buf + len; + while (p < p_end) { + c = utf8_get(p, &clen); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + js_putchar(ctx, '\\'); + js_putchar(ctx, c); + break; + default: + if (c < 32 || (c >= 0xd800 && c < 0xe000)) { + js_printf(ctx, "\\u%04x", c); + } else { + ctx->write_func(ctx->opaque, p, clen); + } + break; + } + p += clen; + } + if (use_quote) + js_putchar(ctx, sep); +} + +void JS_PrintValueF(JSContext *ctx, JSValue val, int flags) +{ + if (JS_IsInt(val)) { + js_printf(ctx, "%d", JS_VALUE_GET_INT(val)); + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + js_dump_float64(ctx, js_get_short_float(val)); + } else +#endif + if (!JS_IsPtr(val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + case JS_TAG_UNINITIALIZED: + case JS_TAG_BOOL: + js_printf(ctx, "%"JSValue_PRI"", val); + break; + case JS_TAG_EXCEPTION: + js_printf(ctx, "[exception %d]", JS_VALUE_GET_SPECIAL_VALUE(val)); + break; + case JS_TAG_CATCH_OFFSET: + js_printf(ctx, "[catch_offset %d]", JS_VALUE_GET_SPECIAL_VALUE(val)); + break; + case JS_TAG_SHORT_FUNC: + { + int idx = JS_VALUE_GET_SPECIAL_VALUE(val); + js_printf(ctx, "function "); + JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[idx].name), JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + } + break; + case JS_TAG_STRING_CHAR: + { + uint8_t buf[UTF8_CHAR_LEN_MAX + 1]; + int len; + len = get_short_string(buf, val); + dump_string(ctx, '`', buf, len, flags); + } + break; + default: + js_printf(ctx, "[tag %d]", (int)JS_VALUE_GET_SPECIAL_TAG(val)); + break; + } + } else { + void *ptr = JS_VALUE_TO_PTR(val); + int mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = ptr; + js_dump_float64(ctx, p->u.dval); + } + break; + case JS_MTAG_OBJECT: + js_dump_object(ctx, ptr, flags); + break; + case JS_MTAG_STRING: + { + JSString *p = ptr; + int sep; + sep = p->is_unique ? '\'' : '\"'; + dump_string(ctx, sep, p->buf, p->len, flags); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *arr = ptr; + js_dump_array(ctx, arr, arr->size); + } + break; + case JS_MTAG_BYTE_ARRAY: + { + JSByteArray *arr = ptr; + js_printf(ctx, "byte_array(%" PRIu64 ")", (uint64_t)arr->size); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = ptr; + js_printf(ctx, "bytecode_function "); + JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + } + break; + case JS_MTAG_VARREF: + { + JSVarRef *pv = ptr; + js_printf(ctx, "var_ref("); + if (pv->is_detached) + JS_PrintValue(ctx, pv->u.value); + else + JS_PrintValue(ctx, *pv->u.pvalue); + js_printf(ctx, ")"); + } + break; + default: + js_printf(ctx, "[mtag %d]", mtag); + break; + } + } +} + +void JS_PrintValue(JSContext *ctx, JSValue val) +{ + return JS_PrintValueF(ctx, val, 0); +} + +static const char *get_mtag_name(unsigned int mtag) +{ + if (mtag >= countof(js_mtag_name)) + return "?"; + else + return js_mtag_name[mtag]; +} + +static uint32_t val_to_offset(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) + return 0; + else + return (uint8_t *)JS_VALUE_TO_PTR(val) - ctx->heap_base; +} + +void JS_DumpMemory(JSContext *ctx, BOOL is_long) +{ + uint8_t *ptr; + uint32_t mtag_mem_size[JS_MTAG_COUNT]; + uint32_t mtag_count[JS_MTAG_COUNT]; + uint32_t tot_size, i; + if (is_long) { + js_printf(ctx, "%10s %s %8s %15s %10s %10s %s\n", "OFFSET", "M", "SIZE", "TAG", "PROTO", "PROPS", "EXTRA"); + } + for(i = 0; i < JS_MTAG_COUNT; i++) { + mtag_mem_size[i] = 0; + mtag_count[i] = 0; + } + tot_size = 0; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + int mtag, size, gc_mark; + mtag = ((JSMemBlockHeader *)ptr)->mtag; + gc_mark = ((JSMemBlockHeader *)ptr)->gc_mark; + size = get_mblock_size(ptr); + mtag_mem_size[mtag] += size; + mtag_count[mtag]++; + tot_size += size; + if (is_long) { + js_printf(ctx, "0x%08x %c %8u %15s", + (unsigned int)((uint8_t *)ptr - ctx->heap_base), + gc_mark ? '*' : ' ', + size, + get_mtag_name(mtag)); + if (mtag != JS_MTAG_FREE) { + if (mtag == JS_MTAG_OBJECT) { + JSObject *p = (JSObject *)ptr; + js_printf(ctx, " 0x%08x 0x%08x", + val_to_offset(ctx, p->proto), val_to_offset(ctx, p->props)); + } else { + js_printf(ctx, " %10s %10s", "", ""); + } + js_printf(ctx, " "); + JS_PrintValueF(ctx, JS_VALUE_FROM_PTR(ptr), JS_DUMP_RAW); + } + js_printf(ctx, "\n"); + } + ptr += size; + } + + js_printf(ctx, "%15s %8s %8s %8s %8s\n", "TAG", "COUNT", "AVG_SIZE", "SIZE", "RATIO"); + for(i = 0; i < JS_MTAG_COUNT; i++) { + if (mtag_count[i] != 0) { + js_printf(ctx, "%15s %8u %8d %8u %7d%%\n", + get_mtag_name(i), + (unsigned int)mtag_count[i], + (int)js_lrint((double)mtag_mem_size[i] / (double)mtag_count[i]), + (unsigned int)mtag_mem_size[i], + (int)js_lrint((double)mtag_mem_size[i] / (double)tot_size * 100.0)); + } + } + js_printf(ctx, "heap size=%u/%u stack_size=%u\n", + (unsigned int)(ctx->heap_free - ctx->heap_base), + (unsigned int)(ctx->stack_top - ctx->heap_base), + (unsigned int)(ctx->stack_top - (uint8_t *)ctx->sp)); +} + +static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx) +{ + int i; + JSValueArray *arr; + + arr = JS_VALUE_TO_PTR( ctx->unique_strings); + js_printf(ctx, "%5s %s\n", "N", "UNIQUE_STRING"); + for(i = 0; i < ctx->unique_strings_len; i++) { + js_printf(ctx, "%5d ", i); + JS_PrintValue(ctx, arr->arr[i]); + js_printf(ctx, "\n"); + } +} +#else +void JS_PrintValueF(JSContext *ctx, JSValue val, int flags) +{ +} +void JS_PrintValue(JSContext *ctx, JSValue val) +{ + return JS_PrintValueF(ctx, val, 0); +} +void JS_DumpMemory(JSContext *ctx, BOOL is_long) +{ +} +static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx) +{ +} +#endif + +void JS_DumpValueF(JSContext *ctx, const char *str, + JSValue val, int flags) +{ + js_printf(ctx, "%s=", str); + JS_PrintValueF(ctx, val, flags); + js_printf(ctx, "\n"); +} + +void JS_DumpValue(JSContext *ctx, const char *str, + JSValue val) +{ + JS_DumpValueF(ctx, str, val, 0); +} + + +/**************************************************/ +/* JS parser */ + +enum { + TOK_NUMBER = 128, + TOK_STRING, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_POW_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, + TOK_POW, + TOK_EOF, + /* keywords */ + TOK_FIRST_KEYWORD, + TOK_NULL = TOK_FIRST_KEYWORD + JS_ATOM_null, + TOK_FALSE = TOK_FIRST_KEYWORD + JS_ATOM_false, + TOK_TRUE = TOK_FIRST_KEYWORD + JS_ATOM_true, + TOK_IF = TOK_FIRST_KEYWORD + JS_ATOM_if, + TOK_ELSE = TOK_FIRST_KEYWORD + JS_ATOM_else, + TOK_RETURN = TOK_FIRST_KEYWORD + JS_ATOM_return, + TOK_VAR = TOK_FIRST_KEYWORD + JS_ATOM_var, + TOK_THIS = TOK_FIRST_KEYWORD + JS_ATOM_this, + TOK_DELETE = TOK_FIRST_KEYWORD + JS_ATOM_delete, + TOK_VOID = TOK_FIRST_KEYWORD + JS_ATOM_void, + TOK_TYPEOF = TOK_FIRST_KEYWORD + JS_ATOM_typeof, + TOK_NEW = TOK_FIRST_KEYWORD + JS_ATOM_new, + TOK_IN = TOK_FIRST_KEYWORD + JS_ATOM_in, + TOK_INSTANCEOF = TOK_FIRST_KEYWORD + JS_ATOM_instanceof, + TOK_DO = TOK_FIRST_KEYWORD + JS_ATOM_do, + TOK_WHILE = TOK_FIRST_KEYWORD + JS_ATOM_while, + TOK_FOR = TOK_FIRST_KEYWORD + JS_ATOM_for, + TOK_BREAK = TOK_FIRST_KEYWORD + JS_ATOM_break, + TOK_CONTINUE = TOK_FIRST_KEYWORD + JS_ATOM_continue, + TOK_SWITCH = TOK_FIRST_KEYWORD + JS_ATOM_switch, + TOK_CASE = TOK_FIRST_KEYWORD + JS_ATOM_case, + TOK_DEFAULT = TOK_FIRST_KEYWORD + JS_ATOM_default, + TOK_THROW = TOK_FIRST_KEYWORD + JS_ATOM_throw, + TOK_TRY = TOK_FIRST_KEYWORD + JS_ATOM_try, + TOK_CATCH = TOK_FIRST_KEYWORD + JS_ATOM_catch, + TOK_FINALLY = TOK_FIRST_KEYWORD + JS_ATOM_finally, + TOK_FUNCTION = TOK_FIRST_KEYWORD + JS_ATOM_function, + TOK_DEBUGGER = TOK_FIRST_KEYWORD + JS_ATOM_debugger, + TOK_WITH = TOK_FIRST_KEYWORD + JS_ATOM_with, + TOK_CLASS = TOK_FIRST_KEYWORD + JS_ATOM_class, + TOK_CONST = TOK_FIRST_KEYWORD + JS_ATOM_const, + TOK_ENUM = TOK_FIRST_KEYWORD + JS_ATOM_enum, + TOK_EXPORT = TOK_FIRST_KEYWORD + JS_ATOM_export, + TOK_EXTENDS = TOK_FIRST_KEYWORD + JS_ATOM_extends, + TOK_IMPORT = TOK_FIRST_KEYWORD + JS_ATOM_import, + TOK_SUPER = TOK_FIRST_KEYWORD + JS_ATOM_super, + TOK_IMPLEMENTS = TOK_FIRST_KEYWORD + JS_ATOM_implements, + TOK_INTERFACE = TOK_FIRST_KEYWORD + JS_ATOM_interface, + TOK_LET = TOK_FIRST_KEYWORD + JS_ATOM_let, + TOK_PACKAGE = TOK_FIRST_KEYWORD + JS_ATOM_package, + TOK_PRIVATE = TOK_FIRST_KEYWORD + JS_ATOM_private, + TOK_PROTECTED = TOK_FIRST_KEYWORD + JS_ATOM_protected, + TOK_PUBLIC = TOK_FIRST_KEYWORD + JS_ATOM_public, + TOK_STATIC = TOK_FIRST_KEYWORD + JS_ATOM_static, + TOK_YIELD = TOK_FIRST_KEYWORD + JS_ATOM_yield, +}; + +/* this structure is pushed on the JS stack, so all members must be JSValue */ +typedef struct BlockEnv { + JSValue prev; /* JS_NULL or stack index */ + JSValue label_name; /* JS_NULL if none */ + JSValue label_break; + JSValue label_cont; + JSValue label_finally; + JSValue drop_count; /* (int) number of stack elements to drop */ +} BlockEnv; + +typedef uint32_t JSSourcePos; + +typedef struct JSToken { + int val; + JSSourcePos source_pos; /* position in source */ + union { + double d; /* TOK_NUMBER */ + struct { + uint32_t re_flags; /* regular expression flags */ + uint32_t re_end_pos; /* at the final '/' */ + } regexp; + } u; + JSValue value; /* associated value: string for TOK_STRING, TOK_REGEXP; + identifier for TOK_IDENT or keyword */ +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + JSToken token; + + BOOL got_lf : 8; /* true if got line feed before the current token */ + /* global eval: variables are defined as global */ + BOOL is_eval : 8; + /* if true, return the last value. */ + BOOL has_retval : 8; + /* if true, implicitly define global variables in an + assignment. */ + BOOL is_repl : 8; + BOOL has_column : 8; /* column debug info is present */ + /* TRUE if the expression result has been dropped (see PF_DROP) */ + BOOL dropped_result : 8; + JSValue source_str; /* source string or JS_NULL */ + JSValue filename_str; /* 'filename' converted to string */ + /* zero terminated source buffer. Automatically updated by the GC + if source_str is a string */ + const uint8_t *source_buf; + uint32_t buf_pos; + uint32_t buf_len; + + /* current function */ + JSValue cur_func; + JSValue byte_code; + uint32_t byte_code_len; + int last_opcode_pos; /* -1 if no last opcode */ + int last_pc2line_pos; /* pc2line pos for the last opcode */ + JSSourcePos last_pc2line_source_pos; + + uint32_t pc2line_bit_len; + JSSourcePos pc2line_source_pos; /* last generated source pos */ + + uint16_t cpool_len; + /* size of the byte code necessary to define the hoisted functions */ + uint32_t hoisted_code_len; + + /* argument + defined local variable count */ + uint16_t local_vars_len; + + int eval_ret_idx; /* variable index for the eval return value, -1 + if no return value */ + JSValue top_break; /* JS_NULL or SP_TO_VALUE(BlockEnv *) */ + + /* regexp parsing only */ + uint8_t capture_count; + uint8_t re_in_js: 1; + uint8_t multi_line : 1; + uint8_t dotall : 1; + uint8_t ignore_case : 1; + uint8_t is_unicode : 1; + + /* error handling */ + jmp_buf jmp_env; + char error_msg[64]; +} JSParseState; + +static int js_parse_json_value(JSParseState *s, int state, int dummy_param); +static JSValue js_parse_regexp(JSParseState *s, int eval_flags); +static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf); +static int re_parse_alternative(JSParseState *s, int state, int dummy_param); +static int re_parse_disjunction(JSParseState *s, int state, int dummy_param); + +#ifdef DUMP_BYTECODE +static __maybe_unused void dump_byte_code(JSContext *ctx, JSFunctionBytecode *b) +{ + JSByteArray *arr, *pc2line; + JSValueArray *cpool, *vars, *ext_vars; + const JSOpCode *oi; + int pos, op, size, addr, idx, arg_count, len, i, line_num, col_num; + int line_num1, col_num1, hoisted_code_len; + uint8_t *tab; + uint32_t pc2line_pos; + + arr = JS_VALUE_TO_PTR(b->byte_code); + if (b->cpool != JS_NULL) + cpool = JS_VALUE_TO_PTR(b->cpool); + else + cpool = NULL; + if (b->vars != JS_NULL) + vars = JS_VALUE_TO_PTR(b->vars); + else + vars = NULL; + if (b->ext_vars != JS_NULL) + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + else + ext_vars = NULL; + if (b->pc2line != JS_NULL) + pc2line = JS_VALUE_TO_PTR(b->pc2line); + else + pc2line = NULL; + + arg_count = b->arg_count; + + JS_PrintValueF(ctx, b->filename, JS_DUMP_NOQUOTE); + js_printf(ctx, ": function "); + JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE); + js_printf(ctx, ":\n"); + + if (b->arg_count && vars) { + js_printf(ctx, " args:"); + for(i = 0; i < b->arg_count; i++) { + js_printf(ctx, " "); + JS_PrintValue(ctx, vars->arr[i]); + } + js_printf(ctx, "\n"); + } + if (vars) { + js_printf(ctx, " locals:"); + for(i = 0; i < vars->size - b->arg_count; i++) { + js_printf(ctx, " "); + JS_PrintValue(ctx, vars->arr[i + b->arg_count]); + } + js_printf(ctx, "\n"); + } + if (ext_vars) { + js_printf(ctx, " refs:"); + for(i = 0; i < b->ext_vars_len; i++) { + int var_kind, var_idx, decl; + static const char *var_kind_str[] = { "arg", "var", "ref", "global" }; + js_printf(ctx, " "); + JS_PrintValue(ctx, ext_vars->arr[2 * i]); + decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]); + var_kind = decl >> 16; + var_idx = decl & 0xffff; + js_printf(ctx, " (%s:%d)", var_kind_str[var_kind], var_idx); + } + js_printf(ctx, "\n"); + } + + js_printf(ctx, " cpool_size: %d\n", cpool ? (int)cpool->size : 0); + js_printf(ctx, " stack_size: %d\n", b->stack_size); + js_printf(ctx, " opcodes:\n"); + tab = arr->buf; + len = arr->size; + pos = 0; + pc2line_pos = 0; + hoisted_code_len = 0; + if (pc2line) + hoisted_code_len = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size); + line_num = 1; + col_num = 1; + line_num1 = 0; + col_num1 = 0; + while (pos < len) { + /* extract the debug info */ + if (pc2line && pos >= hoisted_code_len) { + get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size, + &pc2line_pos, b->has_column); + if (line_num != line_num1 || col_num != col_num1) { + js_printf(ctx, " # %d", line_num); + if (b->has_column) + js_printf(ctx, ", %d", col_num); + js_printf(ctx, "\n"); + line_num1 = line_num; + col_num1 = col_num; + } + } + op = tab[pos]; + js_printf(ctx, "%5d: ", pos); + if (op >= OP_COUNT) { + js_printf(ctx, "invalid opcode (0x%02x)\n", op); + pos++; + continue; + } + oi = &opcode_info[op]; + size = oi->size; + if ((pos + size) > len) { + js_printf(ctx, "truncated opcode (0x%02x)\n", op); + break; + } + js_printf(ctx, "%s", oi->name); + pos++; + switch(oi->fmt) { + case OP_FMT_u8: + js_printf(ctx, " %u", (int)get_u8(tab + pos)); + break; + case OP_FMT_i8: + js_printf(ctx, " %d", (int)get_i8(tab + pos)); + break; + case OP_FMT_u16: + case OP_FMT_npop: + js_printf(ctx, " %u", (int)get_u16(tab + pos)); + break; + case OP_FMT_i16: + js_printf(ctx, " %d", (int)get_i16(tab + pos)); + break; + case OP_FMT_i32: + js_printf(ctx, " %d", (int)get_i32(tab + pos)); + break; + case OP_FMT_u32: + js_printf(ctx, " %u", (int)get_u32(tab + pos)); + break; + case OP_FMT_none_int: + js_printf(ctx, " %d", op - OP_push_0); + break; +#if 0 + case OP_FMT_npopx: + js_printf(ctx, " %d", op - OP_call0); + break; +#endif + case OP_FMT_label8: + addr = get_i8(tab + pos); + goto has_addr1; + case OP_FMT_label16: + addr = get_i16(tab + pos); + goto has_addr1; + case OP_FMT_label: + addr = get_u32(tab + pos); + goto has_addr1; + has_addr1: + js_printf(ctx, " %u", addr + pos); + break; + case OP_FMT_const8: + idx = get_u8(tab + pos); + goto has_pool_idx; + case OP_FMT_const16: + idx = get_u16(tab + pos); + goto has_pool_idx; + has_pool_idx: + js_printf(ctx, " %u: ", idx); + if (idx < cpool->size) { + JS_PrintValue(ctx, cpool->arr[idx]); + } + break; + case OP_FMT_none_loc: + idx = (op - OP_get_loc0) % 4; + goto has_loc; + case OP_FMT_loc8: + idx = get_u8(tab + pos); + goto has_loc; + case OP_FMT_loc: + idx = get_u16(tab + pos); + has_loc: + js_printf(ctx, " %d: ", idx); + idx += arg_count; + if (idx < vars->size) { + JS_PrintValue(ctx, vars->arr[idx]); + } + break; + case OP_FMT_none_arg: + idx = (op - OP_get_arg0) % 4; + goto has_arg; + case OP_FMT_arg: + idx = get_u16(tab + pos); + has_arg: + js_printf(ctx, " %d: ", idx); + if (idx < vars->size) { + JS_PrintValue(ctx, vars->arr[idx]); + } + break; +#if 0 + case OP_FMT_none_var_ref: + idx = (op - OP_get_var_ref0) % 4; + goto has_var_ref; +#endif + case OP_FMT_var_ref: + idx = get_u16(tab + pos); + // has_var_ref: + js_printf(ctx, " %d: ", idx); + if (2 * idx < ext_vars->size) { + JS_PrintValue(ctx, ext_vars->arr[2 * idx]); + } + break; + case OP_FMT_value: + js_printf(ctx, " "); + idx = get_u32(tab + pos); + JS_PrintValue(ctx, idx); + break; + default: + break; + } + js_printf(ctx, "\n"); + pos += oi->size - 1; + } +} +#endif /* DUMP_BYTECODE */ + +static void next_token(JSParseState *s); + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + JSContext *ctx = s->ctx; + switch(token->val) { + case TOK_NUMBER: + /* XXX: TODO */ + js_printf(ctx, "number: %d\n", (int)token->u.d); + break; + case TOK_IDENT: + { + js_printf(ctx, "ident: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } + break; + case TOK_STRING: + { + js_printf(ctx, "string: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } + break; + case TOK_REGEXP: + { + js_printf(ctx, "regexp: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } + break; + case TOK_EOF: + js_printf(ctx, "eof\n"); + break; + default: + if (s->token.val >= TOK_FIRST_KEYWORD) { + js_printf(ctx, "token: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } else if (s->token.val >= 128) { + js_printf(ctx, "token: %d\n", token->val); + } else { + js_printf(ctx, "token: '%c'\n", token->val); + } + break; + } +} + +/* return the zero based line and column number in the source. */ +static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len) +{ + int line_num, col_num, c; + size_t i; + + line_num = 0; + col_num = 0; + for(i = 0; i < len; i++) { + c = buf[i]; + if (c == '\n') { + line_num++; + col_num = 0; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + *pcol_num = col_num; + return line_num; +} + +static void __attribute__((format(printf, 2, 3), noreturn)) js_parse_error(JSParseState *s, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + js_vsnprintf(s->error_msg, sizeof(s->error_msg), fmt, ap); + va_end(ap); + longjmp(s->jmp_env, 1); +} + +static void js_parse_error_mem(JSParseState *s) +{ + return js_parse_error(s, "not enough memory"); +} + +static void js_parse_error_stack_overflow(JSParseState *s) +{ + return js_parse_error(s, "stack overflow"); +} + +static void js_parse_expect1(JSParseState *s, int ch) +{ + if (s->token.val != ch) + js_parse_error(s, "expecting '%c'", ch); +} + +static void js_parse_expect(JSParseState *s, int ch) +{ + js_parse_expect1(s, ch); + next_token(s); +} + +static void js_parse_expect_semi(JSParseState *s) +{ + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { + return; + } + js_parse_error(s, "expecting '%c'", ';'); + } + next_token(s); +} + +#define SKIP_HAS_ARGUMENTS (1 << 0) +#define SKIP_HAS_FUNC_NAME (1 << 1) +#define SKIP_HAS_SEMI (1 << 2) /* semicolon found inside the first level */ + +/* Skip parenthesis or blocks. The current token should be '(', '[' or + '{'. 'func_name' can be JS_NULL. */ +static int js_skip_parens(JSParseState *s, JSValue *pfunc_name) +{ + uint8_t state[128]; + int level, c, bits = 0; + + /* protect from underflow */ + level = 0; + state[level++] = 0; + for (;;) { + switch(s->token.val) { + case '(': + c = ')'; + goto add_level; + case '[': + c = ']'; + goto add_level; + case '{': + c = '}'; + add_level: + if (level >= sizeof(state)) { + js_parse_error(s, "too many nested blocks"); + } + state[level++] = c; + break; + case ')': + case ']': + case '}': + c = state[--level]; + if (s->token.val != c) + js_parse_error(s, "expecting '%c'", c); + break; + case TOK_EOF: + js_parse_error(s, "expecting '%c'", state[level - 1]); + case TOK_IDENT: + if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments)) + bits |= SKIP_HAS_ARGUMENTS; + if (pfunc_name && s->token.value == *pfunc_name) + bits |= SKIP_HAS_FUNC_NAME; + break; + case ';': + if (level == 2) + bits |= SKIP_HAS_SEMI; + break; + } + next_token(s); + if (level <= 1) + break; + } + return bits; +} + +/* skip an expression until ')' */ +static void js_skip_expr(JSParseState *s) +{ + for(;;) { + switch(s->token.val) { + case ')': + return; + case ';': + case TOK_EOF: + js_parse_error(s, "expecting '%c'", ')'); + case '(': + case '[': + case '{': + js_skip_parens(s, NULL); + break; + default: + next_token(s); + break; + } + } +} + +typedef struct JSParsePos { + BOOL got_lf : 8; + BOOL regexp_allowed : 8; + uint32_t source_pos; +} JSParsePos; + +/* return TRUE if a regexp literal is allowed after this token */ +static BOOL is_regexp_allowed(int tok) +{ + switch (tok) { + case TOK_NUMBER: + case TOK_STRING: + case TOK_REGEXP: + case TOK_DEC: + case TOK_INC: + case TOK_NULL: + case TOK_FALSE: + case TOK_TRUE: + case TOK_THIS: + case TOK_IF: + case TOK_WHILE: + case TOK_FOR: + case TOK_DO: + case TOK_CASE: + case TOK_CATCH: + case ')': + case ']': + case TOK_IDENT: + return FALSE; + default: + return TRUE; + } +} + +static void js_parse_get_pos(JSParseState *s, JSParsePos *sp) +{ + sp->source_pos = s->token.source_pos; + sp->got_lf = s->got_lf; + sp->regexp_allowed = is_regexp_allowed(s->token.val); +} + +static void js_parse_seek_token(JSParseState *s, const JSParsePos *sp) +{ + s->buf_pos = sp->source_pos; + s->got_lf = sp->got_lf; + /* the previous token value is only needed so that + is_regexp_allowed() returns the correct value */ + s->token.val = sp->regexp_allowed ? ' ' : ')'; + next_token(s); +} + +/* same as js_skip_parens but go back to the current token */ +static int js_parse_skip_parens_token(JSParseState *s) +{ + JSParsePos pos; + int bits; + + js_parse_get_pos(s, &pos); + bits = js_skip_parens(s, NULL); + js_parse_seek_token(s, &pos); + return bits; +} + +/* return the escape value or -1 */ +static int js_parse_escape(const uint8_t *buf, size_t *plen) +{ + int c; + const uint8_t *p = buf; + c = *p++; + switch(c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case '\'': + case '\"': + case '\\': + break; + case 'x': + { + int h0, h1; + + h0 = from_hex(*p++); + if (h0 < 0) + return -1; + h1 = from_hex(*p++); + if (h1 < 0) + return -1; + c = (h0 << 4) | h1; + } + break; + case 'u': + { + int h, i; + + if (*p == '{') { + p++; + c = 0; + for(;;) { + h = from_hex(*p++); + if (h < 0) + return -1; + c = (c << 4) | h; + if (c > 0x10FFFF) + return -1; + if (*p == '}') + break; + } + p++; + } else { + c = 0; + for(i = 0; i < 4; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + } + } + break; + case '0': + c -= '0'; + if (c != 0 || is_num(*p)) + return -1; + break; + default: + return -2; + } + *plen = p - buf; + return c; +} + +static JSValue js_parse_string(JSParseState *s, uint32_t *ppos, int sep) +{ + JSContext *ctx = s->ctx; + JSValue res; + const uint8_t *buf; + uint32_t pos; + uint32_t c; + size_t escape_len = 0; /* avoid warning */ + StringBuffer b_s, *b = &b_s; + + if (string_buffer_push(ctx, b, 16)) + js_parse_error_mem(s); + buf = s->source_buf; + /* string */ + pos = *ppos; + for(;;) { + c = buf[pos]; + if (c == '\0' || c == '\n' || c == '\r') { + js_parse_error(s, "unexpected end of string"); + } + pos++; + if (c == sep) + break; + if (c == '\\') { + if (buf[pos] == '\n') { + /* ignore escaped newline sequence */ + pos++; + continue; + } + c = js_parse_escape(buf + pos, &escape_len); + if (c == -1) { + js_parse_error(s, "invalid escape sequence"); + } else if (c == -2) { + /* ignore invalid escapes */ + continue; + } + pos += escape_len; + } else if (c >= 0x80) { + size_t clen; + pos--; + c = unicode_from_utf8(buf + pos, UTF8_CHAR_LEN_MAX, &clen); + pos += clen; + if (c == -1) { + js_parse_error(s, "invalid UTF-8 sequence"); + } + } + if (string_buffer_putc(ctx, b, c)) + break; + buf = s->source_buf; /* may be reallocated */ + } + *ppos = pos; + res = string_buffer_pop(ctx, b); + if (JS_IsException(res)) + js_parse_error_mem(s); + return res; +} + +static void js_parse_ident(JSParseState *s, JSToken *token, + uint32_t *ppos, int c) +{ + JSContext *ctx = s->ctx; + uint32_t pos; + JSValue val, val2; + JSGCRef val2_ref; + const uint8_t *buf; + StringBuffer b_s, *b = &b_s; + + if (string_buffer_push(ctx, b, 16)) + js_parse_error_mem(s); + string_buffer_putc(ctx, b, c); /* no allocation */ + buf = s->source_buf; + pos = *ppos; + while (pos < s->buf_len) { + c = buf[pos]; + if (!is_ident_next(c)) + break; + pos++; + if (string_buffer_putc(ctx, b, c)) + break; + buf = s->source_buf; /* may be reallocated */ + } + /* convert to token if necessary */ + token->val = TOK_IDENT; + val2 = string_buffer_pop(ctx, b); + JS_PUSH_VALUE(ctx, val2); + val = JS_MakeUniqueString(ctx, val2); + JS_POP_VALUE(ctx, val2); + if (JS_IsException(val)) + js_parse_error_mem(s); + if (val != val2) + js_free(ctx, JS_VALUE_TO_PTR(val2)); + token->value = val; + if (JS_IsPtr(val)) { + const JSWord *atom_start, *atom_last, *ptr; + atom_start = ctx->atom_table; + atom_last = atom_start + JS_ATOM_yield; + ptr = JS_VALUE_TO_PTR(val); + if (ptr >= atom_start && ptr <= atom_last) { + token->val = TOK_NULL + (ptr - atom_start); + } + } + *ppos = pos; +} + +static void js_parse_regexp_token(JSParseState *s, uint32_t *ppos) +{ + JSContext *ctx = s->ctx; + uint32_t pos; + uint32_t c; + BOOL in_class; + size_t clen; + int re_flags, end_pos, start_pos; + JSString *p; + + in_class = FALSE; + pos = *ppos; + start_pos = pos; + for(;;) { + c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen); + if (c == -1) + js_parse_error(s, "invalid UTF-8 sequence"); + pos += clen; + if (c == '\0' || c == '\n' || c == '\r') { + goto invalid_char; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = TRUE; + } else if (c == ']') { + in_class = FALSE; + } else if (c == '\\') { + c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen); + if (c == -1) + js_parse_error(s, "invalid UTF-8 sequence"); + if (c == '\0' || c == '\n' || c == '\r') { + invalid_char: + js_parse_error(s, "unexpected line terminator in regexp"); + } + pos += clen; + } + } + end_pos = pos - 1; + + clen = js_parse_regexp_flags(&re_flags, s->source_buf + pos); + pos += clen; + if (is_ident_next(s->source_buf[pos])) + js_parse_error(s, "invalid regular expression flags"); + + /* XXX: single char string is not optimized */ + p = js_alloc_string(ctx, end_pos - start_pos); + if (!p) + js_parse_error_mem(s); + p->is_ascii = is_ascii_string((char *)(s->source_buf + start_pos), end_pos - start_pos); + memcpy(p->buf, s->source_buf + start_pos, end_pos - start_pos); + + *ppos = pos; + s->token.val = TOK_REGEXP; + s->token.value = JS_VALUE_FROM_PTR(p); + s->token.u.regexp.re_flags = re_flags; + s->token.u.regexp.re_end_pos = end_pos; +} + +static void next_token(JSParseState *s) +{ + uint32_t pos; + const uint8_t *p; + int c; + + pos = s->buf_pos; + s->got_lf = FALSE; + s->token.value = JS_NULL; + p = s->source_buf + s->buf_pos; + redo: + s->token.source_pos = p - s->source_buf; + c = *p; + switch(c) { + case 0: + s->token.val = TOK_EOF; + break; + case '\"': + case '\'': + p++; + pos = p - s->source_buf; + s->token.value = js_parse_string(s, &pos, c); + s->token.val = TOK_STRING; + p = s->source_buf + pos; + break; + case '\n': + s->got_lf = TRUE; + p++; + goto redo; + case ' ': + case '\t': + case '\f': + case '\v': + case '\r': + p++; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0') + js_parse_error(s, "unexpected end of comment"); + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + p++; + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + for(;;) { + if (*p == '\0' || *p == '\n') + break; + p++; + } + goto redo; + } else if (is_regexp_allowed(s->token.val)) { + /* Note: we recognize regexps in the lexer. It does not + handle all the cases e.g. "({x:1} / 2)" or "a.void / 2" but + is consistent when we tokenize the input without + parsing it. */ + p++; + pos = p - s->source_buf; + js_parse_regexp_token(s, &pos); + p = s->source_buf + pos; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': + case '$': + p++; + pos = p - s->source_buf; + js_parse_ident(s, &s->token, &pos, c); + p = s->source_buf + pos; + break; + case '.': + if (is_digit(p[1])) + goto parse_number; + else + goto def_token; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1])) + goto invalid_number; + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + { + double d; + JSByteArray *tmp_arr; + pos = p - s->source_buf; + tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem)); + if (!tmp_arr) + js_parse_error_mem(s); + p = s->source_buf + pos; + d = js_atod((const char *)p, (const char **)&p, 0, + JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_ACCEPT_UNDERSCORES, + (JSATODTempMem *)tmp_arr->buf); + js_free(s->ctx, tmp_arr); + if (isnan(d)) { + invalid_number: + js_parse_error(s, "invalid number literal"); + } + s->token.val = TOK_NUMBER; + s->token.u.d = d; + } + break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else { + goto def_token; + } + break; + case '>': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_GTE; + } else if (p[1] == '>') { + if (p[2] == '>') { + if (p[3] == '=') { + p += 4; + s->token.val = TOK_SHR_ASSIGN; + } else { + p += 3; + s->token.val = TOK_SHR; + } + } else if (p[2] == '=') { + p += 3; + s->token.val = TOK_SAR_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SAR; + } + } else { + goto def_token; + } + break; + case '=': + if (p[1] == '=') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_STRICT_EQ; + } else { + p += 2; + s->token.val = TOK_EQ; + } + } else { + goto def_token; + } + break; + case '!': + if (p[1] == '=') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_STRICT_NEQ; + } else { + p += 2; + s->token.val = TOK_NEQ; + } + } else { + goto def_token; + } + break; + case '&': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_AND_ASSIGN; + } else if (p[1] == '&') { + p += 2; + s->token.val = TOK_LAND; + } else { + goto def_token; + } + break; + case '^': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_XOR_ASSIGN; + } else { + goto def_token; + } + break; + case '|': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_OR_ASSIGN; + } else if (p[1] == '|') { + p += 2; + s->token.val = TOK_LOR; + } else { + goto def_token; + } + break; + default: + if (c >= 128) { + js_parse_error(s, "unexpected character"); + } + def_token: + s->token.val = c; + p++; + break; + } + s->buf_pos = p - s->source_buf; +#if defined(DUMP_TOKEN) + dump_token(s, &s->token); +#endif +} + +/* test if the current token is a label. XXX: we assume there is no + space between the identifier and the ':' to avoid having to push + back a token */ +static BOOL is_label(JSParseState *s) +{ + return (s->token.val == TOK_IDENT && s->source_buf[s->buf_pos] == ':'); +} + +static inline uint8_t *get_byte_code(JSParseState *s) +{ + JSByteArray *arr; + arr = JS_VALUE_TO_PTR(s->byte_code); + return arr->buf; +} + +static void emit_claim_size(JSParseState *s, int n) +{ + JSValue val; + val = js_resize_byte_array(s->ctx, s->byte_code, s->byte_code_len + n); + if (JS_IsException(val)) + js_parse_error_mem(s); + s->byte_code = val; +} + +static void emit_u8(JSParseState *s, uint8_t val) +{ + JSByteArray *arr; + emit_claim_size(s, 1); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[s->byte_code_len++] = val; +} + +static void emit_u16(JSParseState *s, uint16_t val) +{ + JSByteArray *arr; + emit_claim_size(s, 2); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u16(arr->buf + s->byte_code_len, val); + s->byte_code_len += 2; +} + +static void emit_u32(JSParseState *s, uint32_t val) +{ + JSByteArray *arr; + emit_claim_size(s, 4); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + s->byte_code_len, val); + s->byte_code_len += 4; +} + +/* precondition: 1 <= n <= 25. */ +static void pc2line_put_bits_short(JSParseState *s, int n, uint32_t bits) +{ + JSFunctionBytecode *b; + JSValue val1; + JSByteArray *arr; + uint32_t index, pos; + unsigned int val; + int shift; + uint8_t *p; + + index = s->pc2line_bit_len; + pos = index >> 3; + + /* resize the array if needed */ + b = JS_VALUE_TO_PTR(s->cur_func); + val1 = js_resize_byte_array(s->ctx, b->pc2line, pos + 4); + if (JS_IsException(val1)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(s->cur_func); + b->pc2line = val1; + + arr = JS_VALUE_TO_PTR(val1); + p = arr->buf + pos; + val = get_be32(p); + shift = (32 - (index & 7) - n); + val &= ~(((1U << n) - 1) << shift); /* reset the bits */ + val |= bits << shift; + put_be32(p, val); + s->pc2line_bit_len = index + n; +} + +/* precondition: 1 <= n <= 32 */ +static void pc2line_put_bits(JSParseState *s, int n, uint32_t bits) +{ + int n_max = 25; + if (unlikely(n > n_max)) { + pc2line_put_bits_short(s, n - n_max, bits >> n_max); + bits &= (1 << n_max) - 1; + n = n_max; + } + pc2line_put_bits_short(s, n, bits); +} + +/* 0 <= v < 2^32-1 */ +static void put_ugolomb(JSParseState *s, uint32_t v) +{ + int n; + // printf("put_ugolomb: %u\n", v); + v++; + n = 32 - clz32(v); + if (n > 1) + pc2line_put_bits(s, n - 1, 0); + pc2line_put_bits(s, n, v); +} + +/* v != -2^31 */ +static void put_sgolomb(JSParseState *s, int32_t v1) +{ + uint32_t v = v1; + put_ugolomb(s, (2 * v) ^ -(v >> 31)); +} + +//#define DUMP_PC2LINE_STATS + +#ifdef DUMP_PC2LINE_STATS +static int pc2line_freq[256]; +static int pc2line_freq_tot; +#endif + +/* return the difference between the line numbers from 'pos1' to + 'pos2'. If the difference is zero, '*pcol_num' contains the + difference between the column numbers. Otherwise it contains the + zero based absolute column number. +*/ +static int get_line_col_delta(int *pcol_num, const uint8_t *buf, + int pos1, int pos2) +{ + int line_num, col_num, c, i; + line_num = 0; + col_num = 0; + if (pos2 >= pos1) { + line_num = get_line_col(&col_num, buf + pos1, pos2 - pos1); + } else { + line_num = get_line_col(&col_num, buf + pos2, pos1 - pos2); + line_num = -line_num; + col_num = -col_num; + if (line_num != 0) { + /* find the absolute column position */ + col_num = 0; + for(i = pos2 - 1; i >= 0; i--) { + c = buf[i]; + if (c == '\n') { + break; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + } + } + *pcol_num = col_num; + return line_num; +} + +static void emit_pc2line(JSParseState *s, JSSourcePos pos) +{ + int line_delta, col_delta; + + line_delta = get_line_col_delta(&col_delta, s->source_buf, + s->pc2line_source_pos, pos); + put_sgolomb(s, line_delta); + if (s->has_column) { + if (line_delta == 0) { +#ifdef DUMP_PC2LINE_STATS + pc2line_freq[min_int(max_int(col_delta + 128, 0), 255)]++; + pc2line_freq_tot++; +#endif + put_sgolomb(s, col_delta); + } else { + put_ugolomb(s, col_delta); + } + } + s->pc2line_source_pos = pos; +} + +#ifdef DUMP_PC2LINE_STATS +void dump_pc2line(void) +{ + int i; + for(i = 0; i < 256; i++) { + if (pc2line_freq[i] != 0) { + printf("%d: %d %0.2f\n", + i - 128, pc2line_freq[i], + -log2((double)pc2line_freq[i] / pc2line_freq_tot)); + } + } +} +#endif + +/* warning: pc2line info must be associated to each generated opcode */ +static void emit_op_pos(JSParseState *s, uint8_t op, JSSourcePos source_pos) +{ + s->last_opcode_pos = s->byte_code_len; + s->last_pc2line_pos = s->pc2line_bit_len; + s->last_pc2line_source_pos = s->pc2line_source_pos; + + emit_pc2line(s, source_pos); + emit_u8(s, op); +} + +static void emit_op(JSParseState *s, uint8_t op) +{ + emit_op_pos(s, op, s->pc2line_source_pos); +} + +static void emit_op_param(JSParseState *s, uint8_t op, uint32_t param, + JSSourcePos source_pos) +{ + const JSOpCode *oi; + + emit_op_pos(s, op, source_pos); + oi = &opcode_info[op]; + switch(oi->fmt) { + case OP_FMT_none: + break; + case OP_FMT_npop: + emit_u16(s, param); + break; + default: + assert(0); + } +} + +/* insert 'n' bytes at position pos */ +static void emit_insert(JSParseState *s, int pos, int n) +{ + JSByteArray *arr; + emit_claim_size(s, n); + arr = JS_VALUE_TO_PTR(s->byte_code); + memmove(arr->buf + pos + n, arr->buf + pos, s->byte_code_len - pos); + s->byte_code_len += n; +} + +static inline int get_prev_opcode(JSParseState *s) +{ + if (s->last_opcode_pos < 0) { + return OP_invalid; + } else { + uint8_t *byte_code = get_byte_code(s); + return byte_code[s->last_opcode_pos]; + } +} + +static BOOL js_is_live_code(JSParseState *s) { + switch (get_prev_opcode(s)) { + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_goto: + case OP_ret: + return FALSE; + default: + return TRUE; + } +} + +static void remove_last_op(JSParseState *s) +{ + s->byte_code_len = s->last_opcode_pos; + s->pc2line_bit_len = s->last_pc2line_pos; + s->pc2line_source_pos = s->last_pc2line_source_pos; + s->last_opcode_pos = -1; +} + +static void emit_push_short_int(JSParseState *s, int val) +{ + if (val >= -1 && val <= 7) { + emit_op(s, OP_push_0 + val); + } else if (val == (int8_t)val) { + emit_op(s, OP_push_i8); + emit_u8(s, val); + } else if (val == (int16_t)val) { + emit_op(s, OP_push_i16); + emit_u16(s, val); + } else { + emit_op(s, OP_push_value); + emit_u32(s, JS_NewShortInt(val)); + } +} + +static void emit_var(JSParseState *s, int opcode, int var_idx, + JSSourcePos source_pos) +{ + switch(opcode) { + case OP_get_loc: + if (var_idx < 4) { + emit_op_pos(s, OP_get_loc0 + var_idx, source_pos); + return; + } else if (var_idx < 256) { + emit_op_pos(s, OP_get_loc8, source_pos); + emit_u8(s, var_idx); + return; + } + break; + case OP_put_loc: + if (var_idx < 4) { + emit_op_pos(s, OP_put_loc0 + var_idx, source_pos); + return; + } else if (var_idx < 256) { + emit_op_pos(s, OP_put_loc8, source_pos); + emit_u8(s, var_idx); + return; + } + break; + case OP_get_arg: + if (var_idx < 4) { + emit_op_pos(s, OP_get_arg0 + var_idx, source_pos); + return; + } + break; + case OP_put_arg: + if (var_idx < 4) { + emit_op_pos(s, OP_put_arg0 + var_idx, source_pos); + return; + } + break; + } + emit_op_pos(s, opcode, source_pos); + emit_u16(s, var_idx); +} + + +typedef enum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_METHOD, +} JSParseFunctionEnum; + +static void js_parse_function_decl(JSParseState *s, + JSParseFunctionEnum func_type, JSValue func_name); + +/* labels are short integers so they can be used as JSValue. -1 is not + a valid label. */ +#define LABEL_RESOLVED_FLAG (1 << 29) +#define LABEL_OFFSET_MASK ((1 << 29) - 1) + +#define LABEL_NONE JS_NewShortInt(-1) + +static BOOL label_is_none(JSValue label) +{ + return JS_VALUE_GET_INT(label) < 0; +} + +static JSValue new_label(JSParseState *s) +{ + return JS_NewShortInt(LABEL_OFFSET_MASK); +} + +static void emit_label_pos(JSParseState *s, JSValue *plabel, int pos) +{ + int label; + JSByteArray *arr; + int next; + + label = JS_VALUE_GET_INT(*plabel); + assert(!(label & LABEL_RESOLVED_FLAG)); + arr = JS_VALUE_TO_PTR(s->byte_code); + while (label != LABEL_OFFSET_MASK) { + next = get_u32(arr->buf + label); + put_u32(arr->buf + label, pos - label); + label = next; + } + *plabel = JS_NewShortInt(pos | LABEL_RESOLVED_FLAG); +} + +static void emit_label(JSParseState *s, JSValue *plabel) +{ + emit_label_pos(s, plabel, s->byte_code_len); + /* prevent get_lvalue from using the last expression as an + lvalue. */ + s->last_opcode_pos = -1; +} + +static void emit_goto(JSParseState *s, int opcode, JSValue *plabel) +{ + int label; + /* XXX: generate smaller gotos when possible */ + emit_op(s, opcode); + label = JS_VALUE_GET_INT(*plabel); + if (label & LABEL_RESOLVED_FLAG) { + emit_u32(s, (label & LABEL_OFFSET_MASK) - s->byte_code_len); + } else { + emit_u32(s, label); + *plabel = JS_NewShortInt(s->byte_code_len - 4); + } +} + +/* return the constant pool index. 'val' is not duplicated. */ +static int cpool_add(JSParseState *s, JSValue val) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + JSValue new_cpool; + JSGCRef val_ref; + + b = JS_VALUE_TO_PTR(s->cur_func); + arr = JS_VALUE_TO_PTR(b->cpool); + /* check if the value is already present */ + for(i = 0; i < s->cpool_len; i++) { + if (arr->arr[i] == val) + return i; + } + + if (s->cpool_len > 65535) + js_parse_error(s, "too many constants"); + JS_PUSH_VALUE(s->ctx, val); + new_cpool = js_resize_value_array(s->ctx, b->cpool, max_int(s->cpool_len + 1, 4)); + JS_POP_VALUE(s->ctx, val); + if (JS_IsException(new_cpool)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(s->cur_func); + b->cpool = new_cpool; + arr = JS_VALUE_TO_PTR(b->cpool); + arr->arr[s->cpool_len++] = val; + return s->cpool_len - 1; +} + +static void js_emit_push_const(JSParseState *s, JSValue val) +{ + int idx; + + if (JS_IsPtr(val) +#ifdef JS_USE_SHORT_FLOAT + || JS_IsShortFloat(val) +#endif + ) { + /* We use a constant pool to avoid scanning the bytecode + during the GC. XXX: is it a good choice ? */ + idx = cpool_add(s, val); + emit_op(s, OP_push_const); + emit_u16(s, idx); + } else { + /* no GC mark */ + emit_op(s, OP_push_value); + emit_u32(s, val); + } +} + +/* return the local variable index or -1 if not found */ +static int find_func_var(JSContext *ctx, JSValue func, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + + b = JS_VALUE_TO_PTR(func); + if (b->vars == JS_NULL) + return -1; + arr = JS_VALUE_TO_PTR(b->vars); + for(i = 0; i < arr->size; i++) { + if (arr->arr[i] == name) + return i; + } + return -1; +} + +static int find_var(JSParseState *s, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + + b = JS_VALUE_TO_PTR(s->cur_func); + arr = JS_VALUE_TO_PTR(b->vars); + for(i = 0; i < s->local_vars_len; i++) { + if (arr->arr[i] == name) + return i; + } + return -1; +} + +static JSValue get_ext_var_name(JSParseState *s, int var_idx) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + + b = JS_VALUE_TO_PTR(s->cur_func); + arr = JS_VALUE_TO_PTR(b->ext_vars); + return arr->arr[2 * var_idx]; +} + +static int find_func_ext_var(JSParseState *s, JSValue func, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + + b = JS_VALUE_TO_PTR(func); + arr = JS_VALUE_TO_PTR(b->ext_vars); + for(i = 0; i < b->ext_vars_len; i++) { + if (arr->arr[2 * i] == name) + return i; + } + return -1; +} + +/* return the external variable index or -1 if not found */ +static int find_ext_var(JSParseState *s, JSValue name) +{ + return find_func_ext_var(s, s->cur_func, name); +} + +/* return the external variable index */ +static int add_func_ext_var(JSParseState *s, JSValue func, JSValue name, int decl) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + JSValue new_ext_vars; + JSGCRef name_ref, func_ref; + + b = JS_VALUE_TO_PTR(func); + if (b->ext_vars_len >= JS_MAX_LOCAL_VARS) + js_parse_error(s, "too many variable references"); + JS_PUSH_VALUE(s->ctx, func); + JS_PUSH_VALUE(s->ctx, name); + new_ext_vars = js_resize_value_array(s->ctx, b->ext_vars, max_int(b->ext_vars_len + 1, 2) * 2); + JS_POP_VALUE(s->ctx, name); + JS_POP_VALUE(s->ctx, func); + if (JS_IsException(new_ext_vars)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(func); + b->ext_vars = new_ext_vars; + arr = JS_VALUE_TO_PTR(b->ext_vars); + arr->arr[2 * b->ext_vars_len] = name; + arr->arr[2 * b->ext_vars_len + 1] = JS_NewShortInt(decl); + b->ext_vars_len++; + return b->ext_vars_len - 1; +} + +/* return the external variable index */ +static int add_ext_var(JSParseState *s, JSValue name, int decl) +{ + return add_func_ext_var(s, s->cur_func, name, decl); +} + +/* return the local variable index */ +static int add_var(JSParseState *s, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + JSValue new_vars; + JSGCRef name_ref; + + b = JS_VALUE_TO_PTR(s->cur_func); + if (s->local_vars_len >= JS_MAX_LOCAL_VARS) + js_parse_error(s, "too many local variables"); + JS_PUSH_VALUE(s->ctx, name); + new_vars = js_resize_value_array(s->ctx, b->vars, max_int(s->local_vars_len + 1, 4)); + JS_POP_VALUE(s->ctx, name); + if (JS_IsException(new_vars)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(s->cur_func); + b->vars = new_vars; + arr = JS_VALUE_TO_PTR(b->vars); + arr->arr[s->local_vars_len++] = name; + return s->local_vars_len - 1; +} + +static void get_lvalue(JSParseState *s, int *popcode, + int *pvar_idx, JSSourcePos *psource_pos, BOOL keep) +{ + int opcode, var_idx; + JSSourcePos source_pos; + + /* we check the last opcode to get the lvalue type */ + opcode = get_prev_opcode(s); + switch(opcode) { + case OP_get_loc0: + case OP_get_loc1: + case OP_get_loc2: + case OP_get_loc3: + var_idx = opcode - OP_get_loc0; + opcode = OP_get_loc; + break; + case OP_get_arg0: + case OP_get_arg1: + case OP_get_arg2: + case OP_get_arg3: + var_idx = opcode - OP_get_arg0; + opcode = OP_get_arg; + break; + case OP_get_loc8: + var_idx = get_u8(get_byte_code(s) + s->last_opcode_pos + 1); + opcode = OP_get_loc; + break; + case OP_get_loc: + case OP_get_arg: + case OP_get_var_ref: + case OP_get_field: + var_idx = get_u16(get_byte_code(s) + s->last_opcode_pos + 1); + break; + case OP_get_array_el: + case OP_get_length: + var_idx = -1; + break; + default: + js_parse_error(s, "invalid lvalue"); + } + source_pos = s->pc2line_source_pos; + + /* remove the last opcode */ + remove_last_op(s); + + if (keep) { + /* get the value but keep the object/fields on the stack */ + switch(opcode) { + case OP_get_loc: + case OP_get_arg: + case OP_get_var_ref: + emit_var(s, opcode, var_idx, source_pos); + break; + case OP_get_field: + emit_op_pos(s, OP_get_field2, source_pos); + emit_u16(s, var_idx); + break; + case OP_get_length: + emit_op_pos(s, OP_get_length2, source_pos); + break; + case OP_get_array_el: + emit_op(s, OP_dup2); + emit_op_pos(s, OP_get_array_el, source_pos); /* XXX: add OP_get_array_el3 but need to modify tail call */ + break; + default: + abort(); + } + } + + *popcode = opcode; + *pvar_idx = var_idx; + *psource_pos = source_pos; +} + +typedef enum { + PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ + PUT_LVALUE_NOKEEP_TOP, /* [depth] v -> */ + PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ + PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ +} PutLValueEnum; + +static void put_lvalue(JSParseState *s, int opcode, + int var_idx, JSSourcePos source_pos, + PutLValueEnum special) +{ + switch(opcode) { + case OP_get_loc: + case OP_get_arg: + case OP_get_var_ref: + if (special == PUT_LVALUE_KEEP_TOP) + emit_op(s, OP_dup); + if (opcode == OP_get_var_ref && s->is_repl) + opcode = OP_put_var_ref_nocheck; /* an assignment defines the variable in the REPL */ + else + opcode++; + emit_var(s, opcode, var_idx, source_pos); + break; + case OP_get_field: + case OP_get_length: + switch(special) { + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert2); /* obj a -> a obj a */ + break; + case PUT_LVALUE_NOKEEP_TOP: + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_swap); /* a obj -> obj a */ + break; + default: + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm3); /* obj a b -> a obj b */ + break; + } + emit_op_pos(s, OP_put_field, source_pos); + if (opcode == OP_get_length) { + emit_u16(s, cpool_add(s, js_get_atom(s->ctx, JS_ATOM_length))); + } else { + emit_u16(s, var_idx); + } + break; + case OP_get_array_el: + switch(special) { + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert3); /* obj prop a -> a obj prop a */ + break; + case PUT_LVALUE_NOKEEP_TOP: + break; + case PUT_LVALUE_NOKEEP_BOTTOM: /* a obj prop -> obj prop a */ + emit_op(s, OP_rot3l); /* obj prop a b -> a obj prop b */ + break; + default: + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm4); /* obj prop a b -> a obj prop b */ + break; + } + emit_op_pos(s, OP_put_array_el, source_pos); + break; + default: + abort(); + } +} + +enum { + PARSE_PROP_FIELD, + PARSE_PROP_GET, + PARSE_PROP_SET, + PARSE_PROP_METHOD, +}; + +static int js_parse_property_name(JSParseState *s, JSValue *pname) +{ + JSContext *ctx = s->ctx; + JSValue name; + JSGCRef name_ref; + int prop_type; + + prop_type = PARSE_PROP_FIELD; + + if (s->token.val == TOK_IDENT) { + int is_set; + if (s->token.value == js_get_atom(ctx, JS_ATOM_get)) + is_set = 0; + else if (s->token.value == js_get_atom(ctx, JS_ATOM_set)) + is_set = 1; + else + is_set = -1; + if (is_set >= 0) { + next_token(s); + if (s->token.val == ':' || s->token.val == ',' || + s->token.val == '}' || s->token.val == '(') { + /* not a get set */ + name = js_get_atom(ctx, is_set ? JS_ATOM_set : JS_ATOM_get); + goto done; + } + prop_type = PARSE_PROP_GET + is_set; + } + } + + if (s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD) { + name = s->token.value; + } else if (s->token.val == TOK_STRING) { + name = s->token.value; + } else if (s->token.val == TOK_NUMBER) { + name = JS_NewFloat64(s->ctx, s->token.u.d); + if (JS_IsException(name)) + js_parse_error_mem(s); + } else { + js_parse_error(s, "invalid property name"); + } + name = JS_ToPropertyKey(s->ctx, name); + if (JS_IsException(name)) + js_parse_error_mem(s); + JS_PUSH_VALUE(ctx, name); + next_token(s); + JS_POP_VALUE(ctx, name); + done: + if (prop_type == PARSE_PROP_FIELD && s->token.val == '(') + prop_type = PARSE_PROP_METHOD; + *pname = name; + return prop_type; +} + +/* recursion free parser definitions */ + +#define PF_NO_IN (1 << 0) /* the 'in' operator is not accepted*/ +#define PF_DROP (1 << 1) /* drop result */ +#define PF_ACCEPT_LPAREN (1 << 2) /* js_parse_postfix_expr only */ +#define PF_LEVEL_SHIFT 4 /* optional level parameter */ +#define PF_LEVEL_MASK (0xf << PF_LEVEL_SHIFT) + +typedef enum { + PARSE_FUNC_js_parse_expr_comma, + PARSE_FUNC_js_parse_assign_expr, + PARSE_FUNC_js_parse_cond_expr, + PARSE_FUNC_js_parse_logical_and_or, + PARSE_FUNC_js_parse_expr_binary, + PARSE_FUNC_js_parse_unary, + PARSE_FUNC_js_parse_postfix_expr, + PARSE_FUNC_js_parse_statement, + PARSE_FUNC_js_parse_block, + PARSE_FUNC_js_parse_json_value, + PARSE_FUNC_re_parse_alternative, + PARSE_FUNC_re_parse_disjunction, +} ParseExprFuncEnum; + +typedef int JSParseFunc(JSParseState *s, int state, int param); + +#define PARSE_STATE_INIT 0xfe +#define PARSE_STATE_RET 0xff + +/* may trigger a gc */ +static JSValue parse_stack_alloc(JSParseState *s, JSValue val) +{ + JSGCRef val_ref; + + JS_PUSH_VALUE(s->ctx, val); + if (JS_StackCheck(s->ctx, 1)) + js_parse_error_stack_overflow(s); + JS_POP_VALUE(s->ctx, val); + return val; +} + +/* WARNING: 'val' may be modified after this val if it is a pointer */ +static void js_parse_push_val(JSParseState *s, JSValue val) +{ + JSContext *ctx = s->ctx; + if (unlikely(ctx->sp <= ctx->stack_bottom)) { + val = parse_stack_alloc(s, val); + } + *--(ctx->sp) = val; +} + +/* update the stack bottom when there is a large stack space */ +static JSValue js_parse_pop_val(JSParseState *s) +{ + JSContext *ctx = s->ctx; + JSValue val; + val = *(ctx->sp)++; + if (unlikely(ctx->sp - JS_STACK_SLACK > ctx->stack_bottom)) + ctx->stack_bottom = ctx->sp - JS_STACK_SLACK; + return val; +} + +#define PARSE_PUSH_VAL(s, v) js_parse_push_val(s, v) +#define PARSE_POP_VAL(s, v) v = js_parse_pop_val(s) + +#define PARSE_PUSH_INT(s, v) js_parse_push_val(s, JS_NewShortInt(v)) +#define PARSE_POP_INT(s, v) v = JS_VALUE_GET_INT(js_parse_pop_val(s)) + +#define PARSE_START1() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + } + +#define PARSE_START2() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + } + +#define PARSE_START3() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + case 2: goto parse_state2;\ + } + +#define PARSE_START7() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + case 2: goto parse_state2;\ + case 3: goto parse_state3; \ + case 4: goto parse_state4;\ + case 5: goto parse_state5;\ + case 6: goto parse_state6;\ + } + +#define PARSE_START12() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + case 2: goto parse_state2;\ + case 3: goto parse_state3; \ + case 4: goto parse_state4;\ + case 5: goto parse_state5;\ + case 6: goto parse_state6;\ + case 7: goto parse_state7; \ + case 8: goto parse_state8;\ + case 9: goto parse_state9;\ + case 10: goto parse_state10;\ + case 11: goto parse_state11;\ + } + +/* WARNING: local variables are not preserved across PARSE_CALL(). So + they must be explicitly saved and restored */ +#define PARSE_CALL(s, cur_state, func, param) return (cur_state | (PARSE_FUNC_ ## func << 8) | ((param) << 16)); parse_state ## cur_state : ; + +/* preserve var1, ... across the call */ +#define PARSE_CALL_SAVE1(s, cur_state, func, param, var1) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE2(s, cur_state, func, param, var1, var2) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE3(s, cur_state, func, param, var1, var2, var3) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE4(s, cur_state, func, param, var1, var2, var3, var4) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_PUSH_INT(s, var4); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var4); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE5(s, cur_state, func, param, var1, var2, var3, var4, var5) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_PUSH_INT(s, var4); \ + PARSE_PUSH_INT(s, var5); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var5); \ + PARSE_POP_INT(s, var4); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE6(s, cur_state, func, param, var1, var2, var3, var4, var5, var6) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_PUSH_INT(s, var4); \ + PARSE_PUSH_INT(s, var5); \ + PARSE_PUSH_INT(s, var6); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var6); \ + PARSE_POP_INT(s, var5); \ + PARSE_POP_INT(s, var4); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +static JSParseFunc *parse_func_table[]; + +static void js_parse_call(JSParseState *s, ParseExprFuncEnum func_idx, + int param) +{ + JSContext *ctx = s->ctx; + int ret, state; + JSValue *stack_top; + + stack_top = ctx->sp; + state = PARSE_STATE_INIT; + for(;;) { + ret = parse_func_table[func_idx](s, state, param); + state = ret & 0xff; + if (state == PARSE_STATE_RET) { + /* the function terminated: go back to the calling + function if any */ + if (ctx->sp == stack_top) + break; + PARSE_POP_INT(s, ret); + state = ret & 0xff; + func_idx = (ret >> 8) & 0xff; + param = -1; /* the parameter is not saved */ + } else { + /* push the call position and call another function */ + PARSE_PUSH_INT(s, state | (func_idx << 8)); + state = PARSE_STATE_INIT; + func_idx = (ret >> 8) & 0xff; + param = (ret >> 16); + } + } +} + +static BOOL may_drop_result(JSParseState *s, int parse_flags) +{ + return ((parse_flags & PF_DROP) && + (s->token.val == ';' || s->token.val == ')' || + s->token.val == ',')); +} + +static void js_emit_push_number(JSParseState *s, double d) +{ + JSValue val; + + val = JS_NewFloat64(s->ctx, d); + if (JS_IsException(val)) + js_parse_error_mem(s); + if (JS_IsInt(val)) { + emit_push_short_int(s, JS_VALUE_GET_INT(val)); + } else { + js_emit_push_const(s, val); + } +} + +static int js_parse_postfix_expr(JSParseState *s, int state, int parse_flags) +{ + BOOL is_new = FALSE; + + PARSE_START7(); + switch(s->token.val) { + case TOK_NUMBER: + js_emit_push_number(s, s->token.u.d); + next_token(s); + break; + case TOK_STRING: + { + js_emit_push_const(s, s->token.value); + next_token(s); + } + break; + case TOK_REGEXP: + { + uint32_t saved_buf_pos, saved_buf_len; + uint32_t saved_byte_code_len; + JSValue byte_code; + JSFunctionBytecode *b; + + js_emit_push_const(s, s->token.value); /* regexp source */ + + saved_buf_pos = s->buf_pos; + saved_buf_len = s->buf_len; + /* save the current bytecode back to the function */ + b = JS_VALUE_TO_PTR(s->cur_func); + b->byte_code = s->byte_code; + saved_byte_code_len = s->byte_code_len; + + /* modify the parser to parse the regexp. This way we + avoid instantiating a new JSParseState */ + /* XXX: find a better way as it relies on the regexp + parser to correctly handle the end of regexp */ + s->buf_pos = s->token.source_pos + 1; + s->buf_len = s->token.u.regexp.re_end_pos; + byte_code = js_parse_regexp(s, s->token.u.regexp.re_flags); + + s->buf_pos = saved_buf_pos; + s->buf_len = saved_buf_len; + b = JS_VALUE_TO_PTR(s->cur_func); + s->byte_code = b->byte_code; + s->byte_code_len = saved_byte_code_len; + + js_emit_push_const(s, byte_code); + emit_op(s, OP_regexp); + next_token(s); + } + break; + case '(': + next_token(s); + PARSE_CALL_SAVE1(s, 0, js_parse_expr_comma, 0, parse_flags); + js_parse_expect(s, ')'); + break; + case TOK_FUNCTION: + js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_NULL); + break; + case TOK_NULL: + emit_op(s, OP_null); + next_token(s); + break; + case TOK_THIS: + emit_op(s, OP_push_this); + next_token(s); + break; + case TOK_FALSE: + case TOK_TRUE: + emit_op(s, OP_push_false + (s->token.val == TOK_TRUE)); + next_token(s); + break; + case TOK_IDENT: + { + JSFunctionBytecode *b; + JSValue name; + int var_idx, arg_count, opcode; + + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count; + + name = s->token.value; + + var_idx = find_var(s, name); + if (var_idx >= 0) { + if (var_idx < arg_count) { + opcode = OP_get_arg; + } else { + opcode = OP_get_loc; + var_idx -= arg_count; + } + } else { + var_idx = find_ext_var(s, name); + if (var_idx < 0) { + var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 0); + } + opcode = OP_get_var_ref; + } + emit_var(s, opcode, var_idx, s->token.source_pos); + next_token(s); + } + break; + case '{': + { + JSValue name; + int prop_idx, prop_type, count_pos; + BOOL has_proto; + + next_token(s); + emit_op(s, OP_object); + count_pos = s->byte_code_len; + emit_u16(s, 0); + + has_proto = FALSE; + while (s->token.val != '}') { + prop_type = js_parse_property_name(s, &name); + if (prop_type == PARSE_PROP_FIELD && + name == js_get_atom(s->ctx, JS_ATOM___proto__)) { + if (has_proto) + js_parse_error(s, "duplicate __proto__ property name"); + has_proto = TRUE; + prop_idx = -1; + } else { + uint8_t *byte_code; + int count; + prop_idx = cpool_add(s, name); + /* increment the count */ + byte_code = get_byte_code(s); + count = get_u16(byte_code + count_pos); + put_u16(byte_code + count_pos, min_int(count + 1, 0xffff)); + } + if (prop_type == PARSE_PROP_FIELD) { + js_parse_expect(s, ':'); + PARSE_CALL_SAVE4(s, 1, js_parse_assign_expr, 0, prop_idx, parse_flags, has_proto, count_pos); + if (prop_idx >= 0) { + emit_op(s, OP_define_field); + emit_u16(s, prop_idx); + } else { + emit_op(s, OP_set_proto); + } + } else { + /* getter/setter/method */ + js_parse_function_decl(s, JS_PARSE_FUNC_METHOD, name); + if (prop_type == PARSE_PROP_METHOD) + emit_op(s, OP_define_field); + else if (prop_type == PARSE_PROP_GET) + emit_op(s, OP_define_getter); + else + emit_op(s, OP_define_setter); + emit_u16(s, prop_idx); + } + if (s->token.val != ',') + break; + next_token(s); + } + js_parse_expect(s, '}'); + } + break; + case '[': + { + uint32_t idx; + + next_token(s); + /* small regular arrays are created on the stack */ + idx = 0; + while (s->token.val != ']' && idx < 32) { + /* SPEC: we don't accept empty elements */ + PARSE_CALL_SAVE2(s, 2, js_parse_assign_expr, 0, idx, parse_flags); + idx++; + /* accept trailing comma */ + if (s->token.val == ',') { + next_token(s); + } else if (s->token.val != ']') { + goto done; + } + } + + emit_op_param(s, OP_array_from, idx, s->pc2line_source_pos); + + while (s->token.val != ']') { + if (idx >= JS_SHORTINT_MAX) + js_parse_error(s, "too many elements"); + emit_op(s, OP_dup); + emit_push_short_int(s, idx); + PARSE_CALL_SAVE2(s, 3, js_parse_assign_expr, 0, idx, parse_flags); + emit_op(s, OP_put_array_el); + idx++; + /* accept trailing comma */ + if (s->token.val == ',') { + next_token(s); + } + } + done: + js_parse_expect(s, ']'); + } + break; + case TOK_NEW: + next_token(s); + if (s->token.val == '.') { + next_token(s); + if (s->token.val != TOK_IDENT || + s->token.value != js_get_atom(s->ctx, JS_ATOM_target)) { + js_parse_error(s, "expecting target"); + } + next_token(s); + emit_op(s, OP_new_target); + } else { + PARSE_CALL_SAVE1(s, 4, js_parse_postfix_expr, 0, parse_flags); + if (s->token.val != '(') { + /* new operator on an object */ + emit_op_param(s, OP_call_constructor, 0, s->token.source_pos); + } else { + is_new = TRUE; + break; + } + } + break; + default: + js_parse_error(s, "unexpected character in expression"); + } + + for(;;) { + if (s->token.val == '(' && (parse_flags & PF_ACCEPT_LPAREN)) { + int opcode, arg_count; + uint8_t *byte_code; + JSSourcePos op_source_pos; + + /* function call */ + op_source_pos = s->token.source_pos; + next_token(s); + + if (!is_new) { + opcode = get_prev_opcode(s); + byte_code = get_byte_code(s); + switch(opcode) { + case OP_get_field: + byte_code[s->last_opcode_pos] = OP_get_field2; + break; + case OP_get_length: + byte_code[s->last_opcode_pos] = OP_get_length2; + break; + case OP_get_array_el: + byte_code[s->last_opcode_pos] = OP_get_array_el2; + break; + case OP_get_var_ref: + { + int var_idx = get_u16(byte_code + s->last_opcode_pos + 1); + if (get_ext_var_name(s, var_idx) == js_get_atom(s->ctx, JS_ATOM_eval)) { + js_parse_error(s, "direct eval is not supported. Use (1,eval) instead for indirect eval"); + } + } + /* fall thru */ + default: + opcode = OP_invalid; + break; + } + } else { + opcode = OP_invalid; + } + + arg_count = 0; + if (s->token.val != ')') { + for(;;) { + if (arg_count >= JS_MAX_ARGC) + js_parse_error(s, "too many call arguments"); + arg_count++; + PARSE_CALL_SAVE5(s, 5, js_parse_assign_expr, 0, + parse_flags, arg_count, opcode, is_new, op_source_pos); + if (s->token.val == ')') + break; + js_parse_expect(s, ','); + } + } + next_token(s); + if (opcode == OP_get_field || + opcode == OP_get_length || + opcode == OP_get_array_el) { + emit_op_param(s, OP_call_method, arg_count, op_source_pos); + } else { + if (is_new) { + emit_op_param(s, OP_call_constructor, arg_count, op_source_pos); + } else { + emit_op_param(s, OP_call, arg_count, op_source_pos); + } + } + is_new = FALSE; + } else if (s->token.val == '.') { + JSSourcePos op_source_pos; + int prop_idx; + + op_source_pos = s->token.source_pos; + next_token(s); + if (!(s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD)) { + js_parse_error(s, "expecting field name"); + } + /* we ensure that no numeric property is used with + OP_get_field to enable some optimizations. The only + possible identifiers are NaN and Infinity */ + if (s->token.value == js_get_atom(s->ctx, JS_ATOM_NaN) || + s->token.value == js_get_atom(s->ctx, JS_ATOM_Infinity)) { + js_emit_push_const(s, s->token.value); + emit_op_pos(s, OP_get_array_el, op_source_pos); + } else if (s->token.value == js_get_atom(s->ctx, JS_ATOM_length)) { + emit_op_pos(s, OP_get_length, op_source_pos); + } else { + prop_idx = cpool_add(s, s->token.value); + emit_op_pos(s, OP_get_field, op_source_pos); + emit_u16(s, prop_idx); + } + next_token(s); + } else if (s->token.val == '[') { + JSSourcePos op_source_pos; + op_source_pos = s->token.source_pos; + next_token(s); + PARSE_CALL_SAVE3(s, 6, js_parse_expr_comma, 0, + parse_flags, is_new, op_source_pos); + js_parse_expect(s, ']'); + emit_op_pos(s, OP_get_array_el, op_source_pos); + } else if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { + int opcode, op, var_idx; + JSSourcePos op_source_pos, source_pos; + + op = s->token.val; + op_source_pos = s->token.source_pos; + next_token(s); + get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE); + if (may_drop_result(s, parse_flags)) { + s->dropped_result = TRUE; + emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos); + put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_NOKEEP_TOP); + } else { + emit_op_pos(s, OP_post_dec + op - TOK_DEC, op_source_pos); + put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_KEEP_SECOND); + } + } else { + break; + } + } + return PARSE_STATE_RET; +} + +static void js_emit_delete(JSParseState *s) +{ + int opcode; + + opcode = get_prev_opcode(s); + switch(opcode) { + case OP_get_field: + { + JSByteArray *byte_code; + int prop_idx; + byte_code = JS_VALUE_TO_PTR(s->byte_code); + prop_idx = get_u16(byte_code->buf + s->last_opcode_pos + 1); + remove_last_op(s); + emit_op(s, OP_push_const); + emit_u16(s, prop_idx); + } + break; + case OP_get_length: + remove_last_op(s); + js_emit_push_const(s, js_get_atom(s->ctx, JS_ATOM_length)); + break; + case OP_get_array_el: + remove_last_op(s); + break; + default: + js_parse_error(s, "invalid lvalue for delete"); + } + emit_op(s, OP_delete); +} + +static int js_parse_unary(JSParseState *s, int state, int parse_flags) +{ + PARSE_START7(); + + switch(s->token.val) { + case '+': + case '-': + case '!': + case '~': + { + int op; + JSSourcePos op_source_pos; + + op = s->token.val; + op_source_pos = s->token.source_pos; + next_token(s); + + /* XXX: could handle more cases */ + if (s->token.val == TOK_NUMBER && (op == '-' || op == '+')) { + double d = s->token.u.d; + if (op == '-') + d = -d; + js_emit_push_number(s, d); + next_token(s); + } else { + PARSE_CALL_SAVE2(s, 0, js_parse_unary, 0, op, op_source_pos); + switch(op) { + case '-': + emit_op_pos(s, OP_neg, op_source_pos); + break; + case '+': + emit_op_pos(s, OP_plus, op_source_pos); + break; + case '!': + emit_op_pos(s, OP_lnot, op_source_pos); + break; + case '~': + emit_op_pos(s, OP_not, op_source_pos); + break; + default: + abort(); + } + } + } + break; + case TOK_VOID: + next_token(s); + PARSE_CALL(s, 1, js_parse_unary, 0); + emit_op(s, OP_drop); + emit_op(s, OP_undefined); + break; + case TOK_DEC: + case TOK_INC: + { + int opcode, op, var_idx; + PutLValueEnum special; + JSSourcePos op_source_pos, source_pos; + + op = s->token.val; + op_source_pos = s->token.source_pos; + next_token(s); + PARSE_CALL_SAVE3(s, 2, js_parse_unary, 0, op, parse_flags, op_source_pos); + get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE); + emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos); + + if (may_drop_result(s, parse_flags)) { + special = PUT_LVALUE_NOKEEP_TOP; + s->dropped_result = TRUE; + } else { + special = PUT_LVALUE_KEEP_TOP; + } + put_lvalue(s, opcode, var_idx, source_pos, special); + } + break; + case TOK_TYPEOF: + { + next_token(s); + PARSE_CALL(s, 3, js_parse_unary, 0); + /* access to undefined variable should not return an + exception, so we patch the get_var */ + if (get_prev_opcode(s) == OP_get_var_ref) { + uint8_t *byte_code = get_byte_code(s); + byte_code[s->last_opcode_pos] = OP_get_var_ref_nocheck; + } + emit_op(s, OP_typeof); + } + break; + case TOK_DELETE: + next_token(s); + PARSE_CALL(s, 4, js_parse_unary, 0); + js_emit_delete(s); + break; + default: + PARSE_CALL(s, 5, js_parse_postfix_expr, parse_flags | PF_ACCEPT_LPAREN); + /* XXX: we do not follow the ES7 grammar in order to have a + * more natural expression */ + if (s->token.val == TOK_POW) { + JSSourcePos op_source_pos; + op_source_pos = s->token.source_pos; + next_token(s); + PARSE_CALL_SAVE1(s, 6, js_parse_unary, 0, op_source_pos); + emit_op_pos(s, OP_pow, op_source_pos); + } + break; + } + return PARSE_STATE_RET; +} + +static int js_parse_expr_binary(JSParseState *s, int state, int parse_flags) +{ + int op, opcode, level; + JSSourcePos op_source_pos; + + PARSE_START3(); + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 0) { + PARSE_CALL(s, 0, js_parse_unary, parse_flags); + return PARSE_STATE_RET; + } + PARSE_CALL_SAVE1(s, 1, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags); + parse_flags &= ~PF_DROP; + for(;;) { + op = s->token.val; + op_source_pos = s->token.source_pos; + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + switch(level) { + case 1: + switch(op) { + case '*': + opcode = OP_mul; + break; + case '/': + opcode = OP_div; + break; + case '%': + opcode = OP_mod; + break; + default: + return PARSE_STATE_RET; + } + break; + case 2: + switch(op) { + case '+': + opcode = OP_add; + break; + case '-': + opcode = OP_sub; + break; + default: + return PARSE_STATE_RET; + } + break; + case 3: + switch(op) { + case TOK_SHL: + opcode = OP_shl; + break; + case TOK_SAR: + opcode = OP_sar; + break; + case TOK_SHR: + opcode = OP_shr; + break; + default: + return PARSE_STATE_RET; + } + break; + case 4: + switch(op) { + case '<': + opcode = OP_lt; + break; + case '>': + opcode = OP_gt; + break; + case TOK_LTE: + opcode = OP_lte; + break; + case TOK_GTE: + opcode = OP_gte; + break; + case TOK_INSTANCEOF: + opcode = OP_instanceof; + break; + case TOK_IN: + if (!(parse_flags & PF_NO_IN)) { + opcode = OP_in; + } else { + return PARSE_STATE_RET; + } + break; + default: + return PARSE_STATE_RET; + } + break; + case 5: + switch(op) { + case TOK_EQ: + opcode = OP_eq; + break; + case TOK_NEQ: + opcode = OP_neq; + break; + case TOK_STRICT_EQ: + opcode = OP_strict_eq; + break; + case TOK_STRICT_NEQ: + opcode = OP_strict_neq; + break; + default: + return PARSE_STATE_RET; + } + break; + case 6: + switch(op) { + case '&': + opcode = OP_and; + break; + default: + return PARSE_STATE_RET; + } + break; + case 7: + switch(op) { + case '^': + opcode = OP_xor; + break; + default: + return PARSE_STATE_RET; + } + break; + case 8: + switch(op) { + case '|': + opcode = OP_or; + break; + default: + return PARSE_STATE_RET; + } + break; + default: + abort(); + } + next_token(s); + PARSE_CALL_SAVE3(s, 2, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags, opcode, op_source_pos); + emit_op_pos(s, opcode, op_source_pos); + } + return PARSE_STATE_RET; +} + +static int js_parse_logical_and_or(JSParseState *s, int state, int parse_flags) +{ + JSValue label1; + int level, op; + + PARSE_START3(); + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 0) { + PARSE_CALL(s, 0, js_parse_expr_binary, (parse_flags & ~PF_LEVEL_MASK) | (8 << PF_LEVEL_SHIFT)); + return PARSE_STATE_RET; + } + + PARSE_CALL_SAVE1(s, 1, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags); + + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 1) + op = TOK_LAND; + else + op = TOK_LOR; + parse_flags &= ~PF_DROP; + if (s->token.val == op) { + label1 = new_label(s); + + for(;;) { + next_token(s); + emit_op(s, OP_dup); + emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, &label1); + emit_op(s, OP_drop); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags); + PARSE_POP_VAL(s, label1); + + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 1) + op = TOK_LAND; + else + op = TOK_LOR; + + if (s->token.val != op) + break; + } + + emit_label(s, &label1); + } + return PARSE_STATE_RET; +} + +static int js_parse_cond_expr(JSParseState *s, int state, int parse_flags) +{ + JSValue label1, label2; + + PARSE_START3(); + + PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags | (2 << PF_LEVEL_SHIFT), parse_flags); + + parse_flags &= ~PF_DROP; + if (s->token.val == '?') { + next_token(s); + label1 = new_label(s); + emit_goto(s, OP_if_false, &label1); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL_SAVE1(s, 0, js_parse_assign_expr, parse_flags, + parse_flags); + PARSE_POP_VAL(s, label1); + + label2 = new_label(s); + emit_goto(s, OP_goto, &label2); + + js_parse_expect(s, ':'); + + emit_label(s, &label1); + + PARSE_PUSH_VAL(s, label2); + PARSE_CALL_SAVE1(s, 1, js_parse_assign_expr, parse_flags, + parse_flags); + PARSE_POP_VAL(s, label2); + + emit_label(s, &label2); + } + return PARSE_STATE_RET; +} + +static int js_parse_assign_expr(JSParseState *s, int state, int parse_flags) +{ + int opcode, op, var_idx; + PutLValueEnum special; + JSSourcePos op_source_pos, source_pos; + + PARSE_START2(); + + PARSE_CALL_SAVE1(s, 1, js_parse_cond_expr, parse_flags, parse_flags); + + op = s->token.val; + if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_OR_ASSIGN)) { + op_source_pos = s->token.source_pos; + next_token(s); + get_lvalue(s, &opcode, &var_idx, &source_pos, (op != '=')); + + PARSE_CALL_SAVE6(s, 0, js_parse_assign_expr, parse_flags & ~PF_DROP, + op, opcode, var_idx, parse_flags, + op_source_pos, source_pos); + + if (op != '=') { + static const uint8_t assign_opcodes[] = { + OP_mul, OP_div, OP_mod, OP_add, OP_sub, + OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or, + OP_pow, + }; + emit_op_pos(s, assign_opcodes[op - TOK_MUL_ASSIGN], op_source_pos); + } + + if (may_drop_result(s, parse_flags)) { + special = PUT_LVALUE_NOKEEP_TOP; + s->dropped_result = TRUE; + } else { + special = PUT_LVALUE_KEEP_TOP; + } + put_lvalue(s, opcode, var_idx, source_pos, special); + } + return PARSE_STATE_RET; +} + +static int js_parse_expr_comma(JSParseState *s, int state, int parse_flags) +{ + BOOL comma = FALSE; + + PARSE_START1(); + + for(;;) { + s->dropped_result = FALSE; + PARSE_CALL_SAVE2(s, 0, js_parse_assign_expr, parse_flags, + comma, parse_flags); + if (comma) { + /* prevent get_lvalue from using the last expression as an + lvalue. */ + s->last_opcode_pos = -1; + } + if (s->token.val != ',') + break; + comma = TRUE; + if (!s->dropped_result) + emit_op(s, OP_drop); + next_token(s); + } + if ((parse_flags & PF_DROP) && !s->dropped_result) { + emit_op(s, OP_drop); + } + return PARSE_STATE_RET; +} + +static void js_parse_assign_expr2(JSParseState *s, int parse_flags) +{ + js_parse_call(s, PARSE_FUNC_js_parse_assign_expr, parse_flags); +} + +static void js_parse_expr2(JSParseState *s, int parse_flags) +{ + js_parse_call(s, PARSE_FUNC_js_parse_expr_comma, parse_flags); +} + +static void js_parse_expr(JSParseState *s) +{ + js_parse_expr2(s, 0); +} + +static void js_parse_expr_paren(JSParseState *s) +{ + js_parse_expect(s, '('); + js_parse_expr(s); + js_parse_expect(s, ')'); +} + +static BlockEnv *push_break_entry(JSParseState *s, JSValue label_name, + JSValue label_break, JSValue label_cont, + int drop_count) +{ + JSContext *ctx = s->ctx; + JSGCRef label_name_ref; + int ret, block_env_len; + BlockEnv *be; + + block_env_len = sizeof(BlockEnv) / sizeof(JSValue); + JS_PUSH_VALUE(ctx, label_name); + ret = JS_StackCheck(ctx, block_env_len); + JS_POP_VALUE(ctx, label_name); + if (ret) + js_parse_error_stack_overflow(s); + ctx->sp -= block_env_len; + be = (BlockEnv *)ctx->sp; + be->prev = s->top_break; + s->top_break = SP_TO_VALUE(ctx, be); + be->label_name = label_name; + be->label_break = label_break; + be->label_cont = label_cont; + be->label_finally = LABEL_NONE; + be->drop_count = JS_NewShortInt(drop_count); + return be; +} + +static void pop_break_entry(JSParseState *s) +{ + JSContext *ctx = s->ctx; + BlockEnv *be; + + be = VALUE_TO_SP(ctx, s->top_break); + s->top_break = be->prev; + ctx->sp += sizeof(BlockEnv) / sizeof(JSValue); + ctx->stack_bottom = ctx->sp; +} + +static void emit_return(JSParseState *s, BOOL hasval, JSSourcePos source_pos) +{ + JSValue top_val; + BlockEnv *top; + int i, drop_count; + + drop_count = 0; + top_val = s->top_break; + while (!JS_IsNull(top_val)) { + top = VALUE_TO_SP(s->ctx, top_val); + /* no need to drop if no "finally" */ + drop_count += JS_VALUE_GET_INT(top->drop_count); + + if (!label_is_none(top->label_finally)) { + if (!hasval) { + emit_op(s, OP_undefined); + hasval = TRUE; + } + for(i = 0; i < drop_count; i++) + emit_op(s, OP_nip); /* must keep the stack stop */ + drop_count = 0; + /* execute the "finally" block */ + emit_goto(s, OP_gosub, &top->label_finally); + } + top_val = top->prev; + } + emit_op_pos(s, hasval ? OP_return : OP_return_undef, source_pos); +} + +static void emit_break(JSParseState *s, JSValue label_name, int is_cont) +{ + JSValue top_val; + BlockEnv *top; + int i; + JSValue *plabel; + JSGCRef label_name_ref; + BOOL is_labelled_stmt; + + top_val = s->top_break; + while (!JS_IsNull(top_val)) { + top = VALUE_TO_SP(s->ctx, top_val); + is_labelled_stmt = (top->label_cont == LABEL_NONE && + JS_VALUE_GET_INT(top->drop_count) == 0); + if ((label_name == JS_NULL && !is_labelled_stmt) || + top->label_name == label_name) { + if (is_cont) + plabel = &top->label_cont; + else + plabel = &top->label_break; + if (!label_is_none(*plabel)) { + emit_goto(s, OP_goto, plabel); + return; + } + } + JS_PUSH_VALUE(s->ctx, label_name); + for(i = 0; i < JS_VALUE_GET_INT(top->drop_count); i++) + emit_op(s, OP_drop); + if (!label_is_none(top->label_finally)) { + /* must push dummy value to keep same stack depth */ + emit_op(s, OP_undefined); + emit_goto(s, OP_gosub, &top->label_finally); + emit_op(s, OP_drop); + } + JS_POP_VALUE(s->ctx, label_name); + top_val = top->prev; + } + if (label_name == JS_NULL) { + if (is_cont) + js_parse_error(s, "continue must be inside loop"); + else + js_parse_error(s, "break must be inside loop or switch"); + } else { + js_parse_error(s, "break/continue label not found"); + } +} + +static int define_var(JSParseState *s, JSVarRefKindEnum *pvar_kind, JSValue name) +{ + JSVarRefKindEnum var_kind; + int var_idx; + + if (s->is_eval) { + var_idx = find_ext_var(s, name); + if (var_idx < 0) { + var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 1); + } else { + JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func); + JSValueArray *arr = JS_VALUE_TO_PTR(b->ext_vars); + arr->arr[2 * var_idx + 1] = JS_NewShortInt((JS_VARREF_KIND_GLOBAL << 16) | 1); + } + var_kind = JS_VARREF_KIND_VAR_REF; + } else { + JSFunctionBytecode *b; + int arg_count; + + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count; + + var_idx = find_var(s, name); + if (var_idx >= 0) { + if (var_idx < arg_count) { + var_kind = JS_VARREF_KIND_ARG; + } else { + var_kind = JS_VARREF_KIND_VAR; + var_idx -= arg_count; + } + } else { + var_idx = add_var(s, name); + var_kind = JS_VARREF_KIND_VAR; + var_idx -= arg_count; + } + } + *pvar_kind = var_kind; + return var_idx; +} + +static void put_var(JSParseState *s, JSVarRefKindEnum var_kind, int var_idx, JSSourcePos source_pos) +{ + int opcode; + if (var_kind == JS_VARREF_KIND_ARG) + opcode = OP_put_arg; + else if (var_kind == JS_VARREF_KIND_VAR) + opcode = OP_put_loc; + else + opcode = OP_put_var_ref_nocheck; + emit_var(s, opcode, var_idx, source_pos); +} + +static void js_parse_var(JSParseState *s, BOOL in_accepted) +{ + JSVarRefKindEnum var_kind; + int var_idx; + JSSourcePos ident_source_pos; + + for(;;) { + ident_source_pos = s->token.source_pos; + if (s->token.val != TOK_IDENT) + js_parse_error(s, "variable name expected"); + if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments)) + js_parse_error(s, "invalid variable name"); + var_idx = define_var(s, &var_kind, s->token.value); + next_token(s); + if (s->token.val == '=') { + next_token(s); + js_parse_assign_expr2(s, in_accepted ? 0 : PF_NO_IN); + put_var(s, var_kind, var_idx, ident_source_pos); + } + if (s->token.val != ',') + break; + next_token(s); + } +} + +static void set_eval_ret_undefined(JSParseState *s) +{ + if (s->eval_ret_idx >= 0) { + emit_op(s, OP_undefined); + emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos); + } +} + +static int js_parse_block(JSParseState *s, int state, int dummy_param) +{ + PARSE_START1(); + js_parse_expect(s, '{'); + if (s->token.val != '}') { + for(;;) { + PARSE_CALL(s, 0, js_parse_statement, 0); + if (s->token.val == '}') + break; + } + } + next_token(s); + return PARSE_STATE_RET; +} + +/* The statement parser assumes that the stack contains the result of + the last statement. Note: if not in eval code, the return value of + a statement does not matter */ +static int js_parse_statement(JSParseState *s, int state, int dummy_param) +{ + JSValue label_name; + JSGCRef label_name_ref; + + PARSE_START12(); + + /* specific label handling */ + if (is_label(s)) { + JSValue top_val; + BlockEnv *top; + + label_name = s->token.value; + JS_PUSH_VALUE(s->ctx, label_name); + next_token(s); + js_parse_expect(s, ':'); + JS_POP_VALUE(s->ctx, label_name); + + for(top_val = s->top_break; !JS_IsNull(top_val); top_val = top->prev) { + top = VALUE_TO_SP(s->ctx, top_val); + if (top->label_name == label_name) + js_parse_error(s, "duplicate label name"); + } + + if (s->token.val != TOK_FOR && + s->token.val != TOK_DO && + s->token.val != TOK_WHILE) { + /* labelled regular statement */ + BlockEnv *be; + push_break_entry(s, label_name, new_label(s), LABEL_NONE, 0); + + PARSE_CALL(s, 11, js_parse_statement, 0); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_break); + pop_break_entry(s); + goto done; + } + } else { + label_name = JS_NULL; + } + + switch(s->token.val) { + case '{': + PARSE_CALL(s, 0, js_parse_block, 0); + break; + case TOK_RETURN: + { + BOOL has_val; + JSSourcePos op_source_pos; + if (s->is_eval) + js_parse_error(s, "return not in a function"); + op_source_pos = s->token.source_pos; + next_token(s); + if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { + js_parse_expr(s); + has_val = TRUE; + } else { + has_val = FALSE; + } + emit_return(s, has_val, op_source_pos); + js_parse_expect_semi(s); + } + break; + case TOK_THROW: + { + JSSourcePos op_source_pos; + op_source_pos = s->token.source_pos; + next_token(s); + if (s->got_lf) + js_parse_error(s, "line terminator not allowed after throw"); + js_parse_expr(s); + emit_op_pos(s, OP_throw, op_source_pos); + js_parse_expect_semi(s); + } + break; + case TOK_VAR: + next_token(s); + js_parse_var(s, TRUE); + js_parse_expect_semi(s); + break; + case TOK_IF: + { + JSValue label1, label2; + next_token(s); + set_eval_ret_undefined(s); + js_parse_expr_paren(s); + label1 = new_label(s); + emit_goto(s, OP_if_false, &label1); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL(s, 1, js_parse_statement, 0); + PARSE_POP_VAL(s, label1); + + if (s->token.val == TOK_ELSE) { + next_token(s); + + label2 = new_label(s); + emit_goto(s, OP_goto, &label2); + + emit_label(s, &label1); + + PARSE_PUSH_VAL(s, label2); + PARSE_CALL(s, 2, js_parse_statement, 0); + PARSE_POP_VAL(s, label2); + + label1 = label2; + } + emit_label(s, &label1); + } + break; + case TOK_WHILE: + { + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), new_label(s), 0); + next_token(s); + + set_eval_ret_undefined(s); + + emit_label(s, &be->label_cont); + js_parse_expr_paren(s); + emit_goto(s, OP_if_false, &be->label_break); + + PARSE_CALL(s, 3, js_parse_statement, 0); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_goto(s, OP_goto, &be->label_cont); + + emit_label(s, &be->label_break); + + pop_break_entry(s); + } + break; + case TOK_DO: + { + JSValue label1; + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), new_label(s), 0); + + label1 = new_label(s); + + next_token(s); + set_eval_ret_undefined(s); + + emit_label(s, &label1); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL(s, 4, js_parse_statement, 0); + PARSE_POP_VAL(s, label1); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_cont); + js_parse_expect(s, TOK_WHILE); + js_parse_expr_paren(s); + /* Insert semicolon if missing */ + if (s->token.val == ';') { + next_token(s); + } + emit_goto(s, OP_if_true, &label1); + + emit_label(s, &be->label_break); + + pop_break_entry(s); + } + break; + case TOK_FOR: + { + int bits; + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), new_label(s), 0); + + next_token(s); + set_eval_ret_undefined(s); + + js_parse_expect1(s, '('); + bits = js_parse_skip_parens_token(s); + next_token(s); + + if (!(bits & SKIP_HAS_SEMI)) { + JSValue label_expr, label_body, label_next; + int opcode, var_idx; + + be->drop_count = JS_NewShortInt(1); + + label_expr = new_label(s); + label_body = new_label(s); + label_next = new_label(s); + + emit_goto(s, OP_goto, &label_expr); + + emit_label(s, &label_next); + + if (s->token.val == TOK_VAR) { + JSVarRefKindEnum var_kind; + next_token(s); + var_idx = define_var(s, &var_kind, s->token.value); + put_var(s, var_kind, var_idx, s->pc2line_source_pos); + + next_token(s); + } else { + JSSourcePos source_pos; + + /* XXX: js_parse_left_hand_side_expr */ + js_parse_assign_expr2(s, PF_NO_IN); + + get_lvalue(s, &opcode, &var_idx, &source_pos, FALSE); + put_lvalue(s, opcode, var_idx, source_pos, + PUT_LVALUE_NOKEEP_BOTTOM); + } + + emit_goto(s, OP_goto, &label_body); + + if (s->token.val == TOK_IN) { + opcode = OP_for_in_start; + } else if (s->token.val == TOK_IDENT && + s->token.value == js_get_atom(s->ctx, JS_ATOM_of)) { + opcode = OP_for_of_start; + } else { + js_parse_error(s, "expected 'of' or 'in' in for control expression"); + } + + next_token(s); + + emit_label(s, &label_expr); + js_parse_expr(s); + emit_op(s, opcode); + + emit_goto(s, OP_goto, &be->label_cont); + + js_parse_expect(s, ')'); + + emit_label(s, &label_body); + + PARSE_PUSH_VAL(s, label_next); + PARSE_CALL(s, 5, js_parse_statement, 0); + PARSE_POP_VAL(s, label_next); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_cont); + emit_op(s, OP_for_of_next); + + /* on stack: enum_rec / enum_obj value bool */ + emit_goto(s, OP_if_false, &label_next); + /* drop the undefined value from for_xx_next */ + emit_op(s, OP_drop); + + emit_label(s, &be->label_break); + emit_op(s, OP_drop); + } else { + JSValue label_test; + JSParsePos expr3_pos; + int tmp_val; + + /* initial expression */ + if (s->token.val != ';') { + if (s->token.val == TOK_VAR) { + next_token(s); + js_parse_var(s, FALSE); + } else { + js_parse_expr2(s, PF_NO_IN | PF_DROP); + } + } + js_parse_expect(s, ';'); + + label_test = new_label(s); + + /* test expression */ + emit_label(s, &label_test); + if (s->token.val != ';') { + js_parse_expr(s); + emit_goto(s, OP_if_false, &be->label_break); + } + js_parse_expect(s, ';'); + + if (s->token.val != ')') { + /* skip the third expression if present */ + js_parse_get_pos(s, &expr3_pos); + js_skip_expr(s); + } else { + expr3_pos.source_pos = -1; + expr3_pos.got_lf = 0; /* avoid warning */ + expr3_pos.regexp_allowed = 0; /* avoid warning */ + } + js_parse_expect(s, ')'); + + PARSE_PUSH_VAL(s, label_test); + PARSE_PUSH_INT(s, expr3_pos.got_lf | (expr3_pos.regexp_allowed << 1)); + PARSE_PUSH_INT(s, expr3_pos.source_pos); + PARSE_CALL(s, 6, js_parse_statement, 0); + PARSE_POP_INT(s, expr3_pos.source_pos); + PARSE_POP_INT(s, tmp_val); + expr3_pos.got_lf = tmp_val & 1; + expr3_pos.regexp_allowed = tmp_val >> 1; + PARSE_POP_VAL(s, label_test); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_cont); + + /* parse the third expression, if present, after the + statement */ + if (expr3_pos.source_pos != -1) { + JSParsePos end_pos; + js_parse_get_pos(s, &end_pos); + js_parse_seek_token(s, &expr3_pos); + js_parse_expr2(s, PF_DROP); + js_parse_seek_token(s, &end_pos); + } + + emit_goto(s, OP_goto, &label_test); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_break); + } + pop_break_entry(s); + } + break; + case TOK_BREAK: + case TOK_CONTINUE: + { + int is_cont = (s->token.val == TOK_CONTINUE); + JSValue label_name; + + next_token(s); + if (!s->got_lf && s->token.val == TOK_IDENT) + label_name = s->token.value; + else + label_name = JS_NULL; + emit_break(s, label_name, is_cont); + if (label_name != JS_NULL) { + next_token(s); + } + js_parse_expect_semi(s); + } + break; + case TOK_SWITCH: + { + JSValue label_case; + int default_label_pos; + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), LABEL_NONE, 1); + + next_token(s); + set_eval_ret_undefined(s); + + js_parse_expr_paren(s); + + js_parse_expect(s, '{'); + default_label_pos = -1; + label_case = LABEL_NONE; /* label to the next case */ + while (s->token.val != '}') { + if (s->token.val == TOK_CASE) { + JSValue label1 = LABEL_NONE; + if (!label_is_none(label_case)) { + /* skip the case if needed */ + label1 = new_label(s); + emit_goto(s, OP_goto, &label1); + emit_label(s, &label_case); + label_case = LABEL_NONE; + } + for (;;) { + /* parse a sequence of case clauses */ + next_token(s); + emit_op(s, OP_dup); + js_parse_expr(s); + js_parse_expect(s, ':'); + emit_op(s, OP_strict_eq); + if (s->token.val == TOK_CASE) { + if (label_is_none(label1)) + label1 = new_label(s); + emit_goto(s, OP_if_true, &label1); + } else { + label_case = new_label(s); + emit_goto(s, OP_if_false, &label_case); + if (!label_is_none(label1)) + emit_label(s, &label1); + break; + } + } + } else if (s->token.val == TOK_DEFAULT) { + next_token(s); + js_parse_expect(s, ':'); + if (default_label_pos >= 0) + js_parse_error(s, "duplicate default"); + if (label_is_none(label_case)) { + /* falling thru direct from switch expression */ + label_case = new_label(s); + emit_goto(s, OP_goto, &label_case); + } + default_label_pos = s->byte_code_len; + } else { + if (label_is_none(label_case)) + js_parse_error(s, "invalid switch statement"); + PARSE_PUSH_VAL(s, label_case); + PARSE_CALL_SAVE1(s, 7, js_parse_statement, 0, + default_label_pos); + PARSE_POP_VAL(s, label_case); + } + } + js_parse_expect(s, '}'); + if (default_label_pos >= 0) { + /* patch the default label */ + emit_label_pos(s, &label_case, default_label_pos); + } else if (!label_is_none(label_case)) { + emit_label(s, &label_case); + } + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_break); + emit_op(s, OP_drop); /* drop the switch expression */ + + pop_break_entry(s); + } + break; + case TOK_TRY: + { + JSValue label_catch, label_finally, label_end; + BlockEnv *be; + + set_eval_ret_undefined(s); + next_token(s); + label_catch = new_label(s); + label_finally = new_label(s); + + emit_goto(s, OP_catch, &label_catch); + + be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1); + be->label_finally = label_finally; + + PARSE_PUSH_VAL(s, label_catch); + PARSE_CALL(s, 8, js_parse_block, 0); + PARSE_POP_VAL(s, label_catch); + + be = VALUE_TO_SP(s->ctx, s->top_break); + label_finally = be->label_finally; + pop_break_entry(s); + + /* drop the catch offset */ + emit_op(s, OP_drop); + + /* must push dummy value to keep same stack size */ + emit_op(s, OP_undefined); + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_drop); + + label_end = new_label(s); + emit_goto(s, OP_goto, &label_end); + + if (s->token.val == TOK_CATCH) { + JSValue label_catch2; + int var_idx; + JSValue name; + + label_catch2 = new_label(s); + + next_token(s); + js_parse_expect(s, '('); + if (s->token.val != TOK_IDENT) + js_parse_error(s, "identifier expected"); + name = s->token.value; + /* XXX: the local scope is not implemented, so we add + a normal variable */ + if (find_var(s, name) >= 0 || find_ext_var(s, name) >= 0) { + js_parse_error(s, "catch variable already exists"); + } + var_idx = add_var(s, name); + next_token(s); + js_parse_expect(s, ')'); + + /* store the exception value in the variable */ + emit_label(s, &label_catch); + { + JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func); + emit_var(s, OP_put_loc, var_idx - b->arg_count, s->pc2line_source_pos); + } + + emit_goto(s, OP_catch, &label_catch2); + + be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1); + be->label_finally = label_finally; + + PARSE_PUSH_VAL(s, label_end); + PARSE_PUSH_VAL(s, label_catch2); + PARSE_CALL(s, 9, js_parse_block, 0); + PARSE_POP_VAL(s, label_catch2); + PARSE_POP_VAL(s, label_end); + + be = VALUE_TO_SP(s->ctx, s->top_break); + label_finally = be->label_finally; + pop_break_entry(s); + + /* drop the catch2 offset */ + emit_op(s, OP_drop); + /* must push dummy value to keep same stack size */ + emit_op(s, OP_undefined); + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_drop); + emit_goto(s, OP_goto, &label_end); + + /* catch exceptions thrown in the catch block to execute the + * finally clause and rethrow the exception */ + emit_label(s, &label_catch2); + /* catch value is at TOS, no need to push undefined */ + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_throw); + + } else if (s->token.val == TOK_FINALLY) { + /* finally without catch : execute the finally clause + * and rethrow the exception */ + emit_label(s, &label_catch); + /* catch value is at TOS, no need to push undefined */ + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_throw); + } else { + js_parse_error(s, "expecting catch or finally"); + } + + emit_label(s, &label_finally); + if (s->token.val == TOK_FINALLY) { + next_token(s); + /* XXX: we don't return the correct value in eval() */ + /* on the stack: ret_value gosub_ret_value */ + push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 2); + + PARSE_PUSH_VAL(s, label_end); + PARSE_CALL(s, 10, js_parse_block, 0); + PARSE_POP_VAL(s, label_end); + + pop_break_entry(s); + } + emit_op(s, OP_ret); + emit_label(s, &label_end); + } + break; + case ';': + /* empty statement */ + next_token(s); + break; + default: + if (s->eval_ret_idx >= 0) { + /* store the expression value so that it can be returned + by eval() */ + js_parse_expr(s); + emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos); + } else { + js_parse_expr2(s, PF_DROP); + } + js_parse_expect_semi(s); + break; + } + done: + return PARSE_STATE_RET; +} + +static JSParseFunc *parse_func_table[] = { + js_parse_expr_comma, + js_parse_assign_expr, + js_parse_cond_expr, + js_parse_logical_and_or, + js_parse_expr_binary, + js_parse_unary, + js_parse_postfix_expr, + js_parse_statement, + js_parse_block, + js_parse_json_value, + re_parse_alternative, + re_parse_disjunction, +}; + +static void js_parse_source_element(JSParseState *s) +{ + if (s->token.val == TOK_FUNCTION) { + js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_NULL); + } else { + js_parse_call(s, PARSE_FUNC_js_parse_statement, 0); + } +} + +static JSFunctionBytecode *js_alloc_function_bytecode(JSContext *ctx) +{ + JSFunctionBytecode *b; + b = js_mallocz(ctx, sizeof(JSFunctionBytecode), JS_MTAG_FUNCTION_BYTECODE); + if (!b) + return NULL; + b->func_name = JS_NULL; + b->byte_code = JS_NULL; + b->cpool = JS_NULL; + b->vars = JS_NULL; + b->ext_vars = JS_NULL; + b->filename = JS_NULL; + b->pc2line = JS_NULL; + return b; +} + +/* the current token must be TOK_FUNCTION for JS_PARSE_FUNC_STATEMENT + or JS_PARSE_FUNC_EXPR. Otherwise it is '('. */ +static void js_parse_function_decl(JSParseState *s, + JSParseFunctionEnum func_type, JSValue func_name) +{ + JSContext *ctx = s->ctx; + BOOL is_expr; + JSFunctionBytecode *b; + int idx, skip_bits; + JSVarRefKindEnum var_kind; + JSValue bfunc; + JSGCRef func_name_ref, bfunc_ref; + + is_expr = (func_type != JS_PARSE_FUNC_STATEMENT); + + if (func_type == JS_PARSE_FUNC_STATEMENT || + func_type == JS_PARSE_FUNC_EXPR) { + next_token(s); + if (s->token.val != TOK_IDENT && !is_expr) + js_parse_error(s, "function name expected"); + if (s->token.val == TOK_IDENT) { + func_name = s->token.value; + JS_PUSH_VALUE(ctx, func_name); + next_token(s); + JS_POP_VALUE(ctx, func_name); + } + } + + JS_PUSH_VALUE(ctx, func_name); + b = js_alloc_function_bytecode(s->ctx); + if (!b) + js_parse_error_mem(s); + bfunc = JS_VALUE_FROM_PTR(b); + JS_PUSH_VALUE(ctx, bfunc); + + b->filename = s->filename_str; + b->func_name = func_name_ref.val; + b->source_pos = s->token.source_pos; + b->has_column = s->has_column; + + js_parse_expect1(s, '('); + /* skip the arguments */ + js_skip_parens(s, NULL); + + js_parse_expect1(s, '{'); + + /* skip the code */ + skip_bits = js_skip_parens(s, is_expr ? &func_name_ref.val : NULL); + + b = JS_VALUE_TO_PTR(bfunc_ref.val); + b->has_arguments = ((skip_bits & SKIP_HAS_ARGUMENTS) != 0); + b->has_local_func_name = ((skip_bits & SKIP_HAS_FUNC_NAME) != 0); + + idx = cpool_add(s, bfunc_ref.val); + if (is_expr) { + /* create the function object */ + emit_op(s, OP_fclosure); + emit_u16(s, idx); + } else { + idx = define_var(s, &var_kind, func_name_ref.val); + /* size of hoisted for OP_fclosure + OP_put_loc/OP_put_arg/OP_put_ref */ + s->hoisted_code_len += 3 + 3; + if (var_kind == JS_VARREF_KIND_VAR) { + b = JS_VALUE_TO_PTR(s->cur_func); + idx += b->arg_count; + } + b = JS_VALUE_TO_PTR(bfunc_ref.val); + /* hoisted function definition: save the variable index to + define it at the start of the function */ + b->arg_count = idx + 1; + } + JS_POP_VALUE(ctx, bfunc); + JS_POP_VALUE(ctx, func_name); +} + +static void define_hoisted_functions(JSParseState *s, BOOL is_eval) +{ + JSValueArray *cpool; + JSValue val; + JSFunctionBytecode *b; + int idx, saved_byte_code_len, arg_count, i, op; + + /* add pc2line info */ + b = JS_VALUE_TO_PTR(s->cur_func); + if (b->pc2line != JS_NULL) { + int h, n; + + /* byte align */ + n = (-s->pc2line_bit_len) & 7; + if (n != 0) + pc2line_put_bits(s, n, 0); + + n = s->hoisted_code_len; + h = 0; + for(;;) { + pc2line_put_bits(s, 8, (n & 0x7f) | h); + n >>= 7; + if (n == 0) + break; + h |= 0x80; + } + } + + if (s->hoisted_code_len == 0) + return; + emit_insert(s, 0, s->hoisted_code_len); + + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count; + + saved_byte_code_len = s->byte_code_len; + s->byte_code_len = 0; + cpool = JS_VALUE_TO_PTR(b->cpool); + for(i = 0; i < s->cpool_len; i++) { + val = cpool->arr[i]; + if (JS_IsPtr(val)) { + b = JS_VALUE_TO_PTR(val); + if (b->mtag == JS_MTAG_FUNCTION_BYTECODE && + b->arg_count != 0) { + idx = b->arg_count - 1; + /* XXX: could use smaller opcodes */ + if (is_eval) { + op = OP_put_var_ref_nocheck; + } else if (idx < arg_count) { + op = OP_put_arg; + } else { + idx -= arg_count; + op = OP_put_loc; + } + /* no realloc possible here */ + emit_u8(s, OP_fclosure); + emit_u16(s, i); + + emit_u8(s, op); + emit_u16(s, idx); + } + } + } + s->byte_code_len = saved_byte_code_len; +} + +static void js_parse_function(JSParseState *s) +{ + JSFunctionBytecode *b; + int arg_count; + + next_token(s); + + js_parse_expect(s, '('); + + while (s->token.val != ')') { + JSValue name; + /* XXX: gc */ + if (s->token.val != TOK_IDENT) + js_parse_error(s, "missing formal parameter"); + name = s->token.value; + if (name == js_get_atom(s->ctx, JS_ATOM_eval) || + name == js_get_atom(s->ctx, JS_ATOM_arguments)) { + js_parse_error(s, "invalid argument name"); + } + if (find_var(s, name) >= 0) + js_parse_error(s, "duplicate argument name"); + add_var(s, name); + next_token(s); + if (s->token.val == ')') + break; + js_parse_expect(s, ','); + } + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count = s->local_vars_len; + + next_token(s); + + js_parse_expect(s, '{'); + + /* initialize the arguments */ + b = JS_VALUE_TO_PTR(s->cur_func); + if (b->has_arguments) { + int var_idx; + var_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM_arguments)); + emit_op(s, OP_arguments); + put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos); + } + + /* XXX: initialize the function name */ + b = JS_VALUE_TO_PTR(s->cur_func); + if (b->has_local_func_name) { + int var_idx; + /* XXX: */ + var_idx = add_var(s, b->func_name); + emit_op(s, OP_this_func); + put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos); + } + + while (s->token.val != '}') { + js_parse_source_element(s); + } + + if (js_is_live_code(s)) + emit_op(s, OP_return_undef); + + next_token(s); + + define_hoisted_functions(s, FALSE); + + /* save the bytecode to the function */ + b = JS_VALUE_TO_PTR(s->cur_func); + b->byte_code = s->byte_code; +} + +static void js_parse_program(JSParseState *s) +{ + JSFunctionBytecode *b; + + next_token(s); + + /* hidden variable for the return value */ + if (s->has_retval) { + s->eval_ret_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM__ret_)); + } + + while (s->token.val != TOK_EOF) { + js_parse_source_element(s); + } + + if (s->eval_ret_idx >= 0) { + emit_var(s, OP_get_loc, s->eval_ret_idx, s->pc2line_source_pos); + emit_op(s, OP_return); + } else { + emit_op(s, OP_return_undef); + } + + define_hoisted_functions(s, TRUE); + + /* save the bytecode to the function */ + b = JS_VALUE_TO_PTR(s->cur_func); + b->byte_code = s->byte_code; +} + +#define CVT_VAR_SIZE_MAX 16 + +typedef struct { + uint16_t new_var_idx; /* new local var index */ + uint8_t is_local; +} ConvertVarEntry; + +static void convert_ext_vars_to_local_vars_bytecode(JSParseState *s, + uint8_t *byte_code, int byte_code_len, + int var_start, const ConvertVarEntry *cvt_tab, + int tab_len) +{ + int pos, var_end, j, op, var_idx; + const JSOpCode *oi; + + var_end = var_start + tab_len; + pos = 0; + while (pos < byte_code_len) { + op = byte_code[pos]; + oi = &opcode_info[op]; + switch(op) { + case OP_get_var_ref: + case OP_put_var_ref: + case OP_get_var_ref_nocheck: + case OP_put_var_ref_nocheck: + var_idx = get_u16(byte_code + pos + 1); + if (var_idx >= var_start && var_idx < var_end) { + j = var_idx - var_start; + put_u16(byte_code + pos + 1, cvt_tab[j].new_var_idx); + if (cvt_tab[j].is_local) { + if (op == OP_get_var_ref || op == OP_get_var_ref_nocheck) { + byte_code[pos] = OP_get_loc; + } else { + byte_code[pos] = OP_put_loc; + } + } + } + break; + default: + break; + } + pos += oi->size; + } +} + +/* no allocation */ +static void convert_ext_vars_to_local_vars(JSParseState *s) +{ + JSValueArray *ext_vars; + JSFunctionBytecode *b; + JSByteArray *bc_arr; + JSValue var_name, decl; + int i0, i, j, var_idx, l; + ConvertVarEntry cvt_tab[CVT_VAR_SIZE_MAX]; + + b = JS_VALUE_TO_PTR(s->cur_func); + if (s->local_vars_len == 0 || b->ext_vars_len == 0) + return; + bc_arr = JS_VALUE_TO_PTR(b->byte_code); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + + /* do it by parts to save memory */ + j = 0; + for(i0 = 0; i0 < b->ext_vars_len; i0 += CVT_VAR_SIZE_MAX) { + l = min_int(b->ext_vars_len - i0, CVT_VAR_SIZE_MAX); + for(i = 0; i < l; i++) { + var_name = ext_vars->arr[2 * (i0 + i)]; + decl = ext_vars->arr[2 * (i0 + i) + 1]; + var_idx = find_var(s, var_name); + /* fail safe: we avoid arguments even if they cannot appear */ + if (var_idx >= b->arg_count) { + cvt_tab[i].new_var_idx = var_idx - b->arg_count; + cvt_tab[i].is_local = TRUE; + } else { + cvt_tab[i].new_var_idx = j; + cvt_tab[i].is_local = FALSE; + ext_vars->arr[2 * j] = var_name; + ext_vars->arr[2 * j + 1] = decl; + j++; + } + } + if (j != (i0 + l)) { + convert_ext_vars_to_local_vars_bytecode(s, bc_arr->buf, s->byte_code_len, + i0, cvt_tab, l); + } + } + b->ext_vars_len = j; +} + +/* prepare the analysis of the code starting at position 'pos' */ +static void compute_stack_size_push(JSParseState *s, + JSByteArray *arr, + uint8_t *explore_tab, + uint32_t pos, int stack_len) +{ + int short_stack_len; + +#if 0 + js_printf(s->ctx, "%5d: %d\n", pos, stack_len); +#endif + if (pos >= (uint32_t)arr->size) + js_parse_error(s, "bytecode buffer overflow (pc=%d)", pos); + /* XXX: could avoid the division */ + short_stack_len = 1 + ((unsigned)stack_len % 255); + if (explore_tab[pos] != 0) { + /* already explored: check that the stack size is consistent */ + if (explore_tab[pos] != short_stack_len) { + js_parse_error(s, "inconsistent stack size: %d %d (pc=%d)", explore_tab[pos] - 1, short_stack_len - 1, (int)pos); + } + } else { + explore_tab[pos] = short_stack_len; + /* may initiate a GC */ + PARSE_PUSH_INT(s, pos); + PARSE_PUSH_INT(s, stack_len); + } +} + +static void compute_stack_size(JSParseState *s, JSValue *pfunc) +{ + JSContext *ctx = s->ctx; + JSByteArray *explore_arr, *arr; + JSFunctionBytecode *b; + uint8_t *explore_tab; + JSValue *stack_top, explore_arr_val; + uint32_t pos; + int op, op_len, pos1, n_pop, stack_len; + const JSOpCode *oi; + JSGCRef explore_arr_val_ref; + + b = JS_VALUE_TO_PTR(*pfunc); + arr = JS_VALUE_TO_PTR(b->byte_code); + + explore_arr = js_alloc_byte_array(s->ctx, arr->size); + if (!explore_arr) + js_parse_error_mem(s); + + b = JS_VALUE_TO_PTR(*pfunc); + arr = JS_VALUE_TO_PTR(b->byte_code); + + explore_arr_val = JS_VALUE_FROM_PTR(explore_arr); + explore_tab = explore_arr->buf; + memset(explore_tab, 0, arr->size); + + JS_PUSH_VALUE(ctx, explore_arr_val); + + stack_top = ctx->sp; + + compute_stack_size_push(s, arr, explore_tab, 0, 0); + + while (ctx->sp < stack_top) { + PARSE_POP_INT(s, stack_len); + PARSE_POP_INT(s, pos); + + /* compute_stack_size_push may have initiated a GC */ + b = JS_VALUE_TO_PTR(*pfunc); + arr = JS_VALUE_TO_PTR(b->byte_code); + explore_arr = JS_VALUE_TO_PTR(explore_arr_val_ref.val); + explore_tab = explore_arr->buf; + + op = arr->buf[pos++]; + if (op == OP_invalid || op >= OP_COUNT) + js_parse_error(s, "invalid opcode (pc=%d)", (int)(pos - 1)); + oi = &opcode_info[op]; + op_len = oi->size; + if ((pos + op_len - 1) > arr->size) { + js_parse_error(s, "bytecode buffer overflow (pc=%d)", (int)(pos - 1)); + } + n_pop = oi->n_pop; + if (oi->fmt == OP_FMT_npop) + n_pop += get_u16(arr->buf + pos); + + if (stack_len < n_pop) { + js_parse_error(s, "stack underflow (pc=%d)", (int)(pos - 1)); + } + stack_len += oi->n_push - n_pop; + if (stack_len > b->stack_size) { + if (stack_len > JS_MAX_FUNC_STACK_SIZE) + js_parse_error(s, "stack overflow (pc=%d)", (int)(pos - 1)); + b->stack_size = stack_len; + } + switch(op) { + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_ret: + goto done; /* no code after */ + case OP_goto: + pos += get_u32(arr->buf + pos); + break; + case OP_if_true: + case OP_if_false: + pos1 = pos + get_u32(arr->buf + pos); + compute_stack_size_push(s, arr, explore_tab, pos1, stack_len); + pos += op_len - 1; + break; + case OP_gosub: + pos1 = pos + get_u32(arr->buf + pos); + compute_stack_size_push(s, arr, explore_tab, pos1, stack_len + 1); + pos += op_len - 1; + break; + default: + pos += op_len - 1; + break; + } + compute_stack_size_push(s, arr, explore_tab, pos, stack_len); + done: ; + } + + JS_POP_VALUE(ctx, explore_arr_val); + explore_arr = JS_VALUE_TO_PTR(explore_arr_val); + js_free(s->ctx, explore_arr); +} + +static void resolve_var_refs(JSParseState *s, JSValue *pfunc, JSValue *pparent_func) +{ + JSContext *ctx = s->ctx; + int i, decl, var_idx, arg_count, ext_vars_len; + JSValueArray *ext_vars; + JSValue var_name; + JSFunctionBytecode *b1, *b; + + b = JS_VALUE_TO_PTR(*pfunc); + if (b->ext_vars_len == 0) + return; + b1 = JS_VALUE_TO_PTR(*pparent_func); + arg_count = b1->arg_count; + + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + ext_vars_len = b->ext_vars_len; + + for(i = 0; i < ext_vars_len; i++) { + b = JS_VALUE_TO_PTR(*pfunc); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + var_name = ext_vars->arr[2 * i]; + var_idx = find_func_var(ctx, *pparent_func, var_name); + if (var_idx >= 0) { + if (var_idx < arg_count) { + decl = (JS_VARREF_KIND_ARG << 16) | var_idx; + } else { + decl = (JS_VARREF_KIND_VAR << 16) | (var_idx - arg_count); + } + } else { + var_idx = find_func_ext_var(s, *pparent_func, var_name); + if (var_idx < 0) { + /* the global type may be patched later */ + var_idx = add_func_ext_var(s, *pparent_func, var_name, + (JS_VARREF_KIND_GLOBAL << 16)); + } + decl = (JS_VARREF_KIND_VAR_REF << 16) | var_idx; + } + b = JS_VALUE_TO_PTR(*pfunc); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + ext_vars->arr[2 * i + 1] = JS_NewShortInt(decl); + } +} + +static void reset_parse_state(JSParseState *s, uint32_t input_pos, + JSValue cur_func) +{ + s->buf_pos = input_pos; + s->token.val = ' '; + + s->cur_func = cur_func; + s->byte_code = JS_NULL; + s->byte_code_len = 0; + s->last_opcode_pos = -1; + + s->pc2line_bit_len = 0; + s->pc2line_source_pos = 0; + + s->cpool_len = 0; + s->hoisted_code_len = 0; + + s->local_vars_len = 0; + + s->eval_ret_idx = -1; +} + +static void js_parse_local_functions(JSParseState *s, JSValue *pfunc) +{ + JSContext *ctx = s->ctx; + JSValue *pparent_func; + JSValueArray *cpool; + int err, cpool_pos; + JSValue func; + JSFunctionBytecode *b, *b1; + JSGCRef func_ref; + JSValue *stack_top; + + err = JS_StackCheck(ctx, 3); + if (err) + js_parse_error_stack_overflow(s); + stack_top = ctx->sp; + + *--ctx->sp = JS_NULL; /* parent_func */ + *--ctx->sp = *pfunc; /* func */ + *--ctx->sp = JS_NewShortInt(0); /* cpool_pos */ + + while (ctx->sp < stack_top) { + pparent_func = &ctx->sp[2]; + pfunc = &ctx->sp[1]; + cpool_pos = JS_VALUE_GET_INT(ctx->sp[0]); +#if 0 + JS_DumpValue(ctx, "func", *pfunc); + JS_DumpValue(ctx, "parent", *pparent_func); + JS_DumpValue(ctx, "cpool_pos", ctx->sp[0]); +#endif + if (cpool_pos == 0) { + b = JS_VALUE_TO_PTR(*pfunc); + + convert_ext_vars_to_local_vars(s); + + js_shrink_byte_array(ctx, &b->byte_code, s->byte_code_len); + js_shrink_value_array(ctx, &b->cpool, s->cpool_len); + js_shrink_value_array(ctx, &b->vars, s->local_vars_len); + js_shrink_byte_array(ctx, &b->pc2line, (s->pc2line_bit_len + 7) / 8); + + compute_stack_size(s, pfunc); + } + + b = JS_VALUE_TO_PTR(*pfunc); + if (b->cpool != JS_NULL) { + int cpool_size; + cpool = JS_VALUE_TO_PTR(b->cpool); + cpool_size = cpool->size; + for(; cpool_pos < cpool_size; cpool_pos++) { + b = JS_VALUE_TO_PTR(*pfunc); + cpool = JS_VALUE_TO_PTR(b->cpool); + func = cpool->arr[cpool_pos]; + if (!JS_IsPtr(func)) + continue; + b1 = JS_VALUE_TO_PTR(func); + if (b1->mtag != JS_MTAG_FUNCTION_BYTECODE) + continue; + + reset_parse_state(s, b1->source_pos, func); + + s->is_eval = FALSE; + s->is_repl = FALSE; + s->has_retval = FALSE; + + JS_PUSH_VALUE(ctx, func); + js_parse_function(s); + + /* parse a local function */ + err = JS_StackCheck(ctx, 3); + JS_POP_VALUE(ctx, func); + if (err) + js_parse_error_stack_overflow(s); + /* set the next cpool position */ + *ctx->sp = JS_NewShortInt(cpool_pos + 1); + + *--ctx->sp = *pfunc; /* parent_func */ + *--ctx->sp = func; /* func */ + *--ctx->sp = JS_NewShortInt(0); /* cpool_pos */ + goto next; + } + } + + if (*pparent_func != JS_NULL) { + resolve_var_refs(s, pfunc, pparent_func); + } + /* now we can shrink the external vars */ + b = JS_VALUE_TO_PTR(*pfunc); + js_shrink_value_array(ctx, &b->ext_vars, 2 * b->ext_vars_len); +#ifdef DUMP_FUNC_BYTECODE + dump_byte_code(ctx, b); +#endif + /* remove the stack entry */ + ctx->sp += 3; + ctx->stack_bottom = ctx->sp; + next: ; + } +} + +/* return the parsed value in s->token.value */ +/* XXX: use exact JSON white space definition */ +static int js_parse_json_value(JSParseState *s, int state, int dummy_param) +{ + JSContext *ctx = s->ctx; + const uint8_t *p; + JSValue val; + + PARSE_START2(); + + p = s->source_buf + s->buf_pos; + p += skip_spaces((const char *)p); + s->buf_pos = p - s->source_buf; + if ((*p >= '0' && *p <= '9') || *p == '-') { + double d; + JSByteArray *tmp_arr; + tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem)); + if (!tmp_arr) + js_parse_error_mem(s); + p = s->source_buf + s->buf_pos; + d = js_atod((const char *)p, (const char **)&p, 10, 0, + (JSATODTempMem *)tmp_arr->buf); + js_free(s->ctx, tmp_arr); + if (isnan(d)) + js_parse_error(s, "invalid number literal"); + val = JS_NewFloat64(s->ctx, d); + } else if (*p == 't' && + p[1] == 'r' && p[2] == 'u' && p[3] == 'e') { + p += 4; + val = JS_TRUE; + } else if (*p == 'f' && + p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') { + p += 5; + val = JS_FALSE; + } else if (*p == 'n' && + p[1] == 'u' && p[2] == 'l' && p[3] == 'l') { + p += 4; + val = JS_NULL; + } else if (*p == '\"') { + uint32_t pos; + pos = p + 1 - s->source_buf; + val = js_parse_string(s, &pos, '\"'); + p = s->source_buf + pos; + } else if (*p == '[') { + JSValue val2; + uint32_t idx; + + val = JS_NewArray(ctx, 0); + if (JS_IsException(val)) + js_parse_error_mem(s); + PARSE_PUSH_VAL(s, val); /* 'val' is not usable after this call */ + p = s->source_buf + s->buf_pos + 1; + p += skip_spaces((const char *)p); + if (*p != ']') { + idx = 0; + for(;;) { + s->buf_pos = p - s->source_buf; + PARSE_PUSH_INT(s, idx); + PARSE_CALL(s, 0, js_parse_json_value, 0); + PARSE_POP_INT(s, idx); + val2 = s->token.value; + val2 = JS_SetPropertyUint32(ctx, *ctx->sp, idx, val2); + if (JS_IsException(val2)) + js_parse_error_mem(s); + idx++; + p = s->source_buf + s->buf_pos; + p += skip_spaces((const char *)p); + if (*p != ',') + break; + p++; + } + } + if (*p != ']') + js_parse_error(s, "expecting ']'"); + p++; + PARSE_POP_VAL(s, val); + } else if (*p == '{') { + JSValue val2, prop; + uint32_t pos; + + val = JS_NewObject(ctx); + if (JS_IsException(val)) + js_parse_error_mem(s); + PARSE_PUSH_VAL(s, val); /* 'val' is not usable after this call */ + p = s->source_buf + s->buf_pos + 1; + p += skip_spaces((const char *)p); + if (*p != '}') { + for(;;) { + p += skip_spaces((const char *)p); + s->buf_pos = p - s->source_buf; + if (*p != '\"') + js_parse_error(s, "expecting '\"'"); + pos = p + 1 - s->source_buf; + prop = js_parse_string(s, &pos, '\"'); + prop = JS_ToPropertyKey(ctx, prop); + if (JS_IsException(prop)) + js_parse_error_mem(s); + p = s->source_buf + pos; + p += skip_spaces((const char *)p); + if (*p != ':') + js_parse_error(s, "expecting ':'"); + p++; + s->buf_pos = p - s->source_buf; + PARSE_PUSH_VAL(s, prop); + PARSE_CALL(s, 1, js_parse_json_value, 0); + val2 = s->token.value; + PARSE_POP_VAL(s, prop); + val2 = JS_DefinePropertyValue(ctx, *ctx->sp, prop, val2); + if (JS_IsException(val2)) + js_parse_error_mem(s); + p = s->source_buf + s->buf_pos; + p += skip_spaces((const char *)p); + if (*p != ',') + break; + p++; + } + } + if (*p != '}') + js_parse_error(s, "expecting '}'"); + p++; + PARSE_POP_VAL(s, val); + } else { + js_parse_error(s, "unexpected character"); + } + s->buf_pos = p - s->source_buf; + s->token.value = val; + return PARSE_STATE_RET; +} + +static JSValue js_parse_json(JSParseState *s) +{ + s->buf_pos = 0; + js_parse_call(s, PARSE_FUNC_js_parse_json_value, 0); + s->buf_pos += skip_spaces((const char *)(s->source_buf + s->buf_pos)); + if (s->buf_pos != s->buf_len) { + js_parse_error(s, "unexpected character"); + } + return s->token.value; +} + +/* source_str must be a string or JS_NULL. (input, input_len) is + meaningful only if source_str is JS_NULL. */ +static JSValue JS_Parse2(JSContext *ctx, JSValue source_str, + const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + JSParseState parse_state, *s; + JSFunctionBytecode *b; + JSValue top_func, *saved_sp; + JSGCRef top_func_ref, *saved_top_gc_ref; + uint8_t str_buf[5]; + + /* XXX: start gc at the start of parsing ? */ + /* XXX: if the parse state is too large, move it to JSContext */ + s = &parse_state; + memset(s, 0, sizeof(*s)); + + s->ctx = ctx; + ctx->parse_state = s; + s->source_str = JS_NULL; + s->filename_str = JS_NULL; + s->has_column = ((eval_flags & JS_EVAL_STRIP_COL) == 0); + + if (JS_IsPtr(source_str)) { + JSString *p = JS_VALUE_TO_PTR(source_str); + s->source_str = source_str; + s->buf_len = p->len; + s->source_buf = p->buf; + } else if (JS_VALUE_GET_SPECIAL_TAG(source_str) == JS_TAG_STRING_CHAR) { + s->buf_len = get_short_string(str_buf, source_str); + s->source_buf = str_buf; + } else { + s->buf_len = input_len; + s->source_buf = (const uint8_t *)input; + } + s->top_break = JS_NULL; + saved_top_gc_ref = ctx->top_gc_ref; + saved_sp = ctx->sp; + + if (setjmp(s->jmp_env)) { + int line_num, col_num; + JSValue val; + + ctx->parse_state = NULL; + ctx->top_gc_ref = saved_top_gc_ref; + ctx->sp = saved_sp; + ctx->stack_bottom = ctx->sp; + + line_num = get_line_col(&col_num, s->source_buf, + (eval_flags & (JS_EVAL_JSON | JS_EVAL_REGEXP)) ? + s->buf_pos : s->token.source_pos); + val = JS_ThrowError(ctx, JS_CLASS_SYNTAX_ERROR, "%s", s->error_msg); + build_backtrace(ctx, ctx->current_exception, filename, line_num + 1, col_num + 1, 0); + return val; + } + + if (eval_flags & JS_EVAL_JSON) { + top_func = js_parse_json(s); + } else if (eval_flags & JS_EVAL_REGEXP) { + top_func = js_parse_regexp(s, eval_flags >> JS_EVAL_REGEXP_FLAGS_SHIFT); + } else { + s->filename_str = JS_NewString(ctx, filename); + if (JS_IsException(s->filename_str)) + js_parse_error_mem(s); + + b = js_alloc_function_bytecode(ctx); + if (!b) + js_parse_error_mem(s); + b->filename = s->filename_str; + b->func_name = js_get_atom(ctx, JS_ATOM__eval_); + b->has_column = s->has_column; + top_func = JS_VALUE_FROM_PTR(b); + + reset_parse_state(s, 0, top_func); + + s->is_eval = TRUE; + s->has_retval = ((eval_flags & JS_EVAL_RETVAL) != 0); + s->is_repl = ((eval_flags & JS_EVAL_REPL) != 0); + + JS_PUSH_VALUE(ctx, top_func); + + js_parse_program(s); + + js_parse_local_functions(s, &top_func_ref.val); + + JS_POP_VALUE(ctx, top_func); + } + ctx->parse_state = NULL; + return top_func; +} + +/* warning: it is assumed that input[input_len] = '\0' */ +JSValue JS_Parse(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + return JS_Parse2(ctx, JS_NULL, input, input_len, filename, eval_flags); +} + +JSValue JS_Run(JSContext *ctx, JSValue val) +{ + JSFunctionBytecode *b; + JSGCRef val_ref; + int err; + + if (!JS_IsPtr(val)) + goto fail; + b = JS_VALUE_TO_PTR(val); + if (b->mtag != JS_MTAG_FUNCTION_BYTECODE) { + fail: + return JS_ThrowTypeError(ctx, "bytecode function expected"); + } + + val = js_closure(ctx, val, NULL); + if (JS_IsException(val)) + return val; + JS_PUSH_VALUE(ctx, val); + err = JS_StackCheck(ctx, 2); + JS_POP_VALUE(ctx, val); + if (err) + return JS_EXCEPTION; + JS_PushArg(ctx, val); + JS_PushArg(ctx, JS_NULL); + val = JS_Call(ctx, 0); + return val; +} + +/* warning: it is assumed that input[input_len] = '\0' */ +JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + JSValue val; + val = JS_Parse(ctx, input, input_len, filename, eval_flags); + if (JS_IsException(val)) + return val; + return JS_Run(ctx, val); +} + +/**********************************************************************/ +/* garbage collector */ + +/* return the size in bytes */ +static int get_mblock_size(const void *ptr) +{ + int mtag = ((JSMemBlockHeader *)ptr)->mtag; + int size; + switch(mtag) { + case JS_MTAG_OBJECT: + { + const JSObject *p = ptr; + size = offsetof(JSObject, u) + p->extra_size * JSW; + } + break; + case JS_MTAG_FLOAT64: + size = sizeof(JSFloat64); + break; + case JS_MTAG_STRING: + { + const JSString *p = ptr; + size = sizeof(JSString) + ((p->len + JSW) & ~(JSW - 1)); + } + break; + case JS_MTAG_BYTE_ARRAY: + { + const JSByteArray *p = ptr; + size = sizeof(JSByteArray) + ((p->size + JSW - 1) & ~(JSW - 1)); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray *p = ptr; + size = sizeof(JSValueArray) + p->size * sizeof(p->arr[0]); + } + break; + case JS_MTAG_FREE: + { + const JSFreeBlock *p = ptr; + size = sizeof(JSFreeBlock) + p->size * sizeof(JSWord); + } + break; + case JS_MTAG_VARREF: + { + const JSVarRef *p = ptr; + size = sizeof(JSVarRef); + if (p->is_detached) + size -= sizeof(JSValue); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + size = sizeof(JSFunctionBytecode); + break; + default: + size = 0; + assert(0); + } + return size; +} + +/* gc mark pass */ + +typedef struct { + JSContext *ctx; + JSValue *gsp; + JSValue *gs_bottom; + JSValue *gs_top; + BOOL overflow; +} GCMarkState; + +static BOOL mtag_has_references(int mtag) +{ + return (mtag == JS_MTAG_OBJECT || + mtag == JS_MTAG_VALUE_ARRAY || + mtag == JS_MTAG_VARREF || + mtag == JS_MTAG_FUNCTION_BYTECODE); +} + +static void gc_mark(GCMarkState *s, JSValue val) +{ + JSContext *ctx = s->ctx; + void *ptr; + JSMemBlockHeader *mb; + + if (!JS_IsPtr(val)) + return; + ptr = JS_VALUE_TO_PTR(val); + if (JS_IS_ROM_PTR(ctx, ptr)) + return; + mb = ptr; + if (mb->gc_mark) + return; + mb->gc_mark = 1; + if (mtag_has_references(mb->mtag)) { + if (mb->mtag == JS_MTAG_VALUE_ARRAY) { + /* value array are handled specifically to save stack space */ + if ((s->gsp - s->gs_bottom) < 2) { + s->overflow = TRUE; + } else { + *--s->gsp = 0; + *--s->gsp = val; + } + } else { + if ((s->gsp - s->gs_bottom) < 1) { + s->overflow = TRUE; + } else { + *--s->gsp = val; + } + } + } +} + +/* flush the GC mark stack */ +static void gc_mark_flush(GCMarkState *s) +{ + void *ptr; + JSMemBlockHeader *mb; + JSValue val; + + while (s->gsp < s->gs_top) { + val = *s->gsp++; + ptr = JS_VALUE_TO_PTR(val); + mb = ptr; + + switch(mb->mtag) { + case JS_MTAG_OBJECT: + { + const JSObject *p = ptr; + gc_mark(s, p->proto); + gc_mark(s, p->props); + switch(p->class_id) { + case JS_CLASS_CLOSURE: + { + int i; + gc_mark(s, p->u.closure.func_bytecode); + for(i = 0; i < p->extra_size - 1; i++) + gc_mark(s, p->u.closure.var_refs[i]); + } + break; + case JS_CLASS_C_FUNCTION: + if (p->extra_size > 1) + gc_mark(s, p->u.cfunc.params); + break; + case JS_CLASS_ARRAY: + gc_mark(s, p->u.array.tab); + break; + case JS_CLASS_ERROR: + gc_mark(s, p->u.error.message); + gc_mark(s, p->u.error.stack); + break; + case JS_CLASS_ARRAY_BUFFER: + gc_mark(s, p->u.array_buffer.byte_buffer); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + gc_mark(s, p->u.typed_array.buffer); + break; + case JS_CLASS_REGEXP: + gc_mark(s, p->u.regexp.source); + gc_mark(s, p->u.regexp.byte_code); + break; + } + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray *p = ptr; + int pos; + + pos = *s->gsp++; + + /* fast path to skip non objects */ + while (pos < p->size && !JS_IsPtr(p->arr[pos])) + pos++; + + if (pos < p->size) { + if ((pos + 1) < p->size) { + /* the next element needs to be scanned */ + *--s->gsp = pos + 1; + *--s->gsp = val; + } + /* mark the current element */ + gc_mark(s, p->arr[pos]); + } + } + break; + case JS_MTAG_VARREF: + { + const JSVarRef *p = ptr; + gc_mark(s, p->u.value); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + { + const JSFunctionBytecode *b = ptr; + gc_mark(s, b->func_name); + gc_mark(s, b->byte_code); + gc_mark(s, b->cpool); + gc_mark(s, b->vars); + gc_mark(s, b->ext_vars); + gc_mark(s, b->filename); + gc_mark(s, b->pc2line); + } + break; + default: + break; + } + } +} + +static void gc_mark_root(GCMarkState *s, JSValue val) +{ + gc_mark(s, val); + gc_mark_flush(s); +} + +/* return true if the memory block is marked i.e. it won't be freed by the GC */ +static BOOL gc_mb_is_marked(JSValue val) +{ + JSFreeBlock *b; + if (!JS_IsPtr(val)) + return FALSE; + b = (JSFreeBlock *)JS_VALUE_TO_PTR(val); + return b->gc_mark; +} + +static void gc_mark_all(JSContext *ctx, BOOL keep_atoms) +{ + GCMarkState s_s, *s = &s_s; + JSValue *sp, *sp_end; + + s->ctx = ctx; + /* initialize the GC stack */ + s->overflow = FALSE; + s->gs_top = ctx->sp; + s->gsp = s->gs_top; +#if 1 + s->gs_bottom = (JSValue *)ctx->heap_free; +#else + s->gs_bottom = s->gs_top - 3; /* TEST small stack space */ +#endif + + /* keep the atoms if they are in RAM (only used when compiling to file) */ + if ((uint8_t *)ctx->atom_table == ctx->heap_base && + keep_atoms) { + uint8_t *ptr; + for(ptr = (uint8_t *)ctx->atom_table; + ptr < (uint8_t *)(ctx->atom_table + JS_ATOM_END); + ptr += get_mblock_size(ptr)) { + gc_mark_root(s, JS_VALUE_FROM_PTR(ptr)); + } + } + + /* mark all the memory blocks */ + sp_end = ctx->class_proto + 2 * ctx->class_count; + for(sp = &ctx->current_exception; sp < sp_end; sp++) { + gc_mark_root(s, *sp); + } + + for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) { + gc_mark_root(s, *sp); + } + + { + JSGCRef *ref; + for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_mark_root(s, ref->val); + } + for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { + gc_mark_root(s, ref->val); + } + } + if (ctx->parse_state) { + JSParseState *ps = ctx->parse_state; + + gc_mark_root(s, ps->source_str); + gc_mark_root(s, ps->filename_str); + gc_mark_root(s, ps->token.value); + gc_mark_root(s, ps->cur_func); + gc_mark_root(s, ps->byte_code); + } + + /* if the mark stack overflowed, need to scan the heap */ + while (s->overflow) { + uint8_t *ptr; + int size; + JSMemBlockHeader *mb; + + s->overflow = FALSE; + + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + mb = (JSMemBlockHeader *)ptr; + if (mb->gc_mark && mtag_has_references(mb->mtag)) { + if (mb->mtag == JS_MTAG_VALUE_ARRAY) + *--s->gsp = 0; + *--s->gsp = JS_VALUE_FROM_PTR(ptr); + gc_mark_flush(s); + } + ptr += size; + } + } + + /* update the unique string table (its elements are considered as + weak string references) */ + if (!JS_IsNull(ctx->unique_strings)) { + JSValueArray *arr = JS_VALUE_TO_PTR(ctx->unique_strings); + int i, j; + + j = 0; + for(i = 0; i < arr->size; i++) { + if (gc_mb_is_marked(arr->arr[i])) { + arr->arr[j++] = arr->arr[i]; + } + } + ctx->unique_strings_len = j; + if (j > 0) { + arr->gc_mark = 1; + if (j < arr->size) { + /* shrink the array */ + set_free_block(&arr->arr[j], (arr->size - j) * sizeof(JSValue)); + arr->size = j; + } + } else { + arr->gc_mark = 0; + ctx->unique_strings = JS_NULL; + } + } + + /* update the weak references in the string position cache */ + { + int i; + JSStringPosCacheEntry *ce; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) { + ce = &ctx->string_pos_cache[i]; + if (!gc_mb_is_marked(ce->str)) + ce->str = JS_NULL; + } + } + + /* reset the gc marks and mark the free blocks as free */ + { + uint8_t *ptr, *ptr1; + int size; + JSFreeBlock *b; + + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + b = (JSFreeBlock *)ptr; + if (b->gc_mark) { + b->gc_mark = 0; + } else { + JSObject *p = (void *)ptr; + /* call the user finalizer if needed */ + if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER && + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) { + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque); + } + /* merge all the consecutive free blocks */ + ptr1 = ptr + size; + while (ptr1 < ctx->heap_free && ((JSFreeBlock *)ptr1)->gc_mark == 0) { + ptr1 += get_mblock_size(ptr1); + } + size = ptr1 - ptr; + set_free_block(b, size); + } + ptr += size; + } + } +} + +static JSValue js_value_from_pval(JSContext *ctx, JSValue *pval) +{ + return JS_VALUE_FROM_PTR(pval); +} + +static JSValue *js_value_to_pval(JSContext *ctx, JSValue val) +{ + return JS_VALUE_TO_PTR(val); +} + +static void gc_thread_pointer(JSContext *ctx, JSValue *pval) +{ + JSValue val; + JSValue *ptr; + + val = *pval; + if (!JS_IsPtr(val)) + return; + ptr = JS_VALUE_TO_PTR(val); + if (JS_IS_ROM_PTR(ctx, ptr)) + return; + /* gc_mark = 0 indicates a normal memory block header, gc_mark = 1 + indicates a pointer to another element */ + *pval = *ptr; + *ptr = js_value_from_pval(ctx, pval); +} + +static void gc_update_threaded_pointers(JSContext *ctx, + void *ptr, void *new_ptr) +{ + JSValue val, *pv; + + val = *(JSValue *)ptr; + if (JS_IsPtr(val)) { + /* update the threaded pointers to the node 'ptr' and + unthread it. */ + for(;;) { + pv = js_value_to_pval(ctx, val); + val = *pv; + *pv = JS_VALUE_FROM_PTR(new_ptr); + if (!JS_IsPtr(val)) + break; + } + *(JSValue *)ptr = val; + } +} + +static void gc_thread_block(JSContext *ctx, void *ptr) +{ + int mtag; + + mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_OBJECT: + { + JSObject *p = ptr; + gc_thread_pointer(ctx, &p->proto); + gc_thread_pointer(ctx, &p->props); + switch(p->class_id) { + case JS_CLASS_CLOSURE: + { + int i; + gc_thread_pointer(ctx, &p->u.closure.func_bytecode); + for(i = 0; i < p->extra_size - 1; i++) + gc_thread_pointer(ctx, &p->u.closure.var_refs[i]); + } + break; + case JS_CLASS_C_FUNCTION: + if (p->extra_size > 1) + gc_thread_pointer(ctx, &p->u.cfunc.params); + break; + case JS_CLASS_ARRAY: + gc_thread_pointer(ctx, &p->u.array.tab); + break; + case JS_CLASS_ERROR: + gc_thread_pointer(ctx, &p->u.error.message); + gc_thread_pointer(ctx, &p->u.error.stack); + break; + case JS_CLASS_ARRAY_BUFFER: + gc_thread_pointer(ctx, &p->u.array_buffer.byte_buffer); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + gc_thread_pointer(ctx, &p->u.typed_array.buffer); + break; + case JS_CLASS_REGEXP: + gc_thread_pointer(ctx, &p->u.regexp.source); + gc_thread_pointer(ctx, &p->u.regexp.byte_code); + break; + } + } + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *p = ptr; + int i; + for(i = 0; i < p->size; i++) { + gc_thread_pointer(ctx, &p->arr[i]); + } + } + break; + case JS_MTAG_VARREF: + { + JSVarRef *p = ptr; + gc_thread_pointer(ctx, &p->u.value); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = ptr; + gc_thread_pointer(ctx, &b->func_name); + gc_thread_pointer(ctx, &b->byte_code); + gc_thread_pointer(ctx, &b->cpool); + gc_thread_pointer(ctx, &b->vars); + gc_thread_pointer(ctx, &b->ext_vars); + gc_thread_pointer(ctx, &b->filename); + gc_thread_pointer(ctx, &b->pc2line); + } + break; + default: + break; + } +} + +/* Heap compaction using Jonkers algorithm */ +static void gc_compact_heap(JSContext *ctx) +{ + uint8_t *ptr, *new_ptr; + int size; + JSValue *sp, *sp_end; + + /* thread all the external pointers */ + sp_end = ctx->class_proto + 2 * ctx->class_count; + for(sp = &ctx->unique_strings; sp < sp_end; sp++) { + gc_thread_pointer(ctx, sp); + } + { + int i; + JSStringPosCacheEntry *ce; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) { + ce = &ctx->string_pos_cache[i]; + gc_thread_pointer(ctx, &ce->str); + } + } + + for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) { + gc_thread_pointer(ctx, sp); + } + + { + JSGCRef *ref; + for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_thread_pointer(ctx, &ref->val); + } + for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { + gc_thread_pointer(ctx, &ref->val); + } + } + + if (ctx->parse_state) { + JSParseState *ps = ctx->parse_state; + + gc_thread_pointer(ctx, &ps->source_str); + gc_thread_pointer(ctx, &ps->filename_str); + gc_thread_pointer(ctx, &ps->token.value); + gc_thread_pointer(ctx, &ps->cur_func); + gc_thread_pointer(ctx, &ps->byte_code); + } + + /* pass 1: thread the pointers and update the previous ones */ + new_ptr = ctx->heap_base; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, new_ptr); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + gc_thread_block(ctx, ptr); + new_ptr += size; + } + ptr += size; + } + + /* pass 2: update the threaded pointers and move the block to its + final position */ + new_ptr = ctx->heap_base; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, new_ptr); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + if (new_ptr != ptr) { + memmove(new_ptr, ptr, size); + } + new_ptr += size; + } + ptr += size; + } + ctx->heap_free = new_ptr; + + /* update the source pointer in the parser */ + if (ctx->parse_state) { + JSParseState *ps = ctx->parse_state; + if (JS_IsPtr(ps->source_str)) { + JSString *p = JS_VALUE_TO_PTR(ps->source_str); + ps->source_buf = p->buf; + } + } + + /* rehash the object properties */ + /* XXX: try to do it in the previous pass (add a specific tag ?) */ + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) == JS_MTAG_OBJECT) { + js_rehash_props(ctx, (JSObject *)ptr, TRUE); + } + ptr += size; + } +} + +static void JS_GC2(JSContext *ctx, BOOL keep_atoms) +{ +#ifdef DUMP_GC + js_printf(ctx, "GC : heap size=%u/%u stack_size=%u\n", + (uint32_t)(ctx->heap_free - ctx->heap_base), + (uint32_t)(ctx->stack_top - ctx->heap_base), + (uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp)); +#endif +#if defined(DEBUG_GC) + /* reduce the dummy block size at each GC to change the addresses + after compaction */ + /* XXX: only works a finite number of times */ + { + JSByteArray *arr; + if (JS_IsPtr(ctx->dummy_block)) { + arr = JS_VALUE_TO_PTR(ctx->dummy_block); + if (arr->size >= 8) { + js_shrink_byte_array(ctx, &ctx->dummy_block, arr->size - 4); + if (arr->size == 4) { + js_printf(ctx, "WARNING: debug GC: no longer modifying the addresses\n"); + } + } + } + } +#endif + gc_mark_all(ctx, keep_atoms); + gc_compact_heap(ctx); +#ifdef DUMP_GC + js_printf(ctx, "AFTER: heap size=%u/%u stack_size=%u\n", + (uint32_t)(ctx->heap_free - ctx->heap_base), + (uint32_t)(ctx->stack_top - ctx->heap_base), + (uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp)); +#endif +} + +void JS_GC(JSContext *ctx) +{ + JS_GC2(ctx, TRUE); +} + +/* bytecode saving and loading */ + +#define JS_BYTECODE_VERSION_32 0x0001 +/* bit 15 of bytecode version is a 64-bit indicator */ +#define JS_BYTECODE_VERSION (JS_BYTECODE_VERSION_32 | ((JSW & 8) << 12)) + +void JS_PrepareBytecode(JSContext *ctx, + JSBytecodeHeader *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code) +{ + JSGCRef eval_code_ref; + int i; + + /* remove all the objects except the compiled code */ + ctx->empty_props = JS_NULL; + for(i = 0; i < ctx->class_count; i++) { + ctx->class_proto[i] = JS_NULL; + ctx->class_obj[i] = JS_NULL; + } + ctx->global_obj = JS_NULL; +#ifdef DEBUG_GC + ctx->dummy_block = JS_NULL; +#endif + + JS_PUSH_VALUE(ctx, eval_code); + JS_GC2(ctx, FALSE); + JS_POP_VALUE(ctx, eval_code); + + hdr->magic = JS_BYTECODE_MAGIC; + hdr->version = JS_BYTECODE_VERSION; + hdr->base_addr = (uintptr_t)ctx->heap_base; + hdr->unique_strings = ctx->unique_strings; + hdr->main_func = eval_code; + + *pdata_buf = ctx->heap_base; + *pdata_len = ctx->heap_free - ctx->heap_base; +} + +#if JSW == 8 + +typedef uint32_t JSValue_32; +typedef uint32_t JSWord_32; + +#define JS_MB_HEADER_32 \ + JSWord_32 gc_mark: 1; \ + JSWord_32 mtag: (JS_MTAG_BITS - 1) + +#define JS_MB_PAD_32(n) (32 - (n)) + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS); +} JSMemBlockHeader_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS); + JSValue_32 arr[]; +} JSValueArray_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS); + uint8_t buf[]; +} JSByteArray_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS); + /* unaligned 64 bit access in 32-bit mode */ + struct __attribute__((packed)) { + double dval; + } u; +} JSFloat64_32; + +#define JS_STRING_LEN_MAX_32 ((1 << (32 - JS_MTAG_BITS - 3)) - 1) + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 is_unique: 1; + JSWord_32 is_ascii: 1; + /* true if the string content represents a number, only meaningful + is is_unique = true */ + JSWord_32 is_numeric: 1; + JSWord_32 len: JS_MB_PAD_32(JS_MTAG_BITS + 3); + uint8_t buf[]; +} JSString_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 has_arguments : 1; /* only used during parsing */ + JSWord_32 has_local_func_name : 1; /* only used during parsing */ + JSWord_32 has_column : 1; /* column debug info is present */ + JSWord_32 arg_count : 16; + JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS + 3 + 16); + + JSValue_32 func_name; /* JS_NULL if anonymous function */ + JSValue_32 byte_code; /* JS_NULL if the function is not parsed yet */ + JSValue_32 cpool; /* constant pool */ + JSValue_32 vars; /* only for debug */ + JSValue_32 ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */ + uint16_t stack_size; /* maximum stack size */ + uint16_t ext_vars_len; /* XXX: only used during parsing */ + JSValue_32 filename; /* filename in which the function is defined */ + JSValue_32 pc2line; /* JSByteArray or JS_NULL if not initialized */ + uint32_t source_pos; /* only used during parsing (XXX: shrink) */ +} JSFunctionBytecode_32; + +/* warning: ptr1 and ptr may overlap. However there is always: ptr1 <= ptr. Return 0 if OK. */ +static int convert_mblock_64to32(void *ptr1, const void *ptr) +{ + int mtag, i; + + mtag = ((JSMemBlockHeader*)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FUNCTION_BYTECODE: + { + const JSFunctionBytecode *b = ptr; + JSFunctionBytecode_32 *b1 = ptr1; + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->has_arguments = b->has_arguments; + b1->has_local_func_name = b->has_local_func_name; + b1->has_column = b->has_column; + b1->arg_count = b->arg_count; + b1->dummy = 0; + b1->func_name = b->func_name; + b1->byte_code = b->byte_code; + b1->cpool = b->cpool; + b1->vars = b->vars; + b1->ext_vars = b->ext_vars; + b1->stack_size = b->stack_size; + b1->ext_vars_len = b->ext_vars_len; + b1->filename = b->filename; + b1->pc2line = b->pc2line; + b1->source_pos = b->source_pos; + } + break; + case JS_MTAG_FLOAT64: + { + const JSFloat64 *b = ptr; + JSFloat64_32 *b1 = ptr1; + + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->dummy = 0; + b1->u.dval = b->u.dval; + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray *b = ptr; + JSValueArray_32 *b1 = ptr1; + + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->size = b->size; /* no test needed as long as JS_VALUE_ARRAY_SIZE_MAX is identical */ + for(i = 0; i < b1->size; i++) + b1->arr[i] = b->arr[i]; + } + break; + case JS_MTAG_BYTE_ARRAY: + { + const JSByteArray *b = ptr; + JSByteArray_32 *b1 = ptr1; + + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->size = b->size; /* no test needed as long as JS_BYTE_ARRAY_SIZE_MAX is identical */ + memmove(b1->buf, b->buf, b1->size); + } + break; + case JS_MTAG_STRING: + { + const JSString *b = ptr; + JSString_32 *b1 = ptr1; + + if (b->len > JS_STRING_LEN_MAX_32) + return -1; + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->is_unique = b->is_unique; + b1->is_ascii = b->is_ascii; + b1->is_numeric = b->is_numeric; + b1->len = b->len; + memmove(b1->buf, b->buf, b1->len + 1); + } + break; + default: + abort(); + } + return 0; +} + +/* return the size in bytes */ +static int get_mblock_size_32(const void *ptr) +{ + int mtag = ((JSMemBlockHeader_32 *)ptr)->mtag; + int size; + switch(mtag) { + case JS_MTAG_FLOAT64: + size = sizeof(JSFloat64_32); + break; + case JS_MTAG_STRING: + { + const JSString_32 *p = ptr; + size = sizeof(JSString_32) + ((p->len + 4) & ~(4 - 1)); + } + break; + case JS_MTAG_BYTE_ARRAY: + { + const JSByteArray_32 *p = ptr; + size = sizeof(JSByteArray_32) + ((p->size + 4 - 1) & ~(4 - 1)); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray_32 *p = ptr; + size = sizeof(JSValueArray_32) + p->size * sizeof(p->arr[0]); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + size = sizeof(JSFunctionBytecode_32); + break; + default: + size = 0; + assert(0); + } + return size; +} + +/* Compact and convert a 64 bit heap to a 32 bit heap at offset + 0. Only used for code compilation. Return 0 if OK. */ +static int gc_compact_heap_64to32(JSContext *ctx) +{ + uint8_t *ptr; + int size, size_32; + uintptr_t new_offset; + + gc_thread_pointer(ctx, &ctx->unique_strings); + + /* thread all the external pointers */ + { + JSGCRef *ref; + /* necessary because JS_PUSH_VAL() is called before + gc_compact_heap_64to32() */ + for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_thread_pointer(ctx, &ref->val); + } + } + + /* pass 1: thread the pointers and update the previous ones */ + new_offset = 0; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + gc_thread_block(ctx, ptr); + size_32 = get_mblock_size_32(ptr); + new_offset += size_32; + } + ptr += size; + } + + /* pass 2: update the threaded pointers and move the block to its + final position */ + new_offset = 0; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + size_32 = get_mblock_size_32(ptr); + if (convert_mblock_64to32(ctx->heap_base + new_offset, ptr)) + return -1; + new_offset += size_32; + } + ptr += size; + } + ctx->heap_free = ctx->heap_base + new_offset; + return 0; +} + +#ifdef JS_USE_SHORT_FLOAT + +static int expand_short_float(JSContext *ctx, JSValue *pval) +{ + JSFloat64 *f; + if (JS_IsShortFloat(*pval)) { + f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64); + if (!f) + return -1; + f->u.dval = js_get_short_float(*pval); + *pval = JS_VALUE_FROM_PTR(f); + } + return 0; +} + +/* Expand all the short floats to JSFloat64 structures. Return < 0 if + not enough memory. */ +static int expand_short_floats(JSContext *ctx) +{ + uint8_t *ptr, *p_end; + int mtag, size; + + ptr = ctx->heap_base; + p_end = ctx->heap_free; + while (ptr < p_end) { + size = get_mblock_size(ptr); + mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FUNCTION_BYTECODE: + /* we assume no short floats here */ + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *p = (JSValueArray *)ptr; + int i; + for(i = 0; i < p->size; i++) { + if (expand_short_float(ctx, &p->arr[i])) + return -1; + } + } + break; + case JS_MTAG_STRING: + case JS_MTAG_FLOAT64: + case JS_MTAG_BYTE_ARRAY: + break; + default: + abort(); + } + ptr += size; + } + return 0; +} + +#endif /* JS_USE_SHORT_FLOAT */ + +int JS_PrepareBytecode64to32(JSContext *ctx, + JSBytecodeHeader32 *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code) +{ + JSGCRef eval_code_ref; + int i; + + /* remove all the objects except the compiled code */ + ctx->empty_props = JS_NULL; + for(i = 0; i < ctx->class_count; i++) { + ctx->class_proto[i] = JS_NULL; + ctx->class_obj[i] = JS_NULL; + } + ctx->global_obj = JS_NULL; +#ifdef DEBUG_GC + ctx->dummy_block = JS_NULL; +#endif + + JS_PUSH_VALUE(ctx, eval_code); +#ifdef JS_USE_SHORT_FLOAT + JS_GC2(ctx, FALSE); + if (expand_short_floats(ctx)) + return -1; +#else + gc_mark_all(ctx, FALSE); +#endif + if (gc_compact_heap_64to32(ctx)) + return -1; + JS_POP_VALUE(ctx, eval_code); + + hdr->magic = JS_BYTECODE_MAGIC; + hdr->version = JS_BYTECODE_VERSION_32; + hdr->base_addr = 0; + hdr->unique_strings = ctx->unique_strings; + hdr->main_func = eval_code; + + *pdata_buf = ctx->heap_base; + *pdata_len = ctx->heap_free - ctx->heap_base; + /* ensure that JS_FreeContext() will do nothing */ + ctx->heap_free = ctx->heap_base; + return 0; +} +#endif /* JSW == 8 */ + +BOOL JS_IsBytecode(const uint8_t *buf, size_t buf_len) +{ + const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf; + return (buf_len >= sizeof(*hdr) && hdr->magic == JS_BYTECODE_MAGIC); +} + +typedef struct { + JSContext *ctx; + uintptr_t offset; + BOOL update_atoms; +} BCRelocState; + +static void bc_reloc_value(BCRelocState *s, JSValue *pval) +{ + JSContext *ctx = s->ctx; + JSString *p; + JSValue val, str; + + val = *pval; + if (JS_IsPtr(val)) { + val += s->offset; + + /* unique strings must be unique, so modify the unique string + value if it already exists in the context */ + if (s->update_atoms) { + p = JS_VALUE_TO_PTR(val); + if (p->mtag == JS_MTAG_STRING && p->is_unique) { + const JSValueArray *arr1; + int a, i; + for(i = 0; i < ctx->n_rom_atom_tables; i++) { + arr1 = ctx->rom_atom_tables[i]; + str = find_atom(ctx, &a, arr1, arr1->size, val); + if (!JS_IsNull(str)) { + val = str; + break; + } + } + } + } + *pval = val; + } +} + +int JS_RelocateBytecode2(JSContext *ctx, JSBytecodeHeader *hdr, + uint8_t *buf, uint32_t buf_len, + uintptr_t new_base_addr, BOOL update_atoms) +{ + uint8_t *ptr, *p_end; + int size, mtag; + BCRelocState ss, *s = &ss; + + if (hdr->magic != JS_BYTECODE_MAGIC) + return -1; + if (hdr->version != JS_BYTECODE_VERSION) + return -1; + + /* XXX: add atom checksum to avoid problems if the stdlib is + modified */ + s->ctx = ctx; + s->offset = new_base_addr - hdr->base_addr; + s->update_atoms = update_atoms; + + bc_reloc_value(s, &hdr->unique_strings); + bc_reloc_value(s, &hdr->main_func); + + ptr = buf; + p_end = buf + buf_len; + while (ptr < p_end) { + size = get_mblock_size(ptr); + mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = (JSFunctionBytecode *)ptr; + bc_reloc_value(s, &b->func_name); + bc_reloc_value(s, &b->byte_code); + bc_reloc_value(s, &b->cpool); + bc_reloc_value(s, &b->vars); + bc_reloc_value(s, &b->ext_vars); + bc_reloc_value(s, &b->filename); + bc_reloc_value(s, &b->pc2line); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *p = (JSValueArray *)ptr; + int i; + for(i = 0; i < p->size; i++) { + bc_reloc_value(s, &p->arr[i]); + } + } + break; + case JS_MTAG_STRING: + case JS_MTAG_FLOAT64: + case JS_MTAG_BYTE_ARRAY: + break; + default: + abort(); + } + ptr += size; + } + hdr->base_addr = new_base_addr; + return 0; +} + +/* Relocate the bytecode in 'buf' so that it can be executed + later. Return 0 if OK, != 0 if error */ +int JS_RelocateBytecode(JSContext *ctx, + uint8_t *buf, uint32_t buf_len) +{ + uint8_t *data_ptr; + + if (buf_len < sizeof(JSBytecodeHeader)) + return -1; + data_ptr = buf + sizeof(JSBytecodeHeader); + return JS_RelocateBytecode2(ctx, (JSBytecodeHeader *)buf, + data_ptr, + buf_len - sizeof(JSBytecodeHeader), + (uintptr_t)data_ptr, TRUE); +} + +/* Load the precompiled bytecode from 'buf'. 'buf' must be allocated + as long as the JSContext exists. Use JS_Run() to execute + it. warning: the bytecode is not checked so it should come from a + trusted source. */ +JSValue JS_LoadBytecode(JSContext *ctx, const uint8_t *buf) +{ + const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf; + + if (ctx->unique_strings_len != 0) + return JS_ThrowInternalError(ctx, "no atom must be defined in RAM"); + /* XXX: could stack atom_tables */ + if (ctx->n_rom_atom_tables >= N_ROM_ATOM_TABLES_MAX) + return JS_ThrowInternalError(ctx, "too many rom atom tables"); + if (hdr->magic != JS_BYTECODE_MAGIC) + return JS_ThrowInternalError(ctx, "invalid bytecode magic"); + if ((hdr->version & 0x8000) != (JS_BYTECODE_VERSION & 0x8000)) + return JS_ThrowInternalError(ctx, "bytecode not saved for %d-bit", JSW * 8); + if (hdr->version != JS_BYTECODE_VERSION) + return JS_ThrowInternalError(ctx, "invalid bytecode version"); + if (hdr->base_addr != (uintptr_t)(hdr + 1)) + return JS_ThrowInternalError(ctx, "bytecode not relocated"); + ctx->rom_atom_tables[ctx->n_rom_atom_tables++] = (JSValueArray *)JS_VALUE_TO_PTR(hdr->unique_strings); + return hdr->main_func; +} + +/**********************************************************************/ +/* runtime */ + +JSValue js_function_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + StringBuffer b_s, *b = &b_s; + JSValue val; + int i, n; + + argc &= ~FRAME_CF_CTOR; + string_buffer_push(ctx, b, 0); + string_buffer_puts(ctx, b, "(function anonymous("); + n = argc - 1; + for(i = 0; i < n; i++) { + if (i != 0) { + string_buffer_putc(ctx, b, ','); + } + if (string_buffer_concat(ctx, b, argv[i])) + goto done; + } + string_buffer_puts(ctx, b, "\n) {\n"); + if (n >= 0) { + if (string_buffer_concat(ctx, b, argv[n])) + goto done; + } + string_buffer_puts(ctx, b, "\n})"); + done: + val = string_buffer_pop(ctx, b); + if (JS_IsException(val)) + return val; + val = JS_Parse2(ctx, val, NULL, 0, "", JS_EVAL_RETVAL); + if (JS_IsException(val)) + return val; + return JS_Run(ctx, val); +} + +JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSGCRef obj_ref; + + if (!JS_IsPtr(*this_val)) { + if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC) + goto fail; + return JS_UNDEFINED; + } else { + JSObject *p = JS_VALUE_TO_PTR(*this_val); + if (p->mtag != JS_MTAG_OBJECT) + goto fail; + if (p->class_id == JS_CLASS_CLOSURE) { + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return obj; + } else if (p->class_id == JS_CLASS_C_FUNCTION) { + /* for C constructors, the prototype property is already present */ + return JS_UNDEFINED; + } else { + fail: + return JS_ThrowTypeError(ctx, "not a function"); + } + JS_PUSH_VALUE(ctx, obj); + JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_constructor), + *this_val); + JS_POP_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, obj); + JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype), + obj); + JS_POP_VALUE(ctx, obj); + } + return obj; +} + +JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (!JS_IsFunctionObject(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a function"); + + JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype), + argv[0]); + return JS_UNDEFINED; +} + +JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_name) +{ + JSFunctionBytecode *b; + JSValue ret = js_function_get_length_name1(ctx, this_val, is_name, &b); + if (JS_IsNull(ret)) + return JS_ThrowTypeError(ctx, "not a function"); + return ret; +} + +JSValue js_function_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue str, val; + JSGCRef str_ref; + + str = js_function_get_length_name(ctx, this_val, 0, NULL, 1); + if (JS_IsException(str)) + return str; + JS_PUSH_VALUE(ctx, str); + val = JS_NewString(ctx, "function "); + JS_POP_VALUE(ctx, str); + str = JS_ConcatString(ctx, val, str); + JS_PUSH_VALUE(ctx, str); + val = JS_NewString(ctx, "() {\n [native code]\n}"); + JS_POP_VALUE(ctx, str); + return JS_ConcatString(ctx, str, val); +} + +JSValue js_function_call(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int i; + argc = max_int(argc, 1); + if (JS_StackCheck(ctx, argc + 1)) + return JS_EXCEPTION; + for(i = 0; i < argc - 1; i++) + JS_PushArg(ctx, argv[argc - 1 - i]); + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, argv[0]); + /* we avoid recursing on the C stack */ + return JS_NewTailCall(argc - 1); +} + +JSValue js_function_apply(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValueArray *arr; + JSObject *p; + int len, i; + p = js_get_object_class(ctx, argv[1], JS_CLASS_ARRAY); + if (!p) + return JS_ThrowTypeError(ctx, "not an array"); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + len = p->u.array.len; + if (len > JS_MAX_ARGC) + return JS_ThrowTypeError(ctx, "too many call arguments"); + if (JS_StackCheck(ctx, len + 2)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(argv[1]); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < len; i++) + JS_PushArg(ctx, arr->arr[len - 1 - i]); + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, argv[0]); + /* we avoid recursing on the C stack */ + return JS_NewTailCall(len); +} + +JSValue js_function_bind(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int arg_count; + JSValueArray *arr; + int i; + + arg_count = max_int(argc - 1, 0); + arr = js_alloc_value_array(ctx, 0, 2 + arg_count); + if (!arr) + return JS_EXCEPTION; + /* arr[0] = func, arr[1] = this */ + arr->arr[0] = *this_val; + for(i = 0; i < arg_count + 1; i++) + arr->arr[1 + i] = argv[i]; + return JS_NewCFunctionParams(ctx, JS_CFUNCTION_bound, JS_VALUE_FROM_PTR(arr)); +} + +/* XXX: handle constructor case */ +JSValue js_function_bound(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, JSValue params) +{ + JSValueArray *arr; + JSGCRef params_ref; + int i, err, size, argc2; + + arr = JS_VALUE_TO_PTR(params); + size = arr->size; + JS_PUSH_VALUE(ctx, params); + err = JS_StackCheck(ctx, size + argc); + JS_POP_VALUE(ctx, params); + if (err) + return JS_EXCEPTION; + argc2 = size - 2 + argc; + if (argc2 > JS_MAX_ARGC) + return JS_ThrowTypeError(ctx, "too many call arguments"); + arr = JS_VALUE_TO_PTR(params); + for(i = argc - 1; i >= 0; i--) + JS_PushArg(ctx, argv[i]); + for(i = size - 1; i >= 2; i--) { + JS_PushArg(ctx, arr->arr[i]); + } + JS_PushArg(ctx, arr->arr[0]); /* func */ + JS_PushArg(ctx, arr->arr[1]); /* this_val */ + /* we avoid recursing on the C stack */ + return JS_NewTailCall(argc2); +} + +/**********************************************************************/ + +JSValue js_number_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + if (argc & FRAME_CF_CTOR) + return JS_ThrowTypeError(ctx, "number constructor not supported"); + if (argc == 0) { + return JS_NewShortInt(0); + } else { + if (JS_ToNumber(ctx, &d, argv[0])) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, d); + } +} + +static int js_thisNumberValue(JSContext *ctx, double *pres, JSValue val) +{ + if (!JS_IsNumber(ctx, val)) { + JS_ThrowTypeError(ctx, "not a number"); + return -1; + } + return JS_ToNumber(ctx, pres, val); +} + +JSValue js_number_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int radix, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0])) { + radix = 10; + } else { + if (JS_ToInt32Sat(ctx, &radix, argv[0])) + return JS_EXCEPTION; + if (radix < 2 || radix > 36) + return JS_ThrowRangeError(ctx, "radix must be between 2 and 36"); + } + /* cannot fail */ + flags = JS_DTOA_FORMAT_FREE; + if (radix != 10) + flags |= JS_DTOA_EXP_DISABLED; + return js_dtoa2(ctx, d, radix, 0, flags); +} + +JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int f, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &f, argv[0])) + return JS_EXCEPTION; + if (f < 0 || f > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + if (fabs(d) >= 1e21) { + flags = JS_DTOA_FORMAT_FREE; + } else { + flags = JS_DTOA_FORMAT_FRAC; + } + return js_dtoa2(ctx, d, 10, f, flags); +} + +JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int f, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &f, argv[0])) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0]) || !isfinite(d)) { + f = 0; + flags = JS_DTOA_FORMAT_FREE; + } else { + if (f < 0 || f > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + f++; + flags = JS_DTOA_FORMAT_FIXED; + } + return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED); +} + +JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int p, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0])) { + flags = JS_DTOA_FORMAT_FREE; + p = 0; + } else { + if (JS_ToInt32Sat(ctx, &p, argv[0])) + return JS_EXCEPTION; + if (!isfinite(d)) { + flags = JS_DTOA_FORMAT_FREE; + } else { + if (p < 1 || p > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + flags = JS_DTOA_FORMAT_FIXED; + } + } + return js_dtoa2(ctx, d, 10, p, flags); +} + +JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int radix; + double d; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &radix, argv[1])) + return JS_EXCEPTION; + if (radix != 0 && (radix < 2 || radix > 36)) { + d = NAN; + } else { + if (js_atod1(ctx, &d, argv[0], radix, JS_ATOD_INT_ONLY)) + return JS_EXCEPTION; + } + return JS_NewFloat64(ctx, d); +} + +JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + if (js_atod1(ctx, &d, argv[0], 10, 0)) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, d); +} + +/**********************************************************************/ + +JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (argc & FRAME_CF_CTOR) + return JS_ThrowTypeError(ctx, "Boolean constructor not supported"); + return JS_NewBool(JS_ToBool(ctx, argv[0])); +} + +/**********************************************************************/ + +JSValue js_string_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int len; + + if (!JS_IsString(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a string"); + len = js_string_len(ctx, *this_val); + return JS_NewShortInt(len); +} + +JSValue js_string_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return JS_UNDEFINED; /* ignored */ +} + +JSValue js_string_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int len, start, end; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + len = js_string_len(ctx, *this_val); + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + end = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + return js_sub_string(ctx, *this_val, start, max_int(end, start)); +} + +JSValue js_string_substring(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int a, b, start, end, len; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + len = js_string_len(ctx, *this_val); + if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, 0)) + return JS_EXCEPTION; + b = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, len, 0)) + return JS_EXCEPTION; + } + if (a < b) { + start = a; + end = b; + } else { + start = b; + end = a; + } + return js_sub_string(ctx, *this_val, start, end); +} + +JSValue js_string_charAt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSValue ret; + int idx, c; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &idx, argv[0])) + return JS_EXCEPTION; + if (idx < 0) + goto ret_undef; + c = string_getcp(ctx, *this_val, idx, (magic == magic_codePointAt)); + if (c == -1) { + ret_undef: + if (magic == magic_charCodeAt) + ret = JS_NewFloat64(ctx, NAN); + else if (magic == magic_charAt) + ret = js_get_atom(ctx, JS_ATOM_empty); + else + ret = JS_UNDEFINED; + } else { + if (magic == magic_charCodeAt || magic == magic_codePointAt) + ret = JS_NewShortInt(c); + else + ret = JS_NewStringChar(c); + } + // dump_string_pos_cache(ctx); + return ret; +} + +JSValue js_string_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (argc & FRAME_CF_CTOR) + return JS_ThrowTypeError(ctx, "string constructor not supported"); + if (argc <= 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } else { + return JS_ToString(ctx, argv[0]); + } +} + +JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_fromCodePoint) +{ + int i; + StringBuffer b_s, *b = &b_s; + + string_buffer_push(ctx, b, 0); + for(i = 0; i < argc; i++) { + int c; + if (JS_ToInt32(ctx, &c, argv[i])) + goto fail; + if (is_fromCodePoint) { + if (c < 0 || c > 0x10ffff) { + JS_ThrowRangeError(ctx, "invalid code point"); + goto fail; + } + } else { + c &= 0xffff; + } + if (string_buffer_putc(ctx, b, c)) + break; + } + return string_buffer_pop(ctx, b); + fail: + string_buffer_pop(ctx, b); + return JS_EXCEPTION; +} + +JSValue js_string_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int i; + StringBuffer b_s, *b = &b_s; + JSValue r; + + r = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(r)) + return JS_EXCEPTION; + string_buffer_push(ctx, b, 0); + if (string_buffer_concat(ctx, b, r)) + goto done; + + for (i = 0; i < argc; i++) { + if (string_buffer_concat(ctx, b, argv[i])) + goto done; + } + done: + return string_buffer_pop(ctx, b); +} + +JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int lastIndexOf) +{ + int i, len, v_len, pos, start, stop, ret, inc, j; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + len = js_string_len(ctx, *this_val); + v_len = js_string_len(ctx, argv[0]); + if (lastIndexOf) { + pos = len - v_len; + if (argc > 1) { + double d; + if (JS_ToNumber(ctx, &d, argv[1])) + goto fail; + if (!isnan(d)) { + if (d <= 0) + pos = 0; + else if (d < pos) + pos = d; + } + } + start = pos; + stop = 0; + inc = -1; + } else { + pos = 0; + if (argc > 1) { + if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) + goto fail; + } + start = pos; + stop = len - v_len; + inc = 1; + } + ret = -1; + if (len >= v_len && inc * (stop - start) >= 0) { + for (i = start;; i += inc) { + for(j = 0; j < v_len; j++) { + if (string_getc(ctx, *this_val, i + j) != string_getc(ctx, argv[0], j)) { + goto next; + } + } + ret = i; + break; + next: + if (i == stop) + break; + } + } + return JS_NewShortInt(ret); + +fail: + return JS_EXCEPTION; +} + +static int js_string_indexof(JSContext *ctx, JSValue str, JSValue needle, + int start, int str_len, int needle_len) +{ + int i, j; + for(i = start; i <= str_len - needle_len; i++) { + for(j = 0; j < needle_len; j++) { + if (string_getc(ctx, str, i + j) != + string_getc(ctx, needle, j)) { + goto next; + } + + } + return i; + next: ; + } + return -1; +} + +/* Note: ascii only */ +JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int to_lower) +{ + StringBuffer b_s, *b = &b_s; + int i, c, len; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return *this_val; + len = js_string_len(ctx, *this_val); + if (string_buffer_push(ctx, b, len)) + return JS_EXCEPTION; + for(i = 0; i < len; i++) { + c = string_getc(ctx, *this_val, i); + if (to_lower) { + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + } else { + if (c >= 'a' && c <= 'z') + c += 'A' - 'a'; + } + string_buffer_putc(ctx, b, c); + } + return string_buffer_pop(ctx, b); +} + +/* c < 128 */ +static force_inline BOOL unicode_is_space_ascii(uint32_t c) +{ + return (c >= 0x0009 && c <= 0x000D) || (c == 0x0020); +} + +static BOOL unicode_is_space_non_ascii(uint32_t c) +{ + return (c == 0x00A0 || + c == 0x1680 || + (c >= 0x2000 && c <= 0x200A) || + (c >= 0x2028 && c <= 0x2029) || + c == 0x202F || + c == 0x205F || + c == 0x3000 || + c == 0xFEFF); +} + +static force_inline BOOL unicode_is_space(uint32_t c) +{ + if (likely(c < 128)) { + return unicode_is_space_ascii(c); + } else { + return unicode_is_space_non_ascii(c); + } +} + +JSValue js_string_trim(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + int a, b, len; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return *this_val; + len = js_string_len(ctx, *this_val); + a = 0; + b = len; + if (magic & 1) { + while (a < len && unicode_is_space(string_getc(ctx, *this_val, a))) + a++; + } + if (magic & 2) { + while (b > a && unicode_is_space(string_getc(ctx, *this_val, b - 1))) + b--; + } + return js_sub_string(ctx, *this_val, a, b); +} + +JSValue js_string_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (!JS_IsString(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a string"); + return *this_val; +} + +JSValue js_string_repeat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + StringBuffer b_s, *b = &b_s; + JSStringCharBuf buf; + JSString *p; + int n; + int64_t len; + + if (!JS_IsString(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a string"); + if (JS_ToInt32Sat(ctx, &n, argv[0])) + return -1; + p = get_string_ptr(ctx, &buf, *this_val); + if (n < 0 || (len = (int64_t)n * p->len) > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid repeat count"); + if (p->len == 0 || n == 1) + return *this_val; + if (string_buffer_push(ctx, b, len)) + return JS_EXCEPTION; + while (n-- > 0) { + string_buffer_concat_str(ctx, b, *this_val); + } + return string_buffer_pop(ctx, b); +} + +/**********************************************************************/ + +JSValue js_object_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + /* XXX: incomplete */ + argc &= ~FRAME_CF_CTOR; + if (argc <= 0) { + return JS_NewObject(ctx); + } else { + return argv[0]; + } +} + +JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue *pobj, *pprop, *pdesc; + JSValue val, getter, setter; + JSGCRef val_ref, getter_ref; + int flags; + + pobj = &argv[0]; + pprop = &argv[1]; + pdesc = &argv[2]; + + if (!JS_IsObject(ctx, *pobj)) + return JS_ThrowTypeErrorNotAnObject(ctx); + *pprop = JS_ToPropertyKey(ctx, *pprop); + if (JS_IsException(*pprop)) + return JS_EXCEPTION; + val = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + flags = 0; + if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value))) { + flags |= JS_DEF_PROP_HAS_VALUE; + val = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value)); + if (JS_IsException(val)) + return JS_EXCEPTION; + } + if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get))) { + flags |= JS_DEF_PROP_HAS_GET; + JS_PUSH_VALUE(ctx, val); + getter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get)); + JS_POP_VALUE(ctx, val); + if (JS_IsException(getter)) + return JS_EXCEPTION; + if (!JS_IsUndefined(getter) && !JS_IsFunction(ctx, getter)) + goto bad_getset; + } + if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set))) { + flags |= JS_DEF_PROP_HAS_SET; + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, getter); + setter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set)); + JS_POP_VALUE(ctx, getter); + JS_POP_VALUE(ctx, val); + if (JS_IsException(setter)) + return JS_EXCEPTION; + if (!JS_IsUndefined(setter) && !JS_IsFunction(ctx, setter)) { + bad_getset: + return JS_ThrowTypeError(ctx, "invalid getter or setter"); + } + } + if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) { + if (flags & JS_DEF_PROP_HAS_VALUE) + return JS_ThrowTypeError(ctx, "cannot have both value and get/set"); + val = getter; + } + val = JS_DefinePropertyInternal(ctx, *pobj, *pprop, val, setter, + flags | JS_DEF_PROP_LOOKUP); + if (JS_IsException(val)) + return val; + return *pobj; +} + +JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(argv[0]); + return p->proto; +} + +/* 'obj' must be an object. 'proto' must be JS_NULL or an object */ +static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto) +{ + JSObject *p, *p1; + + p = JS_VALUE_TO_PTR(obj); + if (p->proto != proto) { + if (proto != JS_NULL) { + /* check if there is a cycle */ + p1 = JS_VALUE_TO_PTR(proto); + for(;;) { + if (p1 == p) + return JS_ThrowTypeError(ctx, "circular prototype chain"); + if (p1->proto == JS_NULL) + break; + p1 = JS_VALUE_TO_PTR(p1->proto); + } + } + + p->proto = proto; + } + return JS_UNDEFINED; +} + +JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue proto; + + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + proto = argv[1]; + if (proto != JS_NULL && !JS_IsObject(ctx, proto)) + return JS_ThrowTypeError(ctx, "not a prototype"); + if (JS_IsException(js_set_prototype_internal(ctx, argv[0], proto))) + return JS_EXCEPTION; + return argv[0]; +} + +JSValue js_object_create(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue proto; + proto = argv[0]; + if (proto != JS_NULL && !JS_IsObject(ctx, proto)) + return JS_ThrowTypeError(ctx, "not a prototype"); + if (argc >= 2) + return JS_ThrowTypeError(ctx, "unsupported additional properties"); + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0); +} + +JSValue js_object_keys(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *pret; + JSValue ret, str; + JSValueArray *arr, *ret_arr; + int array_len, prop_count, hash_mask, alloc_size, i, j, pos; + JSGCRef ret_ref; + + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(argv[0]); + + if (p->class_id == JS_CLASS_ARRAY) { + array_len = p->u.array.len; + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + array_len = p->u.typed_array.len; + } else { + array_len = 0; + } + + arr = JS_VALUE_TO_PTR(p->props); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + + alloc_size = array_len + prop_count; + + ret = JS_NewArray(ctx, alloc_size); + if (JS_IsException(ret)) + return ret; + + pos = 0; + for(i = 0; i < array_len; i++) { + JS_PUSH_VALUE(ctx, ret); + str = JS_ToString(ctx, JS_NewShortInt(i)); + JS_POP_VALUE(ctx, ret); + if (JS_IsException(str)) + return str; + pret = JS_VALUE_TO_PTR(ret); + ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab); + ret_arr->arr[pos++] = str; + } + + for(i = 0, j = 0; j < prop_count; i++) { + JSProperty *pr; + p = JS_VALUE_TO_PTR(argv[0]); + arr = JS_VALUE_TO_PTR(p->props); + pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i]; + /* exclude deleted properties */ + if (pr->key != JS_UNINITIALIZED) { + JS_PUSH_VALUE(ctx, ret); + str = JS_ToString(ctx, pr->key); + JS_POP_VALUE(ctx, ret); + if (JS_IsException(str)) + return str; + pret = JS_VALUE_TO_PTR(ret); + ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab); + ret_arr->arr[pos++] = str; + j++; + } + } + pret = JS_VALUE_TO_PTR(ret); + pret->u.array.len = pos; + return ret; +} + +JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue prop; + int array_len, idx; + + if (JS_IsNull(*this_val) || JS_IsUndefined(*this_val)) + return JS_ThrowTypeError(ctx, "cannot convert to object"); + if (!JS_IsObject(ctx, *this_val)) + return JS_FALSE; /* XXX: could improve for strings */ + prop = JS_ToPropertyKey(ctx, argv[0]); + p = JS_VALUE_TO_PTR(*this_val); + if (p->class_id == JS_CLASS_ARRAY) { + array_len = p->u.array.len; + goto check_array; + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + array_len = p->u.typed_array.len; + check_array: + if (JS_IsInt(prop)) { + idx = JS_VALUE_GET_INT(prop); + return JS_NewBool((idx >= 0 && idx < array_len)); + } + } + return JS_NewBool((find_own_property(ctx, p, prop) != NULL)); +} + +JSValue js_object_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + const char *str; + char buf[64]; + /* XXX: not fully compliant */ + if (JS_IsIntOrShortFloat(*this_val)) { + goto number; + } else if (!JS_IsPtr(*this_val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(*this_val)) { + case JS_TAG_NULL: + str = "Null"; + break; + case JS_TAG_UNDEFINED: + str = "Undefined"; + break; + case JS_TAG_SHORT_FUNC: + str = "Function"; + break; + case JS_TAG_BOOL: + str = "Boolean"; + break; + case JS_TAG_STRING_CHAR: + goto string; + default: + goto object; + } + } else { + JSObject *p = JS_VALUE_TO_PTR(*this_val); + switch(p->mtag) { + case JS_MTAG_OBJECT: + switch(p->class_id) { + case JS_CLASS_ARRAY: + str = "Array"; + break; + case JS_CLASS_ERROR: + str = "Error"; + break; + case JS_CLASS_CLOSURE: + case JS_CLASS_C_FUNCTION: + str = "Function"; + break; + default: + object: + str = "Object"; + break; + } + break; + case JS_MTAG_STRING: + string: + str = "String"; + break; + case JS_MTAG_FLOAT64: + number: + str = "Number"; + break; + default: + goto object; + } + } + js_snprintf(buf, sizeof(buf), "[object %s]", str); + return JS_NewString(ctx, buf); +} + +/**********************************************************************/ + +JSValue js_error_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSValue obj, msg; + JSObject *p; + JSGCRef obj_ref; + + argc &= ~FRAME_CF_CTOR; + + obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[magic], JS_CLASS_ERROR, + sizeof(JSErrorData)); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.error.message = JS_NULL; + p->u.error.stack = JS_NULL; + + if (!JS_IsUndefined(argv[0])) { + JS_PUSH_VALUE(ctx, obj); + msg = JS_ToString(ctx, argv[0]); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(msg)) + return msg; + p = JS_VALUE_TO_PTR(obj); + p->u.error.message = msg; + } else { + p = JS_VALUE_TO_PTR(obj); + p->u.error.message = js_get_atom(ctx, JS_ATOM_empty); + } + JS_PUSH_VALUE(ctx, obj); + build_backtrace(ctx, obj, NULL, 0, 0, 1); + JS_POP_VALUE(ctx, obj); + return obj; +} + +JSValue js_error_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue name; + StringBuffer b_s, *b = &b_s; + + if (!JS_IsError(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not an Error object"); + name = JS_GetProperty(ctx, *this_val, js_get_atom(ctx, JS_ATOM_name)); + if (JS_IsException(name)) + return name; + if (JS_IsUndefined(name)) + name = js_get_atom(ctx, JS_ATOM_Error); + else + name = JS_ToString(ctx, name); + if (JS_IsException(name)) + return name; + string_buffer_push(ctx, b, 0); + string_buffer_concat(ctx, b, name); + p = JS_VALUE_TO_PTR(*this_val); + if (p->u.error.message != JS_NULL) { + string_buffer_puts(ctx, b, ": "); + p = JS_VALUE_TO_PTR(*this_val); + string_buffer_concat(ctx, b, p->u.error.message); + } + return string_buffer_pop(ctx, b); +} + +JSValue js_error_get_message(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSObject *p; + if (!JS_IsError(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not an Error object"); + p = JS_VALUE_TO_PTR(*this_val); + if (magic == 0) + return p->u.error.message; + else + return p->u.error.stack; +} + +/**********************************************************************/ + +static JSObject *js_get_array(JSContext *ctx, JSValue obj) +{ + JSObject *p; + p = js_get_object_class(ctx, obj, JS_CLASS_ARRAY); + if (!p) { + JS_ThrowTypeError(ctx, "not an array"); + return NULL; + } + return p; +} + +JSValue js_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + return JS_NewShortInt(p->u.array.len); +} + +static int js_array_resize(JSContext *ctx, JSValue *this_val, int new_len) +{ + JSObject *p; + int i; + + if (new_len < 0 || new_len > JS_SHORTINT_MAX) { + JS_ThrowTypeError(ctx, "invalid array length"); + return -1; + } + p = JS_VALUE_TO_PTR(*this_val); + if (new_len < p->u.array.len) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* shrink the array if the new size is small enough */ + if (new_len < (arr->size / 2) && arr->size >= 4) { + js_shrink_value_array(ctx, &p->u.array.tab, new_len); + p = JS_VALUE_TO_PTR(*this_val); + } else { + for(i = new_len; i < p->u.array.len; i++) + arr->arr[i] = JS_UNDEFINED; + } + } else if (new_len > p->u.array.len) { + JSValueArray *arr; + JSValue new_tab; + new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len); + if (JS_IsException(new_tab)) + return -1; + p = JS_VALUE_TO_PTR(*this_val); + p->u.array.tab = new_tab; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = p->u.array.len; i < new_len; i++) + arr->arr[i] = JS_UNDEFINED; + } + p->u.array.len = new_len; + return 0; +} + +JSValue js_array_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int new_len; + + if (!js_get_array(ctx, *this_val)) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &new_len, argv[0])) + return JS_EXCEPTION; + if (js_array_resize(ctx, this_val, new_len)) + return JS_EXCEPTION; + return JS_UNDEFINED; +} + +JSValue js_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSObject *p; + int len, i; + BOOL has_init; + + argc &= ~FRAME_CF_CTOR; + + if (argc == 1 && JS_IsNumber(ctx, argv[0])) { + /* XXX: we create undefined properties instead of just setting the length */ + if (JS_ToInt32(ctx, &len, argv[0])) + return JS_EXCEPTION; + has_init = FALSE; + } else { + len = argc; + has_init = TRUE; + } + + if (len < 0 || len > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + obj = JS_NewArray(ctx, len); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.array.len = len; + + if (has_init) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < argc; i++) { + arr->arr[i] = argv[i]; + } + } + return obj; +} + +JSValue js_array_push(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_unshift) +{ + JSObject *p; + int new_len, i, from; + JSValueArray *arr; + JSValue new_tab; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + from = p->u.array.len; + new_len = from + argc; + if (new_len > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len); + if (JS_IsException(new_tab)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(*this_val); + p->u.array.tab = new_tab; + p->u.array.len = new_len; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (is_unshift && argc > 0) { + memmove(arr->arr + argc, arr->arr, from * sizeof(JSValue)); + from = 0; + } + for(i = 0; i < argc; i++) { + arr->arr[from + i] = argv[i]; + } + return JS_NewShortInt(new_len); +} + +JSValue js_array_pop(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue ret; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + if (p->u.array.len > 0) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + ret = arr->arr[--p->u.array.len]; + } else { + ret = JS_UNDEFINED; + } + return ret; +} + +JSValue js_array_shift(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue ret; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + if (p->u.array.len > 0) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + ret = arr->arr[0]; + p->u.array.len--; + memmove(arr->arr, arr->arr + 1, p->u.array.len * sizeof(JSValue)); + } else { + ret = JS_UNDEFINED; + } + return ret; +} + +JSValue js_array_join(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + uint32_t i, len; + BOOL is_array; + JSValue sep, val; + JSGCRef sep_ref; + JSObject *p; + JSValueArray *arr; + StringBuffer b_s, *b = &b_s; + + if (!JS_IsObject(ctx, *this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(*this_val); + is_array = (p->class_id == JS_CLASS_ARRAY); + if (is_array) { + len = p->u.array.len; + } else { + if (js_get_length32(ctx, &len, *this_val)) + return JS_EXCEPTION; + } + + if (argc > 0 && !JS_IsUndefined(argv[0])) { + sep = JS_ToString(ctx, argv[0]); + if (JS_IsException(sep)) + return sep; + } else { + sep = JS_NewStringChar(','); + } + JS_PUSH_VALUE(ctx, sep); + + string_buffer_push(ctx, b, 0); + for(i = 0; i < len; i++) { + if (i > 0) { + if (string_buffer_concat(ctx, b, sep_ref.val)) + goto exception; + } + if (is_array) { + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (i < p->u.array.len) + val = arr->arr[i]; + else + val = JS_UNDEFINED; + } else { + val = JS_GetPropertyUint32(ctx, *this_val, i); + if (JS_IsException(val)) + goto exception; + } + if (!JS_IsUndefined(val) && !JS_IsNull(val)) { + if (string_buffer_concat(ctx, b, val)) + goto exception; + } + } + val = string_buffer_pop(ctx, b); + JS_POP_VALUE(ctx, sep); + return val; + + exception: + string_buffer_pop(ctx, b); + JS_POP_VALUE(ctx, sep); + return JS_EXCEPTION; +} + +JSValue js_array_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return js_array_join(ctx, this_val, 0, NULL); +} + +JSValue js_array_isArray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + p = js_get_object_class(ctx, argv[0], JS_CLASS_ARRAY); + return JS_NewBool(p != NULL); +} + +JSValue js_array_reverse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int len; + JSObject *p; + JSValueArray *arr; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + js_reverse_val(arr->arr, len); + return *this_val; +} + +JSValue js_array_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int len, i, j, pos; + int64_t len64; + JSValue obj, val; + JSValueArray *arr, *arr1; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + /* do a first pass to estimate the length */ + len64 = p->u.array.len; + for(i = 0; i < argc; i++) { + p = js_get_object_class(ctx, argv[i], JS_CLASS_ARRAY); + if (p) { + len64 += p->u.array.len; + } else { + len64++; + } + } + if (len64 > JS_SHORTINT_MAX) + return JS_ThrowTypeError(ctx, "Array loo long"); + len = len64; + + obj = JS_NewArray(ctx, len); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + + pos = 0; + for(i = -1; i < argc; i++) { + val = i == -1 ? *this_val : argv[i]; + p = js_get_object_class(ctx, val, JS_CLASS_ARRAY); + if (p) { + arr1 = JS_VALUE_TO_PTR(p->u.array.tab); + for(j = 0; j < p->u.array.len; j++) + arr->arr[pos + j] = arr1->arr[j]; + pos += p->u.array.len; + } else { + arr->arr[pos++] = val; + } + } + return obj; +} + +JSValue js_array_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_lastIndexOf) +{ + JSObject *p; + int len, n, res; + JSValueArray *arr; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + if (is_lastIndexOf) { + n = len - 1; + } else { + n = 0; + } + if (argc > 1) { + if (JS_ToInt32Clamp(ctx, &n, argv[1], + -is_lastIndexOf, len - is_lastIndexOf, len)) + return JS_EXCEPTION; + } + /* the array may be modified */ + p = JS_VALUE_TO_PTR(*this_val); + len = p->u.array.len; /* the length may be modified */ + arr = JS_VALUE_TO_PTR(p->u.array.tab); + res = -1; + if (is_lastIndexOf) { + n = min_int(n, len - 1); + for(;n >= 0; n--) { + if (js_strict_eq(ctx, argv[0], arr->arr[n])) { + res = n; + break; + } + } + } else { + for(;n < len; n++) { + if (js_strict_eq(ctx, argv[0], arr->arr[n])) { + res = n; + break; + } + } + } + return JS_NewShortInt(res); +} + +JSValue js_array_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + int len, start, final, k; + JSValueArray *arr, *arr1; + JSValue obj; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + final = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + /* the array may have been modified */ + p = JS_VALUE_TO_PTR(*this_val); + len = p->u.array.len; /* the length may be modified */ + final = min_int(final, len); + + obj = JS_NewArray(ctx, max_int(final - start, 0)); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + p1 = JS_VALUE_TO_PTR(obj); + arr1 = JS_VALUE_TO_PTR(p1->u.array.tab); + for(k = start; k < final; k++) { + arr1->arr[k - start] = arr->arr[k]; + } + return obj; +} + +JSValue js_array_splice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + int start, len, item_count, del_count, new_len, i, ret; + JSValueArray *arr, *arr1; + JSValue obj; + JSGCRef obj_ref; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + + if (argc == 0) { + item_count = 0; + del_count = 0; + } else if (argc == 1) { + item_count = 0; + del_count = len - start; + } else { + item_count = argc - 2; + if (JS_ToInt32Clamp(ctx, &del_count, argv[1], 0, len - start, 0)) + return JS_EXCEPTION; + } + new_len = len + item_count - del_count; + + obj = JS_NewArray(ctx, del_count); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(*this_val); + /* handling this case has no practical use */ + if (p->u.array.len != len) + return JS_ThrowTypeError(ctx, "array length was modified"); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + p1 = JS_VALUE_TO_PTR(obj); + arr1 = JS_VALUE_TO_PTR(p1->u.array.tab); + + for(i = 0; i < del_count; i++) { + arr1->arr[i] = arr->arr[start + i]; + } + + if (item_count != del_count) { + /* resize */ + if (del_count > item_count) { + memmove(arr->arr + start + item_count, + arr->arr + start + del_count, + (len - (start + del_count)) * sizeof(JSValue)); + } + JS_PUSH_VALUE(ctx, obj); + ret = js_array_resize(ctx, this_val, new_len); + JS_POP_VALUE(ctx, obj); + if (ret) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (del_count < item_count) { + memmove(arr->arr + start + item_count, + arr->arr + start + del_count, + (len - (start + del_count)) * sizeof(JSValue)); + } + } + + for(i = 0; i < item_count; i++) + arr->arr[start + i] = argv[2 + i]; + + return obj; +} + +JSValue js_array_every(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special) +{ + JSObject *p; + JSValueArray *arr; + JSValue res, ret, val; + JSValue *pfunc, *pthis_arg; + JSGCRef val_ref, ret_ref; + int len, k, n; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + + pfunc = &argv[0]; + pthis_arg = NULL; + if (argc > 1) + pthis_arg = &argv[1]; + + if (!JS_IsFunction(ctx, *pfunc)) + return JS_ThrowTypeError(ctx, "not a function"); + + switch (special) { + case js_special_every: + ret = JS_TRUE; + break; + case js_special_some: + ret = JS_FALSE; + break; + case js_special_map: + ret = JS_NewArray(ctx, len); + if (JS_IsException(ret)) + return JS_EXCEPTION; + break; + case js_special_filter: + ret = JS_NewArray(ctx, 0); + if (JS_IsException(ret)) + return JS_EXCEPTION; + break; + case js_special_forEach: + default: + ret = JS_UNDEFINED; + break; + } + n = 0; + + JS_PUSH_VALUE(ctx, ret); + for(k = 0; k < len; k++) { + if (JS_StackCheck(ctx, 5)) + goto exception; + + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* the array length may have been modified by the function call*/ + if (k >= p->u.array.len) + break; + val = arr->arr[k]; + + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, JS_NewShortInt(k)); + JS_PushArg(ctx, val); /* arg0 */ + JS_PushArg(ctx, *pfunc); /* func */ + JS_PushArg(ctx, pthis_arg ? *pthis_arg : JS_UNDEFINED); /* this */ + JS_PUSH_VALUE(ctx, val); + res = JS_Call(ctx, 3); + JS_POP_VALUE(ctx, val); + if (JS_IsException(res)) + goto exception; + + switch (special) { + case js_special_every: + if (!JS_ToBool(ctx, res)) { + ret_ref.val = JS_FALSE; + goto done; + } + break; + case js_special_some: + if (JS_ToBool(ctx, res)) { + ret_ref.val = JS_TRUE; + goto done; + } + break; + case js_special_map: + /* Note: same as defineProperty for arrays */ + res = JS_SetPropertyUint32(ctx, ret_ref.val, k, res); + if (JS_IsException(res)) + goto exception; + break; + case js_special_filter: + if (JS_ToBool(ctx, res)) { + res = JS_SetPropertyUint32(ctx, ret_ref.val, n++, val); + if (JS_IsException(res)) + goto exception; + } + break; + case js_special_forEach: + default: + break; + } + } +done: + JS_POP_VALUE(ctx, ret); + return ret; + exception: + ret_ref.val = JS_EXCEPTION; + goto done; +} + +JSValue js_array_reduce(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special) +{ + JSObject *p; + JSValueArray *arr; + JSValue acc, *pfunc; + JSGCRef acc_ref; + int len, k, k1, ret; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + pfunc = &argv[0]; + + if (!JS_IsFunction(ctx, *pfunc)) + return JS_ThrowTypeError(ctx, "not a function"); + + k = 0; + if (argc > 1) { + acc = argv[1]; + } else { + if (len == 0) + return JS_ThrowTypeError(ctx, "empty array"); + k1 = (special == js_special_reduceRight) ? len - k - 1 : k; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + acc = arr->arr[k1]; + k++; + } + for (; k < len; k++) { + JS_PUSH_VALUE(ctx, acc); + ret = JS_StackCheck(ctx, 6); + JS_POP_VALUE(ctx, acc); + if (ret) + return JS_EXCEPTION; + + k1 = (special == js_special_reduceRight) ? len - k - 1 : k; + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* Note: the array length may have been modified, hence the check */ + if (k1 >= p->u.array.len) + break; + + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, JS_NewShortInt(k1)); + JS_PushArg(ctx, arr->arr[k1]); + JS_PushArg(ctx, acc); /* arg0 */ + JS_PushArg(ctx, *pfunc); /* func */ + JS_PushArg(ctx, JS_UNDEFINED); /* this */ + acc = JS_Call(ctx, 4); + if (JS_IsException(acc)) + return JS_EXCEPTION; + } + return acc; +} + +/* heapsort algorithm */ +static void rqsort_idx(size_t nmemb, + int (*cmp)(size_t, size_t, void *), + void (*swap)(size_t, size_t, void *), + void *opaque) +{ + size_t i, n, c, r, size; + + size = 1; + if (nmemb > 1) { + i = (nmemb / 2) * size; + n = nmemb * size; + + while (i > 0) { + i -= size; + for (r = i; (c = r * 2 + size) < n; r = c) { + if (c < n - size && cmp(c, c + size, opaque) <= 0) + c += size; + if (cmp(r, c, opaque) > 0) + break; + swap(r, c, opaque); + } + } + for (i = n - size; i > 0; i -= size) { + swap(0, i, opaque); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(c, c + size, opaque) <= 0) + c += size; + if (cmp(r, c, opaque) > 0) + break; + swap(r, c, opaque); + } + } + } +} + +typedef struct { + JSContext *ctx; + BOOL exception; + JSValue *parr; + JSValue *pfunc; +} JSArraySortContext; + +/* return -1, 0, 1 */ +static int js_array_sort_cmp(size_t i1, size_t i2, void *opaque) +{ + JSArraySortContext *s = opaque; + JSContext *ctx = s->ctx; + JSValueArray *arr; + int cmp, j1, j2; + + if (s->exception) + return 0; + + arr = JS_VALUE_TO_PTR(*s->parr); + if (s->pfunc) { + JSValue res; + /* custom sort function is specified as returning 0 for identical + * objects: avoid method call overhead. + */ + if (arr->arr[2 * i1] == arr->arr[2 * i2]) + goto cmp_same; + if (JS_StackCheck(ctx, 4)) + goto exception; + arr = JS_VALUE_TO_PTR(*s->parr); + + JS_PushArg(ctx, arr->arr[2 * i2]); + JS_PushArg(ctx, arr->arr[2 * i1]); /* arg0 */ + JS_PushArg(ctx, *s->pfunc); /* func */ + JS_PushArg(ctx, JS_UNDEFINED); /* this */ + res = JS_Call(ctx, 2); + if (JS_IsException(res)) + return JS_EXCEPTION; + if (JS_IsInt(res)) { + int val = JS_VALUE_GET_INT(res); + cmp = (val > 0) - (val < 0); + } else { + double val; + if (JS_ToNumber(ctx, &val, res)) + goto exception; + cmp = (val > 0) - (val < 0); + } + } else { + JSValue str1, str2; + JSGCRef str1_ref; + + str1 = arr->arr[2 * i1]; + if (!JS_IsString(ctx, str1)) { + str1 = JS_ToString(ctx, str1); + if (JS_IsException(str1)) + goto exception; + arr = JS_VALUE_TO_PTR(*s->parr); + } + str2 = arr->arr[2 * i2]; + if (!JS_IsString(ctx, str2)) { + JS_PUSH_VALUE(ctx, str1); + str2 = JS_ToString(ctx, str2); + JS_POP_VALUE(ctx, str1); + if (JS_IsException(str2)) + goto exception; + } + cmp = js_string_compare(ctx, str1, str2); + } + if (cmp != 0) + return cmp; + cmp_same: + /* make sort stable: compare array offsets */ + arr = JS_VALUE_TO_PTR(*s->parr); + j1 = JS_VALUE_GET_INT(arr->arr[2 * i1 + 1]); + j2 = JS_VALUE_GET_INT(arr->arr[2 * i2 + 1]); + return (j1 > j2) - (j1 < j2); + +exception: + s->exception = TRUE; + return 0; +} + +static void js_array_sort_swap(size_t i1, size_t i2, void *opaque) +{ + JSArraySortContext *s = opaque; + JSValueArray *arr; + JSValue tmp, *tab; + + arr = JS_VALUE_TO_PTR(*s->parr); + tab = arr->arr; + tmp = tab[2 * i1]; + tab[2 * i1] = tab[2 * i2]; + tab[2 * i2] = tmp; + + tmp = tab[2 * i1 + 1]; + tab[2 * i1 + 1] = tab[2 * i2 + 1]; + tab[2 * i2 + 1] = tmp; +} + +JSValue js_array_sort(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue *pfunc = &argv[0]; + JSObject *p; + JSValue tab_val; + JSGCRef tab_val_ref; + JSValueArray *tab, *arr; + int i, len, n; + JSArraySortContext ss, *s = &ss; + + if (!JS_IsUndefined(*pfunc)) { + if (!JS_IsFunction(ctx, *pfunc)) + return JS_ThrowTypeError(ctx, "not a function"); + } else { + pfunc = NULL; + } + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + + /* create a temporary array for sorting */ + len = p->u.array.len; + tab = js_alloc_value_array(ctx, 0, len * 2); + if (!tab) + return JS_EXCEPTION; + + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + n = 0; + for(i = 0; i < len; i++) { + if (!JS_IsUndefined(arr->arr[i])) { + tab->arr[2 * n] = arr->arr[i]; + tab->arr[2 * n + 1] = JS_NewShortInt(i); + n++; + } + } + /* the end of 'tab' is already filled with JS_UNDEFINED */ + tab_val = JS_VALUE_FROM_PTR(tab); + + JS_PUSH_VALUE(ctx, tab_val); + s->ctx = ctx; + s->exception = FALSE; + s->parr = &tab_val_ref.val; + s->pfunc = pfunc; + rqsort_idx(n, js_array_sort_cmp, js_array_sort_swap, s); + JS_POP_VALUE(ctx, tab_val); + tab = JS_VALUE_TO_PTR(tab_val); + if (s->exception) { + js_free(ctx, tab); + return JS_EXCEPTION; + } + + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* XXX: could resize the array in case it was shrank by the compare function */ + len = min_int(len, p->u.array.len); + for(i = 0; i < len; i++) { + arr->arr[i] = tab->arr[2 * i]; + } + js_free(ctx, tab); + return *this_val; +} + +/**********************************************************************/ + +/* precondition: a and b are not NaN */ +static double js_fmin(double a, double b) +{ + if (a == 0 && b == 0) { + return uint64_as_float64(float64_as_uint64(a) | float64_as_uint64(b)); + } else if (a <= b) { + return a; + } else { + return b; + } +} + +/* precondition: a and b are not NaN */ +static double js_fmax(double a, double b) +{ + if (a == 0 && b == 0) { + return uint64_as_float64(float64_as_uint64(a) & float64_as_uint64(b)); + } else if (a >= b) { + return a; + } else { + return b; + } +} + +JSValue js_math_min_max(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + BOOL is_max = magic; + double r, a; + int i; + + if (unlikely(argc == 0)) { + return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0); + } + + if (JS_IsInt(argv[0])) { + int a1, r1 = JS_VALUE_GET_INT(argv[0]); + for(i = 1; i < argc; i++) { + if (!JS_IsInt(argv[i])) { + r = r1; + goto generic_case; + } + a1 = JS_VALUE_GET_INT(argv[i]); + if (is_max) + r1 = max_int(r1, a1); + else + r1 = min_int(r1, a1); + } + return JS_NewShortInt(r1); + } else { + if (JS_ToNumber(ctx, &r, argv[0])) + return JS_EXCEPTION; + i = 1; + generic_case: + while (i < argc) { + if (JS_ToNumber(ctx, &a, argv[i])) + return JS_EXCEPTION; + if (!isnan(r)) { + if (isnan(a)) { + r = a; + } else { + if (is_max) + r = js_fmax(r, a); + else + r = js_fmin(r, a); + } + } + i++; + } + return JS_NewFloat64(ctx, r); + } +} + +double js_math_sign(double a) +{ + if (isnan(a) || a == 0.0) + return a; + if (a < 0) + return -1; + else + return 1; +} + +double js_math_fround(double a) +{ + return (float)a; +} + +JSValue js_math_imul(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int a, b; + + if (JS_ToInt32(ctx, &a, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &b, argv[1])) + return JS_EXCEPTION; + /* purposely ignoring overflow */ + return JS_NewInt32(ctx, (uint32_t)a * (uint32_t)b); +} + +JSValue js_math_clz32(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + uint32_t a, r; + + if (JS_ToUint32(ctx, &a, argv[0])) + return JS_EXCEPTION; + if (a == 0) + r = 32; + else + r = clz32(a); + return JS_NewInt32(ctx, r); +} + +JSValue js_math_atan2(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double y, x; + + if (JS_ToNumber(ctx, &y, argv[0])) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &x, argv[1])) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, js_atan2(y, x)); +} + +JSValue js_math_pow(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double y, x; + + if (JS_ToNumber(ctx, &x, argv[0])) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &y, argv[1])) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, js_pow(x, y)); +} + +/* xorshift* random number generator by Marsaglia */ +static uint64_t xorshift64star(uint64_t *pstate) +{ + uint64_t x; + x = *pstate; + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + *pstate = x; + return x * 0x2545F4914F6CDD1D; +} + +JSValue js_math_random(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + uint64_t v; + + v = xorshift64star(&ctx->random_state); + /* 1.0 <= u.d < 2 */ + d = uint64_as_float64(((uint64_t)0x3ff << 52) | (v >> 12)); + return __JS_NewFloat64(ctx, d - 1.0); +} + +/* typed array */ + +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) + +static uint8_t typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = { + 0, 0, 0, 1, 1, 2, 2, 2, 3 +}; + +static int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val) +{ + int v; + /* XXX: should support 53 bit inteers */ + if (JS_ToInt32Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > JS_SHORTINT_MAX) { + JS_ThrowRangeError(ctx, "invalid array index"); + return -1; + } + *plen = v; + return 0; +} + +JSValue js_array_buffer_alloc(JSContext *ctx, uint64_t len) +{ + JSByteArray *arr; + JSValue buffer, obj; + JSGCRef buffer_ref; + JSObject *p; + + if (len > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array buffer length"); + arr = js_alloc_byte_array(ctx, len); + if (!arr) + return JS_EXCEPTION; + memset(arr->buf, 0, len); + buffer = JS_VALUE_FROM_PTR(arr); + JS_PUSH_VALUE(ctx, buffer); + obj = JS_NewObjectClass(ctx, JS_CLASS_ARRAY_BUFFER, sizeof(JSArrayBuffer)); + JS_POP_VALUE(ctx, buffer); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.array_buffer.byte_buffer = buffer; + return obj; +} + +JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + uint64_t len; + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + return js_array_buffer_alloc(ctx, len); +} + +JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p = js_get_object_class(ctx, *this_val, JS_CLASS_ARRAY_BUFFER); + JSByteArray *arr; + if (!p) + return JS_ThrowTypeError(ctx, "expected an ArrayBuffer"); + arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer); + return JS_NewShortInt(arr->size); +} + +JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return JS_ThrowTypeError(ctx, "cannot be called"); +} + +static JSValue js_typed_array_constructor_obj(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + int i, len; + JSValue val, obj; + JSGCRef obj_ref; + JSObject *p; + + p = JS_VALUE_TO_PTR(argv[0]); + if (p->class_id == JS_CLASS_ARRAY) { + len = p->u.array.len; + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + len = p->u.typed_array.len; + } else { + return JS_ThrowTypeError(ctx, "unsupported object class"); + } + val = JS_NewShortInt(len); + obj = js_typed_array_constructor(ctx, NULL, 1 | FRAME_CF_CTOR, &val, magic); + if (JS_IsException(obj)) + return obj; + + for(i = 0; i < len; i++) { + JS_PUSH_VALUE(ctx, obj); + val = JS_GetProperty(ctx, argv[0], JS_NewShortInt(i)); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(val)) + return val; + JS_PUSH_VALUE(ctx, obj); + val = JS_SetPropertyInternal(ctx, obj, JS_NewShortInt(i), val, FALSE); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(val)) + return val; + } + return obj; +} + +JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + int size_log2; + uint64_t len, offset, byte_length; + JSObject *p; + JSByteArray *arr; + JSValue buffer, obj; + JSGCRef buffer_ref; + + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + size_log2 = typed_array_size_log2[magic - JS_CLASS_UINT8C_ARRAY]; + if (!JS_IsObject(ctx, argv[0])) { + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + buffer = js_array_buffer_alloc(ctx, len << size_log2); + if (JS_IsException(buffer)) + return JS_EXCEPTION; + offset = 0; + } else { + p = JS_VALUE_TO_PTR(argv[0]); + if (p->class_id == JS_CLASS_ARRAY_BUFFER) { + arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer); + byte_length = arr->size; + if (JS_ToIndex(ctx, &offset, argv[1])) + return JS_EXCEPTION; + if ((offset & ((1 << size_log2) - 1)) != 0 || + offset > byte_length) + return JS_ThrowRangeError(ctx, "invalid offset"); + if (JS_IsUndefined(argv[2])) { + if ((byte_length & ((1 << size_log2) - 1)) != 0) + goto invalid_length; + len = (byte_length - offset) >> size_log2; + } else { + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + if ((offset + (len << size_log2)) > byte_length) { + invalid_length: + return JS_ThrowRangeError(ctx, "invalid length"); + } + } + buffer = argv[0]; + offset >>= size_log2; + } else { + return js_typed_array_constructor_obj(ctx, this_val, + argc, argv, magic); + } + } + + JS_PUSH_VALUE(ctx, buffer); + obj = JS_NewObjectClass(ctx, magic, sizeof(JSTypedArray)); + JS_POP_VALUE(ctx, buffer); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.typed_array.buffer = buffer; + p->u.typed_array.offset = offset; + p->u.typed_array.len = len; + return obj; +} + +static JSObject *get_typed_array(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (!JS_IsObject(ctx, val)) + goto fail; + p = JS_VALUE_TO_PTR(val); + if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { + fail: + JS_ThrowTypeError(ctx, "not a TypedArray"); + return NULL; + } + return p; +} + +JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSObject *p; + int size_log2; + + p = get_typed_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + size_log2 = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY]; + switch(magic) { + default: + case 0: + return JS_NewShortInt(p->u.typed_array.len); + case 1: + return JS_NewShortInt(p->u.typed_array.len << size_log2); + case 2: + return JS_NewShortInt(p->u.typed_array.offset << size_log2); + case 3: + return p->u.typed_array.buffer; + } +} + +JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + JSByteArray *arr; + int start, final, len; + uint32_t offset, count; + JSValue obj; + + p = get_typed_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.typed_array.len; + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[1])) { + final = len; + } else { + if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + p = JS_VALUE_TO_PTR(*this_val); + offset = p->u.typed_array.offset + start; + count = max_int(final - start, 0); + + /* check offset and count */ + p1 = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(p1->u.array_buffer.byte_buffer); + if (offset + count > arr->size) + return JS_ThrowRangeError(ctx, "invalid length"); + + obj = JS_NewObjectClass(ctx, p->class_id, sizeof(JSTypedArray)); + if (JS_IsException(obj)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(*this_val); + p1 = JS_VALUE_TO_PTR(obj); + p1->u.typed_array.buffer = p->u.typed_array.buffer; + p1->u.typed_array.offset = offset; + p1->u.typed_array.len = count; + return obj; +} + +JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + uint32_t dst_len, src_len, i; + int offset; + + p = get_typed_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + if (argc > 1) { + if (JS_ToInt32Sat(ctx, &offset, argv[1])) + return JS_EXCEPTION; + } else { + offset = 0; + } + if (offset < 0) + goto range_error; + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(*this_val); + dst_len = p->u.typed_array.len; + p1 = JS_VALUE_TO_PTR(argv[0]); + if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + src_len = p1->u.typed_array.len; + if (src_len > dst_len || offset > dst_len - src_len) + goto range_error; + if (p1->class_id == p->class_id) { + JSObject *src_buffer, *dst_buffer; + JSByteArray *src_arr, *dst_arr; + int shift = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY]; + dst_buffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + dst_arr = JS_VALUE_TO_PTR(dst_buffer->u.array_buffer.byte_buffer); + src_buffer = JS_VALUE_TO_PTR(p1->u.typed_array.buffer); + src_arr = JS_VALUE_TO_PTR(src_buffer->u.array_buffer.byte_buffer); + /* same type: must copy to preserve float bits */ + memmove(dst_arr->buf + ((p->u.typed_array.offset + offset) << shift), + src_arr->buf + (p1->u.typed_array.offset << shift), + src_len << shift); + goto done; + } + } else { + if (js_get_length32(ctx, (uint32_t *)&src_len, argv[0])) + return JS_EXCEPTION; + if (src_len > dst_len || offset > dst_len - src_len) { + range_error: + return JS_ThrowRangeError(ctx, "invalid array length"); + } + } + for(i = 0; i < src_len; i++) { + JSValue val; + val = JS_GetPropertyUint32(ctx, argv[0], i); + if (JS_IsException(val)) + return JS_EXCEPTION; + val = JS_SetPropertyUint32(ctx, *this_val, offset + i, val); + if (JS_IsException(val)) + return JS_EXCEPTION; + } + done: + return JS_UNDEFINED; +} + +/* Date */ + +JSValue js_date_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return JS_ThrowTypeError(ctx, "only Date.now() is supported"); +} + +/* global */ + +JSValue js_global_eval(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue val; + + if (!JS_IsString(ctx, argv[0])) + return argv[0]; + val = JS_Parse2(ctx, argv[0], NULL, 0, "", JS_EVAL_RETVAL); + if (JS_IsException(val)) + return val; + return JS_Run(ctx, val); +} + +JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + if (unlikely(JS_ToNumber(ctx, &d, argv[0]))) + return JS_EXCEPTION; + return JS_NewBool(isnan(d)); +} + +JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + if (unlikely(JS_ToNumber(ctx, &d, argv[0]))) + return JS_EXCEPTION; + return JS_NewBool(isfinite(d)); +} + +/* JSON */ + +JSValue js_json_parse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue val; + + val = JS_ToString(ctx, argv[0]); + if (JS_IsException(val)) + return val; + return JS_Parse2(ctx, val, NULL, 0, "", JS_EVAL_JSON); +} + +static int js_to_quoted_string(JSContext *ctx, StringBuffer *b, JSValue str) +{ + int i, c; + JSStringCharBuf buf; + JSString *p; + JSGCRef str_ref; + size_t clen; + + JS_PUSH_VALUE(ctx, str); + string_buffer_putc(ctx, b, '\"'); + + i = 0; + for(;;) { + /* XXX: inefficient */ + p = get_string_ptr(ctx, &buf, str_ref.val); + if (i >= p->len) + break; + c = utf8_get(p->buf + i, &clen); + i += clen; + + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + string_buffer_putc(ctx, b, '\\'); + string_buffer_putc(ctx, b, c); + break; + default: + if (c < 32 || (c >= 0xd800 && c < 0xe000)) { + char buf[7]; + js_snprintf(buf, sizeof(buf), "\\u%04x", c); + string_buffer_puts(ctx, b, buf); + } else { + string_buffer_putc(ctx, b, c); + } + break; + } + } + string_buffer_putc(ctx, b, '\"'); + JS_POP_VALUE(ctx, str); + return 0; +} + +#define JSON_REC_SIZE 3 + +static int check_circular_ref(JSContext *ctx, JSValue *stack_top, JSValue val) +{ + JSValue *sp; + for(sp = ctx->sp; sp < stack_top; sp += JSON_REC_SIZE) { + if (sp[0] == val) { + JS_ThrowTypeError(ctx, "circular reference"); + return -1; + } + } + return 0; +} + +/* XXX: no space nor replacer */ +JSValue js_json_stringify(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj, *stack_top; + StringBuffer b_s, *b = &b_s; + int idx, ret; + +#if 0 + if (JS_IsNumber(ctx, *pspace)) { + int n; + if (JS_ToInt32Clamp(ctx, &n, *pspace, 0, 10, 0)) + return JS_EXCEPTION; + *pspace = JS_NewStringLen(ctx, " ", n); + } else if (JS_IsString(ctx, *pspace)) { + *pspace = js_sub_string(ctx, *pspace, 0, 10); + } else { + *pspace = js_get_atom(ctx, JS_ATOM_empty); + } +#endif + string_buffer_push(ctx, b, 0); + stack_top = ctx->sp; + + ret = JS_StackCheck(ctx, JSON_REC_SIZE); + if (ret) + goto fail; + *--ctx->sp = JS_NULL; /* keys */ + *--ctx->sp = JS_NewShortInt(0); /* prop index */ + *--ctx->sp = argv[0]; /* object */ + + while (ctx->sp < stack_top) { + obj = ctx->sp[0]; + if (JS_IsFunction(ctx, obj)) { + goto output_null; + } else if (JS_IsObject(ctx, obj)) { + JSObject *p = JS_VALUE_TO_PTR(obj); + idx = JS_VALUE_GET_INT(ctx->sp[1]); + if (p->class_id == JS_CLASS_ARRAY) { + JSValueArray *arr; + JSValue val; + + /* array */ + if (idx == 0) + string_buffer_putc(ctx, b, '['); + p = JS_VALUE_TO_PTR(ctx->sp[0]); + if (idx >= p->u.array.len) { + /* end of array */ + string_buffer_putc(ctx, b, ']'); + ctx->sp += JSON_REC_SIZE; + } else { + if (idx != 0) + string_buffer_putc(ctx, b, ','); + ctx->sp[1] = JS_NewShortInt(idx + 1); + ret = JS_StackCheck(ctx, JSON_REC_SIZE); + if (ret) + goto fail; + p = JS_VALUE_TO_PTR(ctx->sp[0]); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + val = arr->arr[idx]; + if (check_circular_ref(ctx, stack_top, val)) + goto fail; + *--ctx->sp = JS_NULL; + *--ctx->sp = JS_NewShortInt(0); + *--ctx->sp = val; + } + } else { + JSValueArray *arr; + JSValue val, prop; + JSGCRef val_ref; + int saved_idx; + + /* object */ + if (idx == 0) { + string_buffer_putc(ctx, b, '{'); + ctx->sp[2] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]); + if (JS_IsException(ctx->sp[2])) + goto fail; + } + saved_idx = idx; + for(;;) { + p = JS_VALUE_TO_PTR(ctx->sp[2]); /* keys */ + if (idx >= p->u.array.len) { + /* end of object */ + string_buffer_putc(ctx, b, '}'); + ctx->sp += JSON_REC_SIZE; + goto end_obj; + } else { + arr = JS_VALUE_TO_PTR(p->u.array.tab); + prop = JS_ToPropertyKey(ctx, arr->arr[idx]); + val = JS_GetProperty(ctx, ctx->sp[0], prop); + if (JS_IsException(val)) + goto fail; + /* skip undefined properties */ + if (!JS_IsUndefined(val)) + break; + idx++; + } + } + JS_PUSH_VALUE(ctx, val); + if (saved_idx != 0) + string_buffer_putc(ctx, b, ','); + ctx->sp[1] = JS_NewShortInt(idx + 1); + p = JS_VALUE_TO_PTR(ctx->sp[2]); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + ret = js_to_quoted_string(ctx, b, arr->arr[idx]); + string_buffer_putc(ctx, b, ':'); + ret |= JS_StackCheck(ctx, JSON_REC_SIZE); + JS_POP_VALUE(ctx, val); + if (ret) + goto fail; + if (check_circular_ref(ctx, stack_top, val)) + goto fail; + *--ctx->sp = JS_NULL; + *--ctx->sp = JS_NewShortInt(0); + *--ctx->sp = val; + end_obj: ; + } + } else if (JS_IsNumber(ctx, obj)) { + double d; + ret = JS_ToNumber(ctx, &d, obj); + if (ret) + goto fail; + if (!isfinite(d)) + goto output_null; + goto to_string; + } else if (JS_IsBool(obj)) { + to_string: + if (string_buffer_concat(ctx, b, obj)) + goto fail; + ctx->sp += JSON_REC_SIZE; + } else if (JS_IsString(ctx, obj)) { + if (js_to_quoted_string(ctx, b, obj)) + goto fail; + ctx->sp += JSON_REC_SIZE; + } else { + output_null: + string_buffer_concat(ctx, b, js_get_atom(ctx, JS_ATOM_null)); + ctx->sp += JSON_REC_SIZE; + } + } + return string_buffer_pop(ctx, b); + + fail: + ctx->sp = stack_top; + string_buffer_pop(ctx, b); + return JS_EXCEPTION; +} + +/**********************************************************************/ +/* regexp */ + +typedef enum { +#define REDEF(id, size) REOP_ ## id, +#include "mquickjs_opcode.h" +#undef REDEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define REGISTER_COUNT_MAX 255 + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define REDEF(id, size) { #id, size }, +#else +#define REDEF(id, size) { size }, +#endif +#include "mquickjs_opcode.h" +#undef REDEF +}; + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UNICODE (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_REGISTER_COUNT 3 + +#define RE_HEADER_LEN 4 + +#define CLASS_RANGE_BASE 0x40000000 + +typedef enum { + CHAR_RANGE_d, + CHAR_RANGE_D, + CHAR_RANGE_s, + CHAR_RANGE_S, + CHAR_RANGE_w, + CHAR_RANGE_W, +} CharRangeEnum; + +static int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +static int lre_get_alloc_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT] * 2 + bc_buf[RE_HEADER_REGISTER_COUNT]; +} + +static int lre_get_flags(const uint8_t *bc_buf) +{ + return get_u16(bc_buf + RE_HEADER_FLAGS); +} + +#ifdef DUMP_REOP +static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, + int buf_len) +{ + int pos, len, opcode, bc_len, re_flags; + uint32_t val, val2; + + assert(buf_len >= RE_HEADER_LEN); + re_flags = lre_get_flags(buf); + bc_len = buf_len - RE_HEADER_LEN; + + printf("flags: 0x%x capture_count=%d reg_count=%d bytecode_len=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_REGISTER_COUNT], + bc_len); + + buf += RE_HEADER_LEN; + + pos = 0; + while (pos < bc_len) { + printf("%5u: ", pos); + opcode = buf[pos]; + len = reopcode_info[opcode].size; + if (opcode >= REOP_COUNT) { + printf(" invalid opcode=0x%02x\n", opcode); + break; + } + if ((pos + len) > bc_len) { + printf(" buffer overflow (opcode=0x%02x)\n", opcode); + break; + } + printf("%s", reopcode_info[opcode].name); + switch(opcode) { + case REOP_char1: + case REOP_char2: + case REOP_char3: + case REOP_char4: + { + int i, n; + n = opcode - REOP_char1 + 1; + for(i = 0; i < n; i++) { + val = buf[pos + 1 + i]; + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%2x", val); + } + } + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_loop: + val2 = buf[pos + 1]; + val = get_u32(buf + pos + 2); + val += (pos + 6); + printf(" r%u, %u", val2, val); + break; + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + { + uint32_t limit; + val2 = buf[pos + 1]; + limit = get_u32(buf + pos + 2); + val = get_u32(buf + pos + 6); + val += (pos + 10); + printf(" r%u, %u, %u", val2, limit, val); + } + break; + case REOP_save_start: + case REOP_save_end: + case REOP_back_reference: + case REOP_back_reference_i: + printf(" %u", buf[pos + 1]); + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_set_i32: + val = buf[pos + 1]; + val2 = get_u32(buf + pos + 2); + printf(" r%u, %d", val, val2); + break; + case REOP_set_char_pos: + case REOP_check_advance: + val = buf[pos + 1]; + printf(" r%u", val); + break; + case REOP_range8: + { + int n, i; + n = buf[pos + 1]; + len += n * 2; + for(i = 0; i < n * 2; i++) { + val = buf[pos + 2 + i]; + printf(" 0x%02x", val); + } + } + break; + case REOP_range: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 8; + for(i = 0; i < n * 2; i++) { + val = get_u32(buf + pos + 3 + i * 4); + printf(" 0x%05x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(JSParseState *s, int op) +{ + emit_u8(s, op); +} + +static void re_emit_op_u8(JSParseState *s, int op, uint32_t val) +{ + emit_u8(s, op); + emit_u8(s, val); +} + +static void re_emit_op_u16(JSParseState *s, int op, uint32_t val) +{ + emit_u8(s, op); + emit_u16(s, val); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(JSParseState *s, int op, uint32_t val) +{ + int pos; + emit_u8(s, op); + pos = s->byte_code_len; + emit_u32(s, val); + return pos; +} + +static int re_emit_goto(JSParseState *s, int op, uint32_t val) +{ + int pos; + emit_u8(s, op); + pos = s->byte_code_len; + emit_u32(s, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8(JSParseState *s, int op, uint32_t arg, uint32_t val) +{ + int pos; + emit_u8(s, op); + emit_u8(s, arg); + pos = s->byte_code_len; + emit_u32(s, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8_u32(JSParseState *s, int op, uint32_t arg0, uint32_t arg1, uint32_t val) +{ + int pos; + emit_u8(s, op); + emit_u8(s, arg0); + emit_u32(s, arg1); + pos = s->byte_code_len; + emit_u32(s, val - (pos + 4)); + return pos; +} + +static void re_emit_char(JSParseState *s, int c) +{ + uint8_t buf[4]; + size_t n, i; + n = unicode_to_utf8(buf, c); + re_emit_op(s, REOP_char1 + n - 1); + for(i = 0; i < n; i++) + emit_u8(s, buf[i]); +} + +static void re_parse_expect(JSParseState *s, int c) +{ + if (s->source_buf[s->buf_pos] != c) + return js_parse_error(s, "expecting '%c'", c); + s->buf_pos++; +} + +/* return JS_SHORTINT_MAX in case of overflow */ +static int parse_digits(const uint8_t **pp) +{ + const uint8_t *p; + uint64_t v; + int c; + + p = *pp; + v = 0; + for(;;) { + c = *p; + if (c < '0' || c > '9') + break; + v = v * 10 + c - '0'; + if (v >= JS_SHORTINT_MAX) + v = JS_SHORTINT_MAX; + p++; + } + *pp = p; + return v; +} + +/* need_check_adv: false if the opcodes always advance the char pointer + need_capture_init: true if all the captures in the atom are not set +*/ +static BOOL re_need_check_adv_and_capture_init(BOOL *pneed_capture_init, + const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + BOOL need_check_adv, need_capture_init; + + need_check_adv = TRUE; + need_capture_init = FALSE; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range8: + val = bc_buf[pos + 1]; + len += val * 2; + need_check_adv = FALSE; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + need_check_adv = FALSE; + break; + case REOP_char1: + case REOP_char2: + case REOP_char3: + case REOP_char4: + case REOP_dot: + case REOP_any: + case REOP_space: + case REOP_not_space: + need_check_adv = FALSE; + break; + case REOP_line_start: + case REOP_line_start_m: + case REOP_line_end: + case REOP_line_end_m: + case REOP_set_i32: + case REOP_set_char_pos: + case REOP_word_boundary: + case REOP_not_word_boundary: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + break; + default: + /* safe behavior: we cannot predict the outcome */ + need_capture_init = TRUE; + goto done; + } + pos += len; + } + done: + *pneed_capture_init = need_capture_init; + return need_check_adv; +} + +/* return the character or a class range (>= CLASS_RANGE_BASE) if inclass + = TRUE */ +static int get_class_atom(JSParseState *s, BOOL inclass) +{ + const uint8_t *p; + uint32_t c; + int ret; + size_t len; + + p = s->source_buf + s->buf_pos; + c = *p; + switch(c) { + case '\\': + p++; + c = *p++; + switch(c) { + case 'd': + c = CHAR_RANGE_d; + goto class_range; + case 'D': + c = CHAR_RANGE_D; + goto class_range; + case 's': + c = CHAR_RANGE_s; + goto class_range; + case 'S': + c = CHAR_RANGE_S; + goto class_range; + case 'w': + c = CHAR_RANGE_w; + goto class_range; + case 'W': + c = CHAR_RANGE_W; + class_range: + c += CLASS_RANGE_BASE; + break; + case 'c': + c = *p; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (((c >= '0' && c <= '9') || c == '_') && + inclass && !s->is_unicode)) { /* Annex B.1.4 */ + c &= 0x1f; + p++; + } else if (s->is_unicode) { + goto invalid_escape; + } else { + /* otherwise return '\' and 'c' */ + p--; + c = '\\'; + } + break; + case '-': + if (!inclass && s->is_unicode) + goto invalid_escape; + break; + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '|': + case '/': + /* always valid to escape these characters */ + break; + default: + p--; + ret = js_parse_escape(p, &len); + if (ret < 0) { + if (s->is_unicode) { + invalid_escape: + s->buf_pos = p - s->source_buf; + js_parse_error(s, "invalid escape sequence in regular expression"); + } else { + goto normal_char; + } + } + p += len; + c = ret; + break; + } + break; + case '\0': + case '/': /* safety for end of regexp in JS parser */ + if ((p - s->source_buf) >= s->buf_len) + js_parse_error(s, "unexpected end"); + goto normal_char; + default: + normal_char: + /* normal char */ + ret = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &len); + /* Note: should not fail with normal JS strings */ + if (ret < 0) + js_parse_error(s, "malformed unicode char"); + p += len; + c = ret; + break; + } + s->buf_pos = p - s->source_buf; + return c; +} + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +static const uint16_t char_range_w[] = { + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +static void re_emit_range_base1(JSParseState *s, const uint16_t *tab, int n) +{ + int i; + for(i = 0; i < n; i++) + emit_u32(s, tab[i]); +} + +static void re_emit_range_base(JSParseState *s, int c) +{ + BOOL invert; + invert = c & 1; + if (invert) + emit_u32(s, 0); + switch(c & ~1) { + case CHAR_RANGE_d: + emit_u32(s, 0x30); + emit_u32(s, 0x39 + 1); + break; + case CHAR_RANGE_s: + re_emit_range_base1(s, char_range_s, countof(char_range_s)); + break; + case CHAR_RANGE_w: + re_emit_range_base1(s, char_range_w, countof(char_range_w)); + break; + default: + abort(); + } + if (invert) + emit_u32(s, 0x110000); +} + +static int range_sort_cmp(size_t i1, size_t i2, void *opaque) +{ + uint8_t *tab = opaque; + return get_u32(&tab[8 * i1]) - get_u32(&tab[8 * i2]); +} + +static void range_sort_swap(size_t i1, size_t i2, void *opaque) +{ + uint8_t *tab = opaque; + uint64_t tmp; + tmp = get_u64(&tab[8 * i1]); + put_u64(&tab[8 * i1], get_u64(&tab[8 * i2])); + put_u64(&tab[8 * i2], tmp); +} + +/* merge consecutive intervals, remove empty intervals and handle overlapping intervals */ +static int range_compress(uint8_t *tab, int len) +{ + int i, j; + uint32_t start, end, start2, end2; + + i = 0; + j = 0; + while (i < len) { + start = get_u32(&tab[8 * i]); + end = get_u32(&tab[8 * i + 4]); + if (start == end) { + /* empty interval : remove */ + } else if ((i + 1) < len) { + start2 = get_u32(&tab[8 * i + 8]); + end2 = get_u32(&tab[8 * i + 12]); + if (end < start2) { + goto copy; + } else { + /* union of the intervals */ + put_u32(&tab[8 * i + 8], start); + put_u32(&tab[8 * i + 12], max_uint32(end, end2)); + } + } else { + copy: + put_u32(&tab[8 * j], start); + put_u32(&tab[8 * j + 4], end); + j++; + } + i++; + } + return j; +} + +static void re_range_optimize(JSParseState *s, int range_start, BOOL invert) +{ + int n, n1; + JSByteArray *arr; + + n = (unsigned)(s->byte_code_len - range_start) / 8; + + arr = JS_VALUE_TO_PTR(s->byte_code); + rqsort_idx(n, range_sort_cmp, range_sort_swap, arr->buf + range_start); + + /* must compress before inverting */ + n1 = range_compress(arr->buf + range_start, n); + s->byte_code_len -= (n - n1) * 8; + + if (invert) { + emit_insert(s, range_start, 4); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + range_start, 0); + emit_u32(s, 0x110000); + arr = JS_VALUE_TO_PTR(s->byte_code); + n = n1 + 1; + n1 = range_compress(arr->buf + range_start, n); + s->byte_code_len -= (n - n1) * 8; + } + n = n1; + + if (n > 65534) + js_parse_error(s, "range too big"); + + /* compress to 8 bit if possible */ + /* XXX: adjust threshold */ + if (n > 0 && n < 16) { + uint8_t *tab = arr->buf + range_start; + int c, i; + c = get_u32(&tab[8 * (n - 1) + 4]); + if (c < 254 || (c == 0x110000 && + get_u32(&tab[8 * (n - 1)]) < 254)) { + s->byte_code_len = range_start - 3; + re_emit_op_u8(s, REOP_range8, n); + for(i = 0; i < 2 * n; i++) { + c = get_u32(&tab[4 * i]); + if (c == 0x110000) + c = 0xff; + emit_u8(s, c); + } + goto done; + } + } + + put_u16(arr->buf + range_start - 2, n); + done: ; +} + +/* add the intersection of the two intervals and if offset != 0 the + translated interval */ +static void add_interval_intersect(JSParseState *s, + uint32_t start, uint32_t end, + uint32_t start1, uint32_t end1, + int offset) +{ + start = max_uint32(start, start1); + end = min_uint32(end, end1); + if (start < end) { + emit_u32(s, start); + emit_u32(s, end); + if (offset != 0) { + emit_u32(s, start + offset); + emit_u32(s, end + offset); + } + } +} + +static void re_parse_char_class(JSParseState *s) +{ + uint32_t c1, c2; + BOOL invert; + int range_start; + + s->buf_pos++; /* skip '[' */ + + invert = FALSE; + if (s->source_buf[s->buf_pos] == '^') { + s->buf_pos++; + invert = TRUE; + } + + re_emit_op_u16(s, REOP_range, 0); + range_start = s->byte_code_len; + + for(;;) { + if (s->source_buf[s->buf_pos] == ']') + break; + + c1 = get_class_atom(s, TRUE); + if (s->source_buf[s->buf_pos] == '-' && s->source_buf[s->buf_pos + 1] != ']') { + s->buf_pos++; + if (c1 >= CLASS_RANGE_BASE) + goto invalid_class_range; + c2 = get_class_atom(s, TRUE); + if (c2 >= CLASS_RANGE_BASE) + goto invalid_class_range; + if (c2 < c1) { + invalid_class_range: + js_parse_error(s, "invalid class range"); + } + goto add_range; + } else { + if (c1 >= CLASS_RANGE_BASE) { + re_emit_range_base(s, c1 - CLASS_RANGE_BASE); + } else { + c2 = c1; + add_range: + c2++; + if (s->ignore_case) { + /* add the intervals exclude the cased characters */ + add_interval_intersect(s, c1, c2, 0, 'A', 0); + add_interval_intersect(s, c1, c2, 'Z' + 1, 'a', 0); + add_interval_intersect(s, c1, c2, 'z' + 1, INT32_MAX, 0); + /* include all the possible cases */ + add_interval_intersect(s, c1, c2, 'A', 'Z' + 1, 32); + add_interval_intersect(s, c1, c2, 'a', 'z' + 1, -32); + } else { + emit_u32(s, c1); + emit_u32(s, c2); + } + } + } + } + s->buf_pos++; /* skip ']' */ + re_range_optimize(s, range_start, invert); +} + +static void re_parse_quantifier(JSParseState *s, int last_atom_start, int last_capture_count) +{ + int c, quant_min, quant_max; + JSByteArray *arr; + BOOL greedy; + const uint8_t *p; + + p = s->source_buf + s->buf_pos; + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = JS_SHORTINT_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = JS_SHORTINT_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + if (!is_digit(p[1])) + goto invalid_quant_count; + p++; + quant_min = parse_digits(&p); + quant_max = quant_min; + if (*p == ',') { + p++; + if (is_digit(*p)) { + quant_max = parse_digits(&p); + if (quant_max < quant_min) { + invalid_quant_count: + js_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = JS_SHORTINT_MAX; /* infinity */ + } + } + s->buf_pos = p - s->source_buf; + re_parse_expect(s, '}'); + p = s->source_buf + s->buf_pos; + } + quantifier: + greedy = TRUE; + + if (*p == '?') { + p++; + greedy = FALSE; + } + s->buf_pos = p - s->source_buf; + + if (last_atom_start < 0) + js_parse_error(s, "nothing to repeat"); + { + BOOL need_capture_init, add_zero_advance_check; + int len, pos; + + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + arr = JS_VALUE_TO_PTR(s->byte_code); + add_zero_advance_check = + re_need_check_adv_and_capture_init(&need_capture_init, + arr->buf + last_atom_start, + s->byte_code_len - last_atom_start); + + /* general case: need to reset the capture at each + iteration. We don't do it if there are no captures + in the atom or if we are sure all captures are + initialized in the atom. If quant_min = 0, we still + need to reset once the captures in case the atom + does not match. */ + if (need_capture_init && last_capture_count != s->capture_count) { + emit_insert(s, last_atom_start, 3); + int pos = last_atom_start; + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[pos++] = REOP_save_reset; + arr->buf[pos++] = last_capture_count; + arr->buf[pos++] = s->capture_count - 1; + } + + len = s->byte_code_len - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (!need_capture_init && last_capture_count != s->capture_count) { + emit_insert(s, last_atom_start, 3); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[last_atom_start++] = REOP_save_reset; + arr->buf[last_atom_start++] = last_capture_count; + arr->buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code_len = last_atom_start; + } else if (quant_max == 1 || quant_max == JS_SHORTINT_MAX) { + BOOL has_goto = (quant_max == JS_SHORTINT_MAX); + emit_insert(s, last_atom_start, 5 + add_zero_advance_check * 2); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(arr->buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2 * 2); + if (add_zero_advance_check) { + arr->buf[last_atom_start + 1 + 4] = REOP_set_char_pos; + arr->buf[last_atom_start + 1 + 4 + 1] = 0; + re_emit_op_u8(s, REOP_check_advance, 0); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + emit_insert(s, last_atom_start, 11 + add_zero_advance_check * 2); + pos = last_atom_start; + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[pos++] = REOP_split_goto_first + greedy; + put_u32(arr->buf + pos, 6 + add_zero_advance_check * 2 + len + 10); + pos += 4; + + arr->buf[pos++] = REOP_set_i32; + arr->buf[pos++] = 0; + put_u32(arr->buf + pos, quant_max); + pos += 4; + last_atom_start = pos; + if (add_zero_advance_check) { + arr->buf[pos++] = REOP_set_char_pos; + arr->buf[pos++] = 0; + } + re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max, last_atom_start); + } + } else if (quant_min == 1 && quant_max == JS_SHORTINT_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == quant_max) + add_zero_advance_check = FALSE; + emit_insert(s, last_atom_start, 6 + add_zero_advance_check * 2); + /* Note: we assume the string length is < JS_SHORTINT_MAX */ + pos = last_atom_start; + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[pos++] = REOP_set_i32; + arr->buf[pos++] = 0; + put_u32(arr->buf + pos, quant_max); + pos += 4; + last_atom_start = pos; + if (add_zero_advance_check) { + arr->buf[pos++] = REOP_set_char_pos; + arr->buf[pos++] = 0; + } + if (quant_min == quant_max) { + /* a simple loop is enough */ + re_emit_goto_u8(s, REOP_loop, 0, last_atom_start); + } else { + re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max - quant_min, last_atom_start); + } + } + last_atom_start = -1; + } + break; + default: + break; + } +} + +/* return the number of bytes if char otherwise 0 */ +static int re_is_char(const uint8_t *buf, int start, int end) +{ + int n; + if (!(buf[start] >= REOP_char1 && buf[start] <= REOP_char4)) + return 0; + n = buf[start] - REOP_char1 + 1; + if ((end - start) != (n + 1)) + return 0; + return n; +} + +static int re_parse_alternative(JSParseState *s, int state, int dummy_param) +{ + int term_start, last_term_start, last_atom_start, last_capture_count, c, n1, n2, i; + JSByteArray *arr; + + PARSE_START3(); + + last_term_start = -1; + for(;;) { + if (s->buf_pos >= s->buf_len) + break; + term_start = s->byte_code_len; + + last_atom_start = -1; + last_capture_count = 0; + c = s->source_buf[s->buf_pos]; + switch(c) { + case '|': + case ')': + goto done; + case '^': + s->buf_pos++; + re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start); + break; + case '$': + s->buf_pos++; + re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end); + break; + case '.': + s->buf_pos++; + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + break; + case '{': + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!s->is_unicode && !is_digit(s->source_buf[s->buf_pos + 1])) + goto parse_class_atom; + /* fall thru */ + case '*': + case '+': + case '?': + js_parse_error(s, "nothing to repeat"); + case '(': + if (s->source_buf[s->buf_pos + 1] == '?') { + c = s->source_buf[s->buf_pos + 2]; + if (c == ':') { + s->buf_pos += 3; + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + PARSE_CALL_SAVE4(s, 0, re_parse_disjunction, 0, + last_term_start, term_start, last_atom_start, last_capture_count); + re_parse_expect(s, ')'); + } else if ((c == '=' || c == '!')) { + int is_neg, pos; + is_neg = (c == '!'); + s->buf_pos += 3; + /* lookahead */ + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + PARSE_CALL_SAVE6(s, 1, re_parse_disjunction, 0, + last_term_start, term_start, last_atom_start, last_capture_count, + is_neg, pos); + re_parse_expect(s, ')'); + re_emit_op(s, REOP_lookahead_match + is_neg); + /* jump after the 'match' after the lookahead is successful */ + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + pos, s->byte_code_len - (pos + 4)); + } else { + js_parse_error(s, "invalid group"); + } + } else { + int capture_index; + s->buf_pos++; + /* capture without group name */ + if (s->capture_count >= CAPTURE_COUNT_MAX) + js_parse_error(s, "too many captures"); + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start, capture_index); + + PARSE_CALL_SAVE5(s, 2, re_parse_disjunction, 0, + last_term_start, term_start, last_atom_start, last_capture_count, + capture_index); + + re_emit_op_u8(s, REOP_save_end, capture_index); + + re_parse_expect(s, ')'); + } + break; + case '\\': + switch(s->source_buf[s->buf_pos + 1]) { + case 'b': + case 'B': + if (s->source_buf[s->buf_pos + 1] != 'b') { + re_emit_op(s, REOP_not_word_boundary); + } else { + re_emit_op(s, REOP_word_boundary); + } + s->buf_pos += 2; + break; + case '0': + s->buf_pos += 2; + c = 0; + if (is_digit(s->source_buf[s->buf_pos])) + js_parse_error(s, "invalid decimal escape in regular expression"); + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *p; + p = s->source_buf + s->buf_pos + 1; + c = parse_digits(&p); + s->buf_pos = p - s->source_buf; + if (c > CAPTURE_COUNT_MAX) + js_parse_error(s, "back reference is out of range"); + /* the range is checked afterwards as we don't know the number of captures */ + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + re_emit_op_u8(s, REOP_back_reference + s->ignore_case, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + re_parse_char_class(s); + break; + case ']': + case '}': + if (s->is_unicode) + js_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, FALSE); + normal_char: + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + if (c >= CLASS_RANGE_BASE) { + int range_start; + c -= CLASS_RANGE_BASE; + if (c == CHAR_RANGE_s || c == CHAR_RANGE_S) { + re_emit_op(s, REOP_space + c - CHAR_RANGE_s); + } else { + re_emit_op_u16(s, REOP_range, 0); + range_start = s->byte_code_len; + + re_emit_range_base(s, c); + re_range_optimize(s, range_start, FALSE); + } + } else { + if (s->ignore_case && + ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) { + /* XXX: could add specific operation */ + if (c >= 'a') + c -= 32; + re_emit_op_u8(s, REOP_range8, 2); + emit_u8(s, c); + emit_u8(s, c + 1); + emit_u8(s, c + 32); + emit_u8(s, c + 32 + 1); + } else { + re_emit_char(s, c); + } + } + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + re_parse_quantifier(s, last_atom_start, last_capture_count); + } + + /* combine several characters when possible */ + arr = JS_VALUE_TO_PTR(s->byte_code); + if (last_term_start >= 0 && + (n1 = re_is_char(arr->buf, last_term_start, term_start)) > 0 && + (n2 = re_is_char(arr->buf, term_start, s->byte_code_len)) > 0 && + (n1 + n2) <= 4) { + n1 += n2; + arr->buf[last_term_start] = REOP_char1 + n1 - 1; + for(i = 0; i < n2; i++) + arr->buf[last_term_start + n1 + i] = arr->buf[last_term_start + n1 + i + 1]; + s->byte_code_len--; + } else { + last_term_start = term_start; + } + } + done: + return PARSE_STATE_RET; +} + +static int re_parse_disjunction(JSParseState *s, int state, int dummy_param) +{ + int start, len, pos; + JSByteArray *arr; + + PARSE_START2(); + + start = s->byte_code_len; + + PARSE_CALL_SAVE1(s, 0, re_parse_alternative, 0, start); + while (s->source_buf[s->buf_pos] == '|') { + s->buf_pos++; + + len = s->byte_code_len - start; + + /* insert a split before the first alternative */ + emit_insert(s, start, 5); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[start] = REOP_split_next_first; + put_u32(arr->buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + PARSE_CALL_SAVE2(s, 1, re_parse_alternative, 0, start, pos); + + /* patch the goto */ + len = s->byte_code_len - (pos + 4); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + pos, len); + } + return PARSE_STATE_RET; +} + +/* Allocate the registers as a stack. The control flow is recursive so + the analysis can be linear. */ +static int re_compute_register_count(JSParseState *s, uint8_t *bc_buf, int bc_buf_len) +{ + int stack_size, stack_size_max, pos, opcode, len; + uint32_t val; + + stack_size = 0; + stack_size_max = 0; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + assert(opcode < REOP_COUNT); + assert((pos + len) <= bc_buf_len); + switch(opcode) { + case REOP_set_i32: + case REOP_set_char_pos: + bc_buf[pos + 1] = stack_size; + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > REGISTER_COUNT_MAX) + js_parse_error(s, "too many regexp registers"); + stack_size_max = stack_size; + } + break; + case REOP_check_advance: + case REOP_loop: + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + assert(stack_size > 0); + stack_size--; + bc_buf[pos + 1] = stack_size; + break; + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + assert(stack_size >= 2); + stack_size -= 2; + bc_buf[pos + 1] = stack_size; + break; + case REOP_range8: + val = bc_buf[pos + 1]; + len += val * 2; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + case REOP_back_reference: + case REOP_back_reference_i: + /* validate back references */ + if (bc_buf[pos + 1] >= s->capture_count) + js_parse_error(s, "back reference is out of range"); + break; + } + pos += len; + } + return stack_size_max; +} + +/* return a JSByteArray. 'source' must be a string */ +static JSValue js_parse_regexp(JSParseState *s, int re_flags) +{ + JSByteArray *arr; + int register_count; + + s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0); + s->byte_code = JS_NULL; + s->byte_code_len = 0; + s->capture_count = 1; + + emit_u16(s, re_flags); + emit_u8(s, 0); /* number of captures */ + emit_u8(s, 0); /* number of registers */ + + if (!(re_flags & LRE_FLAG_STICKY)) { + re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); + re_emit_op(s, REOP_any); + re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); + } + re_emit_op_u8(s, REOP_save_start, 0); + + js_parse_call(s, PARSE_FUNC_re_parse_disjunction, 0); + + re_emit_op_u8(s, REOP_save_end, 0); + re_emit_op(s, REOP_match); + + if (s->buf_pos != s->buf_len) + js_parse_error(s, "extraneous characters at the end"); + + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + register_count = + re_compute_register_count(s, arr->buf + RE_HEADER_LEN, + s->byte_code_len - RE_HEADER_LEN); + arr->buf[RE_HEADER_REGISTER_COUNT] = register_count; + + js_shrink_byte_array(s->ctx, &s->byte_code, s->byte_code_len); + +#ifdef DUMP_REOP + arr = JS_VALUE_TO_PTR(s->byte_code); + lre_dump_bytecode(arr->buf, arr->size); +#endif + + return s->byte_code; +} + +/* regexp interpreter */ + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +static BOOL is_line_terminator(uint32_t c) +{ + return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); +} + +static BOOL is_word_char(uint32_t c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_')); +} + +/* Note: we canonicalize as in the unicode case, but only handle ASCII characters */ +static int lre_canonicalize(uint32_t c) +{ + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + return c; +} + +#define GET_CHAR(c, cptr, cbuf_end) \ + do { \ + size_t clen; \ + c = utf8_get(cptr, &clen); \ + cptr += clen; \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end) \ + do { \ + size_t clen; \ + c = utf8_get(cptr, &clen); \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start) \ + do { \ + const uint8_t *cptr1 = cptr - 1; \ + size_t clen; \ + while ((*cptr1 & 0xc0) == 0x80) \ + cptr1--; \ + c = utf8_get(cptr1, &clen); \ + } while (0) + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, +} REExecStateEnum; + +//#define DUMP_REEXEC + +/* return 1 if match, 0 if not match or < 0 if error. str must be a + JSString. capture_buf and byte_code are JSByteArray */ +static int lre_exec(JSContext *ctx, JSValue capture_buf, + JSValue byte_code, JSValue str, int cindex) +{ + const uint8_t *pc, *cptr, *cbuf; + uint32_t *capture; + int opcode, capture_count; + uint32_t val, c, idx; + const uint8_t *cbuf_end; + JSValue *sp, *bp, *initial_sp, *saved_stack_bottom; + JSByteArray *arr; /* temporary use */ + JSString *ps; /* temporary use */ + JSGCRef capture_buf_ref, byte_code_ref, str_ref; + + arr = JS_VALUE_TO_PTR(byte_code); + pc = arr->buf; + arr = JS_VALUE_TO_PTR(capture_buf); + capture = (uint32_t *)arr->buf; + capture_count = lre_get_capture_count(pc); + pc += RE_HEADER_LEN; + ps = JS_VALUE_TO_PTR(str); + cbuf = ps->buf; + cbuf_end = cbuf + ps->len; + cptr = cbuf + cindex; + + saved_stack_bottom = ctx->stack_bottom; + initial_sp = ctx->sp; + sp = initial_sp; + bp = initial_sp; + +#define LRE_POLL_INTERRUPT() do { \ + if (unlikely(--ctx->interrupt_counter <= 0)) { \ + JSValue ret; \ + int saved_pc, saved_cptr; \ + arr = JS_VALUE_TO_PTR(byte_code); \ + saved_pc = pc - arr->buf; \ + saved_cptr = cptr - cbuf; \ + JS_PUSH_VALUE(ctx, capture_buf); \ + JS_PUSH_VALUE(ctx, byte_code); \ + JS_PUSH_VALUE(ctx, str); \ + ctx->sp = sp; \ + ret = __js_poll_interrupt(ctx); \ + JS_POP_VALUE(ctx, str); \ + JS_POP_VALUE(ctx, byte_code); \ + JS_POP_VALUE(ctx, capture_buf); \ + if (JS_IsException(ret)) { \ + ctx->sp = initial_sp; \ + ctx->stack_bottom = saved_stack_bottom; \ + return -1; \ + } \ + arr = JS_VALUE_TO_PTR(byte_code); \ + pc = arr->buf + saved_pc; \ + ps = JS_VALUE_TO_PTR(str); \ + cbuf = ps->buf; \ + cbuf_end = cbuf + ps->len; \ + cptr = cbuf + saved_cptr; \ + arr = JS_VALUE_TO_PTR(capture_buf); \ + capture = (uint32_t *)arr->buf; \ + } \ + } while(0) + +#define CHECK_STACK_SPACE(n) \ + { \ + if (unlikely((sp - ctx->stack_bottom) < (n))) { \ + int ret, saved_pc, saved_cptr; \ + arr = JS_VALUE_TO_PTR(byte_code); \ + saved_pc = pc - arr->buf; \ + saved_cptr = cptr - cbuf; \ + JS_PUSH_VALUE(ctx, capture_buf); \ + JS_PUSH_VALUE(ctx, byte_code); \ + JS_PUSH_VALUE(ctx, str); \ + ctx->sp = sp; \ + ret = JS_StackCheck(ctx, n); \ + JS_POP_VALUE(ctx, str); \ + JS_POP_VALUE(ctx, byte_code); \ + JS_POP_VALUE(ctx, capture_buf); \ + if (ret < 0) { \ + ctx->sp = initial_sp; \ + ctx->stack_bottom = saved_stack_bottom; \ + return -1; \ + } \ + arr = JS_VALUE_TO_PTR(byte_code); \ + pc = arr->buf + saved_pc; \ + ps = JS_VALUE_TO_PTR(str); \ + cbuf = ps->buf; \ + cbuf_end = cbuf + ps->len; \ + cptr = cbuf + saved_cptr; \ + arr = JS_VALUE_TO_PTR(capture_buf); \ + capture = (uint32_t *)arr->buf; \ + } \ + } + +#define SAVE_CAPTURE(idx, value) \ + { \ + int __v = (value); \ + CHECK_STACK_SPACE(2); \ + sp[-2] = JS_NewShortInt(idx); \ + sp[-1] = JS_NewShortInt(capture[idx]); \ + sp -= 2; \ + capture[idx] = __v; \ + } + + /* avoid saving the previous value if already saved */ +#define SAVE_CAPTURE_CHECK(idx, value) \ + { \ + int __v = (value); \ + JSValue *sp1; \ + sp1 = sp; \ + for(;;) { \ + if (sp1 < bp) { \ + if (JS_VALUE_GET_INT(sp1[0]) == (idx)) \ + break; \ + sp1 += 2; \ + } else { \ + CHECK_STACK_SPACE(2); \ + sp[-2] = JS_NewShortInt(idx); \ + sp[-1] = JS_NewShortInt(capture[idx]); \ + sp -= 2; \ + break; \ + } \ + } \ + capture[idx] = __v; \ + } + +#define RE_PC_TYPE_TO_VALUE(pc, type) (((type) << 1) | (((pc) - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf) << 3)) +#define RE_VALUE_TO_PC(val) (((val) >> 3) + ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf) +#define RE_VALUE_TO_TYPE(val) (((val) >> 1) & 3) + +#ifdef DUMP_REEXEC + printf("%5s %5s %5s %5s %s\n", "PC", "CP", "BP", "SP", "OPCODE"); +#endif + for(;;) { + opcode = *pc++; +#ifdef DUMP_REEXEC + printf("%5ld %5ld %5ld %5ld %s\n", + pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN, + cptr - cbuf, + bp - initial_sp, + sp - initial_sp, + reopcode_info[opcode].name); +#endif + switch(opcode) { + case REOP_match: + ctx->sp = initial_sp; + ctx->stack_bottom = saved_stack_bottom; + return 1; + no_match: + for(;;) { + REExecStateEnum type; + if (bp == initial_sp) { + ctx->sp = initial_sp; + ctx->stack_bottom = saved_stack_bottom; + return 0; + } + /* undo the modifications to capture[] and regs[] */ + while (sp < bp) { + int idx2 = JS_VALUE_GET_INT(sp[0]); + capture[idx2] = JS_VALUE_GET_INT(sp[1]); + sp += 2; + } + + pc = RE_VALUE_TO_PC(sp[0]); + type = RE_VALUE_TO_TYPE(sp[0]); + cptr = JS_VALUE_GET_INT(sp[1]) + cbuf; + bp = VALUE_TO_SP(ctx, sp[2]); + sp += 3; + if (type != RE_EXEC_STATE_LOOKAHEAD) + break; + } + LRE_POLL_INTERRUPT(); + break; + case REOP_lookahead_match: + /* pop all the saved states until reaching the start of + the lookahead and keep the updated captures and + variables and the corresponding undo info. */ + { + JSValue *sp1, *sp_start, *next_sp; + REExecStateEnum type; + + sp_start = sp; + for(;;) { + sp1 = sp; + sp = bp; + pc = RE_VALUE_TO_PC(sp[0]); + type = RE_VALUE_TO_TYPE(sp[0]); + cptr = JS_VALUE_GET_INT(sp[1]) + cbuf; + bp = VALUE_TO_SP(ctx, sp[2]); + sp[2] = SP_TO_VALUE(ctx, sp1); /* save the next value for the copy step */ + sp += 3; + if (type == RE_EXEC_STATE_LOOKAHEAD) + break; + } + if (sp != initial_sp) { + /* keep the undo info if there is a saved state */ + sp1 = sp; + while (sp1 != sp_start) { + sp1 -= 3; + next_sp = VALUE_TO_SP(ctx, sp1[2]); + while (sp1 != next_sp) { + *--sp = *--sp1; + } + } + } + } + break; + case REOP_negative_lookahead_match: + /* pop all the saved states until reaching start of the negative lookahead */ + for(;;) { + REExecStateEnum type; + type = RE_VALUE_TO_TYPE(bp[0]); + /* undo the modifications to capture[] and regs[] */ + while (sp < bp) { + int idx2 = JS_VALUE_GET_INT(sp[0]); + capture[idx2] = JS_VALUE_GET_INT(sp[1]); + sp += 2; + } + pc = RE_VALUE_TO_PC(sp[0]); + type = RE_VALUE_TO_TYPE(sp[0]); + cptr = JS_VALUE_GET_INT(sp[1]) + cbuf; + bp = VALUE_TO_SP(ctx, sp[2]); + sp += 3; + if (type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD) + break; + } + goto no_match; + + case REOP_char1: + if ((cbuf_end - cptr) < 1) + goto no_match; + if (pc[0] != cptr[0]) + goto no_match; + pc++; + cptr++; + break; + case REOP_char2: + if ((cbuf_end - cptr) < 2) + goto no_match; + if (get_u16(pc) != get_u16(cptr)) + goto no_match; + pc += 2; + cptr += 2; + break; + case REOP_char3: + if ((cbuf_end - cptr) < 3) + goto no_match; + if (get_u16(pc) != get_u16(cptr) || pc[2] != cptr[2]) + goto no_match; + pc += 3; + cptr += 3; + break; + case REOP_char4: + if ((cbuf_end - cptr) < 4) + goto no_match; + if (get_u32(pc) != get_u32(cptr)) + goto no_match; + pc += 4; + cptr += 4; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + CHECK_STACK_SPACE(3); + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + sp -= 3; + sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT); + sp[1] = JS_NewShortInt(cptr - cbuf); + sp[2] = SP_TO_VALUE(ctx, bp); + bp = sp; + } + break; + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + CHECK_STACK_SPACE(3); + sp -= 3; + sp[0] = RE_PC_TYPE_TO_VALUE(pc + (int)val, + RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead); + sp[1] = JS_NewShortInt(cptr - cbuf); + sp[2] = SP_TO_VALUE(ctx, bp); + bp = sp; + break; + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + LRE_POLL_INTERRUPT(); + break; + case REOP_line_start: + case REOP_line_start_m: + if (cptr == cbuf) + break; + if (opcode == REOP_line_start) + goto no_match; + PEEK_PREV_CHAR(c, cptr, cbuf); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + case REOP_line_end_m: + if (cptr == cbuf_end) + break; + if (opcode == REOP_line_end) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_dot: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + if (is_line_terminator(c)) + goto no_match; + break; + case REOP_any: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + break; + case REOP_space: + case REOP_not_space: + { + BOOL v1; + if (cptr == cbuf_end) + goto no_match; + c = cptr[0]; + if (c < 128) { + cptr++; + v1 = unicode_is_space_ascii(c); + } else { + size_t clen; + c = __utf8_get(cptr, &clen); + cptr += clen; + v1 = unicode_is_space_non_ascii(c); + } + v1 ^= (opcode - REOP_space); + if (!v1) + goto no_match; + } + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + assert(val < capture_count); + idx = 2 * val + opcode - REOP_save_start; + SAVE_CAPTURE(idx, cptr - cbuf); + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + assert(val2 < capture_count); + CHECK_STACK_SPACE(2 * (val2 - val + 1)); + while (val <= val2) { + idx = 2 * val; + SAVE_CAPTURE(idx, 0); + idx = 2 * val + 1; + SAVE_CAPTURE(idx, 0); + val++; + } + } + break; + case REOP_set_i32: + idx = pc[0]; + val = get_u32(pc + 1); + pc += 5; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, val); + break; + case REOP_loop: + { + uint32_t val2; + idx = pc[0]; + val = get_u32(pc + 1); + pc += 5; + + val2 = capture[2 * capture_count + idx] - 1; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2); + if (val2 != 0) { + pc += (int)val; + LRE_POLL_INTERRUPT(); + } + } + break; + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + { + const uint8_t *pc1; + uint32_t val2, limit; + idx = pc[0]; + limit = get_u32(pc + 1); + val = get_u32(pc + 5); + pc += 9; + + /* decrement the counter */ + val2 = capture[2 * capture_count + idx] - 1; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2); + + if (val2 > limit) { + /* normal loop if counter > limit */ + pc += (int)val; + LRE_POLL_INTERRUPT(); + } else { + /* check advance */ + if ((opcode == REOP_loop_check_adv_split_goto_first || + opcode == REOP_loop_check_adv_split_next_first) && + capture[2 * capture_count + idx + 1] == (cptr - cbuf) && + val2 != limit) { + goto no_match; + } + + /* otherwise conditional split */ + if (val2 != 0) { + CHECK_STACK_SPACE(3); + if (opcode == REOP_loop_split_next_first || + opcode == REOP_loop_check_adv_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + sp -= 3; + sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT); + sp[1] = JS_NewShortInt(cptr - cbuf); + sp[2] = SP_TO_VALUE(ctx, bp); + bp = sp; + } + } + } + break; + case REOP_set_char_pos: + idx = pc[0]; + pc++; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, cptr - cbuf); + break; + case REOP_check_advance: + idx = pc[0]; + pc++; + if (capture[2 * capture_count + idx] == cptr - cbuf) + goto no_match; + break; + case REOP_word_boundary: + case REOP_not_word_boundary: + { + BOOL v1, v2; + BOOL is_boundary = (opcode == REOP_word_boundary); + /* char before */ + if (cptr == cbuf) { + v1 = FALSE; + } else { + PEEK_PREV_CHAR(c, cptr, cbuf); + v1 = is_word_char(c); + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = FALSE; + } else { + PEEK_CHAR(c, cptr, cbuf_end); + v2 = is_word_char(c); + } + if (v1 ^ v2 ^ is_boundary) + goto no_match; + } + break; + /* assumption: 8 bit and small number of ranges */ + case REOP_range8: + { + int n, i; + n = pc[0]; + pc++; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + for(i = 0; i < n - 1; i++) { + if (c >= pc[2 * i] && c < pc[2 * i + 1]) + goto range8_match; + } + /* 0xff = max code point value */ + if (c >= pc[2 * i] && + (c < pc[2 * i + 1] || pc[2 * i + 1] == 0xff)) + goto range8_match; + goto no_match; + range8_match: + pc += 2 * n; + } + break; + case REOP_range: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end || n == 0) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + idx_min = 0; + low = get_u32(pc + 0 * 8); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u32(pc + idx_max * 8 + 4); + if (c >= high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u32(pc + idx * 8); + high = get_u32(pc + idx * 8 + 4); + if (c < low) + idx_max = idx - 1; + else if (c >= high) + idx_min = idx + 1; + else + goto range_match; + } + goto no_match; + range_match: + pc += 8 * n; + } + break; + case REOP_back_reference: + case REOP_back_reference_i: + val = pc[0]; + pc++; + if (capture[2 * val] != -1 && capture[2 * val + 1] != -1) { + const uint8_t *cptr1, *cptr1_end; + int c1, c2; + + cptr1 = cbuf + capture[2 * val]; + cptr1_end = cbuf + capture[2 * val + 1]; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end); + GET_CHAR(c2, cptr, cbuf_end); + if (opcode == REOP_back_reference_i) { + c1 = lre_canonicalize(c1); + c2 = lre_canonicalize(c2); + } + if (c1 != c2) + goto no_match; + } + } + break; + default: +#ifdef DUMP_REEXEC + printf("unknown opcode pc=%ld\n", pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN); +#endif + abort(); + } + } +} + +/* regexp js interface */ + +/* return the length */ +static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf) +{ + const uint8_t *p = buf; + int mask, re_flags; + re_flags = 0; + while (*p != '\0') { + switch(*p) { +#if 0 + case 'd': + mask = LRE_FLAG_INDICES; + break; +#endif + case 'g': + mask = LRE_FLAG_GLOBAL; + break; + case 'i': + mask = LRE_FLAG_IGNORECASE; + break; + case 'm': + mask = LRE_FLAG_MULTILINE; + break; + case 's': + mask = LRE_FLAG_DOTALL; + break; + case 'u': + mask = LRE_FLAG_UNICODE; + break; +#if 0 + case 'v': + mask = LRE_FLAG_UNICODE_SETS; + break; +#endif + case 'y': + mask = LRE_FLAG_STICKY; + break; + default: + goto done; + } + if ((re_flags & mask) != 0) + break; + re_flags |= mask; + p++; + } + done: + *pre_flags = re_flags; + return p - buf; +} + +/* pattern and flags must be strings */ +static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, JSValue flags) +{ + int re_flags; + + re_flags = 0; + if (!JS_IsUndefined(flags)) { + JSString *ps; + JSStringCharBuf buf; + size_t len; + ps = get_string_ptr(ctx, &buf, flags); + len = js_parse_regexp_flags(&re_flags, ps->buf); + if (len != ps->len) + return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); + } + + return JS_Parse2(ctx, pattern, NULL, 0, "", + JS_EVAL_REGEXP | (re_flags << JS_EVAL_REGEXP_FLAGS_SHIFT)); +} + +static JSRegExp *js_get_regexp(JSContext *ctx, JSValue obj) +{ + JSObject *p; + p = js_get_object_class(ctx, obj, JS_CLASS_REGEXP); + if (!p) { + JS_ThrowTypeError(ctx, "not a regular expression"); + return NULL; + } + return &p->u.regexp; +} + +JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + return JS_NewInt32(ctx, re->last_index); +} + +JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + /* XXX: not complete */ + return re->source; +} + +JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re; + int last_index; + if (JS_ToInt32(ctx, &last_index, argv[0])) + return JS_EXCEPTION; + re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + re->last_index = last_index; + return JS_UNDEFINED; +} + +#define RE_FLAG_COUNT 6 + +/* return the string length */ +static size_t js_regexp_flags_str(char *buf, int re_flags) +{ + static const char flag_char[RE_FLAG_COUNT] = { 'g', 'i', 'm', 's', 'u', 'y' }; + char *p = buf; + int i; + + for(i = 0; i < RE_FLAG_COUNT; i++) { + if ((re_flags >> i) & 1) + *p++ = flag_char[i]; + } + *p = '\0'; + return p - buf; +} + +static void dump_regexp(JSContext *ctx, JSObject *p) +{ + JSStringCharBuf buf; + JSString *ps; + char buf2[RE_FLAG_COUNT + 1]; + JSByteArray *arr; + + js_putchar(ctx, '/'); + ps = get_string_ptr(ctx, &buf, p->u.regexp.source); + if (ps->len == 0) { + js_printf(ctx, "(?:)"); + } else { + js_printf(ctx, "%" JSValue_PRI, p->u.regexp.source); + } + arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code); + js_regexp_flags_str(buf2, lre_get_flags(arr->buf)); + js_printf(ctx, "/%s", buf2); +} + +JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re; + JSByteArray *arr; + size_t len; + char buf[RE_FLAG_COUNT + 1]; + + re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + arr = JS_VALUE_TO_PTR(re->byte_code); + len = js_regexp_flags_str(buf, lre_get_flags(arr->buf)); + return JS_NewStringLen(ctx, buf, len); +} + +JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj, byte_code; + JSObject *p; + JSGCRef byte_code_ref; + + argc &= ~FRAME_CF_CTOR; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + if (!JS_IsUndefined(argv[1])) { + argv[1] = JS_ToString(ctx, argv[1]); + if (JS_IsException(argv[1])) + return JS_EXCEPTION; + } + byte_code = js_compile_regexp(ctx, argv[0], argv[1]); + if (JS_IsException(byte_code)) + return JS_EXCEPTION; + JS_PUSH_VALUE(ctx, byte_code); + obj = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp)); + JS_POP_VALUE(ctx, byte_code); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.regexp.source = argv[0]; + p->u.regexp.byte_code = byte_code; + p->u.regexp.last_index = 0; + return obj; +} + +enum { + MAGIC_REGEXP_EXEC, + MAGIC_REGEXP_TEST, + MAGIC_REGEXP_SEARCH, + MAGIC_REGEXP_FORCE_GLOBAL, /* same as exec but force the global flag */ +}; + +JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSObject *p; + JSRegExp *re; + JSValue obj, *capture_buf, res; + uint32_t *capture, last_index_utf8; + int rc, capture_count, i, re_flags, last_index; + JSByteArray *bc_arr, *carr; + JSGCRef capture_buf_ref, obj_ref; + JSString *str; + JSStringCharBuf str_buf; + + re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + last_index = max_int(re->last_index, 0); + + bc_arr = JS_VALUE_TO_PTR(re->byte_code); + re_flags = lre_get_flags(bc_arr->buf); + if (magic == MAGIC_REGEXP_FORCE_GLOBAL) + re_flags |= MAGIC_REGEXP_FORCE_GLOBAL; + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0 || + magic == MAGIC_REGEXP_SEARCH) { + last_index = 0; + } + capture_count = lre_get_capture_count(bc_arr->buf); + + carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf)); + if (!carr) + goto fail; + capture_buf = JS_PushGCRef(ctx, &capture_buf_ref); + *capture_buf = JS_VALUE_FROM_PTR(carr); + capture = (uint32_t *)carr->buf; + for(i = 0; i < 2 * capture_count; i++) + capture[i] = -1; + + if (last_index <= 0) + last_index_utf8 = 0; + else + last_index_utf8 = js_string_utf16_to_utf8_pos(ctx, argv[0], last_index) / 2; + if (last_index_utf8 > js_string_byte_len(ctx, argv[0])) { + rc = 2; + } else { + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + str = get_string_ptr(ctx, &str_buf, argv[0]); + /* JS_VALUE_FROM_PTR(str) is acceptable here because the + GC ignores pointers outside the heap */ + rc = lre_exec(ctx, *capture_buf, re->byte_code, JS_VALUE_FROM_PTR(str), + last_index_utf8); + } + if (rc != 1) { + if (rc >= 0) { + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + re->last_index = 0; + } + if (magic == MAGIC_REGEXP_SEARCH) + obj = JS_NewShortInt(-1); + else if (magic == MAGIC_REGEXP_TEST) + obj = JS_FALSE; + else + obj = JS_NULL; + } else { + goto fail; + } + } else { + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + if (magic == MAGIC_REGEXP_SEARCH) { + obj = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2)); + goto done; + } + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + re->last_index = js_string_utf8_to_utf16_pos(ctx, argv[0], capture[1] * 2); + } + if (magic == MAGIC_REGEXP_TEST) { + obj = JS_TRUE; + } else { + obj = JS_NewArray(ctx, capture_count); + if (JS_IsException(obj)) + goto fail; + + JS_PUSH_VALUE(ctx, obj); + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_index), + JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2))); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(res)) + goto fail; + + JS_PUSH_VALUE(ctx, obj); + res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_input), + argv[0]); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(res)) + goto fail; + + for(i = 0; i < capture_count; i++) { + int start, end; + JSValue val; + + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + start = capture[2 * i]; + end = capture[2 * i + 1]; + if (start != -1 && end != -1) { + JSValueArray *arr; + JS_PUSH_VALUE(ctx, obj); + val = js_sub_string_utf8(ctx, argv[0], 2 * start, 2 * end); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(val)) + goto fail; + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + arr->arr[i] = val; + } + } + } + } + done: + JS_PopGCRef(ctx, &capture_buf_ref); + return obj; + fail: + obj = JS_EXCEPTION; + goto done; +} + +/* if regexp replace: capture_buf != NULL, needle = NULL + if string replace: capture_buf = NULL, captures_len = 1, needle != NULL +*/ +static int js_string_concat_subst(JSContext *ctx, StringBuffer *b, + JSValue *str, JSValue *rep, + uint32_t pos, uint32_t end_of_match, + JSValue *capture_buf, uint32_t captures_len, + JSValue *needle) +{ + JSStringCharBuf buf_rep; + JSString *p; + int rep_len, i, j, j0, c, k; + + if (JS_IsFunction(ctx, *rep)) { + JSValue res, val; + JSGCRef val_ref; + int ret; + + if (JS_StackCheck(ctx, 4 + captures_len)) + return -1; + JS_PushArg(ctx, *str); + JS_PushArg(ctx, JS_NewShortInt(pos)); + if (capture_buf) { + for(k = captures_len - 1; k >= 0; k--) { + uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) { + val = js_sub_string_utf8(ctx, *str, captures[2 * k] * 2, captures[2 * k + 1] * 2); + if (JS_IsException(val)) + return -1; + JS_PUSH_VALUE(ctx, val); + ret = JS_StackCheck(ctx, 3 + k); + JS_POP_VALUE(ctx, val); + if (ret) + return -1; + } else { + val = JS_UNDEFINED; + } + JS_PushArg(ctx, val); + } + } else { + JS_PushArg(ctx, *needle); + } + JS_PushArg(ctx, *rep); /* function */ + JS_PushArg(ctx, JS_UNDEFINED); /* this_val */ + res = JS_Call(ctx, 2 + captures_len); + if (JS_IsException(res)) + return -1; + return string_buffer_concat(ctx, b, res); + } + + p = get_string_ptr(ctx, &buf_rep, *rep); + rep_len = p->len; + i = 0; + for(;;) { + p = get_string_ptr(ctx, &buf_rep, *rep); + j = i; + while (j < rep_len && p->buf[j] != '$') + j++; + if (j + 1 >= rep_len) + break; + j0 = j++; /* j0 = position of '$' */ + c = p->buf[j++]; + string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * j0); + if (c == '$') { + string_buffer_putc(ctx, b, '$'); + } else if (c == '&') { + if (capture_buf) { + string_buffer_concat_utf16(ctx, b, *str, pos, end_of_match); + } else { + string_buffer_concat_str(ctx, b, *needle); + } + } else if (c == '`') { + string_buffer_concat_utf16(ctx, b, *str, 0, pos); + } else if (c == '\'') { + string_buffer_concat_utf16(ctx, b, *str, end_of_match, js_string_len(ctx, *str)); + } else if (c >= '0' && c <= '9') { + k = c - '0'; + if (j < rep_len) { + c = p->buf[j]; + if (c >= '0' && c <= '9') { + k = k * 10 + c - '0'; + j++; + } + } + if (k >= 1 && k < captures_len) { + uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) { + string_buffer_concat_utf8(ctx, b, *str, + captures[2 * k] * 2, captures[2 * k + 1] * 2); + } + } else { + goto no_rep; + } + } else { + no_rep: + string_buffer_concat_utf8(ctx, b, *rep, 2 * j0, 2 * j); + } + i = j; + } + return string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * rep_len); +} + +JSValue js_string_replace(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_replaceAll) +{ + StringBuffer b_s, *b = &b_s; + int pos, endOfLastMatch, needle_len, input_len; + BOOL is_first, is_regexp; + + *this_val = JS_ToString(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP); + if (!is_regexp) { + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + } + if (!JS_IsFunction(ctx, argv[1])) { + argv[1] = JS_ToString(ctx, argv[1]); + if (JS_IsException(argv[1])) + return JS_EXCEPTION; + } + input_len = js_string_len(ctx, *this_val); + endOfLastMatch = 0; + + string_buffer_push(ctx, b, 0); + + if (is_regexp) { + int start, end, last_index, ret, re_flags, i, capture_count; + JSObject *p; + JSByteArray *bc_arr, *carr; + JSValue *capture_buf; + uint32_t *capture; + JSGCRef capture_buf_ref; + + p = JS_VALUE_TO_PTR(argv[0]); + bc_arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code); + re_flags = lre_get_flags(bc_arr->buf); + capture_count = lre_get_capture_count(bc_arr->buf); + + if (re_flags & LRE_FLAG_GLOBAL) + p->u.regexp.last_index = 0; + + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { + last_index = 0; + } else { + last_index = max_int(p->u.regexp.last_index, 0); + } + + carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf)); + if (!carr) { + string_buffer_pop(ctx, b); + return JS_EXCEPTION; + } + capture_buf = JS_PushGCRef(ctx, &capture_buf_ref); + *capture_buf = JS_VALUE_FROM_PTR(carr); + capture = (uint32_t *)carr->buf; + for(i = 0; i < 2 * capture_count; i++) + capture[i] = -1; + + for(;;) { + if (last_index > input_len) { + ret = 0; + } else { + JSString *str; + JSStringCharBuf str_buf; + p = JS_VALUE_TO_PTR(argv[0]); + str = get_string_ptr(ctx, &str_buf, *this_val); + /* JS_VALUE_FROM_PTR(str) is acceptable here because the + GC ignores pointers outside the heap */ + ret = lre_exec(ctx, *capture_buf, p->u.regexp.byte_code, + JS_VALUE_FROM_PTR(str), + js_string_utf16_to_utf8_pos(ctx, *this_val, last_index) / 2); + } + if (ret < 0) { + JS_PopGCRef(ctx, &capture_buf_ref); + string_buffer_pop(ctx, b); + return JS_EXCEPTION; + } + if (ret == 0) { + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + p = JS_VALUE_TO_PTR(argv[0]); + p->u.regexp.last_index = 0; + } + break; + } + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + start = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[0] * 2); + end = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[1] * 2); + string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, start); + js_string_concat_subst(ctx, b, this_val, &argv[1], + start, end, capture_buf, capture_count, NULL); + endOfLastMatch = end; + if (!(re_flags & LRE_FLAG_GLOBAL)) { + if (re_flags & LRE_FLAG_STICKY) { + p = JS_VALUE_TO_PTR(argv[0]); + p->u.regexp.last_index = end; + } + break; + } + if (end == start) { + int c = string_getcp(ctx, *this_val, end, TRUE); + /* since regexp are unicode by default, replace is also unicode by default */ + end += 1 + (c >= 0x10000); + } + last_index = end; + } + JS_PopGCRef(ctx, &capture_buf_ref); + } else { + needle_len = js_string_len(ctx, argv[0]); + + is_first = TRUE; + for(;;) { + if (unlikely(needle_len == 0)) { + if (is_first) + pos = 0; + else if (endOfLastMatch >= input_len) + pos = -1; + else + pos = endOfLastMatch + 1; + } else { + pos = js_string_indexof(ctx, *this_val, argv[0], endOfLastMatch, + input_len, needle_len); + } + if (pos < 0) { + if (is_first) { + string_buffer_pop(ctx, b); + return *this_val; + } else { + break; + } + } + + string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, pos); + + js_string_concat_subst(ctx, b, this_val, &argv[1], + pos, pos + needle_len, NULL, 1, &argv[0]); + + endOfLastMatch = pos + needle_len; + is_first = FALSE; + if (!is_replaceAll) + break; + } + } + string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, input_len); + return string_buffer_pop(ctx, b); +} + +// split(sep, limit) +JSValue js_string_split(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue *A, T, ret, *z; + uint32_t lim, lengthA; + int p, q, s, e; + BOOL undef_sep; + JSGCRef A_ref, z_ref; + BOOL is_regexp; + + *this_val = JS_ToString(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[1])) { + lim = 0xffffffff; + } else { + if (JS_ToUint32(ctx, &lim, argv[1]) < 0) + return JS_EXCEPTION; + } + is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP); + if (!is_regexp) { + undef_sep = JS_IsUndefined(argv[0]); + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + } else { + undef_sep = FALSE; + } + + A = JS_PushGCRef(ctx, &A_ref); + z = JS_PushGCRef(ctx, &z_ref); + *A = JS_NewArray(ctx, 0); + if (JS_IsException(*A)) + goto exception; + lengthA = 0; + + s = js_string_len(ctx, *this_val); + p = 0; + if (lim == 0) + goto done; + if (undef_sep) + goto add_tail; + + if (is_regexp) { + int numberOfCaptures, i, re_flags; + JSObject *p1; + JSValueArray *arr; + JSByteArray *bc_arr; + + p1 = JS_VALUE_TO_PTR(argv[0]); + bc_arr = JS_VALUE_TO_PTR(p1->u.regexp.byte_code); + re_flags = lre_get_flags(bc_arr->buf); + + if (s == 0) { + p1 = JS_VALUE_TO_PTR(argv[0]); + p1->u.regexp.last_index = 0; + *z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL); + if (JS_IsException(*z)) + goto exception; + if (JS_IsNull(*z)) + goto add_tail; + goto done; + } + q = 0; + while (q < s) { + p1 = JS_VALUE_TO_PTR(argv[0]); + p1->u.regexp.last_index = q; + /* XXX: need sticky behavior */ + *z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL); + if (JS_IsException(*z)) + goto exception; + if (JS_IsNull(*z)) { + if (!(re_flags & LRE_FLAG_STICKY)) { + break; + } else { + int c = string_getcp(ctx, *this_val, q, TRUE); + /* since regexp are unicode by default, split is also unicode by default */ + q += 1 + (c >= 0x10000); + } + } else { + if (!(re_flags & LRE_FLAG_STICKY)) { + JSValue res; + res = JS_GetProperty(ctx, *z, js_get_atom(ctx, JS_ATOM_index)); + if (JS_IsException(res)) + goto exception; + q = JS_VALUE_GET_INT(res); + } + p1 = JS_VALUE_TO_PTR(argv[0]); + e = p1->u.regexp.last_index; + if (e > s) + e = s; + if (e == p) { + int c = string_getcp(ctx, *this_val, q, TRUE); + /* since regexp are unicode by default, split is also unicode by default */ + q += 1 + (c >= 0x10000); + } else { + T = js_sub_string(ctx, *this_val, p, q); + if (JS_IsException(T)) + goto exception; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + if (lengthA == lim) + goto done; + p1 = JS_VALUE_TO_PTR(*z); + numberOfCaptures = p1->u.array.len; + for(i = 1; i < numberOfCaptures; i++) { + p1 = JS_VALUE_TO_PTR(*z); + arr = JS_VALUE_TO_PTR(p1->u.array.tab); + T = arr->arr[i]; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + } + q = p = e; + } + } + } + } else { + int r = js_string_len(ctx, argv[0]); + if (s == 0) { + if (r != 0) + goto add_tail; + goto done; + } + + for (q = 0; (q += !r) <= s - r - !r; q = p = e + r) { + + e = js_string_indexof(ctx, *this_val, argv[0], q, s, r); + if (e < 0) + break; + T = js_sub_string(ctx, *this_val, p, e); + if (JS_IsException(T)) + goto exception; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + if (lengthA == lim) + goto done; + } + } +add_tail: + T = js_sub_string(ctx, *this_val, p, s); + if (JS_IsException(T)) + goto exception; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + +done: + JS_PopGCRef(ctx, &z_ref); + return JS_PopGCRef(ctx, &A_ref); + +exception: + JS_PopGCRef(ctx, &z_ref); + JS_PopGCRef(ctx, &A_ref); + return JS_EXCEPTION; +} + +JSValue js_string_match(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re; + int global, n; + JSValue *A, *result, ret; + JSObject *p; + JSValueArray *arr; + JSByteArray *barr; + JSGCRef A_ref, result_ref; + + re = js_get_regexp(ctx, argv[0]); + if (!re) + return JS_EXCEPTION; + barr = JS_VALUE_TO_PTR(re->byte_code); + global = lre_get_flags(barr->buf) & LRE_FLAG_GLOBAL; + if (!global) + return js_regexp_exec(ctx, &argv[0], 1, this_val, 0); + + p = JS_VALUE_TO_PTR(argv[0]); + re = &p->u.regexp; + re->last_index = 0; + + A = JS_PushGCRef(ctx, &A_ref); + result = JS_PushGCRef(ctx, &result_ref); + *A = JS_NULL; + n = 0; + for(;;) { + *result = js_regexp_exec(ctx, &argv[0], 1, this_val, 0); + if (JS_IsException(*result)) + goto fail; + if (*result == JS_NULL) + break; + if (*A == JS_NULL) { + *A = JS_NewArray(ctx, 1); + if (JS_IsException(*A)) + goto fail; + } + + p = JS_VALUE_TO_PTR(*result); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + + ret = JS_SetPropertyUint32(ctx, *A, n++, arr->arr[0]); + if (JS_IsException(ret)) { + fail: + *A = JS_EXCEPTION; + break; + } + } + JS_PopGCRef(ctx, &result_ref); + return JS_PopGCRef(ctx, &A_ref); +} + +JSValue js_string_search(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_SEARCH); +} diff --git a/mquickjs.h b/mquickjs.h new file mode 100644 index 00000000..a1557fe9 --- /dev/null +++ b/mquickjs.h @@ -0,0 +1,382 @@ +/* + * Micro QuickJS Javascript Engine + * + * 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 MQUICKJS_H +#define MQUICKJS_H + +#include + +#if defined(__GNUC__) || defined(__clang__) +#define __js_printf_like(f, a) __attribute__((format(printf, f, a))) +#else +#define __js_printf_like(a, b) +#endif + +#if INTPTR_MAX >= INT64_MAX +#define JS_PTR64 /* pointers are 64 bit wide instead of 32 bit wide */ +#endif + +typedef struct JSContext JSContext; + +#ifdef JS_PTR64 +typedef uint64_t JSWord; +typedef uint64_t JSValue; +#define JSW 8 +#define JSValue_PRI PRIo64 +#define JS_USE_SHORT_FLOAT +#else +typedef uint32_t JSWord; +typedef uint32_t JSValue; +#define JSW 4 +#define JSValue_PRI PRIo32 +#endif + +#define JS_BOOL int + +enum { + JS_TAG_INT = 0, /* 31 bit integer (1 bit) */ + JS_TAG_PTR = 1, /* pointer (2 bits) */ + JS_TAG_SPECIAL = 3, /* other special values (2 bits) */ + JS_TAG_BOOL = JS_TAG_SPECIAL | (0 << 2), /* (5 bits) */ + JS_TAG_NULL = JS_TAG_SPECIAL | (1 << 2), /* (5 bits) */ + JS_TAG_UNDEFINED = JS_TAG_SPECIAL | (2 << 2), /* (5 bits) */ + JS_TAG_EXCEPTION = JS_TAG_SPECIAL | (3 << 2), /* (5 bits) */ + JS_TAG_SHORT_FUNC = JS_TAG_SPECIAL | (4 << 2), /* (5 bits) */ + JS_TAG_UNINITIALIZED = JS_TAG_SPECIAL | (5 << 2), /* (5 bits) */ + JS_TAG_STRING_CHAR = JS_TAG_SPECIAL | (6 << 2), /* (5 bits) */ + JS_TAG_CATCH_OFFSET = JS_TAG_SPECIAL | (7 << 2), /* (5 bits) */ +#ifdef JS_USE_SHORT_FLOAT + JS_TAG_SHORT_FLOAT = 5, /* 3 bits */ +#endif +}; + +#define JS_TAG_SPECIAL_BITS 5 + +#define JS_VALUE_GET_INT(v) ((int)(v) >> 1) +#define JS_VALUE_GET_SPECIAL_VALUE(v) ((int)(v) >> JS_TAG_SPECIAL_BITS) +#define JS_VALUE_GET_SPECIAL_TAG(v) ((v) & ((1 << JS_TAG_SPECIAL_BITS) - 1)) +#define JS_VALUE_MAKE_SPECIAL(tag, v) ((tag) | ((v) << JS_TAG_SPECIAL_BITS)) + +#define JS_NULL JS_VALUE_MAKE_SPECIAL(JS_TAG_NULL, 0) +#define JS_UNDEFINED JS_VALUE_MAKE_SPECIAL(JS_TAG_UNDEFINED, 0) +#define JS_UNINITIALIZED JS_VALUE_MAKE_SPECIAL(JS_TAG_UNINITIALIZED, 0) +#define JS_FALSE JS_VALUE_MAKE_SPECIAL(JS_TAG_BOOL, 0) +#define JS_TRUE JS_VALUE_MAKE_SPECIAL(JS_TAG_BOOL, 1) + +#define JS_EX_NORMAL 0 /* all exceptions except not enough memory */ +#define JS_EX_CALL 1 /* specific exception to generate a tail call. The call flags are added */ +#define JS_EXCEPTION JS_VALUE_MAKE_SPECIAL(JS_TAG_EXCEPTION, JS_EX_NORMAL) + +typedef enum { + JS_CLASS_OBJECT, + JS_CLASS_ARRAY, + JS_CLASS_C_FUNCTION, + JS_CLASS_CLOSURE, + JS_CLASS_NUMBER, + JS_CLASS_BOOLEAN, + JS_CLASS_STRING, + JS_CLASS_DATE, + JS_CLASS_REGEXP, + + JS_CLASS_ERROR, + JS_CLASS_EVAL_ERROR, + JS_CLASS_RANGE_ERROR, + JS_CLASS_REFERENCE_ERROR, + JS_CLASS_SYNTAX_ERROR, + JS_CLASS_TYPE_ERROR, + JS_CLASS_URI_ERROR, + JS_CLASS_INTERNAL_ERROR, + + JS_CLASS_ARRAY_BUFFER, + JS_CLASS_TYPED_ARRAY, + + JS_CLASS_UINT8C_ARRAY, + JS_CLASS_INT8_ARRAY, + JS_CLASS_UINT8_ARRAY, + JS_CLASS_INT16_ARRAY, + JS_CLASS_UINT16_ARRAY, + JS_CLASS_INT32_ARRAY, + JS_CLASS_UINT32_ARRAY, + JS_CLASS_FLOAT32_ARRAY, + JS_CLASS_FLOAT64_ARRAY, + + JS_CLASS_USER, /* user classes start from this value */ +} JSObjectClassEnum; + +/* predefined functions */ +typedef enum { + JS_CFUNCTION_bound, + JS_CFUNCTION_USER, /* user functions start from this value */ +} JSCFunctionEnum; + +/* temporary buffer to hold C strings */ +typedef struct { + uint8_t buf[5]; +} JSCStringBuf; + +typedef struct JSGCRef { + JSValue val; + struct JSGCRef *prev; +} JSGCRef; + +/* stack of JSGCRef */ +JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref); +JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref); + +#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0) +#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref) + +/* list of JSGCRef (they can be removed in any order, slower) */ +JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref); +void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref); + +JSValue JS_NewFloat64(JSContext *ctx, double d); +JSValue JS_NewInt32(JSContext *ctx, int32_t val); +JSValue JS_NewUint32(JSContext *ctx, uint32_t val); +JSValue JS_NewInt64(JSContext *ctx, int64_t val); + +static inline JS_BOOL JS_IsInt(JSValue v) +{ + return (v & 1) == JS_TAG_INT; +} + +static inline JS_BOOL JS_IsPtr(JSValue v) +{ + return (v & (JSW - 1)) == JS_TAG_PTR; +} + +#ifdef JS_USE_SHORT_FLOAT +static inline JS_BOOL JS_IsShortFloat(JSValue v) +{ + return (v & (JSW - 1)) == JS_TAG_SHORT_FLOAT; +} +#endif + +static inline JS_BOOL JS_IsBool(JSValue v) +{ + return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_BOOL; +} + +static inline JS_BOOL JS_IsNull(JSValue v) +{ + return v == JS_NULL; +} + +static inline JS_BOOL JS_IsUndefined(JSValue v) +{ + return v == JS_UNDEFINED; +} + +static inline JS_BOOL JS_IsUninitialized(JSValue v) +{ + return v == JS_UNINITIALIZED; +} + +static inline JS_BOOL JS_IsException(JSValue v) +{ + return v == JS_EXCEPTION; +} + +static inline JSValue JS_NewBool(int val) +{ + return JS_VALUE_MAKE_SPECIAL(JS_TAG_BOOL, (val != 0)); +} + +JS_BOOL JS_IsNumber(JSContext *ctx, JSValue val); +JS_BOOL JS_IsString(JSContext *ctx, JSValue val); +JS_BOOL JS_IsError(JSContext *ctx, JSValue val); +JS_BOOL JS_IsFunction(JSContext *ctx, JSValue val); + +int JS_GetClassID(JSContext *ctx, JSValue val); +void JS_SetOpaque(JSContext *ctx, JSValue val, void *opaque); +void *JS_GetOpaque(JSContext *ctx, JSValue val); + +typedef JSValue JSCFunction(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +/* no JS function call be called from a C finalizer */ +typedef void (*JSCFinalizer)(JSContext *ctx, void *opaque); + +typedef enum JSCFunctionDefEnum { /* XXX: should rename for namespace isolation */ + JS_CFUNC_generic, + JS_CFUNC_generic_magic, + JS_CFUNC_constructor, + JS_CFUNC_constructor_magic, + JS_CFUNC_generic_params, + JS_CFUNC_f_f, +} JSCFunctionDefEnum; + +typedef union JSCFunctionType { + JSCFunction *generic; + JSValue (*generic_magic)(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic); + JSCFunction *constructor; + JSValue (*constructor_magic)(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic); + JSValue (*generic_params)(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, JSValue params); + double (*f_f)(double f); +} JSCFunctionType; + +typedef struct JSCFunctionDef { + JSCFunctionType func; + JSValue name; + uint8_t def_type; + uint8_t arg_count; + int16_t magic; +} JSCFunctionDef; + +typedef struct { + const JSWord *stdlib_table; + const JSCFunctionDef *c_function_table; + const JSCFinalizer *c_finalizer_table; + uint32_t stdlib_table_len; + uint32_t stdlib_table_align; + uint32_t sorted_atoms_offset; + uint32_t global_object_offset; + uint32_t class_count; +} JSSTDLibraryDef; + +typedef void JSWriteFunc(void *opaque, const void *buf, size_t buf_len); +/* return != 0 if the JS code needs to be interrupted */ +typedef int JSInterruptHandler(JSContext *ctx, void *opaque); + +JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def); +/* if prepare_compilation is true, the context will be used to compile + to a binary file. JS_NewContext2() is not expected to be used in + the embedded version */ +JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, JS_BOOL prepare_compilation); +void JS_FreeContext(JSContext *ctx); +void JS_SetContextOpaque(JSContext *ctx, void *opaque); +void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler); +void JS_SetRandomSeed(JSContext *ctx, uint64_t seed); +JSValue JS_GetGlobalObject(JSContext *ctx); +JSValue JS_Throw(JSContext *ctx, JSValue obj); +JSValue __js_printf_like(3, 4) JS_ThrowError(JSContext *ctx, JSObjectClassEnum error_num, + const char *fmt, ...); +#define JS_ThrowTypeError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_TYPE_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowReferenceError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_REFERENCE_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowInternalError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_INTERNAL_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowRangeError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_RANGE_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowSyntaxError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_SYNTAX_ERROR, fmt, ##__VA_ARGS__) +JSValue JS_ThrowOutOfMemory(JSContext *ctx); +JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, const char *str); +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue obj, uint32_t idx); +JSValue JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *str, JSValue val); +JSValue JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val); +JSValue JS_NewObjectClassUser(JSContext *ctx, int class_id); +JSValue JS_NewObject(JSContext *ctx); +JSValue JS_NewArray(JSContext *ctx, int initial_len); +/* create a C function with an object parameter (closure) */ +JSValue JS_NewCFunctionParams(JSContext *ctx, int func_idx, JSValue params); + +#define JS_EVAL_RETVAL (1 << 0) /* return the last value instead of undefined (slower code) */ +#define JS_EVAL_REPL (1 << 1) /* implicitly defined global variables in assignments */ +#define JS_EVAL_STRIP_COL (1 << 2) /* strip column number debug information (save memory) */ +#define JS_EVAL_JSON (1 << 3) /* parse as JSON and return the object */ +#define JS_EVAL_REGEXP (1 << 4) /* internal use */ +#define JS_EVAL_REGEXP_FLAGS_SHIFT 8 /* internal use */ +JSValue JS_Parse(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +JSValue JS_Run(JSContext *ctx, JSValue val); +JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +void JS_GC(JSContext *ctx); +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len); +JSValue JS_NewString(JSContext *ctx, const char *buf); +const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val, JSCStringBuf *buf); +const char *JS_ToCString(JSContext *ctx, JSValue val, JSCStringBuf *buf); +JSValue JS_ToString(JSContext *ctx, JSValue val); +int JS_ToInt32(JSContext *ctx, int *pres, JSValue val); +int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val); +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val); +int JS_ToNumber(JSContext *ctx, double *pres, JSValue val); + +JSValue JS_GetException(JSContext *ctx); +int JS_StackCheck(JSContext *ctx, uint32_t len); +void JS_PushArg(JSContext *ctx, JSValue val); +#define FRAME_CF_CTOR (1 << 16) /* also ored with argc in + C constructors */ +JSValue JS_Call(JSContext *ctx, int call_flags); + +#define JS_BYTECODE_MAGIC 0xacfb + +typedef struct { + uint16_t magic; /* JS_BYTECODE_MAGIC */ + uint16_t version; + uintptr_t base_addr; + JSValue unique_strings; + JSValue main_func; +} JSBytecodeHeader; + +/* only used on the host when compiling to file */ +void JS_PrepareBytecode(JSContext *ctx, + JSBytecodeHeader *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code); +/* only used on the host when compiling to file */ +int JS_RelocateBytecode2(JSContext *ctx, JSBytecodeHeader *hdr, + uint8_t *buf, uint32_t buf_len, + uintptr_t new_base_addr, JS_BOOL update_atoms); +#if JSW == 8 +typedef struct { + uint16_t magic; /* JS_BYTECODE_MAGIC */ + uint16_t version; + uint32_t base_addr; + uint32_t unique_strings; + uint32_t main_func; +} JSBytecodeHeader32; + +/* only used on the host when compiling to file. A 32 bit bytecode is generated on a 64 bit host. */ +int JS_PrepareBytecode64to32(JSContext *ctx, + JSBytecodeHeader32 *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code); +#endif + +JS_BOOL JS_IsBytecode(const uint8_t *buf, size_t buf_len); +/* Relocate the bytecode in 'buf' so that it can be executed + later. Return 0 if OK, != 0 if error */ +int JS_RelocateBytecode(JSContext *ctx, + uint8_t *buf, uint32_t buf_len); +/* Load the precompiled bytecode from 'buf'. 'buf' must be allocated + as long as the JSContext exists. Use JS_Run() to execute + it. warning: the bytecode is not checked so it should come from a + trusted source. */ +JSValue JS_LoadBytecode(JSContext *ctx, const uint8_t *buf); + +/* debug functions */ +void JS_SetLogFunc(JSContext *ctx, JSWriteFunc *write_func); +void JS_PrintValue(JSContext *ctx, JSValue val); +#define JS_DUMP_LONG (1 << 0) /* display object/array content */ +#define JS_DUMP_NOQUOTE (1 << 1) /* strings: no quote for identifiers */ +/* for low level dumps: don't dump special properties and use specific + quotes to distinguish string chars, unique strings and normal + strings */ +#define JS_DUMP_RAW (1 << 2) +void JS_PrintValueF(JSContext *ctx, JSValue val, int flags); +void JS_DumpValueF(JSContext *ctx, const char *str, + JSValue val, int flags); +void JS_DumpValue(JSContext *ctx, const char *str, + JSValue val); +void JS_DumpMemory(JSContext *ctx, JS_BOOL is_long); + +#endif /* MQUICKJS_H */ diff --git a/mquickjs_build.c b/mquickjs_build.c new file mode 100644 index 00000000..61732715 --- /dev/null +++ b/mquickjs_build.c @@ -0,0 +1,932 @@ +/* + * Micro QuickJS build utility + * + * 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 "list.h" +#include "mquickjs_build.h" + +static unsigned JSW = 4; // override this with -m64 + +typedef struct { + char *str; + int offset; +} AtomDef; + +typedef struct { + AtomDef *tab; + int count; + int size; + int offset; +} AtomList; + +typedef struct { + char *name; + int length; + char *magic; + char *cproto_name; + char *cfunc_name; +} CFuncDef; + +typedef struct { + CFuncDef *tab; + int count; + int size; +} CFuncList; + +typedef struct { + struct list_head link; + const JSClassDef *class1; + int class_idx; + char *finalizer_name; + char *class_id; +} ClassDefEntry; + +typedef struct { + AtomList atom_list; + CFuncList cfunc_list; + int cur_offset; + int sorted_atom_table_offset; + int global_object_offset; + struct list_head class_list; +} BuildContext; + +static const char *atoms[] = { +#define DEF(a, b) b, + /* keywords */ + DEF(null, "null") /* must be first */ + DEF(false, "false") + DEF(true, "true") + DEF(if, "if") + DEF(else, "else") + DEF(return, "return") + DEF(var, "var") + DEF(this, "this") + DEF(delete, "delete") + DEF(void, "void") + DEF(typeof, "typeof") + DEF(new, "new") + DEF(in, "in") + DEF(instanceof, "instanceof") + DEF(do, "do") + DEF(while, "while") + DEF(for, "for") + DEF(break, "break") + DEF(continue, "continue") + DEF(switch, "switch") + DEF(case, "case") + DEF(default, "default") + DEF(throw, "throw") + DEF(try, "try") + DEF(catch, "catch") + DEF(finally, "finally") + DEF(function, "function") + DEF(debugger, "debugger") + DEF(with, "with") + /* FutureReservedWord */ + DEF(class, "class") + DEF(const, "const") + DEF(enum, "enum") + DEF(export, "export") + DEF(extends, "extends") + DEF(import, "import") + DEF(super, "super") + /* FutureReservedWords when parsing strict mode code */ + DEF(implements, "implements") + DEF(interface, "interface") + DEF(let, "let") + DEF(package, "package") + DEF(private, "private") + DEF(protected, "protected") + DEF(public, "public") + DEF(static, "static") + DEF(yield, "yield") +#undef DEF + + /* other atoms */ + "", + "toString", + "valueOf", + "number", + "object", + "undefined", + "string", + "boolean", + "", + "", + "eval", + "arguments", + "value", + "get", + "set", + "prototype", + "constructor", + "length", + "target", + "of", + "NaN", + "Infinity", + "-Infinity", + "name", + "Error", + "__proto__", + "index", + "input", +}; + + +static char *cvt_name(char *buf, size_t buf_size, const char *str) +{ + size_t i, len = strlen(str); + assert(len < buf_size); + if (len == 0) { + strcpy(buf, "empty"); + } else { + strcpy(buf, str); + for(i = 0; i < len; i++) { + if (buf[i] == '<' || buf[i] == '>' || buf[i] == '-') + buf[i] = '_'; + } + } + return buf; +} + +static BOOL is_ascii_string(const char *buf, size_t len) +{ + size_t i; + for(i = 0; i < len; i++) { + if ((uint8_t)buf[i] > 0x7f) + return FALSE; + } + return TRUE; +} + +static BOOL is_numeric_string(const char *buf, size_t len) +{ + return (!strcmp(buf, "NaN") || + !strcmp(buf, "Infinity") || + !strcmp(buf, "-Infinity")); +} + +static int find_atom(AtomList *s, const char *str) +{ + int i; + for(i = 0; i < s->count; i++) { + if (!strcmp(str, s->tab[i].str)) + return i; + } + return -1; +} + +static int add_atom(AtomList *s, const char *str) +{ + int i; + AtomDef *e; + i = find_atom(s, str); + if (i >= 0) + return s->tab[i].offset; + if ((s->count + 1) > s->size) { + s->size = max_int(s->count + 1, s->size * 3 / 2); + s->tab = realloc(s->tab, sizeof(s->tab[0]) * s->size); + } + e = &s->tab[s->count++]; + e->str = strdup(str); + e->offset = s->offset; + s->offset += 1 + ((strlen(str) + JSW) / JSW); + return s->count - 1; +} + +static int add_cfunc(CFuncList *s, const char *name, int length, const char *magic, const char *cproto_name, const char *cfunc_name) +{ + int i; + CFuncDef *e; + + for(i = 0; i < s->count; i++) { + e = &s->tab[i]; + if (!strcmp(name, e->name) && + length == e->length && + !strcmp(magic, e->magic) && + !strcmp(cproto_name, e->cproto_name) && + !strcmp(cfunc_name, e->cfunc_name)) { + return i; + } + } + if ((s->count + 1) > s->size) { + s->size = max_int(s->count + 1, s->size * 3 / 2); + s->tab = realloc(s->tab, sizeof(s->tab[0]) * s->size); + } + e = &s->tab[s->count++]; + e->name = strdup(name); + e->magic = strdup(magic); + e->length = length; + e->cproto_name = strdup(cproto_name); + e->cfunc_name = strdup(cfunc_name); + return s->count - 1; +} + +static void dump_atom_defines(void) +{ + AtomList atom_list_s, *s = &atom_list_s; + AtomDef *e; + int i; + char buf[256]; + + memset(s, 0, sizeof(*s)); + + /* add the predefined atoms (they have a corresponding define) */ + for(i = 0; i < countof(atoms); i++) { + add_atom(s, atoms[i]); + } + + for(i = 0; i < s->count; i++) { + e = &s->tab[i]; + printf("#define JS_ATOM_%s %d\n", + cvt_name(buf, sizeof(buf), e->str), e->offset); + } + printf("\n"); + printf("#define JS_ATOM_END %d\n", s->offset); + printf("\n"); +} + +static int atom_cmp(const void *p1, const void *p2) +{ + const AtomDef *a1 = (const AtomDef *)p1; + const AtomDef *a2 = (const AtomDef *)p2; + return strcmp(a1->str, a2->str); +} + +/* js_atom_table must be properly aligned because the property hash + table uses the low bits of the atom pointer value */ +#define ATOM_ALIGN 64 + +static void dump_atoms(BuildContext *ctx) +{ + AtomList *s = &ctx->atom_list; + int i, j, k, l, len, len1, is_ascii, is_numeric; + uint64_t v; + const char *str; + AtomDef *sorted_atoms; + char buf[256]; + + sorted_atoms = malloc(sizeof(sorted_atoms[0]) * s->count); + memcpy(sorted_atoms, s->tab, sizeof(sorted_atoms[0]) * s->count); + qsort(sorted_atoms, s->count, sizeof(sorted_atoms[0]), atom_cmp); + + printf(" /* atom_table */\n"); + for(i = 0; i < s->count; i++) { + str = s->tab[i].str; + len = strlen(str); + is_ascii = is_ascii_string(str, len); + is_numeric = is_numeric_string(str, len); + printf(" (JS_MTAG_STRING << 1) | (1 << JS_MTAG_BITS) | (%d << (JS_MTAG_BITS + 1)) | (%d << (JS_MTAG_BITS + 2)) | (%d << (JS_MTAG_BITS + 3)), /* \"%s\" (offset=%d) */\n", + is_ascii, is_numeric, len, str, ctx->cur_offset); + len1 = (len + JSW) / JSW; + for(j = 0; j < len1; j++) { + l = min_uint32(JSW, len - j * JSW); + v = 0; + for(k = 0; k < l; k++) + v |= (uint64_t)(uint8_t)str[j * JSW + k] << (k * 8); + printf(" 0x%0*" PRIx64 ",\n", JSW * 2, v); + } + assert(ctx->cur_offset == s->tab[i].offset); + ctx->cur_offset += len1 + 1; + } + printf("\n"); + + ctx->sorted_atom_table_offset = ctx->cur_offset; + + printf(" /* sorted atom table (offset=%d) */\n", ctx->cur_offset); + printf(" JS_VALUE_ARRAY_HEADER(%d),\n", s->count); + for(i = 0; i < s->count; i++) { + AtomDef *e = &sorted_atoms[i]; + printf(" JS_ROM_VALUE(%d), /* %s */\n", + e->offset, cvt_name(buf, sizeof(buf), e->str)); + } + ctx->cur_offset += s->count + 1; + printf("\n"); + + free(sorted_atoms); +} + +static int define_value(BuildContext *s, const JSPropDef *d); + +static uint32_t dump_atom(BuildContext *s, const char *str, BOOL value_only) +{ + int len, idx, i, offset; + + len = strlen(str); + for(i = 0; i < len; i++) { + if ((uint8_t)str[i] >= 128) { + fprintf(stderr, "unicode property names are not supported yet (%s)\n", str); + exit(1); + } + } + if (len >= 1 && (str[0] >= '0' && str[0] <= '9')) { + fprintf(stderr, "numeric property names are not supported yet (%s)\n", str); + exit(1); + } + if (len == 1) { + if (value_only) { + /* XXX: hardcoded */ + return ((uint8_t)str[0] << 5) | 0x1b; + } + printf("JS_VALUE_MAKE_SPECIAL(JS_TAG_STRING_CHAR, %d)", + (uint8_t)str[0]); + } else { + idx = find_atom(&s->atom_list, str); + if (idx < 0) { + fprintf(stderr, "atom '%s' is undefined\n", str); + exit(1); + } + offset = s->atom_list.tab[idx].offset; + if (value_only) + return (offset * JSW) + 1; /* correct modulo ATOM_ALIGN */ + printf("JS_ROM_VALUE(%d)", offset); + } + printf(" /* %s */", str); + return 0; +} + +static void dump_cfuncs(BuildContext *s) +{ + int i; + CFuncDef *e; + + printf("static const JSCFunctionDef js_c_function_table[] = {\n"); + for(i = 0; i < s->cfunc_list.count; i++) { + e = &s->cfunc_list.tab[i]; + printf(" { { .%s = %s },\n", e->cproto_name, e->cfunc_name); + printf(" "); + dump_atom(s, e->name, FALSE); + printf(",\n"); + printf(" JS_CFUNC_%s, %d, %s },\n", + e->cproto_name, e->length, e->magic); + } + printf("};\n\n"); +} + +static void dump_cfinalizers(BuildContext *s) +{ + struct list_head *el; + ClassDefEntry *e; + + printf("static const JSCFinalizer js_c_finalizer_table[JS_CLASS_COUNT - JS_CLASS_USER] = {\n"); + list_for_each(el, &s->class_list) { + e = list_entry(el, ClassDefEntry, link); + if (e->finalizer_name && + strcmp(e->finalizer_name, "NULL") != 0) { + printf(" [%s - JS_CLASS_USER] = %s,\n", e->class_id, e->finalizer_name); + } + } + printf("};\n\n"); +} + +typedef enum { + PROPS_KIND_GLOBAL, + PROPS_KIND_PROTO, + PROPS_KIND_CLASS, + PROPS_KIND_OBJECT, +} JSPropsKindEnum; + +static inline uint32_t hash_prop(BuildContext *s, const char *name) +{ + /* Compute the hash for a symbol, must be consistent with + mquickjs.c implementation. + */ + uint32_t prop = dump_atom(s, name, TRUE); + return (prop / JSW) ^ (prop % JSW); /* XXX: improve */ +} + +static int define_props(BuildContext *s, const JSPropDef *props_def, + JSPropsKindEnum props_kind, const char *class_id_str) +{ + int i, *ident_tab, idx, props_ident, n_props; + int prop_idx; + const JSPropDef *d; + uint32_t *prop_hash; + BOOL is_global_object = (props_kind == PROPS_KIND_GLOBAL); + static const JSPropDef dummy_props[] = { + { JS_DEF_END }, + }; + + if (!props_def) + props_def = dummy_props; + + n_props = 0; + for(d = props_def; d->def_type != JS_DEF_END; d++) { + n_props++; + } + if (props_kind == PROPS_KIND_PROTO || + props_kind == PROPS_KIND_CLASS) + n_props++; + ident_tab = malloc(sizeof(ident_tab[0]) * n_props); + + /* define the various objects */ + for(d = props_def, i = 0; d->def_type != JS_DEF_END; d++, i++) { + ident_tab[i] = define_value(s, d); + } + + props_ident = -1; + prop_hash = NULL; + if (is_global_object) { + props_ident = s->cur_offset; + printf(" /* global object properties (offset=%d) */\n", props_ident); + printf(" JS_VALUE_ARRAY_HEADER(%d),\n", 2 * n_props); + s->cur_offset += 2 * n_props + 1; + } else { + int hash_size_log2; + uint32_t hash_size, hash_mask; + uint32_t *hash_table, h; + + if (n_props <= 1) + hash_size_log2 = 0; + else + hash_size_log2 = (32 - clz32(n_props - 1)) - 1; + hash_size = 1 << hash_size_log2; + if (hash_size > ATOM_ALIGN / JSW) { +#if !defined __APPLE__ + // XXX: Cannot request data alignment larger than 64 bytes on Darwin + fprintf(stderr, "Too many properties, consider increasing ATOM_ALIGN\n"); +#endif + hash_size = ATOM_ALIGN / JSW; + } + hash_mask = hash_size - 1; + + hash_table = malloc(sizeof(hash_table[0]) * hash_size); + prop_hash = malloc(sizeof(prop_hash[0]) * n_props); + /* build the hash table */ + for(i = 0; i < hash_size; i++) + hash_table[i] = 0; + prop_idx = 0; + for(i = 0, d = props_def; i < n_props; i++, d++) { + const char *name; + if (d->def_type != JS_DEF_END) { + name = d->name; + } else { + if (props_kind == PROPS_KIND_PROTO) + name = "constructor"; + else + name = "prototype"; + } + h = hash_prop(s, name) & hash_mask; + prop_hash[prop_idx] = hash_table[h]; + hash_table[h] = 2 + hash_size + 3 * prop_idx; + prop_idx++; + } + + props_ident = s->cur_offset; + printf(" /* properties (offset=%d) */\n", props_ident); + printf(" JS_VALUE_ARRAY_HEADER(%d),\n", 2 + hash_size + n_props * 3); + printf(" %d << 1, /* n_props */\n", n_props); + printf(" %d << 1, /* hash_mask */\n", hash_mask); + for(i = 0; i < hash_size; i++) { + printf(" %d << 1,\n", hash_table[i]); + } + s->cur_offset += hash_size + 3 + 3 * n_props; + free(hash_table); + } + prop_idx = 0; + for(d = props_def, i = 0; i < n_props; d++, i++) { + const char *name, *prop_type; + /* name */ + printf(" "); + if (d->def_type != JS_DEF_END) { + name = d->name; + } else { + if (props_kind == PROPS_KIND_PROTO) + name = "constructor"; + else + name = "prototype"; + } + dump_atom(s, name, FALSE); + printf(",\n"); + + printf(" "); + prop_type = "NORMAL"; + switch(d->def_type) { + case JS_DEF_PROP_DOUBLE: + if (ident_tab[i] >= 0) + goto value_ptr; + /* short int */ + printf("%d << 1,", (int32_t)d->u.f64); + break; + case JS_DEF_CGETSET: + if (is_global_object) { + fprintf(stderr, "getter/setter forbidden in global object\n"); + exit(1); + } + prop_type = "GETSET"; + goto value_ptr; + case JS_DEF_CLASS: + value_ptr: + assert(ident_tab[i] >= 0); + printf("JS_ROM_VALUE(%d),", ident_tab[i]); + break; + case JS_DEF_PROP_UNDEFINED: + printf("JS_UNDEFINED,"); + break; + case JS_DEF_PROP_NULL: + printf("JS_NULL,"); + break; + case JS_DEF_PROP_STRING: + dump_atom(s, d->u.str, FALSE); + printf(","); + break; + case JS_DEF_CFUNC: + idx = add_cfunc(&s->cfunc_list, + d->name, + d->u.func.length, + d->u.func.magic, + d->u.func.cproto_name, + d->u.func.func_name); + printf("JS_VALUE_MAKE_SPECIAL(JS_TAG_SHORT_FUNC, %d),", idx); + break; + case JS_DEF_END: + if (props_kind == PROPS_KIND_PROTO) { + /* constructor property */ + printf("(uint32_t)(-%s - 1) << 1,", class_id_str); + } else { + /* prototype property */ + printf("%s << 1,", class_id_str); + } + prop_type = "SPECIAL"; + break; + default: + abort(); + } + printf("\n"); + if (!is_global_object) { + printf(" (%d << 1) | (JS_PROP_%s << 30),\n", + prop_hash[prop_idx], prop_type); + } + prop_idx++; + } + + free(prop_hash); + free(ident_tab); + return props_ident; +} + +static ClassDefEntry *find_class(BuildContext *s, const JSClassDef *d) +{ + struct list_head *el; + ClassDefEntry *e; + + list_for_each(el, &s->class_list) { + e = list_entry(el, ClassDefEntry, link); + if (e->class1 == d) + return e; + } + return NULL; +} + +static void free_class_entries(BuildContext *s) +{ + struct list_head *el, *el1; + ClassDefEntry *e; + list_for_each_safe(el, el1, &s->class_list) { + e = list_entry(el, ClassDefEntry, link); + free(e->class_id); + free(e->finalizer_name); + free(e); + } + init_list_head(&s->class_list); +} + +static int define_class(BuildContext *s, const JSClassDef *d) +{ + int ctor_func_idx = -1, class_props_idx = -1, proto_props_idx = -1; + int ident, parent_class_idx = -1; + ClassDefEntry *e; + + /* check if the class is already defined */ + e = find_class(s, d); + if (e) + return e->class_idx; + + if (d->parent_class) + parent_class_idx = define_class(s, d->parent_class); + + if (d->func_name) { + ctor_func_idx = add_cfunc(&s->cfunc_list, + d->name, + d->length, + d->class_id, + d->cproto_name, + d->func_name); + } + + if (ctor_func_idx >= 0) { + class_props_idx = define_props(s, d->class_props, PROPS_KIND_CLASS, d->class_id); + proto_props_idx = define_props(s, d->proto_props, PROPS_KIND_PROTO, d->class_id); + } else { + if (d->class_props) + class_props_idx = define_props(s, d->class_props, PROPS_KIND_OBJECT, d->class_id); + } + + ident = s->cur_offset; + printf(" /* class (offset=%d) */\n", ident); + printf(" JS_MB_HEADER_DEF(JS_MTAG_OBJECT),\n"); + if (class_props_idx >= 0) + printf(" JS_ROM_VALUE(%d),\n", class_props_idx); + else + printf(" JS_NULL,\n"); + printf(" %d,\n", ctor_func_idx); + if (proto_props_idx >= 0) + printf(" JS_ROM_VALUE(%d),\n", proto_props_idx); + else + printf(" JS_NULL,\n"); + if (parent_class_idx >= 0) { + printf(" JS_ROM_VALUE(%d),\n", parent_class_idx); + } else { + printf(" JS_NULL,\n"); + } + printf("\n"); + + s->cur_offset += 5; + + e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->class_idx = ident; + e->class1 = d; + if (ctor_func_idx >= 0) { + e->class_id = strdup(d->class_id); + e->finalizer_name = strdup(d->finalizer_name); + } + list_add_tail(&e->link, &s->class_list); + return ident; +} + +#define JS_SHORTINT_MIN (-(1 << 30)) +#define JS_SHORTINT_MAX ((1 << 30) - 1) + +static BOOL is_short_int(double d) +{ + return (d >= JS_SHORTINT_MIN && d <= JS_SHORTINT_MAX && (int32_t)d == d); +} + +static int define_value(BuildContext *s, const JSPropDef *d) +{ + int ident; + ident = -1; + switch(d->def_type) { + case JS_DEF_PROP_DOUBLE: + { + uint64_t v; + if (!is_short_int(d->u.f64)) { + ident = s->cur_offset; + printf(" /* float64 (offset=%d) */\n", ident); + printf(" JS_MB_HEADER_DEF(JS_MTAG_FLOAT64),\n"); + v = float64_as_uint64(d->u.f64); + if (JSW == 8) { + printf(" 0x%016zx,\n", (size_t)v); + printf("\n"); + s->cur_offset += 2; + } else { + /* XXX: little endian assumed */ + printf(" 0x%08x,\n", (uint32_t)v); + printf(" 0x%08x,\n", (uint32_t)(v >> 32)); + printf("\n"); + s->cur_offset += 3; + } + } + } + break; + case JS_DEF_CLASS: + ident = define_class(s, d->u.class1); + break; + case JS_DEF_CGETSET: + { + int get_idx = -1, set_idx = -1; + char buf[256]; + if (strcmp(d->u.getset.get_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "get %s", d->name); + get_idx = add_cfunc(&s->cfunc_list, + buf, + 0, /* length */ + d->u.getset.magic, + d->u.getset.cproto_name, + d->u.getset.get_func_name); + } + if (strcmp(d->u.getset.set_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "set %s", d->name); + set_idx = add_cfunc(&s->cfunc_list, + buf, + 1, /* length */ + d->u.getset.magic, + d->u.getset.cproto_name, + d->u.getset.set_func_name); + } + ident = s->cur_offset; + printf(" /* getset (offset=%d) */\n", ident); + printf(" JS_VALUE_ARRAY_HEADER(2),\n"); + if (get_idx >= 0) + printf(" JS_VALUE_MAKE_SPECIAL(JS_TAG_SHORT_FUNC, %d),\n", get_idx); + else + printf(" JS_UNDEFINED,\n"); + if (set_idx >= 0) + printf(" JS_VALUE_MAKE_SPECIAL(JS_TAG_SHORT_FUNC, %d),\n", set_idx); + else + printf(" JS_UNDEFINED,\n"); + printf("\n"); + s->cur_offset += 3; + } + break; + default: + break; + } + return ident; +} + +static void define_atoms_props(BuildContext *s, const JSPropDef *props_def, JSPropsKindEnum props_kind); + +static void define_atoms_class(BuildContext *s, const JSClassDef *d) +{ + ClassDefEntry *e; + /* check if the class is already defined */ + e = find_class(s, d); + if (e) + return; + if (d->parent_class) + define_atoms_class(s, d->parent_class); + if (d->func_name) + add_atom(&s->atom_list, d->name); + if (d->class_props) + define_atoms_props(s, d->class_props, d->func_name ? PROPS_KIND_CLASS : PROPS_KIND_OBJECT); + if (d->proto_props) + define_atoms_props(s, d->proto_props, PROPS_KIND_PROTO); +} + +static void define_atoms_props(BuildContext *s, const JSPropDef *props_def, JSPropsKindEnum props_kind) +{ + const JSPropDef *d; + for(d = props_def; d->def_type != JS_DEF_END; d++) { + add_atom(&s->atom_list, d->name); + switch(d->def_type) { + case JS_DEF_PROP_STRING: + add_atom(&s->atom_list, d->u.str); + break; + case JS_DEF_CLASS: + define_atoms_class(s, d->u.class1); + break; + case JS_DEF_CGETSET: + { + char buf[256]; + if (strcmp(d->u.getset.get_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "get %s", d->name); + add_atom(&s->atom_list, buf); + } + if (strcmp(d->u.getset.set_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "set %s", d->name); + add_atom(&s->atom_list, buf); + } + } + break; + default: + break; + } + } +} + +static int usage(const char *name) +{ + fprintf(stderr, "usage: %s {-m32 | -m64} [-a]\n", name); + fprintf(stderr, + " create a ROM file for the mquickjs standard library\n" + "--help list options\n" + "-m32 force generation for a 32 bit target\n" + "-m64 force generation for a 64 bit target\n" + "-a generate the mquickjs_atom.h header\n" + ); + return 1; +} + +int build_atoms(const char *stdlib_name, const JSPropDef *global_obj, + const JSPropDef *c_function_decl, int argc, char **argv) +{ + int i; + unsigned jsw; + BuildContext ss, *s = &ss; + BOOL build_atom_defines = FALSE; + +#if INTPTR_MAX >= INT64_MAX + jsw = 8; +#else + jsw = 4; +#endif + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-m64")) { + jsw = 8; + } else if (!strcmp(argv[i], "-m32")) { + jsw = 4; + } else if (!strcmp(argv[i], "-a")) { + build_atom_defines = TRUE; + } else if (!strcmp(argv[i], "--help")) { + return usage(argv[0]); + } else { + fprintf(stderr, "invalid argument '%s'\n", argv[i]); + return usage(argv[0]); + } + } + + JSW = jsw; + + if (build_atom_defines) { + dump_atom_defines(); + return 0; + } + + memset(s, 0, sizeof(*s)); + init_list_head(&s->class_list); + + /* add the predefined atoms (they have a corresponding define) */ + for(i = 0; i < countof(atoms); i++) { + add_atom(&s->atom_list, atoms[i]); + } + + /* add the predefined functions */ + if (c_function_decl) { + const JSPropDef *d; + for(d = c_function_decl; d->def_type != JS_DEF_END; d++) { + if (d->def_type != JS_DEF_CFUNC) { + fprintf(stderr, "only C functions are allowed in c_function_decl[]\n"); + exit(1); + } + add_atom(&s->atom_list, d->name); + add_cfunc(&s->cfunc_list, + d->name, + d->u.func.length, + d->u.func.magic, + d->u.func.cproto_name, + d->u.func.func_name); + } + } + + /* first pass to define the atoms */ + define_atoms_props(s, global_obj, PROPS_KIND_GLOBAL); + free_class_entries(s); + + printf("/* this file is automatically generated - do not edit */\n\n"); + printf("#include \"mquickjs_priv.h\"\n\n"); + + printf("static const uint%u_t __attribute((aligned(%d))) js_stdlib_table[] = {\n", + JSW * 8, ATOM_ALIGN); + + dump_atoms(s); + + s->global_object_offset = define_props(s, global_obj, PROPS_KIND_GLOBAL, NULL); + + printf("};\n\n"); + + dump_cfuncs(s); + + printf("#ifndef JS_CLASS_COUNT\n" + "#define JS_CLASS_COUNT JS_CLASS_USER /* total number of classes */\n" + "#endif\n\n"); + + dump_cfinalizers(s); + + free_class_entries(s); + + printf("const JSSTDLibraryDef %s = {\n", stdlib_name); + printf(" js_stdlib_table,\n"); + printf(" js_c_function_table,\n"); + printf(" js_c_finalizer_table,\n"); + printf(" %d,\n", s->cur_offset); + printf(" %d,\n", ATOM_ALIGN); + printf(" %d,\n", s->sorted_atom_table_offset); + printf(" %d,\n", s->global_object_offset); + printf(" JS_CLASS_COUNT,\n"); + printf("};\n\n"); + + return 0; +} diff --git a/mquickjs_build.h b/mquickjs_build.h new file mode 100644 index 00000000..51be6b44 --- /dev/null +++ b/mquickjs_build.h @@ -0,0 +1,97 @@ +/* + * Micro QuickJS build utility + * + * 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 MQUICKJS_BUILD_H +#define MQUICKJS_BUILD_H + +#include +#include + +enum { + JS_DEF_END, + JS_DEF_CFUNC, + JS_DEF_CGETSET, + JS_DEF_PROP_DOUBLE, + JS_DEF_PROP_UNDEFINED, + JS_DEF_PROP_STRING, + JS_DEF_PROP_NULL, + JS_DEF_CLASS, +}; + +typedef struct JSClassDef JSClassDef; + +typedef struct JSPropDef { + int def_type; + const char *name; + union { + struct { + uint8_t length; + const char *magic; + const char *cproto_name; + const char *func_name; + } func; + struct { + const char *magic; + const char *cproto_name; + const char *get_func_name; + const char *set_func_name; + } getset; + double f64; + const JSClassDef *class1; + const char *str; + } u; +} JSPropDef; + +typedef struct JSClassDef { + const char *name; + int length; + const char *cproto_name; + const char *func_name; + const char *class_id; + const JSPropDef *class_props; /* NULL if none */ + const JSPropDef *proto_props; /* NULL if none */ + const JSClassDef *parent_class; /* NULL if none */ + const char *finalizer_name; /* "NULL" if none */ +} JSClassDef; + +#define JS_PROP_END { JS_DEF_END } +#define JS_CFUNC_DEF(name, length, func_name) { JS_DEF_CFUNC, name, { .func = { length, "0", "generic", #func_name } } } +#define JS_CFUNC_MAGIC_DEF(name, length, func_name, magic) { JS_DEF_CFUNC, name, { .func = { length, #magic, "generic_magic", #func_name } } } +#define JS_CFUNC_SPECIAL_DEF(name, length, proto, func_name) { JS_DEF_CFUNC, name, { .func = { length, "0", #proto, #func_name } } } +#define JS_CGETSET_DEF(name, get_name, set_name) { JS_DEF_CGETSET, name, { .getset = { "0", "generic", #get_name, #set_name } } } +#define JS_CGETSET_MAGIC_DEF(name, get_name, set_name, magic) { JS_DEF_CGETSET, name, { .getset = { #magic, "generic_magic", #get_name, #set_name } } } +#define JS_PROP_CLASS_DEF(name, cl) { JS_DEF_CLASS, name, { .class1 = cl } } +#define JS_PROP_DOUBLE_DEF(name, val, flags) { JS_DEF_PROP_DOUBLE, name, { .f64 = val } } +#define JS_PROP_UNDEFINED_DEF(name, flags) { JS_DEF_PROP_UNDEFINED, name } +#define JS_PROP_NULL_DEF(name, flags) { JS_DEF_PROP_NULL, name } +#define JS_PROP_STRING_DEF(name, cstr, flags) { JS_DEF_PROP_STRING, name, { .str = cstr } } + +#define JS_CLASS_DEF(name, length, func_name, class_id, class_props, proto_props, parent_class, finalizer_name) { name, length, "constructor", #func_name, #class_id, class_props, proto_props, parent_class, #finalizer_name } +#define JS_CLASS_MAGIC_DEF(name, length, func_name, class_id, class_props, proto_props, parent_class, finalizer_name) { name, length, "constructor_magic", #func_name, #class_id, class_props, proto_props, parent_class, #finalizer_name } +#define JS_OBJECT_DEF(name, obj_props) { name, 0, NULL, NULL, NULL, obj_props, NULL, NULL, NULL } + +int build_atoms(const char *stdlib_name, const JSPropDef *global_obj, + const JSPropDef *c_function_decl, int argc, char **argv); + +#endif /* MQUICKJS_BUILD_H */ diff --git a/mquickjs_opcode.h b/mquickjs_opcode.h new file mode 100644 index 00000000..2ccd6050 --- /dev/null +++ b/mquickjs_opcode.h @@ -0,0 +1,264 @@ +/* + * Micro QuickJS opcode definitions + * + * 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. + */ +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(i32) +FMT(const16) +FMT(label) +FMT(value) +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF( push_value, 5, 0, 1, value) +DEF( push_const, 3, 0, 1, const16) +DEF( fclosure, 3, 0, 1, const16) +DEF( undefined, 1, 0, 1, none) +DEF( null, 1, 0, 1, none) +DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF( push_false, 1, 0, 1, none) +DEF( push_true, 1, 0, 1, none) +DEF( object, 3, 0, 1, u16) +DEF( this_func, 1, 0, 1, none) +DEF( arguments, 1, 0, 1, none) +DEF( new_target, 1, 0, 1, none) + +DEF( drop, 1, 1, 0, none) /* a -> */ +DEF( nip, 1, 2, 1, none) /* a b -> b */ +//DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF( dup, 1, 1, 2, none) /* a -> a a */ +DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ +//DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +//DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +//DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF( swap, 1, 2, 2, none) /* a b -> b a */ +//DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ +//DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ +//DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +//DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 1, 1, npop) /* func args... -> ret (arguments are not counted in n_pop) */ +DEF( call, 3, 1, 1, npop) /* func args... -> ret (arguments are not counted in n_pop) */ +DEF( call_method, 3, 2, 1, npop) /* this func args.. -> ret (arguments are not counted in n_pop) */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a bytecode string */ + +DEF( get_field, 3, 1, 1, const16) /* obj -> val */ +DEF( get_field2, 3, 1, 2, const16) /* obj -> obj val */ +DEF( put_field, 3, 2, 0, const16) /* obj val -> */ +DEF( get_array_el, 1, 2, 1, none) /* obj prop -> val */ +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( put_array_el, 1, 3, 0, none) /* obj prop val -> */ +DEF( get_length, 1, 1, 1, none) /* obj -> val */ +DEF( get_length2, 1, 1, 2, none) /* obj -> obj val */ +DEF( define_field, 3, 2, 1, const16) /* obj val -> obj */ +DEF( define_getter, 3, 2, 1, const16) /* obj val -> obj */ +DEF( define_setter, 3, 2, 1, const16) /* obj val -> obj */ +DEF( set_proto, 1, 2, 1, none) /* obj proto -> obj */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF(get_var_ref_nocheck, 3, 0, 1, var_ref) +DEF(put_var_ref_nocheck, 3, 1, 0, var_ref) +DEF( if_false, 5, 1, 0, label) +DEF( if_true, 5, 1, 0, label) /* must come after if_false */ +DEF( goto, 5, 0, 0, label) /* must come after if_true */ +DEF( catch, 5, 0, 1, label) +DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ + +DEF( for_in_start, 1, 1, 1, none) /* obj -> iter */ +DEF( for_of_start, 1, 1, 1, none) /* obj -> iter */ +DEF( for_of_next, 1, 1, 3, none) /* iter -> iter val done */ + +/* arithmetic/logic operations */ +DEF( neg, 1, 1, 1, none) +DEF( plus, 1, 1, 1, none) +DEF( dec, 1, 1, 1, none) +DEF( inc, 1, 1, 1, none) +DEF( post_dec, 1, 1, 2, none) +DEF( post_inc, 1, 1, 2, none) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) /* obj prop -> ret */ + +DEF( mul, 1, 2, 1, none) +DEF( div, 1, 2, 1, none) +DEF( mod, 1, 2, 1, none) +DEF( add, 1, 2, 1, none) +DEF( sub, 1, 2, 1, none) +DEF( pow, 1, 2, 1, none) +DEF( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( lt, 1, 2, 1, none) +DEF( lte, 1, 2, 1, none) +DEF( gt, 1, 2, 1, none) +DEF( gte, 1, 2, 1, none) +DEF( instanceof, 1, 2, 1, none) +DEF( in, 1, 2, 1, none) +DEF( eq, 1, 2, 1, none) +DEF( neq, 1, 2, 1, none) +DEF( strict_eq, 1, 2, 1, none) +DEF( strict_neq, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +DEF( push_minus1, 1, 0, 1, none_int) +DEF( push_0, 1, 0, 1, none_int) +DEF( push_1, 1, 0, 1, none_int) +DEF( push_2, 1, 0, 1, none_int) +DEF( push_3, 1, 0, 1, none_int) +DEF( push_4, 1, 0, 1, none_int) +DEF( push_5, 1, 0, 1, none_int) +DEF( push_6, 1, 0, 1, none_int) +DEF( push_7, 1, 0, 1, none_int) +DEF( push_i8, 2, 0, 1, i8) +DEF( push_i16, 3, 0, 1, i16) +DEF( push_const8, 2, 0, 1, const8) +DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) + +DEF( get_loc8, 2, 0, 1, loc8) +DEF( put_loc8, 2, 1, 0, loc8) /* must follow get_loc8 */ + +DEF( get_loc0, 1, 0, 1, none_loc) +DEF( get_loc1, 1, 0, 1, none_loc) +DEF( get_loc2, 1, 0, 1, none_loc) +DEF( get_loc3, 1, 0, 1, none_loc) +DEF( put_loc0, 1, 1, 0, none_loc) /* must follow get_loc */ +DEF( put_loc1, 1, 1, 0, none_loc) +DEF( put_loc2, 1, 1, 0, none_loc) +DEF( put_loc3, 1, 1, 0, none_loc) +DEF( get_arg0, 1, 0, 1, none_arg) +DEF( get_arg1, 1, 0, 1, none_arg) +DEF( get_arg2, 1, 0, 1, none_arg) +DEF( get_arg3, 1, 0, 1, none_arg) +DEF( put_arg0, 1, 1, 0, none_arg) /* must follow get_arg */ +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +#if 0 +DEF( if_false8, 2, 1, 0, label8) +DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF( goto16, 3, 0, 0, label16) + +DEF( call0, 1, 1, 1, npopx) +DEF( call1, 1, 1, 1, npopx) +DEF( call2, 1, 1, 1, npopx) +DEF( call3, 1, 1, 1, npopx) +#endif + +#undef DEF +#undef def +#endif /* DEF */ + +#ifdef REDEF + +/* regular expression bytecode */ +REDEF(invalid, 1) /* never used */ +REDEF(char1, 2) +REDEF(char2, 3) +REDEF(char3, 4) +REDEF(char4, 5) +REDEF(dot, 1) +REDEF(any, 1) /* same as dot but match any character including line terminator */ +REDEF(space, 1) +REDEF(not_space, 1) /* must come after */ +REDEF(line_start, 1) +REDEF(line_start_m, 1) +REDEF(line_end, 1) +REDEF(line_end_m, 1) +REDEF(goto, 5) +REDEF(split_goto_first, 5) +REDEF(split_next_first, 5) +REDEF(match, 1) +REDEF(lookahead_match, 1) +REDEF(negative_lookahead_match, 1) /* must come after */ +REDEF(save_start, 2) /* save start position */ +REDEF(save_end, 2) /* save end position, must come after saved_start */ +REDEF(save_reset, 3) /* reset save positions */ +REDEF(loop, 6) /* decrement the top the stack and goto if != 0 */ +REDEF(loop_split_goto_first, 10) /* loop and then split */ +REDEF(loop_split_next_first, 10) +REDEF(loop_check_adv_split_goto_first, 10) /* loop and then check advance and split */ +REDEF(loop_check_adv_split_next_first, 10) +REDEF(set_i32, 6) /* store the immediate value to a register */ +REDEF(word_boundary, 1) +REDEF(not_word_boundary, 1) +REDEF(back_reference, 2) +REDEF(back_reference_i, 2) +REDEF(range8, 2) /* variable length */ +REDEF(range, 3) /* variable length */ +REDEF(lookahead, 5) +REDEF(negative_lookahead, 5) /* must come after */ +REDEF(set_char_pos, 2) /* store the character position to a register */ +REDEF(check_advance, 2) /* check that the register is different from the character position */ + +#endif /* REDEF */ diff --git a/mquickjs_priv.h b/mquickjs_priv.h new file mode 100644 index 00000000..37bc1535 --- /dev/null +++ b/mquickjs_priv.h @@ -0,0 +1,268 @@ +/* microj private definitions */ +#ifndef MICROJS_PRIV_H +#define MICROJS_PRIV_H + +#include "mquickjs.h" +#include "libm.h" + +#define JS_DUMP /* 2.6 kB */ +//#define DUMP_EXEC +//#define DUMP_FUNC_BYTECODE /* dump the bytecode of each compiled function */ +//#define DUMP_REOP /* dump regexp bytecode */ +//#define DUMP_GC +//#define DUMP_TOKEN /* dump parsed tokens */ +/* run GC before at each malloc() and modify the allocated data pointers */ +//#define DEBUG_GC +#if defined(DUMP_FUNC_BYTECODE) || defined(DUMP_EXEC) +#define DUMP_BYTECODE /* include the dump_byte_code() function */ +#endif + +#define JS_VALUE_TO_PTR(v) (void *)((uintptr_t)(v) - 1) +#define JS_VALUE_FROM_PTR(ptr) (JSWord)((uintptr_t)(ptr) + 1) + +#define JS_IS_ROM_PTR(ctx, ptr) ((uintptr_t)(ptr) < (uintptr_t)ctx || (uintptr_t)(ptr) >= (uintptr_t)ctx->stack_top) + +enum { + JS_MTAG_FREE, + /* javascript values */ + JS_MTAG_OBJECT, + JS_MTAG_FLOAT64, + JS_MTAG_STRING, + /* other special memory blocks */ + JS_MTAG_FUNCTION_BYTECODE, + JS_MTAG_VALUE_ARRAY, + JS_MTAG_BYTE_ARRAY, + JS_MTAG_VARREF, + + JS_MTAG_COUNT, +}; + +/* JS_MTAG_BITS bits are reserved at the start of every memory block */ +#define JS_MTAG_BITS 4 + +#define JS_MB_HEADER \ + JSWord gc_mark: 1; \ + JSWord mtag: (JS_MTAG_BITS - 1) + +typedef enum { + JS_PROP_NORMAL, + JS_PROP_GETSET, /* value is a two element JSValueArray */ + JS_PROP_VARREF, /* value is a JSVarRef (used for global variables) */ + JS_PROP_SPECIAL, /* for the prototype and constructor properties in ROM */ +} JSPropTypeEnum; + +#define JS_MB_HEADER_DEF(tag) ((tag) << 1) +#define JS_VALUE_ARRAY_HEADER(size) (JS_MB_HEADER_DEF(JS_MTAG_VALUE_ARRAY) | ((size) << JS_MTAG_BITS)) + +#define JS_ROM_VALUE(offset) JS_VALUE_FROM_PTR(&js_stdlib_table[offset]) + +/* runtime helpers */ +JSValue js_function_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_name); +JSValue js_function_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_call(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_apply(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_bind(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_bound(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, JSValue params); + +JSValue js_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_number_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_string_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_substring(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +enum { + magic_internalAt, + magic_charAt, + magic_charCodeAt, + magic_codePointAt, +}; +JSValue js_string_charAt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_string_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int lastIndexOf); +JSValue js_string_match(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_replace(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_replaceAll); +JSValue js_string_search(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_split(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int to_lower); +JSValue js_string_trim(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_string_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_repeat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_object_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_create(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_keys(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_string_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_fromCodePoint); + +JSValue js_error_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_error_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_error_get_message(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); + +JSValue js_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_push(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_unshift); +JSValue js_array_pop(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_shift(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_join(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_isArray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_reverse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_lastIndexOf); +JSValue js_array_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_splice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_sort(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +#define js_special_every 0 +#define js_special_some 1 +#define js_special_forEach 2 +#define js_special_map 3 +#define js_special_filter 4 + +JSValue js_array_every(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special); + +#define js_special_reduce 0 +#define js_special_reduceRight 1 + +JSValue js_array_reduce(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special); + +JSValue js_math_min_max(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +double js_math_sign(double a); +double js_math_fround(double a); +JSValue js_math_imul(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_clz32(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_atan2(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_pow(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_random(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_date_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_global_eval(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_json_parse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_json_stringify(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_test); + +#endif /* MICROJS_PRIV_H */ diff --git a/readline.c b/readline.c new file mode 100644 index 00000000..e5265891 --- /dev/null +++ b/readline.c @@ -0,0 +1,742 @@ +/* + * readline utility + * + * Copyright (c) 2003-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 "cutils.h" +#include "readline.h" + +#define IS_NORM 0 +#define IS_ESC 1 +#define IS_CSI 2 + +static void term_show_prompt2(ReadlineState *s) +{ + term_printf("%s", s->term_prompt); + term_flush(); + /* XXX: assuming no unicode chars */ + s->term_cursor_x = strlen(s->term_prompt) % s->term_width; + s->term_cursor_pos = 0; + s->term_esc_state = IS_NORM; + s->utf8_state = 0; +} + +static void term_show_prompt(ReadlineState *s) +{ + term_show_prompt2(s); + s->term_cmd_buf_index = 0; + s->term_cmd_buf_len = 0; +} + +static void print_csi(int n, int code) +{ + if (n == 1) { + term_printf("\033[%c", code); + } else { + term_printf("\033[%d%c", n, code); + } +} + +const char *term_colors[17] = { + "\033[0m", + "\033[30m", + "\033[31m", + "\033[32m", + "\033[33m", + "\033[34m", + "\033[35m", + "\033[36m", + "\033[37m", + "\033[30;1m", + "\033[31;1m", + "\033[32;1m", + "\033[33;1m", + "\033[34;1m", + "\033[35;1m", + "\033[36;1m", + "\033[37;1m", +}; + +static void print_color(int c) +{ + term_printf("%s", term_colors[c]); +} + +static void move_cursor(ReadlineState *s, int delta) +{ + int l; + if (delta > 0) { + while (delta != 0) { + if (s->term_cursor_x == (s->term_width - 1)) { + term_printf("\r\n"); /* translated to CRLF */ + s->term_cursor_x = 0; + delta--; + } else { + l = min_int(s->term_width - 1 - s->term_cursor_x, delta); + print_csi(l, 'C'); /* right */ + delta -= l; + s->term_cursor_x += l; + } + } + } else if (delta < 0) { + delta = -delta; + while (delta != 0) { + if (s->term_cursor_x == 0) { + print_csi(1, 'A'); /* up */ + print_csi(s->term_width - 1, 'C'); /* right */ + delta--; + s->term_cursor_x = s->term_width - 1; + } else { + l = min_int(delta, s->term_cursor_x); + print_csi(l, 'D'); /* left */ + delta -= l; + s->term_cursor_x -= l; + } + } + } +} + +static int char_width(int c) +{ + /* XXX: complete or find a way to use wcwidth() */ + if (c < 0x100) { + return 1; + } else if ((c >= 0x4E00 && c <= 0x9FFF) || /* CJK */ + (c >= 0xFF01 && c <= 0xFF5E) || /* fullwidth ASCII */ + (c >= 0x1F600 && c <= 0x1F64F)) { /* emoji */ + return 2; + } else { + return 1; + } +} + +/* update the displayed command line */ +static void term_update(ReadlineState *s) +{ + int i, len, c, new_cursor_pos, last_color, color_len; + uint8_t buf[UTF8_CHAR_LEN_MAX + 1]; + size_t c_len; + + new_cursor_pos = 0; + if (s->term_cmd_updated) { + move_cursor(s, -s->term_cursor_pos); + s->term_cursor_pos = 0; + last_color = COLOR_NONE; + color_len = 0; + s->term_cmd_buf[s->term_cmd_buf_len] = '\0'; /* add a trailing '\0' to ease colorization */ + for(i = 0; i < s->term_cmd_buf_len; i += c_len) { + if (i == s->term_cmd_buf_index) + new_cursor_pos = s->term_cursor_pos; + c = utf8_get(s->term_cmd_buf + i, &c_len); + if (s->term_is_password) { + len = 1; + buf[0] = '*'; + buf[1] = '\0'; + } else { + len = char_width(c); + memcpy(buf, s->term_cmd_buf + i, c_len); + buf[c_len] = '\0'; + } + /* the wide char does not fit so we display it on the next + line by enlarging the previous char */ + if (s->term_cursor_x + len > s->term_width && i > 0) { + while (s->term_cursor_x < s->term_width) { + term_printf(" "); + s->term_cursor_x++; + s->term_cursor_pos++; + } + s->term_cursor_x = 0; + } + s->term_cursor_pos += len; + s->term_cursor_x += len; + if (s->term_cursor_x >= s->term_width) + s->term_cursor_x = 0; + if (!s->term_is_password && s->get_color) { + if (color_len == 0) { + int new_color = s->get_color(&color_len, (const char *)s->term_cmd_buf, i, s->term_cmd_buf_len); + if (new_color != last_color) { + last_color = new_color; + print_color(COLOR_NONE); /* reset last color */ + print_color(last_color); + } + } + color_len--; + } + term_printf("%s", buf); + } + if (last_color != COLOR_NONE) + print_color(COLOR_NONE); + if (i == s->term_cmd_buf_index) + new_cursor_pos = s->term_cursor_pos; + if (s->term_cursor_x == 0) { + /* show the cursor on the next line */ + term_printf(" \x08"); + } + /* remove the trailing characters */ + print_csi(1, 'J'); + s->term_cmd_updated = FALSE; + } else { + int cursor_x; + /* compute the new cursor pos without display */ + cursor_x = (s->term_cursor_x - s->term_cursor_pos) % s->term_width; + if (cursor_x < 0) + cursor_x += s->term_width; + new_cursor_pos = 0; + for(i = 0; i < s->term_cmd_buf_index; i += c_len) { + c = utf8_get(s->term_cmd_buf + i, &c_len); + if (s->term_is_password) + c = '*'; + len = char_width(c); + /* the wide char does not fit so we display it on the next + line by enlarging the previous char */ + if (cursor_x + len > s->term_width && i > 0) { + new_cursor_pos += s->term_width - cursor_x; + cursor_x = 0; + } + new_cursor_pos += len; + cursor_x += len; + if (cursor_x >= s->term_width) + cursor_x = 0; + } + } + move_cursor(s, new_cursor_pos - s->term_cursor_pos); + s->term_cursor_pos = new_cursor_pos; + term_flush(); +} + +static void term_kill_region(ReadlineState *s, int to, int kill) +{ + int start = s->term_cmd_buf_index; + int end = s->term_cmd_buf_index; + if (to < start) + start = to; + else + end = to; + if (end > s->term_cmd_buf_len) + end = s->term_cmd_buf_len; + if (start < end) { + int len = end - start; + if (kill) { + memcpy(s->term_kill_buf, s->term_cmd_buf + start, + len * sizeof(s->term_cmd_buf[0])); + s->term_kill_buf_len = len; + } + memmove(s->term_cmd_buf + start, s->term_cmd_buf + end, + (s->term_cmd_buf_len - end) * sizeof(s->term_cmd_buf[0])); + s->term_cmd_buf_len -= len; + s->term_cmd_buf_index = start; + s->term_cmd_updated = TRUE; + } +} + +static void term_insert_region(ReadlineState *s, const uint8_t *p, int len) +{ + int pos = s->term_cmd_buf_index; + + if (pos + len < s->term_cmd_buf_size) { + int nchars = s->term_cmd_buf_len - pos; + if (nchars > 0) { + memmove(s->term_cmd_buf + pos + len, + s->term_cmd_buf + pos, + nchars * sizeof(s->term_cmd_buf[0])); + } + memcpy(s->term_cmd_buf + pos, p, len * sizeof(s->term_cmd_buf[0])); + s->term_cmd_buf_len += len; + s->term_cmd_buf_index += len; + s->term_cmd_updated = TRUE; + } +} + +static void term_insert_char(ReadlineState *s, int ch) +{ + uint8_t buf[UTF8_CHAR_LEN_MAX + 1]; + term_insert_region(s, buf, unicode_to_utf8(buf, ch)); +} + +static BOOL is_utf8_ext(int c) +{ + return (c >= 0x80 && c < 0xc0); +} + +static void term_backward_char(ReadlineState *s) +{ + if (s->term_cmd_buf_index > 0) { + s->term_cmd_buf_index--; + while (s->term_cmd_buf_index > 0 && + is_utf8_ext(s->term_cmd_buf[s->term_cmd_buf_index])) { + s->term_cmd_buf_index--; + } + } +} + +static void term_forward_char(ReadlineState *s) +{ + size_t c_len; + if (s->term_cmd_buf_index < s->term_cmd_buf_len) { + utf8_get(s->term_cmd_buf + s->term_cmd_buf_index, &c_len); + s->term_cmd_buf_index += c_len; + } +} + +static void term_delete_char(ReadlineState *s) +{ + size_t c_len; + if (s->term_cmd_buf_index < s->term_cmd_buf_len) { + utf8_get(s->term_cmd_buf + s->term_cmd_buf_index, &c_len); + term_kill_region(s, s->term_cmd_buf_index + c_len, 0); + } +} + +static void term_backspace(ReadlineState *s) +{ + if (s->term_cmd_buf_index > 0) { + term_backward_char(s); + term_delete_char(s); + } +} + +static int skip_word_backward(ReadlineState *s) +{ + int pos = s->term_cmd_buf_index; + + /* skip whitespace backwards */ + while (pos > 0 && isspace(s->term_cmd_buf[pos - 1])) + --pos; + + /* skip word backwards */ + while (pos > 0 && !isspace(s->term_cmd_buf[pos - 1])) + --pos; + + return pos; +} + +static int skip_word_forward(ReadlineState *s) +{ + int pos = s->term_cmd_buf_index; + + /* skip whitespace */ + while (pos < s->term_cmd_buf_len && isspace(s->term_cmd_buf[pos])) + pos++; + + /* skip word */ + while (pos < s->term_cmd_buf_len && !isspace(s->term_cmd_buf[pos])) + pos++; + + return pos; +} + +static void term_skip_word_backward(ReadlineState *s) +{ + s->term_cmd_buf_index = skip_word_backward(s); +} + +static void term_skip_word_forward(ReadlineState *s) +{ + s->term_cmd_buf_index = skip_word_forward(s); +} + +static void term_yank(ReadlineState *s) +{ + term_insert_region(s, s->term_kill_buf, s->term_kill_buf_len); +} + +static void term_kill_word(ReadlineState *s) +{ + term_kill_region(s, skip_word_forward(s), 1); +} + +static void term_kill_word_backward(ReadlineState *s) +{ + term_kill_region(s, skip_word_backward(s), 1); +} + +static void term_bol(ReadlineState *s) +{ + s->term_cmd_buf_index = 0; +} + +static void term_eol(ReadlineState *s) +{ + s->term_cmd_buf_index = s->term_cmd_buf_len; +} + +static void update_cmdline_from_history(ReadlineState *s) +{ + int hist_entry_size; + hist_entry_size = strlen(s->term_history + s->term_hist_entry); + memcpy(s->term_cmd_buf, s->term_history + s->term_hist_entry, hist_entry_size); + s->term_cmd_buf_len = hist_entry_size; + s->term_cmd_buf_index = s->term_cmd_buf_len; + s->term_cmd_updated = TRUE; +} + +static void term_up_char(ReadlineState *s) +{ + int idx; + if (s->term_hist_entry == -1) { + s->term_hist_entry = s->term_history_size; + // XXX: should save current contents to history + } + if (s->term_hist_entry == 0) + return; + /* move to previous entry */ + idx = s->term_hist_entry - 1; + while (idx > 0 && s->term_history[idx - 1] != '\0') + idx--; + s->term_hist_entry = idx; + update_cmdline_from_history(s); +} + +static void term_down_char(ReadlineState *s) +{ + int hist_entry_size; + if (s->term_hist_entry == -1) + return; + hist_entry_size = strlen(s->term_history + s->term_hist_entry) + 1; + if (s->term_hist_entry + hist_entry_size < s->term_history_size) { + s->term_hist_entry += hist_entry_size; + update_cmdline_from_history(s); + } else { + s->term_hist_entry = -1; + s->term_cmd_buf_index = s->term_cmd_buf_len; + } +} + +static void term_hist_add(ReadlineState *s, const char *cmdline) +{ + char *hist_entry; + int idx, cmdline_size, hist_entry_size; + + if (cmdline[0] == '\0') + return; + cmdline_size = strlen(cmdline) + 1; + if (s->term_hist_entry != -1) { + /* We were editing an existing history entry: replace it */ + idx = s->term_hist_entry; + hist_entry = s->term_history + idx; + hist_entry_size = strlen(hist_entry) + 1; + if (hist_entry_size == cmdline_size && !memcmp(hist_entry, cmdline, cmdline_size)) { + goto same_entry; + } + } + /* Search cmdline in the history */ + for (idx = 0; idx < s->term_history_size; idx += hist_entry_size) { + hist_entry = s->term_history + idx; + hist_entry_size = strlen(hist_entry) + 1; + if (hist_entry_size == cmdline_size && !memcmp(hist_entry, cmdline, cmdline_size)) { + same_entry: + /* remove the identical entry */ + memmove(s->term_history + idx, s->term_history + idx + hist_entry_size, + s->term_history_size - (idx + hist_entry_size)); + s->term_history_size -= hist_entry_size; + break; + } + } + + if (cmdline_size <= s->term_history_buf_size) { + /* remove history entries if not enough space */ + while (s->term_history_size + cmdline_size > s->term_history_buf_size) { + hist_entry_size = strlen(s->term_history) + 1; + memmove(s->term_history, s->term_history + hist_entry_size, + s->term_history_size - hist_entry_size); + s->term_history_size -= hist_entry_size; + } + + /* add the cmdline */ + memcpy(s->term_history + s->term_history_size, cmdline, cmdline_size); + s->term_history_size += cmdline_size; + } + s->term_hist_entry = -1; +} + +/* completion support */ + +#if 0 +void add_completion(const char *str) +{ + if (nb_completions < NB_COMPLETIONS_MAX) { + completions[nb_completions++] = qemu_strdup(str); + } +} + +static void term_completion(ReadlineState *s) +{ + int len, i, j, max_width, nb_cols, max_prefix; + char *cmdline; + + nb_completions = 0; + + cmdline = qemu_malloc(term_cmd_buf_index + 1); + if (!cmdline) + return; + memcpy(cmdline, term_cmd_buf, term_cmd_buf_index); + cmdline[term_cmd_buf_index] = '\0'; + readline_find_completion(cmdline); + qemu_free(cmdline); + + /* no completion found */ + if (nb_completions <= 0) + return; + if (nb_completions == 1) { + len = strlen(completions[0]); + for(i = completion_index; i < len; i++) { + term_insert_char(completions[0][i]); + } + /* extra space for next argument. XXX: make it more generic */ + if (len > 0 && completions[0][len - 1] != '/') + term_insert_char(' '); + } else { + term_printf("\n"); + max_width = 0; + max_prefix = 0; + for(i = 0; i < nb_completions; i++) { + len = strlen(completions[i]); + if (i==0) { + max_prefix = len; + } else { + if (len < max_prefix) + max_prefix = len; + for(j=0; j max_width) + max_width = len; + } + if (max_prefix > 0) { + for(i = completion_index; i < max_prefix; i++) { + term_insert_char(completions[0][i]); + } + } + max_width += 2; + if (max_width < 10) + max_width = 10; + else if (max_width > 80) + max_width = 80; + nb_cols = 80 / max_width; + j = 0; + for(i = 0; i < nb_completions; i++) { + term_printf("%-*s", max_width, completions[i]); + if (++j == nb_cols || i == (nb_completions - 1)) { + term_printf("\n"); + j = 0; + } + } + term_show_prompt2(); + } +} +#endif + +static void term_return(ReadlineState *s) +{ + s->term_cmd_buf[s->term_cmd_buf_len] = '\0'; + if (!s->term_is_password) + term_hist_add(s, (const char *)s->term_cmd_buf); + s->term_cmd_buf_index = s->term_cmd_buf_len; +} + +static int readline_handle_char(ReadlineState *s, int ch) +{ + int ret = READLINE_RET_HANDLED; + + switch(s->term_esc_state) { + case IS_NORM: + switch(ch) { + case 1: /* ^A */ + term_bol(s); + break; + case 4: /* ^D */ + if (s->term_cmd_buf_len == 0) { + term_printf("^D\n"); + return READLINE_RET_EXIT; + } + term_delete_char(s); + break; + case 5: /* ^E */ + term_eol(s); + break; + case 9: /* TAB */ + //term_completion(s); + break; + case 10: + case 13: + term_return(s); + ret = READLINE_RET_ACCEPTED; + break; + case 11: /* ^K */ + term_kill_region(s, s->term_cmd_buf_len, 1); + break; + case 21: /* ^U */ + term_kill_region(s, 0, 1); + break; + case 23: /* ^W */ + term_kill_word_backward(s); + break; + case 25: /* ^Y */ + term_yank(s); + break; + case 27: + s->term_esc_state = IS_ESC; + break; + case 127: /* DEL */ + case 8: /* ^H */ + term_backspace(s); + break; + case 155: /* 0x9B */ + s->term_esc_state = IS_CSI; + break; + default: + if (ch >= 32) { + term_insert_char(s, ch); + break; + } + return 0; + } + break; + case IS_ESC: + s->term_esc_state = IS_NORM; + switch (ch) { + case '[': + case 'O': + s->term_esc_state = IS_CSI; + s->term_esc_param2 = 0; + s->term_esc_param1 = 0; + s->term_esc_param = 0; + break; + case 13: + /* ESC+RET or M-RET: validate in multi-line */ + term_return(s); + break; + case 8: + case 127: + term_kill_word_backward(s); + break; + case 'b': + term_skip_word_backward(s); + break; + case 'd': + term_kill_word(s); + break; + case 'f': + term_skip_word_forward(s); + break; + default: + return 0; + } + break; + case IS_CSI: + s->term_esc_state = IS_NORM; + switch(ch) { + case 'A': + term_up_char(s); + break; + case 'B': + case 'E': + term_down_char(s); + break; + case 'D': + term_backward_char(s); + break; + case 'C': + term_forward_char(s); + break; + case 'F': + term_eol(s); + break; + case 'H': + term_bol(s); + break; + case ';': + s->term_esc_param2 = s->term_esc_param1; + s->term_esc_param1 = s->term_esc_param; + s->term_esc_param = 0; + s->term_esc_state = IS_CSI; + break; + case '0' ... '9': + s->term_esc_param = s->term_esc_param * 10 + (ch - '0'); + s->term_esc_state = IS_CSI; + break; + case '~': + switch(s->term_esc_param) { + case 1: + term_bol(s); + break; + case 3: + term_delete_char(s); + break; + case 4: + term_eol(s); + break; + default: + return READLINE_RET_NOT_HANDLED; + } + break; + default: + return READLINE_RET_NOT_HANDLED; + } + break; + } + term_update(s); + if (ret == READLINE_RET_ACCEPTED) { + term_printf("\n"); + } + return ret; +} + +/* return > 0 if command handled, -1 if exit */ +/* XXX: could process buffers to avoid redisplaying at each char input + (copy paste case) */ +int readline_handle_byte(ReadlineState *s, int c) +{ + if (c >= 0xc0 && c < 0xf8) { + s->utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0); + s->utf8_val = c & ((1 << (6 - s->utf8_state)) - 1); + return READLINE_RET_HANDLED; + } + if (s->utf8_state != 0) { + if (c >= 0x80 && c < 0xc0) { + s->utf8_val = (s->utf8_val << 6) | (c & 0x3F); + s->utf8_state--; + if (s->utf8_state) + return READLINE_RET_HANDLED; + c = s->utf8_val; + } + s->utf8_state = 0; + } + return readline_handle_char(s, c); +} + +void readline_start(ReadlineState *s, const char *prompt, int is_password) +{ + s->term_prompt = prompt; + s->term_is_password = is_password; + s->term_hist_entry = -1; + s->term_cmd_buf_index = 0; + s->term_cmd_buf_len = 0; + term_show_prompt(s); +} diff --git a/readline.h b/readline.h new file mode 100644 index 00000000..639750ac --- /dev/null +++ b/readline.h @@ -0,0 +1,98 @@ +/* + * readline utility + * + * Copyright (c) 2003-2025 Fabrice Bellard + * + * 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 READLINE_H +#define READLINE_H + +#include "cutils.h" + +typedef struct ReadlineState ReadlineState; + +typedef void ReadLineFunc(void *opaque, const char *str); +typedef int ReadLineGetColor(int *plen, const char *buf, int pos, int buf_len); + +struct ReadlineState { + int term_cmd_buf_index; /* byte position in the command line */ + int term_cmd_buf_len; /* byte length of the command line */ + + uint32_t utf8_val; + uint8_t term_cmd_updated; /* if the command line was updated */ + uint8_t utf8_state; + uint8_t term_esc_state; + int term_esc_param; + int term_esc_param1; + int term_esc_param2; + int term_cursor_x; /* 0 <= term_cursor_x < term_width */ + int term_cursor_pos; /* linear position */ + + int term_hist_entry; /* position in term_history or -1 */ + int term_history_size; /* size of term_historyf */ + uint8_t term_is_password; + const char *term_prompt; + /* the following fields must be initialized by the user */ + int term_width; + int term_cmd_buf_size; + int term_kill_buf_len; + uint8_t *term_cmd_buf; /* allocated length is term_cmd_buf_size */ + uint8_t *term_kill_buf; /* allocated length is term_cmd_buf_size */ + int term_history_buf_size; + char *term_history; /* zero separated history entries */ + ReadLineGetColor *get_color; /* NULL if no colorization */ +}; + +#define COLOR_NONE 0 +#define COLOR_BLACK 1 +#define COLOR_RED 2 +#define COLOR_GREEN 3 +#define COLOR_YELLOW 4 +#define COLOR_BLUE 5 +#define COLOR_MAGENTA 6 +#define COLOR_CYAN 7 +#define COLOR_WHITE 8 +#define COLOR_GRAY 9 +#define COLOR_BRIGHT_RED 10 +#define COLOR_BRIGHT_GREEN 11 +#define COLOR_BRIGHT_YELLOW 12 +#define COLOR_BRIGHT_BLUE 13 +#define COLOR_BRIGHT_MAGENTA 14 +#define COLOR_BRIGHT_CYAN 15 +#define COLOR_BRIGHT_WHITE 16 + +extern const char *term_colors[17]; + +void add_completion(const char *str); + +#define READLINE_RET_EXIT (-1) +#define READLINE_RET_NOT_HANDLED 0 /* command not handled */ +#define READLINE_RET_HANDLED 1 /* command handled */ +#define READLINE_RET_ACCEPTED 2 /* return pressed */ +/* return READLINE_RET_x */ +int readline_handle_byte(ReadlineState *s, int c); +void readline_start(ReadlineState *s, const char *prompt, int is_password); + +/* the following functions must be provided */ +void readline_find_completion(const char *cmdline); +void term_printf(const char *fmt, ...) __attribute__ ((__format__ (__printf__, 1, 2))); +void term_flush(void); + +#endif /* READLINE_H */ diff --git a/readline_tty.c b/readline_tty.c new file mode 100644 index 00000000..9a7e929b --- /dev/null +++ b/readline_tty.c @@ -0,0 +1,246 @@ +/* + * Readline TTY support + * + * Copyright (c) 2017-2025 Fabrice Bellard + * + * 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 +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include "readline_tty.h" + +static int ctrl_c_pressed; + +#ifdef _WIN32 +/* Windows 10 built-in VT100 emulation */ +#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 + +static BOOL WINAPI ctrl_handler(DWORD type) +{ + if (type == CTRL_C_EVENT) { + ctrl_c_pressed++; + if (ctrl_c_pressed >= 4) { + /* just to be able to stop the process if it is hanged */ + return FALSE; + } else { + return TRUE; + } + } else { + return FALSE; + } +} + +int readline_tty_init(void) +{ + HANDLE handle; + CONSOLE_SCREEN_BUFFER_INFO info; + int n_cols; + + handle = (HANDLE)_get_osfhandle(0); + SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); + _setmode(0, _O_BINARY); + + handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ + SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + SetConsoleCtrlHandler(ctrl_handler, TRUE); + + n_cols = 80; + if (GetConsoleScreenBufferInfo(handle, &info)) { + n_cols = info.dwSize.X; + } + return n_cols; +} + +/* if processed input is enabled, Ctrl-C is handled by ctrl_handler() */ +static void set_processed_input(BOOL enable) +{ + DWORD mode; + HANDLE handle; + + handle = (HANDLE)_get_osfhandle(0); + if (!GetConsoleMode(handle, &mode)) + return; + if (enable) + mode |= ENABLE_PROCESSED_INPUT; + else + mode &= ~ENABLE_PROCESSED_INPUT; + SetConsoleMode(handle, mode); +} + +#else +/* init terminal so that we can grab keys */ +/* XXX: merge with cp_utils.c */ +static struct termios oldtty; +static int old_fd0_flags; + +static void term_exit(void) +{ + tcsetattr (0, TCSANOW, &oldtty); + fcntl(0, F_SETFL, old_fd0_flags); +} + +static void sigint_handler(int signo) +{ + ctrl_c_pressed++; + if (ctrl_c_pressed >= 4) { + /* just to be able to stop the process if it is hanged */ + signal(SIGINT, SIG_DFL); + } +} + +int readline_tty_init(void) +{ + struct termios tty; + struct sigaction sa; + struct winsize ws; + int n_cols; + + tcgetattr (0, &tty); + oldtty = tty; + old_fd0_flags = fcntl(0, F_GETFL); + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + // tty.c_lflag &= ~ISIG; /* ctrl-C returns a signal */ + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr (0, TCSANOW, &tty); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigint_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + + atexit(term_exit); + + // fcntl(0, F_SETFL, O_NONBLOCK); + n_cols = 80; + if (ioctl(0, TIOCGWINSZ, &ws) == 0 && + ws.ws_col >= 4 && ws.ws_row >= 4) { + n_cols = ws.ws_col; + } + return n_cols; +} +#endif + +void term_printf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +void term_flush(void) +{ + fflush(stdout); +} + +const char *readline_tty(ReadlineState *s, + const char *prompt, BOOL multi_line) +{ + int len, i, ctrl_c_count, c, ret; + const char *ret_str; + uint8_t buf[128]; + +#ifdef _WIN32 + set_processed_input(FALSE); + /* ctrl-C is no longer handled by the system */ +#endif + ret_str = NULL; + readline_start(s, prompt, FALSE); + ctrl_c_count = 0; + while (ret_str == NULL) { + len = read(0, buf, sizeof(buf)); + if (len == 0) + break; + for(i = 0; i < len; i++) { + c = buf[i]; +#ifdef _WIN32 + if (c == 3) { + /* ctrl-C */ + ctrl_c_pressed++; + } else +#endif + { + ret = readline_handle_byte(s, c); + if (ret == READLINE_RET_EXIT) { + goto done; + } else if (ret == READLINE_RET_ACCEPTED) { + ret_str = (const char *)s->term_cmd_buf; + goto done; + } + ctrl_c_count = 0; + } + } + if (ctrl_c_pressed) { + ctrl_c_pressed = 0; + if (ctrl_c_count == 0) { + printf("(Press Ctrl-C again to quit)\n"); + ctrl_c_count++; + } else { + printf("Exiting.\n"); + break; + } + } + } +done: +#ifdef _WIN32 + set_processed_input(TRUE); +#endif + return ret_str; +} + +BOOL readline_is_interrupted(void) +{ + BOOL ret; + ret = (ctrl_c_pressed != 0); + ctrl_c_pressed = 0; + return ret; +} diff --git a/readline_tty.h b/readline_tty.h new file mode 100644 index 00000000..7ef33cb5 --- /dev/null +++ b/readline_tty.h @@ -0,0 +1,29 @@ +/* + * Readline TTY support + * + * Copyright (c) 2017-2025 Fabrice Bellard + * + * 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 "readline.h" + +int readline_tty_init(void); +const char *readline_tty(ReadlineState *s, + const char *prompt, BOOL multi_line); +BOOL readline_is_interrupted(void); diff --git a/softfp_template.h b/softfp_template.h new file mode 100644 index 00000000..34034fd8 --- /dev/null +++ b/softfp_template.h @@ -0,0 +1,970 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * 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. + */ +#if F_SIZE == 32 +#define F_UINT uint32_t +#define F_ULONG uint64_t +#define MANT_SIZE 23 +#define EXP_SIZE 8 +#elif F_SIZE == 64 +#define F_UHALF uint32_t +#define F_UINT uint64_t +#ifdef HAVE_INT128 +#define F_ULONG uint128_t +#endif +#define MANT_SIZE 52 +#define EXP_SIZE 11 +#elif F_SIZE == 128 +#define F_UHALF uint64_t +#define F_UINT uint128_t +#define MANT_SIZE 112 +#define EXP_SIZE 15 +#else +#error unsupported F_SIZE +#endif + +#define EXP_MASK ((1 << EXP_SIZE) - 1) +#define MANT_MASK (((F_UINT)1 << MANT_SIZE) - 1) +#define SIGN_MASK ((F_UINT)1 << (F_SIZE - 1)) +#define IMANT_SIZE (F_SIZE - 2) /* internal mantissa size */ +#define RND_SIZE (IMANT_SIZE - MANT_SIZE) +#define QNAN_MASK ((F_UINT)1 << (MANT_SIZE - 1)) +#define EXP_BIAS ((1 << (EXP_SIZE - 1)) - 1) + +/* quiet NaN */ +#define F_QNAN glue(F_QNAN, F_SIZE) +#define clz glue(clz, F_SIZE) +#define pack_sf glue(pack_sf, F_SIZE) +#define unpack_sf glue(unpack_sf, F_SIZE) +#define rshift_rnd glue(rshift_rnd, F_SIZE) +#define round_pack_sf glue(roundpack_sf, F_SIZE) +#define normalize_sf glue(normalize_sf, F_SIZE) +#define normalize2_sf glue(normalize2_sf, F_SIZE) +#define issignan_sf glue(issignan_sf, F_SIZE) +#define isnan_sf glue(isnan_sf, F_SIZE) +#define add_sf glue(add_sf, F_SIZE) +#define mul_sf glue(mul_sf, F_SIZE) +#define fma_sf glue(fma_sf, F_SIZE) +#define div_sf glue(div_sf, F_SIZE) +#define sqrt_sf glue(sqrt_sf, F_SIZE) +#define normalize_subnormal_sf glue(normalize_subnormal_sf, F_SIZE) +#define divrem_u glue(divrem_u, F_SIZE) +#define sqrtrem_u glue(sqrtrem_u, F_SIZE) +#define mul_u glue(mul_u, F_SIZE) +#define cvt_sf32_sf glue(cvt_sf32_sf, F_SIZE) +#define cvt_sf64_sf glue(cvt_sf64_sf, F_SIZE) + +static const F_UINT F_QNAN = (((F_UINT)EXP_MASK << MANT_SIZE) | ((F_UINT)1 << (MANT_SIZE - 1))); + +static inline F_UINT pack_sf(uint32_t a_sign, uint32_t a_exp, F_UINT a_mant) +{ + return ((F_UINT)a_sign << (F_SIZE - 1)) | + ((F_UINT)a_exp << MANT_SIZE) | + (a_mant & MANT_MASK); +} + +static inline F_UINT unpack_sf(uint32_t *pa_sign, int32_t *pa_exp, + F_UINT a) +{ + *pa_sign = a >> (F_SIZE - 1); + *pa_exp = (a >> MANT_SIZE) & EXP_MASK; + return a & MANT_MASK; +} + +static F_UINT rshift_rnd(F_UINT a, int d) +{ + F_UINT mask; + if (d != 0) { + if (d >= F_SIZE) { + a = (a != 0); + } else { + mask = ((F_UINT)1 << d) - 1; + a = (a >> d) | ((a & mask) != 0); + } + } + return a; +} + +#if F_USE_FFLAGS +#define FFLAGS_PARAM , uint32_t *pfflags +#define FFLAGS_ARG , pfflags +#else +#define FFLAGS_PARAM +#define FFLAGS_ARG +#endif + +/* a_mant is considered to have its MSB at F_SIZE - 2 bits */ +static F_UINT round_pack_sf(uint32_t a_sign, int a_exp, F_UINT a_mant, + RoundingModeEnum rm FFLAGS_PARAM) +{ + int diff; + uint32_t addend, rnd_bits; + + switch(rm) { + case RM_RNE: + case RM_RMM: + addend = (1 << (RND_SIZE - 1)); + break; + case RM_RTZ: + addend = 0; + break; + default: + case RM_RDN: + case RM_RUP: + // printf("s=%d rm=%d m=%x\n", a_sign, rm, a_mant); + if (a_sign ^ (rm & 1)) + addend = (1 << RND_SIZE) - 1; + else + addend = 0; + break; + } + + /* potentially subnormal */ + if (a_exp <= 0) { + BOOL is_subnormal; + /* Note: we set the underflow flag if the rounded result + is subnormal and inexact */ + is_subnormal = (a_exp < 0 || + (a_mant + addend) < ((F_UINT)1 << (F_SIZE - 1))); + diff = 1 - a_exp; + a_mant = rshift_rnd(a_mant, diff); + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + if (is_subnormal && rnd_bits != 0) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_UNDERFLOW; +#endif + } + a_exp = 1; + } else { + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + } +#if F_USE_FFLAGS + if (rnd_bits != 0) + *pfflags |= FFLAG_INEXACT; +#endif + a_mant = (a_mant + addend) >> RND_SIZE; + /* half way: select even result */ + if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1))) + a_mant &= ~1; + /* Note the rounding adds at least 1, so this is the maximum + value */ + a_exp += a_mant >> (MANT_SIZE + 1); + if (a_mant <= MANT_MASK) { + /* denormalized or zero */ + a_exp = 0; + } else if (a_exp >= EXP_MASK) { + /* overflow */ + if (addend == 0) { + a_exp = EXP_MASK - 1; + a_mant = MANT_MASK; + } else { + /* infinity */ + a_exp = EXP_MASK; + a_mant = 0; + } +#if F_USE_FFLAGS + *pfflags |= FFLAG_OVERFLOW | FFLAG_INEXACT; +#endif + } + return pack_sf(a_sign, a_exp, a_mant); +} + +/* a_mant is considered to have at most F_SIZE - 1 bits */ +static F_UINT normalize_sf(uint32_t a_sign, int a_exp, F_UINT a_mant, + RoundingModeEnum rm FFLAGS_PARAM) +{ + int shift; + shift = clz(a_mant) - (F_SIZE - 1 - IMANT_SIZE); + assert(shift >= 0); + a_exp -= shift; + a_mant <<= shift; + return round_pack_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +static inline F_UINT normalize_subnormal_sf(int32_t *pa_exp, F_UINT a_mant) +{ + int shift; + shift = MANT_SIZE - ((F_SIZE - 1 - clz(a_mant))); + *pa_exp = 1 - shift; + return a_mant << shift; +} + +#if F_USE_FFLAGS +F_STATIC BOOL issignan_sf(F_UINT a) +{ + uint32_t a_exp1; + F_UINT a_mant; + a_exp1 = (a >> (MANT_SIZE - 1)) & ((1 << (EXP_SIZE + 1)) - 1); + a_mant = a & MANT_MASK; + return (a_exp1 == (2 * EXP_MASK) && a_mant != 0); +} +#endif + +#ifndef F_NORMALIZE_ONLY + +F_STATIC BOOL isnan_sf(F_UINT a) +{ + uint32_t a_exp; + F_UINT a_mant; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + return (a_exp == EXP_MASK && a_mant != 0); +} + + +F_STATIC F_UINT add_sf(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign, a_exp, b_exp; + F_UINT tmp, a_mant, b_mant; + + /* swap so that abs(a) >= abs(b) */ + if ((a & ~SIGN_MASK) < (b & ~SIGN_MASK)) { + tmp = a; + a = b; + b = tmp; + } + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = (a & MANT_MASK) << 3; + b_mant = (b & MANT_MASK) << 3; + if (unlikely(a_exp == EXP_MASK)) { + if (a_mant != 0) { + /* NaN result */ +#if F_USE_FFLAGS + if (!(a_mant & (QNAN_MASK << 3)) || issignan_sf(b)) + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else if (b_exp == EXP_MASK && a_sign != b_sign) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { + /* infinity */ + return a; + } + } + if (a_exp == 0) { + a_exp = 1; + } else { + a_mant |= (F_UINT)1 << (MANT_SIZE + 3); + } + if (b_exp == 0) { + b_exp = 1; + } else { + b_mant |= (F_UINT)1 << (MANT_SIZE + 3); + } + b_mant = rshift_rnd(b_mant, a_exp - b_exp); + if (a_sign == b_sign) { + /* same signs : add the absolute values */ + a_mant += b_mant; + } else { + /* different signs : subtract the absolute values */ + a_mant -= b_mant; + if (a_mant == 0) { + /* zero result : the sign needs a specific handling */ + a_sign = (rm == RM_RDN); + } + } + a_exp += (RND_SIZE - 3); + return normalize_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +F_STATIC F_UINT glue(sub_sf, F_SIZE)(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + return add_sf(a, b ^ SIGN_MASK, rm FFLAGS_ARG); +} + +#ifdef F_ULONG + +static F_UINT mul_u(F_UINT *plow, F_UINT a, F_UINT b) +{ + F_ULONG r; + r = (F_ULONG)a * (F_ULONG)b; + *plow = r; + return r >> F_SIZE; +} + +#else + +#define FH_SIZE (F_SIZE / 2) + +static F_UINT mul_u(F_UINT *plow, F_UINT a, F_UINT b) +{ + F_UHALF a0, a1, b0, b1, r0, r1, r2, r3; + F_UINT r00, r01, r10, r11, c; + a0 = a; + a1 = a >> FH_SIZE; + b0 = b; + b1 = b >> FH_SIZE; + + r00 = (F_UINT)a0 * (F_UINT)b0; + r01 = (F_UINT)a0 * (F_UINT)b1; + r10 = (F_UINT)a1 * (F_UINT)b0; + r11 = (F_UINT)a1 * (F_UINT)b1; + + r0 = r00; + c = (r00 >> FH_SIZE) + (F_UHALF)r01 + (F_UHALF)r10; + r1 = c; + c = (c >> FH_SIZE) + (r01 >> FH_SIZE) + (r10 >> FH_SIZE) + (F_UHALF)r11; + r2 = c; + r3 = (c >> FH_SIZE) + (r11 >> FH_SIZE); + + *plow = ((F_UINT)r1 << FH_SIZE) | r0; + return ((F_UINT)r3 << FH_SIZE) | r2; +} + +#undef FH_SIZE + +#endif + +F_STATIC F_UINT mul_sf(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign, r_sign; + int32_t a_exp, b_exp, r_exp; + F_UINT a_mant, b_mant, r_mant, r_mant_low; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + if (a_exp == EXP_MASK || b_exp == EXP_MASK) { + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + /* infinity */ + if ((a_exp == EXP_MASK && (b_exp == 0 && b_mant == 0)) || + (b_exp == EXP_MASK && (a_exp == 0 && a_mant == 0))) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + if (b_exp == 0) { + if (b_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + r_exp = a_exp + b_exp - (1 << (EXP_SIZE - 1)) + 2; + + r_mant = mul_u(&r_mant_low,a_mant << RND_SIZE, b_mant << (RND_SIZE + 1)); + r_mant |= (r_mant_low != 0); + return normalize_sf(r_sign, r_exp, r_mant, rm FFLAGS_ARG); +} + +#ifdef F_ULONG + +static F_UINT divrem_u(F_UINT *pr, F_UINT ah, F_UINT al, F_UINT b) +{ + F_ULONG a; + a = ((F_ULONG)ah << F_SIZE) | al; + *pr = a % b; + return a / b; +} + +#else + +/* XXX: optimize */ +static F_UINT divrem_u(F_UINT *pr, F_UINT a1, F_UINT a0, F_UINT b) +{ + int i, qb, ab; + + assert(a1 < b); + for(i = 0; i < F_SIZE; i++) { + ab = a1 >> (F_SIZE - 1); + a1 = (a1 << 1) | (a0 >> (F_SIZE - 1)); + if (ab || a1 >= b) { + a1 -= b; + qb = 1; + } else { + qb = 0; + } + a0 = (a0 << 1) | qb; + } + *pr = a1; + return a0; +} + +#endif + +F_STATIC F_UINT div_sf(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign, r_sign; + int32_t a_exp, b_exp, r_exp; + F_UINT a_mant, b_mant, r_mant, r; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0 || isnan_sf(b)) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else if (b_exp == EXP_MASK) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } else if (b_exp == EXP_MASK) { + if (b_mant != 0) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + return pack_sf(r_sign, 0, 0); + } + } + + if (b_exp == 0) { + if (b_mant == 0) { + /* zero */ + if (a_exp == 0 && a_mant == 0) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { +#if F_USE_FFLAGS + *pfflags |= FFLAG_DIVIDE_ZERO; +#endif + return pack_sf(r_sign, EXP_MASK, 0); + } + } + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + r_exp = a_exp - b_exp + (1 << (EXP_SIZE - 1)) - 1; + r_mant = divrem_u(&r, a_mant, 0, b_mant << 2); + if (r != 0) + r_mant |= 1; + return normalize_sf(r_sign, r_exp, r_mant, rm FFLAGS_ARG); +} + +#ifdef F_ULONG + +/* compute sqrt(a) with a = ah*2^F_SIZE+al and a < 2^(F_SIZE - 2) + return true if not exact square. */ +static int sqrtrem_u(F_UINT *pr, F_UINT ah, F_UINT al) +{ + F_ULONG a, u, s; + int l, inexact; + + /* 2^l >= a */ + if (ah != 0) { + l = 2 * F_SIZE - clz(ah - 1); + } else { + if (al == 0) { + *pr = 0; + return 0; + } + l = F_SIZE - clz(al - 1); + } + a = ((F_ULONG)ah << F_SIZE) | al; + u = (F_ULONG)1 << ((l + 1) / 2); + for(;;) { + s = u; + u = ((a / s) + s) / 2; + if (u >= s) + break; + } + inexact = (a - s * s) != 0; + *pr = s; + return inexact; +} + +#else + +static int sqrtrem_u(F_UINT *pr, F_UINT a1, F_UINT a0) +{ + int l, inexact; + F_UINT u, s, r, q, sq0, sq1; + + /* 2^l >= a */ + if (a1 != 0) { + l = 2 * F_SIZE - clz(a1 - 1); + } else { + if (a0 == 0) { + *pr = 0; + return 0; + } + l = F_SIZE - clz(a0 - 1); + } + u = (F_UINT)1 << ((l + 1) / 2); + for(;;) { + s = u; + q = divrem_u(&r, a1, a0, s); + u = (q + s) / 2; + if (u >= s) + break; + } + sq1 = mul_u(&sq0, s, s); + inexact = (sq0 != a0 || sq1 != a1); + *pr = s; + return inexact; +} + +#endif + +F_STATIC F_UINT sqrt_sf(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0) { +#if F_USE_FFLAGS + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else if (a_sign) { + goto neg_error; + } else { + return a; /* +infinity */ + } + } + if (a_sign) { + if (a_exp == 0 && a_mant == 0) + return a; /* -zero */ + neg_error: +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(0, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + a_exp -= EXP_MASK / 2; + /* simpler to handle an even exponent */ + if (a_exp & 1) { + a_exp--; + a_mant <<= 1; + } + a_exp = (a_exp >> 1) + EXP_MASK / 2; + a_mant <<= (F_SIZE - 4 - MANT_SIZE); + if (sqrtrem_u(&a_mant, a_mant, 0)) + a_mant |= 1; + return normalize_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +/* comparisons */ + +F_STATIC int glue(eq_quiet_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return 0; + } + + if ((F_UINT)((a | b) << 1) == 0) + return 1; /* zero case */ + return (a == b); +} + +F_STATIC int glue(le_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return 0; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + return (a_sign || ((F_UINT)((a | b) << 1) == 0)); + } else { + if (a_sign) { + return (a >= b); + } else { + return (a <= b); + } + } +} + +F_STATIC int glue(lt_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return 0; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + return (a_sign && ((F_UINT)((a | b) << 1) != 0)); + } else { + if (a_sign) { + return (a > b); + } else { + return (a < b); + } + } +} + +/* return -1 (a < b), 0 (a = b), 1 (a > b) or 2 (a = nan or b = + nan) */ +F_STATIC int glue(cmp_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return 2; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + if ((F_UINT)((a | b) << 1) != 0) + return 1 - 2 * a_sign; + else + return 0; /* -0 = +0 */ + } else { + if (a < b) + return 2 * a_sign - 1; + else if (a > b) + return 1 - 2 * a_sign; + else + return 0; + } +} + +/* conversions between floats */ + +#if F_SIZE >= 64 + +F_STATIC F_UINT cvt_sf32_sf(uint32_t a FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf32(&a_sign, &a_exp, a); + if (a_exp == 0xff) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf32(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + /* infinity */ + return pack_sf(a_sign, EXP_MASK, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(a_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf32(&a_exp, a_mant); + } + /* convert the exponent value */ + a_exp = a_exp - 0x7f + (EXP_MASK / 2); + /* shift the mantissa */ + a_mant <<= (MANT_SIZE - 23); + /* We assume the target float is large enough to that no + normalization is necessary */ + return pack_sf(a_sign, a_exp, a_mant); +} + +F_STATIC uint32_t glue(glue(cvt_sf, F_SIZE), _sf32)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf(&a_sign, &a_exp, a); + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN32; + } else { + /* infinity */ + return pack_sf32(a_sign, 0xff, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf32(a_sign, 0, 0); /* zero */ + normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + /* convert the exponent value */ + a_exp = a_exp - (EXP_MASK / 2) + 0x7f; + /* shift the mantissa */ + a_mant = rshift_rnd(a_mant, MANT_SIZE - (32 - 2)); + return normalize_sf32(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +#endif + +#if F_SIZE >= 128 + +F_STATIC F_UINT cvt_sf64_sf(uint64_t a FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf64(&a_sign, &a_exp, a); + + if (a_exp == 0x7ff) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf64(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + /* infinity */ + return pack_sf(a_sign, EXP_MASK, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(a_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf64(&a_exp, a_mant); + } + /* convert the exponent value */ + a_exp = a_exp - 0x3ff + (EXP_MASK / 2); + /* shift the mantissa */ + a_mant <<= (MANT_SIZE - 52); + return pack_sf(a_sign, a_exp, a_mant); +} + +F_STATIC uint64_t glue(glue(cvt_sf, F_SIZE), _sf64)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf(&a_sign, &a_exp, a); + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN64; + } else { + /* infinity */ + return pack_sf64(a_sign, 0x7ff, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf64(a_sign, 0, 0); /* zero */ + normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + /* convert the exponent value */ + a_exp = a_exp - (EXP_MASK / 2) + 0x3ff; + /* shift the mantissa */ + a_mant = rshift_rnd(a_mant, MANT_SIZE - (64 - 2)); + return normalize_sf64(a_sign, a_exp, a_mant, rm, pfflags); +} + +#endif + +#undef clz + +#define ICVT_SIZE 32 +#include "softfp_template_icvt.h" + +#define ICVT_SIZE 64 +#include "softfp_template_icvt.h" + +/* additional libm functions */ + +/* return a mod b (exact) */ +F_STATIC F_UINT glue(fmod_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp, b_exp, n; + F_UINT a_mant, b_mant, a_abs, b_abs; + + a_abs = a & ~SIGN_MASK; + b_abs = b & ~SIGN_MASK; + if (b_abs == 0 || + a_abs >= ((F_UINT)EXP_MASK << MANT_SIZE) || + b_abs > ((F_UINT)EXP_MASK << MANT_SIZE)) { + /* XXX: flags */ + return F_QNAN; + } + if (a_abs < b_abs) { + return a; /* |a| < |b| return a */ + } else if (a_abs == b_abs) { + return a & SIGN_MASK; /* |a| = |b| return copy_sign(0, a) */ + } + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = (a & MANT_MASK); + b_mant = (b & MANT_MASK); + + if (a_exp == 0) { + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + if (b_exp == 0) { + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + n = a_exp - b_exp; + if (a_mant >= b_mant) + a_mant -= b_mant; + /* here a_mant < b_mant and n >= 0 */ + /* multiply a_mant by 2^n */ + /* XXX: do it faster */ + while (n != 0) { + a_mant <<= 1; + if (a_mant >= b_mant) + a_mant -= b_mant; + n--; + } + /* Note: the rounding mode does not matter because the result is + exact */ + return normalize_sf(a_sign, b_exp, a_mant << RND_SIZE, RM_RNE FFLAGS_ARG); +} +#endif /* F_NORMALIZE_ONLY */ + +#undef F_SIZE +#undef F_UINT +#undef F_ULONG +#undef F_UHALF +#undef MANT_SIZE +#undef EXP_SIZE +#undef EXP_MASK +#undef MANT_MASK +#undef SIGN_MASK +#undef IMANT_SIZE +#undef RND_SIZE +#undef QNAN_MASK +#undef F_QNAN +#undef F_NORMALIZE_ONLY +#undef EXP_BIAS + +#undef pack_sf +#undef unpack_sf +#undef rshift_rnd +#undef round_pack_sf +#undef normalize_sf +#undef normalize2_sf +#undef issignan_sf +#undef isnan_sf +#undef add_sf +#undef mul_sf +#undef fma_sf +#undef div_sf +#undef sqrt_sf +#undef normalize_subnormal_sf +#undef divrem_u +#undef sqrtrem_u +#undef mul_u +#undef cvt_sf32_sf +#undef cvt_sf64_sf diff --git a/softfp_template_icvt.h b/softfp_template_icvt.h new file mode 100644 index 00000000..02fee109 --- /dev/null +++ b/softfp_template_icvt.h @@ -0,0 +1,172 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * 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. + */ +#if ICVT_SIZE == 32 +#define ICVT_UINT uint32_t +#define ICVT_INT int32_t +#elif ICVT_SIZE == 64 +#define ICVT_UINT uint64_t +#define ICVT_INT int64_t +#elif ICVT_SIZE == 128 +#define ICVT_UINT uint128_t +#define ICVT_INT int128_t +#else +#error unsupported icvt +#endif + +/* conversions between float and integers */ +static ICVT_INT glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm, + BOOL is_unsigned FFLAGS_PARAM) +{ + uint32_t a_sign, addend, rnd_bits; + int32_t a_exp; + F_UINT a_mant; + ICVT_UINT r, r_max; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK && a_mant != 0) + a_sign = 0; /* NaN is like +infinity */ + if (a_exp == 0) { + a_exp = 1; + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + a_mant <<= RND_SIZE; + a_exp = a_exp - (EXP_MASK / 2) - MANT_SIZE; + + if (is_unsigned) + r_max = (ICVT_UINT)a_sign - 1; + else + r_max = ((ICVT_UINT)1 << (ICVT_SIZE - 1)) - (ICVT_UINT)(a_sign ^ 1); + if (a_exp >= 0) { + if (a_exp <= (ICVT_SIZE - 1 - MANT_SIZE)) { + r = (ICVT_UINT)(a_mant >> RND_SIZE) << a_exp; + if (r > r_max) + goto overflow; + } else { + overflow: +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return r_max; + } + } else { + a_mant = rshift_rnd(a_mant, -a_exp); + + switch(rm) { + case RM_RNE: + case RM_RMM: + addend = (1 << (RND_SIZE - 1)); + break; + case RM_RTZ: + addend = 0; + break; + default: + case RM_RDN: + case RM_RUP: + if (a_sign ^ (rm & 1)) + addend = (1 << RND_SIZE) - 1; + else + addend = 0; + break; + } + + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + a_mant = (a_mant + addend) >> RND_SIZE; + /* half way: select even result */ + if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1))) + a_mant &= ~1; + if (a_mant > r_max) + goto overflow; + r = a_mant; +#if F_USE_FFLAGS + if (rnd_bits != 0) + *pfflags |= FFLAG_INEXACT; +#endif + } + if (a_sign) + r = -r; + return r; +} + +F_STATIC ICVT_INT __maybe_unused glue(glue(glue(cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(a, rm, + FALSE FFLAGS_ARG); +} + +F_STATIC ICVT_UINT __maybe_unused glue(glue(glue(cvt_sf, F_SIZE), _u), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE) (a, rm, + TRUE FFLAGS_ARG); +} + +/* conversions between float and integers */ +static F_UINT glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, + RoundingModeEnum rm, + BOOL is_unsigned FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + ICVT_UINT r, mask; + int l; + + if (!is_unsigned && a < 0) { + a_sign = 1; + r = -(ICVT_UINT)a; + } else { + a_sign = 0; + r = a; + } + a_exp = (EXP_MASK / 2) + F_SIZE - 2; + /* need to reduce range before generic float normalization */ + l = ICVT_SIZE - glue(clz, ICVT_SIZE)(r) - (F_SIZE - 1); + if (l > 0) { + mask = r & (((ICVT_UINT)1 << l) - 1); + r = (r >> l) | ((r & mask) != 0); + a_exp += l; + } + a_mant = r; + return normalize_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +F_STATIC F_UINT __maybe_unused glue(glue(glue(cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, + RoundingModeEnum rm + FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, FALSE FFLAGS_ARG); +} + +F_STATIC F_UINT __maybe_unused glue(glue(glue(cvt_u, ICVT_SIZE), _sf), F_SIZE)(ICVT_UINT a, + RoundingModeEnum rm + FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, TRUE FFLAGS_ARG); +} + +#undef ICVT_SIZE +#undef ICVT_INT +#undef ICVT_UINT diff --git a/tests/mandelbrot.js b/tests/mandelbrot.js new file mode 100644 index 00000000..40b6dfa7 --- /dev/null +++ b/tests/mandelbrot.js @@ -0,0 +1,39 @@ +/* Mandelbrot display on a color terminal + (c) 2025 Fabrice Bellard + MIT license +*/ +function mandelbrot(center_x, center_y, scale, w, h, max_it) +{ + var x1, y1, y2, i, x, y, cx, cy, fx, fy, i, t, c, s, c0; + var colors = [ 14, 15, 7, 8, 0, 4, 12, 5, 13, 1, 9, 3, 11, 10, 2, 6]; + fx = scale * 0.5 / Math.min(w, h); + fy = fx * 2; + for(y1 = 0; y1 < h; y1++) { + s = ""; + for(x1 = 0; x1 < w; x1++) { + for(y2 = 0; y2 < 2; y2++) { + cx = (x1 - w * 0.5) * fx + center_x; + cy = (y1 + y2 * 0.5 - h * 0.5) * fy + center_y; + x = 0; + y = 0; + for(i = 0; i < max_it && x * x + y * y < 4; i++) { + t = x * x - y * y + cx; + y = 2 * x * y + cy; + x = t; + } + if (i >= max_it) { + c = 0; + } else { + c = colors[i % colors.length]; + } + if (y2 == 0) + c0 = c; + } + s += "\x1b[" + (c0 >= 8 ? 82 + c0 : 30 + c0) + ";" + (c >= 8 ? 92 + c : 40 + c) + "m\u2580"; + } + s += "\x1b[0m"; /* reset the colors */ + console.log(s); + } +} + +mandelbrot(-0.75, 0.0, 2.0, 80, 25, 50); diff --git a/tests/microbench.js b/tests/microbench.js new file mode 100644 index 00000000..c41e15cd --- /dev/null +++ b/tests/microbench.js @@ -0,0 +1,1137 @@ +/* + * Javascript Micro benchmark + * + * 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. + */ + +function pad(str, n) { + str += ""; + while (str.length < n) + str += " "; + return str; +} + +function pad_left(str, n) { + str += ""; + while (str.length < n) + str = " " + str; + return str; +} + +function pad_center(str, n) { + str += ""; + while (str.length < n) { + if ((n - str.length) & 1) + str = str + " "; + else + str = " " + str; + } + return str; +} + +var ref_data; +var log_data; + +var heads = [ "TEST", "N", "TIME (ns)", "REF (ns)", "SCORE (%)" ]; +var widths = [ 22, 10, 9, 9, 9 ]; +var precs = [ 0, 0, 2, 2, 2 ]; +var total = [ 0, 0, 0, 0, 0 ]; +var total_score = 0; +var total_scale = 0; + +if (typeof console == "undefined") { + var console = { log: print }; +} + +function log_line() { + var i, n, s, a; + s = ""; + for (i = 0, n = arguments.length; i < n; i++) { + if (i > 0) + s += " "; + a = arguments[i]; + if (typeof a == "number") { + total[i] += a; + a = a.toFixed(precs[i]); + a+=""; + s += pad_left(a, widths[i]); + } else { + s += pad_left(a, widths[i]); + } + } + console.log(s); +} + +var clocks_per_sec = 1000; +var max_iterations = 10; +var clock_threshold = 100; /* favoring short measuring spans */ +var min_n_argument = 1; +var get_clock; +if (typeof performance != "undefined") + get_clock = performance.now; +else + get_clock = Date.now; + +function log_one(text, n, ti) { + var ref; + + if (ref_data) + ref = ref_data[text]; + else + ref = null; + + // XXX + // ti = Math.round(ti * 100) / 100; + log_data[text] = ti; + if (typeof ref === "number") { + log_line(text, n, ti, ref, ti * 100 / ref); + total_score += ti * 100 / ref; + total_scale += 100; + } else { + log_line(text, n, ti); + total_score += 100; + total_scale += 100; + } +} + +function bench(f, text) +{ + var i, j, n, t, t1, ti, nb_its, ref, ti_n, ti_n1, min_ti; + + nb_its = n = 1; + if (f.bench) { + ti_n = f(text); + } else { + ti_n = 1000000000; + min_ti = clock_threshold / 10; + for(i = 0; i < 30; i++) { +// print("n=", n); + ti = 1000000000; + for (j = 0; j < max_iterations; j++) { + t = get_clock(); + while ((t1 = get_clock()) == t) + continue; + nb_its = f(n); + if (nb_its < 0) + return; // test failure + t1 = get_clock() - t1; + if (ti > t1) + ti = t1; + } + if (ti >= min_ti) { + ti_n1 = ti / nb_its; + if (ti_n > ti_n1) + ti_n = ti_n1; + } + if (ti >= clock_threshold && n >= min_n_argument) + break; + n = n * [ 2, 2.5, 2 ][i % 3]; + } + // to use only the best timing from the last loop, uncomment below + //ti_n = ti / nb_its; + } + /* nano seconds per iteration */ + log_one(text, n, ti_n * 1e9 / clocks_per_sec); +} + +var global_res; /* to be sure the code is not optimized */ + +function empty_loop(n) { + var j; + for(j = 0; j < n; j++) { + } + return n; +} + +function date_now(n) { + var j; + for(j = 0; j < n; j++) { + Date.now(); + } + return n; +} + +function prop_read(n) +{ + var obj, sum, j; + obj = {a: 1, b: 2, c:3, d:4 }; + sum = 0; + for(j = 0; j < n; j++) { + sum += obj.a; + sum += obj.b; + sum += obj.c; + sum += obj.d; + } + global_res = sum; + return n * 4; +} + +function prop_write(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a = j; + obj.b = j; + obj.c = j; + obj.d = j; + } + return n * 4; +} + +function prop_update(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a += j; + obj.b += j; + obj.c += j; + obj.d += j; + } + return n * 4; +} + +function prop_create(n) +{ + var obj, i, j; + for(j = 0; j < n; j++) { + obj = {}; + obj.a = 1; + obj.b = 2; + obj.c = 3; + obj.d = 4; + obj.e = 5; + obj.f = 6; + obj.g = 7; + obj.h = 8; + obj.i = 9; + obj.j = 10; + for(i = 0; i < 10; i++) { + obj[i] = i; + } + } + return n * 20; +} + +function prop_delete(n) +{ + var obj, j, i, len; + len = 1000; + obj = {}; + for(i = 0; i < n; i++) { + for(j = 0; j < len; j++) { + obj[j] = 1; + } + for(j = 0; j < len; j++) { + delete obj[j]; + } + } + return n * len; +} + +function array_read(n) +{ + var tab, len, sum, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function array_write(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function array_update(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] += j; + tab[1] += j; + tab[2] += j; + tab[3] += j; + tab[4] += j; + tab[5] += j; + tab[6] += j; + tab[7] += j; + tab[8] += j; + tab[9] += j; + } + return len * n; +} + +function array_prop_create(n) +{ + var tab, i, j, len; + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab[i] = i; + } + return len * n; +} + +function array_length_read(n) +{ + var tab, sum, j; + tab = [1, 2, 3]; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab.length; + sum += tab.length; + sum += tab.length; + sum += tab.length; + } + global_res = sum; + return n * 4; +} + +function array_length_decr(n) +{ + var tab, i, j, len; + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab[i] = i; + for(i = len - 1; i >= 0; i--) + tab.length = i; + } + return len * n; +} + +function array_hole_length_decr(n) +{ + var tab, i, j, len; + len = 1000; + tab = []; + for(i = 0; i < len; i++) { + if (i != 3) + tab[i] = i; + } + for(j = 0; j < n; j++) { + for(i = len - 1; i >= 0; i--) + tab.length = i; + } + return len * n; +} + +function array_push(n) +{ + var tab, i, j, len; + len = 500; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab.push(i); + } + return len * n; +} + +function array_pop(n) +{ + var tab, ref, i, j, len, sum; + len = 500; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + tab = ref.slice(); + sum = 0; + for(i = 0; i < len; i++) + sum += tab.pop(); + global_res = sum; + } + return len * n; +} + +function typed_array_read(n) +{ + var tab, len, sum, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function typed_array_write(n) +{ + var tab, len, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function closure_read(n) +{ + function f(n) { + var sum, j; + var0 = 0; + sum = 0; + for(j = 0; j < n; j++) { + sum += var0; + sum += var0; + sum += var0; + sum += var0; + } + global_res = sum; + } + var var0 = 0; + f(n); + return n * 4; +} + +function closure_write(n) +{ + function f(n) { + var j; + for(j = 0; j < n; j++) { + var0 = j; + var0 = j; + var0 = j; + var0 = j; + } + } + var var0; + + f(n); + return n * 4; +} + +var global_var0; + +function global_read(n) +{ + var sum, j; + global_var0 = 0; + sum = 0; + for(j = 0; j < n; j++) { + sum += global_var0; + sum += global_var0; + sum += global_var0; + sum += global_var0; + } + global_res = sum; + return n * 4; +} + +function global_write_strict(n) +{ + var j; + for(j = 0; j < n; j++) { + global_var0 = j; + global_var0 = j; + global_var0 = j; + global_var0 = j; + } + return n * 4; +} + +function func_call(n) +{ + function f(a) + { + return 1; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + sum += f(j); + sum += f(j); + sum += f(j); + sum += f(j); + } + global_res = sum; + return n * 4; +} + +function closure_var(n) +{ + function f(a) + { + sum++; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + f(j); + f(j); + f(j); + f(j); + } + global_res = sum; + return n * 4; +} + +function int_arith(n) +{ + var i, j, sum; + global_res = 0; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < 1000; i++) { + sum += i * i; + } + global_res += sum; + } + return n * 1000; +} + +function float_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = 0.1; + incr = 1.1; + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigfloat_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = BigFloat("0.1"); + incr = BigFloat("1.1"); + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint_arith(n, bits) +{ + var i, j, sum, a, incr, a0, sum0; + sum0 = global_res = BigInt(0); + a0 = BigInt(1) << BigInt(Math.floor((bits - 10) * 0.5)); + incr = BigInt(1); + for(j = 0; j < n; j++) { + sum = sum0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint64_arith(n) +{ + return bigint_arith(n, 64); +} + +function bigint256_arith(n) +{ + return bigint_arith(n, 256); +} + +function set_collection_add(n) +{ + var s, i, j, len = 100; + s = new Set(); + for(j = 0; j < n; j++) { + for(i = 0; i < len; i++) { + s.add(String(i), i); + } + for(i = 0; i < len; i++) { + if (!s.has(String(i))) + throw Error("bug in Set"); + } + } + return n * len; +} + +function array_for(n) +{ + var r, i, j, sum; + r = []; + for(i = 0; i < 100; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < 100; i++) { + sum += r[i]; + } + global_res = sum; + } + return n * 100; +} + +function array_for_in(n) +{ + var r, i, j, sum; + r = []; + for(i = 0; i < 100; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i in r) { + sum += r[i]; + } + global_res = sum; + } + return n * 100; +} + +function array_for_of(n) +{ + var r, i, j, sum; + r = []; + for(i = 0; i < 100; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i of r) { + sum += i; + } + global_res = sum; + } + return n * 100; +} + +function math_min(n) +{ + var i, j, r; + r = 0; + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = Math.min(i, 500); + global_res = r; + } + return n * 1000; +} + +function regexp_ascii(n) +{ + var i, j, r, s; + s = "the quick brown fox jumped over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown fox/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_utf16(n) +{ + var i, j, r, s; + s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown ᶠᵒˣ/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_replace(n) +{ + var i, j, r, s; + s = "the quick abc brown fox jumped abc over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = s.replace(/abc /g, "-"); + global_res = r; + } + return n * 1000; +} + +function string_length(n) +{ + var str, sum, j; + str = "abcde"; + sum = 0; + for(j = 0; j < n; j++) { + sum += str.length; + sum += str.length; + sum += str.length; + sum += str.length; + } + global_res = sum; + return n * 4; +} + +/* incremental string construction as local var */ +function string_build1(n) +{ + var i, j, r; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) + r += "x"; + global_res = r; + } + return n * 100; +} + +/* incremental string construction as arg */ +function string_build2(n, r) +{ + var i, j; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) + r += "x"; + global_res = r; + } + return n * 100; +} + +/* incremental string construction by prepending */ +function string_build3(n, r) +{ + var i, j; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) + r = "x" + r; + global_res = r; + } + return n * 100; +} + +/* incremental string construction with multiple reference */ +function string_build4(n) +{ + var i, j, r, s; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) { + s = r; + r += "x"; + } + global_res = r; + } + return n * 100; +} + +/* sort bench */ + +function sort_bench(text) { + function random(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * n) >> 0]; + } + function random8(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 256) >> 0]; + } + function random1(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 2) >> 0]; + } + function hill(arr, n, def) { + var mid = n >> 1; + for (var i = 0; i < mid; i++) + arr[i] = def[i]; + for (var i = mid; i < n; i++) + arr[i] = def[n - i]; + } + function comb(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) * i]; + } + function crisscross(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) ? n - i : i]; + } + function zero(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[0]; + } + function increasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + } + function decreasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[n - 1 - i]; + } + function alternate(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i ^ 1]; + } + function jigsaw(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i % (n >> 4)]; + } + function incbutone(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + if (n > 0) + arr[n >> 2] = def[n]; + } + function incbutfirst(arr, n, def) { + if (n > 0) + arr[0] = def[n]; + for (var i = 1; i < n; i++) + arr[i] = def[i]; + } + function incbutlast(arr, n, def) { + for (var i = 0; i < n - 1; i++) + arr[i] = def[i + 1]; + if (n > 0) + arr[n - 1] = def[0]; + } + + var sort_cases = [ random, random8, random1, jigsaw, hill, comb, + crisscross, zero, increasing, decreasing, alternate, + incbutone, incbutlast, incbutfirst ]; + + var n = sort_bench.array_size || 10000; + var array_type = sort_bench.array_type || Array; + var def, arr; + var i, j, x, y; + var total = 0; + + var save_total_score = total_score; + var save_total_scale = total_scale; + + // initialize default sorted array (n + 1 elements) + def = new array_type(n + 1); + if (array_type == Array) { + for (i = 0; i <= n; i++) { + def[i] = i + ""; + } + } else { + for (i = 0; i <= n; i++) { + def[i] = i; + } + } + def.sort(); + for (var f of sort_cases) { + var ti = 0, tx = 0; + for (j = 0; j < 100; j++) { + arr = new array_type(n); + f(arr, n, def); + var t1 = get_clock(); + arr.sort(); + t1 = get_clock() - t1; + tx += t1; + if (!ti || ti > t1) + ti = t1; + if (tx >= clocks_per_sec) + break; + } + total += ti; + + i = 0; + x = arr[0]; + if (x !== void 0) { + for (i = 1; i < n; i++) { + y = arr[i]; + if (y === void 0) + break; + if (x > y) + break; + x = y; + } + } + while (i < n && arr[i] === void 0) + i++; + if (i < n) { + console.log("sort_bench: out of order error for " + f.name + + " at offset " + (i - 1) + + ": " + arr[i - 1] + " > " + arr[i]); + } + if (sort_bench.verbose) + log_one("sort_" + f.name, n, ti, n * 100); + } + total_score = save_total_score; + total_scale = save_total_scale; + return total / n / 1000; +} +sort_bench.bench = true; +sort_bench.verbose = false; + +function int_to_string(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j + 1).toString(); + } + return n; +} + +function float_to_string(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j + 0.1).toString(); + } + return n; +} + +function string_to_int(n) +{ + var s, r, j; + r = 0; + s = "12345"; + r = 0; + for(j = 0; j < n; j++) { + r += (s | 0); + } + global_res = r; + return n; +} + +function string_to_float(n) +{ + var s, r, j; + r = 0; + s = "12345.6"; + r = 0; + for(j = 0; j < n; j++) { + r -= s; + } + global_res = r; + return n; +} + +function load_result(filename) +{ + var f, str, res; + if (typeof std === "undefined") + return null; + f = std.open(filename, "r"); + if (!f) + return null; + str = f.readAsString(); + res = JSON.parse(str); + f.close(); + return res; +} + +function save_result(filename, obj) +{ + var f; + if (typeof std === "undefined") + return; + f = std.open(filename, "w"); + f.puts(JSON.stringify(obj, null, 2)); + f.puts("\n"); + f.close(); +} + +function main(argc, argv, g) +{ + var test_list = [ + empty_loop, + date_now, + prop_read, + prop_write, + prop_update, + prop_create, + prop_delete, + array_read, + array_write, + array_update, + array_prop_create, + array_length_read, + array_length_decr, +// array_hole_length_decr, + array_push, + array_pop, + typed_array_read, + typed_array_write, + closure_read, + closure_write, + global_read, + global_write_strict, + func_call, + closure_var, + int_arith, + float_arith, +// set_collection_add, + array_for, + array_for_in, + array_for_of, + math_min, + regexp_ascii, + regexp_utf16, + regexp_replace, + string_length, + string_build1, + string_build2, + //string_build3, + //string_build4, + sort_bench, + int_to_string, + float_to_string, + string_to_int, + string_to_float, + ]; + var tests = []; + var i, j, n, f, name, found; + + if (typeof BigInt == "function") { + /* BigInt test */ + test_list.push(bigint64_arith); + test_list.push(bigint256_arith); + } + + for (i = 1; i < argc;) { + name = argv[i++]; + if (name == "-a") { + sort_bench.verbose = true; + continue; + } + if (name == "-t") { + name = argv[i++]; + sort_bench.array_type = g[name]; + if (typeof sort_bench.array_type != "function") { + console.log("unknown array type: " + name); + return 1; + } + continue; + } + if (name == "-n") { + sort_bench.array_size = +argv[i++]; + continue; + } + for (j = 0, found = false; j < test_list.length; j++) { + f = test_list[j]; + if (f.name.slice(0, name.length) === name) { + tests.push(f); + found = true; + } + } + if (!found) { + console.log("unknown benchmark: " + name); + return 1; + } + } + if (tests.length == 0) + tests = test_list; + + ref_data = load_result("microbench.txt"); + log_data = {}; + log_line.apply(null, heads); + n = 0; + + for(i = 0; i < tests.length; i++) { + f = tests[i]; + bench(f, f.name, ref_data, log_data); + if (ref_data && ref_data[f.name]) + n++; + } + if (ref_data) + log_line("total", "", total[2], total[3], total_score * 100 / total_scale); + else + log_line("total", "", total[2]); + + if (tests == test_list) + save_result("microbench-new.txt", log_data); +} + +if (!scriptArgs) + scriptArgs = []; +main(scriptArgs.length, scriptArgs, this); diff --git a/tests/test_builtin.js b/tests/test_builtin.js new file mode 100644 index 00000000..9032d18e --- /dev/null +++ b/tests/test_builtin.js @@ -0,0 +1,875 @@ +"use strict"; + +function throw_error(msg) { + throw Error(msg); +} + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw_error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("unexpected exception type"); + return; + } + } + if (!err) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("expected exception"); + } +} + +function my_func(a, b) +{ + return a + b; +} + +function test_function() +{ + function f(a, b) { + var i, tab = []; + tab.push(this); + for(i = 0; i < arguments.length; i++) + tab.push(arguments[i]); + return tab; + } + function constructor1(a) { + this.x = a; + } + + var r, g; + + r = my_func.call(null, 1, 2); + assert(r, 3, "call"); + + r = my_func.apply(null, ["abc", 2]); + assert(r, "abc2", "apply"); + + r = new Function("a", "b", "return a + b;"); + assert(r(2,3), 5, "function"); + + g = f.bind(1, 2); +// assert(g.length, 1); +// assert(g.name, "bound f"); + assert(g(3).toString(), "1,2,3"); + + if (0) { + g = constructor1.bind(null, 1); + r = new g(); + assert(r.x, 1); + } +} + +function test() +{ + var r, a, b, c, err; + + r = Error("hello"); + assert(r.message, "hello", "Error"); + + a = new Object(); + a.x = 1; + assert(a.x, 1, "Object"); + + assert(Object.prototype.constructor, Object, "constructor"); + assert(Object.getPrototypeOf(a), Object.prototype, "getPrototypeOf"); + Object.defineProperty(a, "y", { value: 3, writable: true, configurable: true, enumerable: true }); + assert(a.y, 3, "defineProperty"); + + Object.defineProperty(a, "z", { get: function () { return 4; }, set: function(val) { this.z_val = val; }, configurable: true, enumerable: true }); + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + Object.defineProperty(a, "w", {}); + assert("w" in a, true); + a.w = 1; + + Object.defineProperty(a, "w", {}); + assert(a.w, 1); + + a = { get z() { return 4; }, set z(val) { this.z_val = val; } }; + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + a = {}; + b = Object.create(a); + assert(Object.getPrototypeOf(b), a, "create"); + c = {u:2}; + Object.setPrototypeOf(a, c); + assert(Object.getPrototypeOf(a), c, "setPrototypeOf"); + + a={}; + assert(a.toString(), "[object Object]", "toString"); + assert(Object.prototype.toString.call(1), "[object Number]", "toString"); +/* + a={x:1}; + assert(Object.isExtensible(a), true, "extensible"); + Object.preventExtensions(a); + + err = false; + try { + a.y = 2; + } catch(e) { + err = true; + } + assert(Object.isExtensible(a), false, "extensible"); + assert(typeof a.y, "undefined", "extensible"); + assert(err); +*/ + + a = {x: 1}; + assert(a.hasOwnProperty("x"), true); + assert(a.hasOwnProperty("y"), false); + a = [1, 2]; + assert(a.hasOwnProperty(1), true); + assert(a.hasOwnProperty(2), false); +} + +function test_enum() +{ + var a, tab; + a = {x:1, y:1, z:3}; + tab = Object.keys(a); + assert(tab.toString(), "x,y,z", "keys"); +} + +function test_array() +{ + var a, err, i, log; + + a = [1, 2, 3]; + assert(a.length, 3, "array"); + assert(a[2], 3, "array1"); + + a = new Array(10); + assert(a.length, 10, "array2"); + + a = new Array(1, 2); + assert(a[0] === 1 && a[1] === 2); + + a = [1, 2, 3]; + a.length = 2; + assert(a[0] === 1 && a[1] === 2 && a.length === 2); + + a = []; + a[0] = 10; + a[1] = 3; + assert(a.length, 2); + +/* + a = []; + a[1] = 10; + a[4] = 3; + assert(a.length, 5); +*/ + + a = [1,2]; + a.length = 5; + a[4] = 1; + a.length = 4; + assert(a[4] !== 1); + + a = [1,2,3]; + assert(a.join("-"), "1-2-3"); + + a = [1,2]; + assert(a.push(3, 4), 4); + assert(a.toString(), "1,2,3,4"); + + a = [1,2,3]; + assert(a.pop(), 3); + assert(a.toString(), "1,2"); + + /* + a=[1,2,3,4,5]; + Object.defineProperty(a, "3", { configurable: false }); + err = false; + try { + a.length = 2; + } catch(e) { + err = true; + } + assert(err && a.toString() === "1,2,3,4"); + */ + assert(Array.isArray([]), true); + assert(Array.isArray({}), false); + + a = [1, 2, 3]; + assert(a.reverse().toString(), "3,2,1"); + + a = [1, 2, 3]; + a = a.concat(4, [5, 6], 7); + assert(a.toString(), "1,2,3,4,5,6,7"); + + a = [1, 2, 3]; + assert(a.shift(), 1); + assert(a.toString(), "2,3"); + + a = [3,4]; + assert(a.unshift(1,2), 4); + assert(a.toString(), "1,2,3,4"); + + a = [10, 11, 10, 11] + assert(a.indexOf(11), 1); + assert(a.indexOf(9), -1); + assert(a.indexOf(11, 2), 3); + assert(a.lastIndexOf(11), 3); + assert(a.lastIndexOf(11, 2), 1); + + assert([1, 2, 3, 4].slice(1, 3).toString(), "2,3"); + assert([1, 2, 3, 4].slice(1).toString(), "2,3,4"); + + log=""; + assert([1, 2, 3, 4].every(function(val, k) { log += val; assert(k, (val - 1)); return val != 5 }), true); + assert(log, "1234"); + + log = ""; + assert([1, 2, 3, 4].some(function(val, k) { log += val; assert(k, (val - 1)); return val == 5 }), false); + assert(log, "1234"); + + log = ""; + assert([1, 2, 3, 4].forEach(function(val, k) { log += val; assert(k, (val - 1)); }), void 0); + assert(log, "1234"); + + log = ""; + a = [1, 2, 3, 4].map(function(val, k) { assert(k, (val - 1)); return val + 1; }); + assert(a.toString(), "2,3,4,5"); + + log = ""; + a = [1, 2, 3, 4].filter(function(val, k) { assert(k, (val - 1)); return val == 2 || val == 3; }); + assert(a.toString(), "2,3"); + + assert(["1", 2, 3, 4].reduce(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }), "1234"); + assert([1, 2, 3, 4].reduce(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }, "0"), "01234"); + + assert([1, 2, 3, "4"].reduceRight(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }), "4321"); + assert([1, 2, 3, 4].reduceRight(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }, "5"), "54321"); + + a = [1, 2, 3, 4]; + assert(a.splice(1, 2, 10, 11, 12).toString(), "2,3"); + assert(a.toString(), "1,10,11,12,4"); + + a = [1, 2, 3, 4]; + assert(a.splice(1, 2, 10).toString(), "2,3"); + assert(a.toString(), "1,10,4"); + + a = [5, 4, 3, 2, 1]; + a.sort(); + assert(a[0], 1); + assert(a.toString(), "1,2,3,4,5"); + + a = [1, 2, 3, 4, 5]; + a.sort(function(a, b) { return (a < b) - (a > b) } ); + assert(a.toString(), "5,4,3,2,1"); + + /* verify that the sort is stable and that 'undefined' is correctly handled */ + a = [ "b0", "z0", undefined, "b1", "a0", undefined, "z1", "a1", "a2"]; + a.sort(function(a, b) { return (a[0] > b[0]) - (a[0] < b[0]) } ); + assert(a.toString(), "a0,a1,a2,b0,b1,z0,z1,,"); +} + +/* non standard array behaviors */ +function test_array_ext() +{ + var a; + a = [1, 2, 3]; + assert_throws(TypeError, function () { a[1.2] = 1; } ); + assert_throws(TypeError, function () { a[NaN] = 1; } ); + assert_throws(TypeError, function () { a.NaN = 1; } ); + assert_throws(TypeError, function () { a[Infinity] = 1; } ); + assert_throws(TypeError, function () { a.Infinity = 1; } ); + assert_throws(TypeError, function () { a[-Infinity] = 1; } ); + assert_throws(TypeError, function () { a["1.2"] = 1; } ); + assert_throws(TypeError, function () { a["NaN"] = 1; } ); + assert_throws(TypeError, function () { a["Infinity"] = 1; } ); + assert_throws(TypeError, function () { a["-Infinity"] = 1; } ); +} + +function test_string() +{ + var a; + a = String("abc"); + assert(a.length, 3, "string"); + assert(a[1], "b", "string"); + assert(a.charCodeAt(1), 0x62, "string"); + assert(String.fromCharCode(65), "A", "string"); + assert(String.fromCharCode(65, 66, 67), "ABC", "string"); + assert(a.charAt(1), "b"); + assert(a.charAt(-1), ""); + assert(a.charAt(3), ""); + + a = "abcd"; + assert(a.substring(1, 3), "bc", "substring"); + a = String.fromCharCode(0x20ac); + assert(a.charCodeAt(0), 0x20ac, "unicode"); + assert(a, "€", "unicode"); + assert(a, "\u20ac", "unicode"); + assert(a, "\u{20ac}", "unicode"); + assert("a", "\x61", "unicode"); + + a = "\u{10ffff}"; + assert(a.length, 2, "unicode"); + assert(a, "\u{dbff}\u{dfff}", "unicode"); + assert(a.codePointAt(0), 0x10ffff); + assert(a.codePointAt(1), 0xdfff); + assert(String.fromCodePoint(0x10ffff), a); + + assert("a".concat("b", "c", 123), "abc123"); + + assert("abcabc".indexOf("cab"), 2); + assert("abcabc".indexOf("cab2"), -1); + assert("abc".indexOf("c"), 2); + assert("abcabc".lastIndexOf("ab"), 3); + + assert("a,b,c".split(","), ["a","b","c"]); + assert(",b,c".split(","), ["","b","c"]); + assert("a,b,".split(","), ["a","b",""]); + + assert("aaaa".split(), [ "aaaa" ]); + assert("aaaa".split(undefined, 0), [ ]); + assert("aaaa".split(""), [ "a", "a", "a", "a" ]); + assert("aaaa".split("", 0), [ ]); + assert("aaaa".split("", 1), [ "a" ]); + assert("aaaa".split("", 2), [ "a", "a" ]); + assert("aaaa".split("a"), [ "", "", "", "", "" ]); + assert("aaaa".split("a", 2), [ "", "" ]); + assert("aaaa".split("aa"), [ "", "", "" ]); + assert("aaaa".split("aa", 0), [ ]); + assert("aaaa".split("aa", 1), [ "" ]); + assert("aaaa".split("aa", 2), [ "", "" ]); + assert("aaaa".split("aaa"), [ "", "a" ]); + assert("aaaa".split("aaaa"), [ "", "" ]); + assert("aaaa".split("aaaaa"), [ "aaaa" ]); + assert("aaaa".split("aaaaa", 0), [ ]); + assert("aaaa".split("aaaaa", 1), [ "aaaa" ]); + + // assert((1,eval)('"\0"'), "\0"); + assert("123AbCd€".toLowerCase(), "123abcd€"); + assert("123AbCd€".toUpperCase(), "123ABCD€"); + assert(" ab€cd ".trim(), "ab€cd"); + assert(" ab€cd ".trimStart(), "ab€cd "); + assert(" ab€cd ".trimEnd(), " ab€cd"); + assert("abcabc".replace("b", "a$$b$&"), "aa$bbcabc"); + assert("abcabc".replaceAll("b", "a$$b$&"),"aa$bbcaa$bbc"); + + a = ""; + assert("bab".replace("a", function(a0, a1, a2) { a += a0 + "," + a1 + "," + a2; return "hi"; }), "bhib"); + assert(a, "a,1,bab"); + + assert("abc".repeat(3), "abcabcabc"); + assert("abc".repeat(0), ""); + assert("".repeat(1000000000), ""); +} + +/* specific tests for internal UTF-8 storage */ +function test_string2() +{ + var str = "hé€\u{101234}o"; + assert(str, "h\xe9\u20ac\udbc4\u{de34}o", "parse"); + assert(str.length, 6, "length"); + assert(str.slice(1, 2), "é", "slice"); + assert(str.slice(1, 3), "é€", "slice"); + assert(str.slice(2, 5), "€\u{101234}", "slice"); + assert(str.slice(2, 4), "€\u{dbc4}", "slice"); + assert(str.slice(4, 6), "\u{de34}o", "slice"); + assert("hé€" + "\u{101234}o", str, "concat 1"); + assert("h\xe9\u20ac\udbc4" + "\u{de34}o", str, "concat 2"); + + var ch = "\udbc4\u{de34}"; + assert(ch.slice(0, 2), "\udbc4\u{de34}", "slice 1"); + assert(ch.slice(0, 1), "\udbc4", "slice 1"); + assert(ch.slice(1, 2), "\u{de34}", "slice 1"); + + assert("\udbc4" + "\u{de34}", "\u{101234}", "concat 3"); + assert("\udbc4" + "o\u{de34}", "\udbc4o\u{de34}", "concat 4"); + + assert(str[0], "h", "char 1"); + assert(str[1], "é", "char 2"); + assert(str[3], "\u{dbc4}", "char 3"); + assert(str[4], "\u{de34}", "char 4"); + assert(str.charCodeAt(3), 0xdbc4, "char 4"); + assert("€"[0], "€", "char 5"); + assert("\u{101234}"[0], "\u{dbc4}", "char 6"); + assert("\u{101234}"[1], "\u{de34}", "char 6"); + + assert("\udbc4" <= "\udbc4", true); + assert("\udbc3" < "\u{101234}", true); + assert("\udbc4" < "\u{101234}", true); + assert("\udbc5" > "\u{101234}", true); + + assert("\u{101234}" > "\udbc3", true); + assert("\u{101234}" > "\udbc4", true); + assert("\u{101234}" < "\udbc5", true); + + assert("\u{101233}" < "\u{101234}", true); +} + +function test_math() +{ + var a; + a = 1.4; + assert(Math.floor(a), 1); + assert(Math.ceil(a), 2); + assert(Math.imul(0x12345678, 123), -1088058456); + assert(Math.fround(0.1), 0.10000000149011612); +} + +function test_number() +{ + assert(+" 123 ", 123); + assert(+"0b111", 7); + assert(+"0o123", 83); + + assert(parseInt("123"), 123); + assert(parseInt(" 123r"), 123); + assert(parseInt("0x123"), 0x123); + assert(parseInt("0o123"), 0); + assert(parseFloat("0x1234"), 0); + assert(parseFloat("Infinity"), Infinity); + assert(parseFloat("-Infinity"), -Infinity); + assert(parseFloat("123.2"), 123.2); + assert(parseFloat("123.2e3"), 123200); + + assert((25).toExponential(), "2.5e+1"); + assert((25).toExponential(0), "3e+1"); + assert((-25).toExponential(0), "-3e+1"); + assert((2.5).toPrecision(1), "3"); + assert((-2.5).toPrecision(1), "-3"); + assert((25).toPrecision(1), "3e+1"); + assert((1.125).toFixed(2), "1.13"); + assert((-1.125).toFixed(2), "-1.13"); + assert((-1e-10).toFixed(0), "-0"); +} + +function test_global_eval() +{ + var r, g_eval = (1,eval); + + r = g_eval("1+1;"); + assert(r, 2, "eval"); + + /* z is created as a global variable */ + r = g_eval("var z=2; z;"); + assert(r, 2, "eval"); + assert(z, 2); + + assert(g_eval("if (1) 2; else 3;"), 2); + assert(g_eval("if (0) 2; else 3;"), 3); + + z = 2; + assert(g_eval("z"), 2); + + g_eval("z = 3"); + assert(z, 3); +} + +function test_typed_array() +{ + var buffer, a, i; + + a = new Uint8Array(4); + assert(a.length, 4); + for(i = 0; i < a.length; i++) + a[i] = i; + assert(a.toString(), "0,1,2,3"); + a[0] = -1; + assert(a[0], 255); + + a = new Int8Array(3); + a[0] = 255; + assert(a[0], -1); + + a = new Int32Array(3); + a[0] = Math.pow(2, 32) - 1; + assert(a[0], -1); + assert(a.BYTES_PER_ELEMENT, 4); + + a = new Uint8ClampedArray(4); + a[0] = -100; + a[1] = 1.5; + a[2] = 0.5; + a[3] = 1233.5; + assert(a.toString(), "0,2,0,255"); + + buffer = new ArrayBuffer(16); + assert(buffer.byteLength, 16); + a = new Uint32Array(buffer, 12, 1); + assert(a.length, 1); + a[0] = -1; + + a = new Uint16Array(buffer, 2); + a[0] = -1; + + a = new Float32Array(buffer, 8, 1); + a[0] = 1; + + a = new Uint8Array(buffer); + + assert(a.toString(), "0,0,255,255,0,0,0,0,0,0,128,63,255,255,255,255"); + + assert(a.buffer, buffer); + + a = new Int32Array([1, 2, 3, 4]); + assert(a.toString(), "1,2,3,4"); + a.set([10, 11], 2); + assert(a.toString(), "1,2,10,11"); + a.set([12, 13]); + assert(a.toString(), "12,13,10,11"); + a.set(new Int32Array([0, 1]), 1); + assert(a.toString(), "12,0,1,11"); + + a = new Uint8Array([1, 2, 3, 4]); + a = a.subarray(1, 3); + assert(a.toString(), "2,3"); +} + +function repeat(a, n) +{ + return a.repeat(n); +} + +/* return [s, line_num, col_num] where line_num and col_num are the + position of the '@' character in 'str'. 's' is str without the '@' + character */ +function get_string_pos(str) +{ + var p, line_num, col_num, s, q, r; + p = str.indexOf('@'); + assert(p >= 0, true); + q = 0; + line_num = 1; + for(;;) { + r = str.indexOf('\n', q); + if (r < 0 || r >= p) + break; + q = r + 1; + line_num++; + } + col_num = p - q + 1; + s = str.slice(0, p) + str.slice(p + 1); + return [s, line_num, col_num]; +} + +function check_error_pos(e, expected_error, line_num, col_num, level) +{ + var expected_pos, tab, line; + level |= 0; + expected_pos = ":" + line_num + ":" + col_num; + tab = e.stack.split("\n"); + line = tab[level]; + if (line.slice(-1) == ')') + line = line.slice(0, -1); + if (line.indexOf(expected_pos) < 0) { + throw_error("unexpected line or column number. error=|" + e.message + + "| got |" + line + "|, expected |" + expected_pos + "|"); + } +} + +function assert_json_error(str, line_num, col_num) +{ + var err = false; + var expected_pos, tab; + + tab = get_string_pos(str); + + try { + JSON.parse(tab[0]); + } catch(e) { + err = true; + if (!(e instanceof SyntaxError)) { + throw_error("unexpected exception type"); + return; + } + /* XXX: the way quickjs returns JSON errors is not similar to Node or spiderMonkey */ + check_error_pos(e, SyntaxError, tab[1], tab[2]); + } + if (!err) { + throw_error("expected exception"); + } +} + +function test_json() +{ + var a, s, n; + + s = '{"1234":"str","x":1,"y":true,"z":null,"a":[1,2,false]}'; + a = JSON.parse(s); + assert(a.x, 1); + assert(a.y, true); + assert(a.z, null); + assert(a[1234], "str"); + assert(JSON.stringify(a), s); + + assert(JSON.stringify({x: 1, y: undefined, z:2}), '{"x":1,"z":2}'); + + /* larger stack */ + n = 100; + s = repeat("[", n) + repeat("]", n); + a = JSON.parse(s); + assert(JSON.stringify(a), s); + +// assert_json_error('\n" \\@x"'); +// assert_json_error('\n{ "a": @x }"'); +} + +function test_large_eval_parse_stack() +{ + var n = 1000; + var str; + + str = repeat("(", n) + "1" + repeat(")", n); + assert((1,eval)(str), 1); + + str = repeat("{", n) + "1;" + repeat("}", n); + assert((1,eval)(str), 1); + + str = repeat("[", n) + "1" + repeat("]", n) + repeat("[0]", n); + assert((1,eval)(str), 1); +} + +function test_regexp() +{ + var a, str, n; + + str = "abbbbbc"; + a = /(b+)c/.exec(str); + assert(a[0], "bbbbbc"); + assert(a[1], "bbbbb"); + assert(a.index, 1); + assert(a.input, str); + a = /(b+)c/.test(str); + assert(a, true); + assert(/\x61/.exec("a")[0], "a"); + assert(/\u0061/.exec("a")[0], "a"); + assert(/\ca/.exec("\x01")[0], "\x01"); + assert(/\\a/.exec("\\a")[0], "\\a"); + assert(/\c0/.exec("\\c0")[0], "\\c0"); + + a = /(\.(?=com|org)|\/)/.exec("ah.com"); + assert(a.index === 2 && a[0] === "."); + + a = /(\.(?!com|org)|\/)/.exec("ah.com"); + assert(a, null); + + a = /(?=(a+))/.exec("baaabac"); + assert(a.index === 1 && a[0] === "" && a[1] === "aaa"); + + a = /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac"); + assert(a, ["zaacbbbcac","z","ac","a", undefined,"c"]); + +// a = (1,eval)("/\0a/"); +// assert(a.toString(), "/\0a/"); +// assert(a.exec("\0a")[0], "\0a"); + +// assert(/{1a}/.toString(), "/{1a}/"); +// a = /a{1+/.exec("a{11"); +// assert(a, ["a{11"]); + + /* test zero length matches */ + a = /(?:(?=(abc)))a/.exec("abc"); + assert(a, ["a", "abc"]); + a = /(?:(?=(abc)))?a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:(?=(abc))){0,2}a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:|[\w])+([0-9])/.exec("123a23"); + assert(a, ["123a23", "3"]); + a = /()*?a/.exec(","); + assert(a, null); + + /* test \b escape */ + assert(/[\q{a\b}]/.test("a\b"), true); + assert(/[\b]/.test("\b"), true); + + /* test case insensitive matching (test262 hardly tests it) */ + assert("aAbBcC".replace(/[^b]/gui, "X"), "XXbBXX"); + assert("aAbBcC".replace(/[^A-B]/gui, "X"), "aAbBXX"); + + /* case where lastIndex points to the second element of a + surrogate pair */ + a = /(?:)/gu; + a.lastIndex = 1; + a.exec("🐱"); + assert(a.lastIndex, 0); + + /* test backreferences */ + assert(/(abc)\1/.exec("abcabc"), ["abcabc", "abc"]); + assert(/(abc)\1/i.exec("aBcaBC"), ["aBcaBC", "aBc"]); + + /* large parse stack */ + n = 10000; + a = new RegExp(repeat("(?:", n) + "a+" + repeat(")", n)); + assert(a.exec("aa"), ["aa"]); + + /* additional functions */ + + a = "abbbc".match(/b+/); + assert(a, [ "bbb" ]); + assert("abcaaad".match(/a+/g), [ "a", "aaa" ]); + + assert("abc".search(/b/), 1); + assert("abc".search(/d/), -1); + + assert("abbbbcbbd".replace(/b+/, "€$&"), "a€bbbbcbbd"); + assert("abbbbcbbd".replace(/b+/g, "€$&"), "a€bbbbc€bbd"); + assert("abbbbccccd".replace(/(b+)(c+)/g, "_$1_$2_"), "a_bbbb_cccc_d"); + assert("abbbbcd".replace(/b+/g, "_$`_$&_$'_"), "a_a_bbbb_cd_cd"); + + a = ""; + assert("babbc".replace(/a(b+)/, function() { var i; for(i=0;iboldandcoded".split(/<(\/)?([^<>]+)>/), ["A", undefined, "B", "bold", "/", "B", "and", undefined, "CODE", "coded", "/", "CODE", ""]); +} + +function eval_error(eval_str, expected_error, level) +{ + var err = false; + var expected_pos, tab; + + tab = get_string_pos(eval_str); + + try { + (1, eval)(tab[0]); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + throw_error("unexpected exception type"); + return; + } + check_error_pos(e, expected_error, tab[1], tab[2], level); + } + if (!err) { + throw_error("expected exception"); + } +} + +var poisoned_number = { + valueOf: function() { throw Error("poisoned number") }, +}; + +function test_line_column_numbers() +{ + var f, e, tab; + + /* The '@' character provides the expected position of the + error. It is removed before evaluating the string. */ + + /* parsing */ + eval_error("\n 123 @a ", SyntaxError); + eval_error("\n @/* ", SyntaxError); + eval_error("function f @a", SyntaxError); + /* currently regexp syntax errors point to the start of the regexp */ + eval_error("\n @/aaa]/u", SyntaxError); + + /* function definitions */ +/* + tab = get_string_pos("\n @function f() { }; f;"); + e = (1, eval)(tab[0]); + assert(e.lineNumber, tab[1]); + assert(e.columnNumber, tab[2]); +*/ + /* errors */ + tab = get_string_pos('\n Error@("hello");'); + e = (1, eval)(tab[0]); + check_error_pos(e, Error, tab[1], tab[2]); + + eval_error('\n throw Error@("hello");', Error); + + /* operators */ + eval_error('\n 1 + 2 @* poisoned_number;', Error, 1); + eval_error('\n 1 + "café" @* poisoned_number;', Error, 1); + eval_error('\n 1 + 2 @** poisoned_number;', Error, 1); + eval_error('\n 2 * @+ poisoned_number;', Error, 1); + eval_error('\n 2 * @- poisoned_number;', Error, 1); + eval_error('\n 2 * @~ poisoned_number;', Error, 1); + eval_error('\n 2 * @++ poisoned_number;', Error, 1); + eval_error('\n 2 * @-- poisoned_number;', Error, 1); + eval_error('\n 2 * poisoned_number @++;', Error, 1); + eval_error('\n 2 * poisoned_number @--;', Error, 1); + + /* accessors */ + eval_error('\n 1 + null@[0];', TypeError); + eval_error('\n 1 + null @. abcd;', TypeError); + // eval_error('\n 1 + null @( 1234 );', TypeError); + eval_error('var obj = { get a() { throw Error("test"); } }\n 1 + obj @. a;', + Error, 1); + eval_error('var obj = { set a(b) { throw Error("test"); } }\n obj @. a = 1;', + Error, 1); + + /* variables reference */ + eval_error('\n 1 + @not_def', ReferenceError, 0); + + /* assignments */ + eval_error('1 + (@not_def = 1)', ReferenceError, 0); + eval_error('1 + (@not_def += 2)', ReferenceError, 0); + eval_error('var a;\n 1 + (a @+= poisoned_number);', Error, 1); +} + +test(); +test_string(); +test_string2(); +test_array(); +test_array_ext(); +test_enum(); +test_function(); +test_number(); +test_math(); +test_typed_array(); +test_global_eval(); +test_json(); +test_regexp(); +test_line_column_numbers(); +test_large_eval_parse_stack(); diff --git a/tests/test_closure.js b/tests/test_closure.js new file mode 100644 index 00000000..a62e31a7 --- /dev/null +++ b/tests/test_closure.js @@ -0,0 +1,106 @@ +function assert(b, str) +{ + if (b) { + return; + } else { + throw "assertion failed: " + str; + } +} + +var log_str = ""; + +function log(str) +{ + log_str += str + ","; +} + +function f(a, b, c) +{ + var x = 10; + log("a="+a); + function g(d) { + function h() { + log("d=" + d); + log("x=" + x); + } + log("b=" + b); + log("c=" + c); + h(); + } + g(4); + return g; +} + +var g1 = f(1, 2, 3); +g1(5); + +assert(log_str === "a=1,b=2,c=3,d=4,x=10,b=2,c=3,d=5,x=10,", "closure1"); + +function test_closure1() +{ + function f2() + { + var val = 1; + + function set(a) { + val = a; + } + function get(a) { + return val; + } + return { "set": set, "get": get }; + } + + var obj = f2(); + obj.set(10); + var r; + r = obj.get(); + assert(r === 10, "closure2"); +} + +function test_closure2() +{ + var expr_func = function myfunc1(n) { + function myfunc2(n) { + return myfunc1(n - 1); + } + if (n == 0) + return 0; + else + return myfunc2(n); + }; + var r; + r = expr_func(1); + assert(r === 0, "expr"); +} + +function test_closure3() +{ + function fib(n) + { + if (n <= 0) + return 0; + else if (n === 1) + return 1; + else { + return fib(n - 1) + fib(n - 2); + } + } + + var fib_func = function fib1(n) + { + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib1(n - 1) + fib1(n - 2); + }; + + assert(fib(6) === 8, "fib"); + assert(fib_func(6) === 8, "fib"); +} + +test_closure1(); +test_closure2(); +test_closure3(); diff --git a/tests/test_language.js b/tests/test_language.js new file mode 100644 index 00000000..b0d7c4df --- /dev/null +++ b/tests/test_language.js @@ -0,0 +1,355 @@ +function throw_error(msg) { + throw Error(msg); +} + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw_error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("unexpected exception type"); + return; + } + } + if (!err) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("expected exception"); + } +} + +function test_op1() +{ + var r, a; + r = 1 + 2; + assert(r, 3); + + r = 1 - 2; + assert(r, -1); + + r = -1; + assert(r, -1, "-1 === -1"); + + r = +2; + assert(r, 2, "+2 === 2"); + + r = 2 * 3; + assert(r, 6, "2 * 3 === 6"); + + r = 4 / 2; + assert(r, 2, "4 / 2 === 2"); + + r = 4 % 3; + assert(r, 1, "4 % 3 === 3"); + + r = 4 << 2; + assert(r, 16, "4 << 2 === 16"); + + r = 1 << 0; + assert(r, 1, "1 << 0 === 1"); + + r = 1 << 29; + assert(r, 536870912, "1 << 29 === 536870912"); + + r = 1 << 30; + assert(r, 1073741824, "1 << 30 === 1073741824"); + + r = 1 << 31; + assert(r, -2147483648, "1 << 31 === -2147483648"); + + r = 1 << 32; + assert(r, 1, "1 << 32 === 1"); + + r = (1 << 31) < 0; + assert(r, true, "(1 << 31) < 0 === true"); + + r = -4 >> 1; + assert(r, -2, "-4 >> 1 === -2"); + + r = -4 >>> 1; + assert(r, 0x7ffffffe, "-4 >>> 1 === 0x7ffffffe"); + + r = -1 >>> 0; + assert(r, 0xffffffff); + + r = 1 & 1; + assert(r, 1, "1 & 1 === 1"); + + r = 0 | 1; + assert(r, 1, "0 | 1 === 1"); + + r = 1 ^ 1; + assert(r, 0, "1 ^ 1 === 0"); + + r = ~1; + assert(r, -2, "~1 === -2"); + + r = !1; + assert(r, false, "!1 === false"); + + assert((1 < 2), true, "(1 < 2) === true"); + + assert((2 > 1), true, "(2 > 1) === true"); + + assert(('b' > 'a'), true, "('b' > 'a') === true"); + + assert(2 ** 8, 256, "2 ** 8 === 256"); + + /* minus zero */ + assert(1/(-0.0), -Infinity); + a = 0; + assert(1/(-a), -Infinity); + assert(1/(0 * -6), -Infinity); + + /* 31 bit overflow */ + a = 0x3fffffff; + assert(a + 1, 0x40000000); + a = -0x40000000; + assert(-a, 0x40000000); +} + +function test_cvt() +{ + assert((NaN | 0), 0); + assert((Infinity | 0), 0); + assert(((-Infinity) | 0), 0); + assert(("12345" | 0), 12345); + assert(("0x12345" | 0), 0x12345); + assert(((4294967296 * 3 - 4) | 0), -4); + + assert(("12345" >>> 0), 12345); + assert(("0x12345" >>> 0), 0x12345); + assert((NaN >>> 0), 0); + assert((Infinity >>> 0), 0); + assert(((-Infinity) >>> 0), 0); + assert(((4294967296 * 3 - 4) >>> 0), (4294967296 - 4)); +} + +function test_eq() +{ + assert(null == undefined); + assert(undefined == null); + assert(true == 1); + assert(0 == false); + assert("" == 0); + assert("123" == 123); + assert("122" != 123); +// assert((new Number(1)) == 1); +// assert(2 == (new Number(2))); +// assert((new String("abc")) == "abc"); +// assert({} != "abc"); +} + +function test_inc_dec() +{ + var a, r; + + a = 1; + r = a++; + assert(r === 1 && a === 2); + + a = 1; + r = ++a; + assert(r === 2 && a === 2); + + a = 1; + r = a--; + assert(r === 1 && a === 0); + + a = 1; + r = --a; + assert(r === 0 && a === 0); + + a = {x:true}; + a.x++; + assert(a.x, 2, "++"); + + a = {x:true}; + a.x--; + assert(a.x, 0, "--"); + + a = [true]; + a[0]++; + assert(a[0], 2, "++"); + + a = {x:true}; + r = a.x++; + assert(r === 1 && a.x === 2); + + a = {x:true}; + r = a.x--; + assert(r === 1 && a.x === 0); + + a = [true]; + r = a[0]++; + assert(r === 1 && a[0] === 2); + + a = [true]; + r = a[0]--; + assert(r === 1 && a[0] === 0); +} + +function F(x) +{ + this.x = x; +} + +function test_op2() +{ + var a, b; + a = new Object; + a.x = 1; + assert(a.x, 1, "new"); + b = new F(2); + assert(b.x, 2, "new"); + assert((b instanceof F), true, "instanceof F"); + + a = {x : 2}; + assert(("x" in a), true, "in"); + assert(("y" in a), false, "in"); + + a = {}; + assert((a instanceof Object), true, "instanceof Object"); + assert((a instanceof String), false, "instanceof String"); + + assert((typeof 1), "number", "typeof"); + assert((typeof Object), "function", "typeof"); + assert((typeof null), "object", "typeof"); + assert((typeof unknown_var), "undefined", "typeof"); + + a = {x: 1, y: 1}; + assert((delete a.x), true, "delete"); + assert(("x" in a), false, "delete in"); + + a = {x: 1, if: 2}; + assert(a.if, 2); + + a = {x: 1, y: 2, __proto__: { z: 3 }}; + assert(a.x, 1); + assert(a.y, 2); + assert(Object.getPrototypeOf(a).z, 3); + + /* getter/setter/method */ + b = 2; + a = {get x() { return b; }, set x(v) { b = v; }, f(v) { return v + 1 }, + set: 10, get: 11 }; + assert(a.x, 2); + a.x = 3; + assert(a.x, 3); + assert(a.f(3), 4); + assert(a.set, 10); + assert(a.get, 11); + + a = { set() { return 1; }, get() { return 2; }} + assert(a.set(), 1); + assert(a.get(), 2); +} + +function test_prototype() +{ + function f() { } + assert(f.prototype.constructor, f, "prototype"); +} + +function test_arguments() +{ + function f2() { + assert(arguments.length, 2, "arguments"); + assert(arguments[0], 1, "arguments"); + assert(arguments[1], 3, "arguments"); + } + f2(1, 3); +} + +function test_to_primitive() +{ + var obj; + obj = { x : "abc", y: 1234 }; + obj.toString = function () { return this.x; }; + obj.valueOf = function () { return this.y; }; + assert(obj + "", "1234"); + assert(obj * 1, 1234); +} + +function test_labels() +{ + do x: { break x; } while(0); + if (1) + x: { break x; } + else + x: { break x; } + while (0) x: { break x; }; +} + +function test_labels2() +{ + while (1) label: break + var i = 0 + while (i < 3) label: { + if (i > 0) + break + i++ + } + assert(i == 1) + for (;;) label: break + for (i = 0; i < 3; i++) label: { + if (i > 0) + break + } + assert(i == 1) +} + +test_op1(); +test_cvt(); +test_eq(); +test_inc_dec(); +test_op2(); +test_prototype(); +test_arguments(); +test_to_primitive(); +test_labels(); +test_labels2(); diff --git a/tests/test_loop.js b/tests/test_loop.js new file mode 100644 index 00000000..1f5d63d0 --- /dev/null +++ b/tests/test_loop.js @@ -0,0 +1,395 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +/*----------------*/ + +function test_while() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + i++; + } + assert(c === 3); +} + +function test_while_break() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + if (i == 1) + break; + i++; + } + assert(c === 2 && i === 1); +} + +function test_do_while() +{ + var i, c; + i = 0; + c = 0; + do { + c++; + i++; + } while (i < 3); + assert(c === 3 && i === 3); +} + +function test_for() +{ + var i, c; + c = 0; + for(i = 0; i < 3; i++) { + c++; + } + assert(c === 3 && i === 3); + + c = 0; + for(var j = 0; j < 3; j++) { + c++; + } + assert(c === 3 && j === 3); +} + +function test_for_in() +{ + var i, tab, a, b; + + tab = []; + for(i in {x:1, y: 2}) { + tab.push(i); + } + assert(tab.toString(), "x,y", "for_in"); + + if (0) { + /* prototype chain test */ + a = {x:2, y: 2, "1": 3}; + b = {"4" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,x,y,4", "for_in"); + + /* non enumerable properties hide enumerable ones in the + prototype chain */ + a = {y: 2, "1": 3}; + Object.defineProperty(a, "x", { value: 1 }); + b = {"x" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,y", "for_in"); + } + + /* array optimization */ + a = []; + for(i = 0; i < 10; i++) + a.push(i); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "0,1,2,3,4,5,6,7,8,9", "for_in"); + + /* iterate with a field */ + a={x:0}; + tab = []; + for(a.x in {x:1, y: 2}) { + tab.push(a.x); + } + assert(tab.toString(), "x,y", "for_in"); + + /* iterate with a variable field */ + a=[0]; + tab = []; + for(a[0] in {x:1, y: 2}) { + tab.push(a[0]); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable definition in the for in */ + tab = []; + for(var j in {x:1, y: 2}) { + tab.push(j); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable assignment in the for in */ +/* + tab = []; + for(var k = 2 in {x:1, y: 2}) { + tab.push(k); + } + assert(tab.toString(), "x,y", "for_in"); +*/ +} + +function test_for_in2() +{ + var i, tab; + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "y") + continue; + tab.push(i); + } + assert(tab.toString(), "x,z"); + + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "z") + break; + tab.push(i); + } + assert(tab.toString(), "x,y"); +} + +/* +function test_for_in_proxy() { + let removed_key = ""; + let target = {} + let proxy = new Proxy(target, { + ownKeys: function() { + return ["a", "b", "c"]; + }, + getOwnPropertyDescriptor: function(target, key) { + if (removed_key != "" && key == removed_key) + return undefined; + else + return { enumerable: true, configurable: true, value: this[key] }; + } + }); + let str = ""; + for(let o in proxy) { + str += " " + o; + if (o == "a") + removed_key = "b"; + } + assert(str == " a c"); +} +*/ + +function test_for_break() +{ + var i, c; + c = 0; + L1: for(i = 0; i < 3; i++) { + c++; + if (i == 0) + continue; + while (1) { + break L1; + } + } + assert(c === 2 && i === 1); +} + +function test_switch1() +{ + var i, a, s; + s = ""; + for(i = 0; i < 3; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + default: + a = "c"; + break; + } + s += a; + } + assert(s === "abc" && i === 3); +} + +function test_switch2() +{ + var i, a, s; + s = ""; + for(i = 0; i < 4; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + case 2: + continue; + default: + a = "" + i; + break; + } + s += a; + } + assert(s === "ab3" && i === 4); +} + +function test_try_catch1() +{ + try { + throw "hello"; + } catch (e) { + assert(e, "hello", "catch"); + return; + } + assert(false, "catch"); +} + +function test_try_catch2() +{ + var a; + try { + a = 1; + } catch (e) { + a = 2; + } + assert(a, 1, "catch"); +} + +function test_try_catch3() +{ + var s; + s = ""; + try { + s += "t"; + } catch (e) { + s += "c"; + } finally { + s += "f"; + } + assert(s, "tf", "catch"); +} + +function test_try_catch4() +{ + var s; + s = ""; + try { + s += "t"; + throw "c"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + assert(s, "tcf", "catch"); +} + +function test_try_catch5() +{ + var s; + s = ""; + for(;;) { + try { + s += "t"; + break; + s += "b"; + } finally { + s += "f"; + } + } + assert(s, "tf", "catch"); +} + +function test_try_catch6() +{ + function f() { + try { + s += 't'; + return 1; + } finally { + s += "f"; + } + } + var s = ""; + assert(f(), 1); + assert(s, "tf", "catch6"); +} + +function test_try_catch7() +{ + var s; + s = ""; + + try { + try { + s += "t"; + throw "a"; + } finally { + s += "f"; + } + } catch(e) { + s += e; + } finally { + s += "g"; + } + assert(s, "tfag", "catch"); +} + +function test_try_catch8() +{ + var i, s; + + s = ""; + for(var i in {x:1, y:2}) { + try { + s += i; + throw "a"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + } + assert(s, "xafyaf"); +} + +test_while(); +test_while_break(); +test_do_while(); +test_for(); +test_for_break(); +test_switch1(); +test_switch2(); +test_for_in(); +test_for_in2(); +//test_for_in_proxy(); + +test_try_catch1(); +test_try_catch2(); +test_try_catch3(); +test_try_catch4(); +test_try_catch5(); +test_try_catch6(); +test_try_catch7(); +test_try_catch8(); diff --git a/tests/test_rect.js b/tests/test_rect.js new file mode 100644 index 00000000..7027562e --- /dev/null +++ b/tests/test_rect.js @@ -0,0 +1,68 @@ +/* test for example.c */ + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw Error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function cb(param) +{ + return "test" + param; +} + +function test() +{ + var r1, r2, func; + r1 = new Rectangle(100, 200); + assert(r1.x, 100); + assert(r1.y, 200); + + /* test inheritance */ + r2 = new FilledRectangle(100, 200, 0x123456); + assert(r2.x, 100); + assert(r2.y, 200); + assert(r2.color, 0x123456); + + /* test closure */ + func = Rectangle.getClosure("abcd"); + assert(func(), "abcd"); + + /* test function call */ + assert(Rectangle.call(cb, "abc"), "testabc"); +} + +test(); From 20e6abd97a64f804857622fcef0ea259a3dbd8ad Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sat, 14 Feb 2026 18:37:21 -0500 Subject: [PATCH 04/16] Add mquickjs variant support to codegen - Add MQuickJS to CLibrary enum and build matrix - Exclude singlefile and asyncify builds for mquickjs - Add mquickjs-specific object files and defines in Variant.mk Co-Authored-By: Claude Opus 4.5 --- scripts/prepareVariants.ts | 15 ++++++++++++--- templates/Variant.mk | 15 +++++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/scripts/prepareVariants.ts b/scripts/prepareVariants.ts index e2117101..fa4d13ce 100755 --- a/scripts/prepareVariants.ts +++ b/scripts/prepareVariants.ts @@ -29,6 +29,7 @@ enum SyncMode { enum CLibrary { QuickJS = "quickjs", NG = "quickjs-ng", + MQuickJS = "mquickjs", } enum EmscriptenInclusion { @@ -91,7 +92,7 @@ const SEPARATE_FILE_INCLUSION: BuildVariant["exports"] = { } const buildMatrix = { - library: [CLibrary.QuickJS, CLibrary.NG], + library: [CLibrary.QuickJS, CLibrary.NG, CLibrary.MQuickJS], releaseMode: [ReleaseMode.Debug, ReleaseMode.Release], syncMode: [SyncMode.Sync, SyncMode.Asyncify], } as const @@ -163,14 +164,19 @@ function makeTarget(partialVariant: TargetSpec): BuildVariant[] { ...partialVariant, } - // Eliminate singlefile builds for quickjs-ng + // Eliminate singlefile builds for quickjs-ng and mquickjs if ( - variant.library === CLibrary.NG && + (variant.library === CLibrary.NG || variant.library === CLibrary.MQuickJS) && variant.emscriptenInclusion === EmscriptenInclusion.SingleFile ) { return [] } + // Eliminate asyncify builds for mquickjs (not supported yet) + if (variant.library === CLibrary.MQuickJS && variant.syncMode === SyncMode.Asyncify) { + return [] + } + return variant }) }) @@ -535,6 +541,7 @@ and [QuickJSAsyncContext](${DOC_ROOT_URL}/quickjs-emscripten/classes/QuickJSAsyn const describeLibrary = { [CLibrary.QuickJS]: `The original [bellard/quickjs](https://github.com/bellard/quickjs) library.`, [CLibrary.NG]: `[quickjs-ng](https://github.com/quickjs-ng/quickjs) is a fork of quickjs that tends to add features more quickly.`, + [CLibrary.MQuickJS]: `[mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size.`, } const describeModuleFactory = { @@ -872,11 +879,13 @@ function getTargetPackageSuffix(targetName: string, variant: BuildVariant): stri const CLibrarySubtree: Record = { quickjs: "vendor/quickjs", "quickjs-ng": "vendor/quickjs-ng", + mquickjs: "vendor/mquickjs", } const CLibraryGithubRepo: Record = { quickjs: "bellard/quickjs", "quickjs-ng": "quickjs-ng/quickjs", + mquickjs: "bellard/mquickjs", } // Libraries that use release tags (from VERSION file) instead of git subtree SHA diff --git a/templates/Variant.mk b/templates/Variant.mk index b177c4fd..55fda3a3 100644 --- a/templates/Variant.mk +++ b/templates/Variant.mk @@ -21,12 +21,15 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) - # quickjs-ng uses amalgamated build (single source file) - # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) - QUICKJS_OBJS=quickjs-amalgam.o - QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) - QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS +else ifeq ($(QUICKJS_LIB),quickjs-ng) + # quickjs-ng uses different source files than bellard/quickjs + QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o + QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG else # bellard/quickjs uses separate source files From 5d210e7d07da15ef2387897d24c8eaf94bb17c45 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sat, 14 Feb 2026 22:35:06 -0500 Subject: [PATCH 05/16] Add runtime feature detection for QuickJS variants - Add QTS_Has*Support() C functions to detect variant capabilities - Create interface-mquickjs.c with mquickjs-specific implementation - Add QuickJSFeature type and QuickJSUnsupported error class - Add hasSupport() and assertHasSupport() methods to QuickJSWASMModule - Update Variant.mk to select interface file based on variant Features detected: modules, promises, symbols, bigint, intrinsics, eval Co-Authored-By: Claude Opus 4.5 --- c/interface-mquickjs.c | 670 ++++++++++++++++++ c/interface.c | 32 +- .../quickjs-emscripten-core/src/errors.ts | 21 + packages/quickjs-emscripten-core/src/index.ts | 1 + .../src/module-test.ts | 10 +- .../quickjs-emscripten-core/src/module.ts | 69 +- packages/quickjs-emscripten-core/src/types.ts | 13 + templates/Variant.mk | 10 + 8 files changed, 822 insertions(+), 4 deletions(-) create mode 100644 c/interface-mquickjs.c diff --git a/c/interface-mquickjs.c b/c/interface-mquickjs.c new file mode 100644 index 00000000..e3f06c7e --- /dev/null +++ b/c/interface-mquickjs.c @@ -0,0 +1,670 @@ +/** + * interface-mquickjs.c + * + * Implementation of the QTS_* interface for mquickjs. + * mquickjs is a minimal QuickJS variant that lacks many features: + * - No JSRuntime (only JSContext) + * - No modules + * - No promises + * - No symbols (built-in support) + * - No BigInt + * - No intrinsics configuration + * + * This file provides the same QTS_* interface as interface.c but returns + * errors for unsupported operations and adapts to mquickjs's different API. + */ + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#include +#include +#include +#include +#include + +#include "../vendor/mquickjs/mquickjs.h" +#include "../vendor/mquickjs/cutils.h" + +#define PKG "quickjs-emscripten: " +#define LOG_LEN 500 + +#ifdef QTS_DEBUG_MODE +#define QTS_DEBUG(msg) qts_log(msg); +#else +#define QTS_DEBUG(msg) ; +#endif + +/** + * Signal to our FFI code generator that this string argument should be passed as a pointer + * allocated by the caller on the heap, not a JS string on the stack. + */ +#define BorrowedHeapChar const char +#define OwnedHeapChar char +#define JSBorrowedChar const char + +/** + * Signal to our FFI code generator that this function should be called + * asynchronously when compiled with ASYNCIFY. + */ +#define MaybeAsync(T) T + +/** + * Signal to our FFI code generator that this function is only available in + * ASYNCIFY builds. + */ +#define AsyncifyOnly(T) T + +#define JSVoid void + +#define EvalFlags int +#define IntrinsicsFlags int +#define EvalDetectModule int + +// In mquickjs, JSValue and JSValueConst are the same type (just an integer) +typedef JSValue JSValueConst; + +/** + * mquickjs doesn't have JSRuntime, so we create a fake one that holds context references. + * We use the context pointer as the "runtime" pointer to simplify things. + */ +typedef JSContext JSRuntime; + +/** + * Runtime data stored in context opaque + */ +typedef struct qts_RuntimeData { + bool debug_log; + void *interrupt_opaque; + int (*interrupt_handler)(JSContext *ctx, void *opaque); +} qts_RuntimeData; + +qts_RuntimeData *qts_get_runtime_data(JSContext *ctx) { + qts_RuntimeData *data = (qts_RuntimeData *)JS_GetContextOpaque(ctx); + if (data == NULL) { + data = malloc(sizeof(qts_RuntimeData)); + data->debug_log = false; + data->interrupt_opaque = NULL; + data->interrupt_handler = NULL; + JS_SetContextOpaque(ctx, data); + } + return data; +} + +// Helper function for compatibility - context is runtime in mquickjs +static inline JSContext *JS_GetRuntime(JSContext *ctx) { + return ctx; +} + +void qts_log(char *msg) { + fputs(PKG, stderr); + fputs(msg, stderr); + fputs("\n", stderr); +} + +void qts_dump(JSContext *ctx, JSValue value) { + JSCStringBuf buf; + const char *str = JS_ToCString(ctx, value, &buf); + if (!str) { + QTS_DEBUG("QTS_DUMP: can't dump"); + return; + } + fputs(str, stderr); + putchar('\n'); +} + +JSValue *jsvalue_to_heap(JSValue value) { + JSValue *result = malloc(sizeof(JSValue)); + if (result) { + *result = value; + } + return result; +} + +JSValue *QTS_Throw(JSContext *ctx, JSValueConst *error) { + // mquickjs JS_Throw takes the value directly, not a copy + return jsvalue_to_heap(JS_Throw(ctx, *error)); +} + +JSValue *QTS_NewError(JSContext *ctx) { + // mquickjs doesn't have JS_NewError, create a generic error + return jsvalue_to_heap(JS_ThrowError(ctx, JS_CLASS_ERROR, "Error")); +} + +/** + * Limits - mquickjs doesn't support memory limits + */ +void QTS_RuntimeSetMemoryLimit(JSRuntime *rt, size_t limit) { + // Not supported in mquickjs - silently ignore +} + +/** + * Memory diagnostics - not supported in mquickjs + */ +JSValue *QTS_RuntimeComputeMemoryUsage(JSRuntime *rt, JSContext *ctx) { + // Return an empty object - memory diagnostics not supported + return jsvalue_to_heap(JS_NewObject(ctx)); +} + +OwnedHeapChar *QTS_RuntimeDumpMemoryUsage(JSRuntime *rt) { + char *result = strdup("mquickjs: memory diagnostics not supported"); + return result; +} + +int QTS_RecoverableLeakCheck() { + return 0; +} + +int QTS_BuildIsSanitizeLeak() { +#ifdef QTS_SANITIZE_LEAK + return 1; +#else + return 0; +#endif +} + +/** + * Set the stack size limit - not directly supported in mquickjs + */ +void QTS_RuntimeSetMaxStackSize(JSRuntime *rt, size_t stack_size) { + // mquickjs has JS_StackCheck but no way to set the limit dynamically +} + +/** + * Constant pointers. + */ +JSValueConst QTS_Undefined = JS_UNDEFINED; +JSValueConst *QTS_GetUndefined() { + return &QTS_Undefined; +} + +JSValueConst QTS_Null = JS_NULL; +JSValueConst *QTS_GetNull() { + return &QTS_Null; +} + +JSValueConst QTS_False = JS_FALSE; +JSValueConst *QTS_GetFalse() { + return &QTS_False; +} + +JSValueConst QTS_True = JS_TRUE; +JSValueConst *QTS_GetTrue() { + return &QTS_True; +} + +/** + * Standard FFI functions + */ + +// Pre-allocated memory for mquickjs context +// mquickjs requires pre-allocated memory +#define MQUICKJS_HEAP_SIZE (4 * 1024 * 1024) // 4MB default +static uint8_t *mquickjs_heap = NULL; + +JSRuntime *QTS_NewRuntime() { + // mquickjs doesn't have separate runtime, we create a context directly + // The "runtime" is just the context pointer + if (mquickjs_heap == NULL) { + mquickjs_heap = malloc(MQUICKJS_HEAP_SIZE); + if (mquickjs_heap == NULL) { + return NULL; + } + } + + JSContext *ctx = JS_NewContext(mquickjs_heap, MQUICKJS_HEAP_SIZE, NULL); + return ctx; // JSRuntime* is actually JSContext* for mquickjs +} + +void QTS_FreeRuntime(JSRuntime *rt) { + JSContext *ctx = (JSContext *)rt; + qts_RuntimeData *data = (qts_RuntimeData *)JS_GetContextOpaque(ctx); + if (data) { + free(data); + } + JS_FreeContext(ctx); +} + +// Intrinsics enum - for compatibility, but mquickjs doesn't support intrinsics selection +enum QTS_Intrinsic { + QTS_Intrinsic_BaseObjects = 1 << 0, + QTS_Intrinsic_Date = 1 << 1, + QTS_Intrinsic_Eval = 1 << 2, + QTS_Intrinsic_StringNormalize = 1 << 3, + QTS_Intrinsic_RegExp = 1 << 4, + QTS_Intrinsic_RegExpCompiler = 1 << 5, + QTS_Intrinsic_JSON = 1 << 6, + QTS_Intrinsic_Proxy = 1 << 7, + QTS_Intrinsic_MapSet = 1 << 8, + QTS_Intrinsic_TypedArrays = 1 << 9, + QTS_Intrinsic_Promise = 1 << 10, + QTS_Intrinsic_BigInt = 1 << 11, + QTS_Intrinsic_BigFloat = 1 << 12, + QTS_Intrinsic_BigDecimal = 1 << 13, + QTS_Intrinsic_OperatorOverloading = 1 << 14, + QTS_Intrinsic_BignumExt = 1 << 15, +}; + +JSContext *QTS_NewContext(JSRuntime *rt, IntrinsicsFlags intrinsics) { + // mquickjs doesn't have separate context creation from runtime + // The runtime IS the context, just return it + // Intrinsics are ignored in mquickjs + return (JSContext *)rt; +} + +void QTS_FreeContext(JSContext *ctx) { + // In mquickjs, context is freed with the runtime + // Don't free here since QTS_FreeRuntime handles it +} + +void QTS_FreeValuePointer(JSContext *ctx, JSValue *value) { + // mquickjs doesn't have reference counting like quickjs + // Just free the heap allocation + free(value); +} + +void QTS_FreeValuePointerRuntime(JSRuntime *rt, JSValue *value) { + free(value); +} + +void QTS_FreeVoidPointer(JSContext *ctx, JSVoid *ptr) { + free(ptr); +} + +void QTS_FreeCString(JSContext *ctx, JSBorrowedChar *str) { + // mquickjs uses a buffer for ToCString, no need to free + // The string is valid only until next ToCString call +} + +JSValue *QTS_DupValuePointer(JSContext *ctx, JSValueConst *val) { + // mquickjs doesn't have JS_DupValue - values are simple integers + return jsvalue_to_heap(*val); +} + +JSValue *QTS_NewObject(JSContext *ctx) { + return jsvalue_to_heap(JS_NewObject(ctx)); +} + +JSValue *QTS_NewObjectProto(JSContext *ctx, JSValueConst *proto) { + // mquickjs doesn't have JS_NewObjectProto - just create a basic object + return jsvalue_to_heap(JS_NewObject(ctx)); +} + +JSValue *QTS_NewArray(JSContext *ctx) { + return jsvalue_to_heap(JS_NewArray(ctx, 0)); +} + +JSValue *QTS_NewArrayBuffer(JSContext *ctx, JSVoid *buffer, size_t length) { + // mquickjs has ArrayBuffer support via typed arrays + // For now, return an exception - not directly supported + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "ArrayBuffer not supported in mquickjs")); +} + +JSValue *QTS_NewFloat64(JSContext *ctx, double num) { + return jsvalue_to_heap(JS_NewFloat64(ctx, num)); +} + +double QTS_GetFloat64(JSContext *ctx, JSValueConst *value) { + double result = NAN; + JS_ToNumber(ctx, &result, *value); + return result; +} + +JSValue *QTS_NewString(JSContext *ctx, BorrowedHeapChar *string) { + return jsvalue_to_heap(JS_NewString(ctx, string)); +} + +JSBorrowedChar *QTS_GetString(JSContext *ctx, JSValueConst *value) { + // Note: mquickjs uses a static buffer for ToCString + // We need to copy the string to heap for our API + JSCStringBuf buf; + const char *str = JS_ToCString(ctx, *value, &buf); + if (str == NULL) { + return NULL; + } + return strdup(str); +} + +JSVoid *QTS_GetArrayBuffer(JSContext *ctx, JSValueConst *data) { + // Not supported in mquickjs + return NULL; +} + +size_t QTS_GetArrayBufferLength(JSContext *ctx, JSValueConst *data) { + return 0; +} + +JSValue *QTS_NewSymbol(JSContext *ctx, BorrowedHeapChar *description, int isGlobal) { + // Symbols not supported in mquickjs + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Symbols not supported in mquickjs")); +} + +MaybeAsync(JSBorrowedChar *) QTS_GetSymbolDescriptionOrKey(JSContext *ctx, JSValueConst *value) { + return strdup("symbols not supported"); +} + +int QTS_IsGlobalSymbol(JSContext *ctx, JSValueConst *value) { + return 0; +} + +int QTS_IsJobPending(JSRuntime *rt) { + // No job queue in mquickjs + return 0; +} + +MaybeAsync(JSValue *) QTS_ExecutePendingJob(JSRuntime *rt, int maxJobsToExecute, JSContext **lastJobContext) { + // No jobs in mquickjs + *lastJobContext = (JSContext *)rt; + return jsvalue_to_heap(JS_NewInt32((JSContext *)rt, 0)); +} + +MaybeAsync(JSValue *) QTS_GetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name) { + JSCStringBuf buf; + const char *prop_str = JS_ToCString(ctx, *prop_name, &buf); + if (prop_str == NULL) { + return jsvalue_to_heap(JS_EXCEPTION); + } + JSValue result = JS_GetPropertyStr(ctx, *this_val, prop_str); + return jsvalue_to_heap(result); +} + +MaybeAsync(JSValue *) QTS_GetPropNumber(JSContext *ctx, JSValueConst *this_val, int prop_name) { + JSValue prop_val = JS_GetPropertyUint32(ctx, *this_val, (uint32_t)prop_name); + return jsvalue_to_heap(prop_val); +} + +MaybeAsync(void) QTS_SetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value) { + JSCStringBuf buf; + const char *prop_str = JS_ToCString(ctx, *prop_name, &buf); + if (prop_str == NULL) { + return; + } + JS_SetPropertyStr(ctx, *this_val, prop_str, *prop_value); +} + +void QTS_DefineProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value, JSValueConst *get, JSValueConst *set, bool configurable, bool enumerable, bool has_value) { + // mquickjs doesn't have JS_DefineProperty + // Fall back to simple SetProperty + if (has_value) { + QTS_SetProp(ctx, this_val, prop_name, prop_value); + } +} + +MaybeAsync(JSValue *) QTS_GetOwnPropertyNames(JSContext *ctx, JSValue ***out_ptrs, uint32_t *out_len, JSValueConst *obj, int flags) { + // mquickjs doesn't have JS_GetOwnPropertyNames + // Return empty array + *out_len = 0; + *out_ptrs = NULL; + return NULL; +} + +MaybeAsync(JSValue *) QTS_Call(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, int argc, JSValueConst **argv_ptrs) { + // mquickjs has a different calling convention - arguments are pushed onto a stack + // Push this, then args + for (int i = argc - 1; i >= 0; i--) { + JS_PushArg(ctx, *(argv_ptrs[i])); + } + JS_PushArg(ctx, *this_obj); + JS_PushArg(ctx, *func_obj); + + JSValue result = JS_Call(ctx, argc); + return jsvalue_to_heap(result); +} + +JSValue *QTS_ResolveException(JSContext *ctx, JSValue *maybe_exception) { + if (JS_IsException(*maybe_exception)) { + return jsvalue_to_heap(JS_GetException(ctx)); + } + return NULL; +} + +MaybeAsync(JSBorrowedChar *) QTS_Dump(JSContext *ctx, JSValueConst *obj) { + // Simple dump - just convert to string + JSCStringBuf buf; + const char *str = JS_ToCString(ctx, *obj, &buf); + if (str == NULL) { + return strdup("null"); + } + return strdup(str); +} + +MaybeAsync(JSValue *) QTS_Eval(JSContext *ctx, BorrowedHeapChar *js_code, size_t js_code_length, const char *filename, EvalDetectModule detectModule, EvalFlags evalFlags) { + // mquickjs doesn't support modules + if (evalFlags & 0x20) { // JS_EVAL_TYPE_MODULE = 0x20 in quickjs + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Modules not supported in mquickjs")); + } + + int mquickjs_flags = JS_EVAL_RETVAL; // We want the return value + JSValue result = JS_Eval(ctx, js_code, js_code_length, filename, mquickjs_flags); + return jsvalue_to_heap(result); +} + +JSValue *QTS_GetModuleNamespace(JSContext *ctx, JSValueConst *module_func_obj) { + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Modules not supported in mquickjs")); +} + +OwnedHeapChar *QTS_Typeof(JSContext *ctx, JSValueConst *value) { + const char *result = "unknown"; + JSValue val = *value; + + if (JS_IsNumber(ctx, val)) { + result = "number"; + } else if (JS_IsFunction(ctx, val)) { + result = "function"; + } else if (JS_IsBool(val)) { + result = "boolean"; + } else if (JS_IsNull(val)) { + result = "object"; + } else if (JS_IsUndefined(val)) { + result = "undefined"; + } else if (JS_IsString(ctx, val)) { + result = "string"; + } else if (JS_IsPtr(val)) { + // Could be object or other pointer type + result = "object"; + } + + return strdup(result); +} + +int QTS_GetLength(JSContext *ctx, uint32_t *out_len, JSValueConst *value) { + JSValue len_val = JS_GetPropertyStr(ctx, *value, "length"); + if (JS_IsException(len_val)) { + return -1; + } + + int result = JS_ToUint32(ctx, out_len, len_val); + return result; +} + +typedef enum IsEqualOp { + QTS_EqualOp_StrictEq = 0, + QTS_EqualOp_SameValue = 1, + QTS_EqualOp_SameValueZero = 2, +} IsEqualOp; + +int QTS_IsEqual(JSContext *ctx, JSValueConst *a, JSValueConst *b, IsEqualOp op) { + // mquickjs doesn't have these comparison functions + // Simple equality check + return *a == *b ? 1 : 0; +} + +JSValue *QTS_GetGlobalObject(JSContext *ctx) { + return jsvalue_to_heap(JS_GetGlobalObject(ctx)); +} + +JSValue *QTS_NewPromiseCapability(JSContext *ctx, JSValue **resolve_funcs_out) { + // Promises not supported in mquickjs + resolve_funcs_out[0] = jsvalue_to_heap(JS_UNDEFINED); + resolve_funcs_out[1] = jsvalue_to_heap(JS_UNDEFINED); + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Promises not supported in mquickjs")); +} + +// JSPromiseStateEnum equivalent for compatibility +typedef enum { + JS_PROMISE_PENDING, + JS_PROMISE_FULFILLED, + JS_PROMISE_REJECTED, +} JSPromiseStateEnum; + +JSPromiseStateEnum QTS_PromiseState(JSContext *ctx, JSValueConst *promise) { + return -1; // Not a promise +} + +JSValue *QTS_PromiseResult(JSContext *ctx, JSValueConst *promise) { + return jsvalue_to_heap(JS_UNDEFINED); +} + +void QTS_TestStringArg(const char *string) { + // pass +} + +int QTS_GetDebugLogEnabled(JSRuntime *rt) { + qts_RuntimeData *data = qts_get_runtime_data((JSContext *)rt); + return data->debug_log ? 1 : 0; +} + +void QTS_SetDebugLogEnabled(JSRuntime *rt, int is_enabled) { +#ifdef QTS_DEBUG_MODE + qts_RuntimeData *data = qts_get_runtime_data((JSContext *)rt); + data->debug_log = (bool)is_enabled; +#endif +} + +int QTS_BuildIsDebug() { +#ifdef QTS_DEBUG_MODE + return 1; +#else + return 0; +#endif +} + +int QTS_BuildIsAsyncify() { +#ifdef QTS_ASYNCIFY + return 1; +#else + return 0; +#endif +} + +// ---------------------------------------------------------------------------- +// Feature detection - mquickjs has limited feature support + +int QTS_HasModuleSupport() { + return 0; // No module support +} + +int QTS_HasPromiseSupport() { + return 0; // No promise support +} + +int QTS_HasSymbolSupport() { + return 0; // No symbol support +} + +int QTS_HasBigIntSupport() { + return 0; // No BigInt support +} + +int QTS_HasIntrinsicsSupport() { + return 0; // No intrinsics configuration support +} + +int QTS_HasEvalSupport() { + return 1; // Basic eval is supported +} + +// ---------------------------------------------------------------------------- +// C -> Host Callbacks + +#ifdef __EMSCRIPTEN__ +EM_JS(MaybeAsync(JSValue *), qts_host_call_function, (JSContext * ctx, JSValueConst *this_ptr, int argc, JSValueConst *argv, uint32_t magic_func_id), { +#ifdef QTS_ASYNCIFY + const asyncify = {['handleSleep'] : Asyncify.handleSleep}; +#else + const asyncify = undefined; +#endif + return Module['callbacks']['callFunction'](asyncify, ctx, this_ptr, argc, argv, magic_func_id); +}); +#endif + +// Callback storage for C functions +typedef struct { + uint32_t func_id; + JSContext *ctx; +} QTSFunctionData; + +// Function callback wrapper +JSValue qts_call_function_wrapper(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // This is simplified - mquickjs has different function calling + return JS_UNDEFINED; +} + +JSValue *QTS_NewFunction(JSContext *ctx, uint32_t func_id, const char *name) { + // mquickjs has a very different function creation model + // For now, return an error + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Custom functions not yet supported in mquickjs binding")); +} + +JSValueConst *QTS_ArgvGetJSValueConstPointer(JSValueConst *argv, int index) { + return &argv[index]; +} + +// Interrupt handler wrapper +int qts_mquickjs_interrupt_handler(JSContext *ctx, void *opaque) { + qts_RuntimeData *data = qts_get_runtime_data(ctx); + if (data->interrupt_handler) { + return data->interrupt_handler(ctx, data->interrupt_opaque); + } + return 0; +} + +#ifdef __EMSCRIPTEN__ +EM_JS(int, qts_host_interrupt_handler, (JSRuntime * rt), { + const asyncify = undefined; + return Module['callbacks']['shouldInterrupt'](asyncify, rt); +}); +#endif + +int qts_interrupt_handler(JSContext *ctx, void *_unused) { + return qts_host_interrupt_handler(ctx); +} + +void QTS_RuntimeEnableInterruptHandler(JSRuntime *rt) { + JS_SetInterruptHandler((JSContext *)rt, &qts_interrupt_handler); +} + +void QTS_RuntimeDisableInterruptHandler(JSRuntime *rt) { + JS_SetInterruptHandler((JSContext *)rt, NULL); +} + +// Module loading - not supported in mquickjs +#ifdef __EMSCRIPTEN__ +EM_JS(MaybeAsync(char *), qts_host_load_module_source, (JSRuntime * rt, JSContext *ctx, const char *module_name), { + // Not supported + return 0; +}); + +EM_JS(MaybeAsync(char *), qts_host_normalize_module, (JSRuntime * rt, JSContext *ctx, const char *module_base_name, const char *module_name), { + // Not supported + return 0; +}); +#endif + +void QTS_RuntimeEnableModuleLoader(JSRuntime *rt, int use_custom_normalize) { + // Module loading not supported in mquickjs - silently ignore +} + +void QTS_RuntimeDisableModuleLoader(JSRuntime *rt) { + // Module loading not supported in mquickjs +} + +JSValue *QTS_bjson_encode(JSContext *ctx, JSValueConst *val) { + // Binary JSON not supported in mquickjs + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Binary JSON not supported in mquickjs")); +} + +JSValue *QTS_bjson_decode(JSContext *ctx, JSValueConst *data) { + return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Binary JSON not supported in mquickjs")); +} diff --git a/c/interface.c b/c/interface.c index ff68294a..30d763a8 100644 --- a/c/interface.c +++ b/c/interface.c @@ -45,7 +45,10 @@ #include #endif -#ifdef QTS_USE_QUICKJS_NG +#ifdef QTS_USE_MQUICKJS +#include "../vendor/mquickjs/mquickjs.h" +#include "../vendor/mquickjs/cutils.h" +#elif defined(QTS_USE_QUICKJS_NG) // quickjs-ng amalgam only provides quickjs.h and quickjs-libc.h #include "../vendor/quickjs-ng/quickjs-libc.h" #include "../vendor/quickjs-ng/quickjs.h" @@ -1142,6 +1145,33 @@ int QTS_BuildIsAsyncify() { #endif } +// ---------------------------------------------------------------------------- +// Feature detection - QuickJS and QuickJS-ng support all features + +int QTS_HasModuleSupport() { + return 1; +} + +int QTS_HasPromiseSupport() { + return 1; +} + +int QTS_HasSymbolSupport() { + return 1; +} + +int QTS_HasBigIntSupport() { + return 1; +} + +int QTS_HasIntrinsicsSupport() { + return 1; +} + +int QTS_HasEvalSupport() { + return 1; +} + // ---------------------------------------------------------------------------- // C -> Host Callbacks // Note: inside EM_JS, we need to use ['...'] subscript syntax for accessing JS diff --git a/packages/quickjs-emscripten-core/src/errors.ts b/packages/quickjs-emscripten-core/src/errors.ts index 51020743..1d00df53 100644 --- a/packages/quickjs-emscripten-core/src/errors.ts +++ b/packages/quickjs-emscripten-core/src/errors.ts @@ -64,3 +64,24 @@ export class QuickJSHostRefRangeExceeded extends Error { export class QuickJSHostRefInvalid extends Error { name = "QuickJSHostRefInvalid" } + +/** + * Error thrown when attempting to use a feature that is not supported by the + * current QuickJS variant (e.g., modules in mquickjs). + */ +export class QuickJSUnsupported extends Error { + name = "QuickJSUnsupported" + + constructor( + /** The feature that is not supported */ + public feature: string, + /** The operation that was attempted, if specified */ + public operation: string | undefined, + /** The name of the variant that doesn't support this feature */ + public variantName: string | undefined, + ) { + const operationPart = operation ? ` (${operation})` : "" + const variantPart = variantName ? ` in ${variantName}` : "" + super(`Feature not supported: ${feature}${operationPart}${variantPart}`) + } +} diff --git a/packages/quickjs-emscripten-core/src/index.ts b/packages/quickjs-emscripten-core/src/index.ts index 4f62a7b7..695cd2f8 100644 --- a/packages/quickjs-emscripten-core/src/index.ts +++ b/packages/quickjs-emscripten-core/src/index.ts @@ -44,6 +44,7 @@ export type { JSModuleNormalizeFailure, JSModuleNormalizeSuccess, Intrinsics, + QuickJSFeature, } from "./types" export { DefaultIntrinsics } from "./types" export type { ModuleEvalOptions } from "./module" diff --git a/packages/quickjs-emscripten-core/src/module-test.ts b/packages/quickjs-emscripten-core/src/module-test.ts index ed5eaebf..a48cf570 100644 --- a/packages/quickjs-emscripten-core/src/module-test.ts +++ b/packages/quickjs-emscripten-core/src/module-test.ts @@ -1,7 +1,7 @@ import type { QuickJSContext } from "./context" import type { ModuleEvalOptions, QuickJSWASMModule } from "./module" import type { QuickJSRuntime } from "./runtime" -import type { ContextOptions, RuntimeOptions } from "./types" +import type { ContextOptions, QuickJSFeature, RuntimeOptions } from "./types" import { QuickJSMemoryLeakDetected } from "./errors" import { Lifetime } from "./lifetime" @@ -84,4 +84,12 @@ export class TestQuickJSWASMModule implements Pick = ( ...args: [Asyncify | undefined, ...BaseArgs] ) => Result | AsyncifySleepResult @@ -330,6 +334,8 @@ export class QuickJSWASMModule { protected callbacks: QuickJSModuleCallbacks /** @private */ protected module: EitherModule + /** @private */ + private featureCache = new Map() /** @private */ constructor(module: EitherModule, ffi: EitherFFI) { @@ -338,6 +344,65 @@ export class QuickJSWASMModule { this.callbacks = new QuickJSModuleCallbacks(module) } + /** + * Check if this QuickJS variant supports a specific feature. + * + * Different QuickJS builds may have different feature sets. For example, + * mquickjs is a minimal build that doesn't support modules, promises, + * symbols, or BigInt. + * + * @param feature - The feature to check support for + * @returns `true` if the feature is supported, `false` otherwise + */ + hasSupport(feature: QuickJSFeature): boolean { + const cached = this.featureCache.get(feature) + if (cached !== undefined) { + return cached + } + + let result: number + switch (feature) { + case "modules": + result = this.ffi.QTS_HasModuleSupport() + break + case "promises": + result = this.ffi.QTS_HasPromiseSupport() + break + case "symbols": + result = this.ffi.QTS_HasSymbolSupport() + break + case "bigint": + result = this.ffi.QTS_HasBigIntSupport() + break + case "intrinsics": + result = this.ffi.QTS_HasIntrinsicsSupport() + break + case "eval": + result = this.ffi.QTS_HasEvalSupport() + break + default: + return unreachable(feature) + } + + const supported = result === 1 + this.featureCache.set(feature, supported) + return supported + } + + /** + * Assert that this QuickJS variant supports a specific feature. + * Throws {@link QuickJSUnsupported} if the feature is not available. + * + * @param feature - The feature to check support for + * @param operation - Optional description of the operation being attempted + * @throws {QuickJSUnsupported} If the feature is not supported + */ + assertHasSupport(feature: QuickJSFeature, operation?: string): void { + if (!this.hasSupport(feature)) { + throw new QuickJSUnsupported(feature, operation, undefined) + } + } + /** * Create a runtime. * Use the runtime to set limits on CPU and memory usage and configure module diff --git a/packages/quickjs-emscripten-core/src/types.ts b/packages/quickjs-emscripten-core/src/types.ts index a5684914..3edea554 100644 --- a/packages/quickjs-emscripten-core/src/types.ts +++ b/packages/quickjs-emscripten-core/src/types.ts @@ -7,6 +7,19 @@ import type { QuickJSAsyncContext } from "./context-asyncify" import type { InterruptHandler, QuickJSRuntime } from "./runtime" import { QuickJSUnknownIntrinsic } from "./errors" +/** + * Features that may or may not be supported by a QuickJS variant. + * Use {@link QuickJSWASMModule#hasSupport} to check if a feature is available. + * + * - `modules`: ES module support (import/export) + * - `promises`: Promise and async/await support + * - `symbols`: Symbol type support + * - `bigint`: BigInt type support + * - `intrinsics`: Intrinsics configuration support + * - `eval`: eval() function support + */ +export type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" + /** * A QuickJSHandle to a constant that will never change, and does not need to * be disposed. diff --git a/templates/Variant.mk b/templates/Variant.mk index 55fda3a3..dc25276c 100644 --- a/templates/Variant.mk +++ b/templates/Variant.mk @@ -26,16 +26,19 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses different source files than bellard/quickjs QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -159,6 +162,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< From 44814572995b422faddf127e43ffd02001ecf103 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sat, 14 Feb 2026 22:52:31 -0500 Subject: [PATCH 06/16] Add QuickJSFeatures class for shared feature detection - Create QuickJSFeatures class with has() and assertHas() methods - Expose features via module.features, runtime.features, context.features - All share the same QuickJSFeatures instance per module - Add feature checks to unsupported operations: - newUniqueSymbol, newSymbolFor, getSymbol (symbols) - newBigInt, getBigInt (bigint) - newPromise (promises) - setModuleLoader (modules) Co-Authored-By: Claude Opus 4.5 --- .../quickjs-emscripten-core/src/context.ts | 15 ++++ .../quickjs-emscripten-core/src/features.ts | 75 ++++++++++++++++++ packages/quickjs-emscripten-core/src/index.ts | 1 + .../src/module-asyncify.ts | 1 + .../src/module-test.ts | 11 +-- .../quickjs-emscripten-core/src/module.ts | 78 +++---------------- .../src/runtime-asyncify.ts | 2 + .../quickjs-emscripten-core/src/runtime.ts | 10 +++ 8 files changed, 119 insertions(+), 74 deletions(-) create mode 100644 packages/quickjs-emscripten-core/src/features.ts diff --git a/packages/quickjs-emscripten-core/src/context.ts b/packages/quickjs-emscripten-core/src/context.ts index 9bc7c8a0..0f8d4a1a 100644 --- a/packages/quickjs-emscripten-core/src/context.ts +++ b/packages/quickjs-emscripten-core/src/context.ts @@ -26,6 +26,7 @@ import { QuickJSPromisePending, QuickJSUnwrapError, } from "./errors" +import type { QuickJSFeatures } from "./features" import type { Disposable, DisposableArray, DisposableFail, DisposableSuccess } from "./lifetime" import { DisposableResult, @@ -194,6 +195,14 @@ export class QuickJSContext */ public readonly runtime: QuickJSRuntime + /** + * Feature detection for this QuickJS variant. + * Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + */ + get features(): QuickJSFeatures { + return this.runtime.features + } + /** @private */ protected readonly ctx: Lifetime /** @private */ @@ -372,6 +381,7 @@ export class QuickJSContext * No two symbols created with this function will be the same value. */ newUniqueSymbol(description: string | symbol): QuickJSHandle { + this.features.assertHas("symbols", "newUniqueSymbol") const key = (typeof description === "symbol" ? description.description : description) ?? "" const ptr = this.memory .newHeapCharPointer(key) @@ -384,6 +394,7 @@ export class QuickJSContext * All symbols created with the same key will be the same value. */ newSymbolFor(key: string | symbol): QuickJSHandle { + this.features.assertHas("symbols", "newSymbolFor") const description = (typeof key === "symbol" ? key.description : key) ?? "" const ptr = this.memory .newHeapCharPointer(description) @@ -403,6 +414,7 @@ export class QuickJSContext * Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value. */ newBigInt(num: bigint): QuickJSHandle { + this.features.assertHas("bigint", "newBigInt") if (!this._BigInt) { const bigIntHandle = this.getProp(this.global, "BigInt") this.memory.manage(bigIntHandle) @@ -478,6 +490,7 @@ export class QuickJSContext newPromise( value?: PromiseExecutor | Promise, ): QuickJSDeferredPromise { + this.features.assertHas("promises", "newPromise") const deferredPromise = Scope.withScope((scope) => { const mutablePointerArray = scope.manage( this.memory.newMutablePointerArray(2), @@ -788,6 +801,7 @@ export class QuickJSContext * registry in the guest, it will be created with Symbol.for on the host. */ getSymbol(handle: QuickJSHandle): symbol { + this.features.assertHas("symbols", "getSymbol") this.runtime.assertOwned(handle) const key = this.memory.consumeJSCharPointer( this.ffi.QTS_GetSymbolDescriptionOrKey(this.ctx.value, handle.value), @@ -800,6 +814,7 @@ export class QuickJSContext * Converts `handle` to a Javascript bigint. */ getBigInt(handle: QuickJSHandle): bigint { + this.features.assertHas("bigint", "getBigInt") this.runtime.assertOwned(handle) const asString = this.getString(handle) return BigInt(asString) diff --git a/packages/quickjs-emscripten-core/src/features.ts b/packages/quickjs-emscripten-core/src/features.ts new file mode 100644 index 00000000..a583ac2e --- /dev/null +++ b/packages/quickjs-emscripten-core/src/features.ts @@ -0,0 +1,75 @@ +import type { EitherFFI } from "@jitl/quickjs-ffi-types" +import { QuickJSUnsupported } from "./errors" +import type { QuickJSFeature } from "./types" + +/** + * Provides feature detection for a QuickJS variant. + * Different QuickJS builds may have different feature sets. For example, + * mquickjs is a minimal build that doesn't support modules, promises, + * symbols, or BigInt. + * + * Access via {@link QuickJSWASMModule#features}, {@link QuickJSRuntime#features}, + * or {@link QuickJSContext#features}. + */ +export class QuickJSFeatures { + private cache = new Map() + + /** @private */ + constructor(private ffi: EitherFFI) {} + + /** + * Check if this QuickJS variant supports a specific feature. + * @param feature - The feature to check support for + * @returns `true` if the feature is supported, `false` otherwise + */ + has(feature: QuickJSFeature): boolean { + const cached = this.cache.get(feature) + if (cached !== undefined) { + return cached + } + + let result: number + switch (feature) { + case "modules": + result = this.ffi.QTS_HasModuleSupport() + break + case "promises": + result = this.ffi.QTS_HasPromiseSupport() + break + case "symbols": + result = this.ffi.QTS_HasSymbolSupport() + break + case "bigint": + result = this.ffi.QTS_HasBigIntSupport() + break + case "intrinsics": + result = this.ffi.QTS_HasIntrinsicsSupport() + break + case "eval": + result = this.ffi.QTS_HasEvalSupport() + break + default: + return unreachable(feature) + } + + const supported = result === 1 + this.cache.set(feature, supported) + return supported + } + + /** + * Assert that this QuickJS variant supports a specific feature. + * @param feature - The feature to check support for + * @param operation - Optional description of the operation being attempted + * @throws {QuickJSUnsupported} If the feature is not supported + */ + assertHas(feature: QuickJSFeature, operation?: string): void { + if (!this.has(feature)) { + throw new QuickJSUnsupported(feature, operation, undefined) + } + } +} + +function unreachable(x: never): never { + throw new Error(`Unreachable: ${x}`) +} diff --git a/packages/quickjs-emscripten-core/src/index.ts b/packages/quickjs-emscripten-core/src/index.ts index 695cd2f8..b2835f27 100644 --- a/packages/quickjs-emscripten-core/src/index.ts +++ b/packages/quickjs-emscripten-core/src/index.ts @@ -4,6 +4,7 @@ export * from "@jitl/quickjs-ffi-types" export { QuickJSWASMModule } from "./module" export { QuickJSContext } from "./context" export { QuickJSRuntime } from "./runtime" +export { QuickJSFeatures } from "./features" export type { InterruptHandler, ExecutePendingJobsResult } from "./runtime" // Async classes diff --git a/packages/quickjs-emscripten-core/src/module-asyncify.ts b/packages/quickjs-emscripten-core/src/module-asyncify.ts index e4d26cb0..db32d0ad 100644 --- a/packages/quickjs-emscripten-core/src/module-asyncify.ts +++ b/packages/quickjs-emscripten-core/src/module-asyncify.ts @@ -48,6 +48,7 @@ export class QuickJSAsyncWASMModule extends QuickJSWASMModule { ffi: this.ffi, rt, callbacks: this.callbacks, + features: this.features, }) applyBaseRuntimeOptions(runtime, options) diff --git a/packages/quickjs-emscripten-core/src/module-test.ts b/packages/quickjs-emscripten-core/src/module-test.ts index a48cf570..e36e9e96 100644 --- a/packages/quickjs-emscripten-core/src/module-test.ts +++ b/packages/quickjs-emscripten-core/src/module-test.ts @@ -1,7 +1,8 @@ import type { QuickJSContext } from "./context" +import type { QuickJSFeatures } from "./features" import type { ModuleEvalOptions, QuickJSWASMModule } from "./module" import type { QuickJSRuntime } from "./runtime" -import type { ContextOptions, QuickJSFeature, RuntimeOptions } from "./types" +import type { ContextOptions, RuntimeOptions } from "./types" import { QuickJSMemoryLeakDetected } from "./errors" import { Lifetime } from "./lifetime" @@ -85,11 +86,7 @@ export class TestQuickJSWASMModule implements Pick = ( ...args: [Asyncify | undefined, ...BaseArgs] ) => Result | AsyncifySleepResult @@ -334,73 +331,19 @@ export class QuickJSWASMModule { protected callbacks: QuickJSModuleCallbacks /** @private */ protected module: EitherModule - /** @private */ - private featureCache = new Map() + + /** + * Feature detection for this QuickJS variant. + * Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + */ + public readonly features: QuickJSFeatures /** @private */ constructor(module: EitherModule, ffi: EitherFFI) { this.module = module this.ffi = ffi this.callbacks = new QuickJSModuleCallbacks(module) - } - - /** - * Check if this QuickJS variant supports a specific feature. - * - * Different QuickJS builds may have different feature sets. For example, - * mquickjs is a minimal build that doesn't support modules, promises, - * symbols, or BigInt. - * - * @param feature - The feature to check support for - * @returns `true` if the feature is supported, `false` otherwise - */ - hasSupport(feature: QuickJSFeature): boolean { - const cached = this.featureCache.get(feature) - if (cached !== undefined) { - return cached - } - - let result: number - switch (feature) { - case "modules": - result = this.ffi.QTS_HasModuleSupport() - break - case "promises": - result = this.ffi.QTS_HasPromiseSupport() - break - case "symbols": - result = this.ffi.QTS_HasSymbolSupport() - break - case "bigint": - result = this.ffi.QTS_HasBigIntSupport() - break - case "intrinsics": - result = this.ffi.QTS_HasIntrinsicsSupport() - break - case "eval": - result = this.ffi.QTS_HasEvalSupport() - break - default: - return unreachable(feature) - } - - const supported = result === 1 - this.featureCache.set(feature, supported) - return supported - } - - /** - * Assert that this QuickJS variant supports a specific feature. - * Throws {@link QuickJSUnsupported} if the feature is not available. - * - * @param feature - The feature to check support for - * @param operation - Optional description of the operation being attempted - * @throws {QuickJSUnsupported} If the feature is not supported - */ - assertHasSupport(feature: QuickJSFeature, operation?: string): void { - if (!this.hasSupport(feature)) { - throw new QuickJSUnsupported(feature, operation, undefined) - } + this.features = new QuickJSFeatures(ffi) } /** @@ -421,6 +364,7 @@ export class QuickJSWASMModule { callbacks: this.callbacks, ffi: this.ffi, rt, + features: this.features, }) applyBaseRuntimeOptions(runtime, options) diff --git a/packages/quickjs-emscripten-core/src/runtime-asyncify.ts b/packages/quickjs-emscripten-core/src/runtime-asyncify.ts index 17026c39..c1dbeeb4 100644 --- a/packages/quickjs-emscripten-core/src/runtime-asyncify.ts +++ b/packages/quickjs-emscripten-core/src/runtime-asyncify.ts @@ -10,6 +10,7 @@ import type { // eslint-disable-next-line @typescript-eslint/no-unused-vars QuickJSAsyncWASMModule, } from "./module-asyncify" +import type { QuickJSFeatures } from "./features" import type { QuickJSModuleCallbacks } from "./module" import { QuickJSRuntime } from "./runtime" import type { @@ -42,6 +43,7 @@ export class QuickJSAsyncRuntime extends QuickJSRuntime { ffi: QuickJSAsyncFFI rt: Lifetime callbacks: QuickJSModuleCallbacks + features: QuickJSFeatures }) { super(args) } diff --git a/packages/quickjs-emscripten-core/src/runtime.ts b/packages/quickjs-emscripten-core/src/runtime.ts index e404f064..70560deb 100644 --- a/packages/quickjs-emscripten-core/src/runtime.ts +++ b/packages/quickjs-emscripten-core/src/runtime.ts @@ -10,6 +10,7 @@ import { maybeAsyncFn } from "./asyncify-helpers" import { QuickJSContext } from "./context" import { QTS_DEBUG } from "./debug" import { QuickJSWrongOwner } from "./errors" +import type { QuickJSFeatures } from "./features" import type { Disposable } from "./lifetime" import { DisposableResult, Lifetime, Scope, UsingDisposable } from "./lifetime" import { ModuleMemory } from "./memory" @@ -78,6 +79,12 @@ export class QuickJSRuntime extends UsingDisposable implements Disposable { */ public context: QuickJSContext | undefined + /** + * Feature detection for this QuickJS variant. + * Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + */ + public readonly features: QuickJSFeatures + /** @private */ protected module: EitherModule /** @private */ @@ -106,6 +113,7 @@ export class QuickJSRuntime extends UsingDisposable implements Disposable { ffi: EitherFFI rt: Lifetime callbacks: QuickJSModuleCallbacks + features: QuickJSFeatures ownedLifetimes?: Disposable[] }) { super() @@ -115,6 +123,7 @@ export class QuickJSRuntime extends UsingDisposable implements Disposable { this.ffi = args.ffi this.rt = args.rt this.callbacks = args.callbacks + this.features = args.features this.scope.manage(this.rt) this.callbacks.setRuntimeCallbacks(this.rt.value, this.cToHostCallbacks) @@ -173,6 +182,7 @@ export class QuickJSRuntime extends UsingDisposable implements Disposable { * The loader can be removed with {@link removeModuleLoader}. */ setModuleLoader(moduleLoader: JSModuleLoader, moduleNormalizer?: JSModuleNormalizer): void { + this.features.assertHas("modules", "setModuleLoader") this.moduleLoader = moduleLoader this.moduleNormalizer = moduleNormalizer this.ffi.QTS_RuntimeEnableModuleLoader(this.rt.value, this.moduleNormalizer ? 1 : 0) From 084374f0212616df12ecb6611e0186ac7b8c406b Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sun, 15 Feb 2026 03:19:53 -0500 Subject: [PATCH 07/16] Regenerate variant packages with feature detection FFI Updates generated Makefiles with INTERFACE_SOURCE variable and regenerates ffi.ts files with QTS_Has*Support function declarations. Co-Authored-By: Claude Opus 4.5 --- .../package.json | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/package.json diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/package.json b/packages/variant-mquickjs-wasmfile-debug-sync/package.json new file mode 100644 index 00000000..366d7a96 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/package.json @@ -0,0 +1,61 @@ +{ + "name": "@jitl/mquickjs-wasmfile-debug-sync", + "license": "MIT", + "version": "0.31.0", + "description": "Variant of quickjs library: Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/justjake/quickjs-emscripten" + }, + "author": { + "name": "Jake Teton-Landis", + "url": "https://jake.tl" + }, + "scripts": { + "build": "yarn build:c && yarn build:ts", + "build:c": "make", + "build:ts": "npx tsup", + "check:types": "npx tsc --project . --noEmit", + "clean": "make clean", + "prepare": "yarn clean && yarn build" + }, + "files": [ + "LICENSE", + "README.md", + "dist/**/*", + "!dist/*.tsbuildinfo" + ], + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "browser": "./dist/index.mjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "default": "./dist/index.js" + }, + "./package.json": "./package.json", + "./ffi": { + "types": "./dist/ffi.d.ts", + "import": "./dist/ffi.mjs", + "require": "./dist/ffi.js", + "default": "./dist/ffi.js" + }, + "./wasm": "./dist/emscripten-module.wasm", + "./emscripten-module": { + "types": "./dist/emscripten-module.browser.d.ts", + "iife": "./dist/emscripten-module.cjs", + "workerd": "./dist/emscripten-module.cloudflare.cjs", + "browser": "./dist/emscripten-module.browser.mjs", + "import": "./dist/emscripten-module.mjs", + "require": "./dist/emscripten-module.cjs", + "default": "./dist/emscripten-module.cjs" + } + }, + "dependencies": { + "@jitl/quickjs-ffi-types": "workspace:*" + } +} From 71949edc754cc85955dc22d83e57f03aaf5aef47 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sun, 15 Feb 2026 03:24:32 -0500 Subject: [PATCH 08/16] Regenerate all variant packages after adding mquickjs vendor Adds mquickjs vendor subtree from bellard/mquickjs and regenerates all variant packages with feature detection FFI functions. Co-Authored-By: Claude Opus 4.5 --- packages/quickjs-emscripten-core/README.md | 26 ++ .../LICENSE | 47 ++ .../Makefile | 201 ++++++++ .../README.md | 88 ++++ .../src/ffi.ts | 435 ++++++++++++++++++ .../src/index.ts | 25 + .../tsconfig.json | 1 + .../tsup.config.ts | 7 + .../typedoc.json | 1 + .../LICENSE | 47 ++ .../Makefile | 195 ++++++++ .../README.md | 82 ++++ .../package.json | 61 +++ .../src/ffi.ts | 435 ++++++++++++++++++ .../src/index.ts | 25 + .../tsconfig.json | 1 + .../tsup.config.ts | 7 + .../typedoc.json | 1 + variants.json | 132 ++++++ 19 files changed, 1817 insertions(+) create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/LICENSE create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/Makefile create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/README.md create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/tsconfig.json create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/tsup.config.ts create mode 100644 packages/variant-mquickjs-wasmfile-debug-sync/typedoc.json create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/LICENSE create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/Makefile create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/README.md create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/package.json create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/src/index.ts create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/tsconfig.json create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/tsup.config.ts create mode 100644 packages/variant-mquickjs-wasmfile-release-sync/typedoc.json diff --git a/packages/quickjs-emscripten-core/README.md b/packages/quickjs-emscripten-core/README.md index 20f34fec..1a6b3733 100644 --- a/packages/quickjs-emscripten-core/README.md +++ b/packages/quickjs-emscripten-core/README.md @@ -171,6 +171,32 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | | exports | require import browser workerd | Has these package.json export conditions | +### @jitl/mquickjs-wasmfile-debug-sync + +[Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md) | +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +| Variable | Setting | Description | +| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | +| syncMode | sync | The default, normal build. Note that both variants support regular async functions. | +| emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | +| exports | require import browser workerd | Has these package.json export conditions | + +### @jitl/mquickjs-wasmfile-release-sync + +[Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-release-sync/README.md) | +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +| Variable | Setting | Description | +| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| releaseMode | release | Optimized for performance; use when building/deploying your application. | +| syncMode | sync | The default, normal build. Note that both variants support regular async functions. | +| emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | +| exports | require import browser workerd | Has these package.json export conditions | + ### @jitl/quickjs-singlefile-cjs-debug-sync [Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/quickjs-singlefile-cjs-debug-sync/README.md) | diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/LICENSE b/packages/variant-mquickjs-wasmfile-debug-sync/LICENSE new file mode 100644 index 00000000..780adab0 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/LICENSE @@ -0,0 +1,47 @@ +quickjs-emscripten: +The MIT License + +quickjs-emscripten copyright (c) 2019-2024 Jake Teton-Landis + +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. + + +mquickjs: +Micro QuickJS Javascript Engine + +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. diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/Makefile b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile new file mode 100644 index 00000000..77af35c5 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile @@ -0,0 +1,201 @@ +# Tools +EMSDK_VERSION=5.0.1 +EMSDK_DOCKER_IMAGE=emscripten/emsdk:$(EMSDK_VERSION) +EMCC_SRC=../../scripts/emcc.sh +EMCC=EMSDK_VERSION=$(EMSDK_VERSION) EMSDK_DOCKER_IMAGE=$(EMSDK_DOCKER_IMAGE) EMSDK_PROJECT_ROOT=$(REPO_ROOT) EMSDK_DOCKER_CACHE=$(REPO_ROOT)/emsdk-cache/$(EMSDK_VERSION) $(EMCC_SRC) +GENERATE_TS=$(GENERATE_TS_ENV) ../../scripts/generate.ts +THIS_DIR := $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +REPO_ROOT := $(abspath $(THIS_DIR)/../..) + +QUICKJS_LIB=mquickjs + +# Paths +QUICKJS_ROOT=../../vendor/$(QUICKJS_LIB) +WRAPPER_ROOT=../../c +TEMPLATES=../../templates +# Intermediate build files +BUILD_ROOT=build +BUILD_WRAPPER=$(BUILD_ROOT)/wrapper +BUILD_QUICKJS=$(BUILD_ROOT)/quickjs +# Distributed to users +DIST=dist + +# QuickJS +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) + # quickjs-ng uses different source files than bellard/quickjs + QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c +else + # bellard/quickjs uses separate source files + QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o + QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) + QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c +endif +VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) + +# quickjs-emscripten +WRAPPER_DEFINES+=-Wcast-function-type # Likewise, warns about some quickjs casts we don't control. +EMCC_EXPORTED_FUNCS+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.json +EMCC_EXPORTED_FUNCS_ASYNCIFY+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.asyncify.json + +# Emscripten options +# EXPORTED_RUNTIME_METHODS set below after SYNC is defined +CFLAGS_WASM+=-s MODULARIZE=1 +CFLAGS_WASM+=-s IMPORTED_MEMORY=1 # Allow passing WASM memory to Emscripten +CFLAGS_WASM+=-s EXPORT_NAME=QuickJSRaw +CFLAGS_WASM+=-s INVOKE_RUN=0 +CFLAGS_WASM+=-s ALLOW_MEMORY_GROWTH=1 +CFLAGS_WASM+=-s ALLOW_TABLE_GROWTH=1 +CFLAGS_WASM+=-s STACK_SIZE=5MB +# CFLAGS_WASM+=-s MINIMAL_RUNTIME=1 # Appears to break MODULARIZE +CFLAGS_WASM+=-s SUPPORT_ERRNO=0 + +# Emscripten options - like STRICT +# https://github.com/emscripten-core/emscripten/blob/fa339b76424ca9fbe5cf15faea0295d2ac8d58cc/src/settings.js#L1095-L1109 +# CFLAGS_WASM+=-s STRICT_JS=1 # Doesn't work with MODULARIZE +CFLAGS_WASM+=-s IGNORE_MISSING_MAIN=0 --no-entry +CFLAGS_WASM+=-s AUTO_JS_LIBRARIES=0 +CFLAGS_WASM+=-s -lccall.js +CFLAGS_WASM+=-s AUTO_NATIVE_LIBRARIES=0 +CFLAGS_WASM+=-s AUTO_ARCHIVE_INDEXES=0 +CFLAGS_WASM+=-s DEFAULT_TO_CXX=0 +CFLAGS_WASM+=-s ALLOW_UNIMPLEMENTED_SYSCALLS=0 + +# Emscripten options - NodeJS +CFLAGS_WASM+=-s MIN_NODE_VERSION=160000 +CFLAGS_WASM+=-s NODEJS_CATCH_EXIT=0 + +CFLAGS_MJS+=-s EXPORT_ES6=1 +CFLAGS_BROWSER+=-s EXPORT_ES6=1 + +# VARIANT +SYNC=SYNC + +# Set EXPORTED_RUNTIME_METHODS based on sync mode (Asyncify only available in asyncify builds) +ifeq ($(SYNC),ASYNCIFY) +CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.asyncify.json +else +CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json +endif + +CFLAGS_WASM_BROWSER=$(CFLAGS_WASM) + +# Emscripten options - variant & target specific +CFLAGS_WASM+=-O0 +CFLAGS_WASM+=-DQTS_DEBUG_MODE +CFLAGS_WASM+=-DDUMP_LEAKS=1 +CFLAGS_WASM+=-gsource-map +CFLAGS_WASM+=-s ASSERTIONS=1 +CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-extension.js +CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-sourceMapJson.js +CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-wasmOffsetConverter.js +CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-wasmMemory.js +CFLAGS_WASM+=-DQTS_SANITIZE_LEAK +CFLAGS_WASM+=-fsanitize=leak +CFLAGS_WASM+=-g2 +CFLAGS_CJS+=-s ENVIRONMENT=node +CFLAGS_MJS+=-s ENVIRONMENT=node +CFLAGS_BROWSER+=-s ENVIRONMENT=web,worker +CFLAGS_CLOUDFLARE+=-s ENVIRONMENT=web + +# GENERATE_TS options - variant specific +GENERATE_TS_ENV+=DEBUG=true + + +ifdef DEBUG_MAKE + MKDIRP=@echo "\n=====[["" target: $@, deps: $<, variant: $(VARIANT) ""]]=====" ; mkdir -p $(dir $@) +else + MKDIRP=@mkdir -p $(dir $@) + CFLAGS+=-Wunused-command-line-argument=0 +endif + +############################################################################### +# High-level +all: EXPORTS + +clean: + git clean -fx $(DIST) $(BUILD_ROOT) + +############################################################################### +# Emscripten output targets +EXPORTS: BROWSER MJS CJS CLOUDFLARE +CJS: $(DIST)/emscripten-module.cjs $(DIST)/emscripten-module.d.ts +MJS: $(DIST)/emscripten-module.mjs $(DIST)/emscripten-module.d.ts +BROWSER: $(DIST)/emscripten-module.browser.mjs $(DIST)/emscripten-module.browser.d.ts +CLOUDFLARE: $(DIST)/emscripten-module.cloudflare.cjs $(DIST)/emscripten-module.cloudflare.d.ts + +$(DIST)/emscripten-module.mjs: CFLAGS_WASM+=$(CFLAGS_ESM) +$(DIST)/emscripten-module.mjs: $(BUILD_WRAPPER)/interface.o $(VARIANT_QUICKJS_OBJS) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) $(EMCC_EXPORTED_FUNCS) -o $@ $< $(VARIANT_QUICKJS_OBJS) + +$(DIST)/emscripten-module.cjs: CFLAGS_WASM+=$(CFLAGS_CJS) +$(DIST)/emscripten-module.cjs: $(BUILD_WRAPPER)/interface.o $(VARIANT_QUICKJS_OBJS) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) $(EMCC_EXPORTED_FUNCS) -o $@ $< $(VARIANT_QUICKJS_OBJS) + +$(DIST)/emscripten-module.d.ts: $(TEMPLATES)/emscripten-module.$(SYNC).d.ts + $(MKDIRP) + echo '// Generated from $<' > $@ + cat $< >> $@ + +$(DIST)/emscripten-module%.d.ts: $(TEMPLATES)/emscripten-module.$(SYNC).d.ts + $(MKDIRP) + echo '// Generated from $<' > $@ + cat $< >> $@ + +# Browser target needs intermediate build to avoid two copies of .wasm +$(DIST)/emscripten-module.%.mjs: $(BUILD_WRAPPER)/%/emscripten-module.js + $(MKDIRP) + if [ -e $(basename $<).wasm ] ; then cp -v $(basename $<).wasm* $(dir $@); fi + cp $< $@ + +$(DIST)/emscripten-module.%.cjs: $(BUILD_WRAPPER)/%/emscripten-module.js + $(MKDIRP) + if [ -e $(basename $<).wasm ] ; then cp -v $(basename $<).wasm* $(dir $@); fi + cp $< $@ + +$(BUILD_WRAPPER)/browser/emscripten-module.js: CFLAGS_WASM+=$(CFLAGS_BROWSER) +$(BUILD_WRAPPER)/cloudflare/emscripten-module.js: CFLAGS_WASM+=$(CFLAGS_CLOUDFLARE) +$(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_QUICKJS_OBJS) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) $(EMCC_EXPORTED_FUNCS) -o $@ $< $(VARIANT_QUICKJS_OBJS) + +############################################################################### +# Emscripten intermediate files +WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files +$(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +$(BUILD_QUICKJS)/%.o: $(QUICKJS_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(QUICKJS_DEFINES) -c -o $@ $< + +$(BUILD_WRAPPER)/symbols.json: + $(MKDIRP) + $(GENERATE_TS) symbols $@ + +$(BUILD_WRAPPER)/asyncify-remove.json: + $(MKDIRP) + $(GENERATE_TS) sync-symbols $@ + +$(BUILD_WRAPPER)/asyncify-imports.json: + $(MKDIRP) + $(GENERATE_TS) async-callback-symbols $@ diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/README.md b/packages/variant-mquickjs-wasmfile-debug-sync/README.md new file mode 100644 index 00000000..195f2559 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/README.md @@ -0,0 +1,88 @@ +# @jitl/mquickjs-wasmfile-debug-sync + +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +This generated package is part of [quickjs-emscripten](https://github.com/justjake/quickjs-emscripten). +It contains a variant of the quickjs WASM library, and can be used with quickjs-emscripten-core. + +```typescript +import variant from "@jitl/mquickjs-wasmfile-debug-sync" +import { newQuickJSWASMModuleFromVariant } from "quickjs-emscripten-core" +const QuickJS = await newQuickJSWASMModuleFromVariant(variant) +``` + +This variant was built with the following settings: + +## Library: mquickjs + +[mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. + +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. + +## Release mode: debug + +Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. + +## Exports: require import browser workerd + +Exports the following in package.json for the package entrypoint: + +- Exports a NodeJS-compatible CommonJS module, which is faster to load and run compared to an ESModule. +- Exports a NodeJS-compatible ESModule. Cannot be imported synchronously from a NodeJS CommonJS module. +- Exports a browser-compatible ESModule, designed to work in browsers and browser-like environments. +- Targets Cloudflare Workers. + +## Extra async magic? No + +The default, normal build. Note that both variants support regular async functions. + +## Single-file, or separate .wasm file? wasm + +Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. + +## More details + +Full variant JSON description: + +```json +{ + "library": "mquickjs", + "releaseMode": "debug", + "syncMode": "sync", + "description": "Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "emscriptenInclusion": "wasm", + "exports": { + "require": { + "emscriptenEnvironment": ["node"] + }, + "import": { + "emscriptenEnvironment": ["node"] + }, + "browser": { + "emscriptenEnvironment": ["web", "worker"] + }, + "workerd": { + "emscriptenEnvironment": ["web"] + } + } +} +``` + +Variant-specific Emscripten build flags: + +```json +[ + "-O0", + "-DQTS_DEBUG_MODE", + "-DDUMP_LEAKS=1", + "-gsource-map", + "-s ASSERTIONS=1", + "--pre-js $(TEMPLATES)/pre-extension.js", + "--pre-js $(TEMPLATES)/pre-sourceMapJson.js", + "--pre-js $(TEMPLATES)/pre-wasmOffsetConverter.js", + "--pre-js $(TEMPLATES)/pre-wasmMemory.js", + "-DQTS_SANITIZE_LEAK", + "-fsanitize=leak", + "-g2" +] +``` diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts new file mode 100644 index 00000000..9c5141a6 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts @@ -0,0 +1,435 @@ +// This file generated by "generate.ts ffi" in the root of the repo. +import { + QuickJSEmscriptenModule, + JSRuntimePointer, + JSContextPointer, + JSContextPointerPointer, + JSModuleDefPointer, + JSValuePointer, + JSValueConstPointer, + JSValuePointerPointer, + JSValuePointerPointerPointer, + JSValueConstPointerPointer, + QTS_C_To_HostCallbackFuncPointer, + QTS_C_To_HostInterruptFuncPointer, + QTS_C_To_HostLoadModuleFuncPointer, + BorrowedHeapCharPointer, + OwnedHeapCharPointer, + JSBorrowedCharPointer, + JSVoidPointer, + UInt32Pointer, + EvalFlags, + IntrinsicsFlags, + EvalDetectModule, + GetOwnPropertyNamesFlags, + IsEqualOp, + JSPromiseStateEnum, +} from "@jitl/quickjs-ffi-types" + +/** + * Low-level FFI bindings to QuickJS's Emscripten module. + * See instead {@link QuickJSContext}, the public Javascript interface exposed by this + * library. + * + * @unstable The FFI interface is considered private and may change. + */ +export class QuickJSFFI { + constructor(private module: QuickJSEmscriptenModule) {} + /** Set at compile time. */ + readonly DEBUG = true + + QTS_Throw: ( + ctx: JSContextPointer, + error: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_Throw", "number", ["number", "number"]) + + QTS_NewError: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_NewError", + "number", + ["number"], + ) + + QTS_RuntimeSetMemoryLimit: (rt: JSRuntimePointer, limit: number) => void = this.module.cwrap( + "QTS_RuntimeSetMemoryLimit", + null, + ["number", "number"], + ) + + QTS_RuntimeComputeMemoryUsage: (rt: JSRuntimePointer, ctx: JSContextPointer) => JSValuePointer = + this.module.cwrap("QTS_RuntimeComputeMemoryUsage", "number", ["number", "number"]) + + QTS_RuntimeDumpMemoryUsage: (rt: JSRuntimePointer) => OwnedHeapCharPointer = this.module.cwrap( + "QTS_RuntimeDumpMemoryUsage", + "number", + ["number"], + ) + + QTS_RecoverableLeakCheck: () => number = this.module.cwrap( + "QTS_RecoverableLeakCheck", + "number", + [], + ) + + QTS_BuildIsSanitizeLeak: () => number = this.module.cwrap("QTS_BuildIsSanitizeLeak", "number", []) + + QTS_RuntimeSetMaxStackSize: (rt: JSRuntimePointer, stack_size: number) => void = + this.module.cwrap("QTS_RuntimeSetMaxStackSize", null, ["number", "number"]) + + QTS_GetUndefined: () => JSValueConstPointer = this.module.cwrap("QTS_GetUndefined", "number", []) + + QTS_GetNull: () => JSValueConstPointer = this.module.cwrap("QTS_GetNull", "number", []) + + QTS_GetFalse: () => JSValueConstPointer = this.module.cwrap("QTS_GetFalse", "number", []) + + QTS_GetTrue: () => JSValueConstPointer = this.module.cwrap("QTS_GetTrue", "number", []) + + QTS_NewRuntime: () => JSRuntimePointer = this.module.cwrap("QTS_NewRuntime", "number", []) + + QTS_FreeRuntime: (rt: JSRuntimePointer) => void = this.module.cwrap("QTS_FreeRuntime", null, [ + "number", + ]) + + QTS_NewContext: (rt: JSRuntimePointer, intrinsics: IntrinsicsFlags) => JSContextPointer = + this.module.cwrap("QTS_NewContext", "number", ["number", "number"]) + + QTS_FreeContext: (ctx: JSContextPointer) => void = this.module.cwrap("QTS_FreeContext", null, [ + "number", + ]) + + QTS_FreeValuePointer: (ctx: JSContextPointer, value: JSValuePointer) => void = this.module.cwrap( + "QTS_FreeValuePointer", + null, + ["number", "number"], + ) + + QTS_FreeValuePointerRuntime: (rt: JSRuntimePointer, value: JSValuePointer) => void = + this.module.cwrap("QTS_FreeValuePointerRuntime", null, ["number", "number"]) + + QTS_FreeVoidPointer: (ctx: JSContextPointer, ptr: JSVoidPointer) => void = this.module.cwrap( + "QTS_FreeVoidPointer", + null, + ["number", "number"], + ) + + QTS_FreeCString: (ctx: JSContextPointer, str: JSBorrowedCharPointer) => void = this.module.cwrap( + "QTS_FreeCString", + null, + ["number", "number"], + ) + + QTS_DupValuePointer: ( + ctx: JSContextPointer, + val: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_DupValuePointer", "number", ["number", "number"]) + + QTS_NewObject: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_NewObject", + "number", + ["number"], + ) + + QTS_NewObjectProto: ( + ctx: JSContextPointer, + proto: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_NewObjectProto", "number", ["number", "number"]) + + QTS_NewArray: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_NewArray", + "number", + ["number"], + ) + + QTS_NewArrayBuffer: ( + ctx: JSContextPointer, + buffer: JSVoidPointer, + length: number, + ) => JSValuePointer = this.module.cwrap("QTS_NewArrayBuffer", "number", [ + "number", + "number", + "number", + ]) + + QTS_NewFloat64: (ctx: JSContextPointer, num: number) => JSValuePointer = this.module.cwrap( + "QTS_NewFloat64", + "number", + ["number", "number"], + ) + + QTS_GetFloat64: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => number = + this.module.cwrap("QTS_GetFloat64", "number", ["number", "number"]) + + QTS_NewString: (ctx: JSContextPointer, string: BorrowedHeapCharPointer) => JSValuePointer = + this.module.cwrap("QTS_NewString", "number", ["number", "number"]) + + QTS_GetString: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => JSBorrowedCharPointer = this.module.cwrap("QTS_GetString", "number", ["number", "number"]) + + QTS_GetArrayBuffer: ( + ctx: JSContextPointer, + data: JSValuePointer | JSValueConstPointer, + ) => JSVoidPointer = this.module.cwrap("QTS_GetArrayBuffer", "number", ["number", "number"]) + + QTS_GetArrayBufferLength: ( + ctx: JSContextPointer, + data: JSValuePointer | JSValueConstPointer, + ) => number = this.module.cwrap("QTS_GetArrayBufferLength", "number", ["number", "number"]) + + QTS_NewSymbol: ( + ctx: JSContextPointer, + description: BorrowedHeapCharPointer, + isGlobal: number, + ) => JSValuePointer = this.module.cwrap("QTS_NewSymbol", "number", ["number", "number", "number"]) + + QTS_GetSymbolDescriptionOrKey: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => JSBorrowedCharPointer = this.module.cwrap("QTS_GetSymbolDescriptionOrKey", "number", [ + "number", + "number", + ]) + + QTS_IsGlobalSymbol: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => number = this.module.cwrap("QTS_IsGlobalSymbol", "number", ["number", "number"]) + + QTS_IsJobPending: (rt: JSRuntimePointer) => number = this.module.cwrap( + "QTS_IsJobPending", + "number", + ["number"], + ) + + QTS_ExecutePendingJob: ( + rt: JSRuntimePointer, + maxJobsToExecute: number, + lastJobContext: JSContextPointerPointer, + ) => JSValuePointer = this.module.cwrap("QTS_ExecutePendingJob", "number", [ + "number", + "number", + "number", + ]) + + QTS_GetProp: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_GetProp", "number", ["number", "number", "number"]) + + QTS_GetPropNumber: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: number, + ) => JSValuePointer = this.module.cwrap("QTS_GetPropNumber", "number", [ + "number", + "number", + "number", + ]) + + QTS_SetProp: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: JSValuePointer | JSValueConstPointer, + prop_value: JSValuePointer | JSValueConstPointer, + ) => void = this.module.cwrap("QTS_SetProp", null, ["number", "number", "number", "number"]) + + QTS_DefineProp: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: JSValuePointer | JSValueConstPointer, + prop_value: JSValuePointer | JSValueConstPointer, + get: JSValuePointer | JSValueConstPointer, + set: JSValuePointer | JSValueConstPointer, + configurable: boolean, + enumerable: boolean, + has_value: boolean, + ) => void = this.module.cwrap("QTS_DefineProp", null, [ + "number", + "number", + "number", + "number", + "number", + "number", + "boolean", + "boolean", + "boolean", + ]) + + QTS_GetOwnPropertyNames: ( + ctx: JSContextPointer, + out_ptrs: JSValuePointerPointerPointer, + out_len: UInt32Pointer, + obj: JSValuePointer | JSValueConstPointer, + flags: number, + ) => JSValuePointer = this.module.cwrap("QTS_GetOwnPropertyNames", "number", [ + "number", + "number", + "number", + "number", + "number", + ]) + + QTS_Call: ( + ctx: JSContextPointer, + func_obj: JSValuePointer | JSValueConstPointer, + this_obj: JSValuePointer | JSValueConstPointer, + argc: number, + argv_ptrs: JSValueConstPointerPointer, + ) => JSValuePointer = this.module.cwrap("QTS_Call", "number", [ + "number", + "number", + "number", + "number", + "number", + ]) + + QTS_ResolveException: (ctx: JSContextPointer, maybe_exception: JSValuePointer) => JSValuePointer = + this.module.cwrap("QTS_ResolveException", "number", ["number", "number"]) + + QTS_Dump: ( + ctx: JSContextPointer, + obj: JSValuePointer | JSValueConstPointer, + ) => JSBorrowedCharPointer = this.module.cwrap("QTS_Dump", "number", ["number", "number"]) + + QTS_Eval: ( + ctx: JSContextPointer, + js_code: BorrowedHeapCharPointer, + js_code_length: number, + filename: string, + detectModule: EvalDetectModule, + evalFlags: EvalFlags, + ) => JSValuePointer = this.module.cwrap("QTS_Eval", "number", [ + "number", + "number", + "number", + "string", + "number", + "number", + ]) + + QTS_GetModuleNamespace: ( + ctx: JSContextPointer, + module_func_obj: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_GetModuleNamespace", "number", ["number", "number"]) + + QTS_Typeof: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => OwnedHeapCharPointer = this.module.cwrap("QTS_Typeof", "number", ["number", "number"]) + + QTS_GetLength: ( + ctx: JSContextPointer, + out_len: UInt32Pointer, + value: JSValuePointer | JSValueConstPointer, + ) => number = this.module.cwrap("QTS_GetLength", "number", ["number", "number", "number"]) + + QTS_IsEqual: ( + ctx: JSContextPointer, + a: JSValuePointer | JSValueConstPointer, + b: JSValuePointer | JSValueConstPointer, + op: IsEqualOp, + ) => number = this.module.cwrap("QTS_IsEqual", "number", ["number", "number", "number", "number"]) + + QTS_GetGlobalObject: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_GetGlobalObject", + "number", + ["number"], + ) + + QTS_NewPromiseCapability: ( + ctx: JSContextPointer, + resolve_funcs_out: JSValuePointerPointer, + ) => JSValuePointer = this.module.cwrap("QTS_NewPromiseCapability", "number", [ + "number", + "number", + ]) + + QTS_PromiseState: ( + ctx: JSContextPointer, + promise: JSValuePointer | JSValueConstPointer, + ) => JSPromiseStateEnum = this.module.cwrap("QTS_PromiseState", "number", ["number", "number"]) + + QTS_PromiseResult: ( + ctx: JSContextPointer, + promise: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_PromiseResult", "number", ["number", "number"]) + + QTS_TestStringArg: (string: string) => void = this.module.cwrap("QTS_TestStringArg", null, [ + "string", + ]) + + QTS_GetDebugLogEnabled: (rt: JSRuntimePointer) => number = this.module.cwrap( + "QTS_GetDebugLogEnabled", + "number", + ["number"], + ) + + QTS_SetDebugLogEnabled: (rt: JSRuntimePointer, is_enabled: number) => void = this.module.cwrap( + "QTS_SetDebugLogEnabled", + null, + ["number", "number"], + ) + + QTS_BuildIsDebug: () => number = this.module.cwrap("QTS_BuildIsDebug", "number", []) + + QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + + QTS_NewFunction: (ctx: JSContextPointer, func_id: number, name: string) => JSValuePointer = + this.module.cwrap("QTS_NewFunction", "number", ["number", "number", "string"]) + + QTS_ArgvGetJSValueConstPointer: ( + argv: JSValuePointer | JSValueConstPointer, + index: number, + ) => JSValueConstPointer = this.module.cwrap("QTS_ArgvGetJSValueConstPointer", "number", [ + "number", + "number", + ]) + + QTS_RuntimeEnableInterruptHandler: (rt: JSRuntimePointer) => void = this.module.cwrap( + "QTS_RuntimeEnableInterruptHandler", + null, + ["number"], + ) + + QTS_RuntimeDisableInterruptHandler: (rt: JSRuntimePointer) => void = this.module.cwrap( + "QTS_RuntimeDisableInterruptHandler", + null, + ["number"], + ) + + QTS_RuntimeEnableModuleLoader: (rt: JSRuntimePointer, use_custom_normalize: number) => void = + this.module.cwrap("QTS_RuntimeEnableModuleLoader", null, ["number", "number"]) + + QTS_RuntimeDisableModuleLoader: (rt: JSRuntimePointer) => void = this.module.cwrap( + "QTS_RuntimeDisableModuleLoader", + null, + ["number"], + ) + + QTS_bjson_encode: ( + ctx: JSContextPointer, + val: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_bjson_encode", "number", ["number", "number"]) + + QTS_bjson_decode: ( + ctx: JSContextPointer, + data: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_bjson_decode", "number", ["number", "number"]) +} diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts new file mode 100644 index 00000000..5b1d7977 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts @@ -0,0 +1,25 @@ +import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" + +/** + * ### @jitl/mquickjs-wasmfile-debug-sync + * + * [Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md) | + * Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + * + * | Variable | Setting | Description | + * | -- | -- | -- | + * | library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | + * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | + * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | + * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | + * | exports | require import browser workerd | Has these package.json export conditions | + * + */ +const variant: QuickJSSyncVariant = { + type: "sync", + importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), + importModuleLoader: () => + import("@jitl/mquickjs-wasmfile-debug-sync/emscripten-module").then((mod) => mod.default), +} as const + +export default variant diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/tsconfig.json b/packages/variant-mquickjs-wasmfile-debug-sync/tsconfig.json new file mode 100644 index 00000000..3a077e27 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "@jitl/tsconfig/tsconfig.json", "include": ["src/*"], "exclude": ["node_modules"] } diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/tsup.config.ts b/packages/variant-mquickjs-wasmfile-debug-sync/tsup.config.ts new file mode 100644 index 00000000..4978f24e --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/tsup.config.ts @@ -0,0 +1,7 @@ +import { extendConfig } from "@jitl/tsconfig/tsup.base.config.js" +export default extendConfig({ + entry: ["src/index.ts", "src/ffi.ts"], + external: ["@jitl/mquickjs-wasmfile-debug-sync/emscripten-module"], + format: ["esm", "cjs"], + clean: false, +}) diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/typedoc.json b/packages/variant-mquickjs-wasmfile-debug-sync/typedoc.json new file mode 100644 index 00000000..e0f02210 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/typedoc.json @@ -0,0 +1 @@ +{ "extends": "../../typedoc.base.js", "entryPoints": ["./src/index.ts"], "mergeReadme": true } diff --git a/packages/variant-mquickjs-wasmfile-release-sync/LICENSE b/packages/variant-mquickjs-wasmfile-release-sync/LICENSE new file mode 100644 index 00000000..780adab0 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/LICENSE @@ -0,0 +1,47 @@ +quickjs-emscripten: +The MIT License + +quickjs-emscripten copyright (c) 2019-2024 Jake Teton-Landis + +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. + + +mquickjs: +Micro QuickJS Javascript Engine + +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. diff --git a/packages/variant-mquickjs-wasmfile-release-sync/Makefile b/packages/variant-mquickjs-wasmfile-release-sync/Makefile new file mode 100644 index 00000000..aadaa1aa --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/Makefile @@ -0,0 +1,195 @@ +# Tools +EMSDK_VERSION=5.0.1 +EMSDK_DOCKER_IMAGE=emscripten/emsdk:$(EMSDK_VERSION) +EMCC_SRC=../../scripts/emcc.sh +EMCC=EMSDK_VERSION=$(EMSDK_VERSION) EMSDK_DOCKER_IMAGE=$(EMSDK_DOCKER_IMAGE) EMSDK_PROJECT_ROOT=$(REPO_ROOT) EMSDK_DOCKER_CACHE=$(REPO_ROOT)/emsdk-cache/$(EMSDK_VERSION) $(EMCC_SRC) +GENERATE_TS=$(GENERATE_TS_ENV) ../../scripts/generate.ts +THIS_DIR := $(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +REPO_ROOT := $(abspath $(THIS_DIR)/../..) + +QUICKJS_LIB=mquickjs + +# Paths +QUICKJS_ROOT=../../vendor/$(QUICKJS_LIB) +WRAPPER_ROOT=../../c +TEMPLATES=../../templates +# Intermediate build files +BUILD_ROOT=build +BUILD_WRAPPER=$(BUILD_ROOT)/wrapper +BUILD_QUICKJS=$(BUILD_ROOT)/quickjs +# Distributed to users +DIST=dist + +# QuickJS +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) + # quickjs-ng uses different source files than bellard/quickjs + QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c +else + # bellard/quickjs uses separate source files + QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o + QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) + QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c +endif +VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) + +# quickjs-emscripten +WRAPPER_DEFINES+=-Wcast-function-type # Likewise, warns about some quickjs casts we don't control. +EMCC_EXPORTED_FUNCS+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.json +EMCC_EXPORTED_FUNCS_ASYNCIFY+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.asyncify.json + +# Emscripten options +# EXPORTED_RUNTIME_METHODS set below after SYNC is defined +CFLAGS_WASM+=-s MODULARIZE=1 +CFLAGS_WASM+=-s IMPORTED_MEMORY=1 # Allow passing WASM memory to Emscripten +CFLAGS_WASM+=-s EXPORT_NAME=QuickJSRaw +CFLAGS_WASM+=-s INVOKE_RUN=0 +CFLAGS_WASM+=-s ALLOW_MEMORY_GROWTH=1 +CFLAGS_WASM+=-s ALLOW_TABLE_GROWTH=1 +CFLAGS_WASM+=-s STACK_SIZE=5MB +# CFLAGS_WASM+=-s MINIMAL_RUNTIME=1 # Appears to break MODULARIZE +CFLAGS_WASM+=-s SUPPORT_ERRNO=0 + +# Emscripten options - like STRICT +# https://github.com/emscripten-core/emscripten/blob/fa339b76424ca9fbe5cf15faea0295d2ac8d58cc/src/settings.js#L1095-L1109 +# CFLAGS_WASM+=-s STRICT_JS=1 # Doesn't work with MODULARIZE +CFLAGS_WASM+=-s IGNORE_MISSING_MAIN=0 --no-entry +CFLAGS_WASM+=-s AUTO_JS_LIBRARIES=0 +CFLAGS_WASM+=-s -lccall.js +CFLAGS_WASM+=-s AUTO_NATIVE_LIBRARIES=0 +CFLAGS_WASM+=-s AUTO_ARCHIVE_INDEXES=0 +CFLAGS_WASM+=-s DEFAULT_TO_CXX=0 +CFLAGS_WASM+=-s ALLOW_UNIMPLEMENTED_SYSCALLS=0 + +# Emscripten options - NodeJS +CFLAGS_WASM+=-s MIN_NODE_VERSION=160000 +CFLAGS_WASM+=-s NODEJS_CATCH_EXIT=0 + +CFLAGS_MJS+=-s EXPORT_ES6=1 +CFLAGS_BROWSER+=-s EXPORT_ES6=1 + +# VARIANT +SYNC=SYNC + +# Set EXPORTED_RUNTIME_METHODS based on sync mode (Asyncify only available in asyncify builds) +ifeq ($(SYNC),ASYNCIFY) +CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.asyncify.json +else +CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json +endif + +CFLAGS_WASM_BROWSER=$(CFLAGS_WASM) + +# Emscripten options - variant & target specific +CFLAGS_WASM+=-Oz +CFLAGS_WASM+=-flto +CFLAGS_WASM+=--closure 1 +CFLAGS_WASM+=-s FILESYSTEM=0 +CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-extension.js +CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-wasmMemory.js +CFLAGS_CJS+=-s ENVIRONMENT=node +CFLAGS_MJS+=-s ENVIRONMENT=node +CFLAGS_BROWSER+=-s ENVIRONMENT=web,worker +CFLAGS_CLOUDFLARE+=-s ENVIRONMENT=web + +# GENERATE_TS options - variant specific + + + +ifdef DEBUG_MAKE + MKDIRP=@echo "\n=====[["" target: $@, deps: $<, variant: $(VARIANT) ""]]=====" ; mkdir -p $(dir $@) +else + MKDIRP=@mkdir -p $(dir $@) + CFLAGS+=-Wunused-command-line-argument=0 +endif + +############################################################################### +# High-level +all: EXPORTS + +clean: + git clean -fx $(DIST) $(BUILD_ROOT) + +############################################################################### +# Emscripten output targets +EXPORTS: BROWSER MJS CJS CLOUDFLARE +CJS: $(DIST)/emscripten-module.cjs $(DIST)/emscripten-module.d.ts +MJS: $(DIST)/emscripten-module.mjs $(DIST)/emscripten-module.d.ts +BROWSER: $(DIST)/emscripten-module.browser.mjs $(DIST)/emscripten-module.browser.d.ts +CLOUDFLARE: $(DIST)/emscripten-module.cloudflare.cjs $(DIST)/emscripten-module.cloudflare.d.ts + +$(DIST)/emscripten-module.mjs: CFLAGS_WASM+=$(CFLAGS_ESM) +$(DIST)/emscripten-module.mjs: $(BUILD_WRAPPER)/interface.o $(VARIANT_QUICKJS_OBJS) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) $(EMCC_EXPORTED_FUNCS) -o $@ $< $(VARIANT_QUICKJS_OBJS) + +$(DIST)/emscripten-module.cjs: CFLAGS_WASM+=$(CFLAGS_CJS) +$(DIST)/emscripten-module.cjs: $(BUILD_WRAPPER)/interface.o $(VARIANT_QUICKJS_OBJS) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) $(EMCC_EXPORTED_FUNCS) -o $@ $< $(VARIANT_QUICKJS_OBJS) + +$(DIST)/emscripten-module.d.ts: $(TEMPLATES)/emscripten-module.$(SYNC).d.ts + $(MKDIRP) + echo '// Generated from $<' > $@ + cat $< >> $@ + +$(DIST)/emscripten-module%.d.ts: $(TEMPLATES)/emscripten-module.$(SYNC).d.ts + $(MKDIRP) + echo '// Generated from $<' > $@ + cat $< >> $@ + +# Browser target needs intermediate build to avoid two copies of .wasm +$(DIST)/emscripten-module.%.mjs: $(BUILD_WRAPPER)/%/emscripten-module.js + $(MKDIRP) + if [ -e $(basename $<).wasm ] ; then cp -v $(basename $<).wasm* $(dir $@); fi + cp $< $@ + +$(DIST)/emscripten-module.%.cjs: $(BUILD_WRAPPER)/%/emscripten-module.js + $(MKDIRP) + if [ -e $(basename $<).wasm ] ; then cp -v $(basename $<).wasm* $(dir $@); fi + cp $< $@ + +$(BUILD_WRAPPER)/browser/emscripten-module.js: CFLAGS_WASM+=$(CFLAGS_BROWSER) +$(BUILD_WRAPPER)/cloudflare/emscripten-module.js: CFLAGS_WASM+=$(CFLAGS_CLOUDFLARE) +$(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_QUICKJS_OBJS) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) $(EMCC_EXPORTED_FUNCS) -o $@ $< $(VARIANT_QUICKJS_OBJS) + +############################################################################### +# Emscripten intermediate files +WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files +$(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +$(BUILD_QUICKJS)/%.o: $(QUICKJS_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(QUICKJS_DEFINES) -c -o $@ $< + +$(BUILD_WRAPPER)/symbols.json: + $(MKDIRP) + $(GENERATE_TS) symbols $@ + +$(BUILD_WRAPPER)/asyncify-remove.json: + $(MKDIRP) + $(GENERATE_TS) sync-symbols $@ + +$(BUILD_WRAPPER)/asyncify-imports.json: + $(MKDIRP) + $(GENERATE_TS) async-callback-symbols $@ diff --git a/packages/variant-mquickjs-wasmfile-release-sync/README.md b/packages/variant-mquickjs-wasmfile-release-sync/README.md new file mode 100644 index 00000000..42f1236a --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/README.md @@ -0,0 +1,82 @@ +# @jitl/mquickjs-wasmfile-release-sync + +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +This generated package is part of [quickjs-emscripten](https://github.com/justjake/quickjs-emscripten). +It contains a variant of the quickjs WASM library, and can be used with quickjs-emscripten-core. + +```typescript +import variant from "@jitl/mquickjs-wasmfile-release-sync" +import { newQuickJSWASMModuleFromVariant } from "quickjs-emscripten-core" +const QuickJS = await newQuickJSWASMModuleFromVariant(variant) +``` + +This variant was built with the following settings: + +## Library: mquickjs + +[mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. + +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. + +## Release mode: release + +Optimized for performance; use when building/deploying your application. + +## Exports: require import browser workerd + +Exports the following in package.json for the package entrypoint: + +- Exports a NodeJS-compatible CommonJS module, which is faster to load and run compared to an ESModule. +- Exports a NodeJS-compatible ESModule. Cannot be imported synchronously from a NodeJS CommonJS module. +- Exports a browser-compatible ESModule, designed to work in browsers and browser-like environments. +- Targets Cloudflare Workers. + +## Extra async magic? No + +The default, normal build. Note that both variants support regular async functions. + +## Single-file, or separate .wasm file? wasm + +Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. + +## More details + +Full variant JSON description: + +```json +{ + "library": "mquickjs", + "releaseMode": "release", + "syncMode": "sync", + "description": "Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "emscriptenInclusion": "wasm", + "exports": { + "require": { + "emscriptenEnvironment": ["node"] + }, + "import": { + "emscriptenEnvironment": ["node"] + }, + "browser": { + "emscriptenEnvironment": ["web", "worker"] + }, + "workerd": { + "emscriptenEnvironment": ["web"] + } + } +} +``` + +Variant-specific Emscripten build flags: + +```json +[ + "-Oz", + "-flto", + "--closure 1", + "-s FILESYSTEM=0", + "--pre-js $(TEMPLATES)/pre-extension.js", + "--pre-js $(TEMPLATES)/pre-wasmMemory.js" +] +``` diff --git a/packages/variant-mquickjs-wasmfile-release-sync/package.json b/packages/variant-mquickjs-wasmfile-release-sync/package.json new file mode 100644 index 00000000..1e331f2c --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/package.json @@ -0,0 +1,61 @@ +{ + "name": "@jitl/mquickjs-wasmfile-release-sync", + "license": "MIT", + "version": "0.31.0", + "description": "Variant of quickjs library: Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "sideEffects": false, + "repository": { + "type": "git", + "url": "https://github.com/justjake/quickjs-emscripten" + }, + "author": { + "name": "Jake Teton-Landis", + "url": "https://jake.tl" + }, + "scripts": { + "build": "yarn build:c && yarn build:ts", + "build:c": "make", + "build:ts": "npx tsup", + "check:types": "npx tsc --project . --noEmit", + "clean": "make clean", + "prepare": "yarn clean && yarn build" + }, + "files": [ + "LICENSE", + "README.md", + "dist/**/*", + "!dist/*.tsbuildinfo" + ], + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "browser": "./dist/index.mjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "default": "./dist/index.js" + }, + "./package.json": "./package.json", + "./ffi": { + "types": "./dist/ffi.d.ts", + "import": "./dist/ffi.mjs", + "require": "./dist/ffi.js", + "default": "./dist/ffi.js" + }, + "./wasm": "./dist/emscripten-module.wasm", + "./emscripten-module": { + "types": "./dist/emscripten-module.browser.d.ts", + "iife": "./dist/emscripten-module.cjs", + "workerd": "./dist/emscripten-module.cloudflare.cjs", + "browser": "./dist/emscripten-module.browser.mjs", + "import": "./dist/emscripten-module.mjs", + "require": "./dist/emscripten-module.cjs", + "default": "./dist/emscripten-module.cjs" + } + }, + "dependencies": { + "@jitl/quickjs-ffi-types": "workspace:*" + } +} diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts new file mode 100644 index 00000000..86932db9 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts @@ -0,0 +1,435 @@ +// This file generated by "generate.ts ffi" in the root of the repo. +import { + QuickJSEmscriptenModule, + JSRuntimePointer, + JSContextPointer, + JSContextPointerPointer, + JSModuleDefPointer, + JSValuePointer, + JSValueConstPointer, + JSValuePointerPointer, + JSValuePointerPointerPointer, + JSValueConstPointerPointer, + QTS_C_To_HostCallbackFuncPointer, + QTS_C_To_HostInterruptFuncPointer, + QTS_C_To_HostLoadModuleFuncPointer, + BorrowedHeapCharPointer, + OwnedHeapCharPointer, + JSBorrowedCharPointer, + JSVoidPointer, + UInt32Pointer, + EvalFlags, + IntrinsicsFlags, + EvalDetectModule, + GetOwnPropertyNamesFlags, + IsEqualOp, + JSPromiseStateEnum, +} from "@jitl/quickjs-ffi-types" + +/** + * Low-level FFI bindings to QuickJS's Emscripten module. + * See instead {@link QuickJSContext}, the public Javascript interface exposed by this + * library. + * + * @unstable The FFI interface is considered private and may change. + */ +export class QuickJSFFI { + constructor(private module: QuickJSEmscriptenModule) {} + /** Set at compile time. */ + readonly DEBUG = false + + QTS_Throw: ( + ctx: JSContextPointer, + error: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_Throw", "number", ["number", "number"]) + + QTS_NewError: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_NewError", + "number", + ["number"], + ) + + QTS_RuntimeSetMemoryLimit: (rt: JSRuntimePointer, limit: number) => void = this.module.cwrap( + "QTS_RuntimeSetMemoryLimit", + null, + ["number", "number"], + ) + + QTS_RuntimeComputeMemoryUsage: (rt: JSRuntimePointer, ctx: JSContextPointer) => JSValuePointer = + this.module.cwrap("QTS_RuntimeComputeMemoryUsage", "number", ["number", "number"]) + + QTS_RuntimeDumpMemoryUsage: (rt: JSRuntimePointer) => OwnedHeapCharPointer = this.module.cwrap( + "QTS_RuntimeDumpMemoryUsage", + "number", + ["number"], + ) + + QTS_RecoverableLeakCheck: () => number = this.module.cwrap( + "QTS_RecoverableLeakCheck", + "number", + [], + ) + + QTS_BuildIsSanitizeLeak: () => number = this.module.cwrap("QTS_BuildIsSanitizeLeak", "number", []) + + QTS_RuntimeSetMaxStackSize: (rt: JSRuntimePointer, stack_size: number) => void = + this.module.cwrap("QTS_RuntimeSetMaxStackSize", null, ["number", "number"]) + + QTS_GetUndefined: () => JSValueConstPointer = this.module.cwrap("QTS_GetUndefined", "number", []) + + QTS_GetNull: () => JSValueConstPointer = this.module.cwrap("QTS_GetNull", "number", []) + + QTS_GetFalse: () => JSValueConstPointer = this.module.cwrap("QTS_GetFalse", "number", []) + + QTS_GetTrue: () => JSValueConstPointer = this.module.cwrap("QTS_GetTrue", "number", []) + + QTS_NewRuntime: () => JSRuntimePointer = this.module.cwrap("QTS_NewRuntime", "number", []) + + QTS_FreeRuntime: (rt: JSRuntimePointer) => void = this.module.cwrap("QTS_FreeRuntime", null, [ + "number", + ]) + + QTS_NewContext: (rt: JSRuntimePointer, intrinsics: IntrinsicsFlags) => JSContextPointer = + this.module.cwrap("QTS_NewContext", "number", ["number", "number"]) + + QTS_FreeContext: (ctx: JSContextPointer) => void = this.module.cwrap("QTS_FreeContext", null, [ + "number", + ]) + + QTS_FreeValuePointer: (ctx: JSContextPointer, value: JSValuePointer) => void = this.module.cwrap( + "QTS_FreeValuePointer", + null, + ["number", "number"], + ) + + QTS_FreeValuePointerRuntime: (rt: JSRuntimePointer, value: JSValuePointer) => void = + this.module.cwrap("QTS_FreeValuePointerRuntime", null, ["number", "number"]) + + QTS_FreeVoidPointer: (ctx: JSContextPointer, ptr: JSVoidPointer) => void = this.module.cwrap( + "QTS_FreeVoidPointer", + null, + ["number", "number"], + ) + + QTS_FreeCString: (ctx: JSContextPointer, str: JSBorrowedCharPointer) => void = this.module.cwrap( + "QTS_FreeCString", + null, + ["number", "number"], + ) + + QTS_DupValuePointer: ( + ctx: JSContextPointer, + val: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_DupValuePointer", "number", ["number", "number"]) + + QTS_NewObject: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_NewObject", + "number", + ["number"], + ) + + QTS_NewObjectProto: ( + ctx: JSContextPointer, + proto: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_NewObjectProto", "number", ["number", "number"]) + + QTS_NewArray: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_NewArray", + "number", + ["number"], + ) + + QTS_NewArrayBuffer: ( + ctx: JSContextPointer, + buffer: JSVoidPointer, + length: number, + ) => JSValuePointer = this.module.cwrap("QTS_NewArrayBuffer", "number", [ + "number", + "number", + "number", + ]) + + QTS_NewFloat64: (ctx: JSContextPointer, num: number) => JSValuePointer = this.module.cwrap( + "QTS_NewFloat64", + "number", + ["number", "number"], + ) + + QTS_GetFloat64: (ctx: JSContextPointer, value: JSValuePointer | JSValueConstPointer) => number = + this.module.cwrap("QTS_GetFloat64", "number", ["number", "number"]) + + QTS_NewString: (ctx: JSContextPointer, string: BorrowedHeapCharPointer) => JSValuePointer = + this.module.cwrap("QTS_NewString", "number", ["number", "number"]) + + QTS_GetString: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => JSBorrowedCharPointer = this.module.cwrap("QTS_GetString", "number", ["number", "number"]) + + QTS_GetArrayBuffer: ( + ctx: JSContextPointer, + data: JSValuePointer | JSValueConstPointer, + ) => JSVoidPointer = this.module.cwrap("QTS_GetArrayBuffer", "number", ["number", "number"]) + + QTS_GetArrayBufferLength: ( + ctx: JSContextPointer, + data: JSValuePointer | JSValueConstPointer, + ) => number = this.module.cwrap("QTS_GetArrayBufferLength", "number", ["number", "number"]) + + QTS_NewSymbol: ( + ctx: JSContextPointer, + description: BorrowedHeapCharPointer, + isGlobal: number, + ) => JSValuePointer = this.module.cwrap("QTS_NewSymbol", "number", ["number", "number", "number"]) + + QTS_GetSymbolDescriptionOrKey: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => JSBorrowedCharPointer = this.module.cwrap("QTS_GetSymbolDescriptionOrKey", "number", [ + "number", + "number", + ]) + + QTS_IsGlobalSymbol: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => number = this.module.cwrap("QTS_IsGlobalSymbol", "number", ["number", "number"]) + + QTS_IsJobPending: (rt: JSRuntimePointer) => number = this.module.cwrap( + "QTS_IsJobPending", + "number", + ["number"], + ) + + QTS_ExecutePendingJob: ( + rt: JSRuntimePointer, + maxJobsToExecute: number, + lastJobContext: JSContextPointerPointer, + ) => JSValuePointer = this.module.cwrap("QTS_ExecutePendingJob", "number", [ + "number", + "number", + "number", + ]) + + QTS_GetProp: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_GetProp", "number", ["number", "number", "number"]) + + QTS_GetPropNumber: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: number, + ) => JSValuePointer = this.module.cwrap("QTS_GetPropNumber", "number", [ + "number", + "number", + "number", + ]) + + QTS_SetProp: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: JSValuePointer | JSValueConstPointer, + prop_value: JSValuePointer | JSValueConstPointer, + ) => void = this.module.cwrap("QTS_SetProp", null, ["number", "number", "number", "number"]) + + QTS_DefineProp: ( + ctx: JSContextPointer, + this_val: JSValuePointer | JSValueConstPointer, + prop_name: JSValuePointer | JSValueConstPointer, + prop_value: JSValuePointer | JSValueConstPointer, + get: JSValuePointer | JSValueConstPointer, + set: JSValuePointer | JSValueConstPointer, + configurable: boolean, + enumerable: boolean, + has_value: boolean, + ) => void = this.module.cwrap("QTS_DefineProp", null, [ + "number", + "number", + "number", + "number", + "number", + "number", + "boolean", + "boolean", + "boolean", + ]) + + QTS_GetOwnPropertyNames: ( + ctx: JSContextPointer, + out_ptrs: JSValuePointerPointerPointer, + out_len: UInt32Pointer, + obj: JSValuePointer | JSValueConstPointer, + flags: number, + ) => JSValuePointer = this.module.cwrap("QTS_GetOwnPropertyNames", "number", [ + "number", + "number", + "number", + "number", + "number", + ]) + + QTS_Call: ( + ctx: JSContextPointer, + func_obj: JSValuePointer | JSValueConstPointer, + this_obj: JSValuePointer | JSValueConstPointer, + argc: number, + argv_ptrs: JSValueConstPointerPointer, + ) => JSValuePointer = this.module.cwrap("QTS_Call", "number", [ + "number", + "number", + "number", + "number", + "number", + ]) + + QTS_ResolveException: (ctx: JSContextPointer, maybe_exception: JSValuePointer) => JSValuePointer = + this.module.cwrap("QTS_ResolveException", "number", ["number", "number"]) + + QTS_Dump: ( + ctx: JSContextPointer, + obj: JSValuePointer | JSValueConstPointer, + ) => JSBorrowedCharPointer = this.module.cwrap("QTS_Dump", "number", ["number", "number"]) + + QTS_Eval: ( + ctx: JSContextPointer, + js_code: BorrowedHeapCharPointer, + js_code_length: number, + filename: string, + detectModule: EvalDetectModule, + evalFlags: EvalFlags, + ) => JSValuePointer = this.module.cwrap("QTS_Eval", "number", [ + "number", + "number", + "number", + "string", + "number", + "number", + ]) + + QTS_GetModuleNamespace: ( + ctx: JSContextPointer, + module_func_obj: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_GetModuleNamespace", "number", ["number", "number"]) + + QTS_Typeof: ( + ctx: JSContextPointer, + value: JSValuePointer | JSValueConstPointer, + ) => OwnedHeapCharPointer = this.module.cwrap("QTS_Typeof", "number", ["number", "number"]) + + QTS_GetLength: ( + ctx: JSContextPointer, + out_len: UInt32Pointer, + value: JSValuePointer | JSValueConstPointer, + ) => number = this.module.cwrap("QTS_GetLength", "number", ["number", "number", "number"]) + + QTS_IsEqual: ( + ctx: JSContextPointer, + a: JSValuePointer | JSValueConstPointer, + b: JSValuePointer | JSValueConstPointer, + op: IsEqualOp, + ) => number = this.module.cwrap("QTS_IsEqual", "number", ["number", "number", "number", "number"]) + + QTS_GetGlobalObject: (ctx: JSContextPointer) => JSValuePointer = this.module.cwrap( + "QTS_GetGlobalObject", + "number", + ["number"], + ) + + QTS_NewPromiseCapability: ( + ctx: JSContextPointer, + resolve_funcs_out: JSValuePointerPointer, + ) => JSValuePointer = this.module.cwrap("QTS_NewPromiseCapability", "number", [ + "number", + "number", + ]) + + QTS_PromiseState: ( + ctx: JSContextPointer, + promise: JSValuePointer | JSValueConstPointer, + ) => JSPromiseStateEnum = this.module.cwrap("QTS_PromiseState", "number", ["number", "number"]) + + QTS_PromiseResult: ( + ctx: JSContextPointer, + promise: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_PromiseResult", "number", ["number", "number"]) + + QTS_TestStringArg: (string: string) => void = this.module.cwrap("QTS_TestStringArg", null, [ + "string", + ]) + + QTS_GetDebugLogEnabled: (rt: JSRuntimePointer) => number = this.module.cwrap( + "QTS_GetDebugLogEnabled", + "number", + ["number"], + ) + + QTS_SetDebugLogEnabled: (rt: JSRuntimePointer, is_enabled: number) => void = this.module.cwrap( + "QTS_SetDebugLogEnabled", + null, + ["number", "number"], + ) + + QTS_BuildIsDebug: () => number = this.module.cwrap("QTS_BuildIsDebug", "number", []) + + QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + + QTS_NewFunction: (ctx: JSContextPointer, func_id: number, name: string) => JSValuePointer = + this.module.cwrap("QTS_NewFunction", "number", ["number", "number", "string"]) + + QTS_ArgvGetJSValueConstPointer: ( + argv: JSValuePointer | JSValueConstPointer, + index: number, + ) => JSValueConstPointer = this.module.cwrap("QTS_ArgvGetJSValueConstPointer", "number", [ + "number", + "number", + ]) + + QTS_RuntimeEnableInterruptHandler: (rt: JSRuntimePointer) => void = this.module.cwrap( + "QTS_RuntimeEnableInterruptHandler", + null, + ["number"], + ) + + QTS_RuntimeDisableInterruptHandler: (rt: JSRuntimePointer) => void = this.module.cwrap( + "QTS_RuntimeDisableInterruptHandler", + null, + ["number"], + ) + + QTS_RuntimeEnableModuleLoader: (rt: JSRuntimePointer, use_custom_normalize: number) => void = + this.module.cwrap("QTS_RuntimeEnableModuleLoader", null, ["number", "number"]) + + QTS_RuntimeDisableModuleLoader: (rt: JSRuntimePointer) => void = this.module.cwrap( + "QTS_RuntimeDisableModuleLoader", + null, + ["number"], + ) + + QTS_bjson_encode: ( + ctx: JSContextPointer, + val: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_bjson_encode", "number", ["number", "number"]) + + QTS_bjson_decode: ( + ctx: JSContextPointer, + data: JSValuePointer | JSValueConstPointer, + ) => JSValuePointer = this.module.cwrap("QTS_bjson_decode", "number", ["number", "number"]) +} diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts new file mode 100644 index 00000000..ac6ba8bf --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts @@ -0,0 +1,25 @@ +import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" + +/** + * ### @jitl/mquickjs-wasmfile-release-sync + * + * [Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-release-sync/README.md) | + * Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + * + * | Variable | Setting | Description | + * | -- | -- | -- | + * | library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | + * | releaseMode | release | Optimized for performance; use when building/deploying your application. | + * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | + * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | + * | exports | require import browser workerd | Has these package.json export conditions | + * + */ +const variant: QuickJSSyncVariant = { + type: "sync", + importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), + importModuleLoader: () => + import("@jitl/mquickjs-wasmfile-release-sync/emscripten-module").then((mod) => mod.default), +} as const + +export default variant diff --git a/packages/variant-mquickjs-wasmfile-release-sync/tsconfig.json b/packages/variant-mquickjs-wasmfile-release-sync/tsconfig.json new file mode 100644 index 00000000..3a077e27 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "@jitl/tsconfig/tsconfig.json", "include": ["src/*"], "exclude": ["node_modules"] } diff --git a/packages/variant-mquickjs-wasmfile-release-sync/tsup.config.ts b/packages/variant-mquickjs-wasmfile-release-sync/tsup.config.ts new file mode 100644 index 00000000..5dce3022 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/tsup.config.ts @@ -0,0 +1,7 @@ +import { extendConfig } from "@jitl/tsconfig/tsup.base.config.js" +export default extendConfig({ + entry: ["src/index.ts", "src/ffi.ts"], + external: ["@jitl/mquickjs-wasmfile-release-sync/emscripten-module"], + format: ["esm", "cjs"], + clean: false, +}) diff --git a/packages/variant-mquickjs-wasmfile-release-sync/typedoc.json b/packages/variant-mquickjs-wasmfile-release-sync/typedoc.json new file mode 100644 index 00000000..e0f02210 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/typedoc.json @@ -0,0 +1 @@ +{ "extends": "../../typedoc.base.js", "entryPoints": ["./src/index.ts"], "mergeReadme": true } diff --git a/variants.json b/variants.json index 22c1d594..f5e12a99 100644 --- a/variants.json +++ b/variants.json @@ -527,6 +527,138 @@ } } }, + "packages/variant-mquickjs-wasmfile-debug-sync": { + "basename": "mquickjs-wasmfile-debug-sync", + "targetName": "wasmfile", + "dir": "packages/variant-mquickjs-wasmfile-debug-sync", + "packageJson": { + "name": "@jitl/mquickjs-wasmfile-debug-sync", + "license": "MIT", + "version": "(omitted)", + "description": "Variant of quickjs library: Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "sideEffects": false, + "repository": { "type": "git", "url": "https://github.com/justjake/quickjs-emscripten" }, + "author": { "name": "Jake Teton-Landis", "url": "https://jake.tl" }, + "scripts": { + "build": "yarn build:c && yarn build:ts", + "build:c": "make", + "build:ts": "npx tsup", + "check:types": "npx tsc --project . --noEmit", + "clean": "make clean", + "prepare": "yarn clean && yarn build" + }, + "files": ["LICENSE", "README.md", "dist/**/*", "!dist/*.tsbuildinfo"], + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "browser": "./dist/index.mjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "default": "./dist/index.js" + }, + "./package.json": "./package.json", + "./ffi": { + "types": "./dist/ffi.d.ts", + "import": "./dist/ffi.mjs", + "require": "./dist/ffi.js", + "default": "./dist/ffi.js" + }, + "./wasm": "./dist/emscripten-module.wasm", + "./emscripten-module": { + "types": "./dist/emscripten-module.browser.d.ts", + "iife": "./dist/emscripten-module.cjs", + "workerd": "./dist/emscripten-module.cloudflare.cjs", + "browser": "./dist/emscripten-module.browser.mjs", + "import": "./dist/emscripten-module.mjs", + "require": "./dist/emscripten-module.cjs", + "default": "./dist/emscripten-module.cjs" + } + }, + "dependencies": { "@jitl/quickjs-ffi-types": "workspace:*" } + }, + "variant": { + "library": "mquickjs", + "releaseMode": "debug", + "syncMode": "sync", + "description": "Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "emscriptenInclusion": "wasm", + "exports": { + "require": { "emscriptenEnvironment": ["node"] }, + "import": { "emscriptenEnvironment": ["node"] }, + "browser": { "emscriptenEnvironment": ["web", "worker"] }, + "workerd": { "emscriptenEnvironment": ["web"] } + } + } + }, + "packages/variant-mquickjs-wasmfile-release-sync": { + "basename": "mquickjs-wasmfile-release-sync", + "targetName": "wasmfile", + "dir": "packages/variant-mquickjs-wasmfile-release-sync", + "packageJson": { + "name": "@jitl/mquickjs-wasmfile-release-sync", + "license": "MIT", + "version": "(omitted)", + "description": "Variant of quickjs library: Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "sideEffects": false, + "repository": { "type": "git", "url": "https://github.com/justjake/quickjs-emscripten" }, + "author": { "name": "Jake Teton-Landis", "url": "https://jake.tl" }, + "scripts": { + "build": "yarn build:c && yarn build:ts", + "build:c": "make", + "build:ts": "npx tsup", + "check:types": "npx tsc --project . --noEmit", + "clean": "make clean", + "prepare": "yarn clean && yarn build" + }, + "files": ["LICENSE", "README.md", "dist/**/*", "!dist/*.tsbuildinfo"], + "types": "./dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "browser": "./dist/index.mjs", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "default": "./dist/index.js" + }, + "./package.json": "./package.json", + "./ffi": { + "types": "./dist/ffi.d.ts", + "import": "./dist/ffi.mjs", + "require": "./dist/ffi.js", + "default": "./dist/ffi.js" + }, + "./wasm": "./dist/emscripten-module.wasm", + "./emscripten-module": { + "types": "./dist/emscripten-module.browser.d.ts", + "iife": "./dist/emscripten-module.cjs", + "workerd": "./dist/emscripten-module.cloudflare.cjs", + "browser": "./dist/emscripten-module.browser.mjs", + "import": "./dist/emscripten-module.mjs", + "require": "./dist/emscripten-module.cjs", + "default": "./dist/emscripten-module.cjs" + } + }, + "dependencies": { "@jitl/quickjs-ffi-types": "workspace:*" } + }, + "variant": { + "library": "mquickjs", + "releaseMode": "release", + "syncMode": "sync", + "description": "Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "emscriptenInclusion": "wasm", + "exports": { + "require": { "emscriptenEnvironment": ["node"] }, + "import": { "emscriptenEnvironment": ["node"] }, + "browser": { "emscriptenEnvironment": ["web", "worker"] }, + "workerd": { "emscriptenEnvironment": ["web"] } + } + } + }, "packages/variant-quickjs-singlefile-cjs-debug-sync": { "basename": "quickjs-singlefile-cjs-debug-sync", "targetName": "singlefile-cjs", From cdcccea4932a1c7ba4510928a847b8fac4c421f1 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Sun, 15 Feb 2026 16:15:14 -0500 Subject: [PATCH 09/16] Add JS_GetContextOpaque to mquickjs and rebuild all variants mquickjs was missing JS_GetContextOpaque function which is needed by quickjs-emscripten to store runtime data in the context. Added the function to both mquickjs.h and mquickjs.c. Also generates mquickjs_atom.h which is required for building mquickjs. All variants rebuilt with feature detection support (QTS_Has*Support). Co-Authored-By: Claude Opus 4.5 --- .../mquickjs-wasmfile-debug-sync/README.md | 105 +++++++++++++++++ .../mquickjs-wasmfile-debug-sync/exports.md | 40 +++++++ .../mquickjs-wasmfile-release-sync/README.md | 99 ++++++++++++++++ .../mquickjs-wasmfile-release-sync/exports.md | 40 +++++++ doc/packages.md | 2 + doc/quickjs-emscripten-core/README.md | 28 +++++ .../classes/QuickJSFeatures.md | 79 +++++++++++++ .../classes/TestQuickJSWASMModule.md | 34 ++++-- .../interfaces/AsyncRuntimeOptions.md | 16 +-- .../interfaces/ContextEvalOptions.md | 10 +- .../interfaces/ContextOptions.md | 2 +- .../interfaces/JSModuleLoader.md | 2 +- .../interfaces/JSModuleLoaderAsync.md | 2 +- .../interfaces/JSModuleNormalizer.md | 4 +- .../interfaces/JSModuleNormalizerAsync.md | 2 +- .../interfaces/RuntimeOptions.md | 16 +-- .../interfaces/RuntimeOptionsBase.md | 14 +-- .../namespaces/errors/README.md | 1 + .../errors/classes/QuickJSUnsupported.md | 111 ++++++++++++++++++ .../classes/QuickJSFeatures.md | 79 +++++++++++++ .../classes/TestQuickJSWASMModule.md | 34 ++++-- .../interfaces/AsyncRuntimeOptions.md | 16 +-- .../interfaces/ContextEvalOptions.md | 10 +- .../interfaces/ContextOptions.md | 2 +- .../interfaces/JSModuleLoader.md | 2 +- .../interfaces/JSModuleLoaderAsync.md | 2 +- .../interfaces/JSModuleNormalizer.md | 4 +- .../interfaces/JSModuleNormalizerAsync.md | 2 +- .../interfaces/RuntimeOptions.md | 16 +-- .../interfaces/RuntimeOptionsBase.md | 14 +-- .../namespaces/errors/README.md | 1 + .../errors/classes/QuickJSUnsupported.md | 111 ++++++++++++++++++ .../0001-add-JS_GetContextOpaque.patch | 28 +++++ yarn.lock | 16 +++ 34 files changed, 858 insertions(+), 86 deletions(-) create mode 100644 doc/@jitl/mquickjs-wasmfile-debug-sync/README.md create mode 100644 doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md create mode 100644 doc/@jitl/mquickjs-wasmfile-release-sync/README.md create mode 100644 doc/@jitl/mquickjs-wasmfile-release-sync/exports.md create mode 100644 doc/quickjs-emscripten-core/classes/QuickJSFeatures.md create mode 100644 doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md create mode 100644 doc/quickjs-emscripten/classes/QuickJSFeatures.md create mode 100644 doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md create mode 100644 vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch diff --git a/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md b/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md new file mode 100644 index 00000000..c3235fcc --- /dev/null +++ b/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md @@ -0,0 +1,105 @@ +[quickjs-emscripten](../../packages.md) • **@jitl/mquickjs-wasmfile-debug-sync** • [Readme](README.md) \| [Exports](exports.md) + +*** + +# @jitl/mquickjs-wasmfile-debug-sync + +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +This generated package is part of [quickjs-emscripten](https://github.com/justjake/quickjs-emscripten). +It contains a variant of the quickjs WASM library, and can be used with quickjs-emscripten-core. + +```typescript +import variant from "@jitl/mquickjs-wasmfile-debug-sync" +import { newQuickJSWASMModuleFromVariant } from "quickjs-emscripten-core" +const QuickJS = await newQuickJSWASMModuleFromVariant(variant) +``` + +This variant was built with the following settings: + +## Contents + +- [Library: mquickjs](README.md#library-mquickjs) +- [Release mode: debug](README.md#release-mode-debug) +- [Exports: require import browser workerd](README.md#exports-require-import-browser-workerd) +- [Extra async magic? No](README.md#extra-async-magic-no) +- [Single-file, or separate .wasm file? wasm](README.md#single-file-or-separate-wasm-file-wasm) +- [More details](README.md#more-details) + +## Library: mquickjs + +[mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. + +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. + +## Release mode: debug + +Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. + +## Exports: require import browser workerd + +Exports the following in package.json for the package entrypoint: + +- Exports a NodeJS-compatible CommonJS module, which is faster to load and run compared to an ESModule. +- Exports a NodeJS-compatible ESModule. Cannot be imported synchronously from a NodeJS CommonJS module. +- Exports a browser-compatible ESModule, designed to work in browsers and browser-like environments. +- Targets Cloudflare Workers. + +## Extra async magic? No + +The default, normal build. Note that both variants support regular async functions. + +## Single-file, or separate .wasm file? wasm + +Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. + +## More details + +Full variant JSON description: + +```json +{ + "library": "mquickjs", + "releaseMode": "debug", + "syncMode": "sync", + "description": "Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "emscriptenInclusion": "wasm", + "exports": { + "require": { + "emscriptenEnvironment": ["node"] + }, + "import": { + "emscriptenEnvironment": ["node"] + }, + "browser": { + "emscriptenEnvironment": ["web", "worker"] + }, + "workerd": { + "emscriptenEnvironment": ["web"] + } + } +} +``` + +Variant-specific Emscripten build flags: + +```json +[ + "-O0", + "-DQTS_DEBUG_MODE", + "-DDUMP_LEAKS=1", + "-gsource-map", + "-s ASSERTIONS=1", + "--pre-js $(TEMPLATES)/pre-extension.js", + "--pre-js $(TEMPLATES)/pre-sourceMapJson.js", + "--pre-js $(TEMPLATES)/pre-wasmOffsetConverter.js", + "--pre-js $(TEMPLATES)/pre-wasmMemory.js", + "-DQTS_SANITIZE_LEAK", + "-fsanitize=leak", + "-g2" +] +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md b/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md new file mode 100644 index 00000000..513b2819 --- /dev/null +++ b/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md @@ -0,0 +1,40 @@ +[quickjs-emscripten](../../packages.md) • **@jitl/mquickjs-wasmfile-debug-sync** • [Readme](README.md) \| [Exports](exports.md) + +*** + +[quickjs-emscripten](../../packages.md) / @jitl/mquickjs-wasmfile-debug-sync + +# @jitl/mquickjs-wasmfile-debug-sync + +## Contents + +- [Variables](exports.md#variables) + - [default](exports.md#default) + - [@jitl/mquickjs-wasmfile-debug-sync](exports.md#jitlmquickjs-wasmfile-debug-sync) + +## Variables + +### default + +> **`const`** **default**: [`QuickJSSyncVariant`](../../quickjs-emscripten/interfaces/QuickJSSyncVariant.md) + +### @jitl/mquickjs-wasmfile-debug-sync + +[Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md) | +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +| Variable | Setting | Description | +| -- | -- | -- | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | +| syncMode | sync | The default, normal build. Note that both variants support regular async functions. | +| emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | +| exports | require import browser workerd | Has these package.json export conditions | + +#### Source + +[index.ts:18](https://github.com/justjake/quickjs-emscripten/blob/main/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts#L18) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/@jitl/mquickjs-wasmfile-release-sync/README.md b/doc/@jitl/mquickjs-wasmfile-release-sync/README.md new file mode 100644 index 00000000..6e3c19d8 --- /dev/null +++ b/doc/@jitl/mquickjs-wasmfile-release-sync/README.md @@ -0,0 +1,99 @@ +[quickjs-emscripten](../../packages.md) • **@jitl/mquickjs-wasmfile-release-sync** • [Readme](README.md) \| [Exports](exports.md) + +*** + +# @jitl/mquickjs-wasmfile-release-sync + +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +This generated package is part of [quickjs-emscripten](https://github.com/justjake/quickjs-emscripten). +It contains a variant of the quickjs WASM library, and can be used with quickjs-emscripten-core. + +```typescript +import variant from "@jitl/mquickjs-wasmfile-release-sync" +import { newQuickJSWASMModuleFromVariant } from "quickjs-emscripten-core" +const QuickJS = await newQuickJSWASMModuleFromVariant(variant) +``` + +This variant was built with the following settings: + +## Contents + +- [Library: mquickjs](README.md#library-mquickjs) +- [Release mode: release](README.md#release-mode-release) +- [Exports: require import browser workerd](README.md#exports-require-import-browser-workerd) +- [Extra async magic? No](README.md#extra-async-magic-no) +- [Single-file, or separate .wasm file? wasm](README.md#single-file-or-separate-wasm-file-wasm) +- [More details](README.md#more-details) + +## Library: mquickjs + +[mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. + +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. + +## Release mode: release + +Optimized for performance; use when building/deploying your application. + +## Exports: require import browser workerd + +Exports the following in package.json for the package entrypoint: + +- Exports a NodeJS-compatible CommonJS module, which is faster to load and run compared to an ESModule. +- Exports a NodeJS-compatible ESModule. Cannot be imported synchronously from a NodeJS CommonJS module. +- Exports a browser-compatible ESModule, designed to work in browsers and browser-like environments. +- Targets Cloudflare Workers. + +## Extra async magic? No + +The default, normal build. Note that both variants support regular async functions. + +## Single-file, or separate .wasm file? wasm + +Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. + +## More details + +Full variant JSON description: + +```json +{ + "library": "mquickjs", + "releaseMode": "release", + "syncMode": "sync", + "description": "Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS.", + "emscriptenInclusion": "wasm", + "exports": { + "require": { + "emscriptenEnvironment": ["node"] + }, + "import": { + "emscriptenEnvironment": ["node"] + }, + "browser": { + "emscriptenEnvironment": ["web", "worker"] + }, + "workerd": { + "emscriptenEnvironment": ["web"] + } + } +} +``` + +Variant-specific Emscripten build flags: + +```json +[ + "-Oz", + "-flto", + "--closure 1", + "-s FILESYSTEM=0", + "--pre-js $(TEMPLATES)/pre-extension.js", + "--pre-js $(TEMPLATES)/pre-wasmMemory.js" +] +``` + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md b/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md new file mode 100644 index 00000000..bec3f2fb --- /dev/null +++ b/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md @@ -0,0 +1,40 @@ +[quickjs-emscripten](../../packages.md) • **@jitl/mquickjs-wasmfile-release-sync** • [Readme](README.md) \| [Exports](exports.md) + +*** + +[quickjs-emscripten](../../packages.md) / @jitl/mquickjs-wasmfile-release-sync + +# @jitl/mquickjs-wasmfile-release-sync + +## Contents + +- [Variables](exports.md#variables) + - [default](exports.md#default) + - [@jitl/mquickjs-wasmfile-release-sync](exports.md#jitlmquickjs-wasmfile-release-sync) + +## Variables + +### default + +> **`const`** **default**: [`QuickJSSyncVariant`](../../quickjs-emscripten/interfaces/QuickJSSyncVariant.md) + +### @jitl/mquickjs-wasmfile-release-sync + +[Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-release-sync/README.md) | +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +| Variable | Setting | Description | +| -- | -- | -- | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| releaseMode | release | Optimized for performance; use when building/deploying your application. | +| syncMode | sync | The default, normal build. Note that both variants support regular async functions. | +| emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | +| exports | require import browser workerd | Has these package.json export conditions | + +#### Source + +[index.ts:18](https://github.com/justjake/quickjs-emscripten/blob/main/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts#L18) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/packages.md b/doc/packages.md index 69e42148..a870275f 100644 --- a/doc/packages.md +++ b/doc/packages.md @@ -17,6 +17,8 @@ - [@jitl/quickjs-ng-wasmfile-debug-asyncify](@jitl/quickjs-ng-wasmfile-debug-asyncify/README.md) - [@jitl/quickjs-ng-wasmfile-release-sync](@jitl/quickjs-ng-wasmfile-release-sync/README.md) - [@jitl/quickjs-ng-wasmfile-release-asyncify](@jitl/quickjs-ng-wasmfile-release-asyncify/README.md) +- [@jitl/mquickjs-wasmfile-debug-sync](@jitl/mquickjs-wasmfile-debug-sync/README.md) +- [@jitl/mquickjs-wasmfile-release-sync](@jitl/mquickjs-wasmfile-release-sync/README.md) - [@jitl/quickjs-singlefile-cjs-debug-sync](@jitl/quickjs-singlefile-cjs-debug-sync/README.md) - [@jitl/quickjs-singlefile-cjs-debug-asyncify](@jitl/quickjs-singlefile-cjs-debug-asyncify/README.md) - [@jitl/quickjs-singlefile-cjs-release-sync](@jitl/quickjs-singlefile-cjs-release-sync/README.md) diff --git a/doc/quickjs-emscripten-core/README.md b/doc/quickjs-emscripten-core/README.md index 8f9c2a24..b92a0906 100644 --- a/doc/quickjs-emscripten-core/README.md +++ b/doc/quickjs-emscripten-core/README.md @@ -36,6 +36,8 @@ const QuickJS = await newQuickJSWASMModuleFromVariant(releaseVariant) - [@jitl/quickjs-ng-wasmfile-debug-asyncify](README.md#jitlquickjs-ng-wasmfile-debug-asyncify) - [@jitl/quickjs-ng-wasmfile-release-sync](README.md#jitlquickjs-ng-wasmfile-release-sync) - [@jitl/quickjs-ng-wasmfile-release-asyncify](README.md#jitlquickjs-ng-wasmfile-release-asyncify) + - [@jitl/mquickjs-wasmfile-debug-sync](README.md#jitlmquickjs-wasmfile-debug-sync) + - [@jitl/mquickjs-wasmfile-release-sync](README.md#jitlmquickjs-wasmfile-release-sync) - [@jitl/quickjs-singlefile-cjs-debug-sync](README.md#jitlquickjs-singlefile-cjs-debug-sync) - [@jitl/quickjs-singlefile-cjs-debug-asyncify](README.md#jitlquickjs-singlefile-cjs-debug-asyncify) - [@jitl/quickjs-singlefile-cjs-release-sync](README.md#jitlquickjs-singlefile-cjs-release-sync) @@ -202,6 +204,32 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | | exports | require import browser workerd | Has these package.json export conditions | +### @jitl/mquickjs-wasmfile-debug-sync + +[Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md) | +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +| Variable | Setting | Description | +| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | +| syncMode | sync | The default, normal build. Note that both variants support regular async functions. | +| emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | +| exports | require import browser workerd | Has these package.json export conditions | + +### @jitl/mquickjs-wasmfile-release-sync + +[Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/mquickjs-wasmfile-release-sync/README.md) | +Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS CommonJS. + +| Variable | Setting | Description | +| ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| releaseMode | release | Optimized for performance; use when building/deploying your application. | +| syncMode | sync | The default, normal build. Note that both variants support regular async functions. | +| emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | +| exports | require import browser workerd | Has these package.json export conditions | + ### @jitl/quickjs-singlefile-cjs-debug-sync [Docs](https://github.com/justjake/quickjs-emscripten/blob/main/doc/@jitl/quickjs-singlefile-cjs-debug-sync/README.md) | diff --git a/doc/quickjs-emscripten-core/classes/QuickJSFeatures.md b/doc/quickjs-emscripten-core/classes/QuickJSFeatures.md new file mode 100644 index 00000000..a9b8e752 --- /dev/null +++ b/doc/quickjs-emscripten-core/classes/QuickJSFeatures.md @@ -0,0 +1,79 @@ +[quickjs-emscripten](../../packages.md) • **quickjs-emscripten-core** • [Readme](../README.md) \| [Exports](../exports.md) + +*** + +[quickjs-emscripten](../../packages.md) / [quickjs-emscripten-core](../exports.md) / QuickJSFeatures + +# Class: QuickJSFeatures + +Provides feature detection for a QuickJS variant. +Different QuickJS builds may have different feature sets. For example, +mquickjs is a minimal build that doesn't support modules, promises, +symbols, or BigInt. + +Access via [QuickJSWASMModule#features](QuickJSWASMModule.md#features), [QuickJSRuntime#features](QuickJSRuntime.md#features), +or [QuickJSContext#features](QuickJSContext.md#features). + +## Contents + +- [Methods](QuickJSFeatures.md#methods) + - [assertHas()](QuickJSFeatures.md#asserthas) + - [has()](QuickJSFeatures.md#has) + +## Methods + +### assertHas() + +> **assertHas**(`feature`, `operation`?): `void` + +Assert that this QuickJS variant supports a specific feature. + +#### Parameters + +• **feature**: [`QuickJSFeature`](../exports.md#quickjsfeature) + +The feature to check support for + +• **operation?**: `string` + +Optional description of the operation being attempted + +#### Returns + +`void` + +#### Throws + +If the feature is not supported + +#### Source + +[packages/quickjs-emscripten-core/src/features.ts:66](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/features.ts#L66) + +*** + +### has() + +> **has**(`feature`): `boolean` + +Check if this QuickJS variant supports a specific feature. + +#### Parameters + +• **feature**: [`QuickJSFeature`](../exports.md#quickjsfeature) + +The feature to check support for + +#### Returns + +`boolean` + +`true` if the feature is supported, `false` otherwise + +#### Source + +[packages/quickjs-emscripten-core/src/features.ts:25](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/features.ts#L25) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/quickjs-emscripten-core/classes/TestQuickJSWASMModule.md b/doc/quickjs-emscripten-core/classes/TestQuickJSWASMModule.md index 6f1eb687..180e066c 100644 --- a/doc/quickjs-emscripten-core/classes/TestQuickJSWASMModule.md +++ b/doc/quickjs-emscripten-core/classes/TestQuickJSWASMModule.md @@ -23,6 +23,8 @@ freed all the memory you've ever allocated. - [Properties](TestQuickJSWASMModule.md#properties) - [contexts](TestQuickJSWASMModule.md#contexts) - [runtimes](TestQuickJSWASMModule.md#runtimes) +- [Accessors](TestQuickJSWASMModule.md#accessors) + - [features](TestQuickJSWASMModule.md#features) - [Methods](TestQuickJSWASMModule.md#methods) - [assertNoMemoryAllocated()](TestQuickJSWASMModule.md#assertnomemoryallocated) - [disposeAll()](TestQuickJSWASMModule.md#disposeall) @@ -51,7 +53,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:21](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L21) +[packages/quickjs-emscripten-core/src/module-test.ts:22](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L22) ## Properties @@ -61,7 +63,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:19](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L19) +[packages/quickjs-emscripten-core/src/module-test.ts:20](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L20) *** @@ -71,7 +73,21 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:20](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L20) +[packages/quickjs-emscripten-core/src/module-test.ts:21](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L21) + +## Accessors + +### features + +> **`get`** **features**(): [`QuickJSFeatures`](QuickJSFeatures.md) + +#### Returns + +[`QuickJSFeatures`](QuickJSFeatures.md) + +#### Source + +[packages/quickjs-emscripten-core/src/module-test.ts:89](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L89) ## Methods @@ -85,7 +101,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:62](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L62) +[packages/quickjs-emscripten-core/src/module-test.ts:63](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L63) *** @@ -99,7 +115,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:51](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L51) +[packages/quickjs-emscripten-core/src/module-test.ts:52](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L52) *** @@ -123,7 +139,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:47](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L47) +[packages/quickjs-emscripten-core/src/module-test.ts:48](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L48) *** @@ -141,7 +157,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L79) +[packages/quickjs-emscripten-core/src/module-test.ts:80](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L80) *** @@ -163,7 +179,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:35](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L35) +[packages/quickjs-emscripten-core/src/module-test.ts:36](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L36) *** @@ -185,7 +201,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:23](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L23) +[packages/quickjs-emscripten-core/src/module-test.ts:24](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L24) *** diff --git a/doc/quickjs-emscripten-core/interfaces/AsyncRuntimeOptions.md b/doc/quickjs-emscripten-core/interfaces/AsyncRuntimeOptions.md index 252d061a..16196788 100644 --- a/doc/quickjs-emscripten-core/interfaces/AsyncRuntimeOptions.md +++ b/doc/quickjs-emscripten-core/interfaces/AsyncRuntimeOptions.md @@ -35,7 +35,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:119](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L119) +[packages/quickjs-emscripten-core/src/types.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L132) *** @@ -49,7 +49,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) +[packages/quickjs-emscripten-core/src/types.ts:126](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L126) *** @@ -63,7 +63,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:114](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L114) +[packages/quickjs-emscripten-core/src/types.ts:127](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L127) *** @@ -77,7 +77,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:115](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L115) +[packages/quickjs-emscripten-core/src/types.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L128) *** @@ -87,7 +87,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:137](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L137) +[packages/quickjs-emscripten-core/src/types.ts:150](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L150) *** @@ -101,7 +101,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:117](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L117) +[packages/quickjs-emscripten-core/src/types.ts:130](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L130) *** @@ -115,7 +115,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:118](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L118) +[packages/quickjs-emscripten-core/src/types.ts:131](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L131) *** @@ -129,7 +129,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:120](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L120) +[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) *** diff --git a/doc/quickjs-emscripten-core/interfaces/ContextEvalOptions.md b/doc/quickjs-emscripten-core/interfaces/ContextEvalOptions.md index 1781940d..665f644c 100644 --- a/doc/quickjs-emscripten-core/interfaces/ContextEvalOptions.md +++ b/doc/quickjs-emscripten-core/interfaces/ContextEvalOptions.md @@ -25,7 +25,7 @@ don't include the stack frames before this eval in the Error() backtraces #### Source -[packages/quickjs-emscripten-core/src/types.ts:263](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L263) +[packages/quickjs-emscripten-core/src/types.ts:276](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L276) *** @@ -39,7 +39,7 @@ with JS_EvalFunction(). #### Source -[packages/quickjs-emscripten-core/src/types.ts:261](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L261) +[packages/quickjs-emscripten-core/src/types.ts:274](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L274) *** @@ -51,7 +51,7 @@ Force "strict" mode #### Source -[packages/quickjs-emscripten-core/src/types.ts:253](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L253) +[packages/quickjs-emscripten-core/src/types.ts:266](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L266) *** @@ -63,7 +63,7 @@ Force "strip" mode #### Source -[packages/quickjs-emscripten-core/src/types.ts:255](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L255) +[packages/quickjs-emscripten-core/src/types.ts:268](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L268) *** @@ -78,7 +78,7 @@ Global code (default), or "module" code? #### Source -[packages/quickjs-emscripten-core/src/types.ts:251](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L251) +[packages/quickjs-emscripten-core/src/types.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L264) *** diff --git a/doc/quickjs-emscripten-core/interfaces/ContextOptions.md b/doc/quickjs-emscripten-core/interfaces/ContextOptions.md index 0c3cf454..3fba1648 100644 --- a/doc/quickjs-emscripten-core/interfaces/ContextOptions.md +++ b/doc/quickjs-emscripten-core/interfaces/ContextOptions.md @@ -37,7 +37,7 @@ const contextWithoutDateOrEval = runtime.newContext({ #### Source -[packages/quickjs-emscripten-core/src/types.ts:229](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L229) +[packages/quickjs-emscripten-core/src/types.ts:242](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L242) *** diff --git a/doc/quickjs-emscripten-core/interfaces/JSModuleLoader.md b/doc/quickjs-emscripten-core/interfaces/JSModuleLoader.md index 1faf3a53..11610fd7 100644 --- a/doc/quickjs-emscripten-core/interfaces/JSModuleLoader.md +++ b/doc/quickjs-emscripten-core/interfaces/JSModuleLoader.md @@ -22,7 +22,7 @@ Load module (sync) ## Source -[packages/quickjs-emscripten-core/src/types.ts:83](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L83) +[packages/quickjs-emscripten-core/src/types.ts:96](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L96) *** diff --git a/doc/quickjs-emscripten-core/interfaces/JSModuleLoaderAsync.md b/doc/quickjs-emscripten-core/interfaces/JSModuleLoaderAsync.md index 01a7be19..20c2ad39 100644 --- a/doc/quickjs-emscripten-core/interfaces/JSModuleLoaderAsync.md +++ b/doc/quickjs-emscripten-core/interfaces/JSModuleLoaderAsync.md @@ -22,7 +22,7 @@ Load module (async) ## Source -[packages/quickjs-emscripten-core/src/types.ts:76](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L76) +[packages/quickjs-emscripten-core/src/types.ts:89](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L89) *** diff --git a/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizer.md b/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizer.md index a7f0bae8..321fb9fc 100644 --- a/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizer.md +++ b/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizer.md @@ -26,7 +26,7 @@ ## Source -[packages/quickjs-emscripten-core/src/types.ts:100](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L100) +[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) > **JSModuleNormalizer**(`baseModuleName`, `requestedName`, `vm`): [`JSModuleNormalizeResult`](../exports.md#jsmodulenormalizeresult) \| `Promise`\<[`JSModuleNormalizeResult`](../exports.md#jsmodulenormalizeresult)\> @@ -44,7 +44,7 @@ ## Source -[packages/quickjs-emscripten-core/src/types.ts:93](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L93) +[packages/quickjs-emscripten-core/src/types.ts:106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L106) *** diff --git a/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizerAsync.md b/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizerAsync.md index 87171ee9..161a2bcf 100644 --- a/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizerAsync.md +++ b/doc/quickjs-emscripten-core/interfaces/JSModuleNormalizerAsync.md @@ -26,7 +26,7 @@ ## Source -[packages/quickjs-emscripten-core/src/types.ts:93](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L93) +[packages/quickjs-emscripten-core/src/types.ts:106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L106) *** diff --git a/doc/quickjs-emscripten-core/interfaces/RuntimeOptions.md b/doc/quickjs-emscripten-core/interfaces/RuntimeOptions.md index 28800342..634cfe36 100644 --- a/doc/quickjs-emscripten-core/interfaces/RuntimeOptions.md +++ b/doc/quickjs-emscripten-core/interfaces/RuntimeOptions.md @@ -35,7 +35,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:119](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L119) +[packages/quickjs-emscripten-core/src/types.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L132) *** @@ -49,7 +49,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) +[packages/quickjs-emscripten-core/src/types.ts:126](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L126) *** @@ -63,7 +63,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:114](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L114) +[packages/quickjs-emscripten-core/src/types.ts:127](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L127) *** @@ -77,7 +77,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:115](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L115) +[packages/quickjs-emscripten-core/src/types.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L128) *** @@ -87,7 +87,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) +[packages/quickjs-emscripten-core/src/types.ts:146](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L146) *** @@ -101,7 +101,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:117](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L117) +[packages/quickjs-emscripten-core/src/types.ts:130](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L130) *** @@ -115,7 +115,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:118](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L118) +[packages/quickjs-emscripten-core/src/types.ts:131](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L131) *** @@ -129,7 +129,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:120](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L120) +[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) *** diff --git a/doc/quickjs-emscripten-core/interfaces/RuntimeOptionsBase.md b/doc/quickjs-emscripten-core/interfaces/RuntimeOptionsBase.md index bc775a17..48db6079 100644 --- a/doc/quickjs-emscripten-core/interfaces/RuntimeOptionsBase.md +++ b/doc/quickjs-emscripten-core/interfaces/RuntimeOptionsBase.md @@ -31,7 +31,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:119](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L119) +[packages/quickjs-emscripten-core/src/types.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L132) *** @@ -41,7 +41,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) +[packages/quickjs-emscripten-core/src/types.ts:126](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L126) *** @@ -51,7 +51,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:114](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L114) +[packages/quickjs-emscripten-core/src/types.ts:127](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L127) *** @@ -61,7 +61,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:115](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L115) +[packages/quickjs-emscripten-core/src/types.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L128) *** @@ -71,7 +71,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:117](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L117) +[packages/quickjs-emscripten-core/src/types.ts:130](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L130) *** @@ -81,7 +81,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:118](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L118) +[packages/quickjs-emscripten-core/src/types.ts:131](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L131) *** @@ -91,7 +91,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:120](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L120) +[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) *** diff --git a/doc/quickjs-emscripten-core/namespaces/errors/README.md b/doc/quickjs-emscripten-core/namespaces/errors/README.md index beed0e96..78e74617 100644 --- a/doc/quickjs-emscripten-core/namespaces/errors/README.md +++ b/doc/quickjs-emscripten-core/namespaces/errors/README.md @@ -22,6 +22,7 @@ Collects the informative errors this library may throw. - [QuickJSNotImplemented](classes/QuickJSNotImplemented.md) - [QuickJSPromisePending](classes/QuickJSPromisePending.md) - [QuickJSUnknownIntrinsic](classes/QuickJSUnknownIntrinsic.md) +- [QuickJSUnsupported](classes/QuickJSUnsupported.md) - [QuickJSUnwrapError](classes/QuickJSUnwrapError.md) - [QuickJSUseAfterFree](classes/QuickJSUseAfterFree.md) - [QuickJSWrongOwner](classes/QuickJSWrongOwner.md) diff --git a/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md b/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md new file mode 100644 index 00000000..d7d1ad26 --- /dev/null +++ b/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md @@ -0,0 +1,111 @@ +[quickjs-emscripten](../../../../packages.md) • **quickjs-emscripten-core** • [Readme](../../../README.md) \| [Exports](../../../exports.md) + +*** + +[quickjs-emscripten](../../../../packages.md) / [quickjs-emscripten-core](../../../exports.md) / [errors](../README.md) / QuickJSUnsupported + +# Class: QuickJSUnsupported + +Error thrown when attempting to use a feature that is not supported by the +current QuickJS variant (e.g., modules in mquickjs). + +## Contents + +- [Extends](QuickJSUnsupported.md#extends) +- [Constructors](QuickJSUnsupported.md#constructors) + - [new QuickJSUnsupported(feature, operation, variantName)](QuickJSUnsupported.md#new-quickjsunsupportedfeature-operation-variantname) +- [Properties](QuickJSUnsupported.md#properties) + - [feature](QuickJSUnsupported.md#feature) + - [name](QuickJSUnsupported.md#name) + - [operation](QuickJSUnsupported.md#operation) + - [variantName](QuickJSUnsupported.md#variantname) + +## Extends + +- `Error` + +## Constructors + +### new QuickJSUnsupported(feature, operation, variantName) + +> **new QuickJSUnsupported**(`feature`, `operation`, `variantName`): [`QuickJSUnsupported`](QuickJSUnsupported.md) + +#### Parameters + +• **feature**: `string` + +The feature that is not supported + +• **operation**: `undefined` \| `string` + +The operation that was attempted, if specified + +• **variantName**: `undefined` \| `string` + +The name of the variant that doesn't support this feature + +#### Returns + +[`QuickJSUnsupported`](QuickJSUnsupported.md) + +#### Overrides + +`Error.constructor` + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:67](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L67) + +## Properties + +### feature + +> **feature**: `string` + +The feature that is not supported + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:69](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L69) + +*** + +### name + +> **name**: `string` = `"QuickJSUnsupported"` + +#### Overrides + +`Error.name` + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:65](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L65) + +*** + +### operation + +> **operation**: `undefined` \| `string` + +The operation that was attempted, if specified + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:71](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L71) + +*** + +### variantName + +> **variantName**: `undefined` \| `string` + +The name of the variant that doesn't support this feature + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:73](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L73) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/quickjs-emscripten/classes/QuickJSFeatures.md b/doc/quickjs-emscripten/classes/QuickJSFeatures.md new file mode 100644 index 00000000..5ba9ef0b --- /dev/null +++ b/doc/quickjs-emscripten/classes/QuickJSFeatures.md @@ -0,0 +1,79 @@ +[quickjs-emscripten](../../packages.md) • **quickjs-emscripten** • [Readme](../README.md) \| [Exports](../exports.md) + +*** + +[quickjs-emscripten](../../packages.md) / [quickjs-emscripten](../exports.md) / QuickJSFeatures + +# Class: QuickJSFeatures + +Provides feature detection for a QuickJS variant. +Different QuickJS builds may have different feature sets. For example, +mquickjs is a minimal build that doesn't support modules, promises, +symbols, or BigInt. + +Access via [QuickJSWASMModule#features](QuickJSWASMModule.md#features), [QuickJSRuntime#features](QuickJSRuntime.md#features), +or [QuickJSContext#features](QuickJSContext.md#features). + +## Contents + +- [Methods](QuickJSFeatures.md#methods) + - [assertHas()](QuickJSFeatures.md#asserthas) + - [has()](QuickJSFeatures.md#has) + +## Methods + +### assertHas() + +> **assertHas**(`feature`, `operation`?): `void` + +Assert that this QuickJS variant supports a specific feature. + +#### Parameters + +• **feature**: [`QuickJSFeature`](../exports.md#quickjsfeature) + +The feature to check support for + +• **operation?**: `string` + +Optional description of the operation being attempted + +#### Returns + +`void` + +#### Throws + +If the feature is not supported + +#### Source + +[packages/quickjs-emscripten-core/src/features.ts:66](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/features.ts#L66) + +*** + +### has() + +> **has**(`feature`): `boolean` + +Check if this QuickJS variant supports a specific feature. + +#### Parameters + +• **feature**: [`QuickJSFeature`](../exports.md#quickjsfeature) + +The feature to check support for + +#### Returns + +`boolean` + +`true` if the feature is supported, `false` otherwise + +#### Source + +[packages/quickjs-emscripten-core/src/features.ts:25](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/features.ts#L25) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md b/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md index 8634c28c..b8e0d953 100644 --- a/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md +++ b/doc/quickjs-emscripten/classes/TestQuickJSWASMModule.md @@ -23,6 +23,8 @@ freed all the memory you've ever allocated. - [Properties](TestQuickJSWASMModule.md#properties) - [contexts](TestQuickJSWASMModule.md#contexts) - [runtimes](TestQuickJSWASMModule.md#runtimes) +- [Accessors](TestQuickJSWASMModule.md#accessors) + - [features](TestQuickJSWASMModule.md#features) - [Methods](TestQuickJSWASMModule.md#methods) - [assertNoMemoryAllocated()](TestQuickJSWASMModule.md#assertnomemoryallocated) - [disposeAll()](TestQuickJSWASMModule.md#disposeall) @@ -51,7 +53,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:21](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L21) +[packages/quickjs-emscripten-core/src/module-test.ts:22](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L22) ## Properties @@ -61,7 +63,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:19](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L19) +[packages/quickjs-emscripten-core/src/module-test.ts:20](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L20) *** @@ -71,7 +73,21 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:20](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L20) +[packages/quickjs-emscripten-core/src/module-test.ts:21](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L21) + +## Accessors + +### features + +> **`get`** **features**(): [`QuickJSFeatures`](QuickJSFeatures.md) + +#### Returns + +[`QuickJSFeatures`](QuickJSFeatures.md) + +#### Source + +[packages/quickjs-emscripten-core/src/module-test.ts:89](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L89) ## Methods @@ -85,7 +101,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:62](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L62) +[packages/quickjs-emscripten-core/src/module-test.ts:63](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L63) *** @@ -99,7 +115,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:51](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L51) +[packages/quickjs-emscripten-core/src/module-test.ts:52](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L52) *** @@ -123,7 +139,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:47](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L47) +[packages/quickjs-emscripten-core/src/module-test.ts:48](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L48) *** @@ -141,7 +157,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L79) +[packages/quickjs-emscripten-core/src/module-test.ts:80](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L80) *** @@ -163,7 +179,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:35](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L35) +[packages/quickjs-emscripten-core/src/module-test.ts:36](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L36) *** @@ -185,7 +201,7 @@ freed all the memory you've ever allocated. #### Source -[packages/quickjs-emscripten-core/src/module-test.ts:23](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L23) +[packages/quickjs-emscripten-core/src/module-test.ts:24](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-test.ts#L24) *** diff --git a/doc/quickjs-emscripten/interfaces/AsyncRuntimeOptions.md b/doc/quickjs-emscripten/interfaces/AsyncRuntimeOptions.md index b070271a..5c8c8de7 100644 --- a/doc/quickjs-emscripten/interfaces/AsyncRuntimeOptions.md +++ b/doc/quickjs-emscripten/interfaces/AsyncRuntimeOptions.md @@ -35,7 +35,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:119](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L119) +[packages/quickjs-emscripten-core/src/types.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L132) *** @@ -49,7 +49,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) +[packages/quickjs-emscripten-core/src/types.ts:126](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L126) *** @@ -63,7 +63,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:114](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L114) +[packages/quickjs-emscripten-core/src/types.ts:127](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L127) *** @@ -77,7 +77,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:115](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L115) +[packages/quickjs-emscripten-core/src/types.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L128) *** @@ -87,7 +87,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:137](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L137) +[packages/quickjs-emscripten-core/src/types.ts:150](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L150) *** @@ -101,7 +101,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:117](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L117) +[packages/quickjs-emscripten-core/src/types.ts:130](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L130) *** @@ -115,7 +115,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:118](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L118) +[packages/quickjs-emscripten-core/src/types.ts:131](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L131) *** @@ -129,7 +129,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:120](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L120) +[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) *** diff --git a/doc/quickjs-emscripten/interfaces/ContextEvalOptions.md b/doc/quickjs-emscripten/interfaces/ContextEvalOptions.md index af30ce71..e6298c6e 100644 --- a/doc/quickjs-emscripten/interfaces/ContextEvalOptions.md +++ b/doc/quickjs-emscripten/interfaces/ContextEvalOptions.md @@ -25,7 +25,7 @@ don't include the stack frames before this eval in the Error() backtraces #### Source -[packages/quickjs-emscripten-core/src/types.ts:263](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L263) +[packages/quickjs-emscripten-core/src/types.ts:276](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L276) *** @@ -39,7 +39,7 @@ with JS_EvalFunction(). #### Source -[packages/quickjs-emscripten-core/src/types.ts:261](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L261) +[packages/quickjs-emscripten-core/src/types.ts:274](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L274) *** @@ -51,7 +51,7 @@ Force "strict" mode #### Source -[packages/quickjs-emscripten-core/src/types.ts:253](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L253) +[packages/quickjs-emscripten-core/src/types.ts:266](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L266) *** @@ -63,7 +63,7 @@ Force "strip" mode #### Source -[packages/quickjs-emscripten-core/src/types.ts:255](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L255) +[packages/quickjs-emscripten-core/src/types.ts:268](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L268) *** @@ -78,7 +78,7 @@ Global code (default), or "module" code? #### Source -[packages/quickjs-emscripten-core/src/types.ts:251](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L251) +[packages/quickjs-emscripten-core/src/types.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L264) *** diff --git a/doc/quickjs-emscripten/interfaces/ContextOptions.md b/doc/quickjs-emscripten/interfaces/ContextOptions.md index 6b005b9f..9986a9da 100644 --- a/doc/quickjs-emscripten/interfaces/ContextOptions.md +++ b/doc/quickjs-emscripten/interfaces/ContextOptions.md @@ -37,7 +37,7 @@ const contextWithoutDateOrEval = runtime.newContext({ #### Source -[packages/quickjs-emscripten-core/src/types.ts:229](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L229) +[packages/quickjs-emscripten-core/src/types.ts:242](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L242) *** diff --git a/doc/quickjs-emscripten/interfaces/JSModuleLoader.md b/doc/quickjs-emscripten/interfaces/JSModuleLoader.md index 080c0be9..ee195904 100644 --- a/doc/quickjs-emscripten/interfaces/JSModuleLoader.md +++ b/doc/quickjs-emscripten/interfaces/JSModuleLoader.md @@ -22,7 +22,7 @@ Load module (sync) ## Source -[packages/quickjs-emscripten-core/src/types.ts:83](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L83) +[packages/quickjs-emscripten-core/src/types.ts:96](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L96) *** diff --git a/doc/quickjs-emscripten/interfaces/JSModuleLoaderAsync.md b/doc/quickjs-emscripten/interfaces/JSModuleLoaderAsync.md index fc9b7b85..52f8da13 100644 --- a/doc/quickjs-emscripten/interfaces/JSModuleLoaderAsync.md +++ b/doc/quickjs-emscripten/interfaces/JSModuleLoaderAsync.md @@ -22,7 +22,7 @@ Load module (async) ## Source -[packages/quickjs-emscripten-core/src/types.ts:76](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L76) +[packages/quickjs-emscripten-core/src/types.ts:89](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L89) *** diff --git a/doc/quickjs-emscripten/interfaces/JSModuleNormalizer.md b/doc/quickjs-emscripten/interfaces/JSModuleNormalizer.md index c7b69275..b920fb18 100644 --- a/doc/quickjs-emscripten/interfaces/JSModuleNormalizer.md +++ b/doc/quickjs-emscripten/interfaces/JSModuleNormalizer.md @@ -26,7 +26,7 @@ ## Source -[packages/quickjs-emscripten-core/src/types.ts:100](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L100) +[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) > **JSModuleNormalizer**(`baseModuleName`, `requestedName`, `vm`): [`JSModuleNormalizeResult`](../exports.md#jsmodulenormalizeresult) \| `Promise`\<[`JSModuleNormalizeResult`](../exports.md#jsmodulenormalizeresult)\> @@ -44,7 +44,7 @@ ## Source -[packages/quickjs-emscripten-core/src/types.ts:93](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L93) +[packages/quickjs-emscripten-core/src/types.ts:106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L106) *** diff --git a/doc/quickjs-emscripten/interfaces/JSModuleNormalizerAsync.md b/doc/quickjs-emscripten/interfaces/JSModuleNormalizerAsync.md index 6a9d505b..451fd4b1 100644 --- a/doc/quickjs-emscripten/interfaces/JSModuleNormalizerAsync.md +++ b/doc/quickjs-emscripten/interfaces/JSModuleNormalizerAsync.md @@ -26,7 +26,7 @@ ## Source -[packages/quickjs-emscripten-core/src/types.ts:93](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L93) +[packages/quickjs-emscripten-core/src/types.ts:106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L106) *** diff --git a/doc/quickjs-emscripten/interfaces/RuntimeOptions.md b/doc/quickjs-emscripten/interfaces/RuntimeOptions.md index 91d9af44..74b2ab21 100644 --- a/doc/quickjs-emscripten/interfaces/RuntimeOptions.md +++ b/doc/quickjs-emscripten/interfaces/RuntimeOptions.md @@ -35,7 +35,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:119](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L119) +[packages/quickjs-emscripten-core/src/types.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L132) *** @@ -49,7 +49,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) +[packages/quickjs-emscripten-core/src/types.ts:126](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L126) *** @@ -63,7 +63,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:114](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L114) +[packages/quickjs-emscripten-core/src/types.ts:127](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L127) *** @@ -77,7 +77,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:115](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L115) +[packages/quickjs-emscripten-core/src/types.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L128) *** @@ -87,7 +87,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) +[packages/quickjs-emscripten-core/src/types.ts:146](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L146) *** @@ -101,7 +101,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:117](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L117) +[packages/quickjs-emscripten-core/src/types.ts:130](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L130) *** @@ -115,7 +115,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:118](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L118) +[packages/quickjs-emscripten-core/src/types.ts:131](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L131) *** @@ -129,7 +129,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:120](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L120) +[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) *** diff --git a/doc/quickjs-emscripten/interfaces/RuntimeOptionsBase.md b/doc/quickjs-emscripten/interfaces/RuntimeOptionsBase.md index 9b35030b..74f95ca0 100644 --- a/doc/quickjs-emscripten/interfaces/RuntimeOptionsBase.md +++ b/doc/quickjs-emscripten/interfaces/RuntimeOptionsBase.md @@ -31,7 +31,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:119](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L119) +[packages/quickjs-emscripten-core/src/types.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L132) *** @@ -41,7 +41,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:113](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L113) +[packages/quickjs-emscripten-core/src/types.ts:126](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L126) *** @@ -51,7 +51,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:114](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L114) +[packages/quickjs-emscripten-core/src/types.ts:127](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L127) *** @@ -61,7 +61,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:115](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L115) +[packages/quickjs-emscripten-core/src/types.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L128) *** @@ -71,7 +71,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:117](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L117) +[packages/quickjs-emscripten-core/src/types.ts:130](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L130) *** @@ -81,7 +81,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:118](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L118) +[packages/quickjs-emscripten-core/src/types.ts:131](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L131) *** @@ -91,7 +91,7 @@ #### Source -[packages/quickjs-emscripten-core/src/types.ts:120](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L120) +[packages/quickjs-emscripten-core/src/types.ts:133](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L133) *** diff --git a/doc/quickjs-emscripten/namespaces/errors/README.md b/doc/quickjs-emscripten/namespaces/errors/README.md index 9347cf27..cf7ede09 100644 --- a/doc/quickjs-emscripten/namespaces/errors/README.md +++ b/doc/quickjs-emscripten/namespaces/errors/README.md @@ -22,6 +22,7 @@ Collects the informative errors this library may throw. - [QuickJSNotImplemented](classes/QuickJSNotImplemented.md) - [QuickJSPromisePending](classes/QuickJSPromisePending.md) - [QuickJSUnknownIntrinsic](classes/QuickJSUnknownIntrinsic.md) +- [QuickJSUnsupported](classes/QuickJSUnsupported.md) - [QuickJSUnwrapError](classes/QuickJSUnwrapError.md) - [QuickJSUseAfterFree](classes/QuickJSUseAfterFree.md) - [QuickJSWrongOwner](classes/QuickJSWrongOwner.md) diff --git a/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md b/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md new file mode 100644 index 00000000..91827fd7 --- /dev/null +++ b/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md @@ -0,0 +1,111 @@ +[quickjs-emscripten](../../../../packages.md) • **quickjs-emscripten** • [Readme](../../../README.md) \| [Exports](../../../exports.md) + +*** + +[quickjs-emscripten](../../../../packages.md) / [quickjs-emscripten](../../../exports.md) / [errors](../README.md) / QuickJSUnsupported + +# Class: QuickJSUnsupported + +Error thrown when attempting to use a feature that is not supported by the +current QuickJS variant (e.g., modules in mquickjs). + +## Contents + +- [Extends](QuickJSUnsupported.md#extends) +- [Constructors](QuickJSUnsupported.md#constructors) + - [new QuickJSUnsupported(feature, operation, variantName)](QuickJSUnsupported.md#new-quickjsunsupportedfeature-operation-variantname) +- [Properties](QuickJSUnsupported.md#properties) + - [feature](QuickJSUnsupported.md#feature) + - [name](QuickJSUnsupported.md#name) + - [operation](QuickJSUnsupported.md#operation) + - [variantName](QuickJSUnsupported.md#variantname) + +## Extends + +- `Error` + +## Constructors + +### new QuickJSUnsupported(feature, operation, variantName) + +> **new QuickJSUnsupported**(`feature`, `operation`, `variantName`): [`QuickJSUnsupported`](QuickJSUnsupported.md) + +#### Parameters + +• **feature**: `string` + +The feature that is not supported + +• **operation**: `undefined` \| `string` + +The operation that was attempted, if specified + +• **variantName**: `undefined` \| `string` + +The name of the variant that doesn't support this feature + +#### Returns + +[`QuickJSUnsupported`](QuickJSUnsupported.md) + +#### Overrides + +`Error.constructor` + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:67](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L67) + +## Properties + +### feature + +> **feature**: `string` + +The feature that is not supported + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:69](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L69) + +*** + +### name + +> **name**: `string` = `"QuickJSUnsupported"` + +#### Overrides + +`Error.name` + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:65](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L65) + +*** + +### operation + +> **operation**: `undefined` \| `string` + +The operation that was attempted, if specified + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:71](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L71) + +*** + +### variantName + +> **variantName**: `undefined` \| `string` + +The name of the variant that doesn't support this feature + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:73](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L73) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) diff --git a/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch b/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch new file mode 100644 index 00000000..11241b1a --- /dev/null +++ b/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch @@ -0,0 +1,28 @@ +Add JS_GetContextOpaque function to mquickjs + +mquickjs has JS_SetContextOpaque but was missing JS_GetContextOpaque. +This is needed by quickjs-emscripten to store runtime data in the context. + +diff --git a/mquickjs.c b/mquickjs.c +--- a/mquickjs.c ++++ b/mquickjs.c +@@ -3674,6 +3674,11 @@ void JS_SetContextOpaque(JSContext *ctx, void *opaque) + ctx->opaque = opaque; + } + ++void *JS_GetContextOpaque(JSContext *ctx) ++{ ++ return ctx->opaque; ++} ++ + void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler) + { + ctx->interrupt_handler = interrupt_handler; +diff --git a/mquickjs.h b/mquickjs.h +--- a/mquickjs.h ++++ b/mquickjs.h +@@ -266,6 +266,7 @@ JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDe + void JS_FreeContext(JSContext *ctx); + void JS_SetContextOpaque(JSContext *ctx, void *opaque); ++void *JS_GetContextOpaque(JSContext *ctx); + void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler); diff --git a/yarn.lock b/yarn.lock index 803cc9c9..bf2d5424 100644 --- a/yarn.lock +++ b/yarn.lock @@ -613,6 +613,22 @@ __metadata: languageName: node linkType: hard +"@jitl/mquickjs-wasmfile-debug-sync@workspace:packages/variant-mquickjs-wasmfile-debug-sync": + version: 0.0.0-use.local + resolution: "@jitl/mquickjs-wasmfile-debug-sync@workspace:packages/variant-mquickjs-wasmfile-debug-sync" + dependencies: + "@jitl/quickjs-ffi-types": "workspace:*" + languageName: unknown + linkType: soft + +"@jitl/mquickjs-wasmfile-release-sync@workspace:packages/variant-mquickjs-wasmfile-release-sync": + version: 0.0.0-use.local + resolution: "@jitl/mquickjs-wasmfile-release-sync@workspace:packages/variant-mquickjs-wasmfile-release-sync" + dependencies: + "@jitl/quickjs-ffi-types": "workspace:*" + languageName: unknown + linkType: soft + "@jitl/quickjs-asmjs-mjs-release-sync@workspace:*, @jitl/quickjs-asmjs-mjs-release-sync@workspace:packages/variant-quickjs-asmjs-mjs-release-sync": version: 0.0.0-use.local resolution: "@jitl/quickjs-asmjs-mjs-release-sync@workspace:packages/variant-quickjs-asmjs-mjs-release-sync" From 76dd176a45a0079e72ced9b3dd392fa92473be1d Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 10:29:44 -0500 Subject: [PATCH 10/16] Fix package.json syntax after rebase --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c598ae0b..8fd13501 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "vendor:mquickjs:pull": "ACTION=pull yarn vendor:mquickjs:subtree && yarn vendor:mquickjs:patch", "vendor:mquickjs:add": "ACTION=add yarn vendor:mquickjs:subtree", "vendor:mquickjs:patch": "git apply vendor/mquickjs-patches/*", - "vendor:update": "yarn vendor:quickjs:pull && yarn vendor:ng:pull && yarn vendor:mquickjs:pull" + "vendor:update": "yarn vendor:quickjs:pull && yarn vendor:ng:pull && yarn vendor:mquickjs:pull", "smoketest-node": "yarn tarball && ./scripts/smoketest-node-minimal.ts && ./scripts/smoketest-node.ts", "smoketest-cra": "yarn tarball && ./scripts/smoketest-create-react-app.ts", "smoketest-vite": "yarn tarball && ./scripts/smoketest-vite.ts", From db703ded4d2beaf0f9d2de26ac7a61c2dbfb5405 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 10:29:45 -0500 Subject: [PATCH 11/16] Squashed 'vendor/mquickjs/' content from commit ee50431 git-subtree-dir: vendor/mquickjs git-subtree-split: ee50431eac9b14b99f722b537ec4cac0c8dd75ab --- Changelog | 1 + LICENSE | 22 + Makefile | 152 + README.md | 378 + cutils.c | 178 + cutils.h | 355 + dtoa.c | 1620 ++++ dtoa.h | 83 + example.c | 287 + example_stdlib.c | 36 + libm.c | 2260 +++++ libm.h | 46 + list.h | 99 + mqjs.c | 774 ++ mqjs_stdlib.c | 402 + mquickjs.c | 18324 +++++++++++++++++++++++++++++++++++++++ mquickjs.h | 382 + mquickjs_build.c | 932 ++ mquickjs_build.h | 97 + mquickjs_opcode.h | 264 + mquickjs_priv.h | 268 + readline.c | 742 ++ readline.h | 98 + readline_tty.c | 246 + readline_tty.h | 29 + softfp_template.h | 970 +++ softfp_template_icvt.h | 172 + tests/mandelbrot.js | 39 + tests/microbench.js | 1137 +++ tests/test_builtin.js | 875 ++ tests/test_closure.js | 106 + tests/test_language.js | 355 + tests/test_loop.js | 395 + tests/test_rect.js | 68 + 34 files changed, 32192 insertions(+) create mode 100644 Changelog create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cutils.c create mode 100644 cutils.h create mode 100644 dtoa.c create mode 100644 dtoa.h create mode 100644 example.c create mode 100644 example_stdlib.c create mode 100644 libm.c create mode 100644 libm.h create mode 100644 list.h create mode 100644 mqjs.c create mode 100644 mqjs_stdlib.c create mode 100644 mquickjs.c create mode 100644 mquickjs.h create mode 100644 mquickjs_build.c create mode 100644 mquickjs_build.h create mode 100644 mquickjs_opcode.h create mode 100644 mquickjs_priv.h create mode 100644 readline.c create mode 100644 readline.h create mode 100644 readline_tty.c create mode 100644 readline_tty.h create mode 100644 softfp_template.h create mode 100644 softfp_template_icvt.h create mode 100644 tests/mandelbrot.js create mode 100644 tests/microbench.js create mode 100644 tests/test_builtin.js create mode 100644 tests/test_closure.js create mode 100644 tests/test_language.js create mode 100644 tests/test_loop.js create mode 100644 tests/test_rect.js diff --git a/Changelog b/Changelog new file mode 100644 index 00000000..7d056202 --- /dev/null +++ b/Changelog @@ -0,0 +1 @@ +2025-12-22: First public version diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a08db69d --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Micro QuickJS Javascript Engine + +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. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..109fcb27 --- /dev/null +++ b/Makefile @@ -0,0 +1,152 @@ +#CONFIG_PROFILE=y +#CONFIG_X86_32=y +#CONFIG_ARM32=y +#CONFIG_WIN32=y +#CONFIG_SOFTFLOAT=y +#CONFIG_ASAN=y +#CONFIG_GPROF=y +CONFIG_SMALL=y +# consider warnings as errors (for development) +#CONFIG_WERROR=y + +ifdef CONFIG_ARM32 +CROSS_PREFIX=arm-linux-gnu- +endif + +ifdef CONFIG_WIN32 + ifdef CONFIG_X86_32 + CROSS_PREFIX?=i686-w64-mingw32- + else + CROSS_PREFIX?=x86_64-w64-mingw32- + endif + EXE=.exe +else + CROSS_PREFIX?= + EXE= +endif + +HOST_CC=gcc +CC=$(CROSS_PREFIX)gcc +CFLAGS=-Wall -g -MMD -D_GNU_SOURCE -fno-math-errno -fno-trapping-math +HOST_CFLAGS=-Wall -g -MMD -D_GNU_SOURCE -fno-math-errno -fno-trapping-math +ifdef CONFIG_WERROR +CFLAGS+=-Werror +HOST_CFLAGS+=-Werror +endif +ifdef CONFIG_ARM32 +CFLAGS+=-mthumb +endif +ifdef CONFIG_SMALL +CFLAGS+=-Os +else +CFLAGS+=-O2 +endif +#CFLAGS+=-fstack-usage +ifdef CONFIG_SOFTFLOAT +CFLAGS+=-msoft-float +CFLAGS+=-DUSE_SOFTFLOAT +endif # CONFIG_SOFTFLOAT +HOST_CFLAGS+=-O2 +LDFLAGS=-g +HOST_LDFLAGS=-g +ifdef CONFIG_GPROF +CFLAGS+=-p +LDFLAGS+=-p +endif +ifdef CONFIG_ASAN +CFLAGS+=-fsanitize=address -fno-omit-frame-pointer +LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer +endif +ifdef CONFIG_X86_32 +CFLAGS+=-m32 +LDFLAGS+=-m32 +endif +ifdef CONFIG_PROFILE +CFLAGS+=-p +LDFLAGS+=-p +endif + +# when cross compiling from a 64 bit system to a 32 bit system, force +# a 32 bit output +ifdef CONFIG_X86_32 +MQJS_BUILD_FLAGS=-m32 +endif +ifdef CONFIG_ARM32 +MQJS_BUILD_FLAGS=-m32 +endif + +PROGS=mqjs$(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 +LIBS=-lm + +mqjs$(EXE): $(MQJS_OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +mquickjs.o: mquickjs_atom.h + +mqjs_stdlib: mqjs_stdlib.host.o mquickjs_build.host.o + $(HOST_CC) $(HOST_LDFLAGS) -o $@ $^ + +mquickjs_atom.h: mqjs_stdlib + ./mqjs_stdlib -a $(MQJS_BUILD_FLAGS) > $@ + +mqjs_stdlib.h: mqjs_stdlib + ./mqjs_stdlib $(MQJS_BUILD_FLAGS) > $@ + +mqjs.o: mqjs_stdlib.h + +# C API example +example.o: example_stdlib.h + +example$(EXE): example.o mquickjs.o dtoa.o libm.o cutils.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +example_stdlib: example_stdlib.host.o mquickjs_build.host.o + $(HOST_CC) $(HOST_LDFLAGS) -o $@ $^ + +example_stdlib.h: example_stdlib + ./example_stdlib $(MQJS_BUILD_FLAGS) > $@ + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.host.o: %.c + $(HOST_CC) $(HOST_CFLAGS) -c -o $@ $< + +test: mqjs example + ./mqjs tests/test_closure.js + ./mqjs tests/test_language.js + ./mqjs tests/test_loop.js + ./mqjs tests/test_builtin.js +# test bytecode generation and loading + ./mqjs -o test_builtin.bin tests/test_builtin.js +# @sha256sum -c test_builtin.sha256 + ./mqjs -b test_builtin.bin + ./example tests/test_rect.js + +microbench: mqjs + ./mqjs tests/microbench.js + +octane: mqjs + ./mqjs --memory-limit 256M tests/octane/run.js + +size: mqjs + size mqjs mqjs.o readline.o cutils.o dtoa.o libm.o mquickjs.o + +dtoa_test: tests/dtoa_test.o dtoa.o cutils.o tests/gay-fixed.o tests/gay-precision.o tests/gay-shortest.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +libm_test: tests/libm_test.o libm.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +rempio2_test: tests/rempio2_test.o libm.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +clean: + rm -f *.o *.d *~ tests/*.o tests/*.d tests/*~ test_builtin.bin mqjs_stdlib mqjs_stdlib.h mquickjs_build_atoms mquickjs_atom.h mqjs_example example_stdlib example_stdlib.h $(PROGS) $(TEST_PROGS) + +-include $(wildcard *.d) diff --git a/README.md b/README.md new file mode 100644 index 00000000..3e53b59a --- /dev/null +++ b/README.md @@ -0,0 +1,378 @@ +MicroQuickJS +============ + +## Introduction + +MicroQuickJS (aka. MQuickJS) is a JavaScript engine targeted at +embedded systems. It compiles and runs JavaScript programs using as little +as 10 kB of RAM. The whole engine requires about 100 kB of ROM (ARM +Thumb-2 code) including the C library. The speed is comparable to +QuickJS. + +MQuickJS only supports a [subset](#javascript-subset-reference) of JavaScript close to ES5. It +implements a **stricter mode** where some error prone or inefficient +JavaScript constructs are forbidden. + +Although MQuickJS shares much code with QuickJS, it internals are +different in order to consume less memory. In particular, it relies on +a tracing garbage collector, the VM does not use the CPU stack and +strings are stored in UTF-8. + +## REPL + +The REPL is `mqjs`. Usage: + +``` +usage: mqjs [options] [file [args]] +-h --help list options +-e --eval EXPR evaluate EXPR +-i --interactive go to interactive mode +-I --include file include an additional file +-d --dump dump the memory usage stats + --memory-limit n limit the memory usage to 'n' bytes +--no-column no column number in debug information +-o FILE save the bytecode to FILE +-m32 force 32 bit bytecode output (use with -o) +-b --allow-bytecode allow bytecode in input file +``` + +Compile and run a program using 10 kB of RAM: + +```sh +./mqjs --memory-limit 10k tests/mandelbrot.js +``` + + +In addition to normal script execution, `mqjs` can output the compiled +bytecode to a persistent storage (file or ROM): + +```sh +./mqjs -o mandelbrot.bin tests/mandelbrot.js +``` + +Then you can run the compiled bytecode as a normal script: + +```sh +./mqjs -b mandelbrot.bin +``` + +The bytecode format depends on the endianness and word length (32 or +64 bit) of the CPU. On a 64 bit CPU, it is possible to use the option +`-m32` to generate 32 bit bytecode that can run on an embedded 32 bit +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. + +## Stricter mode + +MQuickJS only supports a subset of JavaScript (mostly ES5). It is +always in **stricter** mode where some error prone JavaScript features +are disabled. The general idea is that the stricter mode is a subset +of JavaScript, so it still works as usual in other JavaScript +engines. Here are the main points: + +- Only **strict mode** constructs are allowed, hence no `with` keyword + and global variables must be declared with the `var` keyword. + +- Arrays cannot have holes. Writing an element after the end is not + allowed: +```js + a = [] + a[0] = 1; // OK to extend the array length + a[10] = 2; // TypeError +``` + If you need an array like object with holes, use a normal object + instead: +```js + a = {} + a[0] = 1; + a[10] = 2; +``` + `new Array(len)` still works as expected, but the array elements are + initialized to `undefined`. + Array literals with holes are a syntax error: +```js + [ 1, , 3 ] // SyntaxError +``` +- Only global `eval` is supported so it cannot access to nor modify + local variables: +```js + eval('1 + 2'); // forbidden + (1, eval)('1 + 2'); // OK +``` +- No value boxing: `new Number(1)` is not supported and never + necessary. + +## JavaScript Subset Reference + +- Only strict mode is supported with emphasis on ES5 compatibility. + +- `Array` objects: + + - They have no holes. + + - Numeric properties are always handled by the array object and not + forwarded to its prototype. + + - Out-of-bound sets are an error except when they are at the end of + the array. + + - The `length` property is a getter/setter in the array prototype. + +- all properties are writable, enumerable and configurable. + +- `for in` only iterates over the object own properties. It should be + used with this common pattern to have a consistent behavior with + standard JavaScript: + +```js + for(var prop in obj) { + if (obj.hasOwnProperty(prop)) { + ... + } + } +``` +Always prefer using `for of` instead which is supported with arrays: + +```js + for(var prop of Object.keys(obj)) { + ... + } +``` + +- `prototype`, `length` and `name` are getter/setter in function objects. + +- C functions cannot have their own properties (but C constructors + behave as expected). + +- The global object is supported, but its use is discouraged. It + cannot contain getter/setters and properties directly created in it + are not visible as global variables in the executing script. + +- The variable associated with the `catch` keyword is a normal + variable. + +- Direct `eval` is not supported. Only indirect (=global) `eval` is + supported. + +- No value boxing (e.g. `new Number(1)` is not supported) + +- Regexp: + + - case folding only works with ASCII characters. + + - the matching is unicode only i.e. `/./` matches a unicode code + point instead of an UTF-16 character as with the `u` flag. + +- String: `toLowerCase` / `toUpperCase` only handle ASCII characters. + +- Date: only `Date.now()` is supported. + +ES5 extensions: + +- `for of` is supported but iterates only over arrays. No custom + iterator is supported (yet). + +- Typed arrays. + +- `\u{hex}` is accepted in string literals + +- Math functions: `imul`, `clz32`, `fround`, `trunc`, `log2`, `log10`. + +- The exponentiation operator + +- Regexp: the dotall (`s`), sticky (`y`) and unicode (`u`) flags are + accepted. In unicode mode, the unicode properties are not supported. + +- String functions: `codePointAt`, `replaceAll`, `trimStart`, `trimEnd`. + +- The `globalThis` global property. + +## C API + +### Engine initialization + +MQuickJS has almost no dependency on the C library. In particular it +does not use `malloc()`, `free()` nor `printf()`. When creating a +MQuickJS context, a memory buffer must be provided. The engine only +allocates memory in this buffer: +```c + JSContext *ctx; + uint8_t mem_buf[8192]; + ctx = JS_NewContext(mem_buf, sizeof(mem_buf), &js_stdlib); + ... + JS_FreeContext(ctx); +``` +`JS_FreeContext(ctx)` is only necessary to call the finalizers of user +objects as no system memory is allocated by the engine. + +### Memory handling + +The C API is very similar to QuickJS (see `mquickjs.h`). However, +since there is a compacting garbage collector, there are important +differences: + +1. Explicitly freeing values is not necessary (no `JS_FreeValue()`). + +2. The address of objects can move each time a JS allocation is +called. The general rule is to avoid having variables of type +`JSValue` in C. They may be present only for temporary use between +MQuickJS API calls. In the other cases, always use a pointer to a +`JSValue`. `JS_PushGCRef()` returns a pointer to a temporary opaque +`JSValue` stored in a `JSGCRef` variable. `JS_PopGCRef()` must be used +to release the temporary reference. The opaque value in `JSGCRef` is +automatically updated when objects move. Example: + +```c +JSValue my_js_func(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + JSGCRef obj1_ref, obj2_ref; + JSValue *obj1, *obj2, ret; + + ret = JS_EXCEPTION; + obj1 = JS_PushGCRef(ctx, &obj1_ref); + obj2 = JS_PushGCRef(ctx, &obj2_ref); + *obj1 = JS_NewObject(ctx); + if (JS_IsException(*obj1)) + goto fail; + *obj2 = JS_NewObject(ctx); // obj1 may move + if (JS_IsException(*obj2)) + goto fail; + JS_SetPropertyStr(ctx, *obj1, "x", *obj2); // obj1 and obj2 may move + ret = *obj1; + fail: + PopGCRef(ctx, &obj2_ref); + PopGCRef(ctx, &obj1_ref); + return ret; +} +``` + +When running on a PC, the `DEBUG_GC` define can be used to force the +JS allocator to always move objects at each allocation. It is a good +way to check no invalid JSValue is used. + +### Standard library + +The standard library is compiled by a custom tool (`mquickjs_build.c`) +to C structures that may reside in ROM. Hence the standard library +instantiation is very fast and requires almost no RAM. An example of +standard library for `mqjs` is provided in `mqjs_stdlib.c`. The result +of its compilation is `mqjs_stdlib.h`. + +`example.c` is a complete example using the MQuickJS C API. + +### Persistent bytecode + +The bytecode generated by `mqjs` may be executed from ROM. In this +case, it must be relocated before being flashed into ROM (see +`JS_RelocateBytecode()`). It is then instantiated with +`JS_LoadBytecode()` and run as normal script with `JS_Run()` (see +`mqjs.c`). + +As with QuickJS, no backward compatibility is guaranteed at the +bytecode level. Moreover, the bytecode is not verified before being +executed. Only run JavaScript bytecode from trusted sources. + +### Mathematical library and floating point emulation + +MQuickJS contains its own tiny mathematical library (in +`libm.c`). Moreover, in case the CPU has no floating point support, it +contains its own floating point emulator which may be smaller than the +one provided with the GCC toolchain. + +## Internals and comparison with QuickJS + +### Garbage collection + +A tracing and compacting garbage collector is used instead of +reference counting. It allows smaller objects. The GC adds an overhead +of a few bits per allocated memory block. Moreover, memory +fragmentation is avoided. + +The engine has its own memory allocator and does not depend on the C +library malloc. + +### Value and object representation + +The value has the same size as a CPU word (hence 32 bits on a 32 bit +CPU). A value may contain: + + - a 31 bit integer (1 bit tag) + + - a single unicode codepoint (hence a string of one or two 16 bit code units) + + - a 64 bit floating point number with a small exponent with 64 bit CPU words + + - a pointer to a memory block. Memory blocks have a tag stored in + memory. + +JavaScript objects require at least 3 CPU words (hence 12 bytes on a +32 bit CPU). Additional data may be allocated depending on the object +class. The properties are stored in a hash table. Each property +requires at least 3 CPU words. Properties may reside in ROM for +standard library objects. + +Property keys are JSValues unlike QuickJS where they have a specific +type. They are either a string or a positive 31 bit integer. String +property keys are internalized (unique). + +Strings are internally stored in WTF-8 (UTF-8 + unpaired surrogates) +instead of 8 or 16 bit arrays in QuickJS. Surrogate pairs are not +stored explicitly but are still visible when iterating thru 16 bit +code units in JavaScript. Hence full compatibility with JavaScript and +UTF-8 is maintained. + +C Functions can be stored as a single value to reduce the overhead. In +this case, no additional properties can be added. Most standard +library functions are stored this way. + +### Standard library + +The whole standard library resides in ROM. It is generated at compile +time. Only a few objects are created in RAM. Hence the engine +instantiation time is very low. + +### Bytecode + +It is a stack based bytecode (similar to QuickJS). However, the +bytecode references atoms thru an indirect table. + +Line and column number information is compressed with +[exponential-Golomb codes](https://en.wikipedia.org/wiki/Exponential-Golomb_coding). + +### Compilation + +The parser is very close to the QuickJS one but it avoids recursion so +the C stack usage is bounded. There is no abstract syntax tree. The +bytecode is generated in one pass with several tricks to optimize it +(QuickJS has several optimization passes). + +## Tests and benchmarks + +Running the basic tests: +```sh +make test +``` + +Running the QuickJS micro benchmark: +```sh +make microbench +``` + +Additional tests and a patched version of the Octane benchmark running +in stricter mode can be downloaded +[here](https://bellard.org/mquickjs/mquickjs-extras.tar.xz): + +Running the V8 octane benchmark: +```sh +make octane +``` + +## License + +MQuickJS is released under the MIT license. + +Unless otherwise specified, the MQuickJS sources are copyright Fabrice +Bellard and Charlie Gordon. + diff --git a/cutils.c b/cutils.c new file mode 100644 index 00000000..57a71bde --- /dev/null +++ b/cutils.c @@ -0,0 +1,178 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 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 "cutils.h" + +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +int has_suffix(const char *str, const char *suffix) +{ + size_t len = strlen(str); + size_t slen = strlen(suffix); + return (len >= slen && !memcmp(str + len - slen, suffix, slen)); +} + +size_t __unicode_to_utf8(uint8_t *buf, unsigned int c) +{ + uint8_t *q = buf; + + if (c < 0x800) { + *q++ = (c >> 6) | 0xc0; + } else { + if (c < 0x10000) { + *q++ = (c >> 12) | 0xe0; + } else { + if (c < 0x00200000) { + *q++ = (c >> 18) | 0xf0; + } else { + return 0; + } + *q++ = ((c >> 12) & 0x3f) | 0x80; + } + *q++ = ((c >> 6) & 0x3f) | 0x80; + } + *q++ = (c & 0x3f) | 0x80; + return q - buf; +} + +int __unicode_from_utf8(const uint8_t *p, size_t max_len, size_t *plen) +{ + size_t len = 1; + int c; + + c = p[0]; + if (c < 0xc0) { + goto fail; + } else if (c < 0xe0) { + if (unlikely(max_len < 2 || (p[1] & 0xc0) != 0x80)) + goto fail; + c = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f); + len = 2; + if (unlikely(c < 0x80)) + goto fail; + } else if (c < 0xf0) { + if (unlikely(max_len < 2 || (p[1] & 0xc0) != 0x80)) + goto fail; + if (unlikely(max_len < 3 || (p[2] & 0xc0) != 0x80)) { + len = 2; + goto fail; + } + c = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + len = 3; + if (unlikely(c < 0x800)) + goto fail; + } else if (c < 0xf8) { + if (unlikely(max_len < 2 || (p[1] & 0xc0) != 0x80)) + goto fail; + if (unlikely(max_len < 3 || (p[2] & 0xc0) != 0x80)) { + len = 2; + goto fail; + } + if (unlikely(max_len < 4 || (p[3] & 0xc0) != 0x80)) { + len = 3; + goto fail; + } + c = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f); + len = 4; + /* We explicitly accept surrogate pairs */ + if (unlikely(c < 0x10000 || c > 0x10ffff)) + goto fail; + } else { + fail: + *plen = len; + return -1; + } + *plen = len; + return c; +} + +int __utf8_get(const uint8_t *p, size_t *plen) +{ + size_t len; + int c; + + c = p[0]; + if (c < 0xc0) { + len = 1; + } else if (c < 0xe0) { + c = ((p[0] & 0x1f) << 6) | (p[1] & 0x3f); + len = 2; + } else if (c < 0xf0) { + c = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f); + len = 3; + } else if (c < 0xf8) { + c = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f); + len = 4; + } else { + len = 1; + } + *plen = len; + return c; +} diff --git a/cutils.h b/cutils.h new file mode 100644 index 00000000..c0dd0011 --- /dev/null +++ b/cutils.h @@ -0,0 +1,355 @@ +/* + * C utilities + * + * Copyright (c) 2017 Fabrice Bellard + * Copyright (c) 2018 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 CUTILS_H +#define CUTILS_H + +#include +#include + +/* set if CPU is big endian */ +#undef WORDS_BIGENDIAN + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define force_inline inline __attribute__((always_inline)) +#define no_inline __attribute__((noinline)) +#define __maybe_unused __attribute__((unused)) + +#define xglue(x, y) x ## y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#endif + +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) + +typedef int BOOL; + +#ifndef FALSE +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +void pstrcpy(char *buf, int buf_size, const char *str); +char *pstrcat(char *buf, int buf_size, const char *s); +int strstart(const char *str, const char *val, const char **ptr); +int has_suffix(const char *str, const char *suffix); + +static inline int max_int(int a, int b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int min_int(int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static inline uint32_t max_uint32(uint32_t a, uint32_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline uint32_t min_uint32(uint32_t a, uint32_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline int64_t max_int64(int64_t a, int64_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline int64_t min_int64(int64_t a, int64_t b) +{ + if (a < b) + return a; + else + return b; +} + +static inline size_t max_size_t(size_t a, size_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline size_t min_size_t(size_t a, size_t b) +{ + if (a < b) + return a; + else + return b; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ + return __builtin_clz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ + return __builtin_clzll(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ + return __builtin_ctz(a); +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ + return __builtin_ctzll(a); +} + +struct __attribute__((packed)) packed_u64 { + uint64_t v; +}; + +struct __attribute__((packed)) packed_u32 { + uint32_t v; +}; + +struct __attribute__((packed)) packed_u16 { + uint16_t v; +}; + +static inline uint64_t get_u64(const uint8_t *tab) +{ + return ((const struct packed_u64 *)tab)->v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + return (int64_t)((const struct packed_u64 *)tab)->v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + ((struct packed_u64 *)tab)->v = val; +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + return ((const struct packed_u32 *)tab)->v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + return (int32_t)((const struct packed_u32 *)tab)->v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + ((struct packed_u32 *)tab)->v = val; +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + return ((const struct packed_u16 *)tab)->v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + return (int16_t)((const struct packed_u16 *)tab)->v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + ((struct packed_u16 *)tab)->v = val; +} + +static inline uint32_t get_u8(const uint8_t *tab) +{ + return *tab; +} + +static inline int32_t get_i8(const uint8_t *tab) +{ + return (int8_t)*tab; +} + +static inline void put_u8(uint8_t *tab, uint8_t val) +{ + *tab = val; +} + +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} + +static inline uint64_t bswap64(uint64_t v) +{ + return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) | + ((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) | + ((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) | + ((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) | + ((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) | + ((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) | + ((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) | + ((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8)); +} + +static inline uint32_t get_be32(const uint8_t *d) +{ + return bswap32(get_u32(d)); +} + +static inline void put_be32(uint8_t *d, uint32_t v) +{ + put_u32(d, bswap32(v)); +} + +#define UTF8_CHAR_LEN_MAX 4 + +size_t __unicode_to_utf8(uint8_t *buf, unsigned int c); +int __unicode_from_utf8(const uint8_t *p, size_t max_len, size_t *plen); +int __utf8_get(const uint8_t *p, size_t *plen); + +/* Note: at most 21 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes + are output. */ +static inline size_t unicode_to_utf8(uint8_t *buf, unsigned int c) +{ + if (c < 0x80) { + buf[0] = c; + return 1; + } else { + return __unicode_to_utf8(buf, c); + } +} + +/* return -1 in case of error. Surrogates are accepted. max_len must + be >= 1. *plen is set in case of error and always >= 1. */ +static inline int unicode_from_utf8(const uint8_t *buf, size_t max_len, size_t *plen) +{ + if (buf[0] < 0x80) { + *plen = 1; + return buf[0]; + } else { + return __unicode_from_utf8(buf, max_len, plen); + } +} + +/* Warning: no error checking is done so the UTF-8 encoding must be + validated before. */ +static force_inline int utf8_get(const uint8_t *buf, size_t *plen) +{ + if (likely(buf[0] < 0x80)) { + *plen = 1; + return buf[0]; + } else { + return __utf8_get(buf, plen); + } +} + +static inline int from_hex(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else + return -1; +} + +static inline uint64_t float64_as_uint64(double d) +{ + union { + double d; + uint64_t u64; + } u; + u.d = d; + return u.u64; +} + +static inline double uint64_as_float64(uint64_t u64) +{ + union { + double d; + uint64_t u64; + } u; + u.u64 = u64; + return u.d; +} + +typedef union { + uint32_t u32; + float f; +} f32_union; + +static inline uint32_t float_as_uint(float f) +{ + f32_union u; + u.f = f; + return u.u32; +} + +static inline float uint_as_float(uint32_t v) +{ + f32_union u; + u.u32 = v; + return u.f; +} + +#endif /* CUTILS_H */ diff --git a/dtoa.c b/dtoa.c new file mode 100644 index 00000000..604f3f0c --- /dev/null +++ b/dtoa.c @@ -0,0 +1,1620 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024-2025 Fabrice Bellard + * + * 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 "cutils.h" +#include "dtoa.h" + +/* + TODO: + - test n_digits=101 instead of 100 + - simplify subnormal handling + - reduce max memory usage + - free format: could add shortcut if exact result + - use 64 bit limb_t when possible + - use another algorithm for free format dtoa in base 10 (ryu ?) +*/ + +#define USE_POW5_TABLE +/* use fast path to print small integers in free format */ +#define USE_FAST_INT + +#define LIMB_LOG2_BITS 5 + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; + +#define LIMB_DIGITS 9 + +#define JS_RADIX_MAX 36 + +#define DBIGNUM_LEN_MAX 52 /* ~ 2^(1072+53)*36^100 (dtoa) */ +#define MANT_LEN_MAX 18 /* < 36^100 */ + +typedef intptr_t mp_size_t; + +/* the represented number is sum(i, tab[i]*2^(LIMB_BITS * i)) */ +typedef struct { + int len; /* >= 1 */ + limb_t tab[]; +} mpb_t; + +static limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n) +{ + size_t i; + limb_t k, a; + + k=b; + for(i=0;i> LIMB_BITS; + } + return l; +} + +/* WARNING: d must be >= 2^(LIMB_BITS-1) */ +static inline limb_t udiv1norm_init(limb_t d) +{ + limb_t a0, a1; + a1 = -d - 1; + a0 = -1; + return (((dlimb_t)a1 << LIMB_BITS) | a0) / d; +} + +/* return the quotient and the remainder in '*pr'of 'a1*2^LIMB_BITS+a0 + / d' with 0 <= a1 < d. */ +static inline limb_t udiv1norm(limb_t *pr, limb_t a1, limb_t a0, + limb_t d, limb_t d_inv) +{ + limb_t n1m, n_adj, q, r, ah; + dlimb_t a; + n1m = ((slimb_t)a0 >> (LIMB_BITS - 1)); + n_adj = a0 + (n1m & d); + a = (dlimb_t)d_inv * (a1 - n1m) + n_adj; + q = (a >> LIMB_BITS) + a1; + /* compute a - q * r and update q so that the remainder is between + 0 and d - 1 */ + a = ((dlimb_t)a1 << LIMB_BITS) | a0; + a = a - (dlimb_t)q * d - d; + ah = a >> LIMB_BITS; + q += 1 + ah; + r = (limb_t)a + (ah & d); + *pr = r; + return q; +} + +static limb_t mp_div1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + dlimb_t a1; + for(i = n - 1; i >= 0; i--) { + a1 = ((dlimb_t)r << LIMB_BITS) | taba[i]; + tabr[i] = a1 / b; + r = a1 % b; + } + return r; +} + +/* r = (a + high*B^n) >> shift. Return the remainder r (0 <= r < 2^shift). + 1 <= shift <= LIMB_BITS - 1 */ +static limb_t mp_shr(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t high) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + tab_r[i] = (a >> shift) | (l << (LIMB_BITS - shift)); + l = a; + } + return l & (((limb_t)1 << shift) - 1); +} + +/* r = (a << shift) + low. 1 <= shift <= LIMB_BITS - 1, 0 <= low < + 2^shift. */ +static limb_t mp_shl(limb_t *tab_r, const limb_t *tab, mp_size_t n, + int shift, limb_t low) +{ + mp_size_t i; + limb_t l, a; + + assert(shift >= 1 && shift < LIMB_BITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + tab_r[i] = (a << shift) | l; + l = (a >> (LIMB_BITS - shift)); + } + return l; +} + +static no_inline limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r, limb_t b_inv, int shift) +{ + slimb_t i; + + if (shift != 0) { + r = (r << shift) | mp_shl(tabr, taba, n, shift, 0); + } + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + r >>= shift; + return r; +} + +static __maybe_unused void mpb_dump(const char *str, const mpb_t *a) +{ + int i; + + printf("%s= 0x", str); + for(i = a->len - 1; i >= 0; i--) { + printf("%08x", a->tab[i]); + if (i != 0) + printf("_"); + } + printf("\n"); +} + +static void mpb_renorm(mpb_t *r) +{ + while (r->len > 1 && r->tab[r->len - 1] == 0) + r->len--; +} + +#ifdef USE_POW5_TABLE +static const uint32_t pow5_table[17] = { + 0x00000005, 0x00000019, 0x0000007d, 0x00000271, + 0x00000c35, 0x00003d09, 0x0001312d, 0x0005f5e1, + 0x001dcd65, 0x009502f9, 0x02e90edd, 0x0e8d4a51, + 0x48c27395, 0x6bcc41e9, 0x1afd498d, 0x86f26fc1, + 0xa2bc2ec5, +}; + +static const uint8_t pow5h_table[4] = { + 0x00000001, 0x00000007, 0x00000023, 0x000000b1, +}; + +static const uint32_t pow5_inv_table[13] = { + 0x99999999, 0x47ae147a, 0x0624dd2f, 0xa36e2eb1, + 0x4f8b588e, 0x0c6f7a0b, 0xad7f29ab, 0x5798ee23, + 0x12e0be82, 0xb7cdfd9d, 0x5fd7fe17, 0x19799812, + 0xc25c2684, +}; +#endif + +/* return a^b */ +static uint64_t pow_ui(uint32_t a, uint32_t b) +{ + int i, n_bits; + uint64_t r; + if (b == 0) + return 1; + if (b == 1) + return a; +#ifdef USE_POW5_TABLE + if ((a == 5 || a == 10) && b <= 17) { + r = pow5_table[b - 1]; + if (b >= 14) { + r |= (uint64_t)pow5h_table[b - 14] << 32; + } + if (a == 10) + r <<= b; + return r; + } +#endif + r = a; + n_bits = 32 - clz32(b); + for(i = n_bits - 2; i >= 0; i--) { + r *= r; + if ((b >> i) & 1) + r *= a; + } + return r; +} + +static uint32_t pow_ui_inv(uint32_t *pr_inv, int *pshift, uint32_t a, uint32_t b) +{ + uint32_t r_inv, r; + int shift; +#ifdef USE_POW5_TABLE + if (a == 5 && b >= 1 && b <= 13) { + r = pow5_table[b - 1]; + shift = clz32(r); + r <<= shift; + r_inv = pow5_inv_table[b - 1]; + } else +#endif + { + r = pow_ui(a, b); + shift = clz32(r); + r <<= shift; + r_inv = udiv1norm_init(r); + } + *pshift = shift; + *pr_inv = r_inv; + return r; +} + +enum { + JS_RNDN, /* round to nearest, ties to even */ + JS_RNDNA, /* round to nearest, ties away from zero */ + JS_RNDZ, +}; + +static int mpb_get_bit(const mpb_t *r, int k) +{ + int l; + + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + if (l >= r->len) + return 0; + else + return (r->tab[l] >> k) & 1; +} + +/* compute round(r / 2^shift). 'shift' can be negative */ +static void mpb_shr_round(mpb_t *r, int shift, int rnd_mode) +{ + int l, i; + + if (shift == 0) + return; + if (shift < 0) { + shift = -shift; + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (shift != 0) { + r->tab[r->len] = mp_shl(r->tab, r->tab, r->len, shift, 0); + r->len++; + mpb_renorm(r); + } + if (l > 0) { + for(i = r->len - 1; i >= 0; i--) + r->tab[i + l] = r->tab[i]; + for(i = 0; i < l; i++) + r->tab[i] = 0; + r->len += l; + } + } else { + limb_t bit1, bit2; + int k, add_one; + + switch(rnd_mode) { + default: + case JS_RNDZ: + add_one = 0; + break; + case JS_RNDN: + case JS_RNDNA: + bit1 = mpb_get_bit(r, shift - 1); + if (bit1) { + if (rnd_mode == JS_RNDNA) { + bit2 = 1; + } else { + /* bit2 = oring of all the bits after bit1 */ + bit2 = 0; + if (shift >= 2) { + k = shift - 1; + l = (unsigned)k / LIMB_BITS; + k = k & (LIMB_BITS - 1); + for(i = 0; i < min_int(l, r->len); i++) + bit2 |= r->tab[i]; + if (l < r->len) + bit2 |= r->tab[l] & (((limb_t)1 << k) - 1); + } + } + if (bit2) { + add_one = 1; + } else { + /* round to even */ + add_one = mpb_get_bit(r, shift); + } + } else { + add_one = 0; + } + break; + } + + l = (unsigned)shift / LIMB_BITS; + shift = shift & (LIMB_BITS - 1); + if (l >= r->len) { + r->len = 1; + r->tab[0] = add_one; + } else { + if (l > 0) { + r->len -= l; + for(i = 0; i < r->len; i++) + r->tab[i] = r->tab[i + l]; + } + if (shift != 0) { + mp_shr(r->tab, r->tab, r->len, shift, 0); + mpb_renorm(r); + } + if (add_one) { + limb_t a; + a = mp_add_ui(r->tab, 1, r->len); + if (a) + r->tab[r->len++] = a; + } + } + } +} + +/* return -1, 0 or 1 */ +static int mpb_cmp(const mpb_t *a, const mpb_t *b) +{ + mp_size_t i; + if (a->len < b->len) + return -1; + else if (a->len > b->len) + return 1; + for(i = a->len - 1; i >= 0; i--) { + if (a->tab[i] != b->tab[i]) { + if (a->tab[i] < b->tab[i]) + return -1; + else + return 1; + } + } + return 0; +} + +static void mpb_set_u64(mpb_t *r, uint64_t m) +{ +#if LIMB_BITS == 64 + r->tab[0] = m; + r->len = 1; +#else + r->tab[0] = m; + r->tab[1] = m >> LIMB_BITS; + if (r->tab[1] == 0) + r->len = 1; + else + r->len = 2; +#endif +} + +static uint64_t mpb_get_u64(mpb_t *r) +{ +#if LIMB_BITS == 64 + return r->tab[0]; +#else + if (r->len == 1) { + return r->tab[0]; + } else { + return r->tab[0] | ((uint64_t)r->tab[1] << LIMB_BITS); + } +#endif +} + +/* floor_log2() = position of the first non zero bit or -1 if zero. */ +static int mpb_floor_log2(mpb_t *a) +{ + limb_t v; + v = a->tab[a->len - 1]; + if (v == 0) + return -1; + else + return a->len * LIMB_BITS - 1 - clz32(v); +} + +#define MUL_LOG2_RADIX_BASE_LOG2 24 + +/* round((1 << MUL_LOG2_RADIX_BASE_LOG2)/log2(i + 2)) */ +static const uint32_t mul_log2_radix_table[JS_RADIX_MAX - 1] = { + 0x000000, 0xa1849d, 0x000000, 0x6e40d2, + 0x6308c9, 0x5b3065, 0x000000, 0x50c24e, + 0x4d104d, 0x4a0027, 0x4768ce, 0x452e54, + 0x433d00, 0x418677, 0x000000, 0x3ea16b, + 0x3d645a, 0x3c43c2, 0x3b3b9a, 0x3a4899, + 0x39680b, 0x3897b3, 0x37d5af, 0x372069, + 0x367686, 0x35d6df, 0x354072, 0x34b261, + 0x342bea, 0x33ac62, 0x000000, 0x32bfd9, + 0x3251dd, 0x31e8d6, 0x318465, +}; + +/* return floor(a / log2(radix)) for -2048 <= a <= 2047 */ +static int mul_log2_radix(int a, int radix) +{ + int radix_bits, mult; + + if ((radix & (radix - 1)) == 0) { + /* if the radix is a power of two better to do it exactly */ + radix_bits = 31 - clz32(radix); + if (a < 0) + a -= radix_bits - 1; + return a / radix_bits; + } else { + mult = mul_log2_radix_table[radix - 2]; + return ((int64_t)a * mult) >> MUL_LOG2_RADIX_BASE_LOG2; + } +} + +#if 0 +static void build_mul_log2_radix_table(void) +{ + int base, radix, mult, col, base_log2; + + base_log2 = 24; + base = 1 << base_log2; + col = 0; + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) + mult = 0; + else + mult = lrint((double)base / log2(radix)); + printf("0x%06x, ", mult); + if (++col == 4) { + printf("\n"); + col = 0; + } + } + printf("\n"); +} + +static void mul_log2_radix_test(void) +{ + int radix, i, ref, r; + + for(radix = 2; radix <= 36; radix++) { + for(i = -2048; i <= 2047; i++) { + ref = (int)floor((double)i / log2(radix)); + r = mul_log2_radix(i, radix); + if (ref != r) { + printf("ERROR: radix=%d i=%d r=%d ref=%d\n", + radix, i, r, ref); + exit(1); + } + } + } + if (0) + build_mul_log2_radix_table(); +} +#endif + +static void u32toa_len(char *buf, uint32_t n, size_t len) +{ + int digit, i; + for(i = len - 1; i >= 0; i--) { + digit = n % 10; + n = n / 10; + buf[i] = digit + '0'; + } +} + +/* for power of 2 radixes. len >= 1 */ +static void u64toa_bin_len(char *buf, uint64_t n, unsigned int radix_bits, int len) +{ + int digit, i; + unsigned int mask; + + mask = (1 << radix_bits) - 1; + for(i = len - 1; i >= 0; i--) { + digit = n & mask; + n >>= radix_bits; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } +} + +/* len >= 1. 2 <= radix <= 36 */ +static void limb_to_a(char *buf, limb_t n, unsigned int radix, int len) +{ + int digit, i; + + if (radix == 10) { + /* specific case with constant divisor */ +#if LIMB_BITS == 32 + u32toa_len(buf, n, len); +#else + /* XXX: optimize */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } +#endif + } else { + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % radix; + n = (limb_t)n / radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + buf[i] = digit; + } + } +} + +size_t u32toa(char *buf, uint32_t n) +{ + char buf1[10], *q; + size_t len; + + q = buf1 + sizeof(buf1); + do { + *--q = n % 10 + '0'; + n /= 10; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; +} + +size_t i32toa(char *buf, int32_t n) +{ + if (n >= 0) { + return u32toa(buf, n); + } else { + buf[0] = '-'; + return u32toa(buf + 1, -(uint32_t)n) + 1; + } +} + +#ifdef USE_FAST_INT +size_t u64toa(char *buf, uint64_t n) +{ + if (n < 0x100000000) { + return u32toa(buf, n); + } else { + uint64_t n1; + char *q = buf; + uint32_t n2; + + n1 = n / 1000000000; + n %= 1000000000; + if (n1 >= 0x100000000) { + n2 = n1 / 1000000000; + n1 = n1 % 1000000000; + /* at most two digits */ + if (n2 >= 10) { + *q++ = n2 / 10 + '0'; + n2 %= 10; + } + *q++ = n2 + '0'; + u32toa_len(q, n1, 9); + q += 9; + } else { + q += u32toa(q, n1); + } + u32toa_len(q, n, 9); + q += 9; + return q - buf; + } +} + +size_t i64toa(char *buf, int64_t n) +{ + if (n >= 0) { + return u64toa(buf, n); + } else { + buf[0] = '-'; + return u64toa(buf + 1, -(uint64_t)n) + 1; + } +} + +/* XXX: only tested for 1 <= n < 2^53 */ +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix) +{ + int radix_bits, l; + if (likely(radix == 10)) + return u64toa(buf, n); + if ((radix & (radix - 1)) == 0) { + radix_bits = 31 - clz32(radix); + if (n == 0) + l = 1; + else + l = (64 - clz64(n) + radix_bits - 1) / radix_bits; + u64toa_bin_len(buf, n, radix_bits, l); + return l; + } else { + char buf1[41], *q; /* maximum length for radix = 3 */ + size_t len; + int digit; + q = buf1 + sizeof(buf1); + do { + digit = n % radix; + n /= radix; + if (digit < 10) + digit += '0'; + else + digit += 'a' - 10; + *--q = digit; + } while (n != 0); + len = buf1 + sizeof(buf1) - q; + memcpy(buf, q, len); + return len; + } +} + +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix) +{ + if (n >= 0) { + return u64toa_radix(buf, n, radix); + } else { + buf[0] = '-'; + return u64toa_radix(buf + 1, -(uint64_t)n, radix) + 1; + } +} +#endif /* USE_FAST_INT */ + +static const uint8_t digits_per_limb_table[JS_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +32,20,16,13,12,11,10,10, 9, 9, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +#else +64,40,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12, +#endif +}; + +static const uint32_t radix_base_table[JS_RADIX_MAX - 1] = { + 0x00000000, 0xcfd41b91, 0x00000000, 0x48c27395, + 0x81bf1000, 0x75db9c97, 0x40000000, 0xcfd41b91, + 0x3b9aca00, 0x8c8b6d2b, 0x19a10000, 0x309f1021, + 0x57f6c100, 0x98c29b81, 0x00000000, 0x18754571, + 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x94ace180, 0xcaf18367, 0x0b640000, 0x0e8d4a51, + 0x1269ae40, 0x17179149, 0x1cb91000, 0x23744899, + 0x2b73a840, 0x34e63b41, 0x40000000, 0x4cfa3cc1, + 0x5c13d840, 0x6d91b519, 0x81bf1000, +}; + +/* XXX: remove the table ? */ +static uint8_t dtoa_max_digits_table[JS_RADIX_MAX - 1] = { + 54, 35, 28, 24, 22, 20, 19, 18, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12, +}; + +/* we limit the maximum number of significant digits for atod to about + 128 bits of precision for non power of two bases. The only + requirement for Javascript is at least 20 digits in base 10. For + power of two bases, we do an exact rounding in all the cases. */ +static uint8_t atod_max_digits_table[JS_RADIX_MAX - 1] = { + 64, 80, 32, 55, 49, 45, 21, 40, 38, 37, 35, 34, 33, 32, 16, 31, 30, 30, 29, 29, 28, 28, 27, 27, 27, 26, 26, 26, 26, 25, 12, 25, 25, 24, 24, +}; + +/* if abs(d) >= B^max_exponent, it is an overflow */ +static const int16_t max_exponent[JS_RADIX_MAX - 1] = { + 1024, 647, 512, 442, 397, 365, 342, 324, + 309, 297, 286, 277, 269, 263, 256, 251, + 246, 242, 237, 234, 230, 227, 224, 221, + 218, 216, 214, 211, 209, 207, 205, 203, + 202, 200, 199, +}; + +/* if abs(d) <= B^min_exponent, it is an underflow */ +static const int16_t min_exponent[JS_RADIX_MAX - 1] = { +-1075, -679, -538, -463, -416, -383, -359, -340, + -324, -311, -300, -291, -283, -276, -269, -263, + -258, -254, -249, -245, -242, -238, -235, -232, + -229, -227, -224, -222, -220, -217, -215, -214, + -212, -210, -208, +}; + +#if 0 +void build_tables(void) +{ + int r, j, radix, n, col, i; + + /* radix_base_table */ + for(radix = 2; radix <= 36; radix++) { + r = 1; + for(j = 0; j < digits_per_limb_table[radix - 2]; j++) { + r *= radix; + } + printf(" 0x%08x,", r); + if ((radix % 4) == 1) + printf("\n"); + } + printf("\n"); + + /* dtoa_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + /* Note: over estimated when the radix is a power of two */ + printf(" %d,", 1 + (int)ceil(53.0 / log2(radix))); + } + printf("\n"); + + /* atod_max_digits_table */ + for(radix = 2; radix <= 36; radix++) { + if ((radix & (radix - 1)) == 0) { + /* 64 bits is more than enough */ + n = (int)floor(64.0 / log2(radix)); + } else { + n = (int)floor(128.0 / log2(radix)); + } + printf(" %d,", n); + } + printf("\n"); + + printf("static const int16_t max_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)ceil(1024 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const int16_t min_exponent[JS_RADIX_MAX - 1] = {\n"); + col = 0; + for(radix = 2; radix <= 36; radix++) { + printf("%5d, ", (int)floor(-1075 / log2(radix))); + if (++col == 8) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + printf("static const uint32_t pow5_table[16] = {\n"); + col = 0; + for(i = 2; i <= 17; i++) { + r = 1; + for(j = 0; j < i; j++) { + r *= 5; + } + printf("0x%08x, ", r); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); + + /* high part */ + printf("static const uint8_t pow5h_table[4] = {\n"); + col = 0; + for(i = 14; i <= 17; i++) { + uint64_t r1; + r1 = 1; + for(j = 0; j < i; j++) { + r1 *= 5; + } + printf("0x%08x, ", (uint32_t)(r1 >> 32)); + if (++col == 4) { + col = 0; + printf("\n"); + } + } + printf("\n};\n\n"); +} +#endif + +/* n_digits >= 1. 0 <= dot_pos <= n_digits. If dot_pos == n_digits, + the dot is not displayed. 'a' is modified. */ +static int output_digits(char *buf, + mpb_t *a, int radix, int n_digits1, + int dot_pos) +{ + int n_digits, digits_per_limb, radix_bits, n, len; + + n_digits = n_digits1; + if ((radix & (radix - 1)) == 0) { + /* radix = 2^radix_bits */ + radix_bits = 31 - clz32(radix); + } else { + radix_bits = 0; + } + digits_per_limb = digits_per_limb_table[radix - 2]; + if (radix_bits != 0) { + for(;;) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + u64toa_bin_len(buf + n_digits, a->tab[0], radix_bits, n); + if (n_digits == 0) + break; + mpb_shr_round(a, digits_per_limb * radix_bits, JS_RNDZ); + } + } else { + limb_t r; + while (n_digits != 0) { + n = min_int(n_digits, digits_per_limb); + n_digits -= n; + r = mp_div1(a->tab, a->tab, a->len, radix_base_table[radix - 2], 0); + mpb_renorm(a); + limb_to_a(buf + n_digits, r, radix, n); + } + } + + /* add the dot */ + len = n_digits1; + if (dot_pos != n_digits1) { + memmove(buf + dot_pos + 1, buf + dot_pos, n_digits1 - dot_pos); + buf[dot_pos] = '.'; + len++; + } + return len; +} + +/* return (a, e_offset) such that a = a * (radix1*2^radix_shift)^f * + 2^-e_offset. 'f' can be negative. */ +static int mul_pow(mpb_t *a, int radix1, int radix_shift, int f, BOOL is_int, int e) +{ + int e_offset, d, n, n0; + + e_offset = -f * radix_shift; + if (radix1 != 1) { + d = digits_per_limb_table[radix1 - 2]; + if (f >= 0) { + limb_t h, b; + + b = 0; + n0 = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui(radix1, n); + n0 = n; + } + h = mp_mul1(a->tab, a->tab, a->len, b, 0); + if (h != 0) { + a->tab[a->len++] = h; + } + f -= n; + } + } else { + int extra_bits, l, shift; + limb_t r, rem, b, b_inv; + + f = -f; + l = (f + d - 1) / d; /* high bound for the number of limbs (XXX: make it better) */ + e_offset += l * LIMB_BITS; + if (!is_int) { + /* at least 'e' bits are needed in the final result for rounding */ + extra_bits = max_int(e - mpb_floor_log2(a), 0); + } else { + /* at least two extra bits are needed in the final result + for rounding */ + extra_bits = max_int(2 + e - e_offset, 0); + } + e_offset += extra_bits; + mpb_shr_round(a, -(l * LIMB_BITS + extra_bits), JS_RNDZ); + + b = 0; + b_inv = 0; + shift = 0; + n0 = 0; + rem = 0; + while (f != 0) { + n = min_int(f, d); + if (n != n0) { + b = pow_ui_inv(&b_inv, &shift, radix1, n); + n0 = n; + } + r = mp_div1norm(a->tab, a->tab, a->len, b, 0, b_inv, shift); + rem |= r; + mpb_renorm(a); + f -= n; + } + /* if the remainder is non zero, use it for rounding */ + a->tab[0] |= (rem != 0); + } + } + return e_offset; +} + +/* tmp1 = round(m*2^e*radix^f). 'tmp0' is a temporary storage */ +static void mul_pow_round(mpb_t *tmp1, uint64_t m, int e, int radix1, int radix_shift, int f, + int rnd_mode) +{ + int e_offset; + + mpb_set_u64(tmp1, m); + e_offset = mul_pow(tmp1, radix1, radix_shift, f, TRUE, e); + mpb_shr_round(tmp1, -e + e_offset, rnd_mode); +} + +/* return round(a*2^e_offset) rounded as a float64. 'a' is modified */ +static uint64_t round_to_d(int *pe, mpb_t *a, int e_offset, int rnd_mode) +{ + int e; + uint64_t m; + + if (a->tab[0] == 0 && a->len == 1) { + /* zero result */ + m = 0; + e = 0; /* don't care */ + } else { + int prec, prec1, e_min; + e = mpb_floor_log2(a) + 1 - e_offset; + prec1 = 53; + e_min = -1021; + if (e < e_min) { + /* subnormal result or zero */ + prec = prec1 - (e_min - e); + } else { + prec = prec1; + } + mpb_shr_round(a, e + e_offset - prec, rnd_mode); + m = mpb_get_u64(a); + m <<= (53 - prec); + /* mantissa overflow due to rounding */ + if (m >= (uint64_t)1 << 53) { + m >>= 1; + e++; + } + } + *pe = e; + return m; +} + +/* return (m, e) such that m*2^(e-53) = round(a * radix^f) with 2^52 + <= m < 2^53 or m = 0. + 'a' is modified. */ +static uint64_t mul_pow_round_to_d(int *pe, mpb_t *a, + int radix1, int radix_shift, int f, int rnd_mode) +{ + int e_offset; + + e_offset = mul_pow(a, radix1, radix_shift, f, FALSE, 55); + return round_to_d(pe, a, e_offset, rnd_mode); +} + +#ifdef JS_DTOA_DUMP_STATS +static int out_len_count[17]; + +void js_dtoa_dump_stats(void) +{ + int i, sum; + sum = 0; + for(i = 0; i < 17; i++) + sum += out_len_count[i]; + for(i = 0; i < 17; i++) { + printf("%2d %8d %5.2f%%\n", + i + 1, out_len_count[i], (double)out_len_count[i] / sum * 100); + } +} +#endif + +/* return a maximum bound of the string length. The bound depends on + 'd' only if format = JS_DTOA_FORMAT_FRAC or if JS_DTOA_EXP_DISABLED + is enabled. */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags) +{ + int fmt = flags & JS_DTOA_FORMAT_MASK; + int n, e; + uint64_t a; + + if (fmt != JS_DTOA_FORMAT_FRAC) { + if (fmt == JS_DTOA_FORMAT_FREE) { + n = dtoa_max_digits_table[radix - 2]; + } else { + n = n_digits; + } + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_DISABLED) { + /* no exponential */ + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + e -= 1023; + /* XXX: adjust */ + n += 10 + abs(mul_log2_radix(e - 1, radix)); + } + } else { + /* extra: sign, 1 dot and exponent "e-1000" */ + n += 1 + 1 + 6; + } + } else { + a = float64_as_uint64(d); + e = (a >> 52) & 0x7ff; + if (e == 0x7ff) { + /* NaN, Infinity */ + n = 0; + } else { + /* high bound for the integer part */ + e -= 1023; + /* x < 2^(e + 1) */ + if (e < 0) { + n = 1; + } else { + n = 2 + mul_log2_radix(e - 1, radix); + } + /* sign, extra digit, 1 dot */ + n += 1 + 1 + 1 + n_digits; + } + } + return max_int(n, 9); /* also include NaN and [-]Infinity */ +} + +#if defined(__SANITIZE_ADDRESS__) && 0 +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + return malloc(size); +} +static void dtoa_free(void *ptr) +{ + free(ptr); +} +#else +static void *dtoa_malloc(uint64_t **pptr, size_t size) +{ + void *ret; + ret = *pptr; + *pptr += (size + 7) / 8; + return ret; +} + +static void dtoa_free(void *ptr) +{ +} +#endif + +/* return the length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem) +{ + uint64_t a, m, *mptr = tmp_mem->mem; + int e, sgn, l, E, P, i, E_max, radix1, radix_shift; + char *q; + mpb_t *tmp1, *mant_max; + int fmt = flags & JS_DTOA_FORMAT_MASK; + + tmp1 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + mant_max = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * MANT_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSDTOATempMem) / sizeof(mptr[0])); + + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + a = float64_as_uint64(d); + sgn = a >> 63; + e = (a >> 52) & 0x7ff; + m = a & (((uint64_t)1 << 52) - 1); + q = buf; + if (e == 0x7ff) { + if (m == 0) { + if (sgn) + *q++ = '-'; + memcpy(q, "Infinity", 8); + q += 8; + } else { + memcpy(q, "NaN", 3); + q += 3; + } + goto done; + } else if (e == 0) { + if (m == 0) { + tmp1->len = 1; + tmp1->tab[0] = 0; + E = 1; + if (fmt == JS_DTOA_FORMAT_FREE) + P = 1; + else if (fmt == JS_DTOA_FORMAT_FRAC) + P = n_digits + 1; + else + P = n_digits; + /* "-0" is displayed as "0" if JS_DTOA_MINUS_ZERO is not present */ + if (sgn && (flags & JS_DTOA_MINUS_ZERO)) + *q++ = '-'; + goto output; + } + /* denormal number: convert to a normal number */ + l = clz64(m) - 11; + e -= l - 1; + m <<= l; + } else { + m |= (uint64_t)1 << 52; + } + if (sgn) + *q++ = '-'; + /* remove the bias */ + e -= 1022; + /* d = 2^(e-53)*m */ + // printf("m=0x%016" PRIx64 " e=%d\n", m, e); +#ifdef USE_FAST_INT + if (fmt == JS_DTOA_FORMAT_FREE && + e >= 1 && e <= 53 && + (m & (((uint64_t)1 << (53 - e)) - 1)) == 0 && + (flags & JS_DTOA_EXP_MASK) != JS_DTOA_EXP_ENABLED) { + m >>= 53 - e; + /* 'm' is never zero */ + q += u64toa_radix(q, m, radix); + goto done; + } +#endif + + /* this choice of E implies F=round(x*B^(P-E) is such as: + B^(P-1) <= F < 2.B^P. */ + E = 1 + mul_log2_radix(e - 1, radix); + + if (fmt == JS_DTOA_FORMAT_FREE) { + int P_max, E0, e1, E_found, P_found; + uint64_t m1, mant_found, mant, mant_max1; + /* P_max is guaranteed to work by construction */ + P_max = dtoa_max_digits_table[radix - 2]; + E0 = E; + E_found = 0; + P_found = 0; + mant_found = 0; + /* find the minimum number of digits by successive tries */ + P = P_max; /* P_max is guaranteed to work */ + for(;;) { + /* mant_max always fits on 64 bits */ + mant_max1 = pow_ui(radix, P); + /* compute the mantissa in base B */ + E = E0; + for(;;) { + /* XXX: add inexact flag */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDN); + mant = mpb_get_u64(tmp1); + if (mant < mant_max1) + break; + E++; /* at most one iteration is possible */ + } + /* remove useless trailing zero digits */ + while ((mant % radix) == 0) { + mant /= radix; + P--; + } + /* guaranteed to work for P = P_max */ + if (P_found == 0) + goto prec_found; + /* convert back to base 2 */ + mpb_set_u64(tmp1, mant); + m1 = mul_pow_round_to_d(&e1, tmp1, radix1, radix_shift, E - P, JS_RNDN); + // printf("P=%2d: m=0x%016" PRIx64 " e=%d m1=0x%016" PRIx64 " e1=%d\n", P, m, e, m1, e1); + /* Note: (m, e) is never zero here, so the exponent for m1 + = 0 does not matter */ + if (m1 == m && e1 == e) { + prec_found: + P_found = P; + E_found = E; + mant_found = mant; + if (P == 1) + break; + P--; /* try lower exponent */ + } else { + break; + } + } + P = P_found; + E = E_found; + mpb_set_u64(tmp1, mant_found); +#ifdef JS_DTOA_DUMP_STATS + if (radix == 10) { + out_len_count[P - 1]++; + } +#endif + } else if (fmt == JS_DTOA_FORMAT_FRAC) { + int len; + + assert(n_digits >= 0 && n_digits <= JS_DTOA_MAX_DIGITS); + /* P = max_int(E, 1) + n_digits; */ + /* frac is rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, n_digits, JS_RNDNA); + + /* we add one extra digit on the left and remove it if needed + to avoid testing if the result is < radix^P */ + len = output_digits(q, tmp1, radix, max_int(E + 1, 1) + n_digits, + max_int(E + 1, 1)); + if (q[0] == '0' && len >= 2 && q[1] != '.') { + len--; + memmove(q, q + 1, len); + } + q += len; + goto done; + } else { + int pow_shift; + assert(n_digits >= 1 && n_digits <= JS_DTOA_MAX_DIGITS); + P = n_digits; + /* mant_max = radix^P */ + mant_max->len = 1; + mant_max->tab[0] = 1; + pow_shift = mul_pow(mant_max, radix1, radix_shift, P, FALSE, 0); + mpb_shr_round(mant_max, pow_shift, JS_RNDZ); + + for(;;) { + /* fixed and frac are rounded using RNDNA */ + mul_pow_round(tmp1, m, e - 53, radix1, radix_shift, P - E, JS_RNDNA); + if (mpb_cmp(tmp1, mant_max) < 0) + break; + E++; /* at most one iteration is possible */ + } + } + output: + if (fmt == JS_DTOA_FORMAT_FIXED) + E_max = n_digits; + else + E_max = dtoa_max_digits_table[radix - 2] + 4; + if ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_ENABLED || + ((flags & JS_DTOA_EXP_MASK) == JS_DTOA_EXP_AUTO && (E <= -6 || E > E_max))) { + q += output_digits(q, tmp1, radix, P, 1); + E--; + if (radix == 10) { + *q++ = 'e'; + } else if (radix1 == 1 && radix_shift <= 4) { + E *= radix_shift; + *q++ = 'p'; + } else { + *q++ = '@'; + } + if (E < 0) { + *q++ = '-'; + E = -E; + } else { + *q++ = '+'; + } + q += u32toa(q, E); + } else if (E <= 0) { + *q++ = '0'; + *q++ = '.'; + for(i = 0; i < -E; i++) + *q++ = '0'; + q += output_digits(q, tmp1, radix, P, P); + } else { + q += output_digits(q, tmp1, radix, P, min_int(P, E)); + for(i = 0; i < E - P; i++) + *q++ = '0'; + } + done: + *q = '\0'; + dtoa_free(mant_max); + dtoa_free(tmp1); + return q - buf; +} + +static inline int to_digit(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'Z') + return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') + return c - 'a' + 10; + else + return 36; +} + +/* r = r * radix_base + a. radix_base = 0 means radix_base = 2^32 */ +static void mpb_mul1_base(mpb_t *r, limb_t radix_base, limb_t a) +{ + int i; + if (r->tab[0] == 0 && r->len == 1) { + r->tab[0] = a; + } else { + if (radix_base == 0) { + for(i = r->len; i >= 0; i--) { + r->tab[i + 1] = r->tab[i]; + } + r->tab[0] = a; + } else { + r->tab[r->len] = mp_mul1(r->tab, r->tab, r->len, + radix_base, a); + } + r->len++; + mpb_renorm(r); + } +} + +/* XXX: add fast path for small integers */ +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem) +{ + uint64_t *mptr = tmp_mem->mem; + const char *p, *p_start; + limb_t cur_limb, radix_base, extra_digits; + int is_neg, digit_count, limb_digit_count, digits_per_limb, sep, radix1, radix_shift; + int radix_bits, expn, e, max_digits, expn_offset, dot_pos, sig_pos, pos; + mpb_t *tmp0; + double dval; + BOOL is_bin_exp, is_zero, expn_overflow; + uint64_t m, a; + + tmp0 = dtoa_malloc(&mptr, sizeof(mpb_t) + sizeof(limb_t) * DBIGNUM_LEN_MAX); + assert((mptr - tmp_mem->mem) <= sizeof(JSATODTempMem) / sizeof(mptr[0])); + /* optional separator between digits */ + sep = (flags & JS_ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start = p; + } else if (p[0] == '-') { + is_neg = 1; + p++; + p_start = p; + } else { + p_start = p; + } + + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & JS_ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && + radix == 0 && (flags & JS_ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') + goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit((uint8_t)*p) >= radix) + goto fail; + no_prefix: ; + } else { + if (!(flags & JS_ATOD_INT_ONLY) && strstart(p, "Infinity", &p)) + goto overflow; + } + if (radix == 0) + radix = 10; + + cur_limb = 0; + expn_offset = 0; + digit_count = 0; + limb_digit_count = 0; + max_digits = atod_max_digits_table[radix - 2]; + digits_per_limb = digits_per_limb_table[radix - 2]; + radix_base = radix_base_table[radix - 2]; + radix_shift = ctz32(radix); + radix1 = radix >> radix_shift; + if (radix1 == 1) { + /* radix = 2^radix_bits */ + radix_bits = radix_shift; + } else { + radix_bits = 0; + } + tmp0->len = 1; + tmp0->tab[0] = 0; + extra_digits = 0; + pos = 0; + dot_pos = -1; + /* skip leading zeros */ + for(;;) { + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && p[1] == '0') + p++; + if (*p != '0') + break; + p++; + pos++; + } + + sig_pos = pos; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix) && + !(flags & JS_ATOD_INT_ONLY)) { + if (*p == sep) + goto fail; + if (dot_pos >= 0) + break; + dot_pos = pos; + p++; + } + if (*p == sep && p > p_start && to_digit(p[1]) < radix) + p++; + c = to_digit(*p); + if (c >= radix) + break; + p++; + pos++; + if (digit_count < max_digits) { + /* XXX: could be faster when radix_bits != 0 */ + cur_limb = cur_limb * radix + c; + limb_digit_count++; + if (limb_digit_count == digits_per_limb) { + mpb_mul1_base(tmp0, radix_base, cur_limb); + cur_limb = 0; + limb_digit_count = 0; + } + digit_count++; + } else { + extra_digits |= c; + } + } + if (limb_digit_count != 0) { + mpb_mul1_base(tmp0, pow_ui(radix, limb_digit_count), cur_limb); + } + if (digit_count == 0) { + is_zero = TRUE; + expn_offset = 0; + } else { + is_zero = FALSE; + if (dot_pos < 0) + dot_pos = pos; + expn_offset = sig_pos + digit_count - dot_pos; + } + + /* Use the extra digits for rounding if the base is a power of + two. Otherwise they are just truncated. */ + if (radix_bits != 0 && extra_digits != 0) { + tmp0->tab[0] |= 1; + } + + /* parse the exponent, if any */ + expn = 0; + expn_overflow = FALSE; + is_bin_exp = FALSE; + if (!(flags & JS_ATOD_INT_ONLY) && + ((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits >= 1 && radix_bits <= 4 && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + BOOL exp_is_neg; + int c; + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = 0; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = 1; + p++; + } + c = to_digit(*p); + if (c >= 10) + goto fail; /* XXX: could stop before the exponent part */ + expn = c; + p++; + for(;;) { + if (*p == sep && to_digit(p[1]) < 10) + p++; + c = to_digit(*p); + if (c >= 10) + break; + if (!expn_overflow) { + if (unlikely(expn > ((INT32_MAX - 2 - 9) / 10))) { + expn_overflow = TRUE; + } else { + expn = expn * 10 + c; + } + } + p++; + } + if (exp_is_neg) + expn = -expn; + /* if zero result, the exponent can be arbitrarily large */ + if (!is_zero && expn_overflow) { + if (exp_is_neg) + a = 0; + else + a = (uint64_t)0x7ff << 52; /* infinity */ + goto done; + } + } + + if (p == p_start) + goto fail; + + if (is_zero) { + a = 0; + } else { + int expn1; + if (radix_bits != 0) { + if (!is_bin_exp) + expn *= radix_bits; + expn -= expn_offset * radix_bits; + expn1 = expn + digit_count * radix_bits; + if (expn1 >= 1024 + radix_bits) + goto overflow; + else if (expn1 <= -1075) + goto underflow; + m = round_to_d(&e, tmp0, -expn, JS_RNDN); + } else { + expn -= expn_offset; + expn1 = expn + digit_count; + if (expn1 >= max_exponent[radix - 2] + 1) + goto overflow; + else if (expn1 <= min_exponent[radix - 2]) + goto underflow; + m = mul_pow_round_to_d(&e, tmp0, radix1, radix_shift, expn, JS_RNDN); + } + if (m == 0) { + underflow: + a = 0; + } else if (e > 1024) { + overflow: + /* overflow */ + a = (uint64_t)0x7ff << 52; + } else if (e < -1073) { + /* underflow */ + /* XXX: check rounding */ + a = 0; + } else if (e < -1021) { + /* subnormal */ + a = m >> (-e - 1021); + } else { + a = ((uint64_t)(e + 1022) << 52) | (m & (((uint64_t)1 << 52) - 1)); + } + } + done: + a |= (uint64_t)is_neg << 63; + dval = uint64_as_float64(a); + done1: + if (pnext) + *pnext = p; + dtoa_free(tmp0); + return dval; + fail: + dval = NAN; + goto done1; +} diff --git a/dtoa.h b/dtoa.h new file mode 100644 index 00000000..91b025b4 --- /dev/null +++ b/dtoa.h @@ -0,0 +1,83 @@ +/* + * Tiny float64 printing and parsing library + * + * Copyright (c) 2024-2025 Fabrice Bellard + * + * 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. + */ + +//#define JS_DTOA_DUMP_STATS + +/* maximum number of digits for fixed and frac formats */ +#define JS_DTOA_MAX_DIGITS 101 + +/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */ +/* use as many digits as necessary */ +#define JS_DTOA_FORMAT_FREE (0 << 0) +/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */ +#define JS_DTOA_FORMAT_FIXED (1 << 0) +/* force fractional format: [-]dd.dd with n_digits fractional digits. + 0 <= n_digits <= JS_DTOA_MAX_DIGITS */ +#define JS_DTOA_FORMAT_FRAC (2 << 0) +#define JS_DTOA_FORMAT_MASK (3 << 0) + +/* select exponential notation either in fixed or free format */ +#define JS_DTOA_EXP_AUTO (0 << 2) +#define JS_DTOA_EXP_ENABLED (1 << 2) +#define JS_DTOA_EXP_DISABLED (2 << 2) +#define JS_DTOA_EXP_MASK (3 << 2) + +#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */ + +/* only accepts integers (no dot, no exponent) */ +#define JS_ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2) +/* accept _ between digits as a digit separator */ +#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3) + +typedef struct { + uint64_t mem[37]; +} JSDTOATempMem; + +typedef struct { + uint64_t mem[27]; +} JSATODTempMem; + +/* return a maximum bound of the string length */ +int js_dtoa_max_len(double d, int radix, int n_digits, int flags); +/* return the string length */ +int js_dtoa(char *buf, double d, int radix, int n_digits, int flags, + JSDTOATempMem *tmp_mem); +double js_atod(const char *str, const char **pnext, int radix, int flags, + JSATODTempMem *tmp_mem); + +#ifdef JS_DTOA_DUMP_STATS +void js_dtoa_dump_stats(void); +#endif + +/* additional exported functions */ +size_t u32toa(char *buf, uint32_t n); +size_t i32toa(char *buf, int32_t n); +size_t u64toa(char *buf, uint64_t n); +size_t i64toa(char *buf, int64_t n); +size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix); +size_t i64toa_radix(char *buf, int64_t n, unsigned int radix); diff --git a/example.c b/example.c new file mode 100644 index 00000000..5385ede1 --- /dev/null +++ b/example.c @@ -0,0 +1,287 @@ +/* + * Micro QuickJS C API example + * + * 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 "cutils.h" +#include "mquickjs.h" + +#define JS_CLASS_RECTANGLE (JS_CLASS_USER + 0) +#define JS_CLASS_FILLED_RECTANGLE (JS_CLASS_USER + 1) +/* total number of classes */ +#define JS_CLASS_COUNT (JS_CLASS_USER + 2) + +#define JS_CFUNCTION_rectangle_closure_test (JS_CFUNCTION_USER + 0) + +typedef struct { + int x; + int y; +} RectangleData; + +typedef struct { + RectangleData parent; + int color; +} FilledRectangleData; + +static JSValue js_rectangle_constructor(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + JSValue obj; + RectangleData *d; + + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + argc &= ~FRAME_CF_CTOR; + obj = JS_NewObjectClassUser(ctx, JS_CLASS_RECTANGLE); + d = malloc(sizeof(*d)); + JS_SetOpaque(ctx, obj, d); + if (JS_ToInt32(ctx, &d->x, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &d->y, argv[1])) + return JS_EXCEPTION; + return obj; +} + +static void js_rectangle_finalizer(JSContext *ctx, void *opaque) +{ + RectangleData *d = opaque; + free(d); +} + +static JSValue js_rectangle_get_x(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + RectangleData *d; + int class_id = JS_GetClassID(ctx, *this_val); + if (class_id != JS_CLASS_RECTANGLE && class_id != JS_CLASS_FILLED_RECTANGLE) + return JS_ThrowTypeError(ctx, "expecting Rectangle class"); + d = JS_GetOpaque(ctx, *this_val); + return JS_NewInt32(ctx, d->x); +} + +static JSValue js_rectangle_get_y(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + RectangleData *d; + int class_id = JS_GetClassID(ctx, *this_val); + if (class_id != JS_CLASS_RECTANGLE && class_id != JS_CLASS_FILLED_RECTANGLE) + return JS_ThrowTypeError(ctx, "expecting Rectangle class"); + d = JS_GetOpaque(ctx, *this_val); + return JS_NewInt32(ctx, d->y); +} + +static JSValue js_rectangle_closure_test(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv, JSValue params) +{ + return params; +} + +/* C closure test */ +static JSValue js_rectangle_getClosure(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + return JS_NewCFunctionParams(ctx, JS_CFUNCTION_rectangle_closure_test, argv[0]); +} + +/* example to call a JS function. parameters: function to call, parameter */ +static JSValue js_rectangle_call(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + if (JS_StackCheck(ctx, 3)) + return JS_EXCEPTION; + JS_PushArg(ctx, argv[1]); /* parameter */ + JS_PushArg(ctx, argv[0]); /* func name */ + JS_PushArg(ctx, JS_NULL); /* this */ + return JS_Call(ctx, 1); /* single parameter */ +} + +static JSValue js_filled_rectangle_constructor(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + JSGCRef obj_ref; + JSValue *obj; + FilledRectangleData *d; + + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + obj = JS_PushGCRef(ctx, &obj_ref); + + argc &= ~FRAME_CF_CTOR; + *obj = JS_NewObjectClassUser(ctx, JS_CLASS_FILLED_RECTANGLE); + d = malloc(sizeof(*d)); + JS_SetOpaque(ctx, *obj, d); + if (JS_ToInt32(ctx, &d->parent.x, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &d->parent.y, argv[1])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &d->color, argv[2])) + return JS_EXCEPTION; + JS_PopGCRef(ctx, &obj_ref); + return *obj; +} + +static void js_filled_rectangle_finalizer(JSContext *ctx, void *opaque) +{ + FilledRectangleData *d = opaque; + free(d); +} + +static JSValue js_filled_rectangle_get_color(JSContext *ctx, JSValue *this_val, int argc, + JSValue *argv) +{ + FilledRectangleData *d; + if (JS_GetClassID(ctx, *this_val) != JS_CLASS_FILLED_RECTANGLE) + return JS_ThrowTypeError(ctx, "expecting FilledRectangle class"); + d = JS_GetOpaque(ctx, *this_val); + return JS_NewInt32(ctx, d->color); +} + +static 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(' '); + v = argv[i]; + if (JS_IsString(ctx, v)) { + JSCStringBuf buf; + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v, &buf); + fwrite(str, 1, len, stdout); + } else { + JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); + } + } + putchar('\n'); + 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 +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 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) +{ + return JS_NewInt64(ctx, get_time_ms()); +} + +#include "example_stdlib.h" + +static void js_log_func(void *opaque, const void *buf, size_t buf_len) +{ + fwrite(buf, 1, buf_len, stdout); +} + +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; +} + +int main(int argc, const char **argv) +{ + size_t mem_size; + int buf_len; + uint8_t *mem_buf, *buf; + JSContext *ctx; + const char *filename; + JSValue val; + + if (argc < 2) { + printf("usage: example script.js\n"); + exit(1); + } + + filename = argv[1]; + + mem_size = 65536; + mem_buf = malloc(mem_size); + ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); + JS_SetLogFunc(ctx, js_log_func); + + buf = load_file(filename, &buf_len); + val = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + if (JS_IsException(val)) { + JSValue obj; + obj = JS_GetException(ctx); + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + printf("\n"); + exit(1); + } + + JS_FreeContext(ctx); + free(mem_buf); + return 0; +} diff --git a/example_stdlib.c b/example_stdlib.c new file mode 100644 index 00000000..82d49434 --- /dev/null +++ b/example_stdlib.c @@ -0,0 +1,36 @@ +#include +#include +#include + +#include "mquickjs_build.h" + +/* simple class example */ + +static const JSPropDef js_rectangle_proto[] = { + JS_CGETSET_DEF("x", js_rectangle_get_x, NULL ), + JS_CGETSET_DEF("y", js_rectangle_get_y, NULL ), + JS_PROP_END, +}; + +static const JSPropDef js_rectangle[] = { + JS_CFUNC_DEF("getClosure", 1, js_rectangle_getClosure ), + JS_CFUNC_DEF("call", 2, js_rectangle_call ), + JS_PROP_END, +}; + +static const JSClassDef js_rectangle_class = + JS_CLASS_DEF("Rectangle", 2, js_rectangle_constructor, JS_CLASS_RECTANGLE, js_rectangle, js_rectangle_proto, NULL, js_rectangle_finalizer); + +static const JSPropDef js_filled_rectangle_proto[] = { + JS_CGETSET_DEF("color", js_filled_rectangle_get_color, NULL ), + JS_PROP_END, +}; + +/* inherit from Rectangle */ +static const JSClassDef js_filled_rectangle_class = + JS_CLASS_DEF("FilledRectangle", 3, js_filled_rectangle_constructor, JS_CLASS_FILLED_RECTANGLE, NULL, js_filled_rectangle_proto, &js_rectangle_class, js_filled_rectangle_finalizer); + +/* include the full standard library too */ + +#define CONFIG_CLASS_EXAMPLE +#include "mqjs_stdlib.c" diff --git a/libm.c b/libm.c new file mode 100644 index 00000000..bff8ed1c --- /dev/null +++ b/libm.c @@ -0,0 +1,2260 @@ +/* + * Tiny Math Library + * + * Copyright (c) 2024 Fabrice Bellard + * + * 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. + */ +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +#include +#include +#include +#define NDEBUG +#include + +#include "cutils.h" +#include "libm.h" + +/* define to enable softfloat support */ +//#define USE_SOFTFLOAT +/* use less code for tan() but currently less precise */ +#define USE_TAN_SHORTCUT + +/* + TODO: + - smaller scalbn implementation ? + - add all ES6 math functions +*/ +/* + tc32: + - base: size libm+libgcc: 21368 + - size libm+libgcc: 11832 + + x86: + - size libm softfp: 18510 + - size libm hardfp: 10051 + + TODO: + - unify i32 bit and i64 bit conversions + - unify comparisons operations +*/ + +typedef enum { + RM_RNE, /* Round to Nearest, ties to Even */ + RM_RTZ, /* Round towards Zero */ + RM_RDN, /* Round Down (must be even) */ + RM_RUP, /* Round Up (must be odd) */ + RM_RMM, /* Round to Nearest, ties to Max Magnitude */ + RM_RMMUP, /* only for rint_sf64(): round to nearest, ties to +inf (must be odd) */ +} RoundingModeEnum; + +#define FFLAG_INVALID_OP (1 << 4) +#define FFLAG_DIVIDE_ZERO (1 << 3) +#define FFLAG_OVERFLOW (1 << 2) +#define FFLAG_UNDERFLOW (1 << 1) +#define FFLAG_INEXACT (1 << 0) + +typedef enum { + FMINMAX_PROP, /* min(1, qNaN/sNaN) -> qNaN */ + FMINMAX_IEEE754_2008, /* min(1, qNaN) -> 1, min(1, sNaN) -> qNaN */ + FMINMAX_IEEE754_201X, /* min(1, qNaN/sNaN) -> 1 */ +} SoftFPMinMaxTypeEnum; + +typedef uint32_t sfloat32; +typedef uint64_t sfloat64; + +#define F_STATIC static __maybe_unused +#define F_USE_FFLAGS 0 + +#define F_SIZE 32 +#define F_NORMALIZE_ONLY +#include "softfp_template.h" + +#define F_SIZE 64 +#include "softfp_template.h" + +#ifdef USE_SOFTFLOAT + +/* wrappers */ +double __adddf3(double a, double b) +{ + return uint64_as_float64(add_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +double __subdf3(double a, double b) +{ + return uint64_as_float64(sub_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +double __muldf3(double a, double b) +{ + return uint64_as_float64(mul_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +double __divdf3(double a, double b) +{ + return uint64_as_float64(div_sf64(float64_as_uint64(a), + float64_as_uint64(b), RM_RNE)); +} + +/* comparisons */ + +int __eqdf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + return ret; +} + +/* NaN: return 0 */ +int __nedf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + if (unlikely(ret == 2)) + return 0; + else + return ret; +} + +int __ledf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + return ret; +} + +int __ltdf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + return ret; +} + +int __gedf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + if (unlikely(ret == 2)) + return -1; + else + return ret; +} + +int __gtdf2(double a, double b) +{ + int ret = cmp_sf64(float64_as_uint64(a), + float64_as_uint64(b)); + if (unlikely(ret == 2)) + return -1; + else + return ret; +} + +int __unorddf2(double a, double b) +{ + return isnan_sf64(float64_as_uint64(a)) || + isnan_sf64(float64_as_uint64(b)); +} + +/* conversions */ +double __floatsidf(int32_t a) +{ + return uint64_as_float64(cvt_i32_sf64(a, RM_RNE)); +} + +double __floatdidf(int64_t a) +{ + return uint64_as_float64(cvt_i64_sf64(a, RM_RNE)); +} + +double __floatunsidf(unsigned int a) +{ + return uint64_as_float64(cvt_u32_sf64(a, RM_RNE)); +} + +int32_t __fixdfsi(double a) +{ + return cvt_sf64_i32(float64_as_uint64(a), RM_RTZ); +} + +double __extendsfdf2(float a) +{ + return uint64_as_float64(cvt_sf32_sf64(float_as_uint(a))); +} + +float __truncdfsf2(double a) +{ + return uint_as_float(cvt_sf64_sf32(float64_as_uint64(a), RM_RNE)); +} + +double js_sqrt(double a) +{ + return uint64_as_float64(sqrt_sf64(float64_as_uint64(a), RM_RNE)); +} + +#if defined(__tc32__) +/* XXX: check */ +int __fpclassifyd(double a) +{ + uint64_t u = float64_as_uint64(a); + uint32_t h = u >> 32; + uint32_t l = u; + + h &= 0x7fffffff; + if (h >= 0x7ff00000) { + if (h == 0x7ff00000 && l == 0) + return FP_INFINITE; + else + return FP_NAN; + } else if (h < 0x00100000) { + if (h == 0 && l == 0) + return FP_ZERO; + else + return FP_SUBNORMAL; + } else { + return FP_NORMAL; + } +} +#endif + +#endif /* USE_SOFTFLOAT */ + +int32_t js_lrint(double a) +{ + return cvt_sf64_i32(float64_as_uint64(a), RM_RNE); +} + +double js_fmod(double a, double b) +{ + return uint64_as_float64(fmod_sf64(float64_as_uint64(a), float64_as_uint64(b))); +} + +/* supported rounding modes: RM_UP, RM_DN, RM_RTZ, RM_RMMUP, RM_RMM */ +static double rint_sf64(double a, RoundingModeEnum rm) +{ + uint64_t u = float64_as_uint64(a); + uint64_t frac_mask, one, m, addend; + int e; + unsigned int s; + + e = ((u >> 52) & 0x7ff) - 0x3ff; + s = u >> 63; + if (e < 0) { + m = u & (((uint64_t)1 << 52) - 1); + if (e == -0x3ff && m == 0) { + /* zero: nothing to do */ + } else { + /* abs(a) < 1 */ + s = u >> 63; + one = (uint64_t)0x3ff << 52; + u = 0; + switch(rm) { + case RM_RUP: + case RM_RDN: + if (s ^ (rm & 1)) + u = one; + break; + default: + case RM_RMM: + case RM_RMMUP: + if (e == -1 && (m != 0 || (m == 0 && (!s || rm == RM_RMM)))) + u = one; + break; + case RM_RTZ: + break; + } + u |= (uint64_t)s << 63; + } + } else if (e < 52) { + one = (uint64_t)1 << (52 - e); + frac_mask = one - 1; + addend = 0; + switch(rm) { + case RM_RMMUP: + addend = (one >> 1) - s; + break; + default: + case RM_RMM: + addend = (one >> 1); + break; + case RM_RTZ: + break; + case RM_RUP: + case RM_RDN: + if (s ^ (rm & 1)) + addend = one - 1; + break; + } + u += addend; + u &= ~frac_mask; /* truncate to an integer */ + } + /* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */ + return uint64_as_float64(u); +} + +double js_floor(double x) +{ + return rint_sf64(x, RM_RDN); +} + +double js_ceil(double x) +{ + return rint_sf64(x, RM_RUP); +} + +double js_trunc(double x) +{ + return rint_sf64(x, RM_RTZ); +} + +double js_round_inf(double x) +{ + return rint_sf64(x, RM_RMMUP); +} + +double js_fabs(double x) +{ + uint64_t a = float64_as_uint64(x); + return uint64_as_float64(a & 0x7fffffffffffffff); +} + +/************************************************************/ +/* libm */ + +#define EXTRACT_WORDS(ix0,ix1,d) \ + do { \ + uint64_t __u = float64_as_uint64(d); \ + (ix0) = (uint32_t)(__u >> 32); \ + (ix1) = (uint32_t)__u; \ + } while (0) + +static uint32_t get_high_word(double d) +{ + return float64_as_uint64(d) >> 32; +} + +static double set_high_word(double d, uint32_t h) +{ + uint64_t u = float64_as_uint64(d); + u = (u & 0xffffffff) | ((uint64_t)h << 32); + return uint64_as_float64(u); +} + +static uint32_t get_low_word(double d) +{ + return float64_as_uint64(d); +} + +/* set the low 32 bits to zero */ +static double zero_low(double x) +{ + uint64_t u = float64_as_uint64(x); + u &= 0xffffffff00000000; + return uint64_as_float64(u); +} + +static double float64_from_u32(uint32_t h, uint32_t l) +{ + return uint64_as_float64(((uint64_t)h << 32) | l); +} + +static const double zero = 0.0; +static const double one = 1.0; +static const double half = 5.00000000000000000000e-01; +static const double tiny = 1.0e-300; +static const double huge = 1.0e300; + +/* @(#)s_scalbn.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * scalbn (double x, int n) + * scalbn(x,n) returns x* 2**n computed by exponent + * manipulation rather than by actually performing an + * exponentiation or a multiplication. + */ + +static const double + two54 = 1.80143985094819840000e+16, /* 0x43500000, 0x00000000 */ + twom54 = 5.55111512312578270212e-17; /* 0x3C900000, 0x00000000 */ + +double js_scalbn(double x, int n) +{ + int k,hx,lx; + EXTRACT_WORDS(hx, lx, x); + k = (hx&0x7ff00000)>>20; /* extract exponent */ + if (k==0) { /* 0 or subnormal x */ + if ((lx|(hx&0x7fffffff))==0) return x; /* +-0 */ + x *= two54; + hx = get_high_word(x); + k = ((hx&0x7ff00000)>>20) - 54; + if (n< -50000) return tiny*x; /*underflow*/ + } + if (k==0x7ff) return x+x; /* NaN or Inf */ + k = k+n; + if (k > 0x7fe) return huge*copysign(huge,x); /* overflow */ + if (k > 0) /* normal result */ + {x = set_high_word(x, (hx&0x800fffff)|(k<<20)); return x;} + if (k <= -54) { + if (n > 50000) /* in case integer overflow in n+k */ + return huge*copysign(huge,x); /*overflow*/ + else + return tiny*copysign(tiny,x); /*underflow*/ + } + k += 54; /* subnormal result */ + x = set_high_word(x, (hx&0x800fffff)|(k<<20)); + return x*twom54; +} + +#ifndef USE_SOFTFLOAT +/* @(#)e_sqrt.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_sqrt(x) + * Return correctly rounded sqrt. + * ------------------------------------------ + * | Use the hardware sqrt if you have one | + * ------------------------------------------ + * Method: + * Bit by bit method using integer arithmetic. (Slow, but portable) + * 1. Normalization + * Scale x to y in [1,4) with even powers of 2: + * find an integer k such that 1 <= (y=x*2^(2k)) < 4, then + * sqrt(x) = 2^k * sqrt(y) + * 2. Bit by bit computation + * Let q = sqrt(y) truncated to i bit after binary point (q = 1), + * i 0 + * i+1 2 + * s = 2*q , and y = 2 * ( y - q ). (1) + * i i i i + * + * To compute q from q , one checks whether + * i+1 i + * + * -(i+1) 2 + * (q + 2 ) <= y. (2) + * i + * -(i+1) + * If (2) is false, then q = q ; otherwise q = q + 2 . + * i+1 i i+1 i + * + * With some algebraic manipulation, it is not difficult to see + * that (2) is equivalent to + * -(i+1) + * s + 2 <= y (3) + * i i + * + * The advantage of (3) is that s and y can be computed by + * i i + * the following recurrence formula: + * if (3) is false + * + * s = s , y = y ; (4) + * i+1 i i+1 i + * + * otherwise, + * -i -(i+1) + * s = s + 2 , y = y - s - 2 (5) + * i+1 i i+1 i i + * + * One may easily use induction to prove (4) and (5). + * Note. Since the left hand side of (3) contain only i+2 bits, + * it does not necessary to do a full (53-bit) comparison + * in (3). + * 3. Final rounding + * After generating the 53 bits result, we compute one more bit. + * Together with the remainder, we can decide whether the + * result is exact, bigger than 1/2ulp, or less than 1/2ulp + * (it will never equal to 1/2ulp). + * The rounding mode can be detected by checking whether + * huge + tiny is equal to huge, and whether huge - tiny is + * equal to huge for some floating point number "huge" and "tiny". + * + * Special cases: + * sqrt(+-0) = +-0 ... exact + * sqrt(inf) = inf + * sqrt(-ve) = NaN ... with invalid signal + * sqrt(NaN) = NaN ... with invalid signal for signaling NaN + * + * Other methods : see the appended file at the end of the program below. + *--------------- + */ + +#if defined(__aarch64__) || defined(__x86_64__) || defined(__i386__) +/* hardware sqrt is available */ +double js_sqrt(double x) +{ + return sqrt(x); +} +#else +double js_sqrt(double x) +{ + double z; + int sign = (int)0x80000000; + unsigned r,t1,s1,ix1,q1; + int ix0,s0,q,m,t,i; + + EXTRACT_WORDS(ix0, ix1, x); + + /* take care of Inf and NaN */ + if((ix0&0x7ff00000)==0x7ff00000) { + return x*x+x; /* sqrt(NaN)=NaN, sqrt(+inf)=+inf + sqrt(-inf)=sNaN */ + } + /* take care of zero */ + if(ix0<=0) { + if(((ix0&(~sign))|ix1)==0) return x;/* sqrt(+-0) = +-0 */ + else if(ix0<0) + return (x-x)/(x-x); /* sqrt(-ve) = sNaN */ + } + /* normalize x */ + m = (ix0>>20); + if(m==0) { /* subnormal x */ + while(ix0==0) { + m -= 21; + ix0 |= (ix1>>11); ix1 <<= 21; + } + for(i=0;(ix0&0x00100000)==0;i++) ix0<<=1; + m -= i-1; + ix0 |= (ix1>>(32-i)); + ix1 <<= i; + } + m -= 1023; /* unbias exponent */ + ix0 = (ix0&0x000fffff)|0x00100000; + if(m&1){ /* odd m, double x to make it even */ + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + } + m >>= 1; /* m = [m/2] */ + + /* generate sqrt(x) bit by bit */ + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + q = q1 = s0 = s1 = 0; /* [q,q1] = sqrt(x) */ + r = 0x00200000; /* r = moving bit from right to left */ + + while(r!=0) { + t = s0+r; + if(t<=ix0) { + s0 = t+r; + ix0 -= t; + q += r; + } + ix0 += ix0 + ((ix1&sign)>>31); + ix1 += ix1; + r>>=1; + } + + r = sign; + while(r!=0) { + t1 = s1+r; + t = s0; + if((t>31); + ix1 += ix1; + r>>=1; + } + + /* use floating add to find out rounding direction */ + if((ix0|ix1)!=0) { + z = one-tiny; /* trigger inexact flag */ + if (z>=one) { + z = one+tiny; + if (q1==(unsigned)0xffffffff) { q1=0; q += 1;} + else if (z>one) { + if (q1==(unsigned)0xfffffffe) q+=1; + q1+=2; + } else + q1 += (q1&1); + } + } + ix0 = (q>>1)+0x3fe00000; + ix1 = q1>>1; + if ((q&1)==1) ix1 |= sign; + ix0 += (m <<20); + return float64_from_u32(ix0, ix1); +} +#endif /* !hardware sqrt */ +#endif /* USE_SOFTFLOAT */ + +/* to have smaller code */ +/* n >= 1 */ +/* return sum(x^i*coefs[i] with i = 0 ... n - 1 and n >= 1 using + Horner algorithm. */ +static double eval_poly(double x, const double *coefs, int n) +{ + double r; + int i; + r = coefs[n - 1]; + for(i = n - 2; i >= 0; i--) { + r = r * x + coefs[i]; + } + return r; +} + +/* @(#)k_sin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __kernel_sin( x, y, iy) + * kernel sin function on [-pi/4, pi/4], pi/4 ~ 0.7854 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * Input iy indicates whether y is 0. (if iy=0, y assume to be 0). + * + * Algorithm + * 1. Since sin(-x) = -sin(x), we need only to consider positive x. + * 2. if x < 2^-27 (hx<0x3e400000 0), return x with inexact if x!=0. + * 3. sin(x) is approximated by a polynomial of degree 13 on + * [0,pi/4] + * 3 13 + * sin(x) ~ x + S1*x + ... + S6*x + * where + * + * |sin(x) 2 4 6 8 10 12 | -58 + * |----- - (1+S1*x +S2*x +S3*x +S4*x +S5*x +S6*x )| <= 2 + * | x | + * + * 4. sin(x+y) = sin(x) + sin'(x')*y + * ~ sin(x) + (1-x*x/2)*y + * For better accuracy, let + * 3 2 2 2 2 + * r = x *(S2+x *(S3+x *(S4+x *(S5+x *S6)))) + * then 3 2 + * sin(x) = x + (S1*x + (x *(r-y/2)+y)) + */ + +static const double +S1 = -1.66666666666666324348e-01; /* 0xBFC55555, 0x55555549 */ +static const double S_tab[] = { + /* S2 */ 8.33333333332248946124e-03, /* 0x3F811111, 0x1110F8A6 */ + /* S3 */ -1.98412698298579493134e-04, /* 0xBF2A01A0, 0x19C161D5 */ + /* S4 */ 2.75573137070700676789e-06, /* 0x3EC71DE3, 0x57B1FE7D */ + /* S5 */ -2.50507602534068634195e-08, /* 0xBE5AE5E6, 0x8A2B9CEB */ + /* S6 */ 1.58969099521155010221e-10, /* 0x3DE5D93A, 0x5ACFD57C */ +}; + +/* iy=0 if y is zero */ +static double __kernel_sin(double x, double y, int iy) +{ + double z,r,v; + int ix; + ix = get_high_word(x)&0x7fffffff; /* high word of x */ + if(ix<0x3e400000) /* |x| < 2**-27 */ + {if((int)x==0) return x;} /* generate inexact */ + z = x*x; + v = z*x; + r = eval_poly(z, S_tab, 5); + if(iy==0) return x+v*(S1+z*r); + else return x-((z*(half*y-v*r)-y)-v*S1); +} + + +/* @(#)k_cos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * __kernel_cos( x, y ) + * kernel cos function on [-pi/4, pi/4], pi/4 ~ 0.785398164 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * + * Algorithm + * 1. Since cos(-x) = cos(x), we need only to consider positive x. + * 2. if x < 2^-27 (hx<0x3e400000 0), return 1 with inexact if x!=0. + * 3. cos(x) is approximated by a polynomial of degree 14 on + * [0,pi/4] + * 4 14 + * cos(x) ~ 1 - x*x/2 + C1*x + ... + C6*x + * where the remez error is + * + * | 2 4 6 8 10 12 14 | -58 + * |cos(x)-(1-.5*x +C1*x +C2*x +C3*x +C4*x +C5*x +C6*x )| <= 2 + * | | + * + * 4 6 8 10 12 14 + * 4. let r = C1*x +C2*x +C3*x +C4*x +C5*x +C6*x , then + * cos(x) = 1 - x*x/2 + r + * since cos(x+y) ~ cos(x) - sin(x)*y + * ~ cos(x) - x*y, + * a correction term is necessary in cos(x) and hence + * cos(x+y) = 1 - (x*x/2 - (r - x*y)) + * For better accuracy when x > 0.3, let qx = |x|/4 with + * the last 32 bits mask off, and if x > 0.78125, let qx = 0.28125. + * Then + * cos(x+y) = (1-qx) - ((x*x/2-qx) - (r-x*y)). + * Note that 1-qx and (x*x/2-qx) is EXACT here, and the + * magnitude of the latter is at least a quarter of x*x/2, + * thus, reducing the rounding error in the subtraction. + */ + +static const double C_tab[] = { + /* C1 */ 4.16666666666666019037e-02, /* 0x3FA55555, 0x5555554C */ + /* C2 */ -1.38888888888741095749e-03, /* 0xBF56C16C, 0x16C15177 */ + /* C3 */ 2.48015872894767294178e-05, /* 0x3EFA01A0, 0x19CB1590 */ + /* C4 */ -2.75573143513906633035e-07, /* 0xBE927E4F, 0x809C52AD */ + /* C5 */ 2.08757232129817482790e-09, /* 0x3E21EE9E, 0xBDB4B1C4 */ + /* C6 */ -1.13596475577881948265e-11, /* 0xBDA8FAE9, 0xBE8838D4 */ +}; + +static double __kernel_cos(double x, double y) +{ + double a,hz,z,r,qx; + int ix; + ix = get_high_word(x)&0x7fffffff; /* ix = |x|'s high word*/ + if(ix<0x3e400000) { /* if x < 2**27 */ + if(((int)x)==0) return one; /* generate inexact */ + } + z = x*x; + r = z * eval_poly(z, C_tab, 6); + if(ix < 0x3FD33333) /* if |x| < 0.3 */ + return one - (0.5*z - (z*r - x*y)); + else { + if(ix > 0x3fe90000) { /* x > 0.78125 */ + qx = 0.28125; + } else { + qx = float64_from_u32(ix-0x00200000, 0); /* x/4 */ + } + hz = 0.5*z-qx; + a = one-qx; + return a - (hz - (z*r-x*y)); + } +} + +/* rem_pio2 */ + +#define T_LEN 19 + +/* T[i] = floor(2^(64*(T_LEN - i))/2pi) mod 2^64 */ +static const uint64_t T[T_LEN] = { + 0x1580cc11bf1edaea, + 0x9afed7ec47e35742, + 0xcf41ce7de294a4ba, + 0x5d49eeb1faf97c5e, + 0xd3d18fd9a797fa8b, + 0xdb4d9fb3c9f2c26d, + 0xfbcbc462d6829b47, + 0xc7fe25fff7816603, + 0x272117e2ef7e4a0e, + 0x4e64758e60d4ce7d, + 0x3a671c09ad17df90, + 0xba208d7d4baed121, + 0x3f877ac72c4a69cf, + 0x01924bba82746487, + 0x6dc91b8e909374b8, + 0x7f9458eaf7aef158, + 0x36d8a5664f10e410, + 0x7f09d5f47d4d3770, + 0x28be60db9391054a, /* high part */ +}; + +/* PIO2[i] = floor(2^(64*(2 - i))*PI/4) mod 2^64 */ +static const uint64_t PIO4[2] = { + 0xc4c6628b80dc1cd1, + 0xc90fdaa22168c234, +}; + +static uint64_t get_u64_at_bit(const uint64_t *tab, uint32_t tab_len, + uint32_t pos) +{ + uint64_t v; + uint32_t p = pos / 64; + int shift = pos % 64; + v = tab[p] >> shift; + if (shift != 0 && (p + 1) < tab_len) + v |= tab[p + 1] << (64 - shift); + return v; +} + +/* return n = round(x/(pi/2)) (only low 2 bits are valid) and + (y[0], y[1]) = x - (pi/2) * n. + 'x' must be finite and such as abs(x) >= PI/4. + The initial algorithm comes from the CORE-MATH project. +*/ +static int rem_pio2_large(double x, double *y) +{ + uint64_t m; + int e, sgn, n, rnd, j, i, y_sgn; + uint64_t c[2], d[3]; + uint64_t r0, r1; + uint32_t carry, carry1; + + m = float64_as_uint64(x); + sgn = m >> 63; + e = (m >> 52) & 0x7ff; + /* 1022 <= e <= 2047 */ + m = (m & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + + /* multiply m by T[j:j+192] */ + j = T_LEN * 64 - (e - 1075) - 192; + /* 53 <= j <= 1077 */ + // printf("m=0x%016" PRIx64 " e=%d j=%d\n", m, e, j); + for(i = 0; i < 3; i++) { + d[i] = get_u64_at_bit(T, T_LEN, j + i * 64); + } + r1 = mul_u64(&r0, m, d[0]); + c[0] = r1; + r1 = mul_u64(&r0, m, d[1]); + c[0] += r0; + carry = c[0] < r0; + c[1] = r1 + carry; + mul_u64(&r0, m, d[2]); + c[1] += r0; + + // printf("c0=%016" PRIx64 " %016" PRIx64 "\n", c[1], c[0]); + + /* n = round(c[1]/2^62) */ + n = c[1] >> 62; + rnd = (c[1] >> 61) & 1; + n += rnd; + /* c = c * 4 - n */ + c[1] = (c[1] << 2) | (c[0] >> 62); + c[0] = (c[0] << 2); + y_sgn = sgn; + if (rnd) { + /* 'y' sign change */ + y_sgn ^= 1; + c[0] = ~c[0]; + c[1] = ~c[1]; + if (++c[0] == 0) + c[1]++; + } + // printf("c1=%016" PRIx64 " %016" PRIx64 " n=%d sgn=%d\n", c[1], c[0], n, sgn); + + /* c = c * (PI/2) (high 128 bits of the product) */ + r1 = mul_u64(&r0, c[0], PIO4[1]); + d[0] = r0; + d[1] = r1; + + r1 = mul_u64(&r0, c[1], PIO4[0]); + d[0] += r0; + carry = d[0] < r0; + d[1] += r1; + carry1 = d[1] < r1; + d[1] += carry; + carry1 |= (d[1] < carry); + d[2] = carry1; + + r1 = mul_u64(&r0, c[1], PIO4[1]); + d[1] += r0; + carry = d[1] < r0; + d[2] += r1 + carry; + + /* convert d to two float64 */ + // printf("d=%016" PRIx64 " %016" PRIx64 "\n", d[2], d[1]); + if (d[2] == 0) { + /* should never happen (see ARGUMENT REDUCTION FOR HUGE + ARGUMENTS: Good to the Last Bit, K. C. Ng and the members + of the FP group of SunPro */ + y[0] = y[1] = 0; + } else { + uint64_t m0, m1; + int e1; + + e = clz64(d[2]); + d[2] = (d[2] << e) | (d[1] >> (64 - e)); + d[1] = (d[1] << e); + // printf("d=%016" PRIx64 " %016" PRIx64 " e=%d\n", d[2], d[1], e); + m0 = (d[2] >> 11) & (((uint64_t)1 << 52) - 1); + m1 = ((d[2] & 0x7ff) << 42) | (d[1] >> (64 - 42)); + y[0] = uint64_as_float64(((uint64_t)y_sgn << 63) | + ((uint64_t)(1023 - e) << 52) | + m0); + if (m1 == 0) { + y[1] = 0; + } else { + e1 = clz64(m1) - 11; + m1 = (m1 << e1) & (((uint64_t)1 << 52) - 1); + y[1] = uint64_as_float64(((uint64_t)y_sgn << 63) | + ((uint64_t)(1023 - e - 53 - e1) << 52) | + m1); + } + } + if (sgn) + n = -n; + return n; +} + +#ifdef USE_SOFTFLOAT +/* when using softfloat, the FP reduction should be not much faster + than the generic one */ +int js_rem_pio2(double x, double *y) +{ + int ix,hx; + + hx = get_high_word(x); /* high word of x */ + ix = hx&0x7fffffff; + if(ix<=0x3fe921fb) { + /* |x| ~<= pi/4 , no need for reduction */ + y[0] = x; + y[1] = 0; + return 0; + } + /* + * all other (large) arguments + */ + if(ix>=0x7ff00000) { /* x is inf or NaN */ + y[0]=y[1]=x-x; + return 0; + } + + return rem_pio2_large(x, y); +} +#else +/* + * invpio2: 53 bits of 2/pi + * pio2_1: first 33 bit of pi/2 + * pio2_1t: pi/2 - pio2_1 + * pio2_2: second 33 bit of pi/2 + * pio2_2t: pi/2 - (pio2_1+pio2_2) + * pio2_3: third 33 bit of pi/2 + * pio2_3t: pi/2 - (pio2_1+pio2_2+pio2_3) + */ + +static const double +invpio2 = 6.36619772367581382433e-01; /* 0x3FE45F30, 0x6DC9C883 */ +static const double pio2_tab[3] = { + /* pio2_1 */ 1.57079632673412561417e+00, /* 0x3FF921FB, 0x54400000 */ + /* pio2_2 */ 6.07710050630396597660e-11, /* 0x3DD0B461, 0x1A600000 */ + /* pio2_3 */ 2.02226624871116645580e-21, /* 0x3BA3198A, 0x2E000000 */ +}; +static const double pio2_t_tab[3] = { + /* pio2_1t */ 6.07710050650619224932e-11, /* 0x3DD0B461, 0x1A626331 */ + /* pio2_2t */ 2.02226624879595063154e-21, /* 0x3BA3198A, 0x2E037073 */ + /* pio2_3t */ 8.47842766036889956997e-32, /* 0x397B839A, 0x252049C1 */ +}; +static uint8_t rem_pio2_emax[2] = { 16, 49 }; + +int js_rem_pio2(double x, double *y) +{ + double w,t,r,fn; + int i,j,n,ix,hx,it; + + hx = get_high_word(x); /* high word of x */ + ix = hx&0x7fffffff; + if(ix<=0x3fe921fb) { + /* |x| ~<= pi/4 , no need for reduction */ + y[0] = x; + y[1] = 0; + return 0; + } + if(ix<=0x413921fb) { /* |x| ~<= 2^19*(pi/2), medium size */ + t = fabs(x); + if (ix<0x4002d97c) { + /* |x| < 3pi/4, special case with n=+-1 */ + n = 1; + fn = 1; + } else { + n = (int) (t*invpio2+half); + fn = (double)n; + } + + it = 0; + for(;;) { + /* 1st round good to 85 bit */ + /* 2nd iteration needed, good to 118 */ + /* 3rd iteration need, 151 bits acc */ + r = t-fn*pio2_tab[it]; + w = fn*pio2_t_tab[it]; + y[0] = r-w; + j = ix>>20; + i = j-(((get_high_word(y[0]))>>20)&0x7ff); + if (it == 2 || i <= rem_pio2_emax[it]) + break; + t = r; + it++; + } + y[1] = (r-y[0])-w; + if (hx<0) { + y[0] = -y[0]; + y[1] = -y[1]; + return -n; + } else { + return n; + } + } + /* + * all other (large) arguments + */ + if(ix>=0x7ff00000) { /* x is inf or NaN */ + y[0]=y[1]=x-x; + return 0; + } + + return rem_pio2_large(x, y); +} +#endif /* !USE_SOFTFLOAT */ + +/* @(#)s_sin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* sin(x) + * Return sine function of x. + * + * kernel function: + * __kernel_sin ... sine function on [-pi/4,pi/4] + * __kernel_cos ... cose function on [-pi/4,pi/4] + * __ieee754_rem_pio2 ... argument reduction routine + * + * Method. + * Let S,C and T denote the sin, cos and tan respectively on + * [-PI/4, +PI/4]. Reduce the argument x to y1+y2 = x-k*pi/2 + * in [-pi/4 , +pi/4], and let n = k mod 4. + * We have + * + * n sin(x) cos(x) tan(x) + * ---------------------------------------------------------- + * 0 S C T + * 1 C -S -1/T + * 2 -S -C T + * 3 -C S -1/T + * ---------------------------------------------------------- + * + * Special cases: + * Let trig be any of sin, cos, or tan. + * trig(+-INF) is NaN, with signals; + * trig(NaN) is that NaN; + * + * Accuracy: + * TRIG(x) returns trig(x) nearly rounded + */ + +/* flag = 0: sin() + flag = 1: cos() + flag = 3: tan() +*/ +static double js_sin_cos(double x, int flag) +{ + double y[2], z, s, c; + int ix; + uint32_t n; + + /* High word of x. */ + ix = get_high_word(x); + + /* sin(Inf or NaN) is NaN */ + if (ix>=0x7ff00000) + return x-x; + + n = js_rem_pio2(x,y); + s = c = 0; /* avoid warning */ + if (flag == 3 || (n & 1) == flag) { + s = __kernel_sin(y[0],y[1],1); + if (flag != 3) + goto done; + } + if (flag == 3 || (n & 1) != flag) { + c = __kernel_cos(y[0],y[1]); + if (flag != 3) { + s = c; + goto done; + } + } + if (n & 1) + z = -c / s; + else + z = s / c; + return z; +done: + if ((n + flag) & 2) + s = -s; + return s; +} + +double js_sin(double x) +{ + return js_sin_cos(x, 0); +} + +double js_cos(double x) +{ + return js_sin_cos(x, 1); +} + +#ifdef USE_TAN_SHORTCUT +double js_tan(double x) +{ + return js_sin_cos(x, 3); +} +#endif + +#ifndef USE_TAN_SHORTCUT +/* + * ==================================================== + * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* INDENT OFF */ +/* __kernel_tan( x, y, k ) + * kernel tan function on [-pi/4, pi/4], pi/4 ~ 0.7854 + * Input x is assumed to be bounded by ~pi/4 in magnitude. + * Input y is the tail of x. + * Input k indicates whether tan (if k = 1) or -1/tan (if k = -1) is returned. + * + * Algorithm + * 1. Since tan(-x) = -tan(x), we need only to consider positive x. + * 2. if x < 2^-28 (hx<0x3e300000 0), return x with inexact if x!=0. + * 3. tan(x) is approximated by a odd polynomial of degree 27 on + * [0,0.67434] + * 3 27 + * tan(x) ~ x + T1*x + ... + T13*x + * where + * + * |tan(x) 2 4 26 | -59.2 + * |----- - (1+T1*x +T2*x +.... +T13*x )| <= 2 + * | x | + * + * Note: tan(x+y) = tan(x) + tan'(x)*y + * ~ tan(x) + (1+x*x)*y + * Therefore, for better accuracy in computing tan(x+y), let + * 3 2 2 2 2 + * r = x *(T2+x *(T3+x *(...+x *(T12+x *T13)))) + * then + * 3 2 + * tan(x+y) = x + (T1*x + (x *(r+y)+y)) + * + * 4. For x in [0.67434,pi/4], let y = pi/4 - x, then + * tan(x) = tan(pi/4-y) = (1-tan(y))/(1+tan(y)) + * = 1 - 2*(tan(y) - (tan(y)^2)/(1+tan(y))) + */ + +static const double T0 = 3.33333333333334091986e-01; /* 3FD55555, 55555563 */ +static const double T_even[] = { + 5.39682539762260521377e-02, /* 3FABA1BA, 1BB341FE */ + 8.86323982359930005737e-03, /* 3F8226E3, E96E8493 */ + 1.45620945432529025516e-03, /* 3F57DBC8, FEE08315 */ + 2.46463134818469906812e-04, /* 3F3026F7, 1A8D1068 */ + 7.14072491382608190305e-05, /* 3F12B80F, 32F0A7E9 */ + 2.59073051863633712884e-05, /* 3EFB2A70, 74BF7AD4 */ +}; +static const double T_odd[] = { + 1.33333333333201242699e-01, /* 3FC11111, 1110FE7A */ + 2.18694882948595424599e-02, /* 3F9664F4, 8406D637 */ + 3.59207910759131235356e-03, /* 3F6D6D22, C9560328 */ + 5.88041240820264096874e-04, /* 3F4344D8, F2F26501 */ + 7.81794442939557092300e-05, /* 3F147E88, A03792A6 */ + -1.85586374855275456654e-05, /* BEF375CB, DB605373 */ +}; +static const double pio4 = 7.85398163397448278999e-01, /* 3FE921FB, 54442D18 */ + pio4lo = 3.06161699786838301793e-17; /* 3C81A626, 33145C07 */ + +/* compute -1 / (x+y) carefully */ +static double minus_inv(double x, double y) +{ + double a, t, z, v, s, w; + + w = x + y; + z = zero_low(w); + v = y - (z - x); + a = -one / w; + t = zero_low(a); + s = one + t * z; + return t + a * (s + t * v); +} + +static double +__kernel_tan(double x, double y, int iy) { + double z, r, v, w, s; + int ix, hx; + + hx = get_high_word(x); /* high word of x */ + ix = hx & 0x7fffffff; /* high word of |x| */ + if (ix < 0x3e300000) { /* x < 2**-28 */ + if ((int) x == 0) { /* generate inexact */ + if (((ix | get_low_word(x)) | (iy + 1)) == 0) + return one / fabs(x); + else { + if (iy == 1) + return x; + else + return minus_inv(x, y); + } + } + } + if (ix >= 0x3FE59428) { /* |x| >= 0.6744 */ + if (hx < 0) { + x = -x; + y = -y; + } + z = pio4 - x; + w = pio4lo - y; + x = z + w; + y = 0.0; + } + z = x * x; + w = z * z; + /* + * Break x^5*(T[1]+x^2*T[2]+...) into + * x^5(T[1]+x^4*T[3]+...+x^20*T[11]) + + * x^5(x^2*(T[2]+x^4*T[4]+...+x^22*[T12])) + */ + r = eval_poly(w, T_odd, 6); + v = z * eval_poly(w, T_even, 6); + s = z * x; + r = y + z * (s * (r + v) + y); + r += T0 * s; + w = x + r; + if (ix >= 0x3FE59428) { + v = (double) iy; + return (double) (1 - ((hx >> 30) & 2)) * + (v - 2.0 * (x - (w * w / (w + v) - r))); + } + if (iy == 1) { + return w; + } else { + /* + * if allow error up to 2 ulp, simply return + * -1.0 / (x+r) here + */ + return minus_inv(x, r); + } +} + +/* @(#)s_tan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* tan(x) + * Return tangent function of x. + * + * kernel function: + * __kernel_tan ... tangent function on [-pi/4,pi/4] + * __ieee754_rem_pio2 ... argument reduction routine + * + * Method. + * Let S,C and T denote the sin, cos and tan respectively on + * [-PI/4, +PI/4]. Reduce the argument x to y1+y2 = x-k*pi/2 + * in [-pi/4 , +pi/4], and let n = k mod 4. + * We have + * + * n sin(x) cos(x) tan(x) + * ---------------------------------------------------------- + * 0 S C T + * 1 C -S -1/T + * 2 -S -C T + * 3 -C S -1/T + * ---------------------------------------------------------- + * + * Special cases: + * Let trig be any of sin, cos, or tan. + * trig(+-INF) is NaN, with signals; + * trig(NaN) is that NaN; + * + * Accuracy: + * TRIG(x) returns trig(x) nearly rounded + */ + +double js_tan(double x) +{ + double y[2],z=0.0; + int n, ix; + + /* High word of x. */ + ix = get_high_word(x); + + /* |x| ~< pi/4 */ + ix &= 0x7fffffff; + if(ix <= 0x3fe921fb) return __kernel_tan(x,z,1); + + /* tan(Inf or NaN) is NaN */ + else if (ix>=0x7ff00000) return x-x; /* NaN */ + + /* argument reduction needed */ + else { + n = js_rem_pio2(x,y); + return __kernel_tan(y[0],y[1],1-((n&1)<<1)); /* 1 -- n even + -1 -- n odd */ + } +} +#endif + +/* @(#)e_asin.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_asin(x) + * Method : + * Since asin(x) = x + x^3/6 + x^5*3/40 + x^7*15/336 + ... + * we approximate asin(x) on [0,0.5] by + * asin(x) = x + x*x^2*R(x^2) + * where + * R(x^2) is a rational approximation of (asin(x)-x)/x^3 + * and its remez error is bounded by + * |(asin(x)-x)/x^3 - R(x^2)| < 2^(-58.75) + * + * For x in [0.5,1] + * asin(x) = pi/2-2*asin(sqrt((1-x)/2)) + * Let y = (1-x), z = y/2, s := sqrt(z), and pio2_hi+pio2_lo=pi/2; + * then for x>0.98 + * asin(x) = pi/2 - 2*(s+s*z*R(z)) + * = pio2_hi - (2*(s+s*z*R(z)) - pio2_lo) + * For x<=0.98, let pio4_hi = pio2_hi/2, then + * f = hi part of s; + * c = sqrt(z) - f = (z-f*f)/(s+f) ...f+c=sqrt(z) + * and + * asin(x) = pi/2 - 2*(s+s*z*R(z)) + * = pio4_hi+(pio4-2s)-(2s*z*R(z)-pio2_lo) + * = pio4_hi+(pio4-2f)-(2s*z*R(z)-(pio2_lo+2c)) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + */ + + +static const double +pio2_hi = 1.57079632679489655800e+00, /* 0x3FF921FB, 0x54442D18 */ + pio2_lo = 6.12323399573676603587e-17, /* 0x3C91A626, 0x33145C07 */ + pio4_hi = 7.85398163397448278999e-01; /* 0x3FE921FB, 0x54442D18 */ +/* coefficient for R(x^2) */ +static const double pS[] = { + /* pS0 */ 1.66666666666666657415e-01, /* 0x3FC55555, 0x55555555 */ + /* pS1 */-3.25565818622400915405e-01, /* 0xBFD4D612, 0x03EB6F7D */ + /* pS2 */ 2.01212532134862925881e-01, /* 0x3FC9C155, 0x0E884455 */ + /* pS3 */ -4.00555345006794114027e-02, /* 0xBFA48228, 0xB5688F3B */ + /* pS4 */ 7.91534994289814532176e-04, /* 0x3F49EFE0, 0x7501B288 */ + /* pS5 */ 3.47933107596021167570e-05, /* 0x3F023DE1, 0x0DFDF709 */ +}; + +static const double qS[] = { + /* qS1 */ -2.40339491173441421878e+00, /* 0xC0033A27, 0x1C8A2D4B */ + /* qS2 */ 2.02094576023350569471e+00, /* 0x40002AE5, 0x9C598AC8 */ + /* qS3 */ -6.88283971605453293030e-01, /* 0xBFE6066C, 0x1B8D0159 */ + /* qS4 */ 7.70381505559019352791e-02, /* 0x3FB3B8C5, 0xB12E9282 */ +}; + +static double R(double t) +{ + double p, q, w; + p = t * eval_poly(t, pS, 6); + q = one + t * eval_poly(t, qS, 4); + w = p/q; + return w; +} + +double js_asin(double x) +{ + double t,w,p,q,c,r,s; + int hx,ix; + hx = get_high_word(x); + ix = hx&0x7fffffff; + if(ix>= 0x3ff00000) { /* |x|>= 1 */ + if(((ix-0x3ff00000)|get_low_word(x))==0) + /* asin(1)=+-pi/2 with inexact */ + return x*pio2_hi+x*pio2_lo; + return (x-x)/(x-x); /* asin(|x|>1) is NaN */ + } else if (ix<0x3fe00000) { /* |x|<0.5 */ + if(ix<0x3e400000) { /* if |x| < 2**-27 */ + if(huge+x>one) return x;/* return x with inexact if x!=0*/ + } else { + t = x*x; + w = R(t); + return x+x*w; + } + } + /* 1> |x|>= 0.5 */ + w = one-fabs(x); + t = w*0.5; + r = R(t); + s = js_sqrt(t); + if(ix>=0x3FEF3333) { /* if |x| > 0.975 */ + w = r; + t = pio2_hi-(2.0*(s+s*w)-pio2_lo); + } else { + w = zero_low(s); + c = (t-w*w)/(s+w); + p = 2.0*s*r-(pio2_lo-2.0*c); + q = pio4_hi-2.0*w; + t = pio4_hi-(p-q); + } + if(hx>0) return t; else return -t; +} + + +/* @(#)e_acos.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_acos(x) + * Method : + * acos(x) = pi/2 - asin(x) + * acos(-x) = pi/2 + asin(x) + * For |x|<=0.5 + * acos(x) = pi/2 - (x + x*x^2*R(x^2)) (see asin.c) + * For x>0.5 + * acos(x) = pi/2 - (pi/2 - 2asin(sqrt((1-x)/2))) + * = 2asin(sqrt((1-x)/2)) + * = 2s + 2s*z*R(z) ...z=(1-x)/2, s=sqrt(z) + * = 2f + (2c + 2s*z*R(z)) + * where f=hi part of s, and c = (z-f*f)/(s+f) is the correction term + * for f so that f+c ~ sqrt(z). + * For x<-0.5 + * acos(x) = pi - 2asin(sqrt((1-|x|)/2)) + * = pi - 0.5*(s+s*z*R(z)), where z=(1-|x|)/2,s=sqrt(z) + * + * Special cases: + * if x is NaN, return x itself; + * if |x|>1, return NaN with invalid signal. + * + * Function needed: sqrt + */ + +static const double +pi = 3.14159265358979311600e+00; /* 0x400921FB, 0x54442D18 */ + +double js_acos(double x) +{ + double z,r,w,s,c,df; + int hx,ix; + hx = get_high_word(x); + ix = hx&0x7fffffff; + if(ix>=0x3ff00000) { /* |x| >= 1 */ + if(((ix-0x3ff00000)|get_low_word(x))==0) { /* |x|==1 */ + if(hx>0) return 0.0; /* acos(1) = 0 */ + else return pi+2.0*pio2_lo; /* acos(-1)= pi */ + } + return (x-x)/(x-x); /* acos(|x|>1) is NaN */ + } + if(ix<0x3fe00000) { /* |x| < 0.5 */ + if(ix<=0x3c600000) return pio2_hi+pio2_lo;/*if|x|<2**-57*/ + z = x*x; + r = R(z); + return pio2_hi - (x - (pio2_lo-x*r)); + } else { + z = (one-fabs(x))*0.5; + r = R(z); + s = js_sqrt(z); + if (hx<0) { /* x < -0.5 */ + w = r*s-pio2_lo; + return pi - 2.0*(s+w); + } else { /* x > 0.5 */ + df = zero_low(s); + c = (z-df*df)/(s+df); + w = r*s+c; + return 2.0*(df+w); + } + } +} + + +/* @(#)s_atan.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* atan(x) + * Method + * 1. Reduce x to positive by atan(x) = -atan(-x). + * 2. According to the integer k=4t+0.25 chopped, t=x, the argument + * is further reduced to one of the following intervals and the + * arctangent of t is evaluated by the corresponding formula: + * + * [0,7/16] atan(x) = t-t^3*(a1+t^2*(a2+...(a10+t^2*a11)...) + * [7/16,11/16] atan(x) = atan(1/2) + atan( (t-0.5)/(1+t/2) ) + * [11/16.19/16] atan(x) = atan( 1 ) + atan( (t-1)/(1+t) ) + * [19/16,39/16] atan(x) = atan(3/2) + atan( (t-1.5)/(1+1.5t) ) + * [39/16,INF] atan(x) = atan(INF) + atan( -1/t ) + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double atanhi[] = { + 4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */ + 7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */ + 9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */ + 1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */ +}; + +static const double atanlo[] = { + 2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */ + 3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */ + 1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */ + 6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */ +}; + +static const double aT_even[] = { + 3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */ + 1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */ + 9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */ + 6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */ + 4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */ + 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ +}; +static const double aT_odd[] = { + -1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */ + -1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */ + -7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */ + -5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */ + -3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */ +}; + +double js_atan(double x) +{ + double w,s1,s2,z; + int ix,hx,id; + + hx = get_high_word(x); + ix = hx&0x7fffffff; + if(ix>=0x44100000) { /* if |x| >= 2^66 */ + if(ix>0x7ff00000|| + (ix==0x7ff00000&&(get_low_word(x)!=0))) + return x+x; /* NaN */ + if(hx>0) return atanhi[3]+atanlo[3]; + else return -atanhi[3]-atanlo[3]; + } if (ix < 0x3fdc0000) { /* |x| < 0.4375 */ + if (ix < 0x3e200000) { /* |x| < 2^-29 */ + if(huge+x>one) return x; /* raise inexact */ + } + id = -1; + } else { + x = fabs(x); + if (ix < 0x3ff30000) { /* |x| < 1.1875 */ + if (ix < 0x3fe60000) { /* 7/16 <=|x|<11/16 */ + id = 0; x = (2.0*x-one)/(2.0+x); + } else { /* 11/16<=|x|< 19/16 */ + id = 1; x = (x-one)/(x+one); + } + } else { + if (ix < 0x40038000) { /* |x| < 2.4375 */ + id = 2; x = (x-1.5)/(one+1.5*x); + } else { /* 2.4375 <= |x| < 2^66 */ + id = 3; x = -1.0/x; + } + }} + /* end of argument reduction */ + z = x*x; + w = z*z; + /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ + s1 = z*eval_poly(w, aT_even, 6); + s2 = w*eval_poly(w, aT_odd, 5); + if (id<0) return x - x*(s1+s2); + else { + z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x); + return (hx<0)? -z:z; + } +} + +/* @(#)e_atan2.c 1.3 95/01/18 */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunSoft, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + * + */ + +/* __ieee754_atan2(y,x) + * Method : + * 1. Reduce y to positive by atan2(y,x)=-atan2(-y,x). + * 2. Reduce x to positive by (if x and y are unexceptional): + * ARG (x+iy) = arctan(y/x) ... if x > 0, + * ARG (x+iy) = pi - arctan[y/(-x)] ... if x < 0, + * + * Special cases: + * + * ATAN2((anything), NaN ) is NaN; + * ATAN2(NAN , (anything) ) is NaN; + * ATAN2(+-0, +(anything but NaN)) is +-0 ; + * ATAN2(+-0, -(anything but NaN)) is +-pi ; + * ATAN2(+-(anything but 0 and NaN), 0) is +-pi/2; + * ATAN2(+-(anything but INF and NaN), +INF) is +-0 ; + * ATAN2(+-(anything but INF and NaN), -INF) is +-pi; + * ATAN2(+-INF,+INF ) is +-pi/4 ; + * ATAN2(+-INF,-INF ) is +-3pi/4; + * ATAN2(+-INF, (anything but,0,NaN, and INF)) is +-pi/2; + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +pi_o_4 = 7.8539816339744827900E-01, /* 0x3FE921FB, 0x54442D18 */ +pi_o_2 = 1.5707963267948965580E+00, /* 0x3FF921FB, 0x54442D18 */ +pi_lo = 1.2246467991473531772E-16; /* 0x3CA1A626, 0x33145C07 */ + +double js_atan2(double y, double x) +{ + double z; + int k,m,hx,hy,ix,iy; + unsigned lx,ly; + + EXTRACT_WORDS(hx, lx, x); + EXTRACT_WORDS(hy, ly, y); + ix = hx&0x7fffffff; + iy = hy&0x7fffffff; + if(((ix|((lx|-lx)>>31))>0x7ff00000)|| + ((iy|((ly|-ly)>>31))>0x7ff00000)) /* x or y is NaN */ + return x+y; + if(((hx-0x3ff00000)|lx)==0) + return js_atan(y); /* x=1.0 */ + m = ((hy>>31)&1)|((hx>>30)&2); /* 2*sign(x)+sign(y) */ + + /* when y = 0 */ + if((iy|ly)==0) { + z = 0; + goto done; + } + /* when x = 0 */ + if((ix|lx)==0) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* when x is INF */ + if(ix==0x7ff00000) { + if(iy==0x7ff00000) { + z = pi_o_4; + } else { + z = 0; + } + goto done; + } + /* when y is INF */ + if(iy==0x7ff00000) return (hy<0)? -pi_o_2-tiny: pi_o_2+tiny; + + /* compute y/x */ + k = (iy-ix)>>20; + if(k > 60) { + z=pi_o_2+0.5*pi_lo; /* |y/x| > 2**60 */ + } else if(hx<0&&k<-60) { + z=0.0; /* |y|/x < -2**60 */ + } else { + z=js_atan(fabs(y/x)); /* safe to do y/x */ + } + done: + switch (m) { + case 0: + return z ; /* atan(+,+) */ + case 1: + z = set_high_word(z, get_high_word(z) ^ 0x80000000); + return z ; /* atan(-,+) */ + case 2: + return pi-(z-pi_lo);/* atan(+,-) */ + default: /* case 3 */ + return (z-pi_lo)-pi;/* atan(-,-) */ + } +} + +/* @(#)e_exp.c 1.6 04/04/22 */ +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_exp(x) + * Returns the exponential of x. + * + * Method + * 1. Argument reduction: + * Reduce x to an r so that |r| <= 0.5*ln2 ~ 0.34658. + * Given x, find r and integer k such that + * + * x = k*ln2 + r, |r| <= 0.5*ln2. + * + * Here r will be represented as r = hi-lo for better + * accuracy. + * + * 2. Approximation of exp(r) by a special rational function on + * the interval [0,0.34658]: + * Write + * R(r**2) = r*(exp(r)+1)/(exp(r)-1) = 2 + r*r/6 - r**4/360 + ... + * We use a special Remes algorithm on [0,0.34658] to generate + * a polynomial of degree 5 to approximate R. The maximum error + * of this polynomial approximation is bounded by 2**-59. In + * other words, + * R(z) ~ 2.0 + P1*z + P2*z**2 + P3*z**3 + P4*z**4 + P5*z**5 + * (where z=r*r, and the values of P1 to P5 are listed below) + * and + * | 5 | -59 + * | 2.0+P1*z+...+P5*z - R(z) | <= 2 + * | | + * The computation of exp(r) thus becomes + * 2*r + * exp(r) = 1 + ------- + * R - r + * r*R1(r) + * = 1 + r + ----------- (for better accuracy) + * 2 - R1(r) + * where + * 2 4 10 + * R1(r) = r - (P1*r + P2*r + ... + P5*r ). + * + * 3. Scale back to obtain exp(x): + * From step 1, we have + * exp(x) = 2^k * exp(r) + * + * Special cases: + * exp(INF) is INF, exp(NaN) is NaN; + * exp(-INF) is 0, and + * for finite argument, only exp(0)=1 is exact. + * + * Accuracy: + * according to an error analysis, the error is always less than + * 1 ulp (unit in the last place). + * + * Misc. info. + * For IEEE double + * if x > 7.09782712893383973096e+02 then exp(x) overflow + * if x < -7.45133219101941108420e+02 then exp(x) underflow + * + * Constants: + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +two = 2.0, +halF[2] = {0.5,-0.5,}, +twom1000= 9.33263618503218878990e-302, /* 2**-1000=0x01700000,0*/ +o_threshold= 7.09782712893383973096e+02, /* 0x40862E42, 0xFEFA39EF */ +u_threshold= -7.45133219101941108420e+02, /* 0xc0874910, 0xD52D3051 */ +ln2HI[2] ={ 6.93147180369123816490e-01, /* 0x3fe62e42, 0xfee00000 */ + -6.93147180369123816490e-01,},/* 0xbfe62e42, 0xfee00000 */ +ln2LO[2] ={ 1.90821492927058770002e-10, /* 0x3dea39ef, 0x35793c76 */ + -1.90821492927058770002e-10,},/* 0xbdea39ef, 0x35793c76 */ + invln2 = 1.44269504088896338700e+00; /* 0x3ff71547, 0x652b82fe */ +static const double P[] = { + /* P1 */ 1.66666666666666019037e-01, /* 0x3FC55555, 0x5555553E */ + /* P2 */ -2.77777777770155933842e-03, /* 0xBF66C16C, 0x16BEBD93 */ + /* P3 */ 6.61375632143793436117e-05, /* 0x3F11566A, 0xAF25DE2C */ + /* P4 */ -1.65339022054652515390e-06, /* 0xBEBBBD41, 0xC5D26BF1 */ + /* P5 */ 4.13813679705723846039e-08, /* 0x3E663769, 0x72BEA4D0 */ +}; + +/* compute exp(z+w)*2^n */ +static double kernel_exp(double z, double w, double lo, double hi, int n) +{ + int j; + double t, t1, r; + t = z*z; + t1 = z - t*eval_poly(t, P, 5); + r = (z*t1)/(t1-two) - (w+z*w); + z = one-((lo + r)-hi); + j = get_high_word(z); + j += (n<<20); + if((j>>20)<=0) + z = js_scalbn(z,n); /* subnormal output */ + else + z = set_high_word(z, get_high_word(z) + (n<<20)); + return z; +} + +double js_exp(double x) +{ + double hi,lo,t; + int k,xsb; + unsigned hx; + + hx = get_high_word(x); /* high word of x */ + xsb = (hx>>31)&1; /* sign bit of x */ + hx &= 0x7fffffff; /* high word of |x| */ + + /* filter out non-finite argument */ + if(hx >= 0x40862E42) { /* if |x|>=709.78... */ + if(hx>=0x7ff00000) { + if(((hx&0xfffff)|get_low_word(x))!=0) + return x+x; /* NaN */ + else return (xsb==0)? x:0.0; /* exp(+-inf)={inf,0} */ + } + if(x > o_threshold) return huge*huge; /* overflow */ + if(x < u_threshold) return twom1000*twom1000; /* underflow */ + } + + /* argument reduction */ + if(hx > 0x3fd62e42) { /* if |x| > 0.5 ln2 */ + if(hx < 0x3FF0A2B2) { /* and |x| < 1.5 ln2 */ + hi = x-ln2HI[xsb]; lo=ln2LO[xsb]; k = 1-xsb-xsb; + } else { + k = (int)(invln2*x+halF[xsb]); + t = k; + hi = x - t*ln2HI[0]; /* t*ln2HI is exact here */ + lo = t*ln2LO[0]; + } + x = hi - lo; + } + else if(hx < 0x3e300000) { /* when |x|<2**-28 */ + if(huge+x>one) return one+x;/* trigger inexact */ + k = 0; /* avoid warning */ + } + else k = 0; + + /* x is now in primary range */ + if (k == 0) { + lo = 0; + hi = x; + } + return kernel_exp(x, 0, lo, hi, k); +} + + +/* @(#)e_pow.c 1.5 04/04/22 SMI */ +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* __ieee754_pow(x,y) return x**y + * + * n + * Method: Let x = 2 * (1+f) + * 1. Compute and return log2(x) in two pieces: + * log2(x) = w1 + w2, + * where w1 has 53-24 = 29 bit trailing zeros. + * 2. Perform y*log2(x) = n+y' by simulating multi-precision + * arithmetic, where |y'|<=0.5. + * 3. Return x**y = 2**n*exp(y'*log2) + * + * Special cases: + * 1. (anything) ** 0 is 1 + * 2. (anything) ** 1 is itself + * 3. (anything) ** NAN is NAN + * 4. NAN ** (anything except 0) is NAN + * 5. +-(|x| > 1) ** +INF is +INF + * 6. +-(|x| > 1) ** -INF is +0 + * 7. +-(|x| < 1) ** +INF is +0 + * 8. +-(|x| < 1) ** -INF is +INF + * 9. +-1 ** +-INF is NAN + * 10. +0 ** (+anything except 0, NAN) is +0 + * 11. -0 ** (+anything except 0, NAN, odd integer) is +0 + * 12. +0 ** (-anything except 0, NAN) is +INF + * 13. -0 ** (-anything except 0, NAN, odd integer) is +INF + * 14. -0 ** (odd integer) = -( +0 ** (odd integer) ) + * 15. +INF ** (+anything except 0,NAN) is +INF + * 16. +INF ** (-anything except 0,NAN) is +0 + * 17. -INF ** (anything) = -0 ** (-anything) + * 18. (-anything) ** (integer) is (-1)**(integer)*(+anything**integer) + * 19. (-anything except 0 and inf) ** (non-integer) is NAN + * + * Accuracy: + * pow(x,y) returns x**y nearly rounded. In particular + * pow(integer,integer) + * always returns the correct integer provided it is + * representable. + * + * Constants : + * The hexadecimal values are the intended ones for the following + * constants. The decimal values may be used, provided that the + * compiler will convert from decimal to binary accurately enough + * to produce the hexadecimal values shown. + */ + +static const double +bp[] = {1.0, 1.5,}, +dp_h[] = { 0.0, 5.84962487220764160156e-01,}, /* 0x3FE2B803, 0x40000000 */ +dp_l[] = { 0.0, 1.35003920212974897128e-08,}, /* 0x3E4CFDEB, 0x43CFD006 */ +two53 = 9007199254740992.0, /* 0x43400000, 0x00000000 */ + /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ +lg2 = 6.93147180559945286227e-01, /* 0x3FE62E42, 0xFEFA39EF */ +lg2_h = 6.93147182464599609375e-01, /* 0x3FE62E43, 0x00000000 */ +lg2_l = -1.90465429995776804525e-09, /* 0xBE205C61, 0x0CA86C39 */ +ovt = 8.0085662595372944372e-0017, /* -(1024-log2(ovfl+.5ulp)) */ +cp = 9.61796693925975554329e-01, /* 0x3FEEC709, 0xDC3A03FD =2/(3ln2) */ +cp_h = 9.61796700954437255859e-01, /* 0x3FEEC709, 0xE0000000 =(float)cp */ +cp_l = -7.02846165095275826516e-09, /* 0xBE3E2FE0, 0x145B01F5 =tail of cp_h*/ +ivln2 = 1.44269504088896338700e+00, /* 0x3FF71547, 0x652B82FE =1/ln2 */ +ivln2_h = 1.44269502162933349609e+00, /* 0x3FF71547, 0x60000000 =24b 1/ln2*/ +ivln2_l = 1.92596299112661746887e-08, /* 0x3E54AE0B, 0xF85DDF44 =1/ln2 tail*/ +ivlg10b2 = 0.3010299956639812, /* 0x3fd34413509f79ff 1/log2(10) */ +ivlg10b2_h = 0.30102992057800293, /* 0x3fd3441300000000 1/log2(10) high */ +ivlg10b2_l = 7.508597826552624e-8; /* 0x3e7427de7fbcc47c 1/log2(10) low */ + +static const double L_tab[] = { + /* L1 */ 5.99999999999994648725e-01, /* 0x3FE33333, 0x33333303 */ + /* L2 */ 4.28571428578550184252e-01, /* 0x3FDB6DB6, 0xDB6FABFF */ + /* L3 */ 3.33333329818377432918e-01, /* 0x3FD55555, 0x518F264D */ + /* L4 */ 2.72728123808534006489e-01, /* 0x3FD17460, 0xA91D4101 */ + /* L5 */ 2.30660745775561754067e-01, /* 0x3FCD864A, 0x93C9DB65 */ + /* L6 */ 2.06975017800338417784e-01, /* 0x3FCA7E28, 0x4A454EEF */ +}; + +/* compute (t1, t2) = log2(ax). is_small_ax is true if abs(ax)<= 2**-20 */ +static void kernel_log2(double *pt1, double *pt2, double ax) +{ + double t, u, v, t1, t2, r; + int n, j, ix, k; + double ss, s2, s_h, s_l, t_h, t_l, p_l, p_h, z_h, z_l; + + n = 0; + ix = get_high_word(ax); + /* take care subnormal number */ + if(ix<0x00100000) + {ax *= two53; n -= 53; ix = get_high_word(ax); } + n += ((ix)>>20)-0x3ff; + j = ix&0x000fffff; + /* determine interval */ + ix = j|0x3ff00000; /* normalize ix */ + if(j<=0x3988E) k=0; /* |x|>1)|0x20000000)+0x00080000+(k<<18)); + t_l = ax - (t_h-bp[k]); + s_l = v*((u-s_h*t_h)-s_h*t_l); + /* compute log(ax) */ + s2 = ss*ss; + r = s2*s2*eval_poly(s2, L_tab, 6); + r += s_l*(s_h+ss); + s2 = s_h*s_h; + t_h = zero_low(3.0+s2+r); + t_l = r-((t_h-3.0)-s2); + /* u+v = ss*(1+...) */ + u = s_h*t_h; + v = s_l*t_h+t_l*ss; + /* 2/(3log2)*(ss+...) */ + p_h = zero_low(u+v); + p_l = v-(p_h-u); + z_h = cp_h*p_h; /* cp_h+cp_l = 2/(3*log2) */ + z_l = cp_l*p_h+p_l*cp+dp_l[k]; + /* log2(ax) = (ss+..)*2/(3*log2) = n + dp_h + z_h + z_l */ + t = (double)n; + t1 = zero_low(((z_h+z_l)+dp_h[k])+t); + t2 = z_l-(((t1-t)-dp_h[k])-z_h); + + *pt1 = t1; + *pt2 = t2; +} + +/* flag = 0: log2() + flag = 1: log() + flag = 2: log10() +*/ +static double js_log_internal(double x, int flag) +{ + double p_h, p_l, t, u, v; + int hx, lx; + + EXTRACT_WORDS(hx, lx, x); + if (hx <= 0) { + if (((hx&0x7fffffff)|lx)==0) + return -INFINITY; /* log(+-0)=-inf */ + if (hx<0) + return NAN; /* log(-#) = NaN */ + } else if (hx >= 0x7ff00000) { + /* log(inf) = inf, log(nan) = nan */ + return x+x; + } + kernel_log2(&p_h, &p_l, x); + + t = p_h + p_l; + if (flag == 0) { + return t; + } else { + t = zero_low(t); + if (flag == 1) { + /* multiply (p_l+p_h) by lg2 */ + u = t*lg2_h; + v = (p_l-(t-p_h))*lg2+t*lg2_l; + } else { + /* mutiply (p_l+p_h) by 1/log2(10) */ + u = t*ivlg10b2_h; + v = (p_l-(t-p_h))*ivlg10b2+t*ivlg10b2_l; + } + return u+v; + } +} + +double js_log2(double x) +{ + return js_log_internal(x, 0); +} + +double js_log(double x) +{ + return js_log_internal(x, 1); +} + +double js_log10(double x) +{ + return js_log_internal(x, 2); +} + +double js_pow(double x, double y) +{ + double z,ax,p_h,p_l; + double y1,t1,t2,s,t,u,v,w; + int i,j,k,yisint,n; + int hx,hy,ix,iy; + unsigned lx,ly; + + EXTRACT_WORDS(hx, lx, x); + EXTRACT_WORDS(hy, ly, y); + ix = hx&0x7fffffff; iy = hy&0x7fffffff; + + /* y==zero: x**0 = 1 */ + if((iy|ly)==0) return one; + + /* +-NaN return x+y */ + if(ix > 0x7ff00000 || ((ix==0x7ff00000)&&(lx!=0)) || + iy > 0x7ff00000 || ((iy==0x7ff00000)&&(ly!=0))) + return x+y; + + /* determine if y is an odd int when x < 0 + * yisint = 0 ... y is not an integer + * yisint = 1 ... y is an odd int + * yisint = 2 ... y is an even int + */ + yisint = 0; + if(hx<0) { + if(iy>=0x43400000) yisint = 2; /* even integer y */ + else if(iy>=0x3ff00000) { + k = (iy>>20)-0x3ff; /* exponent */ + if(k>20) { + j = ly>>(52-k); + if((j<<(52-k))==ly) yisint = 2-(j&1); + } else if(ly==0) { + j = iy>>(20-k); + if((j<<(20-k))==iy) yisint = 2-(j&1); + } + } + } + + /* special value of y */ + if(ly==0) { + if (iy==0x7ff00000) { /* y is +-inf */ + if(((ix-0x3ff00000)|lx)==0) + return y - y; /* inf**+-1 is NaN */ + else if (ix >= 0x3ff00000)/* (|x|>1)**+-inf = inf,0 */ + return (hy>=0)? y: zero; + else /* (|x|<1)**-,+inf = inf,0 */ + return (hy<0)?-y: zero; + } + if(iy==0x3ff00000) { /* y is +-1 */ + if(hy<0) return one/x; else return x; + } + if(hy==0x40000000) return x*x; /* y is 2 */ + if(hy==0x3fe00000) { /* y is 0.5 */ + if(hx>=0) /* x >= +0 */ + return js_sqrt(x); + } + } + + ax = fabs(x); + /* special value of x */ + if(lx==0) { + if(ix==0x7ff00000||ix==0||ix==0x3ff00000){ + z = ax; /*x is +-0,+-inf,+-1*/ + if(hy<0) z = one/z; /* z = (1/|x|) */ + if(hx<0) { + if(((ix-0x3ff00000)|yisint)==0) { + z = (z-z)/(z-z); /* (-1)**non-int is NaN */ + } else if(yisint==1) + z = -z; /* (x<0)**odd = -(|x|**odd) */ + } + return z; + } + } + + n = (hx>>31)+1; + + /* (x<0)**(non-int) is NaN */ + if((n|yisint)==0) return (x-x)/(x-x); + + s = one; /* s (sign of result -ve**odd) = -1 else = 1 */ + if((n|(yisint-1))==0) s = -one;/* (-ve)**(odd int) */ + + /* |y| is huge */ + if(iy>0x41e00000) { /* if |y| > 2**31 */ + if(iy>0x43f00000){ /* if |y| > 2**64, must o/uflow */ + if(ix<=0x3fefffff) return (hy<0)? huge*huge:tiny*tiny; + if(ix>=0x3ff00000) return (hy>0)? huge*huge:tiny*tiny; + } + /* over/underflow if x is not close to one */ + if(ix<0x3fefffff) return (hy<0)? s*huge*huge:s*tiny*tiny; + if(ix>0x3ff00000) return (hy>0)? s*huge*huge:s*tiny*tiny; + t = ax-one; /* t has 20 trailing zeros */ + w = (t*t)*(0.5-t*(0.3333333333333333333333-t*0.25)); + u = ivln2_h*t; /* ivln2_h has 21 sig. bits */ + v = t*ivln2_l-w*ivln2; + t1 = zero_low(u+v); + t2 = v-(t1-u); + } else { + kernel_log2(&t1, &t2, ax); + } + /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */ + y1 = zero_low(y); + p_l = (y-y1)*t1+y*t2; + p_h = y1*t1; + z = p_l+p_h; + EXTRACT_WORDS(j, i, z); + if (j>=0x40900000) { /* z >= 1024 */ + if(((j-0x40900000)|i)!=0) /* if z > 1024 */ + return s*huge*huge; /* overflow */ + else { + if(p_l+ovt>z-p_h) return s*huge*huge; /* overflow */ + } + } else if((j&0x7fffffff)>=0x4090cc00 ) { /* z <= -1075 */ + if(((j-0xc090cc00)|i)!=0) /* z < -1075 */ + return s*tiny*tiny; /* underflow */ + else { + if(p_l<=z-p_h) return s*tiny*tiny; /* underflow */ + } + } + /* + * compute 2**(p_h+p_l) + */ + i = j&0x7fffffff; + k = (i>>20)-0x3ff; + n = 0; + if(i>0x3fe00000) { /* if |z| > 0.5, set n = [z+0.5] */ + n = j+(0x00100000>>(k+1)); + k = ((n&0x7fffffff)>>20)-0x3ff; /* new k for n */ + t = zero; + t = set_high_word(t, n&~(0x000fffff>>k)); + n = ((n&0x000fffff)|0x00100000)>>(20-k); + if(j<0) n = -n; + p_h -= t; + } + /* multiply (p_l+p_h) by lg2 */ + t = zero_low(p_l+p_h); + u = t*lg2_h; + v = (p_l-(t-p_h))*lg2+t*lg2_l; + z = u+v; + w = v-(z-u); + return s * kernel_exp(z, w, 0, z, n); +} + diff --git a/libm.h b/libm.h new file mode 100644 index 00000000..17e94e0e --- /dev/null +++ b/libm.h @@ -0,0 +1,46 @@ +/* + * Tiny Math Library + * + * Copyright (c) 2024 Fabrice Bellard + * + * 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. + */ +double js_scalbn(double x, int n); +double js_floor(double x); +double js_ceil(double x); +double js_trunc(double x); +double js_round_inf(double a); +double js_fabs(double x); +double js_sqrt(double x); +int32_t js_lrint(double a); +double js_fmod(double x, double y); +double js_sin(double x); +double js_cos(double x); +double js_tan(double x); +double js_acos(double x); +double js_asin(double x); +double js_atan(double x); +double js_atan2(double y, double x); +double js_exp(double x); +double js_log(double x); +double js_log2(double x); +double js_log10(double x); +double js_pow(double x, double y); +/* exported only for tests */ +int js_rem_pio2(double x, double *y); diff --git a/list.h b/list.h new file mode 100644 index 00000000..5c182340 --- /dev/null +++ b/list.h @@ -0,0 +1,99 @@ +/* + * Linux klist like system + * + * Copyright (c) 2016-2017 Fabrice Bellard + * + * 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 LIST_H +#define LIST_H + +#ifndef NULL +#include +#endif + +struct list_head { + struct list_head *prev; + struct list_head *next; +}; + +#define LIST_HEAD_INIT(el) { &(el), &(el) } + +/* return the pointer of type 'type *' containing 'el' as field 'member' */ +#define list_entry(el, type, member) container_of(el, type, member) + +static inline void init_list_head(struct list_head *head) +{ + head->prev = head; + head->next = head; +} + +/* insert 'el' between 'prev' and 'next' */ +static inline void __list_add(struct list_head *el, + struct list_head *prev, struct list_head *next) +{ + prev->next = el; + el->prev = prev; + el->next = next; + next->prev = el; +} + +/* add 'el' at the head of the list 'head' (= after element head) */ +static inline void list_add(struct list_head *el, struct list_head *head) +{ + __list_add(el, head, head->next); +} + +/* add 'el' at the end of the list 'head' (= before element head) */ +static inline void list_add_tail(struct list_head *el, struct list_head *head) +{ + __list_add(el, head->prev, head); +} + +static inline void list_del(struct list_head *el) +{ + struct list_head *prev, *next; + prev = el->prev; + next = el->next; + prev->next = next; + next->prev = prev; + el->prev = NULL; /* fail safe */ + el->next = NULL; /* fail safe */ +} + +static inline int list_empty(struct list_head *el) +{ + return el->next == el; +} + +#define list_for_each(el, head) \ + for(el = (head)->next; el != (head); el = el->next) + +#define list_for_each_safe(el, el1, head) \ + for(el = (head)->next, el1 = el->next; el != (head); \ + el = el1, el1 = el->next) + +#define list_for_each_prev(el, head) \ + for(el = (head)->prev; el != (head); el = el->prev) + +#define list_for_each_prev_safe(el, el1, head) \ + for(el = (head)->prev, el1 = el->prev; el != (head); \ + el = el1, el1 = el->prev) + +#endif /* LIST_H */ diff --git a/mqjs.c b/mqjs.c new file mode 100644 index 00000000..46ad9536 --- /dev/null +++ b/mqjs.c @@ -0,0 +1,774 @@ +/* + * Micro QuickJS REPL + * + * 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 "cutils.h" +#include "readline_tty.h" +#include "mquickjs.h" + +static uint8_t *load_file(const char *filename, int *plen); +static void dump_error(JSContext *ctx); + +static 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(' '); + v = argv[i]; + if (JS_IsString(ctx, v)) { + JSCStringBuf buf; + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v, &buf); + fwrite(str, 1, len, stdout); + } else { + JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); + } + } + putchar('\n'); + return JS_UNDEFINED; +} + +static 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) +{ + 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) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} +#endif + +static 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) +{ + return JS_NewInt64(ctx, get_time_ms()); +} + +/* load a script */ +static 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; + buf = load_file(filename, &buf_len); + + ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + 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) +{ + 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])) + return JS_EXCEPTION; + for(i = 0; i < MAX_TIMERS; i++) { + th = &js_timer_list[i]; + if (!th->allocated) { + pfunc = JS_AddGCRef(ctx, &th->func); + *pfunc = argv[0]; + th->timeout = get_time_ms() + delay; + th->allocated = TRUE; + return JS_NewInt32(ctx, i); + } + } + return JS_ThrowInternalError(ctx, "too many timers"); +} + +static JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + int timer_id; + JSTimer *th; + + if (JS_ToInt32(ctx, &timer_id, argv[0])) + return JS_EXCEPTION; + if (timer_id >= 0 && timer_id < MAX_TIMERS) { + th = &js_timer_list[timer_id]; + if (th->allocated) { + JS_DeleteGCRef(ctx, &th->func); + th->allocated = FALSE; + } + } + return JS_UNDEFINED; +} + +static void run_timers(JSContext *ctx) +{ + int64_t min_delay, delay, cur_time; + BOOL has_timer; + int i; + JSTimer *th; + struct timespec ts; + + for(;;) { + min_delay = 1000; + cur_time = get_time_ms(); + has_timer = FALSE; + for(i = 0; i < MAX_TIMERS; i++) { + th = &js_timer_list[i]; + if (th->allocated) { + has_timer = TRUE; + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue ret; + /* the timer expired */ + if (JS_StackCheck(ctx, 2)) + 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: + dump_error(ctx); + exit(1); + } + min_delay = 0; + break; + } else if (delay < min_delay) { + min_delay = delay; + } + } + } + if (!has_timer) + break; + if (min_delay > 0) { + ts.tv_sec = min_delay / 1000; + ts.tv_nsec = (min_delay % 1000) * 1000000; + nanosleep(&ts, NULL); + } + } +} + +#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) +{ + fwrite(buf, 1, buf_len, js_log_err_flag ? stderr : stdout); +} + +static void dump_error(JSContext *ctx) +{ + JSValue obj; + obj = JS_GetException(ctx); + fprintf(stderr, "%s", term_colors[STYLE_ERROR_MSG]); + js_log_err_flag++; + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + js_log_err_flag--; + fprintf(stderr, "%s\n", term_colors[COLOR_NONE]); +} + +static int eval_buf(JSContext *ctx, const char *eval_str, const char *filename, BOOL is_repl, int parse_flags) +{ + JSValue val; + int flags; + + flags = parse_flags; + if (is_repl) + flags |= JS_EVAL_RETVAL | JS_EVAL_REPL; + val = JS_Parse(ctx, eval_str, strlen(eval_str), filename, flags); + if (JS_IsException(val)) + goto exception; + + val = JS_Run(ctx, val); + if (JS_IsException(val)) { + exception: + dump_error(ctx); + return 1; + } else { + if (is_repl) { + printf("%s", term_colors[STYLE_RESULT]); + JS_PrintValueF(ctx, val, JS_DUMP_LONG); + printf("%s\n", term_colors[COLOR_NONE]); + } + return 0; + } +} + +static int eval_file(JSContext *ctx, const char *filename, + int argc, const char **argv, int parse_flags, + BOOL allow_bytecode) +{ + uint8_t *buf; + int ret, buf_len; + JSValue val; + + buf = load_file(filename, &buf_len); + if (allow_bytecode && JS_IsBytecode(buf, buf_len)) { + if (JS_RelocateBytecode(ctx, buf, buf_len)) { + fprintf(stderr, "Could not relocate bytecode\n"); + exit(1); + } + val = JS_LoadBytecode(ctx, buf); + } else { + val = JS_Parse(ctx, (char *)buf, buf_len, filename, parse_flags); + } + if (JS_IsException(val)) + goto exception; + + if (argc > 0) { + JSValue obj, arr; + JSGCRef arr_ref, val_ref; + int i; + + JS_PUSH_VALUE(ctx, val); + /* must be defined after JS_LoadBytecode() */ + arr = JS_NewArray(ctx, argc); + JS_PUSH_VALUE(ctx, arr); + for(i = 0; i < argc; i++) { + JS_SetPropertyUint32(ctx, arr_ref.val, i, + JS_NewString(ctx, argv[i])); + } + JS_POP_VALUE(ctx, arr); + obj = JS_GetGlobalObject(ctx); + JS_SetPropertyStr(ctx, obj, "scriptArgs", arr); + JS_POP_VALUE(ctx, val); + } + + + val = JS_Run(ctx, val); + if (JS_IsException(val)) { + exception: + dump_error(ctx); + ret = 1; + } else { + ret = 0; + } + free(buf); + return ret; +} + +static void compile_file(const char *filename, const char *outfilename, + size_t mem_size, int dump_memory, int parse_flags, BOOL force_32bit) +{ + 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; + FILE *f; + + /* When compiling to a file, the actual content of the stdlib does + not matter because the generated bytecode does not depend on + it. We still need it so that the atoms for the parsing are + defined. The JSContext must be discarded once the compilation + is done. */ + 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); + + val = JS_Parse(ctx, eval_str, strlen(eval_str), filename, parse_flags); + free(eval_str); + if (JS_IsException(val)) { + dump_error(ctx); + return; + } + +#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); + + if (dump_memory) + JS_DumpMemory(ctx, (dump_memory >= 2)); + + /* Relocate to zero to have a deterministic + output. JS_DumpMemory() cannot work once the heap is relocated, + so we relocate after it. */ + JS_RelocateBytecode2(ctx, &hdr_buf.hdr, (uint8_t *)data_buf, data_len, 0, FALSE); + hdr_len = sizeof(JSBytecodeHeader); + } + f = fopen(outfilename, "wb"); + if (!f) { + perror(outfilename); + exit(1); + } + fwrite(&hdr_buf, 1, hdr_len, f); + fwrite(data_buf, 1, data_len, f); + fclose(f); + + JS_FreeContext(ctx); + free(mem_buf); +} + +/* repl */ + +static ReadlineState readline_state; +static uint8_t readline_cmd_buf[256]; +static uint8_t readline_kill_buf[256]; +static char readline_history[512]; + +void readline_find_completion(const char *cmdline) +{ +} + +static BOOL is_word(int c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + c == '_' || c == '$'; +} + +static const char js_keywords[] = + "break|case|catch|continue|debugger|default|delete|do|" + "else|finally|for|function|if|in|instanceof|new|" + "return|switch|this|throw|try|typeof|while|with|" + "class|const|enum|import|export|extends|super|" + "implements|interface|let|package|private|protected|" + "public|static|yield|" + "undefined|null|true|false|Infinity|NaN|" + "eval|arguments|" + "await|"; + +static const char js_types[] = "void|var|"; + +static BOOL find_keyword(const char *buf, size_t buf_len, const char *dict) +{ + const char *r, *p = dict; + while (*p != '\0') { + r = strchr(p, '|'); + if (!r) + break; + if ((r - p) == buf_len && !memcmp(buf, p, buf_len)) + return TRUE; + p = r + 1; + } + return FALSE; +} + +/* return the color for the character at position 'pos' and the number + of characters of the same color */ +static int term_get_color(int *plen, const char *buf, int pos, int buf_len) +{ + int c, color, pos1, len; + + c = buf[pos]; + if (c == '"' || c == '\'') { + pos1 = pos + 1; + for(;;) { + if (buf[pos1] == '\0' || buf[pos1] == c) + break; + if (buf[pos1] == '\\' && buf[pos1 + 1] != '\0') + pos1 += 2; + else + pos1++; + } + if (buf[pos1] != '\0') + pos1++; + len = pos1 - pos; + color = STYLE_STRING; + } else if (c == '/' && buf[pos + 1] == '*') { + pos1 = pos + 2; + while (buf[pos1] != '\0' && + !(buf[pos1] == '*' && buf[pos1 + 1] == '/')) { + pos1++; + } + if (buf[pos1] != '\0') + pos1 += 2; + len = pos1 - pos; + color = STYLE_COMMENT; + } else if ((c >= '0' && c <= '9') || c == '.') { + pos1 = pos + 1; + while (is_word(buf[pos1])) + pos1++; + len = pos1 - pos; + color = STYLE_NUMBER; + } else if (is_word(c)) { + pos1 = pos + 1; + while (is_word(buf[pos1])) + pos1++; + len = pos1 - pos; + if (find_keyword(buf + pos, len, js_keywords)) { + color = STYLE_KEYWORD; + } else { + while (buf[pos1] == ' ') + pos1++; + if (buf[pos1] == '(') { + color = STYLE_FUNCTION; + } else { + if (find_keyword(buf + pos, len, js_types)) { + color = STYLE_TYPE; + } else { + color = STYLE_IDENTIFIER; + } + } + } + } else { + color = STYLE_DEFAULT; + len = 1; + } + *plen = len; + return color; +} + +static int js_interrupt_handler(JSContext *ctx, void *opaque) +{ + return readline_is_interrupted(); +} + +static void repl_run(JSContext *ctx) +{ + ReadlineState *s = &readline_state; + const char *cmd; + + s->term_width = readline_tty_init(); + s->term_cmd_buf = readline_cmd_buf; + s->term_kill_buf = readline_kill_buf; + s->term_cmd_buf_size = sizeof(readline_cmd_buf); + s->term_history = readline_history; + s->term_history_buf_size = sizeof(readline_history); + s->get_color = term_get_color; + + JS_SetInterruptHandler(ctx, js_interrupt_handler); + + for(;;) { + cmd = readline_tty(&readline_state, "mqjs > ", FALSE); + if (!cmd) + break; + eval_buf(ctx, cmd, "", TRUE, 0); + run_timers(ctx); + } +} + +static void help(void) +{ + printf("MicroQuickJS" "\n" + "usage: mqjs [options] [file [args]]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-i --interactive go to interactive mode\n" + "-I --include file include an additional file\n" + "-d --dump dump the memory usage stats\n" + " --memory-limit n limit the memory usage to 'n' bytes\n" + "--no-column no column number in debug information\n" + "-o FILE save the bytecode to FILE\n" + "-m32 force 32 bit bytecode output (use with -o)\n" + "-b --allow-bytecode allow bytecode in input file\n"); + exit(1); +} + +int main(int argc, const char **argv) +{ + int optind; + size_t mem_size; + int dump_memory = 0; + int interactive = 0; + const char *expr = NULL; + const char *out_filename = NULL; + const char *include_list[32]; + int include_count = 0; + uint8_t *mem_buf; + JSContext *ctx; + int i, parse_flags; + BOOL force_32bit, allow_bytecode; + + mem_size = 16 << 20; + dump_memory = 0; + parse_flags = 0; + force_32bit = FALSE; + allow_bytecode = FALSE; + + /* cannot use getopt because we want to pass the command line to + the script */ + optind = 1; + while (optind < argc && *argv[optind] == '-') { + const char *arg = argv[optind] + 1; + const char *longopt = ""; + /* a single - is not an option, it also stops argument scanning */ + if (!*arg) + break; + optind++; + if (*arg == '-') { + longopt = arg + 1; + arg += strlen(arg); + /* -- stops argument scanning */ + if (!*longopt) + break; + } + for (; *arg || *longopt; longopt = "") { + char opt = *arg; + if (opt) + arg++; + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) { + help(); + continue; + } + 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 (!strcmp(longopt, "memory-limit")) { + char *p; + double count; + if (optind >= argc) { + fprintf(stderr, "expecting memory limit"); + exit(1); + } + count = strtod(argv[optind++], &p); + switch (tolower((unsigned char)*p)) { + case 'g': + count *= 1024; + /* fall thru */ + case 'm': + count *= 1024; + /* fall thru */ + case 'k': + count *= 1024; + /* fall thru */ + default: + mem_size = (size_t)(count); + break; + } + continue; + } + if (opt == 'd' || !strcmp(longopt, "dump")) { + dump_memory++; + continue; + } + if (opt == 'i' || !strcmp(longopt, "interactive")) { + interactive++; + continue; + } + 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 == 'I' || !strcmp(longopt, "include")) { + if (optind >= argc) { + fprintf(stderr, "expecting filename"); + exit(1); + } + if (include_count >= countof(include_list)) { + fprintf(stderr, "too many included files"); + exit(1); + } + include_list[include_count++] = argv[optind++]; + continue; + } + if (!strcmp(longopt, "no-column")) { + parse_flags |= JS_EVAL_STRIP_COL; + continue; + } + if (opt == 'm' && !strcmp(arg, "32")) { + /* XXX: using a long option is not consistent here */ + force_32bit = TRUE; + arg += strlen(arg); + continue; + } + if (opt == 'b' || !strcmp(longopt, "allow-bytecode")) { + allow_bytecode = TRUE; + continue; + } + if (opt) { + fprintf(stderr, "qjs: unknown option '-%c'\n", opt); + } else { + fprintf(stderr, "qjs: unknown option '--%s'\n", longopt); + } + help(); + } + } + + if (out_filename) { + if (optind >= argc) { + fprintf(stderr, "expecting input filename\n"); + exit(1); + } + compile_file(argv[optind], out_filename, mem_size, dump_memory, + parse_flags, force_32bit); + } else { + mem_buf = malloc(mem_size); + ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); + JS_SetLogFunc(ctx, js_log_func); + { + struct timeval tv; + gettimeofday(&tv, NULL); + JS_SetRandomSeed(ctx, ((uint64_t)tv.tv_sec << 32) ^ tv.tv_usec); + } + + for(i = 0; i < include_count; i++) { + if (eval_file(ctx, include_list[i], 0, NULL, + parse_flags, allow_bytecode)) { + goto fail; + } + } + + if (expr) { + if (eval_buf(ctx, expr, "", FALSE, parse_flags | JS_EVAL_REPL)) + goto fail; + } else if (optind >= argc) { + interactive = 1; + } else { + if (eval_file(ctx, argv[optind], argc - optind, argv + optind, + parse_flags, allow_bytecode)) { + goto fail; + } + } + + if (interactive) { + repl_run(ctx); + } else { + run_timers(ctx); + } + + if (dump_memory) + JS_DumpMemory(ctx, (dump_memory >= 2)); + + JS_FreeContext(ctx); + free(mem_buf); + } + return 0; + fail: + JS_FreeContext(ctx); + free(mem_buf); + return 1; +} diff --git a/mqjs_stdlib.c b/mqjs_stdlib.c new file mode 100644 index 00000000..0a30833c --- /dev/null +++ b/mqjs_stdlib.c @@ -0,0 +1,402 @@ +/* + * Micro QuickJS REPL library + * + * 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 "mquickjs_build.h" + +/* defined in mqjs_example.c */ +//#define CONFIG_CLASS_EXAMPLE + +static const JSPropDef js_object_proto[] = { + JS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty), + JS_CFUNC_DEF("toString", 0, js_object_toString), + JS_PROP_END, +}; + +static const JSPropDef js_object[] = { + JS_CFUNC_DEF("defineProperty", 3, js_object_defineProperty), + JS_CFUNC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf), + JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf), + JS_CFUNC_DEF("create", 2, js_object_create), + JS_CFUNC_DEF("keys", 1, js_object_keys), + JS_PROP_END, +}; + +static const JSClassDef js_object_class = + JS_CLASS_DEF("Object", 1, js_object_constructor, JS_CLASS_OBJECT, + js_object, js_object_proto, NULL, NULL); + +static const JSPropDef js_function_proto[] = { + JS_CGETSET_DEF("prototype", js_function_get_prototype, js_function_set_prototype ), + JS_CFUNC_DEF("call", 1, js_function_call ), + JS_CFUNC_DEF("apply", 2, js_function_apply ), + JS_CFUNC_DEF("bind", 1, js_function_bind ), + JS_CFUNC_DEF("toString", 0, js_function_toString ), + JS_CGETSET_MAGIC_DEF("length", js_function_get_length_name, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("name", js_function_get_length_name, NULL, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_function_class = + JS_CLASS_DEF("Function", 1, js_function_constructor, JS_CLASS_CLOSURE, NULL, js_function_proto, NULL, NULL); + +static const JSPropDef js_number_proto[] = { + JS_CFUNC_DEF("toExponential", 1, js_number_toExponential ), + JS_CFUNC_DEF("toFixed", 1, js_number_toFixed ), + JS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision ), + JS_CFUNC_DEF("toString", 1, js_number_toString ), + JS_PROP_END, +}; + +static const JSPropDef js_number[] = { + JS_CFUNC_DEF("parseInt", 2, js_number_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_number_parseFloat ), + JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ), + JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ), + JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), + JS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0 ), + JS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0 ), + JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0 ), /* ES6 */ + JS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0 ), /* ES6 */ + JS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0 ), /* ES6 */ + JS_PROP_END, +}; + +static const JSClassDef js_number_class = + JS_CLASS_DEF("Number", 1, js_number_constructor, JS_CLASS_NUMBER, js_number, js_number_proto, NULL, NULL); + +static const JSClassDef js_boolean_class = + JS_CLASS_DEF("Boolean", 1, js_boolean_constructor, JS_CLASS_BOOLEAN, NULL, NULL, NULL, NULL); + +static const JSPropDef js_string_proto[] = { + JS_CGETSET_DEF("length", js_string_get_length, js_string_set_length ), + JS_CFUNC_MAGIC_DEF("charAt", 1, js_string_charAt, magic_charAt ), + JS_CFUNC_MAGIC_DEF("charCodeAt", 1, js_string_charAt, magic_charCodeAt ), + JS_CFUNC_MAGIC_DEF("codePointAt", 1, js_string_charAt, magic_codePointAt ), + JS_CFUNC_DEF("slice", 2, js_string_slice ), + JS_CFUNC_DEF("substring", 2, js_string_substring ), + JS_CFUNC_DEF("concat", 1, js_string_concat ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), + JS_CFUNC_DEF("match", 1, js_string_match ), + JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ), + JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ), + JS_CFUNC_DEF("search", 1, js_string_search ), + JS_CFUNC_DEF("split", 2, js_string_split ), + JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), + JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), + JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ), + JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ), + JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ), + JS_CFUNC_DEF("toString", 0, js_string_toString ), + JS_CFUNC_DEF("repeat", 1, js_string_repeat ), + JS_PROP_END, +}; + +static const JSPropDef js_string[] = { + JS_CFUNC_MAGIC_DEF("fromCharCode", 1, js_string_fromCharCode, 0 ), + JS_CFUNC_MAGIC_DEF("fromCodePoint", 1, js_string_fromCharCode, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_string_class = + JS_CLASS_DEF("String", 1, js_string_constructor, JS_CLASS_STRING, js_string, js_string_proto, NULL, NULL); + +static const JSPropDef js_array_proto[] = { + JS_CFUNC_DEF("concat", 1, js_array_concat ), + JS_CGETSET_DEF("length", js_array_get_length, js_array_set_length ), + JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ), + JS_CFUNC_DEF("pop", 0, js_array_pop ), + JS_CFUNC_DEF("join", 1, js_array_join ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_DEF("reverse", 0, js_array_reverse ), + JS_CFUNC_DEF("shift", 0, js_array_shift ), + JS_CFUNC_DEF("slice", 2, js_array_slice ), + JS_CFUNC_DEF("splice", 2, js_array_splice ), + JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_array_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_array_indexOf, 1 ), + JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, js_special_every ), + JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, js_special_some ), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, js_special_forEach ), + JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, js_special_map ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, js_special_filter ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, js_special_reduce ), + JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, js_special_reduceRight ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, js_special_reduce ), + JS_CFUNC_DEF("sort", 1, js_array_sort ), + JS_PROP_END, +}; + +static const JSPropDef js_array[] = { + JS_CFUNC_DEF("isArray", 1, js_array_isArray ), + JS_PROP_END, +}; + +static const JSClassDef js_array_class = + JS_CLASS_DEF("Array", 1, js_array_constructor, JS_CLASS_ARRAY, js_array, js_array_proto, NULL, NULL); + +static const JSPropDef js_error_proto[] = { + JS_CFUNC_DEF("toString", 0, js_error_toString ), + JS_PROP_STRING_DEF("name", "Error", 0 ), + JS_CGETSET_MAGIC_DEF("message", js_error_get_message, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("stack", js_error_get_message, NULL, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_error_class = + JS_CLASS_MAGIC_DEF("Error", 1, js_error_constructor, JS_CLASS_ERROR, NULL, js_error_proto, NULL, NULL); + +#define ERROR_DEF(cname, name, class_id) \ + static const JSPropDef js_ ## cname ## _proto[] = { \ + JS_PROP_STRING_DEF("name", name, 0 ), \ + JS_PROP_END, \ + }; \ + static const JSClassDef js_ ## cname ## _class = \ + JS_CLASS_MAGIC_DEF(name, 1, js_error_constructor, class_id, NULL, js_ ## cname ## _proto, &js_error_class, NULL); + +ERROR_DEF(eval_error, "EvalError", JS_CLASS_EVAL_ERROR) +ERROR_DEF(range_error, "RangeError", JS_CLASS_RANGE_ERROR) +ERROR_DEF(reference_error, "ReferenceError", JS_CLASS_REFERENCE_ERROR) +ERROR_DEF(syntax_error, "SyntaxError", JS_CLASS_SYNTAX_ERROR) +ERROR_DEF(type_error, "TypeError", JS_CLASS_TYPE_ERROR) +ERROR_DEF(uri_error, "URIError", JS_CLASS_URI_ERROR) +ERROR_DEF(internal_error, "InternalError", JS_CLASS_INTERNAL_ERROR) + +static const JSPropDef js_math[] = { + JS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0 ), + JS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1 ), + JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ), + JS_CFUNC_SPECIAL_DEF("abs", 1, f_f, js_fabs ), + JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, js_floor ), + JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, js_ceil ), + JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_round_inf ), + JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, js_sqrt ), + + JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ), + JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ), + JS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0 ), + JS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0 ), + JS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0 ), + JS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0 ), + JS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0 ), + JS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0 ), + + JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, js_sin ), + JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, js_cos ), + JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, js_tan ), + JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, js_asin ), + JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, js_acos ), + JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, js_atan ), + JS_CFUNC_DEF("atan2", 2, js_math_atan2 ), + JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, js_exp ), + JS_CFUNC_SPECIAL_DEF("log", 1, f_f, js_log ), + JS_CFUNC_DEF("pow", 2, js_math_pow ), + JS_CFUNC_DEF("random", 0, js_math_random ), + + /* some ES6 functions */ + JS_CFUNC_DEF("imul", 2, js_math_imul ), + JS_CFUNC_DEF("clz32", 1, js_math_clz32 ), + JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ), + JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, js_trunc ), + JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, js_log2 ), + JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, js_log10 ), + + JS_PROP_END, +}; + +static const JSClassDef js_math_obj = + JS_OBJECT_DEF("Math", js_math); + +static const JSPropDef js_json[] = { + JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("stringify", 3, js_json_stringify ), + JS_PROP_END, +}; + +static const JSClassDef js_json_obj = + JS_OBJECT_DEF("JSON", js_json); + +/* typed arrays */ +static const JSPropDef js_array_buffer_proto[] = { + JS_CGETSET_DEF("byteLength", js_array_buffer_get_byteLength, NULL ), + JS_PROP_END, +}; + +static const JSClassDef js_array_buffer_class = + JS_CLASS_DEF("ArrayBuffer", 1, js_array_buffer_constructor, JS_CLASS_ARRAY_BUFFER, NULL, js_array_buffer_proto, NULL, NULL); + +static const JSPropDef js_typed_array_base_proto[] = { + JS_CGETSET_MAGIC_DEF("length", js_typed_array_get_length, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_length, NULL, 1 ), + JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_length, NULL, 2 ), + JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_length, NULL, 3 ), + JS_CFUNC_DEF("join", 1, js_array_join ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ), + JS_CFUNC_DEF("set", 1, js_typed_array_set ), + JS_PROP_END, +}; + +static const JSClassDef js_typed_array_base_class = + JS_CLASS_DEF("TypedArray", 0, js_typed_array_base_constructor, JS_CLASS_TYPED_ARRAY, NULL, js_typed_array_base_proto, NULL, NULL); + +#define TA_DEF(name, class_name, bpe)\ +static const JSPropDef js_ ## name [] = {\ + JS_PROP_DOUBLE_DEF("BYTES_PER_ELEMENT", bpe, 0),\ + JS_PROP_END,\ +};\ +static const JSPropDef js_ ## name ## _proto[] = {\ + JS_PROP_DOUBLE_DEF("BYTES_PER_ELEMENT", bpe, 0),\ + JS_PROP_END,\ +};\ +static const JSClassDef js_ ## name ## _class =\ + JS_CLASS_MAGIC_DEF(#name, 3, js_typed_array_constructor, class_name, js_ ## name, js_ ## name ## _proto, &js_typed_array_base_class, NULL); + +TA_DEF(Uint8ClampedArray, JS_CLASS_UINT8C_ARRAY, 1) +TA_DEF(Int8Array, JS_CLASS_INT8_ARRAY, 1) +TA_DEF(Uint8Array, JS_CLASS_UINT8_ARRAY, 1) +TA_DEF(Int16Array, JS_CLASS_INT16_ARRAY, 2) +TA_DEF(Uint16Array, JS_CLASS_UINT16_ARRAY, 2) +TA_DEF(Int32Array, JS_CLASS_INT32_ARRAY, 4) +TA_DEF(Uint32Array, JS_CLASS_UINT32_ARRAY, 4) +TA_DEF(Float32Array, JS_CLASS_FLOAT32_ARRAY, 4) +TA_DEF(Float64Array, JS_CLASS_FLOAT64_ARRAY, 8) + +/* regexp */ + +static const JSPropDef js_regexp_proto[] = { + JS_CGETSET_DEF("lastIndex", js_regexp_get_lastIndex, js_regexp_set_lastIndex ), + JS_CGETSET_DEF("source", js_regexp_get_source, NULL ), + JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ), + JS_CFUNC_MAGIC_DEF("exec", 1, js_regexp_exec, 0 ), + JS_CFUNC_MAGIC_DEF("test", 1, js_regexp_exec, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_regexp_class = + JS_CLASS_DEF("RegExp", 2, js_regexp_constructor, JS_CLASS_REGEXP, NULL, js_regexp_proto, NULL, NULL); + +/* other objects */ + +static const JSPropDef js_date[] = { + JS_CFUNC_DEF("now", 0, js_date_now), + JS_PROP_END, +}; + +static const JSClassDef js_date_class = + JS_CLASS_DEF("Date", 7, js_date_constructor, JS_CLASS_DATE, js_date, NULL, NULL, NULL); + +static const JSPropDef js_console[] = { + JS_CFUNC_DEF("log", 1, js_print), + JS_PROP_END, +}; + +static const JSClassDef js_console_obj = + JS_OBJECT_DEF("Console", js_console); + +static const JSPropDef js_performance[] = { + JS_CFUNC_DEF("now", 0, js_performance_now), + JS_PROP_END, +}; +static const JSClassDef js_performance_obj = + JS_OBJECT_DEF("Performance", js_performance); + +static const JSPropDef js_global_object[] = { + JS_PROP_CLASS_DEF("Object", &js_object_class), + JS_PROP_CLASS_DEF("Function", &js_function_class), + JS_PROP_CLASS_DEF("Number", &js_number_class), + JS_PROP_CLASS_DEF("Boolean", &js_boolean_class), + JS_PROP_CLASS_DEF("String", &js_string_class), + JS_PROP_CLASS_DEF("Array", &js_array_class), + JS_PROP_CLASS_DEF("Math", &js_math_obj), + JS_PROP_CLASS_DEF("Date", &js_date_class), + JS_PROP_CLASS_DEF("JSON", &js_json_obj), + JS_PROP_CLASS_DEF("RegExp", &js_regexp_class), + + JS_PROP_CLASS_DEF("Error", &js_error_class), + JS_PROP_CLASS_DEF("EvalError", &js_eval_error_class), + JS_PROP_CLASS_DEF("RangeError", &js_range_error_class), + JS_PROP_CLASS_DEF("ReferenceError", &js_reference_error_class), + JS_PROP_CLASS_DEF("SyntaxError", &js_syntax_error_class), + JS_PROP_CLASS_DEF("TypeError", &js_type_error_class), + JS_PROP_CLASS_DEF("URIError", &js_uri_error_class), + JS_PROP_CLASS_DEF("InternalError", &js_internal_error_class), + + JS_PROP_CLASS_DEF("ArrayBuffer", &js_array_buffer_class), + JS_PROP_CLASS_DEF("Uint8ClampedArray", &js_Uint8ClampedArray_class), + JS_PROP_CLASS_DEF("Int8Array", &js_Int8Array_class), + JS_PROP_CLASS_DEF("Uint8Array", &js_Uint8Array_class), + JS_PROP_CLASS_DEF("Int16Array", &js_Int16Array_class), + JS_PROP_CLASS_DEF("Uint16Array", &js_Uint16Array_class), + JS_PROP_CLASS_DEF("Int32Array", &js_Int32Array_class), + JS_PROP_CLASS_DEF("Uint32Array", &js_Uint32Array_class), + JS_PROP_CLASS_DEF("Float32Array", &js_Float32Array_class), + JS_PROP_CLASS_DEF("Float64Array", &js_Float64Array_class), + + JS_CFUNC_DEF("parseInt", 2, js_number_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_number_parseFloat ), + JS_CFUNC_DEF("eval", 1, js_global_eval), + JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ), + JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ), + + JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), + JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), + JS_PROP_UNDEFINED_DEF("undefined", 0 ), + /* Note: null is expanded as the global object in js_global_object[] */ + JS_PROP_NULL_DEF("globalThis", 0 ), + + JS_PROP_CLASS_DEF("console", &js_console_obj), + JS_PROP_CLASS_DEF("performance", &js_performance_obj), + JS_CFUNC_DEF("print", 1, js_print), +#ifdef CONFIG_CLASS_EXAMPLE + JS_PROP_CLASS_DEF("Rectangle", &js_rectangle_class), + JS_PROP_CLASS_DEF("FilledRectangle", &js_filled_rectangle_class), +#else + JS_CFUNC_DEF("gc", 0, js_gc), + JS_CFUNC_DEF("load", 1, js_load), + JS_CFUNC_DEF("setTimeout", 2, js_setTimeout), + JS_CFUNC_DEF("clearTimeout", 1, js_clearTimeout), +#endif + JS_PROP_END, +}; + +/* Additional C function declarations (only useful for C + closures). They are always defined first. */ +static const JSPropDef js_c_function_decl[] = { + /* must come first if "bind" is defined */ + JS_CFUNC_SPECIAL_DEF("bound", 0, generic_params, js_function_bound ), +#ifdef CONFIG_CLASS_EXAMPLE + JS_CFUNC_SPECIAL_DEF("rectangle_closure_test", 0, generic_params, js_rectangle_closure_test ), +#endif + JS_PROP_END, +}; + +int main(int argc, char **argv) +{ + return build_atoms("js_stdlib", js_global_object, js_c_function_decl, argc, argv); +} diff --git a/mquickjs.c b/mquickjs.c new file mode 100644 index 00000000..a950f3c3 --- /dev/null +++ b/mquickjs.c @@ -0,0 +1,18324 @@ +/* + * Micro QuickJS Javascript Engine + * + * 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 "dtoa.h" +#include "mquickjs_priv.h" + +/* + TODO: + - regexp: better error position info + - use a specific MTAG for short functions instead of an immediate value + - use hash table for atoms + - set the length accessors as non configurable so that the + 'get_length' instruction optimizations are always safe. + - memory: + - fix stack_bottom logic + - launch gc at regular intervals + - only launch compaction when needed (handle free blocks in malloc()) + - avoid pass to rehash the properties + - ensure no undefined bytes (e.g. at end of JSString) in + saved bytecode ? + - reduced memory usage: + - reduce JSFunctionBytecode size (remove source_pos) + - do not explicitly store function names for get/set/bound + - use JSSTDLibraryDef fields instead of copying them to JSContext ? +*/ + +#define __exception __attribute__((warn_unused_result)) + +#define JS_STACK_SLACK 16 /* additional free space on the stack */ +/* min free size in bytes between heap_free and the bottom of the stack */ +#define JS_MIN_FREE_SIZE 512 +/* minimum free size in bytes to create the out of memory object */ +#define JS_MIN_CRITICAL_FREE_SIZE (JS_MIN_FREE_SIZE - 256) +#define JS_MAX_LOCAL_VARS 65535 +#define JS_MAX_FUNC_STACK_SIZE 65535 +#define JS_MAX_ARGC 65535 +/* maximum number of recursing JS_Call() */ +#define JS_MAX_CALL_RECURSE 8 + + +#define JS_VALUE_IS_BOTH_INT(a, b) ((((a) | (b)) & 1) == 0) +#define JS_VALUE_IS_BOTH_SHORT_FLOAT(a, b) (((((a) - JS_TAG_SHORT_FLOAT) | ((b) - JS_TAG_SHORT_FLOAT)) & 7) == 0) + +static __maybe_unused const char *js_mtag_name[JS_MTAG_COUNT] = { + "free", + "object", + "float64", + "string", + "func_bytecode", + "value_array", + "byte_array", + "varref", +}; + +/* function call flags (max 31 bits) */ +#define FRAME_CF_ARGC_MASK 0xffff +/* FRAME_CF_CTOR */ +#define FRAME_CF_POP_RET (1 << 17) /* pop the return value */ +#define FRAME_CF_PC_ADD1 (1 << 18) /* increment the PC by 1 instead of 3 */ + +#define JS_MB_PAD(n) (JSW * 8 - (n)) + +typedef struct { + JS_MB_HEADER; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS); +} JSMemBlockHeader; + +typedef struct { + JS_MB_HEADER; + /* in JSWords excluding the header. Free blocks of JSW bytes + are only generated by js_shrink() and may not be always + compacted */ + JSWord size: JS_MB_PAD(JS_MTAG_BITS); +} JSFreeBlock; + +#if JSW == 8 +#define JS_STRING_LEN_MAX 0x7ffffffe +#else +#define JS_STRING_LEN_MAX ((1 << (32 - JS_MTAG_BITS - 3)) - 1) +#endif + +typedef struct { + JS_MB_HEADER; + JSWord is_unique: 1; + JSWord is_ascii: 1; + /* true if the string content represents a number, only meaningful + is is_unique = true */ + JSWord is_numeric: 1; + JSWord len: JS_MB_PAD(JS_MTAG_BITS + 3); + uint8_t buf[]; +} JSString; + +typedef struct { + JSWord string_buf[sizeof(JSString) / sizeof(JSWord)]; /* for JSString */ + uint8_t buf[5]; +} JSStringCharBuf; + +#define JS_BYTE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1) + +typedef struct { + JS_MB_HEADER; + JSWord size: JS_MB_PAD(JS_MTAG_BITS); + uint8_t buf[]; +} JSByteArray; + +#define JS_VALUE_ARRAY_SIZE_MAX ((1 << (32 - JS_MTAG_BITS)) - 1) + +typedef struct { + JS_MB_HEADER; + JSWord size: JS_MB_PAD(JS_MTAG_BITS); + JSValue arr[]; +} JSValueArray; + +typedef struct JSVarRef { + JS_MB_HEADER; + JSWord is_detached : 1; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 1); + union { + JSValue value; /* is_detached = true */ + struct { + JSValue next; /* is_detached = false: JS_NULL or JSVarRef, + must be at the same address as 'value' */ + JSValue *pvalue; + }; + } u; +} JSVarRef; + +typedef struct { + JS_MB_HEADER; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS); +#ifdef JS_PTR64 + struct { + double dval; + } u; +#else + /* unaligned 64 bit access in 32-bit mode */ + struct __attribute__((packed)) { + double dval; + } u; +#endif +} JSFloat64; + +typedef struct JSROMClass { + JS_MB_HEADER; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS); + JSValue props; + int32_t ctor_idx; /* -1 if defining a normal object */ + JSValue proto_props; + JSValue parent_class; /* JSROMClass or JS_NULL */ +} JSROMClass; + +#define N_ROM_ATOM_TABLES_MAX 2 + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +#define JS_STRING_POS_CACHE_SIZE 2 +#define JS_STRING_POS_CACHE_MIN_LEN 16 + +typedef enum { + POS_TYPE_UTF8, + POS_TYPE_UTF16, +} StringPosTypeEnum; + +typedef struct { + JSValue str; /* JS_NULL or weak reference to a JSString. It + contains at least JS_STRING_POS_CACHE_MIN_LEN + bytes and is a non ascii string */ + uint32_t str_pos[2]; /* 0 = UTF-8 pos (in bytes), 1 = UTF-16 pos */ +} JSStringPosCacheEntry; + +struct JSContext { + /* memory map: + Stack + Free area + Heap + JSContext + */ + uint8_t *heap_base; + uint8_t *heap_free; /* first free area */ + uint8_t *stack_top; + JSValue *stack_bottom; /* sp must always be higher than stack_bottom */ + JSValue *sp; /* current stack pointer */ + JSValue *fp; /* current frame pointer, stack_top if none */ + uint32_t min_free_size; /* min free size between heap_free and the + bottom of the stack */ + BOOL in_out_of_memory : 8; /* != 0 if generating the out of memory object */ + uint8_t n_rom_atom_tables; + uint8_t string_pos_cache_counter; /* used for string_pos_cache[] update */ + uint16_t class_count; /* number of classes including user classes */ + int16_t interrupt_counter; + BOOL current_exception_is_uncatchable : 8; + struct JSParseState *parse_state; /* != NULL during JS_Eval() */ + int unique_strings_len; + int js_call_rec_count; /* number of recursing JS_Call() */ + JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ + JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ + const JSWord *atom_table; /* constant atom table */ + /* 'n_rom_atom_tables' atom tables from code loaded from rom */ + const JSValueArray *rom_atom_tables[N_ROM_ATOM_TABLES_MAX]; + const JSCFunctionDef *c_function_table; + const JSCFinalizer *c_finalizer_table; + uint64_t random_state; + JSInterruptHandler *interrupt_handler; + JSWriteFunc *write_func; /* for the various dump functions */ + void *opaque; + JSValue *class_obj; /* same as class_proto + class_count */ + JSStringPosCacheEntry string_pos_cache[JS_STRING_POS_CACHE_SIZE]; + + /* must only contain JSValue from this point (see JS_GC()) */ + JSValue unique_strings; /* JSValueArray of sorted strings or JS_NULL */ + + JSValue current_exception; /* currently pending exception, must + come after unique_strings */ +#ifdef DEBUG_GC + JSValue dummy_block; /* dummy memory block near the start of the memory */ +#endif + JSValue empty_props; /* empty prop list, for objects with no properties */ + JSValue global_obj; + JSValue minus_zero; /* minus zero float64 value */ + JSValue class_proto[]; /* prototype for each class (class_count + element, then class_count elements for + class_obj */ +}; + +typedef enum { + JS_VARREF_KIND_ARG, /* var_idx is an argument of the parent function */ + JS_VARREF_KIND_VAR, /* var_idx is a local variable of the parent function */ + JS_VARREF_KIND_VAR_REF, /* var_idx is a var ref of the parent function */ + JS_VARREF_KIND_GLOBAL, /* to debug */ +} JSVarRefKindEnum; + +typedef struct JSObject JSObject; + +typedef struct { + /* string, short integer or JS_UNINITIALIZED if no property. If + the last property is uninitialized, hash_next = 2 * + first_free. */ + JSValue key; + /* JS_PROP_GETSET: JSValueArray of two elements + JS_PROP_VARREF: JSVarRef */ + JSValue value; + /* XXX: when JSW = 8, could use 32 bits for hash_next (faster) */ + uint32_t hash_next : 30; /* low bit at zero */ + uint32_t prop_type : 2; +} JSProperty; + +typedef struct { + JSValue func_bytecode; /* JSFunctionBytecode */ + JSValue var_refs[]; /* JSValueArray */ +} JSClosureData; + +typedef struct { + uint32_t idx; + JSValue params; /* optional associated parameters */ +} JSCFunctionData; + +typedef struct { + JSValue tab; /* JS_NULL or JSValueArray */ + uint32_t len; /* maximum value: 2^30-1 */ +} JSArrayData; + +typedef struct { + JSValue message; /* string or JS_NULL */ + JSValue stack; /* string or JS_NULL */ +} JSErrorData; + +typedef struct { + JSValue byte_buffer; /* JSByteBuffer */ +} JSArrayBuffer; + +typedef struct { + JSValue buffer; /* corresponding array buffer */ + uint32_t len; /* in elements */ + uint32_t offset; /* in elements */ +} JSTypedArray; + +typedef struct { + JSValue source; + JSValue byte_code; + int last_index; +} JSRegExp; + +typedef struct { + void *opaque; +} JSObjectUserData; + +struct JSObject { + JS_MB_HEADER; + JSWord class_id: 8; + JSWord extra_size: JS_MB_PAD(JS_MTAG_BITS + 8); /* object additional size, in JSValue */ + + JSValue proto; /* JSObject or JS_NULL */ + /* JSValueArray. structure: + prop_count (number of properties excluding deleted ones) + hash_mask (= hash_size - 1) + hash_table[hash_size] (0 = end of list or offset in array) + JSProperty props[] + */ + JSValue props; + /* number of additional fields depends on the object */ + union { + JSClosureData closure; + JSCFunctionData cfunc; + JSArrayData array; + JSErrorData error; + JSArrayBuffer array_buffer; + JSTypedArray typed_array; + JSRegExp regexp; + JSObjectUserData user; + } u; +}; + +typedef struct JSFunctionBytecode { + JS_MB_HEADER; + JSWord has_arguments : 1; /* only used during parsing */ + JSWord has_local_func_name : 1; /* only used during parsing */ + JSWord has_column : 1; /* column debug info is present */ + /* during parse: variable index + 1 of hoisted function, 0 otherwise */ + JSWord arg_count : 16; + JSWord dummy: JS_MB_PAD(JS_MTAG_BITS + 3 + 16); + + JSValue func_name; /* JS_NULL if anonymous function */ + JSValue byte_code; /* JS_NULL if the function is not parsed yet */ + JSValue cpool; /* constant pool */ + JSValue vars; /* only for debug */ + JSValue ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */ + uint16_t stack_size; /* maximum stack size */ + uint16_t ext_vars_len; /* XXX: only used during parsing */ + JSValue filename; /* filename in which the function is defined */ + JSValue pc2line; /* JSByteArray or JS_NULL if not initialized */ + uint32_t source_pos; /* only used during parsing (XXX: shrink) */ +} JSFunctionBytecode; + +static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size); +static int get_mblock_size(const void *ptr); +static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size); +static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size); +static void build_backtrace(JSContext *ctx, JSValue error_obj, + const char *filename, int line_num, int col_num, int skip_level); +static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val); +static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size); +static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params, + JSValue params); +static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val); +static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto); +static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size); +static JSValueArray *js_alloc_props(JSContext *ctx, int n); + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "mquickjs_opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +typedef enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "mquickjs_opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, +} OPCodeEnum; + +typedef struct { +#ifdef DUMP_BYTECODE + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_pusch items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static __maybe_unused const JSOpCode opcode_info[OP_COUNT] = { +#define FMT(f) +#ifdef DUMP_BYTECODE +#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f }, +#endif +#include "mquickjs_opcode.h" +#undef DEF +#undef FMT +}; + +#include "mquickjs_atom.h" + +JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref) +{ + ref->prev = ctx->top_gc_ref; + ctx->top_gc_ref = ref; + ref->val = JS_UNDEFINED; + return &ref->val; +} + +JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref) +{ + ctx->top_gc_ref = ref->prev; + return ref->val; +} + +JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref) +{ + ref->prev = ctx->last_gc_ref; + ctx->last_gc_ref = ref; + ref->val = JS_UNDEFINED; + return &ref->val; +} + +void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref) +{ + JSGCRef **pref, *ref1; + pref = &ctx->last_gc_ref; + for(;;) { + ref1 = *pref; + if (ref1 == NULL) + abort(); + if (ref1 == ref) { + *pref = ref1->prev; + break; + } + pref = &ref1->prev; + } +} + +#undef JS_PUSH_VALUE +#undef JS_POP_VALUE + +#define JS_PUSH_VALUE(ctx, v) do { \ + v ## _ref.prev = ctx->top_gc_ref; \ + ctx->top_gc_ref = &v ## _ref; \ + v ## _ref.val = v; \ + } while (0) + +#define JS_POP_VALUE(ctx, v) do { \ + v = v ## _ref.val; \ + ctx->top_gc_ref = v ## _ref.prev; \ + } while (0) + +static JSValue js_get_atom(JSContext *ctx, int a) +{ + return JS_VALUE_FROM_PTR(&ctx->atom_table[a]); +} + +static force_inline JSValue JS_NewTailCall(int val) +{ + return JS_VALUE_MAKE_SPECIAL(JS_TAG_EXCEPTION, JS_EX_CALL + val); +} + +static inline JS_BOOL JS_IsExceptionOrTailCall(JSValue v) +{ + return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_EXCEPTION; +} + +static int js_get_mtag(void *ptr) +{ + return ((JSMemBlockHeader *)ptr)->mtag; +} + +static int check_free_mem(JSContext *ctx, JSValue *stack_bottom, uint32_t size) +{ +#ifdef DEBUG_GC + assert(ctx->sp >= stack_bottom); + /* don't start the GC before dummy_block is allocated */ + if (JS_IsPtr(ctx->dummy_block)) { + JS_GC(ctx); + } +#endif + if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) { + JS_GC(ctx); + if (((uint8_t *)stack_bottom - ctx->heap_free) < size + ctx->min_free_size) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + } + return 0; +} + +/* check that 'len' values can be pushed on the stack. Return 0 if OK, + -1 if not enough space. May trigger a GC(). */ +int JS_StackCheck(JSContext *ctx, uint32_t len) +{ + JSValue *new_stack_bottom; + + len += JS_STACK_SLACK; + new_stack_bottom = ctx->sp - len; + if (check_free_mem(ctx, new_stack_bottom, len * sizeof(JSValue))) + return -1; + ctx->stack_bottom = new_stack_bottom; + return 0; +} + +static void *js_malloc(JSContext *ctx, uint32_t size, int mtag) +{ + JSMemBlockHeader *p; + + if (size == 0) + return NULL; + size = (size + JSW - 1) & ~(JSW - 1); + + if (check_free_mem(ctx, ctx->stack_bottom, size)) + return NULL; + + p = (JSMemBlockHeader *)ctx->heap_free; + ctx->heap_free += size; + + p->mtag = mtag; + p->gc_mark = 0; + p->dummy = 0; + return p; +} + +static void *js_mallocz(JSContext *ctx, uint32_t size, int mtag) +{ + uint8_t *ptr; + ptr = js_malloc(ctx, size, mtag); + if (!ptr) + return NULL; + if (size > sizeof(uint32_t)) { + memset(ptr + sizeof(uint32_t), 0, size - sizeof(uint32_t)); + } + return ptr; +} + +/* currently only free the last element */ +static void js_free(JSContext *ctx, void *ptr) +{ + uint8_t *ptr1; + if (!ptr) + return; + ptr1 = ptr; + ptr1 += get_mblock_size(ptr1); + if (ptr1 == ctx->heap_free) + ctx->heap_free = ptr; +} + +/* 'size' is in bytes and must be multiple of JSW and > 0 */ +static void set_free_block(void *ptr, uint32_t size) +{ + JSFreeBlock *p; + p = (JSFreeBlock *)ptr; + p->mtag = JS_MTAG_FREE; + p->gc_mark = 0; + p->size = (size - sizeof(JSFreeBlock)) / sizeof(JSWord); +} + +/* 'ptr' must be != NULL. new_size must be less or equal to the + current block size. */ +static void *js_shrink(JSContext *ctx, void *ptr, uint32_t new_size) +{ + uint32_t old_size; + uint32_t diff; + + new_size = (new_size + (JSW - 1)) & ~(JSW - 1); + + if (new_size == 0) { + js_free(ctx, ptr); + return NULL; + } + old_size = get_mblock_size(ptr); + assert(new_size <= old_size); + diff = old_size - new_size; + if (diff == 0) + return ptr; + set_free_block((uint8_t *)ptr + new_size, diff); + /* add a new free block after 'ptr' */ + return ptr; +} + +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + ctx->current_exception = obj; + ctx->current_exception_is_uncatchable = FALSE; + return JS_EXCEPTION; +} + +/* return the byte length. 'buf' must contain UTF8_CHAR_LEN_MAX + 1 bytes */ +static int get_short_string(uint8_t *buf, JSValue val) +{ + int len; + len = unicode_to_utf8(buf, JS_VALUE_GET_SPECIAL_VALUE(val)); + buf[len] = '\0'; + return len; +} + +/* printf utility */ + +#define PF_ZERO_PAD (1 << 0) /* 0 */ +#define PF_ALT_FORM (1 << 1) /* # */ +#define PF_MARK_POS (1 << 2) /* + */ +#define PF_LEFT_ADJ (1 << 3) /* - */ +#define PF_PAD_POS (1 << 4) /* ' ' */ +#define PF_INT64 (1 << 5) /* l/ll */ + +static BOOL is_digit(int c) +{ + return (c >= '0' && c <= '9'); +} + +/* pad with chars 'c' */ +static void pad(JSWriteFunc *write_func, void *opaque, char c, + int width, int len) +{ + char buf[16]; + int l; + if (len >= width) + return; + width -= len; + memset(buf, c, min_int(sizeof(buf), width)); + while (width != 0) { + l = min_int(width, sizeof(buf)); + write_func(opaque, buf, l); + width -= l; + } +} + +/* The 'o' format can be used to print a JSValue. Only short int, + bool, null, undefined and string types are supported. */ +static void js_vprintf(JSWriteFunc *write_func, void *opaque, const char *fmt, va_list ap) +{ + const char *p; + int width, prec, flags, c; + char tmp_buf[32], *buf; + size_t len; + + while (*fmt != '\0') { + p = fmt; + while (*fmt != '%' && *fmt != '\0') + fmt++; + if (fmt > p) + write_func(opaque, p, fmt - p); + if (*fmt == '\0') + break; + fmt++; + /* get the flags */ + flags = 0; + for(;;) { + c = *fmt; + if (c == '0') { + flags |= PF_ZERO_PAD; + } else if (c == '#') { + flags |= PF_ALT_FORM; + } else if (c == '+') { + flags |= PF_MARK_POS; + } else if (c == '-') { + flags |= PF_LEFT_ADJ; + } else if (c == ' ') { + flags |= PF_MARK_POS; + } else { + break; + } + fmt++; + } + width = 0; + if (*fmt == '*') { + width = va_arg(ap, int); + } else { + while (is_digit(*fmt)) { + width = width * 10 + *fmt - '0'; + fmt++; + } + } + prec = 0; + if (*fmt == '.') { + fmt++; + if (*fmt == '*') { + prec = va_arg(ap, int); + } else { + while (is_digit(*fmt)) { + prec = prec * 10 + *fmt - '0'; + fmt++; + } + } + } + /* modifiers */ + for(;;) { + c = *fmt; + if (c == 'l') { + if (sizeof(long) == sizeof(int64_t) || fmt[-1] == 'l') + flags |= PF_INT64; + } else + if (c == 'z' || c == 't') { + if (sizeof(size_t) == sizeof(uint64_t)) + flags |= PF_INT64; + } else { + break; + } + fmt++; + } + + c = *fmt++; + /* XXX: not complete, just enough for our needs */ + buf = tmp_buf; + len = 0; + switch(c) { + case '%': + write_func(opaque, fmt - 1, 1); + break; + case 'c': + buf[0] = va_arg(ap, int); + len = 1; + flags &= ~PF_ZERO_PAD; + break; + case 's': + buf = va_arg(ap, char *); + if (!buf) + buf = "null"; + len = strlen(buf); + flags &= ~PF_ZERO_PAD; + break; + case 'd': + if (flags & PF_INT64) + len = i64toa(buf, va_arg(ap, int64_t)); + else + len = i32toa(buf, va_arg(ap, int32_t)); + break; + case 'u': + if (flags & PF_INT64) + len = u64toa(buf, va_arg(ap, uint64_t)); + else + len = u32toa(buf, va_arg(ap, uint32_t)); + break; + case 'x': + if (flags & PF_INT64) + len = u64toa_radix(buf, va_arg(ap, uint64_t), 16); + else + len = u64toa_radix(buf, va_arg(ap, uint32_t), 16); + break; + case 'p': + buf[0] = '0'; + buf[1] = 'x'; + len = u64toa_radix(buf + 2, (uintptr_t)va_arg(ap, void *), 16); + len += 2; + break; + case 'o': + { + JSValue val = (flags & PF_INT64) ? va_arg(ap, uint64_t) : va_arg(ap, uint32_t); + if (JS_IsInt(val)) { + len = i32toa(buf, JS_VALUE_GET_INT(val)); + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + /* XXX: print it */ + buf = "[short_float]"; + goto do_strlen; + } else +#endif + if (!JS_IsPtr(val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + buf = "null"; + goto do_strlen; + case JS_TAG_UNDEFINED: + buf = "undefined"; + goto do_strlen; + case JS_TAG_UNINITIALIZED: + buf = "uninitialized"; + goto do_strlen; + case JS_TAG_BOOL: + buf = JS_VALUE_GET_SPECIAL_VALUE(val) ? "true" : "false"; + goto do_strlen; + case JS_TAG_STRING_CHAR: + len = get_short_string((uint8_t *)buf, val); + break; + default: + buf = "[tag]"; + goto do_strlen; + } + } else { + void *ptr = JS_VALUE_TO_PTR(val); + int mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_STRING: + { + JSString *p = ptr; + buf = (char *)p->buf; + len = p->len; + } + break; + default: + buf = "[mtag]"; + do_strlen: + len = strlen(buf); + break; + } + } + /* remove the trailing '\n' if any (used in error output) */ + if ((flags & PF_ALT_FORM) && len > 0 && buf[len - 1] == '\n') + len--; + flags &= ~PF_ZERO_PAD; + } + break; + default: + goto error; + } + if (flags & PF_ZERO_PAD) { + /* XXX: incorrect with prefix */ + pad(write_func, opaque, '0', width, len); + } else { + if (!(flags & PF_LEFT_ADJ)) + pad(write_func, opaque, ' ', width, len); + } + write_func(opaque, buf, len); + if (flags & PF_LEFT_ADJ) + pad(write_func, opaque, ' ', width, len); + } + return; + error: + return; +} + +/* used for the debug output */ +static void __js_printf_like(2, 3) js_printf(JSContext *ctx, + const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + js_vprintf(ctx->write_func, ctx->opaque, fmt, ap); + va_end(ap); +} + +static __maybe_unused void js_putchar(JSContext *ctx, uint8_t c) +{ + ctx->write_func(ctx->opaque, &c, 1); +} + +typedef struct { + char *ptr; + char *buf_end; + int len; +} SNPrintfState; + +static void snprintf_write_func(void *opaque, const void *buf, size_t buf_len) +{ + SNPrintfState *s = opaque; + size_t l; + s->len += buf_len; + l = min_size_t(buf_len, s->buf_end - s->ptr); + if (l != 0) { + memcpy(s->ptr, buf, l); + s->ptr += l; + } +} + +static int js_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) +{ + SNPrintfState ss, *s = &ss; + s->ptr = buf; + s->buf_end = buf + max_size_t(buf_size, 1) - 1; + s->len = 0; + js_vprintf(snprintf_write_func, s, fmt, ap); + if (buf_size > 0) + *s->ptr = '\0'; + return s->len; +} + +static int __maybe_unused __js_printf_like(3, 4) js_snprintf(char *buf, size_t buf_size, const char *fmt, ...) +{ + va_list ap; + int ret; + va_start(ap, fmt); + ret = js_vsnprintf(buf, buf_size, fmt, ap); + va_end(ap); + return ret; +} + +JSValue __js_printf_like(3, 4) JS_ThrowError(JSContext *ctx, JSObjectClassEnum error_num, + const char *fmt, ...) +{ + JSObject *p; + va_list ap; + char buf[128]; + JSValue msg, error_obj; + JSGCRef msg_ref, error_obj_ref; + + va_start(ap, fmt); + js_vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + msg = JS_NewString(ctx, buf); + + JS_PUSH_VALUE(ctx, msg); + error_obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[error_num], JS_CLASS_ERROR, + sizeof(JSErrorData)); + JS_POP_VALUE(ctx, msg); + if (JS_IsException(error_obj)) + return error_obj; + + p = JS_VALUE_TO_PTR(error_obj); + p->u.error.message = msg; + p->u.error.stack = JS_NULL; + + /* in case of syntax error, the backtrace is added later */ + if (error_num != JS_CLASS_SYNTAX_ERROR) { + JS_PUSH_VALUE(ctx, error_obj); + build_backtrace(ctx, error_obj, NULL, 0, 0, 0); + JS_POP_VALUE(ctx, error_obj); + } + + return JS_Throw(ctx, error_obj); +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSValue val; + if (ctx->in_out_of_memory) + return JS_Throw(ctx, JS_NULL); + ctx->in_out_of_memory = TRUE; + ctx->min_free_size = JS_MIN_CRITICAL_FREE_SIZE; + val = JS_ThrowInternalError(ctx, "out of memory"); + ctx->in_out_of_memory = FALSE; + ctx->min_free_size = JS_MIN_FREE_SIZE; + return val; +} + +#define JS_SHORTINT_MIN (-(1 << 30)) +#define JS_SHORTINT_MAX ((1 << 30) - 1) + +#ifdef JS_USE_SHORT_FLOAT + +#define JS_FLOAT64_VALUE_EXP_MIN (1023 - 127) +#define JS_FLOAT64_VALUE_ADDEND ((uint64_t)(JS_FLOAT64_VALUE_EXP_MIN - (JS_TAG_SHORT_FLOAT << 8)) << 52) + +/* 1 <= n <= 63 */ +static inline uint64_t rotl64(uint64_t a, int n) +{ + return (a << n) | (a >> (64 - n)); +} + +static double js_get_short_float(JSValue v) +{ + return uint64_as_float64(rotl64(v, 60) + JS_FLOAT64_VALUE_ADDEND); +} + +static JSValue js_to_short_float(double d) +{ + return rotl64(float64_as_uint64(d) - JS_FLOAT64_VALUE_ADDEND, 4); +} + +#endif /* JS_USE_SHORT_FLOAT */ + +static JSValue js_alloc_float64(JSContext *ctx, double d) +{ + JSFloat64 *f; + f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64); + if (!f) + return JS_EXCEPTION; + f->u.dval = d; + return JS_VALUE_FROM_PTR(f); +} + +/* create a new float64 value which is known not to be a short integer */ +static JSValue __JS_NewFloat64(JSContext *ctx, double d) +{ + if (float64_as_uint64(d) == 0x8000000000000000) { + /* minus zero often happens, so it is worth having a constant + value */ + return ctx->minus_zero; + } else +#ifdef JS_USE_SHORT_FLOAT + /* Note: this test is false for NaN */ + if (fabs(d) >= 0x1p-127 && fabs(d) <= 0x1p+128) { + return js_to_short_float(d); + } else +#endif + { + return js_alloc_float64(ctx, d); + } +} + +static inline JSValue JS_NewShortInt(int32_t val) +{ + return JS_TAG_INT + (val << 1); +} + +#if defined(USE_SOFTFLOAT) +JSValue JS_NewFloat64(JSContext *ctx, double d) +{ + uint64_t a, m; + int e, b, shift; + JSValue v; + + a = float64_as_uint64(d); + if (a == 0) { + v = JS_NewShortInt(0); + } else { + e = (a >> 52) & 0x7ff; + if (e >= 1023 && e <= 1023 + 30 - 1) { + m = (a & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + shift = 52 - (e - 1023); + /* test if exact integer */ + if ((m & (((uint64_t)1 << shift) - 1)) != 0) + goto not_int; + b = m >> shift; + if (a >> 63) + b = -b; + v = JS_NewShortInt(b); + } else if (a == 0xc1d0000000000000) { + v = JS_NewShortInt(-(1 << 30)); + } else { + not_int: + v = __JS_NewFloat64(ctx, d); + } + } + return v; +} +#else +JSValue JS_NewFloat64(JSContext *ctx, double d) +{ + int32_t val; + if (d >= JS_SHORTINT_MIN && d <= JS_SHORTINT_MAX) { + val = (int32_t)d; + /* -0 cannot be represented as integer, so we compare the bit + representation */ + if (float64_as_uint64(d) == float64_as_uint64((double)val)) + return JS_NewShortInt(val); + } + return __JS_NewFloat64(ctx, d); +} +#endif + +static inline BOOL int64_is_short_int(int64_t val) +{ + return val >= JS_SHORTINT_MIN && val <= JS_SHORTINT_MAX; +} + +JSValue JS_NewInt64(JSContext *ctx, int64_t val) +{ + JSValue v; + if (likely(int64_is_short_int(val))) { + v = JS_NewShortInt(val); + } else { + v = __JS_NewFloat64(ctx, val); + } + return v; +} + +JSValue JS_NewInt32(JSContext *ctx, int32_t val) +{ + return JS_NewInt64(ctx, val); +} + +JSValue JS_NewUint32(JSContext *ctx, uint32_t val) +{ + return JS_NewInt64(ctx, val); +} + +static BOOL JS_IsPrimitive(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return JS_VALUE_GET_SPECIAL_TAG(val) != JS_TAG_SHORT_FUNC; + } else { + return (js_get_mtag(JS_VALUE_TO_PTR(val)) != JS_MTAG_OBJECT); + } +} + +/* Note: short functions are not considered as objects by this function */ +static BOOL JS_IsObject(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return FALSE; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT); + } +} + +/* return -1 if not an object */ +int JS_GetClassID(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return -1; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + if (p->mtag != JS_MTAG_OBJECT) + return -1; + else + return p->class_id; + } +} + +void JS_SetOpaque(JSContext *ctx, JSValue val, void *opaque) +{ + JSObject *p; + assert(JS_IsPtr(val)); + p = JS_VALUE_TO_PTR(val); + assert(p->mtag == JS_MTAG_OBJECT); + assert(p->class_id >= JS_CLASS_USER); + p->u.user.opaque = opaque; +} + +void *JS_GetOpaque(JSContext *ctx, JSValue val) +{ + JSObject *p; + assert(JS_IsPtr(val)); + p = JS_VALUE_TO_PTR(val); + assert(p->mtag == JS_MTAG_OBJECT); + assert(p->class_id >= JS_CLASS_USER); + return p->u.user.opaque; +} + +static JSObject *js_get_object_class(JSContext *ctx, JSValue val, int class_id) +{ + if (!JS_IsPtr(val)) { + return NULL; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + if (p->mtag != JS_MTAG_OBJECT || p->class_id != class_id) + return NULL; + else + return p; + } +} + +BOOL JS_IsFunction(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_SHORT_FUNC; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT && + (p->class_id == JS_CLASS_CLOSURE || + p->class_id == JS_CLASS_C_FUNCTION)); + } +} + +static BOOL JS_IsFunctionObject(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return FALSE; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT && + (p->class_id == JS_CLASS_CLOSURE || + p->class_id == JS_CLASS_C_FUNCTION)); + } +} + +BOOL JS_IsError(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return FALSE; + } else { + JSObject *p = JS_VALUE_TO_PTR(val); + return (p->mtag == JS_MTAG_OBJECT && p->class_id == JS_CLASS_ERROR); + } +} + +static force_inline BOOL JS_IsIntOrShortFloat(JSValue val) +{ +#ifdef JS_USE_SHORT_FLOAT + return JS_IsInt(val) || JS_IsShortFloat(val); +#else + return JS_IsInt(val); +#endif +} + +BOOL JS_IsNumber(JSContext *ctx, JSValue val) +{ + if (JS_IsIntOrShortFloat(val)) { + return TRUE; + } else if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + return (js_get_mtag(ptr) == JS_MTAG_FLOAT64); + } else { + return FALSE; + } +} + +BOOL JS_IsString(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) { + return JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR; + } else { + void *ptr = JS_VALUE_TO_PTR(val); + return (js_get_mtag(ptr) == JS_MTAG_STRING); + } +} + +static JSString *js_alloc_string(JSContext *ctx, uint32_t buf_len) +{ + JSString *p; + + if (buf_len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError(ctx, "string too long"); + return NULL; + } + p = js_malloc(ctx, sizeof(JSString) + buf_len + 1, JS_MTAG_STRING); + if (!p) + return NULL; + p->is_unique = FALSE; + p->is_ascii = FALSE; + p->is_numeric = FALSE; + p->len = buf_len; + p->buf[buf_len] = '\0'; + return p; +} + +/* 0 <= c <= 0x10ffff */ +static inline JSValue JS_NewStringChar(uint32_t c) +{ + return JS_VALUE_MAKE_SPECIAL(JS_TAG_STRING_CHAR, c); +} + +static force_inline int utf8_char_len(int c) +{ + int l; + if (c < 0x80) { + l = 1; + } else if (c < 0xc0) { + l = 1; + } else if (c < 0xe0) { + l = 2; + } else if (c < 0xf0) { + l = 3; + } else if (c < 0xf8) { + l = 4; + } else { + l = 1; + } + return l; +} + +static BOOL is_ascii_string(const char *buf, size_t len) +{ + size_t i; + for(i = 0; i < len; i++) { + if ((uint8_t)buf[i] > 0x7f) + return FALSE; + } + return TRUE; +} + +static JSString *get_string_ptr(JSContext *ctx, JSStringCharBuf *buf, + JSValue val) +{ + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + JSString *p = (JSString *)buf; + p->is_unique = FALSE; + p->is_ascii = JS_VALUE_GET_SPECIAL_VALUE(val) <= 0x7f; + p->len = get_short_string(p->buf, val); + return p; + } else { + return JS_VALUE_TO_PTR(val); + } +} + +static JSValue js_sub_string_utf8(JSContext *ctx, JSValue val, + uint32_t start0, uint32_t end0) +{ + JSString *p, *p1; + int len, start, end, c; + BOOL start_surrogate, end_surrogate; + JSStringCharBuf buf; + JSGCRef val_ref; + const uint8_t *ptr; + size_t clen; + + if (end0 - start0 == 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } + start_surrogate = start0 & 1; + end_surrogate = end0 & 1; + start = start0 >> 1; + end = end0 >> 1; + len = end - start; + p1 = get_string_ptr(ctx, &buf, val); + ptr = p1->buf; + if (!start_surrogate && !end_surrogate && utf8_char_len(ptr[start]) == len) { + c = utf8_get(ptr + start, &clen); + return JS_NewStringChar(c); + } + + JS_PUSH_VALUE(ctx, val); + p = js_alloc_string(ctx, len - start_surrogate + (end_surrogate ? 3 : 0)); + JS_POP_VALUE(ctx, val); + if (!p) + return JS_EXCEPTION; + p1 = get_string_ptr(ctx, &buf, val); + ptr = p1->buf; + if (unlikely(start_surrogate || end_surrogate)) { + uint8_t *q = p->buf; + p->is_ascii = FALSE; + if (start_surrogate) { + c = utf8_get(ptr + start, &clen); + c = 0xdc00 + ((c - 0x10000) & 0x3ff); /* right surrogate */ + q += unicode_to_utf8(q, c); + start += 4; + } + memcpy(q, ptr + start, end - start); + q += end - start; + if (end_surrogate) { + c = utf8_get(ptr + end, &clen); + c = 0xd800 + ((c - 0x10000) >> 10); /* left surrogate */ + q += unicode_to_utf8(q, c); + } + assert((q - p->buf) == p->len); + } else { + p->is_ascii = p1->is_ascii ? TRUE : is_ascii_string((const char *)(ptr + start), len); + memcpy(p->buf, ptr + start, len); + } + return JS_VALUE_FROM_PTR(p); +} + +/* Warning: the string must be a valid WTF-8 string (= UTF-8 + + unpaired surrogates). */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t len) +{ + JSString *p; + + if (len == 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } else { + if (utf8_char_len(buf[0]) == len) { + size_t clen; + int c; + c = utf8_get((const uint8_t *)buf, &clen); + return JS_NewStringChar(c); + } + } + p = js_alloc_string(ctx, len); + if (!p) + return JS_EXCEPTION; + p->is_ascii = is_ascii_string((const char *)buf, len); + memcpy(p->buf, buf, len); + return JS_VALUE_FROM_PTR(p); +} + +/* Warning: the string must be a valid UTF-8 string. */ +JSValue JS_NewString(JSContext *ctx, const char *buf) +{ + return JS_NewStringLen(ctx, buf, strlen(buf)); +} + +/* the byte array must be zero terminated. */ +static JSValue js_byte_array_to_string(JSContext *ctx, JSValue val, int len, BOOL is_ascii) +{ + JSByteArray *arr = JS_VALUE_TO_PTR(val); + JSString *p; + + assert(len + 1 <= arr->size); + if (len == 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } else if (utf8_char_len(arr->buf[0]) == len) { + size_t clen; + return JS_NewStringChar(utf8_get(arr->buf, &clen)); + } else { + js_shrink_byte_array(ctx, &val, len + 1); + p = (JSString *)arr; + p->mtag = JS_MTAG_STRING; + p->is_ascii = is_ascii; + p->is_unique = FALSE; + p->is_numeric = FALSE; + p->len = len; + return val; + } +} + +/* in bytes */ +static __maybe_unused int js_string_byte_len(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + int c = JS_VALUE_GET_SPECIAL_VALUE(val); + if (c < 0x80) + return 1; + else if (c < 0x800) + return 2; + else if (c < 0x10000) + return 3; + else + return 4; + } else { + JSString *p = JS_VALUE_TO_PTR(val); + return p->len; + } +} + +/* assuming that utf8_next() returns 4, validate the corresponding UTF-8 sequence */ +static BOOL is_valid_len4_utf8(const uint8_t *buf) +{ + return (((buf[0] & 0xf) << 6) | (buf[1] & 0x3f)) >= 0x10; +} + +static __maybe_unused void dump_string_pos_cache(JSContext *ctx) +{ + int i; + JSStringPosCacheEntry *ce; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) { + ce = &ctx->string_pos_cache[i]; + printf("%d: ", i); + if (ce->str == JS_NULL) { + printf("\n"); + } else { + JSString *p = JS_VALUE_TO_PTR(ce->str); + printf(" utf8_pos=%u/%u utf16_pos=%u\n", + ce->str_pos[POS_TYPE_UTF8], (int)p->len, ce->str_pos[POS_TYPE_UTF16]); + } + } +} + +/* an UTF-8 position is the byte position multiplied by 2. One is + added when the corresponding UTF-16 character represents the right + surrogate if the code is >= 0x10000. +*/ +static uint32_t js_string_convert_pos(JSContext *ctx, JSValue val, uint32_t pos, + StringPosTypeEnum pos_type) +{ + JSStringCharBuf buf; + JSString *p; + size_t i, clen, len, start; + uint32_t d_min, d, j; + JSStringPosCacheEntry *ce, *ce1; + uint32_t surrogate_flag, has_surrogate, limit; + int ce_idx; + + p = get_string_ptr(ctx, &buf, val); + len = p->len; + if (p->is_ascii) { + if (pos_type == POS_TYPE_UTF8) + return min_int(len, pos / 2); + else + return min_int(len, pos) * 2; + } + + if (pos_type == POS_TYPE_UTF8) { + has_surrogate = pos & 1; + pos >>= 1; + } else { + has_surrogate = 0; + } + + ce = NULL; + if (len < JS_STRING_POS_CACHE_MIN_LEN) { + j = 0; + i = 0; + goto uncached; + } + + d_min = pos; + for(ce_idx = 0; ce_idx < JS_STRING_POS_CACHE_SIZE; ce_idx++) { + ce1 = &ctx->string_pos_cache[ce_idx]; + if (ce1->str == val) { + d = ce1->str_pos[pos_type]; + d = d >= pos ? d - pos : pos - d; + if (d < d_min) { + d_min = d; + ce = ce1; + } + } + } + if (!ce) { + /* "random" replacement */ + ce = &ctx->string_pos_cache[ctx->string_pos_cache_counter]; + if (++ctx->string_pos_cache_counter == JS_STRING_POS_CACHE_SIZE) + ctx->string_pos_cache_counter = 0; + ce->str = val; + ce->str_pos[POS_TYPE_UTF8] = 0; + ce->str_pos[POS_TYPE_UTF16] = 0; + } + + i = ce->str_pos[POS_TYPE_UTF8]; + j = ce->str_pos[POS_TYPE_UTF16]; + if (ce->str_pos[pos_type] <= pos) { + uncached: + surrogate_flag = 0; + if (pos_type == POS_TYPE_UTF8) { + limit = INT32_MAX; + len = pos; + } else { + limit = pos; + } + for(; i < len; i += clen) { + if (j == limit) + break; + clen = utf8_char_len(p->buf[i]); + if (clen == 4 && is_valid_len4_utf8(p->buf + i)) { + if ((j + 1) == limit) { + surrogate_flag = 1; + break; + } + j += 2; + } else { + j++; + } + } + } else { + surrogate_flag = 0; + if (pos_type == POS_TYPE_UTF8) { + start = pos; + limit = INT32_MAX; + } else { + limit = pos; + start = 0; + } + while (i > start) { + size_t i0 = i; + i--; + while ((p->buf[i] & 0xc0) == 0x80) + i--; + clen = i0 - i; + if (clen == 4 && is_valid_len4_utf8(p->buf + i)) { + j -= 2; + if ((j + 1) == limit) { + surrogate_flag = 1; + break; + } + } else { + j--; + } + if (j == limit) + break; + } + } + if (ce) { + ce->str_pos[POS_TYPE_UTF8] = i; + ce->str_pos[POS_TYPE_UTF16] = j; + } + if (pos_type == POS_TYPE_UTF8) + return j + has_surrogate; + else + return i * 2 + surrogate_flag; +} + +static uint32_t js_string_utf16_to_utf8_pos(JSContext *ctx, JSValue val, uint32_t utf16_pos) +{ + return js_string_convert_pos(ctx, val, utf16_pos, POS_TYPE_UTF16); +} + +static uint32_t js_string_utf8_to_utf16_pos(JSContext *ctx, JSValue val, uint32_t utf8_pos) +{ + return js_string_convert_pos(ctx, val, utf8_pos, POS_TYPE_UTF8); +} + +/* Testing the third byte is not needed as the UTF-8 encoding must be + correct */ +static BOOL is_utf8_left_surrogate(const uint8_t *p) +{ + return p[0] == 0xed && (p[1] >= 0xa0 && p[1] <= 0xaf); +} + +static BOOL is_utf8_right_surrogate(const uint8_t *p) +{ + return p[0] == 0xed && (p[1] >= 0xb0 && p[1] <= 0xbf); +} + +typedef struct { + JSGCRef buffer_ref; /* string, JSByteBuffer or JS_EXCEPTION */ + int len; /* current string length (in bytes) */ + BOOL is_ascii; +} StringBuffer; + +/* return 0 if OK, -1 in case of exception (exception possible if len > 0) */ +static int string_buffer_push(JSContext *ctx, StringBuffer *s, int len) +{ + s->len = 0; + s->is_ascii = TRUE; + if (len > 0) { + JSByteArray *arr; + arr = js_alloc_byte_array(ctx, len); + if (!arr) + return -1; + s->buffer_ref.val = JS_VALUE_FROM_PTR(arr); + } else { + s->buffer_ref.val = js_get_atom(ctx, JS_ATOM_empty); + } + s->buffer_ref.prev = ctx->top_gc_ref; + ctx->top_gc_ref = &s->buffer_ref; + return 0; +} + +/* val2 must be a string. Return 0 if OK, -1 in case of exception */ +static int string_buffer_concat_str(JSContext *ctx, StringBuffer *s, JSValue val2) +{ + JSStringCharBuf buf1, buf2; + JSByteArray *arr; + JSString *p1, *p2; + int len, len1, len2; + JSValue val1; + uint8_t *q; + + if (JS_IsException(s->buffer_ref.val)) + return -1; + p2 = get_string_ptr(ctx, &buf2, val2); + len2 = p2->len; + if (len2 == 0) + return 0; + if (JS_IsString(ctx, s->buffer_ref.val)) { + p1 = get_string_ptr(ctx, &buf1, s->buffer_ref.val); + len1 = p1->len; + if (len1 == 0) { + /* empty string in buffer: just keep 'val2' */ + s->buffer_ref.val = val2; + return 0; + } + arr = NULL; + val1 = s->buffer_ref.val; + s->buffer_ref.val = JS_NULL; + } else { + arr = JS_VALUE_TO_PTR(s->buffer_ref.val); + len1 = s->len; + val1 = JS_NULL; + } + + len = len1 + len2; + if (len > JS_STRING_LEN_MAX) { + s->buffer_ref.val = JS_ThrowInternalError(ctx, "string too long"); + return -1; + } + + if (!arr || (len + 1) > arr->size) { + JSGCRef val1_ref, val2_ref; + + JS_PUSH_VALUE(ctx, val1); + JS_PUSH_VALUE(ctx, val2); + s->buffer_ref.val = js_resize_byte_array(ctx, s->buffer_ref.val, len + 1); + JS_POP_VALUE(ctx, val2); + JS_POP_VALUE(ctx, val1); + if (JS_IsException(s->buffer_ref.val)) + return -1; + arr = JS_VALUE_TO_PTR(s->buffer_ref.val); + if (val1 != JS_NULL) { + p1 = get_string_ptr(ctx, &buf1, val1); + s->is_ascii = p1->is_ascii; + memcpy(arr->buf, p1->buf, len1); + } + p2 = get_string_ptr(ctx, &buf2, val2); + } + + q = arr->buf + len1; + if (len2 >= 3 && unlikely(is_utf8_right_surrogate(p2->buf)) && + len1 >= 3 && is_utf8_left_surrogate(q - 3)) { + size_t clen; + int c; + /* contract the two surrogates to 4 bytes */ + c = (utf8_get(q - 3, &clen) & 0x3ff) << 10; + c |= (utf8_get(p2->buf, &clen) & 0x3ff); + c += 0x10000; + len -= 2; + len2 -= 3; + q -= 3; + q += unicode_to_utf8(q, c); + s->is_ascii = FALSE; + } + memcpy(q, p2->buf + p2->len - len2, len2); + s->len = len; + s->is_ascii &= p2->is_ascii; + return 0; +} + +/* 'str' must be a string */ +static int string_buffer_concat_utf8(JSContext *ctx, StringBuffer *s, JSValue str, + uint32_t start, uint32_t end) +{ + JSValue val2; + + if (end <= start) + return 0; + /* XXX: avoid explicitly constructing the substring */ + val2 = js_sub_string_utf8(ctx, str, start, end); + if (JS_IsException(val2)) { + s->buffer_ref.val = JS_EXCEPTION; + return -1; + } + return string_buffer_concat_str(ctx, s, val2); +} + +static int string_buffer_concat_utf16(JSContext *ctx, StringBuffer *s, JSValue str, + uint32_t start, uint32_t end) +{ + uint32_t start_utf8, end_utf8; + if (end <= start) + return 0; + start_utf8 = js_string_utf16_to_utf8_pos(ctx, str, start); + end_utf8 = js_string_utf16_to_utf8_pos(ctx, str, end); + return string_buffer_concat_utf8(ctx, s, str, start_utf8, end_utf8); +} + +static int string_buffer_concat(JSContext *ctx, StringBuffer *s, JSValue val2) +{ + val2 = JS_ToString(ctx, val2); + if (JS_IsException(val2)) { + s->buffer_ref.val = JS_EXCEPTION; + return -1; + } + return string_buffer_concat_str(ctx, s, val2); +} + +/* XXX: could optimize */ +static int string_buffer_putc(JSContext *ctx, StringBuffer *s, int c) +{ + return string_buffer_concat_str(ctx, s, JS_NewStringChar(c)); +} + +static int string_buffer_puts(JSContext *ctx, StringBuffer *s, const char *str) +{ + JSValue val; + + /* XXX: avoid this allocation */ + val = JS_NewString(ctx, str); + if (JS_IsException(val)) + return -1; + return string_buffer_concat_str(ctx, s, val); +} + +static JSValue string_buffer_pop(JSContext *ctx, StringBuffer *s) +{ + JSValue res; + if (JS_IsException(s->buffer_ref.val) || + JS_IsString(ctx, s->buffer_ref.val)) { + res = s->buffer_ref.val; + } else { + if (s->len != 0) { + /* add the trailing '\0' */ + JSByteArray *arr = JS_VALUE_TO_PTR(s->buffer_ref.val); + arr->buf[s->len] = '\0'; + } + res = js_byte_array_to_string(ctx, s->buffer_ref.val, s->len, s->is_ascii); + } + ctx->top_gc_ref = s->buffer_ref.prev; + return res; +} + +/* val1 and val2 must be strings or exception */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue val1, JSValue val2) +{ + StringBuffer b_s, *b = &b_s; + + if (JS_IsException(val1) || + JS_IsException(val2)) + return JS_EXCEPTION; + + string_buffer_push(ctx, b, 0); + string_buffer_concat_str(ctx, b, val1); /* no memory allocation */ + string_buffer_concat_str(ctx, b, val2); + return string_buffer_pop(ctx, b); +} + +static BOOL js_string_eq(JSContext *ctx, JSValue val1, JSValue val2) +{ + JSStringCharBuf buf1, buf2; + JSString *p1, *p2; + + p1 = get_string_ptr(ctx, &buf1, val1); + p2 = get_string_ptr(ctx, &buf2, val2); + if (p1->len != p2->len) + return FALSE; + return !memcmp(p1->buf, p2->buf, p1->len); +} + +/* Return the unicode character containing the byte at position + 'i'. Return -1 in case of error. */ +static int string_get_cp(const uint8_t *p) +{ + size_t clen; + while ((*p & 0xc0) == 0x80) + p--; + return utf8_get(p, &clen); +} + +static int js_string_compare(JSContext *ctx, JSValue val1, JSValue val2) +{ + JSStringCharBuf buf1, buf2; + int len, i, res; + JSString *p1, *p2; + + p1 = get_string_ptr(ctx, &buf1, val1); + p2 = get_string_ptr(ctx, &buf2, val2); + len = min_int(p1->len, p2->len); + for(i = 0; i < len; i++) { + if (p1->buf[i] != p2->buf[i]) + break; + } + if (i != len) { + int c1, c2; + /* if valid UTF-8, the strings cannot be equal at this point */ + /* Note: UTF-16 does not preserve unicode order like UTF-8 */ + c1 = string_get_cp(p1->buf + i); + c2 = string_get_cp(p2->buf + i); + if ((c1 < 0x10000 && c2 < 0x10000) || + (c1 >= 0x10000 && c2 >= 0x10000)) { + if (c1 < c2) + res = -1; + else + res = 1; + } else if (c1 < 0x10000) { + /* p1 < p2 if same first UTF-16 char */ + c2 = 0xd800 + ((c2 - 0x10000) >> 10); + if (c1 <= c2) + res = -1; + else + res = 1; + } else { + /* p1 > p2 if same first UTF-16 char */ + c1 = 0xd800 + ((c1 - 0x10000) >> 10); + if (c1 < c2) + res = -1; + else + res = 1; + } + } else { + if (p1->len == p2->len) + res = 0; + else if (p1->len < p2->len) + res = -1; + else + res = 1; + } + return res; +} + +/* return the string length in UTF16 characters. 'val' must be a + string char or a string */ +static int js_string_len(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + return JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1; + } else { + JSString *p; + p = JS_VALUE_TO_PTR(val); + if (p->is_ascii) + return p->len; + else + return js_string_utf8_to_utf16_pos(ctx, val, p->len * 2); + } +} + +/* return the UTF-16 code or the unicode character at a given UTF-8 + position or -1 if outside the string */ +static int string_getcp(JSContext *ctx, JSValue str, uint32_t utf16_pos, BOOL is_codepoint) +{ + JSString *p; + JSStringCharBuf buf; + uint32_t surrogate_flag, c, utf8_pos; + size_t clen; + + utf8_pos = js_string_utf16_to_utf8_pos(ctx, str, utf16_pos); + surrogate_flag = utf8_pos & 1; + utf8_pos >>= 1; + p = get_string_ptr(ctx, &buf, str); + if (utf8_pos >= p->len) + return -1; + c = utf8_get(p->buf + utf8_pos, &clen); + if (c < 0x10000 || (!surrogate_flag && is_codepoint)) { + return c; + } else { + c -= 0x10000; + if (!surrogate_flag) + return 0xd800 + (c >> 10); /* left surrogate */ + else + return 0xdc00 + (c & 0x3ff); /* right surrogate */ + } +} + +static int string_getc(JSContext *ctx, JSValue str, uint32_t utf16_pos) +{ + return string_getcp(ctx, str, utf16_pos, FALSE); +} + +/* precondition: 0 <= start <= end <= string length */ +static JSValue js_sub_string(JSContext *ctx, JSValue val, int start, int end) +{ + uint32_t start_utf8, end_utf8; + + if (end <= start) + return js_get_atom(ctx, JS_ATOM_empty); + start_utf8 = js_string_utf16_to_utf8_pos(ctx, val, start); + end_utf8 = js_string_utf16_to_utf8_pos(ctx, val, end); + return js_sub_string_utf8(ctx, val, start_utf8, end_utf8); +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return TRUE if the property 'val' represents a numeric property. -1 + is returned in case of exception. 'val' must be a string. It is + assumed that NaN and infinities have already been handled. */ +static int js_is_numeric_string(JSContext *ctx, JSValue val) +{ + int c, len; + double d; + const char *r, *q; + JSString *p; + JSByteArray *tmp_arr; + JSGCRef val_ref; + char buf[32]; /* enough for js_dtoa() */ + + p = JS_VALUE_TO_PTR(val); + /* the fast case is when the string is not a number */ + if (p->len == 0 || !p->is_ascii) + return FALSE; + q = (const char *)p->buf; + c = *q; + if (c == '-') { + if (p->len == 1) + return FALSE; + q++; + c = *q; + } + if (!is_num(c)) + return FALSE; + + JS_PUSH_VALUE(ctx, val); + tmp_arr = js_alloc_byte_array(ctx, max_int(sizeof(JSATODTempMem), + sizeof(JSDTOATempMem))); + JS_POP_VALUE(ctx, val); + if (!tmp_arr) + return -1; + p = JS_VALUE_TO_PTR(val); + d = js_atod((char *)p->buf, &r, 10, 0, (JSATODTempMem *)tmp_arr->buf); + if ((r - (char *)p->buf) != p->len) { + js_free(ctx, tmp_arr); + return FALSE; + } + len = js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE, (JSDTOATempMem *)tmp_arr->buf); + js_free(ctx, tmp_arr); + return (p->len == len && !memcmp(buf, p->buf, len)); +} + +/* return JS_NULL if not found */ +static JSValue find_atom(JSContext *ctx, int *pidx, const JSValueArray *arr, int len, JSValue val) +{ + int a, b, m, r; + JSValue val1; + + a = 0; + b = len - 1; + while (a <= b) { + m = (a + b) >> 1; + val1 = arr->arr[m]; + r = js_string_compare(ctx, val, val1); + if (r == 0) { + /* found */ + *pidx = m; + return val1; + } else if (r < 0) { + b = m - 1; + } else { + a = m + 1; + } + } + *pidx = a; + return JS_NULL; +} + +/* if 'val' is not a string, it is returned */ +/* XXX: use hash table */ +static JSValue JS_MakeUniqueString(JSContext *ctx, JSValue val) +{ + JSString *p; + int a, is_numeric, i; + JSValueArray *arr; + const JSValueArray *arr1; + JSValue val1, new_tab; + JSGCRef val_ref; + + if (!JS_IsPtr(val)) + return val; + p = JS_VALUE_TO_PTR(val); + if (p->mtag != JS_MTAG_STRING || p->is_unique) + return val; + + /* not unique: find it in the ROM or RAM sorted unique string table */ + for(i = 0; i < ctx->n_rom_atom_tables; i++) { + arr1 = ctx->rom_atom_tables[i]; + if (arr1) { + val1 = find_atom(ctx, &a, arr1, arr1->size, val); + if (!JS_IsNull(val1)) + return val1; + } + } + + arr = JS_VALUE_TO_PTR( ctx->unique_strings); + val1 = find_atom(ctx, &a, arr, ctx->unique_strings_len, val); + if (!JS_IsNull(val1)) + return val1; + + JS_PUSH_VALUE(ctx, val); + is_numeric = js_is_numeric_string(ctx, val); + JS_POP_VALUE(ctx, val); + if (is_numeric < 0) + return JS_EXCEPTION; + + /* not found: add it in the table */ + JS_PUSH_VALUE(ctx, val); + new_tab = js_resize_value_array(ctx, ctx->unique_strings, + ctx->unique_strings_len + 1); + JS_POP_VALUE(ctx, val); + if (JS_IsException(new_tab)) + return JS_EXCEPTION; + ctx->unique_strings = new_tab; + arr = JS_VALUE_TO_PTR( ctx->unique_strings); + memmove(&arr->arr[a + 1], &arr->arr[a], + sizeof(arr->arr[0]) * (ctx->unique_strings_len - a)); + arr->arr[a] = val; + p = JS_VALUE_TO_PTR(val); + p->is_unique = TRUE; + p->is_numeric = is_numeric; + ctx->unique_strings_len++; + return val; +} + +static int JS_ToBool(JSContext *ctx, JSValue val) +{ + if (JS_IsInt(val)) { + return JS_VALUE_GET_INT(val) != 0; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + double d; + d = js_get_short_float(val); + return !isnan(d) && d != 0; + } else +#endif + if (!JS_IsPtr(val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_SPECIAL_VALUE(val); + case JS_TAG_SHORT_FUNC: + case JS_TAG_STRING_CHAR: + return TRUE; + default: + return FALSE; + } + } else { + JSMemBlockHeader *h = JS_VALUE_TO_PTR(val); + switch(h->mtag) { + case JS_MTAG_STRING: + { + JSString *p = (JSString *)h; + return p->len != 0; + } + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = (JSFloat64 *)h; + return !isnan(p->u.dval) && p->u.dval != 0; + } + default: + case JS_MTAG_OBJECT: + return TRUE; + } + } +} + +/* plen can be NULL. No memory allocation is done if 'val' already is + a string. */ +const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val, + JSCStringBuf *buf) +{ + const char *p; + int len; + + val = JS_ToString(ctx, val); + if (JS_IsException(val)) + return NULL; + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + len = get_short_string(buf->buf, val); + p = (const char *)buf->buf; + } else { + JSString *r; + r = JS_VALUE_TO_PTR(val); + p = (const char *)r->buf; + len = r->len; + } + if (plen) + *plen = len; + return p; +} + +const char *JS_ToCString(JSContext *ctx, JSValue val, JSCStringBuf *buf) +{ + return JS_ToCStringLen(ctx, NULL, val, buf); +} + +JSValue JS_GetException(JSContext *ctx) +{ + JSValue obj; + obj = ctx->current_exception; + ctx->current_exception = JS_UNDEFINED; + return obj; +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val) +{ + if (val == JS_NULL || val == JS_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +/* 'val' must be a string. return TRUE if the string represents a + short integer */ +static inline BOOL is_num_string(JSContext *ctx, int32_t *pval, JSValue val) +{ + JSStringCharBuf buf; + uint32_t n; + uint64_t n64; + JSString *p1; + int c, is_neg; + const uint8_t *p, *p_end; + + p1 = get_string_ptr(ctx, &buf, val); + if (p1->len == 0 || p1->len > 11 || !p1->is_ascii) + return FALSE; + p = p1->buf; + p_end = p + p1->len; + c = *p++; + is_neg = 0; + if (c == '-') { + if (p >= p_end) + return FALSE; + is_neg = 1; + c = *p++; + } + if (!is_num(c)) + return FALSE; + if (c == '0') { + if (p != p_end || is_neg) + return FALSE; + n = 0; + } else { + n = c - '0'; + while (p < p_end) { + c = *p++; + if (!is_num(c)) + return FALSE; + /* XXX: simplify ? */ + n64 = (uint64_t)n * 10 + (c - '0'); + if (n64 > (JS_SHORTINT_MAX + is_neg)) + return FALSE; + n = n64; + } + if (is_neg) + n = -n; + } + *pval = n; + return TRUE; +} + +/* return TRUE if the property 'val' represent a numeric property. It + is assumed that the shortint case has been tested before */ +static BOOL JS_IsNumericProperty(JSContext *ctx, JSValue val) +{ + JSString *p; + if (!JS_IsPtr(val)) + return FALSE; /* JS_TAG_STRING_CHAR */ + p = JS_VALUE_TO_PTR(val); + return p->is_numeric; +} + +static JSValueArray *js_alloc_value_array(JSContext *ctx, int init_base, int new_size) +{ + JSValueArray *arr; + int i; + + if (new_size > JS_VALUE_ARRAY_SIZE_MAX) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + arr = js_malloc(ctx, sizeof(JSValueArray) + new_size * sizeof(JSValue), JS_MTAG_VALUE_ARRAY); + if (!arr) + return NULL; + arr->size = new_size; + for(i = init_base; i < new_size; i++) + arr->arr[i] = JS_UNDEFINED; + return arr; +} + +/* val can be JS_NULL (zero size). 'prop_base' is non zero only when + * resizing the property arrays so that the property array has a size + * which is a multiple of 3 */ +static JSValue js_resize_value_array2(JSContext *ctx, JSValue val, int new_size, int prop_base) +{ + JSValueArray *slots, *new_slots; + int old_size, new_size1; + JSGCRef val_ref; + + if (val == JS_NULL) { + slots = NULL; + old_size = 0; + } else { + slots = JS_VALUE_TO_PTR(val); + old_size = slots->size; + } + if (unlikely(new_size > old_size)) { + new_size1 = old_size + old_size / 2; + if (new_size1 > new_size) { + new_size = new_size1; + /* ensure that the property array has a size which is a + * multiple of 3 */ + if (prop_base != 0) { + int align = (new_size - prop_base) % 3; + if (align != 0) + new_size += 3 - align; + } + } + new_size = max_int(new_size, old_size + old_size / 2); + JS_PUSH_VALUE(ctx, val); + new_slots = js_alloc_value_array(ctx, old_size, new_size); + JS_POP_VALUE(ctx, val); + if (!new_slots) + return JS_EXCEPTION; + if (old_size > 0) { + slots = JS_VALUE_TO_PTR(val); + memcpy(new_slots->arr, slots->arr, old_size * sizeof(JSValue)); + } + val = JS_VALUE_FROM_PTR(new_slots); + } + return val; +} + +static JSValue js_resize_value_array(JSContext *ctx, JSValue val, int new_size) +{ + return js_resize_value_array2(ctx, val, new_size, 0); +} + +/* no allocation is done */ +static void js_shrink_value_array(JSContext *ctx, JSValue *pval, int new_size) +{ + JSValueArray *arr; + if (*pval == JS_NULL) + return; + arr = JS_VALUE_TO_PTR(*pval); + assert(new_size <= arr->size); + if (new_size == 0) { + js_free(ctx, arr); + *pval = JS_NULL; + } else { + arr = js_shrink(ctx, arr, sizeof(JSValueArray) + new_size * sizeof(JSValue)); + arr->size = new_size; + } +} + +static JSByteArray *js_alloc_byte_array(JSContext *ctx, int size) +{ + JSByteArray *arr; + + if (size > JS_BYTE_ARRAY_SIZE_MAX) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + arr = js_malloc(ctx, sizeof(JSByteArray) + size, JS_MTAG_BYTE_ARRAY); + if (!arr) + return NULL; + arr->size = size; + return arr; +} + +static JSValue js_resize_byte_array(JSContext *ctx, JSValue val, int new_size) +{ + JSByteArray *arr, *new_arr; + int old_size; + JSGCRef val_ref; + + if (val == JS_NULL) { + arr = NULL; + old_size = 0; + } else { + arr = JS_VALUE_TO_PTR(val); + old_size = arr->size; + } + if (unlikely(new_size > old_size)) { + new_size = max_int(new_size, old_size + old_size / 2); + JS_PUSH_VALUE(ctx, val); + new_arr = js_alloc_byte_array(ctx, new_size); + JS_POP_VALUE(ctx, val); + if (!new_arr) + return JS_EXCEPTION; + if (old_size > 0) { + arr = JS_VALUE_TO_PTR(val); + memcpy(new_arr->buf, arr->buf, old_size); + } + val = JS_VALUE_FROM_PTR(new_arr); + } + return val; +} + +static void js_shrink_byte_array(JSContext *ctx, JSValue *pval, int new_size) +{ + JSByteArray *arr; + if (*pval == JS_NULL) + return; + arr = JS_VALUE_TO_PTR(*pval); + assert(new_size <= arr->size); + if (new_size == 0) { + js_free(ctx, arr); + *pval = JS_NULL; + } else { + arr = js_shrink(ctx, arr, sizeof(JSByteArray) + new_size); + arr->size = new_size; + } +} + +/* extra_size is in bytes */ +static JSObject *JS_NewObjectProtoClass1(JSContext *ctx, JSValue proto, + int class_id, int extra_size) +{ + JSObject *p; + JSGCRef proto_ref; + extra_size = (unsigned)(extra_size + JSW - 1) / JSW; + JS_PUSH_VALUE(ctx, proto); + p = js_malloc(ctx, offsetof(JSObject, u) + extra_size * JSW, JS_MTAG_OBJECT); + JS_POP_VALUE(ctx, proto); + if (!p) + return NULL; + p->class_id = class_id; + p->extra_size = extra_size; + p->proto = proto; + p->props = ctx->empty_props; + return p; +} + +static JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, int class_id, int extra_size) +{ + JSObject *p; + p = JS_NewObjectProtoClass1(ctx, proto, class_id, extra_size); + if (!p) + return JS_EXCEPTION; + else + return JS_VALUE_FROM_PTR(p); +} + +static JSValue JS_NewObjectClass(JSContext *ctx, int class_id, int extra_size) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id, extra_size); +} + +JSValue JS_NewObjectClassUser(JSContext *ctx, int class_id) +{ + JSObject *p; + assert(class_id >= JS_CLASS_USER); + p = JS_NewObjectProtoClass1(ctx, ctx->class_proto[class_id], class_id, + sizeof(JSObjectUserData)); + if (!p) + return JS_EXCEPTION; + p->u.user.opaque = NULL; + return JS_VALUE_FROM_PTR(p); +} + +JSValue JS_NewObject(JSContext *ctx) +{ + return JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0); +} + +/* same as JS_NewObject() but preallocate for 'n' properties */ +JSValue JS_NewObjectPrealloc(JSContext *ctx, int n) +{ + JSValue obj; + JSValueArray *arr; + JSObject *p; + JSGCRef obj_ref; + + obj = JS_NewObjectClass(ctx, JS_CLASS_OBJECT, 0); + if (JS_IsException(obj) || n <= 0) + return obj; + JS_PUSH_VALUE(ctx, obj); + arr = js_alloc_props(ctx, n); + JS_POP_VALUE(ctx, obj); + if (!arr) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(obj); + p->props = JS_VALUE_FROM_PTR(arr); + return obj; +} + +JSValue JS_NewArray(JSContext *ctx, int initial_len) +{ + JSObject *p; + JSValue val; + JSGCRef val_ref; + + val = JS_NewObjectClass(ctx, JS_CLASS_ARRAY, sizeof(JSArrayData)); + if (JS_IsException(val)) + return val; + p = JS_VALUE_TO_PTR(val); + p->u.array.tab = JS_NULL; + p->u.array.len = 0; + if (initial_len > 0) { + JSValueArray *arr; + JS_PUSH_VALUE(ctx, val); + arr = js_alloc_value_array(ctx, 0, initial_len); + JS_POP_VALUE(ctx, val); + if (!arr) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(val); + p->u.array.tab = JS_VALUE_FROM_PTR(arr); + p->u.array.len = initial_len; + } + return val; +} + +static inline uint32_t hash_prop(JSValue prop) +{ + return (prop / JSW) ^ (prop % JSW); /* XXX: improve */ +} + +/* return NULL if not found */ +static force_inline JSProperty *find_own_property_inlined(JSContext *ctx, + JSObject *p, JSValue prop) +{ + JSValueArray *arr; + JSProperty *pr; + uint32_t hash_mask, h, idx; + + arr = JS_VALUE_TO_PTR(p->props); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + h = hash_prop(prop) & hash_mask; + idx = arr->arr[2 + h]; /* JSValue, hence idx * 2 */ + while (idx != 0) { + pr = (JSProperty *)((uint8_t *)arr->arr + idx * (sizeof(JSValue) / 2)); + if (pr->key == prop) + return pr; + idx = pr->hash_next; /* JSValue, hence idx * 2 */ + } + return NULL; +} + +static inline JSProperty *find_own_property(JSContext *ctx, + JSObject *p, JSValue prop) +{ + return find_own_property_inlined(ctx, p, prop); +} + +static JSValue get_special_prop(JSContext *ctx, JSValue val) +{ + int idx; + /* 'prototype' or 'constructor' property in ROM */ + idx = JS_VALUE_GET_INT(val); + if (idx >= 0) + return ctx->class_proto[idx]; + else + return ctx->class_obj[-idx - 1]; +} + +/* return the value or: + - exception + - tail call : returned in case of getter and handle_getset = + true. The function is put on the stack +*/ +static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj, JSValue prop, + BOOL allow_tail_call) +{ + JSObject *p; + JSValue proto; + JSProperty *pr; + + if (unlikely(!JS_IsPtr(obj))) { + if (JS_IsIntOrShortFloat(obj)) { + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(obj)) { + case JS_TAG_BOOL: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]); + break; + case JS_TAG_SHORT_FUNC: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]); + break; + case JS_TAG_STRING_CHAR: + goto string_proto; + case JS_TAG_NULL: + return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of undefined", prop); + default: + goto no_prop; + } + } + } else { + p = JS_VALUE_TO_PTR(obj); + } + if (unlikely(p->mtag != JS_MTAG_OBJECT)) { + switch(p->mtag) { + case JS_MTAG_FLOAT64: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + break; + case JS_MTAG_STRING: + string_proto: + { + if (JS_IsInt(prop)) { + JSValue ret; + ret = js_string_charAt(ctx, &obj, 1, &prop, magic_internalAt); + if (!JS_IsUndefined(ret)) + return ret; + } + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]); + } + break; + default: + no_prop: + return JS_ThrowTypeError(ctx, "cannot read property '%"JSValue_PRI"' of value", prop); + } + } + + for(;;) { + if (p->class_id == JS_CLASS_ARRAY) { + if (JS_IsInt(prop)) { + uint32_t idx = JS_VALUE_GET_INT(prop); + if (idx < p->u.array.len) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + return arr->arr[idx]; + } + } else if (JS_IsNumericProperty(ctx, prop)) { + return JS_UNDEFINED; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + if (JS_IsInt(prop)) { + uint32_t idx = JS_VALUE_GET_INT(prop); + JSObject *pbuffer; + JSByteArray *arr; + if (idx < p->u.typed_array.len) { + idx += p->u.typed_array.offset; + pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer); + switch(p->class_id) { + default: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + return JS_NewShortInt(*((uint8_t *)arr->buf + idx)); + case JS_CLASS_INT8_ARRAY: + return JS_NewShortInt(*((int8_t *)arr->buf + idx)); + case JS_CLASS_INT16_ARRAY: + return JS_NewShortInt(*((int16_t *)arr->buf + idx)); + case JS_CLASS_UINT16_ARRAY: + return JS_NewShortInt(*((uint16_t *)arr->buf + idx)); + case JS_CLASS_INT32_ARRAY: + return JS_NewInt32(ctx, *((int32_t *)arr->buf + idx)); + case JS_CLASS_UINT32_ARRAY: + return JS_NewUint32(ctx, *((uint32_t *)arr->buf + idx)); + case JS_CLASS_FLOAT32_ARRAY: + return JS_NewFloat64(ctx, *((float *)arr->buf + idx)); + case JS_CLASS_FLOAT64_ARRAY: + return JS_NewFloat64(ctx, *((double *)arr->buf + idx)); + } + } + } else if (JS_IsNumericProperty(ctx, prop)) { + return JS_UNDEFINED; + } + } + + pr = find_own_property(ctx, p, prop); + if (pr) { + if (likely(pr->prop_type == JS_PROP_NORMAL)) { + return pr->value; + } else if (pr->prop_type == JS_PROP_VARREF) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + /* always detached */ + return pv->u.value; + } else if (pr->prop_type == JS_PROP_SPECIAL) { + return get_special_prop(ctx, pr->value); + } else { + JSValueArray *arr = JS_VALUE_TO_PTR(pr->value); + JSValue getter = arr->arr[0]; + if (getter == JS_UNDEFINED) + return JS_UNDEFINED; + if (allow_tail_call) { + /* It is assumed 'this_obj' is on the stack and + that the stack has some slack to add one element. */ + ctx->sp[-1] = ctx->sp[0]; + ctx->sp[0] = getter; + ctx->sp--; + return JS_NewTailCall(0); + } else { + JSGCRef getter_ref, obj_ref; + int err; + JS_PUSH_VALUE(ctx, getter); + JS_PUSH_VALUE(ctx, obj); + err = JS_StackCheck(ctx, 2); + JS_POP_VALUE(ctx, obj); + JS_POP_VALUE(ctx, getter); + if (err) + return JS_EXCEPTION; + JS_PushArg(ctx, getter); + JS_PushArg(ctx, obj); + return JS_Call(ctx, 0); + } + } + } + /* look in the prototype */ + proto = p->proto; + if (proto == JS_NULL) + break; + p = JS_VALUE_TO_PTR(proto); + } + return JS_UNDEFINED; +} + +static JSValue JS_GetProperty(JSContext *ctx, JSValue obj, JSValue prop) +{ + return JS_GetPropertyInternal(ctx, obj, prop, FALSE); +} + +JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, const char *str) +{ + JSValue prop; + JSGCRef this_obj_ref; + + JS_PUSH_VALUE(ctx, this_obj); + prop = JS_NewString(ctx, str); + if (!JS_IsException(prop)) { + prop = JS_ToPropertyKey(ctx, prop); + } + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(prop)) + return prop; + return JS_GetProperty(ctx, this_obj, prop); +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue obj, uint32_t idx) +{ + if (idx > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array index"); + return JS_GetProperty(ctx, obj, JS_NewInt32(ctx, idx)); +} + +static BOOL JS_HasProperty(JSContext *ctx, JSValue obj, JSValue prop) +{ + JSObject *p; + JSProperty *pr; + + if (!JS_IsPtr(obj)) + return FALSE; + p = JS_VALUE_TO_PTR(obj); + if (p->mtag != JS_MTAG_OBJECT) + return FALSE; + for(;;) { + pr = find_own_property(ctx, p, prop); + if (pr) + return TRUE; + obj = p->proto; + if (obj == JS_NULL) + break; + p = JS_VALUE_TO_PTR(obj); + } + return FALSE; +} + +static int get_prop_hash_size_log2(int prop_count) +{ + /* XXX: adjust ? */ + if (prop_count <= 1) + return 0; + else + return (32 - clz32(prop_count - 1)) - 1; +} + +/* allocate 'n' properties, assuming n >= 1 */ +static JSValueArray *js_alloc_props(JSContext *ctx, int n) +{ + int hash_size_log2, hash_mask, size, i, first_free; + JSValueArray *arr; + JSProperty *pr; + + hash_size_log2 = get_prop_hash_size_log2(n); + hash_mask = (1 << hash_size_log2) - 1; + first_free = 2 + hash_mask + 1; + size = first_free + 3 * n; + arr = js_alloc_value_array(ctx, 0, size); + if (!arr) + return NULL; + arr->arr[0] = JS_NewShortInt(0); /* no property is allocated yet */ + arr->arr[1] = JS_NewShortInt(hash_mask); + for(i = 0; i <= hash_mask; i++) + arr->arr[2 + i] = 0; + pr = NULL; /* avoid warning */ + for(i = 0; i < n; i++) { + pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i]; + pr->key = JS_UNINITIALIZED; + } + /* last property */ + pr->hash_next = first_free << 1; + return arr; +} + +static void js_rehash_props(JSContext *ctx, JSObject *p, BOOL gc_rehash) +{ + JSValueArray *arr; + int prop_count, hash_mask, h, idx, i, j; + JSProperty *pr; + + arr = JS_VALUE_TO_PTR(p->props); + if (JS_IS_ROM_PTR(ctx, arr)) + return; + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + if (hash_mask == 0 && gc_rehash) + return; /* no need to rehash if single hash entry */ + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + for(i = 0; i <= hash_mask; i++) { + arr->arr[2 + i] = JS_NewShortInt(0); + } + for(i = 0, j = 0; j < prop_count; i++) { + idx = 2 + (hash_mask + 1) + 3 * i; + pr = (JSProperty *)&arr->arr[idx]; + if (pr->key != JS_UNINITIALIZED) { + h = hash_prop(pr->key) & hash_mask; + pr->hash_next = arr->arr[2 + h]; + arr->arr[2 + h] = JS_NewShortInt(idx); + j++; + } + } +} + +/* Compact the properties. No memory allocation is done */ +static void js_compact_props(JSContext *ctx, JSObject *p) +{ + JSValueArray *arr; + int prop_count, hash_mask, i, j, hash_size_log2; + int new_size, new_hash_mask; + JSProperty *pr, *pr1; + + arr = JS_VALUE_TO_PTR(p->props); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + + /* no property */ + if (prop_count == 0) { + if (p->props != ctx->empty_props) { + //js_free(ctx, p->props); + p->props = ctx->empty_props; + } + return; + } + + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + hash_size_log2 = get_prop_hash_size_log2(prop_count); + new_hash_mask = min_int(hash_mask, (1 << hash_size_log2) - 1); + new_size = 2 + new_hash_mask + 1 + 3 * prop_count; + if (new_size >= arr->size) + return; /* nothing to do */ + // printf("compact_props: new_size=%d size=%d hash=%d\n", new_size, arr->size, new_hash_mask); + + arr->arr[1] = JS_NewShortInt(new_hash_mask); + + /* move the properties, skipping the deleted ones */ + for(i = 0, j = 0; j < prop_count; i++) { + pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i]; + if (pr->key != JS_UNINITIALIZED) { + pr1 = (JSProperty *)&arr->arr[2 + (new_hash_mask + 1) + 3 * j]; + *pr1 = *pr; + j++; + } + } + + js_shrink_value_array(ctx, &p->props, new_size); + + js_rehash_props(ctx, p, FALSE); +} + +/* if the existing properties are in ROM, copy them to RAM. Return non zero if error */ +static int js_update_props(JSContext *ctx, JSValue obj) +{ + JSObject *p; + JSValueArray *arr, *arr1; + JSGCRef obj_ref; + int i, idx, prop_count, hash_mask; + JSProperty *pr; + + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->props); + if (!JS_IS_ROM_PTR(ctx, arr)) + return 0; + JS_PUSH_VALUE(ctx, obj); + arr1 = js_alloc_value_array(ctx, 0, arr->size); + JS_POP_VALUE(ctx, obj); + if (!arr1) + return -1; + /* no rehashing is needed because all the atoms are in ROM */ + memcpy(arr1->arr, arr->arr, arr->size * sizeof(JSValue)); + prop_count = JS_VALUE_GET_INT(arr1->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr1->arr[1]); + /* no deleted properties in ROM */ + assert(arr1->size == 2 + (hash_mask + 1) + 3 * prop_count); + /* convert JS_PROP_SPECIAL properties ("prototype" and "constructor") */ + for(i = 0; i < prop_count; i++) { + idx = 2 + (hash_mask + 1) + 3 * i; + pr = (JSProperty *)&arr1->arr[idx]; + if (pr->prop_type == JS_PROP_SPECIAL) { + pr->value = get_special_prop(ctx, pr->value); + pr->prop_type = JS_PROP_NORMAL; + } + } + + p = JS_VALUE_TO_PTR(obj); + p->props = JS_VALUE_FROM_PTR(arr1); + return 0; +} + +/* compute 'first_free' in a property list */ +static int get_first_free(JSValueArray *arr) +{ + JSProperty *pr1; + int first_free; + + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + if (pr1->key == JS_UNINITIALIZED) + first_free = pr1->hash_next >> 1; + else + first_free = arr->size; + return first_free; +} + +/* It is assumed that the property does not already exists. */ +static JSProperty *js_create_property(JSContext *ctx, JSValue obj, + JSValue prop) +{ + JSObject *p; + JSValueArray *arr; + int prop_count, hash_mask, new_size, h, first_free, new_hash_mask; + JSProperty *pr, *pr1; + JSValue new_props; + JSGCRef obj_ref, prop_ref; + + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->props); + + // JS_DumpValue(ctx, "create", prop); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + /* extend the array if no space left (this single test is valid + even if the property list is empty) */ + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + if (pr1->key != JS_UNINITIALIZED) { + if (p->props == ctx->empty_props) { + /* XXX: remove and move empty_props to ROM */ + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + arr = js_alloc_props(ctx, 1); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!arr) + return NULL; + p = JS_VALUE_TO_PTR(obj); + p->props = JS_VALUE_FROM_PTR(arr); + first_free = 3; + } else { + first_free = arr->size; + new_size = first_free + 3; + new_hash_mask = hash_mask; + if ((prop_count + 1) > 2 * (hash_mask + 1)) { + /* resize the hash table if too many properties */ + new_hash_mask = 2 * (hash_mask + 1) - 1; + new_size += new_hash_mask - hash_mask; + } + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + // printf("resize_props: new_size=%d hash=%d %d\n", new_size, new_hash_mask, hash_mask); + new_props = js_resize_value_array2(ctx, p->props, new_size, 2 + new_hash_mask + 1); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(new_props)) + return NULL; + p = JS_VALUE_TO_PTR(obj); + p->props = new_props; + arr = JS_VALUE_TO_PTR(p->props); + if (new_hash_mask != hash_mask) { + /* rebuild the hash table */ + memmove(&arr->arr[2 + (new_hash_mask + 1)], + &arr->arr[2 + (hash_mask + 1)], + (first_free - (2 + hash_mask + 1)) * sizeof(JSValue)); + first_free += new_hash_mask - hash_mask; + hash_mask = new_hash_mask; + arr->arr[1] = JS_NewShortInt(hash_mask); + js_rehash_props(ctx, p, FALSE); + } + } + /* ensure the last element is marked as uninitialized to store 'first_free' */ + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + pr1->key = JS_UNINITIALIZED; + } else { + first_free = pr1->hash_next >> 1; + } + + pr = (JSProperty *)&arr->arr[first_free]; + pr->key = prop; + pr->value = JS_UNDEFINED; + pr->prop_type = JS_PROP_NORMAL; + h = hash_prop(prop) & hash_mask; + pr->hash_next = arr->arr[2 + h]; + arr->arr[2 + h] = JS_NewShortInt(first_free); + arr->arr[0] = JS_NewShortInt(prop_count + 1); + /* update first_free */ + first_free += 3; + if (first_free < arr->size) { + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + pr1->hash_next = first_free << 1; + } + + return pr; +} + +/* don't do property lookup if not present */ +#define JS_DEF_PROP_LOOKUP (1 << 0) +/* return the raw property value */ +#define JS_DEF_PROP_RET_VAL (1 << 1) +#define JS_DEF_PROP_HAS_VALUE (1 << 2) +#define JS_DEF_PROP_HAS_GET (1 << 3) +#define JS_DEF_PROP_HAS_SET (1 << 4) + +/* XXX: handle arrays and typed arrays */ +static JSValue JS_DefinePropertyInternal(JSContext *ctx, JSValue obj, + JSValue prop, JSValue val, + JSValue setter, int flags) +{ + JSProperty *pr; + JSValueArray *arr; + JSGCRef obj_ref, prop_ref, val_ref, setter_ref; + int ret, prop_type; + + /* move to RAM if needed */ + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + ret = js_update_props(ctx, obj); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (ret) + return JS_EXCEPTION; + + if (flags & JS_DEF_PROP_LOOKUP) { + pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop); + if (pr) { + if (flags & JS_DEF_PROP_HAS_VALUE) { + if (pr->prop_type == JS_PROP_NORMAL) { + pr->value = val; + } else if (pr->prop_type == JS_PROP_VARREF) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + pv->u.value = val; + } else { + goto error_modify; + } + } else if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) { + if (pr->prop_type != JS_PROP_GETSET) { + error_modify: + return JS_ThrowTypeError(ctx, "cannot modify getter/setter/value kind"); + } + arr = JS_VALUE_TO_PTR(pr->value); + if (unlikely(JS_IS_ROM_PTR(ctx, arr))) { + /* move to RAM */ + JSValueArray *arr2; + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + arr2 = js_alloc_value_array(ctx, 0, 2); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!arr2) + return JS_EXCEPTION; + pr = find_own_property(ctx, JS_VALUE_TO_PTR(obj), prop); + arr = JS_VALUE_TO_PTR(pr->value); + arr2->arr[0] = arr->arr[0]; + arr2->arr[1] = arr->arr[1]; + pr->value = JS_VALUE_FROM_PTR(arr2); + arr = arr2; + } + if (flags & JS_DEF_PROP_HAS_GET) + arr->arr[0] = val; + if (flags & JS_DEF_PROP_HAS_SET) + arr->arr[1] = setter; + } + goto done; + } + } + + if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) { + prop_type = JS_PROP_GETSET; + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + arr = js_alloc_value_array(ctx, 0, 2); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!arr) + return JS_EXCEPTION; + arr->arr[0] = val; + arr->arr[1] = setter; + val = JS_VALUE_FROM_PTR(arr); + } else if (obj == ctx->global_obj) { + JSVarRef *pv; + + prop_type = JS_PROP_VARREF; + JS_PUSH_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + pv = js_malloc(ctx, sizeof(JSVarRef) - sizeof(JSValue), JS_MTAG_VARREF); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, obj); + if (!pv) + return JS_EXCEPTION; + pv->is_detached = TRUE; + pv->u.value = val; + val = JS_VALUE_FROM_PTR(pv); + } else { + prop_type = JS_PROP_NORMAL; + } + JS_PUSH_VALUE(ctx, val); + pr = js_create_property(ctx, obj, prop); + JS_POP_VALUE(ctx, val); + if (!pr) + return JS_EXCEPTION; + pr->prop_type = prop_type; + pr->value = val; + done: + if (flags & JS_DEF_PROP_RET_VAL) { + return pr->value; + } else { + return JS_UNDEFINED; + } +} + +static JSValue JS_DefinePropertyValue(JSContext *ctx, JSValue obj, + JSValue prop, JSValue val) +{ + return JS_DefinePropertyInternal(ctx, obj, prop, val, JS_NULL, + JS_DEF_PROP_LOOKUP | JS_DEF_PROP_HAS_VALUE); +} + +static JSValue JS_DefinePropertyGetSet(JSContext *ctx, JSValue obj, + JSValue prop, JSValue getter, + JSValue setter, int flags) +{ + return JS_DefinePropertyInternal(ctx, obj, prop, getter, setter, + JS_DEF_PROP_LOOKUP | flags); +} + +/* return a JSVarRef or an exception. */ +static JSValue add_global_var(JSContext *ctx, JSValue prop, BOOL define_flag) +{ + JSObject *p; + JSProperty *pr; + + p = JS_VALUE_TO_PTR(ctx->global_obj); + pr = find_own_property(ctx, p, prop); + if (pr) { + if (pr->prop_type != JS_PROP_VARREF) + return JS_ThrowReferenceError(ctx, "global variable '%"JSValue_PRI"' must be a reference", prop); + if (define_flag) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + /* define the variable if needed */ + if (pv->u.value == JS_UNINITIALIZED) + pv->u.value = JS_UNDEFINED; + } + return pr->value; + } + return JS_DefinePropertyInternal(ctx, ctx->global_obj, prop, + define_flag ? JS_UNDEFINED : JS_UNINITIALIZED, JS_NULL, + JS_DEF_PROP_RET_VAL | JS_DEF_PROP_HAS_VALUE); +} + +/* return JS_UNDEFINED in the normal case. Otherwise: + - exception + - tail call : returned in case of getter and handle_getset = + true. The function is put on the stack +*/ +static JSValue JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj, + JSValue prop, JSValue val, + BOOL allow_tail_call) +{ + JSValue proto; + JSObject *p; + JSProperty *pr; + BOOL is_obj; + + if (unlikely(!JS_IsPtr(this_obj))) { + is_obj = FALSE; + if (JS_IsIntOrShortFloat(this_obj)) { + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + goto prototype_lookup; + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(this_obj)) { + case JS_TAG_BOOL: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_BOOLEAN]); + goto prototype_lookup; + case JS_TAG_SHORT_FUNC: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_CLOSURE]); + goto prototype_lookup; + case JS_TAG_STRING_CHAR: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]); + goto prototype_lookup; + case JS_TAG_NULL: + return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of undefined", prop); + default: + goto no_prop; + } + } + } else { + is_obj = TRUE; + p = JS_VALUE_TO_PTR(this_obj); + } + if (unlikely(p->mtag != JS_MTAG_OBJECT)) { + is_obj = FALSE; + switch(p->mtag) { + case JS_MTAG_FLOAT64: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_NUMBER]); + goto prototype_lookup; + case JS_MTAG_STRING: + p = JS_VALUE_TO_PTR(ctx->class_proto[JS_CLASS_STRING]); + goto prototype_lookup; + default: + no_prop: + return JS_ThrowTypeError(ctx, "cannot set property '%"JSValue_PRI"' of value", prop); + } + } + + /* search if the property is already present */ + if (p->class_id == JS_CLASS_ARRAY) { + if (JS_IsInt(prop)) { + JSValueArray *arr; + uint32_t idx = JS_VALUE_GET_INT(prop); + /* not standard: we refuse to add properties to object + except at the last position */ + if (idx < p->u.array.len) { + arr = JS_VALUE_TO_PTR(p->u.array.tab); + arr->arr[idx] = val; + return JS_UNDEFINED; + } else if (idx == p->u.array.len) { + JSValue new_tab; + JSGCRef this_obj_ref, val_ref; + + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, val); + new_tab = js_resize_value_array(ctx, p->u.array.tab, idx + 1); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(new_tab)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(this_obj); + p->u.array.tab = new_tab; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + arr->arr[idx] = val; + p->u.array.len++; + return JS_UNDEFINED; + } else { + goto invalid_array_subscript; + } + } else if (JS_IsNumericProperty(ctx, prop)) { + goto invalid_array_subscript; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + if (JS_IsInt(prop)) { + uint32_t idx = JS_VALUE_GET_INT(prop); + int v, conv_ret; + double d; + JSObject *pbuffer; + JSByteArray *arr; + JSGCRef val_ref, this_obj_ref; + + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, val); + switch(p->class_id) { + case JS_CLASS_UINT8C_ARRAY: + conv_ret = JS_ToUint8Clamp(ctx, &v, val); + break; + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + conv_ret = JS_ToNumber(ctx, &d, val); + break; + default: + conv_ret = JS_ToInt32(ctx, &v, val); + break; + } + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, this_obj); + if (conv_ret) + return JS_EXCEPTION; + + p = JS_VALUE_TO_PTR(this_obj); + if (idx >= p->u.typed_array.len) + goto invalid_array_subscript; + idx += p->u.typed_array.offset; + pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer); + switch(p->class_id) { + default: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + *((uint8_t *)arr->buf + idx) = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + *((uint16_t *)arr->buf + idx) = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + *((uint32_t *)arr->buf + idx) = v; + break; + case JS_CLASS_FLOAT32_ARRAY: + *((float *)arr->buf + idx) = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + *((double *)arr->buf + idx) = d; + break; + } + return JS_UNDEFINED; + } else if (JS_IsNumericProperty(ctx, prop)) { + invalid_array_subscript: + return JS_ThrowTypeError(ctx, "invalid array subscript"); + } + } + + redo: + pr = find_own_property(ctx, p, prop); + if (pr) { + if (likely(pr->prop_type == JS_PROP_NORMAL)) { + if (unlikely(JS_IS_ROM_PTR(ctx, pr))) + goto convert_to_ram; + pr->value = val; + return JS_UNDEFINED; + } else if (pr->prop_type == JS_PROP_VARREF) { + JSVarRef *pv = JS_VALUE_TO_PTR(pr->value); + /* always detached */ + pv->u.value = val; + return JS_UNDEFINED; + } else if (pr->prop_type == JS_PROP_SPECIAL) { + JSGCRef val_ref, prop_ref, this_obj_ref; + int err; + convert_to_ram: + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, prop); + JS_PUSH_VALUE(ctx, val); + err = js_update_props(ctx, this_obj); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, prop); + JS_POP_VALUE(ctx, this_obj); + if (err) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(this_obj); + goto redo; + } else { + goto getset; + } + } + + /* search in the prototype chain (getter/setters) */ + for(;;) { + proto = p->proto; + if (proto == JS_NULL) + break; + p = JS_VALUE_TO_PTR(proto); + prototype_lookup: + pr = find_own_property(ctx, p, prop); + if (pr) { + if (unlikely(pr->prop_type == JS_PROP_GETSET)) { + JSValueArray *arr; + JSValue setter; + getset: + arr = JS_VALUE_TO_PTR(pr->value); + setter = arr->arr[1]; + if (allow_tail_call) { + /* It is assumed "this_obj" already is on the stack + and that the stack has some slack to add one + element. */ + ctx->sp[-2] = ctx->sp[0]; + ctx->sp[-1] = setter; + ctx->sp[0] = val; + ctx->sp -= 2; + return JS_NewTailCall(1 | FRAME_CF_POP_RET); + } else { + JSGCRef val_ref, setter_ref, this_obj_ref; + int err; + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, setter); + JS_PUSH_VALUE(ctx, this_obj); + err = JS_StackCheck(ctx, 3); + JS_POP_VALUE(ctx, this_obj); + JS_POP_VALUE(ctx, setter); + JS_POP_VALUE(ctx, val); + if (err) + return JS_EXCEPTION; + JS_PushArg(ctx, val); + JS_PushArg(ctx, setter); + JS_PushArg(ctx, this_obj); + return JS_Call(ctx, 1); + } + } else { + /* stop prototype chain lookup */ + break; + } + } + } + + /* add the property in the object */ + if (!is_obj) + return JS_ThrowTypeErrorNotAnObject(ctx); + return JS_DefinePropertyInternal(ctx, this_obj, prop, val, JS_UNDEFINED, + JS_DEF_PROP_HAS_VALUE); +} + +JSValue JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *str, JSValue val) +{ + JSValue prop; + JSGCRef this_obj_ref, val_ref; + + JS_PUSH_VALUE(ctx, this_obj); + JS_PUSH_VALUE(ctx, val); + prop = JS_NewString(ctx, str); + if (!JS_IsException(prop)) { + prop = JS_ToPropertyKey(ctx, prop); + } + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(prop)) + return prop; + return JS_SetPropertyInternal(ctx, this_obj, prop, val, FALSE); +} + +JSValue JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val) +{ + if (idx > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array index"); + return JS_SetPropertyInternal(ctx, this_obj, JS_NewShortInt(idx), val, FALSE); +} + +/* return JS_FALSE, JS_TRUE or JS_EXCEPTION. Return false only if the + property is not configurable which is never the case here. */ +static JSValue JS_DeleteProperty(JSContext *ctx, JSValue this_obj, + JSValue prop) +{ + JSObject *p; + JSProperty *pr, *pr1; + JSValueArray *arr; + int h, idx, hash_mask, last_idx, prop_count, first_free; + JSGCRef this_obj_ref; + + JS_PUSH_VALUE(ctx, this_obj); + prop = JS_ToPropertyKey(ctx, prop); + JS_POP_VALUE(ctx, this_obj); + if (JS_IsException(prop)) + return prop; + + /* XXX: check return value */ + if (!JS_IsPtr(this_obj)) + return JS_TRUE; + p = JS_VALUE_TO_PTR(this_obj); + if (p->mtag != JS_MTAG_OBJECT) + return JS_TRUE; + + arr = JS_VALUE_TO_PTR(p->props); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + h = hash_prop(prop) & hash_mask; + idx = JS_VALUE_GET_INT(arr->arr[2 + h]); + last_idx = -1; + while (idx != 0) { + pr = (JSProperty *)(arr->arr + idx); + if (pr->key == prop) { + if (JS_IS_ROM_PTR(ctx, arr)) { + JSGCRef this_obj_ref; + int ret; + JS_PUSH_VALUE(ctx, this_obj); + ret = js_update_props(ctx, this_obj); + JS_POP_VALUE(ctx, this_obj); + if (ret) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(this_obj); + arr = JS_VALUE_TO_PTR(p->props); + pr = (JSProperty *)(arr->arr + idx); + } + /* found: remove it */ + if (last_idx >= 0) { + JSProperty *lpr = (JSProperty *)(arr->arr + last_idx); + lpr->hash_next = pr->hash_next; + } else { + arr->arr[2 + h] = pr->hash_next; + } + first_free = get_first_free(arr); + + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + arr->arr[0] = JS_NewShortInt(prop_count - 1); + pr->prop_type = JS_PROP_NORMAL; + pr->key = JS_UNINITIALIZED; + pr->value = JS_UNDEFINED; + + /* update first_free if needed */ + while (first_free > 2 + hash_mask + 1) { + pr1 = (JSProperty *)&arr->arr[first_free - 3]; + if (pr1->key != JS_UNINITIALIZED) + break; + first_free -= 3; + } + + /* update first_free */ + if (first_free < arr->size) { + pr1 = (JSProperty *)&arr->arr[arr->size - 3]; + pr1->hash_next = first_free << 1; + } + + /* compact the properties if needed */ + if ((2 + hash_mask + 1 + 3 * prop_count) < arr->size / 2) + js_compact_props(ctx, p); + return JS_TRUE; + } + last_idx = idx; + idx = pr->hash_next >> 1; + } + /* not found */ + return JS_TRUE; +} + +static JSValue stdlib_init_class(JSContext *ctx, const JSROMClass *class_def) +{ + JSValue obj, proto, parent_class, parent_proto; + JSGCRef parent_class_ref; + JSObject *p; + int ctor_idx = class_def->ctor_idx; + + if (ctor_idx >= 0) { + int class_id = ctx->c_function_table[ctor_idx].magic; + obj = ctx->class_obj[class_id]; + if (!JS_IsNull(obj)) + return obj; /* already defined */ + + /* initialize the parent class if necessary */ + if (!JS_IsNull(class_def->parent_class)) { + JSROMClass *parent_class_def = JS_VALUE_TO_PTR(class_def->parent_class); + int parent_class_id; + parent_class = stdlib_init_class(ctx, parent_class_def); + parent_class_id = ctx->c_function_table[parent_class_def->ctor_idx].magic; + parent_proto = ctx->class_proto[parent_class_id]; + } else { + parent_class = JS_NULL; + parent_proto = ctx->class_proto[JS_CLASS_OBJECT]; + } + /* initialize the prototype before. It is already defined only + for Object and Function */ + proto = ctx->class_proto[class_id]; + if (JS_IsNull(proto)) { + JS_PUSH_VALUE(ctx, parent_class); + proto = JS_NewObjectProtoClass(ctx, parent_proto, JS_CLASS_OBJECT, 0); + JS_POP_VALUE(ctx, parent_class); + ctx->class_proto[class_id] = proto; + } + p = JS_VALUE_TO_PTR(proto); + if (!JS_IsNull(class_def->proto_props)) + p->props = class_def->proto_props; + + if (JS_IsNull(parent_class)) + parent_class = ctx->class_proto[JS_CLASS_CLOSURE]; + obj = js_new_c_function_proto(ctx, ctor_idx, parent_class, FALSE, JS_NULL); + ctx->class_obj[class_id] = obj; + } else { + /* normal object */ + obj = JS_NewObject(ctx); + } + p = JS_VALUE_TO_PTR(obj); + if (!JS_IsNull(class_def->props)) { + /* set the properties from the ROM. They are copied to RAM + when modified */ + p->props = class_def->props; + } + return obj; +} + +static void stdlib_init(JSContext *ctx, const JSValueArray *arr) +{ + JSValue name, val; + int i; + + for(i = 0; i < arr->size; i += 2) { + name = arr->arr[i]; + val = arr->arr[i + 1]; + if (JS_IsObject(ctx, val)) { + val = stdlib_init_class(ctx, JS_VALUE_TO_PTR(val)); + } else if (val == JS_NULL) { + val = ctx->global_obj; + } + JS_DefinePropertyInternal(ctx, ctx->global_obj, name, + val, JS_NULL, + JS_DEF_PROP_HAS_VALUE); + } +} + +static void dummy_write_func(void *opaque, const void *buf, size_t buf_len) +{ + // fwrite(buf, 1, buf_len, stdout); +} + +/* if prepare_compilation is true, the context will be used to compile + to a binary file. It is not expected to be used in the embedded + version */ +JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, BOOL prepare_compilation) +{ + JSContext *ctx; + JSValueArray *arr; + int i, mem_align; + +#ifdef JS_PTR64 + mem_align = 8; +#else + mem_align = 4; +#endif + mem_size = mem_size & ~(mem_align - 1); + assert(mem_size >= 1024); + assert(((uintptr_t)mem_start & (mem_align - 1)) == 0); + + ctx = mem_start; + memset(ctx, 0, sizeof(*ctx)); + ctx->class_count = stdlib_def->class_count; + ctx->class_obj = ctx->class_proto + ctx->class_count; + ctx->heap_base = (void *)(ctx->class_proto + 2 * ctx->class_count); + ctx->heap_free = ctx->heap_base; + ctx->stack_top = mem_start + mem_size; + ctx->sp = (JSValue *)ctx->stack_top; + ctx->stack_bottom = ctx->sp; + ctx->fp = ctx->sp; + ctx->min_free_size = JS_MIN_FREE_SIZE; +#ifdef DEBUG_GC + ctx->dummy_block = JS_NULL; + ctx->unique_strings = JS_NULL; +#endif + ctx->random_state = 1; + ctx->write_func = dummy_write_func; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) + ctx->string_pos_cache[i].str = JS_NULL; + + if (prepare_compilation) { + int atom_table_len; + JSValueArray *arr, *arr1; + uint8_t *ptr; + + /* for compilation, no stdlib is needed. Only the atoms + corresponding to JS_ATOM_x are needed and they are stored + in RAM. */ + /* copy the atoms to a fixed location at the beginning of the + heap */ + ctx->atom_table = (JSWord *)ctx->heap_free; + atom_table_len = stdlib_def->sorted_atoms_offset; + memcpy(ctx->heap_free, stdlib_def->stdlib_table, + atom_table_len * sizeof(JSWord)); + ctx->heap_free += atom_table_len * sizeof(JSWord); + + /* allocate the sorted atom table and populate it */ + arr1 = (JSValueArray *)(stdlib_def->stdlib_table + atom_table_len); + arr = js_alloc_value_array(ctx, 0, arr1->size); + ctx->unique_strings = JS_VALUE_FROM_PTR(arr); + for(i = 0; i < arr1->size; i++) { + ptr = JS_VALUE_TO_PTR(arr1->arr[i]); + ptr = ptr - (uint8_t *)stdlib_def->stdlib_table + + (uint8_t *)ctx->atom_table; + arr->arr[i] = JS_VALUE_FROM_PTR(ptr); + } + ctx->unique_strings_len = arr1->size; + } else { + ctx->atom_table = stdlib_def->stdlib_table; + ctx->rom_atom_tables[0] = (JSValueArray *)(stdlib_def->stdlib_table + + stdlib_def->sorted_atoms_offset); + ctx->n_rom_atom_tables = 1; + ctx->c_function_table = stdlib_def->c_function_table; + ctx->c_finalizer_table = stdlib_def->c_finalizer_table; + ctx->unique_strings = JS_NULL; + ctx->unique_strings_len = 0; + } + + + ctx->current_exception = JS_UNDEFINED; +#ifdef DEBUG_GC + /* set the dummy block at the start of the memory */ + { + JSByteArray *barr; + barr = js_alloc_byte_array(ctx, (min_int(mem_size / 2, 1 << 17)) & ~(JSW - 1)); + ctx->dummy_block = JS_VALUE_FROM_PTR(barr); + } +#endif + + arr = js_alloc_value_array(ctx, 0, 3); + arr->arr[0] = JS_NewShortInt(0); /* prop_count */ + arr->arr[1] = JS_NewShortInt(0); /* hash_mark */ + arr->arr[2] = JS_NewShortInt(0); /* hash_table[1] */ + ctx->empty_props = JS_VALUE_FROM_PTR(arr); + for(i = 0; i < ctx->class_count; i++) + ctx->class_proto[i] = JS_NULL; + for(i = 0; i < ctx->class_count; i++) + ctx->class_obj[i] = JS_NULL; + /* must be done first so that the prototype of Object.prototype is + JS_NULL */ + ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObject(ctx); + /* must be done for proper function init */ + ctx->class_proto[JS_CLASS_CLOSURE] = JS_NewObject(ctx); + + ctx->global_obj = JS_NewObject(ctx); + ctx->minus_zero = js_alloc_float64(ctx, -0.0); /* XXX: use a ROM value instead */ + + if (!prepare_compilation) { + stdlib_init(ctx, (JSValueArray *)(stdlib_def->stdlib_table + stdlib_def->global_object_offset)); + } + + return ctx; +} + +JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def) +{ + return JS_NewContext2(mem_start, mem_size, stdlib_def, FALSE); +} + +void JS_FreeContext(JSContext *ctx) +{ + uint8_t *ptr; + int size; + JSObject *p; + + /* call the user C finalizers */ + /* XXX: could disable it when prepare_compilation = true */ + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + p = (JSObject *)ptr; + if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER && + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) { + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque); + } + ptr += size; + } +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->opaque = opaque; +} + +void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler) +{ + ctx->interrupt_handler = interrupt_handler; +} + +void JS_SetLogFunc(JSContext *ctx, JSWriteFunc *write_func) +{ + ctx->write_func = write_func; +} + +void JS_SetRandomSeed(JSContext *ctx, uint64_t seed) +{ + ctx->random_state = seed; +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return ctx->global_obj; +} + +static JSValue get_var_ref(JSContext *ctx, JSValue *pfirst_var_ref, JSValue *pval) +{ + JSValue val; + JSVarRef *p; + + val = *pfirst_var_ref; + for(;;) { + if (val == JS_NULL) + break; + p = JS_VALUE_TO_PTR(val); + assert(!p->is_detached); + if (p->u.pvalue == pval) + return val; + val = p->u.next; + } + + p = js_malloc(ctx, sizeof(JSVarRef), JS_MTAG_VARREF); + if (!p) + return JS_EXCEPTION; + p->is_detached = FALSE; + p->u.pvalue = pval; + p->u.next = *pfirst_var_ref; + val = JS_VALUE_FROM_PTR(p); + *pfirst_var_ref = val; + return val; +} + +#define FRAME_OFFSET_ARG0 4 +#define FRAME_OFFSET_FUNC_OBJ 3 +#define FRAME_OFFSET_THIS_OBJ 2 +#define FRAME_OFFSET_CALL_FLAGS 1 +#define FRAME_OFFSET_SAVED_FP 0 +#define FRAME_OFFSET_CUR_PC (-1) /* current pc_offset */ +#define FRAME_OFFSET_FIRST_VARREF (-2) +#define FRAME_OFFSET_VAR0 (-3) + +/* stack layout: + + padded_args (padded_argc - argc) + args (argc) + func_obj fp[3] + this_obj fp[2] + call_flags (int) fp[1] + saved_fp (int) fp[0] + cur_pc (int) fp[-1] + first_var_ref (val) fp[-2] + vars (var_count) + temp stack (pointed by sp) +*/ + +#define SP_TO_VALUE(ctx, fp) JS_NewShortInt((uint8_t *)(fp) - (uint8_t *)ctx) +#define VALUE_TO_SP(ctx, val) (void *)((uint8_t *)ctx + JS_VALUE_GET_INT(val)) + +/* buf_end points to the end of the buffer (after the final '\0') */ +static __js_printf_like(3, 4) void cprintf(char **pp, char *buf_end, const char *fmt, ...) +{ + char *p; + va_list ap; + + p = *pp; + if ((p + 1) >= buf_end) + return; + va_start(ap, fmt); + js_vsnprintf(p, buf_end - p, fmt, ap); + va_end(ap); + p += strlen(p); + *pp = p; +} + +static JSValue reloc_c_func_name(JSContext *ctx, JSValue val) +{ + return val; +} + +/* no memory allocation is done */ +/* XXX: handle bound functions */ +static JSValue js_function_get_length_name1(JSContext *ctx, JSValue *this_val, + int is_name, JSFunctionBytecode **pb) +{ + int short_func_idx; + const JSCFunctionDef *fd; + JSValue ret; + + if (!JS_IsPtr(*this_val)) { + if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC) + goto fail; + short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(*this_val); + goto short_func; + } else { + JSObject *p = JS_VALUE_TO_PTR(*this_val); + JSFunctionBytecode *b; + if (p->mtag != JS_MTAG_OBJECT) + goto fail; + if (p->class_id == JS_CLASS_CLOSURE) { + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + if (is_name) { + /* XXX: directly set func_name to the empty string ? */ + if (b->func_name == JS_NULL) + ret = js_get_atom(ctx, JS_ATOM_empty); + else + ret = b->func_name; + } else { + ret = JS_NewShortInt(b->arg_count); + } + *pb = b; + return ret; + } else if (p->class_id == JS_CLASS_C_FUNCTION) { + short_func_idx = p->u.cfunc.idx; + short_func: + fd = &ctx->c_function_table[short_func_idx]; + if (is_name) { + ret = reloc_c_func_name(ctx, fd->name); + } else { + ret = JS_NewShortInt(fd->arg_count); + } + *pb = NULL; + return ret; + } else { + fail: + *pb = NULL; + return JS_NULL; + } + } +} + +static uint32_t get_bit(const uint8_t *buf, uint32_t index) +{ + return (buf[index >> 3] >> (7 - (index & 7))) & 1; +} + +static uint32_t get_bits_slow(const uint8_t *buf, uint32_t index, int n) +{ + int i; + uint32_t val; + val = 0; + for(i = 0; i < n; i++) + val |= get_bit(buf, index + i) << (n - 1 - i); + return val; +} + +static uint32_t get_bits(const uint8_t *buf, uint32_t buf_len, + uint32_t index, int n) +{ + uint32_t val, pos; + + pos = index >> 3; + if (unlikely(n > 25 || (pos + 3) >= buf_len)) { + /* slow case */ + return get_bits_slow(buf, index, n); + } else { + /* fast case */ + val = get_be32(buf + pos); + return (val >> (32 - (index & 7) - n)) & ((1 << n) - 1); + } +} + +static uint32_t get_ugolomb(const uint8_t *buf, uint32_t buf_len, + uint32_t *pindex) +{ + uint32_t index = *pindex; + int i; + uint32_t v; + + i = 0; + for(;;) { + if (get_bit(buf, index++)) + break; + i++; + if (i == 32) { + /* error */ + *pindex = index; + return 0xffffffff; + } + } + if (i == 0) { + v = 0; + } else { + v = ((1 << i) | get_bits(buf, buf_len, index, i)) - 1; + index += i; + } + *pindex = index; + // printf("get_ugolomb: v=%d\n", v); + return v; +} + +static int32_t get_sgolomb(const uint8_t *buf, uint32_t buf_len, + uint32_t *pindex) +{ + uint32_t val; + val = get_ugolomb(buf, buf_len, pindex); + return (val >> 1) ^ -(val & 1); +} + +static int get_pc2line_hoisted_code_len(const uint8_t *buf, size_t buf_len) +{ + size_t i = buf_len; + int v = 0; + while (i > 0) { + i--; + v = (v << 7) | (buf[i] & 0x7f); + if ((buf[i] & 0x80) == 0) + break; + } + return v; +} + +/* line_num, col_num and index are updated */ +static void get_pc2line(int *pline_num, int *pcol_num, const uint8_t *buf, + uint32_t buf_len, uint32_t *pindex, BOOL has_column) +{ + int line_delta, line_num, col_num, col_delta; + + line_num = *pline_num; + col_num = *pcol_num; + + line_delta = get_sgolomb(buf, buf_len, pindex); + line_num += line_delta; + if (has_column) { + if (line_delta == 0) { + col_delta = get_sgolomb(buf, buf_len, pindex); + col_num += col_delta; + } else { + col_num = get_ugolomb(buf, buf_len, pindex) + 1; + } + } else { + col_num = 0; + } + *pline_num = line_num; + *pcol_num = col_num; +} + +/* return 0 if line/col number info */ +static int find_line_col(int *pcol_num, JSFunctionBytecode *b, uint32_t pc) +{ + JSByteArray *arr, *pc2line; + int pos, op, line_num, col_num; + uint32_t pc2line_pos; + + if (b->pc2line == JS_NULL) + goto fail; + arr = JS_VALUE_TO_PTR(b->byte_code); + pc2line = JS_VALUE_TO_PTR(b->pc2line); + + /* skip the hoisted code */ + pos = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size); + if (pc < pos) + pc = pos; + pc2line_pos = 0; + line_num = 1; + col_num = 1; + while (pos < arr->size) { + get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size, + &pc2line_pos, b->has_column); + if (pos == pc) { + *pcol_num = col_num; + return line_num; + } + op = arr->buf[pos]; + pos += opcode_info[op].size; + } + fail: + *pcol_num = 0; + return 0; +} + +static const char *get_func_name(JSContext *ctx, JSValue func_obj, + JSCStringBuf *str_buf, JSFunctionBytecode **pb) +{ + JSValue val; + val = js_function_get_length_name1(ctx, &func_obj, 1, pb); + if (JS_IsNull(val)) + return NULL; + return JS_ToCString(ctx, val, str_buf); +} + +static void build_backtrace(JSContext *ctx, JSValue error_obj, + const char *filename, int line_num, int col_num, int skip_level) +{ + JSObject *p1; + char buf[128], *p, *buf_end, *line_start; + const char *str; + JSValue *fp, stack_str; + JSCStringBuf str_buf; + JSFunctionBytecode *b; + int level; + JSGCRef error_obj_ref; + + if (!JS_IsError(ctx, error_obj)) + return; + p = buf; + buf_end = buf + sizeof(buf); + p[0] = '\0'; + if (filename) { + cprintf(&p, buf_end, " at %s:%d:%d\n", filename, line_num, col_num); + } + fp = ctx->fp; + level = 0; + while (fp != (JSValue *)ctx->stack_top && level < 10) { + if (skip_level != 0) { + skip_level--; + } else { + line_start = p; + str = get_func_name(ctx, fp[FRAME_OFFSET_FUNC_OBJ], &str_buf, &b); + if (!str) + str = ""; + cprintf(&p, buf_end, " at %s", str); + if (b) { + int pc, line_num, col_num; + const char *filename; + filename = JS_ToCString(ctx, b->filename, &str_buf); + pc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]) - 1; + line_num = find_line_col(&col_num, b, pc); + cprintf(&p, buf_end, " (%s", filename); + if (line_num != 0) { + cprintf(&p, buf_end, ":%d", line_num); + if (col_num != 0) + cprintf(&p, buf_end, ":%d", col_num); + } + cprintf(&p, buf_end, ")"); + } else { + cprintf(&p, buf_end, " (native)"); + } + cprintf(&p, buf_end, "\n"); + /* if truncated line, remove it and stop */ + if ((p + 1) >= buf_end) { + *line_start = '\0'; + break; + } + level++; + } + fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]); + } + + JS_PUSH_VALUE(ctx, error_obj); + stack_str = JS_NewString(ctx, buf); + JS_POP_VALUE(ctx, error_obj); + p1 = JS_VALUE_TO_PTR(error_obj); + p1->u.error.stack = stack_str; +} + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE HINT_NUMBER + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint) +{ + int i, atom; + JSValue method, ret; + JSGCRef val_ref, method_ref; + + if (JS_IsPrimitive(ctx, val)) + return val; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + atom = JS_ATOM_toString; + } else { + atom = JS_ATOM_valueOf; + } + JS_PUSH_VALUE(ctx, val); + method = JS_GetProperty(ctx, val, js_get_atom(ctx, atom)); + JS_POP_VALUE(ctx, val); + if (JS_IsException(method)) + return method; + if (JS_IsFunction(ctx, method)) { + int err; + JS_PUSH_VALUE(ctx, method); + JS_PUSH_VALUE(ctx, val); + err = JS_StackCheck(ctx, 2); + JS_POP_VALUE(ctx, val); + JS_POP_VALUE(ctx, method); + if (err) + return JS_EXCEPTION; + + JS_PushArg(ctx, method); + JS_PushArg(ctx, val); + JS_PUSH_VALUE(ctx, val); + ret = JS_Call(ctx, 0); + JS_POP_VALUE(ctx, val); + if (JS_IsException(ret)) + return ret; + if (!JS_IsObject(ctx, ret)) + return ret; + } + } + return JS_ThrowTypeError(ctx, "toPrimitive"); +} + +/* return a string or an exception */ +static JSValue js_dtoa2(JSContext *ctx, double d, int radix, int n_digits, int flags) +{ + int len_max, len; + JSValue str; + JSGCRef str_ref; + JSByteArray *tmp_arr, *p; + + len_max = js_dtoa_max_len(d, radix, n_digits, flags); + p = js_alloc_byte_array(ctx, len_max + 1); + if (!p) + return JS_EXCEPTION; + /* allocate the temporary buffer */ + str = JS_VALUE_FROM_PTR(p); + JS_PUSH_VALUE(ctx, str); + tmp_arr = js_alloc_byte_array(ctx, sizeof(JSDTOATempMem)); + JS_POP_VALUE(ctx, str); + if (!tmp_arr) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(str); + /* Note: tmp_arr->buf is always 32 bit aligned */ + len = js_dtoa((char *)p->buf, d, radix, n_digits, flags, (JSDTOATempMem *)tmp_arr->buf); + js_free(ctx, tmp_arr); + return js_byte_array_to_string(ctx, str, len, TRUE); +} + +JSValue JS_ToString(JSContext *ctx, JSValue val) +{ + char buf[128]; + int atom; + const char *str; + + redo: + if (JS_IsInt(val)) { + int len; + len = i32toa(buf, JS_VALUE_GET_INT(val)); + buf[len] = '\0'; + goto ret_buf; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + return js_dtoa2(ctx, js_get_short_float(val), 10, 0, JS_DTOA_FORMAT_FREE); + } else +#endif + if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + int mtag = js_get_mtag(ptr); + switch(mtag) { + case JS_MTAG_OBJECT: + to_primitive: + val = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val)) + return val; + goto redo; + case JS_MTAG_STRING: + return val; + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = ptr; + return js_dtoa2(ctx, p->u.dval, 10, 0, JS_DTOA_FORMAT_FREE); + } + default: + js_snprintf(buf, sizeof(buf), "[mtag %d]", mtag); + goto ret_buf; + } + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + atom = JS_ATOM_null; + goto ret_atom; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + goto ret_atom; + case JS_TAG_BOOL: + if (JS_VALUE_GET_SPECIAL_VALUE(val)) + atom = JS_ATOM_true; + else + atom = JS_ATOM_false; + ret_atom: + return js_get_atom(ctx, atom); + case JS_TAG_STRING_CHAR: + return val; + case JS_TAG_SHORT_FUNC: + goto to_primitive; + default: + str = "?"; + goto ret_str; + ret_buf: + str = buf; + ret_str: + return JS_NewString(ctx, str); + } + } +} + +/* return either a unique string or an integer. Strings representing + a short integer are converted to short integer */ +static JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val) +{ + int32_t n; + if (JS_IsInt(val)) + return val; + val = JS_ToString(ctx, val); + if (JS_IsException(val)) + return val; + if (is_num_string(ctx, &n, val)) + return JS_NewShortInt(n); + else + return JS_MakeUniqueString(ctx, val); +} + +static int skip_spaces(const char *p1) +{ + const char *p = p1; + int c; + for(;;) { + c = *p; + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + p++; + } + return p - p1; +} + +/* JS_ToString() specific behaviors */ +#define JS_ATOD_TOSTRING (1 << 8) + +/* 'val' must be a string */ +static int js_atod1(JSContext *ctx, double *pres, JSValue val, + int radix, int flags) +{ + JSString *p; + JSByteArray *tmp_arr; + double d; + JSGCRef val_ref; + const char *p1; + + if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + int c = JS_VALUE_GET_SPECIAL_VALUE(val); + if (c >= '0' && c <= '9') { + *pres = c - '0'; + } else { + *pres = NAN; + } + return 0; + } + + JS_PUSH_VALUE(ctx, val); + tmp_arr = js_alloc_byte_array(ctx, sizeof(JSATODTempMem)); + JS_POP_VALUE(ctx, val); + if (!tmp_arr) { + *pres = NAN; + return -1; + } + p = JS_VALUE_TO_PTR(val); + p1 = (char *)p->buf; + p1 += skip_spaces(p1); + if ((p1 - (char *)p->buf) == p->len) { + if (flags & JS_ATOD_TOSTRING) + d = 0; + else + d = NAN; + goto done; + } + d = js_atod(p1, &p1, radix, flags, (JSATODTempMem *)tmp_arr->buf); + js_free(ctx, tmp_arr); + if (flags & JS_ATOD_TOSTRING) { + p1 += skip_spaces(p1); + if ((p1 - (char *)p->buf) < p->len) + d = NAN; + } + done: + *pres = d; + return 0; +} + +/* Note: can fail due to memory allocation even if primitive type */ +int JS_ToNumber(JSContext *ctx, double *pres, JSValue val) +{ + redo: + if (JS_IsInt(val)) { + *pres = (double)JS_VALUE_GET_INT(val); + return 0; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + *pres = js_get_short_float(val); + return 0; + } else +#endif + if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + switch(js_get_mtag(ptr)) { + case JS_MTAG_STRING: + goto atod; + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = ptr; + *pres = p->u.dval; + return 0; + } + case JS_MTAG_OBJECT: + val = JS_ToPrimitive(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) { + *pres = NAN; + return -1; + } + goto redo; + default: + *pres = NAN; + return 0; + } + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + case JS_TAG_BOOL: + *pres = (double)JS_VALUE_GET_SPECIAL_VALUE(val); + return 0; + case JS_TAG_UNDEFINED: + *pres = NAN; + return 0; + case JS_TAG_STRING_CHAR: + atod: + return js_atod1(ctx, pres, val, 0, + JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_TOSTRING); + default: + *pres = NAN; + return 0; + } + } +} + +static int JS_ToInt32Internal(JSContext *ctx, int *pres, JSValue val, BOOL sat_flag) +{ + int32_t ret; + double d; + + if (JS_IsInt(val)) { + ret = JS_VALUE_GET_INT(val); + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + d = js_get_short_float(val); + goto handle_float64; + } else +#endif + if (JS_IsPtr(val)) { + uint64_t u; + int e; + + handle_number: + if (JS_ToNumber(ctx, &d, val)) { + *pres = 0; + return -1; + } +#ifdef JS_USE_SHORT_FLOAT + handle_float64: +#endif + u = float64_as_uint64(d); + e = (u >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (!sat_flag) { + if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u >> 63) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } else { + if (e == 2047 && (u & (((uint64_t)1 << 52) - 1)) != 0) { + /* nan */ + ret = 0; + } else { + /* take the sign into account */ + if (u >> 63) + ret = 0x80000000; + else + ret = 0x7fffffff; + } + } + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_SPECIAL_VALUE(val); + break; + default: + goto handle_number; + } + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int *pres, JSValue val) +{ + return JS_ToInt32Internal(ctx, pres, val, FALSE); +} + +int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Internal(ctx, (int *)pres, val, FALSE); +} + +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val) +{ + return JS_ToInt32Internal(ctx, pres, val, TRUE); +} + +static int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32Sat(ctx, pres, val); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) + *pres = min; + } else { + if (*pres > max) + *pres = max; + } + } + return res; +} + +static int JS_ToUint8Clamp(JSContext *ctx, int *pres, JSValue val) +{ + int32_t ret; + double d; + + if (JS_IsInt(val)) { + ret = JS_VALUE_GET_INT(val); + if (ret < 0) + ret = 0; + else if (ret > 255) + ret = 255; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + d = js_get_short_float(val); + goto handle_float64; + } else +#endif + if (JS_IsPtr(val)) { + handle_number: + if (JS_ToNumber(ctx, &d, val)) { + *pres = 0; + return -1; + } +#ifdef JS_USE_SHORT_FLOAT + handle_float64: +#endif + if (d < 0 || isnan(d)) + ret = 0; + else if (d > 255) + ret = 255; + else + ret = js_lrint(d); + } else { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_SPECIAL_VALUE(val); + break; + default: + goto handle_number; + } + } + *pres = ret; + return 0; +} + +static int js_get_length32(JSContext *ctx, uint32_t *pres, JSValue obj) +{ + JSValue len_val; + len_val = JS_GetProperty(ctx, obj, js_get_atom(ctx, JS_ATOM_length)); + if (JS_IsException(len_val)) { + *pres = 0; + return -1; + } + return JS_ToUint32(ctx, pres, len_val); +} + +static no_inline JSValue js_add_slow(JSContext *ctx) +{ + JSValue *op1, *op2; + + op1 = &ctx->sp[1]; + op2 = &ctx->sp[0]; + *op1 = JS_ToPrimitive(ctx, *op1, HINT_NONE); + if (JS_IsException(*op1)) + return JS_EXCEPTION; + *op2 = JS_ToPrimitive(ctx, *op2, HINT_NONE); + if (JS_IsException(*op2)) + return JS_EXCEPTION; + if (JS_IsString(ctx, *op1) || JS_IsString(ctx, *op2)) { + *op1 = JS_ToString(ctx, *op1); + if (JS_IsException(*op1)) + return JS_EXCEPTION; + *op2 = JS_ToString(ctx, *op2); + if (JS_IsException(*op2)) + return JS_EXCEPTION; + return JS_ConcatString(ctx, *op1, *op2); + } else { + double d1, d2, r; + /* cannot fail */ + if (JS_ToNumber(ctx, &d1, *op1)) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &d2, *op2)) + return JS_EXCEPTION; + r = d1 + d2; + return JS_NewFloat64(ctx, r); + } +} + +static no_inline JSValue js_binary_arith_slow(JSContext *ctx, OPCodeEnum op) +{ + double d1, d2, r; + + if (JS_ToNumber(ctx, &d1, ctx->sp[1])) + return JS_EXCEPTION; + + if (JS_ToNumber(ctx, &d2, ctx->sp[0])) + return JS_EXCEPTION; + + switch(op) { + case OP_sub: + r = d1 - d2; + break; + case OP_mul: + r = d1 * d2; + break; + case OP_div: + r = d1 / d2; + break; + case OP_mod: + r = js_fmod(d1, d2); + break; + case OP_pow: + r = js_pow(d1, d2); + break; + default: + abort(); + } + return JS_NewFloat64(ctx, r); +} + +static no_inline JSValue js_unary_arith_slow(JSContext *ctx, OPCodeEnum op) +{ + double d; + + if (JS_ToNumber(ctx, &d, ctx->sp[0])) + return JS_EXCEPTION; + + switch(op) { + case OP_inc: + d++; + break; + case OP_dec: + d--; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + return JS_NewFloat64(ctx, d); +} + +/* specific case necessary for correct return value semantics */ +static no_inline JSValue js_post_inc_slow(JSContext *ctx, OPCodeEnum op) +{ + JSValue val; + double d, r; + + if (JS_ToNumber(ctx, &d, ctx->sp[0])) + return JS_EXCEPTION; + r = d + 2 * (op - OP_post_dec) - 1; + val = JS_NewFloat64(ctx, d); + if (JS_IsException(val)) + return val; + ctx->sp[0] = val; + return JS_NewFloat64(ctx, r); +} + +static no_inline JSValue js_binary_logic_slow(JSContext *ctx, OPCodeEnum op) +{ + uint32_t v1, v2, r; + + if (JS_ToUint32(ctx, &v1, ctx->sp[1])) + return JS_EXCEPTION; + if (JS_ToUint32(ctx, &v2, ctx->sp[0])) + return JS_EXCEPTION; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_shr: + r = v1 >> (v2 & 0x1f); + return JS_NewUint32(ctx, r); + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + return JS_NewInt32(ctx, r); +} + +static no_inline JSValue js_not_slow(JSContext *ctx) +{ + uint32_t r; + + if (JS_ToUint32(ctx, &r, ctx->sp[0])) + return JS_EXCEPTION; + return JS_NewInt32(ctx, ~r); +} + +static no_inline JSValue js_relational_slow(JSContext *ctx, OPCodeEnum op) +{ + JSValue *op1, *op2; + int res; + double d1, d2; + + op1 = &ctx->sp[1]; + op2 = &ctx->sp[0]; + *op1 = JS_ToPrimitive(ctx, *op1, HINT_NUMBER); + if (JS_IsException(*op1)) + return JS_EXCEPTION; + *op2 = JS_ToPrimitive(ctx, *op2, HINT_NUMBER); + if (JS_IsException(*op2)) + return JS_EXCEPTION; + if (JS_IsString(ctx, *op1) && JS_IsString(ctx, *op2)) { + res = js_string_compare(ctx, *op1, *op2); + switch(op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + case OP_gte: + res = (res >= 0); + break; + } + } else { + if (JS_ToNumber(ctx, &d1, *op1)) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &d2, *op2)) + return JS_EXCEPTION; + switch(op) { + case OP_lt: + res = (d1 < d2); /* if NaN return false */ + break; + case OP_lte: + res = (d1 <= d2); /* if NaN return false */ + break; + case OP_gt: + res = (d1 > d2); /* if NaN return false */ + break; + default: + case OP_gte: + res = (d1 >= d2); /* if NaN return false */ + break; + } + } + return JS_NewBool(res); +} + +static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) +{ + BOOL res; + + if (JS_IsNumber(ctx, op1)) { + if (!JS_IsNumber(ctx, op2)) { + res = FALSE; + } else { + double d1, d2; + /* cannot fail */ + JS_ToNumber(ctx, &d1, op1); + JS_ToNumber(ctx, &d2, op2); + res = (d1 == d2); /* if NaN return false */ + } + } else if (JS_IsString(ctx, op1)) { + if (!JS_IsString(ctx, op2)) { + res = FALSE; + } else { + res = js_string_eq(ctx, op1, op2); + } + } else { + /* special value or object */ + res = (op1 == op2); + } + return res; +} + +static JSValue js_strict_eq_slow(JSContext *ctx, BOOL is_neq) +{ + BOOL res; + res = js_strict_eq(ctx, ctx->sp[1], ctx->sp[0]); + return JS_NewBool(res ^ is_neq); +} + +enum { + /* special tags to simplify the comparison */ + JS_ETAG_NUMBER = JS_TAG_SPECIAL | (8 << 2), + JS_ETAG_STRING = JS_TAG_SPECIAL | (9 << 2), + JS_ETAG_OBJECT = JS_TAG_SPECIAL | (10 << 2), +}; + +static int js_eq_get_type(JSContext *ctx, JSValue val) +{ + if (JS_IsIntOrShortFloat(val)) { + return JS_ETAG_NUMBER; + } else if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_TO_PTR(val); + switch(js_get_mtag(ptr)) { + case JS_MTAG_FLOAT64: + return JS_ETAG_NUMBER; + case JS_MTAG_STRING: + return JS_ETAG_STRING; + default: + case JS_MTAG_OBJECT: + return JS_ETAG_OBJECT; + } + } else { + int tag = JS_VALUE_GET_SPECIAL_TAG(val); + switch(tag) { + case JS_TAG_STRING_CHAR: + return JS_ETAG_STRING; + case JS_TAG_SHORT_FUNC: + return JS_ETAG_OBJECT; + default: + return tag; + } + } +} + +static no_inline JSValue js_eq_slow(JSContext *ctx, BOOL is_neq) +{ + JSValue op1, op2; + int tag1, tag2; + BOOL res; + + redo: + op1 = ctx->sp[1]; + op2 = ctx->sp[0]; + tag1 = js_eq_get_type(ctx, op1); + tag2 = js_eq_get_type(ctx, op2); + if (tag1 == tag2) { + res = js_strict_eq(ctx, op1, op2); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = TRUE; + } else if ((tag1 == JS_ETAG_STRING && tag2 == JS_ETAG_NUMBER) || + (tag2 == JS_ETAG_STRING && tag1 == JS_ETAG_NUMBER)) { + double d1; + double d2; + if (JS_ToNumber(ctx, &d1, ctx->sp[1])) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &d2, ctx->sp[0])) + return JS_EXCEPTION; + res = (d1 == d2); + } else if (tag1 == JS_TAG_BOOL) { + ctx->sp[1] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + ctx->sp[0] = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(op2)); + goto redo; + } else if (tag1 == JS_ETAG_OBJECT && + (tag2 == JS_ETAG_NUMBER || tag2 == JS_ETAG_STRING)) { + ctx->sp[1] = JS_ToPrimitive(ctx, op1, HINT_NONE); + if (JS_IsException(ctx->sp[1])) + return JS_EXCEPTION; + goto redo; + } else if (tag2 == JS_ETAG_OBJECT && + (tag1 == JS_ETAG_NUMBER || tag1 == JS_ETAG_STRING)) { + ctx->sp[0] = JS_ToPrimitive(ctx, op2, HINT_NONE); + if (JS_IsException(ctx->sp[0])) + return JS_EXCEPTION; + goto redo; + } else { + res = FALSE; + } + return JS_NewBool(res ^ is_neq); +} + +static JSValue js_operator_in(JSContext *ctx) +{ + JSValue prop; + int res; + + if (js_eq_get_type(ctx, ctx->sp[0]) != JS_ETAG_OBJECT) + return JS_ThrowTypeError(ctx, "invalid 'in' operand"); + prop = JS_ToPropertyKey(ctx, ctx->sp[1]); + if (JS_IsException(prop)) + return prop; + res = JS_HasProperty(ctx, ctx->sp[0], prop); + return JS_NewBool(res); +} + +static JSValue js_operator_instanceof(JSContext *ctx) +{ + JSValue op1, op2, proto; + JSObject *p; + + op1 = ctx->sp[1]; + op2 = ctx->sp[0]; + if (!JS_IsFunctionObject(ctx, op2)) + return JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + proto = JS_GetProperty(ctx, op2, js_get_atom(ctx, JS_ATOM_prototype)); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(ctx, op1)) + return JS_NewBool(FALSE); + p = JS_VALUE_TO_PTR(op1); + for(;;) { + if (p->proto == JS_NULL) + return JS_NewBool(FALSE); + if (p->proto == proto) + return JS_NewBool(TRUE); + p = JS_VALUE_TO_PTR(p->proto); + } + return JS_NewBool(FALSE); +} + +static JSValue js_operator_typeof(JSContext *ctx, JSValue val) +{ + int tag, atom; + tag = js_eq_get_type(ctx, val); + switch(tag) { + case JS_ETAG_NUMBER: + atom = JS_ATOM_number; + break; + case JS_ETAG_STRING: + atom = JS_ATOM_string; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_ETAG_OBJECT: + if (JS_IsFunction(ctx, val)) + atom = JS_ATOM_function; + else + atom = JS_ATOM_object; + break; + case JS_TAG_NULL: + atom = JS_ATOM_object; + break; + default: + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + } + return js_get_atom(ctx, atom); +} + +static void js_reverse_val(JSValue *tab, int n) +{ + int i; + JSValue tmp; + + for(i = 0; i < n / 2; i++) { + tmp = tab[i]; + tab[i] = tab[n - 1 - i]; + tab[n - 1 - i] = tmp; + } +} + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, JSValue *fp) +{ + JSFunctionBytecode *b; + JSObject *p; + JSGCRef bfunc_ref, closure_ref; + JSValueArray *ext_vars; + JSValue closure; + int ext_vars_len; + + b = JS_VALUE_TO_PTR(bfunc); + if (b->ext_vars != JS_NULL) { + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + ext_vars_len = ext_vars->size / 2; + } else { + ext_vars_len = 0; + } + + JS_PUSH_VALUE(ctx, bfunc); + closure = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_CLOSURE], JS_CLASS_CLOSURE, + sizeof(JSClosureData) + ext_vars_len * sizeof(JSValue)); + JS_POP_VALUE(ctx, bfunc); + if (JS_IsException(closure)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(closure); + p->u.closure.func_bytecode = bfunc; + + if (ext_vars_len > 0) { + JSValue *pfirst_var_ref, val; + int i, var_idx, var_kind, decl; + + /* initialize the var_refs in case of exception */ + memset(p->u.closure.var_refs, 0, sizeof(JSValue) * ext_vars_len); + if (fp) { + pfirst_var_ref = &fp[FRAME_OFFSET_FIRST_VARREF]; + } else { + pfirst_var_ref = NULL; /* not used */ + } + for(i = 0; i < ext_vars_len; i++) { + b = JS_VALUE_TO_PTR(bfunc); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]); + var_kind = decl >> 16; + var_idx = decl & 0xffff; + JS_PUSH_VALUE(ctx, bfunc); + JS_PUSH_VALUE(ctx, closure); + switch(var_kind) { + case JS_VARREF_KIND_ARG: + val = get_var_ref(ctx, pfirst_var_ref, + &fp[FRAME_OFFSET_ARG0 + var_idx]); + break; + case JS_VARREF_KIND_VAR: + val = get_var_ref(ctx, pfirst_var_ref, + &fp[FRAME_OFFSET_VAR0 - var_idx]); + break; + case JS_VARREF_KIND_VAR_REF: + { + JSObject *p; + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + val = p->u.closure.var_refs[var_idx]; + } + break; + case JS_VARREF_KIND_GLOBAL: + /* only for eval code */ + val = add_global_var(ctx, ext_vars->arr[2 * i], (var_idx != 0)); + break; + default: + abort(); + } + JS_POP_VALUE(ctx, closure); + JS_POP_VALUE(ctx, bfunc); + if (JS_IsException(val)) + return val; + p = JS_VALUE_TO_PTR(closure); + p->u.closure.var_refs[i] = val; + } + } + return closure; +} + +static JSValue js_for_of_start(JSContext *ctx, BOOL is_for_in) +{ + JSValueArray *arr; + + if (is_for_in) { + /* XXX: not spec compliant and slow. We return only the own + object keys. */ + ctx->sp[0] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]); + if (JS_IsException(ctx->sp[0])) + return JS_EXCEPTION; + } + + if (!js_get_object_class(ctx, ctx->sp[0], JS_CLASS_ARRAY)) + return JS_ThrowTypeError(ctx, "unsupported type in for...of"); + + arr = js_alloc_value_array(ctx, 0, 2); + if (!arr) + return JS_EXCEPTION; + arr->arr[0] = ctx->sp[0]; + arr->arr[1] = JS_NewShortInt(0); + return JS_VALUE_FROM_PTR(arr); +} + +static JSValue js_for_of_next(JSContext *ctx) +{ + JSValueArray *arr, *arr1; + JSObject *p; + int pos; + + arr = JS_VALUE_TO_PTR(ctx->sp[0]); + pos = JS_VALUE_GET_INT(arr->arr[1]); + p = JS_VALUE_TO_PTR(arr->arr[0]); + if (pos >= p->u.array.len) { + ctx->sp[-2] = JS_TRUE; + ctx->sp[-1] = JS_UNDEFINED; + } else { + ctx->sp[-2] = JS_FALSE; + arr1 = JS_VALUE_TO_PTR(p->u.array.tab); + ctx->sp[-1] = arr1->arr[pos]; + arr->arr[1] = JS_NewShortInt(pos + 1); + } + return JS_UNDEFINED; +} + +static JSValue js_new_c_function_proto(JSContext *ctx, int func_idx, JSValue proto, BOOL has_params, + JSValue params) +{ + JSObject *p; + JSGCRef params_ref; + + JS_PUSH_VALUE(ctx, params); + p = JS_NewObjectProtoClass1(ctx, proto, JS_CLASS_C_FUNCTION, + sizeof(JSCFunctionData) - (!has_params ? sizeof(JSValue) : 0)); + JS_POP_VALUE(ctx, params); + if (!p) + return JS_EXCEPTION; + p->u.cfunc.idx = func_idx; + if (has_params) + p->u.cfunc.params = params; + return JS_VALUE_FROM_PTR(p); +} + +JSValue JS_NewCFunctionParams(JSContext *ctx, int func_idx, JSValue params) +{ + return js_new_c_function_proto(ctx, func_idx, ctx->class_proto[JS_CLASS_CLOSURE], TRUE, params); +} + +static JSValue js_call_constructor_start(JSContext *ctx, JSValue func) +{ + JSValue proto; + proto = JS_GetProperty(ctx, func, js_get_atom(ctx, JS_ATOM_prototype)); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(ctx, proto)) + proto = ctx->class_proto[JS_CLASS_OBJECT]; + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0); +} + +#define SAVE() do { \ + fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf); \ + ctx->sp = sp; \ + ctx->fp = fp; \ + } while (0) + +/* only need to restore PC */ +#define RESTORE() do { \ + b = JS_VALUE_TO_PTR(((JSObject *)JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]))->u.closure.func_bytecode); \ + pc = ((JSByteArray *)JS_VALUE_TO_PTR(b->byte_code))->buf + JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]); \ + } while (0) + +static JSValue __js_poll_interrupt(JSContext *ctx) +{ + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (ctx->interrupt_handler && ctx->interrupt_handler(ctx, ctx->opaque)) { + JS_ThrowInternalError(ctx, "interrupted"); + ctx->current_exception_is_uncatchable = TRUE; + return JS_EXCEPTION; + } + return JS_UNDEFINED; +} + +/* handle user interruption */ +#define POLL_INTERRUPT() do { \ + if (unlikely(--ctx->interrupt_counter <= 0)) { \ + SAVE(); \ + val = __js_poll_interrupt(ctx); \ + RESTORE(); \ + if (JS_IsException(val)) \ + goto exception; \ + } \ + } while(0) + +/* must use JS_StackCheck() before using it */ +void JS_PushArg(JSContext *ctx, JSValue val) +{ +#ifdef DEBUG_GC + assert((ctx->sp - 1) >= ctx->stack_bottom); +#endif + *--ctx->sp = val; +} + +/* Usage: + if (JS_StackCheck(ctx, n + 2)) ... + JS_PushArg(ctx, arg[n - 1]); + ... + JS_PushArg(ctx, arg[0]); + JS_PushArg(ctx, func); + JS_PushArg(ctx, this_obj); + res = JS_Call(ctx, n); +*/ +JSValue JS_Call(JSContext *ctx, int call_flags) +{ + JSValue *fp, *sp, val = JS_UNDEFINED, *initial_fp; + uint8_t *pc; + /* temporary variables */ + int opcode = OP_invalid, i; + JSFunctionBytecode *b; +#ifdef JS_USE_SHORT_FLOAT + double dr; +#endif + + if (ctx->js_call_rec_count >= JS_MAX_CALL_RECURSE) + return JS_ThrowInternalError(ctx, "C stack overflow"); + ctx->js_call_rec_count++; + + sp = ctx->sp; + fp = ctx->fp; + initial_fp = fp; + b = NULL; + pc = NULL; + goto function_call; + +#define CASE(op) case op +#define DEFAULT default +#define BREAK break + + for(;;) { + opcode = *pc++; +#ifdef DUMP_EXEC + { + JSByteArray *arr; + arr = JS_VALUE_TO_PTR(b->byte_code); + js_printf(ctx, " sp=%d\n", (int)(sp - fp)); + js_printf(ctx, "%4d: %s\n", (int)(pc - arr->buf - 1), + opcode_info[opcode].name); + } +#endif + switch(opcode) { + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + *--sp = JS_NewShortInt(opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *--sp = JS_NewShortInt(get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *--sp = JS_NewShortInt(get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_value): + *--sp = get_u32(pc); + pc += 4; + BREAK; + CASE(OP_push_const): + { + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + *--sp = cpool->arr[get_u16(pc)]; + pc += 2; + } + BREAK; + CASE(OP_undefined): + *--sp = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *--sp = JS_NULL; + BREAK; + CASE(OP_push_this): + *--sp = fp[FRAME_OFFSET_THIS_OBJ]; + BREAK; + CASE(OP_push_false): + *--sp = JS_FALSE; + BREAK; + CASE(OP_push_true): + *--sp = JS_TRUE; + BREAK; + CASE(OP_object): + { + int n = get_u16(pc); + SAVE(); + val = JS_NewObjectPrealloc(ctx, n); + RESTORE(); + if (JS_IsException(val)) + goto exception; + *--sp = val; + pc += 2; + } + BREAK; + CASE(OP_regexp): + { + JSObject *p; + SAVE(); + val = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp)); + RESTORE(); + if (JS_IsException(val)) + goto exception; + p = JS_VALUE_TO_PTR(val); + p->u.regexp.source = sp[1]; + p->u.regexp.byte_code = sp[0]; + p->u.regexp.last_index = 0; + sp[1] = val; + sp++; + } + BREAK; + CASE(OP_array_from): + { + JSObject *p; + JSValueArray *arr; + int i, argc; + + argc = get_u16(pc); + SAVE(); + val = JS_NewArray(ctx, argc); + RESTORE(); + if (JS_IsException(val)) + goto exception; + pc += 2; + p = JS_VALUE_TO_PTR(val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < argc; i++) { + arr->arr[i] = sp[argc - 1 - i]; + } + sp += argc; + *--sp = val; + } + BREAK; + CASE(OP_this_func): + *--sp = fp[FRAME_OFFSET_FUNC_OBJ]; + BREAK; + CASE(OP_arguments): + { + JSObject *p; + JSValueArray *arr; + int i, argc; + + argc = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]) & FRAME_CF_ARGC_MASK; + SAVE(); + val = JS_NewArray(ctx, argc); + RESTORE(); + if (JS_IsException(val)) + goto exception; + p = JS_VALUE_TO_PTR(val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < argc; i++) { + arr->arr[i] = fp[FRAME_OFFSET_ARG0 + i]; + } + *--sp = val; + } + BREAK; + CASE(OP_new_target): + call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]); + if (call_flags & FRAME_CF_CTOR) { + *--sp = fp[FRAME_OFFSET_FUNC_OBJ]; + } else { + *--sp = JS_UNDEFINED; + } + BREAK; + CASE(OP_drop): + sp++; + BREAK; + CASE(OP_nip): + sp[1] = sp[0]; + sp++; + BREAK; + CASE(OP_dup): + sp--; + sp[0] = sp[1]; + BREAK; + CASE(OP_dup2): + sp -= 2; + sp[0] = sp[2]; + sp[1] = sp[3]; + BREAK; + CASE(OP_insert2): + sp[-1] = sp[0]; + sp[0] = sp[1]; + sp[1] = sp[-1]; + sp--; + BREAK; + CASE(OP_insert3): + sp[-1] = sp[0]; + sp[0] = sp[1]; + sp[1] = sp[2]; + sp[2] = sp[-1]; + sp--; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[1]; + sp[1] = sp[2]; + sp[2] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[2]; + sp[2] = sp[1]; + sp[1] = sp[0]; + sp[0] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[1]; + sp[1] = sp[2]; + sp[2] = sp[3]; + sp[3] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[1]; + sp[1] = sp[0]; + sp[0] = tmp; + } + BREAK; + + CASE(OP_fclosure): + { + int idx; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + idx = get_u16(pc); + SAVE(); + val = js_closure(ctx, cpool->arr[idx], fp); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + pc += 2; + *--sp = val; + } + BREAK; + + CASE(OP_call_constructor): + call_flags = get_u16(pc) | FRAME_CF_CTOR; + goto global_function_call; + CASE(OP_call): + call_flags = get_u16(pc); + global_function_call: + js_reverse_val(sp, (call_flags & FRAME_CF_ARGC_MASK) + 1); + *--sp = JS_UNDEFINED; + goto generic_function_call; + CASE(OP_call_method): + { + int n, argc, short_func_idx; + JSValue func_obj; + JSObject *p; + JSByteArray *byte_code; + + call_flags = get_u16(pc); + + n = (call_flags & FRAME_CF_ARGC_MASK) + 2; + js_reverse_val(sp, n); + + generic_function_call: + POLL_INTERRUPT(); + byte_code = JS_VALUE_TO_PTR(b->byte_code); + /* save pc + 1 of the current call */ + fp[FRAME_OFFSET_CUR_PC] = JS_NewShortInt(pc - byte_code->buf); + function_call: + *--sp = JS_NewShortInt(call_flags); + *--sp = SP_TO_VALUE(ctx, fp); + + func_obj = sp[FRAME_OFFSET_FUNC_OBJ]; +#if defined(DUMP_EXEC) + JS_DumpValue(ctx, "calling", func_obj); +#endif + if (!JS_IsPtr(func_obj)) { + if (JS_VALUE_GET_SPECIAL_TAG(func_obj) != JS_TAG_SHORT_FUNC) + goto not_a_function; + short_func_idx = JS_VALUE_GET_SPECIAL_VALUE(func_obj); + p = NULL; + goto c_function; + } else { + p = JS_VALUE_TO_PTR(func_obj); + if (p->mtag != JS_MTAG_OBJECT) + goto not_a_function; + if (p->class_id == JS_CLASS_C_FUNCTION) { + const JSCFunctionDef *fd; + int pushed_argc; + short_func_idx = p->u.cfunc.idx; + c_function: + fd = &ctx->c_function_table[short_func_idx]; + /* add undefined arguments if the caller did not + provide enough arguments */ + call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]); + if ((call_flags & FRAME_CF_CTOR) && + (fd->def_type != JS_CFUNC_constructor && + fd->def_type != JS_CFUNC_constructor_magic)) { + sp += 2; /* go back to the caller frame */ + ctx->sp = sp; + ctx->fp = fp; + val = JS_ThrowTypeError(ctx, "not a constructor"); + goto call_exception; + } + + argc = call_flags & FRAME_CF_ARGC_MASK; + /* JS_StackCheck may trigger a gc */ + ctx->sp = sp; + ctx->fp = fp; + n = JS_StackCheck(ctx, max_int(fd->arg_count - argc, 0)); + if (n) { + sp += 2; /* go back to the caller frame */ + val = JS_EXCEPTION; + goto call_exception; + } + pushed_argc = argc; + if (fd->arg_count > argc) { + n = fd->arg_count - argc; + sp -= n; + for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++) + sp[i] = sp[i + n]; + for(i = 0; i < n; i++) + sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED; + pushed_argc = fd->arg_count; + } + fp = sp; + ctx->sp = sp; + ctx->fp = fp; + switch(fd->def_type) { + case JS_CFUNC_generic: + case JS_CFUNC_constructor: + val = fd->func.generic(ctx, &fp[FRAME_OFFSET_THIS_OBJ], + call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK), + fp + FRAME_OFFSET_ARG0); + break; + case JS_CFUNC_generic_magic: + case JS_CFUNC_constructor_magic: + val = fd->func.generic_magic(ctx, &fp[FRAME_OFFSET_THIS_OBJ], + call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK), + fp + FRAME_OFFSET_ARG0, fd->magic); + break; + case JS_CFUNC_generic_params: + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + val = fd->func.generic_params(ctx, &fp[FRAME_OFFSET_THIS_OBJ], + call_flags & (FRAME_CF_CTOR | FRAME_CF_ARGC_MASK), + fp + FRAME_OFFSET_ARG0, p->u.cfunc.params); + break; + case JS_CFUNC_f_f: + { + double d; + if (JS_ToNumber(ctx, &d, fp[FRAME_OFFSET_ARG0])) { + val = JS_EXCEPTION; + } else { + d = fd->func.f_f(d); + } + val = JS_NewFloat64(ctx, d); + } + break; + default: + assert(0); + } + if (JS_IsExceptionOrTailCall(val) && + JS_VALUE_GET_SPECIAL_VALUE(val) >= JS_EX_CALL) { + JSValue *fp1, *sp1; + /* tail call: equivalent to calling the + function after the C function */ + /* XXX: handle the call flags of the caller ? */ + call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL; + sp = ctx->sp; + /* pop the frame */ + fp1 = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]); + /* move the new arguments at the correct stack position */ + argc = (call_flags & FRAME_CF_ARGC_MASK) + 2; + sp1 = fp + FRAME_OFFSET_ARG0 + pushed_argc - argc; + memmove(sp1, sp, sizeof(*sp) * (argc)); + sp = sp1; + fp = fp1; + goto function_call; + } else { + sp = fp + FRAME_OFFSET_ARG0 + pushed_argc; + goto return_call; + } + } else if (p->class_id == JS_CLASS_CLOSURE) { + int n_vars; + call_flags = JS_VALUE_GET_INT(sp[FRAME_OFFSET_CALL_FLAGS]); + if (call_flags & FRAME_CF_CTOR) { + ctx->sp = sp; + ctx->fp = fp; + /* Note: can recurse at this point */ + val = js_call_constructor_start(ctx, func_obj); + if (JS_IsException(val)) + goto call_exception; + sp[FRAME_OFFSET_THIS_OBJ] = val; + func_obj = sp[FRAME_OFFSET_FUNC_OBJ]; + p = JS_VALUE_TO_PTR(func_obj); + } + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + if (b->vars != JS_NULL) { + JSValueArray *vars = JS_VALUE_TO_PTR(b->vars); + n_vars = vars->size - b->arg_count; + } else { + n_vars = 0; + } + argc = call_flags & FRAME_CF_ARGC_MASK; + /* JS_StackCheck may trigger a gc */ + ctx->sp = sp; + ctx->fp = fp; + n = JS_StackCheck(ctx, max_int(b->arg_count - argc, 0) + 2 + n_vars + + b->stack_size); + if (n) { + val = JS_EXCEPTION; + goto call_exception; + } + func_obj = sp[FRAME_OFFSET_FUNC_OBJ]; + p = JS_VALUE_TO_PTR(func_obj); + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + /* add undefined arguments if the caller did not + provide enough arguments */ + if (unlikely(b->arg_count > argc)) { + n = b->arg_count - argc; + sp -= n; + for(i = 0; i < FRAME_OFFSET_ARG0 + argc; i++) + sp[i] = sp[i + n]; + for(i = 0; i < n; i++) + sp[FRAME_OFFSET_ARG0 + argc + i] = JS_UNDEFINED; + } + fp = sp; + *--sp = JS_NewShortInt(0); /* FRAME_OFFSET_CUR_PC */ + *--sp = JS_NULL; /* FRAME_OFFSET_FIRST_VARREF */ + sp -= n_vars; + for(i = 0; i < n_vars; i++) + sp[i] = JS_UNDEFINED; + byte_code = JS_VALUE_TO_PTR(b->byte_code); + pc = byte_code->buf; + } else { + not_a_function: + sp += 2; /* go back to the caller frame */ + ctx->sp = sp; + ctx->fp = fp; + val = JS_ThrowTypeError(ctx, "not a function"); + call_exception: + if (!pc) { + goto done; + } else { + RESTORE(); + goto exception; + } + } + } + } + BREAK; + + exception: + /* 'val' must contain the exception */ + { + JSValue *stack_top, val2; + JSValueArray *vars; + int v; + /* exception before entering in the first function ? + (XXX: remove this test) */ + if (!pc) + goto done; + v = JS_VALUE_GET_SPECIAL_VALUE(val); + if (v >= JS_EX_CALL) { + /* tail call */ + call_flags = JS_VALUE_GET_SPECIAL_VALUE(val) - JS_EX_CALL; + /* the opcode has only one byte, hence the PC must + be updated accordingly after the function + returns */ + if (opcode == OP_get_length || + opcode == OP_get_length2 || + opcode == OP_get_array_el || + opcode == OP_get_array_el2 || + opcode == OP_put_array_el) { + call_flags |= FRAME_CF_PC_ADD1; + } + // js_printf(ctx, "tail call: 0x%x\n", call_flags); + goto generic_function_call; + } + /* XXX: start gc in case of JS_EXCEPTION_MEM */ + stack_top = fp + FRAME_OFFSET_VAR0 + 1; + if (b->vars != JS_NULL) { + vars = JS_VALUE_TO_PTR(b->vars); + stack_top -= (vars->size - b->arg_count); + } + if (ctx->current_exception_is_uncatchable) { + sp = stack_top; + } else { + while (sp < stack_top) { + val2 = *sp++; + if (JS_VALUE_GET_SPECIAL_TAG(val2) == JS_TAG_CATCH_OFFSET) { + JSByteArray *byte_code; + /* exception caught by a 'catch' in the + current function */ + *--sp = ctx->current_exception; + ctx->current_exception = JS_NULL; + byte_code = JS_VALUE_TO_PTR(b->byte_code); + pc = byte_code->buf + JS_VALUE_GET_SPECIAL_VALUE(val2); + goto restart; + } + } + } + } + goto generic_return; + + CASE(OP_return_undef): + val = JS_UNDEFINED; + goto generic_return; + + CASE(OP_return): + val = sp[0]; + generic_return: + { + JSObject *p; + int argc, pc_offset; + JSValue val2; + JSVarRef *pv; + JSByteArray *byte_code; + + /* detach the variable references */ + val2 = fp[FRAME_OFFSET_FIRST_VARREF]; + while (val2 != JS_NULL) { + pv = JS_VALUE_TO_PTR(val2); + val2 = pv->u.next; + assert(!pv->is_detached); + pv->u.value = *pv->u.pvalue; + pv->is_detached = TRUE; + /* shrink 'pv' */ + set_free_block((uint8_t *)pv + sizeof(JSVarRef) - sizeof(JSValue), sizeof(JSValue)); + } + + call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]); + if (unlikely(call_flags & FRAME_CF_CTOR)) { + if (!JS_IsException(val) && !JS_IsObject(ctx, val)) { + val = fp[FRAME_OFFSET_THIS_OBJ]; + } + } + argc = call_flags & FRAME_CF_ARGC_MASK; + argc = max_int(argc, b->arg_count); + sp = fp + FRAME_OFFSET_ARG0 + argc; + return_call: + call_flags = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CALL_FLAGS]); + /* XXX: restore stack_bottom to reduce memory usage */ + fp = VALUE_TO_SP(ctx, fp[FRAME_OFFSET_SAVED_FP]); + if (fp == initial_fp) + goto done; + pc_offset = JS_VALUE_GET_INT(fp[FRAME_OFFSET_CUR_PC]); + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + byte_code = JS_VALUE_TO_PTR(b->byte_code); + pc = byte_code->buf + pc_offset; + /* now we are in the calling function */ + if (JS_IsException(val)) + goto exception; + if (!(call_flags & FRAME_CF_POP_RET)) + *--sp = val; + /* Note: if variable size call, can add a flag in call_flags */ + if (!(call_flags & FRAME_CF_PC_ADD1)) + pc += 2; /* skip the call arg or get_field/put_field arg */ + } + BREAK; + + CASE(OP_catch): + { + int32_t diff; + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + diff = get_u32(pc); + *--sp = JS_VALUE_MAKE_SPECIAL(JS_TAG_CATCH_OFFSET, pc + diff - byte_code->buf); + pc += 4; + } + BREAK; + CASE(OP_throw): + val = *sp++; + SAVE(); + val = JS_Throw(ctx, val); + RESTORE(); + goto exception; + CASE(OP_gosub): + { + int32_t diff; + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + diff = get_u32(pc); + *--sp = JS_NewShortInt(pc + 4 - byte_code->buf); + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + uint32_t pos; + if (unlikely(!JS_IsInt(sp[0]))) + goto ret_fail; + pos = JS_VALUE_GET_INT(sp[0]); + if (unlikely(pos >= byte_code->size)) { + ret_fail: + SAVE(); + val = JS_ThrowInternalError(ctx, "invalid ret value"); + RESTORE(); + goto exception; + } + sp++; + pc = byte_code->buf + pos; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + *--sp = fp[FRAME_OFFSET_VAR0 - idx]; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + fp[FRAME_OFFSET_VAR0 - idx] = sp[0]; + sp++; + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + *--sp = fp[FRAME_OFFSET_ARG0 + idx]; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + fp[FRAME_OFFSET_ARG0 + idx] = sp[0]; + sp++; + } + BREAK; + + CASE(OP_get_loc0): *--sp = fp[FRAME_OFFSET_VAR0 - 0]; BREAK; + CASE(OP_get_loc1): *--sp = fp[FRAME_OFFSET_VAR0 - 1]; BREAK; + CASE(OP_get_loc2): *--sp = fp[FRAME_OFFSET_VAR0 - 2]; BREAK; + CASE(OP_get_loc3): *--sp = fp[FRAME_OFFSET_VAR0 - 3]; BREAK; + CASE(OP_get_loc8): *--sp = fp[FRAME_OFFSET_VAR0 - *pc++]; BREAK; + + CASE(OP_put_loc0): fp[FRAME_OFFSET_VAR0 - 0] = *sp++; BREAK; + CASE(OP_put_loc1): fp[FRAME_OFFSET_VAR0 - 1] = *sp++; BREAK; + CASE(OP_put_loc2): fp[FRAME_OFFSET_VAR0 - 2] = *sp++; BREAK; + CASE(OP_put_loc3): fp[FRAME_OFFSET_VAR0 - 3] = *sp++; BREAK; + CASE(OP_put_loc8): fp[FRAME_OFFSET_VAR0 - *pc++] = *sp++; BREAK; + + CASE(OP_get_arg0): *--sp = fp[FRAME_OFFSET_ARG0 + 0]; BREAK; + CASE(OP_get_arg1): *--sp = fp[FRAME_OFFSET_ARG0 + 1]; BREAK; + CASE(OP_get_arg2): *--sp = fp[FRAME_OFFSET_ARG0 + 2]; BREAK; + CASE(OP_get_arg3): *--sp = fp[FRAME_OFFSET_ARG0 + 3]; BREAK; + + CASE(OP_put_arg0): fp[FRAME_OFFSET_ARG0 + 0] = *sp++; BREAK; + CASE(OP_put_arg1): fp[FRAME_OFFSET_ARG0 + 1] = *sp++; BREAK; + CASE(OP_put_arg2): fp[FRAME_OFFSET_ARG0 + 2] = *sp++; BREAK; + CASE(OP_put_arg3): fp[FRAME_OFFSET_ARG0 + 3] = *sp++; BREAK; + + CASE(OP_get_var_ref): + CASE(OP_get_var_ref_nocheck): + { + int idx; + JSObject *p; + JSVarRef *pv; + idx = get_u16(pc); + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]); + if (pv->is_detached) + val = pv->u.value; + else + val = *pv->u.pvalue; + if (unlikely(val == JS_TAG_UNINITIALIZED) && + opcode == OP_get_var_ref) { + JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + SAVE(); + val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]); + RESTORE(); + goto exception; + } + pc += 2; + *--sp = val; + } + BREAK; + CASE(OP_put_var_ref): + CASE(OP_put_var_ref_nocheck): + { + int idx; + JSObject *p; + JSVarRef *pv; + JSValue *pval; + idx = get_u16(pc); + p = JS_VALUE_TO_PTR(fp[FRAME_OFFSET_FUNC_OBJ]); + pv = JS_VALUE_TO_PTR(p->u.closure.var_refs[idx]); + if (pv->is_detached) + pval = &pv->u.value; + else + pval = pv->u.pvalue; + if (unlikely(*pval == JS_TAG_UNINITIALIZED) && + opcode == OP_put_var_ref) { + JSValueArray *ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + SAVE(); + val = JS_ThrowReferenceError(ctx, "variable '%"JSValue_PRI"' is not defined", ext_vars->arr[2 * idx]); + RESTORE(); + goto exception; + } + *pval = *sp++; + pc += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + POLL_INTERRUPT(); + BREAK; + CASE(OP_if_false): + CASE(OP_if_true): + { + int res; + + pc += 4; + + res = JS_ToBool(ctx, *sp++); + if (res ^ (OP_if_true - opcode)) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + POLL_INTERRUPT(); + } + BREAK; + + CASE(OP_lnot): + { + int res; + res = JS_ToBool(ctx, sp[0]); + sp[0] = JS_NewBool(!res); + } + BREAK; + + CASE(OP_get_field2): + sp--; + sp[0] = sp[1]; + goto get_field_common; + CASE(OP_get_field): + get_field_common: + { + int idx; + JSValue prop, obj; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + idx = get_u16(pc); + prop = cpool->arr[idx]; + obj = sp[0]; + if (likely(JS_IsPtr(obj))) { + /* fast case */ + JSObject *p = JS_VALUE_TO_PTR(obj); + JSProperty *pr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto get_field_slow; + for(;;) { + /* no array check is necessary because 'prop' is + guaranteed not to be a numeric property */ + /* XXX: slow due to short ints */ + pr = find_own_property_inlined(ctx, p, prop); + if (pr) { + if (unlikely(pr->prop_type != JS_PROP_NORMAL)) { + /* sp[0] is this_obj, obj is the current + object */ + goto get_field_slow; + } else { + val = pr->value; + break; + } + } + obj = p->proto; + if (obj == JS_NULL) { + val = JS_UNDEFINED; + break; + } + p = JS_VALUE_TO_PTR(obj); + } + } else { + get_field_slow: + SAVE(); + val = JS_GetPropertyInternal(ctx, obj, prop, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + } + pc += 2; + sp[0] = val; + } + BREAK; + + CASE(OP_get_length2): + sp--; + sp[0] = sp[1]; + goto get_length_common; + + CASE(OP_get_length): + get_length_common: + { + JSValue obj; + obj = sp[0]; + if (likely(JS_IsPtr(obj))) { + /* fast case */ + JSObject *p = JS_VALUE_TO_PTR(obj); + if (p->mtag == JS_MTAG_OBJECT) { + if (p->class_id == JS_CLASS_ARRAY) { + if (unlikely(p->proto != ctx->class_proto[JS_CLASS_ARRAY] || + p->props != ctx->empty_props)) + goto get_length_slow; + val = JS_NewShortInt(p->u.array.len); + } else { + goto get_length_slow; + } + } else if (p->mtag == JS_MTAG_STRING) { + JSString *ps = (JSString *)p; + if (likely(ps->is_ascii)) + val = JS_NewShortInt(ps->len); + else + val = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, obj, ps->len * 2)); + } else { + goto get_length_slow; + } + } else if (JS_VALUE_GET_SPECIAL_TAG(val) == JS_TAG_STRING_CHAR) { + val = JS_NewShortInt(JS_VALUE_GET_SPECIAL_VALUE(val) >= 0x10000 ? 2 : 1); + } else { + get_length_slow: + SAVE(); + val = JS_GetPropertyInternal(ctx, obj, js_get_atom(ctx, JS_ATOM_length), TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + } + sp[0] = val; + } + BREAK; + + CASE(OP_put_field): + { + int idx; + JSValue prop, obj; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + + idx = get_u16(pc); + prop = cpool->arr[idx]; + obj = sp[1]; + if (likely(JS_IsPtr(obj))) { + /* fast case */ + JSObject *p = JS_VALUE_TO_PTR(obj); + JSProperty *pr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto put_field_slow; + /* no array check is necessary because 'prop' is + guaranteed not to be a numeric property */ + /* XXX: slow due to short ints */ + pr = find_own_property_inlined(ctx, p, prop); + if (unlikely(!pr)) + goto put_field_slow; + if (unlikely(pr->prop_type != JS_PROP_NORMAL)) + goto put_field_slow; + /* XXX: slow */ + if (unlikely(JS_IS_ROM_PTR(ctx, pr))) + goto put_field_slow; + pr->value = sp[0]; + sp += 2; + } else { + put_field_slow: + val = *sp++; + SAVE(); + val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + sp++; + } + pc += 2; + } + BREAK; + + CASE(OP_get_array_el2): + val = sp[0]; + sp[0] = sp[1]; + goto get_array_el_common; + CASE(OP_get_array_el): + val = sp[0]; + sp++; + get_array_el_common: + { + JSValue prop = val, obj; + obj = sp[0]; + if (JS_IsPtr(obj) && JS_IsInt(prop)) { + /* fast case with array */ + /* XXX: optimize typed arrays too ? */ + JSObject *p = JS_VALUE_TO_PTR(obj); + uint32_t idx; + JSValueArray *arr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto get_array_el_slow; + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto get_array_el_slow; + idx = JS_VALUE_GET_INT(prop); + if (unlikely(idx >= p->u.array.len)) + goto get_array_el_slow; + + arr = JS_VALUE_TO_PTR(p->u.array.tab); + val = arr->arr[idx]; + } else { + get_array_el_slow: + SAVE(); + prop = JS_ToPropertyKey(ctx, prop); + RESTORE(); + if (JS_IsException(prop)) { + val = prop; + goto exception; + } + SAVE(); + val = JS_GetPropertyInternal(ctx, sp[0], prop, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + } + sp[0] = val; + } + BREAK; + + CASE(OP_put_array_el): + { + JSValue prop, obj; + obj = sp[2]; + prop = sp[1]; + if (JS_IsPtr(obj) && JS_IsInt(prop)) { + /* fast case with array */ + /* XXX: optimize typed arrays too ? */ + JSObject *p = JS_VALUE_TO_PTR(obj); + uint32_t idx; + JSValueArray *arr; + if (unlikely(p->mtag != JS_MTAG_OBJECT)) + goto put_array_el_slow; + if (unlikely(p->class_id != JS_CLASS_ARRAY)) + goto put_array_el_slow; + idx = JS_VALUE_GET_INT(prop); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (unlikely(idx >= p->u.array.len)) { + if (idx == p->u.array.len && + p->u.array.tab != JS_NULL && + idx < arr->size) { + arr->arr[idx] = sp[0]; + p->u.array.len = idx + 1; + } else { + goto put_array_el_slow; + } + } else { + arr->arr[idx] = sp[0]; + } + sp += 3; + } else { + put_array_el_slow: + SAVE(); + sp[1] = JS_ToPropertyKey(ctx, sp[1]); + RESTORE(); + if (JS_IsException(sp[1])) { + val = sp[1]; + goto exception; + } + val = *sp++; + prop = *sp++; + SAVE(); + val = JS_SetPropertyInternal(ctx, sp[0], prop, val, TRUE); + RESTORE(); + if (unlikely(JS_IsExceptionOrTailCall(val))) { + sp = ctx->sp; + goto exception; + } + sp++; + } + } + BREAK; + + CASE(OP_define_field): + CASE(OP_define_getter): + CASE(OP_define_setter): + { + int idx; + JSValue prop; + JSValueArray *cpool = JS_VALUE_TO_PTR(b->cpool); + + idx = get_u16(pc); + prop = cpool->arr[idx]; + + SAVE(); + if (opcode == OP_define_field) { + val = JS_DefinePropertyValue(ctx, sp[1], prop, sp[0]); + } else if (opcode == OP_define_getter) + val = JS_DefinePropertyGetSet(ctx, sp[1], prop, sp[0], JS_UNDEFINED, JS_DEF_PROP_HAS_GET); + else + val = JS_DefinePropertyGetSet(ctx, sp[1], prop, JS_UNDEFINED, sp[0], JS_DEF_PROP_HAS_SET); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + pc += 2; + sp++; + } + BREAK; + + CASE(OP_set_proto): + { + if (JS_IsObject(ctx, sp[0]) || JS_IsNull(sp[0])) { + SAVE(); + val = js_set_prototype_internal(ctx, sp[1], sp[0]); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + } + sp++; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int r; + if (unlikely(__builtin_add_overflow((int)op1, (int)op2, &r))) + goto add_slow; + sp[1] = (uint32_t)r; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) { + double d1, d2; + d1 = js_get_short_float(op1); + d2 = js_get_short_float(op2); + dr = d1 + d2; + sp++; + goto float_result; + } else +#endif + { + add_slow: + SAVE(); + val = js_add_slow(ctx); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + } + sp++; + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int r; + if (unlikely(__builtin_sub_overflow((int)op1, (int)op2, &r))) + goto binary_arith_slow; + sp[1] = (uint32_t)r; + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) { + double d1, d2; + d1 = js_get_short_float(op1); + d2 = js_get_short_float(op2); + dr = d1 - d2; + sp++; + goto float_result; + } else +#endif + { + goto binary_arith_slow; + } + sp++; + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + int64_t r; + v1 = (int)op1; + v2 = (int)op2 >> 1; + r = (int64_t)v1 * (int64_t)v2; + if (unlikely(r != (int)r)) { +#if defined(JS_USE_SHORT_FLOAT) + dr = (double)(r >> 1); + sp++; + goto float_result; +#else + goto binary_arith_slow; +#endif + } + /* -0 case */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + sp[1] = ctx->minus_zero; + } else { + sp[1] = (uint32_t)r; + } + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_VALUE_IS_BOTH_SHORT_FLOAT(op1, op2)) { + double d1, d2; + d1 = js_get_short_float(op1); + d2 = js_get_short_float(op2); + dr = d1 * d2; + sp++; + goto float_result; + } else +#endif + { + goto binary_arith_slow; + } + sp++; + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + SAVE(); + val = JS_NewFloat64(ctx, (double)v1 / (double)v2); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + sp++; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[1] = JS_NewShortInt(r); + sp++; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + SAVE(); + val = js_binary_arith_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_plus): + { + JSValue op1; + op1 = sp[0]; + if (JS_IsIntOrShortFloat(op1) || + (JS_IsPtr(op1) && js_get_mtag(JS_VALUE_TO_PTR(op1)) == JS_MTAG_FLOAT64)) { + } else { + goto unary_arith_slow; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = op1; + if (v1 == 0) { + sp[0] = ctx->minus_zero; + } else if (v1 == INT32_MIN) { +#if defined(JS_USE_SHORT_FLOAT) + dr = -(double)JS_SHORTINT_MIN; + goto float_result; +#else + goto unary_arith_slow; +#endif + } else { + sp[0] = -v1; + } + } else +#if defined(JS_USE_SHORT_FLOAT) + if (JS_IsShortFloat(op1)) { + dr = -js_get_short_float(op1); + float_result: + /* for efficiency, we don't try to store it as a short integer */ + if (likely(fabs(dr) >= 0x1p-127 && fabs(dr) <= 0x1p+128)) { + val = js_to_short_float(dr); + } else if (dr == 0.0) { + if (float64_as_uint64(dr) != 0) { + /* minus zero often happens, so it is worth having a constant + value */ + val = ctx->minus_zero; + } else { + /* XXX: could have a short float + representation for zero and minus zero + so that the float fast case is still + used when they happen */ + val = JS_NewShortInt(0); + } + } else { + /* slow case: need to allocate it */ + SAVE(); + val = js_alloc_float64(ctx, dr); + RESTORE(); + if (JS_IsException(val)) + goto exception; + } + sp[0] = val; + } else +#endif + { + goto unary_arith_slow; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = JS_VALUE_GET_INT(op1); + if (unlikely(v1 == JS_SHORTINT_MAX)) + goto unary_arith_slow; + sp[0] = JS_NewShortInt(v1 + 1); + } else { + goto unary_arith_slow; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = JS_VALUE_GET_INT(op1); + if (unlikely(v1 == JS_SHORTINT_MIN)) + goto unary_arith_slow; + sp[0] = JS_NewShortInt(v1 - 1); + } else { + unary_arith_slow: + SAVE(); + val = js_unary_arith_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[0] = val; + } + } + BREAK; + CASE(OP_post_inc): + CASE(OP_post_dec): + { + JSValue op1; + int v1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + v1 = JS_VALUE_GET_INT(op1) + 2 * (opcode - OP_post_dec) - 1; + if (v1 < JS_SHORTINT_MIN || v1 > JS_SHORTINT_MAX) + goto slow_post_inc_dec; + val = JS_NewShortInt(v1); + } else { + slow_post_inc_dec: + SAVE(); + val = js_post_inc_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + } + *--sp = val; + } + BREAK; + + CASE(OP_not): + { + JSValue op1; + op1 = sp[0]; + if (JS_IsInt(op1)) { + sp[0] = (~op1) & (~1); + } else { + SAVE(); + val = js_not_slow(ctx); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[0] = val; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t r; + r = JS_VALUE_GET_INT(op1) << (JS_VALUE_GET_INT(op2) & 0x1f); + if (unlikely(r < JS_SHORTINT_MIN || r > JS_SHORTINT_MAX)) { +#if defined(JS_USE_SHORT_FLOAT) + dr = (double)r; + sp++; + goto float_result; +#else + goto binary_logic_slow; +#endif + } + sp[1] = JS_NewShortInt(r); + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t r; + r = (uint32_t)JS_VALUE_GET_INT(op1) >> + ((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f); + if (unlikely(r > JS_SHORTINT_MAX)) { +#if defined(JS_USE_SHORT_FLOAT) + dr = (double)r; + sp++; + goto float_result; +#else + goto binary_logic_slow; +#endif + } + sp[1] = JS_NewShortInt(r); + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = ((int)op1 >> ((uint32_t)JS_VALUE_GET_INT(op2) & 0x1f)) & ~1; + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = op1 & op2; + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = op1 | op2; + sp++; + } else { + goto binary_logic_slow; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[1]; + op2 = sp[0]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[1] = op1 ^ op2; + sp++; + } else { + binary_logic_slow: + SAVE(); + val = js_binary_logic_slow(ctx, opcode); + RESTORE(); + if (JS_IsException(val)) + goto exception; + sp[1] = val; + sp++; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[1]; \ + op2 = sp[0]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[1] = JS_NewBool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp++; \ + } else { \ + SAVE(); \ + val = slow_call; \ + RESTORE(); \ + if (JS_IsException(val)) \ + goto exception; \ + sp[1] = val; \ + sp++; \ + } \ + } \ + BREAK; + + OP_CMP(OP_lt, <, js_relational_slow(ctx, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, 1)); + CASE(OP_in): + SAVE(); + val = js_operator_in(ctx); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_instanceof): + SAVE(); + val = js_operator_instanceof(ctx); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_typeof): + SAVE(); + val = js_operator_typeof(ctx, sp[0]); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + BREAK; + CASE(OP_delete): + SAVE(); + val = JS_DeleteProperty(ctx, sp[1], sp[0]); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[1] = val; + sp++; + BREAK; + CASE(OP_for_in_start): + CASE(OP_for_of_start): + SAVE(); + val = js_for_of_start(ctx, (opcode == OP_for_in_start)); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + BREAK; + CASE(OP_for_of_next): + SAVE(); + val = js_for_of_next(ctx); + RESTORE(); + if (unlikely(JS_IsException(val))) + goto exception; + sp -= 2; + BREAK; + default: + { + JSByteArray *byte_code = JS_VALUE_TO_PTR(b->byte_code); + SAVE(); + val = JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - byte_code->buf - 1), opcode); + RESTORE(); + } + goto exception; + } + restart: ; + } /* switch */ + done: + ctx->sp = sp; + ctx->fp = fp; + ctx->js_call_rec_count--; + return val; +} + +#undef SAVE +#undef RESTORE + +static inline int is_ident_first(int c) +{ + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '_' || c == '$'; +} + +static inline int is_ident_next(int c) +{ + return is_ident_first(c) || is_num(c); +} + +/**********************************************************************/ +/* dump utilities */ + +#ifdef JS_DUMP + +static void js_dump_array(JSContext *ctx, JSValueArray *arr, int len) +{ + int i; + + js_printf(ctx, "[ "); + for(i = 0; i < len; i++) { + if (i != 0) + js_printf(ctx, ", "); + JS_PrintValue(ctx, arr->arr[i]); + } + js_printf(ctx, " ]"); +} + +/* put constructors into a separate table */ +/* XXX: improve by using a table */ +static JSValue js_find_class_name(JSContext *ctx, int class_id) +{ + const JSCFunctionDef *fd; + fd = ctx->c_function_table; + while ((fd->def_type != JS_CFUNC_constructor_magic && + fd->def_type != JS_CFUNC_constructor) || + fd->magic != class_id) { + fd++; + } + return reloc_c_func_name(ctx, fd->name); +} + +static void js_dump_float64(JSContext *ctx, double d) +{ + char buf[32]; + JSDTOATempMem tmp_mem; /* XXX: potentially large stack size */ + js_dtoa(buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &tmp_mem); + js_printf(ctx, "%s", buf); +} + +static void dump_regexp(JSContext *ctx, JSObject *p); + +static void js_dump_error(JSContext *ctx, JSObject *p) +{ + JSObject *p1; + JSProperty *pr; + JSValue name; + + /* find the error name without side effect */ + p1 = p; + if (p->proto != JS_NULL) + p1 = JS_VALUE_TO_PTR(p->proto); + pr = find_own_property(ctx, p1, js_get_atom(ctx, JS_ATOM_name)); + if (!pr || !JS_IsString(ctx, pr->value)) + name = js_get_atom(ctx, JS_ATOM_Error); + else + name = pr->value; + js_printf(ctx, "%" JSValue_PRI, name); + if (p->u.error.message != JS_NULL) { + js_printf(ctx, ": %" JSValue_PRI, p->u.error.message); + } + if (p->u.error.stack != JS_NULL) { + /* remove the trailing '\n' if any */ + js_printf(ctx, "\n%#" JSValue_PRI, p->u.error.stack); + } +} + +static void js_dump_object(JSContext *ctx, JSObject *p, int flags) +{ + if (flags & JS_DUMP_LONG) { + switch(p->class_id) { + case JS_CLASS_CLOSURE: + { + JSFunctionBytecode *b = JS_VALUE_TO_PTR(p->u.closure.func_bytecode); + js_printf(ctx, "function "); + JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + } + break; + case JS_CLASS_C_FUNCTION: + js_printf(ctx, "function "); + JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[p->u.cfunc.idx].name), JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + break; + case JS_CLASS_ERROR: + js_dump_error(ctx, p); + break; + case JS_CLASS_REGEXP: + dump_regexp(ctx, p); + break; + default: + case JS_CLASS_ARRAY: + case JS_CLASS_OBJECT: + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + int i, idx; + uint32_t v; + double d; + JSObject *pbuffer; + JSByteArray *arr; + JS_PrintValueF(ctx, js_find_class_name(ctx, p->class_id), + JS_DUMP_NOQUOTE); + js_printf(ctx, "([ "); + pbuffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(pbuffer->u.array_buffer.byte_buffer); + for(i = 0; i < p->u.typed_array.len; i++) { + if (i != 0) + js_printf(ctx, ", "); + idx = i + p->u.typed_array.offset; + switch(p->class_id) { + default: + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + v = *((uint8_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_INT8_ARRAY: + v = *((int8_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_INT16_ARRAY: + v = *((int16_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_UINT16_ARRAY: + v = *((uint16_t *)arr->buf + idx); + goto ta_i32; + case JS_CLASS_INT32_ARRAY: + v = *((int32_t *)arr->buf + idx); + ta_i32: + js_printf(ctx, "%d", v); + break; + case JS_CLASS_UINT32_ARRAY: + v = *((uint32_t *)arr->buf + idx); + js_printf(ctx, "%u", v); + break; + case JS_CLASS_FLOAT32_ARRAY: + d = *((float *)arr->buf + idx); + goto ta_d; + case JS_CLASS_FLOAT64_ARRAY: + d = *((double *)arr->buf + idx); + ta_d: + js_dump_float64(ctx, d); + break; + } + } + js_printf(ctx, " ])"); + } else { + int i, j, prop_count, hash_mask; + JSProperty *pr; + JSValueArray *arr; + BOOL is_first = TRUE; + + arr = JS_VALUE_TO_PTR(p->props); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + if (p->class_id == JS_CLASS_ARRAY) { + JSValueArray *tab = JS_VALUE_TO_PTR(p->u.array.tab); + js_printf(ctx, "[ "); + for(i = 0; i < p->u.array.len; i++) { + if (!is_first) + js_printf(ctx, ", "); + JS_PrintValue(ctx, tab->arr[i]); + is_first = FALSE; + } + } else { + if (p->class_id != JS_CLASS_OBJECT) { + JSValue class_name = js_find_class_name(ctx, p->class_id); + if (!JS_IsNull(class_name)) + JS_PrintValueF(ctx, class_name, JS_DUMP_NOQUOTE); + js_putchar(ctx, ' '); + } + js_printf(ctx, "{ "); + } + for(i = 0, j = 0; j < prop_count; i++) { + pr = (JSProperty *)&arr->arr[2 + (hash_mask + 1) + 3 * i]; + if (pr->key != JS_UNINITIALIZED) { + if (!is_first) + js_printf(ctx, ", "); + JS_PrintValueF(ctx, pr->key, JS_DUMP_NOQUOTE); + js_printf(ctx, ": "); + if (!(flags & JS_DUMP_RAW) && pr->prop_type == JS_PROP_SPECIAL) { + JS_PrintValue(ctx, get_special_prop(ctx, pr->value)); + } else { + JS_PrintValue(ctx, pr->value); + } + is_first = FALSE; + j++; + } + } + js_printf(ctx, " %c", + p->class_id == JS_CLASS_ARRAY ? ']' : '}'); + } + break; + } + } else { + const char *str; + if (p->class_id == JS_CLASS_ARRAY) + str = "Array"; + else if (p->class_id == JS_CLASS_ERROR) + str = "Error"; + else if (p->class_id == JS_CLASS_CLOSURE || + p->class_id == JS_CLASS_C_FUNCTION) { + str = "Function"; + } else { + str = "Object"; + } + js_printf(ctx, "[object %s]", str); + } +} + +static void dump_string(JSContext *ctx, int sep, const uint8_t *buf, size_t len, + int flags) +{ + BOOL use_quote; + const uint8_t *p, *p_end; + size_t i, clen; + int c; + + use_quote = TRUE; + if (flags & JS_DUMP_NOQUOTE) { + if (len >= 1 && is_ident_first(buf[0])) { + for(i = 1; i < len; i++) { + if (!is_ident_next(buf[i])) + goto need_quote; + } + use_quote = FALSE; + } + need_quote: ; + } + + if (!(flags & JS_DUMP_RAW)) + sep = '"'; + if (use_quote) + js_putchar(ctx, sep); + p = buf; + p_end = buf + len; + while (p < p_end) { + c = utf8_get(p, &clen); + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + js_putchar(ctx, '\\'); + js_putchar(ctx, c); + break; + default: + if (c < 32 || (c >= 0xd800 && c < 0xe000)) { + js_printf(ctx, "\\u%04x", c); + } else { + ctx->write_func(ctx->opaque, p, clen); + } + break; + } + p += clen; + } + if (use_quote) + js_putchar(ctx, sep); +} + +void JS_PrintValueF(JSContext *ctx, JSValue val, int flags) +{ + if (JS_IsInt(val)) { + js_printf(ctx, "%d", JS_VALUE_GET_INT(val)); + } else +#ifdef JS_USE_SHORT_FLOAT + if (JS_IsShortFloat(val)) { + js_dump_float64(ctx, js_get_short_float(val)); + } else +#endif + if (!JS_IsPtr(val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(val)) { + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + case JS_TAG_UNINITIALIZED: + case JS_TAG_BOOL: + js_printf(ctx, "%"JSValue_PRI"", val); + break; + case JS_TAG_EXCEPTION: + js_printf(ctx, "[exception %d]", JS_VALUE_GET_SPECIAL_VALUE(val)); + break; + case JS_TAG_CATCH_OFFSET: + js_printf(ctx, "[catch_offset %d]", JS_VALUE_GET_SPECIAL_VALUE(val)); + break; + case JS_TAG_SHORT_FUNC: + { + int idx = JS_VALUE_GET_SPECIAL_VALUE(val); + js_printf(ctx, "function "); + JS_PrintValueF(ctx, reloc_c_func_name(ctx, ctx->c_function_table[idx].name), JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + } + break; + case JS_TAG_STRING_CHAR: + { + uint8_t buf[UTF8_CHAR_LEN_MAX + 1]; + int len; + len = get_short_string(buf, val); + dump_string(ctx, '`', buf, len, flags); + } + break; + default: + js_printf(ctx, "[tag %d]", (int)JS_VALUE_GET_SPECIAL_TAG(val)); + break; + } + } else { + void *ptr = JS_VALUE_TO_PTR(val); + int mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FLOAT64: + { + JSFloat64 *p = ptr; + js_dump_float64(ctx, p->u.dval); + } + break; + case JS_MTAG_OBJECT: + js_dump_object(ctx, ptr, flags); + break; + case JS_MTAG_STRING: + { + JSString *p = ptr; + int sep; + sep = p->is_unique ? '\'' : '\"'; + dump_string(ctx, sep, p->buf, p->len, flags); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *arr = ptr; + js_dump_array(ctx, arr, arr->size); + } + break; + case JS_MTAG_BYTE_ARRAY: + { + JSByteArray *arr = ptr; + js_printf(ctx, "byte_array(%" PRIu64 ")", (uint64_t)arr->size); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = ptr; + js_printf(ctx, "bytecode_function "); + JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE); + js_printf(ctx, "()"); + } + break; + case JS_MTAG_VARREF: + { + JSVarRef *pv = ptr; + js_printf(ctx, "var_ref("); + if (pv->is_detached) + JS_PrintValue(ctx, pv->u.value); + else + JS_PrintValue(ctx, *pv->u.pvalue); + js_printf(ctx, ")"); + } + break; + default: + js_printf(ctx, "[mtag %d]", mtag); + break; + } + } +} + +void JS_PrintValue(JSContext *ctx, JSValue val) +{ + return JS_PrintValueF(ctx, val, 0); +} + +static const char *get_mtag_name(unsigned int mtag) +{ + if (mtag >= countof(js_mtag_name)) + return "?"; + else + return js_mtag_name[mtag]; +} + +static uint32_t val_to_offset(JSContext *ctx, JSValue val) +{ + if (!JS_IsPtr(val)) + return 0; + else + return (uint8_t *)JS_VALUE_TO_PTR(val) - ctx->heap_base; +} + +void JS_DumpMemory(JSContext *ctx, BOOL is_long) +{ + uint8_t *ptr; + uint32_t mtag_mem_size[JS_MTAG_COUNT]; + uint32_t mtag_count[JS_MTAG_COUNT]; + uint32_t tot_size, i; + if (is_long) { + js_printf(ctx, "%10s %s %8s %15s %10s %10s %s\n", "OFFSET", "M", "SIZE", "TAG", "PROTO", "PROPS", "EXTRA"); + } + for(i = 0; i < JS_MTAG_COUNT; i++) { + mtag_mem_size[i] = 0; + mtag_count[i] = 0; + } + tot_size = 0; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + int mtag, size, gc_mark; + mtag = ((JSMemBlockHeader *)ptr)->mtag; + gc_mark = ((JSMemBlockHeader *)ptr)->gc_mark; + size = get_mblock_size(ptr); + mtag_mem_size[mtag] += size; + mtag_count[mtag]++; + tot_size += size; + if (is_long) { + js_printf(ctx, "0x%08x %c %8u %15s", + (unsigned int)((uint8_t *)ptr - ctx->heap_base), + gc_mark ? '*' : ' ', + size, + get_mtag_name(mtag)); + if (mtag != JS_MTAG_FREE) { + if (mtag == JS_MTAG_OBJECT) { + JSObject *p = (JSObject *)ptr; + js_printf(ctx, " 0x%08x 0x%08x", + val_to_offset(ctx, p->proto), val_to_offset(ctx, p->props)); + } else { + js_printf(ctx, " %10s %10s", "", ""); + } + js_printf(ctx, " "); + JS_PrintValueF(ctx, JS_VALUE_FROM_PTR(ptr), JS_DUMP_RAW); + } + js_printf(ctx, "\n"); + } + ptr += size; + } + + js_printf(ctx, "%15s %8s %8s %8s %8s\n", "TAG", "COUNT", "AVG_SIZE", "SIZE", "RATIO"); + for(i = 0; i < JS_MTAG_COUNT; i++) { + if (mtag_count[i] != 0) { + js_printf(ctx, "%15s %8u %8d %8u %7d%%\n", + get_mtag_name(i), + (unsigned int)mtag_count[i], + (int)js_lrint((double)mtag_mem_size[i] / (double)mtag_count[i]), + (unsigned int)mtag_mem_size[i], + (int)js_lrint((double)mtag_mem_size[i] / (double)tot_size * 100.0)); + } + } + js_printf(ctx, "heap size=%u/%u stack_size=%u\n", + (unsigned int)(ctx->heap_free - ctx->heap_base), + (unsigned int)(ctx->stack_top - ctx->heap_base), + (unsigned int)(ctx->stack_top - (uint8_t *)ctx->sp)); +} + +static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx) +{ + int i; + JSValueArray *arr; + + arr = JS_VALUE_TO_PTR( ctx->unique_strings); + js_printf(ctx, "%5s %s\n", "N", "UNIQUE_STRING"); + for(i = 0; i < ctx->unique_strings_len; i++) { + js_printf(ctx, "%5d ", i); + JS_PrintValue(ctx, arr->arr[i]); + js_printf(ctx, "\n"); + } +} +#else +void JS_PrintValueF(JSContext *ctx, JSValue val, int flags) +{ +} +void JS_PrintValue(JSContext *ctx, JSValue val) +{ + return JS_PrintValueF(ctx, val, 0); +} +void JS_DumpMemory(JSContext *ctx, BOOL is_long) +{ +} +static __maybe_unused void JS_DumpUniqueStrings(JSContext *ctx) +{ +} +#endif + +void JS_DumpValueF(JSContext *ctx, const char *str, + JSValue val, int flags) +{ + js_printf(ctx, "%s=", str); + JS_PrintValueF(ctx, val, flags); + js_printf(ctx, "\n"); +} + +void JS_DumpValue(JSContext *ctx, const char *str, + JSValue val) +{ + JS_DumpValueF(ctx, str, val, 0); +} + + +/**************************************************/ +/* JS parser */ + +enum { + TOK_NUMBER = 128, + TOK_STRING, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_POW_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, + TOK_POW, + TOK_EOF, + /* keywords */ + TOK_FIRST_KEYWORD, + TOK_NULL = TOK_FIRST_KEYWORD + JS_ATOM_null, + TOK_FALSE = TOK_FIRST_KEYWORD + JS_ATOM_false, + TOK_TRUE = TOK_FIRST_KEYWORD + JS_ATOM_true, + TOK_IF = TOK_FIRST_KEYWORD + JS_ATOM_if, + TOK_ELSE = TOK_FIRST_KEYWORD + JS_ATOM_else, + TOK_RETURN = TOK_FIRST_KEYWORD + JS_ATOM_return, + TOK_VAR = TOK_FIRST_KEYWORD + JS_ATOM_var, + TOK_THIS = TOK_FIRST_KEYWORD + JS_ATOM_this, + TOK_DELETE = TOK_FIRST_KEYWORD + JS_ATOM_delete, + TOK_VOID = TOK_FIRST_KEYWORD + JS_ATOM_void, + TOK_TYPEOF = TOK_FIRST_KEYWORD + JS_ATOM_typeof, + TOK_NEW = TOK_FIRST_KEYWORD + JS_ATOM_new, + TOK_IN = TOK_FIRST_KEYWORD + JS_ATOM_in, + TOK_INSTANCEOF = TOK_FIRST_KEYWORD + JS_ATOM_instanceof, + TOK_DO = TOK_FIRST_KEYWORD + JS_ATOM_do, + TOK_WHILE = TOK_FIRST_KEYWORD + JS_ATOM_while, + TOK_FOR = TOK_FIRST_KEYWORD + JS_ATOM_for, + TOK_BREAK = TOK_FIRST_KEYWORD + JS_ATOM_break, + TOK_CONTINUE = TOK_FIRST_KEYWORD + JS_ATOM_continue, + TOK_SWITCH = TOK_FIRST_KEYWORD + JS_ATOM_switch, + TOK_CASE = TOK_FIRST_KEYWORD + JS_ATOM_case, + TOK_DEFAULT = TOK_FIRST_KEYWORD + JS_ATOM_default, + TOK_THROW = TOK_FIRST_KEYWORD + JS_ATOM_throw, + TOK_TRY = TOK_FIRST_KEYWORD + JS_ATOM_try, + TOK_CATCH = TOK_FIRST_KEYWORD + JS_ATOM_catch, + TOK_FINALLY = TOK_FIRST_KEYWORD + JS_ATOM_finally, + TOK_FUNCTION = TOK_FIRST_KEYWORD + JS_ATOM_function, + TOK_DEBUGGER = TOK_FIRST_KEYWORD + JS_ATOM_debugger, + TOK_WITH = TOK_FIRST_KEYWORD + JS_ATOM_with, + TOK_CLASS = TOK_FIRST_KEYWORD + JS_ATOM_class, + TOK_CONST = TOK_FIRST_KEYWORD + JS_ATOM_const, + TOK_ENUM = TOK_FIRST_KEYWORD + JS_ATOM_enum, + TOK_EXPORT = TOK_FIRST_KEYWORD + JS_ATOM_export, + TOK_EXTENDS = TOK_FIRST_KEYWORD + JS_ATOM_extends, + TOK_IMPORT = TOK_FIRST_KEYWORD + JS_ATOM_import, + TOK_SUPER = TOK_FIRST_KEYWORD + JS_ATOM_super, + TOK_IMPLEMENTS = TOK_FIRST_KEYWORD + JS_ATOM_implements, + TOK_INTERFACE = TOK_FIRST_KEYWORD + JS_ATOM_interface, + TOK_LET = TOK_FIRST_KEYWORD + JS_ATOM_let, + TOK_PACKAGE = TOK_FIRST_KEYWORD + JS_ATOM_package, + TOK_PRIVATE = TOK_FIRST_KEYWORD + JS_ATOM_private, + TOK_PROTECTED = TOK_FIRST_KEYWORD + JS_ATOM_protected, + TOK_PUBLIC = TOK_FIRST_KEYWORD + JS_ATOM_public, + TOK_STATIC = TOK_FIRST_KEYWORD + JS_ATOM_static, + TOK_YIELD = TOK_FIRST_KEYWORD + JS_ATOM_yield, +}; + +/* this structure is pushed on the JS stack, so all members must be JSValue */ +typedef struct BlockEnv { + JSValue prev; /* JS_NULL or stack index */ + JSValue label_name; /* JS_NULL if none */ + JSValue label_break; + JSValue label_cont; + JSValue label_finally; + JSValue drop_count; /* (int) number of stack elements to drop */ +} BlockEnv; + +typedef uint32_t JSSourcePos; + +typedef struct JSToken { + int val; + JSSourcePos source_pos; /* position in source */ + union { + double d; /* TOK_NUMBER */ + struct { + uint32_t re_flags; /* regular expression flags */ + uint32_t re_end_pos; /* at the final '/' */ + } regexp; + } u; + JSValue value; /* associated value: string for TOK_STRING, TOK_REGEXP; + identifier for TOK_IDENT or keyword */ +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + JSToken token; + + BOOL got_lf : 8; /* true if got line feed before the current token */ + /* global eval: variables are defined as global */ + BOOL is_eval : 8; + /* if true, return the last value. */ + BOOL has_retval : 8; + /* if true, implicitly define global variables in an + assignment. */ + BOOL is_repl : 8; + BOOL has_column : 8; /* column debug info is present */ + /* TRUE if the expression result has been dropped (see PF_DROP) */ + BOOL dropped_result : 8; + JSValue source_str; /* source string or JS_NULL */ + JSValue filename_str; /* 'filename' converted to string */ + /* zero terminated source buffer. Automatically updated by the GC + if source_str is a string */ + const uint8_t *source_buf; + uint32_t buf_pos; + uint32_t buf_len; + + /* current function */ + JSValue cur_func; + JSValue byte_code; + uint32_t byte_code_len; + int last_opcode_pos; /* -1 if no last opcode */ + int last_pc2line_pos; /* pc2line pos for the last opcode */ + JSSourcePos last_pc2line_source_pos; + + uint32_t pc2line_bit_len; + JSSourcePos pc2line_source_pos; /* last generated source pos */ + + uint16_t cpool_len; + /* size of the byte code necessary to define the hoisted functions */ + uint32_t hoisted_code_len; + + /* argument + defined local variable count */ + uint16_t local_vars_len; + + int eval_ret_idx; /* variable index for the eval return value, -1 + if no return value */ + JSValue top_break; /* JS_NULL or SP_TO_VALUE(BlockEnv *) */ + + /* regexp parsing only */ + uint8_t capture_count; + uint8_t re_in_js: 1; + uint8_t multi_line : 1; + uint8_t dotall : 1; + uint8_t ignore_case : 1; + uint8_t is_unicode : 1; + + /* error handling */ + jmp_buf jmp_env; + char error_msg[64]; +} JSParseState; + +static int js_parse_json_value(JSParseState *s, int state, int dummy_param); +static JSValue js_parse_regexp(JSParseState *s, int eval_flags); +static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf); +static int re_parse_alternative(JSParseState *s, int state, int dummy_param); +static int re_parse_disjunction(JSParseState *s, int state, int dummy_param); + +#ifdef DUMP_BYTECODE +static __maybe_unused void dump_byte_code(JSContext *ctx, JSFunctionBytecode *b) +{ + JSByteArray *arr, *pc2line; + JSValueArray *cpool, *vars, *ext_vars; + const JSOpCode *oi; + int pos, op, size, addr, idx, arg_count, len, i, line_num, col_num; + int line_num1, col_num1, hoisted_code_len; + uint8_t *tab; + uint32_t pc2line_pos; + + arr = JS_VALUE_TO_PTR(b->byte_code); + if (b->cpool != JS_NULL) + cpool = JS_VALUE_TO_PTR(b->cpool); + else + cpool = NULL; + if (b->vars != JS_NULL) + vars = JS_VALUE_TO_PTR(b->vars); + else + vars = NULL; + if (b->ext_vars != JS_NULL) + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + else + ext_vars = NULL; + if (b->pc2line != JS_NULL) + pc2line = JS_VALUE_TO_PTR(b->pc2line); + else + pc2line = NULL; + + arg_count = b->arg_count; + + JS_PrintValueF(ctx, b->filename, JS_DUMP_NOQUOTE); + js_printf(ctx, ": function "); + JS_PrintValueF(ctx, b->func_name, JS_DUMP_NOQUOTE); + js_printf(ctx, ":\n"); + + if (b->arg_count && vars) { + js_printf(ctx, " args:"); + for(i = 0; i < b->arg_count; i++) { + js_printf(ctx, " "); + JS_PrintValue(ctx, vars->arr[i]); + } + js_printf(ctx, "\n"); + } + if (vars) { + js_printf(ctx, " locals:"); + for(i = 0; i < vars->size - b->arg_count; i++) { + js_printf(ctx, " "); + JS_PrintValue(ctx, vars->arr[i + b->arg_count]); + } + js_printf(ctx, "\n"); + } + if (ext_vars) { + js_printf(ctx, " refs:"); + for(i = 0; i < b->ext_vars_len; i++) { + int var_kind, var_idx, decl; + static const char *var_kind_str[] = { "arg", "var", "ref", "global" }; + js_printf(ctx, " "); + JS_PrintValue(ctx, ext_vars->arr[2 * i]); + decl = JS_VALUE_GET_INT(ext_vars->arr[2 * i + 1]); + var_kind = decl >> 16; + var_idx = decl & 0xffff; + js_printf(ctx, " (%s:%d)", var_kind_str[var_kind], var_idx); + } + js_printf(ctx, "\n"); + } + + js_printf(ctx, " cpool_size: %d\n", cpool ? (int)cpool->size : 0); + js_printf(ctx, " stack_size: %d\n", b->stack_size); + js_printf(ctx, " opcodes:\n"); + tab = arr->buf; + len = arr->size; + pos = 0; + pc2line_pos = 0; + hoisted_code_len = 0; + if (pc2line) + hoisted_code_len = get_pc2line_hoisted_code_len(pc2line->buf, pc2line->size); + line_num = 1; + col_num = 1; + line_num1 = 0; + col_num1 = 0; + while (pos < len) { + /* extract the debug info */ + if (pc2line && pos >= hoisted_code_len) { + get_pc2line(&line_num, &col_num, pc2line->buf, pc2line->size, + &pc2line_pos, b->has_column); + if (line_num != line_num1 || col_num != col_num1) { + js_printf(ctx, " # %d", line_num); + if (b->has_column) + js_printf(ctx, ", %d", col_num); + js_printf(ctx, "\n"); + line_num1 = line_num; + col_num1 = col_num; + } + } + op = tab[pos]; + js_printf(ctx, "%5d: ", pos); + if (op >= OP_COUNT) { + js_printf(ctx, "invalid opcode (0x%02x)\n", op); + pos++; + continue; + } + oi = &opcode_info[op]; + size = oi->size; + if ((pos + size) > len) { + js_printf(ctx, "truncated opcode (0x%02x)\n", op); + break; + } + js_printf(ctx, "%s", oi->name); + pos++; + switch(oi->fmt) { + case OP_FMT_u8: + js_printf(ctx, " %u", (int)get_u8(tab + pos)); + break; + case OP_FMT_i8: + js_printf(ctx, " %d", (int)get_i8(tab + pos)); + break; + case OP_FMT_u16: + case OP_FMT_npop: + js_printf(ctx, " %u", (int)get_u16(tab + pos)); + break; + case OP_FMT_i16: + js_printf(ctx, " %d", (int)get_i16(tab + pos)); + break; + case OP_FMT_i32: + js_printf(ctx, " %d", (int)get_i32(tab + pos)); + break; + case OP_FMT_u32: + js_printf(ctx, " %u", (int)get_u32(tab + pos)); + break; + case OP_FMT_none_int: + js_printf(ctx, " %d", op - OP_push_0); + break; +#if 0 + case OP_FMT_npopx: + js_printf(ctx, " %d", op - OP_call0); + break; +#endif + case OP_FMT_label8: + addr = get_i8(tab + pos); + goto has_addr1; + case OP_FMT_label16: + addr = get_i16(tab + pos); + goto has_addr1; + case OP_FMT_label: + addr = get_u32(tab + pos); + goto has_addr1; + has_addr1: + js_printf(ctx, " %u", addr + pos); + break; + case OP_FMT_const8: + idx = get_u8(tab + pos); + goto has_pool_idx; + case OP_FMT_const16: + idx = get_u16(tab + pos); + goto has_pool_idx; + has_pool_idx: + js_printf(ctx, " %u: ", idx); + if (idx < cpool->size) { + JS_PrintValue(ctx, cpool->arr[idx]); + } + break; + case OP_FMT_none_loc: + idx = (op - OP_get_loc0) % 4; + goto has_loc; + case OP_FMT_loc8: + idx = get_u8(tab + pos); + goto has_loc; + case OP_FMT_loc: + idx = get_u16(tab + pos); + has_loc: + js_printf(ctx, " %d: ", idx); + idx += arg_count; + if (idx < vars->size) { + JS_PrintValue(ctx, vars->arr[idx]); + } + break; + case OP_FMT_none_arg: + idx = (op - OP_get_arg0) % 4; + goto has_arg; + case OP_FMT_arg: + idx = get_u16(tab + pos); + has_arg: + js_printf(ctx, " %d: ", idx); + if (idx < vars->size) { + JS_PrintValue(ctx, vars->arr[idx]); + } + break; +#if 0 + case OP_FMT_none_var_ref: + idx = (op - OP_get_var_ref0) % 4; + goto has_var_ref; +#endif + case OP_FMT_var_ref: + idx = get_u16(tab + pos); + // has_var_ref: + js_printf(ctx, " %d: ", idx); + if (2 * idx < ext_vars->size) { + JS_PrintValue(ctx, ext_vars->arr[2 * idx]); + } + break; + case OP_FMT_value: + js_printf(ctx, " "); + idx = get_u32(tab + pos); + JS_PrintValue(ctx, idx); + break; + default: + break; + } + js_printf(ctx, "\n"); + pos += oi->size - 1; + } +} +#endif /* DUMP_BYTECODE */ + +static void next_token(JSParseState *s); + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + JSContext *ctx = s->ctx; + switch(token->val) { + case TOK_NUMBER: + /* XXX: TODO */ + js_printf(ctx, "number: %d\n", (int)token->u.d); + break; + case TOK_IDENT: + { + js_printf(ctx, "ident: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } + break; + case TOK_STRING: + { + js_printf(ctx, "string: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } + break; + case TOK_REGEXP: + { + js_printf(ctx, "regexp: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } + break; + case TOK_EOF: + js_printf(ctx, "eof\n"); + break; + default: + if (s->token.val >= TOK_FIRST_KEYWORD) { + js_printf(ctx, "token: "); + JS_PrintValue(s->ctx, token->value); + js_printf(ctx, "\n"); + } else if (s->token.val >= 128) { + js_printf(ctx, "token: %d\n", token->val); + } else { + js_printf(ctx, "token: '%c'\n", token->val); + } + break; + } +} + +/* return the zero based line and column number in the source. */ +static int get_line_col(int *pcol_num, const uint8_t *buf, size_t len) +{ + int line_num, col_num, c; + size_t i; + + line_num = 0; + col_num = 0; + for(i = 0; i < len; i++) { + c = buf[i]; + if (c == '\n') { + line_num++; + col_num = 0; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + *pcol_num = col_num; + return line_num; +} + +static void __attribute__((format(printf, 2, 3), noreturn)) js_parse_error(JSParseState *s, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + js_vsnprintf(s->error_msg, sizeof(s->error_msg), fmt, ap); + va_end(ap); + longjmp(s->jmp_env, 1); +} + +static void js_parse_error_mem(JSParseState *s) +{ + return js_parse_error(s, "not enough memory"); +} + +static void js_parse_error_stack_overflow(JSParseState *s) +{ + return js_parse_error(s, "stack overflow"); +} + +static void js_parse_expect1(JSParseState *s, int ch) +{ + if (s->token.val != ch) + js_parse_error(s, "expecting '%c'", ch); +} + +static void js_parse_expect(JSParseState *s, int ch) +{ + js_parse_expect1(s, ch); + next_token(s); +} + +static void js_parse_expect_semi(JSParseState *s) +{ + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf) { + return; + } + js_parse_error(s, "expecting '%c'", ';'); + } + next_token(s); +} + +#define SKIP_HAS_ARGUMENTS (1 << 0) +#define SKIP_HAS_FUNC_NAME (1 << 1) +#define SKIP_HAS_SEMI (1 << 2) /* semicolon found inside the first level */ + +/* Skip parenthesis or blocks. The current token should be '(', '[' or + '{'. 'func_name' can be JS_NULL. */ +static int js_skip_parens(JSParseState *s, JSValue *pfunc_name) +{ + uint8_t state[128]; + int level, c, bits = 0; + + /* protect from underflow */ + level = 0; + state[level++] = 0; + for (;;) { + switch(s->token.val) { + case '(': + c = ')'; + goto add_level; + case '[': + c = ']'; + goto add_level; + case '{': + c = '}'; + add_level: + if (level >= sizeof(state)) { + js_parse_error(s, "too many nested blocks"); + } + state[level++] = c; + break; + case ')': + case ']': + case '}': + c = state[--level]; + if (s->token.val != c) + js_parse_error(s, "expecting '%c'", c); + break; + case TOK_EOF: + js_parse_error(s, "expecting '%c'", state[level - 1]); + case TOK_IDENT: + if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments)) + bits |= SKIP_HAS_ARGUMENTS; + if (pfunc_name && s->token.value == *pfunc_name) + bits |= SKIP_HAS_FUNC_NAME; + break; + case ';': + if (level == 2) + bits |= SKIP_HAS_SEMI; + break; + } + next_token(s); + if (level <= 1) + break; + } + return bits; +} + +/* skip an expression until ')' */ +static void js_skip_expr(JSParseState *s) +{ + for(;;) { + switch(s->token.val) { + case ')': + return; + case ';': + case TOK_EOF: + js_parse_error(s, "expecting '%c'", ')'); + case '(': + case '[': + case '{': + js_skip_parens(s, NULL); + break; + default: + next_token(s); + break; + } + } +} + +typedef struct JSParsePos { + BOOL got_lf : 8; + BOOL regexp_allowed : 8; + uint32_t source_pos; +} JSParsePos; + +/* return TRUE if a regexp literal is allowed after this token */ +static BOOL is_regexp_allowed(int tok) +{ + switch (tok) { + case TOK_NUMBER: + case TOK_STRING: + case TOK_REGEXP: + case TOK_DEC: + case TOK_INC: + case TOK_NULL: + case TOK_FALSE: + case TOK_TRUE: + case TOK_THIS: + case TOK_IF: + case TOK_WHILE: + case TOK_FOR: + case TOK_DO: + case TOK_CASE: + case TOK_CATCH: + case ')': + case ']': + case TOK_IDENT: + return FALSE; + default: + return TRUE; + } +} + +static void js_parse_get_pos(JSParseState *s, JSParsePos *sp) +{ + sp->source_pos = s->token.source_pos; + sp->got_lf = s->got_lf; + sp->regexp_allowed = is_regexp_allowed(s->token.val); +} + +static void js_parse_seek_token(JSParseState *s, const JSParsePos *sp) +{ + s->buf_pos = sp->source_pos; + s->got_lf = sp->got_lf; + /* the previous token value is only needed so that + is_regexp_allowed() returns the correct value */ + s->token.val = sp->regexp_allowed ? ' ' : ')'; + next_token(s); +} + +/* same as js_skip_parens but go back to the current token */ +static int js_parse_skip_parens_token(JSParseState *s) +{ + JSParsePos pos; + int bits; + + js_parse_get_pos(s, &pos); + bits = js_skip_parens(s, NULL); + js_parse_seek_token(s, &pos); + return bits; +} + +/* return the escape value or -1 */ +static int js_parse_escape(const uint8_t *buf, size_t *plen) +{ + int c; + const uint8_t *p = buf; + c = *p++; + switch(c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case '\'': + case '\"': + case '\\': + break; + case 'x': + { + int h0, h1; + + h0 = from_hex(*p++); + if (h0 < 0) + return -1; + h1 = from_hex(*p++); + if (h1 < 0) + return -1; + c = (h0 << 4) | h1; + } + break; + case 'u': + { + int h, i; + + if (*p == '{') { + p++; + c = 0; + for(;;) { + h = from_hex(*p++); + if (h < 0) + return -1; + c = (c << 4) | h; + if (c > 0x10FFFF) + return -1; + if (*p == '}') + break; + } + p++; + } else { + c = 0; + for(i = 0; i < 4; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + } + } + break; + case '0': + c -= '0'; + if (c != 0 || is_num(*p)) + return -1; + break; + default: + return -2; + } + *plen = p - buf; + return c; +} + +static JSValue js_parse_string(JSParseState *s, uint32_t *ppos, int sep) +{ + JSContext *ctx = s->ctx; + JSValue res; + const uint8_t *buf; + uint32_t pos; + uint32_t c; + size_t escape_len = 0; /* avoid warning */ + StringBuffer b_s, *b = &b_s; + + if (string_buffer_push(ctx, b, 16)) + js_parse_error_mem(s); + buf = s->source_buf; + /* string */ + pos = *ppos; + for(;;) { + c = buf[pos]; + if (c == '\0' || c == '\n' || c == '\r') { + js_parse_error(s, "unexpected end of string"); + } + pos++; + if (c == sep) + break; + if (c == '\\') { + if (buf[pos] == '\n') { + /* ignore escaped newline sequence */ + pos++; + continue; + } + c = js_parse_escape(buf + pos, &escape_len); + if (c == -1) { + js_parse_error(s, "invalid escape sequence"); + } else if (c == -2) { + /* ignore invalid escapes */ + continue; + } + pos += escape_len; + } else if (c >= 0x80) { + size_t clen; + pos--; + c = unicode_from_utf8(buf + pos, UTF8_CHAR_LEN_MAX, &clen); + pos += clen; + if (c == -1) { + js_parse_error(s, "invalid UTF-8 sequence"); + } + } + if (string_buffer_putc(ctx, b, c)) + break; + buf = s->source_buf; /* may be reallocated */ + } + *ppos = pos; + res = string_buffer_pop(ctx, b); + if (JS_IsException(res)) + js_parse_error_mem(s); + return res; +} + +static void js_parse_ident(JSParseState *s, JSToken *token, + uint32_t *ppos, int c) +{ + JSContext *ctx = s->ctx; + uint32_t pos; + JSValue val, val2; + JSGCRef val2_ref; + const uint8_t *buf; + StringBuffer b_s, *b = &b_s; + + if (string_buffer_push(ctx, b, 16)) + js_parse_error_mem(s); + string_buffer_putc(ctx, b, c); /* no allocation */ + buf = s->source_buf; + pos = *ppos; + while (pos < s->buf_len) { + c = buf[pos]; + if (!is_ident_next(c)) + break; + pos++; + if (string_buffer_putc(ctx, b, c)) + break; + buf = s->source_buf; /* may be reallocated */ + } + /* convert to token if necessary */ + token->val = TOK_IDENT; + val2 = string_buffer_pop(ctx, b); + JS_PUSH_VALUE(ctx, val2); + val = JS_MakeUniqueString(ctx, val2); + JS_POP_VALUE(ctx, val2); + if (JS_IsException(val)) + js_parse_error_mem(s); + if (val != val2) + js_free(ctx, JS_VALUE_TO_PTR(val2)); + token->value = val; + if (JS_IsPtr(val)) { + const JSWord *atom_start, *atom_last, *ptr; + atom_start = ctx->atom_table; + atom_last = atom_start + JS_ATOM_yield; + ptr = JS_VALUE_TO_PTR(val); + if (ptr >= atom_start && ptr <= atom_last) { + token->val = TOK_NULL + (ptr - atom_start); + } + } + *ppos = pos; +} + +static void js_parse_regexp_token(JSParseState *s, uint32_t *ppos) +{ + JSContext *ctx = s->ctx; + uint32_t pos; + uint32_t c; + BOOL in_class; + size_t clen; + int re_flags, end_pos, start_pos; + JSString *p; + + in_class = FALSE; + pos = *ppos; + start_pos = pos; + for(;;) { + c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen); + if (c == -1) + js_parse_error(s, "invalid UTF-8 sequence"); + pos += clen; + if (c == '\0' || c == '\n' || c == '\r') { + goto invalid_char; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = TRUE; + } else if (c == ']') { + in_class = FALSE; + } else if (c == '\\') { + c = unicode_from_utf8(s->source_buf + pos, UTF8_CHAR_LEN_MAX, &clen); + if (c == -1) + js_parse_error(s, "invalid UTF-8 sequence"); + if (c == '\0' || c == '\n' || c == '\r') { + invalid_char: + js_parse_error(s, "unexpected line terminator in regexp"); + } + pos += clen; + } + } + end_pos = pos - 1; + + clen = js_parse_regexp_flags(&re_flags, s->source_buf + pos); + pos += clen; + if (is_ident_next(s->source_buf[pos])) + js_parse_error(s, "invalid regular expression flags"); + + /* XXX: single char string is not optimized */ + p = js_alloc_string(ctx, end_pos - start_pos); + if (!p) + js_parse_error_mem(s); + p->is_ascii = is_ascii_string((char *)(s->source_buf + start_pos), end_pos - start_pos); + memcpy(p->buf, s->source_buf + start_pos, end_pos - start_pos); + + *ppos = pos; + s->token.val = TOK_REGEXP; + s->token.value = JS_VALUE_FROM_PTR(p); + s->token.u.regexp.re_flags = re_flags; + s->token.u.regexp.re_end_pos = end_pos; +} + +static void next_token(JSParseState *s) +{ + uint32_t pos; + const uint8_t *p; + int c; + + pos = s->buf_pos; + s->got_lf = FALSE; + s->token.value = JS_NULL; + p = s->source_buf + s->buf_pos; + redo: + s->token.source_pos = p - s->source_buf; + c = *p; + switch(c) { + case 0: + s->token.val = TOK_EOF; + break; + case '\"': + case '\'': + p++; + pos = p - s->source_buf; + s->token.value = js_parse_string(s, &pos, c); + s->token.val = TOK_STRING; + p = s->source_buf + pos; + break; + case '\n': + s->got_lf = TRUE; + p++; + goto redo; + case ' ': + case '\t': + case '\f': + case '\v': + case '\r': + p++; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0') + js_parse_error(s, "unexpected end of comment"); + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + p++; + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + for(;;) { + if (*p == '\0' || *p == '\n') + break; + p++; + } + goto redo; + } else if (is_regexp_allowed(s->token.val)) { + /* Note: we recognize regexps in the lexer. It does not + handle all the cases e.g. "({x:1} / 2)" or "a.void / 2" but + is consistent when we tokenize the input without + parsing it. */ + p++; + pos = p - s->source_buf; + js_parse_regexp_token(s, &pos); + p = s->source_buf + pos; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': + case '$': + p++; + pos = p - s->source_buf; + js_parse_ident(s, &s->token, &pos, c); + p = s->source_buf + pos; + break; + case '.': + if (is_digit(p[1])) + goto parse_number; + else + goto def_token; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit(p[1])) + goto invalid_number; + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + { + double d; + JSByteArray *tmp_arr; + pos = p - s->source_buf; + tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem)); + if (!tmp_arr) + js_parse_error_mem(s); + p = s->source_buf + pos; + d = js_atod((const char *)p, (const char **)&p, 0, + JS_ATOD_ACCEPT_BIN_OCT | JS_ATOD_ACCEPT_UNDERSCORES, + (JSATODTempMem *)tmp_arr->buf); + js_free(s->ctx, tmp_arr); + if (isnan(d)) { + invalid_number: + js_parse_error(s, "invalid number literal"); + } + s->token.val = TOK_NUMBER; + s->token.u.d = d; + } + break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else { + goto def_token; + } + break; + case '>': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_GTE; + } else if (p[1] == '>') { + if (p[2] == '>') { + if (p[3] == '=') { + p += 4; + s->token.val = TOK_SHR_ASSIGN; + } else { + p += 3; + s->token.val = TOK_SHR; + } + } else if (p[2] == '=') { + p += 3; + s->token.val = TOK_SAR_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SAR; + } + } else { + goto def_token; + } + break; + case '=': + if (p[1] == '=') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_STRICT_EQ; + } else { + p += 2; + s->token.val = TOK_EQ; + } + } else { + goto def_token; + } + break; + case '!': + if (p[1] == '=') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_STRICT_NEQ; + } else { + p += 2; + s->token.val = TOK_NEQ; + } + } else { + goto def_token; + } + break; + case '&': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_AND_ASSIGN; + } else if (p[1] == '&') { + p += 2; + s->token.val = TOK_LAND; + } else { + goto def_token; + } + break; + case '^': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_XOR_ASSIGN; + } else { + goto def_token; + } + break; + case '|': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_OR_ASSIGN; + } else if (p[1] == '|') { + p += 2; + s->token.val = TOK_LOR; + } else { + goto def_token; + } + break; + default: + if (c >= 128) { + js_parse_error(s, "unexpected character"); + } + def_token: + s->token.val = c; + p++; + break; + } + s->buf_pos = p - s->source_buf; +#if defined(DUMP_TOKEN) + dump_token(s, &s->token); +#endif +} + +/* test if the current token is a label. XXX: we assume there is no + space between the identifier and the ':' to avoid having to push + back a token */ +static BOOL is_label(JSParseState *s) +{ + return (s->token.val == TOK_IDENT && s->source_buf[s->buf_pos] == ':'); +} + +static inline uint8_t *get_byte_code(JSParseState *s) +{ + JSByteArray *arr; + arr = JS_VALUE_TO_PTR(s->byte_code); + return arr->buf; +} + +static void emit_claim_size(JSParseState *s, int n) +{ + JSValue val; + val = js_resize_byte_array(s->ctx, s->byte_code, s->byte_code_len + n); + if (JS_IsException(val)) + js_parse_error_mem(s); + s->byte_code = val; +} + +static void emit_u8(JSParseState *s, uint8_t val) +{ + JSByteArray *arr; + emit_claim_size(s, 1); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[s->byte_code_len++] = val; +} + +static void emit_u16(JSParseState *s, uint16_t val) +{ + JSByteArray *arr; + emit_claim_size(s, 2); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u16(arr->buf + s->byte_code_len, val); + s->byte_code_len += 2; +} + +static void emit_u32(JSParseState *s, uint32_t val) +{ + JSByteArray *arr; + emit_claim_size(s, 4); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + s->byte_code_len, val); + s->byte_code_len += 4; +} + +/* precondition: 1 <= n <= 25. */ +static void pc2line_put_bits_short(JSParseState *s, int n, uint32_t bits) +{ + JSFunctionBytecode *b; + JSValue val1; + JSByteArray *arr; + uint32_t index, pos; + unsigned int val; + int shift; + uint8_t *p; + + index = s->pc2line_bit_len; + pos = index >> 3; + + /* resize the array if needed */ + b = JS_VALUE_TO_PTR(s->cur_func); + val1 = js_resize_byte_array(s->ctx, b->pc2line, pos + 4); + if (JS_IsException(val1)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(s->cur_func); + b->pc2line = val1; + + arr = JS_VALUE_TO_PTR(val1); + p = arr->buf + pos; + val = get_be32(p); + shift = (32 - (index & 7) - n); + val &= ~(((1U << n) - 1) << shift); /* reset the bits */ + val |= bits << shift; + put_be32(p, val); + s->pc2line_bit_len = index + n; +} + +/* precondition: 1 <= n <= 32 */ +static void pc2line_put_bits(JSParseState *s, int n, uint32_t bits) +{ + int n_max = 25; + if (unlikely(n > n_max)) { + pc2line_put_bits_short(s, n - n_max, bits >> n_max); + bits &= (1 << n_max) - 1; + n = n_max; + } + pc2line_put_bits_short(s, n, bits); +} + +/* 0 <= v < 2^32-1 */ +static void put_ugolomb(JSParseState *s, uint32_t v) +{ + int n; + // printf("put_ugolomb: %u\n", v); + v++; + n = 32 - clz32(v); + if (n > 1) + pc2line_put_bits(s, n - 1, 0); + pc2line_put_bits(s, n, v); +} + +/* v != -2^31 */ +static void put_sgolomb(JSParseState *s, int32_t v1) +{ + uint32_t v = v1; + put_ugolomb(s, (2 * v) ^ -(v >> 31)); +} + +//#define DUMP_PC2LINE_STATS + +#ifdef DUMP_PC2LINE_STATS +static int pc2line_freq[256]; +static int pc2line_freq_tot; +#endif + +/* return the difference between the line numbers from 'pos1' to + 'pos2'. If the difference is zero, '*pcol_num' contains the + difference between the column numbers. Otherwise it contains the + zero based absolute column number. +*/ +static int get_line_col_delta(int *pcol_num, const uint8_t *buf, + int pos1, int pos2) +{ + int line_num, col_num, c, i; + line_num = 0; + col_num = 0; + if (pos2 >= pos1) { + line_num = get_line_col(&col_num, buf + pos1, pos2 - pos1); + } else { + line_num = get_line_col(&col_num, buf + pos2, pos1 - pos2); + line_num = -line_num; + col_num = -col_num; + if (line_num != 0) { + /* find the absolute column position */ + col_num = 0; + for(i = pos2 - 1; i >= 0; i--) { + c = buf[i]; + if (c == '\n') { + break; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + } + } + *pcol_num = col_num; + return line_num; +} + +static void emit_pc2line(JSParseState *s, JSSourcePos pos) +{ + int line_delta, col_delta; + + line_delta = get_line_col_delta(&col_delta, s->source_buf, + s->pc2line_source_pos, pos); + put_sgolomb(s, line_delta); + if (s->has_column) { + if (line_delta == 0) { +#ifdef DUMP_PC2LINE_STATS + pc2line_freq[min_int(max_int(col_delta + 128, 0), 255)]++; + pc2line_freq_tot++; +#endif + put_sgolomb(s, col_delta); + } else { + put_ugolomb(s, col_delta); + } + } + s->pc2line_source_pos = pos; +} + +#ifdef DUMP_PC2LINE_STATS +void dump_pc2line(void) +{ + int i; + for(i = 0; i < 256; i++) { + if (pc2line_freq[i] != 0) { + printf("%d: %d %0.2f\n", + i - 128, pc2line_freq[i], + -log2((double)pc2line_freq[i] / pc2line_freq_tot)); + } + } +} +#endif + +/* warning: pc2line info must be associated to each generated opcode */ +static void emit_op_pos(JSParseState *s, uint8_t op, JSSourcePos source_pos) +{ + s->last_opcode_pos = s->byte_code_len; + s->last_pc2line_pos = s->pc2line_bit_len; + s->last_pc2line_source_pos = s->pc2line_source_pos; + + emit_pc2line(s, source_pos); + emit_u8(s, op); +} + +static void emit_op(JSParseState *s, uint8_t op) +{ + emit_op_pos(s, op, s->pc2line_source_pos); +} + +static void emit_op_param(JSParseState *s, uint8_t op, uint32_t param, + JSSourcePos source_pos) +{ + const JSOpCode *oi; + + emit_op_pos(s, op, source_pos); + oi = &opcode_info[op]; + switch(oi->fmt) { + case OP_FMT_none: + break; + case OP_FMT_npop: + emit_u16(s, param); + break; + default: + assert(0); + } +} + +/* insert 'n' bytes at position pos */ +static void emit_insert(JSParseState *s, int pos, int n) +{ + JSByteArray *arr; + emit_claim_size(s, n); + arr = JS_VALUE_TO_PTR(s->byte_code); + memmove(arr->buf + pos + n, arr->buf + pos, s->byte_code_len - pos); + s->byte_code_len += n; +} + +static inline int get_prev_opcode(JSParseState *s) +{ + if (s->last_opcode_pos < 0) { + return OP_invalid; + } else { + uint8_t *byte_code = get_byte_code(s); + return byte_code[s->last_opcode_pos]; + } +} + +static BOOL js_is_live_code(JSParseState *s) { + switch (get_prev_opcode(s)) { + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_goto: + case OP_ret: + return FALSE; + default: + return TRUE; + } +} + +static void remove_last_op(JSParseState *s) +{ + s->byte_code_len = s->last_opcode_pos; + s->pc2line_bit_len = s->last_pc2line_pos; + s->pc2line_source_pos = s->last_pc2line_source_pos; + s->last_opcode_pos = -1; +} + +static void emit_push_short_int(JSParseState *s, int val) +{ + if (val >= -1 && val <= 7) { + emit_op(s, OP_push_0 + val); + } else if (val == (int8_t)val) { + emit_op(s, OP_push_i8); + emit_u8(s, val); + } else if (val == (int16_t)val) { + emit_op(s, OP_push_i16); + emit_u16(s, val); + } else { + emit_op(s, OP_push_value); + emit_u32(s, JS_NewShortInt(val)); + } +} + +static void emit_var(JSParseState *s, int opcode, int var_idx, + JSSourcePos source_pos) +{ + switch(opcode) { + case OP_get_loc: + if (var_idx < 4) { + emit_op_pos(s, OP_get_loc0 + var_idx, source_pos); + return; + } else if (var_idx < 256) { + emit_op_pos(s, OP_get_loc8, source_pos); + emit_u8(s, var_idx); + return; + } + break; + case OP_put_loc: + if (var_idx < 4) { + emit_op_pos(s, OP_put_loc0 + var_idx, source_pos); + return; + } else if (var_idx < 256) { + emit_op_pos(s, OP_put_loc8, source_pos); + emit_u8(s, var_idx); + return; + } + break; + case OP_get_arg: + if (var_idx < 4) { + emit_op_pos(s, OP_get_arg0 + var_idx, source_pos); + return; + } + break; + case OP_put_arg: + if (var_idx < 4) { + emit_op_pos(s, OP_put_arg0 + var_idx, source_pos); + return; + } + break; + } + emit_op_pos(s, opcode, source_pos); + emit_u16(s, var_idx); +} + + +typedef enum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_METHOD, +} JSParseFunctionEnum; + +static void js_parse_function_decl(JSParseState *s, + JSParseFunctionEnum func_type, JSValue func_name); + +/* labels are short integers so they can be used as JSValue. -1 is not + a valid label. */ +#define LABEL_RESOLVED_FLAG (1 << 29) +#define LABEL_OFFSET_MASK ((1 << 29) - 1) + +#define LABEL_NONE JS_NewShortInt(-1) + +static BOOL label_is_none(JSValue label) +{ + return JS_VALUE_GET_INT(label) < 0; +} + +static JSValue new_label(JSParseState *s) +{ + return JS_NewShortInt(LABEL_OFFSET_MASK); +} + +static void emit_label_pos(JSParseState *s, JSValue *plabel, int pos) +{ + int label; + JSByteArray *arr; + int next; + + label = JS_VALUE_GET_INT(*plabel); + assert(!(label & LABEL_RESOLVED_FLAG)); + arr = JS_VALUE_TO_PTR(s->byte_code); + while (label != LABEL_OFFSET_MASK) { + next = get_u32(arr->buf + label); + put_u32(arr->buf + label, pos - label); + label = next; + } + *plabel = JS_NewShortInt(pos | LABEL_RESOLVED_FLAG); +} + +static void emit_label(JSParseState *s, JSValue *plabel) +{ + emit_label_pos(s, plabel, s->byte_code_len); + /* prevent get_lvalue from using the last expression as an + lvalue. */ + s->last_opcode_pos = -1; +} + +static void emit_goto(JSParseState *s, int opcode, JSValue *plabel) +{ + int label; + /* XXX: generate smaller gotos when possible */ + emit_op(s, opcode); + label = JS_VALUE_GET_INT(*plabel); + if (label & LABEL_RESOLVED_FLAG) { + emit_u32(s, (label & LABEL_OFFSET_MASK) - s->byte_code_len); + } else { + emit_u32(s, label); + *plabel = JS_NewShortInt(s->byte_code_len - 4); + } +} + +/* return the constant pool index. 'val' is not duplicated. */ +static int cpool_add(JSParseState *s, JSValue val) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + JSValue new_cpool; + JSGCRef val_ref; + + b = JS_VALUE_TO_PTR(s->cur_func); + arr = JS_VALUE_TO_PTR(b->cpool); + /* check if the value is already present */ + for(i = 0; i < s->cpool_len; i++) { + if (arr->arr[i] == val) + return i; + } + + if (s->cpool_len > 65535) + js_parse_error(s, "too many constants"); + JS_PUSH_VALUE(s->ctx, val); + new_cpool = js_resize_value_array(s->ctx, b->cpool, max_int(s->cpool_len + 1, 4)); + JS_POP_VALUE(s->ctx, val); + if (JS_IsException(new_cpool)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(s->cur_func); + b->cpool = new_cpool; + arr = JS_VALUE_TO_PTR(b->cpool); + arr->arr[s->cpool_len++] = val; + return s->cpool_len - 1; +} + +static void js_emit_push_const(JSParseState *s, JSValue val) +{ + int idx; + + if (JS_IsPtr(val) +#ifdef JS_USE_SHORT_FLOAT + || JS_IsShortFloat(val) +#endif + ) { + /* We use a constant pool to avoid scanning the bytecode + during the GC. XXX: is it a good choice ? */ + idx = cpool_add(s, val); + emit_op(s, OP_push_const); + emit_u16(s, idx); + } else { + /* no GC mark */ + emit_op(s, OP_push_value); + emit_u32(s, val); + } +} + +/* return the local variable index or -1 if not found */ +static int find_func_var(JSContext *ctx, JSValue func, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + + b = JS_VALUE_TO_PTR(func); + if (b->vars == JS_NULL) + return -1; + arr = JS_VALUE_TO_PTR(b->vars); + for(i = 0; i < arr->size; i++) { + if (arr->arr[i] == name) + return i; + } + return -1; +} + +static int find_var(JSParseState *s, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + + b = JS_VALUE_TO_PTR(s->cur_func); + arr = JS_VALUE_TO_PTR(b->vars); + for(i = 0; i < s->local_vars_len; i++) { + if (arr->arr[i] == name) + return i; + } + return -1; +} + +static JSValue get_ext_var_name(JSParseState *s, int var_idx) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + + b = JS_VALUE_TO_PTR(s->cur_func); + arr = JS_VALUE_TO_PTR(b->ext_vars); + return arr->arr[2 * var_idx]; +} + +static int find_func_ext_var(JSParseState *s, JSValue func, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + int i; + + b = JS_VALUE_TO_PTR(func); + arr = JS_VALUE_TO_PTR(b->ext_vars); + for(i = 0; i < b->ext_vars_len; i++) { + if (arr->arr[2 * i] == name) + return i; + } + return -1; +} + +/* return the external variable index or -1 if not found */ +static int find_ext_var(JSParseState *s, JSValue name) +{ + return find_func_ext_var(s, s->cur_func, name); +} + +/* return the external variable index */ +static int add_func_ext_var(JSParseState *s, JSValue func, JSValue name, int decl) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + JSValue new_ext_vars; + JSGCRef name_ref, func_ref; + + b = JS_VALUE_TO_PTR(func); + if (b->ext_vars_len >= JS_MAX_LOCAL_VARS) + js_parse_error(s, "too many variable references"); + JS_PUSH_VALUE(s->ctx, func); + JS_PUSH_VALUE(s->ctx, name); + new_ext_vars = js_resize_value_array(s->ctx, b->ext_vars, max_int(b->ext_vars_len + 1, 2) * 2); + JS_POP_VALUE(s->ctx, name); + JS_POP_VALUE(s->ctx, func); + if (JS_IsException(new_ext_vars)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(func); + b->ext_vars = new_ext_vars; + arr = JS_VALUE_TO_PTR(b->ext_vars); + arr->arr[2 * b->ext_vars_len] = name; + arr->arr[2 * b->ext_vars_len + 1] = JS_NewShortInt(decl); + b->ext_vars_len++; + return b->ext_vars_len - 1; +} + +/* return the external variable index */ +static int add_ext_var(JSParseState *s, JSValue name, int decl) +{ + return add_func_ext_var(s, s->cur_func, name, decl); +} + +/* return the local variable index */ +static int add_var(JSParseState *s, JSValue name) +{ + JSFunctionBytecode *b; + JSValueArray *arr; + JSValue new_vars; + JSGCRef name_ref; + + b = JS_VALUE_TO_PTR(s->cur_func); + if (s->local_vars_len >= JS_MAX_LOCAL_VARS) + js_parse_error(s, "too many local variables"); + JS_PUSH_VALUE(s->ctx, name); + new_vars = js_resize_value_array(s->ctx, b->vars, max_int(s->local_vars_len + 1, 4)); + JS_POP_VALUE(s->ctx, name); + if (JS_IsException(new_vars)) + js_parse_error_mem(s); + b = JS_VALUE_TO_PTR(s->cur_func); + b->vars = new_vars; + arr = JS_VALUE_TO_PTR(b->vars); + arr->arr[s->local_vars_len++] = name; + return s->local_vars_len - 1; +} + +static void get_lvalue(JSParseState *s, int *popcode, + int *pvar_idx, JSSourcePos *psource_pos, BOOL keep) +{ + int opcode, var_idx; + JSSourcePos source_pos; + + /* we check the last opcode to get the lvalue type */ + opcode = get_prev_opcode(s); + switch(opcode) { + case OP_get_loc0: + case OP_get_loc1: + case OP_get_loc2: + case OP_get_loc3: + var_idx = opcode - OP_get_loc0; + opcode = OP_get_loc; + break; + case OP_get_arg0: + case OP_get_arg1: + case OP_get_arg2: + case OP_get_arg3: + var_idx = opcode - OP_get_arg0; + opcode = OP_get_arg; + break; + case OP_get_loc8: + var_idx = get_u8(get_byte_code(s) + s->last_opcode_pos + 1); + opcode = OP_get_loc; + break; + case OP_get_loc: + case OP_get_arg: + case OP_get_var_ref: + case OP_get_field: + var_idx = get_u16(get_byte_code(s) + s->last_opcode_pos + 1); + break; + case OP_get_array_el: + case OP_get_length: + var_idx = -1; + break; + default: + js_parse_error(s, "invalid lvalue"); + } + source_pos = s->pc2line_source_pos; + + /* remove the last opcode */ + remove_last_op(s); + + if (keep) { + /* get the value but keep the object/fields on the stack */ + switch(opcode) { + case OP_get_loc: + case OP_get_arg: + case OP_get_var_ref: + emit_var(s, opcode, var_idx, source_pos); + break; + case OP_get_field: + emit_op_pos(s, OP_get_field2, source_pos); + emit_u16(s, var_idx); + break; + case OP_get_length: + emit_op_pos(s, OP_get_length2, source_pos); + break; + case OP_get_array_el: + emit_op(s, OP_dup2); + emit_op_pos(s, OP_get_array_el, source_pos); /* XXX: add OP_get_array_el3 but need to modify tail call */ + break; + default: + abort(); + } + } + + *popcode = opcode; + *pvar_idx = var_idx; + *psource_pos = source_pos; +} + +typedef enum { + PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ + PUT_LVALUE_NOKEEP_TOP, /* [depth] v -> */ + PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ + PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ +} PutLValueEnum; + +static void put_lvalue(JSParseState *s, int opcode, + int var_idx, JSSourcePos source_pos, + PutLValueEnum special) +{ + switch(opcode) { + case OP_get_loc: + case OP_get_arg: + case OP_get_var_ref: + if (special == PUT_LVALUE_KEEP_TOP) + emit_op(s, OP_dup); + if (opcode == OP_get_var_ref && s->is_repl) + opcode = OP_put_var_ref_nocheck; /* an assignment defines the variable in the REPL */ + else + opcode++; + emit_var(s, opcode, var_idx, source_pos); + break; + case OP_get_field: + case OP_get_length: + switch(special) { + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert2); /* obj a -> a obj a */ + break; + case PUT_LVALUE_NOKEEP_TOP: + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_swap); /* a obj -> obj a */ + break; + default: + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm3); /* obj a b -> a obj b */ + break; + } + emit_op_pos(s, OP_put_field, source_pos); + if (opcode == OP_get_length) { + emit_u16(s, cpool_add(s, js_get_atom(s->ctx, JS_ATOM_length))); + } else { + emit_u16(s, var_idx); + } + break; + case OP_get_array_el: + switch(special) { + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert3); /* obj prop a -> a obj prop a */ + break; + case PUT_LVALUE_NOKEEP_TOP: + break; + case PUT_LVALUE_NOKEEP_BOTTOM: /* a obj prop -> obj prop a */ + emit_op(s, OP_rot3l); /* obj prop a b -> a obj prop b */ + break; + default: + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm4); /* obj prop a b -> a obj prop b */ + break; + } + emit_op_pos(s, OP_put_array_el, source_pos); + break; + default: + abort(); + } +} + +enum { + PARSE_PROP_FIELD, + PARSE_PROP_GET, + PARSE_PROP_SET, + PARSE_PROP_METHOD, +}; + +static int js_parse_property_name(JSParseState *s, JSValue *pname) +{ + JSContext *ctx = s->ctx; + JSValue name; + JSGCRef name_ref; + int prop_type; + + prop_type = PARSE_PROP_FIELD; + + if (s->token.val == TOK_IDENT) { + int is_set; + if (s->token.value == js_get_atom(ctx, JS_ATOM_get)) + is_set = 0; + else if (s->token.value == js_get_atom(ctx, JS_ATOM_set)) + is_set = 1; + else + is_set = -1; + if (is_set >= 0) { + next_token(s); + if (s->token.val == ':' || s->token.val == ',' || + s->token.val == '}' || s->token.val == '(') { + /* not a get set */ + name = js_get_atom(ctx, is_set ? JS_ATOM_set : JS_ATOM_get); + goto done; + } + prop_type = PARSE_PROP_GET + is_set; + } + } + + if (s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD) { + name = s->token.value; + } else if (s->token.val == TOK_STRING) { + name = s->token.value; + } else if (s->token.val == TOK_NUMBER) { + name = JS_NewFloat64(s->ctx, s->token.u.d); + if (JS_IsException(name)) + js_parse_error_mem(s); + } else { + js_parse_error(s, "invalid property name"); + } + name = JS_ToPropertyKey(s->ctx, name); + if (JS_IsException(name)) + js_parse_error_mem(s); + JS_PUSH_VALUE(ctx, name); + next_token(s); + JS_POP_VALUE(ctx, name); + done: + if (prop_type == PARSE_PROP_FIELD && s->token.val == '(') + prop_type = PARSE_PROP_METHOD; + *pname = name; + return prop_type; +} + +/* recursion free parser definitions */ + +#define PF_NO_IN (1 << 0) /* the 'in' operator is not accepted*/ +#define PF_DROP (1 << 1) /* drop result */ +#define PF_ACCEPT_LPAREN (1 << 2) /* js_parse_postfix_expr only */ +#define PF_LEVEL_SHIFT 4 /* optional level parameter */ +#define PF_LEVEL_MASK (0xf << PF_LEVEL_SHIFT) + +typedef enum { + PARSE_FUNC_js_parse_expr_comma, + PARSE_FUNC_js_parse_assign_expr, + PARSE_FUNC_js_parse_cond_expr, + PARSE_FUNC_js_parse_logical_and_or, + PARSE_FUNC_js_parse_expr_binary, + PARSE_FUNC_js_parse_unary, + PARSE_FUNC_js_parse_postfix_expr, + PARSE_FUNC_js_parse_statement, + PARSE_FUNC_js_parse_block, + PARSE_FUNC_js_parse_json_value, + PARSE_FUNC_re_parse_alternative, + PARSE_FUNC_re_parse_disjunction, +} ParseExprFuncEnum; + +typedef int JSParseFunc(JSParseState *s, int state, int param); + +#define PARSE_STATE_INIT 0xfe +#define PARSE_STATE_RET 0xff + +/* may trigger a gc */ +static JSValue parse_stack_alloc(JSParseState *s, JSValue val) +{ + JSGCRef val_ref; + + JS_PUSH_VALUE(s->ctx, val); + if (JS_StackCheck(s->ctx, 1)) + js_parse_error_stack_overflow(s); + JS_POP_VALUE(s->ctx, val); + return val; +} + +/* WARNING: 'val' may be modified after this val if it is a pointer */ +static void js_parse_push_val(JSParseState *s, JSValue val) +{ + JSContext *ctx = s->ctx; + if (unlikely(ctx->sp <= ctx->stack_bottom)) { + val = parse_stack_alloc(s, val); + } + *--(ctx->sp) = val; +} + +/* update the stack bottom when there is a large stack space */ +static JSValue js_parse_pop_val(JSParseState *s) +{ + JSContext *ctx = s->ctx; + JSValue val; + val = *(ctx->sp)++; + if (unlikely(ctx->sp - JS_STACK_SLACK > ctx->stack_bottom)) + ctx->stack_bottom = ctx->sp - JS_STACK_SLACK; + return val; +} + +#define PARSE_PUSH_VAL(s, v) js_parse_push_val(s, v) +#define PARSE_POP_VAL(s, v) v = js_parse_pop_val(s) + +#define PARSE_PUSH_INT(s, v) js_parse_push_val(s, JS_NewShortInt(v)) +#define PARSE_POP_INT(s, v) v = JS_VALUE_GET_INT(js_parse_pop_val(s)) + +#define PARSE_START1() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + } + +#define PARSE_START2() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + } + +#define PARSE_START3() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + case 2: goto parse_state2;\ + } + +#define PARSE_START7() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + case 2: goto parse_state2;\ + case 3: goto parse_state3; \ + case 4: goto parse_state4;\ + case 5: goto parse_state5;\ + case 6: goto parse_state6;\ + } + +#define PARSE_START12() \ + switch(state) {\ + case PARSE_STATE_INIT: break;\ + default: abort();\ + case 0: goto parse_state0;\ + case 1: goto parse_state1;\ + case 2: goto parse_state2;\ + case 3: goto parse_state3; \ + case 4: goto parse_state4;\ + case 5: goto parse_state5;\ + case 6: goto parse_state6;\ + case 7: goto parse_state7; \ + case 8: goto parse_state8;\ + case 9: goto parse_state9;\ + case 10: goto parse_state10;\ + case 11: goto parse_state11;\ + } + +/* WARNING: local variables are not preserved across PARSE_CALL(). So + they must be explicitly saved and restored */ +#define PARSE_CALL(s, cur_state, func, param) return (cur_state | (PARSE_FUNC_ ## func << 8) | ((param) << 16)); parse_state ## cur_state : ; + +/* preserve var1, ... across the call */ +#define PARSE_CALL_SAVE1(s, cur_state, func, param, var1) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE2(s, cur_state, func, param, var1, var2) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE3(s, cur_state, func, param, var1, var2, var3) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE4(s, cur_state, func, param, var1, var2, var3, var4) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_PUSH_INT(s, var4); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var4); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE5(s, cur_state, func, param, var1, var2, var3, var4, var5) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_PUSH_INT(s, var4); \ + PARSE_PUSH_INT(s, var5); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var5); \ + PARSE_POP_INT(s, var4); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +#define PARSE_CALL_SAVE6(s, cur_state, func, param, var1, var2, var3, var4, var5, var6) \ + PARSE_PUSH_INT(s, var1); \ + PARSE_PUSH_INT(s, var2); \ + PARSE_PUSH_INT(s, var3); \ + PARSE_PUSH_INT(s, var4); \ + PARSE_PUSH_INT(s, var5); \ + PARSE_PUSH_INT(s, var6); \ + PARSE_CALL(s, cur_state, func, param); \ + PARSE_POP_INT(s, var6); \ + PARSE_POP_INT(s, var5); \ + PARSE_POP_INT(s, var4); \ + PARSE_POP_INT(s, var3); \ + PARSE_POP_INT(s, var2); \ + PARSE_POP_INT(s, var1); + +static JSParseFunc *parse_func_table[]; + +static void js_parse_call(JSParseState *s, ParseExprFuncEnum func_idx, + int param) +{ + JSContext *ctx = s->ctx; + int ret, state; + JSValue *stack_top; + + stack_top = ctx->sp; + state = PARSE_STATE_INIT; + for(;;) { + ret = parse_func_table[func_idx](s, state, param); + state = ret & 0xff; + if (state == PARSE_STATE_RET) { + /* the function terminated: go back to the calling + function if any */ + if (ctx->sp == stack_top) + break; + PARSE_POP_INT(s, ret); + state = ret & 0xff; + func_idx = (ret >> 8) & 0xff; + param = -1; /* the parameter is not saved */ + } else { + /* push the call position and call another function */ + PARSE_PUSH_INT(s, state | (func_idx << 8)); + state = PARSE_STATE_INIT; + func_idx = (ret >> 8) & 0xff; + param = (ret >> 16); + } + } +} + +static BOOL may_drop_result(JSParseState *s, int parse_flags) +{ + return ((parse_flags & PF_DROP) && + (s->token.val == ';' || s->token.val == ')' || + s->token.val == ',')); +} + +static void js_emit_push_number(JSParseState *s, double d) +{ + JSValue val; + + val = JS_NewFloat64(s->ctx, d); + if (JS_IsException(val)) + js_parse_error_mem(s); + if (JS_IsInt(val)) { + emit_push_short_int(s, JS_VALUE_GET_INT(val)); + } else { + js_emit_push_const(s, val); + } +} + +static int js_parse_postfix_expr(JSParseState *s, int state, int parse_flags) +{ + BOOL is_new = FALSE; + + PARSE_START7(); + switch(s->token.val) { + case TOK_NUMBER: + js_emit_push_number(s, s->token.u.d); + next_token(s); + break; + case TOK_STRING: + { + js_emit_push_const(s, s->token.value); + next_token(s); + } + break; + case TOK_REGEXP: + { + uint32_t saved_buf_pos, saved_buf_len; + uint32_t saved_byte_code_len; + JSValue byte_code; + JSFunctionBytecode *b; + + js_emit_push_const(s, s->token.value); /* regexp source */ + + saved_buf_pos = s->buf_pos; + saved_buf_len = s->buf_len; + /* save the current bytecode back to the function */ + b = JS_VALUE_TO_PTR(s->cur_func); + b->byte_code = s->byte_code; + saved_byte_code_len = s->byte_code_len; + + /* modify the parser to parse the regexp. This way we + avoid instantiating a new JSParseState */ + /* XXX: find a better way as it relies on the regexp + parser to correctly handle the end of regexp */ + s->buf_pos = s->token.source_pos + 1; + s->buf_len = s->token.u.regexp.re_end_pos; + byte_code = js_parse_regexp(s, s->token.u.regexp.re_flags); + + s->buf_pos = saved_buf_pos; + s->buf_len = saved_buf_len; + b = JS_VALUE_TO_PTR(s->cur_func); + s->byte_code = b->byte_code; + s->byte_code_len = saved_byte_code_len; + + js_emit_push_const(s, byte_code); + emit_op(s, OP_regexp); + next_token(s); + } + break; + case '(': + next_token(s); + PARSE_CALL_SAVE1(s, 0, js_parse_expr_comma, 0, parse_flags); + js_parse_expect(s, ')'); + break; + case TOK_FUNCTION: + js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, JS_NULL); + break; + case TOK_NULL: + emit_op(s, OP_null); + next_token(s); + break; + case TOK_THIS: + emit_op(s, OP_push_this); + next_token(s); + break; + case TOK_FALSE: + case TOK_TRUE: + emit_op(s, OP_push_false + (s->token.val == TOK_TRUE)); + next_token(s); + break; + case TOK_IDENT: + { + JSFunctionBytecode *b; + JSValue name; + int var_idx, arg_count, opcode; + + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count; + + name = s->token.value; + + var_idx = find_var(s, name); + if (var_idx >= 0) { + if (var_idx < arg_count) { + opcode = OP_get_arg; + } else { + opcode = OP_get_loc; + var_idx -= arg_count; + } + } else { + var_idx = find_ext_var(s, name); + if (var_idx < 0) { + var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 0); + } + opcode = OP_get_var_ref; + } + emit_var(s, opcode, var_idx, s->token.source_pos); + next_token(s); + } + break; + case '{': + { + JSValue name; + int prop_idx, prop_type, count_pos; + BOOL has_proto; + + next_token(s); + emit_op(s, OP_object); + count_pos = s->byte_code_len; + emit_u16(s, 0); + + has_proto = FALSE; + while (s->token.val != '}') { + prop_type = js_parse_property_name(s, &name); + if (prop_type == PARSE_PROP_FIELD && + name == js_get_atom(s->ctx, JS_ATOM___proto__)) { + if (has_proto) + js_parse_error(s, "duplicate __proto__ property name"); + has_proto = TRUE; + prop_idx = -1; + } else { + uint8_t *byte_code; + int count; + prop_idx = cpool_add(s, name); + /* increment the count */ + byte_code = get_byte_code(s); + count = get_u16(byte_code + count_pos); + put_u16(byte_code + count_pos, min_int(count + 1, 0xffff)); + } + if (prop_type == PARSE_PROP_FIELD) { + js_parse_expect(s, ':'); + PARSE_CALL_SAVE4(s, 1, js_parse_assign_expr, 0, prop_idx, parse_flags, has_proto, count_pos); + if (prop_idx >= 0) { + emit_op(s, OP_define_field); + emit_u16(s, prop_idx); + } else { + emit_op(s, OP_set_proto); + } + } else { + /* getter/setter/method */ + js_parse_function_decl(s, JS_PARSE_FUNC_METHOD, name); + if (prop_type == PARSE_PROP_METHOD) + emit_op(s, OP_define_field); + else if (prop_type == PARSE_PROP_GET) + emit_op(s, OP_define_getter); + else + emit_op(s, OP_define_setter); + emit_u16(s, prop_idx); + } + if (s->token.val != ',') + break; + next_token(s); + } + js_parse_expect(s, '}'); + } + break; + case '[': + { + uint32_t idx; + + next_token(s); + /* small regular arrays are created on the stack */ + idx = 0; + while (s->token.val != ']' && idx < 32) { + /* SPEC: we don't accept empty elements */ + PARSE_CALL_SAVE2(s, 2, js_parse_assign_expr, 0, idx, parse_flags); + idx++; + /* accept trailing comma */ + if (s->token.val == ',') { + next_token(s); + } else if (s->token.val != ']') { + goto done; + } + } + + emit_op_param(s, OP_array_from, idx, s->pc2line_source_pos); + + while (s->token.val != ']') { + if (idx >= JS_SHORTINT_MAX) + js_parse_error(s, "too many elements"); + emit_op(s, OP_dup); + emit_push_short_int(s, idx); + PARSE_CALL_SAVE2(s, 3, js_parse_assign_expr, 0, idx, parse_flags); + emit_op(s, OP_put_array_el); + idx++; + /* accept trailing comma */ + if (s->token.val == ',') { + next_token(s); + } + } + done: + js_parse_expect(s, ']'); + } + break; + case TOK_NEW: + next_token(s); + if (s->token.val == '.') { + next_token(s); + if (s->token.val != TOK_IDENT || + s->token.value != js_get_atom(s->ctx, JS_ATOM_target)) { + js_parse_error(s, "expecting target"); + } + next_token(s); + emit_op(s, OP_new_target); + } else { + PARSE_CALL_SAVE1(s, 4, js_parse_postfix_expr, 0, parse_flags); + if (s->token.val != '(') { + /* new operator on an object */ + emit_op_param(s, OP_call_constructor, 0, s->token.source_pos); + } else { + is_new = TRUE; + break; + } + } + break; + default: + js_parse_error(s, "unexpected character in expression"); + } + + for(;;) { + if (s->token.val == '(' && (parse_flags & PF_ACCEPT_LPAREN)) { + int opcode, arg_count; + uint8_t *byte_code; + JSSourcePos op_source_pos; + + /* function call */ + op_source_pos = s->token.source_pos; + next_token(s); + + if (!is_new) { + opcode = get_prev_opcode(s); + byte_code = get_byte_code(s); + switch(opcode) { + case OP_get_field: + byte_code[s->last_opcode_pos] = OP_get_field2; + break; + case OP_get_length: + byte_code[s->last_opcode_pos] = OP_get_length2; + break; + case OP_get_array_el: + byte_code[s->last_opcode_pos] = OP_get_array_el2; + break; + case OP_get_var_ref: + { + int var_idx = get_u16(byte_code + s->last_opcode_pos + 1); + if (get_ext_var_name(s, var_idx) == js_get_atom(s->ctx, JS_ATOM_eval)) { + js_parse_error(s, "direct eval is not supported. Use (1,eval) instead for indirect eval"); + } + } + /* fall thru */ + default: + opcode = OP_invalid; + break; + } + } else { + opcode = OP_invalid; + } + + arg_count = 0; + if (s->token.val != ')') { + for(;;) { + if (arg_count >= JS_MAX_ARGC) + js_parse_error(s, "too many call arguments"); + arg_count++; + PARSE_CALL_SAVE5(s, 5, js_parse_assign_expr, 0, + parse_flags, arg_count, opcode, is_new, op_source_pos); + if (s->token.val == ')') + break; + js_parse_expect(s, ','); + } + } + next_token(s); + if (opcode == OP_get_field || + opcode == OP_get_length || + opcode == OP_get_array_el) { + emit_op_param(s, OP_call_method, arg_count, op_source_pos); + } else { + if (is_new) { + emit_op_param(s, OP_call_constructor, arg_count, op_source_pos); + } else { + emit_op_param(s, OP_call, arg_count, op_source_pos); + } + } + is_new = FALSE; + } else if (s->token.val == '.') { + JSSourcePos op_source_pos; + int prop_idx; + + op_source_pos = s->token.source_pos; + next_token(s); + if (!(s->token.val == TOK_IDENT || s->token.val >= TOK_FIRST_KEYWORD)) { + js_parse_error(s, "expecting field name"); + } + /* we ensure that no numeric property is used with + OP_get_field to enable some optimizations. The only + possible identifiers are NaN and Infinity */ + if (s->token.value == js_get_atom(s->ctx, JS_ATOM_NaN) || + s->token.value == js_get_atom(s->ctx, JS_ATOM_Infinity)) { + js_emit_push_const(s, s->token.value); + emit_op_pos(s, OP_get_array_el, op_source_pos); + } else if (s->token.value == js_get_atom(s->ctx, JS_ATOM_length)) { + emit_op_pos(s, OP_get_length, op_source_pos); + } else { + prop_idx = cpool_add(s, s->token.value); + emit_op_pos(s, OP_get_field, op_source_pos); + emit_u16(s, prop_idx); + } + next_token(s); + } else if (s->token.val == '[') { + JSSourcePos op_source_pos; + op_source_pos = s->token.source_pos; + next_token(s); + PARSE_CALL_SAVE3(s, 6, js_parse_expr_comma, 0, + parse_flags, is_new, op_source_pos); + js_parse_expect(s, ']'); + emit_op_pos(s, OP_get_array_el, op_source_pos); + } else if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { + int opcode, op, var_idx; + JSSourcePos op_source_pos, source_pos; + + op = s->token.val; + op_source_pos = s->token.source_pos; + next_token(s); + get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE); + if (may_drop_result(s, parse_flags)) { + s->dropped_result = TRUE; + emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos); + put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_NOKEEP_TOP); + } else { + emit_op_pos(s, OP_post_dec + op - TOK_DEC, op_source_pos); + put_lvalue(s, opcode, var_idx, source_pos, PUT_LVALUE_KEEP_SECOND); + } + } else { + break; + } + } + return PARSE_STATE_RET; +} + +static void js_emit_delete(JSParseState *s) +{ + int opcode; + + opcode = get_prev_opcode(s); + switch(opcode) { + case OP_get_field: + { + JSByteArray *byte_code; + int prop_idx; + byte_code = JS_VALUE_TO_PTR(s->byte_code); + prop_idx = get_u16(byte_code->buf + s->last_opcode_pos + 1); + remove_last_op(s); + emit_op(s, OP_push_const); + emit_u16(s, prop_idx); + } + break; + case OP_get_length: + remove_last_op(s); + js_emit_push_const(s, js_get_atom(s->ctx, JS_ATOM_length)); + break; + case OP_get_array_el: + remove_last_op(s); + break; + default: + js_parse_error(s, "invalid lvalue for delete"); + } + emit_op(s, OP_delete); +} + +static int js_parse_unary(JSParseState *s, int state, int parse_flags) +{ + PARSE_START7(); + + switch(s->token.val) { + case '+': + case '-': + case '!': + case '~': + { + int op; + JSSourcePos op_source_pos; + + op = s->token.val; + op_source_pos = s->token.source_pos; + next_token(s); + + /* XXX: could handle more cases */ + if (s->token.val == TOK_NUMBER && (op == '-' || op == '+')) { + double d = s->token.u.d; + if (op == '-') + d = -d; + js_emit_push_number(s, d); + next_token(s); + } else { + PARSE_CALL_SAVE2(s, 0, js_parse_unary, 0, op, op_source_pos); + switch(op) { + case '-': + emit_op_pos(s, OP_neg, op_source_pos); + break; + case '+': + emit_op_pos(s, OP_plus, op_source_pos); + break; + case '!': + emit_op_pos(s, OP_lnot, op_source_pos); + break; + case '~': + emit_op_pos(s, OP_not, op_source_pos); + break; + default: + abort(); + } + } + } + break; + case TOK_VOID: + next_token(s); + PARSE_CALL(s, 1, js_parse_unary, 0); + emit_op(s, OP_drop); + emit_op(s, OP_undefined); + break; + case TOK_DEC: + case TOK_INC: + { + int opcode, op, var_idx; + PutLValueEnum special; + JSSourcePos op_source_pos, source_pos; + + op = s->token.val; + op_source_pos = s->token.source_pos; + next_token(s); + PARSE_CALL_SAVE3(s, 2, js_parse_unary, 0, op, parse_flags, op_source_pos); + get_lvalue(s, &opcode, &var_idx, &source_pos, TRUE); + emit_op_pos(s, OP_dec + op - TOK_DEC, op_source_pos); + + if (may_drop_result(s, parse_flags)) { + special = PUT_LVALUE_NOKEEP_TOP; + s->dropped_result = TRUE; + } else { + special = PUT_LVALUE_KEEP_TOP; + } + put_lvalue(s, opcode, var_idx, source_pos, special); + } + break; + case TOK_TYPEOF: + { + next_token(s); + PARSE_CALL(s, 3, js_parse_unary, 0); + /* access to undefined variable should not return an + exception, so we patch the get_var */ + if (get_prev_opcode(s) == OP_get_var_ref) { + uint8_t *byte_code = get_byte_code(s); + byte_code[s->last_opcode_pos] = OP_get_var_ref_nocheck; + } + emit_op(s, OP_typeof); + } + break; + case TOK_DELETE: + next_token(s); + PARSE_CALL(s, 4, js_parse_unary, 0); + js_emit_delete(s); + break; + default: + PARSE_CALL(s, 5, js_parse_postfix_expr, parse_flags | PF_ACCEPT_LPAREN); + /* XXX: we do not follow the ES7 grammar in order to have a + * more natural expression */ + if (s->token.val == TOK_POW) { + JSSourcePos op_source_pos; + op_source_pos = s->token.source_pos; + next_token(s); + PARSE_CALL_SAVE1(s, 6, js_parse_unary, 0, op_source_pos); + emit_op_pos(s, OP_pow, op_source_pos); + } + break; + } + return PARSE_STATE_RET; +} + +static int js_parse_expr_binary(JSParseState *s, int state, int parse_flags) +{ + int op, opcode, level; + JSSourcePos op_source_pos; + + PARSE_START3(); + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 0) { + PARSE_CALL(s, 0, js_parse_unary, parse_flags); + return PARSE_STATE_RET; + } + PARSE_CALL_SAVE1(s, 1, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags); + parse_flags &= ~PF_DROP; + for(;;) { + op = s->token.val; + op_source_pos = s->token.source_pos; + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + switch(level) { + case 1: + switch(op) { + case '*': + opcode = OP_mul; + break; + case '/': + opcode = OP_div; + break; + case '%': + opcode = OP_mod; + break; + default: + return PARSE_STATE_RET; + } + break; + case 2: + switch(op) { + case '+': + opcode = OP_add; + break; + case '-': + opcode = OP_sub; + break; + default: + return PARSE_STATE_RET; + } + break; + case 3: + switch(op) { + case TOK_SHL: + opcode = OP_shl; + break; + case TOK_SAR: + opcode = OP_sar; + break; + case TOK_SHR: + opcode = OP_shr; + break; + default: + return PARSE_STATE_RET; + } + break; + case 4: + switch(op) { + case '<': + opcode = OP_lt; + break; + case '>': + opcode = OP_gt; + break; + case TOK_LTE: + opcode = OP_lte; + break; + case TOK_GTE: + opcode = OP_gte; + break; + case TOK_INSTANCEOF: + opcode = OP_instanceof; + break; + case TOK_IN: + if (!(parse_flags & PF_NO_IN)) { + opcode = OP_in; + } else { + return PARSE_STATE_RET; + } + break; + default: + return PARSE_STATE_RET; + } + break; + case 5: + switch(op) { + case TOK_EQ: + opcode = OP_eq; + break; + case TOK_NEQ: + opcode = OP_neq; + break; + case TOK_STRICT_EQ: + opcode = OP_strict_eq; + break; + case TOK_STRICT_NEQ: + opcode = OP_strict_neq; + break; + default: + return PARSE_STATE_RET; + } + break; + case 6: + switch(op) { + case '&': + opcode = OP_and; + break; + default: + return PARSE_STATE_RET; + } + break; + case 7: + switch(op) { + case '^': + opcode = OP_xor; + break; + default: + return PARSE_STATE_RET; + } + break; + case 8: + switch(op) { + case '|': + opcode = OP_or; + break; + default: + return PARSE_STATE_RET; + } + break; + default: + abort(); + } + next_token(s); + PARSE_CALL_SAVE3(s, 2, js_parse_expr_binary, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags, opcode, op_source_pos); + emit_op_pos(s, opcode, op_source_pos); + } + return PARSE_STATE_RET; +} + +static int js_parse_logical_and_or(JSParseState *s, int state, int parse_flags) +{ + JSValue label1; + int level, op; + + PARSE_START3(); + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 0) { + PARSE_CALL(s, 0, js_parse_expr_binary, (parse_flags & ~PF_LEVEL_MASK) | (8 << PF_LEVEL_SHIFT)); + return PARSE_STATE_RET; + } + + PARSE_CALL_SAVE1(s, 1, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags); + + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 1) + op = TOK_LAND; + else + op = TOK_LOR; + parse_flags &= ~PF_DROP; + if (s->token.val == op) { + label1 = new_label(s); + + for(;;) { + next_token(s); + emit_op(s, OP_dup); + emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, &label1); + emit_op(s, OP_drop); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags - (1 << PF_LEVEL_SHIFT), parse_flags); + PARSE_POP_VAL(s, label1); + + level = (parse_flags & PF_LEVEL_MASK) >> PF_LEVEL_SHIFT; + if (level == 1) + op = TOK_LAND; + else + op = TOK_LOR; + + if (s->token.val != op) + break; + } + + emit_label(s, &label1); + } + return PARSE_STATE_RET; +} + +static int js_parse_cond_expr(JSParseState *s, int state, int parse_flags) +{ + JSValue label1, label2; + + PARSE_START3(); + + PARSE_CALL_SAVE1(s, 2, js_parse_logical_and_or, parse_flags | (2 << PF_LEVEL_SHIFT), parse_flags); + + parse_flags &= ~PF_DROP; + if (s->token.val == '?') { + next_token(s); + label1 = new_label(s); + emit_goto(s, OP_if_false, &label1); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL_SAVE1(s, 0, js_parse_assign_expr, parse_flags, + parse_flags); + PARSE_POP_VAL(s, label1); + + label2 = new_label(s); + emit_goto(s, OP_goto, &label2); + + js_parse_expect(s, ':'); + + emit_label(s, &label1); + + PARSE_PUSH_VAL(s, label2); + PARSE_CALL_SAVE1(s, 1, js_parse_assign_expr, parse_flags, + parse_flags); + PARSE_POP_VAL(s, label2); + + emit_label(s, &label2); + } + return PARSE_STATE_RET; +} + +static int js_parse_assign_expr(JSParseState *s, int state, int parse_flags) +{ + int opcode, op, var_idx; + PutLValueEnum special; + JSSourcePos op_source_pos, source_pos; + + PARSE_START2(); + + PARSE_CALL_SAVE1(s, 1, js_parse_cond_expr, parse_flags, parse_flags); + + op = s->token.val; + if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_OR_ASSIGN)) { + op_source_pos = s->token.source_pos; + next_token(s); + get_lvalue(s, &opcode, &var_idx, &source_pos, (op != '=')); + + PARSE_CALL_SAVE6(s, 0, js_parse_assign_expr, parse_flags & ~PF_DROP, + op, opcode, var_idx, parse_flags, + op_source_pos, source_pos); + + if (op != '=') { + static const uint8_t assign_opcodes[] = { + OP_mul, OP_div, OP_mod, OP_add, OP_sub, + OP_shl, OP_sar, OP_shr, OP_and, OP_xor, OP_or, + OP_pow, + }; + emit_op_pos(s, assign_opcodes[op - TOK_MUL_ASSIGN], op_source_pos); + } + + if (may_drop_result(s, parse_flags)) { + special = PUT_LVALUE_NOKEEP_TOP; + s->dropped_result = TRUE; + } else { + special = PUT_LVALUE_KEEP_TOP; + } + put_lvalue(s, opcode, var_idx, source_pos, special); + } + return PARSE_STATE_RET; +} + +static int js_parse_expr_comma(JSParseState *s, int state, int parse_flags) +{ + BOOL comma = FALSE; + + PARSE_START1(); + + for(;;) { + s->dropped_result = FALSE; + PARSE_CALL_SAVE2(s, 0, js_parse_assign_expr, parse_flags, + comma, parse_flags); + if (comma) { + /* prevent get_lvalue from using the last expression as an + lvalue. */ + s->last_opcode_pos = -1; + } + if (s->token.val != ',') + break; + comma = TRUE; + if (!s->dropped_result) + emit_op(s, OP_drop); + next_token(s); + } + if ((parse_flags & PF_DROP) && !s->dropped_result) { + emit_op(s, OP_drop); + } + return PARSE_STATE_RET; +} + +static void js_parse_assign_expr2(JSParseState *s, int parse_flags) +{ + js_parse_call(s, PARSE_FUNC_js_parse_assign_expr, parse_flags); +} + +static void js_parse_expr2(JSParseState *s, int parse_flags) +{ + js_parse_call(s, PARSE_FUNC_js_parse_expr_comma, parse_flags); +} + +static void js_parse_expr(JSParseState *s) +{ + js_parse_expr2(s, 0); +} + +static void js_parse_expr_paren(JSParseState *s) +{ + js_parse_expect(s, '('); + js_parse_expr(s); + js_parse_expect(s, ')'); +} + +static BlockEnv *push_break_entry(JSParseState *s, JSValue label_name, + JSValue label_break, JSValue label_cont, + int drop_count) +{ + JSContext *ctx = s->ctx; + JSGCRef label_name_ref; + int ret, block_env_len; + BlockEnv *be; + + block_env_len = sizeof(BlockEnv) / sizeof(JSValue); + JS_PUSH_VALUE(ctx, label_name); + ret = JS_StackCheck(ctx, block_env_len); + JS_POP_VALUE(ctx, label_name); + if (ret) + js_parse_error_stack_overflow(s); + ctx->sp -= block_env_len; + be = (BlockEnv *)ctx->sp; + be->prev = s->top_break; + s->top_break = SP_TO_VALUE(ctx, be); + be->label_name = label_name; + be->label_break = label_break; + be->label_cont = label_cont; + be->label_finally = LABEL_NONE; + be->drop_count = JS_NewShortInt(drop_count); + return be; +} + +static void pop_break_entry(JSParseState *s) +{ + JSContext *ctx = s->ctx; + BlockEnv *be; + + be = VALUE_TO_SP(ctx, s->top_break); + s->top_break = be->prev; + ctx->sp += sizeof(BlockEnv) / sizeof(JSValue); + ctx->stack_bottom = ctx->sp; +} + +static void emit_return(JSParseState *s, BOOL hasval, JSSourcePos source_pos) +{ + JSValue top_val; + BlockEnv *top; + int i, drop_count; + + drop_count = 0; + top_val = s->top_break; + while (!JS_IsNull(top_val)) { + top = VALUE_TO_SP(s->ctx, top_val); + /* no need to drop if no "finally" */ + drop_count += JS_VALUE_GET_INT(top->drop_count); + + if (!label_is_none(top->label_finally)) { + if (!hasval) { + emit_op(s, OP_undefined); + hasval = TRUE; + } + for(i = 0; i < drop_count; i++) + emit_op(s, OP_nip); /* must keep the stack stop */ + drop_count = 0; + /* execute the "finally" block */ + emit_goto(s, OP_gosub, &top->label_finally); + } + top_val = top->prev; + } + emit_op_pos(s, hasval ? OP_return : OP_return_undef, source_pos); +} + +static void emit_break(JSParseState *s, JSValue label_name, int is_cont) +{ + JSValue top_val; + BlockEnv *top; + int i; + JSValue *plabel; + JSGCRef label_name_ref; + BOOL is_labelled_stmt; + + top_val = s->top_break; + while (!JS_IsNull(top_val)) { + top = VALUE_TO_SP(s->ctx, top_val); + is_labelled_stmt = (top->label_cont == LABEL_NONE && + JS_VALUE_GET_INT(top->drop_count) == 0); + if ((label_name == JS_NULL && !is_labelled_stmt) || + top->label_name == label_name) { + if (is_cont) + plabel = &top->label_cont; + else + plabel = &top->label_break; + if (!label_is_none(*plabel)) { + emit_goto(s, OP_goto, plabel); + return; + } + } + JS_PUSH_VALUE(s->ctx, label_name); + for(i = 0; i < JS_VALUE_GET_INT(top->drop_count); i++) + emit_op(s, OP_drop); + if (!label_is_none(top->label_finally)) { + /* must push dummy value to keep same stack depth */ + emit_op(s, OP_undefined); + emit_goto(s, OP_gosub, &top->label_finally); + emit_op(s, OP_drop); + } + JS_POP_VALUE(s->ctx, label_name); + top_val = top->prev; + } + if (label_name == JS_NULL) { + if (is_cont) + js_parse_error(s, "continue must be inside loop"); + else + js_parse_error(s, "break must be inside loop or switch"); + } else { + js_parse_error(s, "break/continue label not found"); + } +} + +static int define_var(JSParseState *s, JSVarRefKindEnum *pvar_kind, JSValue name) +{ + JSVarRefKindEnum var_kind; + int var_idx; + + if (s->is_eval) { + var_idx = find_ext_var(s, name); + if (var_idx < 0) { + var_idx = add_ext_var(s, name, (JS_VARREF_KIND_GLOBAL << 16) | 1); + } else { + JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func); + JSValueArray *arr = JS_VALUE_TO_PTR(b->ext_vars); + arr->arr[2 * var_idx + 1] = JS_NewShortInt((JS_VARREF_KIND_GLOBAL << 16) | 1); + } + var_kind = JS_VARREF_KIND_VAR_REF; + } else { + JSFunctionBytecode *b; + int arg_count; + + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count; + + var_idx = find_var(s, name); + if (var_idx >= 0) { + if (var_idx < arg_count) { + var_kind = JS_VARREF_KIND_ARG; + } else { + var_kind = JS_VARREF_KIND_VAR; + var_idx -= arg_count; + } + } else { + var_idx = add_var(s, name); + var_kind = JS_VARREF_KIND_VAR; + var_idx -= arg_count; + } + } + *pvar_kind = var_kind; + return var_idx; +} + +static void put_var(JSParseState *s, JSVarRefKindEnum var_kind, int var_idx, JSSourcePos source_pos) +{ + int opcode; + if (var_kind == JS_VARREF_KIND_ARG) + opcode = OP_put_arg; + else if (var_kind == JS_VARREF_KIND_VAR) + opcode = OP_put_loc; + else + opcode = OP_put_var_ref_nocheck; + emit_var(s, opcode, var_idx, source_pos); +} + +static void js_parse_var(JSParseState *s, BOOL in_accepted) +{ + JSVarRefKindEnum var_kind; + int var_idx; + JSSourcePos ident_source_pos; + + for(;;) { + ident_source_pos = s->token.source_pos; + if (s->token.val != TOK_IDENT) + js_parse_error(s, "variable name expected"); + if (s->token.value == js_get_atom(s->ctx, JS_ATOM_arguments)) + js_parse_error(s, "invalid variable name"); + var_idx = define_var(s, &var_kind, s->token.value); + next_token(s); + if (s->token.val == '=') { + next_token(s); + js_parse_assign_expr2(s, in_accepted ? 0 : PF_NO_IN); + put_var(s, var_kind, var_idx, ident_source_pos); + } + if (s->token.val != ',') + break; + next_token(s); + } +} + +static void set_eval_ret_undefined(JSParseState *s) +{ + if (s->eval_ret_idx >= 0) { + emit_op(s, OP_undefined); + emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos); + } +} + +static int js_parse_block(JSParseState *s, int state, int dummy_param) +{ + PARSE_START1(); + js_parse_expect(s, '{'); + if (s->token.val != '}') { + for(;;) { + PARSE_CALL(s, 0, js_parse_statement, 0); + if (s->token.val == '}') + break; + } + } + next_token(s); + return PARSE_STATE_RET; +} + +/* The statement parser assumes that the stack contains the result of + the last statement. Note: if not in eval code, the return value of + a statement does not matter */ +static int js_parse_statement(JSParseState *s, int state, int dummy_param) +{ + JSValue label_name; + JSGCRef label_name_ref; + + PARSE_START12(); + + /* specific label handling */ + if (is_label(s)) { + JSValue top_val; + BlockEnv *top; + + label_name = s->token.value; + JS_PUSH_VALUE(s->ctx, label_name); + next_token(s); + js_parse_expect(s, ':'); + JS_POP_VALUE(s->ctx, label_name); + + for(top_val = s->top_break; !JS_IsNull(top_val); top_val = top->prev) { + top = VALUE_TO_SP(s->ctx, top_val); + if (top->label_name == label_name) + js_parse_error(s, "duplicate label name"); + } + + if (s->token.val != TOK_FOR && + s->token.val != TOK_DO && + s->token.val != TOK_WHILE) { + /* labelled regular statement */ + BlockEnv *be; + push_break_entry(s, label_name, new_label(s), LABEL_NONE, 0); + + PARSE_CALL(s, 11, js_parse_statement, 0); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_break); + pop_break_entry(s); + goto done; + } + } else { + label_name = JS_NULL; + } + + switch(s->token.val) { + case '{': + PARSE_CALL(s, 0, js_parse_block, 0); + break; + case TOK_RETURN: + { + BOOL has_val; + JSSourcePos op_source_pos; + if (s->is_eval) + js_parse_error(s, "return not in a function"); + op_source_pos = s->token.source_pos; + next_token(s); + if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { + js_parse_expr(s); + has_val = TRUE; + } else { + has_val = FALSE; + } + emit_return(s, has_val, op_source_pos); + js_parse_expect_semi(s); + } + break; + case TOK_THROW: + { + JSSourcePos op_source_pos; + op_source_pos = s->token.source_pos; + next_token(s); + if (s->got_lf) + js_parse_error(s, "line terminator not allowed after throw"); + js_parse_expr(s); + emit_op_pos(s, OP_throw, op_source_pos); + js_parse_expect_semi(s); + } + break; + case TOK_VAR: + next_token(s); + js_parse_var(s, TRUE); + js_parse_expect_semi(s); + break; + case TOK_IF: + { + JSValue label1, label2; + next_token(s); + set_eval_ret_undefined(s); + js_parse_expr_paren(s); + label1 = new_label(s); + emit_goto(s, OP_if_false, &label1); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL(s, 1, js_parse_statement, 0); + PARSE_POP_VAL(s, label1); + + if (s->token.val == TOK_ELSE) { + next_token(s); + + label2 = new_label(s); + emit_goto(s, OP_goto, &label2); + + emit_label(s, &label1); + + PARSE_PUSH_VAL(s, label2); + PARSE_CALL(s, 2, js_parse_statement, 0); + PARSE_POP_VAL(s, label2); + + label1 = label2; + } + emit_label(s, &label1); + } + break; + case TOK_WHILE: + { + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), new_label(s), 0); + next_token(s); + + set_eval_ret_undefined(s); + + emit_label(s, &be->label_cont); + js_parse_expr_paren(s); + emit_goto(s, OP_if_false, &be->label_break); + + PARSE_CALL(s, 3, js_parse_statement, 0); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_goto(s, OP_goto, &be->label_cont); + + emit_label(s, &be->label_break); + + pop_break_entry(s); + } + break; + case TOK_DO: + { + JSValue label1; + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), new_label(s), 0); + + label1 = new_label(s); + + next_token(s); + set_eval_ret_undefined(s); + + emit_label(s, &label1); + + PARSE_PUSH_VAL(s, label1); + PARSE_CALL(s, 4, js_parse_statement, 0); + PARSE_POP_VAL(s, label1); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_cont); + js_parse_expect(s, TOK_WHILE); + js_parse_expr_paren(s); + /* Insert semicolon if missing */ + if (s->token.val == ';') { + next_token(s); + } + emit_goto(s, OP_if_true, &label1); + + emit_label(s, &be->label_break); + + pop_break_entry(s); + } + break; + case TOK_FOR: + { + int bits; + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), new_label(s), 0); + + next_token(s); + set_eval_ret_undefined(s); + + js_parse_expect1(s, '('); + bits = js_parse_skip_parens_token(s); + next_token(s); + + if (!(bits & SKIP_HAS_SEMI)) { + JSValue label_expr, label_body, label_next; + int opcode, var_idx; + + be->drop_count = JS_NewShortInt(1); + + label_expr = new_label(s); + label_body = new_label(s); + label_next = new_label(s); + + emit_goto(s, OP_goto, &label_expr); + + emit_label(s, &label_next); + + if (s->token.val == TOK_VAR) { + JSVarRefKindEnum var_kind; + next_token(s); + var_idx = define_var(s, &var_kind, s->token.value); + put_var(s, var_kind, var_idx, s->pc2line_source_pos); + + next_token(s); + } else { + JSSourcePos source_pos; + + /* XXX: js_parse_left_hand_side_expr */ + js_parse_assign_expr2(s, PF_NO_IN); + + get_lvalue(s, &opcode, &var_idx, &source_pos, FALSE); + put_lvalue(s, opcode, var_idx, source_pos, + PUT_LVALUE_NOKEEP_BOTTOM); + } + + emit_goto(s, OP_goto, &label_body); + + if (s->token.val == TOK_IN) { + opcode = OP_for_in_start; + } else if (s->token.val == TOK_IDENT && + s->token.value == js_get_atom(s->ctx, JS_ATOM_of)) { + opcode = OP_for_of_start; + } else { + js_parse_error(s, "expected 'of' or 'in' in for control expression"); + } + + next_token(s); + + emit_label(s, &label_expr); + js_parse_expr(s); + emit_op(s, opcode); + + emit_goto(s, OP_goto, &be->label_cont); + + js_parse_expect(s, ')'); + + emit_label(s, &label_body); + + PARSE_PUSH_VAL(s, label_next); + PARSE_CALL(s, 5, js_parse_statement, 0); + PARSE_POP_VAL(s, label_next); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_cont); + emit_op(s, OP_for_of_next); + + /* on stack: enum_rec / enum_obj value bool */ + emit_goto(s, OP_if_false, &label_next); + /* drop the undefined value from for_xx_next */ + emit_op(s, OP_drop); + + emit_label(s, &be->label_break); + emit_op(s, OP_drop); + } else { + JSValue label_test; + JSParsePos expr3_pos; + int tmp_val; + + /* initial expression */ + if (s->token.val != ';') { + if (s->token.val == TOK_VAR) { + next_token(s); + js_parse_var(s, FALSE); + } else { + js_parse_expr2(s, PF_NO_IN | PF_DROP); + } + } + js_parse_expect(s, ';'); + + label_test = new_label(s); + + /* test expression */ + emit_label(s, &label_test); + if (s->token.val != ';') { + js_parse_expr(s); + emit_goto(s, OP_if_false, &be->label_break); + } + js_parse_expect(s, ';'); + + if (s->token.val != ')') { + /* skip the third expression if present */ + js_parse_get_pos(s, &expr3_pos); + js_skip_expr(s); + } else { + expr3_pos.source_pos = -1; + expr3_pos.got_lf = 0; /* avoid warning */ + expr3_pos.regexp_allowed = 0; /* avoid warning */ + } + js_parse_expect(s, ')'); + + PARSE_PUSH_VAL(s, label_test); + PARSE_PUSH_INT(s, expr3_pos.got_lf | (expr3_pos.regexp_allowed << 1)); + PARSE_PUSH_INT(s, expr3_pos.source_pos); + PARSE_CALL(s, 6, js_parse_statement, 0); + PARSE_POP_INT(s, expr3_pos.source_pos); + PARSE_POP_INT(s, tmp_val); + expr3_pos.got_lf = tmp_val & 1; + expr3_pos.regexp_allowed = tmp_val >> 1; + PARSE_POP_VAL(s, label_test); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_cont); + + /* parse the third expression, if present, after the + statement */ + if (expr3_pos.source_pos != -1) { + JSParsePos end_pos; + js_parse_get_pos(s, &end_pos); + js_parse_seek_token(s, &expr3_pos); + js_parse_expr2(s, PF_DROP); + js_parse_seek_token(s, &end_pos); + } + + emit_goto(s, OP_goto, &label_test); + + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_break); + } + pop_break_entry(s); + } + break; + case TOK_BREAK: + case TOK_CONTINUE: + { + int is_cont = (s->token.val == TOK_CONTINUE); + JSValue label_name; + + next_token(s); + if (!s->got_lf && s->token.val == TOK_IDENT) + label_name = s->token.value; + else + label_name = JS_NULL; + emit_break(s, label_name, is_cont); + if (label_name != JS_NULL) { + next_token(s); + } + js_parse_expect_semi(s); + } + break; + case TOK_SWITCH: + { + JSValue label_case; + int default_label_pos; + BlockEnv *be; + + be = push_break_entry(s, label_name, new_label(s), LABEL_NONE, 1); + + next_token(s); + set_eval_ret_undefined(s); + + js_parse_expr_paren(s); + + js_parse_expect(s, '{'); + default_label_pos = -1; + label_case = LABEL_NONE; /* label to the next case */ + while (s->token.val != '}') { + if (s->token.val == TOK_CASE) { + JSValue label1 = LABEL_NONE; + if (!label_is_none(label_case)) { + /* skip the case if needed */ + label1 = new_label(s); + emit_goto(s, OP_goto, &label1); + emit_label(s, &label_case); + label_case = LABEL_NONE; + } + for (;;) { + /* parse a sequence of case clauses */ + next_token(s); + emit_op(s, OP_dup); + js_parse_expr(s); + js_parse_expect(s, ':'); + emit_op(s, OP_strict_eq); + if (s->token.val == TOK_CASE) { + if (label_is_none(label1)) + label1 = new_label(s); + emit_goto(s, OP_if_true, &label1); + } else { + label_case = new_label(s); + emit_goto(s, OP_if_false, &label_case); + if (!label_is_none(label1)) + emit_label(s, &label1); + break; + } + } + } else if (s->token.val == TOK_DEFAULT) { + next_token(s); + js_parse_expect(s, ':'); + if (default_label_pos >= 0) + js_parse_error(s, "duplicate default"); + if (label_is_none(label_case)) { + /* falling thru direct from switch expression */ + label_case = new_label(s); + emit_goto(s, OP_goto, &label_case); + } + default_label_pos = s->byte_code_len; + } else { + if (label_is_none(label_case)) + js_parse_error(s, "invalid switch statement"); + PARSE_PUSH_VAL(s, label_case); + PARSE_CALL_SAVE1(s, 7, js_parse_statement, 0, + default_label_pos); + PARSE_POP_VAL(s, label_case); + } + } + js_parse_expect(s, '}'); + if (default_label_pos >= 0) { + /* patch the default label */ + emit_label_pos(s, &label_case, default_label_pos); + } else if (!label_is_none(label_case)) { + emit_label(s, &label_case); + } + be = VALUE_TO_SP(s->ctx, s->top_break); + emit_label(s, &be->label_break); + emit_op(s, OP_drop); /* drop the switch expression */ + + pop_break_entry(s); + } + break; + case TOK_TRY: + { + JSValue label_catch, label_finally, label_end; + BlockEnv *be; + + set_eval_ret_undefined(s); + next_token(s); + label_catch = new_label(s); + label_finally = new_label(s); + + emit_goto(s, OP_catch, &label_catch); + + be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1); + be->label_finally = label_finally; + + PARSE_PUSH_VAL(s, label_catch); + PARSE_CALL(s, 8, js_parse_block, 0); + PARSE_POP_VAL(s, label_catch); + + be = VALUE_TO_SP(s->ctx, s->top_break); + label_finally = be->label_finally; + pop_break_entry(s); + + /* drop the catch offset */ + emit_op(s, OP_drop); + + /* must push dummy value to keep same stack size */ + emit_op(s, OP_undefined); + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_drop); + + label_end = new_label(s); + emit_goto(s, OP_goto, &label_end); + + if (s->token.val == TOK_CATCH) { + JSValue label_catch2; + int var_idx; + JSValue name; + + label_catch2 = new_label(s); + + next_token(s); + js_parse_expect(s, '('); + if (s->token.val != TOK_IDENT) + js_parse_error(s, "identifier expected"); + name = s->token.value; + /* XXX: the local scope is not implemented, so we add + a normal variable */ + if (find_var(s, name) >= 0 || find_ext_var(s, name) >= 0) { + js_parse_error(s, "catch variable already exists"); + } + var_idx = add_var(s, name); + next_token(s); + js_parse_expect(s, ')'); + + /* store the exception value in the variable */ + emit_label(s, &label_catch); + { + JSFunctionBytecode *b = JS_VALUE_TO_PTR(s->cur_func); + emit_var(s, OP_put_loc, var_idx - b->arg_count, s->pc2line_source_pos); + } + + emit_goto(s, OP_catch, &label_catch2); + + be = push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 1); + be->label_finally = label_finally; + + PARSE_PUSH_VAL(s, label_end); + PARSE_PUSH_VAL(s, label_catch2); + PARSE_CALL(s, 9, js_parse_block, 0); + PARSE_POP_VAL(s, label_catch2); + PARSE_POP_VAL(s, label_end); + + be = VALUE_TO_SP(s->ctx, s->top_break); + label_finally = be->label_finally; + pop_break_entry(s); + + /* drop the catch2 offset */ + emit_op(s, OP_drop); + /* must push dummy value to keep same stack size */ + emit_op(s, OP_undefined); + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_drop); + emit_goto(s, OP_goto, &label_end); + + /* catch exceptions thrown in the catch block to execute the + * finally clause and rethrow the exception */ + emit_label(s, &label_catch2); + /* catch value is at TOS, no need to push undefined */ + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_throw); + + } else if (s->token.val == TOK_FINALLY) { + /* finally without catch : execute the finally clause + * and rethrow the exception */ + emit_label(s, &label_catch); + /* catch value is at TOS, no need to push undefined */ + emit_goto(s, OP_gosub, &label_finally); + emit_op(s, OP_throw); + } else { + js_parse_error(s, "expecting catch or finally"); + } + + emit_label(s, &label_finally); + if (s->token.val == TOK_FINALLY) { + next_token(s); + /* XXX: we don't return the correct value in eval() */ + /* on the stack: ret_value gosub_ret_value */ + push_break_entry(s, JS_NULL, LABEL_NONE, LABEL_NONE, 2); + + PARSE_PUSH_VAL(s, label_end); + PARSE_CALL(s, 10, js_parse_block, 0); + PARSE_POP_VAL(s, label_end); + + pop_break_entry(s); + } + emit_op(s, OP_ret); + emit_label(s, &label_end); + } + break; + case ';': + /* empty statement */ + next_token(s); + break; + default: + if (s->eval_ret_idx >= 0) { + /* store the expression value so that it can be returned + by eval() */ + js_parse_expr(s); + emit_var(s, OP_put_loc, s->eval_ret_idx, s->pc2line_source_pos); + } else { + js_parse_expr2(s, PF_DROP); + } + js_parse_expect_semi(s); + break; + } + done: + return PARSE_STATE_RET; +} + +static JSParseFunc *parse_func_table[] = { + js_parse_expr_comma, + js_parse_assign_expr, + js_parse_cond_expr, + js_parse_logical_and_or, + js_parse_expr_binary, + js_parse_unary, + js_parse_postfix_expr, + js_parse_statement, + js_parse_block, + js_parse_json_value, + re_parse_alternative, + re_parse_disjunction, +}; + +static void js_parse_source_element(JSParseState *s) +{ + if (s->token.val == TOK_FUNCTION) { + js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, JS_NULL); + } else { + js_parse_call(s, PARSE_FUNC_js_parse_statement, 0); + } +} + +static JSFunctionBytecode *js_alloc_function_bytecode(JSContext *ctx) +{ + JSFunctionBytecode *b; + b = js_mallocz(ctx, sizeof(JSFunctionBytecode), JS_MTAG_FUNCTION_BYTECODE); + if (!b) + return NULL; + b->func_name = JS_NULL; + b->byte_code = JS_NULL; + b->cpool = JS_NULL; + b->vars = JS_NULL; + b->ext_vars = JS_NULL; + b->filename = JS_NULL; + b->pc2line = JS_NULL; + return b; +} + +/* the current token must be TOK_FUNCTION for JS_PARSE_FUNC_STATEMENT + or JS_PARSE_FUNC_EXPR. Otherwise it is '('. */ +static void js_parse_function_decl(JSParseState *s, + JSParseFunctionEnum func_type, JSValue func_name) +{ + JSContext *ctx = s->ctx; + BOOL is_expr; + JSFunctionBytecode *b; + int idx, skip_bits; + JSVarRefKindEnum var_kind; + JSValue bfunc; + JSGCRef func_name_ref, bfunc_ref; + + is_expr = (func_type != JS_PARSE_FUNC_STATEMENT); + + if (func_type == JS_PARSE_FUNC_STATEMENT || + func_type == JS_PARSE_FUNC_EXPR) { + next_token(s); + if (s->token.val != TOK_IDENT && !is_expr) + js_parse_error(s, "function name expected"); + if (s->token.val == TOK_IDENT) { + func_name = s->token.value; + JS_PUSH_VALUE(ctx, func_name); + next_token(s); + JS_POP_VALUE(ctx, func_name); + } + } + + JS_PUSH_VALUE(ctx, func_name); + b = js_alloc_function_bytecode(s->ctx); + if (!b) + js_parse_error_mem(s); + bfunc = JS_VALUE_FROM_PTR(b); + JS_PUSH_VALUE(ctx, bfunc); + + b->filename = s->filename_str; + b->func_name = func_name_ref.val; + b->source_pos = s->token.source_pos; + b->has_column = s->has_column; + + js_parse_expect1(s, '('); + /* skip the arguments */ + js_skip_parens(s, NULL); + + js_parse_expect1(s, '{'); + + /* skip the code */ + skip_bits = js_skip_parens(s, is_expr ? &func_name_ref.val : NULL); + + b = JS_VALUE_TO_PTR(bfunc_ref.val); + b->has_arguments = ((skip_bits & SKIP_HAS_ARGUMENTS) != 0); + b->has_local_func_name = ((skip_bits & SKIP_HAS_FUNC_NAME) != 0); + + idx = cpool_add(s, bfunc_ref.val); + if (is_expr) { + /* create the function object */ + emit_op(s, OP_fclosure); + emit_u16(s, idx); + } else { + idx = define_var(s, &var_kind, func_name_ref.val); + /* size of hoisted for OP_fclosure + OP_put_loc/OP_put_arg/OP_put_ref */ + s->hoisted_code_len += 3 + 3; + if (var_kind == JS_VARREF_KIND_VAR) { + b = JS_VALUE_TO_PTR(s->cur_func); + idx += b->arg_count; + } + b = JS_VALUE_TO_PTR(bfunc_ref.val); + /* hoisted function definition: save the variable index to + define it at the start of the function */ + b->arg_count = idx + 1; + } + JS_POP_VALUE(ctx, bfunc); + JS_POP_VALUE(ctx, func_name); +} + +static void define_hoisted_functions(JSParseState *s, BOOL is_eval) +{ + JSValueArray *cpool; + JSValue val; + JSFunctionBytecode *b; + int idx, saved_byte_code_len, arg_count, i, op; + + /* add pc2line info */ + b = JS_VALUE_TO_PTR(s->cur_func); + if (b->pc2line != JS_NULL) { + int h, n; + + /* byte align */ + n = (-s->pc2line_bit_len) & 7; + if (n != 0) + pc2line_put_bits(s, n, 0); + + n = s->hoisted_code_len; + h = 0; + for(;;) { + pc2line_put_bits(s, 8, (n & 0x7f) | h); + n >>= 7; + if (n == 0) + break; + h |= 0x80; + } + } + + if (s->hoisted_code_len == 0) + return; + emit_insert(s, 0, s->hoisted_code_len); + + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count; + + saved_byte_code_len = s->byte_code_len; + s->byte_code_len = 0; + cpool = JS_VALUE_TO_PTR(b->cpool); + for(i = 0; i < s->cpool_len; i++) { + val = cpool->arr[i]; + if (JS_IsPtr(val)) { + b = JS_VALUE_TO_PTR(val); + if (b->mtag == JS_MTAG_FUNCTION_BYTECODE && + b->arg_count != 0) { + idx = b->arg_count - 1; + /* XXX: could use smaller opcodes */ + if (is_eval) { + op = OP_put_var_ref_nocheck; + } else if (idx < arg_count) { + op = OP_put_arg; + } else { + idx -= arg_count; + op = OP_put_loc; + } + /* no realloc possible here */ + emit_u8(s, OP_fclosure); + emit_u16(s, i); + + emit_u8(s, op); + emit_u16(s, idx); + } + } + } + s->byte_code_len = saved_byte_code_len; +} + +static void js_parse_function(JSParseState *s) +{ + JSFunctionBytecode *b; + int arg_count; + + next_token(s); + + js_parse_expect(s, '('); + + while (s->token.val != ')') { + JSValue name; + /* XXX: gc */ + if (s->token.val != TOK_IDENT) + js_parse_error(s, "missing formal parameter"); + name = s->token.value; + if (name == js_get_atom(s->ctx, JS_ATOM_eval) || + name == js_get_atom(s->ctx, JS_ATOM_arguments)) { + js_parse_error(s, "invalid argument name"); + } + if (find_var(s, name) >= 0) + js_parse_error(s, "duplicate argument name"); + add_var(s, name); + next_token(s); + if (s->token.val == ')') + break; + js_parse_expect(s, ','); + } + b = JS_VALUE_TO_PTR(s->cur_func); + arg_count = b->arg_count = s->local_vars_len; + + next_token(s); + + js_parse_expect(s, '{'); + + /* initialize the arguments */ + b = JS_VALUE_TO_PTR(s->cur_func); + if (b->has_arguments) { + int var_idx; + var_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM_arguments)); + emit_op(s, OP_arguments); + put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos); + } + + /* XXX: initialize the function name */ + b = JS_VALUE_TO_PTR(s->cur_func); + if (b->has_local_func_name) { + int var_idx; + /* XXX: */ + var_idx = add_var(s, b->func_name); + emit_op(s, OP_this_func); + put_var(s, JS_VARREF_KIND_VAR, var_idx - arg_count, s->pc2line_source_pos); + } + + while (s->token.val != '}') { + js_parse_source_element(s); + } + + if (js_is_live_code(s)) + emit_op(s, OP_return_undef); + + next_token(s); + + define_hoisted_functions(s, FALSE); + + /* save the bytecode to the function */ + b = JS_VALUE_TO_PTR(s->cur_func); + b->byte_code = s->byte_code; +} + +static void js_parse_program(JSParseState *s) +{ + JSFunctionBytecode *b; + + next_token(s); + + /* hidden variable for the return value */ + if (s->has_retval) { + s->eval_ret_idx = add_var(s, js_get_atom(s->ctx, JS_ATOM__ret_)); + } + + while (s->token.val != TOK_EOF) { + js_parse_source_element(s); + } + + if (s->eval_ret_idx >= 0) { + emit_var(s, OP_get_loc, s->eval_ret_idx, s->pc2line_source_pos); + emit_op(s, OP_return); + } else { + emit_op(s, OP_return_undef); + } + + define_hoisted_functions(s, TRUE); + + /* save the bytecode to the function */ + b = JS_VALUE_TO_PTR(s->cur_func); + b->byte_code = s->byte_code; +} + +#define CVT_VAR_SIZE_MAX 16 + +typedef struct { + uint16_t new_var_idx; /* new local var index */ + uint8_t is_local; +} ConvertVarEntry; + +static void convert_ext_vars_to_local_vars_bytecode(JSParseState *s, + uint8_t *byte_code, int byte_code_len, + int var_start, const ConvertVarEntry *cvt_tab, + int tab_len) +{ + int pos, var_end, j, op, var_idx; + const JSOpCode *oi; + + var_end = var_start + tab_len; + pos = 0; + while (pos < byte_code_len) { + op = byte_code[pos]; + oi = &opcode_info[op]; + switch(op) { + case OP_get_var_ref: + case OP_put_var_ref: + case OP_get_var_ref_nocheck: + case OP_put_var_ref_nocheck: + var_idx = get_u16(byte_code + pos + 1); + if (var_idx >= var_start && var_idx < var_end) { + j = var_idx - var_start; + put_u16(byte_code + pos + 1, cvt_tab[j].new_var_idx); + if (cvt_tab[j].is_local) { + if (op == OP_get_var_ref || op == OP_get_var_ref_nocheck) { + byte_code[pos] = OP_get_loc; + } else { + byte_code[pos] = OP_put_loc; + } + } + } + break; + default: + break; + } + pos += oi->size; + } +} + +/* no allocation */ +static void convert_ext_vars_to_local_vars(JSParseState *s) +{ + JSValueArray *ext_vars; + JSFunctionBytecode *b; + JSByteArray *bc_arr; + JSValue var_name, decl; + int i0, i, j, var_idx, l; + ConvertVarEntry cvt_tab[CVT_VAR_SIZE_MAX]; + + b = JS_VALUE_TO_PTR(s->cur_func); + if (s->local_vars_len == 0 || b->ext_vars_len == 0) + return; + bc_arr = JS_VALUE_TO_PTR(b->byte_code); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + + /* do it by parts to save memory */ + j = 0; + for(i0 = 0; i0 < b->ext_vars_len; i0 += CVT_VAR_SIZE_MAX) { + l = min_int(b->ext_vars_len - i0, CVT_VAR_SIZE_MAX); + for(i = 0; i < l; i++) { + var_name = ext_vars->arr[2 * (i0 + i)]; + decl = ext_vars->arr[2 * (i0 + i) + 1]; + var_idx = find_var(s, var_name); + /* fail safe: we avoid arguments even if they cannot appear */ + if (var_idx >= b->arg_count) { + cvt_tab[i].new_var_idx = var_idx - b->arg_count; + cvt_tab[i].is_local = TRUE; + } else { + cvt_tab[i].new_var_idx = j; + cvt_tab[i].is_local = FALSE; + ext_vars->arr[2 * j] = var_name; + ext_vars->arr[2 * j + 1] = decl; + j++; + } + } + if (j != (i0 + l)) { + convert_ext_vars_to_local_vars_bytecode(s, bc_arr->buf, s->byte_code_len, + i0, cvt_tab, l); + } + } + b->ext_vars_len = j; +} + +/* prepare the analysis of the code starting at position 'pos' */ +static void compute_stack_size_push(JSParseState *s, + JSByteArray *arr, + uint8_t *explore_tab, + uint32_t pos, int stack_len) +{ + int short_stack_len; + +#if 0 + js_printf(s->ctx, "%5d: %d\n", pos, stack_len); +#endif + if (pos >= (uint32_t)arr->size) + js_parse_error(s, "bytecode buffer overflow (pc=%d)", pos); + /* XXX: could avoid the division */ + short_stack_len = 1 + ((unsigned)stack_len % 255); + if (explore_tab[pos] != 0) { + /* already explored: check that the stack size is consistent */ + if (explore_tab[pos] != short_stack_len) { + js_parse_error(s, "inconsistent stack size: %d %d (pc=%d)", explore_tab[pos] - 1, short_stack_len - 1, (int)pos); + } + } else { + explore_tab[pos] = short_stack_len; + /* may initiate a GC */ + PARSE_PUSH_INT(s, pos); + PARSE_PUSH_INT(s, stack_len); + } +} + +static void compute_stack_size(JSParseState *s, JSValue *pfunc) +{ + JSContext *ctx = s->ctx; + JSByteArray *explore_arr, *arr; + JSFunctionBytecode *b; + uint8_t *explore_tab; + JSValue *stack_top, explore_arr_val; + uint32_t pos; + int op, op_len, pos1, n_pop, stack_len; + const JSOpCode *oi; + JSGCRef explore_arr_val_ref; + + b = JS_VALUE_TO_PTR(*pfunc); + arr = JS_VALUE_TO_PTR(b->byte_code); + + explore_arr = js_alloc_byte_array(s->ctx, arr->size); + if (!explore_arr) + js_parse_error_mem(s); + + b = JS_VALUE_TO_PTR(*pfunc); + arr = JS_VALUE_TO_PTR(b->byte_code); + + explore_arr_val = JS_VALUE_FROM_PTR(explore_arr); + explore_tab = explore_arr->buf; + memset(explore_tab, 0, arr->size); + + JS_PUSH_VALUE(ctx, explore_arr_val); + + stack_top = ctx->sp; + + compute_stack_size_push(s, arr, explore_tab, 0, 0); + + while (ctx->sp < stack_top) { + PARSE_POP_INT(s, stack_len); + PARSE_POP_INT(s, pos); + + /* compute_stack_size_push may have initiated a GC */ + b = JS_VALUE_TO_PTR(*pfunc); + arr = JS_VALUE_TO_PTR(b->byte_code); + explore_arr = JS_VALUE_TO_PTR(explore_arr_val_ref.val); + explore_tab = explore_arr->buf; + + op = arr->buf[pos++]; + if (op == OP_invalid || op >= OP_COUNT) + js_parse_error(s, "invalid opcode (pc=%d)", (int)(pos - 1)); + oi = &opcode_info[op]; + op_len = oi->size; + if ((pos + op_len - 1) > arr->size) { + js_parse_error(s, "bytecode buffer overflow (pc=%d)", (int)(pos - 1)); + } + n_pop = oi->n_pop; + if (oi->fmt == OP_FMT_npop) + n_pop += get_u16(arr->buf + pos); + + if (stack_len < n_pop) { + js_parse_error(s, "stack underflow (pc=%d)", (int)(pos - 1)); + } + stack_len += oi->n_push - n_pop; + if (stack_len > b->stack_size) { + if (stack_len > JS_MAX_FUNC_STACK_SIZE) + js_parse_error(s, "stack overflow (pc=%d)", (int)(pos - 1)); + b->stack_size = stack_len; + } + switch(op) { + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_ret: + goto done; /* no code after */ + case OP_goto: + pos += get_u32(arr->buf + pos); + break; + case OP_if_true: + case OP_if_false: + pos1 = pos + get_u32(arr->buf + pos); + compute_stack_size_push(s, arr, explore_tab, pos1, stack_len); + pos += op_len - 1; + break; + case OP_gosub: + pos1 = pos + get_u32(arr->buf + pos); + compute_stack_size_push(s, arr, explore_tab, pos1, stack_len + 1); + pos += op_len - 1; + break; + default: + pos += op_len - 1; + break; + } + compute_stack_size_push(s, arr, explore_tab, pos, stack_len); + done: ; + } + + JS_POP_VALUE(ctx, explore_arr_val); + explore_arr = JS_VALUE_TO_PTR(explore_arr_val); + js_free(s->ctx, explore_arr); +} + +static void resolve_var_refs(JSParseState *s, JSValue *pfunc, JSValue *pparent_func) +{ + JSContext *ctx = s->ctx; + int i, decl, var_idx, arg_count, ext_vars_len; + JSValueArray *ext_vars; + JSValue var_name; + JSFunctionBytecode *b1, *b; + + b = JS_VALUE_TO_PTR(*pfunc); + if (b->ext_vars_len == 0) + return; + b1 = JS_VALUE_TO_PTR(*pparent_func); + arg_count = b1->arg_count; + + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + ext_vars_len = b->ext_vars_len; + + for(i = 0; i < ext_vars_len; i++) { + b = JS_VALUE_TO_PTR(*pfunc); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + var_name = ext_vars->arr[2 * i]; + var_idx = find_func_var(ctx, *pparent_func, var_name); + if (var_idx >= 0) { + if (var_idx < arg_count) { + decl = (JS_VARREF_KIND_ARG << 16) | var_idx; + } else { + decl = (JS_VARREF_KIND_VAR << 16) | (var_idx - arg_count); + } + } else { + var_idx = find_func_ext_var(s, *pparent_func, var_name); + if (var_idx < 0) { + /* the global type may be patched later */ + var_idx = add_func_ext_var(s, *pparent_func, var_name, + (JS_VARREF_KIND_GLOBAL << 16)); + } + decl = (JS_VARREF_KIND_VAR_REF << 16) | var_idx; + } + b = JS_VALUE_TO_PTR(*pfunc); + ext_vars = JS_VALUE_TO_PTR(b->ext_vars); + ext_vars->arr[2 * i + 1] = JS_NewShortInt(decl); + } +} + +static void reset_parse_state(JSParseState *s, uint32_t input_pos, + JSValue cur_func) +{ + s->buf_pos = input_pos; + s->token.val = ' '; + + s->cur_func = cur_func; + s->byte_code = JS_NULL; + s->byte_code_len = 0; + s->last_opcode_pos = -1; + + s->pc2line_bit_len = 0; + s->pc2line_source_pos = 0; + + s->cpool_len = 0; + s->hoisted_code_len = 0; + + s->local_vars_len = 0; + + s->eval_ret_idx = -1; +} + +static void js_parse_local_functions(JSParseState *s, JSValue *pfunc) +{ + JSContext *ctx = s->ctx; + JSValue *pparent_func; + JSValueArray *cpool; + int err, cpool_pos; + JSValue func; + JSFunctionBytecode *b, *b1; + JSGCRef func_ref; + JSValue *stack_top; + + err = JS_StackCheck(ctx, 3); + if (err) + js_parse_error_stack_overflow(s); + stack_top = ctx->sp; + + *--ctx->sp = JS_NULL; /* parent_func */ + *--ctx->sp = *pfunc; /* func */ + *--ctx->sp = JS_NewShortInt(0); /* cpool_pos */ + + while (ctx->sp < stack_top) { + pparent_func = &ctx->sp[2]; + pfunc = &ctx->sp[1]; + cpool_pos = JS_VALUE_GET_INT(ctx->sp[0]); +#if 0 + JS_DumpValue(ctx, "func", *pfunc); + JS_DumpValue(ctx, "parent", *pparent_func); + JS_DumpValue(ctx, "cpool_pos", ctx->sp[0]); +#endif + if (cpool_pos == 0) { + b = JS_VALUE_TO_PTR(*pfunc); + + convert_ext_vars_to_local_vars(s); + + js_shrink_byte_array(ctx, &b->byte_code, s->byte_code_len); + js_shrink_value_array(ctx, &b->cpool, s->cpool_len); + js_shrink_value_array(ctx, &b->vars, s->local_vars_len); + js_shrink_byte_array(ctx, &b->pc2line, (s->pc2line_bit_len + 7) / 8); + + compute_stack_size(s, pfunc); + } + + b = JS_VALUE_TO_PTR(*pfunc); + if (b->cpool != JS_NULL) { + int cpool_size; + cpool = JS_VALUE_TO_PTR(b->cpool); + cpool_size = cpool->size; + for(; cpool_pos < cpool_size; cpool_pos++) { + b = JS_VALUE_TO_PTR(*pfunc); + cpool = JS_VALUE_TO_PTR(b->cpool); + func = cpool->arr[cpool_pos]; + if (!JS_IsPtr(func)) + continue; + b1 = JS_VALUE_TO_PTR(func); + if (b1->mtag != JS_MTAG_FUNCTION_BYTECODE) + continue; + + reset_parse_state(s, b1->source_pos, func); + + s->is_eval = FALSE; + s->is_repl = FALSE; + s->has_retval = FALSE; + + JS_PUSH_VALUE(ctx, func); + js_parse_function(s); + + /* parse a local function */ + err = JS_StackCheck(ctx, 3); + JS_POP_VALUE(ctx, func); + if (err) + js_parse_error_stack_overflow(s); + /* set the next cpool position */ + *ctx->sp = JS_NewShortInt(cpool_pos + 1); + + *--ctx->sp = *pfunc; /* parent_func */ + *--ctx->sp = func; /* func */ + *--ctx->sp = JS_NewShortInt(0); /* cpool_pos */ + goto next; + } + } + + if (*pparent_func != JS_NULL) { + resolve_var_refs(s, pfunc, pparent_func); + } + /* now we can shrink the external vars */ + b = JS_VALUE_TO_PTR(*pfunc); + js_shrink_value_array(ctx, &b->ext_vars, 2 * b->ext_vars_len); +#ifdef DUMP_FUNC_BYTECODE + dump_byte_code(ctx, b); +#endif + /* remove the stack entry */ + ctx->sp += 3; + ctx->stack_bottom = ctx->sp; + next: ; + } +} + +/* return the parsed value in s->token.value */ +/* XXX: use exact JSON white space definition */ +static int js_parse_json_value(JSParseState *s, int state, int dummy_param) +{ + JSContext *ctx = s->ctx; + const uint8_t *p; + JSValue val; + + PARSE_START2(); + + p = s->source_buf + s->buf_pos; + p += skip_spaces((const char *)p); + s->buf_pos = p - s->source_buf; + if ((*p >= '0' && *p <= '9') || *p == '-') { + double d; + JSByteArray *tmp_arr; + tmp_arr = js_alloc_byte_array(s->ctx, sizeof(JSATODTempMem)); + if (!tmp_arr) + js_parse_error_mem(s); + p = s->source_buf + s->buf_pos; + d = js_atod((const char *)p, (const char **)&p, 10, 0, + (JSATODTempMem *)tmp_arr->buf); + js_free(s->ctx, tmp_arr); + if (isnan(d)) + js_parse_error(s, "invalid number literal"); + val = JS_NewFloat64(s->ctx, d); + } else if (*p == 't' && + p[1] == 'r' && p[2] == 'u' && p[3] == 'e') { + p += 4; + val = JS_TRUE; + } else if (*p == 'f' && + p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') { + p += 5; + val = JS_FALSE; + } else if (*p == 'n' && + p[1] == 'u' && p[2] == 'l' && p[3] == 'l') { + p += 4; + val = JS_NULL; + } else if (*p == '\"') { + uint32_t pos; + pos = p + 1 - s->source_buf; + val = js_parse_string(s, &pos, '\"'); + p = s->source_buf + pos; + } else if (*p == '[') { + JSValue val2; + uint32_t idx; + + val = JS_NewArray(ctx, 0); + if (JS_IsException(val)) + js_parse_error_mem(s); + PARSE_PUSH_VAL(s, val); /* 'val' is not usable after this call */ + p = s->source_buf + s->buf_pos + 1; + p += skip_spaces((const char *)p); + if (*p != ']') { + idx = 0; + for(;;) { + s->buf_pos = p - s->source_buf; + PARSE_PUSH_INT(s, idx); + PARSE_CALL(s, 0, js_parse_json_value, 0); + PARSE_POP_INT(s, idx); + val2 = s->token.value; + val2 = JS_SetPropertyUint32(ctx, *ctx->sp, idx, val2); + if (JS_IsException(val2)) + js_parse_error_mem(s); + idx++; + p = s->source_buf + s->buf_pos; + p += skip_spaces((const char *)p); + if (*p != ',') + break; + p++; + } + } + if (*p != ']') + js_parse_error(s, "expecting ']'"); + p++; + PARSE_POP_VAL(s, val); + } else if (*p == '{') { + JSValue val2, prop; + uint32_t pos; + + val = JS_NewObject(ctx); + if (JS_IsException(val)) + js_parse_error_mem(s); + PARSE_PUSH_VAL(s, val); /* 'val' is not usable after this call */ + p = s->source_buf + s->buf_pos + 1; + p += skip_spaces((const char *)p); + if (*p != '}') { + for(;;) { + p += skip_spaces((const char *)p); + s->buf_pos = p - s->source_buf; + if (*p != '\"') + js_parse_error(s, "expecting '\"'"); + pos = p + 1 - s->source_buf; + prop = js_parse_string(s, &pos, '\"'); + prop = JS_ToPropertyKey(ctx, prop); + if (JS_IsException(prop)) + js_parse_error_mem(s); + p = s->source_buf + pos; + p += skip_spaces((const char *)p); + if (*p != ':') + js_parse_error(s, "expecting ':'"); + p++; + s->buf_pos = p - s->source_buf; + PARSE_PUSH_VAL(s, prop); + PARSE_CALL(s, 1, js_parse_json_value, 0); + val2 = s->token.value; + PARSE_POP_VAL(s, prop); + val2 = JS_DefinePropertyValue(ctx, *ctx->sp, prop, val2); + if (JS_IsException(val2)) + js_parse_error_mem(s); + p = s->source_buf + s->buf_pos; + p += skip_spaces((const char *)p); + if (*p != ',') + break; + p++; + } + } + if (*p != '}') + js_parse_error(s, "expecting '}'"); + p++; + PARSE_POP_VAL(s, val); + } else { + js_parse_error(s, "unexpected character"); + } + s->buf_pos = p - s->source_buf; + s->token.value = val; + return PARSE_STATE_RET; +} + +static JSValue js_parse_json(JSParseState *s) +{ + s->buf_pos = 0; + js_parse_call(s, PARSE_FUNC_js_parse_json_value, 0); + s->buf_pos += skip_spaces((const char *)(s->source_buf + s->buf_pos)); + if (s->buf_pos != s->buf_len) { + js_parse_error(s, "unexpected character"); + } + return s->token.value; +} + +/* source_str must be a string or JS_NULL. (input, input_len) is + meaningful only if source_str is JS_NULL. */ +static JSValue JS_Parse2(JSContext *ctx, JSValue source_str, + const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + JSParseState parse_state, *s; + JSFunctionBytecode *b; + JSValue top_func, *saved_sp; + JSGCRef top_func_ref, *saved_top_gc_ref; + uint8_t str_buf[5]; + + /* XXX: start gc at the start of parsing ? */ + /* XXX: if the parse state is too large, move it to JSContext */ + s = &parse_state; + memset(s, 0, sizeof(*s)); + + s->ctx = ctx; + ctx->parse_state = s; + s->source_str = JS_NULL; + s->filename_str = JS_NULL; + s->has_column = ((eval_flags & JS_EVAL_STRIP_COL) == 0); + + if (JS_IsPtr(source_str)) { + JSString *p = JS_VALUE_TO_PTR(source_str); + s->source_str = source_str; + s->buf_len = p->len; + s->source_buf = p->buf; + } else if (JS_VALUE_GET_SPECIAL_TAG(source_str) == JS_TAG_STRING_CHAR) { + s->buf_len = get_short_string(str_buf, source_str); + s->source_buf = str_buf; + } else { + s->buf_len = input_len; + s->source_buf = (const uint8_t *)input; + } + s->top_break = JS_NULL; + saved_top_gc_ref = ctx->top_gc_ref; + saved_sp = ctx->sp; + + if (setjmp(s->jmp_env)) { + int line_num, col_num; + JSValue val; + + ctx->parse_state = NULL; + ctx->top_gc_ref = saved_top_gc_ref; + ctx->sp = saved_sp; + ctx->stack_bottom = ctx->sp; + + line_num = get_line_col(&col_num, s->source_buf, + (eval_flags & (JS_EVAL_JSON | JS_EVAL_REGEXP)) ? + s->buf_pos : s->token.source_pos); + val = JS_ThrowError(ctx, JS_CLASS_SYNTAX_ERROR, "%s", s->error_msg); + build_backtrace(ctx, ctx->current_exception, filename, line_num + 1, col_num + 1, 0); + return val; + } + + if (eval_flags & JS_EVAL_JSON) { + top_func = js_parse_json(s); + } else if (eval_flags & JS_EVAL_REGEXP) { + top_func = js_parse_regexp(s, eval_flags >> JS_EVAL_REGEXP_FLAGS_SHIFT); + } else { + s->filename_str = JS_NewString(ctx, filename); + if (JS_IsException(s->filename_str)) + js_parse_error_mem(s); + + b = js_alloc_function_bytecode(ctx); + if (!b) + js_parse_error_mem(s); + b->filename = s->filename_str; + b->func_name = js_get_atom(ctx, JS_ATOM__eval_); + b->has_column = s->has_column; + top_func = JS_VALUE_FROM_PTR(b); + + reset_parse_state(s, 0, top_func); + + s->is_eval = TRUE; + s->has_retval = ((eval_flags & JS_EVAL_RETVAL) != 0); + s->is_repl = ((eval_flags & JS_EVAL_REPL) != 0); + + JS_PUSH_VALUE(ctx, top_func); + + js_parse_program(s); + + js_parse_local_functions(s, &top_func_ref.val); + + JS_POP_VALUE(ctx, top_func); + } + ctx->parse_state = NULL; + return top_func; +} + +/* warning: it is assumed that input[input_len] = '\0' */ +JSValue JS_Parse(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + return JS_Parse2(ctx, JS_NULL, input, input_len, filename, eval_flags); +} + +JSValue JS_Run(JSContext *ctx, JSValue val) +{ + JSFunctionBytecode *b; + JSGCRef val_ref; + int err; + + if (!JS_IsPtr(val)) + goto fail; + b = JS_VALUE_TO_PTR(val); + if (b->mtag != JS_MTAG_FUNCTION_BYTECODE) { + fail: + return JS_ThrowTypeError(ctx, "bytecode function expected"); + } + + val = js_closure(ctx, val, NULL); + if (JS_IsException(val)) + return val; + JS_PUSH_VALUE(ctx, val); + err = JS_StackCheck(ctx, 2); + JS_POP_VALUE(ctx, val); + if (err) + return JS_EXCEPTION; + JS_PushArg(ctx, val); + JS_PushArg(ctx, JS_NULL); + val = JS_Call(ctx, 0); + return val; +} + +/* warning: it is assumed that input[input_len] = '\0' */ +JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + JSValue val; + val = JS_Parse(ctx, input, input_len, filename, eval_flags); + if (JS_IsException(val)) + return val; + return JS_Run(ctx, val); +} + +/**********************************************************************/ +/* garbage collector */ + +/* return the size in bytes */ +static int get_mblock_size(const void *ptr) +{ + int mtag = ((JSMemBlockHeader *)ptr)->mtag; + int size; + switch(mtag) { + case JS_MTAG_OBJECT: + { + const JSObject *p = ptr; + size = offsetof(JSObject, u) + p->extra_size * JSW; + } + break; + case JS_MTAG_FLOAT64: + size = sizeof(JSFloat64); + break; + case JS_MTAG_STRING: + { + const JSString *p = ptr; + size = sizeof(JSString) + ((p->len + JSW) & ~(JSW - 1)); + } + break; + case JS_MTAG_BYTE_ARRAY: + { + const JSByteArray *p = ptr; + size = sizeof(JSByteArray) + ((p->size + JSW - 1) & ~(JSW - 1)); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray *p = ptr; + size = sizeof(JSValueArray) + p->size * sizeof(p->arr[0]); + } + break; + case JS_MTAG_FREE: + { + const JSFreeBlock *p = ptr; + size = sizeof(JSFreeBlock) + p->size * sizeof(JSWord); + } + break; + case JS_MTAG_VARREF: + { + const JSVarRef *p = ptr; + size = sizeof(JSVarRef); + if (p->is_detached) + size -= sizeof(JSValue); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + size = sizeof(JSFunctionBytecode); + break; + default: + size = 0; + assert(0); + } + return size; +} + +/* gc mark pass */ + +typedef struct { + JSContext *ctx; + JSValue *gsp; + JSValue *gs_bottom; + JSValue *gs_top; + BOOL overflow; +} GCMarkState; + +static BOOL mtag_has_references(int mtag) +{ + return (mtag == JS_MTAG_OBJECT || + mtag == JS_MTAG_VALUE_ARRAY || + mtag == JS_MTAG_VARREF || + mtag == JS_MTAG_FUNCTION_BYTECODE); +} + +static void gc_mark(GCMarkState *s, JSValue val) +{ + JSContext *ctx = s->ctx; + void *ptr; + JSMemBlockHeader *mb; + + if (!JS_IsPtr(val)) + return; + ptr = JS_VALUE_TO_PTR(val); + if (JS_IS_ROM_PTR(ctx, ptr)) + return; + mb = ptr; + if (mb->gc_mark) + return; + mb->gc_mark = 1; + if (mtag_has_references(mb->mtag)) { + if (mb->mtag == JS_MTAG_VALUE_ARRAY) { + /* value array are handled specifically to save stack space */ + if ((s->gsp - s->gs_bottom) < 2) { + s->overflow = TRUE; + } else { + *--s->gsp = 0; + *--s->gsp = val; + } + } else { + if ((s->gsp - s->gs_bottom) < 1) { + s->overflow = TRUE; + } else { + *--s->gsp = val; + } + } + } +} + +/* flush the GC mark stack */ +static void gc_mark_flush(GCMarkState *s) +{ + void *ptr; + JSMemBlockHeader *mb; + JSValue val; + + while (s->gsp < s->gs_top) { + val = *s->gsp++; + ptr = JS_VALUE_TO_PTR(val); + mb = ptr; + + switch(mb->mtag) { + case JS_MTAG_OBJECT: + { + const JSObject *p = ptr; + gc_mark(s, p->proto); + gc_mark(s, p->props); + switch(p->class_id) { + case JS_CLASS_CLOSURE: + { + int i; + gc_mark(s, p->u.closure.func_bytecode); + for(i = 0; i < p->extra_size - 1; i++) + gc_mark(s, p->u.closure.var_refs[i]); + } + break; + case JS_CLASS_C_FUNCTION: + if (p->extra_size > 1) + gc_mark(s, p->u.cfunc.params); + break; + case JS_CLASS_ARRAY: + gc_mark(s, p->u.array.tab); + break; + case JS_CLASS_ERROR: + gc_mark(s, p->u.error.message); + gc_mark(s, p->u.error.stack); + break; + case JS_CLASS_ARRAY_BUFFER: + gc_mark(s, p->u.array_buffer.byte_buffer); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + gc_mark(s, p->u.typed_array.buffer); + break; + case JS_CLASS_REGEXP: + gc_mark(s, p->u.regexp.source); + gc_mark(s, p->u.regexp.byte_code); + break; + } + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray *p = ptr; + int pos; + + pos = *s->gsp++; + + /* fast path to skip non objects */ + while (pos < p->size && !JS_IsPtr(p->arr[pos])) + pos++; + + if (pos < p->size) { + if ((pos + 1) < p->size) { + /* the next element needs to be scanned */ + *--s->gsp = pos + 1; + *--s->gsp = val; + } + /* mark the current element */ + gc_mark(s, p->arr[pos]); + } + } + break; + case JS_MTAG_VARREF: + { + const JSVarRef *p = ptr; + gc_mark(s, p->u.value); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + { + const JSFunctionBytecode *b = ptr; + gc_mark(s, b->func_name); + gc_mark(s, b->byte_code); + gc_mark(s, b->cpool); + gc_mark(s, b->vars); + gc_mark(s, b->ext_vars); + gc_mark(s, b->filename); + gc_mark(s, b->pc2line); + } + break; + default: + break; + } + } +} + +static void gc_mark_root(GCMarkState *s, JSValue val) +{ + gc_mark(s, val); + gc_mark_flush(s); +} + +/* return true if the memory block is marked i.e. it won't be freed by the GC */ +static BOOL gc_mb_is_marked(JSValue val) +{ + JSFreeBlock *b; + if (!JS_IsPtr(val)) + return FALSE; + b = (JSFreeBlock *)JS_VALUE_TO_PTR(val); + return b->gc_mark; +} + +static void gc_mark_all(JSContext *ctx, BOOL keep_atoms) +{ + GCMarkState s_s, *s = &s_s; + JSValue *sp, *sp_end; + + s->ctx = ctx; + /* initialize the GC stack */ + s->overflow = FALSE; + s->gs_top = ctx->sp; + s->gsp = s->gs_top; +#if 1 + s->gs_bottom = (JSValue *)ctx->heap_free; +#else + s->gs_bottom = s->gs_top - 3; /* TEST small stack space */ +#endif + + /* keep the atoms if they are in RAM (only used when compiling to file) */ + if ((uint8_t *)ctx->atom_table == ctx->heap_base && + keep_atoms) { + uint8_t *ptr; + for(ptr = (uint8_t *)ctx->atom_table; + ptr < (uint8_t *)(ctx->atom_table + JS_ATOM_END); + ptr += get_mblock_size(ptr)) { + gc_mark_root(s, JS_VALUE_FROM_PTR(ptr)); + } + } + + /* mark all the memory blocks */ + sp_end = ctx->class_proto + 2 * ctx->class_count; + for(sp = &ctx->current_exception; sp < sp_end; sp++) { + gc_mark_root(s, *sp); + } + + for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) { + gc_mark_root(s, *sp); + } + + { + JSGCRef *ref; + for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_mark_root(s, ref->val); + } + for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { + gc_mark_root(s, ref->val); + } + } + if (ctx->parse_state) { + JSParseState *ps = ctx->parse_state; + + gc_mark_root(s, ps->source_str); + gc_mark_root(s, ps->filename_str); + gc_mark_root(s, ps->token.value); + gc_mark_root(s, ps->cur_func); + gc_mark_root(s, ps->byte_code); + } + + /* if the mark stack overflowed, need to scan the heap */ + while (s->overflow) { + uint8_t *ptr; + int size; + JSMemBlockHeader *mb; + + s->overflow = FALSE; + + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + mb = (JSMemBlockHeader *)ptr; + if (mb->gc_mark && mtag_has_references(mb->mtag)) { + if (mb->mtag == JS_MTAG_VALUE_ARRAY) + *--s->gsp = 0; + *--s->gsp = JS_VALUE_FROM_PTR(ptr); + gc_mark_flush(s); + } + ptr += size; + } + } + + /* update the unique string table (its elements are considered as + weak string references) */ + if (!JS_IsNull(ctx->unique_strings)) { + JSValueArray *arr = JS_VALUE_TO_PTR(ctx->unique_strings); + int i, j; + + j = 0; + for(i = 0; i < arr->size; i++) { + if (gc_mb_is_marked(arr->arr[i])) { + arr->arr[j++] = arr->arr[i]; + } + } + ctx->unique_strings_len = j; + if (j > 0) { + arr->gc_mark = 1; + if (j < arr->size) { + /* shrink the array */ + set_free_block(&arr->arr[j], (arr->size - j) * sizeof(JSValue)); + arr->size = j; + } + } else { + arr->gc_mark = 0; + ctx->unique_strings = JS_NULL; + } + } + + /* update the weak references in the string position cache */ + { + int i; + JSStringPosCacheEntry *ce; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) { + ce = &ctx->string_pos_cache[i]; + if (!gc_mb_is_marked(ce->str)) + ce->str = JS_NULL; + } + } + + /* reset the gc marks and mark the free blocks as free */ + { + uint8_t *ptr, *ptr1; + int size; + JSFreeBlock *b; + + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + b = (JSFreeBlock *)ptr; + if (b->gc_mark) { + b->gc_mark = 0; + } else { + JSObject *p = (void *)ptr; + /* call the user finalizer if needed */ + if (p->mtag == JS_MTAG_OBJECT && p->class_id >= JS_CLASS_USER && + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER] != NULL) { + ctx->c_finalizer_table[p->class_id - JS_CLASS_USER](ctx, p->u.user.opaque); + } + /* merge all the consecutive free blocks */ + ptr1 = ptr + size; + while (ptr1 < ctx->heap_free && ((JSFreeBlock *)ptr1)->gc_mark == 0) { + ptr1 += get_mblock_size(ptr1); + } + size = ptr1 - ptr; + set_free_block(b, size); + } + ptr += size; + } + } +} + +static JSValue js_value_from_pval(JSContext *ctx, JSValue *pval) +{ + return JS_VALUE_FROM_PTR(pval); +} + +static JSValue *js_value_to_pval(JSContext *ctx, JSValue val) +{ + return JS_VALUE_TO_PTR(val); +} + +static void gc_thread_pointer(JSContext *ctx, JSValue *pval) +{ + JSValue val; + JSValue *ptr; + + val = *pval; + if (!JS_IsPtr(val)) + return; + ptr = JS_VALUE_TO_PTR(val); + if (JS_IS_ROM_PTR(ctx, ptr)) + return; + /* gc_mark = 0 indicates a normal memory block header, gc_mark = 1 + indicates a pointer to another element */ + *pval = *ptr; + *ptr = js_value_from_pval(ctx, pval); +} + +static void gc_update_threaded_pointers(JSContext *ctx, + void *ptr, void *new_ptr) +{ + JSValue val, *pv; + + val = *(JSValue *)ptr; + if (JS_IsPtr(val)) { + /* update the threaded pointers to the node 'ptr' and + unthread it. */ + for(;;) { + pv = js_value_to_pval(ctx, val); + val = *pv; + *pv = JS_VALUE_FROM_PTR(new_ptr); + if (!JS_IsPtr(val)) + break; + } + *(JSValue *)ptr = val; + } +} + +static void gc_thread_block(JSContext *ctx, void *ptr) +{ + int mtag; + + mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_OBJECT: + { + JSObject *p = ptr; + gc_thread_pointer(ctx, &p->proto); + gc_thread_pointer(ctx, &p->props); + switch(p->class_id) { + case JS_CLASS_CLOSURE: + { + int i; + gc_thread_pointer(ctx, &p->u.closure.func_bytecode); + for(i = 0; i < p->extra_size - 1; i++) + gc_thread_pointer(ctx, &p->u.closure.var_refs[i]); + } + break; + case JS_CLASS_C_FUNCTION: + if (p->extra_size > 1) + gc_thread_pointer(ctx, &p->u.cfunc.params); + break; + case JS_CLASS_ARRAY: + gc_thread_pointer(ctx, &p->u.array.tab); + break; + case JS_CLASS_ERROR: + gc_thread_pointer(ctx, &p->u.error.message); + gc_thread_pointer(ctx, &p->u.error.stack); + break; + case JS_CLASS_ARRAY_BUFFER: + gc_thread_pointer(ctx, &p->u.array_buffer.byte_buffer); + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + gc_thread_pointer(ctx, &p->u.typed_array.buffer); + break; + case JS_CLASS_REGEXP: + gc_thread_pointer(ctx, &p->u.regexp.source); + gc_thread_pointer(ctx, &p->u.regexp.byte_code); + break; + } + } + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *p = ptr; + int i; + for(i = 0; i < p->size; i++) { + gc_thread_pointer(ctx, &p->arr[i]); + } + } + break; + case JS_MTAG_VARREF: + { + JSVarRef *p = ptr; + gc_thread_pointer(ctx, &p->u.value); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = ptr; + gc_thread_pointer(ctx, &b->func_name); + gc_thread_pointer(ctx, &b->byte_code); + gc_thread_pointer(ctx, &b->cpool); + gc_thread_pointer(ctx, &b->vars); + gc_thread_pointer(ctx, &b->ext_vars); + gc_thread_pointer(ctx, &b->filename); + gc_thread_pointer(ctx, &b->pc2line); + } + break; + default: + break; + } +} + +/* Heap compaction using Jonkers algorithm */ +static void gc_compact_heap(JSContext *ctx) +{ + uint8_t *ptr, *new_ptr; + int size; + JSValue *sp, *sp_end; + + /* thread all the external pointers */ + sp_end = ctx->class_proto + 2 * ctx->class_count; + for(sp = &ctx->unique_strings; sp < sp_end; sp++) { + gc_thread_pointer(ctx, sp); + } + { + int i; + JSStringPosCacheEntry *ce; + for(i = 0; i < JS_STRING_POS_CACHE_SIZE; i++) { + ce = &ctx->string_pos_cache[i]; + gc_thread_pointer(ctx, &ce->str); + } + } + + for(sp = ctx->sp; sp < (JSValue *)ctx->stack_top; sp++) { + gc_thread_pointer(ctx, sp); + } + + { + JSGCRef *ref; + for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_thread_pointer(ctx, &ref->val); + } + for(ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { + gc_thread_pointer(ctx, &ref->val); + } + } + + if (ctx->parse_state) { + JSParseState *ps = ctx->parse_state; + + gc_thread_pointer(ctx, &ps->source_str); + gc_thread_pointer(ctx, &ps->filename_str); + gc_thread_pointer(ctx, &ps->token.value); + gc_thread_pointer(ctx, &ps->cur_func); + gc_thread_pointer(ctx, &ps->byte_code); + } + + /* pass 1: thread the pointers and update the previous ones */ + new_ptr = ctx->heap_base; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, new_ptr); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + gc_thread_block(ctx, ptr); + new_ptr += size; + } + ptr += size; + } + + /* pass 2: update the threaded pointers and move the block to its + final position */ + new_ptr = ctx->heap_base; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, new_ptr); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + if (new_ptr != ptr) { + memmove(new_ptr, ptr, size); + } + new_ptr += size; + } + ptr += size; + } + ctx->heap_free = new_ptr; + + /* update the source pointer in the parser */ + if (ctx->parse_state) { + JSParseState *ps = ctx->parse_state; + if (JS_IsPtr(ps->source_str)) { + JSString *p = JS_VALUE_TO_PTR(ps->source_str); + ps->source_buf = p->buf; + } + } + + /* rehash the object properties */ + /* XXX: try to do it in the previous pass (add a specific tag ?) */ + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) == JS_MTAG_OBJECT) { + js_rehash_props(ctx, (JSObject *)ptr, TRUE); + } + ptr += size; + } +} + +static void JS_GC2(JSContext *ctx, BOOL keep_atoms) +{ +#ifdef DUMP_GC + js_printf(ctx, "GC : heap size=%u/%u stack_size=%u\n", + (uint32_t)(ctx->heap_free - ctx->heap_base), + (uint32_t)(ctx->stack_top - ctx->heap_base), + (uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp)); +#endif +#if defined(DEBUG_GC) + /* reduce the dummy block size at each GC to change the addresses + after compaction */ + /* XXX: only works a finite number of times */ + { + JSByteArray *arr; + if (JS_IsPtr(ctx->dummy_block)) { + arr = JS_VALUE_TO_PTR(ctx->dummy_block); + if (arr->size >= 8) { + js_shrink_byte_array(ctx, &ctx->dummy_block, arr->size - 4); + if (arr->size == 4) { + js_printf(ctx, "WARNING: debug GC: no longer modifying the addresses\n"); + } + } + } + } +#endif + gc_mark_all(ctx, keep_atoms); + gc_compact_heap(ctx); +#ifdef DUMP_GC + js_printf(ctx, "AFTER: heap size=%u/%u stack_size=%u\n", + (uint32_t)(ctx->heap_free - ctx->heap_base), + (uint32_t)(ctx->stack_top - ctx->heap_base), + (uint32_t)(ctx->stack_top - (uint8_t *)ctx->sp)); +#endif +} + +void JS_GC(JSContext *ctx) +{ + JS_GC2(ctx, TRUE); +} + +/* bytecode saving and loading */ + +#define JS_BYTECODE_VERSION_32 0x0001 +/* bit 15 of bytecode version is a 64-bit indicator */ +#define JS_BYTECODE_VERSION (JS_BYTECODE_VERSION_32 | ((JSW & 8) << 12)) + +void JS_PrepareBytecode(JSContext *ctx, + JSBytecodeHeader *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code) +{ + JSGCRef eval_code_ref; + int i; + + /* remove all the objects except the compiled code */ + ctx->empty_props = JS_NULL; + for(i = 0; i < ctx->class_count; i++) { + ctx->class_proto[i] = JS_NULL; + ctx->class_obj[i] = JS_NULL; + } + ctx->global_obj = JS_NULL; +#ifdef DEBUG_GC + ctx->dummy_block = JS_NULL; +#endif + + JS_PUSH_VALUE(ctx, eval_code); + JS_GC2(ctx, FALSE); + JS_POP_VALUE(ctx, eval_code); + + hdr->magic = JS_BYTECODE_MAGIC; + hdr->version = JS_BYTECODE_VERSION; + hdr->base_addr = (uintptr_t)ctx->heap_base; + hdr->unique_strings = ctx->unique_strings; + hdr->main_func = eval_code; + + *pdata_buf = ctx->heap_base; + *pdata_len = ctx->heap_free - ctx->heap_base; +} + +#if JSW == 8 + +typedef uint32_t JSValue_32; +typedef uint32_t JSWord_32; + +#define JS_MB_HEADER_32 \ + JSWord_32 gc_mark: 1; \ + JSWord_32 mtag: (JS_MTAG_BITS - 1) + +#define JS_MB_PAD_32(n) (32 - (n)) + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS); +} JSMemBlockHeader_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS); + JSValue_32 arr[]; +} JSValueArray_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 size: JS_MB_PAD_32(JS_MTAG_BITS); + uint8_t buf[]; +} JSByteArray_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS); + /* unaligned 64 bit access in 32-bit mode */ + struct __attribute__((packed)) { + double dval; + } u; +} JSFloat64_32; + +#define JS_STRING_LEN_MAX_32 ((1 << (32 - JS_MTAG_BITS - 3)) - 1) + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 is_unique: 1; + JSWord_32 is_ascii: 1; + /* true if the string content represents a number, only meaningful + is is_unique = true */ + JSWord_32 is_numeric: 1; + JSWord_32 len: JS_MB_PAD_32(JS_MTAG_BITS + 3); + uint8_t buf[]; +} JSString_32; + +typedef struct { + JS_MB_HEADER_32; + JSWord_32 has_arguments : 1; /* only used during parsing */ + JSWord_32 has_local_func_name : 1; /* only used during parsing */ + JSWord_32 has_column : 1; /* column debug info is present */ + JSWord_32 arg_count : 16; + JSWord_32 dummy: JS_MB_PAD_32(JS_MTAG_BITS + 3 + 16); + + JSValue_32 func_name; /* JS_NULL if anonymous function */ + JSValue_32 byte_code; /* JS_NULL if the function is not parsed yet */ + JSValue_32 cpool; /* constant pool */ + JSValue_32 vars; /* only for debug */ + JSValue_32 ext_vars; /* records of (var_name, var_kind (2 bits) var_idx (16 bits)) */ + uint16_t stack_size; /* maximum stack size */ + uint16_t ext_vars_len; /* XXX: only used during parsing */ + JSValue_32 filename; /* filename in which the function is defined */ + JSValue_32 pc2line; /* JSByteArray or JS_NULL if not initialized */ + uint32_t source_pos; /* only used during parsing (XXX: shrink) */ +} JSFunctionBytecode_32; + +/* warning: ptr1 and ptr may overlap. However there is always: ptr1 <= ptr. Return 0 if OK. */ +static int convert_mblock_64to32(void *ptr1, const void *ptr) +{ + int mtag, i; + + mtag = ((JSMemBlockHeader*)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FUNCTION_BYTECODE: + { + const JSFunctionBytecode *b = ptr; + JSFunctionBytecode_32 *b1 = ptr1; + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->has_arguments = b->has_arguments; + b1->has_local_func_name = b->has_local_func_name; + b1->has_column = b->has_column; + b1->arg_count = b->arg_count; + b1->dummy = 0; + b1->func_name = b->func_name; + b1->byte_code = b->byte_code; + b1->cpool = b->cpool; + b1->vars = b->vars; + b1->ext_vars = b->ext_vars; + b1->stack_size = b->stack_size; + b1->ext_vars_len = b->ext_vars_len; + b1->filename = b->filename; + b1->pc2line = b->pc2line; + b1->source_pos = b->source_pos; + } + break; + case JS_MTAG_FLOAT64: + { + const JSFloat64 *b = ptr; + JSFloat64_32 *b1 = ptr1; + + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->dummy = 0; + b1->u.dval = b->u.dval; + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray *b = ptr; + JSValueArray_32 *b1 = ptr1; + + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->size = b->size; /* no test needed as long as JS_VALUE_ARRAY_SIZE_MAX is identical */ + for(i = 0; i < b1->size; i++) + b1->arr[i] = b->arr[i]; + } + break; + case JS_MTAG_BYTE_ARRAY: + { + const JSByteArray *b = ptr; + JSByteArray_32 *b1 = ptr1; + + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->size = b->size; /* no test needed as long as JS_BYTE_ARRAY_SIZE_MAX is identical */ + memmove(b1->buf, b->buf, b1->size); + } + break; + case JS_MTAG_STRING: + { + const JSString *b = ptr; + JSString_32 *b1 = ptr1; + + if (b->len > JS_STRING_LEN_MAX_32) + return -1; + b1->gc_mark = b->gc_mark; + b1->mtag = b->mtag; + b1->is_unique = b->is_unique; + b1->is_ascii = b->is_ascii; + b1->is_numeric = b->is_numeric; + b1->len = b->len; + memmove(b1->buf, b->buf, b1->len + 1); + } + break; + default: + abort(); + } + return 0; +} + +/* return the size in bytes */ +static int get_mblock_size_32(const void *ptr) +{ + int mtag = ((JSMemBlockHeader_32 *)ptr)->mtag; + int size; + switch(mtag) { + case JS_MTAG_FLOAT64: + size = sizeof(JSFloat64_32); + break; + case JS_MTAG_STRING: + { + const JSString_32 *p = ptr; + size = sizeof(JSString_32) + ((p->len + 4) & ~(4 - 1)); + } + break; + case JS_MTAG_BYTE_ARRAY: + { + const JSByteArray_32 *p = ptr; + size = sizeof(JSByteArray_32) + ((p->size + 4 - 1) & ~(4 - 1)); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + const JSValueArray_32 *p = ptr; + size = sizeof(JSValueArray_32) + p->size * sizeof(p->arr[0]); + } + break; + case JS_MTAG_FUNCTION_BYTECODE: + size = sizeof(JSFunctionBytecode_32); + break; + default: + size = 0; + assert(0); + } + return size; +} + +/* Compact and convert a 64 bit heap to a 32 bit heap at offset + 0. Only used for code compilation. Return 0 if OK. */ +static int gc_compact_heap_64to32(JSContext *ctx) +{ + uint8_t *ptr; + int size, size_32; + uintptr_t new_offset; + + gc_thread_pointer(ctx, &ctx->unique_strings); + + /* thread all the external pointers */ + { + JSGCRef *ref; + /* necessary because JS_PUSH_VAL() is called before + gc_compact_heap_64to32() */ + for(ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_thread_pointer(ctx, &ref->val); + } + } + + /* pass 1: thread the pointers and update the previous ones */ + new_offset = 0; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + gc_thread_block(ctx, ptr); + size_32 = get_mblock_size_32(ptr); + new_offset += size_32; + } + ptr += size; + } + + /* pass 2: update the threaded pointers and move the block to its + final position */ + new_offset = 0; + ptr = ctx->heap_base; + while (ptr < ctx->heap_free) { + gc_update_threaded_pointers(ctx, ptr, (uint8_t *)new_offset); + size = get_mblock_size(ptr); + if (js_get_mtag(ptr) != JS_MTAG_FREE) { + size_32 = get_mblock_size_32(ptr); + if (convert_mblock_64to32(ctx->heap_base + new_offset, ptr)) + return -1; + new_offset += size_32; + } + ptr += size; + } + ctx->heap_free = ctx->heap_base + new_offset; + return 0; +} + +#ifdef JS_USE_SHORT_FLOAT + +static int expand_short_float(JSContext *ctx, JSValue *pval) +{ + JSFloat64 *f; + if (JS_IsShortFloat(*pval)) { + f = js_malloc(ctx, sizeof(JSFloat64), JS_MTAG_FLOAT64); + if (!f) + return -1; + f->u.dval = js_get_short_float(*pval); + *pval = JS_VALUE_FROM_PTR(f); + } + return 0; +} + +/* Expand all the short floats to JSFloat64 structures. Return < 0 if + not enough memory. */ +static int expand_short_floats(JSContext *ctx) +{ + uint8_t *ptr, *p_end; + int mtag, size; + + ptr = ctx->heap_base; + p_end = ctx->heap_free; + while (ptr < p_end) { + size = get_mblock_size(ptr); + mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FUNCTION_BYTECODE: + /* we assume no short floats here */ + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *p = (JSValueArray *)ptr; + int i; + for(i = 0; i < p->size; i++) { + if (expand_short_float(ctx, &p->arr[i])) + return -1; + } + } + break; + case JS_MTAG_STRING: + case JS_MTAG_FLOAT64: + case JS_MTAG_BYTE_ARRAY: + break; + default: + abort(); + } + ptr += size; + } + return 0; +} + +#endif /* JS_USE_SHORT_FLOAT */ + +int JS_PrepareBytecode64to32(JSContext *ctx, + JSBytecodeHeader32 *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code) +{ + JSGCRef eval_code_ref; + int i; + + /* remove all the objects except the compiled code */ + ctx->empty_props = JS_NULL; + for(i = 0; i < ctx->class_count; i++) { + ctx->class_proto[i] = JS_NULL; + ctx->class_obj[i] = JS_NULL; + } + ctx->global_obj = JS_NULL; +#ifdef DEBUG_GC + ctx->dummy_block = JS_NULL; +#endif + + JS_PUSH_VALUE(ctx, eval_code); +#ifdef JS_USE_SHORT_FLOAT + JS_GC2(ctx, FALSE); + if (expand_short_floats(ctx)) + return -1; +#else + gc_mark_all(ctx, FALSE); +#endif + if (gc_compact_heap_64to32(ctx)) + return -1; + JS_POP_VALUE(ctx, eval_code); + + hdr->magic = JS_BYTECODE_MAGIC; + hdr->version = JS_BYTECODE_VERSION_32; + hdr->base_addr = 0; + hdr->unique_strings = ctx->unique_strings; + hdr->main_func = eval_code; + + *pdata_buf = ctx->heap_base; + *pdata_len = ctx->heap_free - ctx->heap_base; + /* ensure that JS_FreeContext() will do nothing */ + ctx->heap_free = ctx->heap_base; + return 0; +} +#endif /* JSW == 8 */ + +BOOL JS_IsBytecode(const uint8_t *buf, size_t buf_len) +{ + const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf; + return (buf_len >= sizeof(*hdr) && hdr->magic == JS_BYTECODE_MAGIC); +} + +typedef struct { + JSContext *ctx; + uintptr_t offset; + BOOL update_atoms; +} BCRelocState; + +static void bc_reloc_value(BCRelocState *s, JSValue *pval) +{ + JSContext *ctx = s->ctx; + JSString *p; + JSValue val, str; + + val = *pval; + if (JS_IsPtr(val)) { + val += s->offset; + + /* unique strings must be unique, so modify the unique string + value if it already exists in the context */ + if (s->update_atoms) { + p = JS_VALUE_TO_PTR(val); + if (p->mtag == JS_MTAG_STRING && p->is_unique) { + const JSValueArray *arr1; + int a, i; + for(i = 0; i < ctx->n_rom_atom_tables; i++) { + arr1 = ctx->rom_atom_tables[i]; + str = find_atom(ctx, &a, arr1, arr1->size, val); + if (!JS_IsNull(str)) { + val = str; + break; + } + } + } + } + *pval = val; + } +} + +int JS_RelocateBytecode2(JSContext *ctx, JSBytecodeHeader *hdr, + uint8_t *buf, uint32_t buf_len, + uintptr_t new_base_addr, BOOL update_atoms) +{ + uint8_t *ptr, *p_end; + int size, mtag; + BCRelocState ss, *s = &ss; + + if (hdr->magic != JS_BYTECODE_MAGIC) + return -1; + if (hdr->version != JS_BYTECODE_VERSION) + return -1; + + /* XXX: add atom checksum to avoid problems if the stdlib is + modified */ + s->ctx = ctx; + s->offset = new_base_addr - hdr->base_addr; + s->update_atoms = update_atoms; + + bc_reloc_value(s, &hdr->unique_strings); + bc_reloc_value(s, &hdr->main_func); + + ptr = buf; + p_end = buf + buf_len; + while (ptr < p_end) { + size = get_mblock_size(ptr); + mtag = ((JSMemBlockHeader *)ptr)->mtag; + switch(mtag) { + case JS_MTAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = (JSFunctionBytecode *)ptr; + bc_reloc_value(s, &b->func_name); + bc_reloc_value(s, &b->byte_code); + bc_reloc_value(s, &b->cpool); + bc_reloc_value(s, &b->vars); + bc_reloc_value(s, &b->ext_vars); + bc_reloc_value(s, &b->filename); + bc_reloc_value(s, &b->pc2line); + } + break; + case JS_MTAG_VALUE_ARRAY: + { + JSValueArray *p = (JSValueArray *)ptr; + int i; + for(i = 0; i < p->size; i++) { + bc_reloc_value(s, &p->arr[i]); + } + } + break; + case JS_MTAG_STRING: + case JS_MTAG_FLOAT64: + case JS_MTAG_BYTE_ARRAY: + break; + default: + abort(); + } + ptr += size; + } + hdr->base_addr = new_base_addr; + return 0; +} + +/* Relocate the bytecode in 'buf' so that it can be executed + later. Return 0 if OK, != 0 if error */ +int JS_RelocateBytecode(JSContext *ctx, + uint8_t *buf, uint32_t buf_len) +{ + uint8_t *data_ptr; + + if (buf_len < sizeof(JSBytecodeHeader)) + return -1; + data_ptr = buf + sizeof(JSBytecodeHeader); + return JS_RelocateBytecode2(ctx, (JSBytecodeHeader *)buf, + data_ptr, + buf_len - sizeof(JSBytecodeHeader), + (uintptr_t)data_ptr, TRUE); +} + +/* Load the precompiled bytecode from 'buf'. 'buf' must be allocated + as long as the JSContext exists. Use JS_Run() to execute + it. warning: the bytecode is not checked so it should come from a + trusted source. */ +JSValue JS_LoadBytecode(JSContext *ctx, const uint8_t *buf) +{ + const JSBytecodeHeader *hdr = (const JSBytecodeHeader *)buf; + + if (ctx->unique_strings_len != 0) + return JS_ThrowInternalError(ctx, "no atom must be defined in RAM"); + /* XXX: could stack atom_tables */ + if (ctx->n_rom_atom_tables >= N_ROM_ATOM_TABLES_MAX) + return JS_ThrowInternalError(ctx, "too many rom atom tables"); + if (hdr->magic != JS_BYTECODE_MAGIC) + return JS_ThrowInternalError(ctx, "invalid bytecode magic"); + if ((hdr->version & 0x8000) != (JS_BYTECODE_VERSION & 0x8000)) + return JS_ThrowInternalError(ctx, "bytecode not saved for %d-bit", JSW * 8); + if (hdr->version != JS_BYTECODE_VERSION) + return JS_ThrowInternalError(ctx, "invalid bytecode version"); + if (hdr->base_addr != (uintptr_t)(hdr + 1)) + return JS_ThrowInternalError(ctx, "bytecode not relocated"); + ctx->rom_atom_tables[ctx->n_rom_atom_tables++] = (JSValueArray *)JS_VALUE_TO_PTR(hdr->unique_strings); + return hdr->main_func; +} + +/**********************************************************************/ +/* runtime */ + +JSValue js_function_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + StringBuffer b_s, *b = &b_s; + JSValue val; + int i, n; + + argc &= ~FRAME_CF_CTOR; + string_buffer_push(ctx, b, 0); + string_buffer_puts(ctx, b, "(function anonymous("); + n = argc - 1; + for(i = 0; i < n; i++) { + if (i != 0) { + string_buffer_putc(ctx, b, ','); + } + if (string_buffer_concat(ctx, b, argv[i])) + goto done; + } + string_buffer_puts(ctx, b, "\n) {\n"); + if (n >= 0) { + if (string_buffer_concat(ctx, b, argv[n])) + goto done; + } + string_buffer_puts(ctx, b, "\n})"); + done: + val = string_buffer_pop(ctx, b); + if (JS_IsException(val)) + return val; + val = JS_Parse2(ctx, val, NULL, 0, "", JS_EVAL_RETVAL); + if (JS_IsException(val)) + return val; + return JS_Run(ctx, val); +} + +JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSGCRef obj_ref; + + if (!JS_IsPtr(*this_val)) { + if (JS_VALUE_GET_SPECIAL_TAG(*this_val) != JS_TAG_SHORT_FUNC) + goto fail; + return JS_UNDEFINED; + } else { + JSObject *p = JS_VALUE_TO_PTR(*this_val); + if (p->mtag != JS_MTAG_OBJECT) + goto fail; + if (p->class_id == JS_CLASS_CLOSURE) { + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return obj; + } else if (p->class_id == JS_CLASS_C_FUNCTION) { + /* for C constructors, the prototype property is already present */ + return JS_UNDEFINED; + } else { + fail: + return JS_ThrowTypeError(ctx, "not a function"); + } + JS_PUSH_VALUE(ctx, obj); + JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_constructor), + *this_val); + JS_POP_VALUE(ctx, obj); + JS_PUSH_VALUE(ctx, obj); + JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype), + obj); + JS_POP_VALUE(ctx, obj); + } + return obj; +} + +JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (!JS_IsFunctionObject(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a function"); + + JS_DefinePropertyValue(ctx, *this_val, js_get_atom(ctx, JS_ATOM_prototype), + argv[0]); + return JS_UNDEFINED; +} + +JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_name) +{ + JSFunctionBytecode *b; + JSValue ret = js_function_get_length_name1(ctx, this_val, is_name, &b); + if (JS_IsNull(ret)) + return JS_ThrowTypeError(ctx, "not a function"); + return ret; +} + +JSValue js_function_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue str, val; + JSGCRef str_ref; + + str = js_function_get_length_name(ctx, this_val, 0, NULL, 1); + if (JS_IsException(str)) + return str; + JS_PUSH_VALUE(ctx, str); + val = JS_NewString(ctx, "function "); + JS_POP_VALUE(ctx, str); + str = JS_ConcatString(ctx, val, str); + JS_PUSH_VALUE(ctx, str); + val = JS_NewString(ctx, "() {\n [native code]\n}"); + JS_POP_VALUE(ctx, str); + return JS_ConcatString(ctx, str, val); +} + +JSValue js_function_call(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int i; + argc = max_int(argc, 1); + if (JS_StackCheck(ctx, argc + 1)) + return JS_EXCEPTION; + for(i = 0; i < argc - 1; i++) + JS_PushArg(ctx, argv[argc - 1 - i]); + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, argv[0]); + /* we avoid recursing on the C stack */ + return JS_NewTailCall(argc - 1); +} + +JSValue js_function_apply(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValueArray *arr; + JSObject *p; + int len, i; + p = js_get_object_class(ctx, argv[1], JS_CLASS_ARRAY); + if (!p) + return JS_ThrowTypeError(ctx, "not an array"); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + len = p->u.array.len; + if (len > JS_MAX_ARGC) + return JS_ThrowTypeError(ctx, "too many call arguments"); + if (JS_StackCheck(ctx, len + 2)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(argv[1]); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < len; i++) + JS_PushArg(ctx, arr->arr[len - 1 - i]); + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, argv[0]); + /* we avoid recursing on the C stack */ + return JS_NewTailCall(len); +} + +JSValue js_function_bind(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int arg_count; + JSValueArray *arr; + int i; + + arg_count = max_int(argc - 1, 0); + arr = js_alloc_value_array(ctx, 0, 2 + arg_count); + if (!arr) + return JS_EXCEPTION; + /* arr[0] = func, arr[1] = this */ + arr->arr[0] = *this_val; + for(i = 0; i < arg_count + 1; i++) + arr->arr[1 + i] = argv[i]; + return JS_NewCFunctionParams(ctx, JS_CFUNCTION_bound, JS_VALUE_FROM_PTR(arr)); +} + +/* XXX: handle constructor case */ +JSValue js_function_bound(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, JSValue params) +{ + JSValueArray *arr; + JSGCRef params_ref; + int i, err, size, argc2; + + arr = JS_VALUE_TO_PTR(params); + size = arr->size; + JS_PUSH_VALUE(ctx, params); + err = JS_StackCheck(ctx, size + argc); + JS_POP_VALUE(ctx, params); + if (err) + return JS_EXCEPTION; + argc2 = size - 2 + argc; + if (argc2 > JS_MAX_ARGC) + return JS_ThrowTypeError(ctx, "too many call arguments"); + arr = JS_VALUE_TO_PTR(params); + for(i = argc - 1; i >= 0; i--) + JS_PushArg(ctx, argv[i]); + for(i = size - 1; i >= 2; i--) { + JS_PushArg(ctx, arr->arr[i]); + } + JS_PushArg(ctx, arr->arr[0]); /* func */ + JS_PushArg(ctx, arr->arr[1]); /* this_val */ + /* we avoid recursing on the C stack */ + return JS_NewTailCall(argc2); +} + +/**********************************************************************/ + +JSValue js_number_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + if (argc & FRAME_CF_CTOR) + return JS_ThrowTypeError(ctx, "number constructor not supported"); + if (argc == 0) { + return JS_NewShortInt(0); + } else { + if (JS_ToNumber(ctx, &d, argv[0])) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, d); + } +} + +static int js_thisNumberValue(JSContext *ctx, double *pres, JSValue val) +{ + if (!JS_IsNumber(ctx, val)) { + JS_ThrowTypeError(ctx, "not a number"); + return -1; + } + return JS_ToNumber(ctx, pres, val); +} + +JSValue js_number_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int radix, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0])) { + radix = 10; + } else { + if (JS_ToInt32Sat(ctx, &radix, argv[0])) + return JS_EXCEPTION; + if (radix < 2 || radix > 36) + return JS_ThrowRangeError(ctx, "radix must be between 2 and 36"); + } + /* cannot fail */ + flags = JS_DTOA_FORMAT_FREE; + if (radix != 10) + flags |= JS_DTOA_EXP_DISABLED; + return js_dtoa2(ctx, d, radix, 0, flags); +} + +JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int f, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &f, argv[0])) + return JS_EXCEPTION; + if (f < 0 || f > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + if (fabs(d) >= 1e21) { + flags = JS_DTOA_FORMAT_FREE; + } else { + flags = JS_DTOA_FORMAT_FRAC; + } + return js_dtoa2(ctx, d, 10, f, flags); +} + +JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int f, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &f, argv[0])) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0]) || !isfinite(d)) { + f = 0; + flags = JS_DTOA_FORMAT_FREE; + } else { + if (f < 0 || f > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + f++; + flags = JS_DTOA_FORMAT_FIXED; + } + return js_dtoa2(ctx, d, 10, f, flags | JS_DTOA_EXP_ENABLED); +} + +JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int p, flags; + double d; + + if (js_thisNumberValue(ctx, &d, *this_val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0])) { + flags = JS_DTOA_FORMAT_FREE; + p = 0; + } else { + if (JS_ToInt32Sat(ctx, &p, argv[0])) + return JS_EXCEPTION; + if (!isfinite(d)) { + flags = JS_DTOA_FORMAT_FREE; + } else { + if (p < 1 || p > 100) + return JS_ThrowRangeError(ctx, "invalid number of digits"); + flags = JS_DTOA_FORMAT_FIXED; + } + } + return js_dtoa2(ctx, d, 10, p, flags); +} + +JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int radix; + double d; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &radix, argv[1])) + return JS_EXCEPTION; + if (radix != 0 && (radix < 2 || radix > 36)) { + d = NAN; + } else { + if (js_atod1(ctx, &d, argv[0], radix, JS_ATOD_INT_ONLY)) + return JS_EXCEPTION; + } + return JS_NewFloat64(ctx, d); +} + +JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + if (js_atod1(ctx, &d, argv[0], 10, 0)) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, d); +} + +/**********************************************************************/ + +JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (argc & FRAME_CF_CTOR) + return JS_ThrowTypeError(ctx, "Boolean constructor not supported"); + return JS_NewBool(JS_ToBool(ctx, argv[0])); +} + +/**********************************************************************/ + +JSValue js_string_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int len; + + if (!JS_IsString(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a string"); + len = js_string_len(ctx, *this_val); + return JS_NewShortInt(len); +} + +JSValue js_string_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return JS_UNDEFINED; /* ignored */ +} + +JSValue js_string_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int len, start, end; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + len = js_string_len(ctx, *this_val); + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + end = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + return js_sub_string(ctx, *this_val, start, max_int(end, start)); +} + +JSValue js_string_substring(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int a, b, start, end, len; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + len = js_string_len(ctx, *this_val); + if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, 0)) + return JS_EXCEPTION; + b = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, len, 0)) + return JS_EXCEPTION; + } + if (a < b) { + start = a; + end = b; + } else { + start = b; + end = a; + } + return js_sub_string(ctx, *this_val, start, end); +} + +JSValue js_string_charAt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSValue ret; + int idx, c; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &idx, argv[0])) + return JS_EXCEPTION; + if (idx < 0) + goto ret_undef; + c = string_getcp(ctx, *this_val, idx, (magic == magic_codePointAt)); + if (c == -1) { + ret_undef: + if (magic == magic_charCodeAt) + ret = JS_NewFloat64(ctx, NAN); + else if (magic == magic_charAt) + ret = js_get_atom(ctx, JS_ATOM_empty); + else + ret = JS_UNDEFINED; + } else { + if (magic == magic_charCodeAt || magic == magic_codePointAt) + ret = JS_NewShortInt(c); + else + ret = JS_NewStringChar(c); + } + // dump_string_pos_cache(ctx); + return ret; +} + +JSValue js_string_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (argc & FRAME_CF_CTOR) + return JS_ThrowTypeError(ctx, "string constructor not supported"); + if (argc <= 0) { + return js_get_atom(ctx, JS_ATOM_empty); + } else { + return JS_ToString(ctx, argv[0]); + } +} + +JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_fromCodePoint) +{ + int i; + StringBuffer b_s, *b = &b_s; + + string_buffer_push(ctx, b, 0); + for(i = 0; i < argc; i++) { + int c; + if (JS_ToInt32(ctx, &c, argv[i])) + goto fail; + if (is_fromCodePoint) { + if (c < 0 || c > 0x10ffff) { + JS_ThrowRangeError(ctx, "invalid code point"); + goto fail; + } + } else { + c &= 0xffff; + } + if (string_buffer_putc(ctx, b, c)) + break; + } + return string_buffer_pop(ctx, b); + fail: + string_buffer_pop(ctx, b); + return JS_EXCEPTION; +} + +JSValue js_string_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int i; + StringBuffer b_s, *b = &b_s; + JSValue r; + + r = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(r)) + return JS_EXCEPTION; + string_buffer_push(ctx, b, 0); + if (string_buffer_concat(ctx, b, r)) + goto done; + + for (i = 0; i < argc; i++) { + if (string_buffer_concat(ctx, b, argv[i])) + goto done; + } + done: + return string_buffer_pop(ctx, b); +} + +JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int lastIndexOf) +{ + int i, len, v_len, pos, start, stop, ret, inc, j; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + len = js_string_len(ctx, *this_val); + v_len = js_string_len(ctx, argv[0]); + if (lastIndexOf) { + pos = len - v_len; + if (argc > 1) { + double d; + if (JS_ToNumber(ctx, &d, argv[1])) + goto fail; + if (!isnan(d)) { + if (d <= 0) + pos = 0; + else if (d < pos) + pos = d; + } + } + start = pos; + stop = 0; + inc = -1; + } else { + pos = 0; + if (argc > 1) { + if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) + goto fail; + } + start = pos; + stop = len - v_len; + inc = 1; + } + ret = -1; + if (len >= v_len && inc * (stop - start) >= 0) { + for (i = start;; i += inc) { + for(j = 0; j < v_len; j++) { + if (string_getc(ctx, *this_val, i + j) != string_getc(ctx, argv[0], j)) { + goto next; + } + } + ret = i; + break; + next: + if (i == stop) + break; + } + } + return JS_NewShortInt(ret); + +fail: + return JS_EXCEPTION; +} + +static int js_string_indexof(JSContext *ctx, JSValue str, JSValue needle, + int start, int str_len, int needle_len) +{ + int i, j; + for(i = start; i <= str_len - needle_len; i++) { + for(j = 0; j < needle_len; j++) { + if (string_getc(ctx, str, i + j) != + string_getc(ctx, needle, j)) { + goto next; + } + + } + return i; + next: ; + } + return -1; +} + +/* Note: ascii only */ +JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int to_lower) +{ + StringBuffer b_s, *b = &b_s; + int i, c, len; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return *this_val; + len = js_string_len(ctx, *this_val); + if (string_buffer_push(ctx, b, len)) + return JS_EXCEPTION; + for(i = 0; i < len; i++) { + c = string_getc(ctx, *this_val, i); + if (to_lower) { + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + } else { + if (c >= 'a' && c <= 'z') + c += 'A' - 'a'; + } + string_buffer_putc(ctx, b, c); + } + return string_buffer_pop(ctx, b); +} + +/* c < 128 */ +static force_inline BOOL unicode_is_space_ascii(uint32_t c) +{ + return (c >= 0x0009 && c <= 0x000D) || (c == 0x0020); +} + +static BOOL unicode_is_space_non_ascii(uint32_t c) +{ + return (c == 0x00A0 || + c == 0x1680 || + (c >= 0x2000 && c <= 0x200A) || + (c >= 0x2028 && c <= 0x2029) || + c == 0x202F || + c == 0x205F || + c == 0x3000 || + c == 0xFEFF); +} + +static force_inline BOOL unicode_is_space(uint32_t c) +{ + if (likely(c < 128)) { + return unicode_is_space_ascii(c); + } else { + return unicode_is_space_non_ascii(c); + } +} + +JSValue js_string_trim(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + int a, b, len; + + *this_val = JS_ToStringCheckObject(ctx, *this_val); + if (JS_IsException(*this_val)) + return *this_val; + len = js_string_len(ctx, *this_val); + a = 0; + b = len; + if (magic & 1) { + while (a < len && unicode_is_space(string_getc(ctx, *this_val, a))) + a++; + } + if (magic & 2) { + while (b > a && unicode_is_space(string_getc(ctx, *this_val, b - 1))) + b--; + } + return js_sub_string(ctx, *this_val, a, b); +} + +JSValue js_string_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + if (!JS_IsString(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a string"); + return *this_val; +} + +JSValue js_string_repeat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + StringBuffer b_s, *b = &b_s; + JSStringCharBuf buf; + JSString *p; + int n; + int64_t len; + + if (!JS_IsString(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not a string"); + if (JS_ToInt32Sat(ctx, &n, argv[0])) + return -1; + p = get_string_ptr(ctx, &buf, *this_val); + if (n < 0 || (len = (int64_t)n * p->len) > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid repeat count"); + if (p->len == 0 || n == 1) + return *this_val; + if (string_buffer_push(ctx, b, len)) + return JS_EXCEPTION; + while (n-- > 0) { + string_buffer_concat_str(ctx, b, *this_val); + } + return string_buffer_pop(ctx, b); +} + +/**********************************************************************/ + +JSValue js_object_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + /* XXX: incomplete */ + argc &= ~FRAME_CF_CTOR; + if (argc <= 0) { + return JS_NewObject(ctx); + } else { + return argv[0]; + } +} + +JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue *pobj, *pprop, *pdesc; + JSValue val, getter, setter; + JSGCRef val_ref, getter_ref; + int flags; + + pobj = &argv[0]; + pprop = &argv[1]; + pdesc = &argv[2]; + + if (!JS_IsObject(ctx, *pobj)) + return JS_ThrowTypeErrorNotAnObject(ctx); + *pprop = JS_ToPropertyKey(ctx, *pprop); + if (JS_IsException(*pprop)) + return JS_EXCEPTION; + val = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + flags = 0; + if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value))) { + flags |= JS_DEF_PROP_HAS_VALUE; + val = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_value)); + if (JS_IsException(val)) + return JS_EXCEPTION; + } + if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get))) { + flags |= JS_DEF_PROP_HAS_GET; + JS_PUSH_VALUE(ctx, val); + getter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_get)); + JS_POP_VALUE(ctx, val); + if (JS_IsException(getter)) + return JS_EXCEPTION; + if (!JS_IsUndefined(getter) && !JS_IsFunction(ctx, getter)) + goto bad_getset; + } + if (JS_HasProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set))) { + flags |= JS_DEF_PROP_HAS_SET; + JS_PUSH_VALUE(ctx, val); + JS_PUSH_VALUE(ctx, getter); + setter = JS_GetProperty(ctx, *pdesc, js_get_atom(ctx, JS_ATOM_set)); + JS_POP_VALUE(ctx, getter); + JS_POP_VALUE(ctx, val); + if (JS_IsException(setter)) + return JS_EXCEPTION; + if (!JS_IsUndefined(setter) && !JS_IsFunction(ctx, setter)) { + bad_getset: + return JS_ThrowTypeError(ctx, "invalid getter or setter"); + } + } + if (flags & (JS_DEF_PROP_HAS_GET | JS_DEF_PROP_HAS_SET)) { + if (flags & JS_DEF_PROP_HAS_VALUE) + return JS_ThrowTypeError(ctx, "cannot have both value and get/set"); + val = getter; + } + val = JS_DefinePropertyInternal(ctx, *pobj, *pprop, val, setter, + flags | JS_DEF_PROP_LOOKUP); + if (JS_IsException(val)) + return val; + return *pobj; +} + +JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(argv[0]); + return p->proto; +} + +/* 'obj' must be an object. 'proto' must be JS_NULL or an object */ +static JSValue js_set_prototype_internal(JSContext *ctx, JSValue obj, JSValue proto) +{ + JSObject *p, *p1; + + p = JS_VALUE_TO_PTR(obj); + if (p->proto != proto) { + if (proto != JS_NULL) { + /* check if there is a cycle */ + p1 = JS_VALUE_TO_PTR(proto); + for(;;) { + if (p1 == p) + return JS_ThrowTypeError(ctx, "circular prototype chain"); + if (p1->proto == JS_NULL) + break; + p1 = JS_VALUE_TO_PTR(p1->proto); + } + } + + p->proto = proto; + } + return JS_UNDEFINED; +} + +JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue proto; + + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + proto = argv[1]; + if (proto != JS_NULL && !JS_IsObject(ctx, proto)) + return JS_ThrowTypeError(ctx, "not a prototype"); + if (JS_IsException(js_set_prototype_internal(ctx, argv[0], proto))) + return JS_EXCEPTION; + return argv[0]; +} + +JSValue js_object_create(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue proto; + proto = argv[0]; + if (proto != JS_NULL && !JS_IsObject(ctx, proto)) + return JS_ThrowTypeError(ctx, "not a prototype"); + if (argc >= 2) + return JS_ThrowTypeError(ctx, "unsupported additional properties"); + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT, 0); +} + +JSValue js_object_keys(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *pret; + JSValue ret, str; + JSValueArray *arr, *ret_arr; + int array_len, prop_count, hash_mask, alloc_size, i, j, pos; + JSGCRef ret_ref; + + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(argv[0]); + + if (p->class_id == JS_CLASS_ARRAY) { + array_len = p->u.array.len; + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + array_len = p->u.typed_array.len; + } else { + array_len = 0; + } + + arr = JS_VALUE_TO_PTR(p->props); + prop_count = JS_VALUE_GET_INT(arr->arr[0]); + hash_mask = JS_VALUE_GET_INT(arr->arr[1]); + + alloc_size = array_len + prop_count; + + ret = JS_NewArray(ctx, alloc_size); + if (JS_IsException(ret)) + return ret; + + pos = 0; + for(i = 0; i < array_len; i++) { + JS_PUSH_VALUE(ctx, ret); + str = JS_ToString(ctx, JS_NewShortInt(i)); + JS_POP_VALUE(ctx, ret); + if (JS_IsException(str)) + return str; + pret = JS_VALUE_TO_PTR(ret); + ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab); + ret_arr->arr[pos++] = str; + } + + for(i = 0, j = 0; j < prop_count; i++) { + JSProperty *pr; + p = JS_VALUE_TO_PTR(argv[0]); + arr = JS_VALUE_TO_PTR(p->props); + pr = (JSProperty *)&arr->arr[2 + hash_mask + 1 + 3 * i]; + /* exclude deleted properties */ + if (pr->key != JS_UNINITIALIZED) { + JS_PUSH_VALUE(ctx, ret); + str = JS_ToString(ctx, pr->key); + JS_POP_VALUE(ctx, ret); + if (JS_IsException(str)) + return str; + pret = JS_VALUE_TO_PTR(ret); + ret_arr = JS_VALUE_TO_PTR(pret->u.array.tab); + ret_arr->arr[pos++] = str; + j++; + } + } + pret = JS_VALUE_TO_PTR(ret); + pret->u.array.len = pos; + return ret; +} + +JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue prop; + int array_len, idx; + + if (JS_IsNull(*this_val) || JS_IsUndefined(*this_val)) + return JS_ThrowTypeError(ctx, "cannot convert to object"); + if (!JS_IsObject(ctx, *this_val)) + return JS_FALSE; /* XXX: could improve for strings */ + prop = JS_ToPropertyKey(ctx, argv[0]); + p = JS_VALUE_TO_PTR(*this_val); + if (p->class_id == JS_CLASS_ARRAY) { + array_len = p->u.array.len; + goto check_array; + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + array_len = p->u.typed_array.len; + check_array: + if (JS_IsInt(prop)) { + idx = JS_VALUE_GET_INT(prop); + return JS_NewBool((idx >= 0 && idx < array_len)); + } + } + return JS_NewBool((find_own_property(ctx, p, prop) != NULL)); +} + +JSValue js_object_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + const char *str; + char buf[64]; + /* XXX: not fully compliant */ + if (JS_IsIntOrShortFloat(*this_val)) { + goto number; + } else if (!JS_IsPtr(*this_val)) { + switch(JS_VALUE_GET_SPECIAL_TAG(*this_val)) { + case JS_TAG_NULL: + str = "Null"; + break; + case JS_TAG_UNDEFINED: + str = "Undefined"; + break; + case JS_TAG_SHORT_FUNC: + str = "Function"; + break; + case JS_TAG_BOOL: + str = "Boolean"; + break; + case JS_TAG_STRING_CHAR: + goto string; + default: + goto object; + } + } else { + JSObject *p = JS_VALUE_TO_PTR(*this_val); + switch(p->mtag) { + case JS_MTAG_OBJECT: + switch(p->class_id) { + case JS_CLASS_ARRAY: + str = "Array"; + break; + case JS_CLASS_ERROR: + str = "Error"; + break; + case JS_CLASS_CLOSURE: + case JS_CLASS_C_FUNCTION: + str = "Function"; + break; + default: + object: + str = "Object"; + break; + } + break; + case JS_MTAG_STRING: + string: + str = "String"; + break; + case JS_MTAG_FLOAT64: + number: + str = "Number"; + break; + default: + goto object; + } + } + js_snprintf(buf, sizeof(buf), "[object %s]", str); + return JS_NewString(ctx, buf); +} + +/**********************************************************************/ + +JSValue js_error_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSValue obj, msg; + JSObject *p; + JSGCRef obj_ref; + + argc &= ~FRAME_CF_CTOR; + + obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[magic], JS_CLASS_ERROR, + sizeof(JSErrorData)); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.error.message = JS_NULL; + p->u.error.stack = JS_NULL; + + if (!JS_IsUndefined(argv[0])) { + JS_PUSH_VALUE(ctx, obj); + msg = JS_ToString(ctx, argv[0]); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(msg)) + return msg; + p = JS_VALUE_TO_PTR(obj); + p->u.error.message = msg; + } else { + p = JS_VALUE_TO_PTR(obj); + p->u.error.message = js_get_atom(ctx, JS_ATOM_empty); + } + JS_PUSH_VALUE(ctx, obj); + build_backtrace(ctx, obj, NULL, 0, 0, 1); + JS_POP_VALUE(ctx, obj); + return obj; +} + +JSValue js_error_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue name; + StringBuffer b_s, *b = &b_s; + + if (!JS_IsError(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not an Error object"); + name = JS_GetProperty(ctx, *this_val, js_get_atom(ctx, JS_ATOM_name)); + if (JS_IsException(name)) + return name; + if (JS_IsUndefined(name)) + name = js_get_atom(ctx, JS_ATOM_Error); + else + name = JS_ToString(ctx, name); + if (JS_IsException(name)) + return name; + string_buffer_push(ctx, b, 0); + string_buffer_concat(ctx, b, name); + p = JS_VALUE_TO_PTR(*this_val); + if (p->u.error.message != JS_NULL) { + string_buffer_puts(ctx, b, ": "); + p = JS_VALUE_TO_PTR(*this_val); + string_buffer_concat(ctx, b, p->u.error.message); + } + return string_buffer_pop(ctx, b); +} + +JSValue js_error_get_message(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSObject *p; + if (!JS_IsError(ctx, *this_val)) + return JS_ThrowTypeError(ctx, "not an Error object"); + p = JS_VALUE_TO_PTR(*this_val); + if (magic == 0) + return p->u.error.message; + else + return p->u.error.stack; +} + +/**********************************************************************/ + +static JSObject *js_get_array(JSContext *ctx, JSValue obj) +{ + JSObject *p; + p = js_get_object_class(ctx, obj, JS_CLASS_ARRAY); + if (!p) { + JS_ThrowTypeError(ctx, "not an array"); + return NULL; + } + return p; +} + +JSValue js_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + return JS_NewShortInt(p->u.array.len); +} + +static int js_array_resize(JSContext *ctx, JSValue *this_val, int new_len) +{ + JSObject *p; + int i; + + if (new_len < 0 || new_len > JS_SHORTINT_MAX) { + JS_ThrowTypeError(ctx, "invalid array length"); + return -1; + } + p = JS_VALUE_TO_PTR(*this_val); + if (new_len < p->u.array.len) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* shrink the array if the new size is small enough */ + if (new_len < (arr->size / 2) && arr->size >= 4) { + js_shrink_value_array(ctx, &p->u.array.tab, new_len); + p = JS_VALUE_TO_PTR(*this_val); + } else { + for(i = new_len; i < p->u.array.len; i++) + arr->arr[i] = JS_UNDEFINED; + } + } else if (new_len > p->u.array.len) { + JSValueArray *arr; + JSValue new_tab; + new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len); + if (JS_IsException(new_tab)) + return -1; + p = JS_VALUE_TO_PTR(*this_val); + p->u.array.tab = new_tab; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = p->u.array.len; i < new_len; i++) + arr->arr[i] = JS_UNDEFINED; + } + p->u.array.len = new_len; + return 0; +} + +JSValue js_array_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int new_len; + + if (!js_get_array(ctx, *this_val)) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &new_len, argv[0])) + return JS_EXCEPTION; + if (js_array_resize(ctx, this_val, new_len)) + return JS_EXCEPTION; + return JS_UNDEFINED; +} + +JSValue js_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSObject *p; + int len, i; + BOOL has_init; + + argc &= ~FRAME_CF_CTOR; + + if (argc == 1 && JS_IsNumber(ctx, argv[0])) { + /* XXX: we create undefined properties instead of just setting the length */ + if (JS_ToInt32(ctx, &len, argv[0])) + return JS_EXCEPTION; + has_init = FALSE; + } else { + len = argc; + has_init = TRUE; + } + + if (len < 0 || len > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + obj = JS_NewArray(ctx, len); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.array.len = len; + + if (has_init) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + for(i = 0; i < argc; i++) { + arr->arr[i] = argv[i]; + } + } + return obj; +} + +JSValue js_array_push(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_unshift) +{ + JSObject *p; + int new_len, i, from; + JSValueArray *arr; + JSValue new_tab; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + from = p->u.array.len; + new_len = from + argc; + if (new_len > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array length"); + new_tab = js_resize_value_array(ctx, p->u.array.tab, new_len); + if (JS_IsException(new_tab)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(*this_val); + p->u.array.tab = new_tab; + p->u.array.len = new_len; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (is_unshift && argc > 0) { + memmove(arr->arr + argc, arr->arr, from * sizeof(JSValue)); + from = 0; + } + for(i = 0; i < argc; i++) { + arr->arr[from + i] = argv[i]; + } + return JS_NewShortInt(new_len); +} + +JSValue js_array_pop(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue ret; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + if (p->u.array.len > 0) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + ret = arr->arr[--p->u.array.len]; + } else { + ret = JS_UNDEFINED; + } + return ret; +} + +JSValue js_array_shift(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSValue ret; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + if (p->u.array.len > 0) { + JSValueArray *arr = JS_VALUE_TO_PTR(p->u.array.tab); + ret = arr->arr[0]; + p->u.array.len--; + memmove(arr->arr, arr->arr + 1, p->u.array.len * sizeof(JSValue)); + } else { + ret = JS_UNDEFINED; + } + return ret; +} + +JSValue js_array_join(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + uint32_t i, len; + BOOL is_array; + JSValue sep, val; + JSGCRef sep_ref; + JSObject *p; + JSValueArray *arr; + StringBuffer b_s, *b = &b_s; + + if (!JS_IsObject(ctx, *this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(*this_val); + is_array = (p->class_id == JS_CLASS_ARRAY); + if (is_array) { + len = p->u.array.len; + } else { + if (js_get_length32(ctx, &len, *this_val)) + return JS_EXCEPTION; + } + + if (argc > 0 && !JS_IsUndefined(argv[0])) { + sep = JS_ToString(ctx, argv[0]); + if (JS_IsException(sep)) + return sep; + } else { + sep = JS_NewStringChar(','); + } + JS_PUSH_VALUE(ctx, sep); + + string_buffer_push(ctx, b, 0); + for(i = 0; i < len; i++) { + if (i > 0) { + if (string_buffer_concat(ctx, b, sep_ref.val)) + goto exception; + } + if (is_array) { + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (i < p->u.array.len) + val = arr->arr[i]; + else + val = JS_UNDEFINED; + } else { + val = JS_GetPropertyUint32(ctx, *this_val, i); + if (JS_IsException(val)) + goto exception; + } + if (!JS_IsUndefined(val) && !JS_IsNull(val)) { + if (string_buffer_concat(ctx, b, val)) + goto exception; + } + } + val = string_buffer_pop(ctx, b); + JS_POP_VALUE(ctx, sep); + return val; + + exception: + string_buffer_pop(ctx, b); + JS_POP_VALUE(ctx, sep); + return JS_EXCEPTION; +} + +JSValue js_array_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return js_array_join(ctx, this_val, 0, NULL); +} + +JSValue js_array_isArray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + p = js_get_object_class(ctx, argv[0], JS_CLASS_ARRAY); + return JS_NewBool(p != NULL); +} + +JSValue js_array_reverse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int len; + JSObject *p; + JSValueArray *arr; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + js_reverse_val(arr->arr, len); + return *this_val; +} + +JSValue js_array_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int len, i, j, pos; + int64_t len64; + JSValue obj, val; + JSValueArray *arr, *arr1; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + /* do a first pass to estimate the length */ + len64 = p->u.array.len; + for(i = 0; i < argc; i++) { + p = js_get_object_class(ctx, argv[i], JS_CLASS_ARRAY); + if (p) { + len64 += p->u.array.len; + } else { + len64++; + } + } + if (len64 > JS_SHORTINT_MAX) + return JS_ThrowTypeError(ctx, "Array loo long"); + len = len64; + + obj = JS_NewArray(ctx, len); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + + pos = 0; + for(i = -1; i < argc; i++) { + val = i == -1 ? *this_val : argv[i]; + p = js_get_object_class(ctx, val, JS_CLASS_ARRAY); + if (p) { + arr1 = JS_VALUE_TO_PTR(p->u.array.tab); + for(j = 0; j < p->u.array.len; j++) + arr->arr[pos + j] = arr1->arr[j]; + pos += p->u.array.len; + } else { + arr->arr[pos++] = val; + } + } + return obj; +} + +JSValue js_array_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_lastIndexOf) +{ + JSObject *p; + int len, n, res; + JSValueArray *arr; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + if (is_lastIndexOf) { + n = len - 1; + } else { + n = 0; + } + if (argc > 1) { + if (JS_ToInt32Clamp(ctx, &n, argv[1], + -is_lastIndexOf, len - is_lastIndexOf, len)) + return JS_EXCEPTION; + } + /* the array may be modified */ + p = JS_VALUE_TO_PTR(*this_val); + len = p->u.array.len; /* the length may be modified */ + arr = JS_VALUE_TO_PTR(p->u.array.tab); + res = -1; + if (is_lastIndexOf) { + n = min_int(n, len - 1); + for(;n >= 0; n--) { + if (js_strict_eq(ctx, argv[0], arr->arr[n])) { + res = n; + break; + } + } + } else { + for(;n < len; n++) { + if (js_strict_eq(ctx, argv[0], arr->arr[n])) { + res = n; + break; + } + } + } + return JS_NewShortInt(res); +} + +JSValue js_array_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + int len, start, final, k; + JSValueArray *arr, *arr1; + JSValue obj; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + final = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + /* the array may have been modified */ + p = JS_VALUE_TO_PTR(*this_val); + len = p->u.array.len; /* the length may be modified */ + final = min_int(final, len); + + obj = JS_NewArray(ctx, max_int(final - start, 0)); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + p1 = JS_VALUE_TO_PTR(obj); + arr1 = JS_VALUE_TO_PTR(p1->u.array.tab); + for(k = start; k < final; k++) { + arr1->arr[k - start] = arr->arr[k]; + } + return obj; +} + +JSValue js_array_splice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + int start, len, item_count, del_count, new_len, i, ret; + JSValueArray *arr, *arr1; + JSValue obj; + JSGCRef obj_ref; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + + if (argc == 0) { + item_count = 0; + del_count = 0; + } else if (argc == 1) { + item_count = 0; + del_count = len - start; + } else { + item_count = argc - 2; + if (JS_ToInt32Clamp(ctx, &del_count, argv[1], 0, len - start, 0)) + return JS_EXCEPTION; + } + new_len = len + item_count - del_count; + + obj = JS_NewArray(ctx, del_count); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(*this_val); + /* handling this case has no practical use */ + if (p->u.array.len != len) + return JS_ThrowTypeError(ctx, "array length was modified"); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + p1 = JS_VALUE_TO_PTR(obj); + arr1 = JS_VALUE_TO_PTR(p1->u.array.tab); + + for(i = 0; i < del_count; i++) { + arr1->arr[i] = arr->arr[start + i]; + } + + if (item_count != del_count) { + /* resize */ + if (del_count > item_count) { + memmove(arr->arr + start + item_count, + arr->arr + start + del_count, + (len - (start + del_count)) * sizeof(JSValue)); + } + JS_PUSH_VALUE(ctx, obj); + ret = js_array_resize(ctx, this_val, new_len); + JS_POP_VALUE(ctx, obj); + if (ret) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + if (del_count < item_count) { + memmove(arr->arr + start + item_count, + arr->arr + start + del_count, + (len - (start + del_count)) * sizeof(JSValue)); + } + } + + for(i = 0; i < item_count; i++) + arr->arr[start + i] = argv[2 + i]; + + return obj; +} + +JSValue js_array_every(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special) +{ + JSObject *p; + JSValueArray *arr; + JSValue res, ret, val; + JSValue *pfunc, *pthis_arg; + JSGCRef val_ref, ret_ref; + int len, k, n; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + + pfunc = &argv[0]; + pthis_arg = NULL; + if (argc > 1) + pthis_arg = &argv[1]; + + if (!JS_IsFunction(ctx, *pfunc)) + return JS_ThrowTypeError(ctx, "not a function"); + + switch (special) { + case js_special_every: + ret = JS_TRUE; + break; + case js_special_some: + ret = JS_FALSE; + break; + case js_special_map: + ret = JS_NewArray(ctx, len); + if (JS_IsException(ret)) + return JS_EXCEPTION; + break; + case js_special_filter: + ret = JS_NewArray(ctx, 0); + if (JS_IsException(ret)) + return JS_EXCEPTION; + break; + case js_special_forEach: + default: + ret = JS_UNDEFINED; + break; + } + n = 0; + + JS_PUSH_VALUE(ctx, ret); + for(k = 0; k < len; k++) { + if (JS_StackCheck(ctx, 5)) + goto exception; + + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* the array length may have been modified by the function call*/ + if (k >= p->u.array.len) + break; + val = arr->arr[k]; + + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, JS_NewShortInt(k)); + JS_PushArg(ctx, val); /* arg0 */ + JS_PushArg(ctx, *pfunc); /* func */ + JS_PushArg(ctx, pthis_arg ? *pthis_arg : JS_UNDEFINED); /* this */ + JS_PUSH_VALUE(ctx, val); + res = JS_Call(ctx, 3); + JS_POP_VALUE(ctx, val); + if (JS_IsException(res)) + goto exception; + + switch (special) { + case js_special_every: + if (!JS_ToBool(ctx, res)) { + ret_ref.val = JS_FALSE; + goto done; + } + break; + case js_special_some: + if (JS_ToBool(ctx, res)) { + ret_ref.val = JS_TRUE; + goto done; + } + break; + case js_special_map: + /* Note: same as defineProperty for arrays */ + res = JS_SetPropertyUint32(ctx, ret_ref.val, k, res); + if (JS_IsException(res)) + goto exception; + break; + case js_special_filter: + if (JS_ToBool(ctx, res)) { + res = JS_SetPropertyUint32(ctx, ret_ref.val, n++, val); + if (JS_IsException(res)) + goto exception; + } + break; + case js_special_forEach: + default: + break; + } + } +done: + JS_POP_VALUE(ctx, ret); + return ret; + exception: + ret_ref.val = JS_EXCEPTION; + goto done; +} + +JSValue js_array_reduce(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special) +{ + JSObject *p; + JSValueArray *arr; + JSValue acc, *pfunc; + JSGCRef acc_ref; + int len, k, k1, ret; + + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.array.len; + pfunc = &argv[0]; + + if (!JS_IsFunction(ctx, *pfunc)) + return JS_ThrowTypeError(ctx, "not a function"); + + k = 0; + if (argc > 1) { + acc = argv[1]; + } else { + if (len == 0) + return JS_ThrowTypeError(ctx, "empty array"); + k1 = (special == js_special_reduceRight) ? len - k - 1 : k; + arr = JS_VALUE_TO_PTR(p->u.array.tab); + acc = arr->arr[k1]; + k++; + } + for (; k < len; k++) { + JS_PUSH_VALUE(ctx, acc); + ret = JS_StackCheck(ctx, 6); + JS_POP_VALUE(ctx, acc); + if (ret) + return JS_EXCEPTION; + + k1 = (special == js_special_reduceRight) ? len - k - 1 : k; + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* Note: the array length may have been modified, hence the check */ + if (k1 >= p->u.array.len) + break; + + JS_PushArg(ctx, *this_val); + JS_PushArg(ctx, JS_NewShortInt(k1)); + JS_PushArg(ctx, arr->arr[k1]); + JS_PushArg(ctx, acc); /* arg0 */ + JS_PushArg(ctx, *pfunc); /* func */ + JS_PushArg(ctx, JS_UNDEFINED); /* this */ + acc = JS_Call(ctx, 4); + if (JS_IsException(acc)) + return JS_EXCEPTION; + } + return acc; +} + +/* heapsort algorithm */ +static void rqsort_idx(size_t nmemb, + int (*cmp)(size_t, size_t, void *), + void (*swap)(size_t, size_t, void *), + void *opaque) +{ + size_t i, n, c, r, size; + + size = 1; + if (nmemb > 1) { + i = (nmemb / 2) * size; + n = nmemb * size; + + while (i > 0) { + i -= size; + for (r = i; (c = r * 2 + size) < n; r = c) { + if (c < n - size && cmp(c, c + size, opaque) <= 0) + c += size; + if (cmp(r, c, opaque) > 0) + break; + swap(r, c, opaque); + } + } + for (i = n - size; i > 0; i -= size) { + swap(0, i, opaque); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(c, c + size, opaque) <= 0) + c += size; + if (cmp(r, c, opaque) > 0) + break; + swap(r, c, opaque); + } + } + } +} + +typedef struct { + JSContext *ctx; + BOOL exception; + JSValue *parr; + JSValue *pfunc; +} JSArraySortContext; + +/* return -1, 0, 1 */ +static int js_array_sort_cmp(size_t i1, size_t i2, void *opaque) +{ + JSArraySortContext *s = opaque; + JSContext *ctx = s->ctx; + JSValueArray *arr; + int cmp, j1, j2; + + if (s->exception) + return 0; + + arr = JS_VALUE_TO_PTR(*s->parr); + if (s->pfunc) { + JSValue res; + /* custom sort function is specified as returning 0 for identical + * objects: avoid method call overhead. + */ + if (arr->arr[2 * i1] == arr->arr[2 * i2]) + goto cmp_same; + if (JS_StackCheck(ctx, 4)) + goto exception; + arr = JS_VALUE_TO_PTR(*s->parr); + + JS_PushArg(ctx, arr->arr[2 * i2]); + JS_PushArg(ctx, arr->arr[2 * i1]); /* arg0 */ + JS_PushArg(ctx, *s->pfunc); /* func */ + JS_PushArg(ctx, JS_UNDEFINED); /* this */ + res = JS_Call(ctx, 2); + if (JS_IsException(res)) + return JS_EXCEPTION; + if (JS_IsInt(res)) { + int val = JS_VALUE_GET_INT(res); + cmp = (val > 0) - (val < 0); + } else { + double val; + if (JS_ToNumber(ctx, &val, res)) + goto exception; + cmp = (val > 0) - (val < 0); + } + } else { + JSValue str1, str2; + JSGCRef str1_ref; + + str1 = arr->arr[2 * i1]; + if (!JS_IsString(ctx, str1)) { + str1 = JS_ToString(ctx, str1); + if (JS_IsException(str1)) + goto exception; + arr = JS_VALUE_TO_PTR(*s->parr); + } + str2 = arr->arr[2 * i2]; + if (!JS_IsString(ctx, str2)) { + JS_PUSH_VALUE(ctx, str1); + str2 = JS_ToString(ctx, str2); + JS_POP_VALUE(ctx, str1); + if (JS_IsException(str2)) + goto exception; + } + cmp = js_string_compare(ctx, str1, str2); + } + if (cmp != 0) + return cmp; + cmp_same: + /* make sort stable: compare array offsets */ + arr = JS_VALUE_TO_PTR(*s->parr); + j1 = JS_VALUE_GET_INT(arr->arr[2 * i1 + 1]); + j2 = JS_VALUE_GET_INT(arr->arr[2 * i2 + 1]); + return (j1 > j2) - (j1 < j2); + +exception: + s->exception = TRUE; + return 0; +} + +static void js_array_sort_swap(size_t i1, size_t i2, void *opaque) +{ + JSArraySortContext *s = opaque; + JSValueArray *arr; + JSValue tmp, *tab; + + arr = JS_VALUE_TO_PTR(*s->parr); + tab = arr->arr; + tmp = tab[2 * i1]; + tab[2 * i1] = tab[2 * i2]; + tab[2 * i2] = tmp; + + tmp = tab[2 * i1 + 1]; + tab[2 * i1 + 1] = tab[2 * i2 + 1]; + tab[2 * i2 + 1] = tmp; +} + +JSValue js_array_sort(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue *pfunc = &argv[0]; + JSObject *p; + JSValue tab_val; + JSGCRef tab_val_ref; + JSValueArray *tab, *arr; + int i, len, n; + JSArraySortContext ss, *s = &ss; + + if (!JS_IsUndefined(*pfunc)) { + if (!JS_IsFunction(ctx, *pfunc)) + return JS_ThrowTypeError(ctx, "not a function"); + } else { + pfunc = NULL; + } + p = js_get_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + + /* create a temporary array for sorting */ + len = p->u.array.len; + tab = js_alloc_value_array(ctx, 0, len * 2); + if (!tab) + return JS_EXCEPTION; + + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + n = 0; + for(i = 0; i < len; i++) { + if (!JS_IsUndefined(arr->arr[i])) { + tab->arr[2 * n] = arr->arr[i]; + tab->arr[2 * n + 1] = JS_NewShortInt(i); + n++; + } + } + /* the end of 'tab' is already filled with JS_UNDEFINED */ + tab_val = JS_VALUE_FROM_PTR(tab); + + JS_PUSH_VALUE(ctx, tab_val); + s->ctx = ctx; + s->exception = FALSE; + s->parr = &tab_val_ref.val; + s->pfunc = pfunc; + rqsort_idx(n, js_array_sort_cmp, js_array_sort_swap, s); + JS_POP_VALUE(ctx, tab_val); + tab = JS_VALUE_TO_PTR(tab_val); + if (s->exception) { + js_free(ctx, tab); + return JS_EXCEPTION; + } + + p = JS_VALUE_TO_PTR(*this_val); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + /* XXX: could resize the array in case it was shrank by the compare function */ + len = min_int(len, p->u.array.len); + for(i = 0; i < len; i++) { + arr->arr[i] = tab->arr[2 * i]; + } + js_free(ctx, tab); + return *this_val; +} + +/**********************************************************************/ + +/* precondition: a and b are not NaN */ +static double js_fmin(double a, double b) +{ + if (a == 0 && b == 0) { + return uint64_as_float64(float64_as_uint64(a) | float64_as_uint64(b)); + } else if (a <= b) { + return a; + } else { + return b; + } +} + +/* precondition: a and b are not NaN */ +static double js_fmax(double a, double b) +{ + if (a == 0 && b == 0) { + return uint64_as_float64(float64_as_uint64(a) & float64_as_uint64(b)); + } else if (a >= b) { + return a; + } else { + return b; + } +} + +JSValue js_math_min_max(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + BOOL is_max = magic; + double r, a; + int i; + + if (unlikely(argc == 0)) { + return __JS_NewFloat64(ctx, is_max ? -1.0 / 0.0 : 1.0 / 0.0); + } + + if (JS_IsInt(argv[0])) { + int a1, r1 = JS_VALUE_GET_INT(argv[0]); + for(i = 1; i < argc; i++) { + if (!JS_IsInt(argv[i])) { + r = r1; + goto generic_case; + } + a1 = JS_VALUE_GET_INT(argv[i]); + if (is_max) + r1 = max_int(r1, a1); + else + r1 = min_int(r1, a1); + } + return JS_NewShortInt(r1); + } else { + if (JS_ToNumber(ctx, &r, argv[0])) + return JS_EXCEPTION; + i = 1; + generic_case: + while (i < argc) { + if (JS_ToNumber(ctx, &a, argv[i])) + return JS_EXCEPTION; + if (!isnan(r)) { + if (isnan(a)) { + r = a; + } else { + if (is_max) + r = js_fmax(r, a); + else + r = js_fmin(r, a); + } + } + i++; + } + return JS_NewFloat64(ctx, r); + } +} + +double js_math_sign(double a) +{ + if (isnan(a) || a == 0.0) + return a; + if (a < 0) + return -1; + else + return 1; +} + +double js_math_fround(double a) +{ + return (float)a; +} + +JSValue js_math_imul(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + int a, b; + + if (JS_ToInt32(ctx, &a, argv[0])) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &b, argv[1])) + return JS_EXCEPTION; + /* purposely ignoring overflow */ + return JS_NewInt32(ctx, (uint32_t)a * (uint32_t)b); +} + +JSValue js_math_clz32(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + uint32_t a, r; + + if (JS_ToUint32(ctx, &a, argv[0])) + return JS_EXCEPTION; + if (a == 0) + r = 32; + else + r = clz32(a); + return JS_NewInt32(ctx, r); +} + +JSValue js_math_atan2(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double y, x; + + if (JS_ToNumber(ctx, &y, argv[0])) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &x, argv[1])) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, js_atan2(y, x)); +} + +JSValue js_math_pow(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double y, x; + + if (JS_ToNumber(ctx, &x, argv[0])) + return JS_EXCEPTION; + if (JS_ToNumber(ctx, &y, argv[1])) + return JS_EXCEPTION; + return JS_NewFloat64(ctx, js_pow(x, y)); +} + +/* xorshift* random number generator by Marsaglia */ +static uint64_t xorshift64star(uint64_t *pstate) +{ + uint64_t x; + x = *pstate; + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + *pstate = x; + return x * 0x2545F4914F6CDD1D; +} + +JSValue js_math_random(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + uint64_t v; + + v = xorshift64star(&ctx->random_state); + /* 1.0 <= u.d < 2 */ + d = uint64_as_float64(((uint64_t)0x3ff << 52) | (v >> 12)); + return __JS_NewFloat64(ctx, d - 1.0); +} + +/* typed array */ + +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) + +static uint8_t typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = { + 0, 0, 0, 1, 1, 2, 2, 2, 3 +}; + +static int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val) +{ + int v; + /* XXX: should support 53 bit inteers */ + if (JS_ToInt32Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > JS_SHORTINT_MAX) { + JS_ThrowRangeError(ctx, "invalid array index"); + return -1; + } + *plen = v; + return 0; +} + +JSValue js_array_buffer_alloc(JSContext *ctx, uint64_t len) +{ + JSByteArray *arr; + JSValue buffer, obj; + JSGCRef buffer_ref; + JSObject *p; + + if (len > JS_SHORTINT_MAX) + return JS_ThrowRangeError(ctx, "invalid array buffer length"); + arr = js_alloc_byte_array(ctx, len); + if (!arr) + return JS_EXCEPTION; + memset(arr->buf, 0, len); + buffer = JS_VALUE_FROM_PTR(arr); + JS_PUSH_VALUE(ctx, buffer); + obj = JS_NewObjectClass(ctx, JS_CLASS_ARRAY_BUFFER, sizeof(JSArrayBuffer)); + JS_POP_VALUE(ctx, buffer); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.array_buffer.byte_buffer = buffer; + return obj; +} + +JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + uint64_t len; + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + return js_array_buffer_alloc(ctx, len); +} + +JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p = js_get_object_class(ctx, *this_val, JS_CLASS_ARRAY_BUFFER); + JSByteArray *arr; + if (!p) + return JS_ThrowTypeError(ctx, "expected an ArrayBuffer"); + arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer); + return JS_NewShortInt(arr->size); +} + +JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return JS_ThrowTypeError(ctx, "cannot be called"); +} + +static JSValue js_typed_array_constructor_obj(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + int i, len; + JSValue val, obj; + JSGCRef obj_ref; + JSObject *p; + + p = JS_VALUE_TO_PTR(argv[0]); + if (p->class_id == JS_CLASS_ARRAY) { + len = p->u.array.len; + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + len = p->u.typed_array.len; + } else { + return JS_ThrowTypeError(ctx, "unsupported object class"); + } + val = JS_NewShortInt(len); + obj = js_typed_array_constructor(ctx, NULL, 1 | FRAME_CF_CTOR, &val, magic); + if (JS_IsException(obj)) + return obj; + + for(i = 0; i < len; i++) { + JS_PUSH_VALUE(ctx, obj); + val = JS_GetProperty(ctx, argv[0], JS_NewShortInt(i)); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(val)) + return val; + JS_PUSH_VALUE(ctx, obj); + val = JS_SetPropertyInternal(ctx, obj, JS_NewShortInt(i), val, FALSE); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(val)) + return val; + } + return obj; +} + +JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + int size_log2; + uint64_t len, offset, byte_length; + JSObject *p; + JSByteArray *arr; + JSValue buffer, obj; + JSGCRef buffer_ref; + + if (!(argc & FRAME_CF_CTOR)) + return JS_ThrowTypeError(ctx, "must be called with new"); + size_log2 = typed_array_size_log2[magic - JS_CLASS_UINT8C_ARRAY]; + if (!JS_IsObject(ctx, argv[0])) { + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + buffer = js_array_buffer_alloc(ctx, len << size_log2); + if (JS_IsException(buffer)) + return JS_EXCEPTION; + offset = 0; + } else { + p = JS_VALUE_TO_PTR(argv[0]); + if (p->class_id == JS_CLASS_ARRAY_BUFFER) { + arr = JS_VALUE_TO_PTR(p->u.array_buffer.byte_buffer); + byte_length = arr->size; + if (JS_ToIndex(ctx, &offset, argv[1])) + return JS_EXCEPTION; + if ((offset & ((1 << size_log2) - 1)) != 0 || + offset > byte_length) + return JS_ThrowRangeError(ctx, "invalid offset"); + if (JS_IsUndefined(argv[2])) { + if ((byte_length & ((1 << size_log2) - 1)) != 0) + goto invalid_length; + len = (byte_length - offset) >> size_log2; + } else { + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + if ((offset + (len << size_log2)) > byte_length) { + invalid_length: + return JS_ThrowRangeError(ctx, "invalid length"); + } + } + buffer = argv[0]; + offset >>= size_log2; + } else { + return js_typed_array_constructor_obj(ctx, this_val, + argc, argv, magic); + } + } + + JS_PUSH_VALUE(ctx, buffer); + obj = JS_NewObjectClass(ctx, magic, sizeof(JSTypedArray)); + JS_POP_VALUE(ctx, buffer); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.typed_array.buffer = buffer; + p->u.typed_array.offset = offset; + p->u.typed_array.len = len; + return obj; +} + +static JSObject *get_typed_array(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (!JS_IsObject(ctx, val)) + goto fail; + p = JS_VALUE_TO_PTR(val); + if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_FLOAT64_ARRAY)) { + fail: + JS_ThrowTypeError(ctx, "not a TypedArray"); + return NULL; + } + return p; +} + +JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSObject *p; + int size_log2; + + p = get_typed_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + size_log2 = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY]; + switch(magic) { + default: + case 0: + return JS_NewShortInt(p->u.typed_array.len); + case 1: + return JS_NewShortInt(p->u.typed_array.len << size_log2); + case 2: + return JS_NewShortInt(p->u.typed_array.offset << size_log2); + case 3: + return p->u.typed_array.buffer; + } +} + +JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + JSByteArray *arr; + int start, final, len; + uint32_t offset, count; + JSValue obj; + + p = get_typed_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + len = p->u.typed_array.len; + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[1])) { + final = len; + } else { + if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + p = JS_VALUE_TO_PTR(*this_val); + offset = p->u.typed_array.offset + start; + count = max_int(final - start, 0); + + /* check offset and count */ + p1 = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + arr = JS_VALUE_TO_PTR(p1->u.array_buffer.byte_buffer); + if (offset + count > arr->size) + return JS_ThrowRangeError(ctx, "invalid length"); + + obj = JS_NewObjectClass(ctx, p->class_id, sizeof(JSTypedArray)); + if (JS_IsException(obj)) + return JS_EXCEPTION; + p = JS_VALUE_TO_PTR(*this_val); + p1 = JS_VALUE_TO_PTR(obj); + p1->u.typed_array.buffer = p->u.typed_array.buffer; + p1->u.typed_array.offset = offset; + p1->u.typed_array.len = count; + return obj; +} + +JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSObject *p, *p1; + uint32_t dst_len, src_len, i; + int offset; + + p = get_typed_array(ctx, *this_val); + if (!p) + return JS_EXCEPTION; + if (argc > 1) { + if (JS_ToInt32Sat(ctx, &offset, argv[1])) + return JS_EXCEPTION; + } else { + offset = 0; + } + if (offset < 0) + goto range_error; + if (!JS_IsObject(ctx, argv[0])) + return JS_ThrowTypeErrorNotAnObject(ctx); + p = JS_VALUE_TO_PTR(*this_val); + dst_len = p->u.typed_array.len; + p1 = JS_VALUE_TO_PTR(argv[0]); + if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + src_len = p1->u.typed_array.len; + if (src_len > dst_len || offset > dst_len - src_len) + goto range_error; + if (p1->class_id == p->class_id) { + JSObject *src_buffer, *dst_buffer; + JSByteArray *src_arr, *dst_arr; + int shift = typed_array_size_log2[p->class_id - JS_CLASS_UINT8C_ARRAY]; + dst_buffer = JS_VALUE_TO_PTR(p->u.typed_array.buffer); + dst_arr = JS_VALUE_TO_PTR(dst_buffer->u.array_buffer.byte_buffer); + src_buffer = JS_VALUE_TO_PTR(p1->u.typed_array.buffer); + src_arr = JS_VALUE_TO_PTR(src_buffer->u.array_buffer.byte_buffer); + /* same type: must copy to preserve float bits */ + memmove(dst_arr->buf + ((p->u.typed_array.offset + offset) << shift), + src_arr->buf + (p1->u.typed_array.offset << shift), + src_len << shift); + goto done; + } + } else { + if (js_get_length32(ctx, (uint32_t *)&src_len, argv[0])) + return JS_EXCEPTION; + if (src_len > dst_len || offset > dst_len - src_len) { + range_error: + return JS_ThrowRangeError(ctx, "invalid array length"); + } + } + for(i = 0; i < src_len; i++) { + JSValue val; + val = JS_GetPropertyUint32(ctx, argv[0], i); + if (JS_IsException(val)) + return JS_EXCEPTION; + val = JS_SetPropertyUint32(ctx, *this_val, offset + i, val); + if (JS_IsException(val)) + return JS_EXCEPTION; + } + done: + return JS_UNDEFINED; +} + +/* Date */ + +JSValue js_date_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return JS_ThrowTypeError(ctx, "only Date.now() is supported"); +} + +/* global */ + +JSValue js_global_eval(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue val; + + if (!JS_IsString(ctx, argv[0])) + return argv[0]; + val = JS_Parse2(ctx, argv[0], NULL, 0, "", JS_EVAL_RETVAL); + if (JS_IsException(val)) + return val; + return JS_Run(ctx, val); +} + +JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + if (unlikely(JS_ToNumber(ctx, &d, argv[0]))) + return JS_EXCEPTION; + return JS_NewBool(isnan(d)); +} + +JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + double d; + if (unlikely(JS_ToNumber(ctx, &d, argv[0]))) + return JS_EXCEPTION; + return JS_NewBool(isfinite(d)); +} + +/* JSON */ + +JSValue js_json_parse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue val; + + val = JS_ToString(ctx, argv[0]); + if (JS_IsException(val)) + return val; + return JS_Parse2(ctx, val, NULL, 0, "", JS_EVAL_JSON); +} + +static int js_to_quoted_string(JSContext *ctx, StringBuffer *b, JSValue str) +{ + int i, c; + JSStringCharBuf buf; + JSString *p; + JSGCRef str_ref; + size_t clen; + + JS_PUSH_VALUE(ctx, str); + string_buffer_putc(ctx, b, '\"'); + + i = 0; + for(;;) { + /* XXX: inefficient */ + p = get_string_ptr(ctx, &buf, str_ref.val); + if (i >= p->len) + break; + c = utf8_get(p->buf + i, &clen); + i += clen; + + switch(c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + string_buffer_putc(ctx, b, '\\'); + string_buffer_putc(ctx, b, c); + break; + default: + if (c < 32 || (c >= 0xd800 && c < 0xe000)) { + char buf[7]; + js_snprintf(buf, sizeof(buf), "\\u%04x", c); + string_buffer_puts(ctx, b, buf); + } else { + string_buffer_putc(ctx, b, c); + } + break; + } + } + string_buffer_putc(ctx, b, '\"'); + JS_POP_VALUE(ctx, str); + return 0; +} + +#define JSON_REC_SIZE 3 + +static int check_circular_ref(JSContext *ctx, JSValue *stack_top, JSValue val) +{ + JSValue *sp; + for(sp = ctx->sp; sp < stack_top; sp += JSON_REC_SIZE) { + if (sp[0] == val) { + JS_ThrowTypeError(ctx, "circular reference"); + return -1; + } + } + return 0; +} + +/* XXX: no space nor replacer */ +JSValue js_json_stringify(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj, *stack_top; + StringBuffer b_s, *b = &b_s; + int idx, ret; + +#if 0 + if (JS_IsNumber(ctx, *pspace)) { + int n; + if (JS_ToInt32Clamp(ctx, &n, *pspace, 0, 10, 0)) + return JS_EXCEPTION; + *pspace = JS_NewStringLen(ctx, " ", n); + } else if (JS_IsString(ctx, *pspace)) { + *pspace = js_sub_string(ctx, *pspace, 0, 10); + } else { + *pspace = js_get_atom(ctx, JS_ATOM_empty); + } +#endif + string_buffer_push(ctx, b, 0); + stack_top = ctx->sp; + + ret = JS_StackCheck(ctx, JSON_REC_SIZE); + if (ret) + goto fail; + *--ctx->sp = JS_NULL; /* keys */ + *--ctx->sp = JS_NewShortInt(0); /* prop index */ + *--ctx->sp = argv[0]; /* object */ + + while (ctx->sp < stack_top) { + obj = ctx->sp[0]; + if (JS_IsFunction(ctx, obj)) { + goto output_null; + } else if (JS_IsObject(ctx, obj)) { + JSObject *p = JS_VALUE_TO_PTR(obj); + idx = JS_VALUE_GET_INT(ctx->sp[1]); + if (p->class_id == JS_CLASS_ARRAY) { + JSValueArray *arr; + JSValue val; + + /* array */ + if (idx == 0) + string_buffer_putc(ctx, b, '['); + p = JS_VALUE_TO_PTR(ctx->sp[0]); + if (idx >= p->u.array.len) { + /* end of array */ + string_buffer_putc(ctx, b, ']'); + ctx->sp += JSON_REC_SIZE; + } else { + if (idx != 0) + string_buffer_putc(ctx, b, ','); + ctx->sp[1] = JS_NewShortInt(idx + 1); + ret = JS_StackCheck(ctx, JSON_REC_SIZE); + if (ret) + goto fail; + p = JS_VALUE_TO_PTR(ctx->sp[0]); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + val = arr->arr[idx]; + if (check_circular_ref(ctx, stack_top, val)) + goto fail; + *--ctx->sp = JS_NULL; + *--ctx->sp = JS_NewShortInt(0); + *--ctx->sp = val; + } + } else { + JSValueArray *arr; + JSValue val, prop; + JSGCRef val_ref; + int saved_idx; + + /* object */ + if (idx == 0) { + string_buffer_putc(ctx, b, '{'); + ctx->sp[2] = js_object_keys(ctx, NULL, 1, &ctx->sp[0]); + if (JS_IsException(ctx->sp[2])) + goto fail; + } + saved_idx = idx; + for(;;) { + p = JS_VALUE_TO_PTR(ctx->sp[2]); /* keys */ + if (idx >= p->u.array.len) { + /* end of object */ + string_buffer_putc(ctx, b, '}'); + ctx->sp += JSON_REC_SIZE; + goto end_obj; + } else { + arr = JS_VALUE_TO_PTR(p->u.array.tab); + prop = JS_ToPropertyKey(ctx, arr->arr[idx]); + val = JS_GetProperty(ctx, ctx->sp[0], prop); + if (JS_IsException(val)) + goto fail; + /* skip undefined properties */ + if (!JS_IsUndefined(val)) + break; + idx++; + } + } + JS_PUSH_VALUE(ctx, val); + if (saved_idx != 0) + string_buffer_putc(ctx, b, ','); + ctx->sp[1] = JS_NewShortInt(idx + 1); + p = JS_VALUE_TO_PTR(ctx->sp[2]); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + ret = js_to_quoted_string(ctx, b, arr->arr[idx]); + string_buffer_putc(ctx, b, ':'); + ret |= JS_StackCheck(ctx, JSON_REC_SIZE); + JS_POP_VALUE(ctx, val); + if (ret) + goto fail; + if (check_circular_ref(ctx, stack_top, val)) + goto fail; + *--ctx->sp = JS_NULL; + *--ctx->sp = JS_NewShortInt(0); + *--ctx->sp = val; + end_obj: ; + } + } else if (JS_IsNumber(ctx, obj)) { + double d; + ret = JS_ToNumber(ctx, &d, obj); + if (ret) + goto fail; + if (!isfinite(d)) + goto output_null; + goto to_string; + } else if (JS_IsBool(obj)) { + to_string: + if (string_buffer_concat(ctx, b, obj)) + goto fail; + ctx->sp += JSON_REC_SIZE; + } else if (JS_IsString(ctx, obj)) { + if (js_to_quoted_string(ctx, b, obj)) + goto fail; + ctx->sp += JSON_REC_SIZE; + } else { + output_null: + string_buffer_concat(ctx, b, js_get_atom(ctx, JS_ATOM_null)); + ctx->sp += JSON_REC_SIZE; + } + } + return string_buffer_pop(ctx, b); + + fail: + ctx->sp = stack_top; + string_buffer_pop(ctx, b); + return JS_EXCEPTION; +} + +/**********************************************************************/ +/* regexp */ + +typedef enum { +#define REDEF(id, size) REOP_ ## id, +#include "mquickjs_opcode.h" +#undef REDEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define REGISTER_COUNT_MAX 255 + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define REDEF(id, size) { #id, size }, +#else +#define REDEF(id, size) { size }, +#endif +#include "mquickjs_opcode.h" +#undef REDEF +}; + +#define LRE_FLAG_GLOBAL (1 << 0) +#define LRE_FLAG_IGNORECASE (1 << 1) +#define LRE_FLAG_MULTILINE (1 << 2) +#define LRE_FLAG_DOTALL (1 << 3) +#define LRE_FLAG_UNICODE (1 << 4) +#define LRE_FLAG_STICKY (1 << 5) + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_REGISTER_COUNT 3 + +#define RE_HEADER_LEN 4 + +#define CLASS_RANGE_BASE 0x40000000 + +typedef enum { + CHAR_RANGE_d, + CHAR_RANGE_D, + CHAR_RANGE_s, + CHAR_RANGE_S, + CHAR_RANGE_w, + CHAR_RANGE_W, +} CharRangeEnum; + +static int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +static int lre_get_alloc_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT] * 2 + bc_buf[RE_HEADER_REGISTER_COUNT]; +} + +static int lre_get_flags(const uint8_t *bc_buf) +{ + return get_u16(bc_buf + RE_HEADER_FLAGS); +} + +#ifdef DUMP_REOP +static __maybe_unused void lre_dump_bytecode(const uint8_t *buf, + int buf_len) +{ + int pos, len, opcode, bc_len, re_flags; + uint32_t val, val2; + + assert(buf_len >= RE_HEADER_LEN); + re_flags = lre_get_flags(buf); + bc_len = buf_len - RE_HEADER_LEN; + + printf("flags: 0x%x capture_count=%d reg_count=%d bytecode_len=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_REGISTER_COUNT], + bc_len); + + buf += RE_HEADER_LEN; + + pos = 0; + while (pos < bc_len) { + printf("%5u: ", pos); + opcode = buf[pos]; + len = reopcode_info[opcode].size; + if (opcode >= REOP_COUNT) { + printf(" invalid opcode=0x%02x\n", opcode); + break; + } + if ((pos + len) > bc_len) { + printf(" buffer overflow (opcode=0x%02x)\n", opcode); + break; + } + printf("%s", reopcode_info[opcode].name); + switch(opcode) { + case REOP_char1: + case REOP_char2: + case REOP_char3: + case REOP_char4: + { + int i, n; + n = opcode - REOP_char1 + 1; + for(i = 0; i < n; i++) { + val = buf[pos + 1 + i]; + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%2x", val); + } + } + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_loop: + val2 = buf[pos + 1]; + val = get_u32(buf + pos + 2); + val += (pos + 6); + printf(" r%u, %u", val2, val); + break; + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + { + uint32_t limit; + val2 = buf[pos + 1]; + limit = get_u32(buf + pos + 2); + val = get_u32(buf + pos + 6); + val += (pos + 10); + printf(" r%u, %u, %u", val2, limit, val); + } + break; + case REOP_save_start: + case REOP_save_end: + case REOP_back_reference: + case REOP_back_reference_i: + printf(" %u", buf[pos + 1]); + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_set_i32: + val = buf[pos + 1]; + val2 = get_u32(buf + pos + 2); + printf(" r%u, %d", val, val2); + break; + case REOP_set_char_pos: + case REOP_check_advance: + val = buf[pos + 1]; + printf(" r%u", val); + break; + case REOP_range8: + { + int n, i; + n = buf[pos + 1]; + len += n * 2; + for(i = 0; i < n * 2; i++) { + val = buf[pos + 2 + i]; + printf(" 0x%02x", val); + } + } + break; + case REOP_range: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 8; + for(i = 0; i < n * 2; i++) { + val = get_u32(buf + pos + 3 + i * 4); + printf(" 0x%05x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(JSParseState *s, int op) +{ + emit_u8(s, op); +} + +static void re_emit_op_u8(JSParseState *s, int op, uint32_t val) +{ + emit_u8(s, op); + emit_u8(s, val); +} + +static void re_emit_op_u16(JSParseState *s, int op, uint32_t val) +{ + emit_u8(s, op); + emit_u16(s, val); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(JSParseState *s, int op, uint32_t val) +{ + int pos; + emit_u8(s, op); + pos = s->byte_code_len; + emit_u32(s, val); + return pos; +} + +static int re_emit_goto(JSParseState *s, int op, uint32_t val) +{ + int pos; + emit_u8(s, op); + pos = s->byte_code_len; + emit_u32(s, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8(JSParseState *s, int op, uint32_t arg, uint32_t val) +{ + int pos; + emit_u8(s, op); + emit_u8(s, arg); + pos = s->byte_code_len; + emit_u32(s, val - (pos + 4)); + return pos; +} + +static int re_emit_goto_u8_u32(JSParseState *s, int op, uint32_t arg0, uint32_t arg1, uint32_t val) +{ + int pos; + emit_u8(s, op); + emit_u8(s, arg0); + emit_u32(s, arg1); + pos = s->byte_code_len; + emit_u32(s, val - (pos + 4)); + return pos; +} + +static void re_emit_char(JSParseState *s, int c) +{ + uint8_t buf[4]; + size_t n, i; + n = unicode_to_utf8(buf, c); + re_emit_op(s, REOP_char1 + n - 1); + for(i = 0; i < n; i++) + emit_u8(s, buf[i]); +} + +static void re_parse_expect(JSParseState *s, int c) +{ + if (s->source_buf[s->buf_pos] != c) + return js_parse_error(s, "expecting '%c'", c); + s->buf_pos++; +} + +/* return JS_SHORTINT_MAX in case of overflow */ +static int parse_digits(const uint8_t **pp) +{ + const uint8_t *p; + uint64_t v; + int c; + + p = *pp; + v = 0; + for(;;) { + c = *p; + if (c < '0' || c > '9') + break; + v = v * 10 + c - '0'; + if (v >= JS_SHORTINT_MAX) + v = JS_SHORTINT_MAX; + p++; + } + *pp = p; + return v; +} + +/* need_check_adv: false if the opcodes always advance the char pointer + need_capture_init: true if all the captures in the atom are not set +*/ +static BOOL re_need_check_adv_and_capture_init(BOOL *pneed_capture_init, + const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + BOOL need_check_adv, need_capture_init; + + need_check_adv = TRUE; + need_capture_init = FALSE; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range8: + val = bc_buf[pos + 1]; + len += val * 2; + need_check_adv = FALSE; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + need_check_adv = FALSE; + break; + case REOP_char1: + case REOP_char2: + case REOP_char3: + case REOP_char4: + case REOP_dot: + case REOP_any: + case REOP_space: + case REOP_not_space: + need_check_adv = FALSE; + break; + case REOP_line_start: + case REOP_line_start_m: + case REOP_line_end: + case REOP_line_end_m: + case REOP_set_i32: + case REOP_set_char_pos: + case REOP_word_boundary: + case REOP_not_word_boundary: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + break; + default: + /* safe behavior: we cannot predict the outcome */ + need_capture_init = TRUE; + goto done; + } + pos += len; + } + done: + *pneed_capture_init = need_capture_init; + return need_check_adv; +} + +/* return the character or a class range (>= CLASS_RANGE_BASE) if inclass + = TRUE */ +static int get_class_atom(JSParseState *s, BOOL inclass) +{ + const uint8_t *p; + uint32_t c; + int ret; + size_t len; + + p = s->source_buf + s->buf_pos; + c = *p; + switch(c) { + case '\\': + p++; + c = *p++; + switch(c) { + case 'd': + c = CHAR_RANGE_d; + goto class_range; + case 'D': + c = CHAR_RANGE_D; + goto class_range; + case 's': + c = CHAR_RANGE_s; + goto class_range; + case 'S': + c = CHAR_RANGE_S; + goto class_range; + case 'w': + c = CHAR_RANGE_w; + goto class_range; + case 'W': + c = CHAR_RANGE_W; + class_range: + c += CLASS_RANGE_BASE; + break; + case 'c': + c = *p; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (((c >= '0' && c <= '9') || c == '_') && + inclass && !s->is_unicode)) { /* Annex B.1.4 */ + c &= 0x1f; + p++; + } else if (s->is_unicode) { + goto invalid_escape; + } else { + /* otherwise return '\' and 'c' */ + p--; + c = '\\'; + } + break; + case '-': + if (!inclass && s->is_unicode) + goto invalid_escape; + break; + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '|': + case '/': + /* always valid to escape these characters */ + break; + default: + p--; + ret = js_parse_escape(p, &len); + if (ret < 0) { + if (s->is_unicode) { + invalid_escape: + s->buf_pos = p - s->source_buf; + js_parse_error(s, "invalid escape sequence in regular expression"); + } else { + goto normal_char; + } + } + p += len; + c = ret; + break; + } + break; + case '\0': + case '/': /* safety for end of regexp in JS parser */ + if ((p - s->source_buf) >= s->buf_len) + js_parse_error(s, "unexpected end"); + goto normal_char; + default: + normal_char: + /* normal char */ + ret = unicode_from_utf8(p, UTF8_CHAR_LEN_MAX, &len); + /* Note: should not fail with normal JS strings */ + if (ret < 0) + js_parse_error(s, "malformed unicode char"); + p += len; + c = ret; + break; + } + s->buf_pos = p - s->source_buf; + return c; +} + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 0x0009, 0x000D + 1, + 0x0020, 0x0020 + 1, + 0x00A0, 0x00A0 + 1, + 0x1680, 0x1680 + 1, + 0x2000, 0x200A + 1, + /* 2028;LINE SEPARATOR;Zl;0;WS;;;;;N;;;;; */ + /* 2029;PARAGRAPH SEPARATOR;Zp;0;B;;;;;N;;;;; */ + 0x2028, 0x2029 + 1, + 0x202F, 0x202F + 1, + 0x205F, 0x205F + 1, + 0x3000, 0x3000 + 1, + /* FEFF;ZERO WIDTH NO-BREAK SPACE;Cf;0;BN;;;;;N;BYTE ORDER MARK;;;; */ + 0xFEFF, 0xFEFF + 1, +}; + +static const uint16_t char_range_w[] = { + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +static void re_emit_range_base1(JSParseState *s, const uint16_t *tab, int n) +{ + int i; + for(i = 0; i < n; i++) + emit_u32(s, tab[i]); +} + +static void re_emit_range_base(JSParseState *s, int c) +{ + BOOL invert; + invert = c & 1; + if (invert) + emit_u32(s, 0); + switch(c & ~1) { + case CHAR_RANGE_d: + emit_u32(s, 0x30); + emit_u32(s, 0x39 + 1); + break; + case CHAR_RANGE_s: + re_emit_range_base1(s, char_range_s, countof(char_range_s)); + break; + case CHAR_RANGE_w: + re_emit_range_base1(s, char_range_w, countof(char_range_w)); + break; + default: + abort(); + } + if (invert) + emit_u32(s, 0x110000); +} + +static int range_sort_cmp(size_t i1, size_t i2, void *opaque) +{ + uint8_t *tab = opaque; + return get_u32(&tab[8 * i1]) - get_u32(&tab[8 * i2]); +} + +static void range_sort_swap(size_t i1, size_t i2, void *opaque) +{ + uint8_t *tab = opaque; + uint64_t tmp; + tmp = get_u64(&tab[8 * i1]); + put_u64(&tab[8 * i1], get_u64(&tab[8 * i2])); + put_u64(&tab[8 * i2], tmp); +} + +/* merge consecutive intervals, remove empty intervals and handle overlapping intervals */ +static int range_compress(uint8_t *tab, int len) +{ + int i, j; + uint32_t start, end, start2, end2; + + i = 0; + j = 0; + while (i < len) { + start = get_u32(&tab[8 * i]); + end = get_u32(&tab[8 * i + 4]); + if (start == end) { + /* empty interval : remove */ + } else if ((i + 1) < len) { + start2 = get_u32(&tab[8 * i + 8]); + end2 = get_u32(&tab[8 * i + 12]); + if (end < start2) { + goto copy; + } else { + /* union of the intervals */ + put_u32(&tab[8 * i + 8], start); + put_u32(&tab[8 * i + 12], max_uint32(end, end2)); + } + } else { + copy: + put_u32(&tab[8 * j], start); + put_u32(&tab[8 * j + 4], end); + j++; + } + i++; + } + return j; +} + +static void re_range_optimize(JSParseState *s, int range_start, BOOL invert) +{ + int n, n1; + JSByteArray *arr; + + n = (unsigned)(s->byte_code_len - range_start) / 8; + + arr = JS_VALUE_TO_PTR(s->byte_code); + rqsort_idx(n, range_sort_cmp, range_sort_swap, arr->buf + range_start); + + /* must compress before inverting */ + n1 = range_compress(arr->buf + range_start, n); + s->byte_code_len -= (n - n1) * 8; + + if (invert) { + emit_insert(s, range_start, 4); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + range_start, 0); + emit_u32(s, 0x110000); + arr = JS_VALUE_TO_PTR(s->byte_code); + n = n1 + 1; + n1 = range_compress(arr->buf + range_start, n); + s->byte_code_len -= (n - n1) * 8; + } + n = n1; + + if (n > 65534) + js_parse_error(s, "range too big"); + + /* compress to 8 bit if possible */ + /* XXX: adjust threshold */ + if (n > 0 && n < 16) { + uint8_t *tab = arr->buf + range_start; + int c, i; + c = get_u32(&tab[8 * (n - 1) + 4]); + if (c < 254 || (c == 0x110000 && + get_u32(&tab[8 * (n - 1)]) < 254)) { + s->byte_code_len = range_start - 3; + re_emit_op_u8(s, REOP_range8, n); + for(i = 0; i < 2 * n; i++) { + c = get_u32(&tab[4 * i]); + if (c == 0x110000) + c = 0xff; + emit_u8(s, c); + } + goto done; + } + } + + put_u16(arr->buf + range_start - 2, n); + done: ; +} + +/* add the intersection of the two intervals and if offset != 0 the + translated interval */ +static void add_interval_intersect(JSParseState *s, + uint32_t start, uint32_t end, + uint32_t start1, uint32_t end1, + int offset) +{ + start = max_uint32(start, start1); + end = min_uint32(end, end1); + if (start < end) { + emit_u32(s, start); + emit_u32(s, end); + if (offset != 0) { + emit_u32(s, start + offset); + emit_u32(s, end + offset); + } + } +} + +static void re_parse_char_class(JSParseState *s) +{ + uint32_t c1, c2; + BOOL invert; + int range_start; + + s->buf_pos++; /* skip '[' */ + + invert = FALSE; + if (s->source_buf[s->buf_pos] == '^') { + s->buf_pos++; + invert = TRUE; + } + + re_emit_op_u16(s, REOP_range, 0); + range_start = s->byte_code_len; + + for(;;) { + if (s->source_buf[s->buf_pos] == ']') + break; + + c1 = get_class_atom(s, TRUE); + if (s->source_buf[s->buf_pos] == '-' && s->source_buf[s->buf_pos + 1] != ']') { + s->buf_pos++; + if (c1 >= CLASS_RANGE_BASE) + goto invalid_class_range; + c2 = get_class_atom(s, TRUE); + if (c2 >= CLASS_RANGE_BASE) + goto invalid_class_range; + if (c2 < c1) { + invalid_class_range: + js_parse_error(s, "invalid class range"); + } + goto add_range; + } else { + if (c1 >= CLASS_RANGE_BASE) { + re_emit_range_base(s, c1 - CLASS_RANGE_BASE); + } else { + c2 = c1; + add_range: + c2++; + if (s->ignore_case) { + /* add the intervals exclude the cased characters */ + add_interval_intersect(s, c1, c2, 0, 'A', 0); + add_interval_intersect(s, c1, c2, 'Z' + 1, 'a', 0); + add_interval_intersect(s, c1, c2, 'z' + 1, INT32_MAX, 0); + /* include all the possible cases */ + add_interval_intersect(s, c1, c2, 'A', 'Z' + 1, 32); + add_interval_intersect(s, c1, c2, 'a', 'z' + 1, -32); + } else { + emit_u32(s, c1); + emit_u32(s, c2); + } + } + } + } + s->buf_pos++; /* skip ']' */ + re_range_optimize(s, range_start, invert); +} + +static void re_parse_quantifier(JSParseState *s, int last_atom_start, int last_capture_count) +{ + int c, quant_min, quant_max; + JSByteArray *arr; + BOOL greedy; + const uint8_t *p; + + p = s->source_buf + s->buf_pos; + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = JS_SHORTINT_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = JS_SHORTINT_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + if (!is_digit(p[1])) + goto invalid_quant_count; + p++; + quant_min = parse_digits(&p); + quant_max = quant_min; + if (*p == ',') { + p++; + if (is_digit(*p)) { + quant_max = parse_digits(&p); + if (quant_max < quant_min) { + invalid_quant_count: + js_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = JS_SHORTINT_MAX; /* infinity */ + } + } + s->buf_pos = p - s->source_buf; + re_parse_expect(s, '}'); + p = s->source_buf + s->buf_pos; + } + quantifier: + greedy = TRUE; + + if (*p == '?') { + p++; + greedy = FALSE; + } + s->buf_pos = p - s->source_buf; + + if (last_atom_start < 0) + js_parse_error(s, "nothing to repeat"); + { + BOOL need_capture_init, add_zero_advance_check; + int len, pos; + + /* the spec tells that if there is no advance when + running the atom after the first quant_min times, + then there is no match. We remove this test when we + are sure the atom always advances the position. */ + arr = JS_VALUE_TO_PTR(s->byte_code); + add_zero_advance_check = + re_need_check_adv_and_capture_init(&need_capture_init, + arr->buf + last_atom_start, + s->byte_code_len - last_atom_start); + + /* general case: need to reset the capture at each + iteration. We don't do it if there are no captures + in the atom or if we are sure all captures are + initialized in the atom. If quant_min = 0, we still + need to reset once the captures in case the atom + does not match. */ + if (need_capture_init && last_capture_count != s->capture_count) { + emit_insert(s, last_atom_start, 3); + int pos = last_atom_start; + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[pos++] = REOP_save_reset; + arr->buf[pos++] = last_capture_count; + arr->buf[pos++] = s->capture_count - 1; + } + + len = s->byte_code_len - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (!need_capture_init && last_capture_count != s->capture_count) { + emit_insert(s, last_atom_start, 3); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[last_atom_start++] = REOP_save_reset; + arr->buf[last_atom_start++] = last_capture_count; + arr->buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code_len = last_atom_start; + } else if (quant_max == 1 || quant_max == JS_SHORTINT_MAX) { + BOOL has_goto = (quant_max == JS_SHORTINT_MAX); + emit_insert(s, last_atom_start, 5 + add_zero_advance_check * 2); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(arr->buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2 * 2); + if (add_zero_advance_check) { + arr->buf[last_atom_start + 1 + 4] = REOP_set_char_pos; + arr->buf[last_atom_start + 1 + 4 + 1] = 0; + re_emit_op_u8(s, REOP_check_advance, 0); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + emit_insert(s, last_atom_start, 11 + add_zero_advance_check * 2); + pos = last_atom_start; + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[pos++] = REOP_split_goto_first + greedy; + put_u32(arr->buf + pos, 6 + add_zero_advance_check * 2 + len + 10); + pos += 4; + + arr->buf[pos++] = REOP_set_i32; + arr->buf[pos++] = 0; + put_u32(arr->buf + pos, quant_max); + pos += 4; + last_atom_start = pos; + if (add_zero_advance_check) { + arr->buf[pos++] = REOP_set_char_pos; + arr->buf[pos++] = 0; + } + re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max, last_atom_start); + } + } else if (quant_min == 1 && quant_max == JS_SHORTINT_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == quant_max) + add_zero_advance_check = FALSE; + emit_insert(s, last_atom_start, 6 + add_zero_advance_check * 2); + /* Note: we assume the string length is < JS_SHORTINT_MAX */ + pos = last_atom_start; + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[pos++] = REOP_set_i32; + arr->buf[pos++] = 0; + put_u32(arr->buf + pos, quant_max); + pos += 4; + last_atom_start = pos; + if (add_zero_advance_check) { + arr->buf[pos++] = REOP_set_char_pos; + arr->buf[pos++] = 0; + } + if (quant_min == quant_max) { + /* a simple loop is enough */ + re_emit_goto_u8(s, REOP_loop, 0, last_atom_start); + } else { + re_emit_goto_u8_u32(s, (add_zero_advance_check ? REOP_loop_check_adv_split_next_first : REOP_loop_split_next_first) - greedy, 0, quant_max - quant_min, last_atom_start); + } + } + last_atom_start = -1; + } + break; + default: + break; + } +} + +/* return the number of bytes if char otherwise 0 */ +static int re_is_char(const uint8_t *buf, int start, int end) +{ + int n; + if (!(buf[start] >= REOP_char1 && buf[start] <= REOP_char4)) + return 0; + n = buf[start] - REOP_char1 + 1; + if ((end - start) != (n + 1)) + return 0; + return n; +} + +static int re_parse_alternative(JSParseState *s, int state, int dummy_param) +{ + int term_start, last_term_start, last_atom_start, last_capture_count, c, n1, n2, i; + JSByteArray *arr; + + PARSE_START3(); + + last_term_start = -1; + for(;;) { + if (s->buf_pos >= s->buf_len) + break; + term_start = s->byte_code_len; + + last_atom_start = -1; + last_capture_count = 0; + c = s->source_buf[s->buf_pos]; + switch(c) { + case '|': + case ')': + goto done; + case '^': + s->buf_pos++; + re_emit_op(s, s->multi_line ? REOP_line_start_m : REOP_line_start); + break; + case '$': + s->buf_pos++; + re_emit_op(s, s->multi_line ? REOP_line_end_m : REOP_line_end); + break; + case '.': + s->buf_pos++; + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + break; + case '{': + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!s->is_unicode && !is_digit(s->source_buf[s->buf_pos + 1])) + goto parse_class_atom; + /* fall thru */ + case '*': + case '+': + case '?': + js_parse_error(s, "nothing to repeat"); + case '(': + if (s->source_buf[s->buf_pos + 1] == '?') { + c = s->source_buf[s->buf_pos + 2]; + if (c == ':') { + s->buf_pos += 3; + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + PARSE_CALL_SAVE4(s, 0, re_parse_disjunction, 0, + last_term_start, term_start, last_atom_start, last_capture_count); + re_parse_expect(s, ')'); + } else if ((c == '=' || c == '!')) { + int is_neg, pos; + is_neg = (c == '!'); + s->buf_pos += 3; + /* lookahead */ + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + PARSE_CALL_SAVE6(s, 1, re_parse_disjunction, 0, + last_term_start, term_start, last_atom_start, last_capture_count, + is_neg, pos); + re_parse_expect(s, ')'); + re_emit_op(s, REOP_lookahead_match + is_neg); + /* jump after the 'match' after the lookahead is successful */ + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + pos, s->byte_code_len - (pos + 4)); + } else { + js_parse_error(s, "invalid group"); + } + } else { + int capture_index; + s->buf_pos++; + /* capture without group name */ + if (s->capture_count >= CAPTURE_COUNT_MAX) + js_parse_error(s, "too many captures"); + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start, capture_index); + + PARSE_CALL_SAVE5(s, 2, re_parse_disjunction, 0, + last_term_start, term_start, last_atom_start, last_capture_count, + capture_index); + + re_emit_op_u8(s, REOP_save_end, capture_index); + + re_parse_expect(s, ')'); + } + break; + case '\\': + switch(s->source_buf[s->buf_pos + 1]) { + case 'b': + case 'B': + if (s->source_buf[s->buf_pos + 1] != 'b') { + re_emit_op(s, REOP_not_word_boundary); + } else { + re_emit_op(s, REOP_word_boundary); + } + s->buf_pos += 2; + break; + case '0': + s->buf_pos += 2; + c = 0; + if (is_digit(s->source_buf[s->buf_pos])) + js_parse_error(s, "invalid decimal escape in regular expression"); + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *p; + p = s->source_buf + s->buf_pos + 1; + c = parse_digits(&p); + s->buf_pos = p - s->source_buf; + if (c > CAPTURE_COUNT_MAX) + js_parse_error(s, "back reference is out of range"); + /* the range is checked afterwards as we don't know the number of captures */ + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + re_emit_op_u8(s, REOP_back_reference + s->ignore_case, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + re_parse_char_class(s); + break; + case ']': + case '}': + if (s->is_unicode) + js_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, FALSE); + normal_char: + last_atom_start = s->byte_code_len; + last_capture_count = s->capture_count; + if (c >= CLASS_RANGE_BASE) { + int range_start; + c -= CLASS_RANGE_BASE; + if (c == CHAR_RANGE_s || c == CHAR_RANGE_S) { + re_emit_op(s, REOP_space + c - CHAR_RANGE_s); + } else { + re_emit_op_u16(s, REOP_range, 0); + range_start = s->byte_code_len; + + re_emit_range_base(s, c); + re_range_optimize(s, range_start, FALSE); + } + } else { + if (s->ignore_case && + ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) { + /* XXX: could add specific operation */ + if (c >= 'a') + c -= 32; + re_emit_op_u8(s, REOP_range8, 2); + emit_u8(s, c); + emit_u8(s, c + 1); + emit_u8(s, c + 32); + emit_u8(s, c + 32 + 1); + } else { + re_emit_char(s, c); + } + } + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + re_parse_quantifier(s, last_atom_start, last_capture_count); + } + + /* combine several characters when possible */ + arr = JS_VALUE_TO_PTR(s->byte_code); + if (last_term_start >= 0 && + (n1 = re_is_char(arr->buf, last_term_start, term_start)) > 0 && + (n2 = re_is_char(arr->buf, term_start, s->byte_code_len)) > 0 && + (n1 + n2) <= 4) { + n1 += n2; + arr->buf[last_term_start] = REOP_char1 + n1 - 1; + for(i = 0; i < n2; i++) + arr->buf[last_term_start + n1 + i] = arr->buf[last_term_start + n1 + i + 1]; + s->byte_code_len--; + } else { + last_term_start = term_start; + } + } + done: + return PARSE_STATE_RET; +} + +static int re_parse_disjunction(JSParseState *s, int state, int dummy_param) +{ + int start, len, pos; + JSByteArray *arr; + + PARSE_START2(); + + start = s->byte_code_len; + + PARSE_CALL_SAVE1(s, 0, re_parse_alternative, 0, start); + while (s->source_buf[s->buf_pos] == '|') { + s->buf_pos++; + + len = s->byte_code_len - start; + + /* insert a split before the first alternative */ + emit_insert(s, start, 5); + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[start] = REOP_split_next_first; + put_u32(arr->buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + PARSE_CALL_SAVE2(s, 1, re_parse_alternative, 0, start, pos); + + /* patch the goto */ + len = s->byte_code_len - (pos + 4); + arr = JS_VALUE_TO_PTR(s->byte_code); + put_u32(arr->buf + pos, len); + } + return PARSE_STATE_RET; +} + +/* Allocate the registers as a stack. The control flow is recursive so + the analysis can be linear. */ +static int re_compute_register_count(JSParseState *s, uint8_t *bc_buf, int bc_buf_len) +{ + int stack_size, stack_size_max, pos, opcode, len; + uint32_t val; + + stack_size = 0; + stack_size_max = 0; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + assert(opcode < REOP_COUNT); + assert((pos + len) <= bc_buf_len); + switch(opcode) { + case REOP_set_i32: + case REOP_set_char_pos: + bc_buf[pos + 1] = stack_size; + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > REGISTER_COUNT_MAX) + js_parse_error(s, "too many regexp registers"); + stack_size_max = stack_size; + } + break; + case REOP_check_advance: + case REOP_loop: + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + assert(stack_size > 0); + stack_size--; + bc_buf[pos + 1] = stack_size; + break; + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + assert(stack_size >= 2); + stack_size -= 2; + bc_buf[pos + 1] = stack_size; + break; + case REOP_range8: + val = bc_buf[pos + 1]; + len += val * 2; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + case REOP_back_reference: + case REOP_back_reference_i: + /* validate back references */ + if (bc_buf[pos + 1] >= s->capture_count) + js_parse_error(s, "back reference is out of range"); + break; + } + pos += len; + } + return stack_size_max; +} + +/* return a JSByteArray. 'source' must be a string */ +static JSValue js_parse_regexp(JSParseState *s, int re_flags) +{ + JSByteArray *arr; + int register_count; + + s->multi_line = ((re_flags & LRE_FLAG_MULTILINE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0); + s->byte_code = JS_NULL; + s->byte_code_len = 0; + s->capture_count = 1; + + emit_u16(s, re_flags); + emit_u8(s, 0); /* number of captures */ + emit_u8(s, 0); /* number of registers */ + + if (!(re_flags & LRE_FLAG_STICKY)) { + re_emit_op_u32(s, REOP_split_goto_first, 1 + 5); + re_emit_op(s, REOP_any); + re_emit_op_u32(s, REOP_goto, -(5 + 1 + 5)); + } + re_emit_op_u8(s, REOP_save_start, 0); + + js_parse_call(s, PARSE_FUNC_re_parse_disjunction, 0); + + re_emit_op_u8(s, REOP_save_end, 0); + re_emit_op(s, REOP_match); + + if (s->buf_pos != s->buf_len) + js_parse_error(s, "extraneous characters at the end"); + + arr = JS_VALUE_TO_PTR(s->byte_code); + arr->buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + register_count = + re_compute_register_count(s, arr->buf + RE_HEADER_LEN, + s->byte_code_len - RE_HEADER_LEN); + arr->buf[RE_HEADER_REGISTER_COUNT] = register_count; + + js_shrink_byte_array(s->ctx, &s->byte_code, s->byte_code_len); + +#ifdef DUMP_REOP + arr = JS_VALUE_TO_PTR(s->byte_code); + lre_dump_bytecode(arr->buf, arr->size); +#endif + + return s->byte_code; +} + +/* regexp interpreter */ + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +static BOOL is_line_terminator(uint32_t c) +{ + return (c == '\n' || c == '\r' || c == CP_LS || c == CP_PS); +} + +static BOOL is_word_char(uint32_t c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c == '_')); +} + +/* Note: we canonicalize as in the unicode case, but only handle ASCII characters */ +static int lre_canonicalize(uint32_t c) +{ + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + return c; +} + +#define GET_CHAR(c, cptr, cbuf_end) \ + do { \ + size_t clen; \ + c = utf8_get(cptr, &clen); \ + cptr += clen; \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end) \ + do { \ + size_t clen; \ + c = utf8_get(cptr, &clen); \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start) \ + do { \ + const uint8_t *cptr1 = cptr - 1; \ + size_t clen; \ + while ((*cptr1 & 0xc0) == 0x80) \ + cptr1--; \ + c = utf8_get(cptr1, &clen); \ + } while (0) + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, +} REExecStateEnum; + +//#define DUMP_REEXEC + +/* return 1 if match, 0 if not match or < 0 if error. str must be a + JSString. capture_buf and byte_code are JSByteArray */ +static int lre_exec(JSContext *ctx, JSValue capture_buf, + JSValue byte_code, JSValue str, int cindex) +{ + const uint8_t *pc, *cptr, *cbuf; + uint32_t *capture; + int opcode, capture_count; + uint32_t val, c, idx; + const uint8_t *cbuf_end; + JSValue *sp, *bp, *initial_sp, *saved_stack_bottom; + JSByteArray *arr; /* temporary use */ + JSString *ps; /* temporary use */ + JSGCRef capture_buf_ref, byte_code_ref, str_ref; + + arr = JS_VALUE_TO_PTR(byte_code); + pc = arr->buf; + arr = JS_VALUE_TO_PTR(capture_buf); + capture = (uint32_t *)arr->buf; + capture_count = lre_get_capture_count(pc); + pc += RE_HEADER_LEN; + ps = JS_VALUE_TO_PTR(str); + cbuf = ps->buf; + cbuf_end = cbuf + ps->len; + cptr = cbuf + cindex; + + saved_stack_bottom = ctx->stack_bottom; + initial_sp = ctx->sp; + sp = initial_sp; + bp = initial_sp; + +#define LRE_POLL_INTERRUPT() do { \ + if (unlikely(--ctx->interrupt_counter <= 0)) { \ + JSValue ret; \ + int saved_pc, saved_cptr; \ + arr = JS_VALUE_TO_PTR(byte_code); \ + saved_pc = pc - arr->buf; \ + saved_cptr = cptr - cbuf; \ + JS_PUSH_VALUE(ctx, capture_buf); \ + JS_PUSH_VALUE(ctx, byte_code); \ + JS_PUSH_VALUE(ctx, str); \ + ctx->sp = sp; \ + ret = __js_poll_interrupt(ctx); \ + JS_POP_VALUE(ctx, str); \ + JS_POP_VALUE(ctx, byte_code); \ + JS_POP_VALUE(ctx, capture_buf); \ + if (JS_IsException(ret)) { \ + ctx->sp = initial_sp; \ + ctx->stack_bottom = saved_stack_bottom; \ + return -1; \ + } \ + arr = JS_VALUE_TO_PTR(byte_code); \ + pc = arr->buf + saved_pc; \ + ps = JS_VALUE_TO_PTR(str); \ + cbuf = ps->buf; \ + cbuf_end = cbuf + ps->len; \ + cptr = cbuf + saved_cptr; \ + arr = JS_VALUE_TO_PTR(capture_buf); \ + capture = (uint32_t *)arr->buf; \ + } \ + } while(0) + +#define CHECK_STACK_SPACE(n) \ + { \ + if (unlikely((sp - ctx->stack_bottom) < (n))) { \ + int ret, saved_pc, saved_cptr; \ + arr = JS_VALUE_TO_PTR(byte_code); \ + saved_pc = pc - arr->buf; \ + saved_cptr = cptr - cbuf; \ + JS_PUSH_VALUE(ctx, capture_buf); \ + JS_PUSH_VALUE(ctx, byte_code); \ + JS_PUSH_VALUE(ctx, str); \ + ctx->sp = sp; \ + ret = JS_StackCheck(ctx, n); \ + JS_POP_VALUE(ctx, str); \ + JS_POP_VALUE(ctx, byte_code); \ + JS_POP_VALUE(ctx, capture_buf); \ + if (ret < 0) { \ + ctx->sp = initial_sp; \ + ctx->stack_bottom = saved_stack_bottom; \ + return -1; \ + } \ + arr = JS_VALUE_TO_PTR(byte_code); \ + pc = arr->buf + saved_pc; \ + ps = JS_VALUE_TO_PTR(str); \ + cbuf = ps->buf; \ + cbuf_end = cbuf + ps->len; \ + cptr = cbuf + saved_cptr; \ + arr = JS_VALUE_TO_PTR(capture_buf); \ + capture = (uint32_t *)arr->buf; \ + } \ + } + +#define SAVE_CAPTURE(idx, value) \ + { \ + int __v = (value); \ + CHECK_STACK_SPACE(2); \ + sp[-2] = JS_NewShortInt(idx); \ + sp[-1] = JS_NewShortInt(capture[idx]); \ + sp -= 2; \ + capture[idx] = __v; \ + } + + /* avoid saving the previous value if already saved */ +#define SAVE_CAPTURE_CHECK(idx, value) \ + { \ + int __v = (value); \ + JSValue *sp1; \ + sp1 = sp; \ + for(;;) { \ + if (sp1 < bp) { \ + if (JS_VALUE_GET_INT(sp1[0]) == (idx)) \ + break; \ + sp1 += 2; \ + } else { \ + CHECK_STACK_SPACE(2); \ + sp[-2] = JS_NewShortInt(idx); \ + sp[-1] = JS_NewShortInt(capture[idx]); \ + sp -= 2; \ + break; \ + } \ + } \ + capture[idx] = __v; \ + } + +#define RE_PC_TYPE_TO_VALUE(pc, type) (((type) << 1) | (((pc) - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf) << 3)) +#define RE_VALUE_TO_PC(val) (((val) >> 3) + ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf) +#define RE_VALUE_TO_TYPE(val) (((val) >> 1) & 3) + +#ifdef DUMP_REEXEC + printf("%5s %5s %5s %5s %s\n", "PC", "CP", "BP", "SP", "OPCODE"); +#endif + for(;;) { + opcode = *pc++; +#ifdef DUMP_REEXEC + printf("%5ld %5ld %5ld %5ld %s\n", + pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN, + cptr - cbuf, + bp - initial_sp, + sp - initial_sp, + reopcode_info[opcode].name); +#endif + switch(opcode) { + case REOP_match: + ctx->sp = initial_sp; + ctx->stack_bottom = saved_stack_bottom; + return 1; + no_match: + for(;;) { + REExecStateEnum type; + if (bp == initial_sp) { + ctx->sp = initial_sp; + ctx->stack_bottom = saved_stack_bottom; + return 0; + } + /* undo the modifications to capture[] and regs[] */ + while (sp < bp) { + int idx2 = JS_VALUE_GET_INT(sp[0]); + capture[idx2] = JS_VALUE_GET_INT(sp[1]); + sp += 2; + } + + pc = RE_VALUE_TO_PC(sp[0]); + type = RE_VALUE_TO_TYPE(sp[0]); + cptr = JS_VALUE_GET_INT(sp[1]) + cbuf; + bp = VALUE_TO_SP(ctx, sp[2]); + sp += 3; + if (type != RE_EXEC_STATE_LOOKAHEAD) + break; + } + LRE_POLL_INTERRUPT(); + break; + case REOP_lookahead_match: + /* pop all the saved states until reaching the start of + the lookahead and keep the updated captures and + variables and the corresponding undo info. */ + { + JSValue *sp1, *sp_start, *next_sp; + REExecStateEnum type; + + sp_start = sp; + for(;;) { + sp1 = sp; + sp = bp; + pc = RE_VALUE_TO_PC(sp[0]); + type = RE_VALUE_TO_TYPE(sp[0]); + cptr = JS_VALUE_GET_INT(sp[1]) + cbuf; + bp = VALUE_TO_SP(ctx, sp[2]); + sp[2] = SP_TO_VALUE(ctx, sp1); /* save the next value for the copy step */ + sp += 3; + if (type == RE_EXEC_STATE_LOOKAHEAD) + break; + } + if (sp != initial_sp) { + /* keep the undo info if there is a saved state */ + sp1 = sp; + while (sp1 != sp_start) { + sp1 -= 3; + next_sp = VALUE_TO_SP(ctx, sp1[2]); + while (sp1 != next_sp) { + *--sp = *--sp1; + } + } + } + } + break; + case REOP_negative_lookahead_match: + /* pop all the saved states until reaching start of the negative lookahead */ + for(;;) { + REExecStateEnum type; + type = RE_VALUE_TO_TYPE(bp[0]); + /* undo the modifications to capture[] and regs[] */ + while (sp < bp) { + int idx2 = JS_VALUE_GET_INT(sp[0]); + capture[idx2] = JS_VALUE_GET_INT(sp[1]); + sp += 2; + } + pc = RE_VALUE_TO_PC(sp[0]); + type = RE_VALUE_TO_TYPE(sp[0]); + cptr = JS_VALUE_GET_INT(sp[1]) + cbuf; + bp = VALUE_TO_SP(ctx, sp[2]); + sp += 3; + if (type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD) + break; + } + goto no_match; + + case REOP_char1: + if ((cbuf_end - cptr) < 1) + goto no_match; + if (pc[0] != cptr[0]) + goto no_match; + pc++; + cptr++; + break; + case REOP_char2: + if ((cbuf_end - cptr) < 2) + goto no_match; + if (get_u16(pc) != get_u16(cptr)) + goto no_match; + pc += 2; + cptr += 2; + break; + case REOP_char3: + if ((cbuf_end - cptr) < 3) + goto no_match; + if (get_u16(pc) != get_u16(cptr) || pc[2] != cptr[2]) + goto no_match; + pc += 3; + cptr += 3; + break; + case REOP_char4: + if ((cbuf_end - cptr) < 4) + goto no_match; + if (get_u32(pc) != get_u32(cptr)) + goto no_match; + pc += 4; + cptr += 4; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + CHECK_STACK_SPACE(3); + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + sp -= 3; + sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT); + sp[1] = JS_NewShortInt(cptr - cbuf); + sp[2] = SP_TO_VALUE(ctx, bp); + bp = sp; + } + break; + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + CHECK_STACK_SPACE(3); + sp -= 3; + sp[0] = RE_PC_TYPE_TO_VALUE(pc + (int)val, + RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead); + sp[1] = JS_NewShortInt(cptr - cbuf); + sp[2] = SP_TO_VALUE(ctx, bp); + bp = sp; + break; + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + LRE_POLL_INTERRUPT(); + break; + case REOP_line_start: + case REOP_line_start_m: + if (cptr == cbuf) + break; + if (opcode == REOP_line_start) + goto no_match; + PEEK_PREV_CHAR(c, cptr, cbuf); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + case REOP_line_end_m: + if (cptr == cbuf_end) + break; + if (opcode == REOP_line_end) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_dot: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + if (is_line_terminator(c)) + goto no_match; + break; + case REOP_any: + if (cptr == cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + break; + case REOP_space: + case REOP_not_space: + { + BOOL v1; + if (cptr == cbuf_end) + goto no_match; + c = cptr[0]; + if (c < 128) { + cptr++; + v1 = unicode_is_space_ascii(c); + } else { + size_t clen; + c = __utf8_get(cptr, &clen); + cptr += clen; + v1 = unicode_is_space_non_ascii(c); + } + v1 ^= (opcode - REOP_space); + if (!v1) + goto no_match; + } + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + assert(val < capture_count); + idx = 2 * val + opcode - REOP_save_start; + SAVE_CAPTURE(idx, cptr - cbuf); + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + assert(val2 < capture_count); + CHECK_STACK_SPACE(2 * (val2 - val + 1)); + while (val <= val2) { + idx = 2 * val; + SAVE_CAPTURE(idx, 0); + idx = 2 * val + 1; + SAVE_CAPTURE(idx, 0); + val++; + } + } + break; + case REOP_set_i32: + idx = pc[0]; + val = get_u32(pc + 1); + pc += 5; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, val); + break; + case REOP_loop: + { + uint32_t val2; + idx = pc[0]; + val = get_u32(pc + 1); + pc += 5; + + val2 = capture[2 * capture_count + idx] - 1; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2); + if (val2 != 0) { + pc += (int)val; + LRE_POLL_INTERRUPT(); + } + } + break; + case REOP_loop_split_goto_first: + case REOP_loop_split_next_first: + case REOP_loop_check_adv_split_goto_first: + case REOP_loop_check_adv_split_next_first: + { + const uint8_t *pc1; + uint32_t val2, limit; + idx = pc[0]; + limit = get_u32(pc + 1); + val = get_u32(pc + 5); + pc += 9; + + /* decrement the counter */ + val2 = capture[2 * capture_count + idx] - 1; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, val2); + + if (val2 > limit) { + /* normal loop if counter > limit */ + pc += (int)val; + LRE_POLL_INTERRUPT(); + } else { + /* check advance */ + if ((opcode == REOP_loop_check_adv_split_goto_first || + opcode == REOP_loop_check_adv_split_next_first) && + capture[2 * capture_count + idx + 1] == (cptr - cbuf) && + val2 != limit) { + goto no_match; + } + + /* otherwise conditional split */ + if (val2 != 0) { + CHECK_STACK_SPACE(3); + if (opcode == REOP_loop_split_next_first || + opcode == REOP_loop_check_adv_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + sp -= 3; + sp[0] = RE_PC_TYPE_TO_VALUE(pc1, RE_EXEC_STATE_SPLIT); + sp[1] = JS_NewShortInt(cptr - cbuf); + sp[2] = SP_TO_VALUE(ctx, bp); + bp = sp; + } + } + } + break; + case REOP_set_char_pos: + idx = pc[0]; + pc++; + SAVE_CAPTURE_CHECK(2 * capture_count + idx, cptr - cbuf); + break; + case REOP_check_advance: + idx = pc[0]; + pc++; + if (capture[2 * capture_count + idx] == cptr - cbuf) + goto no_match; + break; + case REOP_word_boundary: + case REOP_not_word_boundary: + { + BOOL v1, v2; + BOOL is_boundary = (opcode == REOP_word_boundary); + /* char before */ + if (cptr == cbuf) { + v1 = FALSE; + } else { + PEEK_PREV_CHAR(c, cptr, cbuf); + v1 = is_word_char(c); + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = FALSE; + } else { + PEEK_CHAR(c, cptr, cbuf_end); + v2 = is_word_char(c); + } + if (v1 ^ v2 ^ is_boundary) + goto no_match; + } + break; + /* assumption: 8 bit and small number of ranges */ + case REOP_range8: + { + int n, i; + n = pc[0]; + pc++; + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + for(i = 0; i < n - 1; i++) { + if (c >= pc[2 * i] && c < pc[2 * i + 1]) + goto range8_match; + } + /* 0xff = max code point value */ + if (c >= pc[2 * i] && + (c < pc[2 * i + 1] || pc[2 * i + 1] == 0xff)) + goto range8_match; + goto no_match; + range8_match: + pc += 2 * n; + } + break; + case REOP_range: + { + int n; + uint32_t low, high, idx_min, idx_max, idx; + + n = get_u16(pc); /* n must be >= 1 */ + pc += 2; + if (cptr >= cbuf_end || n == 0) + goto no_match; + GET_CHAR(c, cptr, cbuf_end); + idx_min = 0; + low = get_u32(pc + 0 * 8); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u32(pc + idx_max * 8 + 4); + if (c >= high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u32(pc + idx * 8); + high = get_u32(pc + idx * 8 + 4); + if (c < low) + idx_max = idx - 1; + else if (c >= high) + idx_min = idx + 1; + else + goto range_match; + } + goto no_match; + range_match: + pc += 8 * n; + } + break; + case REOP_back_reference: + case REOP_back_reference_i: + val = pc[0]; + pc++; + if (capture[2 * val] != -1 && capture[2 * val + 1] != -1) { + const uint8_t *cptr1, *cptr1_end; + int c1, c2; + + cptr1 = cbuf + capture[2 * val]; + cptr1_end = cbuf + capture[2 * val + 1]; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end); + GET_CHAR(c2, cptr, cbuf_end); + if (opcode == REOP_back_reference_i) { + c1 = lre_canonicalize(c1); + c2 = lre_canonicalize(c2); + } + if (c1 != c2) + goto no_match; + } + } + break; + default: +#ifdef DUMP_REEXEC + printf("unknown opcode pc=%ld\n", pc - 1 - ((JSByteArray *)JS_VALUE_TO_PTR(byte_code))->buf - RE_HEADER_LEN); +#endif + abort(); + } + } +} + +/* regexp js interface */ + +/* return the length */ +static size_t js_parse_regexp_flags(int *pre_flags, const uint8_t *buf) +{ + const uint8_t *p = buf; + int mask, re_flags; + re_flags = 0; + while (*p != '\0') { + switch(*p) { +#if 0 + case 'd': + mask = LRE_FLAG_INDICES; + break; +#endif + case 'g': + mask = LRE_FLAG_GLOBAL; + break; + case 'i': + mask = LRE_FLAG_IGNORECASE; + break; + case 'm': + mask = LRE_FLAG_MULTILINE; + break; + case 's': + mask = LRE_FLAG_DOTALL; + break; + case 'u': + mask = LRE_FLAG_UNICODE; + break; +#if 0 + case 'v': + mask = LRE_FLAG_UNICODE_SETS; + break; +#endif + case 'y': + mask = LRE_FLAG_STICKY; + break; + default: + goto done; + } + if ((re_flags & mask) != 0) + break; + re_flags |= mask; + p++; + } + done: + *pre_flags = re_flags; + return p - buf; +} + +/* pattern and flags must be strings */ +static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, JSValue flags) +{ + int re_flags; + + re_flags = 0; + if (!JS_IsUndefined(flags)) { + JSString *ps; + JSStringCharBuf buf; + size_t len; + ps = get_string_ptr(ctx, &buf, flags); + len = js_parse_regexp_flags(&re_flags, ps->buf); + if (len != ps->len) + return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); + } + + return JS_Parse2(ctx, pattern, NULL, 0, "", + JS_EVAL_REGEXP | (re_flags << JS_EVAL_REGEXP_FLAGS_SHIFT)); +} + +static JSRegExp *js_get_regexp(JSContext *ctx, JSValue obj) +{ + JSObject *p; + p = js_get_object_class(ctx, obj, JS_CLASS_REGEXP); + if (!p) { + JS_ThrowTypeError(ctx, "not a regular expression"); + return NULL; + } + return &p->u.regexp; +} + +JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + return JS_NewInt32(ctx, re->last_index); +} + +JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + /* XXX: not complete */ + return re->source; +} + +JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re; + int last_index; + if (JS_ToInt32(ctx, &last_index, argv[0])) + return JS_EXCEPTION; + re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + re->last_index = last_index; + return JS_UNDEFINED; +} + +#define RE_FLAG_COUNT 6 + +/* return the string length */ +static size_t js_regexp_flags_str(char *buf, int re_flags) +{ + static const char flag_char[RE_FLAG_COUNT] = { 'g', 'i', 'm', 's', 'u', 'y' }; + char *p = buf; + int i; + + for(i = 0; i < RE_FLAG_COUNT; i++) { + if ((re_flags >> i) & 1) + *p++ = flag_char[i]; + } + *p = '\0'; + return p - buf; +} + +static void dump_regexp(JSContext *ctx, JSObject *p) +{ + JSStringCharBuf buf; + JSString *ps; + char buf2[RE_FLAG_COUNT + 1]; + JSByteArray *arr; + + js_putchar(ctx, '/'); + ps = get_string_ptr(ctx, &buf, p->u.regexp.source); + if (ps->len == 0) { + js_printf(ctx, "(?:)"); + } else { + js_printf(ctx, "%" JSValue_PRI, p->u.regexp.source); + } + arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code); + js_regexp_flags_str(buf2, lre_get_flags(arr->buf)); + js_printf(ctx, "/%s", buf2); +} + +JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re; + JSByteArray *arr; + size_t len; + char buf[RE_FLAG_COUNT + 1]; + + re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + arr = JS_VALUE_TO_PTR(re->byte_code); + len = js_regexp_flags_str(buf, lre_get_flags(arr->buf)); + return JS_NewStringLen(ctx, buf, len); +} + +JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue obj, byte_code; + JSObject *p; + JSGCRef byte_code_ref; + + argc &= ~FRAME_CF_CTOR; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + if (!JS_IsUndefined(argv[1])) { + argv[1] = JS_ToString(ctx, argv[1]); + if (JS_IsException(argv[1])) + return JS_EXCEPTION; + } + byte_code = js_compile_regexp(ctx, argv[0], argv[1]); + if (JS_IsException(byte_code)) + return JS_EXCEPTION; + JS_PUSH_VALUE(ctx, byte_code); + obj = JS_NewObjectClass(ctx, JS_CLASS_REGEXP, sizeof(JSRegExp)); + JS_POP_VALUE(ctx, byte_code); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_TO_PTR(obj); + p->u.regexp.source = argv[0]; + p->u.regexp.byte_code = byte_code; + p->u.regexp.last_index = 0; + return obj; +} + +enum { + MAGIC_REGEXP_EXEC, + MAGIC_REGEXP_TEST, + MAGIC_REGEXP_SEARCH, + MAGIC_REGEXP_FORCE_GLOBAL, /* same as exec but force the global flag */ +}; + +JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic) +{ + JSObject *p; + JSRegExp *re; + JSValue obj, *capture_buf, res; + uint32_t *capture, last_index_utf8; + int rc, capture_count, i, re_flags, last_index; + JSByteArray *bc_arr, *carr; + JSGCRef capture_buf_ref, obj_ref; + JSString *str; + JSStringCharBuf str_buf; + + re = js_get_regexp(ctx, *this_val); + if (!re) + return JS_EXCEPTION; + + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + last_index = max_int(re->last_index, 0); + + bc_arr = JS_VALUE_TO_PTR(re->byte_code); + re_flags = lre_get_flags(bc_arr->buf); + if (magic == MAGIC_REGEXP_FORCE_GLOBAL) + re_flags |= MAGIC_REGEXP_FORCE_GLOBAL; + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0 || + magic == MAGIC_REGEXP_SEARCH) { + last_index = 0; + } + capture_count = lre_get_capture_count(bc_arr->buf); + + carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf)); + if (!carr) + goto fail; + capture_buf = JS_PushGCRef(ctx, &capture_buf_ref); + *capture_buf = JS_VALUE_FROM_PTR(carr); + capture = (uint32_t *)carr->buf; + for(i = 0; i < 2 * capture_count; i++) + capture[i] = -1; + + if (last_index <= 0) + last_index_utf8 = 0; + else + last_index_utf8 = js_string_utf16_to_utf8_pos(ctx, argv[0], last_index) / 2; + if (last_index_utf8 > js_string_byte_len(ctx, argv[0])) { + rc = 2; + } else { + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + str = get_string_ptr(ctx, &str_buf, argv[0]); + /* JS_VALUE_FROM_PTR(str) is acceptable here because the + GC ignores pointers outside the heap */ + rc = lre_exec(ctx, *capture_buf, re->byte_code, JS_VALUE_FROM_PTR(str), + last_index_utf8); + } + if (rc != 1) { + if (rc >= 0) { + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + re->last_index = 0; + } + if (magic == MAGIC_REGEXP_SEARCH) + obj = JS_NewShortInt(-1); + else if (magic == MAGIC_REGEXP_TEST) + obj = JS_FALSE; + else + obj = JS_NULL; + } else { + goto fail; + } + } else { + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + if (magic == MAGIC_REGEXP_SEARCH) { + obj = JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2)); + goto done; + } + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + p = JS_VALUE_TO_PTR(*this_val); + re = &p->u.regexp; + re->last_index = js_string_utf8_to_utf16_pos(ctx, argv[0], capture[1] * 2); + } + if (magic == MAGIC_REGEXP_TEST) { + obj = JS_TRUE; + } else { + obj = JS_NewArray(ctx, capture_count); + if (JS_IsException(obj)) + goto fail; + + JS_PUSH_VALUE(ctx, obj); + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_index), + JS_NewShortInt(js_string_utf8_to_utf16_pos(ctx, argv[0], capture[0] * 2))); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(res)) + goto fail; + + JS_PUSH_VALUE(ctx, obj); + res = JS_DefinePropertyValue(ctx, obj, js_get_atom(ctx, JS_ATOM_input), + argv[0]); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(res)) + goto fail; + + for(i = 0; i < capture_count; i++) { + int start, end; + JSValue val; + + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + start = capture[2 * i]; + end = capture[2 * i + 1]; + if (start != -1 && end != -1) { + JSValueArray *arr; + JS_PUSH_VALUE(ctx, obj); + val = js_sub_string_utf8(ctx, argv[0], 2 * start, 2 * end); + JS_POP_VALUE(ctx, obj); + if (JS_IsException(val)) + goto fail; + p = JS_VALUE_TO_PTR(obj); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + arr->arr[i] = val; + } + } + } + } + done: + JS_PopGCRef(ctx, &capture_buf_ref); + return obj; + fail: + obj = JS_EXCEPTION; + goto done; +} + +/* if regexp replace: capture_buf != NULL, needle = NULL + if string replace: capture_buf = NULL, captures_len = 1, needle != NULL +*/ +static int js_string_concat_subst(JSContext *ctx, StringBuffer *b, + JSValue *str, JSValue *rep, + uint32_t pos, uint32_t end_of_match, + JSValue *capture_buf, uint32_t captures_len, + JSValue *needle) +{ + JSStringCharBuf buf_rep; + JSString *p; + int rep_len, i, j, j0, c, k; + + if (JS_IsFunction(ctx, *rep)) { + JSValue res, val; + JSGCRef val_ref; + int ret; + + if (JS_StackCheck(ctx, 4 + captures_len)) + return -1; + JS_PushArg(ctx, *str); + JS_PushArg(ctx, JS_NewShortInt(pos)); + if (capture_buf) { + for(k = captures_len - 1; k >= 0; k--) { + uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) { + val = js_sub_string_utf8(ctx, *str, captures[2 * k] * 2, captures[2 * k + 1] * 2); + if (JS_IsException(val)) + return -1; + JS_PUSH_VALUE(ctx, val); + ret = JS_StackCheck(ctx, 3 + k); + JS_POP_VALUE(ctx, val); + if (ret) + return -1; + } else { + val = JS_UNDEFINED; + } + JS_PushArg(ctx, val); + } + } else { + JS_PushArg(ctx, *needle); + } + JS_PushArg(ctx, *rep); /* function */ + JS_PushArg(ctx, JS_UNDEFINED); /* this_val */ + res = JS_Call(ctx, 2 + captures_len); + if (JS_IsException(res)) + return -1; + return string_buffer_concat(ctx, b, res); + } + + p = get_string_ptr(ctx, &buf_rep, *rep); + rep_len = p->len; + i = 0; + for(;;) { + p = get_string_ptr(ctx, &buf_rep, *rep); + j = i; + while (j < rep_len && p->buf[j] != '$') + j++; + if (j + 1 >= rep_len) + break; + j0 = j++; /* j0 = position of '$' */ + c = p->buf[j++]; + string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * j0); + if (c == '$') { + string_buffer_putc(ctx, b, '$'); + } else if (c == '&') { + if (capture_buf) { + string_buffer_concat_utf16(ctx, b, *str, pos, end_of_match); + } else { + string_buffer_concat_str(ctx, b, *needle); + } + } else if (c == '`') { + string_buffer_concat_utf16(ctx, b, *str, 0, pos); + } else if (c == '\'') { + string_buffer_concat_utf16(ctx, b, *str, end_of_match, js_string_len(ctx, *str)); + } else if (c >= '0' && c <= '9') { + k = c - '0'; + if (j < rep_len) { + c = p->buf[j]; + if (c >= '0' && c <= '9') { + k = k * 10 + c - '0'; + j++; + } + } + if (k >= 1 && k < captures_len) { + uint32_t *captures = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + if (captures[2 * k] != -1 && captures[2 * k + 1] != -1) { + string_buffer_concat_utf8(ctx, b, *str, + captures[2 * k] * 2, captures[2 * k + 1] * 2); + } + } else { + goto no_rep; + } + } else { + no_rep: + string_buffer_concat_utf8(ctx, b, *rep, 2 * j0, 2 * j); + } + i = j; + } + return string_buffer_concat_utf8(ctx, b, *rep, 2 * i, 2 * rep_len); +} + +JSValue js_string_replace(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_replaceAll) +{ + StringBuffer b_s, *b = &b_s; + int pos, endOfLastMatch, needle_len, input_len; + BOOL is_first, is_regexp; + + *this_val = JS_ToString(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP); + if (!is_regexp) { + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + } + if (!JS_IsFunction(ctx, argv[1])) { + argv[1] = JS_ToString(ctx, argv[1]); + if (JS_IsException(argv[1])) + return JS_EXCEPTION; + } + input_len = js_string_len(ctx, *this_val); + endOfLastMatch = 0; + + string_buffer_push(ctx, b, 0); + + if (is_regexp) { + int start, end, last_index, ret, re_flags, i, capture_count; + JSObject *p; + JSByteArray *bc_arr, *carr; + JSValue *capture_buf; + uint32_t *capture; + JSGCRef capture_buf_ref; + + p = JS_VALUE_TO_PTR(argv[0]); + bc_arr = JS_VALUE_TO_PTR(p->u.regexp.byte_code); + re_flags = lre_get_flags(bc_arr->buf); + capture_count = lre_get_capture_count(bc_arr->buf); + + if (re_flags & LRE_FLAG_GLOBAL) + p->u.regexp.last_index = 0; + + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { + last_index = 0; + } else { + last_index = max_int(p->u.regexp.last_index, 0); + } + + carr = js_alloc_byte_array(ctx, sizeof(uint32_t) * lre_get_alloc_count(bc_arr->buf)); + if (!carr) { + string_buffer_pop(ctx, b); + return JS_EXCEPTION; + } + capture_buf = JS_PushGCRef(ctx, &capture_buf_ref); + *capture_buf = JS_VALUE_FROM_PTR(carr); + capture = (uint32_t *)carr->buf; + for(i = 0; i < 2 * capture_count; i++) + capture[i] = -1; + + for(;;) { + if (last_index > input_len) { + ret = 0; + } else { + JSString *str; + JSStringCharBuf str_buf; + p = JS_VALUE_TO_PTR(argv[0]); + str = get_string_ptr(ctx, &str_buf, *this_val); + /* JS_VALUE_FROM_PTR(str) is acceptable here because the + GC ignores pointers outside the heap */ + ret = lre_exec(ctx, *capture_buf, p->u.regexp.byte_code, + JS_VALUE_FROM_PTR(str), + js_string_utf16_to_utf8_pos(ctx, *this_val, last_index) / 2); + } + if (ret < 0) { + JS_PopGCRef(ctx, &capture_buf_ref); + string_buffer_pop(ctx, b); + return JS_EXCEPTION; + } + if (ret == 0) { + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + p = JS_VALUE_TO_PTR(argv[0]); + p->u.regexp.last_index = 0; + } + break; + } + capture = (uint32_t *)((JSByteArray *)JS_VALUE_TO_PTR(*capture_buf))->buf; + start = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[0] * 2); + end = js_string_utf8_to_utf16_pos(ctx, *this_val, capture[1] * 2); + string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, start); + js_string_concat_subst(ctx, b, this_val, &argv[1], + start, end, capture_buf, capture_count, NULL); + endOfLastMatch = end; + if (!(re_flags & LRE_FLAG_GLOBAL)) { + if (re_flags & LRE_FLAG_STICKY) { + p = JS_VALUE_TO_PTR(argv[0]); + p->u.regexp.last_index = end; + } + break; + } + if (end == start) { + int c = string_getcp(ctx, *this_val, end, TRUE); + /* since regexp are unicode by default, replace is also unicode by default */ + end += 1 + (c >= 0x10000); + } + last_index = end; + } + JS_PopGCRef(ctx, &capture_buf_ref); + } else { + needle_len = js_string_len(ctx, argv[0]); + + is_first = TRUE; + for(;;) { + if (unlikely(needle_len == 0)) { + if (is_first) + pos = 0; + else if (endOfLastMatch >= input_len) + pos = -1; + else + pos = endOfLastMatch + 1; + } else { + pos = js_string_indexof(ctx, *this_val, argv[0], endOfLastMatch, + input_len, needle_len); + } + if (pos < 0) { + if (is_first) { + string_buffer_pop(ctx, b); + return *this_val; + } else { + break; + } + } + + string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, pos); + + js_string_concat_subst(ctx, b, this_val, &argv[1], + pos, pos + needle_len, NULL, 1, &argv[0]); + + endOfLastMatch = pos + needle_len; + is_first = FALSE; + if (!is_replaceAll) + break; + } + } + string_buffer_concat_utf16(ctx, b, *this_val, endOfLastMatch, input_len); + return string_buffer_pop(ctx, b); +} + +// split(sep, limit) +JSValue js_string_split(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSValue *A, T, ret, *z; + uint32_t lim, lengthA; + int p, q, s, e; + BOOL undef_sep; + JSGCRef A_ref, z_ref; + BOOL is_regexp; + + *this_val = JS_ToString(ctx, *this_val); + if (JS_IsException(*this_val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[1])) { + lim = 0xffffffff; + } else { + if (JS_ToUint32(ctx, &lim, argv[1]) < 0) + return JS_EXCEPTION; + } + is_regexp = (JS_GetClassID(ctx, argv[0]) == JS_CLASS_REGEXP); + if (!is_regexp) { + undef_sep = JS_IsUndefined(argv[0]); + argv[0] = JS_ToString(ctx, argv[0]); + if (JS_IsException(argv[0])) + return JS_EXCEPTION; + } else { + undef_sep = FALSE; + } + + A = JS_PushGCRef(ctx, &A_ref); + z = JS_PushGCRef(ctx, &z_ref); + *A = JS_NewArray(ctx, 0); + if (JS_IsException(*A)) + goto exception; + lengthA = 0; + + s = js_string_len(ctx, *this_val); + p = 0; + if (lim == 0) + goto done; + if (undef_sep) + goto add_tail; + + if (is_regexp) { + int numberOfCaptures, i, re_flags; + JSObject *p1; + JSValueArray *arr; + JSByteArray *bc_arr; + + p1 = JS_VALUE_TO_PTR(argv[0]); + bc_arr = JS_VALUE_TO_PTR(p1->u.regexp.byte_code); + re_flags = lre_get_flags(bc_arr->buf); + + if (s == 0) { + p1 = JS_VALUE_TO_PTR(argv[0]); + p1->u.regexp.last_index = 0; + *z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL); + if (JS_IsException(*z)) + goto exception; + if (JS_IsNull(*z)) + goto add_tail; + goto done; + } + q = 0; + while (q < s) { + p1 = JS_VALUE_TO_PTR(argv[0]); + p1->u.regexp.last_index = q; + /* XXX: need sticky behavior */ + *z = js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_FORCE_GLOBAL); + if (JS_IsException(*z)) + goto exception; + if (JS_IsNull(*z)) { + if (!(re_flags & LRE_FLAG_STICKY)) { + break; + } else { + int c = string_getcp(ctx, *this_val, q, TRUE); + /* since regexp are unicode by default, split is also unicode by default */ + q += 1 + (c >= 0x10000); + } + } else { + if (!(re_flags & LRE_FLAG_STICKY)) { + JSValue res; + res = JS_GetProperty(ctx, *z, js_get_atom(ctx, JS_ATOM_index)); + if (JS_IsException(res)) + goto exception; + q = JS_VALUE_GET_INT(res); + } + p1 = JS_VALUE_TO_PTR(argv[0]); + e = p1->u.regexp.last_index; + if (e > s) + e = s; + if (e == p) { + int c = string_getcp(ctx, *this_val, q, TRUE); + /* since regexp are unicode by default, split is also unicode by default */ + q += 1 + (c >= 0x10000); + } else { + T = js_sub_string(ctx, *this_val, p, q); + if (JS_IsException(T)) + goto exception; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + if (lengthA == lim) + goto done; + p1 = JS_VALUE_TO_PTR(*z); + numberOfCaptures = p1->u.array.len; + for(i = 1; i < numberOfCaptures; i++) { + p1 = JS_VALUE_TO_PTR(*z); + arr = JS_VALUE_TO_PTR(p1->u.array.tab); + T = arr->arr[i]; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + } + q = p = e; + } + } + } + } else { + int r = js_string_len(ctx, argv[0]); + if (s == 0) { + if (r != 0) + goto add_tail; + goto done; + } + + for (q = 0; (q += !r) <= s - r - !r; q = p = e + r) { + + e = js_string_indexof(ctx, *this_val, argv[0], q, s, r); + if (e < 0) + break; + T = js_sub_string(ctx, *this_val, p, e); + if (JS_IsException(T)) + goto exception; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + if (lengthA == lim) + goto done; + } + } +add_tail: + T = js_sub_string(ctx, *this_val, p, s); + if (JS_IsException(T)) + goto exception; + ret = JS_SetPropertyUint32(ctx, *A, lengthA++, T); + if (JS_IsException(ret)) + goto exception; + +done: + JS_PopGCRef(ctx, &z_ref); + return JS_PopGCRef(ctx, &A_ref); + +exception: + JS_PopGCRef(ctx, &z_ref); + JS_PopGCRef(ctx, &A_ref); + return JS_EXCEPTION; +} + +JSValue js_string_match(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + JSRegExp *re; + int global, n; + JSValue *A, *result, ret; + JSObject *p; + JSValueArray *arr; + JSByteArray *barr; + JSGCRef A_ref, result_ref; + + re = js_get_regexp(ctx, argv[0]); + if (!re) + return JS_EXCEPTION; + barr = JS_VALUE_TO_PTR(re->byte_code); + global = lre_get_flags(barr->buf) & LRE_FLAG_GLOBAL; + if (!global) + return js_regexp_exec(ctx, &argv[0], 1, this_val, 0); + + p = JS_VALUE_TO_PTR(argv[0]); + re = &p->u.regexp; + re->last_index = 0; + + A = JS_PushGCRef(ctx, &A_ref); + result = JS_PushGCRef(ctx, &result_ref); + *A = JS_NULL; + n = 0; + for(;;) { + *result = js_regexp_exec(ctx, &argv[0], 1, this_val, 0); + if (JS_IsException(*result)) + goto fail; + if (*result == JS_NULL) + break; + if (*A == JS_NULL) { + *A = JS_NewArray(ctx, 1); + if (JS_IsException(*A)) + goto fail; + } + + p = JS_VALUE_TO_PTR(*result); + arr = JS_VALUE_TO_PTR(p->u.array.tab); + + ret = JS_SetPropertyUint32(ctx, *A, n++, arr->arr[0]); + if (JS_IsException(ret)) { + fail: + *A = JS_EXCEPTION; + break; + } + } + JS_PopGCRef(ctx, &result_ref); + return JS_PopGCRef(ctx, &A_ref); +} + +JSValue js_string_search(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv) +{ + return js_regexp_exec(ctx, &argv[0], 1, this_val, MAGIC_REGEXP_SEARCH); +} diff --git a/mquickjs.h b/mquickjs.h new file mode 100644 index 00000000..a1557fe9 --- /dev/null +++ b/mquickjs.h @@ -0,0 +1,382 @@ +/* + * Micro QuickJS Javascript Engine + * + * 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 MQUICKJS_H +#define MQUICKJS_H + +#include + +#if defined(__GNUC__) || defined(__clang__) +#define __js_printf_like(f, a) __attribute__((format(printf, f, a))) +#else +#define __js_printf_like(a, b) +#endif + +#if INTPTR_MAX >= INT64_MAX +#define JS_PTR64 /* pointers are 64 bit wide instead of 32 bit wide */ +#endif + +typedef struct JSContext JSContext; + +#ifdef JS_PTR64 +typedef uint64_t JSWord; +typedef uint64_t JSValue; +#define JSW 8 +#define JSValue_PRI PRIo64 +#define JS_USE_SHORT_FLOAT +#else +typedef uint32_t JSWord; +typedef uint32_t JSValue; +#define JSW 4 +#define JSValue_PRI PRIo32 +#endif + +#define JS_BOOL int + +enum { + JS_TAG_INT = 0, /* 31 bit integer (1 bit) */ + JS_TAG_PTR = 1, /* pointer (2 bits) */ + JS_TAG_SPECIAL = 3, /* other special values (2 bits) */ + JS_TAG_BOOL = JS_TAG_SPECIAL | (0 << 2), /* (5 bits) */ + JS_TAG_NULL = JS_TAG_SPECIAL | (1 << 2), /* (5 bits) */ + JS_TAG_UNDEFINED = JS_TAG_SPECIAL | (2 << 2), /* (5 bits) */ + JS_TAG_EXCEPTION = JS_TAG_SPECIAL | (3 << 2), /* (5 bits) */ + JS_TAG_SHORT_FUNC = JS_TAG_SPECIAL | (4 << 2), /* (5 bits) */ + JS_TAG_UNINITIALIZED = JS_TAG_SPECIAL | (5 << 2), /* (5 bits) */ + JS_TAG_STRING_CHAR = JS_TAG_SPECIAL | (6 << 2), /* (5 bits) */ + JS_TAG_CATCH_OFFSET = JS_TAG_SPECIAL | (7 << 2), /* (5 bits) */ +#ifdef JS_USE_SHORT_FLOAT + JS_TAG_SHORT_FLOAT = 5, /* 3 bits */ +#endif +}; + +#define JS_TAG_SPECIAL_BITS 5 + +#define JS_VALUE_GET_INT(v) ((int)(v) >> 1) +#define JS_VALUE_GET_SPECIAL_VALUE(v) ((int)(v) >> JS_TAG_SPECIAL_BITS) +#define JS_VALUE_GET_SPECIAL_TAG(v) ((v) & ((1 << JS_TAG_SPECIAL_BITS) - 1)) +#define JS_VALUE_MAKE_SPECIAL(tag, v) ((tag) | ((v) << JS_TAG_SPECIAL_BITS)) + +#define JS_NULL JS_VALUE_MAKE_SPECIAL(JS_TAG_NULL, 0) +#define JS_UNDEFINED JS_VALUE_MAKE_SPECIAL(JS_TAG_UNDEFINED, 0) +#define JS_UNINITIALIZED JS_VALUE_MAKE_SPECIAL(JS_TAG_UNINITIALIZED, 0) +#define JS_FALSE JS_VALUE_MAKE_SPECIAL(JS_TAG_BOOL, 0) +#define JS_TRUE JS_VALUE_MAKE_SPECIAL(JS_TAG_BOOL, 1) + +#define JS_EX_NORMAL 0 /* all exceptions except not enough memory */ +#define JS_EX_CALL 1 /* specific exception to generate a tail call. The call flags are added */ +#define JS_EXCEPTION JS_VALUE_MAKE_SPECIAL(JS_TAG_EXCEPTION, JS_EX_NORMAL) + +typedef enum { + JS_CLASS_OBJECT, + JS_CLASS_ARRAY, + JS_CLASS_C_FUNCTION, + JS_CLASS_CLOSURE, + JS_CLASS_NUMBER, + JS_CLASS_BOOLEAN, + JS_CLASS_STRING, + JS_CLASS_DATE, + JS_CLASS_REGEXP, + + JS_CLASS_ERROR, + JS_CLASS_EVAL_ERROR, + JS_CLASS_RANGE_ERROR, + JS_CLASS_REFERENCE_ERROR, + JS_CLASS_SYNTAX_ERROR, + JS_CLASS_TYPE_ERROR, + JS_CLASS_URI_ERROR, + JS_CLASS_INTERNAL_ERROR, + + JS_CLASS_ARRAY_BUFFER, + JS_CLASS_TYPED_ARRAY, + + JS_CLASS_UINT8C_ARRAY, + JS_CLASS_INT8_ARRAY, + JS_CLASS_UINT8_ARRAY, + JS_CLASS_INT16_ARRAY, + JS_CLASS_UINT16_ARRAY, + JS_CLASS_INT32_ARRAY, + JS_CLASS_UINT32_ARRAY, + JS_CLASS_FLOAT32_ARRAY, + JS_CLASS_FLOAT64_ARRAY, + + JS_CLASS_USER, /* user classes start from this value */ +} JSObjectClassEnum; + +/* predefined functions */ +typedef enum { + JS_CFUNCTION_bound, + JS_CFUNCTION_USER, /* user functions start from this value */ +} JSCFunctionEnum; + +/* temporary buffer to hold C strings */ +typedef struct { + uint8_t buf[5]; +} JSCStringBuf; + +typedef struct JSGCRef { + JSValue val; + struct JSGCRef *prev; +} JSGCRef; + +/* stack of JSGCRef */ +JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref); +JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref); + +#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0) +#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref) + +/* list of JSGCRef (they can be removed in any order, slower) */ +JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref); +void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref); + +JSValue JS_NewFloat64(JSContext *ctx, double d); +JSValue JS_NewInt32(JSContext *ctx, int32_t val); +JSValue JS_NewUint32(JSContext *ctx, uint32_t val); +JSValue JS_NewInt64(JSContext *ctx, int64_t val); + +static inline JS_BOOL JS_IsInt(JSValue v) +{ + return (v & 1) == JS_TAG_INT; +} + +static inline JS_BOOL JS_IsPtr(JSValue v) +{ + return (v & (JSW - 1)) == JS_TAG_PTR; +} + +#ifdef JS_USE_SHORT_FLOAT +static inline JS_BOOL JS_IsShortFloat(JSValue v) +{ + return (v & (JSW - 1)) == JS_TAG_SHORT_FLOAT; +} +#endif + +static inline JS_BOOL JS_IsBool(JSValue v) +{ + return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_BOOL; +} + +static inline JS_BOOL JS_IsNull(JSValue v) +{ + return v == JS_NULL; +} + +static inline JS_BOOL JS_IsUndefined(JSValue v) +{ + return v == JS_UNDEFINED; +} + +static inline JS_BOOL JS_IsUninitialized(JSValue v) +{ + return v == JS_UNINITIALIZED; +} + +static inline JS_BOOL JS_IsException(JSValue v) +{ + return v == JS_EXCEPTION; +} + +static inline JSValue JS_NewBool(int val) +{ + return JS_VALUE_MAKE_SPECIAL(JS_TAG_BOOL, (val != 0)); +} + +JS_BOOL JS_IsNumber(JSContext *ctx, JSValue val); +JS_BOOL JS_IsString(JSContext *ctx, JSValue val); +JS_BOOL JS_IsError(JSContext *ctx, JSValue val); +JS_BOOL JS_IsFunction(JSContext *ctx, JSValue val); + +int JS_GetClassID(JSContext *ctx, JSValue val); +void JS_SetOpaque(JSContext *ctx, JSValue val, void *opaque); +void *JS_GetOpaque(JSContext *ctx, JSValue val); + +typedef JSValue JSCFunction(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +/* no JS function call be called from a C finalizer */ +typedef void (*JSCFinalizer)(JSContext *ctx, void *opaque); + +typedef enum JSCFunctionDefEnum { /* XXX: should rename for namespace isolation */ + JS_CFUNC_generic, + JS_CFUNC_generic_magic, + JS_CFUNC_constructor, + JS_CFUNC_constructor_magic, + JS_CFUNC_generic_params, + JS_CFUNC_f_f, +} JSCFunctionDefEnum; + +typedef union JSCFunctionType { + JSCFunction *generic; + JSValue (*generic_magic)(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic); + JSCFunction *constructor; + JSValue (*constructor_magic)(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, int magic); + JSValue (*generic_params)(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, JSValue params); + double (*f_f)(double f); +} JSCFunctionType; + +typedef struct JSCFunctionDef { + JSCFunctionType func; + JSValue name; + uint8_t def_type; + uint8_t arg_count; + int16_t magic; +} JSCFunctionDef; + +typedef struct { + const JSWord *stdlib_table; + const JSCFunctionDef *c_function_table; + const JSCFinalizer *c_finalizer_table; + uint32_t stdlib_table_len; + uint32_t stdlib_table_align; + uint32_t sorted_atoms_offset; + uint32_t global_object_offset; + uint32_t class_count; +} JSSTDLibraryDef; + +typedef void JSWriteFunc(void *opaque, const void *buf, size_t buf_len); +/* return != 0 if the JS code needs to be interrupted */ +typedef int JSInterruptHandler(JSContext *ctx, void *opaque); + +JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def); +/* if prepare_compilation is true, the context will be used to compile + to a binary file. JS_NewContext2() is not expected to be used in + the embedded version */ +JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, JS_BOOL prepare_compilation); +void JS_FreeContext(JSContext *ctx); +void JS_SetContextOpaque(JSContext *ctx, void *opaque); +void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler); +void JS_SetRandomSeed(JSContext *ctx, uint64_t seed); +JSValue JS_GetGlobalObject(JSContext *ctx); +JSValue JS_Throw(JSContext *ctx, JSValue obj); +JSValue __js_printf_like(3, 4) JS_ThrowError(JSContext *ctx, JSObjectClassEnum error_num, + const char *fmt, ...); +#define JS_ThrowTypeError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_TYPE_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowReferenceError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_REFERENCE_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowInternalError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_INTERNAL_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowRangeError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_RANGE_ERROR, fmt, ##__VA_ARGS__) +#define JS_ThrowSyntaxError(ctx, fmt, ...) JS_ThrowError(ctx, JS_CLASS_SYNTAX_ERROR, fmt, ##__VA_ARGS__) +JSValue JS_ThrowOutOfMemory(JSContext *ctx); +JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, const char *str); +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue obj, uint32_t idx); +JSValue JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *str, JSValue val); +JSValue JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val); +JSValue JS_NewObjectClassUser(JSContext *ctx, int class_id); +JSValue JS_NewObject(JSContext *ctx); +JSValue JS_NewArray(JSContext *ctx, int initial_len); +/* create a C function with an object parameter (closure) */ +JSValue JS_NewCFunctionParams(JSContext *ctx, int func_idx, JSValue params); + +#define JS_EVAL_RETVAL (1 << 0) /* return the last value instead of undefined (slower code) */ +#define JS_EVAL_REPL (1 << 1) /* implicitly defined global variables in assignments */ +#define JS_EVAL_STRIP_COL (1 << 2) /* strip column number debug information (save memory) */ +#define JS_EVAL_JSON (1 << 3) /* parse as JSON and return the object */ +#define JS_EVAL_REGEXP (1 << 4) /* internal use */ +#define JS_EVAL_REGEXP_FLAGS_SHIFT 8 /* internal use */ +JSValue JS_Parse(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +JSValue JS_Run(JSContext *ctx, JSValue val); +JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +void JS_GC(JSContext *ctx); +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len); +JSValue JS_NewString(JSContext *ctx, const char *buf); +const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val, JSCStringBuf *buf); +const char *JS_ToCString(JSContext *ctx, JSValue val, JSCStringBuf *buf); +JSValue JS_ToString(JSContext *ctx, JSValue val); +int JS_ToInt32(JSContext *ctx, int *pres, JSValue val); +int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val); +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val); +int JS_ToNumber(JSContext *ctx, double *pres, JSValue val); + +JSValue JS_GetException(JSContext *ctx); +int JS_StackCheck(JSContext *ctx, uint32_t len); +void JS_PushArg(JSContext *ctx, JSValue val); +#define FRAME_CF_CTOR (1 << 16) /* also ored with argc in + C constructors */ +JSValue JS_Call(JSContext *ctx, int call_flags); + +#define JS_BYTECODE_MAGIC 0xacfb + +typedef struct { + uint16_t magic; /* JS_BYTECODE_MAGIC */ + uint16_t version; + uintptr_t base_addr; + JSValue unique_strings; + JSValue main_func; +} JSBytecodeHeader; + +/* only used on the host when compiling to file */ +void JS_PrepareBytecode(JSContext *ctx, + JSBytecodeHeader *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code); +/* only used on the host when compiling to file */ +int JS_RelocateBytecode2(JSContext *ctx, JSBytecodeHeader *hdr, + uint8_t *buf, uint32_t buf_len, + uintptr_t new_base_addr, JS_BOOL update_atoms); +#if JSW == 8 +typedef struct { + uint16_t magic; /* JS_BYTECODE_MAGIC */ + uint16_t version; + uint32_t base_addr; + uint32_t unique_strings; + uint32_t main_func; +} JSBytecodeHeader32; + +/* only used on the host when compiling to file. A 32 bit bytecode is generated on a 64 bit host. */ +int JS_PrepareBytecode64to32(JSContext *ctx, + JSBytecodeHeader32 *hdr, + const uint8_t **pdata_buf, uint32_t *pdata_len, + JSValue eval_code); +#endif + +JS_BOOL JS_IsBytecode(const uint8_t *buf, size_t buf_len); +/* Relocate the bytecode in 'buf' so that it can be executed + later. Return 0 if OK, != 0 if error */ +int JS_RelocateBytecode(JSContext *ctx, + uint8_t *buf, uint32_t buf_len); +/* Load the precompiled bytecode from 'buf'. 'buf' must be allocated + as long as the JSContext exists. Use JS_Run() to execute + it. warning: the bytecode is not checked so it should come from a + trusted source. */ +JSValue JS_LoadBytecode(JSContext *ctx, const uint8_t *buf); + +/* debug functions */ +void JS_SetLogFunc(JSContext *ctx, JSWriteFunc *write_func); +void JS_PrintValue(JSContext *ctx, JSValue val); +#define JS_DUMP_LONG (1 << 0) /* display object/array content */ +#define JS_DUMP_NOQUOTE (1 << 1) /* strings: no quote for identifiers */ +/* for low level dumps: don't dump special properties and use specific + quotes to distinguish string chars, unique strings and normal + strings */ +#define JS_DUMP_RAW (1 << 2) +void JS_PrintValueF(JSContext *ctx, JSValue val, int flags); +void JS_DumpValueF(JSContext *ctx, const char *str, + JSValue val, int flags); +void JS_DumpValue(JSContext *ctx, const char *str, + JSValue val); +void JS_DumpMemory(JSContext *ctx, JS_BOOL is_long); + +#endif /* MQUICKJS_H */ diff --git a/mquickjs_build.c b/mquickjs_build.c new file mode 100644 index 00000000..61732715 --- /dev/null +++ b/mquickjs_build.c @@ -0,0 +1,932 @@ +/* + * Micro QuickJS build utility + * + * 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 "list.h" +#include "mquickjs_build.h" + +static unsigned JSW = 4; // override this with -m64 + +typedef struct { + char *str; + int offset; +} AtomDef; + +typedef struct { + AtomDef *tab; + int count; + int size; + int offset; +} AtomList; + +typedef struct { + char *name; + int length; + char *magic; + char *cproto_name; + char *cfunc_name; +} CFuncDef; + +typedef struct { + CFuncDef *tab; + int count; + int size; +} CFuncList; + +typedef struct { + struct list_head link; + const JSClassDef *class1; + int class_idx; + char *finalizer_name; + char *class_id; +} ClassDefEntry; + +typedef struct { + AtomList atom_list; + CFuncList cfunc_list; + int cur_offset; + int sorted_atom_table_offset; + int global_object_offset; + struct list_head class_list; +} BuildContext; + +static const char *atoms[] = { +#define DEF(a, b) b, + /* keywords */ + DEF(null, "null") /* must be first */ + DEF(false, "false") + DEF(true, "true") + DEF(if, "if") + DEF(else, "else") + DEF(return, "return") + DEF(var, "var") + DEF(this, "this") + DEF(delete, "delete") + DEF(void, "void") + DEF(typeof, "typeof") + DEF(new, "new") + DEF(in, "in") + DEF(instanceof, "instanceof") + DEF(do, "do") + DEF(while, "while") + DEF(for, "for") + DEF(break, "break") + DEF(continue, "continue") + DEF(switch, "switch") + DEF(case, "case") + DEF(default, "default") + DEF(throw, "throw") + DEF(try, "try") + DEF(catch, "catch") + DEF(finally, "finally") + DEF(function, "function") + DEF(debugger, "debugger") + DEF(with, "with") + /* FutureReservedWord */ + DEF(class, "class") + DEF(const, "const") + DEF(enum, "enum") + DEF(export, "export") + DEF(extends, "extends") + DEF(import, "import") + DEF(super, "super") + /* FutureReservedWords when parsing strict mode code */ + DEF(implements, "implements") + DEF(interface, "interface") + DEF(let, "let") + DEF(package, "package") + DEF(private, "private") + DEF(protected, "protected") + DEF(public, "public") + DEF(static, "static") + DEF(yield, "yield") +#undef DEF + + /* other atoms */ + "", + "toString", + "valueOf", + "number", + "object", + "undefined", + "string", + "boolean", + "", + "", + "eval", + "arguments", + "value", + "get", + "set", + "prototype", + "constructor", + "length", + "target", + "of", + "NaN", + "Infinity", + "-Infinity", + "name", + "Error", + "__proto__", + "index", + "input", +}; + + +static char *cvt_name(char *buf, size_t buf_size, const char *str) +{ + size_t i, len = strlen(str); + assert(len < buf_size); + if (len == 0) { + strcpy(buf, "empty"); + } else { + strcpy(buf, str); + for(i = 0; i < len; i++) { + if (buf[i] == '<' || buf[i] == '>' || buf[i] == '-') + buf[i] = '_'; + } + } + return buf; +} + +static BOOL is_ascii_string(const char *buf, size_t len) +{ + size_t i; + for(i = 0; i < len; i++) { + if ((uint8_t)buf[i] > 0x7f) + return FALSE; + } + return TRUE; +} + +static BOOL is_numeric_string(const char *buf, size_t len) +{ + return (!strcmp(buf, "NaN") || + !strcmp(buf, "Infinity") || + !strcmp(buf, "-Infinity")); +} + +static int find_atom(AtomList *s, const char *str) +{ + int i; + for(i = 0; i < s->count; i++) { + if (!strcmp(str, s->tab[i].str)) + return i; + } + return -1; +} + +static int add_atom(AtomList *s, const char *str) +{ + int i; + AtomDef *e; + i = find_atom(s, str); + if (i >= 0) + return s->tab[i].offset; + if ((s->count + 1) > s->size) { + s->size = max_int(s->count + 1, s->size * 3 / 2); + s->tab = realloc(s->tab, sizeof(s->tab[0]) * s->size); + } + e = &s->tab[s->count++]; + e->str = strdup(str); + e->offset = s->offset; + s->offset += 1 + ((strlen(str) + JSW) / JSW); + return s->count - 1; +} + +static int add_cfunc(CFuncList *s, const char *name, int length, const char *magic, const char *cproto_name, const char *cfunc_name) +{ + int i; + CFuncDef *e; + + for(i = 0; i < s->count; i++) { + e = &s->tab[i]; + if (!strcmp(name, e->name) && + length == e->length && + !strcmp(magic, e->magic) && + !strcmp(cproto_name, e->cproto_name) && + !strcmp(cfunc_name, e->cfunc_name)) { + return i; + } + } + if ((s->count + 1) > s->size) { + s->size = max_int(s->count + 1, s->size * 3 / 2); + s->tab = realloc(s->tab, sizeof(s->tab[0]) * s->size); + } + e = &s->tab[s->count++]; + e->name = strdup(name); + e->magic = strdup(magic); + e->length = length; + e->cproto_name = strdup(cproto_name); + e->cfunc_name = strdup(cfunc_name); + return s->count - 1; +} + +static void dump_atom_defines(void) +{ + AtomList atom_list_s, *s = &atom_list_s; + AtomDef *e; + int i; + char buf[256]; + + memset(s, 0, sizeof(*s)); + + /* add the predefined atoms (they have a corresponding define) */ + for(i = 0; i < countof(atoms); i++) { + add_atom(s, atoms[i]); + } + + for(i = 0; i < s->count; i++) { + e = &s->tab[i]; + printf("#define JS_ATOM_%s %d\n", + cvt_name(buf, sizeof(buf), e->str), e->offset); + } + printf("\n"); + printf("#define JS_ATOM_END %d\n", s->offset); + printf("\n"); +} + +static int atom_cmp(const void *p1, const void *p2) +{ + const AtomDef *a1 = (const AtomDef *)p1; + const AtomDef *a2 = (const AtomDef *)p2; + return strcmp(a1->str, a2->str); +} + +/* js_atom_table must be properly aligned because the property hash + table uses the low bits of the atom pointer value */ +#define ATOM_ALIGN 64 + +static void dump_atoms(BuildContext *ctx) +{ + AtomList *s = &ctx->atom_list; + int i, j, k, l, len, len1, is_ascii, is_numeric; + uint64_t v; + const char *str; + AtomDef *sorted_atoms; + char buf[256]; + + sorted_atoms = malloc(sizeof(sorted_atoms[0]) * s->count); + memcpy(sorted_atoms, s->tab, sizeof(sorted_atoms[0]) * s->count); + qsort(sorted_atoms, s->count, sizeof(sorted_atoms[0]), atom_cmp); + + printf(" /* atom_table */\n"); + for(i = 0; i < s->count; i++) { + str = s->tab[i].str; + len = strlen(str); + is_ascii = is_ascii_string(str, len); + is_numeric = is_numeric_string(str, len); + printf(" (JS_MTAG_STRING << 1) | (1 << JS_MTAG_BITS) | (%d << (JS_MTAG_BITS + 1)) | (%d << (JS_MTAG_BITS + 2)) | (%d << (JS_MTAG_BITS + 3)), /* \"%s\" (offset=%d) */\n", + is_ascii, is_numeric, len, str, ctx->cur_offset); + len1 = (len + JSW) / JSW; + for(j = 0; j < len1; j++) { + l = min_uint32(JSW, len - j * JSW); + v = 0; + for(k = 0; k < l; k++) + v |= (uint64_t)(uint8_t)str[j * JSW + k] << (k * 8); + printf(" 0x%0*" PRIx64 ",\n", JSW * 2, v); + } + assert(ctx->cur_offset == s->tab[i].offset); + ctx->cur_offset += len1 + 1; + } + printf("\n"); + + ctx->sorted_atom_table_offset = ctx->cur_offset; + + printf(" /* sorted atom table (offset=%d) */\n", ctx->cur_offset); + printf(" JS_VALUE_ARRAY_HEADER(%d),\n", s->count); + for(i = 0; i < s->count; i++) { + AtomDef *e = &sorted_atoms[i]; + printf(" JS_ROM_VALUE(%d), /* %s */\n", + e->offset, cvt_name(buf, sizeof(buf), e->str)); + } + ctx->cur_offset += s->count + 1; + printf("\n"); + + free(sorted_atoms); +} + +static int define_value(BuildContext *s, const JSPropDef *d); + +static uint32_t dump_atom(BuildContext *s, const char *str, BOOL value_only) +{ + int len, idx, i, offset; + + len = strlen(str); + for(i = 0; i < len; i++) { + if ((uint8_t)str[i] >= 128) { + fprintf(stderr, "unicode property names are not supported yet (%s)\n", str); + exit(1); + } + } + if (len >= 1 && (str[0] >= '0' && str[0] <= '9')) { + fprintf(stderr, "numeric property names are not supported yet (%s)\n", str); + exit(1); + } + if (len == 1) { + if (value_only) { + /* XXX: hardcoded */ + return ((uint8_t)str[0] << 5) | 0x1b; + } + printf("JS_VALUE_MAKE_SPECIAL(JS_TAG_STRING_CHAR, %d)", + (uint8_t)str[0]); + } else { + idx = find_atom(&s->atom_list, str); + if (idx < 0) { + fprintf(stderr, "atom '%s' is undefined\n", str); + exit(1); + } + offset = s->atom_list.tab[idx].offset; + if (value_only) + return (offset * JSW) + 1; /* correct modulo ATOM_ALIGN */ + printf("JS_ROM_VALUE(%d)", offset); + } + printf(" /* %s */", str); + return 0; +} + +static void dump_cfuncs(BuildContext *s) +{ + int i; + CFuncDef *e; + + printf("static const JSCFunctionDef js_c_function_table[] = {\n"); + for(i = 0; i < s->cfunc_list.count; i++) { + e = &s->cfunc_list.tab[i]; + printf(" { { .%s = %s },\n", e->cproto_name, e->cfunc_name); + printf(" "); + dump_atom(s, e->name, FALSE); + printf(",\n"); + printf(" JS_CFUNC_%s, %d, %s },\n", + e->cproto_name, e->length, e->magic); + } + printf("};\n\n"); +} + +static void dump_cfinalizers(BuildContext *s) +{ + struct list_head *el; + ClassDefEntry *e; + + printf("static const JSCFinalizer js_c_finalizer_table[JS_CLASS_COUNT - JS_CLASS_USER] = {\n"); + list_for_each(el, &s->class_list) { + e = list_entry(el, ClassDefEntry, link); + if (e->finalizer_name && + strcmp(e->finalizer_name, "NULL") != 0) { + printf(" [%s - JS_CLASS_USER] = %s,\n", e->class_id, e->finalizer_name); + } + } + printf("};\n\n"); +} + +typedef enum { + PROPS_KIND_GLOBAL, + PROPS_KIND_PROTO, + PROPS_KIND_CLASS, + PROPS_KIND_OBJECT, +} JSPropsKindEnum; + +static inline uint32_t hash_prop(BuildContext *s, const char *name) +{ + /* Compute the hash for a symbol, must be consistent with + mquickjs.c implementation. + */ + uint32_t prop = dump_atom(s, name, TRUE); + return (prop / JSW) ^ (prop % JSW); /* XXX: improve */ +} + +static int define_props(BuildContext *s, const JSPropDef *props_def, + JSPropsKindEnum props_kind, const char *class_id_str) +{ + int i, *ident_tab, idx, props_ident, n_props; + int prop_idx; + const JSPropDef *d; + uint32_t *prop_hash; + BOOL is_global_object = (props_kind == PROPS_KIND_GLOBAL); + static const JSPropDef dummy_props[] = { + { JS_DEF_END }, + }; + + if (!props_def) + props_def = dummy_props; + + n_props = 0; + for(d = props_def; d->def_type != JS_DEF_END; d++) { + n_props++; + } + if (props_kind == PROPS_KIND_PROTO || + props_kind == PROPS_KIND_CLASS) + n_props++; + ident_tab = malloc(sizeof(ident_tab[0]) * n_props); + + /* define the various objects */ + for(d = props_def, i = 0; d->def_type != JS_DEF_END; d++, i++) { + ident_tab[i] = define_value(s, d); + } + + props_ident = -1; + prop_hash = NULL; + if (is_global_object) { + props_ident = s->cur_offset; + printf(" /* global object properties (offset=%d) */\n", props_ident); + printf(" JS_VALUE_ARRAY_HEADER(%d),\n", 2 * n_props); + s->cur_offset += 2 * n_props + 1; + } else { + int hash_size_log2; + uint32_t hash_size, hash_mask; + uint32_t *hash_table, h; + + if (n_props <= 1) + hash_size_log2 = 0; + else + hash_size_log2 = (32 - clz32(n_props - 1)) - 1; + hash_size = 1 << hash_size_log2; + if (hash_size > ATOM_ALIGN / JSW) { +#if !defined __APPLE__ + // XXX: Cannot request data alignment larger than 64 bytes on Darwin + fprintf(stderr, "Too many properties, consider increasing ATOM_ALIGN\n"); +#endif + hash_size = ATOM_ALIGN / JSW; + } + hash_mask = hash_size - 1; + + hash_table = malloc(sizeof(hash_table[0]) * hash_size); + prop_hash = malloc(sizeof(prop_hash[0]) * n_props); + /* build the hash table */ + for(i = 0; i < hash_size; i++) + hash_table[i] = 0; + prop_idx = 0; + for(i = 0, d = props_def; i < n_props; i++, d++) { + const char *name; + if (d->def_type != JS_DEF_END) { + name = d->name; + } else { + if (props_kind == PROPS_KIND_PROTO) + name = "constructor"; + else + name = "prototype"; + } + h = hash_prop(s, name) & hash_mask; + prop_hash[prop_idx] = hash_table[h]; + hash_table[h] = 2 + hash_size + 3 * prop_idx; + prop_idx++; + } + + props_ident = s->cur_offset; + printf(" /* properties (offset=%d) */\n", props_ident); + printf(" JS_VALUE_ARRAY_HEADER(%d),\n", 2 + hash_size + n_props * 3); + printf(" %d << 1, /* n_props */\n", n_props); + printf(" %d << 1, /* hash_mask */\n", hash_mask); + for(i = 0; i < hash_size; i++) { + printf(" %d << 1,\n", hash_table[i]); + } + s->cur_offset += hash_size + 3 + 3 * n_props; + free(hash_table); + } + prop_idx = 0; + for(d = props_def, i = 0; i < n_props; d++, i++) { + const char *name, *prop_type; + /* name */ + printf(" "); + if (d->def_type != JS_DEF_END) { + name = d->name; + } else { + if (props_kind == PROPS_KIND_PROTO) + name = "constructor"; + else + name = "prototype"; + } + dump_atom(s, name, FALSE); + printf(",\n"); + + printf(" "); + prop_type = "NORMAL"; + switch(d->def_type) { + case JS_DEF_PROP_DOUBLE: + if (ident_tab[i] >= 0) + goto value_ptr; + /* short int */ + printf("%d << 1,", (int32_t)d->u.f64); + break; + case JS_DEF_CGETSET: + if (is_global_object) { + fprintf(stderr, "getter/setter forbidden in global object\n"); + exit(1); + } + prop_type = "GETSET"; + goto value_ptr; + case JS_DEF_CLASS: + value_ptr: + assert(ident_tab[i] >= 0); + printf("JS_ROM_VALUE(%d),", ident_tab[i]); + break; + case JS_DEF_PROP_UNDEFINED: + printf("JS_UNDEFINED,"); + break; + case JS_DEF_PROP_NULL: + printf("JS_NULL,"); + break; + case JS_DEF_PROP_STRING: + dump_atom(s, d->u.str, FALSE); + printf(","); + break; + case JS_DEF_CFUNC: + idx = add_cfunc(&s->cfunc_list, + d->name, + d->u.func.length, + d->u.func.magic, + d->u.func.cproto_name, + d->u.func.func_name); + printf("JS_VALUE_MAKE_SPECIAL(JS_TAG_SHORT_FUNC, %d),", idx); + break; + case JS_DEF_END: + if (props_kind == PROPS_KIND_PROTO) { + /* constructor property */ + printf("(uint32_t)(-%s - 1) << 1,", class_id_str); + } else { + /* prototype property */ + printf("%s << 1,", class_id_str); + } + prop_type = "SPECIAL"; + break; + default: + abort(); + } + printf("\n"); + if (!is_global_object) { + printf(" (%d << 1) | (JS_PROP_%s << 30),\n", + prop_hash[prop_idx], prop_type); + } + prop_idx++; + } + + free(prop_hash); + free(ident_tab); + return props_ident; +} + +static ClassDefEntry *find_class(BuildContext *s, const JSClassDef *d) +{ + struct list_head *el; + ClassDefEntry *e; + + list_for_each(el, &s->class_list) { + e = list_entry(el, ClassDefEntry, link); + if (e->class1 == d) + return e; + } + return NULL; +} + +static void free_class_entries(BuildContext *s) +{ + struct list_head *el, *el1; + ClassDefEntry *e; + list_for_each_safe(el, el1, &s->class_list) { + e = list_entry(el, ClassDefEntry, link); + free(e->class_id); + free(e->finalizer_name); + free(e); + } + init_list_head(&s->class_list); +} + +static int define_class(BuildContext *s, const JSClassDef *d) +{ + int ctor_func_idx = -1, class_props_idx = -1, proto_props_idx = -1; + int ident, parent_class_idx = -1; + ClassDefEntry *e; + + /* check if the class is already defined */ + e = find_class(s, d); + if (e) + return e->class_idx; + + if (d->parent_class) + parent_class_idx = define_class(s, d->parent_class); + + if (d->func_name) { + ctor_func_idx = add_cfunc(&s->cfunc_list, + d->name, + d->length, + d->class_id, + d->cproto_name, + d->func_name); + } + + if (ctor_func_idx >= 0) { + class_props_idx = define_props(s, d->class_props, PROPS_KIND_CLASS, d->class_id); + proto_props_idx = define_props(s, d->proto_props, PROPS_KIND_PROTO, d->class_id); + } else { + if (d->class_props) + class_props_idx = define_props(s, d->class_props, PROPS_KIND_OBJECT, d->class_id); + } + + ident = s->cur_offset; + printf(" /* class (offset=%d) */\n", ident); + printf(" JS_MB_HEADER_DEF(JS_MTAG_OBJECT),\n"); + if (class_props_idx >= 0) + printf(" JS_ROM_VALUE(%d),\n", class_props_idx); + else + printf(" JS_NULL,\n"); + printf(" %d,\n", ctor_func_idx); + if (proto_props_idx >= 0) + printf(" JS_ROM_VALUE(%d),\n", proto_props_idx); + else + printf(" JS_NULL,\n"); + if (parent_class_idx >= 0) { + printf(" JS_ROM_VALUE(%d),\n", parent_class_idx); + } else { + printf(" JS_NULL,\n"); + } + printf("\n"); + + s->cur_offset += 5; + + e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->class_idx = ident; + e->class1 = d; + if (ctor_func_idx >= 0) { + e->class_id = strdup(d->class_id); + e->finalizer_name = strdup(d->finalizer_name); + } + list_add_tail(&e->link, &s->class_list); + return ident; +} + +#define JS_SHORTINT_MIN (-(1 << 30)) +#define JS_SHORTINT_MAX ((1 << 30) - 1) + +static BOOL is_short_int(double d) +{ + return (d >= JS_SHORTINT_MIN && d <= JS_SHORTINT_MAX && (int32_t)d == d); +} + +static int define_value(BuildContext *s, const JSPropDef *d) +{ + int ident; + ident = -1; + switch(d->def_type) { + case JS_DEF_PROP_DOUBLE: + { + uint64_t v; + if (!is_short_int(d->u.f64)) { + ident = s->cur_offset; + printf(" /* float64 (offset=%d) */\n", ident); + printf(" JS_MB_HEADER_DEF(JS_MTAG_FLOAT64),\n"); + v = float64_as_uint64(d->u.f64); + if (JSW == 8) { + printf(" 0x%016zx,\n", (size_t)v); + printf("\n"); + s->cur_offset += 2; + } else { + /* XXX: little endian assumed */ + printf(" 0x%08x,\n", (uint32_t)v); + printf(" 0x%08x,\n", (uint32_t)(v >> 32)); + printf("\n"); + s->cur_offset += 3; + } + } + } + break; + case JS_DEF_CLASS: + ident = define_class(s, d->u.class1); + break; + case JS_DEF_CGETSET: + { + int get_idx = -1, set_idx = -1; + char buf[256]; + if (strcmp(d->u.getset.get_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "get %s", d->name); + get_idx = add_cfunc(&s->cfunc_list, + buf, + 0, /* length */ + d->u.getset.magic, + d->u.getset.cproto_name, + d->u.getset.get_func_name); + } + if (strcmp(d->u.getset.set_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "set %s", d->name); + set_idx = add_cfunc(&s->cfunc_list, + buf, + 1, /* length */ + d->u.getset.magic, + d->u.getset.cproto_name, + d->u.getset.set_func_name); + } + ident = s->cur_offset; + printf(" /* getset (offset=%d) */\n", ident); + printf(" JS_VALUE_ARRAY_HEADER(2),\n"); + if (get_idx >= 0) + printf(" JS_VALUE_MAKE_SPECIAL(JS_TAG_SHORT_FUNC, %d),\n", get_idx); + else + printf(" JS_UNDEFINED,\n"); + if (set_idx >= 0) + printf(" JS_VALUE_MAKE_SPECIAL(JS_TAG_SHORT_FUNC, %d),\n", set_idx); + else + printf(" JS_UNDEFINED,\n"); + printf("\n"); + s->cur_offset += 3; + } + break; + default: + break; + } + return ident; +} + +static void define_atoms_props(BuildContext *s, const JSPropDef *props_def, JSPropsKindEnum props_kind); + +static void define_atoms_class(BuildContext *s, const JSClassDef *d) +{ + ClassDefEntry *e; + /* check if the class is already defined */ + e = find_class(s, d); + if (e) + return; + if (d->parent_class) + define_atoms_class(s, d->parent_class); + if (d->func_name) + add_atom(&s->atom_list, d->name); + if (d->class_props) + define_atoms_props(s, d->class_props, d->func_name ? PROPS_KIND_CLASS : PROPS_KIND_OBJECT); + if (d->proto_props) + define_atoms_props(s, d->proto_props, PROPS_KIND_PROTO); +} + +static void define_atoms_props(BuildContext *s, const JSPropDef *props_def, JSPropsKindEnum props_kind) +{ + const JSPropDef *d; + for(d = props_def; d->def_type != JS_DEF_END; d++) { + add_atom(&s->atom_list, d->name); + switch(d->def_type) { + case JS_DEF_PROP_STRING: + add_atom(&s->atom_list, d->u.str); + break; + case JS_DEF_CLASS: + define_atoms_class(s, d->u.class1); + break; + case JS_DEF_CGETSET: + { + char buf[256]; + if (strcmp(d->u.getset.get_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "get %s", d->name); + add_atom(&s->atom_list, buf); + } + if (strcmp(d->u.getset.set_func_name, "NULL") != 0) { + snprintf(buf, sizeof(buf), "set %s", d->name); + add_atom(&s->atom_list, buf); + } + } + break; + default: + break; + } + } +} + +static int usage(const char *name) +{ + fprintf(stderr, "usage: %s {-m32 | -m64} [-a]\n", name); + fprintf(stderr, + " create a ROM file for the mquickjs standard library\n" + "--help list options\n" + "-m32 force generation for a 32 bit target\n" + "-m64 force generation for a 64 bit target\n" + "-a generate the mquickjs_atom.h header\n" + ); + return 1; +} + +int build_atoms(const char *stdlib_name, const JSPropDef *global_obj, + const JSPropDef *c_function_decl, int argc, char **argv) +{ + int i; + unsigned jsw; + BuildContext ss, *s = &ss; + BOOL build_atom_defines = FALSE; + +#if INTPTR_MAX >= INT64_MAX + jsw = 8; +#else + jsw = 4; +#endif + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-m64")) { + jsw = 8; + } else if (!strcmp(argv[i], "-m32")) { + jsw = 4; + } else if (!strcmp(argv[i], "-a")) { + build_atom_defines = TRUE; + } else if (!strcmp(argv[i], "--help")) { + return usage(argv[0]); + } else { + fprintf(stderr, "invalid argument '%s'\n", argv[i]); + return usage(argv[0]); + } + } + + JSW = jsw; + + if (build_atom_defines) { + dump_atom_defines(); + return 0; + } + + memset(s, 0, sizeof(*s)); + init_list_head(&s->class_list); + + /* add the predefined atoms (they have a corresponding define) */ + for(i = 0; i < countof(atoms); i++) { + add_atom(&s->atom_list, atoms[i]); + } + + /* add the predefined functions */ + if (c_function_decl) { + const JSPropDef *d; + for(d = c_function_decl; d->def_type != JS_DEF_END; d++) { + if (d->def_type != JS_DEF_CFUNC) { + fprintf(stderr, "only C functions are allowed in c_function_decl[]\n"); + exit(1); + } + add_atom(&s->atom_list, d->name); + add_cfunc(&s->cfunc_list, + d->name, + d->u.func.length, + d->u.func.magic, + d->u.func.cproto_name, + d->u.func.func_name); + } + } + + /* first pass to define the atoms */ + define_atoms_props(s, global_obj, PROPS_KIND_GLOBAL); + free_class_entries(s); + + printf("/* this file is automatically generated - do not edit */\n\n"); + printf("#include \"mquickjs_priv.h\"\n\n"); + + printf("static const uint%u_t __attribute((aligned(%d))) js_stdlib_table[] = {\n", + JSW * 8, ATOM_ALIGN); + + dump_atoms(s); + + s->global_object_offset = define_props(s, global_obj, PROPS_KIND_GLOBAL, NULL); + + printf("};\n\n"); + + dump_cfuncs(s); + + printf("#ifndef JS_CLASS_COUNT\n" + "#define JS_CLASS_COUNT JS_CLASS_USER /* total number of classes */\n" + "#endif\n\n"); + + dump_cfinalizers(s); + + free_class_entries(s); + + printf("const JSSTDLibraryDef %s = {\n", stdlib_name); + printf(" js_stdlib_table,\n"); + printf(" js_c_function_table,\n"); + printf(" js_c_finalizer_table,\n"); + printf(" %d,\n", s->cur_offset); + printf(" %d,\n", ATOM_ALIGN); + printf(" %d,\n", s->sorted_atom_table_offset); + printf(" %d,\n", s->global_object_offset); + printf(" JS_CLASS_COUNT,\n"); + printf("};\n\n"); + + return 0; +} diff --git a/mquickjs_build.h b/mquickjs_build.h new file mode 100644 index 00000000..51be6b44 --- /dev/null +++ b/mquickjs_build.h @@ -0,0 +1,97 @@ +/* + * Micro QuickJS build utility + * + * 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 MQUICKJS_BUILD_H +#define MQUICKJS_BUILD_H + +#include +#include + +enum { + JS_DEF_END, + JS_DEF_CFUNC, + JS_DEF_CGETSET, + JS_DEF_PROP_DOUBLE, + JS_DEF_PROP_UNDEFINED, + JS_DEF_PROP_STRING, + JS_DEF_PROP_NULL, + JS_DEF_CLASS, +}; + +typedef struct JSClassDef JSClassDef; + +typedef struct JSPropDef { + int def_type; + const char *name; + union { + struct { + uint8_t length; + const char *magic; + const char *cproto_name; + const char *func_name; + } func; + struct { + const char *magic; + const char *cproto_name; + const char *get_func_name; + const char *set_func_name; + } getset; + double f64; + const JSClassDef *class1; + const char *str; + } u; +} JSPropDef; + +typedef struct JSClassDef { + const char *name; + int length; + const char *cproto_name; + const char *func_name; + const char *class_id; + const JSPropDef *class_props; /* NULL if none */ + const JSPropDef *proto_props; /* NULL if none */ + const JSClassDef *parent_class; /* NULL if none */ + const char *finalizer_name; /* "NULL" if none */ +} JSClassDef; + +#define JS_PROP_END { JS_DEF_END } +#define JS_CFUNC_DEF(name, length, func_name) { JS_DEF_CFUNC, name, { .func = { length, "0", "generic", #func_name } } } +#define JS_CFUNC_MAGIC_DEF(name, length, func_name, magic) { JS_DEF_CFUNC, name, { .func = { length, #magic, "generic_magic", #func_name } } } +#define JS_CFUNC_SPECIAL_DEF(name, length, proto, func_name) { JS_DEF_CFUNC, name, { .func = { length, "0", #proto, #func_name } } } +#define JS_CGETSET_DEF(name, get_name, set_name) { JS_DEF_CGETSET, name, { .getset = { "0", "generic", #get_name, #set_name } } } +#define JS_CGETSET_MAGIC_DEF(name, get_name, set_name, magic) { JS_DEF_CGETSET, name, { .getset = { #magic, "generic_magic", #get_name, #set_name } } } +#define JS_PROP_CLASS_DEF(name, cl) { JS_DEF_CLASS, name, { .class1 = cl } } +#define JS_PROP_DOUBLE_DEF(name, val, flags) { JS_DEF_PROP_DOUBLE, name, { .f64 = val } } +#define JS_PROP_UNDEFINED_DEF(name, flags) { JS_DEF_PROP_UNDEFINED, name } +#define JS_PROP_NULL_DEF(name, flags) { JS_DEF_PROP_NULL, name } +#define JS_PROP_STRING_DEF(name, cstr, flags) { JS_DEF_PROP_STRING, name, { .str = cstr } } + +#define JS_CLASS_DEF(name, length, func_name, class_id, class_props, proto_props, parent_class, finalizer_name) { name, length, "constructor", #func_name, #class_id, class_props, proto_props, parent_class, #finalizer_name } +#define JS_CLASS_MAGIC_DEF(name, length, func_name, class_id, class_props, proto_props, parent_class, finalizer_name) { name, length, "constructor_magic", #func_name, #class_id, class_props, proto_props, parent_class, #finalizer_name } +#define JS_OBJECT_DEF(name, obj_props) { name, 0, NULL, NULL, NULL, obj_props, NULL, NULL, NULL } + +int build_atoms(const char *stdlib_name, const JSPropDef *global_obj, + const JSPropDef *c_function_decl, int argc, char **argv); + +#endif /* MQUICKJS_BUILD_H */ diff --git a/mquickjs_opcode.h b/mquickjs_opcode.h new file mode 100644 index 00000000..2ccd6050 --- /dev/null +++ b/mquickjs_opcode.h @@ -0,0 +1,264 @@ +/* + * Micro QuickJS opcode definitions + * + * 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. + */ +#ifdef FMT +FMT(none) +FMT(none_int) +FMT(none_loc) +FMT(none_arg) +FMT(none_var_ref) +FMT(u8) +FMT(i8) +FMT(loc8) +FMT(const8) +FMT(label8) +FMT(u16) +FMT(i16) +FMT(label16) +FMT(npop) +FMT(npopx) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(i32) +FMT(const16) +FMT(label) +FMT(value) +#undef FMT +#endif /* FMT */ + +#ifdef DEF + +#ifndef def +#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f) +#endif + +DEF(invalid, 1, 0, 0, none) /* never emitted */ + +/* push values */ +DEF( push_value, 5, 0, 1, value) +DEF( push_const, 3, 0, 1, const16) +DEF( fclosure, 3, 0, 1, const16) +DEF( undefined, 1, 0, 1, none) +DEF( null, 1, 0, 1, none) +DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */ +DEF( push_false, 1, 0, 1, none) +DEF( push_true, 1, 0, 1, none) +DEF( object, 3, 0, 1, u16) +DEF( this_func, 1, 0, 1, none) +DEF( arguments, 1, 0, 1, none) +DEF( new_target, 1, 0, 1, none) + +DEF( drop, 1, 1, 0, none) /* a -> */ +DEF( nip, 1, 2, 1, none) /* a b -> b */ +//DEF( nip1, 1, 3, 2, none) /* a b c -> b c */ +DEF( dup, 1, 1, 2, none) /* a -> a a */ +DEF( dup1, 1, 2, 3, none) /* a b -> a a b */ +DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */ +//DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */ +DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */ +DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */ +//DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */ +DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */ +DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */ +//DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */ +DEF( swap, 1, 2, 2, none) /* a b -> b a */ +//DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */ +DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */ +//DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */ +//DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */ +//DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */ + +DEF(call_constructor, 3, 1, 1, npop) /* func args... -> ret (arguments are not counted in n_pop) */ +DEF( call, 3, 1, 1, npop) /* func args... -> ret (arguments are not counted in n_pop) */ +DEF( call_method, 3, 2, 1, npop) /* this func args.. -> ret (arguments are not counted in n_pop) */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a bytecode string */ + +DEF( get_field, 3, 1, 1, const16) /* obj -> val */ +DEF( get_field2, 3, 1, 2, const16) /* obj -> obj val */ +DEF( put_field, 3, 2, 0, const16) /* obj val -> */ +DEF( get_array_el, 1, 2, 1, none) /* obj prop -> val */ +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( put_array_el, 1, 3, 0, none) /* obj prop val -> */ +DEF( get_length, 1, 1, 1, none) /* obj -> val */ +DEF( get_length2, 1, 1, 2, none) /* obj -> obj val */ +DEF( define_field, 3, 2, 1, const16) /* obj val -> obj */ +DEF( define_getter, 3, 2, 1, const16) /* obj val -> obj */ +DEF( define_setter, 3, 2, 1, const16) /* obj val -> obj */ +DEF( set_proto, 1, 2, 1, none) /* obj proto -> obj */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( get_var_ref, 3, 0, 1, var_ref) +DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */ +DEF(get_var_ref_nocheck, 3, 0, 1, var_ref) +DEF(put_var_ref_nocheck, 3, 1, 0, var_ref) +DEF( if_false, 5, 1, 0, label) +DEF( if_true, 5, 1, 0, label) /* must come after if_false */ +DEF( goto, 5, 0, 0, label) /* must come after if_true */ +DEF( catch, 5, 0, 1, label) +DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */ +DEF( ret, 1, 1, 0, none) /* used to return from the finally block */ + +DEF( for_in_start, 1, 1, 1, none) /* obj -> iter */ +DEF( for_of_start, 1, 1, 1, none) /* obj -> iter */ +DEF( for_of_next, 1, 1, 3, none) /* iter -> iter val done */ + +/* arithmetic/logic operations */ +DEF( neg, 1, 1, 1, none) +DEF( plus, 1, 1, 1, none) +DEF( dec, 1, 1, 1, none) +DEF( inc, 1, 1, 1, none) +DEF( post_dec, 1, 1, 2, none) +DEF( post_inc, 1, 1, 2, none) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) /* obj prop -> ret */ + +DEF( mul, 1, 2, 1, none) +DEF( div, 1, 2, 1, none) +DEF( mod, 1, 2, 1, none) +DEF( add, 1, 2, 1, none) +DEF( sub, 1, 2, 1, none) +DEF( pow, 1, 2, 1, none) +DEF( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( lt, 1, 2, 1, none) +DEF( lte, 1, 2, 1, none) +DEF( gt, 1, 2, 1, none) +DEF( gte, 1, 2, 1, none) +DEF( instanceof, 1, 2, 1, none) +DEF( in, 1, 2, 1, none) +DEF( eq, 1, 2, 1, none) +DEF( neq, 1, 2, 1, none) +DEF( strict_eq, 1, 2, 1, none) +DEF( strict_neq, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +DEF( push_minus1, 1, 0, 1, none_int) +DEF( push_0, 1, 0, 1, none_int) +DEF( push_1, 1, 0, 1, none_int) +DEF( push_2, 1, 0, 1, none_int) +DEF( push_3, 1, 0, 1, none_int) +DEF( push_4, 1, 0, 1, none_int) +DEF( push_5, 1, 0, 1, none_int) +DEF( push_6, 1, 0, 1, none_int) +DEF( push_7, 1, 0, 1, none_int) +DEF( push_i8, 2, 0, 1, i8) +DEF( push_i16, 3, 0, 1, i16) +DEF( push_const8, 2, 0, 1, const8) +DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */ +DEF(push_empty_string, 1, 0, 1, none) + +DEF( get_loc8, 2, 0, 1, loc8) +DEF( put_loc8, 2, 1, 0, loc8) /* must follow get_loc8 */ + +DEF( get_loc0, 1, 0, 1, none_loc) +DEF( get_loc1, 1, 0, 1, none_loc) +DEF( get_loc2, 1, 0, 1, none_loc) +DEF( get_loc3, 1, 0, 1, none_loc) +DEF( put_loc0, 1, 1, 0, none_loc) /* must follow get_loc */ +DEF( put_loc1, 1, 1, 0, none_loc) +DEF( put_loc2, 1, 1, 0, none_loc) +DEF( put_loc3, 1, 1, 0, none_loc) +DEF( get_arg0, 1, 0, 1, none_arg) +DEF( get_arg1, 1, 0, 1, none_arg) +DEF( get_arg2, 1, 0, 1, none_arg) +DEF( get_arg3, 1, 0, 1, none_arg) +DEF( put_arg0, 1, 1, 0, none_arg) /* must follow get_arg */ +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +#if 0 +DEF( if_false8, 2, 1, 0, label8) +DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */ +DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */ +DEF( goto16, 3, 0, 0, label16) + +DEF( call0, 1, 1, 1, npopx) +DEF( call1, 1, 1, 1, npopx) +DEF( call2, 1, 1, 1, npopx) +DEF( call3, 1, 1, 1, npopx) +#endif + +#undef DEF +#undef def +#endif /* DEF */ + +#ifdef REDEF + +/* regular expression bytecode */ +REDEF(invalid, 1) /* never used */ +REDEF(char1, 2) +REDEF(char2, 3) +REDEF(char3, 4) +REDEF(char4, 5) +REDEF(dot, 1) +REDEF(any, 1) /* same as dot but match any character including line terminator */ +REDEF(space, 1) +REDEF(not_space, 1) /* must come after */ +REDEF(line_start, 1) +REDEF(line_start_m, 1) +REDEF(line_end, 1) +REDEF(line_end_m, 1) +REDEF(goto, 5) +REDEF(split_goto_first, 5) +REDEF(split_next_first, 5) +REDEF(match, 1) +REDEF(lookahead_match, 1) +REDEF(negative_lookahead_match, 1) /* must come after */ +REDEF(save_start, 2) /* save start position */ +REDEF(save_end, 2) /* save end position, must come after saved_start */ +REDEF(save_reset, 3) /* reset save positions */ +REDEF(loop, 6) /* decrement the top the stack and goto if != 0 */ +REDEF(loop_split_goto_first, 10) /* loop and then split */ +REDEF(loop_split_next_first, 10) +REDEF(loop_check_adv_split_goto_first, 10) /* loop and then check advance and split */ +REDEF(loop_check_adv_split_next_first, 10) +REDEF(set_i32, 6) /* store the immediate value to a register */ +REDEF(word_boundary, 1) +REDEF(not_word_boundary, 1) +REDEF(back_reference, 2) +REDEF(back_reference_i, 2) +REDEF(range8, 2) /* variable length */ +REDEF(range, 3) /* variable length */ +REDEF(lookahead, 5) +REDEF(negative_lookahead, 5) /* must come after */ +REDEF(set_char_pos, 2) /* store the character position to a register */ +REDEF(check_advance, 2) /* check that the register is different from the character position */ + +#endif /* REDEF */ diff --git a/mquickjs_priv.h b/mquickjs_priv.h new file mode 100644 index 00000000..37bc1535 --- /dev/null +++ b/mquickjs_priv.h @@ -0,0 +1,268 @@ +/* microj private definitions */ +#ifndef MICROJS_PRIV_H +#define MICROJS_PRIV_H + +#include "mquickjs.h" +#include "libm.h" + +#define JS_DUMP /* 2.6 kB */ +//#define DUMP_EXEC +//#define DUMP_FUNC_BYTECODE /* dump the bytecode of each compiled function */ +//#define DUMP_REOP /* dump regexp bytecode */ +//#define DUMP_GC +//#define DUMP_TOKEN /* dump parsed tokens */ +/* run GC before at each malloc() and modify the allocated data pointers */ +//#define DEBUG_GC +#if defined(DUMP_FUNC_BYTECODE) || defined(DUMP_EXEC) +#define DUMP_BYTECODE /* include the dump_byte_code() function */ +#endif + +#define JS_VALUE_TO_PTR(v) (void *)((uintptr_t)(v) - 1) +#define JS_VALUE_FROM_PTR(ptr) (JSWord)((uintptr_t)(ptr) + 1) + +#define JS_IS_ROM_PTR(ctx, ptr) ((uintptr_t)(ptr) < (uintptr_t)ctx || (uintptr_t)(ptr) >= (uintptr_t)ctx->stack_top) + +enum { + JS_MTAG_FREE, + /* javascript values */ + JS_MTAG_OBJECT, + JS_MTAG_FLOAT64, + JS_MTAG_STRING, + /* other special memory blocks */ + JS_MTAG_FUNCTION_BYTECODE, + JS_MTAG_VALUE_ARRAY, + JS_MTAG_BYTE_ARRAY, + JS_MTAG_VARREF, + + JS_MTAG_COUNT, +}; + +/* JS_MTAG_BITS bits are reserved at the start of every memory block */ +#define JS_MTAG_BITS 4 + +#define JS_MB_HEADER \ + JSWord gc_mark: 1; \ + JSWord mtag: (JS_MTAG_BITS - 1) + +typedef enum { + JS_PROP_NORMAL, + JS_PROP_GETSET, /* value is a two element JSValueArray */ + JS_PROP_VARREF, /* value is a JSVarRef (used for global variables) */ + JS_PROP_SPECIAL, /* for the prototype and constructor properties in ROM */ +} JSPropTypeEnum; + +#define JS_MB_HEADER_DEF(tag) ((tag) << 1) +#define JS_VALUE_ARRAY_HEADER(size) (JS_MB_HEADER_DEF(JS_MTAG_VALUE_ARRAY) | ((size) << JS_MTAG_BITS)) + +#define JS_ROM_VALUE(offset) JS_VALUE_FROM_PTR(&js_stdlib_table[offset]) + +/* runtime helpers */ +JSValue js_function_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_get_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_set_prototype(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_get_length_name(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_name); +JSValue js_function_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_call(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_apply(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_bind(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_function_bound(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, JSValue params); + +JSValue js_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_number_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toFixed(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toExponential(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_toPrecision(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_parseInt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_number_parseFloat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_boolean_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_string_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_set_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_substring(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +enum { + magic_internalAt, + magic_charAt, + magic_charCodeAt, + magic_codePointAt, +}; +JSValue js_string_charAt(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_string_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int lastIndexOf); +JSValue js_string_match(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_replace(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_replaceAll); +JSValue js_string_search(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_split(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_toLowerCase(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int to_lower); +JSValue js_string_trim(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_string_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_repeat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_object_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_defineProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_create(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_keys(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_object_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_string_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_string_fromCharCode(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_fromCodePoint); + +JSValue js_error_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_error_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_error_get_message(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); + +JSValue js_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_push(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_unshift); +JSValue js_array_pop(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_shift(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_join(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_toString(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_isArray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_reverse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_concat(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_indexOf(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_lastIndexOf); +JSValue js_array_slice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_splice(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_sort(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +#define js_special_every 0 +#define js_special_some 1 +#define js_special_forEach 2 +#define js_special_map 3 +#define js_special_filter 4 + +JSValue js_array_every(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special); + +#define js_special_reduce 0 +#define js_special_reduceRight 1 + +JSValue js_array_reduce(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int special); + +JSValue js_math_min_max(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +double js_math_sign(double a); +double js_math_fround(double a); +JSValue js_math_imul(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_clz32(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_atan2(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_pow(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_math_random(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_array_buffer_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_array_buffer_get_byteLength(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_typed_array_base_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_typed_array_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_typed_array_get_length(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int magic); +JSValue js_typed_array_subarray(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_typed_array_set(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_date_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_global_eval(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_global_isNaN(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_global_isFinite(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_json_parse(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_json_stringify(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); + +JSValue js_regexp_constructor(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_get_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_set_lastIndex(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_get_source(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_get_flags(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv); +JSValue js_regexp_exec(JSContext *ctx, JSValue *this_val, + int argc, JSValue *argv, int is_test); + +#endif /* MICROJS_PRIV_H */ diff --git a/readline.c b/readline.c new file mode 100644 index 00000000..e5265891 --- /dev/null +++ b/readline.c @@ -0,0 +1,742 @@ +/* + * readline utility + * + * Copyright (c) 2003-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 "cutils.h" +#include "readline.h" + +#define IS_NORM 0 +#define IS_ESC 1 +#define IS_CSI 2 + +static void term_show_prompt2(ReadlineState *s) +{ + term_printf("%s", s->term_prompt); + term_flush(); + /* XXX: assuming no unicode chars */ + s->term_cursor_x = strlen(s->term_prompt) % s->term_width; + s->term_cursor_pos = 0; + s->term_esc_state = IS_NORM; + s->utf8_state = 0; +} + +static void term_show_prompt(ReadlineState *s) +{ + term_show_prompt2(s); + s->term_cmd_buf_index = 0; + s->term_cmd_buf_len = 0; +} + +static void print_csi(int n, int code) +{ + if (n == 1) { + term_printf("\033[%c", code); + } else { + term_printf("\033[%d%c", n, code); + } +} + +const char *term_colors[17] = { + "\033[0m", + "\033[30m", + "\033[31m", + "\033[32m", + "\033[33m", + "\033[34m", + "\033[35m", + "\033[36m", + "\033[37m", + "\033[30;1m", + "\033[31;1m", + "\033[32;1m", + "\033[33;1m", + "\033[34;1m", + "\033[35;1m", + "\033[36;1m", + "\033[37;1m", +}; + +static void print_color(int c) +{ + term_printf("%s", term_colors[c]); +} + +static void move_cursor(ReadlineState *s, int delta) +{ + int l; + if (delta > 0) { + while (delta != 0) { + if (s->term_cursor_x == (s->term_width - 1)) { + term_printf("\r\n"); /* translated to CRLF */ + s->term_cursor_x = 0; + delta--; + } else { + l = min_int(s->term_width - 1 - s->term_cursor_x, delta); + print_csi(l, 'C'); /* right */ + delta -= l; + s->term_cursor_x += l; + } + } + } else if (delta < 0) { + delta = -delta; + while (delta != 0) { + if (s->term_cursor_x == 0) { + print_csi(1, 'A'); /* up */ + print_csi(s->term_width - 1, 'C'); /* right */ + delta--; + s->term_cursor_x = s->term_width - 1; + } else { + l = min_int(delta, s->term_cursor_x); + print_csi(l, 'D'); /* left */ + delta -= l; + s->term_cursor_x -= l; + } + } + } +} + +static int char_width(int c) +{ + /* XXX: complete or find a way to use wcwidth() */ + if (c < 0x100) { + return 1; + } else if ((c >= 0x4E00 && c <= 0x9FFF) || /* CJK */ + (c >= 0xFF01 && c <= 0xFF5E) || /* fullwidth ASCII */ + (c >= 0x1F600 && c <= 0x1F64F)) { /* emoji */ + return 2; + } else { + return 1; + } +} + +/* update the displayed command line */ +static void term_update(ReadlineState *s) +{ + int i, len, c, new_cursor_pos, last_color, color_len; + uint8_t buf[UTF8_CHAR_LEN_MAX + 1]; + size_t c_len; + + new_cursor_pos = 0; + if (s->term_cmd_updated) { + move_cursor(s, -s->term_cursor_pos); + s->term_cursor_pos = 0; + last_color = COLOR_NONE; + color_len = 0; + s->term_cmd_buf[s->term_cmd_buf_len] = '\0'; /* add a trailing '\0' to ease colorization */ + for(i = 0; i < s->term_cmd_buf_len; i += c_len) { + if (i == s->term_cmd_buf_index) + new_cursor_pos = s->term_cursor_pos; + c = utf8_get(s->term_cmd_buf + i, &c_len); + if (s->term_is_password) { + len = 1; + buf[0] = '*'; + buf[1] = '\0'; + } else { + len = char_width(c); + memcpy(buf, s->term_cmd_buf + i, c_len); + buf[c_len] = '\0'; + } + /* the wide char does not fit so we display it on the next + line by enlarging the previous char */ + if (s->term_cursor_x + len > s->term_width && i > 0) { + while (s->term_cursor_x < s->term_width) { + term_printf(" "); + s->term_cursor_x++; + s->term_cursor_pos++; + } + s->term_cursor_x = 0; + } + s->term_cursor_pos += len; + s->term_cursor_x += len; + if (s->term_cursor_x >= s->term_width) + s->term_cursor_x = 0; + if (!s->term_is_password && s->get_color) { + if (color_len == 0) { + int new_color = s->get_color(&color_len, (const char *)s->term_cmd_buf, i, s->term_cmd_buf_len); + if (new_color != last_color) { + last_color = new_color; + print_color(COLOR_NONE); /* reset last color */ + print_color(last_color); + } + } + color_len--; + } + term_printf("%s", buf); + } + if (last_color != COLOR_NONE) + print_color(COLOR_NONE); + if (i == s->term_cmd_buf_index) + new_cursor_pos = s->term_cursor_pos; + if (s->term_cursor_x == 0) { + /* show the cursor on the next line */ + term_printf(" \x08"); + } + /* remove the trailing characters */ + print_csi(1, 'J'); + s->term_cmd_updated = FALSE; + } else { + int cursor_x; + /* compute the new cursor pos without display */ + cursor_x = (s->term_cursor_x - s->term_cursor_pos) % s->term_width; + if (cursor_x < 0) + cursor_x += s->term_width; + new_cursor_pos = 0; + for(i = 0; i < s->term_cmd_buf_index; i += c_len) { + c = utf8_get(s->term_cmd_buf + i, &c_len); + if (s->term_is_password) + c = '*'; + len = char_width(c); + /* the wide char does not fit so we display it on the next + line by enlarging the previous char */ + if (cursor_x + len > s->term_width && i > 0) { + new_cursor_pos += s->term_width - cursor_x; + cursor_x = 0; + } + new_cursor_pos += len; + cursor_x += len; + if (cursor_x >= s->term_width) + cursor_x = 0; + } + } + move_cursor(s, new_cursor_pos - s->term_cursor_pos); + s->term_cursor_pos = new_cursor_pos; + term_flush(); +} + +static void term_kill_region(ReadlineState *s, int to, int kill) +{ + int start = s->term_cmd_buf_index; + int end = s->term_cmd_buf_index; + if (to < start) + start = to; + else + end = to; + if (end > s->term_cmd_buf_len) + end = s->term_cmd_buf_len; + if (start < end) { + int len = end - start; + if (kill) { + memcpy(s->term_kill_buf, s->term_cmd_buf + start, + len * sizeof(s->term_cmd_buf[0])); + s->term_kill_buf_len = len; + } + memmove(s->term_cmd_buf + start, s->term_cmd_buf + end, + (s->term_cmd_buf_len - end) * sizeof(s->term_cmd_buf[0])); + s->term_cmd_buf_len -= len; + s->term_cmd_buf_index = start; + s->term_cmd_updated = TRUE; + } +} + +static void term_insert_region(ReadlineState *s, const uint8_t *p, int len) +{ + int pos = s->term_cmd_buf_index; + + if (pos + len < s->term_cmd_buf_size) { + int nchars = s->term_cmd_buf_len - pos; + if (nchars > 0) { + memmove(s->term_cmd_buf + pos + len, + s->term_cmd_buf + pos, + nchars * sizeof(s->term_cmd_buf[0])); + } + memcpy(s->term_cmd_buf + pos, p, len * sizeof(s->term_cmd_buf[0])); + s->term_cmd_buf_len += len; + s->term_cmd_buf_index += len; + s->term_cmd_updated = TRUE; + } +} + +static void term_insert_char(ReadlineState *s, int ch) +{ + uint8_t buf[UTF8_CHAR_LEN_MAX + 1]; + term_insert_region(s, buf, unicode_to_utf8(buf, ch)); +} + +static BOOL is_utf8_ext(int c) +{ + return (c >= 0x80 && c < 0xc0); +} + +static void term_backward_char(ReadlineState *s) +{ + if (s->term_cmd_buf_index > 0) { + s->term_cmd_buf_index--; + while (s->term_cmd_buf_index > 0 && + is_utf8_ext(s->term_cmd_buf[s->term_cmd_buf_index])) { + s->term_cmd_buf_index--; + } + } +} + +static void term_forward_char(ReadlineState *s) +{ + size_t c_len; + if (s->term_cmd_buf_index < s->term_cmd_buf_len) { + utf8_get(s->term_cmd_buf + s->term_cmd_buf_index, &c_len); + s->term_cmd_buf_index += c_len; + } +} + +static void term_delete_char(ReadlineState *s) +{ + size_t c_len; + if (s->term_cmd_buf_index < s->term_cmd_buf_len) { + utf8_get(s->term_cmd_buf + s->term_cmd_buf_index, &c_len); + term_kill_region(s, s->term_cmd_buf_index + c_len, 0); + } +} + +static void term_backspace(ReadlineState *s) +{ + if (s->term_cmd_buf_index > 0) { + term_backward_char(s); + term_delete_char(s); + } +} + +static int skip_word_backward(ReadlineState *s) +{ + int pos = s->term_cmd_buf_index; + + /* skip whitespace backwards */ + while (pos > 0 && isspace(s->term_cmd_buf[pos - 1])) + --pos; + + /* skip word backwards */ + while (pos > 0 && !isspace(s->term_cmd_buf[pos - 1])) + --pos; + + return pos; +} + +static int skip_word_forward(ReadlineState *s) +{ + int pos = s->term_cmd_buf_index; + + /* skip whitespace */ + while (pos < s->term_cmd_buf_len && isspace(s->term_cmd_buf[pos])) + pos++; + + /* skip word */ + while (pos < s->term_cmd_buf_len && !isspace(s->term_cmd_buf[pos])) + pos++; + + return pos; +} + +static void term_skip_word_backward(ReadlineState *s) +{ + s->term_cmd_buf_index = skip_word_backward(s); +} + +static void term_skip_word_forward(ReadlineState *s) +{ + s->term_cmd_buf_index = skip_word_forward(s); +} + +static void term_yank(ReadlineState *s) +{ + term_insert_region(s, s->term_kill_buf, s->term_kill_buf_len); +} + +static void term_kill_word(ReadlineState *s) +{ + term_kill_region(s, skip_word_forward(s), 1); +} + +static void term_kill_word_backward(ReadlineState *s) +{ + term_kill_region(s, skip_word_backward(s), 1); +} + +static void term_bol(ReadlineState *s) +{ + s->term_cmd_buf_index = 0; +} + +static void term_eol(ReadlineState *s) +{ + s->term_cmd_buf_index = s->term_cmd_buf_len; +} + +static void update_cmdline_from_history(ReadlineState *s) +{ + int hist_entry_size; + hist_entry_size = strlen(s->term_history + s->term_hist_entry); + memcpy(s->term_cmd_buf, s->term_history + s->term_hist_entry, hist_entry_size); + s->term_cmd_buf_len = hist_entry_size; + s->term_cmd_buf_index = s->term_cmd_buf_len; + s->term_cmd_updated = TRUE; +} + +static void term_up_char(ReadlineState *s) +{ + int idx; + if (s->term_hist_entry == -1) { + s->term_hist_entry = s->term_history_size; + // XXX: should save current contents to history + } + if (s->term_hist_entry == 0) + return; + /* move to previous entry */ + idx = s->term_hist_entry - 1; + while (idx > 0 && s->term_history[idx - 1] != '\0') + idx--; + s->term_hist_entry = idx; + update_cmdline_from_history(s); +} + +static void term_down_char(ReadlineState *s) +{ + int hist_entry_size; + if (s->term_hist_entry == -1) + return; + hist_entry_size = strlen(s->term_history + s->term_hist_entry) + 1; + if (s->term_hist_entry + hist_entry_size < s->term_history_size) { + s->term_hist_entry += hist_entry_size; + update_cmdline_from_history(s); + } else { + s->term_hist_entry = -1; + s->term_cmd_buf_index = s->term_cmd_buf_len; + } +} + +static void term_hist_add(ReadlineState *s, const char *cmdline) +{ + char *hist_entry; + int idx, cmdline_size, hist_entry_size; + + if (cmdline[0] == '\0') + return; + cmdline_size = strlen(cmdline) + 1; + if (s->term_hist_entry != -1) { + /* We were editing an existing history entry: replace it */ + idx = s->term_hist_entry; + hist_entry = s->term_history + idx; + hist_entry_size = strlen(hist_entry) + 1; + if (hist_entry_size == cmdline_size && !memcmp(hist_entry, cmdline, cmdline_size)) { + goto same_entry; + } + } + /* Search cmdline in the history */ + for (idx = 0; idx < s->term_history_size; idx += hist_entry_size) { + hist_entry = s->term_history + idx; + hist_entry_size = strlen(hist_entry) + 1; + if (hist_entry_size == cmdline_size && !memcmp(hist_entry, cmdline, cmdline_size)) { + same_entry: + /* remove the identical entry */ + memmove(s->term_history + idx, s->term_history + idx + hist_entry_size, + s->term_history_size - (idx + hist_entry_size)); + s->term_history_size -= hist_entry_size; + break; + } + } + + if (cmdline_size <= s->term_history_buf_size) { + /* remove history entries if not enough space */ + while (s->term_history_size + cmdline_size > s->term_history_buf_size) { + hist_entry_size = strlen(s->term_history) + 1; + memmove(s->term_history, s->term_history + hist_entry_size, + s->term_history_size - hist_entry_size); + s->term_history_size -= hist_entry_size; + } + + /* add the cmdline */ + memcpy(s->term_history + s->term_history_size, cmdline, cmdline_size); + s->term_history_size += cmdline_size; + } + s->term_hist_entry = -1; +} + +/* completion support */ + +#if 0 +void add_completion(const char *str) +{ + if (nb_completions < NB_COMPLETIONS_MAX) { + completions[nb_completions++] = qemu_strdup(str); + } +} + +static void term_completion(ReadlineState *s) +{ + int len, i, j, max_width, nb_cols, max_prefix; + char *cmdline; + + nb_completions = 0; + + cmdline = qemu_malloc(term_cmd_buf_index + 1); + if (!cmdline) + return; + memcpy(cmdline, term_cmd_buf, term_cmd_buf_index); + cmdline[term_cmd_buf_index] = '\0'; + readline_find_completion(cmdline); + qemu_free(cmdline); + + /* no completion found */ + if (nb_completions <= 0) + return; + if (nb_completions == 1) { + len = strlen(completions[0]); + for(i = completion_index; i < len; i++) { + term_insert_char(completions[0][i]); + } + /* extra space for next argument. XXX: make it more generic */ + if (len > 0 && completions[0][len - 1] != '/') + term_insert_char(' '); + } else { + term_printf("\n"); + max_width = 0; + max_prefix = 0; + for(i = 0; i < nb_completions; i++) { + len = strlen(completions[i]); + if (i==0) { + max_prefix = len; + } else { + if (len < max_prefix) + max_prefix = len; + for(j=0; j max_width) + max_width = len; + } + if (max_prefix > 0) { + for(i = completion_index; i < max_prefix; i++) { + term_insert_char(completions[0][i]); + } + } + max_width += 2; + if (max_width < 10) + max_width = 10; + else if (max_width > 80) + max_width = 80; + nb_cols = 80 / max_width; + j = 0; + for(i = 0; i < nb_completions; i++) { + term_printf("%-*s", max_width, completions[i]); + if (++j == nb_cols || i == (nb_completions - 1)) { + term_printf("\n"); + j = 0; + } + } + term_show_prompt2(); + } +} +#endif + +static void term_return(ReadlineState *s) +{ + s->term_cmd_buf[s->term_cmd_buf_len] = '\0'; + if (!s->term_is_password) + term_hist_add(s, (const char *)s->term_cmd_buf); + s->term_cmd_buf_index = s->term_cmd_buf_len; +} + +static int readline_handle_char(ReadlineState *s, int ch) +{ + int ret = READLINE_RET_HANDLED; + + switch(s->term_esc_state) { + case IS_NORM: + switch(ch) { + case 1: /* ^A */ + term_bol(s); + break; + case 4: /* ^D */ + if (s->term_cmd_buf_len == 0) { + term_printf("^D\n"); + return READLINE_RET_EXIT; + } + term_delete_char(s); + break; + case 5: /* ^E */ + term_eol(s); + break; + case 9: /* TAB */ + //term_completion(s); + break; + case 10: + case 13: + term_return(s); + ret = READLINE_RET_ACCEPTED; + break; + case 11: /* ^K */ + term_kill_region(s, s->term_cmd_buf_len, 1); + break; + case 21: /* ^U */ + term_kill_region(s, 0, 1); + break; + case 23: /* ^W */ + term_kill_word_backward(s); + break; + case 25: /* ^Y */ + term_yank(s); + break; + case 27: + s->term_esc_state = IS_ESC; + break; + case 127: /* DEL */ + case 8: /* ^H */ + term_backspace(s); + break; + case 155: /* 0x9B */ + s->term_esc_state = IS_CSI; + break; + default: + if (ch >= 32) { + term_insert_char(s, ch); + break; + } + return 0; + } + break; + case IS_ESC: + s->term_esc_state = IS_NORM; + switch (ch) { + case '[': + case 'O': + s->term_esc_state = IS_CSI; + s->term_esc_param2 = 0; + s->term_esc_param1 = 0; + s->term_esc_param = 0; + break; + case 13: + /* ESC+RET or M-RET: validate in multi-line */ + term_return(s); + break; + case 8: + case 127: + term_kill_word_backward(s); + break; + case 'b': + term_skip_word_backward(s); + break; + case 'd': + term_kill_word(s); + break; + case 'f': + term_skip_word_forward(s); + break; + default: + return 0; + } + break; + case IS_CSI: + s->term_esc_state = IS_NORM; + switch(ch) { + case 'A': + term_up_char(s); + break; + case 'B': + case 'E': + term_down_char(s); + break; + case 'D': + term_backward_char(s); + break; + case 'C': + term_forward_char(s); + break; + case 'F': + term_eol(s); + break; + case 'H': + term_bol(s); + break; + case ';': + s->term_esc_param2 = s->term_esc_param1; + s->term_esc_param1 = s->term_esc_param; + s->term_esc_param = 0; + s->term_esc_state = IS_CSI; + break; + case '0' ... '9': + s->term_esc_param = s->term_esc_param * 10 + (ch - '0'); + s->term_esc_state = IS_CSI; + break; + case '~': + switch(s->term_esc_param) { + case 1: + term_bol(s); + break; + case 3: + term_delete_char(s); + break; + case 4: + term_eol(s); + break; + default: + return READLINE_RET_NOT_HANDLED; + } + break; + default: + return READLINE_RET_NOT_HANDLED; + } + break; + } + term_update(s); + if (ret == READLINE_RET_ACCEPTED) { + term_printf("\n"); + } + return ret; +} + +/* return > 0 if command handled, -1 if exit */ +/* XXX: could process buffers to avoid redisplaying at each char input + (copy paste case) */ +int readline_handle_byte(ReadlineState *s, int c) +{ + if (c >= 0xc0 && c < 0xf8) { + s->utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0); + s->utf8_val = c & ((1 << (6 - s->utf8_state)) - 1); + return READLINE_RET_HANDLED; + } + if (s->utf8_state != 0) { + if (c >= 0x80 && c < 0xc0) { + s->utf8_val = (s->utf8_val << 6) | (c & 0x3F); + s->utf8_state--; + if (s->utf8_state) + return READLINE_RET_HANDLED; + c = s->utf8_val; + } + s->utf8_state = 0; + } + return readline_handle_char(s, c); +} + +void readline_start(ReadlineState *s, const char *prompt, int is_password) +{ + s->term_prompt = prompt; + s->term_is_password = is_password; + s->term_hist_entry = -1; + s->term_cmd_buf_index = 0; + s->term_cmd_buf_len = 0; + term_show_prompt(s); +} diff --git a/readline.h b/readline.h new file mode 100644 index 00000000..639750ac --- /dev/null +++ b/readline.h @@ -0,0 +1,98 @@ +/* + * readline utility + * + * Copyright (c) 2003-2025 Fabrice Bellard + * + * 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 READLINE_H +#define READLINE_H + +#include "cutils.h" + +typedef struct ReadlineState ReadlineState; + +typedef void ReadLineFunc(void *opaque, const char *str); +typedef int ReadLineGetColor(int *plen, const char *buf, int pos, int buf_len); + +struct ReadlineState { + int term_cmd_buf_index; /* byte position in the command line */ + int term_cmd_buf_len; /* byte length of the command line */ + + uint32_t utf8_val; + uint8_t term_cmd_updated; /* if the command line was updated */ + uint8_t utf8_state; + uint8_t term_esc_state; + int term_esc_param; + int term_esc_param1; + int term_esc_param2; + int term_cursor_x; /* 0 <= term_cursor_x < term_width */ + int term_cursor_pos; /* linear position */ + + int term_hist_entry; /* position in term_history or -1 */ + int term_history_size; /* size of term_historyf */ + uint8_t term_is_password; + const char *term_prompt; + /* the following fields must be initialized by the user */ + int term_width; + int term_cmd_buf_size; + int term_kill_buf_len; + uint8_t *term_cmd_buf; /* allocated length is term_cmd_buf_size */ + uint8_t *term_kill_buf; /* allocated length is term_cmd_buf_size */ + int term_history_buf_size; + char *term_history; /* zero separated history entries */ + ReadLineGetColor *get_color; /* NULL if no colorization */ +}; + +#define COLOR_NONE 0 +#define COLOR_BLACK 1 +#define COLOR_RED 2 +#define COLOR_GREEN 3 +#define COLOR_YELLOW 4 +#define COLOR_BLUE 5 +#define COLOR_MAGENTA 6 +#define COLOR_CYAN 7 +#define COLOR_WHITE 8 +#define COLOR_GRAY 9 +#define COLOR_BRIGHT_RED 10 +#define COLOR_BRIGHT_GREEN 11 +#define COLOR_BRIGHT_YELLOW 12 +#define COLOR_BRIGHT_BLUE 13 +#define COLOR_BRIGHT_MAGENTA 14 +#define COLOR_BRIGHT_CYAN 15 +#define COLOR_BRIGHT_WHITE 16 + +extern const char *term_colors[17]; + +void add_completion(const char *str); + +#define READLINE_RET_EXIT (-1) +#define READLINE_RET_NOT_HANDLED 0 /* command not handled */ +#define READLINE_RET_HANDLED 1 /* command handled */ +#define READLINE_RET_ACCEPTED 2 /* return pressed */ +/* return READLINE_RET_x */ +int readline_handle_byte(ReadlineState *s, int c); +void readline_start(ReadlineState *s, const char *prompt, int is_password); + +/* the following functions must be provided */ +void readline_find_completion(const char *cmdline); +void term_printf(const char *fmt, ...) __attribute__ ((__format__ (__printf__, 1, 2))); +void term_flush(void); + +#endif /* READLINE_H */ diff --git a/readline_tty.c b/readline_tty.c new file mode 100644 index 00000000..9a7e929b --- /dev/null +++ b/readline_tty.c @@ -0,0 +1,246 @@ +/* + * Readline TTY support + * + * Copyright (c) 2017-2025 Fabrice Bellard + * + * 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 +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include "readline_tty.h" + +static int ctrl_c_pressed; + +#ifdef _WIN32 +/* Windows 10 built-in VT100 emulation */ +#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 + +static BOOL WINAPI ctrl_handler(DWORD type) +{ + if (type == CTRL_C_EVENT) { + ctrl_c_pressed++; + if (ctrl_c_pressed >= 4) { + /* just to be able to stop the process if it is hanged */ + return FALSE; + } else { + return TRUE; + } + } else { + return FALSE; + } +} + +int readline_tty_init(void) +{ + HANDLE handle; + CONSOLE_SCREEN_BUFFER_INFO info; + int n_cols; + + handle = (HANDLE)_get_osfhandle(0); + SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); + _setmode(0, _O_BINARY); + + handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ + SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + SetConsoleCtrlHandler(ctrl_handler, TRUE); + + n_cols = 80; + if (GetConsoleScreenBufferInfo(handle, &info)) { + n_cols = info.dwSize.X; + } + return n_cols; +} + +/* if processed input is enabled, Ctrl-C is handled by ctrl_handler() */ +static void set_processed_input(BOOL enable) +{ + DWORD mode; + HANDLE handle; + + handle = (HANDLE)_get_osfhandle(0); + if (!GetConsoleMode(handle, &mode)) + return; + if (enable) + mode |= ENABLE_PROCESSED_INPUT; + else + mode &= ~ENABLE_PROCESSED_INPUT; + SetConsoleMode(handle, mode); +} + +#else +/* init terminal so that we can grab keys */ +/* XXX: merge with cp_utils.c */ +static struct termios oldtty; +static int old_fd0_flags; + +static void term_exit(void) +{ + tcsetattr (0, TCSANOW, &oldtty); + fcntl(0, F_SETFL, old_fd0_flags); +} + +static void sigint_handler(int signo) +{ + ctrl_c_pressed++; + if (ctrl_c_pressed >= 4) { + /* just to be able to stop the process if it is hanged */ + signal(SIGINT, SIG_DFL); + } +} + +int readline_tty_init(void) +{ + struct termios tty; + struct sigaction sa; + struct winsize ws; + int n_cols; + + tcgetattr (0, &tty); + oldtty = tty; + old_fd0_flags = fcntl(0, F_GETFL); + + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP + |INLCR|IGNCR|ICRNL|IXON); + tty.c_oflag |= OPOST; + tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); + // tty.c_lflag &= ~ISIG; /* ctrl-C returns a signal */ + tty.c_cflag &= ~(CSIZE|PARENB); + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + tcsetattr (0, TCSANOW, &tty); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigint_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + + atexit(term_exit); + + // fcntl(0, F_SETFL, O_NONBLOCK); + n_cols = 80; + if (ioctl(0, TIOCGWINSZ, &ws) == 0 && + ws.ws_col >= 4 && ws.ws_row >= 4) { + n_cols = ws.ws_col; + } + return n_cols; +} +#endif + +void term_printf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +void term_flush(void) +{ + fflush(stdout); +} + +const char *readline_tty(ReadlineState *s, + const char *prompt, BOOL multi_line) +{ + int len, i, ctrl_c_count, c, ret; + const char *ret_str; + uint8_t buf[128]; + +#ifdef _WIN32 + set_processed_input(FALSE); + /* ctrl-C is no longer handled by the system */ +#endif + ret_str = NULL; + readline_start(s, prompt, FALSE); + ctrl_c_count = 0; + while (ret_str == NULL) { + len = read(0, buf, sizeof(buf)); + if (len == 0) + break; + for(i = 0; i < len; i++) { + c = buf[i]; +#ifdef _WIN32 + if (c == 3) { + /* ctrl-C */ + ctrl_c_pressed++; + } else +#endif + { + ret = readline_handle_byte(s, c); + if (ret == READLINE_RET_EXIT) { + goto done; + } else if (ret == READLINE_RET_ACCEPTED) { + ret_str = (const char *)s->term_cmd_buf; + goto done; + } + ctrl_c_count = 0; + } + } + if (ctrl_c_pressed) { + ctrl_c_pressed = 0; + if (ctrl_c_count == 0) { + printf("(Press Ctrl-C again to quit)\n"); + ctrl_c_count++; + } else { + printf("Exiting.\n"); + break; + } + } + } +done: +#ifdef _WIN32 + set_processed_input(TRUE); +#endif + return ret_str; +} + +BOOL readline_is_interrupted(void) +{ + BOOL ret; + ret = (ctrl_c_pressed != 0); + ctrl_c_pressed = 0; + return ret; +} diff --git a/readline_tty.h b/readline_tty.h new file mode 100644 index 00000000..7ef33cb5 --- /dev/null +++ b/readline_tty.h @@ -0,0 +1,29 @@ +/* + * Readline TTY support + * + * Copyright (c) 2017-2025 Fabrice Bellard + * + * 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 "readline.h" + +int readline_tty_init(void); +const char *readline_tty(ReadlineState *s, + const char *prompt, BOOL multi_line); +BOOL readline_is_interrupted(void); diff --git a/softfp_template.h b/softfp_template.h new file mode 100644 index 00000000..34034fd8 --- /dev/null +++ b/softfp_template.h @@ -0,0 +1,970 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * 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. + */ +#if F_SIZE == 32 +#define F_UINT uint32_t +#define F_ULONG uint64_t +#define MANT_SIZE 23 +#define EXP_SIZE 8 +#elif F_SIZE == 64 +#define F_UHALF uint32_t +#define F_UINT uint64_t +#ifdef HAVE_INT128 +#define F_ULONG uint128_t +#endif +#define MANT_SIZE 52 +#define EXP_SIZE 11 +#elif F_SIZE == 128 +#define F_UHALF uint64_t +#define F_UINT uint128_t +#define MANT_SIZE 112 +#define EXP_SIZE 15 +#else +#error unsupported F_SIZE +#endif + +#define EXP_MASK ((1 << EXP_SIZE) - 1) +#define MANT_MASK (((F_UINT)1 << MANT_SIZE) - 1) +#define SIGN_MASK ((F_UINT)1 << (F_SIZE - 1)) +#define IMANT_SIZE (F_SIZE - 2) /* internal mantissa size */ +#define RND_SIZE (IMANT_SIZE - MANT_SIZE) +#define QNAN_MASK ((F_UINT)1 << (MANT_SIZE - 1)) +#define EXP_BIAS ((1 << (EXP_SIZE - 1)) - 1) + +/* quiet NaN */ +#define F_QNAN glue(F_QNAN, F_SIZE) +#define clz glue(clz, F_SIZE) +#define pack_sf glue(pack_sf, F_SIZE) +#define unpack_sf glue(unpack_sf, F_SIZE) +#define rshift_rnd glue(rshift_rnd, F_SIZE) +#define round_pack_sf glue(roundpack_sf, F_SIZE) +#define normalize_sf glue(normalize_sf, F_SIZE) +#define normalize2_sf glue(normalize2_sf, F_SIZE) +#define issignan_sf glue(issignan_sf, F_SIZE) +#define isnan_sf glue(isnan_sf, F_SIZE) +#define add_sf glue(add_sf, F_SIZE) +#define mul_sf glue(mul_sf, F_SIZE) +#define fma_sf glue(fma_sf, F_SIZE) +#define div_sf glue(div_sf, F_SIZE) +#define sqrt_sf glue(sqrt_sf, F_SIZE) +#define normalize_subnormal_sf glue(normalize_subnormal_sf, F_SIZE) +#define divrem_u glue(divrem_u, F_SIZE) +#define sqrtrem_u glue(sqrtrem_u, F_SIZE) +#define mul_u glue(mul_u, F_SIZE) +#define cvt_sf32_sf glue(cvt_sf32_sf, F_SIZE) +#define cvt_sf64_sf glue(cvt_sf64_sf, F_SIZE) + +static const F_UINT F_QNAN = (((F_UINT)EXP_MASK << MANT_SIZE) | ((F_UINT)1 << (MANT_SIZE - 1))); + +static inline F_UINT pack_sf(uint32_t a_sign, uint32_t a_exp, F_UINT a_mant) +{ + return ((F_UINT)a_sign << (F_SIZE - 1)) | + ((F_UINT)a_exp << MANT_SIZE) | + (a_mant & MANT_MASK); +} + +static inline F_UINT unpack_sf(uint32_t *pa_sign, int32_t *pa_exp, + F_UINT a) +{ + *pa_sign = a >> (F_SIZE - 1); + *pa_exp = (a >> MANT_SIZE) & EXP_MASK; + return a & MANT_MASK; +} + +static F_UINT rshift_rnd(F_UINT a, int d) +{ + F_UINT mask; + if (d != 0) { + if (d >= F_SIZE) { + a = (a != 0); + } else { + mask = ((F_UINT)1 << d) - 1; + a = (a >> d) | ((a & mask) != 0); + } + } + return a; +} + +#if F_USE_FFLAGS +#define FFLAGS_PARAM , uint32_t *pfflags +#define FFLAGS_ARG , pfflags +#else +#define FFLAGS_PARAM +#define FFLAGS_ARG +#endif + +/* a_mant is considered to have its MSB at F_SIZE - 2 bits */ +static F_UINT round_pack_sf(uint32_t a_sign, int a_exp, F_UINT a_mant, + RoundingModeEnum rm FFLAGS_PARAM) +{ + int diff; + uint32_t addend, rnd_bits; + + switch(rm) { + case RM_RNE: + case RM_RMM: + addend = (1 << (RND_SIZE - 1)); + break; + case RM_RTZ: + addend = 0; + break; + default: + case RM_RDN: + case RM_RUP: + // printf("s=%d rm=%d m=%x\n", a_sign, rm, a_mant); + if (a_sign ^ (rm & 1)) + addend = (1 << RND_SIZE) - 1; + else + addend = 0; + break; + } + + /* potentially subnormal */ + if (a_exp <= 0) { + BOOL is_subnormal; + /* Note: we set the underflow flag if the rounded result + is subnormal and inexact */ + is_subnormal = (a_exp < 0 || + (a_mant + addend) < ((F_UINT)1 << (F_SIZE - 1))); + diff = 1 - a_exp; + a_mant = rshift_rnd(a_mant, diff); + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + if (is_subnormal && rnd_bits != 0) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_UNDERFLOW; +#endif + } + a_exp = 1; + } else { + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + } +#if F_USE_FFLAGS + if (rnd_bits != 0) + *pfflags |= FFLAG_INEXACT; +#endif + a_mant = (a_mant + addend) >> RND_SIZE; + /* half way: select even result */ + if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1))) + a_mant &= ~1; + /* Note the rounding adds at least 1, so this is the maximum + value */ + a_exp += a_mant >> (MANT_SIZE + 1); + if (a_mant <= MANT_MASK) { + /* denormalized or zero */ + a_exp = 0; + } else if (a_exp >= EXP_MASK) { + /* overflow */ + if (addend == 0) { + a_exp = EXP_MASK - 1; + a_mant = MANT_MASK; + } else { + /* infinity */ + a_exp = EXP_MASK; + a_mant = 0; + } +#if F_USE_FFLAGS + *pfflags |= FFLAG_OVERFLOW | FFLAG_INEXACT; +#endif + } + return pack_sf(a_sign, a_exp, a_mant); +} + +/* a_mant is considered to have at most F_SIZE - 1 bits */ +static F_UINT normalize_sf(uint32_t a_sign, int a_exp, F_UINT a_mant, + RoundingModeEnum rm FFLAGS_PARAM) +{ + int shift; + shift = clz(a_mant) - (F_SIZE - 1 - IMANT_SIZE); + assert(shift >= 0); + a_exp -= shift; + a_mant <<= shift; + return round_pack_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +static inline F_UINT normalize_subnormal_sf(int32_t *pa_exp, F_UINT a_mant) +{ + int shift; + shift = MANT_SIZE - ((F_SIZE - 1 - clz(a_mant))); + *pa_exp = 1 - shift; + return a_mant << shift; +} + +#if F_USE_FFLAGS +F_STATIC BOOL issignan_sf(F_UINT a) +{ + uint32_t a_exp1; + F_UINT a_mant; + a_exp1 = (a >> (MANT_SIZE - 1)) & ((1 << (EXP_SIZE + 1)) - 1); + a_mant = a & MANT_MASK; + return (a_exp1 == (2 * EXP_MASK) && a_mant != 0); +} +#endif + +#ifndef F_NORMALIZE_ONLY + +F_STATIC BOOL isnan_sf(F_UINT a) +{ + uint32_t a_exp; + F_UINT a_mant; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + return (a_exp == EXP_MASK && a_mant != 0); +} + + +F_STATIC F_UINT add_sf(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign, a_exp, b_exp; + F_UINT tmp, a_mant, b_mant; + + /* swap so that abs(a) >= abs(b) */ + if ((a & ~SIGN_MASK) < (b & ~SIGN_MASK)) { + tmp = a; + a = b; + b = tmp; + } + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = (a & MANT_MASK) << 3; + b_mant = (b & MANT_MASK) << 3; + if (unlikely(a_exp == EXP_MASK)) { + if (a_mant != 0) { + /* NaN result */ +#if F_USE_FFLAGS + if (!(a_mant & (QNAN_MASK << 3)) || issignan_sf(b)) + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else if (b_exp == EXP_MASK && a_sign != b_sign) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { + /* infinity */ + return a; + } + } + if (a_exp == 0) { + a_exp = 1; + } else { + a_mant |= (F_UINT)1 << (MANT_SIZE + 3); + } + if (b_exp == 0) { + b_exp = 1; + } else { + b_mant |= (F_UINT)1 << (MANT_SIZE + 3); + } + b_mant = rshift_rnd(b_mant, a_exp - b_exp); + if (a_sign == b_sign) { + /* same signs : add the absolute values */ + a_mant += b_mant; + } else { + /* different signs : subtract the absolute values */ + a_mant -= b_mant; + if (a_mant == 0) { + /* zero result : the sign needs a specific handling */ + a_sign = (rm == RM_RDN); + } + } + a_exp += (RND_SIZE - 3); + return normalize_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +F_STATIC F_UINT glue(sub_sf, F_SIZE)(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + return add_sf(a, b ^ SIGN_MASK, rm FFLAGS_ARG); +} + +#ifdef F_ULONG + +static F_UINT mul_u(F_UINT *plow, F_UINT a, F_UINT b) +{ + F_ULONG r; + r = (F_ULONG)a * (F_ULONG)b; + *plow = r; + return r >> F_SIZE; +} + +#else + +#define FH_SIZE (F_SIZE / 2) + +static F_UINT mul_u(F_UINT *plow, F_UINT a, F_UINT b) +{ + F_UHALF a0, a1, b0, b1, r0, r1, r2, r3; + F_UINT r00, r01, r10, r11, c; + a0 = a; + a1 = a >> FH_SIZE; + b0 = b; + b1 = b >> FH_SIZE; + + r00 = (F_UINT)a0 * (F_UINT)b0; + r01 = (F_UINT)a0 * (F_UINT)b1; + r10 = (F_UINT)a1 * (F_UINT)b0; + r11 = (F_UINT)a1 * (F_UINT)b1; + + r0 = r00; + c = (r00 >> FH_SIZE) + (F_UHALF)r01 + (F_UHALF)r10; + r1 = c; + c = (c >> FH_SIZE) + (r01 >> FH_SIZE) + (r10 >> FH_SIZE) + (F_UHALF)r11; + r2 = c; + r3 = (c >> FH_SIZE) + (r11 >> FH_SIZE); + + *plow = ((F_UINT)r1 << FH_SIZE) | r0; + return ((F_UINT)r3 << FH_SIZE) | r2; +} + +#undef FH_SIZE + +#endif + +F_STATIC F_UINT mul_sf(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign, r_sign; + int32_t a_exp, b_exp, r_exp; + F_UINT a_mant, b_mant, r_mant, r_mant_low; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + if (a_exp == EXP_MASK || b_exp == EXP_MASK) { + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + /* infinity */ + if ((a_exp == EXP_MASK && (b_exp == 0 && b_mant == 0)) || + (b_exp == EXP_MASK && (a_exp == 0 && a_mant == 0))) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + if (b_exp == 0) { + if (b_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + r_exp = a_exp + b_exp - (1 << (EXP_SIZE - 1)) + 2; + + r_mant = mul_u(&r_mant_low,a_mant << RND_SIZE, b_mant << (RND_SIZE + 1)); + r_mant |= (r_mant_low != 0); + return normalize_sf(r_sign, r_exp, r_mant, rm FFLAGS_ARG); +} + +#ifdef F_ULONG + +static F_UINT divrem_u(F_UINT *pr, F_UINT ah, F_UINT al, F_UINT b) +{ + F_ULONG a; + a = ((F_ULONG)ah << F_SIZE) | al; + *pr = a % b; + return a / b; +} + +#else + +/* XXX: optimize */ +static F_UINT divrem_u(F_UINT *pr, F_UINT a1, F_UINT a0, F_UINT b) +{ + int i, qb, ab; + + assert(a1 < b); + for(i = 0; i < F_SIZE; i++) { + ab = a1 >> (F_SIZE - 1); + a1 = (a1 << 1) | (a0 >> (F_SIZE - 1)); + if (ab || a1 >= b) { + a1 -= b; + qb = 1; + } else { + qb = 0; + } + a0 = (a0 << 1) | qb; + } + *pr = a1; + return a0; +} + +#endif + +F_STATIC F_UINT div_sf(F_UINT a, F_UINT b, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign, r_sign; + int32_t a_exp, b_exp, r_exp; + F_UINT a_mant, b_mant, r_mant, r; + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + r_sign = a_sign ^ b_sign; + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + b_mant = b & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0 || isnan_sf(b)) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else if (b_exp == EXP_MASK) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { + return pack_sf(r_sign, EXP_MASK, 0); + } + } else if (b_exp == EXP_MASK) { + if (b_mant != 0) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + return pack_sf(r_sign, 0, 0); + } + } + + if (b_exp == 0) { + if (b_mant == 0) { + /* zero */ + if (a_exp == 0 && a_mant == 0) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } else { +#if F_USE_FFLAGS + *pfflags |= FFLAG_DIVIDE_ZERO; +#endif + return pack_sf(r_sign, EXP_MASK, 0); + } + } + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(r_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + r_exp = a_exp - b_exp + (1 << (EXP_SIZE - 1)) - 1; + r_mant = divrem_u(&r, a_mant, 0, b_mant << 2); + if (r != 0) + r_mant |= 1; + return normalize_sf(r_sign, r_exp, r_mant, rm FFLAGS_ARG); +} + +#ifdef F_ULONG + +/* compute sqrt(a) with a = ah*2^F_SIZE+al and a < 2^(F_SIZE - 2) + return true if not exact square. */ +static int sqrtrem_u(F_UINT *pr, F_UINT ah, F_UINT al) +{ + F_ULONG a, u, s; + int l, inexact; + + /* 2^l >= a */ + if (ah != 0) { + l = 2 * F_SIZE - clz(ah - 1); + } else { + if (al == 0) { + *pr = 0; + return 0; + } + l = F_SIZE - clz(al - 1); + } + a = ((F_ULONG)ah << F_SIZE) | al; + u = (F_ULONG)1 << ((l + 1) / 2); + for(;;) { + s = u; + u = ((a / s) + s) / 2; + if (u >= s) + break; + } + inexact = (a - s * s) != 0; + *pr = s; + return inexact; +} + +#else + +static int sqrtrem_u(F_UINT *pr, F_UINT a1, F_UINT a0) +{ + int l, inexact; + F_UINT u, s, r, q, sq0, sq1; + + /* 2^l >= a */ + if (a1 != 0) { + l = 2 * F_SIZE - clz(a1 - 1); + } else { + if (a0 == 0) { + *pr = 0; + return 0; + } + l = F_SIZE - clz(a0 - 1); + } + u = (F_UINT)1 << ((l + 1) / 2); + for(;;) { + s = u; + q = divrem_u(&r, a1, a0, s); + u = (q + s) / 2; + if (u >= s) + break; + } + sq1 = mul_u(&sq0, s, s); + inexact = (sq0 != a0 || sq1 != a1); + *pr = s; + return inexact; +} + +#endif + +F_STATIC F_UINT sqrt_sf(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK) { + if (a_mant != 0) { +#if F_USE_FFLAGS + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else if (a_sign) { + goto neg_error; + } else { + return a; /* +infinity */ + } + } + if (a_sign) { + if (a_exp == 0 && a_mant == 0) + return a; /* -zero */ + neg_error: +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return F_QNAN; + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(0, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + a_exp -= EXP_MASK / 2; + /* simpler to handle an even exponent */ + if (a_exp & 1) { + a_exp--; + a_mant <<= 1; + } + a_exp = (a_exp >> 1) + EXP_MASK / 2; + a_mant <<= (F_SIZE - 4 - MANT_SIZE); + if (sqrtrem_u(&a_mant, a_mant, 0)) + a_mant |= 1; + return normalize_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +/* comparisons */ + +F_STATIC int glue(eq_quiet_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + if (issignan_sf(a) || issignan_sf(b)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return 0; + } + + if ((F_UINT)((a | b) << 1) == 0) + return 1; /* zero case */ + return (a == b); +} + +F_STATIC int glue(le_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return 0; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + return (a_sign || ((F_UINT)((a | b) << 1) == 0)); + } else { + if (a_sign) { + return (a >= b); + } else { + return (a <= b); + } + } +} + +F_STATIC int glue(lt_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return 0; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + return (a_sign && ((F_UINT)((a | b) << 1) != 0)); + } else { + if (a_sign) { + return (a > b); + } else { + return (a < b); + } + } +} + +/* return -1 (a < b), 0 (a = b), 1 (a > b) or 2 (a = nan or b = + nan) */ +F_STATIC int glue(cmp_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign, b_sign; + + if (isnan_sf(a) || isnan_sf(b)) { +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return 2; + } + + a_sign = a >> (F_SIZE - 1); + b_sign = b >> (F_SIZE - 1); + if (a_sign != b_sign) { + if ((F_UINT)((a | b) << 1) != 0) + return 1 - 2 * a_sign; + else + return 0; /* -0 = +0 */ + } else { + if (a < b) + return 2 * a_sign - 1; + else if (a > b) + return 1 - 2 * a_sign; + else + return 0; + } +} + +/* conversions between floats */ + +#if F_SIZE >= 64 + +F_STATIC F_UINT cvt_sf32_sf(uint32_t a FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf32(&a_sign, &a_exp, a); + if (a_exp == 0xff) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf32(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + /* infinity */ + return pack_sf(a_sign, EXP_MASK, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(a_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf32(&a_exp, a_mant); + } + /* convert the exponent value */ + a_exp = a_exp - 0x7f + (EXP_MASK / 2); + /* shift the mantissa */ + a_mant <<= (MANT_SIZE - 23); + /* We assume the target float is large enough to that no + normalization is necessary */ + return pack_sf(a_sign, a_exp, a_mant); +} + +F_STATIC uint32_t glue(glue(cvt_sf, F_SIZE), _sf32)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf(&a_sign, &a_exp, a); + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN32; + } else { + /* infinity */ + return pack_sf32(a_sign, 0xff, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf32(a_sign, 0, 0); /* zero */ + normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + /* convert the exponent value */ + a_exp = a_exp - (EXP_MASK / 2) + 0x7f; + /* shift the mantissa */ + a_mant = rshift_rnd(a_mant, MANT_SIZE - (32 - 2)); + return normalize_sf32(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +#endif + +#if F_SIZE >= 128 + +F_STATIC F_UINT cvt_sf64_sf(uint64_t a FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf64(&a_sign, &a_exp, a); + + if (a_exp == 0x7ff) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf64(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN; + } else { + /* infinity */ + return pack_sf(a_sign, EXP_MASK, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf(a_sign, 0, 0); /* zero */ + a_mant = normalize_subnormal_sf64(&a_exp, a_mant); + } + /* convert the exponent value */ + a_exp = a_exp - 0x3ff + (EXP_MASK / 2); + /* shift the mantissa */ + a_mant <<= (MANT_SIZE - 52); + return pack_sf(a_sign, a_exp, a_mant); +} + +F_STATIC uint64_t glue(glue(cvt_sf, F_SIZE), _sf64)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + + a_mant = unpack_sf(&a_sign, &a_exp, a); + if (a_exp == EXP_MASK) { + if (a_mant != 0) { + /* NaN */ +#if F_USE_FFLAGS + if (issignan_sf(a)) { + *pfflags |= FFLAG_INVALID_OP; + } +#endif + return F_QNAN64; + } else { + /* infinity */ + return pack_sf64(a_sign, 0x7ff, 0); + } + } + if (a_exp == 0) { + if (a_mant == 0) + return pack_sf64(a_sign, 0, 0); /* zero */ + normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + /* convert the exponent value */ + a_exp = a_exp - (EXP_MASK / 2) + 0x3ff; + /* shift the mantissa */ + a_mant = rshift_rnd(a_mant, MANT_SIZE - (64 - 2)); + return normalize_sf64(a_sign, a_exp, a_mant, rm, pfflags); +} + +#endif + +#undef clz + +#define ICVT_SIZE 32 +#include "softfp_template_icvt.h" + +#define ICVT_SIZE 64 +#include "softfp_template_icvt.h" + +/* additional libm functions */ + +/* return a mod b (exact) */ +F_STATIC F_UINT glue(fmod_sf, F_SIZE)(F_UINT a, F_UINT b FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp, b_exp, n; + F_UINT a_mant, b_mant, a_abs, b_abs; + + a_abs = a & ~SIGN_MASK; + b_abs = b & ~SIGN_MASK; + if (b_abs == 0 || + a_abs >= ((F_UINT)EXP_MASK << MANT_SIZE) || + b_abs > ((F_UINT)EXP_MASK << MANT_SIZE)) { + /* XXX: flags */ + return F_QNAN; + } + if (a_abs < b_abs) { + return a; /* |a| < |b| return a */ + } else if (a_abs == b_abs) { + return a & SIGN_MASK; /* |a| = |b| return copy_sign(0, a) */ + } + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + b_exp = (b >> MANT_SIZE) & EXP_MASK; + a_mant = (a & MANT_MASK); + b_mant = (b & MANT_MASK); + + if (a_exp == 0) { + a_mant = normalize_subnormal_sf(&a_exp, a_mant); + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + if (b_exp == 0) { + b_mant = normalize_subnormal_sf(&b_exp, b_mant); + } else { + b_mant |= (F_UINT)1 << MANT_SIZE; + } + n = a_exp - b_exp; + if (a_mant >= b_mant) + a_mant -= b_mant; + /* here a_mant < b_mant and n >= 0 */ + /* multiply a_mant by 2^n */ + /* XXX: do it faster */ + while (n != 0) { + a_mant <<= 1; + if (a_mant >= b_mant) + a_mant -= b_mant; + n--; + } + /* Note: the rounding mode does not matter because the result is + exact */ + return normalize_sf(a_sign, b_exp, a_mant << RND_SIZE, RM_RNE FFLAGS_ARG); +} +#endif /* F_NORMALIZE_ONLY */ + +#undef F_SIZE +#undef F_UINT +#undef F_ULONG +#undef F_UHALF +#undef MANT_SIZE +#undef EXP_SIZE +#undef EXP_MASK +#undef MANT_MASK +#undef SIGN_MASK +#undef IMANT_SIZE +#undef RND_SIZE +#undef QNAN_MASK +#undef F_QNAN +#undef F_NORMALIZE_ONLY +#undef EXP_BIAS + +#undef pack_sf +#undef unpack_sf +#undef rshift_rnd +#undef round_pack_sf +#undef normalize_sf +#undef normalize2_sf +#undef issignan_sf +#undef isnan_sf +#undef add_sf +#undef mul_sf +#undef fma_sf +#undef div_sf +#undef sqrt_sf +#undef normalize_subnormal_sf +#undef divrem_u +#undef sqrtrem_u +#undef mul_u +#undef cvt_sf32_sf +#undef cvt_sf64_sf diff --git a/softfp_template_icvt.h b/softfp_template_icvt.h new file mode 100644 index 00000000..02fee109 --- /dev/null +++ b/softfp_template_icvt.h @@ -0,0 +1,172 @@ +/* + * SoftFP Library + * + * Copyright (c) 2016 Fabrice Bellard + * + * 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. + */ +#if ICVT_SIZE == 32 +#define ICVT_UINT uint32_t +#define ICVT_INT int32_t +#elif ICVT_SIZE == 64 +#define ICVT_UINT uint64_t +#define ICVT_INT int64_t +#elif ICVT_SIZE == 128 +#define ICVT_UINT uint128_t +#define ICVT_INT int128_t +#else +#error unsupported icvt +#endif + +/* conversions between float and integers */ +static ICVT_INT glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm, + BOOL is_unsigned FFLAGS_PARAM) +{ + uint32_t a_sign, addend, rnd_bits; + int32_t a_exp; + F_UINT a_mant; + ICVT_UINT r, r_max; + + a_sign = a >> (F_SIZE - 1); + a_exp = (a >> MANT_SIZE) & EXP_MASK; + a_mant = a & MANT_MASK; + if (a_exp == EXP_MASK && a_mant != 0) + a_sign = 0; /* NaN is like +infinity */ + if (a_exp == 0) { + a_exp = 1; + } else { + a_mant |= (F_UINT)1 << MANT_SIZE; + } + a_mant <<= RND_SIZE; + a_exp = a_exp - (EXP_MASK / 2) - MANT_SIZE; + + if (is_unsigned) + r_max = (ICVT_UINT)a_sign - 1; + else + r_max = ((ICVT_UINT)1 << (ICVT_SIZE - 1)) - (ICVT_UINT)(a_sign ^ 1); + if (a_exp >= 0) { + if (a_exp <= (ICVT_SIZE - 1 - MANT_SIZE)) { + r = (ICVT_UINT)(a_mant >> RND_SIZE) << a_exp; + if (r > r_max) + goto overflow; + } else { + overflow: +#if F_USE_FFLAGS + *pfflags |= FFLAG_INVALID_OP; +#endif + return r_max; + } + } else { + a_mant = rshift_rnd(a_mant, -a_exp); + + switch(rm) { + case RM_RNE: + case RM_RMM: + addend = (1 << (RND_SIZE - 1)); + break; + case RM_RTZ: + addend = 0; + break; + default: + case RM_RDN: + case RM_RUP: + if (a_sign ^ (rm & 1)) + addend = (1 << RND_SIZE) - 1; + else + addend = 0; + break; + } + + rnd_bits = a_mant & ((1 << RND_SIZE ) - 1); + a_mant = (a_mant + addend) >> RND_SIZE; + /* half way: select even result */ + if (rm == RM_RNE && rnd_bits == (1 << (RND_SIZE - 1))) + a_mant &= ~1; + if (a_mant > r_max) + goto overflow; + r = a_mant; +#if F_USE_FFLAGS + if (rnd_bits != 0) + *pfflags |= FFLAG_INEXACT; +#endif + } + if (a_sign) + r = -r; + return r; +} + +F_STATIC ICVT_INT __maybe_unused glue(glue(glue(cvt_sf, F_SIZE), _i), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE)(a, rm, + FALSE FFLAGS_ARG); +} + +F_STATIC ICVT_UINT __maybe_unused glue(glue(glue(cvt_sf, F_SIZE), _u), ICVT_SIZE)(F_UINT a, RoundingModeEnum rm FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_sf, F_SIZE), _i), ICVT_SIZE) (a, rm, + TRUE FFLAGS_ARG); +} + +/* conversions between float and integers */ +static F_UINT glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, + RoundingModeEnum rm, + BOOL is_unsigned FFLAGS_PARAM) +{ + uint32_t a_sign; + int32_t a_exp; + F_UINT a_mant; + ICVT_UINT r, mask; + int l; + + if (!is_unsigned && a < 0) { + a_sign = 1; + r = -(ICVT_UINT)a; + } else { + a_sign = 0; + r = a; + } + a_exp = (EXP_MASK / 2) + F_SIZE - 2; + /* need to reduce range before generic float normalization */ + l = ICVT_SIZE - glue(clz, ICVT_SIZE)(r) - (F_SIZE - 1); + if (l > 0) { + mask = r & (((ICVT_UINT)1 << l) - 1); + r = (r >> l) | ((r & mask) != 0); + a_exp += l; + } + a_mant = r; + return normalize_sf(a_sign, a_exp, a_mant, rm FFLAGS_ARG); +} + +F_STATIC F_UINT __maybe_unused glue(glue(glue(cvt_i, ICVT_SIZE), _sf), F_SIZE)(ICVT_INT a, + RoundingModeEnum rm + FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, FALSE FFLAGS_ARG); +} + +F_STATIC F_UINT __maybe_unused glue(glue(glue(cvt_u, ICVT_SIZE), _sf), F_SIZE)(ICVT_UINT a, + RoundingModeEnum rm + FFLAGS_PARAM) +{ + return glue(glue(glue(internal_cvt_i, ICVT_SIZE), _sf), F_SIZE)(a, rm, TRUE FFLAGS_ARG); +} + +#undef ICVT_SIZE +#undef ICVT_INT +#undef ICVT_UINT diff --git a/tests/mandelbrot.js b/tests/mandelbrot.js new file mode 100644 index 00000000..40b6dfa7 --- /dev/null +++ b/tests/mandelbrot.js @@ -0,0 +1,39 @@ +/* Mandelbrot display on a color terminal + (c) 2025 Fabrice Bellard + MIT license +*/ +function mandelbrot(center_x, center_y, scale, w, h, max_it) +{ + var x1, y1, y2, i, x, y, cx, cy, fx, fy, i, t, c, s, c0; + var colors = [ 14, 15, 7, 8, 0, 4, 12, 5, 13, 1, 9, 3, 11, 10, 2, 6]; + fx = scale * 0.5 / Math.min(w, h); + fy = fx * 2; + for(y1 = 0; y1 < h; y1++) { + s = ""; + for(x1 = 0; x1 < w; x1++) { + for(y2 = 0; y2 < 2; y2++) { + cx = (x1 - w * 0.5) * fx + center_x; + cy = (y1 + y2 * 0.5 - h * 0.5) * fy + center_y; + x = 0; + y = 0; + for(i = 0; i < max_it && x * x + y * y < 4; i++) { + t = x * x - y * y + cx; + y = 2 * x * y + cy; + x = t; + } + if (i >= max_it) { + c = 0; + } else { + c = colors[i % colors.length]; + } + if (y2 == 0) + c0 = c; + } + s += "\x1b[" + (c0 >= 8 ? 82 + c0 : 30 + c0) + ";" + (c >= 8 ? 92 + c : 40 + c) + "m\u2580"; + } + s += "\x1b[0m"; /* reset the colors */ + console.log(s); + } +} + +mandelbrot(-0.75, 0.0, 2.0, 80, 25, 50); diff --git a/tests/microbench.js b/tests/microbench.js new file mode 100644 index 00000000..c41e15cd --- /dev/null +++ b/tests/microbench.js @@ -0,0 +1,1137 @@ +/* + * Javascript Micro benchmark + * + * 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. + */ + +function pad(str, n) { + str += ""; + while (str.length < n) + str += " "; + return str; +} + +function pad_left(str, n) { + str += ""; + while (str.length < n) + str = " " + str; + return str; +} + +function pad_center(str, n) { + str += ""; + while (str.length < n) { + if ((n - str.length) & 1) + str = str + " "; + else + str = " " + str; + } + return str; +} + +var ref_data; +var log_data; + +var heads = [ "TEST", "N", "TIME (ns)", "REF (ns)", "SCORE (%)" ]; +var widths = [ 22, 10, 9, 9, 9 ]; +var precs = [ 0, 0, 2, 2, 2 ]; +var total = [ 0, 0, 0, 0, 0 ]; +var total_score = 0; +var total_scale = 0; + +if (typeof console == "undefined") { + var console = { log: print }; +} + +function log_line() { + var i, n, s, a; + s = ""; + for (i = 0, n = arguments.length; i < n; i++) { + if (i > 0) + s += " "; + a = arguments[i]; + if (typeof a == "number") { + total[i] += a; + a = a.toFixed(precs[i]); + a+=""; + s += pad_left(a, widths[i]); + } else { + s += pad_left(a, widths[i]); + } + } + console.log(s); +} + +var clocks_per_sec = 1000; +var max_iterations = 10; +var clock_threshold = 100; /* favoring short measuring spans */ +var min_n_argument = 1; +var get_clock; +if (typeof performance != "undefined") + get_clock = performance.now; +else + get_clock = Date.now; + +function log_one(text, n, ti) { + var ref; + + if (ref_data) + ref = ref_data[text]; + else + ref = null; + + // XXX + // ti = Math.round(ti * 100) / 100; + log_data[text] = ti; + if (typeof ref === "number") { + log_line(text, n, ti, ref, ti * 100 / ref); + total_score += ti * 100 / ref; + total_scale += 100; + } else { + log_line(text, n, ti); + total_score += 100; + total_scale += 100; + } +} + +function bench(f, text) +{ + var i, j, n, t, t1, ti, nb_its, ref, ti_n, ti_n1, min_ti; + + nb_its = n = 1; + if (f.bench) { + ti_n = f(text); + } else { + ti_n = 1000000000; + min_ti = clock_threshold / 10; + for(i = 0; i < 30; i++) { +// print("n=", n); + ti = 1000000000; + for (j = 0; j < max_iterations; j++) { + t = get_clock(); + while ((t1 = get_clock()) == t) + continue; + nb_its = f(n); + if (nb_its < 0) + return; // test failure + t1 = get_clock() - t1; + if (ti > t1) + ti = t1; + } + if (ti >= min_ti) { + ti_n1 = ti / nb_its; + if (ti_n > ti_n1) + ti_n = ti_n1; + } + if (ti >= clock_threshold && n >= min_n_argument) + break; + n = n * [ 2, 2.5, 2 ][i % 3]; + } + // to use only the best timing from the last loop, uncomment below + //ti_n = ti / nb_its; + } + /* nano seconds per iteration */ + log_one(text, n, ti_n * 1e9 / clocks_per_sec); +} + +var global_res; /* to be sure the code is not optimized */ + +function empty_loop(n) { + var j; + for(j = 0; j < n; j++) { + } + return n; +} + +function date_now(n) { + var j; + for(j = 0; j < n; j++) { + Date.now(); + } + return n; +} + +function prop_read(n) +{ + var obj, sum, j; + obj = {a: 1, b: 2, c:3, d:4 }; + sum = 0; + for(j = 0; j < n; j++) { + sum += obj.a; + sum += obj.b; + sum += obj.c; + sum += obj.d; + } + global_res = sum; + return n * 4; +} + +function prop_write(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a = j; + obj.b = j; + obj.c = j; + obj.d = j; + } + return n * 4; +} + +function prop_update(n) +{ + var obj, j; + obj = {a: 1, b: 2, c:3, d:4 }; + for(j = 0; j < n; j++) { + obj.a += j; + obj.b += j; + obj.c += j; + obj.d += j; + } + return n * 4; +} + +function prop_create(n) +{ + var obj, i, j; + for(j = 0; j < n; j++) { + obj = {}; + obj.a = 1; + obj.b = 2; + obj.c = 3; + obj.d = 4; + obj.e = 5; + obj.f = 6; + obj.g = 7; + obj.h = 8; + obj.i = 9; + obj.j = 10; + for(i = 0; i < 10; i++) { + obj[i] = i; + } + } + return n * 20; +} + +function prop_delete(n) +{ + var obj, j, i, len; + len = 1000; + obj = {}; + for(i = 0; i < n; i++) { + for(j = 0; j < len; j++) { + obj[j] = 1; + } + for(j = 0; j < len; j++) { + delete obj[j]; + } + } + return n * len; +} + +function array_read(n) +{ + var tab, len, sum, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function array_write(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function array_update(n) +{ + var tab, len, i, j; + tab = []; + len = 10; + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] += j; + tab[1] += j; + tab[2] += j; + tab[3] += j; + tab[4] += j; + tab[5] += j; + tab[6] += j; + tab[7] += j; + tab[8] += j; + tab[9] += j; + } + return len * n; +} + +function array_prop_create(n) +{ + var tab, i, j, len; + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab[i] = i; + } + return len * n; +} + +function array_length_read(n) +{ + var tab, sum, j; + tab = [1, 2, 3]; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab.length; + sum += tab.length; + sum += tab.length; + sum += tab.length; + } + global_res = sum; + return n * 4; +} + +function array_length_decr(n) +{ + var tab, i, j, len; + len = 1000; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab[i] = i; + for(i = len - 1; i >= 0; i--) + tab.length = i; + } + return len * n; +} + +function array_hole_length_decr(n) +{ + var tab, i, j, len; + len = 1000; + tab = []; + for(i = 0; i < len; i++) { + if (i != 3) + tab[i] = i; + } + for(j = 0; j < n; j++) { + for(i = len - 1; i >= 0; i--) + tab.length = i; + } + return len * n; +} + +function array_push(n) +{ + var tab, i, j, len; + len = 500; + for(j = 0; j < n; j++) { + tab = []; + for(i = 0; i < len; i++) + tab.push(i); + } + return len * n; +} + +function array_pop(n) +{ + var tab, ref, i, j, len, sum; + len = 500; + ref = []; + for(i = 0; i < len; i++) + ref[i] = i; + for(j = 0; j < n; j++) { + tab = ref.slice(); + sum = 0; + for(i = 0; i < len; i++) + sum += tab.pop(); + global_res = sum; + } + return len * n; +} + +function typed_array_read(n) +{ + var tab, len, sum, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + sum = 0; + for(j = 0; j < n; j++) { + sum += tab[0]; + sum += tab[1]; + sum += tab[2]; + sum += tab[3]; + sum += tab[4]; + sum += tab[5]; + sum += tab[6]; + sum += tab[7]; + sum += tab[8]; + sum += tab[9]; + } + global_res = sum; + return len * n; +} + +function typed_array_write(n) +{ + var tab, len, i, j; + len = 10; + tab = new Int32Array(len); + for(i = 0; i < len; i++) + tab[i] = i; + for(j = 0; j < n; j++) { + tab[0] = j; + tab[1] = j; + tab[2] = j; + tab[3] = j; + tab[4] = j; + tab[5] = j; + tab[6] = j; + tab[7] = j; + tab[8] = j; + tab[9] = j; + } + return len * n; +} + +function closure_read(n) +{ + function f(n) { + var sum, j; + var0 = 0; + sum = 0; + for(j = 0; j < n; j++) { + sum += var0; + sum += var0; + sum += var0; + sum += var0; + } + global_res = sum; + } + var var0 = 0; + f(n); + return n * 4; +} + +function closure_write(n) +{ + function f(n) { + var j; + for(j = 0; j < n; j++) { + var0 = j; + var0 = j; + var0 = j; + var0 = j; + } + } + var var0; + + f(n); + return n * 4; +} + +var global_var0; + +function global_read(n) +{ + var sum, j; + global_var0 = 0; + sum = 0; + for(j = 0; j < n; j++) { + sum += global_var0; + sum += global_var0; + sum += global_var0; + sum += global_var0; + } + global_res = sum; + return n * 4; +} + +function global_write_strict(n) +{ + var j; + for(j = 0; j < n; j++) { + global_var0 = j; + global_var0 = j; + global_var0 = j; + global_var0 = j; + } + return n * 4; +} + +function func_call(n) +{ + function f(a) + { + return 1; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + sum += f(j); + sum += f(j); + sum += f(j); + sum += f(j); + } + global_res = sum; + return n * 4; +} + +function closure_var(n) +{ + function f(a) + { + sum++; + } + + var j, sum; + sum = 0; + for(j = 0; j < n; j++) { + f(j); + f(j); + f(j); + f(j); + } + global_res = sum; + return n * 4; +} + +function int_arith(n) +{ + var i, j, sum; + global_res = 0; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < 1000; i++) { + sum += i * i; + } + global_res += sum; + } + return n * 1000; +} + +function float_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = 0.1; + incr = 1.1; + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigfloat_arith(n) +{ + var i, j, sum, a, incr, a0; + global_res = 0; + a0 = BigFloat("0.1"); + incr = BigFloat("1.1"); + for(j = 0; j < n; j++) { + sum = 0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint_arith(n, bits) +{ + var i, j, sum, a, incr, a0, sum0; + sum0 = global_res = BigInt(0); + a0 = BigInt(1) << BigInt(Math.floor((bits - 10) * 0.5)); + incr = BigInt(1); + for(j = 0; j < n; j++) { + sum = sum0; + a = a0; + for(i = 0; i < 1000; i++) { + sum += a * a; + a += incr; + } + global_res += sum; + } + return n * 1000; +} + +function bigint64_arith(n) +{ + return bigint_arith(n, 64); +} + +function bigint256_arith(n) +{ + return bigint_arith(n, 256); +} + +function set_collection_add(n) +{ + var s, i, j, len = 100; + s = new Set(); + for(j = 0; j < n; j++) { + for(i = 0; i < len; i++) { + s.add(String(i), i); + } + for(i = 0; i < len; i++) { + if (!s.has(String(i))) + throw Error("bug in Set"); + } + } + return n * len; +} + +function array_for(n) +{ + var r, i, j, sum; + r = []; + for(i = 0; i < 100; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i = 0; i < 100; i++) { + sum += r[i]; + } + global_res = sum; + } + return n * 100; +} + +function array_for_in(n) +{ + var r, i, j, sum; + r = []; + for(i = 0; i < 100; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i in r) { + sum += r[i]; + } + global_res = sum; + } + return n * 100; +} + +function array_for_of(n) +{ + var r, i, j, sum; + r = []; + for(i = 0; i < 100; i++) + r[i] = i; + for(j = 0; j < n; j++) { + sum = 0; + for(i of r) { + sum += i; + } + global_res = sum; + } + return n * 100; +} + +function math_min(n) +{ + var i, j, r; + r = 0; + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = Math.min(i, 500); + global_res = r; + } + return n * 1000; +} + +function regexp_ascii(n) +{ + var i, j, r, s; + s = "the quick brown fox jumped over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown fox/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_utf16(n) +{ + var i, j, r, s; + s = "the quick brown ᶠᵒˣ jumped over the lazy ᵈᵒᵍ" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = /the quick brown ᶠᵒˣ/.exec(s) + global_res = r; + } + return n * 1000; +} + +function regexp_replace(n) +{ + var i, j, r, s; + s = "the quick abc brown fox jumped abc over the lazy dog" + for(j = 0; j < n; j++) { + for(i = 0; i < 1000; i++) + r = s.replace(/abc /g, "-"); + global_res = r; + } + return n * 1000; +} + +function string_length(n) +{ + var str, sum, j; + str = "abcde"; + sum = 0; + for(j = 0; j < n; j++) { + sum += str.length; + sum += str.length; + sum += str.length; + sum += str.length; + } + global_res = sum; + return n * 4; +} + +/* incremental string construction as local var */ +function string_build1(n) +{ + var i, j, r; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) + r += "x"; + global_res = r; + } + return n * 100; +} + +/* incremental string construction as arg */ +function string_build2(n, r) +{ + var i, j; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) + r += "x"; + global_res = r; + } + return n * 100; +} + +/* incremental string construction by prepending */ +function string_build3(n, r) +{ + var i, j; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) + r = "x" + r; + global_res = r; + } + return n * 100; +} + +/* incremental string construction with multiple reference */ +function string_build4(n) +{ + var i, j, r, s; + r = ""; + for(j = 0; j < n; j++) { + for(i = 0; i < 100; i++) { + s = r; + r += "x"; + } + global_res = r; + } + return n * 100; +} + +/* sort bench */ + +function sort_bench(text) { + function random(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * n) >> 0]; + } + function random8(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 256) >> 0]; + } + function random1(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(Math.random() * 2) >> 0]; + } + function hill(arr, n, def) { + var mid = n >> 1; + for (var i = 0; i < mid; i++) + arr[i] = def[i]; + for (var i = mid; i < n; i++) + arr[i] = def[n - i]; + } + function comb(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) * i]; + } + function crisscross(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[(i & 1) ? n - i : i]; + } + function zero(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[0]; + } + function increasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + } + function decreasing(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[n - 1 - i]; + } + function alternate(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i ^ 1]; + } + function jigsaw(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i % (n >> 4)]; + } + function incbutone(arr, n, def) { + for (var i = 0; i < n; i++) + arr[i] = def[i]; + if (n > 0) + arr[n >> 2] = def[n]; + } + function incbutfirst(arr, n, def) { + if (n > 0) + arr[0] = def[n]; + for (var i = 1; i < n; i++) + arr[i] = def[i]; + } + function incbutlast(arr, n, def) { + for (var i = 0; i < n - 1; i++) + arr[i] = def[i + 1]; + if (n > 0) + arr[n - 1] = def[0]; + } + + var sort_cases = [ random, random8, random1, jigsaw, hill, comb, + crisscross, zero, increasing, decreasing, alternate, + incbutone, incbutlast, incbutfirst ]; + + var n = sort_bench.array_size || 10000; + var array_type = sort_bench.array_type || Array; + var def, arr; + var i, j, x, y; + var total = 0; + + var save_total_score = total_score; + var save_total_scale = total_scale; + + // initialize default sorted array (n + 1 elements) + def = new array_type(n + 1); + if (array_type == Array) { + for (i = 0; i <= n; i++) { + def[i] = i + ""; + } + } else { + for (i = 0; i <= n; i++) { + def[i] = i; + } + } + def.sort(); + for (var f of sort_cases) { + var ti = 0, tx = 0; + for (j = 0; j < 100; j++) { + arr = new array_type(n); + f(arr, n, def); + var t1 = get_clock(); + arr.sort(); + t1 = get_clock() - t1; + tx += t1; + if (!ti || ti > t1) + ti = t1; + if (tx >= clocks_per_sec) + break; + } + total += ti; + + i = 0; + x = arr[0]; + if (x !== void 0) { + for (i = 1; i < n; i++) { + y = arr[i]; + if (y === void 0) + break; + if (x > y) + break; + x = y; + } + } + while (i < n && arr[i] === void 0) + i++; + if (i < n) { + console.log("sort_bench: out of order error for " + f.name + + " at offset " + (i - 1) + + ": " + arr[i - 1] + " > " + arr[i]); + } + if (sort_bench.verbose) + log_one("sort_" + f.name, n, ti, n * 100); + } + total_score = save_total_score; + total_scale = save_total_scale; + return total / n / 1000; +} +sort_bench.bench = true; +sort_bench.verbose = false; + +function int_to_string(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j + 1).toString(); + } + return n; +} + +function float_to_string(n) +{ + var s, r, j; + r = 0; + for(j = 0; j < n; j++) { + s = (j + 0.1).toString(); + } + return n; +} + +function string_to_int(n) +{ + var s, r, j; + r = 0; + s = "12345"; + r = 0; + for(j = 0; j < n; j++) { + r += (s | 0); + } + global_res = r; + return n; +} + +function string_to_float(n) +{ + var s, r, j; + r = 0; + s = "12345.6"; + r = 0; + for(j = 0; j < n; j++) { + r -= s; + } + global_res = r; + return n; +} + +function load_result(filename) +{ + var f, str, res; + if (typeof std === "undefined") + return null; + f = std.open(filename, "r"); + if (!f) + return null; + str = f.readAsString(); + res = JSON.parse(str); + f.close(); + return res; +} + +function save_result(filename, obj) +{ + var f; + if (typeof std === "undefined") + return; + f = std.open(filename, "w"); + f.puts(JSON.stringify(obj, null, 2)); + f.puts("\n"); + f.close(); +} + +function main(argc, argv, g) +{ + var test_list = [ + empty_loop, + date_now, + prop_read, + prop_write, + prop_update, + prop_create, + prop_delete, + array_read, + array_write, + array_update, + array_prop_create, + array_length_read, + array_length_decr, +// array_hole_length_decr, + array_push, + array_pop, + typed_array_read, + typed_array_write, + closure_read, + closure_write, + global_read, + global_write_strict, + func_call, + closure_var, + int_arith, + float_arith, +// set_collection_add, + array_for, + array_for_in, + array_for_of, + math_min, + regexp_ascii, + regexp_utf16, + regexp_replace, + string_length, + string_build1, + string_build2, + //string_build3, + //string_build4, + sort_bench, + int_to_string, + float_to_string, + string_to_int, + string_to_float, + ]; + var tests = []; + var i, j, n, f, name, found; + + if (typeof BigInt == "function") { + /* BigInt test */ + test_list.push(bigint64_arith); + test_list.push(bigint256_arith); + } + + for (i = 1; i < argc;) { + name = argv[i++]; + if (name == "-a") { + sort_bench.verbose = true; + continue; + } + if (name == "-t") { + name = argv[i++]; + sort_bench.array_type = g[name]; + if (typeof sort_bench.array_type != "function") { + console.log("unknown array type: " + name); + return 1; + } + continue; + } + if (name == "-n") { + sort_bench.array_size = +argv[i++]; + continue; + } + for (j = 0, found = false; j < test_list.length; j++) { + f = test_list[j]; + if (f.name.slice(0, name.length) === name) { + tests.push(f); + found = true; + } + } + if (!found) { + console.log("unknown benchmark: " + name); + return 1; + } + } + if (tests.length == 0) + tests = test_list; + + ref_data = load_result("microbench.txt"); + log_data = {}; + log_line.apply(null, heads); + n = 0; + + for(i = 0; i < tests.length; i++) { + f = tests[i]; + bench(f, f.name, ref_data, log_data); + if (ref_data && ref_data[f.name]) + n++; + } + if (ref_data) + log_line("total", "", total[2], total[3], total_score * 100 / total_scale); + else + log_line("total", "", total[2]); + + if (tests == test_list) + save_result("microbench-new.txt", log_data); +} + +if (!scriptArgs) + scriptArgs = []; +main(scriptArgs.length, scriptArgs, this); diff --git a/tests/test_builtin.js b/tests/test_builtin.js new file mode 100644 index 00000000..9032d18e --- /dev/null +++ b/tests/test_builtin.js @@ -0,0 +1,875 @@ +"use strict"; + +function throw_error(msg) { + throw Error(msg); +} + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw_error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("unexpected exception type"); + return; + } + } + if (!err) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("expected exception"); + } +} + +function my_func(a, b) +{ + return a + b; +} + +function test_function() +{ + function f(a, b) { + var i, tab = []; + tab.push(this); + for(i = 0; i < arguments.length; i++) + tab.push(arguments[i]); + return tab; + } + function constructor1(a) { + this.x = a; + } + + var r, g; + + r = my_func.call(null, 1, 2); + assert(r, 3, "call"); + + r = my_func.apply(null, ["abc", 2]); + assert(r, "abc2", "apply"); + + r = new Function("a", "b", "return a + b;"); + assert(r(2,3), 5, "function"); + + g = f.bind(1, 2); +// assert(g.length, 1); +// assert(g.name, "bound f"); + assert(g(3).toString(), "1,2,3"); + + if (0) { + g = constructor1.bind(null, 1); + r = new g(); + assert(r.x, 1); + } +} + +function test() +{ + var r, a, b, c, err; + + r = Error("hello"); + assert(r.message, "hello", "Error"); + + a = new Object(); + a.x = 1; + assert(a.x, 1, "Object"); + + assert(Object.prototype.constructor, Object, "constructor"); + assert(Object.getPrototypeOf(a), Object.prototype, "getPrototypeOf"); + Object.defineProperty(a, "y", { value: 3, writable: true, configurable: true, enumerable: true }); + assert(a.y, 3, "defineProperty"); + + Object.defineProperty(a, "z", { get: function () { return 4; }, set: function(val) { this.z_val = val; }, configurable: true, enumerable: true }); + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + Object.defineProperty(a, "w", {}); + assert("w" in a, true); + a.w = 1; + + Object.defineProperty(a, "w", {}); + assert(a.w, 1); + + a = { get z() { return 4; }, set z(val) { this.z_val = val; } }; + assert(a.z, 4, "get"); + a.z = 5; + assert(a.z_val, 5, "set"); + + a = {}; + b = Object.create(a); + assert(Object.getPrototypeOf(b), a, "create"); + c = {u:2}; + Object.setPrototypeOf(a, c); + assert(Object.getPrototypeOf(a), c, "setPrototypeOf"); + + a={}; + assert(a.toString(), "[object Object]", "toString"); + assert(Object.prototype.toString.call(1), "[object Number]", "toString"); +/* + a={x:1}; + assert(Object.isExtensible(a), true, "extensible"); + Object.preventExtensions(a); + + err = false; + try { + a.y = 2; + } catch(e) { + err = true; + } + assert(Object.isExtensible(a), false, "extensible"); + assert(typeof a.y, "undefined", "extensible"); + assert(err); +*/ + + a = {x: 1}; + assert(a.hasOwnProperty("x"), true); + assert(a.hasOwnProperty("y"), false); + a = [1, 2]; + assert(a.hasOwnProperty(1), true); + assert(a.hasOwnProperty(2), false); +} + +function test_enum() +{ + var a, tab; + a = {x:1, y:1, z:3}; + tab = Object.keys(a); + assert(tab.toString(), "x,y,z", "keys"); +} + +function test_array() +{ + var a, err, i, log; + + a = [1, 2, 3]; + assert(a.length, 3, "array"); + assert(a[2], 3, "array1"); + + a = new Array(10); + assert(a.length, 10, "array2"); + + a = new Array(1, 2); + assert(a[0] === 1 && a[1] === 2); + + a = [1, 2, 3]; + a.length = 2; + assert(a[0] === 1 && a[1] === 2 && a.length === 2); + + a = []; + a[0] = 10; + a[1] = 3; + assert(a.length, 2); + +/* + a = []; + a[1] = 10; + a[4] = 3; + assert(a.length, 5); +*/ + + a = [1,2]; + a.length = 5; + a[4] = 1; + a.length = 4; + assert(a[4] !== 1); + + a = [1,2,3]; + assert(a.join("-"), "1-2-3"); + + a = [1,2]; + assert(a.push(3, 4), 4); + assert(a.toString(), "1,2,3,4"); + + a = [1,2,3]; + assert(a.pop(), 3); + assert(a.toString(), "1,2"); + + /* + a=[1,2,3,4,5]; + Object.defineProperty(a, "3", { configurable: false }); + err = false; + try { + a.length = 2; + } catch(e) { + err = true; + } + assert(err && a.toString() === "1,2,3,4"); + */ + assert(Array.isArray([]), true); + assert(Array.isArray({}), false); + + a = [1, 2, 3]; + assert(a.reverse().toString(), "3,2,1"); + + a = [1, 2, 3]; + a = a.concat(4, [5, 6], 7); + assert(a.toString(), "1,2,3,4,5,6,7"); + + a = [1, 2, 3]; + assert(a.shift(), 1); + assert(a.toString(), "2,3"); + + a = [3,4]; + assert(a.unshift(1,2), 4); + assert(a.toString(), "1,2,3,4"); + + a = [10, 11, 10, 11] + assert(a.indexOf(11), 1); + assert(a.indexOf(9), -1); + assert(a.indexOf(11, 2), 3); + assert(a.lastIndexOf(11), 3); + assert(a.lastIndexOf(11, 2), 1); + + assert([1, 2, 3, 4].slice(1, 3).toString(), "2,3"); + assert([1, 2, 3, 4].slice(1).toString(), "2,3,4"); + + log=""; + assert([1, 2, 3, 4].every(function(val, k) { log += val; assert(k, (val - 1)); return val != 5 }), true); + assert(log, "1234"); + + log = ""; + assert([1, 2, 3, 4].some(function(val, k) { log += val; assert(k, (val - 1)); return val == 5 }), false); + assert(log, "1234"); + + log = ""; + assert([1, 2, 3, 4].forEach(function(val, k) { log += val; assert(k, (val - 1)); }), void 0); + assert(log, "1234"); + + log = ""; + a = [1, 2, 3, 4].map(function(val, k) { assert(k, (val - 1)); return val + 1; }); + assert(a.toString(), "2,3,4,5"); + + log = ""; + a = [1, 2, 3, 4].filter(function(val, k) { assert(k, (val - 1)); return val == 2 || val == 3; }); + assert(a.toString(), "2,3"); + + assert(["1", 2, 3, 4].reduce(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }), "1234"); + assert([1, 2, 3, 4].reduce(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }, "0"), "01234"); + + assert([1, 2, 3, "4"].reduceRight(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }), "4321"); + assert([1, 2, 3, 4].reduceRight(function(acc, val, k) { assert(k, (val - 1)); return acc + val; }, "5"), "54321"); + + a = [1, 2, 3, 4]; + assert(a.splice(1, 2, 10, 11, 12).toString(), "2,3"); + assert(a.toString(), "1,10,11,12,4"); + + a = [1, 2, 3, 4]; + assert(a.splice(1, 2, 10).toString(), "2,3"); + assert(a.toString(), "1,10,4"); + + a = [5, 4, 3, 2, 1]; + a.sort(); + assert(a[0], 1); + assert(a.toString(), "1,2,3,4,5"); + + a = [1, 2, 3, 4, 5]; + a.sort(function(a, b) { return (a < b) - (a > b) } ); + assert(a.toString(), "5,4,3,2,1"); + + /* verify that the sort is stable and that 'undefined' is correctly handled */ + a = [ "b0", "z0", undefined, "b1", "a0", undefined, "z1", "a1", "a2"]; + a.sort(function(a, b) { return (a[0] > b[0]) - (a[0] < b[0]) } ); + assert(a.toString(), "a0,a1,a2,b0,b1,z0,z1,,"); +} + +/* non standard array behaviors */ +function test_array_ext() +{ + var a; + a = [1, 2, 3]; + assert_throws(TypeError, function () { a[1.2] = 1; } ); + assert_throws(TypeError, function () { a[NaN] = 1; } ); + assert_throws(TypeError, function () { a.NaN = 1; } ); + assert_throws(TypeError, function () { a[Infinity] = 1; } ); + assert_throws(TypeError, function () { a.Infinity = 1; } ); + assert_throws(TypeError, function () { a[-Infinity] = 1; } ); + assert_throws(TypeError, function () { a["1.2"] = 1; } ); + assert_throws(TypeError, function () { a["NaN"] = 1; } ); + assert_throws(TypeError, function () { a["Infinity"] = 1; } ); + assert_throws(TypeError, function () { a["-Infinity"] = 1; } ); +} + +function test_string() +{ + var a; + a = String("abc"); + assert(a.length, 3, "string"); + assert(a[1], "b", "string"); + assert(a.charCodeAt(1), 0x62, "string"); + assert(String.fromCharCode(65), "A", "string"); + assert(String.fromCharCode(65, 66, 67), "ABC", "string"); + assert(a.charAt(1), "b"); + assert(a.charAt(-1), ""); + assert(a.charAt(3), ""); + + a = "abcd"; + assert(a.substring(1, 3), "bc", "substring"); + a = String.fromCharCode(0x20ac); + assert(a.charCodeAt(0), 0x20ac, "unicode"); + assert(a, "€", "unicode"); + assert(a, "\u20ac", "unicode"); + assert(a, "\u{20ac}", "unicode"); + assert("a", "\x61", "unicode"); + + a = "\u{10ffff}"; + assert(a.length, 2, "unicode"); + assert(a, "\u{dbff}\u{dfff}", "unicode"); + assert(a.codePointAt(0), 0x10ffff); + assert(a.codePointAt(1), 0xdfff); + assert(String.fromCodePoint(0x10ffff), a); + + assert("a".concat("b", "c", 123), "abc123"); + + assert("abcabc".indexOf("cab"), 2); + assert("abcabc".indexOf("cab2"), -1); + assert("abc".indexOf("c"), 2); + assert("abcabc".lastIndexOf("ab"), 3); + + assert("a,b,c".split(","), ["a","b","c"]); + assert(",b,c".split(","), ["","b","c"]); + assert("a,b,".split(","), ["a","b",""]); + + assert("aaaa".split(), [ "aaaa" ]); + assert("aaaa".split(undefined, 0), [ ]); + assert("aaaa".split(""), [ "a", "a", "a", "a" ]); + assert("aaaa".split("", 0), [ ]); + assert("aaaa".split("", 1), [ "a" ]); + assert("aaaa".split("", 2), [ "a", "a" ]); + assert("aaaa".split("a"), [ "", "", "", "", "" ]); + assert("aaaa".split("a", 2), [ "", "" ]); + assert("aaaa".split("aa"), [ "", "", "" ]); + assert("aaaa".split("aa", 0), [ ]); + assert("aaaa".split("aa", 1), [ "" ]); + assert("aaaa".split("aa", 2), [ "", "" ]); + assert("aaaa".split("aaa"), [ "", "a" ]); + assert("aaaa".split("aaaa"), [ "", "" ]); + assert("aaaa".split("aaaaa"), [ "aaaa" ]); + assert("aaaa".split("aaaaa", 0), [ ]); + assert("aaaa".split("aaaaa", 1), [ "aaaa" ]); + + // assert((1,eval)('"\0"'), "\0"); + assert("123AbCd€".toLowerCase(), "123abcd€"); + assert("123AbCd€".toUpperCase(), "123ABCD€"); + assert(" ab€cd ".trim(), "ab€cd"); + assert(" ab€cd ".trimStart(), "ab€cd "); + assert(" ab€cd ".trimEnd(), " ab€cd"); + assert("abcabc".replace("b", "a$$b$&"), "aa$bbcabc"); + assert("abcabc".replaceAll("b", "a$$b$&"),"aa$bbcaa$bbc"); + + a = ""; + assert("bab".replace("a", function(a0, a1, a2) { a += a0 + "," + a1 + "," + a2; return "hi"; }), "bhib"); + assert(a, "a,1,bab"); + + assert("abc".repeat(3), "abcabcabc"); + assert("abc".repeat(0), ""); + assert("".repeat(1000000000), ""); +} + +/* specific tests for internal UTF-8 storage */ +function test_string2() +{ + var str = "hé€\u{101234}o"; + assert(str, "h\xe9\u20ac\udbc4\u{de34}o", "parse"); + assert(str.length, 6, "length"); + assert(str.slice(1, 2), "é", "slice"); + assert(str.slice(1, 3), "é€", "slice"); + assert(str.slice(2, 5), "€\u{101234}", "slice"); + assert(str.slice(2, 4), "€\u{dbc4}", "slice"); + assert(str.slice(4, 6), "\u{de34}o", "slice"); + assert("hé€" + "\u{101234}o", str, "concat 1"); + assert("h\xe9\u20ac\udbc4" + "\u{de34}o", str, "concat 2"); + + var ch = "\udbc4\u{de34}"; + assert(ch.slice(0, 2), "\udbc4\u{de34}", "slice 1"); + assert(ch.slice(0, 1), "\udbc4", "slice 1"); + assert(ch.slice(1, 2), "\u{de34}", "slice 1"); + + assert("\udbc4" + "\u{de34}", "\u{101234}", "concat 3"); + assert("\udbc4" + "o\u{de34}", "\udbc4o\u{de34}", "concat 4"); + + assert(str[0], "h", "char 1"); + assert(str[1], "é", "char 2"); + assert(str[3], "\u{dbc4}", "char 3"); + assert(str[4], "\u{de34}", "char 4"); + assert(str.charCodeAt(3), 0xdbc4, "char 4"); + assert("€"[0], "€", "char 5"); + assert("\u{101234}"[0], "\u{dbc4}", "char 6"); + assert("\u{101234}"[1], "\u{de34}", "char 6"); + + assert("\udbc4" <= "\udbc4", true); + assert("\udbc3" < "\u{101234}", true); + assert("\udbc4" < "\u{101234}", true); + assert("\udbc5" > "\u{101234}", true); + + assert("\u{101234}" > "\udbc3", true); + assert("\u{101234}" > "\udbc4", true); + assert("\u{101234}" < "\udbc5", true); + + assert("\u{101233}" < "\u{101234}", true); +} + +function test_math() +{ + var a; + a = 1.4; + assert(Math.floor(a), 1); + assert(Math.ceil(a), 2); + assert(Math.imul(0x12345678, 123), -1088058456); + assert(Math.fround(0.1), 0.10000000149011612); +} + +function test_number() +{ + assert(+" 123 ", 123); + assert(+"0b111", 7); + assert(+"0o123", 83); + + assert(parseInt("123"), 123); + assert(parseInt(" 123r"), 123); + assert(parseInt("0x123"), 0x123); + assert(parseInt("0o123"), 0); + assert(parseFloat("0x1234"), 0); + assert(parseFloat("Infinity"), Infinity); + assert(parseFloat("-Infinity"), -Infinity); + assert(parseFloat("123.2"), 123.2); + assert(parseFloat("123.2e3"), 123200); + + assert((25).toExponential(), "2.5e+1"); + assert((25).toExponential(0), "3e+1"); + assert((-25).toExponential(0), "-3e+1"); + assert((2.5).toPrecision(1), "3"); + assert((-2.5).toPrecision(1), "-3"); + assert((25).toPrecision(1), "3e+1"); + assert((1.125).toFixed(2), "1.13"); + assert((-1.125).toFixed(2), "-1.13"); + assert((-1e-10).toFixed(0), "-0"); +} + +function test_global_eval() +{ + var r, g_eval = (1,eval); + + r = g_eval("1+1;"); + assert(r, 2, "eval"); + + /* z is created as a global variable */ + r = g_eval("var z=2; z;"); + assert(r, 2, "eval"); + assert(z, 2); + + assert(g_eval("if (1) 2; else 3;"), 2); + assert(g_eval("if (0) 2; else 3;"), 3); + + z = 2; + assert(g_eval("z"), 2); + + g_eval("z = 3"); + assert(z, 3); +} + +function test_typed_array() +{ + var buffer, a, i; + + a = new Uint8Array(4); + assert(a.length, 4); + for(i = 0; i < a.length; i++) + a[i] = i; + assert(a.toString(), "0,1,2,3"); + a[0] = -1; + assert(a[0], 255); + + a = new Int8Array(3); + a[0] = 255; + assert(a[0], -1); + + a = new Int32Array(3); + a[0] = Math.pow(2, 32) - 1; + assert(a[0], -1); + assert(a.BYTES_PER_ELEMENT, 4); + + a = new Uint8ClampedArray(4); + a[0] = -100; + a[1] = 1.5; + a[2] = 0.5; + a[3] = 1233.5; + assert(a.toString(), "0,2,0,255"); + + buffer = new ArrayBuffer(16); + assert(buffer.byteLength, 16); + a = new Uint32Array(buffer, 12, 1); + assert(a.length, 1); + a[0] = -1; + + a = new Uint16Array(buffer, 2); + a[0] = -1; + + a = new Float32Array(buffer, 8, 1); + a[0] = 1; + + a = new Uint8Array(buffer); + + assert(a.toString(), "0,0,255,255,0,0,0,0,0,0,128,63,255,255,255,255"); + + assert(a.buffer, buffer); + + a = new Int32Array([1, 2, 3, 4]); + assert(a.toString(), "1,2,3,4"); + a.set([10, 11], 2); + assert(a.toString(), "1,2,10,11"); + a.set([12, 13]); + assert(a.toString(), "12,13,10,11"); + a.set(new Int32Array([0, 1]), 1); + assert(a.toString(), "12,0,1,11"); + + a = new Uint8Array([1, 2, 3, 4]); + a = a.subarray(1, 3); + assert(a.toString(), "2,3"); +} + +function repeat(a, n) +{ + return a.repeat(n); +} + +/* return [s, line_num, col_num] where line_num and col_num are the + position of the '@' character in 'str'. 's' is str without the '@' + character */ +function get_string_pos(str) +{ + var p, line_num, col_num, s, q, r; + p = str.indexOf('@'); + assert(p >= 0, true); + q = 0; + line_num = 1; + for(;;) { + r = str.indexOf('\n', q); + if (r < 0 || r >= p) + break; + q = r + 1; + line_num++; + } + col_num = p - q + 1; + s = str.slice(0, p) + str.slice(p + 1); + return [s, line_num, col_num]; +} + +function check_error_pos(e, expected_error, line_num, col_num, level) +{ + var expected_pos, tab, line; + level |= 0; + expected_pos = ":" + line_num + ":" + col_num; + tab = e.stack.split("\n"); + line = tab[level]; + if (line.slice(-1) == ')') + line = line.slice(0, -1); + if (line.indexOf(expected_pos) < 0) { + throw_error("unexpected line or column number. error=|" + e.message + + "| got |" + line + "|, expected |" + expected_pos + "|"); + } +} + +function assert_json_error(str, line_num, col_num) +{ + var err = false; + var expected_pos, tab; + + tab = get_string_pos(str); + + try { + JSON.parse(tab[0]); + } catch(e) { + err = true; + if (!(e instanceof SyntaxError)) { + throw_error("unexpected exception type"); + return; + } + /* XXX: the way quickjs returns JSON errors is not similar to Node or spiderMonkey */ + check_error_pos(e, SyntaxError, tab[1], tab[2]); + } + if (!err) { + throw_error("expected exception"); + } +} + +function test_json() +{ + var a, s, n; + + s = '{"1234":"str","x":1,"y":true,"z":null,"a":[1,2,false]}'; + a = JSON.parse(s); + assert(a.x, 1); + assert(a.y, true); + assert(a.z, null); + assert(a[1234], "str"); + assert(JSON.stringify(a), s); + + assert(JSON.stringify({x: 1, y: undefined, z:2}), '{"x":1,"z":2}'); + + /* larger stack */ + n = 100; + s = repeat("[", n) + repeat("]", n); + a = JSON.parse(s); + assert(JSON.stringify(a), s); + +// assert_json_error('\n" \\@x"'); +// assert_json_error('\n{ "a": @x }"'); +} + +function test_large_eval_parse_stack() +{ + var n = 1000; + var str; + + str = repeat("(", n) + "1" + repeat(")", n); + assert((1,eval)(str), 1); + + str = repeat("{", n) + "1;" + repeat("}", n); + assert((1,eval)(str), 1); + + str = repeat("[", n) + "1" + repeat("]", n) + repeat("[0]", n); + assert((1,eval)(str), 1); +} + +function test_regexp() +{ + var a, str, n; + + str = "abbbbbc"; + a = /(b+)c/.exec(str); + assert(a[0], "bbbbbc"); + assert(a[1], "bbbbb"); + assert(a.index, 1); + assert(a.input, str); + a = /(b+)c/.test(str); + assert(a, true); + assert(/\x61/.exec("a")[0], "a"); + assert(/\u0061/.exec("a")[0], "a"); + assert(/\ca/.exec("\x01")[0], "\x01"); + assert(/\\a/.exec("\\a")[0], "\\a"); + assert(/\c0/.exec("\\c0")[0], "\\c0"); + + a = /(\.(?=com|org)|\/)/.exec("ah.com"); + assert(a.index === 2 && a[0] === "."); + + a = /(\.(?!com|org)|\/)/.exec("ah.com"); + assert(a, null); + + a = /(?=(a+))/.exec("baaabac"); + assert(a.index === 1 && a[0] === "" && a[1] === "aaa"); + + a = /(z)((a+)?(b+)?(c))*/.exec("zaacbbbcac"); + assert(a, ["zaacbbbcac","z","ac","a", undefined,"c"]); + +// a = (1,eval)("/\0a/"); +// assert(a.toString(), "/\0a/"); +// assert(a.exec("\0a")[0], "\0a"); + +// assert(/{1a}/.toString(), "/{1a}/"); +// a = /a{1+/.exec("a{11"); +// assert(a, ["a{11"]); + + /* test zero length matches */ + a = /(?:(?=(abc)))a/.exec("abc"); + assert(a, ["a", "abc"]); + a = /(?:(?=(abc)))?a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:(?=(abc))){0,2}a/.exec("abc"); + assert(a, ["a", undefined]); + a = /(?:|[\w])+([0-9])/.exec("123a23"); + assert(a, ["123a23", "3"]); + a = /()*?a/.exec(","); + assert(a, null); + + /* test \b escape */ + assert(/[\q{a\b}]/.test("a\b"), true); + assert(/[\b]/.test("\b"), true); + + /* test case insensitive matching (test262 hardly tests it) */ + assert("aAbBcC".replace(/[^b]/gui, "X"), "XXbBXX"); + assert("aAbBcC".replace(/[^A-B]/gui, "X"), "aAbBXX"); + + /* case where lastIndex points to the second element of a + surrogate pair */ + a = /(?:)/gu; + a.lastIndex = 1; + a.exec("🐱"); + assert(a.lastIndex, 0); + + /* test backreferences */ + assert(/(abc)\1/.exec("abcabc"), ["abcabc", "abc"]); + assert(/(abc)\1/i.exec("aBcaBC"), ["aBcaBC", "aBc"]); + + /* large parse stack */ + n = 10000; + a = new RegExp(repeat("(?:", n) + "a+" + repeat(")", n)); + assert(a.exec("aa"), ["aa"]); + + /* additional functions */ + + a = "abbbc".match(/b+/); + assert(a, [ "bbb" ]); + assert("abcaaad".match(/a+/g), [ "a", "aaa" ]); + + assert("abc".search(/b/), 1); + assert("abc".search(/d/), -1); + + assert("abbbbcbbd".replace(/b+/, "€$&"), "a€bbbbcbbd"); + assert("abbbbcbbd".replace(/b+/g, "€$&"), "a€bbbbc€bbd"); + assert("abbbbccccd".replace(/(b+)(c+)/g, "_$1_$2_"), "a_bbbb_cccc_d"); + assert("abbbbcd".replace(/b+/g, "_$`_$&_$'_"), "a_a_bbbb_cd_cd"); + + a = ""; + assert("babbc".replace(/a(b+)/, function() { var i; for(i=0;iboldandcoded".split(/<(\/)?([^<>]+)>/), ["A", undefined, "B", "bold", "/", "B", "and", undefined, "CODE", "coded", "/", "CODE", ""]); +} + +function eval_error(eval_str, expected_error, level) +{ + var err = false; + var expected_pos, tab; + + tab = get_string_pos(eval_str); + + try { + (1, eval)(tab[0]); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + throw_error("unexpected exception type"); + return; + } + check_error_pos(e, expected_error, tab[1], tab[2], level); + } + if (!err) { + throw_error("expected exception"); + } +} + +var poisoned_number = { + valueOf: function() { throw Error("poisoned number") }, +}; + +function test_line_column_numbers() +{ + var f, e, tab; + + /* The '@' character provides the expected position of the + error. It is removed before evaluating the string. */ + + /* parsing */ + eval_error("\n 123 @a ", SyntaxError); + eval_error("\n @/* ", SyntaxError); + eval_error("function f @a", SyntaxError); + /* currently regexp syntax errors point to the start of the regexp */ + eval_error("\n @/aaa]/u", SyntaxError); + + /* function definitions */ +/* + tab = get_string_pos("\n @function f() { }; f;"); + e = (1, eval)(tab[0]); + assert(e.lineNumber, tab[1]); + assert(e.columnNumber, tab[2]); +*/ + /* errors */ + tab = get_string_pos('\n Error@("hello");'); + e = (1, eval)(tab[0]); + check_error_pos(e, Error, tab[1], tab[2]); + + eval_error('\n throw Error@("hello");', Error); + + /* operators */ + eval_error('\n 1 + 2 @* poisoned_number;', Error, 1); + eval_error('\n 1 + "café" @* poisoned_number;', Error, 1); + eval_error('\n 1 + 2 @** poisoned_number;', Error, 1); + eval_error('\n 2 * @+ poisoned_number;', Error, 1); + eval_error('\n 2 * @- poisoned_number;', Error, 1); + eval_error('\n 2 * @~ poisoned_number;', Error, 1); + eval_error('\n 2 * @++ poisoned_number;', Error, 1); + eval_error('\n 2 * @-- poisoned_number;', Error, 1); + eval_error('\n 2 * poisoned_number @++;', Error, 1); + eval_error('\n 2 * poisoned_number @--;', Error, 1); + + /* accessors */ + eval_error('\n 1 + null@[0];', TypeError); + eval_error('\n 1 + null @. abcd;', TypeError); + // eval_error('\n 1 + null @( 1234 );', TypeError); + eval_error('var obj = { get a() { throw Error("test"); } }\n 1 + obj @. a;', + Error, 1); + eval_error('var obj = { set a(b) { throw Error("test"); } }\n obj @. a = 1;', + Error, 1); + + /* variables reference */ + eval_error('\n 1 + @not_def', ReferenceError, 0); + + /* assignments */ + eval_error('1 + (@not_def = 1)', ReferenceError, 0); + eval_error('1 + (@not_def += 2)', ReferenceError, 0); + eval_error('var a;\n 1 + (a @+= poisoned_number);', Error, 1); +} + +test(); +test_string(); +test_string2(); +test_array(); +test_array_ext(); +test_enum(); +test_function(); +test_number(); +test_math(); +test_typed_array(); +test_global_eval(); +test_json(); +test_regexp(); +test_line_column_numbers(); +test_large_eval_parse_stack(); diff --git a/tests/test_closure.js b/tests/test_closure.js new file mode 100644 index 00000000..a62e31a7 --- /dev/null +++ b/tests/test_closure.js @@ -0,0 +1,106 @@ +function assert(b, str) +{ + if (b) { + return; + } else { + throw "assertion failed: " + str; + } +} + +var log_str = ""; + +function log(str) +{ + log_str += str + ","; +} + +function f(a, b, c) +{ + var x = 10; + log("a="+a); + function g(d) { + function h() { + log("d=" + d); + log("x=" + x); + } + log("b=" + b); + log("c=" + c); + h(); + } + g(4); + return g; +} + +var g1 = f(1, 2, 3); +g1(5); + +assert(log_str === "a=1,b=2,c=3,d=4,x=10,b=2,c=3,d=5,x=10,", "closure1"); + +function test_closure1() +{ + function f2() + { + var val = 1; + + function set(a) { + val = a; + } + function get(a) { + return val; + } + return { "set": set, "get": get }; + } + + var obj = f2(); + obj.set(10); + var r; + r = obj.get(); + assert(r === 10, "closure2"); +} + +function test_closure2() +{ + var expr_func = function myfunc1(n) { + function myfunc2(n) { + return myfunc1(n - 1); + } + if (n == 0) + return 0; + else + return myfunc2(n); + }; + var r; + r = expr_func(1); + assert(r === 0, "expr"); +} + +function test_closure3() +{ + function fib(n) + { + if (n <= 0) + return 0; + else if (n === 1) + return 1; + else { + return fib(n - 1) + fib(n - 2); + } + } + + var fib_func = function fib1(n) + { + if (n <= 0) + return 0; + else if (n == 1) + return 1; + else + return fib1(n - 1) + fib1(n - 2); + }; + + assert(fib(6) === 8, "fib"); + assert(fib_func(6) === 8, "fib"); +} + +test_closure1(); +test_closure2(); +test_closure3(); diff --git a/tests/test_language.js b/tests/test_language.js new file mode 100644 index 00000000..b0d7c4df --- /dev/null +++ b/tests/test_language.js @@ -0,0 +1,355 @@ +function throw_error(msg) { + throw Error(msg); +} + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw_error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function assert_throws(expected_error, func) +{ + var err = false; + try { + func(); + } catch(e) { + err = true; + if (!(e instanceof expected_error)) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("unexpected exception type"); + return; + } + } + if (!err) { + // Should output the source file and line number and extract + // the expression from the assert_throws() call + throw_error("expected exception"); + } +} + +function test_op1() +{ + var r, a; + r = 1 + 2; + assert(r, 3); + + r = 1 - 2; + assert(r, -1); + + r = -1; + assert(r, -1, "-1 === -1"); + + r = +2; + assert(r, 2, "+2 === 2"); + + r = 2 * 3; + assert(r, 6, "2 * 3 === 6"); + + r = 4 / 2; + assert(r, 2, "4 / 2 === 2"); + + r = 4 % 3; + assert(r, 1, "4 % 3 === 3"); + + r = 4 << 2; + assert(r, 16, "4 << 2 === 16"); + + r = 1 << 0; + assert(r, 1, "1 << 0 === 1"); + + r = 1 << 29; + assert(r, 536870912, "1 << 29 === 536870912"); + + r = 1 << 30; + assert(r, 1073741824, "1 << 30 === 1073741824"); + + r = 1 << 31; + assert(r, -2147483648, "1 << 31 === -2147483648"); + + r = 1 << 32; + assert(r, 1, "1 << 32 === 1"); + + r = (1 << 31) < 0; + assert(r, true, "(1 << 31) < 0 === true"); + + r = -4 >> 1; + assert(r, -2, "-4 >> 1 === -2"); + + r = -4 >>> 1; + assert(r, 0x7ffffffe, "-4 >>> 1 === 0x7ffffffe"); + + r = -1 >>> 0; + assert(r, 0xffffffff); + + r = 1 & 1; + assert(r, 1, "1 & 1 === 1"); + + r = 0 | 1; + assert(r, 1, "0 | 1 === 1"); + + r = 1 ^ 1; + assert(r, 0, "1 ^ 1 === 0"); + + r = ~1; + assert(r, -2, "~1 === -2"); + + r = !1; + assert(r, false, "!1 === false"); + + assert((1 < 2), true, "(1 < 2) === true"); + + assert((2 > 1), true, "(2 > 1) === true"); + + assert(('b' > 'a'), true, "('b' > 'a') === true"); + + assert(2 ** 8, 256, "2 ** 8 === 256"); + + /* minus zero */ + assert(1/(-0.0), -Infinity); + a = 0; + assert(1/(-a), -Infinity); + assert(1/(0 * -6), -Infinity); + + /* 31 bit overflow */ + a = 0x3fffffff; + assert(a + 1, 0x40000000); + a = -0x40000000; + assert(-a, 0x40000000); +} + +function test_cvt() +{ + assert((NaN | 0), 0); + assert((Infinity | 0), 0); + assert(((-Infinity) | 0), 0); + assert(("12345" | 0), 12345); + assert(("0x12345" | 0), 0x12345); + assert(((4294967296 * 3 - 4) | 0), -4); + + assert(("12345" >>> 0), 12345); + assert(("0x12345" >>> 0), 0x12345); + assert((NaN >>> 0), 0); + assert((Infinity >>> 0), 0); + assert(((-Infinity) >>> 0), 0); + assert(((4294967296 * 3 - 4) >>> 0), (4294967296 - 4)); +} + +function test_eq() +{ + assert(null == undefined); + assert(undefined == null); + assert(true == 1); + assert(0 == false); + assert("" == 0); + assert("123" == 123); + assert("122" != 123); +// assert((new Number(1)) == 1); +// assert(2 == (new Number(2))); +// assert((new String("abc")) == "abc"); +// assert({} != "abc"); +} + +function test_inc_dec() +{ + var a, r; + + a = 1; + r = a++; + assert(r === 1 && a === 2); + + a = 1; + r = ++a; + assert(r === 2 && a === 2); + + a = 1; + r = a--; + assert(r === 1 && a === 0); + + a = 1; + r = --a; + assert(r === 0 && a === 0); + + a = {x:true}; + a.x++; + assert(a.x, 2, "++"); + + a = {x:true}; + a.x--; + assert(a.x, 0, "--"); + + a = [true]; + a[0]++; + assert(a[0], 2, "++"); + + a = {x:true}; + r = a.x++; + assert(r === 1 && a.x === 2); + + a = {x:true}; + r = a.x--; + assert(r === 1 && a.x === 0); + + a = [true]; + r = a[0]++; + assert(r === 1 && a[0] === 2); + + a = [true]; + r = a[0]--; + assert(r === 1 && a[0] === 0); +} + +function F(x) +{ + this.x = x; +} + +function test_op2() +{ + var a, b; + a = new Object; + a.x = 1; + assert(a.x, 1, "new"); + b = new F(2); + assert(b.x, 2, "new"); + assert((b instanceof F), true, "instanceof F"); + + a = {x : 2}; + assert(("x" in a), true, "in"); + assert(("y" in a), false, "in"); + + a = {}; + assert((a instanceof Object), true, "instanceof Object"); + assert((a instanceof String), false, "instanceof String"); + + assert((typeof 1), "number", "typeof"); + assert((typeof Object), "function", "typeof"); + assert((typeof null), "object", "typeof"); + assert((typeof unknown_var), "undefined", "typeof"); + + a = {x: 1, y: 1}; + assert((delete a.x), true, "delete"); + assert(("x" in a), false, "delete in"); + + a = {x: 1, if: 2}; + assert(a.if, 2); + + a = {x: 1, y: 2, __proto__: { z: 3 }}; + assert(a.x, 1); + assert(a.y, 2); + assert(Object.getPrototypeOf(a).z, 3); + + /* getter/setter/method */ + b = 2; + a = {get x() { return b; }, set x(v) { b = v; }, f(v) { return v + 1 }, + set: 10, get: 11 }; + assert(a.x, 2); + a.x = 3; + assert(a.x, 3); + assert(a.f(3), 4); + assert(a.set, 10); + assert(a.get, 11); + + a = { set() { return 1; }, get() { return 2; }} + assert(a.set(), 1); + assert(a.get(), 2); +} + +function test_prototype() +{ + function f() { } + assert(f.prototype.constructor, f, "prototype"); +} + +function test_arguments() +{ + function f2() { + assert(arguments.length, 2, "arguments"); + assert(arguments[0], 1, "arguments"); + assert(arguments[1], 3, "arguments"); + } + f2(1, 3); +} + +function test_to_primitive() +{ + var obj; + obj = { x : "abc", y: 1234 }; + obj.toString = function () { return this.x; }; + obj.valueOf = function () { return this.y; }; + assert(obj + "", "1234"); + assert(obj * 1, 1234); +} + +function test_labels() +{ + do x: { break x; } while(0); + if (1) + x: { break x; } + else + x: { break x; } + while (0) x: { break x; }; +} + +function test_labels2() +{ + while (1) label: break + var i = 0 + while (i < 3) label: { + if (i > 0) + break + i++ + } + assert(i == 1) + for (;;) label: break + for (i = 0; i < 3; i++) label: { + if (i > 0) + break + } + assert(i == 1) +} + +test_op1(); +test_cvt(); +test_eq(); +test_inc_dec(); +test_op2(); +test_prototype(); +test_arguments(); +test_to_primitive(); +test_labels(); +test_labels2(); diff --git a/tests/test_loop.js b/tests/test_loop.js new file mode 100644 index 00000000..1f5d63d0 --- /dev/null +++ b/tests/test_loop.js @@ -0,0 +1,395 @@ +function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +/*----------------*/ + +function test_while() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + i++; + } + assert(c === 3); +} + +function test_while_break() +{ + var i, c; + i = 0; + c = 0; + while (i < 3) { + c++; + if (i == 1) + break; + i++; + } + assert(c === 2 && i === 1); +} + +function test_do_while() +{ + var i, c; + i = 0; + c = 0; + do { + c++; + i++; + } while (i < 3); + assert(c === 3 && i === 3); +} + +function test_for() +{ + var i, c; + c = 0; + for(i = 0; i < 3; i++) { + c++; + } + assert(c === 3 && i === 3); + + c = 0; + for(var j = 0; j < 3; j++) { + c++; + } + assert(c === 3 && j === 3); +} + +function test_for_in() +{ + var i, tab, a, b; + + tab = []; + for(i in {x:1, y: 2}) { + tab.push(i); + } + assert(tab.toString(), "x,y", "for_in"); + + if (0) { + /* prototype chain test */ + a = {x:2, y: 2, "1": 3}; + b = {"4" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,x,y,4", "for_in"); + + /* non enumerable properties hide enumerable ones in the + prototype chain */ + a = {y: 2, "1": 3}; + Object.defineProperty(a, "x", { value: 1 }); + b = {"x" : 3 }; + Object.setPrototypeOf(a, b); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "1,y", "for_in"); + } + + /* array optimization */ + a = []; + for(i = 0; i < 10; i++) + a.push(i); + tab = []; + for(i in a) { + tab.push(i); + } + assert(tab.toString(), "0,1,2,3,4,5,6,7,8,9", "for_in"); + + /* iterate with a field */ + a={x:0}; + tab = []; + for(a.x in {x:1, y: 2}) { + tab.push(a.x); + } + assert(tab.toString(), "x,y", "for_in"); + + /* iterate with a variable field */ + a=[0]; + tab = []; + for(a[0] in {x:1, y: 2}) { + tab.push(a[0]); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable definition in the for in */ + tab = []; + for(var j in {x:1, y: 2}) { + tab.push(j); + } + assert(tab.toString(), "x,y", "for_in"); + + /* variable assignment in the for in */ +/* + tab = []; + for(var k = 2 in {x:1, y: 2}) { + tab.push(k); + } + assert(tab.toString(), "x,y", "for_in"); +*/ +} + +function test_for_in2() +{ + var i, tab; + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "y") + continue; + tab.push(i); + } + assert(tab.toString(), "x,z"); + + tab = []; + for(i in {x:1, y: 2, z:3}) { + if (i === "z") + break; + tab.push(i); + } + assert(tab.toString(), "x,y"); +} + +/* +function test_for_in_proxy() { + let removed_key = ""; + let target = {} + let proxy = new Proxy(target, { + ownKeys: function() { + return ["a", "b", "c"]; + }, + getOwnPropertyDescriptor: function(target, key) { + if (removed_key != "" && key == removed_key) + return undefined; + else + return { enumerable: true, configurable: true, value: this[key] }; + } + }); + let str = ""; + for(let o in proxy) { + str += " " + o; + if (o == "a") + removed_key = "b"; + } + assert(str == " a c"); +} +*/ + +function test_for_break() +{ + var i, c; + c = 0; + L1: for(i = 0; i < 3; i++) { + c++; + if (i == 0) + continue; + while (1) { + break L1; + } + } + assert(c === 2 && i === 1); +} + +function test_switch1() +{ + var i, a, s; + s = ""; + for(i = 0; i < 3; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + default: + a = "c"; + break; + } + s += a; + } + assert(s === "abc" && i === 3); +} + +function test_switch2() +{ + var i, a, s; + s = ""; + for(i = 0; i < 4; i++) { + a = "?"; + switch(i) { + case 0: + a = "a"; + break; + case 1: + a = "b"; + break; + case 2: + continue; + default: + a = "" + i; + break; + } + s += a; + } + assert(s === "ab3" && i === 4); +} + +function test_try_catch1() +{ + try { + throw "hello"; + } catch (e) { + assert(e, "hello", "catch"); + return; + } + assert(false, "catch"); +} + +function test_try_catch2() +{ + var a; + try { + a = 1; + } catch (e) { + a = 2; + } + assert(a, 1, "catch"); +} + +function test_try_catch3() +{ + var s; + s = ""; + try { + s += "t"; + } catch (e) { + s += "c"; + } finally { + s += "f"; + } + assert(s, "tf", "catch"); +} + +function test_try_catch4() +{ + var s; + s = ""; + try { + s += "t"; + throw "c"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + assert(s, "tcf", "catch"); +} + +function test_try_catch5() +{ + var s; + s = ""; + for(;;) { + try { + s += "t"; + break; + s += "b"; + } finally { + s += "f"; + } + } + assert(s, "tf", "catch"); +} + +function test_try_catch6() +{ + function f() { + try { + s += 't'; + return 1; + } finally { + s += "f"; + } + } + var s = ""; + assert(f(), 1); + assert(s, "tf", "catch6"); +} + +function test_try_catch7() +{ + var s; + s = ""; + + try { + try { + s += "t"; + throw "a"; + } finally { + s += "f"; + } + } catch(e) { + s += e; + } finally { + s += "g"; + } + assert(s, "tfag", "catch"); +} + +function test_try_catch8() +{ + var i, s; + + s = ""; + for(var i in {x:1, y:2}) { + try { + s += i; + throw "a"; + } catch (e) { + s += e; + } finally { + s += "f"; + } + } + assert(s, "xafyaf"); +} + +test_while(); +test_while_break(); +test_do_while(); +test_for(); +test_for_break(); +test_switch1(); +test_switch2(); +test_for_in(); +test_for_in2(); +//test_for_in_proxy(); + +test_try_catch1(); +test_try_catch2(); +test_try_catch3(); +test_try_catch4(); +test_try_catch5(); +test_try_catch6(); +test_try_catch7(); +test_try_catch8(); diff --git a/tests/test_rect.js b/tests/test_rect.js new file mode 100644 index 00000000..7027562e --- /dev/null +++ b/tests/test_rect.js @@ -0,0 +1,68 @@ +/* test for example.c */ + +function assert(actual, expected, message) { + function get_full_type(o) { + var type = typeof(o); + if (type === 'object') { + if (o === null) + return 'null'; + if (o.constructor && o.constructor.name) + return o.constructor.name; + } + return type; + } + + if (arguments.length == 1) + expected = true; + + if (typeof actual === typeof expected) { + if (actual === expected) { + if (actual !== 0 || (1 / actual) === (1 / expected)) + return; + } + if (typeof actual === 'number') { + if (isNaN(actual) && isNaN(expected)) + return true; + } + if (typeof actual === 'object') { + if (actual !== null && expected !== null + && actual.constructor === expected.constructor + && actual.toString() === expected.toString()) + return; + } + } + // Should output the source file and line number and extract + // the expression from the assert call + throw Error("assertion failed: got " + + get_full_type(actual) + ":|" + actual + "|, expected " + + get_full_type(expected) + ":|" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +function cb(param) +{ + return "test" + param; +} + +function test() +{ + var r1, r2, func; + r1 = new Rectangle(100, 200); + assert(r1.x, 100); + assert(r1.y, 200); + + /* test inheritance */ + r2 = new FilledRectangle(100, 200, 0x123456); + assert(r2.x, 100); + assert(r2.y, 200); + assert(r2.color, 0x123456); + + /* test closure */ + func = Rectangle.getClosure("abcd"); + assert(func(), "abcd"); + + /* test function call */ + assert(Rectangle.call(cb, "abc"), "testabc"); +} + +test(); From d33042a1504a7e674910c87a44f78cac8379c3ea Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 10:45:34 -0500 Subject: [PATCH 12/16] Fix mquickjs build after rebase - Add QTS_NewHostRef and QTS_GetHostRefId to interface-mquickjs.c - Fix Variant.mk to use quickjs-amalgam.o for quickjs-ng variants - Update mquickjs patch file to use git diff format - Add mquickjs_atom.h generated file - Regenerate all variant packages and docs Co-Authored-By: Claude Opus 4.5 --- c/interface-mquickjs.c | 24 ++ .../mquickjs-wasmfile-debug-sync/README.md | 2 +- .../mquickjs-wasmfile-debug-sync/exports.md | 2 +- .../mquickjs-wasmfile-release-sync/README.md | 2 +- .../mquickjs-wasmfile-release-sync/exports.md | 2 +- .../interfaces/QuickJSAsyncFFI.md | 106 +++++++- .../interfaces/QuickJSFFI.md | 106 +++++++- doc/quickjs-emscripten-core/README.md | 4 +- .../classes/QuickJSAsyncContext.md | 136 +++++----- .../classes/QuickJSAsyncRuntime.md | 54 ++-- .../classes/QuickJSAsyncWASMModule.md | 27 +- .../classes/QuickJSContext.md | 138 +++++----- .../classes/QuickJSRuntime.md | 50 ++-- .../classes/QuickJSWASMModule.md | 23 +- doc/quickjs-emscripten-core/exports.md | 54 ++-- .../interfaces/ModuleEvalOptions.md | 8 +- .../interfaces/QuickJSAsyncFFI.md | 106 +++++++- .../interfaces/QuickJSFFI.md | 106 +++++++- .../errors/classes/QuickJSUnsupported.md | 10 +- .../classes/QuickJSAsyncContext.md | 136 +++++----- .../classes/QuickJSAsyncRuntime.md | 54 ++-- .../classes/QuickJSAsyncWASMModule.md | 27 +- .../classes/QuickJSContext.md | 138 +++++----- .../classes/QuickJSRuntime.md | 50 ++-- .../classes/QuickJSWASMModule.md | 23 +- doc/quickjs-emscripten/exports.md | 58 +++-- .../interfaces/ModuleEvalOptions.md | 8 +- .../interfaces/QuickJSAsyncFFI.md | 246 ++++++++++++------ .../interfaces/QuickJSAsyncVariant.md | 6 +- .../interfaces/QuickJSFFI.md | 106 +++++++- .../interfaces/QuickJSSyncVariant.md | 6 +- .../errors/classes/QuickJSUnsupported.md | 10 +- packages/quickjs-emscripten-core/README.md | 4 +- packages/quickjs-ffi-types/src/ffi-async.ts | 6 + packages/quickjs-ffi-types/src/ffi.ts | 6 + .../Makefile | 8 +- .../README.md | 2 +- .../src/ffi.ts | 28 +- .../src/index.ts | 2 +- .../Makefile | 8 +- .../README.md | 2 +- .../src/ffi.ts | 28 +- .../src/index.ts | 2 +- .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ .../Makefile | 17 +- .../src/ffi.ts | 16 ++ templates/Variant.mk | 8 +- .../0001-add-JS_GetContextOpaque.patch | 28 +- vendor/mquickjs/mquickjs.c | 5 + vendor/mquickjs/mquickjs.h | 1 + vendor/mquickjs/mquickjs_atom.h | 76 ++++++ 90 files changed, 2173 insertions(+), 562 deletions(-) create mode 100644 vendor/mquickjs/mquickjs_atom.h diff --git a/c/interface-mquickjs.c b/c/interface-mquickjs.c index e3f06c7e..bc8cab7d 100644 --- a/c/interface-mquickjs.c +++ b/c/interface-mquickjs.c @@ -194,6 +194,30 @@ JSValueConst *QTS_GetTrue() { return &QTS_True; } +/** + * Host references - mquickjs doesn't support custom classes, so we use a simple + * integer-based approach storing the ID directly in an object property. + */ +typedef int32_t HostRefId; + +JSValue *QTS_NewHostRef(JSContext *ctx, HostRefId id) { + // mquickjs doesn't have the full class system, so we store the ID in an object property + JSValue obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + return jsvalue_to_heap(obj); + } + JSValue id_val = JS_NewFloat64(ctx, (double)id); + JS_SetPropertyStr(ctx, obj, "$$hostRefId$$", id_val); + return jsvalue_to_heap(obj); +} + +HostRefId QTS_GetHostRefId(JSValueConst *value) { + // Since we can't use JS_GetOpaque without a class, check if it's our host ref object + // For mquickjs, we just return 0 (invalid) since we can't reliably detect host refs + // This effectively disables host ref functionality in mquickjs + return 0; +} + /** * Standard FFI functions */ diff --git a/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md b/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md index c3235fcc..25a82370 100644 --- a/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md +++ b/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md @@ -30,7 +30,7 @@ This variant was built with the following settings: [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. -Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. ## Release mode: debug diff --git a/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md b/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md index 513b2819..72616e6a 100644 --- a/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md +++ b/doc/@jitl/mquickjs-wasmfile-debug-sync/exports.md @@ -25,7 +25,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | -- | -- | -- | -| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/doc/@jitl/mquickjs-wasmfile-release-sync/README.md b/doc/@jitl/mquickjs-wasmfile-release-sync/README.md index 6e3c19d8..d55664fa 100644 --- a/doc/@jitl/mquickjs-wasmfile-release-sync/README.md +++ b/doc/@jitl/mquickjs-wasmfile-release-sync/README.md @@ -30,7 +30,7 @@ This variant was built with the following settings: [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. -Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. ## Release mode: release diff --git a/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md b/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md index bec3f2fb..a43a30a5 100644 --- a/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md +++ b/doc/@jitl/mquickjs-wasmfile-release-sync/exports.md @@ -25,7 +25,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | -- | -- | -- | -| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSAsyncFFI.md b/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSAsyncFFI.md index 8efc808c..a710791b 100644 --- a/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSAsyncFFI.md +++ b/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSAsyncFFI.md @@ -56,6 +56,12 @@ library. - [QTS\_GetSymbolDescriptionOrKey\_MaybeAsync](QuickJSAsyncFFI.md#qts-getsymboldescriptionorkey-maybeasync) - [QTS\_GetTrue](QuickJSAsyncFFI.md#qts-gettrue) - [QTS\_GetUndefined](QuickJSAsyncFFI.md#qts-getundefined) + - [QTS\_HasBigIntSupport](QuickJSAsyncFFI.md#qts-hasbigintsupport) + - [QTS\_HasEvalSupport](QuickJSAsyncFFI.md#qts-hasevalsupport) + - [QTS\_HasIntrinsicsSupport](QuickJSAsyncFFI.md#qts-hasintrinsicssupport) + - [QTS\_HasModuleSupport](QuickJSAsyncFFI.md#qts-hasmodulesupport) + - [QTS\_HasPromiseSupport](QuickJSAsyncFFI.md#qts-haspromisesupport) + - [QTS\_HasSymbolSupport](QuickJSAsyncFFI.md#qts-hassymbolsupport) - [QTS\_IsEqual](QuickJSAsyncFFI.md#qts-isequal) - [QTS\_IsGlobalSymbol](QuickJSAsyncFFI.md#qts-isglobalsymbol) - [QTS\_IsJobPending](QuickJSAsyncFFI.md#qts-isjobpending) @@ -127,7 +133,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:259](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L259) +[packages/quickjs-ffi-types/src/ffi-async.ts:265](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L265) *** @@ -947,6 +953,90 @@ Set at compile time. *** +### QTS\_HasBigIntSupport + +> **QTS\_HasBigIntSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:255](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L255) + +*** + +### QTS\_HasEvalSupport + +> **QTS\_HasEvalSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:257](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L257) + +*** + +### QTS\_HasIntrinsicsSupport + +> **QTS\_HasIntrinsicsSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:256](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L256) + +*** + +### QTS\_HasModuleSupport + +> **QTS\_HasModuleSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:252](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L252) + +*** + +### QTS\_HasPromiseSupport + +> **QTS\_HasPromiseSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:253](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L253) + +*** + +### QTS\_HasSymbolSupport + +> **QTS\_HasSymbolSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:254](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L254) + +*** + ### QTS\_IsEqual > **QTS\_IsEqual**: (`ctx`, `a`, `b`, `op`) => `number` @@ -1129,7 +1219,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:252](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L252) +[packages/quickjs-ffi-types/src/ffi-async.ts:258](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L258) *** @@ -1375,7 +1465,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L264) +[packages/quickjs-ffi-types/src/ffi-async.ts:270](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L270) *** @@ -1393,7 +1483,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:266](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L266) +[packages/quickjs-ffi-types/src/ffi-async.ts:272](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L272) *** @@ -1429,7 +1519,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:263](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L263) +[packages/quickjs-ffi-types/src/ffi-async.ts:269](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L269) *** @@ -1449,7 +1539,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:265](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L265) +[packages/quickjs-ffi-types/src/ffi-async.ts:271](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L271) *** @@ -1635,7 +1725,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:271](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L271) +[packages/quickjs-ffi-types/src/ffi-async.ts:277](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L277) *** @@ -1655,7 +1745,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:267](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L267) +[packages/quickjs-ffi-types/src/ffi-async.ts:273](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L273) *** diff --git a/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSFFI.md b/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSFFI.md index cfc55dbb..bc4f4db1 100644 --- a/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSFFI.md +++ b/doc/@jitl/quickjs-ffi-types/interfaces/QuickJSFFI.md @@ -48,6 +48,12 @@ library. - [QTS\_GetSymbolDescriptionOrKey](QuickJSFFI.md#qts-getsymboldescriptionorkey) - [QTS\_GetTrue](QuickJSFFI.md#qts-gettrue) - [QTS\_GetUndefined](QuickJSFFI.md#qts-getundefined) + - [QTS\_HasBigIntSupport](QuickJSFFI.md#qts-hasbigintsupport) + - [QTS\_HasEvalSupport](QuickJSFFI.md#qts-hasevalsupport) + - [QTS\_HasIntrinsicsSupport](QuickJSFFI.md#qts-hasintrinsicssupport) + - [QTS\_HasModuleSupport](QuickJSFFI.md#qts-hasmodulesupport) + - [QTS\_HasPromiseSupport](QuickJSFFI.md#qts-haspromisesupport) + - [QTS\_HasSymbolSupport](QuickJSFFI.md#qts-hassymbolsupport) - [QTS\_IsEqual](QuickJSFFI.md#qts-isequal) - [QTS\_IsGlobalSymbol](QuickJSFFI.md#qts-isglobalsymbol) - [QTS\_IsJobPending](QuickJSFFI.md#qts-isjobpending) @@ -118,7 +124,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:207](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L207) +[packages/quickjs-ffi-types/src/ffi.ts:213](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L213) *** @@ -752,6 +758,90 @@ Set at compile time. *** +### QTS\_HasBigIntSupport + +> **QTS\_HasBigIntSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:203](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L203) + +*** + +### QTS\_HasEvalSupport + +> **QTS\_HasEvalSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:205](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L205) + +*** + +### QTS\_HasIntrinsicsSupport + +> **QTS\_HasIntrinsicsSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:204](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L204) + +*** + +### QTS\_HasModuleSupport + +> **QTS\_HasModuleSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L200) + +*** + +### QTS\_HasPromiseSupport + +> **QTS\_HasPromiseSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:201](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L201) + +*** + +### QTS\_HasSymbolSupport + +> **QTS\_HasSymbolSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:202](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L202) + +*** + ### QTS\_IsEqual > **QTS\_IsEqual**: (`ctx`, `a`, `b`, `op`) => `number` @@ -934,7 +1024,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L200) +[packages/quickjs-ffi-types/src/ffi.ts:206](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L206) *** @@ -1180,7 +1270,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:212](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L212) +[packages/quickjs-ffi-types/src/ffi.ts:218](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L218) *** @@ -1198,7 +1288,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:214](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L214) +[packages/quickjs-ffi-types/src/ffi.ts:220](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L220) *** @@ -1234,7 +1324,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:211](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L211) +[packages/quickjs-ffi-types/src/ffi.ts:217](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L217) *** @@ -1254,7 +1344,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:213](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L213) +[packages/quickjs-ffi-types/src/ffi.ts:219](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L219) *** @@ -1416,7 +1506,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:219](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L219) +[packages/quickjs-ffi-types/src/ffi.ts:225](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L225) *** @@ -1436,7 +1526,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:215](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L215) +[packages/quickjs-ffi-types/src/ffi.ts:221](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L221) *** diff --git a/doc/quickjs-emscripten-core/README.md b/doc/quickjs-emscripten-core/README.md index b92a0906..bf0c0ade 100644 --- a/doc/quickjs-emscripten-core/README.md +++ b/doc/quickjs-emscripten-core/README.md @@ -211,7 +211,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | @@ -224,7 +224,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md b/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md index 5fae5fcc..49cd9b96 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSAsyncContext.md @@ -22,6 +22,7 @@ host functions as though they were synchronous. - [Accessors](QuickJSAsyncContext.md#accessors) - [alive](QuickJSAsyncContext.md#alive) - [false](QuickJSAsyncContext.md#false) + - [features](QuickJSAsyncContext.md#features) - [global](QuickJSAsyncContext.md#global) - [null](QuickJSAsyncContext.md#null) - [true](QuickJSAsyncContext.md#true) @@ -117,7 +118,7 @@ to create a new QuickJSContext. #### Source -[packages/quickjs-emscripten-core/src/context.ts:233](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L233) +[packages/quickjs-emscripten-core/src/context.ts:242](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L242) ## Properties @@ -151,7 +152,7 @@ false after the object has been [dispose](QuickJSAsyncContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L264) +[packages/quickjs-emscripten-core/src/context.ts:273](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L273) *** @@ -167,7 +168,24 @@ false after the object has been [dispose](QuickJSAsyncContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:322](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L322) +[packages/quickjs-emscripten-core/src/context.ts:331](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L331) + +*** + +### features + +> **`get`** **features**(): [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Returns + +[`QuickJSFeatures`](QuickJSFeatures.md) + +#### Source + +[packages/quickjs-emscripten-core/src/context.ts:202](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L202) *** @@ -185,7 +203,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:337](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L337) +[packages/quickjs-emscripten-core/src/context.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L346) *** @@ -201,7 +219,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:296](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L296) +[packages/quickjs-emscripten-core/src/context.ts:305](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L305) *** @@ -217,7 +235,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L309) +[packages/quickjs-emscripten-core/src/context.ts:318](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L318) *** @@ -233,7 +251,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) +[packages/quickjs-emscripten-core/src/context.ts:292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L292) ## Methods @@ -303,7 +321,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1180](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1180) +[packages/quickjs-emscripten-core/src/context.ts:1195](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1195) #### callFunction(func, thisVal, args) @@ -327,7 +345,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1185](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1185) +[packages/quickjs-emscripten-core/src/context.ts:1200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1200) *** @@ -361,7 +379,7 @@ value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1234](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1234) +[packages/quickjs-emscripten-core/src/context.ts:1249](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1249) *** @@ -394,7 +412,7 @@ socket.on("data", chunk => { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1530](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1530) +[packages/quickjs-emscripten-core/src/context.ts:1545](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1545) *** @@ -425,7 +443,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) +[packages/quickjs-emscripten-core/src/context.ts:1136](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1136) *** @@ -450,7 +468,7 @@ will result in an error. #### Source -[packages/quickjs-emscripten-core/src/context.ts:274](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L274) +[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) *** @@ -476,7 +494,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1355](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1355) +[packages/quickjs-emscripten-core/src/context.ts:1370](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1370) *** @@ -510,7 +528,7 @@ socket.write(dataLifetime?.value) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1513](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1513) +[packages/quickjs-emscripten-core/src/context.ts:1528](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1528) *** @@ -537,7 +555,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:932](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L932) +[packages/quickjs-emscripten-core/src/context.ts:947](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L947) *** @@ -600,7 +618,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:1277](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1277) +[packages/quickjs-emscripten-core/src/context.ts:1292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1292) *** @@ -648,7 +666,7 @@ See [EvalFlags](../exports.md#evalflags) for number semantics #### Source -[packages/quickjs-emscripten-core/src/context.ts:1539](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1539) +[packages/quickjs-emscripten-core/src/context.ts:1554](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1554) *** @@ -672,7 +690,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -[packages/quickjs-emscripten-core/src/context.ts:811](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L811) +[packages/quickjs-emscripten-core/src/context.ts:826](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L826) *** @@ -696,7 +714,7 @@ Converts `handle` to a Javascript bigint. #### Source -[packages/quickjs-emscripten-core/src/context.ts:802](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L802) +[packages/quickjs-emscripten-core/src/context.ts:816](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L816) *** @@ -732,7 +750,7 @@ for (using entriesHandle of context.getIterator(mapHandle).unwrap()) { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1081](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1081) +[packages/quickjs-emscripten-core/src/context.ts:1096](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1096) *** @@ -767,7 +785,7 @@ a number if the handle has a numeric length property, otherwise `undefined`. #### Source -[packages/quickjs-emscripten-core/src/context.ts:991](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L991) +[packages/quickjs-emscripten-core/src/context.ts:1006](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1006) *** @@ -793,7 +811,7 @@ Converts `handle` into a Javascript number. #### Source -[packages/quickjs-emscripten-core/src/context.ts:773](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L773) +[packages/quickjs-emscripten-core/src/context.ts:786](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L786) *** @@ -847,7 +865,7 @@ QuickJSEmptyGetOwnPropertyNames if no options are set. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1028](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1028) +[packages/quickjs-emscripten-core/src/context.ts:1043](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1043) *** @@ -881,7 +899,7 @@ resultHandle.dispose(); #### Source -[packages/quickjs-emscripten-core/src/context.ts:836](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L836) +[packages/quickjs-emscripten-core/src/context.ts:851](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L851) *** @@ -911,7 +929,7 @@ Javascript string (which will be converted automatically). #### Source -[packages/quickjs-emscripten-core/src/context.ts:961](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L961) +[packages/quickjs-emscripten-core/src/context.ts:976](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L976) *** @@ -935,7 +953,7 @@ Converts `handle` to a Javascript string. #### Source -[packages/quickjs-emscripten-core/src/context.ts:781](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L781) +[packages/quickjs-emscripten-core/src/context.ts:794](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L794) *** @@ -960,7 +978,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -[packages/quickjs-emscripten-core/src/context.ts:790](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L790) +[packages/quickjs-emscripten-core/src/context.ts:803](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L803) *** @@ -984,7 +1002,7 @@ Access a well-known symbol that is a property of the global Symbol object, like #### Source -[packages/quickjs-emscripten-core/src/context.ts:397](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L397) +[packages/quickjs-emscripten-core/src/context.ts:408](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L408) *** @@ -1005,7 +1023,7 @@ Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaSc #### Source -[packages/quickjs-emscripten-core/src/context.ts:439](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L439) +[packages/quickjs-emscripten-core/src/context.ts:451](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L451) *** @@ -1029,7 +1047,7 @@ Create a new QuickJS [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/ #### Source -[packages/quickjs-emscripten-core/src/context.ts:447](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L447) +[packages/quickjs-emscripten-core/src/context.ts:459](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L459) *** @@ -1085,7 +1103,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:405](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L405) +[packages/quickjs-emscripten-core/src/context.ts:416](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L416) *** @@ -1112,7 +1130,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:635](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L635) +[packages/quickjs-emscripten-core/src/context.ts:648](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L648) #### newConstructorFunction(name, fn) @@ -1134,7 +1152,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:636](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L636) +[packages/quickjs-emscripten-core/src/context.ts:649](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L649) *** @@ -1162,7 +1180,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:679](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L679) +[packages/quickjs-emscripten-core/src/context.ts:692](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L692) #### newError(message) @@ -1182,7 +1200,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:680](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L680) +[packages/quickjs-emscripten-core/src/context.ts:693](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L693) #### newError(undefined) @@ -1198,7 +1216,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:681](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L681) +[packages/quickjs-emscripten-core/src/context.ts:694](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L694) *** @@ -1317,7 +1335,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:612](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L612) +[packages/quickjs-emscripten-core/src/context.ts:625](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L625) #### newFunction(name, fn) @@ -1339,7 +1357,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:613](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L613) +[packages/quickjs-emscripten-core/src/context.ts:626](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L626) *** @@ -1372,7 +1390,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details on how to #### Source -[packages/quickjs-emscripten-core/src/context.ts:661](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L661) +[packages/quickjs-emscripten-core/src/context.ts:674](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L674) *** @@ -1406,7 +1424,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:716](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L716) +[packages/quickjs-emscripten-core/src/context.ts:729](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L729) *** @@ -1430,7 +1448,7 @@ Converts a Javascript number into a QuickJS value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L356) +[packages/quickjs-emscripten-core/src/context.ts:365](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L365) *** @@ -1457,7 +1475,7 @@ Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/R #### Source -[packages/quickjs-emscripten-core/src/context.ts:425](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L425) +[packages/quickjs-emscripten-core/src/context.ts:437](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L437) *** @@ -1482,7 +1500,7 @@ resources; see the documentation on [QuickJSDeferredPromise](QuickJSDeferredProm ##### Source -[packages/quickjs-emscripten-core/src/context.ts:460](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L460) +[packages/quickjs-emscripten-core/src/context.ts:472](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L472) #### newPromise(promise) @@ -1508,7 +1526,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:468](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L468) +[packages/quickjs-emscripten-core/src/context.ts:480](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L480) #### newPromise(newPromiseFn) @@ -1533,7 +1551,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:475](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L475) +[packages/quickjs-emscripten-core/src/context.ts:487](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L487) *** @@ -1557,7 +1575,7 @@ Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:363](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L363) +[packages/quickjs-emscripten-core/src/context.ts:372](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L372) *** @@ -1582,7 +1600,7 @@ All symbols created with the same key will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:386](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L386) +[packages/quickjs-emscripten-core/src/context.ts:396](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L396) *** @@ -1607,7 +1625,7 @@ No two symbols created with this function will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:374](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L374) +[packages/quickjs-emscripten-core/src/context.ts:383](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L383) *** @@ -1639,7 +1657,7 @@ You may need to call [runtime](QuickJSAsyncContext.md#runtime).[QuickJSRuntime#e #### Source -[packages/quickjs-emscripten-core/src/context.ts:875](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L875) +[packages/quickjs-emscripten-core/src/context.ts:890](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L890) *** @@ -1666,7 +1684,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:940](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L940) +[packages/quickjs-emscripten-core/src/context.ts:955](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L955) *** @@ -1693,7 +1711,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:948](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L948) +[packages/quickjs-emscripten-core/src/context.ts:963](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L963) *** @@ -1730,7 +1748,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1106) +[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) *** @@ -1756,7 +1774,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1535](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1535) +[packages/quickjs-emscripten-core/src/context.ts:1550](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1550) *** @@ -1780,7 +1798,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -[packages/quickjs-emscripten-core/src/context.ts:1314](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1314) +[packages/quickjs-emscripten-core/src/context.ts:1329](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1329) *** @@ -1810,7 +1828,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:732](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L732) +[packages/quickjs-emscripten-core/src/context.ts:745](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L745) *** @@ -1838,7 +1856,7 @@ Does not support BigInt values correctly. #### Source -[packages/quickjs-emscripten-core/src/context.ts:764](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L764) +[packages/quickjs-emscripten-core/src/context.ts:777](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L777) *** @@ -1870,7 +1888,7 @@ QuickJSHostRefInvalid if `handle` is not a `HostRef.handle` #### Source -[packages/quickjs-emscripten-core/src/context.ts:747](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L747) +[packages/quickjs-emscripten-core/src/context.ts:760](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L760) *** @@ -1901,7 +1919,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -[packages/quickjs-emscripten-core/src/context.ts:1398](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1398) +[packages/quickjs-emscripten-core/src/context.ts:1413](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1413) *** diff --git a/doc/quickjs-emscripten-core/classes/QuickJSAsyncRuntime.md b/doc/quickjs-emscripten-core/classes/QuickJSAsyncRuntime.md index e20bdd5f..ae9b0e3f 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSAsyncRuntime.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSAsyncRuntime.md @@ -35,6 +35,7 @@ Configure ES module loading with [setModuleLoader](QuickJSAsyncRuntime.md#setmod - [Extends](QuickJSAsyncRuntime.md#extends) - [Properties](QuickJSAsyncRuntime.md#properties) - [context](QuickJSAsyncRuntime.md#context) + - [features](QuickJSAsyncRuntime.md#features) - [Accessors](QuickJSAsyncRuntime.md#accessors) - [alive](QuickJSAsyncRuntime.md#alive) - [Methods](QuickJSAsyncRuntime.md#methods) @@ -78,7 +79,24 @@ A context here may be allocated if one is needed by the runtime, eg for [compute #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:26](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L26) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:27](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L27) + +*** + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Inherited from + +[`quickjs-emscripten-core.QuickJSRuntime.features`](QuickJSRuntime.md#features) + +#### Source + +[packages/quickjs-emscripten-core/src/runtime.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L86) ## Accessors @@ -96,7 +114,7 @@ false after the object has been [dispose](QuickJSAsyncRuntime.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L128) +[packages/quickjs-emscripten-core/src/runtime.ts:137](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L137) ## Methods @@ -144,7 +162,7 @@ QuickJSWrongOwner if owned by a different runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:330](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L330) +[packages/quickjs-emscripten-core/src/runtime.ts:340](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L340) *** @@ -168,7 +186,7 @@ For a human-digestible representation, see [dumpMemoryUsage](QuickJSAsyncRuntime #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:299](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L299) +[packages/quickjs-emscripten-core/src/runtime.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L309) *** @@ -195,7 +213,7 @@ manipulation if debug logging is disabled. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) +[packages/quickjs-emscripten-core/src/runtime.ts:376](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L376) *** @@ -215,7 +233,7 @@ Dispose of the underlying resources used by this object. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L132) +[packages/quickjs-emscripten-core/src/runtime.ts:141](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L141) *** @@ -236,7 +254,7 @@ For programmatic access to this information, see [computeMemoryUsage](QuickJSAsy #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:310](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L310) +[packages/quickjs-emscripten-core/src/runtime.ts:320](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L320) *** @@ -274,7 +292,7 @@ functions or rejected promises. Those errors are available by calling #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:246](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L246) +[packages/quickjs-emscripten-core/src/runtime.ts:256](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L256) *** @@ -297,7 +315,7 @@ true if there is at least one pendingJob queued up. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:197](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L197) +[packages/quickjs-emscripten-core/src/runtime.ts:207](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L207) *** @@ -317,7 +335,7 @@ true if debug logging is enabled #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) +[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) *** @@ -345,7 +363,7 @@ You should dispose a created context before disposing this runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:49](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L49) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:51](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L51) *** @@ -366,7 +384,7 @@ See [setInterruptHandler](QuickJSAsyncRuntime.md#setinterrupthandler). #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:222](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L222) +[packages/quickjs-emscripten-core/src/runtime.ts:232](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L232) *** @@ -386,7 +404,7 @@ Remove the the loader set by [setModuleLoader](QuickJSAsyncRuntime.md#setmodulel #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:184](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L184) +[packages/quickjs-emscripten-core/src/runtime.ts:194](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L194) *** @@ -413,7 +431,7 @@ code. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L346) +[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) *** @@ -441,7 +459,7 @@ The interrupt handler can be removed with [removeInterruptHandler](QuickJSAsyncR #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:210](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L210) +[packages/quickjs-emscripten-core/src/runtime.ts:220](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L220) *** @@ -469,7 +487,7 @@ See the [pull request](https://github.com/justjake/quickjs-emscripten/pull/114) #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:92](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L92) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:94](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L94) *** @@ -494,7 +512,7 @@ To remove the limit, set to `-1`. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:284](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L284) +[packages/quickjs-emscripten-core/src/runtime.ts:294](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L294) *** @@ -523,7 +541,7 @@ The loader can be removed with [removeModuleLoader](QuickJSAsyncRuntime.md#remov #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L75) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L77) *** diff --git a/doc/quickjs-emscripten-core/classes/QuickJSAsyncWASMModule.md b/doc/quickjs-emscripten-core/classes/QuickJSAsyncWASMModule.md index c13cc25a..dd666005 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSAsyncWASMModule.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSAsyncWASMModule.md @@ -21,6 +21,8 @@ modules. ## Contents - [Extends](QuickJSAsyncWASMModule.md#extends) +- [Properties](QuickJSAsyncWASMModule.md#properties) + - [features](QuickJSAsyncWASMModule.md#features) - [Methods](QuickJSAsyncWASMModule.md#methods) - [evalCode()](QuickJSAsyncWASMModule.md#evalcode) - [evalCodeAsync()](QuickJSAsyncWASMModule.md#evalcodeasync) @@ -32,6 +34,23 @@ modules. - [`QuickJSWASMModule`](QuickJSWASMModule.md) +## Properties + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Inherited from + +[`quickjs-emscripten-core.QuickJSWASMModule.features`](QuickJSWASMModule.md#features) + +#### Source + +[packages/quickjs-emscripten-core/src/module.ts:339](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L339) + ## Methods ### evalCode() @@ -50,7 +69,7 @@ Synchronous evalCode is not supported. #### Source -[packages/quickjs-emscripten-core/src/module-asyncify.ts:76](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L76) +[packages/quickjs-emscripten-core/src/module-asyncify.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L77) *** @@ -80,7 +99,7 @@ See the documentation for [QuickJSWASMModule#evalCode](QuickJSWASMModule.md#eval #### Source -[packages/quickjs-emscripten-core/src/module-asyncify.ts:91](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L91) +[packages/quickjs-emscripten-core/src/module-asyncify.ts:92](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L92) *** @@ -104,7 +123,7 @@ and provide the [CustomizeVariantOptions#wasmMemory](../interfaces/CustomizeVari #### Source -[packages/quickjs-emscripten-core/src/module.ts:441](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L441) +[packages/quickjs-emscripten-core/src/module.ts:450](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L450) *** @@ -130,7 +149,7 @@ be disposed when the context is disposed. #### Source -[packages/quickjs-emscripten-core/src/module-asyncify.ts:67](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L67) +[packages/quickjs-emscripten-core/src/module-asyncify.ts:68](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L68) *** diff --git a/doc/quickjs-emscripten-core/classes/QuickJSContext.md b/doc/quickjs-emscripten-core/classes/QuickJSContext.md index 848fc634..5f6b4924 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSContext.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSContext.md @@ -47,6 +47,7 @@ See [QuickJSRuntime](QuickJSRuntime.md) for more information. - [Accessors](QuickJSContext.md#accessors) - [alive](QuickJSContext.md#alive) - [false](QuickJSContext.md#false) + - [features](QuickJSContext.md#features) - [global](QuickJSContext.md#global) - [null](QuickJSContext.md#null) - [true](QuickJSContext.md#true) @@ -145,7 +146,7 @@ to create a new QuickJSContext. #### Source -[packages/quickjs-emscripten-core/src/context.ts:233](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L233) +[packages/quickjs-emscripten-core/src/context.ts:242](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L242) ## Properties @@ -157,7 +158,7 @@ The runtime that created this context. #### Source -[packages/quickjs-emscripten-core/src/context.ts:195](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L195) +[packages/quickjs-emscripten-core/src/context.ts:196](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L196) ## Accessors @@ -175,7 +176,7 @@ false after the object has been [dispose](QuickJSContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L264) +[packages/quickjs-emscripten-core/src/context.ts:273](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L273) *** @@ -191,7 +192,24 @@ false after the object has been [dispose](QuickJSContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:322](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L322) +[packages/quickjs-emscripten-core/src/context.ts:331](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L331) + +*** + +### features + +> **`get`** **features**(): [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Returns + +[`QuickJSFeatures`](QuickJSFeatures.md) + +#### Source + +[packages/quickjs-emscripten-core/src/context.ts:202](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L202) *** @@ -209,7 +227,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:337](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L337) +[packages/quickjs-emscripten-core/src/context.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L346) *** @@ -225,7 +243,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:296](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L296) +[packages/quickjs-emscripten-core/src/context.ts:305](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L305) *** @@ -241,7 +259,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L309) +[packages/quickjs-emscripten-core/src/context.ts:318](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L318) *** @@ -257,7 +275,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) +[packages/quickjs-emscripten-core/src/context.ts:292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L292) ## Methods @@ -331,7 +349,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1180](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1180) +[packages/quickjs-emscripten-core/src/context.ts:1195](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1195) #### callFunction(func, thisVal, args) @@ -355,7 +373,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1185](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1185) +[packages/quickjs-emscripten-core/src/context.ts:1200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1200) *** @@ -385,7 +403,7 @@ value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1234](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1234) +[packages/quickjs-emscripten-core/src/context.ts:1249](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1249) *** @@ -414,7 +432,7 @@ socket.on("data", chunk => { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1530](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1530) +[packages/quickjs-emscripten-core/src/context.ts:1545](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1545) *** @@ -445,7 +463,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) +[packages/quickjs-emscripten-core/src/context.ts:1136](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1136) *** @@ -474,7 +492,7 @@ will result in an error. #### Source -[packages/quickjs-emscripten-core/src/context.ts:274](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L274) +[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) *** @@ -496,7 +514,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1355](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1355) +[packages/quickjs-emscripten-core/src/context.ts:1370](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1370) *** @@ -526,7 +544,7 @@ socket.write(dataLifetime?.value) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1513](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1513) +[packages/quickjs-emscripten-core/src/context.ts:1528](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1528) *** @@ -549,7 +567,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:932](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L932) +[packages/quickjs-emscripten-core/src/context.ts:947](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L947) *** @@ -612,7 +630,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:1277](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1277) +[packages/quickjs-emscripten-core/src/context.ts:1292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1292) *** @@ -630,7 +648,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:1539](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1539) +[packages/quickjs-emscripten-core/src/context.ts:1554](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1554) *** @@ -650,7 +668,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -[packages/quickjs-emscripten-core/src/context.ts:811](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L811) +[packages/quickjs-emscripten-core/src/context.ts:826](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L826) *** @@ -670,7 +688,7 @@ Converts `handle` to a Javascript bigint. #### Source -[packages/quickjs-emscripten-core/src/context.ts:802](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L802) +[packages/quickjs-emscripten-core/src/context.ts:816](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L816) *** @@ -702,7 +720,7 @@ for (using entriesHandle of context.getIterator(mapHandle).unwrap()) { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1081](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1081) +[packages/quickjs-emscripten-core/src/context.ts:1096](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1096) *** @@ -733,7 +751,7 @@ a number if the handle has a numeric length property, otherwise `undefined`. #### Source -[packages/quickjs-emscripten-core/src/context.ts:991](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L991) +[packages/quickjs-emscripten-core/src/context.ts:1006](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1006) *** @@ -759,7 +777,7 @@ Converts `handle` into a Javascript number. #### Source -[packages/quickjs-emscripten-core/src/context.ts:773](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L773) +[packages/quickjs-emscripten-core/src/context.ts:786](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L786) *** @@ -809,7 +827,7 @@ QuickJSEmptyGetOwnPropertyNames if no options are set. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1028](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1028) +[packages/quickjs-emscripten-core/src/context.ts:1043](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1043) *** @@ -839,7 +857,7 @@ resultHandle.dispose(); #### Source -[packages/quickjs-emscripten-core/src/context.ts:836](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L836) +[packages/quickjs-emscripten-core/src/context.ts:851](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L851) *** @@ -869,7 +887,7 @@ Javascript string (which will be converted automatically). #### Source -[packages/quickjs-emscripten-core/src/context.ts:961](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L961) +[packages/quickjs-emscripten-core/src/context.ts:976](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L976) *** @@ -893,7 +911,7 @@ Converts `handle` to a Javascript string. #### Source -[packages/quickjs-emscripten-core/src/context.ts:781](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L781) +[packages/quickjs-emscripten-core/src/context.ts:794](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L794) *** @@ -914,7 +932,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -[packages/quickjs-emscripten-core/src/context.ts:790](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L790) +[packages/quickjs-emscripten-core/src/context.ts:803](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L803) *** @@ -934,7 +952,7 @@ Access a well-known symbol that is a property of the global Symbol object, like #### Source -[packages/quickjs-emscripten-core/src/context.ts:397](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L397) +[packages/quickjs-emscripten-core/src/context.ts:408](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L408) *** @@ -951,7 +969,7 @@ Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaSc #### Source -[packages/quickjs-emscripten-core/src/context.ts:439](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L439) +[packages/quickjs-emscripten-core/src/context.ts:451](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L451) *** @@ -971,7 +989,7 @@ Create a new QuickJS [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/ #### Source -[packages/quickjs-emscripten-core/src/context.ts:447](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L447) +[packages/quickjs-emscripten-core/src/context.ts:459](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L459) *** @@ -991,7 +1009,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:405](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L405) +[packages/quickjs-emscripten-core/src/context.ts:416](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L416) *** @@ -1014,7 +1032,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:635](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L635) +[packages/quickjs-emscripten-core/src/context.ts:648](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L648) #### newConstructorFunction(name, fn) @@ -1032,7 +1050,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:636](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L636) +[packages/quickjs-emscripten-core/src/context.ts:649](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L649) *** @@ -1056,7 +1074,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:679](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L679) +[packages/quickjs-emscripten-core/src/context.ts:692](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L692) #### newError(message) @@ -1072,7 +1090,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:680](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L680) +[packages/quickjs-emscripten-core/src/context.ts:693](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L693) #### newError(undefined) @@ -1084,7 +1102,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:681](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L681) +[packages/quickjs-emscripten-core/src/context.ts:694](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L694) *** @@ -1203,7 +1221,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:612](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L612) +[packages/quickjs-emscripten-core/src/context.ts:625](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L625) #### newFunction(name, fn) @@ -1225,7 +1243,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:613](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L613) +[packages/quickjs-emscripten-core/src/context.ts:626](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L626) *** @@ -1254,7 +1272,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details on how to use #### Source -[packages/quickjs-emscripten-core/src/context.ts:661](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L661) +[packages/quickjs-emscripten-core/src/context.ts:674](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L674) *** @@ -1284,7 +1302,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:716](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L716) +[packages/quickjs-emscripten-core/src/context.ts:729](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L729) *** @@ -1308,7 +1326,7 @@ Converts a Javascript number into a QuickJS value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L356) +[packages/quickjs-emscripten-core/src/context.ts:365](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L365) *** @@ -1335,7 +1353,7 @@ Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/R #### Source -[packages/quickjs-emscripten-core/src/context.ts:425](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L425) +[packages/quickjs-emscripten-core/src/context.ts:437](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L437) *** @@ -1356,7 +1374,7 @@ resources; see the documentation on [QuickJSDeferredPromise](QuickJSDeferredProm ##### Source -[packages/quickjs-emscripten-core/src/context.ts:460](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L460) +[packages/quickjs-emscripten-core/src/context.ts:472](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L472) #### newPromise(promise) @@ -1378,7 +1396,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:468](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L468) +[packages/quickjs-emscripten-core/src/context.ts:480](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L480) #### newPromise(newPromiseFn) @@ -1399,7 +1417,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:475](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L475) +[packages/quickjs-emscripten-core/src/context.ts:487](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L487) *** @@ -1423,7 +1441,7 @@ Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:363](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L363) +[packages/quickjs-emscripten-core/src/context.ts:372](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L372) *** @@ -1444,7 +1462,7 @@ All symbols created with the same key will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:386](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L386) +[packages/quickjs-emscripten-core/src/context.ts:396](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L396) *** @@ -1465,7 +1483,7 @@ No two symbols created with this function will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:374](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L374) +[packages/quickjs-emscripten-core/src/context.ts:383](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L383) *** @@ -1493,7 +1511,7 @@ You may need to call [runtime](QuickJSContext.md#runtime).[QuickJSRuntime#execut #### Source -[packages/quickjs-emscripten-core/src/context.ts:875](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L875) +[packages/quickjs-emscripten-core/src/context.ts:890](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L890) *** @@ -1516,7 +1534,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:940](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L940) +[packages/quickjs-emscripten-core/src/context.ts:955](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L955) *** @@ -1539,7 +1557,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:948](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L948) +[packages/quickjs-emscripten-core/src/context.ts:963](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L963) *** @@ -1576,7 +1594,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1106) +[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) *** @@ -1598,7 +1616,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1535](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1535) +[packages/quickjs-emscripten-core/src/context.ts:1550](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1550) *** @@ -1618,7 +1636,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -[packages/quickjs-emscripten-core/src/context.ts:1314](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1314) +[packages/quickjs-emscripten-core/src/context.ts:1329](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1329) *** @@ -1644,7 +1662,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:732](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L732) +[packages/quickjs-emscripten-core/src/context.ts:745](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L745) *** @@ -1672,7 +1690,7 @@ Does not support BigInt values correctly. #### Source -[packages/quickjs-emscripten-core/src/context.ts:764](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L764) +[packages/quickjs-emscripten-core/src/context.ts:777](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L777) *** @@ -1700,7 +1718,7 @@ QuickJSHostRefInvalid if `handle` is not a `HostRef.handle` #### Source -[packages/quickjs-emscripten-core/src/context.ts:747](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L747) +[packages/quickjs-emscripten-core/src/context.ts:760](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L760) *** @@ -1727,7 +1745,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -[packages/quickjs-emscripten-core/src/context.ts:1398](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1398) +[packages/quickjs-emscripten-core/src/context.ts:1413](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1413) *** diff --git a/doc/quickjs-emscripten-core/classes/QuickJSRuntime.md b/doc/quickjs-emscripten-core/classes/QuickJSRuntime.md index b53d42af..3a6174d2 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSRuntime.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSRuntime.md @@ -36,6 +36,7 @@ Configure ES module loading with [setModuleLoader](QuickJSRuntime.md#setmodulelo - [Implements](QuickJSRuntime.md#implements) - [Properties](QuickJSRuntime.md#properties) - [context](QuickJSRuntime.md#context) + - [features](QuickJSRuntime.md#features) - [Accessors](QuickJSRuntime.md#accessors) - [alive](QuickJSRuntime.md#alive) - [Methods](QuickJSRuntime.md#methods) @@ -79,7 +80,20 @@ A context here may be allocated if one is needed by the runtime, eg for [compute #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L79) +[packages/quickjs-emscripten-core/src/runtime.ts:80](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L80) + +*** + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Source + +[packages/quickjs-emscripten-core/src/runtime.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L86) ## Accessors @@ -97,7 +111,7 @@ false after the object has been [dispose](QuickJSRuntime.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L128) +[packages/quickjs-emscripten-core/src/runtime.ts:137](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L137) ## Methods @@ -145,7 +159,7 @@ QuickJSWrongOwner if owned by a different runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:330](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L330) +[packages/quickjs-emscripten-core/src/runtime.ts:340](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L340) *** @@ -165,7 +179,7 @@ For a human-digestible representation, see [dumpMemoryUsage](QuickJSRuntime.md#d #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:299](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L299) +[packages/quickjs-emscripten-core/src/runtime.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L309) *** @@ -188,7 +202,7 @@ manipulation if debug logging is disabled. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) +[packages/quickjs-emscripten-core/src/runtime.ts:376](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L376) *** @@ -212,7 +226,7 @@ Dispose of the underlying resources used by this object. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L132) +[packages/quickjs-emscripten-core/src/runtime.ts:141](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L141) *** @@ -229,7 +243,7 @@ For programmatic access to this information, see [computeMemoryUsage](QuickJSRun #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:310](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L310) +[packages/quickjs-emscripten-core/src/runtime.ts:320](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L320) *** @@ -263,7 +277,7 @@ functions or rejected promises. Those errors are available by calling #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:246](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L246) +[packages/quickjs-emscripten-core/src/runtime.ts:256](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L256) *** @@ -282,7 +296,7 @@ true if there is at least one pendingJob queued up. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:197](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L197) +[packages/quickjs-emscripten-core/src/runtime.ts:207](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L207) *** @@ -298,7 +312,7 @@ true if debug logging is enabled #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) +[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) *** @@ -322,7 +336,7 @@ You should dispose a created context before disposing this runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:143](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L143) +[packages/quickjs-emscripten-core/src/runtime.ts:152](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L152) *** @@ -339,7 +353,7 @@ See [setInterruptHandler](QuickJSRuntime.md#setinterrupthandler). #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:222](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L222) +[packages/quickjs-emscripten-core/src/runtime.ts:232](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L232) *** @@ -355,7 +369,7 @@ Remove the the loader set by [setModuleLoader](QuickJSRuntime.md#setmoduleloader #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:184](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L184) +[packages/quickjs-emscripten-core/src/runtime.ts:194](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L194) *** @@ -378,7 +392,7 @@ code. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L346) +[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) *** @@ -402,7 +416,7 @@ The interrupt handler can be removed with [removeInterruptHandler](QuickJSRuntim #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:210](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L210) +[packages/quickjs-emscripten-core/src/runtime.ts:220](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L220) *** @@ -423,7 +437,7 @@ To remove the limit, set to `0`. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:318](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L318) +[packages/quickjs-emscripten-core/src/runtime.ts:328](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L328) *** @@ -444,7 +458,7 @@ To remove the limit, set to `-1`. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:284](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L284) +[packages/quickjs-emscripten-core/src/runtime.ts:294](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L294) *** @@ -469,7 +483,7 @@ The loader can be removed with [removeModuleLoader](QuickJSRuntime.md#removemodu #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:175](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L175) +[packages/quickjs-emscripten-core/src/runtime.ts:184](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L184) *** diff --git a/doc/quickjs-emscripten-core/classes/QuickJSWASMModule.md b/doc/quickjs-emscripten-core/classes/QuickJSWASMModule.md index 69abf2e9..be0151a4 100644 --- a/doc/quickjs-emscripten-core/classes/QuickJSWASMModule.md +++ b/doc/quickjs-emscripten-core/classes/QuickJSWASMModule.md @@ -25,6 +25,8 @@ inside QuickJS, create a context with [newContext](QuickJSWASMModule.md#newconte ## Contents - [Extended By](QuickJSWASMModule.md#extended-by) +- [Properties](QuickJSWASMModule.md#properties) + - [features](QuickJSWASMModule.md#features) - [Methods](QuickJSWASMModule.md#methods) - [evalCode()](QuickJSWASMModule.md#evalcode) - [getWasmMemory()](QuickJSWASMModule.md#getwasmmemory) @@ -35,6 +37,19 @@ inside QuickJS, create a context with [newContext](QuickJSWASMModule.md#newconte - [`QuickJSAsyncWASMModule`](QuickJSAsyncWASMModule.md) +## Properties + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Source + +[packages/quickjs-emscripten-core/src/module.ts:339](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L339) + ## Methods ### evalCode() @@ -81,7 +96,7 @@ with name `"InternalError"` and message `"interrupted"`. #### Source -[packages/quickjs-emscripten-core/src/module.ts:410](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L410) +[packages/quickjs-emscripten-core/src/module.ts:419](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L419) *** @@ -101,7 +116,7 @@ and provide the [CustomizeVariantOptions#wasmMemory](../interfaces/CustomizeVari #### Source -[packages/quickjs-emscripten-core/src/module.ts:441](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L441) +[packages/quickjs-emscripten-core/src/module.ts:450](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L450) *** @@ -123,7 +138,7 @@ be disposed when the context is disposed. #### Source -[packages/quickjs-emscripten-core/src/module.ts:375](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L375) +[packages/quickjs-emscripten-core/src/module.ts:384](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L384) *** @@ -145,7 +160,7 @@ loading for one or more [QuickJSContext](QuickJSContext.md)s inside the runtime. #### Source -[packages/quickjs-emscripten-core/src/module.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L346) +[packages/quickjs-emscripten-core/src/module.ts:354](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L354) *** diff --git a/doc/quickjs-emscripten-core/exports.md b/doc/quickjs-emscripten-core/exports.md index fc4489e5..fd2d4013 100644 --- a/doc/quickjs-emscripten-core/exports.md +++ b/doc/quickjs-emscripten-core/exports.md @@ -49,6 +49,7 @@ - [QTS\_C\_To\_HostCallbackFuncPointer](exports.md#qts-c-to-hostcallbackfuncpointer) - [QTS\_C\_To\_HostInterruptFuncPointer](exports.md#qts-c-to-hostinterruptfuncpointer) - [QTS\_C\_To\_HostLoadModuleFuncPointer](exports.md#qts-c-to-hostloadmodulefuncpointer) + - [QuickJSFeature](exports.md#quickjsfeature) - [QuickJSHandle](exports.md#quickjshandle) - [QuickJSPropertyKey](exports.md#quickjspropertykey) - [QuickJSVariant](exports.md#quickjsvariant) @@ -91,6 +92,7 @@ - [QuickJSAsyncWASMModule](classes/QuickJSAsyncWASMModule.md) - [QuickJSContext](classes/QuickJSContext.md) - [QuickJSDeferredPromise](classes/QuickJSDeferredPromise.md) +- [QuickJSFeatures](classes/QuickJSFeatures.md) - [QuickJSRuntime](classes/QuickJSRuntime.md) - [QuickJSWASMModule](classes/QuickJSWASMModule.md) - [Scope](classes/Scope.md) @@ -231,7 +233,7 @@ by the runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:36](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L36) +[packages/quickjs-emscripten-core/src/runtime.ts:37](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L37) *** @@ -256,7 +258,7 @@ Determines if a VM's execution should be interrupted. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:28](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L28) +[packages/quickjs-emscripten-core/src/runtime.ts:29](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L29) *** @@ -338,7 +340,7 @@ Language features that can be enabled or disabled in a QuickJSContext. #### Source -[packages/quickjs-emscripten-core/src/types.ts:146](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L146) +[packages/quickjs-emscripten-core/src/types.ts:159](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L159) *** @@ -397,7 +399,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:69](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L69) +[packages/quickjs-emscripten-core/src/types.ts:82](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L82) *** @@ -407,7 +409,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:70](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L70) +[packages/quickjs-emscripten-core/src/types.ts:83](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L83) *** @@ -417,7 +419,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:68](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L68) +[packages/quickjs-emscripten-core/src/types.ts:81](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L81) *** @@ -427,7 +429,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:87](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L87) +[packages/quickjs-emscripten-core/src/types.ts:100](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L100) *** @@ -437,7 +439,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:88](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L88) +[packages/quickjs-emscripten-core/src/types.ts:101](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L101) *** @@ -447,7 +449,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L86) +[packages/quickjs-emscripten-core/src/types.ts:99](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L99) *** @@ -508,7 +510,7 @@ You can do so from Javascript by calling the .dispose() method. #### Source -[packages/quickjs-emscripten-core/src/types.ts:43](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L43) +[packages/quickjs-emscripten-core/src/types.ts:56](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L56) *** @@ -527,7 +529,7 @@ quickjs-emscripten takes care of disposing JSValueConst references. #### Source -[packages/quickjs-emscripten-core/src/types.ts:26](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L26) +[packages/quickjs-emscripten-core/src/types.ts:39](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L39) *** @@ -654,7 +656,7 @@ for the Emscripten stack. #### Source -[packages/quickjs-emscripten-core/src/types.ts:333](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L333) +[packages/quickjs-emscripten-core/src/types.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L346) *** @@ -708,6 +710,26 @@ Used internally for C-to-Javascript module loading. *** +### QuickJSFeature + +> **QuickJSFeature**: `"modules"` \| `"promises"` \| `"symbols"` \| `"bigint"` \| `"intrinsics"` \| `"eval"` + +Features that may or may not be supported by a QuickJS variant. +Use QuickJSWASMModule#hasSupport to check if a feature is available. + +- `modules`: ES module support (import/export) +- `promises`: Promise and async/await support +- `symbols`: Symbol type support +- `bigint`: BigInt type support +- `intrinsics`: Intrinsics configuration support +- `eval`: eval() function support + +#### Source + +[packages/quickjs-emscripten-core/src/types.ts:21](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L21) + +*** + ### QuickJSHandle > **QuickJSHandle**: [`StaticJSValue`](exports.md#staticjsvalue) \| [`JSValue`](exports.md#jsvalue) \| [`JSValueConst`](exports.md#jsvalueconst) @@ -720,7 +742,7 @@ You must dispose of any handles you create by calling the `.dispose()` method. #### Source -[packages/quickjs-emscripten-core/src/types.ts:53](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L53) +[packages/quickjs-emscripten-core/src/types.ts:66](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L66) *** @@ -733,7 +755,7 @@ Property key for getting or setting a property on a handle with #### Source -[packages/quickjs-emscripten-core/src/context.ts:71](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L71) +[packages/quickjs-emscripten-core/src/context.ts:72](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L72) *** @@ -756,7 +778,7 @@ be disposed. #### Source -[packages/quickjs-emscripten-core/src/types.ts:14](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L14) +[packages/quickjs-emscripten-core/src/types.ts:27](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L27) *** @@ -895,7 +917,7 @@ The default [Intrinsics](exports.md#intrinsics) language features enabled in a Q #### Source -[packages/quickjs-emscripten-core/src/types.ts:173](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L173) +[packages/quickjs-emscripten-core/src/types.ts:186](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L186) *** diff --git a/doc/quickjs-emscripten-core/interfaces/ModuleEvalOptions.md b/doc/quickjs-emscripten-core/interfaces/ModuleEvalOptions.md index 1804e4f6..38017d52 100644 --- a/doc/quickjs-emscripten-core/interfaces/ModuleEvalOptions.md +++ b/doc/quickjs-emscripten-core/interfaces/ModuleEvalOptions.md @@ -27,7 +27,7 @@ To remove the limit, set to `0`. #### Source -[packages/quickjs-emscripten-core/src/module.ts:85](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L85) +[packages/quickjs-emscripten-core/src/module.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L86) *** @@ -39,7 +39,7 @@ Memory limit, in bytes, of WebAssembly heap memory used by the QuickJS VM. #### Source -[packages/quickjs-emscripten-core/src/module.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L79) +[packages/quickjs-emscripten-core/src/module.ts:80](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L80) *** @@ -51,7 +51,7 @@ Module loader for any `import` statements or expressions. #### Source -[packages/quickjs-emscripten-core/src/module.ts:90](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L90) +[packages/quickjs-emscripten-core/src/module.ts:91](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L91) *** @@ -64,7 +64,7 @@ See [shouldInterruptAfterDeadline](../exports.md#shouldinterruptafterdeadline). #### Source -[packages/quickjs-emscripten-core/src/module.ts:74](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L74) +[packages/quickjs-emscripten-core/src/module.ts:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L75) *** diff --git a/doc/quickjs-emscripten-core/interfaces/QuickJSAsyncFFI.md b/doc/quickjs-emscripten-core/interfaces/QuickJSAsyncFFI.md index a52ba33d..97a24a15 100644 --- a/doc/quickjs-emscripten-core/interfaces/QuickJSAsyncFFI.md +++ b/doc/quickjs-emscripten-core/interfaces/QuickJSAsyncFFI.md @@ -56,6 +56,12 @@ library. - [QTS\_GetSymbolDescriptionOrKey\_MaybeAsync](QuickJSAsyncFFI.md#qts-getsymboldescriptionorkey-maybeasync) - [QTS\_GetTrue](QuickJSAsyncFFI.md#qts-gettrue) - [QTS\_GetUndefined](QuickJSAsyncFFI.md#qts-getundefined) + - [QTS\_HasBigIntSupport](QuickJSAsyncFFI.md#qts-hasbigintsupport) + - [QTS\_HasEvalSupport](QuickJSAsyncFFI.md#qts-hasevalsupport) + - [QTS\_HasIntrinsicsSupport](QuickJSAsyncFFI.md#qts-hasintrinsicssupport) + - [QTS\_HasModuleSupport](QuickJSAsyncFFI.md#qts-hasmodulesupport) + - [QTS\_HasPromiseSupport](QuickJSAsyncFFI.md#qts-haspromisesupport) + - [QTS\_HasSymbolSupport](QuickJSAsyncFFI.md#qts-hassymbolsupport) - [QTS\_IsEqual](QuickJSAsyncFFI.md#qts-isequal) - [QTS\_IsGlobalSymbol](QuickJSAsyncFFI.md#qts-isglobalsymbol) - [QTS\_IsJobPending](QuickJSAsyncFFI.md#qts-isjobpending) @@ -127,7 +133,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:259](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L259) +[packages/quickjs-ffi-types/src/ffi-async.ts:265](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L265) *** @@ -947,6 +953,90 @@ Set at compile time. *** +### QTS\_HasBigIntSupport + +> **QTS\_HasBigIntSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:255](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L255) + +*** + +### QTS\_HasEvalSupport + +> **QTS\_HasEvalSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:257](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L257) + +*** + +### QTS\_HasIntrinsicsSupport + +> **QTS\_HasIntrinsicsSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:256](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L256) + +*** + +### QTS\_HasModuleSupport + +> **QTS\_HasModuleSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:252](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L252) + +*** + +### QTS\_HasPromiseSupport + +> **QTS\_HasPromiseSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:253](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L253) + +*** + +### QTS\_HasSymbolSupport + +> **QTS\_HasSymbolSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi-async.ts:254](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L254) + +*** + ### QTS\_IsEqual > **QTS\_IsEqual**: (`ctx`, `a`, `b`, `op`) => `number` @@ -1129,7 +1219,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:252](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L252) +[packages/quickjs-ffi-types/src/ffi-async.ts:258](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L258) *** @@ -1375,7 +1465,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L264) +[packages/quickjs-ffi-types/src/ffi-async.ts:270](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L270) *** @@ -1393,7 +1483,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:266](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L266) +[packages/quickjs-ffi-types/src/ffi-async.ts:272](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L272) *** @@ -1429,7 +1519,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:263](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L263) +[packages/quickjs-ffi-types/src/ffi-async.ts:269](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L269) *** @@ -1449,7 +1539,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:265](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L265) +[packages/quickjs-ffi-types/src/ffi-async.ts:271](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L271) *** @@ -1635,7 +1725,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:271](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L271) +[packages/quickjs-ffi-types/src/ffi-async.ts:277](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L277) *** @@ -1655,7 +1745,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi-async.ts:267](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L267) +[packages/quickjs-ffi-types/src/ffi-async.ts:273](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi-async.ts#L273) *** diff --git a/doc/quickjs-emscripten-core/interfaces/QuickJSFFI.md b/doc/quickjs-emscripten-core/interfaces/QuickJSFFI.md index 638a9afc..9f81c31f 100644 --- a/doc/quickjs-emscripten-core/interfaces/QuickJSFFI.md +++ b/doc/quickjs-emscripten-core/interfaces/QuickJSFFI.md @@ -48,6 +48,12 @@ library. - [QTS\_GetSymbolDescriptionOrKey](QuickJSFFI.md#qts-getsymboldescriptionorkey) - [QTS\_GetTrue](QuickJSFFI.md#qts-gettrue) - [QTS\_GetUndefined](QuickJSFFI.md#qts-getundefined) + - [QTS\_HasBigIntSupport](QuickJSFFI.md#qts-hasbigintsupport) + - [QTS\_HasEvalSupport](QuickJSFFI.md#qts-hasevalsupport) + - [QTS\_HasIntrinsicsSupport](QuickJSFFI.md#qts-hasintrinsicssupport) + - [QTS\_HasModuleSupport](QuickJSFFI.md#qts-hasmodulesupport) + - [QTS\_HasPromiseSupport](QuickJSFFI.md#qts-haspromisesupport) + - [QTS\_HasSymbolSupport](QuickJSFFI.md#qts-hassymbolsupport) - [QTS\_IsEqual](QuickJSFFI.md#qts-isequal) - [QTS\_IsGlobalSymbol](QuickJSFFI.md#qts-isglobalsymbol) - [QTS\_IsJobPending](QuickJSFFI.md#qts-isjobpending) @@ -118,7 +124,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:207](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L207) +[packages/quickjs-ffi-types/src/ffi.ts:213](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L213) *** @@ -752,6 +758,90 @@ Set at compile time. *** +### QTS\_HasBigIntSupport + +> **QTS\_HasBigIntSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:203](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L203) + +*** + +### QTS\_HasEvalSupport + +> **QTS\_HasEvalSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:205](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L205) + +*** + +### QTS\_HasIntrinsicsSupport + +> **QTS\_HasIntrinsicsSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:204](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L204) + +*** + +### QTS\_HasModuleSupport + +> **QTS\_HasModuleSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L200) + +*** + +### QTS\_HasPromiseSupport + +> **QTS\_HasPromiseSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:201](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L201) + +*** + +### QTS\_HasSymbolSupport + +> **QTS\_HasSymbolSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +[packages/quickjs-ffi-types/src/ffi.ts:202](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L202) + +*** + ### QTS\_IsEqual > **QTS\_IsEqual**: (`ctx`, `a`, `b`, `op`) => `number` @@ -934,7 +1024,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L200) +[packages/quickjs-ffi-types/src/ffi.ts:206](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L206) *** @@ -1180,7 +1270,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:212](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L212) +[packages/quickjs-ffi-types/src/ffi.ts:218](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L218) *** @@ -1198,7 +1288,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:214](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L214) +[packages/quickjs-ffi-types/src/ffi.ts:220](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L220) *** @@ -1234,7 +1324,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:211](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L211) +[packages/quickjs-ffi-types/src/ffi.ts:217](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L217) *** @@ -1254,7 +1344,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:213](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L213) +[packages/quickjs-ffi-types/src/ffi.ts:219](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L219) *** @@ -1416,7 +1506,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:219](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L219) +[packages/quickjs-ffi-types/src/ffi.ts:225](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L225) *** @@ -1436,7 +1526,7 @@ Set at compile time. #### Source -[packages/quickjs-ffi-types/src/ffi.ts:215](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L215) +[packages/quickjs-ffi-types/src/ffi.ts:221](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-ffi-types/src/ffi.ts#L221) *** diff --git a/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md b/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md index d7d1ad26..8ea02869 100644 --- a/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md +++ b/doc/quickjs-emscripten-core/namespaces/errors/classes/QuickJSUnsupported.md @@ -54,7 +54,7 @@ The name of the variant that doesn't support this feature #### Source -[packages/quickjs-emscripten-core/src/errors.ts:67](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L67) +[packages/quickjs-emscripten-core/src/errors.ts:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L75) ## Properties @@ -66,7 +66,7 @@ The feature that is not supported #### Source -[packages/quickjs-emscripten-core/src/errors.ts:69](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L69) +[packages/quickjs-emscripten-core/src/errors.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L77) *** @@ -80,7 +80,7 @@ The feature that is not supported #### Source -[packages/quickjs-emscripten-core/src/errors.ts:65](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L65) +[packages/quickjs-emscripten-core/src/errors.ts:73](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L73) *** @@ -92,7 +92,7 @@ The operation that was attempted, if specified #### Source -[packages/quickjs-emscripten-core/src/errors.ts:71](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L71) +[packages/quickjs-emscripten-core/src/errors.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L79) *** @@ -104,7 +104,7 @@ The name of the variant that doesn't support this feature #### Source -[packages/quickjs-emscripten-core/src/errors.ts:73](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L73) +[packages/quickjs-emscripten-core/src/errors.ts:81](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L81) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md b/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md index 9326f931..c582bad5 100644 --- a/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md +++ b/doc/quickjs-emscripten/classes/QuickJSAsyncContext.md @@ -22,6 +22,7 @@ host functions as though they were synchronous. - [Accessors](QuickJSAsyncContext.md#accessors) - [alive](QuickJSAsyncContext.md#alive) - [false](QuickJSAsyncContext.md#false) + - [features](QuickJSAsyncContext.md#features) - [global](QuickJSAsyncContext.md#global) - [null](QuickJSAsyncContext.md#null) - [true](QuickJSAsyncContext.md#true) @@ -117,7 +118,7 @@ to create a new QuickJSContext. #### Source -[packages/quickjs-emscripten-core/src/context.ts:233](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L233) +[packages/quickjs-emscripten-core/src/context.ts:242](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L242) ## Properties @@ -151,7 +152,7 @@ false after the object has been [dispose](QuickJSAsyncContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L264) +[packages/quickjs-emscripten-core/src/context.ts:273](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L273) *** @@ -167,7 +168,24 @@ false after the object has been [dispose](QuickJSAsyncContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:322](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L322) +[packages/quickjs-emscripten-core/src/context.ts:331](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L331) + +*** + +### features + +> **`get`** **features**(): [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Returns + +[`QuickJSFeatures`](QuickJSFeatures.md) + +#### Source + +[packages/quickjs-emscripten-core/src/context.ts:202](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L202) *** @@ -185,7 +203,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:337](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L337) +[packages/quickjs-emscripten-core/src/context.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L346) *** @@ -201,7 +219,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:296](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L296) +[packages/quickjs-emscripten-core/src/context.ts:305](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L305) *** @@ -217,7 +235,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L309) +[packages/quickjs-emscripten-core/src/context.ts:318](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L318) *** @@ -233,7 +251,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) +[packages/quickjs-emscripten-core/src/context.ts:292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L292) ## Methods @@ -303,7 +321,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1180](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1180) +[packages/quickjs-emscripten-core/src/context.ts:1195](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1195) #### callFunction(func, thisVal, args) @@ -327,7 +345,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1185](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1185) +[packages/quickjs-emscripten-core/src/context.ts:1200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1200) *** @@ -361,7 +379,7 @@ value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1234](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1234) +[packages/quickjs-emscripten-core/src/context.ts:1249](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1249) *** @@ -394,7 +412,7 @@ socket.on("data", chunk => { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1530](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1530) +[packages/quickjs-emscripten-core/src/context.ts:1545](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1545) *** @@ -425,7 +443,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) +[packages/quickjs-emscripten-core/src/context.ts:1136](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1136) *** @@ -450,7 +468,7 @@ will result in an error. #### Source -[packages/quickjs-emscripten-core/src/context.ts:274](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L274) +[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) *** @@ -476,7 +494,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1355](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1355) +[packages/quickjs-emscripten-core/src/context.ts:1370](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1370) *** @@ -510,7 +528,7 @@ socket.write(dataLifetime?.value) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1513](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1513) +[packages/quickjs-emscripten-core/src/context.ts:1528](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1528) *** @@ -537,7 +555,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:932](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L932) +[packages/quickjs-emscripten-core/src/context.ts:947](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L947) *** @@ -600,7 +618,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:1277](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1277) +[packages/quickjs-emscripten-core/src/context.ts:1292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1292) *** @@ -648,7 +666,7 @@ See [EvalFlags](../exports.md#evalflags) for number semantics #### Source -[packages/quickjs-emscripten-core/src/context.ts:1539](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1539) +[packages/quickjs-emscripten-core/src/context.ts:1554](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1554) *** @@ -672,7 +690,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -[packages/quickjs-emscripten-core/src/context.ts:811](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L811) +[packages/quickjs-emscripten-core/src/context.ts:826](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L826) *** @@ -696,7 +714,7 @@ Converts `handle` to a Javascript bigint. #### Source -[packages/quickjs-emscripten-core/src/context.ts:802](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L802) +[packages/quickjs-emscripten-core/src/context.ts:816](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L816) *** @@ -732,7 +750,7 @@ for (using entriesHandle of context.getIterator(mapHandle).unwrap()) { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1081](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1081) +[packages/quickjs-emscripten-core/src/context.ts:1096](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1096) *** @@ -767,7 +785,7 @@ a number if the handle has a numeric length property, otherwise `undefined`. #### Source -[packages/quickjs-emscripten-core/src/context.ts:991](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L991) +[packages/quickjs-emscripten-core/src/context.ts:1006](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1006) *** @@ -793,7 +811,7 @@ Converts `handle` into a Javascript number. #### Source -[packages/quickjs-emscripten-core/src/context.ts:773](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L773) +[packages/quickjs-emscripten-core/src/context.ts:786](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L786) *** @@ -847,7 +865,7 @@ QuickJSEmptyGetOwnPropertyNames if no options are set. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1028](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1028) +[packages/quickjs-emscripten-core/src/context.ts:1043](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1043) *** @@ -881,7 +899,7 @@ resultHandle.dispose(); #### Source -[packages/quickjs-emscripten-core/src/context.ts:836](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L836) +[packages/quickjs-emscripten-core/src/context.ts:851](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L851) *** @@ -911,7 +929,7 @@ Javascript string (which will be converted automatically). #### Source -[packages/quickjs-emscripten-core/src/context.ts:961](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L961) +[packages/quickjs-emscripten-core/src/context.ts:976](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L976) *** @@ -935,7 +953,7 @@ Converts `handle` to a Javascript string. #### Source -[packages/quickjs-emscripten-core/src/context.ts:781](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L781) +[packages/quickjs-emscripten-core/src/context.ts:794](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L794) *** @@ -960,7 +978,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -[packages/quickjs-emscripten-core/src/context.ts:790](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L790) +[packages/quickjs-emscripten-core/src/context.ts:803](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L803) *** @@ -984,7 +1002,7 @@ Access a well-known symbol that is a property of the global Symbol object, like #### Source -[packages/quickjs-emscripten-core/src/context.ts:397](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L397) +[packages/quickjs-emscripten-core/src/context.ts:408](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L408) *** @@ -1005,7 +1023,7 @@ Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaSc #### Source -[packages/quickjs-emscripten-core/src/context.ts:439](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L439) +[packages/quickjs-emscripten-core/src/context.ts:451](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L451) *** @@ -1029,7 +1047,7 @@ Create a new QuickJS [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/ #### Source -[packages/quickjs-emscripten-core/src/context.ts:447](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L447) +[packages/quickjs-emscripten-core/src/context.ts:459](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L459) *** @@ -1085,7 +1103,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:405](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L405) +[packages/quickjs-emscripten-core/src/context.ts:416](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L416) *** @@ -1112,7 +1130,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:635](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L635) +[packages/quickjs-emscripten-core/src/context.ts:648](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L648) #### newConstructorFunction(name, fn) @@ -1134,7 +1152,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:636](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L636) +[packages/quickjs-emscripten-core/src/context.ts:649](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L649) *** @@ -1162,7 +1180,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:679](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L679) +[packages/quickjs-emscripten-core/src/context.ts:692](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L692) #### newError(message) @@ -1182,7 +1200,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:680](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L680) +[packages/quickjs-emscripten-core/src/context.ts:693](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L693) #### newError(undefined) @@ -1198,7 +1216,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:681](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L681) +[packages/quickjs-emscripten-core/src/context.ts:694](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L694) *** @@ -1317,7 +1335,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:612](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L612) +[packages/quickjs-emscripten-core/src/context.ts:625](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L625) #### newFunction(name, fn) @@ -1339,7 +1357,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:613](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L613) +[packages/quickjs-emscripten-core/src/context.ts:626](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L626) *** @@ -1372,7 +1390,7 @@ See [newFunction](QuickJSAsyncContext.md#newfunction) for more details on how to #### Source -[packages/quickjs-emscripten-core/src/context.ts:661](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L661) +[packages/quickjs-emscripten-core/src/context.ts:674](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L674) *** @@ -1406,7 +1424,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:716](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L716) +[packages/quickjs-emscripten-core/src/context.ts:729](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L729) *** @@ -1430,7 +1448,7 @@ Converts a Javascript number into a QuickJS value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L356) +[packages/quickjs-emscripten-core/src/context.ts:365](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L365) *** @@ -1457,7 +1475,7 @@ Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/R #### Source -[packages/quickjs-emscripten-core/src/context.ts:425](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L425) +[packages/quickjs-emscripten-core/src/context.ts:437](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L437) *** @@ -1482,7 +1500,7 @@ resources; see the documentation on [QuickJSDeferredPromise](QuickJSDeferredProm ##### Source -[packages/quickjs-emscripten-core/src/context.ts:460](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L460) +[packages/quickjs-emscripten-core/src/context.ts:472](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L472) #### newPromise(promise) @@ -1508,7 +1526,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:468](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L468) +[packages/quickjs-emscripten-core/src/context.ts:480](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L480) #### newPromise(newPromiseFn) @@ -1533,7 +1551,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:475](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L475) +[packages/quickjs-emscripten-core/src/context.ts:487](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L487) *** @@ -1557,7 +1575,7 @@ Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:363](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L363) +[packages/quickjs-emscripten-core/src/context.ts:372](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L372) *** @@ -1582,7 +1600,7 @@ All symbols created with the same key will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:386](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L386) +[packages/quickjs-emscripten-core/src/context.ts:396](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L396) *** @@ -1607,7 +1625,7 @@ No two symbols created with this function will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:374](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L374) +[packages/quickjs-emscripten-core/src/context.ts:383](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L383) *** @@ -1639,7 +1657,7 @@ You may need to call [runtime](QuickJSAsyncContext.md#runtime).[QuickJSRuntime#e #### Source -[packages/quickjs-emscripten-core/src/context.ts:875](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L875) +[packages/quickjs-emscripten-core/src/context.ts:890](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L890) *** @@ -1666,7 +1684,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:940](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L940) +[packages/quickjs-emscripten-core/src/context.ts:955](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L955) *** @@ -1693,7 +1711,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:948](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L948) +[packages/quickjs-emscripten-core/src/context.ts:963](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L963) *** @@ -1730,7 +1748,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1106) +[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) *** @@ -1756,7 +1774,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1535](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1535) +[packages/quickjs-emscripten-core/src/context.ts:1550](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1550) *** @@ -1780,7 +1798,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -[packages/quickjs-emscripten-core/src/context.ts:1314](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1314) +[packages/quickjs-emscripten-core/src/context.ts:1329](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1329) *** @@ -1810,7 +1828,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:732](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L732) +[packages/quickjs-emscripten-core/src/context.ts:745](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L745) *** @@ -1838,7 +1856,7 @@ Does not support BigInt values correctly. #### Source -[packages/quickjs-emscripten-core/src/context.ts:764](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L764) +[packages/quickjs-emscripten-core/src/context.ts:777](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L777) *** @@ -1870,7 +1888,7 @@ QuickJSHostRefInvalid if `handle` is not a `HostRef.handle` #### Source -[packages/quickjs-emscripten-core/src/context.ts:747](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L747) +[packages/quickjs-emscripten-core/src/context.ts:760](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L760) *** @@ -1901,7 +1919,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -[packages/quickjs-emscripten-core/src/context.ts:1398](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1398) +[packages/quickjs-emscripten-core/src/context.ts:1413](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1413) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md b/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md index eb9a422b..1a227697 100644 --- a/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md +++ b/doc/quickjs-emscripten/classes/QuickJSAsyncRuntime.md @@ -35,6 +35,7 @@ Configure ES module loading with [setModuleLoader](QuickJSAsyncRuntime.md#setmod - [Extends](QuickJSAsyncRuntime.md#extends) - [Properties](QuickJSAsyncRuntime.md#properties) - [context](QuickJSAsyncRuntime.md#context) + - [features](QuickJSAsyncRuntime.md#features) - [Accessors](QuickJSAsyncRuntime.md#accessors) - [alive](QuickJSAsyncRuntime.md#alive) - [Methods](QuickJSAsyncRuntime.md#methods) @@ -78,7 +79,24 @@ A context here may be allocated if one is needed by the runtime, eg for [compute #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:26](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L26) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:27](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L27) + +*** + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Inherited from + +[`quickjs-emscripten.QuickJSRuntime.features`](QuickJSRuntime.md#features) + +#### Source + +[packages/quickjs-emscripten-core/src/runtime.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L86) ## Accessors @@ -96,7 +114,7 @@ false after the object has been [dispose](QuickJSAsyncRuntime.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L128) +[packages/quickjs-emscripten-core/src/runtime.ts:137](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L137) ## Methods @@ -144,7 +162,7 @@ QuickJSWrongOwner if owned by a different runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:330](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L330) +[packages/quickjs-emscripten-core/src/runtime.ts:340](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L340) *** @@ -168,7 +186,7 @@ For a human-digestible representation, see [dumpMemoryUsage](QuickJSAsyncRuntime #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:299](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L299) +[packages/quickjs-emscripten-core/src/runtime.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L309) *** @@ -195,7 +213,7 @@ manipulation if debug logging is disabled. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) +[packages/quickjs-emscripten-core/src/runtime.ts:376](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L376) *** @@ -215,7 +233,7 @@ Dispose of the underlying resources used by this object. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L132) +[packages/quickjs-emscripten-core/src/runtime.ts:141](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L141) *** @@ -236,7 +254,7 @@ For programmatic access to this information, see [computeMemoryUsage](QuickJSAsy #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:310](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L310) +[packages/quickjs-emscripten-core/src/runtime.ts:320](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L320) *** @@ -274,7 +292,7 @@ functions or rejected promises. Those errors are available by calling #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:246](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L246) +[packages/quickjs-emscripten-core/src/runtime.ts:256](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L256) *** @@ -297,7 +315,7 @@ true if there is at least one pendingJob queued up. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:197](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L197) +[packages/quickjs-emscripten-core/src/runtime.ts:207](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L207) *** @@ -317,7 +335,7 @@ true if debug logging is enabled #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) +[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) *** @@ -345,7 +363,7 @@ You should dispose a created context before disposing this runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:49](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L49) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:51](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L51) *** @@ -366,7 +384,7 @@ See [setInterruptHandler](QuickJSAsyncRuntime.md#setinterrupthandler). #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:222](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L222) +[packages/quickjs-emscripten-core/src/runtime.ts:232](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L232) *** @@ -386,7 +404,7 @@ Remove the the loader set by [setModuleLoader](QuickJSAsyncRuntime.md#setmodulel #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:184](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L184) +[packages/quickjs-emscripten-core/src/runtime.ts:194](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L194) *** @@ -413,7 +431,7 @@ code. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L346) +[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) *** @@ -441,7 +459,7 @@ The interrupt handler can be removed with [removeInterruptHandler](QuickJSAsyncR #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:210](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L210) +[packages/quickjs-emscripten-core/src/runtime.ts:220](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L220) *** @@ -469,7 +487,7 @@ See the [pull request](https://github.com/justjake/quickjs-emscripten/pull/114) #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:92](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L92) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:94](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L94) *** @@ -494,7 +512,7 @@ To remove the limit, set to `-1`. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:284](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L284) +[packages/quickjs-emscripten-core/src/runtime.ts:294](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L294) *** @@ -523,7 +541,7 @@ The loader can be removed with [removeModuleLoader](QuickJSAsyncRuntime.md#remov #### Source -[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L75) +[packages/quickjs-emscripten-core/src/runtime-asyncify.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime-asyncify.ts#L77) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md b/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md index d7ba40d8..23979c66 100644 --- a/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md +++ b/doc/quickjs-emscripten/classes/QuickJSAsyncWASMModule.md @@ -21,6 +21,8 @@ modules. ## Contents - [Extends](QuickJSAsyncWASMModule.md#extends) +- [Properties](QuickJSAsyncWASMModule.md#properties) + - [features](QuickJSAsyncWASMModule.md#features) - [Methods](QuickJSAsyncWASMModule.md#methods) - [evalCode()](QuickJSAsyncWASMModule.md#evalcode) - [evalCodeAsync()](QuickJSAsyncWASMModule.md#evalcodeasync) @@ -32,6 +34,23 @@ modules. - [`QuickJSWASMModule`](QuickJSWASMModule.md) +## Properties + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Inherited from + +[`quickjs-emscripten.QuickJSWASMModule.features`](QuickJSWASMModule.md#features) + +#### Source + +[packages/quickjs-emscripten-core/src/module.ts:339](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L339) + ## Methods ### evalCode() @@ -50,7 +69,7 @@ Synchronous evalCode is not supported. #### Source -[packages/quickjs-emscripten-core/src/module-asyncify.ts:76](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L76) +[packages/quickjs-emscripten-core/src/module-asyncify.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L77) *** @@ -80,7 +99,7 @@ See the documentation for [QuickJSWASMModule#evalCode](QuickJSWASMModule.md#eval #### Source -[packages/quickjs-emscripten-core/src/module-asyncify.ts:91](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L91) +[packages/quickjs-emscripten-core/src/module-asyncify.ts:92](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L92) *** @@ -104,7 +123,7 @@ and provide the [CustomizeVariantOptions#wasmMemory](../interfaces/CustomizeVari #### Source -[packages/quickjs-emscripten-core/src/module.ts:441](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L441) +[packages/quickjs-emscripten-core/src/module.ts:450](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L450) *** @@ -130,7 +149,7 @@ be disposed when the context is disposed. #### Source -[packages/quickjs-emscripten-core/src/module-asyncify.ts:67](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L67) +[packages/quickjs-emscripten-core/src/module-asyncify.ts:68](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module-asyncify.ts#L68) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSContext.md b/doc/quickjs-emscripten/classes/QuickJSContext.md index 4f967269..df36d81f 100644 --- a/doc/quickjs-emscripten/classes/QuickJSContext.md +++ b/doc/quickjs-emscripten/classes/QuickJSContext.md @@ -47,6 +47,7 @@ See [QuickJSRuntime](QuickJSRuntime.md) for more information. - [Accessors](QuickJSContext.md#accessors) - [alive](QuickJSContext.md#alive) - [false](QuickJSContext.md#false) + - [features](QuickJSContext.md#features) - [global](QuickJSContext.md#global) - [null](QuickJSContext.md#null) - [true](QuickJSContext.md#true) @@ -145,7 +146,7 @@ to create a new QuickJSContext. #### Source -[packages/quickjs-emscripten-core/src/context.ts:233](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L233) +[packages/quickjs-emscripten-core/src/context.ts:242](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L242) ## Properties @@ -157,7 +158,7 @@ The runtime that created this context. #### Source -[packages/quickjs-emscripten-core/src/context.ts:195](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L195) +[packages/quickjs-emscripten-core/src/context.ts:196](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L196) ## Accessors @@ -175,7 +176,7 @@ false after the object has been [dispose](QuickJSContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:264](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L264) +[packages/quickjs-emscripten-core/src/context.ts:273](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L273) *** @@ -191,7 +192,24 @@ false after the object has been [dispose](QuickJSContext.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/context.ts:322](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L322) +[packages/quickjs-emscripten-core/src/context.ts:331](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L331) + +*** + +### features + +> **`get`** **features**(): [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Returns + +[`QuickJSFeatures`](QuickJSFeatures.md) + +#### Source + +[packages/quickjs-emscripten-core/src/context.ts:202](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L202) *** @@ -209,7 +227,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:337](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L337) +[packages/quickjs-emscripten-core/src/context.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L346) *** @@ -225,7 +243,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:296](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L296) +[packages/quickjs-emscripten-core/src/context.ts:305](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L305) *** @@ -241,7 +259,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L309) +[packages/quickjs-emscripten-core/src/context.ts:318](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L318) *** @@ -257,7 +275,7 @@ You can set properties to create global variables. #### Source -[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) +[packages/quickjs-emscripten-core/src/context.ts:292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L292) ## Methods @@ -331,7 +349,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1180](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1180) +[packages/quickjs-emscripten-core/src/context.ts:1195](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1195) #### callFunction(func, thisVal, args) @@ -355,7 +373,7 @@ console.log(context.dump(resultHandle)) // 42 ##### Source -[packages/quickjs-emscripten-core/src/context.ts:1185](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1185) +[packages/quickjs-emscripten-core/src/context.ts:1200](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1200) *** @@ -385,7 +403,7 @@ value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1234](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1234) +[packages/quickjs-emscripten-core/src/context.ts:1249](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1249) *** @@ -414,7 +432,7 @@ socket.on("data", chunk => { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1530](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1530) +[packages/quickjs-emscripten-core/src/context.ts:1545](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1545) *** @@ -445,7 +463,7 @@ Javascript string or number (which will be converted automatically to a JSValue) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) +[packages/quickjs-emscripten-core/src/context.ts:1136](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1136) *** @@ -474,7 +492,7 @@ will result in an error. #### Source -[packages/quickjs-emscripten-core/src/context.ts:274](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L274) +[packages/quickjs-emscripten-core/src/context.ts:283](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L283) *** @@ -496,7 +514,7 @@ Returns `handle.toString()` if it cannot be serialized to JSON. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1355](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1355) +[packages/quickjs-emscripten-core/src/context.ts:1370](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1370) *** @@ -526,7 +544,7 @@ socket.write(dataLifetime?.value) #### Source -[packages/quickjs-emscripten-core/src/context.ts:1513](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1513) +[packages/quickjs-emscripten-core/src/context.ts:1528](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1528) *** @@ -549,7 +567,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:932](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L932) +[packages/quickjs-emscripten-core/src/context.ts:947](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L947) *** @@ -612,7 +630,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:1277](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1277) +[packages/quickjs-emscripten-core/src/context.ts:1292](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1292) *** @@ -630,7 +648,7 @@ interrupted, the error will have name `InternalError` and message #### Source -[packages/quickjs-emscripten-core/src/context.ts:1539](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1539) +[packages/quickjs-emscripten-core/src/context.ts:1554](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1554) *** @@ -650,7 +668,7 @@ Coverts `handle` to a JavaScript ArrayBuffer #### Source -[packages/quickjs-emscripten-core/src/context.ts:811](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L811) +[packages/quickjs-emscripten-core/src/context.ts:826](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L826) *** @@ -670,7 +688,7 @@ Converts `handle` to a Javascript bigint. #### Source -[packages/quickjs-emscripten-core/src/context.ts:802](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L802) +[packages/quickjs-emscripten-core/src/context.ts:816](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L816) *** @@ -702,7 +720,7 @@ for (using entriesHandle of context.getIterator(mapHandle).unwrap()) { #### Source -[packages/quickjs-emscripten-core/src/context.ts:1081](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1081) +[packages/quickjs-emscripten-core/src/context.ts:1096](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1096) *** @@ -733,7 +751,7 @@ a number if the handle has a numeric length property, otherwise `undefined`. #### Source -[packages/quickjs-emscripten-core/src/context.ts:991](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L991) +[packages/quickjs-emscripten-core/src/context.ts:1006](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1006) *** @@ -759,7 +777,7 @@ Converts `handle` into a Javascript number. #### Source -[packages/quickjs-emscripten-core/src/context.ts:773](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L773) +[packages/quickjs-emscripten-core/src/context.ts:786](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L786) *** @@ -809,7 +827,7 @@ QuickJSEmptyGetOwnPropertyNames if no options are set. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1028](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1028) +[packages/quickjs-emscripten-core/src/context.ts:1043](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1043) *** @@ -839,7 +857,7 @@ resultHandle.dispose(); #### Source -[packages/quickjs-emscripten-core/src/context.ts:836](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L836) +[packages/quickjs-emscripten-core/src/context.ts:851](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L851) *** @@ -869,7 +887,7 @@ Javascript string (which will be converted automatically). #### Source -[packages/quickjs-emscripten-core/src/context.ts:961](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L961) +[packages/quickjs-emscripten-core/src/context.ts:976](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L976) *** @@ -893,7 +911,7 @@ Converts `handle` to a Javascript string. #### Source -[packages/quickjs-emscripten-core/src/context.ts:781](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L781) +[packages/quickjs-emscripten-core/src/context.ts:794](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L794) *** @@ -914,7 +932,7 @@ registry in the guest, it will be created with Symbol.for on the host. #### Source -[packages/quickjs-emscripten-core/src/context.ts:790](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L790) +[packages/quickjs-emscripten-core/src/context.ts:803](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L803) *** @@ -934,7 +952,7 @@ Access a well-known symbol that is a property of the global Symbol object, like #### Source -[packages/quickjs-emscripten-core/src/context.ts:397](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L397) +[packages/quickjs-emscripten-core/src/context.ts:408](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L408) *** @@ -951,7 +969,7 @@ Create a new QuickJS [array](https://developer.mozilla.org/en-US/docs/Web/JavaSc #### Source -[packages/quickjs-emscripten-core/src/context.ts:439](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L439) +[packages/quickjs-emscripten-core/src/context.ts:451](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L451) *** @@ -971,7 +989,7 @@ Create a new QuickJS [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/ #### Source -[packages/quickjs-emscripten-core/src/context.ts:447](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L447) +[packages/quickjs-emscripten-core/src/context.ts:459](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L459) *** @@ -991,7 +1009,7 @@ Create a QuickJS [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:405](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L405) +[packages/quickjs-emscripten-core/src/context.ts:416](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L416) *** @@ -1014,7 +1032,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:635](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L635) +[packages/quickjs-emscripten-core/src/context.ts:648](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L648) #### newConstructorFunction(name, fn) @@ -1032,7 +1050,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:636](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L636) +[packages/quickjs-emscripten-core/src/context.ts:649](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L649) *** @@ -1056,7 +1074,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:679](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L679) +[packages/quickjs-emscripten-core/src/context.ts:692](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L692) #### newError(message) @@ -1072,7 +1090,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:680](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L680) +[packages/quickjs-emscripten-core/src/context.ts:693](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L693) #### newError(undefined) @@ -1084,7 +1102,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:681](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L681) +[packages/quickjs-emscripten-core/src/context.ts:694](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L694) *** @@ -1203,7 +1221,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:612](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L612) +[packages/quickjs-emscripten-core/src/context.ts:625](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L625) #### newFunction(name, fn) @@ -1225,7 +1243,7 @@ return deferred.handle ##### Source -[packages/quickjs-emscripten-core/src/context.ts:613](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L613) +[packages/quickjs-emscripten-core/src/context.ts:626](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L626) *** @@ -1254,7 +1272,7 @@ See [newFunction](QuickJSContext.md#newfunction) for more details on how to use #### Source -[packages/quickjs-emscripten-core/src/context.ts:661](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L661) +[packages/quickjs-emscripten-core/src/context.ts:674](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L674) *** @@ -1284,7 +1302,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:716](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L716) +[packages/quickjs-emscripten-core/src/context.ts:729](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L729) *** @@ -1308,7 +1326,7 @@ Converts a Javascript number into a QuickJS value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L356) +[packages/quickjs-emscripten-core/src/context.ts:365](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L365) *** @@ -1335,7 +1353,7 @@ Like [`Object.create`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/R #### Source -[packages/quickjs-emscripten-core/src/context.ts:425](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L425) +[packages/quickjs-emscripten-core/src/context.ts:437](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L437) *** @@ -1356,7 +1374,7 @@ resources; see the documentation on [QuickJSDeferredPromise](QuickJSDeferredProm ##### Source -[packages/quickjs-emscripten-core/src/context.ts:460](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L460) +[packages/quickjs-emscripten-core/src/context.ts:472](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L472) #### newPromise(promise) @@ -1378,7 +1396,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:468](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L468) +[packages/quickjs-emscripten-core/src/context.ts:480](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L480) #### newPromise(newPromiseFn) @@ -1399,7 +1417,7 @@ You can still resolve/reject the created promise "early" using its methods. ##### Source -[packages/quickjs-emscripten-core/src/context.ts:475](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L475) +[packages/quickjs-emscripten-core/src/context.ts:487](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L487) *** @@ -1423,7 +1441,7 @@ Create a QuickJS [string](https://developer.mozilla.org/en-US/docs/Web/JavaScrip #### Source -[packages/quickjs-emscripten-core/src/context.ts:363](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L363) +[packages/quickjs-emscripten-core/src/context.ts:372](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L372) *** @@ -1444,7 +1462,7 @@ All symbols created with the same key will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:386](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L386) +[packages/quickjs-emscripten-core/src/context.ts:396](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L396) *** @@ -1465,7 +1483,7 @@ No two symbols created with this function will be the same value. #### Source -[packages/quickjs-emscripten-core/src/context.ts:374](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L374) +[packages/quickjs-emscripten-core/src/context.ts:383](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L383) *** @@ -1493,7 +1511,7 @@ You may need to call [runtime](QuickJSContext.md#runtime).[QuickJSRuntime#execut #### Source -[packages/quickjs-emscripten-core/src/context.ts:875](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L875) +[packages/quickjs-emscripten-core/src/context.ts:890](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L890) *** @@ -1516,7 +1534,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:940](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L940) +[packages/quickjs-emscripten-core/src/context.ts:955](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L955) *** @@ -1539,7 +1557,7 @@ See [Equality comparisons and sameness](https://developer.mozilla.org/en-US/docs #### Source -[packages/quickjs-emscripten-core/src/context.ts:948](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L948) +[packages/quickjs-emscripten-core/src/context.ts:963](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L963) *** @@ -1576,7 +1594,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1106](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1106) +[packages/quickjs-emscripten-core/src/context.ts:1121](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1121) *** @@ -1598,7 +1616,7 @@ properties. #### Source -[packages/quickjs-emscripten-core/src/context.ts:1535](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1535) +[packages/quickjs-emscripten-core/src/context.ts:1550](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1550) *** @@ -1618,7 +1636,7 @@ Throw an error in the VM, interrupted whatever current execution is in progress #### Source -[packages/quickjs-emscripten-core/src/context.ts:1314](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1314) +[packages/quickjs-emscripten-core/src/context.ts:1329](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1329) *** @@ -1644,7 +1662,7 @@ You must call [HostRef#dispose]([object Object]) or otherwise consume the [HostR #### Source -[packages/quickjs-emscripten-core/src/context.ts:732](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L732) +[packages/quickjs-emscripten-core/src/context.ts:745](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L745) *** @@ -1672,7 +1690,7 @@ Does not support BigInt values correctly. #### Source -[packages/quickjs-emscripten-core/src/context.ts:764](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L764) +[packages/quickjs-emscripten-core/src/context.ts:777](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L777) *** @@ -1700,7 +1718,7 @@ QuickJSHostRefInvalid if `handle` is not a `HostRef.handle` #### Source -[packages/quickjs-emscripten-core/src/context.ts:747](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L747) +[packages/quickjs-emscripten-core/src/context.ts:760](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L760) *** @@ -1727,7 +1745,7 @@ If the result is an error, converts the error to a native object and throws the #### Source -[packages/quickjs-emscripten-core/src/context.ts:1398](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1398) +[packages/quickjs-emscripten-core/src/context.ts:1413](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L1413) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSRuntime.md b/doc/quickjs-emscripten/classes/QuickJSRuntime.md index 7c919940..1920ba68 100644 --- a/doc/quickjs-emscripten/classes/QuickJSRuntime.md +++ b/doc/quickjs-emscripten/classes/QuickJSRuntime.md @@ -36,6 +36,7 @@ Configure ES module loading with [setModuleLoader](QuickJSRuntime.md#setmodulelo - [Implements](QuickJSRuntime.md#implements) - [Properties](QuickJSRuntime.md#properties) - [context](QuickJSRuntime.md#context) + - [features](QuickJSRuntime.md#features) - [Accessors](QuickJSRuntime.md#accessors) - [alive](QuickJSRuntime.md#alive) - [Methods](QuickJSRuntime.md#methods) @@ -79,7 +80,20 @@ A context here may be allocated if one is needed by the runtime, eg for [compute #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L79) +[packages/quickjs-emscripten-core/src/runtime.ts:80](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L80) + +*** + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Source + +[packages/quickjs-emscripten-core/src/runtime.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L86) ## Accessors @@ -97,7 +111,7 @@ false after the object has been [dispose](QuickJSRuntime.md#dispose-1)d #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:128](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L128) +[packages/quickjs-emscripten-core/src/runtime.ts:137](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L137) ## Methods @@ -145,7 +159,7 @@ QuickJSWrongOwner if owned by a different runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:330](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L330) +[packages/quickjs-emscripten-core/src/runtime.ts:340](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L340) *** @@ -165,7 +179,7 @@ For a human-digestible representation, see [dumpMemoryUsage](QuickJSRuntime.md#d #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:299](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L299) +[packages/quickjs-emscripten-core/src/runtime.ts:309](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L309) *** @@ -188,7 +202,7 @@ manipulation if debug logging is disabled. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) +[packages/quickjs-emscripten-core/src/runtime.ts:376](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L376) *** @@ -212,7 +226,7 @@ Dispose of the underlying resources used by this object. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:132](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L132) +[packages/quickjs-emscripten-core/src/runtime.ts:141](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L141) *** @@ -229,7 +243,7 @@ For programmatic access to this information, see [computeMemoryUsage](QuickJSRun #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:310](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L310) +[packages/quickjs-emscripten-core/src/runtime.ts:320](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L320) *** @@ -263,7 +277,7 @@ functions or rejected promises. Those errors are available by calling #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:246](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L246) +[packages/quickjs-emscripten-core/src/runtime.ts:256](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L256) *** @@ -282,7 +296,7 @@ true if there is at least one pendingJob queued up. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:197](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L197) +[packages/quickjs-emscripten-core/src/runtime.ts:207](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L207) *** @@ -298,7 +312,7 @@ true if debug logging is enabled #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) +[packages/quickjs-emscripten-core/src/runtime.ts:366](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L366) *** @@ -322,7 +336,7 @@ You should dispose a created context before disposing this runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:143](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L143) +[packages/quickjs-emscripten-core/src/runtime.ts:152](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L152) *** @@ -339,7 +353,7 @@ See [setInterruptHandler](QuickJSRuntime.md#setinterrupthandler). #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:222](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L222) +[packages/quickjs-emscripten-core/src/runtime.ts:232](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L232) *** @@ -355,7 +369,7 @@ Remove the the loader set by [setModuleLoader](QuickJSRuntime.md#setmoduleloader #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:184](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L184) +[packages/quickjs-emscripten-core/src/runtime.ts:194](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L194) *** @@ -378,7 +392,7 @@ code. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L346) +[packages/quickjs-emscripten-core/src/runtime.ts:356](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L356) *** @@ -402,7 +416,7 @@ The interrupt handler can be removed with [removeInterruptHandler](QuickJSRuntim #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:210](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L210) +[packages/quickjs-emscripten-core/src/runtime.ts:220](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L220) *** @@ -423,7 +437,7 @@ To remove the limit, set to `0`. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:318](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L318) +[packages/quickjs-emscripten-core/src/runtime.ts:328](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L328) *** @@ -444,7 +458,7 @@ To remove the limit, set to `-1`. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:284](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L284) +[packages/quickjs-emscripten-core/src/runtime.ts:294](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L294) *** @@ -469,7 +483,7 @@ The loader can be removed with [removeModuleLoader](QuickJSRuntime.md#removemodu #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:175](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L175) +[packages/quickjs-emscripten-core/src/runtime.ts:184](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L184) *** diff --git a/doc/quickjs-emscripten/classes/QuickJSWASMModule.md b/doc/quickjs-emscripten/classes/QuickJSWASMModule.md index 8e49ce59..1e48d834 100644 --- a/doc/quickjs-emscripten/classes/QuickJSWASMModule.md +++ b/doc/quickjs-emscripten/classes/QuickJSWASMModule.md @@ -25,6 +25,8 @@ inside QuickJS, create a context with [newContext](QuickJSWASMModule.md#newconte ## Contents - [Extended By](QuickJSWASMModule.md#extended-by) +- [Properties](QuickJSWASMModule.md#properties) + - [features](QuickJSWASMModule.md#features) - [Methods](QuickJSWASMModule.md#methods) - [evalCode()](QuickJSWASMModule.md#evalcode) - [getWasmMemory()](QuickJSWASMModule.md#getwasmmemory) @@ -35,6 +37,19 @@ inside QuickJS, create a context with [newContext](QuickJSWASMModule.md#newconte - [`QuickJSAsyncWASMModule`](QuickJSAsyncWASMModule.md) +## Properties + +### features + +> **`readonly`** **features**: [`QuickJSFeatures`](QuickJSFeatures.md) + +Feature detection for this QuickJS variant. +Different builds may have different feature sets (e.g., mquickjs lacks modules, promises). + +#### Source + +[packages/quickjs-emscripten-core/src/module.ts:339](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L339) + ## Methods ### evalCode() @@ -81,7 +96,7 @@ with name `"InternalError"` and message `"interrupted"`. #### Source -[packages/quickjs-emscripten-core/src/module.ts:410](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L410) +[packages/quickjs-emscripten-core/src/module.ts:419](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L419) *** @@ -101,7 +116,7 @@ and provide the [CustomizeVariantOptions#wasmMemory](../interfaces/CustomizeVari #### Source -[packages/quickjs-emscripten-core/src/module.ts:441](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L441) +[packages/quickjs-emscripten-core/src/module.ts:450](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L450) *** @@ -123,7 +138,7 @@ be disposed when the context is disposed. #### Source -[packages/quickjs-emscripten-core/src/module.ts:375](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L375) +[packages/quickjs-emscripten-core/src/module.ts:384](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L384) *** @@ -145,7 +160,7 @@ loading for one or more [QuickJSContext](QuickJSContext.md)s inside the runtime. #### Source -[packages/quickjs-emscripten-core/src/module.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L346) +[packages/quickjs-emscripten-core/src/module.ts:354](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L354) *** diff --git a/doc/quickjs-emscripten/exports.md b/doc/quickjs-emscripten/exports.md index c43ec1b9..acaf8273 100644 --- a/doc/quickjs-emscripten/exports.md +++ b/doc/quickjs-emscripten/exports.md @@ -49,6 +49,7 @@ - [QTS\_C\_To\_HostCallbackFuncPointer](exports.md#qts-c-to-hostcallbackfuncpointer) - [QTS\_C\_To\_HostInterruptFuncPointer](exports.md#qts-c-to-hostinterruptfuncpointer) - [QTS\_C\_To\_HostLoadModuleFuncPointer](exports.md#qts-c-to-hostloadmodulefuncpointer) + - [QuickJSFeature](exports.md#quickjsfeature) - [QuickJSHandle](exports.md#quickjshandle) - [QuickJSPropertyKey](exports.md#quickjspropertykey) - [QuickJSVariant](exports.md#quickjsvariant) @@ -105,6 +106,7 @@ - [QuickJSAsyncWASMModule](classes/QuickJSAsyncWASMModule.md) - [QuickJSContext](classes/QuickJSContext.md) - [QuickJSDeferredPromise](classes/QuickJSDeferredPromise.md) +- [QuickJSFeatures](classes/QuickJSFeatures.md) - [QuickJSRuntime](classes/QuickJSRuntime.md) - [QuickJSWASMModule](classes/QuickJSWASMModule.md) - [Scope](classes/Scope.md) @@ -219,7 +221,7 @@ An `Array` that also implements [Disposable](interfaces/Disposable.md): #### Source -packages/quickjs-ffi-types/dist/index.d.ts:547 +packages/quickjs-ffi-types/dist/index.d.ts:559 *** @@ -245,7 +247,7 @@ by the runtime. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:36](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L36) +[packages/quickjs-emscripten-core/src/runtime.ts:37](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L37) *** @@ -270,7 +272,7 @@ Determines if a VM's execution should be interrupted. #### Source -[packages/quickjs-emscripten-core/src/runtime.ts:28](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L28) +[packages/quickjs-emscripten-core/src/runtime.ts:29](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/runtime.ts#L29) *** @@ -352,7 +354,7 @@ Language features that can be enabled or disabled in a QuickJSContext. #### Source -[packages/quickjs-emscripten-core/src/types.ts:146](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L146) +[packages/quickjs-emscripten-core/src/types.ts:159](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L159) *** @@ -411,7 +413,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:28 #### Source -[packages/quickjs-emscripten-core/src/types.ts:69](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L69) +[packages/quickjs-emscripten-core/src/types.ts:82](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L82) *** @@ -421,7 +423,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:28 #### Source -[packages/quickjs-emscripten-core/src/types.ts:70](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L70) +[packages/quickjs-emscripten-core/src/types.ts:83](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L83) *** @@ -431,7 +433,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:28 #### Source -[packages/quickjs-emscripten-core/src/types.ts:68](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L68) +[packages/quickjs-emscripten-core/src/types.ts:81](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L81) *** @@ -441,7 +443,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:28 #### Source -[packages/quickjs-emscripten-core/src/types.ts:87](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L87) +[packages/quickjs-emscripten-core/src/types.ts:100](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L100) *** @@ -451,7 +453,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:28 #### Source -[packages/quickjs-emscripten-core/src/types.ts:88](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L88) +[packages/quickjs-emscripten-core/src/types.ts:101](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L101) *** @@ -461,7 +463,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:28 #### Source -[packages/quickjs-emscripten-core/src/types.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L86) +[packages/quickjs-emscripten-core/src/types.ts:99](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L99) *** @@ -522,7 +524,7 @@ You can do so from Javascript by calling the .dispose() method. #### Source -[packages/quickjs-emscripten-core/src/types.ts:43](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L43) +[packages/quickjs-emscripten-core/src/types.ts:56](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L56) *** @@ -541,7 +543,7 @@ quickjs-emscripten takes care of disposing JSValueConst references. #### Source -[packages/quickjs-emscripten-core/src/types.ts:26](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L26) +[packages/quickjs-emscripten-core/src/types.ts:39](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L39) *** @@ -668,7 +670,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:75 #### Source -[packages/quickjs-emscripten-core/src/types.ts:333](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L333) +[packages/quickjs-emscripten-core/src/types.ts:346](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L346) *** @@ -722,6 +724,26 @@ packages/quickjs-ffi-types/dist/index.d.ts:65 *** +### QuickJSFeature + +> **QuickJSFeature**: `"modules"` \| `"promises"` \| `"symbols"` \| `"bigint"` \| `"intrinsics"` \| `"eval"` + +Features that may or may not be supported by a QuickJS variant. +Use QuickJSWASMModule#hasSupport to check if a feature is available. + +- `modules`: ES module support (import/export) +- `promises`: Promise and async/await support +- `symbols`: Symbol type support +- `bigint`: BigInt type support +- `intrinsics`: Intrinsics configuration support +- `eval`: eval() function support + +#### Source + +[packages/quickjs-emscripten-core/src/types.ts:21](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L21) + +*** + ### QuickJSHandle > **QuickJSHandle**: [`StaticJSValue`](exports.md#staticjsvalue) \| [`JSValue`](exports.md#jsvalue) \| [`JSValueConst`](exports.md#jsvalueconst) @@ -734,7 +756,7 @@ You must dispose of any handles you create by calling the `.dispose()` method. #### Source -[packages/quickjs-emscripten-core/src/types.ts:53](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L53) +[packages/quickjs-emscripten-core/src/types.ts:66](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L66) *** @@ -747,7 +769,7 @@ Property key for getting or setting a property on a handle with #### Source -[packages/quickjs-emscripten-core/src/context.ts:71](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L71) +[packages/quickjs-emscripten-core/src/context.ts:72](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/context.ts#L72) *** @@ -757,7 +779,7 @@ Property key for getting or setting a property on a handle with #### Source -packages/quickjs-ffi-types/dist/index.d.ts:546 +packages/quickjs-ffi-types/dist/index.d.ts:558 *** @@ -770,7 +792,7 @@ be disposed. #### Source -[packages/quickjs-emscripten-core/src/types.ts:14](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L14) +[packages/quickjs-emscripten-core/src/types.ts:27](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L27) *** @@ -955,7 +977,7 @@ The default [Intrinsics](exports.md#intrinsics) language features enabled in a Q #### Source -[packages/quickjs-emscripten-core/src/types.ts:173](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L173) +[packages/quickjs-emscripten-core/src/types.ts:186](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/types.ts#L186) *** diff --git a/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md b/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md index 463554f6..ce40e56f 100644 --- a/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md +++ b/doc/quickjs-emscripten/interfaces/ModuleEvalOptions.md @@ -27,7 +27,7 @@ To remove the limit, set to `0`. #### Source -[packages/quickjs-emscripten-core/src/module.ts:85](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L85) +[packages/quickjs-emscripten-core/src/module.ts:86](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L86) *** @@ -39,7 +39,7 @@ Memory limit, in bytes, of WebAssembly heap memory used by the QuickJS VM. #### Source -[packages/quickjs-emscripten-core/src/module.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L79) +[packages/quickjs-emscripten-core/src/module.ts:80](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L80) *** @@ -51,7 +51,7 @@ Module loader for any `import` statements or expressions. #### Source -[packages/quickjs-emscripten-core/src/module.ts:90](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L90) +[packages/quickjs-emscripten-core/src/module.ts:91](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L91) *** @@ -64,7 +64,7 @@ See [shouldInterruptAfterDeadline](../exports.md#shouldinterruptafterdeadline). #### Source -[packages/quickjs-emscripten-core/src/module.ts:74](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L74) +[packages/quickjs-emscripten-core/src/module.ts:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/module.ts#L75) *** diff --git a/doc/quickjs-emscripten/interfaces/QuickJSAsyncFFI.md b/doc/quickjs-emscripten/interfaces/QuickJSAsyncFFI.md index 6ddff62b..d657e4d8 100644 --- a/doc/quickjs-emscripten/interfaces/QuickJSAsyncFFI.md +++ b/doc/quickjs-emscripten/interfaces/QuickJSAsyncFFI.md @@ -56,6 +56,12 @@ library. - [QTS\_GetSymbolDescriptionOrKey\_MaybeAsync](QuickJSAsyncFFI.md#qts-getsymboldescriptionorkey-maybeasync) - [QTS\_GetTrue](QuickJSAsyncFFI.md#qts-gettrue) - [QTS\_GetUndefined](QuickJSAsyncFFI.md#qts-getundefined) + - [QTS\_HasBigIntSupport](QuickJSAsyncFFI.md#qts-hasbigintsupport) + - [QTS\_HasEvalSupport](QuickJSAsyncFFI.md#qts-hasevalsupport) + - [QTS\_HasIntrinsicsSupport](QuickJSAsyncFFI.md#qts-hasintrinsicssupport) + - [QTS\_HasModuleSupport](QuickJSAsyncFFI.md#qts-hasmodulesupport) + - [QTS\_HasPromiseSupport](QuickJSAsyncFFI.md#qts-haspromisesupport) + - [QTS\_HasSymbolSupport](QuickJSAsyncFFI.md#qts-hassymbolsupport) - [QTS\_IsEqual](QuickJSAsyncFFI.md#qts-isequal) - [QTS\_IsGlobalSymbol](QuickJSAsyncFFI.md#qts-isglobalsymbol) - [QTS\_IsJobPending](QuickJSAsyncFFI.md#qts-isjobpending) @@ -107,7 +113,7 @@ Set at compile time. #### Source -packages/quickjs-ffi-types/dist/index.d.ts:431 +packages/quickjs-ffi-types/dist/index.d.ts:437 *** @@ -127,7 +133,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:431 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:502 +packages/quickjs-ffi-types/dist/index.d.ts:514 *** @@ -141,7 +147,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:502 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:500 +packages/quickjs-ffi-types/dist/index.d.ts:506 *** @@ -155,7 +161,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:500 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:499 +packages/quickjs-ffi-types/dist/index.d.ts:505 *** @@ -169,7 +175,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:499 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:438 +packages/quickjs-ffi-types/dist/index.d.ts:444 *** @@ -195,7 +201,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:438 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:481 +packages/quickjs-ffi-types/dist/index.d.ts:487 *** @@ -221,7 +227,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:481 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:482 +packages/quickjs-ffi-types/dist/index.d.ts:488 *** @@ -255,7 +261,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:482 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:478 +packages/quickjs-ffi-types/dist/index.d.ts:484 *** @@ -275,7 +281,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:478 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:484 +packages/quickjs-ffi-types/dist/index.d.ts:490 *** @@ -295,7 +301,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:484 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:485 +packages/quickjs-ffi-types/dist/index.d.ts:491 *** @@ -315,7 +321,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:485 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:454 +packages/quickjs-ffi-types/dist/index.d.ts:460 *** @@ -343,7 +349,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:454 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:486 +packages/quickjs-ffi-types/dist/index.d.ts:492 *** @@ -371,7 +377,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:486 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:487 +packages/quickjs-ffi-types/dist/index.d.ts:493 *** @@ -393,7 +399,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:487 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:470 +packages/quickjs-ffi-types/dist/index.d.ts:476 *** @@ -415,7 +421,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:470 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:471 +packages/quickjs-ffi-types/dist/index.d.ts:477 *** @@ -435,7 +441,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:471 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:453 +packages/quickjs-ffi-types/dist/index.d.ts:459 *** @@ -453,7 +459,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:453 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:449 +packages/quickjs-ffi-types/dist/index.d.ts:455 *** @@ -471,7 +477,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:449 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:447 +packages/quickjs-ffi-types/dist/index.d.ts:453 *** @@ -491,7 +497,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:447 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:450 +packages/quickjs-ffi-types/dist/index.d.ts:456 *** @@ -511,7 +517,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:450 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:451 +packages/quickjs-ffi-types/dist/index.d.ts:457 *** @@ -531,7 +537,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:451 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:452 +packages/quickjs-ffi-types/dist/index.d.ts:458 *** @@ -551,7 +557,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:452 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:463 +packages/quickjs-ffi-types/dist/index.d.ts:469 *** @@ -571,7 +577,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:463 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:464 +packages/quickjs-ffi-types/dist/index.d.ts:470 *** @@ -589,7 +595,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:464 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:497 +packages/quickjs-ffi-types/dist/index.d.ts:503 *** @@ -603,7 +609,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:497 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:442 +packages/quickjs-ffi-types/dist/index.d.ts:448 *** @@ -623,7 +629,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:442 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:460 +packages/quickjs-ffi-types/dist/index.d.ts:466 *** @@ -641,7 +647,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:460 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:492 +packages/quickjs-ffi-types/dist/index.d.ts:498 *** @@ -659,7 +665,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:492 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:445 +packages/quickjs-ffi-types/dist/index.d.ts:451 *** @@ -681,7 +687,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:445 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:490 +packages/quickjs-ffi-types/dist/index.d.ts:496 *** @@ -701,7 +707,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:490 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:488 +packages/quickjs-ffi-types/dist/index.d.ts:494 *** @@ -715,7 +721,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:488 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:441 +packages/quickjs-ffi-types/dist/index.d.ts:447 *** @@ -741,7 +747,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:441 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:479 +packages/quickjs-ffi-types/dist/index.d.ts:485 *** @@ -767,7 +773,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:479 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:480 +packages/quickjs-ffi-types/dist/index.d.ts:486 *** @@ -789,7 +795,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:480 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:472 +packages/quickjs-ffi-types/dist/index.d.ts:478 *** @@ -811,7 +817,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:472 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:474 +packages/quickjs-ffi-types/dist/index.d.ts:480 *** @@ -833,7 +839,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:474 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:475 +packages/quickjs-ffi-types/dist/index.d.ts:481 *** @@ -855,7 +861,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:475 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:473 +packages/quickjs-ffi-types/dist/index.d.ts:479 *** @@ -875,7 +881,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:473 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:462 +packages/quickjs-ffi-types/dist/index.d.ts:468 *** @@ -895,7 +901,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:462 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:466 +packages/quickjs-ffi-types/dist/index.d.ts:472 *** @@ -915,7 +921,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:466 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:467 +packages/quickjs-ffi-types/dist/index.d.ts:473 *** @@ -929,7 +935,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:467 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:443 +packages/quickjs-ffi-types/dist/index.d.ts:449 *** @@ -943,7 +949,91 @@ packages/quickjs-ffi-types/dist/index.d.ts:443 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:440 +packages/quickjs-ffi-types/dist/index.d.ts:446 + +*** + +### QTS\_HasBigIntSupport + +> **QTS\_HasBigIntSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:510 + +*** + +### QTS\_HasEvalSupport + +> **QTS\_HasEvalSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:512 + +*** + +### QTS\_HasIntrinsicsSupport + +> **QTS\_HasIntrinsicsSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:511 + +*** + +### QTS\_HasModuleSupport + +> **QTS\_HasModuleSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:507 + +*** + +### QTS\_HasPromiseSupport + +> **QTS\_HasPromiseSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:508 + +*** + +### QTS\_HasSymbolSupport + +> **QTS\_HasSymbolSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:509 *** @@ -967,7 +1057,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:440 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:491 +packages/quickjs-ffi-types/dist/index.d.ts:497 *** @@ -987,7 +1077,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:491 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:468 +packages/quickjs-ffi-types/dist/index.d.ts:474 *** @@ -1005,7 +1095,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:468 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:469 +packages/quickjs-ffi-types/dist/index.d.ts:475 *** @@ -1023,7 +1113,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:469 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:457 +packages/quickjs-ffi-types/dist/index.d.ts:463 *** @@ -1045,7 +1135,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:457 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:458 +packages/quickjs-ffi-types/dist/index.d.ts:464 *** @@ -1065,7 +1155,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:458 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:448 +packages/quickjs-ffi-types/dist/index.d.ts:454 *** @@ -1083,7 +1173,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:448 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:433 +packages/quickjs-ffi-types/dist/index.d.ts:439 *** @@ -1103,7 +1193,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:433 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:459 +packages/quickjs-ffi-types/dist/index.d.ts:465 *** @@ -1129,7 +1219,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:459 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:501 +packages/quickjs-ffi-types/dist/index.d.ts:513 *** @@ -1149,7 +1239,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:501 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:444 +packages/quickjs-ffi-types/dist/index.d.ts:450 *** @@ -1167,7 +1257,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:444 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:455 +packages/quickjs-ffi-types/dist/index.d.ts:461 *** @@ -1187,7 +1277,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:455 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:456 +packages/quickjs-ffi-types/dist/index.d.ts:462 *** @@ -1207,7 +1297,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:456 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:493 +packages/quickjs-ffi-types/dist/index.d.ts:499 *** @@ -1221,7 +1311,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:493 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:446 +packages/quickjs-ffi-types/dist/index.d.ts:452 *** @@ -1241,7 +1331,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:446 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:461 +packages/quickjs-ffi-types/dist/index.d.ts:467 *** @@ -1263,7 +1353,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:461 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:465 +packages/quickjs-ffi-types/dist/index.d.ts:471 *** @@ -1283,7 +1373,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:465 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:495 +packages/quickjs-ffi-types/dist/index.d.ts:501 *** @@ -1303,7 +1393,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:495 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:494 +packages/quickjs-ffi-types/dist/index.d.ts:500 *** @@ -1317,7 +1407,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:494 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:437 +packages/quickjs-ffi-types/dist/index.d.ts:443 *** @@ -1337,7 +1427,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:437 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:483 +packages/quickjs-ffi-types/dist/index.d.ts:489 *** @@ -1357,7 +1447,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:483 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:435 +packages/quickjs-ffi-types/dist/index.d.ts:441 *** @@ -1375,7 +1465,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:435 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:504 +packages/quickjs-ffi-types/dist/index.d.ts:516 *** @@ -1393,7 +1483,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:504 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:506 +packages/quickjs-ffi-types/dist/index.d.ts:518 *** @@ -1411,7 +1501,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:506 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:436 +packages/quickjs-ffi-types/dist/index.d.ts:442 *** @@ -1429,7 +1519,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:436 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:503 +packages/quickjs-ffi-types/dist/index.d.ts:515 *** @@ -1449,7 +1539,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:503 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:505 +packages/quickjs-ffi-types/dist/index.d.ts:517 *** @@ -1469,7 +1559,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:505 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:439 +packages/quickjs-ffi-types/dist/index.d.ts:445 *** @@ -1489,7 +1579,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:439 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:434 +packages/quickjs-ffi-types/dist/index.d.ts:440 *** @@ -1509,7 +1599,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:434 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:498 +packages/quickjs-ffi-types/dist/index.d.ts:504 *** @@ -1533,7 +1623,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:498 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:476 +packages/quickjs-ffi-types/dist/index.d.ts:482 *** @@ -1557,7 +1647,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:476 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:477 +packages/quickjs-ffi-types/dist/index.d.ts:483 *** @@ -1575,7 +1665,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:477 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:496 +packages/quickjs-ffi-types/dist/index.d.ts:502 *** @@ -1595,7 +1685,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:496 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:432 +packages/quickjs-ffi-types/dist/index.d.ts:438 *** @@ -1615,7 +1705,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:432 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:489 +packages/quickjs-ffi-types/dist/index.d.ts:495 *** @@ -1635,7 +1725,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:489 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:508 +packages/quickjs-ffi-types/dist/index.d.ts:520 *** @@ -1655,7 +1745,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:508 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:507 +packages/quickjs-ffi-types/dist/index.d.ts:519 *** diff --git a/doc/quickjs-emscripten/interfaces/QuickJSAsyncVariant.md b/doc/quickjs-emscripten/interfaces/QuickJSAsyncVariant.md index 8063569c..86bbfdcb 100644 --- a/doc/quickjs-emscripten/interfaces/QuickJSAsyncVariant.md +++ b/doc/quickjs-emscripten/interfaces/QuickJSAsyncVariant.md @@ -36,7 +36,7 @@ build variant to [newQuickJSWASMModule](../exports.md#newquickjswasmmodule) or [ #### Source -packages/quickjs-ffi-types/dist/index.d.ts:543 +packages/quickjs-ffi-types/dist/index.d.ts:555 *** @@ -50,7 +50,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:543 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:544 +packages/quickjs-ffi-types/dist/index.d.ts:556 *** @@ -60,7 +60,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:544 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:542 +packages/quickjs-ffi-types/dist/index.d.ts:554 *** diff --git a/doc/quickjs-emscripten/interfaces/QuickJSFFI.md b/doc/quickjs-emscripten/interfaces/QuickJSFFI.md index 43fb7f94..33865414 100644 --- a/doc/quickjs-emscripten/interfaces/QuickJSFFI.md +++ b/doc/quickjs-emscripten/interfaces/QuickJSFFI.md @@ -48,6 +48,12 @@ library. - [QTS\_GetSymbolDescriptionOrKey](QuickJSFFI.md#qts-getsymboldescriptionorkey) - [QTS\_GetTrue](QuickJSFFI.md#qts-gettrue) - [QTS\_GetUndefined](QuickJSFFI.md#qts-getundefined) + - [QTS\_HasBigIntSupport](QuickJSFFI.md#qts-hasbigintsupport) + - [QTS\_HasEvalSupport](QuickJSFFI.md#qts-hasevalsupport) + - [QTS\_HasIntrinsicsSupport](QuickJSFFI.md#qts-hasintrinsicssupport) + - [QTS\_HasModuleSupport](QuickJSFFI.md#qts-hasmodulesupport) + - [QTS\_HasPromiseSupport](QuickJSFFI.md#qts-haspromisesupport) + - [QTS\_HasSymbolSupport](QuickJSFFI.md#qts-hassymbolsupport) - [QTS\_IsEqual](QuickJSFFI.md#qts-isequal) - [QTS\_IsGlobalSymbol](QuickJSFFI.md#qts-isglobalsymbol) - [QTS\_IsJobPending](QuickJSFFI.md#qts-isjobpending) @@ -118,7 +124,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:351 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:413 +packages/quickjs-ffi-types/dist/index.d.ts:419 *** @@ -752,6 +758,90 @@ packages/quickjs-ffi-types/dist/index.d.ts:360 *** +### QTS\_HasBigIntSupport + +> **QTS\_HasBigIntSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:415 + +*** + +### QTS\_HasEvalSupport + +> **QTS\_HasEvalSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:417 + +*** + +### QTS\_HasIntrinsicsSupport + +> **QTS\_HasIntrinsicsSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:416 + +*** + +### QTS\_HasModuleSupport + +> **QTS\_HasModuleSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:412 + +*** + +### QTS\_HasPromiseSupport + +> **QTS\_HasPromiseSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:413 + +*** + +### QTS\_HasSymbolSupport + +> **QTS\_HasSymbolSupport**: () => `number` + +#### Returns + +`number` + +#### Source + +packages/quickjs-ffi-types/dist/index.d.ts:414 + +*** + ### QTS\_IsEqual > **QTS\_IsEqual**: (`ctx`, `a`, `b`, `op`) => `number` @@ -934,7 +1024,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:379 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:412 +packages/quickjs-ffi-types/dist/index.d.ts:418 *** @@ -1180,7 +1270,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:355 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:415 +packages/quickjs-ffi-types/dist/index.d.ts:421 *** @@ -1198,7 +1288,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:415 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:417 +packages/quickjs-ffi-types/dist/index.d.ts:423 *** @@ -1234,7 +1324,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:356 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:414 +packages/quickjs-ffi-types/dist/index.d.ts:420 *** @@ -1254,7 +1344,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:414 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:416 +packages/quickjs-ffi-types/dist/index.d.ts:422 *** @@ -1416,7 +1506,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:400 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:419 +packages/quickjs-ffi-types/dist/index.d.ts:425 *** @@ -1436,7 +1526,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:419 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:418 +packages/quickjs-ffi-types/dist/index.d.ts:424 *** diff --git a/doc/quickjs-emscripten/interfaces/QuickJSSyncVariant.md b/doc/quickjs-emscripten/interfaces/QuickJSSyncVariant.md index 8c0b7fc6..cc7c9e2f 100644 --- a/doc/quickjs-emscripten/interfaces/QuickJSSyncVariant.md +++ b/doc/quickjs-emscripten/interfaces/QuickJSSyncVariant.md @@ -36,7 +36,7 @@ build variant to [newQuickJSWASMModule](../exports.md#newquickjswasmmodule) or [ #### Source -packages/quickjs-ffi-types/dist/index.d.ts:529 +packages/quickjs-ffi-types/dist/index.d.ts:541 *** @@ -50,7 +50,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:529 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:530 +packages/quickjs-ffi-types/dist/index.d.ts:542 *** @@ -60,7 +60,7 @@ packages/quickjs-ffi-types/dist/index.d.ts:530 #### Source -packages/quickjs-ffi-types/dist/index.d.ts:528 +packages/quickjs-ffi-types/dist/index.d.ts:540 *** diff --git a/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md b/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md index 91827fd7..338e1e8b 100644 --- a/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md +++ b/doc/quickjs-emscripten/namespaces/errors/classes/QuickJSUnsupported.md @@ -54,7 +54,7 @@ The name of the variant that doesn't support this feature #### Source -[packages/quickjs-emscripten-core/src/errors.ts:67](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L67) +[packages/quickjs-emscripten-core/src/errors.ts:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L75) ## Properties @@ -66,7 +66,7 @@ The feature that is not supported #### Source -[packages/quickjs-emscripten-core/src/errors.ts:69](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L69) +[packages/quickjs-emscripten-core/src/errors.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L77) *** @@ -80,7 +80,7 @@ The feature that is not supported #### Source -[packages/quickjs-emscripten-core/src/errors.ts:65](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L65) +[packages/quickjs-emscripten-core/src/errors.ts:73](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L73) *** @@ -92,7 +92,7 @@ The operation that was attempted, if specified #### Source -[packages/quickjs-emscripten-core/src/errors.ts:71](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L71) +[packages/quickjs-emscripten-core/src/errors.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L79) *** @@ -104,7 +104,7 @@ The name of the variant that doesn't support this feature #### Source -[packages/quickjs-emscripten-core/src/errors.ts:73](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L73) +[packages/quickjs-emscripten-core/src/errors.ts:81](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L81) *** diff --git a/packages/quickjs-emscripten-core/README.md b/packages/quickjs-emscripten-core/README.md index 1a6b3733..29624e40 100644 --- a/packages/quickjs-emscripten-core/README.md +++ b/packages/quickjs-emscripten-core/README.md @@ -178,7 +178,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | @@ -191,7 +191,7 @@ Variant with separate .WASM file. Supports browser ESM, NodeJS ESM, and NodeJS C | Variable | Setting | Description | | ------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | +| library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | | releaseMode | release | Optimized for performance; use when building/deploying your application. | | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/packages/quickjs-ffi-types/src/ffi-async.ts b/packages/quickjs-ffi-types/src/ffi-async.ts index e3aa0ad6..685e6815 100644 --- a/packages/quickjs-ffi-types/src/ffi-async.ts +++ b/packages/quickjs-ffi-types/src/ffi-async.ts @@ -249,6 +249,12 @@ export interface QuickJSAsyncFFI { QTS_SetDebugLogEnabled: (rt: JSRuntimePointer, is_enabled: number) => void QTS_BuildIsDebug: () => number QTS_BuildIsAsyncify: () => number + QTS_HasModuleSupport: () => number + QTS_HasPromiseSupport: () => number + QTS_HasSymbolSupport: () => number + QTS_HasBigIntSupport: () => number + QTS_HasIntrinsicsSupport: () => number + QTS_HasEvalSupport: () => number QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/quickjs-ffi-types/src/ffi.ts b/packages/quickjs-ffi-types/src/ffi.ts index bf9e02c6..0722667a 100644 --- a/packages/quickjs-ffi-types/src/ffi.ts +++ b/packages/quickjs-ffi-types/src/ffi.ts @@ -197,6 +197,12 @@ export interface QuickJSFFI { QTS_SetDebugLogEnabled: (rt: JSRuntimePointer, is_enabled: number) => void QTS_BuildIsDebug: () => number QTS_BuildIsAsyncify: () => number + QTS_HasModuleSupport: () => number + QTS_HasPromiseSupport: () => number + QTS_HasSymbolSupport: () => number + QTS_HasBigIntSupport: () => number + QTS_HasIntrinsicsSupport: () => number + QTS_HasEvalSupport: () => number QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/Makefile b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile index 77af35c5..17c32721 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/Makefile +++ b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile @@ -28,9 +28,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) CFLAGS_WASM+=-DQTS_USE_MQUICKJS INTERFACE_SOURCE=interface-mquickjs.c else ifeq ($(QUICKJS_LIB),quickjs-ng) - # quickjs-ng uses different source files than bellard/quickjs - QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o - QUICKJS_DEFINES:=-D_GNU_SOURCE + # quickjs-ng uses amalgamated build (single source file) + # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) + QUICKJS_OBJS=quickjs-amalgam.o + QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) + QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG INTERFACE_SOURCE=interface.c else diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/README.md b/packages/variant-mquickjs-wasmfile-debug-sync/README.md index 195f2559..21fadfb3 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/README.md +++ b/packages/variant-mquickjs-wasmfile-debug-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. -Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. ## Release mode: debug diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts index 9c5141a6..f879c8fb 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts @@ -23,6 +23,7 @@ import { EvalDetectModule, GetOwnPropertyNamesFlags, IsEqualOp, + HostRefId, JSPromiseStateEnum, } from "@jitl/quickjs-ffi-types" @@ -83,6 +84,18 @@ export class QuickJSFFI { QTS_GetTrue: () => JSValueConstPointer = this.module.cwrap("QTS_GetTrue", "number", []) + QTS_NewHostRef: (ctx: JSContextPointer, id: HostRefId) => JSValuePointer = this.module.cwrap( + "QTS_NewHostRef", + "number", + ["number", "number"], + ) + + QTS_GetHostRefId: (value: JSValuePointer | JSValueConstPointer) => HostRefId = this.module.cwrap( + "QTS_GetHostRefId", + "number", + ["number"], + ) + QTS_NewRuntime: () => JSRuntimePointer = this.module.cwrap("QTS_NewRuntime", "number", []) QTS_FreeRuntime: (rt: JSRuntimePointer) => void = this.module.cwrap("QTS_FreeRuntime", null, [ @@ -391,8 +404,19 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - QTS_NewFunction: (ctx: JSContextPointer, func_id: number, name: string) => JSValuePointer = - this.module.cwrap("QTS_NewFunction", "number", ["number", "number", "string"]) + QTS_NewFunction: ( + ctx: JSContextPointer, + name: string, + arg_length: number, + is_constructor: boolean, + host_ref_id: HostRefId, + ) => JSValuePointer = this.module.cwrap("QTS_NewFunction", "number", [ + "number", + "string", + "number", + "boolean", + "number", + ]) QTS_ArgvGetJSValueConstPointer: ( argv: JSValuePointer | JSValueConstPointer, diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts index 5b1d7977..11572607 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | + * | library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | * | releaseMode | debug | Enables assertions and memory sanitizers. Try to run your tests against debug variants, in addition to your preferred production variant, to catch more bugs. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/packages/variant-mquickjs-wasmfile-release-sync/Makefile b/packages/variant-mquickjs-wasmfile-release-sync/Makefile index aadaa1aa..6bfcb3f6 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/Makefile +++ b/packages/variant-mquickjs-wasmfile-release-sync/Makefile @@ -28,9 +28,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) CFLAGS_WASM+=-DQTS_USE_MQUICKJS INTERFACE_SOURCE=interface-mquickjs.c else ifeq ($(QUICKJS_LIB),quickjs-ng) - # quickjs-ng uses different source files than bellard/quickjs - QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o - QUICKJS_DEFINES:=-D_GNU_SOURCE + # quickjs-ng uses amalgamated build (single source file) + # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) + QUICKJS_OBJS=quickjs-amalgam.o + QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) + QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG INTERFACE_SOURCE=interface.c else diff --git a/packages/variant-mquickjs-wasmfile-release-sync/README.md b/packages/variant-mquickjs-wasmfile-release-sync/README.md index 42f1236a..cd382f47 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/README.md +++ b/packages/variant-mquickjs-wasmfile-release-sync/README.md @@ -17,7 +17,7 @@ This variant was built with the following settings: [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. -Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. +Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. ## Release mode: release diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts index 86932db9..45ef57f8 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts @@ -23,6 +23,7 @@ import { EvalDetectModule, GetOwnPropertyNamesFlags, IsEqualOp, + HostRefId, JSPromiseStateEnum, } from "@jitl/quickjs-ffi-types" @@ -83,6 +84,18 @@ export class QuickJSFFI { QTS_GetTrue: () => JSValueConstPointer = this.module.cwrap("QTS_GetTrue", "number", []) + QTS_NewHostRef: (ctx: JSContextPointer, id: HostRefId) => JSValuePointer = this.module.cwrap( + "QTS_NewHostRef", + "number", + ["number", "number"], + ) + + QTS_GetHostRefId: (value: JSValuePointer | JSValueConstPointer) => HostRefId = this.module.cwrap( + "QTS_GetHostRefId", + "number", + ["number"], + ) + QTS_NewRuntime: () => JSRuntimePointer = this.module.cwrap("QTS_NewRuntime", "number", []) QTS_FreeRuntime: (rt: JSRuntimePointer) => void = this.module.cwrap("QTS_FreeRuntime", null, [ @@ -391,8 +404,19 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - QTS_NewFunction: (ctx: JSContextPointer, func_id: number, name: string) => JSValuePointer = - this.module.cwrap("QTS_NewFunction", "number", ["number", "number", "string"]) + QTS_NewFunction: ( + ctx: JSContextPointer, + name: string, + arg_length: number, + is_constructor: boolean, + host_ref_id: HostRefId, + ) => JSValuePointer = this.module.cwrap("QTS_NewFunction", "number", [ + "number", + "string", + "number", + "boolean", + "number", + ]) QTS_ArgvGetJSValueConstPointer: ( argv: JSValuePointer | JSValueConstPointer, diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts index ac6ba8bf..98f031d9 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts @@ -8,7 +8,7 @@ import type { QuickJSSyncVariant } from "@jitl/quickjs-ffi-types" * * | Variable | Setting | Description | * | -- | -- | -- | - * | library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-15. | + * | library | mquickjs | [mquickjs](https://github.com/bellard/mquickjs) is a minimal/micro version of QuickJS by Fabrice Bellard, optimized for small size. Version [git+ee50431e](https://github.com/bellard/mquickjs/commit/ee50431eac9b14b99f722b537ec4cac0c8dd75ab) vendored to quickjs-emscripten on 2026-02-16. | * | releaseMode | release | Optimized for performance; use when building/deploying your application. | * | syncMode | sync | The default, normal build. Note that both variants support regular async functions. | * | emscriptenInclusion | wasm | Has a separate .wasm file. May offer better caching in your browser, and reduces the size of your JS bundle. If you have issues, try a 'singlefile' variant. | diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile b/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile index 829bbcd1..bd0c31d0 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -164,6 +172,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts b/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts index 17e6ccac..45ef57f8 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile index 3f6fd189..94e348cb 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -173,6 +181,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts index a154293f..e7f02bb1 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts @@ -495,6 +495,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile b/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile index e6899c27..786bfcea 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -167,6 +175,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts index 6740134d..f879c8fb 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile b/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile index c29e30b1..451aa094 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -168,6 +176,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts index e89c9b41..fb6454c2 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts @@ -492,6 +492,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile b/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile index 1f9b8cd7..440480ef 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -161,6 +169,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts index 17e6ccac..45ef57f8 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile b/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile index 6b064d51..888200be 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -174,6 +182,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts index a154293f..e7f02bb1 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts @@ -495,6 +495,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile b/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile index b0c4f623..932c2c20 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -168,6 +176,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts index 6740134d..f879c8fb 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile b/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile index c8c816d9..f30099fa 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -169,6 +177,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts index e89c9b41..fb6454c2 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts @@ -492,6 +492,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/Makefile b/packages/variant-quickjs-singlefile-browser-release-sync/Makefile index ed64c0f0..d3b52ce4 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-browser-release-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -162,6 +170,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts index 17e6ccac..45ef57f8 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile index e5bf664b..df901314 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -174,6 +182,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts index a154293f..e7f02bb1 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts @@ -495,6 +495,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile b/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile index db00533f..e68390d7 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -168,6 +176,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts index 6740134d..f879c8fb 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile b/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile index 95831d60..f42417e0 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -169,6 +177,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts index e89c9b41..fb6454c2 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts @@ -492,6 +492,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile b/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile index 72e18701..db68e6a2 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -162,6 +170,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts index 17e6ccac..45ef57f8 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile index ff7a1084..a27398c4 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -174,6 +182,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts index a154293f..e7f02bb1 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts @@ -495,6 +495,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile b/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile index ff580933..e73874e2 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -168,6 +176,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts index 6740134d..f879c8fb 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile b/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile index 8323b760..a2ccb06d 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -169,6 +177,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts index e89c9b41..fb6454c2 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts @@ -492,6 +492,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile b/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile index daedba45..4b747abd 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -162,6 +170,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts index 17e6ccac..45ef57f8 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile b/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile index 7096239a..f0c3f906 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -173,6 +181,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts index a154293f..e7f02bb1 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts @@ -495,6 +495,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-debug-sync/Makefile b/packages/variant-quickjs-wasmfile-debug-sync/Makefile index 81bac19c..d12844b8 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/Makefile +++ b/packages/variant-quickjs-wasmfile-debug-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -167,6 +175,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts index 6740134d..f879c8fb 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/Makefile b/packages/variant-quickjs-wasmfile-release-asyncify/Makefile index 675864a6..57323cbc 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/Makefile +++ b/packages/variant-quickjs-wasmfile-release-asyncify/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -168,6 +176,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts b/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts index e89c9b41..fb6454c2 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts @@ -492,6 +492,22 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-release-sync/Makefile b/packages/variant-quickjs-wasmfile-release-sync/Makefile index d5110712..7dfea003 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/Makefile +++ b/packages/variant-quickjs-wasmfile-release-sync/Makefile @@ -21,18 +21,26 @@ BUILD_QUICKJS=$(BUILD_ROOT)/quickjs DIST=dist # QuickJS -ifeq ($(QUICKJS_LIB),quickjs-ng) +ifeq ($(QUICKJS_LIB),mquickjs) + # mquickjs is a minimal QuickJS fork without modules, promises, symbols, bigint + QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o + QUICKJS_DEFINES:=-D_GNU_SOURCE + CFLAGS_WASM+=-DQTS_USE_MQUICKJS + INTERFACE_SOURCE=interface-mquickjs.c +else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) QUICKJS_OBJS=quickjs-amalgam.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG + INTERFACE_SOURCE=interface.c else # bellard/quickjs uses separate source files QUICKJS_OBJS=quickjs.o dtoa.o libregexp.o libunicode.o cutils.o quickjs-libc.o QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) QUICKJS_DEFINES:=-D_GNU_SOURCE -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" -DCONFIG_STACK_CHECK + INTERFACE_SOURCE=interface.c endif VARIANT_QUICKJS_OBJS=$(patsubst %.o, $(BUILD_QUICKJS)/%.o, $(QUICKJS_OBJS)) @@ -161,6 +169,13 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ ############################################################################### # Emscripten intermediate files WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json + +# Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) + $(MKDIRP) + $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< diff --git a/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts index 17e6ccac..45ef57f8 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts @@ -388,6 +388,22 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) + QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) + + QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) + + QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) + + QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) + + QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( + "QTS_HasIntrinsicsSupport", + "number", + [], + ) + + QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/templates/Variant.mk b/templates/Variant.mk index dc25276c..8d609edd 100644 --- a/templates/Variant.mk +++ b/templates/Variant.mk @@ -28,9 +28,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) CFLAGS_WASM+=-DQTS_USE_MQUICKJS INTERFACE_SOURCE=interface-mquickjs.c else ifeq ($(QUICKJS_LIB),quickjs-ng) - # quickjs-ng uses different source files than bellard/quickjs - QUICKJS_OBJS=quickjs.o libbf.o libregexp.o libunicode.o cutils.o quickjs-libc.o - QUICKJS_DEFINES:=-D_GNU_SOURCE + # quickjs-ng uses amalgamated build (single source file) + # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) + QUICKJS_OBJS=quickjs-amalgam.o + QUICKJS_CONFIG_VERSION=$(shell cat $(QUICKJS_ROOT)/VERSION) + QUICKJS_DEFINES:=-D_GNU_SOURCE -DQJS_BUILD_LIBC -DCONFIG_VERSION=\"$(QUICKJS_CONFIG_VERSION)\" CFLAGS_WASM+=-DQTS_USE_QUICKJS_NG INTERFACE_SOURCE=interface.c else diff --git a/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch b/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch index 11241b1a..c02dd417 100644 --- a/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch +++ b/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch @@ -1,15 +1,11 @@ -Add JS_GetContextOpaque function to mquickjs - -mquickjs has JS_SetContextOpaque but was missing JS_GetContextOpaque. -This is needed by quickjs-emscripten to store runtime data in the context. - -diff --git a/mquickjs.c b/mquickjs.c ---- a/mquickjs.c -+++ b/mquickjs.c -@@ -3674,6 +3674,11 @@ void JS_SetContextOpaque(JSContext *ctx, void *opaque) +diff --git a/vendor/mquickjs/mquickjs.c b/vendor/mquickjs/mquickjs.c +index a950f3c..3cd7aff 100644 +--- a/vendor/mquickjs/mquickjs.c ++++ b/vendor/mquickjs/mquickjs.c +@@ -3675,6 +3675,11 @@ void JS_SetContextOpaque(JSContext *ctx, void *opaque) ctx->opaque = opaque; } - + +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->opaque; @@ -18,11 +14,15 @@ diff --git a/mquickjs.c b/mquickjs.c void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler) { ctx->interrupt_handler = interrupt_handler; -diff --git a/mquickjs.h b/mquickjs.h ---- a/mquickjs.h -+++ b/mquickjs.h -@@ -266,6 +266,7 @@ JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDe +diff --git a/vendor/mquickjs/mquickjs.h b/vendor/mquickjs/mquickjs.h +index a1557fe..cf12c95 100644 +--- a/vendor/mquickjs/mquickjs.h ++++ b/vendor/mquickjs/mquickjs.h +@@ -264,6 +264,7 @@ JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef + JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, JS_BOOL prepare_compilation); void JS_FreeContext(JSContext *ctx); void JS_SetContextOpaque(JSContext *ctx, void *opaque); +void *JS_GetContextOpaque(JSContext *ctx); void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler); + void JS_SetRandomSeed(JSContext *ctx, uint64_t seed); + JSValue JS_GetGlobalObject(JSContext *ctx); diff --git a/vendor/mquickjs/mquickjs.c b/vendor/mquickjs/mquickjs.c index a950f3c3..3cd7affb 100644 --- a/vendor/mquickjs/mquickjs.c +++ b/vendor/mquickjs/mquickjs.c @@ -3675,6 +3675,11 @@ void JS_SetContextOpaque(JSContext *ctx, void *opaque) ctx->opaque = opaque; } +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->opaque; +} + void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler) { ctx->interrupt_handler = interrupt_handler; diff --git a/vendor/mquickjs/mquickjs.h b/vendor/mquickjs/mquickjs.h index a1557fe9..cf12c955 100644 --- a/vendor/mquickjs/mquickjs.h +++ b/vendor/mquickjs/mquickjs.h @@ -264,6 +264,7 @@ JSContext *JS_NewContext(void *mem_start, size_t mem_size, const JSSTDLibraryDef JSContext *JS_NewContext2(void *mem_start, size_t mem_size, const JSSTDLibraryDef *stdlib_def, JS_BOOL prepare_compilation); void JS_FreeContext(JSContext *ctx); void JS_SetContextOpaque(JSContext *ctx, void *opaque); +void *JS_GetContextOpaque(JSContext *ctx); void JS_SetInterruptHandler(JSContext *ctx, JSInterruptHandler *interrupt_handler); void JS_SetRandomSeed(JSContext *ctx, uint64_t seed); JSValue JS_GetGlobalObject(JSContext *ctx); diff --git a/vendor/mquickjs/mquickjs_atom.h b/vendor/mquickjs/mquickjs_atom.h new file mode 100644 index 00000000..9d77d104 --- /dev/null +++ b/vendor/mquickjs/mquickjs_atom.h @@ -0,0 +1,76 @@ +#define JS_ATOM_null 0 +#define JS_ATOM_false 2 +#define JS_ATOM_true 4 +#define JS_ATOM_if 6 +#define JS_ATOM_else 8 +#define JS_ATOM_return 10 +#define JS_ATOM_var 12 +#define JS_ATOM_this 14 +#define JS_ATOM_delete 16 +#define JS_ATOM_void 18 +#define JS_ATOM_typeof 20 +#define JS_ATOM_new 22 +#define JS_ATOM_in 24 +#define JS_ATOM_instanceof 26 +#define JS_ATOM_do 29 +#define JS_ATOM_while 31 +#define JS_ATOM_for 33 +#define JS_ATOM_break 35 +#define JS_ATOM_continue 37 +#define JS_ATOM_switch 40 +#define JS_ATOM_case 42 +#define JS_ATOM_default 44 +#define JS_ATOM_throw 46 +#define JS_ATOM_try 48 +#define JS_ATOM_catch 50 +#define JS_ATOM_finally 52 +#define JS_ATOM_function 54 +#define JS_ATOM_debugger 57 +#define JS_ATOM_with 60 +#define JS_ATOM_class 62 +#define JS_ATOM_const 64 +#define JS_ATOM_enum 66 +#define JS_ATOM_export 68 +#define JS_ATOM_extends 70 +#define JS_ATOM_import 72 +#define JS_ATOM_super 74 +#define JS_ATOM_implements 76 +#define JS_ATOM_interface 79 +#define JS_ATOM_let 82 +#define JS_ATOM_package 84 +#define JS_ATOM_private 86 +#define JS_ATOM_protected 88 +#define JS_ATOM_public 91 +#define JS_ATOM_static 93 +#define JS_ATOM_yield 95 +#define JS_ATOM_empty 97 +#define JS_ATOM_toString 99 +#define JS_ATOM_valueOf 102 +#define JS_ATOM_number 104 +#define JS_ATOM_object 106 +#define JS_ATOM_undefined 108 +#define JS_ATOM_string 111 +#define JS_ATOM_boolean 113 +#define JS_ATOM__ret_ 115 +#define JS_ATOM__eval_ 117 +#define JS_ATOM_eval 119 +#define JS_ATOM_arguments 121 +#define JS_ATOM_value 124 +#define JS_ATOM_get 126 +#define JS_ATOM_set 128 +#define JS_ATOM_prototype 130 +#define JS_ATOM_constructor 133 +#define JS_ATOM_length 136 +#define JS_ATOM_target 138 +#define JS_ATOM_of 140 +#define JS_ATOM_NaN 142 +#define JS_ATOM_Infinity 144 +#define JS_ATOM__Infinity 147 +#define JS_ATOM_name 150 +#define JS_ATOM_Error 152 +#define JS_ATOM___proto__ 154 +#define JS_ATOM_index 157 +#define JS_ATOM_input 159 + +#define JS_ATOM_END 161 + From 3340c7168480833cdbcdedf97b06ae2b3aab338d Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 11:00:50 -0500 Subject: [PATCH 13/16] Add requiresFeature test helper for feature-based tests The helper allows tests to declare feature requirements: - If feature is supported: test runs normally - If feature is NOT supported: test passes if it either succeeds OR throws QuickJSUnsupported Usage: requiresFeature('bigint', () => { ... }) // uses vm.features requiresFeature('bigint', module.features, () => { ... }) // explicit Updated bigint and symbol tests to use the new helper. Co-Authored-By: Claude Opus 4.5 --- .../quickjs-emscripten/src/quickjs.test.ts | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/packages/quickjs-emscripten/src/quickjs.test.ts b/packages/quickjs-emscripten/src/quickjs.test.ts index 644fdaa0..98150397 100644 --- a/packages/quickjs-emscripten/src/quickjs.test.ts +++ b/packages/quickjs-emscripten/src/quickjs.test.ts @@ -22,7 +22,9 @@ import type { QuickJSFFI, QuickJSAsyncFFI, ContextOptions, + QuickJSFeature, } from "." +import type { QuickJSFeatures } from "." import { QuickJSContext, QuickJSAsyncContext, @@ -98,6 +100,51 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = } }) + /** + * Helper for tests that require a specific feature. + * - If feature is supported: runs the test function normally + * - If feature is NOT supported: test passes if it either succeeds OR throws QuickJSUnsupported + * + * @param feature - The feature required by this test + * @param featuresOrFn - Either a QuickJSFeatures object, or the test function (uses vm.features) + * @param maybeFn - The test function if featuresOrFn is a QuickJSFeatures object + */ + function requiresFeature( + feature: QuickJSFeature, + featuresOrFn: QuickJSFeatures | (() => unknown), + maybeFn?: () => unknown, + ): void { + let features: QuickJSFeatures + let fn: () => unknown + + if (typeof featuresOrFn === "function") { + // requiresFeature('bigint', () => ...) + features = vm.features + fn = featuresOrFn + } else { + // requiresFeature('bigint', someFeatures, () => ...) + features = featuresOrFn + fn = maybeFn! + } + + if (features.has(feature)) { + // Feature supported - run normally + fn() + } else { + // Feature not supported - allow success OR QuickJSUnsupported error + try { + fn() + // Test succeeded despite feature not being supported - that's fine + } catch (error) { + if (!(error instanceof errors.QuickJSUnsupported)) { + // Unexpected error type - rethrow + throw error + } + // QuickJSUnsupported is expected when feature not supported + } + } + } + beforeEach(async () => { testId++ thisTestFailed = false @@ -144,10 +191,12 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = }) it("can round-trip a bigint", () => { - const int = 2n ** 64n - const numHandle = vm.newBigInt(int) - assert.equal(vm.getBigInt(numHandle), int) - numHandle.dispose() + requiresFeature("bigint", () => { + const int = 2n ** 64n + const numHandle = vm.newBigInt(int) + assert.equal(vm.getBigInt(numHandle), int) + numHandle.dispose() + }) }) it("can dump a bigint", () => { @@ -165,20 +214,24 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = }) it("can round-trip a global symbol", () => { - const handle = vm.newSymbolFor("potatoes") - const dumped = vm.getSymbol(handle) - assert.equal(dumped, Symbol.for("potatoes")) - handle.dispose() + requiresFeature("symbols", () => { + const handle = vm.newSymbolFor("potatoes") + const dumped = vm.getSymbol(handle) + assert.equal(dumped, Symbol.for("potatoes")) + handle.dispose() + }) }) it("can round trip a unique symbol's description", () => { - const symbol = Symbol("cats") - const handle = vm.newUniqueSymbol(symbol) - const dumped = vm.getSymbol(handle) - assert.notStrictEqual(dumped, symbol) - assert.notStrictEqual(dumped, Symbol.for("cats")) - assert.equal(dumped.description, symbol.description) - handle.dispose() + requiresFeature("symbols", () => { + const symbol = Symbol("cats") + const handle = vm.newUniqueSymbol(symbol) + const dumped = vm.getSymbol(handle) + assert.notStrictEqual(dumped, symbol) + assert.notStrictEqual(dumped, Symbol.for("cats")) + assert.equal(dumped.description, symbol.description) + handle.dispose() + }) }) it("can dump a symbol", () => { From 71da4958312f2656bbba6e66fd07ef11b6870d1e Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 12:48:07 -0500 Subject: [PATCH 14/16] Add host function support for mquickjs variant Implement vm.newFunction() for mquickjs by creating a custom stdlib that includes a host function trampoline. mquickjs requires compile-time stdlib customization since its function table is ROM-resident. Key changes: - Add c/qts_mquickjs_stdlib.c: Custom stdlib with qts_host_trampoline in the function table at JS_CFUNCTION_USER (index 1) - Update templates/Variant.mk: Add rules to compile stdlib generator with host cc and generate header with -m32 flag for wasm32 compatibility - Update c/interface-mquickjs.c: Use generated qts_mquickjs_stdlib header - Add QTS_HasFunctionsSupport FFI function returning 1 for mquickjs The trampoline is called when JS invokes a host-created function. It extracts the host_ref_id from the closure params and dispatches to the host callback via qts_host_call_function. Co-Authored-By: Claude Opus 4.5 --- c/interface-mquickjs.c | 194 ++++++- c/interface.c | 4 + c/qts_mquickjs_stdlib.c | 393 ++++++++++++++ .../quickjs-emscripten-core/src/features.ts | 3 + packages/quickjs-emscripten-core/src/types.ts | 3 +- packages/quickjs-emscripten/package.json | 2 + .../quickjs-emscripten/src/quickjs.test.ts | 490 ++++++++++-------- packages/quickjs-emscripten/src/variants.ts | 11 +- packages/quickjs-ffi-types/src/ffi-async.ts | 1 + packages/quickjs-ffi-types/src/ffi.ts | 1 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + .../Makefile | 26 +- .../src/ffi.ts | 2 + templates/Variant.mk | 26 +- 57 files changed, 1520 insertions(+), 252 deletions(-) create mode 100644 c/qts_mquickjs_stdlib.c diff --git a/c/interface-mquickjs.c b/c/interface-mquickjs.c index bc8cab7d..dc1193fa 100644 --- a/c/interface-mquickjs.c +++ b/c/interface-mquickjs.c @@ -27,6 +27,56 @@ #include "../vendor/mquickjs/mquickjs.h" #include "../vendor/mquickjs/cutils.h" +/** + * Stub implementations for REPL-specific functions referenced by mqjs_stdlib.h. + * These are defined in mqjs.c but we don't include that file. + */ +static JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // Return 0 since we don't have access to real time in mquickjs + return JS_NewFloat64(ctx, 0.0); +} + +static JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // No-op print function + return JS_UNDEFINED; +} + +static JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // Return 0 since we don't have access to real time + return JS_NewFloat64(ctx, 0.0); +} + +static JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // No-op - mquickjs doesn't have GC in the traditional sense + return JS_UNDEFINED; +} + +static JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // Not supported + return JS_ThrowTypeError(ctx, "load() is not supported"); +} + +static JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // Not supported + return JS_ThrowTypeError(ctx, "setTimeout() is not supported"); +} + +static JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { + // Not supported + return JS_ThrowTypeError(ctx, "clearTimeout() is not supported"); +} + +/* Forward declaration of the host trampoline function. + * This function is referenced in the generated qts_mquickjs_stdlib.h and defined later in this file. + * It's called when a host-created function is invoked from JavaScript. + */ +JSValue qts_host_trampoline(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, JSValue params); + +/* Generated stdlib header - built from c/qts_mquickjs_stdlib.c + * The include path is set via -I flag in Variant.mk to point to the build directory + */ +#include "qts_mquickjs_stdlib.h" + #define PKG "quickjs-emscripten: " #define LOG_LEN 500 @@ -237,7 +287,7 @@ JSRuntime *QTS_NewRuntime() { } } - JSContext *ctx = JS_NewContext(mquickjs_heap, MQUICKJS_HEAP_SIZE, NULL); + JSContext *ctx = JS_NewContext(mquickjs_heap, MQUICKJS_HEAP_SIZE, &qts_mquickjs_stdlib); return ctx; // JSRuntime* is actually JSContext* for mquickjs } @@ -424,6 +474,14 @@ MaybeAsync(JSValue *) QTS_GetOwnPropertyNames(JSContext *ctx, JSValue ***out_ptr } MaybeAsync(JSValue *) QTS_Call(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, int argc, JSValueConst **argv_ptrs) { +#ifdef QTS_DEBUG_MODE + { + char msg[LOG_LEN]; + snprintf(msg, LOG_LEN, "QTS_Call: func_obj=%llu, this_obj=%llu, argc=%d", + (unsigned long long)*func_obj, (unsigned long long)*this_obj, argc); + qts_log(msg); + } +#endif // mquickjs has a different calling convention - arguments are pushed onto a stack // Push this, then args for (int i = argc - 1; i >= 0; i--) { @@ -433,6 +491,23 @@ MaybeAsync(JSValue *) QTS_Call(JSContext *ctx, JSValueConst *func_obj, JSValueCo JS_PushArg(ctx, *func_obj); JSValue result = JS_Call(ctx, argc); +#ifdef QTS_DEBUG_MODE + { + char msg[LOG_LEN]; + snprintf(msg, LOG_LEN, "QTS_Call: result=%llu, is_exception=%d", + (unsigned long long)result, JS_IsException(result)); + qts_log(msg); + if (JS_IsException(result)) { + JSValue exc = JS_GetException(ctx); + JSCStringBuf buf; + const char *exc_str = JS_ToCString(ctx, exc, &buf); + if (exc_str) { + snprintf(msg, LOG_LEN, "QTS_Call: exception=%s", exc_str); + qts_log(msg); + } + } + } +#endif return jsvalue_to_heap(result); } @@ -599,36 +674,123 @@ int QTS_HasEvalSupport() { return 1; // Basic eval is supported } +int QTS_HasFunctionsSupport() { + return 1; // Host function callbacks supported via trampoline +} + // ---------------------------------------------------------------------------- // C -> Host Callbacks #ifdef __EMSCRIPTEN__ -EM_JS(MaybeAsync(JSValue *), qts_host_call_function, (JSContext * ctx, JSValueConst *this_ptr, int argc, JSValueConst *argv, uint32_t magic_func_id), { +EM_JS(MaybeAsync(JSValue *), qts_host_call_function, (JSContext * ctx, JSValueConst *this_ptr, int argc, JSValueConst *argv, HostRefId host_ref_id), { #ifdef QTS_ASYNCIFY const asyncify = {['handleSleep'] : Asyncify.handleSleep}; #else const asyncify = undefined; #endif - return Module['callbacks']['callFunction'](asyncify, ctx, this_ptr, argc, argv, magic_func_id); +#ifdef QTS_DEBUG_MODE + console.log('qts_host_call_function: host_ref_id=' + host_ref_id + ' (typeof: ' + typeof host_ref_id + ')'); +#endif + return Module['callbacks']['callFunction'](asyncify, ctx, this_ptr, argc, argv, host_ref_id); }); + +/** + * Host function trampoline - called by mquickjs when a host-created function is invoked. + * This is referenced in mqjs_stdlib.c and included in the compiled stdlib table. + * + * @param ctx - The JS context + * @param this_val - Pointer to `this` value + * @param argc - Number of arguments (may include FRAME_CF_CTOR flag) + * @param argv - Array of argument values + * @param params - The host_ref_id stored when the function was created (as a float) + * @return The result of calling the host function + */ +JSValue qts_host_trampoline(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, JSValue params) { + // Extract host_ref_id from params (stored as a float64) + double host_ref_id_double = 0.0; + int convert_result = JS_ToNumber(ctx, &host_ref_id_double, params); + +#ifdef QTS_DEBUG_MODE + { + char msg[LOG_LEN]; + snprintf(msg, LOG_LEN, "qts_host_trampoline: params=%llu, convert_result=%d, host_ref_id_double=%f", + (unsigned long long)params, convert_result, host_ref_id_double); + qts_log(msg); + } #endif -// Callback storage for C functions -typedef struct { - uint32_t func_id; - JSContext *ctx; -} QTSFunctionData; + if (convert_result) { + return JS_ThrowInternalError(ctx, "qts_host_trampoline: invalid params"); + } + HostRefId host_ref_id = (HostRefId)host_ref_id_double; -// Function callback wrapper -JSValue qts_call_function_wrapper(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) { - // This is simplified - mquickjs has different function calling - return JS_UNDEFINED; +#ifdef QTS_DEBUG_MODE + { + char msg[LOG_LEN]; + snprintf(msg, LOG_LEN, "qts_host_trampoline: host_ref_id=%d", host_ref_id); + qts_log(msg); + } +#endif + + // Clear the FRAME_CF_CTOR flag from argc to get actual argument count + int actual_argc = argc & ~FRAME_CF_CTOR; + + // Build argv pointer array for qts_host_call_function + // mquickjs passes arguments directly as JSValue*, we need JSValueConst** + JSValue *result_ptr = qts_host_call_function(ctx, this_val, actual_argc, argv, host_ref_id); + + if (result_ptr == NULL) { + return JS_UNDEFINED; + } + JSValue result = *result_ptr; + free(result_ptr); + return result; +} +#else +// Stub for non-EMSCRIPTEN builds (used by clang analysis) +JSValue qts_host_trampoline(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv, JSValue params) { + return JS_ThrowInternalError(ctx, "qts_host_trampoline: not available outside EMSCRIPTEN"); } +#endif + +/** + * Create a new function that calls back to the host when invoked. + * Uses mquickjs's JS_NewCFunctionParams to create a closure with the host_ref_id. + */ +JSValue *QTS_NewFunction(JSContext *ctx, const char *name, int arg_length, bool is_constructor, HostRefId host_ref_id) { + // Store host_ref_id as a float in params (mquickjs closure data) + JSValue params = JS_NewFloat64(ctx, (double)host_ref_id); + +#ifdef QTS_DEBUG_MODE + { + char msg[LOG_LEN]; + snprintf(msg, LOG_LEN, "QTS_NewFunction: host_ref_id=%d, params=%llu", (int)host_ref_id, (unsigned long long)params); + qts_log(msg); + } +#endif + + // Create function using the trampoline - JS_CFUNCTION_qts_host_trampoline is defined in mqjs_stdlib.c + // It equals JS_CFUNCTION_USER (1), the second entry in js_c_function_decl + #define JS_CFUNCTION_qts_host_trampoline 1 + + JSValue func_obj = JS_NewCFunctionParams(ctx, JS_CFUNCTION_qts_host_trampoline, params); + +#ifdef QTS_DEBUG_MODE + { + char msg[LOG_LEN]; + snprintf(msg, LOG_LEN, "QTS_NewFunction: func_obj=%llu, is_exception=%d", (unsigned long long)func_obj, JS_IsException(func_obj)); + qts_log(msg); + } +#endif + + if (JS_IsException(func_obj)) { + return jsvalue_to_heap(func_obj); + } + + // Note: mquickjs doesn't support setting function name or length properties directly + // Those would need to be set via JS_SetPropertyStr if needed -JSValue *QTS_NewFunction(JSContext *ctx, uint32_t func_id, const char *name) { - // mquickjs has a very different function creation model - // For now, return an error - return jsvalue_to_heap(JS_ThrowInternalError(ctx, "Custom functions not yet supported in mquickjs binding")); + return jsvalue_to_heap(func_obj); } JSValueConst *QTS_ArgvGetJSValueConstPointer(JSValueConst *argv, int index) { diff --git a/c/interface.c b/c/interface.c index 30d763a8..a9bf4a8a 100644 --- a/c/interface.c +++ b/c/interface.c @@ -1172,6 +1172,10 @@ int QTS_HasEvalSupport() { return 1; } +int QTS_HasFunctionsSupport() { + return 1; +} + // ---------------------------------------------------------------------------- // C -> Host Callbacks // Note: inside EM_JS, we need to use ['...'] subscript syntax for accessing JS diff --git a/c/qts_mquickjs_stdlib.c b/c/qts_mquickjs_stdlib.c new file mode 100644 index 00000000..57760cdc --- /dev/null +++ b/c/qts_mquickjs_stdlib.c @@ -0,0 +1,393 @@ +/* + * quickjs-emscripten custom stdlib for mquickjs + * + * Based on vendor/mquickjs/mqjs_stdlib.c + * This file adds the host function trampoline needed for vm.newFunction() + * + * Build: cc -o qts_mquickjs_stdlib qts_mquickjs_stdlib.c + * Generate: ./qts_mquickjs_stdlib > qts_mquickjs_stdlib.h + */ +#include +#include +#include + +#include "../vendor/mquickjs/mquickjs_build.h" + +/* + * Note: The trampoline function (qts_host_trampoline) is just referenced by name + * in js_c_function_decl below. It's defined in interface-mquickjs.c and will be + * resolved at link time when building the WASM module. + * + * All the standard class definitions from mqjs_stdlib.c follow. + */ + +static const JSPropDef js_object_proto[] = { + JS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty), + JS_CFUNC_DEF("toString", 0, js_object_toString), + JS_PROP_END, +}; + +static const JSPropDef js_object[] = { + JS_CFUNC_DEF("defineProperty", 3, js_object_defineProperty), + JS_CFUNC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf), + JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf), + JS_CFUNC_DEF("create", 2, js_object_create), + JS_CFUNC_DEF("keys", 1, js_object_keys), + JS_PROP_END, +}; + +static const JSClassDef js_object_class = + JS_CLASS_DEF("Object", 1, js_object_constructor, JS_CLASS_OBJECT, + js_object, js_object_proto, NULL, NULL); + +static const JSPropDef js_function_proto[] = { + JS_CGETSET_DEF("prototype", js_function_get_prototype, js_function_set_prototype ), + JS_CFUNC_DEF("call", 1, js_function_call ), + JS_CFUNC_DEF("apply", 2, js_function_apply ), + JS_CFUNC_DEF("bind", 1, js_function_bind ), + JS_CFUNC_DEF("toString", 0, js_function_toString ), + JS_CGETSET_MAGIC_DEF("length", js_function_get_length_name, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("name", js_function_get_length_name, NULL, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_function_class = + JS_CLASS_DEF("Function", 1, js_function_constructor, JS_CLASS_CLOSURE, NULL, js_function_proto, NULL, NULL); + +static const JSPropDef js_number_proto[] = { + JS_CFUNC_DEF("toExponential", 1, js_number_toExponential ), + JS_CFUNC_DEF("toFixed", 1, js_number_toFixed ), + JS_CFUNC_DEF("toPrecision", 1, js_number_toPrecision ), + JS_CFUNC_DEF("toString", 1, js_number_toString ), + JS_PROP_END, +}; + +static const JSPropDef js_number[] = { + JS_CFUNC_DEF("parseInt", 2, js_number_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_number_parseFloat ), + JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ), + JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ), + JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), + JS_PROP_DOUBLE_DEF("NEGATIVE_INFINITY", -INFINITY, 0 ), + JS_PROP_DOUBLE_DEF("POSITIVE_INFINITY", INFINITY, 0 ), + JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0 ), /* ES6 */ + JS_PROP_DOUBLE_DEF("MAX_SAFE_INTEGER", 9007199254740991.0, 0 ), /* ES6 */ + JS_PROP_DOUBLE_DEF("MIN_SAFE_INTEGER", -9007199254740991.0, 0 ), /* ES6 */ + JS_PROP_END, +}; + +static const JSClassDef js_number_class = + JS_CLASS_DEF("Number", 1, js_number_constructor, JS_CLASS_NUMBER, js_number, js_number_proto, NULL, NULL); + +static const JSClassDef js_boolean_class = + JS_CLASS_DEF("Boolean", 1, js_boolean_constructor, JS_CLASS_BOOLEAN, NULL, NULL, NULL, NULL); + +static const JSPropDef js_string_proto[] = { + JS_CGETSET_DEF("length", js_string_get_length, js_string_set_length ), + JS_CFUNC_MAGIC_DEF("charAt", 1, js_string_charAt, magic_charAt ), + JS_CFUNC_MAGIC_DEF("charCodeAt", 1, js_string_charAt, magic_charCodeAt ), + JS_CFUNC_MAGIC_DEF("codePointAt", 1, js_string_charAt, magic_codePointAt ), + JS_CFUNC_DEF("slice", 2, js_string_slice ), + JS_CFUNC_DEF("substring", 2, js_string_substring ), + JS_CFUNC_DEF("concat", 1, js_string_concat ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), + JS_CFUNC_DEF("match", 1, js_string_match ), + JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ), + JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ), + JS_CFUNC_DEF("search", 1, js_string_search ), + JS_CFUNC_DEF("split", 2, js_string_split ), + JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), + JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), + JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ), + JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ), + JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ), + JS_CFUNC_DEF("toString", 0, js_string_toString ), + JS_CFUNC_DEF("repeat", 1, js_string_repeat ), + JS_PROP_END, +}; + +static const JSPropDef js_string[] = { + JS_CFUNC_MAGIC_DEF("fromCharCode", 1, js_string_fromCharCode, 0 ), + JS_CFUNC_MAGIC_DEF("fromCodePoint", 1, js_string_fromCharCode, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_string_class = + JS_CLASS_DEF("String", 1, js_string_constructor, JS_CLASS_STRING, js_string, js_string_proto, NULL, NULL); + +static const JSPropDef js_array_proto[] = { + JS_CFUNC_DEF("concat", 1, js_array_concat ), + JS_CGETSET_DEF("length", js_array_get_length, js_array_set_length ), + JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ), + JS_CFUNC_DEF("pop", 0, js_array_pop ), + JS_CFUNC_DEF("join", 1, js_array_join ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_DEF("reverse", 0, js_array_reverse ), + JS_CFUNC_DEF("shift", 0, js_array_shift ), + JS_CFUNC_DEF("slice", 2, js_array_slice ), + JS_CFUNC_DEF("splice", 2, js_array_splice ), + JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_array_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_array_indexOf, 1 ), + JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, js_special_every ), + JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, js_special_some ), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, js_special_forEach ), + JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, js_special_map ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, js_special_filter ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, js_special_reduce ), + JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, js_special_reduceRight ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, js_special_reduce ), + JS_CFUNC_DEF("sort", 1, js_array_sort ), + JS_PROP_END, +}; + +static const JSPropDef js_array[] = { + JS_CFUNC_DEF("isArray", 1, js_array_isArray ), + JS_PROP_END, +}; + +static const JSClassDef js_array_class = + JS_CLASS_DEF("Array", 1, js_array_constructor, JS_CLASS_ARRAY, js_array, js_array_proto, NULL, NULL); + +static const JSPropDef js_error_proto[] = { + JS_CFUNC_DEF("toString", 0, js_error_toString ), + JS_PROP_STRING_DEF("name", "Error", 0 ), + JS_CGETSET_MAGIC_DEF("message", js_error_get_message, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("stack", js_error_get_message, NULL, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_error_class = + JS_CLASS_MAGIC_DEF("Error", 1, js_error_constructor, JS_CLASS_ERROR, NULL, js_error_proto, NULL, NULL); + +#define ERROR_DEF(cname, name, class_id) \ + static const JSPropDef js_ ## cname ## _proto[] = { \ + JS_PROP_STRING_DEF("name", name, 0 ), \ + JS_PROP_END, \ + }; \ + static const JSClassDef js_ ## cname ## _class = \ + JS_CLASS_MAGIC_DEF(name, 1, js_error_constructor, class_id, NULL, js_ ## cname ## _proto, &js_error_class, NULL); + +ERROR_DEF(eval_error, "EvalError", JS_CLASS_EVAL_ERROR) +ERROR_DEF(range_error, "RangeError", JS_CLASS_RANGE_ERROR) +ERROR_DEF(reference_error, "ReferenceError", JS_CLASS_REFERENCE_ERROR) +ERROR_DEF(syntax_error, "SyntaxError", JS_CLASS_SYNTAX_ERROR) +ERROR_DEF(type_error, "TypeError", JS_CLASS_TYPE_ERROR) +ERROR_DEF(uri_error, "URIError", JS_CLASS_URI_ERROR) +ERROR_DEF(internal_error, "InternalError", JS_CLASS_INTERNAL_ERROR) + +static const JSPropDef js_math[] = { + JS_CFUNC_MAGIC_DEF("min", 2, js_math_min_max, 0 ), + JS_CFUNC_MAGIC_DEF("max", 2, js_math_min_max, 1 ), + JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ), + JS_CFUNC_SPECIAL_DEF("abs", 1, f_f, js_fabs ), + JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, js_floor ), + JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, js_ceil ), + JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_round_inf ), + JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, js_sqrt ), + + JS_PROP_DOUBLE_DEF("E", 2.718281828459045, 0 ), + JS_PROP_DOUBLE_DEF("LN10", 2.302585092994046, 0 ), + JS_PROP_DOUBLE_DEF("LN2", 0.6931471805599453, 0 ), + JS_PROP_DOUBLE_DEF("LOG2E", 1.4426950408889634, 0 ), + JS_PROP_DOUBLE_DEF("LOG10E", 0.4342944819032518, 0 ), + JS_PROP_DOUBLE_DEF("PI", 3.141592653589793, 0 ), + JS_PROP_DOUBLE_DEF("SQRT1_2", 0.7071067811865476, 0 ), + JS_PROP_DOUBLE_DEF("SQRT2", 1.4142135623730951, 0 ), + + JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, js_sin ), + JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, js_cos ), + JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, js_tan ), + JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, js_asin ), + JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, js_acos ), + JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, js_atan ), + JS_CFUNC_DEF("atan2", 2, js_math_atan2 ), + JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, js_exp ), + JS_CFUNC_SPECIAL_DEF("log", 1, f_f, js_log ), + JS_CFUNC_DEF("pow", 2, js_math_pow ), + JS_CFUNC_DEF("random", 0, js_math_random ), + + /* some ES6 functions */ + JS_CFUNC_DEF("imul", 2, js_math_imul ), + JS_CFUNC_DEF("clz32", 1, js_math_clz32 ), + JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ), + JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, js_trunc ), + JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, js_log2 ), + JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, js_log10 ), + + JS_PROP_END, +}; + +static const JSClassDef js_math_obj = + JS_OBJECT_DEF("Math", js_math); + +static const JSPropDef js_json[] = { + JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("stringify", 3, js_json_stringify ), + JS_PROP_END, +}; + +static const JSClassDef js_json_obj = + JS_OBJECT_DEF("JSON", js_json); + +/* typed arrays */ +static const JSPropDef js_array_buffer_proto[] = { + JS_CGETSET_DEF("byteLength", js_array_buffer_get_byteLength, NULL ), + JS_PROP_END, +}; + +static const JSClassDef js_array_buffer_class = + JS_CLASS_DEF("ArrayBuffer", 1, js_array_buffer_constructor, JS_CLASS_ARRAY_BUFFER, NULL, js_array_buffer_proto, NULL, NULL); + +static const JSPropDef js_typed_array_base_proto[] = { + JS_CGETSET_MAGIC_DEF("length", js_typed_array_get_length, NULL, 0 ), + JS_CGETSET_MAGIC_DEF("byteLength", js_typed_array_get_length, NULL, 1 ), + JS_CGETSET_MAGIC_DEF("byteOffset", js_typed_array_get_length, NULL, 2 ), + JS_CGETSET_MAGIC_DEF("buffer", js_typed_array_get_length, NULL, 3 ), + JS_CFUNC_DEF("join", 1, js_array_join ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ), + JS_CFUNC_DEF("set", 1, js_typed_array_set ), + JS_PROP_END, +}; + +static const JSClassDef js_typed_array_base_class = + JS_CLASS_DEF("TypedArray", 0, js_typed_array_base_constructor, JS_CLASS_TYPED_ARRAY, NULL, js_typed_array_base_proto, NULL, NULL); + +#define TA_DEF(name, class_name, bpe)\ +static const JSPropDef js_ ## name [] = {\ + JS_PROP_DOUBLE_DEF("BYTES_PER_ELEMENT", bpe, 0),\ + JS_PROP_END,\ +};\ +static const JSPropDef js_ ## name ## _proto[] = {\ + JS_PROP_DOUBLE_DEF("BYTES_PER_ELEMENT", bpe, 0),\ + JS_PROP_END,\ +};\ +static const JSClassDef js_ ## name ## _class =\ + JS_CLASS_MAGIC_DEF(#name, 3, js_typed_array_constructor, class_name, js_ ## name, js_ ## name ## _proto, &js_typed_array_base_class, NULL); + +TA_DEF(Uint8ClampedArray, JS_CLASS_UINT8C_ARRAY, 1) +TA_DEF(Int8Array, JS_CLASS_INT8_ARRAY, 1) +TA_DEF(Uint8Array, JS_CLASS_UINT8_ARRAY, 1) +TA_DEF(Int16Array, JS_CLASS_INT16_ARRAY, 2) +TA_DEF(Uint16Array, JS_CLASS_UINT16_ARRAY, 2) +TA_DEF(Int32Array, JS_CLASS_INT32_ARRAY, 4) +TA_DEF(Uint32Array, JS_CLASS_UINT32_ARRAY, 4) +TA_DEF(Float32Array, JS_CLASS_FLOAT32_ARRAY, 4) +TA_DEF(Float64Array, JS_CLASS_FLOAT64_ARRAY, 8) + +/* regexp */ + +static const JSPropDef js_regexp_proto[] = { + JS_CGETSET_DEF("lastIndex", js_regexp_get_lastIndex, js_regexp_set_lastIndex ), + JS_CGETSET_DEF("source", js_regexp_get_source, NULL ), + JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ), + JS_CFUNC_MAGIC_DEF("exec", 1, js_regexp_exec, 0 ), + JS_CFUNC_MAGIC_DEF("test", 1, js_regexp_exec, 1 ), + JS_PROP_END, +}; + +static const JSClassDef js_regexp_class = + JS_CLASS_DEF("RegExp", 2, js_regexp_constructor, JS_CLASS_REGEXP, NULL, js_regexp_proto, NULL, NULL); + +/* other objects */ + +static const JSPropDef js_date[] = { + JS_CFUNC_DEF("now", 0, js_date_now), + JS_PROP_END, +}; + +static const JSClassDef js_date_class = + JS_CLASS_DEF("Date", 7, js_date_constructor, JS_CLASS_DATE, js_date, NULL, NULL, NULL); + +static const JSPropDef js_console[] = { + JS_CFUNC_DEF("log", 1, js_print), + JS_PROP_END, +}; + +static const JSClassDef js_console_obj = + JS_OBJECT_DEF("Console", js_console); + +static const JSPropDef js_performance[] = { + JS_CFUNC_DEF("now", 0, js_performance_now), + JS_PROP_END, +}; +static const JSClassDef js_performance_obj = + JS_OBJECT_DEF("Performance", js_performance); + +static const JSPropDef js_global_object[] = { + JS_PROP_CLASS_DEF("Object", &js_object_class), + JS_PROP_CLASS_DEF("Function", &js_function_class), + JS_PROP_CLASS_DEF("Number", &js_number_class), + JS_PROP_CLASS_DEF("Boolean", &js_boolean_class), + JS_PROP_CLASS_DEF("String", &js_string_class), + JS_PROP_CLASS_DEF("Array", &js_array_class), + JS_PROP_CLASS_DEF("Math", &js_math_obj), + JS_PROP_CLASS_DEF("Date", &js_date_class), + JS_PROP_CLASS_DEF("JSON", &js_json_obj), + JS_PROP_CLASS_DEF("RegExp", &js_regexp_class), + + JS_PROP_CLASS_DEF("Error", &js_error_class), + JS_PROP_CLASS_DEF("EvalError", &js_eval_error_class), + JS_PROP_CLASS_DEF("RangeError", &js_range_error_class), + JS_PROP_CLASS_DEF("ReferenceError", &js_reference_error_class), + JS_PROP_CLASS_DEF("SyntaxError", &js_syntax_error_class), + JS_PROP_CLASS_DEF("TypeError", &js_type_error_class), + JS_PROP_CLASS_DEF("URIError", &js_uri_error_class), + JS_PROP_CLASS_DEF("InternalError", &js_internal_error_class), + + JS_PROP_CLASS_DEF("ArrayBuffer", &js_array_buffer_class), + JS_PROP_CLASS_DEF("Uint8ClampedArray", &js_Uint8ClampedArray_class), + JS_PROP_CLASS_DEF("Int8Array", &js_Int8Array_class), + JS_PROP_CLASS_DEF("Uint8Array", &js_Uint8Array_class), + JS_PROP_CLASS_DEF("Int16Array", &js_Int16Array_class), + JS_PROP_CLASS_DEF("Uint16Array", &js_Uint16Array_class), + JS_PROP_CLASS_DEF("Int32Array", &js_Int32Array_class), + JS_PROP_CLASS_DEF("Uint32Array", &js_Uint32Array_class), + JS_PROP_CLASS_DEF("Float32Array", &js_Float32Array_class), + JS_PROP_CLASS_DEF("Float64Array", &js_Float64Array_class), + + JS_CFUNC_DEF("parseInt", 2, js_number_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_number_parseFloat ), + JS_CFUNC_DEF("eval", 1, js_global_eval), + JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ), + JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ), + + JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), + JS_PROP_DOUBLE_DEF("NaN", NAN, 0 ), + JS_PROP_UNDEFINED_DEF("undefined", 0 ), + /* Note: null is expanded as the global object in js_global_object[] */ + JS_PROP_NULL_DEF("globalThis", 0 ), + + JS_PROP_CLASS_DEF("console", &js_console_obj), + JS_PROP_CLASS_DEF("performance", &js_performance_obj), + JS_CFUNC_DEF("print", 1, js_print), + JS_CFUNC_DEF("gc", 0, js_gc), + JS_CFUNC_DEF("load", 1, js_load), + JS_CFUNC_DEF("setTimeout", 2, js_setTimeout), + JS_CFUNC_DEF("clearTimeout", 1, js_clearTimeout), + JS_PROP_END, +}; + +/* + * C function declarations for closures. + * + * The function index for user closures starts at JS_CFUNCTION_USER (1). + * We define qts_host_trampoline at index 1 so QTS_NewFunction can use it + * to create host-callable functions. + */ +#define JS_CFUNCTION_qts_host_trampoline JS_CFUNCTION_USER + +static const JSPropDef js_c_function_decl[] = { + /* must come first if "bind" is defined - index 0 (JS_CFUNCTION_bound) */ + JS_CFUNC_SPECIAL_DEF("bound", 0, generic_params, js_function_bound ), + /* Host function trampoline - index 1 (JS_CFUNCTION_USER) */ + JS_CFUNC_SPECIAL_DEF("__qts_host_trampoline__", 0, generic_params, qts_host_trampoline ), + JS_PROP_END, +}; + +int main(int argc, char **argv) +{ + return build_atoms("qts_mquickjs_stdlib", js_global_object, js_c_function_decl, argc, argv); +} diff --git a/packages/quickjs-emscripten-core/src/features.ts b/packages/quickjs-emscripten-core/src/features.ts index a583ac2e..6852ee25 100644 --- a/packages/quickjs-emscripten-core/src/features.ts +++ b/packages/quickjs-emscripten-core/src/features.ts @@ -48,6 +48,9 @@ export class QuickJSFeatures { case "eval": result = this.ffi.QTS_HasEvalSupport() break + case "functions": + result = this.ffi.QTS_HasFunctionsSupport() + break default: return unreachable(feature) } diff --git a/packages/quickjs-emscripten-core/src/types.ts b/packages/quickjs-emscripten-core/src/types.ts index 3edea554..261df7e2 100644 --- a/packages/quickjs-emscripten-core/src/types.ts +++ b/packages/quickjs-emscripten-core/src/types.ts @@ -17,8 +17,9 @@ import { QuickJSUnknownIntrinsic } from "./errors" * - `bigint`: BigInt type support * - `intrinsics`: Intrinsics configuration support * - `eval`: eval() function support + * - `functions`: Host function callbacks (vm.newFunction) */ -export type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" +export type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" | "functions" /** * A QuickJSHandle to a constant that will never change, and does not need to diff --git a/packages/quickjs-emscripten/package.json b/packages/quickjs-emscripten/package.json index 761e86e5..d9cf3efc 100644 --- a/packages/quickjs-emscripten/package.json +++ b/packages/quickjs-emscripten/package.json @@ -53,6 +53,8 @@ "./package.json": "./package.json" }, "dependencies": { + "@jitl/mquickjs-wasmfile-debug-sync": "workspace:*", + "@jitl/mquickjs-wasmfile-release-sync": "workspace:*", "@jitl/quickjs-wasmfile-debug-asyncify": "workspace:*", "@jitl/quickjs-wasmfile-debug-sync": "workspace:*", "@jitl/quickjs-wasmfile-release-asyncify": "workspace:*", diff --git a/packages/quickjs-emscripten/src/quickjs.test.ts b/packages/quickjs-emscripten/src/quickjs.test.ts index 98150397..eb0f7aab 100644 --- a/packages/quickjs-emscripten/src/quickjs.test.ts +++ b/packages/quickjs-emscripten/src/quickjs.test.ts @@ -37,6 +37,8 @@ import { isFail, DEBUG_ASYNC, DEBUG_SYNC, + MQUICKJS_DEBUG_SYNC, + MQUICKJS_RELEASE_SYNC, memoizePromiseFactory, debugLog, errors, @@ -53,6 +55,7 @@ const TEST_SLOW = !process.env.TEST_FAST const TEST_NG = TEST_SLOW && !process.env.TEST_NO_NG const TEST_DEBUG = TEST_SLOW && !process.env.TEST_NO_DEBUG const TEST_ASYNC = TEST_SLOW && !process.env.TEST_NO_ASYNC +const TEST_MQUICKJS = TEST_SLOW && !process.env.TEST_NO_MQUICKJS const TEST_RELEASE = !process.env.TEST_NO_RELEASE const DEBUG = Boolean(process.env.DEBUG) console.log("test sets:", { @@ -60,6 +63,7 @@ console.log("test sets:", { TEST_DEBUG, TEST_NG, TEST_ASYNC, + TEST_MQUICKJS, TEST_SLOW, DEBUG, }) @@ -108,12 +112,13 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = * @param feature - The feature required by this test * @param featuresOrFn - Either a QuickJSFeatures object, or the test function (uses vm.features) * @param maybeFn - The test function if featuresOrFn is a QuickJSFeatures object + * @returns Promise if the test function is async, void otherwise */ function requiresFeature( feature: QuickJSFeature, featuresOrFn: QuickJSFeatures | (() => unknown), maybeFn?: () => unknown, - ): void { + ): unknown { let features: QuickJSFeatures let fn: () => unknown @@ -127,20 +132,33 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = fn = maybeFn! } + const handleResult = (result: unknown): unknown => { + // If the result is a promise, we need to handle rejections + if (result && typeof result === "object" && "then" in result) { + return (result as Promise).catch((error) => { + if (features.has(feature) || !(error instanceof errors.QuickJSUnsupported)) { + throw error + } + // QuickJSUnsupported is expected when feature not supported + }) + } + return result + } + if (features.has(feature)) { // Feature supported - run normally - fn() + return handleResult(fn()) } else { // Feature not supported - allow success OR QuickJSUnsupported error try { - fn() - // Test succeeded despite feature not being supported - that's fine + return handleResult(fn()) } catch (error) { if (!(error instanceof errors.QuickJSUnsupported)) { // Unexpected error type - rethrow throw error } // QuickJSUnsupported is expected when feature not supported + return undefined } } } @@ -613,31 +631,35 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = }) it("module: on syntax error: returns { error: exception }", () => { - const result = vm.evalCode(`["this", "should", "fail].join(' ')`, "mod.js", { - type: "module", - }) - if (!result.error) { - assert.fail("result should be an error") - } - assertError(vm.dump(result.error), { - name: "SyntaxError", - message: "unexpected end of string", + requiresFeature("modules", () => { + const result = vm.evalCode(`["this", "should", "fail].join(' ')`, "mod.js", { + type: "module", + }) + if (!result.error) { + assert.fail("result should be an error") + } + assertError(vm.dump(result.error), { + name: "SyntaxError", + message: "unexpected end of string", + }) + result.error.dispose() }) - result.error.dispose() }) it("module: on TypeError: returns { error: exception }", () => { - const result = vm.evalCode(`null.prop`, "mod.js", { type: "module" }) - if (!result.error) { - result.value.consume((val) => - assert.fail(`result should be an error, instead is: ${inspect(vm.dump(val))}`), - ) - return - } - const error = result.error.consume(vm.dump) - assertError(error, { - name: "TypeError", - message: "cannot read property 'prop' of null", + requiresFeature("modules", () => { + const result = vm.evalCode(`null.prop`, "mod.js", { type: "module" }) + if (!result.error) { + result.value.consume((val) => + assert.fail(`result should be an error, instead is: ${inspect(vm.dump(val))}`), + ) + return + } + const error = result.error.consume(vm.dump) + assertError(error, { + name: "TypeError", + message: "cannot read property 'prop' of null", + }) }) }) @@ -663,25 +685,27 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = }) it("can handle imports", () => { - let moduleLoaderCalls = 0 - vm.runtime.setModuleLoader(() => { - moduleLoaderCalls++ - return `export const name = "from import";` + requiresFeature("modules", () => { + let moduleLoaderCalls = 0 + vm.runtime.setModuleLoader(() => { + moduleLoaderCalls++ + return `export const name = "from import";` + }) + vm.unwrapResult( + vm.evalCode( + ` + import {name} from './foo.js' + globalThis.declaredWithEval = name + `, + "importer.js", + ), + ).dispose() + const declaredWithEval = vm.getProp(vm.global, "declaredWithEval") + assert.equal(vm.getString(declaredWithEval), "from import") + declaredWithEval.dispose() + + assert.equal(moduleLoaderCalls, 1, "Only called once") }) - vm.unwrapResult( - vm.evalCode( - ` - import {name} from './foo.js' - globalThis.declaredWithEval = name - `, - "importer.js", - ), - ).dispose() - const declaredWithEval = vm.getProp(vm.global, "declaredWithEval") - assert.equal(vm.getString(declaredWithEval), "from import") - declaredWithEval.dispose() - - assert.equal(moduleLoaderCalls, 1, "Only called once") }) it("respects maxStackSize", async () => { @@ -693,143 +717,155 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = }) it("returns the module exports", () => { - const modExports = vm.unwrapResult( - vm.evalCode(`export const s = "hello"; export const n = 42; export default "the default";`), - ) + requiresFeature("modules", () => { + const modExports = vm.unwrapResult( + vm.evalCode(`export const s = "hello"; export const n = 42; export default "the default";`), + ) - const s = vm.getProp(modExports, "s").consume(vm.dump) - const n = vm.getProp(modExports, "n").consume(vm.dump) - const defaultExport = vm.getProp(modExports, "default").consume(vm.dump) - const asJson = modExports.consume(vm.dump) - try { - assert.equal(s, "hello") - assert.equal(n, 42) - assert.equal(defaultExport, "the default") - } catch (error) { - console.log("Error with module exports:", asJson) - throw error - } + const s = vm.getProp(modExports, "s").consume(vm.dump) + const n = vm.getProp(modExports, "n").consume(vm.dump) + const defaultExport = vm.getProp(modExports, "default").consume(vm.dump) + const asJson = modExports.consume(vm.dump) + try { + assert.equal(s, "hello") + assert.equal(n, 42) + assert.equal(defaultExport, "the default") + } catch (error) { + console.log("Error with module exports:", asJson) + throw error + } + }) }) it("returns a promise of module exports", () => { - const promise = vm.unwrapResult( - vm.evalCode( - ` + requiresFeature("modules", () => { + const promise = vm.unwrapResult( + vm.evalCode( + ` await Promise.resolve(0); export const s = "hello"; export const n = 42; export default "the default"; `, - "mod.js", - { type: "module" }, - ), - ) + "mod.js", + { type: "module" }, + ), + ) - vm.runtime.executePendingJobs() - const promiseState = promise.consume((p) => vm.getPromiseState(p)) - if (promiseState.type === "pending") { - throw new Error(`By now promise should be resolved.`) - } + vm.runtime.executePendingJobs() + const promiseState = promise.consume((p) => vm.getPromiseState(p)) + if (promiseState.type === "pending") { + throw new Error(`By now promise should be resolved.`) + } - const modExports = vm.unwrapResult(promiseState) - const s = vm.getProp(modExports, "s").consume(vm.dump) - const n = vm.getProp(modExports, "n").consume(vm.dump) - const defaultExport = vm.getProp(modExports, "default").consume(vm.dump) - const asJson = modExports.consume(vm.dump) - try { - assert.equal(s, "hello") - assert.equal(n, 42) - assert.equal(defaultExport, "the default") - } catch (error) { - console.log("Error with module exports:", asJson) - throw error - } + const modExports = vm.unwrapResult(promiseState) + const s = vm.getProp(modExports, "s").consume(vm.dump) + const n = vm.getProp(modExports, "n").consume(vm.dump) + const defaultExport = vm.getProp(modExports, "default").consume(vm.dump) + const asJson = modExports.consume(vm.dump) + try { + assert.equal(s, "hello") + assert.equal(n, 42) + assert.equal(defaultExport, "the default") + } catch (error) { + console.log("Error with module exports:", asJson) + throw error + } + }) }) }) describe("intrinsics", () => { it("evalCode - context respects intrinsic options - Date Unavailable", async () => { - // Eval is required to use `evalCode` - const newContext = await getContext({ - intrinsics: { - BaseObjects: true, - Eval: true, - }, - }) + return requiresFeature("intrinsics", async () => { + // Eval is required to use `evalCode` + const newContext = await getContext({ + intrinsics: { + BaseObjects: true, + Eval: true, + }, + }) - const result = newContext.evalCode(`new Date()`) + const result = newContext.evalCode(`new Date()`) - if (!result.error) { - result.value.dispose() - assert.fail("result should be an error") - } + if (!result.error) { + result.value.dispose() + assert.fail("result should be an error") + } - const error = newContext.dump(result.error) - assert.strictEqual(error.name, "ReferenceError") - // quickjs-ng: "Date is not defined", bellard: "'Date' is not defined" - const expectedMessage = isNG ? "Date is not defined" : "'Date' is not defined" - assert.strictEqual(error.message, expectedMessage) + const error = newContext.dump(result.error) + assert.strictEqual(error.name, "ReferenceError") + // quickjs-ng: "Date is not defined", bellard: "'Date' is not defined" + const expectedMessage = isNG ? "Date is not defined" : "'Date' is not defined" + assert.strictEqual(error.message, expectedMessage) - result.error.dispose() - newContext.dispose() + result.error.dispose() + newContext.dispose() + }) }) it("evalCode - context executes as expected with default intrinsics", async () => { - // Eval is required to use `evalCode` - const newContext = await getContext({ intrinsics: DefaultIntrinsics }) - - let handle - try { - handle = newContext.unwrapResult(newContext.evalCode(`new Date()`)) - } finally { - if (handle?.alive) { - handle.dispose() + return requiresFeature("intrinsics", async () => { + // Eval is required to use `evalCode` + const newContext = await getContext({ intrinsics: DefaultIntrinsics }) + + let handle + try { + handle = newContext.unwrapResult(newContext.evalCode(`new Date()`)) + } finally { + if (handle?.alive) { + handle.dispose() + } + newContext.dispose() } - newContext.dispose() - } + }) }) }) describe(".executePendingJobs", () => { it("runs pending jobs", () => { - let i = 0 - const fnHandle = vm.newFunction("nextId", () => { - return vm.newNumber(++i) - }) - vm.setProp(vm.global, "nextId", fnHandle) - fnHandle.dispose() + requiresFeature("promises", () => { + let i = 0 + const fnHandle = vm.newFunction("nextId", () => { + return vm.newNumber(++i) + }) + vm.setProp(vm.global, "nextId", fnHandle) + fnHandle.dispose() - const result = vm.unwrapResult( - vm.evalCode(`(new Promise(resolve => resolve())).then(nextId).then(nextId).then(nextId);1`), - ) - assert.equal(i, 0) - vm.runtime.executePendingJobs() - assert.equal(i, 3) - assert.equal(vm.getNumber(result), 1) - result.dispose() + const result = vm.unwrapResult( + vm.evalCode(`(new Promise(resolve => resolve())).then(nextId).then(nextId).then(nextId);1`), + ) + assert.equal(i, 0) + vm.runtime.executePendingJobs() + assert.equal(i, 3) + assert.equal(vm.getNumber(result), 1) + result.dispose() + }) }) }) describe(".hasPendingJob", () => { it("returns true when job pending", () => { - let i = 0 - const fnHandle = vm.newFunction("nextId", () => { - return vm.newNumber(++i) - }) - vm.setProp(vm.global, "nextId", fnHandle) - fnHandle.dispose() + requiresFeature("promises", () => { + let i = 0 + const fnHandle = vm.newFunction("nextId", () => { + return vm.newNumber(++i) + }) + vm.setProp(vm.global, "nextId", fnHandle) + fnHandle.dispose() - vm.unwrapResult(vm.evalCode(`(new Promise(resolve => resolve(5)).then(nextId));1`)).dispose() - assert.strictEqual( - vm.runtime.hasPendingJob(), - true, - "has a pending job after creating a promise", - ) + vm.unwrapResult(vm.evalCode(`(new Promise(resolve => resolve(5)).then(nextId));1`)).dispose() + assert.strictEqual( + vm.runtime.hasPendingJob(), + true, + "has a pending job after creating a promise", + ) - const executed = vm.unwrapResult(vm.runtime.executePendingJobs()) - assert.strictEqual(executed, 1, "executed exactly 1 job") + const executed = vm.unwrapResult(vm.runtime.executePendingJobs()) + assert.strictEqual(executed, 1, "executed exactly 1 job") - assert.strictEqual(vm.runtime.hasPendingJob(), false, "no longer any jobs after execution") + assert.strictEqual(vm.runtime.hasPendingJob(), false, "no longer any jobs after execution") + }) }) }) @@ -1082,108 +1118,118 @@ export default "the default"; describe(".newPromise()", () => { it("dispose does not leak", () => { - vm.newPromise().dispose() + requiresFeature("promises", () => { + vm.newPromise().dispose() + }) }) it("passes an end-to-end test", async () => { - const expectedValue = Math.random() - let deferred: QuickJSDeferredPromise = undefined as any + return requiresFeature("promises", async () => { + const expectedValue = Math.random() + let deferred: QuickJSDeferredPromise = undefined as any - function timeout(ms: number) { - return new Promise((resolve) => { - setTimeout(() => resolve(), ms) - }) - } + function timeout(ms: number) { + return new Promise((resolve) => { + setTimeout(() => resolve(), ms) + }) + } - const asyncFuncHandle = vm.newFunction("getThingy", () => { - deferred = vm.newPromise() - timeout(5).then(() => vm.newNumber(expectedValue).consume((val) => deferred.resolve(val))) - return deferred.handle - }) + const asyncFuncHandle = vm.newFunction("getThingy", () => { + deferred = vm.newPromise() + timeout(5).then(() => vm.newNumber(expectedValue).consume((val) => deferred.resolve(val))) + return deferred.handle + }) - asyncFuncHandle.consume((func) => vm.setProp(vm.global, "getThingy", func)) + asyncFuncHandle.consume((func) => vm.setProp(vm.global, "getThingy", func)) - vm.unwrapResult( - vm.evalCode(` - var globalThingy = 'not set by promise'; - getThingy().then(thingy => { globalThingy = thingy }) - `), - ).dispose() + vm.unwrapResult( + vm.evalCode(` + var globalThingy = 'not set by promise'; + getThingy().then(thingy => { globalThingy = thingy }) + `), + ).dispose() - // Wait for the promise to settle - await deferred.settled + // Wait for the promise to settle + await deferred.settled - // Execute promise callbacks inside the VM - vm.runtime.executePendingJobs() + // Execute promise callbacks inside the VM + vm.runtime.executePendingJobs() - // Check that the promise executed. - const vmValue = vm.unwrapResult(vm.evalCode(`globalThingy`)).consume((x) => vm.dump(x)) - assert.equal(vmValue, expectedValue) + // Check that the promise executed. + const vmValue = vm.unwrapResult(vm.evalCode(`globalThingy`)).consume((x) => vm.dump(x)) + assert.equal(vmValue, expectedValue) + }) }) }) describe(".resolvePromise()", () => { it("retrieves async function return value as a successful VM result", async () => { - const result = vm.unwrapResult( - vm.evalCode(` - async function return1() { - return 1 - } + return requiresFeature("promises", async () => { + const result = vm.unwrapResult( + vm.evalCode(` + async function return1() { + return 1 + } - return1() - `), - ) + return1() + `), + ) - assert.equal(vm.typeof(result), "object", "Async function returns an object (promise)") + assert.equal(vm.typeof(result), "object", "Async function returns an object (promise)") - const promise = result.consume((result) => vm.resolvePromise(result)) - vm.runtime.executePendingJobs() - const asyncResult = vm.unwrapResult(await promise) + const promise = result.consume((result) => vm.resolvePromise(result)) + vm.runtime.executePendingJobs() + const asyncResult = vm.unwrapResult(await promise) - assert.equal(vm.dump(asyncResult), 1, "Awaited promise returns 1") + assert.equal(vm.dump(asyncResult), 1, "Awaited promise returns 1") - asyncResult.dispose() + asyncResult.dispose() + }) }) it("retrieves async function error as a error VM result", async () => { - const result = vm.unwrapResult( - vm.evalCode(` - async function throwOops() { - throw new Error('oops') - } + return requiresFeature("promises", async () => { + const result = vm.unwrapResult( + vm.evalCode(` + async function throwOops() { + throw new Error('oops') + } - throwOops() - `), - ) + throwOops() + `), + ) - assert.equal(vm.typeof(result), "object", "Async function returns an object (promise)") + assert.equal(vm.typeof(result), "object", "Async function returns an object (promise)") - const promise = result.consume((result) => vm.resolvePromise(result)) - vm.runtime.executePendingJobs() - const asyncResult = await promise + const promise = result.consume((result) => vm.resolvePromise(result)) + vm.runtime.executePendingJobs() + const asyncResult = await promise - if (!asyncResult.error) { - throw new Error("Should have returned an error") - } - const error = vm.dump(asyncResult.error) - asyncResult.error.dispose() + if (!asyncResult.error) { + throw new Error("Should have returned an error") + } + const error = vm.dump(asyncResult.error) + asyncResult.error.dispose() - assert.equal(error.name, "Error") - assert.equal(error.message, "oops") + assert.equal(error.name, "Error") + assert.equal(error.message, "oops") + }) }) it("converts non-promise handles into a promise, too", async () => { - const stringHandle = vm.newString("foo") - const promise = vm.resolvePromise(stringHandle) - stringHandle.dispose() + return requiresFeature("promises", async () => { + const stringHandle = vm.newString("foo") + const promise = vm.resolvePromise(stringHandle) + stringHandle.dispose() - vm.runtime.executePendingJobs() + vm.runtime.executePendingJobs() - const final = await promise.then((result) => { - const stringHandle2 = vm.unwrapResult(result) - return `unwrapped: ${stringHandle2.consume((stringHandle2) => vm.dump(stringHandle2))}` + const final = await promise.then((result) => { + const stringHandle2 = vm.unwrapResult(result) + return `unwrapped: ${stringHandle2.consume((stringHandle2) => vm.dump(stringHandle2))}` + }) + assert.equal(final, `unwrapped: foo`) }) - assert.equal(final, `unwrapped: foo`) }) }) @@ -1448,6 +1494,24 @@ describe("QuickJSContext", function () { }) } } + + if (TEST_MQUICKJS) { + if (TEST_RELEASE) { + describe("mquickjs RELEASE_SYNC", function () { + const loader = memoizePromiseFactory(() => newQuickJSWASMModule(MQUICKJS_RELEASE_SYNC)) + const getContext: GetTestContext = (opts) => loader().then((mod) => mod.newContext(opts)) + contextTests(getContext) + }) + } + + if (TEST_DEBUG) { + describe("mquickjs DEBUG_SYNC", function () { + const loader = memoizePromiseFactory(() => newQuickJSWASMModule(MQUICKJS_DEBUG_SYNC)) + const getContext: GetTestContext = (opts) => loader().then((mod) => mod.newContext(opts)) + contextTests(getContext, { isDebug: true }) + }) + } + } }) if (TEST_ASYNC) { @@ -1504,6 +1568,12 @@ describe("QuickJSWASMModule", () => { { name: "DEBUG_SYNC", loader: () => newQuickJSWASMModule(DEBUG_SYNC) }, { name: "DEBUG_ASYNC", loader: () => newQuickJSAsyncWASMModule(DEBUG_ASYNC) }, { name: "RELEASE_ASYNC", loader: () => newQuickJSAsyncWASMModule(RELEASE_ASYNC) }, + ...(TEST_MQUICKJS + ? [ + { name: "MQUICKJS_RELEASE_SYNC", loader: () => newQuickJSWASMModule(MQUICKJS_RELEASE_SYNC) }, + { name: "MQUICKJS_DEBUG_SYNC", loader: () => newQuickJSWASMModule(MQUICKJS_DEBUG_SYNC) }, + ] + : []), ] for (const variant of variants) { diff --git a/packages/quickjs-emscripten/src/variants.ts b/packages/quickjs-emscripten/src/variants.ts index 3b359bd9..d642223d 100644 --- a/packages/quickjs-emscripten/src/variants.ts +++ b/packages/quickjs-emscripten/src/variants.ts @@ -13,6 +13,8 @@ import DEBUG_SYNC from "@jitl/quickjs-wasmfile-debug-sync" import RELEASE_SYNC from "@jitl/quickjs-wasmfile-release-sync" import DEBUG_ASYNC from "@jitl/quickjs-wasmfile-debug-asyncify" import RELEASE_ASYNC from "@jitl/quickjs-wasmfile-release-asyncify" +import MQUICKJS_DEBUG_SYNC from "@jitl/mquickjs-wasmfile-debug-sync" +import MQUICKJS_RELEASE_SYNC from "@jitl/mquickjs-wasmfile-release-sync" /** * Create a new, completely isolated WebAssembly module containing the QuickJS library. @@ -53,4 +55,11 @@ export async function newQuickJSAsyncWASMModule( return newQuickJSAsyncWASMModuleFromVariant(variantOrPromise) } -export { DEBUG_SYNC, RELEASE_SYNC, DEBUG_ASYNC, RELEASE_ASYNC } +export { + DEBUG_SYNC, + RELEASE_SYNC, + DEBUG_ASYNC, + RELEASE_ASYNC, + MQUICKJS_DEBUG_SYNC, + MQUICKJS_RELEASE_SYNC, +} diff --git a/packages/quickjs-ffi-types/src/ffi-async.ts b/packages/quickjs-ffi-types/src/ffi-async.ts index 685e6815..421f1d18 100644 --- a/packages/quickjs-ffi-types/src/ffi-async.ts +++ b/packages/quickjs-ffi-types/src/ffi-async.ts @@ -255,6 +255,7 @@ export interface QuickJSAsyncFFI { QTS_HasBigIntSupport: () => number QTS_HasIntrinsicsSupport: () => number QTS_HasEvalSupport: () => number + QTS_HasFunctionsSupport: () => number QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/quickjs-ffi-types/src/ffi.ts b/packages/quickjs-ffi-types/src/ffi.ts index 0722667a..74ae6b04 100644 --- a/packages/quickjs-ffi-types/src/ffi.ts +++ b/packages/quickjs-ffi-types/src/ffi.ts @@ -203,6 +203,7 @@ export interface QuickJSFFI { QTS_HasBigIntSupport: () => number QTS_HasIntrinsicsSupport: () => number QTS_HasEvalSupport: () => number + QTS_HasFunctionsSupport: () => number QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/Makefile b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile index 17c32721..15aa3cc2 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/Makefile +++ b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -177,7 +181,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -201,3 +206,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts index f879c8fb..fcd4904e 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-mquickjs-wasmfile-release-sync/Makefile b/packages/variant-mquickjs-wasmfile-release-sync/Makefile index 6bfcb3f6..953f8880 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/Makefile +++ b/packages/variant-mquickjs-wasmfile-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -171,7 +175,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -195,3 +200,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile b/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile index bd0c31d0..6ad926e1 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -174,7 +178,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -198,3 +203,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts b/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile index 94e348cb..16762c58 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -183,7 +187,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -207,3 +212,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts index e7f02bb1..1af4c9a5 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts @@ -511,6 +511,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile b/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile index 786bfcea..75674542 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -177,7 +181,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -201,3 +206,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts index f879c8fb..fcd4904e 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile b/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile index 451aa094..d0e42cf8 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -178,7 +182,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -202,3 +207,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts index fb6454c2..ba46e83c 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts @@ -508,6 +508,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile b/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile index 440480ef..d29a510f 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -171,7 +175,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -195,3 +200,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile b/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile index 888200be..27401add 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -184,7 +188,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -208,3 +213,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts index e7f02bb1..1af4c9a5 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts @@ -511,6 +511,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile b/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile index 932c2c20..5dd564d0 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -178,7 +182,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -202,3 +207,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts index f879c8fb..fcd4904e 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile b/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile index f30099fa..7a29e706 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -179,7 +183,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -203,3 +208,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts index fb6454c2..ba46e83c 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts @@ -508,6 +508,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/Makefile b/packages/variant-quickjs-singlefile-browser-release-sync/Makefile index d3b52ce4..27aacfed 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-browser-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -172,7 +176,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -196,3 +201,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile index df901314..f91b32c1 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -184,7 +188,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -208,3 +213,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts index e7f02bb1..1af4c9a5 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts @@ -511,6 +511,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile b/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile index e68390d7..8c275b6a 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -178,7 +182,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -202,3 +207,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts index f879c8fb..fcd4904e 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile b/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile index f42417e0..92602ad3 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -179,7 +183,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -203,3 +208,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts index fb6454c2..ba46e83c 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts @@ -508,6 +508,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile b/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile index db68e6a2..52f54ac7 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -172,7 +176,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -196,3 +201,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile index a27398c4..a5286917 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -184,7 +188,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -208,3 +213,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts index e7f02bb1..1af4c9a5 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts @@ -511,6 +511,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile b/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile index e73874e2..2eadbb5a 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -178,7 +182,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -202,3 +207,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts index f879c8fb..fcd4904e 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile b/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile index a2ccb06d..0ea2f0dc 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -179,7 +183,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -203,3 +208,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts index fb6454c2..ba46e83c 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts @@ -508,6 +508,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile b/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile index 4b747abd..b1721f90 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -172,7 +176,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -196,3 +201,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile b/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile index f0c3f906..a9fab004 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -183,7 +187,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -207,3 +212,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts index e7f02bb1..1af4c9a5 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts @@ -511,6 +511,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-debug-sync/Makefile b/packages/variant-quickjs-wasmfile-debug-sync/Makefile index d12844b8..73dc7fc0 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/Makefile +++ b/packages/variant-quickjs-wasmfile-debug-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -177,7 +181,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -201,3 +206,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts index f879c8fb..fcd4904e 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/Makefile b/packages/variant-quickjs-wasmfile-release-asyncify/Makefile index 57323cbc..c6e884d7 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/Makefile +++ b/packages/variant-quickjs-wasmfile-release-asyncify/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -178,7 +182,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -202,3 +207,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts b/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts index fb6454c2..ba46e83c 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts @@ -508,6 +508,8 @@ export class QuickJSAsyncFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-release-sync/Makefile b/packages/variant-quickjs-wasmfile-release-sync/Makefile index 7dfea003..9c8d2d83 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/Makefile +++ b/packages/variant-quickjs-wasmfile-release-sync/Makefile @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -171,7 +175,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -195,3 +200,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ diff --git a/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts index 45ef57f8..6e4fa79f 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts @@ -404,6 +404,8 @@ export class QuickJSFFI { QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) + QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) + QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/templates/Variant.mk b/templates/Variant.mk index 8d609edd..ccf7c253 100644 --- a/templates/Variant.mk +++ b/templates/Variant.mk @@ -26,7 +26,11 @@ ifeq ($(QUICKJS_LIB),mquickjs) QUICKJS_OBJS=mquickjs.o dtoa.o libm.o cutils.o QUICKJS_DEFINES:=-D_GNU_SOURCE CFLAGS_WASM+=-DQTS_USE_MQUICKJS + # Include paths for the generated stdlib header and mquickjs private headers + WRAPPER_DEFINES+=-I$(BUILD_WRAPPER) -I$(QUICKJS_ROOT) INTERFACE_SOURCE=interface-mquickjs.c + # mquickjs needs a generated stdlib header + MQUICKJS_STDLIB_H=$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h else ifeq ($(QUICKJS_LIB),quickjs-ng) # quickjs-ng uses amalgamated build (single source file) # QJS_BUILD_LIBC is needed to include libc functions (js_std_*) @@ -166,7 +170,8 @@ $(BUILD_WRAPPER)/%/emscripten-module.js: $(BUILD_WRAPPER)/interface.o $(VARIANT_ WASM_SYMBOLS=$(BUILD_WRAPPER)/symbols.json $(BUILD_WRAPPER)/asyncify-remove.json $(BUILD_WRAPPER)/asyncify-imports.json # Interface file - may be different per variant (e.g., interface-mquickjs.c for mquickjs) -$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) | $(EMCC_SRC) +# For mquickjs, this depends on the generated stdlib header +$(BUILD_WRAPPER)/interface.o: $(WRAPPER_ROOT)/$(INTERFACE_SOURCE) $(WASM_SYMBOLS) $(MQUICKJS_STDLIB_H) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -190,3 +195,22 @@ $(BUILD_WRAPPER)/asyncify-remove.json: $(BUILD_WRAPPER)/asyncify-imports.json: $(MKDIRP) $(GENERATE_TS) async-callback-symbols $@ + +############################################################################### +# mquickjs stdlib generation +# The stdlib is generated by compiling and running a C program that outputs +# the stdlib header to stdout. This header defines the JS global object, +# builtin classes, and function table. +# +# Uses host cc (not emcc) since this runs at build time to generate a header. + +# Compile the stdlib generator (using host cc, not emcc) +# This is a native executable that runs at build time +$(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen: $(WRAPPER_ROOT)/qts_mquickjs_stdlib.c $(QUICKJS_ROOT)/mquickjs_build.c + $(MKDIRP) + cc -o $@ -I$(QUICKJS_ROOT) $^ + +# Generate the stdlib header by running the generator +# Use -m32 since wasm32 is a 32-bit target +$(BUILD_WRAPPER)/qts_mquickjs_stdlib.h: $(BUILD_WRAPPER)/qts_mquickjs_stdlib_gen + $< -m32 > $@ From cf9f9b6d375c45f984257a6630868cfa1aa00317 Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 13:37:26 -0500 Subject: [PATCH 15/16] Static feature specification as single source of truth Replace runtime C feature detection with static TypeScript configuration: - Add LIBRARY_FEATURES constant in prepareVariants.ts defining feature support for each C library (quickjs, quickjs-ng, mquickjs) - Add QuickJSFeature type and QuickJSFeatureRecord to variant-types.ts - Generate features property in each variant's index.ts - Update module.ts to accept features from variant instead of FFI - Remove QTS_Has*Support() C functions from interface.c and interface-mquickjs.c - Remove corresponding FFI declarations from ffi.ts and ffi-async.ts This ensures feature availability is defined once in prepareVariants.ts and automatically propagated to all generated variants. Co-Authored-By: Claude Opus 4.5 --- c/interface-mquickjs.c | 31 ---------- c/interface.c | 31 ---------- .../quickjs-emscripten-core/src/features.ts | 50 ++-------------- .../src/from-variant.ts | 4 +- .../src/module-asyncify.ts | 10 +++- .../quickjs-emscripten-core/src/module.ts | 5 +- packages/quickjs-ffi-types/src/ffi-async.ts | 7 --- packages/quickjs-ffi-types/src/ffi.ts | 7 --- .../quickjs-ffi-types/src/variant-types.ts | 21 +++++++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ .../src/ffi.ts | 18 ------ .../src/index.ts | 9 +++ scripts/prepareVariants.ts | 57 +++++++++++++++++++ 56 files changed, 303 insertions(+), 541 deletions(-) diff --git a/c/interface-mquickjs.c b/c/interface-mquickjs.c index dc1193fa..844f615d 100644 --- a/c/interface-mquickjs.c +++ b/c/interface-mquickjs.c @@ -647,37 +647,6 @@ int QTS_BuildIsAsyncify() { #endif } -// ---------------------------------------------------------------------------- -// Feature detection - mquickjs has limited feature support - -int QTS_HasModuleSupport() { - return 0; // No module support -} - -int QTS_HasPromiseSupport() { - return 0; // No promise support -} - -int QTS_HasSymbolSupport() { - return 0; // No symbol support -} - -int QTS_HasBigIntSupport() { - return 0; // No BigInt support -} - -int QTS_HasIntrinsicsSupport() { - return 0; // No intrinsics configuration support -} - -int QTS_HasEvalSupport() { - return 1; // Basic eval is supported -} - -int QTS_HasFunctionsSupport() { - return 1; // Host function callbacks supported via trampoline -} - // ---------------------------------------------------------------------------- // C -> Host Callbacks diff --git a/c/interface.c b/c/interface.c index a9bf4a8a..86c18b0e 100644 --- a/c/interface.c +++ b/c/interface.c @@ -1145,37 +1145,6 @@ int QTS_BuildIsAsyncify() { #endif } -// ---------------------------------------------------------------------------- -// Feature detection - QuickJS and QuickJS-ng support all features - -int QTS_HasModuleSupport() { - return 1; -} - -int QTS_HasPromiseSupport() { - return 1; -} - -int QTS_HasSymbolSupport() { - return 1; -} - -int QTS_HasBigIntSupport() { - return 1; -} - -int QTS_HasIntrinsicsSupport() { - return 1; -} - -int QTS_HasEvalSupport() { - return 1; -} - -int QTS_HasFunctionsSupport() { - return 1; -} - // ---------------------------------------------------------------------------- // C -> Host Callbacks // Note: inside EM_JS, we need to use ['...'] subscript syntax for accessing JS diff --git a/packages/quickjs-emscripten-core/src/features.ts b/packages/quickjs-emscripten-core/src/features.ts index 6852ee25..dbcbafd4 100644 --- a/packages/quickjs-emscripten-core/src/features.ts +++ b/packages/quickjs-emscripten-core/src/features.ts @@ -1,6 +1,8 @@ -import type { EitherFFI } from "@jitl/quickjs-ffi-types" +import type { QuickJSFeature, QuickJSFeatureRecord } from "@jitl/quickjs-ffi-types" import { QuickJSUnsupported } from "./errors" -import type { QuickJSFeature } from "./types" + +// Re-export types for convenience +export type { QuickJSFeature, QuickJSFeatureRecord } /** * Provides feature detection for a QuickJS variant. @@ -12,10 +14,8 @@ import type { QuickJSFeature } from "./types" * or {@link QuickJSContext#features}. */ export class QuickJSFeatures { - private cache = new Map() - /** @private */ - constructor(private ffi: EitherFFI) {} + constructor(private readonly featureRecord: QuickJSFeatureRecord) {} /** * Check if this QuickJS variant supports a specific feature. @@ -23,41 +23,7 @@ export class QuickJSFeatures { * @returns `true` if the feature is supported, `false` otherwise */ has(feature: QuickJSFeature): boolean { - const cached = this.cache.get(feature) - if (cached !== undefined) { - return cached - } - - let result: number - switch (feature) { - case "modules": - result = this.ffi.QTS_HasModuleSupport() - break - case "promises": - result = this.ffi.QTS_HasPromiseSupport() - break - case "symbols": - result = this.ffi.QTS_HasSymbolSupport() - break - case "bigint": - result = this.ffi.QTS_HasBigIntSupport() - break - case "intrinsics": - result = this.ffi.QTS_HasIntrinsicsSupport() - break - case "eval": - result = this.ffi.QTS_HasEvalSupport() - break - case "functions": - result = this.ffi.QTS_HasFunctionsSupport() - break - default: - return unreachable(feature) - } - - const supported = result === 1 - this.cache.set(feature, supported) - return supported + return this.featureRecord[feature] } /** @@ -72,7 +38,3 @@ export class QuickJSFeatures { } } } - -function unreachable(x: never): never { - throw new Error(`Unreachable: ${x}`) -} diff --git a/packages/quickjs-emscripten-core/src/from-variant.ts b/packages/quickjs-emscripten-core/src/from-variant.ts index e0422877..28432b91 100644 --- a/packages/quickjs-emscripten-core/src/from-variant.ts +++ b/packages/quickjs-emscripten-core/src/from-variant.ts @@ -50,7 +50,7 @@ export async function newQuickJSWASMModuleFromVariant( const wasmModule = await wasmModuleLoader() wasmModule.type = "sync" const ffi = new QuickJSFFI(wasmModule) - return new QuickJSWASMModule(wasmModule, ffi) + return new QuickJSWASMModule(wasmModule, ffi, variant.features) } /** @@ -88,7 +88,7 @@ export async function newQuickJSAsyncWASMModuleFromVariant( const wasmModule = await wasmModuleLoader() wasmModule.type = "async" const ffi = new QuickJSAsyncFFI(wasmModule) - return new QuickJSAsyncWASMModule(wasmModule, ffi) + return new QuickJSAsyncWASMModule(wasmModule, ffi, variant.features) } /** diff --git a/packages/quickjs-emscripten-core/src/module-asyncify.ts b/packages/quickjs-emscripten-core/src/module-asyncify.ts index db32d0ad..f441880e 100644 --- a/packages/quickjs-emscripten-core/src/module-asyncify.ts +++ b/packages/quickjs-emscripten-core/src/module-asyncify.ts @@ -1,4 +1,8 @@ -import type { QuickJSAsyncEmscriptenModule, QuickJSAsyncFFI } from "@jitl/quickjs-ffi-types" +import type { + QuickJSAsyncEmscriptenModule, + QuickJSAsyncFFI, + QuickJSFeatureRecord, +} from "@jitl/quickjs-ffi-types" import type { QuickJSAsyncContext } from "./context-asyncify" import { QuickJSNotImplemented } from "./errors" import { Lifetime, Scope } from "./lifetime" @@ -27,8 +31,8 @@ export class QuickJSAsyncWASMModule extends QuickJSWASMModule { protected module: QuickJSAsyncEmscriptenModule /** @private */ - constructor(module: QuickJSAsyncEmscriptenModule, ffi: QuickJSAsyncFFI) { - super(module, ffi) + constructor(module: QuickJSAsyncEmscriptenModule, ffi: QuickJSAsyncFFI, features: QuickJSFeatureRecord) { + super(module, ffi, features) this.ffi = ffi this.module = module } diff --git a/packages/quickjs-emscripten-core/src/module.ts b/packages/quickjs-emscripten-core/src/module.ts index e700509b..047f7ab3 100644 --- a/packages/quickjs-emscripten-core/src/module.ts +++ b/packages/quickjs-emscripten-core/src/module.ts @@ -8,6 +8,7 @@ import type { JSRuntimePointer, JSValuePointer, EitherFFI, + QuickJSFeatureRecord, } from "@jitl/quickjs-ffi-types" import type { QuickJSContext } from "./context" import { debugLog } from "./debug" @@ -339,11 +340,11 @@ export class QuickJSWASMModule { public readonly features: QuickJSFeatures /** @private */ - constructor(module: EitherModule, ffi: EitherFFI) { + constructor(module: EitherModule, ffi: EitherFFI, features: QuickJSFeatureRecord) { this.module = module this.ffi = ffi this.callbacks = new QuickJSModuleCallbacks(module) - this.features = new QuickJSFeatures(ffi) + this.features = new QuickJSFeatures(features) } /** diff --git a/packages/quickjs-ffi-types/src/ffi-async.ts b/packages/quickjs-ffi-types/src/ffi-async.ts index 421f1d18..e3aa0ad6 100644 --- a/packages/quickjs-ffi-types/src/ffi-async.ts +++ b/packages/quickjs-ffi-types/src/ffi-async.ts @@ -249,13 +249,6 @@ export interface QuickJSAsyncFFI { QTS_SetDebugLogEnabled: (rt: JSRuntimePointer, is_enabled: number) => void QTS_BuildIsDebug: () => number QTS_BuildIsAsyncify: () => number - QTS_HasModuleSupport: () => number - QTS_HasPromiseSupport: () => number - QTS_HasSymbolSupport: () => number - QTS_HasBigIntSupport: () => number - QTS_HasIntrinsicsSupport: () => number - QTS_HasEvalSupport: () => number - QTS_HasFunctionsSupport: () => number QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/quickjs-ffi-types/src/ffi.ts b/packages/quickjs-ffi-types/src/ffi.ts index 74ae6b04..bf9e02c6 100644 --- a/packages/quickjs-ffi-types/src/ffi.ts +++ b/packages/quickjs-ffi-types/src/ffi.ts @@ -197,13 +197,6 @@ export interface QuickJSFFI { QTS_SetDebugLogEnabled: (rt: JSRuntimePointer, is_enabled: number) => void QTS_BuildIsDebug: () => number QTS_BuildIsAsyncify: () => number - QTS_HasModuleSupport: () => number - QTS_HasPromiseSupport: () => number - QTS_HasSymbolSupport: () => number - QTS_HasBigIntSupport: () => number - QTS_HasIntrinsicsSupport: () => number - QTS_HasEvalSupport: () => number - QTS_HasFunctionsSupport: () => number QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/quickjs-ffi-types/src/variant-types.ts b/packages/quickjs-ffi-types/src/variant-types.ts index 122d35e1..d11fc0e0 100644 --- a/packages/quickjs-ffi-types/src/variant-types.ts +++ b/packages/quickjs-ffi-types/src/variant-types.ts @@ -16,6 +16,25 @@ type EmscriptenImport = } } +/** + * Features that may or may not be supported by a QuickJS variant. + * Use {@link QuickJSWASMModule.features} to check support at runtime. + * + * - `modules`: ES module support (import/export) + * - `promises`: Promise and async/await support + * - `symbols`: Symbol type support + * - `bigint`: BigInt type support + * - `intrinsics`: Intrinsics configuration support + * - `eval`: eval() function support + * - `functions`: Host function callbacks (vm.newFunction) + */ +export type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" | "functions" + +/** + * Feature support record for a QuickJS variant. + */ +export type QuickJSFeatureRecord = Record + /** * A standard (sync) build variant. * @@ -29,6 +48,7 @@ export interface QuickJSSyncVariant { readonly type: "sync" readonly importFFI: () => Promise QuickJSFFI> readonly importModuleLoader: () => Promise> + readonly features: QuickJSFeatureRecord } /** @@ -44,6 +64,7 @@ export interface QuickJSAsyncVariant { readonly type: "async" readonly importFFI: () => Promise QuickJSAsyncFFI> readonly importModuleLoader: () => Promise> + readonly features: QuickJSFeatureRecord } export type QuickJSVariant = QuickJSSyncVariant | QuickJSAsyncVariant diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts index fcd4904e..6740134d 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts index 11572607..da9713ba 100644 --- a/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/mquickjs-wasmfile-debug-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: false, + promises: false, + symbols: false, + bigint: false, + intrinsics: false, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts index 98f031d9..e637f6b6 100644 --- a/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/mquickjs-wasmfile-release-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: false, + promises: false, + symbols: false, + bigint: false, + intrinsics: false, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts b/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts b/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts index 5aab9657..110e98d2 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/src/index.ts @@ -20,5 +20,14 @@ const variant: QuickJSSyncVariant = { type: "sync", importFFI: () => Promise.resolve(QuickJSFFI), importModuleLoader: () => Promise.resolve(moduleLoader), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts index 1af4c9a5..a154293f 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/ffi.ts @@ -495,24 +495,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/index.ts b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/index.ts index fce91208..de23270f 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSAsyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSAsyncFFI), importModuleLoader: () => import("@jitl/quickjs-ng-wasmfile-debug-asyncify/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts index fcd4904e..6740134d 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/index.ts b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/index.ts index 1ce4d0b4..70018722 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/src/index.ts +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/quickjs-ng-wasmfile-debug-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts index ba46e83c..e89c9b41 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/ffi.ts @@ -492,24 +492,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/index.ts b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/index.ts index 0740fab8..bf1464a3 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-ng-wasmfile-release-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts b/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-ng-wasmfile-release-sync/src/index.ts b/packages/variant-quickjs-ng-wasmfile-release-sync/src/index.ts index 8ef614e7..645dddce 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/src/index.ts +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/quickjs-ng-wasmfile-release-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts index 1af4c9a5..a154293f 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/ffi.ts @@ -495,24 +495,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts index b627bbb0..ff46e657 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-singlefile-browser-debug-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts index fcd4904e..6740134d 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts b/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts index d47bace1..351f65b3 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSSyncVariant = { import("@jitl/quickjs-singlefile-browser-debug-sync/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts index ba46e83c..e89c9b41 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/ffi.ts @@ -492,24 +492,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts index d958a04c..cb5c95f7 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-singlefile-browser-release-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-browser-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts b/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts index a6092682..fa5448bf 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-browser-release-sync/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSSyncVariant = { import("@jitl/quickjs-singlefile-browser-release-sync/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts index 1af4c9a5..a154293f 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/ffi.ts @@ -495,24 +495,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts index 96fda0a9..426dfbd8 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-singlefile-cjs-debug-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts index fcd4904e..6740134d 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts index 39f8417b..d8a6a684 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/quickjs-singlefile-cjs-debug-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts index ba46e83c..e89c9b41 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/ffi.ts @@ -492,24 +492,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts index 29ede7e6..b5853d31 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-singlefile-cjs-release-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts b/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts index 8f653a12..71b9cd95 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSSyncVariant = { import("@jitl/quickjs-singlefile-cjs-release-sync/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts index 1af4c9a5..a154293f 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/ffi.ts @@ -495,24 +495,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts index bb6e908f..5384415a 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-singlefile-mjs-debug-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts index fcd4904e..6740134d 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts index 64f8a1d0..af0989ca 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/quickjs-singlefile-mjs-debug-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts index ba46e83c..e89c9b41 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/ffi.ts @@ -492,24 +492,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts index c333f223..0717de4c 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSAsyncVariant = { import("@jitl/quickjs-singlefile-mjs-release-asyncify/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts b/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts b/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts index a69d8d29..7e5e1646 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/src/index.ts @@ -22,6 +22,15 @@ const variant: QuickJSSyncVariant = { import("@jitl/quickjs-singlefile-mjs-release-sync/emscripten-module").then( (mod) => mod.default, ), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts b/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts index 1af4c9a5..a154293f 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/src/ffi.ts @@ -495,24 +495,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts b/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts index fa522edf..1a12c3a2 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSAsyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSAsyncFFI), importModuleLoader: () => import("@jitl/quickjs-wasmfile-debug-asyncify/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts b/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts index fcd4904e..6740134d 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-debug-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts b/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts index b6f3b99e..2ea3b51e 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts +++ b/packages/variant-quickjs-wasmfile-debug-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/quickjs-wasmfile-debug-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts b/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts index ba46e83c..e89c9b41 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-release-asyncify/src/ffi.ts @@ -492,24 +492,6 @@ export class QuickJSAsyncFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts b/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts index 341112de..3d1e9d37 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts +++ b/packages/variant-quickjs-wasmfile-release-asyncify/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSAsyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSAsyncFFI), importModuleLoader: () => import("@jitl/quickjs-wasmfile-release-asyncify/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts b/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts index 6e4fa79f..17e6ccac 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts +++ b/packages/variant-quickjs-wasmfile-release-sync/src/ffi.ts @@ -388,24 +388,6 @@ export class QuickJSFFI { QTS_BuildIsAsyncify: () => number = this.module.cwrap("QTS_BuildIsAsyncify", "number", []) - QTS_HasModuleSupport: () => number = this.module.cwrap("QTS_HasModuleSupport", "number", []) - - QTS_HasPromiseSupport: () => number = this.module.cwrap("QTS_HasPromiseSupport", "number", []) - - QTS_HasSymbolSupport: () => number = this.module.cwrap("QTS_HasSymbolSupport", "number", []) - - QTS_HasBigIntSupport: () => number = this.module.cwrap("QTS_HasBigIntSupport", "number", []) - - QTS_HasIntrinsicsSupport: () => number = this.module.cwrap( - "QTS_HasIntrinsicsSupport", - "number", - [], - ) - - QTS_HasEvalSupport: () => number = this.module.cwrap("QTS_HasEvalSupport", "number", []) - - QTS_HasFunctionsSupport: () => number = this.module.cwrap("QTS_HasFunctionsSupport", "number", []) - QTS_NewFunction: ( ctx: JSContextPointer, name: string, diff --git a/packages/variant-quickjs-wasmfile-release-sync/src/index.ts b/packages/variant-quickjs-wasmfile-release-sync/src/index.ts index 0e5a593f..fa7d1ce0 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/src/index.ts +++ b/packages/variant-quickjs-wasmfile-release-sync/src/index.ts @@ -20,6 +20,15 @@ const variant: QuickJSSyncVariant = { importFFI: () => import("./ffi.js").then((mod) => mod.QuickJSFFI), importModuleLoader: () => import("@jitl/quickjs-wasmfile-release-sync/emscripten-module").then((mod) => mod.default), + features: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, } as const export default variant diff --git a/scripts/prepareVariants.ts b/scripts/prepareVariants.ts index fa4d13ce..743b3956 100755 --- a/scripts/prepareVariants.ts +++ b/scripts/prepareVariants.ts @@ -46,6 +46,58 @@ enum EmscriptenEnvironment { node = "node", } +/** + * Features that may or may not be supported by a QuickJS variant. + * This is the single source of truth for feature availability. + */ +type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" | "functions" + +const FEATURE_DESCRIPTIONS: Record = { + modules: "ES Modules", + promises: "Promises", + symbols: "Symbols", + bigint: "BigInt", + intrinsics: "Intrinsics", + eval: "eval()", + functions: "vm.newFunction()", +} + +/** + * Feature support matrix for each C library. + * This is the single source of truth - it generates: + * - TypeScript feature exports in each variant + * - Feature tables in variant READMEs + */ +const LIBRARY_FEATURES: Record> = { + [CLibrary.QuickJS]: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, + [CLibrary.NG]: { + modules: true, + promises: true, + symbols: true, + bigint: true, + intrinsics: true, + eval: true, + functions: true, + }, + [CLibrary.MQuickJS]: { + modules: false, + promises: false, + symbols: false, + bigint: false, + intrinsics: false, + eval: true, + functions: true, + }, +} + const DEFAULT_EMSCRIPTEN_VERSION = "5.0.1" // Use older emscripten for asmjs to avoid relying on newer browser APIs const ASMJS_EMSCRIPTEN_VERSION = "3.1.43" @@ -774,6 +826,9 @@ function renderIndexTs( [SyncMode.Asyncify]: "QuickJSAsyncVariant", }[variant.syncMode] + const features = LIBRARY_FEATURES[variant.library] + const featuresJson = JSON.stringify(features, null, 2).replace(/\n/g, "\n ") + if (variant.emscriptenInclusion === EmscriptenInclusion.AsmJs) { // Eager loading please! return ` @@ -787,6 +842,7 @@ const variant: ${variantTypeName} = { type: '${modeName}', importFFI: () => Promise.resolve(${className}), importModuleLoader: () => Promise.resolve(moduleLoader), + features: ${featuresJson}, } as const export default variant ` @@ -802,6 +858,7 @@ const variant: ${variantTypeName} = { type: '${modeName}', importFFI: () => import('./ffi.js').then(mod => mod.${className}), importModuleLoader: () => import('${packageJson.name}/emscripten-module').then(mod => mod.default), + features: ${featuresJson}, } as const export default variant From 4db77b774edf2cbe743e2f7a8fa684a3d681278b Mon Sep 17 00:00:00 2001 From: Jake Teton-Landis Date: Mon, 16 Feb 2026 13:39:34 -0500 Subject: [PATCH 16/16] Format code with prettier Co-Authored-By: Claude Opus 4.5 --- .../src/module-asyncify.ts | 6 +++++- packages/quickjs-emscripten-core/src/types.ts | 9 ++++++++- packages/quickjs-emscripten/src/quickjs.test.ts | 17 +++++++++++++---- packages/quickjs-ffi-types/src/variant-types.ts | 9 ++++++++- scripts/prepareVariants.ts | 9 ++++++++- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/packages/quickjs-emscripten-core/src/module-asyncify.ts b/packages/quickjs-emscripten-core/src/module-asyncify.ts index f441880e..5021efc0 100644 --- a/packages/quickjs-emscripten-core/src/module-asyncify.ts +++ b/packages/quickjs-emscripten-core/src/module-asyncify.ts @@ -31,7 +31,11 @@ export class QuickJSAsyncWASMModule extends QuickJSWASMModule { protected module: QuickJSAsyncEmscriptenModule /** @private */ - constructor(module: QuickJSAsyncEmscriptenModule, ffi: QuickJSAsyncFFI, features: QuickJSFeatureRecord) { + constructor( + module: QuickJSAsyncEmscriptenModule, + ffi: QuickJSAsyncFFI, + features: QuickJSFeatureRecord, + ) { super(module, ffi, features) this.ffi = ffi this.module = module diff --git a/packages/quickjs-emscripten-core/src/types.ts b/packages/quickjs-emscripten-core/src/types.ts index 261df7e2..6663664c 100644 --- a/packages/quickjs-emscripten-core/src/types.ts +++ b/packages/quickjs-emscripten-core/src/types.ts @@ -19,7 +19,14 @@ import { QuickJSUnknownIntrinsic } from "./errors" * - `eval`: eval() function support * - `functions`: Host function callbacks (vm.newFunction) */ -export type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" | "functions" +export type QuickJSFeature = + | "modules" + | "promises" + | "symbols" + | "bigint" + | "intrinsics" + | "eval" + | "functions" /** * A QuickJSHandle to a constant that will never change, and does not need to diff --git a/packages/quickjs-emscripten/src/quickjs.test.ts b/packages/quickjs-emscripten/src/quickjs.test.ts index eb0f7aab..931ed600 100644 --- a/packages/quickjs-emscripten/src/quickjs.test.ts +++ b/packages/quickjs-emscripten/src/quickjs.test.ts @@ -719,7 +719,9 @@ function contextTests(getContext: GetTestContext, options: ContextTestOptions = it("returns the module exports", () => { requiresFeature("modules", () => { const modExports = vm.unwrapResult( - vm.evalCode(`export const s = "hello"; export const n = 42; export default "the default";`), + vm.evalCode( + `export const s = "hello"; export const n = 42; export default "the default";`, + ), ) const s = vm.getProp(modExports, "s").consume(vm.dump) @@ -833,7 +835,9 @@ export default "the default"; fnHandle.dispose() const result = vm.unwrapResult( - vm.evalCode(`(new Promise(resolve => resolve())).then(nextId).then(nextId).then(nextId);1`), + vm.evalCode( + `(new Promise(resolve => resolve())).then(nextId).then(nextId).then(nextId);1`, + ), ) assert.equal(i, 0) vm.runtime.executePendingJobs() @@ -854,7 +858,9 @@ export default "the default"; vm.setProp(vm.global, "nextId", fnHandle) fnHandle.dispose() - vm.unwrapResult(vm.evalCode(`(new Promise(resolve => resolve(5)).then(nextId));1`)).dispose() + vm.unwrapResult( + vm.evalCode(`(new Promise(resolve => resolve(5)).then(nextId));1`), + ).dispose() assert.strictEqual( vm.runtime.hasPendingJob(), true, @@ -1570,7 +1576,10 @@ describe("QuickJSWASMModule", () => { { name: "RELEASE_ASYNC", loader: () => newQuickJSAsyncWASMModule(RELEASE_ASYNC) }, ...(TEST_MQUICKJS ? [ - { name: "MQUICKJS_RELEASE_SYNC", loader: () => newQuickJSWASMModule(MQUICKJS_RELEASE_SYNC) }, + { + name: "MQUICKJS_RELEASE_SYNC", + loader: () => newQuickJSWASMModule(MQUICKJS_RELEASE_SYNC), + }, { name: "MQUICKJS_DEBUG_SYNC", loader: () => newQuickJSWASMModule(MQUICKJS_DEBUG_SYNC) }, ] : []), diff --git a/packages/quickjs-ffi-types/src/variant-types.ts b/packages/quickjs-ffi-types/src/variant-types.ts index d11fc0e0..f64c822d 100644 --- a/packages/quickjs-ffi-types/src/variant-types.ts +++ b/packages/quickjs-ffi-types/src/variant-types.ts @@ -28,7 +28,14 @@ type EmscriptenImport = * - `eval`: eval() function support * - `functions`: Host function callbacks (vm.newFunction) */ -export type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" | "functions" +export type QuickJSFeature = + | "modules" + | "promises" + | "symbols" + | "bigint" + | "intrinsics" + | "eval" + | "functions" /** * Feature support record for a QuickJS variant. diff --git a/scripts/prepareVariants.ts b/scripts/prepareVariants.ts index 743b3956..4cdfba89 100755 --- a/scripts/prepareVariants.ts +++ b/scripts/prepareVariants.ts @@ -50,7 +50,14 @@ enum EmscriptenEnvironment { * Features that may or may not be supported by a QuickJS variant. * This is the single source of truth for feature availability. */ -type QuickJSFeature = "modules" | "promises" | "symbols" | "bigint" | "intrinsics" | "eval" | "functions" +type QuickJSFeature = + | "modules" + | "promises" + | "symbols" + | "bigint" + | "intrinsics" + | "eval" + | "functions" const FEATURE_DESCRIPTIONS: Record = { modules: "ES Modules",