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`) 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/c/interface-mquickjs.c b/c/interface-mquickjs.c new file mode 100644 index 00000000..844f615d --- /dev/null +++ b/c/interface-mquickjs.c @@ -0,0 +1,825 @@ +/** + * 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" + +/** + * 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 + +#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; +} + +/** + * 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 + */ + +// 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, &qts_mquickjs_stdlib); + 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) { +#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--) { + JS_PushArg(ctx, *(argv_ptrs[i])); + } + JS_PushArg(ctx, *this_obj); + 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); +} + +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 +} + +// ---------------------------------------------------------------------------- +// C -> Host Callbacks + +#ifdef __EMSCRIPTEN__ +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 +#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 + + if (convert_result) { + return JS_ThrowInternalError(ctx, "qts_host_trampoline: invalid params"); + } + HostRefId host_ref_id = (HostRefId)host_ref_id_double; + +#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 + + return jsvalue_to_heap(func_obj); +} + +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..86c18b0e 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" 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/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/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md b/doc/@jitl/mquickjs-wasmfile-debug-sync/README.md new file mode 100644 index 00000000..25a82370 --- /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-16. + +## 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..72616e6a --- /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-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. | +| 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..d55664fa --- /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-16. + +## 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..a43a30a5 --- /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-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. | +| 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/@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/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..bf0c0ade 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-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. | +| 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-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. | +| 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/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/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/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/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/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/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/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/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..8ea02869 --- /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:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L75) + +## Properties + +### feature + +> **feature**: `string` + +The feature that is not supported + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L77) + +*** + +### name + +> **name**: `string` = `"QuickJSUnsupported"` + +#### Overrides + +`Error.name` + +#### 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) + +*** + +### operation + +> **operation**: `undefined` \| `string` + +The operation that was attempted, if specified + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L79) + +*** + +### variantName + +> **variantName**: `undefined` \| `string` + +The name of the variant that doesn't support this feature + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:81](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L81) + +*** + +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/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/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/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/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/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/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/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/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..338e1e8b --- /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:75](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L75) + +## Properties + +### feature + +> **feature**: `string` + +The feature that is not supported + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:77](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L77) + +*** + +### name + +> **name**: `string` = `"QuickJSUnsupported"` + +#### Overrides + +`Error.name` + +#### 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) + +*** + +### operation + +> **operation**: `undefined` \| `string` + +The operation that was attempted, if specified + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:79](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L79) + +*** + +### variantName + +> **variantName**: `undefined` \| `string` + +The name of the variant that doesn't support this feature + +#### Source + +[packages/quickjs-emscripten-core/src/errors.ts:81](https://github.com/justjake/quickjs-emscripten/blob/main/packages/quickjs-emscripten-core/src/errors.ts#L81) + +*** + +Generated using [typedoc-plugin-markdown](https://www.npmjs.com/package/typedoc-plugin-markdown) and [TypeDoc](https://typedoc.org/) 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/package.json b/package.json index d182c9bd..8fd13501 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/packages/quickjs-emscripten-core/README.md b/packages/quickjs-emscripten-core/README.md index 20f34fec..29624e40 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-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. | +| 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-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. | +| 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/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/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/features.ts b/packages/quickjs-emscripten-core/src/features.ts new file mode 100644 index 00000000..dbcbafd4 --- /dev/null +++ b/packages/quickjs-emscripten-core/src/features.ts @@ -0,0 +1,40 @@ +import type { QuickJSFeature, QuickJSFeatureRecord } from "@jitl/quickjs-ffi-types" +import { QuickJSUnsupported } from "./errors" + +// Re-export types for convenience +export type { QuickJSFeature, QuickJSFeatureRecord } + +/** + * 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 */ + constructor(private readonly featureRecord: QuickJSFeatureRecord) {} + + /** + * 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 { + return this.featureRecord[feature] + } + + /** + * 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) + } + } +} 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/index.ts b/packages/quickjs-emscripten-core/src/index.ts index 4f62a7b7..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 @@ -44,6 +45,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-asyncify.ts b/packages/quickjs-emscripten-core/src/module-asyncify.ts index e4d26cb0..5021efc0 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,12 @@ 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 } @@ -48,6 +56,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 ed5eaebf..e36e9e96 100644 --- a/packages/quickjs-emscripten-core/src/module-test.ts +++ b/packages/quickjs-emscripten-core/src/module-test.ts @@ -1,4 +1,5 @@ import type { QuickJSContext } from "./context" +import type { QuickJSFeatures } from "./features" import type { ModuleEvalOptions, QuickJSWASMModule } from "./module" import type { QuickJSRuntime } from "./runtime" import type { ContextOptions, RuntimeOptions } from "./types" @@ -84,4 +85,8 @@ export class TestQuickJSWASMModule implements Pick 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) diff --git a/packages/quickjs-emscripten-core/src/types.ts b/packages/quickjs-emscripten-core/src/types.ts index a5684914..6663664c 100644 --- a/packages/quickjs-emscripten-core/src/types.ts +++ b/packages/quickjs-emscripten-core/src/types.ts @@ -7,6 +7,27 @@ 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 + * - `functions`: Host function callbacks (vm.newFunction) + */ +export type QuickJSFeature = + | "modules" + | "promises" + | "symbols" + | "bigint" + | "intrinsics" + | "eval" + | "functions" + /** * A QuickJSHandle to a constant that will never change, and does not need to * be disposed. 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 644fdaa0..931ed600 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, @@ -35,6 +37,8 @@ import { isFail, DEBUG_ASYNC, DEBUG_SYNC, + MQUICKJS_DEBUG_SYNC, + MQUICKJS_RELEASE_SYNC, memoizePromiseFactory, debugLog, errors, @@ -51,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:", { @@ -58,6 +63,7 @@ console.log("test sets:", { TEST_DEBUG, TEST_NG, TEST_ASYNC, + TEST_MQUICKJS, TEST_SLOW, DEBUG, }) @@ -98,6 +104,65 @@ 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 + * @returns Promise if the test function is async, void otherwise + */ + function requiresFeature( + feature: QuickJSFeature, + featuresOrFn: QuickJSFeatures | (() => unknown), + maybeFn?: () => unknown, + ): unknown { + 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! + } + + 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 + return handleResult(fn()) + } else { + // Feature not supported - allow success OR QuickJSUnsupported error + try { + 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 + } + } + } + beforeEach(async () => { testId++ thisTestFailed = false @@ -144,10 +209,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 +232,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", () => { @@ -560,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", + }) }) }) @@ -610,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 () => { @@ -640,143 +717,161 @@ 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") + }) }) }) @@ -1029,108 +1124,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`) }) }) @@ -1395,6 +1500,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) { @@ -1451,6 +1574,15 @@ 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/variant-types.ts b/packages/quickjs-ffi-types/src/variant-types.ts index 122d35e1..f64c822d 100644 --- a/packages/quickjs-ffi-types/src/variant-types.ts +++ b/packages/quickjs-ffi-types/src/variant-types.ts @@ -16,6 +16,32 @@ 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 +55,7 @@ export interface QuickJSSyncVariant { readonly type: "sync" readonly importFFI: () => Promise QuickJSFFI> readonly importModuleLoader: () => Promise> + readonly features: QuickJSFeatureRecord } /** @@ -44,6 +71,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/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..15aa3cc2 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/Makefile @@ -0,0 +1,227 @@ +# 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 + # 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_*) + 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)) + +# 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) +# 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 $@ $< + +# 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 $@ + +############################################################################### +# 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/README.md b/packages/variant-mquickjs-wasmfile-debug-sync/README.md new file mode 100644 index 00000000..21fadfb3 --- /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-16. + +## 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/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:*" + } +} 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..6740134d --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/ffi.ts @@ -0,0 +1,443 @@ +// 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, + HostRefId, + 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_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, [ + "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_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, + 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..da9713ba --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-debug-sync/src/index.ts @@ -0,0 +1,34 @@ +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-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. | + * | 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), + 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-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..953f8880 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/Makefile @@ -0,0 +1,221 @@ +# 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 + # 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_*) + 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)) + +# 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) +# 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 $@ $< + +# 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 $@ + +############################################################################### +# 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/README.md b/packages/variant-mquickjs-wasmfile-release-sync/README.md new file mode 100644 index 00000000..cd382f47 --- /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-16. + +## 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..17e6ccac --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/ffi.ts @@ -0,0 +1,443 @@ +// 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, + HostRefId, + 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_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, [ + "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_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, + 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..e637f6b6 --- /dev/null +++ b/packages/variant-mquickjs-wasmfile-release-sync/src/index.ts @@ -0,0 +1,34 @@ +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-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. | + * | 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), + 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/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/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile b/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile index 829bbcd1..6ad926e1 100644 --- a/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile +++ b/packages/variant-quickjs-asmjs-mjs-release-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +176,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -183,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/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/Makefile b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile index 3f6fd189..16762c58 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +185,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -192,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/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/Makefile b/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile index e6899c27..75674542 100644 --- a/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +179,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -186,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/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/Makefile b/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile index c29e30b1..d0e42cf8 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +180,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -187,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/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/Makefile b/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile index 1f9b8cd7..d29a510f 100644 --- a/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile +++ b/packages/variant-quickjs-ng-wasmfile-release-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +173,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -180,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/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/Makefile b/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile index 6b064d51..27401add 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-browser-debug-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +186,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -193,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/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/Makefile b/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile index b0c4f623..5dd564d0 100644 --- a/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-browser-debug-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +180,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -187,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/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/Makefile b/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile index c8c816d9..7a29e706 100644 --- a/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-browser-release-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +181,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -188,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/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/Makefile b/packages/variant-quickjs-singlefile-browser-release-sync/Makefile index ed64c0f0..27aacfed 100644 --- a/packages/variant-quickjs-singlefile-browser-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-browser-release-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +174,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -181,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/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/Makefile b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile index e5bf664b..f91b32c1 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-debug-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +186,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -193,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/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/Makefile b/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile index db00533f..8c275b6a 100644 --- a/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-debug-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +180,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -187,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/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/Makefile b/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile index 95831d60..92602ad3 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-release-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +181,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -188,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/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/Makefile b/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile index 72e18701..52f54ac7 100644 --- a/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-cjs-release-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +174,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -181,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/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/Makefile b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile index ff7a1084..a5286917 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-debug-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +186,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -193,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/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/Makefile b/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile index ff580933..2eadbb5a 100644 --- a/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-debug-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +180,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -187,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/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/Makefile b/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile index 8323b760..0ea2f0dc 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-release-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +181,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -188,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/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/Makefile b/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile index daedba45..b1721f90 100644 --- a/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile +++ b/packages/variant-quickjs-singlefile-mjs-release-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +174,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -181,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/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/Makefile b/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile index 7096239a..a9fab004 100644 --- a/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile +++ b/packages/variant-quickjs-wasmfile-debug-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +185,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -192,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/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/Makefile b/packages/variant-quickjs-wasmfile-debug-sync/Makefile index 81bac19c..73dc7fc0 100644 --- a/packages/variant-quickjs-wasmfile-debug-sync/Makefile +++ b/packages/variant-quickjs-wasmfile-debug-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +179,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -186,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/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/Makefile b/packages/variant-quickjs-wasmfile-release-asyncify/Makefile index 675864a6..c6e884d7 100644 --- a/packages/variant-quickjs-wasmfile-release-asyncify/Makefile +++ b/packages/variant-quickjs-wasmfile-release-asyncify/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +180,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -187,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/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/Makefile b/packages/variant-quickjs-wasmfile-release-sync/Makefile index d5110712..9c8d2d83 100644 --- a/packages/variant-quickjs-wasmfile-release-sync/Makefile +++ b/packages/variant-quickjs-wasmfile-release-sync/Makefile @@ -21,18 +21,30 @@ 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 + # 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_*) 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 +173,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -180,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/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/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/scripts/prepareVariants.ts b/scripts/prepareVariants.ts index e2117101..4cdfba89 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 { @@ -45,6 +46,65 @@ 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" @@ -91,7 +151,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 +223,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 +600,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 = { @@ -767,6 +833,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 ` @@ -780,6 +849,7 @@ const variant: ${variantTypeName} = { type: '${modeName}', importFFI: () => Promise.resolve(${className}), importModuleLoader: () => Promise.resolve(moduleLoader), + features: ${featuresJson}, } as const export default variant ` @@ -795,6 +865,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 @@ -872,11 +943,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/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/templates/Variant.mk b/templates/Variant.mk index b177c4fd..ccf7c253 100644 --- a/templates/Variant.mk +++ b/templates/Variant.mk @@ -21,18 +21,30 @@ 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 + # 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_*) 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)) @@ -156,6 +168,14 @@ $(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) +# 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 $@ $< + +# Generic rule for other wrapper files $(BUILD_WRAPPER)/%.o: $(WRAPPER_ROOT)/%.c $(WASM_SYMBOLS) | $(EMCC_SRC) $(MKDIRP) $(EMCC) $(CFLAGS_WASM) $(WRAPPER_DEFINES) -c -o $@ $< @@ -175,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 > $@ 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(); 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", diff --git a/vendor/mquickjs-patches/.gitkeep b/vendor/mquickjs-patches/.gitkeep new file mode 100644 index 00000000..e69de29b 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..c02dd417 --- /dev/null +++ b/vendor/mquickjs-patches/0001-add-JS_GetContextOpaque.patch @@ -0,0 +1,28 @@ +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; ++} ++ + 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 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/Changelog b/vendor/mquickjs/Changelog new file mode 100644 index 00000000..7d056202 --- /dev/null +++ b/vendor/mquickjs/Changelog @@ -0,0 +1 @@ +2025-12-22: First public version diff --git a/vendor/mquickjs/LICENSE b/vendor/mquickjs/LICENSE new file mode 100644 index 00000000..a08db69d --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/Makefile b/vendor/mquickjs/Makefile new file mode 100644 index 00000000..109fcb27 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/README.md b/vendor/mquickjs/README.md new file mode 100644 index 00000000..3e53b59a --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/cutils.c b/vendor/mquickjs/cutils.c new file mode 100644 index 00000000..57a71bde --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/cutils.h b/vendor/mquickjs/cutils.h new file mode 100644 index 00000000..c0dd0011 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/dtoa.c b/vendor/mquickjs/dtoa.c new file mode 100644 index 00000000..604f3f0c --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/dtoa.h b/vendor/mquickjs/dtoa.h new file mode 100644 index 00000000..91b025b4 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/example.c b/vendor/mquickjs/example.c new file mode 100644 index 00000000..5385ede1 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/example_stdlib.c b/vendor/mquickjs/example_stdlib.c new file mode 100644 index 00000000..82d49434 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/libm.c b/vendor/mquickjs/libm.c new file mode 100644 index 00000000..bff8ed1c --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/libm.h b/vendor/mquickjs/libm.h new file mode 100644 index 00000000..17e94e0e --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/list.h b/vendor/mquickjs/list.h new file mode 100644 index 00000000..5c182340 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/mqjs.c b/vendor/mquickjs/mqjs.c new file mode 100644 index 00000000..46ad9536 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/mqjs_stdlib.c b/vendor/mquickjs/mqjs_stdlib.c new file mode 100644 index 00000000..0a30833c --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/mquickjs.c b/vendor/mquickjs/mquickjs.c new file mode 100644 index 00000000..3cd7affb --- /dev/null +++ b/vendor/mquickjs/mquickjs.c @@ -0,0 +1,18329 @@ +/* + * 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_GetContextOpaque(JSContext *ctx) +{ + return ctx->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/vendor/mquickjs/mquickjs.h b/vendor/mquickjs/mquickjs.h new file mode 100644 index 00000000..cf12c955 --- /dev/null +++ b/vendor/mquickjs/mquickjs.h @@ -0,0 +1,383 @@ +/* + * 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_GetContextOpaque(JSContext *ctx); +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/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 + diff --git a/vendor/mquickjs/mquickjs_build.c b/vendor/mquickjs/mquickjs_build.c new file mode 100644 index 00000000..61732715 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/mquickjs_build.h b/vendor/mquickjs/mquickjs_build.h new file mode 100644 index 00000000..51be6b44 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/mquickjs_opcode.h b/vendor/mquickjs/mquickjs_opcode.h new file mode 100644 index 00000000..2ccd6050 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/mquickjs_priv.h b/vendor/mquickjs/mquickjs_priv.h new file mode 100644 index 00000000..37bc1535 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/readline.c b/vendor/mquickjs/readline.c new file mode 100644 index 00000000..e5265891 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/readline.h b/vendor/mquickjs/readline.h new file mode 100644 index 00000000..639750ac --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/readline_tty.c b/vendor/mquickjs/readline_tty.c new file mode 100644 index 00000000..9a7e929b --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/readline_tty.h b/vendor/mquickjs/readline_tty.h new file mode 100644 index 00000000..7ef33cb5 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/softfp_template.h b/vendor/mquickjs/softfp_template.h new file mode 100644 index 00000000..34034fd8 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/softfp_template_icvt.h b/vendor/mquickjs/softfp_template_icvt.h new file mode 100644 index 00000000..02fee109 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/mandelbrot.js b/vendor/mquickjs/tests/mandelbrot.js new file mode 100644 index 00000000..40b6dfa7 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/microbench.js b/vendor/mquickjs/tests/microbench.js new file mode 100644 index 00000000..c41e15cd --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/test_builtin.js b/vendor/mquickjs/tests/test_builtin.js new file mode 100644 index 00000000..9032d18e --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/test_closure.js b/vendor/mquickjs/tests/test_closure.js new file mode 100644 index 00000000..a62e31a7 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/test_language.js b/vendor/mquickjs/tests/test_language.js new file mode 100644 index 00000000..b0d7c4df --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/test_loop.js b/vendor/mquickjs/tests/test_loop.js new file mode 100644 index 00000000..1f5d63d0 --- /dev/null +++ b/vendor/mquickjs/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/vendor/mquickjs/tests/test_rect.js b/vendor/mquickjs/tests/test_rect.js new file mode 100644 index 00000000..7027562e --- /dev/null +++ b/vendor/mquickjs/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(); 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"