diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-18 23:15:21 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-01-18 23:15:21 +0100 |
commit | 28fefabdeb685f4777ad2ad2ebf05e5e94e179d6 (patch) | |
tree | 56cd048df4a6987b4d9ef5924942119b3f2fd8aa /lib/monoucha0 | |
parent | b56abad0883c0b4179f9c329a7169bb544fac4a5 (diff) | |
parent | 33efaee783c2adb20df019a8bc924411c4a97e7f (diff) | |
download | chawan-28fefabdeb685f4777ad2ad2ebf05e5e94e179d6.tar.gz |
Add 'lib/monoucha0/' from commit '33efaee783c2adb20df019a8bc924411c4a97e7f'
git-subtree-dir: lib/monoucha0 git-subtree-mainline: b56abad0883c0b4179f9c329a7169bb544fac4a5 git-subtree-split: 33efaee783c2adb20df019a8bc924411c4a97e7f
Diffstat (limited to 'lib/monoucha0')
50 files changed, 85771 insertions, 0 deletions
diff --git a/lib/monoucha0/.gitignore b/lib/monoucha0/.gitignore new file mode 100644 index 00000000..63d0df9a --- /dev/null +++ b/lib/monoucha0/.gitignore @@ -0,0 +1,2 @@ +test/* +!test/*.* diff --git a/lib/monoucha0/Makefile b/lib/monoucha0/Makefile new file mode 100644 index 00000000..3e7a5258 --- /dev/null +++ b/lib/monoucha0/Makefile @@ -0,0 +1,29 @@ +NIM ?= nim +TESTDIR ?= /tmp/chagashi_test +FLAGS += --debugger:native --mm:refc + +.PHONY: all +all: test + +.PHONY: test_basic +test_basic: + $(NIM) $(FLAGS) r -p:. test/basic.nim + $(NIM) $(FLAGS) r -p:. -d:monouchaUseOpt=1 test/basic.nim + +.PHONY: test_regexonly +test_regexonly: + $(NIM) $(FLAGS) r -d:lreOnly=1 -p:. test/regexonly.nim + $(NIM) $(FLAGS) r -d:lreOnly=1 -p:. -d:monouchaUseOpt=1 test/basic.nim + +.PHONY: test_manual +test_manual: + $(NIM) $(FLAGS) r -p:. test/manual.nim + $(NIM) $(FLAGS) r -p:. -d:monouchaUseOpt=1 test/manual.nim + +.PHONY: test_etc +test_etc: + $(NIM) $(FLAGS) r -p:. test/etc.nim + $(NIM) $(FLAGS) r -p:. -d:monouchaUseOpt=1 test/etc.nim + +.PHONY: test +test: test_basic test_regexonly test_manual test_etc diff --git a/lib/monoucha0/NEWS b/lib/monoucha0/NEWS new file mode 100644 index 00000000..4ba0201e --- /dev/null +++ b/lib/monoucha0/NEWS @@ -0,0 +1,123 @@ +0.9.1 (2025.01.16) +* add type information to JSArrayBufferView +* add some more bindings + +0.9.0 (2025.01.13) +* drop support for pre-2.0.0 Nim versions +* drop support for generics and non-JSValue varargs +* drop undocumented $ -> toString renaming +* always set fromJS "out" param (set to default on failure) +* do not accept JS_NULL for ref object +* replace jsgetprop with jsgetrealprop +* type erase dfin + +This version brings a significant reduction in the feature set, and +changes in various APIs. Wrappers with implementations deemed +inefficient or overly complex have been removed. + +The JS_NULL change is intended to allow for non-nullable parameters. +In particular, it means that procedures with nullable parameters now +*must* wrap these in an Option to have the same effect as before. +However, ref object return values still convert `nil` to JS_NULL. + +Also, this release includes an optimization that is expected to break +ORC less subtly than before. As always, please make sure to use +--mm:refc. + +0.8.0 (2024.12.26) +* update QuickJS-NG to 0.8.0 + +0.7.2 (2024.11.25) +* fix some deinitialization bugs on JSRuntime.free +The test suite was being run for ORC only, so it failed to catch bugs +with refc deinitialization. + +0.7.1 (2024.11.22) +* fix wrong allocation sizes in jspropenum module +This could lead to memory corruption. + +0.7.0 (2024.11.18) +* clean up and complete exotic pragmas +Now the Monoucha API can express all exotics that QJS provides. + +0.6.0 (2024.11.16) +* update QuickJS-NG to 0.7.0 +* misc optimizations + +0.5.5 (2024.11.09) +* fix fromJS with seq +* fix JS_FreePropertyEnum binding + +0.5.4 (2024.11.08) +* fix some C warnings +* do not link to pthread with --threads:off + +0.5.3 (2024.10.28) +* fix assertion on creating 0-length property enum list + +0.5.2 (2024.10.12) +* fix a bug in our libregexp patch + +0.5.1 (2024.10.02) +* fix build with --threads:off + +0.5.0 (2024.09.29) +* switch to QuickJS-NG + +QuickJS-NG is a better maintained QuickJS fork. It has some useful +features like support for recent standards and column tracking for +errors. + +We import their latest release, which is 0.6.1. I plan to update this +for every new QJS-NG release. + +0.4.3 (2024.09.17) +* fix broken enum conversion + +0.4.2 (2024.09.17) +* throw on trying to call bound functions on prototypes +* fix inherited jsgetprop + +0.4.1 (2024.08.22) +* fix compilation on Nim 2.0.4 + +0.4.0 (2024.08.15) +* remove toJS(Rune) +* remove setInteruptHandler wrapper +* properly convert to pointer-sized int depending on pointer size +* fix incorrect varargs slicing +* misc refactoring + +0.3.0, 0.3.1 (2024.08.09) +* redesign fromJS API to reduce copying + - fromJS now returns an Opt[void] to signal errors, and fills + the third `res` parameter if it succeeded + - JSDict now supports .jsdefault to init values, and throws + on missing non-jsdefault values + - JSDict JSValue members are now automatically freed +This is a breaking change. +* allow eval without file name +* sync with upstream +* fix NUL handling in toJS(string) + +0.2.3 (2024.07.28) +* fix and optimize varargs[JSValue] jsfunc params + +0.2.2 (2024.07.18) +* fix wrong header used in libunicode binding + +0.2.1 (2024.07.17) +* fix a memory corruption bug +* add JSValueConst to bindings (for documentation purposes) +* update manual + +0.2.0 (2024.06.22) +* include missing QuickJS bindings +* add missing err...Error templates +* update manual + +0.1.1 (2024.06.03) +libunicode wrapper fixes + +0.1.0 (2024.06.03) +Initial release diff --git a/lib/monoucha0/README.md b/lib/monoucha0/README.md new file mode 100644 index 00000000..63409a08 --- /dev/null +++ b/lib/monoucha0/README.md @@ -0,0 +1,89 @@ +# Monoucha: seamless Nim-QuickJS integration + +Monoucha is a wrapper library to simplify the process of embedding the +[QuickJS-NG](https://github.com/quickjs-ng/quickjs) JavaScript engine into Nim +programs. + +## Quick start + +Include Monoucha in your project using either Nimble or as a git submodule. + +``` +requires "monoucha" +``` + +Then, + +* There is a [manual](doc/manual.md). Please read the manual. +* [Examples](test/manual.nim) from the manual, organized as unit tests. + +## Warning + +At the time of writing, Monoucha only works with refc. This means you have to +put the following in your nim.cfg: + +``` +--mm:refc +``` + +If you do not do this, you will be rewarded with strange crashes as your program +grows. + +## Dependencies + +monoucha depends on the [nim-results](https://github.com/arnetheduck/nim-results.git) +library. + +QuickJS-NG is already included in this repository; you do not need to install it +separately. + +## Q&A + +* Cool, so how do I use this thing? + +I'm working on a [manual](doc/manual.md). Please read the manual. + +* I'm getting weird memory errors? + +You did not read the above instructions, you have to set --mm:refc. + +Monoucha does not (and never did) work with ORC, or other memory managers for +that matter. You must use refc. + +(If you are still experiencing issues, please open a ticket +[here](https://todo.sr.ht/~bptato/chawan/) and I'll look into it.) + +* I already have QuickJS-NG, why are you not linking to my system library? + +Monoucha does not actually use stock QuickJS-NG, but a fork that tracks +upstream. + +This fork includes some GC modifications necessary for the synchronization +of the Nim and QuickJS runtimes. + +* Can I compile Nim to JS and execute Nim from Nim? + +Possibilities are endless, but [this](https://peterme.net/using-nimscript-as-a-configuration-language-embedding-nimscript-pt-1.html) +looks like a better solution. + +* Can I use Monoucha with `[insert JS engine]` instead of QuickJS? + +No. Feel free to fork and adapt it to whatever engine you want, but here we only +support QuickJS. + +* What *is* a monoucha? + +A kind of tea, from the town once called Monou. You pronounce it as mo-no-u-cha. + +Yes, it's a [pun](https://en.wikipedia.org/w/index.php?title=SpiderMonkey&oldid=1214134789#History). + +## License + +QuickJS was written by Fabrice Bellard and Charlie Gordon, and is maintained as +QuickJS-NG by Ben Noordhuis and Saúl Ibarra Corretgé. + +QuickJS-NG is distributed in this repository under the terms of the MIT +license. See the [monoucha/qjs/LICENSE](monoucha/qjs/LICENSE) file for details. + +Monoucha is released into the public domain. See the [UNLICENSE](UNLICENSE) file +for details. diff --git a/lib/monoucha0/UNLICENSE b/lib/monoucha0/UNLICENSE new file mode 100644 index 00000000..68a49daa --- /dev/null +++ b/lib/monoucha0/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to <http://unlicense.org/> diff --git a/lib/monoucha0/doc/manual.md b/lib/monoucha0/doc/manual.md new file mode 100644 index 00000000..a20b11c6 --- /dev/null +++ b/lib/monoucha0/doc/manual.md @@ -0,0 +1,739 @@ +# Monoucha manual + +**IMPORTANT**: currently, Monoucha only works correctly with refc, ergo you +*must* to put `--mm:refc` in your `nim.cfg`. ORC cannot deal with Monoucha's +GC-related hacks, and if you use ORC, you will run into memory errors on larger +projects. + +I hope to fix this in the future. For now, please use refc. + +**UNDER CONSTRUCTION**: this document is an incomplete draft. Partially untested +code and possible inaccuracies ahead. + +## Table of Contents + +* [Introduction](#introduction) + - [Hello, world](#hello-world) + - [Error handling](#error-handling) +* [Registering objects](#registering-objects) + - [registerType: registering type interfaces](#registertype-registering-type-interfaces) + * [Global objects](#global-objects) + * [Inheritance](#inheritance) + * [Misc registerType parameters](#misc-registertype-parameters) + - [jsget, jsset: basic property reflectors](#jsget-jsset-basic-property-reflection) + - [Non-reference objects](#non-reference-objects) +* [Function pragmas](#function-pragmas) + - [jsfunc: regular functions](#jsfunc-regular-functions) + - [jsctor: constructors](#jsctor-constructors) + - [jsfget, jsfset: custom property reflectors](#jsfget-jsfset-custom-property-reflectors) + - [jsstfunc: static functions](#jsstfunc-static-functions) + - [jsuffunc, jsufget, jsuffget: the LegacyUnforgeable property](#jsuffunc-jsufget-jsuffget-the-legacyunforgeable-property) + - [jsgetownprop, jsgetprop, jssetprop, jsdelprop, jshasprop, jspropnames: magic functions](#jsgetownprop-jsgetprop-jssetprop-jsdelprop-jshasprop-jspropnames-magic-functions) + - [jsfin: object finalizers](#jsfin-object-finalizers) +* [toJS, fromJS](#tojs-fromjs) + - [Using raw JSValues](#using-raw-jsvalues) + - [Using toJS](#using-tojs) + - [Using fromJS](#using-fromjs) + - [Custom type converters](#custom-type-converters) + +## Introduction + +Monoucha is a high-level wrapper to QuickJS. It was created for the +[Chawan](https://sr.ht/~bptato/chawan) browser to avoid having to manually write +bindings to JS APIs. + +First, a disclaimer: while Monoucha *is* high-level, it does not try to +completely abstract away the low-level details. You will in many cases have to +use QuickJS APIs directly to achieve something; Monoucha only provides +abstractions to APIs where doing something manually would be tedious and/or +error-prone. + +Also note that Monoucha is *not* complete. APIs may change, disappear, or appear +at any time in the future. Please pin a specific version if you need a stable +API. + +### Hello, world + +Let's start with a simplified version of the example from the README: + +```nim +import monoucha/fromjs +import monoucha/javascript + +let rt = newJSRuntime() +let ctx = rt.newJSContext() +const code = "'Hello from JS!'" +let val = ctx.eval(code) +var res: string +assert ctx.fromJS(val, res).isSome # no error +echo res # Hello from JS! +JS_FreeValue(ctx, val) +ctx.free() +rt.free() +``` + +This is the minimal required code to run a script. You may notice a few things: + +* eval() takes two parameters, one for code and one for the file name. The file + name will be used for exception handling. +* You have to free the context and runtime handles manually. This is + unfortunately unavoidable; the good news is that you won't have to do much + manual memory management after this. + +The `res` variable then holds a QuickJS JSValue. In this case, we convert it +to a string before freeing it; you can skip conversion if you don't care about +the script's return value, but you must *always* free it. + +You may be thinking to yourself, "come on, why is there no convenience wrapper +around this?" Well, it is because one thing we haven't talked about yet: + +### Error handling + +Let's get ourselves a ReferenceError: + +```nim +const code = "abcd" +let val = ctx.eval(code) +``` + +If you try to convert this into a string, you will get an err() result. +Obviously, you want to print your errors *somewhere*, but since Monoucha does +not know (or care) where you log error messages, it leaves this task to you. + +However, Monoucha does provide an easy way to retrieve the current error +message: + +```nim +if JS_IsException(res): + stderr.writeLine(ctx.getExceptionMsg()) +``` + +In most cases, you should wrap `eval` in a function that deals with exceptions +in the most appropriate way for your application. + +Alternatively, a self-contained evalConvert can be written as follows: + +```nim +import results +import monoucha/tojs + +proc evalConvert[T](ctx: JSContext; code: string; + file = "<input>"): Result[T, string] = + let val = ctx.eval(code, file, flags) + var res: T + if ctx.fromJS(val, res).isNone: + # Exception when converting the value. + JS_FreeValue(ctx, val) + return err(ctx.getExceptionMsg()) + JS_FreeValue(ctx, val) + # All ok! Return the converted object. + return ok(res) +``` + +However, it is usually more efficient (and simpler) to immediately log +exceptions than to pass them around as a Result. + +## Registering objects + +So far we have talked about running JS code and getting its result, which is +nice, but not enough for most use cases. If you are embedding QuickJS, you +probably want some sort of interoperability between JS and Nim code. + +In JavaScript, all objects are passed *by reference*. Monoucha allows you to +transparently use Nim object references in JS, provided you register their +interface first. + +### registerType: registering type interfaces + +To register object types as a JavaScript interface, you must call the +`registerType` macro on the JS context with a type of your choice. + +```nim +macro registerType*(ctx: JSContext; t: typed; parent: JSClassID = 0; + asglobal: static bool = false; nointerface = false; + name: static string = ""; has_extra_getset: static bool = false; + extra_getset: static openArray[TabGetSet] = []; namespace = JS_NULL; + errid = opt(JSErrorEnum); ishtmldda = false): JSClassID +``` + +Typically, you would do this using Nim reference types; non-reference types have +some restrictions, which we will cover later. + +Now for the first example. Following code registers a JS interface for the Nim +object `Moon`: + +```nim +type Moon = ref object + +jsDestructor(Moon) + +# [...] +ctx.registerType(Moon) +const code = "Moon" +let val = ctx.eval(code) +var res: string +assert ctx.fromJS(val, res).isSome # no error +echo res # function Moon() [...] +JS_FreeValue(ctx, val) +``` + +Quite straightforward; just call `registerType`. + +One thing to pay attention to is the jsDestructor template: you must place a +jsDestructor call directly after your type declaration *before* any other +functions, or Monoucha will complain. (This is necessary so that we can generate +a `=destroy` hook for the object.) + +#### Global objects + +`registerType` also allows you to change the global object's type; this is quite +important, as it is the only way to create global functions (discounting the +constructors of object interfaces). + +To register a global type, you must set `asglobal = true` in `registerType`: + +```nim +type Earth = ref object + +# [...] +let earth = Earth() +ctx.registerType(Earth, asglobal = true) +ctx.setGlobal(earth) +const code = "assert(globalThis instanceof Earth)" +let val = ctx.eval(code) +assert not JS_IsException(val) +JS_FreeValue(ctx, val) +``` + +You may notice two things: + +* We call `setGlobal` with an instance of Earth. This is needed to register some + object as the backing Nim object; this same instance of Earth will be passed + to bound functions. +* This time, we do not call jsDestructor. This is because the global object is + special-cased; its reference is kept until the JS context gets + freed. Therefore it does not need a `=destroy` hook. + +#### Inheritance + +`registerType` also allows you to specify inheritance chains by setting the +`parent` parameter: + +```nim +type + Planet = ref object of RootObj + Earth = ref object of Planet + Moon = ref object of Planet + +# [...] +let planetCID = ctx.registerType(Planet) +ctx.registerType(Earth, parent = planetCID, asglobal = true) +ctx.registerType(Moon, parent = planetCID) +ctx.setGlobal(Earth()) # make sure to set a global so global functions work +const code = "assert(globalThis instanceof Planet)" +let val = ctx.eval(code) +assert not JS_IsException(val) +JS_FreeValue(ctx, val) +``` + +In this model, the inheritance tree looks like: + +* Planet + - Earth + - Moon + +Note that there is no strict requirement to actually model the Nim inheritance +chain; e.g. if we set "Rock" as the parent of Planet, then we could use Rock as +the direct ancestor of Earth without even registering Planet at all. + +However, this is a two-edged blade, as it also allows specifying invalid models +which may result in undefined behavior. For example, setting Earth as the parent +of Moon is invalid, as it will result in "Moon" Nim objects being casted to +Earth references. + +#### Misc registerType parameters + +Following parameters also exist: + +* `nointerface`: suppress constructor creation +* `name`: set a different JS name than the Nim name +* `has_extra_getset`, `extra_getset`: add an array of magic getters/setters. + Note that `has_extra_getset` must be set to true in case you pass anything in + `extra_getset` because of a compiler bug. (TODO elaborate here) +* `namespace`: instead of defining the constructor on the global object, define + it on the passed JS object. Note that you must use QuickJS APIs to create + this object, and that the object is not consumed (i.e. is not freed). +* `errid`: set the error ID. TODO this is currently pretty useless as a public + API because JSErrorEnum is not extendable +* `ishtmldda`: creates a "falsy" object, like document.all. Note that currently + only one of these is allowed per context. + + +### jsget, jsset: basic property reflectors + +Time to actually expose some Nim values to JS. + +The `jsget` and `jsset` pragmas can be set on fields of registered object +types to directly expose them to JS: + +```nim +type + Moon = ref object + + Earth = ref object + moon {.jsget.}: Moon + name {.jsgetset.}: string + population {.jsset.}: int64 + +jsDestructor(Moon) + +# [...] +let earth = Earth(moon: Moon(), population: 1, name: "Earth") +ctx.registerType(Earth, asglobal = true) +ctx.registerType(Moon) +ctx.setGlobal(earth) +const code = """ +globalThis.population = 8e9; +"name: " + globalThis.name + ", moon: " + globalThis.moon; +""" +let val = ctx.eval(code) +var res: string +assert ctx.fromJS(val, res).isSome # no error +echo res # name: Earth, moon: [object Moon] +echo earth.population # 8e9 +JS_FreeValue(ctx, val) +``` + +In the above example, we expose an Earth instance as the global object, and +modify/inspect it. By default, object fields are not exposed to JS; `{.jsget.}` +gives JS read-only access, `{.jsset.}` write-only, and `jsgetset` expands to +`{.jsget, jsset.}`. + +### Non-reference objects + +JavaScript only has reference semantics for objects, so this does not make +much sense at first sight. However, children of heap-allocated objects do in +fact have a permanent address, which we can convert to JS so long as we hold a +reference to their parent. + +e.g. this works: + +```nim +type + Moon = object + + Earth = ref object + moon {.jsget.}: Moon + +jsDestructor(Moon) + +# [...] +let earth = Earth(moon: Moon()) +ctx.registerType(Earth, asglobal = true) +ctx.registerType(Moon) +ctx.setGlobal(earth) +const code = "globalThis.moon" +let val = ctx.eval(code) +var res: string +assert ctx.fromJS(val, res).isSome # no error +echo res # [object Moon] +JS_FreeValue(ctx, val) +``` + +Do note that this has some restrictions: for example, you cannot return a +non-reference object from a [wrapped](#function-pragmas) function. + +## Function pragmas + +Arguably the most important feature of Monoucha is that it lets you +automatically wrap functions and expose them to JavaScript by associating them +with types exposed through [registerType](#registerType-registering-type-interfaces). + +This is done by sticking pragmas to function definitions; here we enumerate over +all currently available pragmas. + +### jsfunc: regular functions + +The simplest pragma is `.jsfunc`: this marks the function as a member of the +JS interface associated with the first parameter's type. + +Example: + +```nim +type + Window = ref object + console {.jsget.}: Console + + Console = ref object + +jsDestructor(Console) + +proc log(console: Console; s: string) {.jsfunc.} = + echo s + +# [...] +let window = Window(console: Console()) +ctx.registerType(Window, asglobal = true) +ctx.registerType(Console) +ctx.setGlobal(window) +const code = "console.log('Hello, world!')" +JS_FreeValue(ctx, ctx.eval(code)) +``` + +As you can see, `log` has been exposed as a member of the JS interface +`Console`. + +It is possible to use a different name for the JS function than for the Nim +procedure. e.g. the following will also expose a `log` function: + +```nim +proc jsLog(console: Console; s: string) {.jsfunc: "log".} = # [...] +``` + +In general, you can use any combination of parameters in `.jsfunc` procs. +These are converted on a best-effort basis: e.g. in the above example, +`console.log(1)` would pass the string "1", not an exception. Monoucha tries to +adhere to the WebIDL standard in this regard. (TODO: find & document places +where this is not true yet.) + +In case of types that can be "nil", Monoucha will convert "nil" return +values to JS "null" values. However, "null" is *not* converted to "nil". +Please wrap types where you wish to accept "null" too in `Option`s (from +`std/options`). + +The first parameter *must* be a reference type that has been registered using +`registerType`. Alternatively, you can also use a registered non-reference +object type, but in this case, you *must* annotate it with `var`: + +```nim +type Console2 = object # not ref! + +proc log(console: var Console2; s: string) {.jsfunc.} = # [...] +``` + +It is also possible to insert a "zeroeth" parameter to get a reference to the +current JS context. This is useful if you want to access state global to the +JS context without storing a backreference to the global object: + +```nim +proc log(ctx: JSContext; console: Console; s: string) {.jsfunc.} = + # This assumes you have already setGlobal a Window instance. + let global = JS_GetGlobalObject(ctx) + var window: Window + assert ctx.fromJS(global, window).isSome # no error + JS_FreeValue(ctx, global) + # Now you can do something with the window, e.g. + window.outFile.writeLine(s) +``` + +It is also possible to use `varargs` in `.jsfunc` functions: + +```nim +proc log(ctx: JSContext; console: Console; ss: varargs[JSValue]) {.jsfunc.} = + discard # can be called like `console.log("a", "b", "c", "d")` +``` + +For efficiency reasons, only `JSValue` varargs are supported. + +(In the past, union types and non-JSValue varargs also worked. This +feature was dropped because it generated inefficient and bloated code; +`fromJS` with `JSValue` parameters can be used to the same effect.) + +For further information about individual type conversions, see the +[toJS, fromJS](#tojs-fromjs) section. + +### jsctor: constructors + +The `.jsctor` pragma is used to define a constructor for a specific type: + +```nim +type JSFile = ref object + path {.jsget.}: string + +jsDestructor(JSFile) + +proc newJSFile(path: string): JSFile {.jsctor.} = + return JSFile(path: path) + +# [...] +# Use different name in JS through `name': File in JS is mapped to JSFile +# in Nim. +ctx.registerType(JSFile, name = "File") +const code = "console.log(new File('/path/to/file'))" +JS_FreeValue(ctx, ctx.eval(code)) # [object File] +``` + +Note that `.jsctor`, like other pragmas, supports the same "zeroeth" JSContext +parameter trick as [jsfunc](#jsfunc-regular-functions), which is useful when +the global object is needed for resource allocation. + +### jsfget, jsfset: custom property reflectors + +The `.jsfget` and `.jsfset` pragmas can be used to define custom getter/setter +functions. + +Like `.jsget` and `.jsset`, they appear as regular getters and setters in +JS. However, instead of automatically reflecting a property, `.jsfget` and +`.jsfset` allows you to write custom code to handle property accesses. + +Example: + +```nim +# [...] (see above for constructor) + +func name(file: JSFile): string {.jsfget.} = + return file.path.substr(file.path.rfind('/') + 1) + +proc setName(file: JSFile; s: string) {.jsfset: "name".} = + let i = file.path.rfind('/') + file.path = file.path.substr(0, i) & s + +# [...] +const code = """ +const file = new JSFile("/path/to/file"); +console.log(file.path); /* /path/to/file */ +console.log(file.name); /* file */ +file.name = "new-name"; +console.log(file.path); /* /path/to/new-name */ +""" +JS_FreeValue(ctx, ctx.eval(code)) +``` + +### jsstfunc: static functions + +`.jsstfunc` defines a static function on a given interface. Unlike with +`.jsfunc`, you must provide at least a single parameter for these functions, +with the syntax `Interface.functionName`. + +Note that `Interface` must be an interface registered through `registerType`. +If the interface was renamed, the Nim name (*not* the JS name) must be used. + +Example: + +```nim +# [...] (see above for constructor) + +proc jsExists(path: string): bool {.jsstfunc: "JSFile.exists".} = + return fileExists(path) + +# [...] +const code = """ +console.log(File.exists("doc/manual.md")); /* true */ +""" +JS_FreeValue(ctx, ctx.eval(code)) +``` + +### jsuffunc, jsufget, jsuffget: the LegacyUnforgeable property + +The pragmas `.jsuffunc`, `.jsufget` and `.jsuffget` correspond to the WebIDL +[`[LegacyUnforgeable]`](https://webidl.spec.whatwg.org/#LegacyUnforgeable) +property. + +Concretely, this means that the function (or getter) is defined on *instances* +of the interface, not on the interface (i.e. object prototype) as a +non-configurable property. Even more concretely, this simply means that the +function (or getter) cannot be changed by JavaScript code. + +```nim +# this will always return the result of the fstat call. +proc owner(file: JSFile): int {.jsuffget.} = + let fd = open(cstring(file.path), O_RDONLY, 0) + if fd == -1: return -1 + var stats = Stat.default + if fstat(fd, stats) == -1: + discard close(fd) + return -1 + return int(stats.st_uid) + +proc getOwner(file: JSFile): int {.jsuffget.} = + return file.owner + +# [...] + +const code = """ +const file = new File("doc/manual.md"); +const oldGetOwner = file.getOwner; +file.getOwner = () => -2; /* doesn't work */ +assert(oldGetOwner == file.getOwner); +Object.defineProperty(file, "owner", { value: -2 }); /* throws */ +""" +JS_FreeValue(ctx, ctx.eval(code)) +``` + +### jsgetownprop, jsgetprop, jssetprop, jsdelprop, jshasprop, jspropnames: magic functions + +`.jsgetownprop`, `.jsgetprop`, `.jssetprop`, `.jsdelprop`, `.jshasprop` +and `.jspropnames` generate bindings for magic functions. These are +mainly useful for collections, where you want to provide custom behavior +for property accesses. + +(TODO elaborate...) + +### jsfin: object finalizers + +The `.jsfin` pragma can be used to clean up resources used by objects at the +end of their lifetime. + +In principle, this is just like the Nim `=destroy` property, except it also +tracks the lifetime of possible JS objects which the Nim object may back. (In +other words, it's a cross-GC finalizer.) + +The first parameter must be a reference to the object in question. Only one +`.jsfin` procedure per reference type is allowed, and `.jsfin` is *not* +inherited. This means that you must set up separate `.jsfin` functions for each +child object in the inheritance chain. + +To free up JS objects, you may precede your reference type parameter with a +`JSRuntime` parameter. + +WARNING: like Nim `=destroy`, this pragma is very easy to misuse. In particular, +you must make sure to **NEVER ALLOCATE** in a `.jsfin` finalizer, because this +[breaks](https://github.com/nim-lang/Nim/issues/4851) Nim refc. (I don't know if +this problem is still present in ORC, but at the moment Monoucha does not work +with ORC anyway.) + +Example: + +```nim +type JSFile = ref object + path: string + buffer: pointer # some internal buffer handled as managed memory +jsDestructor(JSFile) + +proc newJSFile(path: string): JSFile {.jsctor.} = + return JSFile( + path: path, + buffer: alloc(4096) + ) + +var unrefd {.global.} = 0 +proc finalize(file: JSFile) {.jsfin.} = + if file.buffer != nil: + dealloc(file.buffer) + # Note: it is not necessary to nil out the pointer; it's just me being + # paranoid :P + file.buffer = nil + inc unrefd + +# [...] + +const code = """ +{ + /* following call is in a separate code, so QJS can unref it + * immediately. */ + const file = new File("doc/manual.md"); +} +/* in contrast, following file will not be deallocated until the runtime is + * gone. */ +const file = new File("doc/manual.md"); +""" +JS_FreeValue(ctx, ctx.eval(code)) +GC_fullCollect() # ensure refc runs +assert unrefd == 1 # first file is already deallocated +ctx.free() +GC_fullCollect() # ensure refc runs +assert unrefd == 1 # the second file is still available +rt.free() +assert unrefd == 2 # runtime is freed, so the second file gets deallocated too +``` + +## toJS, fromJS + +This section covers the handling and conversion of JSValue types. + +While in most cases it is possible to avoid using JSValues, Monoucha does not go +out of its way to completely eliminate them. + +In particular, handling JSValues is unavoidable when: + +* You want to do something with `eval()`'s result +* You need to call a QuickJS API not wrapped by Monoucha (e.g. JS function calls) +* You want a dynamically typed variable, e.g. for "union" types + +### Using raw JSValues + +When passing around raw JSValues, it is important to make sure you +reference/unreference appropriately. For this, use the `JS_DupValue` and +`JS_FreeValue` functions from QuickJS. (When you only have access to a +`JSRuntime`, use the `JS_FreeValueRT` and `JS_DupValueRT` variants instead.) + +To get raw JSValues in `.jsfunc` (or similar) bound functions, you can simply +set the desired parameter's type to `JSValue`. This way, you get a "borrowed" +JSValue; to keep a reference to these after the function exits, reference them +with `JS_DupValue` first. (Analogously, you do not have to free such JSValues as +long as you don't call `JS_DupValue` on them, either.) + +Since JSValues need a JSContext to do anything useful, you may want to set the +first parameter of such functions to a `JSContext` type; this passes the current +JSContext on to the bound function. (For details, see +[above](#jsfunc-regular-functions).) + +### Using toJS + +```nim +proc toJS[T](ctx: JSContext; val: T): JSValue +``` + +Monoucha internally uses the overloaded `toJS` function to convert bound +function return values to JS values. This is available to user code too; simply +import the `monoucha/tojs` module. + +Naturally, JSValues you get from toJS are owned by you, so you should call +`JS_FreeValue` on these when you no longer need them. + +The `tojs` module also includes some other convenience functions: + +* `defineProperty`, `definePropertyC`, `definePropertyE`, `definePropertyCW`, + `definePropertyCWE`: simple wrappers around `JS_DefineProperty*` functions + from the QuickJS API. Unlike the QuickJS versions, they panic on errors, so + only use these if you are 100% sure that they always succeed. + The `C`, `E`, `CW`, `CWE` represent the "configurable", "enumerable", + and "writable" flags of the property. + Warning: like in QuickJS, these functions *consume* a JSValue; that is, if + you pass a JSValue, then be aware that the function will call `JS_FreeValue` + on it. +* `newFunction`: creates a new JavaScript function. `args` is a list of + parameter names, `body` is the JavaScript function body. + +### Using fromJS + +```nim +proc fromJS[T](ctx: JSContext; val: JSValue; res: var T): Err[void] +``` + +`fromJS` is the opposite of `toJS`: it converts JSValues into Nim values. To use +it, import the `monoucha/fromjs` module. + +On success, `fromJS` fills res and returns `Opt[void].some()`. + +On failure, `res` is left in its initial state, **except if it's a JSDict (see +below!)**, a QuickJS exception is thrown (using `JS_Throw()`), and +`Opt[void].none()` is returned. + +**Warning**: since fromJS is mainly meant to be used by automatic wrappers, it +performs the following optimization: for `JSDict` values, `res` is left in +a modified state. + +**Warning 2**: JSDict in general is somewhat finnicky: you must make sure +that their destructors run before deinitializing the runtime. In practice, +this means a) you must not use JSDict in the same procedure where you free the +JSRuntime, b) you must call GC_fullCollect before freeing the runtime if you +use JSDict. (TODO: this all seems very broken. Why isn't JSDict itself just a +ref object?) + +Passing `JS_EXCEPTION` to `fromJS` is valid, and results in no new exception +being thrown. + +### Custom type converters + +In Monoucha, object reference types are automatically converted to JS reference +types. However, value types are different: + +* A non-reference `object` is converted to a JS reference by implicitly turning + it into `ptr object`, as noted [above](#non-reference-objects). +* Trying to pass any other type to/from JS errors out at compilation. + +To work around this limitation, you can override `toJS` and `fromJS` for +specific types. In both cases, it is enough to add an overload for the +respective function and expose it to the module where the converter is needed +(i.e. where you call `registerType`). diff --git a/lib/monoucha0/monoucha.nimble b/lib/monoucha0/monoucha.nimble new file mode 100644 index 00000000..7317f57e --- /dev/null +++ b/lib/monoucha0/monoucha.nimble @@ -0,0 +1,13 @@ +# Package + +version = "0.9.1" +author = "bptato" +description = "Seamless Nim-QuickJS integration" +license = "Unlicense" +skipDirs = @["test"] + + +# Dependencies + +requires "nim >= 2.0.0" +requires "results >= 0.4.0" diff --git a/lib/monoucha0/monoucha/constcharp.nim b/lib/monoucha0/monoucha/constcharp.nim new file mode 100644 index 00000000..79242f9d --- /dev/null +++ b/lib/monoucha0/monoucha/constcharp.nim @@ -0,0 +1,16 @@ +{.push raises: [].} + +type + cstringConstImpl {.importc: "const char*".} = cstring + cstringConst* = distinct cstringConstImpl + +proc `[]`*(s: cstringConst; i: int): char = cstring(s)[i] +proc `$`*(s: cstringConst): string {.borrow.} + +converter toCstring*(s: cstringConst): cstring {.inline.} = + return cstring(s) + +converter toCstringConst*(s: cstring): cstringConst {.inline.} = + return cstringConst(s) + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/eprint.nim b/lib/monoucha0/monoucha/eprint.nim new file mode 100644 index 00000000..73c207e8 --- /dev/null +++ b/lib/monoucha0/monoucha/eprint.nim @@ -0,0 +1,39 @@ +{.used.} + +template eprint0(s: varargs[string]) = + {.cast(noSideEffect), cast(tags: []), cast(raises: []).}: + var o = "" + for i in 0 ..< s.len: + if i != 0: + o &= ' ' + o &= s[i] + when nimvm: + echo o + else: + when not declared(stderr): + echo o + else: + o &= '\n' + stderr.write(o) + +when defined(release): + func eprint*(s: varargs[string, `$`]) + {.deprecated: "eprint is for debugging only".} = + eprint0(s) +else: + func eprint*(s: varargs[string, `$`]) = + eprint0(s) + +func elog*(s: varargs[string, `$`]) = + {.cast(noSideEffect), cast(tags: []), cast(raises: []).}: + var f: File = nil + if not open(f, "a", fmAppend): + return + var o = "" + for i in 0 ..< s.len: + if i != 0: + o &= ' ' + o &= s[i] + o &= '\n' + f.write(o) + close(f) diff --git a/lib/monoucha0/monoucha/fromjs.nim b/lib/monoucha0/monoucha/fromjs.nim new file mode 100644 index 00000000..7cf5db9a --- /dev/null +++ b/lib/monoucha0/monoucha/fromjs.nim @@ -0,0 +1,518 @@ +import std/algorithm +import std/macros +import std/options +import std/tables + +import jsopaque +import jstypes +import optshim +import quickjs + +proc fromJS*(ctx: JSContext; val: JSValue; res: out string): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out int32): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out int64): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out uint32): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out int): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out float64): Opt[void] +proc fromJS*[T: tuple](ctx: JSContext; val: JSValue; res: out T): Opt[void] +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out seq[T]): Opt[void] +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out set[T]): Opt[void] +proc fromJS*[A, B](ctx: JSContext; val: JSValue; res: out Table[A, B]): + Opt[void] +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out Option[T]): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out bool): Opt[void] +proc fromJS*[T: enum](ctx: JSContext; val: JSValue; res: out T): Opt[void] +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out ptr T): Opt[void] +proc fromJS*[T: ref object](ctx: JSContext; val: JSValue; res: out T): + Opt[void] +proc fromJS*[T: JSDict](ctx: JSContext; val: JSValue; res: var T): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out JSArrayBuffer): Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out JSArrayBufferView): + Opt[void] +proc fromJS*(ctx: JSContext; val: JSValue; res: out JSValue): Opt[void] + +func isInstanceOf*(ctx: JSContext; val: JSValue; class: cstring): bool = + let ctxOpaque = ctx.getOpaque() + var classid = JS_GetClassID(val) + let tclassid = ctxOpaque.creg.getOrDefault(class, JS_CLASS_OBJECT) + if classid == JS_CLASS_OBJECT: + let p0 = JS_VALUE_GET_PTR(ctxOpaque.global) + let p1 = JS_VALUE_GET_PTR(val) + if p0 == p1: + classid = ctxOpaque.gclass + var found = false + while true: + if classid == tclassid: + found = true + break + if int(classid) < ctxOpaque.parents.len: + classid = ctxOpaque.parents[int(classid)] + else: + classid = 0 # not defined by us; assume parent is Object. + if classid == 0: + break + return found + +func isSequence*(ctx: JSContext; o: JSValue): bool = + if not JS_IsObject(o): + return false + let prop = JS_GetProperty(ctx, o, ctx.getOpaque().symRefs[jsyIterator]) + # prop can't be exception (throws_ref_error is 0 and tag is object) + result = not JS_IsUndefined(prop) + JS_FreeValue(ctx, prop) + +proc fromJS*(ctx: JSContext; val: JSValue; res: out string): Opt[void] = + var plen {.noinit.}: csize_t + let outp = JS_ToCStringLen(ctx, plen, val) # cstring + if outp == nil: + res = "" + return err() + res = newString(plen) + if plen != 0: + copyMem(addr res[0], cstring(outp), plen) + JS_FreeCString(ctx, outp) + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out int32): Opt[void] = + var n {.noinit.}: int32 + if JS_ToInt32(ctx, n, val) < 0: + res = 0 + return err() + res = n + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out int64): Opt[void] = + var n {.noinit.}: int64 + if JS_ToInt64(ctx, n, val) < 0: + res = 0 + return err() + res = n + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out uint32): Opt[void] = + var n {.noinit.}: uint32 + if JS_ToUint32(ctx, n, val) < 0: + res = 0 + return err() + res = n + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out int): Opt[void] = + when sizeof(int) > 4: + var x: int64 + else: + var x: int32 + ?ctx.fromJS(val, x) + res = int(x) + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out float64): Opt[void] = + var n {.noinit.}: float64 + if JS_ToFloat64(ctx, n, val) < 0: + res = 0 + return err() + res = n + return ok() + +macro fromJSTupleBody(a: tuple) = + let len = a.getType().len - 1 + result = newStmtList(quote do: + var done {.inject.}: bool) + for i in 0 ..< len: + result.add(quote do: + let next = JS_Call(ctx, nextMethod, it, 0, nil) + let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) + defer: + JS_FreeValue(ctx, next) + JS_FreeValue(ctx, doneVal) + ?ctx.fromJS(doneVal, done) + if done: + JS_ThrowTypeError(ctx, + "too few arguments in sequence (got %d, expected %d)", cint(`i`), + cint(`len`)) + return err() + let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) + defer: JS_FreeValue(ctx, valueVal) + ?ctx.fromJS(valueVal, `a`[`i`]) + ) + if i == len - 1: + result.add(quote do: + let next = JS_Call(ctx, nextMethod, it, 0, nil) + defer: JS_FreeValue(ctx, next) + let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) + ?ctx.fromJS(doneVal, done) + var i = `i` + # we're emulating a sequence, so we must query all remaining parameters + # too: + while not done: + inc i + let next = JS_Call(ctx, nextMethod, it, 0, nil) + defer: JS_FreeValue(ctx, next) + let doneVal = JS_GetProperty(ctx, next, + ctx.getOpaque().strRefs[jstDone]) + defer: JS_FreeValue(ctx, doneVal) + ?ctx.fromJS(doneVal, done) + if done: + JS_ThrowTypeError(ctx, + "too many tuple members (got %d, expected %d)", cint(i), + cint(`len`)) + return err() + JS_FreeValue(ctx, JS_GetProperty(ctx, next, + ctx.getOpaque().strRefs[jstValue])) + ) + +proc fromJS*[T: tuple](ctx: JSContext; val: JSValue; res: out T): Opt[void] = + res = default(T) + let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) + if JS_IsException(itprop): + return err() + defer: JS_FreeValue(ctx, itprop) + let it = JS_Call(ctx, itprop, val, 0, nil) + defer: JS_FreeValue(ctx, it) + let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) + if JS_IsException(nextMethod): + return err() + defer: JS_FreeValue(ctx, nextMethod) + fromJSTupleBody(res) + return ok() + +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out seq[T]): Opt[void] = + let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) + if JS_IsException(itprop): + res = @[] + return err() + defer: JS_FreeValue(ctx, itprop) + let it = JS_Call(ctx, itprop, val, 0, nil) + defer: JS_FreeValue(ctx, it) + let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) + if JS_IsException(nextMethod): + res = @[] + return err() + defer: JS_FreeValue(ctx, nextMethod) + var tmp = newSeq[T]() + while true: + let next = JS_Call(ctx, nextMethod, it, 0, nil) + defer: JS_FreeValue(ctx, next) + let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) + defer: JS_FreeValue(ctx, doneVal) + var done: bool + ?ctx.fromJS(doneVal, done) + if done: + break + let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) + defer: JS_FreeValue(ctx, valueVal) + tmp.add(default(T)) + ?ctx.fromJS(valueVal, tmp[^1]) + res = move(tmp) + return ok() + +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out set[T]): Opt[void] = + let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) + if JS_IsException(itprop): + res = {} + return err() + defer: JS_FreeValue(ctx, itprop) + let it = JS_Call(ctx, itprop, val, 0, nil) + defer: JS_FreeValue(ctx, it) + let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) + if JS_IsException(nextMethod): + res = {} + return err() + defer: JS_FreeValue(ctx, nextMethod) + var tmp: set[T] = {} + while true: + let next = JS_Call(ctx, nextMethod, it, 0, nil) + defer: JS_FreeValue(ctx, next) + let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) + defer: JS_FreeValue(ctx, doneVal) + var done: bool + ?ctx.fromJS(doneVal, done) + if done: + break + let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) + defer: JS_FreeValue(ctx, valueVal) + var x: T + ?ctx.fromJS(valueVal, x) + tmp.incl(x) + res = tmp + return ok() + +proc fromJS*[A, B](ctx: JSContext; val: JSValue; res: out Table[A, B]): + Opt[void] = + if not JS_IsObject(val): + if not JS_IsException(val): + JS_ThrowTypeError(ctx, "object expected") + res = default(typeof(res)) + return err() + var ptab: ptr UncheckedArray[JSPropertyEnum] + var plen: uint32 + let flags = cint(JS_GPN_STRING_MASK) + if JS_GetOwnPropertyNames(ctx, addr ptab, addr plen, val, flags) == -1: + # exception + res = default(typeof(res)) + return err() + defer: + JS_FreePropertyEnum(ctx, ptab, plen) + var tmp = initTable[A, B]() + for i in 0 ..< plen: + let atom = ptab[i].atom + let k = JS_AtomToValue(ctx, atom) + defer: JS_FreeValue(ctx, k) + var kn: A + ?ctx.fromJS(k, kn) + let v = JS_GetProperty(ctx, val, atom) + defer: JS_FreeValue(ctx, v) + var vn: B + ?ctx.fromJS(v, vn) + tmp[kn] = move(vn) + res = move(tmp) + return ok() + +# Option vs Opt: +# Option is for nullable types, e.g. if you want to return either a string +# or null. (This is rather pointless for anything else.) +# Opt is for passing down exceptions received up in the chain. +# So e.g. none(T) translates to JS_NULL, but err() translates to JS_EXCEPTION. +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out Option[T]): Opt[void] = + if JS_IsNull(val): + res = none(T) + else: + var x: T + ?ctx.fromJS(val, x) + res = option(move(x)) + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out bool): Opt[void] = + let ret = JS_ToBool(ctx, val) + if ret == -1: # exception + res = false + return err() + res = ret != 0 + return ok() + +type IdentMapItem = tuple[s: string; n: int] + +func getIdentMap[T: enum](e: typedesc[T]): seq[IdentMapItem] = + result = @[] + for e in T.low .. T.high: + result.add(($e, int(e))) + result.sort(proc(x, y: IdentMapItem): int = cmp(x.s, y.s)) + +proc cmpItemOA(x: IdentMapItem; y: openArray[char]): int = + let xlen = x.s.len + let L = min(xlen, y.len) + if L > 0: + let n = cmpMem(unsafeAddr x.s[0], unsafeAddr y[0], L) + if n != 0: + return n + return xlen - y.len + +proc fromJSEnumBody(map: openArray[IdentMapItem]; ctx: JSContext; val: JSValue; + tname: cstring): int = + var plen {.noinit.}: csize_t + let s = JS_ToCStringLen(ctx, plen, val) + if s == nil: + return -1 + let i = map.binarySearch(s.toOpenArray(0, int(plen) - 1), cmpItemOA) + if i == -1: + JS_ThrowTypeError(ctx, "`%s' is not a valid value for enumeration %s", + s, tname) + return i + +proc fromJS*[T: enum](ctx: JSContext; val: JSValue; res: out T): Opt[void] = + const IdentMap = getIdentMap(T) + const tname = cstring($T) + if (let i = fromJSEnumBody(IdentMap, ctx, val, tname); i >= 0): + res = T(IdentMap[i].n) + return ok() + res = default(T) + return err() + +proc fromJS(ctx: JSContext; val: JSValue; t: cstring; res: out pointer): + Opt[void] = + if not JS_IsObject(val): + if not JS_IsException(val): + JS_ThrowTypeError(ctx, "value is not an object") + res = nil + return err() + let p = JS_GetOpaque(val, JS_GetClassID(val)) + if p == nil or not ctx.isInstanceOf(val, t): + JS_ThrowTypeError(ctx, "%s expected", t) + res = nil + return err() + res = p + return ok() + +proc fromJS*[T](ctx: JSContext; val: JSValue; res: out ptr T): Opt[void] = + var x: pointer + const tname = cstring($T) + ?ctx.fromJS(val, tname, x) + res = cast[ptr T](x) + return ok() + +proc fromJS*[T: ref object](ctx: JSContext; val: JSValue; res: out T): + Opt[void] = + var x: pointer + const tname = cstring($T) + ?ctx.fromJS(val, tname, x) + res = cast[T](x) + return ok() + +proc fromJSThis*[T: ptr object](ctx: JSContext; val: JSValue; res: out T): + Opt[void] = + {.warning[ProveInit]:off.}: + return ctx.fromJS(val, res) + +proc fromJSThis*[T: ref object](ctx: JSContext; val: JSValue; res: out T): + Opt[void] = + # translate undefined -> global + {.warning[ProveInit]:off.}: + if JS_IsUndefined(val): + return ctx.fromJS(ctx.getOpaque().global, res) + return ctx.fromJS(val, res) + +macro fromJSDictBody(ctx: JSContext; val: JSValue; res, t: typed) = + let impl = t.getTypeInst()[1].getImpl() + let convertStmts = newStmtList() + let success = ident("success") + var isOptional = true + var identDefsStack = @[impl[2]] + let jsDictType = JSDict.getType() + var undefInit = newNimNode(nnkObjConstr).add(t) + while identDefsStack.len > 0: + let def = identDefsStack.pop() + case def.kind + of nnkRecList, nnkObjectTy: + for child in def.children: + if child.kind != nnkEmpty: + identDefsStack.add(child) + of nnkOfInherit: + let other = def[0].getType() + if not other.sameType(jsDictType) and not jsDictType.sameType(other): + identDefsStack.add(other.getTypeInst().getImpl()[2][2]) + else: + assert def.kind == nnkIdentDefs + var fallback: NimNode = nil + var name = def[0] + if name.kind == nnkPragmaExpr: + for varPragma in name[1]: + if varPragma.kind == nnkExprColonExpr: + if varPragma[0].strVal == "jsdefault": + fallback = varPragma[1] + elif varPragma.kind == nnkSym: + if varPragma.strVal == "jsdefault": + let typ = def[1] + fallback = quote do: `typ`.default + name = name[0] + if name.kind == nnkPostfix: + # This is a public field. We are skipping the postfix * + name = name[1] + if $name == "toFree": + continue + if fallback == nil: + isOptional = false + elif isOptional: + undefInit.add(name.newColonExpr(fallback)) + var it = newStmtList() + let nameStr = newStrLitNode($name) + it.add(quote do: + let prop {.inject.} = JS_GetPropertyStr(`ctx`, `val`, `nameStr`) + ) + let missingStmt = if fallback == nil: + quote do: + missing = `nameStr` + break `success` + else: + quote do: + `res`.`name` = `fallback` + it.add(quote do: + if not JS_IsUndefined(prop): + res.toFree.vals.add(prop) + ?`ctx`.fromJS(prop, `res`.`name`) + else: + `missingStmt` + ) + convertStmts.add(newBlockStmt(it)) + let undefCheck = if isOptional: + quote do: + if JS_IsUndefined(val) or JS_IsNull(val): + res = `undefInit` + return ok() + else: + newStmtList() + result = quote do: + `undefCheck` + if not JS_IsObject(val): + if not JS_IsException(val): + JS_ThrowTypeError(ctx, "dictionary is not an object") + return err() + # Note: following in-place construction is an optimization documented in the + # manual. + res = T(toFree: JSDictToFreeAux(ctx: ctx)) + var missing {.inject.}: cstring = nil + block `success`: + `convertStmts` + if missing != nil: + JS_ThrowTypeError(ctx, "missing field %s", missing) + return err() + return ok() + +# For some reason, the compiler can't deal with this. +proc fromJS*[T: JSDict](ctx: JSContext; val: JSValue; res: var T): Opt[void] = + fromJSDictBody(ctx, val, res, T) + +proc fromJS*(ctx: JSContext; val: JSValue; res: out JSArrayBuffer): Opt[void] = + var len {.noinit.}: csize_t + let p = JS_GetArrayBuffer(ctx, len, val) + if p == nil: + res = JSArrayBuffer.default + return err() + res = JSArrayBuffer(len: len, p: cast[ptr UncheckedArray[uint8]](p)) + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out JSArrayBufferView): + Opt[void] = + var offset {.noinit.}: csize_t + var nmemb {.noinit.}: csize_t + var nsize {.noinit.}: csize_t + let jsbuf = JS_GetTypedArrayBuffer(ctx, val, offset, nmemb, nsize) + var abuf: JSArrayBuffer + # if jsbuf is exception, then GetArrayBuffer fails too (wrong tag) + ?ctx.fromJS(jsbuf, abuf) + res = JSArrayBufferView( + abuf: abuf, + offset: offset, + nmemb: nmemb, + nsize: nsize, + t: JS_GetTypedArrayType(val) + ) + return ok() + +proc fromJS*(ctx: JSContext; val: JSValue; res: out JSValue): Opt[void] = + res = val + return ok() + +const JS_ATOM_TAG_INT = 1u32 shl 31 + +func JS_IsNumber*(v: JSAtom): JS_BOOL = + return (uint32(v) and JS_ATOM_TAG_INT) != 0 + +proc fromJS*(ctx: JSContext; atom: JSAtom; res: out JSAtom): Opt[void] = + res = atom + return ok() + +proc fromJS*(ctx: JSContext; atom: JSAtom; res: out uint32): Opt[void] = + if JS_IsNumber(atom): + res = uint32(atom) and (not JS_ATOM_TAG_INT) + return ok() + res = 0 + return err() + +proc fromJS*(ctx: JSContext; atom: JSAtom; res: out string): Opt[void] = + let cs = JS_AtomToCString(ctx, atom) + if cs == nil: + res = "" + return err() + res = $cs + JS_FreeCString(ctx, cs) + return ok() diff --git a/lib/monoucha0/monoucha/javascript.nim b/lib/monoucha0/monoucha/javascript.nim new file mode 100644 index 00000000..856f15d8 --- /dev/null +++ b/lib/monoucha0/monoucha/javascript.nim @@ -0,0 +1,1590 @@ +## JavaScript binding generator. Horrifying, I know. But it works! +## Pragmas: +## {.jsctor.} for constructors. These need no `this' value, and are +## bound as regular constructors in JS. They must return a ref object, +## which will have a JS counterpart too. (Other functions can return +## ref objects too, which will either use the existing JS counterpart, +## if exists, or create a new one. In other words: cross-language +## reference semantics work seamlessly.) +## {.jsfunc.} is used for binding normal functions. Needs a `this' +## value, as all following pragmas. Generics are not supported, but +## JSValue does. +## By default, the Nim function name is bound; if this is not desired, +## you can rename the function like this: {.jsfunc: "preferredName".} +## This also works for all other pragmas that define named functions +## in JS. +## {.jsstfunc.} binds static functions. Unlike .jsfunc, it does not +## have a `this' value. A class name must be specified, +## e.g. {.jsstfunc: "URL".} to define on the URL class. To +## rename a static function, use the syntax "ClassName:funcName", +## e.g. "Response:error". +## {.jsget.}, {.jsfget.} must be specified on object fields; these +## generate regular getter & setter functions. +## {.jsufget, jsuffget, jsuffunc.} For fields with the +## [LegacyUnforgeable] WebIDL property. +## This makes it so a non-configurable/writable, but enumerable +## property is defined on the object when the *constructor* is called +## (i.e. NOT on the prototype.) +## {.jsfget.} and {.jsfset.} for getters/setters. Note the `f'; bare +## jsget/jsset can only be used on object fields. (I initially wanted +## to use the same keyword, unfortunately that didn't work out.) +## {.jsgetownprop.} Called when GetOwnProperty would return nothing. The +## key must be either a JSAtom, uint32 or string. (Note that the +## string option copies.) +## {.jsgetprop.} for property getters. Called on GetProperty. +## (In fact, this can be emulated using get_own_property, but this +## might still be faster.) +## {.jssetprop.} for property setters. Called on SetProperty - in fact +## this is the set() method of Proxy, except it always returns +## true. Same rules as jsgetprop for keys. +## {.jsdelprop.} for property deletion. It is like the deleteProperty +## method of Proxy. Must return true if deleted, false if not deleted. +## {.jshasprop.} for overriding has_property. Must return a boolean, +## or the integer 1 for true, 0 for false, or -1 for exception. +## {.jspropnames.} overrides get_own_property_names. Must return a +## JSPropertyEnumList object. + +{.push raises: [].} + +import std/macros +import std/options +import std/sets +import std/strutils +import std/tables + +import fromjs +import jserror +import jsopaque +import optshim +import quickjs +import tojs + +export options + +export + JS_NULL, JS_UNDEFINED, JS_FALSE, JS_TRUE, JS_EXCEPTION, JS_UNINITIALIZED, + JS_EVAL_TYPE_GLOBAL, + JS_EVAL_TYPE_MODULE, + JS_EVAL_TYPE_DIRECT, + JS_EVAL_TYPE_INDIRECT, + JS_EVAL_TYPE_MASK, + JS_EVAL_FLAG_SHEBANG, + JS_EVAL_FLAG_STRICT, + JS_EVAL_FLAG_COMPILE_ONLY, + JSRuntime, JSContext, JSValue, JSClassID, JSAtom, + JS_GetGlobalObject, JS_FreeValue, JS_IsException, JS_GetPropertyStr, + JS_IsFunction, JS_NewCFunctionData, JS_Call, JS_DupValue, JS_IsUndefined, + JS_ThrowTypeError, JS_ThrowRangeError, JS_ThrowSyntaxError, + JS_ThrowInternalError, JS_ThrowReferenceError + +when sizeof(int) < sizeof(int64): + export quickjs.`==` + +type + JSFunctionList = openArray[JSCFunctionListEntry] + + BoundFunction = object + t: BoundFunctionType + name: string + id: NimNode + magic: uint16 + unforgeable: bool + isstatic: bool + ctorBody: NimNode + + BoundFunctionType = enum + bfFunction = "js_func" + bfConstructor = "js_ctor" + bfGetter = "js_get" + bfSetter = "js_set" + bfPropertyGetOwn = "js_prop_get_own" + bfPropertyGet = "js_prop_get" + bfPropertySet = "js_prop_set" + bfPropertyDel = "js_prop_del" + bfPropertyHas = "js_prop_has" + bfPropertyNames = "js_prop_names" + bfFinalizer = "js_fin" + +var runtimes {.threadvar.}: seq[JSRuntime] + +proc bindCalloc(s: pointer; count, size: csize_t): pointer {.cdecl.} = + let n = count * size + if n > size: + return nil + return alloc0(count * size) + +proc bindMalloc(s: pointer; size: csize_t): pointer {.cdecl.} = + return alloc(size) + +proc bindFree(s: pointer; p: pointer) {.cdecl.} = + if p != nil: + dealloc(p) + +proc bindRealloc(s: pointer; p: pointer; size: csize_t): pointer {.cdecl.} = + return realloc(p, size) + +proc jsRuntimeCleanUp(rt: JSRuntime) {.cdecl.} = + let rtOpaque = rt.getOpaque() + GC_unref(rtOpaque) + # For refc: ensure there are no ghost Nim objects holding onto JS + # values. + try: + GC_fullCollect() + except Exception: + quit(1) + JS_RunGC(rt) + assert rtOpaque.destroying == nil + var np = 0 + for p in rtOpaque.plist.values: + rtOpaque.tmplist[np] = p + inc np + rtOpaque.plist.clear() + var nu = 0 + for p in rtOpaque.parentMap.values: + rtOpaque.tmpunrefs[nu] = p + inc nu + rtOpaque.parentMap.clear() + for i in 0 ..< nu: + GC_unref(cast[RootRef](rtOpaque.tmpunrefs[i])) + for i in 0 ..< np: + let p = rtOpaque.tmplist[i] + #TODO maybe finalize? + let val = JS_MKPTR(JS_TAG_OBJECT, p) + let classid = JS_GetClassID(val) + rtOpaque.fins.withValue(classid, fin): + fin[](rt, val) + JS_SetOpaque(val, nil) + JS_FreeValueRT(rt, val) + # GC will run again now + +proc newJSRuntime*(): JSRuntime = + ## Instantiate a Monoucha `JSRuntime`. + var mf {.global.} = JSMallocFunctions( + js_calloc: bindCalloc, + js_malloc: bindMalloc, + js_free: bindFree, + js_realloc: bindRealloc, + js_malloc_usable_size: nil + ) + let rt = JS_NewRuntime2(addr mf, nil) + let opaque = JSRuntimeOpaque() + GC_ref(opaque) + JS_SetRuntimeOpaque(rt, cast[pointer](opaque)) + JS_SetRuntimeCleanUpFunc(rt, jsRuntimeCleanUp) + # Must be added after opaque is set, or there is a chance of + # nim_finalize_for_js dereferencing it (at the new call). + runtimes.add(rt) + return rt + +proc newJSContext*(rt: JSRuntime): JSContext = + ## Instantiate a Monoucha `JSContext`. + ## It is only valid to call Monoucha procedures on contexts initialized with + ## `newJSContext`, as it does extra initialization over `JS_NewContext`. + let ctx = JS_NewContext(rt) + let opaque = newJSContextOpaque(ctx) + GC_ref(opaque) + JS_SetContextOpaque(ctx, cast[pointer](opaque)) + return ctx + +func getClass*(ctx: JSContext; class: cstring): JSClassID = + ## Get the class ID of the registered class `class'. + ## Note: this uses the Nim type's name, **not** the JS type's name. + try: + return ctx.getOpaque().creg[class] + except KeyError: + raise newException(Defect, "Class does not exist") + +func hasClass*(ctx: JSContext; class: cstring): bool = + ## Check if `class' is registered. + ## Note: this uses the Nim type's name, **not** the JS type's name. + return class in ctx.getOpaque().creg + +proc free*(ctx: JSContext) = + ## Free the JSContext and associated resources. + ## Note: this is not an alias of `JS_FreeContext`; `free` also frees various + ## JSValues stored on context startup by `newJSContext`. + var opaque = ctx.getOpaque() + if opaque != nil: + for a in opaque.symRefs: + JS_FreeAtom(ctx, a) + for a in opaque.strRefs: + JS_FreeAtom(ctx, a) + for v in opaque.valRefs: + JS_FreeValue(ctx, v) + for classid, v in opaque.ctors: + JS_FreeValue(ctx, v) + for v in opaque.errCtorRefs: + JS_FreeValue(ctx, v) + if opaque.globalUnref != nil: + opaque.globalUnref() + JS_FreeValue(ctx, opaque.global) + GC_unref(opaque) + JS_FreeContext(ctx) + +proc free*(rt: JSRuntime) = + ## Free the `JSRuntime` rt and remove it from the global JSRuntime pool. + # + # We must prepare space for opaque refs & pointers here, so that we + # can avoid allocations during cleanup. Otherwise we risk triggering a + # GC cycle and that would break cleanup too... + # + # (But we must *not* collect them yet; wait until the cycles are + # collected once.) + let rtOpaque = rt.getOpaque() + rtOpaque.tmplist.setLen(rtOpaque.plist.len) + rtOpaque.tmpunrefs.setLen(rtOpaque.parentMap.len) + JS_FreeRuntime(rt) + runtimes.del(runtimes.find(rt)) + +proc setGlobal*[T](ctx: JSContext; obj: T) = + ## Set the global variable to the reference `obj`. + ## Note: you must call `ctx.registerType(T, asglobal = true)` for this to + ## work, `T` being the type of `obj`. + # Add JSValue reference. + let ctxOpaque = ctx.getOpaque() + let opaque = cast[pointer](obj) + ctx.setOpaque(ctxOpaque.global, opaque) + GC_ref(obj) + let rtOpaque = JS_GetRuntime(ctx).getOpaque() + ctx.getOpaque().globalUnref = proc() = + GC_unref(obj) + rtOpaque.plist.del(opaque) + +proc getExceptionMsg*(ctx: JSContext): string = + result = "" + let ex = JS_GetException(ctx) + if fromJS(ctx, ex, result).isSome: + result &= '\n' + let stack = JS_GetPropertyStr(ctx, ex, cstring("stack")); + var s: string + if not JS_IsUndefined(stack) and ctx.fromJS(stack, s).isSome: + result &= s + JS_FreeValue(ctx, stack) + JS_FreeValue(ctx, ex) + +# Returns early with err(JSContext) if an exception was thrown in a +# context. +proc runJSJobs*(rt: JSRuntime): Result[void, JSContext] = + while JS_IsJobPending(rt): + var ctx: JSContext + let r = JS_ExecutePendingJob(rt, ctx) + if r == -1: + return err(ctx) + ok() + +# Add all LegacyUnforgeable functions defined on the prototype chain to +# the opaque. +# Since every prototype has a list of all its ancestor's LegacyUnforgeable +# functions, it is sufficient to simply merge the new list of new classes +# with their parent's list to achieve this. +proc addClassUnforgeable(ctx: JSContext; proto: JSValue; + classid, parent: JSClassID; ourUnforgeable: JSFunctionList) = + let ctxOpaque = ctx.getOpaque() + var merged = @ourUnforgeable + if int(parent) < ctxOpaque.unforgeable.len: + merged.add(ctxOpaque.unforgeable[int(parent)]) + if merged.len > 0: + if int(classid) >= ctxOpaque.unforgeable.len: + ctxOpaque.unforgeable.setLen(int(classid) + 1) + ctxOpaque.unforgeable[int(classid)] = move(merged) + let ufp0 = addr ctxOpaque.unforgeable[int(classid)][0] + let ufp = cast[ptr UncheckedArray[JSCFunctionListEntry]](ufp0) + JS_SetPropertyFunctionList(ctx, proto, ufp, cint(merged.len)) + +proc newProtoFromParentClass(ctx: JSContext; parent: JSClassID): JSValue = + if parent != 0: + let parentProto = JS_GetClassProto(ctx, parent) + let proto = JS_NewObjectProtoClass(ctx, parentProto, parent) + JS_FreeValue(ctx, parentProto) + return proto + return JS_NewObject(ctx) + +func newJSClass*(ctx: JSContext; cdef: JSClassDefConst; tname: cstring; + nimt: pointer; ctor: JSCFunction; funcs: JSFunctionList; parent: JSClassID; + asglobal: bool; nointerface: bool; finalizer: JSFinalizerFunction; + namespace: JSValue; errid: Opt[JSErrorEnum]; + unforgeable, staticfuns: JSFunctionList; ishtmldda: bool): JSClassID + {.discardable.} = + result = 0 + let rt = JS_GetRuntime(ctx) + discard JS_NewClassID(rt, result) + var ctxOpaque = ctx.getOpaque() + var rtOpaque = rt.getOpaque() + if JS_NewClass(rt, result, cdef) != 0: + raise newException(Defect, "Failed to allocate JS class: " & + $cdef.class_name) + ctxOpaque.typemap[nimt] = result + ctxOpaque.creg[tname] = result + if ctxOpaque.parents.len <= int(result): + ctxOpaque.parents.setLen(int(result) + 1) + ctxOpaque.parents[result] = parent + if ishtmldda: + ctxOpaque.htmldda = result + if finalizer != nil: + rtOpaque.fins[result] = finalizer + let proto = ctx.newProtoFromParentClass(parent) + if funcs.len > 0: + # We avoid funcs being GC'ed by putting the list in rtOpaque. + # (QuickJS uses the pointer later.) + #TODO maybe put them in ctxOpaque instead? + rtOpaque.flist.add(@funcs) + let fp0 = addr rtOpaque.flist[^1][0] + let fp = cast[ptr UncheckedArray[JSCFunctionListEntry]](fp0) + JS_SetPropertyFunctionList(ctx, proto, fp, cint(funcs.len)) + #TODO check if this is an indexed property getter + if cdef.exotic != nil and cdef.exotic.get_own_property != nil: + let val = JS_DupValue(ctx, ctxOpaque.valRefs[jsvArrayPrototypeValues]) + let itSym = ctxOpaque.symRefs[jsyIterator] + ctx.defineProperty(proto, itSym, val) + let news = JS_NewAtomString(ctx, cdef.class_name) + doAssert not JS_IsException(news) + ctx.definePropertyC(proto, ctxOpaque.symRefs[jsyToStringTag], + JS_DupValue(ctx, news)) + JS_SetClassProto(ctx, result, proto) + ctx.addClassUnforgeable(proto, result, parent, unforgeable) + if asglobal: + let global = ctxOpaque.global + assert ctxOpaque.gclass == 0 + ctxOpaque.gclass = result + ctx.definePropertyC(global, ctxOpaque.symRefs[jsyToStringTag], + JS_DupValue(ctx, news)) + if JS_SetPrototype(ctx, global, proto) != 1: + raise newException(Defect, "Failed to set global prototype: " & + $cdef.class_name) + # Global already exists, so set unforgeable functions here + if int(result) < ctxOpaque.unforgeable.len and + ctxOpaque.unforgeable[int(result)].len > 0: + let ufp0 = addr ctxOpaque.unforgeable[int(result)][0] + let ufp = cast[ptr UncheckedArray[JSCFunctionListEntry]](ufp0) + JS_SetPropertyFunctionList(ctx, global, ufp, + cint(ctxOpaque.unforgeable[int(result)].len)) + JS_FreeValue(ctx, news) + let jctor = JS_NewCFunction2(ctx, ctor, cstring($cdef.class_name), 0, + JS_CFUNC_constructor, 0) + if staticfuns.len > 0: + rtOpaque.flist.add(@staticfuns) + let fp0 = addr rtOpaque.flist[^1][0] + let fp = cast[ptr UncheckedArray[JSCFunctionListEntry]](fp0) + JS_SetPropertyFunctionList(ctx, jctor, fp, cint(staticfuns.len)) + JS_SetConstructor(ctx, jctor, proto) + if errid.isSome: + ctx.getOpaque().errCtorRefs[errid.get] = JS_DupValue(ctx, jctor) + while ctxOpaque.ctors.len <= int(result): + ctxOpaque.ctors.add(JS_UNDEFINED) + ctxOpaque.ctors[result] = JS_DupValue(ctx, jctor) + if not nointerface: + if JS_IsNull(namespace): + ctx.definePropertyCW(ctxOpaque.global, $cdef.class_name, jctor) + else: + ctx.definePropertyCW(namespace, $cdef.class_name, jctor) + else: + JS_FreeValue(ctx, jctor) + +type FuncParam = tuple + name: string + t: NimNode + val: Option[NimNode] + generic: Option[NimNode] + isptr: bool + +func getMinArgs(params: seq[FuncParam]): int = + for i, it in params: + if it[2].isSome: + return i + let t = it.t + if t.kind == nnkBracketExpr: + if t.typeKind == varargs.getType().typeKind: + assert i == params.high, "Not even nim can properly handle this..." + return i + return params.len + +type + JSFuncGenerator = ref object + t: BoundFunctionType + hasThis: bool + funcName: string + funcParams: seq[FuncParam] + passCtx: bool + thisType: string + thisTypeNode: NimNode + returnType: Option[NimNode] + newName: NimNode + newBranchList: seq[NimNode] + errval: NimNode # JS_EXCEPTION or -1 + # die: didn't match parameters, but could still match other ones + dielabel: NimNode + jsFunCallLists: seq[NimNode] + jsFunCallList: NimNode + jsFunCall: NimNode + jsCallAndRet: NimNode + minArgs: int + actualMinArgs: int # minArgs without JSContext + i: int # nim parameters accounted for + j: int # js parameters accounted for (not including fix ones, e.g. `this') + unforgeable: bool + isstatic: bool + +var BoundFunctions {.compileTime.}: Table[string, seq[BoundFunction]] + +proc getParams(fun: NimNode): seq[FuncParam] = + let formalParams = fun.findChild(it.kind == nnkFormalParams) + var funcParams: seq[FuncParam] = @[] + var returnType = none(NimNode) + if formalParams[0].kind != nnkEmpty: + returnType = some(formalParams[0]) + for i in 1 ..< fun.params.len: + let it = formalParams[i] + let tt = it[^2] + var t: NimNode + if it[^2].kind != nnkEmpty: + t = `tt` + elif it[^1].kind != nnkEmpty: + let x = it[^1] + t = quote do: + typeof(`x`) + else: + error("?? " & treeRepr(it)) + let isptr = t.kind == nnkVarTy + if t.kind == nnkRefTy: + t = t[0] + elif t.kind == nnkVarTy: + t = newNimNode(nnkPtrTy).add(t[0]) + let val = if it[^1].kind != nnkEmpty: + let x = it[^1] + some(newPar(x)) + else: + none(NimNode) + var g = none(NimNode) + for i in 0 ..< it.len - 2: + let name = $it[i] + funcParams.add((name, t, val, g, isptr)) + funcParams + +proc getReturn(fun: NimNode): Option[NimNode] = + let formalParams = fun.findChild(it.kind == nnkFormalParams) + if formalParams[0].kind != nnkEmpty: + some(formalParams[0]) + else: + none(NimNode) + +template getJSParams(): untyped = + [ + (quote do: JSValue), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("argc"), quote do: cint), + newIdentDefs(ident("argv"), quote do: ptr UncheckedArray[JSValue]) + ] + +template getJSGetterParams(): untyped = + [ + (quote do: JSValue), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + ] + +template getJSGetOwnPropParams(): untyped = + [ + (quote do: cint), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("desc"), quote do: ptr JSPropertyDescriptor), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("prop"), quote do: JSAtom), + ] + +template getJSGetPropParams(): untyped = + [ + (quote do: JSValue), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("prop"), quote do: JSAtom), + newIdentDefs(ident("receiver"), quote do: JSValue), + ] + +template getJSSetPropParams(): untyped = + [ + (quote do: cint), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("atom"), quote do: JSAtom), + newIdentDefs(ident("value"), quote do: JSValue), + newIdentDefs(ident("receiver"), quote do: JSValue), + newIdentDefs(ident("flags"), quote do: cint), + ] + +template getJSDelPropParams(): untyped = + [ + (quote do: cint), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("prop"), quote do: JSAtom), + ] + +template getJSHasPropParams(): untyped = + [ + (quote do: cint), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("atom"), quote do: JSAtom), + ] + + +template getJSSetterParams(): untyped = + [ + (quote do: JSValue), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("this"), quote do: JSValue), + newIdentDefs(ident("val"), quote do: JSValue), + ] + +template getJSPropNamesParams(): untyped = + [ + (quote do: cint), + newIdentDefs(ident("ctx"), quote do: JSContext), + newIdentDefs(ident("ptab"), quote do: ptr JSPropertyEnumArray), + newIdentDefs(ident("plen"), quote do: ptr uint32), + newIdentDefs(ident("this"), quote do: JSValue) + ] + +template fromJS_or_die*(ctx, val, res, dl: untyped) = + if ctx.fromJS(val, res).isNone: + break dl + +proc addParam2(gen: var JSFuncGenerator; s, t, val: NimNode; + fallback: NimNode = nil) = + let dl = gen.dielabel + if fallback == nil: + for list in gen.jsFunCallLists.mitems: + list.add(quote do: + var `s`: `t` + fromJS_or_die(ctx, `val`, `s`, `dl`) + ) + else: + let j = gen.j + for list in gen.jsFunCallLists.mitems: + list.add(quote do: + var `s`: `t` + if `j` < argc and not JS_IsUndefined(argv[`j`]): + fromJS_or_die(ctx, `val`, `s`, `dl`) + else: + `s` = `fallback` + ) + +proc addValueParam(gen: var JSFuncGenerator; s, t: NimNode; + fallback: NimNode = nil) = + let j = gen.j + gen.addParam2(s, t, quote do: argv[`j`], fallback) + +proc addThisParam(gen: var JSFuncGenerator; thisName = "this") = + var s = ident("arg_" & $gen.i) + let t = gen.funcParams[gen.i].t + let id = ident(thisName) + let tt = gen.thisType + let fn = gen.funcName + let ev = gen.errval + for list in gen.jsFunCallLists.mitems: + list.add(quote do: + var `s`: `t` + if ctx.fromJSThis(`id`, `s`).isNone: + discard JS_ThrowTypeError(ctx, + "'%s' called on an object that is not an instance of %s", `fn`, `tt`) + return `ev` + ) + if gen.funcParams[gen.i].isptr: + s = quote do: `s`[] + gen.jsFunCall.add(s) + inc gen.i + +proc addFixParam(gen: var JSFuncGenerator; name: string) = + var s = ident("arg_" & $gen.i) + let t = gen.funcParams[gen.i].t + let id = ident(name) + if t.typeKind == ntyGenericParam: + error("Union parameters are no longer supported. Use JSValue instead.") + gen.addParam2(s, t, id) + if gen.funcParams[gen.i].isptr: + s = quote do: `s`[] + gen.jsFunCall.add(s) + inc gen.i + +proc addRequiredParams(gen: var JSFuncGenerator) = + while gen.i < gen.minArgs: + var s = ident("arg_" & $gen.i) + let tt = gen.funcParams[gen.i].t + if tt.typeKind == ntyGenericParam: + error("Union parameters are no longer supported. Use JSValue instead.") + gen.addValueParam(s, tt) + if gen.funcParams[gen.i].isptr: + s = quote do: `s`[] + gen.jsFunCall.add(s) + inc gen.j + inc gen.i + +proc addOptionalParams(gen: var JSFuncGenerator) = + while gen.i < gen.funcParams.len: + let j = gen.j + var s = ident("arg_" & $gen.i) + let tt = gen.funcParams[gen.i].t + if tt.typeKind == varargs.getType().typeKind: # pray it's not a generic... + let vt = tt[1] + if vt.sameType(JSValue.getType()) or JSValue.getType().sameType(vt): + s = quote do: + argv.toOpenArray(`j`, argc - 1) + else: + error("Only JSValue varargs are supported") + else: + if gen.funcParams[gen.i][2].isNone: + error("No fallback value. Maybe a non-optional parameter follows an " & + "optional parameter?") + let fallback = gen.funcParams[gen.i][2].get + if tt.typeKind == ntyGenericParam: + error("Union parameters are no longer supported. Use JSValue instead.") + gen.addValueParam(s, tt, fallback) + if gen.funcParams[gen.i].isptr: + s = quote do: `s`[] + gen.jsFunCall.add(s) + inc gen.j + inc gen.i + +proc finishFunCallList(gen: var JSFuncGenerator) = + for branch in gen.jsFunCallLists: + branch.add(gen.jsFunCall) + +var jsDtors {.compileTime.}: HashSet[string] + +proc registerFunction(typ: string; nf: BoundFunction) = + BoundFunctions.withValue(typ, val): + val[].add(nf) + do: + BoundFunctions[typ] = @[nf] + +proc registerFunction(typ: string; t: BoundFunctionType; name: string; + id: NimNode; magic: uint16 = 0; uf = false; isstatic = false; + ctorBody: NimNode = nil) = + registerFunction(typ, BoundFunction( + t: t, + name: name, + id: id, + magic: magic, + unforgeable: uf, + isstatic: isstatic, + ctorBody: ctorBody + )) + +proc registerConstructor(gen: JSFuncGenerator; jsProc: NimNode) = + registerFunction(gen.thisType, gen.t, gen.funcName, gen.newName, + uf = gen.unforgeable, isstatic = gen.isstatic, ctorBody = jsProc) + +proc registerFunction(gen: JSFuncGenerator) = + registerFunction(gen.thisType, gen.t, gen.funcName, gen.newName, + uf = gen.unforgeable, isstatic = gen.isstatic) + +proc newJSProcBody(gen: var JSFuncGenerator; isva: bool): NimNode = + let ma = gen.actualMinArgs + result = newStmtList() + if isva and ma > 0: + result.add(quote do: + if argc < `ma`: + return JS_ThrowTypeError(ctx, + "At least %d arguments required, but only %d passed", cint(`ma`), + cint(argc)) + ) + result.add(gen.jsCallAndRet) + +proc newJSProc(gen: var JSFuncGenerator; params: openArray[NimNode]; + isva = true): NimNode = + let jsBody = gen.newJSProcBody(isva) + let jsPragmas = newNimNode(nnkPragma).add(ident("cdecl")) + return newProc(gen.newName, params, jsBody, pragmas = jsPragmas) + +func getFuncName(fun: NimNode; jsname, staticName: string): string = + if jsname != "": + return jsname + if staticName != "": + let i = staticName.find('.') + if i != -1: + return staticName.substr(i + 1) + return $fun[0] + +func getErrVal(t: BoundFunctionType): NimNode = + if t in {bfPropertyGetOwn, bfPropertySet, bfPropertyDel, bfPropertyHas, + bfPropertyNames}: + return quote do: cint(-1) + return quote do: JS_EXCEPTION + +proc addJSContext(gen: var JSFuncGenerator) = + if gen.funcParams.len > gen.i: + if gen.funcParams[gen.i].t.eqIdent(ident("JSContext")): + gen.passCtx = true + gen.jsFunCall.add(ident("ctx")) + inc gen.i + elif gen.funcParams[gen.i].t.eqIdent(ident("JSRuntime")): + inc gen.i # special case for finalizers that have a JSRuntime param + +proc addThisName(gen: var JSFuncGenerator; hasThis: bool) = + if hasThis: + var t = gen.funcParams[gen.i].t + if t.kind == nnkPtrTy: + t = t[0] + gen.thisTypeNode = t + gen.thisType = $t + gen.newName = ident($gen.t & "_" & gen.thisType & "_" & gen.funcName) + else: + let rt = gen.returnType.get + if rt.kind in {nnkRefTy, nnkPtrTy}: + gen.thisTypeNode = rt[0] + gen.thisType = rt[0].strVal + else: + if rt.kind == nnkBracketExpr: + gen.thisTypeNode = rt[1] + gen.thisType = rt[1].strVal + else: + gen.thisTypeNode = rt + gen.thisType = rt.strVal + gen.newName = ident($gen.t & "_" & gen.funcName) + +func getActualMinArgs(gen: var JSFuncGenerator): int = + var ma = gen.minArgs + if gen.hasThis and not gen.isstatic: + dec ma + if gen.passCtx: + dec ma + assert ma >= 0 + return ma + +proc initGenerator(fun: NimNode; t: BoundFunctionType; hasThis: bool; + jsname = ""; unforgeable = false; staticName = ""): JSFuncGenerator = + let jsFunCallList = newStmtList() + let funcParams = getParams(fun) + var gen = JSFuncGenerator( + t: t, + funcName: getFuncName(fun, jsname, staticName), + funcParams: funcParams, + returnType: getReturn(fun), + minArgs: funcParams.getMinArgs(), + hasThis: hasThis, + errval: getErrVal(t), + dielabel: ident("ondie"), + jsFunCallList: jsFunCallList, + jsFunCallLists: @[jsFunCallList], + jsFunCall: newCall(fun[0]), + unforgeable: unforgeable, + isstatic: staticName != "" + ) + gen.addJSContext() + gen.actualMinArgs = gen.getActualMinArgs() # must come after passctx is set + if staticName == "": + gen.addThisName(hasThis) + else: + gen.thisType = staticName + if (let i = gen.thisType.find('.'); i != -1): + gen.thisType.setLen(i) + gen.newName = ident($gen.t & "_" & gen.funcName) + return gen + +proc makeJSCallAndRet(gen: var JSFuncGenerator; okstmt, errstmt: NimNode) = + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = if gen.returnType.isSome: + quote do: + block `dl`: + return ctx.toJS(`jfcl`) + `errstmt` + else: + quote do: + block `dl`: + `jfcl` + `okstmt` + `errstmt` + +proc makeCtorJSCallAndRet(gen: var JSFuncGenerator; errstmt: NimNode) = + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = quote do: + block `dl`: + return ctx.toJSNew(`jfcl`, this) + `errstmt` + +macro jsctor*(fun: typed) = + var gen = initGenerator(fun, bfConstructor, hasThis = false) + gen.addRequiredParams() + gen.addOptionalParams() + gen.finishFunCallList() + let errstmt = quote do: + return JS_EXCEPTION + gen.makeCtorJSCallAndRet(errstmt) + let jsProc = gen.newJSProc(getJSParams()) + gen.registerConstructor(jsProc) + return fun + +macro jshasprop*(fun: typed) = + var gen = initGenerator(fun, bfPropertyHas, hasThis = true) + gen.addThisParam() + gen.addFixParam("atom") + gen.finishFunCallList() + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = quote do: + block `dl`: + let retv = `jfcl` + return cint(retv) + return cint(-1) + let jsProc = gen.newJSProc(getJSHasPropParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +macro jsgetownprop*(fun: typed) = + var gen = initGenerator(fun, bfPropertyGetOwn, hasThis = true) + gen.addThisParam() + gen.addFixParam("prop") + var handleRetv: NimNode = nil + if gen.i < gen.funcParams.len: + handleRetv = quote do: discard + gen.jsFunCall.add(ident("desc")) + else: + handleRetv = quote do: + if desc != nil: + # From quickjs.h: + # > If 1 is returned, the property descriptor 'desc' is filled + # > if != NULL. + # So desc may be nil. + desc[].setter = JS_UNDEFINED + desc[].getter = JS_UNDEFINED + desc[].value = retv + desc[].flags = 0 + gen.finishFunCallList() + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = quote do: + block `dl`: + if JS_GetOpaque(this, JS_GetClassID(this)) == nil: + return cint(0) + let retv {.inject.} = ctx.toJS(`jfcl`) + if JS_IsException(retv): + return cint(-1) + if JS_IsUninitialized(retv): + return cint(0) + `handleRetv` + return cint(1) + return cint(-1) + let jsProc = gen.newJSProc(getJSGetOwnPropParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +macro jsgetprop*(fun: typed) = + var gen = initGenerator(fun, bfPropertyGet, hasThis = true) + gen.addThisParam("receiver") + gen.addFixParam("prop") + if gen.i < gen.funcParams.len: + gen.addFixParam("this") + gen.finishFunCallList() + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = quote do: + block `dl`: + return ctx.toJS(`jfcl`) + return JS_EXCEPTION + let jsProc = gen.newJSProc(getJSGetPropParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +macro jssetprop*(fun: typed) = + var gen = initGenerator(fun, bfPropertySet, hasThis = true) + gen.addThisParam("receiver") + gen.addFixParam("atom") + gen.addFixParam("value") + if gen.i < gen.funcParams.len: + gen.addFixParam("this") + gen.finishFunCallList() + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = if gen.returnType.isSome: + quote do: + block `dl`: + let v = toJS(ctx, `jfcl`) + if not JS_IsException(v): + return cint(1) + if JS_IsUninitialized(v): + return cint(0) + return cint(-1) + else: + quote do: + block `dl`: + `jfcl` + return cint(1) + return cint(-1) + let jsProc = gen.newJSProc(getJSSetPropParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +macro jsdelprop*(fun: typed) = + var gen = initGenerator(fun, bfPropertyDel, hasThis = true) + gen.addThisParam() + gen.addFixParam("prop") + gen.finishFunCallList() + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = quote do: + block `dl`: + let retv = `jfcl` + return cint(retv) + return cint(-1) + let jsProc = gen.newJSProc(getJSDelPropParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +macro jspropnames*(fun: typed) = + var gen = initGenerator(fun, bfPropertyNames, hasThis = true) + gen.addThisParam() + gen.finishFunCallList() + let jfcl = gen.jsFunCallList + let dl = gen.dielabel + gen.jsCallAndRet = quote do: + block `dl`: + let retv = `jfcl` + ptab[] = retv.buffer + plen[] = retv.len + return cint(0) + return cint(-1) + let jsProc = gen.newJSProc(getJSPropNamesParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +macro jsfgetn(jsname: static string; uf: static bool; fun: typed) = + var gen = initGenerator(fun, bfGetter, hasThis = true, jsname = jsname, + unforgeable = uf) + if gen.actualMinArgs != 0 or gen.funcParams.len != gen.minArgs: + error("jsfget functions must only accept one parameter.") + if gen.returnType.isNone: + error("jsfget functions must have a return type.") + gen.addThisParam() + gen.finishFunCallList() + gen.makeJSCallAndRet(nil, quote do: discard) + let jsProc = gen.newJSProc(getJSGetterParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +# "Why?" So the compiler doesn't cry. +# Warning: make these typed and you will cry instead. +template jsfget*(fun: untyped) = + jsfgetn("", false, fun) + +template jsuffget*(fun: untyped) = + jsfgetn("", true, fun) + +template jsfget*(jsname, fun: untyped) = + jsfgetn(jsname, false, fun) + +template jsuffget*(jsname, fun: untyped) = + jsfgetn(jsname, true, fun) + +# Ideally we could simulate JS setters using nim setters, but nim setters +# won't accept types that don't match their reflected field's type. +macro jsfsetn(jsname: static string; fun: typed) = + var gen = initGenerator(fun, bfSetter, hasThis = true, jsname = jsname) + if gen.actualMinArgs != 1 or gen.funcParams.len != gen.minArgs: + error("jsfset functions must accept two parameters") + #TODO should check if result is JSResult[void] + gen.addThisParam() + gen.addFixParam("val") + gen.finishFunCallList() + # return param anyway + let okstmt = quote do: discard + let errstmt = quote do: return JS_DupValue(ctx, val) + gen.makeJSCallAndRet(okstmt, errstmt) + let jsProc = gen.newJSProc(getJSSetterParams(), false) + gen.registerFunction() + return newStmtList(fun, jsProc) + +template jsfset*(fun: untyped) = + jsfsetn("", fun) + +template jsfset*(jsname, fun: untyped) = + jsfsetn(jsname, fun) + +macro jsfuncn*(jsname: static string; uf: static bool; + staticName: static string; fun: typed) = + var gen = initGenerator(fun, bfFunction, hasThis = true, jsname = jsname, + unforgeable = uf, staticName = staticName) + if gen.minArgs == 0 and not gen.isstatic: + error("Zero-parameter functions are not supported. " & + "(Maybe pass Window or Client?)") + if not gen.isstatic: + gen.addThisParam() + gen.addRequiredParams() + gen.addOptionalParams() + gen.finishFunCallList() + let okstmt = quote do: + return JS_UNDEFINED + let errstmt = quote do: + return JS_EXCEPTION + gen.makeJSCallAndRet(okstmt, errstmt) + let jsProc = gen.newJSProc(getJSParams()) + gen.registerFunction() + return newStmtList(fun, jsProc) + +template jsfunc*(fun: untyped) = + jsfuncn("", false, "", fun) + +template jsuffunc*(fun: untyped) = + jsfuncn("", true, "", fun) + +template jsfunc*(jsname, fun: untyped) = + jsfuncn(jsname, false, "", fun) + +template jsuffunc*(jsname, fun: untyped) = + jsfuncn(jsname, true, "", fun) + +template jsstfunc*(name, fun: untyped) = + jsfuncn("", false, name, fun) + +macro jsfin*(fun: typed) = + var gen = initGenerator(fun, bfFinalizer, hasThis = true) + let finName = gen.newName + let finFun = ident(gen.funcName) + let t = gen.thisTypeNode + var finStmt: NimNode = nil # warning: won't compile on 2.0.4 with let + if gen.minArgs == 1: + finStmt = quote do: `finFun`(cast[`t`](opaque)) + elif gen.minArgs == 2: + finStmt = quote do: `finFun`(rt, cast[`t`](opaque)) + else: + error("Expected one or two parameters") + let jsProc = quote do: + proc `finName`(rt {.inject.}: JSRuntime; val: JSValue) = + let opaque {.inject.} = JS_GetOpaque(val, JS_GetClassID(val)) + if opaque != nil: + `finStmt` + gen.registerFunction() + return newStmtList(fun, jsProc) + +# Having the same names for these and the macros leads to weird bugs, so the +# macros get an additional f. +template jsget*() {.pragma.} +template jsget*(name: string) {.pragma.} +template jsset*() {.pragma.} +template jsset*(name: string) {.pragma.} +template jsgetset*() {.pragma.} +template jsgetset*(name: string) {.pragma.} +template jsufget*() {.pragma.} +template jsufget*(name: string) {.pragma.} + +proc js_illegal_ctor*(ctx: JSContext; this: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} = + return JS_ThrowTypeError(ctx, "Illegal constructor") + +type + JSObjectPragma = object + name: string + varsym: NimNode + unforgeable: bool + + JSObjectPragmas = object + jsget: seq[JSObjectPragma] + jsset: seq[JSObjectPragma] + jsinclude: seq[JSObjectPragma] + +func getPragmaName(varPragma: NimNode): string = + if varPragma.kind == nnkExprColonExpr: + return $varPragma[0] + return $varPragma + +func getStringFromPragma(varPragma: NimNode): Option[string] = + if varPragma.kind == nnkExprColonExpr: + if not varPragma.len == 1 and varPragma[1].kind == nnkStrLit: + error("Expected string as pragma argument") + return some($varPragma[1]) + return none(string) + +proc findPragmas(t: NimNode): JSObjectPragmas = + let typ = t.getTypeInst()[1] # The type, as declared. + var impl = typ.getTypeImpl() # ref t + if impl.kind in {nnkRefTy, nnkPtrTy}: + impl = impl[0].getImpl() + else: + impl = typ.getImpl() + # stolen from std's macros.customPragmaNode + var identDefsStack = newSeq[NimNode](impl[2].len) + for i, it in identDefsStack.mpairs: + it = impl[2][i] + var pragmas = JSObjectPragmas() + while identDefsStack.len > 0: + let identDefs = identDefsStack.pop() + case identDefs.kind + of nnkRecList: + for child in identDefs.children: + identDefsStack.add(child) + of nnkRecCase: + # Add condition definition + identDefsStack.add(identDefs[0]) + # Add branches + for i in 1 ..< identDefs.len: + identDefsStack.add(identDefs[i].last) + else: + for i in 0 ..< identDefs.len - 2: + let varNode = identDefs[i] + if varNode.kind == nnkPragmaExpr: + var varName = varNode[0] + if varName.kind == nnkPostfix: + # This is a public field. We are skipping the postfix * + varName = varName[1] + let varPragmas = varNode[1] + for varPragma in varPragmas: + let pragmaName = getPragmaName(varPragma) + var op = JSObjectPragma( + name: getStringFromPragma(varPragma).get($varName), + varsym: varName + ) + case pragmaName + of "jsget": pragmas.jsget.add(op) + of "jsset": pragmas.jsset.add(op) + of "jsufget": # LegacyUnforgeable + op.unforgeable = true + pragmas.jsget.add(op) + of "jsgetset": + pragmas.jsget.add(op) + pragmas.jsset.add(op) + of "jsinclude": pragmas.jsinclude.add(op) + return pragmas + +proc nim_finalize_for_js*(obj: pointer) = + for rt in runtimes: + let rtOpaque = rt.getOpaque() + rtOpaque.plist.withValue(obj, v): + let p = v[] + let val = JS_MKPTR(JS_TAG_OBJECT, p) + let classid = JS_GetClassID(val) + rtOpaque.fins.withValue(classid, fin): + fin[](rt, val) + JS_SetOpaque(val, nil) + rtOpaque.plist.del(obj) + if rtOpaque.destroying == obj: + # Allow QJS to collect the JSValue through checkDestroy. + rtOpaque.destroying = nil + else: + JS_FreeValueRT(rt, val) + +type + TabGetSet* = object + name*: string + get*: JSGetterMagicFunction + set*: JSSetterMagicFunction + magic*: int16 + + TabFunc* = object + name*: string + fun*: JSCFunction + +template jsDestructor*[U](T: typedesc[ref U]) = + static: + jsDtors.incl($T) + {.warning[Deprecated]:off.}: + proc `=destroy`(obj: var U) = + nim_finalize_for_js(addr obj) + +template jsDestructor*(T: typedesc[object]) = + static: + jsDtors.incl($T) + {.warning[Deprecated]:off.}: + proc `=destroy`(obj: var T) = + nim_finalize_for_js(addr obj) + +type RegistryInfo = object + t: NimNode # NimNode of type + name: string # JS name, if this is the empty string then it equals tname + tabList: NimNode # array of function table + ctorImpl: NimNode # definition & body of constructor + ctorFun: NimNode # constructor ident + getset: Table[string, (NimNode, NimNode, bool)] # name -> get, set, uf + propGetOwnFun: NimNode # custom own get function ident + propGetFun: NimNode # custom get function ident + propSetFun: NimNode # custom set function ident + propDelFun: NimNode # custom del function ident + propHasFun: NimNode # custom has function ident + propNamesFun: NimNode # custom property names function ident + finFun: NimNode # finalizer ident + finName: NimNode # finalizer wrapper ident + dfin: NimNode # CheckDestroy finalizer ident + classDef: NimNode # ClassDef ident + tabUnforgeable: NimNode # array of unforgeable function table + tabStatic: NimNode # array of static function table + +func tname(info: RegistryInfo): string = + return info.t.strVal + +# Differs from tname if the Nim object's name differs from the JS object's +# name. +func jsname(info: RegistryInfo): string = + if info.name != "": + return info.name + return info.tname + +proc newRegistryInfo(t: NimNode; name: string): RegistryInfo = + return RegistryInfo( + t: t, + name: name, + classDef: ident("classDef"), + tabList: newNimNode(nnkBracket), + tabUnforgeable: newNimNode(nnkBracket), + tabStatic: newNimNode(nnkBracket), + finName: newNilLit(), + finFun: newNilLit(), + propGetOwnFun: newNilLit(), + propGetFun: newNilLit(), + propSetFun: newNilLit(), + propDelFun: newNilLit(), + propHasFun: newNilLit(), + propNamesFun: newNilLit() + ) + +proc bindConstructor(stmts: NimNode; info: var RegistryInfo): NimNode = + if info.ctorFun != nil: + stmts.add(info.ctorImpl) + return info.ctorFun + return ident("js_illegal_ctor") + +proc registerGetters(stmts: NimNode; info: RegistryInfo; + jsget: seq[JSObjectPragma]) = + let t = info.t + let tname = info.tname + let jsname = info.jsname + for op in jsget: + let node = op.varsym + let fn = op.name + let id = ident($bfGetter & "_" & tname & "_" & fn) + stmts.add(quote do: + proc `id`(ctx: JSContext; this: JSValue): JSValue {.cdecl.} = + when `t` is object: + var arg_0: ptr `t` + else: + var arg_0: `t` + if ctx.fromJSThis(this, arg_0).isNone: + return JS_ThrowTypeError(ctx, + "'%s' called on an object that is not an instance of %s", `fn`, + `jsname`) + when typeof(arg_0.`node`) is object: + return toJSP(ctx, arg_0, arg_0.`node`) + else: + return toJS(ctx, arg_0.`node`) + ) + registerFunction(tname, BoundFunction( + t: bfGetter, + name: fn, + id: id, + unforgeable: op.unforgeable + )) + +proc registerSetters(stmts: NimNode; info: RegistryInfo; + jsset: seq[JSObjectPragma]) = + let t = info.t + let tname = info.tname + let jsname = info.jsname + for op in jsset: + let node = op.varsym + let fn = op.name + let id = ident($bfSetter & "_" & tname & "_" & fn) + stmts.add(quote do: + proc `id`(ctx: JSContext; this: JSValue; val: JSValue): JSValue + {.cdecl.} = + when `t` is object: + var arg_0: ptr `t` + else: + var arg_0: `t` + if ctx.fromJS(this, arg_0).isNone: + return JS_ThrowTypeError(ctx, + "'%s' called on an object that is not an instance of %s", `fn`, + `jsname`) + # We can't just set arg_0.`node` directly, or fromJS may damage it. + var nodeVal: typeof(arg_0.`node`) + if ctx.fromJS(val, nodeVal).isNone: + return JS_EXCEPTION + arg_0.`node` = move(nodeVal) + return JS_DupValue(ctx, val) + ) + registerFunction(tname, bfSetter, fn, id) + +proc bindFunctions(stmts: NimNode; info: var RegistryInfo) = + BoundFunctions.withValue(info.tname, funs): + for fun in funs[].mitems: + var f0 = fun.name + let f1 = fun.id + if fun.name.endsWith("_exceptions"): + fun.name = fun.name.substr(0, fun.name.high - "_exceptions".len) + case fun.t + of bfFunction: + f0 = fun.name + if fun.unforgeable: + info.tabUnforgeable.add(quote do: + JS_CFUNC_DEF_NOCONF(`f0`, 0, cast[JSCFunction](`f1`))) + elif fun.isstatic: + info.tabStatic.add(quote do: + JS_CFUNC_DEF(`f0`, 0, cast[JSCFunction](`f1`))) + else: + info.tabList.add(quote do: + JS_CFUNC_DEF(`f0`, 0, cast[JSCFunction](`f1`))) + of bfConstructor: + info.ctorImpl = fun.ctorBody + if info.ctorFun != nil: + error("Class " & info.tname & " has 2+ constructors.") + info.ctorFun = f1 + of bfGetter: + info.getset.withValue(f0, exv): + exv[0] = f1 + exv[2] = fun.unforgeable + do: + info.getset[f0] = (f1, newNilLit(), fun.unforgeable) + of bfSetter: + info.getset.withValue(f0, exv): + exv[1] = f1 + do: + info.getset[f0] = (newNilLit(), f1, false) + of bfPropertyGetOwn: + if info.propGetFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ own property getters.") + info.propGetOwnFun = f1 + of bfPropertyGet: + if info.propGetFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ property getters.") + info.propGetFun = f1 + of bfPropertySet: + if info.propSetFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ property setters.") + info.propSetFun = f1 + of bfPropertyDel: + if info.propDelFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ property deleters.") + info.propDelFun = f1 + of bfPropertyHas: + if info.propHasFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ hasprop getters.") + info.propHasFun = f1 + of bfPropertyNames: + if info.propNamesFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ propnames getters.") + info.propNamesFun = f1 + of bfFinalizer: + f0 = fun.name + info.finFun = ident(f0) + info.finName = f1 + +proc bindGetSet(stmts: NimNode; info: RegistryInfo) = + for k, (get, set, unforgeable) in info.getset: + if not unforgeable: + info.tabList.add(quote do: JS_CGETSET_DEF(`k`, `get`, `set`)) + else: + info.tabUnforgeable.add(quote do: + JS_CGETSET_DEF_NOCONF(`k`, `get`, `set`)) + +proc bindExtraGetSet(stmts: NimNode; info: var RegistryInfo; + extraGetSet: openArray[TabGetSet]) = + for x in extraGetSet: + let k = x.name + let g = x.get + let s = x.set + let m = x.magic + info.tabList.add(quote do: JS_CGETSET_MAGIC_DEF(`k`, `g`, `s`, `m`)) + +proc jsCheckDestroyRef*(rt: JSRuntime; val: JSValue): JS_BOOL {.cdecl.} = + let opaque = JS_GetOpaque(val, JS_GetClassID(val)) + if opaque != nil: + # Before this function is called, the ownership model is + # JSObject -> Nim object. + # Here we change it to Nim object -> JSObject. + # As a result, Nim object's reference count can now reach zero (it is + # no longer "referenced" by the JS object). + # nim_finalize_for_js will be invoked by the Nim GC when the Nim + # refcount reaches zero. Then, the JS object's opaque will be set + # to nil, and its refcount decreased again, so next time this + # function will return true. + # + # Actually, we need another hack to ensure correct + # operation. GC_unref may call the destructor of this object, and + # in this case we cannot ask QJS to keep the JSValue alive. So we set + # the "destroying" pointer to the current opaque, and return true if + # the opaque was collected. + rt.getOpaque().destroying = opaque + # We can lie about the type in refc, as it type erases the reference. + # Sadly, this won't work in ARC... then again, nothing works in ARC, + # so whatever. + GC_unref(cast[RootRef](opaque)) + if rt.getOpaque().destroying == nil: + # Looks like GC_unref called nim_finalize_for_js for this pointer. + # This means we can allow QJS to collect this JSValue. + return true + else: + rt.getOpaque().destroying = nil + # Returning false from this function signals to the QJS GC that it + # should not be collected yet. Accordingly, the JSObject's refcount + # will be set to one again. + return false + return true + +proc jsCheckDestroyNonRef*(rt: JSRuntime; val: JSValue): JS_BOOL {.cdecl.} = + let opaque = JS_GetOpaque(val, JS_GetClassID(val)) + if opaque != nil: + # This is not a reference, just a pointer with a reference to the + # root ancestor object. + # Remove the reference, allowing destruction of the root object once + # again. + let rtOpaque = rt.getOpaque() + var parent: pointer = nil + discard rtOpaque.parentMap.pop(opaque, parent) + GC_unref(cast[RootRef](parent)) + # Of course, nim_finalize_for_js might only be called later for + # this object, because the parent can still have references to it. + # (And for the same reason, a reference to the same object might + # still be necessary.) + # Accordingly, we return false here as well. + return false + return true + +proc bindEndStmts(endstmts: NimNode; info: RegistryInfo) = + let jsname = info.jsname + let dfin = info.dfin + let classDef = info.classDef + if info.propGetOwnFun.kind != nnkNilLit or + info.propGetFun.kind != nnkNilLit or + info.propSetFun.kind != nnkNilLit or + info.propDelFun.kind != nnkNilLit or + info.propHasFun.kind != nnkNilLit or + info.propNamesFun.kind != nnkNilLit: + let propGetOwnFun = info.propGetOwnFun + let propGetFun = info.propGetFun + let propSetFun = info.propSetFun + let propDelFun = info.propDelFun + let propHasFun = info.propHasFun + let propNamesFun = info.propNamesFun + endstmts.add(quote do: + var exotic {.global.} = JSClassExoticMethods( + get_own_property: `propGetOwnFun`, + get_own_property_names: `propNamesFun`, + has_property: `propHasFun`, + get_property: `propGetFun`, + set_property: `propSetFun`, + delete_property: `propDelFun` + ) + var cd {.global.} = JSClassDef( + class_name: `jsname`, + can_destroy: `dfin`, + exotic: JSClassExoticMethodsConst(addr exotic) + ) + let `classDef` = JSClassDefConst(addr cd) + ) + else: + endstmts.add(quote do: + var cd {.global.} = JSClassDef( + class_name: `jsname`, + can_destroy: `dfin` + ) + let `classDef` = JSClassDefConst(addr cd) + ) + +macro registerType*(ctx: JSContext; t: typed; parent: JSClassID = 0; + asglobal: static bool = false; globalparent: static bool = false; + nointerface = false; name: static string = ""; + hasExtraGetSet: static bool = false; + extraGetSet: static openArray[TabGetSet] = []; namespace = JS_NULL; + errid = Opt[JSErrorEnum].err(); ishtmldda = false): JSClassID = + var stmts = newStmtList() + var info = newRegistryInfo(t, name) + if not asglobal: + let impl = t.getTypeInst()[1].getTypeImpl() + if impl.kind == nnkRefTy: + info.dfin = quote do: jsCheckDestroyRef + else: + info.dfin = quote do: jsCheckDestroyNonRef + if info.tname notin jsDtors: + warning("No destructor has been defined for type " & info.tname) + else: + info.dfin = newNilLit() + if info.tname in jsDtors: + error("Global object " & info.tname & " must not have a destructor!") + let pragmas = findPragmas(t) + stmts.registerGetters(info, pragmas.jsget) + stmts.registerSetters(info, pragmas.jsset) + stmts.bindFunctions(info) + stmts.bindGetSet(info) + if hasExtraGetSet: + #HACK: for some reason, extraGetSet gets weird contents when nothing is + # passed to it. So we need an extra flag to signal if anything has + # been passed to it at all. + stmts.bindExtraGetSet(info, extraGetSet) + let sctr = stmts.bindConstructor(info) + let endstmts = newStmtList() + endstmts.bindEndStmts(info) + let tabList = info.tabList + let finName = info.finName + let classDef = info.classDef + let tname = info.tname + let unforgeable = info.tabUnforgeable + let staticfuns = info.tabStatic + let global = asglobal and not globalparent + endstmts.add(quote do: + `ctx`.newJSClass(`classDef`, `tname`, getTypePtr(`t`), `sctr`, `tabList`, + `parent`, bool(`global`), `nointerface`, `finName`, `namespace`, + `errid`, `unforgeable`, `staticfuns`, `ishtmldda`) + ) + stmts.add(newBlockStmt(endstmts)) + return stmts + +proc getMemoryUsage*(rt: JSRuntime): string = + var m: JSMemoryUsage + JS_ComputeMemoryUsage(rt, m) + template row(title: string; count, size, sz2, cnt2: int64, name: string): + string = + var fv = $(float(sz2) / float(cnt2)) + let i = fv.find('.') + if i != -1: + fv.setLen(i + 1) + else: + fv &= ".0" + title & ": " & $count & " " & $size & " (" & fv & ")" & name & "\n" + template row(title: string; count, size, sz2: int64, name: string): + string = + row(title, count, size, sz2, count, name) + template row(title: string; count, size: int64, name: string): string = + row(title, count, size, size, name) + var s = "" + if m.malloc_count != 0: + s &= row("memory allocated", m.malloc_count, m.malloc_size, "/block") + s &= row("memory used", m.memory_used_count, m.memory_used_size, + m.malloc_size - m.memory_used_size, " average slack") + if m.atom_count != 0: + s &= row("atoms", m.atom_count, m.atom_size, "/atom") + if m.str_count != 0: + s &= row("strings", m.str_count, m.str_size, "/string") + if m.obj_count != 0: + s &= row("objects", m.obj_count, m.obj_size, "/object") & + row("properties", m.prop_count, m.prop_size, m.prop_size, m.obj_count, + "/object") & + row("shapes", m.shape_count, m.shape_size, "/shape") + if m.js_func_count != 0: + s &= row("js functions", m.js_func_count, m.js_func_size, "/function") + if m.c_func_count != 0: + s &= "native functions: " & $m.c_func_count & "\n" + if m.array_count != 0: + s &= "arrays: " & $m.array_count & "\n" & + "fast arrays: " & $m.fast_array_count & "\n" & + row("fast array elements", m.fast_array_elements, + m.fast_array_elements * sizeof(JSValue), m.fast_array_elements, + m.fast_array_count, "") + if m.binary_object_count != 0: + s &= "binary objects: " & $m.binary_object_count & " " & + $m.binary_object_size + return s + +proc eval*(ctx: JSContext; s: string; file = "<input>"; + evalFlags = JS_EVAL_TYPE_GLOBAL): JSValue = + return JS_Eval(ctx, cstring(s), csize_t(s.len), cstring(file), + cint(evalFlags)) + +proc compileScript*(ctx: JSContext; s: string; file = "<input>"): JSValue = + return ctx.eval(s, file, JS_EVAL_FLAG_COMPILE_ONLY) + +proc compileModule*(ctx: JSContext; s: string; file = "<input>"): JSValue = + return ctx.eval(s, file, JS_EVAL_TYPE_MODULE or JS_EVAL_FLAG_COMPILE_ONLY) + +proc evalFunction*(ctx: JSContext; val: JSValue): JSValue = + return JS_EvalFunction(ctx, val) + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/jserror.nim b/lib/monoucha0/monoucha/jserror.nim new file mode 100644 index 00000000..ed3fe4f8 --- /dev/null +++ b/lib/monoucha0/monoucha/jserror.nim @@ -0,0 +1,84 @@ +{.push raises: [].} + +import optshim + +type + JSError* = ref object of RootObj + e*: JSErrorEnum + message*: string + + JSErrorEnum* = enum + # QuickJS internal errors + jeEvalError = "EvalError" + jeRangeError = "RangeError" + jeReferenceError = "ReferenceError" + jeSyntaxError = "SyntaxError" + jeTypeError = "TypeError" + jeURIError = "URIError" + jeInternalError = "InternalError" + jeAggregateError = "AggregateError" + # Chawan errors + jeDOMException = "DOMException" + + JSResult*[T] = Result[T, JSError] + +const QuickJSErrors* = [ + jeEvalError, + jeRangeError, + jeReferenceError, + jeSyntaxError, + jeTypeError, + jeURIError, + jeInternalError, + jeAggregateError +] + +proc newEvalError*(message: string): JSError = + return JSError(e: jeEvalError, message: message) + +proc newRangeError*(message: string): JSError = + return JSError(e: jeRangeError, message: message) + +proc newReferenceError*(message: string): JSError = + return JSError(e: jeReferenceError, message: message) + +proc newSyntaxError*(message: string): JSError = + return JSError(e: jeSyntaxError, message: message) + +proc newTypeError*(message: string): JSError = + return JSError(e: jeTypeError, message: message) + +proc newURIError*(message: string): JSError = + return JSError(e: jeURIError, message: message) + +proc newInternalError*(message: string): JSError = + return JSError(e: jeInternalError, message: message) + +proc newAggregateError*(message: string): JSError = + return JSError(e: jeAggregateError, message: message) + +template errEvalError*(message: string): untyped = + err(newEvalError(message)) + +template errRangeError*(message: string): untyped = + err(newRangeError(message)) + +template errReferenceError*(message: string): untyped = + err(newReferenceError(message)) + +template errSyntaxError*(message: string): untyped = + err(newSyntaxError(message)) + +template errTypeError*(message: string): untyped = + err(newTypeError(message)) + +template errURIError*(message: string): untyped = + err(newURIError(message)) + +template errInternalError*(message: string): untyped = + err(newInternalError(message)) + +template errAggregateError*(message: string): untyped = + err(newAggregateError(message)) + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/jsopaque.nim b/lib/monoucha0/monoucha/jsopaque.nim new file mode 100644 index 00000000..8510965d --- /dev/null +++ b/lib/monoucha0/monoucha/jsopaque.nim @@ -0,0 +1,107 @@ +{.push raises: [].} + +import std/tables + +import jserror +import quickjs + +type + JSSymbolRef* = enum + jsyIterator = "iterator" + jsyAsyncIterator = "asyncIterator" + jsyToStringTag = "toStringTag" + + JSStrRef* = enum + jstDone = "done" + jstValue = "value" + jstNext = "next" + jstPrototype = "prototype" + jstThen = "then" + + JSValueRef* = enum + jsvArrayPrototypeValues = "Array.prototype.values" + jsvUint8Array = "Uint8Array" + jsvObjectPrototypeValueOf = "Object.prototype.valueOf" + jsvSet = "Set" + jsvFunction = "Function" + + JSContextOpaque* = ref object + creg*: Table[cstring, JSClassID] + typemap*: Table[pointer, JSClassID] + ctors*: seq[JSValue] # JSClassID -> JSValue + parents*: seq[JSClassID] # JSClassID -> JSClassID + # Parent unforgeables are merged on class creation. + # (i.e. to set all unforgeables on the prototype chain, it is enough to set) + # `unforgeable[classid]'.) + unforgeable*: seq[seq[JSCFunctionListEntry]] # JSClassID -> seq + gclass*: JSClassID # class ID of the global object + global*: JSValue + symRefs*: array[JSSymbolRef, JSAtom] + strRefs*: array[JSStrRef, JSAtom] + valRefs*: array[JSValueRef, JSValue] + errCtorRefs*: array[JSErrorEnum, JSValue] + htmldda*: JSClassID # only one of these exists: document.all. + globalUnref*: JSEmptyOpaqueCallback + + JSFinalizerFunction* = proc(rt: JSRuntime; val: JSValue) {.nimcall, + raises: [].} + + JSEmptyOpaqueCallback* = (proc() {.closure, raises: [].}) + + JSRuntimeOpaque* = ref object + plist*: Table[pointer, pointer] # Nim, JS + flist*: seq[seq[JSCFunctionListEntry]] + fins*: Table[JSClassID, JSFinalizerFunction] + parentMap*: Table[pointer, pointer] + destroying*: pointer + # temp list for uninit + tmplist*: seq[pointer] + tmpunrefs*: seq[pointer] + +func newJSContextOpaque*(ctx: JSContext): JSContextOpaque = + let opaque = JSContextOpaque(global: JS_GetGlobalObject(ctx)) + block: # get well-known symbols and other functions + let sym = JS_GetPropertyStr(ctx, opaque.global, "Symbol") + for s in JSSymbolRef: + let name = $s + let val = JS_GetPropertyStr(ctx, sym, cstring(name)) + assert JS_IsSymbol(val) + opaque.symRefs[s] = JS_ValueToAtom(ctx, val) + JS_FreeValue(ctx, val) + JS_FreeValue(ctx, sym) + for s in JSStrRef: + let ss = $s + opaque.strRefs[s] = JS_NewAtomLen(ctx, cstring(ss), csize_t(ss.len)) + for s in JSValueRef: + let ss = $s + let ret = JS_Eval(ctx, cstring(ss), csize_t(ss.len), "<init>", 0) + assert JS_IsFunction(ctx, ret) + opaque.valRefs[s] = ret + for e in JSErrorEnum: + let s = $e + let err = JS_GetPropertyStr(ctx, opaque.global, cstring(s)) + opaque.errCtorRefs[e] = err + return opaque + +func getOpaque*(ctx: JSContext): JSContextOpaque = + return cast[JSContextOpaque](JS_GetContextOpaque(ctx)) + +func getOpaque*(rt: JSRuntime): JSRuntimeOpaque = + return cast[JSRuntimeOpaque](JS_GetRuntimeOpaque(rt)) + +func isGlobal*(ctx: JSContext; class: JSClassID): bool = + return ctx.getOpaque().gclass == class + +proc setOpaque*(ctx: JSContext; val: JSValue; opaque: pointer) = + let rt = JS_GetRuntime(ctx) + let rtOpaque = rt.getOpaque() + let p = JS_VALUE_GET_PTR(val) + rtOpaque.plist[opaque] = p + JS_SetOpaque(val, opaque) + +func getOpaque*(val: JSValue): pointer = + if JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT: + return JS_GetOpaque(val, JS_GetClassID(val)) + return nil + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/jspropenumlist.nim b/lib/monoucha0/monoucha/jspropenumlist.nim new file mode 100644 index 00000000..74fd7090 --- /dev/null +++ b/lib/monoucha0/monoucha/jspropenumlist.nim @@ -0,0 +1,51 @@ +{.push raises: [].} + +import quickjs + +type + JSPropertyEnumArray* = ptr UncheckedArray[JSPropertyEnum] + + JSPropertyEnumList* = object + buffer*: JSPropertyEnumArray + size: uint32 + len*: uint32 + ctx: JSContext + + JSPropertyEnumWrapper* = object + is_enumerable: bool + name: string + +func newJSPropertyEnumList*(ctx: JSContext; size: uint32): JSPropertyEnumList = + let p = if size != 0: + js_malloc(ctx, csize_t(sizeof(JSPropertyEnum)) * csize_t(size)) + else: + nil + return JSPropertyEnumList( + ctx: ctx, + buffer: cast[JSPropertyEnumArray](p), + size: size + ) + +proc grow(this: var JSPropertyEnumList) = + if this.size == 0: + this.size = 1 + this.size *= 2 + let p = js_realloc(this.ctx, this.buffer, + csize_t(sizeof(JSPropertyEnum)) * csize_t(this.size)) + this.buffer = cast[JSPropertyEnumArray](p) + +proc add*(this: var JSPropertyEnumList; val: uint32) = + let i = this.len + inc this.len + if this.size < this.len: + this.grow() + this.buffer[i].atom = JS_NewAtomUInt32(this.ctx, val) + +proc add*(this: var JSPropertyEnumList; val: string) = + let i = this.len + inc this.len + if this.size < this.len: + this.grow() + this.buffer[i].atom = JS_NewAtomLen(this.ctx, cstring(val), csize_t(val.len)) + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/jsregex.nim b/lib/monoucha0/monoucha/jsregex.nim new file mode 100644 index 00000000..aa550949 --- /dev/null +++ b/lib/monoucha0/monoucha/jsregex.nim @@ -0,0 +1,97 @@ +# Interface for QuickJS libregexp. +{.push raises: [].} + +import libregexp +import optshim + +export LREFlags + +type + Regex* = object + bytecode: seq[uint8] + when defined(debug): + buf: string + + RegexCapture* = tuple # start, end, index + s, e: int + + RegexResult* = object + success*: bool + captures*: seq[seq[RegexCapture]] + +when defined(debug): + func `$`*(regex: Regex): string = + regex.buf + +proc compileRegex*(buf: string; flags: LREFlags = {}): Result[Regex, string] = + ## Compile a regular expression using QuickJS's libregexp library. + ## The result is either a regex, or the error message emitted by libregexp. + ## + ## Use `exec` to actually use the resulting bytecode on a string. + var errorMsg = newString(64) + var plen: cint + let bytecode = lre_compile(addr plen, cstring(errorMsg), cint(errorMsg.len), + cstring(buf), csize_t(buf.len), flags.toCInt, nil) + if bytecode == nil: # Failed to compile. + let i = errorMsg.find('\0') + if i != -1: + errorMsg.setLen(i) + return err(errorMsg) + assert plen > 0 + var bcseq = newSeqUninitialized[uint8](plen) + copyMem(addr bcseq[0], bytecode, plen) + dealloc(bytecode) + var regex = Regex(bytecode: bcseq) + when defined(debug): + regex.buf = buf + return ok(regex) + +proc exec*(regex: Regex; str: string; start = 0; length = -1; nocaps = false): + RegexResult = + let length = if length == -1: + str.len + else: + length + assert start in 0 .. length + let bytecode = unsafeAddr regex.bytecode[0] + let captureCount = lre_get_capture_count(bytecode) + var capture: ptr UncheckedArray[int] = nil + if captureCount > 0: + let size = sizeof(ptr uint8) * captureCount * 2 + capture = cast[ptr UncheckedArray[int]](alloc0(size)) + var cstr = cstring(str) + let flags = lre_get_flags(bytecode).toLREFlags + var start = start + result = RegexResult() + while true: + let ret = lre_exec(cast[ptr ptr uint8](capture), bytecode, + cast[ptr uint8](cstr), cint(start), cint(length), cint(3), nil) + if ret != 1: #TODO error handling? (-1) + break + result.success = true + if captureCount == 0 or nocaps: + break + var caps: seq[RegexCapture] = @[] + let cstrAddress = cast[int](cstr) + let ps = start + start = capture[1] - cstrAddress + for i in 0 ..< captureCount: + let s = capture[i * 2] - cstrAddress + let e = capture[i * 2 + 1] - cstrAddress + caps.add((s, e)) + result.captures.add(caps) + if LRE_FLAG_GLOBAL notin flags: + break + if start >= str.len: + break + if ps == start: # avoid infinite loop: skip the first UTF-8 char. + inc start + while start < str.len and uint8(str[start]) in 0x80u8 .. 0xBFu8: + inc start + if captureCount > 0: + dealloc(capture) + +proc match*(regex: Regex; str: string; start = 0; length = str.len): bool = + return regex.exec(str, start, length, nocaps = true).success + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/jstypes.nim b/lib/monoucha0/monoucha/jstypes.nim new file mode 100644 index 00000000..af471132 --- /dev/null +++ b/lib/monoucha0/monoucha/jstypes.nim @@ -0,0 +1,74 @@ +{.push raises: [].} + +import quickjs + +# This is the WebIDL dictionary type. +# We only use it for type inference in generics. +#TODO required members +type + # JSDictToFreeAux is a hack to ensure JSValues are freed only + # when the JSDict goes out of scope. Ugly and sub-optimal, but it does + # the job. + JSDictToFreeAux* = ref JSDictToFreeAuxObj + JSDictToFreeAuxObj = object + ctx*: JSContext + vals*: seq[JSValue] + + JSDict* = object of RootObj + toFree*: JSDictToFreeAux + +{.warning[Deprecated]:off.}: + proc `=destroy`*(x: var JSDictToFreeAuxObj) = + for val in x.vals: + JS_FreeValue(x.ctx, val) + +# Example usage: +# +# type MyOptions = object of JSDict +# x {.jsdefault: 1.}: int +# y {.jsdefault.}: bool +# +# For the above JSDict, no exception will be thrown if `x` is missing; instead, +# it gets set to `1'. +template jsdefault*(x: untyped) {.pragma.} +template jsdefault*() {.pragma.} + +# Containers compatible with the internal representation of strings in QuickJS. +# To convert these, a copy is still needed; however, they remove the UTF-8 +# transcoding step. +type + NarrowString* = distinct string + WideString* = distinct seq[uint16] + +# Various containers for array buffer types. +# Converting these only requires copying the metadata; buffers are never copied. +type + JSArrayBuffer* = object + p*: ptr UncheckedArray[uint8] + len*: csize_t + dealloc*: JSFreeArrayBufferDataFunc + + JSArrayBufferView* = object + abuf*: JSArrayBuffer + offset*: csize_t # offset into the buffer + nmemb*: csize_t # number of members + nsize*: csize_t # member size + t*: cint # type + + JSUint8Array* = object + abuf*: JSArrayBuffer + offset*: csize_t # offset into the buffer + nmemb*: csize_t # number of members + +func high*(abuf: JSArrayBuffer): int = + return int(abuf.len) - 1 + +# A specialization of JSValue to make writing generic code for functions +# easier. +type JSValueFunction* = ref object + fun*: JSValue + +converter toJSValue*(f: JSValueFunction): JSValue = + f.fun + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/jsutils.nim b/lib/monoucha0/monoucha/jsutils.nim new file mode 100644 index 00000000..26246480 --- /dev/null +++ b/lib/monoucha0/monoucha/jsutils.nim @@ -0,0 +1,16 @@ +{.push raises: [].} + +import quickjs + +template toJSValueArray*(a: openArray[JSValue]): ptr UncheckedArray[JSValue] = + if a.len > 0: + cast[ptr UncheckedArray[JSValue]](unsafeAddr a[0]) + else: + nil + +# Warning: this must be a template, because we're taking the address of +# the passed value, and Nim is pass-by-value. +template toJSValueArray*(a: JSValue): ptr UncheckedArray[JSValue] = + cast[ptr UncheckedArray[JSValue]](unsafeAddr a) + +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/libregexp.nim b/lib/monoucha0/monoucha/libregexp.nim new file mode 100644 index 00000000..4a118afd --- /dev/null +++ b/lib/monoucha0/monoucha/libregexp.nim @@ -0,0 +1,81 @@ +{.push raises: [].} + +from std/os import parentDir + +{.used.} +# used so that we can import it from quickjs.nim + +import libunicode + +when not compileOption("threads"): + const CFLAGS = "-O2 -fwrapv -DMNC_NO_THREADS" +else: + const CFLAGS = "-O2 -fwrapv" + +{.compile("qjs/libregexp.c", CFLAGS).} + +# this is hardcoded into quickjs, so we must override it here. +proc lre_realloc(opaque, p: pointer; size: csize_t): pointer {.exportc.} = + return realloc(p, size) + +# Hack: quickjs provides a lre_check_stack_overflow, but that basically +# depends on the entire QuickJS runtime. So to avoid pulling that in as +# a necessary dependency, we must provide one ourselves, but *only* if +# quickjs has not been imported. +# So we define NOT_LRE_ONLY in quickjs.nim, and check it in the "second +# compilation pass" (i.e. in C). +{.emit: """ +#ifndef NOT_LRE_ONLY +int lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + return 0; +} +#endif +""".} + +type + LREFlag* {.size: sizeof(cint).} = enum + LRE_FLAG_GLOBAL = "g" + LRE_FLAG_IGNORECASE = "i" + LRE_FLAG_MULTILINE = "m" + LRE_FLAG_DOTALL = "s" + LRE_FLAG_UNICODE = "u" + LRE_FLAG_STICKY = "y" + + LREFlags* = set[LREFlag] + +func toCInt*(flags: LREFlags): cint = + cint(cast[uint8](flags)) + +func toLREFlags*(flags: cint): LREFlags = + cast[LREFlags](flags) + +{.passc: "-I" & currentSourcePath().parentDir().} + +{.push header: "qjs/libregexp.h", importc.} +proc lre_compile*(plen: ptr cint; error_msg: cstring; error_msg_size: cint; + buf: cstring; buf_len: csize_t; re_flags: cint; opaque: pointer): ptr uint8 + +proc lre_exec*(capture: ptr ptr uint8; bc_buf, cbuf: ptr uint8; + cindex, clen, cbuf_type: cint; opaque: pointer): cint + +proc lre_get_capture_count*(bc_buf: ptr uint8): cint + +proc lre_get_flags*(bc_buf: ptr uint8): cint + +const LRE_CC_RES_LEN_MAX* = 3 + +# conv_type: +# 0 = to upper +# 1 = to lower +# 2 = case folding +# res must be an array of LRE_CC_RES_LEN_MAX +proc lre_case_conv*(res: ptr UncheckedArray[uint32]; c: uint32; + conv_type: cint): cint + +proc lre_is_space_non_ascii*(c: uint32): cint {.importc.} + +proc lre_is_space*(c: uint32): cint {.importc.} + +{.pop.} # header, importc +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/libunicode.nim b/lib/monoucha0/monoucha/libunicode.nim new file mode 100644 index 00000000..4d4b29a8 --- /dev/null +++ b/lib/monoucha0/monoucha/libunicode.nim @@ -0,0 +1,49 @@ +{.push raises: [].} + +from std/os import parentDir + +{.used.} +# used so that we can import it from libregexp.nim + +when not compileOption("threads"): + const CFLAGS = "-O2 -fwrapv -DMNC_NO_THREADS" +else: + const CFLAGS = "-O2 -fwrapv" + +{.compile("qjs/libunicode.c", CFLAGS).} +{.compile("qjs/cutils.c", CFLAGS).} + +const luheader = "qjs/libunicode.h" + +type + DynBufReallocFunc = proc(opaque, p: pointer; size: csize_t): pointer {.cdecl.} + + CharRange* {.importc, header: luheader.} = object + len*: cint # in points, always even + size*: cint + points*: ptr uint32 # points sorted by increasing value + mem_opaque*: pointer + realloc_func*: DynBufReallocFunc + + UnicodeNormalizationEnum* {.size: sizeof(cint).} = enum + UNICODE_NFC, UNICODE_NFD, UNICODE_NKFC, UNICODE_NKFD + +{.passc: "-I" & currentSourcePath().parentDir().} + +{.push header: luheader, importc.} + +proc cr_init*(cr: ptr CharRange; mem_opaque: pointer; + realloc_func: DynBufReallocFunc) + +proc cr_free*(cr: ptr CharRange) + +proc unicode_normalize*(pdst: ptr ptr uint32; src: ptr uint32; src_len: cint; + n_type: UnicodeNormalizationEnum; opaque: pointer; + realloc_func: DynBufReallocFunc): cint + +proc unicode_script*(cr: ptr CharRange; script_name: cstring; is_ext: cint): + cint +proc unicode_prop*(cr: ptr CharRange; prop_name: cstring): cint +proc unicode_general_category*(cr: ptr CharRange; gc_name: cstring): cint +{.pop.} # header, importc +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/optshim.nim b/lib/monoucha0/monoucha/optshim.nim new file mode 100644 index 00000000..a3c265f7 --- /dev/null +++ b/lib/monoucha0/monoucha/optshim.nim @@ -0,0 +1,9 @@ +const monouchaUseOpt {.booldefine.} = false +when monouchaUseOpt: + import types/opt + export opt +else: + import results + export results + + template isSome*[T: not void, E](res: Result[T, E]): bool = res.isOk diff --git a/lib/monoucha0/monoucha/qjs/LICENSE b/lib/monoucha0/monoucha/qjs/LICENSE new file mode 100644 index 00000000..7d87e3ca --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2017-2021 Fabrice Bellard +Copyright (c) 2017-2021 Charlie Gordon +Copyright (c) 2023 Ben Noordhuis +Copyright (c) 2023 Saúl Ibarra Corretgé + +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/lib/monoucha0/monoucha/qjs/cutils.c b/lib/monoucha0/monoucha/qjs/cutils.c new file mode 100644 index 00000000..0a8ff459 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/cutils.c @@ -0,0 +1,1411 @@ +/* + * 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 <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <time.h> +#if !defined(_MSC_VER) +#include <sys/time.h> +#endif + +#include "cutils.h" + +#undef NANOSEC +#define NANOSEC ((uint64_t) 1e9) + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif + +void js__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 *js__pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + js__pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int js__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 js__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)); +} + +/* Dynamic buffer package */ + +static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func) +{ + memset(s, 0, sizeof(*s)); + if (!realloc_func) + realloc_func = dbuf_default_realloc; + s->opaque = opaque; + s->realloc_func = realloc_func; +} + +void dbuf_init(DynBuf *s) +{ + dbuf_init2(s, NULL, NULL); +} + +/* return < 0 if error */ +int dbuf_realloc(DynBuf *s, size_t new_size) +{ + size_t size; + uint8_t *new_buf; + if (new_size > s->allocated_size) { + if (s->error) + return -1; + size = s->allocated_size * 3 / 2; + if (size > new_size) + new_size = size; + new_buf = s->realloc_func(s->opaque, s->buf, new_size); + if (!new_buf) { + s->error = TRUE; + return -1; + } + s->buf = new_buf; + s->allocated_size = new_size; + } + return 0; +} + +int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len) +{ + size_t end; + end = offset + len; + if (dbuf_realloc(s, end)) + return -1; + memcpy(s->buf + offset, data, len); + if (end > s->size) + s->size = end; + return 0; +} + +int dbuf_put(DynBuf *s, const void *data, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_realloc(s, s->size + len)) + return -1; + } + if (len > 0) { + memcpy(s->buf + s->size, data, len); + s->size += len; + } + return 0; +} + +int dbuf_put_self(DynBuf *s, size_t offset, size_t len) +{ + if (unlikely((s->size + len) > s->allocated_size)) { + if (dbuf_realloc(s, s->size + len)) + return -1; + } + memcpy(s->buf + s->size, s->buf + offset, len); + s->size += len; + return 0; +} + +int dbuf_putc(DynBuf *s, uint8_t c) +{ + return dbuf_put(s, &c, 1); +} + +int dbuf_putstr(DynBuf *s, const char *str) +{ + return dbuf_put(s, (const uint8_t *)str, strlen(str)); +} + +int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, + const char *fmt, ...) +{ + va_list ap; + char buf[128]; + int len; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < (int)sizeof(buf)) { + /* fast case */ + return dbuf_put(s, (uint8_t *)buf, len); + } else { + if (dbuf_realloc(s, s->size + len + 1)) + return -1; + va_start(ap, fmt); + vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size, + fmt, ap); + va_end(ap); + s->size += len; + } + return 0; +} + +void dbuf_free(DynBuf *s) +{ + /* we test s->buf as a fail safe to avoid crashing if dbuf_free() + is called twice */ + if (s->buf) { + s->realloc_func(s->opaque, s->buf, 0); + } + memset(s, 0, sizeof(*s)); +} + +/*--- UTF-8 utility functions --*/ + +/* Note: only encode valid codepoints (0x0000..0x10FFFF). + At most UTF8_CHAR_LEN_MAX bytes are output. */ + +/* Compute the number of bytes of the UTF-8 encoding for a codepoint + `c` is a code-point. + Returns the number of bytes. If a codepoint is beyond 0x10FFFF the + return value is 3 as the codepoint would be encoded as 0xFFFD. + */ +size_t utf8_encode_len(uint32_t c) +{ + if (c < 0x80) + return 1; + if (c < 0x800) + return 2; + if (c < 0x10000) + return 3; + if (c < 0x110000) + return 4; + return 3; +} + +/* Encode a codepoint in UTF-8 + `buf` points to an array of at least `UTF8_CHAR_LEN_MAX` bytes + `c` is a code-point. + Returns the number of bytes. If a codepoint is beyond 0x10FFFF the + return value is 3 and the codepoint is encoded as 0xFFFD. + No null byte is stored after the encoded bytes. + Return value is in range 1..4 + */ +size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c) +{ + if (c < 0x80) { + buf[0] = c; + return 1; + } + if (c < 0x800) { + buf[0] = (c >> 6) | 0xC0; + buf[1] = (c & 0x3F) | 0x80; + return 2; + } + if (c < 0x10000) { + buf[0] = (c >> 12) | 0xE0; + buf[1] = ((c >> 6) & 0x3F) | 0x80; + buf[2] = (c & 0x3F) | 0x80; + return 3; + } + if (c < 0x110000) { + buf[0] = (c >> 18) | 0xF0; + buf[1] = ((c >> 12) & 0x3F) | 0x80; + buf[2] = ((c >> 6) & 0x3F) | 0x80; + buf[3] = (c & 0x3F) | 0x80; + return 4; + } + buf[0] = (0xFFFD >> 12) | 0xE0; + buf[1] = ((0xFFFD >> 6) & 0x3F) | 0x80; + buf[2] = (0xFFFD & 0x3F) | 0x80; + return 3; +} + +/* Decode a single code point from a UTF-8 encoded array of bytes + `p` is a valid pointer to an array of bytes + `pp` is a valid pointer to a `const uint8_t *` to store a pointer + to the byte following the current sequence. + Return the code point at `p`, in the range `0..0x10FFFF` + Return 0xFFFD on error. Only a single byte is consumed in this case + The maximum length for a UTF-8 byte sequence is 4 bytes. + This implements the algorithm specified in whatwg.org, except it accepts + UTF-8 encoded surrogates as JavaScript allows them in strings. + The source string is assumed to have at least UTF8_CHAR_LEN_MAX bytes + or be null terminated. + If `p[0]` is '\0', the return value is `0` and the byte is consumed. + cf: https://encoding.spec.whatwg.org/#utf-8-encoder + */ +uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp) +{ + uint32_t c; + uint8_t lower, upper; + + c = *p++; + if (c < 0x80) { + *pp = p; + return c; + } + switch(c) { + case 0xC2: case 0xC3: + case 0xC4: case 0xC5: case 0xC6: case 0xC7: + case 0xC8: case 0xC9: case 0xCA: case 0xCB: + case 0xCC: case 0xCD: case 0xCE: case 0xCF: + case 0xD0: case 0xD1: case 0xD2: case 0xD3: + case 0xD4: case 0xD5: case 0xD6: case 0xD7: + case 0xD8: case 0xD9: case 0xDA: case 0xDB: + case 0xDC: case 0xDD: case 0xDE: case 0xDF: + if (*p >= 0x80 && *p <= 0xBF) { + *pp = p + 1; + return ((c - 0xC0) << 6) + (*p - 0x80); + } + // otherwise encoding error + break; + case 0xE0: + lower = 0xA0; /* reject invalid encoding */ + goto need2; + case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE6: case 0xE7: + case 0xE8: case 0xE9: case 0xEA: case 0xEB: + case 0xEC: case 0xED: case 0xEE: case 0xEF: + lower = 0x80; + need2: + if (*p >= lower && *p <= 0xBF && p[1] >= 0x80 && p[1] <= 0xBF) { + *pp = p + 2; + return ((c - 0xE0) << 12) + ((*p - 0x80) << 6) + (p[1] - 0x80); + } + // otherwise encoding error + break; + case 0xF0: + lower = 0x90; /* reject invalid encoding */ + upper = 0xBF; + goto need3; + case 0xF4: + lower = 0x80; + upper = 0x8F; /* reject values above 0x10FFFF */ + goto need3; + case 0xF1: case 0xF2: case 0xF3: + lower = 0x80; + upper = 0xBF; + need3: + if (*p >= lower && *p <= upper && p[1] >= 0x80 && p[1] <= 0xBF + && p[2] >= 0x80 && p[2] <= 0xBF) { + *pp = p + 3; + return ((c - 0xF0) << 18) + ((*p - 0x80) << 12) + + ((p[1] - 0x80) << 6) + (p[2] - 0x80); + } + // otherwise encoding error + break; + default: + // invalid lead byte + break; + } + *pp = p; + return 0xFFFD; +} + +uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp) { + switch (max_len) { + case 0: + *pp = p; + return 0xFFFD; + case 1: + if (*p < 0x80) + goto good; + break; + case 2: + if (*p < 0xE0) + goto good; + break; + case 3: + if (*p < 0xF0) + goto good; + break; + default: + good: + return utf8_decode(p, pp); + } + *pp = p + 1; + return 0xFFFD; +} + +/* Scan a UTF-8 encoded buffer for content type + `buf` is a valid pointer to a UTF-8 encoded string + `len` is the number of bytes to scan + `plen` points to a `size_t` variable to receive the number of units + Return value is a mask of bits. + - `UTF8_PLAIN_ASCII`: return value for 7-bit ASCII plain text + - `UTF8_NON_ASCII`: bit for non ASCII code points (8-bit or more) + - `UTF8_HAS_16BIT`: bit for 16-bit code points + - `UTF8_HAS_NON_BMP1`: bit for non-BMP1 code points, needs UTF-16 surrogate pairs + - `UTF8_HAS_ERRORS`: bit for encoding errors + */ +int utf8_scan(const char *buf, size_t buf_len, size_t *plen) +{ + const uint8_t *p, *p_end, *p_next; + size_t i, len; + int kind; + uint8_t cbits; + + kind = UTF8_PLAIN_ASCII; + cbits = 0; + len = buf_len; + // TODO: handle more than 1 byte at a time + for (i = 0; i < buf_len; i++) + cbits |= buf[i]; + if (cbits >= 0x80) { + p = (const uint8_t *)buf; + p_end = p + buf_len; + kind = UTF8_NON_ASCII; + len = 0; + while (p < p_end) { + len++; + if (*p++ >= 0x80) { + /* parse UTF-8 sequence, check for encoding error */ + uint32_t c = utf8_decode_len(p - 1, p_end - (p - 1), &p_next); + if (p_next == p) + kind |= UTF8_HAS_ERRORS; + p = p_next; + if (c > 0xFF) { + kind |= UTF8_HAS_16BIT; + if (c > 0xFFFF) { + len++; + kind |= UTF8_HAS_NON_BMP1; + } + } + } + } + } + *plen = len; + return kind; +} + +/* Decode a string encoded in UTF-8 into an array of bytes + `src` points to the source string. It is assumed to be correctly encoded + and only contains code points below 0x800 + `src_len` is the length of the source string + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len) +{ + const uint8_t *p, *p_end; + size_t i; + + p = (const uint8_t *)src; + p_end = p + src_len; + for (i = 0; p < p_end; i++) { + uint32_t c = *p++; + if (c >= 0xC0) + c = (c << 6) + *p++ - ((0xC0 << 6) + 0x80); + if (i < dest_len) + dest[i] = c; + } + if (i < dest_len) + dest[i] = '\0'; + else if (dest_len > 0) + dest[dest_len - 1] = '\0'; + return i; +} + +/* Decode a string encoded in UTF-8 into an array of 16-bit words + `src` points to the source string. It is assumed to be correctly encoded. + `src_len` is the length of the source string + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length of the destination array. No null terminator is + stored at the end of the array. + */ +size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len) +{ + const uint8_t *p, *p_end; + size_t i; + + p = (const uint8_t *)src; + p_end = p + src_len; + for (i = 0; p < p_end; i++) { + uint32_t c = *p++; + if (c >= 0x80) { + /* parse utf-8 sequence */ + c = utf8_decode_len(p - 1, p_end - (p - 1), &p); + /* encoding errors are converted as 0xFFFD and use a single byte */ + if (c > 0xFFFF) { + if (i < dest_len) + dest[i] = get_hi_surrogate(c); + i++; + c = get_lo_surrogate(c); + } + } + if (i < dest_len) + dest[i] = c; + } + return i; +} + +/* Encode a buffer of 8-bit bytes as a UTF-8 encoded string + `src` points to the source buffer. + `src_len` is the length of the source buffer + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length in bytes of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len) +{ + size_t i, j; + uint32_t c; + + for (i = j = 0; i < src_len; i++) { + c = src[i]; + if (c < 0x80) { + if (j + 1 >= dest_len) + goto overflow; + dest[j++] = c; + } else { + if (j + 2 >= dest_len) + goto overflow; + dest[j++] = (c >> 6) | 0xC0; + dest[j++] = (c & 0x3F) | 0x80; + } + } + if (j < dest_len) + dest[j] = '\0'; + return j; + +overflow: + if (j < dest_len) + dest[j] = '\0'; + while (i < src_len) + j += 1 + (src[i++] >= 0x80); + return j; +} + +/* Encode a buffer of 16-bit code points as a UTF-8 encoded string + `src` points to the source buffer. + `src_len` is the length of the source buffer + `dest` points to the destination array, it can be null if `dest_len` is `0` + `dest_len` is the length in bytes of the destination array. A null + terminator is stored at the end of the array unless `dest_len` is `0`. + */ +size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len) +{ + size_t i, j; + uint32_t c; + + for (i = j = 0; i < src_len;) { + c = src[i++]; + if (c < 0x80) { + if (j + 1 >= dest_len) + goto overflow; + dest[j++] = c; + } else { + if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) + c = from_surrogate(c, src[i++]); + if (j + utf8_encode_len(c) >= dest_len) + goto overflow; + j += utf8_encode((uint8_t *)dest + j, c); + } + } + if (j < dest_len) + dest[j] = '\0'; + return j; + +overflow: + i -= 1 + (c > 0xFFFF); + if (j < dest_len) + dest[j] = '\0'; + while (i < src_len) { + c = src[i++]; + if (c < 0x80) { + j++; + } else { + if (is_hi_surrogate(c) && i < src_len && is_lo_surrogate(src[i])) + c = from_surrogate(c, src[i++]); + j += utf8_encode_len(c); + } + } + return j; +} + +/*--- integer to string conversions --*/ + +/* All conversion functions: + - require a destination array `buf` of sufficient length + - write the string representation at the beginning of `buf` + - null terminate the string + - return the string length + */ + +/* 2 <= base <= 36 */ +char const digits36[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +#define USE_SPECIAL_RADIX_10 1 // special case base 10 radix conversions +#define USE_SINGLE_CASE_FAST 1 // special case single digit numbers + +/* using u32toa_shift variant */ + +#define gen_digit(buf, c) if (is_be()) \ + buf = (buf >> 8) | ((uint64_t)(c) << ((sizeof(buf) - 1) * 8)); \ + else \ + buf = (buf << 8) | (c) + +static size_t u7toa_shift(char dest[minimum_length(8)], uint32_t n) +{ + size_t len = 1; + uint64_t buf = 0; + while (n >= 10) { + uint32_t quo = n % 10; + n /= 10; + gen_digit(buf, '0' + quo); + len++; + } + gen_digit(buf, '0' + n); + memcpy(dest, &buf, sizeof buf); + return len; +} + +static size_t u07toa_shift(char dest[minimum_length(8)], uint32_t n, size_t len) +{ + size_t i; + dest += len; + dest[7] = '\0'; + for (i = 7; i-- > 1;) { + uint32_t quo = n % 10; + n /= 10; + dest[i] = (char)('0' + quo); + } + dest[i] = (char)('0' + n); + return len + 7; +} + +size_t u32toa(char buf[minimum_length(11)], uint32_t n) +{ +#ifdef USE_SINGLE_CASE_FAST /* 10% */ + if (n < 10) { + buf[0] = (char)('0' + n); + buf[1] = '\0'; + return 1; + } +#endif +#define TEN_POW_7 10000000 + if (n >= TEN_POW_7) { + uint32_t quo = n / TEN_POW_7; + n %= TEN_POW_7; + size_t len = u7toa_shift(buf, quo); + return u07toa_shift(buf, n, len); + } + return u7toa_shift(buf, n); +} + +size_t u64toa(char buf[minimum_length(21)], uint64_t n) +{ + if (likely(n < 0x100000000)) + return u32toa(buf, n); + + size_t len; + if (n >= TEN_POW_7) { + uint64_t n1 = n / TEN_POW_7; + n %= TEN_POW_7; + if (n1 >= TEN_POW_7) { + uint32_t quo = n1 / TEN_POW_7; + n1 %= TEN_POW_7; + len = u7toa_shift(buf, quo); + len = u07toa_shift(buf, n1, len); + } else { + len = u7toa_shift(buf, n1); + } + return u07toa_shift(buf, n, len); + } + return u7toa_shift(buf, n); +} + +size_t i32toa(char buf[minimum_length(12)], int32_t n) +{ + if (likely(n >= 0)) + return u32toa(buf, n); + + buf[0] = '-'; + return 1 + u32toa(buf + 1, -(uint32_t)n); +} + +size_t i64toa(char buf[minimum_length(22)], int64_t n) +{ + if (likely(n >= 0)) + return u64toa(buf, n); + + buf[0] = '-'; + return 1 + u64toa(buf + 1, -(uint64_t)n); +} + +/* using u32toa_radix_length variant */ + +static uint8_t const radix_shift[64] = { + 0, 0, 1, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned base) +{ + int shift; + +#ifdef USE_SPECIAL_RADIX_10 + if (likely(base == 10)) + return u32toa(buf, n); +#endif + if (n < base) { + buf[0] = digits36[n]; + buf[1] = '\0'; + return 1; + } + shift = radix_shift[base & 63]; + if (shift) { + uint32_t mask = (1 << shift) - 1; + size_t len = (32 - clz32(n) + shift - 1) / shift; + size_t last = n & mask; + char *end = buf + len; + n >>= shift; + *end-- = '\0'; + *end-- = digits36[last]; + while (n >= base) { + size_t quo = n & mask; + n >>= shift; + *end-- = digits36[quo]; + } + *end = digits36[n]; + return len; + } else { + size_t len = 2; + size_t last = n % base; + n /= base; + uint32_t nbase = base; + while (n >= nbase) { + nbase *= base; + len++; + } + char *end = buf + len; + *end-- = '\0'; + *end-- = digits36[last]; + while (n >= base) { + size_t quo = n % base; + n /= base; + *end-- = digits36[quo]; + } + *end = digits36[n]; + return len; + } +} + +size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned base) +{ + int shift; + +#ifdef USE_SPECIAL_RADIX_10 + if (likely(base == 10)) + return u64toa(buf, n); +#endif + shift = radix_shift[base & 63]; + if (shift) { + if (n < base) { + buf[0] = digits36[n]; + buf[1] = '\0'; + return 1; + } + uint64_t mask = (1 << shift) - 1; + size_t len = (64 - clz64(n) + shift - 1) / shift; + size_t last = n & mask; + char *end = buf + len; + n >>= shift; + *end-- = '\0'; + *end-- = digits36[last]; + while (n >= base) { + size_t quo = n & mask; + n >>= shift; + *end-- = digits36[quo]; + } + *end = digits36[n]; + return len; + } else { + if (likely(n < 0x100000000)) + return u32toa_radix(buf, n, base); + size_t last = n % base; + n /= base; + uint64_t nbase = base; + size_t len = 2; + while (n >= nbase) { + nbase *= base; + len++; + } + char *end = buf + len; + *end-- = '\0'; + *end-- = digits36[last]; + while (n >= base) { + size_t quo = n % base; + n /= base; + *end-- = digits36[quo]; + } + *end = digits36[n]; + return len; + } +} + +size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned int base) +{ + if (likely(n >= 0)) + return u32toa_radix(buf, n, base); + + buf[0] = '-'; + return 1 + u32toa_radix(buf + 1, -(uint32_t)n, base); +} + +size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base) +{ + if (likely(n >= 0)) + return u64toa_radix(buf, n, base); + + buf[0] = '-'; + return 1 + u64toa_radix(buf + 1, -(uint64_t)n, base); +} + +#undef gen_digit +#undef TEN_POW_7 +#undef USE_SPECIAL_RADIX_10 +#undef USE_SINGLE_CASE_FAST + +/*---- sorting with opaque argument ----*/ + +typedef void (*exchange_f)(void *a, void *b, size_t size); +typedef int (*cmp_f)(const void *, const void *, void *opaque); + +static void exchange_bytes(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + + while (size-- != 0) { + uint8_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_byte(void *a, void *b, size_t size) { + uint8_t *ap = (uint8_t *)a; + uint8_t *bp = (uint8_t *)b; + uint8_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int16s(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + + for (size /= sizeof(uint16_t); size-- != 0;) { + uint16_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int16(void *a, void *b, size_t size) { + uint16_t *ap = (uint16_t *)a; + uint16_t *bp = (uint16_t *)b; + uint16_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int32s(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + + for (size /= sizeof(uint32_t); size-- != 0;) { + uint32_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int32(void *a, void *b, size_t size) { + uint32_t *ap = (uint32_t *)a; + uint32_t *bp = (uint32_t *)b; + uint32_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int64s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t); size-- != 0;) { + uint64_t t = *ap; + *ap++ = *bp; + *bp++ = t; + } +} + +static void exchange_one_int64(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = *ap; + *ap = *bp; + *bp = t; +} + +static void exchange_int128s(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + + for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) { + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; + } +} + +static void exchange_one_int128(void *a, void *b, size_t size) { + uint64_t *ap = (uint64_t *)a; + uint64_t *bp = (uint64_t *)b; + uint64_t t = ap[0]; + uint64_t u = ap[1]; + ap[0] = bp[0]; + ap[1] = bp[1]; + bp[0] = t; + bp[1] = u; +} + +static inline exchange_f exchange_func(const void *base, size_t size) { + switch (((uintptr_t)base | (uintptr_t)size) & 15) { + case 0: + if (size == sizeof(uint64_t) * 2) + return exchange_one_int128; + else + return exchange_int128s; + case 8: + if (size == sizeof(uint64_t)) + return exchange_one_int64; + else + return exchange_int64s; + case 4: + case 12: + if (size == sizeof(uint32_t)) + return exchange_one_int32; + else + return exchange_int32s; + case 2: + case 6: + case 10: + case 14: + if (size == sizeof(uint16_t)) + return exchange_one_int16; + else + return exchange_int16s; + default: + if (size == 1) + return exchange_one_byte; + else + return exchange_bytes; + } +} + +static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + uint8_t *basep = (uint8_t *)base; + size_t i, n, c, r; + exchange_f swap = exchange_func(base, size); + + 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(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + for (i = n - size; i > 0; i -= size) { + swap(basep, basep + i, size); + + for (r = 0; (c = r * 2 + size) < i; r = c) { + if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0) + c += size; + if (cmp(basep + r, basep + c, opaque) > 0) + break; + swap(basep + r, basep + c, size); + } + } + } +} + +static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque) +{ + return cmp(a, b, opaque) < 0 ? + (cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) : + (cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c )); +} + +/* pointer based version with local stack and insertion sort threshhold */ +void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque) +{ + struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack; + uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m; + size_t m4, i, lt, gt, span, span2; + int c, depth; + exchange_f swap = exchange_func(base, size); + exchange_f swap_block = exchange_func(base, size | 128); + + if (nmemb < 2 || size <= 0) + return; + + sp->base = (uint8_t *)base; + sp->count = nmemb; + sp->depth = 0; + sp++; + + while (sp > stack) { + sp--; + ptr = sp->base; + nmemb = sp->count; + depth = sp->depth; + + while (nmemb > 6) { + if (++depth > 50) { + /* depth check to ensure worst case logarithmic time */ + heapsortx(ptr, nmemb, size, cmp, opaque); + nmemb = 0; + break; + } + /* select median of 3 from 1/4, 1/2, 3/4 positions */ + /* should use median of 5 or 9? */ + m4 = (nmemb >> 2) * size; + m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque); + swap(ptr, m, size); /* move the pivot to the start or the array */ + i = lt = 1; + pi = plt = ptr + size; + gt = nmemb; + pj = pgt = top = ptr + nmemb * size; + for (;;) { + while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) { + if (c == 0) { + swap(plt, pi, size); + lt++; + plt += size; + } + i++; + pi += size; + } + while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) { + if (c == 0) { + gt--; + pgt -= size; + swap(pgt, pj, size); + } + } + if (pi >= pj) + break; + swap(pi, pj, size); + i++; + pi += size; + } + /* array has 4 parts: + * from 0 to lt excluded: elements identical to pivot + * from lt to pi excluded: elements smaller than pivot + * from pi to gt excluded: elements greater than pivot + * from gt to n excluded: elements identical to pivot + */ + /* move elements identical to pivot in the middle of the array: */ + /* swap values in ranges [0..lt[ and [i-lt..i[ + swapping the smallest span between lt and i-lt is sufficient + */ + span = plt - ptr; + span2 = pi - plt; + lt = i - lt; + if (span > span2) + span = span2; + swap_block(ptr, pi - span, span); + /* swap values in ranges [gt..top[ and [i..top-(top-gt)[ + swapping the smallest span between top-gt and gt-i is sufficient + */ + span = top - pgt; + span2 = pgt - pi; + pgt = top - span2; + gt = nmemb - (gt - i); + if (span > span2) + span = span2; + swap_block(pi, top - span, span); + + /* now array has 3 parts: + * from 0 to lt excluded: elements smaller than pivot + * from lt to gt excluded: elements identical to pivot + * from gt to n excluded: elements greater than pivot + */ + /* stack the larger segment and keep processing the smaller one + to minimize stack use for pathological distributions */ + if (lt > nmemb - gt) { + sp->base = ptr; + sp->count = lt; + sp->depth = depth; + sp++; + ptr = pgt; + nmemb -= gt; + } else { + sp->base = pgt; + sp->count = nmemb - gt; + sp->depth = depth; + sp++; + nmemb = lt; + } + } + /* Use insertion sort for small fragments */ + for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) { + for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size) + swap(pj, pj - size, size); + } + } +} + +/*---- Portable time functions ----*/ + +#ifdef _WIN32 + // From: https://stackoverflow.com/a/26085827 +static int gettimeofday_msvc(struct timeval *tp) +{ + static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t)file_time.dwLowDateTime); + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long)((time - EPOCH) / 10000000L); + tp->tv_usec = (long)(system_time.wMilliseconds * 1000); + + return 0; +} + +uint64_t js__hrtime_ns(void) { + LARGE_INTEGER counter, frequency; + double scaled_freq; + double result; + + if (!QueryPerformanceFrequency(&frequency)) + abort(); + assert(frequency.QuadPart != 0); + + if (!QueryPerformanceCounter(&counter)) + abort(); + assert(counter.QuadPart != 0); + + /* Because we have no guarantee about the order of magnitude of the + * performance counter interval, integer math could cause this computation + * to overflow. Therefore we resort to floating point math. + */ + scaled_freq = (double) frequency.QuadPart / NANOSEC; + result = (double) counter.QuadPart / scaled_freq; + return (uint64_t) result; +} +#else +uint64_t js__hrtime_ns(void) { + struct timespec t; + + if (clock_gettime(CLOCK_MONOTONIC, &t)) + abort(); + + return t.tv_sec * NANOSEC + t.tv_nsec; +} +#endif + +int64_t js__gettimeofday_us(void) { + struct timeval tv; +#ifdef _WIN32 + gettimeofday_msvc(&tv); +#else + gettimeofday(&tv, NULL); +#endif + return ((int64_t)tv.tv_sec * 1000000) + tv.tv_usec; +} + +/*--- Cross-platform threading APIs. ----*/ + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) && !defined(MNC_NO_THREADS) + +#if defined(_WIN32) +typedef void (*js__once_cb)(void); + +typedef struct { + js__once_cb callback; +} js__once_data_t; + +static BOOL WINAPI js__once_inner(INIT_ONCE *once, void *param, void **context) { + js__once_data_t *data = param; + + data->callback(); + + return TRUE; +} + +void js_once(js_once_t *guard, js__once_cb callback) { + js__once_data_t data = { .callback = callback }; + InitOnceExecuteOnce(guard, js__once_inner, (void*) &data, NULL); +} + +void js_mutex_init(js_mutex_t *mutex) { + InitializeCriticalSection(mutex); +} + +void js_mutex_destroy(js_mutex_t *mutex) { + DeleteCriticalSection(mutex); +} + +void js_mutex_lock(js_mutex_t *mutex) { + EnterCriticalSection(mutex); +} + +void js_mutex_unlock(js_mutex_t *mutex) { + LeaveCriticalSection(mutex); +} + +void js_cond_init(js_cond_t *cond) { + InitializeConditionVariable(cond); +} + +void js_cond_destroy(js_cond_t *cond) { + /* nothing to do */ + (void) cond; +} + +void js_cond_signal(js_cond_t *cond) { + WakeConditionVariable(cond); +} + +void js_cond_broadcast(js_cond_t *cond) { + WakeAllConditionVariable(cond); +} + +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { + if (!SleepConditionVariableCS(cond, mutex, INFINITE)) + abort(); +} + +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + if (SleepConditionVariableCS(cond, mutex, (DWORD)(timeout / 1e6))) + return 0; + if (GetLastError() != ERROR_TIMEOUT) + abort(); + return -1; +} + +#else /* !defined(_WIN32) */ + +void js_once(js_once_t *guard, void (*callback)(void)) { + if (pthread_once(guard, callback)) + abort(); +} + +void js_mutex_init(js_mutex_t *mutex) { + if (pthread_mutex_init(mutex, NULL)) + abort(); +} + +void js_mutex_destroy(js_mutex_t *mutex) { + if (pthread_mutex_destroy(mutex)) + abort(); +} + +void js_mutex_lock(js_mutex_t *mutex) { + if (pthread_mutex_lock(mutex)) + abort(); +} + +void js_mutex_unlock(js_mutex_t *mutex) { + if (pthread_mutex_unlock(mutex)) + abort(); +} + +void js_cond_init(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + if (pthread_cond_init(cond, NULL)) + abort(); +#else + pthread_condattr_t attr; + + if (pthread_condattr_init(&attr)) + abort(); + + if (pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) + abort(); + + if (pthread_cond_init(cond, &attr)) + abort(); + + if (pthread_condattr_destroy(&attr)) + abort(); +#endif +} + +void js_cond_destroy(js_cond_t *cond) { +#if defined(__APPLE__) && defined(__MACH__) + /* It has been reported that destroying condition variables that have been + * signalled but not waited on can sometimes result in application crashes. + * See https://codereview.chromium.org/1323293005. + */ + pthread_mutex_t mutex; + struct timespec ts; + int err; + + if (pthread_mutex_init(&mutex, NULL)) + abort(); + + if (pthread_mutex_lock(&mutex)) + abort(); + + ts.tv_sec = 0; + ts.tv_nsec = 1; + + err = pthread_cond_timedwait_relative_np(cond, &mutex, &ts); + if (err != 0 && err != ETIMEDOUT) + abort(); + + if (pthread_mutex_unlock(&mutex)) + abort(); + + if (pthread_mutex_destroy(&mutex)) + abort(); +#endif /* defined(__APPLE__) && defined(__MACH__) */ + + if (pthread_cond_destroy(cond)) + abort(); +} + +void js_cond_signal(js_cond_t *cond) { + if (pthread_cond_signal(cond)) + abort(); +} + +void js_cond_broadcast(js_cond_t *cond) { + if (pthread_cond_broadcast(cond)) + abort(); +} + +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex) { +#if defined(__APPLE__) && defined(__MACH__) + int r; + + errno = 0; + r = pthread_cond_wait(cond, mutex); + + /* Workaround for a bug in OS X at least up to 13.6 + * See https://github.com/libuv/libuv/issues/4165 + */ + if (r == EINVAL && errno == EBUSY) + return; + if (r) + abort(); +#else + if (pthread_cond_wait(cond, mutex)) + abort(); +#endif +} + +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout) { + int r; + struct timespec ts; + +#if !defined(__APPLE__) + timeout += js__hrtime_ns(); +#endif + + ts.tv_sec = timeout / NANOSEC; + ts.tv_nsec = timeout % NANOSEC; +#if defined(__APPLE__) && defined(__MACH__) + r = pthread_cond_timedwait_relative_np(cond, mutex, &ts); +#else + r = pthread_cond_timedwait(cond, mutex, &ts); +#endif + + if (r == 0) + return 0; + + if (r == ETIMEDOUT) + return -1; + + abort(); + + /* Pacify some compilers. */ + return -1; +} + +#endif + +#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ + +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif diff --git a/lib/monoucha0/monoucha/qjs/cutils.h b/lib/monoucha0/monoucha/qjs/cutils.h new file mode 100644 index 00000000..907bd559 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/cutils.h @@ -0,0 +1,616 @@ +/* + * 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 <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) +#include <winsock2.h> +#include <malloc.h> +#define alloca _alloca +#define ssize_t ptrdiff_t +#endif +#if defined(__APPLE__) +#include <malloc/malloc.h> +#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) +#include <malloc.h> +#elif defined(__FreeBSD__) +#include <malloc_np.h> +#elif defined(_WIN32) +#include <windows.h> +#endif +#if !defined(_WIN32) && !defined(EMSCRIPTEN) && !defined(__wasi__) +#include <errno.h> +#include <pthread.h> +#endif + +#if defined(__SANITIZE_ADDRESS__) +# define __ASAN__ 1 +#elif defined(__has_feature) +# if __has_feature(address_sanitizer) +# define __ASAN__ 1 +# endif +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +# define likely(x) (x) +# define unlikely(x) (x) +# define force_inline __forceinline +# define no_inline __declspec(noinline) +# define __maybe_unused +# define __attribute__(x) +# define __attribute(x) +# include <intrin.h> +static void *__builtin_frame_address(unsigned int level) { + return (void *)((char*)_AddressOfReturnAddress() - sizeof(int *) - level * sizeof(int *)); +} +#else +# 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)) +#endif + +// https://stackoverflow.com/a/6849629 +#undef FORMAT_STRING +#if _MSC_VER >= 1400 +# include <sal.h> +# if _MSC_VER > 1400 +# define FORMAT_STRING(p) _Printf_format_string_ p +# else +# define FORMAT_STRING(p) __format_string p +# endif /* FORMAT_STRING */ +#else +# define FORMAT_STRING(p) p +#endif /* _MSC_VER */ + +#if defined(_MSC_VER) && !defined(__clang__) +#include <math.h> +#define INF INFINITY +#define NEG_INF -INFINITY +#else +#define INF (1.0/0.0) +#define NEG_INF (-1.0/0.0) +#endif + +#ifndef offsetof +#define offsetof(type, field) ((size_t) &((type *)0)->field) +#endif +#ifndef countof +#define countof(x) (sizeof(x) / sizeof((x)[0])) +#ifndef endof +#define endof(x) ((x) + countof(x)) +#endif +#endif +#ifndef container_of +/* return the pointer of type 'type *' containing 'ptr' as field 'member' */ +#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member))) +#endif + +#if defined(_MSC_VER) +#define minimum_length(n) n +#else +#define minimum_length(n) static n +#endif + +typedef int BOOL; + +#ifndef FALSE +enum { + FALSE = 0, + TRUE = 1, +}; +#endif + +void js__pstrcpy(char *buf, int buf_size, const char *str); +char *js__pstrcat(char *buf, int buf_size, const char *s); +int js__strstart(const char *str, const char *val, const char **ptr); +int js__has_suffix(const char *str, const char *suffix); + +static inline uint8_t is_be(void) { + union { + uint16_t a; + uint8_t b; + } u = { 0x100 }; + return u.b; +} + +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; +} + +/* WARNING: undefined if a = 0 */ +static inline int clz32(unsigned int a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanReverse(&index, a); + return 31 - index; +#else + return __builtin_clz(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int clz64(uint64_t a) +{ +#if defined(_MSC_VER) && !defined(__clang__) +#if INTPTR_MAX == INT64_MAX + unsigned long index; + _BitScanReverse64(&index, a); + return 63 - index; +#else + if (a >> 32) + return clz32((unsigned)(a >> 32)); + else + return clz32((unsigned)a) + 32; +#endif +#else + return __builtin_clzll(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz32(unsigned int a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanForward(&index, a); + return index; +#else + return __builtin_ctz(a); +#endif +} + +/* WARNING: undefined if a = 0 */ +static inline int ctz64(uint64_t a) +{ +#if defined(_MSC_VER) && !defined(__clang__) + unsigned long index; + _BitScanForward64(&index, a); + return index; +#else + return __builtin_ctzll(a); +#endif +} + +static inline uint64_t get_u64(const uint8_t *tab) +{ + uint64_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int64_t get_i64(const uint8_t *tab) +{ + int64_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u64(uint8_t *tab, uint64_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u32(const uint8_t *tab) +{ + uint32_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int32_t get_i32(const uint8_t *tab) +{ + int32_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u32(uint8_t *tab, uint32_t val) +{ + memcpy(tab, &val, sizeof(val)); +} + +static inline uint32_t get_u16(const uint8_t *tab) +{ + uint16_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline int32_t get_i16(const uint8_t *tab) +{ + int16_t v; + memcpy(&v, tab, sizeof(v)); + return v; +} + +static inline void put_u16(uint8_t *tab, uint16_t val) +{ + memcpy(tab, &val, sizeof(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; +} + +#ifndef bswap16 +static inline uint16_t bswap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} +#endif + +#ifndef bswap32 +static inline uint32_t bswap32(uint32_t v) +{ + return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) | + ((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24); +} +#endif + +#ifndef bswap64 +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)); +} +#endif + +static inline void inplace_bswap16(uint8_t *tab) { + put_u16(tab, bswap16(get_u16(tab))); +} + +static inline void inplace_bswap32(uint8_t *tab) { + put_u32(tab, bswap32(get_u32(tab))); +} + +static inline double fromfp16(uint16_t v) { + double d, s; + int e; + if ((v & 0x7C00) == 0x7C00) { + d = (v & 0x3FF) ? NAN : INFINITY; + } else { + d = (v & 0x3FF) / 1024.; + e = (v & 0x7C00) >> 10; + if (e == 0) { + e = -14; + } else { + d += 1; + e -= 15; + } + d = scalbn(d, e); + } + s = (v & 0x8000) ? -1.0 : 1.0; + return d * s; +} + +static inline uint16_t tofp16(double d) { + uint16_t f, s; + double t; + int e; + s = 0; + if (copysign(1, d) < 0) { // preserve sign when |d| is negative zero + d = -d; + s = 0x8000; + } + if (isinf(d)) + return s | 0x7C00; + if (isnan(d)) + return s | 0x7C01; + if (d == 0) + return s | 0; + d = 2 * frexp(d, &e); + e--; + if (e > 15) + return s | 0x7C00; // out of range, return +/-infinity + if (e < -25) { + d = 0; + e = 0; + } else if (e < -14) { + d = scalbn(d, e + 14); + e = 0; + } else { + d -= 1; + e += 15; + } + d *= 1024.; + f = (uint16_t)d; + t = d - f; + if (t < 0.5) + goto done; + if (t == 0.5) + if ((f & 1) == 0) + goto done; + // adjust for rounding + if (++f == 1024) { + f = 0; + if (++e == 31) + return s | 0x7C00; // out of range, return +/-infinity + } +done: + return s | (e << 10) | f; +} + +static inline int isfp16nan(uint16_t v) { + return (v & 0x7FFF) > 0x7C00; +} + +static inline int isfp16zero(uint16_t v) { + return (v & 0x7FFF) == 0; +} + +/* XXX: should take an extra argument to pass slack information to the caller */ +typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size); + +typedef struct DynBuf { + uint8_t *buf; + size_t size; + size_t allocated_size; + BOOL error; /* true if a memory allocation error occurred */ + DynBufReallocFunc *realloc_func; + void *opaque; /* for realloc_func */ +} DynBuf; + +void dbuf_init(DynBuf *s); +void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func); +int dbuf_realloc(DynBuf *s, size_t new_size); +int dbuf_write(DynBuf *s, size_t offset, const void *data, size_t len); +int dbuf_put(DynBuf *s, const void *data, size_t len); +int dbuf_put_self(DynBuf *s, size_t offset, size_t len); +int dbuf_putc(DynBuf *s, uint8_t c); +int dbuf_putstr(DynBuf *s, const char *str); +static inline int dbuf_put_u16(DynBuf *s, uint16_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 2); +} +static inline int dbuf_put_u32(DynBuf *s, uint32_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 4); +} +static inline int dbuf_put_u64(DynBuf *s, uint64_t val) +{ + return dbuf_put(s, (uint8_t *)&val, 8); +} +int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s, + FORMAT_STRING(const char *fmt), ...); +void dbuf_free(DynBuf *s); +static inline BOOL dbuf_error(DynBuf *s) { + return s->error; +} +static inline void dbuf_set_error(DynBuf *s) +{ + s->error = TRUE; +} + +/*---- UTF-8 and UTF-16 handling ----*/ + +#define UTF8_CHAR_LEN_MAX 4 + +enum { + UTF8_PLAIN_ASCII = 0, // 7-bit ASCII plain text + UTF8_NON_ASCII = 1, // has non ASCII code points (8-bit or more) + UTF8_HAS_16BIT = 2, // has 16-bit code points + UTF8_HAS_NON_BMP1 = 4, // has non-BMP1 code points, needs UTF-16 surrogate pairs + UTF8_HAS_ERRORS = 8, // has encoding errors +}; +int utf8_scan(const char *buf, size_t len, size_t *plen); +size_t utf8_encode_len(uint32_t c); +size_t utf8_encode(uint8_t buf[minimum_length(UTF8_CHAR_LEN_MAX)], uint32_t c); +uint32_t utf8_decode_len(const uint8_t *p, size_t max_len, const uint8_t **pp); +uint32_t utf8_decode(const uint8_t *p, const uint8_t **pp); +size_t utf8_decode_buf8(uint8_t *dest, size_t dest_len, const char *src, size_t src_len); +size_t utf8_decode_buf16(uint16_t *dest, size_t dest_len, const char *src, size_t src_len); +size_t utf8_encode_buf8(char *dest, size_t dest_len, const uint8_t *src, size_t src_len); +size_t utf8_encode_buf16(char *dest, size_t dest_len, const uint16_t *src, size_t src_len); + +static inline BOOL is_surrogate(uint32_t c) +{ + return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF +} + +static inline BOOL is_hi_surrogate(uint32_t c) +{ + return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF +} + +static inline BOOL is_lo_surrogate(uint32_t c) +{ + return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF +} + +static inline uint32_t get_hi_surrogate(uint32_t c) +{ + return (c >> 10) - (0x10000 >> 10) + 0xD800; +} + +static inline uint32_t get_lo_surrogate(uint32_t c) +{ + return (c & 0x3FF) | 0xDC00; +} + +static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo) +{ + return 65536 + 1024 * (hi & 1023) + (lo & 1023); +} + +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 uint8_t is_upper_ascii(uint8_t c) { + return c >= 'A' && c <= 'Z'; +} + +static inline uint8_t to_upper_ascii(uint8_t c) { + return c >= 'a' && c <= 'z' ? c - 'a' + 'A' : c; +} + +extern char const digits36[36]; +size_t u32toa(char buf[minimum_length(11)], uint32_t n); +size_t i32toa(char buf[minimum_length(12)], int32_t n); +size_t u64toa(char buf[minimum_length(21)], uint64_t n); +size_t i64toa(char buf[minimum_length(22)], int64_t n); +size_t u32toa_radix(char buf[minimum_length(33)], uint32_t n, unsigned int base); +size_t i32toa_radix(char buf[minimum_length(34)], int32_t n, unsigned base); +size_t u64toa_radix(char buf[minimum_length(65)], uint64_t n, unsigned int base); +size_t i64toa_radix(char buf[minimum_length(66)], int64_t n, unsigned int base); + +void rqsort(void *base, size_t nmemb, size_t size, + int (*cmp)(const void *, const void *, void *), + void *arg); + +int64_t js__gettimeofday_us(void); +uint64_t js__hrtime_ns(void); + +static inline size_t js__malloc_usable_size(const void *ptr) +{ +#if defined(__APPLE__) + return malloc_size(ptr); +#elif defined(_WIN32) + return _msize((void *)ptr); +#elif defined(__linux__) || defined(__ANDROID__) || defined(__CYGWIN__) || defined(__FreeBSD__) + return malloc_usable_size((void *)ptr); +#else + return 0; +#endif +} + +/* Cross-platform threading APIs. */ + +#if !defined(EMSCRIPTEN) && !defined(__wasi__) && !defined(MNC_NO_THREADS) + +#if defined(_WIN32) +#define JS_ONCE_INIT INIT_ONCE_STATIC_INIT +typedef INIT_ONCE js_once_t; +typedef CRITICAL_SECTION js_mutex_t; +typedef CONDITION_VARIABLE js_cond_t; +#else +#define JS_ONCE_INIT PTHREAD_ONCE_INIT +typedef pthread_once_t js_once_t; +typedef pthread_mutex_t js_mutex_t; +typedef pthread_cond_t js_cond_t; +#endif + +void js_once(js_once_t *guard, void (*callback)(void)); + +void js_mutex_init(js_mutex_t *mutex); +void js_mutex_destroy(js_mutex_t *mutex); +void js_mutex_lock(js_mutex_t *mutex); +void js_mutex_unlock(js_mutex_t *mutex); + +void js_cond_init(js_cond_t *cond); +void js_cond_destroy(js_cond_t *cond); +void js_cond_signal(js_cond_t *cond); +void js_cond_broadcast(js_cond_t *cond); +void js_cond_wait(js_cond_t *cond, js_mutex_t *mutex); +int js_cond_timedwait(js_cond_t *cond, js_mutex_t *mutex, uint64_t timeout); + +#endif /* !defined(EMSCRIPTEN) && !defined(__wasi__) */ + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* CUTILS_H */ diff --git a/lib/monoucha0/monoucha/qjs/libbf.c b/lib/monoucha0/monoucha/qjs/libbf.c new file mode 100644 index 00000000..6293f4ea --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libbf.c @@ -0,0 +1,8424 @@ +/* + * Tiny arbitrary precision floating point library + * + * Copyright (c) 2017-2021 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 <stdlib.h> +#include <stdio.h> +#include <inttypes.h> +#include <math.h> +#include <string.h> +#include <assert.h> + +#ifdef __AVX2__ +#include <immintrin.h> +#endif + +#include "cutils.h" +#include "libbf.h" + +/* enable it to check the multiplication result */ +//#define USE_MUL_CHECK +/* enable it to use FFT/NTT multiplication */ +#define USE_FFT_MUL +/* enable decimal floating point support */ +#define USE_BF_DEC + +//#define inline __attribute__((always_inline)) + +#ifdef __AVX2__ +#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */ +#else +#define FFT_MUL_THRESHOLD 100 /* in limbs of the smallest factor */ +#endif + +/* XXX: adjust */ +#define DIVNORM_LARGE_THRESHOLD 50 +#define UDIV1NORM_THRESHOLD 3 + +#if LIMB_BITS == 64 +#define FMT_LIMB1 "%" PRIx64 +#define FMT_LIMB "%016" PRIx64 +#define PRId_LIMB PRId64 +#define PRIu_LIMB PRIu64 + +#else + +#define FMT_LIMB1 "%x" +#define FMT_LIMB "%08x" +#define PRId_LIMB "d" +#define PRIu_LIMB "u" + +#endif + +typedef intptr_t mp_size_t; + +typedef int bf_op2_func_t(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags); + +#ifdef USE_FFT_MUL + +#define FFT_MUL_R_OVERLAP_A (1 << 0) +#define FFT_MUL_R_OVERLAP_B (1 << 1) +#define FFT_MUL_R_NORESIZE (1 << 2) + +static no_inline int fft_mul(bf_context_t *s, + bf_t *res, limb_t *a_tab, limb_t a_len, + limb_t *b_tab, limb_t b_len, int mul_flags); +static void fft_clear_cache(bf_context_t *s); +#endif +#ifdef USE_BF_DEC +static limb_t get_digit(const limb_t *tab, limb_t len, slimb_t pos); +#endif + + +/* could leading zeros */ +static inline int clz(limb_t a) +{ + if (a == 0) { + return LIMB_BITS; + } else { +#if LIMB_BITS == 64 + return clz64(a); +#else + return clz32(a); +#endif + } +} + +static inline int ctz(limb_t a) +{ + if (a == 0) { + return LIMB_BITS; + } else { +#if LIMB_BITS == 64 + return ctz64(a); +#else + return ctz32(a); +#endif + } +} + +static inline int ceil_log2(limb_t a) +{ + if (a <= 1) + return 0; + else + return LIMB_BITS - clz(a - 1); +} + +/* b must be >= 1 */ +static inline slimb_t ceil_div(slimb_t a, slimb_t b) +{ + if (a >= 0) + return (a + b - 1) / b; + else + return a / b; +} + +/* b must be >= 1 */ +static inline slimb_t floor_div(slimb_t a, slimb_t b) +{ + if (a >= 0) { + return a / b; + } else { + return (a - b + 1) / b; + } +} + +/* return r = a modulo b (0 <= r <= b - 1. b must be >= 1 */ +static inline limb_t smod(slimb_t a, slimb_t b) +{ + a = a % (slimb_t)b; + if (a < 0) + a += b; + return a; +} + +/* signed addition with saturation */ +static inline slimb_t sat_add(slimb_t a, slimb_t b) +{ + slimb_t r; + r = a + b; + /* overflow ? */ + if (((a ^ r) & (b ^ r)) < 0) + r = (a >> (LIMB_BITS - 1)) ^ (((limb_t)1 << (LIMB_BITS - 1)) - 1); + return r; +} + +static inline __maybe_unused limb_t shrd(limb_t low, limb_t high, long shift) +{ + if (shift != 0) + low = (low >> shift) | (high << (LIMB_BITS - shift)); + return low; +} + +static inline __maybe_unused limb_t shld(limb_t a1, limb_t a0, long shift) +{ + if (shift != 0) + return (a1 << shift) | (a0 >> (LIMB_BITS - shift)); + else + return a1; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p, s) realloc_is_forbidden(p, s) + +void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, + void *realloc_opaque) +{ + memset(s, 0, sizeof(*s)); + s->realloc_func = realloc_func; + s->realloc_opaque = realloc_opaque; +} + +void bf_context_end(bf_context_t *s) +{ + bf_clear_cache(s); +} + +void bf_init(bf_context_t *s, bf_t *r) +{ + r->ctx = s; + r->sign = 0; + r->expn = BF_EXP_ZERO; + r->len = 0; + r->tab = NULL; +} + +/* return 0 if OK, -1 if alloc error */ +int bf_resize(bf_t *r, limb_t len) +{ + limb_t *tab; + + if (len != r->len) { + tab = bf_realloc(r->ctx, r->tab, len * sizeof(limb_t)); + if (!tab && len != 0) + return -1; + r->tab = tab; + r->len = len; + } + return 0; +} + +/* return 0 or BF_ST_MEM_ERROR */ +int bf_set_ui(bf_t *r, uint64_t a) +{ + r->sign = 0; + if (a == 0) { + r->expn = BF_EXP_ZERO; + bf_resize(r, 0); /* cannot fail */ + } +#if LIMB_BITS == 32 + else if (a <= 0xffffffff) +#else + else +#endif + { + int shift; + if (bf_resize(r, 1)) + goto fail; + shift = clz(a); + r->tab[0] = a << shift; + r->expn = LIMB_BITS - shift; + } +#if LIMB_BITS == 32 + else { + uint32_t a1, a0; + int shift; + if (bf_resize(r, 2)) + goto fail; + a0 = a; + a1 = a >> 32; + shift = clz(a1); + r->tab[0] = a0 << shift; + r->tab[1] = shld(a1, a0, shift); + r->expn = 2 * LIMB_BITS - shift; + } +#endif + return 0; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +/* return 0 or BF_ST_MEM_ERROR */ +int bf_set_si(bf_t *r, int64_t a) +{ + int ret; + + if (a < 0) { + ret = bf_set_ui(r, -a); + r->sign = 1; + } else { + ret = bf_set_ui(r, a); + } + return ret; +} + +void bf_set_nan(bf_t *r) +{ + bf_resize(r, 0); /* cannot fail */ + r->expn = BF_EXP_NAN; + r->sign = 0; +} + +void bf_set_zero(bf_t *r, int is_neg) +{ + bf_resize(r, 0); /* cannot fail */ + r->expn = BF_EXP_ZERO; + r->sign = is_neg; +} + +void bf_set_inf(bf_t *r, int is_neg) +{ + bf_resize(r, 0); /* cannot fail */ + r->expn = BF_EXP_INF; + r->sign = is_neg; +} + +/* return 0 or BF_ST_MEM_ERROR */ +int bf_set(bf_t *r, const bf_t *a) +{ + if (r == a) + return 0; + if (bf_resize(r, a->len)) { + bf_set_nan(r); + return BF_ST_MEM_ERROR; + } + r->sign = a->sign; + r->expn = a->expn; + if (a->len > 0) + memcpy(r->tab, a->tab, a->len * sizeof(limb_t)); + return 0; +} + +/* equivalent to bf_set(r, a); bf_delete(a) */ +void bf_move(bf_t *r, bf_t *a) +{ + bf_context_t *s = r->ctx; + if (r == a) + return; + bf_free(s, r->tab); + *r = *a; +} + +static limb_t get_limbz(const bf_t *a, limb_t idx) +{ + if (idx >= a->len) + return 0; + else + return a->tab[idx]; +} + +/* get LIMB_BITS at bit position 'pos' in tab */ +static inline limb_t get_bits(const limb_t *tab, limb_t len, slimb_t pos) +{ + limb_t i, a0, a1; + int p; + + i = pos >> LIMB_LOG2_BITS; + p = pos & (LIMB_BITS - 1); + if (i < len) + a0 = tab[i]; + else + a0 = 0; + if (p == 0) { + return a0; + } else { + i++; + if (i < len) + a1 = tab[i]; + else + a1 = 0; + return (a0 >> p) | (a1 << (LIMB_BITS - p)); + } +} + +static inline limb_t get_bit(const limb_t *tab, limb_t len, slimb_t pos) +{ + slimb_t i; + i = pos >> LIMB_LOG2_BITS; + if (i < 0 || i >= len) + return 0; + return (tab[i] >> (pos & (LIMB_BITS - 1))) & 1; +} + +static inline limb_t limb_mask(int start, int last) +{ + limb_t v; + int n; + n = last - start + 1; + if (n == LIMB_BITS) + v = -1; + else + v = (((limb_t)1 << n) - 1) << start; + return v; +} + +static limb_t mp_scan_nz(const limb_t *tab, mp_size_t n) +{ + mp_size_t i; + for(i = 0; i < n; i++) { + if (tab[i] != 0) + return 1; + } + return 0; +} + +/* return != 0 if one bit between 0 and bit_pos inclusive is not zero. */ +static inline limb_t scan_bit_nz(const bf_t *r, slimb_t bit_pos) +{ + slimb_t pos; + limb_t v; + + pos = bit_pos >> LIMB_LOG2_BITS; + if (pos < 0) + return 0; + v = r->tab[pos] & limb_mask(0, bit_pos & (LIMB_BITS - 1)); + if (v != 0) + return 1; + pos--; + while (pos >= 0) { + if (r->tab[pos] != 0) + return 1; + pos--; + } + return 0; +} + +/* return the addend for rounding. Note that prec can be <= 0 (for + BF_FLAG_RADPNT_PREC) */ +static int bf_get_rnd_add(int *pret, const bf_t *r, limb_t l, + slimb_t prec, int rnd_mode) +{ + int add_one, inexact; + limb_t bit1, bit0; + + if (rnd_mode == BF_RNDF) { + bit0 = 1; /* faithful rounding does not honor the INEXACT flag */ + } else { + /* starting limb for bit 'prec + 1' */ + bit0 = scan_bit_nz(r, l * LIMB_BITS - 1 - bf_max(0, prec + 1)); + } + + /* get the bit at 'prec' */ + bit1 = get_bit(r->tab, l, l * LIMB_BITS - 1 - prec); + inexact = (bit1 | bit0) != 0; + + add_one = 0; + switch(rnd_mode) { + case BF_RNDZ: + break; + case BF_RNDN: + if (bit1) { + if (bit0) { + add_one = 1; + } else { + /* round to even */ + add_one = + get_bit(r->tab, l, l * LIMB_BITS - 1 - (prec - 1)); + } + } + break; + case BF_RNDD: + case BF_RNDU: + if (r->sign == (rnd_mode == BF_RNDD)) + add_one = inexact; + break; + case BF_RNDA: + add_one = inexact; + break; + case BF_RNDNA: + case BF_RNDF: + add_one = bit1; + break; + default: + abort(); + } + + if (inexact) + *pret |= BF_ST_INEXACT; + return add_one; +} + +static int bf_set_overflow(bf_t *r, int sign, limb_t prec, bf_flags_t flags) +{ + slimb_t i, l, e_max; + int rnd_mode; + + rnd_mode = flags & BF_RND_MASK; + if (prec == BF_PREC_INF || + rnd_mode == BF_RNDN || + rnd_mode == BF_RNDNA || + rnd_mode == BF_RNDA || + (rnd_mode == BF_RNDD && sign == 1) || + (rnd_mode == BF_RNDU && sign == 0)) { + bf_set_inf(r, sign); + } else { + /* set to maximum finite number */ + l = (prec + LIMB_BITS - 1) / LIMB_BITS; + if (bf_resize(r, l)) { + bf_set_nan(r); + return BF_ST_MEM_ERROR; + } + r->tab[0] = limb_mask((-prec) & (LIMB_BITS - 1), + LIMB_BITS - 1); + for(i = 1; i < l; i++) + r->tab[i] = (limb_t)-1; + e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + r->expn = e_max; + r->sign = sign; + } + return BF_ST_OVERFLOW | BF_ST_INEXACT; +} + +/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is + assumed to have length 'l' (1 <= l <= r->len). Note: 'prec1' can be + infinite (BF_PREC_INF). 'ret' is 0 or BF_ST_INEXACT if the result + is known to be inexact. Can fail with BF_ST_MEM_ERROR in case of + overflow not returning infinity. */ +static int __bf_round(bf_t *r, limb_t prec1, bf_flags_t flags, limb_t l, + int ret) +{ + limb_t v, a; + int shift, add_one, rnd_mode; + slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec; + + /* e_min and e_max are computed to match the IEEE 754 conventions */ + e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + e_min = -e_range + 3; + e_max = e_range; + + if (flags & BF_FLAG_RADPNT_PREC) { + /* 'prec' is the precision after the radix point */ + if (prec1 != BF_PREC_INF) + prec = r->expn + prec1; + else + prec = prec1; + } else if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) { + /* restrict the precision in case of potentially subnormal + result */ + assert(prec1 != BF_PREC_INF); + prec = prec1 - (e_min - r->expn); + } else { + prec = prec1; + } + + /* round to prec bits */ + rnd_mode = flags & BF_RND_MASK; + add_one = bf_get_rnd_add(&ret, r, l, prec, rnd_mode); + + if (prec <= 0) { + if (add_one) { + bf_resize(r, 1); /* cannot fail */ + r->tab[0] = (limb_t)1 << (LIMB_BITS - 1); + r->expn += 1 - prec; + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + return ret; + } else { + goto underflow; + } + } else if (add_one) { + limb_t carry; + + /* add one starting at digit 'prec - 1' */ + bit_pos = l * LIMB_BITS - 1 - (prec - 1); + pos = bit_pos >> LIMB_LOG2_BITS; + carry = (limb_t)1 << (bit_pos & (LIMB_BITS - 1)); + + for(i = pos; i < l; i++) { + v = r->tab[i] + carry; + carry = (v < carry); + r->tab[i] = v; + if (carry == 0) + break; + } + if (carry) { + /* shift right by one digit */ + v = 1; + for(i = l - 1; i >= pos; i--) { + a = r->tab[i]; + r->tab[i] = (a >> 1) | (v << (LIMB_BITS - 1)); + v = a; + } + r->expn++; + } + } + + /* check underflow */ + if (unlikely(r->expn < e_min)) { + if (flags & BF_FLAG_SUBNORMAL) { + /* if inexact, also set the underflow flag */ + if (ret & BF_ST_INEXACT) + ret |= BF_ST_UNDERFLOW; + } else { + underflow: + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + bf_set_zero(r, r->sign); + return ret; + } + } + + /* check overflow */ + if (unlikely(r->expn > e_max)) + return bf_set_overflow(r, r->sign, prec1, flags); + + /* keep the bits starting at 'prec - 1' */ + bit_pos = l * LIMB_BITS - 1 - (prec - 1); + i = bit_pos >> LIMB_LOG2_BITS; + if (i >= 0) { + shift = bit_pos & (LIMB_BITS - 1); + if (shift != 0) + r->tab[i] &= limb_mask(shift, LIMB_BITS - 1); + } else { + i = 0; + } + /* remove trailing zeros */ + while (r->tab[i] == 0) + i++; + if (i > 0) { + l -= i; + memmove(r->tab, r->tab + i, l * sizeof(limb_t)); + } + bf_resize(r, l); /* cannot fail */ + return ret; +} + +/* 'r' must be a finite number. */ +int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags) +{ + limb_t l, v, a; + int shift, ret; + slimb_t i; + + // bf_print_str("bf_renorm", r); + l = r->len; + while (l > 0 && r->tab[l - 1] == 0) + l--; + if (l == 0) { + /* zero */ + r->expn = BF_EXP_ZERO; + bf_resize(r, 0); /* cannot fail */ + ret = 0; + } else { + r->expn -= (r->len - l) * LIMB_BITS; + /* shift to have the MSB set to '1' */ + v = r->tab[l - 1]; + shift = clz(v); + if (shift != 0) { + v = 0; + for(i = 0; i < l; i++) { + a = r->tab[i]; + r->tab[i] = (a << shift) | (v >> (LIMB_BITS - shift)); + v = a; + } + r->expn -= shift; + } + ret = __bf_round(r, prec1, flags, l, 0); + } + // bf_print_str("r_final", r); + return ret; +} + +/* return true if rounding can be done at precision 'prec' assuming + the exact result r is such that |r-a| <= 2^(EXP(a)-k). */ +/* XXX: check the case where the exponent would be incremented by the + rounding */ +int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k) +{ + BOOL is_rndn; + slimb_t bit_pos, n; + limb_t bit; + + if (a->expn == BF_EXP_INF || a->expn == BF_EXP_NAN) + return FALSE; + if (rnd_mode == BF_RNDF) { + return (k >= (prec + 1)); + } + if (a->expn == BF_EXP_ZERO) + return FALSE; + is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); + if (k < (prec + 2)) + return FALSE; + bit_pos = a->len * LIMB_BITS - 1 - prec; + n = k - prec; + /* bit pattern for RNDN or RNDNA: 0111.. or 1000... + for other rounding modes: 000... or 111... + */ + bit = get_bit(a->tab, a->len, bit_pos); + bit_pos--; + n--; + bit ^= is_rndn; + /* XXX: slow, but a few iterations on average */ + while (n != 0) { + if (get_bit(a->tab, a->len, bit_pos) != bit) + return TRUE; + bit_pos--; + n--; + } + return FALSE; +} + +/* Cannot fail with BF_ST_MEM_ERROR. */ +int bf_round(bf_t *r, limb_t prec, bf_flags_t flags) +{ + if (r->len == 0) + return 0; + return __bf_round(r, prec, flags, r->len, 0); +} + +/* for debugging */ +static __maybe_unused void dump_limbs(const char *str, const limb_t *tab, limb_t n) +{ + limb_t i; + printf("%s: len=%" PRId_LIMB "\n", str, n); + for(i = 0; i < n; i++) { + printf("%" PRId_LIMB ": " FMT_LIMB "\n", + i, tab[i]); + } +} + +void mp_print_str(const char *str, const limb_t *tab, limb_t n) +{ + slimb_t i; + printf("%s= 0x", str); + for(i = n - 1; i >= 0; i--) { + if (i != (n - 1)) + printf("_"); + printf(FMT_LIMB, tab[i]); + } + printf("\n"); +} + +static __maybe_unused void mp_print_str_h(const char *str, + const limb_t *tab, limb_t n, + limb_t high) +{ + slimb_t i; + printf("%s= 0x", str); + printf(FMT_LIMB, high); + for(i = n - 1; i >= 0; i--) { + printf("_"); + printf(FMT_LIMB, tab[i]); + } + printf("\n"); +} + +/* for debugging */ +void bf_print_str(const char *str, const bf_t *a) +{ + slimb_t i; + printf("%s=", str); + + if (a->expn == BF_EXP_NAN) { + printf("NaN"); + } else { + if (a->sign) + putchar('-'); + if (a->expn == BF_EXP_ZERO) { + putchar('0'); + } else if (a->expn == BF_EXP_INF) { + printf("Inf"); + } else { + printf("0x0."); + for(i = a->len - 1; i >= 0; i--) + printf(FMT_LIMB, a->tab[i]); + printf("p%" PRId_LIMB, a->expn); + } + } + printf("\n"); +} + +/* compare the absolute value of 'a' and 'b'. Return < 0 if a < b, 0 + if a = b and > 0 otherwise. */ +int bf_cmpu(const bf_t *a, const bf_t *b) +{ + slimb_t i; + limb_t len, v1, v2; + + if (a->expn != b->expn) { + if (a->expn < b->expn) + return -1; + else + return 1; + } + len = bf_max(a->len, b->len); + for(i = len - 1; i >= 0; i--) { + v1 = get_limbz(a, a->len - len + i); + v2 = get_limbz(b, b->len - len + i); + if (v1 != v2) { + if (v1 < v2) + return -1; + else + return 1; + } + } + return 0; +} + +/* Full order: -0 < 0, NaN == NaN and NaN is larger than all other numbers */ +int bf_cmp_full(const bf_t *a, const bf_t *b) +{ + int res; + + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + if (a->expn == b->expn) + res = 0; + else if (a->expn == BF_EXP_NAN) + res = 1; + else + res = -1; + } else if (a->sign != b->sign) { + res = 1 - 2 * a->sign; + } else { + res = bf_cmpu(a, b); + if (a->sign) + res = -res; + } + return res; +} + +/* Standard floating point comparison: return 2 if one of the operands + is NaN (unordered) or -1, 0, 1 depending on the ordering assuming + -0 == +0 */ +int bf_cmp(const bf_t *a, const bf_t *b) +{ + int res; + + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + res = 2; + } else if (a->sign != b->sign) { + if (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_ZERO) + res = 0; + else + res = 1 - 2 * a->sign; + } else { + res = bf_cmpu(a, b); + if (a->sign) + res = -res; + } + return res; +} + +/* Compute the number of bits 'n' matching the pattern: + a= X1000..0 + b= X0111..1 + + When computing a-b, the result will have at least n leading zero + bits. + + Precondition: a > b and a.expn - b.expn = 0 or 1 +*/ +static limb_t count_cancelled_bits(const bf_t *a, const bf_t *b) +{ + slimb_t bit_offset, b_offset, n; + int p, p1; + limb_t v1, v2, mask; + + bit_offset = a->len * LIMB_BITS - 1; + b_offset = (b->len - a->len) * LIMB_BITS - (LIMB_BITS - 1) + + a->expn - b->expn; + n = 0; + + /* first search the equals bits */ + for(;;) { + v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS); + v2 = get_bits(b->tab, b->len, bit_offset + b_offset); + // printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2); + if (v1 != v2) + break; + n += LIMB_BITS; + bit_offset -= LIMB_BITS; + } + /* find the position of the first different bit */ + p = clz(v1 ^ v2) + 1; + n += p; + /* then search for '0' in a and '1' in b */ + p = LIMB_BITS - p; + if (p > 0) { + /* search in the trailing p bits of v1 and v2 */ + mask = limb_mask(0, p - 1); + p1 = bf_min(clz(v1 & mask), clz((~v2) & mask)) - (LIMB_BITS - p); + n += p1; + if (p1 != p) + goto done; + } + bit_offset -= LIMB_BITS; + for(;;) { + v1 = get_limbz(a, bit_offset >> LIMB_LOG2_BITS); + v2 = get_bits(b->tab, b->len, bit_offset + b_offset); + // printf("v1=" FMT_LIMB " v2=" FMT_LIMB "\n", v1, v2); + if (v1 != 0 || v2 != -1) { + /* different: count the matching bits */ + p1 = bf_min(clz(v1), clz(~v2)); + n += p1; + break; + } + n += LIMB_BITS; + bit_offset -= LIMB_BITS; + } + done: + return n; +} + +static int bf_add_internal(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int b_neg) +{ + const bf_t *tmp; + int is_sub, ret, cmp_res, a_sign, b_sign; + + a_sign = a->sign; + b_sign = b->sign ^ b_neg; + is_sub = a_sign ^ b_sign; + cmp_res = bf_cmpu(a, b); + if (cmp_res < 0) { + tmp = a; + a = b; + b = tmp; + a_sign = b_sign; /* b_sign is never used later */ + } + /* abs(a) >= abs(b) */ + if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) { + /* zero result */ + bf_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD); + ret = 0; + } else if (a->len == 0 || b->len == 0) { + ret = 0; + if (a->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN) { + /* at least one operand is NaN */ + bf_set_nan(r); + } else if (b->expn == BF_EXP_INF && is_sub) { + /* infinities with different signs */ + bf_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bf_set_inf(r, a_sign); + } + } else { + /* at least one zero and not subtract */ + bf_set(r, a); + r->sign = a_sign; + goto renorm; + } + } else { + slimb_t d, a_offset, b_bit_offset, i, cancelled_bits; + limb_t carry, v1, v2, u, r_len, carry1, precl, tot_len, z, sub_mask; + + r->sign = a_sign; + r->expn = a->expn; + d = a->expn - b->expn; + /* must add more precision for the leading cancelled bits in + subtraction */ + if (is_sub) { + if (d <= 1) + cancelled_bits = count_cancelled_bits(a, b); + else + cancelled_bits = 1; + } else { + cancelled_bits = 0; + } + + /* add two extra bits for rounding */ + precl = (cancelled_bits + prec + 2 + LIMB_BITS - 1) / LIMB_BITS; + tot_len = bf_max(a->len, b->len + (d + LIMB_BITS - 1) / LIMB_BITS); + r_len = bf_min(precl, tot_len); + if (bf_resize(r, r_len)) + goto fail; + a_offset = a->len - r_len; + b_bit_offset = (b->len - r_len) * LIMB_BITS + d; + + /* compute the bits before for the rounding */ + carry = is_sub; + z = 0; + sub_mask = -is_sub; + i = r_len - tot_len; + while (i < 0) { + slimb_t ap, bp; + BOOL inflag; + + ap = a_offset + i; + bp = b_bit_offset + i * LIMB_BITS; + inflag = FALSE; + if (ap >= 0 && ap < a->len) { + v1 = a->tab[ap]; + inflag = TRUE; + } else { + v1 = 0; + } + if (bp + LIMB_BITS > 0 && bp < (slimb_t)(b->len * LIMB_BITS)) { + v2 = get_bits(b->tab, b->len, bp); + inflag = TRUE; + } else { + v2 = 0; + } + if (!inflag) { + /* outside 'a' and 'b': go directly to the next value + inside a or b so that the running time does not + depend on the exponent difference */ + i = 0; + if (ap < 0) + i = bf_min(i, -a_offset); + /* b_bit_offset + i * LIMB_BITS + LIMB_BITS >= 1 + equivalent to + i >= ceil(-b_bit_offset + 1 - LIMB_BITS) / LIMB_BITS) + */ + if (bp + LIMB_BITS <= 0) + i = bf_min(i, (-b_bit_offset) >> LIMB_LOG2_BITS); + } else { + i++; + } + v2 ^= sub_mask; + u = v1 + v2; + carry1 = u < v1; + u += carry; + carry = (u < carry) | carry1; + z |= u; + } + /* and the result */ + for(i = 0; i < r_len; i++) { + v1 = get_limbz(a, a_offset + i); + v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS); + v2 ^= sub_mask; + u = v1 + v2; + carry1 = u < v1; + u += carry; + carry = (u < carry) | carry1; + r->tab[i] = u; + } + /* set the extra bits for the rounding */ + r->tab[0] |= (z != 0); + + /* carry is only possible in add case */ + if (!is_sub && carry) { + if (bf_resize(r, r_len + 1)) + goto fail; + r->tab[r_len] = 1; + r->expn += LIMB_BITS; + } + renorm: + ret = bf_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +static int __bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_add_internal(r, a, b, prec, flags, 0); +} + +static int __bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_add_internal(r, a, b, prec, flags, 1); +} + +limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, + limb_t n, limb_t carry) +{ + slimb_t i; + limb_t k, a, v, k1; + + k = carry; + for(i=0;i<n;i++) { + v = op1[i]; + a = v + op2[i]; + k1 = a < v; + a = a + k; + k = (a < k) | k1; + res[i] = a; + } + return k; +} + +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<n;i++) { + if (k == 0) + break; + a = tab[i] + k; + k = (a < k); + tab[i] = a; + } + return k; +} + +limb_t mp_sub(limb_t *res, const limb_t *op1, const limb_t *op2, + mp_size_t n, limb_t carry) +{ + int i; + limb_t k, a, v, k1; + + k = carry; + for(i=0;i<n;i++) { + v = op1[i]; + a = v - op2[i]; + k1 = a > v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +/* compute 0 - op2 */ +static limb_t mp_neg(limb_t *res, const limb_t *op2, mp_size_t n, limb_t carry) +{ + int i; + limb_t k, a, v, k1; + + k = carry; + for(i=0;i<n;i++) { + v = 0; + a = v - op2[i]; + k1 = a > v; + v = a - k; + k = (v > a) | k1; + res[i] = v; + } + return k; +} + +limb_t mp_sub_ui(limb_t *tab, limb_t b, mp_size_t n) +{ + mp_size_t i; + limb_t k, a, v; + + k=b; + for(i=0;i<n;i++) { + v = tab[i]; + a = v - k; + k = a > v; + tab[i] = a; + if (k == 0) + break; + } + return k; +} + +/* 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); +} + +/* tabr[] = taba[] * b + l. Return the high carry */ +static limb_t mp_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t l) +{ + limb_t i; + dlimb_t t; + + for(i = 0; i < n; i++) { + t = (dlimb_t)taba[i] * (dlimb_t)b + l; + tabr[i] = t; + l = t >> LIMB_BITS; + } + return l; +} + +/* tabr[] += taba[] * b, return the high word. */ +static limb_t mp_add_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b) +{ + limb_t i, l; + dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = (dlimb_t)taba[i] * (dlimb_t)b + l + tabr[i]; + tabr[i] = t; + l = t >> LIMB_BITS; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +static void mp_mul_basecase(limb_t *result, + const limb_t *op1, limb_t op1_size, + const limb_t *op2, limb_t op2_size) +{ + limb_t i, r; + + result[op1_size] = mp_mul1(result, op1, op1_size, op2[0], 0); + for(i=1;i<op2_size;i++) { + r = mp_add_mul1(result + i, op1, op1_size, op2[i]); + result[i + op1_size] = r; + } +} + +/* return 0 if OK, -1 if memory error */ +/* XXX: change API so that result can be allocated */ +int mp_mul(bf_context_t *s, limb_t *result, + const limb_t *op1, limb_t op1_size, + const limb_t *op2, limb_t op2_size) +{ +#ifdef USE_FFT_MUL + if (unlikely(bf_min(op1_size, op2_size) >= FFT_MUL_THRESHOLD)) { + bf_t r_s, *r = &r_s; + r->tab = result; + /* XXX: optimize memory usage in API */ + if (fft_mul(s, r, (limb_t *)op1, op1_size, + (limb_t *)op2, op2_size, FFT_MUL_R_NORESIZE)) + return -1; + } else +#endif + { + mp_mul_basecase(result, op1, op1_size, op2, op2_size); + } + return 0; +} + +/* tabr[] -= taba[] * b. Return the value to substract to the high + word. */ +static limb_t mp_sub_mul1(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b) +{ + limb_t i, l; + dlimb_t t; + + l = 0; + for(i = 0; i < n; i++) { + t = tabr[i] - (dlimb_t)taba[i] * (dlimb_t)b - l; + tabr[i] = t; + l = -(t >> 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; +} + +/* b must be >= 1 << (LIMB_BITS - 1) */ +static limb_t mp_div1norm(limb_t *tabr, const limb_t *taba, limb_t n, + limb_t b, limb_t r) +{ + slimb_t i; + + if (n >= UDIV1NORM_THRESHOLD) { + limb_t b_inv; + b_inv = udiv1norm_init(b); + for(i = n - 1; i >= 0; i--) { + tabr[i] = udiv1norm(&r, r, taba[i], b, b_inv); + } + } else { + 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; +} + +static int mp_divnorm_large(bf_context_t *s, + limb_t *tabq, limb_t *taba, limb_t na, + const limb_t *tabb, limb_t nb); + +/* base case division: divides taba[0..na-1] by tabb[0..nb-1]. tabb[nb + - 1] must be >= 1 << (LIMB_BITS - 1). na - nb must be >= 0. 'taba' + is modified and contains the remainder (nb limbs). tabq[0..na-nb] + contains the quotient with tabq[na - nb] <= 1. */ +static int mp_divnorm(bf_context_t *s, limb_t *tabq, limb_t *taba, limb_t na, + const limb_t *tabb, limb_t nb) +{ + limb_t r, a, c, q, v, b1, b1_inv, n, dummy_r; + slimb_t i, j; + + b1 = tabb[nb - 1]; + if (nb == 1) { + taba[0] = mp_div1norm(tabq, taba, na, b1, 0); + return 0; + } + n = na - nb; + if (bf_min(n, nb) >= DIVNORM_LARGE_THRESHOLD) { + return mp_divnorm_large(s, tabq, taba, na, tabb, nb); + } + + if (n >= UDIV1NORM_THRESHOLD) + b1_inv = udiv1norm_init(b1); + else + b1_inv = 0; + + /* first iteration: the quotient is only 0 or 1 */ + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[n + j] != tabb[j]) { + if (taba[n + j] < tabb[j]) + q = 0; + break; + } + } + tabq[n] = q; + if (q) { + mp_sub(taba + n, taba + n, tabb, nb, 0); + } + + for(i = n - 1; i >= 0; i--) { + if (unlikely(taba[i + nb] >= b1)) { + q = -1; + } else if (b1_inv) { + q = udiv1norm(&dummy_r, taba[i + nb], taba[i + nb - 1], b1, b1_inv); + } else { + dlimb_t al; + al = ((dlimb_t)taba[i + nb] << LIMB_BITS) | taba[i + nb - 1]; + q = al / b1; + r = al % b1; + } + r = mp_sub_mul1(taba + i, tabb, nb, q); + + v = taba[i + nb]; + a = v - r; + c = (a > v); + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = mp_add(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == 0) { + break; + } + } + } + } + tabq[i] = q; + } + return 0; +} + +/* compute r=B^(2*n)/a such as a*r < B^(2*n) < a*r + 2 with n >= 1. 'a' + has n limbs with a[n-1] >= B/2 and 'r' has n+1 limbs with r[n] = 1. + + See Modern Computer Arithmetic by Richard P. Brent and Paul + Zimmermann, algorithm 3.5 */ +int mp_recip(bf_context_t *s, limb_t *tabr, const limb_t *taba, limb_t n) +{ + mp_size_t l, h, k, i; + limb_t *tabxh, *tabt, c, *tabu; + + if (n <= 2) { + /* return ceil(B^(2*n)/a) - 1 */ + /* XXX: could avoid allocation */ + tabu = bf_malloc(s, sizeof(limb_t) * (2 * n + 1)); + tabt = bf_malloc(s, sizeof(limb_t) * (n + 2)); + if (!tabt || !tabu) + goto fail; + for(i = 0; i < 2 * n; i++) + tabu[i] = 0; + tabu[2 * n] = 1; + if (mp_divnorm(s, tabt, tabu, 2 * n + 1, taba, n)) + goto fail; + for(i = 0; i < n + 1; i++) + tabr[i] = tabt[i]; + if (mp_scan_nz(tabu, n) == 0) { + /* only happens for a=B^n/2 */ + mp_sub_ui(tabr, 1, n + 1); + } + } else { + l = (n - 1) / 2; + h = n - l; + /* n=2p -> l=p-1, h = p + 1, k = p + 3 + n=2p+1-> l=p, h = p + 1; k = p + 2 + */ + tabt = bf_malloc(s, sizeof(limb_t) * (n + h + 1)); + tabu = bf_malloc(s, sizeof(limb_t) * (n + 2 * h - l + 2)); + if (!tabt || !tabu) + goto fail; + tabxh = tabr + l; + if (mp_recip(s, tabxh, taba + l, h)) + goto fail; + if (mp_mul(s, tabt, taba, n, tabxh, h + 1)) /* n + h + 1 limbs */ + goto fail; + while (tabt[n + h] != 0) { + mp_sub_ui(tabxh, 1, h + 1); + c = mp_sub(tabt, tabt, taba, n, 0); + mp_sub_ui(tabt + n, c, h + 1); + } + /* T = B^(n+h) - T */ + mp_neg(tabt, tabt, n + h + 1, 0); + tabt[n + h]++; + if (mp_mul(s, tabu, tabt + l, n + h + 1 - l, tabxh, h + 1)) + goto fail; + /* n + 2*h - l + 2 limbs */ + k = 2 * h - l; + for(i = 0; i < l; i++) + tabr[i] = tabu[i + k]; + mp_add(tabr + l, tabr + l, tabu + 2 * h, h, 0); + } + bf_free(s, tabt); + bf_free(s, tabu); + return 0; + fail: + bf_free(s, tabt); + bf_free(s, tabu); + return -1; +} + +/* return -1, 0 or 1 */ +static int mp_cmp(const limb_t *taba, const limb_t *tabb, mp_size_t n) +{ + mp_size_t i; + for(i = n - 1; i >= 0; i--) { + if (taba[i] != tabb[i]) { + if (taba[i] < tabb[i]) + return -1; + else + return 1; + } + } + return 0; +} + +//#define DEBUG_DIVNORM_LARGE +//#define DEBUG_DIVNORM_LARGE2 + +/* subquadratic divnorm */ +static int mp_divnorm_large(bf_context_t *s, + limb_t *tabq, limb_t *taba, limb_t na, + const limb_t *tabb, limb_t nb) +{ + limb_t *tabb_inv, nq, *tabt, i, n; + nq = na - nb; +#ifdef DEBUG_DIVNORM_LARGE + printf("na=%d nb=%d nq=%d\n", (int)na, (int)nb, (int)nq); + mp_print_str("a", taba, na); + mp_print_str("b", tabb, nb); +#endif + assert(nq >= 1); + n = nq; + if (nq < nb) + n++; + tabb_inv = bf_malloc(s, sizeof(limb_t) * (n + 1)); + tabt = bf_malloc(s, sizeof(limb_t) * 2 * (n + 1)); + if (!tabb_inv || !tabt) + goto fail; + + if (n >= nb) { + for(i = 0; i < n - nb; i++) + tabt[i] = 0; + for(i = 0; i < nb; i++) + tabt[i + n - nb] = tabb[i]; + } else { + /* truncate B: need to increment it so that the approximate + inverse is smaller that the exact inverse */ + for(i = 0; i < n; i++) + tabt[i] = tabb[i + nb - n]; + if (mp_add_ui(tabt, 1, n)) { + /* tabt = B^n : tabb_inv = B^n */ + memset(tabb_inv, 0, n * sizeof(limb_t)); + tabb_inv[n] = 1; + goto recip_done; + } + } + if (mp_recip(s, tabb_inv, tabt, n)) + goto fail; + recip_done: + /* Q=A*B^-1 */ + if (mp_mul(s, tabt, tabb_inv, n + 1, taba + na - (n + 1), n + 1)) + goto fail; + + for(i = 0; i < nq + 1; i++) + tabq[i] = tabt[i + 2 * (n + 1) - (nq + 1)]; +#ifdef DEBUG_DIVNORM_LARGE + mp_print_str("q", tabq, nq + 1); +#endif + + bf_free(s, tabt); + bf_free(s, tabb_inv); + tabb_inv = NULL; + + /* R=A-B*Q */ + tabt = bf_malloc(s, sizeof(limb_t) * (na + 1)); + if (!tabt) + goto fail; + if (mp_mul(s, tabt, tabq, nq + 1, tabb, nb)) + goto fail; + /* we add one more limb for the result */ + mp_sub(taba, taba, tabt, nb + 1, 0); + bf_free(s, tabt); + /* the approximated quotient is smaller than than the exact one, + hence we may have to increment it */ +#ifdef DEBUG_DIVNORM_LARGE2 + int cnt = 0; + static int cnt_max; +#endif + for(;;) { + if (taba[nb] == 0 && mp_cmp(taba, tabb, nb) < 0) + break; + taba[nb] -= mp_sub(taba, taba, tabb, nb, 0); + mp_add_ui(tabq, 1, nq + 1); +#ifdef DEBUG_DIVNORM_LARGE2 + cnt++; +#endif + } +#ifdef DEBUG_DIVNORM_LARGE2 + if (cnt > cnt_max) { + cnt_max = cnt; + printf("\ncnt=%d nq=%d nb=%d\n", cnt_max, (int)nq, (int)nb); + } +#endif + return 0; + fail: + bf_free(s, tabb_inv); + bf_free(s, tabt); + return -1; +} + +int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + int ret, r_sign; + + if (a->len < b->len) { + const bf_t *tmp = a; + a = b; + b = tmp; + } + r_sign = a->sign ^ b->sign; + /* here b->len <= a->len */ + if (b->len == 0) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bf_set_nan(r); + ret = 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) { + if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) || + (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) { + bf_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bf_set_inf(r, r_sign); + ret = 0; + } + } else { + bf_set_zero(r, r_sign); + ret = 0; + } + } else { + bf_t tmp, *r1 = NULL; + limb_t a_len, b_len, precl; + limb_t *a_tab, *b_tab; + + a_len = a->len; + b_len = b->len; + + if ((flags & BF_RND_MASK) == BF_RNDF) { + /* faithful rounding does not require using the full inputs */ + precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS; + a_len = bf_min(a_len, precl); + b_len = bf_min(b_len, precl); + } + a_tab = a->tab + a->len - a_len; + b_tab = b->tab + b->len - b_len; + +#ifdef USE_FFT_MUL + if (b_len >= FFT_MUL_THRESHOLD) { + int mul_flags = 0; + if (r == a) + mul_flags |= FFT_MUL_R_OVERLAP_A; + if (r == b) + mul_flags |= FFT_MUL_R_OVERLAP_B; + if (fft_mul(r->ctx, r, a_tab, a_len, b_tab, b_len, mul_flags)) + goto fail; + } else +#endif + { + if (r == a || r == b) { + bf_init(r->ctx, &tmp); + r1 = r; + r = &tmp; + } + if (bf_resize(r, a_len + b_len)) { + fail: + bf_set_nan(r); + ret = BF_ST_MEM_ERROR; + goto done; + } + mp_mul_basecase(r->tab, a_tab, a_len, b_tab, b_len); + } + r->sign = r_sign; + r->expn = a->expn + b->expn; + ret = bf_normalize_and_round(r, prec, flags); + done: + if (r == &tmp) + bf_move(r1, &tmp); + } + return ret; +} + +/* multiply 'r' by 2^e */ +int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags) +{ + slimb_t e_max; + if (r->len == 0) + return 0; + e_max = ((limb_t)1 << BF_EXT_EXP_BITS_MAX) - 1; + e = bf_max(e, -e_max); + e = bf_min(e, e_max); + r->expn += e; + return __bf_round(r, prec, flags, r->len, 0); +} + +/* Return e such as a=m*2^e with m odd integer. return 0 if a is zero, + Infinite or Nan. */ +slimb_t bf_get_exp_min(const bf_t *a) +{ + slimb_t i; + limb_t v; + int k; + + for(i = 0; i < a->len; i++) { + v = a->tab[i]; + if (v != 0) { + k = ctz(v); + return a->expn - (a->len - i) * LIMB_BITS + k; + } + } + return 0; +} + +/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the + integer defined as floor(a/b) and r = a - q * b. */ +static void bf_tdivremu(bf_t *q, bf_t *r, + const bf_t *a, const bf_t *b) +{ + if (bf_cmpu(a, b) < 0) { + bf_set_ui(q, 0); + bf_set(r, a); + } else { + bf_div(q, a, b, bf_max(a->expn - b->expn + 1, 2), BF_RNDZ); + bf_rint(q, BF_RNDZ); + bf_mul(r, q, b, BF_PREC_INF, BF_RNDZ); + bf_sub(r, a, r, BF_PREC_INF, BF_RNDZ); + } +} + +static int __bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + int ret, r_sign; + limb_t n, nb, precl; + + r_sign = a->sign ^ b->sign; + if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else if (a->expn == BF_EXP_INF) { + bf_set_inf(r, r_sign); + return 0; + } else { + bf_set_zero(r, r_sign); + return 0; + } + } else if (a->expn == BF_EXP_ZERO) { + if (b->expn == BF_EXP_ZERO) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, r_sign); + return 0; + } + } else if (b->expn == BF_EXP_ZERO) { + bf_set_inf(r, r_sign); + return BF_ST_DIVIDE_ZERO; + } + + /* number of limbs of the quotient (2 extra bits for rounding) */ + precl = (prec + 2 + LIMB_BITS - 1) / LIMB_BITS; + nb = b->len; + n = bf_max(a->len, precl); + + { + limb_t *taba, na; + slimb_t d; + + na = n + nb; + +#if LIMB_LOG2_BITS == 6 + if (na >= (SIZE_MAX / sizeof(limb_t)) - 1) { + return BF_ST_MEM_ERROR; /* Return memory error status */ + } +#endif + + taba = bf_malloc(s, (na + 1) * sizeof(limb_t)); + if (!taba) + goto fail; + d = na - a->len; + memset(taba, 0, d * sizeof(limb_t)); + memcpy(taba + d, a->tab, a->len * sizeof(limb_t)); + if (bf_resize(r, n + 1)) + goto fail1; + if (mp_divnorm(s, r->tab, taba, na, b->tab, nb)) { + fail1: + bf_free(s, taba); + goto fail; + } + /* see if non zero remainder */ + if (mp_scan_nz(taba, nb)) + r->tab[0] |= 1; + bf_free(r->ctx, taba); + r->expn = a->expn - b->expn + LIMB_BITS; + r->sign = r_sign; + ret = bf_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +/* division and remainder. + + rnd_mode is the rounding mode for the quotient. The additional + rounding mode BF_RND_EUCLIDIAN is supported. + + 'q' is an integer. 'r' is rounded with prec and flags (prec can be + BF_PREC_INF). +*/ +int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode) +{ + bf_t a1_s, *a1 = &a1_s; + bf_t b1_s, *b1 = &b1_s; + int q_sign, ret; + BOOL is_ceil, is_rndn; + + assert(q != a && q != b); + assert(r != a && r != b); + assert(q != r); + + if (a->len == 0 || b->len == 0) { + bf_set_zero(q, 0); + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set(r, a); + return bf_round(r, prec, flags); + } + } + + q_sign = a->sign ^ b->sign; + is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); + switch(rnd_mode) { + default: + case BF_RNDZ: + case BF_RNDN: + case BF_RNDNA: + is_ceil = FALSE; + break; + case BF_RNDD: + is_ceil = q_sign; + break; + case BF_RNDU: + is_ceil = q_sign ^ 1; + break; + case BF_RNDA: + is_ceil = TRUE; + break; + case BF_DIVREM_EUCLIDIAN: + is_ceil = a->sign; + break; + } + + a1->expn = a->expn; + a1->tab = a->tab; + a1->len = a->len; + a1->sign = 0; + + b1->expn = b->expn; + b1->tab = b->tab; + b1->len = b->len; + b1->sign = 0; + + /* XXX: could improve to avoid having a large 'q' */ + bf_tdivremu(q, r, a1, b1); + if (bf_is_nan(q) || bf_is_nan(r)) + goto fail; + + if (r->len != 0) { + if (is_rndn) { + int res; + b1->expn--; + res = bf_cmpu(r, b1); + b1->expn++; + if (res > 0 || + (res == 0 && + (rnd_mode == BF_RNDNA || + get_bit(q->tab, q->len, q->len * LIMB_BITS - q->expn)))) { + goto do_sub_r; + } + } else if (is_ceil) { + do_sub_r: + ret = bf_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ); + ret |= bf_sub(r, r, b1, BF_PREC_INF, BF_RNDZ); + if (ret & BF_ST_MEM_ERROR) + goto fail; + } + } + + r->sign ^= a->sign; + q->sign = q_sign; + return bf_round(r, prec, flags); + fail: + bf_set_nan(q); + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bf_rem(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode) +{ + bf_t q_s, *q = &q_s; + int ret; + + bf_init(r->ctx, q); + ret = bf_divrem(q, r, a, b, prec, flags, rnd_mode); + bf_delete(q); + return ret; +} + +static inline int bf_get_limb(slimb_t *pres, const bf_t *a, int flags) +{ +#if LIMB_BITS == 32 + return bf_get_int32(pres, a, flags); +#else + return bf_get_int64(pres, a, flags); +#endif +} + +int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode) +{ + bf_t q_s, *q = &q_s; + int ret; + + bf_init(r->ctx, q); + ret = bf_divrem(q, r, a, b, prec, flags, rnd_mode); + bf_get_limb(pq, q, BF_GET_INT_MOD); + bf_delete(q); + return ret; +} + +static __maybe_unused inline limb_t mul_mod(limb_t a, limb_t b, limb_t m) +{ + dlimb_t t; + t = (dlimb_t)a * (dlimb_t)b; + return t % m; +} + +#if defined(USE_MUL_CHECK) +static limb_t mp_mod1(const limb_t *tab, limb_t n, limb_t m, limb_t r) +{ + slimb_t i; + dlimb_t t; + + for(i = n - 1; i >= 0; i--) { + t = ((dlimb_t)r << LIMB_BITS) | tab[i]; + r = t % m; + } + return r; +} +#endif + +static const uint16_t sqrt_table[192] = { +128,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,144,145,146,147,148,149,150,150,151,152,153,154,155,155,156,157,158,159,160,160,161,162,163,163,164,165,166,167,167,168,169,170,170,171,172,173,173,174,175,176,176,177,178,178,179,180,181,181,182,183,183,184,185,185,186,187,187,188,189,189,190,191,192,192,193,193,194,195,195,196,197,197,198,199,199,200,201,201,202,203,203,204,204,205,206,206,207,208,208,209,209,210,211,211,212,212,213,214,214,215,215,216,217,217,218,218,219,219,220,221,221,222,222,223,224,224,225,225,226,226,227,227,228,229,229,230,230,231,231,232,232,233,234,234,235,235,236,236,237,237,238,238,239,240,240,241,241,242,242,243,243,244,244,245,245,246,246,247,247,248,248,249,249,250,250,251,251,252,252,253,253,254,254,255, +}; + +/* a >= 2^(LIMB_BITS - 2). Return (s, r) with s=floor(sqrt(a)) and + r=a-s^2. 0 <= r <= 2 * s */ +static limb_t mp_sqrtrem1(limb_t *pr, limb_t a) +{ + limb_t s1, r1, s, r, q, u, num; + + /* use a table for the 16 -> 8 bit sqrt */ + s1 = sqrt_table[(a >> (LIMB_BITS - 8)) - 64]; + r1 = (a >> (LIMB_BITS - 16)) - s1 * s1; + if (r1 > 2 * s1) { + r1 -= 2 * s1 + 1; + s1++; + } + + /* one iteration to get a 32 -> 16 bit sqrt */ + num = (r1 << 8) | ((a >> (LIMB_BITS - 32 + 8)) & 0xff); + q = num / (2 * s1); /* q <= 2^8 */ + u = num % (2 * s1); + s = (s1 << 8) + q; + r = (u << 8) | ((a >> (LIMB_BITS - 32)) & 0xff); + r -= q * q; + if ((slimb_t)r < 0) { + s--; + r += 2 * s + 1; + } + +#if LIMB_BITS == 64 + s1 = s; + r1 = r; + /* one more iteration for 64 -> 32 bit sqrt */ + num = (r1 << 16) | ((a >> (LIMB_BITS - 64 + 16)) & 0xffff); + q = num / (2 * s1); /* q <= 2^16 */ + u = num % (2 * s1); + s = (s1 << 16) + q; + r = (u << 16) | ((a >> (LIMB_BITS - 64)) & 0xffff); + r -= q * q; + if ((slimb_t)r < 0) { + s--; + r += 2 * s + 1; + } +#endif + *pr = r; + return s; +} + +/* return floor(sqrt(a)) */ +limb_t bf_isqrt(limb_t a) +{ + limb_t s, r; + int k; + + if (a == 0) + return 0; + k = clz(a) & ~1; + s = mp_sqrtrem1(&r, a << k); + s >>= (k >> 1); + return s; +} + +static limb_t mp_sqrtrem2(limb_t *tabs, limb_t *taba) +{ + limb_t s1, r1, s, q, u, a0, a1; + dlimb_t r, num; + int l; + + a0 = taba[0]; + a1 = taba[1]; + s1 = mp_sqrtrem1(&r1, a1); + l = LIMB_BITS / 2; + num = ((dlimb_t)r1 << l) | (a0 >> l); + q = num / (2 * s1); + u = num % (2 * s1); + s = (s1 << l) + q; + r = ((dlimb_t)u << l) | (a0 & (((limb_t)1 << l) - 1)); + if (unlikely((q >> l) != 0)) + r -= (dlimb_t)1 << LIMB_BITS; /* special case when q=2^l */ + else + r -= q * q; + if ((slimb_t)(r >> LIMB_BITS) < 0) { + s--; + r += 2 * (dlimb_t)s + 1; + } + tabs[0] = s; + taba[0] = r; + return r >> LIMB_BITS; +} + +//#define DEBUG_SQRTREM + +/* tmp_buf must contain (n / 2 + 1 limbs). *prh contains the highest + limb of the remainder. */ +static int mp_sqrtrem_rec(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n, + limb_t *tmp_buf, limb_t *prh) +{ + limb_t l, h, rh, ql, qh, c, i; + + if (n == 1) { + *prh = mp_sqrtrem2(tabs, taba); + return 0; + } +#ifdef DEBUG_SQRTREM + mp_print_str("a", taba, 2 * n); +#endif + l = n / 2; + h = n - l; + if (mp_sqrtrem_rec(s, tabs + l, taba + 2 * l, h, tmp_buf, &qh)) + return -1; +#ifdef DEBUG_SQRTREM + mp_print_str("s1", tabs + l, h); + mp_print_str_h("r1", taba + 2 * l, h, qh); + mp_print_str_h("r2", taba + l, n, qh); +#endif + + /* the remainder is in taba + 2 * l. Its high bit is in qh */ + if (qh) { + mp_sub(taba + 2 * l, taba + 2 * l, tabs + l, h, 0); + } + /* instead of dividing by 2*s, divide by s (which is normalized) + and update q and r */ + if (mp_divnorm(s, tmp_buf, taba + l, n, tabs + l, h)) + return -1; + qh += tmp_buf[l]; + for(i = 0; i < l; i++) + tabs[i] = tmp_buf[i]; + ql = mp_shr(tabs, tabs, l, 1, qh & 1); + qh = qh >> 1; /* 0 or 1 */ + if (ql) + rh = mp_add(taba + l, taba + l, tabs + l, h, 0); + else + rh = 0; +#ifdef DEBUG_SQRTREM + mp_print_str_h("q", tabs, l, qh); + mp_print_str_h("u", taba + l, h, rh); +#endif + + mp_add_ui(tabs + l, qh, h); +#ifdef DEBUG_SQRTREM + mp_print_str_h("s2", tabs, n, sh); +#endif + + /* q = qh, tabs[l - 1 ... 0], r = taba[n - 1 ... l] */ + /* subtract q^2. if qh = 1 then q = B^l, so we can take shortcuts */ + if (qh) { + c = qh; + } else { + if (mp_mul(s, taba + n, tabs, l, tabs, l)) + return -1; + c = mp_sub(taba, taba, taba + n, 2 * l, 0); + } + rh -= mp_sub_ui(taba + 2 * l, c, n - 2 * l); + if ((slimb_t)rh < 0) { + mp_sub_ui(tabs, 1, n); + rh += mp_add_mul1(taba, tabs, n, 2); + rh += mp_add_ui(taba, 1, n); + } + *prh = rh; + return 0; +} + +/* 'taba' has 2*n limbs with n >= 1 and taba[2*n-1] >= 2 ^ (LIMB_BITS + - 2). Return (s, r) with s=floor(sqrt(a)) and r=a-s^2. 0 <= r <= 2 + * s. tabs has n limbs. r is returned in the lower n limbs of + taba. Its r[n] is the returned value of the function. */ +/* Algorithm from the article "Karatsuba Square Root" by Paul Zimmermann and + inspirated from its GMP implementation */ +int mp_sqrtrem(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n) +{ + limb_t tmp_buf1[8]; + limb_t *tmp_buf; + mp_size_t n2; + int ret; + n2 = n / 2 + 1; + if (n2 <= countof(tmp_buf1)) { + tmp_buf = tmp_buf1; + } else { + tmp_buf = bf_malloc(s, sizeof(limb_t) * n2); + if (!tmp_buf) + return -1; + } + ret = mp_sqrtrem_rec(s, tabs, taba, n, tmp_buf, taba + n); + if (tmp_buf != tmp_buf1) + bf_free(s, tmp_buf); + return ret; +} + +/* Integer square root with remainder. 'a' must be an integer. r = + floor(sqrt(a)) and rem = a - r^2. BF_ST_INEXACT is set if the result + is inexact. 'rem' can be NULL if the remainder is not needed. */ +int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a) +{ + int ret; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (a->expn == BF_EXP_INF && a->sign) { + goto invalid_op; + } else { + bf_set(r, a); + } + if (rem1) + bf_set_ui(rem1, 0); + ret = 0; + } else if (a->sign) { + invalid_op: + bf_set_nan(r); + if (rem1) + bf_set_ui(rem1, 0); + ret = BF_ST_INVALID_OP; + } else { + bf_t rem_s, *rem; + + bf_sqrt(r, a, (a->expn + 1) / 2, BF_RNDZ); + bf_rint(r, BF_RNDZ); + /* see if the result is exact by computing the remainder */ + if (rem1) { + rem = rem1; + } else { + rem = &rem_s; + bf_init(r->ctx, rem); + } + /* XXX: could avoid recomputing the remainder */ + bf_mul(rem, r, r, BF_PREC_INF, BF_RNDZ); + bf_neg(rem); + bf_add(rem, rem, a, BF_PREC_INF, BF_RNDZ); + if (bf_is_nan(rem)) { + ret = BF_ST_MEM_ERROR; + goto done; + } + if (rem->len != 0) { + ret = BF_ST_INEXACT; + } else { + ret = 0; + } + done: + if (!rem1) + bf_delete(rem); + } + return ret; +} + +int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = a->ctx; + int ret; + + assert(r != a); + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (a->expn == BF_EXP_INF && a->sign) { + goto invalid_op; + } else { + bf_set(r, a); + } + ret = 0; + } else if (a->sign) { + invalid_op: + bf_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + limb_t *a1; + slimb_t n, n1; + limb_t res; + + /* convert the mantissa to an integer with at least 2 * + prec + 4 bits */ + n = (2 * (prec + 2) + 2 * LIMB_BITS - 1) / (2 * LIMB_BITS); + if (bf_resize(r, n)) + goto fail; + a1 = bf_malloc(s, sizeof(limb_t) * 2 * n); + if (!a1) + goto fail; + n1 = bf_min(2 * n, a->len); + memset(a1, 0, (2 * n - n1) * sizeof(limb_t)); + memcpy(a1 + 2 * n - n1, a->tab + a->len - n1, n1 * sizeof(limb_t)); + if (a->expn & 1) { + res = mp_shr(a1, a1, 2 * n, 1, 0); + } else { + res = 0; + } + if (mp_sqrtrem(s, r->tab, a1, n)) { + bf_free(s, a1); + goto fail; + } + if (!res) { + res = mp_scan_nz(a1, n + 1); + } + bf_free(s, a1); + if (!res) { + res = mp_scan_nz(a->tab, a->len - n1); + } + if (res != 0) + r->tab[0] |= 1; + r->sign = 0; + r->expn = (a->expn + 1) >> 1; + ret = bf_round(r, prec, flags); + } + return ret; + fail: + bf_set_nan(r); + return BF_ST_MEM_ERROR; +} + +static no_inline int bf_op2(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, bf_op2_func_t *func) +{ + bf_t tmp; + int ret; + + if (r == a || r == b) { + bf_init(r->ctx, &tmp); + ret = func(&tmp, a, b, prec, flags); + bf_move(r, &tmp); + } else { + ret = func(r, a, b, prec, flags); + } + return ret; +} + +int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2(r, a, b, prec, flags, __bf_add); +} + +int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2(r, a, b, prec, flags, __bf_sub); +} + +int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2(r, a, b, prec, flags, __bf_div); +} + +int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, + bf_flags_t flags) +{ + bf_t b; + int ret; + bf_init(r->ctx, &b); + ret = bf_set_ui(&b, b1); + ret |= bf_mul(r, a, &b, prec, flags); + bf_delete(&b); + return ret; +} + +int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bf_t b; + int ret; + bf_init(r->ctx, &b); + ret = bf_set_si(&b, b1); + ret |= bf_mul(r, a, &b, prec, flags); + bf_delete(&b); + return ret; +} + +int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bf_t b; + int ret; + + bf_init(r->ctx, &b); + ret = bf_set_si(&b, b1); + ret |= bf_add(r, a, &b, prec, flags); + bf_delete(&b); + return ret; +} + +static int bf_pow_ui(bf_t *r, const bf_t *a, limb_t b, limb_t prec, + bf_flags_t flags) +{ + int ret, n_bits, i; + + assert(r != a); + if (b == 0) + return bf_set_ui(r, 1); + ret = bf_set(r, a); + n_bits = LIMB_BITS - clz(b); + for(i = n_bits - 2; i >= 0; i--) { + ret |= bf_mul(r, r, r, prec, flags); + if ((b >> i) & 1) + ret |= bf_mul(r, r, a, prec, flags); + } + return ret; +} + +static int bf_pow_ui_ui(bf_t *r, limb_t a1, limb_t b, + limb_t prec, bf_flags_t flags) +{ + bf_t a; + int ret; + + if (a1 == 10 && b <= LIMB_DIGITS) { + /* use precomputed powers. We do not round at this point + because we expect the caller to do it */ + ret = bf_set_ui(r, mp_pow_dec[b]); + } else { + bf_init(r->ctx, &a); + ret = bf_set_ui(&a, a1); + ret |= bf_pow_ui(r, &a, b, prec, flags); + bf_delete(&a); + } + return ret; +} + +/* convert to integer (infinite precision) */ +int bf_rint(bf_t *r, int rnd_mode) +{ + return bf_round(r, 0, rnd_mode | BF_FLAG_RADPNT_PREC); +} + +/* logical operations */ +#define BF_LOGIC_OR 0 +#define BF_LOGIC_XOR 1 +#define BF_LOGIC_AND 2 + +static inline limb_t bf_logic_op1(limb_t a, limb_t b, int op) +{ + switch(op) { + case BF_LOGIC_OR: + return a | b; + case BF_LOGIC_XOR: + return a ^ b; + default: + case BF_LOGIC_AND: + return a & b; + } +} + +static int bf_logic_op(bf_t *r, const bf_t *a1, const bf_t *b1, int op) +{ + bf_t b1_s, a1_s, *a, *b; + limb_t a_sign, b_sign, r_sign; + slimb_t l, i, a_bit_offset, b_bit_offset; + limb_t v1, v2, v1_mask, v2_mask, r_mask; + int ret; + + assert(r != a1 && r != b1); + + if (a1->expn <= 0) + a_sign = 0; /* minus zero is considered as positive */ + else + a_sign = a1->sign; + + if (b1->expn <= 0) + b_sign = 0; /* minus zero is considered as positive */ + else + b_sign = b1->sign; + + if (a_sign) { + a = &a1_s; + bf_init(r->ctx, a); + if (bf_add_si(a, a1, 1, BF_PREC_INF, BF_RNDZ)) { + b = NULL; + goto fail; + } + } else { + a = (bf_t *)a1; + } + + if (b_sign) { + b = &b1_s; + bf_init(r->ctx, b); + if (bf_add_si(b, b1, 1, BF_PREC_INF, BF_RNDZ)) + goto fail; + } else { + b = (bf_t *)b1; + } + + r_sign = bf_logic_op1(a_sign, b_sign, op); + if (op == BF_LOGIC_AND && r_sign == 0) { + /* no need to compute extra zeros for and */ + if (a_sign == 0 && b_sign == 0) + l = bf_min(a->expn, b->expn); + else if (a_sign == 0) + l = a->expn; + else + l = b->expn; + } else { + l = bf_max(a->expn, b->expn); + } + /* Note: a or b can be zero */ + l = (bf_max(l, 1) + LIMB_BITS - 1) / LIMB_BITS; + if (bf_resize(r, l)) + goto fail; + a_bit_offset = a->len * LIMB_BITS - a->expn; + b_bit_offset = b->len * LIMB_BITS - b->expn; + v1_mask = -a_sign; + v2_mask = -b_sign; + r_mask = -r_sign; + for(i = 0; i < l; i++) { + v1 = get_bits(a->tab, a->len, a_bit_offset + i * LIMB_BITS) ^ v1_mask; + v2 = get_bits(b->tab, b->len, b_bit_offset + i * LIMB_BITS) ^ v2_mask; + r->tab[i] = bf_logic_op1(v1, v2, op) ^ r_mask; + } + r->expn = l * LIMB_BITS; + r->sign = r_sign; + bf_normalize_and_round(r, BF_PREC_INF, BF_RNDZ); /* cannot fail */ + if (r_sign) { + if (bf_add_si(r, r, -1, BF_PREC_INF, BF_RNDZ)) + goto fail; + } + ret = 0; + done: + if (a == &a1_s) + bf_delete(a); + if (b == &b1_s) + bf_delete(b); + return ret; + fail: + bf_set_nan(r); + ret = BF_ST_MEM_ERROR; + goto done; +} + +/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ +int bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b) +{ + return bf_logic_op(r, a, b, BF_LOGIC_OR); +} + +/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ +int bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b) +{ + return bf_logic_op(r, a, b, BF_LOGIC_XOR); +} + +/* 'a' and 'b' must be integers. Return 0 or BF_ST_MEM_ERROR. */ +int bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b) +{ + return bf_logic_op(r, a, b, BF_LOGIC_AND); +} + +/* conversion between fixed size types */ + +typedef union { + double d; + uint64_t u; +} Float64Union; + +int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode) +{ + Float64Union u; + int e, ret; + uint64_t m; + + ret = 0; + if (a->expn == BF_EXP_NAN) { + u.u = 0x7ff8000000000000; /* quiet nan */ + } else { + bf_t b_s, *b = &b_s; + + bf_init(a->ctx, b); + bf_set(b, a); + if (bf_is_finite(b)) { + ret = bf_round(b, 53, rnd_mode | BF_FLAG_SUBNORMAL | bf_set_exp_bits(11)); + } + if (b->expn == BF_EXP_INF) { + e = (1 << 11) - 1; + m = 0; + } else if (b->expn == BF_EXP_ZERO) { + e = 0; + m = 0; + } else { + e = b->expn + 1023 - 1; +#if LIMB_BITS == 32 + if (b->len == 2) { + m = ((uint64_t)b->tab[1] << 32) | b->tab[0]; + } else { + m = ((uint64_t)b->tab[0] << 32); + } +#else + m = b->tab[0]; +#endif + if (e <= 0) { + /* subnormal */ + m = m >> (12 - e); + e = 0; + } else { + m = (m << 1) >> 12; + } + } + u.u = m | ((uint64_t)e << 52) | ((uint64_t)b->sign << 63); + bf_delete(b); + } + *pres = u.d; + return ret; +} + +int bf_set_float64(bf_t *a, double d) +{ + Float64Union u; + uint64_t m; + int shift, e, sgn; + + u.d = d; + sgn = u.u >> 63; + e = (u.u >> 52) & ((1 << 11) - 1); + m = u.u & (((uint64_t)1 << 52) - 1); + if (e == ((1 << 11) - 1)) { + if (m != 0) { + bf_set_nan(a); + } else { + bf_set_inf(a, sgn); + } + } else if (e == 0) { + if (m == 0) { + bf_set_zero(a, sgn); + } else { + /* subnormal number */ + m <<= 12; + shift = clz64(m); + m <<= shift; + e = -shift; + goto norm; + } + } else { + m = (m << 11) | ((uint64_t)1 << 63); + norm: + a->expn = e - 1023 + 1; +#if LIMB_BITS == 32 + if (bf_resize(a, 2)) + goto fail; + a->tab[0] = m; + a->tab[1] = m >> 32; +#else + if (bf_resize(a, 1)) + goto fail; + a->tab[0] = m; +#endif + a->sign = sgn; + } + return 0; +fail: + bf_set_nan(a); + return BF_ST_MEM_ERROR; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there + is an overflow and 0 otherwise. */ +int bf_get_int32(int *pres, const bf_t *a, int flags) +{ + uint32_t v; + int ret; + if (a->expn >= BF_EXP_INF) { + ret = BF_ST_INVALID_OP; + if (flags & BF_GET_INT_MOD) { + v = 0; + } else if (a->expn == BF_EXP_INF) { + v = (uint32_t)INT32_MAX + a->sign; + } else { + v = INT32_MAX; + } + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->expn <= 31) { + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); + if (a->sign) + v = -v; + ret = 0; + } else if (!(flags & BF_GET_INT_MOD)) { + ret = BF_ST_INVALID_OP; + if (a->sign) { + v = (uint32_t)INT32_MAX + 1; + if (a->expn == 32 && + (a->tab[a->len - 1] >> (LIMB_BITS - 32)) == v) { + ret = 0; + } + } else { + v = INT32_MAX; + } + } else { + v = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); + if (a->sign) + v = -v; + ret = 0; + } + *pres = v; + return ret; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there + is an overflow and 0 otherwise. */ +int bf_get_int64(int64_t *pres, const bf_t *a, int flags) +{ + uint64_t v; + int ret; + if (a->expn >= BF_EXP_INF) { + ret = BF_ST_INVALID_OP; + if (flags & BF_GET_INT_MOD) { + v = 0; + } else if (a->expn == BF_EXP_INF) { + v = (uint64_t)INT64_MAX + a->sign; + } else { + v = INT64_MAX; + } + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->expn <= 63) { +#if LIMB_BITS == 32 + if (a->expn <= 32) + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); + else + v = (((uint64_t)a->tab[a->len - 1] << 32) | + get_limbz(a, a->len - 2)) >> (64 - a->expn); +#else + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); +#endif + if (a->sign) + v = -v; + ret = 0; + } else if (!(flags & BF_GET_INT_MOD)) { + ret = BF_ST_INVALID_OP; + if (a->sign) { + uint64_t v1; + v = (uint64_t)INT64_MAX + 1; + if (a->expn == 64) { + v1 = a->tab[a->len - 1]; +#if LIMB_BITS == 32 + v1 = (v1 << 32) | get_limbz(a, a->len - 2); +#endif + if (v1 == v) + ret = 0; + } + } else { + v = INT64_MAX; + } + } else { + slimb_t bit_pos = a->len * LIMB_BITS - a->expn; + v = get_bits(a->tab, a->len, bit_pos); +#if LIMB_BITS == 32 + v |= (uint64_t)get_bits(a->tab, a->len, bit_pos + 32) << 32; +#endif + if (a->sign) + v = -v; + ret = 0; + } + *pres = v; + return ret; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_INVALID_OP if there + is an overflow and 0 otherwise. */ +int bf_get_uint64(uint64_t *pres, const bf_t *a) +{ + uint64_t v; + int ret; + if (a->expn == BF_EXP_NAN) { + goto overflow; + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->sign) { + v = 0; + ret = BF_ST_INVALID_OP; + } else if (a->expn <= 64) { +#if LIMB_BITS == 32 + if (a->expn <= 32) + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); + else + v = (((uint64_t)a->tab[a->len - 1] << 32) | + get_limbz(a, a->len - 2)) >> (64 - a->expn); +#else + v = a->tab[a->len - 1] >> (LIMB_BITS - a->expn); +#endif + ret = 0; + } else { + overflow: + v = UINT64_MAX; + ret = BF_ST_INVALID_OP; + } + *pres = v; + return ret; +} + +/* base conversion from radix */ + +static const uint8_t digits_per_limb_table[BF_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 limb_t get_limb_radix(int radix) +{ + int i, k; + limb_t radixl; + + k = digits_per_limb_table[radix - 2]; + radixl = radix; + for(i = 1; i < k; i++) + radixl *= radix; + return radixl; +} + +/* return != 0 if error */ +static int bf_integer_from_radix_rec(bf_t *r, const limb_t *tab, + limb_t n, int level, limb_t n0, + limb_t radix, bf_t *pow_tab) +{ + int ret; + if (n == 1) { + ret = bf_set_ui(r, tab[0]); + } else { + bf_t T_s, *T = &T_s, *B; + limb_t n1, n2; + + n2 = (((n0 * 2) >> (level + 1)) + 1) / 2; + n1 = n - n2; + // printf("level=%d n0=%ld n1=%ld n2=%ld\n", level, n0, n1, n2); + B = &pow_tab[level]; + if (B->len == 0) { + ret = bf_pow_ui_ui(B, radix, n2, BF_PREC_INF, BF_RNDZ); + if (ret) + return ret; + } + ret = bf_integer_from_radix_rec(r, tab + n2, n1, level + 1, n0, + radix, pow_tab); + if (ret) + return ret; + ret = bf_mul(r, r, B, BF_PREC_INF, BF_RNDZ); + if (ret) + return ret; + bf_init(r->ctx, T); + ret = bf_integer_from_radix_rec(T, tab, n2, level + 1, n0, + radix, pow_tab); + if (!ret) + ret = bf_add(r, r, T, BF_PREC_INF, BF_RNDZ); + bf_delete(T); + } + return ret; + // bf_print_str(" r=", r); +} + +/* return 0 if OK != 0 if memory error */ +static int bf_integer_from_radix(bf_t *r, const limb_t *tab, + limb_t n, limb_t radix) +{ + bf_context_t *s = r->ctx; + int pow_tab_len, i, ret; + limb_t radixl; + bf_t *pow_tab; + + radixl = get_limb_radix(radix); + pow_tab_len = ceil_log2(n) + 2; /* XXX: check */ + pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len); + if (!pow_tab) + return -1; + for(i = 0; i < pow_tab_len; i++) + bf_init(r->ctx, &pow_tab[i]); + ret = bf_integer_from_radix_rec(r, tab, n, 0, n, radixl, pow_tab); + for(i = 0; i < pow_tab_len; i++) { + bf_delete(&pow_tab[i]); + } + bf_free(s, pow_tab); + return ret; +} + +/* compute and round T * radix^expn. */ +int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, + slimb_t expn, limb_t prec, bf_flags_t flags) +{ + int ret, expn_sign, overflow; + slimb_t e, extra_bits, prec1, ziv_extra_bits; + bf_t B_s, *B = &B_s; + + if (T->len == 0) { + return bf_set(r, T); + } else if (expn == 0) { + ret = bf_set(r, T); + ret |= bf_round(r, prec, flags); + return ret; + } + + e = expn; + expn_sign = 0; + if (e < 0) { + e = -e; + expn_sign = 1; + } + bf_init(r->ctx, B); + if (prec == BF_PREC_INF) { + /* infinite precision: only used if the result is known to be exact */ + ret = bf_pow_ui_ui(B, radix, e, BF_PREC_INF, BF_RNDN); + if (expn_sign) { + ret |= bf_div(r, T, B, T->len * LIMB_BITS, BF_RNDN); + } else { + ret |= bf_mul(r, T, B, BF_PREC_INF, BF_RNDN); + } + } else { + ziv_extra_bits = 16; + for(;;) { + prec1 = prec + ziv_extra_bits; + /* XXX: correct overflow/underflow handling */ + /* XXX: rigorous error analysis needed */ + extra_bits = ceil_log2(e) * 2 + 1; + ret = bf_pow_ui_ui(B, radix, e, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); + overflow = !bf_is_finite(B); + /* XXX: if bf_pow_ui_ui returns an exact result, can stop + after the next operation */ + if (expn_sign) + ret |= bf_div(r, T, B, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); + else + ret |= bf_mul(r, T, B, prec1 + extra_bits, BF_RNDN | BF_FLAG_EXT_EXP); + if (ret & BF_ST_MEM_ERROR) + break; + if ((ret & BF_ST_INEXACT) && + !bf_can_round(r, prec, flags & BF_RND_MASK, prec1) && + !overflow) { + /* and more precision and retry */ + ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); + } else { + /* XXX: need to use __bf_round() to pass the inexact + flag for the subnormal case */ + ret = bf_round(r, prec, flags) | (ret & BF_ST_INEXACT); + break; + } + } + } + bf_delete(B); + return ret; +} + +static inline int bf_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; +} + +/* add a limb at 'pos' and decrement pos. new space is created if + needed. Return 0 if OK, -1 if memory error */ +static int bf_add_limb(bf_t *a, slimb_t *ppos, limb_t v) +{ + slimb_t pos; + pos = *ppos; + if (unlikely(pos < 0)) { + limb_t new_size, d, *new_tab; + new_size = bf_max(a->len + 1, a->len * 3 / 2); + new_tab = bf_realloc(a->ctx, a->tab, sizeof(limb_t) * new_size); + if (!new_tab) + return -1; + a->tab = new_tab; + d = new_size - a->len; + memmove(a->tab + d, a->tab, a->len * sizeof(limb_t)); + a->len = new_size; + pos += d; + } + a->tab[pos--] = v; + *ppos = pos; + return 0; +} + +static int bf_tolower(int c) +{ + if (c >= 'A' && c <= 'Z') + c = c - 'A' + 'a'; + return c; +} + +static int strcasestart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (bf_tolower(*p) != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +static int bf_atof_internal(bf_t *r, slimb_t *pexponent, + const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags, BOOL is_dec) +{ + const char *p, *p_start; + int is_neg, radix_bits, exp_is_neg, ret, digits_per_limb, shift; + limb_t cur_limb; + slimb_t pos, expn, int_len, digit_count; + BOOL has_decpt, is_bin_exp; + bf_t a_s, *a; + + *pexponent = 0; + p = str; + if (!(flags & BF_ATOF_NO_NAN_INF) && radix <= 16 && + strcasestart(p, "nan", &p)) { + bf_set_nan(r); + ret = 0; + goto done; + } + 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) && + !(flags & BF_ATOF_NO_HEX)) { + radix = 16; + p += 2; + } else if ((p[1] == 'o' || p[1] == 'O') && + radix == 0 && (flags & BF_ATOF_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && + radix == 0 && (flags & BF_ATOF_BIN_OCT)) { + p += 2; + radix = 2; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (bf_to_digit((uint8_t)*p) >= radix) { + bf_set_nan(r); + ret = 0; + goto done; + } + no_prefix: ; + } else { + if (!(flags & BF_ATOF_NO_NAN_INF) && radix <= 16 && + strcasestart(p, "inf", &p)) { + bf_set_inf(r, is_neg); + ret = 0; + goto done; + } + } + + if (radix == 0) + radix = 10; + if (is_dec) { + assert(radix == 10); + radix_bits = 0; + a = r; + } else if ((radix & (radix - 1)) != 0) { + radix_bits = 0; /* base is not a power of two */ + a = &a_s; + bf_init(r->ctx, a); + } else { + radix_bits = ceil_log2(radix); + a = r; + } + + /* skip leading zeros */ + /* XXX: could also skip zeros after the decimal point */ + while (*p == '0') + p++; + + if (radix_bits) { + shift = digits_per_limb = LIMB_BITS; + } else { + radix_bits = 0; + shift = digits_per_limb = digits_per_limb_table[radix - 2]; + } + cur_limb = 0; + bf_resize(a, 1); + pos = 0; + has_decpt = FALSE; + int_len = digit_count = 0; + for(;;) { + limb_t c; + if (*p == '.' && (p > p_start || bf_to_digit(p[1]) < radix)) { + if (has_decpt) + break; + has_decpt = TRUE; + int_len = digit_count; + p++; + } + c = bf_to_digit(*p); + if (c >= radix) + break; + digit_count++; + p++; + if (radix_bits) { + shift -= radix_bits; + if (shift <= 0) { + cur_limb |= c >> (-shift); + if (bf_add_limb(a, &pos, cur_limb)) + goto mem_error; + if (shift < 0) + cur_limb = c << (LIMB_BITS + shift); + else + cur_limb = 0; + shift += LIMB_BITS; + } else { + cur_limb |= c << shift; + } + } else { + cur_limb = cur_limb * radix + c; + shift--; + if (shift == 0) { + if (bf_add_limb(a, &pos, cur_limb)) + goto mem_error; + shift = digits_per_limb; + cur_limb = 0; + } + } + } + if (!has_decpt) + int_len = digit_count; + + /* add the last limb and pad with zeros */ + if (shift != digits_per_limb) { + if (radix_bits == 0) { + while (shift != 0) { + cur_limb *= radix; + shift--; + } + } + if (bf_add_limb(a, &pos, cur_limb)) { + mem_error: + ret = BF_ST_MEM_ERROR; + if (!radix_bits) + bf_delete(a); + bf_set_nan(r); + goto done; + } + } + + /* reset the next limbs to zero (we prefer to reallocate in the + renormalization) */ + memset(a->tab, 0, (pos + 1) * sizeof(limb_t)); + + if (p == p_start) { + ret = 0; + if (!radix_bits) + bf_delete(a); + bf_set_nan(r); + goto done; + } + + /* parse the exponent, if any */ + expn = 0; + is_bin_exp = FALSE; + if (((radix == 10 && (*p == 'e' || *p == 'E')) || + (radix != 10 && (*p == '@' || + (radix_bits && (*p == 'p' || *p == 'P'))))) && + p > p_start) { + is_bin_exp = (*p == 'p' || *p == 'P'); + p++; + exp_is_neg = 0; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_is_neg = 1; + p++; + } + for(;;) { + int c; + c = bf_to_digit(*p); + if (c >= 10) + break; + if (unlikely(expn > ((BF_RAW_EXP_MAX - 2 - 9) / 10))) { + /* exponent overflow */ + if (exp_is_neg) { + bf_set_zero(r, is_neg); + ret = BF_ST_UNDERFLOW | BF_ST_INEXACT; + } else { + bf_set_inf(r, is_neg); + ret = BF_ST_OVERFLOW | BF_ST_INEXACT; + } + goto done; + } + p++; + expn = expn * 10 + c; + } + if (exp_is_neg) + expn = -expn; + } + if (is_dec) { + a->expn = expn + int_len; + a->sign = is_neg; + ret = bfdec_normalize_and_round((bfdec_t *)a, prec, flags); + } else if (radix_bits) { + /* XXX: may overflow */ + if (!is_bin_exp) + expn *= radix_bits; + a->expn = expn + (int_len * radix_bits); + a->sign = is_neg; + ret = bf_normalize_and_round(a, prec, flags); + } else { + limb_t l; + pos++; + l = a->len - pos; /* number of limbs */ + if (l == 0) { + bf_set_zero(r, is_neg); + ret = 0; + } else { + bf_t T_s, *T = &T_s; + + expn -= l * digits_per_limb - int_len; + bf_init(r->ctx, T); + if (bf_integer_from_radix(T, a->tab + pos, l, radix)) { + bf_set_nan(r); + ret = BF_ST_MEM_ERROR; + } else { + T->sign = is_neg; + if (flags & BF_ATOF_EXPONENT) { + /* return the exponent */ + *pexponent = expn; + ret = bf_set(r, T); + } else { + ret = bf_mul_pow_radix(r, T, radix, expn, prec, flags); + } + } + bf_delete(T); + } + bf_delete(a); + } + done: + if (pnext) + *pnext = p; + return ret; +} + +/* + Return (status, n, exp). 'status' is the floating point status. 'n' + is the parsed number. + + If (flags & BF_ATOF_EXPONENT) and if the radix is not a power of + two, the parsed number is equal to r * + (*pexponent)^radix. Otherwise *pexponent = 0. +*/ +int bf_atof2(bf_t *r, slimb_t *pexponent, + const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags) +{ + return bf_atof_internal(r, pexponent, str, pnext, radix, prec, flags, + FALSE); +} + +int bf_atof(bf_t *r, const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags) +{ + slimb_t dummy_exp; + return bf_atof_internal(r, &dummy_exp, str, pnext, radix, prec, flags, FALSE); +} + +/* base conversion to radix */ + +#if LIMB_BITS == 64 +#define RADIXL_10 UINT64_C(10000000000000000000) +#else +#define RADIXL_10 UINT64_C(1000000000) +#endif + +static const uint32_t inv_log2_radix[BF_RADIX_MAX - 1][LIMB_BITS / 32 + 1] = { +#if LIMB_BITS == 32 +{ 0x80000000, 0x00000000,}, +{ 0x50c24e60, 0xd4d4f4a7,}, +{ 0x40000000, 0x00000000,}, +{ 0x372068d2, 0x0a1ee5ca,}, +{ 0x3184648d, 0xb8153e7a,}, +{ 0x2d983275, 0x9d5369c4,}, +{ 0x2aaaaaaa, 0xaaaaaaab,}, +{ 0x28612730, 0x6a6a7a54,}, +{ 0x268826a1, 0x3ef3fde6,}, +{ 0x25001383, 0xbac8a744,}, +{ 0x23b46706, 0x82c0c709,}, +{ 0x229729f1, 0xb2c83ded,}, +{ 0x219e7ffd, 0xa5ad572b,}, +{ 0x20c33b88, 0xda7c29ab,}, +{ 0x20000000, 0x00000000,}, +{ 0x1f50b57e, 0xac5884b3,}, +{ 0x1eb22cc6, 0x8aa6e26f,}, +{ 0x1e21e118, 0x0c5daab2,}, +{ 0x1d9dcd21, 0x439834e4,}, +{ 0x1d244c78, 0x367a0d65,}, +{ 0x1cb40589, 0xac173e0c,}, +{ 0x1c4bd95b, 0xa8d72b0d,}, +{ 0x1bead768, 0x98f8ce4c,}, +{ 0x1b903469, 0x050f72e5,}, +{ 0x1b3b433f, 0x2eb06f15,}, +{ 0x1aeb6f75, 0x9c46fc38,}, +{ 0x1aa038eb, 0x0e3bfd17,}, +{ 0x1a593062, 0xb38d8c56,}, +{ 0x1a15f4c3, 0x2b95a2e6,}, +{ 0x19d630dc, 0xcc7ddef9,}, +{ 0x19999999, 0x9999999a,}, +{ 0x195fec80, 0x8a609431,}, +{ 0x1928ee7b, 0x0b4f22f9,}, +{ 0x18f46acf, 0x8c06e318,}, +{ 0x18c23246, 0xdc0a9f3d,}, +#else +{ 0x80000000, 0x00000000, 0x00000000,}, +{ 0x50c24e60, 0xd4d4f4a7, 0x021f57bc,}, +{ 0x40000000, 0x00000000, 0x00000000,}, +{ 0x372068d2, 0x0a1ee5ca, 0x19ea911b,}, +{ 0x3184648d, 0xb8153e7a, 0x7fc2d2e1,}, +{ 0x2d983275, 0x9d5369c4, 0x4dec1661,}, +{ 0x2aaaaaaa, 0xaaaaaaaa, 0xaaaaaaab,}, +{ 0x28612730, 0x6a6a7a53, 0x810fabde,}, +{ 0x268826a1, 0x3ef3fde6, 0x23e2566b,}, +{ 0x25001383, 0xbac8a744, 0x385a3349,}, +{ 0x23b46706, 0x82c0c709, 0x3f891718,}, +{ 0x229729f1, 0xb2c83ded, 0x15fba800,}, +{ 0x219e7ffd, 0xa5ad572a, 0xe169744b,}, +{ 0x20c33b88, 0xda7c29aa, 0x9bddee52,}, +{ 0x20000000, 0x00000000, 0x00000000,}, +{ 0x1f50b57e, 0xac5884b3, 0x70e28eee,}, +{ 0x1eb22cc6, 0x8aa6e26f, 0x06d1a2a2,}, +{ 0x1e21e118, 0x0c5daab1, 0x81b4f4bf,}, +{ 0x1d9dcd21, 0x439834e3, 0x81667575,}, +{ 0x1d244c78, 0x367a0d64, 0xc8204d6d,}, +{ 0x1cb40589, 0xac173e0c, 0x3b7b16ba,}, +{ 0x1c4bd95b, 0xa8d72b0d, 0x5879f25a,}, +{ 0x1bead768, 0x98f8ce4c, 0x66cc2858,}, +{ 0x1b903469, 0x050f72e5, 0x0cf5488e,}, +{ 0x1b3b433f, 0x2eb06f14, 0x8c89719c,}, +{ 0x1aeb6f75, 0x9c46fc37, 0xab5fc7e9,}, +{ 0x1aa038eb, 0x0e3bfd17, 0x1bd62080,}, +{ 0x1a593062, 0xb38d8c56, 0x7998ab45,}, +{ 0x1a15f4c3, 0x2b95a2e6, 0x46aed6a0,}, +{ 0x19d630dc, 0xcc7ddef9, 0x5aadd61b,}, +{ 0x19999999, 0x99999999, 0x9999999a,}, +{ 0x195fec80, 0x8a609430, 0xe1106014,}, +{ 0x1928ee7b, 0x0b4f22f9, 0x5f69791d,}, +{ 0x18f46acf, 0x8c06e318, 0x4d2aeb2c,}, +{ 0x18c23246, 0xdc0a9f3d, 0x3fe16970,}, +#endif +}; + +static const limb_t log2_radix[BF_RADIX_MAX - 1] = { +#if LIMB_BITS == 32 +0x20000000, +0x32b80347, +0x40000000, +0x4a4d3c26, +0x52b80347, +0x59d5d9fd, +0x60000000, +0x6570068e, +0x6a4d3c26, +0x6eb3a9f0, +0x72b80347, +0x766a008e, +0x79d5d9fd, +0x7d053f6d, +0x80000000, +0x82cc7edf, +0x8570068e, +0x87ef05ae, +0x8a4d3c26, +0x8c8ddd45, +0x8eb3a9f0, +0x90c10501, +0x92b80347, +0x949a784c, +0x966a008e, +0x982809d6, +0x99d5d9fd, +0x9b74948f, +0x9d053f6d, +0x9e88c6b3, +0xa0000000, +0xa16bad37, +0xa2cc7edf, +0xa4231623, +0xa570068e, +#else +0x2000000000000000, +0x32b803473f7ad0f4, +0x4000000000000000, +0x4a4d3c25e68dc57f, +0x52b803473f7ad0f4, +0x59d5d9fd5010b366, +0x6000000000000000, +0x6570068e7ef5a1e8, +0x6a4d3c25e68dc57f, +0x6eb3a9f01975077f, +0x72b803473f7ad0f4, +0x766a008e4788cbcd, +0x79d5d9fd5010b366, +0x7d053f6d26089673, +0x8000000000000000, +0x82cc7edf592262d0, +0x8570068e7ef5a1e8, +0x87ef05ae409a0289, +0x8a4d3c25e68dc57f, +0x8c8ddd448f8b845a, +0x8eb3a9f01975077f, +0x90c10500d63aa659, +0x92b803473f7ad0f4, +0x949a784bcd1b8afe, +0x966a008e4788cbcd, +0x982809d5be7072dc, +0x99d5d9fd5010b366, +0x9b74948f5532da4b, +0x9d053f6d26089673, +0x9e88c6b3626a72aa, +0xa000000000000000, +0xa16bad3758efd873, +0xa2cc7edf592262d0, +0xa4231623369e78e6, +0xa570068e7ef5a1e8, +#endif +}; + +/* compute floor(a*b) or ceil(a*b) with b = log2(radix) or + b=1/log2(radix). For is_inv = 0, strict accuracy is not guaranteed + when radix is not a power of two. */ +slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, + int is_ceil1) +{ + int is_neg; + limb_t a; + BOOL is_ceil; + + is_ceil = is_ceil1; + a = a1; + if (a1 < 0) { + a = -a; + is_neg = 1; + } else { + is_neg = 0; + } + is_ceil ^= is_neg; + if ((radix & (radix - 1)) == 0) { + int radix_bits; + /* radix is a power of two */ + radix_bits = ceil_log2(radix); + if (is_inv) { + if (is_ceil) + a += radix_bits - 1; + a = a / radix_bits; + } else { + a = a * radix_bits; + } + } else { + const uint32_t *tab; + limb_t b0, b1; + dlimb_t t; + + if (is_inv) { + tab = inv_log2_radix[radix - 2]; +#if LIMB_BITS == 32 + b1 = tab[0]; + b0 = tab[1]; +#else + b1 = ((limb_t)tab[0] << 32) | tab[1]; + b0 = (limb_t)tab[2] << 32; +#endif + t = (dlimb_t)b0 * (dlimb_t)a; + t = (dlimb_t)b1 * (dlimb_t)a + (t >> LIMB_BITS); + a = t >> (LIMB_BITS - 1); + } else { + b0 = log2_radix[radix - 2]; + t = (dlimb_t)b0 * (dlimb_t)a; + a = t >> (LIMB_BITS - 3); + } + /* a = floor(result) and 'result' cannot be an integer */ + a += is_ceil; + } + if (is_neg) + a = -a; + return a; +} + +/* 'n' is the number of output limbs */ +static int bf_integer_to_radix_rec(bf_t *pow_tab, + limb_t *out, const bf_t *a, limb_t n, + int level, limb_t n0, limb_t radixl, + unsigned int radixl_bits) +{ + limb_t n1, n2, q_prec; + int ret; + + assert(n >= 1); + if (n == 1) { + out[0] = get_bits(a->tab, a->len, a->len * LIMB_BITS - a->expn); + } else if (n == 2) { + dlimb_t t; + slimb_t pos; + pos = a->len * LIMB_BITS - a->expn; + t = ((dlimb_t)get_bits(a->tab, a->len, pos + LIMB_BITS) << LIMB_BITS) | + get_bits(a->tab, a->len, pos); + if (likely(radixl == RADIXL_10)) { + /* use division by a constant when possible */ + out[0] = t % RADIXL_10; + out[1] = t / RADIXL_10; + } else { + out[0] = t % radixl; + out[1] = t / radixl; + } + } else { + bf_t Q, R, *B, *B_inv; + int q_add; + bf_init(a->ctx, &Q); + bf_init(a->ctx, &R); + n2 = (((n0 * 2) >> (level + 1)) + 1) / 2; + n1 = n - n2; + B = &pow_tab[2 * level]; + B_inv = &pow_tab[2 * level + 1]; + ret = 0; + if (B->len == 0) { + /* compute BASE^n2 */ + ret |= bf_pow_ui_ui(B, radixl, n2, BF_PREC_INF, BF_RNDZ); + /* we use enough bits for the maximum possible 'n1' value, + i.e. n2 + 1 */ + ret |= bf_set_ui(&R, 1); + ret |= bf_div(B_inv, &R, B, (n2 + 1) * radixl_bits + 2, BF_RNDN); + } + // printf("%d: n1=% " PRId64 " n2=%" PRId64 "\n", level, n1, n2); + q_prec = n1 * radixl_bits; + ret |= bf_mul(&Q, a, B_inv, q_prec, BF_RNDN); + ret |= bf_rint(&Q, BF_RNDZ); + + ret |= bf_mul(&R, &Q, B, BF_PREC_INF, BF_RNDZ); + ret |= bf_sub(&R, a, &R, BF_PREC_INF, BF_RNDZ); + + if (ret & BF_ST_MEM_ERROR) + goto fail; + /* adjust if necessary */ + q_add = 0; + while (R.sign && R.len != 0) { + if (bf_add(&R, &R, B, BF_PREC_INF, BF_RNDZ)) + goto fail; + q_add--; + } + while (bf_cmpu(&R, B) >= 0) { + if (bf_sub(&R, &R, B, BF_PREC_INF, BF_RNDZ)) + goto fail; + q_add++; + } + if (q_add != 0) { + if (bf_add_si(&Q, &Q, q_add, BF_PREC_INF, BF_RNDZ)) + goto fail; + } + if (bf_integer_to_radix_rec(pow_tab, out + n2, &Q, n1, level + 1, n0, + radixl, radixl_bits)) + goto fail; + if (bf_integer_to_radix_rec(pow_tab, out, &R, n2, level + 1, n0, + radixl, radixl_bits)) { + fail: + bf_delete(&Q); + bf_delete(&R); + return -1; + } + bf_delete(&Q); + bf_delete(&R); + } + return 0; +} + +/* return 0 if OK != 0 if memory error */ +static int bf_integer_to_radix(bf_t *r, const bf_t *a, limb_t radixl) +{ + bf_context_t *s = r->ctx; + limb_t r_len; + bf_t *pow_tab; + int i, pow_tab_len, ret; + + r_len = r->len; + pow_tab_len = (ceil_log2(r_len) + 2) * 2; /* XXX: check */ + pow_tab = bf_malloc(s, sizeof(pow_tab[0]) * pow_tab_len); + if (!pow_tab) + return -1; + for(i = 0; i < pow_tab_len; i++) + bf_init(r->ctx, &pow_tab[i]); + + ret = bf_integer_to_radix_rec(pow_tab, r->tab, a, r_len, 0, r_len, radixl, + ceil_log2(radixl)); + + for(i = 0; i < pow_tab_len; i++) { + bf_delete(&pow_tab[i]); + } + bf_free(s, pow_tab); + return ret; +} + +/* a must be >= 0. 'P' is the wanted number of digits in radix + 'radix'. 'r' is the mantissa represented as an integer. *pE + contains the exponent. Return != 0 if memory error. */ +static int bf_convert_to_radix(bf_t *r, slimb_t *pE, + const bf_t *a, int radix, + limb_t P, bf_rnd_t rnd_mode, + BOOL is_fixed_exponent) +{ + slimb_t E, e, prec, extra_bits, ziv_extra_bits, prec0; + bf_t B_s, *B = &B_s; + int e_sign, ret, res; + + if (a->len == 0) { + /* zero case */ + *pE = 0; + return bf_set(r, a); + } + + if (is_fixed_exponent) { + E = *pE; + } else { + /* compute the new exponent */ + E = 1 + bf_mul_log2_radix(a->expn - 1, radix, TRUE, FALSE); + } + // bf_print_str("a", a); + // printf("E=%ld P=%ld radix=%d\n", E, P, radix); + + for(;;) { + e = P - E; + e_sign = 0; + if (e < 0) { + e = -e; + e_sign = 1; + } + /* Note: precision for log2(radix) is not critical here */ + prec0 = bf_mul_log2_radix(P, radix, FALSE, TRUE); + ziv_extra_bits = 16; + for(;;) { + prec = prec0 + ziv_extra_bits; + /* XXX: rigorous error analysis needed */ + extra_bits = ceil_log2(e) * 2 + 1; + ret = bf_pow_ui_ui(r, radix, e, prec + extra_bits, + BF_RNDN | BF_FLAG_EXT_EXP); + if (!e_sign) + ret |= bf_mul(r, r, a, prec + extra_bits, + BF_RNDN | BF_FLAG_EXT_EXP); + else + ret |= bf_div(r, a, r, prec + extra_bits, + BF_RNDN | BF_FLAG_EXT_EXP); + if (ret & BF_ST_MEM_ERROR) + return BF_ST_MEM_ERROR; + /* if the result is not exact, check that it can be safely + rounded to an integer */ + if ((ret & BF_ST_INEXACT) && + !bf_can_round(r, r->expn, rnd_mode, prec)) { + /* and more precision and retry */ + ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); + continue; + } else { + ret = bf_rint(r, rnd_mode); + if (ret & BF_ST_MEM_ERROR) + return BF_ST_MEM_ERROR; + break; + } + } + if (is_fixed_exponent) + break; + /* check that the result is < B^P */ + /* XXX: do a fast approximate test first ? */ + bf_init(r->ctx, B); + ret = bf_pow_ui_ui(B, radix, P, BF_PREC_INF, BF_RNDZ); + if (ret) { + bf_delete(B); + return ret; + } + res = bf_cmpu(r, B); + bf_delete(B); + if (res < 0) + break; + /* try a larger exponent */ + E++; + } + *pE = E; + return 0; +} + +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 */ + for(i = len - 1; i >= 0; i--) { + digit = (limb_t)n % 10; + n = (limb_t)n / 10; + buf[i] = digit + '0'; + } + } 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; + } + } +} + +/* for power of 2 radixes */ +static void limb_to_a2(char *buf, limb_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; + } +} + +/* 'a' must be an integer if the is_dec = FALSE or if the radix is not + a power of two. A dot is added before the 'dot_pos' digit. dot_pos + = n_digits does not display the dot. 0 <= dot_pos <= + n_digits. n_digits >= 1. */ +static void output_digits(DynBuf *s, const bf_t *a1, int radix, limb_t n_digits, + limb_t dot_pos, BOOL is_dec) +{ + limb_t i, v, l; + slimb_t pos, pos_incr; + int digits_per_limb, buf_pos, radix_bits, first_buf_pos; + char buf[65]; + bf_t a_s, *a; + + if (is_dec) { + digits_per_limb = LIMB_DIGITS; + a = (bf_t *)a1; + radix_bits = 0; + pos = a->len; + pos_incr = 1; + first_buf_pos = 0; + } else if ((radix & (radix - 1)) == 0) { + a = (bf_t *)a1; + radix_bits = ceil_log2(radix); + digits_per_limb = LIMB_BITS / radix_bits; + pos_incr = digits_per_limb * radix_bits; + /* digits are aligned relative to the radix point */ + pos = a->len * LIMB_BITS + smod(-a->expn, radix_bits); + first_buf_pos = 0; + } else { + limb_t n, radixl; + + digits_per_limb = digits_per_limb_table[radix - 2]; + radixl = get_limb_radix(radix); + a = &a_s; + bf_init(a1->ctx, a); + n = (n_digits + digits_per_limb - 1) / digits_per_limb; + if (bf_resize(a, n)) { + dbuf_set_error(s); + goto done; + } + if (bf_integer_to_radix(a, a1, radixl)) { + dbuf_set_error(s); + goto done; + } + radix_bits = 0; + pos = n; + pos_incr = 1; + first_buf_pos = pos * digits_per_limb - n_digits; + } + buf_pos = digits_per_limb; + i = 0; + while (i < n_digits) { + if (buf_pos == digits_per_limb) { + pos -= pos_incr; + if (radix_bits == 0) { + v = get_limbz(a, pos); + limb_to_a(buf, v, radix, digits_per_limb); + } else { + v = get_bits(a->tab, a->len, pos); + limb_to_a2(buf, v, radix_bits, digits_per_limb); + } + buf_pos = first_buf_pos; + first_buf_pos = 0; + } + if (i < dot_pos) { + l = dot_pos; + } else { + if (i == dot_pos) + dbuf_putc(s, '.'); + l = n_digits; + } + l = bf_min(digits_per_limb - buf_pos, l - i); + dbuf_put(s, (uint8_t *)(buf + buf_pos), l); + buf_pos += l; + i += l; + } + done: + if (a != a1) + bf_delete(a); +} + +static void *bf_dbuf_realloc(void *opaque, void *ptr, size_t size) +{ + bf_context_t *s = opaque; + return bf_realloc(s, ptr, size); +} + +/* return the length in bytes. A trailing '\0' is added */ +static char *bf_ftoa_internal(size_t *plen, const bf_t *a2, int radix, + limb_t prec, bf_flags_t flags, BOOL is_dec) +{ + bf_context_t *ctx = a2->ctx; + DynBuf s_s, *s = &s_s; + int radix_bits; + + // bf_print_str("ftoa", a2); + // printf("radix=%d\n", radix); + dbuf_init2(s, ctx, bf_dbuf_realloc); + if (a2->expn == BF_EXP_NAN) { + dbuf_putstr(s, "NaN"); + } else { + if (a2->sign) + dbuf_putc(s, '-'); + if (a2->expn == BF_EXP_INF) { + if (flags & BF_FTOA_JS_QUIRKS) + dbuf_putstr(s, "Infinity"); + else + dbuf_putstr(s, "Inf"); + } else { + int fmt, ret; + slimb_t n_digits, n, i, n_max, n1; + bf_t a1_s, *a1 = &a1_s; + + if ((radix & (radix - 1)) != 0) + radix_bits = 0; + else + radix_bits = ceil_log2(radix); + + fmt = flags & BF_FTOA_FORMAT_MASK; + bf_init(ctx, a1); + if (fmt == BF_FTOA_FORMAT_FRAC) { + if (is_dec || radix_bits != 0) { + if (bf_set(a1, a2)) + goto fail1; +#ifdef USE_BF_DEC + if (is_dec) { + if (bfdec_round((bfdec_t *)a1, prec, (flags & BF_RND_MASK) | BF_FLAG_RADPNT_PREC) & BF_ST_MEM_ERROR) + goto fail1; + n = a1->expn; + } else +#endif + { + if (bf_round(a1, prec * radix_bits, (flags & BF_RND_MASK) | BF_FLAG_RADPNT_PREC) & BF_ST_MEM_ERROR) + goto fail1; + n = ceil_div(a1->expn, radix_bits); + } + if (flags & BF_FTOA_ADD_PREFIX) { + if (radix == 16) + dbuf_putstr(s, "0x"); + else if (radix == 8) + dbuf_putstr(s, "0o"); + else if (radix == 2) + dbuf_putstr(s, "0b"); + } + if (a1->expn == BF_EXP_ZERO) { + dbuf_putstr(s, "0"); + if (prec > 0) { + dbuf_putstr(s, "."); + for(i = 0; i < prec; i++) { + dbuf_putc(s, '0'); + } + } + } else { + n_digits = prec + n; + if (n <= 0) { + /* 0.x */ + dbuf_putstr(s, "0."); + for(i = 0; i < -n; i++) { + dbuf_putc(s, '0'); + } + if (n_digits > 0) { + output_digits(s, a1, radix, n_digits, n_digits, is_dec); + } + } else { + output_digits(s, a1, radix, n_digits, n, is_dec); + } + } + } else { + size_t pos, start; + bf_t a_s, *a = &a_s; + + /* make a positive number */ + a->tab = a2->tab; + a->len = a2->len; + a->expn = a2->expn; + a->sign = 0; + + /* one more digit for the rounding */ + n = 1 + bf_mul_log2_radix(bf_max(a->expn, 0), radix, TRUE, TRUE); + n_digits = n + prec; + n1 = n; + if (bf_convert_to_radix(a1, &n1, a, radix, n_digits, + flags & BF_RND_MASK, TRUE)) + goto fail1; + start = s->size; + output_digits(s, a1, radix, n_digits, n, is_dec); + /* remove leading zeros because we allocated one more digit */ + pos = start; + while ((pos + 1) < s->size && s->buf[pos] == '0' && + s->buf[pos + 1] != '.') + pos++; + if (pos > start) { + memmove(s->buf + start, s->buf + pos, s->size - pos); + s->size -= (pos - start); + } + } + } else { +#ifdef USE_BF_DEC + if (is_dec) { + if (bf_set(a1, a2)) + goto fail1; + if (fmt == BF_FTOA_FORMAT_FIXED) { + n_digits = prec; + n_max = n_digits; + if (bfdec_round((bfdec_t *)a1, prec, (flags & BF_RND_MASK)) & BF_ST_MEM_ERROR) + goto fail1; + } else { + /* prec is ignored */ + prec = n_digits = a1->len * LIMB_DIGITS; + /* remove the trailing zero digits */ + while (n_digits > 1 && + get_digit(a1->tab, a1->len, prec - n_digits) == 0) { + n_digits--; + } + n_max = n_digits + 4; + } + n = a1->expn; + } else +#endif + if (radix_bits != 0) { + if (bf_set(a1, a2)) + goto fail1; + if (fmt == BF_FTOA_FORMAT_FIXED) { + slimb_t prec_bits; + n_digits = prec; + n_max = n_digits; + /* align to the radix point */ + prec_bits = prec * radix_bits - + smod(-a1->expn, radix_bits); + if (bf_round(a1, prec_bits, + (flags & BF_RND_MASK)) & BF_ST_MEM_ERROR) + goto fail1; + } else { + limb_t digit_mask; + slimb_t pos; + /* position of the digit before the most + significant digit in bits */ + pos = a1->len * LIMB_BITS + + smod(-a1->expn, radix_bits); + n_digits = ceil_div(pos, radix_bits); + /* remove the trailing zero digits */ + digit_mask = ((limb_t)1 << radix_bits) - 1; + while (n_digits > 1 && + (get_bits(a1->tab, a1->len, pos - n_digits * radix_bits) & digit_mask) == 0) { + n_digits--; + } + n_max = n_digits + 4; + } + n = ceil_div(a1->expn, radix_bits); + } else { + bf_t a_s, *a = &a_s; + + /* make a positive number */ + a->tab = a2->tab; + a->len = a2->len; + a->expn = a2->expn; + a->sign = 0; + + if (fmt == BF_FTOA_FORMAT_FIXED) { + n_digits = prec; + n_max = n_digits; + } else { + slimb_t n_digits_max, n_digits_min; + + assert(prec != BF_PREC_INF); + n_digits = 1 + bf_mul_log2_radix(prec, radix, TRUE, TRUE); + /* max number of digits for non exponential + notation. The rational is to have the same rule + as JS i.e. n_max = 21 for 64 bit float in base 10. */ + n_max = n_digits + 4; + if (fmt == BF_FTOA_FORMAT_FREE_MIN) { + bf_t b_s, *b = &b_s; + + /* find the minimum number of digits by + dichotomy. */ + /* XXX: inefficient */ + n_digits_max = n_digits; + n_digits_min = 1; + bf_init(ctx, b); + while (n_digits_min < n_digits_max) { + n_digits = (n_digits_min + n_digits_max) / 2; + if (bf_convert_to_radix(a1, &n, a, radix, n_digits, + flags & BF_RND_MASK, FALSE)) { + bf_delete(b); + goto fail1; + } + /* convert back to a number and compare */ + ret = bf_mul_pow_radix(b, a1, radix, n - n_digits, + prec, + (flags & ~BF_RND_MASK) | + BF_RNDN); + if (ret & BF_ST_MEM_ERROR) { + bf_delete(b); + goto fail1; + } + if (bf_cmpu(b, a) == 0) { + n_digits_max = n_digits; + } else { + n_digits_min = n_digits + 1; + } + } + bf_delete(b); + n_digits = n_digits_max; + } + } + if (bf_convert_to_radix(a1, &n, a, radix, n_digits, + flags & BF_RND_MASK, FALSE)) { + fail1: + bf_delete(a1); + goto fail; + } + } + if (a1->expn == BF_EXP_ZERO && + fmt != BF_FTOA_FORMAT_FIXED && + !(flags & BF_FTOA_FORCE_EXP)) { + /* just output zero */ + dbuf_putstr(s, "0"); + } else { + if (flags & BF_FTOA_ADD_PREFIX) { + if (radix == 16) + dbuf_putstr(s, "0x"); + else if (radix == 8) + dbuf_putstr(s, "0o"); + else if (radix == 2) + dbuf_putstr(s, "0b"); + } + if (a1->expn == BF_EXP_ZERO) + n = 1; + if ((flags & BF_FTOA_FORCE_EXP) || + n <= -6 || n > n_max) { + const char *fmt; + /* exponential notation */ + output_digits(s, a1, radix, n_digits, 1, is_dec); + if (radix_bits != 0 && radix <= 16) { + if (flags & BF_FTOA_JS_QUIRKS) + fmt = "p%+" PRId_LIMB; + else + fmt = "p%" PRId_LIMB; + dbuf_printf(s, fmt, (n - 1) * radix_bits); + } else { + if (flags & BF_FTOA_JS_QUIRKS) + fmt = "%c%+" PRId_LIMB; + else + fmt = "%c%" PRId_LIMB; + dbuf_printf(s, fmt, + radix <= 10 ? 'e' : '@', n - 1); + } + } else if (n <= 0) { + /* 0.x */ + dbuf_putstr(s, "0."); + for(i = 0; i < -n; i++) { + dbuf_putc(s, '0'); + } + output_digits(s, a1, radix, n_digits, n_digits, is_dec); + } else { + if (n_digits <= n) { + /* no dot */ + output_digits(s, a1, radix, n_digits, n_digits, is_dec); + for(i = 0; i < (n - n_digits); i++) + dbuf_putc(s, '0'); + } else { + output_digits(s, a1, radix, n_digits, n, is_dec); + } + } + } + } + bf_delete(a1); + } + } + dbuf_putc(s, '\0'); + if (dbuf_error(s)) + goto fail; + if (plen) + *plen = s->size - 1; + return (char *)s->buf; + fail: + bf_free(ctx, s->buf); + if (plen) + *plen = 0; + return NULL; +} + +char *bf_ftoa(size_t *plen, const bf_t *a, int radix, limb_t prec, + bf_flags_t flags) +{ + return bf_ftoa_internal(plen, a, radix, prec, flags, FALSE); +} + +/***************************************************************/ +/* transcendental functions */ + +/* Note: the algorithm is from MPFR */ +static void bf_const_log2_rec(bf_t *T, bf_t *P, bf_t *Q, limb_t n1, + limb_t n2, BOOL need_P) +{ + bf_context_t *s = T->ctx; + if ((n2 - n1) == 1) { + if (n1 == 0) { + bf_set_ui(P, 3); + } else { + bf_set_ui(P, n1); + P->sign = 1; + } + bf_set_ui(Q, 2 * n1 + 1); + Q->expn += 2; + bf_set(T, P); + } else { + limb_t m; + bf_t T1_s, *T1 = &T1_s; + bf_t P1_s, *P1 = &P1_s; + bf_t Q1_s, *Q1 = &Q1_s; + + m = n1 + ((n2 - n1) >> 1); + bf_const_log2_rec(T, P, Q, n1, m, TRUE); + bf_init(s, T1); + bf_init(s, P1); + bf_init(s, Q1); + bf_const_log2_rec(T1, P1, Q1, m, n2, need_P); + bf_mul(T, T, Q1, BF_PREC_INF, BF_RNDZ); + bf_mul(T1, T1, P, BF_PREC_INF, BF_RNDZ); + bf_add(T, T, T1, BF_PREC_INF, BF_RNDZ); + if (need_P) + bf_mul(P, P, P1, BF_PREC_INF, BF_RNDZ); + bf_mul(Q, Q, Q1, BF_PREC_INF, BF_RNDZ); + bf_delete(T1); + bf_delete(P1); + bf_delete(Q1); + } +} + +/* compute log(2) with faithful rounding at precision 'prec' */ +static void bf_const_log2_internal(bf_t *T, limb_t prec) +{ + limb_t w, N; + bf_t P_s, *P = &P_s; + bf_t Q_s, *Q = &Q_s; + + w = prec + 15; + N = w / 3 + 1; + bf_init(T->ctx, P); + bf_init(T->ctx, Q); + bf_const_log2_rec(T, P, Q, 0, N, FALSE); + bf_div(T, T, Q, prec, BF_RNDN); + bf_delete(P); + bf_delete(Q); +} + +/* PI constant */ + +#define CHUD_A 13591409 +#define CHUD_B 545140134 +#define CHUD_C 640320 +#define CHUD_BITS_PER_TERM 47 + +static void chud_bs(bf_t *P, bf_t *Q, bf_t *G, int64_t a, int64_t b, int need_g, + limb_t prec) +{ + bf_context_t *s = P->ctx; + int64_t c; + + if (a == (b - 1)) { + bf_t T0, T1; + + bf_init(s, &T0); + bf_init(s, &T1); + bf_set_ui(G, 2 * b - 1); + bf_mul_ui(G, G, 6 * b - 1, prec, BF_RNDN); + bf_mul_ui(G, G, 6 * b - 5, prec, BF_RNDN); + bf_set_ui(&T0, CHUD_B); + bf_mul_ui(&T0, &T0, b, prec, BF_RNDN); + bf_set_ui(&T1, CHUD_A); + bf_add(&T0, &T0, &T1, prec, BF_RNDN); + bf_mul(P, G, &T0, prec, BF_RNDN); + P->sign = b & 1; + + bf_set_ui(Q, b); + bf_mul_ui(Q, Q, b, prec, BF_RNDN); + bf_mul_ui(Q, Q, b, prec, BF_RNDN); + bf_mul_ui(Q, Q, (uint64_t)CHUD_C * CHUD_C * CHUD_C / 24, prec, BF_RNDN); + bf_delete(&T0); + bf_delete(&T1); + } else { + bf_t P2, Q2, G2; + + bf_init(s, &P2); + bf_init(s, &Q2); + bf_init(s, &G2); + + c = (a + b) / 2; + chud_bs(P, Q, G, a, c, 1, prec); + chud_bs(&P2, &Q2, &G2, c, b, need_g, prec); + + /* Q = Q1 * Q2 */ + /* G = G1 * G2 */ + /* P = P1 * Q2 + P2 * G1 */ + bf_mul(&P2, &P2, G, prec, BF_RNDN); + if (!need_g) + bf_set_ui(G, 0); + bf_mul(P, P, &Q2, prec, BF_RNDN); + bf_add(P, P, &P2, prec, BF_RNDN); + bf_delete(&P2); + + bf_mul(Q, Q, &Q2, prec, BF_RNDN); + bf_delete(&Q2); + if (need_g) + bf_mul(G, G, &G2, prec, BF_RNDN); + bf_delete(&G2); + } +} + +/* compute Pi with faithful rounding at precision 'prec' using the + Chudnovsky formula */ +static void bf_const_pi_internal(bf_t *Q, limb_t prec) +{ + bf_context_t *s = Q->ctx; + int64_t n, prec1; + bf_t P, G; + + /* number of serie terms */ + n = prec / CHUD_BITS_PER_TERM + 1; + /* XXX: precision analysis */ + prec1 = prec + 32; + + bf_init(s, &P); + bf_init(s, &G); + + chud_bs(&P, Q, &G, 0, n, 0, BF_PREC_INF); + + bf_mul_ui(&G, Q, CHUD_A, prec1, BF_RNDN); + bf_add(&P, &G, &P, prec1, BF_RNDN); + bf_div(Q, Q, &P, prec1, BF_RNDF); + + bf_set_ui(&P, CHUD_C); + bf_sqrt(&G, &P, prec1, BF_RNDF); + bf_mul_ui(&G, &G, (uint64_t)CHUD_C / 12, prec1, BF_RNDF); + bf_mul(Q, Q, &G, prec, BF_RNDN); + bf_delete(&P); + bf_delete(&G); +} + +static int bf_const_get(bf_t *T, limb_t prec, bf_flags_t flags, + BFConstCache *c, + void (*func)(bf_t *res, limb_t prec), int sign) +{ + limb_t ziv_extra_bits, prec1; + + ziv_extra_bits = 32; + for(;;) { + prec1 = prec + ziv_extra_bits; + if (c->prec < prec1) { + if (c->val.len == 0) + bf_init(T->ctx, &c->val); + func(&c->val, prec1); + c->prec = prec1; + } else { + prec1 = c->prec; + } + bf_set(T, &c->val); + T->sign = sign; + if (!bf_can_round(T, prec, flags & BF_RND_MASK, prec1)) { + /* and more precision and retry */ + ziv_extra_bits = ziv_extra_bits + (ziv_extra_bits / 2); + } else { + break; + } + } + return bf_round(T, prec, flags); +} + +static void bf_const_free(BFConstCache *c) +{ + bf_delete(&c->val); + memset(c, 0, sizeof(*c)); +} + +int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = T->ctx; + return bf_const_get(T, prec, flags, &s->log2_cache, bf_const_log2_internal, 0); +} + +/* return rounded pi * (1 - 2 * sign) */ +static int bf_const_pi_signed(bf_t *T, int sign, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = T->ctx; + return bf_const_get(T, prec, flags, &s->pi_cache, bf_const_pi_internal, + sign); +} + +int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags) +{ + return bf_const_pi_signed(T, 0, prec, flags); +} + +void bf_clear_cache(bf_context_t *s) +{ +#ifdef USE_FFT_MUL + fft_clear_cache(s); +#endif + bf_const_free(&s->log2_cache); + bf_const_free(&s->pi_cache); +} + +/* ZivFunc should compute the result 'r' with faithful rounding at + precision 'prec'. For efficiency purposes, the final bf_round() + does not need to be done in the function. */ +typedef int ZivFunc(bf_t *r, const bf_t *a, limb_t prec, void *opaque); + +static int bf_ziv_rounding(bf_t *r, const bf_t *a, + limb_t prec, bf_flags_t flags, + ZivFunc *f, void *opaque) +{ + int rnd_mode, ret; + slimb_t prec1, ziv_extra_bits; + + rnd_mode = flags & BF_RND_MASK; + if (rnd_mode == BF_RNDF) { + /* no need to iterate */ + f(r, a, prec, opaque); + ret = 0; + } else { + ziv_extra_bits = 32; + for(;;) { + prec1 = prec + ziv_extra_bits; + ret = f(r, a, prec1, opaque); + if (ret & (BF_ST_OVERFLOW | BF_ST_UNDERFLOW | BF_ST_MEM_ERROR)) { + /* overflow or underflow should never happen because + it indicates the rounding cannot be done correctly, + but we do not catch all the cases */ + return ret; + } + /* if the result is exact, we can stop */ + if (!(ret & BF_ST_INEXACT)) { + ret = 0; + break; + } + if (bf_can_round(r, prec, rnd_mode, prec1)) { + ret = BF_ST_INEXACT; + break; + } + ziv_extra_bits = ziv_extra_bits * 2; + // printf("ziv_extra_bits=%" PRId64 "\n", (int64_t)ziv_extra_bits); + } + } + if (r->len == 0) + return ret; + else + return __bf_round(r, prec, flags, r->len, ret); +} + +/* add (1 - 2*e_sign) * 2^e */ +static int bf_add_epsilon(bf_t *r, const bf_t *a, slimb_t e, int e_sign, + limb_t prec, int flags) +{ + bf_t T_s, *T = &T_s; + int ret; + /* small argument case: result = 1 + epsilon * sign(x) */ + bf_init(a->ctx, T); + bf_set_ui(T, 1); + T->sign = e_sign; + T->expn += e; + ret = bf_add(r, r, T, prec, flags); + bf_delete(T); + return ret; +} + +/* Compute the exponential using faithful rounding at precision 'prec'. + Note: the algorithm is from MPFR */ +static int bf_exp_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + slimb_t n, K, l, i, prec1; + + assert(r != a); + + /* argument reduction: + T = a - n*log(2) with 0 <= T < log(2) and n integer. + */ + bf_init(s, T); + if (a->expn <= -1) { + /* 0 <= abs(a) <= 0.5 */ + if (a->sign) + n = -1; + else + n = 0; + } else { + bf_const_log2(T, LIMB_BITS, BF_RNDZ); + bf_div(T, a, T, LIMB_BITS, BF_RNDD); + bf_get_limb(&n, T, 0); + } + + K = bf_isqrt((prec + 1) / 2); + l = (prec - 1) / K + 1; + /* XXX: precision analysis ? */ + prec1 = prec + (K + 2 * l + 18) + K + 8; + if (a->expn > 0) + prec1 += a->expn; + // printf("n=%ld K=%ld prec1=%ld\n", n, K, prec1); + + bf_const_log2(T, prec1, BF_RNDF); + bf_mul_si(T, T, n, prec1, BF_RNDN); + bf_sub(T, a, T, prec1, BF_RNDN); + + /* reduce the range of T */ + bf_mul_2exp(T, -K, BF_PREC_INF, BF_RNDZ); + + /* Taylor expansion around zero : + 1 + x + x^2/2 + ... + x^n/n! + = (1 + x * (1 + x/2 * (1 + ... (x/n)))) + */ + { + bf_t U_s, *U = &U_s; + + bf_init(s, U); + bf_set_ui(r, 1); + for(i = l ; i >= 1; i--) { + bf_set_ui(U, i); + bf_div(U, T, U, prec1, BF_RNDN); + bf_mul(r, r, U, prec1, BF_RNDN); + bf_add_si(r, r, 1, prec1, BF_RNDN); + } + bf_delete(U); + } + bf_delete(T); + + /* undo the range reduction */ + for(i = 0; i < K; i++) { + bf_mul(r, r, r, prec1, BF_RNDN | BF_FLAG_EXT_EXP); + } + + /* undo the argument reduction */ + bf_mul_2exp(r, n, BF_PREC_INF, BF_RNDZ | BF_FLAG_EXT_EXP); + + return BF_ST_INEXACT; +} + +/* crude overflow and underflow tests for exp(a). a_low <= a <= a_high */ +static int check_exp_underflow_overflow(bf_context_t *s, bf_t *r, + const bf_t *a_low, const bf_t *a_high, + limb_t prec, bf_flags_t flags) +{ + bf_t T_s, *T = &T_s; + bf_t log2_s, *log2 = &log2_s; + slimb_t e_min, e_max; + + if (a_high->expn <= 0) + return 0; + + e_max = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + e_min = -e_max + 3; + if (flags & BF_FLAG_SUBNORMAL) + e_min -= (prec - 1); + + bf_init(s, T); + bf_init(s, log2); + bf_const_log2(log2, LIMB_BITS, BF_RNDU); + bf_mul_ui(T, log2, e_max, LIMB_BITS, BF_RNDU); + /* a_low > e_max * log(2) implies exp(a) > e_max */ + if (bf_cmp_lt(T, a_low) > 0) { + /* overflow */ + bf_delete(T); + bf_delete(log2); + return bf_set_overflow(r, 0, prec, flags); + } + /* a_high < (e_min - 2) * log(2) implies exp(a) < (e_min - 2) */ + bf_const_log2(log2, LIMB_BITS, BF_RNDD); + bf_mul_si(T, log2, e_min - 2, LIMB_BITS, BF_RNDD); + if (bf_cmp_lt(a_high, T)) { + int rnd_mode = flags & BF_RND_MASK; + + /* underflow */ + bf_delete(T); + bf_delete(log2); + if (rnd_mode == BF_RNDU) { + /* set the smallest value */ + bf_set_ui(r, 1); + r->expn = e_min; + } else { + bf_set_zero(r, 0); + } + return BF_ST_UNDERFLOW | BF_ST_INEXACT; + } + bf_delete(log2); + bf_delete(T); + return 0; +} + +int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + int ret; + assert(r != a); + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (a->expn == BF_EXP_INF) { + if (a->sign) + bf_set_zero(r, 0); + else + bf_set_inf(r, 0); + } else { + bf_set_ui(r, 1); + } + return 0; + } + + ret = check_exp_underflow_overflow(s, r, a, a, prec, flags); + if (ret) + return ret; + if (a->expn < 0 && (-a->expn) >= (prec + 2)) { + /* small argument case: result = 1 + epsilon * sign(x) */ + bf_set_ui(r, 1); + return bf_add_epsilon(r, r, -(prec + 2), a->sign, prec, flags); + } + + return bf_ziv_rounding(r, a, prec, flags, bf_exp_internal, NULL); +} + +static int bf_log_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + bf_t U_s, *U = &U_s; + bf_t V_s, *V = &V_s; + slimb_t n, prec1, l, i, K; + + assert(r != a); + + bf_init(s, T); + /* argument reduction 1 */ + /* T=a*2^n with 2/3 <= T <= 4/3 */ + { + bf_t U_s, *U = &U_s; + bf_set(T, a); + n = T->expn; + T->expn = 0; + /* U= ~ 2/3 */ + bf_init(s, U); + bf_set_ui(U, 0xaaaaaaaa); + U->expn = 0; + if (bf_cmp_lt(T, U)) { + T->expn++; + n--; + } + bf_delete(U); + } + // printf("n=%ld\n", n); + // bf_print_str("T", T); + + /* XXX: precision analysis */ + /* number of iterations for argument reduction 2 */ + K = bf_isqrt((prec + 1) / 2); + /* order of Taylor expansion */ + l = prec / (2 * K) + 1; + /* precision of the intermediate computations */ + prec1 = prec + K + 2 * l + 32; + + bf_init(s, U); + bf_init(s, V); + + /* Note: cancellation occurs here, so we use more precision (XXX: + reduce the precision by computing the exact cancellation) */ + bf_add_si(T, T, -1, BF_PREC_INF, BF_RNDN); + + /* argument reduction 2 */ + for(i = 0; i < K; i++) { + /* T = T / (1 + sqrt(1 + T)) */ + bf_add_si(U, T, 1, prec1, BF_RNDN); + bf_sqrt(V, U, prec1, BF_RNDF); + bf_add_si(U, V, 1, prec1, BF_RNDN); + bf_div(T, T, U, prec1, BF_RNDN); + } + + { + bf_t Y_s, *Y = &Y_s; + bf_t Y2_s, *Y2 = &Y2_s; + bf_init(s, Y); + bf_init(s, Y2); + + /* compute ln(1+x) = ln((1+y)/(1-y)) with y=x/(2+x) + = y + y^3/3 + ... + y^(2*l + 1) / (2*l+1) + with Y=Y^2 + = y*(1+Y/3+Y^2/5+...) = y*(1+Y*(1/3+Y*(1/5 + ...))) + */ + bf_add_si(Y, T, 2, prec1, BF_RNDN); + bf_div(Y, T, Y, prec1, BF_RNDN); + + bf_mul(Y2, Y, Y, prec1, BF_RNDN); + bf_set_ui(r, 0); + for(i = l; i >= 1; i--) { + bf_set_ui(U, 1); + bf_set_ui(V, 2 * i + 1); + bf_div(U, U, V, prec1, BF_RNDN); + bf_add(r, r, U, prec1, BF_RNDN); + bf_mul(r, r, Y2, prec1, BF_RNDN); + } + bf_add_si(r, r, 1, prec1, BF_RNDN); + bf_mul(r, r, Y, prec1, BF_RNDN); + bf_delete(Y); + bf_delete(Y2); + } + bf_delete(V); + bf_delete(U); + + /* multiplication by 2 for the Taylor expansion and undo the + argument reduction 2*/ + bf_mul_2exp(r, K + 1, BF_PREC_INF, BF_RNDZ); + + /* undo the argument reduction 1 */ + bf_const_log2(T, prec1, BF_RNDF); + bf_mul_si(T, T, n, prec1, BF_RNDN); + bf_add(r, r, T, prec1, BF_RNDN); + + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + + assert(r != a); + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + if (a->sign) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_inf(r, 0); + return 0; + } + } else { + bf_set_inf(r, 1); + return 0; + } + } + if (a->sign) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } + bf_init(s, T); + bf_set_ui(T, 1); + if (bf_cmp_eq(a, T)) { + bf_set_zero(r, 0); + bf_delete(T); + return 0; + } + bf_delete(T); + + return bf_ziv_rounding(r, a, prec, flags, bf_log_internal, NULL); +} + +/* x and y finite and x > 0 */ +static int bf_pow_generic(bf_t *r, const bf_t *x, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + const bf_t *y = opaque; + bf_t T_s, *T = &T_s; + limb_t prec1; + + bf_init(s, T); + /* XXX: proof for the added precision */ + prec1 = prec + 32; + bf_log(T, x, prec1, BF_RNDF | BF_FLAG_EXT_EXP); + bf_mul(T, T, y, prec1, BF_RNDF | BF_FLAG_EXT_EXP); + if (bf_is_nan(T)) + bf_set_nan(r); + else + bf_exp_internal(r, T, prec1, NULL); /* no overflow/underlow test needed */ + bf_delete(T); + return BF_ST_INEXACT; +} + +/* x and y finite, x > 0, y integer and y fits on one limb */ +static int bf_pow_int(bf_t *r, const bf_t *x, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + const bf_t *y = opaque; + bf_t T_s, *T = &T_s; + limb_t prec1; + int ret; + slimb_t y1; + + bf_get_limb(&y1, y, 0); + if (y1 < 0) + y1 = -y1; + /* XXX: proof for the added precision */ + prec1 = prec + ceil_log2(y1) * 2 + 8; + ret = bf_pow_ui(r, x, y1 < 0 ? -y1 : y1, prec1, BF_RNDN | BF_FLAG_EXT_EXP); + if (y->sign) { + bf_init(s, T); + bf_set_ui(T, 1); + ret |= bf_div(r, T, r, prec1, BF_RNDN | BF_FLAG_EXT_EXP); + bf_delete(T); + } + return ret; +} + +/* x must be a finite non zero float. Return TRUE if there is a + floating point number r such as x=r^(2^n) and return this floating + point number 'r'. Otherwise return FALSE and r is undefined. */ +static BOOL check_exact_power2n(bf_t *r, const bf_t *x, slimb_t n) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + slimb_t e, i, er; + limb_t v; + + /* x = m*2^e with m odd integer */ + e = bf_get_exp_min(x); + /* fast check on the exponent */ + if (n > (LIMB_BITS - 1)) { + if (e != 0) + return FALSE; + er = 0; + } else { + if ((e & (((limb_t)1 << n) - 1)) != 0) + return FALSE; + er = e >> n; + } + /* every perfect odd square = 1 modulo 8 */ + v = get_bits(x->tab, x->len, x->len * LIMB_BITS - x->expn + e); + if ((v & 7) != 1) + return FALSE; + + bf_init(s, T); + bf_set(T, x); + T->expn -= e; + for(i = 0; i < n; i++) { + if (i != 0) + bf_set(T, r); + if (bf_sqrtrem(r, NULL, T) != 0) + return FALSE; + } + r->expn += er; + return TRUE; +} + +/* prec = BF_PREC_INF is accepted for x and y integers and y >= 0 */ +int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + bf_t ytmp_s; + BOOL y_is_int, y_is_odd; + int r_sign, ret, rnd_mode; + slimb_t y_emin; + + if (x->len == 0 || y->len == 0) { + if (y->expn == BF_EXP_ZERO) { + /* pow(x, 0) = 1 */ + bf_set_ui(r, 1); + } else if (x->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else { + int cmp_x_abs_1; + bf_set_ui(r, 1); + cmp_x_abs_1 = bf_cmpu(x, r); + if (cmp_x_abs_1 == 0 && (flags & BF_POW_JS_QUIRKS) && + (y->expn >= BF_EXP_INF)) { + bf_set_nan(r); + } else if (cmp_x_abs_1 == 0 && + (!x->sign || y->expn != BF_EXP_NAN)) { + /* pow(1, y) = 1 even if y = NaN */ + /* pow(-1, +/-inf) = 1 */ + } else if (y->expn == BF_EXP_NAN) { + bf_set_nan(r); + } else if (y->expn == BF_EXP_INF) { + if (y->sign == (cmp_x_abs_1 > 0)) { + bf_set_zero(r, 0); + } else { + bf_set_inf(r, 0); + } + } else { + y_emin = bf_get_exp_min(y); + y_is_odd = (y_emin == 0); + if (y->sign == (x->expn == BF_EXP_ZERO)) { + bf_set_inf(r, y_is_odd & x->sign); + if (y->sign) { + /* pow(0, y) with y < 0 */ + return BF_ST_DIVIDE_ZERO; + } + } else { + bf_set_zero(r, y_is_odd & x->sign); + } + } + } + return 0; + } + bf_init(s, T); + bf_set(T, x); + y_emin = bf_get_exp_min(y); + y_is_int = (y_emin >= 0); + rnd_mode = flags & BF_RND_MASK; + if (x->sign) { + if (!y_is_int) { + bf_set_nan(r); + bf_delete(T); + return BF_ST_INVALID_OP; + } + y_is_odd = (y_emin == 0); + r_sign = y_is_odd; + /* change the directed rounding mode if the sign of the result + is changed */ + if (r_sign && (rnd_mode == BF_RNDD || rnd_mode == BF_RNDU)) + flags ^= 1; + bf_neg(T); + } else { + r_sign = 0; + } + + bf_set_ui(r, 1); + if (bf_cmp_eq(T, r)) { + /* abs(x) = 1: nothing more to do */ + ret = 0; + } else { + /* check the overflow/underflow cases */ + { + bf_t al_s, *al = &al_s; + bf_t ah_s, *ah = &ah_s; + limb_t precl = LIMB_BITS; + + bf_init(s, al); + bf_init(s, ah); + /* compute bounds of log(abs(x)) * y with a low precision */ + /* XXX: compute bf_log() once */ + /* XXX: add a fast test before this slow test */ + bf_log(al, T, precl, BF_RNDD); + bf_log(ah, T, precl, BF_RNDU); + bf_mul(al, al, y, precl, BF_RNDD ^ y->sign); + bf_mul(ah, ah, y, precl, BF_RNDU ^ y->sign); + ret = check_exp_underflow_overflow(s, r, al, ah, prec, flags); + bf_delete(al); + bf_delete(ah); + if (ret) + goto done; + } + + if (y_is_int) { + slimb_t T_bits, e; + int_pow: + T_bits = T->expn - bf_get_exp_min(T); + if (T_bits == 1) { + /* pow(2^b, y) = 2^(b*y) */ + bf_mul_si(T, y, T->expn - 1, LIMB_BITS, BF_RNDZ); + bf_get_limb(&e, T, 0); + bf_set_ui(r, 1); + ret = bf_mul_2exp(r, e, prec, flags); + } else if (prec == BF_PREC_INF) { + slimb_t y1; + /* specific case for infinite precision (integer case) */ + bf_get_limb(&y1, y, 0); + assert(!y->sign); + /* x must be an integer, so abs(x) >= 2 */ + if (y1 >= ((slimb_t)1 << BF_EXP_BITS_MAX)) { + bf_delete(T); + return bf_set_overflow(r, 0, BF_PREC_INF, flags); + } + ret = bf_pow_ui(r, T, y1, BF_PREC_INF, BF_RNDZ); + } else { + if (y->expn <= 31) { + /* small enough power: use exponentiation in all cases */ + } else if (y->sign) { + /* cannot be exact */ + goto general_case; + } else { + if (rnd_mode == BF_RNDF) + goto general_case; /* no need to track exact results */ + /* see if the result has a chance to be exact: + if x=a*2^b (a odd), x^y=a^y*2^(b*y) + x^y needs a precision of at least floor_log2(a)*y bits + */ + bf_mul_si(r, y, T_bits - 1, LIMB_BITS, BF_RNDZ); + bf_get_limb(&e, r, 0); + if (prec < e) + goto general_case; + } + ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_int, (void *)y); + } + } else { + if (rnd_mode != BF_RNDF) { + bf_t *y1; + if (y_emin < 0 && check_exact_power2n(r, T, -y_emin)) { + /* the problem is reduced to a power to an integer */ + bf_set(T, r); + y1 = &ytmp_s; + y1->tab = y->tab; + y1->len = y->len; + y1->sign = y->sign; + y1->expn = y->expn - y_emin; + y = y1; + goto int_pow; + } + } + general_case: + ret = bf_ziv_rounding(r, T, prec, flags, bf_pow_generic, (void *)y); + } + } + done: + bf_delete(T); + r->sign = r_sign; + return ret; +} + +/* compute sqrt(-2*x-x^2) to get |sin(x)| from cos(x) - 1. */ +static void bf_sqrt_sin(bf_t *r, const bf_t *x, limb_t prec1) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + bf_init(s, T); + bf_set(T, x); + bf_mul(r, T, T, prec1, BF_RNDN); + bf_mul_2exp(T, 1, BF_PREC_INF, BF_RNDZ); + bf_add(T, T, r, prec1, BF_RNDN); + bf_neg(T); + bf_sqrt(r, T, prec1, BF_RNDF); + bf_delete(T); +} + +static int bf_sincos(bf_t *s, bf_t *c, const bf_t *a, limb_t prec) +{ + bf_context_t *s1 = a->ctx; + bf_t T_s, *T = &T_s; + bf_t U_s, *U = &U_s; + bf_t r_s, *r = &r_s; + slimb_t K, prec1, i, l, mod, prec2; + int is_neg; + + assert(c != a && s != a); + + bf_init(s1, T); + bf_init(s1, U); + bf_init(s1, r); + + /* XXX: precision analysis */ + K = bf_isqrt(prec / 2); + l = prec / (2 * K) + 1; + prec1 = prec + 2 * K + l + 8; + + /* after the modulo reduction, -pi/4 <= T <= pi/4 */ + if (a->expn <= -1) { + /* abs(a) <= 0.25: no modulo reduction needed */ + bf_set(T, a); + mod = 0; + } else { + slimb_t cancel; + cancel = 0; + for(;;) { + prec2 = prec1 + a->expn + cancel; + bf_const_pi(U, prec2, BF_RNDF); + bf_mul_2exp(U, -1, BF_PREC_INF, BF_RNDZ); + bf_remquo(&mod, T, a, U, prec2, BF_RNDN, BF_RNDN); + // printf("T.expn=%ld prec2=%ld\n", T->expn, prec2); + if (mod == 0 || (T->expn != BF_EXP_ZERO && + (T->expn + prec2) >= (prec1 - 1))) + break; + /* increase the number of bits until the precision is good enough */ + cancel = bf_max(-T->expn, (cancel + 1) * 3 / 2); + } + mod &= 3; + } + + is_neg = T->sign; + + /* compute cosm1(x) = cos(x) - 1 */ + bf_mul(T, T, T, prec1, BF_RNDN); + bf_mul_2exp(T, -2 * K, BF_PREC_INF, BF_RNDZ); + + /* Taylor expansion: + -x^2/2 + x^4/4! - x^6/6! + ... + */ + bf_set_ui(r, 1); + for(i = l ; i >= 1; i--) { + bf_set_ui(U, 2 * i - 1); + bf_mul_ui(U, U, 2 * i, BF_PREC_INF, BF_RNDZ); + bf_div(U, T, U, prec1, BF_RNDN); + bf_mul(r, r, U, prec1, BF_RNDN); + bf_neg(r); + if (i != 1) + bf_add_si(r, r, 1, prec1, BF_RNDN); + } + bf_delete(U); + + /* undo argument reduction: + cosm1(2*x)= 2*(2*cosm1(x)+cosm1(x)^2) + */ + for(i = 0; i < K; i++) { + bf_mul(T, r, r, prec1, BF_RNDN); + bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ); + bf_add(r, r, T, prec1, BF_RNDN); + bf_mul_2exp(r, 1, BF_PREC_INF, BF_RNDZ); + } + bf_delete(T); + + if (c) { + if ((mod & 1) == 0) { + bf_add_si(c, r, 1, prec1, BF_RNDN); + } else { + bf_sqrt_sin(c, r, prec1); + c->sign = is_neg ^ 1; + } + c->sign ^= mod >> 1; + } + if (s) { + if ((mod & 1) == 0) { + bf_sqrt_sin(s, r, prec1); + s->sign = is_neg; + } else { + bf_add_si(s, r, 1, prec1, BF_RNDN); + } + s->sign ^= mod >> 1; + } + bf_delete(r); + return BF_ST_INEXACT; +} + +static int bf_cos_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + return bf_sincos(NULL, r, a, prec); +} + +int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_ui(r, 1); + return 0; + } + } + + /* small argument case: result = 1+r(x) with r(x) = -x^2/2 + + O(X^4). We assume r(x) < 2^(2*EXP(x) - 1). */ + if (a->expn < 0) { + slimb_t e; + e = 2 * a->expn - 1; + if (e < -(prec + 2)) { + bf_set_ui(r, 1); + return bf_add_epsilon(r, r, e, 1, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_cos_internal, NULL); +} + +static int bf_sin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + return bf_sincos(r, NULL, a, prec); +} + +int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + + /* small argument case: result = x+r(x) with r(x) = -x^3/6 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 2). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 2); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, 1 - a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_sin_internal, NULL); +} + +static int bf_tan_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + limb_t prec1; + + /* XXX: precision analysis */ + prec1 = prec + 8; + bf_init(s, T); + bf_sincos(r, T, a, prec1); + bf_div(r, r, T, prec1, BF_RNDF); + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + assert(r != a); + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + + /* small argument case: result = x+r(x) with r(x) = x^3/3 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 1). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 1); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_tan_internal, NULL); +} + +/* if add_pi2 is true, add pi/2 to the result (used for acos(x) to + avoid cancellation) */ +static int bf_atan_internal(bf_t *r, const bf_t *a, limb_t prec, + void *opaque) +{ + bf_context_t *s = r->ctx; + BOOL add_pi2 = (BOOL)(intptr_t)opaque; + bf_t T_s, *T = &T_s; + bf_t U_s, *U = &U_s; + bf_t V_s, *V = &V_s; + bf_t X2_s, *X2 = &X2_s; + int cmp_1; + slimb_t prec1, i, K, l; + + /* XXX: precision analysis */ + K = bf_isqrt((prec + 1) / 2); + l = prec / (2 * K) + 1; + prec1 = prec + K + 2 * l + 32; + // printf("prec=%d K=%d l=%d prec1=%d\n", (int)prec, (int)K, (int)l, (int)prec1); + + bf_init(s, T); + cmp_1 = (a->expn >= 1); /* a >= 1 */ + if (cmp_1) { + bf_set_ui(T, 1); + bf_div(T, T, a, prec1, BF_RNDN); + } else { + bf_set(T, a); + } + + /* abs(T) <= 1 */ + + /* argument reduction */ + + bf_init(s, U); + bf_init(s, V); + bf_init(s, X2); + for(i = 0; i < K; i++) { + /* T = T / (1 + sqrt(1 + T^2)) */ + bf_mul(U, T, T, prec1, BF_RNDN); + bf_add_si(U, U, 1, prec1, BF_RNDN); + bf_sqrt(V, U, prec1, BF_RNDN); + bf_add_si(V, V, 1, prec1, BF_RNDN); + bf_div(T, T, V, prec1, BF_RNDN); + } + + /* Taylor series: + x - x^3/3 + ... + (-1)^ l * y^(2*l + 1) / (2*l+1) + */ + bf_mul(X2, T, T, prec1, BF_RNDN); + bf_set_ui(r, 0); + for(i = l; i >= 1; i--) { + bf_set_si(U, 1); + bf_set_ui(V, 2 * i + 1); + bf_div(U, U, V, prec1, BF_RNDN); + bf_neg(r); + bf_add(r, r, U, prec1, BF_RNDN); + bf_mul(r, r, X2, prec1, BF_RNDN); + } + bf_neg(r); + bf_add_si(r, r, 1, prec1, BF_RNDN); + bf_mul(r, r, T, prec1, BF_RNDN); + + /* undo the argument reduction */ + bf_mul_2exp(r, K, BF_PREC_INF, BF_RNDZ); + + bf_delete(U); + bf_delete(V); + bf_delete(X2); + + i = add_pi2; + if (cmp_1 > 0) { + /* undo the inversion : r = sign(a)*PI/2 - r */ + bf_neg(r); + i += 1 - 2 * a->sign; + } + /* add i*(pi/2) with -1 <= i <= 2 */ + if (i != 0) { + bf_const_pi(T, prec1, BF_RNDF); + if (i != 2) + bf_mul_2exp(T, -1, BF_PREC_INF, BF_RNDZ); + T->sign = (i < 0); + bf_add(r, T, r, prec1, BF_RNDN); + } + + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + int res; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + /* -PI/2 or PI/2 */ + bf_const_pi_signed(r, a->sign, prec, flags); + bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ); + return BF_ST_INEXACT; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + + bf_init(s, T); + bf_set_ui(T, 1); + res = bf_cmpu(a, T); + bf_delete(T); + if (res == 0) { + /* short cut: abs(a) == 1 -> +/-pi/4 */ + bf_const_pi_signed(r, a->sign, prec, flags); + bf_mul_2exp(r, -2, BF_PREC_INF, BF_RNDZ); + return BF_ST_INEXACT; + } + + /* small argument case: result = x+r(x) with r(x) = -x^3/3 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 1). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 1); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, 1 - a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_atan_internal, (void *)FALSE); +} + +static int bf_atan2_internal(bf_t *r, const bf_t *y, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + const bf_t *x = opaque; + bf_t T_s, *T = &T_s; + limb_t prec1; + int ret; + + if (y->expn == BF_EXP_NAN || x->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } + + /* compute atan(y/x) assumming inf/inf = 1 and 0/0 = 0 */ + bf_init(s, T); + prec1 = prec + 32; + if (y->expn == BF_EXP_INF && x->expn == BF_EXP_INF) { + bf_set_ui(T, 1); + T->sign = y->sign ^ x->sign; + } else if (y->expn == BF_EXP_ZERO && x->expn == BF_EXP_ZERO) { + bf_set_zero(T, y->sign ^ x->sign); + } else { + bf_div(T, y, x, prec1, BF_RNDF); + } + ret = bf_atan(r, T, prec1, BF_RNDF); + + if (x->sign) { + /* if x < 0 (it includes -0), return sign(y)*pi + atan(y/x) */ + bf_const_pi(T, prec1, BF_RNDF); + T->sign = y->sign; + bf_add(r, r, T, prec1, BF_RNDN); + ret |= BF_ST_INEXACT; + } + + bf_delete(T); + return ret; +} + +int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, + limb_t prec, bf_flags_t flags) +{ + return bf_ziv_rounding(r, y, prec, flags, bf_atan2_internal, (void *)x); +} + +static int bf_asin_internal(bf_t *r, const bf_t *a, limb_t prec, void *opaque) +{ + bf_context_t *s = r->ctx; + BOOL is_acos = (BOOL)(intptr_t)opaque; + bf_t T_s, *T = &T_s; + limb_t prec1, prec2; + + /* asin(x) = atan(x/sqrt(1-x^2)) + acos(x) = pi/2 - asin(x) */ + prec1 = prec + 8; + /* increase the precision in x^2 to compensate the cancellation in + (1-x^2) if x is close to 1 */ + /* XXX: use less precision when possible */ + if (a->expn >= 0) + prec2 = BF_PREC_INF; + else + prec2 = prec1; + bf_init(s, T); + bf_mul(T, a, a, prec2, BF_RNDN); + bf_neg(T); + bf_add_si(T, T, 1, prec2, BF_RNDN); + + bf_sqrt(r, T, prec1, BF_RNDN); + bf_div(T, a, r, prec1, BF_RNDN); + if (is_acos) + bf_neg(T); + bf_atan_internal(r, T, prec1, (void *)(intptr_t)is_acos); + bf_delete(T); + return BF_ST_INEXACT; +} + +int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + int res; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_set_zero(r, a->sign); + return 0; + } + } + bf_init(s, T); + bf_set_ui(T, 1); + res = bf_cmpu(a, T); + bf_delete(T); + if (res > 0) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } + + /* small argument case: result = x+r(x) with r(x) = x^3/6 + + O(X^5). We assume r(x) < 2^(3*EXP(x) - 2). */ + if (a->expn < 0) { + slimb_t e; + e = sat_add(2 * a->expn, a->expn - 2); + if (e < a->expn - bf_max(prec + 2, a->len * LIMB_BITS + 2)) { + bf_set(r, a); + return bf_add_epsilon(r, r, e, a->sign, prec, flags); + } + } + + return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)FALSE); +} + +int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = r->ctx; + bf_t T_s, *T = &T_s; + int res; + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bf_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bf_const_pi(r, prec, flags); + bf_mul_2exp(r, -1, BF_PREC_INF, BF_RNDZ); + return BF_ST_INEXACT; + } + } + bf_init(s, T); + bf_set_ui(T, 1); + res = bf_cmpu(a, T); + bf_delete(T); + if (res > 0) { + bf_set_nan(r); + return BF_ST_INVALID_OP; + } else if (res == 0 && a->sign == 0) { + bf_set_zero(r, 0); + return 0; + } + + return bf_ziv_rounding(r, a, prec, flags, bf_asin_internal, (void *)TRUE); +} + +/***************************************************************/ +/* decimal floating point numbers */ + +#ifdef USE_BF_DEC + +#define adddq(r1, r0, a1, a0) \ + do { \ + limb_t __t = r0; \ + r0 += (a0); \ + r1 += (a1) + (r0 < __t); \ + } while (0) + +#define subdq(r1, r0, a1, a0) \ + do { \ + limb_t __t = r0; \ + r0 -= (a0); \ + r1 -= (a1) + (r0 > __t); \ + } while (0) + +#if LIMB_BITS == 64 + +/* Note: we assume __int128 is available */ +/* uint128_t defined in libbf.h */ +#define muldq(r1, r0, a, b) \ + do { \ + uint128_t __t; \ + __t = (uint128_t)(a) * (uint128_t)(b); \ + r0 = __t; \ + r1 = __t >> 64; \ + } while (0) + +#define divdq(q, r, a1, a0, b) \ + do { \ + uint128_t __t; \ + limb_t __b = (b); \ + __t = ((uint128_t)(a1) << 64) | (a0); \ + q = __t / __b; \ + r = __t % __b; \ + } while (0) + +#else + +#define muldq(r1, r0, a, b) \ + do { \ + uint64_t __t; \ + __t = (uint64_t)(a) * (uint64_t)(b); \ + r0 = __t; \ + r1 = __t >> 32; \ + } while (0) + +#define divdq(q, r, a1, a0, b) \ + do { \ + uint64_t __t; \ + limb_t __b = (b); \ + __t = ((uint64_t)(a1) << 32) | (a0); \ + q = __t / __b; \ + r = __t % __b; \ + } while (0) + +#endif /* LIMB_BITS != 64 */ + +#if LIMB_DIGITS == 19 + +/* WARNING: hardcoded for b = 1e19. It is assumed that: + 0 <= a1 < 2^63 */ +#define divdq_base(q, r, a1, a0)\ +do {\ + uint64_t __a0, __a1, __t0, __t1, __b = BF_DEC_BASE; \ + __a0 = a0;\ + __a1 = a1;\ + __t0 = __a1;\ + __t0 = shld(__t0, __a0, 1);\ + muldq(q, __t1, __t0, UINT64_C(17014118346046923173)); \ + muldq(__t1, __t0, q, __b);\ + subdq(__a1, __a0, __t1, __t0);\ + subdq(__a1, __a0, 1, __b * 2); \ + __t0 = (slimb_t)__a1 >> 1; \ + q += 2 + __t0;\ + adddq(__a1, __a0, 0, __b & __t0);\ + q += __a1; \ + __a0 += __b & __a1; \ + r = __a0;\ +} while(0) + +#elif LIMB_DIGITS == 9 + +/* WARNING: hardcoded for b = 1e9. It is assumed that: + 0 <= a1 < 2^29 */ +#define divdq_base(q, r, a1, a0)\ +do {\ + uint32_t __t0, __t1, __b = BF_DEC_BASE; \ + __t0 = a1;\ + __t1 = a0;\ + __t0 = (__t0 << 3) | (__t1 >> (32 - 3)); \ + muldq(q, __t1, __t0, 2305843009U);\ + r = a0 - q * __b;\ + __t1 = (r >= __b);\ + q += __t1;\ + if (__t1)\ + r -= __b;\ +} while(0) + +#endif + +/* fast integer division by a fixed constant */ + +typedef struct FastDivData { + limb_t m1; /* multiplier */ + int8_t shift1; + int8_t shift2; +} FastDivData; + +/* From "Division by Invariant Integers using Multiplication" by + Torborn Granlund and Peter L. Montgomery */ +/* d must be != 0 */ +static inline __maybe_unused void fast_udiv_init(FastDivData *s, limb_t d) +{ + int l; + limb_t q, r, m1; + if (d == 1) + l = 0; + else + l = 64 - clz64(d - 1); + divdq(q, r, ((limb_t)1 << l) - d, 0, d); + (void)r; + m1 = q + 1; + // printf("d=%lu l=%d m1=0x%016lx\n", d, l, m1); + s->m1 = m1; + s->shift1 = l; + if (s->shift1 > 1) + s->shift1 = 1; + s->shift2 = l - 1; + if (s->shift2 < 0) + s->shift2 = 0; +} + +static inline limb_t fast_udiv(limb_t a, const FastDivData *s) +{ + limb_t t0, t1; + muldq(t1, t0, s->m1, a); + t0 = (a - t1) >> s->shift1; + return (t1 + t0) >> s->shift2; +} + +/* contains 10^i */ +const limb_t mp_pow_dec[LIMB_DIGITS + 1] = { + 1U, + 10U, + 100U, + 1000U, + 10000U, + 100000U, + 1000000U, + 10000000U, + 100000000U, + 1000000000U, +#if LIMB_BITS == 64 + 10000000000U, + 100000000000U, + 1000000000000U, + 10000000000000U, + 100000000000000U, + 1000000000000000U, + 10000000000000000U, + 100000000000000000U, + 1000000000000000000U, + 10000000000000000000U, +#endif +}; + +/* precomputed from fast_udiv_init(10^i) */ +static const FastDivData mp_pow_div[LIMB_DIGITS + 1] = { +#if LIMB_BITS == 32 + { 0x00000001, 0, 0 }, + { 0x9999999a, 1, 3 }, + { 0x47ae147b, 1, 6 }, + { 0x0624dd30, 1, 9 }, + { 0xa36e2eb2, 1, 13 }, + { 0x4f8b588f, 1, 16 }, + { 0x0c6f7a0c, 1, 19 }, + { 0xad7f29ac, 1, 23 }, + { 0x5798ee24, 1, 26 }, + { 0x12e0be83, 1, 29 }, +#else + { 0x0000000000000001, 0, 0 }, + { 0x999999999999999a, 1, 3 }, + { 0x47ae147ae147ae15, 1, 6 }, + { 0x0624dd2f1a9fbe77, 1, 9 }, + { 0xa36e2eb1c432ca58, 1, 13 }, + { 0x4f8b588e368f0847, 1, 16 }, + { 0x0c6f7a0b5ed8d36c, 1, 19 }, + { 0xad7f29abcaf48579, 1, 23 }, + { 0x5798ee2308c39dfa, 1, 26 }, + { 0x12e0be826d694b2f, 1, 29 }, + { 0xb7cdfd9d7bdbab7e, 1, 33 }, + { 0x5fd7fe17964955fe, 1, 36 }, + { 0x19799812dea11198, 1, 39 }, + { 0xc25c268497681c27, 1, 43 }, + { 0x6849b86a12b9b01f, 1, 46 }, + { 0x203af9ee756159b3, 1, 49 }, + { 0xcd2b297d889bc2b7, 1, 53 }, + { 0x70ef54646d496893, 1, 56 }, + { 0x2725dd1d243aba0f, 1, 59 }, + { 0xd83c94fb6d2ac34d, 1, 63 }, +#endif +}; + +/* divide by 10^shift with 0 <= shift <= LIMB_DIGITS */ +static inline limb_t fast_shr_dec(limb_t a, int shift) +{ + return fast_udiv(a, &mp_pow_div[shift]); +} + +/* division and remainder by 10^shift */ +#define fast_shr_rem_dec(q, r, a, shift) q = fast_shr_dec(a, shift), r = a - q * mp_pow_dec[shift] + +limb_t mp_add_dec(limb_t *res, const limb_t *op1, const limb_t *op2, + mp_size_t n, limb_t carry) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t k, a, v; + + k=carry; + for(i=0;i<n;i++) { + /* XXX: reuse the trick in add_mod */ + v = op1[i]; + a = v + op2[i] + k - base; + k = a <= v; + if (!k) + a += base; + res[i]=a; + } + return k; +} + +limb_t mp_add_ui_dec(limb_t *tab, limb_t b, mp_size_t n) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t k, a, v; + + k=b; + for(i=0;i<n;i++) { + v = tab[i]; + a = v + k - base; + k = a <= v; + if (!k) + a += base; + tab[i] = a; + if (k == 0) + break; + } + return k; +} + +limb_t mp_sub_dec(limb_t *res, const limb_t *op1, const limb_t *op2, + mp_size_t n, limb_t carry) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t k, v, a; + + k=carry; + for(i=0;i<n;i++) { + v = op1[i]; + a = v - op2[i] - k; + k = a > v; + if (k) + a += base; + res[i] = a; + } + return k; +} + +limb_t mp_sub_ui_dec(limb_t *tab, limb_t b, mp_size_t n) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t k, v, a; + + k=b; + for(i=0;i<n;i++) { + v = tab[i]; + a = v - k; + k = a > v; + if (k) + a += base; + tab[i]=a; + if (k == 0) + break; + } + return k; +} + +/* taba[] = taba[] * b + l. 0 <= b, l <= base - 1. Return the high carry */ +limb_t mp_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, + limb_t b, limb_t l) +{ + mp_size_t i; + limb_t t0, t1, r; + + for(i = 0; i < n; i++) { + muldq(t1, t0, taba[i], b); + adddq(t1, t0, 0, l); + divdq_base(l, r, t1, t0); + tabr[i] = r; + } + return l; +} + +/* tabr[] += taba[] * b. 0 <= b <= base - 1. Return the value to add + to the high word */ +limb_t mp_add_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, + limb_t b) +{ + mp_size_t i; + limb_t l, t0, t1, r; + + l = 0; + for(i = 0; i < n; i++) { + muldq(t1, t0, taba[i], b); + adddq(t1, t0, 0, l); + adddq(t1, t0, 0, tabr[i]); + divdq_base(l, r, t1, t0); + tabr[i] = r; + } + return l; +} + +/* tabr[] -= taba[] * b. 0 <= b <= base - 1. Return the value to + substract to the high word. */ +limb_t mp_sub_mul1_dec(limb_t *tabr, const limb_t *taba, mp_size_t n, + limb_t b) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t l, t0, t1, r, a, v, c; + + /* XXX: optimize */ + l = 0; + for(i = 0; i < n; i++) { + muldq(t1, t0, taba[i], b); + adddq(t1, t0, 0, l); + divdq_base(l, r, t1, t0); + v = tabr[i]; + a = v - r; + c = a > v; + if (c) + a += base; + /* never bigger than base because r = 0 when l = base - 1 */ + l += c; + tabr[i] = a; + } + return l; +} + +/* size of the result : op1_size + op2_size. */ +void mp_mul_basecase_dec(limb_t *result, + const limb_t *op1, mp_size_t op1_size, + const limb_t *op2, mp_size_t op2_size) +{ + mp_size_t i; + limb_t r; + + result[op1_size] = mp_mul1_dec(result, op1, op1_size, op2[0], 0); + + for(i=1;i<op2_size;i++) { + r = mp_add_mul1_dec(result + i, op1, op1_size, op2[i]); + result[i + op1_size] = r; + } +} + +/* taba[] = (taba[] + r*base^na) / b. 0 <= b < base. 0 <= r < + b. Return the remainder. */ +limb_t mp_div1_dec(limb_t *tabr, const limb_t *taba, mp_size_t na, + limb_t b, limb_t r) +{ + limb_t base = BF_DEC_BASE; + mp_size_t i; + limb_t t0, t1, q; + int shift; + +#if (BF_DEC_BASE % 2) == 0 + if (b == 2) { + limb_t base_div2; + /* Note: only works if base is even */ + base_div2 = base >> 1; + if (r) + r = base_div2; + for(i = na - 1; i >= 0; i--) { + t0 = taba[i]; + tabr[i] = (t0 >> 1) + r; + r = 0; + if (t0 & 1) + r = base_div2; + } + if (r) + r = 1; + } else +#endif + if (na >= UDIV1NORM_THRESHOLD) { + shift = clz(b); + if (shift == 0) { + /* normalized case: b >= 2^(LIMB_BITS-1) */ + limb_t b_inv; + b_inv = udiv1norm_init(b); + for(i = na - 1; i >= 0; i--) { + muldq(t1, t0, r, base); + adddq(t1, t0, 0, taba[i]); + q = udiv1norm(&r, t1, t0, b, b_inv); + tabr[i] = q; + } + } else { + limb_t b_inv; + b <<= shift; + b_inv = udiv1norm_init(b); + for(i = na - 1; i >= 0; i--) { + muldq(t1, t0, r, base); + adddq(t1, t0, 0, taba[i]); + t1 = (t1 << shift) | (t0 >> (LIMB_BITS - shift)); + t0 <<= shift; + q = udiv1norm(&r, t1, t0, b, b_inv); + r >>= shift; + tabr[i] = q; + } + } + } else { + for(i = na - 1; i >= 0; i--) { + muldq(t1, t0, r, base); + adddq(t1, t0, 0, taba[i]); + divdq(q, r, t1, t0, b); + tabr[i] = q; + } + } + return r; +} + +static __maybe_unused void mp_print_str_dec(const char *str, + const limb_t *tab, slimb_t n) +{ + slimb_t i; + printf("%s=", str); + for(i = n - 1; i >= 0; i--) { + if (i != n - 1) + printf("_"); + printf("%0*" PRIu_LIMB, LIMB_DIGITS, tab[i]); + } + printf("\n"); +} + +static __maybe_unused void mp_print_str_h_dec(const char *str, + const limb_t *tab, slimb_t n, + limb_t high) +{ + slimb_t i; + printf("%s=", str); + printf("%0*" PRIu_LIMB, LIMB_DIGITS, high); + for(i = n - 1; i >= 0; i--) { + printf("_"); + printf("%0*" PRIu_LIMB, LIMB_DIGITS, tab[i]); + } + printf("\n"); +} + +//#define DEBUG_DIV_SLOW + +#define DIV_STATIC_ALLOC_LEN 16 + +/* return q = a / b and r = a % b. + + taba[na] must be allocated if tabb1[nb - 1] < B / 2. tabb1[nb - 1] + must be != zero. na must be >= nb. 's' can be NULL if tabb1[nb - 1] + >= B / 2. + + The remainder is is returned in taba and contains nb libms. tabq + contains na - nb + 1 limbs. No overlap is permitted. + + Running time of the standard method: (na - nb + 1) * nb + Return 0 if OK, -1 if memory alloc error +*/ +/* XXX: optimize */ +static int mp_div_dec(bf_context_t *s, limb_t *tabq, + limb_t *taba, mp_size_t na, + const limb_t *tabb1, mp_size_t nb) +{ + limb_t base = BF_DEC_BASE; + limb_t r, mult, t0, t1, a, c, q, v, *tabb; + mp_size_t i, j; + limb_t static_tabb[DIV_STATIC_ALLOC_LEN]; + +#ifdef DEBUG_DIV_SLOW + mp_print_str_dec("a", taba, na); + mp_print_str_dec("b", tabb1, nb); +#endif + + /* normalize tabb */ + r = tabb1[nb - 1]; + assert(r != 0); + i = na - nb; + if (r >= BF_DEC_BASE / 2) { + mult = 1; + tabb = (limb_t *)tabb1; + q = 1; + for(j = nb - 1; j >= 0; j--) { + if (taba[i + j] != tabb[j]) { + if (taba[i + j] < tabb[j]) + q = 0; + break; + } + } + tabq[i] = q; + if (q) { + mp_sub_dec(taba + i, taba + i, tabb, nb, 0); + } + i--; + } else { + mult = base / (r + 1); + if (likely(nb <= DIV_STATIC_ALLOC_LEN)) { + tabb = static_tabb; + } else { + tabb = bf_malloc(s, sizeof(limb_t) * nb); + if (!tabb) + return -1; + } + mp_mul1_dec(tabb, tabb1, nb, mult, 0); + taba[na] = mp_mul1_dec(taba, taba, na, mult, 0); + } + +#ifdef DEBUG_DIV_SLOW + printf("mult=" FMT_LIMB "\n", mult); + mp_print_str_dec("a_norm", taba, na + 1); + mp_print_str_dec("b_norm", tabb, nb); +#endif + + for(; i >= 0; i--) { + if (unlikely(taba[i + nb] >= tabb[nb - 1])) { + /* XXX: check if it is really possible */ + q = base - 1; + } else { + muldq(t1, t0, taba[i + nb], base); + adddq(t1, t0, 0, taba[i + nb - 1]); + divdq(q, r, t1, t0, tabb[nb - 1]); + } + // printf("i=%d q1=%ld\n", i, q); + + r = mp_sub_mul1_dec(taba + i, tabb, nb, q); + // mp_dump("r1", taba + i, nb, bd); + // printf("r2=%ld\n", r); + + v = taba[i + nb]; + a = v - r; + c = a > v; + if (c) + a += base; + taba[i + nb] = a; + + if (c != 0) { + /* negative result */ + for(;;) { + q--; + c = mp_add_dec(taba + i, taba + i, tabb, nb, 0); + /* propagate carry and test if positive result */ + if (c != 0) { + if (++taba[i + nb] == base) { + break; + } + } + } + } + tabq[i] = q; + } + +#ifdef DEBUG_DIV_SLOW + mp_print_str_dec("q", tabq, na - nb + 1); + mp_print_str_dec("r", taba, nb); +#endif + + /* remove the normalization */ + if (mult != 1) { + mp_div1_dec(taba, taba, nb, mult, 0); + if (unlikely(tabb != static_tabb)) + bf_free(s, tabb); + } + return 0; +} + +/* divide by 10^shift */ +static limb_t mp_shr_dec(limb_t *tab_r, const limb_t *tab, mp_size_t n, + limb_t shift, limb_t high) +{ + mp_size_t i; + limb_t l, a, q, r; + + assert(shift >= 1 && shift < LIMB_DIGITS); + l = high; + for(i = n - 1; i >= 0; i--) { + a = tab[i]; + fast_shr_rem_dec(q, r, a, shift); + tab_r[i] = q + l * mp_pow_dec[LIMB_DIGITS - shift]; + l = r; + } + return l; +} + +/* multiply by 10^shift */ +static limb_t mp_shl_dec(limb_t *tab_r, const limb_t *tab, mp_size_t n, + limb_t shift, limb_t low) +{ + mp_size_t i; + limb_t l, a, q, r; + + assert(shift >= 1 && shift < LIMB_DIGITS); + l = low; + for(i = 0; i < n; i++) { + a = tab[i]; + fast_shr_rem_dec(q, r, a, LIMB_DIGITS - shift); + tab_r[i] = r * mp_pow_dec[shift] + l; + l = q; + } + return l; +} + +static limb_t mp_sqrtrem2_dec(limb_t *tabs, limb_t *taba) +{ + int k; + dlimb_t a, b, r; + limb_t taba1[2], s, r0, r1; + + /* convert to binary and normalize */ + a = (dlimb_t)taba[1] * BF_DEC_BASE + taba[0]; + k = clz(a >> LIMB_BITS) & ~1; + b = a << k; + taba1[0] = b; + taba1[1] = b >> LIMB_BITS; + mp_sqrtrem2(&s, taba1); + s >>= (k >> 1); + /* convert the remainder back to decimal */ + r = a - (dlimb_t)s * (dlimb_t)s; + divdq_base(r1, r0, r >> LIMB_BITS, r); + taba[0] = r0; + tabs[0] = s; + return r1; +} + +//#define DEBUG_SQRTREM_DEC + +/* tmp_buf must contain (n / 2 + 1 limbs) */ +static limb_t mp_sqrtrem_rec_dec(limb_t *tabs, limb_t *taba, limb_t n, + limb_t *tmp_buf) +{ + limb_t l, h, rh, ql, qh, c, i; + + if (n == 1) + return mp_sqrtrem2_dec(tabs, taba); +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_dec("a", taba, 2 * n); +#endif + l = n / 2; + h = n - l; + qh = mp_sqrtrem_rec_dec(tabs + l, taba + 2 * l, h, tmp_buf); +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_dec("s1", tabs + l, h); + mp_print_str_h_dec("r1", taba + 2 * l, h, qh); + mp_print_str_h_dec("r2", taba + l, n, qh); +#endif + + /* the remainder is in taba + 2 * l. Its high bit is in qh */ + if (qh) { + mp_sub_dec(taba + 2 * l, taba + 2 * l, tabs + l, h, 0); + } + /* instead of dividing by 2*s, divide by s (which is normalized) + and update q and r */ + mp_div_dec(NULL, tmp_buf, taba + l, n, tabs + l, h); + qh += tmp_buf[l]; + for(i = 0; i < l; i++) + tabs[i] = tmp_buf[i]; + ql = mp_div1_dec(tabs, tabs, l, 2, qh & 1); + qh = qh >> 1; /* 0 or 1 */ + if (ql) + rh = mp_add_dec(taba + l, taba + l, tabs + l, h, 0); + else + rh = 0; +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_h_dec("q", tabs, l, qh); + mp_print_str_h_dec("u", taba + l, h, rh); +#endif + + mp_add_ui_dec(tabs + l, qh, h); +#ifdef DEBUG_SQRTREM_DEC + mp_print_str_dec("s2", tabs, n); +#endif + + /* q = qh, tabs[l - 1 ... 0], r = taba[n - 1 ... l] */ + /* subtract q^2. if qh = 1 then q = B^l, so we can take shortcuts */ + if (qh) { + c = qh; + } else { + mp_mul_basecase_dec(taba + n, tabs, l, tabs, l); + c = mp_sub_dec(taba, taba, taba + n, 2 * l, 0); + } + rh -= mp_sub_ui_dec(taba + 2 * l, c, n - 2 * l); + if ((slimb_t)rh < 0) { + mp_sub_ui_dec(tabs, 1, n); + rh += mp_add_mul1_dec(taba, tabs, n, 2); + rh += mp_add_ui_dec(taba, 1, n); + } + return rh; +} + +/* 'taba' has 2*n limbs with n >= 1 and taba[2*n-1] >= B/4. Return (s, + r) with s=floor(sqrt(a)) and r=a-s^2. 0 <= r <= 2 * s. tabs has n + limbs. r is returned in the lower n limbs of taba. Its r[n] is the + returned value of the function. */ +int mp_sqrtrem_dec(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n) +{ + limb_t tmp_buf1[8]; + limb_t *tmp_buf; + mp_size_t n2; + n2 = n / 2 + 1; + if (n2 <= countof(tmp_buf1)) { + tmp_buf = tmp_buf1; + } else { + tmp_buf = bf_malloc(s, sizeof(limb_t) * n2); + if (!tmp_buf) + return -1; + } + taba[n] = mp_sqrtrem_rec_dec(tabs, taba, n, tmp_buf); + if (tmp_buf != tmp_buf1) + bf_free(s, tmp_buf); + return 0; +} + +/* return the number of leading zero digits, from 0 to LIMB_DIGITS */ +static int clz_dec(limb_t a) +{ + if (a == 0) + return LIMB_DIGITS; + switch(LIMB_BITS - 1 - clz(a)) { + case 0: /* 1-1 */ + return LIMB_DIGITS - 1; + case 1: /* 2-3 */ + return LIMB_DIGITS - 1; + case 2: /* 4-7 */ + return LIMB_DIGITS - 1; + case 3: /* 8-15 */ + if (a < 10) + return LIMB_DIGITS - 1; + else + return LIMB_DIGITS - 2; + case 4: /* 16-31 */ + return LIMB_DIGITS - 2; + case 5: /* 32-63 */ + return LIMB_DIGITS - 2; + case 6: /* 64-127 */ + if (a < 100) + return LIMB_DIGITS - 2; + else + return LIMB_DIGITS - 3; + case 7: /* 128-255 */ + return LIMB_DIGITS - 3; + case 8: /* 256-511 */ + return LIMB_DIGITS - 3; + case 9: /* 512-1023 */ + if (a < 1000) + return LIMB_DIGITS - 3; + else + return LIMB_DIGITS - 4; + case 10: /* 1024-2047 */ + return LIMB_DIGITS - 4; + case 11: /* 2048-4095 */ + return LIMB_DIGITS - 4; + case 12: /* 4096-8191 */ + return LIMB_DIGITS - 4; + case 13: /* 8192-16383 */ + if (a < 10000) + return LIMB_DIGITS - 4; + else + return LIMB_DIGITS - 5; + case 14: /* 16384-32767 */ + return LIMB_DIGITS - 5; + case 15: /* 32768-65535 */ + return LIMB_DIGITS - 5; + case 16: /* 65536-131071 */ + if (a < 100000) + return LIMB_DIGITS - 5; + else + return LIMB_DIGITS - 6; + case 17: /* 131072-262143 */ + return LIMB_DIGITS - 6; + case 18: /* 262144-524287 */ + return LIMB_DIGITS - 6; + case 19: /* 524288-1048575 */ + if (a < 1000000) + return LIMB_DIGITS - 6; + else + return LIMB_DIGITS - 7; + case 20: /* 1048576-2097151 */ + return LIMB_DIGITS - 7; + case 21: /* 2097152-4194303 */ + return LIMB_DIGITS - 7; + case 22: /* 4194304-8388607 */ + return LIMB_DIGITS - 7; + case 23: /* 8388608-16777215 */ + if (a < 10000000) + return LIMB_DIGITS - 7; + else + return LIMB_DIGITS - 8; + case 24: /* 16777216-33554431 */ + return LIMB_DIGITS - 8; + case 25: /* 33554432-67108863 */ + return LIMB_DIGITS - 8; + case 26: /* 67108864-134217727 */ + if (a < 100000000) + return LIMB_DIGITS - 8; + else + return LIMB_DIGITS - 9; +#if LIMB_BITS == 64 + case 27: /* 134217728-268435455 */ + return LIMB_DIGITS - 9; + case 28: /* 268435456-536870911 */ + return LIMB_DIGITS - 9; + case 29: /* 536870912-1073741823 */ + if (a < 1000000000) + return LIMB_DIGITS - 9; + else + return LIMB_DIGITS - 10; + case 30: /* 1073741824-2147483647 */ + return LIMB_DIGITS - 10; + case 31: /* 2147483648-4294967295 */ + return LIMB_DIGITS - 10; + case 32: /* 4294967296-8589934591 */ + return LIMB_DIGITS - 10; + case 33: /* 8589934592-17179869183 */ + if (a < 10000000000) + return LIMB_DIGITS - 10; + else + return LIMB_DIGITS - 11; + case 34: /* 17179869184-34359738367 */ + return LIMB_DIGITS - 11; + case 35: /* 34359738368-68719476735 */ + return LIMB_DIGITS - 11; + case 36: /* 68719476736-137438953471 */ + if (a < 100000000000) + return LIMB_DIGITS - 11; + else + return LIMB_DIGITS - 12; + case 37: /* 137438953472-274877906943 */ + return LIMB_DIGITS - 12; + case 38: /* 274877906944-549755813887 */ + return LIMB_DIGITS - 12; + case 39: /* 549755813888-1099511627775 */ + if (a < 1000000000000) + return LIMB_DIGITS - 12; + else + return LIMB_DIGITS - 13; + case 40: /* 1099511627776-2199023255551 */ + return LIMB_DIGITS - 13; + case 41: /* 2199023255552-4398046511103 */ + return LIMB_DIGITS - 13; + case 42: /* 4398046511104-8796093022207 */ + return LIMB_DIGITS - 13; + case 43: /* 8796093022208-17592186044415 */ + if (a < 10000000000000) + return LIMB_DIGITS - 13; + else + return LIMB_DIGITS - 14; + case 44: /* 17592186044416-35184372088831 */ + return LIMB_DIGITS - 14; + case 45: /* 35184372088832-70368744177663 */ + return LIMB_DIGITS - 14; + case 46: /* 70368744177664-140737488355327 */ + if (a < 100000000000000) + return LIMB_DIGITS - 14; + else + return LIMB_DIGITS - 15; + case 47: /* 140737488355328-281474976710655 */ + return LIMB_DIGITS - 15; + case 48: /* 281474976710656-562949953421311 */ + return LIMB_DIGITS - 15; + case 49: /* 562949953421312-1125899906842623 */ + if (a < 1000000000000000) + return LIMB_DIGITS - 15; + else + return LIMB_DIGITS - 16; + case 50: /* 1125899906842624-2251799813685247 */ + return LIMB_DIGITS - 16; + case 51: /* 2251799813685248-4503599627370495 */ + return LIMB_DIGITS - 16; + case 52: /* 4503599627370496-9007199254740991 */ + return LIMB_DIGITS - 16; + case 53: /* 9007199254740992-18014398509481983 */ + if (a < 10000000000000000) + return LIMB_DIGITS - 16; + else + return LIMB_DIGITS - 17; + case 54: /* 18014398509481984-36028797018963967 */ + return LIMB_DIGITS - 17; + case 55: /* 36028797018963968-72057594037927935 */ + return LIMB_DIGITS - 17; + case 56: /* 72057594037927936-144115188075855871 */ + if (a < 100000000000000000) + return LIMB_DIGITS - 17; + else + return LIMB_DIGITS - 18; + case 57: /* 144115188075855872-288230376151711743 */ + return LIMB_DIGITS - 18; + case 58: /* 288230376151711744-576460752303423487 */ + return LIMB_DIGITS - 18; + case 59: /* 576460752303423488-1152921504606846975 */ + if (a < 1000000000000000000) + return LIMB_DIGITS - 18; + else + return LIMB_DIGITS - 19; +#endif + default: + return 0; + } +} + +/* for debugging */ +void bfdec_print_str(const char *str, const bfdec_t *a) +{ + slimb_t i; + printf("%s=", str); + + if (a->expn == BF_EXP_NAN) { + printf("NaN"); + } else { + if (a->sign) + putchar('-'); + if (a->expn == BF_EXP_ZERO) { + putchar('0'); + } else if (a->expn == BF_EXP_INF) { + printf("Inf"); + } else { + printf("0."); + for(i = a->len - 1; i >= 0; i--) + printf("%0*" PRIu_LIMB, LIMB_DIGITS, a->tab[i]); + printf("e%" PRId_LIMB, a->expn); + } + } + printf("\n"); +} + +/* return != 0 if one digit between 0 and bit_pos inclusive is not zero. */ +static inline limb_t scan_digit_nz(const bfdec_t *r, slimb_t bit_pos) +{ + slimb_t pos; + limb_t v, q; + int shift; + + if (bit_pos < 0) + return 0; + pos = (limb_t)bit_pos / LIMB_DIGITS; + shift = (limb_t)bit_pos % LIMB_DIGITS; + fast_shr_rem_dec(q, v, r->tab[pos], shift + 1); + (void)q; + if (v != 0) + return 1; + pos--; + while (pos >= 0) { + if (r->tab[pos] != 0) + return 1; + pos--; + } + return 0; +} + +static limb_t get_digit(const limb_t *tab, limb_t len, slimb_t pos) +{ + slimb_t i; + int shift; + i = floor_div(pos, LIMB_DIGITS); + if (i < 0 || i >= len) + return 0; + shift = pos - i * LIMB_DIGITS; + return fast_shr_dec(tab[i], shift) % 10; +} + +/* return the addend for rounding. Note that prec can be <= 0 for bf_rint() */ +static int bfdec_get_rnd_add(int *pret, const bfdec_t *r, limb_t l, + slimb_t prec, int rnd_mode) +{ + int add_one, inexact; + limb_t digit1, digit0; + + // bfdec_print_str("get_rnd_add", r); + if (rnd_mode == BF_RNDF) { + digit0 = 1; /* faithful rounding does not honor the INEXACT flag */ + } else { + /* starting limb for bit 'prec + 1' */ + digit0 = scan_digit_nz(r, l * LIMB_DIGITS - 1 - bf_max(0, prec + 1)); + } + + /* get the digit at 'prec' */ + digit1 = get_digit(r->tab, l, l * LIMB_DIGITS - 1 - prec); + inexact = (digit1 | digit0) != 0; + + add_one = 0; + switch(rnd_mode) { + case BF_RNDZ: + break; + case BF_RNDN: + if (digit1 == 5) { + if (digit0) { + add_one = 1; + } else { + /* round to even */ + add_one = + get_digit(r->tab, l, l * LIMB_DIGITS - 1 - (prec - 1)) & 1; + } + } else if (digit1 > 5) { + add_one = 1; + } + break; + case BF_RNDD: + case BF_RNDU: + if (r->sign == (rnd_mode == BF_RNDD)) + add_one = inexact; + break; + case BF_RNDNA: + case BF_RNDF: + add_one = (digit1 >= 5); + break; + case BF_RNDA: + add_one = inexact; + break; + default: + abort(); + } + + if (inexact) + *pret |= BF_ST_INEXACT; + return add_one; +} + +/* round to prec1 bits assuming 'r' is non zero and finite. 'r' is + assumed to have length 'l' (1 <= l <= r->len). prec1 can be + BF_PREC_INF. BF_FLAG_SUBNORMAL is not supported. Cannot fail with + BF_ST_MEM_ERROR. + */ +static int __bfdec_round(bfdec_t *r, limb_t prec1, bf_flags_t flags, limb_t l) +{ + int shift, add_one, rnd_mode, ret; + slimb_t i, bit_pos, pos, e_min, e_max, e_range, prec; + + /* XXX: align to IEEE 754 2008 for decimal numbers ? */ + e_range = (limb_t)1 << (bf_get_exp_bits(flags) - 1); + e_min = -e_range + 3; + e_max = e_range; + + if (flags & BF_FLAG_RADPNT_PREC) { + /* 'prec' is the precision after the decimal point */ + if (prec1 != BF_PREC_INF) + prec = r->expn + prec1; + else + prec = prec1; + } else if (unlikely(r->expn < e_min) && (flags & BF_FLAG_SUBNORMAL)) { + /* restrict the precision in case of potentially subnormal + result */ + assert(prec1 != BF_PREC_INF); + prec = prec1 - (e_min - r->expn); + } else { + prec = prec1; + } + + /* round to prec bits */ + rnd_mode = flags & BF_RND_MASK; + ret = 0; + add_one = bfdec_get_rnd_add(&ret, r, l, prec, rnd_mode); + + if (prec <= 0) { + if (add_one) { + bfdec_resize(r, 1); /* cannot fail because r is non zero */ + r->tab[0] = BF_DEC_BASE / 10; + r->expn += 1 - prec; + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + return ret; + } else { + goto underflow; + } + } else if (add_one) { + limb_t carry; + + /* add one starting at digit 'prec - 1' */ + bit_pos = l * LIMB_DIGITS - 1 - (prec - 1); + pos = bit_pos / LIMB_DIGITS; + carry = mp_pow_dec[bit_pos % LIMB_DIGITS]; + carry = mp_add_ui_dec(r->tab + pos, carry, l - pos); + if (carry) { + /* shift right by one digit */ + mp_shr_dec(r->tab + pos, r->tab + pos, l - pos, 1, 1); + r->expn++; + } + } + + /* check underflow */ + if (unlikely(r->expn < e_min)) { + if (flags & BF_FLAG_SUBNORMAL) { + /* if inexact, also set the underflow flag */ + if (ret & BF_ST_INEXACT) + ret |= BF_ST_UNDERFLOW; + } else { + underflow: + bfdec_set_zero(r, r->sign); + ret |= BF_ST_UNDERFLOW | BF_ST_INEXACT; + return ret; + } + } + + /* check overflow */ + if (unlikely(r->expn > e_max)) { + bfdec_set_inf(r, r->sign); + ret |= BF_ST_OVERFLOW | BF_ST_INEXACT; + return ret; + } + + /* keep the bits starting at 'prec - 1' */ + bit_pos = l * LIMB_DIGITS - 1 - (prec - 1); + i = floor_div(bit_pos, LIMB_DIGITS); + if (i >= 0) { + shift = smod(bit_pos, LIMB_DIGITS); + if (shift != 0) { + r->tab[i] = fast_shr_dec(r->tab[i], shift) * + mp_pow_dec[shift]; + } + } else { + i = 0; + } + /* remove trailing zeros */ + while (r->tab[i] == 0) + i++; + if (i > 0) { + l -= i; + memmove(r->tab, r->tab + i, l * sizeof(limb_t)); + } + bfdec_resize(r, l); /* cannot fail */ + return ret; +} + +/* Cannot fail with BF_ST_MEM_ERROR. */ +int bfdec_round(bfdec_t *r, limb_t prec, bf_flags_t flags) +{ + if (r->len == 0) + return 0; + return __bfdec_round(r, prec, flags, r->len); +} + +/* 'r' must be a finite number. Cannot fail with BF_ST_MEM_ERROR. */ +int bfdec_normalize_and_round(bfdec_t *r, limb_t prec1, bf_flags_t flags) +{ + limb_t l, v; + int shift, ret; + + // bfdec_print_str("bf_renorm", r); + l = r->len; + while (l > 0 && r->tab[l - 1] == 0) + l--; + if (l == 0) { + /* zero */ + r->expn = BF_EXP_ZERO; + bfdec_resize(r, 0); /* cannot fail */ + ret = 0; + } else { + r->expn -= (r->len - l) * LIMB_DIGITS; + /* shift to have the MSB set to '1' */ + v = r->tab[l - 1]; + shift = clz_dec(v); + if (shift != 0) { + mp_shl_dec(r->tab, r->tab, l, shift, 0); + r->expn -= shift; + } + ret = __bfdec_round(r, prec1, flags, l); + } + // bf_print_str("r_final", r); + return ret; +} + +int bfdec_set_ui(bfdec_t *r, uint64_t v) +{ +#if LIMB_BITS == 32 + if (v >= BF_DEC_BASE * BF_DEC_BASE) { + if (bfdec_resize(r, 3)) + goto fail; + r->tab[0] = v % BF_DEC_BASE; + v /= BF_DEC_BASE; + r->tab[1] = v % BF_DEC_BASE; + r->tab[2] = v / BF_DEC_BASE; + r->expn = 3 * LIMB_DIGITS; + } else +#endif + if (v >= BF_DEC_BASE) { + if (bfdec_resize(r, 2)) + goto fail; + r->tab[0] = v % BF_DEC_BASE; + r->tab[1] = v / BF_DEC_BASE; + r->expn = 2 * LIMB_DIGITS; + } else { + if (bfdec_resize(r, 1)) + goto fail; + r->tab[0] = v; + r->expn = LIMB_DIGITS; + } + r->sign = 0; + return bfdec_normalize_and_round(r, BF_PREC_INF, 0); + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bfdec_set_si(bfdec_t *r, int64_t v) +{ + int ret; + if (v < 0) { + ret = bfdec_set_ui(r, -v); + r->sign = 1; + } else { + ret = bfdec_set_ui(r, v); + } + return ret; +} + +static int bfdec_add_internal(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, bf_flags_t flags, int b_neg) +{ + bf_context_t *s = r->ctx; + int is_sub, cmp_res, a_sign, b_sign, ret; + + a_sign = a->sign; + b_sign = b->sign ^ b_neg; + is_sub = a_sign ^ b_sign; + cmp_res = bfdec_cmpu(a, b); + if (cmp_res < 0) { + const bfdec_t *tmp; + tmp = a; + a = b; + b = tmp; + a_sign = b_sign; /* b_sign is never used later */ + } + /* abs(a) >= abs(b) */ + if (cmp_res == 0 && is_sub && a->expn < BF_EXP_INF) { + /* zero result */ + bfdec_set_zero(r, (flags & BF_RND_MASK) == BF_RNDD); + ret = 0; + } else if (a->len == 0 || b->len == 0) { + ret = 0; + if (a->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN) { + /* at least one operand is NaN */ + bfdec_set_nan(r); + ret = 0; + } else if (b->expn == BF_EXP_INF && is_sub) { + /* infinities with different signs */ + bfdec_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bfdec_set_inf(r, a_sign); + } + } else { + /* at least one zero and not subtract */ + if (bfdec_set(r, a)) + return BF_ST_MEM_ERROR; + r->sign = a_sign; + goto renorm; + } + } else { + slimb_t d, a_offset, b_offset, i, r_len; + limb_t carry; + limb_t *b1_tab; + int b_shift; + mp_size_t b1_len; + + d = a->expn - b->expn; + + /* XXX: not efficient in time and memory if the precision is + not infinite */ + r_len = bf_max(a->len, b->len + (d + LIMB_DIGITS - 1) / LIMB_DIGITS); + if (bfdec_resize(r, r_len)) + goto fail; + r->sign = a_sign; + r->expn = a->expn; + + a_offset = r_len - a->len; + for(i = 0; i < a_offset; i++) + r->tab[i] = 0; + for(i = 0; i < a->len; i++) + r->tab[a_offset + i] = a->tab[i]; + + b_shift = d % LIMB_DIGITS; + if (b_shift == 0) { + b1_len = b->len; + b1_tab = (limb_t *)b->tab; + } else { + b1_len = b->len + 1; + b1_tab = bf_malloc(s, sizeof(limb_t) * b1_len); + if (!b1_tab) + goto fail; + b1_tab[0] = mp_shr_dec(b1_tab + 1, b->tab, b->len, b_shift, 0) * + mp_pow_dec[LIMB_DIGITS - b_shift]; + } + b_offset = r_len - (b->len + (d + LIMB_DIGITS - 1) / LIMB_DIGITS); + + if (is_sub) { + carry = mp_sub_dec(r->tab + b_offset, r->tab + b_offset, + b1_tab, b1_len, 0); + if (carry != 0) { + carry = mp_sub_ui_dec(r->tab + b_offset + b1_len, carry, + r_len - (b_offset + b1_len)); + assert(carry == 0); + } + } else { + carry = mp_add_dec(r->tab + b_offset, r->tab + b_offset, + b1_tab, b1_len, 0); + if (carry != 0) { + carry = mp_add_ui_dec(r->tab + b_offset + b1_len, carry, + r_len - (b_offset + b1_len)); + } + if (carry != 0) { + if (bfdec_resize(r, r_len + 1)) { + if (b_shift != 0) + bf_free(s, b1_tab); + goto fail; + } + r->tab[r_len] = 1; + r->expn += LIMB_DIGITS; + } + } + if (b_shift != 0) + bf_free(s, b1_tab); + renorm: + ret = bfdec_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +static int __bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bfdec_add_internal(r, a, b, prec, flags, 0); +} + +static int __bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bfdec_add_internal(r, a, b, prec, flags, 1); +} + +int bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, + (bf_op2_func_t *)__bfdec_add); +} + +int bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, + (bf_op2_func_t *)__bfdec_sub); +} + +int bfdec_mul(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + int ret, r_sign; + + if (a->len < b->len) { + const bfdec_t *tmp = a; + a = b; + b = tmp; + } + r_sign = a->sign ^ b->sign; + /* here b->len <= a->len */ + if (b->len == 0) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + ret = 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_INF) { + if ((a->expn == BF_EXP_INF && b->expn == BF_EXP_ZERO) || + (a->expn == BF_EXP_ZERO && b->expn == BF_EXP_INF)) { + bfdec_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + bfdec_set_inf(r, r_sign); + ret = 0; + } + } else { + bfdec_set_zero(r, r_sign); + ret = 0; + } + } else { + bfdec_t tmp, *r1 = NULL; + limb_t a_len, b_len; + limb_t *a_tab, *b_tab; + + a_len = a->len; + b_len = b->len; + a_tab = a->tab; + b_tab = b->tab; + + if (r == a || r == b) { + bfdec_init(r->ctx, &tmp); + r1 = r; + r = &tmp; + } + if (bfdec_resize(r, a_len + b_len)) { + bfdec_set_nan(r); + ret = BF_ST_MEM_ERROR; + goto done; + } + mp_mul_basecase_dec(r->tab, a_tab, a_len, b_tab, b_len); + r->sign = r_sign; + r->expn = a->expn + b->expn; + ret = bfdec_normalize_and_round(r, prec, flags); + done: + if (r == &tmp) + bfdec_move(r1, &tmp); + } + return ret; +} + +int bfdec_mul_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bfdec_t b; + int ret; + bfdec_init(r->ctx, &b); + ret = bfdec_set_si(&b, b1); + ret |= bfdec_mul(r, a, &b, prec, flags); + bfdec_delete(&b); + return ret; +} + +int bfdec_add_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags) +{ + bfdec_t b; + int ret; + + bfdec_init(r->ctx, &b); + ret = bfdec_set_si(&b, b1); + ret |= bfdec_add(r, a, &b, prec, flags); + bfdec_delete(&b); + return ret; +} + +static int __bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, + limb_t prec, bf_flags_t flags) +{ + int ret, r_sign; + limb_t n, nb, precl; + + r_sign = a->sign ^ b->sign; + if (a->expn >= BF_EXP_INF || b->expn >= BF_EXP_INF) { + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF && b->expn == BF_EXP_INF) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else if (a->expn == BF_EXP_INF) { + bfdec_set_inf(r, r_sign); + return 0; + } else { + bfdec_set_zero(r, r_sign); + return 0; + } + } else if (a->expn == BF_EXP_ZERO) { + if (b->expn == BF_EXP_ZERO) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bfdec_set_zero(r, r_sign); + return 0; + } + } else if (b->expn == BF_EXP_ZERO) { + bfdec_set_inf(r, r_sign); + return BF_ST_DIVIDE_ZERO; + } + + nb = b->len; + if (prec == BF_PREC_INF) { + /* infinite precision: return BF_ST_INVALID_OP if not an exact + result */ + /* XXX: check */ + precl = nb + 1; + } else if (flags & BF_FLAG_RADPNT_PREC) { + /* number of digits after the decimal point */ + /* XXX: check (2 extra digits for rounding + 2 digits) */ + precl = (bf_max(a->expn - b->expn, 0) + 2 + + prec + 2 + LIMB_DIGITS - 1) / LIMB_DIGITS; + } else { + /* number of limbs of the quotient (2 extra digits for rounding) */ + precl = (prec + 2 + LIMB_DIGITS - 1) / LIMB_DIGITS; + } + n = bf_max(a->len, precl); + + { + limb_t *taba, na, i; + slimb_t d; + + na = n + nb; + taba = bf_malloc(r->ctx, (na + 1) * sizeof(limb_t)); + if (!taba) + goto fail; + d = na - a->len; + memset(taba, 0, d * sizeof(limb_t)); + memcpy(taba + d, a->tab, a->len * sizeof(limb_t)); + if (bfdec_resize(r, n + 1)) + goto fail1; + if (mp_div_dec(r->ctx, r->tab, taba, na, b->tab, nb)) { + fail1: + bf_free(r->ctx, taba); + goto fail; + } + /* see if non zero remainder */ + for(i = 0; i < nb; i++) { + if (taba[i] != 0) + break; + } + bf_free(r->ctx, taba); + if (i != nb) { + if (prec == BF_PREC_INF) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else { + r->tab[0] |= 1; + } + } + r->expn = a->expn - b->expn + LIMB_DIGITS; + r->sign = r_sign; + ret = bfdec_normalize_and_round(r, prec, flags); + } + return ret; + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags) +{ + return bf_op2((bf_t *)r, (bf_t *)a, (bf_t *)b, prec, flags, + (bf_op2_func_t *)__bfdec_div); +} + +/* a and b must be finite numbers with a >= 0 and b > 0. 'q' is the + integer defined as floor(a/b) and r = a - q * b. */ +static void bfdec_tdivremu(bf_context_t *s, bfdec_t *q, bfdec_t *r, + const bfdec_t *a, const bfdec_t *b) +{ + if (bfdec_cmpu(a, b) < 0) { + bfdec_set_ui(q, 0); + bfdec_set(r, a); + } else { + bfdec_div(q, a, b, 0, BF_RNDZ | BF_FLAG_RADPNT_PREC); + bfdec_mul(r, q, b, BF_PREC_INF, BF_RNDZ); + bfdec_sub(r, a, r, BF_PREC_INF, BF_RNDZ); + } +} + +/* division and remainder. + + rnd_mode is the rounding mode for the quotient. The additional + rounding mode BF_RND_EUCLIDIAN is supported. + + 'q' is an integer. 'r' is rounded with prec and flags (prec can be + BF_PREC_INF). +*/ +int bfdec_divrem(bfdec_t *q, bfdec_t *r, const bfdec_t *a, const bfdec_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode) +{ + bf_context_t *s = q->ctx; + bfdec_t a1_s, *a1 = &a1_s; + bfdec_t b1_s, *b1 = &b1_s; + bfdec_t r1_s, *r1 = &r1_s; + int q_sign, res; + BOOL is_ceil, is_rndn; + + assert(q != a && q != b); + assert(r != a && r != b); + assert(q != r); + + if (a->len == 0 || b->len == 0) { + bfdec_set_zero(q, 0); + if (a->expn == BF_EXP_NAN || b->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + return 0; + } else if (a->expn == BF_EXP_INF || b->expn == BF_EXP_ZERO) { + bfdec_set_nan(r); + return BF_ST_INVALID_OP; + } else { + bfdec_set(r, a); + return bfdec_round(r, prec, flags); + } + } + + q_sign = a->sign ^ b->sign; + is_rndn = (rnd_mode == BF_RNDN || rnd_mode == BF_RNDNA); + switch(rnd_mode) { + default: + case BF_RNDZ: + case BF_RNDN: + case BF_RNDNA: + is_ceil = FALSE; + break; + case BF_RNDD: + is_ceil = q_sign; + break; + case BF_RNDU: + is_ceil = q_sign ^ 1; + break; + case BF_RNDA: + is_ceil = TRUE; + break; + case BF_DIVREM_EUCLIDIAN: + is_ceil = a->sign; + break; + } + + a1->expn = a->expn; + a1->tab = a->tab; + a1->len = a->len; + a1->sign = 0; + + b1->expn = b->expn; + b1->tab = b->tab; + b1->len = b->len; + b1->sign = 0; + + // bfdec_print_str("a1", a1); + // bfdec_print_str("b1", b1); + /* XXX: could improve to avoid having a large 'q' */ + bfdec_tdivremu(s, q, r, a1, b1); + if (bfdec_is_nan(q) || bfdec_is_nan(r)) + goto fail; + // bfdec_print_str("q", q); + // bfdec_print_str("r", r); + + if (r->len != 0) { + if (is_rndn) { + bfdec_init(s, r1); + if (bfdec_set(r1, r)) + goto fail; + if (bfdec_mul_si(r1, r1, 2, BF_PREC_INF, BF_RNDZ)) { + bfdec_delete(r1); + goto fail; + } + res = bfdec_cmpu(r1, b); + bfdec_delete(r1); + if (res > 0 || + (res == 0 && + (rnd_mode == BF_RNDNA || + (get_digit(q->tab, q->len, q->len * LIMB_DIGITS - q->expn) & 1) != 0))) { + goto do_sub_r; + } + } else if (is_ceil) { + do_sub_r: + res = bfdec_add_si(q, q, 1, BF_PREC_INF, BF_RNDZ); + res |= bfdec_sub(r, r, b1, BF_PREC_INF, BF_RNDZ); + if (res & BF_ST_MEM_ERROR) + goto fail; + } + } + + r->sign ^= a->sign; + q->sign = q_sign; + return bfdec_round(r, prec, flags); + fail: + bfdec_set_nan(q); + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +int bfdec_rem(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode) +{ + bfdec_t q_s, *q = &q_s; + int ret; + + bfdec_init(r->ctx, q); + ret = bfdec_divrem(q, r, a, b, prec, flags, rnd_mode); + bfdec_delete(q); + return ret; +} + +/* convert to integer (infinite precision) */ +int bfdec_rint(bfdec_t *r, int rnd_mode) +{ + return bfdec_round(r, 0, rnd_mode | BF_FLAG_RADPNT_PREC); +} + +int bfdec_sqrt(bfdec_t *r, const bfdec_t *a, limb_t prec, bf_flags_t flags) +{ + bf_context_t *s = a->ctx; + int ret, k; + limb_t *a1, v; + slimb_t n, n1, prec1; + limb_t res; + + assert(r != a); + + if (a->len == 0) { + if (a->expn == BF_EXP_NAN) { + bfdec_set_nan(r); + } else if (a->expn == BF_EXP_INF && a->sign) { + goto invalid_op; + } else { + bfdec_set(r, a); + } + ret = 0; + } else if (a->sign || prec == BF_PREC_INF) { + invalid_op: + bfdec_set_nan(r); + ret = BF_ST_INVALID_OP; + } else { + if (flags & BF_FLAG_RADPNT_PREC) { + prec1 = bf_max(floor_div(a->expn + 1, 2) + prec, 1); + } else { + prec1 = prec; + } + /* convert the mantissa to an integer with at least 2 * + prec + 4 digits */ + n = (2 * (prec1 + 2) + 2 * LIMB_DIGITS - 1) / (2 * LIMB_DIGITS); + if (bfdec_resize(r, n)) + goto fail; + a1 = bf_malloc(s, sizeof(limb_t) * 2 * n); + if (!a1) + goto fail; + n1 = bf_min(2 * n, a->len); + memset(a1, 0, (2 * n - n1) * sizeof(limb_t)); + memcpy(a1 + 2 * n - n1, a->tab + a->len - n1, n1 * sizeof(limb_t)); + if (a->expn & 1) { + res = mp_shr_dec(a1, a1, 2 * n, 1, 0); + } else { + res = 0; + } + /* normalize so that a1 >= B^(2*n)/4. Not need for n = 1 + because mp_sqrtrem2_dec already does it */ + k = 0; + if (n > 1) { + v = a1[2 * n - 1]; + while (v < BF_DEC_BASE / 4) { + k++; + v *= 4; + } + if (k != 0) + mp_mul1_dec(a1, a1, 2 * n, 1 << (2 * k), 0); + } + if (mp_sqrtrem_dec(s, r->tab, a1, n)) { + bf_free(s, a1); + goto fail; + } + if (k != 0) + mp_div1_dec(r->tab, r->tab, n, 1 << k, 0); + if (!res) { + res = mp_scan_nz(a1, n + 1); + } + bf_free(s, a1); + if (!res) { + res = mp_scan_nz(a->tab, a->len - n1); + } + if (res != 0) + r->tab[0] |= 1; + r->sign = 0; + r->expn = (a->expn + 1) >> 1; + ret = bfdec_round(r, prec, flags); + } + return ret; + fail: + bfdec_set_nan(r); + return BF_ST_MEM_ERROR; +} + +/* The rounding mode is always BF_RNDZ. Return BF_ST_OVERFLOW if there + is an overflow and 0 otherwise. No memory error is possible. */ +int bfdec_get_int32(int *pres, const bfdec_t *a) +{ + uint32_t v; + int ret; + if (a->expn >= BF_EXP_INF) { + ret = 0; + if (a->expn == BF_EXP_INF) { + v = (uint32_t)INT32_MAX + a->sign; + /* XXX: return overflow ? */ + } else { + v = INT32_MAX; + } + } else if (a->expn <= 0) { + v = 0; + ret = 0; + } else if (a->expn <= 9) { + v = fast_shr_dec(a->tab[a->len - 1], LIMB_DIGITS - a->expn); + if (a->sign) + v = -v; + ret = 0; + } else if (a->expn == 10) { + uint64_t v1; + uint32_t v_max; +#if LIMB_BITS == 64 + v1 = fast_shr_dec(a->tab[a->len - 1], LIMB_DIGITS - a->expn); +#else + v1 = (uint64_t)a->tab[a->len - 1] * 10 + + get_digit(a->tab, a->len, (a->len - 1) * LIMB_DIGITS - 1); +#endif + v_max = (uint32_t)INT32_MAX + a->sign; + if (v1 > v_max) { + v = v_max; + ret = BF_ST_OVERFLOW; + } else { + v = v1; + if (a->sign) + v = -v; + ret = 0; + } + } else { + v = (uint32_t)INT32_MAX + a->sign; + ret = BF_ST_OVERFLOW; + } + *pres = v; + return ret; +} + +/* power to an integer with infinite precision */ +int bfdec_pow_ui(bfdec_t *r, const bfdec_t *a, limb_t b) +{ + int ret, n_bits, i; + + assert(r != a); + if (b == 0) + return bfdec_set_ui(r, 1); + ret = bfdec_set(r, a); + n_bits = LIMB_BITS - clz(b); + for(i = n_bits - 2; i >= 0; i--) { + ret |= bfdec_mul(r, r, r, BF_PREC_INF, BF_RNDZ); + if ((b >> i) & 1) + ret |= bfdec_mul(r, r, a, BF_PREC_INF, BF_RNDZ); + } + return ret; +} + +char *bfdec_ftoa(size_t *plen, const bfdec_t *a, limb_t prec, bf_flags_t flags) +{ + return bf_ftoa_internal(plen, (const bf_t *)a, 10, prec, flags, TRUE); +} + +int bfdec_atof(bfdec_t *r, const char *str, const char **pnext, + limb_t prec, bf_flags_t flags) +{ + slimb_t dummy_exp; + return bf_atof_internal((bf_t *)r, &dummy_exp, str, pnext, 10, prec, + flags, TRUE); +} + +#endif /* USE_BF_DEC */ + +#ifdef USE_FFT_MUL +/***************************************************************/ +/* Integer multiplication with FFT */ + +/* or LIMB_BITS at bit position 'pos' in tab */ +static inline void put_bits(limb_t *tab, limb_t len, slimb_t pos, limb_t val) +{ + limb_t i; + int p; + + i = pos >> LIMB_LOG2_BITS; + p = pos & (LIMB_BITS - 1); + if (i < len) + tab[i] |= val << p; + if (p != 0) { + i++; + if (i < len) { + tab[i] |= val >> (LIMB_BITS - p); + } + } +} + +#if defined(__AVX2__) + +typedef double NTTLimb; + +/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */ +#define NTT_MOD_LOG2_MIN 50 +#define NTT_MOD_LOG2_MAX 51 +#define NB_MODS 5 +#define NTT_PROOT_2EXP 39 +static const int ntt_int_bits[NB_MODS] = { 254, 203, 152, 101, 50, }; + +static const limb_t ntt_mods[NB_MODS] = { 0x00073a8000000001, 0x0007858000000001, 0x0007a38000000001, 0x0007a68000000001, 0x0007fd8000000001, +}; + +static const limb_t ntt_proot[2][NB_MODS] = { + { 0x00056198d44332c8, 0x0002eb5d640aad39, 0x00047e31eaa35fd0, 0x0005271ac118a150, 0x00075e0ce8442bd5, }, + { 0x000461169761bcc5, 0x0002dac3cb2da688, 0x0004abc97751e3bf, 0x000656778fc8c485, 0x0000dc6469c269fa, }, +}; + +static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = { + 0x00020e4da740da8e, 0x0004c3dc09c09c1d, 0x000063bd097b4271, 0x000799d8f18f18fd, + 0x0005384222222264, 0x000572b07c1f07fe, 0x00035cd08888889a, + 0x00066015555557e3, 0x000725960b60b623, + 0x0002fc1fa1d6ce12, +}; + +#else + +typedef limb_t NTTLimb; + +#if LIMB_BITS == 64 + +#define NTT_MOD_LOG2_MIN 61 +#define NTT_MOD_LOG2_MAX 62 +#define NB_MODS 5 +#define NTT_PROOT_2EXP 51 +static const int ntt_int_bits[NB_MODS] = { 307, 246, 185, 123, 61, }; + +static const limb_t ntt_mods[NB_MODS] = { 0x28d8000000000001, 0x2a88000000000001, 0x2ed8000000000001, 0x3508000000000001, 0x3aa8000000000001, +}; + +static const limb_t ntt_proot[2][NB_MODS] = { + { 0x1b8ea61034a2bea7, 0x21a9762de58206fb, 0x02ca782f0756a8ea, 0x278384537a3e50a1, 0x106e13fee74ce0ab, }, + { 0x233513af133e13b8, 0x1d13140d1c6f75f1, 0x12cde57f97e3eeda, 0x0d6149e23cbe654f, 0x36cd204f522a1379, }, +}; + +static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = { + 0x08a9ed097b425eea, 0x18a44aaaaaaaaab3, 0x2493f57f57f57f5d, 0x126b8d0649a7f8d4, + 0x09d80ed7303b5ccc, 0x25b8bcf3cf3cf3d5, 0x2ce6ce63398ce638, + 0x0e31fad40a57eb59, 0x02a3529fd4a7f52f, + 0x3a5493e93e93e94a, +}; + +#elif LIMB_BITS == 32 + +/* we must have: modulo >= 1 << NTT_MOD_LOG2_MIN */ +#define NTT_MOD_LOG2_MIN 29 +#define NTT_MOD_LOG2_MAX 30 +#define NB_MODS 5 +#define NTT_PROOT_2EXP 20 +static const int ntt_int_bits[NB_MODS] = { 148, 119, 89, 59, 29, }; + +static const limb_t ntt_mods[NB_MODS] = { 0x0000000032b00001, 0x0000000033700001, 0x0000000036d00001, 0x0000000037300001, 0x000000003e500001, +}; + +static const limb_t ntt_proot[2][NB_MODS] = { + { 0x0000000032525f31, 0x0000000005eb3b37, 0x00000000246eda9f, 0x0000000035f25901, 0x00000000022f5768, }, + { 0x00000000051eba1a, 0x00000000107be10e, 0x000000001cd574e0, 0x00000000053806e6, 0x000000002cd6bf98, }, +}; + +static const limb_t ntt_mods_cr[NB_MODS * (NB_MODS - 1) / 2] = { + 0x000000000449559a, 0x000000001eba6ca9, 0x000000002ec18e46, 0x000000000860160b, + 0x000000000d321307, 0x000000000bf51120, 0x000000000f662938, + 0x000000000932ab3e, 0x000000002f40eef8, + 0x000000002e760905, +}; + +#endif /* LIMB_BITS */ + +#endif /* !AVX2 */ + +#if defined(__AVX2__) +#define NTT_TRIG_K_MAX 18 +#else +#define NTT_TRIG_K_MAX 19 +#endif + +typedef struct BFNTTState { + bf_context_t *ctx; + + /* used for mul_mod_fast() */ + limb_t ntt_mods_div[NB_MODS]; + + limb_t ntt_proot_pow[NB_MODS][2][NTT_PROOT_2EXP + 1]; + limb_t ntt_proot_pow_inv[NB_MODS][2][NTT_PROOT_2EXP + 1]; + NTTLimb *ntt_trig[NB_MODS][2][NTT_TRIG_K_MAX + 1]; + /* 1/2^n mod m */ + limb_t ntt_len_inv[NB_MODS][NTT_PROOT_2EXP + 1][2]; +#if defined(__AVX2__) + __m256d ntt_mods_cr_vec[NB_MODS * (NB_MODS - 1) / 2]; + __m256d ntt_mods_vec[NB_MODS]; + __m256d ntt_mods_inv_vec[NB_MODS]; +#else + limb_t ntt_mods_cr_inv[NB_MODS * (NB_MODS - 1) / 2]; +#endif +} BFNTTState; + +static NTTLimb *get_trig(BFNTTState *s, int k, int inverse, int m_idx); + +/* add modulo with up to (LIMB_BITS-1) bit modulo */ +static inline limb_t add_mod(limb_t a, limb_t b, limb_t m) +{ + limb_t r; + r = a + b; + if (r >= m) + r -= m; + return r; +} + +/* sub modulo with up to LIMB_BITS bit modulo */ +static inline limb_t sub_mod(limb_t a, limb_t b, limb_t m) +{ + limb_t r; + r = a - b; + if (r > a) + r += m; + return r; +} + +/* return (r0+r1*B) mod m + precondition: 0 <= r0+r1*B < 2^(64+NTT_MOD_LOG2_MIN) +*/ +static inline limb_t mod_fast(dlimb_t r, + limb_t m, limb_t m_inv) +{ + limb_t a1, q, t0, r1, r0; + + a1 = r >> NTT_MOD_LOG2_MIN; + + q = ((dlimb_t)a1 * m_inv) >> LIMB_BITS; + r = r - (dlimb_t)q * m - m * 2; + r1 = r >> LIMB_BITS; + t0 = (slimb_t)r1 >> 1; + r += m & t0; + r0 = r; + r1 = r >> LIMB_BITS; + r0 += m & r1; + return r0; +} + +/* faster version using precomputed modulo inverse. + precondition: 0 <= a * b < 2^(64+NTT_MOD_LOG2_MIN) */ +static inline limb_t mul_mod_fast(limb_t a, limb_t b, + limb_t m, limb_t m_inv) +{ + dlimb_t r; + r = (dlimb_t)a * (dlimb_t)b; + return mod_fast(r, m, m_inv); +} + +static inline limb_t init_mul_mod_fast(limb_t m) +{ + dlimb_t t; + assert(m < (limb_t)1 << NTT_MOD_LOG2_MAX); + assert(m >= (limb_t)1 << NTT_MOD_LOG2_MIN); + t = (dlimb_t)1 << (LIMB_BITS + NTT_MOD_LOG2_MIN); + return t / m; +} + +/* Faster version used when the multiplier is constant. 0 <= a < 2^64, + 0 <= b < m. */ +static inline limb_t mul_mod_fast2(limb_t a, limb_t b, + limb_t m, limb_t b_inv) +{ + limb_t r, q; + + q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS; + r = a * b - q * m; + if (r >= m) + r -= m; + return r; +} + +/* Faster version used when the multiplier is constant. 0 <= a < 2^64, + 0 <= b < m. Let r = a * b mod m. The return value is 'r' or 'r + + m'. */ +static inline limb_t mul_mod_fast3(limb_t a, limb_t b, + limb_t m, limb_t b_inv) +{ + limb_t r, q; + + q = ((dlimb_t)a * (dlimb_t)b_inv) >> LIMB_BITS; + r = a * b - q * m; + return r; +} + +static inline limb_t init_mul_mod_fast2(limb_t b, limb_t m) +{ + return ((dlimb_t)b << LIMB_BITS) / m; +} + +#ifdef __AVX2__ + +static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m) +{ + slimb_t v; + v = a; + if (v < 0) + v += m; + if (v >= m) + v -= m; + return v; +} + +static inline NTTLimb int_to_ntt_limb(limb_t a, limb_t m) +{ + return (slimb_t)a; +} + +static inline NTTLimb int_to_ntt_limb2(limb_t a, limb_t m) +{ + if (a >= (m / 2)) + a -= m; + return (slimb_t)a; +} + +/* return r + m if r < 0 otherwise r. */ +static inline __m256d ntt_mod1(__m256d r, __m256d m) +{ + return _mm256_blendv_pd(r, r + m, r); +} + +/* input: abs(r) < 2 * m. Output: abs(r) < m */ +static inline __m256d ntt_mod(__m256d r, __m256d mf, __m256d m2f) +{ + return _mm256_blendv_pd(r, r + m2f, r) - mf; +} + +/* input: abs(a*b) < 2 * m^2, output: abs(r) < m */ +static inline __m256d ntt_mul_mod(__m256d a, __m256d b, __m256d mf, + __m256d m_inv) +{ + __m256d r, q, ab1, ab0, qm0, qm1; + ab1 = a * b; + q = _mm256_round_pd(ab1 * m_inv, 0); /* round to nearest */ + qm1 = q * mf; + qm0 = _mm256_fmsub_pd(q, mf, qm1); /* low part */ + ab0 = _mm256_fmsub_pd(a, b, ab1); /* low part */ + r = (ab1 - qm1) + (ab0 - qm0); + return r; +} + +static void *bf_aligned_malloc(bf_context_t *s, size_t size, size_t align) +{ + void *ptr; + void **ptr1; + ptr = bf_malloc(s, size + sizeof(void *) + align - 1); + if (!ptr) + return NULL; + ptr1 = (void **)(((uintptr_t)ptr + sizeof(void *) + align - 1) & + ~(align - 1)); + ptr1[-1] = ptr; + return ptr1; +} + +static void bf_aligned_free(bf_context_t *s, void *ptr) +{ + if (!ptr) + return; + bf_free(s, ((void **)ptr)[-1]); +} + +static void *ntt_malloc(BFNTTState *s, size_t size) +{ + return bf_aligned_malloc(s->ctx, size, 64); +} + +static void ntt_free(BFNTTState *s, void *ptr) +{ + bf_aligned_free(s->ctx, ptr); +} + +static no_inline int ntt_fft(BFNTTState *s, + NTTLimb *out_buf, NTTLimb *in_buf, + NTTLimb *tmp_buf, int fft_len_log2, + int inverse, int m_idx) +{ + limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j; + NTTLimb *tab_in, *tab_out, *tmp, *trig; + __m256d m_inv, mf, m2f, c, a0, a1, b0, b1; + limb_t m; + int l; + + m = ntt_mods[m_idx]; + + m_inv = _mm256_set1_pd(1.0 / (double)m); + mf = _mm256_set1_pd(m); + m2f = _mm256_set1_pd(m * 2); + + n = (limb_t)1 << fft_len_log2; + assert(n >= 8); + stride_in = n / 2; + + tab_in = in_buf; + tab_out = tmp_buf; + trig = get_trig(s, fft_len_log2, inverse, m_idx); + if (!trig) + return -1; + p = 0; + for(k = 0; k < stride_in; k += 4) { + a0 = _mm256_load_pd(&tab_in[k]); + a1 = _mm256_load_pd(&tab_in[k + stride_in]); + c = _mm256_load_pd(trig); + trig += 4; + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); + a0 = _mm256_permute2f128_pd(b0, b1, 0x20); + a1 = _mm256_permute2f128_pd(b0, b1, 0x31); + a0 = _mm256_permute4x64_pd(a0, 0xd8); + a1 = _mm256_permute4x64_pd(a1, 0xd8); + _mm256_store_pd(&tab_out[p], a0); + _mm256_store_pd(&tab_out[p + 4], a1); + p += 2 * 4; + } + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + + trig = get_trig(s, fft_len_log2 - 1, inverse, m_idx); + if (!trig) + return -1; + p = 0; + for(k = 0; k < stride_in; k += 4) { + a0 = _mm256_load_pd(&tab_in[k]); + a1 = _mm256_load_pd(&tab_in[k + stride_in]); + c = _mm256_setr_pd(trig[0], trig[0], trig[1], trig[1]); + trig += 2; + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); + a0 = _mm256_permute2f128_pd(b0, b1, 0x20); + a1 = _mm256_permute2f128_pd(b0, b1, 0x31); + _mm256_store_pd(&tab_out[p], a0); + _mm256_store_pd(&tab_out[p + 4], a1); + p += 2 * 4; + } + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + + nb_blocks = n / 4; + fft_per_block = 4; + + l = fft_len_log2 - 2; + while (nb_blocks != 2) { + nb_blocks >>= 1; + p = 0; + k = 0; + trig = get_trig(s, l, inverse, m_idx); + if (!trig) + return -1; + for(i = 0; i < nb_blocks; i++) { + c = _mm256_set1_pd(trig[0]); + trig++; + for(j = 0; j < fft_per_block; j += 4) { + a0 = _mm256_load_pd(&tab_in[k + j]); + a1 = _mm256_load_pd(&tab_in[k + j + stride_in]); + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mul_mod(a0 - a1, c, mf, m_inv); + _mm256_store_pd(&tab_out[p + j], b0); + _mm256_store_pd(&tab_out[p + j + fft_per_block], b1); + } + k += fft_per_block; + p += 2 * fft_per_block; + } + fft_per_block <<= 1; + l--; + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + } + + tab_out = out_buf; + for(k = 0; k < stride_in; k += 4) { + a0 = _mm256_load_pd(&tab_in[k]); + a1 = _mm256_load_pd(&tab_in[k + stride_in]); + b0 = ntt_mod(a0 + a1, mf, m2f); + b1 = ntt_mod(a0 - a1, mf, m2f); + _mm256_store_pd(&tab_out[k], b0); + _mm256_store_pd(&tab_out[k + stride_in], b1); + } + return 0; +} + +static void ntt_vec_mul(BFNTTState *s, + NTTLimb *tab1, NTTLimb *tab2, limb_t fft_len_log2, + int k_tot, int m_idx) +{ + limb_t i, c_inv, n, m; + __m256d m_inv, mf, a, b, c; + + m = ntt_mods[m_idx]; + c_inv = s->ntt_len_inv[m_idx][k_tot][0]; + m_inv = _mm256_set1_pd(1.0 / (double)m); + mf = _mm256_set1_pd(m); + c = _mm256_set1_pd(int_to_ntt_limb(c_inv, m)); + n = (limb_t)1 << fft_len_log2; + for(i = 0; i < n; i += 4) { + a = _mm256_load_pd(&tab1[i]); + b = _mm256_load_pd(&tab2[i]); + a = ntt_mul_mod(a, b, mf, m_inv); + a = ntt_mul_mod(a, c, mf, m_inv); + _mm256_store_pd(&tab1[i], a); + } +} + +static no_inline void mul_trig(NTTLimb *buf, + limb_t n, limb_t c1, limb_t m, limb_t m_inv1) +{ + limb_t i, c2, c3, c4; + __m256d c, c_mul, a0, mf, m_inv; + assert(n >= 2); + + mf = _mm256_set1_pd(m); + m_inv = _mm256_set1_pd(1.0 / (double)m); + + c2 = mul_mod_fast(c1, c1, m, m_inv1); + c3 = mul_mod_fast(c2, c1, m, m_inv1); + c4 = mul_mod_fast(c2, c2, m, m_inv1); + c = _mm256_setr_pd(1, int_to_ntt_limb(c1, m), + int_to_ntt_limb(c2, m), int_to_ntt_limb(c3, m)); + c_mul = _mm256_set1_pd(int_to_ntt_limb(c4, m)); + for(i = 0; i < n; i += 4) { + a0 = _mm256_load_pd(&buf[i]); + a0 = ntt_mul_mod(a0, c, mf, m_inv); + _mm256_store_pd(&buf[i], a0); + c = ntt_mul_mod(c, c_mul, mf, m_inv); + } +} + +#else + +static void *ntt_malloc(BFNTTState *s, size_t size) +{ + return bf_malloc(s->ctx, size); +} + +static void ntt_free(BFNTTState *s, void *ptr) +{ + bf_free(s->ctx, ptr); +} + +static inline limb_t ntt_limb_to_int(NTTLimb a, limb_t m) +{ + if (a >= m) + a -= m; + return a; +} + +static inline NTTLimb int_to_ntt_limb(slimb_t a, limb_t m) +{ + return a; +} + +static no_inline int ntt_fft(BFNTTState *s, NTTLimb *out_buf, NTTLimb *in_buf, + NTTLimb *tmp_buf, int fft_len_log2, + int inverse, int m_idx) +{ + limb_t nb_blocks, fft_per_block, p, k, n, stride_in, i, j, m, m2; + NTTLimb *tab_in, *tab_out, *tmp, a0, a1, b0, b1, c, *trig, c_inv; + int l; + + m = ntt_mods[m_idx]; + m2 = 2 * m; + n = (limb_t)1 << fft_len_log2; + nb_blocks = n; + fft_per_block = 1; + stride_in = n / 2; + tab_in = in_buf; + tab_out = tmp_buf; + l = fft_len_log2; + while (nb_blocks != 2) { + nb_blocks >>= 1; + p = 0; + k = 0; + trig = get_trig(s, l, inverse, m_idx); + if (!trig) + return -1; + for(i = 0; i < nb_blocks; i++) { + c = trig[0]; + c_inv = trig[1]; + trig += 2; + for(j = 0; j < fft_per_block; j++) { + a0 = tab_in[k + j]; + a1 = tab_in[k + j + stride_in]; + b0 = add_mod(a0, a1, m2); + b1 = a0 - a1 + m2; + b1 = mul_mod_fast3(b1, c, m, c_inv); + tab_out[p + j] = b0; + tab_out[p + j + fft_per_block] = b1; + } + k += fft_per_block; + p += 2 * fft_per_block; + } + fft_per_block <<= 1; + l--; + tmp = tab_in; + tab_in = tab_out; + tab_out = tmp; + } + /* no twiddle in last step */ + tab_out = out_buf; + for(k = 0; k < stride_in; k++) { + a0 = tab_in[k]; + a1 = tab_in[k + stride_in]; + b0 = add_mod(a0, a1, m2); + b1 = sub_mod(a0, a1, m2); + tab_out[k] = b0; + tab_out[k + stride_in] = b1; + } + return 0; +} + +static void ntt_vec_mul(BFNTTState *s, + NTTLimb *tab1, NTTLimb *tab2, int fft_len_log2, + int k_tot, int m_idx) +{ + limb_t i, norm, norm_inv, a, n, m, m_inv; + + m = ntt_mods[m_idx]; + m_inv = s->ntt_mods_div[m_idx]; + norm = s->ntt_len_inv[m_idx][k_tot][0]; + norm_inv = s->ntt_len_inv[m_idx][k_tot][1]; + n = (limb_t)1 << fft_len_log2; + for(i = 0; i < n; i++) { + a = tab1[i]; + /* need to reduce the range so that the product is < + 2^(LIMB_BITS+NTT_MOD_LOG2_MIN) */ + if (a >= m) + a -= m; + a = mul_mod_fast(a, tab2[i], m, m_inv); + a = mul_mod_fast3(a, norm, m, norm_inv); + tab1[i] = a; + } +} + +static no_inline void mul_trig(NTTLimb *buf, + limb_t n, limb_t c_mul, limb_t m, limb_t m_inv) +{ + limb_t i, c0, c_mul_inv; + + c0 = 1; + c_mul_inv = init_mul_mod_fast2(c_mul, m); + for(i = 0; i < n; i++) { + buf[i] = mul_mod_fast(buf[i], c0, m, m_inv); + c0 = mul_mod_fast2(c0, c_mul, m, c_mul_inv); + } +} + +#endif /* !AVX2 */ + +static no_inline NTTLimb *get_trig(BFNTTState *s, + int k, int inverse, int m_idx) +{ + NTTLimb *tab; + limb_t i, n2, c, c_mul, m, c_mul_inv; + + if (k > NTT_TRIG_K_MAX) + return NULL; + + tab = s->ntt_trig[m_idx][inverse][k]; + if (tab) + return tab; + n2 = (limb_t)1 << (k - 1); + m = ntt_mods[m_idx]; +#ifdef __AVX2__ + tab = ntt_malloc(s, sizeof(NTTLimb) * n2); +#else + tab = ntt_malloc(s, sizeof(NTTLimb) * n2 * 2); +#endif + if (!tab) + return NULL; + c = 1; + c_mul = s->ntt_proot_pow[m_idx][inverse][k]; + c_mul_inv = s->ntt_proot_pow_inv[m_idx][inverse][k]; + for(i = 0; i < n2; i++) { +#ifdef __AVX2__ + tab[i] = int_to_ntt_limb2(c, m); +#else + tab[2 * i] = int_to_ntt_limb(c, m); + tab[2 * i + 1] = init_mul_mod_fast2(c, m); +#endif + c = mul_mod_fast2(c, c_mul, m, c_mul_inv); + } + s->ntt_trig[m_idx][inverse][k] = tab; + return tab; +} + +void fft_clear_cache(bf_context_t *s1) +{ + int m_idx, inverse, k; + BFNTTState *s = s1->ntt_state; + if (s) { + for(m_idx = 0; m_idx < NB_MODS; m_idx++) { + for(inverse = 0; inverse < 2; inverse++) { + for(k = 0; k < NTT_TRIG_K_MAX + 1; k++) { + if (s->ntt_trig[m_idx][inverse][k]) { + ntt_free(s, s->ntt_trig[m_idx][inverse][k]); + s->ntt_trig[m_idx][inverse][k] = NULL; + } + } + } + } +#if defined(__AVX2__) + bf_aligned_free(s1, s); +#else + bf_free(s1, s); +#endif + s1->ntt_state = NULL; + } +} + +#define STRIP_LEN 16 + +/* dst = buf1, src = buf2 */ +static int ntt_fft_partial(BFNTTState *s, NTTLimb *buf1, + int k1, int k2, limb_t n1, limb_t n2, int inverse, + limb_t m_idx) +{ + limb_t i, j, c_mul, c0, m, m_inv, strip_len, l; + NTTLimb *buf2, *buf3; + + buf2 = NULL; + buf3 = ntt_malloc(s, sizeof(NTTLimb) * n1); + if (!buf3) + goto fail; + if (k2 == 0) { + if (ntt_fft(s, buf1, buf1, buf3, k1, inverse, m_idx)) + goto fail; + } else { + strip_len = STRIP_LEN; + buf2 = ntt_malloc(s, sizeof(NTTLimb) * n1 * strip_len); + if (!buf2) + goto fail; + m = ntt_mods[m_idx]; + m_inv = s->ntt_mods_div[m_idx]; + c0 = s->ntt_proot_pow[m_idx][inverse][k1 + k2]; + c_mul = 1; + assert((n2 % strip_len) == 0); + for(j = 0; j < n2; j += strip_len) { + for(i = 0; i < n1; i++) { + for(l = 0; l < strip_len; l++) { + buf2[i + l * n1] = buf1[i * n2 + (j + l)]; + } + } + for(l = 0; l < strip_len; l++) { + if (inverse) + mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv); + if (ntt_fft(s, buf2 + l * n1, buf2 + l * n1, buf3, k1, inverse, m_idx)) + goto fail; + if (!inverse) + mul_trig(buf2 + l * n1, n1, c_mul, m, m_inv); + c_mul = mul_mod_fast(c_mul, c0, m, m_inv); + } + + for(i = 0; i < n1; i++) { + for(l = 0; l < strip_len; l++) { + buf1[i * n2 + (j + l)] = buf2[i + l *n1]; + } + } + } + ntt_free(s, buf2); + } + ntt_free(s, buf3); + return 0; + fail: + ntt_free(s, buf2); + ntt_free(s, buf3); + return -1; +} + + +/* dst = buf1, src = buf2, tmp = buf3 */ +static int ntt_conv(BFNTTState *s, NTTLimb *buf1, NTTLimb *buf2, + int k, int k_tot, limb_t m_idx) +{ + limb_t n1, n2, i; + int k1, k2; + + if (k <= NTT_TRIG_K_MAX) { + k1 = k; + } else { + /* recursive split of the FFT */ + k1 = bf_min(k / 2, NTT_TRIG_K_MAX); + } + k2 = k - k1; + n1 = (limb_t)1 << k1; + n2 = (limb_t)1 << k2; + + if (ntt_fft_partial(s, buf1, k1, k2, n1, n2, 0, m_idx)) + return -1; + if (ntt_fft_partial(s, buf2, k1, k2, n1, n2, 0, m_idx)) + return -1; + if (k2 == 0) { + ntt_vec_mul(s, buf1, buf2, k, k_tot, m_idx); + } else { + for(i = 0; i < n1; i++) { + ntt_conv(s, buf1 + i * n2, buf2 + i * n2, k2, k_tot, m_idx); + } + } + if (ntt_fft_partial(s, buf1, k1, k2, n1, n2, 1, m_idx)) + return -1; + return 0; +} + + +static no_inline void limb_to_ntt(BFNTTState *s, + NTTLimb *tabr, limb_t fft_len, + const limb_t *taba, limb_t a_len, int dpl, + int first_m_idx, int nb_mods) +{ + slimb_t i, n; + dlimb_t a, b; + int j, shift; + limb_t base_mask1, a0, a1, a2, r, m, m_inv; + + memset(tabr, 0, sizeof(NTTLimb) * fft_len * nb_mods); + shift = dpl & (LIMB_BITS - 1); + if (shift == 0) + base_mask1 = -1; + else + base_mask1 = ((limb_t)1 << shift) - 1; + n = bf_min(fft_len, (a_len * LIMB_BITS + dpl - 1) / dpl); + for(i = 0; i < n; i++) { + a0 = get_bits(taba, a_len, i * dpl); + if (dpl <= LIMB_BITS) { + a0 &= base_mask1; + a = a0; + } else { + a1 = get_bits(taba, a_len, i * dpl + LIMB_BITS); + if (dpl <= (LIMB_BITS + NTT_MOD_LOG2_MIN)) { + a = a0 | ((dlimb_t)(a1 & base_mask1) << LIMB_BITS); + } else { + if (dpl > 2 * LIMB_BITS) { + a2 = get_bits(taba, a_len, i * dpl + LIMB_BITS * 2) & + base_mask1; + } else { + a1 &= base_mask1; + a2 = 0; + } + // printf("a=0x%016lx%016lx%016lx\n", a2, a1, a0); + a = (a0 >> (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | + ((dlimb_t)a1 << (NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)) | + ((dlimb_t)a2 << (LIMB_BITS + NTT_MOD_LOG2_MAX - NTT_MOD_LOG2_MIN)); + a0 &= ((limb_t)1 << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) - 1; + } + } + for(j = 0; j < nb_mods; j++) { + m = ntt_mods[first_m_idx + j]; + m_inv = s->ntt_mods_div[first_m_idx + j]; + r = mod_fast(a, m, m_inv); + if (dpl > (LIMB_BITS + NTT_MOD_LOG2_MIN)) { + b = ((dlimb_t)r << (LIMB_BITS - NTT_MOD_LOG2_MAX + NTT_MOD_LOG2_MIN)) | a0; + r = mod_fast(b, m, m_inv); + } + tabr[i + j * fft_len] = int_to_ntt_limb(r, m); + } + } +} + +#if defined(__AVX2__) + +#define VEC_LEN 4 + +typedef union { + __m256d v; + double d[4]; +} VecUnion; + +static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len, + const NTTLimb *buf, int fft_len_log2, int dpl, + int nb_mods) +{ + const limb_t *mods = ntt_mods + NB_MODS - nb_mods; + const __m256d *mods_cr_vec, *mf, *m_inv; + VecUnion y[NB_MODS]; + limb_t u[NB_MODS], carry[NB_MODS], fft_len, base_mask1, r; + slimb_t i, len, pos; + int j, k, l, shift, n_limb1, p; + dlimb_t t; + + j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2; + mods_cr_vec = s->ntt_mods_cr_vec + j; + mf = s->ntt_mods_vec + NB_MODS - nb_mods; + m_inv = s->ntt_mods_inv_vec + NB_MODS - nb_mods; + + shift = dpl & (LIMB_BITS - 1); + if (shift == 0) + base_mask1 = -1; + else + base_mask1 = ((limb_t)1 << shift) - 1; + n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS; + for(j = 0; j < NB_MODS; j++) + carry[j] = 0; + for(j = 0; j < NB_MODS; j++) + u[j] = 0; /* avoid warnings */ + memset(tabr, 0, sizeof(limb_t) * r_len); + fft_len = (limb_t)1 << fft_len_log2; + len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl); + len = (len + VEC_LEN - 1) & ~(VEC_LEN - 1); + i = 0; + while (i < len) { + for(j = 0; j < nb_mods; j++) + y[j].v = *(__m256d *)&buf[i + fft_len * j]; + + /* Chinese remainder to get mixed radix representation */ + l = 0; + for(j = 0; j < nb_mods - 1; j++) { + y[j].v = ntt_mod1(y[j].v, mf[j]); + for(k = j + 1; k < nb_mods; k++) { + y[k].v = ntt_mul_mod(y[k].v - y[j].v, + mods_cr_vec[l], mf[k], m_inv[k]); + l++; + } + } + y[j].v = ntt_mod1(y[j].v, mf[j]); + + for(p = 0; p < VEC_LEN; p++) { + /* back to normal representation */ + u[0] = (int64_t)y[nb_mods - 1].d[p]; + l = 1; + for(j = nb_mods - 2; j >= 1; j--) { + r = (int64_t)y[j].d[p]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r; + l++; + } + /* XXX: for nb_mods = 5, l should be 4 */ + + /* last step adds the carry */ + r = (int64_t)y[0].d[p]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r + carry[k]; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r + carry[l]; + + /* write the digits */ + pos = i * dpl; + for(j = 0; j < n_limb1; j++) { + put_bits(tabr, r_len, pos, u[j]); + pos += LIMB_BITS; + } + put_bits(tabr, r_len, pos, u[n_limb1] & base_mask1); + /* shift by dpl digits and set the carry */ + if (shift == 0) { + for(j = n_limb1 + 1; j < nb_mods; j++) + carry[j - (n_limb1 + 1)] = u[j]; + } else { + for(j = n_limb1; j < nb_mods - 1; j++) { + carry[j - n_limb1] = (u[j] >> shift) | + (u[j + 1] << (LIMB_BITS - shift)); + } + carry[nb_mods - 1 - n_limb1] = u[nb_mods - 1] >> shift; + } + i++; + } + } +} +#else +static no_inline void ntt_to_limb(BFNTTState *s, limb_t *tabr, limb_t r_len, + const NTTLimb *buf, int fft_len_log2, int dpl, + int nb_mods) +{ + const limb_t *mods = ntt_mods + NB_MODS - nb_mods; + const limb_t *mods_cr, *mods_cr_inv; + limb_t y[NB_MODS], u[NB_MODS], carry[NB_MODS], fft_len, base_mask1, r; + slimb_t i, len, pos; + int j, k, l, shift, n_limb1; + dlimb_t t; + + j = NB_MODS * (NB_MODS - 1) / 2 - nb_mods * (nb_mods - 1) / 2; + mods_cr = ntt_mods_cr + j; + mods_cr_inv = s->ntt_mods_cr_inv + j; + + shift = dpl & (LIMB_BITS - 1); + if (shift == 0) + base_mask1 = -1; + else + base_mask1 = ((limb_t)1 << shift) - 1; + n_limb1 = ((unsigned)dpl - 1) / LIMB_BITS; + for(j = 0; j < NB_MODS; j++) + carry[j] = 0; + for(j = 0; j < NB_MODS; j++) + u[j] = 0; /* avoid warnings */ + memset(tabr, 0, sizeof(limb_t) * r_len); + fft_len = (limb_t)1 << fft_len_log2; + len = bf_min(fft_len, (r_len * LIMB_BITS + dpl - 1) / dpl); + for(i = 0; i < len; i++) { + for(j = 0; j < nb_mods; j++) { + y[j] = ntt_limb_to_int(buf[i + fft_len * j], mods[j]); + } + + /* Chinese remainder to get mixed radix representation */ + l = 0; + for(j = 0; j < nb_mods - 1; j++) { + for(k = j + 1; k < nb_mods; k++) { + limb_t m; + m = mods[k]; + /* Note: there is no overflow in the sub_mod() because + the modulos are sorted by increasing order */ + y[k] = mul_mod_fast2(y[k] - y[j] + m, + mods_cr[l], m, mods_cr_inv[l]); + l++; + } + } + + /* back to normal representation */ + u[0] = y[nb_mods - 1]; + l = 1; + for(j = nb_mods - 2; j >= 1; j--) { + r = y[j]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r; + l++; + } + + /* last step adds the carry */ + r = y[0]; + for(k = 0; k < l; k++) { + t = (dlimb_t)u[k] * mods[j] + r + carry[k]; + r = t >> LIMB_BITS; + u[k] = t; + } + u[l] = r + carry[l]; + + /* write the digits */ + pos = i * dpl; + for(j = 0; j < n_limb1; j++) { + put_bits(tabr, r_len, pos, u[j]); + pos += LIMB_BITS; + } + put_bits(tabr, r_len, pos, u[n_limb1] & base_mask1); + /* shift by dpl digits and set the carry */ + if (shift == 0) { + for(j = n_limb1 + 1; j < nb_mods; j++) + carry[j - (n_limb1 + 1)] = u[j]; + } else { + for(j = n_limb1; j < nb_mods - 1; j++) { + carry[j - n_limb1] = (u[j] >> shift) | + (u[j + 1] << (LIMB_BITS - shift)); + } + carry[nb_mods - 1 - n_limb1] = u[nb_mods - 1] >> shift; + } + } +} +#endif + +static int ntt_static_init(bf_context_t *s1) +{ + BFNTTState *s; + int inverse, i, j, k, l; + limb_t c, c_inv, c_inv2, m, m_inv; + + if (s1->ntt_state) + return 0; +#if defined(__AVX2__) + s = bf_aligned_malloc(s1, sizeof(*s), 64); +#else + s = bf_malloc(s1, sizeof(*s)); +#endif + if (!s) + return -1; + memset(s, 0, sizeof(*s)); + s1->ntt_state = s; + s->ctx = s1; + + for(j = 0; j < NB_MODS; j++) { + m = ntt_mods[j]; + m_inv = init_mul_mod_fast(m); + s->ntt_mods_div[j] = m_inv; +#if defined(__AVX2__) + s->ntt_mods_vec[j] = _mm256_set1_pd(m); + s->ntt_mods_inv_vec[j] = _mm256_set1_pd(1.0 / (double)m); +#endif + c_inv2 = (m + 1) / 2; /* 1/2 */ + c_inv = 1; + for(i = 0; i <= NTT_PROOT_2EXP; i++) { + s->ntt_len_inv[j][i][0] = c_inv; + s->ntt_len_inv[j][i][1] = init_mul_mod_fast2(c_inv, m); + c_inv = mul_mod_fast(c_inv, c_inv2, m, m_inv); + } + + for(inverse = 0; inverse < 2; inverse++) { + c = ntt_proot[inverse][j]; + for(i = 0; i < NTT_PROOT_2EXP; i++) { + s->ntt_proot_pow[j][inverse][NTT_PROOT_2EXP - i] = c; + s->ntt_proot_pow_inv[j][inverse][NTT_PROOT_2EXP - i] = + init_mul_mod_fast2(c, m); + c = mul_mod_fast(c, c, m, m_inv); + } + } + } + + l = 0; + for(j = 0; j < NB_MODS - 1; j++) { + for(k = j + 1; k < NB_MODS; k++) { +#if defined(__AVX2__) + s->ntt_mods_cr_vec[l] = _mm256_set1_pd(int_to_ntt_limb2(ntt_mods_cr[l], + ntt_mods[k])); +#else + s->ntt_mods_cr_inv[l] = init_mul_mod_fast2(ntt_mods_cr[l], + ntt_mods[k]); +#endif + l++; + } + } + return 0; +} + +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len) +{ + int dpl, fft_len_log2, n_bits, nb_mods, dpl_found, fft_len_log2_found; + int int_bits, nb_mods_found; + limb_t cost, min_cost; + + min_cost = -1; + dpl_found = 0; + nb_mods_found = 4; + fft_len_log2_found = 0; + for(nb_mods = 3; nb_mods <= NB_MODS; nb_mods++) { + int_bits = ntt_int_bits[NB_MODS - nb_mods]; + dpl = bf_min((int_bits - 4) / 2, + 2 * LIMB_BITS + 2 * NTT_MOD_LOG2_MIN - NTT_MOD_LOG2_MAX); + for(;;) { + fft_len_log2 = ceil_log2((len * LIMB_BITS + dpl - 1) / dpl); + if (fft_len_log2 > NTT_PROOT_2EXP) + goto next; + n_bits = fft_len_log2 + 2 * dpl; + if (n_bits <= int_bits) { + cost = ((limb_t)(fft_len_log2 + 1) << fft_len_log2) * nb_mods; + // printf("n=%d dpl=%d: cost=%" PRId64 "\n", nb_mods, dpl, (int64_t)cost); + if (cost < min_cost) { + min_cost = cost; + dpl_found = dpl; + nb_mods_found = nb_mods; + fft_len_log2_found = fft_len_log2; + } + break; + } + dpl--; + if (dpl == 0) + break; + } + next: ; + } + if (!dpl_found) + abort(); + /* limit dpl if possible to reduce fixed cost of limb/NTT conversion */ + if (dpl_found > (LIMB_BITS + NTT_MOD_LOG2_MIN) && + ((limb_t)(LIMB_BITS + NTT_MOD_LOG2_MIN) << fft_len_log2_found) >= + len * LIMB_BITS) { + dpl_found = LIMB_BITS + NTT_MOD_LOG2_MIN; + } + *pnb_mods = nb_mods_found; + *pdpl = dpl_found; + return fft_len_log2_found; +} + +/* return 0 if OK, -1 if memory error */ +static no_inline int fft_mul(bf_context_t *s1, + bf_t *res, limb_t *a_tab, limb_t a_len, + limb_t *b_tab, limb_t b_len, int mul_flags) +{ + BFNTTState *s; + int dpl, fft_len_log2, j, nb_mods, reduced_mem; + slimb_t len, fft_len; + NTTLimb *buf1, *buf2, *ptr; +#if defined(USE_MUL_CHECK) + limb_t ha, hb, hr, h_ref; +#endif + + if (ntt_static_init(s1)) + return -1; + s = s1->ntt_state; + + /* find the optimal number of digits per limb (dpl) */ + len = a_len + b_len; + fft_len_log2 = bf_get_fft_size(&dpl, &nb_mods, len); + fft_len = (uint64_t)1 << fft_len_log2; + // printf("len=%" PRId64 " fft_len_log2=%d dpl=%d\n", len, fft_len_log2, dpl); +#if defined(USE_MUL_CHECK) + ha = mp_mod1(a_tab, a_len, BF_CHKSUM_MOD, 0); + hb = mp_mod1(b_tab, b_len, BF_CHKSUM_MOD, 0); +#endif + if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == 0) { + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); + } else if (mul_flags & FFT_MUL_R_OVERLAP_B) { + limb_t *tmp_tab, tmp_len; + /* it is better to free 'b' first */ + tmp_tab = a_tab; + a_tab = b_tab; + b_tab = tmp_tab; + tmp_len = a_len; + a_len = b_len; + b_len = tmp_len; + } + buf1 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods); + if (!buf1) + return -1; + limb_to_ntt(s, buf1, fft_len, a_tab, a_len, dpl, + NB_MODS - nb_mods, nb_mods); + if ((mul_flags & (FFT_MUL_R_OVERLAP_A | FFT_MUL_R_OVERLAP_B)) == + FFT_MUL_R_OVERLAP_A) { + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); + } + reduced_mem = (fft_len_log2 >= 14); + if (!reduced_mem) { + buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len * nb_mods); + if (!buf2) + goto fail; + limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl, + NB_MODS - nb_mods, nb_mods); + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); /* in case res == b */ + } else { + buf2 = ntt_malloc(s, sizeof(NTTLimb) * fft_len); + if (!buf2) + goto fail; + } + for(j = 0; j < nb_mods; j++) { + if (reduced_mem) { + limb_to_ntt(s, buf2, fft_len, b_tab, b_len, dpl, + NB_MODS - nb_mods + j, 1); + ptr = buf2; + } else { + ptr = buf2 + fft_len * j; + } + if (ntt_conv(s, buf1 + fft_len * j, ptr, + fft_len_log2, fft_len_log2, j + NB_MODS - nb_mods)) + goto fail; + } + if (!(mul_flags & FFT_MUL_R_NORESIZE)) + bf_resize(res, 0); /* in case res == b and reduced mem */ + ntt_free(s, buf2); + buf2 = NULL; + if (!(mul_flags & FFT_MUL_R_NORESIZE)) { + if (bf_resize(res, len)) + goto fail; + } + ntt_to_limb(s, res->tab, len, buf1, fft_len_log2, dpl, nb_mods); + ntt_free(s, buf1); +#if defined(USE_MUL_CHECK) + hr = mp_mod1(res->tab, len, BF_CHKSUM_MOD, 0); + h_ref = mul_mod(ha, hb, BF_CHKSUM_MOD); + if (hr != h_ref) { + printf("ntt_mul_error: len=%" PRId_LIMB " fft_len_log2=%d dpl=%d nb_mods=%d\n", + len, fft_len_log2, dpl, nb_mods); + // printf("ha=0x" FMT_LIMB" hb=0x" FMT_LIMB " hr=0x" FMT_LIMB " expected=0x" FMT_LIMB "\n", ha, hb, hr, h_ref); + exit(1); + } +#endif + return 0; + fail: + ntt_free(s, buf1); + ntt_free(s, buf2); + return -1; +} + +#else /* USE_FFT_MUL */ + +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len) +{ + return 0; +} + +#endif /* !USE_FFT_MUL */ + +#undef malloc +#undef free +#undef realloc diff --git a/lib/monoucha0/monoucha/qjs/libbf.h b/lib/monoucha0/monoucha/qjs/libbf.h new file mode 100644 index 00000000..3586532e --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libbf.h @@ -0,0 +1,545 @@ +/* + * Tiny arbitrary precision floating point library + * + * Copyright (c) 2017-2021 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 LIBBF_H +#define LIBBF_H + +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if INTPTR_MAX >= INT64_MAX && !defined(_WIN32) && !defined(__TINYC__) +#define LIMB_LOG2_BITS 6 +#else +#define LIMB_LOG2_BITS 5 +#endif + +#define LIMB_BITS (1 << LIMB_LOG2_BITS) + +#if LIMB_BITS == 64 +#ifndef INT128_MAX +__extension__ typedef __int128 int128_t; +__extension__ typedef unsigned __int128 uint128_t; +#endif +typedef int64_t slimb_t; +typedef uint64_t limb_t; +typedef uint128_t dlimb_t; +#define BF_RAW_EXP_MIN INT64_MIN +#define BF_RAW_EXP_MAX INT64_MAX + +#define LIMB_DIGITS 19 +#define BF_DEC_BASE UINT64_C(10000000000000000000) + +#else + +typedef int32_t slimb_t; +typedef uint32_t limb_t; +typedef uint64_t dlimb_t; +#define BF_RAW_EXP_MIN INT32_MIN +#define BF_RAW_EXP_MAX INT32_MAX + +#define LIMB_DIGITS 9 +#define BF_DEC_BASE 1000000000U + +#endif + +/* in bits */ +/* minimum number of bits for the exponent */ +#define BF_EXP_BITS_MIN 3 +/* maximum number of bits for the exponent */ +#define BF_EXP_BITS_MAX (LIMB_BITS - 3) +/* extended range for exponent, used internally */ +#define BF_EXT_EXP_BITS_MAX (BF_EXP_BITS_MAX + 1) +/* minimum possible precision */ +#define BF_PREC_MIN 2 +/* minimum possible precision */ +#define BF_PREC_MAX (((limb_t)1 << (LIMB_BITS - 2)) - 2) +/* some operations support infinite precision */ +#define BF_PREC_INF (BF_PREC_MAX + 1) /* infinite precision */ + +#if LIMB_BITS == 64 +#define BF_CHKSUM_MOD (UINT64_C(975620677) * UINT64_C(9795002197)) +#else +#define BF_CHKSUM_MOD 975620677U +#endif + +#define BF_EXP_ZERO BF_RAW_EXP_MIN +#define BF_EXP_INF (BF_RAW_EXP_MAX - 1) +#define BF_EXP_NAN BF_RAW_EXP_MAX + +/* +/-zero is represented with expn = BF_EXP_ZERO and len = 0, + +/-infinity is represented with expn = BF_EXP_INF and len = 0, + NaN is represented with expn = BF_EXP_NAN and len = 0 (sign is ignored) + */ +typedef struct { + struct bf_context_t *ctx; + int sign; + slimb_t expn; + limb_t len; + limb_t *tab; +} bf_t; + +typedef struct { + /* must be kept identical to bf_t */ + struct bf_context_t *ctx; + int sign; + slimb_t expn; + limb_t len; + limb_t *tab; +} bfdec_t; + +typedef enum { + BF_RNDN, /* round to nearest, ties to even */ + BF_RNDZ, /* round to zero */ + BF_RNDD, /* round to -inf (the code relies on (BF_RNDD xor BF_RNDU) = 1) */ + BF_RNDU, /* round to +inf */ + BF_RNDNA, /* round to nearest, ties away from zero */ + BF_RNDA, /* round away from zero */ + BF_RNDF, /* faithful rounding (nondeterministic, either RNDD or RNDU, + inexact flag is always set) */ +} bf_rnd_t; + +/* allow subnormal numbers. Only available if the number of exponent + bits is <= BF_EXP_BITS_USER_MAX and prec != BF_PREC_INF. */ +#define BF_FLAG_SUBNORMAL (1 << 3) +/* 'prec' is the precision after the radix point instead of the whole + mantissa. Can only be used with bf_round() and + bfdec_[add|sub|mul|div|sqrt|round](). */ +#define BF_FLAG_RADPNT_PREC (1 << 4) + +#define BF_RND_MASK 0x7 +#define BF_EXP_BITS_SHIFT 5 +#define BF_EXP_BITS_MASK 0x3f + +/* shortcut for bf_set_exp_bits(BF_EXT_EXP_BITS_MAX) */ +#define BF_FLAG_EXT_EXP (BF_EXP_BITS_MASK << BF_EXP_BITS_SHIFT) + +/* contains the rounding mode and number of exponents bits */ +typedef uint32_t bf_flags_t; + +typedef void *bf_realloc_func_t(void *opaque, void *ptr, size_t size); + +typedef struct { + bf_t val; + limb_t prec; +} BFConstCache; + +typedef struct bf_context_t { + void *realloc_opaque; + bf_realloc_func_t *realloc_func; + BFConstCache log2_cache; + BFConstCache pi_cache; + struct BFNTTState *ntt_state; +} bf_context_t; + +static inline int bf_get_exp_bits(bf_flags_t flags) +{ + int e; + e = (flags >> BF_EXP_BITS_SHIFT) & BF_EXP_BITS_MASK; + if (e == BF_EXP_BITS_MASK) + return BF_EXP_BITS_MAX + 1; + else + return BF_EXP_BITS_MAX - e; +} + +static inline bf_flags_t bf_set_exp_bits(int n) +{ + return ((BF_EXP_BITS_MAX - n) & BF_EXP_BITS_MASK) << BF_EXP_BITS_SHIFT; +} + +/* returned status */ +#define BF_ST_INVALID_OP (1 << 0) +#define BF_ST_DIVIDE_ZERO (1 << 1) +#define BF_ST_OVERFLOW (1 << 2) +#define BF_ST_UNDERFLOW (1 << 3) +#define BF_ST_INEXACT (1 << 4) +/* indicate that a memory allocation error occured. NaN is returned */ +#define BF_ST_MEM_ERROR (1 << 5) + +#define BF_RADIX_MAX 36 /* maximum radix for bf_atof() and bf_ftoa() */ + +static inline slimb_t bf_max(slimb_t a, slimb_t b) +{ + if (a > b) + return a; + else + return b; +} + +static inline slimb_t bf_min(slimb_t a, slimb_t b) +{ + if (a < b) + return a; + else + return b; +} + +void bf_context_init(bf_context_t *s, bf_realloc_func_t *realloc_func, + void *realloc_opaque); +void bf_context_end(bf_context_t *s); +/* free memory allocated for the bf cache data */ +void bf_clear_cache(bf_context_t *s); + +static inline void *bf_realloc(bf_context_t *s, void *ptr, size_t size) +{ + return s->realloc_func(s->realloc_opaque, ptr, size); +} + +/* 'size' must be != 0 */ +static inline void *bf_malloc(bf_context_t *s, size_t size) +{ + return bf_realloc(s, NULL, size); +} + +static inline void bf_free(bf_context_t *s, void *ptr) +{ + /* must test ptr otherwise equivalent to malloc(0) */ + if (ptr) + bf_realloc(s, ptr, 0); +} + +void bf_init(bf_context_t *s, bf_t *r); + +static inline void bf_delete(bf_t *r) +{ + bf_context_t *s = r->ctx; + /* we accept to delete a zeroed bf_t structure */ + if (s && r->tab) { + bf_realloc(s, r->tab, 0); + } +} + +static inline void bf_neg(bf_t *r) +{ + r->sign ^= 1; +} + +static inline int bf_is_finite(const bf_t *a) +{ + return (a->expn < BF_EXP_INF); +} + +static inline int bf_is_nan(const bf_t *a) +{ + return (a->expn == BF_EXP_NAN); +} + +static inline int bf_is_zero(const bf_t *a) +{ + return (a->expn == BF_EXP_ZERO); +} + +static inline void bf_memcpy(bf_t *r, const bf_t *a) +{ + *r = *a; +} + +int bf_set_ui(bf_t *r, uint64_t a); +int bf_set_si(bf_t *r, int64_t a); +void bf_set_nan(bf_t *r); +void bf_set_zero(bf_t *r, int is_neg); +void bf_set_inf(bf_t *r, int is_neg); +int bf_set(bf_t *r, const bf_t *a); +void bf_move(bf_t *r, bf_t *a); +int bf_get_float64(const bf_t *a, double *pres, bf_rnd_t rnd_mode); +int bf_set_float64(bf_t *a, double d); + +int bf_cmpu(const bf_t *a, const bf_t *b); +int bf_cmp_full(const bf_t *a, const bf_t *b); +int bf_cmp(const bf_t *a, const bf_t *b); +static inline int bf_cmp_eq(const bf_t *a, const bf_t *b) +{ + return bf_cmp(a, b) == 0; +} + +static inline int bf_cmp_le(const bf_t *a, const bf_t *b) +{ + return bf_cmp(a, b) <= 0; +} + +static inline int bf_cmp_lt(const bf_t *a, const bf_t *b) +{ + return bf_cmp(a, b) < 0; +} + +int bf_add(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +int bf_sub(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +int bf_add_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, bf_flags_t flags); +int bf_mul(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +int bf_mul_ui(bf_t *r, const bf_t *a, uint64_t b1, limb_t prec, bf_flags_t flags); +int bf_mul_si(bf_t *r, const bf_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bf_mul_2exp(bf_t *r, slimb_t e, limb_t prec, bf_flags_t flags); +int bf_div(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, bf_flags_t flags); +#define BF_DIVREM_EUCLIDIAN BF_RNDF +int bf_divrem(bf_t *q, bf_t *r, const bf_t *a, const bf_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode); +int bf_rem(bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +int bf_remquo(slimb_t *pq, bf_t *r, const bf_t *a, const bf_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +/* round to integer with infinite precision */ +int bf_rint(bf_t *r, int rnd_mode); +int bf_round(bf_t *r, limb_t prec, bf_flags_t flags); +int bf_sqrtrem(bf_t *r, bf_t *rem1, const bf_t *a); +int bf_sqrt(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +slimb_t bf_get_exp_min(const bf_t *a); +int bf_logic_or(bf_t *r, const bf_t *a, const bf_t *b); +int bf_logic_xor(bf_t *r, const bf_t *a, const bf_t *b); +int bf_logic_and(bf_t *r, const bf_t *a, const bf_t *b); + +/* additional flags for bf_atof */ +/* do not accept hex radix prefix (0x or 0X) if radix = 0 or radix = 16 */ +#define BF_ATOF_NO_HEX (1 << 16) +/* accept binary (0b or 0B) or octal (0o or 0O) radix prefix if radix = 0 */ +#define BF_ATOF_BIN_OCT (1 << 17) +/* Do not parse NaN or Inf */ +#define BF_ATOF_NO_NAN_INF (1 << 18) +/* return the exponent separately */ +#define BF_ATOF_EXPONENT (1 << 19) + +int bf_atof(bf_t *a, const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags); +/* this version accepts prec = BF_PREC_INF and returns the radix + exponent */ +int bf_atof2(bf_t *r, slimb_t *pexponent, + const char *str, const char **pnext, int radix, + limb_t prec, bf_flags_t flags); +int bf_mul_pow_radix(bf_t *r, const bf_t *T, limb_t radix, + slimb_t expn, limb_t prec, bf_flags_t flags); + + +/* Conversion of floating point number to string. Return a null + terminated string or NULL if memory error. *plen contains its + length if plen != NULL. The exponent letter is "e" for base 10, + "p" for bases 2, 8, 16 with a binary exponent and "@" for the other + bases. */ + +#define BF_FTOA_FORMAT_MASK (3 << 16) + +/* fixed format: prec significant digits rounded with (flags & + BF_RND_MASK). Exponential notation is used if too many zeros are + needed.*/ +#define BF_FTOA_FORMAT_FIXED (0 << 16) +/* fractional format: prec digits after the decimal point rounded with + (flags & BF_RND_MASK) */ +#define BF_FTOA_FORMAT_FRAC (1 << 16) +/* free format: + + For binary radices with bf_ftoa() and for bfdec_ftoa(): use the minimum + number of digits to represent 'a'. The precision and the rounding + mode are ignored. + + For the non binary radices with bf_ftoa(): use as many digits as + necessary so that bf_atof() return the same number when using + precision 'prec', rounding to nearest and the subnormal + configuration of 'flags'. The result is meaningful only if 'a' is + already rounded to 'prec' bits. If the subnormal flag is set, the + exponent in 'flags' must also be set to the desired exponent range. +*/ +#define BF_FTOA_FORMAT_FREE (2 << 16) +/* same as BF_FTOA_FORMAT_FREE but uses the minimum number of digits + (takes more computation time). Identical to BF_FTOA_FORMAT_FREE for + binary radices with bf_ftoa() and for bfdec_ftoa(). */ +#define BF_FTOA_FORMAT_FREE_MIN (3 << 16) + +/* force exponential notation for fixed or free format */ +#define BF_FTOA_FORCE_EXP (1 << 20) +/* add 0x prefix for base 16, 0o prefix for base 8 or 0b prefix for + base 2 if non zero value */ +#define BF_FTOA_ADD_PREFIX (1 << 21) +/* return "Infinity" instead of "Inf" and add a "+" for positive + exponents */ +#define BF_FTOA_JS_QUIRKS (1 << 22) + +char *bf_ftoa(size_t *plen, const bf_t *a, int radix, limb_t prec, + bf_flags_t flags); + +/* modulo 2^n instead of saturation. NaN and infinity return 0 */ +#define BF_GET_INT_MOD (1 << 0) +int bf_get_int32(int *pres, const bf_t *a, int flags); +int bf_get_int64(int64_t *pres, const bf_t *a, int flags); +int bf_get_uint64(uint64_t *pres, const bf_t *a); + +/* the following functions are exported for testing only. */ +void mp_print_str(const char *str, const limb_t *tab, limb_t n); +void bf_print_str(const char *str, const bf_t *a); +int bf_resize(bf_t *r, limb_t len); +int bf_get_fft_size(int *pdpl, int *pnb_mods, limb_t len); +int bf_normalize_and_round(bf_t *r, limb_t prec1, bf_flags_t flags); +int bf_can_round(const bf_t *a, slimb_t prec, bf_rnd_t rnd_mode, slimb_t k); +slimb_t bf_mul_log2_radix(slimb_t a1, unsigned int radix, int is_inv, + int is_ceil1); +int mp_mul(bf_context_t *s, limb_t *result, + const limb_t *op1, limb_t op1_size, + const limb_t *op2, limb_t op2_size); +limb_t mp_add(limb_t *res, const limb_t *op1, const limb_t *op2, + limb_t n, limb_t carry); +limb_t mp_add_ui(limb_t *tab, limb_t b, size_t n); +int mp_sqrtrem(bf_context_t *s, limb_t *tabs, limb_t *taba, limb_t n); +int mp_recip(bf_context_t *s, limb_t *tabr, const limb_t *taba, limb_t n); +limb_t bf_isqrt(limb_t a); + +/* transcendental functions */ +int bf_const_log2(bf_t *T, limb_t prec, bf_flags_t flags); +int bf_const_pi(bf_t *T, limb_t prec, bf_flags_t flags); +int bf_exp(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_log(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +#define BF_POW_JS_QUIRKS (1 << 16) /* (+/-1)^(+/-Inf) = NaN, 1^NaN = NaN */ +int bf_pow(bf_t *r, const bf_t *x, const bf_t *y, limb_t prec, bf_flags_t flags); +int bf_cos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_sin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_tan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_atan(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_atan2(bf_t *r, const bf_t *y, const bf_t *x, + limb_t prec, bf_flags_t flags); +int bf_asin(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); +int bf_acos(bf_t *r, const bf_t *a, limb_t prec, bf_flags_t flags); + +/* decimal floating point */ + +static inline void bfdec_init(bf_context_t *s, bfdec_t *r) +{ + bf_init(s, (bf_t *)r); +} +static inline void bfdec_delete(bfdec_t *r) +{ + bf_delete((bf_t *)r); +} + +static inline void bfdec_neg(bfdec_t *r) +{ + r->sign ^= 1; +} + +static inline int bfdec_is_finite(const bfdec_t *a) +{ + return (a->expn < BF_EXP_INF); +} + +static inline int bfdec_is_nan(const bfdec_t *a) +{ + return (a->expn == BF_EXP_NAN); +} + +static inline int bfdec_is_zero(const bfdec_t *a) +{ + return (a->expn == BF_EXP_ZERO); +} + +static inline void bfdec_memcpy(bfdec_t *r, const bfdec_t *a) +{ + bf_memcpy((bf_t *)r, (const bf_t *)a); +} + +int bfdec_set_ui(bfdec_t *r, uint64_t a); +int bfdec_set_si(bfdec_t *r, int64_t a); + +static inline void bfdec_set_nan(bfdec_t *r) +{ + bf_set_nan((bf_t *)r); +} +static inline void bfdec_set_zero(bfdec_t *r, int is_neg) +{ + bf_set_zero((bf_t *)r, is_neg); +} +static inline void bfdec_set_inf(bfdec_t *r, int is_neg) +{ + bf_set_inf((bf_t *)r, is_neg); +} +static inline int bfdec_set(bfdec_t *r, const bfdec_t *a) +{ + return bf_set((bf_t *)r, (bf_t *)a); +} +static inline void bfdec_move(bfdec_t *r, bfdec_t *a) +{ + bf_move((bf_t *)r, (bf_t *)a); +} +static inline int bfdec_cmpu(const bfdec_t *a, const bfdec_t *b) +{ + return bf_cmpu((const bf_t *)a, (const bf_t *)b); +} +static inline int bfdec_cmp_full(const bfdec_t *a, const bfdec_t *b) +{ + return bf_cmp_full((const bf_t *)a, (const bf_t *)b); +} +static inline int bfdec_cmp(const bfdec_t *a, const bfdec_t *b) +{ + return bf_cmp((const bf_t *)a, (const bf_t *)b); +} +static inline int bfdec_cmp_eq(const bfdec_t *a, const bfdec_t *b) +{ + return bfdec_cmp(a, b) == 0; +} +static inline int bfdec_cmp_le(const bfdec_t *a, const bfdec_t *b) +{ + return bfdec_cmp(a, b) <= 0; +} +static inline int bfdec_cmp_lt(const bfdec_t *a, const bfdec_t *b) +{ + return bfdec_cmp(a, b) < 0; +} + +int bfdec_add(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_sub(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_add_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bfdec_mul(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_mul_si(bfdec_t *r, const bfdec_t *a, int64_t b1, limb_t prec, + bf_flags_t flags); +int bfdec_div(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags); +int bfdec_divrem(bfdec_t *q, bfdec_t *r, const bfdec_t *a, const bfdec_t *b, + limb_t prec, bf_flags_t flags, int rnd_mode); +int bfdec_rem(bfdec_t *r, const bfdec_t *a, const bfdec_t *b, limb_t prec, + bf_flags_t flags, int rnd_mode); +int bfdec_rint(bfdec_t *r, int rnd_mode); +int bfdec_sqrt(bfdec_t *r, const bfdec_t *a, limb_t prec, bf_flags_t flags); +int bfdec_round(bfdec_t *r, limb_t prec, bf_flags_t flags); +int bfdec_get_int32(int *pres, const bfdec_t *a); +int bfdec_pow_ui(bfdec_t *r, const bfdec_t *a, limb_t b); + +char *bfdec_ftoa(size_t *plen, const bfdec_t *a, limb_t prec, bf_flags_t flags); +int bfdec_atof(bfdec_t *r, const char *str, const char **pnext, + limb_t prec, bf_flags_t flags); + +/* the following functions are exported for testing only. */ +extern const limb_t mp_pow_dec[LIMB_DIGITS + 1]; +void bfdec_print_str(const char *str, const bfdec_t *a); +static inline int bfdec_resize(bfdec_t *r, limb_t len) +{ + return bf_resize((bf_t *)r, len); +} +int bfdec_normalize_and_round(bfdec_t *r, limb_t prec1, bf_flags_t flags); + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBBF_H */ diff --git a/lib/monoucha0/monoucha/qjs/libregexp-opcode.h b/lib/monoucha0/monoucha/qjs/libregexp-opcode.h new file mode 100644 index 00000000..5c1714ab --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libregexp-opcode.h @@ -0,0 +1,58 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 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. + */ + +#ifdef DEF + +DEF(invalid, 1) /* never used */ +DEF(char8, 2) /* 7 bits in fact */ +DEF(char16, 3) +DEF(char32, 5) +DEF(dot, 1) +DEF(any, 1) /* same as dot but match any character including line terminator */ +DEF(line_start, 1) +DEF(line_end, 1) +DEF(goto, 5) +DEF(split_goto_first, 5) +DEF(split_next_first, 5) +DEF(match, 1) +DEF(save_start, 2) /* save start position */ +DEF(save_end, 2) /* save end position, must come after saved_start */ +DEF(save_reset, 3) /* reset save positions */ +DEF(loop, 5) /* decrement the top the stack and goto if != 0 */ +DEF(push_i32, 5) /* push integer on the stack */ +DEF(drop, 1) +DEF(word_boundary, 1) +DEF(not_word_boundary, 1) +DEF(back_reference, 2) +DEF(backward_back_reference, 2) /* must come after back_reference */ +DEF(range, 3) /* variable length */ +DEF(range32, 3) /* variable length */ +DEF(lookahead, 5) +DEF(negative_lookahead, 5) +DEF(push_char_pos, 1) /* push the character position on the stack */ +DEF(check_advance, 1) /* pop one stack element and check that it is different from the character position */ +DEF(prev, 1) /* go to the previous char */ +DEF(simple_greedy_quant, 17) + +#endif /* DEF */ diff --git a/lib/monoucha0/monoucha/qjs/libregexp.c b/lib/monoucha0/monoucha/qjs/libregexp.c new file mode 100644 index 00000000..603c2795 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libregexp.c @@ -0,0 +1,2662 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <inttypes.h> +#include <string.h> +#include <assert.h> + +#include "cutils.h" +#include "libregexp.h" + +/* + TODO: + + - Add a lock step execution mode (=linear time execution guaranteed) + when the regular expression is "simple" i.e. no backreference nor + complicated lookahead. The opcodes are designed for this execution + model. +*/ + +#if defined(TEST) +#define DUMP_REOP +#endif + +typedef enum { +#define DEF(id, size) REOP_ ## id, +#include "libregexp-opcode.h" +#undef DEF + REOP_COUNT, +} REOPCodeEnum; + +#define CAPTURE_COUNT_MAX 255 +#define STACK_SIZE_MAX 255 + +/* unicode code points */ +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +#define TMP_BUF_SIZE 128 + +// invariant: is_unicode ^ unicode_sets (or neither, but not both) +typedef struct { + DynBuf byte_code; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *buf_start; + int re_flags; + BOOL is_unicode; + BOOL unicode_sets; + BOOL ignore_case; + BOOL dotall; + int capture_count; + int total_capture_count; /* -1 = not computed yet */ + int has_named_captures; /* -1 = don't know, 0 = no, 1 = yes */ + void *opaque; + DynBuf group_names; + union { + char error_msg[TMP_BUF_SIZE]; + char tmp_buf[TMP_BUF_SIZE]; + } u; +} REParseState; + +typedef struct { +#ifdef DUMP_REOP + const char *name; +#endif + uint8_t size; +} REOpCode; + +static const REOpCode reopcode_info[REOP_COUNT] = { +#ifdef DUMP_REOP +#define DEF(id, size) { #id, size }, +#else +#define DEF(id, size) { size }, +#endif +#include "libregexp-opcode.h" +#undef DEF +}; + +#define RE_HEADER_FLAGS 0 +#define RE_HEADER_CAPTURE_COUNT 2 +#define RE_HEADER_STACK_SIZE 3 +#define RE_HEADER_BYTECODE_LEN 4 + +#define RE_HEADER_LEN 8 + +static inline int lre_is_digit(int c) { + return c >= '0' && c <= '9'; +} + +/* insert 'len' bytes at position 'pos'. Return < 0 if error. */ +static int dbuf_insert(DynBuf *s, int pos, int len) +{ + if (dbuf_realloc(s, s->size + len)) + return -1; + memmove(s->buf + pos + len, s->buf + pos, s->size - pos); + s->size += len; + return 0; +} + +static const uint16_t char_range_d[] = { + 1, + 0x0030, 0x0039 + 1, +}; + +/* code point ranges for Zs,Zl or Zp property */ +static const uint16_t char_range_s[] = { + 10, + 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, +}; + +BOOL lre_is_space(int c) +{ + int i, n, low, high; + n = (countof(char_range_s) - 1) / 2; + for(i = 0; i < n; i++) { + low = char_range_s[2 * i + 1]; + if (c < low) + return FALSE; + high = char_range_s[2 * i + 2]; + if (c < high) + return TRUE; + } + return FALSE; +} + +uint32_t const lre_id_start_table_ascii[4] = { + /* $ A-Z _ a-z */ + 0x00000000, 0x00000010, 0x87FFFFFE, 0x07FFFFFE +}; + +uint32_t const lre_id_continue_table_ascii[4] = { + /* $ 0-9 A-Z _ a-z */ + 0x00000000, 0x03FF0010, 0x87FFFFFE, 0x07FFFFFE +}; + + +static const uint16_t char_range_w[] = { + 4, + 0x0030, 0x0039 + 1, + 0x0041, 0x005A + 1, + 0x005F, 0x005F + 1, + 0x0061, 0x007A + 1, +}; + +#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 const uint16_t *char_range_table[] = { + char_range_d, + char_range_s, + char_range_w, +}; + +static int cr_init_char_range(REParseState *s, CharRange *cr, uint32_t c) +{ + BOOL invert; + const uint16_t *c_pt; + int len, i; + + invert = c & 1; + c_pt = char_range_table[c >> 1]; + len = *c_pt++; + cr_init(cr, s->opaque, lre_realloc); + for(i = 0; i < len * 2; i++) { + if (cr_add_point(cr, c_pt[i])) + goto fail; + } + if (invert) { + if (cr_invert(cr)) + goto fail; + } + return 0; + fail: + cr_free(cr); + return -1; +} + +#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, i; + uint32_t val; + + assert(buf_len >= RE_HEADER_LEN); + + re_flags = lre_get_flags(buf); + bc_len = get_u32(buf + RE_HEADER_BYTECODE_LEN); + assert(bc_len + RE_HEADER_LEN <= buf_len); + printf("flags: 0x%x capture_count=%d stack_size=%d\n", + re_flags, buf[RE_HEADER_CAPTURE_COUNT], buf[RE_HEADER_STACK_SIZE]); + if (re_flags & LRE_FLAG_NAMED_GROUPS) { + const char *p; + p = (char *)buf + RE_HEADER_LEN + bc_len; + printf("named groups: "); + for(i = 1; i < buf[RE_HEADER_CAPTURE_COUNT]; i++) { + if (i != 1) + printf(","); + printf("<%s>", p); + p += strlen(p) + 1; + } + printf("\n"); + assert(p == (char *)(buf + buf_len)); + } + printf("bytecode_len=%d\n", 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_char8: + val = get_u8(buf + pos + 1); + goto printchar; + case REOP_char16: + val = get_u16(buf + pos + 1); + goto printchar; + case REOP_char32: + val = get_u32(buf + pos + 1); + printchar: + if (val >= ' ' && val <= 126) + printf(" '%c'", val); + else + printf(" 0x%08x", val); + break; + case REOP_goto: + case REOP_split_goto_first: + case REOP_split_next_first: + case REOP_loop: + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(buf + pos + 1); + val += (pos + 5); + printf(" %u", val); + break; + case REOP_simple_greedy_quant: + printf(" %u %u %u %u", + get_u32(buf + pos + 1) + (pos + 17), + get_u32(buf + pos + 1 + 4), + get_u32(buf + pos + 1 + 8), + get_u32(buf + pos + 1 + 12)); + break; + case REOP_save_start: + case REOP_save_end: + case REOP_back_reference: + case REOP_backward_back_reference: + printf(" %u", buf[pos + 1]); + break; + case REOP_save_reset: + printf(" %u %u", buf[pos + 1], buf[pos + 2]); + break; + case REOP_push_i32: + val = get_u32(buf + pos + 1); + printf(" %d", val); + break; + case REOP_range: + { + int n, i; + n = get_u16(buf + pos + 1); + len += n * 4; + for(i = 0; i < n * 2; i++) { + val = get_u16(buf + pos + 3 + i * 2); + printf(" 0x%04x", val); + } + } + break; + case REOP_range32: + { + 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%08x", val); + } + } + break; + default: + break; + } + printf("\n"); + pos += len; + } +} +#endif + +static void re_emit_op(REParseState *s, int op) +{ + dbuf_putc(&s->byte_code, op); +} + +/* return the offset of the u32 value */ +static int re_emit_op_u32(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val); + return pos; +} + +static int re_emit_goto(REParseState *s, int op, uint32_t val) +{ + int pos; + dbuf_putc(&s->byte_code, op); + pos = s->byte_code.size; + dbuf_put_u32(&s->byte_code, val - (pos + 4)); + return pos; +} + +static void re_emit_op_u8(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_putc(&s->byte_code, val); +} + +static void re_emit_op_u16(REParseState *s, int op, uint32_t val) +{ + dbuf_putc(&s->byte_code, op); + dbuf_put_u16(&s->byte_code, val); +} + +static int __attribute__((format(printf, 2, 3))) re_parse_error(REParseState *s, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vsnprintf(s->u.error_msg, sizeof(s->u.error_msg), fmt, ap); + va_end(ap); + return -1; +} + +static int re_parse_out_of_memory(REParseState *s) +{ + return re_parse_error(s, "out of memory"); +} + +/* If allow_overflow is false, return -1 in case of + overflow. Otherwise return INT32_MAX. */ +static int parse_digits(const uint8_t **pp, BOOL allow_overflow) +{ + 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 >= INT32_MAX) { + if (allow_overflow) + v = INT32_MAX; + else + return -1; + } + p++; + } + *pp = p; + return v; +} + +static int re_parse_expect(REParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + p = *pp; + if (*p != c) + return re_parse_error(s, "expecting '%c'", c); + p++; + *pp = p; + return 0; +} + +/* Parse an escape sequence, *pp points after the '\': + allow_utf16 value: + 0 : no UTF-16 escapes allowed + 1 : UTF-16 escapes allowed + 2 : UTF-16 escapes allowed and escapes of surrogate pairs are + converted to a unicode character (unicode regexp case). + + Return the unicode char and update *pp if recognized, + return -1 if malformed escape, + return -2 otherwise. */ +int lre_parse_escape(const uint8_t **pp, int allow_utf16) +{ + const uint8_t *p; + uint32_t c; + + p = *pp; + 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 'x': + case 'u': + { + int h, n, i; + uint32_t c1; + + if (*p == '{' && allow_utf16) { + 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 { + if (c == 'x') { + n = 2; + } else { + n = 4; + } + + c = 0; + for(i = 0; i < n; i++) { + h = from_hex(*p++); + if (h < 0) { + return -1; + } + c = (c << 4) | h; + } + if (is_hi_surrogate(c) && + allow_utf16 == 2 && p[0] == '\\' && p[1] == 'u') { + /* convert an escaped surrogate pair into a + unicode char */ + c1 = 0; + for(i = 0; i < 4; i++) { + h = from_hex(p[2 + i]); + if (h < 0) + break; + c1 = (c1 << 4) | h; + } + if (i == 4 && is_lo_surrogate(c1)) { + p += 6; + c = from_surrogate(c, c1); + } + } + } + } + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c -= '0'; + if (allow_utf16 == 2) { + /* only accept \0 not followed by digit */ + if (c != 0 || lre_is_digit(*p)) + return -1; + } else { + /* parse a legacy octal sequence */ + uint32_t v; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + if (c >= 32) + break; + v = *p - '0'; + if (v > 7) + break; + c = (c << 3) | v; + p++; + } + break; + default: + return -2; + } + *pp = p; + return c; +} + +/* XXX: we use the same chars for name and value */ +static BOOL is_unicode_char(int c) +{ + return ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_')); +} + +static int parse_unicode_property(REParseState *s, CharRange *cr, + const uint8_t **pp, BOOL is_inv) +{ + const uint8_t *p; + char name[64], value[64]; + char *q; + BOOL script_ext; + int ret; + + p = *pp; + if (*p != '{') + return re_parse_error(s, "expecting '{' after \\p"); + p++; + q = name; + while (is_unicode_char(*p)) { + if ((q - name) >= sizeof(name) - 1) + goto unknown_property_name; + *q++ = *p++; + } + *q = '\0'; + q = value; + if (*p == '=') { + p++; + while (is_unicode_char(*p)) { + if ((q - value) >= sizeof(value) - 1) + return re_parse_error(s, "unknown unicode property value"); + *q++ = *p++; + } + } + *q = '\0'; + if (*p != '}') + return re_parse_error(s, "expecting '}'"); + p++; + // printf("name=%s value=%s\n", name, value); + + if (!strcmp(name, "Script") || !strcmp(name, "sc")) { + script_ext = FALSE; + goto do_script; + } else if (!strcmp(name, "Script_Extensions") || !strcmp(name, "scx")) { + script_ext = TRUE; + do_script: + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_script(cr, value, script_ext); + if (ret) { + cr_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode script"); + else + goto out_of_memory; + } + } else if (!strcmp(name, "General_Category") || !strcmp(name, "gc")) { + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_general_category(cr, value); + if (ret) { + cr_free(cr); + if (ret == -2) + return re_parse_error(s, "unknown unicode general category"); + else + goto out_of_memory; + } + } else if (value[0] == '\0') { + cr_init(cr, s->opaque, lre_realloc); + ret = unicode_general_category(cr, name); + if (ret == -1) { + cr_free(cr); + goto out_of_memory; + } + if (ret < 0) { + ret = unicode_prop(cr, name); + if (ret) { + cr_free(cr); + if (ret == -2) + goto unknown_property_name; + else + goto out_of_memory; + } + } + } else { + unknown_property_name: + return re_parse_error(s, "unknown unicode property name"); + } + + if (is_inv) { + if (cr_invert(cr)) { + cr_free(cr); + return -1; + } + } + *pp = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +/* return -1 if error otherwise the character or a class range + (CLASS_RANGE_BASE). In case of class range, 'cr' is + initialized. Otherwise, it is ignored. */ +static int get_class_atom(REParseState *s, CharRange *cr, + const uint8_t **pp, BOOL inclass) +{ + const uint8_t *p, *p_next; + uint32_t c; + int ret; + + p = *pp; + + c = *p; + switch(c) { + case '\\': + p++; + if (p >= s->buf_end) + goto unexpected_end; + 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: + if (cr_init_char_range(s, cr, c)) + return -1; + 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 'p': + case 'P': + if (s->is_unicode) { + if (parse_unicode_property(s, cr, &p, (c == 'P'))) + return -1; + c = CLASS_RANGE_BASE; + break; + } + /* fall thru */ + default: + p--; + ret = lre_parse_escape(&p, s->is_unicode * 2); + if (ret >= 0) { + c = ret; + } else { + if (ret == -2 && *p != '\0' && strchr("^$\\.*+?()[]{}|/", *p)) { + /* always valid to escape these characters */ + goto normal_char; + } else if (s->is_unicode) { + // special case: allowed inside [] but not outside + if (ret == -2 && *p == '-' && inclass) + goto normal_char; + invalid_escape: + return re_parse_error(s, "invalid escape sequence in regular expression"); + } else { + /* just ignore the '\' */ + goto normal_char; + } + } + break; + } + break; + case '\0': + if (p >= s->buf_end) { + unexpected_end: + return re_parse_error(s, "unexpected end"); + } + /* fall thru */ + default: + normal_char: + p++; + if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + return re_parse_error(s, "invalid UTF-8 sequence"); + p = p_next; + if (c > 0xFFFF && !s->is_unicode) { + // TODO(chqrlie): should handle non BMP-1 code points in + // the calling function and no require the source string + // to be CESU-8 encoded if not s->is_unicode + return re_parse_error(s, "malformed unicode char"); + } + } + break; + } + *pp = p; + return c; +} + +static int re_emit_range(REParseState *s, const CharRange *cr) +{ + int len, i; + uint32_t high; + + len = (unsigned)cr->len / 2; + if (len >= 65535) + return re_parse_error(s, "too many ranges"); + if (len == 0) { + /* not sure it can really happen. Emit a match that is always + false */ + re_emit_op_u32(s, REOP_char32, -1); + } else { + high = cr->points[cr->len - 1]; + if (high == UINT32_MAX) + high = cr->points[cr->len - 2]; + if (high <= 0xffff) { + /* can use 16 bit ranges with the conversion that 0xffff = + infinity */ + re_emit_op_u16(s, REOP_range, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u16(&s->byte_code, cr->points[i]); + high = cr->points[i + 1] - 1; + if (high == UINT32_MAX - 1) + high = 0xffff; + dbuf_put_u16(&s->byte_code, high); + } + } else { + re_emit_op_u16(s, REOP_range32, len); + for(i = 0; i < cr->len; i += 2) { + dbuf_put_u32(&s->byte_code, cr->points[i]); + dbuf_put_u32(&s->byte_code, cr->points[i + 1] - 1); + } + } + } + return 0; +} + +// s->unicode turns patterns like []] into syntax errors +// s->unicode_sets turns more patterns into errors, like [a-] or [[] +static int re_parse_char_class(REParseState *s, const uint8_t **pp) +{ + const uint8_t *p; + uint32_t c1, c2; + CharRange cr_s, *cr = &cr_s; + CharRange cr1_s, *cr1 = &cr1_s; + BOOL invert; + + cr_init(cr, s->opaque, lre_realloc); + p = *pp; + p++; /* skip '[' */ + + if (s->unicode_sets) { + static const char verboten[] = + "()[{}/-|" "\0" + "&&!!##$$%%**++,,..::;;<<==>>??@@``~~" "\0" + "^^^_^^"; + const char *s = verboten; + int n = 1; + do { + if (!memcmp(s, p, n)) + if (p[n] == ']') + goto invalid_class_range; + s += n; + if (!*s) { + s++; + n++; + } + } while (n < 4); + } + + invert = FALSE; + if (*p == '^') { + p++; + invert = TRUE; + } + + for(;;) { + if (*p == ']') + break; + c1 = get_class_atom(s, cr1, &p, TRUE); + if ((int)c1 < 0) + goto fail; + if (*p == '-' && p[1] == ']' && s->unicode_sets) { + if (c1 >= CLASS_RANGE_BASE) + cr_free(cr1); + goto invalid_class_range; + } + if (*p == '-' && p[1] != ']') { + const uint8_t *p0 = p + 1; + if (c1 >= CLASS_RANGE_BASE) { + if (s->is_unicode) { + cr_free(cr1); + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + c2 = get_class_atom(s, cr1, &p0, TRUE); + if ((int)c2 < 0) + goto fail; + if (c2 >= CLASS_RANGE_BASE) { + cr_free(cr1); + if (s->is_unicode) { + goto invalid_class_range; + } + /* Annex B: match '-' character */ + goto class_atom; + } + p = p0; + if (c2 < c1) { + invalid_class_range: + re_parse_error(s, "invalid class range"); + goto fail; + } + if (cr_union_interval(cr, c1, c2)) + goto memory_error; + } else { + class_atom: + if (c1 >= CLASS_RANGE_BASE) { + int ret; + ret = cr_union1(cr, cr1->points, cr1->len); + cr_free(cr1); + if (ret) + goto memory_error; + } else { + if (cr_union_interval(cr, c1, c1)) + goto memory_error; + } + } + } + if (s->ignore_case) { + if (cr_regexp_canonicalize(cr, s->is_unicode)) + goto memory_error; + } + if (invert) { + if (cr_invert(cr)) + goto memory_error; + } + if (re_emit_range(s, cr)) + goto fail; + cr_free(cr); + p++; /* skip ']' */ + *pp = p; + return 0; + memory_error: + re_parse_out_of_memory(s); + fail: + cr_free(cr); + return -1; +} + +/* Return: + - true if the opcodes may not advance the char pointer + - false if the opcodes always advance the char pointer +*/ +static BOOL re_need_check_advance(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len; + uint32_t val; + BOOL ret; + + ret = TRUE; + pos = 0; + + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char32: + case REOP_char16: + case REOP_char8: + case REOP_dot: + case REOP_any: + simple_char: + ret = FALSE; + break; + case REOP_line_start: + case REOP_line_end: + case REOP_push_i32: + case REOP_push_char_pos: + case REOP_drop: + case REOP_word_boundary: + case REOP_not_word_boundary: + case REOP_prev: + /* no effect */ + break; + case REOP_save_start: + case REOP_save_end: + case REOP_save_reset: + case REOP_back_reference: + case REOP_backward_back_reference: + break; + default: + /* safe behvior: we cannot predict the outcome */ + return TRUE; + } + pos += len; + } + return ret; +} + +/* return -1 if a simple quantifier cannot be used. Otherwise return + the number of characters in the atom. */ +static int re_is_simple_quantifier(const uint8_t *bc_buf, int bc_buf_len) +{ + int pos, opcode, len, count; + uint32_t val; + + count = 0; + pos = 0; + while (pos < bc_buf_len) { + opcode = bc_buf[pos]; + len = reopcode_info[opcode].size; + switch(opcode) { + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + goto simple_char; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + goto simple_char; + case REOP_char32: + case REOP_char16: + case REOP_char8: + case REOP_dot: + case REOP_any: + simple_char: + count++; + break; + case REOP_line_start: + case REOP_line_end: + case REOP_word_boundary: + case REOP_not_word_boundary: + break; + default: + return -1; + } + pos += len; + } + return count; +} + +/* '*pp' is the first char after '<' */ +static int re_parse_group_name(char *buf, int buf_size, const uint8_t **pp) +{ + const uint8_t *p, *p_next; + uint32_t c, d; + char *q; + + p = *pp; + q = buf; + for(;;) { + c = *p++; + if (c == '\\') { + if (*p != 'u') + return -1; + c = lre_parse_escape(&p, 2); // accept surrogate pairs + if ((int)c < 0) + return -1; + } else if (c == '>') { + break; + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + return -1; + p = p_next; + if (is_hi_surrogate(c)) { + d = utf8_decode(p, &p_next); + if (is_lo_surrogate(d)) { + c = from_surrogate(c, d); + p = p_next; + } + } + } + if (q == buf) { + if (!lre_js_is_ident_first(c)) + return -1; + } else { + if (!lre_js_is_ident_next(c)) + return -1; + } + if ((q - buf + UTF8_CHAR_LEN_MAX + 1) > buf_size) + return -1; + if (c < 0x80) { + *q++ = c; + } else { + q += utf8_encode((uint8_t*)q, c); + } + } + if (q == buf) + return -1; + *q = '\0'; + *pp = p; + return 0; +} + +/* if capture_name = NULL: return the number of captures + 1. + Otherwise, return the capture index corresponding to capture_name + or -1 if none */ +static int re_parse_captures(REParseState *s, int *phas_named_captures, + const char *capture_name) +{ + const uint8_t *p; + int capture_index; + char name[TMP_BUF_SIZE]; + + capture_index = 1; + *phas_named_captures = 0; + for (p = s->buf_start; p < s->buf_end; p++) { + switch (*p) { + case '(': + if (p[1] == '?') { + if (p[2] == '<' && p[3] != '=' && p[3] != '!') { + *phas_named_captures = 1; + /* potential named capture */ + if (capture_name) { + p += 3; + if (re_parse_group_name(name, sizeof(name), &p) == 0) { + if (!strcmp(name, capture_name)) + return capture_index; + } + } + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + } else { + capture_index++; + if (capture_index >= CAPTURE_COUNT_MAX) + goto done; + } + break; + case '\\': + p++; + break; + case '[': + for (p += 1 + (*p == ']'); p < s->buf_end && *p != ']'; p++) { + if (*p == '\\') + p++; + } + break; + } + } + done: + if (capture_name) + return -1; + else + return capture_index; +} + +static int re_count_captures(REParseState *s) +{ + if (s->total_capture_count < 0) { + s->total_capture_count = re_parse_captures(s, &s->has_named_captures, + NULL); + } + return s->total_capture_count; +} + +static BOOL re_has_named_captures(REParseState *s) +{ + if (s->has_named_captures < 0) + re_count_captures(s); + return s->has_named_captures; +} + +static int find_group_name(REParseState *s, const char *name) +{ + const char *p, *buf_end; + size_t len, name_len; + int capture_index; + + p = (char *)s->group_names.buf; + if (!p) return -1; + buf_end = (char *)s->group_names.buf + s->group_names.size; + name_len = strlen(name); + capture_index = 1; + while (p < buf_end) { + len = strlen(p); + if (len == name_len && memcmp(name, p, name_len) == 0) + return capture_index; + p += len + 1; + capture_index++; + } + return -1; +} + +static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir); + +static int re_parse_term(REParseState *s, BOOL is_backward_dir) +{ + const uint8_t *p; + int c, last_atom_start, quant_min, quant_max, last_capture_count; + BOOL greedy, add_zero_advance_check, is_neg, is_backward_lookahead; + CharRange cr_s, *cr = &cr_s; + + last_atom_start = -1; + last_capture_count = 0; + p = s->buf_ptr; + c = *p; + switch(c) { + case '^': + p++; + re_emit_op(s, REOP_line_start); + break; + case '$': + p++; + re_emit_op(s, REOP_line_end); + break; + case '.': + p++; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + re_emit_op(s, s->dotall ? REOP_any : REOP_dot); + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case '{': + if (s->is_unicode) { + return re_parse_error(s, "syntax error"); + } else if (!lre_is_digit(p[1])) { + /* Annex B: we accept '{' not followed by digits as a + normal atom */ + goto parse_class_atom; + } else { + const uint8_t *p1 = p + 1; + /* Annex B: error if it is like a repetition count */ + parse_digits(&p1, TRUE); + if (*p1 == ',') { + p1++; + if (lre_is_digit(*p1)) { + parse_digits(&p1, TRUE); + } + } + if (*p1 != '}') { + goto parse_class_atom; + } + } + /* fall thru */ + case '*': + case '+': + case '?': + return re_parse_error(s, "nothing to repeat"); + case '(': + if (p[1] == '?') { + if (p[2] == ':') { + p += 3; + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + } else if ((p[2] == '=' || p[2] == '!')) { + is_neg = (p[2] == '!'); + is_backward_lookahead = FALSE; + p += 3; + goto lookahead; + } else if (p[2] == '<' && + (p[3] == '=' || p[3] == '!')) { + int pos; + is_neg = (p[3] == '!'); + is_backward_lookahead = TRUE; + p += 4; + /* lookahead */ + lookahead: + /* Annex B allows lookahead to be used as an atom for + the quantifiers */ + if (!s->is_unicode && !is_backward_lookahead) { + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + } + pos = re_emit_op_u32(s, REOP_lookahead + is_neg, 0); + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_lookahead)) + return -1; + p = s->buf_ptr; + if (re_parse_expect(s, &p, ')')) + return -1; + re_emit_op(s, REOP_match); + /* jump after the 'match' after the lookahead is successful */ + if (dbuf_error(&s->byte_code)) + return -1; + put_u32(s->byte_code.buf + pos, s->byte_code.size - (pos + 4)); + } else if (p[2] == '<') { + p += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p)) { + return re_parse_error(s, "invalid group name"); + } + if (find_group_name(s, s->u.tmp_buf) > 0) { + return re_parse_error(s, "duplicate group name"); + } + /* group name with a trailing zero */ + dbuf_put(&s->group_names, (uint8_t *)s->u.tmp_buf, + strlen(s->u.tmp_buf) + 1); + s->has_named_captures = 1; + goto parse_capture; + } else { + return re_parse_error(s, "invalid group"); + } + } else { + int capture_index; + p++; + /* capture without group name */ + dbuf_putc(&s->group_names, 0); + parse_capture: + if (s->capture_count >= CAPTURE_COUNT_MAX) + return re_parse_error(s, "too many captures"); + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + capture_index = s->capture_count++; + re_emit_op_u8(s, REOP_save_start + is_backward_dir, + capture_index); + + s->buf_ptr = p; + if (re_parse_disjunction(s, is_backward_dir)) + return -1; + p = s->buf_ptr; + + re_emit_op_u8(s, REOP_save_start + 1 - is_backward_dir, + capture_index); + + if (re_parse_expect(s, &p, ')')) + return -1; + } + break; + case '\\': + switch(p[1]) { + case 'b': + case 'B': + re_emit_op(s, REOP_word_boundary + (p[1] != 'b')); + p += 2; + break; + case 'k': + { + const uint8_t *p1; + int dummy_res; + + p1 = p; + if (p1[2] != '<') { + /* annex B: we tolerate invalid group names in non + unicode mode if there is no named capture + definition */ + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "expecting group name"); + else + goto parse_class_atom; + } + p1 += 3; + if (re_parse_group_name(s->u.tmp_buf, sizeof(s->u.tmp_buf), + &p1)) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "invalid group name"); + else + goto parse_class_atom; + } + c = find_group_name(s, s->u.tmp_buf); + if (c < 0) { + /* no capture name parsed before, try to look + after (inefficient, but hopefully not common */ + c = re_parse_captures(s, &dummy_res, s->u.tmp_buf); + if (c < 0) { + if (s->is_unicode || re_has_named_captures(s)) + return re_parse_error(s, "group name not defined"); + else + goto parse_class_atom; + } + } + p = p1; + } + goto emit_back_reference; + case '0': + p += 2; + c = 0; + if (s->is_unicode) { + if (lre_is_digit(*p)) { + return re_parse_error(s, "invalid decimal escape in regular expression"); + } + } else { + /* Annex B.1.4: accept legacy octal */ + if (*p >= '0' && *p <= '7') { + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } + goto normal_char; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + { + const uint8_t *q = ++p; + + c = parse_digits(&p, FALSE); + if (c < 0 || (c >= s->capture_count && c >= re_count_captures(s))) { + if (!s->is_unicode) { + /* Annex B.1.4: accept legacy octal */ + p = q; + if (*p <= '7') { + c = 0; + if (*p <= '3') + c = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + if (*p >= '0' && *p <= '7') { + c = (c << 3) + *p++ - '0'; + } + } + } else { + c = *p++; + } + goto normal_char; + } + return re_parse_error(s, "back reference out of range in regular expression"); + } + emit_back_reference: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + re_emit_op_u8(s, REOP_back_reference + is_backward_dir, c); + } + break; + default: + goto parse_class_atom; + } + break; + case '[': + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (re_parse_char_class(s, &p)) + return -1; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + case ']': + case '}': + if (s->is_unicode) + return re_parse_error(s, "syntax error"); + goto parse_class_atom; + default: + parse_class_atom: + c = get_class_atom(s, cr, &p, FALSE); + if ((int)c < 0) + return -1; + normal_char: + last_atom_start = s->byte_code.size; + last_capture_count = s->capture_count; + if (is_backward_dir) + re_emit_op(s, REOP_prev); + if (c >= CLASS_RANGE_BASE) { + int ret; + /* Note: canonicalization is not needed */ + ret = re_emit_range(s, cr); + cr_free(cr); + if (ret) + return -1; + } else { + if (s->ignore_case) + c = lre_canonicalize(c, s->is_unicode); + if (c <= 0x7f) + re_emit_op_u8(s, REOP_char8, c); + else if (c <= 0xffff) + re_emit_op_u16(s, REOP_char16, c); + else + re_emit_op_u32(s, REOP_char32, c); + } + if (is_backward_dir) + re_emit_op(s, REOP_prev); + break; + } + + /* quantifier */ + if (last_atom_start >= 0) { + c = *p; + switch(c) { + case '*': + p++; + quant_min = 0; + quant_max = INT32_MAX; + goto quantifier; + case '+': + p++; + quant_min = 1; + quant_max = INT32_MAX; + goto quantifier; + case '?': + p++; + quant_min = 0; + quant_max = 1; + goto quantifier; + case '{': + { + const uint8_t *p1 = p; + /* As an extension (see ES6 annex B), we accept '{' not + followed by digits as a normal atom */ + if (!lre_is_digit(p[1])) { + if (s->is_unicode) + goto invalid_quant_count; + break; + } + p++; + quant_min = parse_digits(&p, TRUE); + quant_max = quant_min; + if (*p == ',') { + p++; + if (lre_is_digit(*p)) { + quant_max = parse_digits(&p, TRUE); + if (quant_max < quant_min) { + invalid_quant_count: + return re_parse_error(s, "invalid repetition count"); + } + } else { + quant_max = INT32_MAX; /* infinity */ + } + } + if (*p != '}' && !s->is_unicode) { + /* Annex B: normal atom if invalid '{' syntax */ + p = p1; + break; + } + if (re_parse_expect(s, &p, '}')) + return -1; + } + quantifier: + greedy = TRUE; + if (*p == '?') { + p++; + greedy = FALSE; + } + if (last_atom_start < 0) { + return re_parse_error(s, "nothing to repeat"); + } + if (greedy) { + int len, pos; + + if (quant_max > 0) { + /* specific optimization for simple quantifiers */ + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + len = re_is_simple_quantifier(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + if (len > 0) { + re_emit_op(s, REOP_match); + + if (dbuf_insert(&s->byte_code, last_atom_start, 17)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_simple_greedy_quant; + put_u32(&s->byte_code.buf[pos], + s->byte_code.size - last_atom_start - 17); + pos += 4; + put_u32(&s->byte_code.buf[pos], quant_min); + pos += 4; + put_u32(&s->byte_code.buf[pos], quant_max); + pos += 4; + put_u32(&s->byte_code.buf[pos], len); + pos += 4; + goto done; + } + } + + if (dbuf_error(&s->byte_code)) + goto out_of_memory; + } + /* 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. */ + add_zero_advance_check = re_need_check_advance(s->byte_code.buf + last_atom_start, + s->byte_code.size - last_atom_start); + + { + int len, pos; + len = s->byte_code.size - last_atom_start; + if (quant_min == 0) { + /* need to reset the capture in case the atom is + not executed */ + if (last_capture_count != s->capture_count) { + if (dbuf_insert(&s->byte_code, last_atom_start, 3)) + goto out_of_memory; + s->byte_code.buf[last_atom_start++] = REOP_save_reset; + s->byte_code.buf[last_atom_start++] = last_capture_count; + s->byte_code.buf[last_atom_start++] = s->capture_count - 1; + } + if (quant_max == 0) { + s->byte_code.size = last_atom_start; + } else if (quant_max == 1 || quant_max == INT32_MAX) { + BOOL has_goto = (quant_max == INT32_MAX); + if (dbuf_insert(&s->byte_code, last_atom_start, 5 + add_zero_advance_check)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_split_goto_first + + greedy; + put_u32(s->byte_code.buf + last_atom_start + 1, + len + 5 * has_goto + add_zero_advance_check * 2); + if (add_zero_advance_check) { + s->byte_code.buf[last_atom_start + 1 + 4] = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } + if (has_goto) + re_emit_goto(s, REOP_goto, last_atom_start); + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 10 + add_zero_advance_check)) + goto out_of_memory; + pos = last_atom_start; + s->byte_code.buf[pos++] = REOP_push_i32; + put_u32(s->byte_code.buf + pos, quant_max); + pos += 4; + s->byte_code.buf[pos++] = REOP_split_goto_first + greedy; + put_u32(s->byte_code.buf + pos, len + 5 + add_zero_advance_check * 2); + pos += 4; + if (add_zero_advance_check) { + s->byte_code.buf[pos++] = REOP_push_char_pos; + re_emit_op(s, REOP_check_advance); + } + re_emit_goto(s, REOP_loop, last_atom_start + 5); + re_emit_op(s, REOP_drop); + } + } else if (quant_min == 1 && quant_max == INT32_MAX && + !add_zero_advance_check) { + re_emit_goto(s, REOP_split_next_first - greedy, + last_atom_start); + } else { + if (quant_min == 1) { + /* nothing to add */ + } else { + if (dbuf_insert(&s->byte_code, last_atom_start, 5)) + goto out_of_memory; + s->byte_code.buf[last_atom_start] = REOP_push_i32; + put_u32(s->byte_code.buf + last_atom_start + 1, + quant_min); + last_atom_start += 5; + re_emit_goto(s, REOP_loop, last_atom_start); + re_emit_op(s, REOP_drop); + } + if (quant_max == INT32_MAX) { + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_goto, pos); + } else if (quant_max > quant_min) { + re_emit_op_u32(s, REOP_push_i32, quant_max - quant_min); + pos = s->byte_code.size; + re_emit_op_u32(s, REOP_split_goto_first + greedy, + len + 5 + add_zero_advance_check * 2); + if (add_zero_advance_check) + re_emit_op(s, REOP_push_char_pos); + /* copy the atom */ + dbuf_put_self(&s->byte_code, last_atom_start, len); + if (add_zero_advance_check) + re_emit_op(s, REOP_check_advance); + re_emit_goto(s, REOP_loop, pos); + re_emit_op(s, REOP_drop); + } + } + last_atom_start = -1; + } + break; + default: + break; + } + } + done: + s->buf_ptr = p; + return 0; + out_of_memory: + return re_parse_out_of_memory(s); +} + +static int re_parse_alternative(REParseState *s, BOOL is_backward_dir) +{ + const uint8_t *p; + int ret; + size_t start, term_start, end, term_size; + + start = s->byte_code.size; + for(;;) { + p = s->buf_ptr; + if (p >= s->buf_end) + break; + if (*p == '|' || *p == ')') + break; + term_start = s->byte_code.size; + ret = re_parse_term(s, is_backward_dir); + if (ret) + return ret; + if (is_backward_dir) { + /* reverse the order of the terms (XXX: inefficient, but + speed is not really critical here) */ + end = s->byte_code.size; + term_size = end - term_start; + if (dbuf_realloc(&s->byte_code, end + term_size)) + return -1; + memmove(s->byte_code.buf + start + term_size, + s->byte_code.buf + start, + end - start); + memcpy(s->byte_code.buf + start, s->byte_code.buf + end, + term_size); + } + } + return 0; +} + +static int re_parse_disjunction(REParseState *s, BOOL is_backward_dir) +{ + int start, len, pos; + + if (lre_check_stack_overflow(s->opaque, 0)) + return re_parse_error(s, "stack overflow"); + + start = s->byte_code.size; + if (re_parse_alternative(s, is_backward_dir)) + return -1; + while (*s->buf_ptr == '|') { + s->buf_ptr++; + + len = s->byte_code.size - start; + + /* insert a split before the first alternative */ + if (dbuf_insert(&s->byte_code, start, 5)) { + return re_parse_out_of_memory(s); + } + s->byte_code.buf[start] = REOP_split_next_first; + put_u32(s->byte_code.buf + start + 1, len + 5); + + pos = re_emit_op_u32(s, REOP_goto, 0); + + if (re_parse_alternative(s, is_backward_dir)) + return -1; + + /* patch the goto */ + len = s->byte_code.size - (pos + 4); + put_u32(s->byte_code.buf + pos, len); + } + return 0; +} + +/* the control flow is recursive so the analysis can be linear */ +static int lre_compute_stack_size(const 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; + bc_buf += RE_HEADER_LEN; + bc_buf_len -= RE_HEADER_LEN; + 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_push_i32: + case REOP_push_char_pos: + stack_size++; + if (stack_size > stack_size_max) { + if (stack_size > STACK_SIZE_MAX) + return -1; + stack_size_max = stack_size; + } + break; + case REOP_drop: + case REOP_check_advance: + assert(stack_size > 0); + stack_size--; + break; + case REOP_range: + val = get_u16(bc_buf + pos + 1); + len += val * 4; + break; + case REOP_range32: + val = get_u16(bc_buf + pos + 1); + len += val * 8; + break; + } + pos += len; + } + return stack_size_max; +} + +/* 'buf' must be a zero terminated UTF-8 string of length buf_len. + Return NULL if error and allocate an error message in *perror_msg, + otherwise the compiled bytecode and its length in plen. +*/ +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque) +{ + REParseState s_s, *s = &s_s; + int stack_size; + BOOL is_sticky; + + memset(s, 0, sizeof(*s)); + s->opaque = opaque; + s->buf_ptr = (const uint8_t *)buf; + s->buf_end = s->buf_ptr + buf_len; + s->buf_start = s->buf_ptr; + s->re_flags = re_flags; + s->is_unicode = ((re_flags & LRE_FLAG_UNICODE) != 0); + is_sticky = ((re_flags & LRE_FLAG_STICKY) != 0); + s->ignore_case = ((re_flags & LRE_FLAG_IGNORECASE) != 0); + s->dotall = ((re_flags & LRE_FLAG_DOTALL) != 0); + s->unicode_sets = ((re_flags & LRE_FLAG_UNICODE_SETS) != 0); + s->capture_count = 1; + s->total_capture_count = -1; + s->has_named_captures = -1; + + dbuf_init2(&s->byte_code, opaque, lre_realloc); + dbuf_init2(&s->group_names, opaque, lre_realloc); + + dbuf_put_u16(&s->byte_code, re_flags); /* first element is the flags */ + dbuf_putc(&s->byte_code, 0); /* second element is the number of captures */ + dbuf_putc(&s->byte_code, 0); /* stack size */ + dbuf_put_u32(&s->byte_code, 0); /* bytecode length */ + + if (!is_sticky) { + /* iterate thru all positions (about the same as .*?( ... ) ) + . We do it without an explicit loop so that lock step + thread execution will be possible in an optimized + implementation */ + 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); + + if (re_parse_disjunction(s, FALSE)) { + error: + dbuf_free(&s->byte_code); + dbuf_free(&s->group_names); + js__pstrcpy(error_msg, error_msg_size, s->u.error_msg); + *plen = 0; + return NULL; + } + + re_emit_op_u8(s, REOP_save_end, 0); + + re_emit_op(s, REOP_match); + + if (*s->buf_ptr != '\0') { + re_parse_error(s, "extraneous characters at the end"); + goto error; + } + + if (dbuf_error(&s->byte_code)) { + re_parse_out_of_memory(s); + goto error; + } + + stack_size = lre_compute_stack_size(s->byte_code.buf, s->byte_code.size); + if (stack_size < 0) { + re_parse_error(s, "too many imbricated quantifiers"); + goto error; + } + + s->byte_code.buf[RE_HEADER_CAPTURE_COUNT] = s->capture_count; + s->byte_code.buf[RE_HEADER_STACK_SIZE] = stack_size; + put_u32(s->byte_code.buf + RE_HEADER_BYTECODE_LEN, + s->byte_code.size - RE_HEADER_LEN); + + /* add the named groups if needed */ + if (s->group_names.size > (s->capture_count - 1)) { + dbuf_put(&s->byte_code, s->group_names.buf, s->group_names.size); + put_u16(s->byte_code.buf + RE_HEADER_FLAGS, + LRE_FLAG_NAMED_GROUPS | lre_get_flags(s->byte_code.buf)); + } + dbuf_free(&s->group_names); + +#ifdef DUMP_REOP + lre_dump_bytecode(s->byte_code.buf, s->byte_code.size); +#endif + + error_msg[0] = '\0'; + *plen = s->byte_code.size; + return s->byte_code.buf; +} + +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 == '_')); +} + +#define GET_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = *cptr++; \ + } else if (cbuf_type < 3) { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p < _end) \ + if (is_lo_surrogate(*_p)) \ + c = from_surrogate(c, *_p++); \ + cptr = (const void *)_p; \ + } else { \ + c = utf8_decode_len(cptr, cbuf_end - cptr, &cptr); \ + } \ + } while (0) + +#define PEEK_CHAR(c, cptr, cbuf_end, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[0]; \ + } else if (cbuf_type < 3) { \ + const uint16_t *_p = (const uint16_t *)cptr; \ + const uint16_t *_end = (const uint16_t *)cbuf_end; \ + c = *_p++; \ + if (is_hi_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p < _end) \ + if (is_lo_surrogate(*_p)) \ + c = from_surrogate(c, *_p); \ + } else { \ + const uint8_t *__cpt2; \ + c = utf8_decode_len(cptr, cbuf_end - cptr, &__cpt2); \ + } \ + } while (0) + +#define PEEK_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + c = cptr[-1]; \ + } else if (cbuf_type < 3) { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p > _start) \ + if (is_hi_surrogate(_p[-1])) \ + c = from_surrogate(*--_p, c); \ + } else { \ + const uint8_t *__cpt2 = cptr; \ + int __i = 1; \ + while (__cpt2 > cbuf_start && ((*__cpt2-- >> 6) & 2)) \ + __i++; \ + c = utf8_decode_len(__cpt2, __i, &__cpt2); \ + } \ + } while (0) + +#define GET_PREV_CHAR(c, cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + c = cptr[0]; \ + } else if (cbuf_type < 3) { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + c = *_p; \ + if (is_lo_surrogate(c)) \ + if (cbuf_type == 2) \ + if (_p > _start) \ + if (is_hi_surrogate(_p[-1])) \ + c = from_surrogate(*--_p, c); \ + cptr = (const void *)_p; \ + } else { \ + const uint8_t *__cpt2; \ + int __i = 1; \ + while (cptr > cbuf_start && ((*cptr-- >> 6) & 2)) \ + __i++; \ + c = utf8_decode_len(cptr, __i, &__cpt2); \ + } \ + } while (0) + +#define PREV_CHAR(cptr, cbuf_start, cbuf_type) \ + do { \ + if (cbuf_type == 0) { \ + cptr--; \ + } else if (cbuf_type < 3) { \ + const uint16_t *_p = (const uint16_t *)cptr - 1; \ + const uint16_t *_start = (const uint16_t *)cbuf_start; \ + if (is_lo_surrogate(*_p)) \ + if (cbuf_type == 2) \ + if (_p > _start) \ + if (is_hi_surrogate(_p[-1])) \ + _p--; \ + cptr = (const void *)_p; \ + } else { \ + while (cptr > cbuf_start && ((*cptr-- >> 6) & 2)); \ + } \ + } while (0) + +typedef uintptr_t StackInt; + +typedef enum { + RE_EXEC_STATE_SPLIT, + RE_EXEC_STATE_LOOKAHEAD, + RE_EXEC_STATE_NEGATIVE_LOOKAHEAD, + RE_EXEC_STATE_GREEDY_QUANT, +} REExecStateEnum; + +typedef struct REExecState { + REExecStateEnum type : 8; + uint8_t stack_len; + size_t count; /* only used for RE_EXEC_STATE_GREEDY_QUANT */ + const uint8_t *cptr; + const uint8_t *pc; + void *buf[]; +} REExecState; + +typedef struct { + const uint8_t *cbuf; + const uint8_t *cbuf_end; + /* 0 = 8 bit chars, 1 = 16 bit chars, 2 = 16 bit chars, UTF-16, + 3 = 8 bit chars, UTF-8 */ + int cbuf_type; + int capture_count; + int stack_size_max; + BOOL multi_line; + BOOL ignore_case; + BOOL is_unicode; + void *opaque; /* used for stack overflow check */ + + size_t state_size; + uint8_t *state_stack; + size_t state_stack_size; + size_t state_stack_len; +} REExecContext; + +static int push_state(REExecContext *s, + uint8_t **capture, + StackInt *stack, size_t stack_len, + const uint8_t *pc, const uint8_t *cptr, + REExecStateEnum type, size_t count) +{ + REExecState *rs; + uint8_t *new_stack; + size_t new_size, i, n; + StackInt *stack_buf; + + if (unlikely((s->state_stack_len + 1) > s->state_stack_size)) { + /* reallocate the stack */ + new_size = s->state_stack_size * 3 / 2; + if (new_size < 8) + new_size = 8; + new_stack = lre_realloc(s->opaque, s->state_stack, new_size * s->state_size); + if (!new_stack) + return -1; + s->state_stack_size = new_size; + s->state_stack = new_stack; + } + rs = (REExecState *)(s->state_stack + s->state_stack_len * s->state_size); + s->state_stack_len++; + rs->type = type; + rs->count = count; + rs->stack_len = stack_len; + rs->cptr = cptr; + rs->pc = pc; + n = 2 * s->capture_count; + for(i = 0; i < n; i++) + rs->buf[i] = capture[i]; + stack_buf = (StackInt *)(rs->buf + n); + for(i = 0; i < stack_len; i++) + stack_buf[i] = stack[i]; + return 0; +} + +/* return 1 if match, 0 if not match or -1 if error. */ +static intptr_t lre_exec_backtrack(REExecContext *s, uint8_t **capture, + StackInt *stack, int stack_len, + const uint8_t *pc, const uint8_t *cptr, + BOOL no_recurse) +{ + int opcode, ret; + int cbuf_type; + uint32_t val, c; + const uint8_t *cbuf_end; + + cbuf_type = s->cbuf_type; + cbuf_end = s->cbuf_end; + + for(;;) { + // printf("top=%p: pc=%d\n", th_list.top, (int)(pc - (bc_buf + RE_HEADER_LEN))); + opcode = *pc++; + switch(opcode) { + case REOP_match: + { + REExecState *rs; + if (no_recurse) + return (intptr_t)cptr; + ret = 1; + goto recurse; + no_match: + if (no_recurse) + return 0; + ret = 0; + recurse: + for(;;) { + if (s->state_stack_len == 0) + return ret; + rs = (REExecState *)(s->state_stack + + (s->state_stack_len - 1) * s->state_size); + if (rs->type == RE_EXEC_STATE_SPLIT) { + if (!ret) { + pop_state: + memcpy(capture, rs->buf, + sizeof(capture[0]) * 2 * s->capture_count); + pop_state1: + pc = rs->pc; + cptr = rs->cptr; + stack_len = rs->stack_len; + memcpy(stack, rs->buf + 2 * s->capture_count, + stack_len * sizeof(stack[0])); + s->state_stack_len--; + break; + } + } else if (rs->type == RE_EXEC_STATE_GREEDY_QUANT) { + if (!ret) { + uint32_t char_count, i; + memcpy(capture, rs->buf, + sizeof(capture[0]) * 2 * s->capture_count); + stack_len = rs->stack_len; + memcpy(stack, rs->buf + 2 * s->capture_count, + stack_len * sizeof(stack[0])); + pc = rs->pc; + cptr = rs->cptr; + /* go backward */ + char_count = get_u32(pc + 12); + for(i = 0; i < char_count; i++) { + PREV_CHAR(cptr, s->cbuf, cbuf_type); + } + pc = (pc + 16) + (int)get_u32(pc); + rs->cptr = cptr; + rs->count--; + if (rs->count == 0) { + s->state_stack_len--; + } + break; + } + } else { + ret = ((rs->type == RE_EXEC_STATE_LOOKAHEAD && ret) || + (rs->type == RE_EXEC_STATE_NEGATIVE_LOOKAHEAD && !ret)); + if (ret) { + /* keep the capture in case of positive lookahead */ + if (rs->type == RE_EXEC_STATE_LOOKAHEAD) + goto pop_state1; + else + goto pop_state; + } + } + s->state_stack_len--; + } + } + break; + case REOP_char32: + val = get_u32(pc); + pc += 4; + goto test_char; + case REOP_char16: + val = get_u16(pc); + pc += 2; + goto test_char; + case REOP_char8: + val = get_u8(pc); + pc += 1; + test_char: + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + if (val != c) + goto no_match; + break; + case REOP_split_goto_first: + case REOP_split_next_first: + { + const uint8_t *pc1; + + val = get_u32(pc); + pc += 4; + if (opcode == REOP_split_next_first) { + pc1 = pc + (int)val; + } else { + pc1 = pc; + pc = pc + (int)val; + } + ret = push_state(s, capture, stack, stack_len, + pc1, cptr, RE_EXEC_STATE_SPLIT, 0); + if (ret < 0) + return -1; + break; + } + case REOP_lookahead: + case REOP_negative_lookahead: + val = get_u32(pc); + pc += 4; + ret = push_state(s, capture, stack, stack_len, + pc + (int)val, cptr, + RE_EXEC_STATE_LOOKAHEAD + opcode - REOP_lookahead, + 0); + if (ret < 0) + return -1; + break; + + case REOP_goto: + val = get_u32(pc); + pc += 4 + (int)val; + break; + case REOP_line_start: + if (cptr == s->cbuf) + break; + if (!s->multi_line) + goto no_match; + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + if (!is_line_terminator(c)) + goto no_match; + break; + case REOP_line_end: + if (cptr == cbuf_end) + break; + if (!s->multi_line) + goto no_match; + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + 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, cbuf_type); + 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, cbuf_type); + break; + case REOP_save_start: + case REOP_save_end: + val = *pc++; + assert(val < s->capture_count); + capture[2 * val + opcode - REOP_save_start] = (uint8_t *)cptr; + break; + case REOP_save_reset: + { + uint32_t val2; + val = pc[0]; + val2 = pc[1]; + pc += 2; + assert(val2 < s->capture_count); + while (val <= val2) { + capture[2 * val] = NULL; + capture[2 * val + 1] = NULL; + val++; + } + } + break; + case REOP_push_i32: + val = get_u32(pc); + pc += 4; + stack[stack_len++] = val; + break; + case REOP_drop: + stack_len--; + break; + case REOP_loop: + val = get_u32(pc); + pc += 4; + if (--stack[stack_len - 1] != 0) { + pc += (int)val; + } + break; + case REOP_push_char_pos: + stack[stack_len++] = (uintptr_t)cptr; + break; + case REOP_check_advance: + if (stack[--stack_len] == (uintptr_t)cptr) + goto no_match; + break; + case REOP_word_boundary: + case REOP_not_word_boundary: + { + BOOL v1, v2; + /* char before */ + if (cptr == s->cbuf) { + v1 = FALSE; + } else { + PEEK_PREV_CHAR(c, cptr, s->cbuf, cbuf_type); + v1 = is_word_char(c); + } + /* current char */ + if (cptr >= cbuf_end) { + v2 = FALSE; + } else { + PEEK_CHAR(c, cptr, cbuf_end, cbuf_type); + v2 = is_word_char(c); + } + if (v1 ^ v2 ^ (REOP_not_word_boundary - opcode)) + goto no_match; + } + break; + case REOP_back_reference: + case REOP_backward_back_reference: + { + const uint8_t *cptr1, *cptr1_end, *cptr1_start; + uint32_t c1, c2; + + val = *pc++; + if (val >= s->capture_count) + goto no_match; + cptr1_start = capture[2 * val]; + cptr1_end = capture[2 * val + 1]; + if (!cptr1_start || !cptr1_end) + break; + if (opcode == REOP_back_reference) { + cptr1 = cptr1_start; + while (cptr1 < cptr1_end) { + if (cptr >= cbuf_end) + goto no_match; + GET_CHAR(c1, cptr1, cptr1_end, cbuf_type); + GET_CHAR(c2, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } else { + cptr1 = cptr1_end; + while (cptr1 > cptr1_start) { + if (cptr == s->cbuf) + goto no_match; + GET_PREV_CHAR(c1, cptr1, cptr1_start, cbuf_type); + GET_PREV_CHAR(c2, cptr, s->cbuf, cbuf_type); + if (s->ignore_case) { + c1 = lre_canonicalize(c1, s->is_unicode); + c2 = lre_canonicalize(c2, s->is_unicode); + } + if (c1 != c2) + goto no_match; + } + } + } + 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) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + idx_min = 0; + low = get_u16(pc + 0 * 4); + if (c < low) + goto no_match; + idx_max = n - 1; + high = get_u16(pc + idx_max * 4 + 2); + /* 0xffff in for last value means +infinity */ + if (unlikely(c >= 0xffff) && high == 0xffff) + goto range_match; + if (c > high) + goto no_match; + while (idx_min <= idx_max) { + idx = (idx_min + idx_max) / 2; + low = get_u16(pc + idx * 4); + high = get_u16(pc + idx * 4 + 2); + 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 += 4 * n; + } + break; + case REOP_range32: + { + 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) + goto no_match; + GET_CHAR(c, cptr, cbuf_end, cbuf_type); + if (s->ignore_case) { + c = lre_canonicalize(c, s->is_unicode); + } + 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 range32_match; + } + goto no_match; + range32_match: + pc += 8 * n; + } + break; + case REOP_prev: + /* go to the previous char */ + if (cptr == s->cbuf) + goto no_match; + PREV_CHAR(cptr, s->cbuf, cbuf_type); + break; + case REOP_simple_greedy_quant: + { + uint32_t next_pos, quant_min, quant_max; + size_t q; + intptr_t res; + const uint8_t *pc1; + + next_pos = get_u32(pc); + quant_min = get_u32(pc + 4); + quant_max = get_u32(pc + 8); + pc += 16; + pc1 = pc; + pc += (int)next_pos; + + q = 0; + for(;;) { + res = lre_exec_backtrack(s, capture, stack, stack_len, + pc1, cptr, TRUE); + if (res == -1) + return res; + if (!res) + break; + cptr = (uint8_t *)res; + q++; + if (q >= quant_max && quant_max != INT32_MAX) + break; + } + if (q < quant_min) + goto no_match; + if (q > quant_min) { + /* will examine all matches down to quant_min */ + ret = push_state(s, capture, stack, stack_len, + pc1 - 16, cptr, + RE_EXEC_STATE_GREEDY_QUANT, + q - quant_min); + if (ret < 0) + return -1; + } + } + break; + default: + abort(); + } + } +} + +/* Return 1 if match, 0 if not match or -1 if error. cindex is the + starting position of the match and must be such as 0 <= cindex <= + clen. */ +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque) +{ + REExecContext s_s, *s = &s_s; + int re_flags, i, alloca_size, ret, cbuf_width = cbuf_type; + StackInt *stack_buf; + + if (cbuf_width == 3) /* UTF-8 */ + cbuf_width = 0; + re_flags = lre_get_flags(bc_buf); + s->multi_line = (re_flags & LRE_FLAG_MULTILINE) != 0; + s->ignore_case = (re_flags & LRE_FLAG_IGNORECASE) != 0; + s->is_unicode = (re_flags & LRE_FLAG_UNICODE) != 0; + s->capture_count = bc_buf[RE_HEADER_CAPTURE_COUNT]; + s->stack_size_max = bc_buf[RE_HEADER_STACK_SIZE]; + s->cbuf = cbuf; + s->cbuf_end = cbuf + (clen << cbuf_width); + s->cbuf_type = cbuf_type; + if (s->cbuf_type == 1 && s->is_unicode) + s->cbuf_type = 2; + s->opaque = opaque; + + s->state_size = sizeof(REExecState) + + s->capture_count * sizeof(capture[0]) * 2 + + s->stack_size_max * sizeof(stack_buf[0]); + s->state_stack = NULL; + s->state_stack_len = 0; + s->state_stack_size = 0; + + for(i = 0; i < s->capture_count * 2; i++) + capture[i] = NULL; + alloca_size = s->stack_size_max * sizeof(stack_buf[0]); + stack_buf = alloca(alloca_size); + ret = lre_exec_backtrack(s, capture, stack_buf, 0, bc_buf + RE_HEADER_LEN, + cbuf + (cindex << cbuf_width), FALSE); + lre_realloc(s->opaque, s->state_stack, 0); + return ret; +} + +int lre_get_capture_count(const uint8_t *bc_buf) +{ + return bc_buf[RE_HEADER_CAPTURE_COUNT]; +} + +int lre_get_flags(const uint8_t *bc_buf) +{ + return get_u16(bc_buf + RE_HEADER_FLAGS); +} + +/* Return NULL if no group names. Otherwise, return a pointer to + 'capture_count - 1' zero terminated UTF-8 strings. */ +const char *lre_get_groupnames(const uint8_t *bc_buf) +{ + uint32_t re_bytecode_len; + if ((lre_get_flags(bc_buf) & LRE_FLAG_NAMED_GROUPS) == 0) + return NULL; + re_bytecode_len = get_u32(bc_buf + RE_HEADER_BYTECODE_LEN); + return (const char *)(bc_buf + RE_HEADER_LEN + re_bytecode_len); +} + +void lre_byte_swap(uint8_t *buf, size_t len, BOOL is_byte_swapped) +{ + uint8_t *p, *pe; + uint32_t n, r, nw; + + p = buf; + if (len < RE_HEADER_LEN) + abort(); + + // format is: + // <header> + // <bytecode> + // <capture group name 1> + // <capture group name 2> + // etc. + inplace_bswap16(&p[RE_HEADER_FLAGS]); + + n = get_u32(&p[RE_HEADER_BYTECODE_LEN]); + inplace_bswap32(&p[RE_HEADER_BYTECODE_LEN]); + if (is_byte_swapped) + n = bswap32(n); + if (n > len - RE_HEADER_LEN) + abort(); + + p = &buf[RE_HEADER_LEN]; + pe = &p[n]; + + while (p < pe) { + n = reopcode_info[*p].size; + switch (n) { + case 1: + case 2: + break; + case 3: + switch (*p) { + case REOP_save_reset: // has two 8 bit arguments + break; + case REOP_range32: // variable length + nw = get_u16(&p[1]); // number of pairs of uint32_t + if (is_byte_swapped) + n = bswap16(n); + for (r = 3 + 8 * nw; n < r; n += 4) + inplace_bswap32(&p[n]); + goto doswap16; + case REOP_range: // variable length + nw = get_u16(&p[1]); // number of pairs of uint16_t + if (is_byte_swapped) + n = bswap16(n); + for (r = 3 + 4 * nw; n < r; n += 2) + inplace_bswap16(&p[n]); + goto doswap16; + default: + doswap16: + inplace_bswap16(&p[1]); + break; + } + break; + case 5: + inplace_bswap32(&p[1]); + break; + case 17: + assert(*p == REOP_simple_greedy_quant); + inplace_bswap32(&p[1]); + inplace_bswap32(&p[5]); + inplace_bswap32(&p[9]); + inplace_bswap32(&p[13]); + break; + default: + abort(); + } + p = &p[n]; + } +} + +#ifdef TEST + +BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + return FALSE; +} + +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +int main(int argc, char **argv) +{ + int len, flags, ret, i; + uint8_t *bc; + char error_msg[64]; + uint8_t *capture[CAPTURE_COUNT_MAX * 2]; + const char *input; + int input_len, capture_count; + + if (argc < 4) { + printf("usage: %s regexp flags input\n", argv[0]); + exit(1); + } + flags = atoi(argv[2]); + bc = lre_compile(&len, error_msg, sizeof(error_msg), argv[1], + strlen(argv[1]), flags, NULL); + if (!bc) { + fprintf(stderr, "error: %s\n", error_msg); + exit(1); + } + + input = argv[3]; + input_len = strlen(input); + + ret = lre_exec(capture, bc, (uint8_t *)input, 0, input_len, 0, NULL); + printf("ret=%d\n", ret); + if (ret == 1) { + capture_count = lre_get_capture_count(bc); + for(i = 0; i < 2 * capture_count; i++) { + uint8_t *ptr; + ptr = capture[i]; + printf("%d: ", i); + if (!ptr) + printf("<nil>"); + else + printf("%u", (int)(ptr - (uint8_t *)input)); + printf("\n"); + } + } + return 0; +} +#endif diff --git a/lib/monoucha0/monoucha/qjs/libregexp.h b/lib/monoucha0/monoucha/qjs/libregexp.h new file mode 100644 index 00000000..c2e664a2 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libregexp.h @@ -0,0 +1,95 @@ +/* + * Regular Expression Engine + * + * Copyright (c) 2017-2018 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 LIBREGEXP_H +#define LIBREGEXP_H + +#include <stddef.h> + +#include "libunicode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LRE_BOOL int /* for documentation purposes */ + +#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 LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */ +#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */ +#define LRE_FLAG_UNICODE_SETS (1 << 8) + +uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size, + const char *buf, size_t buf_len, int re_flags, + void *opaque); +int lre_get_capture_count(const uint8_t *bc_buf); +int lre_get_flags(const uint8_t *bc_buf); +const char *lre_get_groupnames(const uint8_t *bc_buf); +int lre_exec(uint8_t **capture, + const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen, + int cbuf_type, void *opaque); + +int lre_parse_escape(const uint8_t **pp, int allow_utf16); +LRE_BOOL lre_is_space(int c); + +void lre_byte_swap(uint8_t *buf, size_t len, LRE_BOOL is_byte_swapped); + +/* must be provided by the user */ +LRE_BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size); +void *lre_realloc(void *opaque, void *ptr, size_t size); + +/* JS identifier test */ +extern uint32_t const lre_id_start_table_ascii[4]; +extern uint32_t const lre_id_continue_table_ascii[4]; + +static inline int lre_js_is_ident_first(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_start_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + return lre_is_id_start(c); + } +} + +static inline int lre_js_is_ident_next(int c) +{ + if ((uint32_t)c < 128) { + return (lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1; + } else { + /* ZWNJ and ZWJ are accepted in identifiers */ + return lre_is_id_continue(c) || c == 0x200C || c == 0x200D; + } +} + +#undef LRE_BOOL + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBREGEXP_H */ diff --git a/lib/monoucha0/monoucha/qjs/libunicode-table.h b/lib/monoucha0/monoucha/qjs/libunicode-table.h new file mode 100644 index 00000000..b48a3a74 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libunicode-table.h @@ -0,0 +1,4658 @@ +/* Compressed unicode tables */ +/* Automatically generated file - do not edit */ + +#include <stdint.h> + +static const uint32_t case_conv_table1[378] = { + 0x00209a30, 0x00309a00, 0x005a8173, 0x00601730, + 0x006c0730, 0x006f81b3, 0x00701700, 0x007c0700, + 0x007f8100, 0x00803040, 0x009801c3, 0x00988190, + 0x00990640, 0x009c9040, 0x00a481b4, 0x00a52e40, + 0x00bc0130, 0x00bc8640, 0x00bf8170, 0x00c00100, + 0x00c08130, 0x00c10440, 0x00c30130, 0x00c38240, + 0x00c48230, 0x00c58240, 0x00c70130, 0x00c78130, + 0x00c80130, 0x00c88240, 0x00c98130, 0x00ca0130, + 0x00ca8100, 0x00cb0130, 0x00cb8130, 0x00cc0240, + 0x00cd0100, 0x00cd8101, 0x00ce0130, 0x00ce8130, + 0x00cf0100, 0x00cf8130, 0x00d00640, 0x00d30130, + 0x00d38240, 0x00d48130, 0x00d60240, 0x00d70130, + 0x00d78240, 0x00d88230, 0x00d98440, 0x00db8130, + 0x00dc0240, 0x00de0240, 0x00df8100, 0x00e20350, + 0x00e38350, 0x00e50350, 0x00e69040, 0x00ee8100, + 0x00ef1240, 0x00f801b4, 0x00f88350, 0x00fa0240, + 0x00fb0130, 0x00fb8130, 0x00fc2840, 0x01100130, + 0x01111240, 0x011d0131, 0x011d8240, 0x011e8130, + 0x011f0131, 0x011f8201, 0x01208240, 0x01218130, + 0x01220130, 0x01228130, 0x01230a40, 0x01280101, + 0x01288101, 0x01290101, 0x01298100, 0x012a0100, + 0x012b0200, 0x012c8100, 0x012d8100, 0x012e0101, + 0x01300100, 0x01308101, 0x01318100, 0x01320101, + 0x01328101, 0x01330101, 0x01340100, 0x01348100, + 0x01350101, 0x01358101, 0x01360101, 0x01378100, + 0x01388101, 0x01390100, 0x013a8100, 0x013e8101, + 0x01400100, 0x01410101, 0x01418100, 0x01438101, + 0x01440100, 0x01448100, 0x01450200, 0x01460100, + 0x01490100, 0x014e8101, 0x014f0101, 0x01a28173, + 0x01b80440, 0x01bb0240, 0x01bd8300, 0x01bf8130, + 0x01c30130, 0x01c40330, 0x01c60130, 0x01c70230, + 0x01c801d0, 0x01c89130, 0x01d18930, 0x01d60100, + 0x01d68300, 0x01d801d3, 0x01d89100, 0x01e10173, + 0x01e18900, 0x01e60100, 0x01e68200, 0x01e78130, + 0x01e80173, 0x01e88173, 0x01ea8173, 0x01eb0173, + 0x01eb8100, 0x01ec1840, 0x01f80173, 0x01f88173, + 0x01f90100, 0x01f98100, 0x01fa01a0, 0x01fa8173, + 0x01fb8240, 0x01fc8130, 0x01fd0240, 0x01fe8330, + 0x02001030, 0x02082030, 0x02182000, 0x02281000, + 0x02302240, 0x02453640, 0x02600130, 0x02608e40, + 0x02678100, 0x02686040, 0x0298a630, 0x02b0a600, + 0x02c381b5, 0x08502631, 0x08638131, 0x08668131, + 0x08682b00, 0x087e8300, 0x09d05011, 0x09f80610, + 0x09fc0620, 0x0e400174, 0x0e408174, 0x0e410174, + 0x0e418174, 0x0e420174, 0x0e428174, 0x0e430174, + 0x0e438180, 0x0e440180, 0x0e448240, 0x0e482b30, + 0x0e5e8330, 0x0ebc8101, 0x0ebe8101, 0x0ec70101, + 0x0f007e40, 0x0f3f1840, 0x0f4b01b5, 0x0f4b81b6, + 0x0f4c01b6, 0x0f4c81b6, 0x0f4d01b7, 0x0f4d8180, + 0x0f4f0130, 0x0f506040, 0x0f800800, 0x0f840830, + 0x0f880600, 0x0f8c0630, 0x0f900800, 0x0f940830, + 0x0f980800, 0x0f9c0830, 0x0fa00600, 0x0fa40630, + 0x0fa801b0, 0x0fa88100, 0x0fa901d3, 0x0fa98100, + 0x0faa01d3, 0x0faa8100, 0x0fab01d3, 0x0fab8100, + 0x0fac8130, 0x0fad8130, 0x0fae8130, 0x0faf8130, + 0x0fb00800, 0x0fb40830, 0x0fb80200, 0x0fb90400, + 0x0fbb0201, 0x0fbc0201, 0x0fbd0201, 0x0fbe0201, + 0x0fc008b7, 0x0fc40867, 0x0fc808b8, 0x0fcc0868, + 0x0fd008b8, 0x0fd40868, 0x0fd80200, 0x0fd901b9, + 0x0fd981b1, 0x0fda01b9, 0x0fdb01b1, 0x0fdb81d7, + 0x0fdc0230, 0x0fdd0230, 0x0fde0161, 0x0fdf0173, + 0x0fe101b9, 0x0fe181b2, 0x0fe201ba, 0x0fe301b2, + 0x0fe381d8, 0x0fe40430, 0x0fe60162, 0x0fe80201, + 0x0fe901d0, 0x0fe981d0, 0x0feb01b0, 0x0feb81d0, + 0x0fec0230, 0x0fed0230, 0x0ff00201, 0x0ff101d3, + 0x0ff181d3, 0x0ff201ba, 0x0ff28101, 0x0ff301b0, + 0x0ff381d3, 0x0ff40231, 0x0ff50230, 0x0ff60131, + 0x0ff901ba, 0x0ff981b2, 0x0ffa01bb, 0x0ffb01b2, + 0x0ffb81d9, 0x0ffc0230, 0x0ffd0230, 0x0ffe0162, + 0x109301a0, 0x109501a0, 0x109581a0, 0x10990131, + 0x10a70101, 0x10b01031, 0x10b81001, 0x10c18240, + 0x125b1a31, 0x12681a01, 0x16003031, 0x16183001, + 0x16300240, 0x16310130, 0x16318130, 0x16320130, + 0x16328100, 0x16330100, 0x16338640, 0x16368130, + 0x16370130, 0x16378130, 0x16380130, 0x16390240, + 0x163a8240, 0x163f0230, 0x16406440, 0x16758440, + 0x16790240, 0x16802600, 0x16938100, 0x16968100, + 0x53202e40, 0x53401c40, 0x53910e40, 0x53993e40, + 0x53bc8440, 0x53be8130, 0x53bf0a40, 0x53c58240, + 0x53c68130, 0x53c80440, 0x53ca0101, 0x53cb1440, + 0x53d50130, 0x53d58130, 0x53d60130, 0x53d68130, + 0x53d70130, 0x53d80130, 0x53d88130, 0x53d90130, + 0x53d98131, 0x53da1040, 0x53e20131, 0x53e28130, + 0x53e30130, 0x53e38440, 0x53e58130, 0x53e60240, + 0x53e80240, 0x53eb0640, 0x53ee0130, 0x53fa8240, + 0x55a98101, 0x55b85020, 0x7d8001b2, 0x7d8081b2, + 0x7d8101b2, 0x7d8181da, 0x7d8201da, 0x7d8281b3, + 0x7d8301b3, 0x7d8981bb, 0x7d8a01bb, 0x7d8a81bb, + 0x7d8b01bc, 0x7d8b81bb, 0x7f909a31, 0x7fa09a01, + 0x82002831, 0x82142801, 0x82582431, 0x826c2401, + 0x82b80b31, 0x82be0f31, 0x82c60731, 0x82ca0231, + 0x82cb8b01, 0x82d18f01, 0x82d98701, 0x82dd8201, + 0x86403331, 0x86603301, 0x86a81631, 0x86b81601, + 0x8c502031, 0x8c602001, 0xb7202031, 0xb7302001, + 0xf4802231, 0xf4912201, +}; + +static const uint8_t case_conv_table2[378] = { + 0x01, 0x00, 0x9c, 0x06, 0x07, 0x4d, 0x03, 0x04, + 0x10, 0x00, 0x8f, 0x0b, 0x00, 0x00, 0x11, 0x00, + 0x08, 0x00, 0x53, 0x4b, 0x52, 0x00, 0x53, 0x00, + 0x54, 0x00, 0x3b, 0x55, 0x56, 0x00, 0x58, 0x5a, + 0x40, 0x5f, 0x5e, 0x00, 0x47, 0x52, 0x63, 0x65, + 0x43, 0x66, 0x00, 0x68, 0x00, 0x6a, 0x00, 0x6c, + 0x00, 0x6e, 0x00, 0x70, 0x00, 0x00, 0x41, 0x00, + 0x00, 0x00, 0x00, 0x1a, 0x00, 0x93, 0x00, 0x00, + 0x20, 0x36, 0x00, 0x28, 0x00, 0x24, 0x00, 0x24, + 0x25, 0x2d, 0x00, 0x13, 0x6d, 0x6f, 0x00, 0x29, + 0x27, 0x2a, 0x14, 0x16, 0x18, 0x1b, 0x1c, 0x41, + 0x1e, 0x42, 0x1f, 0x4e, 0x3c, 0x40, 0x22, 0x21, + 0x44, 0x21, 0x43, 0x26, 0x28, 0x27, 0x29, 0x23, + 0x2b, 0x4b, 0x2d, 0x46, 0x2f, 0x4c, 0x31, 0x4d, + 0x33, 0x47, 0x45, 0x99, 0x00, 0x00, 0x97, 0x91, + 0x7f, 0x80, 0x85, 0x86, 0x12, 0x82, 0x84, 0x78, + 0x79, 0x12, 0x7d, 0xa3, 0x7e, 0x7a, 0x7b, 0x8c, + 0x92, 0x98, 0xa6, 0xa0, 0x87, 0x00, 0x9a, 0xa1, + 0x95, 0x77, 0x33, 0x95, 0x00, 0x90, 0x00, 0x76, + 0x9b, 0x9a, 0x99, 0x98, 0x00, 0x00, 0xa0, 0x00, + 0x9e, 0x00, 0xa3, 0xa2, 0x15, 0x31, 0x32, 0x33, + 0xb7, 0xb8, 0x55, 0xac, 0xab, 0x12, 0x14, 0x1e, + 0x21, 0x22, 0x22, 0x2a, 0x34, 0x35, 0x00, 0xa8, + 0xa9, 0x39, 0x22, 0x4c, 0x00, 0x00, 0x97, 0x01, + 0x5a, 0xda, 0x1d, 0x36, 0x05, 0x00, 0xc7, 0xc6, + 0xc9, 0xc8, 0xcb, 0xca, 0xcd, 0xcc, 0xcf, 0xce, + 0xc4, 0xd8, 0x45, 0xd9, 0x42, 0xda, 0x46, 0xdb, + 0xd1, 0xd3, 0xd5, 0xd7, 0xdd, 0xdc, 0xf1, 0xf9, + 0x01, 0x11, 0x0a, 0x12, 0x80, 0x9f, 0x00, 0x21, + 0x80, 0xa3, 0xf0, 0x00, 0xc0, 0x40, 0xc6, 0x60, + 0xea, 0xde, 0xe6, 0x99, 0xc0, 0x00, 0x00, 0x06, + 0x60, 0xdf, 0x29, 0x00, 0x15, 0x12, 0x06, 0x16, + 0xfb, 0xe0, 0x09, 0x15, 0x12, 0x84, 0x0b, 0xc6, + 0x16, 0x02, 0xe2, 0x06, 0xc0, 0x40, 0x00, 0x46, + 0x60, 0xe1, 0xe3, 0x6d, 0x37, 0x38, 0x39, 0x18, + 0x17, 0x1a, 0x19, 0x00, 0x1d, 0x1c, 0x1f, 0x1e, + 0x00, 0x61, 0xba, 0x67, 0x45, 0x48, 0x00, 0x50, + 0x64, 0x4f, 0x51, 0x00, 0x00, 0x49, 0x00, 0x00, + 0x00, 0xa5, 0xa6, 0xa7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x00, 0x00, 0x5c, 0x00, 0x4a, 0x00, + 0x5d, 0x57, 0x59, 0x62, 0x60, 0x72, 0x6b, 0x71, + 0x54, 0x00, 0x3e, 0x69, 0xbb, 0x00, 0x5b, 0x00, + 0x00, 0x00, 0x25, 0x00, 0x48, 0xaa, 0x8a, 0x8b, + 0x8c, 0xab, 0xac, 0x58, 0x58, 0xaf, 0x94, 0xb0, + 0x6f, 0xb2, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66, + 0x6c, 0x6d, 0x6e, 0x6f, 0x68, 0x69, 0x6a, 0x6b, + 0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76, + 0x79, 0x78, +}; + +static const uint16_t case_conv_ext[58] = { + 0x0399, 0x0308, 0x0301, 0x03a5, 0x0313, 0x0300, 0x0342, 0x0391, + 0x0397, 0x03a9, 0x0046, 0x0049, 0x004c, 0x0053, 0x0069, 0x0307, + 0x02bc, 0x004e, 0x004a, 0x030c, 0x0535, 0x0552, 0x0048, 0x0331, + 0x0054, 0x0057, 0x030a, 0x0059, 0x0041, 0x02be, 0x1f08, 0x1f80, + 0x1f28, 0x1f90, 0x1f68, 0x1fa0, 0x1fba, 0x0386, 0x1fb3, 0x1fca, + 0x0389, 0x1fc3, 0x03a1, 0x1ffa, 0x038f, 0x1ff3, 0x0544, 0x0546, + 0x053b, 0x054e, 0x053d, 0x03b8, 0x0462, 0xa64a, 0x1e60, 0x03c9, + 0x006b, 0x00e5, +}; + +static const uint8_t unicode_prop_Cased1_table[193] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0xfc, 0x80, 0xd3, + 0x80, 0x9b, 0x81, 0x8d, 0x02, 0x80, 0xe1, 0x80, + 0x91, 0x85, 0x9a, 0x01, 0x00, 0x01, 0x11, 0x03, + 0x04, 0x08, 0x01, 0x08, 0x30, 0x08, 0x01, 0x15, + 0x20, 0x00, 0x39, 0x99, 0x31, 0x9d, 0x84, 0x40, + 0x94, 0x80, 0xd6, 0x82, 0xa6, 0x80, 0x41, 0x62, + 0x80, 0xa6, 0x80, 0x4b, 0x72, 0x80, 0x4c, 0x02, + 0xf8, 0x02, 0x80, 0x8f, 0x80, 0xb0, 0x40, 0xdb, + 0x08, 0x80, 0x41, 0xd0, 0x80, 0x8c, 0x80, 0x8f, + 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, 0x14, 0x28, + 0x10, 0x11, 0x02, 0x01, 0x18, 0x0b, 0x24, 0x4b, + 0x26, 0x01, 0x01, 0x86, 0xe5, 0x80, 0x60, 0x79, + 0xb6, 0x81, 0x40, 0x91, 0x81, 0xbd, 0x88, 0x94, + 0x05, 0x80, 0x98, 0x80, 0xa2, 0x00, 0x80, 0x9b, + 0x12, 0x82, 0x43, 0x34, 0xa2, 0x06, 0x80, 0x8d, + 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, 0x88, + 0x60, 0xcc, 0x44, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x89, 0x80, 0x93, 0x2d, 0x41, + 0x04, 0xbd, 0x50, 0xc1, 0x99, 0x85, 0x99, 0x85, + 0x99, +}; + +static const uint8_t unicode_prop_Cased1_index[18] = { + 0xb9, 0x02, 0x80, 0xa0, 0x1e, 0x40, 0x9e, 0xa6, + 0x40, 0xbb, 0x07, 0x01, 0xdb, 0xd6, 0x01, 0x8a, + 0xf1, 0x01, +}; + +static const uint8_t unicode_prop_Case_Ignorable_table[764] = { + 0xa6, 0x05, 0x80, 0x8a, 0x80, 0xa2, 0x00, 0x80, + 0xc6, 0x03, 0x00, 0x03, 0x01, 0x81, 0x41, 0xf6, + 0x40, 0xbf, 0x19, 0x18, 0x88, 0x08, 0x80, 0x40, + 0xfa, 0x86, 0x40, 0xce, 0x04, 0x80, 0xb0, 0xac, + 0x00, 0x01, 0x01, 0x00, 0xab, 0x80, 0x8a, 0x85, + 0x89, 0x8a, 0x00, 0xa2, 0x80, 0x89, 0x94, 0x8f, + 0x80, 0xe4, 0x38, 0x89, 0x03, 0xa0, 0x00, 0x80, + 0x9d, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0x18, 0x08, + 0x97, 0x97, 0xaa, 0x82, 0xab, 0x06, 0x0c, 0x88, + 0xa8, 0xb9, 0xb6, 0x00, 0x03, 0x3b, 0x02, 0x86, + 0x89, 0x81, 0x8c, 0x80, 0x8e, 0x80, 0xb9, 0x03, + 0x1f, 0x80, 0x93, 0x81, 0x99, 0x01, 0x81, 0xb8, + 0x03, 0x0b, 0x09, 0x12, 0x80, 0x9d, 0x0a, 0x80, + 0x8a, 0x81, 0xb8, 0x03, 0x20, 0x0b, 0x80, 0x93, + 0x81, 0x95, 0x28, 0x80, 0xb9, 0x01, 0x00, 0x1f, + 0x06, 0x81, 0x8a, 0x81, 0x9d, 0x80, 0xbc, 0x80, + 0x8b, 0x80, 0xb1, 0x02, 0x80, 0xb6, 0x00, 0x14, + 0x10, 0x1e, 0x81, 0x8a, 0x81, 0x9c, 0x80, 0xb9, + 0x01, 0x05, 0x04, 0x81, 0x93, 0x81, 0x9b, 0x81, + 0xb8, 0x0b, 0x1f, 0x80, 0x93, 0x81, 0x9c, 0x80, + 0xc7, 0x06, 0x10, 0x80, 0xd9, 0x01, 0x86, 0x8a, + 0x88, 0xe1, 0x01, 0x88, 0x88, 0x00, 0x86, 0xc8, + 0x81, 0x9a, 0x00, 0x00, 0x80, 0xb6, 0x8d, 0x04, + 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, 0x80, 0xe5, + 0x18, 0x28, 0x09, 0x81, 0x98, 0x0b, 0x82, 0x8f, + 0x83, 0x8c, 0x01, 0x0d, 0x80, 0x8e, 0x80, 0xdd, + 0x80, 0x42, 0x5f, 0x82, 0x43, 0xb1, 0x82, 0x9c, + 0x81, 0x9d, 0x81, 0x9d, 0x81, 0xbf, 0x08, 0x37, + 0x01, 0x8a, 0x10, 0x20, 0xac, 0x84, 0xb2, 0x80, + 0xc0, 0x81, 0xa1, 0x80, 0xf5, 0x13, 0x81, 0x88, + 0x05, 0x82, 0x40, 0xda, 0x09, 0x80, 0xb9, 0x00, + 0x30, 0x00, 0x01, 0x3d, 0x89, 0x08, 0xa6, 0x07, + 0x9e, 0xb0, 0x83, 0xaf, 0x00, 0x20, 0x04, 0x80, + 0xa7, 0x88, 0x8b, 0x81, 0x9f, 0x19, 0x08, 0x82, + 0xb7, 0x00, 0x0a, 0x00, 0x82, 0xb9, 0x39, 0x81, + 0xbf, 0x85, 0xd1, 0x10, 0x8c, 0x06, 0x18, 0x28, + 0x11, 0xb1, 0xbe, 0x8c, 0x80, 0xa1, 0xe4, 0x41, + 0xbc, 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, + 0x82, 0x8c, 0x81, 0x8b, 0x27, 0x81, 0x89, 0x01, + 0x01, 0x84, 0xb0, 0x20, 0x89, 0x00, 0x8c, 0x80, + 0x8f, 0x8c, 0xb2, 0xa0, 0x4b, 0x8a, 0x81, 0xf0, + 0x82, 0xfc, 0x80, 0x8e, 0x80, 0xdf, 0x9f, 0xae, + 0x80, 0x41, 0xd4, 0x80, 0xa3, 0x1a, 0x24, 0x80, + 0xdc, 0x85, 0xdc, 0x82, 0x60, 0x6f, 0x15, 0x80, + 0x44, 0xe1, 0x85, 0x41, 0x0d, 0x80, 0xe1, 0x18, + 0x89, 0x00, 0x9b, 0x83, 0xcf, 0x81, 0x8d, 0xa1, + 0xcd, 0x80, 0x96, 0x82, 0xe6, 0x12, 0x0f, 0x02, + 0x03, 0x80, 0x98, 0x0c, 0x80, 0x40, 0x96, 0x81, + 0x99, 0x91, 0x8c, 0x80, 0xa5, 0x87, 0x98, 0x8a, + 0xad, 0x82, 0xaf, 0x01, 0x19, 0x81, 0x90, 0x80, + 0x94, 0x81, 0xc1, 0x29, 0x09, 0x81, 0x8b, 0x07, + 0x80, 0xa2, 0x80, 0x8a, 0x80, 0xb2, 0x00, 0x11, + 0x0c, 0x08, 0x80, 0x9a, 0x80, 0x8d, 0x0c, 0x08, + 0x80, 0xe3, 0x84, 0x88, 0x82, 0xf8, 0x01, 0x03, + 0x80, 0x60, 0x4f, 0x2f, 0x80, 0x40, 0x92, 0x90, + 0x42, 0x3c, 0x8f, 0x10, 0x8b, 0x8f, 0xa1, 0x01, + 0x80, 0x40, 0xa8, 0x06, 0x05, 0x80, 0x8a, 0x80, + 0xa2, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x94, 0x82, 0x42, 0x00, 0x80, 0x40, 0xe1, + 0x80, 0x40, 0x94, 0x84, 0x44, 0x04, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x45, 0x10, 0x0c, 0x83, 0xa7, + 0x13, 0x80, 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x83, + 0xa5, 0x80, 0x99, 0x20, 0x80, 0x41, 0x3a, 0x81, + 0xce, 0x83, 0xc5, 0x8a, 0xb0, 0x83, 0xfa, 0x80, + 0xb5, 0x8e, 0xa8, 0x01, 0x81, 0x89, 0x82, 0xb0, + 0x19, 0x09, 0x03, 0x80, 0x89, 0x80, 0xb1, 0x82, + 0xa3, 0x20, 0x87, 0xbd, 0x80, 0x8b, 0x81, 0xb3, + 0x88, 0x89, 0x19, 0x80, 0xde, 0x11, 0x00, 0x0d, + 0x01, 0x80, 0x40, 0x9c, 0x02, 0x87, 0x94, 0x81, + 0xb8, 0x0a, 0x80, 0xa4, 0x32, 0x84, 0xc5, 0x85, + 0x8c, 0x00, 0x00, 0x80, 0x8d, 0x81, 0xd4, 0x39, + 0x10, 0x80, 0x96, 0x80, 0xd3, 0x28, 0x03, 0x08, + 0x81, 0x40, 0xed, 0x1d, 0x08, 0x81, 0x9a, 0x81, + 0xd4, 0x39, 0x00, 0x81, 0xe9, 0x00, 0x01, 0x28, + 0x80, 0xe4, 0x00, 0x01, 0x18, 0x84, 0x41, 0x02, + 0x88, 0x01, 0x40, 0xff, 0x08, 0x03, 0x80, 0x40, + 0x8f, 0x19, 0x0b, 0x80, 0x9f, 0x89, 0xa7, 0x29, + 0x1f, 0x80, 0x88, 0x29, 0x82, 0xad, 0x8c, 0x01, + 0x41, 0x95, 0x30, 0x28, 0x80, 0xd1, 0x95, 0x0e, + 0x01, 0x01, 0xf9, 0x2a, 0x00, 0x08, 0x30, 0x80, + 0xc7, 0x0a, 0x00, 0x80, 0x41, 0x5a, 0x81, 0x8a, + 0x81, 0xb3, 0x24, 0x00, 0x80, 0x96, 0x80, 0x54, + 0xd4, 0x90, 0x85, 0x8e, 0x60, 0x2c, 0xc7, 0x8b, + 0x12, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x88, 0x83, + 0x41, 0xfb, 0x82, 0xa7, 0x81, 0x41, 0xe1, 0x80, + 0xbe, 0x90, 0xbf, 0x08, 0x81, 0x60, 0x40, 0x0a, + 0x18, 0x30, 0x81, 0x4c, 0x9d, 0x08, 0x83, 0x52, + 0x5b, 0xad, 0x81, 0x96, 0x42, 0x1f, 0x82, 0x88, + 0x8f, 0x0e, 0x9d, 0x83, 0x40, 0x93, 0x82, 0x47, + 0xba, 0xb6, 0x83, 0xb1, 0x38, 0x8d, 0x80, 0x95, + 0x20, 0x8e, 0x45, 0x4f, 0x30, 0x90, 0x0e, 0x01, + 0x04, 0x84, 0xbd, 0xa0, 0x80, 0x40, 0x9f, 0x8d, + 0x41, 0x6f, 0x80, 0xbc, 0x83, 0x41, 0xfa, 0x84, + 0x40, 0xfd, 0x81, 0x42, 0xdf, 0x86, 0xec, 0x87, + 0x4a, 0xae, 0x84, 0x6c, 0x0c, 0x00, 0x80, 0x9d, + 0xdf, 0xff, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Case_Ignorable_index[72] = { + 0xbe, 0x05, 0x00, 0xfe, 0x07, 0x00, 0x52, 0x0a, + 0xa0, 0xc1, 0x0b, 0x00, 0x82, 0x0d, 0x00, 0x3f, + 0x10, 0x80, 0xd4, 0x17, 0x40, 0xcf, 0x1a, 0x20, + 0xf5, 0x1c, 0x00, 0x80, 0x20, 0x00, 0x16, 0xa0, + 0x00, 0xc6, 0xa8, 0x00, 0xc2, 0xaa, 0x60, 0x56, + 0xfe, 0x20, 0xb1, 0x07, 0x01, 0x02, 0x10, 0x01, + 0x42, 0x12, 0x41, 0xc4, 0x14, 0x21, 0xe1, 0x19, + 0x81, 0x48, 0x1d, 0x01, 0x44, 0x6b, 0x01, 0x83, + 0xd1, 0x21, 0x3e, 0xe1, 0x01, 0xf0, 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_ID_Start_table[1133] = { + 0xc0, 0x99, 0x85, 0x99, 0xae, 0x80, 0x89, 0x03, + 0x04, 0x96, 0x80, 0x9e, 0x80, 0x41, 0xc9, 0x83, + 0x8b, 0x8d, 0x26, 0x00, 0x80, 0x40, 0x80, 0x20, + 0x09, 0x18, 0x05, 0x00, 0x10, 0x00, 0x93, 0x80, + 0xd2, 0x80, 0x40, 0x8a, 0x87, 0x40, 0xa5, 0x80, + 0xa5, 0x08, 0x85, 0xa8, 0xc6, 0x9a, 0x1b, 0xac, + 0xaa, 0xa2, 0x08, 0xe2, 0x00, 0x8e, 0x0e, 0x81, + 0x89, 0x11, 0x80, 0x8f, 0x00, 0x9d, 0x9c, 0xd8, + 0x8a, 0x80, 0x97, 0xa0, 0x88, 0x0b, 0x04, 0x95, + 0x18, 0x88, 0x02, 0x80, 0x96, 0x98, 0x86, 0x8a, + 0x84, 0x97, 0x05, 0x90, 0xa9, 0xb9, 0xb5, 0x10, + 0x91, 0x06, 0x89, 0x8e, 0x8f, 0x1f, 0x09, 0x81, + 0x95, 0x06, 0x00, 0x13, 0x10, 0x8f, 0x80, 0x8c, + 0x08, 0x82, 0x8d, 0x81, 0x89, 0x07, 0x2b, 0x09, + 0x95, 0x06, 0x01, 0x01, 0x01, 0x9e, 0x18, 0x80, + 0x92, 0x82, 0x8f, 0x88, 0x02, 0x80, 0x95, 0x06, + 0x01, 0x04, 0x10, 0x91, 0x80, 0x8e, 0x81, 0x96, + 0x80, 0x8a, 0x39, 0x09, 0x95, 0x06, 0x01, 0x04, + 0x10, 0x9d, 0x08, 0x82, 0x8e, 0x80, 0x90, 0x00, + 0x2a, 0x10, 0x1a, 0x08, 0x00, 0x0a, 0x0a, 0x12, + 0x8b, 0x95, 0x80, 0xb3, 0x38, 0x10, 0x96, 0x80, + 0x8f, 0x10, 0x99, 0x11, 0x01, 0x81, 0x9d, 0x03, + 0x38, 0x10, 0x96, 0x80, 0x89, 0x04, 0x10, 0x9e, + 0x08, 0x81, 0x8e, 0x81, 0x90, 0x88, 0x02, 0x80, + 0xa8, 0x08, 0x8f, 0x04, 0x17, 0x82, 0x97, 0x2c, + 0x91, 0x82, 0x97, 0x80, 0x88, 0x00, 0x0e, 0xb9, + 0xaf, 0x01, 0x8b, 0x86, 0xb9, 0x08, 0x00, 0x20, + 0x97, 0x00, 0x80, 0x89, 0x01, 0x88, 0x01, 0x20, + 0x80, 0x94, 0x83, 0x9f, 0x80, 0xbe, 0x38, 0xa3, + 0x9a, 0x84, 0xf2, 0xaa, 0x93, 0x80, 0x8f, 0x2b, + 0x1a, 0x02, 0x0e, 0x13, 0x8c, 0x8b, 0x80, 0x90, + 0xa5, 0x00, 0x20, 0x81, 0xaa, 0x80, 0x41, 0x4c, + 0x03, 0x0e, 0x00, 0x03, 0x81, 0xa8, 0x03, 0x81, + 0xa0, 0x03, 0x0e, 0x00, 0x03, 0x81, 0x8e, 0x80, + 0xb8, 0x03, 0x81, 0xc2, 0xa4, 0x8f, 0x8f, 0xd5, + 0x0d, 0x82, 0x42, 0x6b, 0x81, 0x90, 0x80, 0x99, + 0x84, 0xca, 0x82, 0x8a, 0x86, 0x91, 0x8c, 0x92, + 0x8d, 0x91, 0x8d, 0x8c, 0x02, 0x8e, 0xb3, 0xa2, + 0x03, 0x80, 0xc2, 0xd8, 0x86, 0xa8, 0x00, 0x84, + 0xc5, 0x89, 0x9e, 0xb0, 0x9d, 0x0c, 0x8a, 0xab, + 0x83, 0x99, 0xb5, 0x96, 0x88, 0xb4, 0xd1, 0x80, + 0xdc, 0xae, 0x90, 0x87, 0xb5, 0x9d, 0x8c, 0x81, + 0x89, 0xab, 0x99, 0xa3, 0xa8, 0x82, 0x89, 0xa3, + 0x81, 0x8a, 0x84, 0xaa, 0x0a, 0xa8, 0x18, 0x28, + 0x0a, 0x04, 0x40, 0xbf, 0xbf, 0x41, 0x15, 0x0d, + 0x81, 0xa5, 0x0d, 0x0f, 0x00, 0x00, 0x00, 0x80, + 0x9e, 0x81, 0xb4, 0x06, 0x00, 0x12, 0x06, 0x13, + 0x0d, 0x83, 0x8c, 0x22, 0x06, 0xf3, 0x80, 0x8c, + 0x80, 0x8f, 0x8c, 0xe4, 0x03, 0x01, 0x89, 0x00, + 0x0d, 0x28, 0x00, 0x00, 0x80, 0x8f, 0x0b, 0x24, + 0x18, 0x90, 0xa8, 0x4a, 0x76, 0x40, 0xe4, 0x2b, + 0x11, 0x8b, 0xa5, 0x00, 0x20, 0x81, 0xb7, 0x30, + 0x8f, 0x96, 0x88, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x86, 0x42, 0x25, 0x82, 0x98, 0x88, + 0x34, 0x0c, 0x83, 0xd5, 0x1c, 0x80, 0xd9, 0x03, + 0x84, 0xaa, 0x80, 0xdd, 0x90, 0x9f, 0xaf, 0x8f, + 0x41, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x56, 0x8c, + 0xc2, 0xad, 0x81, 0x41, 0x0c, 0x82, 0x8f, 0x89, + 0x81, 0x93, 0xae, 0x8f, 0x9e, 0x81, 0xcf, 0xa6, + 0x88, 0x81, 0xe6, 0x81, 0xc2, 0x09, 0x00, 0x07, + 0x94, 0x8f, 0x02, 0x03, 0x80, 0x96, 0x9c, 0xb3, + 0x8d, 0xb1, 0xbd, 0x2a, 0x00, 0x81, 0x8a, 0x9b, + 0x89, 0x96, 0x98, 0x9c, 0x86, 0xae, 0x9b, 0x80, + 0x8f, 0x20, 0x89, 0x89, 0x20, 0xa8, 0x96, 0x10, + 0x87, 0x93, 0x96, 0x10, 0x82, 0xb1, 0x00, 0x11, + 0x0c, 0x08, 0x00, 0x97, 0x11, 0x8a, 0x32, 0x8b, + 0x29, 0x29, 0x85, 0x88, 0x30, 0x30, 0xaa, 0x80, + 0x8d, 0x85, 0xf2, 0x9c, 0x60, 0x2b, 0xa3, 0x8b, + 0x96, 0x83, 0xb0, 0x60, 0x21, 0x03, 0x41, 0x6d, + 0x81, 0xe9, 0xa5, 0x86, 0x8b, 0x24, 0x00, 0x89, + 0x80, 0x8c, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8b, 0xf3, 0x20, 0x40, 0x86, 0xa3, 0x99, 0x85, + 0x99, 0x8a, 0xd8, 0x15, 0x0d, 0x0d, 0x0a, 0xa2, + 0x8b, 0x80, 0x99, 0x80, 0x92, 0x01, 0x80, 0x8e, + 0x81, 0x8d, 0xa1, 0xfa, 0xc4, 0xb4, 0x41, 0x0a, + 0x9c, 0x82, 0xb0, 0xae, 0x9f, 0x8c, 0x9d, 0x84, + 0xa5, 0x89, 0x9d, 0x81, 0xa3, 0x1f, 0x04, 0xa9, + 0x40, 0x9d, 0x91, 0xa3, 0x83, 0xa3, 0x83, 0xa7, + 0x87, 0xb3, 0x8b, 0x8a, 0x80, 0x8e, 0x06, 0x01, + 0x80, 0x8a, 0x80, 0x8e, 0x06, 0x01, 0x82, 0xb3, + 0x8b, 0x41, 0x36, 0x88, 0x95, 0x89, 0x87, 0x97, + 0x28, 0xa9, 0x80, 0x88, 0xc4, 0x29, 0x00, 0xab, + 0x01, 0x10, 0x81, 0x96, 0x89, 0x96, 0x88, 0x9e, + 0xc0, 0x92, 0x01, 0x89, 0x95, 0x89, 0x99, 0xc5, + 0xb7, 0x29, 0xbf, 0x80, 0x8e, 0x18, 0x10, 0x9c, + 0xa9, 0x9c, 0x82, 0x9c, 0xa2, 0x38, 0x9b, 0x9a, + 0xb5, 0x89, 0x95, 0x89, 0x92, 0x8c, 0x91, 0xed, + 0xc8, 0xb6, 0xb2, 0x8c, 0xb2, 0x8c, 0xa3, 0xa5, + 0x9b, 0x88, 0x96, 0x40, 0xf9, 0xa9, 0x29, 0x8f, + 0x82, 0xba, 0x9c, 0x89, 0x07, 0x95, 0xa9, 0x91, + 0xad, 0x94, 0x9a, 0x96, 0x8b, 0xb4, 0xb8, 0x09, + 0x80, 0x8c, 0xac, 0x9f, 0x98, 0x99, 0xa3, 0x9c, + 0x01, 0x07, 0xa2, 0x10, 0x8b, 0xaf, 0x8d, 0x83, + 0x94, 0x00, 0x80, 0xa2, 0x91, 0x80, 0x98, 0x92, + 0x81, 0xbe, 0x30, 0x00, 0x18, 0x8e, 0x80, 0x89, + 0x86, 0xae, 0xa5, 0x39, 0x09, 0x95, 0x06, 0x01, + 0x04, 0x10, 0x91, 0x80, 0x8b, 0x84, 0x9d, 0x89, + 0x00, 0x08, 0x80, 0xa5, 0x00, 0x98, 0x00, 0x80, + 0xab, 0xb4, 0x91, 0x83, 0x93, 0x82, 0x9d, 0xaf, + 0x93, 0x08, 0x80, 0x40, 0xb7, 0xae, 0xa8, 0x83, + 0xa3, 0xaf, 0x93, 0x80, 0xba, 0xaa, 0x8c, 0x80, + 0xc6, 0x9a, 0xa4, 0x86, 0x40, 0xb8, 0xab, 0xf3, + 0xbf, 0x9e, 0x39, 0x01, 0x38, 0x08, 0x97, 0x8e, + 0x00, 0x80, 0xdd, 0x39, 0xa6, 0x8f, 0x00, 0x80, + 0x9b, 0x80, 0x89, 0xa7, 0x30, 0x94, 0x80, 0x8a, + 0xad, 0x92, 0x80, 0x91, 0xc8, 0x40, 0xc6, 0xa0, + 0x9e, 0x88, 0x80, 0xa4, 0x90, 0x80, 0xb0, 0x9d, + 0xef, 0x30, 0x08, 0xa5, 0x94, 0x80, 0x98, 0x28, + 0x08, 0x9f, 0x8d, 0x80, 0x41, 0x46, 0x92, 0x8e, + 0x00, 0x8c, 0x80, 0xa1, 0xfb, 0x80, 0xce, 0x43, + 0x99, 0xe5, 0xee, 0x90, 0x40, 0xc3, 0x4a, 0x4b, + 0xe0, 0x8e, 0x44, 0x2f, 0x90, 0x85, 0x98, 0x4f, + 0x9a, 0x84, 0x42, 0x46, 0x5a, 0xb8, 0x9d, 0x46, + 0xe1, 0x42, 0x38, 0x86, 0x9e, 0x90, 0xce, 0x90, + 0x9d, 0x91, 0xaf, 0x8f, 0x83, 0x9e, 0x94, 0x84, + 0x92, 0x41, 0xaf, 0xac, 0x40, 0xd2, 0xbf, 0xff, + 0xca, 0x20, 0xc1, 0x8c, 0xbf, 0x08, 0x80, 0x9b, + 0x57, 0xf7, 0x87, 0x44, 0xd5, 0xa8, 0x89, 0x60, + 0x22, 0xe6, 0x18, 0x30, 0x08, 0x41, 0x22, 0x8e, + 0x80, 0x9c, 0x11, 0x80, 0x8d, 0x1f, 0x41, 0x8b, + 0x49, 0x03, 0xea, 0x84, 0x8c, 0x82, 0x88, 0x86, + 0x89, 0x57, 0x65, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, + 0x07, 0x47, 0x33, 0x9e, 0x2d, 0x41, 0x04, 0xbd, + 0x40, 0x91, 0xac, 0x89, 0x86, 0x8f, 0x80, 0x41, + 0x40, 0x9d, 0x91, 0xab, 0x41, 0xe3, 0x9b, 0x40, + 0xe3, 0x9d, 0x08, 0x41, 0xee, 0x30, 0x18, 0x08, + 0x8e, 0x80, 0x40, 0xc4, 0xba, 0xc3, 0x30, 0x44, + 0xb3, 0x18, 0x9a, 0x01, 0x00, 0x08, 0x80, 0x89, + 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, 0x00, 0x02, + 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, 0x80, 0x89, + 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, 0x51, 0x43, + 0x60, 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, + 0xdd, 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, + 0x42, 0x6d, 0x49, 0xa1, 0x42, 0x1d, 0x45, 0xe1, + 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_ID_Start_index[108] = { + 0xf6, 0x03, 0x20, 0xa6, 0x07, 0x00, 0xa9, 0x09, + 0x20, 0xb1, 0x0a, 0x00, 0xba, 0x0b, 0x20, 0x3b, + 0x0d, 0x20, 0xc7, 0x0e, 0x20, 0x49, 0x12, 0x00, + 0x9b, 0x16, 0x00, 0xac, 0x19, 0x00, 0xc0, 0x1d, + 0x80, 0x80, 0x20, 0x20, 0x70, 0x2d, 0x00, 0x00, + 0x32, 0x00, 0xdd, 0xa7, 0x00, 0x4c, 0xaa, 0x20, + 0xc7, 0xd7, 0x20, 0xfc, 0xfd, 0x20, 0x9d, 0x02, + 0x21, 0x96, 0x05, 0x01, 0x9f, 0x08, 0x01, 0x49, + 0x0c, 0x21, 0x76, 0x10, 0x21, 0xa9, 0x12, 0x01, + 0xb0, 0x14, 0x01, 0x42, 0x19, 0x41, 0x90, 0x1c, + 0x01, 0xf1, 0x2f, 0x21, 0x90, 0x6b, 0x21, 0x33, + 0xb1, 0x21, 0x06, 0xd5, 0x01, 0xc3, 0xd7, 0x01, + 0xff, 0xe7, 0x21, 0x63, 0xee, 0x01, 0x5e, 0xee, + 0x42, 0xb0, 0x23, 0x03, +}; + +static const uint8_t unicode_prop_ID_Continue1_table[695] = { + 0xaf, 0x89, 0xa4, 0x80, 0xd6, 0x80, 0x42, 0x47, + 0xef, 0x96, 0x80, 0x40, 0xfa, 0x84, 0x41, 0x08, + 0xac, 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, + 0x9e, 0x28, 0xe4, 0x31, 0x29, 0x08, 0x19, 0x89, + 0x96, 0x80, 0x9d, 0x9a, 0xda, 0x8a, 0x8e, 0x89, + 0xa0, 0x88, 0x88, 0x80, 0x97, 0x18, 0x88, 0x02, + 0x04, 0xaa, 0x82, 0xba, 0x88, 0xa9, 0x97, 0x80, + 0xa0, 0xb5, 0x10, 0x91, 0x06, 0x89, 0x09, 0x89, + 0x90, 0x82, 0xb7, 0x00, 0x31, 0x09, 0x82, 0x88, + 0x80, 0x89, 0x09, 0x89, 0x8d, 0x01, 0x82, 0xb7, + 0x00, 0x23, 0x09, 0x12, 0x80, 0x93, 0x8b, 0x10, + 0x8a, 0x82, 0xb7, 0x00, 0x38, 0x10, 0x82, 0x93, + 0x09, 0x89, 0x89, 0x28, 0x82, 0xb7, 0x00, 0x31, + 0x09, 0x16, 0x82, 0x89, 0x09, 0x89, 0x91, 0x80, + 0xba, 0x22, 0x10, 0x83, 0x88, 0x80, 0x8d, 0x89, + 0x8f, 0x84, 0xb6, 0x00, 0x30, 0x10, 0x1e, 0x81, + 0x8a, 0x09, 0x89, 0x90, 0x82, 0xb7, 0x00, 0x30, + 0x10, 0x1e, 0x81, 0x8a, 0x09, 0x89, 0x10, 0x8b, + 0x83, 0xb6, 0x08, 0x30, 0x10, 0x83, 0x88, 0x80, + 0x89, 0x09, 0x89, 0x90, 0x82, 0xc5, 0x03, 0x28, + 0x00, 0x3d, 0x89, 0x09, 0xbc, 0x01, 0x86, 0x8b, + 0x38, 0x89, 0xd6, 0x01, 0x88, 0x8a, 0x30, 0x89, + 0xbd, 0x0d, 0x89, 0x8a, 0x00, 0x00, 0x03, 0x81, + 0xb0, 0x93, 0x01, 0x84, 0x8a, 0x80, 0xa3, 0x88, + 0x80, 0xe3, 0x93, 0x80, 0x89, 0x8b, 0x1b, 0x10, + 0x11, 0x32, 0x83, 0x8c, 0x8b, 0x80, 0x8e, 0x42, + 0xbe, 0x82, 0x88, 0x88, 0x43, 0x9f, 0x83, 0x9b, + 0x82, 0x9c, 0x81, 0x9d, 0x81, 0xbf, 0x9f, 0x88, + 0x01, 0x89, 0xa0, 0x10, 0x8a, 0x40, 0x8e, 0x80, + 0xf5, 0x8b, 0x83, 0x8b, 0x89, 0x89, 0xff, 0x8a, + 0xbb, 0x84, 0xb8, 0x89, 0x80, 0x9c, 0x81, 0x8a, + 0x85, 0x89, 0x95, 0x8d, 0x80, 0x8f, 0xb0, 0x84, + 0xae, 0x90, 0x8a, 0x89, 0x90, 0x88, 0x8b, 0x82, + 0x9d, 0x8c, 0x81, 0x89, 0xab, 0x8d, 0xaf, 0x93, + 0x87, 0x89, 0x85, 0x89, 0xf5, 0x10, 0x94, 0x18, + 0x28, 0x0a, 0x40, 0xc5, 0xbf, 0x42, 0x0b, 0x81, + 0xb0, 0x81, 0x92, 0x80, 0xfa, 0x8c, 0x18, 0x82, + 0x8b, 0x4b, 0xfd, 0x82, 0x40, 0x8c, 0x80, 0xdf, + 0x9f, 0x42, 0x29, 0x85, 0xe8, 0x81, 0xdf, 0x80, + 0x60, 0x75, 0x23, 0x89, 0xc4, 0x03, 0x89, 0x9f, + 0x81, 0xcf, 0x81, 0x41, 0x0f, 0x02, 0x03, 0x80, + 0x96, 0x23, 0x80, 0xd2, 0x81, 0xb1, 0x91, 0x89, + 0x89, 0x85, 0x91, 0x8c, 0x8a, 0x9b, 0x87, 0x98, + 0x8c, 0xab, 0x83, 0xae, 0x8d, 0x8e, 0x89, 0x8a, + 0x80, 0x89, 0x89, 0xae, 0x8d, 0x8b, 0x07, 0x09, + 0x89, 0xa0, 0x82, 0xb1, 0x00, 0x11, 0x0c, 0x08, + 0x80, 0xa8, 0x24, 0x81, 0x40, 0xeb, 0x38, 0x09, + 0x89, 0x60, 0x4f, 0x23, 0x80, 0x42, 0xe0, 0x8f, + 0x8f, 0x8f, 0x11, 0x97, 0x82, 0x40, 0xbf, 0x89, + 0xa4, 0x80, 0xa4, 0x80, 0x42, 0x96, 0x80, 0x40, + 0xe1, 0x80, 0x40, 0x94, 0x84, 0x41, 0x24, 0x89, + 0x45, 0x56, 0x10, 0x0c, 0x83, 0xa7, 0x13, 0x80, + 0x40, 0xa4, 0x81, 0x42, 0x3c, 0x1f, 0x89, 0x85, + 0x89, 0x9e, 0x84, 0x41, 0x3c, 0x81, 0xce, 0x83, + 0xc5, 0x8a, 0xb0, 0x83, 0xf9, 0x82, 0xb4, 0x8e, + 0x9e, 0x8a, 0x09, 0x89, 0x83, 0xac, 0x8a, 0x30, + 0xac, 0x89, 0x2a, 0xa3, 0x8d, 0x80, 0x89, 0x21, + 0xab, 0x80, 0x8b, 0x82, 0xaf, 0x8d, 0x3b, 0x80, + 0x8b, 0xd1, 0x8b, 0x28, 0x08, 0x40, 0x9c, 0x8b, + 0x84, 0x89, 0x2b, 0xb6, 0x08, 0x31, 0x09, 0x82, + 0x88, 0x80, 0x89, 0x09, 0x32, 0x84, 0xc2, 0x88, + 0x00, 0x08, 0x03, 0x04, 0x00, 0x8d, 0x81, 0xd1, + 0x91, 0x88, 0x89, 0x18, 0xd0, 0x93, 0x8b, 0x89, + 0x40, 0xd4, 0x31, 0x88, 0x9a, 0x81, 0xd1, 0x90, + 0x8e, 0x89, 0xd0, 0x8c, 0x87, 0x89, 0x85, 0x93, + 0xb8, 0x8e, 0x83, 0x89, 0x40, 0xf1, 0x8e, 0x40, + 0xa4, 0x89, 0xc5, 0x28, 0x09, 0x18, 0x00, 0x81, + 0x8b, 0x89, 0xf6, 0x31, 0x32, 0x80, 0x9b, 0x89, + 0xa7, 0x30, 0x1f, 0x80, 0x88, 0x8a, 0xad, 0x8f, + 0x41, 0x55, 0x89, 0xb4, 0x38, 0x87, 0x8f, 0x89, + 0xb7, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x30, 0x07, 0x89, 0xaf, 0x20, 0x08, 0x27, 0x89, + 0x41, 0x48, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, + 0x84, 0x8c, 0x8a, 0x54, 0xe4, 0x05, 0x8e, 0x60, + 0x2c, 0xc7, 0x9b, 0x49, 0x25, 0x89, 0xd5, 0x89, + 0xa5, 0x84, 0xba, 0x86, 0x98, 0x89, 0x42, 0x15, + 0x89, 0x41, 0xd4, 0x00, 0xb6, 0x33, 0xd0, 0x80, + 0x8a, 0x81, 0x60, 0x4c, 0xaa, 0x81, 0x50, 0x50, + 0x89, 0x42, 0x05, 0xad, 0x81, 0x96, 0x42, 0x1d, + 0x22, 0x2f, 0x39, 0x86, 0x9d, 0x83, 0x40, 0x93, + 0x82, 0x45, 0x88, 0xb1, 0x41, 0xff, 0xb6, 0x83, + 0xb1, 0x38, 0x8d, 0x80, 0x95, 0x20, 0x8e, 0x45, + 0x4f, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, 0x80, + 0x40, 0x9f, 0x86, 0x88, 0x89, 0x41, 0x63, 0x80, + 0xbc, 0x8d, 0x41, 0xf1, 0x8d, 0x40, 0xf3, 0x08, + 0x89, 0x42, 0xd4, 0x86, 0xec, 0x34, 0x89, 0x52, + 0x95, 0x89, 0x6c, 0x05, 0x05, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_ID_Continue1_index[66] = { + 0xfa, 0x06, 0x00, 0x70, 0x09, 0x00, 0xf0, 0x0a, + 0x40, 0x57, 0x0c, 0x00, 0xf0, 0x0d, 0x60, 0xc7, + 0x0f, 0x20, 0xea, 0x17, 0x40, 0x05, 0x1b, 0x00, + 0x0e, 0x20, 0x00, 0xa0, 0xa6, 0x20, 0xe6, 0xa9, + 0x20, 0x10, 0xfe, 0x00, 0x40, 0x0a, 0x01, 0xc3, + 0x10, 0x01, 0x4e, 0x13, 0x01, 0x41, 0x16, 0x01, + 0x0b, 0x1a, 0x01, 0xaa, 0x1d, 0x01, 0x7a, 0x6d, + 0x21, 0x45, 0xd2, 0x21, 0xaf, 0xe2, 0x01, 0xf0, + 0x01, 0x0e, +}; + +static const uint8_t unicode_prop_White_Space_table[22] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x99, 0x80, + 0x55, 0xde, 0x80, 0x49, 0x7e, 0x8a, 0x9c, 0x0c, + 0x80, 0xae, 0x80, 0x4f, 0x9f, 0x80, +}; + +static const uint8_t unicode_prop_White_Space_index[3] = { + 0x01, 0x30, 0x00, +}; + +static const uint8_t unicode_cc_table[916] = { + 0xb2, 0xcf, 0xd4, 0x00, 0xe8, 0x03, 0xdc, 0x00, + 0xe8, 0x00, 0xd8, 0x04, 0xdc, 0x01, 0xca, 0x03, + 0xdc, 0x01, 0xca, 0x0a, 0xdc, 0x04, 0x01, 0x03, + 0xdc, 0xc7, 0x00, 0xf0, 0xc0, 0x02, 0xdc, 0xc2, + 0x01, 0xdc, 0x80, 0xc2, 0x03, 0xdc, 0xc0, 0x00, + 0xe8, 0x01, 0xdc, 0xc0, 0x41, 0xe9, 0x00, 0xea, + 0x41, 0xe9, 0x00, 0xea, 0x00, 0xe9, 0xcc, 0xb0, + 0xe2, 0xc4, 0xb0, 0xd8, 0x00, 0xdc, 0xc3, 0x00, + 0xdc, 0xc2, 0x00, 0xde, 0x00, 0xdc, 0xc5, 0x05, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xde, 0x00, + 0xe4, 0xc0, 0x49, 0x0a, 0x43, 0x13, 0x80, 0x00, + 0x17, 0x80, 0x41, 0x18, 0x80, 0xc0, 0x00, 0xdc, + 0x80, 0x00, 0x12, 0xb0, 0x17, 0xc7, 0x42, 0x1e, + 0xaf, 0x47, 0x1b, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0x8f, 0x00, 0x23, 0xb0, + 0x34, 0xc6, 0x81, 0xc3, 0x00, 0xdc, 0xc0, 0x81, + 0xc1, 0x80, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xa2, + 0x00, 0x24, 0x9d, 0xc0, 0x00, 0xdc, 0xc1, 0x00, + 0xdc, 0xc1, 0x02, 0xdc, 0xc0, 0x01, 0xdc, 0xc0, + 0x00, 0xdc, 0xc2, 0x00, 0xdc, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x6f, 0xc6, 0x00, 0xdc, 0xc0, 0x88, 0x00, 0xdc, + 0x97, 0xc3, 0x80, 0xc8, 0x80, 0xc2, 0x80, 0xc4, + 0xaa, 0x02, 0xdc, 0xb0, 0x0a, 0xc1, 0x02, 0xdc, + 0xc3, 0xa9, 0xc4, 0x04, 0xdc, 0xcd, 0x80, 0x00, + 0xdc, 0xc1, 0x00, 0xdc, 0xc1, 0x00, 0xdc, 0xc2, + 0x02, 0xdc, 0x42, 0x1b, 0xc2, 0x00, 0xdc, 0xc1, + 0x01, 0xdc, 0xc4, 0xb0, 0x0b, 0x00, 0x07, 0x8f, + 0x00, 0x09, 0x82, 0xc0, 0x00, 0xdc, 0xc1, 0xb0, + 0x36, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xaf, 0xc0, + 0xb0, 0x0c, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, + 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3d, + 0x00, 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x4e, 0x00, + 0x09, 0xb0, 0x3d, 0x00, 0x07, 0x8f, 0x00, 0x09, + 0x86, 0x00, 0x54, 0x00, 0x5b, 0xb0, 0x34, 0x00, + 0x07, 0x8f, 0x00, 0x09, 0xb0, 0x3c, 0x01, 0x09, + 0x8f, 0x00, 0x09, 0xb0, 0x4b, 0x00, 0x09, 0xb0, + 0x3c, 0x01, 0x67, 0x00, 0x09, 0x8c, 0x03, 0x6b, + 0xb0, 0x3b, 0x01, 0x76, 0x00, 0x09, 0x8c, 0x03, + 0x7a, 0xb0, 0x1b, 0x01, 0xdc, 0x9a, 0x00, 0xdc, + 0x80, 0x00, 0xdc, 0x80, 0x00, 0xd8, 0xb0, 0x06, + 0x41, 0x81, 0x80, 0x00, 0x84, 0x84, 0x03, 0x82, + 0x81, 0x00, 0x82, 0x80, 0xc1, 0x00, 0x09, 0x80, + 0xc1, 0xb0, 0x0d, 0x00, 0xdc, 0xb0, 0x3f, 0x00, + 0x07, 0x80, 0x01, 0x09, 0xb0, 0x21, 0x00, 0xdc, + 0xb2, 0x9e, 0xc2, 0xb3, 0x83, 0x01, 0x09, 0x9d, + 0x00, 0x09, 0xb0, 0x6c, 0x00, 0x09, 0x89, 0xc0, + 0xb0, 0x9a, 0x00, 0xe4, 0xb0, 0x5e, 0x00, 0xde, + 0xc0, 0x00, 0xdc, 0xb0, 0xaa, 0xc0, 0x00, 0xdc, + 0xb0, 0x16, 0x00, 0x09, 0x93, 0xc7, 0x81, 0x00, + 0xdc, 0xaf, 0xc4, 0x05, 0xdc, 0xc1, 0x00, 0xdc, + 0x80, 0x01, 0xdc, 0xc1, 0x01, 0xdc, 0xc4, 0x00, + 0xdc, 0xc3, 0xb0, 0x34, 0x00, 0x07, 0x8e, 0x00, + 0x09, 0xa5, 0xc0, 0x00, 0xdc, 0xc6, 0xb0, 0x05, + 0x01, 0x09, 0xb0, 0x09, 0x00, 0x07, 0x8a, 0x01, + 0x09, 0xb0, 0x12, 0x00, 0x07, 0xb0, 0x67, 0xc2, + 0x41, 0x00, 0x04, 0xdc, 0xc1, 0x03, 0xdc, 0xc0, + 0x41, 0x00, 0x05, 0x01, 0x83, 0x00, 0xdc, 0x85, + 0xc0, 0x82, 0xc1, 0xb0, 0x95, 0xc1, 0x00, 0xdc, + 0xc6, 0x00, 0xdc, 0xc1, 0x00, 0xea, 0x00, 0xd6, + 0x00, 0xdc, 0x00, 0xca, 0xe4, 0x00, 0xe8, 0x01, + 0xe4, 0x00, 0xdc, 0x00, 0xda, 0xc0, 0x00, 0xe9, + 0x00, 0xdc, 0xc0, 0x00, 0xdc, 0xb2, 0x9f, 0xc1, + 0x01, 0x01, 0xc3, 0x02, 0x01, 0xc1, 0x83, 0xc0, + 0x82, 0x01, 0x01, 0xc0, 0x00, 0xdc, 0xc0, 0x01, + 0x01, 0x03, 0xdc, 0xc0, 0xb8, 0x03, 0xcd, 0xc2, + 0xb0, 0x5c, 0x00, 0x09, 0xb0, 0x2f, 0xdf, 0xb1, + 0xf9, 0x00, 0xda, 0x00, 0xe4, 0x00, 0xe8, 0x00, + 0xde, 0x01, 0xe0, 0xb0, 0x38, 0x01, 0x08, 0xb8, + 0x6d, 0xa3, 0xc0, 0x83, 0xc9, 0x9f, 0xc1, 0xb0, + 0x1f, 0xc1, 0xb0, 0xe3, 0x00, 0x09, 0xa4, 0x00, + 0x09, 0xb0, 0x66, 0x00, 0x09, 0x9a, 0xd1, 0xb0, + 0x08, 0x02, 0xdc, 0xa4, 0x00, 0x09, 0xb0, 0x2e, + 0x00, 0x07, 0x8b, 0x00, 0x09, 0xb0, 0xbe, 0xc0, + 0x80, 0xc1, 0x00, 0xdc, 0x81, 0xc1, 0x84, 0xc1, + 0x80, 0xc0, 0xb0, 0x03, 0x00, 0x09, 0xb0, 0xc5, + 0x00, 0x09, 0xb8, 0x46, 0xff, 0x00, 0x1a, 0xb2, + 0xd0, 0xc6, 0x06, 0xdc, 0xc1, 0xb3, 0x9c, 0x00, + 0xdc, 0xb0, 0xb1, 0x00, 0xdc, 0xb0, 0x64, 0xc4, + 0xb6, 0x61, 0x00, 0xdc, 0x80, 0xc0, 0xa7, 0xc0, + 0x00, 0x01, 0x00, 0xdc, 0x83, 0x00, 0x09, 0xb0, + 0x74, 0xc0, 0x00, 0xdc, 0xb2, 0x0c, 0xc3, 0xb0, + 0x10, 0xc4, 0xb1, 0x0c, 0xc1, 0xb0, 0x1f, 0x02, + 0xdc, 0xb0, 0x15, 0x01, 0xdc, 0xc2, 0x00, 0xdc, + 0xc0, 0x03, 0xdc, 0xb0, 0x00, 0xc0, 0x00, 0xdc, + 0xc0, 0x00, 0xdc, 0xb0, 0x8f, 0x00, 0x09, 0xa8, + 0x00, 0x09, 0x8d, 0x00, 0x09, 0xb0, 0x08, 0x00, + 0x09, 0x00, 0x07, 0xb0, 0x14, 0xc2, 0xaf, 0x01, + 0x09, 0xb0, 0x0d, 0x00, 0x07, 0xb0, 0x1b, 0x00, + 0x09, 0x88, 0x00, 0x07, 0xb0, 0x39, 0x00, 0x09, + 0x00, 0x07, 0xb0, 0x81, 0x00, 0x07, 0x00, 0x09, + 0xb0, 0x1f, 0x01, 0x07, 0x8f, 0x00, 0x09, 0x97, + 0xc6, 0x82, 0xc4, 0xb0, 0x28, 0x02, 0x09, 0xb0, + 0x40, 0x00, 0x09, 0x82, 0x00, 0x07, 0x96, 0xc0, + 0xb0, 0x32, 0x00, 0x09, 0x00, 0x07, 0xb0, 0xca, + 0x00, 0x09, 0x00, 0x07, 0xb0, 0x4d, 0x00, 0x09, + 0xb0, 0x45, 0x00, 0x09, 0x00, 0x07, 0xb0, 0x42, + 0x00, 0x09, 0xb0, 0xdc, 0x00, 0x09, 0x00, 0x07, + 0xb0, 0xd1, 0x01, 0x09, 0x83, 0x00, 0x07, 0xb0, + 0x6b, 0x00, 0x09, 0xb0, 0x22, 0x00, 0x09, 0x91, + 0x00, 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x74, + 0x00, 0x09, 0xb0, 0xd1, 0x00, 0x07, 0x80, 0x01, + 0x09, 0xb0, 0x20, 0x00, 0x09, 0xb1, 0x78, 0x01, + 0x09, 0xb8, 0x39, 0xbb, 0x00, 0x09, 0xb8, 0x01, + 0x8f, 0x04, 0x01, 0xb0, 0x0a, 0xc6, 0xb4, 0x88, + 0x01, 0x06, 0xb8, 0x44, 0x7b, 0x00, 0x01, 0xb8, + 0x0c, 0x95, 0x01, 0xd8, 0x02, 0x01, 0x82, 0x00, + 0xe2, 0x04, 0xd8, 0x87, 0x07, 0xdc, 0x81, 0xc4, + 0x01, 0xdc, 0x9d, 0xc3, 0xb0, 0x63, 0xc2, 0xb8, + 0x05, 0x8a, 0xc6, 0x80, 0xd0, 0x81, 0xc6, 0x80, + 0xc1, 0x80, 0xc4, 0xb0, 0x33, 0xc0, 0xb0, 0x6f, + 0xc6, 0xb1, 0x46, 0xc0, 0xb0, 0x0c, 0xc3, 0xb1, + 0xcb, 0x01, 0xe8, 0x00, 0xdc, 0xc0, 0xb0, 0xcd, + 0xc0, 0x00, 0xdc, 0xb2, 0xaf, 0x06, 0xdc, 0xb0, + 0x3c, 0xc5, 0x00, 0x07, +}; + +static const uint8_t unicode_cc_index[87] = { + 0x4d, 0x03, 0x00, 0x97, 0x05, 0x20, 0xc6, 0x05, + 0x00, 0xe7, 0x06, 0x00, 0x45, 0x07, 0x00, 0x9c, + 0x08, 0x00, 0x4d, 0x09, 0x00, 0x3c, 0x0b, 0x00, + 0x3d, 0x0d, 0x00, 0x36, 0x0f, 0x00, 0x38, 0x10, + 0x20, 0x3a, 0x19, 0x00, 0xcb, 0x1a, 0x20, 0xd3, + 0x1c, 0x00, 0xcf, 0x1d, 0x00, 0xe2, 0x20, 0x00, + 0x2e, 0x30, 0x20, 0x2b, 0xa9, 0x20, 0xed, 0xab, + 0x00, 0x39, 0x0a, 0x01, 0x4c, 0x0f, 0x01, 0x35, + 0x11, 0x21, 0x66, 0x13, 0x01, 0x40, 0x16, 0x01, + 0x47, 0x1a, 0x01, 0xf0, 0x6a, 0x21, 0x8a, 0xd1, + 0x01, 0xec, 0xe4, 0x21, 0x4b, 0xe9, 0x01, +}; + +static const uint32_t unicode_decomp_table1[709] = { + 0x00280081, 0x002a0097, 0x002a8081, 0x002bc097, + 0x002c8115, 0x002d0097, 0x002d4081, 0x002e0097, + 0x002e4115, 0x002f0199, 0x00302016, 0x00400842, + 0x00448a42, 0x004a0442, 0x004c0096, 0x004c8117, + 0x004d0242, 0x004e4342, 0x004fc12f, 0x0050c342, + 0x005240bf, 0x00530342, 0x00550942, 0x005a0842, + 0x005e0096, 0x005e4342, 0x005fc081, 0x00680142, + 0x006bc142, 0x00710185, 0x0071c317, 0x00734844, + 0x00778344, 0x00798342, 0x007b02be, 0x007c4197, + 0x007d0142, 0x007e0444, 0x00800e42, 0x00878142, + 0x00898744, 0x00ac0483, 0x00b60317, 0x00b80283, + 0x00d00214, 0x00d10096, 0x00dd0080, 0x00de8097, + 0x00df8080, 0x00e10097, 0x00e1413e, 0x00e1c080, + 0x00e204be, 0x00ea83ae, 0x00f282ae, 0x00f401ad, + 0x00f4c12e, 0x00f54103, 0x00fc0303, 0x00fe4081, + 0x0100023e, 0x0101c0be, 0x010301be, 0x010640be, + 0x010e40be, 0x0114023e, 0x0115c0be, 0x011701be, + 0x011d8144, 0x01304144, 0x01340244, 0x01358144, + 0x01368344, 0x01388344, 0x013a8644, 0x013e0144, + 0x0161c085, 0x018882ae, 0x019d422f, 0x01b00184, + 0x01b4c084, 0x024a4084, 0x024c4084, 0x024d0084, + 0x0256042e, 0x0272c12e, 0x02770120, 0x0277c084, + 0x028cc084, 0x028d8084, 0x029641ae, 0x02978084, + 0x02d20084, 0x02d2c12e, 0x02d70120, 0x02e50084, + 0x02f281ae, 0x03120084, 0x03300084, 0x0331c122, + 0x0332812e, 0x035281ae, 0x03768084, 0x037701ae, + 0x038cc085, 0x03acc085, 0x03b7012f, 0x03c30081, + 0x03d0c084, 0x03d34084, 0x03d48084, 0x03d5c084, + 0x03d70084, 0x03da4084, 0x03dcc084, 0x03dd412e, + 0x03ddc085, 0x03de0084, 0x03de4085, 0x03e04084, + 0x03e4c084, 0x03e74084, 0x03e88084, 0x03e9c084, + 0x03eb0084, 0x03ee4084, 0x04098084, 0x043f0081, + 0x06c18484, 0x06c48084, 0x06cec184, 0x06d00120, + 0x06d0c084, 0x074b0383, 0x074cc41f, 0x074f1783, + 0x075e0081, 0x0766d283, 0x07801d44, 0x078e8942, + 0x07931844, 0x079f0d42, 0x07a58216, 0x07a68085, + 0x07a6c0be, 0x07a80d44, 0x07aea044, 0x07c00122, + 0x07c08344, 0x07c20122, 0x07c28344, 0x07c40122, + 0x07c48244, 0x07c60122, 0x07c68244, 0x07c8113e, + 0x07d08244, 0x07d20122, 0x07d28244, 0x07d40122, + 0x07d48344, 0x07d64c3e, 0x07dc4080, 0x07dc80be, + 0x07dcc080, 0x07dd00be, 0x07dd4080, 0x07dd80be, + 0x07ddc080, 0x07de00be, 0x07de4080, 0x07de80be, + 0x07dec080, 0x07df00be, 0x07df4080, 0x07e00820, + 0x07e40820, 0x07e80820, 0x07ec05be, 0x07eec080, + 0x07ef00be, 0x07ef4097, 0x07ef8080, 0x07efc117, + 0x07f0443e, 0x07f24080, 0x07f280be, 0x07f2c080, + 0x07f303be, 0x07f4c080, 0x07f582ae, 0x07f6c080, + 0x07f7433e, 0x07f8c080, 0x07f903ae, 0x07fac080, + 0x07fb013e, 0x07fb8102, 0x07fc83be, 0x07fe4080, + 0x07fe80be, 0x07fec080, 0x07ff00be, 0x07ff4080, + 0x07ff8097, 0x0800011e, 0x08008495, 0x08044081, + 0x0805c097, 0x08090081, 0x08094097, 0x08098099, + 0x080bc081, 0x080cc085, 0x080d00b1, 0x080d8085, + 0x080dc0b1, 0x080f0197, 0x0811c197, 0x0815c0b3, + 0x0817c081, 0x081c0595, 0x081ec081, 0x081f0215, + 0x0820051f, 0x08228583, 0x08254415, 0x082a0097, + 0x08400119, 0x08408081, 0x0840c0bf, 0x08414119, + 0x0841c081, 0x084240bf, 0x0842852d, 0x08454081, + 0x08458097, 0x08464295, 0x08480097, 0x08484099, + 0x08488097, 0x08490081, 0x08498080, 0x084a0081, + 0x084a8102, 0x084b0495, 0x084d421f, 0x084e4081, + 0x084ec099, 0x084f0283, 0x08514295, 0x08540119, + 0x0854809b, 0x0854c619, 0x0857c097, 0x08580081, + 0x08584097, 0x08588099, 0x0858c097, 0x08590081, + 0x08594097, 0x08598099, 0x0859c09b, 0x085a0097, + 0x085a4081, 0x085a8097, 0x085ac099, 0x085b0295, + 0x085c4097, 0x085c8099, 0x085cc097, 0x085d0081, + 0x085d4097, 0x085d8099, 0x085dc09b, 0x085e0097, + 0x085e4081, 0x085e8097, 0x085ec099, 0x085f0215, + 0x08624099, 0x0866813e, 0x086b80be, 0x087341be, + 0x088100be, 0x088240be, 0x088300be, 0x088901be, + 0x088b0085, 0x088b40b1, 0x088bc085, 0x088c00b1, + 0x089040be, 0x089100be, 0x0891c1be, 0x089801be, + 0x089b42be, 0x089d0144, 0x089e0144, 0x08a00144, + 0x08a10144, 0x08a20144, 0x08ab023e, 0x08b80244, + 0x08ba8220, 0x08ca411e, 0x0918049f, 0x091a4523, + 0x091cc097, 0x091d04a5, 0x091f452b, 0x0921c09b, + 0x092204a1, 0x09244525, 0x0926c099, 0x09270d25, + 0x092d8d1f, 0x09340d1f, 0x093a8081, 0x0a8300b3, + 0x0a9d0099, 0x0a9d4097, 0x0a9d8099, 0x0ab700be, + 0x0b1f0115, 0x0b5bc081, 0x0ba7c081, 0x0bbcc081, + 0x0bc004ad, 0x0bc244ad, 0x0bc484ad, 0x0bc6f383, + 0x0be0852d, 0x0be31d03, 0x0bf1882d, 0x0c000081, + 0x0c0d8283, 0x0c130b84, 0x0c194284, 0x0c1c0122, + 0x0c1cc122, 0x0c1d8122, 0x0c1e4122, 0x0c1f0122, + 0x0c250084, 0x0c26c123, 0x0c278084, 0x0c27c085, + 0x0c2b0b84, 0x0c314284, 0x0c340122, 0x0c34c122, + 0x0c358122, 0x0c364122, 0x0c370122, 0x0c3d0084, + 0x0c3dc220, 0x0c3f8084, 0x0c3fc085, 0x0c4c4a2d, + 0x0c51451f, 0x0c53ca9f, 0x0c5915ad, 0x0c648703, + 0x0c800741, 0x0c838089, 0x0c83c129, 0x0c8441a9, + 0x0c850089, 0x0c854129, 0x0c85c2a9, 0x0c870089, + 0x0c87408f, 0x0c87808d, 0x0c881241, 0x0c910203, + 0x0c940099, 0x0c9444a3, 0x0c968323, 0x0c98072d, + 0x0c9b84af, 0x0c9dc2a1, 0x0c9f00b5, 0x0c9f40b3, + 0x0c9f8085, 0x0ca01883, 0x0cac4223, 0x0cad4523, + 0x0cafc097, 0x0cb004a1, 0x0cb241a5, 0x0cb30097, + 0x0cb34099, 0x0cb38097, 0x0cb3c099, 0x0cb417ad, + 0x0cbfc085, 0x0cc001b3, 0x0cc0c0b1, 0x0cc100b3, + 0x0cc14131, 0x0cc1c0b5, 0x0cc200b3, 0x0cc241b1, + 0x0cc30133, 0x0cc38131, 0x0cc40085, 0x0cc440b1, + 0x0cc48133, 0x0cc50085, 0x0cc540b5, 0x0cc580b7, + 0x0cc5c0b5, 0x0cc600b1, 0x0cc64135, 0x0cc6c0b3, + 0x0cc701b1, 0x0cc7c0b3, 0x0cc800b5, 0x0cc840b3, + 0x0cc881b1, 0x0cc9422f, 0x0cca4131, 0x0ccac0b5, + 0x0ccb00b1, 0x0ccb40b3, 0x0ccb80b5, 0x0ccbc0b1, + 0x0ccc012f, 0x0ccc80b5, 0x0cccc0b3, 0x0ccd00b5, + 0x0ccd40b1, 0x0ccd80b5, 0x0ccdc085, 0x0cce02b1, + 0x0ccf40b3, 0x0ccf80b1, 0x0ccfc085, 0x0cd001b1, + 0x0cd0c0b3, 0x0cd101b1, 0x0cd1c0b5, 0x0cd200b3, + 0x0cd24085, 0x0cd280b5, 0x0cd2c085, 0x0cd30133, + 0x0cd381b1, 0x0cd440b3, 0x0cd48085, 0x0cd4c0b1, + 0x0cd500b3, 0x0cd54085, 0x0cd580b5, 0x0cd5c0b1, + 0x0cd60521, 0x0cd88525, 0x0cdb02a5, 0x0cdc4099, + 0x0cdc8117, 0x0cdd0099, 0x0cdd4197, 0x0cde0127, + 0x0cde8285, 0x0cdfc089, 0x0ce0043f, 0x0ce20099, + 0x0ce2409b, 0x0ce283bf, 0x0ce44219, 0x0ce54205, + 0x0ce6433f, 0x0ce7c131, 0x0ce84085, 0x0ce881b1, + 0x0ce94085, 0x0ce98107, 0x0cea0089, 0x0cea4097, + 0x0cea8219, 0x0ceb809d, 0x0cebc08d, 0x0cec083f, + 0x0cf00105, 0x0cf0809b, 0x0cf0c197, 0x0cf1809b, + 0x0cf1c099, 0x0cf20517, 0x0cf48099, 0x0cf4c117, + 0x0cf54119, 0x0cf5c097, 0x0cf6009b, 0x0cf64099, + 0x0cf68217, 0x0cf78119, 0x0cf804a1, 0x0cfa4525, + 0x0cfcc525, 0x0cff4125, 0x0cffc099, 0x29a70103, + 0x29dc0081, 0x29fc8195, 0x29fe0103, 0x2ad70203, + 0x2ada4081, 0x3e401482, 0x3e4a7f82, 0x3e6a3f82, + 0x3e8aa102, 0x3e9b0110, 0x3e9c2f82, 0x3eb3c590, + 0x3ec00197, 0x3ec0c119, 0x3ec1413f, 0x3ec4c2af, + 0x3ec74184, 0x3ec804ad, 0x3eca4081, 0x3eca8304, + 0x3ecc03a0, 0x3ece02a0, 0x3ecf8084, 0x3ed00120, + 0x3ed0c120, 0x3ed184ae, 0x3ed3c085, 0x3ed4312d, + 0x3ef4cbad, 0x3efa892f, 0x3eff022d, 0x3f002f2f, + 0x3f1782a5, 0x3f18c0b1, 0x3f1907af, 0x3f1cffaf, + 0x3f3c81a5, 0x3f3d64af, 0x3f542031, 0x3f649b31, + 0x3f7c0131, 0x3f7c83b3, 0x3f7e40b1, 0x3f7e80bd, + 0x3f7ec0bb, 0x3f7f00b3, 0x3f840503, 0x3f8c01ad, + 0x3f8cc315, 0x3f8e462d, 0x3f91cc03, 0x3f97c695, + 0x3f9c01af, 0x3f9d0085, 0x3f9d852f, 0x3fa03aad, + 0x3fbd442f, 0x3fc06f1f, 0x3fd7c11f, 0x3fd85fad, + 0x3fe80081, 0x3fe84f1f, 0x3ff0831f, 0x3ff2831f, + 0x3ff4831f, 0x3ff6819f, 0x3ff80783, 0x41724092, + 0x41790092, 0x41e04d83, 0x41e70f91, 0x44268192, + 0x442ac092, 0x444b8112, 0x44d2c112, 0x44e0c192, + 0x44e38092, 0x44e44092, 0x44f14212, 0x452ec212, + 0x456e8112, 0x464e0092, 0x58484412, 0x5b5a0192, + 0x73358d1f, 0x733c051f, 0x74578392, 0x746ec312, + 0x75000d1f, 0x75068d1f, 0x750d0d1f, 0x7513839f, + 0x7515891f, 0x751a0d1f, 0x75208d1f, 0x75271015, + 0x752f439f, 0x7531459f, 0x75340d1f, 0x753a8d1f, + 0x75410395, 0x7543441f, 0x7545839f, 0x75478d1f, + 0x754e0795, 0x7552839f, 0x75548d1f, 0x755b0d1f, + 0x75618d1f, 0x75680d1f, 0x756e8d1f, 0x75750d1f, + 0x757b8d1f, 0x75820d1f, 0x75888d1f, 0x758f0d1f, + 0x75958d1f, 0x759c0d1f, 0x75a28d1f, 0x75a90103, + 0x75aa089f, 0x75ae4081, 0x75ae839f, 0x75b04081, + 0x75b08c9f, 0x75b6c081, 0x75b7032d, 0x75b8889f, + 0x75bcc081, 0x75bd039f, 0x75bec081, 0x75bf0c9f, + 0x75c54081, 0x75c5832d, 0x75c7089f, 0x75cb4081, + 0x75cb839f, 0x75cd4081, 0x75cd8c9f, 0x75d3c081, + 0x75d4032d, 0x75d5889f, 0x75d9c081, 0x75da039f, + 0x75dbc081, 0x75dc0c9f, 0x75e24081, 0x75e2832d, + 0x75e4089f, 0x75e84081, 0x75e8839f, 0x75ea4081, + 0x75ea8c9f, 0x75f0c081, 0x75f1042d, 0x75f3851f, + 0x75f6051f, 0x75f8851f, 0x75fb051f, 0x75fd851f, + 0x780c049f, 0x780e419f, 0x780f059f, 0x7811c203, + 0x7812d0ad, 0x781b0103, 0x7b80022d, 0x7b814dad, + 0x7b884203, 0x7b89c081, 0x7b8a452d, 0x7b8d0403, + 0x7b908081, 0x7b91dc03, 0x7ba0052d, 0x7ba2c8ad, + 0x7ba84483, 0x7baac8ad, 0x7c400097, 0x7c404521, + 0x7c440d25, 0x7c4a8087, 0x7c4ac115, 0x7c4b4117, + 0x7c4c0d1f, 0x7c528217, 0x7c538099, 0x7c53c097, + 0x7c5a8197, 0x7c640097, 0x7c80012f, 0x7c808081, + 0x7c841603, 0x7c9004c1, 0x7c940103, 0x7efc051f, + 0xbe0001ac, 0xbe00d110, 0xbe0947ac, 0xbe0d3910, + 0xbe29872c, 0xbe2d022c, 0xbe2e3790, 0xbe49ff90, + 0xbe69bc10, +}; + +static const uint16_t unicode_decomp_table2[709] = { + 0x0020, 0x0000, 0x0061, 0x0002, 0x0004, 0x0006, 0x03bc, 0x0008, + 0x000a, 0x000c, 0x0015, 0x0095, 0x00a5, 0x00b9, 0x00c1, 0x00c3, + 0x00c7, 0x00cb, 0x00d1, 0x00d7, 0x00dd, 0x00e0, 0x00e6, 0x00f8, + 0x0108, 0x010a, 0x0073, 0x0110, 0x0112, 0x0114, 0x0120, 0x012c, + 0x0144, 0x014d, 0x0153, 0x0162, 0x0168, 0x016a, 0x0176, 0x0192, + 0x0194, 0x01a9, 0x01bb, 0x01c7, 0x01d1, 0x01d5, 0x02b9, 0x01d7, + 0x003b, 0x01d9, 0x01db, 0x00b7, 0x01e1, 0x01fc, 0x020c, 0x0218, + 0x021d, 0x0223, 0x0227, 0x03a3, 0x0233, 0x023f, 0x0242, 0x024b, + 0x024e, 0x0251, 0x025d, 0x0260, 0x0269, 0x026c, 0x026f, 0x0275, + 0x0278, 0x0281, 0x028a, 0x029c, 0x029f, 0x02a3, 0x02af, 0x02b9, + 0x02c5, 0x02c9, 0x02cd, 0x02d1, 0x02d5, 0x02e7, 0x02ed, 0x02f1, + 0x02f5, 0x02f9, 0x02fd, 0x0305, 0x0309, 0x030d, 0x0313, 0x0317, + 0x031b, 0x0323, 0x0327, 0x032b, 0x032f, 0x0335, 0x033d, 0x0341, + 0x0349, 0x034d, 0x0351, 0x0f0b, 0x0357, 0x035b, 0x035f, 0x0363, + 0x0367, 0x036b, 0x036f, 0x0373, 0x0379, 0x037d, 0x0381, 0x0385, + 0x0389, 0x038d, 0x0391, 0x0395, 0x0399, 0x039d, 0x03a1, 0x10dc, + 0x03a5, 0x03c9, 0x03cd, 0x03d9, 0x03dd, 0x03e1, 0x03ef, 0x03f1, + 0x043d, 0x044f, 0x0499, 0x04f0, 0x0502, 0x054a, 0x0564, 0x056c, + 0x0570, 0x0573, 0x059a, 0x05fa, 0x05fe, 0x0607, 0x060b, 0x0614, + 0x0618, 0x061e, 0x0622, 0x0628, 0x068e, 0x0694, 0x0698, 0x069e, + 0x06a2, 0x06ab, 0x03ac, 0x06f3, 0x03ad, 0x06f6, 0x03ae, 0x06f9, + 0x03af, 0x06fc, 0x03cc, 0x06ff, 0x03cd, 0x0702, 0x03ce, 0x0705, + 0x0709, 0x070d, 0x0711, 0x0386, 0x0732, 0x0735, 0x03b9, 0x0737, + 0x073b, 0x0388, 0x0753, 0x0389, 0x0756, 0x0390, 0x076b, 0x038a, + 0x0777, 0x03b0, 0x0789, 0x038e, 0x0799, 0x079f, 0x07a3, 0x038c, + 0x07b8, 0x038f, 0x07bb, 0x00b4, 0x07be, 0x07c0, 0x07c2, 0x2010, + 0x07cb, 0x002e, 0x07cd, 0x07cf, 0x0020, 0x07d2, 0x07d6, 0x07db, + 0x07df, 0x07e4, 0x07ea, 0x07f0, 0x0020, 0x07f6, 0x2212, 0x0801, + 0x0805, 0x0807, 0x081d, 0x0825, 0x0827, 0x0043, 0x082d, 0x0830, + 0x0190, 0x0836, 0x0839, 0x004e, 0x0845, 0x0847, 0x084c, 0x084e, + 0x0851, 0x005a, 0x03a9, 0x005a, 0x0853, 0x0857, 0x0860, 0x0069, + 0x0862, 0x0865, 0x086f, 0x0874, 0x087a, 0x087e, 0x08a2, 0x0049, + 0x08a4, 0x08a6, 0x08a9, 0x0056, 0x08ab, 0x08ad, 0x08b0, 0x08b4, + 0x0058, 0x08b6, 0x08b8, 0x08bb, 0x08c0, 0x08c2, 0x08c5, 0x0076, + 0x08c7, 0x08c9, 0x08cc, 0x08d0, 0x0078, 0x08d2, 0x08d4, 0x08d7, + 0x08db, 0x08de, 0x08e4, 0x08e7, 0x08f0, 0x08f3, 0x08f6, 0x08f9, + 0x0902, 0x0906, 0x090b, 0x090f, 0x0914, 0x0917, 0x091a, 0x0923, + 0x092c, 0x093b, 0x093e, 0x0941, 0x0944, 0x0947, 0x094a, 0x0956, + 0x095c, 0x0960, 0x0962, 0x0964, 0x0968, 0x096a, 0x0970, 0x0978, + 0x097c, 0x0980, 0x0986, 0x0989, 0x098f, 0x0991, 0x0030, 0x0993, + 0x0999, 0x099c, 0x099e, 0x09a1, 0x09a4, 0x2d61, 0x6bcd, 0x9f9f, + 0x09a6, 0x09b1, 0x09bc, 0x09c7, 0x0a95, 0x0aa1, 0x0b15, 0x0020, + 0x0b27, 0x0b31, 0x0b8d, 0x0ba1, 0x0ba5, 0x0ba9, 0x0bad, 0x0bb1, + 0x0bb5, 0x0bb9, 0x0bbd, 0x0bc1, 0x0bc5, 0x0c21, 0x0c35, 0x0c39, + 0x0c3d, 0x0c41, 0x0c45, 0x0c49, 0x0c4d, 0x0c51, 0x0c55, 0x0c59, + 0x0c6f, 0x0c71, 0x0c73, 0x0ca0, 0x0cbc, 0x0cdc, 0x0ce4, 0x0cec, + 0x0cf4, 0x0cfc, 0x0d04, 0x0d0c, 0x0d14, 0x0d22, 0x0d2e, 0x0d7a, + 0x0d82, 0x0d85, 0x0d89, 0x0d8d, 0x0d9d, 0x0db1, 0x0db5, 0x0dbc, + 0x0dc2, 0x0dc6, 0x0e28, 0x0e2c, 0x0e30, 0x0e32, 0x0e36, 0x0e3c, + 0x0e3e, 0x0e41, 0x0e43, 0x0e46, 0x0e77, 0x0e7b, 0x0e89, 0x0e8e, + 0x0e94, 0x0e9c, 0x0ea3, 0x0ea9, 0x0eb4, 0x0ebe, 0x0ec6, 0x0eca, + 0x0ecf, 0x0ed9, 0x0edd, 0x0ee4, 0x0eec, 0x0ef3, 0x0ef8, 0x0f04, + 0x0f0a, 0x0f15, 0x0f1b, 0x0f22, 0x0f28, 0x0f33, 0x0f3d, 0x0f45, + 0x0f4c, 0x0f51, 0x0f57, 0x0f5e, 0x0f63, 0x0f69, 0x0f70, 0x0f76, + 0x0f7d, 0x0f82, 0x0f89, 0x0f8d, 0x0f9e, 0x0fa4, 0x0fa9, 0x0fad, + 0x0fb8, 0x0fbe, 0x0fc9, 0x0fd0, 0x0fd6, 0x0fda, 0x0fe1, 0x0fe5, + 0x0fef, 0x0ffa, 0x1000, 0x1004, 0x1009, 0x100f, 0x1013, 0x101a, + 0x101f, 0x1023, 0x1029, 0x102f, 0x1032, 0x1036, 0x1039, 0x103f, + 0x1045, 0x1059, 0x1061, 0x1079, 0x107c, 0x1080, 0x1095, 0x10a1, + 0x10b1, 0x10c3, 0x10cb, 0x10cf, 0x10da, 0x10de, 0x10ea, 0x10f2, + 0x10f4, 0x1100, 0x1105, 0x1111, 0x1141, 0x1149, 0x114d, 0x1153, + 0x1157, 0x115a, 0x116e, 0x1171, 0x1175, 0x117b, 0x117d, 0x1181, + 0x1184, 0x118c, 0x1192, 0x1196, 0x119c, 0x11a2, 0x11a8, 0x11ab, + 0xa76f, 0x11af, 0x11b2, 0x11b6, 0x028d, 0x11be, 0x1210, 0x130e, + 0x140c, 0x1490, 0x1495, 0x1553, 0x156c, 0x1572, 0x1578, 0x157e, + 0x158a, 0x1596, 0x002b, 0x15a1, 0x15b9, 0x15bd, 0x15c1, 0x15c5, + 0x15c9, 0x15cd, 0x15e1, 0x15e5, 0x1649, 0x1662, 0x1688, 0x168e, + 0x174c, 0x1752, 0x1757, 0x1777, 0x1877, 0x187d, 0x1911, 0x19d3, + 0x1a77, 0x1a7f, 0x1a9d, 0x1aa2, 0x1ab6, 0x1ac0, 0x1ac6, 0x1ada, + 0x1adf, 0x1ae5, 0x1af3, 0x1b23, 0x1b30, 0x1b38, 0x1b3c, 0x1b52, + 0x1bc9, 0x1bdb, 0x1bdd, 0x1bdf, 0x3164, 0x1c20, 0x1c22, 0x1c24, + 0x1c26, 0x1c28, 0x1c2a, 0x1c48, 0x1c4d, 0x1c52, 0x1c88, 0x1cce, + 0x1cdc, 0x1ce1, 0x1cea, 0x1cf3, 0x1d01, 0x1d06, 0x1d0b, 0x1d1d, + 0x1d2f, 0x1d38, 0x1d3d, 0x1d61, 0x1d6f, 0x1d71, 0x1d73, 0x1d93, + 0x1dae, 0x1db0, 0x1db2, 0x1db4, 0x1db6, 0x1db8, 0x1dba, 0x1dbc, + 0x1ddc, 0x1dde, 0x1de0, 0x1de2, 0x1de4, 0x1deb, 0x1ded, 0x1def, + 0x1df1, 0x1e00, 0x1e02, 0x1e04, 0x1e06, 0x1e08, 0x1e0a, 0x1e0c, + 0x1e0e, 0x1e10, 0x1e12, 0x1e14, 0x1e16, 0x1e18, 0x1e1a, 0x1e1c, + 0x1e20, 0x03f4, 0x1e22, 0x2207, 0x1e24, 0x2202, 0x1e26, 0x1e2e, + 0x03f4, 0x1e30, 0x2207, 0x1e32, 0x2202, 0x1e34, 0x1e3c, 0x03f4, + 0x1e3e, 0x2207, 0x1e40, 0x2202, 0x1e42, 0x1e4a, 0x03f4, 0x1e4c, + 0x2207, 0x1e4e, 0x2202, 0x1e50, 0x1e58, 0x03f4, 0x1e5a, 0x2207, + 0x1e5c, 0x2202, 0x1e5e, 0x1e68, 0x1e6a, 0x1e6c, 0x1e6e, 0x1e70, + 0x1e72, 0x1e74, 0x1e76, 0x1e78, 0x1e80, 0x1ea3, 0x1ea7, 0x1ead, + 0x1eca, 0x062d, 0x1ed2, 0x1ede, 0x062c, 0x1eee, 0x1f5e, 0x1f6a, + 0x1f7d, 0x1f8f, 0x1fa2, 0x1fa4, 0x1fa8, 0x1fae, 0x1fb4, 0x1fb6, + 0x1fba, 0x1fbc, 0x1fc4, 0x1fc7, 0x1fc9, 0x1fcf, 0x1fd1, 0x30b5, + 0x1fd7, 0x202f, 0x2045, 0x2049, 0x204b, 0x2050, 0x209d, 0x20ae, + 0x21af, 0x21bf, 0x21c5, 0x22bf, 0x23dd, +}; + +static const uint8_t unicode_decomp_data[9451] = { + 0x20, 0x88, 0x20, 0x84, 0x32, 0x33, 0x20, 0x81, + 0x20, 0xa7, 0x31, 0x6f, 0x31, 0xd0, 0x34, 0x31, + 0xd0, 0x32, 0x33, 0xd0, 0x34, 0x41, 0x80, 0x41, + 0x81, 0x41, 0x82, 0x41, 0x83, 0x41, 0x88, 0x41, + 0x8a, 0x00, 0x00, 0x43, 0xa7, 0x45, 0x80, 0x45, + 0x81, 0x45, 0x82, 0x45, 0x88, 0x49, 0x80, 0x49, + 0x81, 0x49, 0x82, 0x49, 0x88, 0x00, 0x00, 0x4e, + 0x83, 0x4f, 0x80, 0x4f, 0x81, 0x4f, 0x82, 0x4f, + 0x83, 0x4f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x55, + 0x80, 0x55, 0x81, 0x55, 0x82, 0x55, 0x88, 0x59, + 0x81, 0x00, 0x00, 0x00, 0x00, 0x61, 0x80, 0x61, + 0x81, 0x61, 0x82, 0x61, 0x83, 0x61, 0x88, 0x61, + 0x8a, 0x00, 0x00, 0x63, 0xa7, 0x65, 0x80, 0x65, + 0x81, 0x65, 0x82, 0x65, 0x88, 0x69, 0x80, 0x69, + 0x81, 0x69, 0x82, 0x69, 0x88, 0x00, 0x00, 0x6e, + 0x83, 0x6f, 0x80, 0x6f, 0x81, 0x6f, 0x82, 0x6f, + 0x83, 0x6f, 0x88, 0x00, 0x00, 0x00, 0x00, 0x75, + 0x80, 0x75, 0x81, 0x75, 0x82, 0x75, 0x88, 0x79, + 0x81, 0x00, 0x00, 0x79, 0x88, 0x41, 0x84, 0x41, + 0x86, 0x41, 0xa8, 0x43, 0x81, 0x43, 0x82, 0x43, + 0x87, 0x43, 0x8c, 0x44, 0x8c, 0x45, 0x84, 0x45, + 0x86, 0x45, 0x87, 0x45, 0xa8, 0x45, 0x8c, 0x47, + 0x82, 0x47, 0x86, 0x47, 0x87, 0x47, 0xa7, 0x48, + 0x82, 0x49, 0x83, 0x49, 0x84, 0x49, 0x86, 0x49, + 0xa8, 0x49, 0x87, 0x49, 0x4a, 0x69, 0x6a, 0x4a, + 0x82, 0x4b, 0xa7, 0x4c, 0x81, 0x4c, 0xa7, 0x4c, + 0x8c, 0x4c, 0x00, 0x00, 0x6b, 0x20, 0x6b, 0x4e, + 0x81, 0x4e, 0xa7, 0x4e, 0x8c, 0xbc, 0x02, 0x6e, + 0x4f, 0x84, 0x4f, 0x86, 0x4f, 0x8b, 0x52, 0x81, + 0x52, 0xa7, 0x52, 0x8c, 0x53, 0x81, 0x53, 0x82, + 0x53, 0xa7, 0x53, 0x8c, 0x54, 0xa7, 0x54, 0x8c, + 0x55, 0x83, 0x55, 0x84, 0x55, 0x86, 0x55, 0x8a, + 0x55, 0x8b, 0x55, 0xa8, 0x57, 0x82, 0x59, 0x82, + 0x59, 0x88, 0x5a, 0x81, 0x5a, 0x87, 0x5a, 0x8c, + 0x4f, 0x9b, 0x55, 0x9b, 0x44, 0x00, 0x7d, 0x01, + 0x44, 0x00, 0x7e, 0x01, 0x64, 0x00, 0x7e, 0x01, + 0x4c, 0x4a, 0x4c, 0x6a, 0x6c, 0x6a, 0x4e, 0x4a, + 0x4e, 0x6a, 0x6e, 0x6a, 0x41, 0x00, 0x8c, 0x49, + 0x00, 0x8c, 0x4f, 0x00, 0x8c, 0x55, 0x00, 0x8c, + 0xdc, 0x00, 0x84, 0xdc, 0x00, 0x81, 0xdc, 0x00, + 0x8c, 0xdc, 0x00, 0x80, 0xc4, 0x00, 0x84, 0x26, + 0x02, 0x84, 0xc6, 0x00, 0x84, 0x47, 0x8c, 0x4b, + 0x8c, 0x4f, 0xa8, 0xea, 0x01, 0x84, 0xeb, 0x01, + 0x84, 0xb7, 0x01, 0x8c, 0x92, 0x02, 0x8c, 0x6a, + 0x00, 0x8c, 0x44, 0x5a, 0x44, 0x7a, 0x64, 0x7a, + 0x47, 0x81, 0x4e, 0x00, 0x80, 0xc5, 0x00, 0x81, + 0xc6, 0x00, 0x81, 0xd8, 0x00, 0x81, 0x41, 0x8f, + 0x41, 0x91, 0x45, 0x8f, 0x45, 0x91, 0x49, 0x8f, + 0x49, 0x91, 0x4f, 0x8f, 0x4f, 0x91, 0x52, 0x8f, + 0x52, 0x91, 0x55, 0x8f, 0x55, 0x91, 0x53, 0xa6, + 0x54, 0xa6, 0x48, 0x8c, 0x41, 0x00, 0x87, 0x45, + 0x00, 0xa7, 0xd6, 0x00, 0x84, 0xd5, 0x00, 0x84, + 0x4f, 0x00, 0x87, 0x2e, 0x02, 0x84, 0x59, 0x00, + 0x84, 0x68, 0x00, 0x66, 0x02, 0x6a, 0x00, 0x72, + 0x00, 0x79, 0x02, 0x7b, 0x02, 0x81, 0x02, 0x77, + 0x00, 0x79, 0x00, 0x20, 0x86, 0x20, 0x87, 0x20, + 0x8a, 0x20, 0xa8, 0x20, 0x83, 0x20, 0x8b, 0x63, + 0x02, 0x6c, 0x00, 0x73, 0x00, 0x78, 0x00, 0x95, + 0x02, 0x80, 0x81, 0x00, 0x93, 0x88, 0x81, 0x20, + 0xc5, 0x20, 0x81, 0xa8, 0x00, 0x81, 0x91, 0x03, + 0x81, 0x95, 0x03, 0x81, 0x97, 0x03, 0x81, 0x99, + 0x03, 0x81, 0x00, 0x00, 0x00, 0x9f, 0x03, 0x81, + 0x00, 0x00, 0x00, 0xa5, 0x03, 0x81, 0xa9, 0x03, + 0x81, 0xca, 0x03, 0x81, 0x01, 0x03, 0x98, 0x07, + 0xa4, 0x07, 0xb0, 0x00, 0xb4, 0x00, 0xb6, 0x00, + 0xb8, 0x00, 0xca, 0x00, 0x01, 0x03, 0xb8, 0x07, + 0xc4, 0x07, 0xbe, 0x00, 0xc4, 0x00, 0xc8, 0x00, + 0xa5, 0x03, 0x0d, 0x13, 0x00, 0x01, 0x03, 0xd1, + 0x00, 0xd1, 0x07, 0xc6, 0x03, 0xc0, 0x03, 0xba, + 0x03, 0xc1, 0x03, 0xc2, 0x03, 0x00, 0x00, 0x98, + 0x03, 0xb5, 0x03, 0x15, 0x04, 0x80, 0x15, 0x04, + 0x88, 0x00, 0x00, 0x00, 0x13, 0x04, 0x81, 0x06, + 0x04, 0x88, 0x1a, 0x04, 0x81, 0x18, 0x04, 0x80, + 0x23, 0x04, 0x86, 0x18, 0x04, 0x86, 0x38, 0x04, + 0x86, 0x35, 0x04, 0x80, 0x35, 0x04, 0x88, 0x00, + 0x00, 0x00, 0x33, 0x04, 0x81, 0x56, 0x04, 0x88, + 0x3a, 0x04, 0x81, 0x38, 0x04, 0x80, 0x43, 0x04, + 0x86, 0x74, 0x04, 0x8f, 0x16, 0x04, 0x86, 0x10, + 0x04, 0x86, 0x10, 0x04, 0x88, 0x15, 0x04, 0x86, + 0xd8, 0x04, 0x88, 0x16, 0x04, 0x88, 0x17, 0x04, + 0x88, 0x18, 0x04, 0x84, 0x18, 0x04, 0x88, 0x1e, + 0x04, 0x88, 0xe8, 0x04, 0x88, 0x2d, 0x04, 0x88, + 0x23, 0x04, 0x84, 0x23, 0x04, 0x88, 0x23, 0x04, + 0x8b, 0x27, 0x04, 0x88, 0x2b, 0x04, 0x88, 0x65, + 0x05, 0x82, 0x05, 0x27, 0x06, 0x00, 0x2c, 0x00, + 0x2d, 0x21, 0x2d, 0x00, 0x2e, 0x23, 0x2d, 0x27, + 0x06, 0x00, 0x4d, 0x21, 0x4d, 0xa0, 0x4d, 0x23, + 0x4d, 0xd5, 0x06, 0x54, 0x06, 0x00, 0x00, 0x00, + 0x00, 0xc1, 0x06, 0x54, 0x06, 0xd2, 0x06, 0x54, + 0x06, 0x28, 0x09, 0x3c, 0x09, 0x30, 0x09, 0x3c, + 0x09, 0x33, 0x09, 0x3c, 0x09, 0x15, 0x09, 0x00, + 0x27, 0x01, 0x27, 0x02, 0x27, 0x07, 0x27, 0x0c, + 0x27, 0x0d, 0x27, 0x16, 0x27, 0x1a, 0x27, 0xbe, + 0x09, 0x09, 0x00, 0x09, 0x19, 0xa1, 0x09, 0xbc, + 0x09, 0xaf, 0x09, 0xbc, 0x09, 0x32, 0x0a, 0x3c, + 0x0a, 0x38, 0x0a, 0x3c, 0x0a, 0x16, 0x0a, 0x00, + 0x26, 0x01, 0x26, 0x06, 0x26, 0x2b, 0x0a, 0x3c, + 0x0a, 0x47, 0x0b, 0x56, 0x0b, 0x3e, 0x0b, 0x09, + 0x00, 0x09, 0x19, 0x21, 0x0b, 0x3c, 0x0b, 0x92, + 0x0b, 0xd7, 0x0b, 0xbe, 0x0b, 0x08, 0x00, 0x09, + 0x00, 0x08, 0x19, 0x46, 0x0c, 0x56, 0x0c, 0xbf, + 0x0c, 0xd5, 0x0c, 0xc6, 0x0c, 0xd5, 0x0c, 0xc2, + 0x0c, 0x04, 0x00, 0x08, 0x13, 0x3e, 0x0d, 0x08, + 0x00, 0x09, 0x00, 0x08, 0x19, 0xd9, 0x0d, 0xca, + 0x0d, 0xca, 0x0d, 0x0f, 0x05, 0x12, 0x00, 0x0f, + 0x15, 0x4d, 0x0e, 0x32, 0x0e, 0xcd, 0x0e, 0xb2, + 0x0e, 0x99, 0x0e, 0x12, 0x00, 0x12, 0x08, 0x42, + 0x0f, 0xb7, 0x0f, 0x4c, 0x0f, 0xb7, 0x0f, 0x51, + 0x0f, 0xb7, 0x0f, 0x56, 0x0f, 0xb7, 0x0f, 0x5b, + 0x0f, 0xb7, 0x0f, 0x40, 0x0f, 0xb5, 0x0f, 0x71, + 0x0f, 0x72, 0x0f, 0x71, 0x0f, 0x00, 0x03, 0x41, + 0x0f, 0xb2, 0x0f, 0x81, 0x0f, 0xb3, 0x0f, 0x80, + 0x0f, 0xb3, 0x0f, 0x81, 0x0f, 0x71, 0x0f, 0x80, + 0x0f, 0x92, 0x0f, 0xb7, 0x0f, 0x9c, 0x0f, 0xb7, + 0x0f, 0xa1, 0x0f, 0xb7, 0x0f, 0xa6, 0x0f, 0xb7, + 0x0f, 0xab, 0x0f, 0xb7, 0x0f, 0x90, 0x0f, 0xb5, + 0x0f, 0x25, 0x10, 0x2e, 0x10, 0x05, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x07, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x09, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x1b, 0x35, + 0x1b, 0x11, 0x1b, 0x35, 0x1b, 0x3a, 0x1b, 0x35, + 0x1b, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x1b, 0x35, + 0x1b, 0x3e, 0x1b, 0x35, 0x1b, 0x42, 0x1b, 0x35, + 0x1b, 0x41, 0x00, 0xc6, 0x00, 0x42, 0x00, 0x00, + 0x00, 0x44, 0x00, 0x45, 0x00, 0x8e, 0x01, 0x47, + 0x00, 0x4f, 0x00, 0x22, 0x02, 0x50, 0x00, 0x52, + 0x00, 0x54, 0x00, 0x55, 0x00, 0x57, 0x00, 0x61, + 0x00, 0x50, 0x02, 0x51, 0x02, 0x02, 0x1d, 0x62, + 0x00, 0x64, 0x00, 0x65, 0x00, 0x59, 0x02, 0x5b, + 0x02, 0x5c, 0x02, 0x67, 0x00, 0x00, 0x00, 0x6b, + 0x00, 0x6d, 0x00, 0x4b, 0x01, 0x6f, 0x00, 0x54, + 0x02, 0x16, 0x1d, 0x17, 0x1d, 0x70, 0x00, 0x74, + 0x00, 0x75, 0x00, 0x1d, 0x1d, 0x6f, 0x02, 0x76, + 0x00, 0x25, 0x1d, 0xb2, 0x03, 0xb3, 0x03, 0xb4, + 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x69, 0x00, 0x72, + 0x00, 0x75, 0x00, 0x76, 0x00, 0xb2, 0x03, 0xb3, + 0x03, 0xc1, 0x03, 0xc6, 0x03, 0xc7, 0x03, 0x52, + 0x02, 0x63, 0x00, 0x55, 0x02, 0xf0, 0x00, 0x5c, + 0x02, 0x66, 0x00, 0x5f, 0x02, 0x61, 0x02, 0x65, + 0x02, 0x68, 0x02, 0x69, 0x02, 0x6a, 0x02, 0x7b, + 0x1d, 0x9d, 0x02, 0x6d, 0x02, 0x85, 0x1d, 0x9f, + 0x02, 0x71, 0x02, 0x70, 0x02, 0x72, 0x02, 0x73, + 0x02, 0x74, 0x02, 0x75, 0x02, 0x78, 0x02, 0x82, + 0x02, 0x83, 0x02, 0xab, 0x01, 0x89, 0x02, 0x8a, + 0x02, 0x1c, 0x1d, 0x8b, 0x02, 0x8c, 0x02, 0x7a, + 0x00, 0x90, 0x02, 0x91, 0x02, 0x92, 0x02, 0xb8, + 0x03, 0x41, 0x00, 0xa5, 0x42, 0x00, 0x87, 0x42, + 0x00, 0xa3, 0x42, 0x00, 0xb1, 0xc7, 0x00, 0x81, + 0x44, 0x00, 0x87, 0x44, 0x00, 0xa3, 0x44, 0x00, + 0xb1, 0x44, 0x00, 0xa7, 0x44, 0x00, 0xad, 0x12, + 0x01, 0x80, 0x12, 0x01, 0x81, 0x45, 0x00, 0xad, + 0x45, 0x00, 0xb0, 0x28, 0x02, 0x86, 0x46, 0x00, + 0x87, 0x47, 0x00, 0x84, 0x48, 0x00, 0x87, 0x48, + 0x00, 0xa3, 0x48, 0x00, 0x88, 0x48, 0x00, 0xa7, + 0x48, 0x00, 0xae, 0x49, 0x00, 0xb0, 0xcf, 0x00, + 0x81, 0x4b, 0x00, 0x81, 0x4b, 0x00, 0xa3, 0x4b, + 0x00, 0xb1, 0x4c, 0x00, 0xa3, 0x36, 0x1e, 0x84, + 0x4c, 0xb1, 0x4c, 0xad, 0x4d, 0x81, 0x4d, 0x87, + 0x4d, 0xa3, 0x4e, 0x87, 0x4e, 0xa3, 0x4e, 0xb1, + 0x4e, 0xad, 0xd5, 0x00, 0x81, 0xd5, 0x00, 0x88, + 0x4c, 0x01, 0x80, 0x4c, 0x01, 0x81, 0x50, 0x00, + 0x81, 0x50, 0x00, 0x87, 0x52, 0x00, 0x87, 0x52, + 0x00, 0xa3, 0x5a, 0x1e, 0x84, 0x52, 0x00, 0xb1, + 0x53, 0x00, 0x87, 0x53, 0x00, 0xa3, 0x5a, 0x01, + 0x87, 0x60, 0x01, 0x87, 0x62, 0x1e, 0x87, 0x54, + 0x00, 0x87, 0x54, 0x00, 0xa3, 0x54, 0x00, 0xb1, + 0x54, 0x00, 0xad, 0x55, 0x00, 0xa4, 0x55, 0x00, + 0xb0, 0x55, 0x00, 0xad, 0x68, 0x01, 0x81, 0x6a, + 0x01, 0x88, 0x56, 0x83, 0x56, 0xa3, 0x57, 0x80, + 0x57, 0x81, 0x57, 0x88, 0x57, 0x87, 0x57, 0xa3, + 0x58, 0x87, 0x58, 0x88, 0x59, 0x87, 0x5a, 0x82, + 0x5a, 0xa3, 0x5a, 0xb1, 0x68, 0xb1, 0x74, 0x88, + 0x77, 0x8a, 0x79, 0x8a, 0x61, 0x00, 0xbe, 0x02, + 0x7f, 0x01, 0x87, 0x41, 0x00, 0xa3, 0x41, 0x00, + 0x89, 0xc2, 0x00, 0x81, 0xc2, 0x00, 0x80, 0xc2, + 0x00, 0x89, 0xc2, 0x00, 0x83, 0xa0, 0x1e, 0x82, + 0x02, 0x01, 0x81, 0x02, 0x01, 0x80, 0x02, 0x01, + 0x89, 0x02, 0x01, 0x83, 0xa0, 0x1e, 0x86, 0x45, + 0x00, 0xa3, 0x45, 0x00, 0x89, 0x45, 0x00, 0x83, + 0xca, 0x00, 0x81, 0xca, 0x00, 0x80, 0xca, 0x00, + 0x89, 0xca, 0x00, 0x83, 0xb8, 0x1e, 0x82, 0x49, + 0x00, 0x89, 0x49, 0x00, 0xa3, 0x4f, 0x00, 0xa3, + 0x4f, 0x00, 0x89, 0xd4, 0x00, 0x81, 0xd4, 0x00, + 0x80, 0xd4, 0x00, 0x89, 0xd4, 0x00, 0x83, 0xcc, + 0x1e, 0x82, 0xa0, 0x01, 0x81, 0xa0, 0x01, 0x80, + 0xa0, 0x01, 0x89, 0xa0, 0x01, 0x83, 0xa0, 0x01, + 0xa3, 0x55, 0x00, 0xa3, 0x55, 0x00, 0x89, 0xaf, + 0x01, 0x81, 0xaf, 0x01, 0x80, 0xaf, 0x01, 0x89, + 0xaf, 0x01, 0x83, 0xaf, 0x01, 0xa3, 0x59, 0x00, + 0x80, 0x59, 0x00, 0xa3, 0x59, 0x00, 0x89, 0x59, + 0x00, 0x83, 0xb1, 0x03, 0x13, 0x03, 0x00, 0x1f, + 0x80, 0x00, 0x1f, 0x81, 0x00, 0x1f, 0xc2, 0x91, + 0x03, 0x13, 0x03, 0x08, 0x1f, 0x80, 0x08, 0x1f, + 0x81, 0x08, 0x1f, 0xc2, 0xb5, 0x03, 0x13, 0x03, + 0x10, 0x1f, 0x80, 0x10, 0x1f, 0x81, 0x95, 0x03, + 0x13, 0x03, 0x18, 0x1f, 0x80, 0x18, 0x1f, 0x81, + 0xb7, 0x03, 0x93, 0xb7, 0x03, 0x94, 0x20, 0x1f, + 0x80, 0x21, 0x1f, 0x80, 0x20, 0x1f, 0x81, 0x21, + 0x1f, 0x81, 0x20, 0x1f, 0xc2, 0x21, 0x1f, 0xc2, + 0x97, 0x03, 0x93, 0x97, 0x03, 0x94, 0x28, 0x1f, + 0x80, 0x29, 0x1f, 0x80, 0x28, 0x1f, 0x81, 0x29, + 0x1f, 0x81, 0x28, 0x1f, 0xc2, 0x29, 0x1f, 0xc2, + 0xb9, 0x03, 0x93, 0xb9, 0x03, 0x94, 0x30, 0x1f, + 0x80, 0x31, 0x1f, 0x80, 0x30, 0x1f, 0x81, 0x31, + 0x1f, 0x81, 0x30, 0x1f, 0xc2, 0x31, 0x1f, 0xc2, + 0x99, 0x03, 0x93, 0x99, 0x03, 0x94, 0x38, 0x1f, + 0x80, 0x39, 0x1f, 0x80, 0x38, 0x1f, 0x81, 0x39, + 0x1f, 0x81, 0x38, 0x1f, 0xc2, 0x39, 0x1f, 0xc2, + 0xbf, 0x03, 0x93, 0xbf, 0x03, 0x94, 0x40, 0x1f, + 0x80, 0x40, 0x1f, 0x81, 0x9f, 0x03, 0x13, 0x03, + 0x48, 0x1f, 0x80, 0x48, 0x1f, 0x81, 0xc5, 0x03, + 0x13, 0x03, 0x50, 0x1f, 0x80, 0x50, 0x1f, 0x81, + 0x50, 0x1f, 0xc2, 0xa5, 0x03, 0x94, 0x00, 0x00, + 0x00, 0x59, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x59, + 0x1f, 0x81, 0x00, 0x00, 0x00, 0x59, 0x1f, 0xc2, + 0xc9, 0x03, 0x93, 0xc9, 0x03, 0x94, 0x60, 0x1f, + 0x80, 0x61, 0x1f, 0x80, 0x60, 0x1f, 0x81, 0x61, + 0x1f, 0x81, 0x60, 0x1f, 0xc2, 0x61, 0x1f, 0xc2, + 0xa9, 0x03, 0x93, 0xa9, 0x03, 0x94, 0x68, 0x1f, + 0x80, 0x69, 0x1f, 0x80, 0x68, 0x1f, 0x81, 0x69, + 0x1f, 0x81, 0x68, 0x1f, 0xc2, 0x69, 0x1f, 0xc2, + 0xb1, 0x03, 0x80, 0xb5, 0x03, 0x80, 0xb7, 0x03, + 0x80, 0xb9, 0x03, 0x80, 0xbf, 0x03, 0x80, 0xc5, + 0x03, 0x80, 0xc9, 0x03, 0x80, 0x00, 0x1f, 0x45, + 0x03, 0x20, 0x1f, 0x45, 0x03, 0x60, 0x1f, 0x45, + 0x03, 0xb1, 0x03, 0x86, 0xb1, 0x03, 0x84, 0x70, + 0x1f, 0xc5, 0xb1, 0x03, 0xc5, 0xac, 0x03, 0xc5, + 0x00, 0x00, 0x00, 0xb1, 0x03, 0xc2, 0xb6, 0x1f, + 0xc5, 0x91, 0x03, 0x86, 0x91, 0x03, 0x84, 0x91, + 0x03, 0x80, 0x91, 0x03, 0xc5, 0x20, 0x93, 0x20, + 0x93, 0x20, 0xc2, 0xa8, 0x00, 0xc2, 0x74, 0x1f, + 0xc5, 0xb7, 0x03, 0xc5, 0xae, 0x03, 0xc5, 0x00, + 0x00, 0x00, 0xb7, 0x03, 0xc2, 0xc6, 0x1f, 0xc5, + 0x95, 0x03, 0x80, 0x97, 0x03, 0x80, 0x97, 0x03, + 0xc5, 0xbf, 0x1f, 0x80, 0xbf, 0x1f, 0x81, 0xbf, + 0x1f, 0xc2, 0xb9, 0x03, 0x86, 0xb9, 0x03, 0x84, + 0xca, 0x03, 0x80, 0x00, 0x03, 0xb9, 0x42, 0xca, + 0x42, 0x99, 0x06, 0x99, 0x04, 0x99, 0x00, 0xfe, + 0x1f, 0x80, 0xfe, 0x1f, 0x81, 0xfe, 0x1f, 0xc2, + 0xc5, 0x03, 0x86, 0xc5, 0x03, 0x84, 0xcb, 0x03, + 0x80, 0x00, 0x03, 0xc1, 0x13, 0xc1, 0x14, 0xc5, + 0x42, 0xcb, 0x42, 0xa5, 0x06, 0xa5, 0x04, 0xa5, + 0x00, 0xa1, 0x03, 0x94, 0xa8, 0x00, 0x80, 0x85, + 0x03, 0x60, 0x00, 0x7c, 0x1f, 0xc5, 0xc9, 0x03, + 0xc5, 0xce, 0x03, 0xc5, 0x00, 0x00, 0x00, 0xc9, + 0x03, 0xc2, 0xf6, 0x1f, 0xc5, 0x9f, 0x03, 0x80, + 0xa9, 0x03, 0x80, 0xa9, 0x03, 0xc5, 0x20, 0x94, + 0x02, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0xb3, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x32, 0x20, 0x32, 0x20, 0x32, 0x20, + 0x00, 0x00, 0x00, 0x35, 0x20, 0x35, 0x20, 0x35, + 0x20, 0x00, 0x00, 0x00, 0x21, 0x21, 0x00, 0x00, + 0x20, 0x85, 0x3f, 0x3f, 0x3f, 0x21, 0x21, 0x3f, + 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x30, 0x69, + 0x00, 0x00, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x2b, 0x3d, 0x28, 0x29, 0x6e, 0x30, 0x00, 0x2b, + 0x00, 0x12, 0x22, 0x3d, 0x00, 0x28, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x61, 0x00, 0x65, 0x00, 0x6f, + 0x00, 0x78, 0x00, 0x59, 0x02, 0x68, 0x6b, 0x6c, + 0x6d, 0x6e, 0x70, 0x73, 0x74, 0x52, 0x73, 0x61, + 0x2f, 0x63, 0x61, 0x2f, 0x73, 0xb0, 0x00, 0x43, + 0x63, 0x2f, 0x6f, 0x63, 0x2f, 0x75, 0xb0, 0x00, + 0x46, 0x48, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x20, + 0xdf, 0x01, 0x01, 0x04, 0x24, 0x4e, 0x6f, 0x50, + 0x51, 0x52, 0x52, 0x52, 0x53, 0x4d, 0x54, 0x45, + 0x4c, 0x54, 0x4d, 0x4b, 0x00, 0xc5, 0x00, 0x42, + 0x43, 0x00, 0x65, 0x45, 0x46, 0x00, 0x4d, 0x6f, + 0xd0, 0x05, 0x46, 0x41, 0x58, 0xc0, 0x03, 0xb3, + 0x03, 0x93, 0x03, 0xa0, 0x03, 0x11, 0x22, 0x44, + 0x64, 0x65, 0x69, 0x6a, 0x31, 0xd0, 0x37, 0x31, + 0xd0, 0x39, 0x31, 0xd0, 0x31, 0x30, 0x31, 0xd0, + 0x33, 0x32, 0xd0, 0x33, 0x31, 0xd0, 0x35, 0x32, + 0xd0, 0x35, 0x33, 0xd0, 0x35, 0x34, 0xd0, 0x35, + 0x31, 0xd0, 0x36, 0x35, 0xd0, 0x36, 0x31, 0xd0, + 0x38, 0x33, 0xd0, 0x38, 0x35, 0xd0, 0x38, 0x37, + 0xd0, 0x38, 0x31, 0xd0, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x56, 0x56, 0x49, 0x56, 0x49, 0x49, + 0x56, 0x49, 0x49, 0x49, 0x49, 0x58, 0x58, 0x49, + 0x58, 0x49, 0x49, 0x4c, 0x43, 0x44, 0x4d, 0x69, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x76, 0x76, + 0x69, 0x76, 0x69, 0x69, 0x76, 0x69, 0x69, 0x69, + 0x69, 0x78, 0x78, 0x69, 0x78, 0x69, 0x69, 0x6c, + 0x63, 0x64, 0x6d, 0x30, 0xd0, 0x33, 0x90, 0x21, + 0xb8, 0x92, 0x21, 0xb8, 0x94, 0x21, 0xb8, 0xd0, + 0x21, 0xb8, 0xd4, 0x21, 0xb8, 0xd2, 0x21, 0xb8, + 0x03, 0x22, 0xb8, 0x08, 0x22, 0xb8, 0x0b, 0x22, + 0xb8, 0x23, 0x22, 0xb8, 0x00, 0x00, 0x00, 0x25, + 0x22, 0xb8, 0x2b, 0x22, 0x2b, 0x22, 0x2b, 0x22, + 0x00, 0x00, 0x00, 0x2e, 0x22, 0x2e, 0x22, 0x2e, + 0x22, 0x00, 0x00, 0x00, 0x3c, 0x22, 0xb8, 0x43, + 0x22, 0xb8, 0x45, 0x22, 0xb8, 0x00, 0x00, 0x00, + 0x48, 0x22, 0xb8, 0x3d, 0x00, 0xb8, 0x00, 0x00, + 0x00, 0x61, 0x22, 0xb8, 0x4d, 0x22, 0xb8, 0x3c, + 0x00, 0xb8, 0x3e, 0x00, 0xb8, 0x64, 0x22, 0xb8, + 0x65, 0x22, 0xb8, 0x72, 0x22, 0xb8, 0x76, 0x22, + 0xb8, 0x7a, 0x22, 0xb8, 0x82, 0x22, 0xb8, 0x86, + 0x22, 0xb8, 0xa2, 0x22, 0xb8, 0xa8, 0x22, 0xb8, + 0xa9, 0x22, 0xb8, 0xab, 0x22, 0xb8, 0x7c, 0x22, + 0xb8, 0x91, 0x22, 0xb8, 0xb2, 0x22, 0x38, 0x03, + 0x08, 0x30, 0x31, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x32, 0x30, 0x28, 0x00, 0x31, 0x00, 0x29, 0x00, + 0x28, 0x00, 0x31, 0x00, 0x30, 0x00, 0x29, 0x00, + 0x28, 0x32, 0x30, 0x29, 0x31, 0x00, 0x2e, 0x00, + 0x31, 0x00, 0x30, 0x00, 0x2e, 0x00, 0x32, 0x30, + 0x2e, 0x28, 0x00, 0x61, 0x00, 0x29, 0x00, 0x41, + 0x00, 0x61, 0x00, 0x2b, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x3a, 0x3a, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0xdd, 0x2a, 0xb8, 0x6a, 0x56, 0x00, 0x4e, + 0x00, 0x28, 0x36, 0x3f, 0x59, 0x85, 0x8c, 0xa0, + 0xba, 0x3f, 0x51, 0x00, 0x26, 0x2c, 0x43, 0x57, + 0x6c, 0xa1, 0xb6, 0xc1, 0x9b, 0x52, 0x00, 0x5e, + 0x7a, 0x7f, 0x9d, 0xa6, 0xc1, 0xce, 0xe7, 0xb6, + 0x53, 0xc8, 0x53, 0xe3, 0x53, 0xd7, 0x56, 0x1f, + 0x57, 0xeb, 0x58, 0x02, 0x59, 0x0a, 0x59, 0x15, + 0x59, 0x27, 0x59, 0x73, 0x59, 0x50, 0x5b, 0x80, + 0x5b, 0xf8, 0x5b, 0x0f, 0x5c, 0x22, 0x5c, 0x38, + 0x5c, 0x6e, 0x5c, 0x71, 0x5c, 0xdb, 0x5d, 0xe5, + 0x5d, 0xf1, 0x5d, 0xfe, 0x5d, 0x72, 0x5e, 0x7a, + 0x5e, 0x7f, 0x5e, 0xf4, 0x5e, 0xfe, 0x5e, 0x0b, + 0x5f, 0x13, 0x5f, 0x50, 0x5f, 0x61, 0x5f, 0x73, + 0x5f, 0xc3, 0x5f, 0x08, 0x62, 0x36, 0x62, 0x4b, + 0x62, 0x2f, 0x65, 0x34, 0x65, 0x87, 0x65, 0x97, + 0x65, 0xa4, 0x65, 0xb9, 0x65, 0xe0, 0x65, 0xe5, + 0x65, 0xf0, 0x66, 0x08, 0x67, 0x28, 0x67, 0x20, + 0x6b, 0x62, 0x6b, 0x79, 0x6b, 0xb3, 0x6b, 0xcb, + 0x6b, 0xd4, 0x6b, 0xdb, 0x6b, 0x0f, 0x6c, 0x14, + 0x6c, 0x34, 0x6c, 0x6b, 0x70, 0x2a, 0x72, 0x36, + 0x72, 0x3b, 0x72, 0x3f, 0x72, 0x47, 0x72, 0x59, + 0x72, 0x5b, 0x72, 0xac, 0x72, 0x84, 0x73, 0x89, + 0x73, 0xdc, 0x74, 0xe6, 0x74, 0x18, 0x75, 0x1f, + 0x75, 0x28, 0x75, 0x30, 0x75, 0x8b, 0x75, 0x92, + 0x75, 0x76, 0x76, 0x7d, 0x76, 0xae, 0x76, 0xbf, + 0x76, 0xee, 0x76, 0xdb, 0x77, 0xe2, 0x77, 0xf3, + 0x77, 0x3a, 0x79, 0xb8, 0x79, 0xbe, 0x79, 0x74, + 0x7a, 0xcb, 0x7a, 0xf9, 0x7a, 0x73, 0x7c, 0xf8, + 0x7c, 0x36, 0x7f, 0x51, 0x7f, 0x8a, 0x7f, 0xbd, + 0x7f, 0x01, 0x80, 0x0c, 0x80, 0x12, 0x80, 0x33, + 0x80, 0x7f, 0x80, 0x89, 0x80, 0xe3, 0x81, 0x00, + 0x07, 0x10, 0x19, 0x29, 0x38, 0x3c, 0x8b, 0x8f, + 0x95, 0x4d, 0x86, 0x6b, 0x86, 0x40, 0x88, 0x4c, + 0x88, 0x63, 0x88, 0x7e, 0x89, 0x8b, 0x89, 0xd2, + 0x89, 0x00, 0x8a, 0x37, 0x8c, 0x46, 0x8c, 0x55, + 0x8c, 0x78, 0x8c, 0x9d, 0x8c, 0x64, 0x8d, 0x70, + 0x8d, 0xb3, 0x8d, 0xab, 0x8e, 0xca, 0x8e, 0x9b, + 0x8f, 0xb0, 0x8f, 0xb5, 0x8f, 0x91, 0x90, 0x49, + 0x91, 0xc6, 0x91, 0xcc, 0x91, 0xd1, 0x91, 0x77, + 0x95, 0x80, 0x95, 0x1c, 0x96, 0xb6, 0x96, 0xb9, + 0x96, 0xe8, 0x96, 0x51, 0x97, 0x5e, 0x97, 0x62, + 0x97, 0x69, 0x97, 0xcb, 0x97, 0xed, 0x97, 0xf3, + 0x97, 0x01, 0x98, 0xa8, 0x98, 0xdb, 0x98, 0xdf, + 0x98, 0x96, 0x99, 0x99, 0x99, 0xac, 0x99, 0xa8, + 0x9a, 0xd8, 0x9a, 0xdf, 0x9a, 0x25, 0x9b, 0x2f, + 0x9b, 0x32, 0x9b, 0x3c, 0x9b, 0x5a, 0x9b, 0xe5, + 0x9c, 0x75, 0x9e, 0x7f, 0x9e, 0xa5, 0x9e, 0x00, + 0x16, 0x1e, 0x28, 0x2c, 0x54, 0x58, 0x69, 0x6e, + 0x7b, 0x96, 0xa5, 0xad, 0xe8, 0xf7, 0xfb, 0x12, + 0x30, 0x00, 0x00, 0x41, 0x53, 0x44, 0x53, 0x45, + 0x53, 0x4b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x4f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x51, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x55, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x57, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x59, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5b, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5d, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x5f, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x61, 0x30, 0x99, 0x30, 0x64, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x66, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0x68, 0x30, 0x99, + 0x30, 0x6f, 0x30, 0x99, 0x30, 0x72, 0x30, 0x99, + 0x30, 0x75, 0x30, 0x99, 0x30, 0x78, 0x30, 0x99, + 0x30, 0x7b, 0x30, 0x99, 0x30, 0x46, 0x30, 0x99, + 0x30, 0x20, 0x00, 0x99, 0x30, 0x9d, 0x30, 0x99, + 0x30, 0x88, 0x30, 0x8a, 0x30, 0xab, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xad, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbb, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbd, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xbf, 0x30, 0x99, + 0x30, 0x00, 0x00, 0x00, 0x00, 0xc1, 0x30, 0x99, + 0x30, 0xc4, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc6, 0x30, 0x99, 0x30, 0x00, 0x00, 0x00, + 0x00, 0xc8, 0x30, 0x99, 0x30, 0xcf, 0x30, 0x99, + 0x30, 0xd2, 0x30, 0x99, 0x30, 0xd5, 0x30, 0x99, + 0x30, 0xd8, 0x30, 0x99, 0x30, 0xdb, 0x30, 0x99, + 0x30, 0xa6, 0x30, 0x99, 0x30, 0xef, 0x30, 0x99, + 0x30, 0xfd, 0x30, 0x99, 0x30, 0xb3, 0x30, 0xc8, + 0x30, 0x00, 0x11, 0x00, 0x01, 0xaa, 0x02, 0xac, + 0xad, 0x03, 0x04, 0x05, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0x1a, 0x06, 0x07, 0x08, 0x21, 0x09, + 0x11, 0x61, 0x11, 0x14, 0x11, 0x4c, 0x00, 0x01, + 0xb3, 0xb4, 0xb8, 0xba, 0xbf, 0xc3, 0xc5, 0x08, + 0xc9, 0xcb, 0x09, 0x0a, 0x0c, 0x0e, 0x0f, 0x13, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x22, + 0x2c, 0x33, 0x38, 0xdd, 0xde, 0x43, 0x44, 0x45, + 0x70, 0x71, 0x74, 0x7d, 0x7e, 0x80, 0x8a, 0x8d, + 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, + 0x0a, 0x4e, 0x2d, 0x4e, 0x0b, 0x4e, 0x32, 0x75, + 0x59, 0x4e, 0x19, 0x4e, 0x01, 0x4e, 0x29, 0x59, + 0x30, 0x57, 0xba, 0x4e, 0x28, 0x00, 0x29, 0x00, + 0x00, 0x11, 0x02, 0x11, 0x03, 0x11, 0x05, 0x11, + 0x06, 0x11, 0x07, 0x11, 0x09, 0x11, 0x0b, 0x11, + 0x0c, 0x11, 0x0e, 0x11, 0x0f, 0x11, 0x10, 0x11, + 0x11, 0x11, 0x12, 0x11, 0x28, 0x00, 0x00, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x02, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x05, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x09, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0e, 0x11, + 0x61, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0c, 0x11, + 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, + 0x69, 0x11, 0x0c, 0x11, 0x65, 0x11, 0xab, 0x11, + 0x29, 0x00, 0x28, 0x00, 0x0b, 0x11, 0x69, 0x11, + 0x12, 0x11, 0x6e, 0x11, 0x29, 0x00, 0x28, 0x00, + 0x29, 0x00, 0x00, 0x4e, 0x8c, 0x4e, 0x09, 0x4e, + 0xdb, 0x56, 0x94, 0x4e, 0x6d, 0x51, 0x03, 0x4e, + 0x6b, 0x51, 0x5d, 0x4e, 0x41, 0x53, 0x08, 0x67, + 0x6b, 0x70, 0x34, 0x6c, 0x28, 0x67, 0xd1, 0x91, + 0x1f, 0x57, 0xe5, 0x65, 0x2a, 0x68, 0x09, 0x67, + 0x3e, 0x79, 0x0d, 0x54, 0x79, 0x72, 0xa1, 0x8c, + 0x5d, 0x79, 0xb4, 0x52, 0xe3, 0x4e, 0x7c, 0x54, + 0x66, 0x5b, 0xe3, 0x76, 0x01, 0x4f, 0xc7, 0x8c, + 0x54, 0x53, 0x6d, 0x79, 0x11, 0x4f, 0xea, 0x81, + 0xf3, 0x81, 0x4f, 0x55, 0x7c, 0x5e, 0x87, 0x65, + 0x8f, 0x7b, 0x50, 0x54, 0x45, 0x32, 0x00, 0x31, + 0x00, 0x33, 0x00, 0x30, 0x00, 0x00, 0x11, 0x00, + 0x02, 0x03, 0x05, 0x06, 0x07, 0x09, 0x0b, 0x0c, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x00, 0x11, 0x00, + 0x61, 0x02, 0x61, 0x03, 0x61, 0x05, 0x61, 0x06, + 0x61, 0x07, 0x61, 0x09, 0x61, 0x0b, 0x61, 0x0c, + 0x61, 0x0e, 0x11, 0x61, 0x11, 0x00, 0x11, 0x0e, + 0x61, 0xb7, 0x00, 0x69, 0x0b, 0x11, 0x01, 0x63, + 0x00, 0x69, 0x0b, 0x11, 0x6e, 0x11, 0x00, 0x4e, + 0x8c, 0x4e, 0x09, 0x4e, 0xdb, 0x56, 0x94, 0x4e, + 0x6d, 0x51, 0x03, 0x4e, 0x6b, 0x51, 0x5d, 0x4e, + 0x41, 0x53, 0x08, 0x67, 0x6b, 0x70, 0x34, 0x6c, + 0x28, 0x67, 0xd1, 0x91, 0x1f, 0x57, 0xe5, 0x65, + 0x2a, 0x68, 0x09, 0x67, 0x3e, 0x79, 0x0d, 0x54, + 0x79, 0x72, 0xa1, 0x8c, 0x5d, 0x79, 0xb4, 0x52, + 0xd8, 0x79, 0x37, 0x75, 0x73, 0x59, 0x69, 0x90, + 0x2a, 0x51, 0x70, 0x53, 0xe8, 0x6c, 0x05, 0x98, + 0x11, 0x4f, 0x99, 0x51, 0x63, 0x6b, 0x0a, 0x4e, + 0x2d, 0x4e, 0x0b, 0x4e, 0xe6, 0x5d, 0xf3, 0x53, + 0x3b, 0x53, 0x97, 0x5b, 0x66, 0x5b, 0xe3, 0x76, + 0x01, 0x4f, 0xc7, 0x8c, 0x54, 0x53, 0x1c, 0x59, + 0x33, 0x00, 0x36, 0x00, 0x34, 0x00, 0x30, 0x00, + 0x35, 0x30, 0x31, 0x00, 0x08, 0x67, 0x31, 0x00, + 0x30, 0x00, 0x08, 0x67, 0x48, 0x67, 0x65, 0x72, + 0x67, 0x65, 0x56, 0x4c, 0x54, 0x44, 0xa2, 0x30, + 0x00, 0x02, 0x04, 0x06, 0x08, 0x09, 0x0b, 0x0d, + 0x0f, 0x11, 0x13, 0x15, 0x17, 0x19, 0x1b, 0x1d, + 0x1f, 0x22, 0x24, 0x26, 0x28, 0x29, 0x2a, 0x2b, + 0x2c, 0x2d, 0x30, 0x33, 0x36, 0x39, 0x3c, 0x3d, + 0x3e, 0x3f, 0x40, 0x42, 0x44, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4d, 0x4e, 0x4f, 0x50, 0xe4, + 0x4e, 0x8c, 0x54, 0xa1, 0x30, 0x01, 0x30, 0x5b, + 0x27, 0x01, 0x4a, 0x34, 0x00, 0x01, 0x52, 0x39, + 0x01, 0xa2, 0x30, 0x00, 0x5a, 0x49, 0xa4, 0x30, + 0x00, 0x27, 0x4f, 0x0c, 0xa4, 0x30, 0x00, 0x4f, + 0x1d, 0x02, 0x05, 0x4f, 0xa8, 0x30, 0x00, 0x11, + 0x07, 0x54, 0x21, 0xa8, 0x30, 0x00, 0x54, 0x03, + 0x54, 0xa4, 0x30, 0x06, 0x4f, 0x15, 0x06, 0x58, + 0x3c, 0x07, 0x00, 0x46, 0xab, 0x30, 0x00, 0x3e, + 0x18, 0x1d, 0x00, 0x42, 0x3f, 0x51, 0xac, 0x30, + 0x00, 0x41, 0x47, 0x00, 0x47, 0x32, 0xae, 0x30, + 0xac, 0x30, 0xae, 0x30, 0x00, 0x1d, 0x4e, 0xad, + 0x30, 0x00, 0x38, 0x3d, 0x4f, 0x01, 0x3e, 0x13, + 0x4f, 0xad, 0x30, 0xed, 0x30, 0xad, 0x30, 0x00, + 0x40, 0x03, 0x3c, 0x33, 0xad, 0x30, 0x00, 0x40, + 0x34, 0x4f, 0x1b, 0x3e, 0xad, 0x30, 0x00, 0x40, + 0x42, 0x16, 0x1b, 0xb0, 0x30, 0x00, 0x39, 0x30, + 0xa4, 0x30, 0x0c, 0x45, 0x3c, 0x24, 0x4f, 0x0b, + 0x47, 0x18, 0x00, 0x49, 0xaf, 0x30, 0x00, 0x3e, + 0x4d, 0x1e, 0xb1, 0x30, 0x00, 0x4b, 0x08, 0x02, + 0x3a, 0x19, 0x02, 0x4b, 0x2c, 0xa4, 0x30, 0x11, + 0x00, 0x0b, 0x47, 0xb5, 0x30, 0x00, 0x3e, 0x0c, + 0x47, 0x2b, 0xb0, 0x30, 0x07, 0x3a, 0x43, 0x00, + 0xb9, 0x30, 0x02, 0x3a, 0x08, 0x02, 0x3a, 0x0f, + 0x07, 0x43, 0x00, 0xb7, 0x30, 0x10, 0x00, 0x12, + 0x34, 0x11, 0x3c, 0x13, 0x17, 0xa4, 0x30, 0x2a, + 0x1f, 0x24, 0x2b, 0x00, 0x20, 0xbb, 0x30, 0x16, + 0x41, 0x00, 0x38, 0x0d, 0xc4, 0x30, 0x0d, 0x38, + 0x00, 0xd0, 0x30, 0x00, 0x2c, 0x1c, 0x1b, 0xa2, + 0x30, 0x32, 0x00, 0x17, 0x26, 0x49, 0xaf, 0x30, + 0x25, 0x00, 0x3c, 0xb3, 0x30, 0x21, 0x00, 0x20, + 0x38, 0xa1, 0x30, 0x34, 0x00, 0x48, 0x22, 0x28, + 0xa3, 0x30, 0x32, 0x00, 0x59, 0x25, 0xa7, 0x30, + 0x2f, 0x1c, 0x10, 0x00, 0x44, 0xd5, 0x30, 0x00, + 0x14, 0x1e, 0xaf, 0x30, 0x29, 0x00, 0x10, 0x4d, + 0x3c, 0xda, 0x30, 0xbd, 0x30, 0xb8, 0x30, 0x22, + 0x13, 0x1a, 0x20, 0x33, 0x0c, 0x22, 0x3b, 0x01, + 0x22, 0x44, 0x00, 0x21, 0x44, 0x07, 0xa4, 0x30, + 0x39, 0x00, 0x4f, 0x24, 0xc8, 0x30, 0x14, 0x23, + 0x00, 0xdb, 0x30, 0xf3, 0x30, 0xc9, 0x30, 0x14, + 0x2a, 0x00, 0x12, 0x33, 0x22, 0x12, 0x33, 0x2a, + 0xa4, 0x30, 0x3a, 0x00, 0x0b, 0x49, 0xa4, 0x30, + 0x3a, 0x00, 0x47, 0x3a, 0x1f, 0x2b, 0x3a, 0x47, + 0x0b, 0xb7, 0x30, 0x27, 0x3c, 0x00, 0x30, 0x3c, + 0xaf, 0x30, 0x30, 0x00, 0x3e, 0x44, 0xdf, 0x30, + 0xea, 0x30, 0xd0, 0x30, 0x0f, 0x1a, 0x00, 0x2c, + 0x1b, 0xe1, 0x30, 0xac, 0x30, 0xac, 0x30, 0x35, + 0x00, 0x1c, 0x47, 0x35, 0x50, 0x1c, 0x3f, 0xa2, + 0x30, 0x42, 0x5a, 0x27, 0x42, 0x5a, 0x49, 0x44, + 0x00, 0x51, 0xc3, 0x30, 0x27, 0x00, 0x05, 0x28, + 0xea, 0x30, 0xe9, 0x30, 0xd4, 0x30, 0x17, 0x00, + 0x28, 0xd6, 0x30, 0x15, 0x26, 0x00, 0x15, 0xec, + 0x30, 0xe0, 0x30, 0xb2, 0x30, 0x3a, 0x41, 0x16, + 0x00, 0x41, 0xc3, 0x30, 0x2c, 0x00, 0x05, 0x30, + 0x00, 0xb9, 0x70, 0x31, 0x00, 0x30, 0x00, 0xb9, + 0x70, 0x32, 0x00, 0x30, 0x00, 0xb9, 0x70, 0x68, + 0x50, 0x61, 0x64, 0x61, 0x41, 0x55, 0x62, 0x61, + 0x72, 0x6f, 0x56, 0x70, 0x63, 0x64, 0x6d, 0x64, + 0x00, 0x6d, 0x00, 0xb2, 0x00, 0x49, 0x00, 0x55, + 0x00, 0x73, 0x5e, 0x10, 0x62, 0x2d, 0x66, 0x8c, + 0x54, 0x27, 0x59, 0x63, 0x6b, 0x0e, 0x66, 0xbb, + 0x6c, 0x2a, 0x68, 0x0f, 0x5f, 0x1a, 0x4f, 0x3e, + 0x79, 0x70, 0x00, 0x41, 0x6e, 0x00, 0x41, 0xbc, + 0x03, 0x41, 0x6d, 0x00, 0x41, 0x6b, 0x00, 0x41, + 0x4b, 0x00, 0x42, 0x4d, 0x00, 0x42, 0x47, 0x00, + 0x42, 0x63, 0x61, 0x6c, 0x6b, 0x63, 0x61, 0x6c, + 0x70, 0x00, 0x46, 0x6e, 0x00, 0x46, 0xbc, 0x03, + 0x46, 0xbc, 0x03, 0x67, 0x6d, 0x00, 0x67, 0x6b, + 0x00, 0x67, 0x48, 0x00, 0x7a, 0x6b, 0x48, 0x7a, + 0x4d, 0x48, 0x7a, 0x47, 0x48, 0x7a, 0x54, 0x48, + 0x7a, 0xbc, 0x03, 0x13, 0x21, 0x6d, 0x00, 0x13, + 0x21, 0x64, 0x00, 0x13, 0x21, 0x6b, 0x00, 0x13, + 0x21, 0x66, 0x00, 0x6d, 0x6e, 0x00, 0x6d, 0xbc, + 0x03, 0x6d, 0x6d, 0x00, 0x6d, 0x63, 0x00, 0x6d, + 0x6b, 0x00, 0x6d, 0x63, 0x00, 0x0a, 0x0a, 0x4f, + 0x00, 0x0a, 0x4f, 0x6d, 0x00, 0xb2, 0x00, 0x63, + 0x00, 0x08, 0x0a, 0x4f, 0x0a, 0x0a, 0x50, 0x00, + 0x0a, 0x50, 0x6d, 0x00, 0xb3, 0x00, 0x6b, 0x00, + 0x6d, 0x00, 0xb3, 0x00, 0x6d, 0x00, 0x15, 0x22, + 0x73, 0x00, 0x6d, 0x00, 0x15, 0x22, 0x73, 0x00, + 0xb2, 0x00, 0x50, 0x61, 0x6b, 0x50, 0x61, 0x4d, + 0x50, 0x61, 0x47, 0x50, 0x61, 0x72, 0x61, 0x64, + 0x72, 0x61, 0x64, 0xd1, 0x73, 0x72, 0x00, 0x61, + 0x00, 0x64, 0x00, 0x15, 0x22, 0x73, 0x00, 0xb2, + 0x00, 0x70, 0x00, 0x73, 0x6e, 0x00, 0x73, 0xbc, + 0x03, 0x73, 0x6d, 0x00, 0x73, 0x70, 0x00, 0x56, + 0x6e, 0x00, 0x56, 0xbc, 0x03, 0x56, 0x6d, 0x00, + 0x56, 0x6b, 0x00, 0x56, 0x4d, 0x00, 0x56, 0x70, + 0x00, 0x57, 0x6e, 0x00, 0x57, 0xbc, 0x03, 0x57, + 0x6d, 0x00, 0x57, 0x6b, 0x00, 0x57, 0x4d, 0x00, + 0x57, 0x6b, 0x00, 0xa9, 0x03, 0x4d, 0x00, 0xa9, + 0x03, 0x61, 0x2e, 0x6d, 0x2e, 0x42, 0x71, 0x63, + 0x63, 0x63, 0x64, 0x43, 0xd1, 0x6b, 0x67, 0x43, + 0x6f, 0x2e, 0x64, 0x42, 0x47, 0x79, 0x68, 0x61, + 0x48, 0x50, 0x69, 0x6e, 0x4b, 0x4b, 0x4b, 0x4d, + 0x6b, 0x74, 0x6c, 0x6d, 0x6c, 0x6e, 0x6c, 0x6f, + 0x67, 0x6c, 0x78, 0x6d, 0x62, 0x6d, 0x69, 0x6c, + 0x6d, 0x6f, 0x6c, 0x50, 0x48, 0x70, 0x2e, 0x6d, + 0x2e, 0x50, 0x50, 0x4d, 0x50, 0x52, 0x73, 0x72, + 0x53, 0x76, 0x57, 0x62, 0x56, 0xd1, 0x6d, 0x41, + 0xd1, 0x6d, 0x31, 0x00, 0xe5, 0x65, 0x31, 0x00, + 0x30, 0x00, 0xe5, 0x65, 0x32, 0x00, 0x30, 0x00, + 0xe5, 0x65, 0x33, 0x00, 0x30, 0x00, 0xe5, 0x65, + 0x67, 0x61, 0x6c, 0x4a, 0x04, 0x4c, 0x04, 0x43, + 0x46, 0x51, 0x26, 0x01, 0x53, 0x01, 0x27, 0xa7, + 0x37, 0xab, 0x6b, 0x02, 0x52, 0xab, 0x48, 0x8c, + 0xf4, 0x66, 0xca, 0x8e, 0xc8, 0x8c, 0xd1, 0x6e, + 0x32, 0x4e, 0xe5, 0x53, 0x9c, 0x9f, 0x9c, 0x9f, + 0x51, 0x59, 0xd1, 0x91, 0x87, 0x55, 0x48, 0x59, + 0xf6, 0x61, 0x69, 0x76, 0x85, 0x7f, 0x3f, 0x86, + 0xba, 0x87, 0xf8, 0x88, 0x8f, 0x90, 0x02, 0x6a, + 0x1b, 0x6d, 0xd9, 0x70, 0xde, 0x73, 0x3d, 0x84, + 0x6a, 0x91, 0xf1, 0x99, 0x82, 0x4e, 0x75, 0x53, + 0x04, 0x6b, 0x1b, 0x72, 0x2d, 0x86, 0x1e, 0x9e, + 0x50, 0x5d, 0xeb, 0x6f, 0xcd, 0x85, 0x64, 0x89, + 0xc9, 0x62, 0xd8, 0x81, 0x1f, 0x88, 0xca, 0x5e, + 0x17, 0x67, 0x6a, 0x6d, 0xfc, 0x72, 0xce, 0x90, + 0x86, 0x4f, 0xb7, 0x51, 0xde, 0x52, 0xc4, 0x64, + 0xd3, 0x6a, 0x10, 0x72, 0xe7, 0x76, 0x01, 0x80, + 0x06, 0x86, 0x5c, 0x86, 0xef, 0x8d, 0x32, 0x97, + 0x6f, 0x9b, 0xfa, 0x9d, 0x8c, 0x78, 0x7f, 0x79, + 0xa0, 0x7d, 0xc9, 0x83, 0x04, 0x93, 0x7f, 0x9e, + 0xd6, 0x8a, 0xdf, 0x58, 0x04, 0x5f, 0x60, 0x7c, + 0x7e, 0x80, 0x62, 0x72, 0xca, 0x78, 0xc2, 0x8c, + 0xf7, 0x96, 0xd8, 0x58, 0x62, 0x5c, 0x13, 0x6a, + 0xda, 0x6d, 0x0f, 0x6f, 0x2f, 0x7d, 0x37, 0x7e, + 0x4b, 0x96, 0xd2, 0x52, 0x8b, 0x80, 0xdc, 0x51, + 0xcc, 0x51, 0x1c, 0x7a, 0xbe, 0x7d, 0xf1, 0x83, + 0x75, 0x96, 0x80, 0x8b, 0xcf, 0x62, 0x02, 0x6a, + 0xfe, 0x8a, 0x39, 0x4e, 0xe7, 0x5b, 0x12, 0x60, + 0x87, 0x73, 0x70, 0x75, 0x17, 0x53, 0xfb, 0x78, + 0xbf, 0x4f, 0xa9, 0x5f, 0x0d, 0x4e, 0xcc, 0x6c, + 0x78, 0x65, 0x22, 0x7d, 0xc3, 0x53, 0x5e, 0x58, + 0x01, 0x77, 0x49, 0x84, 0xaa, 0x8a, 0xba, 0x6b, + 0xb0, 0x8f, 0x88, 0x6c, 0xfe, 0x62, 0xe5, 0x82, + 0xa0, 0x63, 0x65, 0x75, 0xae, 0x4e, 0x69, 0x51, + 0xc9, 0x51, 0x81, 0x68, 0xe7, 0x7c, 0x6f, 0x82, + 0xd2, 0x8a, 0xcf, 0x91, 0xf5, 0x52, 0x42, 0x54, + 0x73, 0x59, 0xec, 0x5e, 0xc5, 0x65, 0xfe, 0x6f, + 0x2a, 0x79, 0xad, 0x95, 0x6a, 0x9a, 0x97, 0x9e, + 0xce, 0x9e, 0x9b, 0x52, 0xc6, 0x66, 0x77, 0x6b, + 0x62, 0x8f, 0x74, 0x5e, 0x90, 0x61, 0x00, 0x62, + 0x9a, 0x64, 0x23, 0x6f, 0x49, 0x71, 0x89, 0x74, + 0xca, 0x79, 0xf4, 0x7d, 0x6f, 0x80, 0x26, 0x8f, + 0xee, 0x84, 0x23, 0x90, 0x4a, 0x93, 0x17, 0x52, + 0xa3, 0x52, 0xbd, 0x54, 0xc8, 0x70, 0xc2, 0x88, + 0xaa, 0x8a, 0xc9, 0x5e, 0xf5, 0x5f, 0x7b, 0x63, + 0xae, 0x6b, 0x3e, 0x7c, 0x75, 0x73, 0xe4, 0x4e, + 0xf9, 0x56, 0xe7, 0x5b, 0xba, 0x5d, 0x1c, 0x60, + 0xb2, 0x73, 0x69, 0x74, 0x9a, 0x7f, 0x46, 0x80, + 0x34, 0x92, 0xf6, 0x96, 0x48, 0x97, 0x18, 0x98, + 0x8b, 0x4f, 0xae, 0x79, 0xb4, 0x91, 0xb8, 0x96, + 0xe1, 0x60, 0x86, 0x4e, 0xda, 0x50, 0xee, 0x5b, + 0x3f, 0x5c, 0x99, 0x65, 0x02, 0x6a, 0xce, 0x71, + 0x42, 0x76, 0xfc, 0x84, 0x7c, 0x90, 0x8d, 0x9f, + 0x88, 0x66, 0x2e, 0x96, 0x89, 0x52, 0x7b, 0x67, + 0xf3, 0x67, 0x41, 0x6d, 0x9c, 0x6e, 0x09, 0x74, + 0x59, 0x75, 0x6b, 0x78, 0x10, 0x7d, 0x5e, 0x98, + 0x6d, 0x51, 0x2e, 0x62, 0x78, 0x96, 0x2b, 0x50, + 0x19, 0x5d, 0xea, 0x6d, 0x2a, 0x8f, 0x8b, 0x5f, + 0x44, 0x61, 0x17, 0x68, 0x87, 0x73, 0x86, 0x96, + 0x29, 0x52, 0x0f, 0x54, 0x65, 0x5c, 0x13, 0x66, + 0x4e, 0x67, 0xa8, 0x68, 0xe5, 0x6c, 0x06, 0x74, + 0xe2, 0x75, 0x79, 0x7f, 0xcf, 0x88, 0xe1, 0x88, + 0xcc, 0x91, 0xe2, 0x96, 0x3f, 0x53, 0xba, 0x6e, + 0x1d, 0x54, 0xd0, 0x71, 0x98, 0x74, 0xfa, 0x85, + 0xa3, 0x96, 0x57, 0x9c, 0x9f, 0x9e, 0x97, 0x67, + 0xcb, 0x6d, 0xe8, 0x81, 0xcb, 0x7a, 0x20, 0x7b, + 0x92, 0x7c, 0xc0, 0x72, 0x99, 0x70, 0x58, 0x8b, + 0xc0, 0x4e, 0x36, 0x83, 0x3a, 0x52, 0x07, 0x52, + 0xa6, 0x5e, 0xd3, 0x62, 0xd6, 0x7c, 0x85, 0x5b, + 0x1e, 0x6d, 0xb4, 0x66, 0x3b, 0x8f, 0x4c, 0x88, + 0x4d, 0x96, 0x8b, 0x89, 0xd3, 0x5e, 0x40, 0x51, + 0xc0, 0x55, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x58, + 0x00, 0x00, 0x74, 0x66, 0x00, 0x00, 0x00, 0x00, + 0xde, 0x51, 0x2a, 0x73, 0xca, 0x76, 0x3c, 0x79, + 0x5e, 0x79, 0x65, 0x79, 0x8f, 0x79, 0x56, 0x97, + 0xbe, 0x7c, 0xbd, 0x7f, 0x00, 0x00, 0x12, 0x86, + 0x00, 0x00, 0xf8, 0x8a, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x90, 0xfd, 0x90, 0xef, 0x98, 0xfc, 0x98, + 0x28, 0x99, 0xb4, 0x9d, 0xde, 0x90, 0xb7, 0x96, + 0xae, 0x4f, 0xe7, 0x50, 0x4d, 0x51, 0xc9, 0x52, + 0xe4, 0x52, 0x51, 0x53, 0x9d, 0x55, 0x06, 0x56, + 0x68, 0x56, 0x40, 0x58, 0xa8, 0x58, 0x64, 0x5c, + 0x6e, 0x5c, 0x94, 0x60, 0x68, 0x61, 0x8e, 0x61, + 0xf2, 0x61, 0x4f, 0x65, 0xe2, 0x65, 0x91, 0x66, + 0x85, 0x68, 0x77, 0x6d, 0x1a, 0x6e, 0x22, 0x6f, + 0x6e, 0x71, 0x2b, 0x72, 0x22, 0x74, 0x91, 0x78, + 0x3e, 0x79, 0x49, 0x79, 0x48, 0x79, 0x50, 0x79, + 0x56, 0x79, 0x5d, 0x79, 0x8d, 0x79, 0x8e, 0x79, + 0x40, 0x7a, 0x81, 0x7a, 0xc0, 0x7b, 0xf4, 0x7d, + 0x09, 0x7e, 0x41, 0x7e, 0x72, 0x7f, 0x05, 0x80, + 0xed, 0x81, 0x79, 0x82, 0x79, 0x82, 0x57, 0x84, + 0x10, 0x89, 0x96, 0x89, 0x01, 0x8b, 0x39, 0x8b, + 0xd3, 0x8c, 0x08, 0x8d, 0xb6, 0x8f, 0x38, 0x90, + 0xe3, 0x96, 0xff, 0x97, 0x3b, 0x98, 0x75, 0x60, + 0xee, 0x42, 0x18, 0x82, 0x02, 0x26, 0x4e, 0xb5, + 0x51, 0x68, 0x51, 0x80, 0x4f, 0x45, 0x51, 0x80, + 0x51, 0xc7, 0x52, 0xfa, 0x52, 0x9d, 0x55, 0x55, + 0x55, 0x99, 0x55, 0xe2, 0x55, 0x5a, 0x58, 0xb3, + 0x58, 0x44, 0x59, 0x54, 0x59, 0x62, 0x5a, 0x28, + 0x5b, 0xd2, 0x5e, 0xd9, 0x5e, 0x69, 0x5f, 0xad, + 0x5f, 0xd8, 0x60, 0x4e, 0x61, 0x08, 0x61, 0x8e, + 0x61, 0x60, 0x61, 0xf2, 0x61, 0x34, 0x62, 0xc4, + 0x63, 0x1c, 0x64, 0x52, 0x64, 0x56, 0x65, 0x74, + 0x66, 0x17, 0x67, 0x1b, 0x67, 0x56, 0x67, 0x79, + 0x6b, 0xba, 0x6b, 0x41, 0x6d, 0xdb, 0x6e, 0xcb, + 0x6e, 0x22, 0x6f, 0x1e, 0x70, 0x6e, 0x71, 0xa7, + 0x77, 0x35, 0x72, 0xaf, 0x72, 0x2a, 0x73, 0x71, + 0x74, 0x06, 0x75, 0x3b, 0x75, 0x1d, 0x76, 0x1f, + 0x76, 0xca, 0x76, 0xdb, 0x76, 0xf4, 0x76, 0x4a, + 0x77, 0x40, 0x77, 0xcc, 0x78, 0xb1, 0x7a, 0xc0, + 0x7b, 0x7b, 0x7c, 0x5b, 0x7d, 0xf4, 0x7d, 0x3e, + 0x7f, 0x05, 0x80, 0x52, 0x83, 0xef, 0x83, 0x79, + 0x87, 0x41, 0x89, 0x86, 0x89, 0x96, 0x89, 0xbf, + 0x8a, 0xf8, 0x8a, 0xcb, 0x8a, 0x01, 0x8b, 0xfe, + 0x8a, 0xed, 0x8a, 0x39, 0x8b, 0x8a, 0x8b, 0x08, + 0x8d, 0x38, 0x8f, 0x72, 0x90, 0x99, 0x91, 0x76, + 0x92, 0x7c, 0x96, 0xe3, 0x96, 0x56, 0x97, 0xdb, + 0x97, 0xff, 0x97, 0x0b, 0x98, 0x3b, 0x98, 0x12, + 0x9b, 0x9c, 0x9f, 0x4a, 0x28, 0x44, 0x28, 0xd5, + 0x33, 0x9d, 0x3b, 0x18, 0x40, 0x39, 0x40, 0x49, + 0x52, 0xd0, 0x5c, 0xd3, 0x7e, 0x43, 0x9f, 0x8e, + 0x9f, 0x2a, 0xa0, 0x02, 0x66, 0x66, 0x66, 0x69, + 0x66, 0x6c, 0x66, 0x66, 0x69, 0x66, 0x66, 0x6c, + 0x7f, 0x01, 0x74, 0x73, 0x00, 0x74, 0x65, 0x05, + 0x0f, 0x11, 0x0f, 0x00, 0x0f, 0x06, 0x19, 0x11, + 0x0f, 0x08, 0xd9, 0x05, 0xb4, 0x05, 0x00, 0x00, + 0x00, 0x00, 0xf2, 0x05, 0xb7, 0x05, 0xd0, 0x05, + 0x12, 0x00, 0x03, 0x04, 0x0b, 0x0c, 0x0d, 0x18, + 0x1a, 0xe9, 0x05, 0xc1, 0x05, 0xe9, 0x05, 0xc2, + 0x05, 0x49, 0xfb, 0xc1, 0x05, 0x49, 0xfb, 0xc2, + 0x05, 0xd0, 0x05, 0xb7, 0x05, 0xd0, 0x05, 0xb8, + 0x05, 0xd0, 0x05, 0xbc, 0x05, 0xd8, 0x05, 0xbc, + 0x05, 0xde, 0x05, 0xbc, 0x05, 0xe0, 0x05, 0xbc, + 0x05, 0xe3, 0x05, 0xbc, 0x05, 0xb9, 0x05, 0x2d, + 0x03, 0x2e, 0x03, 0x2f, 0x03, 0x30, 0x03, 0x31, + 0x03, 0x1c, 0x00, 0x18, 0x06, 0x22, 0x06, 0x2b, + 0x06, 0xd0, 0x05, 0xdc, 0x05, 0x71, 0x06, 0x00, + 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0f, 0x0f, 0x0f, 0x0f, 0x09, 0x09, 0x09, + 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x08, 0x08, 0x08, + 0x08, 0x33, 0x33, 0x33, 0x33, 0x35, 0x35, 0x35, + 0x35, 0x13, 0x13, 0x13, 0x13, 0x12, 0x12, 0x12, + 0x12, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x1c, 0x1c, 0x1b, 0x1b, 0x1d, 0x1d, 0x17, + 0x17, 0x27, 0x27, 0x20, 0x20, 0x38, 0x38, 0x38, + 0x38, 0x3e, 0x3e, 0x3e, 0x3e, 0x42, 0x42, 0x42, + 0x42, 0x40, 0x40, 0x40, 0x40, 0x49, 0x49, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4f, 0x4f, 0x50, 0x50, 0x50, + 0x50, 0x4d, 0x4d, 0x4d, 0x4d, 0x61, 0x61, 0x62, + 0x62, 0x49, 0x06, 0x64, 0x64, 0x64, 0x64, 0x7e, + 0x7e, 0x7d, 0x7d, 0x7f, 0x7f, 0x2e, 0x82, 0x82, + 0x7c, 0x7c, 0x80, 0x80, 0x87, 0x87, 0x87, 0x87, + 0x00, 0x00, 0x26, 0x06, 0x00, 0x01, 0x00, 0x01, + 0x00, 0xaf, 0x00, 0xaf, 0x00, 0x22, 0x00, 0x22, + 0x00, 0xa1, 0x00, 0xa1, 0x00, 0xa0, 0x00, 0xa0, + 0x00, 0xa2, 0x00, 0xa2, 0x00, 0xaa, 0x00, 0xaa, + 0x00, 0xaa, 0x00, 0x23, 0x00, 0x23, 0x00, 0x23, + 0xcc, 0x06, 0x00, 0x00, 0x00, 0x00, 0x26, 0x06, + 0x00, 0x06, 0x00, 0x07, 0x00, 0x1f, 0x00, 0x23, + 0x00, 0x24, 0x02, 0x06, 0x02, 0x07, 0x02, 0x08, + 0x02, 0x1f, 0x02, 0x23, 0x02, 0x24, 0x04, 0x06, + 0x04, 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x23, + 0x04, 0x24, 0x05, 0x06, 0x05, 0x1f, 0x05, 0x23, + 0x05, 0x24, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, + 0x07, 0x1f, 0x08, 0x06, 0x08, 0x07, 0x08, 0x1f, + 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x08, 0x0d, 0x1f, + 0x0f, 0x07, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, + 0x10, 0x08, 0x10, 0x1f, 0x11, 0x07, 0x11, 0x1f, + 0x12, 0x1f, 0x13, 0x06, 0x13, 0x1f, 0x14, 0x06, + 0x14, 0x1f, 0x1b, 0x06, 0x1b, 0x07, 0x1b, 0x08, + 0x1b, 0x1f, 0x1b, 0x23, 0x1b, 0x24, 0x1c, 0x07, + 0x1c, 0x1f, 0x1c, 0x23, 0x1c, 0x24, 0x1d, 0x01, + 0x1d, 0x06, 0x1d, 0x07, 0x1d, 0x08, 0x1d, 0x1e, + 0x1d, 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x06, + 0x1e, 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x23, + 0x1e, 0x24, 0x1f, 0x06, 0x1f, 0x07, 0x1f, 0x08, + 0x1f, 0x1f, 0x1f, 0x23, 0x1f, 0x24, 0x20, 0x06, + 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, 0x23, + 0x20, 0x24, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x23, + 0x21, 0x24, 0x24, 0x06, 0x24, 0x07, 0x24, 0x08, + 0x24, 0x1f, 0x24, 0x23, 0x24, 0x24, 0x0a, 0x4a, + 0x0b, 0x4a, 0x23, 0x4a, 0x20, 0x00, 0x4c, 0x06, + 0x51, 0x06, 0x51, 0x06, 0xff, 0x00, 0x1f, 0x26, + 0x06, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x1f, 0x00, + 0x20, 0x00, 0x23, 0x00, 0x24, 0x02, 0x0b, 0x02, + 0x0c, 0x02, 0x1f, 0x02, 0x20, 0x02, 0x23, 0x02, + 0x24, 0x04, 0x0b, 0x04, 0x0c, 0x04, 0x1f, 0x26, + 0x06, 0x04, 0x20, 0x04, 0x23, 0x04, 0x24, 0x05, + 0x0b, 0x05, 0x0c, 0x05, 0x1f, 0x05, 0x20, 0x05, + 0x23, 0x05, 0x24, 0x1b, 0x23, 0x1b, 0x24, 0x1c, + 0x23, 0x1c, 0x24, 0x1d, 0x01, 0x1d, 0x1e, 0x1d, + 0x1f, 0x1d, 0x23, 0x1d, 0x24, 0x1e, 0x1f, 0x1e, + 0x23, 0x1e, 0x24, 0x1f, 0x01, 0x1f, 0x1f, 0x20, + 0x0b, 0x20, 0x0c, 0x20, 0x1f, 0x20, 0x20, 0x20, + 0x23, 0x20, 0x24, 0x23, 0x4a, 0x24, 0x0b, 0x24, + 0x0c, 0x24, 0x1f, 0x24, 0x20, 0x24, 0x23, 0x24, + 0x24, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, + 0x1f, 0x00, 0x21, 0x02, 0x06, 0x02, 0x07, 0x02, + 0x08, 0x02, 0x1f, 0x02, 0x21, 0x04, 0x06, 0x04, + 0x07, 0x04, 0x08, 0x04, 0x1f, 0x04, 0x21, 0x05, + 0x1f, 0x06, 0x07, 0x06, 0x1f, 0x07, 0x06, 0x07, + 0x1f, 0x08, 0x06, 0x08, 0x1f, 0x0d, 0x06, 0x0d, + 0x07, 0x0d, 0x08, 0x0d, 0x1f, 0x0f, 0x07, 0x0f, + 0x08, 0x0f, 0x1f, 0x10, 0x06, 0x10, 0x07, 0x10, + 0x08, 0x10, 0x1f, 0x11, 0x07, 0x12, 0x1f, 0x13, + 0x06, 0x13, 0x1f, 0x14, 0x06, 0x14, 0x1f, 0x1b, + 0x06, 0x1b, 0x07, 0x1b, 0x08, 0x1b, 0x1f, 0x1c, + 0x07, 0x1c, 0x1f, 0x1d, 0x06, 0x1d, 0x07, 0x1d, + 0x08, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x06, 0x1e, + 0x07, 0x1e, 0x08, 0x1e, 0x1f, 0x1e, 0x21, 0x1f, + 0x06, 0x1f, 0x07, 0x1f, 0x08, 0x1f, 0x1f, 0x20, + 0x06, 0x20, 0x07, 0x20, 0x08, 0x20, 0x1f, 0x20, + 0x21, 0x21, 0x06, 0x21, 0x1f, 0x21, 0x4a, 0x24, + 0x06, 0x24, 0x07, 0x24, 0x08, 0x24, 0x1f, 0x24, + 0x21, 0x00, 0x1f, 0x00, 0x21, 0x02, 0x1f, 0x02, + 0x21, 0x04, 0x1f, 0x04, 0x21, 0x05, 0x1f, 0x05, + 0x21, 0x0d, 0x1f, 0x0d, 0x21, 0x0e, 0x1f, 0x0e, + 0x21, 0x1d, 0x1e, 0x1d, 0x1f, 0x1e, 0x1f, 0x20, + 0x1f, 0x20, 0x21, 0x24, 0x1f, 0x24, 0x21, 0x40, + 0x06, 0x4e, 0x06, 0x51, 0x06, 0x27, 0x06, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x10, + 0x22, 0x10, 0x23, 0x12, 0x22, 0x12, 0x23, 0x13, + 0x22, 0x13, 0x23, 0x0c, 0x22, 0x0c, 0x23, 0x0d, + 0x22, 0x0d, 0x23, 0x06, 0x22, 0x06, 0x23, 0x05, + 0x22, 0x05, 0x23, 0x07, 0x22, 0x07, 0x23, 0x0e, + 0x22, 0x0e, 0x23, 0x0f, 0x22, 0x0f, 0x23, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0d, + 0x0a, 0x0c, 0x0a, 0x0e, 0x0a, 0x0f, 0x0a, 0x0d, + 0x05, 0x0d, 0x06, 0x0d, 0x07, 0x0d, 0x1e, 0x0c, + 0x20, 0x0d, 0x20, 0x10, 0x1e, 0x0c, 0x05, 0x0c, + 0x06, 0x0c, 0x07, 0x0d, 0x05, 0x0d, 0x06, 0x0d, + 0x07, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x24, 0x00, + 0x24, 0x2a, 0x06, 0x00, 0x02, 0x1b, 0x00, 0x03, + 0x02, 0x00, 0x03, 0x02, 0x00, 0x03, 0x1b, 0x00, + 0x04, 0x1b, 0x00, 0x1b, 0x02, 0x00, 0x1b, 0x03, + 0x00, 0x1b, 0x04, 0x02, 0x1b, 0x03, 0x02, 0x1b, + 0x03, 0x03, 0x1b, 0x20, 0x03, 0x1b, 0x1f, 0x09, + 0x03, 0x02, 0x09, 0x02, 0x03, 0x09, 0x02, 0x1f, + 0x09, 0x1b, 0x03, 0x09, 0x1b, 0x03, 0x09, 0x1b, + 0x02, 0x09, 0x1b, 0x1b, 0x09, 0x1b, 0x1b, 0x0b, + 0x03, 0x03, 0x0b, 0x03, 0x03, 0x0b, 0x1b, 0x1b, + 0x0a, 0x03, 0x1b, 0x0a, 0x03, 0x1b, 0x0a, 0x02, + 0x20, 0x0a, 0x1b, 0x04, 0x0a, 0x1b, 0x04, 0x0a, + 0x1b, 0x1b, 0x0a, 0x1b, 0x1b, 0x0c, 0x03, 0x1f, + 0x0c, 0x04, 0x1b, 0x0c, 0x04, 0x1b, 0x0d, 0x1b, + 0x03, 0x0d, 0x1b, 0x03, 0x0d, 0x1b, 0x1b, 0x0d, + 0x1b, 0x20, 0x0f, 0x02, 0x1b, 0x0f, 0x1b, 0x1b, + 0x0f, 0x1b, 0x1b, 0x0f, 0x1b, 0x1f, 0x10, 0x1b, + 0x1b, 0x10, 0x1b, 0x20, 0x10, 0x1b, 0x1f, 0x17, + 0x04, 0x1b, 0x17, 0x04, 0x1b, 0x18, 0x1b, 0x03, + 0x18, 0x1b, 0x1b, 0x1a, 0x03, 0x1b, 0x1a, 0x03, + 0x20, 0x1a, 0x03, 0x1f, 0x1a, 0x02, 0x02, 0x1a, + 0x02, 0x02, 0x1a, 0x04, 0x1b, 0x1a, 0x04, 0x1b, + 0x1a, 0x1b, 0x03, 0x1a, 0x1b, 0x03, 0x1b, 0x03, + 0x02, 0x1b, 0x03, 0x1b, 0x1b, 0x03, 0x20, 0x1b, + 0x02, 0x03, 0x1b, 0x02, 0x1b, 0x1b, 0x04, 0x02, + 0x1b, 0x04, 0x1b, 0x28, 0x06, 0x1d, 0x04, 0x06, + 0x1f, 0x1d, 0x04, 0x1f, 0x1d, 0x1d, 0x1e, 0x05, + 0x1d, 0x1e, 0x05, 0x21, 0x1e, 0x04, 0x1d, 0x1e, + 0x04, 0x1d, 0x1e, 0x04, 0x21, 0x1e, 0x1d, 0x22, + 0x1e, 0x1d, 0x21, 0x22, 0x1d, 0x1d, 0x22, 0x1d, + 0x1d, 0x00, 0x06, 0x22, 0x02, 0x04, 0x22, 0x02, + 0x04, 0x21, 0x02, 0x06, 0x22, 0x02, 0x06, 0x21, + 0x02, 0x1d, 0x22, 0x02, 0x1d, 0x21, 0x04, 0x1d, + 0x22, 0x04, 0x05, 0x21, 0x04, 0x1d, 0x21, 0x0b, + 0x06, 0x21, 0x0d, 0x05, 0x22, 0x0c, 0x05, 0x22, + 0x0e, 0x05, 0x22, 0x1c, 0x04, 0x22, 0x1c, 0x1d, + 0x22, 0x22, 0x05, 0x22, 0x22, 0x04, 0x22, 0x22, + 0x1d, 0x22, 0x1d, 0x1d, 0x22, 0x1a, 0x1d, 0x22, + 0x1e, 0x05, 0x22, 0x1a, 0x1d, 0x05, 0x1c, 0x05, + 0x1d, 0x11, 0x1d, 0x22, 0x1b, 0x1d, 0x22, 0x1e, + 0x04, 0x05, 0x1d, 0x06, 0x22, 0x1c, 0x04, 0x1d, + 0x1b, 0x1d, 0x1d, 0x1c, 0x04, 0x1d, 0x1e, 0x04, + 0x05, 0x04, 0x05, 0x22, 0x05, 0x04, 0x22, 0x1d, + 0x04, 0x22, 0x19, 0x1d, 0x22, 0x00, 0x05, 0x22, + 0x1b, 0x1d, 0x1d, 0x11, 0x04, 0x1d, 0x0d, 0x1d, + 0x1d, 0x0b, 0x06, 0x22, 0x1e, 0x04, 0x22, 0x35, + 0x06, 0x00, 0x0f, 0x9d, 0x0d, 0x0f, 0x9d, 0x27, + 0x06, 0x00, 0x1d, 0x1d, 0x20, 0x00, 0x1c, 0x01, + 0x0a, 0x1e, 0x06, 0x1e, 0x08, 0x0e, 0x1d, 0x12, + 0x1e, 0x0a, 0x0c, 0x21, 0x1d, 0x12, 0x1d, 0x23, + 0x20, 0x21, 0x0c, 0x1d, 0x1e, 0x35, 0x06, 0x00, + 0x0f, 0x14, 0x27, 0x06, 0x0e, 0x1d, 0x22, 0xff, + 0x00, 0x1d, 0x1d, 0x20, 0xff, 0x12, 0x1d, 0x23, + 0x20, 0xff, 0x21, 0x0c, 0x1d, 0x1e, 0x27, 0x06, + 0x05, 0x1d, 0xff, 0x05, 0x1d, 0x00, 0x1d, 0x20, + 0x27, 0x06, 0x0a, 0xa5, 0x00, 0x1d, 0x2c, 0x00, + 0x01, 0x30, 0x02, 0x30, 0x3a, 0x00, 0x3b, 0x00, + 0x21, 0x00, 0x3f, 0x00, 0x16, 0x30, 0x17, 0x30, + 0x26, 0x20, 0x13, 0x20, 0x12, 0x01, 0x00, 0x5f, + 0x5f, 0x28, 0x29, 0x7b, 0x7d, 0x08, 0x30, 0x0c, + 0x0d, 0x08, 0x09, 0x02, 0x03, 0x00, 0x01, 0x04, + 0x05, 0x06, 0x07, 0x5b, 0x00, 0x5d, 0x00, 0x3e, + 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x3e, 0x20, 0x5f, + 0x00, 0x5f, 0x00, 0x5f, 0x00, 0x2c, 0x00, 0x01, + 0x30, 0x2e, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x3a, + 0x00, 0x3f, 0x00, 0x21, 0x00, 0x14, 0x20, 0x28, + 0x00, 0x29, 0x00, 0x7b, 0x00, 0x7d, 0x00, 0x14, + 0x30, 0x15, 0x30, 0x23, 0x26, 0x2a, 0x2b, 0x2d, + 0x3c, 0x3e, 0x3d, 0x00, 0x5c, 0x24, 0x25, 0x40, + 0x40, 0x06, 0xff, 0x0b, 0x00, 0x0b, 0xff, 0x0c, + 0x20, 0x00, 0x4d, 0x06, 0x40, 0x06, 0xff, 0x0e, + 0x00, 0x0e, 0xff, 0x0f, 0x00, 0x0f, 0xff, 0x10, + 0x00, 0x10, 0xff, 0x11, 0x00, 0x11, 0xff, 0x12, + 0x00, 0x12, 0x21, 0x06, 0x00, 0x01, 0x01, 0x02, + 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x05, + 0x05, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, + 0x08, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0f, + 0x0f, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x12, + 0x12, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, + 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, + 0x16, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, + 0x18, 0x19, 0x19, 0x19, 0x19, 0x20, 0x20, 0x20, + 0x20, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, + 0x22, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, + 0x24, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, + 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x29, 0x29, + 0x29, 0x22, 0x06, 0x22, 0x00, 0x22, 0x00, 0x22, + 0x01, 0x22, 0x01, 0x22, 0x03, 0x22, 0x03, 0x22, + 0x05, 0x22, 0x05, 0x21, 0x00, 0x85, 0x29, 0x01, + 0x30, 0x01, 0x0b, 0x0c, 0x00, 0xfa, 0xf1, 0xa0, + 0xa2, 0xa4, 0xa6, 0xa8, 0xe2, 0xe4, 0xe6, 0xc2, + 0xfb, 0xa1, 0xa3, 0xa5, 0xa7, 0xa9, 0xaa, 0xac, + 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, + 0xbe, 0xc0, 0xc3, 0xc5, 0xc7, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xd1, 0xd4, 0xd7, 0xda, 0xdd, + 0xde, 0xdf, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xe8, + 0xe9, 0xea, 0xeb, 0xec, 0xee, 0xf2, 0x98, 0x99, + 0x31, 0x31, 0x4f, 0x31, 0x55, 0x31, 0x5b, 0x31, + 0x61, 0x31, 0xa2, 0x00, 0xa3, 0x00, 0xac, 0x00, + 0xaf, 0x00, 0xa6, 0x00, 0xa5, 0x00, 0xa9, 0x20, + 0x00, 0x00, 0x02, 0x25, 0x90, 0x21, 0x91, 0x21, + 0x92, 0x21, 0x93, 0x21, 0xa0, 0x25, 0xcb, 0x25, + 0xd2, 0x05, 0x07, 0x03, 0x01, 0xda, 0x05, 0x07, + 0x03, 0x01, 0xd0, 0x02, 0xd1, 0x02, 0xe6, 0x00, + 0x99, 0x02, 0x53, 0x02, 0x00, 0x00, 0xa3, 0x02, + 0x66, 0xab, 0xa5, 0x02, 0xa4, 0x02, 0x56, 0x02, + 0x57, 0x02, 0x91, 0x1d, 0x58, 0x02, 0x5e, 0x02, + 0xa9, 0x02, 0x64, 0x02, 0x62, 0x02, 0x60, 0x02, + 0x9b, 0x02, 0x27, 0x01, 0x9c, 0x02, 0x67, 0x02, + 0x84, 0x02, 0xaa, 0x02, 0xab, 0x02, 0x6c, 0x02, + 0x04, 0xdf, 0x8e, 0xa7, 0x6e, 0x02, 0x05, 0xdf, + 0x8e, 0x02, 0x06, 0xdf, 0xf8, 0x00, 0x76, 0x02, + 0x77, 0x02, 0x71, 0x00, 0x7a, 0x02, 0x08, 0xdf, + 0x7d, 0x02, 0x7e, 0x02, 0x80, 0x02, 0xa8, 0x02, + 0xa6, 0x02, 0x67, 0xab, 0xa7, 0x02, 0x88, 0x02, + 0x71, 0x2c, 0x00, 0x00, 0x8f, 0x02, 0xa1, 0x02, + 0xa2, 0x02, 0x98, 0x02, 0xc0, 0x01, 0xc1, 0x01, + 0xc2, 0x01, 0x0a, 0xdf, 0x1e, 0xdf, 0x41, 0x04, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x14, 0x99, 0x10, + 0xba, 0x10, 0x00, 0x00, 0x00, 0x00, 0x9b, 0x10, + 0xba, 0x10, 0x05, 0x05, 0xa5, 0x10, 0xba, 0x10, + 0x05, 0x31, 0x11, 0x27, 0x11, 0x32, 0x11, 0x27, + 0x11, 0x55, 0x47, 0x13, 0x3e, 0x13, 0x47, 0x13, + 0x57, 0x13, 0x55, 0x82, 0x13, 0xc9, 0x13, 0x00, + 0x00, 0x00, 0x00, 0x84, 0x13, 0xbb, 0x13, 0x05, + 0x05, 0x8b, 0x13, 0xc2, 0x13, 0x05, 0x90, 0x13, + 0xc9, 0x13, 0x05, 0xc2, 0x13, 0xc2, 0x13, 0x00, + 0x00, 0x00, 0x00, 0xc2, 0x13, 0xb8, 0x13, 0xc2, + 0x13, 0xc9, 0x13, 0x05, 0x55, 0xb9, 0x14, 0xba, + 0x14, 0xb9, 0x14, 0xb0, 0x14, 0x00, 0x00, 0x00, + 0x00, 0xb9, 0x14, 0xbd, 0x14, 0x55, 0x50, 0xb8, + 0x15, 0xaf, 0x15, 0xb9, 0x15, 0xaf, 0x15, 0x55, + 0x35, 0x19, 0x30, 0x19, 0x05, 0x1e, 0x61, 0x1e, + 0x61, 0x1e, 0x61, 0x29, 0x61, 0x1e, 0x61, 0x1f, + 0x61, 0x29, 0x61, 0x1f, 0x61, 0x1e, 0x61, 0x20, + 0x61, 0x21, 0x61, 0x1f, 0x61, 0x22, 0x61, 0x1f, + 0x61, 0x21, 0x61, 0x20, 0x61, 0x55, 0x55, 0x55, + 0x55, 0x67, 0x6d, 0x67, 0x6d, 0x63, 0x6d, 0x67, + 0x6d, 0x69, 0x6d, 0x67, 0x6d, 0x55, 0x05, 0x41, + 0x00, 0x30, 0x00, 0x57, 0xd1, 0x65, 0xd1, 0x58, + 0xd1, 0x65, 0xd1, 0x5f, 0xd1, 0x6e, 0xd1, 0x5f, + 0xd1, 0x6f, 0xd1, 0x5f, 0xd1, 0x70, 0xd1, 0x5f, + 0xd1, 0x71, 0xd1, 0x5f, 0xd1, 0x72, 0xd1, 0x55, + 0x55, 0x55, 0x05, 0xb9, 0xd1, 0x65, 0xd1, 0xba, + 0xd1, 0x65, 0xd1, 0xbb, 0xd1, 0x6e, 0xd1, 0xbc, + 0xd1, 0x6e, 0xd1, 0xbb, 0xd1, 0x6f, 0xd1, 0xbc, + 0xd1, 0x6f, 0xd1, 0x55, 0x55, 0x55, 0x41, 0x00, + 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, 0x69, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x43, 0x44, + 0x00, 0x00, 0x47, 0x00, 0x00, 0x4a, 0x4b, 0x00, + 0x00, 0x4e, 0x4f, 0x50, 0x51, 0x00, 0x53, 0x54, + 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, + 0x63, 0x64, 0x00, 0x66, 0x68, 0x00, 0x70, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x42, 0x00, 0x44, + 0x45, 0x46, 0x47, 0x4a, 0x00, 0x53, 0x00, 0x61, + 0x00, 0x41, 0x42, 0x00, 0x44, 0x45, 0x46, 0x47, + 0x00, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x00, 0x4f, + 0x53, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x41, 0x00, 0x61, 0x00, + 0x41, 0x00, 0x61, 0x00, 0x31, 0x01, 0x37, 0x02, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, + 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, + 0x1f, 0x04, 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, + 0xb1, 0x03, 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, + 0x20, 0x05, 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, + 0xd1, 0x03, 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, + 0x91, 0x03, 0xa3, 0x03, 0xb1, 0x03, 0xd1, 0x03, + 0x24, 0x00, 0x1f, 0x04, 0x20, 0x05, 0x0b, 0x0c, + 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, + 0x30, 0x00, 0x30, 0x04, 0x3a, 0x04, 0x3e, 0x04, + 0x4b, 0x04, 0x4d, 0x04, 0x4e, 0x04, 0x89, 0xa6, + 0x30, 0x04, 0xa9, 0x26, 0x28, 0xb9, 0x7f, 0x9f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0a, 0x0b, 0x0e, 0x0f, 0x11, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x61, 0x26, + 0x25, 0x2f, 0x7b, 0x51, 0xa6, 0xb1, 0x04, 0x27, + 0x06, 0x00, 0x01, 0x05, 0x08, 0x2a, 0x06, 0x1e, + 0x08, 0x03, 0x0d, 0x20, 0x19, 0x1a, 0x1b, 0x1c, + 0x09, 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, + 0x01, 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x44, 0x90, + 0x77, 0x45, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x33, 0x06, 0x17, 0x10, 0x11, 0x12, + 0x13, 0x00, 0x06, 0x0e, 0x02, 0x0f, 0x34, 0x06, + 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, 0x00, 0x00, + 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, 0x2d, 0x06, + 0x00, 0x00, 0x4a, 0x06, 0x00, 0x00, 0x44, 0x06, + 0x00, 0x00, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x00, 0x00, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x00, 0x00, 0x3a, 0x06, + 0x00, 0x00, 0xba, 0x06, 0x00, 0x00, 0x6f, 0x06, + 0x00, 0x00, 0x28, 0x06, 0x2c, 0x06, 0x00, 0x00, + 0x47, 0x06, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x06, + 0x37, 0x06, 0x4a, 0x06, 0x43, 0x06, 0x00, 0x00, + 0x45, 0x06, 0x46, 0x06, 0x33, 0x06, 0x39, 0x06, + 0x41, 0x06, 0x35, 0x06, 0x42, 0x06, 0x00, 0x00, + 0x34, 0x06, 0x2a, 0x06, 0x2b, 0x06, 0x2e, 0x06, + 0x00, 0x00, 0x36, 0x06, 0x38, 0x06, 0x3a, 0x06, + 0x6e, 0x06, 0x00, 0x00, 0xa1, 0x06, 0x27, 0x06, + 0x00, 0x01, 0x05, 0x08, 0x20, 0x21, 0x0b, 0x06, + 0x10, 0x23, 0x2a, 0x06, 0x1a, 0x1b, 0x1c, 0x09, + 0x0f, 0x17, 0x0b, 0x18, 0x07, 0x0a, 0x00, 0x01, + 0x04, 0x06, 0x0c, 0x0e, 0x10, 0x28, 0x06, 0x2c, + 0x06, 0x2f, 0x06, 0x00, 0x00, 0x48, 0x06, 0x32, + 0x06, 0x2d, 0x06, 0x37, 0x06, 0x4a, 0x06, 0x2a, + 0x06, 0x1a, 0x1b, 0x1c, 0x09, 0x0f, 0x17, 0x0b, + 0x18, 0x07, 0x0a, 0x00, 0x01, 0x04, 0x06, 0x0c, + 0x0e, 0x10, 0x30, 0x2e, 0x30, 0x00, 0x2c, 0x00, + 0x28, 0x00, 0x41, 0x00, 0x29, 0x00, 0x14, 0x30, + 0x53, 0x00, 0x15, 0x30, 0x43, 0x52, 0x43, 0x44, + 0x57, 0x5a, 0x41, 0x00, 0x48, 0x56, 0x4d, 0x56, + 0x53, 0x44, 0x53, 0x53, 0x50, 0x50, 0x56, 0x57, + 0x43, 0x4d, 0x43, 0x4d, 0x44, 0x4d, 0x52, 0x44, + 0x4a, 0x4b, 0x30, 0x30, 0x00, 0x68, 0x68, 0x4b, + 0x62, 0x57, 0x5b, 0xcc, 0x53, 0xc7, 0x30, 0x8c, + 0x4e, 0x1a, 0x59, 0xe3, 0x89, 0x29, 0x59, 0xa4, + 0x4e, 0x20, 0x66, 0x21, 0x71, 0x99, 0x65, 0x4d, + 0x52, 0x8c, 0x5f, 0x8d, 0x51, 0xb0, 0x65, 0x1d, + 0x52, 0x42, 0x7d, 0x1f, 0x75, 0xa9, 0x8c, 0xf0, + 0x58, 0x39, 0x54, 0x14, 0x6f, 0x95, 0x62, 0x55, + 0x63, 0x00, 0x4e, 0x09, 0x4e, 0x4a, 0x90, 0xe6, + 0x5d, 0x2d, 0x4e, 0xf3, 0x53, 0x07, 0x63, 0x70, + 0x8d, 0x53, 0x62, 0x81, 0x79, 0x7a, 0x7a, 0x08, + 0x54, 0x80, 0x6e, 0x09, 0x67, 0x08, 0x67, 0x33, + 0x75, 0x72, 0x52, 0xb6, 0x55, 0x4d, 0x91, 0x14, + 0x30, 0x15, 0x30, 0x2c, 0x67, 0x09, 0x4e, 0x8c, + 0x4e, 0x89, 0x5b, 0xb9, 0x70, 0x53, 0x62, 0xd7, + 0x76, 0xdd, 0x52, 0x57, 0x65, 0x97, 0x5f, 0xef, + 0x53, 0x30, 0x00, 0x38, 0x4e, 0x05, 0x00, 0x09, + 0x22, 0x01, 0x60, 0x4f, 0xae, 0x4f, 0xbb, 0x4f, + 0x02, 0x50, 0x7a, 0x50, 0x99, 0x50, 0xe7, 0x50, + 0xcf, 0x50, 0x9e, 0x34, 0x3a, 0x06, 0x4d, 0x51, + 0x54, 0x51, 0x64, 0x51, 0x77, 0x51, 0x1c, 0x05, + 0xb9, 0x34, 0x67, 0x51, 0x8d, 0x51, 0x4b, 0x05, + 0x97, 0x51, 0xa4, 0x51, 0xcc, 0x4e, 0xac, 0x51, + 0xb5, 0x51, 0xdf, 0x91, 0xf5, 0x51, 0x03, 0x52, + 0xdf, 0x34, 0x3b, 0x52, 0x46, 0x52, 0x72, 0x52, + 0x77, 0x52, 0x15, 0x35, 0x02, 0x00, 0x20, 0x80, + 0x80, 0x00, 0x08, 0x00, 0x00, 0xc7, 0x52, 0x00, + 0x02, 0x1d, 0x33, 0x3e, 0x3f, 0x50, 0x82, 0x8a, + 0x93, 0xac, 0xb6, 0xb8, 0xb8, 0xb8, 0x2c, 0x0a, + 0x70, 0x70, 0xca, 0x53, 0xdf, 0x53, 0x63, 0x0b, + 0xeb, 0x53, 0xf1, 0x53, 0x06, 0x54, 0x9e, 0x54, + 0x38, 0x54, 0x48, 0x54, 0x68, 0x54, 0xa2, 0x54, + 0xf6, 0x54, 0x10, 0x55, 0x53, 0x55, 0x63, 0x55, + 0x84, 0x55, 0x84, 0x55, 0x99, 0x55, 0xab, 0x55, + 0xb3, 0x55, 0xc2, 0x55, 0x16, 0x57, 0x06, 0x56, + 0x17, 0x57, 0x51, 0x56, 0x74, 0x56, 0x07, 0x52, + 0xee, 0x58, 0xce, 0x57, 0xf4, 0x57, 0x0d, 0x58, + 0x8b, 0x57, 0x32, 0x58, 0x31, 0x58, 0xac, 0x58, + 0xe4, 0x14, 0xf2, 0x58, 0xf7, 0x58, 0x06, 0x59, + 0x1a, 0x59, 0x22, 0x59, 0x62, 0x59, 0xa8, 0x16, + 0xea, 0x16, 0xec, 0x59, 0x1b, 0x5a, 0x27, 0x5a, + 0xd8, 0x59, 0x66, 0x5a, 0xee, 0x36, 0xfc, 0x36, + 0x08, 0x5b, 0x3e, 0x5b, 0x3e, 0x5b, 0xc8, 0x19, + 0xc3, 0x5b, 0xd8, 0x5b, 0xe7, 0x5b, 0xf3, 0x5b, + 0x18, 0x1b, 0xff, 0x5b, 0x06, 0x5c, 0x53, 0x5f, + 0x22, 0x5c, 0x81, 0x37, 0x60, 0x5c, 0x6e, 0x5c, + 0xc0, 0x5c, 0x8d, 0x5c, 0xe4, 0x1d, 0x43, 0x5d, + 0xe6, 0x1d, 0x6e, 0x5d, 0x6b, 0x5d, 0x7c, 0x5d, + 0xe1, 0x5d, 0xe2, 0x5d, 0x2f, 0x38, 0xfd, 0x5d, + 0x28, 0x5e, 0x3d, 0x5e, 0x69, 0x5e, 0x62, 0x38, + 0x83, 0x21, 0x7c, 0x38, 0xb0, 0x5e, 0xb3, 0x5e, + 0xb6, 0x5e, 0xca, 0x5e, 0x92, 0xa3, 0xfe, 0x5e, + 0x31, 0x23, 0x31, 0x23, 0x01, 0x82, 0x22, 0x5f, + 0x22, 0x5f, 0xc7, 0x38, 0xb8, 0x32, 0xda, 0x61, + 0x62, 0x5f, 0x6b, 0x5f, 0xe3, 0x38, 0x9a, 0x5f, + 0xcd, 0x5f, 0xd7, 0x5f, 0xf9, 0x5f, 0x81, 0x60, + 0x3a, 0x39, 0x1c, 0x39, 0x94, 0x60, 0xd4, 0x26, + 0xc7, 0x60, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, + 0x02, 0x08, 0x00, 0x80, 0x08, 0x00, 0x00, 0x08, + 0x80, 0x28, 0x80, 0x02, 0x00, 0x00, 0x02, 0x48, + 0x61, 0x00, 0x04, 0x06, 0x04, 0x32, 0x46, 0x6a, + 0x5c, 0x67, 0x96, 0xaa, 0xae, 0xc8, 0xd3, 0x5d, + 0x62, 0x00, 0x54, 0x77, 0xf3, 0x0c, 0x2b, 0x3d, + 0x63, 0xfc, 0x62, 0x68, 0x63, 0x83, 0x63, 0xe4, + 0x63, 0xf1, 0x2b, 0x22, 0x64, 0xc5, 0x63, 0xa9, + 0x63, 0x2e, 0x3a, 0x69, 0x64, 0x7e, 0x64, 0x9d, + 0x64, 0x77, 0x64, 0x6c, 0x3a, 0x4f, 0x65, 0x6c, + 0x65, 0x0a, 0x30, 0xe3, 0x65, 0xf8, 0x66, 0x49, + 0x66, 0x19, 0x3b, 0x91, 0x66, 0x08, 0x3b, 0xe4, + 0x3a, 0x92, 0x51, 0x95, 0x51, 0x00, 0x67, 0x9c, + 0x66, 0xad, 0x80, 0xd9, 0x43, 0x17, 0x67, 0x1b, + 0x67, 0x21, 0x67, 0x5e, 0x67, 0x53, 0x67, 0xc3, + 0x33, 0x49, 0x3b, 0xfa, 0x67, 0x85, 0x67, 0x52, + 0x68, 0x85, 0x68, 0x6d, 0x34, 0x8e, 0x68, 0x1f, + 0x68, 0x14, 0x69, 0x9d, 0x3b, 0x42, 0x69, 0xa3, + 0x69, 0xea, 0x69, 0xa8, 0x6a, 0xa3, 0x36, 0xdb, + 0x6a, 0x18, 0x3c, 0x21, 0x6b, 0xa7, 0x38, 0x54, + 0x6b, 0x4e, 0x3c, 0x72, 0x6b, 0x9f, 0x6b, 0xba, + 0x6b, 0xbb, 0x6b, 0x8d, 0x3a, 0x0b, 0x1d, 0xfa, + 0x3a, 0x4e, 0x6c, 0xbc, 0x3c, 0xbf, 0x6c, 0xcd, + 0x6c, 0x67, 0x6c, 0x16, 0x6d, 0x3e, 0x6d, 0x77, + 0x6d, 0x41, 0x6d, 0x69, 0x6d, 0x78, 0x6d, 0x85, + 0x6d, 0x1e, 0x3d, 0x34, 0x6d, 0x2f, 0x6e, 0x6e, + 0x6e, 0x33, 0x3d, 0xcb, 0x6e, 0xc7, 0x6e, 0xd1, + 0x3e, 0xf9, 0x6d, 0x6e, 0x6f, 0x5e, 0x3f, 0x8e, + 0x3f, 0xc6, 0x6f, 0x39, 0x70, 0x1e, 0x70, 0x1b, + 0x70, 0x96, 0x3d, 0x4a, 0x70, 0x7d, 0x70, 0x77, + 0x70, 0xad, 0x70, 0x25, 0x05, 0x45, 0x71, 0x63, + 0x42, 0x9c, 0x71, 0xab, 0x43, 0x28, 0x72, 0x35, + 0x72, 0x50, 0x72, 0x08, 0x46, 0x80, 0x72, 0x95, + 0x72, 0x35, 0x47, 0x02, 0x20, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x00, 0x00, + 0x02, 0x02, 0x80, 0x8a, 0x00, 0x00, 0x20, 0x00, + 0x08, 0x0a, 0x00, 0x80, 0x88, 0x80, 0x20, 0x14, + 0x48, 0x7a, 0x73, 0x8b, 0x73, 0xac, 0x3e, 0xa5, + 0x73, 0xb8, 0x3e, 0xb8, 0x3e, 0x47, 0x74, 0x5c, + 0x74, 0x71, 0x74, 0x85, 0x74, 0xca, 0x74, 0x1b, + 0x3f, 0x24, 0x75, 0x36, 0x4c, 0x3e, 0x75, 0x92, + 0x4c, 0x70, 0x75, 0x9f, 0x21, 0x10, 0x76, 0xa1, + 0x4f, 0xb8, 0x4f, 0x44, 0x50, 0xfc, 0x3f, 0x08, + 0x40, 0xf4, 0x76, 0xf3, 0x50, 0xf2, 0x50, 0x19, + 0x51, 0x33, 0x51, 0x1e, 0x77, 0x1f, 0x77, 0x1f, + 0x77, 0x4a, 0x77, 0x39, 0x40, 0x8b, 0x77, 0x46, + 0x40, 0x96, 0x40, 0x1d, 0x54, 0x4e, 0x78, 0x8c, + 0x78, 0xcc, 0x78, 0xe3, 0x40, 0x26, 0x56, 0x56, + 0x79, 0x9a, 0x56, 0xc5, 0x56, 0x8f, 0x79, 0xeb, + 0x79, 0x2f, 0x41, 0x40, 0x7a, 0x4a, 0x7a, 0x4f, + 0x7a, 0x7c, 0x59, 0xa7, 0x5a, 0xa7, 0x5a, 0xee, + 0x7a, 0x02, 0x42, 0xab, 0x5b, 0xc6, 0x7b, 0xc9, + 0x7b, 0x27, 0x42, 0x80, 0x5c, 0xd2, 0x7c, 0xa0, + 0x42, 0xe8, 0x7c, 0xe3, 0x7c, 0x00, 0x7d, 0x86, + 0x5f, 0x63, 0x7d, 0x01, 0x43, 0xc7, 0x7d, 0x02, + 0x7e, 0x45, 0x7e, 0x34, 0x43, 0x28, 0x62, 0x47, + 0x62, 0x59, 0x43, 0xd9, 0x62, 0x7a, 0x7f, 0x3e, + 0x63, 0x95, 0x7f, 0xfa, 0x7f, 0x05, 0x80, 0xda, + 0x64, 0x23, 0x65, 0x60, 0x80, 0xa8, 0x65, 0x70, + 0x80, 0x5f, 0x33, 0xd5, 0x43, 0xb2, 0x80, 0x03, + 0x81, 0x0b, 0x44, 0x3e, 0x81, 0xb5, 0x5a, 0xa7, + 0x67, 0xb5, 0x67, 0x93, 0x33, 0x9c, 0x33, 0x01, + 0x82, 0x04, 0x82, 0x9e, 0x8f, 0x6b, 0x44, 0x91, + 0x82, 0x8b, 0x82, 0x9d, 0x82, 0xb3, 0x52, 0xb1, + 0x82, 0xb3, 0x82, 0xbd, 0x82, 0xe6, 0x82, 0x3c, + 0x6b, 0xe5, 0x82, 0x1d, 0x83, 0x63, 0x83, 0xad, + 0x83, 0x23, 0x83, 0xbd, 0x83, 0xe7, 0x83, 0x57, + 0x84, 0x53, 0x83, 0xca, 0x83, 0xcc, 0x83, 0xdc, + 0x83, 0x36, 0x6c, 0x6b, 0x6d, 0x02, 0x00, 0x00, + 0x20, 0x22, 0x2a, 0xa0, 0x0a, 0x00, 0x20, 0x80, + 0x28, 0x00, 0xa8, 0x20, 0x20, 0x00, 0x02, 0x80, + 0x22, 0x02, 0x8a, 0x08, 0x00, 0xaa, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x28, 0xd5, 0x6c, 0x2b, + 0x45, 0xf1, 0x84, 0xf3, 0x84, 0x16, 0x85, 0xca, + 0x73, 0x64, 0x85, 0x2c, 0x6f, 0x5d, 0x45, 0x61, + 0x45, 0xb1, 0x6f, 0xd2, 0x70, 0x6b, 0x45, 0x50, + 0x86, 0x5c, 0x86, 0x67, 0x86, 0x69, 0x86, 0xa9, + 0x86, 0x88, 0x86, 0x0e, 0x87, 0xe2, 0x86, 0x79, + 0x87, 0x28, 0x87, 0x6b, 0x87, 0x86, 0x87, 0xd7, + 0x45, 0xe1, 0x87, 0x01, 0x88, 0xf9, 0x45, 0x60, + 0x88, 0x63, 0x88, 0x67, 0x76, 0xd7, 0x88, 0xde, + 0x88, 0x35, 0x46, 0xfa, 0x88, 0xbb, 0x34, 0xae, + 0x78, 0x66, 0x79, 0xbe, 0x46, 0xc7, 0x46, 0xa0, + 0x8a, 0xed, 0x8a, 0x8a, 0x8b, 0x55, 0x8c, 0xa8, + 0x7c, 0xab, 0x8c, 0xc1, 0x8c, 0x1b, 0x8d, 0x77, + 0x8d, 0x2f, 0x7f, 0x04, 0x08, 0xcb, 0x8d, 0xbc, + 0x8d, 0xf0, 0x8d, 0xde, 0x08, 0xd4, 0x8e, 0x38, + 0x8f, 0xd2, 0x85, 0xed, 0x85, 0x94, 0x90, 0xf1, + 0x90, 0x11, 0x91, 0x2e, 0x87, 0x1b, 0x91, 0x38, + 0x92, 0xd7, 0x92, 0xd8, 0x92, 0x7c, 0x92, 0xf9, + 0x93, 0x15, 0x94, 0xfa, 0x8b, 0x8b, 0x95, 0x95, + 0x49, 0xb7, 0x95, 0x77, 0x8d, 0xe6, 0x49, 0xc3, + 0x96, 0xb2, 0x5d, 0x23, 0x97, 0x45, 0x91, 0x1a, + 0x92, 0x6e, 0x4a, 0x76, 0x4a, 0xe0, 0x97, 0x0a, + 0x94, 0xb2, 0x4a, 0x96, 0x94, 0x0b, 0x98, 0x0b, + 0x98, 0x29, 0x98, 0xb6, 0x95, 0xe2, 0x98, 0x33, + 0x4b, 0x29, 0x99, 0xa7, 0x99, 0xc2, 0x99, 0xfe, + 0x99, 0xce, 0x4b, 0x30, 0x9b, 0x12, 0x9b, 0x40, + 0x9c, 0xfd, 0x9c, 0xce, 0x4c, 0xed, 0x4c, 0x67, + 0x9d, 0xce, 0xa0, 0xf8, 0x4c, 0x05, 0xa1, 0x0e, + 0xa2, 0x91, 0xa2, 0xbb, 0x9e, 0x56, 0x4d, 0xf9, + 0x9e, 0xfe, 0x9e, 0x05, 0x9f, 0x0f, 0x9f, 0x16, + 0x9f, 0x3b, 0x9f, 0x00, 0xa6, 0x02, 0x88, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x28, 0x00, + 0x08, 0xa0, 0x80, 0xa0, 0x80, 0x00, 0x80, 0x80, + 0x00, 0x0a, 0x88, 0x80, 0x00, 0x80, 0x00, 0x20, + 0x2a, 0x00, 0x80, +}; + +static const uint16_t unicode_comp_table[965] = { + 0x4a01, 0x49c0, 0x4a02, 0x0280, 0x0281, 0x0282, 0x0283, 0x02c0, + 0x02c2, 0x0a00, 0x0284, 0x2442, 0x0285, 0x07c0, 0x0980, 0x0982, + 0x2440, 0x2280, 0x02c4, 0x2282, 0x2284, 0x2286, 0x02c6, 0x02c8, + 0x02ca, 0x02cc, 0x0287, 0x228a, 0x02ce, 0x228c, 0x2290, 0x2292, + 0x228e, 0x0288, 0x0289, 0x028a, 0x2482, 0x0300, 0x0302, 0x0304, + 0x028b, 0x2480, 0x0308, 0x0984, 0x0986, 0x2458, 0x0a02, 0x0306, + 0x2298, 0x229a, 0x229e, 0x0900, 0x030a, 0x22a0, 0x030c, 0x030e, + 0x0840, 0x0310, 0x0312, 0x22a2, 0x22a6, 0x09c0, 0x22a4, 0x22a8, + 0x22aa, 0x028c, 0x028d, 0x028e, 0x0340, 0x0342, 0x0344, 0x0380, + 0x028f, 0x248e, 0x07c2, 0x0988, 0x098a, 0x2490, 0x0346, 0x22ac, + 0x0400, 0x22b0, 0x0842, 0x22b2, 0x0402, 0x22b4, 0x0440, 0x0444, + 0x22b6, 0x0442, 0x22c2, 0x22c0, 0x22c4, 0x22c6, 0x22c8, 0x0940, + 0x04c0, 0x0291, 0x22ca, 0x04c4, 0x22cc, 0x04c2, 0x22d0, 0x22ce, + 0x0292, 0x0293, 0x0294, 0x0295, 0x0540, 0x0542, 0x0a08, 0x0296, + 0x2494, 0x0544, 0x07c4, 0x098c, 0x098e, 0x06c0, 0x2492, 0x0844, + 0x2308, 0x230a, 0x0580, 0x230c, 0x0584, 0x0990, 0x0992, 0x230e, + 0x0582, 0x2312, 0x0586, 0x0588, 0x2314, 0x058c, 0x2316, 0x0998, + 0x058a, 0x231e, 0x0590, 0x2320, 0x099a, 0x058e, 0x2324, 0x2322, + 0x0299, 0x029a, 0x029b, 0x05c0, 0x05c2, 0x05c4, 0x029c, 0x24ac, + 0x05c6, 0x05c8, 0x07c6, 0x0994, 0x0996, 0x0700, 0x24aa, 0x2326, + 0x05ca, 0x232a, 0x2328, 0x2340, 0x2342, 0x2344, 0x2346, 0x05cc, + 0x234a, 0x2348, 0x234c, 0x234e, 0x2350, 0x24b8, 0x029d, 0x05ce, + 0x24be, 0x0a0c, 0x2352, 0x0600, 0x24bc, 0x24ba, 0x0640, 0x2354, + 0x0642, 0x0644, 0x2356, 0x2358, 0x02a0, 0x02a1, 0x02a2, 0x02a3, + 0x02c1, 0x02c3, 0x0a01, 0x02a4, 0x2443, 0x02a5, 0x07c1, 0x0981, + 0x0983, 0x2441, 0x2281, 0x02c5, 0x2283, 0x2285, 0x2287, 0x02c7, + 0x02c9, 0x02cb, 0x02cd, 0x02a7, 0x228b, 0x02cf, 0x228d, 0x2291, + 0x2293, 0x228f, 0x02a8, 0x02a9, 0x02aa, 0x2483, 0x0301, 0x0303, + 0x0305, 0x02ab, 0x2481, 0x0309, 0x0985, 0x0987, 0x2459, 0x0a03, + 0x0307, 0x2299, 0x229b, 0x229f, 0x0901, 0x030b, 0x22a1, 0x030d, + 0x030f, 0x0841, 0x0311, 0x0313, 0x22a3, 0x22a7, 0x09c1, 0x22a5, + 0x22a9, 0x22ab, 0x2380, 0x02ac, 0x02ad, 0x02ae, 0x0341, 0x0343, + 0x0345, 0x02af, 0x248f, 0x07c3, 0x0989, 0x098b, 0x2491, 0x0347, + 0x22ad, 0x0401, 0x0884, 0x22b1, 0x0843, 0x22b3, 0x0403, 0x22b5, + 0x0441, 0x0445, 0x22b7, 0x0443, 0x22c3, 0x22c1, 0x22c5, 0x22c7, + 0x22c9, 0x0941, 0x04c1, 0x02b1, 0x22cb, 0x04c5, 0x22cd, 0x04c3, + 0x22d1, 0x22cf, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x0541, 0x0543, + 0x0a09, 0x02b6, 0x2495, 0x0545, 0x07c5, 0x098d, 0x098f, 0x06c1, + 0x2493, 0x0845, 0x2309, 0x230b, 0x0581, 0x230d, 0x0585, 0x0991, + 0x0993, 0x230f, 0x0583, 0x2313, 0x0587, 0x0589, 0x2315, 0x058d, + 0x2317, 0x0999, 0x058b, 0x231f, 0x2381, 0x0591, 0x2321, 0x099b, + 0x058f, 0x2325, 0x2323, 0x02b9, 0x02ba, 0x02bb, 0x05c1, 0x05c3, + 0x05c5, 0x02bc, 0x24ad, 0x05c7, 0x05c9, 0x07c7, 0x0995, 0x0997, + 0x0701, 0x24ab, 0x2327, 0x05cb, 0x232b, 0x2329, 0x2341, 0x2343, + 0x2345, 0x2347, 0x05cd, 0x234b, 0x2349, 0x2382, 0x234d, 0x234f, + 0x2351, 0x24b9, 0x02bd, 0x05cf, 0x24bf, 0x0a0d, 0x2353, 0x02bf, + 0x24bd, 0x2383, 0x24bb, 0x0641, 0x2355, 0x0643, 0x0645, 0x2357, + 0x2359, 0x3101, 0x0c80, 0x2e00, 0x2446, 0x2444, 0x244a, 0x2448, + 0x0800, 0x0942, 0x0944, 0x0804, 0x2288, 0x2486, 0x2484, 0x248a, + 0x2488, 0x22ae, 0x2498, 0x2496, 0x249c, 0x249a, 0x2300, 0x0a06, + 0x2302, 0x0a04, 0x0946, 0x07ce, 0x07ca, 0x07c8, 0x07cc, 0x2447, + 0x2445, 0x244b, 0x2449, 0x0801, 0x0943, 0x0945, 0x0805, 0x2289, + 0x2487, 0x2485, 0x248b, 0x2489, 0x22af, 0x2499, 0x2497, 0x249d, + 0x249b, 0x2301, 0x0a07, 0x2303, 0x0a05, 0x0947, 0x07cf, 0x07cb, + 0x07c9, 0x07cd, 0x2450, 0x244e, 0x2454, 0x2452, 0x2451, 0x244f, + 0x2455, 0x2453, 0x2294, 0x2296, 0x2295, 0x2297, 0x2304, 0x2306, + 0x2305, 0x2307, 0x2318, 0x2319, 0x231a, 0x231b, 0x232c, 0x232d, + 0x232e, 0x232f, 0x2400, 0x24a2, 0x24a0, 0x24a6, 0x24a4, 0x24a8, + 0x24a3, 0x24a1, 0x24a7, 0x24a5, 0x24a9, 0x24b0, 0x24ae, 0x24b4, + 0x24b2, 0x24b6, 0x24b1, 0x24af, 0x24b5, 0x24b3, 0x24b7, 0x0882, + 0x0880, 0x0881, 0x0802, 0x0803, 0x229c, 0x229d, 0x0a0a, 0x0a0b, + 0x0883, 0x0b40, 0x2c8a, 0x0c81, 0x2c89, 0x2c88, 0x2540, 0x2541, + 0x2d00, 0x2e07, 0x0d00, 0x2640, 0x2641, 0x2e80, 0x0d01, 0x26c8, + 0x26c9, 0x2f00, 0x2f84, 0x0d02, 0x2f83, 0x2f82, 0x0d40, 0x26d8, + 0x26d9, 0x3186, 0x0d04, 0x2740, 0x2741, 0x3100, 0x3086, 0x0d06, + 0x3085, 0x3084, 0x0d41, 0x2840, 0x3200, 0x0d07, 0x284f, 0x2850, + 0x3280, 0x2c84, 0x2e03, 0x2857, 0x0d42, 0x2c81, 0x2c80, 0x24c0, + 0x24c1, 0x2c86, 0x2c83, 0x28c0, 0x0d43, 0x25c0, 0x25c1, 0x2940, + 0x0d44, 0x26c0, 0x26c1, 0x2e05, 0x2e02, 0x29c0, 0x0d45, 0x2f05, + 0x2f04, 0x0d80, 0x26d0, 0x26d1, 0x2f80, 0x2a40, 0x0d82, 0x26e0, + 0x26e1, 0x3080, 0x3081, 0x2ac0, 0x0d83, 0x3004, 0x3003, 0x0d81, + 0x27c0, 0x27c1, 0x3082, 0x2b40, 0x0d84, 0x2847, 0x2848, 0x3184, + 0x3181, 0x2f06, 0x0d08, 0x2f81, 0x3005, 0x0d46, 0x3083, 0x3182, + 0x0e00, 0x0e01, 0x0f40, 0x1180, 0x1182, 0x0f03, 0x0f00, 0x11c0, + 0x0f01, 0x1140, 0x1202, 0x1204, 0x0f81, 0x1240, 0x0fc0, 0x1242, + 0x0f80, 0x1244, 0x1284, 0x0f82, 0x1286, 0x1288, 0x128a, 0x12c0, + 0x1282, 0x1181, 0x1183, 0x1043, 0x1040, 0x11c1, 0x1041, 0x1141, + 0x1203, 0x1205, 0x10c1, 0x1241, 0x1000, 0x1243, 0x10c0, 0x1245, + 0x1285, 0x10c2, 0x1287, 0x1289, 0x128b, 0x12c1, 0x1283, 0x1080, + 0x1100, 0x1101, 0x1200, 0x1201, 0x1280, 0x1281, 0x1340, 0x1341, + 0x1343, 0x1342, 0x1344, 0x13c2, 0x1400, 0x13c0, 0x1440, 0x1480, + 0x14c0, 0x1540, 0x1541, 0x1740, 0x1700, 0x1741, 0x17c0, 0x1800, + 0x1802, 0x1801, 0x1840, 0x1880, 0x1900, 0x18c0, 0x18c1, 0x1901, + 0x1940, 0x1942, 0x1941, 0x1980, 0x19c0, 0x19c2, 0x19c1, 0x1c80, + 0x1cc0, 0x1dc0, 0x1f80, 0x2000, 0x2002, 0x2004, 0x2006, 0x2008, + 0x2040, 0x2080, 0x2082, 0x20c0, 0x20c1, 0x2100, 0x22b8, 0x22b9, + 0x2310, 0x2311, 0x231c, 0x231d, 0x244c, 0x2456, 0x244d, 0x2457, + 0x248c, 0x248d, 0x249e, 0x249f, 0x2500, 0x2502, 0x2504, 0x2bc0, + 0x2501, 0x2503, 0x2505, 0x2bc1, 0x2bc2, 0x2bc3, 0x2bc4, 0x2bc5, + 0x2bc6, 0x2bc7, 0x2580, 0x2582, 0x2584, 0x2bc8, 0x2581, 0x2583, + 0x2585, 0x2bc9, 0x2bca, 0x2bcb, 0x2bcc, 0x2bcd, 0x2bce, 0x2bcf, + 0x2600, 0x2602, 0x2601, 0x2603, 0x2680, 0x2682, 0x2681, 0x2683, + 0x26c2, 0x26c4, 0x26c6, 0x2c00, 0x26c3, 0x26c5, 0x26c7, 0x2c01, + 0x2c02, 0x2c03, 0x2c04, 0x2c05, 0x2c06, 0x2c07, 0x26ca, 0x26cc, + 0x26ce, 0x2c08, 0x26cb, 0x26cd, 0x26cf, 0x2c09, 0x2c0a, 0x2c0b, + 0x2c0c, 0x2c0d, 0x2c0e, 0x2c0f, 0x26d2, 0x26d4, 0x26d6, 0x26d3, + 0x26d5, 0x26d7, 0x26da, 0x26dc, 0x26de, 0x26db, 0x26dd, 0x26df, + 0x2700, 0x2702, 0x2701, 0x2703, 0x2780, 0x2782, 0x2781, 0x2783, + 0x2800, 0x2802, 0x2804, 0x2801, 0x2803, 0x2805, 0x2842, 0x2844, + 0x2846, 0x2849, 0x284b, 0x284d, 0x2c40, 0x284a, 0x284c, 0x284e, + 0x2c41, 0x2c42, 0x2c43, 0x2c44, 0x2c45, 0x2c46, 0x2c47, 0x2851, + 0x2853, 0x2855, 0x2c48, 0x2852, 0x2854, 0x2856, 0x2c49, 0x2c4a, + 0x2c4b, 0x2c4c, 0x2c4d, 0x2c4e, 0x2c4f, 0x2c82, 0x2e01, 0x3180, + 0x2c87, 0x2f01, 0x2f02, 0x2f03, 0x2e06, 0x3185, 0x3000, 0x3001, + 0x3002, 0x4640, 0x4641, 0x4680, 0x46c0, 0x46c2, 0x46c1, 0x4700, + 0x4740, 0x4780, 0x47c0, 0x47c2, 0x4900, 0x4940, 0x4980, 0x4982, + 0x4a00, 0x49c2, 0x4a03, 0x4a04, 0x4a40, 0x4a41, 0x4a80, 0x4a81, + 0x4ac0, 0x4ac1, 0x4bc0, 0x4bc1, 0x4b00, 0x4b01, 0x4b40, 0x4b41, + 0x4bc2, 0x4bc3, 0x4b80, 0x4b81, 0x4b82, 0x4b83, 0x4c00, 0x4c01, + 0x4c02, 0x4c03, 0x5600, 0x5440, 0x5442, 0x5444, 0x5446, 0x5448, + 0x544a, 0x544c, 0x544e, 0x5450, 0x5452, 0x5454, 0x5456, 0x5480, + 0x5482, 0x5484, 0x54c0, 0x54c1, 0x5500, 0x5501, 0x5540, 0x5541, + 0x5580, 0x5581, 0x55c0, 0x55c1, 0x5680, 0x58c0, 0x5700, 0x5702, + 0x5704, 0x5706, 0x5708, 0x570a, 0x570c, 0x570e, 0x5710, 0x5712, + 0x5714, 0x5716, 0x5740, 0x5742, 0x5744, 0x5780, 0x5781, 0x57c0, + 0x57c1, 0x5800, 0x5801, 0x5840, 0x5841, 0x5880, 0x5881, 0x5900, + 0x5901, 0x5902, 0x5903, 0x5940, 0x8ec0, 0x8f00, 0x8fc0, 0x8fc2, + 0x9000, 0x9040, 0x9041, 0x9080, 0x9081, 0x90c0, 0x90c2, 0x9100, + 0x9140, 0x9182, 0x9180, 0x9183, 0x91c1, 0x91c0, 0x91c3, 0x9200, + 0x9201, 0x9240, 0x9280, 0x9282, 0x9284, 0x9281, 0x9285, 0x9287, + 0x9286, 0x9283, 0x92c1, 0x92c0, 0x92c2, +}; + +typedef enum { + UNICODE_GC_Cn, + UNICODE_GC_Lu, + UNICODE_GC_Ll, + UNICODE_GC_Lt, + UNICODE_GC_Lm, + UNICODE_GC_Lo, + UNICODE_GC_Mn, + UNICODE_GC_Mc, + UNICODE_GC_Me, + UNICODE_GC_Nd, + UNICODE_GC_Nl, + UNICODE_GC_No, + UNICODE_GC_Sm, + UNICODE_GC_Sc, + UNICODE_GC_Sk, + UNICODE_GC_So, + UNICODE_GC_Pc, + UNICODE_GC_Pd, + UNICODE_GC_Ps, + UNICODE_GC_Pe, + UNICODE_GC_Pi, + UNICODE_GC_Pf, + UNICODE_GC_Po, + UNICODE_GC_Zs, + UNICODE_GC_Zl, + UNICODE_GC_Zp, + UNICODE_GC_Cc, + UNICODE_GC_Cf, + UNICODE_GC_Cs, + UNICODE_GC_Co, + UNICODE_GC_LC, + UNICODE_GC_L, + UNICODE_GC_M, + UNICODE_GC_N, + UNICODE_GC_S, + UNICODE_GC_P, + UNICODE_GC_Z, + UNICODE_GC_C, + UNICODE_GC_COUNT, +} UnicodeGCEnum; + +static const char unicode_gc_name_table[] = + "Cn,Unassigned" "\0" + "Lu,Uppercase_Letter" "\0" + "Ll,Lowercase_Letter" "\0" + "Lt,Titlecase_Letter" "\0" + "Lm,Modifier_Letter" "\0" + "Lo,Other_Letter" "\0" + "Mn,Nonspacing_Mark" "\0" + "Mc,Spacing_Mark" "\0" + "Me,Enclosing_Mark" "\0" + "Nd,Decimal_Number,digit" "\0" + "Nl,Letter_Number" "\0" + "No,Other_Number" "\0" + "Sm,Math_Symbol" "\0" + "Sc,Currency_Symbol" "\0" + "Sk,Modifier_Symbol" "\0" + "So,Other_Symbol" "\0" + "Pc,Connector_Punctuation" "\0" + "Pd,Dash_Punctuation" "\0" + "Ps,Open_Punctuation" "\0" + "Pe,Close_Punctuation" "\0" + "Pi,Initial_Punctuation" "\0" + "Pf,Final_Punctuation" "\0" + "Po,Other_Punctuation" "\0" + "Zs,Space_Separator" "\0" + "Zl,Line_Separator" "\0" + "Zp,Paragraph_Separator" "\0" + "Cc,Control,cntrl" "\0" + "Cf,Format" "\0" + "Cs,Surrogate" "\0" + "Co,Private_Use" "\0" + "LC,Cased_Letter" "\0" + "L,Letter" "\0" + "M,Mark,Combining_Mark" "\0" + "N,Number" "\0" + "S,Symbol" "\0" + "P,Punctuation,punct" "\0" + "Z,Separator" "\0" + "C,Other" "\0" +; + +static const uint8_t unicode_gc_table[4070] = { + 0xfa, 0x18, 0x17, 0x56, 0x0d, 0x56, 0x12, 0x13, + 0x16, 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, + 0x4c, 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, + 0x10, 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, + 0xfa, 0x19, 0x17, 0x16, 0x6d, 0x0f, 0x16, 0x0e, + 0x0f, 0x05, 0x14, 0x0c, 0x1b, 0x0f, 0x0e, 0x0f, + 0x0c, 0x2b, 0x0e, 0x02, 0x36, 0x0e, 0x0b, 0x05, + 0x15, 0x4b, 0x16, 0xe1, 0x0f, 0x0c, 0xc1, 0xe2, + 0x10, 0x0c, 0xe2, 0x00, 0xff, 0x30, 0x02, 0xff, + 0x08, 0x02, 0xff, 0x27, 0xbf, 0x22, 0x21, 0x02, + 0x5f, 0x5f, 0x21, 0x22, 0x61, 0x02, 0x21, 0x02, + 0x41, 0x42, 0x21, 0x02, 0x21, 0x02, 0x9f, 0x7f, + 0x02, 0x5f, 0x5f, 0x21, 0x02, 0x5f, 0x3f, 0x02, + 0x05, 0x3f, 0x22, 0x65, 0x01, 0x03, 0x02, 0x01, + 0x03, 0x02, 0x01, 0x03, 0x02, 0xff, 0x08, 0x02, + 0xff, 0x0a, 0x02, 0x01, 0x03, 0x02, 0x5f, 0x21, + 0x02, 0xff, 0x32, 0xa2, 0x21, 0x02, 0x21, 0x22, + 0x5f, 0x41, 0x02, 0xff, 0x00, 0xe2, 0x3c, 0x05, + 0xe2, 0x13, 0xe4, 0x0a, 0x6e, 0xe4, 0x04, 0xee, + 0x06, 0x84, 0xce, 0x04, 0x0e, 0x04, 0xee, 0x09, + 0xe6, 0x68, 0x7f, 0x04, 0x0e, 0x3f, 0x20, 0x04, + 0x42, 0x16, 0x01, 0x60, 0x2e, 0x01, 0x16, 0x41, + 0x00, 0x01, 0x00, 0x21, 0x02, 0xe1, 0x09, 0x00, + 0xe1, 0x01, 0xe2, 0x1b, 0x3f, 0x02, 0x41, 0x42, + 0xff, 0x10, 0x62, 0x3f, 0x0c, 0x5f, 0x3f, 0x02, + 0xe1, 0x2b, 0xe2, 0x28, 0xff, 0x1a, 0x0f, 0x86, + 0x28, 0xff, 0x2f, 0xff, 0x06, 0x02, 0xff, 0x58, + 0x00, 0xe1, 0x1e, 0x20, 0x04, 0xb6, 0xe2, 0x21, + 0x16, 0x11, 0x20, 0x2f, 0x0d, 0x00, 0xe6, 0x25, + 0x11, 0x06, 0x16, 0x26, 0x16, 0x26, 0x16, 0x06, + 0xe0, 0x00, 0xe5, 0x13, 0x60, 0x65, 0x36, 0xe0, + 0x03, 0xbb, 0x4c, 0x36, 0x0d, 0x36, 0x2f, 0xe6, + 0x03, 0x16, 0x1b, 0x56, 0xe5, 0x18, 0x04, 0xe5, + 0x02, 0xe6, 0x0d, 0xe9, 0x02, 0x76, 0x25, 0x06, + 0xe5, 0x5b, 0x16, 0x05, 0xc6, 0x1b, 0x0f, 0xa6, + 0x24, 0x26, 0x0f, 0x66, 0x25, 0xe9, 0x02, 0x45, + 0x2f, 0x05, 0xf6, 0x06, 0x00, 0x1b, 0x05, 0x06, + 0xe5, 0x16, 0xe6, 0x13, 0x20, 0xe5, 0x51, 0xe6, + 0x03, 0x05, 0xe0, 0x06, 0xe9, 0x02, 0xe5, 0x19, + 0xe6, 0x01, 0x24, 0x0f, 0x56, 0x04, 0x20, 0x06, + 0x2d, 0xe5, 0x0e, 0x66, 0x04, 0xe6, 0x01, 0x04, + 0x46, 0x04, 0x86, 0x20, 0xf6, 0x07, 0x00, 0xe5, + 0x11, 0x46, 0x20, 0x16, 0x00, 0xe5, 0x03, 0x80, + 0xe5, 0x10, 0x0e, 0xa5, 0x00, 0x3b, 0x80, 0xe6, + 0x01, 0xe5, 0x21, 0x04, 0xe6, 0x10, 0x1b, 0xe6, + 0x18, 0x07, 0xe5, 0x2e, 0x06, 0x07, 0x06, 0x05, + 0x47, 0xe6, 0x00, 0x67, 0x06, 0x27, 0x05, 0xc6, + 0xe5, 0x02, 0x26, 0x36, 0xe9, 0x02, 0x16, 0x04, + 0xe5, 0x07, 0x06, 0x27, 0x00, 0xe5, 0x00, 0x20, + 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x05, + 0x40, 0x65, 0x20, 0x06, 0x05, 0x47, 0x66, 0x20, + 0x27, 0x20, 0x27, 0x06, 0x05, 0xe0, 0x00, 0x07, + 0x60, 0x25, 0x00, 0x45, 0x26, 0x20, 0xe9, 0x02, + 0x25, 0x2d, 0xab, 0x0f, 0x0d, 0x05, 0x16, 0x06, + 0x20, 0x26, 0x07, 0x00, 0xa5, 0x60, 0x25, 0x20, + 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, 0x25, + 0x00, 0x25, 0x20, 0x06, 0x00, 0x47, 0x26, 0x60, + 0x26, 0x20, 0x46, 0x40, 0x06, 0xc0, 0x65, 0x00, + 0x05, 0xc0, 0xe9, 0x02, 0x26, 0x45, 0x06, 0x16, + 0xe0, 0x02, 0x26, 0x07, 0x00, 0xe5, 0x01, 0x00, + 0x45, 0x00, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, + 0x00, 0x85, 0x20, 0x06, 0x05, 0x47, 0x86, 0x00, + 0x26, 0x07, 0x00, 0x27, 0x06, 0x20, 0x05, 0xe0, + 0x07, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x16, 0x0d, + 0xc0, 0x05, 0xa6, 0x00, 0x06, 0x27, 0x00, 0xe5, + 0x00, 0x20, 0x25, 0x20, 0xe5, 0x0e, 0x00, 0xc5, + 0x00, 0x25, 0x00, 0x85, 0x20, 0x06, 0x05, 0x07, + 0x06, 0x07, 0x66, 0x20, 0x27, 0x20, 0x27, 0x06, + 0xc0, 0x26, 0x07, 0x60, 0x25, 0x00, 0x45, 0x26, + 0x20, 0xe9, 0x02, 0x0f, 0x05, 0xab, 0xe0, 0x02, + 0x06, 0x05, 0x00, 0xa5, 0x40, 0x45, 0x00, 0x65, + 0x40, 0x25, 0x00, 0x05, 0x00, 0x25, 0x40, 0x25, + 0x40, 0x45, 0x40, 0xe5, 0x04, 0x60, 0x27, 0x06, + 0x27, 0x40, 0x47, 0x00, 0x47, 0x06, 0x20, 0x05, + 0xa0, 0x07, 0xe0, 0x06, 0xe9, 0x02, 0x4b, 0xaf, + 0x0d, 0x0f, 0x80, 0x06, 0x47, 0x06, 0xe5, 0x00, + 0x00, 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x08, + 0x20, 0x06, 0x05, 0x46, 0x67, 0x00, 0x46, 0x00, + 0x66, 0xc0, 0x26, 0x00, 0x45, 0x20, 0x05, 0x20, + 0x25, 0x26, 0x20, 0xe9, 0x02, 0xc0, 0x16, 0xcb, + 0x0f, 0x05, 0x06, 0x27, 0x16, 0xe5, 0x00, 0x00, + 0x45, 0x00, 0xe5, 0x0f, 0x00, 0xe5, 0x02, 0x00, + 0x85, 0x20, 0x06, 0x05, 0x07, 0x06, 0x87, 0x00, + 0x06, 0x27, 0x00, 0x27, 0x26, 0xc0, 0x27, 0xa0, + 0x25, 0x00, 0x25, 0x26, 0x20, 0xe9, 0x02, 0x00, + 0x25, 0x07, 0xe0, 0x04, 0x26, 0x27, 0xe5, 0x01, + 0x00, 0x45, 0x00, 0xe5, 0x21, 0x26, 0x05, 0x47, + 0x66, 0x00, 0x47, 0x00, 0x47, 0x06, 0x05, 0x0f, + 0x60, 0x45, 0x07, 0xcb, 0x45, 0x26, 0x20, 0xe9, + 0x02, 0xeb, 0x01, 0x0f, 0xa5, 0x00, 0x06, 0x27, + 0x00, 0xe5, 0x0a, 0x40, 0xe5, 0x10, 0x00, 0xe5, + 0x01, 0x00, 0x05, 0x20, 0xc5, 0x40, 0x06, 0x60, + 0x47, 0x46, 0x00, 0x06, 0x00, 0xe7, 0x00, 0xa0, + 0xe9, 0x02, 0x20, 0x27, 0x16, 0xe0, 0x04, 0xe5, + 0x28, 0x06, 0x25, 0xc6, 0x60, 0x0d, 0xa5, 0x04, + 0xe6, 0x00, 0x16, 0xe9, 0x02, 0x36, 0xe0, 0x1d, + 0x25, 0x00, 0x05, 0x00, 0x85, 0x00, 0xe5, 0x10, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x06, 0x25, 0xe6, + 0x01, 0x05, 0x20, 0x85, 0x00, 0x04, 0x00, 0xc6, + 0x00, 0xe9, 0x02, 0x20, 0x65, 0xe0, 0x18, 0x05, + 0x4f, 0xf6, 0x07, 0x0f, 0x16, 0x4f, 0x26, 0xaf, + 0xe9, 0x02, 0xeb, 0x02, 0x0f, 0x06, 0x0f, 0x06, + 0x0f, 0x06, 0x12, 0x13, 0x12, 0x13, 0x27, 0xe5, + 0x00, 0x00, 0xe5, 0x1c, 0x60, 0xe6, 0x06, 0x07, + 0x86, 0x16, 0x26, 0x85, 0xe6, 0x03, 0x00, 0xe6, + 0x1c, 0x00, 0xef, 0x00, 0x06, 0xaf, 0x00, 0x2f, + 0x96, 0x6f, 0x36, 0xe0, 0x1d, 0xe5, 0x23, 0x27, + 0x66, 0x07, 0xa6, 0x07, 0x26, 0x27, 0x26, 0x05, + 0xe9, 0x02, 0xb6, 0xa5, 0x27, 0x26, 0x65, 0x46, + 0x05, 0x47, 0x25, 0xc7, 0x45, 0x66, 0xe5, 0x05, + 0x06, 0x27, 0x26, 0xa7, 0x06, 0x05, 0x07, 0xe9, + 0x02, 0x47, 0x06, 0x2f, 0xe1, 0x1e, 0x00, 0x01, + 0x80, 0x01, 0x20, 0xe2, 0x23, 0x16, 0x04, 0x42, + 0xe5, 0x80, 0xc1, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x21, 0x00, 0x65, + 0x20, 0xe5, 0x19, 0x00, 0x65, 0x20, 0xc5, 0x00, + 0x05, 0x00, 0x65, 0x20, 0xe5, 0x07, 0x00, 0xe5, + 0x31, 0x00, 0x65, 0x20, 0xe5, 0x3b, 0x20, 0x46, + 0xf6, 0x01, 0xeb, 0x0c, 0x40, 0xe5, 0x08, 0xef, + 0x02, 0xa0, 0xe1, 0x4e, 0x20, 0xa2, 0x20, 0x11, + 0xe5, 0x81, 0xe4, 0x0f, 0x16, 0xe5, 0x09, 0x17, + 0xe5, 0x12, 0x12, 0x13, 0x40, 0xe5, 0x43, 0x56, + 0x4a, 0xe5, 0x00, 0xc0, 0xe5, 0x0a, 0x46, 0x07, + 0xe0, 0x01, 0xe5, 0x0b, 0x26, 0x07, 0x36, 0xe0, + 0x01, 0xe5, 0x0a, 0x26, 0xe0, 0x04, 0xe5, 0x05, + 0x00, 0x45, 0x00, 0x26, 0xe0, 0x04, 0xe5, 0x2c, + 0x26, 0x07, 0xc6, 0xe7, 0x00, 0x06, 0x27, 0xe6, + 0x03, 0x56, 0x04, 0x56, 0x0d, 0x05, 0x06, 0x20, + 0xe9, 0x02, 0xa0, 0xeb, 0x02, 0xa0, 0xb6, 0x11, + 0x76, 0x46, 0x1b, 0x06, 0xe9, 0x02, 0xa0, 0xe5, + 0x1b, 0x04, 0xe5, 0x2d, 0xc0, 0x85, 0x26, 0xe5, + 0x1a, 0x06, 0x05, 0x80, 0xe5, 0x3e, 0xe0, 0x02, + 0xe5, 0x17, 0x00, 0x46, 0x67, 0x26, 0x47, 0x60, + 0x27, 0x06, 0xa7, 0x46, 0x60, 0x0f, 0x40, 0x36, + 0xe9, 0x02, 0xe5, 0x16, 0x20, 0x85, 0xe0, 0x03, + 0xe5, 0x24, 0x60, 0xe5, 0x12, 0xa0, 0xe9, 0x02, + 0x0b, 0x40, 0xef, 0x1a, 0xe5, 0x0f, 0x26, 0x27, + 0x06, 0x20, 0x36, 0xe5, 0x2d, 0x07, 0x06, 0x07, + 0xc6, 0x00, 0x06, 0x07, 0x06, 0x27, 0xe6, 0x00, + 0xa7, 0xe6, 0x02, 0x20, 0x06, 0xe9, 0x02, 0xa0, + 0xe9, 0x02, 0xa0, 0xd6, 0x04, 0xb6, 0x20, 0xe6, + 0x06, 0x08, 0xe6, 0x08, 0xe0, 0x29, 0x66, 0x07, + 0xe5, 0x27, 0x06, 0x07, 0x86, 0x07, 0x06, 0x87, + 0x06, 0x27, 0xe5, 0x00, 0x00, 0x36, 0xe9, 0x02, + 0xd6, 0xef, 0x02, 0xe6, 0x01, 0xef, 0x01, 0x56, + 0x26, 0x07, 0xe5, 0x16, 0x07, 0x66, 0x27, 0x26, + 0x07, 0x46, 0x25, 0xe9, 0x02, 0xe5, 0x24, 0x06, + 0x07, 0x26, 0x47, 0x06, 0x07, 0x46, 0x27, 0xe0, + 0x00, 0x76, 0xe5, 0x1c, 0xe7, 0x00, 0xe6, 0x00, + 0x27, 0x26, 0x40, 0x96, 0xe9, 0x02, 0x40, 0x45, + 0xe9, 0x02, 0xe5, 0x16, 0xa4, 0x36, 0xe2, 0x01, + 0x3f, 0x80, 0xe1, 0x23, 0x20, 0x41, 0xf6, 0x00, + 0xe0, 0x00, 0x46, 0x16, 0xe6, 0x05, 0x07, 0xc6, + 0x65, 0x06, 0xa5, 0x06, 0x25, 0x07, 0x26, 0x05, + 0x80, 0xe2, 0x24, 0xe4, 0x37, 0xe2, 0x05, 0x04, + 0xe2, 0x1a, 0xe4, 0x1d, 0xe6, 0x38, 0xff, 0x80, + 0x0e, 0xe2, 0x00, 0xff, 0x5a, 0xe2, 0x00, 0xe1, + 0x00, 0xa2, 0x20, 0xa1, 0x20, 0xe2, 0x00, 0xe1, + 0x00, 0xe2, 0x00, 0xe1, 0x00, 0xa2, 0x20, 0xa1, + 0x20, 0xe2, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x3f, 0xc2, 0xe1, 0x00, 0xe2, 0x06, + 0x20, 0xe2, 0x00, 0xe3, 0x00, 0xe2, 0x00, 0xe3, + 0x00, 0xe2, 0x00, 0xe3, 0x00, 0x82, 0x00, 0x22, + 0x61, 0x03, 0x0e, 0x02, 0x4e, 0x42, 0x00, 0x22, + 0x61, 0x03, 0x4e, 0x62, 0x20, 0x22, 0x61, 0x00, + 0x4e, 0xe2, 0x00, 0x81, 0x4e, 0x20, 0x42, 0x00, + 0x22, 0x61, 0x03, 0x2e, 0x00, 0xf7, 0x03, 0x9b, + 0xb1, 0x36, 0x14, 0x15, 0x12, 0x34, 0x15, 0x12, + 0x14, 0xf6, 0x00, 0x18, 0x19, 0x9b, 0x17, 0xf6, + 0x01, 0x14, 0x15, 0x76, 0x30, 0x56, 0x0c, 0x12, + 0x13, 0xf6, 0x03, 0x0c, 0x16, 0x10, 0xf6, 0x02, + 0x17, 0x9b, 0x00, 0xfb, 0x02, 0x0b, 0x04, 0x20, + 0xab, 0x4c, 0x12, 0x13, 0x04, 0xeb, 0x02, 0x4c, + 0x12, 0x13, 0x00, 0xe4, 0x05, 0x40, 0xed, 0x19, + 0xe0, 0x07, 0xe6, 0x05, 0x68, 0x06, 0x48, 0xe6, + 0x04, 0xe0, 0x07, 0x2f, 0x01, 0x6f, 0x01, 0x2f, + 0x02, 0x41, 0x22, 0x41, 0x02, 0x0f, 0x01, 0x2f, + 0x0c, 0x81, 0xaf, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x61, 0x0f, 0x02, 0x61, 0x02, 0x65, 0x02, + 0x2f, 0x22, 0x21, 0x8c, 0x3f, 0x42, 0x0f, 0x0c, + 0x2f, 0x02, 0x0f, 0xeb, 0x08, 0xea, 0x1b, 0x3f, + 0x6a, 0x0b, 0x2f, 0x60, 0x8c, 0x8f, 0x2c, 0x6f, + 0x0c, 0x2f, 0x0c, 0x2f, 0x0c, 0xcf, 0x0c, 0xef, + 0x17, 0x2c, 0x2f, 0x0c, 0x0f, 0x0c, 0xef, 0x17, + 0xec, 0x80, 0x84, 0xef, 0x00, 0x12, 0x13, 0x12, + 0x13, 0xef, 0x0c, 0x2c, 0xcf, 0x12, 0x13, 0xef, + 0x49, 0x0c, 0xef, 0x16, 0xec, 0x11, 0xef, 0x20, + 0xac, 0xef, 0x40, 0xe0, 0x0e, 0xef, 0x03, 0xe0, + 0x0d, 0xeb, 0x34, 0xef, 0x46, 0xeb, 0x0e, 0xef, + 0x80, 0x2f, 0x0c, 0xef, 0x01, 0x0c, 0xef, 0x2e, + 0xec, 0x00, 0xef, 0x67, 0x0c, 0xef, 0x80, 0x70, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xeb, 0x16, + 0xef, 0x24, 0x8c, 0x12, 0x13, 0xec, 0x17, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0xec, 0x08, 0xef, 0x80, 0x78, 0xec, 0x7b, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0xec, 0x37, + 0x12, 0x13, 0x12, 0x13, 0xec, 0x18, 0x12, 0x13, + 0xec, 0x80, 0x7a, 0xef, 0x28, 0xec, 0x0d, 0x2f, + 0xac, 0xef, 0x1f, 0x20, 0xef, 0x18, 0x00, 0xef, + 0x61, 0xe1, 0x28, 0xe2, 0x28, 0x5f, 0x21, 0x22, + 0xdf, 0x41, 0x02, 0x3f, 0x02, 0x3f, 0x82, 0x24, + 0x41, 0x02, 0xff, 0x5a, 0x02, 0xaf, 0x7f, 0x46, + 0x3f, 0x80, 0x76, 0x0b, 0x36, 0xe2, 0x1e, 0x00, + 0x02, 0x80, 0x02, 0x20, 0xe5, 0x30, 0xc0, 0x04, + 0x16, 0xe0, 0x06, 0x06, 0xe5, 0x0f, 0xe0, 0x01, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, 0xc5, 0x00, + 0xe6, 0x18, 0x36, 0x14, 0x15, 0x14, 0x15, 0x56, + 0x14, 0x15, 0x16, 0x14, 0x15, 0xf6, 0x01, 0x11, + 0x36, 0x11, 0x16, 0x14, 0x15, 0x36, 0x14, 0x15, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, + 0x96, 0x04, 0xf6, 0x02, 0x31, 0x76, 0x11, 0x16, + 0x12, 0xf6, 0x05, 0x2f, 0x56, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, 0xe0, 0x1a, + 0xef, 0x12, 0x00, 0xef, 0x51, 0xe0, 0x04, 0xef, + 0x80, 0x4e, 0xe0, 0x12, 0xef, 0x08, 0x17, 0x56, + 0x0f, 0x04, 0x05, 0x0a, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x2f, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x11, + 0x12, 0x33, 0x0f, 0xea, 0x01, 0x66, 0x27, 0x11, + 0x84, 0x2f, 0x4a, 0x04, 0x05, 0x16, 0x2f, 0x00, + 0xe5, 0x4e, 0x20, 0x26, 0x2e, 0x24, 0x05, 0x11, + 0xe5, 0x52, 0x16, 0x44, 0x05, 0x80, 0xe5, 0x23, + 0x00, 0xe5, 0x56, 0x00, 0x2f, 0x6b, 0xef, 0x02, + 0xe5, 0x18, 0xef, 0x1e, 0xe0, 0x01, 0x0f, 0xe5, + 0x08, 0xef, 0x17, 0x00, 0xeb, 0x02, 0xef, 0x16, + 0xeb, 0x00, 0x0f, 0xeb, 0x07, 0xef, 0x18, 0xeb, + 0x02, 0xef, 0x1f, 0xeb, 0x07, 0xef, 0x80, 0xb8, + 0xe5, 0x99, 0x38, 0xef, 0x38, 0xe5, 0xc0, 0x11, + 0x8d, 0x04, 0xe5, 0x83, 0xef, 0x40, 0xef, 0x2f, + 0xe0, 0x01, 0xe5, 0x20, 0xa4, 0x36, 0xe5, 0x80, + 0x84, 0x04, 0x56, 0xe5, 0x08, 0xe9, 0x02, 0x25, + 0xe0, 0x0c, 0xff, 0x26, 0x05, 0x06, 0x48, 0x16, + 0xe6, 0x02, 0x16, 0x04, 0xff, 0x14, 0x24, 0x26, + 0xe5, 0x3e, 0xea, 0x02, 0x26, 0xb6, 0xe0, 0x00, + 0xee, 0x0f, 0xe4, 0x01, 0x2e, 0xff, 0x06, 0x22, + 0xff, 0x36, 0x04, 0xe2, 0x00, 0x9f, 0xff, 0x02, + 0x04, 0x2e, 0x7f, 0x05, 0x7f, 0x22, 0xff, 0x0d, + 0x61, 0x02, 0x81, 0x02, 0xff, 0x07, 0x41, 0x02, + 0x5f, 0x3f, 0x20, 0x3f, 0x00, 0x02, 0x00, 0x02, + 0xdf, 0xe0, 0x0d, 0x44, 0x3f, 0x05, 0x24, 0x02, + 0xc5, 0x06, 0x45, 0x06, 0x65, 0x06, 0xe5, 0x0f, + 0x27, 0x26, 0x07, 0x6f, 0x06, 0x40, 0xab, 0x2f, + 0x0d, 0x0f, 0xa0, 0xe5, 0x2c, 0x76, 0xe0, 0x00, + 0x27, 0xe5, 0x2a, 0xe7, 0x08, 0x26, 0xe0, 0x00, + 0x36, 0xe9, 0x02, 0xa0, 0xe6, 0x0a, 0xa5, 0x56, + 0x05, 0x16, 0x25, 0x06, 0xe9, 0x02, 0xe5, 0x14, + 0xe6, 0x00, 0x36, 0xe5, 0x0f, 0xe6, 0x03, 0x27, + 0xe0, 0x03, 0x16, 0xe5, 0x15, 0x40, 0x46, 0x07, + 0xe5, 0x27, 0x06, 0x27, 0x66, 0x27, 0x26, 0x47, + 0xf6, 0x05, 0x00, 0x04, 0xe9, 0x02, 0x60, 0x36, + 0x85, 0x06, 0x04, 0xe5, 0x01, 0xe9, 0x02, 0x85, + 0x00, 0xe5, 0x21, 0xa6, 0x27, 0x26, 0x27, 0x26, + 0xe0, 0x01, 0x45, 0x06, 0xe5, 0x00, 0x06, 0x07, + 0x20, 0xe9, 0x02, 0x20, 0x76, 0xe5, 0x08, 0x04, + 0xa5, 0x4f, 0x05, 0x07, 0x06, 0x07, 0xe5, 0x2a, + 0x06, 0x05, 0x46, 0x25, 0x26, 0x85, 0x26, 0x05, + 0x06, 0x05, 0xe0, 0x10, 0x25, 0x04, 0x36, 0xe5, + 0x03, 0x07, 0x26, 0x27, 0x36, 0x05, 0x24, 0x07, + 0x06, 0xe0, 0x02, 0xa5, 0x20, 0xa5, 0x20, 0xa5, + 0xe0, 0x01, 0xc5, 0x00, 0xc5, 0x00, 0xe2, 0x23, + 0x0e, 0x64, 0xe2, 0x01, 0x04, 0x2e, 0x60, 0xe2, + 0x48, 0xe5, 0x1b, 0x27, 0x06, 0x27, 0x06, 0x27, + 0x16, 0x07, 0x06, 0x20, 0xe9, 0x02, 0xa0, 0xe5, + 0xab, 0x1c, 0xe0, 0x04, 0xe5, 0x0f, 0x60, 0xe5, + 0x29, 0x60, 0xfc, 0x87, 0x78, 0xfd, 0x98, 0x78, + 0xe5, 0x80, 0xe6, 0x20, 0xe5, 0x62, 0xe0, 0x1e, + 0xc2, 0xe0, 0x04, 0x82, 0x80, 0x05, 0x06, 0xe5, + 0x02, 0x0c, 0xe5, 0x05, 0x00, 0x85, 0x00, 0x05, + 0x00, 0x25, 0x00, 0x25, 0x00, 0xe5, 0x64, 0xee, + 0x09, 0xe0, 0x08, 0xe5, 0x80, 0xe3, 0x13, 0x12, + 0xef, 0x08, 0xe5, 0x38, 0x20, 0xe5, 0x2e, 0xc0, + 0x0f, 0xe0, 0x18, 0xe5, 0x04, 0x0d, 0x4f, 0xe6, + 0x08, 0xd6, 0x12, 0x13, 0x16, 0xa0, 0xe6, 0x08, + 0x16, 0x31, 0x30, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, 0x13, 0x12, + 0x13, 0x12, 0x13, 0x36, 0x12, 0x13, 0x76, 0x50, + 0x56, 0x00, 0x76, 0x11, 0x12, 0x13, 0x12, 0x13, + 0x12, 0x13, 0x56, 0x0c, 0x11, 0x4c, 0x00, 0x16, + 0x0d, 0x36, 0x60, 0x85, 0x00, 0xe5, 0x7f, 0x20, + 0x1b, 0x00, 0x56, 0x0d, 0x56, 0x12, 0x13, 0x16, + 0x0c, 0x16, 0x11, 0x36, 0xe9, 0x02, 0x36, 0x4c, + 0x36, 0xe1, 0x12, 0x12, 0x16, 0x13, 0x0e, 0x10, + 0x0e, 0xe2, 0x12, 0x12, 0x0c, 0x13, 0x0c, 0x12, + 0x13, 0x16, 0x12, 0x13, 0x36, 0xe5, 0x02, 0x04, + 0xe5, 0x25, 0x24, 0xe5, 0x17, 0x40, 0xa5, 0x20, + 0xa5, 0x20, 0xa5, 0x20, 0x45, 0x40, 0x2d, 0x0c, + 0x0e, 0x0f, 0x2d, 0x00, 0x0f, 0x6c, 0x2f, 0xe0, + 0x02, 0x5b, 0x2f, 0x20, 0xe5, 0x04, 0x00, 0xe5, + 0x12, 0x00, 0xe5, 0x0b, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x20, 0xe5, 0x06, 0xe0, 0x1a, 0xe5, 0x73, + 0x80, 0x56, 0x60, 0xeb, 0x25, 0x40, 0xef, 0x01, + 0xea, 0x2d, 0x6b, 0xef, 0x09, 0x2b, 0x4f, 0x00, + 0xef, 0x05, 0x40, 0x0f, 0xe0, 0x27, 0xef, 0x25, + 0x06, 0xe0, 0x7a, 0xe5, 0x15, 0x40, 0xe5, 0x29, + 0xe0, 0x07, 0x06, 0xeb, 0x13, 0x60, 0xe5, 0x18, + 0x6b, 0xe0, 0x01, 0xe5, 0x0c, 0x0a, 0xe5, 0x00, + 0x0a, 0x80, 0xe5, 0x1e, 0x86, 0x80, 0xe5, 0x16, + 0x00, 0x16, 0xe5, 0x1c, 0x60, 0xe5, 0x00, 0x16, + 0x8a, 0xe0, 0x22, 0xe1, 0x20, 0xe2, 0x20, 0xe5, + 0x46, 0x20, 0xe9, 0x02, 0xa0, 0xe1, 0x1c, 0x60, + 0xe2, 0x1c, 0x60, 0xe5, 0x20, 0xe0, 0x00, 0xe5, + 0x2c, 0xe0, 0x03, 0x16, 0xe1, 0x03, 0x00, 0xe1, + 0x07, 0x00, 0xc1, 0x00, 0x21, 0x00, 0xe2, 0x03, + 0x00, 0xe2, 0x07, 0x00, 0xc2, 0x00, 0x22, 0x40, + 0xe5, 0x2c, 0xe0, 0x04, 0xe5, 0x80, 0xaf, 0xe0, + 0x01, 0xe5, 0x0e, 0xe0, 0x02, 0xe5, 0x00, 0xe0, + 0x10, 0xa4, 0x00, 0xe4, 0x22, 0x00, 0xe4, 0x01, + 0xe0, 0x3d, 0xa5, 0x20, 0x05, 0x00, 0xe5, 0x24, + 0x00, 0x25, 0x40, 0x05, 0x20, 0xe5, 0x0f, 0x00, + 0x16, 0xeb, 0x00, 0xe5, 0x0f, 0x2f, 0xcb, 0xe5, + 0x17, 0xe0, 0x00, 0xeb, 0x01, 0xe0, 0x28, 0xe5, + 0x0b, 0x00, 0x25, 0x80, 0x8b, 0xe5, 0x0e, 0xab, + 0x40, 0x16, 0xe5, 0x12, 0x80, 0x16, 0xe0, 0x38, + 0xe5, 0x30, 0x60, 0x2b, 0x25, 0xeb, 0x08, 0x20, + 0xeb, 0x26, 0x05, 0x46, 0x00, 0x26, 0x80, 0x66, + 0x65, 0x00, 0x45, 0x00, 0xe5, 0x15, 0x20, 0x46, + 0x60, 0x06, 0xeb, 0x01, 0xc0, 0xf6, 0x01, 0xc0, + 0xe5, 0x15, 0x2b, 0x16, 0xe5, 0x15, 0x4b, 0xe0, + 0x18, 0xe5, 0x00, 0x0f, 0xe5, 0x14, 0x26, 0x60, + 0x8b, 0xd6, 0xe0, 0x01, 0xe5, 0x2e, 0x40, 0xd6, + 0xe5, 0x0e, 0x20, 0xeb, 0x00, 0xe5, 0x0b, 0x80, + 0xeb, 0x00, 0xe5, 0x0a, 0xc0, 0x76, 0xe0, 0x04, + 0xcb, 0xe0, 0x48, 0xe5, 0x41, 0xe0, 0x2f, 0xe1, + 0x2b, 0xe0, 0x05, 0xe2, 0x2b, 0xc0, 0xab, 0xe5, + 0x1c, 0x66, 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xe9, + 0x02, 0x65, 0x04, 0x05, 0xe1, 0x0e, 0x40, 0x86, + 0x11, 0x04, 0xe2, 0x0e, 0xe0, 0x00, 0x2c, 0xe0, + 0x80, 0x48, 0xeb, 0x17, 0x00, 0xe5, 0x22, 0x00, + 0x26, 0x11, 0x20, 0x25, 0xe0, 0x08, 0x45, 0xe0, + 0x2f, 0x66, 0xe5, 0x15, 0xeb, 0x02, 0x05, 0xe0, + 0x00, 0xe5, 0x0e, 0xe6, 0x03, 0x6b, 0x96, 0xe0, + 0x0e, 0xe5, 0x0a, 0x66, 0x76, 0xe0, 0x1e, 0xe5, + 0x0d, 0xcb, 0xe0, 0x0c, 0xe5, 0x0f, 0xe0, 0x01, + 0x07, 0x06, 0x07, 0xe5, 0x2d, 0xe6, 0x07, 0xd6, + 0x60, 0xeb, 0x0c, 0xe9, 0x02, 0x06, 0x25, 0x26, + 0x05, 0xe0, 0x01, 0x46, 0x07, 0xe5, 0x25, 0x47, + 0x66, 0x27, 0x26, 0x36, 0x1b, 0x76, 0x06, 0xe0, + 0x02, 0x1b, 0x20, 0xe5, 0x11, 0xc0, 0xe9, 0x02, + 0xa0, 0x46, 0xe5, 0x1c, 0x86, 0x07, 0xe6, 0x00, + 0x00, 0xe9, 0x02, 0x76, 0x05, 0x27, 0x05, 0xe0, + 0x00, 0xe5, 0x1b, 0x06, 0x36, 0x05, 0xe0, 0x01, + 0x26, 0x07, 0xe5, 0x28, 0x47, 0xe6, 0x01, 0x27, + 0x65, 0x76, 0x66, 0x16, 0x07, 0x06, 0xe9, 0x02, + 0x05, 0x16, 0x05, 0x56, 0x00, 0xeb, 0x0c, 0xe0, + 0x03, 0xe5, 0x0a, 0x00, 0xe5, 0x11, 0x47, 0x46, + 0x27, 0x06, 0x07, 0x26, 0xb6, 0x06, 0x25, 0x06, + 0xe0, 0x36, 0xc5, 0x00, 0x05, 0x00, 0x65, 0x00, + 0xe5, 0x07, 0x00, 0xe5, 0x02, 0x16, 0xa0, 0xe5, + 0x27, 0x06, 0x47, 0xe6, 0x00, 0x80, 0xe9, 0x02, + 0xa0, 0x26, 0x27, 0x00, 0xe5, 0x00, 0x20, 0x25, + 0x20, 0xe5, 0x0e, 0x00, 0xc5, 0x00, 0x25, 0x00, + 0x85, 0x00, 0x26, 0x05, 0x27, 0x06, 0x67, 0x20, + 0x27, 0x20, 0x47, 0x20, 0x05, 0xa0, 0x07, 0x80, + 0x85, 0x27, 0x20, 0xc6, 0x40, 0x86, 0xe0, 0x03, + 0xe5, 0x02, 0x00, 0x05, 0x20, 0x05, 0x00, 0xe5, + 0x1e, 0x00, 0x05, 0x47, 0xa6, 0x00, 0x07, 0x20, + 0x07, 0x00, 0x67, 0x00, 0x27, 0x06, 0x07, 0x06, + 0x05, 0x06, 0x05, 0x36, 0x00, 0x36, 0xe0, 0x00, + 0x26, 0xe0, 0x15, 0xe5, 0x2d, 0x47, 0xe6, 0x00, + 0x27, 0x46, 0x07, 0x06, 0x65, 0x96, 0xe9, 0x02, + 0x36, 0x00, 0x16, 0x06, 0x45, 0xe0, 0x16, 0xe5, + 0x28, 0x47, 0xa6, 0x07, 0x06, 0x67, 0x26, 0x07, + 0x26, 0x25, 0x16, 0x05, 0xe0, 0x00, 0xe9, 0x02, + 0xe0, 0x80, 0x1e, 0xe5, 0x27, 0x47, 0x66, 0x20, + 0x67, 0x26, 0x07, 0x26, 0xf6, 0x0f, 0x65, 0x26, + 0xe0, 0x1a, 0xe5, 0x28, 0x47, 0xe6, 0x00, 0x27, + 0x06, 0x07, 0x26, 0x56, 0x05, 0xe0, 0x03, 0xe9, + 0x02, 0xa0, 0xf6, 0x05, 0xe0, 0x0b, 0xe5, 0x23, + 0x06, 0x07, 0x06, 0x27, 0xa6, 0x07, 0x06, 0x05, + 0x16, 0xa0, 0xe9, 0x02, 0xa0, 0xe9, 0x0c, 0xe0, + 0x14, 0xe5, 0x13, 0x20, 0x06, 0x07, 0x06, 0x27, + 0x66, 0x07, 0x86, 0x60, 0xe9, 0x02, 0x2b, 0x56, + 0x0f, 0xc5, 0xe0, 0x80, 0x31, 0xe5, 0x24, 0x47, + 0xe6, 0x01, 0x07, 0x26, 0x16, 0xe0, 0x5c, 0xe1, + 0x18, 0xe2, 0x18, 0xe9, 0x02, 0xeb, 0x01, 0xe0, + 0x04, 0xe5, 0x00, 0x20, 0x05, 0x20, 0xe5, 0x00, + 0x00, 0x25, 0x00, 0xe5, 0x10, 0xa7, 0x00, 0x27, + 0x20, 0x26, 0x07, 0x06, 0x05, 0x07, 0x05, 0x07, + 0x06, 0x56, 0xe0, 0x01, 0xe9, 0x02, 0xe0, 0x3e, + 0xe5, 0x00, 0x20, 0xe5, 0x1f, 0x47, 0x66, 0x20, + 0x26, 0x67, 0x06, 0x05, 0x16, 0x05, 0x07, 0xe0, + 0x13, 0x05, 0xe6, 0x02, 0xe5, 0x20, 0xa6, 0x07, + 0x05, 0x66, 0xf6, 0x00, 0x06, 0xe0, 0x00, 0x05, + 0xa6, 0x27, 0x46, 0xe5, 0x26, 0xe6, 0x05, 0x07, + 0x26, 0x56, 0x05, 0x96, 0xe0, 0x05, 0xe5, 0x41, + 0xc0, 0xf6, 0x02, 0xe0, 0x80, 0x2e, 0xe5, 0x19, + 0x16, 0xe0, 0x06, 0xe9, 0x02, 0xa0, 0xe5, 0x01, + 0x00, 0xe5, 0x1d, 0x07, 0xc6, 0x00, 0xa6, 0x07, + 0x06, 0x05, 0x96, 0xe0, 0x02, 0xe9, 0x02, 0xeb, + 0x0b, 0x40, 0x36, 0xe5, 0x16, 0x20, 0xe6, 0x0e, + 0x00, 0x07, 0xc6, 0x07, 0x26, 0x07, 0x26, 0xe0, + 0x41, 0xc5, 0x00, 0x25, 0x00, 0xe5, 0x1e, 0xa6, + 0x40, 0x06, 0x00, 0x26, 0x00, 0xc6, 0x05, 0x06, + 0xe0, 0x00, 0xe9, 0x02, 0xa0, 0xa5, 0x00, 0x25, + 0x00, 0xe5, 0x18, 0x87, 0x00, 0x26, 0x00, 0x27, + 0x06, 0x07, 0x06, 0x05, 0xc0, 0xe9, 0x02, 0xe0, + 0x80, 0xae, 0xe5, 0x0b, 0x26, 0x27, 0x36, 0xc0, + 0x26, 0x05, 0x07, 0xe5, 0x05, 0x00, 0xe5, 0x1a, + 0x27, 0x86, 0x40, 0x27, 0x06, 0x07, 0x06, 0xf6, + 0x05, 0xe9, 0x02, 0x06, 0xe0, 0x4d, 0x05, 0xe0, + 0x07, 0xeb, 0x0d, 0xef, 0x00, 0x6d, 0xef, 0x09, + 0xe0, 0x05, 0x16, 0xe5, 0x83, 0x12, 0xe0, 0x5e, + 0xea, 0x67, 0x00, 0x96, 0xe0, 0x03, 0xe5, 0x80, + 0x3c, 0xe0, 0x89, 0xc4, 0xe5, 0x59, 0x36, 0xe0, + 0x05, 0xe5, 0x83, 0xa8, 0xfb, 0x08, 0x06, 0xa5, + 0xe6, 0x07, 0xe0, 0x02, 0xe5, 0x8f, 0x13, 0x80, + 0xe5, 0x81, 0xbf, 0xe0, 0x9a, 0x31, 0xe5, 0x16, + 0xe6, 0x04, 0x47, 0x46, 0xe9, 0x02, 0xe0, 0x86, + 0x3e, 0xe5, 0x81, 0xb1, 0xc0, 0xe5, 0x17, 0x00, + 0xe9, 0x02, 0x60, 0x36, 0xe5, 0x47, 0x00, 0xe9, + 0x02, 0xa0, 0xe5, 0x16, 0x20, 0x86, 0x16, 0xe0, + 0x02, 0xe5, 0x28, 0xc6, 0x96, 0x6f, 0x64, 0x16, + 0x0f, 0xe0, 0x02, 0xe9, 0x02, 0x00, 0xcb, 0x00, + 0xe5, 0x0d, 0x80, 0xe5, 0x0b, 0xe0, 0x81, 0x28, + 0x44, 0xe5, 0x20, 0x24, 0x56, 0xe9, 0x02, 0xe0, + 0x80, 0x3e, 0xe1, 0x18, 0xe2, 0x18, 0xeb, 0x0f, + 0x76, 0xe0, 0x5d, 0xe5, 0x43, 0x60, 0x06, 0x05, + 0xe7, 0x2f, 0xc0, 0x66, 0xe4, 0x05, 0xe0, 0x38, + 0x24, 0x16, 0x04, 0x06, 0xe0, 0x03, 0x27, 0xe0, + 0x06, 0xe5, 0x97, 0x70, 0xe0, 0x00, 0xe5, 0x84, + 0x4e, 0xe0, 0x21, 0xe5, 0x02, 0xe0, 0xa2, 0x5f, + 0x64, 0x00, 0xc4, 0x00, 0x24, 0x00, 0xe5, 0x80, + 0x9b, 0xe0, 0x07, 0x05, 0xe0, 0x15, 0x45, 0x20, + 0x05, 0xe0, 0x06, 0x65, 0xe0, 0x00, 0xe5, 0x81, + 0x04, 0xe0, 0x88, 0x7c, 0xe5, 0x63, 0x80, 0xe5, + 0x05, 0x40, 0xe5, 0x01, 0xc0, 0xe5, 0x02, 0x20, + 0x0f, 0x26, 0x16, 0x7b, 0xe0, 0x8e, 0xd4, 0xef, + 0x80, 0x68, 0xe9, 0x02, 0xa0, 0xef, 0x81, 0x2c, + 0xe0, 0x44, 0xe6, 0x26, 0x20, 0xe6, 0x0f, 0xe0, + 0x01, 0xef, 0x6c, 0xe0, 0x34, 0xef, 0x80, 0x6e, + 0xe0, 0x02, 0xef, 0x1f, 0x20, 0xef, 0x34, 0x27, + 0x46, 0x4f, 0xa7, 0xfb, 0x00, 0xe6, 0x00, 0x2f, + 0xc6, 0xef, 0x16, 0x66, 0xef, 0x35, 0xe0, 0x0d, + 0xef, 0x3a, 0x46, 0x0f, 0xe0, 0x72, 0xeb, 0x0c, + 0xe0, 0x04, 0xeb, 0x0c, 0xe0, 0x04, 0xef, 0x4f, + 0xe0, 0x01, 0xeb, 0x11, 0xe0, 0x7f, 0xe1, 0x12, + 0xe2, 0x12, 0xe1, 0x12, 0xc2, 0x00, 0xe2, 0x0a, + 0xe1, 0x12, 0xe2, 0x12, 0x01, 0x00, 0x21, 0x20, + 0x01, 0x20, 0x21, 0x20, 0x61, 0x00, 0xe1, 0x00, + 0x62, 0x00, 0x02, 0x00, 0xc2, 0x00, 0xe2, 0x03, + 0xe1, 0x12, 0xe2, 0x12, 0x21, 0x00, 0x61, 0x20, + 0xe1, 0x00, 0x00, 0xc1, 0x00, 0xe2, 0x12, 0x21, + 0x00, 0x61, 0x00, 0x81, 0x00, 0x01, 0x40, 0xc1, + 0x00, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x12, 0xe1, 0x12, 0xe2, 0x12, 0xe1, + 0x12, 0xe2, 0x14, 0x20, 0xe1, 0x11, 0x0c, 0xe2, + 0x11, 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, + 0x0c, 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, + 0xa2, 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, + 0xe1, 0x11, 0x0c, 0xe2, 0x11, 0x0c, 0xa2, 0x3f, + 0x20, 0xe9, 0x2a, 0xef, 0x81, 0x78, 0xe6, 0x2f, + 0x6f, 0xe6, 0x2a, 0xef, 0x00, 0x06, 0xef, 0x06, + 0x06, 0x2f, 0x96, 0xe0, 0x07, 0x86, 0x00, 0xe6, + 0x07, 0xe0, 0x83, 0xc8, 0xe2, 0x02, 0x05, 0xe2, + 0x0c, 0xa0, 0xa2, 0xe0, 0x80, 0x4d, 0xc6, 0x00, + 0xe6, 0x09, 0x20, 0xc6, 0x00, 0x26, 0x00, 0x86, + 0x80, 0xe4, 0x36, 0xe0, 0x19, 0x06, 0xe0, 0x68, + 0xe5, 0x25, 0x40, 0xc6, 0xc4, 0x20, 0xe9, 0x02, + 0x60, 0x05, 0x0f, 0xe0, 0x80, 0xb8, 0xe5, 0x16, + 0x06, 0xe0, 0x09, 0xe5, 0x24, 0x66, 0xe9, 0x02, + 0x80, 0x0d, 0xe0, 0x81, 0x48, 0xe5, 0x13, 0x04, + 0x66, 0xe9, 0x02, 0xe0, 0x80, 0x4e, 0xe5, 0x16, + 0x26, 0x05, 0xe9, 0x02, 0x60, 0x16, 0xe0, 0x81, + 0x58, 0xc5, 0x00, 0x65, 0x00, 0x25, 0x00, 0xe5, + 0x07, 0x00, 0xe5, 0x80, 0x3d, 0x20, 0xeb, 0x01, + 0xc6, 0xe0, 0x21, 0xe1, 0x1a, 0xe2, 0x1a, 0xc6, + 0x04, 0x60, 0xe9, 0x02, 0x60, 0x36, 0xe0, 0x82, + 0x89, 0xeb, 0x33, 0x0f, 0x4b, 0x0d, 0x6b, 0xe0, + 0x44, 0xeb, 0x25, 0x0f, 0xeb, 0x07, 0xe0, 0x80, + 0x3a, 0x65, 0x00, 0xe5, 0x13, 0x00, 0x25, 0x00, + 0x05, 0x20, 0x05, 0x00, 0xe5, 0x02, 0x00, 0x65, + 0x00, 0x05, 0x00, 0x05, 0xa0, 0x05, 0x60, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x45, 0x00, 0x25, + 0x00, 0x05, 0x20, 0x05, 0x00, 0x05, 0x00, 0x05, + 0x00, 0x05, 0x00, 0x05, 0x00, 0x25, 0x00, 0x05, + 0x20, 0x65, 0x00, 0xc5, 0x00, 0x65, 0x00, 0x65, + 0x00, 0x05, 0x00, 0xe5, 0x02, 0x00, 0xe5, 0x09, + 0x80, 0x45, 0x00, 0x85, 0x00, 0xe5, 0x09, 0xe0, + 0x2c, 0x2c, 0xe0, 0x80, 0x86, 0xef, 0x24, 0x60, + 0xef, 0x5c, 0xe0, 0x04, 0xef, 0x07, 0x20, 0xef, + 0x07, 0x00, 0xef, 0x07, 0x00, 0xef, 0x1d, 0xe0, + 0x02, 0xeb, 0x05, 0xef, 0x80, 0x19, 0xe0, 0x30, + 0xef, 0x15, 0xe0, 0x05, 0xef, 0x24, 0x60, 0xef, + 0x01, 0xc0, 0x2f, 0xe0, 0x06, 0xaf, 0xe0, 0x80, + 0x12, 0xef, 0x80, 0x73, 0x8e, 0xef, 0x82, 0x50, + 0x60, 0xef, 0x09, 0x40, 0xef, 0x05, 0x40, 0xef, + 0x6f, 0x60, 0xef, 0x57, 0xa0, 0xef, 0x04, 0x60, + 0x0f, 0xe0, 0x07, 0xef, 0x04, 0x60, 0xef, 0x30, + 0xe0, 0x00, 0xef, 0x02, 0xa0, 0xef, 0x20, 0xe0, + 0x00, 0xef, 0x16, 0x20, 0xef, 0x04, 0x60, 0x2f, + 0xe0, 0x36, 0xef, 0x80, 0xcc, 0xe0, 0x04, 0xef, + 0x06, 0x20, 0xef, 0x05, 0x40, 0xef, 0x02, 0x80, + 0xef, 0x30, 0xc0, 0xef, 0x07, 0x20, 0xef, 0x03, + 0xa0, 0xef, 0x01, 0xc0, 0xef, 0x80, 0x0b, 0x00, + 0xef, 0x54, 0xe9, 0x02, 0xe0, 0x83, 0x7e, 0xe5, + 0xc0, 0x66, 0x58, 0xe0, 0x18, 0xe5, 0x8f, 0xb2, + 0xa0, 0xe5, 0x80, 0x56, 0x20, 0xe5, 0x95, 0xfa, + 0xe0, 0x06, 0xe5, 0x9c, 0xa9, 0xe0, 0x07, 0xe5, + 0x81, 0xe6, 0xe0, 0x89, 0x1a, 0xe5, 0x81, 0x96, + 0xe0, 0x85, 0x5a, 0xe5, 0x92, 0xc3, 0x80, 0xe5, + 0x8f, 0xd8, 0xe0, 0xca, 0x9b, 0xc9, 0x1b, 0xe0, + 0x16, 0xfb, 0x58, 0xe0, 0x78, 0xe6, 0x80, 0x68, + 0xe0, 0xc0, 0xbd, 0x88, 0xfd, 0xc0, 0xbf, 0x76, + 0x20, 0xfd, 0xc0, 0xbf, 0x76, 0x20, +}; + +typedef enum { + UNICODE_SCRIPT_Unknown, + UNICODE_SCRIPT_Adlam, + UNICODE_SCRIPT_Ahom, + UNICODE_SCRIPT_Anatolian_Hieroglyphs, + UNICODE_SCRIPT_Arabic, + UNICODE_SCRIPT_Armenian, + UNICODE_SCRIPT_Avestan, + UNICODE_SCRIPT_Balinese, + UNICODE_SCRIPT_Bamum, + UNICODE_SCRIPT_Bassa_Vah, + UNICODE_SCRIPT_Batak, + UNICODE_SCRIPT_Bengali, + UNICODE_SCRIPT_Bhaiksuki, + UNICODE_SCRIPT_Bopomofo, + UNICODE_SCRIPT_Brahmi, + UNICODE_SCRIPT_Braille, + UNICODE_SCRIPT_Buginese, + UNICODE_SCRIPT_Buhid, + UNICODE_SCRIPT_Canadian_Aboriginal, + UNICODE_SCRIPT_Carian, + UNICODE_SCRIPT_Caucasian_Albanian, + UNICODE_SCRIPT_Chakma, + UNICODE_SCRIPT_Cham, + UNICODE_SCRIPT_Cherokee, + UNICODE_SCRIPT_Chorasmian, + UNICODE_SCRIPT_Common, + UNICODE_SCRIPT_Coptic, + UNICODE_SCRIPT_Cuneiform, + UNICODE_SCRIPT_Cypriot, + UNICODE_SCRIPT_Cyrillic, + UNICODE_SCRIPT_Cypro_Minoan, + UNICODE_SCRIPT_Deseret, + UNICODE_SCRIPT_Devanagari, + UNICODE_SCRIPT_Dives_Akuru, + UNICODE_SCRIPT_Dogra, + UNICODE_SCRIPT_Duployan, + UNICODE_SCRIPT_Egyptian_Hieroglyphs, + UNICODE_SCRIPT_Elbasan, + UNICODE_SCRIPT_Elymaic, + UNICODE_SCRIPT_Ethiopic, + UNICODE_SCRIPT_Georgian, + UNICODE_SCRIPT_Glagolitic, + UNICODE_SCRIPT_Gothic, + UNICODE_SCRIPT_Garay, + UNICODE_SCRIPT_Grantha, + UNICODE_SCRIPT_Greek, + UNICODE_SCRIPT_Gujarati, + UNICODE_SCRIPT_Gunjala_Gondi, + UNICODE_SCRIPT_Gurmukhi, + UNICODE_SCRIPT_Gurung_Khema, + UNICODE_SCRIPT_Han, + UNICODE_SCRIPT_Hangul, + UNICODE_SCRIPT_Hanifi_Rohingya, + UNICODE_SCRIPT_Hanunoo, + UNICODE_SCRIPT_Hatran, + UNICODE_SCRIPT_Hebrew, + UNICODE_SCRIPT_Hiragana, + UNICODE_SCRIPT_Imperial_Aramaic, + UNICODE_SCRIPT_Inherited, + UNICODE_SCRIPT_Inscriptional_Pahlavi, + UNICODE_SCRIPT_Inscriptional_Parthian, + UNICODE_SCRIPT_Javanese, + UNICODE_SCRIPT_Kaithi, + UNICODE_SCRIPT_Kannada, + UNICODE_SCRIPT_Katakana, + UNICODE_SCRIPT_Kawi, + UNICODE_SCRIPT_Kayah_Li, + UNICODE_SCRIPT_Kharoshthi, + UNICODE_SCRIPT_Khmer, + UNICODE_SCRIPT_Khojki, + UNICODE_SCRIPT_Khitan_Small_Script, + UNICODE_SCRIPT_Khudawadi, + UNICODE_SCRIPT_Kirat_Rai, + UNICODE_SCRIPT_Lao, + UNICODE_SCRIPT_Latin, + UNICODE_SCRIPT_Lepcha, + UNICODE_SCRIPT_Limbu, + UNICODE_SCRIPT_Linear_A, + UNICODE_SCRIPT_Linear_B, + UNICODE_SCRIPT_Lisu, + UNICODE_SCRIPT_Lycian, + UNICODE_SCRIPT_Lydian, + UNICODE_SCRIPT_Makasar, + UNICODE_SCRIPT_Mahajani, + UNICODE_SCRIPT_Malayalam, + UNICODE_SCRIPT_Mandaic, + UNICODE_SCRIPT_Manichaean, + UNICODE_SCRIPT_Marchen, + UNICODE_SCRIPT_Masaram_Gondi, + UNICODE_SCRIPT_Medefaidrin, + UNICODE_SCRIPT_Meetei_Mayek, + UNICODE_SCRIPT_Mende_Kikakui, + UNICODE_SCRIPT_Meroitic_Cursive, + UNICODE_SCRIPT_Meroitic_Hieroglyphs, + UNICODE_SCRIPT_Miao, + UNICODE_SCRIPT_Modi, + UNICODE_SCRIPT_Mongolian, + UNICODE_SCRIPT_Mro, + UNICODE_SCRIPT_Multani, + UNICODE_SCRIPT_Myanmar, + UNICODE_SCRIPT_Nabataean, + UNICODE_SCRIPT_Nag_Mundari, + UNICODE_SCRIPT_Nandinagari, + UNICODE_SCRIPT_New_Tai_Lue, + UNICODE_SCRIPT_Newa, + UNICODE_SCRIPT_Nko, + UNICODE_SCRIPT_Nushu, + UNICODE_SCRIPT_Nyiakeng_Puachue_Hmong, + UNICODE_SCRIPT_Ogham, + UNICODE_SCRIPT_Ol_Chiki, + UNICODE_SCRIPT_Ol_Onal, + UNICODE_SCRIPT_Old_Hungarian, + UNICODE_SCRIPT_Old_Italic, + UNICODE_SCRIPT_Old_North_Arabian, + UNICODE_SCRIPT_Old_Permic, + UNICODE_SCRIPT_Old_Persian, + UNICODE_SCRIPT_Old_Sogdian, + UNICODE_SCRIPT_Old_South_Arabian, + UNICODE_SCRIPT_Old_Turkic, + UNICODE_SCRIPT_Old_Uyghur, + UNICODE_SCRIPT_Oriya, + UNICODE_SCRIPT_Osage, + UNICODE_SCRIPT_Osmanya, + UNICODE_SCRIPT_Pahawh_Hmong, + UNICODE_SCRIPT_Palmyrene, + UNICODE_SCRIPT_Pau_Cin_Hau, + UNICODE_SCRIPT_Phags_Pa, + UNICODE_SCRIPT_Phoenician, + UNICODE_SCRIPT_Psalter_Pahlavi, + UNICODE_SCRIPT_Rejang, + UNICODE_SCRIPT_Runic, + UNICODE_SCRIPT_Samaritan, + UNICODE_SCRIPT_Saurashtra, + UNICODE_SCRIPT_Sharada, + UNICODE_SCRIPT_Shavian, + UNICODE_SCRIPT_Siddham, + UNICODE_SCRIPT_SignWriting, + UNICODE_SCRIPT_Sinhala, + UNICODE_SCRIPT_Sogdian, + UNICODE_SCRIPT_Sora_Sompeng, + UNICODE_SCRIPT_Soyombo, + UNICODE_SCRIPT_Sundanese, + UNICODE_SCRIPT_Sunuwar, + UNICODE_SCRIPT_Syloti_Nagri, + UNICODE_SCRIPT_Syriac, + UNICODE_SCRIPT_Tagalog, + UNICODE_SCRIPT_Tagbanwa, + UNICODE_SCRIPT_Tai_Le, + UNICODE_SCRIPT_Tai_Tham, + UNICODE_SCRIPT_Tai_Viet, + UNICODE_SCRIPT_Takri, + UNICODE_SCRIPT_Tamil, + UNICODE_SCRIPT_Tangut, + UNICODE_SCRIPT_Telugu, + UNICODE_SCRIPT_Thaana, + UNICODE_SCRIPT_Thai, + UNICODE_SCRIPT_Tibetan, + UNICODE_SCRIPT_Tifinagh, + UNICODE_SCRIPT_Tirhuta, + UNICODE_SCRIPT_Tangsa, + UNICODE_SCRIPT_Todhri, + UNICODE_SCRIPT_Toto, + UNICODE_SCRIPT_Tulu_Tigalari, + UNICODE_SCRIPT_Ugaritic, + UNICODE_SCRIPT_Vai, + UNICODE_SCRIPT_Vithkuqi, + UNICODE_SCRIPT_Wancho, + UNICODE_SCRIPT_Warang_Citi, + UNICODE_SCRIPT_Yezidi, + UNICODE_SCRIPT_Yi, + UNICODE_SCRIPT_Zanabazar_Square, + UNICODE_SCRIPT_COUNT, +} UnicodeScriptEnum; + +static const char unicode_script_name_table[] = + "Adlam,Adlm" "\0" + "Ahom,Ahom" "\0" + "Anatolian_Hieroglyphs,Hluw" "\0" + "Arabic,Arab" "\0" + "Armenian,Armn" "\0" + "Avestan,Avst" "\0" + "Balinese,Bali" "\0" + "Bamum,Bamu" "\0" + "Bassa_Vah,Bass" "\0" + "Batak,Batk" "\0" + "Bengali,Beng" "\0" + "Bhaiksuki,Bhks" "\0" + "Bopomofo,Bopo" "\0" + "Brahmi,Brah" "\0" + "Braille,Brai" "\0" + "Buginese,Bugi" "\0" + "Buhid,Buhd" "\0" + "Canadian_Aboriginal,Cans" "\0" + "Carian,Cari" "\0" + "Caucasian_Albanian,Aghb" "\0" + "Chakma,Cakm" "\0" + "Cham,Cham" "\0" + "Cherokee,Cher" "\0" + "Chorasmian,Chrs" "\0" + "Common,Zyyy" "\0" + "Coptic,Copt,Qaac" "\0" + "Cuneiform,Xsux" "\0" + "Cypriot,Cprt" "\0" + "Cyrillic,Cyrl" "\0" + "Cypro_Minoan,Cpmn" "\0" + "Deseret,Dsrt" "\0" + "Devanagari,Deva" "\0" + "Dives_Akuru,Diak" "\0" + "Dogra,Dogr" "\0" + "Duployan,Dupl" "\0" + "Egyptian_Hieroglyphs,Egyp" "\0" + "Elbasan,Elba" "\0" + "Elymaic,Elym" "\0" + "Ethiopic,Ethi" "\0" + "Georgian,Geor" "\0" + "Glagolitic,Glag" "\0" + "Gothic,Goth" "\0" + "Garay,Gara" "\0" + "Grantha,Gran" "\0" + "Greek,Grek" "\0" + "Gujarati,Gujr" "\0" + "Gunjala_Gondi,Gong" "\0" + "Gurmukhi,Guru" "\0" + "Gurung_Khema,Gukh" "\0" + "Han,Hani" "\0" + "Hangul,Hang" "\0" + "Hanifi_Rohingya,Rohg" "\0" + "Hanunoo,Hano" "\0" + "Hatran,Hatr" "\0" + "Hebrew,Hebr" "\0" + "Hiragana,Hira" "\0" + "Imperial_Aramaic,Armi" "\0" + "Inherited,Zinh,Qaai" "\0" + "Inscriptional_Pahlavi,Phli" "\0" + "Inscriptional_Parthian,Prti" "\0" + "Javanese,Java" "\0" + "Kaithi,Kthi" "\0" + "Kannada,Knda" "\0" + "Katakana,Kana" "\0" + "Kawi,Kawi" "\0" + "Kayah_Li,Kali" "\0" + "Kharoshthi,Khar" "\0" + "Khmer,Khmr" "\0" + "Khojki,Khoj" "\0" + "Khitan_Small_Script,Kits" "\0" + "Khudawadi,Sind" "\0" + "Kirat_Rai,Krai" "\0" + "Lao,Laoo" "\0" + "Latin,Latn" "\0" + "Lepcha,Lepc" "\0" + "Limbu,Limb" "\0" + "Linear_A,Lina" "\0" + "Linear_B,Linb" "\0" + "Lisu,Lisu" "\0" + "Lycian,Lyci" "\0" + "Lydian,Lydi" "\0" + "Makasar,Maka" "\0" + "Mahajani,Mahj" "\0" + "Malayalam,Mlym" "\0" + "Mandaic,Mand" "\0" + "Manichaean,Mani" "\0" + "Marchen,Marc" "\0" + "Masaram_Gondi,Gonm" "\0" + "Medefaidrin,Medf" "\0" + "Meetei_Mayek,Mtei" "\0" + "Mende_Kikakui,Mend" "\0" + "Meroitic_Cursive,Merc" "\0" + "Meroitic_Hieroglyphs,Mero" "\0" + "Miao,Plrd" "\0" + "Modi,Modi" "\0" + "Mongolian,Mong" "\0" + "Mro,Mroo" "\0" + "Multani,Mult" "\0" + "Myanmar,Mymr" "\0" + "Nabataean,Nbat" "\0" + "Nag_Mundari,Nagm" "\0" + "Nandinagari,Nand" "\0" + "New_Tai_Lue,Talu" "\0" + "Newa,Newa" "\0" + "Nko,Nkoo" "\0" + "Nushu,Nshu" "\0" + "Nyiakeng_Puachue_Hmong,Hmnp" "\0" + "Ogham,Ogam" "\0" + "Ol_Chiki,Olck" "\0" + "Ol_Onal,Onao" "\0" + "Old_Hungarian,Hung" "\0" + "Old_Italic,Ital" "\0" + "Old_North_Arabian,Narb" "\0" + "Old_Permic,Perm" "\0" + "Old_Persian,Xpeo" "\0" + "Old_Sogdian,Sogo" "\0" + "Old_South_Arabian,Sarb" "\0" + "Old_Turkic,Orkh" "\0" + "Old_Uyghur,Ougr" "\0" + "Oriya,Orya" "\0" + "Osage,Osge" "\0" + "Osmanya,Osma" "\0" + "Pahawh_Hmong,Hmng" "\0" + "Palmyrene,Palm" "\0" + "Pau_Cin_Hau,Pauc" "\0" + "Phags_Pa,Phag" "\0" + "Phoenician,Phnx" "\0" + "Psalter_Pahlavi,Phlp" "\0" + "Rejang,Rjng" "\0" + "Runic,Runr" "\0" + "Samaritan,Samr" "\0" + "Saurashtra,Saur" "\0" + "Sharada,Shrd" "\0" + "Shavian,Shaw" "\0" + "Siddham,Sidd" "\0" + "SignWriting,Sgnw" "\0" + "Sinhala,Sinh" "\0" + "Sogdian,Sogd" "\0" + "Sora_Sompeng,Sora" "\0" + "Soyombo,Soyo" "\0" + "Sundanese,Sund" "\0" + "Sunuwar,Sunu" "\0" + "Syloti_Nagri,Sylo" "\0" + "Syriac,Syrc" "\0" + "Tagalog,Tglg" "\0" + "Tagbanwa,Tagb" "\0" + "Tai_Le,Tale" "\0" + "Tai_Tham,Lana" "\0" + "Tai_Viet,Tavt" "\0" + "Takri,Takr" "\0" + "Tamil,Taml" "\0" + "Tangut,Tang" "\0" + "Telugu,Telu" "\0" + "Thaana,Thaa" "\0" + "Thai,Thai" "\0" + "Tibetan,Tibt" "\0" + "Tifinagh,Tfng" "\0" + "Tirhuta,Tirh" "\0" + "Tangsa,Tnsa" "\0" + "Todhri,Todr" "\0" + "Toto,Toto" "\0" + "Tulu_Tigalari,Tutg" "\0" + "Ugaritic,Ugar" "\0" + "Vai,Vaii" "\0" + "Vithkuqi,Vith" "\0" + "Wancho,Wcho" "\0" + "Warang_Citi,Wara" "\0" + "Yezidi,Yezi" "\0" + "Yi,Yiii" "\0" + "Zanabazar_Square,Zanb" "\0" +; + +static const uint8_t unicode_script_table[2803] = { + 0xc0, 0x19, 0x99, 0x4a, 0x85, 0x19, 0x99, 0x4a, + 0xae, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x80, 0x4a, + 0x84, 0x19, 0x96, 0x4a, 0x80, 0x19, 0x9e, 0x4a, + 0x80, 0x19, 0xe1, 0x60, 0x4a, 0xa6, 0x19, 0x84, + 0x4a, 0x84, 0x19, 0x81, 0x0d, 0x93, 0x19, 0xe0, + 0x0f, 0x3a, 0x83, 0x2d, 0x80, 0x19, 0x82, 0x2d, + 0x01, 0x83, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x03, + 0x80, 0x2d, 0x80, 0x19, 0x80, 0x2d, 0x80, 0x19, + 0x82, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x93, 0x2d, + 0x00, 0xbe, 0x2d, 0x8d, 0x1a, 0x8f, 0x2d, 0xe0, + 0x24, 0x1d, 0x81, 0x3a, 0xe0, 0x48, 0x1d, 0x00, + 0xa5, 0x05, 0x01, 0xb1, 0x05, 0x01, 0x82, 0x05, + 0x00, 0xb6, 0x37, 0x07, 0x9a, 0x37, 0x03, 0x85, + 0x37, 0x0a, 0x84, 0x04, 0x80, 0x19, 0x85, 0x04, + 0x80, 0x19, 0x8d, 0x04, 0x80, 0x19, 0x82, 0x04, + 0x80, 0x19, 0x9f, 0x04, 0x80, 0x19, 0x89, 0x04, + 0x8a, 0x3a, 0x99, 0x04, 0x80, 0x3a, 0xe0, 0x0b, + 0x04, 0x80, 0x19, 0xa1, 0x04, 0x8d, 0x90, 0x00, + 0xbb, 0x90, 0x01, 0x82, 0x90, 0xaf, 0x04, 0xb1, + 0x9a, 0x0d, 0xba, 0x69, 0x01, 0x82, 0x69, 0xad, + 0x83, 0x01, 0x8e, 0x83, 0x00, 0x9b, 0x55, 0x01, + 0x80, 0x55, 0x00, 0x8a, 0x90, 0x04, 0x9e, 0x04, + 0x00, 0x81, 0x04, 0x04, 0xca, 0x04, 0x80, 0x19, + 0x9c, 0x04, 0xd0, 0x20, 0x83, 0x3a, 0x8e, 0x20, + 0x81, 0x19, 0x99, 0x20, 0x83, 0x0b, 0x00, 0x87, + 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x95, 0x0b, 0x00, + 0x86, 0x0b, 0x00, 0x80, 0x0b, 0x02, 0x83, 0x0b, + 0x01, 0x88, 0x0b, 0x01, 0x81, 0x0b, 0x01, 0x83, + 0x0b, 0x07, 0x80, 0x0b, 0x03, 0x81, 0x0b, 0x00, + 0x84, 0x0b, 0x01, 0x98, 0x0b, 0x01, 0x82, 0x30, + 0x00, 0x85, 0x30, 0x03, 0x81, 0x30, 0x01, 0x95, + 0x30, 0x00, 0x86, 0x30, 0x00, 0x81, 0x30, 0x00, + 0x81, 0x30, 0x00, 0x81, 0x30, 0x01, 0x80, 0x30, + 0x00, 0x84, 0x30, 0x03, 0x81, 0x30, 0x01, 0x82, + 0x30, 0x02, 0x80, 0x30, 0x06, 0x83, 0x30, 0x00, + 0x80, 0x30, 0x06, 0x90, 0x30, 0x09, 0x82, 0x2e, + 0x00, 0x88, 0x2e, 0x00, 0x82, 0x2e, 0x00, 0x95, + 0x2e, 0x00, 0x86, 0x2e, 0x00, 0x81, 0x2e, 0x00, + 0x84, 0x2e, 0x01, 0x89, 0x2e, 0x00, 0x82, 0x2e, + 0x00, 0x82, 0x2e, 0x01, 0x80, 0x2e, 0x0e, 0x83, + 0x2e, 0x01, 0x8b, 0x2e, 0x06, 0x86, 0x2e, 0x00, + 0x82, 0x78, 0x00, 0x87, 0x78, 0x01, 0x81, 0x78, + 0x01, 0x95, 0x78, 0x00, 0x86, 0x78, 0x00, 0x81, + 0x78, 0x00, 0x84, 0x78, 0x01, 0x88, 0x78, 0x01, + 0x81, 0x78, 0x01, 0x82, 0x78, 0x06, 0x82, 0x78, + 0x03, 0x81, 0x78, 0x00, 0x84, 0x78, 0x01, 0x91, + 0x78, 0x09, 0x81, 0x97, 0x00, 0x85, 0x97, 0x02, + 0x82, 0x97, 0x00, 0x83, 0x97, 0x02, 0x81, 0x97, + 0x00, 0x80, 0x97, 0x00, 0x81, 0x97, 0x02, 0x81, + 0x97, 0x02, 0x82, 0x97, 0x02, 0x8b, 0x97, 0x03, + 0x84, 0x97, 0x02, 0x82, 0x97, 0x00, 0x83, 0x97, + 0x01, 0x80, 0x97, 0x05, 0x80, 0x97, 0x0d, 0x94, + 0x97, 0x04, 0x8c, 0x99, 0x00, 0x82, 0x99, 0x00, + 0x96, 0x99, 0x00, 0x8f, 0x99, 0x01, 0x88, 0x99, + 0x00, 0x82, 0x99, 0x00, 0x83, 0x99, 0x06, 0x81, + 0x99, 0x00, 0x82, 0x99, 0x01, 0x80, 0x99, 0x01, + 0x83, 0x99, 0x01, 0x89, 0x99, 0x06, 0x88, 0x99, + 0x8c, 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x96, 0x3f, + 0x00, 0x89, 0x3f, 0x00, 0x84, 0x3f, 0x01, 0x88, + 0x3f, 0x00, 0x82, 0x3f, 0x00, 0x83, 0x3f, 0x06, + 0x81, 0x3f, 0x05, 0x81, 0x3f, 0x00, 0x83, 0x3f, + 0x01, 0x89, 0x3f, 0x00, 0x82, 0x3f, 0x0b, 0x8c, + 0x54, 0x00, 0x82, 0x54, 0x00, 0xb2, 0x54, 0x00, + 0x82, 0x54, 0x00, 0x85, 0x54, 0x03, 0x8f, 0x54, + 0x01, 0x99, 0x54, 0x00, 0x82, 0x89, 0x00, 0x91, + 0x89, 0x02, 0x97, 0x89, 0x00, 0x88, 0x89, 0x00, + 0x80, 0x89, 0x01, 0x86, 0x89, 0x02, 0x80, 0x89, + 0x03, 0x85, 0x89, 0x00, 0x80, 0x89, 0x00, 0x87, + 0x89, 0x05, 0x89, 0x89, 0x01, 0x82, 0x89, 0x0b, + 0xb9, 0x9b, 0x03, 0x80, 0x19, 0x9b, 0x9b, 0x24, + 0x81, 0x49, 0x00, 0x80, 0x49, 0x00, 0x84, 0x49, + 0x00, 0x97, 0x49, 0x00, 0x80, 0x49, 0x00, 0x96, + 0x49, 0x01, 0x84, 0x49, 0x00, 0x80, 0x49, 0x00, + 0x86, 0x49, 0x00, 0x89, 0x49, 0x01, 0x83, 0x49, + 0x1f, 0xc7, 0x9c, 0x00, 0xa3, 0x9c, 0x03, 0xa6, + 0x9c, 0x00, 0xa3, 0x9c, 0x00, 0x8e, 0x9c, 0x00, + 0x86, 0x9c, 0x83, 0x19, 0x81, 0x9c, 0x24, 0xe0, + 0x3f, 0x63, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, + 0x80, 0x28, 0x01, 0xaa, 0x28, 0x80, 0x19, 0x83, + 0x28, 0xe0, 0x9f, 0x33, 0xc8, 0x27, 0x00, 0x83, + 0x27, 0x01, 0x86, 0x27, 0x00, 0x80, 0x27, 0x00, + 0x83, 0x27, 0x01, 0xa8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xa0, 0x27, 0x00, 0x83, 0x27, 0x01, 0x86, + 0x27, 0x00, 0x80, 0x27, 0x00, 0x83, 0x27, 0x01, + 0x8e, 0x27, 0x00, 0xb8, 0x27, 0x00, 0x83, 0x27, + 0x01, 0xc2, 0x27, 0x01, 0x9f, 0x27, 0x02, 0x99, + 0x27, 0x05, 0xd5, 0x17, 0x01, 0x85, 0x17, 0x01, + 0xe2, 0x1f, 0x12, 0x9c, 0x6c, 0x02, 0xca, 0x82, + 0x82, 0x19, 0x8a, 0x82, 0x06, 0x95, 0x91, 0x08, + 0x80, 0x91, 0x94, 0x35, 0x81, 0x19, 0x08, 0x93, + 0x11, 0x0b, 0x8c, 0x92, 0x00, 0x82, 0x92, 0x00, + 0x81, 0x92, 0x0b, 0xdd, 0x44, 0x01, 0x89, 0x44, + 0x05, 0x89, 0x44, 0x05, 0x81, 0x60, 0x81, 0x19, + 0x80, 0x60, 0x80, 0x19, 0x93, 0x60, 0x05, 0xd8, + 0x60, 0x06, 0xaa, 0x60, 0x04, 0xc5, 0x12, 0x09, + 0x9e, 0x4c, 0x00, 0x8b, 0x4c, 0x03, 0x8b, 0x4c, + 0x03, 0x80, 0x4c, 0x02, 0x8b, 0x4c, 0x9d, 0x93, + 0x01, 0x84, 0x93, 0x0a, 0xab, 0x67, 0x03, 0x99, + 0x67, 0x05, 0x8a, 0x67, 0x02, 0x81, 0x67, 0x9f, + 0x44, 0x9b, 0x10, 0x01, 0x81, 0x10, 0xbe, 0x94, + 0x00, 0x9c, 0x94, 0x01, 0x8a, 0x94, 0x05, 0x89, + 0x94, 0x05, 0x8d, 0x94, 0x01, 0x9e, 0x3a, 0x30, + 0xcc, 0x07, 0x00, 0xb1, 0x07, 0xbf, 0x8d, 0xb3, + 0x0a, 0x07, 0x83, 0x0a, 0xb7, 0x4b, 0x02, 0x8e, + 0x4b, 0x02, 0x82, 0x4b, 0xaf, 0x6d, 0x8a, 0x1d, + 0x04, 0xaa, 0x28, 0x01, 0x82, 0x28, 0x87, 0x8d, + 0x07, 0x82, 0x3a, 0x80, 0x19, 0x8c, 0x3a, 0x80, + 0x19, 0x86, 0x3a, 0x83, 0x19, 0x80, 0x3a, 0x85, + 0x19, 0x80, 0x3a, 0x82, 0x19, 0x81, 0x3a, 0x80, + 0x19, 0x04, 0xa5, 0x4a, 0x84, 0x2d, 0x80, 0x1d, + 0xb0, 0x4a, 0x84, 0x2d, 0x83, 0x4a, 0x84, 0x2d, + 0x8c, 0x4a, 0x80, 0x1d, 0xc5, 0x4a, 0x80, 0x2d, + 0xbf, 0x3a, 0xe0, 0x9f, 0x4a, 0x95, 0x2d, 0x01, + 0x85, 0x2d, 0x01, 0xa5, 0x2d, 0x01, 0x85, 0x2d, + 0x01, 0x87, 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x80, + 0x2d, 0x00, 0x80, 0x2d, 0x00, 0x9e, 0x2d, 0x01, + 0xb4, 0x2d, 0x00, 0x8e, 0x2d, 0x00, 0x8d, 0x2d, + 0x01, 0x85, 0x2d, 0x00, 0x92, 0x2d, 0x01, 0x82, + 0x2d, 0x00, 0x88, 0x2d, 0x00, 0x8b, 0x19, 0x81, + 0x3a, 0xd6, 0x19, 0x00, 0x8a, 0x19, 0x80, 0x4a, + 0x01, 0x8a, 0x19, 0x80, 0x4a, 0x8e, 0x19, 0x00, + 0x8c, 0x4a, 0x02, 0xa0, 0x19, 0x0e, 0xa0, 0x3a, + 0x0e, 0xa5, 0x19, 0x80, 0x2d, 0x82, 0x19, 0x81, + 0x4a, 0x85, 0x19, 0x80, 0x4a, 0x9a, 0x19, 0x80, + 0x4a, 0x90, 0x19, 0xa8, 0x4a, 0x82, 0x19, 0x03, + 0xe2, 0x39, 0x19, 0x15, 0x8a, 0x19, 0x14, 0xe3, + 0x3f, 0x19, 0xe0, 0x9f, 0x0f, 0xe2, 0x13, 0x19, + 0x01, 0x9f, 0x19, 0x00, 0xe0, 0x08, 0x19, 0xdf, + 0x29, 0x9f, 0x4a, 0xe0, 0x13, 0x1a, 0x04, 0x86, + 0x1a, 0xa5, 0x28, 0x00, 0x80, 0x28, 0x04, 0x80, + 0x28, 0x01, 0xb7, 0x9d, 0x06, 0x81, 0x9d, 0x0d, + 0x80, 0x9d, 0x96, 0x27, 0x08, 0x86, 0x27, 0x00, + 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0x86, 0x27, 0x00, 0x86, 0x27, 0x00, 0x86, + 0x27, 0x00, 0x86, 0x27, 0x00, 0x9f, 0x1d, 0xdd, + 0x19, 0x21, 0x99, 0x32, 0x00, 0xd8, 0x32, 0x0b, + 0xe0, 0x75, 0x32, 0x19, 0x94, 0x19, 0x80, 0x32, + 0x80, 0x19, 0x80, 0x32, 0x98, 0x19, 0x88, 0x32, + 0x83, 0x3a, 0x81, 0x33, 0x87, 0x19, 0x83, 0x32, + 0x83, 0x19, 0x00, 0xd5, 0x38, 0x01, 0x81, 0x3a, + 0x81, 0x19, 0x82, 0x38, 0x80, 0x19, 0xd9, 0x40, + 0x81, 0x19, 0x82, 0x40, 0x04, 0xaa, 0x0d, 0x00, + 0xdd, 0x33, 0x00, 0x8f, 0x19, 0x9f, 0x0d, 0xa5, + 0x19, 0x08, 0x80, 0x19, 0x8f, 0x40, 0x9e, 0x33, + 0x00, 0xbf, 0x19, 0x9e, 0x33, 0xd0, 0x19, 0xae, + 0x40, 0x80, 0x19, 0xd7, 0x40, 0xe0, 0x47, 0x19, + 0xf0, 0x09, 0x5f, 0x32, 0xbf, 0x19, 0xf0, 0x41, + 0x9f, 0x32, 0xe4, 0x2c, 0xa9, 0x02, 0xb6, 0xa9, + 0x08, 0xaf, 0x4f, 0xe0, 0xcb, 0xa4, 0x13, 0xdf, + 0x1d, 0xd7, 0x08, 0x07, 0xa1, 0x19, 0xe0, 0x05, + 0x4a, 0x82, 0x19, 0xc2, 0x4a, 0x01, 0x81, 0x4a, + 0x00, 0x80, 0x4a, 0x00, 0x87, 0x4a, 0x14, 0x8d, + 0x4a, 0xac, 0x8f, 0x02, 0x89, 0x19, 0x05, 0xb7, + 0x7e, 0x07, 0xc5, 0x84, 0x07, 0x8b, 0x84, 0x05, + 0x9f, 0x20, 0xad, 0x42, 0x80, 0x19, 0x80, 0x42, + 0xa3, 0x81, 0x0a, 0x80, 0x81, 0x9c, 0x33, 0x02, + 0xcd, 0x3d, 0x00, 0x80, 0x19, 0x89, 0x3d, 0x03, + 0x81, 0x3d, 0x9e, 0x63, 0x00, 0xb6, 0x16, 0x08, + 0x8d, 0x16, 0x01, 0x89, 0x16, 0x01, 0x83, 0x16, + 0x9f, 0x63, 0xc2, 0x95, 0x17, 0x84, 0x95, 0x96, + 0x5a, 0x09, 0x85, 0x27, 0x01, 0x85, 0x27, 0x01, + 0x85, 0x27, 0x08, 0x86, 0x27, 0x00, 0x86, 0x27, + 0x00, 0xaa, 0x4a, 0x80, 0x19, 0x88, 0x4a, 0x80, + 0x2d, 0x83, 0x4a, 0x81, 0x19, 0x03, 0xcf, 0x17, + 0xad, 0x5a, 0x01, 0x89, 0x5a, 0x05, 0xf0, 0x1b, + 0x43, 0x33, 0x0b, 0x96, 0x33, 0x03, 0xb0, 0x33, + 0x70, 0x10, 0xa3, 0xe1, 0x0d, 0x32, 0x01, 0xe0, + 0x09, 0x32, 0x25, 0x86, 0x4a, 0x0b, 0x84, 0x05, + 0x04, 0x99, 0x37, 0x00, 0x84, 0x37, 0x00, 0x80, + 0x37, 0x00, 0x81, 0x37, 0x00, 0x81, 0x37, 0x00, + 0x89, 0x37, 0xe0, 0x12, 0x04, 0x0f, 0xe1, 0x0a, + 0x04, 0x81, 0x19, 0xcf, 0x04, 0x01, 0xb5, 0x04, + 0x06, 0x80, 0x04, 0x1f, 0x8f, 0x04, 0x8f, 0x3a, + 0x89, 0x19, 0x05, 0x8d, 0x3a, 0x81, 0x1d, 0xa2, + 0x19, 0x00, 0x92, 0x19, 0x00, 0x83, 0x19, 0x03, + 0x84, 0x04, 0x00, 0xe0, 0x26, 0x04, 0x01, 0x80, + 0x19, 0x00, 0x9f, 0x19, 0x99, 0x4a, 0x85, 0x19, + 0x99, 0x4a, 0x8a, 0x19, 0x89, 0x40, 0x80, 0x19, + 0xac, 0x40, 0x81, 0x19, 0x9e, 0x33, 0x02, 0x85, + 0x33, 0x01, 0x85, 0x33, 0x01, 0x85, 0x33, 0x01, + 0x82, 0x33, 0x02, 0x86, 0x19, 0x00, 0x86, 0x19, + 0x09, 0x84, 0x19, 0x01, 0x8b, 0x4e, 0x00, 0x99, + 0x4e, 0x00, 0x92, 0x4e, 0x00, 0x81, 0x4e, 0x00, + 0x8e, 0x4e, 0x01, 0x8d, 0x4e, 0x21, 0xe0, 0x1a, + 0x4e, 0x04, 0x82, 0x19, 0x03, 0xac, 0x19, 0x02, + 0x88, 0x19, 0xce, 0x2d, 0x00, 0x8c, 0x19, 0x02, + 0x80, 0x2d, 0x2e, 0xac, 0x19, 0x80, 0x3a, 0x60, + 0x21, 0x9c, 0x50, 0x02, 0xb0, 0x13, 0x0e, 0x80, + 0x3a, 0x9a, 0x19, 0x03, 0xa3, 0x70, 0x08, 0x82, + 0x70, 0x9a, 0x2a, 0x04, 0xaa, 0x72, 0x04, 0x9d, + 0xa3, 0x00, 0x80, 0xa3, 0xa3, 0x73, 0x03, 0x8d, + 0x73, 0x29, 0xcf, 0x1f, 0xaf, 0x86, 0x9d, 0x7a, + 0x01, 0x89, 0x7a, 0x05, 0xa3, 0x79, 0x03, 0xa3, + 0x79, 0x03, 0xa7, 0x25, 0x07, 0xb3, 0x14, 0x0a, + 0x80, 0x14, 0x8a, 0xa5, 0x00, 0x8e, 0xa5, 0x00, + 0x86, 0xa5, 0x00, 0x81, 0xa5, 0x00, 0x8a, 0xa5, + 0x00, 0x8e, 0xa5, 0x00, 0x86, 0xa5, 0x00, 0x81, + 0xa5, 0x02, 0xb3, 0xa0, 0x0b, 0xe0, 0xd6, 0x4d, + 0x08, 0x95, 0x4d, 0x09, 0x87, 0x4d, 0x17, 0x85, + 0x4a, 0x00, 0xa9, 0x4a, 0x00, 0x88, 0x4a, 0x44, + 0x85, 0x1c, 0x01, 0x80, 0x1c, 0x00, 0xab, 0x1c, + 0x00, 0x81, 0x1c, 0x02, 0x80, 0x1c, 0x01, 0x80, + 0x1c, 0x95, 0x39, 0x00, 0x88, 0x39, 0x9f, 0x7c, + 0x9e, 0x64, 0x07, 0x88, 0x64, 0x2f, 0x92, 0x36, + 0x00, 0x81, 0x36, 0x04, 0x84, 0x36, 0x9b, 0x7f, + 0x02, 0x80, 0x7f, 0x99, 0x51, 0x04, 0x80, 0x51, + 0x3f, 0x9f, 0x5d, 0x97, 0x5c, 0x03, 0x93, 0x5c, + 0x01, 0xad, 0x5c, 0x83, 0x43, 0x00, 0x81, 0x43, + 0x04, 0x87, 0x43, 0x00, 0x82, 0x43, 0x00, 0x9c, + 0x43, 0x01, 0x82, 0x43, 0x03, 0x89, 0x43, 0x06, + 0x88, 0x43, 0x06, 0x9f, 0x75, 0x9f, 0x71, 0x1f, + 0xa6, 0x56, 0x03, 0x8b, 0x56, 0x08, 0xb5, 0x06, + 0x02, 0x86, 0x06, 0x95, 0x3c, 0x01, 0x87, 0x3c, + 0x92, 0x3b, 0x04, 0x87, 0x3b, 0x91, 0x80, 0x06, + 0x83, 0x80, 0x0b, 0x86, 0x80, 0x4f, 0xc8, 0x76, + 0x36, 0xb2, 0x6f, 0x0c, 0xb2, 0x6f, 0x06, 0x85, + 0x6f, 0xa7, 0x34, 0x07, 0x89, 0x34, 0x05, 0xa5, + 0x2b, 0x02, 0x9c, 0x2b, 0x07, 0x81, 0x2b, 0x60, + 0x6f, 0x9e, 0x04, 0x00, 0xa9, 0xa8, 0x00, 0x82, + 0xa8, 0x01, 0x81, 0xa8, 0x0f, 0x82, 0x04, 0x36, + 0x83, 0x04, 0xa7, 0x74, 0x07, 0xa9, 0x8a, 0x15, + 0x99, 0x77, 0x25, 0x9b, 0x18, 0x13, 0x96, 0x26, + 0x08, 0xcd, 0x0e, 0x03, 0xa3, 0x0e, 0x08, 0x80, + 0x0e, 0xc2, 0x3e, 0x09, 0x80, 0x3e, 0x01, 0x98, + 0x8b, 0x06, 0x89, 0x8b, 0x05, 0xb4, 0x15, 0x00, + 0x91, 0x15, 0x07, 0xa6, 0x53, 0x08, 0xdf, 0x85, + 0x00, 0x93, 0x89, 0x0a, 0x91, 0x45, 0x00, 0xae, + 0x45, 0x3d, 0x86, 0x62, 0x00, 0x80, 0x62, 0x00, + 0x83, 0x62, 0x00, 0x8e, 0x62, 0x00, 0x8a, 0x62, + 0x05, 0xba, 0x47, 0x04, 0x89, 0x47, 0x05, 0x83, + 0x2c, 0x00, 0x87, 0x2c, 0x01, 0x81, 0x2c, 0x01, + 0x95, 0x2c, 0x00, 0x86, 0x2c, 0x00, 0x81, 0x2c, + 0x00, 0x84, 0x2c, 0x00, 0x80, 0x3a, 0x88, 0x2c, + 0x01, 0x81, 0x2c, 0x01, 0x82, 0x2c, 0x01, 0x80, + 0x2c, 0x05, 0x80, 0x2c, 0x04, 0x86, 0x2c, 0x01, + 0x86, 0x2c, 0x02, 0x84, 0x2c, 0x0a, 0x89, 0xa2, + 0x00, 0x80, 0xa2, 0x01, 0x80, 0xa2, 0x00, 0xa5, + 0xa2, 0x00, 0x89, 0xa2, 0x00, 0x80, 0xa2, 0x01, + 0x80, 0xa2, 0x00, 0x83, 0xa2, 0x00, 0x89, 0xa2, + 0x00, 0x81, 0xa2, 0x07, 0x81, 0xa2, 0x1c, 0xdb, + 0x68, 0x00, 0x84, 0x68, 0x1d, 0xc7, 0x9e, 0x07, + 0x89, 0x9e, 0x60, 0x45, 0xb5, 0x87, 0x01, 0xa5, + 0x87, 0x21, 0xc4, 0x5f, 0x0a, 0x89, 0x5f, 0x05, + 0x8c, 0x60, 0x12, 0xb9, 0x96, 0x05, 0x89, 0x96, + 0x05, 0x93, 0x63, 0x1b, 0x9a, 0x02, 0x01, 0x8e, + 0x02, 0x03, 0x96, 0x02, 0x60, 0x58, 0xbb, 0x22, + 0x60, 0x03, 0xd2, 0xa7, 0x0b, 0x80, 0xa7, 0x86, + 0x21, 0x01, 0x80, 0x21, 0x01, 0x87, 0x21, 0x00, + 0x81, 0x21, 0x00, 0x9d, 0x21, 0x00, 0x81, 0x21, + 0x01, 0x8b, 0x21, 0x08, 0x89, 0x21, 0x45, 0x87, + 0x66, 0x01, 0xad, 0x66, 0x01, 0x8a, 0x66, 0x1a, + 0xc7, 0xaa, 0x07, 0xd2, 0x8c, 0x0c, 0x8f, 0x12, + 0xb8, 0x7d, 0x06, 0x89, 0x20, 0x60, 0x55, 0xa1, + 0x8e, 0x0d, 0x89, 0x8e, 0x05, 0x88, 0x0c, 0x00, + 0xac, 0x0c, 0x00, 0x8d, 0x0c, 0x09, 0x9c, 0x0c, + 0x02, 0x9f, 0x57, 0x01, 0x95, 0x57, 0x00, 0x8d, + 0x57, 0x48, 0x86, 0x58, 0x00, 0x81, 0x58, 0x00, + 0xab, 0x58, 0x02, 0x80, 0x58, 0x00, 0x81, 0x58, + 0x00, 0x88, 0x58, 0x07, 0x89, 0x58, 0x05, 0x85, + 0x2f, 0x00, 0x81, 0x2f, 0x00, 0xa4, 0x2f, 0x00, + 0x81, 0x2f, 0x00, 0x85, 0x2f, 0x06, 0x89, 0x2f, + 0x60, 0xd5, 0x98, 0x52, 0x06, 0x90, 0x41, 0x00, + 0xa8, 0x41, 0x02, 0x9c, 0x41, 0x54, 0x80, 0x4f, + 0x0e, 0xb1, 0x97, 0x0c, 0x80, 0x97, 0xe3, 0x39, + 0x1b, 0x60, 0x05, 0xe0, 0x0e, 0x1b, 0x00, 0x84, + 0x1b, 0x0a, 0xe0, 0x63, 0x1b, 0x69, 0xeb, 0xe0, + 0x02, 0x1e, 0x0c, 0xe3, 0xf5, 0x24, 0x09, 0xef, + 0x3a, 0x24, 0x04, 0xe1, 0xe6, 0x03, 0x70, 0x0a, + 0x58, 0xb9, 0x31, 0x66, 0x65, 0xe1, 0xd8, 0x08, + 0x06, 0x9e, 0x61, 0x00, 0x89, 0x61, 0x03, 0x81, + 0x61, 0xce, 0x9f, 0x00, 0x89, 0x9f, 0x05, 0x9d, + 0x09, 0x01, 0x85, 0x09, 0x09, 0xc5, 0x7b, 0x09, + 0x89, 0x7b, 0x00, 0x86, 0x7b, 0x00, 0x94, 0x7b, + 0x04, 0x92, 0x7b, 0x61, 0x4f, 0xb9, 0x48, 0x60, + 0x65, 0xda, 0x59, 0x60, 0x04, 0xca, 0x5e, 0x03, + 0xb8, 0x5e, 0x06, 0x90, 0x5e, 0x3f, 0x80, 0x98, + 0x80, 0x6a, 0x81, 0x32, 0x80, 0x46, 0x0a, 0x81, + 0x32, 0x0d, 0xf0, 0x07, 0x97, 0x98, 0x07, 0xe2, + 0x9f, 0x98, 0xe1, 0x75, 0x46, 0x28, 0x80, 0x46, + 0x88, 0x98, 0x70, 0x12, 0x86, 0x83, 0x40, 0x00, + 0x86, 0x40, 0x00, 0x81, 0x40, 0x00, 0x80, 0x40, + 0xe0, 0xbe, 0x38, 0x82, 0x40, 0x0e, 0x80, 0x38, + 0x1c, 0x82, 0x38, 0x01, 0x80, 0x40, 0x0d, 0x83, + 0x40, 0x07, 0xe1, 0x2b, 0x6a, 0x68, 0xa3, 0xe0, + 0x0a, 0x23, 0x04, 0x8c, 0x23, 0x02, 0x88, 0x23, + 0x06, 0x89, 0x23, 0x01, 0x83, 0x23, 0x83, 0x19, + 0x6e, 0xfb, 0xe0, 0x99, 0x19, 0x05, 0xe1, 0x53, + 0x19, 0x4b, 0xad, 0x3a, 0x01, 0x96, 0x3a, 0x08, + 0xe0, 0x13, 0x19, 0x3b, 0xe0, 0x95, 0x19, 0x09, + 0xa6, 0x19, 0x01, 0xbd, 0x19, 0x82, 0x3a, 0x90, + 0x19, 0x87, 0x3a, 0x81, 0x19, 0x86, 0x3a, 0x9d, + 0x19, 0x83, 0x3a, 0xbc, 0x19, 0x14, 0xc5, 0x2d, + 0x60, 0x19, 0x93, 0x19, 0x0b, 0x93, 0x19, 0x0b, + 0xd6, 0x19, 0x08, 0x98, 0x19, 0x60, 0x26, 0xd4, + 0x19, 0x00, 0xc6, 0x19, 0x00, 0x81, 0x19, 0x01, + 0x80, 0x19, 0x01, 0x81, 0x19, 0x01, 0x83, 0x19, + 0x00, 0x8b, 0x19, 0x00, 0x80, 0x19, 0x00, 0x86, + 0x19, 0x00, 0xc0, 0x19, 0x00, 0x83, 0x19, 0x01, + 0x87, 0x19, 0x00, 0x86, 0x19, 0x00, 0x9b, 0x19, + 0x00, 0x83, 0x19, 0x00, 0x84, 0x19, 0x00, 0x80, + 0x19, 0x02, 0x86, 0x19, 0x00, 0xe0, 0xf3, 0x19, + 0x01, 0xe0, 0xc3, 0x19, 0x01, 0xb1, 0x19, 0xe2, + 0x2b, 0x88, 0x0e, 0x84, 0x88, 0x00, 0x8e, 0x88, + 0x63, 0xef, 0x9e, 0x4a, 0x05, 0x85, 0x4a, 0x60, + 0x74, 0x86, 0x29, 0x00, 0x90, 0x29, 0x01, 0x86, + 0x29, 0x00, 0x81, 0x29, 0x00, 0x84, 0x29, 0x04, + 0xbd, 0x1d, 0x20, 0x80, 0x1d, 0x60, 0x0f, 0xac, + 0x6b, 0x02, 0x8d, 0x6b, 0x01, 0x89, 0x6b, 0x03, + 0x81, 0x6b, 0x60, 0xdf, 0x9e, 0xa1, 0x10, 0xb9, + 0xa6, 0x04, 0x80, 0xa6, 0x61, 0x6f, 0xa9, 0x65, + 0x60, 0x75, 0xaa, 0x6e, 0x03, 0x80, 0x6e, 0x61, + 0x7f, 0x86, 0x27, 0x00, 0x83, 0x27, 0x00, 0x81, + 0x27, 0x00, 0x8e, 0x27, 0x00, 0xe0, 0x64, 0x5b, + 0x01, 0x8f, 0x5b, 0x28, 0xcb, 0x01, 0x03, 0x89, + 0x01, 0x03, 0x81, 0x01, 0x62, 0xb0, 0xc3, 0x19, + 0x4b, 0xbc, 0x19, 0x60, 0x61, 0x83, 0x04, 0x00, + 0x9a, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, 0x04, + 0x01, 0x80, 0x04, 0x00, 0x89, 0x04, 0x00, 0x83, + 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, 0x05, + 0x80, 0x04, 0x03, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x82, 0x04, 0x00, 0x81, + 0x04, 0x00, 0x80, 0x04, 0x01, 0x80, 0x04, 0x00, + 0x80, 0x04, 0x00, 0x80, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x80, 0x04, 0x00, 0x81, 0x04, 0x00, 0x80, + 0x04, 0x01, 0x83, 0x04, 0x00, 0x86, 0x04, 0x00, + 0x83, 0x04, 0x00, 0x83, 0x04, 0x00, 0x80, 0x04, + 0x00, 0x89, 0x04, 0x00, 0x90, 0x04, 0x04, 0x82, + 0x04, 0x00, 0x84, 0x04, 0x00, 0x90, 0x04, 0x33, + 0x81, 0x04, 0x60, 0xad, 0xab, 0x19, 0x03, 0xe0, + 0x03, 0x19, 0x0b, 0x8e, 0x19, 0x01, 0x8e, 0x19, + 0x00, 0x8e, 0x19, 0x00, 0xa4, 0x19, 0x09, 0xe0, + 0x4d, 0x19, 0x37, 0x99, 0x19, 0x80, 0x38, 0x81, + 0x19, 0x0c, 0xab, 0x19, 0x03, 0x88, 0x19, 0x06, + 0x81, 0x19, 0x0d, 0x85, 0x19, 0x60, 0x39, 0xe3, + 0x77, 0x19, 0x03, 0x90, 0x19, 0x02, 0x8c, 0x19, + 0x02, 0xe0, 0x16, 0x19, 0x03, 0xde, 0x19, 0x05, + 0x8b, 0x19, 0x03, 0x80, 0x19, 0x0e, 0x8b, 0x19, + 0x03, 0xb7, 0x19, 0x07, 0x89, 0x19, 0x05, 0xa7, + 0x19, 0x07, 0x9d, 0x19, 0x01, 0x8b, 0x19, 0x03, + 0x81, 0x19, 0x3d, 0xe0, 0xf3, 0x19, 0x0b, 0x8d, + 0x19, 0x01, 0x8c, 0x19, 0x02, 0x89, 0x19, 0x04, + 0xb7, 0x19, 0x06, 0x8e, 0x19, 0x01, 0x8a, 0x19, + 0x05, 0x88, 0x19, 0x06, 0xe0, 0x32, 0x19, 0x00, + 0xe0, 0x05, 0x19, 0x63, 0xa5, 0xf0, 0x96, 0x7f, + 0x32, 0x1f, 0xef, 0xd9, 0x32, 0x05, 0xe0, 0x7d, + 0x32, 0x01, 0xf0, 0x06, 0x21, 0x32, 0x0d, 0xf0, + 0x0c, 0xd0, 0x32, 0x0e, 0xe2, 0x0d, 0x32, 0x69, + 0x41, 0xe1, 0xbd, 0x32, 0x65, 0x81, 0xf0, 0x02, + 0xea, 0x32, 0x04, 0xef, 0xff, 0x32, 0x7a, 0xcb, + 0xf0, 0x80, 0x19, 0x1d, 0xdf, 0x19, 0x60, 0x1f, + 0xe0, 0x8f, 0x3a, +}; + +static const uint8_t unicode_script_ext_table[1253] = { + 0x80, 0x36, 0x00, 0x00, 0x10, 0x06, 0x13, 0x1a, + 0x23, 0x25, 0x28, 0x29, 0x2f, 0x2a, 0x2d, 0x32, + 0x4a, 0x51, 0x53, 0x72, 0x86, 0x81, 0x83, 0x00, + 0x00, 0x07, 0x0b, 0x1d, 0x20, 0x4a, 0x4f, 0x9b, + 0xa1, 0x09, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x00, + 0x00, 0x02, 0x02, 0x0d, 0x4a, 0x00, 0x00, 0x00, + 0x02, 0x4a, 0x4f, 0x08, 0x00, 0x00, 0x02, 0x4a, + 0x9b, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x4a, 0x25, + 0x00, 0x00, 0x08, 0x17, 0x1a, 0x1d, 0x2d, 0x4a, + 0x72, 0x8e, 0x93, 0x00, 0x08, 0x17, 0x1d, 0x2d, + 0x4a, 0x79, 0x8e, 0x93, 0xa0, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x9d, 0x00, 0x05, 0x29, 0x4a, 0x8e, + 0x90, 0x9b, 0x00, 0x0b, 0x14, 0x17, 0x1a, 0x1d, + 0x2a, 0x2d, 0x4a, 0x79, 0x90, 0x9d, 0xa0, 0x00, + 0x06, 0x1a, 0x25, 0x29, 0x2a, 0x40, 0x4a, 0x00, + 0x04, 0x1d, 0x2d, 0x4a, 0x72, 0x00, 0x09, 0x1a, + 0x23, 0x37, 0x4a, 0x72, 0x90, 0x93, 0x9d, 0xa0, + 0x00, 0x0a, 0x05, 0x1d, 0x23, 0x2a, 0x2d, 0x37, + 0x4a, 0x72, 0x90, 0x93, 0x00, 0x02, 0x4a, 0x9d, + 0x00, 0x03, 0x23, 0x4a, 0x90, 0x00, 0x04, 0x17, + 0x1d, 0x4a, 0x79, 0x00, 0x03, 0x17, 0x4a, 0x93, + 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x02, 0x27, 0x4a, + 0x00, 0x00, 0x00, 0x02, 0x4a, 0x8e, 0x00, 0x03, + 0x1d, 0x4a, 0xa0, 0x00, 0x00, 0x00, 0x04, 0x2d, + 0x4a, 0x72, 0xa0, 0x0b, 0x00, 0x00, 0x02, 0x4a, + 0x90, 0x01, 0x00, 0x00, 0x05, 0x17, 0x23, 0x40, + 0x4a, 0x90, 0x00, 0x04, 0x17, 0x23, 0x4a, 0x90, + 0x00, 0x02, 0x4a, 0x90, 0x06, 0x00, 0x00, 0x03, + 0x4a, 0x8e, 0x90, 0x00, 0x02, 0x4a, 0x90, 0x00, + 0x00, 0x00, 0x03, 0x17, 0x4a, 0x90, 0x00, 0x06, + 0x14, 0x17, 0x2a, 0x4a, 0x8e, 0x9b, 0x0f, 0x00, + 0x00, 0x01, 0x2d, 0x01, 0x00, 0x00, 0x01, 0x2d, + 0x11, 0x00, 0x00, 0x02, 0x4a, 0x79, 0x04, 0x00, + 0x00, 0x03, 0x14, 0x4a, 0xa0, 0x03, 0x00, 0x0c, + 0x01, 0x4a, 0x03, 0x00, 0x01, 0x02, 0x1a, 0x2d, + 0x80, 0x8c, 0x00, 0x00, 0x02, 0x1d, 0x72, 0x00, + 0x02, 0x1d, 0x29, 0x01, 0x02, 0x1d, 0x4a, 0x00, + 0x02, 0x1d, 0x29, 0x80, 0x80, 0x00, 0x00, 0x03, + 0x05, 0x28, 0x29, 0x80, 0x01, 0x00, 0x00, 0x07, + 0x04, 0x2b, 0x69, 0x34, 0x90, 0x9a, 0xa8, 0x0d, + 0x00, 0x00, 0x07, 0x04, 0x2b, 0x69, 0x34, 0x90, + 0x9a, 0xa8, 0x00, 0x03, 0x04, 0x90, 0x9a, 0x01, + 0x00, 0x00, 0x08, 0x01, 0x04, 0x2b, 0x69, 0x34, + 0x90, 0x9a, 0xa8, 0x1f, 0x00, 0x00, 0x09, 0x01, + 0x04, 0x55, 0x56, 0x77, 0x80, 0x34, 0x8a, 0x90, + 0x09, 0x00, 0x0a, 0x02, 0x04, 0x90, 0x09, 0x00, + 0x09, 0x03, 0x04, 0x9a, 0xa8, 0x05, 0x00, 0x00, + 0x02, 0x04, 0x90, 0x62, 0x00, 0x00, 0x02, 0x04, + 0x34, 0x81, 0xfb, 0x00, 0x00, 0x0d, 0x0b, 0x20, + 0x2c, 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x85, + 0x97, 0x99, 0x9e, 0x00, 0x0c, 0x0b, 0x20, 0x2c, + 0x2e, 0x30, 0x3f, 0x4a, 0x54, 0x78, 0x97, 0x99, + 0x9e, 0x10, 0x00, 0x00, 0x15, 0x0b, 0x20, 0x22, + 0x2f, 0x58, 0x2c, 0x2e, 0x30, 0x3f, 0x53, 0x54, + 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, 0x97, + 0x99, 0x9e, 0x00, 0x17, 0x0b, 0x20, 0x22, 0x2f, + 0x58, 0x2c, 0x2e, 0x31, 0x30, 0x3f, 0x4c, 0x53, + 0x54, 0x66, 0x6e, 0x78, 0x47, 0x89, 0x8f, 0x96, + 0x97, 0x99, 0x9e, 0x09, 0x04, 0x20, 0x22, 0x3e, + 0x53, 0x75, 0x00, 0x09, 0x03, 0x0b, 0x15, 0x8f, + 0x75, 0x00, 0x09, 0x02, 0x30, 0x62, 0x75, 0x00, + 0x09, 0x02, 0x2e, 0x45, 0x80, 0x75, 0x00, 0x0d, + 0x02, 0x2c, 0x97, 0x80, 0x71, 0x00, 0x09, 0x03, + 0x3f, 0x66, 0xa2, 0x82, 0xcf, 0x00, 0x09, 0x03, + 0x15, 0x63, 0x93, 0x80, 0x30, 0x00, 0x00, 0x03, + 0x28, 0x29, 0x4a, 0x85, 0x6e, 0x00, 0x02, 0x01, + 0x82, 0x46, 0x00, 0x01, 0x04, 0x11, 0x35, 0x92, + 0x91, 0x80, 0x4a, 0x00, 0x01, 0x02, 0x60, 0x7e, + 0x00, 0x00, 0x00, 0x02, 0x60, 0x7e, 0x84, 0x49, + 0x00, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, 0x00, + 0x01, 0x20, 0x00, 0x04, 0x0b, 0x20, 0x2c, 0x3f, + 0x00, 0x03, 0x20, 0x2c, 0x3f, 0x00, 0x01, 0x20, + 0x01, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x02, 0x0b, 0x20, 0x00, 0x02, 0x20, 0x85, + 0x00, 0x06, 0x20, 0x3f, 0x54, 0x78, 0x97, 0x99, + 0x00, 0x01, 0x20, 0x01, 0x02, 0x20, 0x85, 0x01, + 0x01, 0x20, 0x00, 0x02, 0x20, 0x85, 0x00, 0x02, + 0x0b, 0x20, 0x06, 0x01, 0x20, 0x00, 0x02, 0x20, + 0x66, 0x00, 0x02, 0x0b, 0x20, 0x01, 0x01, 0x20, + 0x00, 0x02, 0x0b, 0x20, 0x03, 0x01, 0x20, 0x00, + 0x0b, 0x0b, 0x20, 0x2c, 0x3f, 0x54, 0x66, 0x78, + 0x89, 0x99, 0x9e, 0xa2, 0x00, 0x02, 0x20, 0x2c, + 0x00, 0x04, 0x20, 0x2c, 0x3f, 0xa2, 0x01, 0x02, + 0x0b, 0x20, 0x00, 0x01, 0x0b, 0x01, 0x02, 0x20, + 0x2c, 0x00, 0x01, 0x66, 0x80, 0x44, 0x00, 0x01, + 0x01, 0x2d, 0x35, 0x00, 0x00, 0x03, 0x1d, 0x4a, + 0x90, 0x00, 0x00, 0x00, 0x01, 0x90, 0x81, 0xb3, + 0x00, 0x00, 0x03, 0x4a, 0x60, 0x7e, 0x1e, 0x00, + 0x00, 0x02, 0x01, 0x04, 0x09, 0x00, 0x00, 0x06, + 0x13, 0x28, 0x29, 0x6f, 0x50, 0x76, 0x01, 0x00, + 0x00, 0x04, 0x13, 0x2d, 0x6f, 0x5d, 0x80, 0x11, + 0x00, 0x00, 0x03, 0x20, 0x2c, 0x4a, 0x8c, 0xa5, + 0x00, 0x00, 0x02, 0x1a, 0x4a, 0x17, 0x00, 0x00, + 0x02, 0x06, 0x76, 0x00, 0x07, 0x06, 0x13, 0x28, + 0x6f, 0x3e, 0x51, 0x83, 0x09, 0x00, 0x00, 0x01, + 0x23, 0x03, 0x00, 0x00, 0x03, 0x01, 0x04, 0x6f, + 0x00, 0x00, 0x00, 0x02, 0x1d, 0x29, 0x81, 0x2b, + 0x00, 0x0f, 0x02, 0x32, 0x98, 0x00, 0x00, 0x00, + 0x07, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, 0xa9, + 0x00, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x7e, 0xa9, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x01, 0x00, 0x00, 0x01, 0x32, 0x00, 0x00, + 0x01, 0x08, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x60, + 0x9c, 0xa9, 0x01, 0x09, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x4f, 0x60, 0x9c, 0xa9, 0x05, 0x06, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0xa9, 0x00, 0x00, 0x00, + 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, 0x07, 0x06, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0xa9, 0x03, 0x05, + 0x0d, 0x33, 0x32, 0x38, 0x40, 0x09, 0x00, 0x03, + 0x02, 0x0d, 0x32, 0x01, 0x00, 0x00, 0x05, 0x0d, + 0x33, 0x32, 0x38, 0x40, 0x04, 0x02, 0x38, 0x40, + 0x00, 0x00, 0x00, 0x05, 0x0d, 0x33, 0x32, 0x38, + 0x40, 0x03, 0x00, 0x01, 0x03, 0x32, 0x38, 0x40, + 0x01, 0x01, 0x32, 0x58, 0x00, 0x03, 0x02, 0x38, + 0x40, 0x02, 0x00, 0x00, 0x02, 0x38, 0x40, 0x59, + 0x00, 0x00, 0x06, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0xa9, 0x00, 0x02, 0x38, 0x40, 0x80, 0x12, 0x00, + 0x0f, 0x01, 0x32, 0x1f, 0x00, 0x25, 0x01, 0x32, + 0x08, 0x00, 0x00, 0x02, 0x32, 0x98, 0x2f, 0x00, + 0x27, 0x01, 0x32, 0x37, 0x00, 0x30, 0x01, 0x32, + 0x0e, 0x00, 0x0b, 0x01, 0x32, 0x32, 0x00, 0x00, + 0x01, 0x32, 0x57, 0x00, 0x18, 0x01, 0x32, 0x09, + 0x00, 0x04, 0x01, 0x32, 0x5f, 0x00, 0x1e, 0x01, + 0x32, 0xc0, 0x31, 0xef, 0x00, 0x00, 0x02, 0x1d, + 0x29, 0x80, 0x0f, 0x00, 0x07, 0x02, 0x32, 0x4a, + 0x80, 0xa7, 0x00, 0x02, 0x10, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x54, 0x5f, 0x66, + 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x02, 0x0f, 0x20, + 0x22, 0x2e, 0x30, 0x45, 0x3f, 0x3e, 0x53, 0x5f, + 0x66, 0x85, 0x47, 0x96, 0x9e, 0xa2, 0x01, 0x0b, + 0x20, 0x22, 0x2e, 0x30, 0x45, 0x3e, 0x53, 0x5f, + 0x47, 0x96, 0x9e, 0x00, 0x0c, 0x20, 0x22, 0x2e, + 0x30, 0x45, 0x3e, 0x53, 0x5f, 0x85, 0x47, 0x96, + 0x9e, 0x00, 0x0b, 0x20, 0x22, 0x2e, 0x30, 0x45, + 0x3e, 0x53, 0x5f, 0x47, 0x96, 0x9e, 0x80, 0x36, + 0x00, 0x00, 0x03, 0x0b, 0x20, 0xa2, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x97, 0x39, 0x00, 0x00, 0x03, + 0x42, 0x4a, 0x63, 0x80, 0x1f, 0x00, 0x00, 0x02, + 0x10, 0x3d, 0xc0, 0x12, 0xed, 0x00, 0x01, 0x02, + 0x04, 0x69, 0x80, 0x31, 0x00, 0x00, 0x02, 0x04, + 0x9a, 0x09, 0x00, 0x00, 0x02, 0x04, 0x9a, 0x46, + 0x00, 0x01, 0x05, 0x0d, 0x33, 0x32, 0x38, 0x40, + 0x80, 0x99, 0x00, 0x04, 0x06, 0x0d, 0x33, 0x32, + 0x38, 0x40, 0xa9, 0x09, 0x00, 0x00, 0x02, 0x38, + 0x40, 0x2c, 0x00, 0x01, 0x02, 0x38, 0x40, 0x80, + 0xdf, 0x00, 0x01, 0x03, 0x1e, 0x1c, 0x4e, 0x00, + 0x02, 0x1c, 0x4e, 0x03, 0x00, 0x2c, 0x03, 0x1c, + 0x4d, 0x4e, 0x02, 0x00, 0x08, 0x02, 0x1c, 0x4e, + 0x81, 0x1f, 0x00, 0x1b, 0x02, 0x04, 0x1a, 0x87, + 0x75, 0x00, 0x00, 0x02, 0x56, 0x77, 0x87, 0x8d, + 0x00, 0x00, 0x02, 0x2c, 0x97, 0x00, 0x00, 0x00, + 0x02, 0x2c, 0x97, 0x36, 0x00, 0x01, 0x02, 0x2c, + 0x97, 0x8c, 0x12, 0x00, 0x01, 0x02, 0x2c, 0x97, + 0x00, 0x00, 0x00, 0x02, 0x2c, 0x97, 0xc0, 0x5c, + 0x4b, 0x00, 0x03, 0x01, 0x23, 0x96, 0x3b, 0x00, + 0x11, 0x01, 0x32, 0x9e, 0x5d, 0x00, 0x01, 0x01, + 0x32, 0xce, 0xcd, 0x2d, 0x00, +}; + +static const uint8_t unicode_prop_Hyphen_table[28] = { + 0xac, 0x80, 0xfe, 0x80, 0x44, 0xdb, 0x80, 0x52, + 0x7a, 0x80, 0x48, 0x08, 0x81, 0x4e, 0x04, 0x80, + 0x42, 0xe2, 0x80, 0x60, 0xcd, 0x66, 0x80, 0x40, + 0xa8, 0x80, 0xd6, 0x80, +}; + +static const uint8_t unicode_prop_Other_Math_table[200] = { + 0xdd, 0x80, 0x43, 0x70, 0x11, 0x80, 0x99, 0x09, + 0x81, 0x5c, 0x1f, 0x80, 0x9a, 0x82, 0x8a, 0x80, + 0x9f, 0x83, 0x97, 0x81, 0x8d, 0x81, 0xc0, 0x8c, + 0x18, 0x11, 0x1c, 0x91, 0x03, 0x01, 0x89, 0x00, + 0x14, 0x28, 0x11, 0x09, 0x02, 0x05, 0x13, 0x24, + 0xca, 0x21, 0x18, 0x08, 0x08, 0x00, 0x21, 0x0b, + 0x0b, 0x91, 0x09, 0x00, 0x06, 0x00, 0x29, 0x41, + 0x21, 0x83, 0x40, 0xa7, 0x08, 0x80, 0x97, 0x80, + 0x90, 0x80, 0x41, 0xbc, 0x81, 0x8b, 0x88, 0x24, + 0x21, 0x09, 0x14, 0x8d, 0x00, 0x01, 0x85, 0x97, + 0x81, 0xb8, 0x00, 0x80, 0x9c, 0x83, 0x88, 0x81, + 0x41, 0x55, 0x81, 0x9e, 0x89, 0x41, 0x92, 0x95, + 0xbe, 0x83, 0x9f, 0x81, 0x60, 0xd4, 0x62, 0x00, + 0x03, 0x80, 0x40, 0xd2, 0x00, 0x80, 0x60, 0xd4, + 0xc0, 0xd4, 0x80, 0xc6, 0x01, 0x08, 0x09, 0x0b, + 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, 0x03, 0x0f, + 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, 0x16, 0x80, + 0x41, 0x53, 0x81, 0x98, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x80, 0x9e, + 0x80, 0x98, 0x80, 0x9e, 0x80, 0x98, 0x07, 0x81, + 0xb1, 0x55, 0xff, 0x18, 0x9a, 0x01, 0x00, 0x08, + 0x80, 0x89, 0x03, 0x00, 0x00, 0x28, 0x18, 0x00, + 0x00, 0x02, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x0b, 0x06, 0x03, 0x03, 0x00, + 0x80, 0x89, 0x80, 0x90, 0x22, 0x04, 0x80, 0x90, +}; + +static const uint8_t unicode_prop_Other_Alphabetic_table[443] = { + 0x43, 0x44, 0x80, 0x9c, 0x8c, 0x42, 0x3f, 0x8d, + 0x00, 0x01, 0x01, 0x00, 0xc7, 0x8a, 0xaf, 0x8c, + 0x06, 0x8f, 0x80, 0xe4, 0x33, 0x19, 0x0b, 0x80, + 0xa2, 0x80, 0x9d, 0x8f, 0xe5, 0x8a, 0xe4, 0x0a, + 0x88, 0x02, 0x03, 0xe9, 0x80, 0xbb, 0x8b, 0x16, + 0x85, 0x93, 0xb5, 0x09, 0x8e, 0x01, 0x22, 0x89, + 0x81, 0x9c, 0x82, 0xb9, 0x31, 0x09, 0x81, 0x89, + 0x80, 0x89, 0x81, 0x9c, 0x82, 0xb9, 0x23, 0x09, + 0x0b, 0x80, 0x9d, 0x0a, 0x80, 0x8a, 0x82, 0xb9, + 0x38, 0x10, 0x81, 0x94, 0x81, 0x95, 0x13, 0x82, + 0xb9, 0x31, 0x09, 0x81, 0x88, 0x81, 0x89, 0x81, + 0x9d, 0x80, 0xba, 0x22, 0x10, 0x82, 0x89, 0x80, + 0xa7, 0x84, 0xb8, 0x30, 0x10, 0x17, 0x81, 0x8a, + 0x81, 0x9c, 0x82, 0xb9, 0x30, 0x10, 0x17, 0x81, + 0x8a, 0x81, 0x8e, 0x80, 0x8b, 0x83, 0xb9, 0x30, + 0x10, 0x82, 0x89, 0x80, 0x89, 0x81, 0x9c, 0x82, + 0xca, 0x28, 0x00, 0x87, 0x91, 0x81, 0xbc, 0x01, + 0x86, 0x91, 0x80, 0xe2, 0x01, 0x28, 0x81, 0x8f, + 0x80, 0x40, 0xa2, 0x92, 0x88, 0x8a, 0x80, 0xa3, + 0xed, 0x8b, 0x00, 0x0b, 0x96, 0x1b, 0x10, 0x11, + 0x32, 0x83, 0x8c, 0x8b, 0x00, 0x89, 0x83, 0x46, + 0x73, 0x81, 0x9d, 0x81, 0x9d, 0x81, 0x9d, 0x81, + 0xc1, 0x92, 0x40, 0xbb, 0x81, 0xa1, 0x80, 0xf5, + 0x8b, 0x83, 0x88, 0x40, 0xdd, 0x84, 0xb8, 0x89, + 0x81, 0x93, 0xc9, 0x81, 0x8a, 0x82, 0xb0, 0x84, + 0xaf, 0x8e, 0xbb, 0x82, 0x9d, 0x88, 0x09, 0xb8, + 0x8a, 0xb1, 0x92, 0x41, 0x9b, 0xa1, 0x46, 0xc0, + 0xb3, 0x48, 0xf5, 0x9f, 0x60, 0x78, 0x73, 0x87, + 0xa1, 0x81, 0x41, 0x61, 0x07, 0x80, 0x96, 0x84, + 0xd7, 0x81, 0xb1, 0x8f, 0x00, 0xb8, 0x80, 0xa5, + 0x84, 0x9b, 0x8b, 0xac, 0x83, 0xaf, 0x8b, 0xa4, + 0x80, 0xc2, 0x8d, 0x8b, 0x07, 0x81, 0xac, 0x82, + 0xb1, 0x00, 0x11, 0x0c, 0x80, 0xab, 0x24, 0x80, + 0x40, 0xec, 0x87, 0x60, 0x4f, 0x32, 0x80, 0x48, + 0x56, 0x84, 0x46, 0x85, 0x10, 0x0c, 0x83, 0x43, + 0x13, 0x83, 0xc0, 0x80, 0x41, 0x40, 0x81, 0xce, + 0x80, 0x41, 0x02, 0x82, 0xb4, 0x8d, 0xac, 0x81, + 0x8a, 0x82, 0xac, 0x88, 0x88, 0x80, 0xbc, 0x82, + 0xa3, 0x8b, 0x91, 0x81, 0xb8, 0x82, 0xaf, 0x8c, + 0x8d, 0x81, 0xdb, 0x88, 0x08, 0x28, 0x08, 0x40, + 0x9c, 0x89, 0x96, 0x83, 0xb9, 0x31, 0x09, 0x81, + 0x89, 0x80, 0x89, 0x81, 0xd3, 0x88, 0x00, 0x08, + 0x03, 0x01, 0xe6, 0x8c, 0x02, 0xe9, 0x91, 0x40, + 0xec, 0x31, 0x86, 0x9c, 0x81, 0xd1, 0x8e, 0x00, + 0xe9, 0x8a, 0xe6, 0x8d, 0x41, 0x00, 0x8c, 0x40, + 0xf6, 0x28, 0x09, 0x0a, 0x00, 0x80, 0x40, 0x8d, + 0x31, 0x2b, 0x80, 0x9b, 0x89, 0xa9, 0x20, 0x83, + 0x91, 0x8a, 0xad, 0x8d, 0x41, 0x96, 0x38, 0x86, + 0xd2, 0x95, 0x80, 0x8d, 0xf9, 0x2a, 0x00, 0x08, + 0x10, 0x02, 0x80, 0xc1, 0x20, 0x08, 0x83, 0x41, + 0x5b, 0x83, 0x88, 0x08, 0x80, 0xaf, 0x32, 0x82, + 0x60, 0x41, 0xdc, 0x90, 0x4e, 0x1f, 0x00, 0xb6, + 0x33, 0xdc, 0x81, 0x60, 0x4c, 0xab, 0x80, 0x60, + 0x23, 0x60, 0x30, 0x90, 0x0e, 0x01, 0x04, 0xe3, + 0x80, 0x48, 0xb6, 0x80, 0x47, 0xe7, 0x99, 0x85, + 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Lowercase_table[69] = { + 0x40, 0xa9, 0x80, 0x8e, 0x80, 0x41, 0xf4, 0x88, + 0x31, 0x9d, 0x84, 0xdf, 0x80, 0xb3, 0x80, 0x4d, + 0x80, 0x80, 0x4c, 0x2e, 0xbe, 0x8c, 0x80, 0xa1, + 0xa4, 0x42, 0xb0, 0x80, 0x8c, 0x80, 0x8f, 0x8c, + 0x40, 0xd2, 0x8f, 0x43, 0x4f, 0x99, 0x47, 0x91, + 0x81, 0x60, 0x7a, 0x1d, 0x81, 0x40, 0xd1, 0x80, + 0x40, 0x80, 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, + 0x80, 0x60, 0x5c, 0x15, 0x01, 0x10, 0xa9, 0x80, + 0x88, 0x60, 0xd8, 0x74, 0xbd, +}; + +static const uint8_t unicode_prop_Other_Uppercase_table[15] = { + 0x60, 0x21, 0x5f, 0x8f, 0x43, 0x45, 0x99, 0x61, + 0xcc, 0x5f, 0x99, 0x85, 0x99, 0x85, 0x99, +}; + +static const uint8_t unicode_prop_Other_Grapheme_Extend_table[112] = { + 0x49, 0xbd, 0x80, 0x97, 0x80, 0x41, 0x65, 0x80, + 0x97, 0x80, 0xe5, 0x80, 0x97, 0x80, 0x40, 0xe7, + 0x00, 0x03, 0x08, 0x81, 0x88, 0x81, 0xe6, 0x80, + 0x97, 0x80, 0xf6, 0x80, 0x8e, 0x80, 0x49, 0x34, + 0x80, 0x9d, 0x80, 0x43, 0xff, 0x04, 0x00, 0x04, + 0x81, 0xe4, 0x80, 0xc6, 0x81, 0x44, 0x17, 0x80, + 0x50, 0x20, 0x81, 0x60, 0x79, 0x22, 0x80, 0xeb, + 0x80, 0x60, 0x55, 0xdc, 0x81, 0x52, 0x1f, 0x80, + 0xf3, 0x80, 0x41, 0x07, 0x80, 0x8d, 0x80, 0x88, + 0x80, 0xdf, 0x80, 0x88, 0x01, 0x00, 0x14, 0x80, + 0x40, 0xdf, 0x80, 0x8b, 0x80, 0x40, 0xf0, 0x80, + 0x41, 0x05, 0x80, 0x42, 0x78, 0x80, 0x8b, 0x80, + 0x46, 0x02, 0x80, 0x60, 0x50, 0xad, 0x81, 0x60, + 0x61, 0x72, 0x0d, 0x85, 0x6c, 0x2e, 0xac, 0xdf, +}; + +static const uint8_t unicode_prop_Other_Default_Ignorable_Code_Point_table[32] = { + 0x43, 0x4e, 0x80, 0x4e, 0x0e, 0x81, 0x46, 0x52, + 0x81, 0x48, 0xae, 0x80, 0x50, 0xfd, 0x80, 0x60, + 0xce, 0x3a, 0x80, 0xce, 0x88, 0x6d, 0x00, 0x06, + 0x00, 0x9d, 0xdf, 0xff, 0x40, 0xef, 0x4e, 0x0f, +}; + +static const uint8_t unicode_prop_Other_ID_Start_table[11] = { + 0x58, 0x84, 0x81, 0x48, 0x90, 0x80, 0x94, 0x80, + 0x4f, 0x6b, 0x81, +}; + +static const uint8_t unicode_prop_Other_ID_Continue_table[22] = { + 0x40, 0xb6, 0x80, 0x42, 0xce, 0x80, 0x4f, 0xe0, + 0x88, 0x46, 0x67, 0x80, 0x46, 0x30, 0x81, 0x50, + 0xec, 0x80, 0x60, 0xce, 0x68, 0x80, +}; + +static const uint8_t unicode_prop_Prepended_Concatenation_Mark_table[19] = { + 0x45, 0xff, 0x85, 0x40, 0xd6, 0x80, 0xb0, 0x80, + 0x41, 0x7f, 0x81, 0xcf, 0x80, 0x61, 0x07, 0xd9, + 0x80, 0x8e, 0x80, +}; + +static const uint8_t unicode_prop_XID_Start1_table[31] = { + 0x43, 0x79, 0x80, 0x4a, 0xb7, 0x80, 0xfe, 0x80, + 0x60, 0x21, 0xe6, 0x81, 0x60, 0xcb, 0xc0, 0x85, + 0x41, 0x95, 0x81, 0xf3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x41, 0x1e, 0x81, +}; + +static const uint8_t unicode_prop_XID_Continue1_table[23] = { + 0x43, 0x79, 0x80, 0x60, 0x2d, 0x1f, 0x81, 0x60, + 0xcb, 0xc0, 0x85, 0x41, 0x95, 0x81, 0xf3, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Titlecased1_table[22] = { + 0x41, 0xc3, 0x08, 0x08, 0x81, 0xa4, 0x81, 0x4e, + 0xdc, 0xaa, 0x0a, 0x4e, 0x87, 0x3f, 0x3f, 0x87, + 0x8b, 0x80, 0x8e, 0x80, 0xae, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_Casefolded1_table[29] = { + 0x41, 0xef, 0x80, 0x41, 0x9e, 0x80, 0x9e, 0x80, + 0x5a, 0xe4, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, + 0x80, 0xde, 0x06, 0x06, 0x80, 0x8a, 0x09, 0x81, + 0x89, 0x10, 0x81, 0x8d, 0x80, +}; + +static const uint8_t unicode_prop_Changes_When_NFKC_Casefolded1_table[450] = { + 0x40, 0x9f, 0x06, 0x00, 0x01, 0x00, 0x01, 0x12, + 0x10, 0x82, 0xf3, 0x80, 0x8b, 0x80, 0x40, 0x84, + 0x01, 0x01, 0x80, 0xa2, 0x01, 0x80, 0x40, 0xbb, + 0x88, 0x9e, 0x29, 0x84, 0xda, 0x08, 0x81, 0x89, + 0x80, 0xa3, 0x04, 0x02, 0x04, 0x08, 0x07, 0x80, + 0x9e, 0x80, 0xa0, 0x82, 0x9c, 0x80, 0x42, 0x28, + 0x80, 0xd7, 0x83, 0x42, 0xde, 0x87, 0xfb, 0x08, + 0x80, 0xd2, 0x01, 0x80, 0xa1, 0x11, 0x80, 0x40, + 0xfc, 0x81, 0x42, 0xd4, 0x80, 0xfe, 0x80, 0xa7, + 0x81, 0xad, 0x80, 0xb5, 0x80, 0x88, 0x03, 0x03, + 0x03, 0x80, 0x8b, 0x80, 0x88, 0x00, 0x26, 0x80, + 0x90, 0x80, 0x88, 0x03, 0x03, 0x03, 0x80, 0x8b, + 0x80, 0x41, 0x41, 0x80, 0xe1, 0x81, 0x46, 0x52, + 0x81, 0xd4, 0x84, 0x45, 0x1b, 0x10, 0x8a, 0x80, + 0x91, 0x80, 0x9b, 0x8c, 0x80, 0xa1, 0xa4, 0x40, + 0xd5, 0x83, 0x40, 0xb5, 0x00, 0x00, 0x00, 0x80, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0xb7, 0x05, 0x00, 0x13, 0x05, 0x11, 0x02, 0x0c, + 0x11, 0x00, 0x00, 0x0c, 0x15, 0x05, 0x08, 0x8f, + 0x00, 0x20, 0x8b, 0x12, 0x2a, 0x08, 0x0b, 0x00, + 0x07, 0x82, 0x8c, 0x06, 0x92, 0x81, 0x9a, 0x80, + 0x8c, 0x8a, 0x80, 0xd6, 0x18, 0x10, 0x8a, 0x01, + 0x0c, 0x0a, 0x00, 0x10, 0x11, 0x02, 0x06, 0x05, + 0x1c, 0x85, 0x8f, 0x8f, 0x8f, 0x88, 0x80, 0x40, + 0xa1, 0x08, 0x81, 0x40, 0xf7, 0x81, 0x41, 0x34, + 0xd5, 0x99, 0x9a, 0x45, 0x20, 0x80, 0xe6, 0x82, + 0xe4, 0x80, 0x41, 0x9e, 0x81, 0x40, 0xf0, 0x80, + 0x41, 0x2e, 0x80, 0xd2, 0x80, 0x8b, 0x40, 0xd5, + 0xa9, 0x80, 0xb4, 0x00, 0x82, 0xdf, 0x09, 0x80, + 0xde, 0x80, 0xb0, 0xdd, 0x82, 0x8d, 0xdf, 0x9e, + 0x80, 0xa7, 0x87, 0xae, 0x80, 0x41, 0x7f, 0x60, + 0x72, 0x9b, 0x81, 0x40, 0xd1, 0x80, 0x40, 0x80, + 0x12, 0x81, 0x43, 0x61, 0x83, 0x88, 0x80, 0x60, + 0x4d, 0x95, 0x41, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0xc3, 0x81, 0xe9, 0xc2, + 0x00, 0x97, 0x04, 0x00, 0x01, 0x01, 0x80, 0xeb, + 0xa0, 0x41, 0x6a, 0x91, 0xbf, 0x81, 0xb5, 0xa7, + 0x8c, 0x82, 0x99, 0x95, 0x94, 0x81, 0x8b, 0x80, + 0x92, 0x03, 0x1a, 0x00, 0x80, 0x40, 0x86, 0x08, + 0x80, 0x9f, 0x99, 0x40, 0x83, 0x15, 0x0d, 0x0d, + 0x0a, 0x16, 0x06, 0x80, 0x88, 0x47, 0x87, 0x20, + 0xa9, 0x80, 0x88, 0x60, 0xb4, 0xe4, 0x83, 0x50, + 0x31, 0xa3, 0x44, 0x63, 0x86, 0x8d, 0x87, 0xbf, + 0x85, 0x42, 0x3e, 0xd4, 0x80, 0xc6, 0x01, 0x08, + 0x09, 0x0b, 0x80, 0x8b, 0x00, 0x06, 0x80, 0xc0, + 0x03, 0x0f, 0x06, 0x80, 0x9b, 0x03, 0x04, 0x00, + 0x16, 0x80, 0x41, 0x53, 0x81, 0x41, 0x23, 0x81, + 0xb1, 0x48, 0x2f, 0xbd, 0x4d, 0x91, 0x18, 0x9a, + 0x01, 0x00, 0x08, 0x80, 0x89, 0x03, 0x00, 0x00, + 0x28, 0x18, 0x00, 0x00, 0x02, 0x01, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x06, + 0x03, 0x03, 0x00, 0x80, 0x89, 0x80, 0x90, 0x22, + 0x04, 0x80, 0x90, 0x42, 0x43, 0x8a, 0x84, 0x9e, + 0x80, 0x9f, 0x99, 0x82, 0xa2, 0x80, 0xee, 0x82, + 0x8c, 0xab, 0x83, 0x88, 0x31, 0x49, 0x9d, 0x89, + 0x60, 0xfc, 0x05, 0x42, 0x1d, 0x6b, 0x05, 0xe1, + 0x4f, 0xff, +}; + +static const uint8_t unicode_prop_ASCII_Hex_Digit_table[5] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_Bidi_Control_table[10] = { + 0x46, 0x1b, 0x80, 0x59, 0xf0, 0x81, 0x99, 0x84, + 0xb6, 0x83, +}; + +static const uint8_t unicode_prop_Dash_table[58] = { + 0xac, 0x80, 0x45, 0x5b, 0x80, 0xb2, 0x80, 0x4e, + 0x40, 0x80, 0x44, 0x04, 0x80, 0x48, 0x08, 0x85, + 0xbc, 0x80, 0xa6, 0x80, 0x8e, 0x80, 0x41, 0x85, + 0x80, 0x4c, 0x03, 0x01, 0x80, 0x9e, 0x0b, 0x80, + 0x9b, 0x80, 0x41, 0xbd, 0x80, 0x92, 0x80, 0xee, + 0x80, 0x60, 0xcd, 0x8f, 0x81, 0xa4, 0x80, 0x89, + 0x80, 0x40, 0xa8, 0x80, 0x4e, 0x5f, 0x80, 0x41, + 0x3d, 0x80, +}; + +static const uint8_t unicode_prop_Deprecated_table[23] = { + 0x41, 0x48, 0x80, 0x45, 0x28, 0x80, 0x49, 0x02, + 0x00, 0x80, 0x48, 0x28, 0x81, 0x48, 0xc4, 0x85, + 0x42, 0xb8, 0x81, 0x6d, 0xdc, 0xd5, 0x80, +}; + +static const uint8_t unicode_prop_Diacritic_table[438] = { + 0xdd, 0x00, 0x80, 0xc6, 0x05, 0x03, 0x01, 0x81, + 0x41, 0xf6, 0x40, 0x9e, 0x07, 0x25, 0x90, 0x0b, + 0x80, 0x88, 0x81, 0x40, 0xfc, 0x84, 0x40, 0xd0, + 0x80, 0xb6, 0x90, 0x80, 0x9a, 0x00, 0x01, 0x00, + 0x40, 0x85, 0x3b, 0x81, 0x40, 0x85, 0x0b, 0x0a, + 0x82, 0xc2, 0x9a, 0xda, 0x8a, 0xb9, 0x8a, 0xa1, + 0x81, 0xfd, 0x87, 0xa8, 0x89, 0x8f, 0x9b, 0xbc, + 0x80, 0x8f, 0x02, 0x83, 0x9b, 0x80, 0xc9, 0x80, + 0x8f, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, 0x80, + 0x8f, 0x80, 0xae, 0x82, 0xbb, 0x80, 0x8f, 0x06, + 0x80, 0xf6, 0x80, 0xed, 0x80, 0x8f, 0x80, 0xed, + 0x80, 0x8f, 0x80, 0xec, 0x81, 0x8f, 0x80, 0xfb, + 0x80, 0xee, 0x80, 0x8b, 0x28, 0x80, 0xea, 0x80, + 0x8c, 0x84, 0xca, 0x81, 0x9a, 0x00, 0x00, 0x03, + 0x81, 0xc1, 0x10, 0x81, 0xbd, 0x80, 0xef, 0x00, + 0x81, 0xa7, 0x0b, 0x84, 0x98, 0x30, 0x80, 0x89, + 0x81, 0x42, 0xc0, 0x82, 0x43, 0xb3, 0x81, 0x9d, + 0x80, 0x40, 0x93, 0x8a, 0x88, 0x80, 0x41, 0x5a, + 0x82, 0x41, 0x23, 0x80, 0x93, 0x39, 0x80, 0xaf, + 0x8e, 0x81, 0x8a, 0xe7, 0x80, 0x8e, 0x80, 0xa5, + 0x88, 0xb5, 0x81, 0xb9, 0x80, 0x8a, 0x81, 0xc1, + 0x81, 0xbf, 0x85, 0xd1, 0x98, 0x18, 0x28, 0x0a, + 0xb1, 0xbe, 0xd8, 0x8b, 0xa4, 0x8a, 0x41, 0xbc, + 0x00, 0x82, 0x8a, 0x82, 0x8c, 0x82, 0x8c, 0x82, + 0x8c, 0x81, 0x4c, 0xef, 0x82, 0x41, 0x3c, 0x80, + 0x41, 0xf9, 0x85, 0xe8, 0x83, 0xde, 0x80, 0x60, + 0x75, 0x71, 0x80, 0x8b, 0x08, 0x80, 0x9b, 0x81, + 0xd1, 0x81, 0x8d, 0xa1, 0xe5, 0x82, 0xec, 0x81, + 0x8b, 0x80, 0xa4, 0x80, 0x40, 0x96, 0x80, 0x9a, + 0x91, 0xb8, 0x83, 0xa3, 0x80, 0xde, 0x80, 0x8b, + 0x80, 0xa3, 0x80, 0x40, 0x94, 0x82, 0xc0, 0x83, + 0xb2, 0x80, 0xe3, 0x84, 0x88, 0x82, 0xff, 0x81, + 0x60, 0x4f, 0x2f, 0x80, 0x43, 0x00, 0x8f, 0x41, + 0x0d, 0x00, 0x80, 0xae, 0x80, 0xac, 0x81, 0xc2, + 0x80, 0x42, 0xfb, 0x80, 0x44, 0x9e, 0x28, 0xa9, + 0x80, 0x88, 0x42, 0x7c, 0x13, 0x80, 0x40, 0xa4, + 0x81, 0x42, 0x3a, 0x85, 0xa5, 0x80, 0x99, 0x84, + 0x41, 0x8e, 0x82, 0xc5, 0x8a, 0xb0, 0x83, 0x40, + 0xbf, 0x80, 0xa8, 0x80, 0xc7, 0x81, 0xf7, 0x81, + 0xbd, 0x80, 0xcb, 0x80, 0x88, 0x82, 0xe7, 0x81, + 0x40, 0xb1, 0x81, 0xcf, 0x81, 0x8f, 0x80, 0x97, + 0x32, 0x84, 0xd8, 0x10, 0x81, 0x8c, 0x81, 0xde, + 0x02, 0x80, 0xfa, 0x81, 0x40, 0xfa, 0x81, 0xfd, + 0x80, 0xf5, 0x81, 0xf2, 0x80, 0x41, 0x0c, 0x81, + 0x41, 0x01, 0x0b, 0x80, 0x40, 0x9b, 0x80, 0xd2, + 0x80, 0x91, 0x80, 0xd0, 0x80, 0x41, 0xa4, 0x80, + 0x41, 0x01, 0x00, 0x81, 0xd0, 0x80, 0x41, 0xa8, + 0x81, 0x96, 0x80, 0x54, 0xeb, 0x8e, 0x60, 0x2c, + 0xd8, 0x80, 0x49, 0xbf, 0x84, 0xba, 0x86, 0x42, + 0x33, 0x81, 0x42, 0x21, 0x90, 0xcf, 0x81, 0x60, + 0x3f, 0xfd, 0x18, 0x30, 0x81, 0x5f, 0x00, 0xad, + 0x81, 0x96, 0x42, 0x1f, 0x12, 0x2f, 0x39, 0x86, + 0x9d, 0x83, 0x4e, 0x81, 0xbd, 0x40, 0xc1, 0x86, + 0x41, 0x76, 0x80, 0xbc, 0x83, 0x42, 0xfd, 0x81, + 0x42, 0xdf, 0x86, 0xec, 0x10, 0x82, +}; + +static const uint8_t unicode_prop_Extender_table[111] = { + 0x40, 0xb6, 0x80, 0x42, 0x17, 0x81, 0x43, 0x6d, + 0x80, 0x41, 0xb8, 0x80, 0x42, 0x75, 0x80, 0x40, + 0x88, 0x80, 0xd8, 0x80, 0x42, 0xef, 0x80, 0xfe, + 0x80, 0x49, 0x42, 0x80, 0xb7, 0x80, 0x42, 0x62, + 0x80, 0x41, 0x8d, 0x80, 0xc3, 0x80, 0x53, 0x88, + 0x80, 0xaa, 0x84, 0xe6, 0x81, 0xdc, 0x82, 0x60, + 0x6f, 0x15, 0x80, 0x45, 0xf5, 0x80, 0x43, 0xc1, + 0x80, 0x95, 0x80, 0x40, 0x88, 0x80, 0xeb, 0x80, + 0x94, 0x81, 0x60, 0x54, 0x7a, 0x80, 0x48, 0x0f, + 0x81, 0x45, 0xca, 0x80, 0x9a, 0x03, 0x80, 0x44, + 0xc6, 0x80, 0x41, 0x24, 0x80, 0xf3, 0x81, 0x41, + 0xf1, 0x82, 0x44, 0xce, 0x80, 0x60, 0x50, 0xa8, + 0x81, 0x44, 0x9b, 0x08, 0x80, 0x60, 0x71, 0x57, + 0x81, 0x44, 0xb0, 0x80, 0x43, 0x53, 0x82, +}; + +static const uint8_t unicode_prop_Hex_Digit_table[12] = { + 0xaf, 0x89, 0x35, 0x99, 0x85, 0x60, 0xfe, 0xa8, + 0x89, 0x35, 0x99, 0x85, +}; + +static const uint8_t unicode_prop_IDS_Unary_Operator_table[4] = { + 0x60, 0x2f, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_IDS_Binary_Operator_table[8] = { + 0x60, 0x2f, 0xef, 0x09, 0x89, 0x41, 0xf0, 0x80, +}; + +static const uint8_t unicode_prop_IDS_Trinary_Operator_table[4] = { + 0x60, 0x2f, 0xf1, 0x81, +}; + +static const uint8_t unicode_prop_Ideographic_table[72] = { + 0x60, 0x30, 0x05, 0x81, 0x98, 0x88, 0x8d, 0x82, + 0x43, 0xc4, 0x59, 0xbf, 0xbf, 0x60, 0x51, 0xff, + 0x60, 0x58, 0xff, 0x41, 0x6d, 0x81, 0xe9, 0x60, + 0x75, 0x09, 0x80, 0x9a, 0x57, 0xf7, 0x87, 0x44, + 0xd5, 0xa8, 0x89, 0x60, 0x24, 0x66, 0x41, 0x8b, + 0x60, 0x4d, 0x03, 0x60, 0xa6, 0xdf, 0x9f, 0x50, + 0x39, 0x85, 0x40, 0xdd, 0x81, 0x56, 0x81, 0x8d, + 0x5d, 0x30, 0x8e, 0x42, 0x6d, 0x49, 0xa1, 0x42, + 0x1d, 0x45, 0xe1, 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Join_Control_table[4] = { + 0x60, 0x20, 0x0b, 0x81, +}; + +static const uint8_t unicode_prop_Logical_Order_Exception_table[15] = { + 0x4e, 0x3f, 0x84, 0xfa, 0x84, 0x4a, 0xef, 0x11, + 0x80, 0x60, 0x90, 0xf9, 0x09, 0x00, 0x81, +}; + +static const uint8_t unicode_prop_Modifier_Combining_Mark_table[16] = { + 0x46, 0x53, 0x09, 0x80, 0x40, 0x82, 0x05, 0x02, + 0x81, 0x41, 0xe0, 0x08, 0x12, 0x80, 0x9e, 0x80, +}; + +static const uint8_t unicode_prop_Noncharacter_Code_Point_table[71] = { + 0x60, 0xfd, 0xcf, 0x9f, 0x42, 0x0d, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, 0x60, + 0xff, 0xfd, 0x81, 0x60, 0xff, 0xfd, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_Syntax_table[58] = { + 0xa0, 0x8e, 0x89, 0x86, 0x99, 0x18, 0x80, 0x99, + 0x83, 0xa1, 0x30, 0x00, 0x08, 0x00, 0x0b, 0x03, + 0x02, 0x80, 0x96, 0x80, 0x9e, 0x80, 0x5f, 0x17, + 0x97, 0x87, 0x8e, 0x81, 0x92, 0x80, 0x89, 0x41, + 0x30, 0x42, 0xcf, 0x40, 0x9f, 0x42, 0x75, 0x9d, + 0x44, 0x6b, 0x41, 0xff, 0xff, 0x41, 0x80, 0x13, + 0x98, 0x8e, 0x80, 0x60, 0xcd, 0x0c, 0x81, 0x41, + 0x04, 0x81, +}; + +static const uint8_t unicode_prop_Pattern_White_Space_table[11] = { + 0x88, 0x84, 0x91, 0x80, 0xe3, 0x80, 0x5f, 0x87, + 0x81, 0x97, 0x81, +}; + +static const uint8_t unicode_prop_Quotation_Mark_table[31] = { + 0xa1, 0x03, 0x80, 0x40, 0x82, 0x80, 0x8e, 0x80, + 0x5f, 0x5b, 0x87, 0x98, 0x81, 0x4e, 0x06, 0x80, + 0x41, 0xc8, 0x83, 0x8c, 0x82, 0x60, 0xce, 0x20, + 0x83, 0x40, 0xbc, 0x03, 0x80, 0xd9, 0x81, +}; + +static const uint8_t unicode_prop_Radical_table[9] = { + 0x60, 0x2e, 0x7f, 0x99, 0x80, 0xd8, 0x8b, 0x40, + 0xd5, +}; + +static const uint8_t unicode_prop_Regional_Indicator_table[4] = { + 0x61, 0xf1, 0xe5, 0x99, +}; + +static const uint8_t unicode_prop_Sentence_Terminal_table[213] = { + 0xa0, 0x80, 0x8b, 0x80, 0x8f, 0x80, 0x45, 0x48, + 0x80, 0x40, 0x92, 0x82, 0x40, 0xb3, 0x80, 0xaa, + 0x82, 0x40, 0xf5, 0x80, 0xbc, 0x00, 0x02, 0x81, + 0x41, 0x24, 0x81, 0x46, 0xe3, 0x81, 0x43, 0x15, + 0x03, 0x81, 0x43, 0x04, 0x80, 0x40, 0xc5, 0x81, + 0x40, 0x9c, 0x81, 0xac, 0x04, 0x80, 0x41, 0x39, + 0x81, 0x41, 0x61, 0x83, 0x40, 0xa1, 0x81, 0x89, + 0x09, 0x81, 0x9c, 0x82, 0x40, 0xba, 0x81, 0xc0, + 0x81, 0x43, 0xa3, 0x80, 0x96, 0x81, 0x88, 0x82, + 0x4c, 0xae, 0x82, 0x41, 0x31, 0x80, 0x8c, 0x80, + 0x95, 0x81, 0x41, 0xac, 0x80, 0x60, 0x74, 0xfb, + 0x80, 0x41, 0x0d, 0x81, 0x40, 0xe2, 0x02, 0x80, + 0x41, 0x7d, 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, + 0x97, 0x81, 0x40, 0x92, 0x82, 0x40, 0x8f, 0x81, + 0x40, 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, + 0xba, 0x02, 0x81, 0x40, 0xa8, 0x80, 0x8b, 0x80, + 0x8f, 0x80, 0xc0, 0x80, 0x4a, 0xf3, 0x81, 0x44, + 0xfc, 0x84, 0xab, 0x83, 0x40, 0xbc, 0x81, 0xf4, + 0x83, 0xfe, 0x82, 0x40, 0x80, 0x0d, 0x80, 0x8f, + 0x81, 0xd7, 0x08, 0x81, 0xeb, 0x80, 0x41, 0x29, + 0x81, 0xf4, 0x81, 0x41, 0x74, 0x0c, 0x8e, 0xe8, + 0x81, 0x40, 0xf8, 0x82, 0x42, 0x04, 0x00, 0x80, + 0x40, 0xfa, 0x81, 0xd6, 0x81, 0x41, 0xa3, 0x81, + 0x42, 0xb3, 0x81, 0xc9, 0x81, 0x60, 0x4b, 0x28, + 0x81, 0x40, 0x84, 0x80, 0xc0, 0x81, 0x8a, 0x80, + 0x42, 0x28, 0x81, 0x41, 0x27, 0x80, 0x60, 0x4e, + 0x05, 0x80, 0x5d, 0xe7, 0x80, +}; + +static const uint8_t unicode_prop_Soft_Dotted_table[79] = { + 0xe8, 0x81, 0x40, 0xc3, 0x80, 0x41, 0x18, 0x80, + 0x9d, 0x80, 0xb3, 0x80, 0x93, 0x80, 0x41, 0x3f, + 0x80, 0xe1, 0x00, 0x80, 0x59, 0x08, 0x80, 0xb2, + 0x80, 0x8c, 0x02, 0x80, 0x40, 0x83, 0x80, 0x40, + 0x9c, 0x80, 0x41, 0xa4, 0x80, 0x40, 0xd5, 0x81, + 0x4b, 0x31, 0x80, 0x61, 0xa7, 0xa4, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, + 0x81, 0xb1, 0x81, 0xb1, 0x81, 0xb1, 0x81, 0x48, + 0x85, 0x80, 0x41, 0x30, 0x81, 0x99, 0x80, +}; + +static const uint8_t unicode_prop_Terminal_Punctuation_table[264] = { + 0xa0, 0x80, 0x89, 0x00, 0x80, 0x8a, 0x0a, 0x80, + 0x43, 0x3d, 0x07, 0x80, 0x42, 0x00, 0x80, 0xb8, + 0x80, 0xc7, 0x80, 0x8d, 0x00, 0x82, 0x40, 0xb3, + 0x80, 0xaa, 0x8a, 0x00, 0x40, 0xea, 0x81, 0xb5, + 0x28, 0x87, 0x9e, 0x80, 0x41, 0x04, 0x81, 0x44, + 0xf3, 0x81, 0x40, 0xab, 0x03, 0x85, 0x41, 0x36, + 0x81, 0x43, 0x14, 0x87, 0x43, 0x04, 0x80, 0xfb, + 0x82, 0xc6, 0x81, 0x40, 0x9c, 0x12, 0x80, 0xa6, + 0x19, 0x81, 0x41, 0x39, 0x81, 0x41, 0x61, 0x83, + 0x40, 0xa1, 0x81, 0x89, 0x08, 0x82, 0x9c, 0x82, + 0x40, 0xba, 0x84, 0xbd, 0x81, 0x43, 0xa3, 0x80, + 0x96, 0x81, 0x88, 0x82, 0x4c, 0xae, 0x82, 0x41, + 0x31, 0x80, 0x8c, 0x03, 0x80, 0x89, 0x00, 0x0a, + 0x81, 0x41, 0xab, 0x81, 0x60, 0x74, 0xfa, 0x81, + 0x41, 0x0c, 0x82, 0x40, 0xe2, 0x84, 0x41, 0x7d, + 0x81, 0xd5, 0x81, 0xde, 0x80, 0x40, 0x96, 0x82, + 0x40, 0x92, 0x82, 0xfe, 0x80, 0x8f, 0x81, 0x40, + 0xf8, 0x80, 0x60, 0x52, 0x25, 0x01, 0x81, 0xb8, + 0x10, 0x83, 0x40, 0xa8, 0x80, 0x89, 0x00, 0x80, + 0x8a, 0x0a, 0x80, 0xc0, 0x01, 0x80, 0x44, 0x39, + 0x80, 0xaf, 0x80, 0x44, 0x85, 0x80, 0x40, 0xc6, + 0x80, 0x41, 0x35, 0x81, 0x40, 0x97, 0x85, 0xc3, + 0x85, 0xd8, 0x83, 0x43, 0xb7, 0x84, 0xab, 0x83, + 0x40, 0xbc, 0x86, 0xef, 0x83, 0xfe, 0x82, 0x40, + 0x80, 0x0d, 0x80, 0x8f, 0x81, 0xd7, 0x84, 0xeb, + 0x80, 0x41, 0x29, 0x81, 0xf4, 0x82, 0x8b, 0x81, + 0x41, 0x65, 0x1a, 0x8e, 0xe8, 0x81, 0x40, 0xf8, + 0x82, 0x42, 0x04, 0x00, 0x80, 0x40, 0xfa, 0x81, + 0xd6, 0x0b, 0x81, 0x41, 0x9d, 0x82, 0xac, 0x80, + 0x42, 0x84, 0x81, 0xc9, 0x81, 0x45, 0x2a, 0x84, + 0x60, 0x45, 0xf8, 0x81, 0x40, 0x84, 0x80, 0xc0, + 0x82, 0x89, 0x80, 0x42, 0x28, 0x81, 0x41, 0x26, + 0x81, 0x60, 0x4e, 0x05, 0x80, 0x5d, 0xe6, 0x83, +}; + +static const uint8_t unicode_prop_Unified_Ideograph_table[48] = { + 0x60, 0x33, 0xff, 0x59, 0xbf, 0xbf, 0x60, 0x51, + 0xff, 0x60, 0x5a, 0x0d, 0x08, 0x00, 0x81, 0x89, + 0x00, 0x00, 0x09, 0x82, 0x61, 0x05, 0xd5, 0x60, + 0xa6, 0xdf, 0x9f, 0x50, 0x39, 0x85, 0x40, 0xdd, + 0x81, 0x56, 0x81, 0x8d, 0x5d, 0x30, 0x8e, 0x42, + 0x6d, 0x51, 0xa1, 0x53, 0x4a, 0x84, 0x50, 0x5f, +}; + +static const uint8_t unicode_prop_Variation_Selector_table[13] = { + 0x58, 0x0a, 0x10, 0x80, 0x60, 0xe5, 0xef, 0x8f, + 0x6d, 0x02, 0xef, 0x40, 0xef, +}; + +static const uint8_t unicode_prop_Bidi_Mirrored_table[173] = { + 0xa7, 0x81, 0x91, 0x00, 0x80, 0x9b, 0x00, 0x80, + 0x9c, 0x00, 0x80, 0xac, 0x80, 0x8e, 0x80, 0x4e, + 0x7d, 0x83, 0x47, 0x5c, 0x81, 0x49, 0x9b, 0x81, + 0x89, 0x81, 0xb5, 0x81, 0x8d, 0x81, 0x40, 0xb0, + 0x80, 0x40, 0xbf, 0x1a, 0x2a, 0x02, 0x0a, 0x18, + 0x18, 0x00, 0x03, 0x88, 0x20, 0x80, 0x91, 0x23, + 0x88, 0x08, 0x00, 0x38, 0x9f, 0x0b, 0x20, 0x88, + 0x09, 0x92, 0x21, 0x88, 0x21, 0x0b, 0x97, 0x81, + 0x8f, 0x3b, 0x93, 0x0e, 0x81, 0x44, 0x3c, 0x8d, + 0xc9, 0x01, 0x18, 0x08, 0x14, 0x1c, 0x12, 0x8d, + 0x41, 0x92, 0x95, 0x0d, 0x80, 0x8d, 0x38, 0x35, + 0x10, 0x1c, 0x01, 0x0c, 0x18, 0x02, 0x09, 0x89, + 0x29, 0x81, 0x8b, 0x92, 0x03, 0x08, 0x00, 0x08, + 0x03, 0x21, 0x2a, 0x97, 0x81, 0x8a, 0x0b, 0x18, + 0x09, 0x0b, 0xaa, 0x0f, 0x80, 0xa7, 0x20, 0x00, + 0x14, 0x22, 0x18, 0x14, 0x00, 0x40, 0xff, 0x80, + 0x42, 0x02, 0x1a, 0x08, 0x81, 0x8d, 0x09, 0x89, + 0xaa, 0x87, 0x41, 0xaa, 0x89, 0x0f, 0x60, 0xce, + 0x3c, 0x2c, 0x81, 0x40, 0xa1, 0x81, 0x91, 0x00, + 0x80, 0x9b, 0x00, 0x80, 0x9c, 0x00, 0x00, 0x08, + 0x81, 0x60, 0xd7, 0x76, 0x80, 0xb8, 0x80, 0xb8, + 0x80, 0xb8, 0x80, 0xb8, 0x80, +}; + +static const uint8_t unicode_prop_Emoji_table[238] = { + 0xa2, 0x05, 0x04, 0x89, 0xee, 0x03, 0x80, 0x5f, + 0x8c, 0x80, 0x8b, 0x80, 0x40, 0xd7, 0x80, 0x95, + 0x80, 0xd9, 0x85, 0x8e, 0x81, 0x41, 0x6e, 0x81, + 0x8b, 0x80, 0x40, 0xa5, 0x80, 0x98, 0x8a, 0x1a, + 0x40, 0xc6, 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, + 0x88, 0x80, 0xb9, 0x18, 0x84, 0x88, 0x01, 0x01, + 0x09, 0x03, 0x01, 0x00, 0x09, 0x02, 0x02, 0x0f, + 0x14, 0x00, 0x04, 0x8b, 0x8a, 0x09, 0x00, 0x08, + 0x80, 0x91, 0x01, 0x81, 0x91, 0x28, 0x00, 0x0a, + 0x0c, 0x01, 0x0b, 0x81, 0x8a, 0x0c, 0x09, 0x04, + 0x08, 0x00, 0x81, 0x93, 0x0c, 0x28, 0x19, 0x03, + 0x01, 0x01, 0x28, 0x01, 0x00, 0x00, 0x05, 0x02, + 0x05, 0x80, 0x89, 0x81, 0x8e, 0x01, 0x03, 0x00, + 0x03, 0x10, 0x80, 0x8a, 0x81, 0xaf, 0x82, 0x88, + 0x80, 0x8d, 0x80, 0x8d, 0x80, 0x41, 0x73, 0x81, + 0x41, 0xce, 0x82, 0x92, 0x81, 0xb2, 0x03, 0x80, + 0x44, 0xd9, 0x80, 0x8b, 0x80, 0x42, 0x58, 0x00, + 0x80, 0x61, 0xbd, 0x69, 0x80, 0x40, 0xc9, 0x80, + 0x40, 0x9f, 0x81, 0x8b, 0x81, 0x8d, 0x01, 0x89, + 0xca, 0x99, 0x01, 0x96, 0x80, 0x93, 0x01, 0x88, + 0x94, 0x81, 0x40, 0xad, 0xa1, 0x81, 0xef, 0x09, + 0x02, 0x81, 0xd2, 0x0a, 0x80, 0x41, 0x06, 0x80, + 0xbe, 0x8a, 0x28, 0x97, 0x31, 0x0f, 0x8b, 0x01, + 0x19, 0x03, 0x81, 0x8c, 0x09, 0x07, 0x81, 0x88, + 0x04, 0x82, 0x8b, 0x17, 0x11, 0x00, 0x03, 0x05, + 0x02, 0x05, 0xd5, 0xaf, 0xc5, 0x27, 0x0a, 0x83, + 0x89, 0x10, 0x01, 0x10, 0x81, 0x89, 0x40, 0xe2, + 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, 0x89, 0x80, + 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, 0x84, 0xb7, + 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Component_table[28] = { + 0xa2, 0x05, 0x04, 0x89, 0x5f, 0xd2, 0x80, 0x40, + 0xd4, 0x80, 0x60, 0xdd, 0x2a, 0x80, 0x60, 0xf3, + 0xd5, 0x99, 0x41, 0xfa, 0x84, 0x45, 0xaf, 0x83, + 0x6c, 0x06, 0x6b, 0xdf, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_table[4] = { + 0x61, 0xf3, 0xfa, 0x84, +}; + +static const uint8_t unicode_prop_Emoji_Modifier_Base_table[71] = { + 0x60, 0x26, 0x1c, 0x80, 0x40, 0xda, 0x80, 0x8f, + 0x83, 0x61, 0xcc, 0x76, 0x80, 0xbb, 0x11, 0x01, + 0x82, 0xf4, 0x09, 0x8a, 0x94, 0x92, 0x10, 0x1a, + 0x02, 0x30, 0x00, 0x97, 0x80, 0x40, 0xc8, 0x0b, + 0x80, 0x94, 0x03, 0x81, 0x40, 0xad, 0x12, 0x84, + 0xd2, 0x80, 0x8f, 0x82, 0x88, 0x80, 0x8a, 0x80, + 0x42, 0x3e, 0x01, 0x07, 0x3d, 0x80, 0x88, 0x89, + 0x0a, 0xb7, 0x80, 0xbc, 0x08, 0x08, 0x80, 0x90, + 0x10, 0x8c, 0x40, 0xe4, 0x82, 0xa9, 0x88, +}; + +static const uint8_t unicode_prop_Emoji_Presentation_table[144] = { + 0x60, 0x23, 0x19, 0x81, 0x40, 0xcc, 0x1a, 0x01, + 0x80, 0x42, 0x08, 0x81, 0x94, 0x81, 0xb1, 0x8b, + 0xaa, 0x80, 0x92, 0x80, 0x8c, 0x07, 0x81, 0x90, + 0x0c, 0x0f, 0x04, 0x80, 0x94, 0x06, 0x08, 0x03, + 0x01, 0x06, 0x03, 0x81, 0x9b, 0x80, 0xa2, 0x00, + 0x03, 0x10, 0x80, 0xbc, 0x82, 0x97, 0x80, 0x8d, + 0x80, 0x43, 0x5a, 0x81, 0xb2, 0x03, 0x80, 0x61, + 0xc4, 0xad, 0x80, 0x40, 0xc9, 0x80, 0x40, 0xbd, + 0x01, 0x89, 0xca, 0x99, 0x00, 0x97, 0x80, 0x93, + 0x01, 0x20, 0x82, 0x94, 0x81, 0x40, 0xad, 0xa0, + 0x8b, 0x88, 0x80, 0xc5, 0x80, 0x95, 0x8b, 0xaa, + 0x1c, 0x8b, 0x90, 0x10, 0x82, 0xc6, 0x00, 0x80, + 0x40, 0xba, 0x81, 0xbe, 0x8c, 0x18, 0x97, 0x91, + 0x80, 0x99, 0x81, 0x8c, 0x80, 0xd5, 0xd4, 0xaf, + 0xc5, 0x28, 0x12, 0x0a, 0x1b, 0x8a, 0x0e, 0x88, + 0x40, 0xe2, 0x8b, 0x18, 0x41, 0x1a, 0xae, 0x80, + 0x89, 0x80, 0x40, 0xb8, 0xef, 0x8c, 0x82, 0x89, + 0x84, 0xb7, 0x86, 0x8e, 0x81, 0x8a, 0x85, 0x88, +}; + +static const uint8_t unicode_prop_Extended_Pictographic_table[156] = { + 0x40, 0xa8, 0x03, 0x80, 0x5f, 0x8c, 0x80, 0x8b, + 0x80, 0x40, 0xd7, 0x80, 0x95, 0x80, 0xd9, 0x85, + 0x8e, 0x81, 0x41, 0x6e, 0x81, 0x8b, 0x80, 0xde, + 0x80, 0xc5, 0x80, 0x98, 0x8a, 0x1a, 0x40, 0xc6, + 0x80, 0x40, 0xe6, 0x81, 0x89, 0x80, 0x88, 0x80, + 0xb9, 0x18, 0x28, 0x8b, 0x80, 0xf1, 0x89, 0xf5, + 0x81, 0x8a, 0x00, 0x00, 0x28, 0x10, 0x28, 0x89, + 0x81, 0x8e, 0x01, 0x03, 0x00, 0x03, 0x10, 0x80, + 0x8a, 0x84, 0xac, 0x82, 0x88, 0x80, 0x8d, 0x80, + 0x8d, 0x80, 0x41, 0x73, 0x81, 0x41, 0xce, 0x82, + 0x92, 0x81, 0xb2, 0x03, 0x80, 0x44, 0xd9, 0x80, + 0x8b, 0x80, 0x42, 0x58, 0x00, 0x80, 0x61, 0xbd, + 0x65, 0x40, 0xff, 0x8c, 0x82, 0x9e, 0x80, 0xbb, + 0x85, 0x8b, 0x81, 0x8d, 0x01, 0x89, 0x91, 0xb8, + 0x9a, 0x8e, 0x89, 0x80, 0x93, 0x01, 0x88, 0x03, + 0x88, 0x41, 0xb1, 0x84, 0x41, 0x3d, 0x87, 0x41, + 0x09, 0xaf, 0xff, 0xf3, 0x8b, 0xd4, 0xaa, 0x8b, + 0x83, 0xb7, 0x87, 0x89, 0x85, 0xa7, 0x87, 0x9d, + 0xd1, 0x8b, 0xae, 0x80, 0x89, 0x80, 0x41, 0xb8, + 0x40, 0xff, 0x43, 0xfd, +}; + +static const uint8_t unicode_prop_Default_Ignorable_Code_Point_table[51] = { + 0x40, 0xac, 0x80, 0x42, 0xa0, 0x80, 0x42, 0xcb, + 0x80, 0x4b, 0x41, 0x81, 0x46, 0x52, 0x81, 0xd4, + 0x84, 0x47, 0xfa, 0x84, 0x99, 0x84, 0xb0, 0x8f, + 0x50, 0xf3, 0x80, 0x60, 0xcc, 0x9a, 0x8f, 0x40, + 0xee, 0x80, 0x40, 0x9f, 0x80, 0xce, 0x88, 0x60, + 0xbc, 0xa6, 0x83, 0x54, 0xce, 0x87, 0x6c, 0x2e, + 0x84, 0x4f, 0xff, +}; + +typedef enum { + UNICODE_PROP_Hyphen, + UNICODE_PROP_Other_Math, + UNICODE_PROP_Other_Alphabetic, + UNICODE_PROP_Other_Lowercase, + UNICODE_PROP_Other_Uppercase, + UNICODE_PROP_Other_Grapheme_Extend, + UNICODE_PROP_Other_Default_Ignorable_Code_Point, + UNICODE_PROP_Other_ID_Start, + UNICODE_PROP_Other_ID_Continue, + UNICODE_PROP_Prepended_Concatenation_Mark, + UNICODE_PROP_ID_Continue1, + UNICODE_PROP_XID_Start1, + UNICODE_PROP_XID_Continue1, + UNICODE_PROP_Changes_When_Titlecased1, + UNICODE_PROP_Changes_When_Casefolded1, + UNICODE_PROP_Changes_When_NFKC_Casefolded1, + UNICODE_PROP_ASCII_Hex_Digit, + UNICODE_PROP_Bidi_Control, + UNICODE_PROP_Dash, + UNICODE_PROP_Deprecated, + UNICODE_PROP_Diacritic, + UNICODE_PROP_Extender, + UNICODE_PROP_Hex_Digit, + UNICODE_PROP_IDS_Unary_Operator, + UNICODE_PROP_IDS_Binary_Operator, + UNICODE_PROP_IDS_Trinary_Operator, + UNICODE_PROP_Ideographic, + UNICODE_PROP_Join_Control, + UNICODE_PROP_Logical_Order_Exception, + UNICODE_PROP_Modifier_Combining_Mark, + UNICODE_PROP_Noncharacter_Code_Point, + UNICODE_PROP_Pattern_Syntax, + UNICODE_PROP_Pattern_White_Space, + UNICODE_PROP_Quotation_Mark, + UNICODE_PROP_Radical, + UNICODE_PROP_Regional_Indicator, + UNICODE_PROP_Sentence_Terminal, + UNICODE_PROP_Soft_Dotted, + UNICODE_PROP_Terminal_Punctuation, + UNICODE_PROP_Unified_Ideograph, + UNICODE_PROP_Variation_Selector, + UNICODE_PROP_White_Space, + UNICODE_PROP_Bidi_Mirrored, + UNICODE_PROP_Emoji, + UNICODE_PROP_Emoji_Component, + UNICODE_PROP_Emoji_Modifier, + UNICODE_PROP_Emoji_Modifier_Base, + UNICODE_PROP_Emoji_Presentation, + UNICODE_PROP_Extended_Pictographic, + UNICODE_PROP_Default_Ignorable_Code_Point, + UNICODE_PROP_ID_Start, + UNICODE_PROP_Case_Ignorable, + UNICODE_PROP_ASCII, + UNICODE_PROP_Alphabetic, + UNICODE_PROP_Any, + UNICODE_PROP_Assigned, + UNICODE_PROP_Cased, + UNICODE_PROP_Changes_When_Casefolded, + UNICODE_PROP_Changes_When_Casemapped, + UNICODE_PROP_Changes_When_Lowercased, + UNICODE_PROP_Changes_When_NFKC_Casefolded, + UNICODE_PROP_Changes_When_Titlecased, + UNICODE_PROP_Changes_When_Uppercased, + UNICODE_PROP_Grapheme_Base, + UNICODE_PROP_Grapheme_Extend, + UNICODE_PROP_ID_Continue, + UNICODE_PROP_ID_Compat_Math_Start, + UNICODE_PROP_ID_Compat_Math_Continue, + UNICODE_PROP_Lowercase, + UNICODE_PROP_Math, + UNICODE_PROP_Uppercase, + UNICODE_PROP_XID_Continue, + UNICODE_PROP_XID_Start, + UNICODE_PROP_Cased1, + UNICODE_PROP_InCB, + UNICODE_PROP_COUNT, +} UnicodePropertyEnum; + +static const char unicode_prop_name_table[] = + "ASCII_Hex_Digit,AHex" "\0" + "Bidi_Control,Bidi_C" "\0" + "Dash" "\0" + "Deprecated,Dep" "\0" + "Diacritic,Dia" "\0" + "Extender,Ext" "\0" + "Hex_Digit,Hex" "\0" + "IDS_Unary_Operator,IDSU" "\0" + "IDS_Binary_Operator,IDSB" "\0" + "IDS_Trinary_Operator,IDST" "\0" + "Ideographic,Ideo" "\0" + "Join_Control,Join_C" "\0" + "Logical_Order_Exception,LOE" "\0" + "Modifier_Combining_Mark,MCM" "\0" + "Noncharacter_Code_Point,NChar" "\0" + "Pattern_Syntax,Pat_Syn" "\0" + "Pattern_White_Space,Pat_WS" "\0" + "Quotation_Mark,QMark" "\0" + "Radical" "\0" + "Regional_Indicator,RI" "\0" + "Sentence_Terminal,STerm" "\0" + "Soft_Dotted,SD" "\0" + "Terminal_Punctuation,Term" "\0" + "Unified_Ideograph,UIdeo" "\0" + "Variation_Selector,VS" "\0" + "White_Space,space" "\0" + "Bidi_Mirrored,Bidi_M" "\0" + "Emoji" "\0" + "Emoji_Component,EComp" "\0" + "Emoji_Modifier,EMod" "\0" + "Emoji_Modifier_Base,EBase" "\0" + "Emoji_Presentation,EPres" "\0" + "Extended_Pictographic,ExtPict" "\0" + "Default_Ignorable_Code_Point,DI" "\0" + "ID_Start,IDS" "\0" + "Case_Ignorable,CI" "\0" + "ASCII" "\0" + "Alphabetic,Alpha" "\0" + "Any" "\0" + "Assigned" "\0" + "Cased" "\0" + "Changes_When_Casefolded,CWCF" "\0" + "Changes_When_Casemapped,CWCM" "\0" + "Changes_When_Lowercased,CWL" "\0" + "Changes_When_NFKC_Casefolded,CWKCF" "\0" + "Changes_When_Titlecased,CWT" "\0" + "Changes_When_Uppercased,CWU" "\0" + "Grapheme_Base,Gr_Base" "\0" + "Grapheme_Extend,Gr_Ext" "\0" + "ID_Continue,IDC" "\0" + "ID_Compat_Math_Start" "\0" + "ID_Compat_Math_Continue" "\0" + "Lowercase,Lower" "\0" + "Math" "\0" + "Uppercase,Upper" "\0" + "XID_Continue,XIDC" "\0" + "XID_Start,XIDS" "\0" +; + +static const uint8_t * const unicode_prop_table[] = { + unicode_prop_Hyphen_table, + unicode_prop_Other_Math_table, + unicode_prop_Other_Alphabetic_table, + unicode_prop_Other_Lowercase_table, + unicode_prop_Other_Uppercase_table, + unicode_prop_Other_Grapheme_Extend_table, + unicode_prop_Other_Default_Ignorable_Code_Point_table, + unicode_prop_Other_ID_Start_table, + unicode_prop_Other_ID_Continue_table, + unicode_prop_Prepended_Concatenation_Mark_table, + unicode_prop_ID_Continue1_table, + unicode_prop_XID_Start1_table, + unicode_prop_XID_Continue1_table, + unicode_prop_Changes_When_Titlecased1_table, + unicode_prop_Changes_When_Casefolded1_table, + unicode_prop_Changes_When_NFKC_Casefolded1_table, + unicode_prop_ASCII_Hex_Digit_table, + unicode_prop_Bidi_Control_table, + unicode_prop_Dash_table, + unicode_prop_Deprecated_table, + unicode_prop_Diacritic_table, + unicode_prop_Extender_table, + unicode_prop_Hex_Digit_table, + unicode_prop_IDS_Unary_Operator_table, + unicode_prop_IDS_Binary_Operator_table, + unicode_prop_IDS_Trinary_Operator_table, + unicode_prop_Ideographic_table, + unicode_prop_Join_Control_table, + unicode_prop_Logical_Order_Exception_table, + unicode_prop_Modifier_Combining_Mark_table, + unicode_prop_Noncharacter_Code_Point_table, + unicode_prop_Pattern_Syntax_table, + unicode_prop_Pattern_White_Space_table, + unicode_prop_Quotation_Mark_table, + unicode_prop_Radical_table, + unicode_prop_Regional_Indicator_table, + unicode_prop_Sentence_Terminal_table, + unicode_prop_Soft_Dotted_table, + unicode_prop_Terminal_Punctuation_table, + unicode_prop_Unified_Ideograph_table, + unicode_prop_Variation_Selector_table, + unicode_prop_White_Space_table, + unicode_prop_Bidi_Mirrored_table, + unicode_prop_Emoji_table, + unicode_prop_Emoji_Component_table, + unicode_prop_Emoji_Modifier_table, + unicode_prop_Emoji_Modifier_Base_table, + unicode_prop_Emoji_Presentation_table, + unicode_prop_Extended_Pictographic_table, + unicode_prop_Default_Ignorable_Code_Point_table, + unicode_prop_ID_Start_table, + unicode_prop_Case_Ignorable_table, +}; + +static const uint16_t unicode_prop_len_table[] = { + countof(unicode_prop_Hyphen_table), + countof(unicode_prop_Other_Math_table), + countof(unicode_prop_Other_Alphabetic_table), + countof(unicode_prop_Other_Lowercase_table), + countof(unicode_prop_Other_Uppercase_table), + countof(unicode_prop_Other_Grapheme_Extend_table), + countof(unicode_prop_Other_Default_Ignorable_Code_Point_table), + countof(unicode_prop_Other_ID_Start_table), + countof(unicode_prop_Other_ID_Continue_table), + countof(unicode_prop_Prepended_Concatenation_Mark_table), + countof(unicode_prop_ID_Continue1_table), + countof(unicode_prop_XID_Start1_table), + countof(unicode_prop_XID_Continue1_table), + countof(unicode_prop_Changes_When_Titlecased1_table), + countof(unicode_prop_Changes_When_Casefolded1_table), + countof(unicode_prop_Changes_When_NFKC_Casefolded1_table), + countof(unicode_prop_ASCII_Hex_Digit_table), + countof(unicode_prop_Bidi_Control_table), + countof(unicode_prop_Dash_table), + countof(unicode_prop_Deprecated_table), + countof(unicode_prop_Diacritic_table), + countof(unicode_prop_Extender_table), + countof(unicode_prop_Hex_Digit_table), + countof(unicode_prop_IDS_Unary_Operator_table), + countof(unicode_prop_IDS_Binary_Operator_table), + countof(unicode_prop_IDS_Trinary_Operator_table), + countof(unicode_prop_Ideographic_table), + countof(unicode_prop_Join_Control_table), + countof(unicode_prop_Logical_Order_Exception_table), + countof(unicode_prop_Modifier_Combining_Mark_table), + countof(unicode_prop_Noncharacter_Code_Point_table), + countof(unicode_prop_Pattern_Syntax_table), + countof(unicode_prop_Pattern_White_Space_table), + countof(unicode_prop_Quotation_Mark_table), + countof(unicode_prop_Radical_table), + countof(unicode_prop_Regional_Indicator_table), + countof(unicode_prop_Sentence_Terminal_table), + countof(unicode_prop_Soft_Dotted_table), + countof(unicode_prop_Terminal_Punctuation_table), + countof(unicode_prop_Unified_Ideograph_table), + countof(unicode_prop_Variation_Selector_table), + countof(unicode_prop_White_Space_table), + countof(unicode_prop_Bidi_Mirrored_table), + countof(unicode_prop_Emoji_table), + countof(unicode_prop_Emoji_Component_table), + countof(unicode_prop_Emoji_Modifier_table), + countof(unicode_prop_Emoji_Modifier_Base_table), + countof(unicode_prop_Emoji_Presentation_table), + countof(unicode_prop_Extended_Pictographic_table), + countof(unicode_prop_Default_Ignorable_Code_Point_table), + countof(unicode_prop_ID_Start_table), + countof(unicode_prop_Case_Ignorable_table), +}; + diff --git a/lib/monoucha0/monoucha/qjs/libunicode.c b/lib/monoucha0/monoucha/qjs/libunicode.c new file mode 100644 index 00000000..e68b0cad --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libunicode.c @@ -0,0 +1,1744 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 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 <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <assert.h> + +#include "cutils.h" +#include "libunicode.h" +#include "libunicode-table.h" + +// note: stored as 4 bit tag, not much room left +enum { + RUN_TYPE_U, + RUN_TYPE_L, + RUN_TYPE_UF, + RUN_TYPE_LF, + RUN_TYPE_UL, + RUN_TYPE_LSU, + RUN_TYPE_U2L_399_EXT2, + RUN_TYPE_UF_D20, + RUN_TYPE_UF_D1_EXT, + RUN_TYPE_U_EXT, + RUN_TYPE_LF_EXT, + RUN_TYPE_UF_EXT2, + RUN_TYPE_LF_EXT2, + RUN_TYPE_UF_EXT3, +}; + +static int lre_case_conv1(uint32_t c, int conv_type) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + lre_case_conv(res, c, conv_type); + return res[0]; +} + +/* case conversion using the table entry 'idx' with value 'v' */ +static int lre_case_conv_entry(uint32_t *res, uint32_t c, int conv_type, uint32_t idx, uint32_t v) +{ + uint32_t code, data, type, a, is_lower; + is_lower = (conv_type != 0); + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + data = ((v & 0xf) << 8) | case_conv_table2[idx]; + code = v >> (32 - 17); + switch(type) { + case RUN_TYPE_U: + case RUN_TYPE_L: + case RUN_TYPE_UF: + case RUN_TYPE_LF: + if (conv_type == (type & 1) || + (type >= RUN_TYPE_UF && conv_type == 2)) { + c = c - code + (case_conv_table1[data] >> (32 - 17)); + } + break; + case RUN_TYPE_UL: + a = c - code; + if ((a & 1) != (1 - is_lower)) + break; + c = (a ^ 1) + code; + break; + case RUN_TYPE_LSU: + a = c - code; + if (a == 1) { + c += 2 * is_lower - 1; + } else if (a == (1 - is_lower) * 2) { + c += (2 * is_lower - 1) * 2; + } + break; + case RUN_TYPE_U2L_399_EXT2: + if (!is_lower) { + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = 0x399; + return 2; + } else { + c = c - code + case_conv_ext[data & 0x3f]; + } + break; + case RUN_TYPE_UF_D20: + if (conv_type == 1) + break; + c = data + (conv_type == 2) * 0x20; + break; + case RUN_TYPE_UF_D1_EXT: + if (conv_type == 1) + break; + c = case_conv_ext[data] + (conv_type == 2); + break; + case RUN_TYPE_U_EXT: + case RUN_TYPE_LF_EXT: + if (is_lower != (type - RUN_TYPE_U_EXT)) + break; + c = case_conv_ext[data]; + break; + case RUN_TYPE_LF_EXT2: + if (!is_lower) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + return 2; + case RUN_TYPE_UF_EXT2: + if (conv_type == 1) + break; + res[0] = c - code + case_conv_ext[data >> 6]; + res[1] = case_conv_ext[data & 0x3f]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + } + return 2; + default: + case RUN_TYPE_UF_EXT3: + if (conv_type == 1) + break; + res[0] = case_conv_ext[data >> 8]; + res[1] = case_conv_ext[(data >> 4) & 0xf]; + res[2] = case_conv_ext[data & 0xf]; + if (conv_type == 2) { + /* convert to lower */ + res[0] = lre_case_conv1(res[0], 1); + res[1] = lre_case_conv1(res[1], 1); + res[2] = lre_case_conv1(res[2], 1); + } + return 3; + } + res[0] = c; + return 1; +} + +/* conv_type: + 0 = to upper + 1 = to lower + 2 = case folding (= to lower with modifications) +*/ +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type) +{ + if (c < 128) { + if (conv_type) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_conv_entry(res, c, conv_type, idx, v); + } + } + } + res[0] = c; + return 1; +} + +static int lre_case_folding_entry(uint32_t c, uint32_t idx, uint32_t v, BOOL is_unicode) +{ + uint32_t res[LRE_CC_RES_LEN_MAX]; + int len; + + if (is_unicode) { + len = lre_case_conv_entry(res, c, 2, idx, v); + if (len == 1) { + c = res[0]; + } else { + /* handle the few specific multi-character cases (see + unicode_gen.c:dump_case_folding_special_cases()) */ + if (c == 0xfb06) { + c = 0xfb05; + } else if (c == 0x01fd3) { + c = 0x390; + } else if (c == 0x01fe3) { + c = 0x3b0; + } + } + } else { + if (likely(c < 128)) { + if (c >= 'a' && c <= 'z') + c = c - 'a' + 'A'; + } else { + /* legacy regexp: to upper case if single char >= 128 */ + len = lre_case_conv_entry(res, c, FALSE, idx, v); + if (len == 1 && res[0] >= 128) + c = res[0]; + } + } + return c; +} + +/* JS regexp specific rules for case folding */ +int lre_canonicalize(uint32_t c, BOOL is_unicode) +{ + if (c < 128) { + /* fast case */ + if (is_unicode) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } else { + if (c >= 'a' && c <= 'z') { + c = c - 'a' + 'A'; + } + } + } else { + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return lre_case_folding_entry(c, idx, v, is_unicode); + } + } + } + return c; +} + +static uint32_t get_le24(const uint8_t *ptr) +{ + return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16); +} + +#define UNICODE_INDEX_BLOCK_LEN 32 + +/* return -1 if not in table, otherwise the offset in the block */ +static int get_index_pos(uint32_t *pcode, uint32_t c, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, v; + int idx_min, idx_max, idx; + + idx_min = 0; + v = get_le24(index_table); + code = v & ((1 << 21) - 1); + if (c < code) { + *pcode = 0; + return 0; + } + idx_max = index_table_len - 1; + code = get_le24(index_table + idx_max * 3); + if (c >= code) + return -1; + /* invariant: tab[idx_min] <= c < tab2[idx_max] */ + while ((idx_max - idx_min) > 1) { + idx = (idx_max + idx_min) / 2; + v = get_le24(index_table + idx * 3); + code = v & ((1 << 21) - 1); + if (c < code) { + idx_max = idx; + } else { + idx_min = idx; + } + } + v = get_le24(index_table + idx_min * 3); + *pcode = v & ((1 << 21) - 1); + return (idx_min + 1) * UNICODE_INDEX_BLOCK_LEN + (v >> 21); +} + +static BOOL lre_is_in_table(uint32_t c, const uint8_t *table, + const uint8_t *index_table, int index_table_len) +{ + uint32_t code, b, bit; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, index_table, index_table_len); + if (pos < 0) + return FALSE; /* outside the table */ + p = table + pos; + bit = 0; + for(;;) { + b = *p++; + if (b < 64) { + code += (b >> 3) + 1; + if (c < code) + return bit; + bit ^= 1; + code += (b & 7) + 1; + } else if (b >= 0x80) { + code += b - 0x80 + 1; + } else if (b < 0x60) { + code += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + code += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (c < code) + return bit; + bit ^= 1; + } +} + +BOOL lre_is_cased(uint32_t c) +{ + uint32_t v, code, len; + int idx, idx_min, idx_max; + + idx_min = 0; + idx_max = countof(case_conv_table1) - 1; + while (idx_min <= idx_max) { + idx = (unsigned)(idx_max + idx_min) / 2; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + return TRUE; + } + } + return lre_is_in_table(c, unicode_prop_Cased1_table, + unicode_prop_Cased1_index, + sizeof(unicode_prop_Cased1_index) / 3); +} + +BOOL lre_is_case_ignorable(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_Case_Ignorable_table, + unicode_prop_Case_Ignorable_index, + sizeof(unicode_prop_Case_Ignorable_index) / 3); +} + +/* character range */ + +static __maybe_unused void cr_dump(CharRange *cr) +{ + int i; + for(i = 0; i < cr->len; i++) + printf("%d: 0x%04x\n", i, cr->points[i]); +} + +static void *cr_default_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +void cr_init(CharRange *cr, void *mem_opaque, DynBufReallocFunc *realloc_func) +{ + cr->len = cr->size = 0; + cr->points = NULL; + cr->mem_opaque = mem_opaque; + cr->realloc_func = realloc_func ? realloc_func : cr_default_realloc; +} + +void cr_free(CharRange *cr) +{ + cr->realloc_func(cr->mem_opaque, cr->points, 0); +} + +int cr_realloc(CharRange *cr, int size) +{ + int new_size; + uint32_t *new_buf; + + if (size > cr->size) { + new_size = max_int(size, cr->size * 3 / 2); + new_buf = cr->realloc_func(cr->mem_opaque, cr->points, + new_size * sizeof(cr->points[0])); + if (!new_buf) + return -1; + cr->points = new_buf; + cr->size = new_size; + } + return 0; +} + +int cr_copy(CharRange *cr, const CharRange *cr1) +{ + if (cr_realloc(cr, cr1->len)) + return -1; + memcpy(cr->points, cr1->points, sizeof(cr->points[0]) * cr1->len); + cr->len = cr1->len; + return 0; +} + +/* merge consecutive intervals and remove empty intervals */ +static void cr_compress(CharRange *cr) +{ + int i, j, k, len; + uint32_t *pt; + + pt = cr->points; + len = cr->len; + i = 0; + j = 0; + k = 0; + while ((i + 1) < len) { + if (pt[i] == pt[i + 1]) { + /* empty interval */ + i += 2; + } else { + j = i; + while ((j + 3) < len && pt[j + 1] == pt[j + 2]) + j += 2; + /* just copy */ + pt[k] = pt[i]; + pt[k + 1] = pt[j + 1]; + k += 2; + i = j + 2; + } + } + cr->len = k; +} + +/* union or intersection */ +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op) +{ + int a_idx, b_idx, is_in; + uint32_t v; + + a_idx = 0; + b_idx = 0; + for(;;) { + /* get one more point from a or b in increasing order */ + if (a_idx < a_len && b_idx < b_len) { + if (a_pt[a_idx] < b_pt[b_idx]) { + goto a_add; + } else if (a_pt[a_idx] == b_pt[b_idx]) { + v = a_pt[a_idx]; + a_idx++; + b_idx++; + } else { + goto b_add; + } + } else if (a_idx < a_len) { + a_add: + v = a_pt[a_idx++]; + } else if (b_idx < b_len) { + b_add: + v = b_pt[b_idx++]; + } else { + break; + } + /* add the point if the in/out status changes */ + switch(op) { + case CR_OP_UNION: + is_in = (a_idx & 1) | (b_idx & 1); + break; + case CR_OP_INTER: + is_in = (a_idx & 1) & (b_idx & 1); + break; + case CR_OP_XOR: + is_in = (a_idx & 1) ^ (b_idx & 1); + break; + default: + abort(); + } + if (is_in != (cr->len & 1)) { + if (cr_add_point(cr, v)) + return -1; + } + } + cr_compress(cr); + return 0; +} + +int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len) +{ + CharRange a = *cr; + int ret; + cr->len = 0; + cr->size = 0; + cr->points = NULL; + ret = cr_op(cr, a.points, a.len, b_pt, b_len, CR_OP_UNION); + cr_free(&a); + return ret; +} + +int cr_invert(CharRange *cr) +{ + int len; + len = cr->len; + if (cr_realloc(cr, len + 2)) + return -1; + memmove(cr->points + 1, cr->points, len * sizeof(cr->points[0])); + cr->points[0] = 0; + cr->points[len + 1] = UINT32_MAX; + cr->len = len + 2; + cr_compress(cr); + return 0; +} + +BOOL lre_is_id_start(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_ID_Start_table, + unicode_prop_ID_Start_index, + sizeof(unicode_prop_ID_Start_index) / 3); +} + +BOOL lre_is_id_continue(uint32_t c) +{ + return lre_is_id_start(c) || + lre_is_in_table(c, unicode_prop_ID_Continue1_table, + unicode_prop_ID_Continue1_index, + sizeof(unicode_prop_ID_Continue1_index) / 3); +} + +BOOL lre_is_white_space(uint32_t c) +{ + return lre_is_in_table(c, unicode_prop_White_Space_table, + unicode_prop_White_Space_index, + sizeof(unicode_prop_White_Space_index) / 3); +} + +#define UNICODE_DECOMP_LEN_MAX 18 + +typedef enum { + DECOMP_TYPE_C1, /* 16 bit char */ + DECOMP_TYPE_L1, /* 16 bit char table */ + DECOMP_TYPE_L2, + DECOMP_TYPE_L3, + DECOMP_TYPE_L4, + DECOMP_TYPE_L5, /* XXX: not used */ + DECOMP_TYPE_L6, /* XXX: could remove */ + DECOMP_TYPE_L7, /* XXX: could remove */ + DECOMP_TYPE_LL1, /* 18 bit char table */ + DECOMP_TYPE_LL2, + DECOMP_TYPE_S1, /* 8 bit char table */ + DECOMP_TYPE_S2, + DECOMP_TYPE_S3, + DECOMP_TYPE_S4, + DECOMP_TYPE_S5, + DECOMP_TYPE_I1, /* increment 16 bit char value */ + DECOMP_TYPE_I2_0, + DECOMP_TYPE_I2_1, + DECOMP_TYPE_I3_1, + DECOMP_TYPE_I3_2, + DECOMP_TYPE_I4_1, + DECOMP_TYPE_I4_2, + DECOMP_TYPE_B1, /* 16 bit base + 8 bit offset */ + DECOMP_TYPE_B2, + DECOMP_TYPE_B3, + DECOMP_TYPE_B4, + DECOMP_TYPE_B5, + DECOMP_TYPE_B6, + DECOMP_TYPE_B7, + DECOMP_TYPE_B8, + DECOMP_TYPE_B18, + DECOMP_TYPE_LS2, + DECOMP_TYPE_PAT3, + DECOMP_TYPE_S2_UL, + DECOMP_TYPE_LS2_UL, +} DecompTypeEnum; + +static uint32_t unicode_get_short_code(uint32_t c) +{ + static const uint16_t unicode_short_table[2] = { 0x2044, 0x2215 }; + + if (c < 0x80) + return c; + else if (c < 0x80 + 0x50) + return c - 0x80 + 0x300; + else + return unicode_short_table[c - 0x80 - 0x50]; +} + +static uint32_t unicode_get_lower_simple(uint32_t c) +{ + if (c < 0x100 || (c >= 0x410 && c <= 0x42f)) + c += 0x20; + else + c++; + return c; +} + +static uint16_t unicode_get16(const uint8_t *p) +{ + return p[0] | (p[1] << 8); +} + +static int unicode_decomp_entry(uint32_t *res, uint32_t c, + int idx, uint32_t code, uint32_t len, + uint32_t type) +{ + uint32_t c1; + int l, i, p; + const uint8_t *d; + + if (type == DECOMP_TYPE_C1) { + res[0] = unicode_decomp_table2[idx]; + return 1; + } else { + d = unicode_decomp_data + unicode_decomp_table2[idx]; + switch(type) { + case DECOMP_TYPE_L1: + case DECOMP_TYPE_L2: + case DECOMP_TYPE_L3: + case DECOMP_TYPE_L4: + case DECOMP_TYPE_L5: + case DECOMP_TYPE_L6: + case DECOMP_TYPE_L7: + l = type - DECOMP_TYPE_L1 + 1; + d += (c - code) * l * 2; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get16(d + 2 * i)) == 0) + return 0; + } + return l; + case DECOMP_TYPE_LL1: + case DECOMP_TYPE_LL2: + { + uint32_t k, p; + l = type - DECOMP_TYPE_LL1 + 1; + k = (c - code) * l; + p = len * l * 2; + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * k) | + (((d[p + (k / 4)] >> ((k % 4) * 2)) & 3) << 16); + if (!c1) + return 0; + res[i] = c1; + k++; + } + } + return l; + case DECOMP_TYPE_S1: + case DECOMP_TYPE_S2: + case DECOMP_TYPE_S3: + case DECOMP_TYPE_S4: + case DECOMP_TYPE_S5: + l = type - DECOMP_TYPE_S1 + 1; + d += (c - code) * l; + for(i = 0; i < l; i++) { + if ((res[i] = unicode_get_short_code(d[i])) == 0) + return 0; + } + return l; + case DECOMP_TYPE_I1: + l = 1; + p = 0; + goto decomp_type_i; + case DECOMP_TYPE_I2_0: + case DECOMP_TYPE_I2_1: + case DECOMP_TYPE_I3_1: + case DECOMP_TYPE_I3_2: + case DECOMP_TYPE_I4_1: + case DECOMP_TYPE_I4_2: + l = 2 + ((type - DECOMP_TYPE_I2_0) >> 1); + p = ((type - DECOMP_TYPE_I2_0) & 1) + (l > 2); + decomp_type_i: + for(i = 0; i < l; i++) { + c1 = unicode_get16(d + 2 * i); + if (i == p) + c1 += c - code; + res[i] = c1; + } + return l; + case DECOMP_TYPE_B18: + l = 18; + goto decomp_type_b; + case DECOMP_TYPE_B1: + case DECOMP_TYPE_B2: + case DECOMP_TYPE_B3: + case DECOMP_TYPE_B4: + case DECOMP_TYPE_B5: + case DECOMP_TYPE_B6: + case DECOMP_TYPE_B7: + case DECOMP_TYPE_B8: + l = type - DECOMP_TYPE_B1 + 1; + decomp_type_b: + { + uint32_t c_min; + c_min = unicode_get16(d); + d += 2 + (c - code) * l; + for(i = 0; i < l; i++) { + c1 = d[i]; + if (c1 == 0xff) + c1 = 0x20; + else + c1 += c_min; + res[i] = c1; + } + } + return l; + case DECOMP_TYPE_LS2: + d += (c - code) * 3; + if (!(res[0] = unicode_get16(d))) + return 0; + res[1] = unicode_get_short_code(d[2]); + return 2; + case DECOMP_TYPE_PAT3: + res[0] = unicode_get16(d); + res[2] = unicode_get16(d + 2); + d += 4 + (c - code) * 2; + res[1] = unicode_get16(d); + return 3; + case DECOMP_TYPE_S2_UL: + case DECOMP_TYPE_LS2_UL: + c1 = c - code; + if (type == DECOMP_TYPE_S2_UL) { + d += c1 & ~1; + c = unicode_get_short_code(*d); + d++; + } else { + d += (c1 >> 1) * 3; + c = unicode_get16(d); + d += 2; + } + if (c1 & 1) + c = unicode_get_lower_simple(c); + res[0] = c; + res[1] = unicode_get_short_code(*d); + return 2; + } + } + return 0; +} + + +/* return the length of the decomposition (length <= + UNICODE_DECOMP_LEN_MAX) or 0 if no decomposition */ +static int unicode_decomp_char(uint32_t *res, uint32_t c, BOOL is_compat1) +{ + uint32_t v, type, is_compat, code, len; + int idx_min, idx_max, idx; + + idx_min = 0; + idx_max = countof(unicode_decomp_table1) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + v = unicode_decomp_table1[idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + // printf("idx=%d code=%05x len=%d\n", idx, code, len); + if (c < code) { + idx_max = idx - 1; + } else if (c >= code + len) { + idx_min = idx + 1; + } else { + is_compat = v & 1; + if (is_compat1 < is_compat) + break; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + return unicode_decomp_entry(res, c, idx, code, len, type); + } + } + return 0; +} + +/* return 0 if no pair found */ +static int unicode_compose_pair(uint32_t c0, uint32_t c1) +{ + uint32_t code, len, type, v, idx1, d_idx, d_offset, ch; + int idx_min, idx_max, idx, d; + uint32_t pair[2]; + + idx_min = 0; + idx_max = countof(unicode_comp_table) - 1; + while (idx_min <= idx_max) { + idx = (idx_max + idx_min) / 2; + idx1 = unicode_comp_table[idx]; + + /* idx1 represent an entry of the decomposition table */ + d_idx = idx1 >> 6; + d_offset = idx1 & 0x3f; + v = unicode_decomp_table1[d_idx]; + code = v >> (32 - 18); + len = (v >> (32 - 18 - 7)) & 0x7f; + type = (v >> (32 - 18 - 7 - 6)) & 0x3f; + ch = code + d_offset; + unicode_decomp_entry(pair, ch, d_idx, code, len, type); + d = c0 - pair[0]; + if (d == 0) + d = c1 - pair[1]; + if (d < 0) { + idx_max = idx - 1; + } else if (d > 0) { + idx_min = idx + 1; + } else { + return ch; + } + } + return 0; +} + +/* return the combining class of character c (between 0 and 255) */ +static int unicode_get_cc(uint32_t c) +{ + uint32_t code, n, type, cc, c1, b; + int pos; + const uint8_t *p; + + pos = get_index_pos(&code, c, + unicode_cc_index, sizeof(unicode_cc_index) / 3); + if (pos < 0) + return 0; + p = unicode_cc_table + pos; + for(;;) { + b = *p++; + type = b >> 6; + n = b & 0x3f; + if (n < 48) { + } else if (n < 56) { + n = (n - 48) << 8; + n |= *p++; + n += 48; + } else { + n = (n - 56) << 8; + n |= *p++ << 8; + n |= *p++; + n += 48 + (1 << 11); + } + if (type <= 1) + p++; + c1 = code + n + 1; + if (c < c1) { + switch(type) { + case 0: + cc = p[-1]; + break; + case 1: + cc = p[-1] + c - code; + break; + case 2: + cc = 0; + break; + default: + case 3: + cc = 230; + break; + } + return cc; + } + code = c1; + } +} + +static void sort_cc(int *buf, int len) +{ + int i, j, k, cc, cc1, start, ch1; + + for(i = 0; i < len; i++) { + cc = unicode_get_cc(buf[i]); + if (cc != 0) { + start = i; + j = i + 1; + while (j < len) { + ch1 = buf[j]; + cc1 = unicode_get_cc(ch1); + if (cc1 == 0) + break; + k = j - 1; + while (k >= start) { + if (unicode_get_cc(buf[k]) <= cc1) + break; + buf[k + 1] = buf[k]; + k--; + } + buf[k + 1] = ch1; + j++; + } + i = j; + } + } +} + +static void to_nfd_rec(DynBuf *dbuf, + const int *src, int src_len, int is_compat) +{ + uint32_t c, v; + int i, l; + uint32_t res[UNICODE_DECOMP_LEN_MAX]; + + for(i = 0; i < src_len; i++) { + c = src[i]; + if (c >= 0xac00 && c < 0xd7a4) { + /* Hangul decomposition */ + c -= 0xac00; + dbuf_put_u32(dbuf, 0x1100 + c / 588); + dbuf_put_u32(dbuf, 0x1161 + (c % 588) / 28); + v = c % 28; + if (v != 0) + dbuf_put_u32(dbuf, 0x11a7 + v); + } else { + l = unicode_decomp_char(res, c, is_compat); + if (l) { + to_nfd_rec(dbuf, (int *)res, l, is_compat); + } else { + dbuf_put_u32(dbuf, c); + } + } + } +} + +/* return 0 if not found */ +static int compose_pair(uint32_t c0, uint32_t c1) +{ + /* Hangul composition */ + if (c0 >= 0x1100 && c0 < 0x1100 + 19 && + c1 >= 0x1161 && c1 < 0x1161 + 21) { + return 0xac00 + (c0 - 0x1100) * 588 + (c1 - 0x1161) * 28; + } else if (c0 >= 0xac00 && c0 < 0xac00 + 11172 && + (c0 - 0xac00) % 28 == 0 && + c1 >= 0x11a7 && c1 < 0x11a7 + 28) { + return c0 + c1 - 0x11a7; + } else { + return unicode_compose_pair(c0, c1); + } +} + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, DynBufReallocFunc *realloc_func) +{ + int *buf, buf_len, i, p, starter_pos, cc, last_cc, out_len; + BOOL is_compat; + DynBuf dbuf_s, *dbuf = &dbuf_s; + + is_compat = n_type >> 1; + + dbuf_init2(dbuf, opaque, realloc_func); + if (dbuf_realloc(dbuf, sizeof(int) * src_len)) + goto fail; + + /* common case: latin1 is unaffected by NFC */ + if (n_type == UNICODE_NFC) { + for(i = 0; i < src_len; i++) { + if (src[i] >= 0x100) + goto not_latin1; + } + buf = (int *)dbuf->buf; + memcpy(buf, src, src_len * sizeof(int)); + *pdst = (uint32_t *)buf; + return src_len; + not_latin1: ; + } + + to_nfd_rec(dbuf, (const int *)src, src_len, is_compat); + if (dbuf_error(dbuf)) { + fail: + *pdst = NULL; + return -1; + } + buf = (int *)dbuf->buf; + buf_len = dbuf->size / sizeof(int); + + sort_cc(buf, buf_len); + + if (buf_len <= 1 || (n_type & 1) != 0) { + /* NFD / NFKD */ + *pdst = (uint32_t *)buf; + return buf_len; + } + + i = 1; + out_len = 1; + while (i < buf_len) { + /* find the starter character and test if it is blocked from + the character at 'i' */ + last_cc = unicode_get_cc(buf[i]); + starter_pos = out_len - 1; + while (starter_pos >= 0) { + cc = unicode_get_cc(buf[starter_pos]); + if (cc == 0) + break; + if (cc >= last_cc) + goto next; + last_cc = 256; + starter_pos--; + } + if (starter_pos >= 0 && + (p = compose_pair(buf[starter_pos], buf[i])) != 0) { + buf[starter_pos] = p; + i++; + } else { + next: + buf[out_len++] = buf[i++]; + } + } + *pdst = (uint32_t *)buf; + return out_len; +} + +/* char ranges for various unicode properties */ + +static int unicode_find_name(const char *name_table, const char *name) +{ + const char *p, *r; + int pos; + size_t name_len, len; + + p = name_table; + pos = 0; + name_len = strlen(name); + while (*p) { + for(;;) { + r = strchr(p, ','); + if (!r) + len = strlen(p); + else + len = r - p; + if (len == name_len && !memcmp(p, name, name_len)) + return pos; + p += len + 1; + if (!r) + break; + } + pos++; + } + return -1; +} + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_script(CharRange *cr, + const char *script_name, BOOL is_ext) +{ + int script_idx; + const uint8_t *p, *p_end; + uint32_t c, c1, b, n, v, v_len, i, type; + CharRange cr1_s = { 0 }, *cr1 = NULL; + CharRange cr2_s = { 0 }, *cr2 = &cr2_s; + BOOL is_common; + + script_idx = unicode_find_name(unicode_script_name_table, script_name); + if (script_idx < 0) + return -2; + /* Note: we remove the "Unknown" Script */ + script_idx += UNICODE_SCRIPT_Unknown + 1; + + is_common = (script_idx == UNICODE_SCRIPT_Common || + script_idx == UNICODE_SCRIPT_Inherited); + if (is_ext) { + cr1 = &cr1_s; + cr_init(cr1, cr->mem_opaque, cr->realloc_func); + cr_init(cr2, cr->mem_opaque, cr->realloc_func); + } else { + cr1 = cr; + } + + p = unicode_script_table; + p_end = unicode_script_table + countof(unicode_script_table); + c = 0; + while (p < p_end) { + b = *p++; + type = b >> 7; + n = b & 0x7f; + if (n < 96) { + } else if (n < 112) { + n = (n - 96) << 8; + n |= *p++; + n += 96; + } else { + n = (n - 112) << 16; + n |= *p++ << 8; + n |= *p++; + n += 96 + (1 << 12); + } + if (type == 0) + v = 0; + else + v = *p++; + c1 = c + n + 1; + if (v == script_idx) { + if (cr_add_interval(cr1, c, c1)) + goto fail; + } + c = c1; + } + + if (is_ext) { + /* add the script extensions */ + p = unicode_script_ext_table; + p_end = unicode_script_ext_table + countof(unicode_script_ext_table); + c = 0; + while (p < p_end) { + b = *p++; + if (b < 128) { + n = b; + } else if (b < 128 + 64) { + n = (b - 128) << 8; + n |= *p++; + n += 128; + } else { + n = (b - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 128 + (1 << 14); + } + c1 = c + n + 1; + v_len = *p++; + if (is_common) { + if (v_len != 0) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + } + } else { + for(i = 0; i < v_len; i++) { + if (p[i] == script_idx) { + if (cr_add_interval(cr2, c, c1)) + goto fail; + break; + } + } + } + p += v_len; + c = c1; + } + if (is_common) { + /* remove all the characters with script extensions */ + if (cr_invert(cr2)) + goto fail; + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_INTER)) + goto fail; + } else { + if (cr_op(cr, cr1->points, cr1->len, cr2->points, cr2->len, + CR_OP_UNION)) + goto fail; + } + cr_free(cr1); + cr_free(cr2); + } + return 0; + fail: + if (is_ext) { + cr_free(cr1); + cr_free(cr2); + } + goto fail; +} + +#define M(id) (1U << UNICODE_GC_ ## id) + +static int unicode_general_category1(CharRange *cr, uint32_t gc_mask) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, n, v; + + p = unicode_gc_table; + p_end = unicode_gc_table + countof(unicode_gc_table); + c = 0; + while (p < p_end) { + b = *p++; + n = b >> 5; + v = b & 0x1f; + if (n == 7) { + n = *p++; + if (n < 128) { + n += 7; + } else if (n < 128 + 64) { + n = (n - 128) << 8; + n |= *p++; + n += 7 + 128; + } else { + n = (n - 128 - 64) << 16; + n |= *p++ << 8; + n |= *p++; + n += 7 + 128 + (1 << 14); + } + } + c0 = c; + c += n + 1; + if (v == 31) { + /* run of Lu / Ll */ + b = gc_mask & (M(Lu) | M(Ll)); + if (b != 0) { + if (b == (M(Lu) | M(Ll))) { + goto add_range; + } else { + c0 += ((gc_mask & M(Ll)) != 0); + for(; c0 < c; c0 += 2) { + if (cr_add_interval(cr, c0, c0 + 1)) + return -1; + } + } + } + } else if ((gc_mask >> v) & 1) { + add_range: + if (cr_add_interval(cr, c0, c)) + return -1; + } + } + return 0; +} + +static int unicode_prop1(CharRange *cr, int prop_idx) +{ + const uint8_t *p, *p_end; + uint32_t c, c0, b, bit; + + p = unicode_prop_table[prop_idx]; + p_end = p + unicode_prop_len_table[prop_idx]; + c = 0; + bit = 0; + while (p < p_end) { + c0 = c; + b = *p++; + if (b < 64) { + c += (b >> 3) + 1; + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + c0 = c; + c += (b & 7) + 1; + } else if (b >= 0x80) { + c += b - 0x80 + 1; + } else if (b < 0x60) { + c += (((b - 0x40) << 8) | p[0]) + 1; + p++; + } else { + c += (((b - 0x60) << 16) | (p[0] << 8) | p[1]) + 1; + p += 2; + } + if (bit) { + if (cr_add_interval(cr, c0, c)) + return -1; + } + bit ^= 1; + } + return 0; +} + +#define CASE_U (1 << 0) +#define CASE_L (1 << 1) +#define CASE_F (1 << 2) + +/* use the case conversion table to generate range of characters. + CASE_U: set char if modified by uppercasing, + CASE_L: set char if modified by lowercasing, + CASE_F: set char if modified by case folding, + */ +static int unicode_case1(CharRange *cr, int case_mask) +{ +#define MR(x) (1 << RUN_TYPE_ ## x) + const uint32_t tab_run_mask[3] = { + MR(U) | MR(UF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(UF_D20) | + MR(UF_D1_EXT) | MR(U_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + + MR(L) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2), + + MR(UF) | MR(LF) | MR(UL) | MR(LSU) | MR(U2L_399_EXT2) | MR(LF_EXT) | MR(LF_EXT2) | MR(UF_D20) | MR(UF_D1_EXT) | MR(LF_EXT) | MR(UF_EXT2) | MR(UF_EXT3), + }; +#undef MR + uint32_t mask, v, code, type, len, i, idx; + + if (case_mask == 0) + return 0; + mask = 0; + for(i = 0; i < 3; i++) { + if ((case_mask >> i) & 1) + mask |= tab_run_mask[i]; + } + for(idx = 0; idx < countof(case_conv_table1); idx++) { + v = case_conv_table1[idx]; + type = (v >> (32 - 17 - 7 - 4)) & 0xf; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + if ((mask >> type) & 1) { + // printf("%d: type=%d %04x %04x\n", idx, type, code, code + len - 1); + switch(type) { + case RUN_TYPE_UL: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + code += ((case_mask & CASE_U) != 0); + for(i = 0; i < len; i += 2) { + if (cr_add_interval(cr, code + i, code + i + 1)) + return -1; + } + break; + case RUN_TYPE_LSU: + if ((case_mask & CASE_U) && (case_mask & (CASE_L | CASE_F))) + goto def_case; + if (!(case_mask & CASE_U)) { + if (cr_add_interval(cr, code, code + 1)) + return -1; + } + if (cr_add_interval(cr, code + 1, code + 2)) + return -1; + if (case_mask & CASE_U) { + if (cr_add_interval(cr, code + 2, code + 3)) + return -1; + } + break; + default: + def_case: + if (cr_add_interval(cr, code, code + len)) + return -1; + break; + } + } + } + return 0; +} + +static int point_cmp(const void *p1, const void *p2, void *arg) +{ + uint32_t v1 = *(uint32_t *)p1; + uint32_t v2 = *(uint32_t *)p2; + return (v1 > v2) - (v1 < v2); +} + +static void cr_sort_and_remove_overlap(CharRange *cr) +{ + uint32_t start, end, start1, end1, i, j; + + /* the resulting ranges are not necessarily sorted and may overlap */ + rqsort(cr->points, cr->len / 2, sizeof(cr->points[0]) * 2, point_cmp, NULL); + j = 0; + for(i = 0; i < cr->len; ) { + start = cr->points[i]; + end = cr->points[i + 1]; + i += 2; + while (i < cr->len) { + start1 = cr->points[i]; + end1 = cr->points[i + 1]; + if (start1 > end) { + /* |------| + * |-------| */ + break; + } else if (end1 <= end) { + /* |------| + * |--| */ + i += 2; + } else { + /* |------| + * |-------| */ + end = end1; + i += 2; + } + } + cr->points[j] = start; + cr->points[j + 1] = end; + j += 2; + } + cr->len = j; +} + +/* canonicalize a character set using the JS regex case folding rules + (see lre_canonicalize()) */ +int cr_regexp_canonicalize(CharRange *cr, BOOL is_unicode) +{ + CharRange cr_inter, cr_mask, cr_result, cr_sub; + uint32_t v, code, len, i, idx, start, end, c, d_start, d_end, d; + + cr_init(&cr_mask, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_inter, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_result, cr->mem_opaque, cr->realloc_func); + cr_init(&cr_sub, cr->mem_opaque, cr->realloc_func); + + if (unicode_case1(&cr_mask, is_unicode ? CASE_F : CASE_U)) + goto fail; + if (cr_op(&cr_inter, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + if (cr_invert(&cr_mask)) + goto fail; + if (cr_op(&cr_sub, cr_mask.points, cr_mask.len, cr->points, cr->len, CR_OP_INTER)) + goto fail; + + /* cr_inter = cr & cr_mask */ + /* cr_sub = cr & ~cr_mask */ + + /* use the case conversion table to compute the result */ + d_start = -1; + d_end = -1; + idx = 0; + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + for(i = 0; i < cr_inter.len; i += 2) { + start = cr_inter.points[i]; + end = cr_inter.points[i + 1]; + + for(c = start; c < end; c++) { + for(;;) { + if (c >= code && c < code + len) + break; + idx++; + assert(idx < countof(case_conv_table1)); + v = case_conv_table1[idx]; + code = v >> (32 - 17); + len = (v >> (32 - 17 - 7)) & 0x7f; + } + d = lre_case_folding_entry(c, idx, v, is_unicode); + /* try to merge with the current interval */ + if (d_start == -1) { + d_start = d; + d_end = d + 1; + } else if (d_end == d) { + d_end++; + } else { + cr_add_interval(&cr_result, d_start, d_end); + d_start = d; + d_end = d + 1; + } + } + } + if (d_start != -1) { + if (cr_add_interval(&cr_result, d_start, d_end)) + goto fail; + } + + /* the resulting ranges are not necessarily sorted and may overlap */ + cr_sort_and_remove_overlap(&cr_result); + + /* or with the character not affected by the case folding */ + cr->len = 0; + if (cr_op(cr, cr_result.points, cr_result.len, cr_sub.points, cr_sub.len, CR_OP_UNION)) + goto fail; + + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return 0; + fail: + cr_free(&cr_inter); + cr_free(&cr_mask); + cr_free(&cr_result); + cr_free(&cr_sub); + return -1; +} + +typedef enum { + POP_GC, + POP_PROP, + POP_CASE, + POP_UNION, + POP_INTER, + POP_XOR, + POP_INVERT, + POP_END, +} PropOPEnum; + +#define POP_STACK_LEN_MAX 4 + +static int unicode_prop_ops(CharRange *cr, ...) +{ + va_list ap; + CharRange stack[POP_STACK_LEN_MAX]; + int stack_len, op, ret, i; + uint32_t a; + + va_start(ap, cr); + stack_len = 0; + for(;;) { + op = va_arg(ap, int); + switch(op) { + case POP_GC: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_general_category1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_PROP: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_prop1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_CASE: + assert(stack_len < POP_STACK_LEN_MAX); + a = va_arg(ap, int); + cr_init(&stack[stack_len++], cr->mem_opaque, cr->realloc_func); + if (unicode_case1(&stack[stack_len - 1], a)) + goto fail; + break; + case POP_UNION: + case POP_INTER: + case POP_XOR: + { + CharRange *cr1, *cr2, *cr3; + assert(stack_len >= 2); + assert(stack_len < POP_STACK_LEN_MAX); + cr1 = &stack[stack_len - 2]; + cr2 = &stack[stack_len - 1]; + cr3 = &stack[stack_len++]; + cr_init(cr3, cr->mem_opaque, cr->realloc_func); + if (cr_op(cr3, cr1->points, cr1->len, + cr2->points, cr2->len, op - POP_UNION + CR_OP_UNION)) + goto fail; + cr_free(cr1); + cr_free(cr2); + *cr1 = *cr3; + stack_len -= 2; + } + break; + case POP_INVERT: + assert(stack_len >= 1); + if (cr_invert(&stack[stack_len - 1])) + goto fail; + break; + case POP_END: + goto done; + default: + abort(); + } + } + done: + assert(stack_len == 1); + ret = cr_copy(cr, &stack[0]); + cr_free(&stack[0]); + return ret; + fail: + for(i = 0; i < stack_len; i++) + cr_free(&stack[i]); + return -1; +} + +static const uint32_t unicode_gc_mask_table[] = { + M(Lu) | M(Ll) | M(Lt), /* LC */ + M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo), /* L */ + M(Mn) | M(Mc) | M(Me), /* M */ + M(Nd) | M(Nl) | M(No), /* N */ + M(Sm) | M(Sc) | M(Sk) | M(So), /* S */ + M(Pc) | M(Pd) | M(Ps) | M(Pe) | M(Pi) | M(Pf) | M(Po), /* P */ + M(Zs) | M(Zl) | M(Zp), /* Z */ + M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn), /* C */ +}; + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_general_category(CharRange *cr, const char *gc_name) +{ + int gc_idx; + uint32_t gc_mask; + + gc_idx = unicode_find_name(unicode_gc_name_table, gc_name); + if (gc_idx < 0) + return -2; + if (gc_idx <= UNICODE_GC_Co) { + gc_mask = (uint64_t)1 << gc_idx; + } else { + gc_mask = unicode_gc_mask_table[gc_idx - UNICODE_GC_LC]; + } + return unicode_general_category1(cr, gc_mask); +} + + +/* 'cr' must be initialized and empty. Return 0 if OK, -1 if error, -2 + if not found */ +int unicode_prop(CharRange *cr, const char *prop_name) +{ + int prop_idx, ret; + + prop_idx = unicode_find_name(unicode_prop_name_table, prop_name); + if (prop_idx < 0) + return -2; + prop_idx += UNICODE_PROP_ASCII_Hex_Digit; + + ret = 0; + switch(prop_idx) { + case UNICODE_PROP_ASCII: + if (cr_add_interval(cr, 0x00, 0x7f + 1)) + return -1; + break; + case UNICODE_PROP_Any: + if (cr_add_interval(cr, 0x00000, 0x10ffff + 1)) + return -1; + break; + case UNICODE_PROP_Assigned: + ret = unicode_prop_ops(cr, + POP_GC, M(Cn), + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Math: + ret = unicode_prop_ops(cr, + POP_GC, M(Sm), + POP_PROP, UNICODE_PROP_Other_Math, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Lowercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Ll), + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Uppercase: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Cased: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Alphabetic: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_Uppercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Lowercase, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_Alphabetic, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_Grapheme_Base: + ret = unicode_prop_ops(cr, + POP_GC, M(Cc) | M(Cf) | M(Cs) | M(Co) | M(Cn) | M(Zl) | M(Zp) | M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_INVERT, + POP_END); + break; + case UNICODE_PROP_Grapheme_Extend: + ret = unicode_prop_ops(cr, + POP_GC, M(Me) | M(Mn), + POP_PROP, UNICODE_PROP_Other_Grapheme_Extend, + POP_UNION, + POP_END); + break; + case UNICODE_PROP_XID_Start: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Start1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_XID_Continue: + ret = unicode_prop_ops(cr, + POP_GC, M(Lu) | M(Ll) | M(Lt) | M(Lm) | M(Lo) | M(Nl) | + M(Mn) | M(Mc) | M(Nd) | M(Pc), + POP_PROP, UNICODE_PROP_Other_ID_Start, + POP_UNION, + POP_PROP, UNICODE_PROP_Other_ID_Continue, + POP_UNION, + POP_PROP, UNICODE_PROP_Pattern_Syntax, + POP_PROP, UNICODE_PROP_Pattern_White_Space, + POP_UNION, + POP_PROP, UNICODE_PROP_XID_Continue1, + POP_UNION, + POP_INVERT, + POP_INTER, + POP_END); + break; + case UNICODE_PROP_Changes_When_Uppercased: + ret = unicode_case1(cr, CASE_U); + break; + case UNICODE_PROP_Changes_When_Lowercased: + ret = unicode_case1(cr, CASE_L); + break; + case UNICODE_PROP_Changes_When_Casemapped: + ret = unicode_case1(cr, CASE_U | CASE_L | CASE_F); + break; + case UNICODE_PROP_Changes_When_Titlecased: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_U, + POP_PROP, UNICODE_PROP_Changes_When_Titlecased1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_Casefolded1, + POP_XOR, + POP_END); + break; + case UNICODE_PROP_Changes_When_NFKC_Casefolded: + ret = unicode_prop_ops(cr, + POP_CASE, CASE_F, + POP_PROP, UNICODE_PROP_Changes_When_NFKC_Casefolded1, + POP_XOR, + POP_END); + break; + /* we use the existing tables */ + case UNICODE_PROP_ID_Continue: + ret = unicode_prop_ops(cr, + POP_PROP, UNICODE_PROP_ID_Start, + POP_PROP, UNICODE_PROP_ID_Continue1, + POP_XOR, + POP_END); + break; + default: + if (prop_idx >= countof(unicode_prop_table)) + return -2; + ret = unicode_prop1(cr, prop_idx); + break; + } + return ret; +} diff --git a/lib/monoucha0/monoucha/qjs/libunicode.h b/lib/monoucha0/monoucha/qjs/libunicode.h new file mode 100644 index 00000000..00400ffa --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/libunicode.h @@ -0,0 +1,129 @@ +/* + * Unicode utilities + * + * Copyright (c) 2017-2018 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 LIBUNICODE_H +#define LIBUNICODE_H + +#include <stddef.h> +#include <inttypes.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define LRE_BOOL int /* for documentation purposes */ + +#define LRE_CC_RES_LEN_MAX 3 + +typedef enum { + UNICODE_NFC, + UNICODE_NFD, + UNICODE_NFKC, + UNICODE_NFKD, +} UnicodeNormalizationEnum; + +int lre_case_conv(uint32_t *res, uint32_t c, int conv_type); +int lre_canonicalize(uint32_t c, LRE_BOOL is_unicode); +LRE_BOOL lre_is_cased(uint32_t c); +LRE_BOOL lre_is_case_ignorable(uint32_t c); + +/* char ranges */ + +typedef struct { + int len; /* in points, always even */ + int size; + uint32_t *points; /* points sorted by increasing value */ + void *mem_opaque; + void *(*realloc_func)(void *opaque, void *ptr, size_t size); +} CharRange; + +typedef enum { + CR_OP_UNION, + CR_OP_INTER, + CR_OP_XOR, +} CharRangeOpEnum; + +void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); +void cr_free(CharRange *cr); +int cr_realloc(CharRange *cr, int size); +int cr_copy(CharRange *cr, const CharRange *cr1); + +static inline int cr_add_point(CharRange *cr, uint32_t v) +{ + if (cr->len >= cr->size) { + if (cr_realloc(cr, cr->len + 1)) + return -1; + } + cr->points[cr->len++] = v; + return 0; +} + +static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + if ((cr->len + 2) > cr->size) { + if (cr_realloc(cr, cr->len + 2)) + return -1; + } + cr->points[cr->len++] = c1; + cr->points[cr->len++] = c2; + return 0; +} + +int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len); + +static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2) +{ + uint32_t b_pt[2]; + b_pt[0] = c1; + b_pt[1] = c2 + 1; + return cr_union1(cr, b_pt, 2); +} + +int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len, + const uint32_t *b_pt, int b_len, int op); + +int cr_invert(CharRange *cr); +int cr_regexp_canonicalize(CharRange *cr, LRE_BOOL is_unicode); + +LRE_BOOL lre_is_id_start(uint32_t c); +LRE_BOOL lre_is_id_continue(uint32_t c); +LRE_BOOL lre_is_white_space(uint32_t c); + +int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len, + UnicodeNormalizationEnum n_type, + void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size)); + +/* Unicode character range functions */ + +int unicode_script(CharRange *cr, + const char *script_name, LRE_BOOL is_ext); +int unicode_general_category(CharRange *cr, const char *gc_name); +int unicode_prop(CharRange *cr, const char *prop_name); + +#undef LRE_BOOL + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIBUNICODE_H */ diff --git a/lib/monoucha0/monoucha/qjs/list.h b/lib/monoucha0/monoucha/qjs/list.h new file mode 100644 index 00000000..b8dd7168 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/list.h @@ -0,0 +1,107 @@ +/* + * 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 <stddef.h> +#endif + +#ifdef __cplusplus +extern "C" { +#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) + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* LIST_H */ diff --git a/lib/monoucha0/monoucha/qjs/quickjs-atom.h b/lib/monoucha0/monoucha/qjs/quickjs-atom.h new file mode 100644 index 00000000..358fe230 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/quickjs-atom.h @@ -0,0 +1,262 @@ +/* + * QuickJS atom definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-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. + */ + +#ifdef DEF + +/* Note: first atoms are considered as keywords in the parser */ +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") +DEF(await, "await") + +/* empty string */ +DEF(empty_string, "") +/* identifiers */ +DEF(keys, "keys") +DEF(size, "size") +DEF(length, "length") +DEF(message, "message") +DEF(cause, "cause") +DEF(errors, "errors") +DEF(stack, "stack") +DEF(name, "name") +DEF(toString, "toString") +DEF(toLocaleString, "toLocaleString") +DEF(valueOf, "valueOf") +DEF(eval, "eval") +DEF(prototype, "prototype") +DEF(constructor, "constructor") +DEF(configurable, "configurable") +DEF(writable, "writable") +DEF(enumerable, "enumerable") +DEF(value, "value") +DEF(get, "get") +DEF(set, "set") +DEF(of, "of") +DEF(__proto__, "__proto__") +DEF(undefined, "undefined") +DEF(number, "number") +DEF(boolean, "boolean") +DEF(string, "string") +DEF(object, "object") +DEF(symbol, "symbol") +DEF(integer, "integer") +DEF(unknown, "unknown") +DEF(arguments, "arguments") +DEF(callee, "callee") +DEF(caller, "caller") +DEF(_eval_, "<eval>") +DEF(_ret_, "<ret>") +DEF(_var_, "<var>") +DEF(_arg_var_, "<arg_var>") +DEF(_with_, "<with>") +DEF(lastIndex, "lastIndex") +DEF(target, "target") +DEF(index, "index") +DEF(input, "input") +DEF(defineProperties, "defineProperties") +DEF(apply, "apply") +DEF(join, "join") +DEF(concat, "concat") +DEF(split, "split") +DEF(construct, "construct") +DEF(getPrototypeOf, "getPrototypeOf") +DEF(setPrototypeOf, "setPrototypeOf") +DEF(isExtensible, "isExtensible") +DEF(preventExtensions, "preventExtensions") +DEF(has, "has") +DEF(deleteProperty, "deleteProperty") +DEF(defineProperty, "defineProperty") +DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") +DEF(ownKeys, "ownKeys") +DEF(add, "add") +DEF(done, "done") +DEF(next, "next") +DEF(values, "values") +DEF(source, "source") +DEF(flags, "flags") +DEF(global, "global") +DEF(unicode, "unicode") +DEF(raw, "raw") +DEF(new_target, "new.target") +DEF(this_active_func, "this.active_func") +DEF(home_object, "<home_object>") +DEF(computed_field, "<computed_field>") +DEF(static_computed_field, "<static_computed_field>") /* must come after computed_fields */ +DEF(class_fields_init, "<class_fields_init>") +DEF(brand, "<brand>") +DEF(hash_constructor, "#constructor") +DEF(as, "as") +DEF(from, "from") +DEF(meta, "meta") +DEF(_default_, "*default*") +DEF(_star_, "*") +DEF(Module, "Module") +DEF(then, "then") +DEF(resolve, "resolve") +DEF(reject, "reject") +DEF(promise, "promise") +DEF(proxy, "proxy") +DEF(revoke, "revoke") +DEF(async, "async") +DEF(exec, "exec") +DEF(groups, "groups") +DEF(indices, "indices") +DEF(status, "status") +DEF(reason, "reason") +DEF(globalThis, "globalThis") +DEF(bigint, "bigint") +DEF(not_equal, "not-equal") +DEF(timed_out, "timed-out") +DEF(ok, "ok") +DEF(toJSON, "toJSON") +DEF(maxByteLength, "maxByteLength") +/* class names */ +DEF(Object, "Object") +DEF(Array, "Array") +DEF(Error, "Error") +DEF(Number, "Number") +DEF(String, "String") +DEF(Boolean, "Boolean") +DEF(Symbol, "Symbol") +DEF(Arguments, "Arguments") +DEF(Math, "Math") +DEF(JSON, "JSON") +DEF(Date, "Date") +DEF(Function, "Function") +DEF(GeneratorFunction, "GeneratorFunction") +DEF(ForInIterator, "ForInIterator") +DEF(RegExp, "RegExp") +DEF(ArrayBuffer, "ArrayBuffer") +DEF(SharedArrayBuffer, "SharedArrayBuffer") +/* must keep same order as class IDs for typed arrays */ +DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Int8Array, "Int8Array") +DEF(Uint8Array, "Uint8Array") +DEF(Int16Array, "Int16Array") +DEF(Uint16Array, "Uint16Array") +DEF(Int32Array, "Int32Array") +DEF(Uint32Array, "Uint32Array") +DEF(BigInt64Array, "BigInt64Array") +DEF(BigUint64Array, "BigUint64Array") +DEF(Float16Array, "Float16Array") +DEF(Float32Array, "Float32Array") +DEF(Float64Array, "Float64Array") +DEF(DataView, "DataView") +DEF(BigInt, "BigInt") +DEF(WeakRef, "WeakRef") +DEF(FinalizationRegistry, "FinalizationRegistry") +DEF(Map, "Map") +DEF(Set, "Set") /* Map + 1 */ +DEF(WeakMap, "WeakMap") /* Map + 2 */ +DEF(WeakSet, "WeakSet") /* Map + 3 */ +DEF(Iterator, "Iterator") +DEF(IteratorHelper, "Iterator Helper") +DEF(IteratorWrap, "Iterator Wrap") +DEF(Map_Iterator, "Map Iterator") +DEF(Set_Iterator, "Set Iterator") +DEF(Array_Iterator, "Array Iterator") +DEF(String_Iterator, "String Iterator") +DEF(RegExp_String_Iterator, "RegExp String Iterator") +DEF(Generator, "Generator") +DEF(Proxy, "Proxy") +DEF(Promise, "Promise") +DEF(PromiseResolveFunction, "PromiseResolveFunction") +DEF(PromiseRejectFunction, "PromiseRejectFunction") +DEF(AsyncFunction, "AsyncFunction") +DEF(AsyncFunctionResolve, "AsyncFunctionResolve") +DEF(AsyncFunctionReject, "AsyncFunctionReject") +DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") +DEF(AsyncGenerator, "AsyncGenerator") +DEF(EvalError, "EvalError") +DEF(RangeError, "RangeError") +DEF(ReferenceError, "ReferenceError") +DEF(SyntaxError, "SyntaxError") +DEF(TypeError, "TypeError") +DEF(URIError, "URIError") +DEF(InternalError, "InternalError") +DEF(CallSite, "CallSite") +/* private symbols */ +DEF(Private_brand, "<brand>") +/* symbols */ +DEF(Symbol_toPrimitive, "Symbol.toPrimitive") +DEF(Symbol_iterator, "Symbol.iterator") +DEF(Symbol_match, "Symbol.match") +DEF(Symbol_matchAll, "Symbol.matchAll") +DEF(Symbol_replace, "Symbol.replace") +DEF(Symbol_search, "Symbol.search") +DEF(Symbol_split, "Symbol.split") +DEF(Symbol_toStringTag, "Symbol.toStringTag") +DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") +DEF(Symbol_hasInstance, "Symbol.hasInstance") +DEF(Symbol_species, "Symbol.species") +DEF(Symbol_unscopables, "Symbol.unscopables") +DEF(Symbol_asyncIterator, "Symbol.asyncIterator") + +#endif /* DEF */ diff --git a/lib/monoucha0/monoucha/qjs/quickjs-c-atomics.h b/lib/monoucha0/monoucha/qjs/quickjs-c-atomics.h new file mode 100644 index 00000000..8fc6b720 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/quickjs-c-atomics.h @@ -0,0 +1,54 @@ +/* + * QuickJS C atomics definitions + * + * Copyright (c) 2023 Marcin Kolny + * + * 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 (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) + // Use GCC builtins for version < 4.9 +# if((__GNUC__ << 16) + __GNUC_MINOR__ < ((4) << 16) + 9) +# define GCC_BUILTIN_ATOMICS +# endif +#endif + +#ifdef GCC_BUILTIN_ATOMICS +#define atomic_fetch_add(obj, arg) \ + __atomic_fetch_add(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_compare_exchange_strong(obj, expected, desired) \ + __atomic_compare_exchange_n(obj, expected, desired, 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#define atomic_exchange(obj, desired) \ + __atomic_exchange_n (obj, desired, __ATOMIC_SEQ_CST) +#define atomic_load(obj) \ + __atomic_load_n(obj, __ATOMIC_SEQ_CST) +#define atomic_store(obj, desired) \ + __atomic_store_n(obj, desired, __ATOMIC_SEQ_CST) +#define atomic_fetch_or(obj, arg) \ + __atomic_fetch_or(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_xor(obj, arg) \ + __atomic_fetch_xor(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_and(obj, arg) \ + __atomic_fetch_and(obj, arg, __ATOMIC_SEQ_CST) +#define atomic_fetch_sub(obj, arg) \ + __atomic_fetch_sub(obj, arg, __ATOMIC_SEQ_CST) +#define _Atomic +#else +#include <stdatomic.h> +#endif diff --git a/lib/monoucha0/monoucha/qjs/quickjs-opcode.h b/lib/monoucha0/monoucha/qjs/quickjs-opcode.h new file mode 100644 index 00000000..09dacb3e --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/quickjs-opcode.h @@ -0,0 +1,374 @@ +/* + * QuickJS opcode definitions + * + * Copyright (c) 2017-2018 Fabrice Bellard + * Copyright (c) 2017-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. + */ + +#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(npop_u16) +FMT(loc) +FMT(arg) +FMT(var_ref) +FMT(u32) +FMT(u32x2) +FMT(i32) +FMT(const) +FMT(label) +FMT(atom) +FMT(atom_u8) +FMT(atom_u16) +FMT(atom_label_u8) +FMT(atom_label_u16) +FMT(label_u16) +#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_i32, 5, 0, 1, i32) +DEF( push_const, 5, 0, 1, const) +DEF( fclosure, 5, 0, 1, const) /* must follow push_const */ +DEF(push_atom_value, 5, 0, 1, atom) +DEF( private_symbol, 5, 0, 1, atom) +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, 1, 0, 1, none) +DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */ +DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */ + +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, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */ +DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */ +DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */ +DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */ +DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */ +DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */ +DEF( apply, 3, 3, 1, u16) +DEF( return, 1, 1, 0, none) +DEF( return_undef, 1, 0, 0, none) +DEF(check_ctor_return, 1, 1, 2, none) +DEF( check_ctor, 1, 0, 0, none) +DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */ +DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */ +DEF( return_async, 1, 1, 0, none) +DEF( throw, 1, 1, 0, none) +DEF( throw_error, 6, 0, 0, atom_u8) +DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */ +DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */ +DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a + bytecode string */ +DEF( get_super, 1, 1, 1, none) +DEF( import, 1, 1, 1, none) /* dynamic module import */ + +DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */ +DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */ +DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */ +DEF( put_var, 5, 1, 0, atom) /* must come after get_var */ +DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */ +DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */ + +DEF( get_ref_value, 1, 2, 3, none) +DEF( put_ref_value, 1, 3, 0, none) + +DEF( define_var, 6, 0, 0, atom_u8) +DEF(check_define_var, 6, 0, 0, atom_u8) +DEF( define_func, 6, 1, 0, atom_u8) + +// order matters, see IC counterparts +DEF( get_field, 5, 1, 1, atom) +DEF( get_field2, 5, 1, 2, atom) +DEF( put_field, 5, 2, 0, atom) + +DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */ +DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */ +DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */ +DEF( get_array_el, 1, 2, 1, none) +DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */ +DEF( put_array_el, 1, 3, 0, none) +DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */ +DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */ +DEF( define_field, 5, 2, 1, atom) +DEF( set_name, 5, 1, 1, atom) +DEF(set_name_computed, 1, 2, 2, none) +DEF( set_proto, 1, 2, 1, none) +DEF(set_home_object, 1, 2, 2, none) +DEF(define_array_el, 1, 3, 2, none) +DEF( append, 1, 3, 2, none) /* append enumerated object, update length */ +DEF(copy_data_properties, 2, 3, 3, u8) +DEF( define_method, 6, 2, 1, atom_u8) +DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */ +DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */ +DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */ + +DEF( get_loc, 3, 0, 1, loc) +DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */ +DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */ +DEF( get_arg, 3, 0, 1, arg) +DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */ +DEF( set_arg, 3, 1, 1, arg) /* must come after put_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( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */ +DEF(set_loc_uninitialized, 3, 0, 0, loc) +DEF( get_loc_check, 3, 0, 1, loc) +DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */ +DEF( put_loc_check_init, 3, 1, 0, loc) +DEF(get_var_ref_check, 3, 0, 1, var_ref) +DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */ +DEF(put_var_ref_check_init, 3, 1, 0, var_ref) +DEF( close_loc, 3, 0, 0, loc) +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( nip_catch, 1, 2, 1, none) /* catch ... a -> a */ + +DEF( to_object, 1, 1, 1, none) +//DEF( to_string, 1, 1, 1, none) +DEF( to_propkey, 1, 1, 1, none) +DEF( to_propkey2, 1, 2, 2, none) + +DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */ +DEF(with_get_ref_undef, 10, 1, 0, atom_label_u8) + +DEF( make_loc_ref, 7, 0, 2, atom_u16) +DEF( make_arg_ref, 7, 0, 2, atom_u16) +DEF(make_var_ref_ref, 7, 0, 2, atom_u16) +DEF( make_var_ref, 5, 0, 2, atom) + +DEF( for_in_start, 1, 1, 1, none) +DEF( for_of_start, 1, 1, 3, none) +DEF(for_await_of_start, 1, 1, 3, none) +DEF( for_in_next, 1, 1, 3, none) +DEF( for_of_next, 2, 3, 5, u8) +DEF(iterator_check_object, 1, 1, 1, none) +DEF(iterator_get_value_done, 1, 1, 2, none) +DEF( iterator_close, 1, 3, 0, none) +DEF( iterator_next, 1, 4, 4, none) +DEF( iterator_call, 2, 4, 5, u8) +DEF( initial_yield, 1, 0, 0, none) +DEF( yield, 1, 1, 2, none) +DEF( yield_star, 1, 1, 2, none) +DEF(async_yield_star, 1, 1, 2, none) +DEF( await, 1, 1, 1, none) + +/* 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( dec_loc, 2, 0, 0, loc8) +DEF( inc_loc, 2, 0, 0, loc8) +DEF( add_loc, 2, 1, 0, loc8) +DEF( not, 1, 1, 1, none) +DEF( lnot, 1, 1, 1, none) +DEF( typeof, 1, 1, 1, none) +DEF( delete, 1, 2, 1, none) +DEF( delete_var, 5, 0, 1, atom) + +/* warning: order matters (see js_parse_assign_expr) */ +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( shl, 1, 2, 1, none) +DEF( sar, 1, 2, 1, none) +DEF( shr, 1, 2, 1, none) +DEF( and, 1, 2, 1, none) +DEF( xor, 1, 2, 1, none) +DEF( or, 1, 2, 1, none) +DEF( pow, 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(is_undefined_or_null, 1, 1, 1, none) +DEF( private_in, 1, 2, 1, none) +/* must be the last non short and non temporary opcode */ +DEF( nop, 1, 0, 0, none) + +/* temporary opcodes: never emitted in the final bytecode */ + +def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ +def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ + +def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */ + +def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */ +def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */ +def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */ +def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */ +def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */ +def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */ +def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */ +def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */ + +def( source_loc, 9, 0, 0, u32x2) /* emitted in phase 1, removed in phase 3 */ + +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) +DEF( set_loc8, 2, 1, 1, loc8) + +DEF( get_loc0_loc1, 1, 0, 2, none_loc) +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) +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( set_loc0, 1, 1, 1, none_loc) +DEF( set_loc1, 1, 1, 1, none_loc) +DEF( set_loc2, 1, 1, 1, none_loc) +DEF( set_loc3, 1, 1, 1, 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) +DEF( put_arg1, 1, 1, 0, none_arg) +DEF( put_arg2, 1, 1, 0, none_arg) +DEF( put_arg3, 1, 1, 0, none_arg) +DEF( set_arg0, 1, 1, 1, none_arg) +DEF( set_arg1, 1, 1, 1, none_arg) +DEF( set_arg2, 1, 1, 1, none_arg) +DEF( set_arg3, 1, 1, 1, none_arg) +DEF( get_var_ref0, 1, 0, 1, none_var_ref) +DEF( get_var_ref1, 1, 0, 1, none_var_ref) +DEF( get_var_ref2, 1, 0, 1, none_var_ref) +DEF( get_var_ref3, 1, 0, 1, none_var_ref) +DEF( put_var_ref0, 1, 1, 0, none_var_ref) +DEF( put_var_ref1, 1, 1, 0, none_var_ref) +DEF( put_var_ref2, 1, 1, 0, none_var_ref) +DEF( put_var_ref3, 1, 1, 0, none_var_ref) +DEF( set_var_ref0, 1, 1, 1, none_var_ref) +DEF( set_var_ref1, 1, 1, 1, none_var_ref) +DEF( set_var_ref2, 1, 1, 1, none_var_ref) +DEF( set_var_ref3, 1, 1, 1, none_var_ref) + +DEF( get_length, 1, 1, 1, none) + +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) + +DEF( is_undefined, 1, 1, 1, none) +DEF( is_null, 1, 1, 1, none) +DEF(typeof_is_undefined, 1, 1, 1, none) +DEF( typeof_is_function, 1, 1, 1, none) + +// order matters, see non-IC counterparts +DEF( get_field_ic, 5, 1, 1, none) +DEF( get_field2_ic, 5, 1, 2, none) +DEF( put_field_ic, 5, 2, 0, none) + +#undef DEF +#undef def +#endif /* DEF */ diff --git a/lib/monoucha0/monoucha/qjs/quickjs.c b/lib/monoucha0/monoucha/qjs/quickjs.c new file mode 100644 index 00000000..12dc51e1 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/quickjs.c @@ -0,0 +1,56258 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * Copyright (c) 2023 Ben Noordhuis + * Copyright (c) 2023 Saúl Ibarra Corretgé + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <inttypes.h> +#include <string.h> +#include <assert.h> +#if !defined(_MSC_VER) +#include <sys/time.h> +#if defined(_WIN32) +#include <timezoneapi.h> +#endif +#endif +#include <time.h> +#include <fenv.h> +#include <math.h> + +#include "cutils.h" +#include "list.h" +#include "quickjs.h" +#include "libregexp.h" +#include "libbf.h" + +#if defined(EMSCRIPTEN) || defined(_MSC_VER) +#define DIRECT_DISPATCH 0 +#else +#define DIRECT_DISPATCH 1 +#endif + +#if defined(__APPLE__) +#define MALLOC_OVERHEAD 0 +#else +#define MALLOC_OVERHEAD 8 +#endif + +#if defined(__NEWLIB__) +#define NO_TM_GMTOFF +#endif + +// atomic_store etc. are completely busted in recent versions of tcc; +// somehow the compiler forgets to load |ptr| into %rdi when calling +// the __atomic_*() helpers in its lib/stdatomic.c and lib/atomic.S +#if !defined(__TINYC__) && !defined(EMSCRIPTEN) && !defined(__wasi__) && !__STDC_NO_ATOMICS__ && !defined(MNC_NO_THREADS) +#include "quickjs-c-atomics.h" +#define CONFIG_ATOMICS +#endif + +#ifndef __GNUC__ +#define __extension__ +#endif + +// Debug trace system: the debug output will be produced to the dump stream (currently +// stdout) if qjs is invoked with -D<bitmask> with the corresponding bit set. + +#ifndef NDEBUG +#define DUMP_BYTECODE_FINAL 0x01 /* dump pass 3 final byte code */ +#define DUMP_BYTECODE_PASS2 0x02 /* dump pass 2 code */ +#define DUMP_BYTECODE_PASS1 0x04 /* dump pass 1 code */ +#define DUMP_BYTECODE_HEX 0x10 /* dump bytecode in hex */ +#define DUMP_BYTECODE_PC2LINE 0x20 /* dump line number table */ +#define DUMP_BYTECODE_STACK 0x40 /* dump compute_stack_size */ +#define DUMP_BYTECODE_STEP 0x80 /* dump executed bytecode */ +#define DUMP_READ_OBJECT 0x100 /* dump the marshalled objects at load time */ +#define DUMP_FREE 0x200 /* dump every object free */ +#define DUMP_GC 0x400 /* dump the occurrence of the automatic GC */ +#define DUMP_GC_FREE 0x800 /* dump objects freed by the GC */ +#define DUMP_MODULE_RESOLVE 0x1000 /* dump module resolution steps */ +#define DUMP_PROMISE 0x2000 /* dump promise steps */ +#define DUMP_LEAKS 0x4000 /* dump leaked objects and strings in JS_FreeRuntime */ +#define DUMP_ATOM_LEAKS 0x8000 /* dump leaked atoms in JS_FreeRuntime */ +#define DUMP_MEM 0x10000 /* dump memory usage in JS_FreeRuntime */ +#define DUMP_OBJECTS 0x20000 /* dump objects in JS_FreeRuntime */ +#define DUMP_ATOMS 0x40000 /* dump atoms in JS_FreeRuntime */ +#define DUMP_SHAPES 0x80000 /* dump shapes in JS_FreeRuntime */ +#endif + +//#define FORCE_GC_AT_MALLOC /* test the GC by forcing it before each object allocation */ + +#define check_dump_flag(rt, flag) ((rt->dump_flags & (flag +0)) == (flag +0)) + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define QJS_VERSION_STRING \ + STRINGIFY(QJS_VERSION_MAJOR) "." STRINGIFY(QJS_VERSION_MINOR) "." STRINGIFY(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX + +const char* JS_GetVersion(void) { + return QJS_VERSION_STRING; +} + +#undef STRINFIGY_ +#undef STRINGIFY + +enum { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ + JS_CLASS_BIG_INT, /* u.object_data */ + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_ITERATOR, + JS_CLASS_ITERATOR_HELPER, /* u.iterator_helper_data */ + JS_CLASS_ITERATOR_WRAP, /* u.iterator_wrap_data */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + JS_CLASS_WEAK_REF, + JS_CLASS_FINALIZATION_REGISTRY, + JS_CLASS_CALL_SITE, + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +/* number of typed array types */ +#define JS_TYPED_ARRAY_COUNT (JS_CLASS_FLOAT64_ARRAY - JS_CLASS_UINT8C_ARRAY + 1) +static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT]; +#define typed_array_size_log2(classid) (typed_array_size_log2[(classid)- JS_CLASS_UINT8C_ARRAY]) + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ + JS_PLAIN_ERROR = JS_NATIVE_ERROR_COUNT +} JSErrorEnum; + +#define JS_MAX_LOCAL_VARS 65535 +#define JS_STACK_SIZE_MAX 65534 +#define JS_STRING_LEN_MAX ((1 << 30) - 1) + +#define __exception __attribute__((warn_unused_result)) + +typedef struct JSShape JSShape; +typedef struct JSString JSString; +typedef struct JSString JSAtomStruct; + +#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR(v)) + +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + +typedef struct JSMallocState { + size_t malloc_count; + size_t malloc_size; + size_t malloc_limit; + void *opaque; /* user opaque */ +} JSMallocState; + +typedef struct JSRuntimeFinalizerState { + struct JSRuntimeFinalizerState *next; + JSRuntimeFinalizer *finalizer; + void *arg; +} JSRuntimeFinalizerState; + +struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; + const char *rt_info; + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t *atom_hash; + JSAtomStruct **atom_array; + int atom_free_index; /* 0 = none */ + + JSClassID js_class_id_alloc; /* counter for user defined classes */ + int class_count; /* size of class_array */ + JSClass *class_array; + + struct list_head context_list; /* list of JSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + struct list_head gc_obj_list; + /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ + struct list_head gc_zero_ref_count_list; + struct list_head tmp_obj_list; /* used during GC */ + /* used during GC (for keeping track of objects with a can_destroy hook) */ + struct list_head tmp_hook_obj_list; + JSGCPhaseEnum gc_phase : 8; + size_t malloc_gc_threshold; +#ifdef DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + /* stack limitation */ + uintptr_t stack_size; /* in bytes, 0 if no limit */ + uintptr_t stack_top; + uintptr_t stack_limit; /* lower stack limit */ + + JSValue current_exception; + /* true if inside an out of memory error, to avoid recursing */ + BOOL in_out_of_memory : 8; + /* and likewise if inside Error.prepareStackTrace() */ + BOOL in_prepare_stack_trace : 8; + /* true if inside JS_FreeRuntime */ + BOOL in_free : 8; + + struct JSStackFrame *current_stack_frame; + + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + JSHostPromiseRejectionTracker *host_promise_rejection_tracker; + void *host_promise_rejection_tracker_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + + JSModuleNormalizeFunc *module_normalize_func; + JSModuleLoaderFunc *module_loader_func; + void *module_loader_opaque; + /* timestamp for internal use in module evaluation */ + int64_t module_async_evaluation_next_timestamp; + + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + + BOOL can_block : 8; /* TRUE if Atomics.wait can block */ + uint32_t dump_flags : 24; + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape **shape_hash; + bf_context_t bf_ctx; + void *user_opaque; + void *libc_opaque; + JSRuntimeFinalizerState *finalizers; + JSRuntimeCleanUpFunc *user_cleanup; +}; + +struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + JSClassCall *call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods *exotic; + /* called before object would be destroyed */ + JSClassCanDestroy *can_destroy; +}; + +typedef struct JSStackFrame { + struct JSStackFrame *prev_frame; /* NULL if first stack frame */ + JSValue cur_func; /* current function, JS_UNDEFINED if the frame is detached */ + JSValue *arg_buf; /* arguments */ + JSValue *var_buf; /* variables */ + struct list_head var_ref_list; /* list of JSVarRef.link */ + uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + uint32_t arg_count : 31; + uint32_t is_strict_mode : 1; + /* only used in generators. Current stack pointer value. NULL if + the function is running. */ + JSValue *cur_sp; +} JSStackFrame; + +typedef enum { + JS_GC_OBJ_TYPE_JS_OBJECT, + JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, + JS_GC_OBJ_TYPE_SHAPE, + JS_GC_OBJ_TYPE_VAR_REF, + JS_GC_OBJ_TYPE_ASYNC_FUNCTION, + JS_GC_OBJ_TYPE_JS_CONTEXT, +} JSGCObjectTypeEnum; + +/* header for GC objects. GC objects are C data structures with a + reference count that can reference other GC objects. JS Objects are + a particular type of GC object. */ +struct JSGCObjectHeader { + int ref_count; /* must come first, 32-bit */ + JSGCObjectTypeEnum gc_obj_type : 4; + uint8_t mark : 4; /* used by the GC */ + uint8_t dummy1; /* not used by the GC */ + uint16_t dummy2; /* not used by the GC */ + struct list_head link; +}; + +typedef struct JSVarRef { + union { + JSGCObjectHeader header; /* must come first */ + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + + /* 0 : the JSVarRef is on the stack. header.link is an element + of JSStackFrame.var_ref_list. + 1 : the JSVarRef is detached. header.link has the normal meanning + */ + uint8_t is_detached : 1; + uint8_t is_arg : 1; + uint16_t var_idx; /* index of the corresponding function variable on + the stack */ + }; + }; + JSValue *pvalue; /* pointer to the value, either on the stack or + to 'value' */ + JSValue value; /* used when the variable is no longer on the stack */ +} JSVarRef; + +typedef struct JSRefCountHeader { + int ref_count; +} JSRefCountHeader; + +/* the same structure is used for big integers. + Big integers are never infinite or NaNs */ +typedef struct JSBigInt { + JSRefCountHeader header; /* must come first, 32-bit */ + bf_t num; +} JSBigInt; + +typedef enum { + JS_AUTOINIT_ID_PROTOTYPE, + JS_AUTOINIT_ID_MODULE_NS, + JS_AUTOINIT_ID_PROP, +} JSAutoInitIDEnum; + +/* 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 + +struct JSContext { + JSGCObjectHeader header; /* must come first */ + JSRuntime *rt; + struct list_head link; + + uint16_t binary_object_count; + int binary_object_size; + + JSShape *array_shape; /* initial shape for Array objects */ + + JSValue *class_proto; + JSValue function_proto; + JSValue function_ctor; + JSValue array_ctor; + JSValue regexp_ctor; + JSValue promise_ctor; + JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + JSValue error_ctor; + JSValue error_prepare_stack; + int error_stack_trace_limit; + JSValue iterator_ctor; + JSValue iterator_proto; + JSValue async_iterator_proto; + JSValue array_proto_values; + JSValue throw_type_error; + JSValue eval_obj; + + JSValue global_obj; /* global object */ + JSValue global_var_obj; /* contains the global let/const definitions */ + + double time_origin; + + uint64_t random_state; + bf_context_t *bf_ctx; /* points to rt->bf_ctx, shared by all contexts */ + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + + struct list_head loaded_modules; /* list of JSModuleDef.link */ + + /* if NULL, RegExp compilation is not supported */ + JSValue (*compile_regexp)(JSContext *ctx, JSValue pattern, + JSValue flags); + /* if NULL, eval is not supported */ + JSValue (*eval_internal)(JSContext *ctx, JSValue this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx); + void *user_opaque; +}; + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +typedef enum { + JS_WEAK_REF_KIND_MAP, + JS_WEAK_REF_KIND_WEAK_REF, + JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY, +} JSWeakRefKindEnum; + +typedef struct JSWeakRefRecord { + JSWeakRefKindEnum kind; + struct JSWeakRefRecord *next_weak_ref; + union { + struct JSMapRecord *map_record; + struct JSWeakRefData *weak_ref_data; + struct JSFinRecEntry *fin_rec_entry; + } u; +} JSWeakRefRecord; + +enum { + JS_ATOM_TYPE_STRING = 1, + JS_ATOM_TYPE_GLOBAL_SYMBOL, + JS_ATOM_TYPE_SYMBOL, + JS_ATOM_TYPE_PRIVATE, +}; + +enum { + JS_ATOM_HASH_SYMBOL, + JS_ATOM_HASH_PRIVATE, +}; + +typedef enum { + JS_ATOM_KIND_STRING, + JS_ATOM_KIND_SYMBOL, + JS_ATOM_KIND_PRIVATE, +} JSAtomKindEnum; + +#define JS_ATOM_HASH_MASK ((1 << 30) - 1) + +struct JSString { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len : 31; + uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 30; + uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ + JSWeakRefRecord *first_weak_ref; +#ifdef DUMP_LEAKS + struct list_head link; /* string list */ +#endif + union { + __extension__ uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ + __extension__ uint16_t str16[0]; + } u; +}; + +typedef struct JSClosureVar { + uint8_t is_local : 1; + uint8_t is_arg : 1; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* 8 bits available */ + uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSAtom var_name; +} JSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ +} JSVarScope; + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator + function declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, /* function expression name */ + JS_VAR_PRIVATE_FIELD, + JS_VAR_PRIVATE_METHOD, + JS_VAR_PRIVATE_GETTER, + JS_VAR_PRIVATE_SETTER, /* must come after JS_VAR_PRIVATE_GETTER */ + JS_VAR_PRIVATE_GETTER_SETTER, /* must come after JS_VAR_PRIVATE_SETTER */ +} JSVarKindEnum; + +/* XXX: could use a different structure in bytecode functions to save + memory */ +typedef struct JSVarDef { + JSAtom var_name; + /* index into fd->scopes of this variable lexical scope */ + int scope_level; + /* during compilation: + - if scope_level = 0: scope in which the variable is defined + - if scope_level != 0: index into fd->vars of the next + variable in the same or enclosing lexical scope + in a bytecode function: + index into fd->vars of the next + variable in the same or enclosing lexical scope + */ + int scope_next; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; + uint8_t is_static_private : 1; /* only used during private class field parsing */ + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* only used during compilation: function pool index for lexical + variables with var_kind = + JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of + the definition of the 'var' variables (they have scope_level = + 0) */ + int func_pool_idx : 24; /* only used during compilation : index in + the constant pool for hoisted function + definition */ +} JSVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +typedef enum JSFunctionKindEnum { + JS_FUNC_NORMAL = 0, + JS_FUNC_GENERATOR = (1 << 0), + JS_FUNC_ASYNC = (1 << 1), + JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), +} JSFunctionKindEnum; + +#define IC_CACHE_ITEM_CAPACITY 4 + +typedef struct JSInlineCacheRingSlot { + /* SoA for space optimization: 56 bytes */ + JSShape* shape[IC_CACHE_ITEM_CAPACITY]; + uint32_t prop_offset[IC_CACHE_ITEM_CAPACITY]; + JSAtom atom; + uint8_t index; +} JSInlineCacheRingSlot; + +typedef struct JSInlineCacheHashSlot { + JSAtom atom; + uint32_t index; + struct JSInlineCacheHashSlot *next; +} JSInlineCacheHashSlot; + +typedef struct JSInlineCache { + uint32_t count; + uint32_t capacity; + uint32_t hash_bits; + JSInlineCacheHashSlot **hash; + JSInlineCacheRingSlot *cache; +} JSInlineCache; + +#define INLINE_CACHE_MISS ((uint32_t)-1) // sentinel + +// This is a struct so we don't tie up two argument registers in calls to +// JS_GetPropertyInternal2 and JS_SetPropertyInternal2 in the common case +// where there is no IC and therefore no offset to update. +typedef struct JSInlineCacheUpdate { + JSInlineCache *ic; + uint32_t offset; +} JSInlineCacheUpdate; + +static JSInlineCache *init_ic(JSContext *ctx); +static int rebuild_ic(JSContext *ctx, JSInlineCache *ic); +static int resize_ic_hash(JSContext *ctx, JSInlineCache *ic); +static int free_ic(JSRuntime *rt, JSInlineCache *ic); +static void add_ic_slot(JSContext *ctx, JSInlineCacheUpdate *icu, + JSAtom atom, JSObject *object, uint32_t prop_offset); + +static uint32_t get_ic_prop_offset(const JSInlineCacheUpdate *icu, + JSShape *shape) +{ + uint32_t i, cache_offset = icu->offset; + JSInlineCache *ic = icu->ic; + JSInlineCacheRingSlot *cr; + JSShape *shape_slot; + assert(cache_offset < ic->capacity); + cr = ic->cache + cache_offset; + i = cr->index; + for (;;) { + shape_slot = *(cr->shape + i); + if (likely(shape_slot == shape)) { + cr->index = i; + return cr->prop_offset[i]; + } + + i = (i + 1) % countof(cr->shape); + if (unlikely(i == cr->index)) { + break; + } + } + + return INLINE_CACHE_MISS; +} + +static force_inline JSAtom get_ic_atom(JSInlineCache *ic, uint32_t cache_offset) +{ + assert(cache_offset < ic->capacity); + return ic->cache[cache_offset].atom; +} + +typedef struct JSFunctionBytecode { + JSGCObjectHeader header; /* must come first */ + uint8_t is_strict_mode : 1; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t is_derived_class_constructor : 1; + /* true if home_object needs to be initialized */ + uint8_t need_home_object : 1; + uint8_t func_kind : 2; + uint8_t new_target_allowed : 1; + uint8_t super_call_allowed : 1; + uint8_t super_allowed : 1; + uint8_t arguments_allowed : 1; + uint8_t backtrace_barrier : 1; /* stop backtrace on this function */ + /* XXX: 5 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSAtom func_name; + JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) (self pointer) */ + JSClosureVar *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + JSContext *realm; /* function realm */ + JSValue *cpool; /* constant pool (self pointer) */ + int cpool_count; + int closure_var_count; + JSInlineCache *ic; + JSAtom filename; + int line_num; + int col_num; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; +} JSFunctionBytecode; + +typedef struct JSBoundFunction { + JSValue func_obj; + JSValue this_val; + int argc; + JSValue argv[]; +} JSBoundFunction; + +typedef enum JSIteratorKindEnum { + JS_ITERATOR_KIND_KEY, + JS_ITERATOR_KIND_VALUE, + JS_ITERATOR_KIND_KEY_AND_VALUE, +} JSIteratorKindEnum; + +typedef enum JSIteratorHelperKindEnum { + JS_ITERATOR_HELPER_KIND_DROP, + JS_ITERATOR_HELPER_KIND_EVERY, + JS_ITERATOR_HELPER_KIND_FILTER, + JS_ITERATOR_HELPER_KIND_FIND, + JS_ITERATOR_HELPER_KIND_FLAT_MAP, + JS_ITERATOR_HELPER_KIND_FOR_EACH, + JS_ITERATOR_HELPER_KIND_MAP, + JS_ITERATOR_HELPER_KIND_SOME, + JS_ITERATOR_HELPER_KIND_TAKE, +} JSIteratorHelperKindEnum; + +typedef struct JSForInIterator { + JSValue obj; + BOOL is_array; + uint32_t array_length; + uint32_t idx; +} JSForInIterator; + +typedef struct JSRegExp { + JSString *pattern; + JSString *bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSProxyData { + JSValue target; + JSValue handler; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef struct JSArrayBuffer { + int byte_length; /* 0 if detached */ + int max_byte_length; /* -1 if not resizable; >= byte_length otherwise */ + uint8_t detached; + uint8_t shared; /* if shared, the array buffer cannot be detached */ + uint8_t *data; /* NULL if detached */ + struct list_head array_list; + void *opaque; + JSFreeArrayBufferDataFunc *free_func; +} JSArrayBuffer; + +typedef struct JSTypedArray { + struct list_head link; /* link to arraybuffer */ + JSObject *obj; /* back pointer to the TypedArray/DataView object */ + JSObject *buffer; /* based array buffer */ + uint32_t offset; /* byte offset in the array buffer */ + uint32_t length; /* byte length in the array buffer */ + BOOL track_rab; /* auto-track length of backing array buffer */ +} JSTypedArray; + +typedef struct JSAsyncFunctionState { + JSValue this_val; /* 'this' generator argument */ + int argc; /* number of function arguments */ + BOOL throw_flag; /* used to throw an exception in JS_CallInternal() */ + JSStackFrame frame; +} JSAsyncFunctionState; + +/* XXX: could use an object instead to avoid the + JS_TAG_ASYNC_FUNCTION tag for the GC */ +typedef struct JSAsyncFunctionData { + JSGCObjectHeader header; /* must come first */ + JSValue resolving_funcs[2]; + BOOL is_active; /* true if the async function state is valid */ + JSAsyncFunctionState func_state; +} JSAsyncFunctionData; + +typedef struct JSReqModuleEntry { + JSAtom module_name; + JSModuleDef *module; /* used using resolution */ +} JSReqModuleEntry; + +typedef enum JSExportTypeEnum { + JS_EXPORT_TYPE_LOCAL, + JS_EXPORT_TYPE_INDIRECT, +} JSExportTypeEnum; + +typedef struct JSExportEntry { + union { + struct { + int var_idx; /* closure variable index */ + JSVarRef *var_ref; /* if != NULL, reference to the variable */ + } local; /* for local export */ + int req_module_idx; /* module for indirect export */ + } u; + JSExportTypeEnum export_type; + JSAtom local_name; /* '*' if export ns from. not used for local + export after compilation */ + JSAtom export_name; /* exported variable name */ +} JSExportEntry; + +typedef struct JSStarExportEntry { + int req_module_idx; /* in req_module_entries */ +} JSStarExportEntry; + +typedef struct JSImportEntry { + int var_idx; /* closure variable index */ + JSAtom import_name; + int req_module_idx; /* in req_module_entries */ +} JSImportEntry; + +typedef enum { + JS_MODULE_STATUS_UNLINKED, + JS_MODULE_STATUS_LINKING, + JS_MODULE_STATUS_LINKED, + JS_MODULE_STATUS_EVALUATING, + JS_MODULE_STATUS_EVALUATING_ASYNC, + JS_MODULE_STATUS_EVALUATED, +} JSModuleStatus; + +struct JSModuleDef { + JSRefCountHeader header; /* must come first, 32-bit */ + JSAtom module_name; + struct list_head link; + + JSReqModuleEntry *req_module_entries; + int req_module_entries_count; + int req_module_entries_size; + + JSExportEntry *export_entries; + int export_entries_count; + int export_entries_size; + + JSStarExportEntry *star_export_entries; + int star_export_entries_count; + int star_export_entries_size; + + JSImportEntry *import_entries; + int import_entries_count; + int import_entries_size; + + JSValue module_ns; + JSValue func_obj; /* only used for JS modules */ + JSModuleInitFunc *init_func; /* only used for C modules */ + BOOL has_tla : 8; /* true if func_obj contains await */ + BOOL resolved : 8; + BOOL func_created : 8; + JSModuleStatus status : 8; + /* temp use during js_module_link() & js_module_evaluate() */ + int dfs_index, dfs_ancestor_index; + JSModuleDef *stack_prev; + /* temp use during js_module_evaluate() */ + JSModuleDef **async_parent_modules; + int async_parent_modules_count; + int async_parent_modules_size; + int pending_async_dependencies; + BOOL async_evaluation; + int64_t async_evaluation_timestamp; + JSModuleDef *cycle_root; + JSValue promise; /* corresponds to spec field: capability */ + JSValue resolving_funcs[2]; /* corresponds to spec field: capability */ + /* true if evaluation yielded an exception. It is saved in + eval_exception */ + BOOL eval_has_exception : 8; + JSValue eval_exception; + JSValue meta_obj; /* for import.meta */ +}; + +typedef struct JSJobEntry { + struct list_head link; + JSContext *ctx; + JSJobFunc *job_func; + int argc; + JSValue argv[]; +} JSJobEntry; + +typedef struct JSProperty { + union { + JSValue value; /* JS_PROP_NORMAL */ + struct { /* JS_PROP_GETSET */ + JSObject *getter; /* NULL if undefined */ + JSObject *setter; /* NULL if undefined */ + } getset; + JSVarRef *var_ref; /* JS_PROP_VARREF */ + struct { /* JS_PROP_AUTOINIT */ + /* in order to use only 2 pointers, we compress the realm + and the init function pointer */ + uintptr_t realm_and_id; /* realm and init_id (JS_AUTOINIT_ID_x) + in the 2 low bits */ + void *opaque; + } init; + } u; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ +#define JS_ARRAY_INITIAL_SIZE 2 + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ + JSGCObjectHeader header; + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + /* If true, the shape may have small array index properties 'n' with 0 + <= n <= 2^31-1. If false, the shape is guaranteed not to have + small array index properties */ + uint8_t has_small_array_index; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; /* include deleted properties */ + int deleted_prop_count; + JSShape *shape_hash_next; /* in JSRuntime.shape_hash[h] list */ + JSObject *proto; + JSShapeProperty prop[]; /* prop_size elements */ +}; + +struct JSObject { + union { + JSGCObjectHeader header; + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ + + uint8_t extensible : 1; + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ + uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ + uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ + uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ + }; + }; + /* byte offsets: 16/24 */ + JSShape *shape; /* prototype and property names + flag */ + JSProperty *prop; /* array of properties */ + /* byte offsets: 24/40 */ + JSWeakRefRecord *first_weak_ref; + /* byte offsets: 28/48 */ + union { + void *opaque; + struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ + struct JSMapState *map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData *map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData *array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData *regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData *generator_data; /* JS_CLASS_GENERATOR */ + struct JSIteratorHelperData *iterator_helper_data; /* JS_CLASS_ITERATOR_HELPER */ + struct JSIteratorWrapData *iterator_wrap_data; /* JS_CLASS_ITERATOR_WRAP */ + struct JSProxyData *proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData *promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData *promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionData *async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSAsyncFromSyncIteratorData *async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ + struct JSAsyncGeneratorData *async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ + struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct JSFunctionBytecode *function_bytecode; + JSVarRef **var_refs; + JSObject *home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + JSContext *realm; + JSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + } u1; + union { + JSValue *values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + void *ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t *int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t *uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t *int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t *uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t *int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t *uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t *int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t *uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + uint16_t *fp16_ptr; /* JS_CLASS_FLOAT16_ARRAY */ + float *float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double *double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + } u; + /* byte sizes: 40/48/72 */ +}; + +typedef struct JSCallSiteData { + JSValue filename; + JSValue func; + JSValue func_name; + BOOL native; + int line_num; + int col_num; +} JSCallSiteData; + +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#undef DEF +; + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_ ## f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs-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 "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_ ## id, +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, +} OPCodeEnum; + +static int JS_InitAtoms(JSRuntime *rt); +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type); +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p); +static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b); +static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, int flags); +static JSValue js_call_bound_function(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, int flags); +static JSValue JS_CallInternal(JSContext *ctx, JSValue func_obj, + JSValue this_obj, JSValue new_target, + int argc, JSValue *argv, int flags); +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValue func_obj, + JSValue new_target, + int argc, JSValue *argv, int flags); +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValue this_obj, + int argc, JSValue *argv); +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValue *argv); +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, BOOL is_array_ctor); +static JSValue JS_EvalObject(JSContext *ctx, JSValue this_obj, + JSValue val, int flags, int scope_idx); +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); + +static __maybe_unused void JS_DumpString(JSRuntime *rt, const JSString *p); +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt); +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p); +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p); +static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val); +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt); +static __maybe_unused void JS_DumpShapes(JSRuntime *rt); + +static JSValue js_function_apply(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic); +static void js_array_finalizer(JSRuntime *rt, JSValue val); +static void js_array_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_object_data_finalizer(JSRuntime *rt, JSValue val); +static void js_object_data_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_c_function_finalizer(JSRuntime *rt, JSValue val); +static void js_c_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val); +static void js_bytecode_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_bound_function_finalizer(JSRuntime *rt, JSValue val); +static void js_bound_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_for_in_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_regexp_finalizer(JSRuntime *rt, JSValue val); +static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val); +static void js_typed_array_finalizer(JSRuntime *rt, JSValue val); +static void js_typed_array_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_proxy_finalizer(JSRuntime *rt, JSValue val); +static void js_proxy_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_map_finalizer(JSRuntime *rt, JSValue val); +static void js_map_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_map_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_array_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_helper_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValue val); +static void js_iterator_wrap_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val); +static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_generator_finalizer(JSRuntime *rt, JSValue obj); +static void js_generator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_promise_finalizer(JSRuntime *rt, JSValue val); +static void js_promise_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val); +static void js_promise_resolve_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); + +#define HINT_STRING 0 +#define HINT_NUMBER 1 +#define HINT_NONE 2 +#define HINT_FORCE_ORDINARY (1 << 4) // don't try Symbol.toPrimitive +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint); +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val); +static int JS_ToBoolFree(JSContext *ctx, JSValue val); +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val); +static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val); +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val); +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len); +static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, + JSValue flags); +static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValue ctor, + JSValue pattern, JSValue bc); +static void gc_decref(JSRuntime *rt); +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name); +static JSValue js_array_push(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int unshift); + +typedef enum JSStrictEqModeEnum { + JS_EQ_STRICT, + JS_EQ_SAME_VALUE, + JS_EQ_SAME_VALUE_ZERO, +} JSStrictEqModeEnum; + +static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode); +static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2); +static BOOL js_same_value(JSContext *ctx, JSValue op1, JSValue op2); +static BOOL js_same_value_zero(JSContext *ctx, JSValue op1, JSValue op2); +static JSValue JS_ToObject(JSContext *ctx, JSValue val); +static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val); +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags); +static JSValue JS_NewBigInt(JSContext *ctx); +static inline bf_t *JS_GetBigInt(JSValue val) +{ + JSBigInt *p = JS_VALUE_GET_PTR(val); + return &p->num; +} +static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val); +static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val); +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val); +static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValue val); +static bf_t *JS_ToBigInt1(JSContext *ctx, bf_t *buf, JSValue val); +static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf); +JSValue JS_ThrowOutOfMemory(JSContext *ctx); +static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx); +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValue obj); +static int js_proxy_setPrototypeOf(JSContext *ctx, JSValue obj, + JSValue proto_val, BOOL throw_flag); +static int js_proxy_isExtensible(JSContext *ctx, JSValue obj); +static int js_proxy_preventExtensions(JSContext *ctx, JSValue obj); +static int js_proxy_isArray(JSContext *ctx, JSValue obj); +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, + int flags); +static int js_string_memcmp(const JSString *p1, const JSString *p2, int len); +static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref); +static BOOL is_valid_weakref_target(JSValue val); +static void insert_weakref_record(JSValue target, struct JSWeakRefRecord *wr); +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValue new_target, + uint64_t len, uint64_t *max_len, + JSClassID class_id, + uint8_t *buf, + JSFreeArrayBufferDataFunc *free_func, + void *opaque, BOOL alloc_flag); +static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr); +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValue obj); +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf); +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, + int classid); +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValue new_target, + JSValue src_obj, + int classid, uint32_t len); +static BOOL typed_array_is_oob(JSObject *p); +static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p); +static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx); +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx); +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, int var_idx, + BOOL is_arg); +static JSValue js_generator_function_call(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, + int flags); +static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val); +static void js_async_function_resolve_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static JSValue JS_EvalInternal(JSContext *ctx, JSValue this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx); +static void js_free_module_def(JSContext *ctx, JSModuleDef *m); +static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, + JS_MarkFunc *mark_func); +static JSValue js_import_meta(JSContext *ctx); +static JSValue js_dynamic_import(JSContext *ctx, JSValue specifier); +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref); +static JSValue js_new_promise_capability(JSContext *ctx, + JSValue *resolving_funcs, + JSValue ctor); +static __exception int perform_promise_then(JSContext *ctx, + JSValue promise, + JSValue *resolve_reject, + JSValue *cap_resolving_funcs); +static JSValue js_promise_resolve(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic); +static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static int js_string_compare(JSContext *ctx, + const JSString *p1, const JSString *p2); +static JSValue JS_ToNumber(JSContext *ctx, JSValue val); +static int JS_SetPropertyValue(JSContext *ctx, JSValue this_obj, + JSValue prop, JSValue val, int flags); +static int JS_NumberIsInteger(JSContext *ctx, JSValue val); +static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValue val); +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val); +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop); +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc); +static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, + JS_MarkFunc *mark_func); +static void JS_AddIntrinsicBasicObjects(JSContext *ctx); +static void js_free_shape(JSRuntime *rt, JSShape *sh); +static void js_free_shape_null(JSRuntime *rt, JSShape *sh); +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs); +static int init_shape_hash(JSRuntime *rt); +static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, + JSValue obj); +static __exception int js_get_length64(JSContext *ctx, int64_t *pres, + JSValue obj); +static __exception int js_set_length64(JSContext *ctx, JSValue obj, + int64_t len); +static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len); +static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, + JSValue array_arg); +static BOOL js_get_fast_array(JSContext *ctx, JSValue obj, + JSValue **arrpp, uint32_t *countp); +static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, + JSValue sync_iter); +static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val); +static void js_c_function_data_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +static JSValue js_c_function_data_call(JSContext *ctx, JSValue func_obj, + JSValue this_val, + int argc, JSValue *argv, int flags); +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val); +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type); +static void remove_gc_object(JSGCObjectHeader *h); +static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s); +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); +static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque); +static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, + JSAtom atom, void *opaque); +void JS_SetUncatchableError(JSContext *ctx, JSValue val, BOOL flag); + +static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd); +static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf); +static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num); +static void _JS_AddIntrinsicCallSite(JSContext *ctx); + +static void JS_SetOpaqueInternal(JSValue obj, void *opaque); + +static const JSClassExoticMethods js_arguments_exotic_methods; +static const JSClassExoticMethods js_string_exotic_methods; +static const JSClassExoticMethods js_proxy_exotic_methods; +static const JSClassExoticMethods js_module_ns_exotic_methods; + +static inline BOOL double_is_int32(double d) +{ + uint64_t u, e; + JSFloat64Union t; + + t.d = d; + u = t.u64; + + e = ((u >> 52) & 0x7FF) - 1023; + if (e > 30) { + // accept 0, INT32_MIN, reject too large, too small, nan, inf, -0 + return !u || (u == 0xc1e0000000000000); + } else { + // shift out sign, exponent and whole part bits + // value is fractional if remaining low bits are non-zero + return !(u << 12 << e); + } +} + +static JSValue js_float64(double d) +{ + return __JS_NewFloat64(d); +} + +static int compare_u32(uint32_t a, uint32_t b) +{ + return -(a < b) + (b < a); // -1, 0 or 1 +} + +static JSValue js_int32(int32_t v) +{ + return JS_MKVAL(JS_TAG_INT, v); +} + +static JSValue js_uint32(uint32_t v) +{ + if (v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +static JSValue js_int64(int64_t v) +{ + if (v >= INT32_MIN && v <= INT32_MAX) + return js_int32(v); + else + return js_float64(v); +} + +#define JS_NewInt64(ctx, val) js_int64(val) + +static JSValue js_number(double d) +{ + if (double_is_int32(d)) + return js_int32((int32_t)d); + else + return js_float64(d); +} + +JSValue JS_NewNumber(JSContext *ctx, double d) +{ + return js_number(d); +} + +static JSValue js_bool(JS_BOOL v) +{ + return JS_MKVAL(JS_TAG_BOOL, (v != 0)); +} + +static JSValue js_dup(JSValue v) +{ + if (JS_VALUE_HAS_REF_COUNT(v)) { + JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + p->ref_count++; + } + return v; +} + +JSValue JS_DupValue(JSContext *ctx, JSValue v) +{ + return js_dup(v); +} + +JSValue JS_DupValueRT(JSRuntime *rt, JSValue v) +{ + return js_dup(v); +} + +static void js_trigger_gc(JSRuntime *rt, size_t size) +{ + BOOL force_gc; +#ifdef FORCE_GC_AT_MALLOC + force_gc = TRUE; +#else + force_gc = ((rt->malloc_state.malloc_size + size) > + rt->malloc_gc_threshold); +#endif + if (force_gc) { +#ifdef DUMP_GC + if (check_dump_flag(rt, DUMP_GC)) { + printf("GC: size=%" PRIu64 "\n", + (uint64_t)rt->malloc_state.malloc_size); + } +#endif + JS_RunGC(rt); + rt->malloc_gc_threshold = rt->malloc_state.malloc_size + + (rt->malloc_state.malloc_size >> 1); + } +} + +static size_t js_malloc_usable_size_unknown(const void *ptr) +{ + return 0; +} + +void *js_calloc_rt(JSRuntime *rt, size_t count, size_t size) +{ + void *ptr; + JSMallocState *s; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(count != 0 && size != 0); + + if (size > 0) + if (unlikely(count != (count * size) / size)) + return NULL; + + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (unlikely(s->malloc_size + (count * size) > s->malloc_limit - 1)) + return NULL; + + ptr = rt->mf.js_calloc(s->opaque, count, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +void *js_malloc_rt(JSRuntime *rt, size_t size) +{ + void *ptr; + JSMallocState *s; + + /* Do not allocate zero bytes: behavior is platform dependent */ + assert(size != 0); + + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (unlikely(s->malloc_size + size > s->malloc_limit - 1)) + return NULL; + + ptr = rt->mf.js_malloc(s->opaque, size); + if (!ptr) + return NULL; + + s->malloc_count++; + s->malloc_size += rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + return ptr; +} + +void js_free_rt(JSRuntime *rt, void *ptr) +{ + JSMallocState *s; + + if (!ptr) + return; + + s = &rt->malloc_state; + s->malloc_count--; + s->malloc_size -= rt->mf.js_malloc_usable_size(ptr) + MALLOC_OVERHEAD; + rt->mf.js_free(s->opaque, ptr); +} + +void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size) +{ + size_t old_size; + JSMallocState *s; + + if (!ptr) { + if (size == 0) + return NULL; + return js_malloc_rt(rt, size); + } + if (unlikely(size == 0)) { + js_free_rt(rt, ptr); + return NULL; + } + old_size = rt->mf.js_malloc_usable_size(ptr); + s = &rt->malloc_state; + /* When malloc_limit is 0 (unlimited), malloc_limit - 1 will be SIZE_MAX. */ + if (s->malloc_size + size - old_size > s->malloc_limit - 1) + return NULL; + + ptr = rt->mf.js_realloc(s->opaque, ptr, size); + if (!ptr) + return NULL; + + s->malloc_size += rt->mf.js_malloc_usable_size(ptr) - old_size; + return ptr; +} + +static void *js_dbuf_realloc(void *opaque, void *ptr, size_t size) +{ + JSRuntime *rt = opaque; + return js_realloc_rt(rt, ptr, size); +} + +size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr) +{ + return rt->mf.js_malloc_usable_size(ptr); +} + +/** + * This used to be implemented as malloc + memset, but using calloc + * yields better performance in initial, bursty allocations, something useful + * for QuickJS. + * + * More information: https://github.com/quickjs-ng/quickjs/pull/519 + */ +void *js_mallocz_rt(JSRuntime *rt, size_t size) +{ + return js_calloc_rt(rt, 1, size); +} + +/* called by libbf */ +static void *js_bf_realloc(void *opaque, void *ptr, size_t size) +{ + JSRuntime *rt = opaque; + return js_realloc_rt(rt, ptr, size); +} + +/* Throw out of memory in case of error */ +void *js_calloc(JSContext *ctx, size_t count, size_t size) +{ + void *ptr; + ptr = js_calloc_rt(ctx->rt, count, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_malloc(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_malloc_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_mallocz(JSContext *ctx, size_t size) +{ + void *ptr; + ptr = js_mallocz_rt(ctx->rt, size); + if (unlikely(!ptr)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ptr; +} + +void js_free(JSContext *ctx, void *ptr) +{ + js_free_rt(ctx->rt, ptr); +} + +/* Throw out of memory in case of error */ +void *js_realloc(JSContext *ctx, void *ptr, size_t size) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return ret; +} + +/* store extra allocated size in *pslack if successful */ +void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack) +{ + void *ret; + ret = js_realloc_rt(ctx->rt, ptr, size); + if (unlikely(!ret && size != 0)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + if (pslack) { + size_t new_size = js_malloc_usable_size_rt(ctx->rt, ret); + *pslack = (new_size > size) ? new_size - size : 0; + } + return ret; +} + +size_t js_malloc_usable_size(JSContext *ctx, const void *ptr) +{ + return js_malloc_usable_size_rt(ctx->rt, ptr); +} + +/* Throw out of memory exception in case of error */ +char *js_strndup(JSContext *ctx, const char *s, size_t n) +{ + char *ptr; + ptr = js_malloc(ctx, n + 1); + if (ptr) { + memcpy(ptr, s, n); + ptr[n] = '\0'; + } + return ptr; +} + +char *js_strdup(JSContext *ctx, const char *str) +{ + return js_strndup(ctx, str, strlen(str)); +} + +static no_inline int js_realloc_array(JSContext *ctx, void **parray, + int elem_size, int *psize, int req_size) +{ + int new_size; + size_t slack; + void *new_array; + /* XXX: potential arithmetic overflow */ + new_size = max_int(req_size, *psize * 3 / 2); + new_array = js_realloc2(ctx, *parray, new_size * elem_size, &slack); + if (!new_array) + return -1; + new_size += slack / elem_size; + *psize = new_size; + *parray = new_array; + return 0; +} + +/* resize the array and update its size if req_size > *psize */ +static inline int js_resize_array(JSContext *ctx, void **parray, int elem_size, + int *psize, int req_size) +{ + if (unlikely(req_size > *psize)) + return js_realloc_array(ctx, parray, elem_size, psize, req_size); + else + return 0; +} + +static inline void js_dbuf_init(JSContext *ctx, DynBuf *s) +{ + dbuf_init2(s, ctx->rt, js_dbuf_realloc); +} + +static inline int is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static inline int string_get(const JSString *p, int idx) { + return p->is_wide_char ? p->u.str16[idx] : p->u.str8[idx]; +} + +typedef struct JSClassShortDef { + JSAtom class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; +} JSClassShortDef; + +static JSClassShortDef const js_std_class_def[] = { + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_OBJECT */ + { JS_ATOM_Array, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARRAY */ + { JS_ATOM_Error, NULL, NULL }, /* JS_CLASS_ERROR */ + { JS_ATOM_Number, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_NUMBER */ + { JS_ATOM_String, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_STRING */ + { JS_ATOM_Boolean, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BOOLEAN */ + { JS_ATOM_Symbol, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_SYMBOL */ + { JS_ATOM_Arguments, js_array_finalizer, js_array_mark }, /* JS_CLASS_ARGUMENTS */ + { JS_ATOM_Arguments, NULL, NULL }, /* JS_CLASS_MAPPED_ARGUMENTS */ + { JS_ATOM_Date, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_DATE */ + { JS_ATOM_Object, NULL, NULL }, /* JS_CLASS_MODULE_NS */ + { JS_ATOM_Function, js_c_function_finalizer, js_c_function_mark }, /* JS_CLASS_C_FUNCTION */ + { JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */ + { JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */ + { JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */ + { JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */ + { JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */ + { JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ + { JS_ATOM_ArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_ARRAY_BUFFER */ + { JS_ATOM_SharedArrayBuffer, js_array_buffer_finalizer, NULL }, /* JS_CLASS_SHARED_ARRAY_BUFFER */ + { JS_ATOM_Uint8ClampedArray, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8C_ARRAY */ + { JS_ATOM_Int8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT8_ARRAY */ + { JS_ATOM_Uint8Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT8_ARRAY */ + { JS_ATOM_Int16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT16_ARRAY */ + { JS_ATOM_Uint16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT16_ARRAY */ + { JS_ATOM_Int32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_INT32_ARRAY */ + { JS_ATOM_Uint32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_UINT32_ARRAY */ + { JS_ATOM_BigInt64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_INT64_ARRAY */ + { JS_ATOM_BigUint64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_BIG_UINT64_ARRAY */ + { JS_ATOM_Float16Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT16_ARRAY */ + { JS_ATOM_Float32Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT32_ARRAY */ + { JS_ATOM_Float64Array, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_FLOAT64_ARRAY */ + { JS_ATOM_DataView, js_typed_array_finalizer, js_typed_array_mark }, /* JS_CLASS_DATAVIEW */ + { JS_ATOM_BigInt, js_object_data_finalizer, js_object_data_mark }, /* JS_CLASS_BIG_INT */ + { JS_ATOM_Map, js_map_finalizer, js_map_mark }, /* JS_CLASS_MAP */ + { JS_ATOM_Set, js_map_finalizer, js_map_mark }, /* JS_CLASS_SET */ + { JS_ATOM_WeakMap, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKMAP */ + { JS_ATOM_WeakSet, js_map_finalizer, js_map_mark }, /* JS_CLASS_WEAKSET */ + { JS_ATOM_Iterator, NULL, NULL }, /* JS_CLASS_ITERATOR */ + { JS_ATOM_IteratorHelper, js_iterator_helper_finalizer, js_iterator_helper_mark }, /* JS_CLASS_ITERATOR_HELPER */ + { JS_ATOM_IteratorWrap, js_iterator_wrap_finalizer, js_iterator_wrap_mark }, /* JS_CLASS_ITERATOR_WRAP */ + { JS_ATOM_Map_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_MAP_ITERATOR */ + { JS_ATOM_Set_Iterator, js_map_iterator_finalizer, js_map_iterator_mark }, /* JS_CLASS_SET_ITERATOR */ + { JS_ATOM_Array_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_ARRAY_ITERATOR */ + { JS_ATOM_String_Iterator, js_array_iterator_finalizer, js_array_iterator_mark }, /* JS_CLASS_STRING_ITERATOR */ + { JS_ATOM_RegExp_String_Iterator, js_regexp_string_iterator_finalizer, js_regexp_string_iterator_mark }, /* JS_CLASS_REGEXP_STRING_ITERATOR */ + { JS_ATOM_Generator, js_generator_finalizer, js_generator_mark }, /* JS_CLASS_GENERATOR */ +}; + +static int init_class_range(JSRuntime *rt, JSClassShortDef const *tab, + int start, int count) +{ + JSClassDef cm_s, *cm = &cm_s; + int i, class_id; + + for(i = 0; i < count; i++) { + class_id = i + start; + memset(cm, 0, sizeof(*cm)); + cm->finalizer = tab[i].finalizer; + cm->gc_mark = tab[i].gc_mark; + if (JS_NewClass1(rt, class_id, cm, tab[i].class_name) < 0) + return -1; + } + return 0; +} + +/* Note: OS and CPU dependent */ +static inline uintptr_t js_get_stack_pointer(void) +{ + return (uintptr_t)__builtin_frame_address(0); +} + +static inline BOOL js_check_stack_overflow(JSRuntime *rt, size_t alloca_size) +{ + uintptr_t sp; + sp = js_get_stack_pointer() - alloca_size; + return unlikely(sp < rt->stack_limit); +} + +JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) +{ + JSRuntime *rt; + JSMallocState ms; + + memset(&ms, 0, sizeof(ms)); + ms.opaque = opaque; + ms.malloc_limit = 0; + + rt = mf->js_calloc(opaque, 1, sizeof(JSRuntime)); + if (!rt) + return NULL; + rt->mf = *mf; + if (!rt->mf.js_malloc_usable_size) { + /* use dummy function if none provided */ + rt->mf.js_malloc_usable_size = js_malloc_usable_size_unknown; + } + /* Inline what js_malloc_rt does since we cannot use it here. */ + ms.malloc_count++; + ms.malloc_size += rt->mf.js_malloc_usable_size(rt) + MALLOC_OVERHEAD; + rt->malloc_state = ms; + rt->malloc_gc_threshold = 256 * 1024; + + bf_context_init(&rt->bf_ctx, js_bf_realloc, rt); + + init_list_head(&rt->context_list); + init_list_head(&rt->gc_obj_list); + init_list_head(&rt->gc_zero_ref_count_list); + rt->gc_phase = JS_GC_PHASE_NONE; + +#ifdef DUMP_LEAKS + init_list_head(&rt->string_list); +#endif + init_list_head(&rt->job_list); + + if (JS_InitAtoms(rt)) + goto fail; + + /* create the object, array and function classes */ + if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT, + countof(js_std_class_def)) < 0) + goto fail; + rt->class_array[JS_CLASS_ARGUMENTS].exotic = &js_arguments_exotic_methods; + rt->class_array[JS_CLASS_STRING].exotic = &js_string_exotic_methods; + rt->class_array[JS_CLASS_MODULE_NS].exotic = &js_module_ns_exotic_methods; + + rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function; + rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_c_function_data_call; + rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function; + rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_generator_function_call; + if (init_shape_hash(rt)) + goto fail; + + rt->js_class_id_alloc = JS_CLASS_INIT_COUNT; + + rt->stack_size = JS_DEFAULT_STACK_SIZE; +#ifdef __wasi__ + rt->stack_size = 0; +#endif + + JS_UpdateStackTop(rt); + + rt->current_exception = JS_UNINITIALIZED; + + return rt; + fail: + JS_FreeRuntime(rt); + return NULL; +} + +void *JS_GetRuntimeOpaque(JSRuntime *rt) +{ + return rt->user_opaque; +} + +void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque) +{ + rt->user_opaque = opaque; +} + +int JS_AddRuntimeFinalizer(JSRuntime *rt, JSRuntimeFinalizer *finalizer, + void *arg) +{ + JSRuntimeFinalizerState *fs = js_malloc_rt(rt, sizeof(*fs)); + if (!fs) + return -1; + fs->next = rt->finalizers; + fs->finalizer = finalizer; + fs->arg = arg; + rt->finalizers = fs; + return 0; +} + +void JS_SetRuntimeCleanUpFunc(JSRuntime *rt, JSRuntimeCleanUpFunc cleanup_func) +{ + rt->user_cleanup = cleanup_func; +} + +static void *js_def_calloc(void *opaque, size_t count, size_t size) +{ + return calloc(count, size); +} + +static void *js_def_malloc(void *opaque, size_t size) +{ + return malloc(size); +} + +static void js_def_free(void *opaque, void *ptr) +{ + free(ptr); +} + +static void *js_def_realloc(void *opaque, void *ptr, size_t size) +{ + return realloc(ptr, size); +} + +static const JSMallocFunctions def_malloc_funcs = { + js_def_calloc, + js_def_malloc, + js_def_free, + js_def_realloc, + js__malloc_usable_size +}; + +JSRuntime *JS_NewRuntime(void) +{ + return JS_NewRuntime2(&def_malloc_funcs, NULL); +} + +void JS_SetMemoryLimit(JSRuntime *rt, size_t limit) +{ + rt->malloc_state.malloc_limit = limit; +} + +void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags) +{ + rt->dump_flags = flags; +} + +size_t JS_GetGCThreshold(JSRuntime *rt) { + return rt->malloc_gc_threshold; +} + +/* use -1 to disable automatic GC */ +void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold) +{ + rt->malloc_gc_threshold = gc_threshold; +} + +#define malloc(s) malloc_is_forbidden(s) +#define free(p) free_is_forbidden(p) +#define realloc(p,s) realloc_is_forbidden(p,s) + +void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque) +{ + rt->interrupt_handler = cb; + rt->interrupt_opaque = opaque; +} + +void JS_SetCanBlock(JSRuntime *rt, BOOL can_block) +{ + rt->can_block = can_block; +} + +void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, + const JSSharedArrayBufferFunctions *sf) +{ + rt->sab_funcs = *sf; +} + +/* return 0 if OK, < 0 if exception */ +int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, + int argc, JSValue *argv) +{ + JSRuntime *rt = ctx->rt; + JSJobEntry *e; + int i; + + assert(!rt->in_free); + + e = js_malloc(ctx, sizeof(*e) + argc * sizeof(JSValue)); + if (!e) + return -1; + e->ctx = ctx; + e->job_func = job_func; + e->argc = argc; + for(i = 0; i < argc; i++) { + e->argv[i] = js_dup(argv[i]); + } + list_add_tail(&e->link, &rt->job_list); + return 0; +} + +BOOL JS_IsJobPending(JSRuntime *rt) +{ + return !list_empty(&rt->job_list); +} + +/* return < 0 if exception, 0 if no job pending, 1 if a job was + executed successfully. the context of the job is stored in '*pctx' */ +int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx) +{ + JSContext *ctx; + JSJobEntry *e; + JSValue res; + int i, ret; + + if (list_empty(&rt->job_list)) { + *pctx = NULL; + return 0; + } + + /* get the first pending job and execute it */ + e = list_entry(rt->job_list.next, JSJobEntry, link); + list_del(&e->link); + ctx = e->ctx; + res = e->job_func(e->ctx, e->argc, e->argv); + for(i = 0; i < e->argc; i++) + JS_FreeValue(ctx, e->argv[i]); + if (JS_IsException(res)) + ret = -1; + else + ret = 1; + JS_FreeValue(ctx, res); + js_free(ctx, e); + *pctx = ctx; + return ret; +} + +static inline uint32_t atom_get_free(const JSAtomStruct *p) +{ + return (uintptr_t)p >> 1; +} + +static inline BOOL atom_is_free(const JSAtomStruct *p) +{ + return (uintptr_t)p & 1; +} + +static inline JSAtomStruct *atom_set_free(uint32_t v) +{ + return (JSAtomStruct *)(((uintptr_t)v << 1) | 1); +} + +/* Note: the string contents are uninitialized */ +static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char) +{ + JSString *str; + str = js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char); + if (unlikely(!str)) + return NULL; + str->header.ref_count = 1; + str->is_wide_char = is_wide_char; + str->len = max_len; + str->atom_type = 0; + str->hash = 0; /* optional but costless */ + str->hash_next = 0; /* optional */ +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &rt->string_list); +#endif + return str; +} + +static JSString *js_alloc_string(JSContext *ctx, int max_len, int is_wide_char) +{ + JSString *p; + p = js_alloc_string_rt(ctx->rt, max_len, is_wide_char); + if (unlikely(!p)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return p; +} + +/* same as JS_FreeValueRT() but faster */ +static inline void js_free_string(JSRuntime *rt, JSString *str) +{ + if (--str->header.ref_count <= 0) { + if (str->atom_type) { + JS_FreeAtomStruct(rt, str); + } else { +#ifdef DUMP_LEAKS + list_del(&str->link); +#endif + js_free_rt(rt, str); + } + } +} + +void JS_SetRuntimeInfo(JSRuntime *rt, const char *s) +{ + if (rt) + rt->rt_info = s; +} + +void JS_FreeRuntime(JSRuntime *rt) +{ + struct list_head *el, *el1; + int i; + + rt->in_free = TRUE; + JS_FreeValueRT(rt, rt->current_exception); + + list_for_each_safe(el, el1, &rt->job_list) { + JSJobEntry *e = list_entry(el, JSJobEntry, link); + for(i = 0; i < e->argc; i++) + JS_FreeValueRT(rt, e->argv[i]); + js_free_rt(rt, e); + } + init_list_head(&rt->job_list); + + if (rt->user_cleanup) + rt->user_cleanup(rt); + + JS_RunGC(rt); + +#ifdef DUMP_LEAKS + /* leaking objects */ + if (check_dump_flag(rt, DUMP_LEAKS)) { + BOOL header_done; + JSGCObjectHeader *p; + int count; + + /* remove the internal refcounts to display only the object + referenced externally */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + p->mark = 0; + } + gc_decref(rt); + + header_done = FALSE; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count != 0) { + if (!header_done) { + printf("Object leaks:\n"); + JS_DumpObjectHeader(rt); + header_done = TRUE; + } + JS_DumpGCObject(rt, p); + } + } + + count = 0; + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + if (p->ref_count == 0) { + count++; + } + } + if (count != 0) + printf("Secondary object leaks: %d\n", count); + } +#endif + + assert(list_empty(&rt->gc_obj_list)); + + /* free the classes */ + for(i = 0; i < rt->class_count; i++) { + JSClass *cl = &rt->class_array[i]; + if (cl->class_id != 0) { + JS_FreeAtomRT(rt, cl->class_name); + } + } + js_free_rt(rt, rt->class_array); + + bf_context_end(&rt->bf_ctx); + +#ifdef DUMP_ATOM_LEAKS + /* only the atoms defined in JS_InitAtoms() should be left */ + if (check_dump_flag(rt, DUMP_ATOM_LEAKS)) { + BOOL header_done = FALSE; + + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p) /* && p->str*/) { + if (i >= JS_ATOM_END || p->header.ref_count != 1) { + if (!header_done) { + header_done = TRUE; + if (rt->rt_info) { + printf("%s:1: atom leakage:", rt->rt_info); + } else { + printf("Atom leaks:\n" + " %6s %6s %s\n", + "ID", "REFCNT", "NAME"); + } + } + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u %6u ", i, p->header.ref_count); + } + switch (p->atom_type) { + case JS_ATOM_TYPE_STRING: + JS_DumpString(rt, p); + break; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + printf("Symbol.for("); + JS_DumpString(rt, p); + printf(")"); + break; + case JS_ATOM_TYPE_SYMBOL: + if (p->hash == JS_ATOM_HASH_SYMBOL) { + printf("Symbol("); + JS_DumpString(rt, p); + printf(")"); + } else { + printf("Private("); + JS_DumpString(rt, p); + printf(")"); + } + break; + } + if (rt->rt_info) { + printf(":%u", p->header.ref_count); + } else { + printf("\n"); + } + } + } + } + if (rt->rt_info && header_done) + printf("\n"); + } +#endif + + /* free the atoms */ + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + js_free_rt(rt, rt->atom_array); + js_free_rt(rt, rt->atom_hash); + js_free_rt(rt, rt->shape_hash); +#ifdef DUMP_LEAKS + if (check_dump_flag(rt, DUMP_LEAKS) && !list_empty(&rt->string_list)) { + if (rt->rt_info) { + printf("%s:1: string leakage:", rt->rt_info); + } else { + printf("String leaks:\n" + " %6s %s\n", + "REFCNT", "VALUE"); + } + list_for_each_safe(el, el1, &rt->string_list) { + JSString *str = list_entry(el, JSString, link); + if (rt->rt_info) { + printf(" "); + } else { + printf(" %6u ", str->header.ref_count); + } + JS_DumpString(rt, str); + if (rt->rt_info) { + printf(":%u", str->header.ref_count); + } else { + printf("\n"); + } + list_del(&str->link); + js_free_rt(rt, str); + } + if (rt->rt_info) + printf("\n"); + } +#endif + + while (rt->finalizers) { + JSRuntimeFinalizerState *fs = rt->finalizers; + rt->finalizers = fs->next; + fs->finalizer(rt, fs->arg); + js_free_rt(rt, fs); + } + +#ifdef DUMP_LEAKS + if (check_dump_flag(rt, DUMP_LEAKS)) { + JSMallocState *s = &rt->malloc_state; + if (s->malloc_count > 1) { + if (rt->rt_info) + printf("%s:1: ", rt->rt_info); + printf("Memory leak: %"PRIu64" bytes lost in %"PRIu64" block%s\n", + (uint64_t)(s->malloc_size - sizeof(JSRuntime)), + (uint64_t)(s->malloc_count - 1), &"s"[s->malloc_count == 2]); + } + } +#endif + + { + JSMallocState *ms = &rt->malloc_state; + rt->mf.js_free(ms->opaque, rt); + } +} + +JSContext *JS_NewContextRaw(JSRuntime *rt) +{ + JSContext *ctx; + int i; + + ctx = js_mallocz_rt(rt, sizeof(JSContext)); + if (!ctx) + return NULL; + ctx->header.ref_count = 1; + add_gc_object(rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT); + + ctx->class_proto = js_malloc_rt(rt, sizeof(ctx->class_proto[0]) * + rt->class_count); + if (!ctx->class_proto) { + js_free_rt(rt, ctx); + return NULL; + } + ctx->rt = rt; + list_add_tail(&ctx->link, &rt->context_list); + ctx->bf_ctx = &rt->bf_ctx; + for(i = 0; i < rt->class_count; i++) + ctx->class_proto[i] = JS_NULL; + ctx->array_ctor = JS_NULL; + ctx->iterator_ctor = JS_NULL; + ctx->regexp_ctor = JS_NULL; + ctx->promise_ctor = JS_NULL; + ctx->error_ctor = JS_NULL; + ctx->error_prepare_stack = JS_UNDEFINED; + ctx->error_stack_trace_limit = 10; + init_list_head(&ctx->loaded_modules); + + JS_AddIntrinsicBasicObjects(ctx); + return ctx; +} + +JSContext *JS_NewContext(JSRuntime *rt) +{ + JSContext *ctx; + + ctx = JS_NewContextRaw(rt); + if (!ctx) + return NULL; + + JS_AddIntrinsicBaseObjects(ctx); + JS_AddIntrinsicDate(ctx); + JS_AddIntrinsicEval(ctx); + JS_AddIntrinsicRegExp(ctx); + JS_AddIntrinsicJSON(ctx); + JS_AddIntrinsicProxy(ctx); + JS_AddIntrinsicMapSet(ctx); + JS_AddIntrinsicTypedArrays(ctx); + JS_AddIntrinsicPromise(ctx); + JS_AddIntrinsicBigInt(ctx); + JS_AddIntrinsicWeakRef(ctx); + + JS_AddPerformance(ctx); + + return ctx; +} + +void *JS_GetContextOpaque(JSContext *ctx) +{ + return ctx->user_opaque; +} + +void JS_SetContextOpaque(JSContext *ctx, void *opaque) +{ + ctx->user_opaque = opaque; +} + +/* set the new value and free the old value after (freeing the value + can reallocate the object data) */ +static inline void set_value(JSContext *ctx, JSValue *pval, JSValue new_val) +{ + JSValue old_val; + old_val = *pval; + *pval = new_val; + JS_FreeValue(ctx, old_val); +} + +void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj) +{ + assert(class_id < ctx->rt->class_count); + set_value(ctx, &ctx->class_proto[class_id], obj); +} + +JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id) +{ + assert(class_id < ctx->rt->class_count); + return js_dup(ctx->class_proto[class_id]); +} + +JSValue JS_GetFunctionProto(JSContext *ctx) +{ + return js_dup(ctx->function_proto); +} + +typedef enum JSFreeModuleEnum { + JS_FREE_MODULE_ALL, + JS_FREE_MODULE_NOT_RESOLVED, +} JSFreeModuleEnum; + +/* XXX: would be more efficient with separate module lists */ +static void js_free_modules(JSContext *ctx, JSFreeModuleEnum flag) +{ + struct list_head *el, *el1; + list_for_each_safe(el, el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + if (flag == JS_FREE_MODULE_ALL || + (flag == JS_FREE_MODULE_NOT_RESOLVED && !m->resolved)) { + js_free_module_def(ctx, m); + } + } +} + +JSContext *JS_DupContext(JSContext *ctx) +{ + ctx->header.ref_count++; + return ctx; +} + +/* used by the GC */ +static void JS_MarkContext(JSRuntime *rt, JSContext *ctx, + JS_MarkFunc *mark_func) +{ + int i; + struct list_head *el; + + /* modules are not seen by the GC, so we directly mark the objects + referenced by each module */ + list_for_each(el, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el, JSModuleDef, link); + js_mark_module_def(rt, m, mark_func); + } + + JS_MarkValue(rt, ctx->global_obj, mark_func); + JS_MarkValue(rt, ctx->global_var_obj, mark_func); + + JS_MarkValue(rt, ctx->throw_type_error, mark_func); + JS_MarkValue(rt, ctx->eval_obj, mark_func); + + JS_MarkValue(rt, ctx->array_proto_values, mark_func); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_MarkValue(rt, ctx->native_error_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->error_ctor, mark_func); + JS_MarkValue(rt, ctx->error_prepare_stack, mark_func); + for(i = 0; i < rt->class_count; i++) { + JS_MarkValue(rt, ctx->class_proto[i], mark_func); + } + JS_MarkValue(rt, ctx->iterator_ctor, mark_func); + JS_MarkValue(rt, ctx->async_iterator_proto, mark_func); + JS_MarkValue(rt, ctx->promise_ctor, mark_func); + JS_MarkValue(rt, ctx->array_ctor, mark_func); + JS_MarkValue(rt, ctx->regexp_ctor, mark_func); + JS_MarkValue(rt, ctx->function_ctor, mark_func); + JS_MarkValue(rt, ctx->function_proto, mark_func); + + if (ctx->array_shape) + mark_func(rt, &ctx->array_shape->header); +} + +void JS_FreeContext(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + int i; + + if (--ctx->header.ref_count > 0) + return; + assert(ctx->header.ref_count == 0); + +#ifdef DUMP_ATOMS + if (check_dump_flag(rt, DUMP_ATOMS)) + JS_DumpAtoms(ctx->rt); +#endif +#ifdef DUMP_SHAPES + if (check_dump_flag(rt, DUMP_SHAPES)) + JS_DumpShapes(ctx->rt); +#endif +#ifdef DUMP_OBJECTS + if (check_dump_flag(rt, DUMP_OBJECTS)) { + struct list_head *el; + JSGCObjectHeader *p; + printf("JSObjects: {\n"); + JS_DumpObjectHeader(ctx->rt); + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + JS_DumpGCObject(rt, p); + } + printf("}\n"); + } +#endif +#ifdef DUMP_MEM + if (check_dump_flag(rt, DUMP_MEM)) { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } +#endif + + js_free_modules(ctx, JS_FREE_MODULE_ALL); + + JS_FreeValue(ctx, ctx->global_obj); + JS_FreeValue(ctx, ctx->global_var_obj); + + JS_FreeValue(ctx, ctx->throw_type_error); + JS_FreeValue(ctx, ctx->eval_obj); + + JS_FreeValue(ctx, ctx->array_proto_values); + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JS_FreeValue(ctx, ctx->native_error_proto[i]); + } + JS_FreeValue(ctx, ctx->error_ctor); + JS_FreeValue(ctx, ctx->error_prepare_stack); + for(i = 0; i < rt->class_count; i++) { + JS_FreeValue(ctx, ctx->class_proto[i]); + } + js_free_rt(rt, ctx->class_proto); + JS_FreeValue(ctx, ctx->iterator_ctor); + JS_FreeValue(ctx, ctx->async_iterator_proto); + JS_FreeValue(ctx, ctx->promise_ctor); + JS_FreeValue(ctx, ctx->array_ctor); + JS_FreeValue(ctx, ctx->regexp_ctor); + JS_FreeValue(ctx, ctx->function_ctor); + JS_FreeValue(ctx, ctx->function_proto); + + js_free_shape_null(ctx->rt, ctx->array_shape); + + list_del(&ctx->link); + remove_gc_object(&ctx->header); + js_free_rt(ctx->rt, ctx); +} + +JSRuntime *JS_GetRuntime(JSContext *ctx) +{ + return ctx->rt; +} + +static void update_stack_limit(JSRuntime *rt) +{ +#if defined(__wasi__) || (defined(__ASAN__) && !defined(NDEBUG)) + rt->stack_limit = 0; /* no limit */ +#else + if (rt->stack_size == 0) { + rt->stack_limit = 0; /* no limit */ + } else { + rt->stack_limit = rt->stack_top - rt->stack_size; + } +#endif +} + +void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size) +{ + rt->stack_size = stack_size; + update_stack_limit(rt); +} + +void JS_UpdateStackTop(JSRuntime *rt) +{ + rt->stack_top = js_get_stack_pointer(); + update_stack_limit(rt); +} + +static inline BOOL is_strict_mode(JSContext *ctx) +{ + JSStackFrame *sf = ctx->rt->current_stack_frame; + return sf && sf->is_strict_mode; +} + +/* JSAtom support */ + +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +/* return the max count from the hash size */ +#define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) + +static inline BOOL __JS_AtomIsConst(JSAtom v) +{ + return (int32_t)v < JS_ATOM_END; +} + +static inline BOOL __JS_AtomIsTaggedInt(JSAtom v) +{ + return (v & JS_ATOM_TAG_INT) != 0; +} + +static inline JSAtom __JS_AtomFromUInt32(uint32_t v) +{ + return v | JS_ATOM_TAG_INT; +} + +static inline uint32_t __JS_AtomToUInt32(JSAtom atom) +{ + return atom & ~JS_ATOM_TAG_INT; +} + +static inline int is_num(int c) +{ + return c >= '0' && c <= '9'; +} + +/* return TRUE if the string is a number n with 0 <= n <= 2^32-1 */ +static inline BOOL is_num_string(uint32_t *pval, const JSString *p) +{ + uint32_t n; + uint64_t n64; + int c, i, len; + + len = p->len; + if (len == 0 || len > 10) + return FALSE; + c = string_get(p, 0); + if (is_num(c)) { + if (c == '0') { + if (len != 1) + return FALSE; + n = 0; + } else { + n = c - '0'; + for(i = 1; i < len; i++) { + c = string_get(p, i); + if (!is_num(c)) + return FALSE; + n64 = (uint64_t)n * 10 + (c - '0'); + if ((n64 >> 32) != 0) + return FALSE; + n = n64; + } + } + *pval = n; + return TRUE; + } else { + return FALSE; + } +} + +/* XXX: could use faster version ? */ +static inline uint32_t hash_string8(const uint8_t *str, size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static inline uint32_t hash_string16(const uint16_t *str, + size_t len, uint32_t h) +{ + size_t i; + + for(i = 0; i < len; i++) + h = h * 263 + str[i]; + return h; +} + +static uint32_t hash_string(const JSString *str, uint32_t h) +{ + if (str->is_wide_char) + h = hash_string16(str->u.str16, str->len, h); + else + h = hash_string8(str->u.str8, str->len, h); + return h; +} + +static __maybe_unused void JS_DumpString(JSRuntime *rt, + const JSString *p) +{ + int i, c, sep; + + if (p == NULL) { + printf("<null>"); + return; + } + if (p->header.ref_count != 1) + printf("%d", p->header.ref_count); + if (p->is_wide_char) + putchar('L'); + sep = '\"'; + putchar(sep); + for(i = 0; i < p->len; i++) { + c = string_get(p, i); + if (c == sep || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar(sep); +} + +static __maybe_unused void JS_DumpAtoms(JSRuntime *rt) +{ + JSAtomStruct *p; + int h, i; + /* This only dumps hashed atoms, not JS_ATOM_TYPE_SYMBOL atoms */ + printf("JSAtom count=%d size=%d hash_size=%d:\n", + rt->atom_count, rt->atom_size, rt->atom_hash_size); + printf("JSAtom hash table: {\n"); + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + if (h) { + printf(" %d:", i); + while (h) { + p = rt->atom_array[h]; + printf(" "); + JS_DumpString(rt, p); + h = p->hash_next; + } + printf("\n"); + } + } + printf("}\n"); + printf("JSAtom table: {\n"); + for(i = 0; i < rt->atom_size; i++) { + p = rt->atom_array[i]; + if (!atom_is_free(p)) { + printf(" %d: { %d %08x ", i, p->atom_type, p->hash); + if (!(p->len == 0 && p->is_wide_char != 0)) + JS_DumpString(rt, p); + printf(" %d }\n", p->hash_next); + } + } + printf("}\n"); +} + +static int JS_ResizeAtomHash(JSRuntime *rt, int new_hash_size) +{ + JSAtomStruct *p; + uint32_t new_hash_mask, h, i, hash_next1, j, *new_hash; + + assert((new_hash_size & (new_hash_size - 1)) == 0); /* power of two */ + new_hash_mask = new_hash_size - 1; + new_hash = js_mallocz_rt(rt, sizeof(rt->atom_hash[0]) * new_hash_size); + if (!new_hash) + return -1; + for(i = 0; i < rt->atom_hash_size; i++) { + h = rt->atom_hash[i]; + while (h != 0) { + p = rt->atom_array[h]; + hash_next1 = p->hash_next; + /* add in new hash table */ + j = p->hash & new_hash_mask; + p->hash_next = new_hash[j]; + new_hash[j] = h; + h = hash_next1; + } + } + js_free_rt(rt, rt->atom_hash); + rt->atom_hash = new_hash; + rt->atom_hash_size = new_hash_size; + rt->atom_count_resize = JS_ATOM_COUNT_RESIZE(new_hash_size); + // JS_DumpAtoms(rt); + return 0; +} + +static int JS_InitAtoms(JSRuntime *rt) +{ + int i, len, atom_type; + const char *p; + + rt->atom_hash_size = 0; + rt->atom_hash = NULL; + rt->atom_count = 0; + rt->atom_size = 0; + rt->atom_free_index = 0; + if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ + return -1; + + p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { + if (i == JS_ATOM_Private_brand) + atom_type = JS_ATOM_TYPE_PRIVATE; + else if (i >= JS_ATOM_Symbol_toPrimitive) + atom_type = JS_ATOM_TYPE_SYMBOL; + else + atom_type = JS_ATOM_TYPE_STRING; + len = strlen(p); + if (__JS_NewAtomInit(rt, p, len, atom_type) == JS_ATOM_NULL) + return -1; + p = p + len + 1; + } + return 0; +} + +static JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v) +{ + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +JSAtom JS_DupAtom(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + if (!__JS_AtomIsConst(v)) { + rt = ctx->rt; + p = rt->atom_array[v]; + p->header.ref_count++; + } + return v; +} + +static JSAtomKindEnum JS_AtomGetKind(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return JS_ATOM_KIND_STRING; + p = rt->atom_array[v]; + switch(p->atom_type) { + case JS_ATOM_TYPE_STRING: + return JS_ATOM_KIND_STRING; + case JS_ATOM_TYPE_GLOBAL_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_TYPE_SYMBOL: + switch(p->hash) { + case JS_ATOM_HASH_SYMBOL: + return JS_ATOM_KIND_SYMBOL; + case JS_ATOM_HASH_PRIVATE: + return JS_ATOM_KIND_PRIVATE; + default: + abort(); + } + default: + abort(); + } + return (JSAtomKindEnum){-1}; // pacify compiler +} + +static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p1; + + i = rt->atom_hash[p->hash & (rt->atom_hash_size - 1)]; + p1 = rt->atom_array[i]; + while (p1 != p) { + assert(i != 0); + i = p1->hash_next; + p1 = rt->atom_array[i]; + } + } + return i; +} + +/* string case (internal). Return JS_ATOM_NULL if error. 'str' is + freed. */ +static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + int len; + + if (atom_type < JS_ATOM_TYPE_SYMBOL) { + /* str is not NULL */ + if (str->atom_type == atom_type) { + /* str is the atom, return its index */ + i = js_get_atom_index(rt, str); + /* reduce string refcount and increase atom's unless constant */ + if (__JS_AtomIsConst(i)) + str->header.ref_count--; + return i; + } + /* try and locate an already registered atom */ + len = str->len; + h = hash_string(str, atom_type); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == atom_type && + p->len == len && + js_string_memcmp(p, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + goto done; + } + i = p->hash_next; + } + } else { + h1 = 0; /* avoid warning */ + if (atom_type == JS_ATOM_TYPE_SYMBOL) { + h = JS_ATOM_HASH_SYMBOL; + } else { + h = JS_ATOM_HASH_PRIVATE; + atom_type = JS_ATOM_TYPE_SYMBOL; + } + } + + if (rt->atom_free_index == 0) { + /* allow new atom entries */ + uint32_t new_size, start; + JSAtomStruct **new_array; + + /* alloc new with size progression 3/2: + 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 + preallocating space for predefined atoms (at least 195). + */ + new_size = max_int(211, rt->atom_size * 3 / 2); + if (new_size > JS_ATOM_MAX) + goto fail; + /* XXX: should use realloc2 to use slack space */ + new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size); + if (!new_array) + goto fail; + /* Note: the atom 0 is not used */ + start = rt->atom_size; + if (start == 0) { + /* JS_ATOM_NULL entry */ + p = js_mallocz_rt(rt, sizeof(JSAtomStruct)); + if (!p) { + js_free_rt(rt, new_array); + goto fail; + } + p->header.ref_count = 1; /* not refcounted */ + p->atom_type = JS_ATOM_TYPE_SYMBOL; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + new_array[0] = p; + rt->atom_count++; + start = 1; + } + rt->atom_size = new_size; + rt->atom_array = new_array; + rt->atom_free_index = start; + for(i = start; i < new_size; i++) { + uint32_t next; + if (i == (new_size - 1)) + next = 0; + else + next = i + 1; + rt->atom_array[i] = atom_set_free(next); + } + } + + if (str) { + if (str->atom_type == 0) { + p = str; + p->atom_type = atom_type; + } else { + p = js_malloc_rt(rt, sizeof(JSString) + + (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + if (unlikely(!p)) + goto fail; + p->header.ref_count = 1; + p->is_wide_char = str->is_wide_char; + p->len = str->len; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + memcpy(p->u.str8, str->u.str8, (str->len << str->is_wide_char) + + 1 - str->is_wide_char); + js_free_string(rt, str); + } + } else { + p = js_malloc_rt(rt, sizeof(JSAtomStruct)); /* empty wide string */ + if (!p) + return JS_ATOM_NULL; + p->header.ref_count = 1; + p->is_wide_char = 1; /* Hack to represent NULL as a JSString */ + p->len = 0; +#ifdef DUMP_LEAKS + list_add_tail(&p->link, &rt->string_list); +#endif + } + + /* use an already free entry */ + i = rt->atom_free_index; + rt->atom_free_index = atom_get_free(rt->atom_array[i]); + rt->atom_array[i] = p; + + p->hash = h; + p->hash_next = i; /* atom_index */ + p->atom_type = atom_type; + p->first_weak_ref = NULL; + + rt->atom_count++; + + if (atom_type != JS_ATOM_TYPE_SYMBOL) { + p->hash_next = rt->atom_hash[h1]; + rt->atom_hash[h1] = i; + if (unlikely(rt->atom_count >= rt->atom_count_resize)) + JS_ResizeAtomHash(rt, rt->atom_hash_size * 2); + } + + // JS_DumpAtoms(rt); + return i; + + fail: + i = JS_ATOM_NULL; + done: + if (str) + js_free_string(rt, str); + return i; +} + +// XXX: `str` must be pure ASCII. No UTF-8 encoded strings +// XXX: `str` must not be the string representation of a small integer +static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, + int atom_type) +{ + JSString *p; + p = js_alloc_string_rt(rt, len, 0); + if (!p) + return JS_ATOM_NULL; + memcpy(p->u.str8, str, len); + p->u.str8[len] = '\0'; + return __JS_NewAtom(rt, p, atom_type); +} + +// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings +static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len, + int atom_type) +{ + uint32_t h, h1, i; + JSAtomStruct *p; + + h = hash_string8((const uint8_t *)str, len, JS_ATOM_TYPE_STRING); + h &= JS_ATOM_HASH_MASK; + h1 = h & (rt->atom_hash_size - 1); + i = rt->atom_hash[h1]; + while (i != 0) { + p = rt->atom_array[i]; + if (p->hash == h && + p->atom_type == JS_ATOM_TYPE_STRING && + p->len == len && + p->is_wide_char == 0 && + memcmp(p->u.str8, str, len) == 0) { + if (!__JS_AtomIsConst(i)) + p->header.ref_count++; + return i; + } + i = p->hash_next; + } + return JS_ATOM_NULL; +} + +static void JS_FreeAtomStruct(JSRuntime *rt, JSAtomStruct *p) +{ + uint32_t i = p->hash_next; /* atom_index */ + if (p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JSAtomStruct *p0, *p1; + uint32_t h0; + + h0 = p->hash & (rt->atom_hash_size - 1); + i = rt->atom_hash[h0]; + p1 = rt->atom_array[i]; + if (p1 == p) { + rt->atom_hash[h0] = p1->hash_next; + } else { + for(;;) { + assert(i != 0); + p0 = p1; + i = p1->hash_next; + p1 = rt->atom_array[i]; + if (p1 == p) { + p0->hash_next = p1->hash_next; + break; + } + } + } + } + /* insert in free atom list */ + rt->atom_array[i] = atom_set_free(rt->atom_free_index); + rt->atom_free_index = i; + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, &p->first_weak_ref); + } + /* free the string structure */ +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + rt->atom_count--; + assert(rt->atom_count >= 0); +} + +static void __JS_FreeAtom(JSRuntime *rt, uint32_t i) +{ + JSAtomStruct *p; + + p = rt->atom_array[i]; + if (--p->header.ref_count > 0) + return; + JS_FreeAtomStruct(rt, p); +} + +/* Warning: 'p' is freed */ +static JSAtom JS_NewAtomStr(JSContext *ctx, JSString *p) +{ + JSRuntime *rt = ctx->rt; + uint32_t n; + if (is_num_string(&n, p)) { + if (n <= JS_ATOM_MAX_INT) { + js_free_string(rt, p); + return __JS_AtomFromUInt32(n); + } + } + /* XXX: should generate an exception */ + return __JS_NewAtom(rt, p, JS_ATOM_TYPE_STRING); +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len) +{ + JSValue val; + + if (len == 0 || !is_digit(*str)) { + // TODO(chqrlie): this does not work if `str` has UTF-8 encoded contents + // bug example: `({ "\u00c3\u00a9": 1 }).\u00e9` evaluates to `1`. + JSAtom atom = __JS_FindAtom(ctx->rt, str, len, JS_ATOM_TYPE_STRING); + if (atom) + return atom; + } + val = JS_NewStringLen(ctx, str, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(val)); +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSAtom JS_NewAtom(JSContext *ctx, const char *str) +{ + return JS_NewAtomLen(ctx, str, strlen(str)); +} + +JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n) +{ + if (n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32(n); + } else { + char buf[16]; + size_t len = u32toa(buf, n); + JSValue val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +static JSAtom JS_NewAtomInt64(JSContext *ctx, int64_t n) +{ + if ((uint64_t)n <= JS_ATOM_MAX_INT) { + return __JS_AtomFromUInt32((uint32_t)n); + } else { + char buf[24]; + size_t len = i64toa(buf, n); + JSValue val = js_new_string8_len(ctx, buf, len); + if (JS_IsException(val)) + return JS_ATOM_NULL; + return __JS_NewAtom(ctx->rt, JS_VALUE_GET_STRING(val), + JS_ATOM_TYPE_STRING); + } +} + +/* 'p' is freed */ +static JSValue JS_NewSymbolInternal(JSContext *ctx, JSString *p, int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSAtom atom; + atom = __JS_NewAtom(rt, p, atom_type); + if (atom == JS_ATOM_NULL) + return JS_ThrowOutOfMemory(ctx); + return JS_MKPTR(JS_TAG_SYMBOL, rt->atom_array[atom]); +} + +/* descr must be a non-numeric string atom */ +static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr, + int atom_type) +{ + JSRuntime *rt = ctx->rt; + JSString *p; + + assert(!__JS_AtomIsTaggedInt(descr)); + assert(descr < rt->atom_size); + p = rt->atom_array[descr]; + js_dup(JS_MKPTR(JS_TAG_STRING, p)); + return JS_NewSymbolInternal(ctx, p, atom_type); +} + +/* `description` may be pure ASCII or UTF-8 encoded */ +JSValue JS_NewSymbol(JSContext *ctx, const char *description, JS_BOOL is_global) +{ + JSAtom atom = JS_NewAtom(ctx, description); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + return JS_NewSymbolFromAtom(ctx, atom, is_global ? JS_ATOM_TYPE_GLOBAL_SYMBOL : JS_ATOM_TYPE_SYMBOL); +} + +#define ATOM_GET_STR_BUF_SIZE 64 + +/* Should only be used for debug. */ +static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size, + JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + snprintf(buf, buf_size, "%u", __JS_AtomToUInt32(atom)); + } else if (atom == JS_ATOM_NULL) { + snprintf(buf, buf_size, "<null>"); + } else if (atom >= rt->atom_size) { + assert(atom < rt->atom_size); + snprintf(buf, buf_size, "<invalid %x>", atom); + } else { + JSAtomStruct *p = rt->atom_array[atom]; + *buf = '\0'; + if (atom_is_free(p)) { + assert(!atom_is_free(p)); + snprintf(buf, buf_size, "<free %x>", atom); + } else if (p != NULL) { + JSString *str = p; + if (str->is_wide_char) { + /* encode surrogates correctly */ + utf8_encode_buf16(buf, buf_size, str->u.str16, str->len); + } else { + /* special case ASCII strings */ + int i, c = 0; + for(i = 0; i < str->len; i++) { + c |= str->u.str8[i]; + } + if (c < 0x80) + return (const char *)str->u.str8; + utf8_encode_buf8(buf, buf_size, str->u.str8, str->len); + } + } + } + return buf; +} + +static const char *JS_AtomGetStr(JSContext *ctx, char *buf, int buf_size, JSAtom atom) +{ + return JS_AtomGetStrRT(ctx->rt, buf, buf_size, atom); +} + +static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, BOOL force_string) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (__JS_AtomIsTaggedInt(atom)) { + size_t len = u32toa(buf, __JS_AtomToUInt32(atom)); + return js_new_string8_len(ctx, buf, len); + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING) { + goto ret_string; + } else if (force_string) { + if (p->len == 0 && p->is_wide_char != 0) { + /* no description string */ + p = rt->atom_array[JS_ATOM_empty_string]; + } + ret_string: + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); + } else { + return js_dup(JS_MKPTR(JS_TAG_SYMBOL, p)); + } + } +} + +JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, FALSE); +} + +JSValue JS_AtomToString(JSContext *ctx, JSAtom atom) +{ + return __JS_AtomToValue(ctx, atom, TRUE); +} + +/* return TRUE if the atom is an array index (i.e. 0 <= index <= + 2^32-2 and return its value */ +static BOOL JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom) +{ + if (__JS_AtomIsTaggedInt(atom)) { + *pval = __JS_AtomToUInt32(atom); + return TRUE; + } else { + JSRuntime *rt = ctx->rt; + JSAtomStruct *p; + uint32_t val; + + assert(atom < rt->atom_size); + p = rt->atom_array[atom]; + if (p->atom_type == JS_ATOM_TYPE_STRING && + is_num_string(&val, p) && val != -1) { + *pval = val; + return TRUE; + } else { + *pval = 0; + return FALSE; + } + } +} + +/* This test must be fast if atom is not a numeric index (e.g. a + method name). Return JS_UNDEFINED if not a numeric + index. JS_EXCEPTION can also be returned. */ +static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom) +{ + JSRuntime *rt = ctx->rt; + JSAtomStruct *p1; + JSString *p; + int c, len, ret; + JSValue num, str; + + if (__JS_AtomIsTaggedInt(atom)) + return js_int32(__JS_AtomToUInt32(atom)); + assert(atom < rt->atom_size); + p1 = rt->atom_array[atom]; + if (p1->atom_type != JS_ATOM_TYPE_STRING) + return JS_UNDEFINED; + p = p1; + len = p->len; + if (p->is_wide_char) { + const uint16_t *r = p->u.str16, *r_end = p->u.str16 + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) + goto minus_zero; + } + /* XXX: should test NaN, but the tests do not check it */ + if (!is_num(c)) { + /* XXX: String should be normalized, therefore 8-bit only */ + const uint16_t nfinity16[7] = { 'n', 'f', 'i', 'n', 'i', 't', 'y' }; + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, nfinity16, sizeof(nfinity16)))) + return JS_UNDEFINED; + } + } else { + const uint8_t *r = p->u.str8, *r_end = p->u.str8 + len; + if (r >= r_end) + return JS_UNDEFINED; + c = *r; + if (c == '-') { + if (r >= r_end) + return JS_UNDEFINED; + r++; + c = *r; + /* -0 case is specific */ + if (c == '0' && len == 2) { + minus_zero: + return js_float64(-0.0); + } + } + if (!is_num(c)) { + if (!(c =='I' && (r_end - r) == 8 && + !memcmp(r + 1, "nfinity", 7))) + return JS_UNDEFINED; + } + } + /* this is ECMA CanonicalNumericIndexString primitive */ + num = JS_ToNumber(ctx, JS_MKPTR(JS_TAG_STRING, p)); + if (JS_IsException(num)) + return num; + str = JS_ToString(ctx, num); + if (JS_IsException(str)) { + JS_FreeValue(ctx, num); + return str; + } + ret = js_string_compare(ctx, p, JS_VALUE_GET_STRING(str)); + JS_FreeValue(ctx, str); + if (ret == 0) { + return num; + } else { + JS_FreeValue(ctx, num); + return JS_UNDEFINED; + } +} + +/* return -1 if exception or TRUE/FALSE */ +static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom) +{ + JSValue num; + num = JS_AtomIsNumericIndex1(ctx, atom); + if (likely(JS_IsUndefined(num))) + return FALSE; + if (JS_IsException(num)) + return -1; + JS_FreeValue(ctx, num); + return TRUE; +} + +void JS_FreeAtom(JSContext *ctx, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(ctx->rt, v); +} + +void JS_FreeAtomRT(JSRuntime *rt, JSAtom v) +{ + if (!__JS_AtomIsConst(v)) + __JS_FreeAtom(rt, v); +} + +/* return TRUE if 'v' is a symbol with a string description */ +static BOOL JS_AtomSymbolHasDescription(JSContext *ctx, JSAtom v) +{ + JSRuntime *rt; + JSAtomStruct *p; + + rt = ctx->rt; + if (__JS_AtomIsTaggedInt(v)) + return FALSE; + p = rt->atom_array[v]; + return (((p->atom_type == JS_ATOM_TYPE_SYMBOL && + p->hash == JS_ATOM_HASH_SYMBOL) || + p->atom_type == JS_ATOM_TYPE_GLOBAL_SYMBOL) && + !(p->len == 0 && p->is_wide_char != 0)); +} + +static __maybe_unused void print_atom(JSContext *ctx, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + const char *p; + int i; + + /* XXX: should handle embedded null characters */ + /* XXX: should move encoding code to JS_AtomGetStr */ + p = JS_AtomGetStr(ctx, buf, sizeof(buf), atom); + for (i = 0; p[i]; i++) { + int c = (unsigned char)p[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c == '_' || c == '$') || (c >= '0' && c <= '9' && i > 0))) + break; + } + if (i > 0 && p[i] == '\0') { + printf("%s", p); + } else { + putchar('"'); + printf("%.*s", i, p); + for (; p[i]; i++) { + int c = (unsigned char)p[i]; + if (c == '\"' || c == '\\') { + putchar('\\'); + putchar(c); + } else if (c >= ' ' && c <= 126) { + putchar(c); + } else if (c == '\n') { + putchar('\\'); + putchar('n'); + } else { + printf("\\u%04x", c); + } + } + putchar('\"'); + } +} + +/* free with JS_FreeCString() */ +const char *JS_AtomToCString(JSContext *ctx, JSAtom atom) +{ + JSValue str; + const char *cstr; + + str = JS_AtomToString(ctx, atom); + if (JS_IsException(str)) + return NULL; + cstr = JS_ToCString(ctx, str); + JS_FreeValue(ctx, str); + return cstr; +} + +/* return a string atom containing name concatenated with str1 */ +/* `str1` may be pure ASCII or UTF-8 encoded */ +// TODO(chqrlie): use string concatenation instead of UTF-8 conversion +static JSAtom js_atom_concat_str(JSContext *ctx, JSAtom name, const char *str1) +{ + JSValue str; + JSAtom atom; + const char *cstr; + char *cstr2; + size_t len, len1; + + str = JS_AtomToString(ctx, name); + if (JS_IsException(str)) + return JS_ATOM_NULL; + cstr = JS_ToCStringLen(ctx, &len, str); + if (!cstr) + goto fail; + len1 = strlen(str1); + cstr2 = js_malloc(ctx, len + len1 + 1); + if (!cstr2) + goto fail; + memcpy(cstr2, cstr, len); + memcpy(cstr2 + len, str1, len1); + cstr2[len + len1] = '\0'; + atom = JS_NewAtomLen(ctx, cstr2, len + len1); + js_free(ctx, cstr2); + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return atom; + fail: + JS_FreeCString(ctx, cstr); + JS_FreeValue(ctx, str); + return JS_ATOM_NULL; +} + +static JSAtom js_atom_concat_num(JSContext *ctx, JSAtom name, uint32_t n) +{ + char buf[16]; + u32toa(buf, n); + return js_atom_concat_str(ctx, name, buf); +} + +static inline BOOL JS_IsEmptyString(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING && JS_VALUE_GET_STRING(v)->len == 0; +} + +/* JSClass support */ + +/* a new class ID is allocated if *pclass_id != 0 */ +JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id) +{ + JSClassID class_id = *pclass_id; + if (class_id == 0) { + class_id = rt->js_class_id_alloc++; + *pclass_id = class_id; + } + return class_id; +} + +JSClassID JS_GetClassID(JSValue v) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_INVALID_CLASS_ID; + p = JS_VALUE_GET_OBJ(v); + return p->class_id; +} + +BOOL JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id) +{ + return (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0); +} + +/* create a new object internal class. Return -1 if error, 0 if + OK. The finalizer can be NULL if none is needed. */ +static int JS_NewClass1(JSRuntime *rt, JSClassID class_id, + const JSClassDef *class_def, JSAtom name) +{ + int new_size, i; + JSClass *cl, *new_class_array; + struct list_head *el; + + if (class_id >= (1 << 16)) + return -1; + if (class_id < rt->class_count && + rt->class_array[class_id].class_id != 0) + return -1; + + if (class_id >= rt->class_count) { + new_size = max_int(JS_CLASS_INIT_COUNT, + max_int(class_id + 1, rt->class_count * 3 / 2)); + + /* reallocate the context class prototype array, if any */ + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSValue *new_tab; + new_tab = js_realloc_rt(rt, ctx->class_proto, + sizeof(ctx->class_proto[0]) * new_size); + if (!new_tab) + return -1; + for(i = rt->class_count; i < new_size; i++) + new_tab[i] = JS_NULL; + ctx->class_proto = new_tab; + } + /* reallocate the class array */ + new_class_array = js_realloc_rt(rt, rt->class_array, + sizeof(JSClass) * new_size); + if (!new_class_array) + return -1; + memset(new_class_array + rt->class_count, 0, + (new_size - rt->class_count) * sizeof(JSClass)); + rt->class_array = new_class_array; + rt->class_count = new_size; + } + cl = &rt->class_array[class_id]; + cl->class_id = class_id; + cl->class_name = JS_DupAtomRT(rt, name); + cl->finalizer = class_def->finalizer; + cl->gc_mark = class_def->gc_mark; + cl->call = class_def->call; + cl->exotic = class_def->exotic; + cl->can_destroy = class_def->can_destroy; + return 0; +} + +int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) +{ + int ret, len; + JSAtom name; + + // XXX: class_def->class_name must be raw 8-bit contents. No UTF-8 encoded strings + len = strlen(class_def->class_name); + name = __JS_FindAtom(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) { + name = __JS_NewAtomInit(rt, class_def->class_name, len, JS_ATOM_TYPE_STRING); + if (name == JS_ATOM_NULL) + return -1; + } + ret = JS_NewClass1(rt, class_id, class_def, name); + JS_FreeAtomRT(rt, name); + return ret; +} + +// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed +// XXX: no special case for len == 0 +static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str8, buf, len); + str->u.str8[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); +} + +// XXX: `buf` contains raw 8-bit data, no UTF-8 decoding is performed +// XXX: no special case for the empty string +static inline JSValue js_new_string8(JSContext *ctx, const char *str) +{ + return js_new_string8_len(ctx, str, strlen(str)); +} + +static JSValue js_new_string16_len(JSContext *ctx, const uint16_t *buf, int len) +{ + JSString *str; + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str16, buf, len * 2); + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue js_new_string_char(JSContext *ctx, uint16_t c) +{ + if (c < 0x100) { + char ch8 = c; + return js_new_string8_len(ctx, &ch8, 1); + } else { + uint16_t ch16 = c; + return js_new_string16_len(ctx, &ch16, 1); + } +} + +static JSValue js_sub_string(JSContext *ctx, JSString *p, int start, int end) +{ + int len = end - start; + if (start == 0 && end == p->len) { + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); + } + if (len <= 0) { + return JS_AtomToString(ctx, JS_ATOM_empty_string); + } + if (p->is_wide_char) { + JSString *str; + int i; + uint16_t c = 0; + for (i = start; i < end; i++) { + c |= p->u.str16[i]; + } + if (c > 0xFF) + return js_new_string16_len(ctx, p->u.str16 + start, len); + + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + for (i = 0; i < len; i++) { + str->u.str8[i] = p->u.str16[start + i]; + } + str->u.str8[len] = '\0'; + return JS_MKPTR(JS_TAG_STRING, str); + } else { + return js_new_string8_len(ctx, (const char *)(p->u.str8 + start), len); + } +} + +typedef struct StringBuffer { + JSContext *ctx; + JSString *str; + int len; + int size; + int is_wide_char; + int error_status; +} StringBuffer; + +/* It is valid to call string_buffer_end() and all string_buffer functions even + if string_buffer_init() or another string_buffer function returns an error. + If the error_status is set, string_buffer_end() returns JS_EXCEPTION. + */ +static int string_buffer_init2(JSContext *ctx, StringBuffer *s, int size, + int is_wide) +{ + s->ctx = ctx; + s->size = size; + s->len = 0; + s->is_wide_char = is_wide; + s->error_status = 0; + s->str = js_alloc_string(ctx, size, is_wide); + if (unlikely(!s->str)) { + s->size = 0; + return s->error_status = -1; + } +#ifdef DUMP_LEAKS + /* the StringBuffer may reallocate the JSString, only link it at the end */ + list_del(&s->str->link); +#endif + return 0; +} + +static inline int string_buffer_init(JSContext *ctx, StringBuffer *s, int size) +{ + return string_buffer_init2(ctx, s, size, 0); +} + +static void string_buffer_free(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; +} + +static int string_buffer_set_error(StringBuffer *s) +{ + js_free(s->ctx, s->str); + s->str = NULL; + s->size = 0; + s->len = 0; + return s->error_status = -1; +} + +static no_inline int string_buffer_widen(StringBuffer *s, int size) +{ + JSString *str; + size_t slack; + int i; + + if (s->error_status) + return -1; + + str = js_realloc2(s->ctx, s->str, sizeof(JSString) + (size << 1), &slack); + if (!str) + return string_buffer_set_error(s); + size += slack >> 1; + for(i = s->len; i-- > 0;) { + str->u.str16[i] = str->u.str8[i]; + } + s->is_wide_char = 1; + s->size = size; + s->str = str; + return 0; +} + +static no_inline int string_buffer_realloc(StringBuffer *s, int new_len, int c) +{ + JSString *new_str; + int new_size; + size_t new_size_bytes, slack; + + if (s->error_status) + return -1; + + if (new_len > JS_STRING_LEN_MAX) { + JS_ThrowRangeError(s->ctx, "invalid string length"); + return string_buffer_set_error(s); + } + new_size = min_int(max_int(new_len, s->size * 3 / 2), JS_STRING_LEN_MAX); + if (!s->is_wide_char && c >= 0x100) { + return string_buffer_widen(s, new_size); + } + new_size_bytes = sizeof(JSString) + (new_size << s->is_wide_char) + 1 - s->is_wide_char; + new_str = js_realloc2(s->ctx, s->str, new_size_bytes, &slack); + if (!new_str) + return string_buffer_set_error(s); + new_size = min_int(new_size + (slack >> s->is_wide_char), JS_STRING_LEN_MAX); + s->size = new_size; + s->str = new_str; + return 0; +} + +static no_inline int string_buffer_putc_slow(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + } else { + if (string_buffer_widen(s, s->size)) + return -1; + s->str->u.str16[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xff */ +static int string_buffer_putc8(StringBuffer *s, uint32_t c) +{ + if (unlikely(s->len >= s->size)) { + if (string_buffer_realloc(s, s->len + 1, c)) + return -1; + } + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + } else { + s->str->u.str8[s->len++] = c; + } + return 0; +} + +/* 0 <= c <= 0xffff */ +static int string_buffer_putc16(StringBuffer *s, uint32_t c) +{ + if (likely(s->len < s->size)) { + if (s->is_wide_char) { + s->str->u.str16[s->len++] = c; + return 0; + } else if (c < 0x100) { + s->str->u.str8[s->len++] = c; + return 0; + } + } + return string_buffer_putc_slow(s, c); +} + +/* 0 <= c <= 0x10ffff */ +static int string_buffer_putc(StringBuffer *s, uint32_t c) +{ + if (unlikely(c >= 0x10000)) { + /* surrogate pair */ + if (string_buffer_putc16(s, get_hi_surrogate(c))) + return -1; + c = get_lo_surrogate(c); + } + return string_buffer_putc16(s, c); +} + +static int string_getc(const JSString *p, int *pidx) +{ + int idx, c, c1; + idx = *pidx; + if (p->is_wide_char) { + c = p->u.str16[idx++]; + if (is_hi_surrogate(c) && idx < p->len) { + c1 = p->u.str16[idx]; + if (is_lo_surrogate(c1)) { + c = from_surrogate(c, c1); + idx++; + } + } + } else { + c = p->u.str8[idx++]; + } + *pidx = idx; + return c; +} + +static int string_buffer_write8(StringBuffer *s, const uint8_t *p, int len) +{ + int i; + + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, 0)) + return -1; + } + if (s->is_wide_char) { + for (i = 0; i < len; i++) { + s->str->u.str16[s->len + i] = p[i]; + } + s->len += len; + } else { + memcpy(&s->str->u.str8[s->len], p, len); + s->len += len; + } + return 0; +} + +static int string_buffer_write16(StringBuffer *s, const uint16_t *p, int len) +{ + int c = 0, i; + + for (i = 0; i < len; i++) { + c |= p[i]; + } + if (s->len + len > s->size) { + if (string_buffer_realloc(s, s->len + len, c)) + return -1; + } else if (!s->is_wide_char && c >= 0x100) { + if (string_buffer_widen(s, s->size)) + return -1; + } + if (s->is_wide_char) { + memcpy(&s->str->u.str16[s->len], p, len << 1); + s->len += len; + } else { + for (i = 0; i < len; i++) { + s->str->u.str8[s->len + i] = p[i]; + } + s->len += len; + } + return 0; +} + +/* appending an ASCII string */ +static int string_buffer_puts8(StringBuffer *s, const char *str) +{ + return string_buffer_write8(s, (const uint8_t *)str, strlen(str)); +} + +static int string_buffer_concat(StringBuffer *s, const JSString *p, + uint32_t from, uint32_t to) +{ + if (to <= from) + return 0; + if (p->is_wide_char) + return string_buffer_write16(s, p->u.str16 + from, to - from); + else + return string_buffer_write8(s, p->u.str8 + from, to - from); +} + +static int string_buffer_concat_value(StringBuffer *s, JSValue v) +{ + JSString *p; + JSValue v1; + int res; + + if (s->error_status) { + /* prevent exception overload */ + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v1 = JS_ToString(s->ctx, v); + if (JS_IsException(v1)) + return string_buffer_set_error(s); + p = JS_VALUE_GET_STRING(v1); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v1); + return res; + } + p = JS_VALUE_GET_STRING(v); + return string_buffer_concat(s, p, 0, p->len); +} + +static int string_buffer_concat_value_free(StringBuffer *s, JSValue v) +{ + JSString *p; + int res; + + if (s->error_status) { + /* prevent exception overload */ + JS_FreeValue(s->ctx, v); + return -1; + } + if (unlikely(JS_VALUE_GET_TAG(v) != JS_TAG_STRING)) { + v = JS_ToStringFree(s->ctx, v); + if (JS_IsException(v)) + return string_buffer_set_error(s); + } + p = JS_VALUE_GET_STRING(v); + res = string_buffer_concat(s, p, 0, p->len); + JS_FreeValue(s->ctx, v); + return res; +} + +static int string_buffer_fill(StringBuffer *s, int c, int count) +{ + /* XXX: optimize */ + if (s->len + count > s->size) { + if (string_buffer_realloc(s, s->len + count, c)) + return -1; + } + while (count-- > 0) { + if (string_buffer_putc16(s, c)) + return -1; + } + return 0; +} + +static JSValue string_buffer_end(StringBuffer *s) +{ + JSString *str; + str = s->str; + if (s->error_status) + return JS_EXCEPTION; + if (s->len == 0) { + js_free(s->ctx, str); + s->str = NULL; + return JS_AtomToString(s->ctx, JS_ATOM_empty_string); + } + if (s->len < s->size) { + /* smaller size so js_realloc should not fail, but OK if it does */ + /* XXX: should add some slack to avoid unnecessary calls */ + /* XXX: might need to use malloc+free to ensure smaller size */ + str = js_realloc_rt(s->ctx->rt, str, sizeof(JSString) + + (s->len << s->is_wide_char) + 1 - s->is_wide_char); + if (str == NULL) + str = s->str; + s->str = str; + } + if (!s->is_wide_char) + str->u.str8[s->len] = 0; +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &s->ctx->rt->string_list); +#endif + str->is_wide_char = s->is_wide_char; + str->len = s->len; + s->str = NULL; + return JS_MKPTR(JS_TAG_STRING, str); +} + +/* create a string from a UTF-8 buffer */ +JSValue JS_NewStringLen(JSContext *ctx, const char *buf, size_t buf_len) +{ + JSString *str; + size_t len; + int kind; + + if (buf_len <= 0) { + return JS_AtomToString(ctx, JS_ATOM_empty_string); + } + /* Compute string kind and length: 7-bit, 8-bit, 16-bit, 16-bit UTF-16 */ + kind = utf8_scan(buf, buf_len, &len); + if (len > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid string length"); + + switch (kind) { + case UTF8_PLAIN_ASCII: + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str8, buf, len); + str->u.str8[len] = '\0'; + break; + case UTF8_NON_ASCII: + /* buf contains non-ASCII code-points, but limited to 8-bit values */ + str = js_alloc_string(ctx, len, 0); + if (!str) + return JS_EXCEPTION; + utf8_decode_buf8(str->u.str8, len + 1, buf, buf_len); + break; + default: + // This causes a potential problem in JS_ThrowError if message is invalid + //if (kind & UTF8_HAS_ERRORS) + // return JS_ThrowRangeError(ctx, "invalid UTF-8 sequence"); + str = js_alloc_string(ctx, len, 1); + if (!str) + return JS_EXCEPTION; + utf8_decode_buf16(str->u.str16, len, buf, buf_len); + break; + } + return JS_MKPTR(JS_TAG_STRING, str); +} + +static JSValue JS_ConcatString3(JSContext *ctx, const char *str1, + JSValue str2, const char *str3) +{ + StringBuffer b_s, *b = &b_s; + int len1, len3; + JSString *p; + + if (unlikely(JS_VALUE_GET_TAG(str2) != JS_TAG_STRING)) { + str2 = JS_ToStringFree(ctx, str2); + if (JS_IsException(str2)) + goto fail; + } + p = JS_VALUE_GET_STRING(str2); + len1 = strlen(str1); + len3 = strlen(str3); + + if (string_buffer_init2(ctx, b, len1 + p->len + len3, p->is_wide_char)) + goto fail; + + string_buffer_write8(b, (const uint8_t *)str1, len1); + string_buffer_concat(b, p, 0, p->len); + string_buffer_write8(b, (const uint8_t *)str3, len3); + + JS_FreeValue(ctx, str2); + return string_buffer_end(b); + + fail: + JS_FreeValue(ctx, str2); + return JS_EXCEPTION; +} + +/* `str` may be pure ASCII or UTF-8 encoded */ +JSValue JS_NewAtomString(JSContext *ctx, const char *str) +{ + JSAtom atom = JS_NewAtom(ctx, str); + if (atom == JS_ATOM_NULL) + return JS_EXCEPTION; + JSValue val = JS_AtomToString(ctx, atom); + JS_FreeAtom(ctx, atom); + return val; +} + +/* return (NULL, 0) if exception. */ +/* return pointer into a JSString with a live ref_count */ +/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 sequences */ +const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValue val1, BOOL cesu8) +{ + JSValue val; + JSString *str, *str_new; + int pos, len, c, c1; + JSObject *p; + uint8_t *q; + + if (JS_VALUE_GET_TAG(val1) == JS_TAG_STRING) { + val = js_dup(val1); + goto go; + } + + val = JS_ToString(ctx, val1); + if (!JS_IsException(val)) + goto go; + + // Stringification can fail when there is an exception pending, + // e.g. a stack overflow InternalError. Special-case exception + // objects to make debugging easier, look up the .message property + // and stringify that. + if (JS_VALUE_GET_TAG(val1) != JS_TAG_OBJECT) + goto fail; + + p = JS_VALUE_GET_OBJ(val1); + if (p->class_id != JS_CLASS_ERROR) + goto fail; + + val = JS_GetProperty(ctx, val1, JS_ATOM_message); + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) { + JS_FreeValue(ctx, val); + goto fail; + } + +go: + str = JS_VALUE_GET_STRING(val); + len = str->len; + if (!str->is_wide_char) { + const uint8_t *src = str->u.str8; + int count; + + /* count the number of non-ASCII characters */ + /* Scanning the whole string is required for ASCII strings, + and computing the number of non-ASCII bytes is less expensive + than testing each byte, hence this method is faster for ASCII + strings, which is the most common case. + */ + count = 0; + for (pos = 0; pos < len; pos++) { + count += src[pos] >> 7; + } + if (count == 0) { + if (plen) + *plen = len; + return (const char *)src; + } + str_new = js_alloc_string(ctx, len + count, 0); + if (!str_new) + goto fail; + q = str_new->u.str8; + for (pos = 0; pos < len; pos++) { + c = src[pos]; + if (c < 0x80) { + *q++ = c; + } else { + *q++ = (c >> 6) | 0xc0; + *q++ = (c & 0x3f) | 0x80; + } + } + } else { + const uint16_t *src = str->u.str16; + /* Allocate 3 bytes per 16 bit code point. Surrogate pairs may + produce 4 bytes but use 2 code points. + */ + str_new = js_alloc_string(ctx, len * 3, 0); + if (!str_new) + goto fail; + q = str_new->u.str8; + pos = 0; + while (pos < len) { + c = src[pos++]; + if (c < 0x80) { + *q++ = c; + } else { + if (is_hi_surrogate(c)) { + if (pos < len && !cesu8) { + c1 = src[pos]; + if (is_lo_surrogate(c1)) { + pos++; + c = from_surrogate(c, c1); + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } else { + /* Keep unmatched surrogate code points */ + /* c = 0xfffd; */ /* error */ + } + } + q += utf8_encode(q, c); + } + } + } + + *q = '\0'; + str_new->len = q - str_new->u.str8; + JS_FreeValue(ctx, val); + if (plen) + *plen = str_new->len; + return (const char *)str_new->u.str8; + fail: + if (plen) + *plen = 0; + return NULL; +} + +void JS_FreeCString(JSContext *ctx, const char *ptr) +{ + JSString *p; + if (!ptr) + return; + /* purposely removing constness */ + p = container_of(ptr, JSString, u); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, p)); +} + +/* create a string from a narrow Unicode (latin-1) buffer */ +JSValue JS_NewNarrowStringLen(JSContext *ctx, const char *buf, size_t buf_len) +{ + if (buf_len > JS_STRING_LEN_MAX) + return JS_ThrowInternalError(ctx, "string too long"); + return js_new_string8_len(ctx, buf, buf_len); +} + +JS_BOOL JS_IsStringWideChar(JSValue value) +{ + if (unlikely(JS_VALUE_GET_TAG(value) != JS_TAG_STRING)) + return FALSE; + return JS_VALUE_GET_STRING(value)->is_wide_char; +} + +uint8_t *JS_GetNarrowStringBuffer(JSValue value) +{ + if (unlikely(JS_VALUE_GET_TAG(value) != JS_TAG_STRING)) + return NULL; + return JS_VALUE_GET_STRING(value)->u.str8; +} + +uint32_t JS_GetStringLength(JSValue value) +{ + if (unlikely(JS_VALUE_GET_TAG(value) != JS_TAG_STRING)) + return 0; + return JS_VALUE_GET_STRING(value)->len; +} + +static int memcmp16_8(const uint16_t *src1, const uint8_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int memcmp16(const uint16_t *src1, const uint16_t *src2, int len) +{ + int c, i; + for(i = 0; i < len; i++) { + c = src1[i] - src2[i]; + if (c != 0) + return c; + } + return 0; +} + +static int js_string_memcmp(const JSString *p1, const JSString *p2, int len) +{ + int res; + + if (likely(!p1->is_wide_char)) { + if (likely(!p2->is_wide_char)) + res = memcmp(p1->u.str8, p2->u.str8, len); + else + res = -memcmp16_8(p2->u.str16, p1->u.str8, len); + } else { + if (!p2->is_wide_char) + res = memcmp16_8(p1->u.str16, p2->u.str8, len); + else + res = memcmp16(p1->u.str16, p2->u.str16, len); + } + return res; +} + +/* return < 0, 0 or > 0 */ +static int js_string_compare(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + int res, len; + len = min_int(p1->len, p2->len); + res = js_string_memcmp(p1, p2, len); + if (res == 0) + res = compare_u32(p1->len, p2->len); + return res; +} + +static void copy_str16(uint16_t *dst, const JSString *p, int offset, int len) +{ + if (p->is_wide_char) { + memcpy(dst, p->u.str16 + offset, len * 2); + } else { + const uint8_t *src1 = p->u.str8 + offset; + int i; + + for(i = 0; i < len; i++) + dst[i] = src1[i]; + } +} + +static JSValue JS_ConcatString1(JSContext *ctx, + const JSString *p1, const JSString *p2) +{ + JSString *p; + uint32_t len; + int is_wide_char; + + len = p1->len + p2->len; + if (len > JS_STRING_LEN_MAX) + return JS_ThrowRangeError(ctx, "invalid string length"); + is_wide_char = p1->is_wide_char | p2->is_wide_char; + p = js_alloc_string(ctx, len, is_wide_char); + if (!p) + return JS_EXCEPTION; + if (!is_wide_char) { + memcpy(p->u.str8, p1->u.str8, p1->len); + memcpy(p->u.str8 + p1->len, p2->u.str8, p2->len); + p->u.str8[len] = '\0'; + } else { + copy_str16(p->u.str16, p1, 0, p1->len); + copy_str16(p->u.str16 + p1->len, p2, 0, p2->len); + } + return JS_MKPTR(JS_TAG_STRING, p); +} + +/* op1 and op2 are converted to strings. For convience, op1 or op2 = + JS_EXCEPTION are accepted and return JS_EXCEPTION. */ +static JSValue JS_ConcatString(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue ret; + JSString *p1, *p2; + + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_STRING)) { + op1 = JS_ToStringFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + return JS_EXCEPTION; + } + } + if (unlikely(JS_VALUE_GET_TAG(op2) != JS_TAG_STRING)) { + op2 = JS_ToStringFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + return JS_EXCEPTION; + } + } + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + + /* XXX: could also check if p1 is empty */ + if (p2->len == 0) { + goto ret_op1; + } + if (p1->header.ref_count == 1 && p1->is_wide_char == p2->is_wide_char + && js_malloc_usable_size(ctx, p1) >= sizeof(*p1) + ((p1->len + p2->len) << p2->is_wide_char) + 1 - p1->is_wide_char) { + /* Concatenate in place in available space at the end of p1 */ + if (p1->is_wide_char) { + memcpy(p1->u.str16 + p1->len, p2->u.str16, p2->len << 1); + p1->len += p2->len; + } else { + memcpy(p1->u.str8 + p1->len, p2->u.str8, p2->len); + p1->len += p2->len; + p1->u.str8[p1->len] = '\0'; + } + ret_op1: + JS_FreeValue(ctx, op2); + return op1; + } + ret = JS_ConcatString1(ctx, p1, p2); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return ret; +} + +/* Shape support */ + +static inline size_t get_shape_size(size_t hash_size, size_t prop_size) +{ + return hash_size * sizeof(uint32_t) + sizeof(JSShape) + + prop_size * sizeof(JSShapeProperty); +} + +static inline JSShape *get_shape_from_alloc(void *sh_alloc, size_t hash_size) +{ + return (JSShape *)(void *)((uint32_t *)sh_alloc + hash_size); +} + +static inline uint32_t *prop_hash_end(JSShape *sh) +{ + return (uint32_t *)sh; +} + +static inline void *get_alloc_from_shape(JSShape *sh) +{ + return prop_hash_end(sh) - ((intptr_t)sh->prop_hash_mask + 1); +} + +static inline JSShapeProperty *get_shape_prop(JSShape *sh) +{ + return sh->prop; +} + +static int init_shape_hash(JSRuntime *rt) +{ + rt->shape_hash_bits = 4; /* 16 shapes */ + rt->shape_hash_size = 1 << rt->shape_hash_bits; + rt->shape_hash_count = 0; + rt->shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + rt->shape_hash_size); + if (!rt->shape_hash) + return -1; + return 0; +} + +/* same magic hash multiplier as the Linux kernel */ +static uint32_t shape_hash(uint32_t h, uint32_t val) +{ + return (h + val) * 0x9e370001; +} + +/* truncate the shape hash to 'hash_bits' bits */ +static uint32_t get_shape_hash(uint32_t h, int hash_bits) +{ + return h >> (32 - hash_bits); +} + +static uint32_t shape_initial_hash(JSObject *proto) +{ + uint32_t h; + h = shape_hash(1, (uintptr_t)proto); + if (sizeof(proto) > 4) + h = shape_hash(h, (uint64_t)(uintptr_t)proto >> 32); + return h; +} + +static int resize_shape_hash(JSRuntime *rt, int new_shape_hash_bits) +{ + int new_shape_hash_size, i; + uint32_t h; + JSShape **new_shape_hash, *sh, *sh_next; + + new_shape_hash_size = 1 << new_shape_hash_bits; + new_shape_hash = js_mallocz_rt(rt, sizeof(rt->shape_hash[0]) * + new_shape_hash_size); + if (!new_shape_hash) + return -1; + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh_next) { + sh_next = sh->shape_hash_next; + h = get_shape_hash(sh->hash, new_shape_hash_bits); + sh->shape_hash_next = new_shape_hash[h]; + new_shape_hash[h] = sh; + } + } + js_free_rt(rt, rt->shape_hash); + rt->shape_hash_bits = new_shape_hash_bits; + rt->shape_hash_size = new_shape_hash_size; + rt->shape_hash = new_shape_hash; + return 0; +} + +static void js_shape_hash_link(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + sh->shape_hash_next = rt->shape_hash[h]; + rt->shape_hash[h] = sh; + rt->shape_hash_count++; +} + +static void js_shape_hash_unlink(JSRuntime *rt, JSShape *sh) +{ + uint32_t h; + JSShape **psh; + + h = get_shape_hash(sh->hash, rt->shape_hash_bits); + psh = &rt->shape_hash[h]; + while (*psh != sh) + psh = &(*psh)->shape_hash_next; + *psh = sh->shape_hash_next; + rt->shape_hash_count--; +} + +/* create a new empty shape with prototype 'proto' */ +static no_inline JSShape *js_new_shape2(JSContext *ctx, JSObject *proto, + int hash_size, int prop_size) +{ + JSRuntime *rt = ctx->rt; + void *sh_alloc; + JSShape *sh; + + /* resize the shape hash table if necessary */ + if (2 * (rt->shape_hash_count + 1) > rt->shape_hash_size) { + resize_shape_hash(rt, rt->shape_hash_bits + 1); + } + + sh_alloc = js_malloc(ctx, get_shape_size(hash_size, prop_size)); + if (!sh_alloc) + return NULL; + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + if (proto) + js_dup(JS_MKPTR(JS_TAG_OBJECT, proto)); + sh->proto = proto; + memset(prop_hash_end(sh) - hash_size, 0, sizeof(prop_hash_end(sh)[0]) * + hash_size); + sh->prop_hash_mask = hash_size - 1; + sh->prop_size = prop_size; + sh->prop_count = 0; + sh->deleted_prop_count = 0; + + /* insert in the hash table */ + sh->hash = shape_initial_hash(proto); + sh->is_hashed = TRUE; + sh->has_small_array_index = FALSE; + js_shape_hash_link(ctx->rt, sh); + return sh; +} + +static JSShape *js_new_shape(JSContext *ctx, JSObject *proto) +{ + return js_new_shape2(ctx, proto, JS_PROP_INITIAL_HASH_SIZE, + JS_PROP_INITIAL_SIZE); +} + +/* The shape is cloned. The new shape is not inserted in the shape + hash table */ +static JSShape *js_clone_shape(JSContext *ctx, JSShape *sh1) +{ + JSShape *sh; + void *sh_alloc, *sh_alloc1; + size_t size; + JSShapeProperty *pr; + uint32_t i, hash_size; + + hash_size = sh1->prop_hash_mask + 1; + size = get_shape_size(hash_size, sh1->prop_size); + sh_alloc = js_malloc(ctx, size); + if (!sh_alloc) + return NULL; + sh_alloc1 = get_alloc_from_shape(sh1); + memcpy(sh_alloc, sh_alloc1, size); + sh = get_shape_from_alloc(sh_alloc, hash_size); + sh->header.ref_count = 1; + add_gc_object(ctx->rt, &sh->header, JS_GC_OBJ_TYPE_SHAPE); + sh->is_hashed = FALSE; + if (sh->proto) { + js_dup(JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + JS_DupAtom(ctx, pr->atom); + } + return sh; +} + +static JSShape *js_dup_shape(JSShape *sh) +{ + sh->header.ref_count++; + return sh; +} + +static void js_free_shape0(JSRuntime *rt, JSShape *sh) +{ + uint32_t i; + JSShapeProperty *pr; + + assert(sh->header.ref_count == 0); + if (sh->is_hashed) + js_shape_hash_unlink(rt, sh); + if (sh->proto != NULL) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + } + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JS_FreeAtomRT(rt, pr->atom); + pr++; + } + remove_gc_object(&sh->header); + js_free_rt(rt, get_alloc_from_shape(sh)); +} + +static void js_free_shape(JSRuntime *rt, JSShape *sh) +{ + if (unlikely(--sh->header.ref_count <= 0)) { + js_free_shape0(rt, sh); + } +} + +static void js_free_shape_null(JSRuntime *rt, JSShape *sh) +{ + if (sh) + js_free_shape(rt, sh); +} + +/* make space to hold at least 'count' properties */ +static no_inline int resize_properties(JSContext *ctx, JSShape **psh, + JSObject *p, uint32_t count) +{ + JSShape *sh; + uint32_t new_size, new_hash_size, new_hash_mask, i; + JSShapeProperty *pr; + void *sh_alloc; + intptr_t h; + + sh = *psh; + new_size = max_int(count, sh->prop_size * 3 / 2); + /* Reallocate prop array first to avoid crash or size inconsistency + in case of memory allocation failure */ + if (p) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (unlikely(!new_prop)) + return -1; + p->prop = new_prop; + } + new_hash_size = sh->prop_hash_mask + 1; + while (new_hash_size < new_size) + new_hash_size = 2 * new_hash_size; + if (new_hash_size != (sh->prop_hash_mask + 1)) { + JSShape *old_sh; + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + /* copy all the fields and the properties */ + memcpy(sh, old_sh, + sizeof(JSShape) + sizeof(sh->prop[0]) * old_sh->prop_count); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + new_hash_mask = new_hash_size - 1; + sh->prop_hash_mask = new_hash_mask; + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + for(i = 0, pr = sh->prop; i < sh->prop_count; i++, pr++) { + if (pr->atom != JS_ATOM_NULL) { + h = ((uintptr_t)pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = i + 1; + } + } + js_free(ctx, get_alloc_from_shape(old_sh)); + } else { + /* only resize the properties */ + list_del(&sh->header.link); + sh_alloc = js_realloc(ctx, get_alloc_from_shape(sh), + get_shape_size(new_hash_size, new_size)); + if (unlikely(!sh_alloc)) { + /* insert again in the GC list */ + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + return -1; + } + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + } + *psh = sh; + sh->prop_size = new_size; + return 0; +} + +/* remove the deleted properties. */ +static int compact_properties(JSContext *ctx, JSObject *p) +{ + JSShape *sh, *old_sh; + void *sh_alloc; + intptr_t h; + uint32_t new_hash_size, i, j, new_hash_mask, new_size; + JSShapeProperty *old_pr, *pr; + JSProperty *prop, *new_prop; + + sh = p->shape; + assert(!sh->is_hashed); + + new_size = max_int(JS_PROP_INITIAL_SIZE, + sh->prop_count - sh->deleted_prop_count); + assert(new_size <= sh->prop_size); + + new_hash_size = sh->prop_hash_mask + 1; + while ((new_hash_size / 2) >= new_size) + new_hash_size = new_hash_size / 2; + new_hash_mask = new_hash_size - 1; + + /* resize the hash table and the properties */ + old_sh = sh; + sh_alloc = js_malloc(ctx, get_shape_size(new_hash_size, new_size)); + if (!sh_alloc) + return -1; + sh = get_shape_from_alloc(sh_alloc, new_hash_size); + list_del(&old_sh->header.link); + memcpy(sh, old_sh, sizeof(JSShape)); + list_add_tail(&sh->header.link, &ctx->rt->gc_obj_list); + + memset(prop_hash_end(sh) - new_hash_size, 0, + sizeof(prop_hash_end(sh)[0]) * new_hash_size); + + j = 0; + old_pr = old_sh->prop; + pr = sh->prop; + prop = p->prop; + for(i = 0; i < sh->prop_count; i++) { + if (old_pr->atom != JS_ATOM_NULL) { + pr->atom = old_pr->atom; + pr->flags = old_pr->flags; + h = ((uintptr_t)old_pr->atom & new_hash_mask); + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = j + 1; + prop[j] = prop[i]; + j++; + pr++; + } + old_pr++; + } + assert(j == (sh->prop_count - sh->deleted_prop_count)); + sh->prop_hash_mask = new_hash_mask; + sh->prop_size = new_size; + sh->deleted_prop_count = 0; + sh->prop_count = j; + + p->shape = sh; + js_free(ctx, get_alloc_from_shape(old_sh)); + + /* reduce the size of the object properties */ + new_prop = js_realloc(ctx, p->prop, sizeof(new_prop[0]) * new_size); + if (new_prop) + p->prop = new_prop; + return 0; +} + +static int add_shape_property(JSContext *ctx, JSShape **psh, + JSObject *p, JSAtom atom, int prop_flags) +{ + JSRuntime *rt = ctx->rt; + JSShape *sh = *psh; + JSShapeProperty *pr, *prop; + uint32_t hash_mask, new_shape_hash = 0; + intptr_t h; + + /* update the shape hash */ + if (sh->is_hashed) { + js_shape_hash_unlink(rt, sh); + new_shape_hash = shape_hash(shape_hash(sh->hash, atom), prop_flags); + } + + if (unlikely(sh->prop_count >= sh->prop_size)) { + if (resize_properties(ctx, psh, p, sh->prop_count + 1)) { + /* in case of error, reinsert in the hash table. + sh is still valid if resize_properties() failed */ + if (sh->is_hashed) + js_shape_hash_link(rt, sh); + return -1; + } + sh = *psh; + } + if (sh->is_hashed) { + sh->hash = new_shape_hash; + js_shape_hash_link(rt, sh); + } + /* Initialize the new shape property. + The object property at p->prop[sh->prop_count] is uninitialized */ + prop = get_shape_prop(sh); + pr = &prop[sh->prop_count++]; + pr->atom = JS_DupAtom(ctx, atom); + pr->flags = prop_flags; + sh->has_small_array_index |= __JS_AtomIsTaggedInt(atom); + /* add in hash table */ + hash_mask = sh->prop_hash_mask; + h = atom & hash_mask; + pr->hash_next = prop_hash_end(sh)[-h - 1]; + prop_hash_end(sh)[-h - 1] = sh->prop_count; + return 0; +} + +/* find a hashed empty shape matching the prototype. Return NULL if + not found */ +static JSShape *find_hashed_shape_proto(JSRuntime *rt, JSObject *proto) +{ + JSShape *sh1; + uint32_t h, h1; + + h = shape_initial_hash(proto); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + if (sh1->hash == h && + sh1->proto == proto && + sh1->prop_count == 0) { + return sh1; + } + } + return NULL; +} + +/* find a hashed shape matching sh + (prop, prop_flags). Return NULL if + not found */ +static JSShape *find_hashed_shape_prop(JSRuntime *rt, JSShape *sh, + JSAtom atom, int prop_flags) +{ + JSShape *sh1; + uint32_t h, h1, i, n; + + h = sh->hash; + h = shape_hash(h, atom); + h = shape_hash(h, prop_flags); + h1 = get_shape_hash(h, rt->shape_hash_bits); + for(sh1 = rt->shape_hash[h1]; sh1 != NULL; sh1 = sh1->shape_hash_next) { + /* we test the hash first so that the rest is done only if the + shapes really match */ + if (sh1->hash == h && + sh1->proto == sh->proto && + sh1->prop_count == ((n = sh->prop_count) + 1)) { + for(i = 0; i < n; i++) { + if (unlikely(sh1->prop[i].atom != sh->prop[i].atom) || + unlikely(sh1->prop[i].flags != sh->prop[i].flags)) + goto next; + } + if (unlikely(sh1->prop[n].atom != atom) || + unlikely(sh1->prop[n].flags != prop_flags)) + goto next; + return sh1; + } + next: ; + } + return NULL; +} + +static __maybe_unused void JS_DumpShape(JSRuntime *rt, int i, JSShape *sh) +{ + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + int j; + + /* XXX: should output readable class prototype */ + printf("%5d %3d%c %14p %5d %5d", i, + sh->header.ref_count, " *"[sh->is_hashed], + (void *)sh->proto, sh->prop_size, sh->prop_count); + for(j = 0; j < sh->prop_count; j++) { + printf(" %s", JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), + sh->prop[j].atom)); + } + printf("\n"); +} + +static __maybe_unused void JS_DumpShapes(JSRuntime *rt) +{ + int i; + JSShape *sh; + struct list_head *el; + JSObject *p; + JSGCObjectHeader *gp; + + printf("JSShapes: {\n"); + printf("%5s %4s %14s %5s %5s %s\n", "SLOT", "REFS", "PROTO", "SIZE", "COUNT", "PROPS"); + for(i = 0; i < rt->shape_hash_size; i++) { + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + JS_DumpShape(rt, i, sh); + assert(sh->is_hashed); + } + } + /* dump non-hashed shapes */ + list_for_each(el, &rt->gc_obj_list) { + gp = list_entry(el, JSGCObjectHeader, link); + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + if (!p->shape->is_hashed) { + JS_DumpShape(rt, -1, p->shape); + } + } + } + printf("}\n"); +} + +static JSValue JS_NewObjectFromShape(JSContext *ctx, JSShape *sh, JSClassID class_id) +{ + JSObject *p; + + js_trigger_gc(ctx->rt, sizeof(JSObject)); + p = js_malloc(ctx, sizeof(JSObject)); + if (unlikely(!p)) + goto fail; + p->class_id = class_id; + p->extensible = TRUE; + p->free_mark = 0; + p->is_exotic = 0; + p->fast_array = 0; + p->is_constructor = 0; + p->is_uncatchable_error = 0; + p->tmp_mark = 0; + p->is_HTMLDDA = 0; + p->first_weak_ref = NULL; + p->u.opaque = NULL; + p->shape = sh; + p->prop = js_malloc(ctx, sizeof(JSProperty) * sh->prop_size); + if (unlikely(!p->prop)) { + js_free(ctx, p); + fail: + js_free_shape(ctx->rt, sh); + return JS_EXCEPTION; + } + + switch(class_id) { + case JS_CLASS_OBJECT: + break; + case JS_CLASS_ARRAY: + { + JSProperty *pr; + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.values = NULL; + p->u.array.count = 0; + p->u.array.u1.size = 0; + /* the length property is always the first one */ + if (likely(sh == ctx->array_shape)) { + pr = &p->prop[0]; + } else { + /* only used for the first array */ + /* cannot fail */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_LENGTH); + } + pr->u.value = js_int32(0); + } + break; + case JS_CLASS_C_FUNCTION: + p->prop[0].u.value = JS_UNDEFINED; + break; + case JS_CLASS_ARGUMENTS: + 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_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + p->is_exotic = 1; + p->fast_array = 1; + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_DATAVIEW: + p->u.array.u.ptr = NULL; + p->u.array.count = 0; + break; + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + p->u.object_data = JS_UNDEFINED; + goto set_exotic; + case JS_CLASS_REGEXP: + p->u.regexp.pattern = NULL; + p->u.regexp.bytecode = NULL; + goto set_exotic; + default: + set_exotic: + if (ctx->rt->class_array[class_id].exotic) { + p->is_exotic = 1; + } + break; + } + p->header.ref_count = 1; + add_gc_object(ctx->rt, &p->header, JS_GC_OBJ_TYPE_JS_OBJECT); + return JS_MKPTR(JS_TAG_OBJECT, p); +} + +static JSObject *get_proto_obj(JSValue proto_val) +{ + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) + return NULL; + else + return JS_VALUE_GET_OBJ(proto_val); +} + +/* WARNING: proto must be an object or JS_NULL */ +JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto_val, + JSClassID class_id) +{ + JSShape *sh; + JSObject *proto; + + proto = get_proto_obj(proto_val); + sh = find_hashed_shape_proto(ctx->rt, proto); + if (likely(sh)) { + sh = js_dup_shape(sh); + } else { + sh = js_new_shape(ctx, proto); + if (!sh) + return JS_EXCEPTION; + } + return JS_NewObjectFromShape(ctx, sh, class_id); +} + +static int JS_SetObjectData(JSContext *ctx, JSValue obj, JSValue val) +{ + JSObject *p; + + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_SYMBOL: + case JS_CLASS_DATE: + case JS_CLASS_BIG_INT: + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = val; + return 0; + } + } + JS_FreeValue(ctx, val); + if (!JS_IsException(obj)) + JS_ThrowTypeError(ctx, "invalid object type"); + return -1; +} + +JSValue JS_NewObjectClass(JSContext *ctx, int class_id) +{ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[class_id], class_id); +} + +JSValue JS_NewObjectProto(JSContext *ctx, JSValue proto) +{ + return JS_NewObjectProtoClass(ctx, proto, JS_CLASS_OBJECT); +} + +JSValue JS_NewArray(JSContext *ctx) +{ + return JS_NewObjectFromShape(ctx, js_dup_shape(ctx->array_shape), + JS_CLASS_ARRAY); +} + +JSValue JS_NewObject(JSContext *ctx) +{ + /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ + return JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); +} + +static void js_function_set_properties(JSContext *ctx, JSValue func_obj, + JSAtom name, int len) +{ + /* ES6 feature non compatible with ES5.1: length is configurable */ + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, js_int32(len), + JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, + JS_AtomToString(ctx, name), JS_PROP_CONFIGURABLE); +} + +static BOOL js_class_has_bytecode(JSClassID class_id) +{ + return (class_id == JS_CLASS_BYTECODE_FUNCTION || + class_id == JS_CLASS_GENERATOR_FUNCTION || + class_id == JS_CLASS_ASYNC_FUNCTION || + class_id == JS_CLASS_ASYNC_GENERATOR_FUNCTION); +} + +/* return NULL without exception if not a function or no bytecode */ +static JSFunctionBytecode *JS_GetFunctionBytecode(JSValue val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(val); + if (!js_class_has_bytecode(p->class_id)) + return NULL; + return p->u.func.function_bytecode; +} + +static void js_method_set_home_object(JSContext *ctx, JSValue func_obj, + JSValue home_obj) +{ + JSObject *p, *p1; + JSFunctionBytecode *b; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(func_obj); + if (!js_class_has_bytecode(p->class_id)) + return; + b = p->u.func.function_bytecode; + if (b->need_home_object) { + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + if (JS_VALUE_GET_TAG(home_obj) == JS_TAG_OBJECT) + p1 = JS_VALUE_GET_OBJ(js_dup(home_obj)); + else + p1 = NULL; + p->u.func.home_object = p1; + } +} + +static JSValue js_get_function_name(JSContext *ctx, JSAtom name) +{ + JSValue name_str; + + name_str = JS_AtomToString(ctx, name); + if (JS_AtomSymbolHasDescription(ctx, name)) { + name_str = JS_ConcatString3(ctx, "[", name_str, "]"); + } + return name_str; +} + +/* Modify the name of a method according to the atom and + 'flags'. 'flags' is a bitmask of JS_PROP_HAS_GET and + JS_PROP_HAS_SET. Also set the home object of the method. + Return < 0 if exception. */ +static int js_method_set_properties(JSContext *ctx, JSValue func_obj, + JSAtom name, int flags, JSValue home_obj) +{ + JSValue name_str; + + name_str = js_get_function_name(ctx, name); + if (flags & JS_PROP_HAS_GET) { + name_str = JS_ConcatString3(ctx, "get ", name_str, ""); + } else if (flags & JS_PROP_HAS_SET) { + name_str = JS_ConcatString3(ctx, "set ", name_str, ""); + } + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name_str, + JS_PROP_CONFIGURABLE) < 0) + return -1; + js_method_set_home_object(ctx, func_obj, home_obj); + return 0; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +/* `name` may be NULL, pure ASCII or UTF-8 encoded */ +static JSValue JS_NewCFunction3(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic, + JSValue proto_val) +{ + JSValue func_obj; + JSObject *p; + JSAtom name_atom; + + func_obj = JS_NewObjectProtoClass(ctx, proto_val, JS_CLASS_C_FUNCTION); + if (JS_IsException(func_obj)) + return func_obj; + p = JS_VALUE_GET_OBJ(func_obj); + p->u.cfunc.realm = JS_DupContext(ctx); + p->u.cfunc.c_function.generic = func; + p->u.cfunc.length = length; + p->u.cfunc.cproto = cproto; + p->u.cfunc.magic = magic; + p->is_constructor = (cproto == JS_CFUNC_constructor || + cproto == JS_CFUNC_constructor_magic || + cproto == JS_CFUNC_constructor_or_func || + cproto == JS_CFUNC_constructor_or_func_magic); + if (!name) + name = ""; + name_atom = JS_NewAtom(ctx, name); + js_function_set_properties(ctx, func_obj, name_atom, length); + JS_FreeAtom(ctx, name_atom); + return func_obj; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic) +{ + return JS_NewCFunction3(ctx, func, name, length, cproto, magic, + ctx->function_proto); +} + +typedef struct JSCFunctionDataRecord { + JSCFunctionData *func; + uint8_t length; + uint8_t data_len; + uint16_t magic; + JSValue data[]; +} JSCFunctionDataRecord; + +static void js_c_function_data_finalizer(JSRuntime *rt, JSValue val) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_FreeValueRT(rt, s->data[i]); + } + js_free_rt(rt, s); + } +} + +static void js_c_function_data_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(val, JS_CLASS_C_FUNCTION_DATA); + int i; + + if (s) { + for(i = 0; i < s->data_len; i++) { + JS_MarkValue(rt, s->data[i], mark_func); + } + } +} + +static JSValue js_c_function_data_call(JSContext *ctx, JSValue func_obj, + JSValue this_val, + int argc, JSValue *argv, int flags) +{ + JSCFunctionDataRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_FUNCTION_DATA); + JSValue *arg_buf; + int i; + + /* XXX: could add the function on the stack for debug */ + if (unlikely(argc < s->length)) { + arg_buf = alloca(sizeof(arg_buf[0]) * s->length); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < s->length; i++) + arg_buf[i] = JS_UNDEFINED; + } else { + arg_buf = argv; + } + + return s->func(ctx, this_val, argc, arg_buf, s->magic, s->data); +} + +JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, + int length, int magic, int data_len, + JSValue *data) +{ + JSCFunctionDataRecord *s; + JSValue func_obj; + int i; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_C_FUNCTION_DATA); + if (JS_IsException(func_obj)) + return func_obj; + s = js_malloc(ctx, sizeof(*s) + data_len * sizeof(JSValue)); + if (!s) { + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; + } + s->func = func; + s->length = length; + s->data_len = data_len; + s->magic = magic; + for(i = 0; i < data_len; i++) + s->data[i] = js_dup(data[i]); + JS_SetOpaqueInternal(func_obj, s); + js_function_set_properties(ctx, func_obj, + JS_ATOM_empty_string, length); + return func_obj; +} + +static JSContext *js_autoinit_get_realm(JSProperty *pr) +{ + return (JSContext *)(pr->u.init.realm_and_id & ~3); +} + +static JSAutoInitIDEnum js_autoinit_get_id(JSProperty *pr) +{ + return pr->u.init.realm_and_id & 3; +} + +static void js_autoinit_free(JSRuntime *rt, JSProperty *pr) +{ + JS_FreeContext(js_autoinit_get_realm(pr)); +} + +static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr, + JS_MarkFunc *mark_func) +{ + mark_func(rt, &js_autoinit_get_realm(pr)->header); +} + +static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags) +{ + if (unlikely(prop_flags & JS_PROP_TMASK)) { + if ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(rt, pr->u.var_ref); + } else if ((prop_flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_free(rt, pr); + } + } else { + JS_FreeValueRT(rt, pr->u.value); + } +} + +static force_inline JSShapeProperty *find_own_property1(JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + return pr; + } + h = pr->hash_next; + } + return NULL; +} + +static force_inline JSShapeProperty *find_own_property(JSProperty **ppr, + JSObject *p, + JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *prop; + intptr_t h; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + *ppr = &p->prop[h - 1]; + /* the compiler should be able to assume that pr != NULL here */ + return pr; + } + h = pr->hash_next; + } + *ppr = NULL; + return NULL; +} + +static force_inline JSShapeProperty* find_own_property_ic(JSProperty** ppr, JSObject* p, + JSAtom atom, uint32_t* offset) +{ + JSShape* sh; + JSShapeProperty *pr, *prop; + intptr_t h, i; + sh = p->shape; + h = (uintptr_t)atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h - 1]; + prop = get_shape_prop(sh); + while (h) { + i = h - 1; + pr = &prop[i]; + if (likely(pr->atom == atom)) { + *ppr = &p->prop[i]; + *offset = i; + /* the compiler should be able to assume that pr != NULL here */ + return pr; + } + h = pr->hash_next; + } + *ppr = NULL; + return NULL; +} + +/* indicate that the object may be part of a function prototype cycle */ +static void set_cycle_flag(JSContext *ctx, JSValue obj) +{ +} + +static void free_var_ref(JSRuntime *rt, JSVarRef *var_ref) +{ + if (var_ref) { + assert(var_ref->header.ref_count > 0); + if (--var_ref->header.ref_count == 0) { + if (var_ref->is_detached) { + JS_FreeValueRT(rt, var_ref->value); + remove_gc_object(&var_ref->header); + } else { + list_del(&var_ref->header.link); /* still on the stack */ + } + js_free_rt(rt, var_ref); + } + } +} + +static void js_array_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_FreeValueRT(rt, p->u.array.u.values[i]); + } + js_free_rt(rt, p->u.array.u.values); +} + +static void js_array_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + int i; + + for(i = 0; i < p->u.array.count; i++) { + JS_MarkValue(rt, p->u.array.u.values[i], mark_func); + } +} + +static void js_object_data_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_FreeValueRT(rt, p->u.object_data); + p->u.object_data = JS_UNDEFINED; +} + +static void js_object_data_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JS_MarkValue(rt, p->u.object_data, mark_func); +} + +static void js_c_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + JS_FreeContext(p->u.cfunc.realm); +} + +static void js_c_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + + if (p->u.cfunc.realm) + mark_func(rt, &p->u.cfunc.realm->header); +} + +static void js_bytecode_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p1, *p = JS_VALUE_GET_OBJ(val); + JSFunctionBytecode *b; + JSVarRef **var_refs; + int i; + + p1 = p->u.func.home_object; + if (p1) { + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, p1)); + } + b = p->u.func.function_bytecode; + if (b) { + var_refs = p->u.func.var_refs; + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) + free_var_ref(rt, var_refs[i]); + js_free_rt(rt, var_refs); + } + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b)); + } +} + +static void js_bytecode_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSVarRef **var_refs = p->u.func.var_refs; + JSFunctionBytecode *b = p->u.func.function_bytecode; + int i; + + if (p->u.func.home_object) { + JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object), + mark_func); + } + if (b) { + if (var_refs) { + for(i = 0; i < b->closure_var_count; i++) { + JSVarRef *var_ref = var_refs[i]; + if (var_ref && var_ref->is_detached) { + mark_func(rt, &var_ref->header); + } + } + } + /* must mark the function bytecode because template objects may be + part of a cycle */ + JS_MarkValue(rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b), mark_func); + } +} + +static void js_bound_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_FreeValueRT(rt, bf->func_obj); + JS_FreeValueRT(rt, bf->this_val); + for(i = 0; i < bf->argc; i++) { + JS_FreeValueRT(rt, bf->argv[i]); + } + js_free_rt(rt, bf); +} + +static void js_bound_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSBoundFunction *bf = p->u.bound_function; + int i; + + JS_MarkValue(rt, bf->func_obj, mark_func); + JS_MarkValue(rt, bf->this_val, mark_func); + for(i = 0; i < bf->argc; i++) + JS_MarkValue(rt, bf->argv[i], mark_func); +} + +static void js_for_in_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_FreeValueRT(rt, it->obj); + js_free_rt(rt, it); +} + +static void js_for_in_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSForInIterator *it = p->u.for_in_iterator; + JS_MarkValue(rt, it->obj, mark_func); +} + +static void free_object(JSRuntime *rt, JSObject *p) +{ + int i; + JSClassFinalizer *finalizer; + JSShape *sh; + JSShapeProperty *pr; + + p->free_mark = 1; /* used to tell the object is invalid when + freeing cycles */ + /* free all the fields */ + sh = p->shape; + pr = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + free_property(rt, &p->prop[i], pr->flags); + pr++; + } + js_free_rt(rt, p->prop); + /* as an optimization we destroy the shape immediately without + putting it in gc_zero_ref_count_list */ + js_free_shape(rt, sh); + + /* fail safe */ + p->shape = NULL; + p->prop = NULL; + + if (unlikely(p->first_weak_ref)) { + reset_weak_ref(rt, &p->first_weak_ref); + } + + finalizer = rt->class_array[p->class_id].finalizer; + if (finalizer) + (*finalizer)(rt, JS_MKPTR(JS_TAG_OBJECT, p)); + + /* fail safe */ + p->class_id = 0; + p->u.opaque = NULL; + p->u.func.var_refs = NULL; + p->u.func.home_object = NULL; + + remove_gc_object(&p->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && p->header.ref_count != 0) { + list_add_tail(&p->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, p); + } +} + +static void free_gc_object(JSRuntime *rt, JSGCObjectHeader *gp) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + free_object(rt, (JSObject *)gp); + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + free_function_bytecode(rt, (JSFunctionBytecode *)gp); + break; + default: + abort(); + } +} + +/* Check if object has a can_destroy hook. */ +static int gc_has_can_destroy_hook(JSRuntime *rt, JSGCObjectHeader *p) +{ + JSObject *obj; + + if (p->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) + return 0; + obj = (JSObject *)p; + return rt->class_array[obj->class_id].can_destroy != NULL; +} + +/* User-defined override for object destruction. */ +static int gc_can_destroy(JSRuntime *rt, JSGCObjectHeader *p) +{ + JSClassCanDestroy *can_destroy; + JSObject *obj; + + obj = (JSObject *)p; + can_destroy = rt->class_array[obj->class_id].can_destroy; + if (!((*can_destroy)(rt, JS_MKPTR(JS_TAG_OBJECT, obj)))) + return 0; + return 1; +} + +static void free_zero_refcount(JSRuntime *rt) +{ + struct list_head *el; + JSGCObjectHeader *p; + + rt->gc_phase = JS_GC_PHASE_DECREF; + for(;;) { + el = rt->gc_zero_ref_count_list.next; + if (el == &rt->gc_zero_ref_count_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count == 0); + free_gc_object(rt, p); + } + rt->gc_phase = JS_GC_PHASE_NONE; +} + +/* called with the ref_count of 'v' reaches zero. */ +static void js_free_value_rt(JSRuntime *rt, JSValue v) +{ + uint32_t tag = JS_VALUE_GET_TAG(v); + +#ifdef DUMP_FREE + if (check_dump_flag(rt, DUMP_FREE)) { + /* Prevent invalid object access during GC */ + if ((rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) + || (tag != JS_TAG_OBJECT && tag != JS_TAG_FUNCTION_BYTECODE)) { + printf("Freeing "); + if (tag == JS_TAG_OBJECT) { + JS_DumpObject(rt, JS_VALUE_GET_OBJ(v)); + } else { + JS_DumpValue(rt, v); + printf("\n"); + } + } + } +#endif + + switch(tag) { + case JS_TAG_STRING: + { + JSString *p = JS_VALUE_GET_STRING(v); + if (p->atom_type) { + JS_FreeAtomStruct(rt, p); + } else { +#ifdef DUMP_LEAKS + list_del(&p->link); +#endif + js_free_rt(rt, p); + } + } + break; + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + { + JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); + if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { + if (gc_has_can_destroy_hook(rt, p) && !gc_can_destroy(rt, p)) { + p->ref_count++; + break; + } + list_del(&p->link); + list_add(&p->link, &rt->gc_zero_ref_count_list); + if (rt->gc_phase == JS_GC_PHASE_NONE) { + free_zero_refcount(rt); + } + } + } + break; + case JS_TAG_MODULE: + abort(); /* never freed here */ + break; + case JS_TAG_BIG_INT: + { + JSBigInt *bf = JS_VALUE_GET_PTR(v); + bf_delete(&bf->num); + js_free_rt(rt, bf); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(v); + JS_FreeAtomStruct(rt, p); + } + break; + default: + printf("js_free_value_rt: unknown tag=%d\n", tag); + abort(); + } +} + +void JS_FreeValueRT(JSRuntime *rt, JSValue v) +{ + if (JS_VALUE_HAS_REF_COUNT(v)) { + JSRefCountHeader *p = (JSRefCountHeader *)JS_VALUE_GET_PTR(v); + if (--p->ref_count <= 0) { + js_free_value_rt(rt, v); + } + } +} + +void JS_FreeValue(JSContext *ctx, JSValue v) +{ + JS_FreeValueRT(ctx->rt, v); +} + +/* garbage collection */ + +static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h, + JSGCObjectTypeEnum type) +{ + h->mark = 0; + h->gc_obj_type = type; + list_add_tail(&h->link, &rt->gc_obj_list); +} + +static void remove_gc_object(JSGCObjectHeader *h) +{ + list_del(&h->link); +} + +void JS_MarkValue(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) +{ + if (JS_VALUE_HAS_REF_COUNT(val)) { + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + case JS_TAG_FUNCTION_BYTECODE: + mark_func(rt, JS_VALUE_GET_PTR(val)); + break; + default: + break; + } + } +} + +static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, + JS_MarkFunc *mark_func) +{ + switch(gp->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + { + JSObject *p = (JSObject *)gp; + JSShapeProperty *prs; + JSShape *sh; + int i; + sh = p->shape; + mark_func(rt, &sh->header); + /* mark all the fields */ + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL) { + if (prs->flags & JS_PROP_TMASK) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (pr->u.getset.getter) + mark_func(rt, &pr->u.getset.getter->header); + if (pr->u.getset.setter) + mark_func(rt, &pr->u.getset.setter->header); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (pr->u.var_ref->is_detached) { + /* Note: the tag does not matter + provided it is a GC object */ + mark_func(rt, &pr->u.var_ref->header); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + js_autoinit_mark(rt, pr, mark_func); + } + } else { + JS_MarkValue(rt, pr->u.value, mark_func); + } + } + prs++; + } + + if (p->class_id != JS_CLASS_OBJECT) { + JSClassGCMark *gc_mark; + gc_mark = rt->class_array[p->class_id].gc_mark; + if (gc_mark) + gc_mark(rt, JS_MKPTR(JS_TAG_OBJECT, p), mark_func); + } + } + break; + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + /* the template objects can be part of a cycle */ + { + JSShape **shape, *(*shapes)[IC_CACHE_ITEM_CAPACITY]; + JSFunctionBytecode *b = (JSFunctionBytecode *)gp; + int i; + for(i = 0; i < b->cpool_count; i++) { + JS_MarkValue(rt, b->cpool[i], mark_func); + } + if (b->realm) + mark_func(rt, &b->realm->header); + if (b->ic) { + for (i = 0; i < b->ic->count; i++) { + shapes = &b->ic->cache[i].shape; + for (shape = *shapes; shape != endof(*shapes); shape++) + if (*shape) + mark_func(rt, &(*shape)->header); + } + } + } + break; + case JS_GC_OBJ_TYPE_VAR_REF: + { + JSVarRef *var_ref = (JSVarRef *)gp; + /* only detached variable referenced are taken into account */ + assert(var_ref->is_detached); + JS_MarkValue(rt, *var_ref->pvalue, mark_func); + } + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + { + JSAsyncFunctionData *s = (JSAsyncFunctionData *)gp; + if (s->is_active) + async_func_mark(rt, &s->func_state, mark_func); + JS_MarkValue(rt, s->resolving_funcs[0], mark_func); + JS_MarkValue(rt, s->resolving_funcs[1], mark_func); + } + break; + case JS_GC_OBJ_TYPE_SHAPE: + { + JSShape *sh = (JSShape *)gp; + if (sh->proto != NULL) { + mark_func(rt, &sh->proto->header); + } + } + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + { + JSContext *ctx = (JSContext *)gp; + JS_MarkContext(rt, ctx, mark_func); + } + break; + default: + abort(); + } +} + +static void gc_decref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + assert(p->ref_count > 0); + p->ref_count--; + if (p->ref_count == 0 && p->mark == 1) { + list_del(&p->link); + if (gc_has_can_destroy_hook(rt, p)) + list_add_tail(&p->link, &rt->tmp_hook_obj_list); + else + list_add_tail(&p->link, &rt->tmp_obj_list); + } +} + +static void gc_decref(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; + + init_list_head(&rt->tmp_obj_list); + init_list_head(&rt->tmp_hook_obj_list); + + /* decrement the refcount of all the children of all the GC + objects and move the GC objects with zero refcount to + tmp_obj_list */ + list_for_each_safe(el, el1, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->mark == 0); + mark_children(rt, p, gc_decref_child); + p->mark = 1; + if (p->ref_count == 0) { + list_del(&p->link); + if (gc_has_can_destroy_hook(rt, p)) + list_add_tail(&p->link, &rt->tmp_hook_obj_list); + else + list_add_tail(&p->link, &rt->tmp_obj_list); + } + } +} + +static void gc_scan_incref_child(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; + if (p->ref_count == 1) { + /* ref_count was 0: remove from tmp_obj_list and add at the + end of gc_obj_list */ + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_obj_list); + p->mark = 0; /* reset the mark for the next GC call */ + } +} + +static void gc_scan_incref_child2(JSRuntime *rt, JSGCObjectHeader *p) +{ + p->ref_count++; +} + +static void gc_scan(JSRuntime *rt) +{ + struct list_head *el, *el1, *gc_tail; + JSGCObjectHeader *p; + int redo; + + /* keep the objects with a refcount > 0 and their children. */ + list_for_each(el, &rt->gc_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count > 0); + p->mark = 0; /* reset the mark for the next GC call */ + mark_children(rt, p, gc_scan_incref_child); + } + + /* restore objects whose can_destroy hook returns 0 and their children. */ + do { + /* save previous tail position of gc_obj_list */ + gc_tail = rt->gc_obj_list.prev; + redo = 0; + list_for_each_safe(el, el1, &rt->tmp_hook_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + list_del(&p->link); + /* gc_has_can_destroy_hook is the condition for objects to be + placed in tmp_hook_obj_list, so it is true here. */ + if (gc_can_destroy(rt, p)) { + /* object can be destroyed; move to tmp_obj_list. */ + list_add_tail(&p->link, &rt->tmp_obj_list); + } else { + /* hook says we cannot destroy yet; move back to gc_obj_list. */ + p->ref_count++; + list_add_tail(&p->link, &rt->gc_obj_list); + redo = 1; + break; + } + } + /* if redo, restore object and all its descendants. + Note: we must do this outside the previous loop, because el/el1 + might get moved into gc_obj_list here. */ + for (el = gc_tail->next; el != &rt->gc_obj_list; el = el->next) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->ref_count > 0); + p->mark = 0; /* reset the mark for the next GC call */ + mark_children(rt, p, gc_scan_incref_child); + } + } while(redo); + + /* restore the refcount of the objects to be deleted. */ + list_for_each(el, &rt->tmp_obj_list) { + p = list_entry(el, JSGCObjectHeader, link); + mark_children(rt, p, gc_scan_incref_child2); + } +} + +static void gc_free_cycles(JSRuntime *rt) +{ + struct list_head *el, *el1; + JSGCObjectHeader *p; +#ifdef DUMP_GC_FREE + BOOL header_done = FALSE; +#endif + + rt->gc_phase = JS_GC_PHASE_REMOVE_CYCLES; + + for(;;) { + el = rt->tmp_obj_list.next; + if (el == &rt->tmp_obj_list) + break; + p = list_entry(el, JSGCObjectHeader, link); + /* Only need to free the GC object associated with JS + values. The rest will be automatically removed because they + must be referenced by them. */ + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_JS_OBJECT: + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: +#ifdef DUMP_GC_FREE + if (check_dump_flag(rt, DUMP_GC_FREE)) { + if (!header_done) { + printf("Freeing cycles:\n"); + JS_DumpObjectHeader(rt); + header_done = TRUE; + } + JS_DumpGCObject(rt, p); + } +#endif + free_gc_object(rt, p); + break; + default: + list_del(&p->link); + list_add_tail(&p->link, &rt->gc_zero_ref_count_list); + break; + } + } + rt->gc_phase = JS_GC_PHASE_NONE; + + list_for_each_safe(el, el1, &rt->gc_zero_ref_count_list) { + p = list_entry(el, JSGCObjectHeader, link); + assert(p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT || + p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); + js_free_rt(rt, p); + } + + init_list_head(&rt->gc_zero_ref_count_list); +} + +void JS_RunGC(JSRuntime *rt) +{ + /* decrement the reference of the children of each object. mark = + 1 after this pass. */ + gc_decref(rt); + + /* keep the GC objects with a non zero refcount and their childs */ + gc_scan(rt); + + /* free the GC objects in a cycle */ + gc_free_cycles(rt); +} + +/* Return false if not an object or if the object has already been + freed (zombie objects are visible in finalizers when freeing + cycles). */ +BOOL JS_IsLiveObject(JSRuntime *rt, JSValue obj) +{ + JSObject *p; + if (!JS_IsObject(obj)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return !p->free_mark; +} + +/* Compute memory used by various object types */ +/* XXX: poor man's approach to handling multiply referenced objects */ +typedef struct JSMemoryUsage_helper { + double memory_used_count; + double str_count; + double str_size; + int64_t js_func_count; + double js_func_size; + int64_t js_func_code_size; + int64_t js_func_pc2line_count; + int64_t js_func_pc2line_size; +} JSMemoryUsage_helper; + +static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp); + +static void compute_jsstring_size(JSString *str, JSMemoryUsage_helper *hp) +{ + if (!str->atom_type) { /* atoms are handled separately */ + double s_ref_count = str->header.ref_count; + hp->str_count += 1 / s_ref_count; + hp->str_size += ((sizeof(*str) + (str->len << str->is_wide_char) + + 1 - str->is_wide_char) / s_ref_count); + } +} + +static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *hp) +{ + int memory_used_count, js_func_size, i; + + memory_used_count = 0; + js_func_size = sizeof(*b); + if (b->vardefs) { + js_func_size += (b->arg_count + b->var_count) * sizeof(*b->vardefs); + } + if (b->cpool) { + js_func_size += b->cpool_count * sizeof(*b->cpool); + for (i = 0; i < b->cpool_count; i++) { + JSValue val = b->cpool[i]; + compute_value_size(val, hp); + } + } + if (b->closure_var) { + js_func_size += b->closure_var_count * sizeof(*b->closure_var); + } + if (b->byte_code_buf) { + hp->js_func_code_size += b->byte_code_len; + } + memory_used_count++; + js_func_size += b->source_len + 1; + if (b->pc2line_len) { + memory_used_count++; + hp->js_func_pc2line_count += 1; + hp->js_func_pc2line_size += b->pc2line_len; + } + hp->js_func_size += js_func_size; + hp->js_func_count += 1; + hp->memory_used_count += memory_used_count; +} + +static void compute_value_size(JSValue val, JSMemoryUsage_helper *hp) +{ + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_STRING: + compute_jsstring_size(JS_VALUE_GET_STRING(val), hp); + break; + case JS_TAG_BIG_INT: + /* should track JSBigInt usage */ + break; + } +} + +void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s) +{ + struct list_head *el, *el1; + int i; + JSMemoryUsage_helper mem = { 0 }, *hp = &mem; + + memset(s, 0, sizeof(*s)); + s->malloc_count = rt->malloc_state.malloc_count; + s->malloc_size = rt->malloc_state.malloc_size; + s->malloc_limit = rt->malloc_state.malloc_limit; + + s->memory_used_count = 2; /* rt + rt->class_array */ + s->memory_used_size = sizeof(JSRuntime) + sizeof(JSClass) * rt->class_count; + + list_for_each(el, &rt->context_list) { + JSContext *ctx = list_entry(el, JSContext, link); + JSShape *sh = ctx->array_shape; + s->memory_used_count += 2; /* ctx + ctx->class_proto */ + s->memory_used_size += sizeof(JSContext) + + sizeof(JSValue) * rt->class_count; + s->binary_object_count += ctx->binary_object_count; + s->binary_object_size += ctx->binary_object_size; + + /* the hashed shapes are counted separately */ + if (sh && !sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + list_for_each(el1, &ctx->loaded_modules) { + JSModuleDef *m = list_entry(el1, JSModuleDef, link); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*m); + if (m->req_module_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->req_module_entries_count * sizeof(*m->req_module_entries); + } + if (m->export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->export_entries_count * sizeof(*m->export_entries); + for (i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL && me->u.local.var_ref) { + /* potential multiple count */ + s->memory_used_count += 1; + compute_value_size(me->u.local.var_ref->value, hp); + } + } + } + if (m->star_export_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->star_export_entries_count * sizeof(*m->star_export_entries); + } + if (m->import_entries) { + s->memory_used_count += 1; + s->memory_used_size += m->import_entries_count * sizeof(*m->import_entries); + } + compute_value_size(m->module_ns, hp); + compute_value_size(m->func_obj, hp); + } + } + + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + JSShape *sh; + JSShapeProperty *prs; + + /* XXX: could count the other GC object types too */ + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + compute_bytecode_size((JSFunctionBytecode *)gp, hp); + continue; + } else if (gp->gc_obj_type != JS_GC_OBJ_TYPE_JS_OBJECT) { + continue; + } + p = (JSObject *)gp; + sh = p->shape; + s->obj_count++; + if (p->prop) { + s->memory_used_count++; + s->prop_size += sh->prop_size * sizeof(*p->prop); + s->prop_count += sh->prop_count; + prs = get_shape_prop(sh); + for(i = 0; i < sh->prop_count; i++) { + JSProperty *pr = &p->prop[i]; + if (prs->atom != JS_ATOM_NULL && !(prs->flags & JS_PROP_TMASK)) { + compute_value_size(pr->u.value, hp); + } + prs++; + } + } + /* the hashed shapes are counted separately */ + if (!sh->is_hashed) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + + switch(p->class_id) { + case JS_CLASS_ARRAY: /* u.array | length */ + case JS_CLASS_ARGUMENTS: /* u.array | length */ + s->array_count++; + if (p->fast_array) { + s->fast_array_count++; + if (p->u.array.u.values) { + s->memory_used_count++; + s->memory_used_size += p->u.array.count * + sizeof(*p->u.array.u.values); + s->fast_array_elements += p->u.array.count; + for (i = 0; i < p->u.array.count; i++) { + compute_value_size(p->u.array.u.values[i], hp); + } + } + } + break; + case JS_CLASS_NUMBER: /* u.object_data */ + case JS_CLASS_STRING: /* u.object_data */ + case JS_CLASS_BOOLEAN: /* u.object_data */ + case JS_CLASS_SYMBOL: /* u.object_data */ + case JS_CLASS_DATE: /* u.object_data */ + case JS_CLASS_BIG_INT: /* u.object_data */ + compute_value_size(p->u.object_data, hp); + break; + case JS_CLASS_C_FUNCTION: /* u.cfunc */ + s->c_func_count++; + break; + case JS_CLASS_BYTECODE_FUNCTION: /* u.func */ + { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs = p->u.func.var_refs; + /* home_object: object will be accounted for in list scan */ + if (var_refs) { + s->memory_used_count++; + s->js_func_size += b->closure_var_count * sizeof(*var_refs); + for (i = 0; i < b->closure_var_count; i++) { + if (var_refs[i]) { + double ref_count = var_refs[i]->header.ref_count; + s->memory_used_count += 1 / ref_count; + s->js_func_size += sizeof(*var_refs[i]) / ref_count; + /* handle non object closed values */ + if (var_refs[i]->pvalue == &var_refs[i]->value) { + /* potential multiple count */ + compute_value_size(var_refs[i]->value, hp); + } + } + } + } + } + break; + case JS_CLASS_BOUND_FUNCTION: /* u.bound_function */ + { + JSBoundFunction *bf = p->u.bound_function; + /* func_obj and this_val are objects */ + for (i = 0; i < bf->argc; i++) { + compute_value_size(bf->argv[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*bf) + bf->argc * sizeof(*bf->argv); + } + break; + case JS_CLASS_C_FUNCTION_DATA: /* u.c_function_data_record */ + { + JSCFunctionDataRecord *fd = p->u.c_function_data_record; + if (fd) { + for (i = 0; i < fd->data_len; i++) { + compute_value_size(fd->data[i], hp); + } + s->memory_used_count += 1; + s->memory_used_size += sizeof(*fd) + fd->data_len * sizeof(*fd->data); + } + } + break; + case JS_CLASS_REGEXP: /* u.regexp */ + compute_jsstring_size(p->u.regexp.pattern, hp); + compute_jsstring_size(p->u.regexp.bytecode, hp); + break; + + case JS_CLASS_FOR_IN_ITERATOR: /* u.for_in_iterator */ + { + JSForInIterator *it = p->u.for_in_iterator; + if (it) { + compute_value_size(it->obj, hp); + s->memory_used_count += 1; + s->memory_used_size += sizeof(*it); + } + } + break; + case JS_CLASS_ARRAY_BUFFER: /* u.array_buffer */ + case JS_CLASS_SHARED_ARRAY_BUFFER: /* u.array_buffer */ + { + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf) { + s->memory_used_count += 1; + s->memory_used_size += sizeof(*abuf); + if (abuf->data) { + s->memory_used_count += 1; + s->memory_used_size += abuf->byte_length; + } + } + } + break; + case JS_CLASS_GENERATOR: /* u.generator_data */ + case JS_CLASS_UINT8C_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT8_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_INT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_UINT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_INT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_BIG_UINT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT16_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT32_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_FLOAT64_ARRAY: /* u.typed_array / u.array */ + case JS_CLASS_DATAVIEW: /* u.typed_array */ + case JS_CLASS_MAP: /* u.map_state */ + case JS_CLASS_SET: /* u.map_state */ + case JS_CLASS_WEAKMAP: /* u.map_state */ + case JS_CLASS_WEAKSET: /* u.map_state */ + case JS_CLASS_MAP_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_SET_ITERATOR: /* u.map_iterator_data */ + case JS_CLASS_ARRAY_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_STRING_ITERATOR: /* u.array_iterator_data */ + case JS_CLASS_PROXY: /* u.proxy_data */ + case JS_CLASS_PROMISE: /* u.promise_data */ + case JS_CLASS_PROMISE_RESOLVE_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_PROMISE_REJECT_FUNCTION: /* u.promise_function_data */ + case JS_CLASS_ASYNC_FUNCTION_RESOLVE: /* u.async_function_data */ + case JS_CLASS_ASYNC_FUNCTION_REJECT: /* u.async_function_data */ + case JS_CLASS_ASYNC_FROM_SYNC_ITERATOR: /* u.async_from_sync_iterator_data */ + case JS_CLASS_ASYNC_GENERATOR: /* u.async_generator_data */ + /* TODO */ + default: + /* XXX: class definition should have an opaque block size */ + if (p->u.opaque) { + s->memory_used_count += 1; + } + break; + } + } + s->obj_size += s->obj_count * sizeof(JSObject); + + /* hashed shapes */ + s->memory_used_count++; /* rt->shape_hash */ + s->memory_used_size += sizeof(rt->shape_hash[0]) * rt->shape_hash_size; + for(i = 0; i < rt->shape_hash_size; i++) { + JSShape *sh; + for(sh = rt->shape_hash[i]; sh != NULL; sh = sh->shape_hash_next) { + int hash_size = sh->prop_hash_mask + 1; + s->shape_count++; + s->shape_size += get_shape_size(hash_size, sh->prop_size); + } + } + + /* atoms */ + s->memory_used_count += 2; /* rt->atom_array, rt->atom_hash */ + s->atom_count = rt->atom_count; + s->atom_size = sizeof(rt->atom_array[0]) * rt->atom_size + + sizeof(rt->atom_hash[0]) * rt->atom_hash_size; + for(i = 0; i < rt->atom_size; i++) { + JSAtomStruct *p = rt->atom_array[i]; + if (!atom_is_free(p)) { + s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) + + 1 - p->is_wide_char); + } + } + s->str_count = round(mem.str_count); + s->str_size = round(mem.str_size); + s->js_func_count = mem.js_func_count; + s->js_func_size = round(mem.js_func_size); + s->js_func_code_size = mem.js_func_code_size; + s->js_func_pc2line_count = mem.js_func_pc2line_count; + s->js_func_pc2line_size = mem.js_func_pc2line_size; + s->memory_used_count += round(mem.memory_used_count) + + s->atom_count + s->str_count + + s->obj_count + s->shape_count + + s->js_func_count + s->js_func_pc2line_count; + s->memory_used_size += s->atom_size + s->str_size + + s->obj_size + s->prop_size + s->shape_size + + s->js_func_size + s->js_func_code_size + s->js_func_pc2line_size; +} + +void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) +{ + fprintf(fp, "QuickJS-ng memory usage -- %s version, %d-bit, %s Endian, malloc limit: %"PRId64"\n\n", + JS_GetVersion(), (int)sizeof(void *) * 8, is_be() ? "Big" : "Little", s->malloc_limit); + if (rt) { + static const struct { + const char *name; + size_t size; + } object_types[] = { + { "JSRuntime", sizeof(JSRuntime) }, + { "JSContext", sizeof(JSContext) }, + { "JSObject", sizeof(JSObject) }, + { "JSString", sizeof(JSString) }, + { "JSFunctionBytecode", sizeof(JSFunctionBytecode) }, + }; + int i, usage_size_ok = 0; + for(i = 0; i < countof(object_types); i++) { + unsigned int size = object_types[i].size; + void *p = js_malloc_rt(rt, size); + if (p) { + unsigned int size1 = js_malloc_usable_size_rt(rt, p); + if (size1 >= size) { + usage_size_ok = 1; + fprintf(fp, " %3u + %-2u %s\n", + size, size1 - size, object_types[i].name); + } + js_free_rt(rt, p); + } + } + if (!usage_size_ok) { + fprintf(fp, " malloc_usable_size unavailable\n"); + } + { + int obj_classes[JS_CLASS_INIT_COUNT + 1] = { 0 }; + int class_id; + struct list_head *el; + list_for_each(el, &rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + JSObject *p; + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + p = (JSObject *)gp; + obj_classes[min_uint32(p->class_id, JS_CLASS_INIT_COUNT)]++; + } + } + fprintf(fp, "\n" "JSObject classes\n"); + if (obj_classes[0]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[0], 0, "none"); + for (class_id = 1; class_id < JS_CLASS_INIT_COUNT; class_id++) { + if (obj_classes[class_id]) { + char buf[ATOM_GET_STR_BUF_SIZE]; + fprintf(fp, " %5d %2.0d %s\n", obj_classes[class_id], class_id, + JS_AtomGetStrRT(rt, buf, sizeof(buf), js_std_class_def[class_id - 1].class_name)); + } + } + if (obj_classes[JS_CLASS_INIT_COUNT]) + fprintf(fp, " %5d %2.0d %s\n", obj_classes[JS_CLASS_INIT_COUNT], 0, "other"); + } + fprintf(fp, "\n"); + } + fprintf(fp, "%-20s %8s %8s\n", "NAME", "COUNT", "SIZE"); + + if (s->malloc_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per block)\n", + "memory allocated", s->malloc_count, s->malloc_size, + (double)s->malloc_size / s->malloc_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%d overhead, %0.1f average slack)\n", + "memory used", s->memory_used_count, s->memory_used_size, + MALLOC_OVERHEAD, ((double)(s->malloc_size - s->memory_used_size) / + s->memory_used_count)); + } + if (s->atom_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per atom)\n", + "atoms", s->atom_count, s->atom_size, + (double)s->atom_size / s->atom_count); + } + if (s->str_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per string)\n", + "strings", s->str_count, s->str_size, + (double)s->str_size / s->str_count); + } + if (s->obj_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + "objects", s->obj_count, s->obj_size, + (double)s->obj_size / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per object)\n", + " properties", s->prop_count, s->prop_size, + (double)s->prop_count / s->obj_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per shape)\n", + " shapes", s->shape_count, s->shape_size, + (double)s->shape_size / s->shape_count); + } + if (s->js_func_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "bytecode functions", s->js_func_count, s->js_func_size); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " bytecode", s->js_func_count, s->js_func_code_size, + (double)s->js_func_code_size / s->js_func_count); + if (s->js_func_pc2line_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per function)\n", + " pc2line", s->js_func_pc2line_count, + s->js_func_pc2line_size, + (double)s->js_func_pc2line_size / s->js_func_pc2line_count); + } + } + if (s->c_func_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "C functions", s->c_func_count); + } + if (s->array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", "arrays", s->array_count); + if (s->fast_array_count) { + fprintf(fp, "%-20s %8"PRId64"\n", " fast arrays", s->fast_array_count); + fprintf(fp, "%-20s %8"PRId64" %8"PRId64" (%0.1f per fast array)\n", + " elements", s->fast_array_elements, + s->fast_array_elements * (int)sizeof(JSValue), + (double)s->fast_array_elements / s->fast_array_count); + } + } + if (s->binary_object_count) { + fprintf(fp, "%-20s %8"PRId64" %8"PRId64"\n", + "binary objects", s->binary_object_count, s->binary_object_size); + } +} + +JSValue JS_GetGlobalObject(JSContext *ctx) +{ + return js_dup(ctx->global_obj); +} + +/* WARNING: obj is freed */ +JSValue JS_Throw(JSContext *ctx, JSValue obj) +{ + JSRuntime *rt = ctx->rt; + JS_FreeValue(ctx, rt->current_exception); + rt->current_exception = obj; + return JS_EXCEPTION; +} + +/* return the pending exception (cannot be called twice). */ +JSValue JS_GetException(JSContext *ctx) +{ + JSValue val; + JSRuntime *rt = ctx->rt; + val = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + return val; +} + +JS_BOOL JS_HasException(JSContext *ctx) +{ + return !JS_IsUninitialized(ctx->rt->current_exception); +} + +static void dbuf_put_leb128(DynBuf *s, uint32_t v) +{ + uint32_t a; + for(;;) { + a = v & 0x7f; + v >>= 7; + if (v != 0) { + dbuf_putc(s, a | 0x80); + } else { + dbuf_putc(s, a); + break; + } + } +} + +static void dbuf_put_sleb128(DynBuf *s, int32_t v1) +{ + uint32_t v = v1; + dbuf_put_leb128(s, (2 * v) ^ -(v >> 31)); +} + +static int get_leb128(uint32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + const uint8_t *ptr = buf; + uint32_t v, a, i; + v = 0; + for(i = 0; i < 5; i++) { + if (unlikely(ptr >= buf_end)) + break; + a = *ptr++; + v |= (a & 0x7f) << (i * 7); + if (!(a & 0x80)) { + *pval = v; + return ptr - buf; + } + } + *pval = 0; + return -1; +} + +static int get_sleb128(int32_t *pval, const uint8_t *buf, + const uint8_t *buf_end) +{ + int ret; + uint32_t val; + ret = get_leb128(&val, buf, buf_end); + if (ret < 0) { + *pval = 0; + return -1; + } + *pval = (val >> 1) ^ -(val & 1); + return ret; +} + +static int find_line_num(JSContext *ctx, JSFunctionBytecode *b, + uint32_t pc_value, int *col) +{ + const uint8_t *p_end, *p; + int new_line_num, new_col_num, line_num, col_num, pc, v, ret; + unsigned int op; + + *col = 1; + p = b->pc2line_buf; + if (!p) + goto fail; + p_end = p + b->pc2line_len; + pc = 0; + line_num = b->line_num; + col_num = b->col_num; + while (p < p_end) { + op = *p++; + if (op == 0) { + uint32_t val; + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + pc += val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128(&v, p, p_end); + if (ret < 0) + goto fail; + p += ret; + new_col_num = col_num + v; + if (pc_value < pc) + break; + line_num = new_line_num; + col_num = new_col_num; + } + *col = col_num; + return line_num; +fail: + /* should never happen */ + return b->line_num; +} + +/* in order to avoid executing arbitrary code during the stack trace + generation, we only look at simple 'name' properties containing a + string. */ +static const char *get_func_name(JSContext *ctx, JSValue func) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValue val; + + if (JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT) + return NULL; + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(func), JS_ATOM_name); + if (!prs) + return NULL; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return NULL; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return NULL; + return JS_ToCString(ctx, val); +} + +#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) +/* only taken into account if filename is provided */ +#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1) +#define JS_BACKTRACE_FLAG_FILTER_FUNC (1 << 2) + +/* if filename != NULL, an additional level is added with the filename + and line number information (used for parse error). */ +static void build_backtrace(JSContext *ctx, JSValue error_obj, JSValue filter_func, + const char *filename, int line_num, int col_num, + int backtrace_flags) +{ + JSStackFrame *sf, *sf_start; + JSValue stack, prepare, saved_exception; + DynBuf dbuf; + const char *func_name_str; + const char *str1; + JSObject *p; + JSFunctionBytecode *b; + BOOL backtrace_barrier, has_prepare; + JSRuntime *rt; + JSCallSiteData csd[64]; + uint32_t i; + int stack_trace_limit; + + stack_trace_limit = ctx->error_stack_trace_limit; + stack_trace_limit = min_int(stack_trace_limit, countof(csd)); + stack_trace_limit = max_int(stack_trace_limit, 0); + rt = ctx->rt; + has_prepare = FALSE; + i = 0; + + if (!rt->in_prepare_stack_trace && !JS_IsNull(ctx->error_ctor)) { + prepare = js_dup(ctx->error_prepare_stack); + has_prepare = JS_IsFunction(ctx, prepare); + rt->in_prepare_stack_trace = TRUE; + } + + if (has_prepare) { + saved_exception = rt->current_exception; + rt->current_exception = JS_NULL; + if (stack_trace_limit == 0) + goto done; + if (filename) + js_new_callsite_data2(ctx, &csd[i++], filename, line_num, col_num); + } else { + js_dbuf_init(ctx, &dbuf); + if (stack_trace_limit == 0) + goto done; + if (filename) { + i++; + dbuf_printf(&dbuf, " at %s", filename); + if (line_num != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num, col_num); + dbuf_putc(&dbuf, '\n'); + } + } + + if (filename && (backtrace_flags & JS_BACKTRACE_FLAG_SINGLE_LEVEL)) + goto done; + + sf_start = rt->current_stack_frame; + + /* Find the frame we want to start from. Note that when a filter is used the filter + function will be the first, but we also specify we want to skip the first one. */ + if (backtrace_flags & JS_BACKTRACE_FLAG_FILTER_FUNC) { + for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { + if (js_same_value(ctx, sf->cur_func, filter_func)) { + sf_start = sf; + break; + } + } + } + + for (sf = sf_start; sf != NULL && i < stack_trace_limit; sf = sf->prev_frame) { + if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { + backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; + continue; + } + + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = NULL; + backtrace_barrier = FALSE; + + if (js_class_has_bytecode(p->class_id)) { + b = p->u.func.function_bytecode; + backtrace_barrier = b->backtrace_barrier; + } + + if (has_prepare) { + js_new_callsite_data(ctx, &csd[i], sf); + } else { + /* func_name_str is UTF-8 encoded if needed */ + func_name_str = get_func_name(ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + str1 = "<anonymous>"; + else + str1 = func_name_str; + dbuf_printf(&dbuf, " at %s", str1); + JS_FreeCString(ctx, func_name_str); + + if (b) { + const char *atom_str; + int line_num1, col_num1; + + /* Bytecode functions must have cur_pc set in the stack frame. */ + if (sf->cur_pc == NULL) + abort(); + + line_num1 = find_line_num(ctx, b, + sf->cur_pc - b->byte_code_buf - 1, + &col_num1); + atom_str = b->filename ? JS_AtomToCString(ctx, b->filename) : NULL; + dbuf_printf(&dbuf, " (%s", atom_str ? atom_str : "<null>"); + JS_FreeCString(ctx, atom_str); + if (line_num1 != -1) + dbuf_printf(&dbuf, ":%d:%d", line_num1, col_num1); + dbuf_putc(&dbuf, ')'); + } else { + dbuf_printf(&dbuf, " (native)"); + } + dbuf_putc(&dbuf, '\n'); + } + i++; + + /* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */ + if (backtrace_barrier) + break; + } + done: + if (has_prepare) { + int j = 0, k; + stack = JS_NewArray(ctx); + if (JS_IsException(stack)) { + stack = JS_NULL; + } else { + for (; j < i; j++) { + JSValue v = js_new_callsite(ctx, &csd[j]); + if (JS_IsException(v)) + break; + if (JS_DefinePropertyValueUint32(ctx, stack, j, v, JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, v); + break; + } + } + } + // Clear the csd's we didn't use in case of error. + for (k = j; k < i; k++) { + JS_FreeValue(ctx, csd[k].filename); + JS_FreeValue(ctx, csd[k].func); + JS_FreeValue(ctx, csd[k].func_name); + } + JSValue args[] = { + error_obj, + stack, + }; + JSValue stack2 = JS_Call(ctx, prepare, ctx->error_ctor, countof(args), args); + JS_FreeValue(ctx, stack); + if (JS_IsException(stack2)) + stack = JS_NULL; + else + stack = stack2; + JS_FreeValue(ctx, prepare); + JS_FreeValue(ctx, rt->current_exception); + rt->current_exception = saved_exception; + } else { + if (dbuf_error(&dbuf)) + stack = JS_NULL; + else + stack = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); + dbuf_free(&dbuf); + } + + rt->in_prepare_stack_trace = FALSE; + JS_DefinePropertyValue(ctx, error_obj, JS_ATOM_stack, stack, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); +} + +/* Note: it is important that no exception is returned by this function */ +static BOOL is_backtrace_needed(JSContext *ctx, JSValue obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ERROR) + return FALSE; + if (find_own_property1(p, JS_ATOM_stack)) + return FALSE; + return TRUE; +} + +JSValue JS_NewError(JSContext *ctx) +{ + return JS_NewObjectClass(ctx, JS_CLASS_ERROR); +} + +static JSValue JS_MakeError(JSContext *ctx, JSErrorEnum error_num, + const char *message, BOOL add_backtrace) +{ + JSValue obj, msg; + + if (error_num == JS_PLAIN_ERROR) { + obj = JS_NewError(ctx); + } else { + obj = JS_NewObjectProtoClass(ctx, ctx->native_error_proto[error_num], + JS_CLASS_ERROR); + } + if (JS_IsException(obj)) + return JS_EXCEPTION; + msg = JS_NewString(ctx, message); + if (JS_IsException(msg)) + msg = JS_NewString(ctx, "Invalid error message"); + if (!JS_IsException(msg)) { + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + if (add_backtrace) + build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, 0); + return obj; +} + +/* fmt and arguments may be pure ASCII or UTF-8 encoded contents */ +static JSValue JS_ThrowError2(JSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap, BOOL add_backtrace) +{ + char buf[256]; + JSValue obj; + + vsnprintf(buf, sizeof(buf), fmt, ap); + obj = JS_MakeError(ctx, error_num, buf, add_backtrace); + if (unlikely(JS_IsException(obj))) { + /* out of memory: throw JS_NULL to avoid recursing */ + obj = JS_NULL; + } + return JS_Throw(ctx, obj); +} + +static JSValue JS_ThrowError(JSContext *ctx, JSErrorEnum error_num, + const char *fmt, va_list ap) +{ + JSRuntime *rt = ctx->rt; + JSStackFrame *sf; + BOOL add_backtrace; + + /* the backtrace is added later if called from a bytecode function */ + sf = rt->current_stack_frame; + add_backtrace = !rt->in_out_of_memory && + (!sf || (JS_GetFunctionBytecode(sf->cur_func) == NULL)); + return JS_ThrowError2(ctx, error_num, fmt, ap, add_backtrace); +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowPlainError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_PLAIN_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_SYNTAX_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +static int __attribute__((format(printf, 3, 4))) JS_ThrowTypeErrorOrFalse(JSContext *ctx, int flags, const char *fmt, ...) +{ + va_list ap; + + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + va_start(ap, fmt); + JS_ThrowError(ctx, JS_TYPE_ERROR, fmt, ap); + va_end(ap); + return -1; + } else { + return FALSE; + } +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowTypeErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* never use it directly */ +static JSValue __attribute__((format(printf, 3, 4))) __JS_ThrowSyntaxErrorAtom(JSContext *ctx, JSAtom atom, const char *fmt, ...) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowSyntaxError(ctx, fmt, + JS_AtomGetStr(ctx, buf, sizeof(buf), atom)); +} + +/* %s is replaced by 'atom'. The macro is used so that gcc can check + the format string. */ +#define JS_ThrowTypeErrorAtom(ctx, fmt, atom) __JS_ThrowTypeErrorAtom(ctx, atom, fmt, "") +#define JS_ThrowSyntaxErrorAtom(ctx, fmt, atom) __JS_ThrowSyntaxErrorAtom(ctx, atom, fmt, "") + +static int JS_ThrowTypeErrorReadOnly(JSContext *ctx, int flags, JSAtom atom) +{ + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeErrorAtom(ctx, "'%s' is read-only", atom); + return -1; + } else { + return FALSE; + } +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_REFERENCE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_RANGE_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue __attribute__((format(printf, 2, 3))) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...) +{ + JSValue val; + va_list ap; + + va_start(ap, fmt); + val = JS_ThrowError(ctx, JS_INTERNAL_ERROR, fmt, ap); + va_end(ap); + return val; +} + +JSValue JS_ThrowOutOfMemory(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + if (!rt->in_out_of_memory) { + rt->in_out_of_memory = TRUE; + JS_ThrowInternalError(ctx, "out of memory"); + rt->in_out_of_memory = FALSE; + } + return JS_EXCEPTION; +} + +static JSValue JS_ThrowStackOverflow(JSContext *ctx) +{ + return JS_ThrowRangeError(ctx, "Maximum call stack size exceeded"); +} + +static JSValue JS_ThrowTypeErrorNotAnObject(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not an object"); +} + +static JSValue JS_ThrowTypeErrorNotASymbol(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "not a symbol"); +} + +static JSValue JS_ThrowReferenceErrorNotDefined(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not defined", + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized(JSContext *ctx, JSAtom name) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError(ctx, "%s is not initialized", + name == JS_ATOM_NULL ? "lexical variable" : + JS_AtomGetStr(ctx, buf, sizeof(buf), name)); +} + +static JSValue JS_ThrowReferenceErrorUninitialized2(JSContext *ctx, + JSFunctionBytecode *b, + int idx, BOOL is_ref) +{ + JSAtom atom = JS_ATOM_NULL; + if (is_ref) { + atom = b->closure_var[idx].var_name; + } else { + /* not present if the function is stripped and contains no eval() */ + if (b->vardefs) + atom = b->vardefs[b->arg_count + idx].var_name; + } + return JS_ThrowReferenceErrorUninitialized(ctx, atom); +} + +static JSValue JS_ThrowTypeErrorInvalidClass(JSContext *ctx, int class_id) +{ + JSRuntime *rt = ctx->rt; + JSAtom name; + name = rt->class_array[class_id].class_name; + return JS_ThrowTypeErrorAtom(ctx, "%s object expected", name); +} + +static no_inline __exception int __js_poll_interrupts(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (rt->interrupt_handler) { + if (rt->interrupt_handler(rt, rt->interrupt_opaque)) { + /* XXX: should set a specific flag to avoid catching */ + JS_ThrowInternalError(ctx, "interrupted"); + JS_SetUncatchableError(ctx, ctx->rt->current_exception, TRUE); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts(JSContext *ctx) +{ + if (unlikely(--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts(ctx); + } else { + return 0; + } +} + +/* return -1 (exception) or TRUE/FALSE */ +static int JS_SetPrototypeInternal(JSContext *ctx, JSValue obj, + JSValue proto_val, + BOOL throw_flag) +{ + JSObject *proto, *p, *p1; + JSShape *sh; + + if (throw_flag) { + if (JS_VALUE_GET_TAG(obj) == JS_TAG_NULL || + JS_VALUE_GET_TAG(obj) == JS_TAG_UNDEFINED) + goto not_obj; + } else { + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto not_obj; + } + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_OBJECT) { + if (JS_VALUE_GET_TAG(proto_val) != JS_TAG_NULL) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + proto = NULL; + } else { + proto = JS_VALUE_GET_OBJ(proto_val); + } + + if (throw_flag && JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return TRUE; + + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_setPrototypeOf(ctx, obj, proto_val, throw_flag); + sh = p->shape; + if (sh->proto == proto) + return TRUE; + if (p == JS_VALUE_GET_OBJ(ctx->class_proto[JS_CLASS_OBJECT])) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "'Immutable prototype object \'Object.prototype\' cannot have their prototype set'"); + return -1; + } + return FALSE; + } + if (!p->extensible) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "object is not extensible"); + return -1; + } else { + return FALSE; + } + } + if (proto) { + /* check if there is a cycle */ + p1 = proto; + do { + if (p1 == p) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "circular prototype chain"); + return -1; + } else { + return FALSE; + } + } + /* Note: for Proxy objects, proto is NULL */ + p1 = p1->shape->proto; + } while (p1 != NULL); + js_dup(proto_val); + } + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + sh = p->shape; + if (sh->proto) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, sh->proto)); + sh->proto = proto; + return TRUE; +} + +/* return -1 (exception) or TRUE/FALSE */ +int JS_SetPrototype(JSContext *ctx, JSValue obj, JSValue proto_val) +{ + return JS_SetPrototypeInternal(ctx, obj, proto_val, TRUE); +} + +/* Only works for primitive types, otherwise return JS_NULL. */ +static JSValue JS_GetPrototypePrimitive(JSContext *ctx, JSValue val) +{ + switch(JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_BIG_INT: + val = ctx->class_proto[JS_CLASS_BIG_INT]; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + val = ctx->class_proto[JS_CLASS_NUMBER]; + break; + case JS_TAG_BOOL: + val = ctx->class_proto[JS_CLASS_BOOLEAN]; + break; + case JS_TAG_STRING: + val = ctx->class_proto[JS_CLASS_STRING]; + break; + case JS_TAG_SYMBOL: + val = ctx->class_proto[JS_CLASS_SYMBOL]; + break; + case JS_TAG_OBJECT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + val = JS_NULL; + break; + } + return val; +} + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of Proxy object. */ +JSValue JS_GetPrototype(JSContext *ctx, JSValue obj) +{ + JSValue val; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + val = js_proxy_getPrototypeOf(ctx, obj); + } else { + p = p->shape->proto; + if (!p) + val = JS_NULL; + else + val = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + val = js_dup(JS_GetPrototypePrimitive(ctx, obj)); + } + return val; +} + +static JSValue JS_GetPrototypeFree(JSContext *ctx, JSValue obj) +{ + JSValue obj1; + obj1 = JS_GetPrototype(ctx, obj); + JS_FreeValue(ctx, obj); + return obj1; +} + +int JS_GetLength(JSContext *ctx, JSValue obj, int64_t *pres) { + return js_get_length64(ctx, pres, obj); +} + +int JS_SetLength(JSContext *ctx, JSValue obj, int64_t len) { + return js_set_length64(ctx, obj, len); +} + +/* return TRUE, FALSE or (-1) in case of exception */ +static int JS_OrdinaryIsInstanceOf(JSContext *ctx, JSValue val, + JSValue obj) +{ + JSValue obj_proto; + JSObject *proto; + const JSObject *p, *proto1; + BOOL ret; + + if (!JS_IsFunction(ctx, obj)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_BOUND_FUNCTION) { + JSBoundFunction *s = p->u.bound_function; + return JS_IsInstanceOf(ctx, val, s->func_obj); + } + + /* Only explicitly boxed values are instances of constructors */ + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + obj_proto = JS_GetProperty(ctx, obj, JS_ATOM_prototype); + if (JS_VALUE_GET_TAG(obj_proto) != JS_TAG_OBJECT) { + if (!JS_IsException(obj_proto)) + JS_ThrowTypeError(ctx, "operand 'prototype' property is not an object"); + ret = -1; + goto done; + } + proto = JS_VALUE_GET_OBJ(obj_proto); + p = JS_VALUE_GET_OBJ(val); + for(;;) { + proto1 = p->shape->proto; + if (!proto1) { + /* slow case if proxy in the prototype chain */ + if (unlikely(p->class_id == JS_CLASS_PROXY)) { + JSValue obj1; + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, (JSObject *)p)); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsException(obj1)) { + ret = -1; + break; + } + if (JS_IsNull(obj1)) { + ret = FALSE; + break; + } + if (proto == JS_VALUE_GET_OBJ(obj1)) { + JS_FreeValue(ctx, obj1); + ret = TRUE; + break; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + ret = -1; + break; + } + } + } else { + ret = FALSE; + } + break; + } + p = proto1; + if (proto == p) { + ret = TRUE; + break; + } + } +done: + JS_FreeValue(ctx, obj_proto); + return ret; +} + +/* return TRUE, FALSE or (-1) in case of exception */ +int JS_IsInstanceOf(JSContext *ctx, JSValue val, JSValue obj) +{ + JSValue method; + + if (!JS_IsObject(obj)) + goto fail; + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_hasInstance); + if (JS_IsException(method)) + return -1; + if (!JS_IsNull(method) && !JS_IsUndefined(method)) { + JSValue ret; + ret = JS_CallFree(ctx, method, obj, 1, &val); + return JS_ToBoolFree(ctx, ret); + } + + /* legacy case */ + if (!JS_IsFunction(ctx, obj)) { + fail: + JS_ThrowTypeError(ctx, "invalid 'instanceof' right operand"); + return -1; + } + return JS_OrdinaryIsInstanceOf(ctx, val, obj); +} + +/* return the value associated to the autoinit property or an exception */ +typedef JSValue JSAutoInitFunc(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); + +static JSAutoInitFunc *js_autoinit_func_table[] = { + js_instantiate_prototype, /* JS_AUTOINIT_ID_PROTOTYPE */ + js_module_ns_autoinit, /* JS_AUTOINIT_ID_MODULE_NS */ + JS_InstantiateFunctionListItem2, /* JS_AUTOINIT_ID_PROP */ +}; + +/* warning: 'prs' is reallocated after it */ +static int JS_AutoInitProperty(JSContext *ctx, JSObject *p, JSAtom prop, + JSProperty *pr, JSShapeProperty *prs) +{ + JSValue val; + JSContext *realm; + JSAutoInitFunc *func; + + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + + realm = js_autoinit_get_realm(pr); + func = js_autoinit_func_table[js_autoinit_get_id(pr)]; + /* 'func' shall not modify the object properties 'pr' */ + val = func(realm, p, prop, pr->u.init.opaque); + js_autoinit_free(ctx->rt, pr); + prs->flags &= ~JS_PROP_TMASK; + pr->u.value = JS_UNDEFINED; + if (JS_IsException(val)) + return -1; + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPropertyInternal2(JSContext *ctx, JSValue obj, + JSAtom prop, JSValue this_obj, + JSInlineCacheUpdate *icu, + BOOL throw_ref_error) +{ + JSObject *p; + JSProperty *pr; + JSShapeProperty *prs; + uint32_t tag, offset, proto_depth; + + offset = proto_depth = 0; + tag = JS_VALUE_GET_TAG(obj); + if (unlikely(tag != JS_TAG_OBJECT)) { + switch(tag) { + case JS_TAG_NULL: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of null", prop); + case JS_TAG_UNDEFINED: + return JS_ThrowTypeErrorAtom(ctx, "cannot read property '%s' of undefined", prop); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_STRING: + { + JSString *p1 = JS_VALUE_GET_STRING(obj); + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx, ch; + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + ch = string_get(p1, idx); + return js_new_string_char(ctx, ch); + } + } else if (prop == JS_ATOM_length) { + return js_int32(p1->len); + } + } + break; + default: + break; + } + /* cannot raise an exception */ + p = JS_VALUE_GET_OBJ(JS_GetPrototypePrimitive(ctx, obj)); + if (!p) + return JS_UNDEFINED; + } else { + p = JS_VALUE_GET_OBJ(obj); + } + + for(;;) { + prs = find_own_property_ic(&pr, p, prop, &offset); + if (prs) { + /* found */ + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + if (unlikely(!pr->u.getset.getter)) { + return JS_UNDEFINED; + } else { + JSValue func = JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter); + /* Note: the field could be removed in the getter */ + func = js_dup(func); + return JS_CallFree(ctx, func, this_obj, 0, NULL); + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return js_dup(val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return JS_EXCEPTION; + continue; + } + } else { + if (proto_depth == 0) + add_ic_slot(ctx, icu, prop, p, offset); + return js_dup(pr->u.value); + } + } + if (unlikely(p->is_exotic)) { + /* exotic behaviors */ + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + /* we avoid duplicating the code */ + return JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + return JS_UNDEFINED; + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + int ret; + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return JS_EXCEPTION; + return JS_UNDEFINED; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->get_property) { + JSValue obj1, retval; + /* XXX: should pass throw_ref_error */ + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + retval = em->get_property(ctx, obj1, prop, this_obj); + JS_FreeValue(ctx, obj1); + return retval; + } + if (em->get_own_property) { + JSPropertyDescriptor desc; + int ret; + JSValue obj1; + + /* Note: if 'p' is a prototype, it can be + freed in the called function */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->get_own_property(ctx, &desc, obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + return JS_EXCEPTION; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.setter); + return JS_CallFree(ctx, desc.getter, this_obj, 0, NULL); + } else { + return desc.value; + } + } + } + } + } + } + proto_depth++; + p = p->shape->proto; + if (!p) + break; + } + if (unlikely(throw_ref_error)) { + return JS_ThrowReferenceErrorNotDefined(ctx, prop); + } else { + return JS_UNDEFINED; + } +} + +static JSValue JS_GetPropertyInternal(JSContext *ctx, JSValue obj, + JSAtom prop, JSValue this_obj, + BOOL throw_ref_error) +{ + return JS_GetPropertyInternal2(ctx, obj, prop, this_obj, NULL, throw_ref_error); +} + +JSValue JS_GetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop) +{ + return JS_GetPropertyInternal2(ctx, this_obj, prop, this_obj, NULL, FALSE); +} + +static JSValue JS_GetPropertyInternalWithIC(JSContext *ctx, JSValue obj, + JSAtom prop, JSValue this_obj, + JSInlineCacheUpdate *icu, + BOOL throw_ref_error) +{ + uint32_t tag, offset; + JSObject *p; + tag = JS_VALUE_GET_TAG(obj); + if (unlikely(tag != JS_TAG_OBJECT)) + goto slow_path; + p = JS_VALUE_GET_OBJ(obj); + offset = get_ic_prop_offset(icu, p->shape); + if (likely(offset != INLINE_CACHE_MISS)) + return js_dup(p->prop[offset].u.value); +slow_path: + return JS_GetPropertyInternal2(ctx, obj, prop, this_obj, icu, throw_ref_error); +} + +static JSValue JS_ThrowTypeErrorPrivateNotFound(JSContext *ctx, JSAtom atom) +{ + return JS_ThrowTypeErrorAtom(ctx, "private class field '%s' does not exist", + atom); +} + +/* Private fields can be added even on non extensible objects or + Proxies */ +static int JS_DefinePrivateField(JSContext *ctx, JSValue obj, + JSValue name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + JS_ThrowTypeErrorAtom(ctx, "private class field '%s' already exists", + prop); + goto fail; + } + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (unlikely(!pr)) { + fail: + JS_FreeValue(ctx, val); + return -1; + } + pr->u.value = val; + return 0; +} + +static JSValue JS_GetPrivateField(JSContext *ctx, JSValue obj, + JSValue name) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return JS_ThrowTypeErrorNotAnObject(ctx); + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) + return JS_ThrowTypeErrorNotASymbol(ctx); + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + return JS_EXCEPTION; + } + return js_dup(pr->u.value); +} + +static int JS_SetPrivateField(JSContext *ctx, JSValue obj, + JSValue name, JSValue val) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSAtom prop; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail; + } + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(name) != JS_TAG_SYMBOL)) { + JS_ThrowTypeErrorNotASymbol(ctx); + goto fail; + } + prop = js_symbol_to_atom(ctx, name); + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, prop); + if (!prs) { + JS_ThrowTypeErrorPrivateNotFound(ctx, prop); + fail: + JS_FreeValue(ctx, val); + return -1; + } + set_value(ctx, &pr->u.value, val); + return 0; +} + +/* add a private brand field to 'home_obj' if not already present and + if obj is != null add a private brand to it */ +static int JS_AddBrand(JSContext *ctx, JSValue obj, JSValue home_obj) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + JSAtom brand_atom; + + if (unlikely(JS_VALUE_GET_TAG(home_obj) != JS_TAG_OBJECT)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(home_obj); + prs = find_own_property(&pr, p, JS_ATOM_Private_brand); + if (!prs) { + /* if the brand is not present, add it */ + brand = JS_NewSymbolFromAtom(ctx, JS_ATOM_brand, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(brand)) + return -1; + pr = add_property(ctx, p, JS_ATOM_Private_brand, JS_PROP_C_W_E); + if (!pr) { + JS_FreeValue(ctx, brand); + return -1; + } + pr->u.value = js_dup(brand); + } else { + brand = js_dup(pr->u.value); + } + brand_atom = js_symbol_to_atom(ctx, brand); + + if (JS_IsObject(obj)) { + p1 = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p1, brand_atom); + if (unlikely(prs)) { + JS_FreeAtom(ctx, brand_atom); + JS_ThrowTypeError(ctx, "private method is already present"); + return -1; + } + pr = add_property(ctx, p1, brand_atom, JS_PROP_C_W_E); + JS_FreeAtom(ctx, brand_atom); + if (!pr) + return -1; + pr->u.value = JS_UNDEFINED; + } else { + JS_FreeAtom(ctx, brand_atom); + } + + return 0; +} + +/* return a boolean telling if the brand of the home object of 'func' + is present on 'obj' or -1 in case of exception */ +static int JS_CheckBrand(JSContext *ctx, JSValue obj, JSValue func) +{ + JSObject *p, *p1, *home_obj; + JSShapeProperty *prs; + JSProperty *pr; + JSValue brand; + + /* get the home object of 'func' */ + if (unlikely(JS_VALUE_GET_TAG(func) != JS_TAG_OBJECT)) + goto not_obj; + p1 = JS_VALUE_GET_OBJ(func); + if (!js_class_has_bytecode(p1->class_id)) + goto not_obj; + home_obj = p1->u.func.home_object; + if (!home_obj) + goto not_obj; + prs = find_own_property(&pr, home_obj, JS_ATOM_Private_brand); + if (!prs) { + JS_ThrowTypeError(ctx, "expecting <brand> private field"); + return -1; + } + brand = pr->u.value; + /* safety check */ + if (unlikely(JS_VALUE_GET_TAG(brand) != JS_TAG_SYMBOL)) + goto not_obj; + + /* get the brand array of 'obj' */ + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) { + not_obj: + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property(&pr, p, js_symbol_to_atom(ctx, brand)); + return (prs != NULL); +} + +static uint32_t js_string_obj_get_length(JSContext *ctx, + JSValue obj) +{ + JSObject *p; + JSString *p1; + uint32_t len = 0; + + /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(p->u.object_data); + len = p1->len; + } + return len; +} + +static int num_keys_cmp(const void *p1, const void *p2, void *opaque) +{ + JSContext *ctx = opaque; + JSAtom atom1 = ((const JSPropertyEnum *)p1)->atom; + JSAtom atom2 = ((const JSPropertyEnum *)p2)->atom; + uint32_t v1, v2; + BOOL atom1_is_integer, atom2_is_integer; + + atom1_is_integer = JS_AtomIsArrayIndex(ctx, &v1, atom1); + atom2_is_integer = JS_AtomIsArrayIndex(ctx, &v2, atom2); + assert(atom1_is_integer && atom2_is_integer); + if (v1 < v2) + return -1; + else if (v1 == v2) + return 0; + else + return 1; +} + +static void js_free_prop_enum(JSContext *ctx, JSPropertyEnum *tab, uint32_t len) +{ + uint32_t i; + if (tab) { + for(i = 0; i < len; i++) + JS_FreeAtom(ctx, tab[i].atom); + js_free(ctx, tab); + } +} + +/* return < 0 in case if exception, 0 if OK. ptab and its atoms must + be freed by the user. */ +static int __exception JS_GetOwnPropertyNamesInternal(JSContext *ctx, + JSPropertyEnum **ptab, + uint32_t *plen, + JSObject *p, int flags) +{ + int i, j; + JSShape *sh; + JSShapeProperty *prs; + JSPropertyEnum *tab_atom, *tab_exotic; + JSAtom atom; + uint32_t num_keys_count, str_keys_count, sym_keys_count, atom_count; + uint32_t num_index, str_index, sym_index, exotic_count, exotic_keys_count; + BOOL is_enumerable, num_sorted; + uint32_t num_key; + JSAtomKindEnum kind; + + /* clear pointer for consistency in case of failure */ + *ptab = NULL; + *plen = 0; + + /* compute the number of returned properties */ + num_keys_count = 0; + str_keys_count = 0; + sym_keys_count = 0; + exotic_keys_count = 0; + exotic_count = 0; + tab_exotic = NULL; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + /* need to raise an exception in case of the module + name space (implicit GetOwnProperty) */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) && + (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY))) { + JSVarRef *var_ref = p->prop[i].u.var_ref; + if (unlikely(JS_IsUninitialized(*var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + num_keys_count++; + } else if (kind == JS_ATOM_KIND_STRING) { + str_keys_count++; + } else { + sym_keys_count++; + } + } + } + } + + if (p->is_exotic) { + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += p->u.array.count; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + num_keys_count += js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property_names) { + if (em->get_own_property_names(ctx, &tab_exotic, &exotic_count, + JS_MKPTR(JS_TAG_OBJECT, p))) + return -1; + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + kind = JS_AtomGetKind(ctx, atom); + if (((flags >> kind) & 1) != 0) { + is_enumerable = FALSE; + if (flags & (JS_GPN_SET_ENUM | JS_GPN_ENUM_ONLY)) { + JSPropertyDescriptor desc; + int res; + /* set the "is_enumerable" field if necessary */ + res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); + if (res < 0) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + if (res) { + is_enumerable = + ((desc.flags & JS_PROP_ENUMERABLE) != 0); + js_free_desc(ctx, &desc); + } + tab_exotic[i].is_enumerable = is_enumerable; + } + if (!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) { + exotic_keys_count++; + } + } + } + } + } + } + + /* fill them */ + + atom_count = num_keys_count + str_keys_count + sym_keys_count + exotic_keys_count; + /* avoid allocating 0 bytes */ + tab_atom = js_malloc(ctx, sizeof(tab_atom[0]) * max_int(atom_count, 1)); + if (!tab_atom) { + js_free_prop_enum(ctx, tab_exotic, exotic_count); + return -1; + } + + num_index = 0; + str_index = num_keys_count; + sym_index = str_index + str_keys_count; + + num_sorted = TRUE; + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + atom = prs->atom; + if (atom != JS_ATOM_NULL) { + is_enumerable = ((prs->flags & JS_PROP_ENUMERABLE) != 0); + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + if (JS_AtomIsArrayIndex(ctx, &num_key, atom)) { + j = num_index++; + num_sorted = FALSE; + } else if (kind == JS_ATOM_KIND_STRING) { + j = str_index++; + } else { + j = sym_index++; + } + tab_atom[j].atom = JS_DupAtom(ctx, atom); + tab_atom[j].is_enumerable = is_enumerable; + } + } + } + + if (p->is_exotic) { + int len; + if (p->fast_array) { + if (flags & JS_GPN_STRING_MASK) { + len = p->u.array.count; + goto add_array_keys; + } + } else if (p->class_id == JS_CLASS_STRING) { + if (flags & JS_GPN_STRING_MASK) { + len = js_string_obj_get_length(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + add_array_keys: + for(i = 0; i < len; i++) { + tab_atom[num_index].atom = __JS_AtomFromUInt32(i); + if (tab_atom[num_index].atom == JS_ATOM_NULL) { + js_free_prop_enum(ctx, tab_atom, num_index); + return -1; + } + tab_atom[num_index].is_enumerable = TRUE; + num_index++; + } + } + } else { + /* Note: exotic keys are not reordered and comes after the object own properties. */ + for(i = 0; i < exotic_count; i++) { + atom = tab_exotic[i].atom; + is_enumerable = tab_exotic[i].is_enumerable; + kind = JS_AtomGetKind(ctx, atom); + if ((!(flags & JS_GPN_ENUM_ONLY) || is_enumerable) && + ((flags >> kind) & 1) != 0) { + tab_atom[sym_index].atom = atom; + tab_atom[sym_index].is_enumerable = is_enumerable; + sym_index++; + } else { + JS_FreeAtom(ctx, atom); + } + } + js_free(ctx, tab_exotic); + } + } + + assert(num_index == num_keys_count); + assert(str_index == num_keys_count + str_keys_count); + assert(sym_index == atom_count); + + if (num_keys_count != 0 && !num_sorted) { + rqsort(tab_atom, num_keys_count, sizeof(tab_atom[0]), num_keys_cmp, + ctx); + } + *ptab = tab_atom; + *plen = atom_count; + return 0; +} + +int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValue obj, int flags) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, + JS_VALUE_GET_OBJ(obj), flags); +} + +/* Return -1 if exception, + FALSE if the property does not exist, TRUE if it exists. If TRUE is + returned, the property descriptor 'desc' is filled present. */ +static int JS_GetOwnPropertyInternal(JSContext *ctx, JSPropertyDescriptor *desc, + JSObject *p, JSAtom prop) +{ + JSShapeProperty *prs; + JSProperty *pr; + +retry: + prs = find_own_property(&pr, p, prop); + if (prs) { + if (desc) { + desc->flags = prs->flags & JS_PROP_C_W_E; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_UNDEFINED; + if (unlikely(prs->flags & JS_PROP_TMASK)) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + desc->flags |= JS_PROP_GETSET; + if (pr->u.getset.getter) + desc->getter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + desc->setter = js_dup(JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + JSValue val = *pr->u.var_ref->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + desc->value = js_dup(val); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto retry; + } + } else { + desc->value = js_dup(pr->u.value); + } + } else { + /* for consistency, send the exception even if desc is NULL */ + if (unlikely((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF)) { + if (unlikely(JS_IsUninitialized(*pr->u.var_ref->pvalue))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* nothing to do: delay instantiation until actual value and/or attributes are read */ + } + } + return TRUE; + } + if (p->is_exotic) { + if (p->fast_array) { + /* specific case for fast arrays */ + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx; + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + if (desc) { + desc->flags = JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | + JS_PROP_CONFIGURABLE; + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + desc->value = JS_GetPropertyUint32(ctx, JS_MKPTR(JS_TAG_OBJECT, p), idx); + } + return TRUE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->get_own_property) { + return em->get_own_property(ctx, desc, + JS_MKPTR(JS_TAG_OBJECT, p), prop); + } + } + } + return FALSE; +} + +int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValue obj, JSAtom prop) +{ + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return JS_GetOwnPropertyInternal(ctx, desc, JS_VALUE_GET_OBJ(obj), prop); +} + +void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, + uint32_t len) +{ + js_free_prop_enum(ctx, tab, len); +} + +/* return -1 if exception (Proxy object only) or TRUE/FALSE */ +int JS_IsExtensible(JSContext *ctx, JSValue obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isExtensible(ctx, obj); + else + return p->extensible; +} + +/* return -1 if exception (Proxy object only) or TRUE/FALSE */ +int JS_PreventExtensions(JSContext *ctx, JSValue obj) +{ + JSObject *p; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_preventExtensions(ctx, obj); + p->extensible = FALSE; + return TRUE; +} + +/* return -1 if exception otherwise TRUE or FALSE */ +int JS_HasProperty(JSContext *ctx, JSValue obj, JSAtom prop) +{ + JSObject *p; + int ret; + JSValue obj1; + + if (unlikely(JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + for(;;) { + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->has_property) { + /* has_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = em->has_property(ctx, obj1, prop); + JS_FreeValue(ctx, obj1); + return ret; + } + } + /* JS_GetOwnPropertyInternal can free the prototype */ + js_dup(JS_MKPTR(JS_TAG_OBJECT, p)); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, prop); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret != 0) + return ret; + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return FALSE; + } + } + p = p->shape->proto; + if (!p) + break; + } + return FALSE; +} + +/* val must be a symbol */ +static JSAtom js_symbol_to_atom(JSContext *ctx, JSValue val) +{ + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + return js_get_atom_index(ctx->rt, p); +} + +/* return JS_ATOM_NULL in case of exception */ +JSAtom JS_ValueToAtom(JSContext *ctx, JSValue val) +{ + JSAtom atom; + uint32_t tag; + tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_INT && + (uint32_t)JS_VALUE_GET_INT(val) <= JS_ATOM_MAX_INT) { + /* fast path for integer values */ + atom = __JS_AtomFromUInt32(JS_VALUE_GET_INT(val)); + } else if (tag == JS_TAG_SYMBOL) { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + atom = JS_DupAtom(ctx, js_get_atom_index(ctx->rt, p)); + } else { + JSValue str; + str = JS_ToPropertyKey(ctx, val); + if (JS_IsException(str)) + return JS_ATOM_NULL; + if (JS_VALUE_GET_TAG(str) == JS_TAG_SYMBOL) { + atom = js_symbol_to_atom(ctx, str); + } else { + atom = JS_NewAtomStr(ctx, JS_VALUE_GET_STRING(str)); + } + } + return atom; +} + +static BOOL js_get_fast_array_element(JSContext *ctx, JSObject *p, + uint32_t idx, JSValue *pval) +{ + switch(p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_dup(p->u.array.u.values[idx]); + return TRUE; + case JS_CLASS_INT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_int32(p->u.array.u.int8_ptr[idx]); + return TRUE; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_int32(p->u.array.u.uint8_ptr[idx]); + return TRUE; + case JS_CLASS_INT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_int32(p->u.array.u.int16_ptr[idx]); + return TRUE; + case JS_CLASS_UINT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_int32(p->u.array.u.uint16_ptr[idx]); + return TRUE; + case JS_CLASS_INT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_int32(p->u.array.u.int32_ptr[idx]); + return TRUE; + case JS_CLASS_UINT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_uint32(p->u.array.u.uint32_ptr[idx]); + return TRUE; + case JS_CLASS_BIG_INT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); + return TRUE; + case JS_CLASS_BIG_UINT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); + return TRUE; + case JS_CLASS_FLOAT16_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_float64(fromfp16(p->u.array.u.fp16_ptr[idx])); + return TRUE; + case JS_CLASS_FLOAT32_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_float64(p->u.array.u.float_ptr[idx]); + return TRUE; + case JS_CLASS_FLOAT64_ARRAY: + if (unlikely(idx >= p->u.array.count)) return FALSE; + *pval = js_float64(p->u.array.u.double_ptr[idx]); + return TRUE; + default: + return FALSE; + } +} + +static JSValue JS_GetPropertyValue(JSContext *ctx, JSValue this_obj, + JSValue prop) +{ + JSAtom atom; + JSValue ret; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) { + if (JS_VALUE_GET_TAG(prop) == JS_TAG_INT) { + JSObject *p = JS_VALUE_GET_OBJ(this_obj); + uint32_t idx = JS_VALUE_GET_INT(prop); + JSValue val; + /* fast path for array and typed array access */ + if (js_get_fast_array_element(ctx, p, idx, &val)) + return val; + } + } else { + switch(tag) { + case JS_TAG_NULL: + JS_FreeValue(ctx, prop); + return JS_ThrowTypeError(ctx, "cannot read property of null"); + case JS_TAG_UNDEFINED: + JS_FreeValue(ctx, prop); + return JS_ThrowTypeError(ctx, "cannot read property of undefined"); + } + } + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx) +{ + return JS_GetPropertyInt64(ctx, this_obj, idx); +} + +/* Check if an object has a generalized numeric property. Return value: + -1 for exception, *pval set to JS_EXCEPTION + TRUE if property exists, stored into *pval, + FALSE if property does not exist. *pval set to JS_UNDEFINED. + */ +static int JS_TryGetPropertyInt64(JSContext *ctx, JSValue obj, int64_t idx, JSValue *pval) +{ + JSValue val; + JSAtom prop; + int present; + + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && + (uint64_t)idx <= INT32_MAX)) { + /* fast path for array and typed array access */ + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (js_get_fast_array_element(ctx, p, idx, pval)) + return TRUE; + } + val = JS_EXCEPTION; + present = -1; + prop = JS_NewAtomInt64(ctx, idx); + if (likely(prop != JS_ATOM_NULL)) { + present = JS_HasProperty(ctx, obj, prop); + if (present > 0) { + val = JS_GetProperty(ctx, obj, prop); + if (unlikely(JS_IsException(val))) + present = -1; + } else if (present == FALSE) { + val = JS_UNDEFINED; + } + JS_FreeAtom(ctx, prop); + } + *pval = val; + return present; +} + +JSValue JS_GetPropertyInt64(JSContext *ctx, JSValue obj, int64_t idx) +{ + JSAtom prop; + JSValue val; + + if (likely(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT && + (uint64_t)idx <= INT32_MAX)) { + /* fast path for array and typed array access */ + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (js_get_fast_array_element(ctx, p, idx, &val)) + return val; + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return JS_EXCEPTION; + + val = JS_GetProperty(ctx, obj, prop); + JS_FreeAtom(ctx, prop); + return val; +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *prop) +{ + JSAtom atom; + JSValue ret; + atom = JS_NewAtom(ctx, prop); + ret = JS_GetProperty(ctx, this_obj, atom); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* Note: the property value is not initialized. Return NULL if memory + error. */ +static JSProperty *add_property(JSContext *ctx, + JSObject *p, JSAtom prop, int prop_flags) +{ + JSShape *sh, *new_sh; + + sh = p->shape; + if (sh->is_hashed) { + /* try to find an existing shape */ + new_sh = find_hashed_shape_prop(ctx->rt, sh, prop, prop_flags); + if (new_sh) { + /* matching shape found: use it */ + /* the property array may need to be resized */ + if (new_sh->prop_size != sh->prop_size) { + JSProperty *new_prop; + new_prop = js_realloc(ctx, p->prop, sizeof(p->prop[0]) * + new_sh->prop_size); + if (!new_prop) + return NULL; + p->prop = new_prop; + } + p->shape = js_dup_shape(new_sh); + js_free_shape(ctx->rt, sh); + return &p->prop[new_sh->prop_count - 1]; + } else if (sh->header.ref_count != 1) { + /* if the shape is shared, clone it */ + new_sh = js_clone_shape(ctx, sh); + if (!new_sh) + return NULL; + /* hash the cloned shape */ + new_sh->is_hashed = TRUE; + js_shape_hash_link(ctx->rt, new_sh); + js_free_shape(ctx->rt, p->shape); + p->shape = new_sh; + } + } + assert(p->shape->header.ref_count == 1); + if (add_shape_property(ctx, &p->shape, p, prop, prop_flags)) + return NULL; + return &p->prop[p->shape->prop_count - 1]; +} + +/* can be called on Array or Arguments objects. return < 0 if + memory alloc error. */ +static no_inline __exception int convert_fast_array_to_array(JSContext *ctx, + JSObject *p) +{ + JSProperty *pr; + JSShape *sh; + JSValue *tab; + uint32_t i, len, new_count; + + if (js_shape_prepare_update(ctx, p, NULL)) + return -1; + len = p->u.array.count; + /* resize the properties once to simplify the error handling */ + sh = p->shape; + new_count = sh->prop_count + len; + if (new_count > sh->prop_size) { + if (resize_properties(ctx, &p->shape, p, new_count)) + return -1; + } + + tab = p->u.array.u.values; + for(i = 0; i < len; i++) { + /* add_property cannot fail here but + __JS_AtomFromUInt32(i) fails for i > INT32_MAX */ + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E); + pr->u.value = *tab++; + } + js_free(ctx, p->u.array.u.values); + p->u.array.count = 0; + p->u.array.u.values = NULL; /* fail safe */ + p->u.array.u1.size = 0; + p->fast_array = 0; + return 0; +} + +static int delete_property(JSContext *ctx, JSObject *p, JSAtom atom) +{ + JSShape *sh; + JSShapeProperty *pr, *lpr, *prop; + JSProperty *pr1; + uint32_t lpr_idx; + intptr_t h, h1; + + redo: + sh = p->shape; + h1 = atom & sh->prop_hash_mask; + h = prop_hash_end(sh)[-h1 - 1]; + prop = get_shape_prop(sh); + lpr = NULL; + lpr_idx = 0; /* prevent warning */ + while (h != 0) { + pr = &prop[h - 1]; + if (likely(pr->atom == atom)) { + /* found ! */ + if (!(pr->flags & JS_PROP_CONFIGURABLE)) + return FALSE; + /* realloc the shape if needed */ + if (lpr) + lpr_idx = lpr - get_shape_prop(sh); + if (js_shape_prepare_update(ctx, p, &pr)) + return -1; + sh = p->shape; + /* remove property */ + if (lpr) { + lpr = get_shape_prop(sh) + lpr_idx; + lpr->hash_next = pr->hash_next; + } else { + prop_hash_end(sh)[-h1 - 1] = pr->hash_next; + } + sh->deleted_prop_count++; + /* free the entry */ + pr1 = &p->prop[h - 1]; + free_property(ctx->rt, pr1, pr->flags); + JS_FreeAtom(ctx, pr->atom); + /* put default values */ + pr->flags = 0; + pr->atom = JS_ATOM_NULL; + pr1->u.value = JS_UNDEFINED; + + /* compact the properties if too many deleted properties */ + if (sh->deleted_prop_count >= 8 && + sh->deleted_prop_count >= ((unsigned)sh->prop_count / 2)) { + compact_properties(ctx, p); + } + return TRUE; + } + lpr = pr; + h = pr->hash_next; + } + + if (p->is_exotic) { + if (p->fast_array) { + uint32_t idx; + if (JS_AtomIsArrayIndex(ctx, &idx, atom) && + idx < p->u.array.count) { + if (p->class_id == JS_CLASS_ARRAY || + p->class_id == JS_CLASS_ARGUMENTS) { + /* Special case deleting the last element of a fast Array */ + if (idx == p->u.array.count - 1) { + JS_FreeValue(ctx, p->u.array.u.values[idx]); + p->u.array.count = idx; + return TRUE; + } + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto redo; + } else { + return FALSE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em && em->delete_property) { + return em->delete_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), atom); + } + } + } + /* not found */ + return TRUE; +} + +static int call_setter(JSContext *ctx, JSObject *setter, + JSValue this_obj, JSValue val, int flags) +{ + JSValue ret, func; + if (likely(setter)) { + func = JS_MKPTR(JS_TAG_OBJECT, setter); + /* Note: the field could be removed in the setter */ + func = js_dup(func); + ret = JS_CallFree(ctx, func, this_obj, 1, &val); + JS_FreeValue(ctx, val); + if (JS_IsException(ret)) + return -1; + JS_FreeValue(ctx, ret); + return TRUE; + } else { + JS_FreeValue(ctx, val); + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "no setter for property"); + return -1; + } + return FALSE; + } +} + +/* set the array length and remove the array elements if necessary. */ +static int set_array_length(JSContext *ctx, JSObject *p, JSValue val, + int flags) +{ + uint32_t len, idx, cur_len; + int i, ret; + + /* Note: this call can reallocate the properties of 'p' */ + ret = JS_ToArrayLengthFree(ctx, &len, val, FALSE); + if (ret) + return -1; + /* JS_ToArrayLengthFree() must be done before the read-only test */ + if (unlikely(!(p->shape->prop[0].flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + + if (likely(p->fast_array)) { + uint32_t old_len = p->u.array.count; + if (len < old_len) { + for(i = len; i < old_len; i++) { + JS_FreeValue(ctx, p->u.array.u.values[i]); + } + p->u.array.count = len; + } + p->prop[0].u.value = js_uint32(len); + } else { + /* Note: length is always a uint32 because the object is an + array */ + JS_ToUint32(ctx, &cur_len, p->prop[0].u.value); + if (len < cur_len) { + uint32_t d; + JSShape *sh; + JSShapeProperty *pr; + + d = cur_len - len; + sh = p->shape; + if (d <= sh->prop_count) { + JSAtom atom; + + /* faster to iterate */ + while (cur_len > len) { + atom = JS_NewAtomUInt32(ctx, cur_len - 1); + ret = delete_property(ctx, p, atom); + JS_FreeAtom(ctx, atom); + if (unlikely(!ret)) { + /* unlikely case: property is not + configurable */ + break; + } + cur_len--; + } + } else { + /* faster to iterate thru all the properties. Need two + passes in case one of the property is not + configurable */ + cur_len = len; + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len && + !(pr->flags & JS_PROP_CONFIGURABLE)) { + cur_len = idx + 1; + } + } + } + + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; + i++, pr++) { + if (pr->atom != JS_ATOM_NULL && + JS_AtomIsArrayIndex(ctx, &idx, pr->atom)) { + if (idx >= cur_len) { + /* remove the property */ + delete_property(ctx, p, pr->atom); + /* WARNING: the shape may have been modified */ + sh = p->shape; + pr = get_shape_prop(sh) + i; + } + } + } + } + } else { + cur_len = len; + } + set_value(ctx, &p->prop[0].u.value, js_uint32(cur_len)); + if (unlikely(cur_len > len)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "not configurable"); + } + } + return TRUE; +} + +/* return -1 if exception */ +static int expand_fast_array(JSContext *ctx, JSObject *p, uint32_t new_len) +{ + uint32_t new_size; + size_t slack; + JSValue *new_array_prop; + /* XXX: potential arithmetic overflow */ + new_size = max_int(new_len, p->u.array.u1.size * 3 / 2); + new_array_prop = js_realloc2(ctx, p->u.array.u.values, sizeof(JSValue) * new_size, &slack); + if (!new_array_prop) + return -1; + new_size += slack / sizeof(*new_array_prop); + p->u.array.u.values = new_array_prop; + p->u.array.u1.size = new_size; + return 0; +} + +/* Preconditions: 'p' must be of class JS_CLASS_ARRAY, p->fast_array = + TRUE and p->extensible = TRUE */ +static int add_fast_array_element(JSContext *ctx, JSObject *p, + JSValue val, int flags) +{ + uint32_t new_len, array_len; + /* extend the array by one */ + /* XXX: convert to slow array if new_len > 2^31-1 elements */ + new_len = p->u.array.count + 1; + /* update the length if necessary. We assume that if the length is + not an integer, then if it >= 2^31. */ + if (likely(JS_VALUE_GET_TAG(p->prop[0].u.value) == JS_TAG_INT)) { + array_len = JS_VALUE_GET_INT(p->prop[0].u.value); + if (new_len > array_len) { + if (unlikely(!(get_shape_prop(p->shape)->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + } + p->prop[0].u.value = js_int32(new_len); + } + } + if (unlikely(new_len > p->u.array.u1.size)) { + if (expand_fast_array(ctx, p, new_len)) { + JS_FreeValue(ctx, val); + return -1; + } + } + p->u.array.u.values[new_len - 1] = val; + p->u.array.count = new_len; + return TRUE; +} + +static void js_free_desc(JSContext *ctx, JSPropertyDescriptor *desc) +{ + JS_FreeValue(ctx, desc->getter); + JS_FreeValue(ctx, desc->setter); + JS_FreeValue(ctx, desc->value); +} + +/* return -1 in case of exception or TRUE or FALSE. Warning: 'val' is + freed by the function. 'flags' is a bitmask of JS_PROP_NO_ADD, + JS_PROP_THROW or JS_PROP_THROW_STRICT. If JS_PROP_NO_ADD is set, + the new property is not added and an error is raised. + 'obj' must be an object when obj != this_obj. + */ +static int JS_SetPropertyInternal2(JSContext *ctx, JSValue obj, JSAtom prop, + JSValue val, JSValue this_obj, int flags, + JSInlineCacheUpdate *icu) +{ + JSObject *p, *p1; + JSShapeProperty *prs; + JSProperty *pr; + JSPropertyDescriptor desc; + int ret; + uint32_t offset = 0; + + switch(JS_VALUE_GET_TAG(this_obj)) { + case JS_TAG_NULL: + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of null", prop); + goto fail; + case JS_TAG_UNDEFINED: + JS_ThrowTypeErrorAtom(ctx, "cannot set property '%s' of undefined", prop); + goto fail; + case JS_TAG_OBJECT: + p = JS_VALUE_GET_OBJ(this_obj); + p1 = JS_VALUE_GET_OBJ(obj); + if (p == p1) + break; + goto retry2; + default: + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + obj = JS_GetPrototypePrimitive(ctx, obj); + p = NULL; + p1 = JS_VALUE_GET_OBJ(obj); + goto prototype_lookup; + } + +retry: + prs = find_own_property_ic(&pr, p1, prop, &offset); + if (prs) { + if (likely((prs->flags & (JS_PROP_TMASK | JS_PROP_WRITABLE | + JS_PROP_LENGTH)) == JS_PROP_WRITABLE)) { + /* fast case */ + add_ic_slot(ctx, icu, prop, p, offset); + set_value(ctx, &pr->u.value, val); + return TRUE; + } else if (prs->flags & JS_PROP_LENGTH) { + assert(p->class_id == JS_CLASS_ARRAY); + assert(prop == JS_ATOM_length); + return set_array_length(ctx, p, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (p->class_id == JS_CLASS_MODULE_NS) + goto read_only_prop; + set_value(ctx, pr->u.var_ref->pvalue, val); + return TRUE; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + goto fail; + goto retry; + } else { + goto read_only_prop; + } + } + + for(;;) { + if (p1->is_exotic) { + if (p1->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx < p1->u.array.count) { + if (unlikely(p == p1)) + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, flags); + else + break; + } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + goto typed_array_oob; + } + } else if (p1->class_id >= JS_CLASS_UINT8C_ARRAY && + p1->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + goto fail; + typed_array_oob: + // per spec: evaluate value for side effects + if (p1->class_id == JS_CLASS_BIG_INT64_ARRAY || + p1->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + return -1; + } else { + val = JS_ToNumberFree(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(val)) + return -1; + } + return TRUE; + } + } + } else { + const JSClassExoticMethods *em = ctx->rt->class_array[p1->class_id].exotic; + if (em) { + JSValue obj1; + if (em->set_property) { + /* set_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->set_property(ctx, obj1, prop, + val, this_obj, flags); + JS_FreeValue(ctx, obj1); + JS_FreeValue(ctx, val); + return ret; + } + if (em->get_own_property) { + /* get_own_property can free the prototype */ + obj1 = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + ret = em->get_own_property(ctx, &desc, + obj1, prop); + JS_FreeValue(ctx, obj1); + if (ret < 0) + goto fail; + if (ret) { + if (desc.flags & JS_PROP_GETSET) { + JSObject *setter; + if (JS_IsUndefined(desc.setter)) + setter = NULL; + else + setter = JS_VALUE_GET_OBJ(desc.setter); + ret = call_setter(ctx, setter, this_obj, val, flags); + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + return ret; + } else { + JS_FreeValue(ctx, desc.value); + if (!(desc.flags & JS_PROP_WRITABLE)) + goto read_only_prop; + if (likely(p == p1)) { + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + JS_FreeValue(ctx, val); + return ret; + } else { + break; + } + } + } + } + } + } + } + p1 = p1->shape->proto; + prototype_lookup: + if (!p1) + break; + + retry2: + prs = find_own_property(&pr, p1, prop); + if (prs) { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + return call_setter(ctx, pr->u.getset.setter, this_obj, val, flags); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry (potentially useless) */ + if (JS_AutoInitProperty(ctx, p1, prop, pr, prs)) + return -1; + goto retry2; + } else if (!(prs->flags & JS_PROP_WRITABLE)) { + goto read_only_prop; + } + } + } + + if (unlikely(flags & JS_PROP_NO_ADD)) { + JS_ThrowReferenceErrorNotDefined(ctx, prop); + goto fail; + } + + if (unlikely(!p)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "not an object"); + goto done; + } + + if (unlikely(!p->extensible)) { + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + goto done; + } + + if (p == JS_VALUE_GET_OBJ(obj)) { + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY && p->fast_array && + __JS_AtomIsTaggedInt(prop)) { + uint32_t idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + /* fast case */ + return add_fast_array_element(ctx, p, val, flags); + } + } + goto generic_create_prop; + } else { + pr = add_property(ctx, p, prop, JS_PROP_C_W_E); + if (!pr) + goto fail; + pr->u.value = val; + return TRUE; + } + } + + // TODO(bnoordhuis) return JSProperty slot and update in place + // when plain property (not is_exotic/setter/etc.) to avoid + // calling find_own_property() thrice? + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (ret < 0) + goto fail; + + if (ret) { + JS_FreeValue(ctx, desc.value); + if (desc.flags & JS_PROP_GETSET) { + JS_FreeValue(ctx, desc.getter); + JS_FreeValue(ctx, desc.setter); + ret = JS_ThrowTypeErrorOrFalse(ctx, flags, "setter is forbidden"); + goto done; + } else if (!(desc.flags & JS_PROP_WRITABLE) || + p->class_id == JS_CLASS_MODULE_NS) { + read_only_prop: + ret = JS_ThrowTypeErrorReadOnly(ctx, flags, prop); + goto done; + } + ret = JS_DefineProperty(ctx, this_obj, prop, val, + JS_UNDEFINED, JS_UNDEFINED, + JS_PROP_HAS_VALUE); + } else { + generic_create_prop: + ret = JS_CreateProperty(ctx, p, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | + JS_PROP_HAS_VALUE | + JS_PROP_HAS_ENUMERABLE | + JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_CONFIGURABLE | + JS_PROP_C_W_E); + } + +done: + JS_FreeValue(ctx, val); + return ret; +fail: + JS_FreeValue(ctx, val); + return -1; +} + +static int JS_SetPropertyInternal(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, int flags) +{ + return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, + flags, NULL); +} + +int JS_SetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop, JSValue val) +{ + return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, JS_PROP_THROW, NULL); +} + +// XXX(bnoordhuis) only used by OP_put_field_ic, maybe inline at call site +static int JS_SetPropertyInternalWithIC(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, int flags, + JSInlineCacheUpdate *icu) { + uint32_t tag, offset; + JSObject *p; + tag = JS_VALUE_GET_TAG(this_obj); + if (unlikely(tag != JS_TAG_OBJECT)) + goto slow_path; + p = JS_VALUE_GET_OBJ(this_obj); + offset = get_ic_prop_offset(icu, p->shape); + if (likely(offset != INLINE_CACHE_MISS)) { + set_value(ctx, &p->prop[offset].u.value, val); + return TRUE; + } +slow_path: + return JS_SetPropertyInternal2(ctx, this_obj, prop, val, this_obj, + flags, icu); +} + +/* flags can be JS_PROP_THROW or JS_PROP_THROW_STRICT */ +static int JS_SetPropertyValue(JSContext *ctx, JSValue this_obj, + JSValue prop, JSValue val, int flags) +{ + if (likely(JS_VALUE_GET_TAG(this_obj) == JS_TAG_OBJECT && + JS_VALUE_GET_TAG(prop) == JS_TAG_INT)) { + JSObject *p; + uint32_t idx; + double d; + int32_t v; + + /* fast path for array access */ + p = JS_VALUE_GET_OBJ(this_obj); + idx = JS_VALUE_GET_INT(prop); + switch(p->class_id) { + case JS_CLASS_ARRAY: + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + JSObject *p1; + JSShape *sh1; + + /* fast path to add an element to the array */ + if (idx != (uint32_t)p->u.array.count || + !p->fast_array || !p->extensible) + goto slow_path; + /* check if prototype chain has a numeric property */ + p1 = p->shape->proto; + while (p1 != NULL) { + sh1 = p1->shape; + if (p1->class_id == JS_CLASS_ARRAY) { + if (unlikely(!p1->fast_array)) + goto slow_path; + } else if (p1->class_id == JS_CLASS_OBJECT) { + if (unlikely(sh1->has_small_array_index)) + goto slow_path; + } else { + goto slow_path; + } + p1 = sh1->proto; + } + /* add element */ + return add_fast_array_element(ctx, p, val, flags); + } + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_ARGUMENTS: + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto slow_path; + set_value(ctx, &p->u.array.u.values[idx], val); + break; + case JS_CLASS_UINT8C_ARRAY: + if (JS_ToUint8ClampFree(ctx, &v, val)) + goto ta_cvt_fail; + /* Note: the conversion can detach the typed array, so the + array bound check must be done after */ + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint8_ptr[idx] = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint16_ptr[idx] = v; + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + if (JS_ToInt32Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint32_ptr[idx] = v; + break; + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + /* XXX: need specific conversion function */ + { + int64_t v; + if (JS_ToBigInt64Free(ctx, &v, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.uint64_ptr[idx] = v; + } + break; + case JS_CLASS_FLOAT16_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.fp16_ptr[idx] = tofp16(d); + break; + case JS_CLASS_FLOAT32_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) + goto ta_cvt_fail; + if (unlikely(idx >= (uint32_t)p->u.array.count)) + goto ta_out_of_bound; + p->u.array.u.float_ptr[idx] = d; + break; + case JS_CLASS_FLOAT64_ARRAY: + if (JS_ToFloat64Free(ctx, &d, val)) { + ta_cvt_fail: + if (flags & JS_PROP_REFLECT_DEFINE_PROPERTY) { + JS_FreeValue(ctx, JS_GetException(ctx)); + return FALSE; + } + return -1; + } + if (unlikely(idx >= (uint32_t)p->u.array.count)) { + ta_out_of_bound: + if (typed_array_is_oob(p)) + if (flags & JS_PROP_DEFINE_PROPERTY) + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound numeric index"); + return TRUE; // per spec: no OOB exception + } + p->u.array.u.double_ptr[idx] = d; + break; + default: + goto slow_path; + } + return TRUE; + } else { + JSAtom atom; + int ret; + slow_path: + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; + } +} + +int JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val) +{ + return JS_SetPropertyValue(ctx, this_obj, js_uint32(idx), val, + JS_PROP_THROW); +} + +int JS_SetPropertyInt64(JSContext *ctx, JSValue this_obj, + int64_t idx, JSValue val) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= INT32_MAX) { + /* fast path for fast arrays */ + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), val, + JS_PROP_THROW); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) { + JS_FreeValue(ctx, val); + return -1; + } + res = JS_SetProperty(ctx, this_obj, prop, val); + JS_FreeAtom(ctx, prop); + return res; +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +int JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *prop, JSValue val) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + ret = JS_SetPropertyInternal(ctx, this_obj, atom, val, JS_PROP_THROW); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* compute the property flags. For each flag: (JS_PROP_HAS_x forces + it, otherwise def_flags is used) + Note: makes assumption about the bit pattern of the flags +*/ +static int get_prop_flags(int flags, int def_flags) +{ + int mask; + mask = (flags >> JS_PROP_HAS_SHIFT) & JS_PROP_C_W_E; + return (flags & mask) | (def_flags & ~mask); +} + +static int JS_CreateProperty(JSContext *ctx, JSObject *p, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, + int flags) +{ + JSProperty *pr; + int ret, prop_flags; + + /* add a new property or modify an existing exotic one */ + if (p->is_exotic) { + if (p->class_id == JS_CLASS_ARRAY) { + uint32_t idx, len; + + if (p->fast_array) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx == p->u.array.count) { + if (!p->extensible) + goto not_extensible; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) + goto convert_to_array; + prop_flags = get_prop_flags(flags, 0); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_array; + return add_fast_array_element(ctx, p, + js_dup(val), flags); + } else { + goto convert_to_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + /* convert the fast array to normal array */ + convert_to_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + goto generic_array; + } + } else if (JS_AtomIsArrayIndex(ctx, &idx, prop)) { + JSProperty *plen; + JSShapeProperty *pslen; + generic_array: + /* update the length field */ + plen = &p->prop[0]; + JS_ToUint32(ctx, &len, plen->u.value); + if ((idx + 1) > len) { + pslen = get_shape_prop(p->shape); + if (unlikely(!(pslen->flags & JS_PROP_WRITABLE))) + return JS_ThrowTypeErrorReadOnly(ctx, flags, JS_ATOM_length); + /* XXX: should update the length after defining + the property */ + len = idx + 1; + set_value(ctx, &plen->u.value, js_uint32(len)); + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_AtomIsNumericIndex(ctx, prop); + if (ret != 0) { + if (ret < 0) + return -1; + return JS_ThrowTypeErrorOrFalse(ctx, flags, "cannot create numeric index in typed array"); + } + } else if (!(flags & JS_PROP_NO_EXOTIC)) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + if (em) { + if (em->define_own_property) { + return em->define_own_property(ctx, JS_MKPTR(JS_TAG_OBJECT, p), + prop, val, getter, setter, flags); + } + ret = JS_IsExtensible(ctx, JS_MKPTR(JS_TAG_OBJECT, p)); + if (ret < 0) + return -1; + if (!ret) + goto not_extensible; + } + } + } + + if (!p->extensible) { + not_extensible: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "object is not extensible"); + } + + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + prop_flags = (flags & (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + } else { + prop_flags = flags & JS_PROP_C_W_E; + } + pr = add_property(ctx, p, prop, prop_flags); + if (unlikely(!pr)) + return -1; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + pr->u.getset.getter = NULL; + if ((flags & JS_PROP_HAS_GET) && JS_IsFunction(ctx, getter)) { + pr->u.getset.getter = + JS_VALUE_GET_OBJ(js_dup(getter)); + } + pr->u.getset.setter = NULL; + if ((flags & JS_PROP_HAS_SET) && JS_IsFunction(ctx, setter)) { + pr->u.getset.setter = + JS_VALUE_GET_OBJ(js_dup(setter)); + } + } else { + if (flags & JS_PROP_HAS_VALUE) { + pr->u.value = js_dup(val); + } else { + pr->u.value = JS_UNDEFINED; + } + } + return TRUE; +} + +/* return FALSE if not OK */ +static BOOL check_define_prop_flags(int prop_flags, int flags) +{ + BOOL has_accessor, is_getset; + + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + if ((flags & (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) == + (JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE)) { + return FALSE; + } + if ((flags & JS_PROP_HAS_ENUMERABLE) && + (flags & JS_PROP_ENUMERABLE) != (prop_flags & JS_PROP_ENUMERABLE)) + return FALSE; + } + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (!(prop_flags & JS_PROP_CONFIGURABLE)) { + has_accessor = ((flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) != 0); + is_getset = ((prop_flags & JS_PROP_TMASK) == JS_PROP_GETSET); + if (has_accessor != is_getset) + return FALSE; + if (!has_accessor && !is_getset && !(prop_flags & JS_PROP_WRITABLE)) { + /* not writable: cannot set the writable bit */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) + return FALSE; + } + } + } + return TRUE; +} + +/* ensure that the shape can be safely modified */ +static int js_shape_prepare_update(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs) +{ + JSShape *sh; + uint32_t idx = 0; /* prevent warning */ + + sh = p->shape; + if (sh->is_hashed) { + if (sh->header.ref_count != 1) { + if (pprs) + idx = *pprs - get_shape_prop(sh); + /* clone the shape (the resulting one is no longer hashed) */ + sh = js_clone_shape(ctx, sh); + if (!sh) + return -1; + js_free_shape(ctx->rt, p->shape); + p->shape = sh; + if (pprs) + *pprs = get_shape_prop(sh) + idx; + } else { + js_shape_hash_unlink(ctx->rt, sh); + sh->is_hashed = FALSE; + } + } + return 0; +} + +static int js_update_property_flags(JSContext *ctx, JSObject *p, + JSShapeProperty **pprs, int flags) +{ + if (flags != (*pprs)->flags) { + if (js_shape_prepare_update(ctx, p, pprs)) + return -1; + (*pprs)->flags = flags; + } + return 0; +} + +/* allowed flags: + JS_PROP_CONFIGURABLE, JS_PROP_WRITABLE, JS_PROP_ENUMERABLE + JS_PROP_HAS_GET, JS_PROP_HAS_SET, JS_PROP_HAS_VALUE, + JS_PROP_HAS_CONFIGURABLE, JS_PROP_HAS_WRITABLE, JS_PROP_HAS_ENUMERABLE, + JS_PROP_THROW, JS_PROP_NO_EXOTIC. + If JS_PROP_THROW is set, return an exception instead of FALSE. + if JS_PROP_NO_EXOTIC is set, do not call the exotic + define_own_property callback. + return -1 (exception), FALSE or TRUE. +*/ +int JS_DefineProperty(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int mask, res; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + p = JS_VALUE_GET_OBJ(this_obj); + + redo_prop_update: + prs = find_own_property(&pr, p, prop); + if (prs) { + /* the range of the Array length property is always tested before */ + if ((prs->flags & JS_PROP_LENGTH) && (flags & JS_PROP_HAS_VALUE)) { + uint32_t array_length; + if (JS_ToArrayLengthFree(ctx, &array_length, + js_dup(val), FALSE)) { + return -1; + } + /* this code relies on the fact that Uint32 are never allocated */ + val = js_uint32(array_length); + /* prs may have been modified */ + prs = find_own_property(&pr, p, prop); + assert(prs != NULL); + } + /* property already exists */ + if (!check_define_prop_flags(prs->flags, flags)) { + not_configurable: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); + } + + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + /* Instantiate property and retry */ + if (JS_AutoInitProperty(ctx, p, prop, pr, prs)) + return -1; + goto redo_prop_update; + } + + if (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | + JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + JSObject *new_getter, *new_setter; + + if (JS_IsFunction(ctx, getter)) { + new_getter = JS_VALUE_GET_OBJ(getter); + } else { + new_getter = NULL; + } + if (JS_IsFunction(ctx, setter)) { + new_setter = JS_VALUE_GET_OBJ(setter); + } else { + new_setter = NULL; + } + + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_GETSET) { + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + /* convert to getset */ + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + free_var_ref(ctx->rt, pr->u.var_ref); + } else { + JS_FreeValue(ctx, pr->u.value); + } + prs->flags = (prs->flags & + (JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)) | + JS_PROP_GETSET; + pr->u.getset.getter = NULL; + pr->u.getset.setter = NULL; + } else { + if (!(prs->flags & JS_PROP_CONFIGURABLE)) { + if ((flags & JS_PROP_HAS_GET) && + new_getter != pr->u.getset.getter) { + goto not_configurable; + } + if ((flags & JS_PROP_HAS_SET) && + new_setter != pr->u.getset.setter) { + goto not_configurable; + } + } + } + if (flags & JS_PROP_HAS_GET) { + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (new_getter) + js_dup(getter); + pr->u.getset.getter = new_getter; + } + if (flags & JS_PROP_HAS_SET) { + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + if (new_setter) + js_dup(setter); + pr->u.getset.setter = new_setter; + } + } else { + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + /* convert to data descriptor */ + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + if (pr->u.getset.getter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.getter)); + if (pr->u.getset.setter) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_OBJECT, pr->u.getset.setter)); + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + pr->u.value = JS_UNDEFINED; + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + /* Note: JS_PROP_VARREF is always writable */ + } else { + if ((prs->flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && + (flags & JS_PROP_HAS_VALUE)) { + if (!js_same_value(ctx, val, pr->u.value)) { + goto not_configurable; + } else { + return TRUE; + } + } + } + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + if (flags & JS_PROP_HAS_VALUE) { + if (p->class_id == JS_CLASS_MODULE_NS) { + /* JS_PROP_WRITABLE is always true for variable + references, but they are write protected in module name + spaces. */ + if (!js_same_value(ctx, val, *pr->u.var_ref->pvalue)) + goto not_configurable; + } + /* update the reference */ + set_value(ctx, pr->u.var_ref->pvalue, + js_dup(val)); + } + /* if writable is set to false, no longer a + reference (for mapped arguments) */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == JS_PROP_HAS_WRITABLE) { + JSValue val1; + if (js_shape_prepare_update(ctx, p, &prs)) + return -1; + val1 = js_dup(*pr->u.var_ref->pvalue); + free_var_ref(ctx->rt, pr->u.var_ref); + pr->u.value = val1; + prs->flags &= ~(JS_PROP_TMASK | JS_PROP_WRITABLE); + } + } else if (prs->flags & JS_PROP_LENGTH) { + if (flags & JS_PROP_HAS_VALUE) { + /* Note: no JS code is executable because + 'val' is guaranted to be a Uint32 */ + res = set_array_length(ctx, p, js_dup(val), + flags); + } else { + res = TRUE; + } + /* still need to reset the writable flag if + needed. The JS_PROP_LENGTH is kept because the + Uint32 test is still done if the length + property is read-only. */ + if ((flags & (JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE)) == + JS_PROP_HAS_WRITABLE) { + prs = get_shape_prop(p->shape); + if (js_update_property_flags(ctx, p, &prs, + prs->flags & ~JS_PROP_WRITABLE)) + return -1; + } + return res; + } else { + if (flags & JS_PROP_HAS_VALUE) { + JS_FreeValue(ctx, pr->u.value); + pr->u.value = js_dup(val); + } + if (flags & JS_PROP_HAS_WRITABLE) { + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~JS_PROP_WRITABLE) | + (flags & JS_PROP_WRITABLE))) + return -1; + } + } + } + } + mask = 0; + if (flags & JS_PROP_HAS_CONFIGURABLE) + mask |= JS_PROP_CONFIGURABLE; + if (flags & JS_PROP_HAS_ENUMERABLE) + mask |= JS_PROP_ENUMERABLE; + if (js_update_property_flags(ctx, p, &prs, + (prs->flags & ~mask) | (flags & mask))) + return -1; + return TRUE; + } + + /* handle modification of fast array elements */ + if (p->fast_array) { + uint32_t idx; + uint32_t prop_flags; + if (p->class_id == JS_CLASS_ARRAY) { + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx < p->u.array.count) { + prop_flags = get_prop_flags(flags, JS_PROP_C_W_E); + if (prop_flags != JS_PROP_C_W_E) + goto convert_to_slow_array; + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + convert_to_slow_array: + if (convert_fast_array_to_array(ctx, p)) + return -1; + else + goto redo_prop_update; + } + if (flags & JS_PROP_HAS_VALUE) { + set_value(ctx, &p->u.array.u.values[idx], js_dup(val)); + } + return TRUE; + } + } + } else if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSValue num; + int ret; + + if (!__JS_AtomIsTaggedInt(prop)) { + /* slow path with to handle all numeric indexes */ + num = JS_AtomIsNumericIndex1(ctx, prop); + if (JS_IsUndefined(num)) + goto typed_array_done; + if (JS_IsException(num)) + return -1; + ret = JS_NumberIsInteger(ctx, num); + if (ret < 0) { + JS_FreeValue(ctx, num); + return -1; + } + if (!ret) { + JS_FreeValue(ctx, num); + return JS_ThrowTypeErrorOrFalse(ctx, flags, "non integer index in typed array"); + } + ret = JS_NumberIsNegativeOrMinusZero(ctx, num); + JS_FreeValue(ctx, num); + if (ret) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "negative index in typed array"); + } + if (!__JS_AtomIsTaggedInt(prop)) + goto typed_array_oob; + } + idx = __JS_AtomToUInt32(prop); + /* if the typed array is detached, p->u.array.count = 0 */ + if (idx >= typed_array_get_length(ctx, p)) { + typed_array_oob: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "out-of-bound index in typed array"); + } + prop_flags = get_prop_flags(flags, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET) || + prop_flags != (JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE)) { + return JS_ThrowTypeErrorOrFalse(ctx, flags, "invalid descriptor flags"); + } + if (flags & JS_PROP_HAS_VALUE) { + return JS_SetPropertyValue(ctx, this_obj, js_int32(idx), js_dup(val), flags); + } + return TRUE; + typed_array_done: ; + } + } + + return JS_CreateProperty(ctx, p, prop, val, getter, setter, flags); +} + +static int JS_DefineAutoInitProperty(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSAutoInitIDEnum id, + void *opaque, int flags) +{ + JSObject *p; + JSProperty *pr; + + if (JS_VALUE_GET_TAG(this_obj) != JS_TAG_OBJECT) + return FALSE; + + p = JS_VALUE_GET_OBJ(this_obj); + + if (find_own_property(&pr, p, prop)) { + /* property already exists */ + abort(); + return FALSE; + } + + /* Specialized CreateProperty */ + pr = add_property(ctx, p, prop, (flags & JS_PROP_C_W_E) | JS_PROP_AUTOINIT); + if (unlikely(!pr)) + return -1; + pr->u.init.realm_and_id = (uintptr_t)JS_DupContext(ctx); + assert((pr->u.init.realm_and_id & 3) == 0); + assert(id <= 3); + pr->u.init.realm_and_id |= id; + pr->u.init.opaque = opaque; + return TRUE; +} + +/* shortcut to add or redefine a new property value */ +int JS_DefinePropertyValue(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, val, JS_UNDEFINED, JS_UNDEFINED, + flags | JS_PROP_HAS_VALUE | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, val); + return ret; +} + +int JS_DefinePropertyValueValue(JSContext *ctx, JSValue this_obj, + JSValue prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_ValueToAtom(ctx, prop); + JS_FreeValue(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, val); + return -1; + } + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +int JS_DefinePropertyValueUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_uint32(idx), + val, flags); +} + +int JS_DefinePropertyValueInt64(JSContext *ctx, JSValue this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), + val, flags); +} + +/* `prop` may be pure ASCII or UTF-8 encoded */ +int JS_DefinePropertyValueStr(JSContext *ctx, JSValue this_obj, + const char *prop, JSValue val, int flags) +{ + JSAtom atom; + int ret; + atom = JS_NewAtom(ctx, prop); + ret = JS_DefinePropertyValue(ctx, this_obj, atom, val, flags); + JS_FreeAtom(ctx, atom); + return ret; +} + +/* shortcut to add getter & setter */ +int JS_DefinePropertyGetSet(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue getter, JSValue setter, + int flags) +{ + int ret; + ret = JS_DefineProperty(ctx, this_obj, prop, JS_UNDEFINED, getter, setter, + flags | JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE); + JS_FreeValue(ctx, getter); + JS_FreeValue(ctx, setter); + return ret; +} + +static int JS_CreateDataPropertyUint32(JSContext *ctx, JSValue this_obj, + int64_t idx, JSValue val, int flags) +{ + return JS_DefinePropertyValueValue(ctx, this_obj, js_int64(idx), + val, flags | JS_PROP_CONFIGURABLE | + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE); +} + + +/* return TRUE if 'obj' has a non empty 'name' string */ +static BOOL js_object_has_name(JSContext *ctx, JSValue obj) +{ + JSProperty *pr; + JSShapeProperty *prs; + JSValue val; + JSString *p; + + prs = find_own_property(&pr, JS_VALUE_GET_OBJ(obj), JS_ATOM_name); + if (!prs) + return FALSE; + if ((prs->flags & JS_PROP_TMASK) != JS_PROP_NORMAL) + return TRUE; + val = pr->u.value; + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + return TRUE; + p = JS_VALUE_GET_STRING(val); + return (p->len != 0); +} + +static int JS_DefineObjectName(JSContext *ctx, JSValue obj, + JSAtom name, int flags) +{ + if (name != JS_ATOM_NULL + && JS_IsObject(obj) + && !js_object_has_name(ctx, obj) + && JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, JS_AtomToString(ctx, name), flags) < 0) { + return -1; + } + return 0; +} + +static int JS_DefineObjectNameComputed(JSContext *ctx, JSValue obj, + JSValue str, int flags) +{ + if (JS_IsObject(obj) && + !js_object_has_name(ctx, obj)) { + JSAtom prop; + JSValue name_str; + prop = JS_ValueToAtom(ctx, str); + if (prop == JS_ATOM_NULL) + return -1; + name_str = js_get_function_name(ctx, prop); + JS_FreeAtom(ctx, prop); + if (JS_IsException(name_str)) + return -1; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_name, name_str, flags) < 0) + return -1; + } + return 0; +} + +#define DEFINE_GLOBAL_LEX_VAR (1 << 7) +#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) + +static JSValue JS_ThrowSyntaxErrorVarRedeclaration(JSContext *ctx, JSAtom prop) +{ + return JS_ThrowSyntaxErrorAtom(ctx, "redeclaration of '%s'", prop); +} + +/* flags is 0, DEFINE_GLOBAL_LEX_VAR or DEFINE_GLOBAL_FUNC_VAR */ +/* XXX: could support exotic global object. */ +static int JS_CheckDefineGlobalVar(JSContext *ctx, JSAtom prop, int flags) +{ + JSObject *p; + JSShapeProperty *prs; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + /* XXX: should handle JS_PROP_AUTOINIT */ + if (flags & DEFINE_GLOBAL_LEX_VAR) { + if (prs && !(prs->flags & JS_PROP_CONFIGURABLE)) + goto fail_redeclaration; + } else { + if (!prs && !p->extensible) + goto define_error; + if (flags & DEFINE_GLOBAL_FUNC_VAR) { + if (prs) { + if (!(prs->flags & JS_PROP_CONFIGURABLE) && + ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET || + ((prs->flags & (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)) != + (JS_PROP_WRITABLE | JS_PROP_ENUMERABLE)))) { + define_error: + JS_ThrowTypeErrorAtom(ctx, "cannot define variable '%s'", + prop); + return -1; + } + } + } + } + /* check if there already is a lexical declaration */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + fail_redeclaration: + JS_ThrowSyntaxErrorVarRedeclaration(ctx, prop); + return -1; + } + return 0; +} + +/* def_flags is (0, DEFINE_GLOBAL_LEX_VAR) | + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalVar(JSContext *ctx, JSAtom prop, int def_flags) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + JSValue val; + int flags; + + if (def_flags & DEFINE_GLOBAL_LEX_VAR) { + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + flags = JS_PROP_ENUMERABLE | (def_flags & JS_PROP_WRITABLE) | + JS_PROP_CONFIGURABLE; + val = JS_UNINITIALIZED; + } else { + p = JS_VALUE_GET_OBJ(ctx->global_obj); + flags = JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | + (def_flags & JS_PROP_CONFIGURABLE); + val = JS_UNDEFINED; + } + prs = find_own_property1(p, prop); + if (prs) + return 0; + if (!p->extensible) + return 0; + pr = add_property(ctx, p, prop, flags); + if (unlikely(!pr)) + return -1; + pr->u.value = val; + return 0; +} + +/* 'def_flags' is 0 or JS_PROP_CONFIGURABLE. */ +/* XXX: could support exotic global object. */ +static int JS_DefineGlobalFunction(JSContext *ctx, JSAtom prop, + JSValue func, int def_flags) +{ + + JSObject *p; + JSShapeProperty *prs; + int flags; + + p = JS_VALUE_GET_OBJ(ctx->global_obj); + prs = find_own_property1(p, prop); + flags = JS_PROP_HAS_VALUE | JS_PROP_THROW; + if (!prs || (prs->flags & JS_PROP_CONFIGURABLE)) { + flags |= JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | def_flags | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_WRITABLE | JS_PROP_HAS_ENUMERABLE; + } + if (JS_DefineProperty(ctx, ctx->global_obj, prop, func, + JS_UNDEFINED, JS_UNDEFINED, flags) < 0) + return -1; + return 0; +} + +static JSValue JS_GetGlobalVar(JSContext *ctx, JSAtom prop, + BOOL throw_ref_error) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_TMASK properties */ + if (unlikely(JS_IsUninitialized(pr->u.value))) + return JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return js_dup(pr->u.value); + } + return JS_GetPropertyInternal(ctx, ctx->global_obj, prop, + ctx->global_obj, throw_ref_error); +} + +/* construct a reference to a global variable */ +static int JS_GetGlobalVarRef(JSContext *ctx, JSAtom prop, JSValue *sp) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + /* XXX: conformance: do these tests in + OP_put_var_ref/OP_get_var_ref ? */ + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + sp[0] = js_dup(ctx->global_var_obj); + } else { + int ret; + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + if (ret) { + sp[0] = js_dup(ctx->global_obj); + } else { + sp[0] = JS_UNDEFINED; + } + } + sp[1] = JS_AtomToValue(ctx, prop); + return 0; +} + +/* use for strict variable access: test if the variable exists */ +static int JS_CheckGlobalVar(JSContext *ctx, JSAtom prop) +{ + JSObject *p; + JSShapeProperty *prs; + int ret; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property1(p, prop); + if (prs) { + ret = TRUE; + } else { + ret = JS_HasProperty(ctx, ctx->global_obj, prop); + if (ret < 0) + return -1; + } + return ret; +} + +/* flag = 0: normal variable write + flag = 1: initialize lexical variable + flag = 2: normal variable write, strict check was done before +*/ +static int JS_SetGlobalVar(JSContext *ctx, JSAtom prop, JSValue val, + int flag) +{ + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + int flags; + + /* no exotic behavior is possible in global_var_obj */ + p = JS_VALUE_GET_OBJ(ctx->global_var_obj); + prs = find_own_property(&pr, p, prop); + if (prs) { + /* XXX: should handle JS_PROP_AUTOINIT properties? */ + if (flag != 1) { + if (unlikely(JS_IsUninitialized(pr->u.value))) { + JS_FreeValue(ctx, val); + JS_ThrowReferenceErrorUninitialized(ctx, prs->atom); + return -1; + } + if (unlikely(!(prs->flags & JS_PROP_WRITABLE))) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, prop); + } + } + set_value(ctx, &pr->u.value, val); + return 0; + } + flags = JS_PROP_THROW_STRICT; + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + return JS_SetPropertyInternal(ctx, ctx->global_obj, prop, val, flags); +} + +/* return -1, FALSE or TRUE. return FALSE if not configurable or + invalid object. return -1 in case of exception. + flags can be 0, JS_PROP_THROW or JS_PROP_THROW_STRICT */ +int JS_DeleteProperty(JSContext *ctx, JSValue obj, JSAtom prop, int flags) +{ + JSValue obj1; + JSObject *p; + int res; + + obj1 = JS_ToObject(ctx, obj); + if (JS_IsException(obj1)) + return -1; + p = JS_VALUE_GET_OBJ(obj1); + res = delete_property(ctx, p, prop); + JS_FreeValue(ctx, obj1); + if (res != FALSE) + return res; + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "could not delete property"); + return -1; + } + return FALSE; +} + +int JS_DeletePropertyInt64(JSContext *ctx, JSValue obj, int64_t idx, int flags) +{ + JSAtom prop; + int res; + + if ((uint64_t)idx <= JS_ATOM_MAX_INT) { + /* fast path for fast arrays */ + return JS_DeleteProperty(ctx, obj, __JS_AtomFromUInt32(idx), flags); + } + prop = JS_NewAtomInt64(ctx, idx); + if (prop == JS_ATOM_NULL) + return -1; + res = JS_DeleteProperty(ctx, obj, prop, flags); + JS_FreeAtom(ctx, prop); + return res; +} + +BOOL JS_IsFunction(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + switch(p->class_id) { + case JS_CLASS_BYTECODE_FUNCTION: + return TRUE; + case JS_CLASS_PROXY: + return p->u.proxy_data->is_func; + default: + return (ctx->rt->class_array[p->class_id].call != NULL); + } +} + +BOOL JS_IsCFunction(JSContext *ctx, JSValue val, JSCFunction *func, int magic) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_C_FUNCTION) + return (p->u.cfunc.c_function.generic == func && p->u.cfunc.magic == magic); + else + return FALSE; +} + +BOOL JS_IsConstructor(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return p->is_constructor; +} + +BOOL JS_SetConstructorBit(JSContext *ctx, JSValue func_obj, BOOL val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(func_obj); + p->is_constructor = val; + return TRUE; +} + +BOOL JS_IsError(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return (p->class_id == JS_CLASS_ERROR); +} + +/* used to avoid catching interrupt exceptions */ +BOOL JS_IsUncatchableError(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(val); + return p->class_id == JS_CLASS_ERROR && p->is_uncatchable_error; +} + +void JS_SetUncatchableError(JSContext *ctx, JSValue val, BOOL flag) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(val); + if (p->class_id == JS_CLASS_ERROR) + p->is_uncatchable_error = flag; +} + +void JS_ResetUncatchableError(JSContext *ctx) +{ + JS_SetUncatchableError(ctx, ctx->rt->current_exception, FALSE); +} + +int JS_SetOpaque(JSValue obj, void *opaque) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); +#if 0 + // User code can't set the opaque of internal objects. + if (p->class_id >= JS_CLASS_INIT_COUNT) { + p->u.opaque = opaque; + return 0; + } +#endif + /* + * (Monoucha) temporary band-aid until there is a way to change + * the global object's class. + * ref. https://github.com/quickjs-ng/quickjs/issues/695 + */ + p = JS_VALUE_GET_OBJ(obj); + p->u.opaque = opaque; + } + + return -1; +} + +/* |obj| must be a JSObject of an internal class. */ +static void JS_SetOpaqueInternal(JSValue obj, void *opaque) +{ + JSObject *p; + assert(JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT); + p = JS_VALUE_GET_OBJ(obj); + assert(p->class_id < JS_CLASS_INIT_COUNT); + p->u.opaque = opaque; +} + +/* return NULL if not an object of class class_id */ +void *JS_GetOpaque(JSValue obj, JSClassID class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return NULL; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != class_id) + return NULL; + return p->u.opaque; +} + +void *JS_GetOpaque2(JSContext *ctx, JSValue obj, JSClassID class_id) +{ + void *p = JS_GetOpaque(obj, class_id); + if (unlikely(!p)) { + JS_ThrowTypeErrorInvalidClass(ctx, class_id); + } + return p; +} + +void *JS_GetAnyOpaque(JSValue obj, JSClassID *class_id) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + *class_id = 0; + return NULL; + } + p = JS_VALUE_GET_OBJ(obj); + *class_id = p->class_id; + return p->u.opaque; +} + +static JSValue JS_ToPrimitiveFree(JSContext *ctx, JSValue val, int hint) +{ + int i; + BOOL force_ordinary; + + JSAtom method_name; + JSValue method, ret; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) + return val; + force_ordinary = hint & HINT_FORCE_ORDINARY; + hint &= ~HINT_FORCE_ORDINARY; + if (!force_ordinary) { + method = JS_GetProperty(ctx, val, JS_ATOM_Symbol_toPrimitive); + if (JS_IsException(method)) + goto exception; + /* ECMA says *If exoticToPrim is not undefined* but tests in + test262 use null as a non callable converter */ + if (!JS_IsUndefined(method) && !JS_IsNull(method)) { + JSAtom atom; + JSValue arg; + switch(hint) { + case HINT_STRING: + atom = JS_ATOM_string; + break; + case HINT_NUMBER: + atom = JS_ATOM_number; + break; + default: + case HINT_NONE: + atom = JS_ATOM_default; + break; + } + arg = JS_AtomToString(ctx, atom); + ret = JS_CallFree(ctx, method, val, 1, &arg); + JS_FreeValue(ctx, arg); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) + return ret; + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "toPrimitive"); + } + } + if (hint != HINT_STRING) + hint = HINT_NUMBER; + for(i = 0; i < 2; i++) { + if ((i ^ hint) == 0) { + method_name = JS_ATOM_toString; + } else { + method_name = JS_ATOM_valueOf; + } + method = JS_GetProperty(ctx, val, method_name); + if (JS_IsException(method)) + goto exception; + if (JS_IsFunction(ctx, method)) { + ret = JS_CallFree(ctx, method, val, 0, NULL); + if (JS_IsException(ret)) + goto exception; + if (JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, val); + return ret; + } + JS_FreeValue(ctx, ret); + } else { + JS_FreeValue(ctx, method); + } + } + JS_ThrowTypeError(ctx, "toPrimitive"); +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue JS_ToPrimitive(JSContext *ctx, JSValue val, int hint) +{ + return JS_ToPrimitiveFree(ctx, js_dup(val), hint); +} + +void JS_SetIsHTMLDDA(JSContext *ctx, JSValue obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return; + p = JS_VALUE_GET_OBJ(obj); + p->is_HTMLDDA = TRUE; +} + +static inline BOOL JS_IsHTMLDDA(JSContext *ctx, JSValue obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return FALSE; + p = JS_VALUE_GET_OBJ(obj); + return p->is_HTMLDDA; +} + +static int JS_ToBoolFree(JSContext *ctx, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + return JS_VALUE_GET_INT(val) != 0; + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_VALUE_GET_INT(val); + case JS_TAG_EXCEPTION: + return -1; + case JS_TAG_STRING: + { + BOOL ret = JS_VALUE_GET_STRING(val)->len != 0; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + BOOL ret; + ret = p->num.expn != BF_EXP_ZERO && p->num.expn != BF_EXP_NAN; + JS_FreeValue(ctx, val); + return ret; + } + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + BOOL ret; + ret = !p->is_HTMLDDA; + JS_FreeValue(ctx, val); + return ret; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d = JS_VALUE_GET_FLOAT64(val); + return !isnan(d) && d != 0; + } else { + JS_FreeValue(ctx, val); + return TRUE; + } + } +} + +int JS_ToBool(JSContext *ctx, JSValue val) +{ + return JS_ToBoolFree(ctx, js_dup(val)); +} + +/* pc points to pure ASCII or UTF-8, null terminated contents */ +static int skip_spaces(const char *pc) +{ + const uint8_t *p, *p_next, *p_start; + uint32_t c; + + p = p_start = (const uint8_t *)pc; + for (;;) { + c = *p++; + if (c < 0x80) { + if (!((c >= 0x09 && c <= 0x0d) || (c == 0x20))) + break; + } else { + c = utf8_decode(p - 1, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not a space */ + if (!lre_is_space(c)) + break; + p = p_next; + } + } + return p - 1 - p_start; +} + +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; +} + +/* XXX: remove */ +static double js_strtod(const char *str, int radix, BOOL is_float) +{ + double d; + int c; + + if (!is_float || radix != 10) { + const char *p = str; + uint64_t n_max, n; + int int_exp, is_neg; + + is_neg = 0; + if (*p == '-') { + is_neg = 1; + p++; + } + + /* skip leading zeros */ + while (*p == '0') + p++; + n = 0; + if (radix == 10) + n_max = ((uint64_t)-1 - 9) / 10; /* most common case */ + else + n_max = ((uint64_t)-1 - (radix - 1)) / radix; + /* XXX: could be more precise */ + int_exp = 0; + while ((c = to_digit(*p)) < radix) { + if (n <= n_max) { + n = n * radix + c; + } else { + if (radix == 10) + goto strtod_case; + int_exp++; + } + p++; + } + d = n; + if (int_exp != 0) { + d *= pow(radix, int_exp); + } + if (is_neg) + d = -d; + } else { + strtod_case: + d = strtod(str, NULL); + } + return d; +} + +static JSValue js_string_to_bigint(JSContext *ctx, const char *buf, int radix) +{ + bf_t *a; + int ret; + JSValue val; + val = JS_NewBigInt(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigInt(val); + ret = bf_atof(a, buf, NULL, radix, BF_PREC_INF, BF_RNDZ); + if (ret & BF_ST_MEM_ERROR) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + return JS_CompactBigInt1(ctx, val); +} + +/* `js_atof(ctx, p, len, pp, radix, flags)` + Convert the string pointed to by `p` to a number value. + Return an exception in case of memory error. + Return `JS_NAN` if invalid syntax. + - `p` points to a null terminated UTF-8 encoded char array, + - `len` the length of the array, + - `pp` if not null receives a pointer to the next character, + - `radix` must be in range 2 to 36, else return `JS_NAN`. + - `flags` is a combination of the flags below. + There is a null byte at `p[len]`, but there might be embedded null + bytes between `p[0]` and `p[len]` which must produce `JS_NAN` if + the `ATOD_NO_TRAILING_CHARS` flag is present. + */ + +#define ATOD_TRIM_SPACES (1 << 0) /* trim white space */ +#define ATOD_ACCEPT_EMPTY (1 << 1) /* accept an empty string, value is 0 */ +#define ATOD_ACCEPT_FLOAT (1 << 2) /* parse decimal floating point syntax */ +#define ATOD_ACCEPT_INFINITY (1 << 3) /* parse Infinity as a float point number */ +#define ATOD_ACCEPT_BIN_OCT (1 << 4) /* accept 0o and 0b prefixes */ +#define ATOD_ACCEPT_HEX_PREFIX (1 << 5) /* accept 0x prefix for radix 16 */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 6) /* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_SUFFIX (1 << 7) /* allow 'n' suffix to produce BigInt */ +#define ATOD_WANT_BIG_INT (1 << 8) /* return type must be BigInt */ +#define ATOD_DECIMAL_AFTER_SIGN (1 << 9) /* only accept decimal number after sign */ +#define ATOD_NO_TRAILING_CHARS (1 << 10) /* do not accept trailing characters */ + +static JSValue js_atof(JSContext *ctx, const char *p, size_t len, + const char **pp, int radix, int flags) +{ + const char *p_start; + const char *end = p + len; + int sep; + BOOL is_float; + char buf1[64], *buf = buf1; + size_t i, j; + JSValue val = JS_NAN; + double d; + char sign; + + if (radix < 2 || radix > 36) + goto done; + + /* optional separator between digits */ + sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + sign = 0; + if (flags & ATOD_TRIM_SPACES) + p += skip_spaces(p); + if (p == end && (flags & ATOD_ACCEPT_EMPTY)) { + if (pp) *pp = p; + if (flags & ATOD_WANT_BIG_INT) + return JS_NewBigInt64(ctx, 0); + else + return js_int32(0); + } + if (*p == '+' || *p == '-') { + sign = *p; + p++; + if (flags & ATOD_DECIMAL_AFTER_SIGN) + flags &= ~(ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT); + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && + ((flags & ATOD_ACCEPT_HEX_PREFIX) || radix == 16)) { + p += 2; + radix = 16; + } else if (flags & ATOD_ACCEPT_BIN_OCT) { + if (p[1] == 'o' || p[1] == 'O') { + p += 2; + radix = 8; + } else if (p[1] == 'b' || p[1] == 'B') { + p += 2; + radix = 2; + } + } + } else { + if (*p == 'I' && (flags & ATOD_ACCEPT_INFINITY) && js__strstart(p, "Infinity", &p)) { + d = INF; + if (sign == '-') + d = -d; + val = js_float64(d); + goto done; + } + } + is_float = FALSE; + p_start = p; + while (to_digit(*p) < radix) { + p++; + if (*p == sep && to_digit(p[1]) < radix) + p++; + } + if ((flags & ATOD_ACCEPT_FLOAT) && radix == 10) { + if (*p == '.' && (p > p_start || to_digit(p[1]) < radix)) { + is_float = TRUE; + p++; + while (to_digit(*p) < radix) { + p++; + if (*p == sep && to_digit(p[1]) < radix) + p++; + } + } + if (p > p_start && (*p == 'e' || *p == 'E')) { + i = 1; + if (p[1] == '+' || p[1] == '-') { + i++; + } + if (is_digit(p[i])) { + is_float = TRUE; + p += i + 1; + while (is_digit(*p) || (*p == sep && is_digit(p[1]))) + p++; + } + } + } + if (p == p_start) + goto done; + + len = p - p_start; + if (unlikely((len + 2) > sizeof(buf1))) { + buf = js_malloc_rt(ctx->rt, len + 2); /* no exception raised */ + if (!buf) { + if (pp) *pp = p; + return JS_ThrowOutOfMemory(ctx); + } + } + /* remove the separators and the radix prefix */ + j = 0; + if (sign == '-') + buf[j++] = '-'; + for (i = 0; i < len; i++) { + if (p_start[i] != '_') + buf[j++] = p_start[i]; + } + buf[j] = '\0'; + + if (flags & ATOD_ACCEPT_SUFFIX) { + if (*p == 'n') { + p++; + flags |= ATOD_WANT_BIG_INT; + } + } + + if (flags & ATOD_WANT_BIG_INT) { + if (!is_float) + val = js_string_to_bigint(ctx, buf, radix); + } else { + d = js_strtod(buf, radix, is_float); + val = js_number(d); /* return int or float64 */ + } + + done: + if (flags & ATOD_NO_TRAILING_CHARS) { + if (flags & ATOD_TRIM_SPACES) + p += skip_spaces(p); + if (p != end) { + JS_FreeValue(ctx, val); + val = JS_NAN; + } + } + if (buf != buf1) + js_free_rt(ctx->rt, buf); + if (pp) *pp = p; + return val; +} + +typedef enum JSToNumberHintEnum { + TON_FLAG_NUMBER, + TON_FLAG_NUMERIC, +} JSToNumberHintEnum; + +static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val, + JSToNumberHintEnum flag) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_BIG_INT: + if (flag != TON_FLAG_NUMERIC) { + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert BigInt to number"); + } + ret = val; + break; + case JS_TAG_FLOAT64: + case JS_TAG_INT: + case JS_TAG_EXCEPTION: + ret = val; + break; + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = js_int32(JS_VALUE_GET_INT(val)); + break; + case JS_TAG_UNDEFINED: + ret = JS_NAN; + break; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + goto redo; + case JS_TAG_STRING: + { + const char *str; + size_t len; + int flags; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY | + ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY | + ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | + ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS; + ret = js_atof(ctx, str, len, NULL, 10, flags); + JS_FreeCString(ctx, str); + } + break; + case JS_TAG_SYMBOL: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert symbol to number"); + default: + JS_FreeValue(ctx, val); + ret = JS_NAN; + break; + } + return ret; +} + +static JSValue JS_ToNumberFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMBER); +} + +static JSValue JS_ToNumericFree(JSContext *ctx, JSValue val) +{ + return JS_ToNumberHintFree(ctx, val, TON_FLAG_NUMERIC); +} + +static JSValue JS_ToNumeric(JSContext *ctx, JSValue val) +{ + return JS_ToNumericFree(ctx, js_dup(val)); +} + +static __exception int __JS_ToFloat64Free(JSContext *ctx, double *pres, + JSValue val) +{ + double d; + uint32_t tag; + + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = JS_FLOAT64_NAN; + return -1; + } + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + d = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(val); + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + /* XXX: there can be a double rounding issue with some + primitives (such as JS_ToUint8ClampFree()), but it is + not critical to fix it. */ + bf_get_float64(&p->num, &d, BF_RNDN); + JS_FreeValue(ctx, val); + } + break; + default: + abort(); + } + *pres = d; + return 0; +} + +static inline int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_TAG(val); + if (tag <= JS_TAG_NULL) { + *pres = JS_VALUE_GET_INT(val); + return 0; + } else if (JS_TAG_IS_FLOAT64(tag)) { + *pres = JS_VALUE_GET_FLOAT64(val); + return 0; + } else { + return __JS_ToFloat64Free(ctx, pres, val); + } +} + +int JS_ToFloat64(JSContext *ctx, double *pres, JSValue val) +{ + return JS_ToFloat64Free(ctx, pres, js_dup(val)); +} + +static JSValue JS_ToNumber(JSContext *ctx, JSValue val) +{ + return JS_ToNumberFree(ctx, js_dup(val)); +} + +/* same as JS_ToNumber() but return 0 in case of NaN/Undefined */ +static __maybe_unused JSValue JS_ToIntegerFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + JSValue ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = js_int32(JS_VALUE_GET_INT(val)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = js_int32(0); + } else { + /* convert -0 to +0 */ + d = trunc(d) + 0.0; + ret = js_number(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return val; + goto redo; + } + return ret; +} + +/* Note: the integer value is satured to 32 bits */ +static int JS_ToInt32SatFree(JSContext *ctx, int *pres, JSValue val) +{ + uint32_t tag; + int ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + ret = 0; + } else { + if (d < INT32_MIN) + ret = INT32_MIN; + else if (d > INT32_MAX) + ret = INT32_MAX; + else + ret = (int)d; + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32Sat(JSContext *ctx, int *pres, JSValue val) +{ + return JS_ToInt32SatFree(ctx, pres, js_dup(val)); +} + +int JS_ToInt32Clamp(JSContext *ctx, int *pres, JSValue val, + int min, int max, int min_offset) +{ + int res = JS_ToInt32SatFree(ctx, pres, js_dup(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_ToInt64SatFree(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + *pres = JS_VALUE_GET_INT(val); + return 0; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + *pres = 0; + } else { + if (d < INT64_MIN) + *pres = INT64_MIN; + else if (d >= 0x1p63) + *pres = INT64_MAX; + else + *pres = (int64_t)d; + } + } + return 0; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } +} + +int JS_ToInt64Sat(JSContext *ctx, int64_t *pres, JSValue val) +{ + return JS_ToInt64SatFree(ctx, pres, js_dup(val)); +} + +int JS_ToInt64Clamp(JSContext *ctx, int64_t *pres, JSValue val, + int64_t min, int64_t max, int64_t neg_offset) +{ + int res = JS_ToInt64SatFree(ctx, pres, js_dup(val)); + if (res == 0) { + if (*pres < 0) + *pres += neg_offset; + if (*pres < min) + *pres = min; + else if (*pres > max) + *pres = max; + } + return res; +} + +/* Same as JS_ToInt32Free() but with a 64 bit result. Return (<0, 0) + in case of exception */ +static int JS_ToInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + uint32_t tag; + int64_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^64) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 62))) { + /* fast case */ + ret = (int64_t)d; + } else if (e <= (1023 + 62 + 53)) { + uint64_t v; + /* remainder modulo 2^64 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + ret = v << ((e - 1023) - 52); + /* take the sign into account */ + if (u.u64 >> 63) + if (ret != INT64_MIN) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValue val) +{ + return JS_ToInt64Free(ctx, pres, js_dup(val)); +} + +int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValue val) +{ + if (JS_IsBigInt(ctx, val)) + return JS_ToBigInt64(ctx, pres, val); + else + return JS_ToInt64(ctx, pres, val); +} + +/* return (<0, 0) in case of exception */ +static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int32_t ret; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_VALUE_GET_INT(val); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64(val); + u.d = d; + /* we avoid doing fmod(x, 2^32) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely(e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u.u64 & (((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.u64 >> 63) + if (ret != INT32_MIN) + ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValue val) +{ + return JS_ToInt32Free(ctx, pres, js_dup(val)); +} + +static inline int JS_ToUint32Free(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32Free(ctx, (int32_t *)pres, val); +} + +static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val) +{ + uint32_t tag; + int res; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = JS_VALUE_GET_INT(val); + res = max_int(0, min_int(255, res)); + break; + case JS_TAG_FLOAT64: + { + double d = JS_VALUE_GET_FLOAT64(val); + if (isnan(d)) { + res = 0; + } else { + if (d < 0) + res = 0; + else if (d > 255) + res = 255; + else + res = lrint(d); + } + } + break; + default: + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = res; + return 0; +} + +static __exception int JS_ToArrayLengthFree(JSContext *ctx, uint32_t *plen, + JSValue val, BOOL is_array_ctor) +{ + uint32_t tag, len; + + tag = JS_VALUE_GET_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + { + int v; + v = JS_VALUE_GET_INT(val); + if (v < 0) + goto fail; + len = v; + } + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + bf_t a; + BOOL res; + bf_get_int32((int32_t *)&len, &p->num, BF_GET_INT_MOD); + bf_init(ctx->bf_ctx, &a); + bf_set_ui(&a, len); + res = bf_cmp_eq(&a, &p->num); + bf_delete(&a); + JS_FreeValue(ctx, val); + if (!res) + goto fail; + } + break; + default: + if (JS_TAG_IS_FLOAT64(tag)) { + double d; + d = JS_VALUE_GET_FLOAT64(val); + if (!(d >= 0 && d <= UINT32_MAX)) + goto fail; + len = (uint32_t)d; + if (len != d) + goto fail; + } else { + uint32_t len1; + + if (is_array_ctor) { + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len, val, TRUE)) + return -1; + } else { + /* legacy behavior: must do the conversion twice and compare */ + if (JS_ToUint32(ctx, &len, val)) { + JS_FreeValue(ctx, val); + return -1; + } + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + return -1; + /* cannot recurse because val is a number */ + if (JS_ToArrayLengthFree(ctx, &len1, val, FALSE)) + return -1; + if (len1 != len) { + fail: + JS_ThrowRangeError(ctx, "invalid array length"); + return -1; + } + } + } + break; + } + *plen = len; + return 0; +} + +#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) + +static BOOL is_safe_integer(double d) +{ + return isfinite(d) && floor(d) == d && + fabs(d) <= (double)MAX_SAFE_INTEGER; +} + +int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val) +{ + int64_t v; + if (JS_ToInt64Sat(ctx, &v, val)) + return -1; + if (v < 0 || v > MAX_SAFE_INTEGER) { + JS_ThrowRangeError(ctx, "invalid array index"); + *plen = 0; + return -1; + } + *plen = v; + return 0; +} + +/* convert a value to a length between 0 and MAX_SAFE_INTEGER. + return -1 for exception */ +static __exception int JS_ToLengthFree(JSContext *ctx, int64_t *plen, + JSValue val) +{ + int res = JS_ToInt64Clamp(ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); + JS_FreeValue(ctx, val); + return res; +} + +/* Note: can return an exception */ +static int JS_NumberIsInteger(JSContext *ctx, JSValue val) +{ + double d; + if (!JS_IsNumber(val)) + return FALSE; + if (unlikely(JS_ToFloat64(ctx, &d, val))) + return -1; + return isfinite(d) && floor(d) == d; +} + +static BOOL JS_NumberIsNegativeOrMinusZero(JSContext *ctx, JSValue val) +{ + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + { + int v; + v = JS_VALUE_GET_INT(val); + return (v < 0); + } + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + u.d = JS_VALUE_GET_FLOAT64(val); + return (u.u64 >> 63); + } + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + /* Note: integer zeros are not necessarily positive */ + return p->num.sign && !bf_is_zero(&p->num); + } + default: + return FALSE; + } +} + +static JSValue js_bigint_to_string1(JSContext *ctx, JSValue val, int radix) +{ + JSValue ret; + bf_t a_s, *a; + char *str; + int saved_sign; + size_t len; + + a = JS_ToBigInt(ctx, &a_s, val); + if (!a) + return JS_EXCEPTION; + saved_sign = a->sign; + if (a->expn == BF_EXP_ZERO) + a->sign = 0; + str = bf_ftoa(&len, a, radix, 0, BF_RNDZ | BF_FTOA_FORMAT_FRAC | + BF_FTOA_JS_QUIRKS); + a->sign = saved_sign; + JS_FreeBigInt(ctx, a, &a_s); + if (!str) + return JS_ThrowOutOfMemory(ctx); + ret = js_new_string8_len(ctx, str, len); + bf_free(ctx->bf_ctx, str); + return ret; +} + +static JSValue js_bigint_to_string(JSContext *ctx, JSValue val) +{ + return js_bigint_to_string1(ctx, val, 10); +} + +/*---- floating point number to string conversions ----*/ + +/* JavaScript rounding is specified as round to nearest tie away + from zero (RNDNA), but in `printf` the "ties" case is not + specified (in most cases it is RNDN, round to nearest, tie to even), + so we must round manually. We generate 2 extra places and make + an extra call to snprintf if these are exactly '50'. + We set the current rounding mode to FE_DOWNWARD to check if the + last 2 places become '49'. If not, we must round up, which is + performed in place using the string digits. + + Note that we cannot rely on snprintf for rounding up: + the code below fails on macOS for `0.5.toFixed(0)`: gives `0` expected `1` + fesetround(FE_UPWARD); + snprintf(dest, size, "%.*f", n_digits, d); + fesetround(FE_TONEAREST); + */ + +/* `js_fcvt` minimum buffer length: + - up to 21 digits in integral part + - 1 potential decimal point + - up to 102 decimals + - 1 null terminator + */ +#define JS_FCVT_BUF_SIZE (21+1+102+1) + +/* `js_ecvt` minimum buffer length: + - 1 leading digit + - 1 potential decimal point + - up to 102 decimals + - 5 exponent characters (from 'e-324' to 'e+308') + - 1 null terminator + */ +#define JS_ECVT_BUF_SIZE (1+1+102+5+1) + +/* `js_dtoa` minimum buffer length: + - 8 byte prefix + - either JS_FCVT_BUF_SIZE or JS_ECVT_BUF_SIZE + - JS_FCVT_BUF_SIZE is larger than JS_ECVT_BUF_SIZE + */ +#define JS_DTOA_BUF_SIZE (8+JS_FCVT_BUF_SIZE) + +/* `js_ecvt1`: compute the digits and decimal point spot for a double + - `d` is finite, positive or zero + - `n_digits` number of significant digits in range 1..103 + - `buf` receives the printf result + - `buf` has a fixed format: n_digits with a decimal point at offset 1 + and exponent 'e{+/-}xx[x]' at offset n_digits+1 + Return n_digits + Store the position of the decimal point into `*decpt` + */ +static int js_ecvt1(double d, int n_digits, + char dest[minimum_length(JS_ECVT_BUF_SIZE)], + size_t size, int *decpt) +{ + /* d is positive, ensure decimal point is always present */ + snprintf(dest, size, "%#.*e", n_digits - 1, d); + /* dest contents: + 0: first digit + 1: '.' decimal point (locale specific) + 2..n_digits: (n_digits-1) additional digits + n_digits+1: 'e' exponent mark + n_digits+2..: exponent sign, value and null terminator + */ + /* extract the exponent (actually the position of the decimal point) */ + *decpt = 1 + atoi(dest + n_digits + 2); + return n_digits; +} + +/* `js_ecvt`: compute the digits and decimal point spot for a double + with proper javascript rounding. We cannot use `ecvt` for multiple + resasons: portability, because of the number of digits is typically + limited to 17, finally because the default rounding is inadequate. + `d` is finite and positive or zero. + `n_digits` number of significant digits in range 1..101 + or 0 for automatic (only as many digits as necessary) + Return the number of digits produced in `dest`. + Store the position of the decimal point into `*decpt` + */ +static int js_ecvt(double d, int n_digits, + char dest[minimum_length(JS_ECVT_BUF_SIZE)], + size_t size, int *decpt) +{ + if (n_digits == 0) { + /* find the minimum number of digits (XXX: inefficient but simple) */ + // TODO(chqrlie) use direct method from quickjs-printf + unsigned int n_digits_min = 1; + unsigned int n_digits_max = 17; + for (;;) { + n_digits = (n_digits_min + n_digits_max) / 2; + js_ecvt1(d, n_digits, dest, size, decpt); + if (n_digits_min == n_digits_max) + return n_digits; + /* dest contents: + 0: first digit + 1: '.' decimal point (locale specific) + 2..n_digits: (n_digits-1) additional digits + n_digits+1: 'e' exponent mark + n_digits+2..: exponent sign, value and null terminator + */ + if (strtod(dest, NULL) == d) { + unsigned int n0 = n_digits; + /* enough digits */ + /* strip the trailing zeros */ + while (dest[n_digits] == '0') + n_digits--; + if (n_digits == n_digits_min) + return n_digits; + /* done if trailing zeros and not denormal or huge */ + if (n_digits < n0 && d > 3e-308 && d < 8e307) + return n_digits; + n_digits_max = n_digits; + } else { + /* need at least one more digit */ + n_digits_min = n_digits + 1; + } + } + } else { +#if defined(FE_DOWNWARD) && defined(FE_TONEAREST) + int i; + /* generate 2 extra digits: 99% chances to avoid 2 calls */ + js_ecvt1(d, n_digits + 2, dest, size, decpt); + if (dest[n_digits + 1] < '5') + return n_digits; /* truncate the 2 extra digits */ + if (dest[n_digits + 1] == '5' && dest[n_digits + 2] == '0') { + /* close to half-way: try rounding toward 0 */ + fesetround(FE_DOWNWARD); + js_ecvt1(d, n_digits + 2, dest, size, decpt); + fesetround(FE_TONEAREST); + if (dest[n_digits + 1] < '5') + return n_digits; /* truncate the 2 extra digits */ + } + /* round up in the string */ + for(i = n_digits;; i--) { + /* ignore the locale specific decimal point */ + if (is_digit(dest[i])) { + if (dest[i]++ < '9') + break; + dest[i] = '0'; + if (i == 0) { + dest[0] = '1'; + (*decpt)++; + break; + } + } + } + return n_digits; /* truncate the 2 extra digits */ +#else + /* No disambiguation available, eg: __wasi__ targets */ + return js_ecvt1(d, n_digits, dest, size, decpt); +#endif + } +} + +/* `js_fcvt`: convert a floating point value to %f format using RNDNA + `d` is finite and positive or zero. + `n_digits` number of decimal places in range 0..100 + Return the number of characters produced in `dest`. + */ +static size_t js_fcvt(double d, int n_digits, + char dest[minimum_length(JS_FCVT_BUF_SIZE)], size_t size) +{ +#if defined(FE_DOWNWARD) && defined(FE_TONEAREST) + int i, n1; + /* generate 2 extra digits: 99% chances to avoid 2 calls */ + n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2; + if (dest[n1] >= '5') { + if (dest[n1] == '5' && dest[n1 + 1] == '0') { + /* close to half-way: try rounding toward 0 */ + fesetround(FE_DOWNWARD); + n1 = snprintf(dest, size, "%.*f", n_digits + 2, d) - 2; + fesetround(FE_TONEAREST); + } + if (dest[n1] >= '5') { /* number should be rounded up */ + /* d is either exactly half way or greater: round the string manually */ + for (i = n1 - 1;; i--) { + /* ignore the locale specific decimal point */ + if (is_digit(dest[i])) { + if (dest[i]++ < '9') + break; + dest[i] = '0'; + if (i == 0) { + dest[0] = '1'; + dest[n1] = '0'; + dest[n1 - n_digits - 1] = '0'; + dest[n1 - n_digits] = '.'; + n1++; + break; + } + } + } + } + } + /* truncate the extra 2 digits and the decimal point if !n_digits */ + n1 -= !n_digits; + //dest[n1] = '\0'; // optional + return n1; +#else + /* No disambiguation available, eg: __wasi__ targets */ + return snprintf(dest, size, "%.*f", n_digits, d); +#endif +} + +static JSValue js_dtoa_infinite(JSContext *ctx, double d) +{ + // TODO(chqrlie) use atoms for NaN and Infinite? + if (isnan(d)) + return js_new_string8(ctx, "NaN"); + if (d < 0) + return js_new_string8(ctx, "-Infinity"); + else + return js_new_string8(ctx, "Infinity"); +} + +#define JS_DTOA_TOSTRING 0 /* use as many digits as necessary */ +#define JS_DTOA_EXPONENTIAL 1 /* use exponential notation either fixed or variable digits */ +#define JS_DTOA_FIXED 2 /* force fixed number of fractional digits */ +#define JS_DTOA_PRECISION 3 /* use n_digits significant digits (1 <= n_digits <= 101) */ + +/* `js_dtoa`: convert a floating point number to a string + - `mode`: one of the 4 supported formats + - `n_digits`: digit number according to mode + - TOSTRING: 0 only. As many digits as necessary + - EXPONENTIAL: 0 as many decimals as necessary + - 1..101 number of significant digits + - FIXED: 0..100 number of decimal places + - PRECISION: 1..101 number of significant digits + */ +// XXX: should use libbf or quickjs-printf. +static JSValue js_dtoa(JSContext *ctx, double d, int n_digits, int mode) +{ + char buf[JS_DTOA_BUF_SIZE]; + size_t len; + char *start; + int sign, decpt, exp, i, k, n, n_max; + + if (!isfinite(d)) + return js_dtoa_infinite(ctx, d); + + sign = (d < 0); + start = buf + 8; + d = fabs(d); /* also converts -0 to 0 */ + + if (mode != JS_DTOA_EXPONENTIAL && n_digits == 0) { + /* fast path for exact integers in variable format: + clip to MAX_SAFE_INTEGER because to ensure insignificant + digits are generated as 0. + used for JS_DTOA_TOSTRING and JS_DTOA_FIXED without decimals. + */ + if (d <= (double)MAX_SAFE_INTEGER) { + uint64_t u64 = (uint64_t)d; + if (d == u64) { + len = u64toa(start, u64); + goto done; + } + } + } + if (mode == JS_DTOA_FIXED) { + len = js_fcvt(d, n_digits, start, sizeof(buf) - 8); + // TODO(chqrlie) patch the locale specific decimal point + goto done; + } + + n_max = (n_digits > 0) ? n_digits : 21; + /* the number has k digits (1 <= k <= n_max) */ + k = js_ecvt(d, n_digits, start, sizeof(buf) - 8, &decpt); + /* buffer contents: + 0: first digit + 1: '.' decimal point + 2..k: (k-1) additional digits + */ + n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */ + if (mode != JS_DTOA_EXPONENTIAL) { + /* mode is JS_DTOA_PRECISION or JS_DTOA_TOSTRING */ + if (n >= 1 && n <= n_max) { + /* between 1 and n_max digits before the decimal point */ + if (k <= n) { + /* all digits before the point, append zeros */ + start[1] = start[0]; + start++; + for(i = k; i < n; i++) + start[i] = '0'; + len = n; + } else { + /* k > n: move digits before the point */ + for(i = 1; i < n; i++) + start[i] = start[i + 1]; + start[i] = '.'; + len = 1 + k; + } + goto done; + } + if (n >= -5 && n <= 0) { + /* insert -n leading 0 decimals and a '0.' prefix */ + n = -n; + start[1] = start[0]; + start -= n + 1; + start[0] = '0'; + start[1] = '.'; + for(i = 0; i < n; i++) + start[2 + i] = '0'; + len = 2 + k + n; + goto done; + } + } + /* exponential notation */ + exp = n - 1; + /* count the digits and the decimal point if at least one decimal */ + len = k + (k > 1); + start[1] = '.'; /* patch the locale specific decimal point */ + start[len] = 'e'; + start[len + 1] = '+'; + if (exp < 0) { + start[len + 1] = '-'; + exp = -exp; + } + len += 2 + 1 + (exp > 9) + (exp > 99); + for (i = len - 1; exp > 9;) { + int quo = exp / 10; + start[i--] = (char)('0' + exp % 10); + exp = quo; + } + start[i] = (char)('0' + exp); + + done: + start[-1] = '-'; /* prepend the sign if negative */ + return js_new_string8_len(ctx, start - sign, len + sign); +} + +/* `js_dtoa_radix`: convert a floating point number using a specific base + - `d` must be finite + - `radix` must be in range 2..36 + */ +static JSValue js_dtoa_radix(JSContext *ctx, double d, int radix) +{ + char buf[2200], *ptr, *ptr2, *ptr3; + int sign, digit; + double frac, d0; + int64_t n0; + + if (!isfinite(d)) + return js_dtoa_infinite(ctx, d); + + sign = (d < 0); + d = fabs(d); + d0 = trunc(d); + n0 = 0; + frac = d - d0; + ptr2 = buf + 1100; /* ptr2 points to the end of the string */ + ptr = ptr2; /* ptr points to the beginning of the string */ + if (d0 <= MAX_SAFE_INTEGER) { + int64_t n = n0 = (int64_t)d0; + while (n >= radix) { + digit = n % radix; + n = n / radix; + *--ptr = digits36[digit]; + } + *--ptr = digits36[(size_t)n]; + } else { + /* no decimals */ + while (d0 >= radix) { + digit = fmod(d0, radix); + d0 = trunc(d0 / radix); + if (d0 >= MAX_SAFE_INTEGER) + digit = 0; + *--ptr = digits36[digit]; + } + *--ptr = digits36[(size_t)d0]; + goto done; + } + if (frac != 0) { + double log2_radix = log2(radix); + double prec = 1023 + 51; // handle subnormals + *ptr2++ = '.'; + while (frac != 0 && n0 <= MAX_SAFE_INTEGER/2 && prec > 0) { + frac *= radix; + digit = trunc(frac); + frac -= digit; + *ptr2++ = digits36[digit]; + n0 = n0 * radix + digit; + prec -= log2_radix; + } + if (frac * radix >= radix / 2) { + /* round up the string representation manually */ + char nine = digits36[radix - 1]; + while (ptr2[-1] == nine) { + /* strip trailing '9' or equivalent digits */ + ptr2--; + } + if (ptr2[-1] == '.') { + /* strip the 'decimal' point */ + ptr2--; + /* increment the integral part */ + for (ptr3 = ptr2;;) { + if (ptr3[-1] != nine) { + ptr3[-1] = (ptr3[-1] == '9') ? 'a' : ptr3[-1] + 1; + break; + } + *--ptr3 = '0'; + if (ptr3 <= ptr) { + /* prepend a '1' if number was all nines */ + *--ptr = '1'; + break; + } + } + } else { + /* increment the last fractional digit */ + ptr2[-1] = (ptr2[-1] == '9') ? 'a' : ptr2[-1] + 1; + } + } else { + /* strip trailing fractional zeros */ + while (ptr2[-1] == '0') + ptr2--; + /* strip the 'decimal' point if last */ + ptr2 -= (ptr2[-1] == '.'); + } + } +done: + ptr[-1] = '-'; + ptr -= sign; + return js_new_string8_len(ctx, ptr, ptr2 - ptr); +} + +JSValue JS_ToStringInternal(JSContext *ctx, JSValue val, BOOL is_ToPropertyKey) +{ + uint32_t tag; + char buf[32]; + size_t len; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_STRING: + return js_dup(val); + case JS_TAG_INT: + len = i32toa(buf, JS_VALUE_GET_INT(val)); + return js_new_string8_len(ctx, buf, len); + case JS_TAG_BOOL: + return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? + JS_ATOM_true : JS_ATOM_false); + case JS_TAG_NULL: + return JS_AtomToString(ctx, JS_ATOM_null); + case JS_TAG_UNDEFINED: + return JS_AtomToString(ctx, JS_ATOM_undefined); + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_OBJECT: + { + JSValue val1, ret; + val1 = JS_ToPrimitive(ctx, val, HINT_STRING); + if (JS_IsException(val1)) + return val1; + ret = JS_ToStringInternal(ctx, val1, is_ToPropertyKey); + JS_FreeValue(ctx, val1); + return ret; + } + break; + case JS_TAG_FUNCTION_BYTECODE: + return js_new_string8(ctx, "[function bytecode]"); + case JS_TAG_SYMBOL: + if (is_ToPropertyKey) { + return js_dup(val); + } else { + return JS_ThrowTypeError(ctx, "cannot convert symbol to string"); + } + case JS_TAG_FLOAT64: + return js_dtoa(ctx, JS_VALUE_GET_FLOAT64(val), 0, JS_DTOA_TOSTRING); + case JS_TAG_BIG_INT: + return js_bigint_to_string(ctx, val); + case JS_TAG_UNINITIALIZED: + return js_new_string8(ctx, "[uninitialized]"); + default: + return js_new_string8(ctx, "[unsupported type]"); + } +} + +JSValue JS_ToString(JSContext *ctx, JSValue val) +{ + return JS_ToStringInternal(ctx, val, FALSE); +} + +static JSValue JS_ToStringFree(JSContext *ctx, JSValue val) +{ + JSValue ret; + ret = JS_ToString(ctx, val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue JS_ToLocaleStringFree(JSContext *ctx, JSValue val) +{ + if (JS_IsUndefined(val) || JS_IsNull(val)) + return JS_ToStringFree(ctx, val); + return JS_InvokeFree(ctx, val, JS_ATOM_toLocaleString, 0, NULL); +} + +JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val) +{ + return JS_ToStringInternal(ctx, val, TRUE); +} + +static JSValue JS_ToStringCheckObject(JSContext *ctx, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_TAG(val); + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return JS_ThrowTypeError(ctx, "null or undefined are forbidden"); + return JS_ToString(ctx, val); +} + +static JSValue JS_ToQuotedString(JSContext *ctx, JSValue val1) +{ + JSValue val; + JSString *p; + int i; + uint32_t c; + StringBuffer b_s, *b = &b_s; + char buf[16]; + + val = JS_ToStringCheckObject(ctx, val1); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + + if (string_buffer_init(ctx, b, p->len + 2)) + goto fail; + + if (string_buffer_putc8(b, '\"')) + goto fail; + for(i = 0; i < p->len; ) { + c = string_getc(p, &i); + 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: + if (string_buffer_putc8(b, '\\')) + goto fail; + if (string_buffer_putc8(b, c)) + goto fail; + break; + default: + if (c < 32 || is_surrogate(c)) { + snprintf(buf, sizeof(buf), "\\u%04x", c); + if (string_buffer_write8(b, (uint8_t*)buf, 6)) + goto fail; + } else { + if (string_buffer_putc(b, c)) + goto fail; + } + break; + } + } + if (string_buffer_putc8(b, '\"')) + goto fail; + JS_FreeValue(ctx, val); + return string_buffer_end(b); + fail: + JS_FreeValue(ctx, val); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static __maybe_unused void JS_DumpObjectHeader(JSRuntime *rt) +{ + printf("%14s %4s %4s %14s %10s %s\n", + "ADDRESS", "REFS", "SHRF", "PROTO", "CLASS", "PROPS"); +} + +/* for debug only: dump an object without side effect */ +static __maybe_unused void JS_DumpObject(JSRuntime *rt, JSObject *p) +{ + uint32_t i; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + JSShape *sh; + JSShapeProperty *prs; + JSProperty *pr; + BOOL is_first = TRUE; + + /* XXX: should encode atoms with special characters */ + sh = p->shape; /* the shape can be NULL while freeing an object */ + printf("%14p %4d ", + (void *)p, + p->header.ref_count); + if (sh) { + printf("%3d%c %14p ", + sh->header.ref_count, + " *"[sh->is_hashed], + (void *)sh->proto); + } else { + printf("%3s %14s ", "-", "-"); + } + printf("%10s ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), rt->class_array[p->class_id].class_name)); + if (p->is_exotic && p->fast_array) { + printf("[ "); + for(i = 0; i < p->u.array.count; i++) { + if (i != 0) + printf(", "); + switch (p->class_id) { + case JS_CLASS_ARRAY: + case JS_CLASS_ARGUMENTS: + JS_DumpValue(rt, p->u.array.u.values[i]); + 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_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + { + int size = 1 << typed_array_size_log2(p->class_id); + const uint8_t *b = p->u.array.u.uint8_ptr + i * size; + while (size-- > 0) + printf("%02X", *b++); + } + break; + } + } + printf(" ] "); + } + + if (sh) { + printf("{ "); + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->atom != JS_ATOM_NULL) { + pr = &p->prop[i]; + if (!is_first) + printf(", "); + printf("%s: ", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), prs->atom)); + if ((prs->flags & JS_PROP_TMASK) == JS_PROP_GETSET) { + printf("[getset %p %p]", (void *)pr->u.getset.getter, + (void *)pr->u.getset.setter); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_VARREF) { + printf("[varref %p]", (void *)pr->u.var_ref); + } else if ((prs->flags & JS_PROP_TMASK) == JS_PROP_AUTOINIT) { + printf("[autoinit %p %d %p]", + (void *)js_autoinit_get_realm(pr), + js_autoinit_get_id(pr), + (void *)pr->u.init.opaque); + } else { + JS_DumpValue(rt, pr->u.value); + } + is_first = FALSE; + } + } + printf(" }"); + } + + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + JSVarRef **var_refs; + if (b->closure_var_count) { + var_refs = p->u.func.var_refs; + printf(" Closure:"); + for(i = 0; i < b->closure_var_count; i++) { + printf(" "); + JS_DumpValue(rt, var_refs[i]->value); + } + if (p->u.func.home_object) { + printf(" HomeObject: "); + JS_DumpValue(rt, JS_MKPTR(JS_TAG_OBJECT, p->u.func.home_object)); + } + } + } + printf("\n"); +} + +static __maybe_unused void JS_DumpGCObject(JSRuntime *rt, JSGCObjectHeader *p) +{ + if (p->gc_obj_type == JS_GC_OBJ_TYPE_JS_OBJECT) { + JS_DumpObject(rt, (JSObject *)p); + } else { + printf("%14p %4d ", + (void *)p, + p->ref_count); + switch(p->gc_obj_type) { + case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: + printf("[function bytecode]"); + break; + case JS_GC_OBJ_TYPE_SHAPE: + printf("[shape]"); + break; + case JS_GC_OBJ_TYPE_VAR_REF: + printf("[var_ref]"); + break; + case JS_GC_OBJ_TYPE_ASYNC_FUNCTION: + printf("[async_function]"); + break; + case JS_GC_OBJ_TYPE_JS_CONTEXT: + printf("[js_context]"); + break; + default: + printf("[unknown %d]", p->gc_obj_type); + break; + } + printf("\n"); + } +} + +static __maybe_unused void JS_DumpValue(JSRuntime *rt, JSValue val) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(val); + const char *str; + + switch(tag) { + case JS_TAG_INT: + printf("%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL(val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + case JS_TAG_UNDEFINED: + str = "undefined"; + print_str: + printf("%s", str); + break; + case JS_TAG_FLOAT64: + printf("%.14g", JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + char *str; + str = bf_ftoa(NULL, &p->num, 10, 0, + BF_RNDZ | BF_FTOA_FORMAT_FRAC); + printf("%sn", str); + bf_realloc(&rt->bf_ctx, str, 0); + } + break; + case JS_TAG_STRING: + { + JSString *p; + p = JS_VALUE_GET_STRING(val); + JS_DumpString(rt, p); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + { + JSFunctionBytecode *b = JS_VALUE_GET_PTR(val); + char buf[ATOM_GET_STR_BUF_SIZE]; + if (b->func_name) { + printf("[bytecode %s]", JS_AtomGetStrRT(rt, buf, sizeof(buf), b->func_name)); + } else { + printf("[bytecode (anonymous)]"); + } + } + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAtom atom = rt->class_array[p->class_id].class_name; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("[%s %p]", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), atom), (void *)p); + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + printf("Symbol(%s)", + JS_AtomGetStrRT(rt, atom_buf, sizeof(atom_buf), js_get_atom_index(rt, p))); + } + break; + case JS_TAG_MODULE: + printf("[module]"); + break; + default: + printf("[unknown tag %d]", tag); + break; + } +} + +/* return -1 if exception (proxy case) or TRUE/FALSE */ +int JS_IsArray(JSContext *ctx, JSValue val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(val); + if (unlikely(p->class_id == JS_CLASS_PROXY)) + return js_proxy_isArray(ctx, val); + else + return p->class_id == JS_CLASS_ARRAY; + } else { + return FALSE; + } +} + +static double js_math_pow(double a, double b) +{ + if (unlikely(!isfinite(b)) && fabs(a) == 1) { + /* not compatible with IEEE 754 */ + return JS_FLOAT64_NAN; + } else { + return pow(a, b); + } +} + +JSValue JS_NewBigInt64(JSContext *ctx, int64_t v) +{ + JSValue val; + bf_t *a; + val = JS_NewBigInt(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigInt(val); + if (bf_set_si(a, v)) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + return val; +} + +JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v) +{ + JSValue val; + bf_t *a; + val = JS_NewBigInt(ctx); + if (JS_IsException(val)) + return val; + a = JS_GetBigInt(val); + if (bf_set_ui(a, v)) { + JS_FreeValue(ctx, val); + return JS_ThrowOutOfMemory(ctx); + } + + return val; +} + +/* if the returned bigint is allocated it is equal to + 'buf'. Otherwise it is a pointer to the bigint in 'val'. Return + NULL in case of error. */ +// TODO(bnoordhuis) Merge with JS_ToBigInt() +static bf_t *JS_ToBigInt1(JSContext *ctx, bf_t *buf, JSValue val) +{ + uint32_t tag; + bf_t *r; + JSBigInt *p; + + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + r = buf; + bf_init(ctx->bf_ctx, r); + if (bf_set_si(r, JS_VALUE_GET_INT(val))) + goto fail; + break; + case JS_TAG_FLOAT64: + r = buf; + bf_init(ctx->bf_ctx, r); + if (bf_set_float64(r, JS_VALUE_GET_FLOAT64(val))) { + fail: + bf_delete(r); + return NULL; + } + break; + case JS_TAG_BIG_INT: + p = JS_VALUE_GET_PTR(val); + r = &p->num; + break; + case JS_TAG_UNDEFINED: + default: + r = buf; + bf_init(ctx->bf_ctx, r); + bf_set_nan(r); + break; + } + return r; +} + +/* return NaN if bad bigint literal */ +static JSValue JS_StringToBigInt(JSContext *ctx, JSValue val) +{ + const char *str; + size_t len; + int flags; + + str = JS_ToCStringLen(ctx, &len, val); + JS_FreeValue(ctx, val); + if (!str) + return JS_EXCEPTION; + flags = ATOD_WANT_BIG_INT | + ATOD_TRIM_SPACES | ATOD_ACCEPT_EMPTY | + ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | + ATOD_DECIMAL_AFTER_SIGN | ATOD_NO_TRAILING_CHARS; + val = js_atof(ctx, str, len, NULL, 10, flags); + JS_FreeCString(ctx, str); + return val; +} + +static JSValue JS_StringToBigIntErr(JSContext *ctx, JSValue val) +{ + val = JS_StringToBigInt(ctx, val); + if (JS_VALUE_IS_NAN(val)) + return JS_ThrowSyntaxError(ctx, "invalid BigInt literal"); + return val; +} + +/* if the returned bigint is allocated it is equal to + 'buf'. Otherwise it is a pointer to the bigint in 'val'. */ +static bf_t *JS_ToBigIntFree(JSContext *ctx, bf_t *buf, JSValue val) +{ + uint32_t tag; + bf_t *r; + JSBigInt *p; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + case JS_TAG_FLOAT64: + goto fail; + case JS_TAG_BOOL: + r = buf; + bf_init(ctx->bf_ctx, r); + bf_set_si(r, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BIG_INT: + p = JS_VALUE_GET_PTR(val); + r = &p->num; + break; + case JS_TAG_STRING: + val = JS_StringToBigIntErr(ctx, val); + if (JS_IsException(val)) + return NULL; + goto redo; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + return NULL; + goto redo; + default: + fail: + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "cannot convert to BigInt"); + return NULL; + } + return r; +} + +static bf_t *JS_ToBigInt(JSContext *ctx, bf_t *buf, JSValue val) +{ + return JS_ToBigIntFree(ctx, buf, js_dup(val)); +} + +static __maybe_unused JSValue JS_ToBigIntValueFree(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_TAG(val) == JS_TAG_BIG_INT) { + return val; + } else { + bf_t a_s, *a, *r; + int ret; + JSValue res; + + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) + return JS_EXCEPTION; + a = JS_ToBigIntFree(ctx, &a_s, val); + if (!a) { + JS_FreeValue(ctx, res); + return JS_EXCEPTION; + } + r = JS_GetBigInt(res); + ret = bf_set(r, a); + JS_FreeBigInt(ctx, a, &a_s); + if (ret) { + JS_FreeValue(ctx, res); + return JS_ThrowOutOfMemory(ctx); + } + return JS_CompactBigInt(ctx, res); + } +} + +/* free the bf_t allocated by JS_ToBigInt */ +static void JS_FreeBigInt(JSContext *ctx, bf_t *a, bf_t *buf) +{ + if (a == buf) { + bf_delete(a); + } else { + JSBigInt *p = (JSBigInt *)((uint8_t *)a - offsetof(JSBigInt, num)); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_BIG_INT, p)); + } +} + +/* XXX: merge with JS_ToInt64Free with a specific flag */ +static int JS_ToBigInt64Free(JSContext *ctx, int64_t *pres, JSValue val) +{ + bf_t a_s, *a; + + a = JS_ToBigIntFree(ctx, &a_s, val); + if (!a) { + *pres = 0; + return -1; + } + bf_get_int64(pres, a, BF_GET_INT_MOD); + JS_FreeBigInt(ctx, a, &a_s); + return 0; +} + +int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValue val) +{ + return JS_ToBigInt64Free(ctx, pres, js_dup(val)); +} + +int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValue val) +{ + return JS_ToBigInt64Free(ctx, (int64_t *)pres, js_dup(val)); +} + +static JSValue JS_NewBigInt(JSContext *ctx) +{ + JSBigInt *p; + p = js_malloc(ctx, sizeof(*p)); + if (!p) + return JS_EXCEPTION; + p->header.ref_count = 1; + bf_init(ctx->bf_ctx, &p->num); + return JS_MKPTR(JS_TAG_BIG_INT, p); +} + +static JSValue JS_CompactBigInt1(JSContext *ctx, JSValue val) +{ + if (JS_VALUE_GET_TAG(val) != JS_TAG_BIG_INT) + return val; /* fail safe */ + bf_t *a = JS_GetBigInt(val); + if (a->expn == BF_EXP_ZERO && a->sign) { + assert(((JSBigInt*)JS_VALUE_GET_PTR(val))->header.ref_count == 1); + a->sign = 0; + } + return val; +} + +/* Nnormalize the zero representation. Could also be used to convert the bigint + to a short bigint value. The reference count of the value must be + 1. Cannot fail */ +static JSValue JS_CompactBigInt(JSContext *ctx, JSValue val) +{ + return JS_CompactBigInt1(ctx, val); +} + +static JSValue throw_bf_exception(JSContext *ctx, int status) +{ + const char *str; + if (status & BF_ST_MEM_ERROR) + return JS_ThrowOutOfMemory(ctx); + if (status & BF_ST_DIVIDE_ZERO) { + str = "division by zero"; + } else if (status & BF_ST_INVALID_OP) { + str = "invalid operation"; + } else { + str = "integer overflow"; + } + return JS_ThrowRangeError(ctx, "%s", str); +} + +static int js_unary_arith_bigint(JSContext *ctx, + JSValue *pres, OPCodeEnum op, JSValue op1) +{ + bf_t a_s, *r, *a; + int ret, v; + JSValue res; + + if (op == OP_plus) { + JS_ThrowTypeError(ctx, "BigInt argument with unary +"); + JS_FreeValue(ctx, op1); + return -1; + } + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) { + JS_FreeValue(ctx, op1); + return -1; + } + r = JS_GetBigInt(res); + a = JS_ToBigIntFree(ctx, &a_s, op1); // infallible, always a bigint + ret = 0; + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + ret = bf_add_si(r, a, v, BF_PREC_INF, BF_RNDZ); + break; + case OP_plus: + ret = bf_set(r, a); + break; + case OP_neg: + ret = bf_set(r, a); + bf_neg(r); + break; + case OP_not: + ret = bf_add_si(r, a, 1, BF_PREC_INF, BF_RNDZ); + bf_neg(r); + break; + default: + abort(); + } + JS_FreeBigInt(ctx, a, &a_s); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + res = JS_CompactBigInt(ctx, res); + *pres = res; + return 0; +} + +static no_inline __exception int js_unary_arith_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1; + int v; + uint32_t tag; + + op1 = sp[-1]; + /* fast path for float64 */ + if (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(op1))) + goto handle_float64; + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) + goto exception; + tag = JS_VALUE_GET_TAG(op1); + switch(tag) { + case JS_TAG_INT: + { + int64_t v64; + v64 = JS_VALUE_GET_INT(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + v64 += v; + break; + case OP_plus: + break; + case OP_neg: + if (v64 == 0) { + sp[-1] = js_float64(-0.0); + return 0; + } else { + v64 = -v64; + } + break; + default: + abort(); + } + sp[-1] = js_int64(v64); + } + break; + case JS_TAG_BIG_INT: + if (js_unary_arith_bigint(ctx, sp - 1, op, op1)) + goto exception; + break; + default: + handle_float64: + { + double d = JS_VALUE_GET_FLOAT64(op1); + switch(op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + d += v; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort(); + } + sp[-1] = js_float64(d); + } + break; + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static __exception int js_post_inc_slow(JSContext *ctx, + JSValue *sp, OPCodeEnum op) +{ + JSValue op1; + + /* XXX: allow custom operators */ + op1 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + sp[-1] = JS_UNDEFINED; + return -1; + } + sp[-1] = op1; + sp[0] = js_dup(op1); + return js_unary_arith_slow(ctx, sp + 1, op - OP_post_dec + OP_dec); +} + +static no_inline int js_not_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1; + + op1 = JS_ToNumericFree(ctx, sp[-1]); + if (JS_IsException(op1)) + goto exception; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT) { + if (js_unary_arith_bigint(ctx, sp - 1, OP_not, op1)) + goto exception; + } else { + int32_t v1; + if (unlikely(JS_ToInt32Free(ctx, &v1, op1))) + goto exception; + sp[-1] = js_int32(~v1); + } + return 0; + exception: + sp[-1] = JS_UNDEFINED; + return -1; +} + +static int js_binary_arith_bigint(JSContext *ctx, OPCodeEnum op, + JSValue *pres, JSValue op1, JSValue op2) +{ + bf_t a_s, b_s, *r, *a, *b; + int ret; + JSValue res; + + a = JS_ToBigIntFree(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, op2); + return -1; + } + b = JS_ToBigIntFree(ctx, &b_s, op2); + if (!b) { + JS_FreeBigInt(ctx, a, &a_s); + return -1; + } + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) { + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeBigInt(ctx, b, &b_s); + return -1; + } + r = JS_GetBigInt(res); + ret = 0; + switch(op) { + case OP_add: + ret = bf_add(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_sub: + ret = bf_sub(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_mul: + ret = bf_mul(r, a, b, BF_PREC_INF, BF_RNDZ); + break; + case OP_div: + { + bf_t rem_s, *rem = &rem_s; + bf_init(ctx->bf_ctx, rem); + ret = bf_divrem(r, rem, a, b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); + bf_delete(rem); + } + break; + case OP_mod: + ret = bf_rem(r, a, b, BF_PREC_INF, BF_RNDZ, + BF_RNDZ) & BF_ST_INVALID_OP; + break; + case OP_pow: + if (b->sign) { + ret = BF_ST_INVALID_OP; + } else { + ret = bf_pow(r, a, b, BF_PREC_INF, BF_RNDZ | BF_POW_JS_QUIRKS); + } + break; + + /* logical operations */ + case OP_shl: + case OP_sar: + { + slimb_t v2; +#if LIMB_BITS == 32 + bf_get_int32(&v2, b, 0); + if (v2 == INT32_MIN) + v2 = INT32_MIN + 1; +#else + bf_get_int64(&v2, b, 0); + if (v2 == INT64_MIN) + v2 = INT64_MIN + 1; +#endif + if (op == OP_sar) + v2 = -v2; + ret = bf_set(r, a); + ret |= bf_mul_2exp(r, v2, BF_PREC_INF, BF_RNDZ); + if (v2 < 0) { + ret |= bf_rint(r, BF_RNDD) & (BF_ST_OVERFLOW | BF_ST_MEM_ERROR); + } + } + break; + case OP_and: + ret = bf_logic_and(r, a, b); + break; + case OP_or: + ret = bf_logic_or(r, a, b); + break; + case OP_xor: + ret = bf_logic_xor(r, a, b); + break; + default: + abort(); + } + JS_FreeBigInt(ctx, a, &a_s); + JS_FreeBigInt(ctx, b, &b_s); + if (unlikely(ret)) { + JS_FreeValue(ctx, res); + throw_bf_exception(ctx, ret); + return -1; + } + *pres = JS_CompactBigInt(ctx, res); + return 0; +} + +static no_inline __exception int js_binary_arith_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + double d1, d2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float operations */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + goto handle_float64; + } + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + switch(op) { + case OP_sub: + v = (int64_t)v1 - (int64_t)v2; + break; + case OP_mul: + v = (int64_t)v1 * (int64_t)v2; + if (v == 0 && (v1 | v2) < 0) { + sp[-2] = js_float64(-0.0); + return 0; + } + break; + case OP_div: + sp[-2] = js_float64((double)v1 / (double)v2); + return 0; + case OP_mod: + if (v1 < 0 || v2 <= 0) { + sp[-2] = js_number(fmod(v1, v2)); + return 0; + } else { + v = (int64_t)v1 % (int64_t)v2; + } + break; + case OP_pow: + sp[-2] = js_number(js_math_pow(v1, v2)); + return 0; + default: + abort(); + } + sp[-2] = js_int64(v); + } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + if (js_binary_arith_bigint(ctx, op, sp - 2, op1, op2)) + goto exception; + } else { + double dr; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + handle_float64: + switch(op) { + case OP_sub: + dr = d1 - d2; + break; + case OP_mul: + dr = d1 * d2; + break; + case OP_div: + dr = d1 / d2; + break; + case OP_mod: + dr = fmod(d1, d2); + break; + case OP_pow: + dr = js_math_pow(d1, d2); + break; + default: + abort(); + } + sp[-2] = js_float64(dr); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_add_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + /* fast path for float64 */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + double d1, d2; + d1 = JS_VALUE_GET_FLOAT64(op1); + d2 = JS_VALUE_GET_FLOAT64(op2); + sp[-2] = js_float64(d1 + d2); + return 0; + } + + if (tag1 == JS_TAG_OBJECT || tag2 == JS_TAG_OBJECT) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + } + + if (tag1 == JS_TAG_STRING || tag2 == JS_TAG_STRING) { + sp[-2] = JS_ConcatString(ctx, op1, op2); + if (JS_IsException(sp[-2])) + goto exception; + return 0; + } + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + v = (int64_t)v1 + (int64_t)v2; + sp[-2] = js_int64(v); + } else if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + if (js_binary_arith_bigint(ctx, OP_add, sp - 2, op1, op2)) + goto exception; + } else { + double d1, d2; + /* float64 result */ + if (JS_ToFloat64Free(ctx, &d1, op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (JS_ToFloat64Free(ctx, &d2, op2)) + goto exception; + sp[-2] = js_float64(d1 + d2); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline __exception int js_binary_logic_slow(JSContext *ctx, + JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + uint32_t tag1, tag2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + tag1 = JS_VALUE_GET_TAG(op1); + tag2 = JS_VALUE_GET_TAG(op2); + if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + if (tag1 != tag2) { + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + JS_ThrowTypeError(ctx, "both operands must be BigInt"); + goto exception; + } else if (js_binary_arith_bigint(ctx, op, sp - 2, op1, op2)) { + goto exception; + } + } else { + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v1, op1))) { + JS_FreeValue(ctx, op2); + goto exception; + } + if (unlikely(JS_ToInt32Free(ctx, (int32_t *)&v2, op2))) + goto exception; + switch(op) { + case OP_shl: + r = v1 << (v2 & 0x1f); + break; + case OP_sar: + r = (int)v1 >> (v2 & 0x1f); + break; + case OP_and: + r = v1 & v2; + break; + case OP_or: + r = v1 | v2; + break; + case OP_xor: + r = v1 ^ v2; + break; + default: + abort(); + } + sp[-2] = js_int32(r); + } + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static int js_compare_bigint(JSContext *ctx, OPCodeEnum op, + JSValue op1, JSValue op2) +{ + bf_t a_s, b_s, *a, *b; + int res; + + a = JS_ToBigInt1(ctx, &a_s, op1); + if (!a) { + JS_FreeValue(ctx, op2); + return -1; + } + b = JS_ToBigInt1(ctx, &b_s, op2); + if (!b) { + if (a == &a_s) + bf_delete(a); + JS_FreeValue(ctx, op1); + return -1; + } + switch(op) { + case OP_lt: + res = bf_cmp_lt(a, b); /* if NaN return false */ + break; + case OP_lte: + res = bf_cmp_le(a, b); /* if NaN return false */ + break; + case OP_gt: + res = bf_cmp_lt(b, a); /* if NaN return false */ + break; + case OP_gte: + res = bf_cmp_le(b, a); /* if NaN return false */ + break; + case OP_eq: + res = bf_cmp_eq(a, b); /* if NaN return false */ + break; + default: + abort(); + } + if (a == &a_s) + bf_delete(a); + if (b == &b_s) + bf_delete(b); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + return res; +} + +static no_inline int js_relational_slow(JSContext *ctx, JSValue *sp, + OPCodeEnum op) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NUMBER); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NUMBER); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_STRING && tag2 == JS_TAG_STRING) { + JSString *p1, *p2; + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + res = js_string_compare(ctx, p1, p2); + 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; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } else if ((tag1 <= JS_TAG_NULL || tag1 == JS_TAG_FLOAT64) && + (tag2 <= JS_TAG_NULL || tag2 == JS_TAG_FLOAT64)) { + /* fast path for float64/int */ + goto float64_compare; + } else { + if (((tag1 == JS_TAG_BIG_INT && tag2 == JS_TAG_STRING) || + (tag2 == JS_TAG_BIG_INT && tag1 == JS_TAG_STRING))) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = FALSE; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + + if (tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT) { + res = js_compare_bigint(ctx, op, op1, op2); + if (res < 0) + goto exception; + } else { + double d1, d2; + + float64_compare: + /* can use floating point comparison */ + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + 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; + } + } + } + done: + sp[-2] = js_bool(res); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static BOOL tag_is_number(uint32_t tag) +{ + return (tag == JS_TAG_INT || tag == JS_TAG_BIG_INT || + tag == JS_TAG_FLOAT64); +} + +static no_inline __exception int js_eq_slow(JSContext *ctx, JSValue *sp, + BOOL is_neq) +{ + JSValue op1, op2; + int res; + uint32_t tag1, tag2; + + op1 = sp[-2]; + op2 = sp[-1]; + redo: + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + if (tag_is_number(tag1) && tag_is_number(tag2)) { + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + } else if ((tag1 == JS_TAG_FLOAT64 && + (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) || + (tag2 == JS_TAG_FLOAT64 && + (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64))) { + double d1, d2; + if (tag1 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64(op1); + } else { + d1 = JS_VALUE_GET_INT(op1); + } + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else { + d2 = JS_VALUE_GET_INT(op2); + } + res = (d1 == d2); + } else { + res = js_compare_bigint(ctx, OP_eq, op1, op2); + if (res < 0) + goto exception; + } + } else if (tag1 == tag2) { + res = js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); + } else if ((tag1 == JS_TAG_NULL && tag2 == JS_TAG_UNDEFINED) || + (tag2 == JS_TAG_NULL && tag1 == JS_TAG_UNDEFINED)) { + res = TRUE; + } else if ((tag1 == JS_TAG_STRING && tag_is_number(tag2)) || + (tag2 == JS_TAG_STRING && tag_is_number(tag1))) { + + if ((tag1 == JS_TAG_BIG_INT || tag2 == JS_TAG_BIG_INT)) { + if (tag1 == JS_TAG_STRING) { + op1 = JS_StringToBigInt(ctx, op1); + if (JS_VALUE_GET_TAG(op1) != JS_TAG_BIG_INT) + goto invalid_bigint_string; + } + if (tag2 == JS_TAG_STRING) { + op2 = JS_StringToBigInt(ctx, op2); + if (JS_VALUE_GET_TAG(op2) != JS_TAG_BIG_INT) { + invalid_bigint_string: + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + res = FALSE; + goto done; + } + } + } else { + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + } + res = js_strict_eq(ctx, op1, op2); + } else if (tag1 == JS_TAG_BOOL) { + op1 = js_int32(JS_VALUE_GET_INT(op1)); + goto redo; + } else if (tag2 == JS_TAG_BOOL) { + op2 = js_int32(JS_VALUE_GET_INT(op2)); + goto redo; + } else if ((tag1 == JS_TAG_OBJECT && + (tag_is_number(tag2) || tag2 == JS_TAG_STRING || tag2 == JS_TAG_SYMBOL)) || + (tag2 == JS_TAG_OBJECT && + (tag_is_number(tag1) || tag1 == JS_TAG_STRING || tag1 == JS_TAG_SYMBOL))) { + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToPrimitiveFree(ctx, op2, HINT_NONE); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + goto redo; + } else { + /* IsHTMLDDA object is equivalent to undefined for '==' and '!=' */ + if ((JS_IsHTMLDDA(ctx, op1) && + (tag2 == JS_TAG_NULL || tag2 == JS_TAG_UNDEFINED)) || + (JS_IsHTMLDDA(ctx, op2) && + (tag1 == JS_TAG_NULL || tag1 == JS_TAG_UNDEFINED))) { + res = TRUE; + } else { + res = FALSE; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + } + done: + sp[-2] = js_bool(res ^ is_neq); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static no_inline int js_shr_slow(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + uint32_t v1, v2, r; + + op1 = sp[-2]; + op2 = sp[-1]; + op1 = JS_ToNumericFree(ctx, op1); + if (JS_IsException(op1)) { + JS_FreeValue(ctx, op2); + goto exception; + } + op2 = JS_ToNumericFree(ctx, op2); + if (JS_IsException(op2)) { + JS_FreeValue(ctx, op1); + goto exception; + } + + if ((JS_VALUE_GET_TAG(op1) == JS_TAG_BIG_INT || + JS_VALUE_GET_TAG(op2) == JS_TAG_BIG_INT)) { + JS_ThrowTypeError(ctx, "BigInt operands are forbidden for >>>"); + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + goto exception; + } + /* cannot give an exception */ + JS_ToUint32Free(ctx, &v1, op1); + JS_ToUint32Free(ctx, &v2, op2); + r = v1 >> (v2 & 0x1f); + sp[-2] = js_uint32(r); + return 0; + exception: + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +/* XXX: Should take JSValue arguments */ +static BOOL js_strict_eq2(JSContext *ctx, JSValue op1, JSValue op2, + JSStrictEqModeEnum eq_mode) +{ + BOOL res; + int tag1, tag2; + double d1, d2; + + tag1 = JS_VALUE_GET_NORM_TAG(op1); + tag2 = JS_VALUE_GET_NORM_TAG(op2); + switch(tag1) { + case JS_TAG_BOOL: + if (tag1 != tag2) { + res = FALSE; + } else { + res = JS_VALUE_GET_INT(op1) == JS_VALUE_GET_INT(op2); + goto done_no_free; + } + break; + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + res = (tag1 == tag2); + break; + case JS_TAG_STRING: + { + JSString *p1, *p2; + if (tag1 != tag2) { + res = FALSE; + } else { + p1 = JS_VALUE_GET_STRING(op1); + p2 = JS_VALUE_GET_STRING(op2); + res = (js_string_compare(ctx, p1, p2) == 0); + } + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p1, *p2; + if (tag1 != tag2) { + res = FALSE; + } else { + p1 = JS_VALUE_GET_PTR(op1); + p2 = JS_VALUE_GET_PTR(op2); + res = (p1 == p2); + } + } + break; + case JS_TAG_OBJECT: + if (tag1 != tag2) + res = FALSE; + else + res = JS_VALUE_GET_OBJ(op1) == JS_VALUE_GET_OBJ(op2); + break; + case JS_TAG_INT: + d1 = JS_VALUE_GET_INT(op1); + if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + goto number_test; + } else if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + goto number_test; + } else { + res = FALSE; + } + break; + case JS_TAG_FLOAT64: + d1 = JS_VALUE_GET_FLOAT64(op1); + if (tag2 == JS_TAG_FLOAT64) { + d2 = JS_VALUE_GET_FLOAT64(op2); + } else if (tag2 == JS_TAG_INT) { + d2 = JS_VALUE_GET_INT(op2); + } else { + res = FALSE; + break; + } + number_test: + if (unlikely(eq_mode >= JS_EQ_SAME_VALUE)) { + JSFloat64Union u1, u2; + /* NaN is not always normalized, so this test is necessary */ + if (isnan(d1) || isnan(d2)) { + res = isnan(d1) == isnan(d2); + } else if (eq_mode == JS_EQ_SAME_VALUE_ZERO) { + res = (d1 == d2); /* +0 == -0 */ + } else { + u1.d = d1; + u2.d = d2; + res = (u1.u64 == u2.u64); /* +0 != -0 */ + } + } else { + res = (d1 == d2); /* if NaN return false and +0 == -0 */ + } + goto done_no_free; + case JS_TAG_BIG_INT: + { + bf_t a_s, *a, b_s, *b; + if (tag1 != tag2) { + res = FALSE; + break; + } + a = JS_ToBigInt1(ctx, &a_s, op1); + b = JS_ToBigInt1(ctx, &b_s, op2); + res = bf_cmp_eq(a, b); + if (a == &a_s) + bf_delete(a); + if (b == &b_s) + bf_delete(b); + } + break; + default: + res = FALSE; + break; + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + done_no_free: + return res; +} + +static BOOL js_strict_eq(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_strict_eq2(ctx, op1, op2, JS_EQ_STRICT); +} + +static BOOL js_same_value(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE); +} + +static BOOL js_same_value_zero(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_SAME_VALUE_ZERO); +} + +static no_inline int js_strict_eq_slow(JSContext *ctx, JSValue *sp, + BOOL is_neq) +{ + BOOL res; + res = js_strict_eq(ctx, sp[-2], sp[-1]); + sp[-2] = js_bool(res ^ is_neq); + return 0; +} + +static __exception int js_operator_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + if (JS_VALUE_GET_TAG(op2) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + atom = JS_ValueToAtom(ctx, op1); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_HasProperty(ctx, op2, atom); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_operator_private_in(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + int ret; + op1 = sp[-2]; /* object */ + op2 = sp[-1]; /* field name or method function */ + if (JS_VALUE_GET_TAG(op1) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "invalid 'in' operand"); + return -1; + } + if (JS_IsObject(op2)) { + /* method: use the brand */ + ret = JS_CheckBrand(ctx, op1, op2); + if (ret < 0) + return -1; + } else { + JSAtom atom; + JSObject *p; + JSShapeProperty *prs; + JSProperty *pr; + /* field */ + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + p = JS_VALUE_GET_OBJ(op1); + prs = find_own_property(&pr, p, atom); + JS_FreeAtom(ctx, atom); + ret = (prs != NULL); + } + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = JS_NewBool(ctx, ret); + return 0; +} + +static __exception int js_has_unscopable(JSContext *ctx, JSValue obj, + JSAtom atom) +{ + JSValue arr, val; + int ret; + + arr = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_unscopables); + if (JS_IsException(arr)) + return -1; + ret = 0; + if (JS_IsObject(arr)) { + val = JS_GetProperty(ctx, arr, atom); + ret = JS_ToBoolFree(ctx, val); + } + JS_FreeValue(ctx, arr); + return ret; +} + +static __exception int js_operator_instanceof(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + BOOL ret; + + op1 = sp[-2]; + op2 = sp[-1]; + ret = JS_IsInstanceOf(ctx, op1, op2); + if (ret < 0) + return ret; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static __exception int js_operator_typeof(JSContext *ctx, JSValue op1) +{ + JSAtom atom; + uint32_t tag; + + tag = JS_VALUE_GET_NORM_TAG(op1); + switch(tag) { + case JS_TAG_BIG_INT: + atom = JS_ATOM_bigint; + break; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + atom = JS_ATOM_number; + break; + case JS_TAG_UNDEFINED: + atom = JS_ATOM_undefined; + break; + case JS_TAG_BOOL: + atom = JS_ATOM_boolean; + break; + case JS_TAG_STRING: + atom = JS_ATOM_string; + break; + case JS_TAG_OBJECT: + { + JSObject *p; + p = JS_VALUE_GET_OBJ(op1); + if (unlikely(p->is_HTMLDDA)) + atom = JS_ATOM_undefined; + else if (JS_IsFunction(ctx, op1)) + atom = JS_ATOM_function; + else + goto obj_type; + } + break; + case JS_TAG_NULL: + obj_type: + atom = JS_ATOM_object; + break; + case JS_TAG_SYMBOL: + atom = JS_ATOM_symbol; + break; + default: + atom = JS_ATOM_unknown; + break; + } + return atom; +} + +static __exception int js_operator_delete(JSContext *ctx, JSValue *sp) +{ + JSValue op1, op2; + JSAtom atom; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + atom = JS_ValueToAtom(ctx, op2); + if (unlikely(atom == JS_ATOM_NULL)) + return -1; + ret = JS_DeleteProperty(ctx, op1, atom, JS_PROP_THROW_STRICT); + JS_FreeAtom(ctx, atom); + if (unlikely(ret < 0)) + return -1; + JS_FreeValue(ctx, op1); + JS_FreeValue(ctx, op2); + sp[-2] = js_bool(ret); + return 0; +} + +static JSValue js_throw_type_error(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_ThrowTypeError(ctx, "invalid property access"); +} + +/* XXX: not 100% compatible, but mozilla seems to use a similar + implementation to ensure that caller in non strict mode does not + throw (ES5 compatibility) */ +static JSValue js_function_proto_caller(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (!b || b->is_strict_mode || !b->has_prototype) { + return js_throw_type_error(ctx, this_val, 0, NULL); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_fileName(JSContext *ctx, + JSValue this_val) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b) { + return JS_AtomToString(ctx, b->filename); + } + return JS_UNDEFINED; +} + +static JSValue js_function_proto_int32(JSContext *ctx, + JSValue this_val, + int magic) +{ + JSFunctionBytecode *b = JS_GetFunctionBytecode(this_val); + if (b) { + int *field = (int *) ((char *)b + magic); + return js_int32(*field); + } + return JS_UNDEFINED; +} + +static int js_arguments_define_own_property(JSContext *ctx, + JSValue this_obj, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, int flags) +{ + JSObject *p; + uint32_t idx; + p = JS_VALUE_GET_OBJ(this_obj); + /* convert to normal array when redefining an existing numeric field */ + if (p->fast_array && JS_AtomIsArrayIndex(ctx, &idx, prop) && + idx < p->u.array.count) { + if (convert_fast_array_to_array(ctx, p)) + return -1; + } + /* run the default define own property */ + return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, + flags | JS_PROP_NO_EXOTIC); +} + +static const JSClassExoticMethods js_arguments_exotic_methods = { + .define_own_property = js_arguments_define_own_property, +}; + +static JSValue js_build_arguments(JSContext *ctx, int argc, JSValue *argv) +{ + JSValue val, *tab; + JSProperty *pr; + JSObject *p; + int i; + + val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_ARGUMENTS); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* add the length field (cannot fail) */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (!pr) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + pr->u.value = js_int32(argc); + + /* initialize the fast array part */ + tab = NULL; + if (argc > 0) { + tab = js_malloc(ctx, sizeof(tab[0]) * argc); + if (!tab) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + for(i = 0; i < argc; i++) { + tab[i] = js_dup(argv[i]); + } + } + p->u.array.u.values = tab; + p->u.array.count = argc; + + JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, + js_dup(ctx->array_proto_values), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + /* add callee property to throw a TypeError in strict mode */ + JS_DefineProperty(ctx, val, JS_ATOM_callee, JS_UNDEFINED, + ctx->throw_type_error, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET); + return val; +} + +#define GLOBAL_VAR_OFFSET 0x40000000 +#define ARGUMENT_VAR_OFFSET 0x20000000 + +/* legacy arguments object: add references to the function arguments */ +static JSValue js_build_mapped_arguments(JSContext *ctx, int argc, + JSValue *argv, + JSStackFrame *sf, int arg_count) +{ + JSValue val; + JSProperty *pr; + JSObject *p; + int i; + + val = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_MAPPED_ARGUMENTS); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_OBJ(val); + + /* add the length field (cannot fail) */ + pr = add_property(ctx, p, JS_ATOM_length, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (!pr) + goto fail; + pr->u.value = js_int32(argc); + + for(i = 0; i < arg_count; i++) { + JSVarRef *var_ref; + var_ref = get_var_ref(ctx, sf, i, TRUE); + if (!var_ref) + goto fail; + pr = add_property(ctx, p, __JS_AtomFromUInt32(i), JS_PROP_C_W_E | JS_PROP_VARREF); + if (!pr) { + free_var_ref(ctx->rt, var_ref); + goto fail; + } + pr->u.var_ref = var_ref; + } + + /* the arguments not mapped to the arguments of the function can + be normal properties */ + for(i = arg_count; i < argc; i++) { + if (JS_DefinePropertyValueUint32(ctx, val, i, + js_dup(argv[i]), + JS_PROP_C_W_E) < 0) + goto fail; + } + + JS_DefinePropertyValue(ctx, val, JS_ATOM_Symbol_iterator, + js_dup(ctx->array_proto_values), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + /* callee returns this function in non strict mode */ + JS_DefinePropertyValue(ctx, val, JS_ATOM_callee, + js_dup(ctx->rt->current_stack_frame->cur_func), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue js_build_rest(JSContext *ctx, int first, int argc, JSValue *argv) +{ + JSValue val; + int i, ret; + + val = JS_NewArray(ctx); + if (JS_IsException(val)) + return val; + for (i = first; i < argc; i++) { + ret = JS_DefinePropertyValueUint32(ctx, val, i - first, + js_dup(argv[i]), + JS_PROP_C_W_E); + if (ret < 0) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + } + return val; +} + +static JSValue build_for_in_iterator(JSContext *ctx, JSValue obj) +{ + JSObject *p; + JSPropertyEnum *tab_atom; + int i; + JSValue enum_obj, obj1; + JSForInIterator *it; + uint32_t tag, tab_atom_count; + + tag = JS_VALUE_GET_TAG(obj); + if (tag != JS_TAG_OBJECT && tag != JS_TAG_NULL && tag != JS_TAG_UNDEFINED) { + obj = JS_ToObjectFree(ctx, obj); + } + + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + enum_obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_FOR_IN_ITERATOR); + if (JS_IsException(enum_obj)) { + js_free(ctx, it); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + it->is_array = FALSE; + it->obj = obj; + it->idx = 0; + p = JS_VALUE_GET_OBJ(enum_obj); + p->u.for_in_iterator = it; + + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) + return enum_obj; + + /* fast path: assume no enumerable properties in the prototype chain */ + obj1 = js_dup(obj); + for(;;) { + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + if (tab_atom_count != 0) { + JS_FreeValue(ctx, obj1); + goto slow_path; + } + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + + p = JS_VALUE_GET_OBJ(obj); + + if (p->fast_array) { + JSShape *sh; + JSShapeProperty *prs; + /* check that there are no enumerable normal fields */ + sh = p->shape; + for(i = 0, prs = get_shape_prop(sh); i < sh->prop_count; i++, prs++) { + if (prs->flags & JS_PROP_ENUMERABLE) + goto normal_case; + } + /* for fast arrays, we only store the number of elements */ + it->is_array = TRUE; + it->array_length = p->u.array.count; + } else { + normal_case: + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY)) + goto fail; + for(i = 0; i < tab_atom_count; i++) { + JS_SetPropertyInternal(ctx, enum_obj, tab_atom[i].atom, JS_NULL, 0); + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + } + return enum_obj; + + slow_path: + /* non enumerable properties hide the enumerables ones in the + prototype chain */ + obj1 = js_dup(obj); + for(;;) { + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, + JS_VALUE_GET_OBJ(obj1), + JS_GPN_STRING_MASK | JS_GPN_SET_ENUM)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + for(i = 0; i < tab_atom_count; i++) { + JS_DefinePropertyValue(ctx, enum_obj, tab_atom[i].atom, JS_NULL, + (tab_atom[i].is_enumerable ? + JS_PROP_ENUMERABLE : 0)); + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + obj1 = JS_GetPrototypeFree(ctx, obj1); + if (JS_IsNull(obj1)) + break; + if (JS_IsException(obj1)) + goto fail; + /* must check for timeout to avoid infinite loop */ + if (js_poll_interrupts(ctx)) { + JS_FreeValue(ctx, obj1); + goto fail; + } + } + return enum_obj; + + fail: + JS_FreeValue(ctx, enum_obj); + return JS_EXCEPTION; +} + +/* obj -> enum_obj */ +static __exception int js_for_in_start(JSContext *ctx, JSValue *sp) +{ + sp[-1] = build_for_in_iterator(ctx, sp[-1]); + if (JS_IsException(sp[-1])) + return -1; + return 0; +} + +/* enum_obj -> enum_obj value done */ +static __exception int js_for_in_next(JSContext *ctx, JSValue *sp) +{ + JSValue enum_obj; + JSObject *p; + JSAtom prop; + JSForInIterator *it; + int ret; + + enum_obj = sp[-1]; + /* fail safe */ + if (JS_VALUE_GET_TAG(enum_obj) != JS_TAG_OBJECT) + goto done; + p = JS_VALUE_GET_OBJ(enum_obj); + if (p->class_id != JS_CLASS_FOR_IN_ITERATOR) + goto done; + it = p->u.for_in_iterator; + + for(;;) { + if (it->is_array) { + if (it->idx >= it->array_length) + goto done; + prop = __JS_AtomFromUInt32(it->idx); + it->idx++; + } else { + JSShape *sh = p->shape; + JSShapeProperty *prs; + if (it->idx >= sh->prop_count) + goto done; + prs = get_shape_prop(sh) + it->idx; + prop = prs->atom; + it->idx++; + if (prop == JS_ATOM_NULL || !(prs->flags & JS_PROP_ENUMERABLE)) + continue; + } + // check if the property was deleted unless we're dealing with a proxy + JSValue obj = it->obj; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_PROXY) + break; + } + ret = JS_HasProperty(ctx, obj, prop); + if (ret < 0) + return ret; + if (ret) + break; + } + /* return the property */ + sp[0] = JS_AtomToValue(ctx, prop); + sp[1] = JS_FALSE; + return 0; + done: + /* return the end */ + sp[0] = JS_UNDEFINED; + sp[1] = JS_TRUE; + return 0; +} + +static JSValue JS_GetIterator2(JSContext *ctx, JSValue obj, + JSValue method) +{ + JSValue enum_obj; + + enum_obj = JS_Call(ctx, method, obj, 0, NULL); + if (JS_IsException(enum_obj)) + return enum_obj; + if (!JS_IsObject(enum_obj)) { + JS_FreeValue(ctx, enum_obj); + return JS_ThrowTypeErrorNotAnObject(ctx); + } + return enum_obj; +} + +static JSValue JS_GetIterator(JSContext *ctx, JSValue obj, BOOL is_async) +{ + JSValue method, ret, sync_iter; + + if (is_async) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_asyncIterator); + if (JS_IsException(method)) + return method; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + sync_iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(sync_iter)) + return sync_iter; + ret = JS_CreateAsyncFromSyncIterator(ctx, sync_iter); + JS_FreeValue(ctx, sync_iter); + return ret; + } + } else { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return method; + } + if (!JS_IsFunction(ctx, method)) { + JS_FreeValue(ctx, method); + return JS_ThrowTypeError(ctx, "value is not iterable"); + } + ret = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + return ret; +} + +/* return *pdone = 2 if the iterator object is not parsed */ +static JSValue JS_IteratorNext2(JSContext *ctx, JSValue enum_obj, + JSValue method, + int argc, JSValue *argv, int *pdone) +{ + JSValue obj; + + /* fast path for the built-in iterators (avoid creating the + intermediate result object) */ + if (JS_IsObject(method)) { + JSObject *p = JS_VALUE_GET_OBJ(method); + if (p->class_id == JS_CLASS_C_FUNCTION && + p->u.cfunc.cproto == JS_CFUNC_iterator_next) { + JSCFunctionType func; + JSValue args[1]; + + /* in case the function expects one argument */ + if (argc == 0) { + args[0] = JS_UNDEFINED; + argv = args; + } + func = p->u.cfunc.c_function; + return func.iterator_next(ctx, enum_obj, argc, argv, + pdone, p->u.cfunc.magic); + } + } + obj = JS_Call(ctx, method, enum_obj, argc, argv); + if (JS_IsException(obj)) + goto fail; + if (!JS_IsObject(obj)) { + JS_FreeValue(ctx, obj); + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto fail; + } + *pdone = 2; + return obj; + fail: + *pdone = FALSE; + return JS_EXCEPTION; +} + +static JSValue JS_IteratorNext(JSContext *ctx, JSValue enum_obj, + JSValue method, + int argc, JSValue *argv, BOOL *pdone) +{ + JSValue obj, value, done_val; + int done; + + obj = JS_IteratorNext2(ctx, enum_obj, method, argc, argv, &done); + if (JS_IsException(obj)) + goto fail; + if (done != 2) { + *pdone = done; + return obj; + } else { + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + *pdone = JS_ToBoolFree(ctx, done_val); + value = JS_UNDEFINED; + if (!*pdone) { + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + } + JS_FreeValue(ctx, obj); + return value; + } + fail: + JS_FreeValue(ctx, obj); + *pdone = FALSE; + return JS_EXCEPTION; +} + +/* return < 0 in case of exception */ +static int JS_IteratorClose(JSContext *ctx, JSValue enum_obj, + BOOL is_exception_pending) +{ + JSValue method, ret, ex_obj; + int res; + + if (is_exception_pending) { + ex_obj = ctx->rt->current_exception; + ctx->rt->current_exception = JS_UNINITIALIZED; + res = -1; + } else { + ex_obj = JS_UNDEFINED; + res = 0; + } + method = JS_GetProperty(ctx, enum_obj, JS_ATOM_return); + if (JS_IsException(method)) { + res = -1; + goto done; + } + if (JS_IsUndefined(method) || JS_IsNull(method)) { + goto done; + } + ret = JS_CallFree(ctx, method, enum_obj, 0, NULL); + if (!is_exception_pending) { + if (JS_IsException(ret)) { + res = -1; + } else if (!JS_IsObject(ret)) { + JS_ThrowTypeErrorNotAnObject(ctx); + res = -1; + } + } + JS_FreeValue(ctx, ret); + done: + if (is_exception_pending) { + JS_Throw(ctx, ex_obj); + } + return res; +} + +/* obj -> enum_rec (3 slots) */ +static __exception int js_for_of_start(JSContext *ctx, JSValue *sp, + BOOL is_async) +{ + JSValue op1, obj, method; + op1 = sp[-1]; + obj = JS_GetIterator(ctx, op1, is_async); + if (JS_IsException(obj)) + return -1; + JS_FreeValue(ctx, op1); + sp[-1] = obj; + method = JS_GetProperty(ctx, obj, JS_ATOM_next); + if (JS_IsException(method)) + return -1; + sp[0] = method; + return 0; +} + +/* enum_rec [objs] -> enum_rec [objs] value done. There are 'offset' + objs. If 'done' is true or in case of exception, 'enum_rec' is set + to undefined. If 'done' is true, 'value' is always set to + undefined. */ +static __exception int js_for_of_next(JSContext *ctx, JSValue *sp, int offset) +{ + JSValue value = JS_UNDEFINED; + int done = 1; + + if (likely(!JS_IsUndefined(sp[offset]))) { + value = JS_IteratorNext(ctx, sp[offset], sp[offset + 1], 0, NULL, &done); + if (JS_IsException(value)) + done = -1; + if (done) { + /* value is JS_UNDEFINED or JS_EXCEPTION */ + /* replace the iteration object with undefined */ + JS_FreeValue(ctx, sp[offset]); + sp[offset] = JS_UNDEFINED; + if (done < 0) { + return -1; + } else { + JS_FreeValue(ctx, value); + value = JS_UNDEFINED; + } + } + } + sp[0] = value; + sp[1] = js_bool(done); + return 0; +} + +static JSValue JS_IteratorGetCompleteValue(JSContext *ctx, JSValue obj, + BOOL *pdone) +{ + JSValue done_val, value; + BOOL done; + done_val = JS_GetProperty(ctx, obj, JS_ATOM_done); + if (JS_IsException(done_val)) + goto fail; + done = JS_ToBoolFree(ctx, done_val); + value = JS_GetProperty(ctx, obj, JS_ATOM_value); + if (JS_IsException(value)) + goto fail; + *pdone = done; + return value; + fail: + *pdone = FALSE; + return JS_EXCEPTION; +} + +static __exception int js_iterator_get_value_done(JSContext *ctx, JSValue *sp) +{ + JSValue obj, value; + BOOL done; + obj = sp[-1]; + if (!JS_IsObject(obj)) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + return -1; + } + value = JS_IteratorGetCompleteValue(ctx, obj, &done); + if (JS_IsException(value)) + return -1; + JS_FreeValue(ctx, obj); + sp[-1] = value; + sp[0] = js_bool(done); + return 0; +} + +static JSValue js_create_iterator_result(JSContext *ctx, + JSValue val, + BOOL done) +{ + JSValue obj; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, val); + return obj; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_value, + val, JS_PROP_C_W_E) < 0) { + goto fail; + } + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_done, + js_bool(done), JS_PROP_C_W_E) < 0) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static JSValue js_array_iterator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic); + +static JSValue js_create_array_iterator(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic); + +static BOOL js_is_fast_array(JSContext *ctx, JSValue obj) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + return TRUE; + } + } + return FALSE; +} + +/* Access an Array's internal JSValue array if available */ +static BOOL js_get_fast_array(JSContext *ctx, JSValue obj, + JSValue **arrpp, uint32_t *countp) +{ + /* Try and handle fast arrays explicitly */ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_ARRAY && p->fast_array) { + *countp = p->u.array.count; + *arrpp = p->u.array.u.values; + return TRUE; + } + } + return FALSE; +} + +static __exception int js_append_enumerate(JSContext *ctx, JSValue *sp) +{ + JSValue iterator, enumobj, method, value; + int is_array_iterator; + JSValue *arrp; + uint32_t i, count32, pos; + + if (JS_VALUE_GET_TAG(sp[-2]) != JS_TAG_INT) { + JS_ThrowInternalError(ctx, "invalid index for append"); + return -1; + } + + pos = JS_VALUE_GET_INT(sp[-2]); + + /* XXX: further optimisations: + - use ctx->array_proto_values? + - check if array_iterator_prototype next method is built-in and + avoid constructing actual iterator object? + - build this into js_for_of_start and use in all `for (x of o)` loops + */ + iterator = JS_GetProperty(ctx, sp[-1], JS_ATOM_Symbol_iterator); + if (JS_IsException(iterator)) + return -1; + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = js_create_array_iterator }; + is_array_iterator = JS_IsCFunction(ctx, iterator, + ft.generic, + JS_ITERATOR_KIND_VALUE); + JS_FreeValue(ctx, iterator); + + enumobj = JS_GetIterator(ctx, sp[-1], FALSE); + if (JS_IsException(enumobj)) + return -1; + method = JS_GetProperty(ctx, enumobj, JS_ATOM_next); + if (JS_IsException(method)) { + JS_FreeValue(ctx, enumobj); + return -1; + } + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft2 = { .iterator_next = js_array_iterator_next }; + if (is_array_iterator + && JS_IsCFunction(ctx, method, ft2.generic, 0) + && js_get_fast_array(ctx, sp[-1], &arrp, &count32)) { + uint32_t len; + if (js_get_length32(ctx, &len, sp[-1])) + goto exception; + /* if len > count32, the elements >= count32 might be read in + the prototypes and might have side effects */ + if (len != count32) + goto general_case; + /* Handle fast arrays explicitly */ + for (i = 0; i < count32; i++) { + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, + js_dup(arrp[i]), JS_PROP_C_W_E) < 0) + goto exception; + } + } else { + general_case: + for (;;) { + BOOL done; + value = JS_IteratorNext(ctx, enumobj, method, 0, NULL, &done); + if (JS_IsException(value)) + goto exception; + if (done) { + /* value is JS_UNDEFINED */ + break; + } + if (JS_DefinePropertyValueUint32(ctx, sp[-3], pos++, value, JS_PROP_C_W_E) < 0) + goto exception; + } + } + /* Note: could raise an error if too many elements */ + sp[-2] = js_int32(pos); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return 0; + +exception: + JS_IteratorClose(ctx, enumobj, TRUE); + JS_FreeValue(ctx, enumobj); + JS_FreeValue(ctx, method); + return -1; +} + +static __exception int JS_CopyDataProperties(JSContext *ctx, + JSValue target, + JSValue source, + JSValue excluded, + BOOL setprop) +{ + JSPropertyEnum *tab_atom; + JSValue val; + uint32_t i, tab_atom_count; + JSObject *p; + JSObject *pexcl = NULL; + int ret, gpn_flags; + JSPropertyDescriptor desc; + BOOL is_enumerable; + + if (JS_VALUE_GET_TAG(source) != JS_TAG_OBJECT) + return 0; + + if (JS_VALUE_GET_TAG(excluded) == JS_TAG_OBJECT) + pexcl = JS_VALUE_GET_OBJ(excluded); + + p = JS_VALUE_GET_OBJ(source); + + gpn_flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK | JS_GPN_ENUM_ONLY; + if (p->is_exotic) { + const JSClassExoticMethods *em = ctx->rt->class_array[p->class_id].exotic; + /* cannot use JS_GPN_ENUM_ONLY with e.g. proxies because it + introduces a visible change */ + if (em && em->get_own_property_names) { + gpn_flags &= ~JS_GPN_ENUM_ONLY; + } + } + if (JS_GetOwnPropertyNamesInternal(ctx, &tab_atom, &tab_atom_count, p, + gpn_flags)) + return -1; + + for (i = 0; i < tab_atom_count; i++) { + if (pexcl) { + ret = JS_GetOwnPropertyInternal(ctx, NULL, pexcl, tab_atom[i].atom); + if (ret) { + if (ret < 0) + goto exception; + continue; + } + } + if (!(gpn_flags & JS_GPN_ENUM_ONLY)) { + /* test if the property is enumerable */ + ret = JS_GetOwnPropertyInternal(ctx, &desc, p, tab_atom[i].atom); + if (ret < 0) + goto exception; + if (!ret) + continue; + is_enumerable = (desc.flags & JS_PROP_ENUMERABLE) != 0; + js_free_desc(ctx, &desc); + if (!is_enumerable) + continue; + } + val = JS_GetProperty(ctx, source, tab_atom[i].atom); + if (JS_IsException(val)) + goto exception; + if (setprop) + ret = JS_SetProperty(ctx, target, tab_atom[i].atom, val); + else + ret = JS_DefinePropertyValue(ctx, target, tab_atom[i].atom, val, + JS_PROP_C_W_E); + if (ret < 0) + goto exception; + } + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return 0; + exception: + js_free_prop_enum(ctx, tab_atom, tab_atom_count); + return -1; +} + +/* only valid inside C functions */ +static JSValue JS_GetActiveFunction(JSContext *ctx) +{ + return ctx->rt->current_stack_frame->cur_func; +} + +static JSVarRef *get_var_ref(JSContext *ctx, JSStackFrame *sf, + int var_idx, BOOL is_arg) +{ + JSVarRef *var_ref; + struct list_head *el; + + list_for_each(el, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, header.link); + if (var_ref->var_idx == var_idx && var_ref->is_arg == is_arg) { + var_ref->header.ref_count++; + return var_ref; + } + } + /* create a new one */ + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + var_ref->header.ref_count = 1; + var_ref->is_detached = FALSE; + var_ref->is_arg = is_arg; + var_ref->var_idx = var_idx; + list_add_tail(&var_ref->header.link, &sf->var_ref_list); + if (is_arg) + var_ref->pvalue = &sf->arg_buf[var_idx]; + else + var_ref->pvalue = &sf->var_buf[var_idx]; + var_ref->value = JS_UNDEFINED; + return var_ref; +} + +static JSValue js_closure2(JSContext *ctx, JSValue func_obj, + JSFunctionBytecode *b, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSObject *p; + JSVarRef **var_refs; + int i; + + p = JS_VALUE_GET_OBJ(func_obj); + p->u.func.function_bytecode = b; + p->u.func.home_object = NULL; + p->u.func.var_refs = NULL; + if (b->closure_var_count) { + var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); + if (!var_refs) + goto fail; + p->u.func.var_refs = var_refs; + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSVarRef *var_ref; + if (cv->is_local) { + /* reuse the existing variable reference if it already exists */ + var_ref = get_var_ref(ctx, sf, cv->var_idx, cv->is_arg); + if (!var_ref) + goto fail; + } else { + var_ref = cur_var_refs[cv->var_idx]; + var_ref->header.ref_count++; + } + var_refs[i] = var_ref; + } + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +static JSValue js_instantiate_prototype(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque) +{ + JSValue obj, this_val; + int ret; + + this_val = JS_MKPTR(JS_TAG_OBJECT, p); + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + set_cycle_flag(ctx, obj); + set_cycle_flag(ctx, this_val); + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_constructor, + js_dup(this_val), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + if (ret < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static const uint16_t func_kind_to_class_id[] = { + [JS_FUNC_NORMAL] = JS_CLASS_BYTECODE_FUNCTION, + [JS_FUNC_GENERATOR] = JS_CLASS_GENERATOR_FUNCTION, + [JS_FUNC_ASYNC] = JS_CLASS_ASYNC_FUNCTION, + [JS_FUNC_ASYNC_GENERATOR] = JS_CLASS_ASYNC_GENERATOR_FUNCTION, +}; + +static JSValue js_closure(JSContext *ctx, JSValue bfunc, + JSVarRef **cur_var_refs, + JSStackFrame *sf) +{ + JSFunctionBytecode *b; + JSValue func_obj; + JSAtom name_atom; + + b = JS_VALUE_GET_PTR(bfunc); + func_obj = JS_NewObjectClass(ctx, func_kind_to_class_id[b->func_kind]); + if (JS_IsException(func_obj)) { + JS_FreeValue(ctx, bfunc); + return JS_EXCEPTION; + } + func_obj = js_closure2(ctx, func_obj, b, cur_var_refs, sf); + if (JS_IsException(func_obj)) { + /* bfunc has been freed */ + goto fail; + } + name_atom = b->func_name; + if (name_atom == JS_ATOM_NULL) + name_atom = JS_ATOM_empty_string; + js_function_set_properties(ctx, func_obj, name_atom, + b->defined_arg_count); + + if (b->func_kind & JS_FUNC_GENERATOR) { + JSValue proto; + int proto_class_id; + /* generators have a prototype field which is used as + prototype for the generator object */ + if (b->func_kind == JS_FUNC_ASYNC_GENERATOR) + proto_class_id = JS_CLASS_ASYNC_GENERATOR; + else + proto_class_id = JS_CLASS_GENERATOR; + proto = JS_NewObjectProto(ctx, ctx->class_proto[proto_class_id]); + if (JS_IsException(proto)) + goto fail; + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, proto, + JS_PROP_WRITABLE); + } else if (b->has_prototype) { + /* add the 'prototype' property: delay instantiation to avoid + creating cycles for every javascript function. The prototype + object is created on the fly when first accessed */ + JS_SetConstructorBit(ctx, func_obj, TRUE); + JS_DefineAutoInitProperty(ctx, func_obj, JS_ATOM_prototype, + JS_AUTOINIT_ID_PROTOTYPE, NULL, + JS_PROP_WRITABLE); + } + return func_obj; + fail: + /* bfunc is freed when func_obj is freed */ + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +#define JS_DEFINE_CLASS_HAS_HERITAGE (1 << 0) + +static int js_op_define_class(JSContext *ctx, JSValue *sp, + JSAtom class_name, int class_flags, + JSVarRef **cur_var_refs, + JSStackFrame *sf, BOOL is_computed_name) +{ + JSValue bfunc, parent_class, proto = JS_UNDEFINED; + JSValue ctor = JS_UNDEFINED, parent_proto = JS_UNDEFINED; + JSFunctionBytecode *b; + + parent_class = sp[-2]; + bfunc = sp[-1]; + + if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) { + if (JS_IsNull(parent_class)) { + parent_proto = JS_NULL; + parent_class = js_dup(ctx->function_proto); + } else { + if (!JS_IsConstructor(ctx, parent_class)) { + JS_ThrowTypeError(ctx, "parent class must be constructor"); + goto fail; + } + parent_proto = JS_GetProperty(ctx, parent_class, JS_ATOM_prototype); + if (JS_IsException(parent_proto)) + goto fail; + if (!JS_IsNull(parent_proto) && !JS_IsObject(parent_proto)) { + JS_ThrowTypeError(ctx, "parent prototype must be an object or null"); + goto fail; + } + } + } else { + /* parent_class is JS_UNDEFINED in this case */ + parent_proto = js_dup(ctx->class_proto[JS_CLASS_OBJECT]); + parent_class = js_dup(ctx->function_proto); + } + proto = JS_NewObjectProto(ctx, parent_proto); + if (JS_IsException(proto)) + goto fail; + + b = JS_VALUE_GET_PTR(bfunc); + assert(b->func_kind == JS_FUNC_NORMAL); + ctor = JS_NewObjectProtoClass(ctx, parent_class, + JS_CLASS_BYTECODE_FUNCTION); + if (JS_IsException(ctor)) + goto fail; + ctor = js_closure2(ctx, ctor, b, cur_var_refs, sf); + bfunc = JS_UNDEFINED; + if (JS_IsException(ctor)) + goto fail; + js_method_set_home_object(ctx, ctor, proto); + JS_SetConstructorBit(ctx, ctor, TRUE); + + JS_DefinePropertyValue(ctx, ctor, JS_ATOM_length, + js_int32(b->defined_arg_count), + JS_PROP_CONFIGURABLE); + + if (is_computed_name) { + if (JS_DefineObjectNameComputed(ctx, ctor, sp[-3], + JS_PROP_CONFIGURABLE) < 0) + goto fail; + } else { + if (JS_DefineObjectName(ctx, ctor, class_name, JS_PROP_CONFIGURABLE) < 0) + goto fail; + } + + /* the constructor property must be first. It can be overriden by + computed property names */ + if (JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + js_dup(ctor), + JS_PROP_CONFIGURABLE | + JS_PROP_WRITABLE | JS_PROP_THROW) < 0) + goto fail; + /* set the prototype property */ + if (JS_DefinePropertyValue(ctx, ctor, JS_ATOM_prototype, + js_dup(proto), JS_PROP_THROW) < 0) + goto fail; + set_cycle_flag(ctx, ctor); + set_cycle_flag(ctx, proto); + + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, parent_class); + + sp[-2] = ctor; + sp[-1] = proto; + return 0; + fail: + JS_FreeValue(ctx, parent_class); + JS_FreeValue(ctx, parent_proto); + JS_FreeValue(ctx, bfunc); + JS_FreeValue(ctx, proto); + JS_FreeValue(ctx, ctor); + sp[-2] = JS_UNDEFINED; + sp[-1] = JS_UNDEFINED; + return -1; +} + +static void close_var_refs(JSRuntime *rt, JSStackFrame *sf) +{ + struct list_head *el, *el1; + JSVarRef *var_ref; + int var_idx; + + list_for_each_safe(el, el1, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, header.link); + var_idx = var_ref->var_idx; + if (var_ref->is_arg) + var_ref->value = js_dup(sf->arg_buf[var_idx]); + else + var_ref->value = js_dup(sf->var_buf[var_idx]); + var_ref->pvalue = &var_ref->value; + /* the reference is no longer to a local variable */ + var_ref->is_detached = TRUE; + add_gc_object(rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + } +} + +static void close_lexical_var(JSContext *ctx, JSStackFrame *sf, int var_idx) +{ + struct list_head *el, *el1; + JSVarRef *var_ref; + + list_for_each_safe(el, el1, &sf->var_ref_list) { + var_ref = list_entry(el, JSVarRef, header.link); + if (var_idx == var_ref->var_idx && !var_ref->is_arg) { + var_ref->value = js_dup(sf->var_buf[var_idx]); + var_ref->pvalue = &var_ref->value; + list_del(&var_ref->header.link); + /* the reference is no longer to a local variable */ + var_ref->is_detached = TRUE; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + } + } +} + +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) +#define JS_CALL_FLAG_GENERATOR (1 << 2) + +static JSValue js_call_c_function(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, int flags) +{ + JSRuntime *rt = ctx->rt; + JSCFunctionType func; + JSObject *p; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSValue ret_val; + JSValue *arg_buf; + int arg_count, i; + JSCFunctionEnum cproto; + + p = JS_VALUE_GET_OBJ(func_obj); + cproto = p->u.cfunc.cproto; + arg_count = p->u.cfunc.length; + + /* better to always check stack overflow */ + if (js_check_stack_overflow(rt, sizeof(arg_buf[0]) * arg_count)) + return JS_ThrowStackOverflow(ctx); + + prev_sf = rt->current_stack_frame; + sf->prev_frame = prev_sf; + rt->current_stack_frame = sf; + ctx = p->u.cfunc.realm; /* change the current realm */ + + sf->is_strict_mode = FALSE; + sf->cur_func = func_obj; + sf->arg_count = argc; + arg_buf = argv; + + if (unlikely(argc < arg_count)) { + /* ensure that at least argc_count arguments are readable */ + arg_buf = alloca(sizeof(arg_buf[0]) * arg_count); + for(i = 0; i < argc; i++) + arg_buf[i] = argv[i]; + for(i = argc; i < arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = arg_count; + } + sf->arg_buf = arg_buf; + + func = p->u.cfunc.c_function; + switch(cproto) { + case JS_CFUNC_constructor: + case JS_CFUNC_constructor_or_func: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor) { + not_a_constructor: + ret_val = JS_ThrowTypeError(ctx, "must be called with new"); + break; + } else { + this_obj = JS_UNDEFINED; + } + } + /* here this_obj is new_target */ + /* fall thru */ + case JS_CFUNC_generic: + ret_val = func.generic(ctx, this_obj, argc, arg_buf); + break; + case JS_CFUNC_constructor_magic: + case JS_CFUNC_constructor_or_func_magic: + if (!(flags & JS_CALL_FLAG_CONSTRUCTOR)) { + if (cproto == JS_CFUNC_constructor_magic) { + goto not_a_constructor; + } else { + this_obj = JS_UNDEFINED; + } + } + /* fall thru */ + case JS_CFUNC_generic_magic: + ret_val = func.generic_magic(ctx, this_obj, argc, arg_buf, + p->u.cfunc.magic); + break; + case JS_CFUNC_getter: + ret_val = func.getter(ctx, this_obj); + break; + case JS_CFUNC_setter: + ret_val = func.setter(ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_getter_magic: + ret_val = func.getter_magic(ctx, this_obj, p->u.cfunc.magic); + break; + case JS_CFUNC_setter_magic: + ret_val = func.setter_magic(ctx, this_obj, arg_buf[0], p->u.cfunc.magic); + break; + case JS_CFUNC_f_f: + { + double d1; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = js_number(func.f_f(d1)); + } + break; + case JS_CFUNC_f_f_f: + { + double d1, d2; + + if (unlikely(JS_ToFloat64(ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + if (unlikely(JS_ToFloat64(ctx, &d2, arg_buf[1]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = js_number(func.f_f_f(d1, d2)); + } + break; + case JS_CFUNC_iterator_next: + { + int done; + ret_val = func.iterator_next(ctx, this_obj, argc, arg_buf, + &done, p->u.cfunc.magic); + if (!JS_IsException(ret_val) && done != 2) { + ret_val = js_create_iterator_result(ctx, ret_val, done); + } + } + break; + default: + abort(); + } + + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +static JSValue js_call_bound_function(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, int flags) +{ + JSObject *p; + JSBoundFunction *bf; + JSValue *arg_buf, new_target; + int arg_count, i; + + p = JS_VALUE_GET_OBJ(func_obj); + bf = p->u.bound_function; + arg_count = bf->argc + argc; + if (js_check_stack_overflow(ctx->rt, sizeof(JSValue) * arg_count)) + return JS_ThrowStackOverflow(ctx); + arg_buf = alloca(sizeof(JSValue) * arg_count); + for(i = 0; i < bf->argc; i++) { + arg_buf[i] = bf->argv[i]; + } + for(i = 0; i < argc; i++) { + arg_buf[bf->argc + i] = argv[i]; + } + if (flags & JS_CALL_FLAG_CONSTRUCTOR) { + new_target = this_obj; + if (js_same_value(ctx, func_obj, new_target)) + new_target = bf->func_obj; + return JS_CallConstructor2(ctx, bf->func_obj, new_target, + arg_count, arg_buf); + } else { + return JS_Call(ctx, bf->func_obj, bf->this_val, + arg_count, arg_buf); + } +} + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_ARGUMENTS, + OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS, + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_NEW_TARGET, + OP_SPECIAL_OBJECT_HOME_OBJECT, + OP_SPECIAL_OBJECT_VAR_OBJECT, + OP_SPECIAL_OBJECT_IMPORT_META, +} OPSpecialObjectEnum; + +#define FUNC_RET_AWAIT 0 +#define FUNC_RET_YIELD 1 +#define FUNC_RET_YIELD_STAR 2 + +#if defined(DUMP_BYTECODE_FINAL) || \ + defined(DUMP_BYTECODE_PASS2) || \ + defined(DUMP_BYTECODE_PASS1) || \ + defined(DUMP_BYTECODE_STACK) || \ + defined(DUMP_BYTECODE_STEP) || \ + defined(DUMP_READ_OBJECT) +#define DUMP_BYTECODE +#endif + +#ifdef DUMP_BYTECODE +static void dump_single_byte_code(JSContext *ctx, const uint8_t *pc, + JSFunctionBytecode *b, int start_pos); +static void print_func_name(JSFunctionBytecode *b); +#endif + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallInternal(JSContext *caller_ctx, JSValue func_obj, + JSValue this_obj, JSValue new_target, + int argc, JSValue *argv, int flags) +{ + JSRuntime *rt = caller_ctx->rt; + JSContext *ctx; + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + uint8_t *pc; + int opcode, arg_allocated_size, i; + JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, *sp, ret_val, *pval; + JSVarRef **var_refs; + size_t alloca_size; + JSInlineCache *ic; + +#ifdef DUMP_BYTECODE_STEP +#define DUMP_BYTECODE_OR_DONT(pc) \ + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_STEP)) dump_single_byte_code(ctx, pc, b, 0); +#else +#define DUMP_BYTECODE_OR_DONT(pc) +#endif + +#if !DIRECT_DISPATCH +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break +#else + __extension__ static const void * const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" + [ OP_COUNT ... 255 ] = &&case_default + }; +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) +#endif + + if (js_poll_interrupts(caller_ctx)) + return JS_EXCEPTION; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) { + if (flags & JS_CALL_FLAG_GENERATOR) { + JSAsyncFunctionState *s = JS_VALUE_GET_PTR(func_obj); + /* func_obj get contains a pointer to JSFuncAsyncState */ + /* the stack frame is already allocated */ + sf = &s->frame; + p = JS_VALUE_GET_OBJ(sf->cur_func); + b = p->u.func.function_bytecode; + ctx = b->realm; + var_refs = p->u.func.var_refs; + local_buf = arg_buf = sf->arg_buf; + var_buf = sf->var_buf; + stack_buf = sf->var_buf + b->var_count; + sp = sf->cur_sp; + sf->cur_sp = NULL; /* cur_sp is NULL if the function is running */ + pc = sf->cur_pc; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + ic = b->ic; + if (s->throw_flag) + goto exception; + else + goto restart; + } else { + goto not_a_function; + } + } + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeError(caller_ctx, "not a function"); + } + return call_func(caller_ctx, func_obj, this_obj, argc, + argv, flags); + } + b = p->u.func.function_bytecode; + + if (unlikely(argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + arg_allocated_size = b->arg_count; + } else { + arg_allocated_size = 0; + } + + alloca_size = sizeof(JSValue) * (arg_allocated_size + b->var_count + + b->stack_size); + if (js_check_stack_overflow(rt, alloca_size)) + return JS_ThrowStackOverflow(caller_ctx); + + sf->is_strict_mode = b->is_strict_mode; + arg_buf = argv; + sf->arg_count = argc; + sf->cur_func = func_obj; + init_list_head(&sf->var_ref_list); + var_refs = p->u.func.var_refs; + + local_buf = alloca(alloca_size); + if (unlikely(arg_allocated_size)) { + int n = min_int(argc, b->arg_count); + arg_buf = local_buf; + for(i = 0; i < n; i++) + arg_buf[i] = JS_DupValue(caller_ctx, argv[i]); + for(; i < b->arg_count; i++) + arg_buf[i] = JS_UNDEFINED; + sf->arg_count = b->arg_count; + } + var_buf = local_buf + arg_allocated_size; + sf->var_buf = var_buf; + sf->arg_buf = arg_buf; + + for(i = 0; i < b->var_count; i++) + var_buf[i] = JS_UNDEFINED; + + stack_buf = var_buf + b->var_count; + sp = stack_buf; + pc = b->byte_code_buf; + /* sf->cur_pc must we set to pc before any recursive calls to JS_CallInternal. */ + sf->cur_pc = NULL; + sf->prev_frame = rt->current_stack_frame; + rt->current_stack_frame = sf; + ctx = b->realm; /* set the current realm */ + ic = b->ic; + +#ifdef DUMP_BYTECODE_STEP + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_STEP)) + print_func_name(b); +#endif + + restart: + for(;;) { + int call_argc; + JSValue *call_argv; + + SWITCH(pc) { + CASE(OP_push_i32): + *sp++ = js_int32(get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_push_const): + *sp++ = js_dup(b->cpool[get_u32(pc)]); + pc += 4; + BREAK; + 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_int32(opcode - OP_push_0); + BREAK; + CASE(OP_push_i8): + *sp++ = js_int32(get_i8(pc)); + pc += 1; + BREAK; + CASE(OP_push_i16): + *sp++ = js_int32(get_i16(pc)); + pc += 2; + BREAK; + CASE(OP_push_const8): + *sp++ = js_dup(b->cpool[*pc++]); + BREAK; + CASE(OP_fclosure8): + *sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_push_empty_string): + *sp++ = JS_AtomToString(ctx, JS_ATOM_empty_string); + BREAK; + CASE(OP_get_length): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + CASE(OP_push_atom_value): + *sp++ = JS_AtomToValue(ctx, get_u32(pc)); + pc += 4; + BREAK; + CASE(OP_undefined): + *sp++ = JS_UNDEFINED; + BREAK; + CASE(OP_null): + *sp++ = JS_NULL; + BREAK; + CASE(OP_push_this): + /* OP_push_this is only called at the start of a function */ + { + JSValue val; + if (!b->is_strict_mode) { + uint32_t tag = JS_VALUE_GET_TAG(this_obj); + if (likely(tag == JS_TAG_OBJECT)) + goto normal_this; + if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { + val = js_dup(ctx->global_obj); + } else { + val = JS_ToObject(ctx, this_obj); + if (JS_IsException(val)) + goto exception; + } + } else { + normal_this: + val = js_dup(this_obj); + } + *sp++ = val; + } + BREAK; + CASE(OP_push_false): + *sp++ = JS_FALSE; + BREAK; + CASE(OP_push_true): + *sp++ = JS_TRUE; + BREAK; + CASE(OP_object): + *sp++ = JS_NewObject(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + BREAK; + CASE(OP_special_object): + { + int arg = *pc++; + switch(arg) { + case OP_SPECIAL_OBJECT_ARGUMENTS: + *sp++ = js_build_arguments(ctx, argc, argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: + *sp++ = js_build_mapped_arguments(ctx, argc, argv, + sf, min_int(argc, b->arg_count)); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = js_dup(sf->cur_func); + break; + case OP_SPECIAL_OBJECT_NEW_TARGET: + *sp++ = js_dup(new_target); + break; + case OP_SPECIAL_OBJECT_HOME_OBJECT: + { + JSObject *p1; + p1 = p->u.func.home_object; + if (unlikely(!p1)) + *sp++ = JS_UNDEFINED; + else + *sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + } + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + case OP_SPECIAL_OBJECT_IMPORT_META: + *sp++ = js_import_meta(ctx); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + break; + default: + abort(); + } + } + BREAK; + CASE(OP_rest): + { + int first = get_u16(pc); + pc += 2; + *sp++ = js_build_rest(ctx, first, argc, argv); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + + CASE(OP_drop): + JS_FreeValue(ctx, sp[-1]); + sp--; + BREAK; + CASE(OP_nip): + JS_FreeValue(ctx, sp[-2]); + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + JS_FreeValue(ctx, sp[-3]); + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE(OP_dup): + sp[0] = js_dup(sp[-1]); + sp++; + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + sp[0] = js_dup(sp[-2]); + sp[1] = js_dup(sp[-1]); + sp += 2; + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + sp[0] = js_dup(sp[-3]); + sp[1] = js_dup(sp[-2]); + sp[2] = js_dup(sp[-1]); + sp += 3; + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = js_dup(sp[-2]); + sp++; + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = js_dup(sp[0]); + sp++; + BREAK; + CASE(OP_perm3): /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_rot3l): /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot4l): /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot5l): /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_rot3r): /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE(OP_swap): /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE(OP_swap2): /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE(OP_fclosure): + { + JSValue bfunc = js_dup(b->cpool[get_u32(pc)]); + pc += 4; + *sp++ = js_closure(ctx, bfunc, var_refs, sf); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + } + BREAK; + CASE(OP_call0): + CASE(OP_call1): + CASE(OP_call2): + CASE(OP_call3): + call_argc = opcode - OP_call0; + goto has_call_argc; + CASE(OP_call): + CASE(OP_tail_call): + { + call_argc = get_u16(pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call) + goto done; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_constructor): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallConstructorInternal(ctx, call_argv[-2], + call_argv[-1], + call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_call_method): + CASE(OP_tail_call_method): + { + call_argc = get_u16(pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + ret_val = JS_CallInternal(ctx, call_argv[-1], call_argv[-2], + JS_UNDEFINED, call_argc, call_argv, 0); + if (unlikely(JS_IsException(ret_val))) + goto exception; + if (opcode == OP_tail_call_method) + goto done; + for(i = -2; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE(OP_array_from): + { + int i, ret; + + call_argc = get_u16(pc); + pc += 2; + ret_val = JS_NewArray(ctx); + if (unlikely(JS_IsException(ret_val))) + goto exception; + call_argv = sp - call_argc; + for(i = 0; i < call_argc; i++) { + ret = JS_DefinePropertyValue(ctx, ret_val, __JS_AtomFromUInt32(i), call_argv[i], + JS_PROP_C_W_E | JS_PROP_THROW); + call_argv[i] = JS_UNDEFINED; + if (ret < 0) { + JS_FreeValue(ctx, ret_val); + goto exception; + } + } + sp -= call_argc; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_apply): + { + int magic; + magic = get_u16(pc); + pc += 2; + sf->cur_pc = pc; + + ret_val = js_function_apply(ctx, sp[-3], 2, &sp[-2], magic); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + *sp++ = ret_val; + } + BREAK; + CASE(OP_return): + ret_val = *--sp; + goto done; + CASE(OP_return_undef): + ret_val = JS_UNDEFINED; + goto done; + + CASE(OP_check_ctor_return): + /* return TRUE if 'this' should be returned */ + if (!JS_IsObject(sp[-1])) { + if (!JS_IsUndefined(sp[-1])) { + JS_ThrowTypeError(caller_ctx, "derived class constructor must return an object or undefined"); + goto exception; + } + sp[0] = JS_TRUE; + } else { + sp[0] = JS_FALSE; + } + sp++; + BREAK; + CASE(OP_check_ctor): + if (JS_IsUndefined(new_target)) { + JS_ThrowTypeError(ctx, "class constructors must be invoked with 'new'"); + goto exception; + } + BREAK; + CASE(OP_check_brand): + { + int ret = JS_CheckBrand(ctx, sp[-2], sp[-1]); + if (ret < 0) + goto exception; + if (!ret) { + JS_ThrowTypeError(ctx, "invalid brand on object"); + goto exception; + } + } + BREAK; + CASE(OP_add_brand): + if (JS_AddBrand(ctx, sp[-2], sp[-1]) < 0) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + BREAK; + + CASE(OP_throw): + JS_Throw(ctx, *--sp); + goto exception; + + CASE(OP_throw_error): +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_DELETE_SUPER 3 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSAtom atom; + int type; + atom = get_u32(pc); + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_RO) + JS_ThrowTypeErrorReadOnly(ctx, JS_PROP_THROW, atom); + else + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration(ctx, atom); + else + if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized(ctx, atom); + else + if (type == JS_THROW_ERROR_DELETE_SUPER) + JS_ThrowReferenceError(ctx, "unsupported reference to 'super'"); + else + if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError(ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError(ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE(OP_eval): + { + JSValue obj; + int scope_idx; + call_argc = get_u16(pc); + scope_idx = get_u16(pc + 2) - 1; + pc += 4; + call_argv = sp - call_argc; + sf->cur_pc = pc; + if (js_same_value(ctx, call_argv[-1], ctx->eval_obj)) { + if (call_argc >= 1) + obj = call_argv[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_CallInternal(ctx, call_argv[-1], JS_UNDEFINED, + JS_UNDEFINED, call_argc, call_argv, 0); + } + if (unlikely(JS_IsException(ret_val))) + goto exception; + for(i = -1; i < call_argc; i++) + JS_FreeValue(ctx, call_argv[i]); + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + /* could merge with OP_apply */ + CASE(OP_apply_eval): + { + int scope_idx; + uint32_t len; + JSValue *tab; + JSValue obj; + + scope_idx = get_u16(pc) - 1; + pc += 2; + sf->cur_pc = pc; + tab = build_arg_list(ctx, &len, sp[-1]); + if (!tab) + goto exception; + if (js_same_value(ctx, sp[-2], ctx->eval_obj)) { + if (len >= 1) + obj = tab[0]; + else + obj = JS_UNDEFINED; + ret_val = JS_EvalObject(ctx, JS_UNDEFINED, obj, + JS_EVAL_TYPE_DIRECT, scope_idx); + } else { + ret_val = JS_Call(ctx, sp[-2], JS_UNDEFINED, len, + tab); + } + free_arg_list(ctx, tab, len); + if (unlikely(JS_IsException(ret_val))) + goto exception; + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + *sp++ = ret_val; + } + BREAK; + + CASE(OP_regexp): + { + sp[-2] = js_regexp_constructor_internal(ctx, JS_UNDEFINED, + sp[-2], sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_super): + { + JSValue proto; + proto = JS_GetPrototype(ctx, sp[-1]); + if (JS_IsException(proto)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = proto; + } + BREAK; + + CASE(OP_import): + { + JSValue val; + sf->cur_pc = pc; + val = js_dynamic_import(ctx, sp[-1]); + if (JS_IsException(val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_check_var): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_CheckGlobalVar(ctx, atom); + if (ret < 0) + goto exception; + *sp++ = js_bool(ret); + } + BREAK; + + CASE(OP_get_var_undef): + CASE(OP_get_var): + { + JSValue val; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + val = JS_GetGlobalVar(ctx, atom, opcode - OP_get_var_undef); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_put_var): + CASE(OP_put_var_init): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + ret = JS_SetGlobalVar(ctx, atom, sp[-1], opcode - OP_put_var); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_var_strict): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + + /* sp[-2] is JS_TRUE or JS_FALSE */ + if (unlikely(!JS_VALUE_GET_INT(sp[-2]))) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + goto exception; + } + ret = JS_SetGlobalVar(ctx, atom, sp[-1], 2); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_check_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_CheckDefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_var): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalVar(ctx, atom, flags)) + goto exception; + } + BREAK; + CASE(OP_define_func): + { + JSAtom atom; + int flags; + atom = get_u32(pc); + flags = pc[4]; + pc += 5; + if (JS_DefineGlobalFunction(ctx, atom, sp[-1], flags)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp--; + } + BREAK; + + CASE(OP_get_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = js_dup(var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], js_dup(sp[-1])); + } + BREAK; + CASE(OP_get_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + sp[0] = js_dup(arg_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_arg): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &arg_buf[idx], js_dup(sp[-1])); + } + BREAK; + + CASE(OP_get_loc8): *sp++ = js_dup(var_buf[*pc++]); BREAK; + CASE(OP_put_loc8): set_value(ctx, &var_buf[*pc++], *--sp); BREAK; + CASE(OP_set_loc8): set_value(ctx, &var_buf[*pc++], js_dup(sp[-1])); BREAK; + + // Observation: get_loc0 and get_loc1 are individually very + // frequent opcodes _and_ they are very often paired together, + // making them ideal candidates for opcode fusion. + CASE(OP_get_loc0_loc1): + *sp++ = js_dup(var_buf[0]); + *sp++ = js_dup(var_buf[1]); + BREAK; + + CASE(OP_get_loc0): *sp++ = js_dup(var_buf[0]); BREAK; + CASE(OP_get_loc1): *sp++ = js_dup(var_buf[1]); BREAK; + CASE(OP_get_loc2): *sp++ = js_dup(var_buf[2]); BREAK; + CASE(OP_get_loc3): *sp++ = js_dup(var_buf[3]); BREAK; + CASE(OP_put_loc0): set_value(ctx, &var_buf[0], *--sp); BREAK; + CASE(OP_put_loc1): set_value(ctx, &var_buf[1], *--sp); BREAK; + CASE(OP_put_loc2): set_value(ctx, &var_buf[2], *--sp); BREAK; + CASE(OP_put_loc3): set_value(ctx, &var_buf[3], *--sp); BREAK; + CASE(OP_set_loc0): set_value(ctx, &var_buf[0], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc1): set_value(ctx, &var_buf[1], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc2): set_value(ctx, &var_buf[2], js_dup(sp[-1])); BREAK; + CASE(OP_set_loc3): set_value(ctx, &var_buf[3], js_dup(sp[-1])); BREAK; + CASE(OP_get_arg0): *sp++ = js_dup(arg_buf[0]); BREAK; + CASE(OP_get_arg1): *sp++ = js_dup(arg_buf[1]); BREAK; + CASE(OP_get_arg2): *sp++ = js_dup(arg_buf[2]); BREAK; + CASE(OP_get_arg3): *sp++ = js_dup(arg_buf[3]); BREAK; + CASE(OP_put_arg0): set_value(ctx, &arg_buf[0], *--sp); BREAK; + CASE(OP_put_arg1): set_value(ctx, &arg_buf[1], *--sp); BREAK; + CASE(OP_put_arg2): set_value(ctx, &arg_buf[2], *--sp); BREAK; + CASE(OP_put_arg3): set_value(ctx, &arg_buf[3], *--sp); BREAK; + CASE(OP_set_arg0): set_value(ctx, &arg_buf[0], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg1): set_value(ctx, &arg_buf[1], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg2): set_value(ctx, &arg_buf[2], js_dup(sp[-1])); BREAK; + CASE(OP_set_arg3): set_value(ctx, &arg_buf[3], js_dup(sp[-1])); BREAK; + CASE(OP_get_var_ref0): *sp++ = js_dup(*var_refs[0]->pvalue); BREAK; + CASE(OP_get_var_ref1): *sp++ = js_dup(*var_refs[1]->pvalue); BREAK; + CASE(OP_get_var_ref2): *sp++ = js_dup(*var_refs[2]->pvalue); BREAK; + CASE(OP_get_var_ref3): *sp++ = js_dup(*var_refs[3]->pvalue); BREAK; + CASE(OP_put_var_ref0): set_value(ctx, var_refs[0]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref1): set_value(ctx, var_refs[1]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref2): set_value(ctx, var_refs[2]->pvalue, *--sp); BREAK; + CASE(OP_put_var_ref3): set_value(ctx, var_refs[3]->pvalue, *--sp); BREAK; + CASE(OP_set_var_ref0): set_value(ctx, var_refs[0]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref1): set_value(ctx, var_refs[1]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref2): set_value(ctx, var_refs[2]->pvalue, js_dup(sp[-1])); BREAK; + CASE(OP_set_var_ref3): set_value(ctx, var_refs[3]->pvalue, js_dup(sp[-1])); BREAK; + + CASE(OP_get_var_ref): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + sp[0] = js_dup(val); + sp++; + } + BREAK; + CASE(OP_put_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_var_ref): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, var_refs[idx]->pvalue, js_dup(sp[-1])); + } + BREAK; + CASE(OP_get_var_ref_check): + { + int idx; + JSValue val; + idx = get_u16(pc); + pc += 2; + val = *var_refs[idx]->pvalue; + if (unlikely(JS_IsUninitialized(val))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + sp[0] = js_dup(val); + sp++; + } + BREAK; + CASE(OP_put_var_ref_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_var_ref_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(*var_refs[idx]->pvalue))) { + JS_ThrowReferenceErrorUninitialized2(ctx, b, idx, TRUE); + goto exception; + } + set_value(ctx, var_refs[idx]->pvalue, sp[-1]); + sp--; + } + BREAK; + CASE(OP_set_loc_uninitialized): + { + int idx; + idx = get_u16(pc); + pc += 2; + set_value(ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE(OP_get_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, + FALSE); + goto exception; + } + sp[0] = js_dup(var_buf[idx]); + sp++; + } + BREAK; + CASE(OP_put_loc_check): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2(caller_ctx, b, idx, + FALSE); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_put_loc_check_init): + { + int idx; + idx = get_u16(pc); + pc += 2; + if (unlikely(!JS_IsUninitialized(var_buf[idx]))) { + JS_ThrowReferenceError(caller_ctx, + "'this' can be initialized only once"); + goto exception; + } + set_value(ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE(OP_close_loc): + { + int idx; + idx = get_u16(pc); + pc += 2; + close_lexical_var(ctx, sf, idx); + } + BREAK; + + CASE(OP_make_loc_ref): + CASE(OP_make_arg_ref): + CASE(OP_make_var_ref_ref): + { + JSVarRef *var_ref; + JSProperty *pr; + JSAtom atom; + int idx; + atom = get_u32(pc); + idx = get_u16(pc + 4); + pc += 6; + *sp++ = JS_NewObjectProto(ctx, JS_NULL); + if (unlikely(JS_IsException(sp[-1]))) + goto exception; + if (opcode == OP_make_var_ref_ref) { + var_ref = var_refs[idx]; + var_ref->header.ref_count++; + } else { + var_ref = get_var_ref(ctx, sf, idx, opcode == OP_make_arg_ref); + if (!var_ref) + goto exception; + } + pr = add_property(ctx, JS_VALUE_GET_OBJ(sp[-1]), atom, + JS_PROP_WRITABLE | JS_PROP_VARREF); + if (!pr) { + free_var_ref(rt, var_ref); + goto exception; + } + pr->u.var_ref = var_ref; + *sp++ = JS_AtomToValue(ctx, atom); + } + BREAK; + CASE(OP_make_var_ref): + { + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + if (JS_GetGlobalVarRef(ctx, atom, sp)) + goto exception; + sp += 2; + } + BREAK; + + CASE(OP_goto): + pc += (int32_t)get_u32(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto16): + pc += (int16_t)get_u16(pc); + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_goto8): + pc += (int8_t)pc[0]; + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + BREAK; + CASE(OP_if_true): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int32_t)get_u32(pc - 4) - 4; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_true8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_if_false8): + { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1); + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp--; + if (!res) { + pc += (int8_t)pc[-1] - 1; + } + if (unlikely(js_poll_interrupts(ctx))) + goto exception; + } + BREAK; + CASE(OP_catch): + { + int32_t diff; + diff = get_u32(pc); + sp[0] = JS_NewCatchOffset(ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE(OP_gosub): + { + int32_t diff; + diff = get_u32(pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = js_int32(pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE(OP_ret): + { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely(JS_VALUE_GET_TAG(op1) != JS_TAG_INT)) + goto ret_fail; + pos = JS_VALUE_GET_INT(op1); + if (unlikely(pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError(ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE(OP_for_in_start): + sf->cur_pc = pc; + if (js_for_in_start(ctx, sp)) + goto exception; + BREAK; + CASE(OP_for_in_next): + sf->cur_pc = pc; + if (js_for_in_next(ctx, sp)) + goto exception; + sp += 2; + BREAK; + CASE(OP_for_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, FALSE)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_for_of_next): + { + int offset = -3 - pc[0]; + pc += 1; + sf->cur_pc = pc; + if (js_for_of_next(ctx, sp, offset)) + goto exception; + sp += 2; + } + BREAK; + CASE(OP_for_await_of_start): + sf->cur_pc = pc; + if (js_for_of_start(ctx, sp, TRUE)) + goto exception; + sp += 1; + *sp++ = JS_NewCatchOffset(ctx, 0); + BREAK; + CASE(OP_iterator_get_value_done): + sf->cur_pc = pc; + if (js_iterator_get_value_done(ctx, sp)) + goto exception; + sp += 1; + BREAK; + CASE(OP_iterator_check_object): + if (unlikely(!JS_IsObject(sp[-1]))) { + JS_ThrowTypeError(ctx, "iterator must return an object"); + goto exception; + } + BREAK; + + CASE(OP_iterator_close): + /* iter_obj next catch_offset -> */ + sp--; /* drop the catch offset to avoid getting caught by exception */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + if (!JS_IsUndefined(sp[-1])) { + sf->cur_pc = pc; + if (JS_IteratorClose(ctx, sp[-1], FALSE)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + } + sp--; + BREAK; + CASE(OP_nip_catch): + { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf && + JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_CATCH_OFFSET) { + JS_FreeValue(ctx, *--sp); + } + if (unlikely(sp == stack_buf)) { + JS_ThrowInternalError(ctx, "nip_catch"); + JS_FreeValue(ctx, ret_val); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_iterator_next): + /* stack: iter_obj next catch_offset val */ + { + JSValue ret; + sf->cur_pc = pc; + ret = JS_Call(ctx, sp[-3], sp[-4], 1, (sp - 1)); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + } + BREAK; + + CASE(OP_iterator_call): + /* stack: iter_obj next catch_offset val */ + { + JSValue method, ret; + BOOL ret_flag; + int flags; + flags = *pc++; + sf->cur_pc = pc; + method = JS_GetProperty(ctx, sp[-4], (flags & 1) ? + JS_ATOM_throw : JS_ATOM_return); + if (JS_IsException(method)) + goto exception; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + ret_flag = TRUE; + } else { + if (flags & 2) { + /* no argument */ + ret = JS_CallFree(ctx, method, sp[-4], + 0, NULL); + } else { + ret = JS_CallFree(ctx, method, sp[-4], + 1, (sp - 1)); + } + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret; + ret_flag = FALSE; + } + sp[0] = js_bool(ret_flag); + sp += 1; + } + BREAK; + + CASE(OP_lnot): + { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG(op1) <= JS_TAG_UNDEFINED) { + res = JS_VALUE_GET_INT(op1) != 0; + } else { + res = JS_ToBoolFree(ctx, op1); + } + sp[-1] = js_bool(!res); + } + BREAK; + + CASE(OP_get_field): + { + JSValue val; + JSAtom atom; + JSInlineCacheUpdate icu; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + icu = (JSInlineCacheUpdate){ic, INLINE_CACHE_MISS}; + val = JS_GetPropertyInternal2(ctx, sp[-1], atom, sp[-1], &icu, FALSE); + if (unlikely(JS_IsException(val))) + goto exception; + if (icu.offset != INLINE_CACHE_MISS) { + put_u8(pc - 5, OP_get_field_ic); + put_u32(pc - 4, icu.offset); + JS_FreeAtom(ctx, atom); + } + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_get_field_ic): + { + JSValue val; + JSAtom atom; + uint32_t ic_offset; + JSInlineCacheUpdate icu; + ic_offset = get_u32(pc); + atom = get_ic_atom(ic, ic_offset); + pc += 4; + sf->cur_pc = pc; + icu = (JSInlineCacheUpdate){ic, ic_offset}; + val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], &icu, FALSE); + if (unlikely(JS_IsException(val))) + goto exception; + assert(icu.offset == ic_offset); + JS_FreeValue(ctx, sp[-1]); + sp[-1] = val; + } + BREAK; + + CASE(OP_get_field2): + { + JSValue val; + JSAtom atom; + JSInlineCacheUpdate icu; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + icu = (JSInlineCacheUpdate){ic, INLINE_CACHE_MISS}; + val = JS_GetPropertyInternal2(ctx, sp[-1], atom, sp[-1], &icu, FALSE); + if (unlikely(JS_IsException(val))) + goto exception; + if (icu.offset != INLINE_CACHE_MISS) { + put_u8(pc - 5, OP_get_field2_ic); + put_u32(pc - 4, icu.offset); + JS_FreeAtom(ctx, atom); + } + *sp++ = val; + } + BREAK; + + CASE(OP_get_field2_ic): + { + JSValue val; + JSAtom atom; + uint32_t ic_offset; + JSInlineCacheUpdate icu; + ic_offset = get_u32(pc); + atom = get_ic_atom(ic, ic_offset); + pc += 4; + sf->cur_pc = pc; + icu = (JSInlineCacheUpdate){ic, ic_offset}; + val = JS_GetPropertyInternalWithIC(ctx, sp[-1], atom, sp[-1], &icu, FALSE); + if (unlikely(JS_IsException(val))) + goto exception; + assert(icu.offset == ic_offset); + *sp++ = val; + } + BREAK; + + CASE(OP_put_field): + { + int ret; + JSAtom atom; + JSInlineCacheUpdate icu; + atom = get_u32(pc); + pc += 4; + sf->cur_pc = pc; + icu = (JSInlineCacheUpdate){ic, INLINE_CACHE_MISS}; + ret = JS_SetPropertyInternal2(ctx, + sp[-2], atom, + sp[-1], sp[-2], + JS_PROP_THROW_STRICT, &icu); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + if (icu.offset != INLINE_CACHE_MISS) { + put_u8(pc - 5, OP_put_field_ic); + put_u32(pc - 4, icu.offset); + JS_FreeAtom(ctx, atom); + } + } + BREAK; + + CASE(OP_put_field_ic): + { + int ret; + JSAtom atom; + uint32_t ic_offset; + JSInlineCacheUpdate icu; + ic_offset = get_u32(pc); + atom = get_ic_atom(ic, ic_offset); + pc += 4; + sf->cur_pc = pc; + icu = (JSInlineCacheUpdate){ic, ic_offset}; + ret = JS_SetPropertyInternalWithIC(ctx, sp[-2], atom, sp[-1], + JS_PROP_THROW_STRICT, &icu); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + assert(icu.offset == ic_offset); + } + BREAK; + + CASE(OP_private_symbol): + { + JSAtom atom; + JSValue val; + + atom = get_u32(pc); + pc += 4; + val = JS_NewSymbolFromAtom(ctx, atom, JS_ATOM_TYPE_PRIVATE); + if (JS_IsException(val)) + goto exception; + *sp++ = val; + } + BREAK; + + CASE(OP_get_private_field): + { + JSValue val; + sf->cur_pc = pc; + val = JS_GetPrivateField(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_put_private_field): + { + int ret; + sf->cur_pc = pc; + ret = JS_SetPrivateField(ctx, sp[-3], sp[-1], sp[-2]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-1]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_private_field): + { + int ret; + ret = JS_DefinePrivateField(ctx, sp[-3], sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_field): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefinePropertyValue(ctx, sp[-2], atom, sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp--; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_set_name): + { + int ret; + JSAtom atom; + atom = get_u32(pc); + pc += 4; + + ret = JS_DefineObjectName(ctx, sp[-1], atom, JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_name_computed): + { + int ret; + ret = JS_DefineObjectNameComputed(ctx, sp[-1], sp[-2], JS_PROP_CONFIGURABLE); + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + CASE(OP_set_proto): + { + JSValue proto; + proto = sp[-1]; + if (JS_IsObject(proto) || JS_IsNull(proto)) { + if (JS_SetPrototypeInternal(ctx, sp[-2], proto, TRUE) < 0) + goto exception; + } + JS_FreeValue(ctx, proto); + sp--; + } + BREAK; + CASE(OP_set_home_object): + js_method_set_home_object(ctx, sp[-1], sp[-2]); + BREAK; + CASE(OP_define_method): + CASE(OP_define_method_computed): + { + JSValue getter, setter, value; + JSValue obj; + JSAtom atom; + int flags, ret, op_flags; + BOOL is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_GETTER 1 +#define OP_DEFINE_METHOD_SETTER 2 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + opcode += OP_define_method - OP_define_method_computed; + } else { + atom = get_u32(pc); + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + flags = JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE | + JS_PROP_HAS_ENUMERABLE | JS_PROP_THROW; + if (op_flags & OP_DEFINE_METHOD_ENUMERABLE) + flags |= JS_PROP_ENUMERABLE; + op_flags &= 3; + value = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + if (op_flags == OP_DEFINE_METHOD_METHOD) { + value = sp[-1]; + flags |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE | JS_PROP_WRITABLE; + } else if (op_flags == OP_DEFINE_METHOD_GETTER) { + getter = sp[-1]; + flags |= JS_PROP_HAS_GET; + } else { + setter = sp[-1]; + flags |= JS_PROP_HAS_SET; + } + ret = js_method_set_properties(ctx, sp[-1], atom, flags, obj); + if (ret >= 0) { + ret = JS_DefineProperty(ctx, obj, atom, value, + getter, setter, flags); + } + JS_FreeValue(ctx, sp[-1]); + if (is_computed) { + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-2]); + } + sp -= 1 + is_computed; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_define_class): + CASE(OP_define_class_computed): + { + int class_flags; + JSAtom atom; + + atom = get_u32(pc); + class_flags = pc[4]; + pc += 5; + if (js_op_define_class(ctx, sp, atom, class_flags, + var_refs, sf, + (opcode == OP_define_class_computed)) < 0) + goto exception; + } + BREAK; + + CASE(OP_get_array_el): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + JS_FreeValue(ctx, sp[-2]); + sp[-2] = val; + sp--; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_array_el2): + { + JSValue val; + + sf->cur_pc = pc; + val = JS_GetPropertyValue(ctx, sp[-2], sp[-1]); + sp[-1] = val; + if (unlikely(JS_IsException(val))) + goto exception; + } + BREAK; + + CASE(OP_get_ref_value): + { + JSValue val; + sf->cur_pc = pc; + if (unlikely(JS_IsUndefined(sp[-2]))) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-1]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } + val = JS_GetPropertyValue(ctx, sp[-2], + js_dup(sp[-1])); + if (unlikely(JS_IsException(val))) + goto exception; + sp[0] = val; + sp++; + } + BREAK; + + CASE(OP_get_super_value): + { + JSValue val; + JSAtom atom; + sf->cur_pc = pc; + atom = JS_ValueToAtom(ctx, sp[-1]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + val = JS_GetPropertyInternal2(ctx, sp[-2], atom, sp[-3], NULL, FALSE); + JS_FreeAtom(ctx, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + JS_FreeValue(ctx, sp[-2]); + JS_FreeValue(ctx, sp[-3]); + sp[-3] = val; + sp -= 2; + } + BREAK; + + CASE(OP_put_array_el): + { + int ret; + sf->cur_pc = pc; + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_ref_value): + { + int ret, flags; + sf->cur_pc = pc; + flags = JS_PROP_THROW_STRICT; + if (unlikely(JS_IsUndefined(sp[-3]))) { + if (is_strict_mode(ctx)) { + JSAtom atom = JS_ValueToAtom(ctx, sp[-2]); + if (atom != JS_ATOM_NULL) { + JS_ThrowReferenceErrorNotDefined(ctx, atom); + JS_FreeAtom(ctx, atom); + } + goto exception; + } else { + sp[-3] = js_dup(ctx->global_obj); + } + } else { + if (is_strict_mode(ctx)) + flags |= JS_PROP_NO_ADD; + } + ret = JS_SetPropertyValue(ctx, sp[-3], sp[-2], sp[-1], flags); + JS_FreeValue(ctx, sp[-3]); + sp -= 3; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_put_super_value): + { + int ret; + JSAtom atom; + sf->cur_pc = pc; + if (JS_VALUE_GET_TAG(sp[-3]) != JS_TAG_OBJECT) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto exception; + } + atom = JS_ValueToAtom(ctx, sp[-2]); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + ret = JS_SetPropertyInternal2(ctx, + sp[-3], atom, + sp[-1], sp[-4], + JS_PROP_THROW_STRICT, NULL); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, sp[-4]); + JS_FreeValue(ctx, sp[-3]); + JS_FreeValue(ctx, sp[-2]); + sp -= 4; + if (ret < 0) + goto exception; + } + BREAK; + + CASE(OP_define_array_el): + { + int ret; + ret = JS_DefinePropertyValueValue(ctx, sp[-3], js_dup(sp[-2]), sp[-1], + JS_PROP_C_W_E | JS_PROP_THROW); + sp -= 1; + if (unlikely(ret < 0)) + goto exception; + } + BREAK; + + CASE(OP_append): /* array pos enumobj -- array pos */ + { + sf->cur_pc = pc; + if (js_append_enumerate(ctx, sp)) + goto exception; + JS_FreeValue(ctx, *--sp); + } + BREAK; + + CASE(OP_copy_data_properties): /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + sf->cur_pc = pc; + if (JS_CopyDataProperties(ctx, sp[-1 - (mask & 3)], + sp[-1 - ((mask >> 2) & 7)], + sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE(OP_add): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) + JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto add_slow; + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) + + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else { + add_slow: + sf->cur_pc = pc; + if (js_add_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_add_loc): + { + JSValue *pv; + int idx; + idx = *pc; + pc += 1; + + pv = &var_buf[idx]; + if (likely(JS_VALUE_IS_BOTH_INT(*pv, sp[-1]))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(*pv) + + JS_VALUE_GET_INT(sp[-1]); + if (unlikely((int)r != r)) + goto add_loc_slow; + *pv = js_int32(r); + sp--; + } else if (JS_VALUE_GET_TAG(*pv) == JS_TAG_STRING) { + JSValue op1; + op1 = sp[-1]; + sp--; + sf->cur_pc = pc; + op1 = JS_ToPrimitiveFree(ctx, op1, HINT_NONE); + if (JS_IsException(op1)) + goto exception; + op1 = JS_ConcatString(ctx, js_dup(*pv), op1); + if (JS_IsException(op1)) + goto exception; + set_value(ctx, pv, op1); + } else { + JSValue ops[2]; + add_loc_slow: + /* In case of exception, js_add_slow frees ops[0] + and ops[1], so we must duplicate *pv */ + sf->cur_pc = pc; + ops[0] = js_dup(*pv); + ops[1] = sp[-1]; + sp--; + if (js_add_slow(ctx, ops + 2)) + goto exception; + set_value(ctx, pv, ops[0]); + } + } + BREAK; + CASE(OP_sub): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT(op1) - JS_VALUE_GET_INT(op2); + if (unlikely((int)r != r)) + goto binary_arith_slow; + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + sp[-2] = js_float64(JS_VALUE_GET_FLOAT64(op1) - + JS_VALUE_GET_FLOAT64(op2)); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mul): + { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + r = (int64_t)v1 * v2; + if (unlikely((int)r != r)) { + d = (double)r; + goto mul_fp_res; + } + /* need to test zero case for -0 result */ + if (unlikely(r == 0 && (v1 | v2) < 0)) { + d = -0.0; + goto mul_fp_res; + } + sp[-2] = js_int32(r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT(op1, op2)) { + d = JS_VALUE_GET_FLOAT64(op1) * JS_VALUE_GET_FLOAT64(op2); + mul_fp_res: + sp[-2] = js_float64(d); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_div): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2); + sp[-2] = js_number((double)v1 / (double)v2); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_mod): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + 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); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely(v1 < 0 || v2 <= 0)) + goto binary_arith_slow; + r = v1 % v2; + sp[-2] = js_int32(r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE(OP_pow): + binary_arith_slow: + sf->cur_pc = pc; + if (js_binary_arith_slow(ctx, sp, opcode)) + goto exception; + sp--; + BREAK; + + CASE(OP_plus): + { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) { + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_neg): + { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG(op1); + if (tag == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + /* Note: -0 cannot be expressed as integer */ + if (unlikely(val == 0)) { + d = -0.0; + goto neg_fp_res; + } + if (unlikely(val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = js_int32(-val); + } else if (JS_TAG_IS_FLOAT64(tag)) { + d = -JS_VALUE_GET_FLOAT64(op1); + neg_fp_res: + sp[-1] = js_float64(d); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_inc): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_slow; + sp[-1] = js_int32(val + 1); + } else { + inc_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_dec): + { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_slow; + sp[-1] = js_int32(val - 1); + } else { + dec_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow(ctx, sp, opcode)) + goto exception; + } + } + BREAK; + CASE(OP_post_inc): + CASE(OP_post_dec): + sf->cur_pc = pc; + if (js_post_inc_slow(ctx, sp, opcode)) + goto exception; + sp++; + BREAK; + CASE(OP_inc_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MAX)) + goto inc_loc_slow; + var_buf[idx] = js_int32(val + 1); + } else { + inc_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = js_dup(op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_inc)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_dec_loc): + { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT(op1); + if (unlikely(val == INT32_MIN)) + goto dec_loc_slow; + var_buf[idx] = js_int32(val - 1); + } else { + dec_loc_slow: + sf->cur_pc = pc; + /* must duplicate otherwise the variable value may + be destroyed before JS code accesses it */ + op1 = js_dup(op1); + if (js_unary_arith_slow(ctx, &op1 + 1, OP_dec)) + goto exception; + set_value(ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE(OP_not): + { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) { + sp[-1] = js_int32(~JS_VALUE_GET_INT(op1)); + } else { + sf->cur_pc = pc; + if (js_not_slow(ctx, sp)) + goto exception; + } + } + BREAK; + + CASE(OP_shl): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT(op1); + v2 = JS_VALUE_GET_INT(op2) & 0x1f; + sp[-2] = js_int32(v1 << v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_shr): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + v2 &= 0x1f; + sp[-2] = js_uint32((uint32_t)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_shr_slow(ctx, sp)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_sar): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT(op2); + if (unlikely(v2 > 0x1f)) { + v2 &= 0x1f; + } + sp[-2] = js_int32((int)JS_VALUE_GET_INT(op1) >> v2); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_and): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) & JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_or): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) | JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + CASE(OP_xor): + { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { + sp[-2] = js_int32(JS_VALUE_GET_INT(op1) ^ JS_VALUE_GET_INT(op2)); + sp--; + } else { + sf->cur_pc = pc; + if (js_binary_logic_slow(ctx, sp, opcode)) + goto exception; + sp--; + } + } + BREAK; + + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE(opcode): \ + { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely(JS_VALUE_IS_BOTH_INT(op1, op2))) { \ + sp[-2] = js_bool(JS_VALUE_GET_INT(op1) binary_op JS_VALUE_GET_INT(op2)); \ + sp--; \ + } else { \ + sf->cur_pc = pc; \ + if (slow_call) \ + goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP(OP_lt, <, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_lte, <=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gt, >, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_gte, >=, js_relational_slow(ctx, sp, opcode)); + OP_CMP(OP_eq, ==, js_eq_slow(ctx, sp, 0)); + OP_CMP(OP_neq, !=, js_eq_slow(ctx, sp, 1)); + OP_CMP(OP_strict_eq, ==, js_strict_eq_slow(ctx, sp, 0)); + OP_CMP(OP_strict_neq, !=, js_strict_eq_slow(ctx, sp, 1)); + + CASE(OP_in): + sf->cur_pc = pc; + if (js_operator_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_private_in): + if (js_operator_private_in(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_instanceof): + sf->cur_pc = pc; + if (js_operator_instanceof(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_typeof): + { + JSValue op1; + JSAtom atom; + + op1 = sp[-1]; + atom = js_operator_typeof(ctx, op1); + JS_FreeValue(ctx, op1); + sp[-1] = JS_AtomToString(ctx, atom); + } + BREAK; + CASE(OP_delete): + sf->cur_pc = pc; + if (js_operator_delete(ctx, sp)) + goto exception; + sp--; + BREAK; + CASE(OP_delete_var): + { + JSAtom atom; + int ret; + + atom = get_u32(pc); + pc += 4; + + sf->cur_pc = pc; + ret = JS_DeleteProperty(ctx, ctx->global_obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + *sp++ = js_bool(ret); + } + BREAK; + + CASE(OP_to_object): + if (JS_VALUE_GET_TAG(sp[-1]) != JS_TAG_OBJECT) { + sf->cur_pc = pc; + ret_val = JS_ToObject(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + } + BREAK; + + CASE(OP_to_propkey): + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + + CASE(OP_to_propkey2): + /* must be tested first */ + if (unlikely(JS_IsUndefined(sp[-2]) || JS_IsNull(sp[-2]))) { + JS_ThrowTypeError(ctx, "value has no property"); + goto exception; + } + switch (JS_VALUE_GET_TAG(sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + case JS_TAG_SYMBOL: + break; + default: + sf->cur_pc = pc; + ret_val = JS_ToPropertyKey(ctx, sp[-1]); + if (JS_IsException(ret_val)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = ret_val; + break; + } + BREAK; + CASE(OP_with_get_var): + CASE(OP_with_put_var): + CASE(OP_with_delete_var): + CASE(OP_with_make_ref): + CASE(OP_with_get_ref): + CASE(OP_with_get_ref_undef): + { + JSAtom atom; + int32_t diff; + JSValue obj, val; + int ret, is_with; + atom = get_u32(pc); + diff = get_u32(pc + 4); + is_with = pc[8]; + pc += 9; + sf->cur_pc = pc; + + obj = sp[-1]; + ret = JS_HasProperty(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) { + if (is_with) { + ret = js_has_unscopable(ctx, obj, atom); + if (unlikely(ret < 0)) + goto exception; + if (ret) + goto no_with; + } + switch (opcode) { + case OP_with_get_var: + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + set_value(ctx, &sp[-1], val); + break; + case OP_with_put_var: + /* XXX: check if strict mode */ + ret = JS_SetPropertyInternal(ctx, obj, atom, sp[-2], + JS_PROP_THROW_STRICT); + JS_FreeValue(ctx, sp[-1]); + sp -= 2; + if (unlikely(ret < 0)) + goto exception; + break; + case OP_with_delete_var: + ret = JS_DeleteProperty(ctx, obj, atom, 0); + if (unlikely(ret < 0)) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = js_bool(ret); + break; + case OP_with_make_ref: + /* produce a pair object/propname on the stack */ + *sp++ = JS_AtomToValue(ctx, atom); + break; + case OP_with_get_ref: + /* produce a pair object/method on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + *sp++ = val; + break; + case OP_with_get_ref_undef: + /* produce a pair undefined/function on the stack */ + val = JS_GetProperty(ctx, obj, atom); + if (unlikely(JS_IsException(val))) + goto exception; + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_UNDEFINED; + *sp++ = val; + break; + } + pc += diff - 5; + } else { + no_with: + /* if not jumping, drop the object argument */ + JS_FreeValue(ctx, sp[-1]); + sp--; + } + } + BREAK; + + CASE(OP_await): + ret_val = js_int32(FUNC_RET_AWAIT); + goto done_generator; + CASE(OP_yield): + ret_val = js_int32(FUNC_RET_YIELD); + goto done_generator; + CASE(OP_yield_star): + CASE(OP_async_yield_star): + ret_val = js_int32(FUNC_RET_YIELD_STAR); + goto done_generator; + CASE(OP_return_async): + CASE(OP_initial_yield): + ret_val = JS_UNDEFINED; + goto done_generator; + + CASE(OP_nop): + BREAK; + CASE(OP_is_undefined_or_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED || + JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_undefined): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_UNDEFINED) { + goto set_true; + } else { + goto free_and_set_false; + } + CASE(OP_is_null): + if (JS_VALUE_GET_TAG(sp[-1]) == JS_TAG_NULL) { + goto set_true; + } else { + goto free_and_set_false; + } + /* XXX: could merge to a single opcode */ + CASE(OP_typeof_is_undefined): + /* different from OP_is_undefined because of isHTMLDDA */ + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_undefined) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + CASE(OP_typeof_is_function): + if (js_operator_typeof(ctx, sp[-1]) == JS_ATOM_function) { + goto free_and_set_true; + } else { + goto free_and_set_false; + } + free_and_set_true: + JS_FreeValue(ctx, sp[-1]); + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + JS_FreeValue(ctx, sp[-1]); + sp[-1] = JS_FALSE; + BREAK; + CASE(OP_invalid): + DEFAULT: + JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", + (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } + exception: + if (is_backtrace_needed(ctx, rt->current_exception)) { + /* add the backtrace information now (it is not done + before if the exception happens in a bytecode + operation */ + sf->cur_pc = pc; + build_backtrace(ctx, rt->current_exception, JS_UNDEFINED, NULL, 0, 0, 0); + } + if (!JS_IsUncatchableError(ctx, rt->current_exception)) { + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue(ctx, val); + if (JS_VALUE_GET_TAG(val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT(val); + if (pos == 0) { + /* enumerator: close it with a throw */ + JS_FreeValue(ctx, sp[-1]); /* drop the next method */ + sp--; + JS_IteratorClose(ctx, sp[-1], TRUE); + } else { + *sp++ = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + } + ret_val = JS_EXCEPTION; + /* the local variables are freed by the caller in the generator + case. Hence the label 'done' should never be reached in a + generator function. */ + if (b->func_kind != JS_FUNC_NORMAL) { + done_generator: + sf->cur_pc = pc; + sf->cur_sp = sp; + } else { + done: + if (unlikely(!list_empty(&sf->var_ref_list))) { + /* variable references reference the stack: must close them */ + close_var_refs(rt, sf); + } + /* free the local variables and stack */ + for(pval = local_buf; pval < sp; pval++) { + JS_FreeValue(ctx, *pval); + } + } + rt->current_stack_frame = sf->prev_frame; + return ret_val; +} + +JSValue JS_Call(JSContext *ctx, JSValue func_obj, JSValue this_obj, + int argc, JSValue *argv) +{ + return JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, argv, JS_CALL_FLAG_COPY_ARGV); +} + +static JSValue JS_CallFree(JSContext *ctx, JSValue func_obj, JSValue this_obj, + int argc, JSValue *argv) +{ + JSValue res = JS_CallInternal(ctx, func_obj, this_obj, JS_UNDEFINED, + argc, argv, JS_CALL_FLAG_COPY_ARGV); + JS_FreeValue(ctx, func_obj); + return res; +} + +/* warning: the refcount of the context is not incremented. Return + NULL in case of exception (case of revoked proxy only) */ +static JSContext *JS_GetFunctionRealm(JSContext *ctx, JSValue func_obj) +{ + JSObject *p; + JSContext *realm; + + if (JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT) + return ctx; + p = JS_VALUE_GET_OBJ(func_obj); + switch(p->class_id) { + case JS_CLASS_C_FUNCTION: + realm = p->u.cfunc.realm; + break; + case JS_CLASS_BYTECODE_FUNCTION: + case JS_CLASS_GENERATOR_FUNCTION: + case JS_CLASS_ASYNC_FUNCTION: + case JS_CLASS_ASYNC_GENERATOR_FUNCTION: + { + JSFunctionBytecode *b; + b = p->u.func.function_bytecode; + realm = b->realm; + } + break; + case JS_CLASS_PROXY: + { + JSProxyData *s = p->u.opaque; + if (!s) + return ctx; + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return NULL; + } else { + realm = JS_GetFunctionRealm(ctx, s->target); + } + } + break; + case JS_CLASS_BOUND_FUNCTION: + { + JSBoundFunction *bf = p->u.bound_function; + realm = JS_GetFunctionRealm(ctx, bf->func_obj); + } + break; + default: + realm = ctx; + break; + } + return realm; +} + +static JSValue js_create_from_ctor(JSContext *ctx, JSValue ctor, + int class_id) +{ + JSValue proto, obj; + JSContext *realm; + + if (JS_IsUndefined(ctor)) { + proto = js_dup(ctx->class_proto[class_id]); + } else { + proto = JS_GetProperty(ctx, ctor, JS_ATOM_prototype); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(proto)) { + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, ctor); + if (!realm) + return JS_EXCEPTION; + proto = js_dup(realm->class_proto[class_id]); + } + } + obj = JS_NewObjectProtoClass(ctx, proto, class_id); + JS_FreeValue(ctx, proto); + return obj; +} + +JSValue JS_NewObjectFromCtor(JSContext *ctx, JSValueConst ctor, + JSClassID class_id) +{ + return js_create_from_ctor(ctx, ctor, class_id); +} + +/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ +static JSValue JS_CallConstructorInternal(JSContext *ctx, + JSValue func_obj, + JSValue new_target, + int argc, JSValue *argv, int flags) +{ + JSObject *p; + JSFunctionBytecode *b; + + if (js_poll_interrupts(ctx)) + return JS_EXCEPTION; + flags |= JS_CALL_FLAG_CONSTRUCTOR; + if (unlikely(JS_VALUE_GET_TAG(func_obj) != JS_TAG_OBJECT)) + goto not_a_function; + p = JS_VALUE_GET_OBJ(func_obj); + if (unlikely(!p->is_constructor)) + return JS_ThrowTypeError(ctx, "not a constructor"); + if (unlikely(p->class_id != JS_CLASS_BYTECODE_FUNCTION)) { + JSClassCall *call_func; + call_func = ctx->rt->class_array[p->class_id].call; + if (!call_func) { + not_a_function: + return JS_ThrowTypeError(ctx, "not a function"); + } + return call_func(ctx, func_obj, new_target, argc, + argv, flags); + } + + b = p->u.func.function_bytecode; + if (b->is_derived_class_constructor) { + return JS_CallInternal(ctx, func_obj, JS_UNDEFINED, new_target, argc, argv, flags); + } else { + JSValue obj, ret; + /* legacy constructor behavior */ + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); + if (JS_IsException(obj)) + return JS_EXCEPTION; + ret = JS_CallInternal(ctx, func_obj, obj, new_target, argc, argv, flags); + if (JS_VALUE_GET_TAG(ret) == JS_TAG_OBJECT || + JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + return ret; + } else { + JS_FreeValue(ctx, ret); + return obj; + } + } +} + +JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj, + JSValue new_target, + int argc, JSValue *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, new_target, + argc, argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_CallConstructor(JSContext *ctx, JSValue func_obj, + int argc, JSValue *argv) +{ + return JS_CallConstructorInternal(ctx, func_obj, func_obj, + argc, argv, + JS_CALL_FLAG_COPY_ARGV); +} + +JSValue JS_Invoke(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValue *argv) +{ + JSValue func_obj; + func_obj = JS_GetProperty(ctx, this_val, atom); + if (JS_IsException(func_obj)) + return func_obj; + return JS_CallFree(ctx, func_obj, this_val, argc, argv); +} + +static JSValue JS_InvokeFree(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValue *argv) +{ + JSValue res = JS_Invoke(ctx, this_val, atom, argc, argv); + JS_FreeValue(ctx, this_val); + return res; +} + +/* JSAsyncFunctionState (used by generator and async functions) */ +static __exception int async_func_init(JSContext *ctx, JSAsyncFunctionState *s, + JSValue func_obj, JSValue this_obj, + int argc, JSValue *argv) +{ + JSObject *p; + JSFunctionBytecode *b; + JSStackFrame *sf; + int local_count, i, arg_buf_len, n; + + sf = &s->frame; + init_list_head(&sf->var_ref_list); + p = JS_VALUE_GET_OBJ(func_obj); + b = p->u.func.function_bytecode; + sf->is_strict_mode = b->is_strict_mode; + sf->cur_pc = b->byte_code_buf; + arg_buf_len = max_int(b->arg_count, argc); + local_count = arg_buf_len + b->var_count + b->stack_size; + sf->arg_buf = js_malloc(ctx, sizeof(JSValue) * max_int(local_count, 1)); + if (!sf->arg_buf) + return -1; + sf->cur_func = js_dup(func_obj); + s->this_val = js_dup(this_obj); + s->argc = argc; + sf->arg_count = arg_buf_len; + sf->var_buf = sf->arg_buf + arg_buf_len; + sf->cur_sp = sf->var_buf + b->var_count; + for(i = 0; i < argc; i++) + sf->arg_buf[i] = js_dup(argv[i]); + n = arg_buf_len + b->var_count; + for(i = argc; i < n; i++) + sf->arg_buf[i] = JS_UNDEFINED; + return 0; +} + +static void async_func_mark(JSRuntime *rt, JSAsyncFunctionState *s, + JS_MarkFunc *mark_func) +{ + JSStackFrame *sf; + JSValue *sp; + + sf = &s->frame; + JS_MarkValue(rt, sf->cur_func, mark_func); + JS_MarkValue(rt, s->this_val, mark_func); + if (sf->cur_sp) { + /* if the function is running, cur_sp is not known so we + cannot mark the stack. Marking the variables is not needed + because a running function cannot be part of a removable + cycle */ + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) + JS_MarkValue(rt, *sp, mark_func); + } +} + +static void async_func_free(JSRuntime *rt, JSAsyncFunctionState *s) +{ + JSStackFrame *sf; + JSValue *sp; + + sf = &s->frame; + + /* close the closure variables. */ + close_var_refs(rt, sf); + + if (sf->arg_buf) { + /* cannot free the function if it is running */ + assert(sf->cur_sp != NULL); + for(sp = sf->arg_buf; sp < sf->cur_sp; sp++) { + JS_FreeValueRT(rt, *sp); + } + js_free_rt(rt, sf->arg_buf); + } + JS_FreeValueRT(rt, sf->cur_func); + JS_FreeValueRT(rt, s->this_val); +} + +static JSValue async_func_resume(JSContext *ctx, JSAsyncFunctionState *s) +{ + JSValue func_obj; + + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + + /* the tag does not matter provided it is not an object */ + func_obj = JS_MKPTR(JS_TAG_INT, s); + return JS_CallInternal(ctx, func_obj, s->this_val, JS_UNDEFINED, + s->argc, s->frame.arg_buf, JS_CALL_FLAG_GENERATOR); +} + + +/* Generators */ + +typedef enum JSGeneratorStateEnum { + JS_GENERATOR_STATE_SUSPENDED_START, + JS_GENERATOR_STATE_SUSPENDED_YIELD, + JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_GENERATOR_STATE_EXECUTING, + JS_GENERATOR_STATE_COMPLETED, +} JSGeneratorStateEnum; + +typedef struct JSGeneratorData { + JSGeneratorStateEnum state; + JSAsyncFunctionState func_state; +} JSGeneratorData; + +static void free_generator_stack_rt(JSRuntime *rt, JSGeneratorData *s) +{ + if (s->state == JS_GENERATOR_STATE_COMPLETED) + return; + async_func_free(rt, &s->func_state); + s->state = JS_GENERATOR_STATE_COMPLETED; +} + +static void js_generator_finalizer(JSRuntime *rt, JSValue obj) +{ + JSGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_GENERATOR); + + if (s) { + free_generator_stack_rt(rt, s); + js_free_rt(rt, s); + } +} + +static void free_generator_stack(JSContext *ctx, JSGeneratorData *s) +{ + free_generator_stack_rt(ctx->rt, s); +} + +static void js_generator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSGeneratorData *s = p->u.generator_data; + + if (!s || s->state == JS_GENERATOR_STATE_COMPLETED) + return; + async_func_mark(rt, &s->func_state, mark_func); +} + +/* XXX: use enum */ +#define GEN_MAGIC_NEXT 0 +#define GEN_MAGIC_RETURN 1 +#define GEN_MAGIC_THROW 2 + +static JSValue js_generator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_GENERATOR); + JSStackFrame *sf; + JSValue ret, func_ret; + + *pdone = TRUE; + if (!s) + return JS_ThrowTypeError(ctx, "not a generator"); + sf = &s->func_state.frame; + switch(s->state) { + default: + case JS_GENERATOR_STATE_SUSPENDED_START: + if (magic == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + free_generator_stack(ctx, s); + goto done; + } + break; + case JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + case JS_GENERATOR_STATE_SUSPENDED_YIELD: + /* cur_sp[-1] was set to JS_UNDEFINED in the previous call */ + ret = js_dup(argv[0]); + if (magic == GEN_MAGIC_THROW && + s->state == JS_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, ret); + s->func_state.throw_flag = TRUE; + } else { + sf->cur_sp[-1] = ret; + sf->cur_sp[0] = js_int32(magic); + sf->cur_sp++; + exec_no_arg: + s->func_state.throw_flag = FALSE; + } + s->state = JS_GENERATOR_STATE_EXECUTING; + func_ret = async_func_resume(ctx, &s->func_state); + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD; + if (JS_IsException(func_ret)) { + /* finalize the execution in case of exception */ + free_generator_stack(ctx, s); + return func_ret; + } + if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + /* get the returned yield value at the top of the stack */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + if (JS_VALUE_GET_INT(func_ret) == FUNC_RET_YIELD_STAR) { + s->state = JS_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + /* return (value, done) object */ + *pdone = 2; + } else { + *pdone = FALSE; + } + } else { + /* end of iterator */ + ret = sf->cur_sp[-1]; + sf->cur_sp[-1] = JS_UNDEFINED; + JS_FreeValue(ctx, func_ret); + free_generator_stack(ctx, s); + } + break; + case JS_GENERATOR_STATE_COMPLETED: + done: + /* execution is finished */ + switch(magic) { + default: + case GEN_MAGIC_NEXT: + ret = JS_UNDEFINED; + break; + case GEN_MAGIC_RETURN: + ret = js_dup(argv[0]); + break; + case GEN_MAGIC_THROW: + ret = JS_Throw(ctx, js_dup(argv[0])); + break; + } + break; + case JS_GENERATOR_STATE_EXECUTING: + ret = JS_ThrowTypeError(ctx, "cannot invoke a running generator"); + break; + } + return ret; +} + +static JSValue js_generator_function_call(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, + int flags) +{ + JSValue obj, func_ret; + JSGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_GENERATOR_STATE_SUSPENDED_START; + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->state = JS_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' */ + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_GENERATOR); + if (JS_IsException(obj)) + goto fail; + JS_SetOpaqueInternal(obj, s); + return obj; + fail: + free_generator_stack_rt(ctx->rt, s); + js_free(ctx, s); + return JS_EXCEPTION; +} + +/* AsyncFunction */ + +static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s) +{ + if (s->is_active) { + async_func_free(rt, &s->func_state); + s->is_active = FALSE; + } +} + +static void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s) +{ + js_async_function_terminate(rt, s); + JS_FreeValueRT(rt, s->resolving_funcs[0]); + JS_FreeValueRT(rt, s->resolving_funcs[1]); + remove_gc_object(&s->header); + js_free_rt(rt, s); +} + +static void js_async_function_free(JSRuntime *rt, JSAsyncFunctionData *s) +{ + if (--s->header.ref_count == 0) { + js_async_function_free0(rt, s); + } +} + +static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionData *s = p->u.async_function_data; + if (s) { + js_async_function_free(rt, s); + } +} + +static void js_async_function_resolve_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSAsyncFunctionData *s = p->u.async_function_data; + if (s) { + mark_func(rt, &s->header); + } +} + +static int js_async_function_resolve_create(JSContext *ctx, + JSAsyncFunctionData *s, + JSValue *resolving_funcs) +{ + int i; + JSObject *p; + + for(i = 0; i < 2; i++) { + resolving_funcs[i] = + JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_ASYNC_FUNCTION_RESOLVE + i); + if (JS_IsException(resolving_funcs[i])) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + p = JS_VALUE_GET_OBJ(resolving_funcs[i]); + s->header.ref_count++; + p->u.async_function_data = s; + } + return 0; +} + +static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s) +{ + JSValue func_ret, ret2; + + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) { + JSValue error; + fail: + error = JS_GetException(ctx); + ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED, + 1, &error); + JS_FreeValue(ctx, error); + js_async_function_terminate(ctx->rt, s); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + } else { + JSValue value; + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + if (JS_IsUndefined(func_ret)) { + /* function returned */ + ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED, + 1, &value); + JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */ + JS_FreeValue(ctx, value); + js_async_function_terminate(ctx->rt, s); + } else { + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + /* await */ + JS_FreeValue(ctx, func_ret); /* not used */ + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, &value, 0); + JS_FreeValue(ctx, value); + if (JS_IsException(promise)) + goto fail; + if (js_async_function_resolve_create(ctx, s, resolving_funcs)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + resolving_funcs, + resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + } + } +} + +static JSValue js_async_function_resolve_call(JSContext *ctx, + JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, + int flags) +{ + JSObject *p = JS_VALUE_GET_OBJ(func_obj); + JSAsyncFunctionData *s = p->u.async_function_data; + BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE; + JSValue arg; + + if (argc > 0) + arg = argv[0]; + else + arg = JS_UNDEFINED; + s->func_state.throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, js_dup(arg)); + } else { + /* return value of await */ + s->func_state.frame.cur_sp[-1] = js_dup(arg); + } + js_async_function_resume(ctx, s); + return JS_UNDEFINED; +} + +static JSValue js_async_function_call(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, int flags) +{ + JSValue promise; + JSAsyncFunctionData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->header.ref_count = 1; + add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION); + s->is_active = FALSE; + s->resolving_funcs[0] = JS_UNDEFINED; + s->resolving_funcs[1] = JS_UNDEFINED; + + promise = JS_NewPromiseCapability(ctx, s->resolving_funcs); + if (JS_IsException(promise)) + goto fail; + + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + fail: + JS_FreeValue(ctx, promise); + js_async_function_free(ctx->rt, s); + return JS_EXCEPTION; + } + s->is_active = TRUE; + + js_async_function_resume(ctx, s); + + js_async_function_free(ctx->rt, s); + + return promise; +} + +/* AsyncGenerator */ + +typedef enum JSAsyncGeneratorStateEnum { + JS_ASYNC_GENERATOR_STATE_SUSPENDED_START, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD, + JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR, + JS_ASYNC_GENERATOR_STATE_EXECUTING, + JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN, + JS_ASYNC_GENERATOR_STATE_COMPLETED, +} JSAsyncGeneratorStateEnum; + +typedef struct JSAsyncGeneratorRequest { + struct list_head link; + /* completion */ + int completion_type; /* GEN_MAGIC_x */ + JSValue result; + /* promise capability */ + JSValue promise; + JSValue resolving_funcs[2]; +} JSAsyncGeneratorRequest; + +typedef struct JSAsyncGeneratorData { + JSObject *generator; /* back pointer to the object (const) */ + JSAsyncGeneratorStateEnum state; + JSAsyncFunctionState func_state; + struct list_head queue; /* list of JSAsyncGeneratorRequest.link */ +} JSAsyncGeneratorData; + +static void js_async_generator_free(JSRuntime *rt, + JSAsyncGeneratorData *s) +{ + struct list_head *el, *el1; + JSAsyncGeneratorRequest *req; + + list_for_each_safe(el, el1, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_FreeValueRT(rt, req->result); + JS_FreeValueRT(rt, req->promise); + JS_FreeValueRT(rt, req->resolving_funcs[0]); + JS_FreeValueRT(rt, req->resolving_funcs[1]); + js_free_rt(rt, req); + } + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && + s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { + async_func_free(rt, &s->func_state); + } + js_free_rt(rt, s); +} + +static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR); + + if (s) { + js_async_generator_free(rt, s); + } +} + +static void js_async_generator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR); + struct list_head *el; + JSAsyncGeneratorRequest *req; + if (s) { + list_for_each(el, &s->queue) { + req = list_entry(el, JSAsyncGeneratorRequest, link); + JS_MarkValue(rt, req->result, mark_func); + JS_MarkValue(rt, req->promise, mark_func); + JS_MarkValue(rt, req->resolving_funcs[0], mark_func); + JS_MarkValue(rt, req->resolving_funcs[1], mark_func); + } + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED && + s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) { + async_func_mark(rt, &s->func_state, mark_func); + } + } +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv, + int magic, JSValue *func_data); + +static int js_async_generator_resolve_function_create(JSContext *ctx, + JSValue generator, + JSValue *resolving_funcs, + BOOL is_resume_next) +{ + int i; + JSValue func; + + for(i = 0; i < 2; i++) { + func = JS_NewCFunctionData(ctx, js_async_generator_resolve_function, 1, + i + is_resume_next * 2, 1, &generator); + if (JS_IsException(func)) { + if (i == 1) + JS_FreeValue(ctx, resolving_funcs[0]); + return -1; + } + resolving_funcs[i] = func; + } + return 0; +} + +static int js_async_generator_await(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int i, res; + + promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, &value, 0); + if (JS_IsException(promise)) + goto fail; + + if (js_async_generator_resolve_function_create(ctx, JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs, FALSE)) { + JS_FreeValue(ctx, promise); + goto fail; + } + + /* Note: no need to create 'thrownawayCapability' as in + the spec */ + for(i = 0; i < 2; i++) + resolving_funcs1[i] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + resolving_funcs, + resolving_funcs1); + JS_FreeValue(ctx, promise); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (res) + goto fail; + return 0; + fail: + return -1; +} + +static void js_async_generator_resolve_or_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue result, + int is_reject) +{ + JSAsyncGeneratorRequest *next; + JSValue ret; + + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + list_del(&next->link); + ret = JS_Call(ctx, next->resolving_funcs[is_reject], JS_UNDEFINED, 1, + &result); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, next->result); + JS_FreeValue(ctx, next->promise); + JS_FreeValue(ctx, next->resolving_funcs[0]); + JS_FreeValue(ctx, next->resolving_funcs[1]); + js_free(ctx, next); +} + +static void js_async_generator_resolve(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value, + BOOL done) +{ + JSValue result; + result = js_create_iterator_result(ctx, js_dup(value), done); + /* XXX: better exception handling ? */ + js_async_generator_resolve_or_reject(ctx, s, result, 0); + JS_FreeValue(ctx, result); + } + +static void js_async_generator_reject(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue exception) +{ + js_async_generator_resolve_or_reject(ctx, s, exception, 1); +} + +static void js_async_generator_complete(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + async_func_free(ctx->rt, &s->func_state); + } +} + +static int js_async_generator_completed_return(JSContext *ctx, + JSAsyncGeneratorData *s, + JSValue value) +{ + JSValue promise, resolving_funcs[2], resolving_funcs1[2]; + int res; + + // Can fail looking up JS_ATOM_constructor when is_reject==0. + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &value, + /*is_reject*/0); + // A poisoned .constructor property is observable and the resulting + // exception should be delivered to the catch handler. + if (JS_IsException(promise)) { + JSValue err = JS_GetException(ctx); + promise = js_promise_resolve(ctx, ctx->promise_ctor, 1, &err, + /*is_reject*/1); + JS_FreeValue(ctx, err); + if (JS_IsException(promise)) + return -1; + } + if (js_async_generator_resolve_function_create(ctx, + JS_MKPTR(JS_TAG_OBJECT, s->generator), + resolving_funcs1, + TRUE)) { + JS_FreeValue(ctx, promise); + return -1; + } + resolving_funcs[0] = JS_UNDEFINED; + resolving_funcs[1] = JS_UNDEFINED; + res = perform_promise_then(ctx, promise, + resolving_funcs1, + resolving_funcs); + JS_FreeValue(ctx, resolving_funcs1[0]); + JS_FreeValue(ctx, resolving_funcs1[1]); + JS_FreeValue(ctx, promise); + return res; +} + +static void js_async_generator_resume_next(JSContext *ctx, + JSAsyncGeneratorData *s) +{ + JSAsyncGeneratorRequest *next; + JSValue func_ret, value; + + for(;;) { + if (list_empty(&s->queue)) + break; + next = list_entry(s->queue.next, JSAsyncGeneratorRequest, link); + switch(s->state) { + case JS_ASYNC_GENERATOR_STATE_EXECUTING: + /* only happens when restarting execution after await() */ + goto resume_exec; + case JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN: + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_START: + if (next->completion_type == GEN_MAGIC_NEXT) { + goto exec_no_arg; + } else { + js_async_generator_complete(ctx, s); + } + break; + case JS_ASYNC_GENERATOR_STATE_COMPLETED: + if (next->completion_type == GEN_MAGIC_NEXT) { + js_async_generator_resolve(ctx, s, JS_UNDEFINED, TRUE); + } else if (next->completion_type == GEN_MAGIC_RETURN) { + s->state = JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN; + js_async_generator_completed_return(ctx, s, next->result); + } else { + js_async_generator_reject(ctx, s, next->result); + } + goto done; + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD: + case JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR: + value = js_dup(next->result); + if (next->completion_type == GEN_MAGIC_THROW && + s->state == JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD) { + JS_Throw(ctx, value); + s->func_state.throw_flag = TRUE; + } else { + /* 'yield' returns a value. 'yield *' also returns a value + in case the 'throw' method is called */ + s->func_state.frame.cur_sp[-1] = value; + s->func_state.frame.cur_sp[0] = + js_int32(next->completion_type); + s->func_state.frame.cur_sp++; + exec_no_arg: + s->func_state.throw_flag = FALSE; + } + s->state = JS_ASYNC_GENERATOR_STATE_EXECUTING; + resume_exec: + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) { + value = JS_GetException(ctx); + js_async_generator_complete(ctx, s); + js_async_generator_reject(ctx, s, value); + JS_FreeValue(ctx, value); + } else if (JS_VALUE_GET_TAG(func_ret) == JS_TAG_INT) { + int func_ret_code, ret; + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + func_ret_code = JS_VALUE_GET_INT(func_ret); + switch(func_ret_code) { + case FUNC_RET_YIELD: + case FUNC_RET_YIELD_STAR: + if (func_ret_code == FUNC_RET_YIELD_STAR) + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD_STAR; + else + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_YIELD; + js_async_generator_resolve(ctx, s, value, FALSE); + JS_FreeValue(ctx, value); + break; + case FUNC_RET_AWAIT: + ret = js_async_generator_await(ctx, s, value); + JS_FreeValue(ctx, value); + if (ret < 0) { + /* exception: throw it */ + s->func_state.throw_flag = TRUE; + goto resume_exec; + } + goto done; + default: + abort(); + } + } else { + assert(JS_IsUndefined(func_ret)); + /* end of function */ + value = s->func_state.frame.cur_sp[-1]; + s->func_state.frame.cur_sp[-1] = JS_UNDEFINED; + js_async_generator_complete(ctx, s); + js_async_generator_resolve(ctx, s, value, TRUE); + JS_FreeValue(ctx, value); + } + break; + default: + abort(); + } + } + done: ; +} + +static JSValue js_async_generator_resolve_function(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv, + int magic, JSValue *func_data) +{ + BOOL is_reject = magic & 1; + JSAsyncGeneratorData *s = JS_GetOpaque(func_data[0], JS_CLASS_ASYNC_GENERATOR); + JSValue arg = argv[0]; + + /* XXX: what if s == NULL */ + + if (magic >= 2) { + /* resume next case in AWAITING_RETURN state */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN || + s->state == JS_ASYNC_GENERATOR_STATE_COMPLETED); + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + if (is_reject) { + js_async_generator_reject(ctx, s, arg); + } else { + js_async_generator_resolve(ctx, s, arg, TRUE); + } + } else { + /* restart function execution after await() */ + assert(s->state == JS_ASYNC_GENERATOR_STATE_EXECUTING); + s->func_state.throw_flag = is_reject; + if (is_reject) { + JS_Throw(ctx, js_dup(arg)); + } else { + /* return value of await */ + s->func_state.frame.cur_sp[-1] = js_dup(arg); + } + js_async_generator_resume_next(ctx, s); + } + return JS_UNDEFINED; +} + +/* magic = GEN_MAGIC_x */ +static JSValue js_async_generator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int magic) +{ + JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR); + JSValue promise, resolving_funcs[2]; + JSAsyncGeneratorRequest *req; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + if (!s) { + JSValue err, res2; + JS_ThrowTypeError(ctx, "not an AsyncGenerator object"); + err = JS_GetException(ctx); + res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, &err); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; + } + req = js_mallocz(ctx, sizeof(*req)); + if (!req) + goto fail; + req->completion_type = magic; + req->result = js_dup(argv[0]); + req->promise = js_dup(promise); + req->resolving_funcs[0] = resolving_funcs[0]; + req->resolving_funcs[1] = resolving_funcs[1]; + list_add_tail(&req->link, &s->queue); + if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) { + js_async_generator_resume_next(ctx, s); + } + return promise; + fail: + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; +} + +static JSValue js_async_generator_function_call(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, + int flags) +{ + JSValue obj, func_ret; + JSAsyncGeneratorData *s; + + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + return JS_EXCEPTION; + s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START; + init_list_head(&s->queue); + if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) { + s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED; + goto fail; + } + + /* execute the function up to 'OP_initial_yield' (no yield nor + await are possible) */ + func_ret = async_func_resume(ctx, &s->func_state); + if (JS_IsException(func_ret)) + goto fail; + JS_FreeValue(ctx, func_ret); + + obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR); + if (JS_IsException(obj)) + goto fail; + s->generator = JS_VALUE_GET_OBJ(obj); + JS_SetOpaqueInternal(obj, s); + return obj; + fail: + js_async_generator_free(ctx->rt, s); + return JS_EXCEPTION; +} + +/* JS parser */ + +enum { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + 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_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DOUBLE_QUESTION_MARK_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_ARROW, + TOK_ELLIPSIS, + TOK_DOUBLE_QUESTION_MARK, + TOK_QUESTION_MARK_DOT, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_VAR, + TOK_THIS, + TOK_DELETE, + TOK_VOID, + TOK_TYPEOF, + TOK_NEW, + TOK_IN, + TOK_INSTANCEOF, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_SWITCH, + TOK_CASE, + TOK_DEFAULT, + TOK_THROW, + TOK_TRY, + TOK_CATCH, + TOK_FINALLY, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PACKAGE, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; + +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT + +/* unicode code points */ +#define CP_NBSP 0x00a0 +#define CP_BOM 0xfeff + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + +typedef struct BlockEnv { + struct BlockEnv *prev; + JSAtom label_name; /* JS_ATOM_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + uint8_t has_iterator : 1; + uint8_t is_regular_stmt : 1; // i.e. not a loop statement +} BlockEnv; + +typedef struct JSGlobalVar { + int cpool_idx; /* if >= 0, index in the constant pool for hoisted + function defintion*/ + uint8_t force_init : 1; /* force initialization to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int scope_level; /* scope of definition */ + JSAtom var_name; /* variable name */ +} JSGlobalVar; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct SourceLocSlot { + uint32_t pc; + int line_num; + int col_num; +} SourceLocSlot; + +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_PARSE_FUNC_CLASS_CONSTRUCTOR, + JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR, +} JSParseFunctionEnum; + +typedef enum JSParseExportEnum { + JS_PARSE_EXPORT_NONE, + JS_PARSE_EXPORT_NAMED, + JS_PARSE_EXPORT_DEFAULT, +} JSParseExportEnum; + +typedef struct JSFunctionDef { + JSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + BOOL is_eval; /* TRUE if eval code */ + int eval_type; /* only valid if is_eval = TRUE */ + BOOL is_global_var; /* TRUE if variables are not defined locally: + eval global, eval module or non strict eval */ + BOOL is_func_expr; /* TRUE if function expression */ + BOOL has_home_object; /* TRUE if the home object is available */ + BOOL has_prototype; /* true if a prototype field is necessary */ + BOOL has_simple_parameter_list; + BOOL has_parameter_expressions; /* if true, an argument scope is created */ + BOOL has_use_strict; /* to reject directive in special cases */ + BOOL has_eval_call; /* true if the function contains a call to eval() */ + BOOL has_arguments_binding; /* true if the 'arguments' binding is + available in the function */ + BOOL has_this_binding; /* true if the 'this' and new.target binding are + available in the function */ + BOOL new_target_allowed; /* true if the 'new.target' does not + throw a syntax error */ + BOOL super_call_allowed; /* true if super() is allowed */ + BOOL super_allowed; /* true if super. or super[] is allowed */ + BOOL arguments_allowed; /* true if the 'arguments' identifier is allowed */ + BOOL is_derived_class_constructor; + BOOL in_function_body; + BOOL backtrace_barrier; + JSFunctionKindEnum func_kind : 8; + JSParseFunctionEnum func_type : 7; + uint8_t is_strict_mode : 1; + JSAtom func_name; /* JS_ATOM_NULL if no name */ + + JSVarDef *vars; + uint32_t *vars_htab; // indexes into vars[] + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int arguments_var_idx; /* -1 if none */ + int arguments_arg_idx; /* argument variable definition in argument scope, + -1 if none */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int eval_ret_idx; /* variable containing the return value of the eval, -1 if none */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int new_target_var_idx; /* variable containg the 'new.target' value, -1 if none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' value, -1 if none */ + int home_object_var_idx; + BOOL need_home_object; + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int global_var_count; + int global_var_size; + JSGlobalVar *global_vars; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + JSValue *cpool; + int cpool_count; + int cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + JSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + SourceLocSlot *source_loc_slots; + int source_loc_size; + int source_loc_count; + int line_number_last; + int line_number_last_pc; + int col_number_last; + + /* pc2line table */ + JSAtom filename; + int line_num; + int col_num; + DynBuf pc2line; + + char *source; /* raw source, utf-8 encoded */ + int source_len; + + JSModuleDef *module; /* != NULL when parsing a module */ + BOOL has_await; /* TRUE if await is used (used in module eval) */ + JSInlineCache *ic; /* inline cache for field op */ +} JSFunctionDef; + +typedef struct JSToken { + int val; + int line_num; /* line number of token start */ + int col_num; /* column number of token start */ + const uint8_t *ptr; + union { + struct { + JSValue str; + int sep; + } str; + struct { + JSValue val; + } num; + struct { + JSAtom atom; + BOOL has_escape; + BOOL is_reserved; + } ident; + struct { + JSValue body; + JSValue flags; + } regexp; + } u; +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + int last_line_num; /* line number of last token */ + int last_col_num; /* column number of last token */ + int line_num; /* line number of current offset */ + int col_num; /* column number of current offset */ + const char *filename; + JSToken token; + BOOL got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_start; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *eol; // most recently seen end-of-line character + const uint8_t *mark; // first token character, invariant: eol < mark + + /* current function code */ + JSFunctionDef *cur_func; + BOOL is_module; /* parsing a module */ + BOOL allow_html_comments; +} JSParseState; + +typedef struct JSOpCode { +#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_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { +#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 "quickjs-opcode.h" +#undef DEF +#undef FMT +}; + +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? \ + (op) + (OP_TEMP_END - OP_TEMP_START) : (op)] + +static __exception int next_token(JSParseState *s); + +static void free_token(JSParseState *s, JSToken *token) +{ + switch(token->val) { + case TOK_NUMBER: + JS_FreeValue(s->ctx, token->u.num.val); + break; + case TOK_STRING: + case TOK_TEMPLATE: + JS_FreeValue(s->ctx, token->u.str.str); + break; + case TOK_REGEXP: + JS_FreeValue(s->ctx, token->u.regexp.body); + JS_FreeValue(s->ctx, token->u.regexp.flags); + break; + case TOK_IDENT: + case TOK_PRIVATE_NAME: + JS_FreeAtom(s->ctx, token->u.ident.atom); + break; + default: + if (token->val >= TOK_FIRST_KEYWORD && + token->val <= TOK_LAST_KEYWORD) { + JS_FreeAtom(s->ctx, token->u.ident.atom); + } + break; + } +} + +static void __attribute((unused)) dump_token(JSParseState *s, + const JSToken *token) +{ + printf("%d:%d ", token->line_num, token->col_num); + switch(token->val) { + case TOK_NUMBER: + { + double d; + JS_ToFloat64(s->ctx, &d, token->u.num.val); /* no exception possible */ + printf("number: %.14g\n", d); + } + break; + case TOK_IDENT: + dump_atom: + { + char buf[ATOM_GET_STR_BUF_SIZE]; + printf("ident: '%s'\n", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), token->u.ident.atom)); + } + break; + case TOK_STRING: + { + const char *str; + /* XXX: quote the string */ + str = JS_ToCString(s->ctx, token->u.str.str); + printf("string: '%s'\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_TEMPLATE: + { + const char *str; + str = JS_ToCString(s->ctx, token->u.str.str); + printf("template: `%s`\n", str); + JS_FreeCString(s->ctx, str); + } + break; + case TOK_REGEXP: + { + const char *str, *str2; + str = JS_ToCString(s->ctx, token->u.regexp.body); + str2 = JS_ToCString(s->ctx, token->u.regexp.flags); + printf("regexp: '%s' '%s'\n", str, str2); + JS_FreeCString(s->ctx, str); + JS_FreeCString(s->ctx, str2); + } + break; + case TOK_EOF: + printf("eof\n"); + break; + default: + if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { + goto dump_atom; + } else if (s->token.val >= 256) { + printf("token: %d\n", token->val); + } else { + printf("token: '%c'\n", token->val); + } + break; + } +} + +int __attribute__((format(printf, 2, 3))) js_parse_error(JSParseState *s, const char *fmt, ...) +{ + JSContext *ctx = s->ctx; + va_list ap; + int backtrace_flags; + + va_start(ap, fmt); + JS_ThrowError2(ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); + va_end(ap); + backtrace_flags = 0; + if (s->cur_func && s->cur_func->backtrace_barrier) + backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + build_backtrace(ctx, ctx->rt->current_exception, JS_UNDEFINED, s->filename, + s->line_num, s->col_num, backtrace_flags); + return -1; +} + +static int js_parse_expect(JSParseState *s, int tok) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + + if (s->token.val == tok) + return next_token(s); + + switch(s->token.val) { + case TOK_EOF: + return js_parse_error(s, "Unexpected end of input"); + case TOK_NUMBER: + return js_parse_error(s, "Unexpected number"); + case TOK_STRING: + return js_parse_error(s, "Unexpected string"); + case TOK_TEMPLATE: + return js_parse_error(s, "Unexpected string template"); + case TOK_REGEXP: + return js_parse_error(s, "Unexpected regexp"); + case TOK_IDENT: + return js_parse_error(s, "Unexpected identifier '%s'", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), + s->token.u.ident.atom)); + case TOK_ERROR: + return js_parse_error(s, "Invalid or unexpected token"); + default: + return js_parse_error(s, "Unexpected token '%.*s'", + (int)(s->buf_ptr - s->token.ptr), + (const char *)s->token.ptr); + } +} + +static int 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 0; + } + return js_parse_error(s, "expecting '%c'", ';'); + } + return next_token(s); +} + +static int js_parse_error_reserved_identifier(JSParseState *s) +{ + char buf1[ATOM_GET_STR_BUF_SIZE]; + return js_parse_error(s, "'%s' is a reserved identifier", + JS_AtomGetStr(s->ctx, buf1, sizeof(buf1), + s->token.u.ident.atom)); +} + +static __exception int js_parse_template_part(JSParseState *s, + const uint8_t *p) +{ + const uint8_t *p_next; + uint32_t c; + StringBuffer b_s, *b = &b_s; + + /* p points to the first byte of the template part */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + if (c == '`') { + /* template end part */ + break; + } + if (c == '$' && *p == '{') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + if (p >= s->buf_end) + goto unexpected_eof; + c = *p++; + } + /* newline sequences are normalized as single '\n' bytes */ + if (c == '\r') { + if (*p == '\n') + p++; + c = '\n'; + } + if (c == '\n') { + s->line_num++; + s->eol = &p[-1]; + s->mark = p; + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + s->token.val = TOK_TEMPLATE; + s->token.u.str.sep = c; + s->token.u.str.str = string_buffer_end(b); + s->buf_ptr = p; + return 0; + + unexpected_eof: + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static __exception int js_parse_string(JSParseState *s, int sep, + BOOL do_throw, const uint8_t *p, + JSToken *token, const uint8_t **pp) +{ + const uint8_t *p_next; + int ret; + uint32_t c; + StringBuffer b_s, *b = &b_s; + + /* string */ + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + for(;;) { + if (p >= s->buf_end) + goto invalid_char; + c = *p; + if (c < 0x20) { + if (sep == '`') { + if (c == '\r') { + if (p[1] == '\n') + p++; + c = '\n'; + } + /* do not update s->line_num */ + } else if (c == '\n' || c == '\r') + goto invalid_char; + } + p++; + if (c == sep) + break; + if (c == '$' && *p == '{' && sep == '`') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + c = *p; + switch(c) { + case '\0': + if (p >= s->buf_end) { + if (sep != '`') + goto invalid_char; + if (do_throw) + js_parse_error(s, "Unexpected end of input"); + goto fail; + } + p++; + break; + case '\'': + case '\"': + case '\\': + p++; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + /* ignore escaped newline sequence */ + p++; + if (sep != '`') { + s->line_num++; + s->eol = &p[-1]; + s->mark = p; + } + continue; + default: + if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { + /* accept isolated \0 */ + p++; + c = '\0'; + } else + if ((c >= '0' && c <= '9') + && (s->cur_func->is_strict_mode || sep == '`')) { + if (do_throw) { + js_parse_error(s, "%s are not allowed in %s", + (c >= '8') ? "\\8 and \\9" : "Octal escape sequences", + (sep == '`') ? "template strings" : "strict mode"); + } + goto fail; + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) { + goto invalid_utf8; + } + p = p_next; + /* LS or PS are skipped */ + if (c == CP_LS || c == CP_PS) + continue; + } else { + ret = lre_parse_escape(&p, TRUE); + if (ret == -1) { + if (do_throw) { + js_parse_error(s, "Invalid %s escape sequence", + c == 'u' ? "Unicode" : "hexadecimal"); + } + goto fail; + } else if (ret < 0) { + /* ignore the '\' (could output a warning) */ + p++; + } else { + c = ret; + } + } + break; + } + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) + goto invalid_utf8; + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + token->val = TOK_STRING; + token->u.str.sep = c; + token->u.str.str = string_buffer_end(b); + *pp = p; + return 0; + + invalid_utf8: + if (do_throw) + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + invalid_char: + if (do_throw) + js_parse_error(s, "unexpected end of string"); + fail: + string_buffer_free(b); + return -1; +} + +static inline BOOL token_is_pseudo_keyword(JSParseState *s, JSAtom atom) { + return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom && + !s->token.u.ident.has_escape; +} + +static __exception int js_parse_regexp(JSParseState *s) +{ + const uint8_t *p, *p_next; + BOOL in_class; + StringBuffer b_s, *b = &b_s; + StringBuffer b2_s, *b2 = &b2_s; + uint32_t c; + + p = s->buf_ptr; + p++; + in_class = FALSE; + if (string_buffer_init(s->ctx, b, 32)) + return -1; + if (string_buffer_init(s->ctx, b2, 1)) + goto fail; + for(;;) { + if (p >= s->buf_end) { + eof_error: + js_parse_error(s, "unexpected end of regexp"); + goto fail; + } + c = *p++; + if (c == '\n' || c == '\r') { + goto eol_error; + } else if (c == '/') { + if (!in_class) + break; + } else if (c == '[') { + in_class = TRUE; + } else if (c == ']') { + /* XXX: incorrect as the first character in a class */ + in_class = FALSE; + } else if (c == '\\') { + if (string_buffer_putc8(b, c)) + goto fail; + c = *p++; + if (c == '\n' || c == '\r') + goto eol_error; + else if (c == '\0' && p >= s->buf_end) + goto eof_error; + else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + goto invalid_utf8; + } + p = p_next; + if (c == CP_LS || c == CP_PS) + goto eol_error; + } + } else if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + invalid_utf8: + js_parse_error(s, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + eol_error: + js_parse_error(s, "unexpected line terminator in regexp"); + goto fail; + } + } + if (string_buffer_putc(b, c)) + goto fail; + } + + /* flags */ + for(;;) { + c = utf8_decode(p, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ + if (!lre_js_is_ident_next(c)) + break; + if (string_buffer_putc(b2, c)) + goto fail; + p = p_next; + } + + s->token.val = TOK_REGEXP; + s->token.u.regexp.body = string_buffer_end(b); + s->token.u.regexp.flags = string_buffer_end(b2); + s->buf_ptr = p; + return 0; + fail: + string_buffer_free(b); + string_buffer_free(b2); + return -1; +} + +static __exception int ident_realloc(JSContext *ctx, char **pbuf, size_t *psize, + char *static_buf) +{ + char *buf, *new_buf; + size_t size, new_size; + + buf = *pbuf; + size = *psize; + if (size >= (SIZE_MAX / 3) * 2) + new_size = SIZE_MAX; + else + new_size = size + (size >> 1); + if (buf == static_buf) { + new_buf = js_malloc(ctx, new_size); + if (!new_buf) + return -1; + memcpy(new_buf, buf, size); + } else { + new_buf = js_realloc(ctx, buf, new_size); + if (!new_buf) + return -1; + } + *pbuf = new_buf; + *psize = new_size; + return 0; +} + +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident(JSParseState *s) +{ + if (s->token.u.ident.atom <= JS_ATOM_LAST_KEYWORD || + (s->token.u.ident.atom <= JS_ATOM_LAST_STRICT_KEYWORD && + s->cur_func->is_strict_mode) || + (s->token.u.ident.atom == JS_ATOM_yield && + ((s->cur_func->func_kind & JS_FUNC_GENERATOR) || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + (s->cur_func->parent->func_kind & JS_FUNC_GENERATOR)))) || + (s->token.u.ident.atom == JS_ATOM_await && + (s->is_module || + (s->cur_func->func_kind & JS_FUNC_ASYNC) || + s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT || + (s->cur_func->func_type == JS_PARSE_FUNC_ARROW && + !s->cur_func->in_function_body && s->cur_func->parent && + ((s->cur_func->parent->func_kind & JS_FUNC_ASYNC) || + s->cur_func->parent->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))))) { + if (s->token.u.ident.has_escape) { + s->token.u.ident.is_reserved = TRUE; + s->token.val = TOK_IDENT; + } else { + /* The keywords atoms are pre allocated */ + s->token.val = s->token.u.ident.atom - 1 + TOK_FIRST_KEYWORD; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token(JSParseState *s) +{ + if (s->token.val == TOK_IDENT || + (s->token.val >= TOK_FIRST_KEYWORD && + s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = FALSE; + update_token_ident(s); + } +} + +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom parse_ident(JSParseState *s, const uint8_t **pp, + BOOL *pident_has_escape, int c, BOOL is_private) +{ + const uint8_t *p, *p_next; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom = JS_ATOM_NULL; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + if (is_private) + buf[ident_pos++] = '#'; + for(;;) { + if (c < 0x80) { + buf[ident_pos++] = c; + } else { + ident_pos += utf8_encode((uint8_t*)buf + ident_pos, c); + } + c = *p; + p_next = p + 1; + if (c == '\\' && *p_next == 'u') { + c = lre_parse_escape(&p_next, TRUE); + *pident_has_escape = TRUE; + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + /* no need to test for invalid UTF-8, 0xFFFD is not ident_next */ + } + if (!lre_js_is_ident_next(c)) + break; + p = p_next; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) + goto done; + } + } + /* buf is pure ASCII or UTF-8 encoded */ + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + + +static __exception int next_token(JSParseState *s) +{ + const uint8_t *p, *p_next; + int c; + BOOL ident_has_escape; + JSAtom atom; + int flags, radix; + + if (js_check_stack_overflow(s->ctx->rt, 1000)) { + JS_ThrowStackOverflow(s->ctx); + return -1; + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->got_lf = FALSE; + s->last_line_num = s->token.line_num; + s->last_col_num = s->token.col_num; + redo: + s->token.line_num = s->line_num; + s->token.col_num = s->col_num; + s->token.ptr = p; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': + if (js_parse_template_part(s, p + 1)) + goto fail; + p = s->buf_ptr; + break; + case '\'': + case '\"': + if (js_parse_string(s, c, TRUE, p + 1, &s->token, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + p++; + line_terminator: + s->eol = &p[-1]; + s->mark = p; + s->got_lf = TRUE; + s->line_num++; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + s->mark = ++p; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for(;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error(s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n') { + s->line_num++; + s->got_lf = TRUE; /* considered as LF for ASI */ + s->eol = p++; + s->mark = p; + } else if (*p == '\r') { + s->got_lf = TRUE; /* considered as LF for ASI */ + p++; + } else if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* ignore invalid UTF-8 in comments */ + if (c == CP_LS || c == CP_PS) { + s->got_lf = TRUE; /* considered as LF for ASI */ + } + } else { + p++; + } + } + s->mark = p; + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + skip_line_comment: + for(;;) { + if (*p == '\0' && p >= s->buf_end) + break; + if (*p == '\r' || *p == '\n') + break; + if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* ignore invalid UTF-8 in comments */ + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } + } else { + p++; + } + } + s->mark = p; + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case '\\': + if (p[1] == 'u') { + const uint8_t *p1 = p + 1; + int c1 = lre_parse_escape(&p1, TRUE); + if (c1 >= 0 && lre_js_is_ident_first(c1)) { + c = c1; + p = p1; + ident_has_escape = TRUE; + goto has_ident; + } else { + /* XXX: syntax error? */ + } + } + goto def_token; + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case '$': + /* identifier */ + s->mark = p; + p++; + ident_has_escape = FALSE; + has_ident: + atom = parse_ident(s, &p, &ident_has_escape, c, FALSE); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = ident_has_escape; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + update_token_ident(s); + break; + case '#': + /* private name */ + { + p++; + c = *p; + p_next = p + 1; + if (c == '\\' && *p_next == 'u') { + c = lre_parse_escape(&p_next, TRUE); + } else if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) + goto invalid_utf8; + } + if (!lre_js_is_ident_first(c)) { + js_parse_error(s, "invalid first character of private name"); + goto fail; + } + p = p_next; + ident_has_escape = FALSE; /* not used */ + atom = parse_ident(s, &p, &ident_has_escape, c, TRUE); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.val = TOK_PRIVATE_NAME; + } + break; + case '.': + if (p[1] == '.' && p[2] == '.') { + p += 3; + s->token.val = TOK_ELLIPSIS; + break; + } + if (p[1] >= '0' && p[1] <= '9') { + flags = ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_FLOAT; + radix = 10; + goto parse_number; + } + goto def_token; + case '0': + if (is_digit(p[1])) { /* handle legacy octal */ + if (s->cur_func->is_strict_mode) { + js_parse_error(s, "Octal literals are not allowed in strict mode"); + goto fail; + } + /* Legacy octal: no separators, no suffix, no floats, + base 8 unless non octal digits are detected */ + flags = 0; + radix = 8; + while (is_digit(*p)) { + if (*p >= '8' && *p <= '9') + radix = 10; + p++; + } + p = s->token.ptr; + goto parse_number; + } + if (p[1] == '_') { + js_parse_error(s, "Numeric separator can not be used after leading 0"); + goto fail; + } + flags = ATOD_ACCEPT_HEX_PREFIX | ATOD_ACCEPT_BIN_OCT | + ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + radix = 10; + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + { + JSValue ret; + const char *p1; + + flags = ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + radix = 10; + parse_number: + p1 = (const char *)p; + ret = js_atof(s->ctx, p1, s->buf_end - p, &p1, radix, flags); + p = (const uint8_t *)p1; + if (JS_IsException(ret)) + goto fail; + /* reject `10instanceof Number` */ + if (JS_VALUE_IS_NAN(ret) || + lre_js_is_ident_next(utf8_decode(p, &p_next))) { + JS_FreeValue(s->ctx, ret); + js_parse_error(s, "invalid number literal"); + goto fail; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } + 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] == '-') { + if (s->allow_html_comments && p[2] == '>' && + (s->got_lf || s->last_ptr == s->buf_start)) { + /* Annex B: `-->` at beginning of line is an html comment end. + It extends to the end of the line. + */ + goto skip_line_comment; + } + 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 if (s->allow_html_comments && + p[1] == '!' && p[2] == '-' && p[3] == '-') { + /* Annex B: handle `<!--` single line html comments */ + goto skip_line_comment; + } 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 if (p[1] == '>') { + p += 2; + s->token.val = TOK_ARROW; + } 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] == '&') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_LAND_ASSIGN; + } else { + 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] == '|') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_LOR_ASSIGN; + } else { + p += 2; + s->token.val = TOK_LOR; + } + } else { + goto def_token; + } + break; + case '?': + if (p[1] == '?') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; + } else { + p += 2; + s->token.val = TOK_DOUBLE_QUESTION_MARK; + } + } else if (p[1] == '.' && !(p[2] >= '0' && p[2] <= '9')) { + p += 2; + s->token.val = TOK_QUESTION_MARK_DOT; + } else { + goto def_token; + } + break; + default: + if (c >= 0x80) { /* non-ASCII code-point */ + c = utf8_decode(p, &p_next); + if (p_next == p + 1) + goto invalid_utf8; + p = p_next; + switch(c) { + case CP_PS: + case CP_LS: + /* XXX: should avoid incrementing line_number, but + needed to handle HTML comments */ + goto line_terminator; + default: + if (lre_is_space(c)) { + s->mark = p; + goto redo; + } else if (lre_js_is_ident_first(c)) { + ident_has_escape = FALSE; + goto has_ident; + } else { + js_parse_error(s, "unexpected character"); + goto fail; + } + } + } + def_token: + s->token.val = c; + p++; + break; + } + s->token.col_num = max_int(1, s->mark - s->eol); + s->buf_ptr = p; + + // dump_token(s, &s->token); + return 0; + + invalid_utf8: + js_parse_error(s, "invalid UTF-8 sequence"); + fail: + s->token.val = TOK_ERROR; + return -1; +} + +static int json_parse_error(JSParseState *s, const uint8_t *curp, const char *msg) +{ + const uint8_t *p, *line_start; + int position = curp - s->buf_start; + int line = 1; + for (line_start = p = s->buf_start; p < curp; p++) { + /* column count does not account for TABs nor wide characters */ + if (*p == '\r' || *p == '\n') { + p += 1 + (p[0] == '\r' && p[1] == '\n'); + line++; + line_start = p; + } + } + return js_parse_error(s, "%s in JSON at position %d (line %d column %d)", + msg, position, line, (int)(p - line_start) + 1); +} + +static int json_parse_string(JSParseState *s, const uint8_t **pp) +{ + const uint8_t *p, *p_next; + int i; + uint32_t c; + StringBuffer b_s, *b = &b_s; + + if (string_buffer_init(s->ctx, b, 32)) + goto fail; + + p = *pp; + for(;;) { + if (p >= s->buf_end) { + goto end_of_input; + } + c = *p++; + if (c == '"') + break; + if (c < 0x20) { + json_parse_error(s, p - 1, "Bad control character in string literal"); + goto fail; + } + if (c == '\\') { + 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 '\"': break; + case '\\': break; + case '/': break; /* for v8 compatibility */ + case 'u': + c = 0; + for(i = 0; i < 4; i++) { + int h = from_hex(*p++); + if (h < 0) { + json_parse_error(s, p - 1, "Bad Unicode escape"); + goto fail; + } + c = (c << 4) | h; + } + break; + default: + if (p > s->buf_end) + goto end_of_input; + json_parse_error(s, p - 1, "Bad escaped character"); + goto fail; + } + } else + if (c >= 0x80) { + c = utf8_decode(p - 1, &p_next); + if (p_next == p) { + json_parse_error(s, p - 1, "Bad UTF-8 sequence"); + goto fail; + } + p = p_next; + } + if (string_buffer_putc(b, c)) + goto fail; + } + s->token.val = TOK_STRING; + s->token.u.str.sep = '"'; + s->token.u.str.str = string_buffer_end(b); + *pp = p; + return 0; + + end_of_input: + js_parse_error(s, "Unexpected end of JSON input"); + fail: + string_buffer_free(b); + return -1; +} + +static int json_parse_number(JSParseState *s, const uint8_t **pp) +{ + const uint8_t *p = *pp; + const uint8_t *p_start = p; + + if (*p == '+' || *p == '-') + p++; + + if (!is_digit(*p)) + return js_parse_error(s, "Unexpected token '%c'", *p_start); + + if (p[0] == '0' && is_digit(p[1])) + return json_parse_error(s, p, "Unexpected number"); + + while (is_digit(*p)) + p++; + + if (*p == '.') { + p++; + if (!is_digit(*p)) + return json_parse_error(s, p, "Unterminated fractional number"); + while (is_digit(*p)) + p++; + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') + p++; + if (!is_digit(*p)) + return json_parse_error(s, p, "Exponent part is missing a number"); + while (is_digit(*p)) + p++; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = js_float64(strtod((const char *)p_start, NULL)); + *pp = p; + return 0; +} + +/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +static JSAtom json_parse_ident(JSParseState *s, const uint8_t **pp, int c) +{ + const uint8_t *p; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSAtom atom; + + p = *pp; + buf = ident_buf; + ident_size = sizeof(ident_buf); + ident_pos = 0; + for(;;) { + buf[ident_pos++] = c; + c = *p; + if (c >= 128 || + !((lre_id_continue_table_ascii[c >> 5] >> (c & 31)) & 1)) + break; + p++; + if (unlikely(ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc(s->ctx, &buf, &ident_size, ident_buf)) { + atom = JS_ATOM_NULL; + goto done; + } + } + } + /* buf contains pure ASCII */ + atom = JS_NewAtomLen(s->ctx, buf, ident_pos); + done: + if (unlikely(buf != ident_buf)) + js_free(s->ctx, buf); + *pp = p; + return atom; +} + +static __exception int json_next_token(JSParseState *s) +{ + const uint8_t *p, *p_next; + int c; + JSAtom atom; + + if (js_check_stack_overflow(s->ctx->rt, 1000)) { + JS_ThrowStackOverflow(s->ctx); + return -1; + } + + free_token(s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->last_line_num = s->token.line_num; + s->last_col_num = s->token.col_num; + redo: + s->token.line_num = s->line_num; + s->token.col_num = s->col_num; + s->token.ptr = p; + c = *p; + switch(c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '\'': + /* JSON does not accept single quoted strings */ + goto def_token; + case '\"': + p++; + if (json_parse_string(s, &p)) + goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { + p++; + } + /* fall thru */ + case '\n': + s->line_num++; + s->eol = p++; + s->mark = p; + goto redo; + case '\f': + case '\v': + /* JSONWhitespace does not match <VT>, nor <FF> */ + goto def_token; + case ' ': + case '\t': + p++; + s->mark = p; + goto redo; + case '/': + /* JSON does not accept comments */ + goto def_token; + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': + case '$': + /* identifier : only pure ascii characters are accepted */ + p++; + atom = json_parse_ident(s, &p, c); + if (atom == JS_ATOM_NULL) + goto fail; + s->token.u.ident.atom = atom; + s->token.u.ident.has_escape = FALSE; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + break; + case '-': + if (!is_digit(p[1])) { + json_parse_error(s, p, "No number after minus sign"); + goto fail; + } + goto parse_number; + case '0': + if (is_digit(p[1])) { + json_parse_error(s, p, "Unexpected number"); + goto fail; + } + goto parse_number; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': + case '9': + /* number */ + parse_number: + if (json_parse_number(s, &p)) + goto fail; + break; + default: + if (c >= 0x80) { + c = utf8_decode(p, &p_next); + if (p_next == p + 1) { + js_parse_error(s, "Unexpected token '\\x%02x' in JSON", *p); + } else { + if (c > 0xFFFF) { + c = get_hi_surrogate(c); + } + js_parse_error(s, "Unexpected token '\\u%04x' in JSON", c); + } + goto fail; + } + def_token: + s->token.val = c; + p++; + break; + } + s->token.col_num = s->mark - s->eol; + s->buf_ptr = p; + + // dump_token(s, &s->token); + return 0; + + fail: + s->token.val = TOK_ERROR; + return -1; +} + +/* only used for ':' and '=>', 'let' or 'function' look-ahead. *pp is + only set if TOK_IMPORT is returned */ +/* XXX: handle all unicode cases */ +static int simple_next_token(const uint8_t **pp, BOOL no_line_terminator) +{ + const uint8_t *p; + uint32_t c; + + /* skip spaces and comments */ + p = *pp; + for (;;) { + switch(c = *p++) { + case '\r': + case '\n': + if (no_line_terminator) + return '\n'; + continue; + case ' ': + case '\t': + case '\v': + case '\f': + continue; + case '/': + if (*p == '/') { + if (no_line_terminator) + return '\n'; + while (*p && *p != '\r' && *p != '\n') + p++; + continue; + } + if (*p == '*') { + while (*++p) { + if ((*p == '\r' || *p == '\n') && no_line_terminator) + return '\n'; + if (*p == '*' && p[1] == '/') { + p += 2; + break; + } + } + continue; + } + break; + case '=': + if (*p == '>') + return TOK_ARROW; + break; + default: + if (lre_js_is_ident_first(c)) { + if (c == 'i') { + if (p[0] == 'n' && !lre_js_is_ident_next(p[1])) { + return TOK_IN; + } + if (p[0] == 'm' && p[1] == 'p' && p[2] == 'o' && + p[3] == 'r' && p[4] == 't' && + !lre_js_is_ident_next(p[5])) { + *pp = p + 5; + return TOK_IMPORT; + } + } else if (c == 'o' && *p == 'f' && !lre_js_is_ident_next(p[1])) { + return TOK_OF; + } else if (c == 'e' && + p[0] == 'x' && p[1] == 'p' && p[2] == 'o' && + p[3] == 'r' && p[4] == 't' && + !lre_js_is_ident_next(p[5])) { + *pp = p + 5; + return TOK_EXPORT; + } else if (c == 'f' && p[0] == 'u' && p[1] == 'n' && + p[2] == 'c' && p[3] == 't' && p[4] == 'i' && + p[5] == 'o' && p[6] == 'n' && !lre_js_is_ident_next(p[7])) { + return TOK_FUNCTION; + } else if (c == 'a' && p[0] == 'w' && p[1] == 'a' && + p[2] == 'i' && p[3] == 't' && !lre_js_is_ident_next(p[4])) { + return TOK_AWAIT; + } + return TOK_IDENT; + } + break; + } + return c; + } +} + +static int peek_token(JSParseState *s, BOOL no_line_terminator) +{ + const uint8_t *p = s->buf_ptr; + return simple_next_token(&p, no_line_terminator); +} + +static void skip_shebang(const uint8_t **pp, const uint8_t *buf_end) +{ + const uint8_t *p = *pp; + int c; + + if (p[0] == '#' && p[1] == '!') { + p += 2; + while (p < buf_end) { + if (*p == '\n' || *p == '\r') { + break; + } else if (*p >= 0x80) { + c = utf8_decode(p, &p); + /* purposely ignore UTF-8 encoding errors in this comment line */ + if (c == CP_LS || c == CP_PS) + break; + } else { + p++; + } + } + *pp = p; + } +} + +static inline int get_prev_opcode(JSFunctionDef *fd) { + if (fd->last_opcode_pos < 0) + return OP_invalid; + else + return fd->byte_code.buf[fd->last_opcode_pos]; +} + +static BOOL js_is_live_code(JSParseState *s) { + switch (get_prev_opcode(s->cur_func)) { + case OP_tail_call: + case OP_tail_call_method: + case OP_return: + case OP_return_undef: + case OP_return_async: + case OP_throw: + case OP_throw_error: + case OP_goto: + case OP_goto8: + case OP_goto16: + case OP_ret: + return FALSE; + default: + return TRUE; + } +} + +static void emit_u8(JSParseState *s, uint8_t val) +{ + dbuf_putc(&s->cur_func->byte_code, val); +} + +static void emit_u16(JSParseState *s, uint16_t val) +{ + dbuf_put_u16(&s->cur_func->byte_code, val); +} + +static void emit_u32(JSParseState *s, uint32_t val) +{ + dbuf_put_u32(&s->cur_func->byte_code, val); +} + +static void emit_source_loc(JSParseState *s) +{ + JSFunctionDef *fd = s->cur_func; + DynBuf *bc = &fd->byte_code; + + dbuf_putc(bc, OP_source_loc); + dbuf_put_u32(bc, s->last_line_num); + dbuf_put_u32(bc, s->last_col_num); +} + +static void emit_op(JSParseState *s, uint8_t val) +{ + JSFunctionDef *fd = s->cur_func; + DynBuf *bc = &fd->byte_code; + + fd->last_opcode_pos = bc->size; + dbuf_putc(bc, val); +} + +static void emit_atom(JSParseState *s, JSAtom name) +{ + emit_u32(s, JS_DupAtom(s->ctx, name)); +} + +static force_inline uint32_t get_index_hash(JSAtom atom, int hash_bits) +{ + return (atom * 0x9e370001) >> (32 - hash_bits); +} + +static void emit_ic(JSParseState *s, JSAtom atom) +{ + uint32_t h; + JSContext *ctx; + JSInlineCache *ic; + JSInlineCacheHashSlot *ch; + + ic = s->cur_func->ic; + ctx = s->ctx; + if (ic->count + 1 >= ic->capacity && resize_ic_hash(ctx, ic)) + return; + h = get_index_hash(atom, ic->hash_bits); + for (ch = ic->hash[h]; ch != NULL; ch = ch->next) + if (ch->atom == atom) + return; + ch = js_malloc(ctx, sizeof(*ch)); + if (unlikely(!ch)) + return; + ch->atom = JS_DupAtom(ctx, atom); + ch->index = 0; + ch->next = ic->hash[h]; + ic->hash[h] = ch; + ic->count += 1; +} + +static int update_label(JSFunctionDef *s, int label, int delta) +{ + LabelSlot *ls; + + assert(label >= 0 && label < s->label_count); + ls = &s->label_slots[label]; + ls->ref_count += delta; + assert(ls->ref_count >= 0); + return ls->ref_count; +} + +static int new_label_fd(JSFunctionDef *fd, int label) +{ + LabelSlot *ls; + + if (label < 0) { + if (js_resize_array(fd->ctx, (void *)&fd->label_slots, + sizeof(fd->label_slots[0]), + &fd->label_size, fd->label_count + 1)) + return -1; + label = fd->label_count++; + ls = &fd->label_slots[label]; + ls->ref_count = 0; + ls->pos = -1; + ls->pos2 = -1; + ls->addr = -1; + ls->first_reloc = NULL; + } + return label; +} + +static int new_label(JSParseState *s) +{ + return new_label_fd(s->cur_func, -1); +} + +/* don't update the last opcode and don't emit line number info */ +static void emit_label_raw(JSParseState *s, int label) +{ + emit_u8(s, OP_label); + emit_u32(s, label); + s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; +} + +/* return the label ID offset */ +static int emit_label(JSParseState *s, int label) +{ + if (label >= 0) { + emit_op(s, OP_label); + emit_u32(s, label); + s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; + return s->cur_func->byte_code.size - 4; + } else { + return -1; + } +} + +/* return label or -1 if dead code */ +static int emit_goto(JSParseState *s, int opcode, int label) +{ + if (js_is_live_code(s)) { + if (label < 0) + label = new_label(s); + emit_op(s, opcode); + emit_u32(s, label); + s->cur_func->label_slots[label].ref_count++; + return label; + } + return -1; +} + +/* return the constant pool index. 'val' is not duplicated. */ +static int cpool_add(JSParseState *s, JSValue val) +{ + JSFunctionDef *fd = s->cur_func; + + if (js_resize_array(s->ctx, (void *)&fd->cpool, sizeof(fd->cpool[0]), + &fd->cpool_size, fd->cpool_count + 1)) + return -1; + fd->cpool[fd->cpool_count++] = val; + return fd->cpool_count - 1; +} + +static __exception int emit_push_const(JSParseState *s, JSValue val, + BOOL as_atom) +{ + int idx; + + if (JS_VALUE_GET_TAG(val) == JS_TAG_STRING && as_atom) { + JSAtom atom; + /* warning: JS_NewAtomStr frees the string value */ + JS_DupValue(s->ctx, val); + atom = JS_NewAtomStr(s->ctx, JS_VALUE_GET_STRING(val)); + if (atom != JS_ATOM_NULL && !__JS_AtomIsTaggedInt(atom)) { + emit_op(s, OP_push_atom_value); + emit_u32(s, atom); + return 0; + } + } + + idx = cpool_add(s, JS_DupValue(s->ctx, val)); + if (idx < 0) + return -1; + emit_op(s, OP_push_const); + emit_u32(s, idx); + return 0; +} + +// perl hash; variation of k&r hash with a different magic multiplier +// and a final shuffle to improve distribution of the low-order bits +static uint32_t hash_bytes(uint32_t h, const void *b, size_t n) +{ + const char *p; + + for (p = b; p < (char *)b + n; p++) + h = 33*h + *p; + h += h >> 5; + return h; +} + +static uint32_t hash_atom(JSAtom atom) +{ + return hash_bytes(0, &atom, sizeof(atom)); +} + +// caveat emptor: the table size must be a power of two in order for +// masking to work, and the load factor constant must be an odd number (5) +// +// f(n)=n+n/t is used to estimate the load factor but changing t to an +// even number introduces gaps in the output of f, sometimes "jumping" +// over the next power of two; it's at powers of two when the hash table +// must be resized +static int update_var_htab(JSContext *ctx, JSFunctionDef *fd) +{ + uint32_t i, j, k, m, *p; + + if (fd->var_count < 27) // 27 + 27/5 == 32 + return 0; + k = fd->var_count - 1; + m = fd->var_count + fd->var_count/5; + if (m & (m - 1)) // unless power of two + goto insert; + m *= 2; + p = js_realloc(ctx, fd->vars_htab, m * sizeof(*fd->vars_htab)); + if (!p) + return -1; + for (i = 0; i < m; i++) + p[i] = UINT32_MAX; + fd->vars_htab = p; + k = 0; + m--; +insert: + m = UINT32_MAX >> clz32(m); + do { + i = hash_atom(fd->vars[k].var_name); + j = 1; + for (;;) { + p = &fd->vars_htab[i & m]; + if (*p == UINT32_MAX) + break; + i += j; + j += 1; // quadratic probing + } + *p = k++; + } while (k < (uint32_t)fd->var_count); + return 0; +} + +static int find_var_htab(JSFunctionDef *fd, JSAtom var_name) +{ + uint32_t i, j, m, *p; + + i = hash_atom(var_name); + j = 1; + m = fd->var_count + fd->var_count/5; + m = UINT32_MAX >> clz32(m); + for (;;) { + p = &fd->vars_htab[i & m]; + if (*p == UINT32_MAX) + return -1; + if (fd->vars[*p].var_name == var_name) + return *p; + i += j; + j += 1; // quadratic probing + } + return -1; // pacify compiler +} + +/* return the variable index or -1 if not found, + add ARGUMENT_VAR_OFFSET for argument variables */ +static int find_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) +{ + int i; + for(i = fd->arg_count; i-- > 0;) { + if (fd->args[i].var_name == name) + return i | ARGUMENT_VAR_OFFSET; + } + return -1; +} + +static int find_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) +{ + JSVarDef *vd; + int i; + + if (fd->vars_htab) { + i = find_var_htab(fd, name); + if (i == -1) + goto not_found; + vd = &fd->vars[i]; + if (fd->vars[i].scope_level == 0) + return i; + } + for(i = fd->var_count; i-- > 0;) { + vd = &fd->vars[i]; + if (vd->var_name == name) + if (vd->scope_level == 0) + return i; + } +not_found: + return find_arg(ctx, fd, name); +} + +/* find a variable declaration in a given scope */ +static int find_var_in_scope(JSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_level) +{ + int scope_idx; + for(scope_idx = fd->scopes[scope_level].first; scope_idx >= 0; + scope_idx = fd->vars[scope_idx].scope_next) { + if (fd->vars[scope_idx].scope_level != scope_level) + break; + if (fd->vars[scope_idx].var_name == name) + return scope_idx; + } + return -1; +} + +/* return true if scope == parent_scope or if scope is a child of + parent_scope */ +static BOOL is_child_scope(JSContext *ctx, JSFunctionDef *fd, + int scope, int parent_scope) +{ + while (scope >= 0) { + if (scope == parent_scope) + return TRUE; + scope = fd->scopes[scope].parent; + } + return FALSE; +} + +/* find a 'var' declaration in the same scope or a child scope */ +static int find_var_in_child_scope(JSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_level) +{ + int i; + for(i = 0; i < fd->var_count; i++) { + JSVarDef *vd = &fd->vars[i]; + if (vd->var_name == name && vd->scope_level == 0) { + if (is_child_scope(ctx, fd, vd->scope_next, + scope_level)) + return i; + } + } + return -1; +} + + +static JSGlobalVar *find_global_var(JSFunctionDef *fd, JSAtom name) +{ + int i; + for(i = 0; i < fd->global_var_count; i++) { + JSGlobalVar *hf = &fd->global_vars[i]; + if (hf->var_name == name) + return hf; + } + return NULL; + +} + +static JSGlobalVar *find_lexical_global_var(JSFunctionDef *fd, JSAtom name) +{ + JSGlobalVar *hf = find_global_var(fd, name); + if (hf && hf->is_lexical) + return hf; + else + return NULL; +} + +static int find_lexical_decl(JSContext *ctx, JSFunctionDef *fd, JSAtom name, + int scope_idx, BOOL check_catch_var) +{ + while (scope_idx >= 0) { + JSVarDef *vd = &fd->vars[scope_idx]; + if (vd->var_name == name && + (vd->is_lexical || (vd->var_kind == JS_VAR_CATCH && + check_catch_var))) + return scope_idx; + scope_idx = vd->scope_next; + } + + if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_GLOBAL) { + if (find_lexical_global_var(fd, name)) + return GLOBAL_VAR_OFFSET; + } + return -1; +} + +static int push_scope(JSParseState *s) { + if (s->cur_func) { + JSFunctionDef *fd = s->cur_func; + int scope = fd->scope_count; + /* XXX: should check for scope overflow */ + if ((fd->scope_count + 1) > fd->scope_size) { + int new_size; + size_t slack; + JSVarScope *new_buf; + /* XXX: potential arithmetic overflow */ + new_size = max_int(fd->scope_count + 1, fd->scope_size * 3 / 2); + if (fd->scopes == fd->def_scope_array) { + new_buf = js_realloc2(s->ctx, NULL, new_size * sizeof(*fd->scopes), &slack); + if (!new_buf) + return -1; + memcpy(new_buf, fd->scopes, fd->scope_count * sizeof(*fd->scopes)); + } else { + new_buf = js_realloc2(s->ctx, fd->scopes, new_size * sizeof(*fd->scopes), &slack); + if (!new_buf) + return -1; + } + new_size += slack / sizeof(*new_buf); + fd->scopes = new_buf; + fd->scope_size = new_size; + } + fd->scope_count++; + fd->scopes[scope].parent = fd->scope_level; + fd->scopes[scope].first = fd->scope_first; + emit_op(s, OP_enter_scope); + emit_u16(s, scope); + return fd->scope_level = scope; + } + return 0; +} + +static int get_first_lexical_var(JSFunctionDef *fd, int scope) +{ + while (scope >= 0) { + int scope_idx = fd->scopes[scope].first; + if (scope_idx >= 0) + return scope_idx; + scope = fd->scopes[scope].parent; + } + return -1; +} + +static void pop_scope(JSParseState *s) { + if (s->cur_func) { + /* disable scoped variables */ + JSFunctionDef *fd = s->cur_func; + int scope = fd->scope_level; + emit_op(s, OP_leave_scope); + emit_u16(s, scope); + fd->scope_level = fd->scopes[scope].parent; + fd->scope_first = get_first_lexical_var(fd, fd->scope_level); + } +} + +static void close_scopes(JSParseState *s, int scope, int scope_stop) +{ + while (scope > scope_stop) { + emit_op(s, OP_leave_scope); + emit_u16(s, scope); + scope = s->cur_func->scopes[scope].parent; + } +} + +/* return the variable index or -1 if error */ +static int add_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) +{ + JSVarDef *vd; + + /* the local variable indexes are currently stored on 16 bits */ + if (fd->var_count >= JS_MAX_LOCAL_VARS) { + // XXX: add_var() should take JSParseState *s and use js_parse_error + JS_ThrowSyntaxError(ctx, "too many variables declared (only %d allowed)", + JS_MAX_LOCAL_VARS - 1); + return -1; + } + if (js_resize_array(ctx, (void **)&fd->vars, sizeof(fd->vars[0]), + &fd->var_size, fd->var_count + 1)) + return -1; + vd = &fd->vars[fd->var_count++]; + memset(vd, 0, sizeof(*vd)); + vd->var_name = JS_DupAtom(ctx, name); + vd->func_pool_idx = -1; + if (update_var_htab(ctx, fd)) + return -1; + return fd->var_count - 1; +} + +static int add_scope_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name, + JSVarKindEnum var_kind) +{ + int idx = add_var(ctx, fd, name); + if (idx >= 0) { + JSVarDef *vd = &fd->vars[idx]; + vd->var_kind = var_kind; + vd->scope_level = fd->scope_level; + vd->scope_next = fd->scope_first; + fd->scopes[fd->scope_level].first = idx; + fd->scope_first = idx; + } + return idx; +} + +static int add_func_var(JSContext *ctx, JSFunctionDef *fd, JSAtom name) +{ + int idx = fd->func_var_idx; + if (idx < 0 && (idx = add_var(ctx, fd, name)) >= 0) { + fd->func_var_idx = idx; + fd->vars[idx].var_kind = JS_VAR_FUNCTION_NAME; + if (fd->is_strict_mode) + fd->vars[idx].is_const = TRUE; + } + return idx; +} + +static int add_arguments_var(JSContext *ctx, JSFunctionDef *fd) +{ + int idx = fd->arguments_var_idx; + if (idx < 0 && (idx = add_var(ctx, fd, JS_ATOM_arguments)) >= 0) { + fd->arguments_var_idx = idx; + } + return idx; +} + +/* add an argument definition in the argument scope. Only needed when + "eval()" may be called in the argument scope. Return 0 if OK. */ +static int add_arguments_arg(JSContext *ctx, JSFunctionDef *fd) +{ + int idx; + if (fd->arguments_arg_idx < 0) { + idx = find_var_in_scope(ctx, fd, JS_ATOM_arguments, ARG_SCOPE_INDEX); + if (idx < 0) { + /* XXX: the scope links are not fully updated. May be an + issue if there are child scopes of the argument + scope */ + idx = add_var(ctx, fd, JS_ATOM_arguments); + if (idx < 0) + return -1; + fd->vars[idx].scope_next = fd->scopes[ARG_SCOPE_INDEX].first; + fd->scopes[ARG_SCOPE_INDEX].first = idx; + fd->vars[idx].scope_level = ARG_SCOPE_INDEX; + fd->vars[idx].is_lexical = TRUE; + + fd->arguments_arg_idx = idx; + } + } + return 0; +} + +static int add_arg(JSContext *ctx, JSFunctionDef *fd, JSAtom name) +{ + JSVarDef *vd; + + /* the local variable indexes are currently stored on 16 bits */ + if (fd->arg_count >= JS_MAX_LOCAL_VARS) { + // XXX: add_arg() should take JSParseState *s and use js_parse_error + JS_ThrowSyntaxError(ctx, "too many parameters in function definition (only %d allowed)", + JS_MAX_LOCAL_VARS - 1); + return -1; + } + if (js_resize_array(ctx, (void **)&fd->args, sizeof(fd->args[0]), + &fd->arg_size, fd->arg_count + 1)) + return -1; + vd = &fd->args[fd->arg_count++]; + memset(vd, 0, sizeof(*vd)); + vd->var_name = JS_DupAtom(ctx, name); + vd->func_pool_idx = -1; + return fd->arg_count - 1; +} + +/* add a global variable definition */ +static JSGlobalVar *add_global_var(JSContext *ctx, JSFunctionDef *s, + JSAtom name) +{ + JSGlobalVar *hf; + + if (js_resize_array(ctx, (void **)&s->global_vars, + sizeof(s->global_vars[0]), + &s->global_var_size, s->global_var_count + 1)) + return NULL; + hf = &s->global_vars[s->global_var_count++]; + hf->cpool_idx = -1; + hf->force_init = FALSE; + hf->is_lexical = FALSE; + hf->is_const = FALSE; + hf->scope_level = s->scope_level; + hf->var_name = JS_DupAtom(ctx, name); + return hf; +} + +typedef enum { + JS_VAR_DEF_WITH, + JS_VAR_DEF_LET, + JS_VAR_DEF_CONST, + JS_VAR_DEF_FUNCTION_DECL, /* function declaration */ + JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */ + JS_VAR_DEF_CATCH, + JS_VAR_DEF_VAR, +} JSVarDefEnum; + +static int define_var(JSParseState *s, JSFunctionDef *fd, JSAtom name, + JSVarDefEnum var_def_type) +{ + JSContext *ctx = s->ctx; + JSVarDef *vd; + int idx; + + switch (var_def_type) { + case JS_VAR_DEF_WITH: + idx = add_scope_var(ctx, fd, name, JS_VAR_NORMAL); + break; + + case JS_VAR_DEF_LET: + case JS_VAR_DEF_CONST: + case JS_VAR_DEF_FUNCTION_DECL: + case JS_VAR_DEF_NEW_FUNCTION_DECL: + idx = find_lexical_decl(ctx, fd, name, fd->scope_first, TRUE); + if (idx >= 0) { + if (idx < GLOBAL_VAR_OFFSET) { + if (fd->vars[idx].scope_level == fd->scope_level) { + /* same scope: in non strict mode, functions + can be redefined (annex B.3.3.4). */ + if (!(!fd->is_strict_mode && + var_def_type == JS_VAR_DEF_FUNCTION_DECL && + fd->vars[idx].var_kind == JS_VAR_FUNCTION_DECL)) { + goto redef_lex_error; + } + } else if (fd->vars[idx].var_kind == JS_VAR_CATCH && (fd->vars[idx].scope_level + 2) == fd->scope_level) { + goto redef_lex_error; + } + } else { + if (fd->scope_level == fd->body_scope) { + redef_lex_error: + /* redefining a scoped var in the same scope: error */ + return js_parse_error(s, "invalid redefinition of lexical identifier"); + } + } + } + if (var_def_type != JS_VAR_DEF_FUNCTION_DECL && + var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL && + fd->scope_level == fd->body_scope && + find_arg(ctx, fd, name) >= 0) { + /* lexical variable redefines a parameter name */ + return js_parse_error(s, "invalid redefinition of parameter name"); + } + + if (find_var_in_child_scope(ctx, fd, name, fd->scope_level) >= 0) { + return js_parse_error(s, "invalid redefinition of a variable"); + } + + if (fd->is_global_var) { + JSGlobalVar *hf; + hf = find_global_var(fd, name); + if (hf && is_child_scope(ctx, fd, hf->scope_level, + fd->scope_level)) { + return js_parse_error(s, "invalid redefinition of global identifier"); + } + } + + if (fd->is_eval && + (fd->eval_type == JS_EVAL_TYPE_GLOBAL || + fd->eval_type == JS_EVAL_TYPE_MODULE) && + fd->scope_level == fd->body_scope) { + JSGlobalVar *hf; + hf = add_global_var(s->ctx, fd, name); + if (!hf) + return -1; + hf->is_lexical = TRUE; + hf->is_const = (var_def_type == JS_VAR_DEF_CONST); + idx = GLOBAL_VAR_OFFSET; + } else { + JSVarKindEnum var_kind; + if (var_def_type == JS_VAR_DEF_FUNCTION_DECL) + var_kind = JS_VAR_FUNCTION_DECL; + else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL) + var_kind = JS_VAR_NEW_FUNCTION_DECL; + else + var_kind = JS_VAR_NORMAL; + idx = add_scope_var(ctx, fd, name, var_kind); + if (idx >= 0) { + vd = &fd->vars[idx]; + vd->is_lexical = 1; + vd->is_const = (var_def_type == JS_VAR_DEF_CONST); + } + } + break; + + case JS_VAR_DEF_CATCH: + idx = add_scope_var(ctx, fd, name, JS_VAR_CATCH); + break; + + case JS_VAR_DEF_VAR: + if (find_lexical_decl(ctx, fd, name, fd->scope_first, + FALSE) >= 0) { + invalid_lexical_redefinition: + /* error to redefine a var that inside a lexical scope */ + return js_parse_error(s, "invalid redefinition of lexical identifier"); + } + if (fd->is_global_var) { + JSGlobalVar *hf; + hf = find_global_var(fd, name); + if (hf && hf->is_lexical && hf->scope_level == fd->scope_level && + fd->eval_type == JS_EVAL_TYPE_MODULE) { + goto invalid_lexical_redefinition; + } + hf = add_global_var(s->ctx, fd, name); + if (!hf) + return -1; + idx = GLOBAL_VAR_OFFSET; + } else { + /* if the variable already exists, don't add it again */ + idx = find_var(ctx, fd, name); + if (idx >= 0) + break; + idx = add_var(ctx, fd, name); + if (idx >= 0) { + if (name == JS_ATOM_arguments && fd->has_arguments_binding) + fd->arguments_var_idx = idx; + fd->vars[idx].scope_next = fd->scope_level; + } + } + break; + default: + abort(); + } + return idx; +} + +/* add a private field variable in the current scope */ +static int add_private_class_field(JSParseState *s, JSFunctionDef *fd, + JSAtom name, JSVarKindEnum var_kind, BOOL is_static) +{ + JSContext *ctx = s->ctx; + JSVarDef *vd; + int idx; + + idx = add_scope_var(ctx, fd, name, var_kind); + if (idx < 0) + return idx; + vd = &fd->vars[idx]; + vd->is_lexical = 1; + vd->is_const = 1; + vd->is_static_private = is_static; + return idx; +} + +static __exception int js_parse_expr(JSParseState *s); +static __exception int js_parse_function_decl(JSParseState *s, + JSParseFunctionEnum func_type, + JSFunctionKindEnum func_kind, + JSAtom func_name, const uint8_t *ptr, + int start_line, int start_col); +static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s); +static __exception int js_parse_function_decl2(JSParseState *s, + JSParseFunctionEnum func_type, + JSFunctionKindEnum func_kind, + JSAtom func_name, + const uint8_t *ptr, + int function_line_num, + int function_col_num, + JSParseExportEnum export_flag, + JSFunctionDef **pfd); +static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags); +static __exception int js_parse_assign_expr(JSParseState *s); +static __exception int js_parse_unary(JSParseState *s, int parse_flags); +static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, + JSAtom label_name, + int label_break, int label_cont, + int drop_count); +static void pop_break_entry(JSFunctionDef *fd); +static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m, + JSAtom local_name, JSAtom export_name, + JSExportTypeEnum export_type); + +/* Note: all the fields are already sealed except length */ +static int seal_template_obj(JSContext *ctx, JSValue obj) +{ + JSObject *p; + JSShapeProperty *prs; + + p = JS_VALUE_GET_OBJ(obj); + prs = find_own_property1(p, JS_ATOM_length); + if (prs) { + if (js_update_property_flags(ctx, p, &prs, + prs->flags & ~(JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE))) + return -1; + } + p->extensible = FALSE; + return 0; +} + +static __exception int js_parse_template(JSParseState *s, int call, int *argc) +{ + JSContext *ctx = s->ctx; + JSValue raw_array, template_object; + JSToken cooked; + int depth, ret; + + raw_array = JS_UNDEFINED; /* avoid warning */ + template_object = JS_UNDEFINED; /* avoid warning */ + if (call) { + /* Create a template object: an array of cooked strings */ + /* Create an array of raw strings and store it to the raw property */ + template_object = JS_NewArray(ctx); + if (JS_IsException(template_object)) + return -1; + // pool_idx = s->cur_func->cpool_count; + ret = emit_push_const(s, template_object, 0); + JS_FreeValue(ctx, template_object); + if (ret) + return -1; + raw_array = JS_NewArray(ctx); + if (JS_IsException(raw_array)) + return -1; + if (JS_DefinePropertyValue(ctx, template_object, JS_ATOM_raw, + raw_array, JS_PROP_THROW) < 0) { + return -1; + } + } + + depth = 0; + while (s->token.val == TOK_TEMPLATE) { + const uint8_t *p = s->token.ptr + 1; + cooked = s->token; + if (call) { + if (JS_DefinePropertyValueUint32(ctx, raw_array, depth, + js_dup(s->token.u.str.str), + JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) { + return -1; + } + /* re-parse the string with escape sequences but do not throw a + syntax error if it contains invalid sequences + */ + if (js_parse_string(s, '`', FALSE, p, &cooked, &p)) { + cooked.u.str.str = JS_UNDEFINED; + } + if (JS_DefinePropertyValueUint32(ctx, template_object, depth, + cooked.u.str.str, + JS_PROP_ENUMERABLE | JS_PROP_THROW) < 0) { + return -1; + } + } else { + JSString *str; + /* re-parse the string with escape sequences and throw a + syntax error if it contains invalid sequences + */ + JS_FreeValue(ctx, s->token.u.str.str); + s->token.u.str.str = JS_UNDEFINED; + if (js_parse_string(s, '`', TRUE, p, &cooked, &p)) + return -1; + str = JS_VALUE_GET_STRING(cooked.u.str.str); + if (str->len != 0 || depth == 0) { + ret = emit_push_const(s, cooked.u.str.str, 1); + JS_FreeValue(s->ctx, cooked.u.str.str); + if (ret) + return -1; + if (depth == 0) { + if (s->token.u.str.sep == '`') + goto done1; + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_concat); + emit_ic(s, JS_ATOM_concat); + } + depth++; + } else { + JS_FreeValue(s->ctx, cooked.u.str.str); + } + } + if (s->token.u.str.sep == '`') + goto done; + if (next_token(s)) + return -1; + if (js_parse_expr(s)) + return -1; + depth++; + if (s->token.val != '}') { + return js_parse_error(s, "expected '}' after template expression"); + } + /* XXX: should convert to string at this stage? */ + free_token(s, &s->token); + /* Resume TOK_TEMPLATE parsing (s->token.line_num and + * s->token.ptr are OK) */ + s->got_lf = FALSE; + s->last_line_num = s->token.line_num; + s->last_col_num = s->token.col_num; + if (js_parse_template_part(s, s->buf_ptr)) + return -1; + } + return js_parse_expect(s, TOK_TEMPLATE); + + done: + if (call) { + /* Seal the objects */ + seal_template_obj(ctx, raw_array); + seal_template_obj(ctx, template_object); + *argc = depth + 1; + } else { + emit_op(s, OP_call_method); + emit_u16(s, depth - 1); + } + done1: + return next_token(s); +} + + +#define PROP_TYPE_IDENT 0 +#define PROP_TYPE_VAR 1 +#define PROP_TYPE_GET 2 +#define PROP_TYPE_SET 3 +#define PROP_TYPE_STAR 4 +#define PROP_TYPE_ASYNC 5 +#define PROP_TYPE_ASYNC_STAR 6 + +#define PROP_TYPE_PRIVATE (1 << 4) + +static BOOL token_is_ident(int tok) +{ + /* Accept keywords and reserved words as property names */ + return (tok == TOK_IDENT || + (tok >= TOK_FIRST_KEYWORD && + tok <= TOK_LAST_KEYWORD)); +} + +/* if the property is an expression, name = JS_ATOM_NULL */ +static int __exception js_parse_property_name(JSParseState *s, + JSAtom *pname, + BOOL allow_method, BOOL allow_var, + BOOL allow_private) +{ + int is_private = 0; + BOOL is_non_reserved_ident; + JSAtom name; + int prop_type; + + prop_type = PROP_TYPE_IDENT; + if (allow_method) { + if (token_is_pseudo_keyword(s, JS_ATOM_get) + || token_is_pseudo_keyword(s, JS_ATOM_set)) { + /* get x(), set x() */ + name = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail1; + if (s->token.val == ':' || s->token.val == ',' || + s->token.val == '}' || s->token.val == '(' || + s->token.val == '=' || s->token.val == ';') { + is_non_reserved_ident = TRUE; + goto ident_found; + } + prop_type = PROP_TYPE_GET + (name == JS_ATOM_set); + JS_FreeAtom(s->ctx, name); + } else if (s->token.val == '*') { + if (next_token(s)) + goto fail; + prop_type = PROP_TYPE_STAR; + } else if (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) != '\n') { + name = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail1; + if (s->token.val == ':' || s->token.val == ',' || + s->token.val == '}' || s->token.val == '(' || + s->token.val == '=' || s->token.val == ';') { + is_non_reserved_ident = TRUE; + goto ident_found; + } + JS_FreeAtom(s->ctx, name); + if (s->token.val == '*') { + if (next_token(s)) + goto fail; + prop_type = PROP_TYPE_ASYNC_STAR; + } else { + prop_type = PROP_TYPE_ASYNC; + } + } + } + + if (token_is_ident(s->token.val)) { + /* variable can only be a non-reserved identifier */ + is_non_reserved_ident = + (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved); + /* keywords and reserved words have a valid atom */ + name = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail1; + ident_found: + if (is_non_reserved_ident && + prop_type == PROP_TYPE_IDENT && allow_var) { + if (!(s->token.val == ':' || + (s->token.val == '(' && allow_method))) { + prop_type = PROP_TYPE_VAR; + } + } + } else if (s->token.val == TOK_STRING) { + name = JS_ValueToAtom(s->ctx, s->token.u.str.str); + if (name == JS_ATOM_NULL) + goto fail; + if (next_token(s)) + goto fail1; + } else if (s->token.val == TOK_NUMBER) { + JSValue val; + val = s->token.u.num.val; + name = JS_ValueToAtom(s->ctx, val); + if (name == JS_ATOM_NULL) + goto fail; + if (next_token(s)) + goto fail1; + } else if (s->token.val == '[') { + if (next_token(s)) + goto fail; + if (js_parse_expr(s)) + goto fail; + if (js_parse_expect(s, ']')) + goto fail; + name = JS_ATOM_NULL; + } else if (s->token.val == TOK_PRIVATE_NAME && allow_private) { + name = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail1; + is_private = PROP_TYPE_PRIVATE; + } else { + goto invalid_prop; + } + if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR && + s->token.val != '(') { + JS_FreeAtom(s->ctx, name); + invalid_prop: + js_parse_error(s, "invalid property name"); + goto fail; + } + *pname = name; + return prop_type | is_private; + fail1: + JS_FreeAtom(s->ctx, name); + fail: + *pname = JS_ATOM_NULL; + return -1; +} + +typedef struct JSParsePos { + int last_line_num; + int last_col_num; + int line_num; + int col_num; + BOOL got_lf; + const uint8_t *ptr; + const uint8_t *eol; + const uint8_t *mark; +} JSParsePos; + +static int js_parse_get_pos(JSParseState *s, JSParsePos *sp) +{ + sp->last_line_num = s->last_line_num; + sp->last_col_num = s->last_col_num; + sp->line_num = s->token.line_num; + sp->col_num = s->token.col_num; + sp->ptr = s->token.ptr; + sp->eol = s->eol; + sp->mark = s->mark; + sp->got_lf = s->got_lf; + return 0; +} + +static __exception int js_parse_seek_token(JSParseState *s, const JSParsePos *sp) +{ + s->token.line_num = sp->last_line_num; + s->token.col_num = sp->last_col_num; + s->line_num = sp->line_num; + s->col_num = sp->col_num; + s->buf_ptr = sp->ptr; + s->eol = sp->eol; + s->mark = sp->mark; + s->got_lf = sp->got_lf; + return next_token(s); +} + +/* 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 ')': + case ']': + case '}': /* XXX: regexp may occur after */ + case TOK_IDENT: + return FALSE; + default: + return TRUE; + } +} + +#define SKIP_HAS_SEMI (1 << 0) +#define SKIP_HAS_ELLIPSIS (1 << 1) +#define SKIP_HAS_ASSIGNMENT (1 << 2) + +/* XXX: improve speed with early bailout */ +/* XXX: no longer works if regexps are present. Could use previous + regexp parsing heuristics to handle most cases */ +static int js_parse_skip_parens_token(JSParseState *s, int *pbits, BOOL no_line_terminator) +{ + char state[256]; + size_t level = 0; + JSParsePos pos; + int last_tok, tok = TOK_EOF; + int c, tok_len, bits = 0; + + /* protect from underflow */ + state[level++] = 0; + + js_parse_get_pos(s, &pos); + last_tok = 0; + for (;;) { + switch(s->token.val) { + case '(': + case '[': + case '{': + if (level >= sizeof(state)) + goto done; + state[level++] = s->token.val; + break; + case ')': + if (state[--level] != '(') + goto done; + break; + case ']': + if (state[--level] != '[') + goto done; + break; + case '}': + c = state[--level]; + if (c == '`') { + /* continue the parsing of the template */ + free_token(s, &s->token); + /* Resume TOK_TEMPLATE parsing (s->token.line_num and + * s->token.ptr are OK) */ + s->got_lf = FALSE; + s->last_line_num = s->token.line_num; + s->last_col_num = s->token.col_num; + if (js_parse_template_part(s, s->buf_ptr)) + goto done; + goto handle_template; + } else if (c != '{') { + goto done; + } + break; + case TOK_TEMPLATE: + handle_template: + if (s->token.u.str.sep != '`') { + /* '${' inside the template : closing '}' and continue + parsing the template */ + if (level >= sizeof(state)) + goto done; + state[level++] = '`'; + } + break; + case TOK_EOF: + goto done; + case ';': + if (level == 2) { + bits |= SKIP_HAS_SEMI; + } + break; + case TOK_ELLIPSIS: + if (level == 2) { + bits |= SKIP_HAS_ELLIPSIS; + } + break; + case '=': + bits |= SKIP_HAS_ASSIGNMENT; + break; + + case TOK_DIV_ASSIGN: + tok_len = 2; + goto parse_regexp; + case '/': + tok_len = 1; + parse_regexp: + if (is_regexp_allowed(last_tok)) { + s->buf_ptr -= tok_len; + if (js_parse_regexp(s)) { + /* XXX: should clear the exception */ + goto done; + } + } + break; + } + /* last_tok is only used to recognize regexps */ + if (s->token.val == TOK_IDENT && + (token_is_pseudo_keyword(s, JS_ATOM_of) || + token_is_pseudo_keyword(s, JS_ATOM_yield))) { + last_tok = TOK_OF; + } else { + last_tok = s->token.val; + } + if (next_token(s)) { + /* XXX: should clear the exception generated by next_token() */ + break; + } + if (level <= 1) { + tok = s->token.val; + if (token_is_pseudo_keyword(s, JS_ATOM_of)) + tok = TOK_OF; + if (no_line_terminator && s->last_line_num != s->token.line_num) + tok = '\n'; + break; + } + } + done: + if (pbits) { + *pbits = bits; + } + if (js_parse_seek_token(s, &pos)) + return -1; + return tok; +} + +static void set_object_name(JSParseState *s, JSAtom name) +{ + JSFunctionDef *fd = s->cur_func; + int opcode; + + opcode = get_prev_opcode(fd); + if (opcode == OP_set_name) { + /* XXX: should free atom after OP_set_name? */ + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op(s, OP_set_name); + emit_atom(s, name); + } else if (opcode == OP_set_class_name) { + int define_class_pos; + JSAtom atom; + define_class_pos = fd->last_opcode_pos + 1 - + get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + assert(fd->byte_code.buf[define_class_pos] == OP_define_class); + /* for consistency we free the previous atom which is + JS_ATOM_empty_string */ + atom = get_u32(fd->byte_code.buf + define_class_pos + 1); + JS_FreeAtom(s->ctx, atom); + put_u32(fd->byte_code.buf + define_class_pos + 1, + JS_DupAtom(s->ctx, name)); + fd->last_opcode_pos = -1; + } +} + +static void set_object_name_computed(JSParseState *s) +{ + JSFunctionDef *fd = s->cur_func; + int opcode; + + opcode = get_prev_opcode(fd); + if (opcode == OP_set_name) { + /* XXX: should free atom after OP_set_name? */ + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op(s, OP_set_name_computed); + } else if (opcode == OP_set_class_name) { + int define_class_pos; + define_class_pos = fd->last_opcode_pos + 1 - + get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + assert(fd->byte_code.buf[define_class_pos] == OP_define_class); + fd->byte_code.buf[define_class_pos] = OP_define_class_computed; + fd->last_opcode_pos = -1; + } +} + +static __exception int js_parse_object_literal(JSParseState *s) +{ + JSAtom name = JS_ATOM_NULL; + const uint8_t *start_ptr; + int start_line, start_col, prop_type; + BOOL has_proto; + + if (next_token(s)) + goto fail; + /* XXX: add an initial length that will be patched back */ + emit_op(s, OP_object); + has_proto = FALSE; + while (s->token.val != '}') { + /* specific case for getter/setter */ + start_ptr = s->token.ptr; + start_line = s->token.line_num; + start_col = s->token.col_num; + + if (s->token.val == TOK_ELLIPSIS) { + if (next_token(s)) + return -1; + if (js_parse_assign_expr(s)) + return -1; + emit_op(s, OP_null); /* dummy excludeList */ + emit_op(s, OP_copy_data_properties); + emit_u8(s, 2 | (1 << 2) | (0 << 5)); + emit_op(s, OP_drop); /* pop excludeList */ + emit_op(s, OP_drop); /* pop src object */ + goto next; + } + + prop_type = js_parse_property_name(s, &name, TRUE, TRUE, FALSE); + if (prop_type < 0) + goto fail; + + if (prop_type == PROP_TYPE_VAR) { + /* shortcut for x: x */ + emit_op(s, OP_scope_get_var); + emit_atom(s, name); + emit_u16(s, s->cur_func->scope_level); + emit_op(s, OP_define_field); + emit_atom(s, name); + } else if (s->token.val == '(') { + BOOL is_getset = (prop_type == PROP_TYPE_GET || + prop_type == PROP_TYPE_SET); + JSParseFunctionEnum func_type; + JSFunctionKindEnum func_kind; + int op_flags; + + func_kind = JS_FUNC_NORMAL; + if (is_getset) { + func_type = JS_PARSE_FUNC_GETTER + prop_type - PROP_TYPE_GET; + } else { + func_type = JS_PARSE_FUNC_METHOD; + if (prop_type == PROP_TYPE_STAR) + func_kind = JS_FUNC_GENERATOR; + else if (prop_type == PROP_TYPE_ASYNC) + func_kind = JS_FUNC_ASYNC; + else if (prop_type == PROP_TYPE_ASYNC_STAR) + func_kind = JS_FUNC_ASYNC_GENERATOR; + } + if (js_parse_function_decl(s, func_type, func_kind, JS_ATOM_NULL, + start_ptr, start_line, start_col)) + goto fail; + if (name == JS_ATOM_NULL) { + emit_op(s, OP_define_method_computed); + } else { + emit_op(s, OP_define_method); + emit_atom(s, name); + } + if (is_getset) { + op_flags = OP_DEFINE_METHOD_GETTER + + prop_type - PROP_TYPE_GET; + } else { + op_flags = OP_DEFINE_METHOD_METHOD; + } + emit_u8(s, op_flags | OP_DEFINE_METHOD_ENUMERABLE); + } else { + if (js_parse_expect(s, ':')) + goto fail; + if (js_parse_assign_expr(s)) + goto fail; + if (name == JS_ATOM_NULL) { + set_object_name_computed(s); + emit_op(s, OP_define_array_el); + emit_op(s, OP_drop); + } else if (name == JS_ATOM___proto__) { + if (has_proto) { + js_parse_error(s, "duplicate __proto__ property name"); + goto fail; + } + emit_op(s, OP_set_proto); + has_proto = TRUE; + } else { + set_object_name(s, name); + emit_op(s, OP_define_field); + emit_atom(s, name); + } + } + JS_FreeAtom(s->ctx, name); + next: + name = JS_ATOM_NULL; + if (s->token.val != ',') + break; + if (next_token(s)) + goto fail; + } + if (js_parse_expect(s, '}')) + goto fail; + return 0; + fail: + JS_FreeAtom(s->ctx, name); + return -1; +} + +/* allow the 'in' binary operator */ +#define PF_IN_ACCEPTED (1 << 0) +/* allow function calls parsing in js_parse_postfix_expr() */ +#define PF_POSTFIX_CALL (1 << 1) +/* allow the exponentiation operator in js_parse_unary() */ +#define PF_POW_ALLOWED (1 << 2) +/* forbid the exponentiation operator in js_parse_unary() */ +#define PF_POW_FORBIDDEN (1 << 3) + +static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags); + +static __exception int js_parse_left_hand_side_expr(JSParseState *s) +{ + return js_parse_postfix_expr(s, PF_POSTFIX_CALL); +} + +/* find field in the current scope */ +static int find_private_class_field(JSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_level) +{ + int idx; + idx = fd->scopes[scope_level].first; + while (idx != -1) { + if (fd->vars[idx].scope_level != scope_level) + break; + if (fd->vars[idx].var_name == name) + return idx; + idx = fd->vars[idx].scope_next; + } + return -1; +} + +/* initialize the class fields, called by the constructor. Note: + super() can be called in an arrow function, so <this> and + <class_fields_init> can be variable references */ +static void emit_class_field_init(JSParseState *s) +{ + int label_next; + + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_class_fields_init); + emit_u16(s, s->cur_func->scope_level); + + /* no need to call the class field initializer if not defined */ + emit_op(s, OP_dup); + label_next = emit_goto(s, OP_if_false, -1); + + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + + emit_op(s, OP_swap); + + emit_op(s, OP_call_method); + emit_u16(s, 0); + + emit_label(s, label_next); + emit_op(s, OP_drop); +} + +/* build a private setter function name from the private getter name */ +static JSAtom get_private_setter_name(JSContext *ctx, JSAtom name) +{ + return js_atom_concat_str(ctx, name, "<set>"); +} + +typedef struct { + JSFunctionDef *fields_init_fd; + int computed_fields_count; + BOOL need_brand; + int brand_push_pos; + BOOL is_static; +} ClassFieldsDef; + +static __exception int emit_class_init_start(JSParseState *s, + ClassFieldsDef *cf) +{ + int label_add_brand; + + cf->fields_init_fd = js_parse_function_class_fields_init(s); + if (!cf->fields_init_fd) + return -1; + + s->cur_func = cf->fields_init_fd; + + if (!cf->is_static) { + /* add the brand to the newly created instance */ + /* XXX: would be better to add the code only if needed, maybe in a + later pass */ + emit_op(s, OP_push_false); /* will be patched later */ + cf->brand_push_pos = cf->fields_init_fd->last_opcode_pos; + label_add_brand = emit_goto(s, OP_if_false, -1); + + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_home_object); + emit_u16(s, 0); + + emit_op(s, OP_add_brand); + + emit_label(s, label_add_brand); + } + s->cur_func = s->cur_func->parent; + return 0; +} + +static void emit_class_init_end(JSParseState *s, ClassFieldsDef *cf) +{ + int cpool_idx; + + s->cur_func = cf->fields_init_fd; + emit_op(s, OP_return_undef); + s->cur_func = s->cur_func->parent; + + cpool_idx = cpool_add(s, JS_NULL); + cf->fields_init_fd->parent_cpool_idx = cpool_idx; + emit_op(s, OP_fclosure); + emit_u32(s, cpool_idx); + emit_op(s, OP_set_home_object); +} + +static void emit_return(JSParseState *s, BOOL hasval); + +static JSFunctionDef *js_new_function_def(JSContext *ctx, + JSFunctionDef *parent, + BOOL is_eval, + BOOL is_func_expr, + const char *filename, + int line_num, + int col_num); + +static __exception int js_parse_class_default_ctor(JSParseState *s, + BOOL has_super, + JSFunctionDef **pfd) +{ + JSParseFunctionEnum func_type; + JSFunctionDef *fd = s->cur_func; + + fd = js_new_function_def(s->ctx, fd, FALSE, FALSE, s->filename, + s->token.line_num, s->token.col_num); + if (!fd) + return -1; + + s->cur_func = fd; + fd->has_home_object = TRUE; + fd->super_allowed = TRUE; + fd->has_prototype = FALSE; + fd->has_this_binding = TRUE; + fd->new_target_allowed = TRUE; + + /* error if not invoked as a constructor */ + emit_op(s, OP_check_ctor); + + push_scope(s); /* enter body scope */ + fd->body_scope = fd->scope_level; + if (has_super) { + fd->is_derived_class_constructor = TRUE; + fd->super_call_allowed = TRUE; + fd->arguments_allowed = TRUE; + fd->has_arguments_binding = TRUE; + + func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR; + /* super */ + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this_active_func); + emit_u16(s, 0); + + emit_op(s, OP_get_super); + + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_new_target); + emit_u16(s, 0); + + emit_op(s, OP_array_from); + emit_u16(s, 0); + emit_op(s, OP_push_i32); + emit_u32(s, 0); + + /* arguments */ + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_arguments); + emit_u16(s, 0); + + emit_op(s, OP_append); + /* drop the index */ + emit_op(s, OP_drop); + + emit_op(s, OP_apply); + emit_u16(s, 1); + /* set the 'this' value */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + emit_class_field_init(s); + } else { + func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; + emit_class_field_init(s); + } + + fd->func_kind = JS_FUNC_NORMAL; + fd->func_type = func_type; + emit_return(s, FALSE); + + s->cur_func = fd->parent; + if (pfd) + *pfd = fd; + + int idx; + /* the real object will be set at the end of the compilation */ + idx = cpool_add(s, JS_NULL); + fd->parent_cpool_idx = idx; + + return 0; +} + + +static __exception int js_parse_class(JSParseState *s, BOOL is_class_expr, + JSParseExportEnum export_flag) +{ + JSContext *ctx = s->ctx; + JSFunctionDef *fd = s->cur_func; + JSAtom name = JS_ATOM_NULL, class_name = JS_ATOM_NULL, class_name1; + JSAtom class_var_name = JS_ATOM_NULL; + JSFunctionDef *method_fd, *ctor_fd; + int class_name_var_idx, prop_type, ctor_cpool_offset; + int class_flags = 0, i, define_class_offset; + BOOL is_static, is_private, is_strict_mode; + const uint8_t *class_start_ptr = s->token.ptr; + const uint8_t *start_ptr; + ClassFieldsDef class_fields[2]; + + /* classes are parsed and executed in strict mode */ + is_strict_mode = fd->is_strict_mode; + fd->is_strict_mode = TRUE; + if (next_token(s)) + goto fail; + if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier(s); + goto fail; + } + class_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail; + } else if (!is_class_expr && export_flag != JS_PARSE_EXPORT_DEFAULT) { + js_parse_error(s, "class statement requires a name"); + goto fail; + } + if (!is_class_expr) { + if (class_name == JS_ATOM_NULL) + class_var_name = JS_ATOM__default_; /* export default */ + else + class_var_name = class_name; + class_var_name = JS_DupAtom(ctx, class_var_name); + } + + push_scope(s); + + if (s->token.val == TOK_EXTENDS) { + class_flags = JS_DEFINE_CLASS_HAS_HERITAGE; + if (next_token(s)) + goto fail; + if (js_parse_left_hand_side_expr(s)) + goto fail; + } else { + emit_op(s, OP_undefined); + } + + /* add a 'const' definition for the class name */ + if (class_name != JS_ATOM_NULL) { + class_name_var_idx = define_var(s, fd, class_name, JS_VAR_DEF_CONST); + if (class_name_var_idx < 0) + goto fail; + } + + if (js_parse_expect(s, '{')) + goto fail; + + /* this scope contains the private fields */ + push_scope(s); + + emit_op(s, OP_push_const); + ctor_cpool_offset = fd->byte_code.size; + emit_u32(s, 0); /* will be patched at the end of the class parsing */ + + if (class_name == JS_ATOM_NULL) { + if (class_var_name != JS_ATOM_NULL) + class_name1 = JS_ATOM_default; + else + class_name1 = JS_ATOM_empty_string; + } else { + class_name1 = class_name; + } + + emit_op(s, OP_define_class); + emit_atom(s, class_name1); + emit_u8(s, class_flags); + define_class_offset = fd->last_opcode_pos; + + for(i = 0; i < 2; i++) { + ClassFieldsDef *cf = &class_fields[i]; + cf->fields_init_fd = NULL; + cf->computed_fields_count = 0; + cf->need_brand = FALSE; + cf->is_static = i; + } + + ctor_fd = NULL; + while (s->token.val != '}') { + if (s->token.val == ';') { + if (next_token(s)) + goto fail; + continue; + } + is_static = FALSE; + if (s->token.val == TOK_STATIC) { + int next = peek_token(s, TRUE); + if (!(next == ';' || next == '}' || next == '(' || next == '=')) + is_static = TRUE; + } + prop_type = -1; + if (is_static) { + if (next_token(s)) + goto fail; + if (s->token.val == '{') { + ClassFieldsDef *cf = &class_fields[is_static]; + if (!cf->fields_init_fd) + if (emit_class_init_start(s, cf)) + goto fail; + s->cur_func = cf->fields_init_fd; + // stack is now: <empty> + JSFunctionDef *init; + if (js_parse_function_decl2(s, JS_PARSE_FUNC_CLASS_STATIC_INIT, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + s->token.line_num, + s->token.col_num, + JS_PARSE_EXPORT_NONE, &init) < 0) { + goto fail; + } + // stack is now: fclosure + push_scope(s); + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + // stack is now: fclosure this + if (class_name != JS_ATOM_NULL) { + // TODO(bnoordhuis) pass as argument to init method? + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, class_name); + emit_u16(s, s->cur_func->scope_level); + } + emit_op(s, OP_swap); + // stack is now: this fclosure + emit_op(s, OP_call_method); + emit_u16(s, 0); + // stack is now: returnvalue + emit_op(s, OP_drop); + // stack is now: <empty> + pop_scope(s); + s->cur_func = s->cur_func->parent; + continue; + } + /* allow "static" field name */ + if (s->token.val == ';' || s->token.val == '=') { + is_static = FALSE; + name = JS_DupAtom(ctx, JS_ATOM_static); + prop_type = PROP_TYPE_IDENT; + } + } + if (is_static) + emit_op(s, OP_swap); + start_ptr = s->token.ptr; + if (prop_type < 0) { + prop_type = js_parse_property_name(s, &name, TRUE, FALSE, TRUE); + if (prop_type < 0) + goto fail; + } + is_private = prop_type & PROP_TYPE_PRIVATE; + prop_type &= ~PROP_TYPE_PRIVATE; + + if ((name == JS_ATOM_constructor && !is_static && + prop_type != PROP_TYPE_IDENT) || + (name == JS_ATOM_prototype && is_static) || + name == JS_ATOM_hash_constructor) { + js_parse_error(s, "invalid method name"); + goto fail; + } + if (prop_type == PROP_TYPE_GET || prop_type == PROP_TYPE_SET) { + BOOL is_set = prop_type - PROP_TYPE_GET; + JSFunctionDef *method_fd; + + if (is_private) { + int idx, var_kind, is_static1; + idx = find_private_class_field(ctx, fd, name, fd->scope_level); + if (idx >= 0) { + var_kind = fd->vars[idx].var_kind; + is_static1 = fd->vars[idx].is_static_private; + if (var_kind == JS_VAR_PRIVATE_FIELD || + var_kind == JS_VAR_PRIVATE_METHOD || + var_kind == JS_VAR_PRIVATE_GETTER_SETTER || + var_kind == (JS_VAR_PRIVATE_GETTER + is_set) || + (var_kind == (JS_VAR_PRIVATE_GETTER + 1 - is_set) && + is_static != is_static1)) { + goto private_field_already_defined; + } + fd->vars[idx].var_kind = JS_VAR_PRIVATE_GETTER_SETTER; + } else { + if (add_private_class_field(s, fd, name, + JS_VAR_PRIVATE_GETTER + is_set, is_static) < 0) + goto fail; + } + class_fields[is_static].need_brand = TRUE; + } + + if (js_parse_function_decl2(s, JS_PARSE_FUNC_GETTER + is_set, + JS_FUNC_NORMAL, JS_ATOM_NULL, + start_ptr, + s->token.line_num, + s->token.col_num, + JS_PARSE_EXPORT_NONE, &method_fd)) + goto fail; + if (is_private) { + method_fd->need_home_object = TRUE; /* needed for brand check */ + emit_op(s, OP_set_home_object); + /* XXX: missing function name */ + emit_op(s, OP_scope_put_var_init); + if (is_set) { + JSAtom setter_name; + int ret; + + setter_name = get_private_setter_name(ctx, name); + if (setter_name == JS_ATOM_NULL) + goto fail; + emit_atom(s, setter_name); + ret = add_private_class_field(s, fd, setter_name, + JS_VAR_PRIVATE_SETTER, is_static); + JS_FreeAtom(ctx, setter_name); + if (ret < 0) + goto fail; + } else { + emit_atom(s, name); + } + emit_u16(s, s->cur_func->scope_level); + } else { + if (name == JS_ATOM_NULL) { + emit_op(s, OP_define_method_computed); + } else { + emit_op(s, OP_define_method); + emit_atom(s, name); + } + emit_u8(s, OP_DEFINE_METHOD_GETTER + is_set); + } + } else if (prop_type == PROP_TYPE_IDENT && s->token.val != '(') { + ClassFieldsDef *cf = &class_fields[is_static]; + JSAtom field_var_name = JS_ATOM_NULL; + + /* class field */ + + /* XXX: spec: not consistent with method name checks */ + if (name == JS_ATOM_constructor || name == JS_ATOM_prototype) { + js_parse_error(s, "invalid field name"); + goto fail; + } + + if (is_private) { + if (find_private_class_field(ctx, fd, name, + fd->scope_level) >= 0) { + goto private_field_already_defined; + } + if (add_private_class_field(s, fd, name, + JS_VAR_PRIVATE_FIELD, is_static) < 0) + goto fail; + emit_op(s, OP_private_symbol); + emit_atom(s, name); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, name); + emit_u16(s, s->cur_func->scope_level); + } + + if (!cf->fields_init_fd) { + if (emit_class_init_start(s, cf)) + goto fail; + } + if (name == JS_ATOM_NULL ) { + /* save the computed field name into a variable */ + field_var_name = js_atom_concat_num(ctx, JS_ATOM_computed_field + is_static, cf->computed_fields_count); + if (field_var_name == JS_ATOM_NULL) + goto fail; + if (define_var(s, fd, field_var_name, JS_VAR_DEF_CONST) < 0) { + JS_FreeAtom(ctx, field_var_name); + goto fail; + } + emit_op(s, OP_to_propkey); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, field_var_name); + emit_u16(s, s->cur_func->scope_level); + } + s->cur_func = cf->fields_init_fd; + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + + // expose class name to static initializers + if (is_static && class_name != JS_ATOM_NULL) { + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, class_name); + emit_u16(s, s->cur_func->scope_level); + } + + if (name == JS_ATOM_NULL) { + emit_op(s, OP_scope_get_var); + emit_atom(s, field_var_name); + emit_u16(s, s->cur_func->scope_level); + cf->computed_fields_count++; + JS_FreeAtom(ctx, field_var_name); + } else if (is_private) { + emit_op(s, OP_scope_get_var); + emit_atom(s, name); + emit_u16(s, s->cur_func->scope_level); + } + + if (s->token.val == '=') { + if (next_token(s)) + goto fail; + if (js_parse_assign_expr(s)) + goto fail; + } else { + emit_op(s, OP_undefined); + } + if (is_private) { + set_object_name_computed(s); + emit_op(s, OP_define_private_field); + } else if (name == JS_ATOM_NULL) { + set_object_name_computed(s); + emit_op(s, OP_define_array_el); + emit_op(s, OP_drop); + } else { + set_object_name(s, name); + emit_op(s, OP_define_field); + emit_atom(s, name); + } + s->cur_func = s->cur_func->parent; + if (js_parse_expect_semi(s)) + goto fail; + } else { + JSParseFunctionEnum func_type; + JSFunctionKindEnum func_kind; + + func_type = JS_PARSE_FUNC_METHOD; + func_kind = JS_FUNC_NORMAL; + if (prop_type == PROP_TYPE_STAR) { + func_kind = JS_FUNC_GENERATOR; + } else if (prop_type == PROP_TYPE_ASYNC) { + func_kind = JS_FUNC_ASYNC; + } else if (prop_type == PROP_TYPE_ASYNC_STAR) { + func_kind = JS_FUNC_ASYNC_GENERATOR; + } else if (name == JS_ATOM_constructor && !is_static) { + if (ctor_fd) { + js_parse_error(s, "property constructor appears more than once"); + goto fail; + } + if (class_flags & JS_DEFINE_CLASS_HAS_HERITAGE) + func_type = JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR; + else + func_type = JS_PARSE_FUNC_CLASS_CONSTRUCTOR; + } + if (is_private) { + class_fields[is_static].need_brand = TRUE; + } + if (js_parse_function_decl2(s, func_type, func_kind, JS_ATOM_NULL, + start_ptr, + s->token.line_num, + s->token.col_num, + JS_PARSE_EXPORT_NONE, &method_fd)) + goto fail; + if (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR || + func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { + ctor_fd = method_fd; + } else if (is_private) { + method_fd->need_home_object = TRUE; /* needed for brand check */ + if (find_private_class_field(ctx, fd, name, + fd->scope_level) >= 0) { + private_field_already_defined: + js_parse_error(s, "private class field is already defined"); + goto fail; + } + if (add_private_class_field(s, fd, name, + JS_VAR_PRIVATE_METHOD, is_static) < 0) + goto fail; + emit_op(s, OP_set_home_object); + emit_op(s, OP_set_name); + emit_atom(s, name); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, name); + emit_u16(s, s->cur_func->scope_level); + } else { + if (name == JS_ATOM_NULL) { + emit_op(s, OP_define_method_computed); + } else { + emit_op(s, OP_define_method); + emit_atom(s, name); + } + emit_u8(s, OP_DEFINE_METHOD_METHOD); + } + } + if (is_static) + emit_op(s, OP_swap); + JS_FreeAtom(ctx, name); + name = JS_ATOM_NULL; + } + + if (s->token.val != '}') { + js_parse_error(s, "expecting '%c'", '}'); + goto fail; + } + + if (!ctor_fd) { + if (js_parse_class_default_ctor(s, class_flags & JS_DEFINE_CLASS_HAS_HERITAGE, &ctor_fd)) + goto fail; + } + /* patch the constant pool index for the constructor */ + put_u32(fd->byte_code.buf + ctor_cpool_offset, ctor_fd->parent_cpool_idx); + + /* store the class source code in the constructor. */ + js_free(ctx, ctor_fd->source); + ctor_fd->source_len = s->buf_ptr - class_start_ptr; + ctor_fd->source = js_strndup(ctx, (const char *)class_start_ptr, ctor_fd->source_len); + if (!ctor_fd->source) + goto fail; + + /* consume the '}' */ + if (next_token(s)) + goto fail; + + { + ClassFieldsDef *cf = &class_fields[0]; + int var_idx; + + if (cf->need_brand) { + /* add a private brand to the prototype */ + emit_op(s, OP_dup); + emit_op(s, OP_null); + emit_op(s, OP_swap); + emit_op(s, OP_add_brand); + + /* define the brand field in 'this' of the initializer */ + if (!cf->fields_init_fd) { + if (emit_class_init_start(s, cf)) + goto fail; + } + /* patch the start of the function to enable the + OP_add_brand_instance code */ + cf->fields_init_fd->byte_code.buf[cf->brand_push_pos] = OP_push_true; + } + + /* store the function to initialize the fields to that it can be + referenced by the constructor */ + var_idx = define_var(s, fd, JS_ATOM_class_fields_init, + JS_VAR_DEF_CONST); + if (var_idx < 0) + goto fail; + if (cf->fields_init_fd) { + emit_class_init_end(s, cf); + } else { + emit_op(s, OP_undefined); + } + emit_op(s, OP_scope_put_var_init); + emit_atom(s, JS_ATOM_class_fields_init); + emit_u16(s, s->cur_func->scope_level); + } + + /* drop the prototype */ + emit_op(s, OP_drop); + + if (class_fields[1].need_brand) { + /* add a private brand to the class */ + emit_op(s, OP_dup); + emit_op(s, OP_dup); + emit_op(s, OP_add_brand); + } + + /* initialize the static fields */ + if (class_fields[1].fields_init_fd != NULL) { + ClassFieldsDef *cf = &class_fields[1]; + emit_op(s, OP_dup); + emit_class_init_end(s, cf); + emit_op(s, OP_call_method); + emit_u16(s, 0); + emit_op(s, OP_drop); + } + + if (class_name != JS_ATOM_NULL) { + /* store the class name in the scoped class name variable (it + is independent from the class statement variable + definition) */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, class_name); + emit_u16(s, fd->scope_level); + } + pop_scope(s); + pop_scope(s); + + /* the class statements have a block level scope */ + if (class_var_name != JS_ATOM_NULL) { + if (define_var(s, fd, class_var_name, JS_VAR_DEF_LET) < 0) + goto fail; + emit_op(s, OP_scope_put_var_init); + emit_atom(s, class_var_name); + emit_u16(s, fd->scope_level); + } else { + if (class_name == JS_ATOM_NULL) { + /* cannot use OP_set_name because the name of the class + must be defined before the static initializers are + executed */ + emit_op(s, OP_set_class_name); + emit_u32(s, fd->last_opcode_pos + 1 - define_class_offset); + } + } + + if (export_flag != JS_PARSE_EXPORT_NONE) { + if (!add_export_entry(s, fd->module, + class_var_name, + export_flag == JS_PARSE_EXPORT_NAMED ? class_var_name : JS_ATOM_default, + JS_EXPORT_TYPE_LOCAL)) + goto fail; + } + + JS_FreeAtom(ctx, class_name); + JS_FreeAtom(ctx, class_var_name); + fd->is_strict_mode = is_strict_mode; + return 0; + fail: + JS_FreeAtom(ctx, name); + JS_FreeAtom(ctx, class_name); + JS_FreeAtom(ctx, class_var_name); + fd->is_strict_mode = is_strict_mode; + return -1; +} + +static __exception int js_parse_array_literal(JSParseState *s) +{ + uint32_t idx; + BOOL need_length; + + if (next_token(s)) + return -1; + /* small regular arrays are created on the stack */ + idx = 0; + while (s->token.val != ']' && idx < 32) { + if (s->token.val == ',' || s->token.val == TOK_ELLIPSIS) + break; + if (js_parse_assign_expr(s)) + return -1; + idx++; + /* accept trailing comma */ + if (s->token.val == ',') { + if (next_token(s)) + return -1; + } else + if (s->token.val != ']') + goto done; + } + emit_op(s, OP_array_from); + emit_u16(s, idx); + + /* larger arrays and holes are handled with explicit indices */ + need_length = FALSE; + while (s->token.val != ']' && idx < 0x7fffffff) { + if (s->token.val == TOK_ELLIPSIS) + break; + need_length = TRUE; + if (s->token.val != ',') { + if (js_parse_assign_expr(s)) + return -1; + emit_op(s, OP_define_field); + emit_u32(s, __JS_AtomFromUInt32(idx)); + need_length = FALSE; + } + idx++; + /* accept trailing comma */ + if (s->token.val == ',') { + if (next_token(s)) + return -1; + } + } + if (s->token.val == ']') { + if (need_length) { + /* Set the length: Cannot use OP_define_field because + length is not configurable */ + emit_op(s, OP_dup); + emit_op(s, OP_push_i32); + emit_u32(s, idx); + emit_op(s, OP_put_field); + emit_atom(s, JS_ATOM_length); + emit_ic(s, JS_ATOM_length); + } + goto done; + } + + /* huge arrays and spread elements require a dynamic index on the stack */ + emit_op(s, OP_push_i32); + emit_u32(s, idx); + + /* stack has array, index */ + while (s->token.val != ']') { + if (s->token.val == TOK_ELLIPSIS) { + if (next_token(s)) + return -1; + if (js_parse_assign_expr(s)) + return -1; + emit_op(s, OP_append); + } else { + need_length = TRUE; + if (s->token.val != ',') { + if (js_parse_assign_expr(s)) + return -1; + /* a idx val */ + emit_op(s, OP_define_array_el); + need_length = FALSE; + } + emit_op(s, OP_inc); + } + if (s->token.val != ',') + break; + if (next_token(s)) + return -1; + } + if (need_length) { + /* Set the length: cannot use OP_define_field because + length is not configurable */ + emit_op(s, OP_dup1); /* array length - array array length */ + emit_op(s, OP_put_field); + emit_atom(s, JS_ATOM_length); + emit_ic(s, JS_ATOM_length); + } else { + emit_op(s, OP_drop); /* array length - array */ + } +done: + return js_parse_expect(s, ']'); +} + +/* XXX: remove */ +static BOOL has_with_scope(JSFunctionDef *s, int scope_level) +{ + /* check if scope chain contains a with statement */ + while (s) { + int scope_idx = s->scopes[scope_level].first; + while (scope_idx >= 0) { + JSVarDef *vd = &s->vars[scope_idx]; + + if (vd->var_name == JS_ATOM__with_) + return TRUE; + scope_idx = vd->scope_next; + } + /* check parent scopes */ + scope_level = s->parent_scope_level; + s = s->parent; + } + return FALSE; +} + +static __exception int get_lvalue(JSParseState *s, int *popcode, int *pscope, + JSAtom *pname, int *plabel, int *pdepth, BOOL keep, + int tok) +{ + JSFunctionDef *fd; + int opcode, scope, label, depth; + JSAtom name; + + /* we check the last opcode to get the lvalue type */ + fd = s->cur_func; + scope = 0; + name = JS_ATOM_NULL; + label = -1; + depth = 0; + switch(opcode = get_prev_opcode(fd)) { + case OP_scope_get_var: + name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); + if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) && + fd->is_strict_mode) { + return js_parse_error(s, "invalid lvalue in strict mode"); + } + if (name == JS_ATOM_this || name == JS_ATOM_new_target) + goto invalid_lvalue; + depth = 2; /* will generate OP_get_ref_value */ + break; + case OP_get_field: + name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + depth = 1; + break; + case OP_scope_get_private_field: + name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); + depth = 1; + break; + case OP_get_array_el: + depth = 2; + break; + case OP_get_super_value: + depth = 3; + break; + default: + invalid_lvalue: + if (tok == TOK_FOR) { + return js_parse_error(s, "invalid for in/of left hand-side"); + } else if (tok == TOK_INC || tok == TOK_DEC) { + return js_parse_error(s, "invalid increment/decrement operand"); + } else if (tok == '[' || tok == '{') { + return js_parse_error(s, "invalid destructuring target"); + } else { + return js_parse_error(s, "invalid assignment left-hand side"); + } + } + /* remove the last opcode */ + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + + if (keep) { + /* get the value but keep the object/fields on the stack */ + switch(opcode) { + case OP_scope_get_var: + label = new_label(s); + emit_op(s, OP_scope_make_ref); + emit_atom(s, name); + emit_u32(s, label); + emit_u16(s, scope); + update_label(fd, label, 1); + emit_op(s, OP_get_ref_value); + opcode = OP_get_ref_value; + break; + case OP_get_field: + emit_op(s, OP_get_field2); + emit_atom(s, name); + emit_ic(s, name); + break; + case OP_scope_get_private_field: + emit_op(s, OP_scope_get_private_field2); + emit_atom(s, name); + emit_u16(s, scope); + break; + case OP_get_array_el: + /* XXX: replace by a single opcode ? */ + emit_op(s, OP_to_propkey2); + emit_op(s, OP_dup2); + emit_op(s, OP_get_array_el); + break; + case OP_get_super_value: + emit_op(s, OP_to_propkey); + emit_op(s, OP_dup3); + emit_op(s, OP_get_super_value); + break; + default: + abort(); + } + } else { + switch(opcode) { + case OP_scope_get_var: + label = new_label(s); + emit_op(s, OP_scope_make_ref); + emit_atom(s, name); + emit_u32(s, label); + emit_u16(s, scope); + update_label(fd, label, 1); + opcode = OP_get_ref_value; + break; + case OP_get_array_el: + emit_op(s, OP_to_propkey2); + break; + case OP_get_super_value: + emit_op(s, OP_to_propkey); + break; + } + } + + *popcode = opcode; + *pscope = scope; + /* name has refcount for OP_get_field and OP_get_ref_value, + and JS_ATOM_NULL for other opcodes */ + *pname = name; + *plabel = label; + if (pdepth) + *pdepth = depth; + return 0; +} + +typedef enum { + PUT_LVALUE_NOKEEP, /* [depth] v -> */ + PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently + just disable optimizations) */ + PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ + PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ + PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ +} PutLValueEnum; + +/* name has a live reference. 'is_let' is only used with opcode = + OP_scope_get_var which is never generated by get_lvalue(). */ +static void put_lvalue(JSParseState *s, int opcode, int scope, + JSAtom name, int label, PutLValueEnum special, + BOOL is_let) +{ + switch(opcode) { + case OP_get_field: + case OP_scope_get_private_field: + /* depth = 1 */ + switch(special) { + case PUT_LVALUE_NOKEEP: + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert2); /* obj v -> v obj v */ + break; + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm3); /* obj v0 v -> v0 obj v */ + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_swap); + break; + default: + abort(); + } + break; + case OP_get_array_el: + case OP_get_ref_value: + /* depth = 2 */ + if (opcode == OP_get_ref_value) { + JS_FreeAtom(s->ctx, name); + emit_label(s, label); + } + switch(special) { + case PUT_LVALUE_NOKEEP: + emit_op(s, OP_nop); /* will trigger optimization */ + break; + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert3); /* obj prop v -> v obj prop v */ + break; + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_rot3l); + break; + default: + abort(); + } + break; + case OP_get_super_value: + /* depth = 3 */ + switch(special) { + case PUT_LVALUE_NOKEEP: + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: + emit_op(s, OP_insert4); /* this obj prop v -> v this obj prop v */ + break; + case PUT_LVALUE_KEEP_SECOND: + emit_op(s, OP_perm5); /* this obj prop v0 v -> v0 this obj prop v */ + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op(s, OP_rot4l); + break; + default: + abort(); + } + break; + default: + break; + } + + switch(opcode) { + case OP_scope_get_var: /* val -- */ + assert(special == PUT_LVALUE_NOKEEP || + special == PUT_LVALUE_NOKEEP_DEPTH); + emit_op(s, is_let ? OP_scope_put_var_init : OP_scope_put_var); + emit_u32(s, name); /* has refcount */ + emit_u16(s, scope); + break; + case OP_get_field: + emit_op(s, OP_put_field); + emit_u32(s, name); /* name has refcount */ + emit_ic(s, name); + break; + case OP_scope_get_private_field: + emit_op(s, OP_scope_put_private_field); + emit_u32(s, name); /* name has refcount */ + emit_u16(s, scope); + break; + case OP_get_array_el: + emit_op(s, OP_put_array_el); + break; + case OP_get_ref_value: + emit_op(s, OP_put_ref_value); + break; + case OP_get_super_value: + emit_op(s, OP_put_super_value); + break; + default: + abort(); + } +} + +static __exception int js_parse_expr_paren(JSParseState *s) +{ + if (js_parse_expect(s, '(')) + return -1; + if (js_parse_expr(s)) + return -1; + if (js_parse_expect(s, ')')) + return -1; + return 0; +} + +static int js_unsupported_keyword(JSParseState *s, JSAtom atom) +{ + char buf[ATOM_GET_STR_BUF_SIZE]; + return js_parse_error(s, "unsupported keyword: %s", + JS_AtomGetStr(s->ctx, buf, sizeof(buf), atom)); +} + +static __exception int js_define_var(JSParseState *s, JSAtom name, int tok) +{ + JSFunctionDef *fd = s->cur_func; + JSVarDefEnum var_def_type; + + if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) { + return js_parse_error(s, "yield is a reserved identifier"); + } + if ((name == JS_ATOM_arguments || name == JS_ATOM_eval) + && fd->is_strict_mode) { + return js_parse_error(s, "invalid variable name in strict mode"); + } + if (tok == TOK_LET || tok == TOK_CONST) { + if (name == JS_ATOM_let) + return js_parse_error(s, "invalid lexical variable name 'let'"); + // |undefined| is allowed as an identifier except at the global + // scope of a classic script; sloppy or strict doesn't matter + if (name == JS_ATOM_undefined + && fd->scope_level == 1 + && fd->is_global_var + && !fd->module) { + return js_parse_error(s, "'undefined' already declared"); + } + } + switch(tok) { + case TOK_LET: + var_def_type = JS_VAR_DEF_LET; + break; + case TOK_CONST: + var_def_type = JS_VAR_DEF_CONST; + break; + case TOK_VAR: + var_def_type = JS_VAR_DEF_VAR; + break; + case TOK_CATCH: + var_def_type = JS_VAR_DEF_CATCH; + break; + default: + abort(); + } + if (define_var(s, fd, name, var_def_type) < 0) + return -1; + return 0; +} + +static void js_emit_spread_code(JSParseState *s, int depth) +{ + int label_rest_next, label_rest_done; + + /* XXX: could check if enum object is an actual array and optimize + slice extraction. enumeration record and target array are in a + different order from OP_append case. */ + /* enum_rec xxx -- enum_rec xxx array 0 */ + emit_op(s, OP_array_from); + emit_u16(s, 0); + emit_op(s, OP_push_i32); + emit_u32(s, 0); + emit_label(s, label_rest_next = new_label(s)); + emit_op(s, OP_for_of_next); + emit_u8(s, 2 + depth); + label_rest_done = emit_goto(s, OP_if_true, -1); + /* array idx val -- array idx */ + emit_op(s, OP_define_array_el); + emit_op(s, OP_inc); + emit_goto(s, OP_goto, label_rest_next); + emit_label(s, label_rest_done); + /* enum_rec xxx array idx undef -- enum_rec xxx array */ + emit_op(s, OP_drop); + emit_op(s, OP_drop); +} + +static int js_parse_check_duplicate_parameter(JSParseState *s, JSAtom name) +{ + /* Check for duplicate parameter names */ + JSFunctionDef *fd = s->cur_func; + int i; + for (i = 0; i < fd->arg_count; i++) { + if (fd->args[i].var_name == name) + goto duplicate; + } + for (i = 0; i < fd->var_count; i++) { + if (fd->vars[i].var_name == name) + goto duplicate; + } + return 0; + +duplicate: + return js_parse_error(s, "Duplicate parameter name not allowed in this context"); +} + +static JSAtom js_parse_destructuring_var(JSParseState *s, int tok, int is_arg) +{ + JSAtom name; + + if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) + || (s->cur_func->is_strict_mode && + (s->token.u.ident.atom == JS_ATOM_eval || s->token.u.ident.atom == JS_ATOM_arguments))) { + js_parse_error(s, "invalid destructuring target"); + return JS_ATOM_NULL; + } + name = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (is_arg && js_parse_check_duplicate_parameter(s, name)) + goto fail; + if (next_token(s)) + goto fail; + + return name; +fail: + JS_FreeAtom(s->ctx, name); + return JS_ATOM_NULL; +} + +/* Return -1 if error, 0 if no initializer, 1 if an initializer is + present at the top level. */ +static int js_parse_destructuring_element(JSParseState *s, int tok, int is_arg, + int hasval, int has_ellipsis, + BOOL allow_initializer) +{ + int label_parse, label_assign, label_done, label_lvalue, depth_lvalue; + int start_addr, assign_addr; + JSAtom prop_name, var_name; + int opcode, scope, tok1, skip_bits; + BOOL has_initializer; + + label_lvalue = -1; + + if (has_ellipsis < 0) { + /* pre-parse destructuration target for spread detection */ + js_parse_skip_parens_token(s, &skip_bits, FALSE); + has_ellipsis = skip_bits & SKIP_HAS_ELLIPSIS; + } + + label_parse = new_label(s); + label_assign = new_label(s); + + start_addr = s->cur_func->byte_code.size; + if (hasval) { + /* consume value from the stack */ + emit_op(s, OP_dup); + emit_op(s, OP_undefined); + emit_op(s, OP_strict_eq); + emit_goto(s, OP_if_true, label_parse); + emit_label(s, label_assign); + } else { + emit_goto(s, OP_goto, label_parse); + emit_label(s, label_assign); + /* leave value on the stack */ + emit_op(s, OP_dup); + } + assign_addr = s->cur_func->byte_code.size; + if (s->token.val == '{') { + if (next_token(s)) + return -1; + /* throw an exception if the value cannot be converted to an object */ + emit_op(s, OP_to_object); + if (has_ellipsis) { + /* add excludeList on stack just below src object */ + emit_op(s, OP_object); + emit_op(s, OP_swap); + } + while (s->token.val != '}') { + int prop_type; + if (s->token.val == TOK_ELLIPSIS) { + if (!has_ellipsis) { + JS_ThrowInternalError(s->ctx, "unexpected ellipsis token"); + return -1; + } + if (next_token(s)) + return -1; + if (tok) { + var_name = js_parse_destructuring_var(s, tok, is_arg); + if (var_name == JS_ATOM_NULL) + return -1; + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + } else { + if (js_parse_left_hand_side_expr(s)) + return -1; + + if (get_lvalue(s, &opcode, &scope, &var_name, + &label_lvalue, &depth_lvalue, FALSE, '{')) + return -1; + } + if (s->token.val != '}') { + js_parse_error(s, "assignment rest property must be last"); + goto var_error; + } + emit_op(s, OP_object); /* target */ + emit_op(s, OP_copy_data_properties); + emit_u8(s, 0 | ((depth_lvalue + 1) << 2) | ((depth_lvalue + 2) << 5)); + goto set_val; + } + prop_type = js_parse_property_name(s, &prop_name, FALSE, TRUE, FALSE); + if (prop_type < 0) + return -1; + var_name = JS_ATOM_NULL; + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + if (prop_type == PROP_TYPE_IDENT) { + if (next_token(s)) + goto prop_error; + if ((s->token.val == '[' || s->token.val == '{') + && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' || + tok1 == '=' || tok1 == '}')) { + if (prop_name == JS_ATOM_NULL) { + /* computed property name on stack */ + if (has_ellipsis) { + /* define the property in excludeList */ + emit_op(s, OP_to_propkey); /* avoid calling ToString twice */ + emit_op(s, OP_perm3); /* TOS: src excludeList prop */ + emit_op(s, OP_null); /* TOS: src excludeList prop null */ + emit_op(s, OP_define_array_el); /* TOS: src excludeList prop */ + emit_op(s, OP_perm3); /* TOS: excludeList src prop */ + } + /* get the computed property from the source object */ + emit_op(s, OP_get_array_el2); + } else { + /* named property */ + if (has_ellipsis) { + /* define the property in excludeList */ + emit_op(s, OP_swap); /* TOS: src excludeList */ + emit_op(s, OP_null); /* TOS: src excludeList null */ + emit_op(s, OP_define_field); /* TOS: src excludeList */ + emit_atom(s, prop_name); + emit_op(s, OP_swap); /* TOS: excludeList src */ + } + /* get the named property from the source object */ + emit_op(s, OP_get_field2); + emit_u32(s, prop_name); + emit_ic(s, prop_name); + } + if (js_parse_destructuring_element(s, tok, is_arg, TRUE, -1, TRUE) < 0) + return -1; + if (s->token.val == '}') + break; + /* accept a trailing comma before the '}' */ + if (js_parse_expect(s, ',')) + return -1; + continue; + } + if (prop_name == JS_ATOM_NULL) { + emit_op(s, OP_to_propkey2); + if (has_ellipsis) { + /* define the property in excludeList */ + emit_op(s, OP_perm3); + emit_op(s, OP_null); + emit_op(s, OP_define_array_el); + emit_op(s, OP_perm3); + } + /* source prop -- source source prop */ + emit_op(s, OP_dup1); + } else { + if (has_ellipsis) { + /* define the property in excludeList */ + emit_op(s, OP_swap); + emit_op(s, OP_null); + emit_op(s, OP_define_field); + emit_atom(s, prop_name); + emit_op(s, OP_swap); + } + /* source -- source source */ + emit_op(s, OP_dup); + } + if (tok) { + var_name = js_parse_destructuring_var(s, tok, is_arg); + if (var_name == JS_ATOM_NULL) + goto prop_error; + } else { + if (js_parse_left_hand_side_expr(s)) + goto prop_error; + lvalue: + if (get_lvalue(s, &opcode, &scope, &var_name, + &label_lvalue, &depth_lvalue, FALSE, '{')) + goto prop_error; + /* swap ref and lvalue object if any */ + if (prop_name == JS_ATOM_NULL) { + switch(depth_lvalue) { + case 1: + /* source prop x -> x source prop */ + emit_op(s, OP_rot3r); + break; + case 2: + /* source prop x y -> x y source prop */ + emit_op(s, OP_swap2); /* t p2 s p1 */ + break; + case 3: + /* source prop x y z -> x y z source prop */ + emit_op(s, OP_rot5l); + emit_op(s, OP_rot5l); + break; + } + } else { + switch(depth_lvalue) { + case 1: + /* source x -> x source */ + emit_op(s, OP_swap); + break; + case 2: + /* source x y -> x y source */ + emit_op(s, OP_rot3l); + break; + case 3: + /* source x y z -> x y z source */ + emit_op(s, OP_rot4l); + break; + } + } + } + if (prop_name == JS_ATOM_NULL) { + /* computed property name on stack */ + /* XXX: should have OP_get_array_el2x with depth */ + /* source prop -- val */ + emit_op(s, OP_get_array_el); + } else { + /* named property */ + /* XXX: should have OP_get_field2x with depth */ + /* source -- val */ + emit_op(s, OP_get_field); + emit_u32(s, prop_name); + emit_ic(s, prop_name); + } + } else { + /* prop_type = PROP_TYPE_VAR, cannot be a computed property */ + if (is_arg && js_parse_check_duplicate_parameter(s, prop_name)) + goto prop_error; + if (s->cur_func->is_strict_mode && + (prop_name == JS_ATOM_eval || prop_name == JS_ATOM_arguments)) { + js_parse_error(s, "invalid destructuring target"); + goto prop_error; + } + if (has_ellipsis) { + /* define the property in excludeList */ + emit_op(s, OP_swap); + emit_op(s, OP_null); + emit_op(s, OP_define_field); + emit_atom(s, prop_name); + emit_op(s, OP_swap); + } + if (!tok || tok == TOK_VAR) { + /* generate reference */ + /* source -- source source */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_get_var); + emit_atom(s, prop_name); + emit_u16(s, s->cur_func->scope_level); + goto lvalue; + } + var_name = JS_DupAtom(s->ctx, prop_name); + /* source -- source val */ + emit_op(s, OP_get_field2); + emit_u32(s, prop_name); + emit_ic(s, prop_name); + } + set_val: + if (tok) { + if (js_define_var(s, var_name, tok)) + goto var_error; + scope = s->cur_func->scope_level; + } + if (s->token.val == '=') { /* handle optional default value */ + int label_hasval; + emit_op(s, OP_dup); + emit_op(s, OP_undefined); + emit_op(s, OP_strict_eq); + label_hasval = emit_goto(s, OP_if_false, -1); + if (next_token(s)) + goto var_error; + emit_op(s, OP_drop); + if (js_parse_assign_expr(s)) + goto var_error; + if (opcode == OP_scope_get_var || opcode == OP_get_ref_value) + set_object_name(s, var_name); + emit_label(s, label_hasval); + } + /* store value into lvalue object */ + put_lvalue(s, opcode, scope, var_name, label_lvalue, + PUT_LVALUE_NOKEEP_DEPTH, + (tok == TOK_CONST || tok == TOK_LET)); + if (s->token.val == '}') + break; + /* accept a trailing comma before the '}' */ + if (js_parse_expect(s, ',')) + return -1; + } + /* drop the source object */ + emit_op(s, OP_drop); + if (has_ellipsis) { + emit_op(s, OP_drop); /* pop excludeList */ + } + if (next_token(s)) + return -1; + } else if (s->token.val == '[') { + BOOL has_spread; + int enum_depth; + BlockEnv block_env; + + if (next_token(s)) + return -1; + /* the block environment is only needed in generators in case + 'yield' triggers a 'return' */ + push_break_entry(s->cur_func, &block_env, + JS_ATOM_NULL, -1, -1, 2); + block_env.has_iterator = TRUE; + emit_op(s, OP_for_of_start); + has_spread = FALSE; + while (s->token.val != ']') { + /* get the next value */ + if (s->token.val == TOK_ELLIPSIS) { + if (next_token(s)) + return -1; + if (s->token.val == ',' || s->token.val == ']') + return js_parse_error(s, "missing binding pattern..."); + has_spread = TRUE; + } + if (s->token.val == ',') { + /* do nothing, skip the value, has_spread is false */ + emit_op(s, OP_for_of_next); + emit_u8(s, 0); + emit_op(s, OP_drop); + emit_op(s, OP_drop); + } else if ((s->token.val == '[' || s->token.val == '{') + && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == ',' || + tok1 == '=' || tok1 == ']')) { + if (has_spread) { + if (tok1 == '=') + return js_parse_error(s, "rest element cannot have a default value"); + js_emit_spread_code(s, 0); + } else { + emit_op(s, OP_for_of_next); + emit_u8(s, 0); + emit_op(s, OP_drop); + } + if (js_parse_destructuring_element(s, tok, is_arg, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + return -1; + } else { + var_name = JS_ATOM_NULL; + enum_depth = 0; + if (tok) { + var_name = js_parse_destructuring_var(s, tok, is_arg); + if (var_name == JS_ATOM_NULL) + goto var_error; + if (js_define_var(s, var_name, tok)) + goto var_error; + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + } else { + if (js_parse_left_hand_side_expr(s)) + return -1; + if (get_lvalue(s, &opcode, &scope, &var_name, + &label_lvalue, &enum_depth, FALSE, '[')) { + return -1; + } + } + if (has_spread) { + js_emit_spread_code(s, enum_depth); + } else { + emit_op(s, OP_for_of_next); + emit_u8(s, enum_depth); + emit_op(s, OP_drop); + } + if (s->token.val == '=' && !has_spread) { + /* handle optional default value */ + int label_hasval; + emit_op(s, OP_dup); + emit_op(s, OP_undefined); + emit_op(s, OP_strict_eq); + label_hasval = emit_goto(s, OP_if_false, -1); + if (next_token(s)) + goto var_error; + emit_op(s, OP_drop); + if (js_parse_assign_expr(s)) + goto var_error; + if (opcode == OP_scope_get_var || opcode == OP_get_ref_value) + set_object_name(s, var_name); + emit_label(s, label_hasval); + } + /* store value into lvalue object */ + put_lvalue(s, opcode, scope, var_name, + label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, + (tok == TOK_CONST || tok == TOK_LET)); + } + if (s->token.val == ']') + break; + if (has_spread) + return js_parse_error(s, "rest element must be the last one"); + /* accept a trailing comma before the ']' */ + if (js_parse_expect(s, ',')) + return -1; + } + /* close iterator object: + if completed, enum_obj has been replaced by undefined */ + emit_op(s, OP_iterator_close); + pop_break_entry(s->cur_func); + if (next_token(s)) + return -1; + } else { + return js_parse_error(s, "invalid assignment syntax"); + } + if (s->token.val == '=' && allow_initializer) { + label_done = emit_goto(s, OP_goto, -1); + if (next_token(s)) + return -1; + emit_label(s, label_parse); + if (hasval) + emit_op(s, OP_drop); + if (js_parse_assign_expr(s)) + return -1; + emit_goto(s, OP_goto, label_assign); + emit_label(s, label_done); + has_initializer = TRUE; + } else { + /* normally hasval is true except if + js_parse_skip_parens_token() was wrong in the parsing */ + // assert(hasval); + if (!hasval) { + js_parse_error(s, "too complicated destructuring expression"); + return -1; + } + /* remove test and decrement label ref count */ + memset(s->cur_func->byte_code.buf + start_addr, OP_nop, + assign_addr - start_addr); + s->cur_func->label_slots[label_parse].ref_count--; + has_initializer = FALSE; + } + return has_initializer; + + prop_error: + JS_FreeAtom(s->ctx, prop_name); + var_error: + JS_FreeAtom(s->ctx, var_name); + return -1; +} + +typedef enum FuncCallType { + FUNC_CALL_NORMAL, + FUNC_CALL_NEW, + FUNC_CALL_SUPER_CTOR, + FUNC_CALL_TEMPLATE, +} FuncCallType; + +static void optional_chain_test(JSParseState *s, int *poptional_chaining_label, + int drop_count) +{ + int label_next, i; + if (*poptional_chaining_label < 0) + *poptional_chaining_label = new_label(s); + /* XXX: could be more efficient with a specific opcode */ + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined_or_null); + label_next = emit_goto(s, OP_if_false, -1); + for(i = 0; i < drop_count; i++) + emit_op(s, OP_drop); + emit_op(s, OP_undefined); + emit_goto(s, OP_goto, *poptional_chaining_label); + emit_label(s, label_next); +} + +/* allowed parse_flags: PF_POSTFIX_CALL */ +static __exception int js_parse_postfix_expr(JSParseState *s, int parse_flags) +{ + FuncCallType call_type; + int optional_chaining_label; + BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; + + call_type = FUNC_CALL_NORMAL; + switch(s->token.val) { + case TOK_NUMBER: + { + JSValue val; + val = s->token.u.num.val; + + if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { + emit_op(s, OP_push_i32); + emit_u32(s, JS_VALUE_GET_INT(val)); + } else { + if (emit_push_const(s, val, 0) < 0) + return -1; + } + } + if (next_token(s)) + return -1; + break; + case TOK_TEMPLATE: + if (js_parse_template(s, 0, NULL)) + return -1; + break; + case TOK_STRING: + if (emit_push_const(s, s->token.u.str.str, 1)) + return -1; + if (next_token(s)) + return -1; + break; + + case TOK_DIV_ASSIGN: + s->buf_ptr -= 2; + goto parse_regexp; + case '/': + s->buf_ptr--; + parse_regexp: + { + JSValue str; + int ret, backtrace_flags; + if (!s->ctx->compile_regexp) + return js_parse_error(s, "RegExp are not supported"); + /* the previous token is '/' or '/=', so no need to free */ + if (js_parse_regexp(s)) + return -1; + ret = emit_push_const(s, s->token.u.regexp.body, 0); + str = s->ctx->compile_regexp(s->ctx, s->token.u.regexp.body, + s->token.u.regexp.flags); + if (JS_IsException(str)) { + /* add the line number info */ + backtrace_flags = 0; + if (s->cur_func && s->cur_func->backtrace_barrier) + backtrace_flags = JS_BACKTRACE_FLAG_SINGLE_LEVEL; + build_backtrace(s->ctx, s->ctx->rt->current_exception, JS_UNDEFINED, + s->filename, + s->token.line_num, + s->token.col_num, + backtrace_flags); + return -1; + } + ret = emit_push_const(s, str, 0); + JS_FreeValue(s->ctx, str); + if (ret) + return -1; + /* we use a specific opcode to be sure the correct + function is called (otherwise the bytecode would have + to be verified by the RegExp constructor) */ + emit_op(s, OP_regexp); + if (next_token(s)) + return -1; + } + break; + case '(': + if (js_parse_expr_paren(s)) + return -1; + break; + case TOK_FUNCTION: + if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + s->token.line_num, + s->token.col_num)) + return -1; + break; + case TOK_CLASS: + if (js_parse_class(s, TRUE, JS_PARSE_EXPORT_NONE)) + return -1; + break; + case TOK_NULL: + if (next_token(s)) + return -1; + emit_op(s, OP_null); + break; + case TOK_THIS: + if (next_token(s)) + return -1; + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + break; + case TOK_FALSE: + if (next_token(s)) + return -1; + emit_op(s, OP_push_false); + break; + case TOK_TRUE: + if (next_token(s)) + return -1; + emit_op(s, OP_push_true); + break; + case TOK_IDENT: + { + JSAtom name; + if (s->token.u.ident.is_reserved) { + return js_parse_error_reserved_identifier(s); + } + if (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) != '\n') { + const uint8_t *source_ptr; + int source_line_num; + int source_col_num; + + source_ptr = s->token.ptr; + source_line_num = s->token.line_num; + source_col_num = s->token.col_num; + if (next_token(s)) + return -1; + if (s->token.val == TOK_FUNCTION) { + if (js_parse_function_decl(s, JS_PARSE_FUNC_EXPR, + JS_FUNC_ASYNC, JS_ATOM_NULL, + source_ptr, + source_line_num, + source_col_num)) + return -1; + } else { + name = JS_DupAtom(s->ctx, JS_ATOM_async); + goto do_get_var; + } + } else { + if (s->token.u.ident.atom == JS_ATOM_arguments && + !s->cur_func->arguments_allowed) { + js_parse_error(s, "'arguments' identifier is not allowed in class field initializer"); + return -1; + } + name = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) { /* update line number before emitting code */ + JS_FreeAtom(s->ctx, name); + return -1; + } + do_get_var: + emit_op(s, OP_scope_get_var); + emit_u32(s, name); + emit_u16(s, s->cur_func->scope_level); + } + } + break; + case '{': + case '[': + { + int skip_bits; + if (js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { + if (js_parse_destructuring_element(s, 0, 0, FALSE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + return -1; + } else { + if (s->token.val == '{') { + if (js_parse_object_literal(s)) + return -1; + } else { + if (js_parse_array_literal(s)) + return -1; + } + } + } + break; + case TOK_NEW: + if (next_token(s)) + return -1; + if (s->token.val == '.') { + if (next_token(s)) + return -1; + if (!token_is_pseudo_keyword(s, JS_ATOM_target)) + return js_parse_error(s, "expecting target"); + if (!s->cur_func->new_target_allowed) + return js_parse_error(s, "new.target only allowed within functions"); + if (next_token(s)) + return -1; + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_new_target); + emit_u16(s, 0); + } else { + emit_source_loc(s); + if (js_parse_postfix_expr(s, 0)) + return -1; + accept_lparen = TRUE; + if (s->token.val != '(') { + /* new operator on an object */ + emit_op(s, OP_dup); + emit_op(s, OP_call_constructor); + emit_u16(s, 0); + } else { + call_type = FUNC_CALL_NEW; + } + } + break; + case TOK_SUPER: + if (next_token(s)) + return -1; + if (s->token.val == '(') { + if (!s->cur_func->super_call_allowed) + return js_parse_error(s, "super() is only valid in a derived class constructor"); + call_type = FUNC_CALL_SUPER_CTOR; + } else if (s->token.val == '.' || s->token.val == '[') { + if (!s->cur_func->super_allowed) + return js_parse_error(s, "'super' is only valid in a method"); + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_home_object); + emit_u16(s, 0); + emit_op(s, OP_get_super); + } else { + return js_parse_error(s, "invalid use of 'super'"); + } + break; + case TOK_IMPORT: + if (next_token(s)) + return -1; + if (s->token.val == '.') { + if (next_token(s)) + return -1; + if (!token_is_pseudo_keyword(s, JS_ATOM_meta)) + return js_parse_error(s, "meta expected"); + if (!s->is_module) + return js_parse_error(s, "import.meta only valid in module code"); + if (next_token(s)) + return -1; + emit_op(s, OP_special_object); + emit_u8(s, OP_SPECIAL_OBJECT_IMPORT_META); + } else { + if (js_parse_expect(s, '(')) + return -1; + if (!accept_lparen) + return js_parse_error(s, "invalid use of 'import()'"); + if (js_parse_assign_expr(s)) + return -1; + if (js_parse_expect(s, ')')) + return -1; + emit_op(s, OP_import); + } + break; + default: + return js_parse_error(s, "unexpected token in expression: '%.*s'", + (int)(s->buf_ptr - s->token.ptr), s->token.ptr); + } + + optional_chaining_label = -1; + for(;;) { + JSFunctionDef *fd = s->cur_func; + BOOL has_optional_chain = FALSE; + + if (s->token.val == TOK_QUESTION_MARK_DOT) { + /* optional chaining */ + if (next_token(s)) + return -1; + has_optional_chain = TRUE; + if (s->token.val == '(' && accept_lparen) { + goto parse_func_call; + } else if (s->token.val == '[') { + goto parse_array_access; + } else { + goto parse_property; + } + } else if (s->token.val == TOK_TEMPLATE && + call_type == FUNC_CALL_NORMAL) { + if (optional_chaining_label >= 0) { + return js_parse_error(s, "template literal cannot appear in an optional chain"); + } + call_type = FUNC_CALL_TEMPLATE; + goto parse_func_call2; + } else if (s->token.val == '(' && accept_lparen) { + int opcode, arg_count, drop_count; + + /* function call */ + parse_func_call: + if (next_token(s)) + return -1; + + if (call_type == FUNC_CALL_NORMAL) { + parse_func_call2: + emit_source_loc(s); + switch(opcode = get_prev_opcode(fd)) { + case OP_get_field: + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; + drop_count = 2; + break; + case OP_get_field_opt_chain: + { + int opt_chain_label, next_label; + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 4 + 1); + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; + fd->byte_code.size = fd->last_opcode_pos + 1 + 4; + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op(s, OP_undefined); + emit_label(s, next_label); + drop_count = 2; + opcode = OP_get_field; + } + break; + case OP_scope_get_private_field: + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_private_field2; + drop_count = 2; + break; + case OP_get_array_el: + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; + drop_count = 2; + break; + case OP_get_array_el_opt_chain: + { + int opt_chain_label, next_label; + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 1); + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; + fd->byte_code.size = fd->last_opcode_pos + 1; + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op(s, OP_undefined); + emit_label(s, next_label); + drop_count = 2; + opcode = OP_get_array_el; + } + break; + case OP_scope_get_var: + { + JSAtom name; + int scope; + name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + scope = get_u16(fd->byte_code.buf + fd->last_opcode_pos + 5); + if (name == JS_ATOM_eval && call_type == FUNC_CALL_NORMAL && !has_optional_chain) { + /* direct 'eval' */ + opcode = OP_eval; + } else { + /* verify if function name resolves to a simple + get_loc/get_arg: a function call inside a `with` + statement can resolve to a method call of the + `with` context object + */ + /* XXX: always generate the OP_scope_get_ref + and remove it in variable resolution + pass ? */ + if (has_with_scope(fd, scope)) { + opcode = OP_scope_get_ref; + fd->byte_code.buf[fd->last_opcode_pos] = opcode; + } + } + drop_count = 1; + } + break; + case OP_get_super_value: + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el; + /* on stack: this func_obj */ + opcode = OP_get_array_el; + drop_count = 2; + break; + default: + opcode = OP_invalid; + drop_count = 1; + break; + } + if (has_optional_chain) { + optional_chain_test(s, &optional_chaining_label, + drop_count); + } + } else { + opcode = OP_invalid; + } + + if (call_type == FUNC_CALL_TEMPLATE) { + if (js_parse_template(s, 1, &arg_count)) + return -1; + goto emit_func_call; + } else if (call_type == FUNC_CALL_SUPER_CTOR) { + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this_active_func); + emit_u16(s, 0); + + emit_op(s, OP_get_super); + + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_new_target); + emit_u16(s, 0); + } else if (call_type == FUNC_CALL_NEW) { + emit_op(s, OP_dup); /* new.target = function */ + } + + /* parse arguments */ + arg_count = 0; + while (s->token.val != ')') { + if (arg_count >= 65535) { + return js_parse_error(s, "Too many arguments in function call (only %d allowed)", + 65535 - 1); + } + if (s->token.val == TOK_ELLIPSIS) + break; + if (js_parse_assign_expr(s)) + return -1; + arg_count++; + if (s->token.val == ')') + break; + /* accept a trailing comma before the ')' */ + if (js_parse_expect(s, ',')) + return -1; + } + if (s->token.val == TOK_ELLIPSIS) { + emit_op(s, OP_array_from); + emit_u16(s, arg_count); + emit_op(s, OP_push_i32); + emit_u32(s, arg_count); + + /* on stack: array idx */ + while (s->token.val != ')') { + if (s->token.val == TOK_ELLIPSIS) { + if (next_token(s)) + return -1; + if (js_parse_assign_expr(s)) + return -1; + /* XXX: could pass is_last indicator? */ + emit_op(s, OP_append); + } else { + if (js_parse_assign_expr(s)) + return -1; + /* array idx val */ + emit_op(s, OP_define_array_el); + emit_op(s, OP_inc); + } + if (s->token.val == ')') + break; + /* accept a trailing comma before the ')' */ + if (js_parse_expect(s, ',')) + return -1; + } + if (next_token(s)) + return -1; + /* drop the index */ + emit_op(s, OP_drop); + + /* apply function call */ + switch(opcode) { + case OP_get_field: + case OP_scope_get_private_field: + case OP_get_array_el: + case OP_scope_get_ref: + /* obj func array -> func obj array */ + emit_op(s, OP_perm3); + emit_op(s, OP_apply); + emit_u16(s, call_type == FUNC_CALL_NEW); + break; + case OP_eval: + emit_op(s, OP_apply_eval); + emit_u16(s, fd->scope_level); + fd->has_eval_call = TRUE; + break; + default: + if (call_type == FUNC_CALL_SUPER_CTOR) { + emit_op(s, OP_apply); + emit_u16(s, 1); + /* set the 'this' value */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + + emit_class_field_init(s); + } else if (call_type == FUNC_CALL_NEW) { + /* obj func array -> func obj array */ + emit_op(s, OP_perm3); + emit_op(s, OP_apply); + emit_u16(s, 1); + } else { + /* func array -> func undef array */ + emit_op(s, OP_undefined); + emit_op(s, OP_swap); + emit_op(s, OP_apply); + emit_u16(s, 0); + } + break; + } + } else { + if (next_token(s)) + return -1; + emit_func_call: + switch(opcode) { + case OP_get_field: + case OP_scope_get_private_field: + case OP_get_array_el: + case OP_scope_get_ref: + emit_op(s, OP_call_method); + emit_u16(s, arg_count); + break; + case OP_eval: + emit_op(s, OP_eval); + emit_u16(s, arg_count); + emit_u16(s, fd->scope_level); + fd->has_eval_call = TRUE; + break; + default: + if (call_type == FUNC_CALL_SUPER_CTOR) { + emit_op(s, OP_call_constructor); + emit_u16(s, arg_count); + + /* set the 'this' value */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + + emit_class_field_init(s); + } else if (call_type == FUNC_CALL_NEW) { + emit_op(s, OP_call_constructor); + emit_u16(s, arg_count); + } else { + emit_op(s, OP_call); + emit_u16(s, arg_count); + } + break; + } + } + call_type = FUNC_CALL_NORMAL; + } else if (s->token.val == '.') { + if (next_token(s)) + return -1; + parse_property: + if (s->token.val == TOK_PRIVATE_NAME) { + /* private class field */ + if (get_prev_opcode(fd) == OP_get_super) { + return js_parse_error(s, "private class field forbidden after super"); + } + if (has_optional_chain) { + optional_chain_test(s, &optional_chaining_label, 1); + } + emit_op(s, OP_scope_get_private_field); + emit_atom(s, s->token.u.ident.atom); + emit_u16(s, s->cur_func->scope_level); + } else { + if (!token_is_ident(s->token.val)) { + return js_parse_error(s, "expecting field name"); + } + if (get_prev_opcode(fd) == OP_get_super) { + JSValue val; + int ret; + val = JS_AtomToValue(s->ctx, s->token.u.ident.atom); + ret = emit_push_const(s, val, 1); + JS_FreeValue(s->ctx, val); + if (ret) + return -1; + emit_op(s, OP_get_super_value); + } else { + if (has_optional_chain) { + optional_chain_test(s, &optional_chaining_label, 1); + } + emit_op(s, OP_get_field); + emit_atom(s, s->token.u.ident.atom); + emit_ic(s, s->token.u.ident.atom); + } + } + if (next_token(s)) + return -1; + } else if (s->token.val == '[') { + int prev_op; + + parse_array_access: + prev_op = get_prev_opcode(fd); + if (has_optional_chain) { + optional_chain_test(s, &optional_chaining_label, 1); + } + if (next_token(s)) + return -1; + if (js_parse_expr(s)) + return -1; + if (js_parse_expect(s, ']')) + return -1; + if (prev_op == OP_get_super) { + emit_op(s, OP_get_super_value); + } else { + emit_op(s, OP_get_array_el); + } + } else { + break; + } + } + if (optional_chaining_label >= 0) { + JSFunctionDef *fd = s->cur_func; + int opcode; + emit_label_raw(s, optional_chaining_label); + /* modify the last opcode so that it is an indicator of an + optional chain */ + opcode = get_prev_opcode(fd); + if (opcode == OP_get_field || opcode == OP_get_array_el) { + if (opcode == OP_get_field) + opcode = OP_get_field_opt_chain; + else + opcode = OP_get_array_el_opt_chain; + fd->byte_code.buf[fd->last_opcode_pos] = opcode; + } else { + fd->last_opcode_pos = -1; + } + } + return 0; +} + +static __exception int js_parse_delete(JSParseState *s) +{ + JSFunctionDef *fd = s->cur_func; + JSAtom name; + int opcode; + + if (next_token(s)) + return -1; + if (js_parse_unary(s, PF_POW_FORBIDDEN)) + return -1; + switch(opcode = get_prev_opcode(fd)) { + case OP_get_field: + case OP_get_field_opt_chain: + { + JSValue val; + int ret, opt_chain_label, next_label; + if (opcode == OP_get_field_opt_chain) { + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 4 + 1); + } else { + opt_chain_label = -1; + } + name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + fd->byte_code.size = fd->last_opcode_pos; + val = JS_AtomToValue(s->ctx, name); + ret = emit_push_const(s, val, 1); + JS_FreeValue(s->ctx, val); + JS_FreeAtom(s->ctx, name); + if (ret) + return ret; + emit_op(s, OP_delete); + if (opt_chain_label >= 0) { + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* if the optional chain is not taken, return 'true' */ + emit_op(s, OP_drop); + emit_op(s, OP_push_true); + emit_label(s, next_label); + } + fd->last_opcode_pos = -1; + } + break; + case OP_get_array_el: + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op(s, OP_delete); + break; + case OP_get_array_el_opt_chain: + { + int opt_chain_label, next_label; + opt_chain_label = get_u32(fd->byte_code.buf + + fd->last_opcode_pos + 1 + 1); + fd->byte_code.size = fd->last_opcode_pos; + emit_op(s, OP_delete); + next_label = emit_goto(s, OP_goto, -1); + emit_label(s, opt_chain_label); + /* if the optional chain is not taken, return 'true' */ + emit_op(s, OP_drop); + emit_op(s, OP_push_true); + emit_label(s, next_label); + fd->last_opcode_pos = -1; + } + break; + case OP_scope_get_var: + /* 'delete this': this is not a reference */ + name = get_u32(fd->byte_code.buf + fd->last_opcode_pos + 1); + if (name == JS_ATOM_this || name == JS_ATOM_new_target) + goto ret_true; + if (fd->is_strict_mode) { + return js_parse_error(s, "cannot delete a direct reference in strict mode"); + } else { + fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_delete_var; + } + break; + case OP_scope_get_private_field: + return js_parse_error(s, "cannot delete a private class field"); + case OP_get_super_value: + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op(s, OP_throw_error); + emit_atom(s, JS_ATOM_NULL); + emit_u8(s, JS_THROW_ERROR_DELETE_SUPER); + break; + default: + ret_true: + emit_op(s, OP_drop); + emit_op(s, OP_push_true); + break; + } + return 0; +} + +/* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */ +static __exception int js_parse_unary(JSParseState *s, int parse_flags) +{ + int op; + + switch(s->token.val) { + case '+': + case '-': + case '!': + case '~': + case TOK_VOID: + op = s->token.val; + if (next_token(s)) + return -1; + if (js_parse_unary(s, PF_POW_FORBIDDEN)) + return -1; + switch(op) { + case '-': + emit_op(s, OP_neg); + break; + case '+': + emit_op(s, OP_plus); + break; + case '!': + emit_op(s, OP_lnot); + break; + case '~': + emit_op(s, OP_not); + break; + case TOK_VOID: + emit_op(s, OP_drop); + emit_op(s, OP_undefined); + break; + default: + abort(); + } + parse_flags = 0; + break; + case TOK_DEC: + case TOK_INC: + { + int opcode, op, scope, label; + JSAtom name; + op = s->token.val; + if (next_token(s)) + return -1; + if (js_parse_unary(s, 0)) + return -1; + if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) + return -1; + emit_op(s, OP_dec + op - TOK_DEC); + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, + FALSE); + } + break; + case TOK_TYPEOF: + { + JSFunctionDef *fd; + if (next_token(s)) + return -1; + if (js_parse_unary(s, PF_POW_FORBIDDEN)) + return -1; + /* reference access should not return an exception, so we + patch the get_var */ + fd = s->cur_func; + if (get_prev_opcode(fd) == OP_scope_get_var) { + fd->byte_code.buf[fd->last_opcode_pos] = OP_scope_get_var_undef; + } + emit_op(s, OP_typeof); + parse_flags = 0; + } + break; + case TOK_DELETE: + if (js_parse_delete(s)) + return -1; + parse_flags = 0; + break; + case TOK_AWAIT: + if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) + return js_parse_error(s, "unexpected 'await' keyword"); + if (!s->cur_func->in_function_body) + return js_parse_error(s, "await in default expression"); + if (next_token(s)) + return -1; + if (js_parse_unary(s, PF_POW_FORBIDDEN)) + return -1; + s->cur_func->has_await = TRUE; + emit_op(s, OP_await); + parse_flags = 0; + break; + default: + if (js_parse_postfix_expr(s, PF_POSTFIX_CALL)) + return -1; + if (!s->got_lf && + (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { + int opcode, op, scope, label; + JSAtom name; + op = s->token.val; + if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, TRUE, op)) + return -1; + emit_op(s, OP_post_dec + op - TOK_DEC); + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, + FALSE); + if (next_token(s)) + return -1; + } + break; + } + if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) { + if (s->token.val == TOK_POW) { + /* Strict ES7 exponentiation syntax rules: To solve + conficting semantics between different implementations + regarding the precedence of prefix operators and the + postifx exponential, ES7 specifies that -2**2 is a + syntax error. */ + if (parse_flags & PF_POW_FORBIDDEN) + return js_parse_error(s, "unparenthesized unary expression can't appear on the left-hand side of '**'"); + if (next_token(s)) + return -1; + if (js_parse_unary(s, PF_POW_ALLOWED)) + return -1; + emit_op(s, OP_pow); + } + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_expr_binary(JSParseState *s, int level, + int parse_flags) +{ + int op, opcode; + + if (level == 0) { + return js_parse_unary(s, PF_POW_ALLOWED); + } else if (s->token.val == TOK_PRIVATE_NAME && + (parse_flags & PF_IN_ACCEPTED) && level == 4 && + peek_token(s, FALSE) == TOK_IN) { + JSAtom atom; + atom = JS_DupAtom(s->ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail_private_in; + if (s->token.val != TOK_IN) + goto fail_private_in; + if (next_token(s)) + goto fail_private_in; + if (js_parse_expr_binary(s, level - 1, parse_flags)) { + fail_private_in: + JS_FreeAtom(s->ctx, atom); + return -1; + } + emit_op(s, OP_scope_in_private_field); + emit_atom(s, atom); + emit_u16(s, s->cur_func->scope_level); + JS_FreeAtom(s->ctx, atom); + return 0; + } else { + if (js_parse_expr_binary(s, level - 1, parse_flags)) + return -1; + } + for(;;) { + op = s->token.val; + switch(level) { + case 1: + switch(op) { + case '*': + opcode = OP_mul; + break; + case '/': + opcode = OP_div; + break; + case '%': + opcode = OP_mod; + break; + default: + return 0; + } + break; + case 2: + switch(op) { + case '+': + opcode = OP_add; + break; + case '-': + opcode = OP_sub; + break; + default: + return 0; + } + 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 0; + } + 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_IN_ACCEPTED) { + opcode = OP_in; + } else { + return 0; + } + break; + default: + return 0; + } + 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 0; + } + break; + case 6: + switch(op) { + case '&': + opcode = OP_and; + break; + default: + return 0; + } + break; + case 7: + switch(op) { + case '^': + opcode = OP_xor; + break; + default: + return 0; + } + break; + case 8: + switch(op) { + case '|': + opcode = OP_or; + break; + default: + return 0; + } + break; + default: + abort(); + } + if (next_token(s)) + return -1; + if (js_parse_expr_binary(s, level - 1, parse_flags)) + return -1; + emit_op(s, opcode); + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_logical_and_or(JSParseState *s, int op, + int parse_flags) +{ + int label1; + + if (op == TOK_LAND) { + if (js_parse_expr_binary(s, 8, parse_flags)) + return -1; + } else { + if (js_parse_logical_and_or(s, TOK_LAND, parse_flags)) + return -1; + } + if (s->token.val == op) { + label1 = new_label(s); + + for(;;) { + if (next_token(s)) + return -1; + emit_op(s, OP_dup); + emit_goto(s, op == TOK_LAND ? OP_if_false : OP_if_true, label1); + emit_op(s, OP_drop); + + if (op == TOK_LAND) { + if (js_parse_expr_binary(s, 8, parse_flags)) + return -1; + } else { + if (js_parse_logical_and_or(s, TOK_LAND, parse_flags)) + return -1; + } + if (s->token.val != op) { + if (s->token.val == TOK_DOUBLE_QUESTION_MARK) + return js_parse_error(s, "cannot mix ?? with && or ||"); + break; + } + } + + emit_label(s, label1); + } + return 0; +} + +static __exception int js_parse_coalesce_expr(JSParseState *s, int parse_flags) +{ + int label1; + + if (js_parse_logical_and_or(s, TOK_LOR, parse_flags)) + return -1; + if (s->token.val == TOK_DOUBLE_QUESTION_MARK) { + label1 = new_label(s); + for(;;) { + if (next_token(s)) + return -1; + + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined_or_null); + emit_goto(s, OP_if_false, label1); + emit_op(s, OP_drop); + + if (js_parse_expr_binary(s, 8, parse_flags)) + return -1; + if (s->token.val != TOK_DOUBLE_QUESTION_MARK) + break; + } + emit_label(s, label1); + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_cond_expr(JSParseState *s, int parse_flags) +{ + int label1, label2; + + if (js_parse_coalesce_expr(s, parse_flags)) + return -1; + if (s->token.val == '?') { + if (next_token(s)) + return -1; + label1 = emit_goto(s, OP_if_false, -1); + + if (js_parse_assign_expr(s)) + return -1; + if (js_parse_expect(s, ':')) + return -1; + + label2 = emit_goto(s, OP_goto, -1); + + emit_label(s, label1); + + if (js_parse_assign_expr2(s, parse_flags & PF_IN_ACCEPTED)) + return -1; + + emit_label(s, label2); + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_assign_expr2(JSParseState *s, int parse_flags) +{ + int opcode, op, scope; + JSAtom name0 = JS_ATOM_NULL; + JSAtom name; + + if (s->token.val == TOK_YIELD) { + BOOL is_star = FALSE, is_async; + + if (!(s->cur_func->func_kind & JS_FUNC_GENERATOR)) + return js_parse_error(s, "unexpected 'yield' keyword"); + if (!s->cur_func->in_function_body) + return js_parse_error(s, "yield in default expression"); + if (next_token(s)) + return -1; + /* XXX: is there a better method to detect 'yield' without + parameters ? */ + if (s->token.val != ';' && s->token.val != ')' && + s->token.val != ']' && s->token.val != '}' && + s->token.val != ',' && s->token.val != ':' && !s->got_lf) { + if (s->token.val == '*') { + is_star = TRUE; + if (next_token(s)) + return -1; + } + if (js_parse_assign_expr2(s, parse_flags)) + return -1; + } else { + emit_op(s, OP_undefined); + } + is_async = (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR); + + if (is_star) { + int label_loop, label_return, label_next; + int label_return1, label_yield, label_throw, label_throw1; + int label_throw2; + + label_loop = new_label(s); + label_yield = new_label(s); + + emit_op(s, is_async ? OP_for_await_of_start : OP_for_of_start); + + /* remove the catch offset (XXX: could avoid pushing back + undefined) */ + emit_op(s, OP_drop); + emit_op(s, OP_undefined); + + emit_op(s, OP_undefined); /* initial value */ + + emit_label(s, label_loop); + emit_op(s, OP_iterator_next); + if (is_async) + emit_op(s, OP_await); + emit_op(s, OP_iterator_check_object); + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_done); + emit_ic(s, JS_ATOM_done); + label_next = emit_goto(s, OP_if_true, -1); /* end of loop */ + emit_label(s, label_yield); + if (is_async) { + /* OP_async_yield_star takes the value as parameter */ + emit_op(s, OP_get_field); + emit_atom(s, JS_ATOM_value); + emit_ic(s, JS_ATOM_value); + emit_op(s, OP_async_yield_star); + } else { + /* OP_yield_star takes (value, done) as parameter */ + emit_op(s, OP_yield_star); + } + emit_op(s, OP_dup); + label_return = emit_goto(s, OP_if_true, -1); + emit_op(s, OP_drop); + emit_goto(s, OP_goto, label_loop); + + emit_label(s, label_return); + emit_op(s, OP_push_i32); + emit_u32(s, 2); + emit_op(s, OP_strict_eq); + label_throw = emit_goto(s, OP_if_true, -1); + + /* return handling */ + if (is_async) + emit_op(s, OP_await); + emit_op(s, OP_iterator_call); + emit_u8(s, 0); + label_return1 = emit_goto(s, OP_if_true, -1); + if (is_async) + emit_op(s, OP_await); + emit_op(s, OP_iterator_check_object); + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_done); + emit_ic(s, JS_ATOM_done); + emit_goto(s, OP_if_false, label_yield); + + emit_op(s, OP_get_field); + emit_atom(s, JS_ATOM_value); + emit_ic(s, JS_ATOM_value); + + emit_label(s, label_return1); + emit_op(s, OP_nip); + emit_op(s, OP_nip); + emit_op(s, OP_nip); + emit_return(s, TRUE); + + /* throw handling */ + emit_label(s, label_throw); + emit_op(s, OP_iterator_call); + emit_u8(s, 1); + label_throw1 = emit_goto(s, OP_if_true, -1); + if (is_async) + emit_op(s, OP_await); + emit_op(s, OP_iterator_check_object); + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_done); + emit_ic(s, JS_ATOM_done); + emit_goto(s, OP_if_false, label_yield); + emit_goto(s, OP_goto, label_next); + /* close the iterator and throw a type error exception */ + emit_label(s, label_throw1); + emit_op(s, OP_iterator_call); + emit_u8(s, 2); + label_throw2 = emit_goto(s, OP_if_true, -1); + if (is_async) + emit_op(s, OP_await); + emit_label(s, label_throw2); + + emit_op(s, OP_throw_error); + emit_atom(s, JS_ATOM_NULL); + emit_u8(s, JS_THROW_ERROR_ITERATOR_THROW); + + emit_label(s, label_next); + emit_op(s, OP_get_field); + emit_atom(s, JS_ATOM_value); + emit_ic(s, JS_ATOM_value); + emit_op(s, OP_nip); /* keep the value associated with + done = true */ + emit_op(s, OP_nip); + emit_op(s, OP_nip); + } else { + int label_next; + + if (is_async) + emit_op(s, OP_await); + emit_op(s, OP_yield); + label_next = emit_goto(s, OP_if_false, -1); + emit_return(s, TRUE); + emit_label(s, label_next); + } + return 0; + } else if (s->token.val == '(' && + js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) { + return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, s->token.line_num, + s->token.col_num); + } else if (token_is_pseudo_keyword(s, JS_ATOM_async)) { + const uint8_t *source_ptr; + int tok, source_line_num, source_col_num; + JSParsePos pos; + + /* fast test */ + tok = peek_token(s, TRUE); + if (tok == TOK_FUNCTION || tok == '\n') + goto next; + + source_ptr = s->token.ptr; + source_line_num = s->token.line_num; + source_col_num = s->token.col_num; + js_parse_get_pos(s, &pos); + if (next_token(s)) + return -1; + if ((s->token.val == '(' && + js_parse_skip_parens_token(s, NULL, TRUE) == TOK_ARROW) || + (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && + peek_token(s, TRUE) == TOK_ARROW)) { + return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, + JS_FUNC_ASYNC, JS_ATOM_NULL, + source_ptr, source_line_num, + source_col_num); + } else { + /* undo the token parsing */ + if (js_parse_seek_token(s, &pos)) + return -1; + } + } else if (s->token.val == TOK_IDENT && + peek_token(s, TRUE) == TOK_ARROW) { + return js_parse_function_decl(s, JS_PARSE_FUNC_ARROW, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, s->token.line_num, + s->token.col_num); + } + next: + if (s->token.val == TOK_IDENT) { + /* name0 is used to check for OP_set_name pattern, not duplicated */ + name0 = s->token.u.ident.atom; + } + if (js_parse_cond_expr(s, parse_flags)) + return -1; + + op = s->token.val; + if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) { + int label; + if (next_token(s)) + return -1; + if (get_lvalue(s, &opcode, &scope, &name, &label, NULL, (op != '='), op) < 0) + return -1; + + // comply with rather obtuse evaluation order of computed properties: + // obj[key]=val evaluates val->obj->key when obj is null/undefined + // but key->obj->val when an object + // FIXME(bnoordhuis) less stack shuffling; don't to_propkey twice in + // happy path; replace `dup is_undefined_or_null if_true` with new + // opcode if_undefined_or_null? replace `swap dup` with over? + if (op == '=' && opcode == OP_get_array_el) { + int label_next = -1; + JSFunctionDef *fd = s->cur_func; + assert(OP_to_propkey2 == fd->byte_code.buf[fd->last_opcode_pos]); + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op(s, OP_swap); // obj key -> key obj + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined_or_null); + label_next = emit_goto(s, OP_if_true, -1); + emit_op(s, OP_swap); + emit_op(s, OP_to_propkey); + emit_op(s, OP_swap); + emit_label(s, label_next); + emit_op(s, OP_swap); + } + + if (js_parse_assign_expr2(s, parse_flags)) { + JS_FreeAtom(s->ctx, name); + return -1; + } + + if (op == '=' && opcode == OP_get_array_el) { + emit_op(s, OP_swap); // obj key val -> obj val key + emit_op(s, OP_to_propkey); + emit_op(s, OP_swap); + } + + if (op == '=') { + if (opcode == OP_get_ref_value && name == name0) { + set_object_name(s, name); + } + } else { + emit_op(s, op - TOK_MUL_ASSIGN + OP_mul); + } + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); + } else if (op >= TOK_LAND_ASSIGN && op <= TOK_DOUBLE_QUESTION_MARK_ASSIGN) { + int label, label1, depth_lvalue, label2; + + if (next_token(s)) + return -1; + if (get_lvalue(s, &opcode, &scope, &name, &label, + &depth_lvalue, TRUE, op) < 0) + return -1; + + emit_op(s, OP_dup); + if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN) + emit_op(s, OP_is_undefined_or_null); + label1 = emit_goto(s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, + -1); + emit_op(s, OP_drop); + + if (js_parse_assign_expr2(s, parse_flags)) { + JS_FreeAtom(s->ctx, name); + return -1; + } + + if (opcode == OP_get_ref_value && name == name0) { + set_object_name(s, name); + } + + switch(depth_lvalue) { + case 1: + emit_op(s, OP_insert2); + break; + case 2: + emit_op(s, OP_insert3); + break; + case 3: + emit_op(s, OP_insert4); + break; + default: + abort(); + } + + /* XXX: we disable the OP_put_ref_value optimization by not + using put_lvalue() otherwise depth_lvalue is not correct */ + put_lvalue(s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, + FALSE); + label2 = emit_goto(s, OP_goto, -1); + + emit_label(s, label1); + + /* remove the lvalue stack entries */ + while (depth_lvalue != 0) { + emit_op(s, OP_nip); + depth_lvalue--; + } + + emit_label(s, label2); + } + return 0; +} + +static __exception int js_parse_assign_expr(JSParseState *s) +{ + return js_parse_assign_expr2(s, PF_IN_ACCEPTED); +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_expr2(JSParseState *s, int parse_flags) +{ + BOOL comma = FALSE; + for(;;) { + if (js_parse_assign_expr2(s, parse_flags)) + return -1; + if (comma) { + /* prevent get_lvalue from using the last expression + as an lvalue. This also prevents the conversion of + of get_var to get_ref for method lookup in function + call inside `with` statement. + */ + s->cur_func->last_opcode_pos = -1; + } + if (s->token.val != ',') + break; + comma = TRUE; + if (next_token(s)) + return -1; + emit_op(s, OP_drop); + } + return 0; +} + +static __exception int js_parse_expr(JSParseState *s) +{ + return js_parse_expr2(s, PF_IN_ACCEPTED); +} + +static void push_break_entry(JSFunctionDef *fd, BlockEnv *be, + JSAtom label_name, + int label_break, int label_cont, + int drop_count) +{ + be->prev = fd->top_break; + fd->top_break = be; + be->label_name = label_name; + be->label_break = label_break; + be->label_cont = label_cont; + be->drop_count = drop_count; + be->label_finally = -1; + be->scope_level = fd->scope_level; + be->has_iterator = FALSE; + be->is_regular_stmt = FALSE; +} + +static void pop_break_entry(JSFunctionDef *fd) +{ + BlockEnv *be; + be = fd->top_break; + fd->top_break = be->prev; +} + +static __exception int emit_break(JSParseState *s, JSAtom name, int is_cont) +{ + BlockEnv *top; + int i, scope_level; + + scope_level = s->cur_func->scope_level; + top = s->cur_func->top_break; + while (top != NULL) { + close_scopes(s, scope_level, top->scope_level); + scope_level = top->scope_level; + if (is_cont && + top->label_cont != -1 && + (name == JS_ATOM_NULL || top->label_name == name)) { + /* continue stays inside the same block */ + emit_goto(s, OP_goto, top->label_cont); + return 0; + } + if (!is_cont && top->label_break != -1) { + if (top->label_name == name || + (name == JS_ATOM_NULL && !top->is_regular_stmt)) { + emit_goto(s, OP_goto, top->label_break); + return 0; + } + } + i = 0; + if (top->has_iterator) { + emit_op(s, OP_iterator_close); + i += 3; + } + for(; i < top->drop_count; i++) + emit_op(s, OP_drop); + if (top->label_finally != -1) { + /* 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); + } + top = top->prev; + } + if (name == JS_ATOM_NULL) { + if (is_cont) + return js_parse_error(s, "continue must be inside loop"); + else + return js_parse_error(s, "break must be inside loop or switch"); + } else { + return js_parse_error(s, "break/continue label not found"); + } +} + +/* execute the finally blocks before return */ +static void emit_return(JSParseState *s, BOOL hasval) +{ + BlockEnv *top; + + if (s->cur_func->func_kind != JS_FUNC_NORMAL) { + if (!hasval) { + /* no value: direct return in case of async generator */ + emit_op(s, OP_undefined); + hasval = TRUE; + } else if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { + /* the await must be done before handling the "finally" in + case it raises an exception */ + emit_op(s, OP_await); + } + } + + top = s->cur_func->top_break; + while (top != NULL) { + if (top->has_iterator || top->label_finally != -1) { + if (!hasval) { + emit_op(s, OP_undefined); + hasval = TRUE; + } + /* Remove the stack elements up to and including the catch + offset. When 'yield' is used in an expression we have + no easy way to count them, so we use this specific + instruction instead. */ + emit_op(s, OP_nip_catch); + /* stack: iter_obj next ret_val */ + if (top->has_iterator) { + if (s->cur_func->func_kind == JS_FUNC_ASYNC_GENERATOR) { + int label_next, label_next2; + emit_op(s, OP_nip); /* next */ + emit_op(s, OP_swap); + emit_op(s, OP_get_field2); + emit_atom(s, JS_ATOM_return); + emit_ic(s, JS_ATOM_return); + /* stack: iter_obj return_func */ + emit_op(s, OP_dup); + emit_op(s, OP_is_undefined_or_null); + label_next = emit_goto(s, OP_if_true, -1); + emit_op(s, OP_call_method); + emit_u16(s, 0); + emit_op(s, OP_iterator_check_object); + emit_op(s, OP_await); + label_next2 = emit_goto(s, OP_goto, -1); + emit_label(s, label_next); + emit_op(s, OP_drop); + emit_label(s, label_next2); + emit_op(s, OP_drop); + } else { + emit_op(s, OP_rot3r); + emit_op(s, OP_undefined); /* dummy catch offset */ + emit_op(s, OP_iterator_close); + } + } else { + /* execute the "finally" block */ + emit_goto(s, OP_gosub, top->label_finally); + } + } + top = top->prev; + } + if (s->cur_func->is_derived_class_constructor) { + int label_return; + + /* 'this' can be uninitialized, so it may be accessed only if + the derived class constructor does not return an object */ + if (hasval) { + emit_op(s, OP_check_ctor_return); + label_return = emit_goto(s, OP_if_false, -1); + emit_op(s, OP_drop); + } else { + label_return = -1; + } + + /* XXX: if this is not initialized, should throw the + ReferenceError in the caller realm */ + emit_op(s, OP_scope_get_var); + emit_atom(s, JS_ATOM_this); + emit_u16(s, 0); + + emit_label(s, label_return); + emit_op(s, OP_return); + } else if (s->cur_func->func_kind != JS_FUNC_NORMAL) { + emit_op(s, OP_return_async); + } else { + emit_op(s, hasval ? OP_return : OP_return_undef); + } +} + +#define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */ +/* ored with DECL_MASK_FUNC if function declarations are allowed with a label */ +#define DECL_MASK_FUNC_WITH_LABEL (1 << 1) +#define DECL_MASK_OTHER (1 << 2) /* all other declarations */ +#define DECL_MASK_ALL (DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER) + +static __exception int js_parse_statement_or_decl(JSParseState *s, + int decl_mask); + +static __exception int js_parse_statement(JSParseState *s) +{ + return js_parse_statement_or_decl(s, 0); +} + +static __exception int js_parse_block(JSParseState *s) +{ + if (js_parse_expect(s, '{')) + return -1; + if (s->token.val != '}') { + push_scope(s); + for(;;) { + if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) + return -1; + if (s->token.val == '}') + break; + } + pop_scope(s); + } + if (next_token(s)) + return -1; + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_var(JSParseState *s, int parse_flags, int tok, + BOOL export_flag) +{ + JSContext *ctx = s->ctx; + JSFunctionDef *fd = s->cur_func; + JSAtom name = JS_ATOM_NULL; + + for (;;) { + if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) { + return js_parse_error_reserved_identifier(s); + } + name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (name == JS_ATOM_let && (tok == TOK_LET || tok == TOK_CONST)) { + js_parse_error(s, "'let' is not a valid lexical identifier"); + goto var_error; + } + if (next_token(s)) + goto var_error; + if (js_define_var(s, name, tok)) + goto var_error; + if (export_flag) { + if (!add_export_entry(s, s->cur_func->module, name, name, + JS_EXPORT_TYPE_LOCAL)) + goto var_error; + } + + if (s->token.val == '=') { + if (next_token(s)) + goto var_error; + if (tok == TOK_VAR) { + /* Must make a reference for proper `with` semantics */ + int opcode, scope, label; + JSAtom name1; + + emit_op(s, OP_scope_get_var); + emit_atom(s, name); + emit_u16(s, fd->scope_level); + if (get_lvalue(s, &opcode, &scope, &name1, &label, NULL, FALSE, '=') < 0) + goto var_error; + if (js_parse_assign_expr2(s, parse_flags)) { + JS_FreeAtom(ctx, name1); + goto var_error; + } + set_object_name(s, name); + put_lvalue(s, opcode, scope, name1, label, + PUT_LVALUE_NOKEEP, FALSE); + } else { + if (js_parse_assign_expr2(s, parse_flags)) + goto var_error; + set_object_name(s, name); + emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ? + OP_scope_put_var_init : OP_scope_put_var); + emit_atom(s, name); + emit_u16(s, fd->scope_level); + } + } else { + if (tok == TOK_CONST) { + js_parse_error(s, "missing initializer for const variable"); + goto var_error; + } + if (tok == TOK_LET) { + /* initialize lexical variable upon entering its scope */ + emit_op(s, OP_undefined); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, name); + emit_u16(s, fd->scope_level); + } + } + JS_FreeAtom(ctx, name); + } else { + int skip_bits; + if ((s->token.val == '[' || s->token.val == '{') + && js_parse_skip_parens_token(s, &skip_bits, FALSE) == '=') { + emit_op(s, OP_undefined); + if (js_parse_destructuring_element(s, tok, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + return -1; + } else { + return js_parse_error(s, "variable name expected"); + } + } + if (s->token.val != ',') + break; + if (next_token(s)) + return -1; + } + return 0; + + var_error: + JS_FreeAtom(ctx, name); + return -1; +} + +/* test if the current token is a label. Use simplistic look-ahead scanner */ +static BOOL is_label(JSParseState *s) +{ + return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved && + peek_token(s, FALSE) == ':'); +} + +/* test if the current token is a let keyword. Use simplistic look-ahead scanner */ +static int is_let(JSParseState *s, int decl_mask) +{ + int res = FALSE; + + if (token_is_pseudo_keyword(s, JS_ATOM_let)) { + JSParsePos pos; + js_parse_get_pos(s, &pos); + for (;;) { + if (next_token(s)) { + res = -1; + break; + } + if (s->token.val == '[') { + /* let [ is a syntax restriction: + it never introduces an ExpressionStatement */ + res = TRUE; + break; + } + if (s->token.val == '{' || + (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) || + s->token.val == TOK_LET || + s->token.val == TOK_YIELD || + s->token.val == TOK_AWAIT) { + /* Check for possible ASI if not scanning for Declaration */ + /* XXX: should also check that `{` introduces a BindingPattern, + but Firefox does not and rejects eval("let=1;let\n{if(1)2;}") */ + if (s->last_line_num == s->token.line_num || (decl_mask & DECL_MASK_OTHER)) { + res = TRUE; + break; + } + break; + } + break; + } + if (js_parse_seek_token(s, &pos)) { + res = -1; + } + } + return res; +} + +/* XXX: handle IteratorClose when exiting the loop before the + enumeration is done */ +static __exception int js_parse_for_in_of(JSParseState *s, int label_name, + BOOL is_async) +{ + JSContext *ctx = s->ctx; + JSFunctionDef *fd = s->cur_func; + JSAtom var_name; + BOOL has_initializer, is_for_of, has_destructuring; + int tok, tok1, opcode, scope, block_scope_level; + int label_next, label_expr, label_cont, label_body, label_break; + int pos_next, pos_expr; + BlockEnv break_entry; + + has_initializer = FALSE; + has_destructuring = FALSE; + is_for_of = FALSE; + block_scope_level = fd->scope_level; + label_cont = new_label(s); + label_body = new_label(s); + label_break = new_label(s); + label_next = new_label(s); + + /* create scope for the lexical variables declared in the enumeration + expressions. XXX: Not completely correct because of weird capturing + semantics in `for (i of o) a.push(function(){return i})` */ + push_scope(s); + + /* local for_in scope starts here so individual elements + can be closed in statement. */ + push_break_entry(s->cur_func, &break_entry, + label_name, label_break, label_cont, 1); + break_entry.scope_level = block_scope_level; + + label_expr = emit_goto(s, OP_goto, -1); + + pos_next = s->cur_func->byte_code.size; + emit_label(s, label_next); + + tok = s->token.val; + switch (is_let(s, DECL_MASK_OTHER)) { + case TRUE: + tok = TOK_LET; + break; + case FALSE: + break; + default: + return -1; + } + if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) { + if (next_token(s)) + return -1; + + if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { + if (s->token.val == '[' || s->token.val == '{') { + if (js_parse_destructuring_element(s, tok, 0, TRUE, -1, FALSE) < 0) + return -1; + has_destructuring = TRUE; + } else { + return js_parse_error(s, "variable name expected"); + } + var_name = JS_ATOM_NULL; + } else { + var_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) { + JS_FreeAtom(s->ctx, var_name); + return -1; + } + if (js_define_var(s, var_name, tok)) { + JS_FreeAtom(s->ctx, var_name); + return -1; + } + emit_op(s, (tok == TOK_CONST || tok == TOK_LET) ? + OP_scope_put_var_init : OP_scope_put_var); + emit_atom(s, var_name); + emit_u16(s, fd->scope_level); + } + } else if (!is_async && token_is_pseudo_keyword(s, JS_ATOM_async) && peek_token(s, FALSE) == TOK_OF) { + return js_parse_error(s, "'for of' expression cannot start with 'async'"); + } else { + int skip_bits; + if ((s->token.val == '[' || s->token.val == '{') + && ((tok1 = js_parse_skip_parens_token(s, &skip_bits, FALSE)) == TOK_IN || tok1 == TOK_OF)) { + if (js_parse_destructuring_element(s, 0, 0, TRUE, skip_bits & SKIP_HAS_ELLIPSIS, TRUE) < 0) + return -1; + } else { + int lvalue_label; + if (js_parse_left_hand_side_expr(s)) + return -1; + if (get_lvalue(s, &opcode, &scope, &var_name, &lvalue_label, + NULL, FALSE, TOK_FOR)) + return -1; + put_lvalue(s, opcode, scope, var_name, lvalue_label, + PUT_LVALUE_NOKEEP_BOTTOM, FALSE); + } + var_name = JS_ATOM_NULL; + } + emit_goto(s, OP_goto, label_body); + + pos_expr = s->cur_func->byte_code.size; + emit_label(s, label_expr); + if (s->token.val == '=') { + /* XXX: potential scoping issue if inside `with` statement */ + has_initializer = TRUE; + /* parse and evaluate initializer prior to evaluating the + object (only used with "for in" with a non lexical variable + in non strict mode */ + if (next_token(s) || js_parse_assign_expr2(s, 0)) { + JS_FreeAtom(ctx, var_name); + return -1; + } + if (var_name != JS_ATOM_NULL) { + emit_op(s, OP_scope_put_var); + emit_atom(s, var_name); + emit_u16(s, fd->scope_level); + } + } + JS_FreeAtom(ctx, var_name); + + if (token_is_pseudo_keyword(s, JS_ATOM_of)) { + is_for_of = TRUE; + break_entry.has_iterator = TRUE; + break_entry.drop_count += 2; + if (has_initializer) + goto initializer_error; + } else if (s->token.val == TOK_IN) { + if (is_async) + return js_parse_error(s, "'for await' loop should be used with 'of'"); + if (has_initializer && + (tok != TOK_VAR || fd->is_strict_mode || has_destructuring)) { + initializer_error: + return js_parse_error(s, "a declaration in the head of a for-%s loop can't have an initializer", + is_for_of ? "of" : "in"); + } + } else { + return js_parse_error(s, "expected 'of' or 'in' in for control expression"); + } + if (next_token(s)) + return -1; + if (is_for_of) { + if (js_parse_assign_expr(s)) + return -1; + } else { + if (js_parse_expr(s)) + return -1; + } + /* close the scope after having evaluated the expression so that + the TDZ values are in the closures */ + close_scopes(s, s->cur_func->scope_level, block_scope_level); + if (is_for_of) { + if (is_async) + emit_op(s, OP_for_await_of_start); + else + emit_op(s, OP_for_of_start); + /* on stack: enum_rec */ + } else { + emit_op(s, OP_for_in_start); + /* on stack: enum_obj */ + } + emit_goto(s, OP_goto, label_cont); + + if (js_parse_expect(s, ')')) + return -1; + + { + /* move the `next` code here */ + DynBuf *bc = &s->cur_func->byte_code; + int chunk_size = pos_expr - pos_next; + int offset = bc->size - pos_next; + int i; + dbuf_realloc(bc, bc->size + chunk_size); + dbuf_put(bc, bc->buf + pos_next, chunk_size); + memset(bc->buf + pos_next, OP_nop, chunk_size); + /* `next` part ends with a goto */ + s->cur_func->last_opcode_pos = bc->size - 5; + /* relocate labels */ + for (i = label_cont; i < s->cur_func->label_count; i++) { + LabelSlot *ls = &s->cur_func->label_slots[i]; + if (ls->pos >= pos_next && ls->pos < pos_expr) + ls->pos += offset; + } + } + + emit_label(s, label_body); + if (js_parse_statement(s)) + return -1; + + close_scopes(s, s->cur_func->scope_level, block_scope_level); + + emit_label(s, label_cont); + if (is_for_of) { + if (is_async) { + /* call the next method */ + /* stack: iter_obj next catch_offset */ + emit_op(s, OP_dup3); + emit_op(s, OP_drop); + emit_op(s, OP_call_method); + emit_u16(s, 0); + /* get the result of the promise */ + emit_op(s, OP_await); + /* unwrap the value and done values */ + emit_op(s, OP_iterator_get_value_done); + } else { + emit_op(s, OP_for_of_next); + emit_u8(s, 0); + } + } else { + emit_op(s, OP_for_in_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, label_break); + if (is_for_of) { + /* close and drop enum_rec */ + emit_op(s, OP_iterator_close); + } else { + emit_op(s, OP_drop); + } + pop_break_entry(s->cur_func); + pop_scope(s); + return 0; +} + +static void set_eval_ret_undefined(JSParseState *s) +{ + if (s->cur_func->eval_ret_idx >= 0) { + emit_op(s, OP_undefined); + emit_op(s, OP_put_loc); + emit_u16(s, s->cur_func->eval_ret_idx); + } +} + +static __exception int js_parse_statement_or_decl(JSParseState *s, + int decl_mask) +{ + JSContext *ctx = s->ctx; + JSAtom label_name; + int tok; + + /* specific label handling */ + /* XXX: support multiple labels on loop statements */ + label_name = JS_ATOM_NULL; + if (is_label(s)) { + BlockEnv *be; + + label_name = JS_DupAtom(ctx, s->token.u.ident.atom); + + for (be = s->cur_func->top_break; be; be = be->prev) { + if (be->label_name == label_name) { + js_parse_error(s, "duplicate label name"); + goto fail; + } + } + + if (next_token(s)) + goto fail; + if (js_parse_expect(s, ':')) + goto fail; + if (s->token.val != TOK_FOR + && s->token.val != TOK_DO + && s->token.val != TOK_WHILE) { + /* labelled regular statement */ + JSFunctionDef *fd = s->cur_func; + int label_break, mask; + BlockEnv break_entry; + + label_break = new_label(s); + push_break_entry(fd, &break_entry, label_name, label_break, -1, 0); + fd->top_break->is_regular_stmt = 1; + if (!fd->is_strict_mode && + (decl_mask & DECL_MASK_FUNC_WITH_LABEL)) { + mask = DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL; + } else { + mask = 0; + } + if (js_parse_statement_or_decl(s, mask)) + goto fail; + emit_label(s, label_break); + pop_break_entry(fd); + goto done; + } + } + + switch(tok = s->token.val) { + case '{': + if (js_parse_block(s)) + goto fail; + break; + case TOK_RETURN: + if (s->cur_func->is_eval) { + js_parse_error(s, "return not in a function"); + goto fail; + } + if (s->cur_func->func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { + js_parse_error(s, "return in a static initializer block"); + goto fail; + } + if (next_token(s)) + goto fail; + if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { + if (js_parse_expr(s)) + goto fail; + emit_return(s, TRUE); + } else { + emit_return(s, FALSE); + } + if (js_parse_expect_semi(s)) + goto fail; + break; + case TOK_THROW: + if (next_token(s)) + goto fail; + if (s->got_lf) { + js_parse_error(s, "line terminator not allowed after throw"); + goto fail; + } + if (js_parse_expr(s)) + goto fail; + emit_op(s, OP_throw); + if (js_parse_expect_semi(s)) + goto fail; + break; + case TOK_LET: + case TOK_CONST: + haslet: + if (!(decl_mask & DECL_MASK_OTHER)) { + js_parse_error(s, "lexical declarations can't appear in single-statement context"); + goto fail; + } + /* fall thru */ + case TOK_VAR: + if (next_token(s)) + goto fail; + if (js_parse_var(s, TRUE, tok, FALSE)) + goto fail; + if (js_parse_expect_semi(s)) + goto fail; + break; + case TOK_IF: + { + int label1, label2, mask; + if (next_token(s)) + goto fail; + /* create a new scope for `let f;if(1) function f(){}` */ + push_scope(s); + set_eval_ret_undefined(s); + if (js_parse_expr_paren(s)) + goto fail; + label1 = emit_goto(s, OP_if_false, -1); + if (s->cur_func->is_strict_mode) + mask = 0; + else + mask = DECL_MASK_FUNC; /* Annex B.3.4 */ + + if (js_parse_statement_or_decl(s, mask)) + goto fail; + + if (s->token.val == TOK_ELSE) { + label2 = emit_goto(s, OP_goto, -1); + if (next_token(s)) + goto fail; + + emit_label(s, label1); + if (js_parse_statement_or_decl(s, mask)) + goto fail; + + label1 = label2; + } + emit_label(s, label1); + pop_scope(s); + } + break; + case TOK_WHILE: + { + int label_cont, label_break; + BlockEnv break_entry; + + label_cont = new_label(s); + label_break = new_label(s); + + push_break_entry(s->cur_func, &break_entry, + label_name, label_break, label_cont, 0); + + if (next_token(s)) + goto fail; + + set_eval_ret_undefined(s); + + emit_label(s, label_cont); + if (js_parse_expr_paren(s)) + goto fail; + emit_goto(s, OP_if_false, label_break); + + if (js_parse_statement(s)) + goto fail; + emit_goto(s, OP_goto, label_cont); + + emit_label(s, label_break); + + pop_break_entry(s->cur_func); + } + break; + case TOK_DO: + { + int label_cont, label_break, label1; + BlockEnv break_entry; + + label_cont = new_label(s); + label_break = new_label(s); + label1 = new_label(s); + + push_break_entry(s->cur_func, &break_entry, + label_name, label_break, label_cont, 0); + + if (next_token(s)) + goto fail; + + emit_label(s, label1); + + set_eval_ret_undefined(s); + + if (js_parse_statement(s)) + goto fail; + + emit_label(s, label_cont); + if (js_parse_expect(s, TOK_WHILE)) + goto fail; + if (js_parse_expr_paren(s)) + goto fail; + /* Insert semicolon if missing */ + if (s->token.val == ';') { + if (next_token(s)) + goto fail; + } + emit_goto(s, OP_if_true, label1); + + emit_label(s, label_break); + + pop_break_entry(s->cur_func); + } + break; + case TOK_FOR: + { + int label_cont, label_break, label_body, label_test; + int pos_cont, pos_body, block_scope_level; + BlockEnv break_entry; + int tok, bits; + BOOL is_async; + + if (next_token(s)) + goto fail; + + set_eval_ret_undefined(s); + bits = 0; + is_async = FALSE; + if (s->token.val == '(') { + js_parse_skip_parens_token(s, &bits, FALSE); + } else if (s->token.val == TOK_AWAIT) { + if (!(s->cur_func->func_kind & JS_FUNC_ASYNC)) { + js_parse_error(s, "for await is only valid in asynchronous functions"); + goto fail; + } + is_async = TRUE; + if (next_token(s)) + goto fail; + s->cur_func->has_await = TRUE; + } + if (js_parse_expect(s, '(')) + goto fail; + + if (!(bits & SKIP_HAS_SEMI)) { + /* parse for/in or for/of */ + if (js_parse_for_in_of(s, label_name, is_async)) + goto fail; + break; + } + block_scope_level = s->cur_func->scope_level; + + /* create scope for the lexical variables declared in the initial, + test and increment expressions */ + push_scope(s); + /* initial expression */ + tok = s->token.val; + if (tok != ';') { + switch (is_let(s, DECL_MASK_OTHER)) { + case TRUE: + tok = TOK_LET; + break; + case FALSE: + break; + default: + goto fail; + } + if (tok == TOK_VAR || tok == TOK_LET || tok == TOK_CONST) { + if (next_token(s)) + goto fail; + if (js_parse_var(s, FALSE, tok, FALSE)) + goto fail; + } else { + if (js_parse_expr2(s, FALSE)) + goto fail; + emit_op(s, OP_drop); + } + + /* close the closures before the first iteration */ + close_scopes(s, s->cur_func->scope_level, block_scope_level); + } + if (js_parse_expect(s, ';')) + goto fail; + + label_test = new_label(s); + label_cont = new_label(s); + label_body = new_label(s); + label_break = new_label(s); + + push_break_entry(s->cur_func, &break_entry, + label_name, label_break, label_cont, 0); + + /* test expression */ + if (s->token.val == ';') { + /* no test expression */ + label_test = label_body; + } else { + emit_label(s, label_test); + if (js_parse_expr(s)) + goto fail; + emit_goto(s, OP_if_false, label_break); + } + if (js_parse_expect(s, ';')) + goto fail; + + if (s->token.val == ')') { + /* no end expression */ + break_entry.label_cont = label_cont = label_test; + pos_cont = 0; /* avoid warning */ + } else { + /* skip the end expression */ + emit_goto(s, OP_goto, label_body); + + pos_cont = s->cur_func->byte_code.size; + emit_label(s, label_cont); + if (js_parse_expr(s)) + goto fail; + emit_op(s, OP_drop); + if (label_test != label_body) + emit_goto(s, OP_goto, label_test); + } + if (js_parse_expect(s, ')')) + goto fail; + + pos_body = s->cur_func->byte_code.size; + emit_label(s, label_body); + if (js_parse_statement(s)) + goto fail; + + /* close the closures before the next iteration */ + /* XXX: check continue case */ + close_scopes(s, s->cur_func->scope_level, block_scope_level); + + if (label_test != label_body && label_cont != label_test) { + /* move the increment code here */ + DynBuf *bc = &s->cur_func->byte_code; + int chunk_size = pos_body - pos_cont; + int offset = bc->size - pos_cont; + int i; + dbuf_realloc(bc, bc->size + chunk_size); + dbuf_put(bc, bc->buf + pos_cont, chunk_size); + memset(bc->buf + pos_cont, OP_nop, chunk_size); + /* increment part ends with a goto */ + s->cur_func->last_opcode_pos = bc->size - 5; + /* relocate labels */ + for (i = label_cont; i < s->cur_func->label_count; i++) { + LabelSlot *ls = &s->cur_func->label_slots[i]; + if (ls->pos >= pos_cont && ls->pos < pos_body) + ls->pos += offset; + } + } else { + emit_goto(s, OP_goto, label_cont); + } + + emit_label(s, label_break); + + pop_break_entry(s->cur_func); + pop_scope(s); + } + break; + case TOK_BREAK: + case TOK_CONTINUE: + { + int is_cont = s->token.val - TOK_BREAK; + int label; + + if (next_token(s)) + goto fail; + if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) + label = s->token.u.ident.atom; + else + label = JS_ATOM_NULL; + if (emit_break(s, label, is_cont)) + goto fail; + if (label != JS_ATOM_NULL) { + if (next_token(s)) + goto fail; + } + if (js_parse_expect_semi(s)) + goto fail; + } + break; + case TOK_SWITCH: + { + int label_case, label_break, label1; + int default_label_pos; + BlockEnv break_entry; + + if (next_token(s)) + goto fail; + + set_eval_ret_undefined(s); + if (js_parse_expr_paren(s)) + goto fail; + + push_scope(s); + label_break = new_label(s); + push_break_entry(s->cur_func, &break_entry, + label_name, label_break, -1, 1); + + if (js_parse_expect(s, '{')) + goto fail; + + default_label_pos = -1; + label_case = -1; + while (s->token.val != '}') { + if (s->token.val == TOK_CASE) { + label1 = -1; + if (label_case >= 0) { + /* skip the case if needed */ + label1 = emit_goto(s, OP_goto, -1); + } + emit_label(s, label_case); + label_case = -1; + for (;;) { + /* parse a sequence of case clauses */ + if (next_token(s)) + goto fail; + emit_op(s, OP_dup); + if (js_parse_expr(s)) + goto fail; + if (js_parse_expect(s, ':')) + goto fail; + emit_op(s, OP_strict_eq); + if (s->token.val == TOK_CASE) { + label1 = emit_goto(s, OP_if_true, label1); + } else { + label_case = emit_goto(s, OP_if_false, -1); + emit_label(s, label1); + break; + } + } + } else if (s->token.val == TOK_DEFAULT) { + if (next_token(s)) + goto fail; + if (js_parse_expect(s, ':')) + goto fail; + if (default_label_pos >= 0) { + js_parse_error(s, "duplicate default"); + goto fail; + } + if (label_case < 0) { + /* falling thru direct from switch expression */ + label_case = emit_goto(s, OP_goto, -1); + } + /* Emit a dummy label opcode. Label will be patched after + the end of the switch body. Do not use emit_label(s, 0) + because it would clobber label 0 address, preventing + proper optimizer operation. + */ + emit_op(s, OP_label); + emit_u32(s, 0); + default_label_pos = s->cur_func->byte_code.size - 4; + } else { + if (label_case < 0) { + /* falling thru direct from switch expression */ + js_parse_error(s, "invalid switch statement"); + goto fail; + } + if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) + goto fail; + } + } + if (js_parse_expect(s, '}')) + goto fail; + if (default_label_pos >= 0) { + /* Ugly patch for the the `default` label, shameful and risky */ + put_u32(s->cur_func->byte_code.buf + default_label_pos, + label_case); + s->cur_func->label_slots[label_case].pos = default_label_pos + 4; + } else { + emit_label(s, label_case); + } + emit_label(s, label_break); + emit_op(s, OP_drop); /* drop the switch expression */ + + pop_break_entry(s->cur_func); + pop_scope(s); + } + break; + case TOK_TRY: + { + int label_catch, label_catch2, label_finally, label_end; + JSAtom name; + BlockEnv block_env; + + set_eval_ret_undefined(s); + if (next_token(s)) + goto fail; + label_catch = new_label(s); + label_catch2 = new_label(s); + label_finally = new_label(s); + label_end = new_label(s); + + emit_goto(s, OP_catch, label_catch); + + push_break_entry(s->cur_func, &block_env, + JS_ATOM_NULL, -1, -1, 1); + block_env.label_finally = label_finally; + + if (js_parse_block(s)) + goto fail; + + pop_break_entry(s->cur_func); + + if (js_is_live_code(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); + + emit_goto(s, OP_goto, label_end); + } + + if (s->token.val == TOK_CATCH) { + if (next_token(s)) + goto fail; + + push_scope(s); /* catch variable */ + emit_label(s, label_catch); + + if (s->token.val == '{') { + /* support optional-catch-binding feature */ + emit_op(s, OP_drop); /* pop the exception object */ + } else { + if (js_parse_expect(s, '(')) + goto fail; + if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved)) { + if (s->token.val == '[' || s->token.val == '{') { + /* XXX: TOK_LET is not completely correct */ + if (js_parse_destructuring_element(s, TOK_LET, 0, TRUE, -1, TRUE) < 0) + goto fail; + } else { + js_parse_error(s, "identifier expected"); + goto fail; + } + } else { + name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s) + || js_define_var(s, name, TOK_CATCH) < 0) { + JS_FreeAtom(ctx, name); + goto fail; + } + /* store the exception value in the catch variable */ + emit_op(s, OP_scope_put_var); + emit_u32(s, name); + emit_u16(s, s->cur_func->scope_level); + } + if (js_parse_expect(s, ')')) + goto fail; + } + /* XXX: should keep the address to nop it out if there is no finally block */ + emit_goto(s, OP_catch, label_catch2); + + push_scope(s); /* catch block */ + push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, + -1, -1, 1); + block_env.label_finally = label_finally; + + if (js_parse_block(s)) + goto fail; + + pop_break_entry(s->cur_func); + pop_scope(s); /* catch block */ + pop_scope(s); /* catch variable */ + + if (js_is_live_code(s)) { + /* drop the catch2 offset */ + emit_op(s, OP_drop); + /* XXX: should keep the address to nop it out if there is no finally block */ + /* 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"); + goto fail; + } + emit_label(s, label_finally); + if (s->token.val == TOK_FINALLY) { + int saved_eval_ret_idx = 0; /* avoid warning */ + + if (next_token(s)) + goto fail; + /* on the stack: ret_value gosub_ret_value */ + push_break_entry(s->cur_func, &block_env, JS_ATOM_NULL, + -1, -1, 2); + + if (s->cur_func->eval_ret_idx >= 0) { + /* 'finally' updates eval_ret only if not a normal + termination */ + saved_eval_ret_idx = + add_var(s->ctx, s->cur_func, JS_ATOM__ret_); + if (saved_eval_ret_idx < 0) + goto fail; + emit_op(s, OP_get_loc); + emit_u16(s, s->cur_func->eval_ret_idx); + emit_op(s, OP_put_loc); + emit_u16(s, saved_eval_ret_idx); + set_eval_ret_undefined(s); + } + + if (js_parse_block(s)) + goto fail; + + if (s->cur_func->eval_ret_idx >= 0) { + emit_op(s, OP_get_loc); + emit_u16(s, saved_eval_ret_idx); + emit_op(s, OP_put_loc); + emit_u16(s, s->cur_func->eval_ret_idx); + } + pop_break_entry(s->cur_func); + } + emit_op(s, OP_ret); + emit_label(s, label_end); + } + break; + case ';': + /* empty statement */ + if (next_token(s)) + goto fail; + break; + case TOK_WITH: + if (s->cur_func->is_strict_mode) { + js_parse_error(s, "invalid keyword: with"); + goto fail; + } else { + int with_idx; + + if (next_token(s)) + goto fail; + + if (js_parse_expr_paren(s)) + goto fail; + + push_scope(s); + with_idx = define_var(s, s->cur_func, JS_ATOM__with_, + JS_VAR_DEF_WITH); + if (with_idx < 0) + goto fail; + emit_op(s, OP_to_object); + emit_op(s, OP_put_loc); + emit_u16(s, with_idx); + + set_eval_ret_undefined(s); + if (js_parse_statement(s)) + goto fail; + + /* Popping scope drops lexical context for the with object variable */ + pop_scope(s); + } + break; + case TOK_FUNCTION: + /* ES6 Annex B.3.2 and B.3.3 semantics */ + if (!(decl_mask & DECL_MASK_FUNC)) + goto func_decl_error; + if (!(decl_mask & DECL_MASK_OTHER) && peek_token(s, FALSE) == '*') + goto func_decl_error; + goto parse_func_var; + case TOK_IDENT: + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier(s); + goto fail; + } + /* Determine if `let` introduces a Declaration or an ExpressionStatement */ + switch (is_let(s, decl_mask)) { + case TRUE: + tok = TOK_LET; + goto haslet; + case FALSE: + break; + default: + goto fail; + } + if (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) == TOK_FUNCTION) { + if (!(decl_mask & DECL_MASK_OTHER)) { + func_decl_error: + js_parse_error(s, "function declarations can't appear in single-statement context"); + goto fail; + } + parse_func_var: + if (js_parse_function_decl(s, JS_PARSE_FUNC_VAR, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + s->token.line_num, + s->token.col_num)) + goto fail; + break; + } + goto hasexpr; + + case TOK_CLASS: + if (!(decl_mask & DECL_MASK_OTHER)) { + js_parse_error(s, "class declarations can't appear in single-statement context"); + goto fail; + } + if (js_parse_class(s, FALSE, JS_PARSE_EXPORT_NONE)) + return -1; + break; + + case TOK_DEBUGGER: + /* currently no debugger, so just skip the keyword */ + if (next_token(s)) + goto fail; + if (js_parse_expect_semi(s)) + goto fail; + break; + + case TOK_ENUM: + case TOK_EXPORT: + case TOK_EXTENDS: + js_unsupported_keyword(s, s->token.u.ident.atom); + goto fail; + + default: + hasexpr: + emit_source_loc(s); + if (js_parse_expr(s)) + goto fail; + if (s->cur_func->eval_ret_idx >= 0) { + /* store the expression value so that it can be returned + by eval() */ + emit_op(s, OP_put_loc); + emit_u16(s, s->cur_func->eval_ret_idx); + } else { + emit_op(s, OP_drop); /* drop the result */ + } + if (js_parse_expect_semi(s)) + goto fail; + break; + } +done: + JS_FreeAtom(ctx, label_name); + return 0; +fail: + JS_FreeAtom(ctx, label_name); + return -1; +} + +/* 'name' is freed */ +static JSModuleDef *js_new_module_def(JSContext *ctx, JSAtom name) +{ + JSModuleDef *m; + m = js_mallocz(ctx, sizeof(*m)); + if (!m) { + JS_FreeAtom(ctx, name); + return NULL; + } + m->header.ref_count = 1; + m->module_name = name; + m->module_ns = JS_UNDEFINED; + m->func_obj = JS_UNDEFINED; + m->eval_exception = JS_UNDEFINED; + m->meta_obj = JS_UNDEFINED; + m->promise = JS_UNDEFINED; + m->resolving_funcs[0] = JS_UNDEFINED; + m->resolving_funcs[1] = JS_UNDEFINED; + list_add_tail(&m->link, &ctx->loaded_modules); + return m; +} + +static void js_mark_module_def(JSRuntime *rt, JSModuleDef *m, + JS_MarkFunc *mark_func) +{ + int i; + + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL && + me->u.local.var_ref) { + mark_func(rt, &me->u.local.var_ref->header); + } + } + + JS_MarkValue(rt, m->module_ns, mark_func); + JS_MarkValue(rt, m->func_obj, mark_func); + JS_MarkValue(rt, m->eval_exception, mark_func); + JS_MarkValue(rt, m->meta_obj, mark_func); + JS_MarkValue(rt, m->promise, mark_func); + JS_MarkValue(rt, m->resolving_funcs[0], mark_func); + JS_MarkValue(rt, m->resolving_funcs[1], mark_func); +} + +static void js_free_module_def(JSContext *ctx, JSModuleDef *m) +{ + int i; + + JS_FreeAtom(ctx, m->module_name); + + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + JS_FreeAtom(ctx, rme->module_name); + } + js_free(ctx, m->req_module_entries); + + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL) + free_var_ref(ctx->rt, me->u.local.var_ref); + JS_FreeAtom(ctx, me->export_name); + JS_FreeAtom(ctx, me->local_name); + } + js_free(ctx, m->export_entries); + + js_free(ctx, m->star_export_entries); + + for(i = 0; i < m->import_entries_count; i++) { + JSImportEntry *mi = &m->import_entries[i]; + JS_FreeAtom(ctx, mi->import_name); + } + js_free(ctx, m->import_entries); + js_free(ctx, m->async_parent_modules); + + JS_FreeValue(ctx, m->module_ns); + JS_FreeValue(ctx, m->func_obj); + JS_FreeValue(ctx, m->eval_exception); + JS_FreeValue(ctx, m->meta_obj); + JS_FreeValue(ctx, m->promise); + JS_FreeValue(ctx, m->resolving_funcs[0]); + JS_FreeValue(ctx, m->resolving_funcs[1]); + list_del(&m->link); + js_free(ctx, m); +} + +static int add_req_module_entry(JSContext *ctx, JSModuleDef *m, + JSAtom module_name) +{ + JSReqModuleEntry *rme; + int i; + + /* no need to add the module request if it is already present */ + for(i = 0; i < m->req_module_entries_count; i++) { + rme = &m->req_module_entries[i]; + if (rme->module_name == module_name) + return i; + } + + if (js_resize_array(ctx, (void **)&m->req_module_entries, + sizeof(JSReqModuleEntry), + &m->req_module_entries_size, + m->req_module_entries_count + 1)) + return -1; + rme = &m->req_module_entries[m->req_module_entries_count++]; + rme->module_name = JS_DupAtom(ctx, module_name); + rme->module = NULL; + return i; +} + +static JSExportEntry *find_export_entry(JSContext *ctx, const JSModuleDef *m, + JSAtom export_name) +{ + JSExportEntry *me; + int i; + for(i = 0; i < m->export_entries_count; i++) { + me = &m->export_entries[i]; + if (me->export_name == export_name) + return me; + } + return NULL; +} + +static JSExportEntry *add_export_entry2(JSContext *ctx, + JSParseState *s, JSModuleDef *m, + JSAtom local_name, JSAtom export_name, + JSExportTypeEnum export_type) +{ + JSExportEntry *me; + + if (find_export_entry(ctx, m, export_name)) { + char buf1[ATOM_GET_STR_BUF_SIZE]; + if (s) { + js_parse_error(s, "duplicate exported name '%s'", + JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name)); + } else { + JS_ThrowSyntaxErrorAtom(ctx, "duplicate exported name '%s'", export_name); + } + return NULL; + } + + if (js_resize_array(ctx, (void **)&m->export_entries, + sizeof(JSExportEntry), + &m->export_entries_size, + m->export_entries_count + 1)) + return NULL; + me = &m->export_entries[m->export_entries_count++]; + memset(me, 0, sizeof(*me)); + me->local_name = JS_DupAtom(ctx, local_name); + me->export_name = JS_DupAtom(ctx, export_name); + me->export_type = export_type; + return me; +} + +static JSExportEntry *add_export_entry(JSParseState *s, JSModuleDef *m, + JSAtom local_name, JSAtom export_name, + JSExportTypeEnum export_type) +{ + return add_export_entry2(s->ctx, s, m, local_name, export_name, + export_type); +} + +static int add_star_export_entry(JSContext *ctx, JSModuleDef *m, + int req_module_idx) +{ + JSStarExportEntry *se; + + if (js_resize_array(ctx, (void **)&m->star_export_entries, + sizeof(JSStarExportEntry), + &m->star_export_entries_size, + m->star_export_entries_count + 1)) + return -1; + se = &m->star_export_entries[m->star_export_entries_count++]; + se->req_module_idx = req_module_idx; + return 0; +} + +/* create a C module */ +/* `name_str` may be pure ASCII or UTF-8 encoded */ +JSModuleDef *JS_NewCModule(JSContext *ctx, const char *name_str, + JSModuleInitFunc *func) +{ + JSModuleDef *m; + JSAtom name; + name = JS_NewAtom(ctx, name_str); + if (name == JS_ATOM_NULL) + return NULL; + m = js_new_module_def(ctx, name); + m->init_func = func; + return m; +} + +/* `export_name` may be pure ASCII or UTF-8 encoded */ +int JS_AddModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name) +{ + JSExportEntry *me; + JSAtom name; + name = JS_NewAtom(ctx, export_name); + if (name == JS_ATOM_NULL) + return -1; + me = add_export_entry2(ctx, NULL, m, JS_ATOM_NULL, name, + JS_EXPORT_TYPE_LOCAL); + JS_FreeAtom(ctx, name); + if (!me) + return -1; + else + return 0; +} + +/* `export_name` may be pure ASCII or UTF-8 encoded */ +int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name, + JSValue val) +{ + JSExportEntry *me; + JSAtom name; + name = JS_NewAtom(ctx, export_name); + if (name == JS_ATOM_NULL) + goto fail; + me = find_export_entry(ctx, m, name); + JS_FreeAtom(ctx, name); + if (!me) + goto fail; + set_value(ctx, me->u.local.var_ref->pvalue, val); + return 0; + fail: + JS_FreeValue(ctx, val); + return -1; +} + +void JS_SetModuleLoaderFunc(JSRuntime *rt, + JSModuleNormalizeFunc *module_normalize, + JSModuleLoaderFunc *module_loader, void *opaque) +{ + rt->module_normalize_func = module_normalize; + rt->module_loader_func = module_loader; + rt->module_loader_opaque = opaque; +} + +/* default module filename normalizer */ +static char *js_default_module_normalize_name(JSContext *ctx, + const char *base_name, + const char *name) +{ + char *filename, *p; + const char *r; + int cap; + int len; + + if (name[0] != '.') { + /* if no initial dot, the module name is not modified */ + return js_strdup(ctx, name); + } + + p = strrchr(base_name, '/'); + if (p) + len = p - base_name; + else + len = 0; + + cap = len + strlen(name) + 1 + 1; + filename = js_malloc(ctx, cap); + if (!filename) + return NULL; + memcpy(filename, base_name, len); + filename[len] = '\0'; + + /* we only normalize the leading '..' or '.' */ + r = name; + for(;;) { + if (r[0] == '.' && r[1] == '/') { + r += 2; + } else if (r[0] == '.' && r[1] == '.' && r[2] == '/') { + /* remove the last path element of filename, except if "." + or ".." */ + if (filename[0] == '\0') + break; + p = strrchr(filename, '/'); + if (!p) + p = filename; + else + p++; + if (!strcmp(p, ".") || !strcmp(p, "..")) + break; + if (p > filename) + p--; + *p = '\0'; + r += 3; + } else { + break; + } + } + if (filename[0] != '\0') + js__pstrcat(filename, cap, "/"); + js__pstrcat(filename, cap, r); + // printf("normalize: %s %s -> %s\n", base_name, name, filename); + return filename; +} + +static JSModuleDef *js_find_loaded_module(JSContext *ctx, JSAtom name) +{ + struct list_head *el; + JSModuleDef *m; + + /* first look at the loaded modules */ + list_for_each(el, &ctx->loaded_modules) { + m = list_entry(el, JSModuleDef, link); + if (m->module_name == name) + return m; + } + return NULL; +} + +/* return NULL in case of exception (e.g. module could not be loaded) */ +/* `base_cname` and `cname1` may be pure ASCII or UTF-8 encoded */ +static JSModuleDef *js_host_resolve_imported_module(JSContext *ctx, + const char *base_cname, + const char *cname1) +{ + JSRuntime *rt = ctx->rt; + JSModuleDef *m; + char *cname; + JSAtom module_name; + + if (!rt->module_normalize_func) { + cname = js_default_module_normalize_name(ctx, base_cname, cname1); + } else { + cname = rt->module_normalize_func(ctx, base_cname, cname1, + rt->module_loader_opaque); + } + if (!cname) + return NULL; + + module_name = JS_NewAtom(ctx, cname); + if (module_name == JS_ATOM_NULL) { + js_free(ctx, cname); + return NULL; + } + + /* first look at the loaded modules */ + m = js_find_loaded_module(ctx, module_name); + if (m) { + js_free(ctx, cname); + JS_FreeAtom(ctx, module_name); + return m; + } + + JS_FreeAtom(ctx, module_name); + + /* load the module */ + if (!rt->module_loader_func) { + /* XXX: use a syntax error ? */ + // XXX: update JS_DetectModule when you change this + JS_ThrowReferenceError(ctx, "could not load module '%s'", + cname); + js_free(ctx, cname); + return NULL; + } + + m = rt->module_loader_func(ctx, cname, rt->module_loader_opaque); + js_free(ctx, cname); + return m; +} + +static JSModuleDef *js_host_resolve_imported_module_atom(JSContext *ctx, + JSAtom base_module_name, + JSAtom module_name1) +{ + const char *base_cname, *cname; + JSModuleDef *m; + + base_cname = JS_AtomToCString(ctx, base_module_name); + if (!base_cname) + return NULL; + cname = JS_AtomToCString(ctx, module_name1); + if (!cname) { + JS_FreeCString(ctx, base_cname); + return NULL; + } + m = js_host_resolve_imported_module(ctx, base_cname, cname); + JS_FreeCString(ctx, base_cname); + JS_FreeCString(ctx, cname); + return m; +} + +typedef struct JSResolveEntry { + JSModuleDef *module; + JSAtom name; +} JSResolveEntry; + +typedef struct JSResolveState { + JSResolveEntry *array; + int size; + int count; +} JSResolveState; + +static int find_resolve_entry(JSResolveState *s, + JSModuleDef *m, JSAtom name) +{ + int i; + for(i = 0; i < s->count; i++) { + JSResolveEntry *re = &s->array[i]; + if (re->module == m && re->name == name) + return i; + } + return -1; +} + +static int add_resolve_entry(JSContext *ctx, JSResolveState *s, + JSModuleDef *m, JSAtom name) +{ + JSResolveEntry *re; + + if (js_resize_array(ctx, (void **)&s->array, + sizeof(JSResolveEntry), + &s->size, s->count + 1)) + return -1; + re = &s->array[s->count++]; + re->module = m; + re->name = JS_DupAtom(ctx, name); + return 0; +} + +typedef enum JSResolveResultEnum { + JS_RESOLVE_RES_EXCEPTION = -1, /* memory alloc error */ + JS_RESOLVE_RES_FOUND = 0, + JS_RESOLVE_RES_NOT_FOUND, + JS_RESOLVE_RES_CIRCULAR, + JS_RESOLVE_RES_AMBIGUOUS, +} JSResolveResultEnum; + +static JSResolveResultEnum js_resolve_export1(JSContext *ctx, + JSModuleDef **pmodule, + JSExportEntry **pme, + JSModuleDef *m, + JSAtom export_name, + JSResolveState *s) +{ + JSExportEntry *me; + + *pmodule = NULL; + *pme = NULL; + if (find_resolve_entry(s, m, export_name) >= 0) + return JS_RESOLVE_RES_CIRCULAR; + if (add_resolve_entry(ctx, s, m, export_name) < 0) + return JS_RESOLVE_RES_EXCEPTION; + me = find_export_entry(ctx, m, export_name); + if (me) { + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + /* local export */ + *pmodule = m; + *pme = me; + return JS_RESOLVE_RES_FOUND; + } else { + /* indirect export */ + JSModuleDef *m1; + m1 = m->req_module_entries[me->u.req_module_idx].module; + if (me->local_name == JS_ATOM__star_) { + /* export ns from */ + *pmodule = m; + *pme = me; + return JS_RESOLVE_RES_FOUND; + } else { + return js_resolve_export1(ctx, pmodule, pme, m1, + me->local_name, s); + } + } + } else { + if (export_name != JS_ATOM_default) { + /* not found in direct or indirect exports: try star exports */ + int i; + + for(i = 0; i < m->star_export_entries_count; i++) { + JSStarExportEntry *se = &m->star_export_entries[i]; + JSModuleDef *m1, *res_m; + JSExportEntry *res_me; + JSResolveResultEnum ret; + + m1 = m->req_module_entries[se->req_module_idx].module; + ret = js_resolve_export1(ctx, &res_m, &res_me, m1, + export_name, s); + if (ret == JS_RESOLVE_RES_AMBIGUOUS || + ret == JS_RESOLVE_RES_EXCEPTION) { + return ret; + } else if (ret == JS_RESOLVE_RES_FOUND) { + if (*pme != NULL) { + if (*pmodule != res_m || + res_me->local_name != (*pme)->local_name) { + *pmodule = NULL; + *pme = NULL; + return JS_RESOLVE_RES_AMBIGUOUS; + } + } else { + *pmodule = res_m; + *pme = res_me; + } + } + } + if (*pme != NULL) + return JS_RESOLVE_RES_FOUND; + } + return JS_RESOLVE_RES_NOT_FOUND; + } +} + +/* If the return value is JS_RESOLVE_RES_FOUND, return the module + (*pmodule) and the corresponding local export entry + (*pme). Otherwise return (NULL, NULL) */ +static JSResolveResultEnum js_resolve_export(JSContext *ctx, + JSModuleDef **pmodule, + JSExportEntry **pme, + JSModuleDef *m, + JSAtom export_name) +{ + JSResolveState ss, *s = &ss; + int i; + JSResolveResultEnum ret; + + s->array = NULL; + s->size = 0; + s->count = 0; + + ret = js_resolve_export1(ctx, pmodule, pme, m, export_name, s); + + for(i = 0; i < s->count; i++) + JS_FreeAtom(ctx, s->array[i].name); + js_free(ctx, s->array); + + return ret; +} + +static void js_resolve_export_throw_error(JSContext *ctx, + JSResolveResultEnum res, + JSModuleDef *m, JSAtom export_name) +{ + char buf1[ATOM_GET_STR_BUF_SIZE]; + char buf2[ATOM_GET_STR_BUF_SIZE]; + switch(res) { + case JS_RESOLVE_RES_EXCEPTION: + break; + default: + case JS_RESOLVE_RES_NOT_FOUND: + JS_ThrowSyntaxError(ctx, "Could not find export '%s' in module '%s'", + JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), + JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); + break; + case JS_RESOLVE_RES_CIRCULAR: + JS_ThrowSyntaxError(ctx, "circular reference when looking for export '%s' in module '%s'", + JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), + JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); + break; + case JS_RESOLVE_RES_AMBIGUOUS: + JS_ThrowSyntaxError(ctx, "export '%s' in module '%s' is ambiguous", + JS_AtomGetStr(ctx, buf1, sizeof(buf1), export_name), + JS_AtomGetStr(ctx, buf2, sizeof(buf2), m->module_name)); + break; + } +} + + +typedef enum { + EXPORTED_NAME_AMBIGUOUS, + EXPORTED_NAME_NORMAL, + EXPORTED_NAME_NS, +} ExportedNameEntryEnum; + +typedef struct ExportedNameEntry { + JSAtom export_name; + ExportedNameEntryEnum export_type; + union { + JSExportEntry *me; /* using when the list is built */ + JSVarRef *var_ref; /* EXPORTED_NAME_NORMAL */ + JSModuleDef *module; /* for EXPORTED_NAME_NS */ + } u; +} ExportedNameEntry; + +typedef struct GetExportNamesState { + JSModuleDef **modules; + int modules_size; + int modules_count; + + ExportedNameEntry *exported_names; + int exported_names_size; + int exported_names_count; +} GetExportNamesState; + +static int find_exported_name(GetExportNamesState *s, JSAtom name) +{ + int i; + for(i = 0; i < s->exported_names_count; i++) { + if (s->exported_names[i].export_name == name) + return i; + } + return -1; +} + +static __exception int get_exported_names(JSContext *ctx, + GetExportNamesState *s, + JSModuleDef *m, BOOL from_star) +{ + ExportedNameEntry *en; + int i, j; + + /* check circular reference */ + for(i = 0; i < s->modules_count; i++) { + if (s->modules[i] == m) + return 0; + } + if (js_resize_array(ctx, (void **)&s->modules, sizeof(s->modules[0]), + &s->modules_size, s->modules_count + 1)) + return -1; + s->modules[s->modules_count++] = m; + + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (from_star && me->export_name == JS_ATOM_default) + continue; + j = find_exported_name(s, me->export_name); + if (j < 0) { + if (js_resize_array(ctx, (void **)&s->exported_names, sizeof(s->exported_names[0]), + &s->exported_names_size, + s->exported_names_count + 1)) + return -1; + en = &s->exported_names[s->exported_names_count++]; + en->export_name = me->export_name; + /* avoid a second lookup for simple module exports */ + if (from_star || me->export_type != JS_EXPORT_TYPE_LOCAL) + en->u.me = NULL; + else + en->u.me = me; + } else { + en = &s->exported_names[j]; + en->u.me = NULL; + } + } + for(i = 0; i < m->star_export_entries_count; i++) { + JSStarExportEntry *se = &m->star_export_entries[i]; + JSModuleDef *m1; + m1 = m->req_module_entries[se->req_module_idx].module; + if (get_exported_names(ctx, s, m1, TRUE)) + return -1; + } + return 0; +} + +/* Unfortunately, the spec gives a different behavior from GetOwnProperty ! */ +static int js_module_ns_has(JSContext *ctx, JSValue obj, JSAtom atom) +{ + return (find_own_property1(JS_VALUE_GET_OBJ(obj), atom) != NULL); +} + +static const JSClassExoticMethods js_module_ns_exotic_methods = { + .has_property = js_module_ns_has, +}; + +static int exported_names_cmp(const void *p1, const void *p2, void *opaque) +{ + JSContext *ctx = opaque; + const ExportedNameEntry *me1 = p1; + const ExportedNameEntry *me2 = p2; + JSValue str1, str2; + int ret; + + /* XXX: should avoid allocation memory in atom comparison */ + str1 = JS_AtomToString(ctx, me1->export_name); + str2 = JS_AtomToString(ctx, me2->export_name); + if (JS_IsException(str1) || JS_IsException(str2)) { + /* XXX: raise an error ? */ + ret = 0; + } else { + ret = js_string_compare(ctx, JS_VALUE_GET_STRING(str1), + JS_VALUE_GET_STRING(str2)); + } + JS_FreeValue(ctx, str1); + JS_FreeValue(ctx, str2); + return ret; +} + +static JSValue js_module_ns_autoinit(JSContext *ctx, JSObject *p, JSAtom atom, + void *opaque) +{ + JSModuleDef *m = opaque; + return JS_GetModuleNamespace(ctx, m); +} + +static JSValue js_build_module_ns(JSContext *ctx, JSModuleDef *m) +{ + JSValue obj; + JSObject *p; + GetExportNamesState s_s, *s = &s_s; + int i, ret; + JSProperty *pr; + + obj = JS_NewObjectClass(ctx, JS_CLASS_MODULE_NS); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_GET_OBJ(obj); + + memset(s, 0, sizeof(*s)); + ret = get_exported_names(ctx, s, m, FALSE); + js_free(ctx, s->modules); + if (ret) + goto fail; + + /* Resolve the exported names. The ambiguous exports are removed */ + for(i = 0; i < s->exported_names_count; i++) { + ExportedNameEntry *en = &s->exported_names[i]; + JSResolveResultEnum res; + JSExportEntry *res_me; + JSModuleDef *res_m; + + if (en->u.me) { + res_me = en->u.me; /* fast case: no resolution needed */ + res_m = m; + res = JS_RESOLVE_RES_FOUND; + } else { + res = js_resolve_export(ctx, &res_m, &res_me, m, + en->export_name); + } + if (res != JS_RESOLVE_RES_FOUND) { + if (res != JS_RESOLVE_RES_AMBIGUOUS) { + js_resolve_export_throw_error(ctx, res, m, en->export_name); + goto fail; + } + en->export_type = EXPORTED_NAME_AMBIGUOUS; + } else { + if (res_me->local_name == JS_ATOM__star_) { + en->export_type = EXPORTED_NAME_NS; + en->u.module = res_m->req_module_entries[res_me->u.req_module_idx].module; + } else { + en->export_type = EXPORTED_NAME_NORMAL; + if (res_me->u.local.var_ref) { + en->u.var_ref = res_me->u.local.var_ref; + } else { + JSObject *p1 = JS_VALUE_GET_OBJ(res_m->func_obj); + p1 = JS_VALUE_GET_OBJ(res_m->func_obj); + en->u.var_ref = p1->u.func.var_refs[res_me->u.local.var_idx]; + } + } + } + } + + /* sort the exported names */ + rqsort(s->exported_names, s->exported_names_count, + sizeof(s->exported_names[0]), exported_names_cmp, ctx); + + for(i = 0; i < s->exported_names_count; i++) { + ExportedNameEntry *en = &s->exported_names[i]; + switch(en->export_type) { + case EXPORTED_NAME_NORMAL: + { + JSVarRef *var_ref = en->u.var_ref; + if (!var_ref) { + js_resolve_export_throw_error(ctx, JS_RESOLVE_RES_CIRCULAR, + m, en->export_name); + goto fail; + } + pr = add_property(ctx, p, en->export_name, + JS_PROP_ENUMERABLE | JS_PROP_WRITABLE | + JS_PROP_VARREF); + if (!pr) + goto fail; + var_ref->header.ref_count++; + pr->u.var_ref = var_ref; + } + break; + case EXPORTED_NAME_NS: + /* the exported namespace must be created on demand */ + if (JS_DefineAutoInitProperty(ctx, obj, + en->export_name, + JS_AUTOINIT_ID_MODULE_NS, + en->u.module, JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) + goto fail; + break; + default: + break; + } + } + + js_free(ctx, s->exported_names); + + JS_DefinePropertyValue(ctx, obj, JS_ATOM_Symbol_toStringTag, + JS_AtomToString(ctx, JS_ATOM_Module), + 0); + + p->extensible = FALSE; + return obj; + fail: + js_free(ctx, s->exported_names); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m) +{ + if (JS_IsUndefined(m->module_ns)) { + JSValue val; + val = js_build_module_ns(ctx, m); + if (JS_IsException(val)) + return JS_EXCEPTION; + m->module_ns = val; + } + return js_dup(m->module_ns); +} + +#ifdef DUMP_MODULE_RESOLVE +#define module_trace(ctx, ...) \ + do { \ + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) \ + printf(__VA_ARGS__); \ + } while (0) +#else +#define module_trace(...) +#endif + +/* Load all the required modules for module 'm' */ +static int js_resolve_module(JSContext *ctx, JSModuleDef *m) +{ + int i; + JSModuleDef *m1; + + if (m->resolved) + return 0; +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + char buf1[ATOM_GET_STR_BUF_SIZE]; + printf("resolving module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + } +#endif + m->resolved = TRUE; + /* resolve each requested module */ + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + m1 = js_host_resolve_imported_module_atom(ctx, m->module_name, + rme->module_name); + if (!m1) + return -1; + rme->module = m1; + /* already done in js_host_resolve_imported_module() except if + the module was loaded with JS_EvalBinary() */ + if (js_resolve_module(ctx, m1) < 0) + return -1; + } + return 0; +} + +static JSVarRef *js_create_module_var(JSContext *ctx, BOOL is_lexical) +{ + JSVarRef *var_ref; + var_ref = js_malloc(ctx, sizeof(JSVarRef)); + if (!var_ref) + return NULL; + var_ref->header.ref_count = 1; + if (is_lexical) + var_ref->value = JS_UNINITIALIZED; + else + var_ref->value = JS_UNDEFINED; + var_ref->pvalue = &var_ref->value; + var_ref->is_detached = TRUE; + add_gc_object(ctx->rt, &var_ref->header, JS_GC_OBJ_TYPE_VAR_REF); + return var_ref; +} + +/* Create the <eval> function associated with the module */ +static int js_create_module_bytecode_function(JSContext *ctx, JSModuleDef *m) +{ + JSFunctionBytecode *b; + int i; + JSVarRef **var_refs; + JSValue func_obj, bfunc; + JSObject *p; + + bfunc = m->func_obj; + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_BYTECODE_FUNCTION); + + if (JS_IsException(func_obj)) + return -1; + b = JS_VALUE_GET_PTR(bfunc); + + p = JS_VALUE_GET_OBJ(func_obj); + p->u.func.function_bytecode = b; + b->header.ref_count++; + p->u.func.home_object = NULL; + p->u.func.var_refs = NULL; + if (b->closure_var_count) { + var_refs = js_mallocz(ctx, sizeof(var_refs[0]) * b->closure_var_count); + if (!var_refs) + goto fail; + p->u.func.var_refs = var_refs; + + /* create the global variables. The other variables are + imported from other modules */ + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSVarRef *var_ref; + if (cv->is_local) { + var_ref = js_create_module_var(ctx, cv->is_lexical); + if (!var_ref) + goto fail; + + module_trace(ctx, "local %d: %p\n", i, var_ref); + + var_refs[i] = var_ref; + } + } + } + m->func_obj = func_obj; + JS_FreeValue(ctx, bfunc); + return 0; + fail: + JS_FreeValue(ctx, func_obj); + return -1; +} + +/* must be done before js_link_module() because of cyclic references */ +static int js_create_module_function(JSContext *ctx, JSModuleDef *m) +{ + BOOL is_c_module; + int i; + JSVarRef *var_ref; + + if (m->func_created) + return 0; + + is_c_module = (m->init_func != NULL); + + if (is_c_module) { + /* initialize the exported variables */ + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + var_ref = js_create_module_var(ctx, FALSE); + if (!var_ref) + return -1; + me->u.local.var_ref = var_ref; + } + } + } else { + if (js_create_module_bytecode_function(ctx, m)) + return -1; + } + m->func_created = TRUE; + + /* do it on the dependencies */ + + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + if (js_create_module_function(ctx, rme->module) < 0) + return -1; + } + + return 0; +} + + +/* Prepare a module to be executed by resolving all the imported + variables. */ +static int js_inner_module_linking(JSContext *ctx, JSModuleDef *m, + JSModuleDef **pstack_top, int index) +{ + int i; + JSImportEntry *mi; + JSModuleDef *m1; + JSVarRef **var_refs, *var_ref; + JSObject *p; + BOOL is_c_module; + JSValue ret_val; + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return -1; + } + +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + char buf1[ATOM_GET_STR_BUF_SIZE]; + printf("js_inner_module_linking '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + } +#endif + + if (m->status == JS_MODULE_STATUS_LINKING || + m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED) + return index; + + assert(m->status == JS_MODULE_STATUS_UNLINKED); + m->status = JS_MODULE_STATUS_LINKING; + m->dfs_index = index; + m->dfs_ancestor_index = index; + index++; + /* push 'm' on stack */ + m->stack_prev = *pstack_top; + *pstack_top = m; + + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + m1 = rme->module; + index = js_inner_module_linking(ctx, m1, pstack_top, index); + if (index < 0) + goto fail; + assert(m1->status == JS_MODULE_STATUS_LINKING || + m1->status == JS_MODULE_STATUS_LINKED || + m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m1->status == JS_MODULE_STATUS_EVALUATED); + if (m1->status == JS_MODULE_STATUS_LINKING) { + m->dfs_ancestor_index = min_int(m->dfs_ancestor_index, + m1->dfs_ancestor_index); + } + } + +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + char buf1[ATOM_GET_STR_BUF_SIZE]; + printf("instantiating module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + } +#endif + /* check the indirect exports */ + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_INDIRECT && + me->local_name != JS_ATOM__star_) { + JSResolveResultEnum ret; + JSExportEntry *res_me; + JSModuleDef *res_m, *m1; + m1 = m->req_module_entries[me->u.req_module_idx].module; + ret = js_resolve_export(ctx, &res_m, &res_me, m1, me->local_name); + if (ret != JS_RESOLVE_RES_FOUND) { + js_resolve_export_throw_error(ctx, ret, m, me->export_name); + goto fail; + } + } + } + +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + printf("exported bindings:\n"); + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + printf(" name="); print_atom(ctx, me->export_name); + printf(" local="); print_atom(ctx, me->local_name); + printf(" type=%d idx=%d\n", me->export_type, me->u.local.var_idx); + } + } +#endif + + is_c_module = (m->init_func != NULL); + + if (!is_c_module) { + p = JS_VALUE_GET_OBJ(m->func_obj); + var_refs = p->u.func.var_refs; + + for(i = 0; i < m->import_entries_count; i++) { + mi = &m->import_entries[i]; +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + printf("import var_idx=%d name=", mi->var_idx); + print_atom(ctx, mi->import_name); + printf(": "); + } +#endif + m1 = m->req_module_entries[mi->req_module_idx].module; + if (mi->import_name == JS_ATOM__star_) { + JSValue val; + /* name space import */ + val = JS_GetModuleNamespace(ctx, m1); + if (JS_IsException(val)) + goto fail; + set_value(ctx, &var_refs[mi->var_idx]->value, val); + + module_trace(ctx, "namespace\n"); + + } else { + JSResolveResultEnum ret; + JSExportEntry *res_me; + JSModuleDef *res_m; + JSObject *p1; + + ret = js_resolve_export(ctx, &res_m, + &res_me, m1, mi->import_name); + if (ret != JS_RESOLVE_RES_FOUND) { + js_resolve_export_throw_error(ctx, ret, m1, mi->import_name); + goto fail; + } + if (res_me->local_name == JS_ATOM__star_) { + JSValue val; + JSModuleDef *m2; + /* name space import from */ + m2 = res_m->req_module_entries[res_me->u.req_module_idx].module; + val = JS_GetModuleNamespace(ctx, m2); + if (JS_IsException(val)) + goto fail; + var_ref = js_create_module_var(ctx, TRUE); + if (!var_ref) { + JS_FreeValue(ctx, val); + goto fail; + } + set_value(ctx, &var_ref->value, val); + var_refs[mi->var_idx] = var_ref; + + module_trace(ctx, "namespace from\n"); + + } else { + var_ref = res_me->u.local.var_ref; + if (!var_ref) { + p1 = JS_VALUE_GET_OBJ(res_m->func_obj); + var_ref = p1->u.func.var_refs[res_me->u.local.var_idx]; + } + var_ref->header.ref_count++; + var_refs[mi->var_idx] = var_ref; + + module_trace(ctx, "local export (var_ref=%p)\n", var_ref); + } + } + } + + /* keep the exported variables in the module export entries (they + are used when the eval function is deleted and cannot be + initialized before in case imports are exported) */ + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + var_ref = var_refs[me->u.local.var_idx]; + var_ref->header.ref_count++; + me->u.local.var_ref = var_ref; + } + } + + /* initialize the global variables */ + ret_val = JS_Call(ctx, m->func_obj, JS_TRUE, 0, NULL); + if (JS_IsException(ret_val)) + goto fail; + JS_FreeValue(ctx, ret_val); + } + + assert(m->dfs_ancestor_index <= m->dfs_index); + if (m->dfs_index == m->dfs_ancestor_index) { + for(;;) { + /* pop m1 from stack */ + m1 = *pstack_top; + *pstack_top = m1->stack_prev; + m1->status = JS_MODULE_STATUS_LINKED; + if (m1 == m) + break; + } + } + +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + printf("js_inner_module_linking done\n"); + } +#endif + return index; + fail: + return -1; +} + +/* Prepare a module to be executed by resolving all the imported + variables. */ +static int js_link_module(JSContext *ctx, JSModuleDef *m) +{ + JSModuleDef *stack_top, *m1; + +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + char buf1[ATOM_GET_STR_BUF_SIZE]; + printf("js_link_module '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + } +#endif + assert(m->status == JS_MODULE_STATUS_UNLINKED || + m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + stack_top = NULL; + if (js_inner_module_linking(ctx, m, &stack_top, 0) < 0) { + while (stack_top != NULL) { + m1 = stack_top; + assert(m1->status == JS_MODULE_STATUS_LINKING); + m1->status = JS_MODULE_STATUS_UNLINKED; + stack_top = m1->stack_prev; + } + return -1; + } + assert(stack_top == NULL); + assert(m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + return 0; +} + +/* return JS_ATOM_NULL if the name cannot be found. Only works with + not striped bytecode functions. */ +JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels) +{ + JSStackFrame *sf; + JSFunctionBytecode *b; + JSObject *p; + /* XXX: currently we just use the filename of the englobing + function. It does not work for eval(). Need to add a + ScriptOrModule info in JSFunctionBytecode */ + sf = ctx->rt->current_stack_frame; + if (!sf) + return JS_ATOM_NULL; + while (n_stack_levels-- > 0) { + sf = sf->prev_frame; + if (!sf) + return JS_ATOM_NULL; + } + if (JS_VALUE_GET_TAG(sf->cur_func) != JS_TAG_OBJECT) + return JS_ATOM_NULL; + p = JS_VALUE_GET_OBJ(sf->cur_func); + if (!js_class_has_bytecode(p->class_id)) + return JS_ATOM_NULL; + b = p->u.func.function_bytecode; + return JS_DupAtom(ctx, b->filename); +} + +JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m) +{ + return JS_DupAtom(ctx, m->module_name); +} + +JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m) +{ + JSValue obj; + /* allocate meta_obj only if requested to save memory */ + obj = m->meta_obj; + if (JS_IsUndefined(obj)) { + obj = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(obj)) + return JS_EXCEPTION; + m->meta_obj = obj; + } + return js_dup(obj); +} + +static JSValue js_import_meta(JSContext *ctx) +{ + JSAtom filename; + JSModuleDef *m; + + filename = JS_GetScriptOrModuleName(ctx, 0); + if (filename == JS_ATOM_NULL) + goto fail; + + /* XXX: inefficient, need to add a module or script pointer in + JSFunctionBytecode */ + m = js_find_loaded_module(ctx, filename); + JS_FreeAtom(ctx, filename); + if (!m) { + fail: + JS_ThrowTypeError(ctx, "import.meta not supported in this context"); + return JS_EXCEPTION; + } + return JS_GetImportMeta(ctx, m); +} + +static JSValue JS_NewModuleValue(JSContext *ctx, JSModuleDef *m) +{ + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); +} + +static JSValue js_load_module_rejected(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSValueConst *resolving_funcs = (JSValueConst *)func_data; + JSValueConst error; + JSValue ret; + + /* XXX: check if the test is necessary */ + if (argc >= 1) + error = argv[0]; + else + error = JS_UNDEFINED; + ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, &error); + JS_FreeValue(ctx, ret); + return JS_UNDEFINED; +} + +static JSValue js_load_module_fulfilled(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSValueConst *resolving_funcs = (JSValueConst *)func_data; + JSModuleDef *m = JS_VALUE_GET_PTR(func_data[2]); + JSValue ret, ns; + + /* return the module namespace */ + ns = JS_GetModuleNamespace(ctx, m); + if (JS_IsException(ns)) { + JSValue err = JS_GetException(ctx); + js_load_module_rejected(ctx, JS_UNDEFINED, 1, (JSValueConst *)&err, 0, func_data); + return JS_UNDEFINED; + } + ret = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, + 1, (JSValueConst *)&ns); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, ns); + return JS_UNDEFINED; +} + +static void JS_LoadModuleInternal(JSContext *ctx, const char *basename, + const char *filename, + JSValueConst *resolving_funcs) +{ + JSValue evaluate_promise; + JSModuleDef *m; + JSValue ret, err, func_obj, evaluate_resolving_funcs[2]; + JSValueConst func_data[3]; + + m = js_host_resolve_imported_module(ctx, basename, filename); + if (!m) + goto fail; + + if (js_resolve_module(ctx, m) < 0) { + js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED); + goto fail; + } + + /* Evaluate the module code */ + func_obj = JS_NewModuleValue(ctx, m); + evaluate_promise = JS_EvalFunction(ctx, func_obj); + if (JS_IsException(evaluate_promise)) { + fail: + err = JS_GetException(ctx); + ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, (JSValueConst *)&err); + JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ + JS_FreeValue(ctx, err); + return; + } + + func_obj = JS_NewModuleValue(ctx, m); + func_data[0] = resolving_funcs[0]; + func_data[1] = resolving_funcs[1]; + func_data[2] = func_obj; + evaluate_resolving_funcs[0] = JS_NewCFunctionData(ctx, js_load_module_fulfilled, 0, 0, 3, func_data); + evaluate_resolving_funcs[1] = JS_NewCFunctionData(ctx, js_load_module_rejected, 0, 0, 3, func_data); + JS_FreeValue(ctx, func_obj); + ret = js_promise_then(ctx, evaluate_promise, 2, (JSValueConst *)evaluate_resolving_funcs); + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, evaluate_resolving_funcs[0]); + JS_FreeValue(ctx, evaluate_resolving_funcs[1]); + JS_FreeValue(ctx, evaluate_promise); +} + +/* Return a promise or an exception in case of memory error. Used by + os.Worker() */ +JSValue JS_LoadModule(JSContext *ctx, const char *basename, + const char *filename) +{ + JSValue promise, resolving_funcs[2]; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + JS_LoadModuleInternal(ctx, basename, filename, + (JSValueConst *)resolving_funcs); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; +} + +static JSValue js_dynamic_import_job(JSContext *ctx, + int argc, JSValue *argv) +{ + JSValue *resolving_funcs = argv; + JSValue basename_val = argv[2]; + JSValue specifier = argv[3]; + const char *basename = NULL, *filename; + JSValue ret, err; + + if (!JS_IsString(basename_val)) { + JS_ThrowTypeError(ctx, "no function filename for import()"); + goto exception; + } + basename = JS_ToCString(ctx, basename_val); + if (!basename) + goto exception; + + filename = JS_ToCString(ctx, specifier); + if (!filename) + goto exception; + + JS_LoadModuleInternal(ctx, basename, filename, + resolving_funcs); + JS_FreeCString(ctx, filename); + JS_FreeCString(ctx, basename); + return JS_UNDEFINED; + exception: + err = JS_GetException(ctx); + ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, + 1, &err); + JS_FreeValue(ctx, ret); /* XXX: what to do if exception ? */ + JS_FreeValue(ctx, err); + JS_FreeCString(ctx, basename); + return JS_UNDEFINED; +} + +static JSValue js_dynamic_import(JSContext *ctx, JSValue specifier) +{ + JSAtom basename; + JSValue promise, resolving_funcs[2], basename_val; + JSValue args[4]; + + basename = JS_GetScriptOrModuleName(ctx, 0); + if (basename == JS_ATOM_NULL) + basename_val = JS_NULL; + else + basename_val = JS_AtomToValue(ctx, basename); + JS_FreeAtom(ctx, basename); + if (JS_IsException(basename_val)) + return basename_val; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) { + JS_FreeValue(ctx, basename_val); + return promise; + } + + args[0] = resolving_funcs[0]; + args[1] = resolving_funcs[1]; + args[2] = basename_val; + args[3] = specifier; + + /* cannot run JS_LoadModuleInternal synchronously because it would + cause an unexpected recursion in js_evaluate_module() */ + JS_EnqueueJob(ctx, js_dynamic_import_job, 4, args); + + JS_FreeValue(ctx, basename_val); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; +} + +static void js_set_module_evaluated(JSContext *ctx, JSModuleDef *m) +{ + m->status = JS_MODULE_STATUS_EVALUATED; + if (!JS_IsUndefined(m->promise)) { + JSValue value, ret_val; + assert(m->cycle_root == m); + value = JS_UNDEFINED; + ret_val = JS_Call(ctx, m->resolving_funcs[0], JS_UNDEFINED, + 1, (JSValueConst *)&value); + JS_FreeValue(ctx, ret_val); + } +} + +typedef struct { + JSModuleDef **tab; + int count; + int size; +} ExecModuleList; + +/* XXX: slow. Could use a linked list instead of ExecModuleList */ +static BOOL find_in_exec_module_list(ExecModuleList *exec_list, JSModuleDef *m) +{ + int i; + for(i = 0; i < exec_list->count; i++) { + if (exec_list->tab[i] == m) + return TRUE; + } + return FALSE; +} + +static int gather_available_ancestors(JSContext *ctx, JSModuleDef *module, + ExecModuleList *exec_list) +{ + int i; + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return -1; + } + for(i = 0; i < module->async_parent_modules_count; i++) { + JSModuleDef *m = module->async_parent_modules[i]; + if (!find_in_exec_module_list(exec_list, m) && + !m->cycle_root->eval_has_exception) { + assert(m->status == JS_MODULE_STATUS_EVALUATING_ASYNC); + assert(!m->eval_has_exception); + assert(m->async_evaluation); + assert(m->pending_async_dependencies > 0); + m->pending_async_dependencies--; + if (m->pending_async_dependencies == 0) { + if (js_resize_array(ctx, (void **)&exec_list->tab, sizeof(exec_list->tab[0]), &exec_list->size, exec_list->count + 1)) { + return -1; + } + exec_list->tab[exec_list->count++] = m; + if (!m->has_tla) { + if (gather_available_ancestors(ctx, m, exec_list)) + return -1; + } + } + } + } + return 0; +} + +static int exec_module_list_cmp(const void *p1, const void *p2, void *opaque) +{ + JSModuleDef *m1 = *(JSModuleDef **)p1; + JSModuleDef *m2 = *(JSModuleDef **)p2; + return (m1->async_evaluation_timestamp > m2->async_evaluation_timestamp) - + (m1->async_evaluation_timestamp < m2->async_evaluation_timestamp); +} + +static int js_execute_async_module(JSContext *ctx, JSModuleDef *m); +static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m, + JSValue *pvalue); + +static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSModuleDef *module = JS_VALUE_GET_PTR(func_data[0]); + JSValueConst error = argv[0]; + int i; + + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + + if (module->status == JS_MODULE_STATUS_EVALUATED) { + assert(module->eval_has_exception); + return JS_UNDEFINED; + } + + assert(module->status == JS_MODULE_STATUS_EVALUATING_ASYNC); + assert(!module->eval_has_exception); + assert(module->async_evaluation); + + module->eval_has_exception = TRUE; + module->eval_exception = JS_DupValue(ctx, error); + module->status = JS_MODULE_STATUS_EVALUATED; + + for(i = 0; i < module->async_parent_modules_count; i++) { + JSModuleDef *m = module->async_parent_modules[i]; + JSValue m_obj = JS_NewModuleValue(ctx, m); + js_async_module_execution_rejected(ctx, JS_UNDEFINED, 1, &error, 0, + &m_obj); + JS_FreeValue(ctx, m_obj); + } + + if (!JS_IsUndefined(module->promise)) { + JSValue ret_val; + assert(module->cycle_root == module); + ret_val = JS_Call(ctx, module->resolving_funcs[1], JS_UNDEFINED, + 1, &error); + JS_FreeValue(ctx, ret_val); + } + return JS_UNDEFINED; +} + +static JSValue js_async_module_execution_fulfilled(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic, JSValue *func_data) +{ + JSModuleDef *module = JS_VALUE_GET_PTR(func_data[0]); + ExecModuleList exec_list_s, *exec_list = &exec_list_s; + int i; + + if (module->status == JS_MODULE_STATUS_EVALUATED) { + assert(module->eval_has_exception); + return JS_UNDEFINED; + } + assert(module->status == JS_MODULE_STATUS_EVALUATING_ASYNC); + assert(!module->eval_has_exception); + assert(module->async_evaluation); + module->async_evaluation = FALSE; + js_set_module_evaluated(ctx, module); + + exec_list->tab = NULL; + exec_list->count = 0; + exec_list->size = 0; + + if (gather_available_ancestors(ctx, module, exec_list) < 0) { + js_free(ctx, exec_list->tab); + return JS_EXCEPTION; + } + + /* sort by increasing async_evaluation timestamp */ + rqsort(exec_list->tab, exec_list->count, sizeof(exec_list->tab[0]), + exec_module_list_cmp, NULL); + + for(i = 0; i < exec_list->count; i++) { + JSModuleDef *m = exec_list->tab[i]; + if (m->status == JS_MODULE_STATUS_EVALUATED) { + assert(m->eval_has_exception); + } else if (m->has_tla) { + js_execute_async_module(ctx, m); + } else { + JSValue error; + if (js_execute_sync_module(ctx, m, &error) < 0) { + JSValue m_obj = JS_NewModuleValue(ctx, m); + js_async_module_execution_rejected(ctx, JS_UNDEFINED, + 1, (JSValueConst *)&error, 0, + &m_obj); + JS_FreeValue(ctx, m_obj); + JS_FreeValue(ctx, error); + } else { + js_set_module_evaluated(ctx, m); + } + } + } + js_free(ctx, exec_list->tab); + return JS_UNDEFINED; +} + +static int js_execute_async_module(JSContext *ctx, JSModuleDef *m) +{ + JSValue promise, m_obj; + JSValue resolve_funcs[2], ret_val; + promise = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0); + if (JS_IsException(promise)) + return -1; + m_obj = JS_NewModuleValue(ctx, m); + resolve_funcs[0] = JS_NewCFunctionData(ctx, js_async_module_execution_fulfilled, 0, 0, 1, (JSValueConst *)&m_obj); + resolve_funcs[1] = JS_NewCFunctionData(ctx, js_async_module_execution_rejected, 0, 0, 1, (JSValueConst *)&m_obj); + ret_val = js_promise_then(ctx, promise, 2, (JSValueConst *)resolve_funcs); + JS_FreeValue(ctx, ret_val); + JS_FreeValue(ctx, m_obj); + JS_FreeValue(ctx, resolve_funcs[0]); + JS_FreeValue(ctx, resolve_funcs[1]); + JS_FreeValue(ctx, promise); + return 0; +} + +/* return < 0 in case of exception. *pvalue contains the exception. */ +static int js_execute_sync_module(JSContext *ctx, JSModuleDef *m, + JSValue *pvalue) +{ + if (m->init_func) { + /* C module init : no asynchronous execution */ + if (m->init_func(ctx, m) < 0) + goto fail; + } else { + JSValue promise; + JSPromiseStateEnum state; + + promise = js_async_function_call(ctx, m->func_obj, JS_UNDEFINED, 0, NULL, 0); + if (JS_IsException(promise)) + goto fail; + state = JS_PromiseState(ctx, promise); + if (state == JS_PROMISE_FULFILLED) { + JS_FreeValue(ctx, promise); + } else if (state == JS_PROMISE_REJECTED) { + *pvalue = JS_PromiseResult(ctx, promise); + JS_FreeValue(ctx, promise); + return -1; + } else { + JS_FreeValue(ctx, promise); + JS_ThrowTypeError(ctx, "promise is pending"); + fail: + *pvalue = JS_GetException(ctx); + return -1; + } + } + *pvalue = JS_UNDEFINED; + return 0; +} + +/* spec: InnerModuleEvaluation. Return (index, JS_UNDEFINED) or (-1, + exception) */ +static int js_inner_module_evaluation(JSContext *ctx, JSModuleDef *m, + int index, JSModuleDef **pstack_top, + JSValue *pvalue) +{ + JSModuleDef *m1; + int i; + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + *pvalue = JS_GetException(ctx); + return -1; + } + +#ifdef DUMP_MODULE_RESOLVE + if (check_dump_flag(ctx->rt, DUMP_MODULE_RESOLVE)) { + char buf1[ATOM_GET_STR_BUF_SIZE]; + printf("js_inner_module_evaluation '%s':\n", JS_AtomGetStr(ctx, buf1, sizeof(buf1), m->module_name)); + } +#endif + + if (m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED) { + if (m->eval_has_exception) { + *pvalue = JS_DupValue(ctx, m->eval_exception); + return -1; + } else { + *pvalue = JS_UNDEFINED; + return index; + } + } + if (m->status == JS_MODULE_STATUS_EVALUATING) { + *pvalue = JS_UNDEFINED; + return index; + } + assert(m->status == JS_MODULE_STATUS_LINKED); + + m->status = JS_MODULE_STATUS_EVALUATING; + m->dfs_index = index; + m->dfs_ancestor_index = index; + m->pending_async_dependencies = 0; + index++; + /* push 'm' on stack */ + m->stack_prev = *pstack_top; + *pstack_top = m; + + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + m1 = rme->module; + index = js_inner_module_evaluation(ctx, m1, index, pstack_top, pvalue); + if (index < 0) + return -1; + assert(m1->status == JS_MODULE_STATUS_EVALUATING || + m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m1->status == JS_MODULE_STATUS_EVALUATED); + if (m1->status == JS_MODULE_STATUS_EVALUATING) { + m->dfs_ancestor_index = min_int(m->dfs_ancestor_index, + m1->dfs_ancestor_index); + } else { + m1 = m1->cycle_root; + assert(m1->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m1->status == JS_MODULE_STATUS_EVALUATED); + if (m1->eval_has_exception) { + *pvalue = JS_DupValue(ctx, m1->eval_exception); + return -1; + } + } + if (m1->async_evaluation) { + m->pending_async_dependencies++; + if (js_resize_array(ctx, (void **)&m1->async_parent_modules, sizeof(m1->async_parent_modules[0]), &m1->async_parent_modules_size, m1->async_parent_modules_count + 1)) { + *pvalue = JS_GetException(ctx); + return -1; + } + m1->async_parent_modules[m1->async_parent_modules_count++] = m; + } + } + + if (m->pending_async_dependencies > 0) { + assert(!m->async_evaluation); + m->async_evaluation = TRUE; + m->async_evaluation_timestamp = + ctx->rt->module_async_evaluation_next_timestamp++; + } else if (m->has_tla) { + assert(!m->async_evaluation); + m->async_evaluation = TRUE; + m->async_evaluation_timestamp = + ctx->rt->module_async_evaluation_next_timestamp++; + js_execute_async_module(ctx, m); + } else { + if (js_execute_sync_module(ctx, m, pvalue) < 0) + return -1; + } + + assert(m->dfs_ancestor_index <= m->dfs_index); + if (m->dfs_index == m->dfs_ancestor_index) { + for(;;) { + /* pop m1 from stack */ + m1 = *pstack_top; + *pstack_top = m1->stack_prev; + if (!m1->async_evaluation) { + m1->status = JS_MODULE_STATUS_EVALUATED; + } else { + m1->status = JS_MODULE_STATUS_EVALUATING_ASYNC; + } + /* spec bug: cycle_root must be assigned before the test */ + m1->cycle_root = m; + if (m1 == m) + break; + } + } + *pvalue = JS_UNDEFINED; + return index; +} + +/* Run the <eval> function of the module and of all its requested + modules. Return a promise or an exception. */ +static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m) +{ + JSModuleDef *m1, *stack_top; + JSValue ret_val, result; + + assert(m->status == JS_MODULE_STATUS_LINKED || + m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + if (m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED) { + m = m->cycle_root; + } + /* a promise may be created only on the cycle_root of a cycle */ + if (!JS_IsUndefined(m->promise)) + return JS_DupValue(ctx, m->promise); + m->promise = JS_NewPromiseCapability(ctx, m->resolving_funcs); + if (JS_IsException(m->promise)) + return JS_EXCEPTION; + + stack_top = NULL; + if (js_inner_module_evaluation(ctx, m, 0, &stack_top, &result) < 0) { + while (stack_top != NULL) { + m1 = stack_top; + assert(m1->status == JS_MODULE_STATUS_EVALUATING); + m1->status = JS_MODULE_STATUS_EVALUATED; + m1->eval_has_exception = TRUE; + m1->eval_exception = JS_DupValue(ctx, result); + m1->cycle_root = m; /* spec bug: should be present */ + stack_top = m1->stack_prev; + } + JS_FreeValue(ctx, result); + assert(m->status == JS_MODULE_STATUS_EVALUATED); + assert(m->eval_has_exception); + ret_val = JS_Call(ctx, m->resolving_funcs[1], JS_UNDEFINED, + 1, (JSValueConst *)&m->eval_exception); + JS_FreeValue(ctx, ret_val); + } else { + assert(m->status == JS_MODULE_STATUS_EVALUATING_ASYNC || + m->status == JS_MODULE_STATUS_EVALUATED); + assert(!m->eval_has_exception); + if (!m->async_evaluation) { + JSValue value; + assert(m->status == JS_MODULE_STATUS_EVALUATED); + value = JS_UNDEFINED; + ret_val = JS_Call(ctx, m->resolving_funcs[0], JS_UNDEFINED, + 1, (JSValueConst *)&value); + JS_FreeValue(ctx, ret_val); + } + assert(stack_top == NULL); + } + return JS_DupValue(ctx, m->promise); +} + +static __exception JSAtom js_parse_from_clause(JSParseState *s) +{ + JSAtom module_name; + if (!token_is_pseudo_keyword(s, JS_ATOM_from)) { + js_parse_error(s, "from clause expected"); + return JS_ATOM_NULL; + } + if (next_token(s)) + return JS_ATOM_NULL; + if (s->token.val != TOK_STRING) { + js_parse_error(s, "string expected"); + return JS_ATOM_NULL; + } + module_name = JS_ValueToAtom(s->ctx, s->token.u.str.str); + if (module_name == JS_ATOM_NULL) + return JS_ATOM_NULL; + if (next_token(s)) { + JS_FreeAtom(s->ctx, module_name); + return JS_ATOM_NULL; + } + return module_name; +} + +static __exception int js_parse_export(JSParseState *s) +{ + JSContext *ctx = s->ctx; + JSModuleDef *m = s->cur_func->module; + JSAtom local_name, export_name; + int first_export, idx, i, tok; + JSAtom module_name; + JSExportEntry *me; + + if (next_token(s)) + return -1; + + tok = s->token.val; + if (tok == TOK_CLASS) { + return js_parse_class(s, FALSE, JS_PARSE_EXPORT_NAMED); + } else if (tok == TOK_FUNCTION || + (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) == TOK_FUNCTION)) { + return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + s->token.line_num, + s->token.col_num, + JS_PARSE_EXPORT_NAMED, NULL); + } + + if (next_token(s)) + return -1; + + switch(tok) { + case '{': + first_export = m->export_entries_count; + while (s->token.val != '}') { + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + return -1; + } + local_name = JS_DupAtom(ctx, s->token.u.ident.atom); + export_name = JS_ATOM_NULL; + if (next_token(s)) + goto fail; + if (token_is_pseudo_keyword(s, JS_ATOM_as)) { + if (next_token(s)) + goto fail; + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + goto fail; + } + export_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) { + fail: + JS_FreeAtom(ctx, local_name); + fail1: + JS_FreeAtom(ctx, export_name); + return -1; + } + } else { + export_name = JS_DupAtom(ctx, local_name); + } + me = add_export_entry(s, m, local_name, export_name, + JS_EXPORT_TYPE_LOCAL); + JS_FreeAtom(ctx, local_name); + JS_FreeAtom(ctx, export_name); + if (!me) + return -1; + if (s->token.val != ',') + break; + if (next_token(s)) + return -1; + } + if (js_parse_expect(s, '}')) + return -1; + if (token_is_pseudo_keyword(s, JS_ATOM_from)) { + module_name = js_parse_from_clause(s); + if (module_name == JS_ATOM_NULL) + return -1; + idx = add_req_module_entry(ctx, m, module_name); + JS_FreeAtom(ctx, module_name); + if (idx < 0) + return -1; + for(i = first_export; i < m->export_entries_count; i++) { + me = &m->export_entries[i]; + me->export_type = JS_EXPORT_TYPE_INDIRECT; + me->u.req_module_idx = idx; + } + } + break; + case '*': + if (token_is_pseudo_keyword(s, JS_ATOM_as)) { + /* export ns from */ + if (next_token(s)) + return -1; + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + return -1; + } + export_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) + goto fail1; + module_name = js_parse_from_clause(s); + if (module_name == JS_ATOM_NULL) + goto fail1; + idx = add_req_module_entry(ctx, m, module_name); + JS_FreeAtom(ctx, module_name); + if (idx < 0) + goto fail1; + me = add_export_entry(s, m, JS_ATOM__star_, export_name, + JS_EXPORT_TYPE_INDIRECT); + JS_FreeAtom(ctx, export_name); + if (!me) + return -1; + me->u.req_module_idx = idx; + } else { + module_name = js_parse_from_clause(s); + if (module_name == JS_ATOM_NULL) + return -1; + idx = add_req_module_entry(ctx, m, module_name); + JS_FreeAtom(ctx, module_name); + if (idx < 0) + return -1; + if (add_star_export_entry(ctx, m, idx) < 0) + return -1; + } + break; + case TOK_DEFAULT: + if (s->token.val == TOK_CLASS) { + return js_parse_class(s, FALSE, JS_PARSE_EXPORT_DEFAULT); + } else if (s->token.val == TOK_FUNCTION || + (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) == TOK_FUNCTION)) { + return js_parse_function_decl2(s, JS_PARSE_FUNC_STATEMENT, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + s->token.line_num, + s->token.col_num, + JS_PARSE_EXPORT_DEFAULT, NULL); + } else { + if (js_parse_assign_expr(s)) + return -1; + } + /* set the name of anonymous functions */ + set_object_name(s, JS_ATOM_default); + + /* store the value in the _default_ global variable and export + it */ + local_name = JS_ATOM__default_; + if (define_var(s, s->cur_func, local_name, JS_VAR_DEF_LET) < 0) + return -1; + emit_op(s, OP_scope_put_var_init); + emit_atom(s, local_name); + emit_u16(s, 0); + + if (!add_export_entry(s, m, local_name, JS_ATOM_default, + JS_EXPORT_TYPE_LOCAL)) + return -1; + break; + case TOK_VAR: + case TOK_LET: + case TOK_CONST: + return js_parse_var(s, TRUE, tok, TRUE); + default: + return js_parse_error(s, "invalid export syntax"); + } + return js_parse_expect_semi(s); +} + +static int add_closure_var(JSContext *ctx, JSFunctionDef *s, + BOOL is_local, BOOL is_arg, + int var_idx, JSAtom var_name, + BOOL is_const, BOOL is_lexical, + JSVarKindEnum var_kind); + +static int add_import(JSParseState *s, JSModuleDef *m, + JSAtom local_name, JSAtom import_name) +{ + JSContext *ctx = s->ctx; + int i, var_idx; + JSImportEntry *mi; + BOOL is_local; + + if (local_name == JS_ATOM_arguments || local_name == JS_ATOM_eval) + return js_parse_error(s, "invalid import binding"); + + if (local_name != JS_ATOM_default) { + for (i = 0; i < s->cur_func->closure_var_count; i++) { + if (s->cur_func->closure_var[i].var_name == local_name) + return js_parse_error(s, "duplicate import binding"); + } + } + + is_local = (import_name == JS_ATOM__star_); + var_idx = add_closure_var(ctx, s->cur_func, is_local, FALSE, + m->import_entries_count, + local_name, TRUE, TRUE, JS_VAR_NORMAL); + if (var_idx < 0) + return -1; + if (js_resize_array(ctx, (void **)&m->import_entries, + sizeof(JSImportEntry), + &m->import_entries_size, + m->import_entries_count + 1)) + return -1; + mi = &m->import_entries[m->import_entries_count++]; + mi->import_name = JS_DupAtom(ctx, import_name); + mi->var_idx = var_idx; + return 0; +} + +static __exception int js_parse_import(JSParseState *s) +{ + JSContext *ctx = s->ctx; + JSModuleDef *m = s->cur_func->module; + JSAtom local_name, import_name, module_name; + int first_import, i, idx; + + if (next_token(s)) + return -1; + + first_import = m->import_entries_count; + if (s->token.val == TOK_STRING) { + module_name = JS_ValueToAtom(ctx, s->token.u.str.str); + if (module_name == JS_ATOM_NULL) + return -1; + if (next_token(s)) { + JS_FreeAtom(ctx, module_name); + return -1; + } + } else { + if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) { + return js_parse_error_reserved_identifier(s); + } + /* "default" import */ + local_name = JS_DupAtom(ctx, s->token.u.ident.atom); + import_name = JS_ATOM_default; + if (next_token(s)) + goto fail; + if (add_import(s, m, local_name, import_name)) + goto fail; + JS_FreeAtom(ctx, local_name); + + if (s->token.val != ',') + goto end_import_clause; + if (next_token(s)) + return -1; + } + + if (s->token.val == '*') { + /* name space import */ + if (next_token(s)) + return -1; + if (!token_is_pseudo_keyword(s, JS_ATOM_as)) + return js_parse_error(s, "expecting 'as'"); + if (next_token(s)) + return -1; + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + return -1; + } + local_name = JS_DupAtom(ctx, s->token.u.ident.atom); + import_name = JS_ATOM__star_; + if (next_token(s)) + goto fail; + if (add_import(s, m, local_name, import_name)) + goto fail; + JS_FreeAtom(ctx, local_name); + } else if (s->token.val == '{') { + if (next_token(s)) + return -1; + + while (s->token.val != '}') { + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + return -1; + } + import_name = JS_DupAtom(ctx, s->token.u.ident.atom); + local_name = JS_ATOM_NULL; + if (next_token(s)) + goto fail; + if (token_is_pseudo_keyword(s, JS_ATOM_as)) { + if (next_token(s)) + goto fail; + if (!token_is_ident(s->token.val)) { + js_parse_error(s, "identifier expected"); + goto fail; + } + local_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) { + fail: + JS_FreeAtom(ctx, local_name); + JS_FreeAtom(ctx, import_name); + return -1; + } + } else { + local_name = JS_DupAtom(ctx, import_name); + } + if (add_import(s, m, local_name, import_name)) + goto fail; + JS_FreeAtom(ctx, local_name); + JS_FreeAtom(ctx, import_name); + if (s->token.val != ',') + break; + if (next_token(s)) + return -1; + } + if (js_parse_expect(s, '}')) + return -1; + } + end_import_clause: + module_name = js_parse_from_clause(s); + if (module_name == JS_ATOM_NULL) + return -1; + } + idx = add_req_module_entry(ctx, m, module_name); + JS_FreeAtom(ctx, module_name); + if (idx < 0) + return -1; + for(i = first_import; i < m->import_entries_count; i++) + m->import_entries[i].req_module_idx = idx; + + return js_parse_expect_semi(s); +} + +static __exception int js_parse_source_element(JSParseState *s) +{ + JSFunctionDef *fd = s->cur_func; + int tok; + + if (s->token.val == TOK_FUNCTION || + (token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) == TOK_FUNCTION)) { + if (js_parse_function_decl(s, JS_PARSE_FUNC_STATEMENT, + JS_FUNC_NORMAL, JS_ATOM_NULL, + s->token.ptr, + s->token.line_num, + s->token.col_num)) + return -1; + } else if (s->token.val == TOK_EXPORT && fd->module) { + if (js_parse_export(s)) + return -1; + } else if (s->token.val == TOK_IMPORT && fd->module && + ((tok = peek_token(s, FALSE)) != '(' && tok != '.')) { + /* the peek_token is needed to avoid confusion with ImportCall + (dynamic import) or import.meta */ + if (js_parse_import(s)) + return -1; + } else { + if (js_parse_statement_or_decl(s, DECL_MASK_ALL)) + return -1; + } + return 0; +} + +/* `filename` may be pure ASCII or UTF-8 encoded */ +static JSFunctionDef *js_new_function_def(JSContext *ctx, + JSFunctionDef *parent, + BOOL is_eval, + BOOL is_func_expr, + const char *filename, + int line_num, + int col_num) +{ + JSFunctionDef *fd; + + fd = js_mallocz(ctx, sizeof(*fd)); + if (!fd) + return NULL; + + fd->ctx = ctx; + init_list_head(&fd->child_list); + + /* insert in parent list */ + fd->parent = parent; + fd->parent_cpool_idx = -1; + if (parent) { + list_add_tail(&fd->link, &parent->child_list); + fd->is_strict_mode = parent->is_strict_mode; + fd->parent_scope_level = parent->scope_level; + } + + fd->is_eval = is_eval; + fd->is_func_expr = is_func_expr; + js_dbuf_init(ctx, &fd->byte_code); + fd->last_opcode_pos = -1; + fd->func_name = JS_ATOM_NULL; + fd->var_object_idx = -1; + fd->arg_var_object_idx = -1; + fd->arguments_var_idx = -1; + fd->arguments_arg_idx = -1; + fd->func_var_idx = -1; + fd->eval_ret_idx = -1; + fd->this_var_idx = -1; + fd->new_target_var_idx = -1; + fd->this_active_func_var_idx = -1; + fd->home_object_var_idx = -1; + + /* XXX: should distinguish arg, var and var object and body scopes */ + fd->scopes = fd->def_scope_array; + fd->scope_size = countof(fd->def_scope_array); + fd->scope_count = 1; + fd->scopes[0].first = -1; + fd->scopes[0].parent = -1; + fd->scope_level = 0; /* 0: var/arg scope */ + fd->scope_first = -1; + fd->body_scope = -1; + + fd->filename = JS_NewAtom(ctx, filename); + fd->line_num = line_num; + fd->col_num = col_num; + + js_dbuf_init(ctx, &fd->pc2line); + //fd->pc2line_last_line_num = line_num; + //fd->pc2line_last_pc = 0; + + fd->ic = init_ic(ctx); + return fd; +} + +static void free_bytecode_atoms(JSRuntime *rt, + const uint8_t *bc_buf, int bc_len, + BOOL use_short_opcodes) +{ + int pos, len, op; + JSAtom atom; + const JSOpCode *oi; + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + if (use_short_opcodes) + oi = &short_opcode_info(op); + else + oi = &opcode_info[op]; + + len = oi->size; + switch(oi->fmt) { + case OP_FMT_atom: + case OP_FMT_atom_u8: + case OP_FMT_atom_u16: + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + atom = get_u32(bc_buf + pos + 1); + JS_FreeAtomRT(rt, atom); + break; + default: + break; + } + pos += len; + } +} + +static void js_free_function_def(JSContext *ctx, JSFunctionDef *fd) +{ + int i; + struct list_head *el, *el1; + + /* free the child functions */ + list_for_each_safe(el, el1, &fd->child_list) { + JSFunctionDef *fd1; + fd1 = list_entry(el, JSFunctionDef, link); + js_free_function_def(ctx, fd1); + } + + free_bytecode_atoms(ctx->rt, fd->byte_code.buf, fd->byte_code.size, + fd->use_short_opcodes); + dbuf_free(&fd->byte_code); + js_free(ctx, fd->jump_slots); + js_free(ctx, fd->label_slots); + js_free(ctx, fd->source_loc_slots); + + /* free ic */ + if (fd->ic) + free_ic(ctx->rt, fd->ic); + + for(i = 0; i < fd->cpool_count; i++) { + JS_FreeValue(ctx, fd->cpool[i]); + } + js_free(ctx, fd->cpool); + + JS_FreeAtom(ctx, fd->func_name); + + for(i = 0; i < fd->var_count; i++) { + JS_FreeAtom(ctx, fd->vars[i].var_name); + } + js_free(ctx, fd->vars); + js_free(ctx, fd->vars_htab); // XXX can probably be freed earlier? + for(i = 0; i < fd->arg_count; i++) { + JS_FreeAtom(ctx, fd->args[i].var_name); + } + js_free(ctx, fd->args); + + for(i = 0; i < fd->global_var_count; i++) { + JS_FreeAtom(ctx, fd->global_vars[i].var_name); + } + js_free(ctx, fd->global_vars); + + for(i = 0; i < fd->closure_var_count; i++) { + JSClosureVar *cv = &fd->closure_var[i]; + JS_FreeAtom(ctx, cv->var_name); + } + js_free(ctx, fd->closure_var); + + if (fd->scopes != fd->def_scope_array) + js_free(ctx, fd->scopes); + + JS_FreeAtom(ctx, fd->filename); + dbuf_free(&fd->pc2line); + + js_free(ctx, fd->source); + + if (fd->parent) { + /* remove in parent list */ + list_del(&fd->link); + } + js_free(ctx, fd); +} + +#ifdef DUMP_BYTECODE +static const char *skip_lines(const char *p, int n) { + while (p && n-- > 0 && *p) { + while (*p && *p++ != '\n') + continue; + } + return p; +} + +static void print_lines(const char *source, int line, int line1) { + const char *s = source; + const char *p = skip_lines(s, line); + if (p && *p) { + while (line++ < line1) { + p = skip_lines(s = p, 1); + printf(";; %.*s", (int)(p - s), s); + if (!*p) { + if (p[-1] != '\n') + printf("\n"); + break; + } + } + } +} + +static void dump_byte_code(JSContext *ctx, int pass, + const uint8_t *tab, int len, + const JSVarDef *args, int arg_count, + const JSVarDef *vars, int var_count, + const JSClosureVar *closure_var, int closure_var_count, + const JSValue *cpool, uint32_t cpool_count, + const char *source, int line_num, + const LabelSlot *label_slots, JSFunctionBytecode *b, + int start_pos) +{ + const JSOpCode *oi; + int pos, pos_next, op, size, idx, addr, line, line1, in_source; + uint8_t *bits = js_mallocz(ctx, len * sizeof(*bits)); + BOOL use_short_opcodes = (b != NULL); + + if (start_pos != 0 || bits == NULL) + goto no_labels; + + /* scan for jump targets */ + for (pos = 0; pos < len; pos = pos_next) { + op = tab[pos]; + if (use_short_opcodes) + oi = &short_opcode_info(op); + else + oi = &opcode_info[op]; + pos_next = pos + oi->size; + if (op < OP_COUNT) { + switch (oi->fmt) { + case OP_FMT_label8: + pos++; + addr = (int8_t)tab[pos]; + goto has_addr; + case OP_FMT_label16: + pos++; + addr = (int16_t)get_u16(tab + pos); + goto has_addr; + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + pos += 4; + /* fall thru */ + case OP_FMT_label: + case OP_FMT_label_u16: + pos++; + addr = get_u32(tab + pos); + goto has_addr; + has_addr: + if (pass == 1) + addr = label_slots[addr].pos; + if (pass == 2) + addr = label_slots[addr].pos2; + if (pass == 3) + addr += pos; + if (addr >= 0 && addr < len) + bits[addr] |= 1; + break; + } + } + } + no_labels: + in_source = 0; + if (source) { + /* Always print first line: needed if single line */ + print_lines(source, 0, 1); + in_source = 1; + } + line1 = line = 1; + pos = 0; + while (pos < len) { + op = tab[pos]; + if (source) { + if (b) { + int col1; + line1 = find_line_num(ctx, b, pos, &col1) - line_num + 1; + } else if (op == OP_source_loc) { + line1 = get_u32(tab + pos + 1) - line_num + 1; + } + if (line1 > line) { + if (!in_source) + printf("\n"); + in_source = 1; + print_lines(source, line, line1); + line = line1; + //bits[pos] |= 2; + } + } + if (in_source) + printf("\n"); + in_source = 0; + if (op >= OP_COUNT) { + printf("invalid opcode (0x%02x)\n", op); + pos++; + continue; + } + if (use_short_opcodes) + oi = &short_opcode_info(op); + else + oi = &opcode_info[op]; + size = oi->size; + if (pos + size > len) { + printf("truncated opcode (0x%02x)\n", op); + break; + } +#ifdef DUMP_BYTECODE_HEX + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_HEX)) { + int i, x, x0; + x = x0 = printf("%5d ", pos); + for (i = 0; i < size; i++) { + if (i == 6) { + printf("\n%*s", x = x0, ""); + } + x += printf(" %02X", tab[pos + i]); + } + printf("%*s", x0 + 20 - x, ""); + } +#endif + if (bits && bits[pos]) { + printf("%5d: ", pos); + } else { + printf(" "); + } + printf("%-15s", oi->name); /* align opcode arguments */ + pos++; + switch(oi->fmt) { + case OP_FMT_none_int: + printf(" %d", op - OP_push_0); + break; + case OP_FMT_npopx: + printf(" %d", op - OP_call0); + break; + case OP_FMT_u8: + printf(" %u", get_u8(tab + pos)); + break; + case OP_FMT_i8: + printf(" %d", get_i8(tab + pos)); + break; + case OP_FMT_u16: + case OP_FMT_npop: + printf(" %u", get_u16(tab + pos)); + break; + case OP_FMT_npop_u16: + printf(" %u,%u", get_u16(tab + pos), get_u16(tab + pos + 2)); + break; + case OP_FMT_i16: + printf(" %d", get_i16(tab + pos)); + break; + case OP_FMT_i32: + printf(" %d", get_i32(tab + pos)); + break; + case OP_FMT_u32: + printf(" %u", get_u32(tab + pos)); + break; + case OP_FMT_u32x2: + printf(" %u:%u", get_u32(tab + pos), get_u32(tab + pos + 4)); + break; + 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: + case OP_FMT_label_u16: + addr = get_u32(tab + pos); + has_addr1: + if (pass == 1) + printf(" %d:%u", addr, label_slots[addr].pos); + if (pass == 2) + printf(" %d:%u", addr, label_slots[addr].pos2); + if (pass == 3) { + if (start_pos) + printf(" %04x", addr + pos + start_pos); + else + printf(" %d", addr + pos); + } + if (oi->fmt == OP_FMT_label_u16) + printf(",%u", get_u16(tab + pos + 4)); + break; + case OP_FMT_const8: + idx = get_u8(tab + pos); + goto has_pool_idx; + case OP_FMT_const: + idx = get_u32(tab + pos); + goto has_pool_idx; + has_pool_idx: + printf(" %-4u ; ", idx); + if (idx < cpool_count) { + JS_DumpValue(ctx->rt, cpool[idx]); + } + break; + case OP_FMT_atom: + printf(" "); + print_atom(ctx, get_u32(tab + pos)); + break; + case OP_FMT_atom_u8: + printf(" "); + print_atom(ctx, get_u32(tab + pos)); + printf(",%d", get_u8(tab + pos + 4)); + break; + case OP_FMT_atom_u16: + printf(" "); + print_atom(ctx, get_u32(tab + pos)); + printf(",%d", get_u16(tab + pos + 4)); + break; + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + printf(" "); + print_atom(ctx, get_u32(tab + pos)); + addr = get_u32(tab + pos + 4); + if (pass == 1) + printf(",%u:%u", addr, label_slots[addr].pos); + if (pass == 2) + printf(",%u:%u", addr, label_slots[addr].pos2); + if (pass == 3) + printf(",%u", addr + pos + 4); + if (oi->fmt == OP_FMT_atom_label_u8) + printf(",%u", get_u8(tab + pos + 8)); + else + printf(",%u", get_u16(tab + pos + 8)); + break; + case OP_FMT_none_loc: + if (op == OP_get_loc0_loc1) { + printf(" 0, 1 ; "); + if (var_count > 0) + print_atom(ctx, vars[0].var_name); + if (var_count > 1) + print_atom(ctx, vars[1].var_name); + } else { + idx = (op - OP_get_loc0) % 4; + goto has_loc; + } + break; + case OP_FMT_loc8: + idx = get_u8(tab + pos); + goto has_loc; + case OP_FMT_loc: + idx = get_u16(tab + pos); + has_loc: + printf(" %-4d ; ", idx); + if (idx < var_count) { + print_atom(ctx, vars[idx].var_name); + } + 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: + printf(" %-4d ; ", idx); + if (idx < arg_count) { + print_atom(ctx, args[idx].var_name); + } + break; + case OP_FMT_none_var_ref: + idx = (op - OP_get_var_ref0) % 4; + goto has_var_ref; + case OP_FMT_var_ref: + idx = get_u16(tab + pos); + has_var_ref: + printf(" %-4d ; ", idx); + if (idx < closure_var_count) { + print_atom(ctx, closure_var[idx].var_name); + } + break; + default: + break; + } + printf("\n"); + pos += oi->size - 1; + } + if (source) { + if (!in_source) + printf("\n"); + print_lines(source, line, INT32_MAX); + } + js_free(ctx, bits); +} + +// caveat emptor: intended to be called during execution of bytecode +// and only works for pass3 bytecode +static __maybe_unused void dump_single_byte_code(JSContext *ctx, + const uint8_t *pc, + JSFunctionBytecode *b, + int start_pos) +{ + JSVarDef *args, *vars; + + args = vars = b->vardefs; + if (vars) + vars = &vars[b->arg_count]; + + dump_byte_code(ctx, /*pass*/3, pc, short_opcode_info(*pc).size, + args, b->arg_count, vars, b->var_count, + b->closure_var, b->closure_var_count, + b->cpool, b->cpool_count, + NULL, b->line_num, + NULL, b, start_pos); +} + +static __maybe_unused void print_func_name(JSFunctionBytecode *b) +{ + print_lines(b->source, 0, 1); +} + +static __maybe_unused void dump_pc2line(JSContext *ctx, + const uint8_t *buf, int len, + int line_num, int col_num) +{ + const uint8_t *p_end, *p_next, *p; + int pc, v; + unsigned int op; + + if (len <= 0) + return; + + printf("%5s %5s %5s\n", "PC", "LINE", "COLUMN"); + + p = buf; + p_end = buf + len; + pc = 0; + while (p < p_end) { + op = *p++; + if (op == 0) { + v = utf8_decode_len(p, p_end - p, &p_next); + if (v < 0) + goto fail; + pc += v; + p = p_next; + v = utf8_decode_len(p, p_end - p, &p_next); + if (v < 0) + goto fail; + if (v & 1) { + v = -(v >> 1) - 1; + } else { + v = v >> 1; + } + line_num += v; + p = p_next; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + v = utf8_decode_len(p, p_end - p, &p_next); + if (v < 0) + goto fail; + if (v & 1) { + v = -(v >> 1) - 1; + } else { + v = v >> 1; + } + col_num += v; + p = p_next; + printf("%5d %5d %5d\n", pc, line_num, col_num); + } + return; +fail: + printf("invalid pc2line encode pos=%d\n", (int)(p - buf)); +} + +static __maybe_unused void js_dump_function_bytecode(JSContext *ctx, JSFunctionBytecode *b) +{ + int i; + char atom_buf[ATOM_GET_STR_BUF_SIZE]; + const char *str; + + if (b->filename != JS_ATOM_NULL) { + str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->filename); + printf("%s:%d:%d: ", str, b->line_num, b->col_num); + } + + str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name); + printf("function: %s%s\n", &"*"[b->func_kind != JS_FUNC_GENERATOR], str); + printf(" mode: %s\n", b->is_strict_mode ? "strict" : "sloppy"); + if (b->arg_count && b->vardefs) { + printf(" args:"); + for(i = 0; i < b->arg_count; i++) { + printf(" %s", JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), + b->vardefs[i].var_name)); + } + printf("\n"); + } + if (b->var_count && b->vardefs) { + printf(" locals:\n"); + for(i = 0; i < b->var_count; i++) { + JSVarDef *vd = &b->vardefs[b->arg_count + i]; + printf("%5d: %s %s", i, + vd->var_kind == JS_VAR_CATCH ? "catch" : + (vd->var_kind == JS_VAR_FUNCTION_DECL || + vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" : + vd->is_const ? "const" : + vd->is_lexical ? "let" : "var", + JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name)); + if (vd->scope_level) + printf(" [level:%d next:%d]", vd->scope_level, vd->scope_next); + printf("\n"); + } + } + if (b->closure_var_count) { + printf(" closure vars:\n"); + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + printf("%5d: %s %s:%s%d %s\n", i, + JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name), + cv->is_local ? "local" : "parent", + cv->is_arg ? "arg" : "loc", cv->var_idx, + cv->is_const ? "const" : + cv->is_lexical ? "let" : "var"); + } + } + printf(" stack_size: %d\n", b->stack_size); + printf(" opcodes:\n"); + dump_byte_code(ctx, 3, b->byte_code_buf, b->byte_code_len, + b->vardefs, b->arg_count, + b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count, + b->closure_var, b->closure_var_count, + b->cpool, b->cpool_count, + b->source, b->line_num, NULL, b, 0); +#ifdef DUMP_BYTECODE_PC2LINE + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_PC2LINE)) + dump_pc2line(ctx, b->pc2line_buf, b->pc2line_len, b->line_num, b->col_num); +#endif + printf("\n"); +} +#endif + +static int add_closure_var(JSContext *ctx, JSFunctionDef *s, + BOOL is_local, BOOL is_arg, + int var_idx, JSAtom var_name, + BOOL is_const, BOOL is_lexical, + JSVarKindEnum var_kind) +{ + JSClosureVar *cv; + + /* the closure variable indexes are currently stored on 16 bits */ + if (s->closure_var_count >= JS_MAX_LOCAL_VARS) { + // XXX: add_closure_var() should take JSParseState *s and use js_parse_error + JS_ThrowSyntaxError(ctx, "too many closure variables used (only %d allowed)", + JS_MAX_LOCAL_VARS - 1); + return -1; + } + + if (js_resize_array(ctx, (void **)&s->closure_var, + sizeof(s->closure_var[0]), + &s->closure_var_size, s->closure_var_count + 1)) + return -1; + cv = &s->closure_var[s->closure_var_count++]; + cv->is_local = is_local; + cv->is_arg = is_arg; + cv->is_const = is_const; + cv->is_lexical = is_lexical; + cv->var_kind = var_kind; + cv->var_idx = var_idx; + cv->var_name = JS_DupAtom(ctx, var_name); + return s->closure_var_count - 1; +} + +static int find_closure_var(JSContext *ctx, JSFunctionDef *s, + JSAtom var_name) +{ + int i; + for(i = 0; i < s->closure_var_count; i++) { + JSClosureVar *cv = &s->closure_var[i]; + if (cv->var_name == var_name) + return i; + } + return -1; +} + +/* 'fd' must be a parent of 's'. Create in 's' a closure referencing a + local variable (is_local = TRUE) or a closure (is_local = FALSE) in + 'fd' */ +static int get_closure_var2(JSContext *ctx, JSFunctionDef *s, + JSFunctionDef *fd, BOOL is_local, + BOOL is_arg, int var_idx, JSAtom var_name, + BOOL is_const, BOOL is_lexical, + JSVarKindEnum var_kind) +{ + int i; + + if (fd != s->parent) { + var_idx = get_closure_var2(ctx, s->parent, fd, is_local, + is_arg, var_idx, var_name, + is_const, is_lexical, var_kind); + if (var_idx < 0) + return -1; + is_local = FALSE; + } + for(i = 0; i < s->closure_var_count; i++) { + JSClosureVar *cv = &s->closure_var[i]; + if (cv->var_idx == var_idx && cv->is_arg == is_arg && + cv->is_local == is_local) + return i; + } + return add_closure_var(ctx, s, is_local, is_arg, var_idx, var_name, + is_const, is_lexical, var_kind); +} + +static int get_closure_var(JSContext *ctx, JSFunctionDef *s, + JSFunctionDef *fd, BOOL is_arg, + int var_idx, JSAtom var_name, + BOOL is_const, BOOL is_lexical, + JSVarKindEnum var_kind) +{ + return get_closure_var2(ctx, s, fd, TRUE, is_arg, + var_idx, var_name, is_const, is_lexical, + var_kind); +} + +static int get_with_scope_opcode(int op) +{ + if (op == OP_scope_get_var_undef) + return OP_with_get_var; + else + return OP_with_get_var + (op - OP_scope_get_var); +} + +static BOOL can_opt_put_ref_value(const uint8_t *bc_buf, int pos) +{ + int opcode = bc_buf[pos]; + return (bc_buf[pos + 1] == OP_put_ref_value && + (opcode == OP_insert3 || + opcode == OP_perm4 || + opcode == OP_nop || + opcode == OP_rot3l)); +} + +static BOOL can_opt_put_global_ref_value(const uint8_t *bc_buf, int pos) +{ + int opcode = bc_buf[pos]; + return (bc_buf[pos + 1] == OP_put_ref_value && + (opcode == OP_insert3 || + opcode == OP_perm4 || + opcode == OP_nop || + opcode == OP_rot3l)); +} + +static int optimize_scope_make_ref(JSContext *ctx, JSFunctionDef *s, + DynBuf *bc, uint8_t *bc_buf, + LabelSlot *ls, int pos_next, + int get_op, int var_idx) +{ + int label_pos, end_pos, pos; + + /* XXX: should optimize `loc(a) += expr` as `expr add_loc(a)` + but only if expr does not modify `a`. + should scan the code between pos_next and label_pos + for operations that can potentially change `a`: + OP_scope_make_ref(a), function calls, jumps and gosub. + */ + /* replace the reference get/put with normal variable + accesses */ + if (bc_buf[pos_next] == OP_get_ref_value) { + dbuf_putc(bc, get_op); + dbuf_put_u16(bc, var_idx); + pos_next++; + } + /* remove the OP_label to make room for replacement */ + /* label should have a refcount of 0 anyway */ + /* XXX: should avoid this patch by inserting nops in phase 1 */ + label_pos = ls->pos; + pos = label_pos - 5; + assert(bc_buf[pos] == OP_label); + /* label points to an instruction pair: + - insert3 / put_ref_value + - perm4 / put_ref_value + - rot3l / put_ref_value + - nop / put_ref_value + */ + end_pos = label_pos + 2; + if (bc_buf[label_pos] == OP_insert3) + bc_buf[pos++] = OP_dup; + bc_buf[pos] = get_op + 1; + put_u16(bc_buf + pos + 1, var_idx); + pos += 3; + /* pad with OP_nop */ + while (pos < end_pos) + bc_buf[pos++] = OP_nop; + return pos_next; +} + +static int optimize_scope_make_global_ref(JSContext *ctx, JSFunctionDef *s, + DynBuf *bc, uint8_t *bc_buf, + LabelSlot *ls, int pos_next, + JSAtom var_name) +{ + int label_pos, end_pos, pos, op; + BOOL is_strict_mode = s->is_strict_mode; + + /* replace the reference get/put with normal variable + accesses */ + if (is_strict_mode) { + /* need to check if the variable exists before evaluating the right + expression */ + /* XXX: need an extra OP_true if destructuring an array */ + dbuf_putc(bc, OP_check_var); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + } else { + /* XXX: need 2 extra OP_true if destructuring an array */ + } + if (bc_buf[pos_next] == OP_get_ref_value) { + dbuf_putc(bc, OP_get_var); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + pos_next++; + } + /* remove the OP_label to make room for replacement */ + /* label should have a refcount of 0 anyway */ + /* XXX: should have emitted several OP_nop to avoid this kludge */ + label_pos = ls->pos; + pos = label_pos - 5; + assert(bc_buf[pos] == OP_label); + end_pos = label_pos + 2; + op = bc_buf[label_pos]; + if (is_strict_mode) { + if (op != OP_nop) { + switch(op) { + case OP_insert3: + op = OP_insert2; + break; + case OP_perm4: + op = OP_perm3; + break; + case OP_rot3l: + op = OP_swap; + break; + default: + abort(); + } + bc_buf[pos++] = op; + } + } else { + if (op == OP_insert3) + bc_buf[pos++] = OP_dup; + } + if (is_strict_mode) { + bc_buf[pos] = OP_put_var_strict; + /* XXX: need 1 extra OP_drop if destructuring an array */ + } else { + bc_buf[pos] = OP_put_var; + /* XXX: need 2 extra OP_drop if destructuring an array */ + } + put_u32(bc_buf + pos + 1, JS_DupAtom(ctx, var_name)); + pos += 5; + /* pad with OP_nop */ + while (pos < end_pos) + bc_buf[pos++] = OP_nop; + return pos_next; +} + +static int add_var_this(JSContext *ctx, JSFunctionDef *fd) +{ + int idx; + idx = add_var(ctx, fd, JS_ATOM_this); + if (idx >= 0 && fd->is_derived_class_constructor) { + JSVarDef *vd = &fd->vars[idx]; + /* XXX: should have is_this flag or var type */ + vd->is_lexical = 1; /* used to trigger 'uninitialized' checks + in a derived class constructor */ + } + return idx; +} + +static int resolve_pseudo_var(JSContext *ctx, JSFunctionDef *s, + JSAtom var_name) +{ + int var_idx; + + if (!s->has_this_binding) + return -1; + switch(var_name) { + case JS_ATOM_home_object: + /* 'home_object' pseudo variable */ + if (s->home_object_var_idx < 0) + s->home_object_var_idx = add_var(ctx, s, var_name); + var_idx = s->home_object_var_idx; + break; + case JS_ATOM_this_active_func: + /* 'this.active_func' pseudo variable */ + if (s->this_active_func_var_idx < 0) + s->this_active_func_var_idx = add_var(ctx, s, var_name); + var_idx = s->this_active_func_var_idx; + break; + case JS_ATOM_new_target: + /* 'new.target' pseudo variable */ + if (s->new_target_var_idx < 0) + s->new_target_var_idx = add_var(ctx, s, var_name); + var_idx = s->new_target_var_idx; + break; + case JS_ATOM_this: + /* 'this' pseudo variable */ + if (s->this_var_idx < 0) + s->this_var_idx = add_var_this(ctx, s); + var_idx = s->this_var_idx; + break; + default: + var_idx = -1; + break; + } + return var_idx; +} + +/* test if 'var_name' is in the variable object on the stack. If is it + the case, handle it and jump to 'label_done' */ +static void var_object_test(JSContext *ctx, JSFunctionDef *s, + JSAtom var_name, int op, DynBuf *bc, + int *plabel_done, BOOL is_with) +{ + dbuf_putc(bc, get_with_scope_opcode(op)); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + *plabel_done = new_label_fd(s, *plabel_done); + dbuf_put_u32(bc, *plabel_done); + dbuf_putc(bc, is_with); + update_label(s, *plabel_done, 1); + s->jump_size++; +} + +/* return the position of the next opcode */ +static int resolve_scope_var(JSContext *ctx, JSFunctionDef *s, + JSAtom var_name, int scope_level, int op, + DynBuf *bc, uint8_t *bc_buf, + LabelSlot *ls, int pos_next) +{ + int idx, var_idx, is_put; + int label_done; + JSFunctionDef *fd; + JSVarDef *vd; + BOOL is_pseudo_var, is_arg_scope; + + label_done = -1; + + /* XXX: could be simpler to use a specific function to + resolve the pseudo variables */ + is_pseudo_var = (var_name == JS_ATOM_home_object || + var_name == JS_ATOM_this_active_func || + var_name == JS_ATOM_new_target || + var_name == JS_ATOM_this); + + /* resolve local scoped variables */ + var_idx = -1; + for (idx = s->scopes[scope_level].first; idx >= 0;) { + vd = &s->vars[idx]; + if (vd->var_name == var_name) { + if (op == OP_scope_put_var || op == OP_scope_make_ref) { + if (vd->is_const) { + dbuf_putc(bc, OP_throw_error); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, JS_THROW_VAR_RO); + goto done; + } + } + var_idx = idx; + break; + } else + if (vd->var_name == JS_ATOM__with_ && !is_pseudo_var) { + dbuf_putc(bc, OP_get_loc); + dbuf_put_u16(bc, idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, 1); + } + idx = vd->scope_next; + } + is_arg_scope = (idx == ARG_SCOPE_END); + if (var_idx < 0) { + /* argument scope: variables are not visible but pseudo + variables are visible */ + if (!is_arg_scope) { + var_idx = find_var(ctx, s, var_name); + } + + if (var_idx < 0 && is_pseudo_var) + var_idx = resolve_pseudo_var(ctx, s, var_name); + + if (var_idx < 0 && var_name == JS_ATOM_arguments && + s->has_arguments_binding) { + /* 'arguments' pseudo variable */ + var_idx = add_arguments_var(ctx, s); + } + if (var_idx < 0 && s->is_func_expr && var_name == s->func_name) { + /* add a new variable with the function name */ + var_idx = add_func_var(ctx, s, var_name); + } + } + if (var_idx >= 0) { + if ((op == OP_scope_put_var || op == OP_scope_make_ref) && + !(var_idx & ARGUMENT_VAR_OFFSET) && + s->vars[var_idx].is_const) { + /* only happens when assigning a function expression name + in strict mode */ + dbuf_putc(bc, OP_throw_error); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, JS_THROW_VAR_RO); + goto done; + } + /* OP_scope_put_var_init is only used to initialize a + lexical variable, so it is never used in a with or var object. It + can be used with a closure (module global variable case). */ + switch (op) { + case OP_scope_make_ref: + if (!(var_idx & ARGUMENT_VAR_OFFSET) && + s->vars[var_idx].var_kind == JS_VAR_FUNCTION_NAME) { + /* Create a dummy object reference for the func_var */ + dbuf_putc(bc, OP_object); + dbuf_putc(bc, OP_get_loc); + dbuf_put_u16(bc, var_idx); + dbuf_putc(bc, OP_define_field); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, OP_push_atom_value); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + } else + if (label_done == -1 && can_opt_put_ref_value(bc_buf, ls->pos)) { + int get_op; + if (var_idx & ARGUMENT_VAR_OFFSET) { + get_op = OP_get_arg; + var_idx -= ARGUMENT_VAR_OFFSET; + } else { + if (s->vars[var_idx].is_lexical) + get_op = OP_get_loc_check; + else + get_op = OP_get_loc; + } + pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls, + pos_next, get_op, var_idx); + } else { + /* Create a dummy object with a named slot that is + a reference to the local variable */ + if (var_idx & ARGUMENT_VAR_OFFSET) { + dbuf_putc(bc, OP_make_arg_ref); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET); + } else { + dbuf_putc(bc, OP_make_loc_ref); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_put_u16(bc, var_idx); + } + } + break; + case OP_scope_get_ref: + dbuf_putc(bc, OP_undefined); + /* fall thru */ + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + case OP_scope_put_var_init: + is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); + if (var_idx & ARGUMENT_VAR_OFFSET) { + dbuf_putc(bc, OP_get_arg + is_put); + dbuf_put_u16(bc, var_idx - ARGUMENT_VAR_OFFSET); + } else { + if (is_put) { + if (s->vars[var_idx].is_lexical) { + if (op == OP_scope_put_var_init) { + /* 'this' can only be initialized once */ + if (var_name == JS_ATOM_this) + dbuf_putc(bc, OP_put_loc_check_init); + else + dbuf_putc(bc, OP_put_loc); + } else { + dbuf_putc(bc, OP_put_loc_check); + } + } else { + dbuf_putc(bc, OP_put_loc); + } + } else { + if (s->vars[var_idx].is_lexical) { + dbuf_putc(bc, OP_get_loc_check); + } else { + dbuf_putc(bc, OP_get_loc); + } + } + dbuf_put_u16(bc, var_idx); + } + break; + case OP_scope_delete_var: + dbuf_putc(bc, OP_push_false); + break; + } + goto done; + } + /* check eval object */ + if (!is_arg_scope && s->var_object_idx >= 0 && !is_pseudo_var) { + dbuf_putc(bc, OP_get_loc); + dbuf_put_u16(bc, s->var_object_idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, 0); + } + /* check eval object in argument scope */ + if (s->arg_var_object_idx >= 0 && !is_pseudo_var) { + dbuf_putc(bc, OP_get_loc); + dbuf_put_u16(bc, s->arg_var_object_idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, 0); + } + + /* check parent scopes */ + for (fd = s; fd->parent;) { + scope_level = fd->parent_scope_level; + fd = fd->parent; + for (idx = fd->scopes[scope_level].first; idx >= 0;) { + vd = &fd->vars[idx]; + if (vd->var_name == var_name) { + if (op == OP_scope_put_var || op == OP_scope_make_ref) { + if (vd->is_const) { + dbuf_putc(bc, OP_throw_error); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, JS_THROW_VAR_RO); + goto done; + } + } + var_idx = idx; + break; + } else if (vd->var_name == JS_ATOM__with_ && !is_pseudo_var) { + vd->is_captured = 1; + idx = get_closure_var(ctx, s, fd, FALSE, idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); + if (idx >= 0) { + dbuf_putc(bc, OP_get_var_ref); + dbuf_put_u16(bc, idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, 1); + } + } + idx = vd->scope_next; + } + is_arg_scope = (idx == ARG_SCOPE_END); + if (var_idx >= 0) + break; + + if (!is_arg_scope) { + var_idx = find_var(ctx, fd, var_name); + if (var_idx >= 0) + break; + } + if (is_pseudo_var) { + var_idx = resolve_pseudo_var(ctx, fd, var_name); + if (var_idx >= 0) + break; + } + if (var_name == JS_ATOM_arguments && fd->has_arguments_binding) { + var_idx = add_arguments_var(ctx, fd); + break; + } + if (fd->is_func_expr && fd->func_name == var_name) { + /* add a new variable with the function name */ + var_idx = add_func_var(ctx, fd, var_name); + break; + } + + /* check eval object */ + if (!is_arg_scope && fd->var_object_idx >= 0 && !is_pseudo_var) { + vd = &fd->vars[fd->var_object_idx]; + vd->is_captured = 1; + idx = get_closure_var(ctx, s, fd, FALSE, + fd->var_object_idx, vd->var_name, + FALSE, FALSE, JS_VAR_NORMAL); + dbuf_putc(bc, OP_get_var_ref); + dbuf_put_u16(bc, idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, 0); + } + + /* check eval object in argument scope */ + if (fd->arg_var_object_idx >= 0 && !is_pseudo_var) { + vd = &fd->vars[fd->arg_var_object_idx]; + vd->is_captured = 1; + idx = get_closure_var(ctx, s, fd, FALSE, + fd->arg_var_object_idx, vd->var_name, + FALSE, FALSE, JS_VAR_NORMAL); + dbuf_putc(bc, OP_get_var_ref); + dbuf_put_u16(bc, idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, 0); + } + + if (fd->is_eval) + break; /* it it necessarily the top level function */ + } + + /* check direct eval scope (in the closure of the eval function + which is necessarily at the top level) */ + if (!fd) + fd = s; + if (var_idx < 0 && fd->is_eval) { + int idx1; + for (idx1 = 0; idx1 < fd->closure_var_count; idx1++) { + JSClosureVar *cv = &fd->closure_var[idx1]; + if (var_name == cv->var_name) { + if (fd != s) { + idx = get_closure_var2(ctx, s, fd, + FALSE, + cv->is_arg, idx1, + cv->var_name, cv->is_const, + cv->is_lexical, cv->var_kind); + } else { + idx = idx1; + } + goto has_idx; + } else if ((cv->var_name == JS_ATOM__var_ || + cv->var_name == JS_ATOM__arg_var_ || + cv->var_name == JS_ATOM__with_) && !is_pseudo_var) { + int is_with = (cv->var_name == JS_ATOM__with_); + if (fd != s) { + idx = get_closure_var2(ctx, s, fd, + FALSE, + cv->is_arg, idx1, + cv->var_name, FALSE, FALSE, + JS_VAR_NORMAL); + } else { + idx = idx1; + } + dbuf_putc(bc, OP_get_var_ref); + dbuf_put_u16(bc, idx); + var_object_test(ctx, s, var_name, op, bc, &label_done, is_with); + } + } + } + + if (var_idx >= 0) { + /* find the corresponding closure variable */ + if (var_idx & ARGUMENT_VAR_OFFSET) { + fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1; + idx = get_closure_var(ctx, s, fd, + TRUE, var_idx - ARGUMENT_VAR_OFFSET, + var_name, FALSE, FALSE, JS_VAR_NORMAL); + } else { + fd->vars[var_idx].is_captured = 1; + idx = get_closure_var(ctx, s, fd, + FALSE, var_idx, + var_name, + fd->vars[var_idx].is_const, + fd->vars[var_idx].is_lexical, + fd->vars[var_idx].var_kind); + } + if (idx >= 0) { + has_idx: + if ((op == OP_scope_put_var || op == OP_scope_make_ref) && + s->closure_var[idx].is_const) { + dbuf_putc(bc, OP_throw_error); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, JS_THROW_VAR_RO); + goto done; + } + switch (op) { + case OP_scope_make_ref: + if (s->closure_var[idx].var_kind == JS_VAR_FUNCTION_NAME) { + /* Create a dummy object reference for the func_var */ + dbuf_putc(bc, OP_object); + dbuf_putc(bc, OP_get_var_ref); + dbuf_put_u16(bc, idx); + dbuf_putc(bc, OP_define_field); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, OP_push_atom_value); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + } else + if (label_done == -1 && + can_opt_put_ref_value(bc_buf, ls->pos)) { + int get_op; + if (s->closure_var[idx].is_lexical) + get_op = OP_get_var_ref_check; + else + get_op = OP_get_var_ref; + pos_next = optimize_scope_make_ref(ctx, s, bc, bc_buf, ls, + pos_next, + get_op, idx); + } else { + /* Create a dummy object with a named slot that is + a reference to the closure variable */ + dbuf_putc(bc, OP_make_var_ref_ref); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_put_u16(bc, idx); + } + break; + case OP_scope_get_ref: + /* XXX: should create a dummy object with a named slot that is + a reference to the closure variable */ + dbuf_putc(bc, OP_undefined); + /* fall thru */ + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + case OP_scope_put_var_init: + is_put = (op == OP_scope_put_var || + op == OP_scope_put_var_init); + if (is_put) { + if (s->closure_var[idx].is_lexical) { + if (op == OP_scope_put_var_init) { + /* 'this' can only be initialized once */ + if (var_name == JS_ATOM_this) + dbuf_putc(bc, OP_put_var_ref_check_init); + else + dbuf_putc(bc, OP_put_var_ref); + } else { + dbuf_putc(bc, OP_put_var_ref_check); + } + } else { + dbuf_putc(bc, OP_put_var_ref); + } + } else { + if (s->closure_var[idx].is_lexical) { + dbuf_putc(bc, OP_get_var_ref_check); + } else { + dbuf_putc(bc, OP_get_var_ref); + } + } + dbuf_put_u16(bc, idx); + break; + case OP_scope_delete_var: + dbuf_putc(bc, OP_push_false); + break; + } + goto done; + } + } + + /* global variable access */ + + switch (op) { + case OP_scope_make_ref: + if (label_done == -1 && can_opt_put_global_ref_value(bc_buf, ls->pos)) { + pos_next = optimize_scope_make_global_ref(ctx, s, bc, bc_buf, ls, + pos_next, var_name); + } else { + dbuf_putc(bc, OP_make_var_ref); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + } + break; + case OP_scope_get_ref: + /* XXX: should create a dummy object with a named slot that is + a reference to the global variable */ + dbuf_putc(bc, OP_undefined); + dbuf_putc(bc, OP_get_var); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + break; + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + dbuf_putc(bc, OP_get_var_undef + (op - OP_scope_get_var_undef)); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + break; + case OP_scope_put_var_init: + dbuf_putc(bc, OP_put_var_init); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + break; + case OP_scope_delete_var: + dbuf_putc(bc, OP_delete_var); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + break; + } +done: + if (label_done >= 0) { + dbuf_putc(bc, OP_label); + dbuf_put_u32(bc, label_done); + s->label_slots[label_done].pos2 = bc->size; + } + return pos_next; +} + +/* search in all scopes */ +static int find_private_class_field_all(JSContext *ctx, JSFunctionDef *fd, + JSAtom name, int scope_level) +{ + int idx; + + idx = fd->scopes[scope_level].first; + while (idx >= 0) { + if (fd->vars[idx].var_name == name) + return idx; + idx = fd->vars[idx].scope_next; + } + return -1; +} + +static void get_loc_or_ref(DynBuf *bc, BOOL is_ref, int idx) +{ + /* if the field is not initialized, the error is catched when + accessing it */ + if (is_ref) + dbuf_putc(bc, OP_get_var_ref); + else + dbuf_putc(bc, OP_get_loc); + dbuf_put_u16(bc, idx); +} + +static int resolve_scope_private_field1(JSContext *ctx, + BOOL *pis_ref, int *pvar_kind, + JSFunctionDef *s, + JSAtom var_name, int scope_level) +{ + int idx, var_kind; + JSFunctionDef *fd; + BOOL is_ref; + + fd = s; + is_ref = FALSE; + for(;;) { + idx = find_private_class_field_all(ctx, fd, var_name, scope_level); + if (idx >= 0) { + var_kind = fd->vars[idx].var_kind; + if (is_ref) { + idx = get_closure_var(ctx, s, fd, FALSE, idx, var_name, + TRUE, TRUE, JS_VAR_NORMAL); + if (idx < 0) + return -1; + } + break; + } + scope_level = fd->parent_scope_level; + if (!fd->parent) { + if (fd->is_eval) { + /* closure of the eval function (top level) */ + for (idx = 0; idx < fd->closure_var_count; idx++) { + JSClosureVar *cv = &fd->closure_var[idx]; + if (cv->var_name == var_name) { + var_kind = cv->var_kind; + is_ref = TRUE; + if (fd != s) { + idx = get_closure_var2(ctx, s, fd, + FALSE, + cv->is_arg, idx, + cv->var_name, cv->is_const, + cv->is_lexical, + cv->var_kind); + if (idx < 0) + return -1; + } + goto done; + } + } + } + /* XXX: no line number info */ + // XXX: resolve_scope_private_field1() should take JSParseState *s and use js_parse_error_atom + JS_ThrowSyntaxErrorAtom(ctx, "undefined private field '%s'", + var_name); + return -1; + } else { + fd = fd->parent; + } + is_ref = TRUE; + } + done: + *pis_ref = is_ref; + *pvar_kind = var_kind; + return idx; +} + +/* return 0 if OK or -1 if the private field could not be resolved */ +static int resolve_scope_private_field(JSContext *ctx, JSFunctionDef *s, + JSAtom var_name, int scope_level, int op, + DynBuf *bc) +{ + int idx, var_kind; + BOOL is_ref; + + idx = resolve_scope_private_field1(ctx, &is_ref, &var_kind, s, + var_name, scope_level); + if (idx < 0) + return -1; + assert(var_kind != JS_VAR_NORMAL); + switch (op) { + case OP_scope_get_private_field: + case OP_scope_get_private_field2: + switch(var_kind) { + case JS_VAR_PRIVATE_FIELD: + if (op == OP_scope_get_private_field2) + dbuf_putc(bc, OP_dup); + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_get_private_field); + break; + case JS_VAR_PRIVATE_METHOD: + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_check_brand); + if (op != OP_scope_get_private_field2) + dbuf_putc(bc, OP_nip); + break; + case JS_VAR_PRIVATE_GETTER: + case JS_VAR_PRIVATE_GETTER_SETTER: + if (op == OP_scope_get_private_field2) + dbuf_putc(bc, OP_dup); + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_check_brand); + dbuf_putc(bc, OP_call_method); + dbuf_put_u16(bc, 0); + break; + case JS_VAR_PRIVATE_SETTER: + /* XXX: add clearer error message */ + dbuf_putc(bc, OP_throw_error); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, JS_THROW_VAR_RO); + break; + default: + abort(); + } + break; + case OP_scope_put_private_field: + switch(var_kind) { + case JS_VAR_PRIVATE_FIELD: + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_put_private_field); + break; + case JS_VAR_PRIVATE_METHOD: + case JS_VAR_PRIVATE_GETTER: + /* XXX: add clearer error message */ + dbuf_putc(bc, OP_throw_error); + dbuf_put_u32(bc, JS_DupAtom(ctx, var_name)); + dbuf_putc(bc, JS_THROW_VAR_RO); + break; + case JS_VAR_PRIVATE_SETTER: + case JS_VAR_PRIVATE_GETTER_SETTER: + { + JSAtom setter_name = get_private_setter_name(ctx, var_name); + if (setter_name == JS_ATOM_NULL) + return -1; + idx = resolve_scope_private_field1(ctx, &is_ref, + &var_kind, s, + setter_name, scope_level); + JS_FreeAtom(ctx, setter_name); + if (idx < 0) + return -1; + assert(var_kind == JS_VAR_PRIVATE_SETTER); + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_swap); + /* obj func value */ + dbuf_putc(bc, OP_rot3r); + /* value obj func */ + dbuf_putc(bc, OP_check_brand); + dbuf_putc(bc, OP_rot3l); + /* obj func value */ + dbuf_putc(bc, OP_call_method); + dbuf_put_u16(bc, 1); + dbuf_putc(bc, OP_drop); + } + break; + default: + abort(); + } + break; + case OP_scope_in_private_field: + get_loc_or_ref(bc, is_ref, idx); + dbuf_putc(bc, OP_private_in); + break; + default: + abort(); + } + return 0; +} + +static void mark_eval_captured_variables(JSContext *ctx, JSFunctionDef *s, + int scope_level) +{ + int idx; + JSVarDef *vd; + + for (idx = s->scopes[scope_level].first; idx >= 0;) { + vd = &s->vars[idx]; + vd->is_captured = 1; + idx = vd->scope_next; + } +} + +/* XXX: should handle the argument scope generically */ +static BOOL is_var_in_arg_scope(const JSVarDef *vd) +{ + return (vd->var_name == JS_ATOM_home_object || + vd->var_name == JS_ATOM_this_active_func || + vd->var_name == JS_ATOM_new_target || + vd->var_name == JS_ATOM_this || + vd->var_name == JS_ATOM__arg_var_ || + vd->var_kind == JS_VAR_FUNCTION_NAME); +} + +static void add_eval_variables(JSContext *ctx, JSFunctionDef *s) +{ + JSFunctionDef *fd; + JSVarDef *vd; + int i, scope_level, scope_idx; + BOOL has_arguments_binding, has_this_binding, is_arg_scope; + + /* in non strict mode, variables are created in the caller's + environment object */ + if (!s->is_eval && !s->is_strict_mode) { + s->var_object_idx = add_var(ctx, s, JS_ATOM__var_); + if (s->has_parameter_expressions) { + /* an additional variable object is needed for the + argument scope */ + s->arg_var_object_idx = add_var(ctx, s, JS_ATOM__arg_var_); + } + } + + /* eval can potentially use 'arguments' so we must define it */ + has_this_binding = s->has_this_binding; + if (has_this_binding) { + if (s->this_var_idx < 0) + s->this_var_idx = add_var_this(ctx, s); + if (s->new_target_var_idx < 0) + s->new_target_var_idx = add_var(ctx, s, JS_ATOM_new_target); + if (s->is_derived_class_constructor && s->this_active_func_var_idx < 0) + s->this_active_func_var_idx = add_var(ctx, s, JS_ATOM_this_active_func); + if (s->has_home_object && s->home_object_var_idx < 0) + s->home_object_var_idx = add_var(ctx, s, JS_ATOM_home_object); + } + has_arguments_binding = s->has_arguments_binding; + if (has_arguments_binding) { + add_arguments_var(ctx, s); + /* also add an arguments binding in the argument scope to + raise an error if a direct eval in the argument scope tries + to redefine it */ + if (s->has_parameter_expressions && !s->is_strict_mode) + add_arguments_arg(ctx, s); + } + if (s->is_func_expr && s->func_name != JS_ATOM_NULL) + add_func_var(ctx, s, s->func_name); + + /* eval can use all the variables of the enclosing functions, so + they must be all put in the closure. The closure variables are + ordered by scope. It works only because no closure are created + before. */ + assert(s->is_eval || s->closure_var_count == 0); + + /* XXX: inefficient, but eval performance is less critical */ + fd = s; + for(;;) { + scope_level = fd->parent_scope_level; + fd = fd->parent; + if (!fd) + break; + /* add 'this' if it was not previously added */ + if (!has_this_binding && fd->has_this_binding) { + if (fd->this_var_idx < 0) + fd->this_var_idx = add_var_this(ctx, fd); + if (fd->new_target_var_idx < 0) + fd->new_target_var_idx = add_var(ctx, fd, JS_ATOM_new_target); + if (fd->is_derived_class_constructor && fd->this_active_func_var_idx < 0) + fd->this_active_func_var_idx = add_var(ctx, fd, JS_ATOM_this_active_func); + if (fd->has_home_object && fd->home_object_var_idx < 0) + fd->home_object_var_idx = add_var(ctx, fd, JS_ATOM_home_object); + has_this_binding = TRUE; + } + /* add 'arguments' if it was not previously added */ + if (!has_arguments_binding && fd->has_arguments_binding) { + add_arguments_var(ctx, fd); + has_arguments_binding = TRUE; + } + /* add function name */ + if (fd->is_func_expr && fd->func_name != JS_ATOM_NULL) + add_func_var(ctx, fd, fd->func_name); + + /* add lexical variables */ + scope_idx = fd->scopes[scope_level].first; + while (scope_idx >= 0) { + vd = &fd->vars[scope_idx]; + vd->is_captured = 1; + get_closure_var(ctx, s, fd, FALSE, scope_idx, + vd->var_name, vd->is_const, vd->is_lexical, vd->var_kind); + scope_idx = vd->scope_next; + } + is_arg_scope = (scope_idx == ARG_SCOPE_END); + if (!is_arg_scope) { + /* add unscoped variables */ + /* XXX: propagate is_const and var_kind too ? */ + for(i = 0; i < fd->arg_count; i++) { + vd = &fd->args[i]; + if (vd->var_name != JS_ATOM_NULL) { + get_closure_var(ctx, s, fd, + TRUE, i, vd->var_name, FALSE, + vd->is_lexical, JS_VAR_NORMAL); + } + } + for(i = 0; i < fd->var_count; i++) { + vd = &fd->vars[i]; + /* do not close top level last result */ + if (vd->scope_level == 0 && + vd->var_name != JS_ATOM__ret_ && + vd->var_name != JS_ATOM_NULL) { + get_closure_var(ctx, s, fd, + FALSE, i, vd->var_name, FALSE, + vd->is_lexical, JS_VAR_NORMAL); + } + } + } else { + for(i = 0; i < fd->var_count; i++) { + vd = &fd->vars[i]; + /* do not close top level last result */ + if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) { + get_closure_var(ctx, s, fd, + FALSE, i, vd->var_name, FALSE, + vd->is_lexical, JS_VAR_NORMAL); + } + } + } + if (fd->is_eval) { + int idx; + /* add direct eval variables (we are necessarily at the + top level) */ + for (idx = 0; idx < fd->closure_var_count; idx++) { + JSClosureVar *cv = &fd->closure_var[idx]; + get_closure_var2(ctx, s, fd, + FALSE, cv->is_arg, + idx, cv->var_name, cv->is_const, + cv->is_lexical, cv->var_kind); + } + } + } +} + +static void set_closure_from_var(JSContext *ctx, JSClosureVar *cv, + JSVarDef *vd, int var_idx) +{ + cv->is_local = TRUE; + cv->is_arg = FALSE; + cv->is_const = vd->is_const; + cv->is_lexical = vd->is_lexical; + cv->var_kind = vd->var_kind; + cv->var_idx = var_idx; + cv->var_name = JS_DupAtom(ctx, vd->var_name); +} + +/* for direct eval compilation: add references to the variables of the + calling function */ +static __exception int add_closure_variables(JSContext *ctx, JSFunctionDef *s, + JSFunctionBytecode *b, int scope_idx) +{ + int i, count; + JSVarDef *vd; + BOOL is_arg_scope; + + count = b->arg_count + b->var_count + b->closure_var_count; + s->closure_var = NULL; + s->closure_var_count = 0; + s->closure_var_size = count; + if (count == 0) + return 0; + s->closure_var = js_malloc(ctx, sizeof(s->closure_var[0]) * count); + if (!s->closure_var) + return -1; + /* Add lexical variables in scope at the point of evaluation */ + for (i = scope_idx; i >= 0;) { + vd = &b->vardefs[b->arg_count + i]; + if (vd->scope_level > 0) { + JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; + set_closure_from_var(ctx, cv, vd, i); + } + i = vd->scope_next; + } + is_arg_scope = (i == ARG_SCOPE_END); + if (!is_arg_scope) { + /* Add argument variables */ + for(i = 0; i < b->arg_count; i++) { + JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; + vd = &b->vardefs[i]; + cv->is_local = TRUE; + cv->is_arg = TRUE; + cv->is_const = FALSE; + cv->is_lexical = FALSE; + cv->var_kind = JS_VAR_NORMAL; + cv->var_idx = i; + cv->var_name = JS_DupAtom(ctx, vd->var_name); + } + /* Add local non lexical variables */ + for(i = 0; i < b->var_count; i++) { + vd = &b->vardefs[b->arg_count + i]; + if (vd->scope_level == 0 && vd->var_name != JS_ATOM__ret_) { + JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; + set_closure_from_var(ctx, cv, vd, i); + } + } + } else { + /* only add pseudo variables */ + for(i = 0; i < b->var_count; i++) { + vd = &b->vardefs[b->arg_count + i]; + if (vd->scope_level == 0 && is_var_in_arg_scope(vd)) { + JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; + set_closure_from_var(ctx, cv, vd, i); + } + } + } + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv0 = &b->closure_var[i]; + JSClosureVar *cv = &s->closure_var[s->closure_var_count++]; + cv->is_local = FALSE; + cv->is_arg = cv0->is_arg; + cv->is_const = cv0->is_const; + cv->is_lexical = cv0->is_lexical; + cv->var_kind = cv0->var_kind; + cv->var_idx = i; + cv->var_name = JS_DupAtom(ctx, cv0->var_name); + } + return 0; +} + +typedef struct CodeContext { + const uint8_t *bc_buf; /* code buffer */ + int bc_len; /* length of the code buffer */ + int pos; /* position past the matched code pattern */ + int line_num; /* last visited OP_source_loc parameter or -1 */ + int col_num; /* last visited OP_source_loc parameter or -1 */ + int op; + int idx; + int label; + int val; + JSAtom atom; +} CodeContext; + +#define M2(op1, op2) ((uint32_t)(op1) | ((uint32_t)(op2) << 8)) +#define M3(op1, op2, op3) ((uint32_t)(op1) | ((uint32_t)(op2) << 8) | ((uint32_t)(op3) << 16)) +#define M4(op1, op2, op3, op4) ((uint32_t)(op1) | ((uint32_t)(op2) << 8) | ((uint32_t)(op3) << 16) | ((uint32_t)(op4) << 24)) + +static BOOL code_match(CodeContext *s, int pos, ...) +{ + const uint8_t *tab = s->bc_buf; + int op, len, op1, line_num, col_num, pos_next; + va_list ap; + BOOL ret = FALSE; + + line_num = -1; + col_num = -1; + va_start(ap, pos); + + for(;;) { + op1 = va_arg(ap, int); + if (op1 == -1) { + s->pos = pos; + s->line_num = line_num; + s->col_num = col_num; + ret = TRUE; + break; + } + for (;;) { + if (pos >= s->bc_len) + goto done; + op = tab[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + if (pos_next > s->bc_len) + goto done; + if (op == OP_source_loc) { + line_num = get_u32(tab + pos + 1); + col_num = get_u32(tab + pos + 5); + pos = pos_next; + } else { + break; + } + } + if (op != op1) { + if (op1 == (uint8_t)op1 || !op) + break; + if (op != (uint8_t)op1 + && op != (uint8_t)(op1 >> 8) + && op != (uint8_t)(op1 >> 16) + && op != (uint8_t)(op1 >> 24)) { + break; + } + s->op = op; + } + + pos++; + switch(opcode_info[op].fmt) { + case OP_FMT_loc8: + case OP_FMT_u8: + { + int idx = tab[pos]; + int arg = va_arg(ap, int); + if (arg == -1) { + s->idx = idx; + } else { + if (arg != idx) + goto done; + } + break; + } + case OP_FMT_u16: + case OP_FMT_npop: + case OP_FMT_loc: + case OP_FMT_arg: + case OP_FMT_var_ref: + { + int idx = get_u16(tab + pos); + int arg = va_arg(ap, int); + if (arg == -1) { + s->idx = idx; + } else { + if (arg != idx) + goto done; + } + break; + } + case OP_FMT_i32: + case OP_FMT_u32: + case OP_FMT_label: + case OP_FMT_const: + { + s->label = get_u32(tab + pos); + break; + } + case OP_FMT_label_u16: + { + s->label = get_u32(tab + pos); + s->val = get_u16(tab + pos + 4); + break; + } + case OP_FMT_atom: + { + s->atom = get_u32(tab + pos); + break; + } + case OP_FMT_atom_u8: + { + s->atom = get_u32(tab + pos); + s->val = get_u8(tab + pos + 4); + break; + } + case OP_FMT_atom_u16: + { + s->atom = get_u32(tab + pos); + s->val = get_u16(tab + pos + 4); + break; + } + case OP_FMT_atom_label_u8: + { + s->atom = get_u32(tab + pos); + s->label = get_u32(tab + pos + 4); + s->val = get_u8(tab + pos + 8); + break; + } + default: + break; + } + pos = pos_next; + } + done: + va_end(ap); + return ret; +} + +static void instantiate_hoisted_definitions(JSContext *ctx, JSFunctionDef *s, DynBuf *bc) +{ + int i, idx, label_next = -1; + + /* add the hoisted functions in arguments and local variables */ + for(i = 0; i < s->arg_count; i++) { + JSVarDef *vd = &s->args[i]; + if (vd->func_pool_idx >= 0) { + dbuf_putc(bc, OP_fclosure); + dbuf_put_u32(bc, vd->func_pool_idx); + dbuf_putc(bc, OP_put_arg); + dbuf_put_u16(bc, i); + } + } + for(i = 0; i < s->var_count; i++) { + JSVarDef *vd = &s->vars[i]; + if (vd->scope_level == 0 && vd->func_pool_idx >= 0) { + dbuf_putc(bc, OP_fclosure); + dbuf_put_u32(bc, vd->func_pool_idx); + dbuf_putc(bc, OP_put_loc); + dbuf_put_u16(bc, i); + } + } + + /* the module global variables must be initialized before + evaluating the module so that the exported functions are + visible if there are cyclic module references */ + if (s->module) { + label_next = new_label_fd(s, -1); + + /* if 'this' is true, initialize the global variables and return */ + dbuf_putc(bc, OP_push_this); + dbuf_putc(bc, OP_if_false); + dbuf_put_u32(bc, label_next); + update_label(s, label_next, 1); + s->jump_size++; + } + + /* add the global variables (only happens if s->is_global_var is + true) */ + for(i = 0; i < s->global_var_count; i++) { + JSGlobalVar *hf = &s->global_vars[i]; + int has_closure = 0; + BOOL force_init = hf->force_init; + /* we are in an eval, so the closure contains all the + enclosing variables */ + /* If the outer function has a variable environment, + create a property for the variable there */ + for(idx = 0; idx < s->closure_var_count; idx++) { + JSClosureVar *cv = &s->closure_var[idx]; + if (cv->var_name == hf->var_name) { + has_closure = 2; + force_init = FALSE; + break; + } + if (cv->var_name == JS_ATOM__var_ || + cv->var_name == JS_ATOM__arg_var_) { + dbuf_putc(bc, OP_get_var_ref); + dbuf_put_u16(bc, idx); + has_closure = 1; + force_init = TRUE; + break; + } + } + if (!has_closure) { + int flags; + + flags = 0; + if (s->eval_type != JS_EVAL_TYPE_GLOBAL) + flags |= JS_PROP_CONFIGURABLE; + if (hf->cpool_idx >= 0 && !hf->is_lexical) { + /* global function definitions need a specific handling */ + dbuf_putc(bc, OP_fclosure); + dbuf_put_u32(bc, hf->cpool_idx); + + dbuf_putc(bc, OP_define_func); + dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); + dbuf_putc(bc, flags); + + goto done_global_var; + } else { + if (hf->is_lexical) { + flags |= DEFINE_GLOBAL_LEX_VAR; + if (!hf->is_const) + flags |= JS_PROP_WRITABLE; + } + dbuf_putc(bc, OP_define_var); + dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); + dbuf_putc(bc, flags); + } + } + if (hf->cpool_idx >= 0 || force_init) { + if (hf->cpool_idx >= 0) { + dbuf_putc(bc, OP_fclosure); + dbuf_put_u32(bc, hf->cpool_idx); + if (hf->var_name == JS_ATOM__default_) { + /* set default export function name */ + dbuf_putc(bc, OP_set_name); + dbuf_put_u32(bc, JS_DupAtom(ctx, JS_ATOM_default)); + } + } else { + dbuf_putc(bc, OP_undefined); + } + if (has_closure == 2) { + dbuf_putc(bc, OP_put_var_ref); + dbuf_put_u16(bc, idx); + } else if (has_closure == 1) { + dbuf_putc(bc, OP_define_field); + dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); + dbuf_putc(bc, OP_drop); + } else { + /* XXX: Check if variable is writable and enumerable */ + dbuf_putc(bc, OP_put_var); + dbuf_put_u32(bc, JS_DupAtom(ctx, hf->var_name)); + } + } + done_global_var: + JS_FreeAtom(ctx, hf->var_name); + } + + if (s->module) { + dbuf_putc(bc, OP_return_undef); + + dbuf_putc(bc, OP_label); + dbuf_put_u32(bc, label_next); + s->label_slots[label_next].pos2 = bc->size; + } + + js_free(ctx, s->global_vars); + s->global_vars = NULL; + s->global_var_count = 0; + s->global_var_size = 0; +} + +static int skip_dead_code(JSFunctionDef *s, const uint8_t *bc_buf, int bc_len, + int pos, int *linep, int *colp) +{ + int op, len, label; + + for (; pos < bc_len; pos += len) { + op = bc_buf[pos]; + len = opcode_info[op].size; + if (op == OP_source_loc) { + *linep = get_u32(bc_buf + pos + 1); + *colp = get_u32(bc_buf + pos + 5); + } else if (op == OP_label) { + label = get_u32(bc_buf + pos + 1); + if (update_label(s, label, 0) > 0) + break; + assert(s->label_slots[label].first_reloc == NULL); + } else { + /* XXX: output a warning for unreachable code? */ + JSAtom atom; + switch(opcode_info[op].fmt) { + case OP_FMT_label: + case OP_FMT_label_u16: + label = get_u32(bc_buf + pos + 1); + update_label(s, label, -1); + break; + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + label = get_u32(bc_buf + pos + 5); + update_label(s, label, -1); + /* fall thru */ + case OP_FMT_atom: + case OP_FMT_atom_u8: + case OP_FMT_atom_u16: + atom = get_u32(bc_buf + pos + 1); + JS_FreeAtom(s->ctx, atom); + break; + default: + break; + } + } + } + return pos; +} + +static int get_label_pos(JSFunctionDef *s, int label) +{ + int i, pos; + for (i = 0; i < 20; i++) { + pos = s->label_slots[label].pos; + for (;;) { + switch (s->byte_code.buf[pos]) { + case OP_source_loc: + pos += 9; + continue; + case OP_label: + pos += 5; + continue; + case OP_goto: + label = get_u32(s->byte_code.buf + pos + 1); + break; + default: + return pos; + } + break; + } + } + return pos; +} + +/* convert global variable accesses to local variables or closure + variables when necessary */ +static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s) +{ + int pos, pos_next, bc_len, op, len, i, idx, line_num, col_num; + uint8_t *bc_buf; + JSAtom var_name; + DynBuf bc_out; + CodeContext cc; + int scope; + + cc.bc_buf = bc_buf = s->byte_code.buf; + cc.bc_len = bc_len = s->byte_code.size; + js_dbuf_init(ctx, &bc_out); + + /* first pass for runtime checks (must be done before the + variables are created) */ + for(i = 0; i < s->global_var_count; i++) { + JSGlobalVar *hf = &s->global_vars[i]; + int flags; + + /* check if global variable (XXX: simplify) */ + for(idx = 0; idx < s->closure_var_count; idx++) { + JSClosureVar *cv = &s->closure_var[idx]; + if (cv->var_name == hf->var_name) { + if (s->eval_type == JS_EVAL_TYPE_DIRECT && + cv->is_lexical) { + /* Check if a lexical variable is + redefined as 'var'. XXX: Could abort + compilation here, but for consistency + with the other checks, we delay the + error generation. */ + dbuf_putc(&bc_out, OP_throw_error); + dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name)); + dbuf_putc(&bc_out, JS_THROW_VAR_REDECL); + } + goto next; + } + if (cv->var_name == JS_ATOM__var_ || + cv->var_name == JS_ATOM__arg_var_) + goto next; + } + + dbuf_putc(&bc_out, OP_check_define_var); + dbuf_put_u32(&bc_out, JS_DupAtom(ctx, hf->var_name)); + flags = 0; + if (hf->is_lexical) + flags |= DEFINE_GLOBAL_LEX_VAR; + if (hf->cpool_idx >= 0) + flags |= DEFINE_GLOBAL_FUNC_VAR; + dbuf_putc(&bc_out, flags); + next: ; + } + + line_num = 0; /* avoid warning */ + col_num = 0; + for (pos = 0; pos < bc_len; pos = pos_next) { + op = bc_buf[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + switch(op) { + case OP_source_loc: + line_num = get_u32(bc_buf + pos + 1); + col_num = get_u32(bc_buf + pos + 5); + s->source_loc_size++; + goto no_change; + + case OP_eval: /* convert scope index to adjusted variable index */ + { + int call_argc = get_u16(bc_buf + pos + 1); + scope = get_u16(bc_buf + pos + 1 + 2); + mark_eval_captured_variables(ctx, s, scope); + dbuf_putc(&bc_out, op); + dbuf_put_u16(&bc_out, call_argc); + dbuf_put_u16(&bc_out, s->scopes[scope].first + 1); + } + break; + case OP_apply_eval: /* convert scope index to adjusted variable index */ + scope = get_u16(bc_buf + pos + 1); + mark_eval_captured_variables(ctx, s, scope); + dbuf_putc(&bc_out, op); + dbuf_put_u16(&bc_out, s->scopes[scope].first + 1); + break; + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + case OP_scope_delete_var: + case OP_scope_get_ref: + case OP_scope_put_var_init: + var_name = get_u32(bc_buf + pos + 1); + scope = get_u16(bc_buf + pos + 5); + pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out, + NULL, NULL, pos_next); + JS_FreeAtom(ctx, var_name); + break; + case OP_scope_make_ref: + { + int label; + LabelSlot *ls; + var_name = get_u32(bc_buf + pos + 1); + label = get_u32(bc_buf + pos + 5); + scope = get_u16(bc_buf + pos + 9); + ls = &s->label_slots[label]; + ls->ref_count--; /* always remove label reference */ + pos_next = resolve_scope_var(ctx, s, var_name, scope, op, &bc_out, + bc_buf, ls, pos_next); + JS_FreeAtom(ctx, var_name); + } + break; + case OP_scope_get_private_field: + case OP_scope_get_private_field2: + case OP_scope_put_private_field: + case OP_scope_in_private_field: + { + int ret; + var_name = get_u32(bc_buf + pos + 1); + scope = get_u16(bc_buf + pos + 5); + ret = resolve_scope_private_field(ctx, s, var_name, scope, op, &bc_out); + if (ret < 0) + goto fail; + JS_FreeAtom(ctx, var_name); + } + break; + case OP_gosub: + s->jump_size++; + { + /* remove calls to empty finalizers */ + int label; + LabelSlot *ls; + + label = get_u32(bc_buf + pos + 1); + assert(label >= 0 && label < s->label_count); + ls = &s->label_slots[label]; + if (code_match(&cc, ls->pos, OP_ret, -1)) { + ls->ref_count--; + break; + } + } + goto no_change; + case OP_insert3: + /* Transformation: insert3 put_array_el|put_ref_value drop -> put_array_el|put_ref_value */ + if (code_match(&cc, pos_next, M2(OP_put_array_el, OP_put_ref_value), OP_drop, -1)) { + dbuf_putc(&bc_out, cc.op); + pos_next = cc.pos; + if (cc.line_num == -1) + break; + if (cc.line_num != line_num || cc.col_num != col_num) { + line_num = cc.line_num; + col_num = cc.col_num; + s->source_loc_size++; + dbuf_putc(&bc_out, OP_source_loc); + dbuf_put_u32(&bc_out, line_num); + dbuf_put_u32(&bc_out, col_num); + } + break; + } + goto no_change; + + case OP_goto: + s->jump_size++; + /* fall thru */ + case OP_tail_call: + case OP_tail_call_method: + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_throw_error: + case OP_ret: + { + /* remove dead code */ + int line = -1; + int col = -1; + dbuf_put(&bc_out, bc_buf + pos, len); + pos = skip_dead_code(s, bc_buf, bc_len, pos + len, + &line, &col); + pos_next = pos; + if (line < 0 || pos >= bc_len) + break; + if (line_num != line || col_num != col) { + line_num = line; + col_num = col; + s->source_loc_size++; + dbuf_putc(&bc_out, OP_source_loc); + dbuf_put_u32(&bc_out, line_num); + dbuf_put_u32(&bc_out, col_num); + } + break; + } + goto no_change; + + case OP_label: + { + int label; + LabelSlot *ls; + + label = get_u32(bc_buf + pos + 1); + assert(label >= 0 && label < s->label_count); + ls = &s->label_slots[label]; + ls->pos2 = bc_out.size + opcode_info[op].size; + } + goto no_change; + + case OP_enter_scope: + { + int scope_idx, scope = get_u16(bc_buf + pos + 1); + + if (scope == s->body_scope) { + instantiate_hoisted_definitions(ctx, s, &bc_out); + } + + for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) { + JSVarDef *vd = &s->vars[scope_idx]; + if (vd->scope_level == scope) { + if (scope_idx != s->arguments_arg_idx) { + if (vd->var_kind == JS_VAR_FUNCTION_DECL || + vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) { + /* Initialize lexical variable upon entering scope */ + dbuf_putc(&bc_out, OP_fclosure); + dbuf_put_u32(&bc_out, vd->func_pool_idx); + dbuf_putc(&bc_out, OP_put_loc); + dbuf_put_u16(&bc_out, scope_idx); + } else { + /* XXX: should check if variable can be used + before initialization */ + dbuf_putc(&bc_out, OP_set_loc_uninitialized); + dbuf_put_u16(&bc_out, scope_idx); + } + } + scope_idx = vd->scope_next; + } else { + break; + } + } + } + break; + + case OP_leave_scope: + { + int scope_idx, scope = get_u16(bc_buf + pos + 1); + + for(scope_idx = s->scopes[scope].first; scope_idx >= 0;) { + JSVarDef *vd = &s->vars[scope_idx]; + if (vd->scope_level == scope) { + if (vd->is_captured) { + dbuf_putc(&bc_out, OP_close_loc); + dbuf_put_u16(&bc_out, scope_idx); + } + scope_idx = vd->scope_next; + } else { + break; + } + } + } + break; + + case OP_set_name: + { + /* remove dummy set_name opcodes */ + JSAtom name = get_u32(bc_buf + pos + 1); + if (name == JS_ATOM_NULL) + break; + } + goto no_change; + + case OP_if_false: + case OP_if_true: + case OP_catch: + s->jump_size++; + goto no_change; + + case OP_dup: + /* Transformation: dup if_false(l1) drop, l1: if_false(l2) -> if_false(l2) */ + /* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2) */ + if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), OP_drop, -1)) { + int lab0, lab1, op1, pos1, line1, col1, pos2; + lab0 = lab1 = cc.label; + assert(lab1 >= 0 && lab1 < s->label_count); + op1 = cc.op; + pos1 = cc.pos; + line1 = cc.line_num; + col1 = cc.col_num; + while (code_match(&cc, (pos2 = get_label_pos(s, lab1)), OP_dup, op1, OP_drop, -1)) { + lab1 = cc.label; + } + if (code_match(&cc, pos2, op1, -1)) { + s->jump_size++; + update_label(s, lab0, -1); + update_label(s, cc.label, +1); + dbuf_putc(&bc_out, op1); + dbuf_put_u32(&bc_out, cc.label); + pos_next = pos1; + if (line1 == -1) + break; + if (line1 != line_num || col1 != col_num) { + line_num = line1; + col_num = col1; + s->source_loc_size++; + dbuf_putc(&bc_out, OP_source_loc); + dbuf_put_u32(&bc_out, line_num); + dbuf_put_u32(&bc_out, col_num); + } + break; + } + } + goto no_change; + + case OP_nop: + /* remove erased code */ + break; + case OP_set_class_name: + /* only used during parsing */ + break; + + case OP_get_field_opt_chain: /* equivalent to OP_get_field */ + { + JSAtom name = get_u32(bc_buf + pos + 1); + dbuf_putc(&bc_out, OP_get_field); + dbuf_put_u32(&bc_out, name); + } + break; + case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */ + dbuf_putc(&bc_out, OP_get_array_el); + break; + + default: + no_change: + dbuf_put(&bc_out, bc_buf + pos, len); + break; + } + } + + /* set the new byte code */ + dbuf_free(&s->byte_code); + s->byte_code = bc_out; + if (dbuf_error(&s->byte_code)) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + return 0; + fail: + /* continue the copy to keep the atom refcounts consistent */ + /* XXX: find a better solution ? */ + for (; pos < bc_len; pos = pos_next) { + op = bc_buf[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + dbuf_put(&bc_out, bc_buf + pos, len); + } + dbuf_free(&s->byte_code); + s->byte_code = bc_out; + return -1; +} + +/* the pc2line table gives a line number for each PC value */ +static void add_pc2line_info(JSFunctionDef *s, uint32_t pc, + int line_num, int col_num) +{ + if (s->source_loc_slots == NULL) + return; + if (s->source_loc_count >= s->source_loc_size) + return; + if (pc < s->line_number_last_pc) + return; + if (line_num == s->line_number_last) + if (col_num == s->col_number_last) + return; + s->source_loc_slots[s->source_loc_count].pc = pc; + s->source_loc_slots[s->source_loc_count].line_num = line_num; + s->source_loc_slots[s->source_loc_count].col_num = col_num; + s->source_loc_count++; + s->line_number_last_pc = pc; + s->line_number_last = line_num; + s->col_number_last = col_num; +} + +static void compute_pc2line_info(JSFunctionDef *s) +{ + if (s->source_loc_slots) { + int last_line_num = s->line_num; + int last_col_num = s->col_num; + uint32_t last_pc = 0; + int i; + + js_dbuf_init(s->ctx, &s->pc2line); + for (i = 0; i < s->source_loc_count; i++) { + uint32_t pc = s->source_loc_slots[i].pc; + int line_num = s->source_loc_slots[i].line_num; + int col_num = s->source_loc_slots[i].col_num; + int diff_pc, diff_line, diff_col; + + if (line_num < 0) + continue; + + diff_pc = pc - last_pc; + if (diff_pc < 0) + continue; + + diff_line = line_num - last_line_num; + diff_col = col_num - last_col_num; + if (diff_line == 0 && diff_col == 0) + continue; + + if (diff_line >= PC2LINE_BASE && + diff_line < PC2LINE_BASE + PC2LINE_RANGE && + diff_pc <= PC2LINE_DIFF_PC_MAX) { + dbuf_putc(&s->pc2line, (diff_line - PC2LINE_BASE) + + diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST); + } else { + /* longer encoding */ + dbuf_putc(&s->pc2line, 0); + dbuf_put_leb128(&s->pc2line, diff_pc); + dbuf_put_sleb128(&s->pc2line, diff_line); + } + dbuf_put_sleb128(&s->pc2line, diff_col); + + last_pc = pc; + last_line_num = line_num; + last_col_num = col_num; + } + } +} + +static RelocEntry *add_reloc(JSContext *ctx, LabelSlot *ls, uint32_t addr, int size) +{ + RelocEntry *re; + re = js_malloc(ctx, sizeof(*re)); + if (!re) + return NULL; + re->addr = addr; + re->size = size; + re->next = ls->first_reloc; + ls->first_reloc = re; + return re; +} + +static BOOL code_has_label(CodeContext *s, int pos, int label) +{ + while (pos < s->bc_len) { + int op = s->bc_buf[pos]; + if (op == OP_source_loc) { + pos += 9; + continue; + } + if (op == OP_label) { + int lab = get_u32(s->bc_buf + pos + 1); + if (lab == label) + return TRUE; + pos += 5; + continue; + } + if (op == OP_goto) { + int lab = get_u32(s->bc_buf + pos + 1); + if (lab == label) + return TRUE; + } + break; + } + return FALSE; +} + +/* return the target label, following the OP_goto jumps + the first opcode at destination is stored in *pop + */ +static int find_jump_target(JSFunctionDef *s, int label, int *pop) +{ + int i, pos, op; + + update_label(s, label, -1); + for (i = 0; i < 10; i++) { + assert(label >= 0 && label < s->label_count); + pos = s->label_slots[label].pos2; + for (;;) { + switch(op = s->byte_code.buf[pos]) { + case OP_source_loc: + case OP_label: + pos += opcode_info[op].size; + continue; + case OP_goto: + label = get_u32(s->byte_code.buf + pos + 1); + break; + case OP_drop: + /* ignore drop opcodes if followed by OP_return_undef */ + while (s->byte_code.buf[++pos] == OP_drop) + continue; + if (s->byte_code.buf[pos] == OP_return_undef) + op = OP_return_undef; + /* fall thru */ + default: + goto done; + } + break; + } + } + /* cycle detected, could issue a warning */ + done: + *pop = op; + update_label(s, label, +1); + return label; +} + +static void push_short_int(DynBuf *bc_out, int val) +{ + if (val >= -1 && val <= 7) { + dbuf_putc(bc_out, OP_push_0 + val); + return; + } + if (val == (int8_t)val) { + dbuf_putc(bc_out, OP_push_i8); + dbuf_putc(bc_out, val); + return; + } + if (val == (int16_t)val) { + dbuf_putc(bc_out, OP_push_i16); + dbuf_put_u16(bc_out, val); + return; + } + dbuf_putc(bc_out, OP_push_i32); + dbuf_put_u32(bc_out, val); +} + +static void put_short_code(DynBuf *bc_out, int op, int idx) +{ + if (idx < 4) { + switch (op) { + case OP_get_loc: + dbuf_putc(bc_out, OP_get_loc0 + idx); + return; + case OP_put_loc: + dbuf_putc(bc_out, OP_put_loc0 + idx); + return; + case OP_set_loc: + dbuf_putc(bc_out, OP_set_loc0 + idx); + return; + case OP_get_arg: + dbuf_putc(bc_out, OP_get_arg0 + idx); + return; + case OP_put_arg: + dbuf_putc(bc_out, OP_put_arg0 + idx); + return; + case OP_set_arg: + dbuf_putc(bc_out, OP_set_arg0 + idx); + return; + case OP_get_var_ref: + dbuf_putc(bc_out, OP_get_var_ref0 + idx); + return; + case OP_put_var_ref: + dbuf_putc(bc_out, OP_put_var_ref0 + idx); + return; + case OP_set_var_ref: + dbuf_putc(bc_out, OP_set_var_ref0 + idx); + return; + case OP_call: + dbuf_putc(bc_out, OP_call0 + idx); + return; + } + } + if (idx < 256) { + switch (op) { + case OP_get_loc: + dbuf_putc(bc_out, OP_get_loc8); + dbuf_putc(bc_out, idx); + return; + case OP_put_loc: + dbuf_putc(bc_out, OP_put_loc8); + dbuf_putc(bc_out, idx); + return; + case OP_set_loc: + dbuf_putc(bc_out, OP_set_loc8); + dbuf_putc(bc_out, idx); + return; + } + } + dbuf_putc(bc_out, op); + dbuf_put_u16(bc_out, idx); +} + +/* peephole optimizations and resolve goto/labels */ +static __exception int resolve_labels(JSContext *ctx, JSFunctionDef *s) +{ + int pos, pos_next, bc_len, op, op1, len, i, line_num, col_num, patch_offsets; + const uint8_t *bc_buf; + DynBuf bc_out; + LabelSlot *label_slots, *ls; + RelocEntry *re, *re_next; + CodeContext cc; + int label; + JumpSlot *jp; + + label_slots = s->label_slots; + + line_num = s->line_num; + col_num = s->col_num; + + cc.bc_buf = bc_buf = s->byte_code.buf; + cc.bc_len = bc_len = s->byte_code.size; + js_dbuf_init(ctx, &bc_out); + + if (s->jump_size) { + s->jump_slots = js_mallocz(s->ctx, sizeof(*s->jump_slots) * s->jump_size); + if (s->jump_slots == NULL) + return -1; + } + + if (s->source_loc_size) { + s->source_loc_slots = js_mallocz(s->ctx, sizeof(*s->source_loc_slots) * s->source_loc_size); + if (s->source_loc_slots == NULL) + return -1; + s->line_number_last = s->line_num; + s->col_number_last = s->col_num; + s->line_number_last_pc = 0; + } + + /* initialize the 'home_object' variable if needed */ + if (s->home_object_var_idx >= 0) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_HOME_OBJECT); + put_short_code(&bc_out, OP_put_loc, s->home_object_var_idx); + } + /* initialize the 'this.active_func' variable if needed */ + if (s->this_active_func_var_idx >= 0) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); + put_short_code(&bc_out, OP_put_loc, s->this_active_func_var_idx); + } + /* initialize the 'new.target' variable if needed */ + if (s->new_target_var_idx >= 0) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_NEW_TARGET); + put_short_code(&bc_out, OP_put_loc, s->new_target_var_idx); + } + /* initialize the 'this' variable if needed. In a derived class + constructor, this is initially uninitialized. */ + if (s->this_var_idx >= 0) { + if (s->is_derived_class_constructor) { + dbuf_putc(&bc_out, OP_set_loc_uninitialized); + dbuf_put_u16(&bc_out, s->this_var_idx); + } else { + dbuf_putc(&bc_out, OP_push_this); + put_short_code(&bc_out, OP_put_loc, s->this_var_idx); + } + } + /* initialize the 'arguments' variable if needed */ + if (s->arguments_var_idx >= 0) { + if (s->is_strict_mode || !s->has_simple_parameter_list) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_ARGUMENTS); + } else { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS); + } + if (s->arguments_arg_idx >= 0) + put_short_code(&bc_out, OP_set_loc, s->arguments_arg_idx); + put_short_code(&bc_out, OP_put_loc, s->arguments_var_idx); + } + /* initialize a reference to the current function if needed */ + if (s->func_var_idx >= 0) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); + put_short_code(&bc_out, OP_put_loc, s->func_var_idx); + } + /* initialize the variable environment object if needed */ + if (s->var_object_idx >= 0) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); + put_short_code(&bc_out, OP_put_loc, s->var_object_idx); + } + if (s->arg_var_object_idx >= 0) { + dbuf_putc(&bc_out, OP_special_object); + dbuf_putc(&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); + put_short_code(&bc_out, OP_put_loc, s->arg_var_object_idx); + } + + for (pos = 0; pos < bc_len; pos = pos_next) { + int val; + op = bc_buf[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + switch(op) { + case OP_source_loc: + /* line number info (for debug). We put it in a separate + compressed table to reduce memory usage and get better + performance */ + line_num = get_u32(bc_buf + pos + 1); + col_num = get_u32(bc_buf + pos + 5); + break; + + case OP_label: + { + label = get_u32(bc_buf + pos + 1); + assert(label >= 0 && label < s->label_count); + ls = &label_slots[label]; + assert(ls->addr == -1); + ls->addr = bc_out.size; + /* resolve the relocation entries */ + for(re = ls->first_reloc; re != NULL; re = re_next) { + int diff = ls->addr - re->addr; + re_next = re->next; + switch (re->size) { + case 4: + put_u32(bc_out.buf + re->addr, diff); + break; + case 2: + assert(diff == (int16_t)diff); + put_u16(bc_out.buf + re->addr, diff); + break; + case 1: + assert(diff == (int8_t)diff); + put_u8(bc_out.buf + re->addr, diff); + break; + } + js_free(ctx, re); + } + ls->first_reloc = NULL; + } + break; + + case OP_call: + case OP_call_method: + { + /* detect and transform tail calls */ + int argc; + argc = get_u16(bc_buf + pos + 1); + if (code_match(&cc, pos_next, OP_return, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op + 1, argc); + pos_next = skip_dead_code(s, bc_buf, bc_len, cc.pos, + &line_num, &col_num); + break; + } + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op, argc); + break; + } + goto no_change; + + case OP_return: + case OP_return_undef: + case OP_return_async: + case OP_throw: + case OP_throw_error: + pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, + &line_num, &col_num); + goto no_change; + + case OP_goto: + label = get_u32(bc_buf + pos + 1); + has_goto: + { + /* Use custom matcher because multiple labels can follow */ + label = find_jump_target(s, label, &op1); + if (code_has_label(&cc, pos_next, label)) { + /* jump to next instruction: remove jump */ + update_label(s, label, -1); + break; + } + if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) { + /* jump to return/throw: remove jump, append return/throw */ + /* updating the line number obfuscates assembly listing */ + update_label(s, label, -1); + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, op1); + pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, + &line_num, &col_num); + break; + } + /* XXX: should duplicate single instructions followed by goto or return */ + /* For example, can match one of these followed by return: + push_i32 / push_const / push_atom_value / get_var / + undefined / null / push_false / push_true / get_ref_value / + get_loc / get_arg / get_var_ref + */ + } + goto has_label; + + case OP_gosub: + label = get_u32(bc_buf + pos + 1); + goto has_label; + + case OP_catch: + label = get_u32(bc_buf + pos + 1); + goto has_label; + + case OP_if_true: + case OP_if_false: + label = get_u32(bc_buf + pos + 1); + label = find_jump_target(s, label, &op1); + /* transform if_false/if_true(l1) label(l1) -> drop label(l1) */ + if (code_has_label(&cc, pos_next, label)) { + update_label(s, label, -1); + dbuf_putc(&bc_out, OP_drop); + break; + } + /* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1) */ + if (code_match(&cc, pos_next, OP_goto, -1)) { + int pos1 = cc.pos; + int line1 = cc.line_num; + int col1 = cc.col_num; + if (code_has_label(&cc, pos1, label)) { + if (line1 >= 0) line_num = line1; + if (col1 >= 0) col_num = col1; + pos_next = pos1; + update_label(s, label, -1); + label = cc.label; + op ^= OP_if_true ^ OP_if_false; + } + } + has_label: + add_pc2line_info(s, bc_out.size, line_num, col_num); + if (op == OP_goto) { + pos_next = skip_dead_code(s, bc_buf, bc_len, pos_next, + &line_num, &col_num); + } + assert(label >= 0 && label < s->label_count); + ls = &label_slots[label]; + jp = &s->jump_slots[s->jump_count++]; + jp->op = op; + jp->size = 4; + jp->pos = bc_out.size + 1; + jp->label = label; + + if (ls->addr == -1) { + int diff = ls->pos2 - pos - 1; + if (diff < 128 && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { + jp->size = 1; + jp->op = OP_if_false8 + (op - OP_if_false); + dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false)); + dbuf_putc(&bc_out, 0); + if (!add_reloc(ctx, ls, bc_out.size - 1, 1)) + goto fail; + break; + } + if (diff < 32768 && op == OP_goto) { + jp->size = 2; + jp->op = OP_goto16; + dbuf_putc(&bc_out, OP_goto16); + dbuf_put_u16(&bc_out, 0); + if (!add_reloc(ctx, ls, bc_out.size - 2, 2)) + goto fail; + break; + } + } else { + int diff = ls->addr - bc_out.size - 1; + if (diff == (int8_t)diff && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { + jp->size = 1; + jp->op = OP_if_false8 + (op - OP_if_false); + dbuf_putc(&bc_out, OP_if_false8 + (op - OP_if_false)); + dbuf_putc(&bc_out, diff); + break; + } + if (diff == (int16_t)diff && op == OP_goto) { + jp->size = 2; + jp->op = OP_goto16; + dbuf_putc(&bc_out, OP_goto16); + dbuf_put_u16(&bc_out, diff); + break; + } + } + dbuf_putc(&bc_out, op); + dbuf_put_u32(&bc_out, ls->addr - bc_out.size); + if (ls->addr == -1) { + /* unresolved yet: create a new relocation entry */ + if (!add_reloc(ctx, ls, bc_out.size - 4, 4)) + goto fail; + } + break; + case OP_with_get_var: + case OP_with_put_var: + case OP_with_delete_var: + case OP_with_make_ref: + case OP_with_get_ref: + case OP_with_get_ref_undef: + { + JSAtom atom; + int is_with; + + atom = get_u32(bc_buf + pos + 1); + label = get_u32(bc_buf + pos + 5); + is_with = bc_buf[pos + 9]; + label = find_jump_target(s, label, &op1); + assert(label >= 0 && label < s->label_count); + ls = &label_slots[label]; + add_pc2line_info(s, bc_out.size, line_num, col_num); + jp = &s->jump_slots[s->jump_count++]; + jp->op = op; + jp->size = 4; + jp->pos = bc_out.size + 5; + jp->label = label; + dbuf_putc(&bc_out, op); + dbuf_put_u32(&bc_out, atom); + dbuf_put_u32(&bc_out, ls->addr - bc_out.size); + if (ls->addr == -1) { + /* unresolved yet: create a new relocation entry */ + if (!add_reloc(ctx, ls, bc_out.size - 4, 4)) + goto fail; + } + dbuf_putc(&bc_out, is_with); + } + break; + + case OP_drop: + /* remove useless drops before return */ + if (code_match(&cc, pos_next, OP_return_undef, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + break; + } + goto no_change; + + case OP_null: + /* transform null strict_eq into is_null */ + if (code_match(&cc, pos_next, OP_strict_eq, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_is_null); + pos_next = cc.pos; + break; + } + /* transform null strict_neq if_false/if_true -> is_null if_true/if_false */ + if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_is_null); + pos_next = cc.pos; + label = cc.label; + op = cc.op ^ OP_if_false ^ OP_if_true; + goto has_label; + } + /* fall thru */ + case OP_push_false: + case OP_push_true: + val = (op == OP_push_true); + if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) { + has_constant_test: + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + if (val == cc.op - OP_if_false) { + /* transform null if_false(l1) -> goto l1 */ + /* transform false if_false(l1) -> goto l1 */ + /* transform true if_true(l1) -> goto l1 */ + pos_next = cc.pos; + op = OP_goto; + label = cc.label; + goto has_goto; + } else { + /* transform null if_true(l1) -> nop */ + /* transform false if_true(l1) -> nop */ + /* transform true if_false(l1) -> nop */ + pos_next = cc.pos; + update_label(s, cc.label, -1); + break; + } + } + goto no_change; + + case OP_push_i32: + /* transform i32(val) neg -> i32(-val) */ + val = get_i32(bc_buf + pos + 1); + if ((val != INT32_MIN && val != 0) + && code_match(&cc, pos_next, OP_neg, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + if (code_match(&cc, cc.pos, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + } else { + add_pc2line_info(s, bc_out.size, line_num, col_num); + push_short_int(&bc_out, -val); + } + pos_next = cc.pos; + break; + } + /* remove push/drop pairs generated by the parser */ + if (code_match(&cc, pos_next, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + pos_next = cc.pos; + break; + } + /* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */ + if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) { + val = (val != 0); + goto has_constant_test; + } + add_pc2line_info(s, bc_out.size, line_num, col_num); + push_short_int(&bc_out, val); + break; + + case OP_push_const: + case OP_fclosure: + { + int idx = get_u32(bc_buf + pos + 1); + if (idx < 256) { + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_push_const8 + op - OP_push_const); + dbuf_putc(&bc_out, idx); + break; + } + } + goto no_change; + + case OP_get_field: + { + JSAtom atom = get_u32(bc_buf + pos + 1); + if (atom == JS_ATOM_length) { + JS_FreeAtom(ctx, atom); + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_get_length); + break; + } + } + goto no_change; + + case OP_push_atom_value: + { + JSAtom atom = get_u32(bc_buf + pos + 1); + /* remove push/drop pairs generated by the parser */ + if (code_match(&cc, pos_next, OP_drop, -1)) { + JS_FreeAtom(ctx, atom); + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + pos_next = cc.pos; + break; + } + if (atom == JS_ATOM_empty_string) { + JS_FreeAtom(ctx, atom); + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_push_empty_string); + break; + } + } + goto no_change; + + case OP_to_propkey: + case OP_to_propkey2: + /* remove redundant to_propkey/to_propkey2 opcodes when storing simple data */ + if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_put_array_el, -1) + || code_match(&cc, pos_next, M3(OP_push_i32, OP_push_const, OP_push_atom_value), OP_put_array_el, -1) + || code_match(&cc, pos_next, M4(OP_undefined, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) { + break; + } + goto no_change; + + case OP_undefined: + /* remove push/drop pairs generated by the parser */ + if (code_match(&cc, pos_next, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + pos_next = cc.pos; + break; + } + /* transform undefined return -> return_undefined */ + if (code_match(&cc, pos_next, OP_return, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_return_undef); + pos_next = cc.pos; + break; + } + /* transform undefined if_true(l1)/if_false(l1) -> nop/goto(l1) */ + if (code_match(&cc, pos_next, M2(OP_if_false, OP_if_true), -1)) { + val = 0; + goto has_constant_test; + } + /* transform undefined strict_eq -> is_undefined */ + if (code_match(&cc, pos_next, OP_strict_eq, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_is_undefined); + pos_next = cc.pos; + break; + } + /* transform undefined strict_neq if_false/if_true -> is_undefined if_true/if_false */ + if (code_match(&cc, pos_next, OP_strict_neq, M2(OP_if_false, OP_if_true), -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_is_undefined); + pos_next = cc.pos; + label = cc.label; + op = cc.op ^ OP_if_false ^ OP_if_true; + goto has_label; + } + goto no_change; + + case OP_insert2: + /* Transformation: + insert2 put_field(a) drop -> put_field(a) + insert2 put_var_strict(a) drop -> put_var_strict(a) + */ + if (code_match(&cc, pos_next, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, cc.op); + dbuf_put_u32(&bc_out, cc.atom); + pos_next = cc.pos; + break; + } + goto no_change; + + case OP_dup: + { + /* Transformation: dup put_x(n) drop -> put_x(n) */ + int op1, line2 = -1; + /* Transformation: dup put_x(n) -> set_x(n) */ + if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + op1 = cc.op + 1; /* put_x -> set_x */ + pos_next = cc.pos; + if (code_match(&cc, cc.pos, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + op1 -= 1; /* set_x drop -> put_x */ + pos_next = cc.pos; + if (code_match(&cc, cc.pos, op1 - 1, cc.idx, -1)) { + line2 = cc.line_num; /* delay line number update */ + op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ + pos_next = cc.pos; + } + } + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op1, cc.idx); + if (line2 >= 0) line_num = line2; + break; + } + } + goto no_change; + + case OP_swap: + // transformation: swap swap -> nothing! + if (code_match(&cc, pos_next, OP_swap, -1, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + pos_next = cc.pos; + break; + } + goto no_change; + + case OP_get_loc: + { + /* transformation: + get_loc(n) post_dec put_loc(n) drop -> dec_loc(n) + get_loc(n) post_inc put_loc(n) drop -> inc_loc(n) + get_loc(n) dec dup put_loc(n) drop -> dec_loc(n) + get_loc(n) inc dup put_loc(n) drop -> inc_loc(n) + */ + int idx; + idx = get_u16(bc_buf + pos + 1); + if (idx >= 256) + goto no_change; + if (code_match(&cc, pos_next, M2(OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1) || + code_match(&cc, pos_next, M2(OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc); + dbuf_putc(&bc_out, idx); + pos_next = cc.pos; + break; + } + /* transformation: + get_loc(n) push_atom_value(x) add dup put_loc(n) drop -> push_atom_value(x) add_loc(n) + */ + if (code_match(&cc, pos_next, OP_push_atom_value, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + if (cc.atom == JS_ATOM_empty_string) { + JS_FreeAtom(ctx, cc.atom); + dbuf_putc(&bc_out, OP_push_empty_string); + } else { + dbuf_putc(&bc_out, OP_push_atom_value); + dbuf_put_u32(&bc_out, cc.atom); + } + dbuf_putc(&bc_out, OP_add_loc); + dbuf_putc(&bc_out, idx); + pos_next = cc.pos; + break; + } + /* transformation: + get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x) add_loc(n) + */ + if (code_match(&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + push_short_int(&bc_out, cc.label); + dbuf_putc(&bc_out, OP_add_loc); + dbuf_putc(&bc_out, idx); + pos_next = cc.pos; + break; + } + /* transformation: XXX: also do these: + get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n) + get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n) + get_loc(n) get_var_ref(x) add dup put_loc(n) drop -> get_var_ref(x) add_loc(n) + */ + if (code_match(&cc, pos_next, M3(OP_get_loc, OP_get_arg, OP_get_var_ref), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, cc.op, cc.idx); + dbuf_putc(&bc_out, OP_add_loc); + dbuf_putc(&bc_out, idx); + pos_next = cc.pos; + break; + } + /* transformation: get_loc(0) get_loc(1) -> get_loc0_loc1 */ + if (idx == 0 && code_match(&cc, pos_next, OP_get_loc, 1, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_get_loc0_loc1); + pos_next = cc.pos; + break; + } + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op, idx); + } + break; + case OP_get_arg: + case OP_get_var_ref: + { + int idx; + idx = get_u16(bc_buf + pos + 1); + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op, idx); + } + break; + case OP_put_loc: + case OP_put_arg: + case OP_put_var_ref: + { + /* transformation: put_x(n) get_x(n) -> set_x(n) */ + int idx; + idx = get_u16(bc_buf + pos + 1); + if (code_match(&cc, pos_next, op - 1, idx, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op + 1, idx); + pos_next = cc.pos; + break; + } + add_pc2line_info(s, bc_out.size, line_num, col_num); + put_short_code(&bc_out, op, idx); + } + break; + + case OP_post_inc: + case OP_post_dec: + { + /* transformation: + post_inc put_x drop -> inc put_x + post_inc perm3 put_field drop -> inc put_field + post_inc perm3 put_var_strict drop -> inc put_var_strict + post_inc perm4 put_array_el drop -> inc put_array_el + */ + int op1, idx; + if (code_match(&cc, pos_next, M3(OP_put_loc, OP_put_arg, OP_put_var_ref), -1, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + op1 = cc.op; + idx = cc.idx; + pos_next = cc.pos; + if (code_match(&cc, cc.pos, op1 - 1, idx, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ + pos_next = cc.pos; + } + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); + put_short_code(&bc_out, op1, idx); + break; + } + if (code_match(&cc, pos_next, OP_perm3, M2(OP_put_field, OP_put_var_strict), OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); + dbuf_putc(&bc_out, cc.op); + dbuf_put_u32(&bc_out, cc.atom); + pos_next = cc.pos; + break; + } + if (code_match(&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, OP_dec + (op - OP_post_dec)); + dbuf_putc(&bc_out, OP_put_array_el); + pos_next = cc.pos; + break; + } + } + goto no_change; + + case OP_typeof: + /* simplify typeof tests */ + if (code_match(&cc, pos_next, OP_push_atom_value, M4(OP_strict_eq, OP_strict_neq, OP_eq, OP_neq), -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + int op1 = (cc.op == OP_strict_eq || cc.op == OP_eq) ? OP_strict_eq : OP_strict_neq; + int op2 = -1; + switch (cc.atom) { + case JS_ATOM_undefined: + op2 = OP_typeof_is_undefined; + break; + case JS_ATOM_function: + op2 = OP_typeof_is_function; + break; + } + if (op2 >= 0) { + /* transform typeof(s) == "<type>" into is_<type> */ + if (op1 == OP_strict_eq) { + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, op2); + JS_FreeAtom(ctx, cc.atom); + pos_next = cc.pos; + break; + } + if (op1 == OP_strict_neq && code_match(&cc, cc.pos, OP_if_false, -1)) { + /* transform typeof(s) != "<type>" if_false into is_<type> if_true */ + if (cc.line_num >= 0) line_num = cc.line_num; + if (cc.col_num >= 0) col_num = cc.col_num; + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_putc(&bc_out, op2); + JS_FreeAtom(ctx, cc.atom); + pos_next = cc.pos; + label = cc.label; + op = OP_if_true; + goto has_label; + } + } + } + goto no_change; + + default: + no_change: + add_pc2line_info(s, bc_out.size, line_num, col_num); + dbuf_put(&bc_out, bc_buf + pos, len); + break; + } + } + + /* check that there were no missing labels */ + for(i = 0; i < s->label_count; i++) { + assert(label_slots[i].first_reloc == NULL); + } + + /* more jump optimizations */ + patch_offsets = 0; + for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) { + LabelSlot *ls; + JumpSlot *jp1; + int j, pos, diff, delta; + + delta = 3; + switch (op = jp->op) { + case OP_goto16: + delta = 1; + /* fall thru */ + case OP_if_false: + case OP_if_true: + case OP_goto: + pos = jp->pos; + diff = s->label_slots[jp->label].addr - pos; + if (diff >= -128 && diff <= 127 + delta) { + //put_u8(bc_out.buf + pos, diff); + jp->size = 1; + if (op == OP_goto16) { + bc_out.buf[pos - 1] = jp->op = OP_goto8; + } else { + bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false); + } + goto shrink; + } else + if (diff == (int16_t)diff && op == OP_goto) { + //put_u16(bc_out.buf + pos, diff); + jp->size = 2; + delta = 2; + bc_out.buf[pos - 1] = jp->op = OP_goto16; + shrink: + /* XXX: should reduce complexity, using 2 finger copy scheme */ + memmove(bc_out.buf + pos + jp->size, bc_out.buf + pos + jp->size + delta, + bc_out.size - pos - jp->size - delta); + bc_out.size -= delta; + patch_offsets++; + for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) { + if (ls->addr > pos) + ls->addr -= delta; + } + for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) { + if (jp1->pos > pos) + jp1->pos -= delta; + } + for (j = 0; j < s->source_loc_count; j++) { + if (s->source_loc_slots[j].pc > pos) + s->source_loc_slots[j].pc -= delta; + } + continue; + } + break; + } + } + + if (patch_offsets) { + JumpSlot *jp1; + int j; + for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) { + int diff1 = s->label_slots[jp1->label].addr - jp1->pos; + switch (jp1->size) { + case 1: + put_u8(bc_out.buf + jp1->pos, diff1); + break; + case 2: + put_u16(bc_out.buf + jp1->pos, diff1); + break; + case 4: + put_u32(bc_out.buf + jp1->pos, diff1); + break; + } + } + } + + js_free(ctx, s->jump_slots); + s->jump_slots = NULL; + js_free(ctx, s->label_slots); + s->label_slots = NULL; + /* XXX: should delay until copying to runtime bytecode function */ + compute_pc2line_info(s); + js_free(ctx, s->source_loc_slots); + s->source_loc_slots = NULL; + /* set the new byte code */ + dbuf_free(&s->byte_code); + s->byte_code = bc_out; + s->use_short_opcodes = TRUE; + if (dbuf_error(&s->byte_code)) { + JS_ThrowOutOfMemory(ctx); + return -1; + } + return 0; + fail: + /* XXX: not safe */ + dbuf_free(&bc_out); + return -1; +} + +/* compute the maximum stack size needed by the function */ + +typedef struct StackSizeState { + int bc_len; + int stack_len_max; + uint16_t *stack_level_tab; + int32_t *catch_pos_tab; + int *pc_stack; + int pc_stack_len; + int pc_stack_size; +} StackSizeState; + +/* 'op' is only used for error indication */ +static __exception int ss_check(JSContext *ctx, StackSizeState *s, + int pos, int op, int stack_len, int catch_pos) +{ + if ((unsigned)pos >= s->bc_len) { + JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); + return -1; + } + if (stack_len > s->stack_len_max) { + s->stack_len_max = stack_len; + if (s->stack_len_max > JS_STACK_SIZE_MAX) { + JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos); + return -1; + } + } + if (s->stack_level_tab[pos] != 0xffff) { + /* already explored: check that the stack size is consistent */ + if (s->stack_level_tab[pos] != stack_len) { + JS_ThrowInternalError(ctx, "inconsistent stack size: %d %d (pc=%d)", + s->stack_level_tab[pos], stack_len, pos); + return -1; + } else if (s->catch_pos_tab[pos] != catch_pos) { + JS_ThrowInternalError(ctx, "inconsistent catch position: %d %d (pc=%d)", + s->catch_pos_tab[pos], catch_pos, pos); + return -1; + } else { + return 0; + } + } + + /* mark as explored and store the stack size */ + s->stack_level_tab[pos] = stack_len; + s->catch_pos_tab[pos] = catch_pos; + + /* queue the new PC to explore */ + if (js_resize_array(ctx, (void **)&s->pc_stack, sizeof(s->pc_stack[0]), + &s->pc_stack_size, s->pc_stack_len + 1)) + return -1; + s->pc_stack[s->pc_stack_len++] = pos; + return 0; +} + +static __exception int compute_stack_size(JSContext *ctx, + JSFunctionDef *fd, + int *pstack_size) +{ + StackSizeState s_s, *s = &s_s; + int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level; + const JSOpCode *oi; + const uint8_t *bc_buf; + + bc_buf = fd->byte_code.buf; + s->bc_len = fd->byte_code.size; + /* bc_len > 0 */ + s->stack_level_tab = js_malloc(ctx, sizeof(s->stack_level_tab[0]) * + s->bc_len); + if (!s->stack_level_tab) + return -1; + for(i = 0; i < s->bc_len; i++) + s->stack_level_tab[i] = 0xffff; + s->pc_stack = NULL; + s->catch_pos_tab = js_malloc(ctx, sizeof(s->catch_pos_tab[0]) * s->bc_len); + if (!s->catch_pos_tab) + goto fail; + + s->stack_len_max = 0; + s->pc_stack_len = 0; + s->pc_stack_size = 0; + + /* breadth-first graph exploration */ + if (ss_check(ctx, s, 0, OP_invalid, 0, -1)) + goto fail; + + while (s->pc_stack_len > 0) { + pos = s->pc_stack[--s->pc_stack_len]; + stack_len = s->stack_level_tab[pos]; + catch_pos = s->catch_pos_tab[pos]; + op = bc_buf[pos]; + if (op == 0 || op >= OP_COUNT) { + JS_ThrowInternalError(ctx, "invalid opcode (op=%d, pc=%d)", op, pos); + goto fail; + } + oi = &short_opcode_info(op); +#ifdef DUMP_BYTECODE_STACK + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_STACK)) + printf("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos); +#endif + pos_next = pos + oi->size; + if (pos_next > s->bc_len) { + JS_ThrowInternalError(ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); + goto fail; + } + n_pop = oi->n_pop; + /* call pops a variable number of arguments */ + if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) { + n_pop += get_u16(bc_buf + pos + 1); + } else if (oi->fmt == OP_FMT_npopx) { + n_pop += op - OP_call0; + } + + if (stack_len < n_pop) { + JS_ThrowInternalError(ctx, "stack underflow (op=%d, pc=%d)", op, pos); + goto fail; + } + stack_len += oi->n_push - n_pop; + if (stack_len > s->stack_len_max) { + s->stack_len_max = stack_len; + if (s->stack_len_max > JS_STACK_SIZE_MAX) { + JS_ThrowInternalError(ctx, "stack overflow (op=%d, pc=%d)", op, pos); + goto fail; + } + } + switch(op) { + case OP_tail_call: + case OP_tail_call_method: + case OP_return: + case OP_return_undef: + case OP_return_async: + case OP_throw: + case OP_throw_error: + case OP_ret: + goto done_insn; + case OP_goto: + diff = get_u32(bc_buf + pos + 1); + pos_next = pos + 1 + diff; + break; + case OP_goto16: + diff = (int16_t)get_u16(bc_buf + pos + 1); + pos_next = pos + 1 + diff; + break; + case OP_goto8: + diff = (int8_t)bc_buf[pos + 1]; + pos_next = pos + 1 + diff; + break; + case OP_if_true8: + case OP_if_false8: + diff = (int8_t)bc_buf[pos + 1]; + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + break; + case OP_if_true: + case OP_if_false: + diff = get_u32(bc_buf + pos + 1); + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + break; + case OP_gosub: + diff = get_u32(bc_buf + pos + 1); + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos)) + goto fail; + break; + case OP_with_get_var: + case OP_with_delete_var: + diff = get_u32(bc_buf + pos + 5); + if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 1, catch_pos)) + goto fail; + break; + case OP_with_make_ref: + case OP_with_get_ref: + case OP_with_get_ref_undef: + diff = get_u32(bc_buf + pos + 5); + if (ss_check(ctx, s, pos + 5 + diff, op, stack_len + 2, catch_pos)) + goto fail; + break; + case OP_with_put_var: + diff = get_u32(bc_buf + pos + 5); + if (ss_check(ctx, s, pos + 5 + diff, op, stack_len - 1, catch_pos)) + goto fail; + break; + case OP_catch: + diff = get_u32(bc_buf + pos + 1); + if (ss_check(ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + catch_pos = pos; + break; + case OP_for_of_start: + case OP_for_await_of_start: + catch_pos = pos; + break; + /* we assume the catch offset entry is only removed with + some op codes */ + case OP_drop: + catch_level = stack_len; + goto check_catch; + case OP_nip: + catch_level = stack_len - 1; + goto check_catch; + case OP_nip1: + catch_level = stack_len - 1; + goto check_catch; + case OP_iterator_close: + catch_level = stack_len + 2; + check_catch: + /* Note: for for_of_start/for_await_of_start we consider + the catch offset is on the first stack entry instead of + the thirst */ + if (catch_pos >= 0) { + int level; + level = s->stack_level_tab[catch_pos]; + if (bc_buf[catch_pos] != OP_catch) + level++; /* for_of_start, for_wait_of_start */ + /* catch_level = stack_level before op_catch is executed ? */ + if (catch_level == level) { + catch_pos = s->catch_pos_tab[catch_pos]; + } + } + break; + case OP_nip_catch: + if (catch_pos < 0) { + JS_ThrowInternalError(ctx, "nip_catch: no catch op (pc=%d)", pos); + goto fail; + } + stack_len = s->stack_level_tab[catch_pos]; + if (bc_buf[catch_pos] != OP_catch) + stack_len++; /* for_of_start, for_wait_of_start */ + stack_len++; /* no stack overflow is possible by construction */ + catch_pos = s->catch_pos_tab[catch_pos]; + break; + default: + break; + } + if (ss_check(ctx, s, pos_next, op, stack_len, catch_pos)) + goto fail; + done_insn: ; + } + js_free(ctx, s->pc_stack); + js_free(ctx, s->catch_pos_tab); + js_free(ctx, s->stack_level_tab); + *pstack_size = s->stack_len_max; + return 0; + fail: + js_free(ctx, s->pc_stack); + js_free(ctx, s->catch_pos_tab); + js_free(ctx, s->stack_level_tab); + *pstack_size = 0; + return -1; +} + +static int add_module_variables(JSContext *ctx, JSFunctionDef *fd) +{ + int i, idx; + JSModuleDef *m = fd->module; + JSExportEntry *me; + JSGlobalVar *hf; + + /* The imported global variables were added as closure variables + in js_parse_import(). We add here the module global + variables. */ + + for(i = 0; i < fd->global_var_count; i++) { + hf = &fd->global_vars[i]; + if (add_closure_var(ctx, fd, TRUE, FALSE, i, hf->var_name, hf->is_const, + hf->is_lexical, JS_VAR_NORMAL) < 0) + return -1; + } + + /* resolve the variable names of the local exports */ + for(i = 0; i < m->export_entries_count; i++) { + me = &m->export_entries[i]; + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + idx = find_closure_var(ctx, fd, me->local_name); + if (idx < 0) { + // XXX: add_module_variables() should take JSParseState *s and use js_parse_error_atom + JS_ThrowSyntaxErrorAtom(ctx, "exported variable '%s' does not exist", + me->local_name); + return -1; + } + me->u.local.var_idx = idx; + } + } + return 0; +} + +/* create a function object from a function definition. The function + definition is freed. All the child functions are also created. It + must be done this way to resolve all the variables. */ +static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) +{ + JSValue func_obj; + JSFunctionBytecode *b; + struct list_head *el, *el1; + int stack_size, scope, idx; + int function_size, byte_code_offset, cpool_offset; + int closure_var_offset, vardefs_offset; + + /* recompute scope linkage */ + for (scope = 0; scope < fd->scope_count; scope++) { + fd->scopes[scope].first = -1; + } + if (fd->has_parameter_expressions) { + /* special end of variable list marker for the argument scope */ + fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END; + } + for (idx = 0; idx < fd->var_count; idx++) { + JSVarDef *vd = &fd->vars[idx]; + vd->scope_next = fd->scopes[vd->scope_level].first; + fd->scopes[vd->scope_level].first = idx; + } + for (scope = 2; scope < fd->scope_count; scope++) { + JSVarScope *sd = &fd->scopes[scope]; + if (sd->first < 0) + sd->first = fd->scopes[sd->parent].first; + } + for (idx = 0; idx < fd->var_count; idx++) { + JSVarDef *vd = &fd->vars[idx]; + if (vd->scope_next < 0 && vd->scope_level > 1) { + scope = fd->scopes[vd->scope_level].parent; + vd->scope_next = fd->scopes[scope].first; + } + } + + /* if the function contains an eval call, the closure variables + are used to compile the eval and they must be ordered by scope, + so it is necessary to create the closure variables before any + other variable lookup is done. */ + if (fd->has_eval_call) + add_eval_variables(ctx, fd); + + /* add the module global variables in the closure */ + if (fd->module) { + if (add_module_variables(ctx, fd)) + goto fail; + } + + /* first create all the child functions */ + list_for_each_safe(el, el1, &fd->child_list) { + JSFunctionDef *fd1; + int cpool_idx; + + fd1 = list_entry(el, JSFunctionDef, link); + cpool_idx = fd1->parent_cpool_idx; + func_obj = js_create_function(ctx, fd1); + if (JS_IsException(func_obj)) + goto fail; + /* save it in the constant pool */ + assert(cpool_idx >= 0); + fd->cpool[cpool_idx] = func_obj; + } + +#ifdef DUMP_BYTECODE_PASS1 + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_PASS1)) { + printf("pass 1\n"); + dump_byte_code(ctx, 1, fd->byte_code.buf, fd->byte_code.size, + fd->args, fd->arg_count, fd->vars, fd->var_count, + fd->closure_var, fd->closure_var_count, + fd->cpool, fd->cpool_count, fd->source, fd->line_num, + fd->label_slots, NULL, 0); + printf("\n"); + } +#endif + + if (resolve_variables(ctx, fd)) + goto fail; + +#ifdef DUMP_BYTECODE_PASS2 + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_PASS2)) { + printf("pass 2\n"); + dump_byte_code(ctx, 2, fd->byte_code.buf, fd->byte_code.size, + fd->args, fd->arg_count, fd->vars, fd->var_count, + fd->closure_var, fd->closure_var_count, + fd->cpool, fd->cpool_count, fd->source, fd->line_num, + fd->label_slots, NULL, 0); + printf("\n"); + } +#endif + + if (resolve_labels(ctx, fd)) + goto fail; + + if (compute_stack_size(ctx, fd, &stack_size) < 0) + goto fail; + + function_size = sizeof(*b); + cpool_offset = function_size; + function_size += fd->cpool_count * sizeof(*fd->cpool); + vardefs_offset = function_size; + function_size += (fd->arg_count + fd->var_count) * sizeof(*b->vardefs); + closure_var_offset = function_size; + function_size += fd->closure_var_count * sizeof(*fd->closure_var); + byte_code_offset = function_size; + function_size += fd->byte_code.size; + + b = js_mallocz(ctx, function_size); + if (!b) + goto fail; + b->header.ref_count = 1; + + b->byte_code_buf = (void *)((uint8_t*)b + byte_code_offset); + b->byte_code_len = fd->byte_code.size; + memcpy(b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size); + js_free(ctx, fd->byte_code.buf); + fd->byte_code.buf = NULL; + + b->func_name = fd->func_name; + if (fd->arg_count + fd->var_count > 0) { + b->vardefs = (void *)((uint8_t*)b + vardefs_offset); + if (fd->arg_count > 0) + memcpy(b->vardefs, fd->args, fd->arg_count * sizeof(fd->args[0])); + if (fd->var_count > 0) + memcpy(b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof(fd->vars[0])); + b->var_count = fd->var_count; + b->arg_count = fd->arg_count; + b->defined_arg_count = fd->defined_arg_count; + js_free(ctx, fd->args); + js_free(ctx, fd->vars); + js_free(ctx, fd->vars_htab); + } + b->cpool_count = fd->cpool_count; + if (b->cpool_count) { + b->cpool = (void *)((uint8_t*)b + cpool_offset); + memcpy(b->cpool, fd->cpool, b->cpool_count * sizeof(*b->cpool)); + } + js_free(ctx, fd->cpool); + fd->cpool = NULL; + + b->stack_size = stack_size; + + /* XXX: source and pc2line info should be packed at the end of the + JSFunctionBytecode structure, avoiding allocation overhead + */ + b->filename = fd->filename; + b->line_num = fd->line_num; + b->col_num = fd->col_num; + + b->pc2line_buf = js_realloc(ctx, fd->pc2line.buf, fd->pc2line.size); + if (!b->pc2line_buf) + b->pc2line_buf = fd->pc2line.buf; + b->pc2line_len = fd->pc2line.size; + b->source = fd->source; + b->source_len = fd->source_len; + + if (fd->scopes != fd->def_scope_array) + js_free(ctx, fd->scopes); + + b->closure_var_count = fd->closure_var_count; + if (b->closure_var_count) { + b->closure_var = (void *)((uint8_t*)b + closure_var_offset); + memcpy(b->closure_var, fd->closure_var, b->closure_var_count * sizeof(*b->closure_var)); + } + js_free(ctx, fd->closure_var); + fd->closure_var = NULL; + + b->has_prototype = fd->has_prototype; + b->has_simple_parameter_list = fd->has_simple_parameter_list; + b->is_strict_mode = fd->is_strict_mode; + b->is_derived_class_constructor = fd->is_derived_class_constructor; + b->func_kind = fd->func_kind; + b->need_home_object = (fd->home_object_var_idx >= 0 || + fd->need_home_object); + b->new_target_allowed = fd->new_target_allowed; + b->super_call_allowed = fd->super_call_allowed; + b->super_allowed = fd->super_allowed; + b->arguments_allowed = fd->arguments_allowed; + b->backtrace_barrier = fd->backtrace_barrier; + b->realm = JS_DupContext(ctx); + b->ic = fd->ic; + fd->ic = NULL; + rebuild_ic(ctx, b->ic); + if (b->ic->count == 0) { + free_ic(ctx->rt, b->ic); + b->ic = NULL; + } + + add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); + +#ifdef DUMP_BYTECODE_FINAL + if (check_dump_flag(ctx->rt, DUMP_BYTECODE_FINAL)) + js_dump_function_bytecode(ctx, b); +#endif + + if (fd->parent) { + /* remove from parent list */ + list_del(&fd->link); + } + + js_free(ctx, fd); + return JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); + fail: + js_free_function_def(ctx, fd); + return JS_EXCEPTION; +} + +static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) +{ + int i; + + free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, TRUE); + + if (b->ic) + free_ic(rt, b->ic); + + if (b->vardefs) { + for(i = 0; i < b->arg_count + b->var_count; i++) { + JS_FreeAtomRT(rt, b->vardefs[i].var_name); + } + } + for(i = 0; i < b->cpool_count; i++) + JS_FreeValueRT(rt, b->cpool[i]); + + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JS_FreeAtomRT(rt, cv->var_name); + } + if (b->realm) + JS_FreeContext(b->realm); + + JS_FreeAtomRT(rt, b->func_name); + JS_FreeAtomRT(rt, b->filename); + js_free_rt(rt, b->pc2line_buf); + js_free_rt(rt, b->source); + + remove_gc_object(&b->header); + if (rt->gc_phase == JS_GC_PHASE_REMOVE_CYCLES && b->header.ref_count != 0) { + list_add_tail(&b->header.link, &rt->gc_zero_ref_count_list); + } else { + js_free_rt(rt, b); + } +} + +static __exception int js_parse_directives(JSParseState *s) +{ + char str[20]; + JSParsePos pos; + BOOL has_semi; + + if (s->token.val != TOK_STRING) + return 0; + + js_parse_get_pos(s, &pos); + + while(s->token.val == TOK_STRING) { + /* Copy actual source string representation */ + snprintf(str, sizeof str, "%.*s", + (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1); + + if (next_token(s)) + return -1; + + has_semi = FALSE; + switch (s->token.val) { + case ';': + if (next_token(s)) + return -1; + has_semi = TRUE; + break; + case '}': + case TOK_EOF: + has_semi = TRUE; + break; + case TOK_NUMBER: + case TOK_STRING: + case TOK_TEMPLATE: + case TOK_IDENT: + case TOK_REGEXP: + case TOK_DEC: + case TOK_INC: + case TOK_NULL: + case TOK_FALSE: + case TOK_TRUE: + case TOK_IF: + case TOK_RETURN: + case TOK_VAR: + case TOK_THIS: + case TOK_DELETE: + case TOK_TYPEOF: + case TOK_NEW: + case TOK_DO: + case TOK_WHILE: + case TOK_FOR: + case TOK_SWITCH: + case TOK_THROW: + case TOK_TRY: + case TOK_FUNCTION: + case TOK_DEBUGGER: + case TOK_WITH: + case TOK_CLASS: + case TOK_CONST: + case TOK_ENUM: + case TOK_EXPORT: + case TOK_IMPORT: + case TOK_SUPER: + case TOK_INTERFACE: + case TOK_LET: + case TOK_PACKAGE: + case TOK_PRIVATE: + case TOK_PROTECTED: + case TOK_PUBLIC: + case TOK_STATIC: + /* automatic insertion of ';' */ + if (s->got_lf) + has_semi = TRUE; + break; + default: + break; + } + if (!has_semi) + break; + if (!strcmp(str, "use strict")) { + s->cur_func->has_use_strict = TRUE; + s->cur_func->is_strict_mode = TRUE; + } + } + return js_parse_seek_token(s, &pos); +} + +static BOOL js_invalid_strict_name(JSAtom name) { + switch (name) { + case JS_ATOM_eval: + case JS_ATOM_arguments: + case JS_ATOM_implements: // future strict reserved words + case JS_ATOM_interface: + case JS_ATOM_let: + case JS_ATOM_package: + case JS_ATOM_private: + case JS_ATOM_protected: + case JS_ATOM_public: + case JS_ATOM_static: + case JS_ATOM_yield: + return TRUE; + default: + return FALSE; + } +} + +static int js_parse_function_check_names(JSParseState *s, JSFunctionDef *fd, + JSAtom func_name) +{ + JSAtom name; + int i, idx; + + if (fd->is_strict_mode) { + if (!fd->has_simple_parameter_list && fd->has_use_strict) { + return js_parse_error(s, "\"use strict\" not allowed in function with default or destructuring parameter"); + } + if (js_invalid_strict_name(func_name)) { + return js_parse_error(s, "invalid function name in strict code"); + } + for (idx = 0; idx < fd->arg_count; idx++) { + name = fd->args[idx].var_name; + if (js_invalid_strict_name(name)) { + return js_parse_error(s, "invalid argument name in strict code"); + } + } + } + /* check async_generator case */ + if (fd->is_strict_mode + || !fd->has_simple_parameter_list + || (fd->func_type == JS_PARSE_FUNC_METHOD && fd->func_kind == JS_FUNC_ASYNC) + || fd->func_type == JS_PARSE_FUNC_ARROW + || fd->func_type == JS_PARSE_FUNC_METHOD) { + for (idx = 0; idx < fd->arg_count; idx++) { + name = fd->args[idx].var_name; + if (name != JS_ATOM_NULL) { + for (i = 0; i < idx; i++) { + if (fd->args[i].var_name == name) + goto duplicate; + } + /* Check if argument name duplicates a destructuring parameter */ + /* XXX: should have a flag for such variables */ + for (i = 0; i < fd->var_count; i++) { + if (fd->vars[i].var_name == name && + fd->vars[i].scope_level == 0) + goto duplicate; + } + } + } + } + return 0; + +duplicate: + return js_parse_error(s, "Duplicate parameter name not allowed in this context"); +} + +/* create a function to initialize class fields */ +static JSFunctionDef *js_parse_function_class_fields_init(JSParseState *s) +{ + JSFunctionDef *fd; + + fd = js_new_function_def(s->ctx, s->cur_func, FALSE, FALSE, + s->filename, 0, 0); + if (!fd) + return NULL; + fd->func_name = JS_ATOM_NULL; + fd->has_prototype = FALSE; + fd->has_home_object = TRUE; + + fd->has_arguments_binding = FALSE; + fd->has_this_binding = TRUE; + fd->is_derived_class_constructor = FALSE; + fd->new_target_allowed = TRUE; + fd->super_call_allowed = FALSE; + fd->super_allowed = fd->has_home_object; + fd->arguments_allowed = FALSE; + + fd->func_kind = JS_FUNC_NORMAL; + fd->func_type = JS_PARSE_FUNC_METHOD; + return fd; +} + +/* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and + JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */ +static __exception int js_parse_function_decl2(JSParseState *s, + JSParseFunctionEnum func_type, + JSFunctionKindEnum func_kind, + JSAtom func_name, + const uint8_t *ptr, + int function_line_num, + int function_col_num, + JSParseExportEnum export_flag, + JSFunctionDef **pfd) +{ + JSContext *ctx = s->ctx; + JSFunctionDef *fd = s->cur_func; + BOOL is_expr; + int func_idx, lexical_func_idx = -1; + BOOL has_opt_arg; + BOOL create_func_var = FALSE; + + is_expr = (func_type != JS_PARSE_FUNC_STATEMENT && + func_type != JS_PARSE_FUNC_VAR); + + if (func_type == JS_PARSE_FUNC_STATEMENT || + func_type == JS_PARSE_FUNC_VAR || + func_type == JS_PARSE_FUNC_EXPR) { + if (func_kind == JS_FUNC_NORMAL && + token_is_pseudo_keyword(s, JS_ATOM_async) && + peek_token(s, TRUE) != '\n') { + if (next_token(s)) + return -1; + func_kind = JS_FUNC_ASYNC; + } + if (next_token(s)) + return -1; + if (s->token.val == '*') { + if (next_token(s)) + return -1; + func_kind |= JS_FUNC_GENERATOR; + } + + if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved || + (s->token.u.ident.atom == JS_ATOM_yield && + func_type == JS_PARSE_FUNC_EXPR && + (func_kind & JS_FUNC_GENERATOR)) || + (s->token.u.ident.atom == JS_ATOM_await && + ((func_type == JS_PARSE_FUNC_EXPR && + (func_kind & JS_FUNC_ASYNC)) || + func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT))) { + return js_parse_error_reserved_identifier(s); + } + } + if (s->token.val == TOK_IDENT || + (((s->token.val == TOK_YIELD && !fd->is_strict_mode) || + (s->token.val == TOK_AWAIT && !s->is_module)) && + func_type == JS_PARSE_FUNC_EXPR)) { + func_name = JS_DupAtom(ctx, s->token.u.ident.atom); + if (next_token(s)) { + JS_FreeAtom(ctx, func_name); + return -1; + } + } else { + if (func_type != JS_PARSE_FUNC_EXPR && + export_flag != JS_PARSE_EXPORT_DEFAULT) { + return js_parse_error(s, "function name expected"); + } + } + } else if (func_type != JS_PARSE_FUNC_ARROW) { + func_name = JS_DupAtom(ctx, func_name); + } + + if (fd->is_eval && fd->eval_type == JS_EVAL_TYPE_MODULE && + (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR)) { + JSGlobalVar *hf; + hf = find_global_var(fd, func_name); + /* XXX: should check scope chain */ + if (hf && hf->scope_level == fd->scope_level) { + js_parse_error(s, "invalid redefinition of global identifier in module code"); + JS_FreeAtom(ctx, func_name); + return -1; + } + } + + if (func_type == JS_PARSE_FUNC_VAR) { + if (!fd->is_strict_mode + && func_kind == JS_FUNC_NORMAL + && find_lexical_decl(ctx, fd, func_name, fd->scope_first, FALSE) < 0 + && !((func_idx = find_var(ctx, fd, func_name)) >= 0 && (func_idx & ARGUMENT_VAR_OFFSET)) + && !(func_name == JS_ATOM_arguments && fd->has_arguments_binding)) { + create_func_var = TRUE; + } + /* Create the lexical name here so that the function closure + contains it */ + if (fd->is_eval && + (fd->eval_type == JS_EVAL_TYPE_GLOBAL || + fd->eval_type == JS_EVAL_TYPE_MODULE) && + fd->scope_level == fd->body_scope) { + /* avoid creating a lexical variable in the global + scope. XXX: check annex B */ + JSGlobalVar *hf; + hf = find_global_var(fd, func_name); + /* XXX: should check scope chain */ + if (hf && hf->scope_level == fd->scope_level) { + js_parse_error(s, "invalid redefinition of global identifier"); + JS_FreeAtom(ctx, func_name); + return -1; + } + } else { + /* Always create a lexical name, fail if at the same scope as + existing name */ + /* Lexical variable will be initialized upon entering scope */ + lexical_func_idx = define_var(s, fd, func_name, + func_kind != JS_FUNC_NORMAL ? + JS_VAR_DEF_NEW_FUNCTION_DECL : + JS_VAR_DEF_FUNCTION_DECL); + if (lexical_func_idx < 0) { + JS_FreeAtom(ctx, func_name); + return -1; + } + } + } + + fd = js_new_function_def(ctx, fd, FALSE, is_expr, s->filename, + function_line_num, function_col_num); + if (!fd) { + JS_FreeAtom(ctx, func_name); + return -1; + } + if (pfd) + *pfd = fd; + s->cur_func = fd; + fd->func_name = func_name; + /* XXX: test !fd->is_generator is always false */ + fd->has_prototype = (func_type == JS_PARSE_FUNC_STATEMENT || + func_type == JS_PARSE_FUNC_VAR || + func_type == JS_PARSE_FUNC_EXPR) && + func_kind == JS_FUNC_NORMAL; + fd->has_home_object = (func_type == JS_PARSE_FUNC_METHOD || + func_type == JS_PARSE_FUNC_GETTER || + func_type == JS_PARSE_FUNC_SETTER || + func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR || + func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); + fd->has_arguments_binding = (func_type != JS_PARSE_FUNC_ARROW && + func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT); + fd->has_this_binding = fd->has_arguments_binding; + fd->is_derived_class_constructor = (func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR); + if (func_type == JS_PARSE_FUNC_ARROW) { + fd->new_target_allowed = fd->parent->new_target_allowed; + fd->super_call_allowed = fd->parent->super_call_allowed; + fd->super_allowed = fd->parent->super_allowed; + fd->arguments_allowed = fd->parent->arguments_allowed; + } else if (func_type == JS_PARSE_FUNC_CLASS_STATIC_INIT) { + fd->new_target_allowed = TRUE; // although new.target === undefined + fd->super_call_allowed = FALSE; + fd->super_allowed = TRUE; + fd->arguments_allowed = FALSE; + } else { + fd->new_target_allowed = TRUE; + fd->super_call_allowed = fd->is_derived_class_constructor; + fd->super_allowed = fd->has_home_object; + fd->arguments_allowed = TRUE; + } + + /* fd->in_function_body == FALSE prevents yield/await during the parsing + of the arguments in generator/async functions. They are parsed as + regular identifiers for other function kinds. */ + fd->func_kind = func_kind; + fd->func_type = func_type; + + if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR || + func_type == JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) { + /* error if not invoked as a constructor */ + emit_op(s, OP_check_ctor); + } + + if (func_type == JS_PARSE_FUNC_CLASS_CONSTRUCTOR) { + emit_class_field_init(s); + } + + /* parse arguments */ + fd->has_simple_parameter_list = TRUE; + fd->has_parameter_expressions = FALSE; + has_opt_arg = FALSE; + if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { + JSAtom name; + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier(s); + goto fail; + } + name = s->token.u.ident.atom; + if (add_arg(ctx, fd, name) < 0) + goto fail; + fd->defined_arg_count = 1; + } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { + if (s->token.val == '(') { + int skip_bits; + /* if there is an '=' inside the parameter list, we + consider there is a parameter expression inside */ + js_parse_skip_parens_token(s, &skip_bits, FALSE); + if (skip_bits & SKIP_HAS_ASSIGNMENT) + fd->has_parameter_expressions = TRUE; + if (next_token(s)) + goto fail; + } else { + if (js_parse_expect(s, '(')) + goto fail; + } + + if (fd->has_parameter_expressions) { + fd->scope_level = -1; /* force no parent scope */ + if (push_scope(s) < 0) + return -1; + } + + while (s->token.val != ')') { + JSAtom name; + BOOL rest = FALSE; + int idx, has_initializer; + + if (s->token.val == TOK_ELLIPSIS) { + fd->has_simple_parameter_list = FALSE; + rest = TRUE; + if (next_token(s)) + goto fail; + } + if (s->token.val == '[' || s->token.val == '{') { + fd->has_simple_parameter_list = FALSE; + if (rest) { + emit_op(s, OP_rest); + emit_u16(s, fd->arg_count); + } else { + /* unnamed arg for destructuring */ + idx = add_arg(ctx, fd, JS_ATOM_NULL); + emit_op(s, OP_get_arg); + emit_u16(s, idx); + } + has_initializer = js_parse_destructuring_element(s, fd->has_parameter_expressions ? TOK_LET : TOK_VAR, 1, TRUE, -1, TRUE); + if (has_initializer < 0) + goto fail; + if (has_initializer) + has_opt_arg = TRUE; + if (!has_opt_arg) + fd->defined_arg_count++; + } else if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier(s); + goto fail; + } + name = s->token.u.ident.atom; + if (name == JS_ATOM_yield && fd->func_kind == JS_FUNC_GENERATOR) { + js_parse_error_reserved_identifier(s); + goto fail; + } + if (fd->has_parameter_expressions) { + if (js_parse_check_duplicate_parameter(s, name)) + goto fail; + if (define_var(s, fd, name, JS_VAR_DEF_LET) < 0) + goto fail; + } + /* XXX: could avoid allocating an argument if rest is true */ + idx = add_arg(ctx, fd, name); + if (idx < 0) + goto fail; + if (next_token(s)) + goto fail; + if (rest) { + emit_op(s, OP_rest); + emit_u16(s, idx); + if (fd->has_parameter_expressions) { + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, name); + emit_u16(s, fd->scope_level); + } + emit_op(s, OP_put_arg); + emit_u16(s, idx); + fd->has_simple_parameter_list = FALSE; + has_opt_arg = TRUE; + } else if (s->token.val == '=') { + int label; + + fd->has_simple_parameter_list = FALSE; + has_opt_arg = TRUE; + + if (next_token(s)) + goto fail; + + label = new_label(s); + emit_op(s, OP_get_arg); + emit_u16(s, idx); + emit_op(s, OP_dup); + emit_op(s, OP_undefined); + emit_op(s, OP_strict_eq); + emit_goto(s, OP_if_false, label); + emit_op(s, OP_drop); + if (js_parse_assign_expr(s)) + goto fail; + set_object_name(s, name); + emit_op(s, OP_dup); + emit_op(s, OP_put_arg); + emit_u16(s, idx); + emit_label(s, label); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, name); + emit_u16(s, fd->scope_level); + } else { + if (!has_opt_arg) { + fd->defined_arg_count++; + } + if (fd->has_parameter_expressions) { + /* copy the argument to the argument scope */ + emit_op(s, OP_get_arg); + emit_u16(s, idx); + emit_op(s, OP_scope_put_var_init); + emit_atom(s, name); + emit_u16(s, fd->scope_level); + } + } + } else { + js_parse_error(s, "missing formal parameter"); + goto fail; + } + if (rest && s->token.val != ')') { + js_parse_expect(s, ')'); + goto fail; + } + if (s->token.val == ')') + break; + if (js_parse_expect(s, ',')) + goto fail; + } + if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) || + (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) { + js_parse_error(s, "invalid number of arguments for getter or setter"); + goto fail; + } + } + + if (fd->has_parameter_expressions) { + int idx; + + /* Copy the variables in the argument scope to the variable + scope (see FunctionDeclarationInstantiation() in spec). The + normal arguments are already present, so no need to copy + them. */ + idx = fd->scopes[fd->scope_level].first; + while (idx >= 0) { + JSVarDef *vd = &fd->vars[idx]; + if (vd->scope_level != fd->scope_level) + break; + if (find_var(ctx, fd, vd->var_name) < 0) { + if (add_var(ctx, fd, vd->var_name) < 0) + goto fail; + vd = &fd->vars[idx]; /* fd->vars may have been reallocated */ + emit_op(s, OP_scope_get_var); + emit_atom(s, vd->var_name); + emit_u16(s, fd->scope_level); + emit_op(s, OP_scope_put_var); + emit_atom(s, vd->var_name); + emit_u16(s, 0); + } + idx = vd->scope_next; + } + + /* the argument scope has no parent, hence we don't use pop_scope(s) */ + emit_op(s, OP_leave_scope); + emit_u16(s, fd->scope_level); + + /* set the variable scope as the current scope */ + fd->scope_level = 0; + fd->scope_first = fd->scopes[fd->scope_level].first; + } + + if (next_token(s)) + goto fail; + + /* generator function: yield after the parameters are evaluated */ + if (func_kind == JS_FUNC_GENERATOR || + func_kind == JS_FUNC_ASYNC_GENERATOR) + emit_op(s, OP_initial_yield); + + /* in generators, yield expression is forbidden during the parsing + of the arguments */ + fd->in_function_body = TRUE; + push_scope(s); /* enter body scope */ + fd->body_scope = fd->scope_level; + + if (s->token.val == TOK_ARROW) { + if (next_token(s)) + goto fail; + + if (s->token.val != '{') { + if (js_parse_function_check_names(s, fd, func_name)) + goto fail; + + if (js_parse_assign_expr(s)) + goto fail; + + if (func_kind != JS_FUNC_NORMAL) + emit_op(s, OP_return_async); + else + emit_op(s, OP_return); + + /* save the function source code */ + /* the end of the function source code is after the last + token of the function source stored into s->last_ptr */ + fd->source_len = s->last_ptr - ptr; + fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); + if (!fd->source) + goto fail; + + goto done; + } + } + + // js_parse_class() already consumed the '{' + if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) + if (js_parse_expect(s, '{')) + goto fail; + + if (js_parse_directives(s)) + goto fail; + + /* in strict_mode, check function and argument names */ + if (js_parse_function_check_names(s, fd, func_name)) + goto fail; + + while (s->token.val != '}') { + if (js_parse_source_element(s)) + goto fail; + } + + /* save the function source code */ + fd->source_len = s->buf_ptr - ptr; + fd->source = js_strndup(ctx, (const char *)ptr, fd->source_len); + if (!fd->source) + goto fail; + + if (next_token(s)) { + /* consume the '}' */ + goto fail; + } + + /* in case there is no return, add one */ + if (js_is_live_code(s)) { + emit_return(s, FALSE); + } +done: + s->cur_func = fd->parent; + + /* Reparse identifiers after the function is terminated so that + the token is parsed in the englobing function. It could be done + by just using next_token() here for normal functions, but it is + necessary for arrow functions with an expression body. */ + reparse_ident_token(s); + + /* create the function object */ + { + int idx; + JSAtom func_name = fd->func_name; + + /* the real object will be set at the end of the compilation */ + idx = cpool_add(s, JS_NULL); + fd->parent_cpool_idx = idx; + + if (is_expr) { + /* for constructors, no code needs to be generated here */ + if (func_type != JS_PARSE_FUNC_CLASS_CONSTRUCTOR && + func_type != JS_PARSE_FUNC_DERIVED_CLASS_CONSTRUCTOR) { + /* OP_fclosure creates the function object from the bytecode + and adds the scope information */ + emit_op(s, OP_fclosure); + emit_u32(s, idx); + if (func_name == JS_ATOM_NULL) { + emit_op(s, OP_set_name); + emit_u32(s, JS_ATOM_NULL); + } + } + } else if (func_type == JS_PARSE_FUNC_VAR) { + emit_op(s, OP_fclosure); + emit_u32(s, idx); + if (create_func_var) { + if (s->cur_func->is_global_var) { + JSGlobalVar *hf; + /* the global variable must be defined at the start of the + function */ + hf = add_global_var(ctx, s->cur_func, func_name); + if (!hf) + goto fail; + /* it is considered as defined at the top level + (needed for annex B.3.3.4 and B.3.3.5 + checks) */ + hf->scope_level = 0; + hf->force_init = s->cur_func->is_strict_mode; + /* store directly into global var, bypass lexical scope */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var); + emit_atom(s, func_name); + emit_u16(s, 0); + } else { + /* do not call define_var to bypass lexical scope check */ + func_idx = find_var(ctx, s->cur_func, func_name); + if (func_idx < 0) { + func_idx = add_var(ctx, s->cur_func, func_name); + if (func_idx < 0) + goto fail; + } + /* store directly into local var, bypass lexical catch scope */ + emit_op(s, OP_dup); + emit_op(s, OP_scope_put_var); + emit_atom(s, func_name); + emit_u16(s, 0); + } + } + if (lexical_func_idx >= 0) { + /* lexical variable will be initialized upon entering scope */ + s->cur_func->vars[lexical_func_idx].func_pool_idx = idx; + emit_op(s, OP_drop); + } else { + /* store function object into its lexical name */ + /* XXX: could use OP_put_loc directly */ + emit_op(s, OP_scope_put_var_init); + emit_atom(s, func_name); + emit_u16(s, s->cur_func->scope_level); + } + } else { + if (!s->cur_func->is_global_var) { + int var_idx = define_var(s, s->cur_func, func_name, JS_VAR_DEF_VAR); + + if (var_idx < 0) + goto fail; + /* the variable will be assigned at the top of the function */ + if (var_idx & ARGUMENT_VAR_OFFSET) { + s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx; + } else { + s->cur_func->vars[var_idx].func_pool_idx = idx; + } + } else { + JSAtom func_var_name; + JSGlobalVar *hf; + if (func_name == JS_ATOM_NULL) + func_var_name = JS_ATOM__default_; /* export default */ + else + func_var_name = func_name; + /* the variable will be assigned at the top of the function */ + hf = add_global_var(ctx, s->cur_func, func_var_name); + if (!hf) + goto fail; + hf->cpool_idx = idx; + if (export_flag != JS_PARSE_EXPORT_NONE) { + if (!add_export_entry(s, s->cur_func->module, func_var_name, + export_flag == JS_PARSE_EXPORT_NAMED ? func_var_name : JS_ATOM_default, JS_EXPORT_TYPE_LOCAL)) + goto fail; + } + } + } + } + return 0; + fail: + s->cur_func = fd->parent; + js_free_function_def(ctx, fd); + if (pfd) + *pfd = NULL; + return -1; +} + +static __exception int js_parse_function_decl(JSParseState *s, + JSParseFunctionEnum func_type, + JSFunctionKindEnum func_kind, + JSAtom func_name, + const uint8_t *ptr, + int start_line, + int start_col) +{ + return js_parse_function_decl2(s, func_type, func_kind, func_name, ptr, + start_line, start_col, + JS_PARSE_EXPORT_NONE, NULL); +} + +static __exception int js_parse_program(JSParseState *s) +{ + JSFunctionDef *fd = s->cur_func; + int idx; + + if (next_token(s)) + return -1; + + if (js_parse_directives(s)) + return -1; + + fd->is_global_var = (fd->eval_type == JS_EVAL_TYPE_GLOBAL) || + (fd->eval_type == JS_EVAL_TYPE_MODULE) || + !fd->is_strict_mode; + + if (!s->is_module) { + /* hidden variable for the return value */ + fd->eval_ret_idx = idx = add_var(s->ctx, fd, JS_ATOM__ret_); + if (idx < 0) + return -1; + } + + while (s->token.val != TOK_EOF) { + if (js_parse_source_element(s)) + return -1; + } + + if (!s->is_module) { + /* return the value of the hidden variable eval_ret_idx */ + if (fd->func_kind == JS_FUNC_ASYNC) { + /* wrap the return value in an object so that promises can + be safely returned */ + emit_op(s, OP_object); + emit_op(s, OP_dup); + + emit_op(s, OP_get_loc); + emit_u16(s, fd->eval_ret_idx); + + emit_op(s, OP_put_field); + emit_atom(s, JS_ATOM_value); + } else { + emit_op(s, OP_get_loc); + emit_u16(s, fd->eval_ret_idx); + } + emit_return(s, TRUE); + } else { + emit_return(s, FALSE); + } + + return 0; +} + +static void js_parse_init(JSContext *ctx, JSParseState *s, + const char *input, size_t input_len, + const char *filename) +{ + memset(s, 0, sizeof(*s)); + s->ctx = ctx; + s->filename = filename; + s->line_num = 1; + s->col_num = 1; + s->buf_start = s->buf_ptr = (const uint8_t *)input; + s->buf_end = s->buf_ptr + input_len; + s->mark = s->buf_ptr + min_int(1, input_len); + s->eol = s->buf_ptr; + s->token.val = ' '; + s->token.line_num = 1; + s->token.col_num = 1; +} + +static JSValue JS_EvalFunctionInternal(JSContext *ctx, JSValue fun_obj, + JSValue this_obj, + JSVarRef **var_refs, JSStackFrame *sf) +{ + JSValue ret_val; + uint32_t tag; + + tag = JS_VALUE_GET_TAG(fun_obj); + if (tag == JS_TAG_FUNCTION_BYTECODE) { + fun_obj = js_closure(ctx, fun_obj, var_refs, sf); + ret_val = JS_CallFree(ctx, fun_obj, this_obj, 0, NULL); + } else if (tag == JS_TAG_MODULE) { + JSModuleDef *m; + m = JS_VALUE_GET_PTR(fun_obj); + /* the module refcount should be >= 2 */ + JS_FreeValue(ctx, fun_obj); + if (js_create_module_function(ctx, m) < 0) + goto fail; + if (js_link_module(ctx, m) < 0) + goto fail; + ret_val = js_evaluate_module(ctx, m); + if (JS_IsException(ret_val)) { + fail: + return JS_EXCEPTION; + } + } else { + JS_FreeValue(ctx, fun_obj); + ret_val = JS_ThrowTypeError(ctx, "bytecode function expected"); + } + return ret_val; +} + +JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj) +{ + return JS_EvalFunctionInternal(ctx, fun_obj, ctx->global_obj, NULL, NULL); +} + +/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ +/* `export_name` and `input` may be pure ASCII or UTF-8 encoded */ +static JSValue __JS_EvalInternal(JSContext *ctx, JSValue this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx) +{ + JSParseState s1, *s = &s1; + int err, eval_type; + JSValue fun_obj, ret_val; + JSStackFrame *sf; + JSVarRef **var_refs; + JSFunctionBytecode *b; + JSFunctionDef *fd; + JSModuleDef *m; + BOOL is_strict_mode; + + js_parse_init(ctx, s, input, input_len, filename); + skip_shebang(&s->buf_ptr, s->buf_end); + + eval_type = flags & JS_EVAL_TYPE_MASK; + m = NULL; + if (eval_type == JS_EVAL_TYPE_DIRECT) { + JSObject *p; + sf = ctx->rt->current_stack_frame; + assert(sf != NULL); + assert(JS_VALUE_GET_TAG(sf->cur_func) == JS_TAG_OBJECT); + p = JS_VALUE_GET_OBJ(sf->cur_func); + assert(js_class_has_bytecode(p->class_id)); + b = p->u.func.function_bytecode; + var_refs = p->u.func.var_refs; + is_strict_mode = b->is_strict_mode; + } else { + sf = NULL; + b = NULL; + var_refs = NULL; + is_strict_mode = (flags & JS_EVAL_FLAG_STRICT) != 0; + if (eval_type == JS_EVAL_TYPE_MODULE) { + JSAtom module_name = JS_NewAtom(ctx, filename); + if (module_name == JS_ATOM_NULL) + return JS_EXCEPTION; + m = js_new_module_def(ctx, module_name); + if (!m) + return JS_EXCEPTION; + is_strict_mode = TRUE; + } + } + fd = js_new_function_def(ctx, NULL, TRUE, FALSE, filename, 1, 1); + if (!fd) + goto fail1; + s->cur_func = fd; + fd->eval_type = eval_type; + fd->has_this_binding = (eval_type != JS_EVAL_TYPE_DIRECT); + fd->backtrace_barrier = ((flags & JS_EVAL_FLAG_BACKTRACE_BARRIER) != 0); + if (eval_type == JS_EVAL_TYPE_DIRECT) { + fd->new_target_allowed = b->new_target_allowed; + fd->super_call_allowed = b->super_call_allowed; + fd->super_allowed = b->super_allowed; + fd->arguments_allowed = b->arguments_allowed; + } else { + fd->new_target_allowed = FALSE; + fd->super_call_allowed = FALSE; + fd->super_allowed = FALSE; + fd->arguments_allowed = TRUE; + } + fd->is_strict_mode = is_strict_mode; + fd->func_name = JS_DupAtom(ctx, JS_ATOM__eval_); + if (b) { + if (add_closure_variables(ctx, fd, b, scope_idx)) + goto fail; + } + fd->module = m; + if (m != NULL || (flags & JS_EVAL_FLAG_ASYNC)) { + fd->in_function_body = TRUE; + fd->func_kind = JS_FUNC_ASYNC; + } + s->is_module = (m != NULL); + s->allow_html_comments = !s->is_module; + + push_scope(s); /* body scope */ + fd->body_scope = fd->scope_level; + + err = js_parse_program(s); + if (err) { + fail: + free_token(s, &s->token); + js_free_function_def(ctx, fd); + goto fail1; + } + + if (m != NULL) + m->has_tla = fd->has_await; + + /* create the function object and all the enclosed functions */ + fun_obj = js_create_function(ctx, fd); + if (JS_IsException(fun_obj)) + goto fail1; + /* Could add a flag to avoid resolution if necessary */ + if (m) { + m->func_obj = fun_obj; + if (js_resolve_module(ctx, m) < 0) + goto fail1; + fun_obj = JS_NewModuleValue(ctx, m); + } + if (flags & JS_EVAL_FLAG_COMPILE_ONLY) { + ret_val = fun_obj; + } else { + ret_val = JS_EvalFunctionInternal(ctx, fun_obj, this_obj, var_refs, sf); + } + return ret_val; + fail1: + /* XXX: should free all the unresolved dependencies */ + if (m) + js_free_module_def(ctx, m); + return JS_EXCEPTION; +} + +/* the indirection is needed to make 'eval' optional */ +static JSValue JS_EvalInternal(JSContext *ctx, JSValue this_obj, + const char *input, size_t input_len, + const char *filename, int flags, int scope_idx) +{ + if (unlikely(!ctx->eval_internal)) { + return JS_ThrowTypeError(ctx, "eval is not supported"); + } + return ctx->eval_internal(ctx, this_obj, input, input_len, filename, + flags, scope_idx); +} + +static JSValue JS_EvalObject(JSContext *ctx, JSValue this_obj, + JSValue val, int flags, int scope_idx) +{ + JSValue ret; + const char *str; + size_t len; + + if (!JS_IsString(val)) + return js_dup(val); + str = JS_ToCStringLen(ctx, &len, val); + if (!str) + return JS_EXCEPTION; + ret = JS_EvalInternal(ctx, this_obj, str, len, "<input>", flags, scope_idx); + JS_FreeCString(ctx, str); + return ret; + +} + +JSValue JS_EvalThis(JSContext *ctx, JSValue this_obj, + const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + JSValue ret; + + assert((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_GLOBAL || + (eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE); + ret = JS_EvalInternal(ctx, this_obj, input, input_len, filename, + eval_flags, -1); + return ret; +} + +JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags) +{ + return JS_EvalThis(ctx, ctx->global_obj, input, input_len, filename, + eval_flags); +} + +int JS_ResolveModule(JSContext *ctx, JSValue obj) +{ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { + JSModuleDef *m = JS_VALUE_GET_PTR(obj); + if (js_resolve_module(ctx, m) < 0) { + js_free_modules(ctx, JS_FREE_MODULE_NOT_RESOLVED); + return -1; + } + } + return 0; +} + +/*******************************************************************/ +/* object list */ + +typedef struct { + JSObject *obj; + uint32_t hash_next; /* -1 if no next entry */ +} JSObjectListEntry; + +/* XXX: reuse it to optimize weak references */ +typedef struct { + JSObjectListEntry *object_tab; + int object_count; + int object_size; + uint32_t *hash_table; + uint32_t hash_size; +} JSObjectList; + +static void js_object_list_init(JSObjectList *s) +{ + memset(s, 0, sizeof(*s)); +} + +static uint32_t js_object_list_get_hash(JSObject *p, uint32_t hash_size) +{ + return ((uintptr_t)p * 3163) & (hash_size - 1); +} + +static int js_object_list_resize_hash(JSContext *ctx, JSObjectList *s, + uint32_t new_hash_size) +{ + JSObjectListEntry *e; + uint32_t i, h, *new_hash_table; + + new_hash_table = js_malloc(ctx, sizeof(new_hash_table[0]) * new_hash_size); + if (!new_hash_table) + return -1; + js_free(ctx, s->hash_table); + s->hash_table = new_hash_table; + s->hash_size = new_hash_size; + + for(i = 0; i < s->hash_size; i++) { + s->hash_table[i] = -1; + } + for(i = 0; i < s->object_count; i++) { + e = &s->object_tab[i]; + h = js_object_list_get_hash(e->obj, s->hash_size); + e->hash_next = s->hash_table[h]; + s->hash_table[h] = i; + } + return 0; +} + +/* the reference count of 'obj' is not modified. Return 0 if OK, -1 if + memory error */ +static int js_object_list_add(JSContext *ctx, JSObjectList *s, JSObject *obj) +{ + JSObjectListEntry *e; + uint32_t h, new_hash_size; + + if (js_resize_array(ctx, (void *)&s->object_tab, + sizeof(s->object_tab[0]), + &s->object_size, s->object_count + 1)) + return -1; + if (unlikely((s->object_count + 1) >= s->hash_size)) { + new_hash_size = max_uint32(s->hash_size, 4); + while (new_hash_size <= s->object_count) + new_hash_size *= 2; + if (js_object_list_resize_hash(ctx, s, new_hash_size)) + return -1; + } + e = &s->object_tab[s->object_count++]; + h = js_object_list_get_hash(obj, s->hash_size); + e->obj = obj; + e->hash_next = s->hash_table[h]; + s->hash_table[h] = s->object_count - 1; + return 0; +} + +/* return -1 if not present or the object index */ +static int js_object_list_find(JSContext *ctx, JSObjectList *s, JSObject *obj) +{ + JSObjectListEntry *e; + uint32_t h, p; + + /* must test empty size because there is no hash table */ + if (s->object_count == 0) + return -1; + h = js_object_list_get_hash(obj, s->hash_size); + p = s->hash_table[h]; + while (p != -1) { + e = &s->object_tab[p]; + if (e->obj == obj) + return p; + p = e->hash_next; + } + return -1; +} + +static void js_object_list_end(JSContext *ctx, JSObjectList *s) +{ + js_free(ctx, s->object_tab); + js_free(ctx, s->hash_table); +} + +/*******************************************************************/ +/* binary object writer & reader */ + +typedef enum BCTagEnum { + BC_TAG_NULL = 1, + BC_TAG_UNDEFINED, + BC_TAG_BOOL_FALSE, + BC_TAG_BOOL_TRUE, + BC_TAG_INT32, + BC_TAG_FLOAT64, + BC_TAG_STRING, + BC_TAG_OBJECT, + BC_TAG_ARRAY, + BC_TAG_BIG_INT, + BC_TAG_TEMPLATE_OBJECT, + BC_TAG_FUNCTION_BYTECODE, + BC_TAG_MODULE, + BC_TAG_TYPED_ARRAY, + BC_TAG_ARRAY_BUFFER, + BC_TAG_SHARED_ARRAY_BUFFER, + BC_TAG_REGEXP, + BC_TAG_DATE, + BC_TAG_OBJECT_VALUE, + BC_TAG_OBJECT_REFERENCE, + BC_TAG_MAP, + BC_TAG_SET, + BC_TAG_SYMBOL, +} BCTagEnum; + +#define BC_VERSION 19 + +typedef struct BCWriterState { + JSContext *ctx; + DynBuf dbuf; + BOOL allow_bytecode : 8; + BOOL allow_sab : 8; + BOOL allow_reference : 8; + BOOL allow_source : 1; + BOOL allow_debug : 1; + uint32_t first_atom; + uint32_t *atom_to_idx; + int atom_to_idx_size; + JSAtom *idx_to_atom; + int idx_to_atom_count; + int idx_to_atom_size; + uint8_t **sab_tab; + int sab_tab_len; + int sab_tab_size; + /* list of referenced objects (used if allow_reference = TRUE) */ + JSObjectList object_list; +} BCWriterState; + +#ifdef DUMP_READ_OBJECT +static const char * const bc_tag_str[] = { + "invalid", + "null", + "undefined", + "false", + "true", + "int32", + "float64", + "string", + "object", + "array", + "BigInt", + "template", + "function", + "module", + "TypedArray", + "ArrayBuffer", + "SharedArrayBuffer", + "RegExp", + "Date", + "ObjectValue", + "ObjectReference", + "Map", + "Set", + "Symbol", +}; +#endif + +static void bc_put_u8(BCWriterState *s, uint8_t v) +{ + dbuf_putc(&s->dbuf, v); +} + +static void bc_put_u16(BCWriterState *s, uint16_t v) +{ + if (is_be()) + v = bswap16(v); + dbuf_put_u16(&s->dbuf, v); +} + +static __maybe_unused void bc_put_u32(BCWriterState *s, uint32_t v) +{ + if (is_be()) + v = bswap32(v); + dbuf_put_u32(&s->dbuf, v); +} + +static void bc_put_u64(BCWriterState *s, uint64_t v) +{ + if (is_be()) + v = bswap64(v); + dbuf_put(&s->dbuf, (uint8_t *)&v, sizeof(v)); +} + +static void bc_put_leb128(BCWriterState *s, uint32_t v) +{ + dbuf_put_leb128(&s->dbuf, v); +} + +static void bc_put_sleb128(BCWriterState *s, int32_t v) +{ + dbuf_put_sleb128(&s->dbuf, v); +} + +static void bc_set_flags(uint32_t *pflags, int *pidx, uint32_t val, int n) +{ + *pflags = *pflags | (val << *pidx); + *pidx += n; +} + +static int bc_atom_to_idx(BCWriterState *s, uint32_t *pres, JSAtom atom) +{ + uint32_t v; + + if (atom < s->first_atom || __JS_AtomIsTaggedInt(atom)) { + *pres = atom; + return 0; + } + atom -= s->first_atom; + if (atom < s->atom_to_idx_size && s->atom_to_idx[atom] != 0) { + *pres = s->atom_to_idx[atom]; + return 0; + } + if (atom >= s->atom_to_idx_size) { + int old_size, i; + old_size = s->atom_to_idx_size; + if (js_resize_array(s->ctx, (void **)&s->atom_to_idx, + sizeof(s->atom_to_idx[0]), &s->atom_to_idx_size, + atom + 1)) + return -1; + /* XXX: could add a specific js_resize_array() function to do it */ + for(i = old_size; i < s->atom_to_idx_size; i++) + s->atom_to_idx[i] = 0; + } + if (js_resize_array(s->ctx, (void **)&s->idx_to_atom, + sizeof(s->idx_to_atom[0]), + &s->idx_to_atom_size, s->idx_to_atom_count + 1)) + goto fail; + + v = s->idx_to_atom_count++; + s->idx_to_atom[v] = atom + s->first_atom; + v += s->first_atom; + s->atom_to_idx[atom] = v; + *pres = v; + return 0; + fail: + *pres = 0; + return -1; +} + +static int bc_put_atom(BCWriterState *s, JSAtom atom) +{ + uint32_t v; + + if (__JS_AtomIsTaggedInt(atom)) { + v = (__JS_AtomToUInt32(atom) << 1) | 1; + } else { + if (bc_atom_to_idx(s, &v, atom)) + return -1; + v <<= 1; + } + bc_put_leb128(s, v); + return 0; +} + +static void bc_byte_swap(uint8_t *bc_buf, int bc_len) +{ + int pos, len, op, fmt; + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + len = short_opcode_info(op).size; + fmt = short_opcode_info(op).fmt; + switch(fmt) { + case OP_FMT_u16: + case OP_FMT_i16: + case OP_FMT_label16: + case OP_FMT_npop: + case OP_FMT_loc: + case OP_FMT_arg: + case OP_FMT_var_ref: + put_u16(bc_buf + pos + 1, + bswap16(get_u16(bc_buf + pos + 1))); + break; + case OP_FMT_i32: + case OP_FMT_u32: + case OP_FMT_const: + case OP_FMT_label: + case OP_FMT_atom: + case OP_FMT_atom_u8: + put_u32(bc_buf + pos + 1, + bswap32(get_u32(bc_buf + pos + 1))); + break; + case OP_FMT_atom_u16: + case OP_FMT_label_u16: + put_u32(bc_buf + pos + 1, + bswap32(get_u32(bc_buf + pos + 1))); + put_u16(bc_buf + pos + 1 + 4, + bswap16(get_u16(bc_buf + pos + 1 + 4))); + break; + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + put_u32(bc_buf + pos + 1, + bswap32(get_u32(bc_buf + pos + 1))); + put_u32(bc_buf + pos + 1 + 4, + bswap32(get_u32(bc_buf + pos + 1 + 4))); + if (fmt == OP_FMT_atom_label_u16) { + put_u16(bc_buf + pos + 1 + 4 + 4, + bswap16(get_u16(bc_buf + pos + 1 + 4 + 4))); + } + break; + case OP_FMT_npop_u16: + put_u16(bc_buf + pos + 1, + bswap16(get_u16(bc_buf + pos + 1))); + put_u16(bc_buf + pos + 1 + 2, + bswap16(get_u16(bc_buf + pos + 1 + 2))); + break; + default: + break; + } + pos += len; + } +} + +static BOOL is_ic_op(uint8_t op) +{ + return op >= OP_get_field_ic && op <= OP_put_field_ic; +} + +static int JS_WriteFunctionBytecode(BCWriterState *s, + const JSFunctionBytecode *b) +{ + int pos, len, bc_len, op; + JSAtom atom; + uint8_t *bc_buf; + uint32_t val; + + bc_len = b->byte_code_len; + bc_buf = js_malloc(s->ctx, bc_len); + if (!bc_buf) + return -1; + memcpy(bc_buf, b->byte_code_buf, bc_len); + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + len = short_opcode_info(op).size; + switch(short_opcode_info(op).fmt) { + case OP_FMT_atom: + case OP_FMT_atom_u8: + case OP_FMT_atom_u16: + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + atom = get_u32(bc_buf + pos + 1); + if (bc_atom_to_idx(s, &val, atom)) + goto fail; + put_u32(bc_buf + pos + 1, val); + break; + default: + // IC (inline cache) opcodes should not end up in the serialized + // bytecode; translate them to their non-IC counterparts here + if (is_ic_op(op)) { + val = get_u32(bc_buf + pos + 1); + atom = get_ic_atom(b->ic, val); + if (bc_atom_to_idx(s, &val, atom)) + goto fail; + put_u32(bc_buf + pos + 1, val); + bc_buf[pos] -= (OP_get_field_ic - OP_get_field); + } + break; + } + pos += len; + } + + if (is_be()) + bc_byte_swap(bc_buf, bc_len); + + dbuf_put(&s->dbuf, bc_buf, bc_len); + + js_free(s->ctx, bc_buf); + return 0; + fail: + js_free(s->ctx, bc_buf); + return -1; +} + +static void JS_WriteString(BCWriterState *s, JSString *p) +{ + int i; + bc_put_leb128(s, ((uint32_t)p->len << 1) | p->is_wide_char); + if (p->is_wide_char) { + for(i = 0; i < p->len; i++) + bc_put_u16(s, p->u.str16[i]); + } else { + dbuf_put(&s->dbuf, p->u.str8, p->len); + } +} + +static int JS_WriteBigInt(BCWriterState *s, JSValue obj) +{ + uint32_t tag, tag1; + int64_t e; + JSBigInt *bf = JS_VALUE_GET_PTR(obj); + bf_t *a = &bf->num; + size_t len, i, n1, j; + limb_t v; + + tag = JS_VALUE_GET_TAG(obj); + switch(tag) { + case JS_TAG_BIG_INT: + tag1 = BC_TAG_BIG_INT; + break; + default: + abort(); + } + bc_put_u8(s, tag1); + + /* sign + exponent */ + if (a->expn == BF_EXP_ZERO) + e = 0; + else if (a->expn == BF_EXP_INF) + e = 1; + else if (a->expn == BF_EXP_NAN) + e = 2; + else if (a->expn >= 0) + e = a->expn + 3; + else + e = a->expn; + e = (e * 2) | a->sign; + if (e < INT32_MIN || e > INT32_MAX) { + JS_ThrowRangeError(s->ctx, "maximum BigInt size exceeded"); + return -1; + } + bc_put_sleb128(s, e); + + /* mantissa */ + if (a->len != 0) { + i = 0; + while (i < a->len && a->tab[i] == 0) + i++; + assert(i < a->len); + v = a->tab[i]; + n1 = sizeof(limb_t); + while ((v & 0xff) == 0) { + n1--; + v >>= 8; + } + i++; + len = (a->len - i) * sizeof(limb_t) + n1; + if (len > INT32_MAX) { + JS_ThrowRangeError(s->ctx, "maximum BigInt size exceeded"); + return -1; + } + bc_put_leb128(s, len); + /* always saved in byte based little endian representation */ + for(j = 0; j < n1; j++) { + bc_put_u8(s, v >> (j * 8)); + } + for(; i < a->len; i++) { + limb_t v = a->tab[i]; +#if LIMB_BITS == 32 + bc_put_u32(s, v); +#else + bc_put_u64(s, v); +#endif + } + } + return 0; +} + +static int JS_WriteObjectRec(BCWriterState *s, JSValue obj); + +static int JS_WriteFunctionTag(BCWriterState *s, JSValue obj) +{ + JSFunctionBytecode *b = JS_VALUE_GET_PTR(obj); + uint32_t flags; + int idx, i; + + bc_put_u8(s, BC_TAG_FUNCTION_BYTECODE); + flags = idx = 0; + bc_set_flags(&flags, &idx, b->has_prototype, 1); + bc_set_flags(&flags, &idx, b->has_simple_parameter_list, 1); + bc_set_flags(&flags, &idx, b->is_derived_class_constructor, 1); + bc_set_flags(&flags, &idx, b->need_home_object, 1); + bc_set_flags(&flags, &idx, b->func_kind, 2); + bc_set_flags(&flags, &idx, b->new_target_allowed, 1); + bc_set_flags(&flags, &idx, b->super_call_allowed, 1); + bc_set_flags(&flags, &idx, b->super_allowed, 1); + bc_set_flags(&flags, &idx, b->arguments_allowed, 1); + bc_set_flags(&flags, &idx, b->backtrace_barrier, 1); + bc_set_flags(&flags, &idx, s->allow_debug, 1); + assert(idx <= 16); + bc_put_u16(s, flags); + bc_put_u8(s, b->is_strict_mode); + bc_put_atom(s, b->func_name); + + bc_put_leb128(s, b->arg_count); + bc_put_leb128(s, b->var_count); + bc_put_leb128(s, b->defined_arg_count); + bc_put_leb128(s, b->stack_size); + bc_put_leb128(s, b->closure_var_count); + bc_put_leb128(s, b->cpool_count); + bc_put_leb128(s, b->byte_code_len); + if (b->vardefs) { + /* XXX: this field is redundant */ + bc_put_leb128(s, b->arg_count + b->var_count); + for(i = 0; i < b->arg_count + b->var_count; i++) { + JSVarDef *vd = &b->vardefs[i]; + bc_put_atom(s, vd->var_name); + bc_put_leb128(s, vd->scope_level); + bc_put_leb128(s, vd->scope_next + 1); + flags = idx = 0; + bc_set_flags(&flags, &idx, vd->var_kind, 4); + bc_set_flags(&flags, &idx, vd->is_const, 1); + bc_set_flags(&flags, &idx, vd->is_lexical, 1); + bc_set_flags(&flags, &idx, vd->is_captured, 1); + assert(idx <= 8); + bc_put_u8(s, flags); + } + } else { + bc_put_leb128(s, 0); + } + + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + bc_put_atom(s, cv->var_name); + bc_put_leb128(s, cv->var_idx); + flags = idx = 0; + bc_set_flags(&flags, &idx, cv->is_local, 1); + bc_set_flags(&flags, &idx, cv->is_arg, 1); + bc_set_flags(&flags, &idx, cv->is_const, 1); + bc_set_flags(&flags, &idx, cv->is_lexical, 1); + bc_set_flags(&flags, &idx, cv->var_kind, 4); + assert(idx <= 8); + bc_put_u8(s, flags); + } + + // write constant pool before code so code can be disassembled + // on the fly at read time + for(i = 0; i < b->cpool_count; i++) { + if (JS_WriteObjectRec(s, b->cpool[i])) + goto fail; + } + + if (JS_WriteFunctionBytecode(s, b)) + goto fail; + + if (s->allow_debug) { + bc_put_atom(s, b->filename); + bc_put_leb128(s, b->line_num); + bc_put_leb128(s, b->col_num); + bc_put_leb128(s, b->pc2line_len); + dbuf_put(&s->dbuf, b->pc2line_buf, b->pc2line_len); + if (s->allow_source && b->source) { + bc_put_leb128(s, b->source_len); + dbuf_put(&s->dbuf, b->source, b->source_len); + } else { + bc_put_leb128(s, 0); + } + } + return 0; + fail: + return -1; +} + +static int JS_WriteModule(BCWriterState *s, JSValue obj) +{ + JSModuleDef *m = JS_VALUE_GET_PTR(obj); + int i; + + bc_put_u8(s, BC_TAG_MODULE); + bc_put_atom(s, m->module_name); + + bc_put_leb128(s, m->req_module_entries_count); + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + bc_put_atom(s, rme->module_name); + } + + bc_put_leb128(s, m->export_entries_count); + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + bc_put_u8(s, me->export_type); + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + bc_put_leb128(s, me->u.local.var_idx); + } else { + bc_put_leb128(s, me->u.req_module_idx); + bc_put_atom(s, me->local_name); + } + bc_put_atom(s, me->export_name); + } + + bc_put_leb128(s, m->star_export_entries_count); + for(i = 0; i < m->star_export_entries_count; i++) { + JSStarExportEntry *se = &m->star_export_entries[i]; + bc_put_leb128(s, se->req_module_idx); + } + + bc_put_leb128(s, m->import_entries_count); + for(i = 0; i < m->import_entries_count; i++) { + JSImportEntry *mi = &m->import_entries[i]; + bc_put_leb128(s, mi->var_idx); + bc_put_atom(s, mi->import_name); + bc_put_leb128(s, mi->req_module_idx); + } + + bc_put_u8(s, m->has_tla); + + if (JS_WriteObjectRec(s, m->func_obj)) + goto fail; + return 0; + fail: + return -1; +} + +static int JS_WriteArray(BCWriterState *s, JSValue obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + uint32_t i, len; + JSValue val; + int ret; + BOOL is_template; + + if (s->allow_bytecode && !p->extensible) { + /* not extensible array: we consider it is a + template when we are saving bytecode */ + bc_put_u8(s, BC_TAG_TEMPLATE_OBJECT); + is_template = TRUE; + } else { + bc_put_u8(s, BC_TAG_ARRAY); + is_template = FALSE; + } + if (js_get_length32(s->ctx, &len, obj)) + goto fail1; + bc_put_leb128(s, len); + for(i = 0; i < len; i++) { + val = JS_GetPropertyUint32(s->ctx, obj, i); + if (JS_IsException(val)) + goto fail1; + ret = JS_WriteObjectRec(s, val); + JS_FreeValue(s->ctx, val); + if (ret) + goto fail1; + } + if (is_template) { + val = JS_GetProperty(s->ctx, obj, JS_ATOM_raw); + if (JS_IsException(val)) + goto fail1; + ret = JS_WriteObjectRec(s, val); + JS_FreeValue(s->ctx, val); + if (ret) + goto fail1; + } + return 0; + fail1: + return -1; +} + +static int JS_WriteObjectTag(BCWriterState *s, JSValue obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + uint32_t i, prop_count; + JSShape *sh; + JSShapeProperty *pr; + int pass; + JSAtom atom; + + bc_put_u8(s, BC_TAG_OBJECT); + prop_count = 0; + sh = p->shape; + for(pass = 0; pass < 2; pass++) { + if (pass == 1) + bc_put_leb128(s, prop_count); + for(i = 0, pr = get_shape_prop(sh); i < sh->prop_count; i++, pr++) { + atom = pr->atom; + if (atom != JS_ATOM_NULL && (pr->flags & JS_PROP_ENUMERABLE)) { + if (pr->flags & JS_PROP_TMASK) { + JS_ThrowTypeError(s->ctx, "only value properties are supported"); + goto fail; + } + if (pass == 0) { + prop_count++; + } else { + bc_put_atom(s, atom); + if (JS_WriteObjectRec(s, p->prop[i].u.value)) + goto fail; + } + } + } + } + return 0; + fail: + return -1; +} + +static int JS_WriteTypedArray(BCWriterState *s, JSValue obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSTypedArray *ta = p->u.typed_array; + + bc_put_u8(s, BC_TAG_TYPED_ARRAY); + bc_put_u8(s, p->class_id - JS_CLASS_UINT8C_ARRAY); + bc_put_leb128(s, p->u.array.count); + bc_put_leb128(s, ta->offset); + if (JS_WriteObjectRec(s, JS_MKPTR(JS_TAG_OBJECT, ta->buffer))) + return -1; + return 0; +} + +static int JS_WriteArrayBuffer(BCWriterState *s, JSValue obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSArrayBuffer *abuf = p->u.array_buffer; + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(s->ctx); + return -1; + } + bc_put_u8(s, BC_TAG_ARRAY_BUFFER); + bc_put_leb128(s, abuf->byte_length); + bc_put_leb128(s, abuf->max_byte_length); + dbuf_put(&s->dbuf, abuf->data, abuf->byte_length); + return 0; +} + +static int JS_WriteSharedArrayBuffer(BCWriterState *s, JSValue obj) +{ + JSObject *p = JS_VALUE_GET_OBJ(obj); + JSArrayBuffer *abuf = p->u.array_buffer; + assert(!abuf->detached); /* SharedArrayBuffer are never detached */ + bc_put_u8(s, BC_TAG_SHARED_ARRAY_BUFFER); + bc_put_leb128(s, abuf->byte_length); + bc_put_leb128(s, abuf->max_byte_length); + bc_put_u64(s, (uintptr_t)abuf->data); + if (js_resize_array(s->ctx, (void **)&s->sab_tab, sizeof(s->sab_tab[0]), + &s->sab_tab_size, s->sab_tab_len + 1)) + return -1; + /* keep the SAB pointer so that the user can clone it or free it */ + s->sab_tab[s->sab_tab_len++] = abuf->data; + return 0; +} + +static int JS_WriteRegExp(BCWriterState *s, JSRegExp regexp) +{ + JSString *bc = regexp.bytecode; + assert(!bc->is_wide_char); + + JS_WriteString(s, regexp.pattern); + + if (is_be()) + lre_byte_swap(bc->u.str8, bc->len, /*is_byte_swapped*/FALSE); + + JS_WriteString(s, bc); + + if (is_be()) + lre_byte_swap(bc->u.str8, bc->len, /*is_byte_swapped*/TRUE); + + return 0; +} + +static int JS_WriteMap(BCWriterState *s, struct JSMapState *map_state); +static int JS_WriteSet(BCWriterState *s, struct JSMapState *map_state); + +static int JS_WriteObjectRec(BCWriterState *s, JSValue obj) +{ + uint32_t tag; + + if (js_check_stack_overflow(s->ctx->rt, 0)) { + JS_ThrowStackOverflow(s->ctx); + return -1; + } + + tag = JS_VALUE_GET_NORM_TAG(obj); + switch(tag) { + case JS_TAG_NULL: + bc_put_u8(s, BC_TAG_NULL); + break; + case JS_TAG_UNDEFINED: + bc_put_u8(s, BC_TAG_UNDEFINED); + break; + case JS_TAG_BOOL: + bc_put_u8(s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT(obj)); + break; + case JS_TAG_INT: + bc_put_u8(s, BC_TAG_INT32); + bc_put_sleb128(s, JS_VALUE_GET_INT(obj)); + break; + case JS_TAG_FLOAT64: + { + JSFloat64Union u; + bc_put_u8(s, BC_TAG_FLOAT64); + u.d = JS_VALUE_GET_FLOAT64(obj); + bc_put_u64(s, u.u64); + } + break; + case JS_TAG_STRING: + { + JSString *p = JS_VALUE_GET_STRING(obj); + bc_put_u8(s, BC_TAG_STRING); + JS_WriteString(s, p); + } + break; + case JS_TAG_FUNCTION_BYTECODE: + if (!s->allow_bytecode) + goto invalid_tag; + if (JS_WriteFunctionTag(s, obj)) + goto fail; + break; + case JS_TAG_MODULE: + if (!s->allow_bytecode) + goto invalid_tag; + if (JS_WriteModule(s, obj)) + goto fail; + break; + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(obj); + int ret, idx; + + if (s->allow_reference) { + idx = js_object_list_find(s->ctx, &s->object_list, p); + if (idx >= 0) { + bc_put_u8(s, BC_TAG_OBJECT_REFERENCE); + bc_put_leb128(s, idx); + break; + } else { + if (js_object_list_add(s->ctx, &s->object_list, p)) + goto fail; + } + } else { + if (p->tmp_mark) { + JS_ThrowTypeError(s->ctx, "circular reference"); + goto fail; + } + p->tmp_mark = 1; + } + switch(p->class_id) { + case JS_CLASS_ARRAY: + ret = JS_WriteArray(s, obj); + break; + case JS_CLASS_OBJECT: + ret = JS_WriteObjectTag(s, obj); + break; + case JS_CLASS_ARRAY_BUFFER: + ret = JS_WriteArrayBuffer(s, obj); + break; + case JS_CLASS_SHARED_ARRAY_BUFFER: + if (!s->allow_sab) + goto invalid_tag; + ret = JS_WriteSharedArrayBuffer(s, obj); + break; + case JS_CLASS_REGEXP: + bc_put_u8(s, BC_TAG_REGEXP); + ret = JS_WriteRegExp(s, p->u.regexp); + break; + case JS_CLASS_DATE: + bc_put_u8(s, BC_TAG_DATE); + ret = JS_WriteObjectRec(s, p->u.object_data); + break; + case JS_CLASS_NUMBER: + case JS_CLASS_STRING: + case JS_CLASS_BOOLEAN: + case JS_CLASS_BIG_INT: + bc_put_u8(s, BC_TAG_OBJECT_VALUE); + ret = JS_WriteObjectRec(s, p->u.object_data); + break; + case JS_CLASS_MAP: + bc_put_u8(s, BC_TAG_MAP); + ret = JS_WriteMap(s, p->u.map_state); + break; + case JS_CLASS_SET: + bc_put_u8(s, BC_TAG_SET); + ret = JS_WriteSet(s, p->u.map_state); + break; + default: + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + ret = JS_WriteTypedArray(s, obj); + } else { + JS_ThrowTypeError(s->ctx, "unsupported object class"); + ret = -1; + } + break; + } + p->tmp_mark = 0; + if (ret) + goto fail; + } + break; + case JS_TAG_BIG_INT: + if (JS_WriteBigInt(s, obj)) + goto fail; + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(obj); + if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL && p->atom_type != JS_ATOM_TYPE_SYMBOL) { + JS_ThrowTypeError(s->ctx, "unsupported symbol type"); + goto fail; + } + JSAtom atom = js_get_atom_index(s->ctx->rt, p); + bc_put_u8(s, BC_TAG_SYMBOL); + bc_put_atom(s, atom); + } + break; + default: + invalid_tag: + JS_ThrowInternalError(s->ctx, "unsupported tag (%d)", tag); + goto fail; + } + return 0; + + fail: + return -1; +} + +/* create the atom table */ +static int JS_WriteObjectAtoms(BCWriterState *s) +{ + JSRuntime *rt = s->ctx->rt; + DynBuf dbuf1; + int i, atoms_size; + + dbuf1 = s->dbuf; + js_dbuf_init(s->ctx, &s->dbuf); + bc_put_u8(s, BC_VERSION); + + bc_put_leb128(s, s->idx_to_atom_count); + for(i = 0; i < s->idx_to_atom_count; i++) { + JSAtom atom = s->idx_to_atom[i]; + if (__JS_AtomIsConst(atom)) { + bc_put_u8(s, 0 /* the type */); + /* TODO(saghul): encoding for tagged integers and keyword-ish atoms could be + more efficient. */ + bc_put_u32(s, atom); + } else { + JSAtomStruct *p = rt->atom_array[atom]; + uint8_t type = p->atom_type; + assert(type != JS_ATOM_TYPE_PRIVATE); + bc_put_u8(s, type); + JS_WriteString(s, p); + } + } + /* XXX: should check for OOM in above phase */ + + /* move the atoms at the start */ + /* XXX: could just append dbuf1 data, but it uses more memory if + dbuf1 is larger than dbuf */ + atoms_size = s->dbuf.size; + if (dbuf_realloc(&dbuf1, dbuf1.size + atoms_size)) + goto fail; + memmove(dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size); + memcpy(dbuf1.buf, s->dbuf.buf, atoms_size); + dbuf1.size += atoms_size; + dbuf_free(&s->dbuf); + s->dbuf = dbuf1; + return 0; + fail: + dbuf_free(&dbuf1); + return -1; +} + +uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValue obj, + int flags, JSSABTab *psab_tab) +{ + BCWriterState ss, *s = &ss; + + memset(s, 0, sizeof(*s)); + s->ctx = ctx; + s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0); + s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0); + s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0); + s->allow_source = ((flags & JS_WRITE_OBJ_STRIP_SOURCE) == 0); + s->allow_debug = ((flags & JS_WRITE_OBJ_STRIP_DEBUG) == 0); + /* XXX: could use a different version when bytecode is included */ + if (s->allow_bytecode) + s->first_atom = JS_ATOM_END; + else + s->first_atom = 1; + js_dbuf_init(ctx, &s->dbuf); + js_object_list_init(&s->object_list); + + if (JS_WriteObjectRec(s, obj)) + goto fail; + if (JS_WriteObjectAtoms(s)) + goto fail; + js_object_list_end(ctx, &s->object_list); + js_free(ctx, s->atom_to_idx); + js_free(ctx, s->idx_to_atom); + *psize = s->dbuf.size; + if (psab_tab) { + psab_tab->tab = s->sab_tab; + psab_tab->len = s->sab_tab_len; + } else { + js_free(ctx, s->sab_tab); + } + return s->dbuf.buf; + fail: + js_object_list_end(ctx, &s->object_list); + js_free(ctx, s->atom_to_idx); + js_free(ctx, s->idx_to_atom); + dbuf_free(&s->dbuf); + *psize = 0; + if (psab_tab) { + psab_tab->tab = NULL; + psab_tab->len = 0; + } + return NULL; +} + +uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValue obj, + int flags) +{ + return JS_WriteObject2(ctx, psize, obj, flags, NULL); +} + +typedef struct BCReaderState { + JSContext *ctx; + const uint8_t *buf_start, *ptr, *buf_end; + uint32_t first_atom; + uint32_t idx_to_atom_count; + JSAtom *idx_to_atom; + int error_state; + BOOL allow_sab : 8; + BOOL allow_bytecode : 8; + BOOL allow_reference : 8; + /* object references */ + JSObject **objects; + int objects_count; + int objects_size; + /* SAB references */ + uint8_t **sab_tab; + int sab_tab_len; + int sab_tab_size; + /* used for DUMP_READ_OBJECT */ + const uint8_t *ptr_last; + int level; +} BCReaderState; + +#ifdef DUMP_READ_OBJECT +static void __attribute__((format(printf, 2, 3))) bc_read_trace(BCReaderState *s, const char *fmt, ...) { + va_list ap; + int i, n, n0; + + if (!check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) + return; + + if (!s->ptr_last) + s->ptr_last = s->buf_start; + + n = n0 = 0; + if (s->ptr > s->ptr_last || s->ptr == s->buf_start) { + n0 = printf("%04x: ", (int)(s->ptr_last - s->buf_start)); + n += n0; + } + for (i = 0; s->ptr_last < s->ptr; i++) { + if ((i & 7) == 0 && i > 0) { + printf("\n%*s", n0, ""); + n = n0; + } + n += printf(" %02x", *s->ptr_last++); + } + if (*fmt == '}') + s->level--; + if (n < 32 + s->level * 2) { + printf("%*s", 32 + s->level * 2 - n, ""); + } + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + if (strchr(fmt, '{')) + s->level++; +} +#else +#define bc_read_trace(...) +#endif + +static int bc_read_error_end(BCReaderState *s) +{ + if (!s->error_state) { + JS_ThrowSyntaxError(s->ctx, "read after the end of the buffer"); + } + return s->error_state = -1; +} + +static int bc_get_u8(BCReaderState *s, uint8_t *pval) +{ + if (unlikely(s->buf_end - s->ptr < 1)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end(s); + } + *pval = *s->ptr++; + return 0; +} + +static int bc_get_u16(BCReaderState *s, uint16_t *pval) +{ + uint16_t v; + if (unlikely(s->buf_end - s->ptr < 2)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end(s); + } + v = get_u16(s->ptr); + if (is_be()) + v = bswap16(v); + *pval = v; + s->ptr += 2; + return 0; +} + +static __maybe_unused int bc_get_u32(BCReaderState *s, uint32_t *pval) +{ + uint32_t v; + if (unlikely(s->buf_end - s->ptr < 4)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end(s); + } + v = get_u32(s->ptr); + if (is_be()) + v = bswap32(v); + *pval = v; + s->ptr += 4; + return 0; +} + +static int bc_get_u64(BCReaderState *s, uint64_t *pval) +{ + uint64_t v; + if (unlikely(s->buf_end - s->ptr < 8)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end(s); + } + v = get_u64(s->ptr); + if (is_be()) + v = bswap64(v); + *pval = v; + s->ptr += 8; + return 0; +} + +static int bc_get_leb128(BCReaderState *s, uint32_t *pval) +{ + int ret; + ret = get_leb128(pval, s->ptr, s->buf_end); + if (unlikely(ret < 0)) + return bc_read_error_end(s); + s->ptr += ret; + return 0; +} + +static int bc_get_sleb128(BCReaderState *s, int32_t *pval) +{ + int ret; + ret = get_sleb128(pval, s->ptr, s->buf_end); + if (unlikely(ret < 0)) + return bc_read_error_end(s); + s->ptr += ret; + return 0; +} + +/* XXX: used to read an `int` with a positive value */ +static int bc_get_leb128_int(BCReaderState *s, int *pval) +{ + return bc_get_leb128(s, (uint32_t *)pval); +} + +static int bc_get_leb128_u16(BCReaderState *s, uint16_t *pval) +{ + uint32_t val; + if (bc_get_leb128(s, &val)) { + *pval = 0; + return -1; + } + *pval = val; + return 0; +} + +static int bc_get_buf(BCReaderState *s, void *buf, uint32_t buf_len) +{ + if (buf_len != 0) { + if (unlikely(!buf || s->buf_end - s->ptr < buf_len)) + return bc_read_error_end(s); + memcpy(buf, s->ptr, buf_len); + s->ptr += buf_len; + } + return 0; +} + +static int bc_idx_to_atom(BCReaderState *s, JSAtom *patom, uint32_t idx) +{ + JSAtom atom; + + if (__JS_AtomIsTaggedInt(idx)) { + atom = idx; + } else if (idx < s->first_atom) { + atom = JS_DupAtom(s->ctx, idx); + } else { + idx -= s->first_atom; + if (idx >= s->idx_to_atom_count) { + JS_ThrowSyntaxError(s->ctx, "invalid atom index (pos=%u)", + (unsigned int)(s->ptr - s->buf_start)); + *patom = JS_ATOM_NULL; + return s->error_state = -1; + } + atom = JS_DupAtom(s->ctx, s->idx_to_atom[idx]); + } + *patom = atom; + return 0; +} + +static int bc_get_atom(BCReaderState *s, JSAtom *patom) +{ + uint32_t v; + if (bc_get_leb128(s, &v)) + return -1; + if (v & 1) { + *patom = __JS_AtomFromUInt32(v >> 1); + return 0; + } else { + return bc_idx_to_atom(s, patom, v >> 1); + } +} + +static JSString *JS_ReadString(BCReaderState *s) +{ + uint32_t len; + size_t size; + BOOL is_wide_char; + JSString *p; + + if (bc_get_leb128(s, &len)) + return NULL; + is_wide_char = len & 1; + len >>= 1; + p = js_alloc_string(s->ctx, len, is_wide_char); + if (!p) { + s->error_state = -1; + return NULL; + } + size = (size_t)len << is_wide_char; + if ((s->buf_end - s->ptr) < size) { + bc_read_error_end(s); + js_free_string(s->ctx->rt, p); + return NULL; + } + memcpy(p->u.str8, s->ptr, size); + s->ptr += size; + if (is_wide_char) { + if (is_be()) { + uint32_t i; + for (i = 0; i < len; i++) + p->u.str16[i] = bswap16(p->u.str16[i]); + } + } else { + p->u.str8[size] = '\0'; /* add the trailing zero for 8 bit strings */ + } +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + bc_read_trace(s, "%s", ""); // hex dump and indentation + JS_DumpString(s->ctx->rt, p); + printf("\n"); + } +#endif + return p; +} + +static uint32_t bc_get_flags(uint32_t flags, int *pidx, int n) +{ + uint32_t val; + /* XXX: this does not work for n == 32 */ + val = (flags >> *pidx) & ((1U << n) - 1); + *pidx += n; + return val; +} + +static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b, + int byte_code_offset, uint32_t bc_len) +{ + uint8_t *bc_buf; + int pos, len, op; + JSAtom atom; + uint32_t idx; + + bc_buf = (uint8_t*)b + byte_code_offset; + if (bc_get_buf(s, bc_buf, bc_len)) + return -1; + b->byte_code_buf = bc_buf; + + if (is_be()) + bc_byte_swap(bc_buf, bc_len); + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + len = short_opcode_info(op).size; + switch(short_opcode_info(op).fmt) { + case OP_FMT_atom: + case OP_FMT_atom_u8: + case OP_FMT_atom_u16: + case OP_FMT_atom_label_u8: + case OP_FMT_atom_label_u16: + idx = get_u32(bc_buf + pos + 1); + if (bc_idx_to_atom(s, &atom, idx)) { + /* Note: the atoms will be freed up to this position */ + b->byte_code_len = pos; + return -1; + } + put_u32(bc_buf + pos + 1, atom); + break; + default: + assert(!is_ic_op(op)); // should not end up in serialized bytecode + break; + } +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + const uint8_t *save_ptr = s->ptr; + s->ptr = s->ptr_last + len; + s->level -= 4; + bc_read_trace(s, "%s", ""); // hex dump + indent + dump_single_byte_code(s->ctx, bc_buf + pos, b, + s->ptr - s->buf_start - len); + s->level += 4; + s->ptr = save_ptr; + } +#endif + pos += len; + } + return 0; +} + +static JSValue JS_ReadBigInt(BCReaderState *s) +{ + JSValue obj; + uint8_t v8; + int32_t e; + uint32_t len; + limb_t l, i, n; + limb_t v; + bf_t *a; + + obj = JS_NewBigInt(s->ctx); + if (JS_IsException(obj)) + goto fail; + + /* sign + exponent */ + if (bc_get_sleb128(s, &e)) + goto fail; + + a = JS_GetBigInt(obj); + a->sign = e & 1; + e >>= 1; + if (e == 0) + a->expn = BF_EXP_ZERO; + else if (e == 1) + a->expn = BF_EXP_INF; + else if (e == 2) + a->expn = BF_EXP_NAN; + else if (e >= 3) + a->expn = e - 3; + else + a->expn = e; + + /* mantissa */ + if (a->expn != BF_EXP_ZERO && + a->expn != BF_EXP_INF && + a->expn != BF_EXP_NAN) { + if (bc_get_leb128(s, &len)) + goto fail; + bc_read_trace(s, "len=%" PRId64 "\n", (int64_t)len); + if (len == 0) { + JS_ThrowRangeError(s->ctx, "maximum BigInt size exceeded"); + goto fail; + } + l = (len + sizeof(limb_t) - 1) / sizeof(limb_t); + if (bf_resize(a, l)) { + JS_ThrowOutOfMemory(s->ctx); + goto fail; + } + n = len & (sizeof(limb_t) - 1); + if (n != 0) { + v = 0; + for(i = 0; i < n; i++) { + if (bc_get_u8(s, &v8)) + goto fail; + v |= (limb_t)v8 << ((sizeof(limb_t) - n + i) * 8); + } + a->tab[0] = v; + i = 1; + } else { + i = 0; + } + for(; i < l; i++) { +#if LIMB_BITS == 32 + if (bc_get_u32(s, &v)) + goto fail; +#else + if (bc_get_u64(s, &v)) + goto fail; +#endif + a->tab[i] = v; + } + } + return obj; + fail: + JS_FreeValue(s->ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectRec(BCReaderState *s); + +static int BC_add_object_ref1(BCReaderState *s, JSObject *p) +{ + if (s->allow_reference) { + if (js_resize_array(s->ctx, (void *)&s->objects, + sizeof(s->objects[0]), + &s->objects_size, s->objects_count + 1)) + return -1; + s->objects[s->objects_count++] = p; + } + return 0; +} + +static int BC_add_object_ref(BCReaderState *s, JSValue obj) +{ + return BC_add_object_ref1(s, JS_VALUE_GET_OBJ(obj)); +} + +static JSValue JS_ReadFunctionTag(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSFunctionBytecode bc, *b; + JSValue obj = JS_UNDEFINED; + uint16_t v16; + uint8_t v8; + int idx, i, local_count, has_debug_info; + int function_size, cpool_offset, byte_code_offset; + int closure_var_offset, vardefs_offset; + + memset(&bc, 0, sizeof(bc)); + bc.header.ref_count = 1; + //bc.gc_header.mark = 0; + + if (bc_get_u16(s, &v16)) + goto fail; + idx = 0; + bc.has_prototype = bc_get_flags(v16, &idx, 1); + bc.has_simple_parameter_list = bc_get_flags(v16, &idx, 1); + bc.is_derived_class_constructor = bc_get_flags(v16, &idx, 1); + bc.need_home_object = bc_get_flags(v16, &idx, 1); + bc.func_kind = bc_get_flags(v16, &idx, 2); + bc.new_target_allowed = bc_get_flags(v16, &idx, 1); + bc.super_call_allowed = bc_get_flags(v16, &idx, 1); + bc.super_allowed = bc_get_flags(v16, &idx, 1); + bc.arguments_allowed = bc_get_flags(v16, &idx, 1); + bc.backtrace_barrier = bc_get_flags(v16, &idx, 1); + has_debug_info = bc_get_flags(v16, &idx, 1); + if (bc_get_u8(s, &v8)) + goto fail; + bc.is_strict_mode = (v8 > 0); + if (bc_get_atom(s, &bc.func_name)) + goto fail; + if (bc_get_leb128_u16(s, &bc.arg_count)) + goto fail; + if (bc_get_leb128_u16(s, &bc.var_count)) + goto fail; + if (bc_get_leb128_u16(s, &bc.defined_arg_count)) + goto fail; + if (bc_get_leb128_u16(s, &bc.stack_size)) + goto fail; + if (bc_get_leb128_int(s, &bc.closure_var_count)) + goto fail; + if (bc_get_leb128_int(s, &bc.cpool_count)) + goto fail; + if (bc_get_leb128_int(s, &bc.byte_code_len)) + goto fail; + if (bc_get_leb128_int(s, &local_count)) + goto fail; + + function_size = sizeof(*b); + cpool_offset = function_size; + function_size += bc.cpool_count * sizeof(*bc.cpool); + vardefs_offset = function_size; + function_size += local_count * sizeof(*bc.vardefs); + closure_var_offset = function_size; + function_size += bc.closure_var_count * sizeof(*bc.closure_var); + byte_code_offset = function_size; + function_size += bc.byte_code_len; + + b = js_mallocz(ctx, function_size); + if (!b) + goto fail; + + memcpy(b, &bc, sizeof(*b)); + bc.func_name = JS_ATOM_NULL; + b->header.ref_count = 1; + if (local_count != 0) { + b->vardefs = (void *)((uint8_t*)b + vardefs_offset); + } + if (b->closure_var_count != 0) { + b->closure_var = (void *)((uint8_t*)b + closure_var_offset); + } + if (b->cpool_count != 0) { + b->cpool = (void *)((uint8_t*)b + cpool_offset); + } + + add_gc_object(ctx->rt, &b->header, JS_GC_OBJ_TYPE_FUNCTION_BYTECODE); + + obj = JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, b); + +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + if (b->func_name) { + bc_read_trace(s, "name: "); + print_atom(s->ctx, b->func_name); + printf("\n"); + } + } +#endif + bc_read_trace(s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", + b->arg_count, b->var_count, b->defined_arg_count, + b->closure_var_count, b->cpool_count); + bc_read_trace(s, "stack=%d bclen=%d locals=%d\n", + b->stack_size, b->byte_code_len, local_count); + + if (local_count != 0) { + bc_read_trace(s, "vars {\n"); + bc_read_trace(s, "off flags scope name\n"); + for(i = 0; i < local_count; i++) { + JSVarDef *vd = &b->vardefs[i]; + if (bc_get_atom(s, &vd->var_name)) + goto fail; + if (bc_get_leb128_int(s, &vd->scope_level)) + goto fail; + if (bc_get_leb128_int(s, &vd->scope_next)) + goto fail; + vd->scope_next--; + if (bc_get_u8(s, &v8)) + goto fail; + idx = 0; + vd->var_kind = bc_get_flags(v8, &idx, 4); + vd->is_const = bc_get_flags(v8, &idx, 1); + vd->is_lexical = bc_get_flags(v8, &idx, 1); + vd->is_captured = bc_get_flags(v8, &idx, 1); +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + bc_read_trace(s, "%3d %d%c%c%c %4d ", + i, vd->var_kind, + vd->is_const ? 'C' : '.', + vd->is_lexical ? 'L' : '.', + vd->is_captured ? 'X' : '.', + vd->scope_level); + print_atom(s->ctx, vd->var_name); + printf("\n"); + } +#endif + } + bc_read_trace(s, "}\n"); + } + if (b->closure_var_count != 0) { + bc_read_trace(s, "closure vars {\n"); + bc_read_trace(s, "off flags idx name\n"); + for(i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + int var_idx; + if (bc_get_atom(s, &cv->var_name)) + goto fail; + if (bc_get_leb128_int(s, &var_idx)) + goto fail; + cv->var_idx = var_idx; + if (bc_get_u8(s, &v8)) + goto fail; + idx = 0; + cv->is_local = bc_get_flags(v8, &idx, 1); + cv->is_arg = bc_get_flags(v8, &idx, 1); + cv->is_const = bc_get_flags(v8, &idx, 1); + cv->is_lexical = bc_get_flags(v8, &idx, 1); + cv->var_kind = bc_get_flags(v8, &idx, 4); +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + bc_read_trace(s, "%3d %d%c%c%c%c %3d ", + i, cv->var_kind, + cv->is_local ? 'L' : '.', + cv->is_arg ? 'A' : '.', + cv->is_const ? 'C' : '.', + cv->is_lexical ? 'X' : '.', + cv->var_idx); + print_atom(s->ctx, cv->var_name); + printf("\n"); + } +#endif + } + bc_read_trace(s, "}\n"); + } + if (b->cpool_count != 0) { + bc_read_trace(s, "cpool {\n"); + for(i = 0; i < b->cpool_count; i++) { + JSValue val; + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + b->cpool[i] = val; + } + bc_read_trace(s, "}\n"); + } + { + bc_read_trace(s, "bytecode {\n"); + if (JS_ReadFunctionBytecode(s, b, byte_code_offset, b->byte_code_len)) + goto fail; + bc_read_trace(s, "}\n"); + } + if (!has_debug_info) + goto nodebug; + + /* read optional debug information */ + bc_read_trace(s, "debug {\n"); + if (bc_get_atom(s, &b->filename)) + goto fail; + if (bc_get_leb128_int(s, &b->line_num)) + goto fail; + if (bc_get_leb128_int(s, &b->col_num)) + goto fail; +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + bc_read_trace(s, "filename: "); + print_atom(s->ctx, b->filename); + printf(", line: %d, column: %d\n", b->line_num, b->col_num); + } +#endif + if (bc_get_leb128_int(s, &b->pc2line_len)) + goto fail; + if (b->pc2line_len) { + bc_read_trace(s, "positions: %d bytes\n", b->pc2line_len); + b->pc2line_buf = js_mallocz(ctx, b->pc2line_len); + if (!b->pc2line_buf) + goto fail; + if (bc_get_buf(s, b->pc2line_buf, b->pc2line_len)) + goto fail; + } + if (bc_get_leb128_int(s, &b->source_len)) + goto fail; + if (b->source_len) { + bc_read_trace(s, "source: %d bytes\n", b->source_len); + if (s->ptr_last) + s->ptr_last += b->source_len; // omit source code hex dump + /* b->source is a UTF-8 encoded null terminated C string */ + b->source = js_mallocz(ctx, b->source_len + 1); + if (!b->source) + goto fail; + if (bc_get_buf(s, b->source, b->source_len)) + goto fail; + } + bc_read_trace(s, "}\n"); + + nodebug: + b->realm = JS_DupContext(ctx); + return obj; + + fail: + JS_FreeAtom(ctx, bc.func_name); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadModule(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue obj; + JSModuleDef *m = NULL; + JSAtom module_name; + int i; + uint8_t v8; + + if (bc_get_atom(s, &module_name)) + goto fail; +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + bc_read_trace(s, "name: "); + print_atom(s->ctx, module_name); + printf("\n"); + } +#endif + m = js_new_module_def(ctx, module_name); + if (!m) + goto fail; + obj = js_dup(JS_MKPTR(JS_TAG_MODULE, m)); + if (bc_get_leb128_int(s, &m->req_module_entries_count)) + goto fail; + obj = JS_NewModuleValue(ctx, m); + if (m->req_module_entries_count != 0) { + m->req_module_entries_size = m->req_module_entries_count; + m->req_module_entries = js_mallocz(ctx, sizeof(m->req_module_entries[0]) * m->req_module_entries_size); + if (!m->req_module_entries) + goto fail; + for(i = 0; i < m->req_module_entries_count; i++) { + JSReqModuleEntry *rme = &m->req_module_entries[i]; + if (bc_get_atom(s, &rme->module_name)) + goto fail; + } + } + + if (bc_get_leb128_int(s, &m->export_entries_count)) + goto fail; + if (m->export_entries_count != 0) { + m->export_entries_size = m->export_entries_count; + m->export_entries = js_mallocz(ctx, sizeof(m->export_entries[0]) * m->export_entries_size); + if (!m->export_entries) + goto fail; + for(i = 0; i < m->export_entries_count; i++) { + JSExportEntry *me = &m->export_entries[i]; + if (bc_get_u8(s, &v8)) + goto fail; + me->export_type = v8; + if (me->export_type == JS_EXPORT_TYPE_LOCAL) { + if (bc_get_leb128_int(s, &me->u.local.var_idx)) + goto fail; + } else { + if (bc_get_leb128_int(s, &me->u.req_module_idx)) + goto fail; + if (bc_get_atom(s, &me->local_name)) + goto fail; + } + if (bc_get_atom(s, &me->export_name)) + goto fail; + } + } + + if (bc_get_leb128_int(s, &m->star_export_entries_count)) + goto fail; + if (m->star_export_entries_count != 0) { + m->star_export_entries_size = m->star_export_entries_count; + m->star_export_entries = js_mallocz(ctx, sizeof(m->star_export_entries[0]) * m->star_export_entries_size); + if (!m->star_export_entries) + goto fail; + for(i = 0; i < m->star_export_entries_count; i++) { + JSStarExportEntry *se = &m->star_export_entries[i]; + if (bc_get_leb128_int(s, &se->req_module_idx)) + goto fail; + } + } + + if (bc_get_leb128_int(s, &m->import_entries_count)) + goto fail; + if (m->import_entries_count != 0) { + m->import_entries_size = m->import_entries_count; + m->import_entries = js_mallocz(ctx, sizeof(m->import_entries[0]) * m->import_entries_size); + if (!m->import_entries) + goto fail; + for(i = 0; i < m->import_entries_count; i++) { + JSImportEntry *mi = &m->import_entries[i]; + if (bc_get_leb128_int(s, &mi->var_idx)) + goto fail; + if (bc_get_atom(s, &mi->import_name)) + goto fail; + if (bc_get_leb128_int(s, &mi->req_module_idx)) + goto fail; + } + } + + if (bc_get_u8(s, &v8)) + goto fail; + m->has_tla = (v8 != 0); + + m->func_obj = JS_ReadObjectRec(s); + if (JS_IsException(m->func_obj)) + goto fail; + return obj; + fail: + if (m) { + js_free_module_def(ctx, m); + } + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectTag(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue obj; + uint32_t prop_count, i; + JSAtom atom; + JSValue val; + int ret; + + obj = JS_NewObject(ctx); + if (BC_add_object_ref(s, obj)) + goto fail; + if (bc_get_leb128(s, &prop_count)) + goto fail; + for(i = 0; i < prop_count; i++) { + if (bc_get_atom(s, &atom)) + goto fail; +#ifdef DUMP_READ_OBJECT + if (check_dump_flag(s->ctx->rt, DUMP_READ_OBJECT)) { + bc_read_trace(s, "propname: "); + print_atom(s->ctx, atom); + printf("\n"); + } +#endif + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) { + JS_FreeAtom(ctx, atom); + goto fail; + } + ret = JS_DefinePropertyValue(ctx, obj, atom, val, JS_PROP_C_W_E); + JS_FreeAtom(ctx, atom); + if (ret < 0) + goto fail; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadArray(BCReaderState *s, int tag) +{ + JSContext *ctx = s->ctx; + JSValue obj; + uint32_t len, i; + JSValue val; + int ret, prop_flags; + BOOL is_template; + + obj = JS_NewArray(ctx); + if (BC_add_object_ref(s, obj)) + goto fail; + is_template = (tag == BC_TAG_TEMPLATE_OBJECT); + if (bc_get_leb128(s, &len)) + goto fail; + for(i = 0; i < len; i++) { + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + if (is_template) + prop_flags = JS_PROP_ENUMERABLE; + else + prop_flags = JS_PROP_C_W_E; + ret = JS_DefinePropertyValueUint32(ctx, obj, i, val, + prop_flags); + if (ret < 0) + goto fail; + } + if (is_template) { + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + if (!JS_IsUndefined(val)) { + ret = JS_DefinePropertyValue(ctx, obj, JS_ATOM_raw, val, 0); + if (ret < 0) + goto fail; + } + JS_PreventExtensions(ctx, obj); + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadTypedArray(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue obj = JS_UNDEFINED, array_buffer = JS_UNDEFINED; + uint8_t array_tag; + JSValue args[3]; + uint32_t offset, len, idx; + + if (bc_get_u8(s, &array_tag)) + return JS_EXCEPTION; + if (array_tag >= JS_TYPED_ARRAY_COUNT) + return JS_ThrowTypeError(ctx, "invalid typed array"); + if (bc_get_leb128(s, &len)) + return JS_EXCEPTION; + if (bc_get_leb128(s, &offset)) + return JS_EXCEPTION; + /* XXX: this hack could be avoided if the typed array could be + created before the array buffer */ + idx = s->objects_count; + if (BC_add_object_ref1(s, NULL)) + goto fail; + array_buffer = JS_ReadObjectRec(s); + if (JS_IsException(array_buffer)) + return JS_EXCEPTION; + if (!js_get_array_buffer(ctx, array_buffer)) { + JS_FreeValue(ctx, array_buffer); + return JS_EXCEPTION; + } + args[0] = array_buffer; + args[1] = js_int64(offset); + args[2] = js_int64(len); + obj = js_typed_array_constructor(ctx, JS_UNDEFINED, + 3, args, + JS_CLASS_UINT8C_ARRAY + array_tag); + if (JS_IsException(obj)) + goto fail; + if (s->allow_reference) { + s->objects[idx] = JS_VALUE_GET_OBJ(obj); + } + JS_FreeValue(ctx, array_buffer); + return obj; + fail: + JS_FreeValue(ctx, array_buffer); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadArrayBuffer(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + uint32_t byte_length, max_byte_length; + uint64_t max_byte_length_u64, *pmax_byte_length = NULL; + JSValue obj; + + if (bc_get_leb128(s, &byte_length)) + return JS_EXCEPTION; + if (bc_get_leb128(s, &max_byte_length)) + return JS_EXCEPTION; + if (max_byte_length < byte_length) + return JS_ThrowTypeError(ctx, "invalid array buffer"); + if (max_byte_length != UINT32_MAX) { + max_byte_length_u64 = max_byte_length; + pmax_byte_length = &max_byte_length_u64; + } + if (unlikely(s->buf_end - s->ptr < byte_length)) { + bc_read_error_end(s); + return JS_EXCEPTION; + } + // makes a copy of the input + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, + byte_length, pmax_byte_length, + JS_CLASS_ARRAY_BUFFER, + (uint8_t*)s->ptr, + js_array_buffer_free, NULL, + /*alloc_flag*/TRUE); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + s->ptr += byte_length; + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadSharedArrayBuffer(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + uint32_t byte_length, max_byte_length; + uint64_t max_byte_length_u64, *pmax_byte_length = NULL; + uint8_t *data_ptr; + JSValue obj; + uint64_t u64; + + if (bc_get_leb128(s, &byte_length)) + return JS_EXCEPTION; + if (bc_get_leb128(s, &max_byte_length)) + return JS_EXCEPTION; + if (max_byte_length < byte_length) + return JS_ThrowTypeError(ctx, "invalid array buffer"); + if (max_byte_length != UINT32_MAX) { + max_byte_length_u64 = max_byte_length; + pmax_byte_length = &max_byte_length_u64; + } + if (bc_get_u64(s, &u64)) + return JS_EXCEPTION; + data_ptr = (uint8_t *)(uintptr_t)u64; + if (js_resize_array(s->ctx, (void **)&s->sab_tab, sizeof(s->sab_tab[0]), + &s->sab_tab_size, s->sab_tab_len + 1)) + return JS_EXCEPTION; + /* keep the SAB pointer so that the user can clone it or free it */ + s->sab_tab[s->sab_tab_len++] = data_ptr; + /* the SharedArrayBuffer is cloned */ + obj = js_array_buffer_constructor3(ctx, JS_UNDEFINED, + byte_length, pmax_byte_length, + JS_CLASS_SHARED_ARRAY_BUFFER, + data_ptr, + NULL, NULL, FALSE); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadRegExp(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSString *pattern; + JSString *bc; + + pattern = JS_ReadString(s); + if (!pattern) + return JS_EXCEPTION; + + bc = JS_ReadString(s); + if (!bc) { + js_free_string(ctx->rt, pattern); + return JS_EXCEPTION; + } + + if (bc->is_wide_char) { + js_free_string(ctx->rt, pattern); + js_free_string(ctx->rt, bc); + return JS_ThrowInternalError(ctx, "bad regexp bytecode"); + } + + if (is_be()) + lre_byte_swap(bc->u.str8, bc->len, /*is_byte_swapped*/TRUE); + + return js_regexp_constructor_internal(ctx, JS_UNDEFINED, + JS_MKPTR(JS_TAG_STRING, pattern), + JS_MKPTR(JS_TAG_STRING, bc)); +} + +static JSValue JS_ReadDate(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue val, obj = JS_UNDEFINED; + + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + if (!JS_IsNumber(val)) { + JS_ThrowTypeError(ctx, "Number tag expected for date"); + goto fail; + } + obj = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_DATE], + JS_CLASS_DATE); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + JS_SetObjectData(ctx, obj, val); + return obj; + fail: + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectValue(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + JSValue val, obj = JS_UNDEFINED; + + val = JS_ReadObjectRec(s); + if (JS_IsException(val)) + goto fail; + obj = JS_ToObject(ctx, val); + if (JS_IsException(obj)) + goto fail; + if (BC_add_object_ref(s, obj)) + goto fail; + JS_FreeValue(ctx, val); + return obj; + fail: + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue JS_ReadMap(BCReaderState *s); +static JSValue JS_ReadSet(BCReaderState *s); + +static JSValue JS_ReadObjectRec(BCReaderState *s) +{ + JSContext *ctx = s->ctx; + uint8_t tag; + JSValue obj = JS_UNDEFINED; + + if (js_check_stack_overflow(ctx->rt, 0)) + return JS_ThrowStackOverflow(ctx); + + if (bc_get_u8(s, &tag)) + return JS_EXCEPTION; + + bc_read_trace(s, "%s {\n", bc_tag_str[tag]); + + switch(tag) { + case BC_TAG_NULL: + obj = JS_NULL; + break; + case BC_TAG_UNDEFINED: + obj = JS_UNDEFINED; + break; + case BC_TAG_BOOL_FALSE: + case BC_TAG_BOOL_TRUE: + obj = js_bool(tag - BC_TAG_BOOL_FALSE); + break; + case BC_TAG_INT32: + { + int32_t val; + if (bc_get_sleb128(s, &val)) + return JS_EXCEPTION; + bc_read_trace(s, "%d\n", val); + obj = js_int32(val); + } + break; + case BC_TAG_FLOAT64: + { + JSFloat64Union u; + if (bc_get_u64(s, &u.u64)) + return JS_EXCEPTION; + bc_read_trace(s, "%g\n", u.d); + obj = js_float64(u.d); + } + break; + case BC_TAG_STRING: + { + JSString *p; + p = JS_ReadString(s); + if (!p) + return JS_EXCEPTION; + obj = JS_MKPTR(JS_TAG_STRING, p); + } + break; + case BC_TAG_FUNCTION_BYTECODE: + if (!s->allow_bytecode) + goto no_allow_bytecode; + obj = JS_ReadFunctionTag(s); + break; + case BC_TAG_MODULE: + if (!s->allow_bytecode) { + no_allow_bytecode: + return JS_ThrowSyntaxError(ctx, "no bytecode allowed"); + } + obj = JS_ReadModule(s); + break; + case BC_TAG_OBJECT: + obj = JS_ReadObjectTag(s); + break; + case BC_TAG_ARRAY: + case BC_TAG_TEMPLATE_OBJECT: + obj = JS_ReadArray(s, tag); + break; + case BC_TAG_TYPED_ARRAY: + obj = JS_ReadTypedArray(s); + break; + case BC_TAG_ARRAY_BUFFER: + obj = JS_ReadArrayBuffer(s); + break; + case BC_TAG_SHARED_ARRAY_BUFFER: + if (!s->allow_sab || !ctx->rt->sab_funcs.sab_dup) + goto invalid_tag; + obj = JS_ReadSharedArrayBuffer(s); + break; + case BC_TAG_REGEXP: + obj = JS_ReadRegExp(s); + break; + case BC_TAG_DATE: + obj = JS_ReadDate(s); + break; + case BC_TAG_OBJECT_VALUE: + obj = JS_ReadObjectValue(s); + break; + case BC_TAG_BIG_INT: + obj = JS_ReadBigInt(s); + break; + case BC_TAG_OBJECT_REFERENCE: + { + uint32_t val; + if (!s->allow_reference) + return JS_ThrowSyntaxError(ctx, "object references are not allowed"); + if (bc_get_leb128(s, &val)) + return JS_EXCEPTION; + bc_read_trace(s, "%u\n", val); + if (val >= s->objects_count) { + return JS_ThrowSyntaxError(ctx, "invalid object reference (%u >= %u)", + val, s->objects_count); + } + obj = js_dup(JS_MKPTR(JS_TAG_OBJECT, s->objects[val])); + } + break; + case BC_TAG_MAP: + obj = JS_ReadMap(s); + break; + case BC_TAG_SET: + obj = JS_ReadSet(s); + break; + case BC_TAG_SYMBOL: + { + JSAtom atom; + if (bc_get_atom(s, &atom)) + return JS_EXCEPTION; + if (__JS_AtomIsConst(atom)) { + obj = JS_AtomToValue(s->ctx, atom); + } else { + JSAtomStruct *p = s->ctx->rt->atom_array[atom]; + obj = JS_NewSymbolFromAtom(s->ctx, atom, p->atom_type); + } + } + break; + default: + invalid_tag: + return JS_ThrowSyntaxError(ctx, "invalid tag (tag=%d pos=%u)", + tag, (unsigned int)(s->ptr - s->buf_start)); + } + bc_read_trace(s, "}\n"); + return obj; +} + +static int JS_ReadObjectAtoms(BCReaderState *s) +{ + uint8_t v8, type; + JSString *p; + int i; + JSAtom atom; + + if (bc_get_u8(s, &v8)) + return -1; + if (v8 != BC_VERSION) { + JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)", + v8, BC_VERSION); + return -1; + } + if (bc_get_leb128(s, &s->idx_to_atom_count)) + return -1; + if (s->idx_to_atom_count > 1000*1000) { + JS_ThrowInternalError(s->ctx, "unreasonable atom count: %u", + s->idx_to_atom_count); + return -1; + } + + bc_read_trace(s, "%u atom indexes {\n", s->idx_to_atom_count); + + if (s->idx_to_atom_count != 0) { + s->idx_to_atom = js_mallocz(s->ctx, s->idx_to_atom_count * + sizeof(s->idx_to_atom[0])); + if (!s->idx_to_atom) + return s->error_state = -1; + } + for(i = 0; i < s->idx_to_atom_count; i++) { + if (bc_get_u8(s, &type)) { + return -1; + } + if (type == 0) { + if (bc_get_u32(s, &atom)) + return -1; + if (!__JS_AtomIsConst(atom)) { + JS_ThrowInternalError(s->ctx, "out of range atom"); + return -1; + } + } else { + if (type < JS_ATOM_TYPE_STRING || type >= JS_ATOM_TYPE_PRIVATE) { + JS_ThrowInternalError(s->ctx, "invalid symbol type %d", type); + return -1; + } + p = JS_ReadString(s); + if (!p) + return -1; + atom = __JS_NewAtom(s->ctx->rt, p, type); + } + if (atom == JS_ATOM_NULL) + return s->error_state = -1; + s->idx_to_atom[i] = atom; + } + bc_read_trace(s, "}\n"); + return 0; +} + +static void bc_reader_free(BCReaderState *s) +{ + int i; + if (s->idx_to_atom) { + for(i = 0; i < s->idx_to_atom_count; i++) { + JS_FreeAtom(s->ctx, s->idx_to_atom[i]); + } + js_free(s->ctx, s->idx_to_atom); + } + js_free(s->ctx, s->objects); +} + +JSValue JS_ReadObject2(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int flags, JSSABTab *psab_tab) +{ + BCReaderState ss, *s = &ss; + JSValue obj; + + ctx->binary_object_count += 1; + ctx->binary_object_size += buf_len; + + memset(s, 0, sizeof(*s)); + s->ctx = ctx; + s->buf_start = buf; + s->buf_end = buf + buf_len; + s->ptr = buf; + s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0); + s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0); + s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0); + if (s->allow_bytecode) + s->first_atom = JS_ATOM_END; + else + s->first_atom = 1; + if (JS_ReadObjectAtoms(s)) { + obj = JS_EXCEPTION; + } else { + obj = JS_ReadObjectRec(s); + } + if (psab_tab) { + psab_tab->tab = s->sab_tab; + psab_tab->len = s->sab_tab_len; + } else { + js_free(ctx, s->sab_tab); + } + bc_reader_free(s); + return obj; +} + +JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int flags) +{ + return JS_ReadObject2(ctx, buf, buf_len, flags, NULL); +} + +/*******************************************************************/ +/* runtime functions & objects */ + +static JSValue js_string_constructor(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv); +static JSValue js_boolean_constructor(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv); +static JSValue js_number_constructor(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv); + +static int check_function(JSContext *ctx, JSValue obj) +{ + if (likely(JS_IsFunction(ctx, obj))) + return 0; + JS_ThrowTypeError(ctx, "not a function"); + return -1; +} + +static int check_exception_free(JSContext *ctx, JSValue obj) +{ + JS_FreeValue(ctx, obj); + return JS_IsException(obj); +} + +/* `export_name` may be pure ASCII or UTF-8 encoded */ +static JSAtom find_atom(JSContext *ctx, const char *name) +{ + JSAtom atom; + int len; + + if (*name == '[') { + name++; + len = strlen(name) - 1; + /* We assume 8 bit non null strings, which is the case for these + symbols */ + for(atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) { + JSAtomStruct *p = ctx->rt->atom_array[atom]; + JSString *str = p; + if (str->len == len && !memcmp(str->u.str8, name, len)) + return JS_DupAtom(ctx, atom); + } + abort(); + } else { + atom = JS_NewAtom(ctx, name); + } + return atom; +} + +static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, + JSAtom atom, void *opaque) +{ + const JSCFunctionListEntry *e = opaque; + JSValue val; + + switch(e->def_type) { + case JS_DEF_CFUNC: + val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic, + e->name, e->u.func.length, e->u.func.cproto, e->magic); + break; + case JS_DEF_PROP_STRING: + val = JS_NewAtomString(ctx, e->u.str); + break; + case JS_DEF_OBJECT: + val = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len); + break; + default: + abort(); + } + return val; +} + +static int JS_InstantiateFunctionListItem(JSContext *ctx, JSValue obj, + JSAtom atom, + const JSCFunctionListEntry *e) +{ + JSValue val; + int prop_flags = e->prop_flags; + + switch(e->def_type) { + case JS_DEF_ALIAS: /* using autoinit for aliases is not safe */ + { + JSAtom atom1 = find_atom(ctx, e->u.alias.name); + switch (e->u.alias.base) { + case -1: + val = JS_GetProperty(ctx, obj, atom1); + break; + case 0: + val = JS_GetProperty(ctx, ctx->global_obj, atom1); + break; + case 1: + val = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], atom1); + break; + default: + abort(); + } + JS_FreeAtom(ctx, atom1); + if (atom == JS_ATOM_Symbol_toPrimitive) { + /* Symbol.toPrimitive functions are not writable */ + prop_flags = JS_PROP_CONFIGURABLE; + } else if (atom == JS_ATOM_Symbol_hasInstance) { + /* Function.prototype[Symbol.hasInstance] is not writable nor configurable */ + prop_flags = 0; + } + } + break; + case JS_DEF_CFUNC: + if (atom == JS_ATOM_Symbol_toPrimitive) { + /* Symbol.toPrimitive functions are not writable */ + prop_flags = JS_PROP_CONFIGURABLE; + } else if (atom == JS_ATOM_Symbol_hasInstance) { + /* Function.prototype[Symbol.hasInstance] is not writable nor configurable */ + prop_flags = 0; + } + JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP, + (void *)e, prop_flags); + return 0; + case JS_DEF_CGETSET: /* XXX: use autoinit again ? */ + case JS_DEF_CGETSET_MAGIC: + { + JSValue getter, setter; + char buf[64]; + + getter = JS_UNDEFINED; + if (e->u.getset.get.generic) { + snprintf(buf, sizeof(buf), "get %s", e->name); + getter = JS_NewCFunction2(ctx, e->u.getset.get.generic, + buf, 0, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_getter_magic : JS_CFUNC_getter, + e->magic); + } + setter = JS_UNDEFINED; + if (e->u.getset.set.generic) { + snprintf(buf, sizeof(buf), "set %s", e->name); + setter = JS_NewCFunction2(ctx, e->u.getset.set.generic, + buf, 1, e->def_type == JS_DEF_CGETSET_MAGIC ? JS_CFUNC_setter_magic : JS_CFUNC_setter, + e->magic); + } + JS_DefinePropertyGetSet(ctx, obj, atom, getter, setter, prop_flags); + return 0; + } + break; + case JS_DEF_PROP_INT32: + val = js_int32(e->u.i32); + break; + case JS_DEF_PROP_INT64: + val = js_int64(e->u.i64); + break; + case JS_DEF_PROP_DOUBLE: + val = js_float64(e->u.f64); + break; + case JS_DEF_PROP_UNDEFINED: + val = JS_UNDEFINED; + break; + case JS_DEF_PROP_STRING: + case JS_DEF_OBJECT: + JS_DefineAutoInitProperty(ctx, obj, atom, JS_AUTOINIT_ID_PROP, + (void *)e, prop_flags); + return 0; + default: + abort(); + } + JS_DefinePropertyValue(ctx, obj, atom, val, prop_flags); + return 0; +} + +void JS_SetPropertyFunctionList(JSContext *ctx, JSValue obj, + const JSCFunctionListEntry *tab, int len) +{ + int i; + + for (i = 0; i < len; i++) { + const JSCFunctionListEntry *e = &tab[i]; + JSAtom atom = find_atom(ctx, e->name); + JS_InstantiateFunctionListItem(ctx, obj, atom, e); + JS_FreeAtom(ctx, atom); + } +} + +int JS_AddModuleExportList(JSContext *ctx, JSModuleDef *m, + const JSCFunctionListEntry *tab, int len) +{ + int i; + for(i = 0; i < len; i++) { + if (JS_AddModuleExport(ctx, m, tab[i].name)) + return -1; + } + return 0; +} + +int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, + const JSCFunctionListEntry *tab, int len) +{ + int i; + JSValue val; + + for(i = 0; i < len; i++) { + const JSCFunctionListEntry *e = &tab[i]; + switch(e->def_type) { + case JS_DEF_CFUNC: + val = JS_NewCFunction2(ctx, e->u.func.cfunc.generic, + e->name, e->u.func.length, e->u.func.cproto, e->magic); + break; + case JS_DEF_PROP_STRING: + /* `e->u.str` may be pure ASCII or UTF-8 encoded */ + val = JS_NewString(ctx, e->u.str); + break; + case JS_DEF_PROP_INT32: + val = js_int32(e->u.i32); + break; + case JS_DEF_PROP_INT64: + val = js_int64(e->u.i64); + break; + case JS_DEF_PROP_DOUBLE: + val = js_float64(e->u.f64); + break; + case JS_DEF_OBJECT: + val = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, val, e->u.prop_list.tab, e->u.prop_list.len); + break; + default: + abort(); + } + if (JS_SetModuleExport(ctx, m, e->name, val)) + return -1; + } + return 0; +} + +/* Note: 'func_obj' is not necessarily a constructor */ +static void JS_SetConstructor2(JSContext *ctx, + JSValue func_obj, + JSValue proto, + int proto_flags, int ctor_flags) +{ + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_prototype, + js_dup(proto), proto_flags); + JS_DefinePropertyValue(ctx, proto, JS_ATOM_constructor, + js_dup(func_obj), ctor_flags); + set_cycle_flag(ctx, func_obj); + set_cycle_flag(ctx, proto); +} + +void JS_SetConstructor(JSContext *ctx, JSValue func_obj, + JSValue proto) +{ + JS_SetConstructor2(ctx, func_obj, proto, + 0, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); +} + +static void JS_NewGlobalCConstructor2(JSContext *ctx, + JSValue func_obj, + const char *name, + JSValue proto) +{ + JS_DefinePropertyValueStr(ctx, ctx->global_obj, name, + js_dup(func_obj), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_SetConstructor(ctx, func_obj, proto); + JS_FreeValue(ctx, func_obj); +} + +static JSValue JS_NewGlobalCConstructor(JSContext *ctx, const char *name, + JSCFunction *func, int length, + JSValue proto) +{ + JSValue func_obj; + func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor_or_func, 0); + JS_NewGlobalCConstructor2(ctx, func_obj, name, proto); + return func_obj; +} + +static JSValue JS_NewGlobalCConstructorOnly(JSContext *ctx, const char *name, + JSCFunction *func, int length, + JSValue proto) +{ + JSValue func_obj; + func_obj = JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_constructor, 0); + JS_NewGlobalCConstructor2(ctx, func_obj, name, proto); + return func_obj; +} + +static JSValue js_global_eval(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_EvalObject(ctx, ctx->global_obj, argv[0], JS_EVAL_TYPE_INDIRECT, -1); +} + +static JSValue js_global_isNaN(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + double d; + + if (unlikely(JS_ToFloat64(ctx, &d, argv[0]))) + return JS_EXCEPTION; + return js_bool(isnan(d)); +} + +static JSValue js_global_isFinite(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + BOOL res; + double d; + if (unlikely(JS_ToFloat64(ctx, &d, argv[0]))) + return JS_EXCEPTION; + res = isfinite(d); + return js_bool(res); +} + +static JSValue js_microtask_job(JSContext *ctx, + int argc, JSValue *argv) +{ + return JS_Call(ctx, argv[0], ctx->global_obj, 0, NULL); +} + +static JSValue js_global_queueMicrotask(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + if (check_function(ctx, argv[0])) + return JS_EXCEPTION; + if (JS_EnqueueJob(ctx, js_microtask_job, 1, &argv[0])) + return JS_EXCEPTION; + return JS_UNDEFINED; +} + +/* Object class */ + +static JSValue JS_ToObject(JSContext *ctx, JSValue val) +{ + int tag = JS_VALUE_GET_NORM_TAG(val); + JSValue obj; + + switch(tag) { + default: + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return JS_ThrowTypeError(ctx, "Cannot convert undefined or null to object"); + case JS_TAG_OBJECT: + case JS_TAG_EXCEPTION: + return js_dup(val); + case JS_TAG_BIG_INT: + obj = JS_NewObjectClass(ctx, JS_CLASS_BIG_INT); + goto set_value; + case JS_TAG_INT: + case JS_TAG_FLOAT64: + obj = JS_NewObjectClass(ctx, JS_CLASS_NUMBER); + goto set_value; + case JS_TAG_STRING: + /* XXX: should call the string constructor */ + { + JSString *p1 = JS_VALUE_GET_STRING(val); + obj = JS_NewObjectClass(ctx, JS_CLASS_STRING); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, js_int32(p1->len), 0); + } + goto set_value; + case JS_TAG_BOOL: + obj = JS_NewObjectClass(ctx, JS_CLASS_BOOLEAN); + goto set_value; + case JS_TAG_SYMBOL: + obj = JS_NewObjectClass(ctx, JS_CLASS_SYMBOL); + set_value: + if (!JS_IsException(obj)) + JS_SetObjectData(ctx, obj, js_dup(val)); + return obj; + } +} + +static JSValue JS_ToObjectFree(JSContext *ctx, JSValue val) +{ + JSValue obj = JS_ToObject(ctx, val); + JS_FreeValue(ctx, val); + return obj; +} + +static int js_obj_to_desc(JSContext *ctx, JSPropertyDescriptor *d, + JSValue desc) +{ + JSValue val, getter, setter; + int present; + int flags; + + if (!JS_IsObject(desc)) { + JS_ThrowTypeError(ctx, "Property description must be an object"); + return -1; + } + flags = 0; + val = JS_UNDEFINED; + getter = JS_UNDEFINED; + setter = JS_UNDEFINED; + present = JS_HasProperty(ctx, desc, JS_ATOM_enumerable); + if (present < 0) + goto fail; + if (present) { + JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_enumerable); + if (JS_IsException(prop)) + goto fail; + flags |= JS_PROP_HAS_ENUMERABLE; + if (JS_ToBoolFree(ctx, prop)) + flags |= JS_PROP_ENUMERABLE; + } + present = JS_HasProperty(ctx, desc, JS_ATOM_configurable); + if (present < 0) + goto fail; + if (present) { + JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_configurable); + if (JS_IsException(prop)) + goto fail; + flags |= JS_PROP_HAS_CONFIGURABLE; + if (JS_ToBoolFree(ctx, prop)) + flags |= JS_PROP_CONFIGURABLE; + } + present = JS_HasProperty(ctx, desc, JS_ATOM_value); + if (present < 0) + goto fail; + if (present) { + flags |= JS_PROP_HAS_VALUE; + val = JS_GetProperty(ctx, desc, JS_ATOM_value); + if (JS_IsException(val)) + goto fail; + } + present = JS_HasProperty(ctx, desc, JS_ATOM_writable); + if (present < 0) + goto fail; + if (present) { + JSValue prop = JS_GetProperty(ctx, desc, JS_ATOM_writable); + if (JS_IsException(prop)) + goto fail; + flags |= JS_PROP_HAS_WRITABLE; + if (JS_ToBoolFree(ctx, prop)) + flags |= JS_PROP_WRITABLE; + } + present = JS_HasProperty(ctx, desc, JS_ATOM_get); + if (present < 0) + goto fail; + if (present) { + flags |= JS_PROP_HAS_GET; + getter = JS_GetProperty(ctx, desc, JS_ATOM_get); + if (JS_IsException(getter) || + !(JS_IsUndefined(getter) || JS_IsFunction(ctx, getter))) { + JS_ThrowTypeError(ctx, "Getter must be a function"); + goto fail; + } + } + present = JS_HasProperty(ctx, desc, JS_ATOM_set); + if (present < 0) + goto fail; + if (present) { + flags |= JS_PROP_HAS_SET; + setter = JS_GetProperty(ctx, desc, JS_ATOM_set); + if (JS_IsException(setter) || + !(JS_IsUndefined(setter) || JS_IsFunction(ctx, setter))) { + JS_ThrowTypeError(ctx, "Setter must be a function"); + goto fail; + } + } + if ((flags & (JS_PROP_HAS_SET | JS_PROP_HAS_GET)) && + (flags & (JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE))) { + JS_ThrowTypeError(ctx, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"); + goto fail; + } + d->flags = flags; + d->value = val; + d->getter = getter; + d->setter = setter; + return 0; + fail: + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, getter); + JS_FreeValue(ctx, setter); + return -1; +} + +static __exception int JS_DefinePropertyDesc(JSContext *ctx, JSValue obj, + JSAtom prop, JSValue desc, + int flags) +{ + JSPropertyDescriptor d; + int ret; + + if (js_obj_to_desc(ctx, &d, desc) < 0) + return -1; + + ret = JS_DefineProperty(ctx, obj, prop, + d.value, d.getter, d.setter, d.flags | flags); + js_free_desc(ctx, &d); + return ret; +} + +static __exception int JS_ObjectDefineProperties(JSContext *ctx, + JSValue obj, + JSValue properties) +{ + JSValue props, desc; + JSObject *p; + JSPropertyEnum *atoms; + uint32_t len, i; + int ret = -1; + + if (!JS_IsObject(obj)) { + JS_ThrowTypeError(ctx, "Object.defineProperties called on non-object"); + return -1; + } + desc = JS_UNDEFINED; + props = JS_ToObject(ctx, properties); + if (JS_IsException(props)) + return -1; + p = JS_VALUE_GET_OBJ(props); + if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK) < 0) + goto exception; + // XXX: ECMA specifies that all descriptions should be validated before + // modifying the object. This would require allocating an array + // JSPropertyDescriptor and use 2 separate loops. + for(i = 0; i < len; i++) { + JS_FreeValue(ctx, desc); + desc = JS_GetProperty(ctx, props, atoms[i].atom); + if (JS_IsException(desc)) + goto exception; + if (JS_DefinePropertyDesc(ctx, obj, atoms[i].atom, desc, + JS_PROP_THROW | JS_PROP_DEFINE_PROPERTY) < 0) + goto exception; + } + ret = 0; + +exception: + js_free_prop_enum(ctx, atoms, len); + JS_FreeValue(ctx, props); + JS_FreeValue(ctx, desc); + return ret; +} + +static JSValue js_object_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue ret; + if (!JS_IsUndefined(new_target) && + JS_VALUE_GET_OBJ(new_target) != + JS_VALUE_GET_OBJ(JS_GetActiveFunction(ctx))) { + ret = js_create_from_ctor(ctx, new_target, JS_CLASS_OBJECT); + } else { + int tag = JS_VALUE_GET_NORM_TAG(argv[0]); + switch(tag) { + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + ret = JS_NewObject(ctx); + break; + default: + ret = JS_ToObject(ctx, argv[0]); + break; + } + } + return ret; +} + +static JSValue js_object_create(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue proto, props; + JSValue obj; + + proto = argv[0]; + if (!JS_IsObject(proto) && !JS_IsNull(proto)) + return JS_ThrowTypeError(ctx, "object prototype may only be an Object or null"); + obj = JS_NewObjectProto(ctx, proto); + if (JS_IsException(obj)) + return JS_EXCEPTION; + props = argv[1]; + if (!JS_IsUndefined(props)) { + if (JS_ObjectDefineProperties(ctx, obj, props)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + } + return obj; +} + +static JSValue js_object_getPrototypeOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue val; + + val = argv[0]; + if (JS_VALUE_GET_TAG(val) != JS_TAG_OBJECT) { + /* ES6 feature non compatible with ES5.1: primitive types are + accepted */ + if (magic || JS_VALUE_GET_TAG(val) == JS_TAG_NULL || + JS_VALUE_GET_TAG(val) == JS_TAG_UNDEFINED) + return JS_ThrowTypeErrorNotAnObject(ctx); + } + return JS_GetPrototype(ctx, val); +} + +static JSValue js_object_setPrototypeOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + obj = argv[0]; + if (JS_SetPrototypeInternal(ctx, obj, argv[1], TRUE) < 0) + return JS_EXCEPTION; + return js_dup(obj); +} + +/* magic = 1 if called as Reflect.defineProperty */ +static JSValue js_object_defineProperty(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue obj, prop, desc; + int ret, flags; + JSAtom atom; + + obj = argv[0]; + prop = argv[1]; + desc = argv[2]; + + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + atom = JS_ValueToAtom(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + flags = JS_PROP_THROW | JS_PROP_DEFINE_PROPERTY; + if (magic) + flags = JS_PROP_REFLECT_DEFINE_PROPERTY; + ret = JS_DefinePropertyDesc(ctx, obj, atom, desc, flags); + JS_FreeAtom(ctx, atom); + if (ret < 0) { + return JS_EXCEPTION; + } else if (magic) { + return js_bool(ret); + } else { + return js_dup(obj); + } +} + +static JSValue js_object_defineProperties(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // defineProperties(obj, properties) + JSValue obj = argv[0]; + + if (JS_ObjectDefineProperties(ctx, obj, argv[1])) + return JS_EXCEPTION; + else + return js_dup(obj); +} + +/* magic = 1 if called as __defineSetter__ */ +static JSValue js_object___defineGetter__(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue obj; + JSValue prop, value, get, set; + int ret, flags; + JSAtom atom; + + prop = argv[0]; + value = argv[1]; + + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + return JS_EXCEPTION; + + if (check_function(ctx, value)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + atom = JS_ValueToAtom(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + flags = JS_PROP_THROW | + JS_PROP_HAS_ENUMERABLE | JS_PROP_ENUMERABLE | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE; + if (magic) { + get = JS_UNDEFINED; + set = value; + flags |= JS_PROP_HAS_SET; + } else { + get = value; + set = JS_UNDEFINED; + flags |= JS_PROP_HAS_GET; + } + ret = JS_DefineProperty(ctx, obj, atom, JS_UNDEFINED, get, set, flags); + JS_FreeValue(ctx, obj); + JS_FreeAtom(ctx, atom); + if (ret < 0) { + return JS_EXCEPTION; + } else { + return JS_UNDEFINED; + } +} + +static JSValue js_object_getOwnPropertyDescriptor(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue prop; + JSAtom atom; + JSValue ret, obj; + JSPropertyDescriptor desc; + int res, flags; + + if (magic) { + /* Reflect.getOwnPropertyDescriptor case */ + if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + obj = js_dup(argv[0]); + } else { + obj = JS_ToObject(ctx, argv[0]); + if (JS_IsException(obj)) + return obj; + } + prop = argv[1]; + atom = JS_ValueToAtom(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + goto exception; + ret = JS_UNDEFINED; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), atom); + if (res < 0) + goto exception; + if (res) { + ret = JS_NewObject(ctx); + if (JS_IsException(ret)) + goto exception1; + flags = JS_PROP_C_W_E | JS_PROP_THROW; + if (desc.flags & JS_PROP_GETSET) { + if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, js_dup(desc.getter), flags) < 0 + || JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, js_dup(desc.setter), flags) < 0) + goto exception1; + } else { + if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, js_dup(desc.value), flags) < 0 + || JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, + js_bool(desc.flags & JS_PROP_WRITABLE), + flags) < 0) + goto exception1; + } + if (JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, + js_bool(desc.flags & JS_PROP_ENUMERABLE), + flags) < 0 + || JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, + js_bool(desc.flags & JS_PROP_CONFIGURABLE), + flags) < 0) + goto exception1; + js_free_desc(ctx, &desc); + } + } + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, obj); + return ret; + +exception1: + js_free_desc(ctx, &desc); + JS_FreeValue(ctx, ret); +exception: + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_object_getOwnPropertyDescriptors(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + //getOwnPropertyDescriptors(obj) + JSValue obj, r; + JSObject *p; + JSPropertyEnum *props; + uint32_t len, i; + + r = JS_UNDEFINED; + obj = JS_ToObject(ctx, argv[0]); + if (JS_IsException(obj)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_OBJ(obj); + if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK)) + goto exception; + r = JS_NewObject(ctx); + if (JS_IsException(r)) + goto exception; + for(i = 0; i < len; i++) { + JSValue atomValue, desc; + JSValue args[2]; + + atomValue = JS_AtomToValue(ctx, props[i].atom); + if (JS_IsException(atomValue)) + goto exception; + args[0] = obj; + args[1] = atomValue; + desc = js_object_getOwnPropertyDescriptor(ctx, JS_UNDEFINED, 2, args, 0); + JS_FreeValue(ctx, atomValue); + if (JS_IsException(desc)) + goto exception; + if (!JS_IsUndefined(desc)) { + if (JS_DefinePropertyValue(ctx, r, props[i].atom, desc, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + } + js_free_prop_enum(ctx, props, len); + JS_FreeValue(ctx, obj); + return r; + +exception: + js_free_prop_enum(ctx, props, len); + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, r); + return JS_EXCEPTION; +} + +static JSValue JS_GetOwnPropertyNames2(JSContext *ctx, JSValue obj1, + int flags, int kind) +{ + JSValue obj, r, val, key, value; + JSObject *p; + JSPropertyEnum *atoms; + uint32_t len, i, j; + + r = JS_UNDEFINED; + val = JS_UNDEFINED; + obj = JS_ToObject(ctx, obj1); + if (JS_IsException(obj)) + return JS_EXCEPTION; + p = JS_VALUE_GET_OBJ(obj); + if (JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, p, flags & ~JS_GPN_ENUM_ONLY)) + goto exception; + r = JS_NewArray(ctx); + if (JS_IsException(r)) + goto exception; + for(j = i = 0; i < len; i++) { + JSAtom atom = atoms[i].atom; + if (flags & JS_GPN_ENUM_ONLY) { + JSPropertyDescriptor desc; + int res; + + /* Check if property is still enumerable */ + res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); + if (res < 0) + goto exception; + if (!res) + continue; + js_free_desc(ctx, &desc); + if (!(desc.flags & JS_PROP_ENUMERABLE)) + continue; + } + switch(kind) { + default: + case JS_ITERATOR_KIND_KEY: + val = JS_AtomToValue(ctx, atom); + if (JS_IsException(val)) + goto exception; + break; + case JS_ITERATOR_KIND_VALUE: + val = JS_GetProperty(ctx, obj, atom); + if (JS_IsException(val)) + goto exception; + break; + case JS_ITERATOR_KIND_KEY_AND_VALUE: + val = JS_NewArray(ctx); + if (JS_IsException(val)) + goto exception; + key = JS_AtomToValue(ctx, atom); + if (JS_IsException(key)) + goto exception1; + if (JS_CreateDataPropertyUint32(ctx, val, 0, key, JS_PROP_THROW) < 0) + goto exception1; + value = JS_GetProperty(ctx, obj, atom); + if (JS_IsException(value)) + goto exception1; + if (JS_CreateDataPropertyUint32(ctx, val, 1, value, JS_PROP_THROW) < 0) + goto exception1; + break; + } + if (JS_CreateDataPropertyUint32(ctx, r, j++, val, 0) < 0) + goto exception; + } + goto done; + +exception1: + JS_FreeValue(ctx, val); +exception: + JS_FreeValue(ctx, r); + r = JS_EXCEPTION; +done: + js_free_prop_enum(ctx, atoms, len); + JS_FreeValue(ctx, obj); + return r; +} + +static JSValue js_object_getOwnPropertyNames(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_GetOwnPropertyNames2(ctx, argv[0], + JS_GPN_STRING_MASK, JS_ITERATOR_KIND_KEY); +} + +static JSValue js_object_getOwnPropertySymbols(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_GetOwnPropertyNames2(ctx, argv[0], + JS_GPN_SYMBOL_MASK, JS_ITERATOR_KIND_KEY); +} + +static JSValue js_object_groupBy(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue cb, res, iter, next, groups, k, v, prop; + JSValue args[2]; + int64_t idx; + BOOL done; + + // "is function?" check must be observed before argv[0] is accessed + cb = argv[1]; + if (check_function(ctx, cb)) + return JS_EXCEPTION; + + // TODO(bnoordhuis) add fast path for arrays but as groupBy() is + // defined in terms of iterators, the fast path must check that + // this[Symbol.iterator] is the built-in array iterator + iter = JS_GetIterator(ctx, argv[0], /*is_async*/FALSE); + if (JS_IsException(iter)) + return JS_EXCEPTION; + + k = JS_UNDEFINED; + v = JS_UNDEFINED; + prop = JS_UNDEFINED; + groups = JS_UNDEFINED; + + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + + groups = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(groups)) + goto exception; + + for (idx = 0; ; idx++) { + v = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(v)) + goto exception; + if (done) + break; // v is JS_UNDEFINED + + args[0] = v; + args[1] = js_int64(idx); + k = JS_Call(ctx, cb, ctx->global_obj, 2, args); + if (JS_IsException(k)) + goto exception; + + k = js_dup(k); + prop = JS_GetPropertyValue(ctx, groups, k); + if (JS_IsException(prop)) + goto exception; + + if (JS_IsUndefined(prop)) { + prop = JS_NewArray(ctx); + if (JS_IsException(prop)) + goto exception; + k = js_dup(k); + prop = js_dup(prop); + if (JS_SetPropertyValue(ctx, groups, k, prop, + JS_PROP_C_W_E|JS_PROP_THROW) < 0) { + goto exception; + } + } + + res = js_array_push(ctx, prop, 1, &v, /*unshift*/0); + if (JS_IsException(res)) + goto exception; + // res is an int64 + + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, k); + JS_FreeValue(ctx, v); + prop = JS_UNDEFINED; + k = JS_UNDEFINED; + v = JS_UNDEFINED; + } + + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return groups; + +exception: + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, k); + JS_FreeValue(ctx, v); + JS_FreeValue(ctx, groups); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return JS_EXCEPTION; +} + +static JSValue js_object_keys(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int kind) +{ + return JS_GetOwnPropertyNames2(ctx, argv[0], + JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK, kind); +} + +static JSValue js_object_isExtensible(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int reflect) +{ + JSValue obj; + int ret; + + obj = argv[0]; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + if (reflect) + return JS_ThrowTypeErrorNotAnObject(ctx); + else + return JS_FALSE; + } + ret = JS_IsExtensible(ctx, obj); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_object_preventExtensions(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int reflect) +{ + JSValue obj; + int ret; + + obj = argv[0]; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) { + if (reflect) + return JS_ThrowTypeErrorNotAnObject(ctx); + else + return js_dup(obj); + } + ret = JS_PreventExtensions(ctx, obj); + if (ret < 0) + return JS_EXCEPTION; + if (reflect) { + return js_bool(ret); + } else { + if (!ret) + return JS_ThrowTypeError(ctx, "proxy preventExtensions handler returned false"); + return js_dup(obj); + } +} + +static JSValue js_object_hasOwnProperty(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSAtom atom; + JSObject *p; + BOOL ret; + + atom = JS_ValueToAtom(ctx, argv[0]); /* must be done first */ + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) { + JS_FreeAtom(ctx, atom); + return obj; + } + p = JS_VALUE_GET_OBJ(obj); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, obj); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_object_hasOwn(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSAtom atom; + JSObject *p; + BOOL ret; + + obj = JS_ToObject(ctx, argv[0]); + if (JS_IsException(obj)) + return obj; + atom = JS_ValueToAtom(ctx, argv[1]); + if (unlikely(atom == JS_ATOM_NULL)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + p = JS_VALUE_GET_OBJ(obj); + ret = JS_GetOwnPropertyInternal(ctx, NULL, p, atom); + JS_FreeAtom(ctx, atom); + JS_FreeValue(ctx, obj); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_object_valueOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_ToObject(ctx, this_val); +} + +static JSValue js_object_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, tag; + int is_array; + JSAtom atom; + JSObject *p; + + if (JS_IsNull(this_val)) { + tag = js_new_string8(ctx, "Null"); + } else if (JS_IsUndefined(this_val)) { + tag = js_new_string8(ctx, "Undefined"); + } else { + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + return obj; + is_array = JS_IsArray(ctx, obj); + if (is_array < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + if (is_array) { + atom = JS_ATOM_Array; + } else if (JS_IsFunction(ctx, obj)) { + atom = JS_ATOM_Function; + } else { + p = JS_VALUE_GET_OBJ(obj); + switch(p->class_id) { + case JS_CLASS_STRING: + case JS_CLASS_ARGUMENTS: + case JS_CLASS_MAPPED_ARGUMENTS: + case JS_CLASS_ERROR: + case JS_CLASS_BOOLEAN: + case JS_CLASS_NUMBER: + case JS_CLASS_DATE: + case JS_CLASS_REGEXP: + atom = ctx->rt->class_array[p->class_id].class_name; + break; + default: + atom = JS_ATOM_Object; + break; + } + } + tag = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_toStringTag); + JS_FreeValue(ctx, obj); + if (JS_IsException(tag)) + return JS_EXCEPTION; + if (!JS_IsString(tag)) { + JS_FreeValue(ctx, tag); + tag = JS_AtomToString(ctx, atom); + } + } + return JS_ConcatString3(ctx, "[object ", tag, "]"); +} + +static JSValue js_object_toLocaleString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_Invoke(ctx, this_val, JS_ATOM_toString, 0, NULL); +} + +static JSValue js_object_assign(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // Object.assign(obj, source1) + JSValue obj, s; + int i; + + s = JS_UNDEFINED; + obj = JS_ToObject(ctx, argv[0]); + if (JS_IsException(obj)) + goto exception; + for (i = 1; i < argc; i++) { + if (!JS_IsNull(argv[i]) && !JS_IsUndefined(argv[i])) { + s = JS_ToObject(ctx, argv[i]); + if (JS_IsException(s)) + goto exception; + if (JS_CopyDataProperties(ctx, obj, s, JS_UNDEFINED, TRUE)) + goto exception; + JS_FreeValue(ctx, s); + } + } + return obj; +exception: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, s); + return JS_EXCEPTION; +} + +static JSValue js_object_seal(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int freeze_flag) +{ + JSValue obj = argv[0]; + JSObject *p; + JSPropertyEnum *props; + uint32_t len, i; + int flags, desc_flags, res; + + if (!JS_IsObject(obj)) + return js_dup(obj); + + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_MODULE_NS) { + return JS_ThrowTypeError(ctx, "cannot %s module namespace", + freeze_flag ? "freeze" : "seal"); + } + + res = JS_PreventExtensions(ctx, obj); + if (res < 0) + return JS_EXCEPTION; + if (!res) { + return JS_ThrowTypeError(ctx, "proxy preventExtensions handler returned false"); + } + + flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK; + if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags)) + return JS_EXCEPTION; + + for(i = 0; i < len; i++) { + JSPropertyDescriptor desc; + JSAtom prop = props[i].atom; + + desc_flags = JS_PROP_THROW | JS_PROP_HAS_CONFIGURABLE; + if (freeze_flag) { + res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (res < 0) + goto exception; + if (res) { + if (desc.flags & JS_PROP_WRITABLE) + desc_flags |= JS_PROP_HAS_WRITABLE; + js_free_desc(ctx, &desc); + } + } + if (JS_DefineProperty(ctx, obj, prop, JS_UNDEFINED, + JS_UNDEFINED, JS_UNDEFINED, desc_flags) < 0) + goto exception; + } + js_free_prop_enum(ctx, props, len); + return js_dup(obj); + + exception: + js_free_prop_enum(ctx, props, len); + return JS_EXCEPTION; +} + +static JSValue js_object_isSealed(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int is_frozen) +{ + JSValue obj = argv[0]; + JSObject *p; + JSPropertyEnum *props; + uint32_t len, i; + int flags, res; + + if (!JS_IsObject(obj)) + return JS_TRUE; + + p = JS_VALUE_GET_OBJ(obj); + flags = JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK; + if (JS_GetOwnPropertyNamesInternal(ctx, &props, &len, p, flags)) + return JS_EXCEPTION; + + for(i = 0; i < len; i++) { + JSPropertyDescriptor desc; + JSAtom prop = props[i].atom; + + res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (res < 0) + goto exception; + if (res) { + js_free_desc(ctx, &desc); + if ((desc.flags & JS_PROP_CONFIGURABLE) + || (is_frozen && (desc.flags & JS_PROP_WRITABLE))) { + res = FALSE; + goto done; + } + } + } + res = JS_IsExtensible(ctx, obj); + if (res < 0) + return JS_EXCEPTION; + res ^= 1; +done: + js_free_prop_enum(ctx, props, len); + return js_bool(res); + +exception: + js_free_prop_enum(ctx, props, len); + return JS_EXCEPTION; +} + +static JSValue js_object_fromEntries(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, iter, next_method = JS_UNDEFINED; + JSValue iterable; + BOOL done; + + /* RequireObjectCoercible() not necessary because it is tested in + JS_GetIterator() by JS_GetProperty() */ + iterable = argv[0]; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return obj; + + iter = JS_GetIterator(ctx, iterable, FALSE); + if (JS_IsException(iter)) + goto fail; + next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next_method)) + goto fail; + + for(;;) { + JSValue key, value, item; + item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) { + JS_FreeValue(ctx, item); + break; + } + + key = JS_UNDEFINED; + value = JS_UNDEFINED; + if (!JS_IsObject(item)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail1; + } + key = JS_GetPropertyUint32(ctx, item, 0); + if (JS_IsException(key)) + goto fail1; + value = JS_GetPropertyUint32(ctx, item, 1); + if (JS_IsException(value)) { + JS_FreeValue(ctx, key); + goto fail1; + } + if (JS_DefinePropertyValueValue(ctx, obj, key, value, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) { + fail1: + JS_FreeValue(ctx, item); + goto fail; + } + JS_FreeValue(ctx, item); + } + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + return obj; + fail: + if (JS_IsObject(iter)) { + /* close the iterator object, preserving pending exception */ + JS_IteratorClose(ctx, iter, TRUE); + } + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_object_is(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_bool(js_same_value(ctx, argv[0], argv[1])); +} + +static JSValue JS_SpeciesConstructor(JSContext *ctx, JSValue obj, + JSValue defaultConstructor) +{ + JSValue ctor, species; + + if (!JS_IsObject(obj)) + return JS_ThrowTypeErrorNotAnObject(ctx); + ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor); + if (JS_IsException(ctor)) + return ctor; + if (JS_IsUndefined(ctor)) + return js_dup(defaultConstructor); + if (!JS_IsObject(ctor)) { + JS_FreeValue(ctx, ctor); + return JS_ThrowTypeErrorNotAnObject(ctx); + } + species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species); + JS_FreeValue(ctx, ctor); + if (JS_IsException(species)) + return species; + if (JS_IsUndefined(species) || JS_IsNull(species)) + return js_dup(defaultConstructor); + if (!JS_IsConstructor(ctx, species)) { + JS_FreeValue(ctx, species); + return JS_ThrowTypeError(ctx, "not a constructor"); + } + return species; +} + +static JSValue js_object_get___proto__(JSContext *ctx, JSValue this_val) +{ + JSValue val, ret; + + val = JS_ToObject(ctx, this_val); + if (JS_IsException(val)) + return val; + ret = JS_GetPrototype(ctx, val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue js_object_set___proto__(JSContext *ctx, JSValue this_val, + JSValue proto) +{ + if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + if (!JS_IsObject(proto) && !JS_IsNull(proto)) + return JS_UNDEFINED; + if (JS_SetPrototypeInternal(ctx, this_val, proto, TRUE) < 0) + return JS_EXCEPTION; + else + return JS_UNDEFINED; +} + +static JSValue js_object_isPrototypeOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, v1; + JSValue v; + int res; + + v = argv[0]; + if (!JS_IsObject(v)) + return JS_FALSE; + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + return JS_EXCEPTION; + v1 = js_dup(v); + for(;;) { + v1 = JS_GetPrototypeFree(ctx, v1); + if (JS_IsException(v1)) + goto exception; + if (JS_IsNull(v1)) { + res = FALSE; + break; + } + if (JS_VALUE_GET_OBJ(obj) == JS_VALUE_GET_OBJ(v1)) { + res = TRUE; + break; + } + /* avoid infinite loop (possible with proxies) */ + if (js_poll_interrupts(ctx)) + goto exception; + } + JS_FreeValue(ctx, v1); + JS_FreeValue(ctx, obj); + return js_bool(res); + +exception: + JS_FreeValue(ctx, v1); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_object_propertyIsEnumerable(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, res = JS_EXCEPTION; + JSAtom prop = JS_ATOM_NULL; + JSPropertyDescriptor desc; + int has_prop; + + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + goto exception; + prop = JS_ValueToAtom(ctx, argv[0]); + if (unlikely(prop == JS_ATOM_NULL)) + goto exception; + + has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop); + if (has_prop < 0) + goto exception; + if (has_prop) { + res = js_bool(desc.flags & JS_PROP_ENUMERABLE); + js_free_desc(ctx, &desc); + } else { + res = JS_FALSE; + } + +exception: + JS_FreeAtom(ctx, prop); + JS_FreeValue(ctx, obj); + return res; +} + +static JSValue js_object___lookupGetter__(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int setter) +{ + JSValue obj, res = JS_EXCEPTION; + JSAtom prop = JS_ATOM_NULL; + JSPropertyDescriptor desc; + int has_prop; + + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + goto exception; + prop = JS_ValueToAtom(ctx, argv[0]); + if (unlikely(prop == JS_ATOM_NULL)) + goto exception; + + for (;;) { + has_prop = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(obj), prop); + if (has_prop < 0) + goto exception; + if (has_prop) { + if (desc.flags & JS_PROP_GETSET) + res = js_dup(setter ? desc.setter : desc.getter); + else + res = JS_UNDEFINED; + js_free_desc(ctx, &desc); + break; + } + obj = JS_GetPrototypeFree(ctx, obj); + if (JS_IsException(obj)) + goto exception; + if (JS_IsNull(obj)) { + res = JS_UNDEFINED; + break; + } + /* avoid infinite loop (possible with proxies) */ + if (js_poll_interrupts(ctx)) + goto exception; + } + +exception: + JS_FreeAtom(ctx, prop); + JS_FreeValue(ctx, obj); + return res; +} + +static const JSCFunctionListEntry js_object_funcs[] = { + JS_CFUNC_DEF("create", 2, js_object_create ), + JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 0 ), + JS_CFUNC_DEF("setPrototypeOf", 2, js_object_setPrototypeOf ), + JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 0 ), + JS_CFUNC_DEF("defineProperties", 2, js_object_defineProperties ), + JS_CFUNC_DEF("getOwnPropertyNames", 1, js_object_getOwnPropertyNames ), + JS_CFUNC_DEF("getOwnPropertySymbols", 1, js_object_getOwnPropertySymbols ), + JS_CFUNC_DEF("groupBy", 2, js_object_groupBy ), + JS_CFUNC_MAGIC_DEF("keys", 1, js_object_keys, JS_ITERATOR_KIND_KEY ), + JS_CFUNC_MAGIC_DEF("values", 1, js_object_keys, JS_ITERATOR_KIND_VALUE ), + JS_CFUNC_MAGIC_DEF("entries", 1, js_object_keys, JS_ITERATOR_KIND_KEY_AND_VALUE ), + JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 0 ), + JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 0 ), + JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 0 ), + JS_CFUNC_DEF("getOwnPropertyDescriptors", 1, js_object_getOwnPropertyDescriptors ), + JS_CFUNC_DEF("is", 2, js_object_is ), + JS_CFUNC_DEF("assign", 2, js_object_assign ), + JS_CFUNC_MAGIC_DEF("seal", 1, js_object_seal, 0 ), + JS_CFUNC_MAGIC_DEF("freeze", 1, js_object_seal, 1 ), + JS_CFUNC_MAGIC_DEF("isSealed", 1, js_object_isSealed, 0 ), + JS_CFUNC_MAGIC_DEF("isFrozen", 1, js_object_isSealed, 1 ), + JS_CFUNC_DEF("fromEntries", 1, js_object_fromEntries ), + JS_CFUNC_DEF("hasOwn", 2, js_object_hasOwn ), +}; + +static const JSCFunctionListEntry js_object_proto_funcs[] = { + JS_CFUNC_DEF("toString", 0, js_object_toString ), + JS_CFUNC_DEF("toLocaleString", 0, js_object_toLocaleString ), + JS_CFUNC_DEF("valueOf", 0, js_object_valueOf ), + JS_CFUNC_DEF("hasOwnProperty", 1, js_object_hasOwnProperty ), + JS_CFUNC_DEF("isPrototypeOf", 1, js_object_isPrototypeOf ), + JS_CFUNC_DEF("propertyIsEnumerable", 1, js_object_propertyIsEnumerable ), + JS_CGETSET_DEF("__proto__", js_object_get___proto__, js_object_set___proto__ ), + JS_CFUNC_MAGIC_DEF("__defineGetter__", 2, js_object___defineGetter__, 0 ), + JS_CFUNC_MAGIC_DEF("__defineSetter__", 2, js_object___defineGetter__, 1 ), + JS_CFUNC_MAGIC_DEF("__lookupGetter__", 1, js_object___lookupGetter__, 0 ), + JS_CFUNC_MAGIC_DEF("__lookupSetter__", 1, js_object___lookupGetter__, 1 ), +}; + +/* Function class */ + +static JSValue js_function_proto(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return JS_UNDEFINED; +} + +/* XXX: add a specific eval mode so that Function("}), ({") is rejected */ +static JSValue js_function_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv, int magic) +{ + JSFunctionKindEnum func_kind = magic; + int i, n, ret; + JSValue s, proto, obj = JS_UNDEFINED; + StringBuffer b_s, *b = &b_s; + + string_buffer_init(ctx, b, 0); + string_buffer_putc8(b, '('); + + if (func_kind == JS_FUNC_ASYNC || func_kind == JS_FUNC_ASYNC_GENERATOR) { + string_buffer_puts8(b, "async "); + } + string_buffer_puts8(b, "function"); + + if (func_kind == JS_FUNC_GENERATOR || func_kind == JS_FUNC_ASYNC_GENERATOR) { + string_buffer_putc8(b, '*'); + } + string_buffer_puts8(b, " anonymous("); + + n = argc - 1; + for(i = 0; i < n; i++) { + if (i != 0) { + string_buffer_putc8(b, ','); + } + if (string_buffer_concat_value(b, argv[i])) + goto fail; + } + string_buffer_puts8(b, "\n) {\n"); + if (n >= 0) { + if (string_buffer_concat_value(b, argv[n])) + goto fail; + } + string_buffer_puts8(b, "\n})"); + s = string_buffer_end(b); + if (JS_IsException(s)) + goto fail1; + + obj = JS_EvalObject(ctx, ctx->global_obj, s, JS_EVAL_TYPE_INDIRECT, -1); + JS_FreeValue(ctx, s); + if (JS_IsException(obj)) + goto fail1; + if (!JS_IsUndefined(new_target)) { + /* set the prototype */ + proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype); + if (JS_IsException(proto)) + goto fail1; + if (!JS_IsObject(proto)) { + JSContext *realm; + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, new_target); + if (!realm) + goto fail1; + proto = js_dup(realm->class_proto[func_kind_to_class_id[func_kind]]); + } + ret = JS_SetPrototypeInternal(ctx, obj, proto, TRUE); + JS_FreeValue(ctx, proto); + if (ret < 0) + goto fail1; + } + return obj; + + fail: + string_buffer_free(b); + fail1: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static __exception int js_get_length32(JSContext *ctx, uint32_t *pres, + JSValue obj) +{ + JSValue len_val; + len_val = JS_GetProperty(ctx, obj, JS_ATOM_length); + if (JS_IsException(len_val)) { + *pres = 0; + return -1; + } + return JS_ToUint32Free(ctx, pres, len_val); +} + +static __exception int js_get_length64(JSContext *ctx, int64_t *pres, + JSValue obj) +{ + JSValue len_val; + len_val = JS_GetProperty(ctx, obj, JS_ATOM_length); + if (JS_IsException(len_val)) { + *pres = 0; + return -1; + } + return JS_ToLengthFree(ctx, pres, len_val); +} + +static __exception int js_set_length64(JSContext *ctx, JSValue obj, int64_t len) +{ + return JS_SetProperty(ctx, obj, JS_ATOM_length, js_int64(len)); +} + +static void free_arg_list(JSContext *ctx, JSValue *tab, uint32_t len) +{ + uint32_t i; + for(i = 0; i < len; i++) { + JS_FreeValue(ctx, tab[i]); + } + js_free(ctx, tab); +} + +/* XXX: should use ValueArray */ +static JSValue *build_arg_list(JSContext *ctx, uint32_t *plen, + JSValue array_arg) +{ + uint32_t len, i; + JSValue *tab, ret; + JSObject *p; + + if (JS_VALUE_GET_TAG(array_arg) != JS_TAG_OBJECT) { + JS_ThrowTypeError(ctx, "not a object"); + return NULL; + } + if (js_get_length32(ctx, &len, array_arg)) + return NULL; + if (len > JS_MAX_LOCAL_VARS) { + // XXX: check for stack overflow? + JS_ThrowRangeError(ctx, "too many arguments in function call (only %d allowed)", + JS_MAX_LOCAL_VARS); + return NULL; + } + /* avoid allocating 0 bytes */ + tab = js_mallocz(ctx, sizeof(tab[0]) * max_uint32(1, len)); + if (!tab) + return NULL; + p = JS_VALUE_GET_OBJ(array_arg); + if ((p->class_id == JS_CLASS_ARRAY || p->class_id == JS_CLASS_ARGUMENTS) && + p->fast_array && + len == p->u.array.count) { + for(i = 0; i < len; i++) { + tab[i] = js_dup(p->u.array.u.values[i]); + } + } else { + for(i = 0; i < len; i++) { + ret = JS_GetPropertyUint32(ctx, array_arg, i); + if (JS_IsException(ret)) { + free_arg_list(ctx, tab, i); + return NULL; + } + tab[i] = ret; + } + } + *plen = len; + return tab; +} + +/* magic value: 0 = normal apply, 1 = apply for constructor, 2 = + Reflect.apply */ +static JSValue js_function_apply(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue this_arg, array_arg; + uint32_t len; + JSValue *tab, ret; + + if (check_function(ctx, this_val)) + return JS_EXCEPTION; + this_arg = argv[0]; + array_arg = argv[1]; + if ((JS_VALUE_GET_TAG(array_arg) == JS_TAG_UNDEFINED || + JS_VALUE_GET_TAG(array_arg) == JS_TAG_NULL) && magic != 2) { + return JS_Call(ctx, this_val, this_arg, 0, NULL); + } + tab = build_arg_list(ctx, &len, array_arg); + if (!tab) + return JS_EXCEPTION; + if (magic & 1) { + ret = JS_CallConstructor2(ctx, this_val, this_arg, len, tab); + } else { + ret = JS_Call(ctx, this_val, this_arg, len, tab); + } + free_arg_list(ctx, tab, len); + return ret; +} + +static JSValue js_function_call(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + if (argc <= 0) { + return JS_Call(ctx, this_val, JS_UNDEFINED, 0, NULL); + } else { + return JS_Call(ctx, this_val, argv[0], argc - 1, argv + 1); + } +} + +static JSValue js_function_bind(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSBoundFunction *bf; + JSValue func_obj, name1, len_val; + JSObject *p; + int arg_count, i, ret; + + if (check_function(ctx, this_val)) + return JS_EXCEPTION; + + func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_BOUND_FUNCTION); + if (JS_IsException(func_obj)) + return JS_EXCEPTION; + p = JS_VALUE_GET_OBJ(func_obj); + p->is_constructor = JS_IsConstructor(ctx, this_val); + arg_count = max_int(0, argc - 1); + bf = js_malloc(ctx, sizeof(*bf) + arg_count * sizeof(JSValue)); + if (!bf) + goto exception; + bf->func_obj = js_dup(this_val); + bf->this_val = js_dup(argv[0]); + bf->argc = arg_count; + for(i = 0; i < arg_count; i++) { + bf->argv[i] = js_dup(argv[i + 1]); + } + p->u.bound_function = bf; + + /* XXX: the spec could be simpler by only using GetOwnProperty */ + ret = JS_GetOwnProperty(ctx, NULL, this_val, JS_ATOM_length); + if (ret < 0) + goto exception; + if (!ret) { + len_val = js_int32(0); + } else { + len_val = JS_GetProperty(ctx, this_val, JS_ATOM_length); + if (JS_IsException(len_val)) + goto exception; + if (JS_VALUE_GET_TAG(len_val) == JS_TAG_INT) { + /* most common case */ + int len1 = JS_VALUE_GET_INT(len_val); + if (len1 <= arg_count) + len1 = 0; + else + len1 -= arg_count; + len_val = js_int32(len1); + } else if (JS_VALUE_GET_NORM_TAG(len_val) == JS_TAG_FLOAT64) { + double d = JS_VALUE_GET_FLOAT64(len_val); + if (isnan(d)) { + d = 0.0; + } else { + d = trunc(d); + if (d <= (double)arg_count) + d = 0.0; + else + d -= (double)arg_count; /* also converts -0 to +0 */ + } + len_val = js_number(d); + } else { + JS_FreeValue(ctx, len_val); + len_val = js_int32(0); + } + } + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_length, + len_val, JS_PROP_CONFIGURABLE); + + name1 = JS_GetProperty(ctx, this_val, JS_ATOM_name); + if (JS_IsException(name1)) + goto exception; + if (!JS_IsString(name1)) { + JS_FreeValue(ctx, name1); + name1 = JS_AtomToString(ctx, JS_ATOM_empty_string); + } + name1 = JS_ConcatString3(ctx, "bound ", name1, ""); + if (JS_IsException(name1)) + goto exception; + JS_DefinePropertyValue(ctx, func_obj, JS_ATOM_name, name1, + JS_PROP_CONFIGURABLE); + return func_obj; + exception: + JS_FreeValue(ctx, func_obj); + return JS_EXCEPTION; +} + +static JSValue js_function_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + JSFunctionKindEnum func_kind = JS_FUNC_NORMAL; + + if (check_function(ctx, this_val)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_OBJ(this_val); + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + /* `b->source` must be pure ASCII or UTF-8 encoded */ + if (b->source) + return JS_NewStringLen(ctx, b->source, b->source_len); + } + { + JSValue name; + const char *pref, *suff; + + switch(func_kind) { + default: + case JS_FUNC_NORMAL: + pref = "function "; + break; + case JS_FUNC_GENERATOR: + pref = "function *"; + break; + case JS_FUNC_ASYNC: + pref = "async function "; + break; + case JS_FUNC_ASYNC_GENERATOR: + pref = "async function *"; + break; + } + suff = "() {\n [native code]\n}"; + name = JS_GetProperty(ctx, this_val, JS_ATOM_name); + if (JS_IsUndefined(name)) + name = JS_AtomToString(ctx, JS_ATOM_empty_string); + return JS_ConcatString3(ctx, pref, name, suff); + } +} + +static JSValue js_function_hasInstance(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + int ret; + ret = JS_OrdinaryIsInstanceOf(ctx, argv[0], this_val); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static const JSCFunctionListEntry js_function_proto_funcs[] = { + JS_CFUNC_DEF("call", 1, js_function_call ), + JS_CFUNC_MAGIC_DEF("apply", 2, js_function_apply, 0 ), + JS_CFUNC_DEF("bind", 1, js_function_bind ), + JS_CFUNC_DEF("toString", 0, js_function_toString ), + JS_CFUNC_DEF("[Symbol.hasInstance]", 1, js_function_hasInstance ), + JS_CGETSET_DEF("fileName", js_function_proto_fileName, NULL ), + JS_CGETSET_MAGIC_DEF("lineNumber", js_function_proto_int32, NULL, + offsetof(JSFunctionBytecode, line_num)), + JS_CGETSET_MAGIC_DEF("columnNumber", js_function_proto_int32, NULL, + offsetof(JSFunctionBytecode, col_num)), +}; + +/* Error class */ + +static JSValue iterator_to_array(JSContext *ctx, JSValue items) +{ + JSValue iter, next_method = JS_UNDEFINED; + JSValue v, r = JS_UNDEFINED; + int64_t k; + BOOL done; + + iter = JS_GetIterator(ctx, items, FALSE); + if (JS_IsException(iter)) + goto exception; + next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next_method)) + goto exception; + r = JS_NewArray(ctx); + if (JS_IsException(r)) + goto exception; + for (k = 0;; k++) { + v = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); + if (JS_IsException(v)) + goto exception_close; + if (done) + break; + if (JS_DefinePropertyValueInt64(ctx, r, k, v, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception_close; + } + done: + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + return r; + exception_close: + JS_IteratorClose(ctx, iter, TRUE); + exception: + JS_FreeValue(ctx, r); + r = JS_EXCEPTION; + goto done; +} + +static JSValue js_error_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv, int magic) +{ + JSValue obj, msg, proto, cause; + JSValue message; + int opts; + BOOL present; + + if (JS_IsUndefined(new_target)) + new_target = JS_GetActiveFunction(ctx); + proto = JS_GetProperty(ctx, new_target, JS_ATOM_prototype); + if (JS_IsException(proto)) + return proto; + if (!JS_IsObject(proto)) { + JSContext *realm; + JSValue proto1; + + JS_FreeValue(ctx, proto); + realm = JS_GetFunctionRealm(ctx, new_target); + if (!realm) + return JS_EXCEPTION; + if (magic < 0) { + proto1 = realm->class_proto[JS_CLASS_ERROR]; + } else { + proto1 = realm->native_error_proto[magic]; + } + proto = js_dup(proto1); + } + obj = JS_NewObjectProtoClass(ctx, proto, JS_CLASS_ERROR); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) + return obj; + if (magic == JS_AGGREGATE_ERROR) { + message = argv[1]; + opts = 2; + } else { + message = argv[0]; + opts = 1; + } + + if (!JS_IsUndefined(message)) { + msg = JS_ToString(ctx, message); + if (unlikely(JS_IsException(msg))) + goto exception; + JS_DefinePropertyValue(ctx, obj, JS_ATOM_message, msg, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + + if (argc > opts && JS_VALUE_GET_TAG(argv[opts]) == JS_TAG_OBJECT) { + present = JS_HasProperty(ctx, argv[opts], JS_ATOM_cause); + if (unlikely(present < 0)) + goto exception; + if (present) { + cause = JS_GetProperty(ctx, argv[opts], JS_ATOM_cause); + if (unlikely(JS_IsException(cause))) + goto exception; + JS_DefinePropertyValue(ctx, obj, JS_ATOM_cause, cause, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + } + + if (magic == JS_AGGREGATE_ERROR) { + JSValue error_list = iterator_to_array(ctx, argv[0]); + if (JS_IsException(error_list)) + goto exception; + JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, error_list, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + } + + /* skip the Error() function in the backtrace */ + build_backtrace(ctx, obj, JS_UNDEFINED, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); + return obj; + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_error_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue name, msg; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + name = JS_GetProperty(ctx, this_val, JS_ATOM_name); + if (JS_IsUndefined(name)) + name = JS_AtomToString(ctx, JS_ATOM_Error); + else + name = JS_ToStringFree(ctx, name); + if (JS_IsException(name)) + return JS_EXCEPTION; + + msg = JS_GetProperty(ctx, this_val, JS_ATOM_message); + if (JS_IsUndefined(msg)) + msg = JS_AtomToString(ctx, JS_ATOM_empty_string); + else + msg = JS_ToStringFree(ctx, msg); + if (JS_IsException(msg)) { + JS_FreeValue(ctx, name); + return JS_EXCEPTION; + } + if (!JS_IsEmptyString(name) && !JS_IsEmptyString(msg)) + name = JS_ConcatString3(ctx, "", name, ": "); + return JS_ConcatString(ctx, name, msg); +} + +static const JSCFunctionListEntry js_error_proto_funcs[] = { + JS_CFUNC_DEF("toString", 0, js_error_toString ), + JS_PROP_STRING_DEF("name", "Error", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), + JS_PROP_STRING_DEF("message", "", JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), +}; + +static JSValue js_error_isError(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_bool(JS_IsError(ctx, argv[0])); +} + +static JSValue js_error_get_stackTraceLimit(JSContext *ctx, JSValue this_val) +{ + JSValue val; + + val = JS_ToObject(ctx, this_val); + if (JS_IsException(val)) + return val; + JS_FreeValue(ctx, val); + return js_int32(ctx->error_stack_trace_limit); +} + +static JSValue js_error_set_stackTraceLimit(JSContext *ctx, JSValue this_val, JSValue value) +{ + if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + int limit; + if (JS_ToInt32(ctx, &limit, value) < 0) + return JS_EXCEPTION; + ctx->error_stack_trace_limit = limit; + return JS_UNDEFINED; +} + +static JSValue js_error_get_prepareStackTrace(JSContext *ctx, JSValue this_val) +{ + JSValue val; + + val = JS_ToObject(ctx, this_val); + if (JS_IsException(val)) + return val; + JS_FreeValue(ctx, val); + return js_dup(ctx->error_prepare_stack); +} + +static JSValue js_error_set_prepareStackTrace(JSContext *ctx, JSValue this_val, JSValue value) +{ + if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + JS_FreeValue(ctx, ctx->error_prepare_stack); + ctx->error_prepare_stack = js_dup(value); + return JS_UNDEFINED; +} + +static JSValue js_error_capture_stack_trace(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue v = argv[0]; + if (JS_VALUE_GET_TAG(v) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + build_backtrace(ctx, v, argv[1], NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL|JS_BACKTRACE_FLAG_FILTER_FUNC); + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry js_error_funcs[] = { + JS_CFUNC_DEF("isError", 1, js_error_isError ), + JS_CFUNC_DEF("captureStackTrace", 2, js_error_capture_stack_trace), + JS_CGETSET_DEF("stackTraceLimit", js_error_get_stackTraceLimit, js_error_set_stackTraceLimit ), + JS_CGETSET_DEF("prepareStackTrace", js_error_get_prepareStackTrace, js_error_set_prepareStackTrace ), +}; + +/* AggregateError */ + +/* used by C code. */ +static JSValue js_aggregate_error_constructor(JSContext *ctx, + JSValue errors) +{ + JSValue obj; + + obj = JS_NewObjectProtoClass(ctx, + ctx->native_error_proto[JS_AGGREGATE_ERROR], + JS_CLASS_ERROR); + if (JS_IsException(obj)) + return obj; + JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, js_dup(errors), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + return obj; +} + +/* Array */ + +static int JS_CopySubArray(JSContext *ctx, + JSValue obj, int64_t to_pos, + int64_t from_pos, int64_t count, int dir) +{ + JSObject *p; + int64_t i, from, to, len; + JSValue val; + int fromPresent; + + p = NULL; + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ARRAY || !p->fast_array) { + p = NULL; + } + } + + for (i = 0; i < count; ) { + if (dir < 0) { + from = from_pos + count - i - 1; + to = to_pos + count - i - 1; + } else { + from = from_pos + i; + to = to_pos + i; + } + if (p && p->fast_array && + from >= 0 && from < (len = p->u.array.count) && + to >= 0 && to < len) { + int64_t l, j; + /* Fast path for fast arrays. Since we don't look at the + prototype chain, we can optimize only the cases where + all the elements are present in the array. */ + l = count - i; + if (dir < 0) { + l = min_int64(l, from + 1); + l = min_int64(l, to + 1); + for(j = 0; j < l; j++) { + set_value(ctx, &p->u.array.u.values[to - j], + js_dup(p->u.array.u.values[from - j])); + } + } else { + l = min_int64(l, len - from); + l = min_int64(l, len - to); + for(j = 0; j < l; j++) { + set_value(ctx, &p->u.array.u.values[to + j], + js_dup(p->u.array.u.values[from + j])); + } + } + i += l; + } else { + fromPresent = JS_TryGetPropertyInt64(ctx, obj, from, &val); + if (fromPresent < 0) + goto exception; + + if (fromPresent) { + if (JS_SetPropertyInt64(ctx, obj, to, val) < 0) + goto exception; + } else { + if (JS_DeletePropertyInt64(ctx, obj, to, JS_PROP_THROW) < 0) + goto exception; + } + i++; + } + } + return 0; + + exception: + return -1; +} + +static JSValue js_array_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue obj; + int i; + + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_ARRAY); + if (JS_IsException(obj)) + return obj; + if (argc == 1 && JS_IsNumber(argv[0])) { + uint32_t len; + if (JS_ToArrayLengthFree(ctx, &len, js_dup(argv[0]), TRUE)) + goto fail; + if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_uint32(len)) < 0) + goto fail; + } else { + for(i = 0; i < argc; i++) { + if (JS_SetPropertyUint32(ctx, obj, i, js_dup(argv[i])) < 0) + goto fail; + } + } + return obj; +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_from(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // from(items, mapfn = void 0, this_arg = void 0) + JSValue items = argv[0], mapfn, this_arg; + JSValue args[2]; + JSValue stack[2]; + JSValue iter, r, v, v2, arrayLike; + int64_t k, len; + int done, mapping; + + mapping = FALSE; + mapfn = JS_UNDEFINED; + this_arg = JS_UNDEFINED; + r = JS_UNDEFINED; + arrayLike = JS_UNDEFINED; + stack[0] = JS_UNDEFINED; + stack[1] = JS_UNDEFINED; + + if (argc > 1) { + mapfn = argv[1]; + if (!JS_IsUndefined(mapfn)) { + if (check_function(ctx, mapfn)) + goto exception; + mapping = 1; + if (argc > 2) + this_arg = argv[2]; + } + } + iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); + if (JS_IsException(iter)) + goto exception; + if (!JS_IsUndefined(iter)) { + JS_FreeValue(ctx, iter); + if (JS_IsConstructor(ctx, this_val)) + r = JS_CallConstructor(ctx, this_val, 0, NULL); + else + r = JS_NewArray(ctx); + if (JS_IsException(r)) + goto exception; + stack[0] = js_dup(items); + if (js_for_of_start(ctx, &stack[1], FALSE)) + goto exception; + for (k = 0;; k++) { + v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done); + if (JS_IsException(v)) + goto exception_close; + if (done) + break; + if (mapping) { + args[0] = v; + args[1] = js_int32(k); + v2 = JS_Call(ctx, mapfn, this_arg, 2, args); + JS_FreeValue(ctx, v); + v = v2; + if (JS_IsException(v)) + goto exception_close; + } + if (JS_DefinePropertyValueInt64(ctx, r, k, v, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception_close; + } + } else { + arrayLike = JS_ToObject(ctx, items); + if (JS_IsException(arrayLike)) + goto exception; + if (js_get_length64(ctx, &len, arrayLike) < 0) + goto exception; + v = js_int64(len); + args[0] = v; + if (JS_IsConstructor(ctx, this_val)) { + r = JS_CallConstructor(ctx, this_val, 1, args); + } else { + r = js_array_constructor(ctx, JS_UNDEFINED, 1, args); + } + JS_FreeValue(ctx, v); + if (JS_IsException(r)) + goto exception; + for(k = 0; k < len; k++) { + v = JS_GetPropertyInt64(ctx, arrayLike, k); + if (JS_IsException(v)) + goto exception; + if (mapping) { + args[0] = v; + args[1] = js_int32(k); + v2 = JS_Call(ctx, mapfn, this_arg, 2, args); + JS_FreeValue(ctx, v); + v = v2; + if (JS_IsException(v)) + goto exception; + } + if (JS_DefinePropertyValueInt64(ctx, r, k, v, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + } + if (JS_SetProperty(ctx, r, JS_ATOM_length, js_uint32(k)) < 0) + goto exception; + goto done; + + exception_close: + if (!JS_IsUndefined(stack[0])) + JS_IteratorClose(ctx, stack[0], TRUE); + exception: + JS_FreeValue(ctx, r); + r = JS_EXCEPTION; + done: + JS_FreeValue(ctx, arrayLike); + JS_FreeValue(ctx, stack[0]); + JS_FreeValue(ctx, stack[1]); + return r; +} + +static JSValue js_array_of(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, args[1]; + int i; + + if (JS_IsConstructor(ctx, this_val)) { + args[0] = js_int32(argc); + obj = JS_CallConstructor(ctx, this_val, 1, args); + } else { + obj = JS_NewArray(ctx); + } + if (JS_IsException(obj)) + return JS_EXCEPTION; + for(i = 0; i < argc; i++) { + if (JS_CreateDataPropertyUint32(ctx, obj, i, js_dup(argv[i]), + JS_PROP_THROW) < 0) { + goto fail; + } + } + if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_uint32(argc)) < 0) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static JSValue js_array_isArray(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + int ret; + ret = JS_IsArray(ctx, argv[0]); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_get_this(JSContext *ctx, + JSValue this_val) +{ + return js_dup(this_val); +} + +static JSValue JS_ArraySpeciesCreate(JSContext *ctx, JSValue obj, + JSValue len_val) +{ + JSValue ctor, ret, species; + int res; + JSContext *realm; + + res = JS_IsArray(ctx, obj); + if (res < 0) + return JS_EXCEPTION; + if (!res) + return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val); + ctor = JS_GetProperty(ctx, obj, JS_ATOM_constructor); + if (JS_IsException(ctor)) + return ctor; + if (JS_IsConstructor(ctx, ctor)) { + /* legacy web compatibility */ + realm = JS_GetFunctionRealm(ctx, ctor); + if (!realm) { + JS_FreeValue(ctx, ctor); + return JS_EXCEPTION; + } + if (realm != ctx && + js_same_value(ctx, ctor, realm->array_ctor)) { + JS_FreeValue(ctx, ctor); + ctor = JS_UNDEFINED; + } + } + if (JS_IsObject(ctor)) { + species = JS_GetProperty(ctx, ctor, JS_ATOM_Symbol_species); + JS_FreeValue(ctx, ctor); + if (JS_IsException(species)) + return species; + ctor = species; + if (JS_IsNull(ctor)) + ctor = JS_UNDEFINED; + } + if (JS_IsUndefined(ctor)) { + return js_array_constructor(ctx, JS_UNDEFINED, 1, &len_val); + } else { + ret = JS_CallConstructor(ctx, ctor, 1, &len_val); + JS_FreeValue(ctx, ctor); + return ret; + } +} + +static const JSCFunctionListEntry js_array_funcs[] = { + JS_CFUNC_DEF("isArray", 1, js_array_isArray ), + JS_CFUNC_DEF("from", 1, js_array_from ), + JS_CFUNC_DEF("of", 0, js_array_of ), + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static int JS_isConcatSpreadable(JSContext *ctx, JSValue obj) +{ + JSValue val; + + if (!JS_IsObject(obj)) + return FALSE; + val = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_isConcatSpreadable); + if (JS_IsException(val)) + return -1; + if (!JS_IsUndefined(val)) + return JS_ToBoolFree(ctx, val); + return JS_IsArray(ctx, obj); +} + +static JSValue js_array_at(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, ret; + int64_t len, idx; + + ret = JS_EXCEPTION; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (JS_ToInt64Sat(ctx, &idx, argv[0])) + goto exception; + + if (idx < 0) + idx = len + idx; + + if (idx < 0 || idx >= len) { + ret = JS_UNDEFINED; + } else { + ret = JS_GetPropertyInt64(ctx, obj, idx); + } + + exception: + JS_FreeValue(ctx, obj); + return ret; +} + +static JSValue js_array_with(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, obj, ret, *arrp, *pval; + JSObject *p; + int64_t i, len, idx; + uint32_t count32; + + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (len > UINT32_MAX) { + JS_ThrowRangeError(ctx, "invalid array length"); + goto exception; + } + + if (JS_ToInt64Sat(ctx, &idx, argv[0])) + goto exception; + + if (idx < 0) + idx = len + idx; + + if (idx < 0 || idx >= len) { + JS_ThrowRangeError(ctx, "invalid array index: %" PRId64, idx); + goto exception; + } + + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + goto exception; + + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) + goto exception; + p->u.array.count = len; + + i = 0; + pval = p->u.array.u.values; + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (; i < idx; i++, pval++) + *pval = js_dup(arrp[i]); + *pval = js_dup(argv[1]); + for (i++, pval++; i < len; i++, pval++) + *pval = js_dup(arrp[i]); + } else { + for (; i < idx; i++, pval++) + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) + goto fill_and_fail; + *pval = js_dup(argv[1]); + for (i++, pval++; i < len; i++, pval++) { + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { + fill_and_fail: + for (; i < len; i++, pval++) + *pval = JS_UNDEFINED; + goto exception; + } + } + } + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0) + goto exception; + + ret = arr; + arr = JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + +static JSValue js_array_concat(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, arr, val; + JSValue e; + int64_t len, k, n; + int i, res; + + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + goto exception; + + arr = JS_ArraySpeciesCreate(ctx, obj, js_int32(0)); + if (JS_IsException(arr)) + goto exception; + n = 0; + for (i = -1; i < argc; i++) { + if (i < 0) + e = obj; + else + e = argv[i]; + + res = JS_isConcatSpreadable(ctx, e); + if (res < 0) + goto exception; + if (res) { + if (js_get_length64(ctx, &len, e)) + goto exception; + if (n + len > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array loo long"); + goto exception; + } + for (k = 0; k < len; k++, n++) { + res = JS_TryGetPropertyInt64(ctx, e, k, &val); + if (res < 0) + goto exception; + if (res) { + if (JS_DefinePropertyValueInt64(ctx, arr, n, val, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + } + } else { + if (n >= MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array loo long"); + goto exception; + } + if (JS_DefinePropertyValueInt64(ctx, arr, n, js_dup(e), + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + n++; + } + } + if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(n)) < 0) + goto exception; + + JS_FreeValue(ctx, obj); + return arr; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +#define special_every 0 +#define special_some 1 +#define special_forEach 2 +#define special_map 3 +#define special_filter 4 +#define special_TA 8 + +static JSObject *get_typed_array(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_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; +} + +// Be *very* careful if you touch the typed array's memory directly: +// the length is only valid until the next call into JS land because +// JS code can detach or resize the backing array buffer. Functions +// like JS_GetProperty and JS_ToIndex call JS code. +// +// Exclusively reading or writing elements with JS_GetProperty, +// JS_GetPropertyInt64, JS_SetProperty, etc. is safe because they +// perform bounds checks, as does js_get_fast_array_element. +static int js_typed_array_get_length_unsafe(JSContext *ctx, JSValue obj) +{ + JSObject *p; + p = get_typed_array(ctx, obj); + if (!p) + return -1; + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + return -1; + } + return p->u.array.count; +} + +static JSValue js_typed_array___speciesCreate(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv); + +static JSValue js_array_every(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int special) +{ + JSValue obj, val, index_val, res, ret; + JSValue args[3]; + JSValue func, this_arg; + int64_t len, k, n; + int present; + + ret = JS_UNDEFINED; + val = JS_UNDEFINED; + if (special & special_TA) { + obj = js_dup(this_val); + len = js_typed_array_get_length_unsafe(ctx, obj); + if (len < 0) + goto exception; + } else { + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + } + func = argv[0]; + this_arg = JS_UNDEFINED; + if (argc > 1) + this_arg = argv[1]; + + if (check_function(ctx, func)) + goto exception; + + switch (special) { + case special_every: + case special_every | special_TA: + ret = JS_TRUE; + break; + case special_some: + case special_some | special_TA: + ret = JS_FALSE; + break; + case special_map: + /* XXX: JS_ArraySpeciesCreate should take int64_t */ + ret = JS_ArraySpeciesCreate(ctx, obj, js_int64(len)); + if (JS_IsException(ret)) + goto exception; + break; + case special_filter: + ret = JS_ArraySpeciesCreate(ctx, obj, js_int32(0)); + if (JS_IsException(ret)) + goto exception; + break; + case special_map | special_TA: + args[0] = obj; + args[1] = js_int32(len); + ret = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args); + if (JS_IsException(ret)) + goto exception; + break; + case special_filter | special_TA: + ret = JS_NewArray(ctx); + if (JS_IsException(ret)) + goto exception; + break; + } + n = 0; + + for(k = 0; k < len; k++) { + if (special & special_TA) { + val = JS_GetPropertyInt64(ctx, obj, k); + if (JS_IsException(val)) + goto exception; + present = TRUE; + } else { + present = JS_TryGetPropertyInt64(ctx, obj, k, &val); + if (present < 0) + goto exception; + } + if (present) { + index_val = js_int64(k); + args[0] = val; + args[1] = index_val; + args[2] = obj; + res = JS_Call(ctx, func, this_arg, 3, args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(res)) + goto exception; + switch (special) { + case special_every: + case special_every | special_TA: + if (!JS_ToBoolFree(ctx, res)) { + ret = JS_FALSE; + goto done; + } + break; + case special_some: + case special_some | special_TA: + if (JS_ToBoolFree(ctx, res)) { + ret = JS_TRUE; + goto done; + } + break; + case special_map: + if (JS_DefinePropertyValueInt64(ctx, ret, k, res, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + break; + case special_map | special_TA: + if (JS_SetPropertyValue(ctx, ret, js_int32(k), res, JS_PROP_THROW) < 0) + goto exception; + break; + case special_filter: + case special_filter | special_TA: + if (JS_ToBoolFree(ctx, res)) { + if (JS_DefinePropertyValueInt64(ctx, ret, n++, js_dup(val), + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + break; + default: + JS_FreeValue(ctx, res); + break; + } + JS_FreeValue(ctx, val); + val = JS_UNDEFINED; + } + } +done: + if (special == (special_filter | special_TA)) { + JSValue arr; + args[0] = obj; + args[1] = js_int32(n); + arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args); + if (JS_IsException(arr)) + goto exception; + args[0] = ret; + res = JS_Invoke(ctx, arr, JS_ATOM_set, 1, args); + if (check_exception_free(ctx, res)) + goto exception; + JS_FreeValue(ctx, ret); + ret = arr; + } + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return ret; + +exception: + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +#define special_reduce 0 +#define special_reduceRight 1 + +static JSValue js_array_reduce(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int special) +{ + JSValue obj, val, index_val, acc, acc1; + JSValue args[4]; + JSValue func; + int64_t len, k, k1; + int present; + + acc = JS_UNDEFINED; + val = JS_UNDEFINED; + if (special & special_TA) { + obj = js_dup(this_val); + len = js_typed_array_get_length_unsafe(ctx, obj); + if (len < 0) + goto exception; + } else { + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + } + func = argv[0]; + + if (check_function(ctx, func)) + goto exception; + + k = 0; + if (argc > 1) { + acc = js_dup(argv[1]); + } else { + for(;;) { + if (k >= len) { + JS_ThrowTypeError(ctx, "empty array"); + goto exception; + } + k1 = (special & special_reduceRight) ? len - k - 1 : k; + k++; + if (special & special_TA) { + acc = JS_GetPropertyInt64(ctx, obj, k1); + if (JS_IsException(acc)) + goto exception; + break; + } else { + present = JS_TryGetPropertyInt64(ctx, obj, k1, &acc); + if (present < 0) + goto exception; + if (present) + break; + } + } + } + for (; k < len; k++) { + k1 = (special & special_reduceRight) ? len - k - 1 : k; + if (special & special_TA) { + val = JS_GetPropertyInt64(ctx, obj, k1); + if (JS_IsException(val)) + goto exception; + present = TRUE; + } else { + present = JS_TryGetPropertyInt64(ctx, obj, k1, &val); + if (present < 0) + goto exception; + } + if (present) { + index_val = js_int64(k1); + args[0] = acc; + args[1] = val; + args[2] = index_val; + args[3] = obj; + acc1 = JS_Call(ctx, func, JS_UNDEFINED, 4, args); + JS_FreeValue(ctx, index_val); + JS_FreeValue(ctx, val); + val = JS_UNDEFINED; + if (JS_IsException(acc1)) + goto exception; + JS_FreeValue(ctx, acc); + acc = acc1; + } + } + JS_FreeValue(ctx, obj); + return acc; + +exception: + JS_FreeValue(ctx, acc); + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_fill(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + int64_t len, start, end; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + start = 0; + if (argc > 1 && !JS_IsUndefined(argv[1])) { + if (JS_ToInt64Clamp(ctx, &start, argv[1], 0, len, len)) + goto exception; + } + + end = len; + if (argc > 2 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt64Clamp(ctx, &end, argv[2], 0, len, len)) + goto exception; + } + + /* XXX: should special case fast arrays */ + while (start < end) { + if (JS_SetPropertyInt64(ctx, obj, start, js_dup(argv[0])) < 0) + goto exception; + start++; + } + return obj; + + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_includes(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, val; + int64_t len, n; + JSValue *arrp; + uint32_t count; + int res; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + res = TRUE; + if (len > 0) { + n = 0; + if (argc > 1) { + if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len)) + goto exception; + } + if (js_get_fast_array(ctx, obj, &arrp, &count)) { + for (; n < count; n++) { + if (js_strict_eq2(ctx, js_dup(argv[0]), js_dup(arrp[n]), + JS_EQ_SAME_VALUE_ZERO)) { + goto done; + } + } + } + for (; n < len; n++) { + val = JS_GetPropertyInt64(ctx, obj, n); + if (JS_IsException(val)) + goto exception; + if (js_strict_eq2(ctx, js_dup(argv[0]), val, + JS_EQ_SAME_VALUE_ZERO)) { + goto done; + } + } + } + res = FALSE; + done: + JS_FreeValue(ctx, obj); + return js_bool(res); + + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_indexOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, val; + int64_t len, n; + JSValue *arrp; + uint32_t count; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (len > 0) { + n = 0; + if (argc > 1) { + if (JS_ToInt64Clamp(ctx, &n, argv[1], 0, len, len)) + goto exception; + } + if (js_get_fast_array(ctx, obj, &arrp, &count)) { + for (; n < count; n++) { + if (js_strict_eq2(ctx, js_dup(argv[0]), js_dup(arrp[n]), + JS_EQ_STRICT)) { + goto done; + } + } + } + for (; n < len; n++) { + int present = JS_TryGetPropertyInt64(ctx, obj, n, &val); + if (present < 0) + goto exception; + if (present) { + if (js_strict_eq2(ctx, js_dup(argv[0]), val, JS_EQ_STRICT)) { + goto done; + } + } + } + } + n = -1; + done: + JS_FreeValue(ctx, obj); + return js_int64(n); + + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_lastIndexOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, val; + int64_t len, n; + JSValue *arrp; + uint32_t count; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (len > 0) { + n = len - 1; + if (argc > 1) { + if (JS_ToInt64Clamp(ctx, &n, argv[1], -1, len - 1, len)) + goto exception; + } + if (js_get_fast_array(ctx, obj, &arrp, &count) && count == len) { + for (; n >= 0; n--) { + if (js_strict_eq2(ctx, js_dup(argv[0]), js_dup(arrp[n]), + JS_EQ_STRICT)) { + goto done; + } + } + } + for (; n >= 0; n--) { + int present = JS_TryGetPropertyInt64(ctx, obj, n, &val); + if (present < 0) + goto exception; + if (present) { + if (js_strict_eq2(ctx, js_dup(argv[0]), val, JS_EQ_STRICT)) { + goto done; + } + } + } + } + n = -1; + done: + JS_FreeValue(ctx, obj); + return js_int64(n); + + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +enum { + ArrayFind, + ArrayFindIndex, + ArrayFindLast, + ArrayFindLastIndex, +}; + +static JSValue js_array_find(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int mode) +{ + JSValue func, this_arg; + JSValue args[3]; + JSValue obj, val, index_val, res; + int64_t len, k, end; + int dir; + + index_val = JS_UNDEFINED; + val = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + func = argv[0]; + if (check_function(ctx, func)) + goto exception; + + this_arg = JS_UNDEFINED; + if (argc > 1) + this_arg = argv[1]; + + k = 0; + dir = 1; + end = len; + if (mode == ArrayFindLast || mode == ArrayFindLastIndex) { + k = len - 1; + dir = -1; + end = -1; + } + + // TODO(bnoordhuis) add fast path for fast arrays + for(; k != end; k += dir) { + index_val = js_int64(k); + val = JS_GetPropertyValue(ctx, obj, index_val); + if (JS_IsException(val)) + goto exception; + args[0] = val; + args[1] = index_val; + args[2] = this_val; + res = JS_Call(ctx, func, this_arg, 3, args); + if (JS_IsException(res)) + goto exception; + if (JS_ToBoolFree(ctx, res)) { + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) { + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return index_val; + } else { + JS_FreeValue(ctx, index_val); + JS_FreeValue(ctx, obj); + return val; + } + } + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, index_val); + } + JS_FreeValue(ctx, obj); + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) + return js_int32(-1); + else + return JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, index_val); + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, method, ret; + + obj = JS_ToObject(ctx, this_val); + if (JS_IsException(obj)) + return JS_EXCEPTION; + method = JS_GetProperty(ctx, obj, JS_ATOM_join); + if (JS_IsException(method)) { + ret = JS_EXCEPTION; + } else + if (!JS_IsFunction(ctx, method)) { + /* Use intrinsic Object.prototype.toString */ + JS_FreeValue(ctx, method); + ret = js_object_toString(ctx, obj, 0, NULL); + } else { + ret = JS_CallFree(ctx, method, obj, 0, NULL); + } + JS_FreeValue(ctx, obj); + return ret; +} + +static JSValue js_array_join(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int toLocaleString) +{ + JSValue obj, sep = JS_UNDEFINED, el; + StringBuffer b_s, *b = &b_s; + JSString *p = NULL; + int64_t i, n; + int c; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &n, obj)) + goto exception; + + c = ','; /* default separator */ + if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) { + sep = JS_ToString(ctx, argv[0]); + if (JS_IsException(sep)) + goto exception; + p = JS_VALUE_GET_STRING(sep); + if (p->len == 1 && !p->is_wide_char) + c = p->u.str8[0]; + else + c = -1; + } + string_buffer_init(ctx, b, 0); + + for(i = 0; i < n; i++) { + if (i > 0) { + if (c >= 0) { + string_buffer_putc8(b, c); + } else { + string_buffer_concat(b, p, 0, p->len); + } + } + el = JS_GetPropertyUint32(ctx, obj, i); + if (JS_IsException(el)) + goto fail; + if (!JS_IsNull(el) && !JS_IsUndefined(el)) { + if (toLocaleString) { + el = JS_ToLocaleStringFree(ctx, el); + } + if (string_buffer_concat_value_free(b, el)) + goto fail; + } + } + JS_FreeValue(ctx, sep); + JS_FreeValue(ctx, obj); + return string_buffer_end(b); + +fail: + string_buffer_free(b); + JS_FreeValue(ctx, sep); +exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_pop(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int shift) +{ + JSValue obj, res = JS_UNDEFINED; + int64_t len, newLen; + JSValue *arrp; + uint32_t count32; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + newLen = 0; + if (len > 0) { + newLen = len - 1; + /* Special case fast arrays */ + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (shift) { + res = arrp[0]; + memmove(arrp, arrp + 1, (count32 - 1) * sizeof(*arrp)); + p->u.array.count--; + } else { + res = arrp[count32 - 1]; + p->u.array.count--; + } + } else { + if (shift) { + res = JS_GetPropertyInt64(ctx, obj, 0); + if (JS_IsException(res)) + goto exception; + if (JS_CopySubArray(ctx, obj, 0, 1, len - 1, +1)) + goto exception; + } else { + res = JS_GetPropertyInt64(ctx, obj, newLen); + if (JS_IsException(res)) + goto exception; + } + if (JS_DeletePropertyInt64(ctx, obj, newLen, JS_PROP_THROW) < 0) + goto exception; + } + } + if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_int64(newLen)) < 0) + goto exception; + + JS_FreeValue(ctx, obj); + return res; + + exception: + JS_FreeValue(ctx, res); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_push(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int unshift) +{ + JSValue obj; + int i; + int64_t len, from, newLen; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + newLen = len + argc; + if (newLen > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array loo long"); + goto exception; + } + from = len; + if (unshift && argc > 0) { + if (JS_CopySubArray(ctx, obj, argc, 0, len, -1)) + goto exception; + from = 0; + } + for(i = 0; i < argc; i++) { + if (JS_SetPropertyInt64(ctx, obj, from + i, js_dup(argv[i])) < 0) + goto exception; + } + if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_int64(newLen)) < 0) + goto exception; + + JS_FreeValue(ctx, obj); + return js_int64(newLen); + + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_array_reverse(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, lval, hval; + JSValue *arrp; + int64_t len, l, h; + int l_present, h_present; + uint32_t count32; + + lval = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + /* Special case fast arrays */ + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + uint32_t ll, hh; + + if (count32 > 1) { + for (ll = 0, hh = count32 - 1; ll < hh; ll++, hh--) { + lval = arrp[ll]; + arrp[ll] = arrp[hh]; + arrp[hh] = lval; + } + } + return obj; + } + + for (l = 0, h = len - 1; l < h; l++, h--) { + l_present = JS_TryGetPropertyInt64(ctx, obj, l, &lval); + if (l_present < 0) + goto exception; + h_present = JS_TryGetPropertyInt64(ctx, obj, h, &hval); + if (h_present < 0) + goto exception; + if (h_present) { + if (JS_SetPropertyInt64(ctx, obj, l, hval) < 0) + goto exception; + + if (l_present) { + if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) { + lval = JS_UNDEFINED; + goto exception; + } + lval = JS_UNDEFINED; + } else { + if (JS_DeletePropertyInt64(ctx, obj, h, JS_PROP_THROW) < 0) + goto exception; + } + } else { + if (l_present) { + if (JS_DeletePropertyInt64(ctx, obj, l, JS_PROP_THROW) < 0) + goto exception; + if (JS_SetPropertyInt64(ctx, obj, h, lval) < 0) { + lval = JS_UNDEFINED; + goto exception; + } + lval = JS_UNDEFINED; + } + } + } + return obj; + + exception: + JS_FreeValue(ctx, lval); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +// Note: a.toReversed() is a.slice().reverse() with the twist that a.slice() +// leaves holes in sparse arrays intact whereas a.toReversed() replaces them +// with undefined, thus in effect creating a dense array. +// Does not use Array[@@species], always returns a base Array. +static JSValue js_array_toReversed(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, obj, ret, *arrp, *pval; + JSObject *p; + int64_t i, len; + uint32_t count32; + + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (len > UINT32_MAX) { + JS_ThrowRangeError(ctx, "invalid array length"); + goto exception; + } + + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + goto exception; + + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) + goto exception; + p->u.array.count = len; + + i = len - 1; + pval = p->u.array.u.values; + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (; i >= 0; i--, pval++) + *pval = js_dup(arrp[i]); + } else { + // Query order is observable; test262 expects descending order. + for (; i >= 0; i--, pval++) { + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { + // Exception; initialize remaining elements. + for (; i >= 0; i--, pval++) + *pval = JS_UNDEFINED; + goto exception; + } + } + } + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0) + goto exception; + } + + ret = arr; + arr = JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + +static JSValue js_array_slice(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int splice) +{ + JSValue obj, arr, val, len_val; + int64_t len, start, k, final, n, count, del_count, new_len; + int kPresent; + JSValue *arrp; + uint32_t count32, i, item_count; + + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) + goto exception; + + if (splice) { + 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_ToInt64Clamp(ctx, &del_count, argv[1], 0, len - start, 0)) + goto exception; + } + if (len + item_count - del_count > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array loo long"); + goto exception; + } + count = del_count; + } else { + item_count = 0; /* avoid warning */ + final = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt64Clamp(ctx, &final, argv[1], 0, len, len)) + goto exception; + } + count = max_int64(final - start, 0); + } + len_val = js_int64(count); + arr = JS_ArraySpeciesCreate(ctx, obj, len_val); + JS_FreeValue(ctx, len_val); + if (JS_IsException(arr)) + goto exception; + + k = start; + final = start + count; + n = 0; + /* The fast array test on arr ensures that + JS_CreateDataPropertyUint32() won't modify obj in case arr is + an exotic object */ + /* Special case fast arrays */ + if (js_get_fast_array(ctx, obj, &arrp, &count32) && + js_is_fast_array(ctx, arr)) { + /* XXX: should share code with fast array constructor */ + for (; k < final && k < count32; k++, n++) { + if (JS_CreateDataPropertyUint32(ctx, arr, n, js_dup(arrp[k]), JS_PROP_THROW) < 0) + goto exception; + } + } + /* Copy the remaining elements if any (handle case of inherited properties) */ + for (; k < final; k++, n++) { + kPresent = JS_TryGetPropertyInt64(ctx, obj, k, &val); + if (kPresent < 0) + goto exception; + if (kPresent) { + if (JS_CreateDataPropertyUint32(ctx, arr, n, val, JS_PROP_THROW) < 0) + goto exception; + } + } + if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(n)) < 0) + goto exception; + + if (splice) { + new_len = len + item_count - del_count; + if (item_count != del_count) { + if (JS_CopySubArray(ctx, obj, start + item_count, + start + del_count, len - (start + del_count), + item_count <= del_count ? +1 : -1) < 0) + goto exception; + + for (k = len; k-- > new_len; ) { + if (JS_DeletePropertyInt64(ctx, obj, k, JS_PROP_THROW) < 0) + goto exception; + } + } + for (i = 0; i < item_count; i++) { + if (JS_SetPropertyInt64(ctx, obj, start + i, js_dup(argv[i + 2])) < 0) + goto exception; + } + if (JS_SetProperty(ctx, obj, JS_ATOM_length, js_int64(new_len)) < 0) + goto exception; + } + JS_FreeValue(ctx, obj); + return arr; + + exception: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; +} + +static JSValue js_array_toSpliced(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, obj, ret, *arrp, *pval, *last; + JSObject *p; + int64_t i, j, len, newlen, start, add, del; + uint32_t count32; + + pval = NULL; + last = NULL; + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + start = 0; + if (argc > 0) + if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) + goto exception; + + del = 0; + if (argc > 0) + del = len - start; + if (argc > 1) + if (JS_ToInt64Clamp(ctx, &del, argv[1], 0, del, 0)) + goto exception; + + add = 0; + if (argc > 2) + add = argc - 2; + + newlen = len + add - del; + if (newlen > UINT32_MAX) { + // Per spec: TypeError if newlen >= 2**53, RangeError below + if (newlen > MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "invalid array length"); + } else { + JS_ThrowRangeError(ctx, "invalid array length"); + } + goto exception; + } + + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + goto exception; + + if (newlen <= 0) + goto done; + + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, newlen) < 0) + goto exception; + + p->u.array.count = newlen; + pval = &p->u.array.u.values[0]; + last = &p->u.array.u.values[newlen]; + + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (i = 0; i < start; i++, pval++) + *pval = js_dup(arrp[i]); + for (j = 0; j < add; j++, pval++) + *pval = js_dup(argv[2 + j]); + for (i += del; i < len; i++, pval++) + *pval = js_dup(arrp[i]); + } else { + for (i = 0; i < start; i++, pval++) + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) + goto exception; + for (j = 0; j < add; j++, pval++) + *pval = js_dup(argv[2 + j]); + for (i += del; i < len; i++, pval++) + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) + goto exception; + } + + assert(pval == last); + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(newlen)) < 0) + goto exception; + +done: + ret = arr; + arr = JS_UNDEFINED; + +exception: + while (pval != last) + *pval++ = JS_UNDEFINED; + + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + +static JSValue js_array_copyWithin(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + int64_t len, from, to, final, count; + + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (JS_ToInt64Clamp(ctx, &to, argv[0], 0, len, len)) + goto exception; + + if (JS_ToInt64Clamp(ctx, &from, argv[1], 0, len, len)) + goto exception; + + final = len; + if (argc > 2 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt64Clamp(ctx, &final, argv[2], 0, len, len)) + goto exception; + } + + count = min_int64(final - from, len - to); + + if (JS_CopySubArray(ctx, obj, to, from, count, + (from < to && to < from + count) ? -1 : +1)) + goto exception; + + return obj; + + exception: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static int64_t JS_FlattenIntoArray(JSContext *ctx, JSValue target, + JSValue source, int64_t sourceLen, + int64_t targetIndex, int depth, + JSValue mapperFunction, + JSValue thisArg) +{ + JSValue element; + int64_t sourceIndex, elementLen; + int present, is_array; + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return -1; + } + + for (sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { + present = JS_TryGetPropertyInt64(ctx, source, sourceIndex, &element); + if (present < 0) + return -1; + if (!present) + continue; + if (!JS_IsUndefined(mapperFunction)) { + JSValue args[3] = { element, js_int64(sourceIndex), source }; + element = JS_Call(ctx, mapperFunction, thisArg, 3, args); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + if (JS_IsException(element)) + return -1; + } + if (depth > 0) { + is_array = JS_IsArray(ctx, element); + if (is_array < 0) + goto fail; + if (is_array) { + if (js_get_length64(ctx, &elementLen, element) < 0) + goto fail; + targetIndex = JS_FlattenIntoArray(ctx, target, element, + elementLen, targetIndex, + depth - 1, + JS_UNDEFINED, JS_UNDEFINED); + if (targetIndex < 0) + goto fail; + JS_FreeValue(ctx, element); + continue; + } + } + if (targetIndex >= MAX_SAFE_INTEGER) { + JS_ThrowTypeError(ctx, "Array too long"); + goto fail; + } + if (JS_DefinePropertyValueInt64(ctx, target, targetIndex, element, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + return -1; + targetIndex++; + } + return targetIndex; + +fail: + JS_FreeValue(ctx, element); + return -1; +} + +static JSValue js_array_flatten(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int map) +{ + JSValue obj, arr; + JSValue mapperFunction, thisArg; + int64_t sourceLen; + int depthNum; + + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &sourceLen, obj)) + goto exception; + + depthNum = 1; + mapperFunction = JS_UNDEFINED; + thisArg = JS_UNDEFINED; + if (map) { + mapperFunction = argv[0]; + if (argc > 1) { + thisArg = argv[1]; + } + if (check_function(ctx, mapperFunction)) + goto exception; + } else { + if (argc > 0 && !JS_IsUndefined(argv[0])) { + if (JS_ToInt32Sat(ctx, &depthNum, argv[0]) < 0) + goto exception; + } + } + arr = JS_ArraySpeciesCreate(ctx, obj, js_int32(0)); + if (JS_IsException(arr)) + goto exception; + if (JS_FlattenIntoArray(ctx, arr, obj, sourceLen, 0, depthNum, + mapperFunction, thisArg) < 0) + goto exception; + JS_FreeValue(ctx, obj); + return arr; + +exception: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; +} + +/* Array sort */ + +typedef struct ValueSlot { + JSValue val; + JSString *str; + int64_t pos; +} ValueSlot; + +struct array_sort_context { + JSContext *ctx; + int exception; + int has_method; + JSValue method; +}; + +static int js_array_cmp_generic(const void *a, const void *b, void *opaque) { + struct array_sort_context *psc = opaque; + JSContext *ctx = psc->ctx; + JSValue argv[2]; + JSValue res; + ValueSlot *ap = (ValueSlot *)(void *)a; + ValueSlot *bp = (ValueSlot *)(void *)b; + int cmp; + + if (psc->exception) + return 0; + + if (psc->has_method) { + /* custom sort function is specified as returning 0 for identical + * objects: avoid method call overhead. + */ + if (!memcmp(&ap->val, &bp->val, sizeof(ap->val))) + goto cmp_same; + argv[0] = ap->val; + argv[1] = bp->val; + res = JS_Call(ctx, psc->method, JS_UNDEFINED, 2, argv); + if (JS_IsException(res)) + goto exception; + if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) { + int val = JS_VALUE_GET_INT(res); + cmp = (val > 0) - (val < 0); + } else { + double val; + if (JS_ToFloat64Free(ctx, &val, res) < 0) + goto exception; + cmp = (val > 0) - (val < 0); + } + } else { + /* Not supposed to bypass ToString even for identical objects as + * tested in test262/test/built-ins/Array/prototype/sort/bug_596_1.js + */ + if (!ap->str) { + JSValue str = JS_ToString(ctx, ap->val); + if (JS_IsException(str)) + goto exception; + ap->str = JS_VALUE_GET_STRING(str); + } + if (!bp->str) { + JSValue str = JS_ToString(ctx, bp->val); + if (JS_IsException(str)) + goto exception; + bp->str = JS_VALUE_GET_STRING(str); + } + cmp = js_string_compare(ctx, ap->str, bp->str); + } + if (cmp != 0) + return cmp; +cmp_same: + /* make sort stable: compare array offsets */ + return (ap->pos > bp->pos) - (ap->pos < bp->pos); + +exception: + psc->exception = 1; + return 0; +} + +static JSValue js_array_sort(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + struct array_sort_context asc = { ctx, 0, 0, argv[0] }; + JSValue obj = JS_UNDEFINED; + ValueSlot *array = NULL; + size_t array_size = 0, pos = 0, n = 0; + int64_t i, len, undefined_count = 0; + int present; + + if (!JS_IsUndefined(asc.method)) { + if (check_function(ctx, asc.method)) + goto exception; + asc.has_method = 1; + } + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + /* XXX: should special case fast arrays */ + for (i = 0; i < len; i++) { + if (pos >= array_size) { + size_t new_size, slack; + ValueSlot *new_array; + new_size = (array_size + (array_size >> 1) + 31) & ~15; + new_array = js_realloc2(ctx, array, new_size * sizeof(*array), &slack); + if (new_array == NULL) + goto exception; + new_size += slack / sizeof(*new_array); + array = new_array; + array_size = new_size; + } + present = JS_TryGetPropertyInt64(ctx, obj, i, &array[pos].val); + if (present < 0) + goto exception; + if (present == 0) + continue; + if (JS_IsUndefined(array[pos].val)) { + undefined_count++; + continue; + } + array[pos].str = NULL; + array[pos].pos = i; + pos++; + } + rqsort(array, pos, sizeof(*array), js_array_cmp_generic, &asc); + if (asc.exception) + goto exception; + + /* XXX: should special case fast arrays */ + while (n < pos) { + if (array[n].str) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str)); + if (array[n].pos == n) { + JS_FreeValue(ctx, array[n].val); + } else { + if (JS_SetPropertyInt64(ctx, obj, n, array[n].val) < 0) { + n++; + goto exception; + } + } + n++; + } + js_free(ctx, array); + for (i = n; undefined_count-- > 0; i++) { + if (JS_SetPropertyInt64(ctx, obj, i, JS_UNDEFINED) < 0) + goto fail; + } + for (; i < len; i++) { + if (JS_DeletePropertyInt64(ctx, obj, i, JS_PROP_THROW) < 0) + goto fail; + } + return obj; + +exception: + for (; n < pos; n++) { + JS_FreeValue(ctx, array[n].val); + if (array[n].str) + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, array[n].str)); + } + js_free(ctx, array); +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +// Note: a.toSorted() is a.slice().sort() with the twist that a.slice() +// leaves holes in sparse arrays intact whereas a.toSorted() replaces them +// with undefined, thus in effect creating a dense array. +// Does not use Array[@@species], always returns a base Array. +static JSValue js_array_toSorted(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, obj, ret, *arrp, *pval; + JSObject *p; + int64_t i, len; + uint32_t count32; + int ok; + + ok = JS_IsUndefined(argv[0]) || JS_IsFunction(ctx, argv[0]); + if (!ok) + return JS_ThrowTypeError(ctx, "not a function"); + + ret = JS_EXCEPTION; + arr = JS_UNDEFINED; + obj = JS_ToObject(ctx, this_val); + if (js_get_length64(ctx, &len, obj)) + goto exception; + + if (len > UINT32_MAX) { + JS_ThrowRangeError(ctx, "invalid array length"); + goto exception; + } + + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + goto exception; + + if (len > 0) { + p = JS_VALUE_GET_OBJ(arr); + if (expand_fast_array(ctx, p, len) < 0) + goto exception; + p->u.array.count = len; + + i = 0; + pval = p->u.array.u.values; + if (js_get_fast_array(ctx, obj, &arrp, &count32) && count32 == len) { + for (; i < len; i++, pval++) + *pval = js_dup(arrp[i]); + } else { + for (; i < len; i++, pval++) { + if (-1 == JS_TryGetPropertyInt64(ctx, obj, i, pval)) { + for (; i < len; i++, pval++) + *pval = JS_UNDEFINED; + goto exception; + } + } + } + + if (JS_SetProperty(ctx, arr, JS_ATOM_length, js_int64(len)) < 0) + goto exception; + } + + ret = js_array_sort(ctx, arr, argc, argv); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, ret); + + ret = arr; + arr = JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, obj); + return ret; +} + +typedef struct JSArrayIteratorData { + JSValue obj; + JSIteratorKindEnum kind; + uint32_t idx; +} JSArrayIteratorData; + +static void js_array_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSArrayIteratorData *it = p->u.array_iterator_data; + if (it) { + JS_FreeValueRT(rt, it->obj); + js_free_rt(rt, it); + } +} + +static void js_array_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSArrayIteratorData *it = p->u.array_iterator_data; + if (it) { + JS_MarkValue(rt, it->obj, mark_func); + } +} + +static JSValue js_create_array(JSContext *ctx, int len, JSValue *tab) +{ + JSValue obj; + int i; + + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + for(i = 0; i < len; i++) { + if (JS_CreateDataPropertyUint32(ctx, obj, i, js_dup(tab[i]), 0) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + } + return obj; +} + +static JSValue js_create_array_iterator(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue enum_obj, arr; + JSArrayIteratorData *it; + JSIteratorKindEnum kind; + int class_id; + + kind = magic & 3; + if (magic & 4) { + /* string iterator case */ + arr = JS_ToStringCheckObject(ctx, this_val); + class_id = JS_CLASS_STRING_ITERATOR; + } else { + arr = JS_ToObject(ctx, this_val); + class_id = JS_CLASS_ARRAY_ITERATOR; + } + if (JS_IsException(arr)) + goto fail; + enum_obj = JS_NewObjectClass(ctx, class_id); + if (JS_IsException(enum_obj)) + goto fail; + it = js_malloc(ctx, sizeof(*it)); + if (!it) + goto fail1; + it->obj = arr; + it->kind = kind; + it->idx = 0; + JS_SetOpaqueInternal(enum_obj, it); + return enum_obj; + fail1: + JS_FreeValue(ctx, enum_obj); + fail: + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; +} + +static JSValue js_array_iterator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSArrayIteratorData *it; + uint32_t len, idx; + JSValue val, obj; + JSObject *p; + + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_ITERATOR); + if (!it) + goto fail1; + if (JS_IsUndefined(it->obj)) + goto done; + p = JS_VALUE_GET_OBJ(it->obj); + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + goto fail1; + } + len = p->u.array.count; + } else { + if (js_get_length32(ctx, &len, it->obj)) { + fail1: + *pdone = FALSE; + return JS_EXCEPTION; + } + } + idx = it->idx; + if (idx >= len) { + JS_FreeValue(ctx, it->obj); + it->obj = JS_UNDEFINED; + done: + *pdone = TRUE; + return JS_UNDEFINED; + } + it->idx = idx + 1; + *pdone = FALSE; + if (it->kind == JS_ITERATOR_KIND_KEY) { + return js_uint32(idx); + } else { + val = JS_GetPropertyUint32(ctx, it->obj, idx); + if (JS_IsException(val)) + return JS_EXCEPTION; + if (it->kind == JS_ITERATOR_KIND_VALUE) { + return val; + } else { + JSValue args[2]; + JSValue num; + num = js_uint32(idx); + args[0] = num; + args[1] = val; + obj = js_create_array(ctx, 2, args); + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, num); + return obj; + } + } +} + +typedef struct JSIteratorWrapData { + JSValue wrapped_iter; + JSValue wrapped_next; +} JSIteratorWrapData; + +static void js_iterator_wrap_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorWrapData *it = p->u.iterator_wrap_data; + if (it) { + JS_FreeValueRT(rt, it->wrapped_iter); + JS_FreeValueRT(rt, it->wrapped_next); + js_free_rt(rt, it); + } +} + +static void js_iterator_wrap_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorWrapData *it = p->u.iterator_wrap_data; + if (it) { + JS_MarkValue(rt, it->wrapped_iter, mark_func); + JS_MarkValue(rt, it->wrapped_next, mark_func); + } +} + +static JSValue js_iterator_wrap_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSIteratorWrapData *it; + JSValue method, ret; + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ITERATOR_WRAP); + if (!it) + return JS_EXCEPTION; + if (magic == GEN_MAGIC_NEXT) + return JS_IteratorNext(ctx, it->wrapped_iter, it->wrapped_next, argc, argv, pdone); + method = JS_GetProperty(ctx, it->wrapped_iter, JS_ATOM_return); + if (JS_IsException(method)) + return JS_EXCEPTION; + if (JS_IsNull(method) || JS_IsUndefined(method)) { + *pdone = TRUE; + return JS_UNDEFINED; + } + ret = JS_IteratorNext2(ctx, it->wrapped_iter, method, argc, argv, pdone); + JS_FreeValue(ctx, method); + return ret; +} + +static const JSCFunctionListEntry js_iterator_wrap_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_iterator_wrap_next, GEN_MAGIC_NEXT ), + JS_ITERATOR_NEXT_DEF("return", 0, js_iterator_wrap_next, GEN_MAGIC_RETURN ), +}; + +static JSValue js_iterator_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSObject *p; + + if (JS_TAG_OBJECT != JS_VALUE_GET_TAG(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + p = JS_VALUE_GET_OBJ(new_target); + if (p->class_id == JS_CLASS_C_FUNCTION) + if (p->u.cfunc.c_function.generic == js_iterator_constructor) + return JS_ThrowTypeError(ctx, "abstract class not constructable"); + return js_create_from_ctor(ctx, new_target, JS_CLASS_ITERATOR); +} + +static JSValue js_iterator_from(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, method, iter; + JSIteratorWrapData *it; + int ret; + + obj = argv[0]; + if (JS_IsString(obj)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return JS_EXCEPTION; + return JS_CallFree(ctx, method, obj, 0, NULL); + } + if (!JS_IsObject(obj)) + return JS_ThrowTypeError(ctx, "Iterator.from called on non-object"); + ret = JS_OrdinaryIsInstanceOf(ctx, obj, ctx->iterator_ctor); + if (ret < 0) + return JS_EXCEPTION; + if (ret) + return js_dup(obj); + method = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) + return JS_EXCEPTION; + if (JS_IsNull(method) || JS_IsUndefined(method)) { + method = JS_GetProperty(ctx, obj, JS_ATOM_next); + if (JS_IsException(method)) + return JS_EXCEPTION; + iter = JS_NewObjectClass(ctx, JS_CLASS_ITERATOR_WRAP); + if (JS_IsException(iter)) + goto fail; + it = js_malloc(ctx, sizeof(*it)); + if (!it) + goto fail; + it->wrapped_iter = js_dup(obj); + it->wrapped_next = method; + JS_SetOpaqueInternal(iter, it); + } else { + iter = JS_GetIterator2(ctx, obj, method); + JS_FreeValue(ctx, method); + if (JS_IsException(iter)) + return JS_EXCEPTION; + } + return iter; +fail: + JS_FreeValue(ctx, method); + JS_FreeValue(ctx, iter); + return JS_EXCEPTION; +} + +static int check_iterator(JSContext *ctx, JSValue obj) +{ + if (!JS_IsObject(obj)) { + JS_ThrowTypeErrorNotAnObject(ctx); + return -1; + } + return 0; +} + +typedef struct JSIteratorHelperData { + JSValue obj; + JSValue next; + JSValue func; // predicate (filter) or mapper (flatMap, map) + JSValue inner; // innerValue (flatMap) + int64_t count; // limit (drop, take) or counter (filter, map, flatMap) + JSIteratorHelperKindEnum kind : 8; + uint8_t executing : 1; + uint8_t done : 1; +} JSIteratorHelperData; + +static JSValue js_create_iterator_helper(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue func, obj, method; + int64_t count; + JSIteratorHelperData *it; + + if (check_iterator(ctx, this_val) < 0) + return JS_EXCEPTION; + func = JS_UNDEFINED; + count = 0; + + switch(magic) { + case JS_ITERATOR_HELPER_KIND_DROP: + case JS_ITERATOR_HELPER_KIND_TAKE: + { + JSValue v; + double dlimit; + v = JS_ToNumber(ctx, argv[0]); + if (JS_IsException(v)) + return JS_EXCEPTION; + // Check for Infinity. + if (JS_ToFloat64(ctx, &dlimit, v)) { + JS_FreeValue(ctx, v); + return JS_EXCEPTION; + } + if (isnan(dlimit)) { + JS_FreeValue(ctx, v); + goto fail; + } + if (!isfinite(dlimit)) { + JS_FreeValue(ctx, v); + if (dlimit < 0) + goto fail; + else + count = MAX_SAFE_INTEGER; + } else { + v = JS_ToIntegerFree(ctx, v); + if (JS_IsException(v)) + return JS_EXCEPTION; + if (JS_ToInt64Free(ctx, &count, v)) + return JS_EXCEPTION; + } + if (count < 0) { + fail: + return JS_ThrowRangeError(ctx, "must be positive"); + } + } + break; + case JS_ITERATOR_HELPER_KIND_FILTER: + case JS_ITERATOR_HELPER_KIND_FLAT_MAP: + case JS_ITERATOR_HELPER_KIND_MAP: + { + func = argv[0]; + if (check_function(ctx, func)) + return JS_EXCEPTION; + } + break; + default: + abort(); + break; + } + + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + return JS_EXCEPTION; + obj = JS_NewObjectClass(ctx, JS_CLASS_ITERATOR_HELPER); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, method); + return JS_EXCEPTION; + } + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; + } + it->kind = magic; + it->obj = js_dup(this_val); + it->func = js_dup(func); + it->next = method; + it->inner = JS_UNDEFINED; + it->count = count; + it->executing = 0; + it->done = 0; + JS_SetOpaqueInternal(obj, it); + return obj; +} + +static JSValue js_iterator_proto_func(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue item, method, ret, func, index_val, r; + JSValue args[2]; + int64_t idx; + BOOL done; + + if (check_iterator(ctx, this_val) < 0) + return JS_EXCEPTION; + if (check_function(ctx, argv[0])) + return JS_EXCEPTION; + func = js_dup(argv[0]); + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + goto fail; + + r = JS_UNDEFINED; + + switch(magic) { + case JS_ITERATOR_HELPER_KIND_EVERY: + { + r = JS_TRUE; + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) + break; + index_val = js_int64(idx); + args[0] = item; + args[1] = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + if (!JS_ToBoolFree(ctx, ret)) { + if (JS_IteratorClose(ctx, this_val, FALSE) < 0) + r = JS_EXCEPTION; + else + r = JS_FALSE; + break; + } + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + case JS_ITERATOR_HELPER_KIND_FIND: + { + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) + break; + index_val = js_int64(idx); + args[0] = item; + args[1] = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, item); + goto fail; + } + if (JS_ToBoolFree(ctx, ret)) { + if (JS_IteratorClose(ctx, this_val, FALSE) < 0) { + JS_FreeValue(ctx, item); + r = JS_EXCEPTION; + } else { + r = item; + } + break; + } + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + case JS_ITERATOR_HELPER_KIND_FOR_EACH: + { + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) + break; + index_val = js_int64(idx); + args[0] = item; + args[1] = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + JS_FreeValue(ctx, ret); + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + case JS_ITERATOR_HELPER_KIND_SOME: + { + r = JS_FALSE; + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) + break; + index_val = js_int64(idx); + args[0] = item; + args[1] = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + if (JS_ToBoolFree(ctx, ret)) { + if (JS_IteratorClose(ctx, this_val, FALSE) < 0) + r = JS_EXCEPTION; + else + r = JS_TRUE; + break; + } + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + } + break; + default: + abort(); + break; + } + + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return r; +fail: + JS_IteratorClose(ctx, this_val, TRUE); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; +} + +static JSValue js_iterator_proto_reduce(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue item, method, ret, func, index_val, acc; + JSValue args[3]; + int64_t idx; + BOOL done; + + if (check_iterator(ctx, this_val) < 0) + return JS_EXCEPTION; + if (check_function(ctx, argv[0])) + return JS_EXCEPTION; + acc = JS_UNDEFINED; + func = js_dup(argv[0]); + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + goto exception; + if (argc > 1) { + acc = js_dup(argv[1]); + idx = 0; + } else { + acc = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(acc)) + goto exception; + if (done) { + JS_ThrowTypeError(ctx, "empty iterator"); + goto exception; + } + idx = 1; + } + for (/* empty */; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) + break; + index_val = js_int64(idx); + args[0] = acc; + args[1] = item; + args[2] = index_val; + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto exception; + JS_FreeValue(ctx, acc); + acc = ret; + index_val = JS_UNDEFINED; + ret = JS_UNDEFINED; + item = JS_UNDEFINED; + } + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return acc; +exception: + JS_IteratorClose(ctx, this_val, TRUE); + JS_FreeValue(ctx, acc); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; +} + +static JSValue js_iterator_proto_toArray(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue item, method, result; + int64_t idx; + BOOL done; + + result = JS_UNDEFINED; + if (check_iterator(ctx, this_val) < 0) + return JS_EXCEPTION; + method = JS_GetProperty(ctx, this_val, JS_ATOM_next); + if (JS_IsException(method)) + return JS_EXCEPTION; + result = JS_NewArray(ctx); + if (JS_IsException(result)) + goto exception; + for (idx = 0; /*empty*/; idx++) { + item = JS_IteratorNext(ctx, this_val, method, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) + break; + if (JS_DefinePropertyValueInt64(ctx, result, idx, item, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + if (JS_SetProperty(ctx, result, JS_ATOM_length, js_uint32(idx)) < 0) + goto exception; + JS_FreeValue(ctx, method); + return result; +exception: + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, method); + return JS_EXCEPTION; +} + +static JSValue js_iterator_proto_iterator(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_dup(this_val); +} + +static JSValue js_iterator_proto_get_toStringTag(JSContext *ctx, JSValue this_val) +{ + return JS_AtomToString(ctx, JS_ATOM_Iterator); +} + +static JSValue js_iterator_proto_set_toStringTag(JSContext *ctx, JSValue this_val, JSValue val) +{ + int res; + + if (check_iterator(ctx, this_val) < 0) + return JS_EXCEPTION; + if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_ITERATOR])) + return JS_ThrowTypeError(ctx, "Cannot assign to read only property"); + res = JS_GetOwnProperty(ctx, NULL, this_val, JS_ATOM_Symbol_toStringTag); + if (res < 0) + return JS_EXCEPTION; + if (res) { + if (JS_SetProperty(ctx, this_val, JS_ATOM_Symbol_toStringTag, js_dup(val)) < 0) + return JS_EXCEPTION; + } else { + if (JS_DefinePropertyValue(ctx, this_val, JS_ATOM_Symbol_toStringTag, js_dup(val), JS_PROP_C_W_E) < 0) + return JS_EXCEPTION; + } + return JS_UNDEFINED; +} + +static void js_iterator_helper_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorHelperData *it = p->u.iterator_helper_data; + if (it) { + JS_FreeValueRT(rt, it->obj); + JS_FreeValueRT(rt, it->func); + JS_FreeValueRT(rt, it->next); + JS_FreeValueRT(rt, it->inner); + js_free_rt(rt, it); + } +} + +static void js_iterator_helper_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSIteratorHelperData *it = p->u.iterator_helper_data; + if (it) { + JS_MarkValue(rt, it->obj, mark_func); + JS_MarkValue(rt, it->func, mark_func); + JS_MarkValue(rt, it->next, mark_func); + JS_MarkValue(rt, it->inner, mark_func); + } +} + +static JSValue js_iterator_helper_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSIteratorHelperData *it; + JSValue ret; + + *pdone = FALSE; + + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_ITERATOR_HELPER); + if (!it) + return JS_EXCEPTION; + if (it->executing) + return JS_ThrowTypeError(ctx, "cannot invoke a running iterator"); + if (it->done) { + *pdone = TRUE; + return JS_UNDEFINED; + } + + it->executing = 1; + + switch (it->kind) { + case JS_ITERATOR_HELPER_KIND_DROP: + { + JSValue item, method; + if (magic == GEN_MAGIC_NEXT) { + method = js_dup(it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + while (it->count > 0) { + it->count--; + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + if (JS_IsException(item)) { + JS_FreeValue(ctx, method); + goto fail; + } + JS_FreeValue(ctx, item); + if (magic == GEN_MAGIC_RETURN) + *pdone = TRUE; + if (*pdone) { + JS_FreeValue(ctx, method); + ret = JS_UNDEFINED; + goto done; + } + } + + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail; + ret = item; + goto done; + } + break; + case JS_ITERATOR_HELPER_KIND_FILTER: + { + JSValue item, method, selected, index_val, args[2]; + if (magic == GEN_MAGIC_NEXT) { + method = js_dup(it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + filter_again: + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + if (JS_IsException(item)) { + JS_FreeValue(ctx, method); + goto fail; + } + if (*pdone || magic == GEN_MAGIC_RETURN) { + JS_FreeValue(ctx, method); + ret = item; + goto done; + } + index_val = js_int64(it->count++); + args[0] = item; + args[1] = index_val; + selected = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(selected)) { + JS_FreeValue(ctx, method); + goto fail; + } + if (JS_ToBoolFree(ctx, selected)) { + JS_FreeValue(ctx, method); + ret = item; + goto done; + } + goto filter_again; + } + break; + case JS_ITERATOR_HELPER_KIND_FLAT_MAP: + { + JSValue item, method, index_val, args[2], iter; + flat_map_again: + if (JS_IsUndefined(it->inner)) { + if (magic == GEN_MAGIC_NEXT) { + method = js_dup(it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail; + if (*pdone || magic == GEN_MAGIC_RETURN) { + ret = item; + goto done; + } + index_val = js_int64(it->count++); + args[0] = item; + args[1] = index_val; + ret = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + if (!JS_IsObject(ret)) { + JS_FreeValue(ctx, ret); + JS_ThrowTypeError(ctx, "not an object"); + goto fail; + } + method = JS_GetProperty(ctx, ret, JS_ATOM_Symbol_iterator); + if (JS_IsException(method)) { + JS_FreeValue(ctx, ret); + goto fail; + } + if (JS_IsNull(method) || JS_IsUndefined(method)) { + JS_FreeValue(ctx, method); + iter = ret; + } else { + iter = JS_GetIterator2(ctx, ret, method); + JS_FreeValue(ctx, method); + JS_FreeValue(ctx, ret); + if (JS_IsException(iter)) + goto fail; + } + + it->inner = iter; + } + + if (magic == GEN_MAGIC_NEXT) + method = JS_GetProperty(ctx, it->inner, JS_ATOM_next); + else + method = JS_GetProperty(ctx, it->inner, JS_ATOM_return); + if (JS_IsException(method)) { + inner_fail: + JS_IteratorClose(ctx, it->inner, FALSE); + JS_FreeValue(ctx, it->inner); + it->inner = JS_UNDEFINED; + goto fail; + } + if (magic == GEN_MAGIC_RETURN && (JS_IsUndefined(method) || JS_IsNull(method))) { + goto inner_end; + } else { + item = JS_IteratorNext(ctx, it->inner, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto inner_fail; + } + if (*pdone) { + inner_end: + *pdone = FALSE; // The outer iterator must continue. + JS_IteratorClose(ctx, it->inner, FALSE); + JS_FreeValue(ctx, it->inner); + it->inner = JS_UNDEFINED; + goto flat_map_again; + } + ret = item; + goto done; + } + break; + case JS_ITERATOR_HELPER_KIND_MAP: + { + JSValue item, method, index_val, args[2]; + if (magic == GEN_MAGIC_NEXT) { + method = js_dup(it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail; + if (*pdone || magic == GEN_MAGIC_RETURN) { + ret = item; + goto done; + } + index_val = js_int64(it->count++); + args[0] = item; + args[1] = index_val; + ret = JS_Call(ctx, it->func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, index_val); + if (JS_IsException(ret)) + goto fail; + goto done; + } + break; + case JS_ITERATOR_HELPER_KIND_TAKE: + { + JSValue item, method; + if (it->count > 0) { + if (magic == GEN_MAGIC_NEXT) { + method = js_dup(it->next); + } else { + method = JS_GetProperty(ctx, it->obj, JS_ATOM_return); + if (JS_IsException(method)) + goto fail; + } + it->count--; + item = JS_IteratorNext(ctx, it->obj, method, 0, NULL, pdone); + JS_FreeValue(ctx, method); + if (JS_IsException(item)) + goto fail; + ret = item; + goto done; + } + + *pdone = TRUE; + if (JS_IteratorClose(ctx, it->obj, FALSE)) + ret = JS_EXCEPTION; + else + ret = JS_UNDEFINED; + goto done; + } + break; + default: + abort(); + } + +done: + it->done = magic == GEN_MAGIC_NEXT ? *pdone : 1; + it->executing = 0; + return ret; +fail: + /* close the iterator object, preserving pending exception */ + JS_IteratorClose(ctx, it->obj, TRUE); + ret = JS_EXCEPTION; + goto done; +} + +static const JSCFunctionListEntry js_iterator_funcs[] = { + JS_CFUNC_DEF("from", 1, js_iterator_from ), +}; + +static const JSCFunctionListEntry js_iterator_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("drop", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_DROP ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_FILTER ), + JS_CFUNC_MAGIC_DEF("flatMap", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_FLAT_MAP ), + JS_CFUNC_MAGIC_DEF("map", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_MAP ), + JS_CFUNC_MAGIC_DEF("take", 1, js_create_iterator_helper, JS_ITERATOR_HELPER_KIND_TAKE ), + JS_CFUNC_MAGIC_DEF("every", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_EVERY ), + JS_CFUNC_MAGIC_DEF("find", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_FIND), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_FOR_EACH ), + JS_CFUNC_MAGIC_DEF("some", 1, js_iterator_proto_func, JS_ITERATOR_HELPER_KIND_SOME ), + JS_CFUNC_DEF("reduce", 1, js_iterator_proto_reduce ), + JS_CFUNC_DEF("toArray", 0, js_iterator_proto_toArray ), + JS_CFUNC_DEF("[Symbol.iterator]", 0, js_iterator_proto_iterator ), + JS_CGETSET_DEF("[Symbol.toStringTag]", js_iterator_proto_get_toStringTag, js_iterator_proto_set_toStringTag), +}; + +static const JSCFunctionListEntry js_iterator_helper_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_iterator_helper_next, GEN_MAGIC_NEXT ), + JS_ITERATOR_NEXT_DEF("return", 0, js_iterator_helper_next, GEN_MAGIC_RETURN ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Iterator Helper", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_array_proto_funcs[] = { + JS_CFUNC_DEF("at", 1, js_array_at ), + JS_CFUNC_DEF("with", 2, js_array_with ), + JS_CFUNC_DEF("concat", 1, js_array_concat ), + JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every ), + JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some ), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach ), + JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce ), + JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight ), + JS_CFUNC_DEF("fill", 1, js_array_fill ), + JS_CFUNC_MAGIC_DEF("find", 1, js_array_find, ArrayFind ), + JS_CFUNC_MAGIC_DEF("findIndex", 1, js_array_find, ArrayFindIndex ), + JS_CFUNC_MAGIC_DEF("findLast", 1, js_array_find, ArrayFindLast ), + JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_array_find, ArrayFindLastIndex ), + JS_CFUNC_DEF("indexOf", 1, js_array_indexOf ), + JS_CFUNC_DEF("lastIndexOf", 1, js_array_lastIndexOf ), + JS_CFUNC_DEF("includes", 1, js_array_includes ), + JS_CFUNC_MAGIC_DEF("join", 1, js_array_join, 0 ), + JS_CFUNC_DEF("toString", 0, js_array_toString ), + JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_array_join, 1 ), + JS_CFUNC_MAGIC_DEF("pop", 0, js_array_pop, 0 ), + JS_CFUNC_MAGIC_DEF("push", 1, js_array_push, 0 ), + JS_CFUNC_MAGIC_DEF("shift", 0, js_array_pop, 1 ), + JS_CFUNC_MAGIC_DEF("unshift", 1, js_array_push, 1 ), + JS_CFUNC_DEF("reverse", 0, js_array_reverse ), + JS_CFUNC_DEF("toReversed", 0, js_array_toReversed ), + JS_CFUNC_DEF("sort", 1, js_array_sort ), + JS_CFUNC_DEF("toSorted", 1, js_array_toSorted ), + JS_CFUNC_MAGIC_DEF("slice", 2, js_array_slice, 0 ), + JS_CFUNC_MAGIC_DEF("splice", 2, js_array_slice, 1 ), + JS_CFUNC_DEF("toSpliced", 2, js_array_toSpliced ), + JS_CFUNC_DEF("copyWithin", 2, js_array_copyWithin ), + JS_CFUNC_MAGIC_DEF("flatMap", 1, js_array_flatten, 1 ), + JS_CFUNC_MAGIC_DEF("flat", 0, js_array_flatten, 0 ), + JS_CFUNC_MAGIC_DEF("values", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE ), + JS_ALIAS_DEF("[Symbol.iterator]", "values" ), + JS_CFUNC_MAGIC_DEF("keys", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY ), + JS_CFUNC_MAGIC_DEF("entries", 0, js_create_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ), +}; + +static const JSCFunctionListEntry js_array_iterator_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_array_iterator_next, 0 ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Array Iterator", JS_PROP_CONFIGURABLE ), +}; + +/* Number */ + +static JSValue js_number_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue val, obj; + if (argc == 0) { + val = js_int32(0); + } else { + val = JS_ToNumeric(ctx, argv[0]); + if (JS_IsException(val)) + return val; + switch(JS_VALUE_GET_TAG(val)) { + case JS_TAG_BIG_INT: + { + JSBigInt *p = JS_VALUE_GET_PTR(val); + double d; + bf_get_float64(&p->num, &d, BF_RNDN); + JS_FreeValue(ctx, val); + val = js_float64(d); + } + break; + default: + break; + } + } + if (!JS_IsUndefined(new_target)) { + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_NUMBER); + if (!JS_IsException(obj)) + JS_SetObjectData(ctx, obj, val); + return obj; + } else { + return val; + } +} + +static JSValue js_number_isNaN(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + if (!JS_IsNumber(argv[0])) + return JS_FALSE; + return js_global_isNaN(ctx, this_val, argc, argv); +} + +static JSValue js_number_isFinite(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + if (!JS_IsNumber(argv[0])) + return JS_FALSE; + return js_global_isFinite(ctx, this_val, argc, argv); +} + +static JSValue js_number_isInteger(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + int ret; + ret = JS_NumberIsInteger(ctx, argv[0]); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_number_isSafeInteger(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + double d; + if (!JS_IsNumber(argv[0])) + return JS_FALSE; + if (unlikely(JS_ToFloat64(ctx, &d, argv[0]))) + return JS_EXCEPTION; + return js_bool(is_safe_integer(d)); +} + +static const JSCFunctionListEntry js_number_funcs[] = { + /* global ParseInt and parseFloat should be defined already or delayed */ + JS_ALIAS_BASE_DEF("parseInt", "parseInt", 0 ), + JS_ALIAS_BASE_DEF("parseFloat", "parseFloat", 0 ), + JS_CFUNC_DEF("isNaN", 1, js_number_isNaN ), + JS_CFUNC_DEF("isFinite", 1, js_number_isFinite ), + JS_CFUNC_DEF("isInteger", 1, js_number_isInteger ), + JS_CFUNC_DEF("isSafeInteger", 1, js_number_isSafeInteger ), + JS_PROP_DOUBLE_DEF("MAX_VALUE", 1.7976931348623157e+308, 0 ), + JS_PROP_DOUBLE_DEF("MIN_VALUE", 5e-324, 0 ), + JS_PROP_U2D_DEF("NaN", 0x7FF8ull<<48, 0 ), // workaround for msvc + 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 */ +}; + +static JSValue js_thisNumberValue(JSContext *ctx, JSValue this_val) +{ + if (JS_IsNumber(this_val)) + return js_dup(this_val); + + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_NUMBER) { + if (JS_IsNumber(p->u.object_data)) + return js_dup(p->u.object_data); + } + } + return JS_ThrowTypeError(ctx, "not a number"); +} + +static JSValue js_number_valueOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_thisNumberValue(ctx, this_val); +} + +static int js_get_radix(JSContext *ctx, JSValue val) +{ + int radix; + if (JS_ToInt32Sat(ctx, &radix, val)) + return -1; + if (radix < 2 || radix > 36) { + JS_ThrowRangeError(ctx, "toString() radix argument must be between 2 and 36"); + return -1; + } + return radix; +} + +static JSValue js_number_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + char buf[72]; + JSValue val; + int base; + double d; + + val = js_thisNumberValue(ctx, this_val); + if (JS_IsException(val)) + return val; + if (magic || JS_IsUndefined(argv[0])) { + if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { + size_t len = i32toa(buf, JS_VALUE_GET_INT(val)); + return js_new_string8_len(ctx, buf, len); + } + base = 10; + } else { + base = js_get_radix(ctx, argv[0]); + if (base < 0) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + } + if (JS_VALUE_GET_TAG(val) == JS_TAG_INT) { + size_t len = i32toa_radix(buf, JS_VALUE_GET_INT(val), base); + return js_new_string8_len(ctx, buf, len); + } + if (JS_ToFloat64Free(ctx, &d, val)) + return JS_EXCEPTION; + if (base != 10) + return js_dtoa_radix(ctx, d, base); + + return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING); +} + +static JSValue js_number_toFixed(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val; + int f; + double d; + + val = js_thisNumberValue(ctx, this_val); + if (JS_IsException(val)) + return val; + if (JS_ToFloat64Free(ctx, &d, val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &f, argv[0])) + return JS_EXCEPTION; + if (f < 0 || f > 100) { + return JS_ThrowRangeError(ctx, "toFixed() digits argument must be between 0 and 100"); + } + if (fabs(d) >= 1e21) { + // use ToString(d) + return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING); + } else { + return js_dtoa(ctx, d, f, JS_DTOA_FIXED); + } +} + +static JSValue js_number_toExponential(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val; + double d; + int f; + + val = js_thisNumberValue(ctx, this_val); + if (JS_IsException(val)) + return val; + if (JS_ToFloat64Free(ctx, &d, val)) + return JS_EXCEPTION; + if (JS_ToInt32Sat(ctx, &f, argv[0])) + return JS_EXCEPTION; + if (!isfinite(d)) + return js_dtoa_infinite(ctx, d); + if (!JS_IsUndefined(argv[0])) { + if (f < 0 || f > 100) { + return JS_ThrowRangeError(ctx, "toExponential() argument must be between 0 and 100"); + } + f += 1; /* number of significant digits between 1 and 101 */ + } + return js_dtoa(ctx, d, f, JS_DTOA_EXPONENTIAL); +} + +static JSValue js_number_toPrecision(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val; + int p; + double d; + + val = js_thisNumberValue(ctx, this_val); + if (JS_IsException(val)) + return val; + if (JS_ToFloat64Free(ctx, &d, val)) + return JS_EXCEPTION; + if (JS_IsUndefined(argv[0])) + return js_dtoa(ctx, d, 0, JS_DTOA_TOSTRING); + if (JS_ToInt32Sat(ctx, &p, argv[0])) + return JS_EXCEPTION; + if (!isfinite(d)) + return js_dtoa_infinite(ctx, d); + if (p < 1 || p > 100) { + return JS_ThrowRangeError(ctx, "toPrecision() argument must be between 1 and 100"); + } + return js_dtoa(ctx, d, p, JS_DTOA_PRECISION); +} + +static const JSCFunctionListEntry js_number_proto_funcs[] = { + 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_MAGIC_DEF("toString", 1, js_number_toString, 0 ), + JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_number_toString, 1 ), + JS_CFUNC_DEF("valueOf", 0, js_number_valueOf ), +}; + +static JSValue js_parseInt(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + const char *str; + int radix, flags; + JSValue ret; + size_t len; + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + if (JS_ToInt32(ctx, &radix, argv[1])) { + JS_FreeCString(ctx, str); + return JS_EXCEPTION; + } + flags = ATOD_TRIM_SPACES; + if (radix == 0) { + flags |= ATOD_ACCEPT_HEX_PREFIX; // Only 0x and 0X are supported + radix = 10; + } + ret = js_atof(ctx, str, len, NULL, radix, flags); + JS_FreeCString(ctx, str); + return ret; +} + +static JSValue js_parseFloat(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + const char *str; + JSValue ret; + int flags; + size_t len; + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + flags = ATOD_TRIM_SPACES | ATOD_ACCEPT_FLOAT | ATOD_ACCEPT_INFINITY; + ret = js_atof(ctx, str, len, NULL, 10, flags); + JS_FreeCString(ctx, str); + return ret; +} + +/* Boolean */ +static JSValue js_boolean_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue val, obj; + val = js_bool(JS_ToBool(ctx, argv[0])); + if (!JS_IsUndefined(new_target)) { + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_BOOLEAN); + if (!JS_IsException(obj)) + JS_SetObjectData(ctx, obj, val); + return obj; + } else { + return val; + } +} + +static JSValue js_thisBooleanValue(JSContext *ctx, JSValue this_val) +{ + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_BOOL) + return js_dup(this_val); + + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_BOOLEAN) { + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_BOOL) + return p->u.object_data; + } + } + return JS_ThrowTypeError(ctx, "not a boolean"); +} + +static JSValue js_boolean_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val = js_thisBooleanValue(ctx, this_val); + if (JS_IsException(val)) + return val; + return JS_AtomToString(ctx, JS_VALUE_GET_BOOL(val) ? + JS_ATOM_true : JS_ATOM_false); +} + +static JSValue js_boolean_valueOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_thisBooleanValue(ctx, this_val); +} + +static const JSCFunctionListEntry js_boolean_proto_funcs[] = { + JS_CFUNC_DEF("toString", 0, js_boolean_toString ), + JS_CFUNC_DEF("valueOf", 0, js_boolean_valueOf ), +}; + +/* String */ + +static int js_string_get_own_property(JSContext *ctx, + JSPropertyDescriptor *desc, + JSValue obj, JSAtom prop) +{ + JSObject *p; + JSString *p1; + uint32_t idx, ch; + + /* This is a class exotic method: obj class_id is JS_CLASS_STRING */ + if (__JS_AtomIsTaggedInt(prop)) { + p = JS_VALUE_GET_OBJ(obj); + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) { + p1 = JS_VALUE_GET_STRING(p->u.object_data); + idx = __JS_AtomToUInt32(prop); + if (idx < p1->len) { + if (desc) { + ch = string_get(p1, idx); + desc->flags = JS_PROP_ENUMERABLE; + desc->value = js_new_string_char(ctx, ch); + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + } + return TRUE; + } + } + } + return FALSE; +} + +static int js_string_define_own_property(JSContext *ctx, + JSValue this_obj, + JSAtom prop, JSValue val, + JSValue getter, + JSValue setter, int flags) +{ + uint32_t idx; + JSObject *p; + JSString *p1, *p2; + + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + p = JS_VALUE_GET_OBJ(this_obj); + if (JS_VALUE_GET_TAG(p->u.object_data) != JS_TAG_STRING) + goto def; + p1 = JS_VALUE_GET_STRING(p->u.object_data); + if (idx >= p1->len) + goto def; + if (!check_define_prop_flags(JS_PROP_ENUMERABLE, flags)) + goto fail; + /* check that the same value is configured */ + if (flags & JS_PROP_HAS_VALUE) { + if (JS_VALUE_GET_TAG(val) != JS_TAG_STRING) + goto fail; + p2 = JS_VALUE_GET_STRING(val); + if (p2->len != 1) + goto fail; + if (string_get(p1, idx) != string_get(p2, 0)) { + fail: + return JS_ThrowTypeErrorOrFalse(ctx, flags, "property is not configurable"); + } + } + return TRUE; + } else { + def: + return JS_DefineProperty(ctx, this_obj, prop, val, getter, setter, + flags | JS_PROP_NO_EXOTIC); + } +} + +static int js_string_delete_property(JSContext *ctx, + JSValue obj, JSAtom prop) +{ + uint32_t idx; + + if (__JS_AtomIsTaggedInt(prop)) { + idx = __JS_AtomToUInt32(prop); + if (idx < js_string_obj_get_length(ctx, obj)) { + return FALSE; + } + } + return TRUE; +} + +static const JSClassExoticMethods js_string_exotic_methods = { + .get_own_property = js_string_get_own_property, + .define_own_property = js_string_define_own_property, + .delete_property = js_string_delete_property, +}; + +static JSValue js_string_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue val, obj; + if (argc == 0) { + val = JS_AtomToString(ctx, JS_ATOM_empty_string); + } else { + if (JS_IsUndefined(new_target) && JS_IsSymbol(argv[0])) { + JSAtomStruct *p = JS_VALUE_GET_PTR(argv[0]); + val = JS_ConcatString3(ctx, "Symbol(", JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p)), ")"); + } else { + val = JS_ToString(ctx, argv[0]); + } + if (JS_IsException(val)) + return val; + } + if (!JS_IsUndefined(new_target)) { + JSString *p1 = JS_VALUE_GET_STRING(val); + + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_STRING); + if (!JS_IsException(obj)) { + JS_SetObjectData(ctx, obj, val); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_length, js_int32(p1->len), 0); + } + return obj; + } else { + return val; + } +} + +static JSValue js_thisStringValue(JSContext *ctx, JSValue this_val) +{ + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_STRING) + return js_dup(this_val); + + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_STRING) { + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_STRING) + return js_dup(p->u.object_data); + } + } + return JS_ThrowTypeError(ctx, "not a string"); +} + +static JSValue js_string_fromCharCode(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + int i; + StringBuffer b_s, *b = &b_s; + + // shortcut for single argument common case + if (argc == 1 && JS_VALUE_GET_TAG(argv[0]) == JS_TAG_INT) { + uint16_t c16 = JS_VALUE_GET_INT(argv[0]); + return js_new_string_char(ctx, c16); + } + + string_buffer_init(ctx, b, argc); + + for(i = 0; i < argc; i++) { + int32_t c; + if (JS_ToInt32(ctx, &c, argv[i]) || string_buffer_putc16(b, c & 0xffff)) { + string_buffer_free(b); + return JS_EXCEPTION; + } + } + return string_buffer_end(b); +} + +static JSValue js_string_fromCodePoint(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + double d; + int i, c; + StringBuffer b_s, *b = NULL; + + // shortcut for single argument common case + if (argc == 1 && JS_VALUE_GET_TAG(argv[0]) == JS_TAG_INT) { + c = JS_VALUE_GET_INT(argv[0]); + if (c < 0 || c > 0x10ffff) + goto range_error; + if (c <= 0xffff) { + return js_new_string_char(ctx, c); + } else { + uint16_t c16[2]; + c16[0] = get_hi_surrogate(c); + c16[1] = get_lo_surrogate(c); + return js_new_string16_len(ctx, c16, 2); + } + } + + /* XXX: could pre-compute string length if all arguments are JS_TAG_INT */ + b = &b_s; + if (string_buffer_init(ctx, b, argc)) + goto fail; + for(i = 0; i < argc; i++) { + if (JS_VALUE_GET_TAG(argv[i]) == JS_TAG_INT) { + c = JS_VALUE_GET_INT(argv[i]); + if (c < 0 || c > 0x10ffff) + goto range_error; + } else { + if (JS_ToFloat64(ctx, &d, argv[i])) + goto fail; + if (!(d >= 0 && d <= 0x10ffff) || (c = (int)d) != d) + goto range_error; + } + if (string_buffer_putc(b, c)) + goto fail; + } + return string_buffer_end(b); + + range_error: + JS_ThrowRangeError(ctx, "invalid code point"); + fail: + if (b) string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue js_string_raw(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // raw(temp,...a) + JSValue cooked, val, raw; + StringBuffer b_s, *b = &b_s; + int64_t i, n; + + string_buffer_init(ctx, b, 0); + raw = JS_UNDEFINED; + cooked = JS_ToObject(ctx, argv[0]); + if (JS_IsException(cooked)) + goto exception; + raw = JS_ToObjectFree(ctx, JS_GetProperty(ctx, cooked, JS_ATOM_raw)); + if (JS_IsException(raw)) + goto exception; + if (js_get_length64(ctx, &n, raw) < 0) + goto exception; + + for (i = 0; i < n; i++) { + val = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, raw, i)); + if (JS_IsException(val)) + goto exception; + string_buffer_concat_value_free(b, val); + if (i < n - 1 && i + 1 < argc) { + if (string_buffer_concat_value(b, argv[i + 1])) + goto exception; + } + } + JS_FreeValue(ctx, cooked); + JS_FreeValue(ctx, raw); + return string_buffer_end(b); + +exception: + JS_FreeValue(ctx, cooked); + JS_FreeValue(ctx, raw); + string_buffer_free(b); + return JS_EXCEPTION; +} + +/* only used in test262 */ +JSValue js_string_codePointRange(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + uint32_t start, end, i, n; + StringBuffer b_s, *b = &b_s; + + if (JS_ToUint32(ctx, &start, argv[0]) || + JS_ToUint32(ctx, &end, argv[1])) + return JS_EXCEPTION; + end = min_uint32(end, 0x10ffff + 1); + + if (start > end) { + start = end; + } + n = end - start; + if (end > 0x10000) { + n += end - max_uint32(start, 0x10000); + } + if (string_buffer_init2(ctx, b, n, end >= 0x100)) + return JS_EXCEPTION; + for(i = start; i < end; i++) { + string_buffer_putc(b, i); + } + return string_buffer_end(b); +} + +static JSValue js_string_at(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val, ret; + JSString *p; + int idx, c; + + val = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + if (JS_ToInt32Sat(ctx, &idx, argv[0])) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (idx < 0) + idx = p->len + idx; + if (idx < 0 || idx >= p->len) { + ret = JS_UNDEFINED; + } else { + c = string_get(p, idx); + ret = js_new_string_char(ctx, c); + } + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue js_string_charCodeAt(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val, ret; + JSString *p; + int idx, c; + + val = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + if (JS_ToInt32Sat(ctx, &idx, argv[0])) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (idx < 0 || idx >= p->len) { + ret = JS_NAN; + } else { + c = string_get(p, idx); + ret = js_int32(c); + } + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue js_string_charAt(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val, ret; + JSString *p; + int idx, c; + + val = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + if (JS_ToInt32Sat(ctx, &idx, argv[0])) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (idx < 0 || idx >= p->len) { + ret = JS_AtomToString(ctx, JS_ATOM_empty_string); + } else { + c = string_get(p, idx); + ret = js_new_string_char(ctx, c); + } + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue js_string_codePointAt(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val, ret; + JSString *p; + int idx, c; + + val = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + if (JS_ToInt32Sat(ctx, &idx, argv[0])) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (idx < 0 || idx >= p->len) { + ret = JS_UNDEFINED; + } else { + c = string_getc(p, &idx); + ret = js_int32(c); + } + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue js_string_concat(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue r; + int i; + + /* XXX: Use more efficient method */ + /* XXX: This method is OK if r has a single refcount */ + /* XXX: should use string_buffer? */ + r = JS_ToStringCheckObject(ctx, this_val); + for (i = 0; i < argc; i++) { + if (JS_IsException(r)) + break; + r = JS_ConcatString(ctx, r, js_dup(argv[i])); + } + return r; +} + +static int string_cmp(JSString *p1, JSString *p2, int x1, int x2, int len) +{ + int i, c1, c2; + for (i = 0; i < len; i++) { + if ((c1 = string_get(p1, x1 + i)) != (c2 = string_get(p2, x2 + i))) + return c1 - c2; + } + return 0; +} + +static int string_indexof_char(JSString *p, int c, int from) +{ + /* assuming 0 <= from <= p->len */ + int i, len = p->len; + if (p->is_wide_char) { + for (i = from; i < len; i++) { + if (p->u.str16[i] == c) + return i; + } + } else { + if ((c & ~0xff) == 0) { + for (i = from; i < len; i++) { + if (p->u.str8[i] == (uint8_t)c) + return i; + } + } + } + return -1; +} + +static int string_indexof(JSString *p1, JSString *p2, int from) +{ + /* assuming 0 <= from <= p1->len */ + int c, i, j, len1 = p1->len, len2 = p2->len; + if (len2 == 0) + return from; + for (i = from, c = string_get(p2, 0); i + len2 <= len1; i = j + 1) { + j = string_indexof_char(p1, c, i); + if (j < 0 || j + len2 > len1) + break; + if (!string_cmp(p1, p2, j + 1, 1, len2 - 1)) + return j; + } + return -1; +} + +static int64_t string_advance_index(JSString *p, int64_t index, BOOL unicode) +{ + if (!unicode || index >= p->len || !p->is_wide_char) { + index++; + } else { + int index32 = (int)index; + string_getc(p, &index32); + index = index32; + } + return index; +} + +static JSValue js_string_isWellFormed(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str; + JSValue ret; + JSString *p; + uint32_t c, i, n; + + ret = JS_TRUE; + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_STRING(str); + if (!p->is_wide_char || p->len == 0) + goto done; // by definition well-formed + + for (i = 0, n = p->len; i < n; i++) { + c = p->u.str16[i]; + if (!is_surrogate(c)) + continue; + if (is_lo_surrogate(c) || i + 1 == n) + break; + c = p->u.str16[++i]; + if (!is_lo_surrogate(c)) + break; + } + + if (i < n) + ret = JS_FALSE; + +done: + JS_FreeValue(ctx, str); + return ret; +} + +static JSValue js_string_toWellFormed(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str; + JSValue ret; + JSString *p; + uint32_t c, i, n; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_STRING(str); + if (!p->is_wide_char || p->len == 0) + return str; // by definition well-formed + + // TODO(bnoordhuis) don't clone when input is well-formed + ret = js_new_string16_len(ctx, p->u.str16, p->len); + JS_FreeValue(ctx, str); + if (JS_IsException(ret)) + return JS_EXCEPTION; + + p = JS_VALUE_GET_STRING(ret); + for (i = 0, n = p->len; i < n; i++) { + c = p->u.str16[i]; + if (!is_surrogate(c)) + continue; + if (is_lo_surrogate(c) || i + 1 == n) { + p->u.str16[i] = 0xFFFD; + continue; + } + c = p->u.str16[++i]; + if (!is_lo_surrogate(c)) + p->u.str16[--i] = 0xFFFD; + } + + return ret; +} + +static JSValue js_string_indexOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int lastIndexOf) +{ + JSValue str, v; + int i, len, v_len, pos, start, stop, ret, inc; + JSString *p; + JSString *p1; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return str; + v = JS_ToString(ctx, argv[0]); + if (JS_IsException(v)) + goto fail; + p = JS_VALUE_GET_STRING(str); + p1 = JS_VALUE_GET_STRING(v); + len = p->len; + v_len = p1->len; + if (lastIndexOf) { + pos = len - v_len; + if (argc > 1) { + double d; + if (JS_ToFloat64(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) { + if (!string_cmp(p, p1, i, 0, v_len)) { + ret = i; + break; + } + if (i == stop) + break; + } + } + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, v); + return js_int32(ret); + +fail: + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, v); + return JS_EXCEPTION; +} + +/* return < 0 if exception or TRUE/FALSE */ +static int js_is_regexp(JSContext *ctx, JSValue obj); + +static JSValue js_string_includes(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue str, v = JS_UNDEFINED; + int i, len, v_len, pos, start, stop, ret; + JSString *p; + JSString *p1; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return str; + ret = js_is_regexp(ctx, argv[0]); + if (ret) { + if (ret > 0) + JS_ThrowTypeError(ctx, "regexp not supported"); + goto fail; + } + v = JS_ToString(ctx, argv[0]); + if (JS_IsException(v)) + goto fail; + p = JS_VALUE_GET_STRING(str); + p1 = JS_VALUE_GET_STRING(v); + len = p->len; + v_len = p1->len; + pos = (magic == 2) ? len : 0; + if (argc > 1 && !JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &pos, argv[1], 0, len, 0)) + goto fail; + } + len -= v_len; + ret = 0; + if (magic == 0) { + start = pos; + stop = len; + } else { + if (magic == 1) { + if (pos > len) + goto done; + } else { + pos -= v_len; + } + start = stop = pos; + } + if (start >= 0 && start <= stop) { + for (i = start;; i++) { + if (!string_cmp(p, p1, i, 0, v_len)) { + ret = 1; + break; + } + if (i == stop) + break; + } + } + done: + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, v); + return js_bool(ret); + +fail: + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, v); + return JS_EXCEPTION; +} + +static int check_regexp_g_flag(JSContext *ctx, JSValue regexp) +{ + int ret; + JSValue flags; + + ret = js_is_regexp(ctx, regexp); + if (ret < 0) + return -1; + if (ret) { + flags = JS_GetProperty(ctx, regexp, JS_ATOM_flags); + if (JS_IsException(flags)) + return -1; + if (JS_IsUndefined(flags) || JS_IsNull(flags)) { + JS_ThrowTypeError(ctx, "cannot convert to object"); + return -1; + } + flags = JS_ToStringFree(ctx, flags); + if (JS_IsException(flags)) + return -1; + ret = string_indexof_char(JS_VALUE_GET_STRING(flags), 'g', 0); + JS_FreeValue(ctx, flags); + if (ret < 0) { + JS_ThrowTypeError(ctx, "regexp must have the 'g' flag"); + return -1; + } + } + return 0; +} + +static JSValue js_string_match(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int atom) +{ + // match(rx), search(rx), matchAll(rx) + // atom is JS_ATOM_Symbol_match, JS_ATOM_Symbol_search, or JS_ATOM_Symbol_matchAll + JSValue O = this_val, regexp = argv[0], args[2]; + JSValue matcher, S, rx, result, str; + int args_len; + + if (JS_IsUndefined(O) || JS_IsNull(O)) + return JS_ThrowTypeError(ctx, "cannot convert to object"); + + if (!JS_IsUndefined(regexp) && !JS_IsNull(regexp)) { + matcher = JS_GetProperty(ctx, regexp, atom); + if (JS_IsException(matcher)) + return JS_EXCEPTION; + if (atom == JS_ATOM_Symbol_matchAll) { + if (check_regexp_g_flag(ctx, regexp) < 0) { + JS_FreeValue(ctx, matcher); + return JS_EXCEPTION; + } + } + if (!JS_IsUndefined(matcher) && !JS_IsNull(matcher)) { + return JS_CallFree(ctx, matcher, regexp, 1, &O); + } + } + S = JS_ToString(ctx, O); + if (JS_IsException(S)) + return JS_EXCEPTION; + args_len = 1; + args[0] = regexp; + str = JS_UNDEFINED; + if (atom == JS_ATOM_Symbol_matchAll) { + str = js_new_string8(ctx, "g"); + if (JS_IsException(str)) + goto fail; + args[args_len++] = str; + } + rx = JS_CallConstructor(ctx, ctx->regexp_ctor, args_len, args); + JS_FreeValue(ctx, str); + if (JS_IsException(rx)) { + fail: + JS_FreeValue(ctx, S); + return JS_EXCEPTION; + } + result = JS_InvokeFree(ctx, rx, atom, 1, &S); + JS_FreeValue(ctx, S); + return result; +} + +static JSValue js_string___GetSubstitution(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // GetSubstitution(matched, str, position, captures, namedCaptures, rep) + JSValue matched, str, captures, namedCaptures, rep; + JSValue capture, name, s; + uint32_t position, len, matched_len, captures_len; + int i, j, j0, k, k1; + int c, c1; + StringBuffer b_s, *b = &b_s; + JSString *sp, *rp; + + matched = argv[0]; + str = argv[1]; + captures = argv[3]; + namedCaptures = argv[4]; + rep = argv[5]; + + if (!JS_IsString(rep) || !JS_IsString(str)) + return JS_ThrowTypeError(ctx, "not a string"); + + sp = JS_VALUE_GET_STRING(str); + rp = JS_VALUE_GET_STRING(rep); + + string_buffer_init(ctx, b, 0); + + captures_len = 0; + if (!JS_IsUndefined(captures)) { + if (js_get_length32(ctx, &captures_len, captures)) + goto exception; + } + if (js_get_length32(ctx, &matched_len, matched)) + goto exception; + if (JS_ToUint32(ctx, &position, argv[2]) < 0) + goto exception; + + len = rp->len; + i = 0; + for(;;) { + j = string_indexof_char(rp, '$', i); + if (j < 0 || j + 1 >= len) + break; + string_buffer_concat(b, rp, i, j); + j0 = j++; + c = string_get(rp, j++); + if (c == '$') { + string_buffer_putc8(b, '$'); + } else if (c == '&') { + if (string_buffer_concat_value(b, matched)) + goto exception; + } else if (c == '`') { + string_buffer_concat(b, sp, 0, position); + } else if (c == '\'') { + string_buffer_concat(b, sp, position + matched_len, sp->len); + } else if (c >= '0' && c <= '9') { + k = c - '0'; + if (j < len) { + c1 = string_get(rp, j); + if (c1 >= '0' && c1 <= '9') { + /* This behavior is specified in ES6 and refined in ECMA 2019 */ + /* ECMA 2019 does not have the extra test, but + Test262 S15.5.4.11_A3_T1..3 require this behavior */ + k1 = k * 10 + c1 - '0'; + if (k1 >= 1 && k1 < captures_len) { + k = k1; + j++; + } + } + } + if (k >= 1 && k < captures_len) { + s = JS_GetPropertyInt64(ctx, captures, k); + if (JS_IsException(s)) + goto exception; + if (!JS_IsUndefined(s)) { + if (string_buffer_concat_value_free(b, s)) + goto exception; + } + } else { + goto norep; + } + } else if (c == '<' && !JS_IsUndefined(namedCaptures)) { + k = string_indexof_char(rp, '>', j); + if (k < 0) + goto norep; + name = js_sub_string(ctx, rp, j, k); + if (JS_IsException(name)) + goto exception; + capture = JS_GetPropertyValue(ctx, namedCaptures, name); + if (JS_IsException(capture)) + goto exception; + if (!JS_IsUndefined(capture)) { + if (string_buffer_concat_value_free(b, capture)) + goto exception; + } + j = k + 1; + } else { + norep: + string_buffer_concat(b, rp, j0, j); + } + i = j; + } + string_buffer_concat(b, rp, i, rp->len); + return string_buffer_end(b); +exception: + string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue js_string_replace(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int is_replaceAll) +{ + // replace(rx, rep) + JSValue O = this_val, searchValue = argv[0], replaceValue = argv[1]; + JSValue args[6]; + JSValue str, search_str, replaceValue_str, repl_str; + JSString *sp, *searchp; + StringBuffer b_s, *b = &b_s; + int pos, functionalReplace, endOfLastMatch; + BOOL is_first; + + if (JS_IsUndefined(O) || JS_IsNull(O)) + return JS_ThrowTypeError(ctx, "cannot convert to object"); + + search_str = JS_UNDEFINED; + replaceValue_str = JS_UNDEFINED; + repl_str = JS_UNDEFINED; + + if (!JS_IsUndefined(searchValue) && !JS_IsNull(searchValue)) { + JSValue replacer; + if (is_replaceAll) { + if (check_regexp_g_flag(ctx, searchValue) < 0) + return JS_EXCEPTION; + } + replacer = JS_GetProperty(ctx, searchValue, JS_ATOM_Symbol_replace); + if (JS_IsException(replacer)) + return JS_EXCEPTION; + if (!JS_IsUndefined(replacer) && !JS_IsNull(replacer)) { + args[0] = O; + args[1] = replaceValue; + return JS_CallFree(ctx, replacer, searchValue, 2, args); + } + } + string_buffer_init(ctx, b, 0); + + str = JS_ToString(ctx, O); + if (JS_IsException(str)) + goto exception; + search_str = JS_ToString(ctx, searchValue); + if (JS_IsException(search_str)) + goto exception; + functionalReplace = JS_IsFunction(ctx, replaceValue); + if (!functionalReplace) { + replaceValue_str = JS_ToString(ctx, replaceValue); + if (JS_IsException(replaceValue_str)) + goto exception; + } + + sp = JS_VALUE_GET_STRING(str); + searchp = JS_VALUE_GET_STRING(search_str); + endOfLastMatch = 0; + is_first = TRUE; + for(;;) { + if (unlikely(searchp->len == 0)) { + if (is_first) + pos = 0; + else if (endOfLastMatch >= sp->len) + pos = -1; + else + pos = endOfLastMatch + 1; + } else { + pos = string_indexof(sp, searchp, endOfLastMatch); + } + if (pos < 0) { + if (is_first) { + string_buffer_free(b); + JS_FreeValue(ctx, search_str); + JS_FreeValue(ctx, replaceValue_str); + return str; + } else { + break; + } + } + if (functionalReplace) { + args[0] = search_str; + args[1] = js_int32(pos); + args[2] = str; + repl_str = JS_ToStringFree(ctx, JS_Call(ctx, replaceValue, JS_UNDEFINED, 3, args)); + } else { + args[0] = search_str; + args[1] = str; + args[2] = js_int32(pos); + args[3] = JS_UNDEFINED; + args[4] = JS_UNDEFINED; + args[5] = replaceValue_str; + repl_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args); + } + if (JS_IsException(repl_str)) + goto exception; + + string_buffer_concat(b, sp, endOfLastMatch, pos); + string_buffer_concat_value_free(b, repl_str); + endOfLastMatch = pos + searchp->len; + is_first = FALSE; + if (!is_replaceAll) + break; + } + string_buffer_concat(b, sp, endOfLastMatch, sp->len); + JS_FreeValue(ctx, search_str); + JS_FreeValue(ctx, replaceValue_str); + JS_FreeValue(ctx, str); + return string_buffer_end(b); + +exception: + string_buffer_free(b); + JS_FreeValue(ctx, search_str); + JS_FreeValue(ctx, replaceValue_str); + JS_FreeValue(ctx, str); + return JS_EXCEPTION; +} + +static JSValue js_string_split(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // split(sep, limit) + JSValue O = this_val, separator = argv[0], limit = argv[1]; + JSValue args[2]; + JSValue S, A, R, T; + uint32_t lim, lengthA; + int64_t p, q, s, r, e; + JSString *sp, *rp; + + if (JS_IsUndefined(O) || JS_IsNull(O)) + return JS_ThrowTypeError(ctx, "cannot convert to object"); + + S = JS_UNDEFINED; + A = JS_UNDEFINED; + R = JS_UNDEFINED; + + if (!JS_IsUndefined(separator) && !JS_IsNull(separator)) { + JSValue splitter; + splitter = JS_GetProperty(ctx, separator, JS_ATOM_Symbol_split); + if (JS_IsException(splitter)) + return JS_EXCEPTION; + if (!JS_IsUndefined(splitter) && !JS_IsNull(splitter)) { + args[0] = O; + args[1] = limit; + return JS_CallFree(ctx, splitter, separator, 2, args); + } + } + S = JS_ToString(ctx, O); + if (JS_IsException(S)) + goto exception; + A = JS_NewArray(ctx); + if (JS_IsException(A)) + goto exception; + lengthA = 0; + if (JS_IsUndefined(limit)) { + lim = 0xffffffff; + } else { + if (JS_ToUint32(ctx, &lim, limit) < 0) + goto exception; + } + sp = JS_VALUE_GET_STRING(S); + s = sp->len; + R = JS_ToString(ctx, separator); + if (JS_IsException(R)) + goto exception; + rp = JS_VALUE_GET_STRING(R); + r = rp->len; + p = 0; + if (lim == 0) + goto done; + if (JS_IsUndefined(separator)) + goto add_tail; + if (s == 0) { + if (r != 0) + goto add_tail; + goto done; + } + q = p; + for (q = p; (q += !r) <= s - r - !r; q = p = e + r) { + e = string_indexof(sp, rp, q); + if (e < 0) + break; + T = js_sub_string(ctx, sp, p, e); + if (JS_IsException(T)) + goto exception; + if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T, 0) < 0) + goto exception; + if (lengthA == lim) + goto done; + } +add_tail: + T = js_sub_string(ctx, sp, p, s); + if (JS_IsException(T)) + goto exception; + if (JS_CreateDataPropertyUint32(ctx, A, lengthA++, T,0 ) < 0) + goto exception; +done: + JS_FreeValue(ctx, S); + JS_FreeValue(ctx, R); + return A; + +exception: + JS_FreeValue(ctx, A); + JS_FreeValue(ctx, S); + JS_FreeValue(ctx, R); + return JS_EXCEPTION; +} + +static JSValue js_string_substring(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str, ret; + int a, b, start, end; + JSString *p; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, p->len, 0)) { + JS_FreeValue(ctx, str); + return JS_EXCEPTION; + } + b = p->len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &b, argv[1], 0, p->len, 0)) { + JS_FreeValue(ctx, str); + return JS_EXCEPTION; + } + } + if (a < b) { + start = a; + end = b; + } else { + start = b; + end = a; + } + ret = js_sub_string(ctx, p, start, end); + JS_FreeValue(ctx, str); + return ret; +} + +static JSValue js_string_substr(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str, ret; + int a, len, n; + JSString *p; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + len = p->len; + if (JS_ToInt32Clamp(ctx, &a, argv[0], 0, len, len)) { + JS_FreeValue(ctx, str); + return JS_EXCEPTION; + } + n = len - a; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &n, argv[1], 0, len - a, 0)) { + JS_FreeValue(ctx, str); + return JS_EXCEPTION; + } + } + ret = js_sub_string(ctx, p, a, a + n); + JS_FreeValue(ctx, str); + return ret; +} + +static JSValue js_string_slice(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str, ret; + int len, start, end; + JSString *p; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + len = p->len; + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) { + JS_FreeValue(ctx, str); + return JS_EXCEPTION; + } + end = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &end, argv[1], 0, len, len)) { + JS_FreeValue(ctx, str); + return JS_EXCEPTION; + } + } + ret = js_sub_string(ctx, p, start, max_int(end, start)); + JS_FreeValue(ctx, str); + return ret; +} + +static JSValue js_string_pad(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int padEnd) +{ + JSValue str, v = JS_UNDEFINED; + StringBuffer b_s, *b = &b_s; + JSString *p, *p1 = NULL; + int n, len, c = ' '; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + goto fail1; + if (JS_ToInt32Sat(ctx, &n, argv[0])) + goto fail2; + p = JS_VALUE_GET_STRING(str); + len = p->len; + if (len >= n) + return str; + if (argc > 1 && !JS_IsUndefined(argv[1])) { + v = JS_ToString(ctx, argv[1]); + if (JS_IsException(v)) + goto fail2; + p1 = JS_VALUE_GET_STRING(v); + if (p1->len == 0) { + JS_FreeValue(ctx, v); + return str; + } + if (p1->len == 1) { + c = string_get(p1, 0); + p1 = NULL; + } + } + if (n > JS_STRING_LEN_MAX) { + JS_ThrowRangeError(ctx, "invalid string length"); + goto fail2; + } + if (string_buffer_init(ctx, b, n)) + goto fail3; + n -= len; + if (padEnd) { + if (string_buffer_concat(b, p, 0, len)) + goto fail; + } + if (p1) { + while (n > 0) { + int chunk = min_int(n, p1->len); + if (string_buffer_concat(b, p1, 0, chunk)) + goto fail; + n -= chunk; + } + } else { + if (string_buffer_fill(b, c, n)) + goto fail; + } + if (!padEnd) { + if (string_buffer_concat(b, p, 0, len)) + goto fail; + } + JS_FreeValue(ctx, v); + JS_FreeValue(ctx, str); + return string_buffer_end(b); + +fail: + string_buffer_free(b); +fail3: + JS_FreeValue(ctx, v); +fail2: + JS_FreeValue(ctx, str); +fail1: + return JS_EXCEPTION; +} + +static JSValue js_string_repeat(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str; + StringBuffer b_s, *b = &b_s; + JSString *p; + int64_t val; + int n, len; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + goto fail; + if (JS_ToInt64Sat(ctx, &val, argv[0])) + goto fail; + if (val < 0 || val > 2147483647) { + JS_ThrowRangeError(ctx, "invalid repeat count"); + goto fail; + } + n = val; + p = JS_VALUE_GET_STRING(str); + len = p->len; + if (len == 0 || n == 1) + return str; + if (val * len > JS_STRING_LEN_MAX) { + JS_ThrowRangeError(ctx, "invalid string length"); + goto fail; + } + if (string_buffer_init2(ctx, b, n * len, p->is_wide_char)) + goto fail; + if (len == 1) { + string_buffer_fill(b, string_get(p, 0), n); + } else { + while (n-- > 0) { + string_buffer_concat(b, p, 0, len); + } + } + JS_FreeValue(ctx, str); + return string_buffer_end(b); + +fail: + JS_FreeValue(ctx, str); + return JS_EXCEPTION; +} + +static JSValue js_string_trim(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue str, ret; + int a, b, len; + JSString *p; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return str; + p = JS_VALUE_GET_STRING(str); + a = 0; + b = len = p->len; + if (magic & 1) { + while (a < len && lre_is_space(string_get(p, a))) + a++; + } + if (magic & 2) { + while (b > a && lre_is_space(string_get(p, b - 1))) + b--; + } + ret = js_sub_string(ctx, p, a, b); + JS_FreeValue(ctx, str); + return ret; +} + +/* return 0 if before the first char */ +static int string_prevc(JSString *p, int *pidx) +{ + int idx, c, c1; + + idx = *pidx; + if (idx <= 0) + return 0; + idx--; + if (p->is_wide_char) { + c = p->u.str16[idx]; + if (is_lo_surrogate(c) && idx > 0) { + c1 = p->u.str16[idx - 1]; + if (is_hi_surrogate(c1)) { + c = from_surrogate(c1, c); + idx--; + } + } + } else { + c = p->u.str8[idx]; + } + *pidx = idx; + return c; +} + +static BOOL test_final_sigma(JSString *p, int sigma_pos) +{ + int k, c1; + + /* before C: skip case ignorable chars and check there is + a cased letter */ + k = sigma_pos; + for(;;) { + c1 = string_prevc(p, &k); + if (!lre_is_case_ignorable(c1)) + break; + } + if (!lre_is_cased(c1)) + return FALSE; + + /* after C: skip case ignorable chars and check there is + no cased letter */ + k = sigma_pos + 1; + for(;;) { + if (k >= p->len) + return TRUE; + c1 = string_getc(p, &k); + if (!lre_is_case_ignorable(c1)) + break; + } + return !lre_is_cased(c1); +} + +static int to_utf32_buf(JSContext *ctx, JSString *p, uint32_t **pbuf) +{ + uint32_t *b; + int i, j, n; + + j = -1; + n = p->len; + b = js_malloc(ctx, max_int(1, n) * sizeof(*b)); + if (b) + for (i = j = 0; i < n;) + b[j++] = string_getc(p, &i); + *pbuf = b; + return j; +} + +static JSValue js_string_localeCompare(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + int i, n, an, bn, cmp; + uint32_t *as, *bs, *ts; + JSValue a, b, ret; + + ret = JS_EXCEPTION; + as = NULL; + bs = NULL; + + a = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(a)) + return JS_EXCEPTION; + + b = JS_ToString(ctx, argv[0]); + if (JS_IsException(b)) + goto exception; + + an = to_utf32_buf(ctx, JS_VALUE_GET_STRING(a), &as); + if (an == -1) + goto exception; + + bn = to_utf32_buf(ctx, JS_VALUE_GET_STRING(b), &bs); + if (bn == -1) + goto exception; + + // TODO(bnoordhuis) skip normalization when input is latin1 + an = unicode_normalize(&ts, as, an, UNICODE_NFC, ctx, + (DynBufReallocFunc *)js_realloc); + if (an == -1) + goto exception; + js_free(ctx, as); + as = ts; + + // TODO(bnoordhuis) skip normalization when input is latin1 + bn = unicode_normalize(&ts, bs, bn, UNICODE_NFC, ctx, + (DynBufReallocFunc *)js_realloc); + if (bn == -1) + goto exception; + js_free(ctx, bs); + bs = ts; + + n = min_int(an, bn); + for (i = 0; i < n; i++) + if (as[i] != bs[i]) + break; + if (i < n) + cmp = compare_u32(as[i], bs[i]); + else + cmp = compare_u32(an, bn); + ret = js_int32(cmp); + +exception: + JS_FreeValue(ctx, a); + JS_FreeValue(ctx, b); + js_free(ctx, as); + js_free(ctx, bs); + return ret; +} + +static JSValue js_string_toLowerCase(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int to_lower) +{ + JSValue val; + StringBuffer b_s, *b = &b_s; + JSString *p; + int i, c, j, l; + uint32_t res[LRE_CC_RES_LEN_MAX]; + + val = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_STRING(val); + if (p->len == 0) + return val; + if (string_buffer_init(ctx, b, p->len)) + goto fail; + for(i = 0; i < p->len;) { + c = string_getc(p, &i); + if (c == 0x3a3 && to_lower && test_final_sigma(p, i - 1)) { + res[0] = 0x3c2; /* final sigma */ + l = 1; + } else { + l = lre_case_conv(res, c, to_lower); + } + for(j = 0; j < l; j++) { + if (string_buffer_putc(b, res[j])) + goto fail; + } + } + JS_FreeValue(ctx, val); + return string_buffer_end(b); + fail: + JS_FreeValue(ctx, val); + string_buffer_free(b); + return JS_EXCEPTION; +} + +/* return (-1, NULL) if exception, otherwise (len, buf) */ +static int JS_ToUTF32String(JSContext *ctx, uint32_t **pbuf, JSValue val1) +{ + JSValue val; + int len; + + val = JS_ToString(ctx, val1); + if (JS_IsException(val)) + return -1; + len = to_utf32_buf(ctx, JS_VALUE_GET_STRING(val), pbuf); + JS_FreeValue(ctx, val); + return len; +} + +static JSValue JS_NewUTF32String(JSContext *ctx, const uint32_t *buf, int len) +{ + int i; + StringBuffer b_s, *b = &b_s; + if (string_buffer_init(ctx, b, len)) + return JS_EXCEPTION; + for(i = 0; i < len; i++) { + if (string_buffer_putc(b, buf[i])) + goto fail; + } + return string_buffer_end(b); + fail: + string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue js_string_normalize(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + const char *form, *p; + size_t form_len; + int is_compat, buf_len, out_len; + UnicodeNormalizationEnum n_type; + JSValue val; + uint32_t *buf, *out_buf; + + val = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(val)) + return val; + buf_len = JS_ToUTF32String(ctx, &buf, val); + JS_FreeValue(ctx, val); + if (buf_len < 0) + return JS_EXCEPTION; + + if (argc == 0 || JS_IsUndefined(argv[0])) { + n_type = UNICODE_NFC; + } else { + form = JS_ToCStringLen(ctx, &form_len, argv[0]); + if (!form) + goto fail1; + p = form; + if (p[0] != 'N' || p[1] != 'F') + goto bad_form; + p += 2; + is_compat = FALSE; + if (*p == 'K') { + is_compat = TRUE; + p++; + } + if (*p == 'C' || *p == 'D') { + n_type = UNICODE_NFC + is_compat * 2 + (*p - 'C'); + if ((p + 1 - form) != form_len) + goto bad_form; + } else { + bad_form: + JS_FreeCString(ctx, form); + JS_ThrowRangeError(ctx, "bad normalization form"); + fail1: + js_free(ctx, buf); + return JS_EXCEPTION; + } + JS_FreeCString(ctx, form); + } + + out_len = unicode_normalize(&out_buf, buf, buf_len, n_type, + ctx->rt, (DynBufReallocFunc *)js_realloc_rt); + js_free(ctx, buf); + if (out_len < 0) + return JS_EXCEPTION; + val = JS_NewUTF32String(ctx, out_buf, out_len); + js_free(ctx, out_buf); + return val; +} + +/* also used for String.prototype.valueOf */ +static JSValue js_string_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_thisStringValue(ctx, this_val); +} + +/* String Iterator */ + +static JSValue js_string_iterator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSArrayIteratorData *it; + uint32_t idx, c, start; + JSString *p; + + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_STRING_ITERATOR); + if (!it) { + *pdone = FALSE; + return JS_EXCEPTION; + } + if (JS_IsUndefined(it->obj)) + goto done; + p = JS_VALUE_GET_STRING(it->obj); + idx = it->idx; + if (idx >= p->len) { + JS_FreeValue(ctx, it->obj); + it->obj = JS_UNDEFINED; + done: + *pdone = TRUE; + return JS_UNDEFINED; + } + + start = idx; + c = string_getc(p, (int *)&idx); + it->idx = idx; + *pdone = FALSE; + if (c <= 0xffff) { + return js_new_string_char(ctx, c); + } else { + return js_new_string16_len(ctx, p->u.str16 + start, 2); + } +} + +/* ES6 Annex B 2.3.2 etc. */ +enum { + magic_string_anchor, + magic_string_big, + magic_string_blink, + magic_string_bold, + magic_string_fixed, + magic_string_fontcolor, + magic_string_fontsize, + magic_string_italics, + magic_string_link, + magic_string_small, + magic_string_strike, + magic_string_sub, + magic_string_sup, +}; + +static JSValue js_string_CreateHTML(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue str; + const JSString *p; + StringBuffer b_s, *b = &b_s; + static struct { const char *tag, *attr; } const defs[] = { + { "a", "name" }, { "big", NULL }, { "blink", NULL }, { "b", NULL }, + { "tt", NULL }, { "font", "color" }, { "font", "size" }, { "i", NULL }, + { "a", "href" }, { "small", NULL }, { "strike", NULL }, + { "sub", NULL }, { "sup", NULL }, + }; + + str = JS_ToStringCheckObject(ctx, this_val); + if (JS_IsException(str)) + return JS_EXCEPTION; + string_buffer_init(ctx, b, 7); + string_buffer_putc8(b, '<'); + string_buffer_puts8(b, defs[magic].tag); + if (defs[magic].attr) { + // r += " " + attr + "=\"" + value + "\""; + JSValue value; + int i; + + string_buffer_putc8(b, ' '); + string_buffer_puts8(b, defs[magic].attr); + string_buffer_puts8(b, "=\""); + value = JS_ToStringCheckObject(ctx, argv[0]); + if (JS_IsException(value)) { + JS_FreeValue(ctx, str); + string_buffer_free(b); + return JS_EXCEPTION; + } + p = JS_VALUE_GET_STRING(value); + for (i = 0; i < p->len; i++) { + int c = string_get(p, i); + if (c == '"') { + string_buffer_puts8(b, """); + } else { + string_buffer_putc16(b, c); + } + } + JS_FreeValue(ctx, value); + string_buffer_putc8(b, '\"'); + } + // return r + ">" + str + "</" + tag + ">"; + string_buffer_putc8(b, '>'); + string_buffer_concat_value_free(b, str); + string_buffer_puts8(b, "</"); + string_buffer_puts8(b, defs[magic].tag); + string_buffer_putc8(b, '>'); + return string_buffer_end(b); +} + +static const JSCFunctionListEntry js_string_funcs[] = { + JS_CFUNC_DEF("fromCharCode", 1, js_string_fromCharCode ), + JS_CFUNC_DEF("fromCodePoint", 1, js_string_fromCodePoint ), + JS_CFUNC_DEF("raw", 1, js_string_raw ), +}; + +static const JSCFunctionListEntry js_string_proto_funcs[] = { + JS_PROP_INT32_DEF("length", 0, JS_PROP_CONFIGURABLE ), + JS_CFUNC_DEF("at", 1, js_string_at ), + JS_CFUNC_DEF("charCodeAt", 1, js_string_charCodeAt ), + JS_CFUNC_DEF("charAt", 1, js_string_charAt ), + JS_CFUNC_DEF("concat", 1, js_string_concat ), + JS_CFUNC_DEF("codePointAt", 1, js_string_codePointAt ), + JS_CFUNC_DEF("isWellFormed", 0, js_string_isWellFormed ), + JS_CFUNC_DEF("toWellFormed", 0, js_string_toWellFormed ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_string_indexOf, 0 ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_string_indexOf, 1 ), + JS_CFUNC_MAGIC_DEF("includes", 1, js_string_includes, 0 ), + JS_CFUNC_MAGIC_DEF("endsWith", 1, js_string_includes, 2 ), + JS_CFUNC_MAGIC_DEF("startsWith", 1, js_string_includes, 1 ), + JS_CFUNC_MAGIC_DEF("match", 1, js_string_match, JS_ATOM_Symbol_match ), + JS_CFUNC_MAGIC_DEF("matchAll", 1, js_string_match, JS_ATOM_Symbol_matchAll ), + JS_CFUNC_MAGIC_DEF("search", 1, js_string_match, JS_ATOM_Symbol_search ), + JS_CFUNC_DEF("split", 2, js_string_split ), + JS_CFUNC_DEF("substring", 2, js_string_substring ), + JS_CFUNC_DEF("substr", 2, js_string_substr ), + JS_CFUNC_DEF("slice", 2, js_string_slice ), + JS_CFUNC_DEF("repeat", 1, js_string_repeat ), + JS_CFUNC_MAGIC_DEF("replace", 2, js_string_replace, 0 ), + JS_CFUNC_MAGIC_DEF("replaceAll", 2, js_string_replace, 1 ), + JS_CFUNC_MAGIC_DEF("padEnd", 1, js_string_pad, 1 ), + JS_CFUNC_MAGIC_DEF("padStart", 1, js_string_pad, 0 ), + JS_CFUNC_MAGIC_DEF("trim", 0, js_string_trim, 3 ), + JS_CFUNC_MAGIC_DEF("trimEnd", 0, js_string_trim, 2 ), + JS_ALIAS_DEF("trimRight", "trimEnd" ), + JS_CFUNC_MAGIC_DEF("trimStart", 0, js_string_trim, 1 ), + JS_ALIAS_DEF("trimLeft", "trimStart" ), + JS_CFUNC_DEF("toString", 0, js_string_toString ), + JS_CFUNC_DEF("valueOf", 0, js_string_toString ), + JS_CFUNC_DEF("localeCompare", 1, js_string_localeCompare ), + JS_CFUNC_DEF("normalize", 0, js_string_normalize ), + JS_CFUNC_MAGIC_DEF("toLowerCase", 0, js_string_toLowerCase, 1 ), + JS_CFUNC_MAGIC_DEF("toUpperCase", 0, js_string_toLowerCase, 0 ), + JS_CFUNC_MAGIC_DEF("toLocaleLowerCase", 0, js_string_toLowerCase, 1 ), + JS_CFUNC_MAGIC_DEF("toLocaleUpperCase", 0, js_string_toLowerCase, 0 ), + JS_CFUNC_MAGIC_DEF("[Symbol.iterator]", 0, js_create_array_iterator, JS_ITERATOR_KIND_VALUE | 4 ), + /* ES6 Annex B 2.3.2 etc. */ + JS_CFUNC_MAGIC_DEF("anchor", 1, js_string_CreateHTML, magic_string_anchor ), + JS_CFUNC_MAGIC_DEF("big", 0, js_string_CreateHTML, magic_string_big ), + JS_CFUNC_MAGIC_DEF("blink", 0, js_string_CreateHTML, magic_string_blink ), + JS_CFUNC_MAGIC_DEF("bold", 0, js_string_CreateHTML, magic_string_bold ), + JS_CFUNC_MAGIC_DEF("fixed", 0, js_string_CreateHTML, magic_string_fixed ), + JS_CFUNC_MAGIC_DEF("fontcolor", 1, js_string_CreateHTML, magic_string_fontcolor ), + JS_CFUNC_MAGIC_DEF("fontsize", 1, js_string_CreateHTML, magic_string_fontsize ), + JS_CFUNC_MAGIC_DEF("italics", 0, js_string_CreateHTML, magic_string_italics ), + JS_CFUNC_MAGIC_DEF("link", 1, js_string_CreateHTML, magic_string_link ), + JS_CFUNC_MAGIC_DEF("small", 0, js_string_CreateHTML, magic_string_small ), + JS_CFUNC_MAGIC_DEF("strike", 0, js_string_CreateHTML, magic_string_strike ), + JS_CFUNC_MAGIC_DEF("sub", 0, js_string_CreateHTML, magic_string_sub ), + JS_CFUNC_MAGIC_DEF("sup", 0, js_string_CreateHTML, magic_string_sup ), +}; + +static const JSCFunctionListEntry js_string_iterator_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_string_iterator_next, 0 ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "String Iterator", JS_PROP_CONFIGURABLE ), +}; + + +/* Math */ + +/* precondition: a and b are not NaN */ +static double js_fmin(double a, double b) +{ + if (a == 0 && b == 0) { + JSFloat64Union a1, b1; + a1.d = a; + b1.d = b; + a1.u64 |= b1.u64; + return a1.d; + } else { + return fmin(a, b); + } +} + +/* precondition: a and b are not NaN */ +static double js_fmax(double a, double b) +{ + if (a == 0 && b == 0) { + JSFloat64Union a1, b1; + a1.d = a; + b1.d = b; + a1.u64 &= b1.u64; + return a1.d; + } else { + return fmax(a, b); + } +} + +static 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; + uint32_t tag; + + if (unlikely(argc == 0)) { + return js_float64(is_max ? NEG_INF : INF); + } + + tag = JS_VALUE_GET_TAG(argv[0]); + if (tag == JS_TAG_INT) { + int a1, r1 = JS_VALUE_GET_INT(argv[0]); + for(i = 1; i < argc; i++) { + tag = JS_VALUE_GET_TAG(argv[i]); + if (tag != JS_TAG_INT) { + 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_int32(r1); + } else { + if (JS_ToFloat64(ctx, &r, argv[0])) + return JS_EXCEPTION; + i = 1; + generic_case: + while (i < argc) { + if (JS_ToFloat64(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_number(r); + } +} + +static double js_math_sign(double a) +{ + if (isnan(a) || a == 0.0) + return a; + if (a < 0) + return -1; + else + return 1; +} + +static double js_math_round(double a) +{ + JSFloat64Union u; + uint64_t frac_mask, one; + unsigned int e, s; + + u.d = a; + e = (u.u64 >> 52) & 0x7ff; + if (e < 1023) { + /* abs(a) < 1 */ + if (e == (1023 - 1) && u.u64 != 0xbfe0000000000000) { + /* abs(a) > 0.5 or a = 0.5: return +/-1.0 */ + u.u64 = (u.u64 & ((uint64_t)1 << 63)) | ((uint64_t)1023 << 52); + } else { + /* return +/-0.0 */ + u.u64 &= (uint64_t)1 << 63; + } + } else if (e < (1023 + 52)) { + s = u.u64 >> 63; + one = (uint64_t)1 << (52 - (e - 1023)); + frac_mask = one - 1; + u.u64 += (one >> 1) - s; + u.u64 &= ~frac_mask; /* truncate to an integer */ + } + /* otherwise: abs(a) >= 2^52, or NaN, +/-Infinity: no change */ + return u.d; +} + +static JSValue js_math_hypot(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + double r, a; + int i; + + r = 0; + if (argc > 0) { + if (JS_ToFloat64(ctx, &r, argv[0])) + return JS_EXCEPTION; + if (argc == 1) { + r = fabs(r); + } else { + /* use the built-in function to minimize precision loss */ + for (i = 1; i < argc; i++) { + if (JS_ToFloat64(ctx, &a, argv[i])) + return JS_EXCEPTION; + r = hypot(r, a); + } + } + } + return js_float64(r); +} + +static double js_math_f16round(double a) +{ + return fromfp16(tofp16(a)); +} + +static double js_math_fround(double a) +{ + return (float)a; +} + +static JSValue js_math_imul(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + uint32_t a, b, c; + int32_t d; + + if (JS_ToUint32(ctx, &a, argv[0])) + return JS_EXCEPTION; + if (JS_ToUint32(ctx, &b, argv[1])) + return JS_EXCEPTION; + c = a * b; + memcpy(&d, &c, sizeof(d)); + return js_int32(d); +} + +static 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_int32(r); +} + +static JSValue js_math_sumPrecise(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue iter, next, item, ret; + bf_t a, b; + BOOL done; + double d; + int r; + + iter = JS_GetIterator(ctx, argv[0], /*async*/FALSE); + if (JS_IsException(iter)) + return JS_EXCEPTION; + bf_init(ctx->bf_ctx, &a); + bf_init(ctx->bf_ctx, &b); + ret = JS_EXCEPTION; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto fail; + bf_set_zero(&a, /*is_neg*/TRUE); + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) + break; // item == JS_UNDEFINED + switch (JS_VALUE_GET_TAG(item)) { + default: + JS_FreeValue(ctx, item); + JS_ThrowTypeError(ctx, "not a number"); + goto fail; + case JS_TAG_INT: + d = JS_VALUE_GET_INT(item); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(item); + break; + } + if (bf_set_float64(&b, d)) + goto oom; + // Infinity + -Infinity results in BF_ST_INVALID_OP, sets |a| to nan + if ((r = bf_add(&a, &a, &b, BF_PREC_INF, BF_RNDN))) + if (r != BF_ST_INVALID_OP) + goto oom; + } + bf_get_float64(&a, &d, BF_RNDN); // return value deliberately ignored + ret = js_float64(d); +fail: + JS_IteratorClose(ctx, iter, JS_IsException(ret)); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + bf_delete(&a); + bf_delete(&b); + return ret; +oom: + JS_ThrowOutOfMemory(ctx); + goto fail; +} + +/* 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; +} + +static void js_random_init(JSContext *ctx) +{ + ctx->random_state = js__gettimeofday_us(); + /* the state must be non zero */ + if (ctx->random_state == 0) + ctx->random_state = 1; +} + +static JSValue js_math_random(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSFloat64Union u; + uint64_t v; + + v = xorshift64star(&ctx->random_state); + /* 1.0 <= u.d < 2 */ + u.u64 = ((uint64_t)0x3ff << 52) | (v >> 12); + return js_float64(u.d - 1.0); +} + +/* use local wrappers for math functions to + - avoid initializing data with dynamic library entry points. + - avoid some overhead if the call can be inlined at compile or link time. + */ +static double js_math_fabs(double d) { return fabs(d); } +static double js_math_floor(double d) { return floor(d); } +static double js_math_ceil(double d) { return ceil(d); } +static double js_math_sqrt(double d) { return sqrt(d); } +static double js_math_acos(double d) { return acos(d); } +static double js_math_asin(double d) { return asin(d); } +static double js_math_atan(double d) { return atan(d); } +static double js_math_atan2(double a, double b) { return atan2(a, b); } +static double js_math_cos(double d) { return cos(d); } +static double js_math_exp(double d) { return exp(d); } +static double js_math_log(double d) { return log(d); } +static double js_math_sin(double d) { return sin(d); } +static double js_math_tan(double d) { return tan(d); } +static double js_math_trunc(double d) { return trunc(d); } +static double js_math_cosh(double d) { return cosh(d); } +static double js_math_sinh(double d) { return sinh(d); } +static double js_math_tanh(double d) { return tanh(d); } +static double js_math_acosh(double d) { return acosh(d); } +static double js_math_asinh(double d) { return asinh(d); } +static double js_math_atanh(double d) { return atanh(d); } +static double js_math_expm1(double d) { return expm1(d); } +static double js_math_log1p(double d) { return log1p(d); } +static double js_math_log2(double d) { return log2(d); } +static double js_math_log10(double d) { return log10(d); } +static double js_math_cbrt(double d) { return cbrt(d); } + +static const JSCFunctionListEntry js_math_funcs[] = { + 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("abs", 1, f_f, js_math_fabs ), + JS_CFUNC_SPECIAL_DEF("floor", 1, f_f, js_math_floor ), + JS_CFUNC_SPECIAL_DEF("ceil", 1, f_f, js_math_ceil ), + JS_CFUNC_SPECIAL_DEF("round", 1, f_f, js_math_round ), + JS_CFUNC_SPECIAL_DEF("sqrt", 1, f_f, js_math_sqrt ), + + JS_CFUNC_SPECIAL_DEF("acos", 1, f_f, js_math_acos ), + JS_CFUNC_SPECIAL_DEF("asin", 1, f_f, js_math_asin ), + JS_CFUNC_SPECIAL_DEF("atan", 1, f_f, js_math_atan ), + JS_CFUNC_SPECIAL_DEF("atan2", 2, f_f_f, js_math_atan2 ), + JS_CFUNC_SPECIAL_DEF("cos", 1, f_f, js_math_cos ), + JS_CFUNC_SPECIAL_DEF("exp", 1, f_f, js_math_exp ), + JS_CFUNC_SPECIAL_DEF("log", 1, f_f, js_math_log ), + JS_CFUNC_SPECIAL_DEF("pow", 2, f_f_f, js_math_pow ), + JS_CFUNC_SPECIAL_DEF("sin", 1, f_f, js_math_sin ), + JS_CFUNC_SPECIAL_DEF("tan", 1, f_f, js_math_tan ), + /* ES6 */ + JS_CFUNC_SPECIAL_DEF("trunc", 1, f_f, js_math_trunc ), + JS_CFUNC_SPECIAL_DEF("sign", 1, f_f, js_math_sign ), + JS_CFUNC_SPECIAL_DEF("cosh", 1, f_f, js_math_cosh ), + JS_CFUNC_SPECIAL_DEF("sinh", 1, f_f, js_math_sinh ), + JS_CFUNC_SPECIAL_DEF("tanh", 1, f_f, js_math_tanh ), + JS_CFUNC_SPECIAL_DEF("acosh", 1, f_f, js_math_acosh ), + JS_CFUNC_SPECIAL_DEF("asinh", 1, f_f, js_math_asinh ), + JS_CFUNC_SPECIAL_DEF("atanh", 1, f_f, js_math_atanh ), + JS_CFUNC_SPECIAL_DEF("expm1", 1, f_f, js_math_expm1 ), + JS_CFUNC_SPECIAL_DEF("log1p", 1, f_f, js_math_log1p ), + JS_CFUNC_SPECIAL_DEF("log2", 1, f_f, js_math_log2 ), + JS_CFUNC_SPECIAL_DEF("log10", 1, f_f, js_math_log10 ), + JS_CFUNC_SPECIAL_DEF("cbrt", 1, f_f, js_math_cbrt ), + JS_CFUNC_DEF("hypot", 2, js_math_hypot ), + JS_CFUNC_DEF("random", 0, js_math_random ), + JS_CFUNC_SPECIAL_DEF("f16round", 1, f_f, js_math_f16round ), + JS_CFUNC_SPECIAL_DEF("fround", 1, f_f, js_math_fround ), + JS_CFUNC_DEF("imul", 2, js_math_imul ), + JS_CFUNC_DEF("clz32", 1, js_math_clz32 ), + JS_CFUNC_DEF("sumPrecise", 1, js_math_sumPrecise ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Math", JS_PROP_CONFIGURABLE ), + 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 ), +}; + +static const JSCFunctionListEntry js_math_obj[] = { + JS_OBJECT_DEF("Math", js_math_funcs, countof(js_math_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), +}; + +/* Date */ + +/* OS dependent. d = argv[0] is in ms from 1970. Return the difference + between UTC time and local time 'd' in minutes */ +static int getTimezoneOffset(int64_t time) { +#if 0 + /* TODO: expose this as a hook */ +#if defined(_WIN32) + DWORD r; + TIME_ZONE_INFORMATION t; + r = GetTimeZoneInformation(&t); + if (r == TIME_ZONE_ID_INVALID) + return 0; + if (r == TIME_ZONE_ID_DAYLIGHT) + return (int)(t.Bias + t.DaylightBias); + return (int)t.Bias; +#else + time_t ti; + struct tm tm; + + time /= 1000; /* convert to seconds */ + if (sizeof(time_t) == 4) { + /* on 32-bit systems, we need to clamp the time value to the + range of `time_t`. This is better than truncating values to + 32 bits and hopefully provides the same result as 64-bit + implementation of localtime_r. + */ + if ((time_t)-1 < 0) { + if (time < INT32_MIN) { + time = INT32_MIN; + } else if (time > INT32_MAX) { + time = INT32_MAX; + } + } else { + if (time < 0) { + time = 0; + } else if (time > UINT32_MAX) { + time = UINT32_MAX; + } + } + } + ti = time; + localtime_r(&ti, &tm); +#ifdef NO_TM_GMTOFF + struct tm gmt; + gmtime_r(&ti, &gmt); + + /* disable DST adjustment on the local tm struct */ + tm.tm_isdst = 0; + + return (int)difftime(mktime(&gmt), mktime(&tm)) / 60; +#else + return -tm.tm_gmtoff / 60; +#endif /* NO_TM_GMTOFF */ +#endif /* _WIN32 */ +#endif /* 0 */ + return 0; +} + +/* RegExp */ + +static void js_regexp_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSRegExp *re = &p->u.regexp; + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->bytecode)); + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_STRING, re->pattern)); +} + +/* create a string containing the RegExp bytecode */ +static JSValue js_compile_regexp(JSContext *ctx, JSValue pattern, + JSValue flags) +{ + const char *str; + int re_flags, mask; + uint8_t *re_bytecode_buf; + size_t i, len; + int re_bytecode_len; + JSValue ret; + char error_msg[64]; + + re_flags = 0; + if (!JS_IsUndefined(flags)) { + str = JS_ToCStringLen(ctx, &len, flags); + if (!str) + return JS_EXCEPTION; + /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */ + for (i = 0; i < len; i++) { + switch(str[i]) { + case 'd': + mask = LRE_FLAG_INDICES; + break; + 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; + case 'v': + mask = LRE_FLAG_UNICODE_SETS; + break; + case 'y': + mask = LRE_FLAG_STICKY; + break; + default: + goto bad_flags; + } + if ((re_flags & mask) != 0) { + bad_flags: + JS_FreeCString(ctx, str); + return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); + } + re_flags |= mask; + } + JS_FreeCString(ctx, str); + } + + if (re_flags & LRE_FLAG_UNICODE) + if (re_flags & LRE_FLAG_UNICODE_SETS) + return JS_ThrowSyntaxError(ctx, "invalid regular expression flags"); + + str = JS_ToCStringLen2(ctx, &len, pattern, !(re_flags & LRE_FLAG_UNICODE)); + if (!str) + return JS_EXCEPTION; + re_bytecode_buf = lre_compile(&re_bytecode_len, error_msg, + sizeof(error_msg), str, len, re_flags, ctx); + JS_FreeCString(ctx, str); + if (!re_bytecode_buf) { + JS_ThrowSyntaxError(ctx, "%s", error_msg); + return JS_EXCEPTION; + } + + ret = js_new_string8_len(ctx, (char *)re_bytecode_buf, re_bytecode_len); + js_free(ctx, re_bytecode_buf); + return ret; +} + +/* create a RegExp object from a string containing the RegExp bytecode + and the source pattern */ +static JSValue js_regexp_constructor_internal(JSContext *ctx, JSValue ctor, + JSValue pattern, JSValue bc) +{ + JSValue obj; + JSObject *p; + JSRegExp *re; + + /* sanity check */ + if (JS_VALUE_GET_TAG(bc) != JS_TAG_STRING || + JS_VALUE_GET_TAG(pattern) != JS_TAG_STRING) { + JS_ThrowTypeError(ctx, "string expected"); + fail: + JS_FreeValue(ctx, bc); + JS_FreeValue(ctx, pattern); + return JS_EXCEPTION; + } + + obj = js_create_from_ctor(ctx, ctor, JS_CLASS_REGEXP); + if (JS_IsException(obj)) + goto fail; + p = JS_VALUE_GET_OBJ(obj); + re = &p->u.regexp; + re->pattern = JS_VALUE_GET_STRING(pattern); + re->bytecode = JS_VALUE_GET_STRING(bc); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_lastIndex, js_int32(0), + JS_PROP_WRITABLE); + return obj; +} + +static JSRegExp *js_get_regexp(JSContext *ctx, JSValue obj, BOOL throw_error) +{ + if (JS_VALUE_GET_TAG(obj) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(obj); + if (p->class_id == JS_CLASS_REGEXP) + return &p->u.regexp; + } + if (throw_error) { + JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP); + } + return NULL; +} + +/* return < 0 if exception or TRUE/FALSE */ +static int js_is_regexp(JSContext *ctx, JSValue obj) +{ + JSValue m; + + if (!JS_IsObject(obj)) + return FALSE; + m = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_match); + if (JS_IsException(m)) + return -1; + if (!JS_IsUndefined(m)) + return JS_ToBoolFree(ctx, m); + return js_get_regexp(ctx, obj, FALSE) != NULL; +} + +static JSValue js_regexp_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue pattern, flags, bc, val; + JSValue pat, flags1; + JSRegExp *re; + int pat_is_regexp; + + pat = argv[0]; + flags1 = argv[1]; + pat_is_regexp = js_is_regexp(ctx, pat); + if (pat_is_regexp < 0) + return JS_EXCEPTION; + if (JS_IsUndefined(new_target)) { + /* called as a function */ + new_target = JS_GetActiveFunction(ctx); + if (pat_is_regexp && JS_IsUndefined(flags1)) { + JSValue ctor; + BOOL res; + ctor = JS_GetProperty(ctx, pat, JS_ATOM_constructor); + if (JS_IsException(ctor)) + return ctor; + res = js_same_value(ctx, ctor, new_target); + JS_FreeValue(ctx, ctor); + if (res) + return js_dup(pat); + } + } + re = js_get_regexp(ctx, pat, FALSE); + if (re) { + pattern = js_dup(JS_MKPTR(JS_TAG_STRING, re->pattern)); + if (JS_IsUndefined(flags1)) { + bc = js_dup(JS_MKPTR(JS_TAG_STRING, re->bytecode)); + goto no_compilation; + } else { + flags = JS_ToString(ctx, flags1); + if (JS_IsException(flags)) + goto fail; + } + } else { + flags = JS_UNDEFINED; + if (pat_is_regexp) { + pattern = JS_GetProperty(ctx, pat, JS_ATOM_source); + if (JS_IsException(pattern)) + goto fail; + if (JS_IsUndefined(flags1)) { + flags = JS_GetProperty(ctx, pat, JS_ATOM_flags); + if (JS_IsException(flags)) + goto fail; + } else { + flags = js_dup(flags1); + } + } else { + pattern = js_dup(pat); + flags = js_dup(flags1); + } + if (JS_IsUndefined(pattern)) { + pattern = JS_AtomToString(ctx, JS_ATOM_empty_string); + } else { + val = pattern; + pattern = JS_ToString(ctx, val); + JS_FreeValue(ctx, val); + if (JS_IsException(pattern)) + goto fail; + } + } + bc = js_compile_regexp(ctx, pattern, flags); + if (JS_IsException(bc)) + goto fail; + JS_FreeValue(ctx, flags); + no_compilation: + return js_regexp_constructor_internal(ctx, new_target, pattern, bc); + fail: + JS_FreeValue(ctx, pattern); + JS_FreeValue(ctx, flags); + return JS_EXCEPTION; +} + +static JSValue js_regexp_compile(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSRegExp *re1, *re; + JSValue pattern1, flags1; + JSValue bc, pattern; + + re = js_get_regexp(ctx, this_val, TRUE); + if (!re) + return JS_EXCEPTION; + pattern1 = argv[0]; + flags1 = argv[1]; + re1 = js_get_regexp(ctx, pattern1, FALSE); + if (re1) { + if (!JS_IsUndefined(flags1)) + return JS_ThrowTypeError(ctx, "flags must be undefined"); + pattern = js_dup(JS_MKPTR(JS_TAG_STRING, re1->pattern)); + bc = js_dup(JS_MKPTR(JS_TAG_STRING, re1->bytecode)); + } else { + bc = JS_UNDEFINED; + if (JS_IsUndefined(pattern1)) + pattern = JS_AtomToString(ctx, JS_ATOM_empty_string); + else + pattern = JS_ToString(ctx, pattern1); + if (JS_IsException(pattern)) + goto fail; + bc = js_compile_regexp(ctx, pattern, flags1); + if (JS_IsException(bc)) + goto fail; + } + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->pattern)); + JS_FreeValue(ctx, JS_MKPTR(JS_TAG_STRING, re->bytecode)); + re->pattern = JS_VALUE_GET_STRING(pattern); + re->bytecode = JS_VALUE_GET_STRING(bc); + if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, + js_int32(0)) < 0) + return JS_EXCEPTION; + return js_dup(this_val); + fail: + JS_FreeValue(ctx, pattern); + JS_FreeValue(ctx, bc); + return JS_EXCEPTION; +} + +static JSValue js_regexp_get_source(JSContext *ctx, JSValue this_val) +{ + JSRegExp *re; + JSString *p; + StringBuffer b_s, *b = &b_s; + int i, n, c, c2, bra; + + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + + if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP])) + goto empty_regex; + + re = js_get_regexp(ctx, this_val, TRUE); + if (!re) + return JS_EXCEPTION; + + p = re->pattern; + + if (p->len == 0) { + empty_regex: + return js_new_string8(ctx, "(?:)"); + } + string_buffer_init2(ctx, b, p->len, p->is_wide_char); + + /* Escape '/' and newline sequences as needed */ + bra = 0; + for (i = 0, n = p->len; i < n;) { + c2 = -1; + switch (c = string_get(p, i++)) { + case '\\': + if (i < n) + c2 = string_get(p, i++); + break; + case ']': + bra = 0; + break; + case '[': + if (!bra) { + if (i < n && string_get(p, i) == ']') + c2 = string_get(p, i++); + bra = 1; + } + break; + case '\n': + c = '\\'; + c2 = 'n'; + break; + case '\r': + c = '\\'; + c2 = 'r'; + break; + case '/': + if (!bra) { + c = '\\'; + c2 = '/'; + } + break; + } + string_buffer_putc16(b, c); + if (c2 >= 0) + string_buffer_putc16(b, c2); + } + return string_buffer_end(b); +} + +static JSValue js_regexp_get_flag(JSContext *ctx, JSValue this_val, int mask) +{ + JSRegExp *re; + int flags; + + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + + re = js_get_regexp(ctx, this_val, FALSE); + if (!re) { + if (js_same_value(ctx, this_val, ctx->class_proto[JS_CLASS_REGEXP])) + return JS_UNDEFINED; + else + return JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_REGEXP); + } + + flags = lre_get_flags(re->bytecode->u.str8); + return js_bool(flags & mask); +} + +static JSValue js_regexp_get_flags(JSContext *ctx, JSValue this_val) +{ + char str[8], *p = str; + int res; + + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + + res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "hasIndices")); + if (res < 0) + goto exception; + if (res) + *p++ = 'd'; + res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_global)); + if (res < 0) + goto exception; + if (res) + *p++ = 'g'; + res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "ignoreCase")); + if (res < 0) + goto exception; + if (res) + *p++ = 'i'; + res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "multiline")); + if (res < 0) + goto exception; + if (res) + *p++ = 'm'; + res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "dotAll")); + if (res < 0) + goto exception; + if (res) + *p++ = 's'; + res = JS_ToBoolFree(ctx, JS_GetProperty(ctx, this_val, JS_ATOM_unicode)); + if (res < 0) + goto exception; + if (res) + *p++ = 'u'; + res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "unicodeSets")); + if (res < 0) + goto exception; + if (res) + *p++ = 'v'; + res = JS_ToBoolFree(ctx, JS_GetPropertyStr(ctx, this_val, "sticky")); + if (res < 0) + goto exception; + if (res) + *p++ = 'y'; + if (p == str) + return JS_AtomToString(ctx, JS_ATOM_empty_string); + return js_new_string8_len(ctx, str, p - str); + +exception: + return JS_EXCEPTION; +} + +static JSValue js_regexp_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue pattern, flags; + StringBuffer b_s, *b = &b_s; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + string_buffer_init(ctx, b, 0); + string_buffer_putc8(b, '/'); + pattern = JS_GetProperty(ctx, this_val, JS_ATOM_source); + if (string_buffer_concat_value_free(b, pattern)) + goto fail; + string_buffer_putc8(b, '/'); + flags = JS_GetProperty(ctx, this_val, JS_ATOM_flags); + if (string_buffer_concat_value_free(b, flags)) + goto fail; + return string_buffer_end(b); + +fail: + string_buffer_free(b); + return JS_EXCEPTION; +} + +BOOL lre_check_stack_overflow(void *opaque, size_t alloca_size) +{ + if (!opaque) + return 0; + JSContext *ctx = opaque; + return js_check_stack_overflow(ctx->rt, alloca_size); +} + +#if 0 +void *lre_realloc(void *opaque, void *ptr, size_t size) +{ + JSContext *ctx = opaque; + /* No JS exception is raised here */ + return js_realloc_rt(ctx->rt, ptr, size); +} +#endif + +static JSValue js_regexp_escape(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + StringBuffer b_s, *b = &b_s; + JSString *p; + uint32_t c, i; + char s[16]; + + if (!JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "not a string"); + p = JS_VALUE_GET_STRING(argv[0]); + string_buffer_init2(ctx, b, 0, p->is_wide_char); + for (i = 0; i < p->len; i++) { + c = p->is_wide_char ? (uint32_t)p->u.str16[i] : (uint32_t)p->u.str8[i]; + if (c < 33) { + if (c >= 9 && c <= 13) { + string_buffer_putc8(b, '\\'); + string_buffer_putc8(b, "tnvfr"[c - 9]); + } else { + goto hex2; + } + } else if (c < 128) { + if ((c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z')) { + if (i == 0) + goto hex2; + } else if (strchr(",-=<>#&!%:;@~'`\"", c)) { + goto hex2; + } else if (c != '_') { + string_buffer_putc8(b, '\\'); + } + string_buffer_putc8(b, c); + } else if (c < 256) { + hex2: + snprintf(s, sizeof(s), "\\x%02x", c); + string_buffer_puts8(b, s); + } else if (is_surrogate(c) || lre_is_white_space(c) || c == 0xFEFF) { + snprintf(s, sizeof(s), "\\u%04x", c); + string_buffer_puts8(b, s); + } else { + string_buffer_putc16(b, c); + } + } + return string_buffer_end(b); +} + +static JSValue js_regexp_exec(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); + JSString *str; + JSValue t, ret, str_val, obj, val, groups; + JSValue indices, indices_groups; + uint8_t *re_bytecode; + uint8_t **capture, *str_buf; + int rc, capture_count, shift, i, re_flags; + int64_t last_index; + const char *group_name_ptr; + + if (!re) + return JS_EXCEPTION; + + str_val = JS_ToString(ctx, argv[0]); + if (JS_IsException(str_val)) + return JS_EXCEPTION; + + ret = JS_EXCEPTION; + obj = JS_NULL; + groups = JS_UNDEFINED; + indices = JS_UNDEFINED; + indices_groups = JS_UNDEFINED; + capture = NULL; + + val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); + if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) + goto fail; + + re_bytecode = re->bytecode->u.str8; + re_flags = lre_get_flags(re_bytecode); + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { + last_index = 0; + } + str = JS_VALUE_GET_STRING(str_val); + capture_count = lre_get_capture_count(re_bytecode); + if (capture_count > 0) { + capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2); + if (!capture) + goto fail; + } + shift = str->is_wide_char; + str_buf = str->u.str8; + if (last_index > str->len) { + rc = 2; + } else { + rc = lre_exec(capture, re_bytecode, + str_buf, last_index, str->len, + shift, ctx); + } + if (rc != 1) { + if (rc >= 0) { + if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { + if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, + js_int32(0)) < 0) + goto fail; + } + } else { + JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + goto fail; + } + } else { + int prop_flags; + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, + js_int32((capture[1] - str_buf) >> shift)) < 0) + goto fail; + } + obj = JS_NewArray(ctx); + if (JS_IsException(obj)) + goto fail; + prop_flags = JS_PROP_C_W_E | JS_PROP_THROW; + group_name_ptr = lre_get_groupnames(re_bytecode); + if (group_name_ptr) { + groups = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(groups)) + goto fail; + } + if (re_flags & LRE_FLAG_INDICES) { + indices = JS_NewArray(ctx); + if (JS_IsException(indices)) + goto fail; + if (group_name_ptr) { + indices_groups = JS_NewObjectProto(ctx, JS_NULL); + if (JS_IsException(indices_groups)) + goto fail; + } + } + + for(i = 0; i < capture_count; i++) { + const char *name = NULL; + uint8_t **match = &capture[2 * i]; + int start = -1; + int end = -1; + + if (group_name_ptr && i > 0) { + if (*group_name_ptr) name = group_name_ptr; + group_name_ptr += strlen(group_name_ptr) + 1; + } + + if (match[0] && match[1]) { + start = (match[0] - str_buf) >> shift; + end = (match[1] - str_buf) >> shift; + } + + if (!JS_IsUndefined(indices)) { + JSValue val = JS_UNDEFINED; + if (start != -1) { + val = JS_NewArray(ctx); + if (JS_IsException(val)) + goto fail; + if (JS_DefinePropertyValueUint32(ctx, val, 0, + js_int32(start), + prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + if (JS_DefinePropertyValueUint32(ctx, val, 1, + js_int32(end), + prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + } + if (name && !JS_IsUndefined(indices_groups)) { + val = js_dup(val); + if (JS_DefinePropertyValueStr(ctx, indices_groups, + name, val, prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + } + if (JS_DefinePropertyValueUint32(ctx, indices, i, val, + prop_flags) < 0) { + goto fail; + } + } + + JSValue val = JS_UNDEFINED; + if (start != -1) { + val = js_sub_string(ctx, str, start, end); + if (JS_IsException(val)) + goto fail; + } + + if (name) { + if (JS_DefinePropertyValueStr(ctx, groups, name, + js_dup(val), + prop_flags) < 0) { + JS_FreeValue(ctx, val); + goto fail; + } + } + + if (JS_DefinePropertyValueUint32(ctx, obj, i, val, prop_flags) < 0) + goto fail; + } + + t = groups, groups = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_groups, + t, prop_flags) < 0) { + goto fail; + } + + t = js_int32((capture[0] - str_buf) >> shift); + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_index, t, prop_flags) < 0) + goto fail; + + t = str_val, str_val = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_input, t, prop_flags) < 0) + goto fail; + + if (!JS_IsUndefined(indices)) { + t = indices_groups, indices_groups = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, indices, JS_ATOM_groups, + t, prop_flags) < 0) { + goto fail; + } + t = indices, indices = JS_UNDEFINED; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_indices, + t, prop_flags) < 0) { + goto fail; + } + } + } + ret = obj; + obj = JS_UNDEFINED; +fail: + JS_FreeValue(ctx, indices_groups); + JS_FreeValue(ctx, indices); + JS_FreeValue(ctx, str_val); + JS_FreeValue(ctx, groups); + JS_FreeValue(ctx, obj); + js_free(ctx, capture); + return ret; +} + +/* delete portions of a string that match a given regex */ +static JSValue JS_RegExpDelete(JSContext *ctx, JSValue this_val, JSValue arg) +{ + JSRegExp *re = js_get_regexp(ctx, this_val, TRUE); + JSString *str; + JSValue str_val, val; + uint8_t *re_bytecode; + int ret; + uint8_t **capture, *str_buf; + int capture_count, shift, re_flags; + int next_src_pos, start, end; + int64_t last_index; + StringBuffer b_s, *b = &b_s; + + if (!re) + return JS_EXCEPTION; + + string_buffer_init(ctx, b, 0); + + capture = NULL; + str_val = JS_ToString(ctx, arg); + if (JS_IsException(str_val)) + goto fail; + str = JS_VALUE_GET_STRING(str_val); + re_bytecode = re->bytecode->u.str8; + re_flags = lre_get_flags(re_bytecode); + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { + last_index = 0; + } else { + val = JS_GetProperty(ctx, this_val, JS_ATOM_lastIndex); + if (JS_IsException(val) || JS_ToLengthFree(ctx, &last_index, val)) + goto fail; + } + capture_count = lre_get_capture_count(re_bytecode); + if (capture_count > 0) { + capture = js_malloc(ctx, sizeof(capture[0]) * capture_count * 2); + if (!capture) + goto fail; + } + shift = str->is_wide_char; + str_buf = str->u.str8; + next_src_pos = 0; + for (;;) { + if (last_index > str->len) + break; + + ret = lre_exec(capture, re_bytecode, + str_buf, last_index, str->len, shift, ctx); + if (ret != 1) { + if (ret >= 0) { + if (ret == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { + if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, + js_int32(0)) < 0) + goto fail; + } + } else { + JS_ThrowInternalError(ctx, "out of memory in regexp execution"); + goto fail; + } + break; + } + start = (capture[0] - str_buf) >> shift; + end = (capture[1] - str_buf) >> shift; + last_index = end; + if (next_src_pos < start) { + if (string_buffer_concat(b, str, next_src_pos, start)) + goto fail; + } + next_src_pos = end; + if (!(re_flags & LRE_FLAG_GLOBAL)) { + if (JS_SetProperty(ctx, this_val, JS_ATOM_lastIndex, + js_int32(end)) < 0) + goto fail; + break; + } + if (end == start) { + if (!(re_flags & LRE_FLAG_UNICODE) || (unsigned)end >= str->len || !str->is_wide_char) { + end++; + } else { + string_getc(str, &end); + } + } + last_index = end; + } + if (string_buffer_concat(b, str, next_src_pos, str->len)) + goto fail; + JS_FreeValue(ctx, str_val); + js_free(ctx, capture); + return string_buffer_end(b); +fail: + JS_FreeValue(ctx, str_val); + js_free(ctx, capture); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue JS_RegExpExec(JSContext *ctx, JSValue r, JSValue s) +{ + JSValue method, ret; + + method = JS_GetProperty(ctx, r, JS_ATOM_exec); + if (JS_IsException(method)) + return method; + if (JS_IsFunction(ctx, method)) { + ret = JS_CallFree(ctx, method, r, 1, &s); + if (JS_IsException(ret)) + return ret; + if (!JS_IsObject(ret) && !JS_IsNull(ret)) { + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "RegExp exec method must return an object or null"); + } + return ret; + } + JS_FreeValue(ctx, method); + return js_regexp_exec(ctx, r, 1, &s); +} + +static JSValue js_regexp_test(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val; + BOOL ret; + + val = JS_RegExpExec(ctx, this_val, argv[0]); + if (JS_IsException(val)) + return JS_EXCEPTION; + ret = !JS_IsNull(val); + JS_FreeValue(ctx, val); + return js_bool(ret); +} + +static JSValue js_regexp_Symbol_match(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // [Symbol.match](str) + JSValue rx = this_val; + JSValue A, S, flags, result, matchStr; + int global, n, fullUnicode, isEmpty; + JSString *p; + + if (!JS_IsObject(rx)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + A = JS_UNDEFINED; + flags = JS_UNDEFINED; + result = JS_UNDEFINED; + matchStr = JS_UNDEFINED; + S = JS_ToString(ctx, argv[0]); + if (JS_IsException(S)) + goto exception; + + flags = JS_GetProperty(ctx, rx, JS_ATOM_flags); + if (JS_IsException(flags)) + goto exception; + flags = JS_ToStringFree(ctx, flags); + if (JS_IsException(flags)) + goto exception; + p = JS_VALUE_GET_STRING(flags); + + // TODO(bnoordhuis) query 'u' flag the same way? + global = (-1 != string_indexof_char(p, 'g', 0)); + if (!global) { + A = JS_RegExpExec(ctx, rx, S); + } else { + fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode)); + if (fullUnicode < 0) + goto exception; + + if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int32(0)) < 0) + goto exception; + A = JS_NewArray(ctx); + if (JS_IsException(A)) + goto exception; + n = 0; + for(;;) { + JS_FreeValue(ctx, result); + result = JS_RegExpExec(ctx, rx, S); + if (JS_IsException(result)) + goto exception; + if (JS_IsNull(result)) + break; + matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); + if (JS_IsException(matchStr)) + goto exception; + isEmpty = JS_IsEmptyString(matchStr); + if (JS_SetPropertyInt64(ctx, A, n++, matchStr) < 0) + goto exception; + if (isEmpty) { + int64_t thisIndex, nextIndex; + if (JS_ToLengthFree(ctx, &thisIndex, + JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0) + goto exception; + p = JS_VALUE_GET_STRING(S); + nextIndex = string_advance_index(p, thisIndex, fullUnicode); + if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int64(nextIndex)) < 0) + goto exception; + } + } + if (n == 0) { + JS_FreeValue(ctx, A); + A = JS_NULL; + } + } + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, flags); + JS_FreeValue(ctx, S); + return A; + +exception: + JS_FreeValue(ctx, A); + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, flags); + JS_FreeValue(ctx, S); + return JS_EXCEPTION; +} + +typedef struct JSRegExpStringIteratorData { + JSValue iterating_regexp; + JSValue iterated_string; + BOOL global; + BOOL unicode; + BOOL done; +} JSRegExpStringIteratorData; + +static void js_regexp_string_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data; + if (it) { + JS_FreeValueRT(rt, it->iterating_regexp); + JS_FreeValueRT(rt, it->iterated_string); + js_free_rt(rt, it); + } +} + +static void js_regexp_string_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSRegExpStringIteratorData *it = p->u.regexp_string_iterator_data; + if (it) { + JS_MarkValue(rt, it->iterating_regexp, mark_func); + JS_MarkValue(rt, it->iterated_string, mark_func); + } +} + +static JSValue js_regexp_string_iterator_next(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSRegExpStringIteratorData *it; + JSValue R, S; + JSValue matchStr = JS_UNDEFINED, match = JS_UNDEFINED; + JSString *sp; + + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_REGEXP_STRING_ITERATOR); + if (!it) + goto exception; + if (it->done) { + *pdone = TRUE; + return JS_UNDEFINED; + } + R = it->iterating_regexp; + S = it->iterated_string; + match = JS_RegExpExec(ctx, R, S); + if (JS_IsException(match)) + goto exception; + if (JS_IsNull(match)) { + it->done = TRUE; + *pdone = TRUE; + return JS_UNDEFINED; + } else if (it->global) { + matchStr = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, match, 0)); + if (JS_IsException(matchStr)) + goto exception; + if (JS_IsEmptyString(matchStr)) { + int64_t thisIndex, nextIndex; + if (JS_ToLengthFree(ctx, &thisIndex, + JS_GetProperty(ctx, R, JS_ATOM_lastIndex)) < 0) + goto exception; + sp = JS_VALUE_GET_STRING(S); + nextIndex = string_advance_index(sp, thisIndex, it->unicode); + if (JS_SetProperty(ctx, R, JS_ATOM_lastIndex, js_int64(nextIndex)) < 0) + goto exception; + } + JS_FreeValue(ctx, matchStr); + } else { + it->done = TRUE; + } + *pdone = FALSE; + return match; + exception: + JS_FreeValue(ctx, match); + JS_FreeValue(ctx, matchStr); + *pdone = FALSE; + return JS_EXCEPTION; +} + +static JSValue js_regexp_Symbol_matchAll(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // [Symbol.matchAll](str) + JSValue R = this_val; + JSValue S, C, flags, matcher, iter; + JSValue args[2]; + JSString *strp; + int64_t lastIndex; + JSRegExpStringIteratorData *it; + + if (!JS_IsObject(R)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + C = JS_UNDEFINED; + flags = JS_UNDEFINED; + matcher = JS_UNDEFINED; + iter = JS_UNDEFINED; + + S = JS_ToString(ctx, argv[0]); + if (JS_IsException(S)) + goto exception; + C = JS_SpeciesConstructor(ctx, R, ctx->regexp_ctor); + if (JS_IsException(C)) + goto exception; + flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, R, JS_ATOM_flags)); + if (JS_IsException(flags)) + goto exception; + args[0] = R; + args[1] = flags; + matcher = JS_CallConstructor(ctx, C, 2, args); + if (JS_IsException(matcher)) + goto exception; + if (JS_ToLengthFree(ctx, &lastIndex, + JS_GetProperty(ctx, R, JS_ATOM_lastIndex))) + goto exception; + if (JS_SetProperty(ctx, matcher, JS_ATOM_lastIndex, js_int64(lastIndex)) < 0) + goto exception; + + iter = JS_NewObjectClass(ctx, JS_CLASS_REGEXP_STRING_ITERATOR); + if (JS_IsException(iter)) + goto exception; + it = js_malloc(ctx, sizeof(*it)); + if (!it) + goto exception; + it->iterating_regexp = matcher; + it->iterated_string = S; + strp = JS_VALUE_GET_STRING(flags); + it->global = string_indexof_char(strp, 'g', 0) >= 0; + it->unicode = string_indexof_char(strp, 'u', 0) >= 0; + it->done = FALSE; + JS_SetOpaqueInternal(iter, it); + + JS_FreeValue(ctx, C); + JS_FreeValue(ctx, flags); + return iter; + exception: + JS_FreeValue(ctx, S); + JS_FreeValue(ctx, C); + JS_FreeValue(ctx, flags); + JS_FreeValue(ctx, matcher); + JS_FreeValue(ctx, iter); + return JS_EXCEPTION; +} + +typedef struct ValueBuffer { + JSContext *ctx; + JSValue *arr; + JSValue def[4]; + int len; + int size; + int error_status; +} ValueBuffer; + +static int value_buffer_init(JSContext *ctx, ValueBuffer *b) +{ + b->ctx = ctx; + b->len = 0; + b->size = 4; + b->error_status = 0; + b->arr = b->def; + return 0; +} + +static void value_buffer_free(ValueBuffer *b) +{ + while (b->len > 0) + JS_FreeValue(b->ctx, b->arr[--b->len]); + if (b->arr != b->def) + js_free(b->ctx, b->arr); + b->arr = b->def; + b->size = 4; +} + +static int value_buffer_append(ValueBuffer *b, JSValue val) +{ + if (b->error_status) + return -1; + + if (b->len >= b->size) { + int new_size = (b->len + (b->len >> 1) + 31) & ~16; + size_t slack; + JSValue *new_arr; + + if (b->arr == b->def) { + new_arr = js_realloc2(b->ctx, NULL, sizeof(*b->arr) * new_size, &slack); + if (new_arr) + memcpy(new_arr, b->def, sizeof b->def); + } else { + new_arr = js_realloc2(b->ctx, b->arr, sizeof(*b->arr) * new_size, &slack); + } + if (!new_arr) { + value_buffer_free(b); + JS_FreeValue(b->ctx, val); + b->error_status = -1; + return -1; + } + new_size += slack / sizeof(*new_arr); + b->arr = new_arr; + b->size = new_size; + } + b->arr[b->len++] = val; + return 0; +} + +static int js_is_standard_regexp(JSContext *ctx, JSValue rx) +{ + JSValue val; + int res; + + val = JS_GetProperty(ctx, rx, JS_ATOM_constructor); + if (JS_IsException(val)) + return -1; + // rx.constructor === RegExp + res = js_same_value(ctx, val, ctx->regexp_ctor); + JS_FreeValue(ctx, val); + if (res) { + val = JS_GetProperty(ctx, rx, JS_ATOM_exec); + if (JS_IsException(val)) + return -1; + // rx.exec === RE_exec + res = JS_IsCFunction(ctx, val, js_regexp_exec, 0); + JS_FreeValue(ctx, val); + } + return res; +} + +static JSValue js_regexp_Symbol_replace(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // [Symbol.replace](str, rep) + JSValue rx = this_val, rep = argv[1]; + JSValue args[6]; + JSValue flags, str, rep_val, matched, tab, rep_str, namedCaptures, res; + JSString *p, *sp, *rp; + StringBuffer b_s, *b = &b_s; + ValueBuffer v_b, *results = &v_b; + int nextSourcePosition, n, j, functionalReplace, is_global, fullUnicode; + uint32_t nCaptures; + int64_t position; + + if (!JS_IsObject(rx)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + string_buffer_init(ctx, b, 0); + value_buffer_init(ctx, results); + + rep_val = JS_UNDEFINED; + matched = JS_UNDEFINED; + tab = JS_UNDEFINED; + flags = JS_UNDEFINED; + rep_str = JS_UNDEFINED; + namedCaptures = JS_UNDEFINED; + + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + goto exception; + + sp = JS_VALUE_GET_STRING(str); + rp = NULL; + functionalReplace = JS_IsFunction(ctx, rep); + if (!functionalReplace) { + rep_val = JS_ToString(ctx, rep); + if (JS_IsException(rep_val)) + goto exception; + rp = JS_VALUE_GET_STRING(rep_val); + } + + flags = JS_GetProperty(ctx, rx, JS_ATOM_flags); + if (JS_IsException(flags)) + goto exception; + flags = JS_ToStringFree(ctx, flags); + if (JS_IsException(flags)) + goto exception; + p = JS_VALUE_GET_STRING(flags); + + // TODO(bnoordhuis) query 'u' flag the same way? + fullUnicode = 0; + is_global = (-1 != string_indexof_char(p, 'g', 0)); + if (is_global) { + fullUnicode = JS_ToBoolFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_unicode)); + if (fullUnicode < 0) + goto exception; + if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int32(0)) < 0) + goto exception; + } + + if (rp && rp->len == 0 && is_global && js_is_standard_regexp(ctx, rx)) { + /* use faster version for simple cases */ + res = JS_RegExpDelete(ctx, rx, str); + goto done; + } + for(;;) { + JSValue result; + result = JS_RegExpExec(ctx, rx, str); + if (JS_IsException(result)) + goto exception; + if (JS_IsNull(result)) + break; + if (value_buffer_append(results, result) < 0) + goto exception; + if (!is_global) + break; + JS_FreeValue(ctx, matched); + matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); + if (JS_IsException(matched)) + goto exception; + if (JS_IsEmptyString(matched)) { + /* always advance of at least one char */ + int64_t thisIndex, nextIndex; + if (JS_ToLengthFree(ctx, &thisIndex, JS_GetProperty(ctx, rx, JS_ATOM_lastIndex)) < 0) + goto exception; + nextIndex = string_advance_index(sp, thisIndex, fullUnicode); + if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int64(nextIndex)) < 0) + goto exception; + } + } + nextSourcePosition = 0; + for(j = 0; j < results->len; j++) { + JSValue result; + result = results->arr[j]; + if (js_get_length32(ctx, &nCaptures, result) < 0) + goto exception; + JS_FreeValue(ctx, matched); + matched = JS_ToStringFree(ctx, JS_GetPropertyInt64(ctx, result, 0)); + if (JS_IsException(matched)) + goto exception; + if (JS_ToLengthFree(ctx, &position, JS_GetProperty(ctx, result, JS_ATOM_index))) + goto exception; + if (position > sp->len) + position = sp->len; + else if (position < 0) + position = 0; + /* ignore substition if going backward (can happen + with custom regexp object) */ + JS_FreeValue(ctx, tab); + tab = JS_NewArray(ctx); + if (JS_IsException(tab)) + goto exception; + if (JS_DefinePropertyValueInt64(ctx, tab, 0, js_dup(matched), + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + for(n = 1; n < nCaptures; n++) { + JSValue capN; + capN = JS_GetPropertyInt64(ctx, result, n); + if (JS_IsException(capN)) + goto exception; + if (!JS_IsUndefined(capN)) { + capN = JS_ToStringFree(ctx, capN); + if (JS_IsException(capN)) + goto exception; + } + if (JS_DefinePropertyValueInt64(ctx, tab, n, capN, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + JS_FreeValue(ctx, namedCaptures); + namedCaptures = JS_GetProperty(ctx, result, JS_ATOM_groups); + if (JS_IsException(namedCaptures)) + goto exception; + if (functionalReplace) { + if (JS_DefinePropertyValueInt64(ctx, tab, n++, js_int32(position), JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + if (JS_DefinePropertyValueInt64(ctx, tab, n++, js_dup(str), JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + if (!JS_IsUndefined(namedCaptures)) { + if (JS_DefinePropertyValueInt64(ctx, tab, n++, js_dup(namedCaptures), JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + } + args[0] = JS_UNDEFINED; + args[1] = tab; + JS_FreeValue(ctx, rep_str); + rep_str = JS_ToStringFree(ctx, js_function_apply(ctx, rep, 2, args, 0)); + } else { + JSValue namedCaptures1; + if (!JS_IsUndefined(namedCaptures)) { + namedCaptures1 = JS_ToObject(ctx, namedCaptures); + if (JS_IsException(namedCaptures1)) + goto exception; + } else { + namedCaptures1 = JS_UNDEFINED; + } + args[0] = matched; + args[1] = str; + args[2] = js_int32(position); + args[3] = tab; + args[4] = namedCaptures1; + args[5] = rep_val; + JS_FreeValue(ctx, rep_str); + rep_str = js_string___GetSubstitution(ctx, JS_UNDEFINED, 6, args); + JS_FreeValue(ctx, namedCaptures1); + } + if (JS_IsException(rep_str)) + goto exception; + if (position >= nextSourcePosition) { + string_buffer_concat(b, sp, nextSourcePosition, position); + string_buffer_concat_value(b, rep_str); + nextSourcePosition = position + JS_VALUE_GET_STRING(matched)->len; + } + } + string_buffer_concat(b, sp, nextSourcePosition, sp->len); + res = string_buffer_end(b); + goto done1; + +exception: + res = JS_EXCEPTION; +done: + string_buffer_free(b); +done1: + value_buffer_free(results); + JS_FreeValue(ctx, rep_val); + JS_FreeValue(ctx, matched); + JS_FreeValue(ctx, flags); + JS_FreeValue(ctx, tab); + JS_FreeValue(ctx, rep_str); + JS_FreeValue(ctx, namedCaptures); + JS_FreeValue(ctx, str); + return res; +} + +static JSValue js_regexp_Symbol_search(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue rx = this_val; + JSValue str, previousLastIndex, currentLastIndex, result, index; + + if (!JS_IsObject(rx)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + result = JS_UNDEFINED; + currentLastIndex = JS_UNDEFINED; + previousLastIndex = JS_UNDEFINED; + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + goto exception; + + previousLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex); + if (JS_IsException(previousLastIndex)) + goto exception; + + if (!js_same_value(ctx, previousLastIndex, js_int32(0))) { + if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, js_int32(0)) < 0) { + goto exception; + } + } + result = JS_RegExpExec(ctx, rx, str); + if (JS_IsException(result)) + goto exception; + currentLastIndex = JS_GetProperty(ctx, rx, JS_ATOM_lastIndex); + if (JS_IsException(currentLastIndex)) + goto exception; + if (js_same_value(ctx, currentLastIndex, previousLastIndex)) { + JS_FreeValue(ctx, previousLastIndex); + } else { + if (JS_SetProperty(ctx, rx, JS_ATOM_lastIndex, previousLastIndex) < 0) { + previousLastIndex = JS_UNDEFINED; + goto exception; + } + } + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, currentLastIndex); + + if (JS_IsNull(result)) { + return js_int32(-1); + } else { + index = JS_GetProperty(ctx, result, JS_ATOM_index); + JS_FreeValue(ctx, result); + return index; + } + +exception: + JS_FreeValue(ctx, result); + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, currentLastIndex); + JS_FreeValue(ctx, previousLastIndex); + return JS_EXCEPTION; +} + +static JSValue js_regexp_Symbol_split(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // [Symbol.split](str, limit) + JSValue rx = this_val; + JSValue args[2]; + JSValue str, ctor, splitter, A, flags, z, sub; + JSString *strp; + uint32_t lim, size, p, q; + int unicodeMatching; + int64_t lengthA, e, numberOfCaptures, i; + + if (!JS_IsObject(rx)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + ctor = JS_UNDEFINED; + splitter = JS_UNDEFINED; + A = JS_UNDEFINED; + flags = JS_UNDEFINED; + z = JS_UNDEFINED; + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + goto exception; + ctor = JS_SpeciesConstructor(ctx, rx, ctx->regexp_ctor); + if (JS_IsException(ctor)) + goto exception; + flags = JS_ToStringFree(ctx, JS_GetProperty(ctx, rx, JS_ATOM_flags)); + if (JS_IsException(flags)) + goto exception; + strp = JS_VALUE_GET_STRING(flags); + unicodeMatching = string_indexof_char(strp, 'u', 0) >= 0; + if (string_indexof_char(strp, 'y', 0) < 0) { + flags = JS_ConcatString3(ctx, "", flags, "y"); + if (JS_IsException(flags)) + goto exception; + } + args[0] = rx; + args[1] = flags; + splitter = JS_CallConstructor(ctx, ctor, 2, args); + if (JS_IsException(splitter)) + goto exception; + A = JS_NewArray(ctx); + if (JS_IsException(A)) + goto exception; + lengthA = 0; + if (JS_IsUndefined(argv[1])) { + lim = 0xffffffff; + } else { + if (JS_ToUint32(ctx, &lim, argv[1]) < 0) + goto exception; + if (lim == 0) + goto done; + } + strp = JS_VALUE_GET_STRING(str); + p = q = 0; + size = strp->len; + if (size == 0) { + z = JS_RegExpExec(ctx, splitter, str); + if (JS_IsException(z)) + goto exception; + if (JS_IsNull(z)) + goto add_tail; + goto done; + } + while (q < size) { + if (JS_SetProperty(ctx, splitter, JS_ATOM_lastIndex, js_int32(q)) < 0) + goto exception; + JS_FreeValue(ctx, z); + z = JS_RegExpExec(ctx, splitter, str); + if (JS_IsException(z)) + goto exception; + if (JS_IsNull(z)) { + q = string_advance_index(strp, q, unicodeMatching); + } else { + if (JS_ToLengthFree(ctx, &e, JS_GetProperty(ctx, splitter, JS_ATOM_lastIndex))) + goto exception; + if (e > size) + e = size; + if (e == p) { + q = string_advance_index(strp, q, unicodeMatching); + } else { + sub = js_sub_string(ctx, strp, p, q); + if (JS_IsException(sub)) + goto exception; + if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub, + JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + if (lengthA == lim) + goto done; + p = e; + if (js_get_length64(ctx, &numberOfCaptures, z)) + goto exception; + for(i = 1; i < numberOfCaptures; i++) { + sub = JS_GetPropertyInt64(ctx, z, i); + if (JS_IsException(sub)) + goto exception; + if (!JS_IsUndefined(sub)) { + sub = JS_ToStringFree(ctx, sub); + if (JS_IsException(sub)) + goto exception; + } + if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub, JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + if (lengthA == lim) + goto done; + } + q = p; + } + } + } +add_tail: + if (p > size) + p = size; + sub = js_sub_string(ctx, strp, p, size); + if (JS_IsException(sub)) + goto exception; + if (JS_DefinePropertyValueInt64(ctx, A, lengthA++, sub, JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception; + goto done; +exception: + JS_FreeValue(ctx, A); + A = JS_EXCEPTION; +done: + JS_FreeValue(ctx, str); + JS_FreeValue(ctx, ctor); + JS_FreeValue(ctx, splitter); + JS_FreeValue(ctx, flags); + JS_FreeValue(ctx, z); + return A; +} + +static const JSCFunctionListEntry js_regexp_funcs[] = { + JS_CFUNC_DEF("escape", 1, js_regexp_escape ), + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static const JSCFunctionListEntry js_regexp_proto_funcs[] = { + JS_CGETSET_DEF("flags", js_regexp_get_flags, NULL ), + JS_CGETSET_DEF("source", js_regexp_get_source, NULL ), + JS_CGETSET_MAGIC_DEF("global", js_regexp_get_flag, NULL, LRE_FLAG_GLOBAL ), + JS_CGETSET_MAGIC_DEF("ignoreCase", js_regexp_get_flag, NULL, LRE_FLAG_IGNORECASE ), + JS_CGETSET_MAGIC_DEF("multiline", js_regexp_get_flag, NULL, LRE_FLAG_MULTILINE ), + JS_CGETSET_MAGIC_DEF("dotAll", js_regexp_get_flag, NULL, LRE_FLAG_DOTALL ), + JS_CGETSET_MAGIC_DEF("unicode", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE ), + JS_CGETSET_MAGIC_DEF("unicodeSets", js_regexp_get_flag, NULL, LRE_FLAG_UNICODE_SETS ), + JS_CGETSET_MAGIC_DEF("sticky", js_regexp_get_flag, NULL, LRE_FLAG_STICKY ), + JS_CGETSET_MAGIC_DEF("hasIndices", js_regexp_get_flag, NULL, LRE_FLAG_INDICES ), + JS_CFUNC_DEF("exec", 1, js_regexp_exec ), + JS_CFUNC_DEF("compile", 2, js_regexp_compile ), + JS_CFUNC_DEF("test", 1, js_regexp_test ), + JS_CFUNC_DEF("toString", 0, js_regexp_toString ), + JS_CFUNC_DEF("[Symbol.replace]", 2, js_regexp_Symbol_replace ), + JS_CFUNC_DEF("[Symbol.match]", 1, js_regexp_Symbol_match ), + JS_CFUNC_DEF("[Symbol.matchAll]", 1, js_regexp_Symbol_matchAll ), + JS_CFUNC_DEF("[Symbol.search]", 1, js_regexp_Symbol_search ), + JS_CFUNC_DEF("[Symbol.split]", 2, js_regexp_Symbol_split ), +}; + +static const JSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_regexp_string_iterator_next, 0 ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "RegExp String Iterator", JS_PROP_CONFIGURABLE ), +}; + +void JS_AddIntrinsicRegExpCompiler(JSContext *ctx) +{ + ctx->compile_regexp = js_compile_regexp; +} + +void JS_AddIntrinsicRegExp(JSContext *ctx) +{ + JSValue obj; + + JS_AddIntrinsicRegExpCompiler(ctx); + + ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs, + countof(js_regexp_proto_funcs)); + obj = JS_NewGlobalCConstructor(ctx, "RegExp", js_regexp_constructor, 2, + ctx->class_proto[JS_CLASS_REGEXP]); + ctx->regexp_ctor = js_dup(obj); + JS_SetPropertyFunctionList(ctx, obj, js_regexp_funcs, countof(js_regexp_funcs)); + + ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR] = + JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_REGEXP_STRING_ITERATOR], + js_regexp_string_iterator_proto_funcs, + countof(js_regexp_string_iterator_proto_funcs)); +} + +/* JSON */ + +static JSValue json_parse_value(JSParseState *s) +{ + JSContext *ctx = s->ctx; + JSValue val = JS_NULL; + int ret; + + switch(s->token.val) { + case '{': + { + JSValue prop_val; + JSAtom prop_name; + + if (json_next_token(s)) + goto fail; + val = JS_NewObject(ctx); + if (JS_IsException(val)) + goto fail; + if (s->token.val != '}') { + for(;;) { + if (s->token.val == TOK_STRING) { + prop_name = JS_ValueToAtom(ctx, s->token.u.str.str); + if (prop_name == JS_ATOM_NULL) + goto fail; + } else { + json_parse_error(s, s->token.ptr, "Expected property name or '}'"); + goto fail; + } + if (json_next_token(s)) + goto fail1; + if (s->token.val != ':') { + json_parse_error(s, s->token.ptr, "Expected ':' after property name"); + goto fail1; + } + if (json_next_token(s)) + goto fail1; + prop_val = json_parse_value(s); + if (JS_IsException(prop_val)) { + fail1: + JS_FreeAtom(ctx, prop_name); + goto fail; + } + ret = JS_DefinePropertyValue(ctx, val, prop_name, + prop_val, JS_PROP_C_W_E); + JS_FreeAtom(ctx, prop_name); + if (ret < 0) + goto fail; + + if (s->token.val == '}') + break; + if (s->token.val != ',') { + json_parse_error(s, s->token.ptr, "Expected ',' or '}' after property value"); + goto fail; + } + if (json_next_token(s)) + goto fail; + } + } + if (json_next_token(s)) + goto fail; + } + break; + case '[': + { + JSValue el; + uint32_t idx; + + if (json_next_token(s)) + goto fail; + val = JS_NewArray(ctx); + if (JS_IsException(val)) + goto fail; + if (s->token.val != ']') { + for(idx = 0;; idx++) { + el = json_parse_value(s); + if (JS_IsException(el)) + goto fail; + ret = JS_DefinePropertyValueUint32(ctx, val, idx, el, JS_PROP_C_W_E); + if (ret < 0) + goto fail; + if (s->token.val == ']') + break; + if (s->token.val != ',') { + json_parse_error(s, s->token.ptr, "Expected ',' or ']' after array element"); + goto fail; + } + if (json_next_token(s)) + goto fail; + } + } + if (json_next_token(s)) + goto fail; + } + break; + case TOK_STRING: + val = js_dup(s->token.u.str.str); + if (json_next_token(s)) + goto fail; + break; + case TOK_NUMBER: + val = s->token.u.num.val; + if (json_next_token(s)) + goto fail; + break; + case TOK_IDENT: + if (s->token.u.ident.atom == JS_ATOM_false || + s->token.u.ident.atom == JS_ATOM_true) { + val = js_bool(s->token.u.ident.atom == JS_ATOM_true); + } else if (s->token.u.ident.atom == JS_ATOM_null) { + val = JS_NULL; + } else { + goto def_token; + } + if (json_next_token(s)) + goto fail; + break; + default: + def_token: + if (s->token.val == TOK_EOF) { + js_parse_error(s, "Unexpected end of JSON input"); + } else { + js_parse_error(s, "unexpected token: '%.*s'", + (int)(s->buf_ptr - s->token.ptr), s->token.ptr); + } + goto fail; + } + return val; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +/* 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. */ +JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, const char *filename) +{ + JSParseState s1, *s = &s1; + JSValue val = JS_UNDEFINED; + + js_parse_init(ctx, s, buf, buf_len, filename); + if (json_next_token(s)) + goto fail; + val = json_parse_value(s); + if (JS_IsException(val)) + goto fail; + if (s->token.val != TOK_EOF) { + if (js_parse_error(s, "unexpected data at the end")) + goto fail; + } + return val; + fail: + JS_FreeValue(ctx, val); + free_token(s, &s->token); + return JS_EXCEPTION; +} + +static JSValue internalize_json_property(JSContext *ctx, JSValue holder, + JSAtom name, JSValue reviver) +{ + JSValue val, new_el, name_val, res; + JSValue args[2]; + int ret, is_array; + uint32_t i, len = 0; + JSAtom prop; + JSPropertyEnum *atoms = NULL; + + if (js_check_stack_overflow(ctx->rt, 0)) { + return JS_ThrowStackOverflow(ctx); + } + + val = JS_GetProperty(ctx, holder, name); + if (JS_IsException(val)) + return val; + if (JS_IsObject(val)) { + is_array = JS_IsArray(ctx, val); + if (is_array < 0) + goto fail; + if (is_array) { + if (js_get_length32(ctx, &len, val)) + goto fail; + } else { + ret = JS_GetOwnPropertyNamesInternal(ctx, &atoms, &len, JS_VALUE_GET_OBJ(val), JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK); + if (ret < 0) + goto fail; + } + for(i = 0; i < len; i++) { + if (is_array) { + prop = JS_NewAtomUInt32(ctx, i); + if (prop == JS_ATOM_NULL) + goto fail; + } else { + prop = JS_DupAtom(ctx, atoms[i].atom); + } + new_el = internalize_json_property(ctx, val, prop, reviver); + if (JS_IsException(new_el)) { + JS_FreeAtom(ctx, prop); + goto fail; + } + if (JS_IsUndefined(new_el)) { + ret = JS_DeleteProperty(ctx, val, prop, 0); + } else { + ret = JS_DefinePropertyValue(ctx, val, prop, new_el, JS_PROP_C_W_E); + } + JS_FreeAtom(ctx, prop); + if (ret < 0) + goto fail; + } + } + js_free_prop_enum(ctx, atoms, len); + atoms = NULL; + name_val = JS_AtomToValue(ctx, name); + if (JS_IsException(name_val)) + goto fail; + args[0] = name_val; + args[1] = val; + res = JS_Call(ctx, reviver, holder, 2, args); + JS_FreeValue(ctx, name_val); + JS_FreeValue(ctx, val); + return res; + fail: + js_free_prop_enum(ctx, atoms, len); + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue js_json_parse(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, root; + JSValue reviver; + const char *str; + size_t len; + + str = JS_ToCStringLen(ctx, &len, argv[0]); + if (!str) + return JS_EXCEPTION; + obj = JS_ParseJSON(ctx, str, len, "<input>"); + JS_FreeCString(ctx, str); + if (JS_IsException(obj)) + return obj; + if (argc > 1 && JS_IsFunction(ctx, argv[1])) { + reviver = argv[1]; + root = JS_NewObject(ctx); + if (JS_IsException(root)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + if (JS_DefinePropertyValue(ctx, root, JS_ATOM_empty_string, obj, + JS_PROP_C_W_E) < 0) { + JS_FreeValue(ctx, root); + return JS_EXCEPTION; + } + obj = internalize_json_property(ctx, root, JS_ATOM_empty_string, + reviver); + JS_FreeValue(ctx, root); + } + return obj; +} + +typedef struct JSONStringifyContext { + JSValue replacer_func; + JSValue stack; + JSValue property_list; + JSValue gap; + JSValue empty; + StringBuffer *b; +} JSONStringifyContext; + +static JSValue JS_ToQuotedStringFree(JSContext *ctx, JSValue val) { + JSValue r = JS_ToQuotedString(ctx, val); + JS_FreeValue(ctx, val); + return r; +} + +static JSValue js_json_check(JSContext *ctx, JSONStringifyContext *jsc, + JSValue holder, JSValue val, JSValue key) +{ + JSValue v; + JSValue args[2]; + + if (JS_IsObject(val) || JS_IsBigInt(ctx, val)) { + JSValue f = JS_GetProperty(ctx, val, JS_ATOM_toJSON); + if (JS_IsException(f)) + goto exception; + if (JS_IsFunction(ctx, f)) { + v = JS_CallFree(ctx, f, val, 1, &key); + JS_FreeValue(ctx, val); + val = v; + if (JS_IsException(val)) + goto exception; + } else { + JS_FreeValue(ctx, f); + } + } + + if (!JS_IsUndefined(jsc->replacer_func)) { + args[0] = key; + args[1] = val; + v = JS_Call(ctx, jsc->replacer_func, holder, 2, args); + JS_FreeValue(ctx, val); + val = v; + if (JS_IsException(val)) + goto exception; + } + + switch (JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_OBJECT: + if (JS_IsFunction(ctx, val)) + break; + case JS_TAG_STRING: + case JS_TAG_INT: + case JS_TAG_FLOAT64: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_BIG_INT: + case JS_TAG_EXCEPTION: + return val; + default: + break; + } + JS_FreeValue(ctx, val); + return JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static int js_json_to_str(JSContext *ctx, JSONStringifyContext *jsc, + JSValue holder, JSValue val, + JSValue indent) +{ + JSValue indent1, sep, sep1, tab, v, prop; + JSObject *p; + int64_t i, len; + int cl, ret; + BOOL has_content; + + indent1 = JS_UNDEFINED; + sep = JS_UNDEFINED; + sep1 = JS_UNDEFINED; + tab = JS_UNDEFINED; + prop = JS_UNDEFINED; + + if (JS_IsObject(val)) { + p = JS_VALUE_GET_OBJ(val); + cl = p->class_id; + if (cl == JS_CLASS_STRING) { + val = JS_ToStringFree(ctx, val); + if (JS_IsException(val)) + goto exception; + goto concat_primitive; + } else if (cl == JS_CLASS_NUMBER) { + val = JS_ToNumberFree(ctx, val); + if (JS_IsException(val)) + goto exception; + goto concat_primitive; + } else if (cl == JS_CLASS_BOOLEAN || cl == JS_CLASS_BIG_INT) { + set_value(ctx, &val, js_dup(p->u.object_data)); + goto concat_primitive; + } + v = js_array_includes(ctx, jsc->stack, 1, &val); + if (JS_IsException(v)) + goto exception; + if (JS_ToBoolFree(ctx, v)) { + JS_ThrowTypeError(ctx, "circular reference"); + goto exception; + } + indent1 = JS_ConcatString(ctx, js_dup(indent), js_dup(jsc->gap)); + if (JS_IsException(indent1)) + goto exception; + if (!JS_IsEmptyString(jsc->gap)) { + sep = JS_ConcatString3(ctx, "\n", js_dup(indent1), ""); + if (JS_IsException(sep)) + goto exception; + sep1 = js_new_string8(ctx, " "); + if (JS_IsException(sep1)) + goto exception; + } else { + sep = js_dup(jsc->empty); + sep1 = js_dup(jsc->empty); + } + v = js_array_push(ctx, jsc->stack, 1, &val, 0); + if (check_exception_free(ctx, v)) + goto exception; + ret = JS_IsArray(ctx, val); + if (ret < 0) + goto exception; + if (ret) { + if (js_get_length64(ctx, &len, val)) + goto exception; + string_buffer_putc8(jsc->b, '['); + for(i = 0; i < len; i++) { + if (i > 0) + string_buffer_putc8(jsc->b, ','); + string_buffer_concat_value(jsc->b, sep); + v = JS_GetPropertyInt64(ctx, val, i); + if (JS_IsException(v)) + goto exception; + /* XXX: could do this string conversion only when needed */ + prop = JS_ToStringFree(ctx, js_int64(i)); + if (JS_IsException(prop)) + goto exception; + v = js_json_check(ctx, jsc, val, v, prop); + JS_FreeValue(ctx, prop); + prop = JS_UNDEFINED; + if (JS_IsException(v)) + goto exception; + if (JS_IsUndefined(v)) + v = JS_NULL; + if (js_json_to_str(ctx, jsc, val, v, indent1)) + goto exception; + } + if (len > 0 && !JS_IsEmptyString(jsc->gap)) { + string_buffer_putc8(jsc->b, '\n'); + string_buffer_concat_value(jsc->b, indent); + } + string_buffer_putc8(jsc->b, ']'); + } else { + if (!JS_IsUndefined(jsc->property_list)) + tab = js_dup(jsc->property_list); + else + tab = js_object_keys(ctx, JS_UNDEFINED, 1, &val, JS_ITERATOR_KIND_KEY); + if (JS_IsException(tab)) + goto exception; + if (js_get_length64(ctx, &len, tab)) + goto exception; + string_buffer_putc8(jsc->b, '{'); + has_content = FALSE; + for(i = 0; i < len; i++) { + JS_FreeValue(ctx, prop); + prop = JS_GetPropertyInt64(ctx, tab, i); + if (JS_IsException(prop)) + goto exception; + v = JS_GetPropertyValue(ctx, val, js_dup(prop)); + if (JS_IsException(v)) + goto exception; + v = js_json_check(ctx, jsc, val, v, prop); + if (JS_IsException(v)) + goto exception; + if (!JS_IsUndefined(v)) { + if (has_content) + string_buffer_putc8(jsc->b, ','); + prop = JS_ToQuotedStringFree(ctx, prop); + if (JS_IsException(prop)) { + JS_FreeValue(ctx, v); + goto exception; + } + string_buffer_concat_value(jsc->b, sep); + string_buffer_concat_value(jsc->b, prop); + string_buffer_putc8(jsc->b, ':'); + string_buffer_concat_value(jsc->b, sep1); + if (js_json_to_str(ctx, jsc, val, v, indent1)) + goto exception; + has_content = TRUE; + } + } + if (has_content && JS_VALUE_GET_STRING(jsc->gap)->len != 0) { + string_buffer_putc8(jsc->b, '\n'); + string_buffer_concat_value(jsc->b, indent); + } + string_buffer_putc8(jsc->b, '}'); + } + if (check_exception_free(ctx, js_array_pop(ctx, jsc->stack, 0, NULL, 0))) + goto exception; + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, tab); + JS_FreeValue(ctx, sep); + JS_FreeValue(ctx, sep1); + JS_FreeValue(ctx, indent1); + JS_FreeValue(ctx, prop); + return 0; + } + concat_primitive: + switch (JS_VALUE_GET_NORM_TAG(val)) { + case JS_TAG_STRING: + val = JS_ToQuotedStringFree(ctx, val); + if (JS_IsException(val)) + goto exception; + goto concat_value; + case JS_TAG_FLOAT64: + if (!isfinite(JS_VALUE_GET_FLOAT64(val))) { + val = JS_NULL; + } + goto concat_value; + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + concat_value: + return string_buffer_concat_value_free(jsc->b, val); + case JS_TAG_BIG_INT: + JS_ThrowTypeError(ctx, "BigInt are forbidden in JSON.stringify"); + goto exception; + default: + JS_FreeValue(ctx, val); + return 0; + } + +exception: + JS_FreeValue(ctx, val); + JS_FreeValue(ctx, tab); + JS_FreeValue(ctx, sep); + JS_FreeValue(ctx, sep1); + JS_FreeValue(ctx, indent1); + JS_FreeValue(ctx, prop); + return -1; +} + +JSValue JS_JSONStringify(JSContext *ctx, JSValue obj, + JSValue replacer, JSValue space0) +{ + StringBuffer b_s; + JSONStringifyContext jsc_s, *jsc = &jsc_s; + JSValue val, v, space, ret, wrapper; + int res; + int64_t i, j, n; + + jsc->replacer_func = JS_UNDEFINED; + jsc->stack = JS_UNDEFINED; + jsc->property_list = JS_UNDEFINED; + jsc->gap = JS_UNDEFINED; + jsc->b = &b_s; + jsc->empty = JS_AtomToString(ctx, JS_ATOM_empty_string); + ret = JS_UNDEFINED; + wrapper = JS_UNDEFINED; + + string_buffer_init(ctx, jsc->b, 0); + jsc->stack = JS_NewArray(ctx); + if (JS_IsException(jsc->stack)) + goto exception; + if (JS_IsFunction(ctx, replacer)) { + jsc->replacer_func = replacer; + } else { + res = JS_IsArray(ctx, replacer); + if (res < 0) + goto exception; + if (res) { + /* XXX: enumeration is not fully correct */ + jsc->property_list = JS_NewArray(ctx); + if (JS_IsException(jsc->property_list)) + goto exception; + if (js_get_length64(ctx, &n, replacer)) + goto exception; + for (i = j = 0; i < n; i++) { + JSValue present; + v = JS_GetPropertyInt64(ctx, replacer, i); + if (JS_IsException(v)) + goto exception; + if (JS_IsObject(v)) { + JSObject *p = JS_VALUE_GET_OBJ(v); + if (p->class_id == JS_CLASS_STRING || + p->class_id == JS_CLASS_NUMBER) { + v = JS_ToStringFree(ctx, v); + if (JS_IsException(v)) + goto exception; + } else { + JS_FreeValue(ctx, v); + continue; + } + } else if (JS_IsNumber(v)) { + v = JS_ToStringFree(ctx, v); + if (JS_IsException(v)) + goto exception; + } else if (!JS_IsString(v)) { + JS_FreeValue(ctx, v); + continue; + } + present = js_array_includes(ctx, jsc->property_list, + 1, &v); + if (JS_IsException(present)) { + JS_FreeValue(ctx, v); + goto exception; + } + if (!JS_ToBoolFree(ctx, present)) { + JS_SetPropertyInt64(ctx, jsc->property_list, j++, v); + } else { + JS_FreeValue(ctx, v); + } + } + } + } + space = js_dup(space0); + if (JS_IsObject(space)) { + JSObject *p = JS_VALUE_GET_OBJ(space); + if (p->class_id == JS_CLASS_NUMBER) { + space = JS_ToNumberFree(ctx, space); + } else if (p->class_id == JS_CLASS_STRING) { + space = JS_ToStringFree(ctx, space); + } + if (JS_IsException(space)) { + JS_FreeValue(ctx, space); + goto exception; + } + } + if (JS_IsNumber(space)) { + int n; + if (JS_ToInt32Clamp(ctx, &n, space, 0, 10, 0)) + goto exception; + jsc->gap = JS_NewStringLen(ctx, " ", n); + } else if (JS_IsString(space)) { + JSString *p = JS_VALUE_GET_STRING(space); + jsc->gap = js_sub_string(ctx, p, 0, min_int(p->len, 10)); + } else { + jsc->gap = js_dup(jsc->empty); + } + JS_FreeValue(ctx, space); + if (JS_IsException(jsc->gap)) + goto exception; + wrapper = JS_NewObject(ctx); + if (JS_IsException(wrapper)) + goto exception; + if (JS_DefinePropertyValue(ctx, wrapper, JS_ATOM_empty_string, + js_dup(obj), JS_PROP_C_W_E) < 0) + goto exception; + val = js_dup(obj); + + val = js_json_check(ctx, jsc, wrapper, val, jsc->empty); + if (JS_IsException(val)) + goto exception; + if (JS_IsUndefined(val)) { + ret = JS_UNDEFINED; + goto done1; + } + if (js_json_to_str(ctx, jsc, wrapper, val, jsc->empty)) + goto exception; + + ret = string_buffer_end(jsc->b); + goto done; + +exception: + ret = JS_EXCEPTION; +done1: + string_buffer_free(jsc->b); +done: + JS_FreeValue(ctx, wrapper); + JS_FreeValue(ctx, jsc->empty); + JS_FreeValue(ctx, jsc->gap); + JS_FreeValue(ctx, jsc->property_list); + JS_FreeValue(ctx, jsc->stack); + return ret; +} + +static JSValue js_json_stringify(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // stringify(val, replacer, space) + return JS_JSONStringify(ctx, argv[0], argv[1], argv[2]); +} + +static const JSCFunctionListEntry js_json_funcs[] = { + JS_CFUNC_DEF("parse", 2, js_json_parse ), + JS_CFUNC_DEF("stringify", 3, js_json_stringify ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "JSON", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_json_obj[] = { + JS_OBJECT_DEF("JSON", js_json_funcs, countof(js_json_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), +}; + +void JS_AddIntrinsicJSON(JSContext *ctx) +{ + /* add JSON as autoinit object */ + JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_json_obj, countof(js_json_obj)); +} + +/* Reflect */ + +static JSValue js_reflect_apply(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_function_apply(ctx, argv[0], max_int(0, argc - 1), argv + 1, 2); +} + +static JSValue js_reflect_construct(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue func, array_arg, new_target; + JSValue *tab, ret; + uint32_t len; + + func = argv[0]; + array_arg = argv[1]; + if (argc > 2) { + new_target = argv[2]; + if (!JS_IsConstructor(ctx, new_target)) + return JS_ThrowTypeError(ctx, "not a constructor"); + } else { + new_target = func; + } + tab = build_arg_list(ctx, &len, array_arg); + if (!tab) + return JS_EXCEPTION; + ret = JS_CallConstructor2(ctx, func, new_target, len, tab); + free_arg_list(ctx, tab, len); + return ret; +} + +static JSValue js_reflect_deleteProperty(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSAtom atom; + int ret; + + obj = argv[0]; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + atom = JS_ValueToAtom(ctx, argv[1]); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_DeleteProperty(ctx, obj, atom, 0); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_reflect_get(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, prop, receiver; + JSAtom atom; + JSValue ret; + + obj = argv[0]; + prop = argv[1]; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + if (argc > 2) + receiver = argv[2]; + else + receiver = obj; + atom = JS_ValueToAtom(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_GetPropertyInternal(ctx, obj, atom, receiver, FALSE); + JS_FreeAtom(ctx, atom); + return ret; +} + +static JSValue js_reflect_has(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, prop; + JSAtom atom; + int ret; + + obj = argv[0]; + prop = argv[1]; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + atom = JS_ValueToAtom(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_HasProperty(ctx, obj, atom); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_reflect_set(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj, prop, val, receiver; + int ret; + JSAtom atom; + + obj = argv[0]; + prop = argv[1]; + val = argv[2]; + if (argc > 3) + receiver = argv[3]; + else + receiver = obj; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + atom = JS_ValueToAtom(ctx, prop); + if (unlikely(atom == JS_ATOM_NULL)) + return JS_EXCEPTION; + ret = JS_SetPropertyInternal2(ctx, obj, atom, js_dup(val), receiver, + 0, NULL); + JS_FreeAtom(ctx, atom); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_reflect_setPrototypeOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + int ret; + ret = JS_SetPrototypeInternal(ctx, argv[0], argv[1], FALSE); + if (ret < 0) + return JS_EXCEPTION; + else + return js_bool(ret); +} + +static JSValue js_reflect_ownKeys(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + return JS_GetOwnPropertyNames2(ctx, argv[0], + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK, + JS_ITERATOR_KIND_KEY); +} + +static const JSCFunctionListEntry js_reflect_funcs[] = { + JS_CFUNC_DEF("apply", 3, js_reflect_apply ), + JS_CFUNC_DEF("construct", 2, js_reflect_construct ), + JS_CFUNC_MAGIC_DEF("defineProperty", 3, js_object_defineProperty, 1 ), + JS_CFUNC_DEF("deleteProperty", 2, js_reflect_deleteProperty ), + JS_CFUNC_DEF("get", 2, js_reflect_get ), + JS_CFUNC_MAGIC_DEF("getOwnPropertyDescriptor", 2, js_object_getOwnPropertyDescriptor, 1 ), + JS_CFUNC_MAGIC_DEF("getPrototypeOf", 1, js_object_getPrototypeOf, 1 ), + JS_CFUNC_DEF("has", 2, js_reflect_has ), + JS_CFUNC_MAGIC_DEF("isExtensible", 1, js_object_isExtensible, 1 ), + JS_CFUNC_DEF("ownKeys", 1, js_reflect_ownKeys ), + JS_CFUNC_MAGIC_DEF("preventExtensions", 1, js_object_preventExtensions, 1 ), + JS_CFUNC_DEF("set", 3, js_reflect_set ), + JS_CFUNC_DEF("setPrototypeOf", 2, js_reflect_setPrototypeOf ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Reflect", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_reflect_obj[] = { + JS_OBJECT_DEF("Reflect", js_reflect_funcs, countof(js_reflect_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), +}; + +/* Proxy */ + +static void js_proxy_finalizer(JSRuntime *rt, JSValue val) +{ + JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY); + if (s) { + JS_FreeValueRT(rt, s->target); + JS_FreeValueRT(rt, s->handler); + js_free_rt(rt, s); + } +} + +static void js_proxy_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSProxyData *s = JS_GetOpaque(val, JS_CLASS_PROXY); + if (s) { + JS_MarkValue(rt, s->target, mark_func); + JS_MarkValue(rt, s->handler, mark_func); + } +} + +static JSValue JS_ThrowTypeErrorRevokedProxy(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "revoked proxy"); +} + +static JSProxyData *get_proxy_method(JSContext *ctx, JSValue *pmethod, + JSValue obj, JSAtom name) +{ + JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); + JSValue method; + + /* safer to test recursion in all proxy methods */ + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return NULL; + } + + /* 's' should never be NULL */ + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return NULL; + } + method = JS_GetProperty(ctx, s->handler, name); + if (JS_IsException(method)) + return NULL; + if (JS_IsNull(method)) + method = JS_UNDEFINED; + *pmethod = method; + return s; +} + +static JSValue js_proxy_getPrototypeOf(JSContext *ctx, JSValue obj) +{ + JSProxyData *s; + JSValue method, ret, proto1; + int res; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_getPrototypeOf); + if (!s) + return JS_EXCEPTION; + if (JS_IsUndefined(method)) + return JS_GetPrototype(ctx, s->target); + ret = JS_CallFree(ctx, method, s->handler, 1, &s->target); + if (JS_IsException(ret)) + return ret; + if (JS_VALUE_GET_TAG(ret) != JS_TAG_NULL && + JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + goto fail; + } + res = JS_IsExtensible(ctx, s->target); + if (res < 0) { + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; + } + if (!res) { + /* check invariant */ + proto1 = JS_GetPrototype(ctx, s->target); + if (JS_IsException(proto1)) { + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; + } + if (JS_VALUE_GET_OBJ(proto1) != JS_VALUE_GET_OBJ(ret)) { + JS_FreeValue(ctx, proto1); + fail: + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); + } + JS_FreeValue(ctx, proto1); + } + return ret; +} + +static int js_proxy_setPrototypeOf(JSContext *ctx, JSValue obj, + JSValue proto_val, BOOL throw_flag) +{ + JSProxyData *s; + JSValue method, ret, proto1; + JSValue args[2]; + BOOL res; + int res2; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_setPrototypeOf); + if (!s) + return -1; + if (JS_IsUndefined(method)) + return JS_SetPrototypeInternal(ctx, s->target, proto_val, throw_flag); + args[0] = s->target; + args[1] = proto_val; + ret = JS_CallFree(ctx, method, s->handler, 2, args); + if (JS_IsException(ret)) + return -1; + res = JS_ToBoolFree(ctx, ret); + if (!res) { + if (throw_flag) { + JS_ThrowTypeError(ctx, "proxy: bad prototype"); + return -1; + } else { + return FALSE; + } + } + res2 = JS_IsExtensible(ctx, s->target); + if (res2 < 0) + return -1; + if (!res2) { + proto1 = JS_GetPrototype(ctx, s->target); + if (JS_IsException(proto1)) + return -1; + if (JS_VALUE_GET_OBJ(proto_val) != JS_VALUE_GET_OBJ(proto1)) { + JS_FreeValue(ctx, proto1); + JS_ThrowTypeError(ctx, "proxy: inconsistent prototype"); + return -1; + } + JS_FreeValue(ctx, proto1); + } + return TRUE; +} + +static int js_proxy_isExtensible(JSContext *ctx, JSValue obj) +{ + JSProxyData *s; + JSValue method, ret; + BOOL res; + int res2; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_isExtensible); + if (!s) + return -1; + if (JS_IsUndefined(method)) + return JS_IsExtensible(ctx, s->target); + ret = JS_CallFree(ctx, method, s->handler, 1, &s->target); + if (JS_IsException(ret)) + return -1; + res = JS_ToBoolFree(ctx, ret); + res2 = JS_IsExtensible(ctx, s->target); + if (res2 < 0) + return res2; + if (res != res2) { + JS_ThrowTypeError(ctx, "proxy: inconsistent isExtensible"); + return -1; + } + return res; +} + +static int js_proxy_preventExtensions(JSContext *ctx, JSValue obj) +{ + JSProxyData *s; + JSValue method, ret; + BOOL res; + int res2; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_preventExtensions); + if (!s) + return -1; + if (JS_IsUndefined(method)) + return JS_PreventExtensions(ctx, s->target); + ret = JS_CallFree(ctx, method, s->handler, 1, &s->target); + if (JS_IsException(ret)) + return -1; + res = JS_ToBoolFree(ctx, ret); + if (res) { + res2 = JS_IsExtensible(ctx, s->target); + if (res2 < 0) + return res2; + if (res2) { + JS_ThrowTypeError(ctx, "proxy: inconsistent preventExtensions"); + return -1; + } + } + return res; +} + +static int js_proxy_has(JSContext *ctx, JSValue obj, JSAtom atom) +{ + JSProxyData *s; + JSValue method, ret1, atom_val; + int ret, res; + JSObject *p; + JSValue args[2]; + BOOL res2; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_has); + if (!s) + return -1; + if (JS_IsUndefined(method)) + return JS_HasProperty(ctx, s->target, atom); + atom_val = JS_AtomToValue(ctx, atom); + if (JS_IsException(atom_val)) { + JS_FreeValue(ctx, method); + return -1; + } + args[0] = s->target; + args[1] = atom_val; + ret1 = JS_CallFree(ctx, method, s->handler, 2, args); + JS_FreeValue(ctx, atom_val); + if (JS_IsException(ret1)) + return -1; + ret = JS_ToBoolFree(ctx, ret1); + if (!ret) { + JSPropertyDescriptor desc; + p = JS_VALUE_GET_OBJ(s->target); + res = JS_GetOwnPropertyInternal(ctx, &desc, p, atom); + if (res < 0) + return -1; + if (res) { + res2 = !(desc.flags & JS_PROP_CONFIGURABLE); + js_free_desc(ctx, &desc); + if (res2 || !p->extensible) { + JS_ThrowTypeError(ctx, "proxy: inconsistent has"); + return -1; + } + } + } + return ret; +} + +static JSValue js_proxy_get(JSContext *ctx, JSValue obj, JSAtom atom, + JSValue receiver) +{ + JSProxyData *s; + JSValue method, ret, atom_val; + int res; + JSValue args[3]; + JSPropertyDescriptor desc; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_get); + if (!s) + return JS_EXCEPTION; + /* Note: recursion is possible thru the prototype of s->target */ + if (JS_IsUndefined(method)) + return JS_GetPropertyInternal(ctx, s->target, atom, receiver, FALSE); + atom_val = JS_AtomToValue(ctx, atom); + if (JS_IsException(atom_val)) { + JS_FreeValue(ctx, method); + return JS_EXCEPTION; + } + args[0] = s->target; + args[1] = atom_val; + args[2] = receiver; + ret = JS_CallFree(ctx, method, s->handler, 3, args); + JS_FreeValue(ctx, atom_val); + if (JS_IsException(ret)) + return JS_EXCEPTION; + res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); + if (res < 0) { + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; + } + if (res) { + if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { + if (!js_same_value(ctx, desc.value, ret)) { + goto fail; + } + } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET) { + if (JS_IsUndefined(desc.getter) && !JS_IsUndefined(ret)) { + fail: + js_free_desc(ctx, &desc); + JS_FreeValue(ctx, ret); + return JS_ThrowTypeError(ctx, "proxy: inconsistent get"); + } + } + js_free_desc(ctx, &desc); + } + return ret; +} + +static int js_proxy_set(JSContext *ctx, JSValue obj, JSAtom atom, + JSValue value, JSValue receiver, int flags) +{ + JSProxyData *s; + JSValue method, ret1, atom_val; + int ret, res; + JSValue args[4]; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_set); + if (!s) + return -1; + if (JS_IsUndefined(method)) { + return JS_SetPropertyInternal2(ctx, s->target, atom, + js_dup(value), receiver, + flags, NULL); + } + atom_val = JS_AtomToValue(ctx, atom); + if (JS_IsException(atom_val)) { + JS_FreeValue(ctx, method); + return -1; + } + args[0] = s->target; + args[1] = atom_val; + args[2] = value; + args[3] = receiver; + ret1 = JS_CallFree(ctx, method, s->handler, 4, args); + JS_FreeValue(ctx, atom_val); + if (JS_IsException(ret1)) + return -1; + ret = JS_ToBoolFree(ctx, ret1); + if (ret) { + JSPropertyDescriptor desc; + res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); + if (res < 0) + return -1; + if (res) { + if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0) { + if (!js_same_value(ctx, desc.value, value)) { + goto fail; + } + } else if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == JS_PROP_GETSET && JS_IsUndefined(desc.setter)) { + fail: + js_free_desc(ctx, &desc); + JS_ThrowTypeError(ctx, "proxy: inconsistent set"); + return -1; + } + js_free_desc(ctx, &desc); + } + } else { + if ((flags & JS_PROP_THROW) || + ((flags & JS_PROP_THROW_STRICT) && is_strict_mode(ctx))) { + JS_ThrowTypeError(ctx, "proxy: cannot set property"); + return -1; + } + } + return ret; +} + +static JSValue js_create_desc(JSContext *ctx, JSValue val, + JSValue getter, JSValue setter, + int flags) +{ + JSValue ret; + ret = JS_NewObject(ctx); + if (JS_IsException(ret)) + return ret; + if (flags & JS_PROP_HAS_GET) { + JS_DefinePropertyValue(ctx, ret, JS_ATOM_get, js_dup(getter), + JS_PROP_C_W_E); + } + if (flags & JS_PROP_HAS_SET) { + JS_DefinePropertyValue(ctx, ret, JS_ATOM_set, js_dup(setter), + JS_PROP_C_W_E); + } + if (flags & JS_PROP_HAS_VALUE) { + JS_DefinePropertyValue(ctx, ret, JS_ATOM_value, js_dup(val), + JS_PROP_C_W_E); + } + if (flags & JS_PROP_HAS_WRITABLE) { + JS_DefinePropertyValue(ctx, ret, JS_ATOM_writable, + js_bool(flags & JS_PROP_WRITABLE), + JS_PROP_C_W_E); + } + if (flags & JS_PROP_HAS_ENUMERABLE) { + JS_DefinePropertyValue(ctx, ret, JS_ATOM_enumerable, + js_bool(flags & JS_PROP_ENUMERABLE), + JS_PROP_C_W_E); + } + if (flags & JS_PROP_HAS_CONFIGURABLE) { + JS_DefinePropertyValue(ctx, ret, JS_ATOM_configurable, + js_bool(flags & JS_PROP_CONFIGURABLE), + JS_PROP_C_W_E); + } + return ret; +} + +static int js_proxy_get_own_property(JSContext *ctx, JSPropertyDescriptor *pdesc, + JSValue obj, JSAtom prop) +{ + JSProxyData *s; + JSValue method, trap_result_obj, prop_val; + int res, target_desc_ret, ret; + JSObject *p; + JSValue args[2]; + JSPropertyDescriptor result_desc, target_desc; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_getOwnPropertyDescriptor); + if (!s) + return -1; + p = JS_VALUE_GET_OBJ(s->target); + if (JS_IsUndefined(method)) { + return JS_GetOwnPropertyInternal(ctx, pdesc, p, prop); + } + prop_val = JS_AtomToValue(ctx, prop); + if (JS_IsException(prop_val)) { + JS_FreeValue(ctx, method); + return -1; + } + args[0] = s->target; + args[1] = prop_val; + trap_result_obj = JS_CallFree(ctx, method, s->handler, 2, args); + JS_FreeValue(ctx, prop_val); + if (JS_IsException(trap_result_obj)) + return -1; + if (!JS_IsObject(trap_result_obj) && !JS_IsUndefined(trap_result_obj)) { + JS_FreeValue(ctx, trap_result_obj); + goto fail; + } + target_desc_ret = JS_GetOwnPropertyInternal(ctx, &target_desc, p, prop); + if (target_desc_ret < 0) { + JS_FreeValue(ctx, trap_result_obj); + return -1; + } + if (target_desc_ret) + js_free_desc(ctx, &target_desc); + if (JS_IsUndefined(trap_result_obj)) { + if (target_desc_ret) { + if (!(target_desc.flags & JS_PROP_CONFIGURABLE) || !p->extensible) + goto fail; + } + ret = FALSE; + } else { + int flags1, extensible_target; + extensible_target = JS_IsExtensible(ctx, s->target); + if (extensible_target < 0) { + JS_FreeValue(ctx, trap_result_obj); + return -1; + } + res = js_obj_to_desc(ctx, &result_desc, trap_result_obj); + JS_FreeValue(ctx, trap_result_obj); + if (res < 0) + return -1; + + if (target_desc_ret) { + /* convert result_desc.flags to defineProperty flags */ + flags1 = result_desc.flags | JS_PROP_HAS_CONFIGURABLE | JS_PROP_HAS_ENUMERABLE; + if (result_desc.flags & JS_PROP_GETSET) + flags1 |= JS_PROP_HAS_GET | JS_PROP_HAS_SET; + else + flags1 |= JS_PROP_HAS_VALUE | JS_PROP_HAS_WRITABLE; + /* XXX: not complete check: need to compare value & + getter/setter as in defineproperty */ + if (!check_define_prop_flags(target_desc.flags, flags1)) + goto fail1; + } else { + if (!extensible_target) + goto fail1; + } + if (!(result_desc.flags & JS_PROP_CONFIGURABLE)) { + if (!target_desc_ret || (target_desc.flags & JS_PROP_CONFIGURABLE)) + goto fail1; + if ((result_desc.flags & + (JS_PROP_GETSET | JS_PROP_WRITABLE)) == 0 && + target_desc_ret && + (target_desc.flags & JS_PROP_WRITABLE) != 0) { + /* proxy-missing-checks */ + fail1: + js_free_desc(ctx, &result_desc); + fail: + JS_ThrowTypeError(ctx, "proxy: inconsistent getOwnPropertyDescriptor"); + return -1; + } + } + ret = TRUE; + if (pdesc) { + *pdesc = result_desc; + } else { + js_free_desc(ctx, &result_desc); + } + } + return ret; +} + +static int js_proxy_define_own_property(JSContext *ctx, JSValue obj, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, + int flags) +{ + JSProxyData *s; + JSValue method, ret1, prop_val, desc_val; + int res, ret; + JSObject *p; + JSValue args[3]; + JSPropertyDescriptor desc; + BOOL setting_not_configurable; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_defineProperty); + if (!s) + return -1; + if (JS_IsUndefined(method)) { + return JS_DefineProperty(ctx, s->target, prop, val, getter, setter, flags); + } + prop_val = JS_AtomToValue(ctx, prop); + if (JS_IsException(prop_val)) { + JS_FreeValue(ctx, method); + return -1; + } + desc_val = js_create_desc(ctx, val, getter, setter, flags); + if (JS_IsException(desc_val)) { + JS_FreeValue(ctx, prop_val); + JS_FreeValue(ctx, method); + return -1; + } + args[0] = s->target; + args[1] = prop_val; + args[2] = desc_val; + ret1 = JS_CallFree(ctx, method, s->handler, 3, args); + JS_FreeValue(ctx, prop_val); + JS_FreeValue(ctx, desc_val); + if (JS_IsException(ret1)) + return -1; + ret = JS_ToBoolFree(ctx, ret1); + if (!ret) { + if (flags & JS_PROP_THROW) { + JS_ThrowTypeError(ctx, "proxy: defineProperty exception"); + return -1; + } else { + return 0; + } + } + p = JS_VALUE_GET_OBJ(s->target); + res = JS_GetOwnPropertyInternal(ctx, &desc, p, prop); + if (res < 0) + return -1; + setting_not_configurable = ((flags & (JS_PROP_HAS_CONFIGURABLE | + JS_PROP_CONFIGURABLE)) == + JS_PROP_HAS_CONFIGURABLE); + if (!res) { + if (!p->extensible || setting_not_configurable) + goto fail; + } else { + if (!check_define_prop_flags(desc.flags, flags) || + ((desc.flags & JS_PROP_CONFIGURABLE) && setting_not_configurable)) { + goto fail1; + } + if (flags & (JS_PROP_HAS_GET | JS_PROP_HAS_SET)) { + if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE)) == + JS_PROP_GETSET) { + if ((flags & JS_PROP_HAS_GET) && + !js_same_value(ctx, getter, desc.getter)) { + goto fail1; + } + if ((flags & JS_PROP_HAS_SET) && + !js_same_value(ctx, setter, desc.setter)) { + goto fail1; + } + } + } else if (flags & JS_PROP_HAS_VALUE) { + if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == + JS_PROP_WRITABLE && !(flags & JS_PROP_WRITABLE)) { + /* missing-proxy-check feature */ + goto fail1; + } else if ((desc.flags & (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE)) == 0 && + !js_same_value(ctx, val, desc.value)) { + goto fail1; + } + } + if (flags & JS_PROP_HAS_WRITABLE) { + if ((desc.flags & (JS_PROP_GETSET | JS_PROP_CONFIGURABLE | + JS_PROP_WRITABLE)) == JS_PROP_WRITABLE) { + /* proxy-missing-checks */ + fail1: + js_free_desc(ctx, &desc); + fail: + JS_ThrowTypeError(ctx, "proxy: inconsistent defineProperty"); + return -1; + } + } + js_free_desc(ctx, &desc); + } + return 1; +} + +static int js_proxy_delete_property(JSContext *ctx, JSValue obj, + JSAtom atom) +{ + JSProxyData *s; + JSValue method, ret, atom_val; + int res, res2, is_extensible; + JSValue args[2]; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_deleteProperty); + if (!s) + return -1; + if (JS_IsUndefined(method)) { + return JS_DeleteProperty(ctx, s->target, atom, 0); + } + atom_val = JS_AtomToValue(ctx, atom);; + if (JS_IsException(atom_val)) { + JS_FreeValue(ctx, method); + return -1; + } + args[0] = s->target; + args[1] = atom_val; + ret = JS_CallFree(ctx, method, s->handler, 2, args); + JS_FreeValue(ctx, atom_val); + if (JS_IsException(ret)) + return -1; + res = JS_ToBoolFree(ctx, ret); + if (res) { + JSPropertyDescriptor desc; + res2 = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), atom); + if (res2 < 0) + return -1; + if (res2) { + if (!(desc.flags & JS_PROP_CONFIGURABLE)) + goto fail; + is_extensible = JS_IsExtensible(ctx, s->target); + if (is_extensible < 0) + goto fail1; + if (!is_extensible) { + /* proxy-missing-checks */ + fail: + JS_ThrowTypeError(ctx, "proxy: inconsistent deleteProperty"); + fail1: + js_free_desc(ctx, &desc); + return -1; + } + js_free_desc(ctx, &desc); + } + } + return res; +} + +/* return the index of the property or -1 if not found */ +static int find_prop_key(const JSPropertyEnum *tab, int n, JSAtom atom) +{ + int i; + for(i = 0; i < n; i++) { + if (tab[i].atom == atom) + return i; + } + return -1; +} + +static int js_proxy_get_own_property_names(JSContext *ctx, + JSPropertyEnum **ptab, + uint32_t *plen, + JSValue obj) +{ + JSProxyData *s; + JSValue method, prop_array, val; + uint32_t len, i, len2; + JSPropertyEnum *tab, *tab2; + JSAtom atom; + JSPropertyDescriptor desc; + int res, is_extensible, idx; + + s = get_proxy_method(ctx, &method, obj, JS_ATOM_ownKeys); + if (!s) + return -1; + if (JS_IsUndefined(method)) { + return JS_GetOwnPropertyNamesInternal(ctx, ptab, plen, + JS_VALUE_GET_OBJ(s->target), + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK); + } + prop_array = JS_CallFree(ctx, method, s->handler, 1, &s->target); + if (JS_IsException(prop_array)) + return -1; + tab = NULL; + len = 0; + tab2 = NULL; + len2 = 0; + if (js_get_length32(ctx, &len, prop_array)) + goto fail; + if (len > 0) { + tab = js_mallocz(ctx, sizeof(tab[0]) * len); + if (!tab) + goto fail; + } + for(i = 0; i < len; i++) { + val = JS_GetPropertyUint32(ctx, prop_array, i); + if (JS_IsException(val)) + goto fail; + if (!JS_IsString(val) && !JS_IsSymbol(val)) { + JS_FreeValue(ctx, val); + JS_ThrowTypeError(ctx, "proxy: properties must be strings or symbols"); + goto fail; + } + atom = JS_ValueToAtom(ctx, val); + JS_FreeValue(ctx, val); + if (atom == JS_ATOM_NULL) + goto fail; + tab[i].atom = atom; + tab[i].is_enumerable = FALSE; /* XXX: redundant? */ + } + + /* check duplicate properties (XXX: inefficient, could store the + * properties an a temporary object to use the hash) */ + for(i = 1; i < len; i++) { + if (find_prop_key(tab, i, tab[i].atom) >= 0) { + JS_ThrowTypeError(ctx, "proxy: duplicate property"); + goto fail; + } + } + + is_extensible = JS_IsExtensible(ctx, s->target); + if (is_extensible < 0) + goto fail; + + /* check if there are non configurable properties */ + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + goto fail; + } + if (JS_GetOwnPropertyNamesInternal(ctx, &tab2, &len2, JS_VALUE_GET_OBJ(s->target), + JS_GPN_STRING_MASK | JS_GPN_SYMBOL_MASK)) + goto fail; + for(i = 0; i < len2; i++) { + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + goto fail; + } + res = JS_GetOwnPropertyInternal(ctx, &desc, JS_VALUE_GET_OBJ(s->target), + tab2[i].atom); + if (res < 0) + goto fail; + if (res) { /* safety, property should be found */ + js_free_desc(ctx, &desc); + if (!(desc.flags & JS_PROP_CONFIGURABLE) || !is_extensible) { + idx = find_prop_key(tab, len, tab2[i].atom); + if (idx < 0) { + JS_ThrowTypeError(ctx, "proxy: target property must be present in proxy ownKeys"); + goto fail; + } + /* mark the property as found */ + if (!is_extensible) + tab[idx].is_enumerable = TRUE; + } + } + } + if (!is_extensible) { + /* check that all property in 'tab' were checked */ + for(i = 0; i < len; i++) { + if (!tab[i].is_enumerable) { + JS_ThrowTypeError(ctx, "proxy: property not present in target were returned by non extensible proxy"); + goto fail; + } + } + } + + js_free_prop_enum(ctx, tab2, len2); + JS_FreeValue(ctx, prop_array); + *ptab = tab; + *plen = len; + return 0; + fail: + js_free_prop_enum(ctx, tab2, len2); + js_free_prop_enum(ctx, tab, len); + JS_FreeValue(ctx, prop_array); + return -1; +} + +static JSValue js_proxy_call_constructor(JSContext *ctx, JSValue func_obj, + JSValue new_target, + int argc, JSValue *argv) +{ + JSProxyData *s; + JSValue method, arg_array, ret; + JSValue args[3]; + + s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_construct); + if (!s) + return JS_EXCEPTION; + if (!JS_IsConstructor(ctx, s->target)) + return JS_ThrowTypeError(ctx, "not a constructor"); + if (JS_IsUndefined(method)) + return JS_CallConstructor2(ctx, s->target, new_target, argc, argv); + arg_array = js_create_array(ctx, argc, argv); + if (JS_IsException(arg_array)) { + ret = JS_EXCEPTION; + goto fail; + } + args[0] = s->target; + args[1] = arg_array; + args[2] = new_target; + ret = JS_Call(ctx, method, s->handler, 3, args); + if (!JS_IsException(ret) && JS_VALUE_GET_TAG(ret) != JS_TAG_OBJECT) { + JS_FreeValue(ctx, ret); + ret = JS_ThrowTypeErrorNotAnObject(ctx); + } + fail: + JS_FreeValue(ctx, method); + JS_FreeValue(ctx, arg_array); + return ret; +} + +static JSValue js_proxy_call(JSContext *ctx, JSValue func_obj, + JSValue this_obj, + int argc, JSValue *argv, int flags) +{ + JSProxyData *s; + JSValue method, arg_array, ret; + JSValue args[3]; + + if (flags & JS_CALL_FLAG_CONSTRUCTOR) + return js_proxy_call_constructor(ctx, func_obj, this_obj, argc, argv); + + s = get_proxy_method(ctx, &method, func_obj, JS_ATOM_apply); + if (!s) + return JS_EXCEPTION; + if (!s->is_func) { + JS_FreeValue(ctx, method); + return JS_ThrowTypeError(ctx, "not a function"); + } + if (JS_IsUndefined(method)) + return JS_Call(ctx, s->target, this_obj, argc, argv); + arg_array = js_create_array(ctx, argc, argv); + if (JS_IsException(arg_array)) { + ret = JS_EXCEPTION; + goto fail; + } + args[0] = s->target; + args[1] = this_obj; + args[2] = arg_array; + ret = JS_Call(ctx, method, s->handler, 3, args); + fail: + JS_FreeValue(ctx, method); + JS_FreeValue(ctx, arg_array); + return ret; +} + +static int js_proxy_isArray(JSContext *ctx, JSValue obj) +{ + JSProxyData *s = JS_GetOpaque(obj, JS_CLASS_PROXY); + if (!s) + return FALSE; + + if (js_check_stack_overflow(ctx->rt, 0)) { + JS_ThrowStackOverflow(ctx); + return -1; + } + + if (s->is_revoked) { + JS_ThrowTypeErrorRevokedProxy(ctx); + return -1; + } + return JS_IsArray(ctx, s->target); +} + +static const JSClassExoticMethods js_proxy_exotic_methods = { + .get_own_property = js_proxy_get_own_property, + .define_own_property = js_proxy_define_own_property, + .delete_property = js_proxy_delete_property, + .get_own_property_names = js_proxy_get_own_property_names, + .has_property = js_proxy_has, + .get_property = js_proxy_get, + .set_property = js_proxy_set, +}; + +static JSValue js_proxy_constructor(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue target, handler; + JSValue obj; + JSProxyData *s; + + target = argv[0]; + handler = argv[1]; + if (JS_VALUE_GET_TAG(target) != JS_TAG_OBJECT || + JS_VALUE_GET_TAG(handler) != JS_TAG_OBJECT) + return JS_ThrowTypeErrorNotAnObject(ctx); + + obj = JS_NewObjectProtoClass(ctx, JS_NULL, JS_CLASS_PROXY); + if (JS_IsException(obj)) + return obj; + s = js_malloc(ctx, sizeof(JSProxyData)); + if (!s) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + s->target = js_dup(target); + s->handler = js_dup(handler); + s->is_func = JS_IsFunction(ctx, target); + s->is_revoked = FALSE; + JS_SetOpaqueInternal(obj, s); + JS_SetConstructorBit(ctx, obj, JS_IsConstructor(ctx, target)); + return obj; +} + +static JSValue js_proxy_revoke(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic, + JSValue *func_data) +{ + JSProxyData *s = JS_GetOpaque(func_data[0], JS_CLASS_PROXY); + if (s) { + /* We do not free the handler and target in case they are + referenced as constants in the C call stack */ + s->is_revoked = TRUE; + JS_FreeValue(ctx, func_data[0]); + func_data[0] = JS_NULL; + } + return JS_UNDEFINED; +} + +static JSValue js_proxy_revoke_constructor(JSContext *ctx, + JSValue proxy_obj) +{ + return JS_NewCFunctionData(ctx, js_proxy_revoke, 0, 0, 1, &proxy_obj); +} + +static JSValue js_proxy_revocable(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue proxy_obj, revoke_obj = JS_UNDEFINED, obj; + + proxy_obj = js_proxy_constructor(ctx, JS_UNDEFINED, argc, argv); + if (JS_IsException(proxy_obj)) + goto fail; + revoke_obj = js_proxy_revoke_constructor(ctx, proxy_obj); + if (JS_IsException(revoke_obj)) + goto fail; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + goto fail; + // XXX: exceptions? + JS_DefinePropertyValue(ctx, obj, JS_ATOM_proxy, proxy_obj, JS_PROP_C_W_E); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_revoke, revoke_obj, JS_PROP_C_W_E); + return obj; + fail: + JS_FreeValue(ctx, proxy_obj); + JS_FreeValue(ctx, revoke_obj); + return JS_EXCEPTION; +} + +static const JSCFunctionListEntry js_proxy_funcs[] = { + JS_CFUNC_DEF("revocable", 2, js_proxy_revocable ), +}; + +static const JSClassShortDef js_proxy_class_def[] = { + { JS_ATOM_Object, js_proxy_finalizer, js_proxy_mark }, /* JS_CLASS_PROXY */ +}; + +void JS_AddIntrinsicProxy(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + JSValue obj1; + + if (!JS_IsRegisteredClass(rt, JS_CLASS_PROXY)) { + init_class_range(rt, js_proxy_class_def, JS_CLASS_PROXY, + countof(js_proxy_class_def)); + rt->class_array[JS_CLASS_PROXY].exotic = &js_proxy_exotic_methods; + rt->class_array[JS_CLASS_PROXY].call = js_proxy_call; + } + + obj1 = JS_NewCFunction2(ctx, js_proxy_constructor, "Proxy", 2, + JS_CFUNC_constructor, 0); + JS_SetConstructorBit(ctx, obj1, TRUE); + JS_SetPropertyFunctionList(ctx, obj1, js_proxy_funcs, + countof(js_proxy_funcs)); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "Proxy", + obj1, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); +} + +/* Symbol */ + +static JSValue js_symbol_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue str; + JSString *p; + + if (!JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "not a constructor"); + if (argc == 0 || JS_IsUndefined(argv[0])) { + p = NULL; + } else { + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return JS_EXCEPTION; + p = JS_VALUE_GET_STRING(str); + } + return JS_NewSymbolInternal(ctx, p, JS_ATOM_TYPE_SYMBOL); +} + +static JSValue js_thisSymbolValue(JSContext *ctx, JSValue this_val) +{ + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_SYMBOL) + return js_dup(this_val); + + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_SYMBOL) { + if (JS_VALUE_GET_TAG(p->u.object_data) == JS_TAG_SYMBOL) + return js_dup(p->u.object_data); + } + } + return JS_ThrowTypeError(ctx, "not a symbol"); +} + +static JSValue js_symbol_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val, ret; + val = js_thisSymbolValue(ctx, this_val); + if (JS_IsException(val)) + return val; + /* XXX: use JS_ToStringInternal() with a flags */ + ret = js_string_constructor(ctx, JS_UNDEFINED, 1, &val); + JS_FreeValue(ctx, val); + return ret; +} + +static JSValue js_symbol_valueOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_thisSymbolValue(ctx, this_val); +} + +static JSValue js_symbol_get_description(JSContext *ctx, JSValue this_val) +{ + JSValue val, ret; + JSAtomStruct *p; + + val = js_thisSymbolValue(ctx, this_val); + if (JS_IsException(val)) + return val; + p = JS_VALUE_GET_PTR(val); + if (p->len == 0 && p->is_wide_char != 0) { + ret = JS_UNDEFINED; + } else { + ret = JS_AtomToString(ctx, js_get_atom_index(ctx->rt, p)); + } + JS_FreeValue(ctx, val); + return ret; +} + +static const JSCFunctionListEntry js_symbol_proto_funcs[] = { + JS_CFUNC_DEF("toString", 0, js_symbol_toString ), + JS_CFUNC_DEF("valueOf", 0, js_symbol_valueOf ), + // XXX: should have writable: false + JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_symbol_valueOf ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Symbol", JS_PROP_CONFIGURABLE ), + JS_CGETSET_DEF("description", js_symbol_get_description, NULL ), +}; + +static JSValue js_symbol_for(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str; + + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return JS_EXCEPTION; + return JS_NewSymbolInternal(ctx, JS_VALUE_GET_STRING(str), JS_ATOM_TYPE_GLOBAL_SYMBOL); +} + +static JSValue js_symbol_keyFor(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSAtomStruct *p; + + if (!JS_IsSymbol(argv[0])) + return JS_ThrowTypeError(ctx, "not a symbol"); + p = JS_VALUE_GET_PTR(argv[0]); + if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL) + return JS_UNDEFINED; + return js_dup(JS_MKPTR(JS_TAG_STRING, p)); +} + +static const JSCFunctionListEntry js_symbol_funcs[] = { + JS_CFUNC_DEF("for", 1, js_symbol_for ), + JS_CFUNC_DEF("keyFor", 1, js_symbol_keyFor ), +}; + +/* Set/Map/WeakSet/WeakMap */ + +typedef struct JSMapRecord { + int ref_count; /* used during enumeration to avoid freeing the record */ + BOOL empty; /* TRUE if the record is deleted */ + struct JSMapState *map; + struct list_head link; + struct list_head hash_link; + JSValue key; + JSValue value; +} JSMapRecord; + +typedef struct JSMapState { + BOOL is_weak; /* TRUE if WeakSet/WeakMap */ + struct list_head records; /* list of JSMapRecord.link */ + uint32_t record_count; + struct list_head *hash_table; + uint32_t hash_size; /* must be a power of two */ + uint32_t record_count_threshold; /* count at which a hash table + resize is needed */ +} JSMapState; + +#define MAGIC_SET (1 << 0) +#define MAGIC_WEAK (1 << 1) + +static JSValue js_map_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv, int magic) +{ + JSMapState *s; + JSValue obj, adder = JS_UNDEFINED, iter = JS_UNDEFINED, next_method = JS_UNDEFINED; + JSValue arr; + BOOL is_set, is_weak; + + is_set = magic & MAGIC_SET; + is_weak = ((magic & MAGIC_WEAK) != 0); + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_MAP + magic); + if (JS_IsException(obj)) + return JS_EXCEPTION; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + goto fail; + init_list_head(&s->records); + s->is_weak = is_weak; + JS_SetOpaqueInternal(obj, s); + s->hash_size = 1; + s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size); + if (!s->hash_table) + goto fail; + init_list_head(&s->hash_table[0]); + s->record_count_threshold = 4; + + arr = JS_UNDEFINED; + if (argc > 0) + arr = argv[0]; + if (!JS_IsUndefined(arr) && !JS_IsNull(arr)) { + JSValue item, ret; + BOOL done; + + adder = JS_GetProperty(ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set); + if (JS_IsException(adder)) + goto fail; + if (!JS_IsFunction(ctx, adder)) { + JS_ThrowTypeError(ctx, "set/add is not a function"); + goto fail; + } + + iter = JS_GetIterator(ctx, arr, FALSE); + if (JS_IsException(iter)) + goto fail; + next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next_method)) + goto fail; + + for(;;) { + item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail; + if (done) { + JS_FreeValue(ctx, item); + break; + } + if (is_set) { + ret = JS_Call(ctx, adder, obj, 1, &item); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, item); + goto fail; + } + } else { + JSValue key, value; + JSValue args[2]; + key = JS_UNDEFINED; + value = JS_UNDEFINED; + if (!JS_IsObject(item)) { + JS_ThrowTypeErrorNotAnObject(ctx); + goto fail1; + } + key = JS_GetPropertyUint32(ctx, item, 0); + if (JS_IsException(key)) + goto fail1; + value = JS_GetPropertyUint32(ctx, item, 1); + if (JS_IsException(value)) + goto fail1; + args[0] = key; + args[1] = value; + ret = JS_Call(ctx, adder, obj, 2, args); + if (JS_IsException(ret)) { + fail1: + JS_FreeValue(ctx, item); + JS_FreeValue(ctx, key); + JS_FreeValue(ctx, value); + goto fail; + } + JS_FreeValue(ctx, key); + JS_FreeValue(ctx, value); + } + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, item); + } + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, adder); + } + return obj; + fail: + if (JS_IsObject(iter)) { + /* close the iterator object, preserving pending exception */ + JS_IteratorClose(ctx, iter, TRUE); + } + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, adder); + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +/* XXX: could normalize strings to speed up comparison */ +static JSValue map_normalize_key(JSContext *ctx, JSValue key) +{ + uint32_t tag = JS_VALUE_GET_TAG(key); + /* convert -0.0 to +0.0 */ + if (JS_TAG_IS_FLOAT64(tag) && JS_VALUE_GET_FLOAT64(key) == 0.0) { + key = js_int32(0); + } + return key; +} + +/* XXX: better hash ? */ +static uint32_t map_hash_key(JSContext *ctx, JSValue key) +{ + uint32_t tag = JS_VALUE_GET_NORM_TAG(key); + uint32_t h; + double d; + JSFloat64Union u; + bf_t *a; + + switch(tag) { + case JS_TAG_BOOL: + h = JS_VALUE_GET_INT(key); + break; + case JS_TAG_STRING: + h = hash_string(JS_VALUE_GET_STRING(key), 0); + break; + case JS_TAG_OBJECT: + case JS_TAG_SYMBOL: + h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163; + break; + case JS_TAG_INT: + d = JS_VALUE_GET_INT(key); + goto hash_float64; + case JS_TAG_BIG_INT: + a = JS_GetBigInt(key); + h = hash_string8((void *)a->tab, a->len * sizeof(*a->tab), 0); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64(key); + /* normalize the NaN */ + if (isnan(d)) + d = JS_FLOAT64_NAN; + hash_float64: + u.d = d; + h = (u.u32[0] ^ u.u32[1]) * 3163; + return h ^= JS_TAG_FLOAT64; + default: + h = 0; + break; + } + h ^= tag; + return h; +} + +static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s, + JSValue key) +{ + struct list_head *el; + JSMapRecord *mr; + uint32_t h; + h = map_hash_key(ctx, key) & (s->hash_size - 1); + list_for_each(el, &s->hash_table[h]) { + mr = list_entry(el, JSMapRecord, hash_link); + if (js_same_value_zero(ctx, mr->key, key)) + return mr; + } + return NULL; +} + +static void map_hash_resize(JSContext *ctx, JSMapState *s) +{ + uint32_t new_hash_size, i, h; + size_t slack; + struct list_head *new_hash_table, *el; + JSMapRecord *mr; + + /* XXX: no reporting of memory allocation failure */ + if (s->hash_size == 1) + new_hash_size = 4; + else + new_hash_size = s->hash_size * 2; + new_hash_table = js_realloc2(ctx, s->hash_table, + sizeof(new_hash_table[0]) * new_hash_size, &slack); + if (!new_hash_table) + return; + new_hash_size += slack / sizeof(*new_hash_table); + + for(i = 0; i < new_hash_size; i++) + init_list_head(&new_hash_table[i]); + + list_for_each(el, &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (!mr->empty) { + h = map_hash_key(ctx, mr->key) & (new_hash_size - 1); + list_add_tail(&mr->hash_link, &new_hash_table[h]); + } + } + s->hash_table = new_hash_table; + s->hash_size = new_hash_size; + s->record_count_threshold = new_hash_size * 2; +} + +static JSWeakRefRecord **get_first_weak_ref(JSValue key) +{ + switch (JS_VALUE_GET_TAG(key)) { + case JS_TAG_OBJECT: + { + JSObject *p = JS_VALUE_GET_OBJ(key); + return &p->first_weak_ref; + } + break; + case JS_TAG_SYMBOL: + { + JSAtomStruct *p = JS_VALUE_GET_PTR(key); + return &p->first_weak_ref; + } + break; + default: + abort(); + } + return NULL; // pacify compiler +} + +static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s, + JSValue key) +{ + uint32_t h; + JSMapRecord *mr; + + mr = js_malloc(ctx, sizeof(*mr)); + if (!mr) + return NULL; + mr->ref_count = 1; + mr->map = s; + mr->empty = FALSE; + if (s->is_weak) { + JSWeakRefRecord *wr = js_malloc(ctx, sizeof(*wr)); + if (!wr) { + js_free(ctx, mr); + return NULL; + } + wr->kind = JS_WEAK_REF_KIND_MAP; + wr->u.map_record = mr; + insert_weakref_record(key, wr); + } else { + js_dup(key); + } + mr->key = key; + h = map_hash_key(ctx, key) & (s->hash_size - 1); + list_add_tail(&mr->hash_link, &s->hash_table[h]); + list_add_tail(&mr->link, &s->records); + s->record_count++; + if (s->record_count >= s->record_count_threshold) { + map_hash_resize(ctx, s); + } + return mr; +} + +/* Remove the weak reference from the object weak + reference list. we don't use a doubly linked list to + save space, assuming a given object has few weak + references to it */ +static void delete_map_weak_ref(JSRuntime *rt, JSMapRecord *mr) +{ + JSWeakRefRecord **pwr, *wr; + + pwr = get_first_weak_ref(mr->key); + for(;;) { + wr = *pwr; + assert(wr != NULL); + if (wr->kind == JS_WEAK_REF_KIND_MAP && wr->u.map_record == mr) + break; + pwr = &wr->next_weak_ref; + } + *pwr = wr->next_weak_ref; + js_free_rt(rt, wr); +} + +static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr) +{ + if (mr->empty) + return; + list_del(&mr->hash_link); + if (s->is_weak) { + delete_map_weak_ref(rt, mr); + } else { + JS_FreeValueRT(rt, mr->key); + } + JS_FreeValueRT(rt, mr->value); + if (--mr->ref_count == 0) { + list_del(&mr->link); + js_free_rt(rt, mr); + } else { + /* keep a zombie record for iterators */ + mr->empty = TRUE; + mr->key = JS_UNDEFINED; + mr->value = JS_UNDEFINED; + } + s->record_count--; +} + +static void map_decref_record(JSRuntime *rt, JSMapRecord *mr) +{ + if (--mr->ref_count == 0) { + /* the record can be safely removed */ + assert(mr->empty); + list_del(&mr->link); + js_free_rt(rt, mr); + } +} + +static JSValue js_map_set(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + JSMapRecord *mr; + JSValue key, value; + int is_set; + + if (!s) + return JS_EXCEPTION; + is_set = (magic & MAGIC_SET); + key = map_normalize_key(ctx, argv[0]); + if (s->is_weak && !is_valid_weakref_target(key)) + return JS_ThrowTypeError(ctx, "invalid value used as %s key", is_set ? "WeakSet" : "WeakMap"); + if (is_set) + value = JS_UNDEFINED; + else + value = argv[1]; + mr = map_find_record(ctx, s, key); + if (mr) { + JS_FreeValue(ctx, mr->value); + } else { + mr = map_add_record(ctx, s, key); + if (!mr) + return JS_EXCEPTION; + } + mr->value = js_dup(value); + return js_dup(this_val); +} + +static JSValue js_map_get(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + JSMapRecord *mr; + JSValue key; + + if (!s) + return JS_EXCEPTION; + key = map_normalize_key(ctx, argv[0]); + mr = map_find_record(ctx, s, key); + if (!mr) + return JS_UNDEFINED; + else + return js_dup(mr->value); +} + +static JSValue js_map_has(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + JSMapRecord *mr; + JSValue key; + + if (!s) + return JS_EXCEPTION; + key = map_normalize_key(ctx, argv[0]); + mr = map_find_record(ctx, s, key); + return js_bool(mr != NULL); +} + +static JSValue js_map_delete(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + JSMapRecord *mr; + JSValue key; + + if (!s) + return JS_EXCEPTION; + key = map_normalize_key(ctx, argv[0]); + mr = map_find_record(ctx, s, key); + if (!mr) + return JS_FALSE; + map_delete_record(ctx->rt, s, mr); + return JS_TRUE; +} + +static JSValue js_map_clear(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + struct list_head *el, *el1; + JSMapRecord *mr; + + if (!s) + return JS_EXCEPTION; + list_for_each_safe(el, el1, &s->records) { + mr = list_entry(el, JSMapRecord, link); + map_delete_record(ctx->rt, s, mr); + } + return JS_UNDEFINED; +} + +static JSValue js_map_get_size(JSContext *ctx, JSValue this_val, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + if (!s) + return JS_EXCEPTION; + return js_uint32(s->record_count); +} + +static JSValue js_map_forEach(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + JSValue func, this_arg; + JSValue ret, args[3]; + struct list_head *el; + JSMapRecord *mr; + + if (!s) + return JS_EXCEPTION; + func = argv[0]; + if (argc > 1) + this_arg = argv[1]; + else + this_arg = JS_UNDEFINED; + if (check_function(ctx, func)) + return JS_EXCEPTION; + /* Note: the list can be modified while traversing it, but the + current element is locked */ + el = s->records.next; + while (el != &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (!mr->empty) { + mr->ref_count++; + /* must duplicate in case the record is deleted */ + args[1] = js_dup(mr->key); + if (magic) + args[0] = args[1]; + else + args[0] = js_dup(mr->value); + args[2] = this_val; + ret = JS_Call(ctx, func, this_arg, 3, args); + JS_FreeValue(ctx, args[0]); + if (!magic) + JS_FreeValue(ctx, args[1]); + el = el->next; + map_decref_record(ctx->rt, mr); + if (JS_IsException(ret)) + return ret; + JS_FreeValue(ctx, ret); + } else { + el = el->next; + } + } + return JS_UNDEFINED; +} + +static JSValue js_map_groupBy(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue cb, res, iter, next, groups, k, v, prop; + JSValue args[2]; + int64_t idx; + BOOL done; + + // "is function?" check must be observed before argv[0] is accessed + cb = argv[1]; + if (check_function(ctx, cb)) + return JS_EXCEPTION; + + iter = JS_GetIterator(ctx, argv[0], /*is_async*/FALSE); + if (JS_IsException(iter)) + return JS_EXCEPTION; + + k = JS_UNDEFINED; + v = JS_UNDEFINED; + prop = JS_UNDEFINED; + groups = JS_UNDEFINED; + + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + + groups = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, 0); + if (JS_IsException(groups)) + goto exception; + + for (idx = 0; ; idx++) { + v = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(v)) + goto exception; + if (done) + break; // v is JS_UNDEFINED + + args[0] = v; + args[1] = js_int64(idx); + k = JS_Call(ctx, cb, ctx->global_obj, 2, args); + if (JS_IsException(k)) + goto exception; + + prop = js_map_get(ctx, groups, 1, &k, 0); + if (JS_IsException(prop)) + goto exception; + + if (JS_IsUndefined(prop)) { + prop = JS_NewArray(ctx); + if (JS_IsException(prop)) + goto exception; + args[0] = k; + args[1] = prop; + res = js_map_set(ctx, groups, 2, args, 0); + if (JS_IsException(res)) + goto exception; + JS_FreeValue(ctx, res); + } + + res = js_array_push(ctx, prop, 1, &v, /*unshift*/0); + if (JS_IsException(res)) + goto exception; + // res is an int64 + + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, k); + JS_FreeValue(ctx, v); + prop = JS_UNDEFINED; + k = JS_UNDEFINED; + v = JS_UNDEFINED; + } + + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return groups; + +exception: + JS_FreeValue(ctx, prop); + JS_FreeValue(ctx, k); + JS_FreeValue(ctx, v); + JS_FreeValue(ctx, groups); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return JS_EXCEPTION; +} + +static void js_map_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p; + JSMapState *s; + struct list_head *el, *el1; + JSMapRecord *mr; + + p = JS_VALUE_GET_OBJ(val); + s = p->u.map_state; + if (s) { + /* if the object is deleted we are sure that no iterator is + using it */ + list_for_each_safe(el, el1, &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (!mr->empty) { + if (s->is_weak) + delete_map_weak_ref(rt, mr); + else + JS_FreeValueRT(rt, mr->key); + JS_FreeValueRT(rt, mr->value); + } + js_free_rt(rt, mr); + } + js_free_rt(rt, s->hash_table); + js_free_rt(rt, s); + } +} + +static void js_map_mark(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSMapState *s; + struct list_head *el; + JSMapRecord *mr; + + s = p->u.map_state; + if (s) { + list_for_each(el, &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (!s->is_weak) + JS_MarkValue(rt, mr->key, mark_func); + JS_MarkValue(rt, mr->value, mark_func); + } + } +} + +/* Map Iterator */ + +typedef struct JSMapIteratorData { + JSValue obj; + JSIteratorKindEnum kind; + JSMapRecord *cur_record; +} JSMapIteratorData; + +static void js_map_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p; + JSMapIteratorData *it; + + p = JS_VALUE_GET_OBJ(val); + it = p->u.map_iterator_data; + if (it) { + /* During the GC sweep phase the Map finalizer may be + called before the Map iterator finalizer */ + if (JS_IsLiveObject(rt, it->obj) && it->cur_record) { + map_decref_record(rt, it->cur_record); + } + JS_FreeValueRT(rt, it->obj); + js_free_rt(rt, it); + } +} + +static void js_map_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSMapIteratorData *it; + it = p->u.map_iterator_data; + if (it) { + /* the record is already marked by the object */ + JS_MarkValue(rt, it->obj, mark_func); + } +} + +static JSValue js_create_map_iterator(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSIteratorKindEnum kind; + JSMapState *s; + JSMapIteratorData *it; + JSValue enum_obj; + + kind = magic >> 2; + magic &= 3; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic); + if (!s) + return JS_EXCEPTION; + enum_obj = JS_NewObjectClass(ctx, JS_CLASS_MAP_ITERATOR + magic); + if (JS_IsException(enum_obj)) + goto fail; + it = js_malloc(ctx, sizeof(*it)); + if (!it) { + JS_FreeValue(ctx, enum_obj); + goto fail; + } + it->obj = js_dup(this_val); + it->kind = kind; + it->cur_record = NULL; + JS_SetOpaqueInternal(enum_obj, it); + return enum_obj; + fail: + return JS_EXCEPTION; +} + +static JSValue js_map_iterator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + BOOL *pdone, int magic) +{ + JSMapIteratorData *it; + JSMapState *s; + JSMapRecord *mr; + struct list_head *el; + + it = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic); + if (!it) { + *pdone = FALSE; + return JS_EXCEPTION; + } + if (JS_IsUndefined(it->obj)) + goto done; + s = JS_GetOpaque(it->obj, JS_CLASS_MAP + magic); + assert(s != NULL); + if (!it->cur_record) { + el = s->records.next; + } else { + mr = it->cur_record; + el = mr->link.next; + map_decref_record(ctx->rt, mr); /* the record can be freed here */ + } + for(;;) { + if (el == &s->records) { + /* no more record */ + it->cur_record = NULL; + JS_FreeValue(ctx, it->obj); + it->obj = JS_UNDEFINED; + done: + /* end of enumeration */ + *pdone = TRUE; + return JS_UNDEFINED; + } + mr = list_entry(el, JSMapRecord, link); + if (!mr->empty) + break; + /* get the next record */ + el = mr->link.next; + } + + /* lock the record so that it won't be freed */ + mr->ref_count++; + it->cur_record = mr; + *pdone = FALSE; + + if (it->kind == JS_ITERATOR_KIND_KEY) { + return js_dup(mr->key); + } else { + JSValue args[2]; + args[0] = mr->key; + if (magic) + args[1] = mr->key; + else + args[1] = mr->value; + if (it->kind == JS_ITERATOR_KIND_VALUE) { + return js_dup(args[1]); + } else { + return js_create_array(ctx, 2, args); + } + } +} + +static JSValue js_map_read(BCReaderState *s, int magic) +{ + JSContext *ctx = s->ctx; + JSValue obj, rv, argv[2]; + uint32_t i, prop_count; + + argv[0] = JS_UNDEFINED; + argv[1] = JS_UNDEFINED; + obj = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, magic); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (BC_add_object_ref(s, obj)) + goto fail; + if (bc_get_leb128(s, &prop_count)) + goto fail; + for(i = 0; i < prop_count; i++) { + argv[0] = JS_ReadObjectRec(s); + if (JS_IsException(argv[0])) + goto fail; + if (!(magic & MAGIC_SET)) { + argv[1] = JS_ReadObjectRec(s); + if (JS_IsException(argv[1])) + goto fail; + } + rv = js_map_set(ctx, obj, countof(argv), argv, magic); + if (JS_IsException(rv)) + goto fail; + JS_FreeValue(ctx, rv); + JS_FreeValue(ctx, argv[0]); + JS_FreeValue(ctx, argv[1]); + argv[0] = JS_UNDEFINED; + argv[1] = JS_UNDEFINED; + } + return obj; + fail: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, argv[0]); + JS_FreeValue(ctx, argv[1]); + return JS_EXCEPTION; +} + +static int js_map_write(BCWriterState *s, struct JSMapState *map_state, + int magic) +{ + struct list_head *el; + JSMapRecord *mr; + + bc_put_leb128(s, map_state ? map_state->record_count : 0); + if (map_state) { + list_for_each(el, &map_state->records) { + mr = list_entry(el, JSMapRecord, link); + if (JS_WriteObjectRec(s, mr->key)) + return -1; + // mr->value is always JS_UNDEFINED for sets + if (!(magic & MAGIC_SET)) + if (JS_WriteObjectRec(s, mr->value)) + return -1; + } + } + + return 0; +} + +static JSValue JS_ReadMap(BCReaderState *s) +{ + return js_map_read(s, 0); +} + +static JSValue JS_ReadSet(BCReaderState *s) +{ + return js_map_read(s, MAGIC_SET); +} + +static int JS_WriteMap(BCWriterState *s, struct JSMapState *map_state) +{ + return js_map_write(s, map_state, 0); +} + +static int JS_WriteSet(BCWriterState *s, struct JSMapState *map_state) +{ + return js_map_write(s, map_state, MAGIC_SET); +} + +static int js_setlike_get_size(JSContext *ctx, JSValue setlike, int64_t *pout) +{ + JSMapState *s; + JSValue v; + double d; + + s = JS_GetOpaque(setlike, JS_CLASS_SET); + if (s) { + *pout = s->record_count; + } else { + v = JS_GetProperty(ctx, setlike, JS_ATOM_size); + if (JS_IsException(v)) + return -1; + if (JS_IsUndefined(v)) { + JS_ThrowTypeError(ctx, ".size is undefined"); + return -1; + } + if (JS_ToFloat64Free(ctx, &d, v) < 0) + return -1; + if (isnan(d)) { + JS_ThrowTypeError(ctx, ".size is not a number"); + return -1; + } + *pout = d; + } + return 0; +} + +static int js_setlike_get_has(JSContext *ctx, JSValue setlike, JSValue *pout) +{ + JSValue v; + + v = JS_GetProperty(ctx, setlike, JS_ATOM_has); + if (JS_IsException(v)) + return -1; + if (JS_IsUndefined(v)) { + JS_ThrowTypeError(ctx, ".has is undefined"); + return -1; + } + if (!JS_IsFunction(ctx, v)) { + JS_ThrowTypeError(ctx, ".has is not a function"); + JS_FreeValue(ctx, v); + return -1; + } + *pout = v; + return 0; +} + +static int js_setlike_get_keys(JSContext *ctx, JSValue setlike, JSValue *pout) +{ + JSValue v; + + v = JS_GetProperty(ctx, setlike, JS_ATOM_keys); + if (JS_IsException(v)) + return -1; + if (JS_IsUndefined(v)) { + JS_ThrowTypeError(ctx, ".keys is undefined"); + return -1; + } + if (!JS_IsFunction(ctx, v)) { + JS_ThrowTypeError(ctx, ".keys is not a function"); + JS_FreeValue(ctx, v); + return -1; + } + *pout = v; + return 0; +} + +static JSValue js_set_isDisjointFrom(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue item, iter, keys, has, next, rv, rval; + BOOL done, found; + JSMapState *s; + int64_t size; + int ok; + + has = JS_UNDEFINED; + iter = JS_UNDEFINED; + keys = JS_UNDEFINED; + next = JS_UNDEFINED; + rval = JS_EXCEPTION; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + goto exception; + // order matters! + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + goto exception; + if (js_setlike_get_has(ctx, argv[0], &has) < 0) + goto exception; + if (js_setlike_get_keys(ctx, argv[0], &keys) < 0) + goto exception; + if (s->record_count > size) { + iter = JS_Call(ctx, keys, argv[0], 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + found = FALSE; + do { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + found = (NULL != map_find_record(ctx, s, item)); + JS_FreeValue(ctx, item); + } while (!found); + } else { + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + found = FALSE; + do { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv[0], 1, &item); + JS_FreeValue(ctx, item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok < 0) + goto exception; + found = (ok > 0); + } while (!found); + } + rval = !found ? JS_TRUE : JS_FALSE; +exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return rval; +} + +static JSValue js_set_isSubsetOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue item, iter, keys, has, next, rv, rval; + BOOL done, found; + JSMapState *s; + int64_t size; + int ok; + + has = JS_UNDEFINED; + iter = JS_UNDEFINED; + keys = JS_UNDEFINED; + next = JS_UNDEFINED; + rval = JS_EXCEPTION; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + goto exception; + // order matters! + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + goto exception; + if (js_setlike_get_has(ctx, argv[0], &has) < 0) + goto exception; + if (js_setlike_get_keys(ctx, argv[0], &keys) < 0) + goto exception; + found = FALSE; + if (s->record_count > size) + goto fini; + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + found = TRUE; + do { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv[0], 1, &item); + JS_FreeValue(ctx, item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok < 0) + goto exception; + found = (ok > 0); + } while (found); +fini: + rval = found ? JS_TRUE : JS_FALSE; +exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return rval; +} + +static JSValue js_set_isSupersetOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue item, iter, keys, has, next, rval; + BOOL done, found; + JSMapState *s; + int64_t size; + + has = JS_UNDEFINED; + iter = JS_UNDEFINED; + keys = JS_UNDEFINED; + next = JS_UNDEFINED; + rval = JS_EXCEPTION; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + goto exception; + // order matters! + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + goto exception; + if (js_setlike_get_has(ctx, argv[0], &has) < 0) + goto exception; + if (js_setlike_get_keys(ctx, argv[0], &keys) < 0) + goto exception; + found = FALSE; + if (s->record_count < size) + goto fini; + iter = JS_Call(ctx, keys, argv[0], 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + found = TRUE; + do { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + found = (NULL != map_find_record(ctx, s, item)); + JS_FreeValue(ctx, item); + } while (found); +fini: + rval = found ? JS_TRUE : JS_FALSE; +exception: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return rval; +} + +static JSValue js_set_intersection(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue newset, item, iter, keys, has, next, rv; + JSMapState *s, *t; + JSMapRecord *mr; + int64_t size; + BOOL done; + int ok; + + has = JS_UNDEFINED; + iter = JS_UNDEFINED; + keys = JS_UNDEFINED; + next = JS_UNDEFINED; + newset = JS_UNDEFINED; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + goto exception; + // order matters! + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + goto exception; + if (js_setlike_get_has(ctx, argv[0], &has) < 0) + goto exception; + if (js_setlike_get_keys(ctx, argv[0], &keys) < 0) + goto exception; + if (s->record_count > size) { + iter = JS_Call(ctx, keys, argv[0], 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + if (!map_find_record(ctx, s, item)) { + JS_FreeValue(ctx, item); + } else if (map_find_record(ctx, t, item)) { + JS_FreeValue(ctx, item); // no duplicates + } else if ((mr = map_add_record(ctx, t, item))) { + mr->value = JS_UNDEFINED; + } else { + JS_FreeValue(ctx, item); + goto exception; + } + } + } else { + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv[0], 1, &item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok > 0) { + item = map_normalize_key(ctx, item); + if (map_find_record(ctx, t, item)) { + JS_FreeValue(ctx, item); // no duplicates + } else if ((mr = map_add_record(ctx, t, item))) { + mr->value = JS_UNDEFINED; + } else { + JS_FreeValue(ctx, item); + goto exception; + } + } else { + JS_FreeValue(ctx, item); + if (ok < 0) + goto exception; + } + } + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return newset; +} + +static JSValue js_set_difference(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue newset, item, iter, keys, has, next, rv; + JSMapState *s, *t; + JSMapRecord *mr; + int64_t size; + BOOL done; + int ok; + + has = JS_UNDEFINED; + iter = JS_UNDEFINED; + keys = JS_UNDEFINED; + next = JS_UNDEFINED; + newset = JS_UNDEFINED; + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + goto exception; + // order matters! + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + goto exception; + if (js_setlike_get_has(ctx, argv[0], &has) < 0) + goto exception; + if (js_setlike_get_keys(ctx, argv[0], &keys) < 0) + goto exception; + if (s->record_count > size) { + iter = JS_Call(ctx, keys, argv[0], 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + newset = js_map_constructor(ctx, JS_UNDEFINED, 1, &this_val, MAGIC_SET); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + item = map_normalize_key(ctx, item); + mr = map_find_record(ctx, t, item); + if (mr) + map_delete_record(ctx->rt, t, mr); + JS_FreeValue(ctx, item); + } + } else { + iter = js_create_map_iterator(ctx, this_val, 0, NULL, MAGIC_SET); + if (JS_IsException(iter)) + goto exception; + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + goto exception; + t = JS_GetOpaque(newset, JS_CLASS_SET); + for (;;) { + item = js_map_iterator_next(ctx, iter, 0, NULL, &done, MAGIC_SET); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = JS_Call(ctx, has, argv[0], 1, &item); + ok = JS_ToBoolFree(ctx, rv); // returns -1 if rv is JS_EXCEPTION + if (ok == 0) { + item = map_normalize_key(ctx, item); + if (map_find_record(ctx, t, item)) { + JS_FreeValue(ctx, item); // no duplicates + } else if ((mr = map_add_record(ctx, t, item))) { + mr->value = JS_UNDEFINED; + } else { + JS_FreeValue(ctx, item); + goto exception; + } + } else { + JS_FreeValue(ctx, item); + if (ok < 0) + goto exception; + } + } + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, has); + JS_FreeValue(ctx, keys); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, next); + return newset; +} + +static JSValue js_set_symmetricDifference(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue newset, item, iter, next, rv; + struct list_head *el; + JSMapState *s, *t; + JSMapRecord *mr; + int64_t size; + BOOL done, present; + + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + // order matters! they're JS-observable side effects + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + return JS_EXCEPTION; + if (js_setlike_get_has(ctx, argv[0], &rv) < 0) + return JS_EXCEPTION; + JS_FreeValue(ctx, rv); + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + return JS_EXCEPTION; + t = JS_GetOpaque(newset, JS_CLASS_SET); + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + // can't clone this_val using js_map_constructor(), + // test262 mandates we don't call the .add method + list_for_each(el, &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (mr->empty) + continue; + mr = map_add_record(ctx, t, js_dup(mr->key)); + if (!mr) + goto exception; + mr->value = JS_UNDEFINED; + } + iter = JS_GetProperty(ctx, argv[0], JS_ATOM_keys); + if (JS_IsException(iter)) + goto exception; + iter = JS_CallFree(ctx, iter, argv[0], 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + // note the subtlety here: due to mutating iterators, it's + // possible for keys to disappear during iteration; test262 + // still expects us to maintain insertion order though, so + // we first check |this|, then |new|; |new| is a copy of |this| + // - if item exists in |this|, delete (if it exists) from |new| + // - if item misses in |this| and |new|, add to |new| + // - if item exists in |new| but misses in |this|, *don't* add it, + // mutating iterator erased it + item = map_normalize_key(ctx, item); + present = (NULL != map_find_record(ctx, s, item)); + mr = map_find_record(ctx, t, item); + if (present) { + if (mr) + map_delete_record(ctx->rt, t, mr); + JS_FreeValue(ctx, item); + } else if (mr) { + JS_FreeValue(ctx, item); + } else { + mr = map_add_record(ctx, t, item); + if (!mr) { + JS_FreeValue(ctx, item); + goto exception; + } + mr->value = JS_UNDEFINED; + } + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, next); + JS_FreeValue(ctx, iter); + return newset; +} + +static JSValue js_set_union(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue newset, item, iter, next, rv; + struct list_head *el; + JSMapState *s, *t; + JSMapRecord *mr; + int64_t size; + BOOL done; + + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_SET); + if (!s) + return JS_EXCEPTION; + // order matters! they're JS-observable side effects + if (js_setlike_get_size(ctx, argv[0], &size) < 0) + return JS_EXCEPTION; + if (js_setlike_get_has(ctx, argv[0], &rv) < 0) + return JS_EXCEPTION; + JS_FreeValue(ctx, rv); + newset = js_map_constructor(ctx, JS_UNDEFINED, 0, NULL, MAGIC_SET); + if (JS_IsException(newset)) + return JS_EXCEPTION; + t = JS_GetOpaque(newset, JS_CLASS_SET); + iter = JS_UNDEFINED; + next = JS_UNDEFINED; + list_for_each(el, &s->records) { + mr = list_entry(el, JSMapRecord, link); + if (mr->empty) + continue; + mr = map_add_record(ctx, t, js_dup(mr->key)); + if (!mr) + goto exception; + mr->value = JS_UNDEFINED; + } + iter = JS_GetProperty(ctx, argv[0], JS_ATOM_keys); + if (JS_IsException(iter)) + goto exception; + iter = JS_CallFree(ctx, iter, argv[0], 0, NULL); + if (JS_IsException(iter)) + goto exception; + next = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next)) + goto exception; + for (;;) { + item = JS_IteratorNext(ctx, iter, next, 0, NULL, &done); + if (JS_IsException(item)) + goto exception; + if (done) // item is JS_UNDEFINED + break; + rv = js_map_set(ctx, newset, 1, &item, MAGIC_SET); + JS_FreeValue(ctx, item); + if (JS_IsException(rv)) + goto exception; + JS_FreeValue(ctx, rv); + } + goto fini; +exception: + JS_FreeValue(ctx, newset); + newset = JS_EXCEPTION; +fini: + JS_FreeValue(ctx, next); + JS_FreeValue(ctx, iter); + return newset; +} + +static const JSCFunctionListEntry js_map_funcs[] = { + JS_CFUNC_DEF("groupBy", 2, js_map_groupBy ), + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static const JSCFunctionListEntry js_set_funcs[] = { + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static const JSCFunctionListEntry js_map_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ), + JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ), + JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ), + JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ), + JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ), + JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, 0), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0 ), + JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_VALUE << 2) | 0 ), + JS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | 0 ), + JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0 ), + JS_ALIAS_DEF("[Symbol.iterator]", "entries" ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_map_iterator_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0 ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_set_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET ), + JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET ), + JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET ), + JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ), + JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ), + JS_CFUNC_DEF("isDisjointFrom", 1, js_set_isDisjointFrom ), + JS_CFUNC_DEF("isSubsetOf", 1, js_set_isSubsetOf ), + JS_CFUNC_DEF("isSupersetOf", 1, js_set_isSupersetOf ), + JS_CFUNC_DEF("intersection", 1, js_set_intersection ), + JS_CFUNC_DEF("difference", 1, js_set_difference ), + JS_CFUNC_DEF("symmetricDifference", 1, js_set_symmetricDifference ), + JS_CFUNC_DEF("union", 1, js_set_union ), + JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ), + JS_ALIAS_DEF("keys", "values" ), + JS_ALIAS_DEF("[Symbol.iterator]", "values" ), + JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_set_iterator_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_weak_map_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ), + JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ), + JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ), + JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_weak_set_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK ), + JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK ), + JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry * const js_map_proto_funcs_ptr[6] = { + js_map_proto_funcs, + js_set_proto_funcs, + js_weak_map_proto_funcs, + js_weak_set_proto_funcs, + js_map_iterator_proto_funcs, + js_set_iterator_proto_funcs, +}; + +static const uint8_t js_map_proto_funcs_count[6] = { + countof(js_map_proto_funcs), + countof(js_set_proto_funcs), + countof(js_weak_map_proto_funcs), + countof(js_weak_set_proto_funcs), + countof(js_map_iterator_proto_funcs), + countof(js_set_iterator_proto_funcs), +}; + +void JS_AddIntrinsicMapSet(JSContext *ctx) +{ + int i; + JSValue obj1; + char buf[ATOM_GET_STR_BUF_SIZE]; + + for(i = 0; i < 4; i++) { + const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf), + JS_ATOM_Map + i); + int class_id = JS_CLASS_MAP + i; + ctx->class_proto[class_id] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[class_id], + js_map_proto_funcs_ptr[i], + js_map_proto_funcs_count[i]); + obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0, + JS_CFUNC_constructor_magic, i); + if (class_id == JS_CLASS_MAP) + JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs, countof(js_map_funcs)); + else if (class_id == JS_CLASS_SET) + JS_SetPropertyFunctionList(ctx, obj1, js_set_funcs, countof(js_set_funcs)); + + JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_proto[class_id]); + } + + for(i = 0; i < 2; i++) { + ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] = + JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP_ITERATOR + i], + js_map_proto_funcs_ptr[i + 4], + js_map_proto_funcs_count[i + 4]); + } +} + +/* Generator */ +static const JSCFunctionListEntry js_generator_function_proto_funcs[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "GeneratorFunction", JS_PROP_CONFIGURABLE), +}; + +static const JSCFunctionListEntry js_generator_proto_funcs[] = { + JS_ITERATOR_NEXT_DEF("next", 1, js_generator_next, GEN_MAGIC_NEXT ), + JS_ITERATOR_NEXT_DEF("return", 1, js_generator_next, GEN_MAGIC_RETURN ), + JS_ITERATOR_NEXT_DEF("throw", 1, js_generator_next, GEN_MAGIC_THROW ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Generator", JS_PROP_CONFIGURABLE), +}; + +/* Promise */ + +typedef struct JSPromiseData { + JSPromiseStateEnum promise_state; + /* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */ + struct list_head promise_reactions[2]; + BOOL is_handled; /* Note: only useful to debug */ + JSValue promise_result; +} JSPromiseData; + +typedef struct JSPromiseFunctionDataResolved { + int ref_count; + BOOL already_resolved; +} JSPromiseFunctionDataResolved; + +typedef struct JSPromiseFunctionData { + JSValue promise; + JSPromiseFunctionDataResolved *presolved; +} JSPromiseFunctionData; + +typedef struct JSPromiseReactionData { + struct list_head link; /* not used in promise_reaction_job */ + JSValue resolving_funcs[2]; + JSValue handler; +} JSPromiseReactionData; + + JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise) +{ + JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); + if (!s) + return -1; + return s->promise_state; +} + +JSValue JS_PromiseResult(JSContext *ctx, JSValue promise) +{ + JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); + if (!s) + return JS_UNDEFINED; + return JS_DupValue(ctx, s->promise_result); +} + +static int js_create_resolving_functions(JSContext *ctx, JSValue *args, + JSValue promise); + +static void promise_reaction_data_free(JSRuntime *rt, + JSPromiseReactionData *rd) +{ + JS_FreeValueRT(rt, rd->resolving_funcs[0]); + JS_FreeValueRT(rt, rd->resolving_funcs[1]); + JS_FreeValueRT(rt, rd->handler); + js_free_rt(rt, rd); +} + +#ifdef DUMP_PROMISE +#define promise_trace(ctx, ...) \ + do { \ + if (check_dump_flag(ctx->rt, DUMP_PROMISE)) \ + printf(__VA_ARGS__); \ + } while (0) +#else +#define promise_trace(...) +#endif + +static JSValue promise_reaction_job(JSContext *ctx, int argc, + JSValue *argv) +{ + JSValue handler, arg, func; + JSValue res, res2; + BOOL is_reject; + + assert(argc == 5); + handler = argv[2]; + is_reject = JS_ToBool(ctx, argv[3]); + arg = argv[4]; + + promise_trace(ctx, "promise_reaction_job: is_reject=%d\n", is_reject); + + if (JS_IsUndefined(handler)) { + if (is_reject) { + res = JS_Throw(ctx, js_dup(arg)); + } else { + res = js_dup(arg); + } + } else { + res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg); + } + is_reject = JS_IsException(res); + if (is_reject) + res = JS_GetException(ctx); + func = argv[is_reject]; + /* as an extension, we support undefined as value to avoid + creating a dummy promise in the 'await' implementation of async + functions */ + if (!JS_IsUndefined(func)) { + res2 = JS_Call(ctx, func, JS_UNDEFINED, + 1, &res); + } else { + res2 = JS_UNDEFINED; + } + JS_FreeValue(ctx, res); + + return res2; +} + +void JS_SetHostPromiseRejectionTracker(JSRuntime *rt, + JSHostPromiseRejectionTracker *cb, + void *opaque) +{ + rt->host_promise_rejection_tracker = cb; + rt->host_promise_rejection_tracker_opaque = opaque; +} + +static void fulfill_or_reject_promise(JSContext *ctx, JSValue promise, + JSValue value, BOOL is_reject) +{ + JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); + struct list_head *el, *el1; + JSPromiseReactionData *rd; + JSValue args[5]; + + if (!s || s->promise_state != JS_PROMISE_PENDING) + return; /* should never happen */ + set_value(ctx, &s->promise_result, js_dup(value)); + s->promise_state = JS_PROMISE_FULFILLED + is_reject; + + promise_trace(ctx, "fulfill_or_reject_promise: is_reject=%d\n", is_reject); + + if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) { + JSRuntime *rt = ctx->rt; + if (rt->host_promise_rejection_tracker) { + rt->host_promise_rejection_tracker(ctx, promise, value, FALSE, + rt->host_promise_rejection_tracker_opaque); + } + } + + list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) { + rd = list_entry(el, JSPromiseReactionData, link); + args[0] = rd->resolving_funcs[0]; + args[1] = rd->resolving_funcs[1]; + args[2] = rd->handler; + args[3] = js_bool(is_reject); + args[4] = value; + JS_EnqueueJob(ctx, promise_reaction_job, 5, args); + list_del(&rd->link); + promise_reaction_data_free(ctx->rt, rd); + } + + list_for_each_safe(el, el1, &s->promise_reactions[1 - is_reject]) { + rd = list_entry(el, JSPromiseReactionData, link); + list_del(&rd->link); + promise_reaction_data_free(ctx->rt, rd); + } +} + +static void reject_promise(JSContext *ctx, JSValue promise, + JSValue value) +{ + fulfill_or_reject_promise(ctx, promise, value, TRUE); +} + +static JSValue js_promise_resolve_thenable_job(JSContext *ctx, + int argc, JSValue *argv) +{ + JSValue promise, thenable, then; + JSValue args[2], res; + + promise_trace(ctx, "js_promise_resolve_thenable_job\n"); + + assert(argc == 3); + promise = argv[0]; + thenable = argv[1]; + then = argv[2]; + if (js_create_resolving_functions(ctx, args, promise) < 0) + return JS_EXCEPTION; + res = JS_Call(ctx, then, thenable, 2, args); + if (JS_IsException(res)) { + JSValue error = JS_GetException(ctx); + res = JS_Call(ctx, args[1], JS_UNDEFINED, 1, &error); + JS_FreeValue(ctx, error); + } + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + return res; +} + +static void js_promise_resolve_function_free_resolved(JSRuntime *rt, + JSPromiseFunctionDataResolved *sr) +{ + if (--sr->ref_count == 0) { + js_free_rt(rt, sr); + } +} + +static int js_create_resolving_functions(JSContext *ctx, + JSValue *resolving_funcs, + JSValue promise) + +{ + JSValue obj; + JSPromiseFunctionData *s; + JSPromiseFunctionDataResolved *sr; + int i, ret; + + sr = js_malloc(ctx, sizeof(*sr)); + if (!sr) + return -1; + sr->ref_count = 1; + sr->already_resolved = FALSE; /* must be shared between the two functions */ + ret = 0; + for(i = 0; i < 2; i++) { + obj = JS_NewObjectProtoClass(ctx, ctx->function_proto, + JS_CLASS_PROMISE_RESOLVE_FUNCTION + i); + if (JS_IsException(obj)) + goto fail; + s = js_malloc(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, obj); + fail: + + if (i != 0) + JS_FreeValue(ctx, resolving_funcs[0]); + ret = -1; + break; + } + sr->ref_count++; + s->presolved = sr; + s->promise = js_dup(promise); + JS_SetOpaqueInternal(obj, s); + js_function_set_properties(ctx, obj, JS_ATOM_empty_string, 1); + resolving_funcs[i] = obj; + } + js_promise_resolve_function_free_resolved(ctx->rt, sr); + return ret; +} + +static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val) +{ + JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data; + if (s) { + js_promise_resolve_function_free_resolved(rt, s->presolved); + JS_FreeValueRT(rt, s->promise); + js_free_rt(rt, s); + } +} + +static void js_promise_resolve_function_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data; + if (s) { + JS_MarkValue(rt, s->promise, mark_func); + } +} + +static JSValue js_promise_resolve_function_call(JSContext *ctx, + JSValue func_obj, + JSValue this_val, + int argc, JSValue *argv, + int flags) +{ + JSObject *p = JS_VALUE_GET_OBJ(func_obj); + JSPromiseFunctionData *s; + JSValue resolution, args[3]; + JSValue then; + BOOL is_reject; + + s = p->u.promise_function_data; + if (!s || s->presolved->already_resolved) + return JS_UNDEFINED; + s->presolved->already_resolved = TRUE; + is_reject = p->class_id - JS_CLASS_PROMISE_RESOLVE_FUNCTION; + if (argc > 0) + resolution = argv[0]; + else + resolution = JS_UNDEFINED; +#ifdef DUMP_PROMISE + if (check_dump_flag(ctx->rt, DUMP_PROMISE)) { + printf("js_promise_resolving_function_call: is_reject=%d resolution=", is_reject); + JS_DumpValue(ctx->rt, resolution); + printf("\n"); + } +#endif + if (is_reject || !JS_IsObject(resolution)) { + goto done; + } else if (js_same_value(ctx, resolution, s->promise)) { + JS_ThrowTypeError(ctx, "promise self resolution"); + goto fail_reject; + } + then = JS_GetProperty(ctx, resolution, JS_ATOM_then); + if (JS_IsException(then)) { + JSValue error; + fail_reject: + error = JS_GetException(ctx); + reject_promise(ctx, s->promise, error); + JS_FreeValue(ctx, error); + } else if (!JS_IsFunction(ctx, then)) { + JS_FreeValue(ctx, then); + done: + fulfill_or_reject_promise(ctx, s->promise, resolution, is_reject); + } else { + args[0] = s->promise; + args[1] = resolution; + args[2] = then; + JS_EnqueueJob(ctx, js_promise_resolve_thenable_job, 3, args); + JS_FreeValue(ctx, then); + } + return JS_UNDEFINED; +} + +static void js_promise_finalizer(JSRuntime *rt, JSValue val) +{ + JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE); + struct list_head *el, *el1; + int i; + + if (!s) + return; + for(i = 0; i < 2; i++) { + list_for_each_safe(el, el1, &s->promise_reactions[i]) { + JSPromiseReactionData *rd = + list_entry(el, JSPromiseReactionData, link); + promise_reaction_data_free(rt, rd); + } + } + JS_FreeValueRT(rt, s->promise_result); + js_free_rt(rt, s); +} + +static void js_promise_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE); + struct list_head *el; + int i; + + if (!s) + return; + for(i = 0; i < 2; i++) { + list_for_each(el, &s->promise_reactions[i]) { + JSPromiseReactionData *rd = + list_entry(el, JSPromiseReactionData, link); + JS_MarkValue(rt, rd->resolving_funcs[0], mark_func); + JS_MarkValue(rt, rd->resolving_funcs[1], mark_func); + JS_MarkValue(rt, rd->handler, mark_func); + } + } + JS_MarkValue(rt, s->promise_result, mark_func); +} + +static JSValue js_promise_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + JSValue executor; + JSValue obj; + JSPromiseData *s; + JSValue args[2], ret; + int i; + + executor = argv[0]; + if (check_function(ctx, executor)) + return JS_EXCEPTION; + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_PROMISE); + if (JS_IsException(obj)) + return JS_EXCEPTION; + s = js_mallocz(ctx, sizeof(*s)); + if (!s) + goto fail; + s->promise_state = JS_PROMISE_PENDING; + s->is_handled = FALSE; + for(i = 0; i < 2; i++) + init_list_head(&s->promise_reactions[i]); + s->promise_result = JS_UNDEFINED; + JS_SetOpaqueInternal(obj, s); + if (js_create_resolving_functions(ctx, args, obj)) + goto fail; + ret = JS_Call(ctx, executor, JS_UNDEFINED, 2, args); + if (JS_IsException(ret)) { + JSValue ret2, error; + error = JS_GetException(ctx); + ret2 = JS_Call(ctx, args[1], JS_UNDEFINED, 1, &error); + JS_FreeValue(ctx, error); + if (JS_IsException(ret2)) + goto fail1; + JS_FreeValue(ctx, ret2); + } + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + return obj; + fail1: + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_promise_executor(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, + int magic, JSValue *func_data) +{ + int i; + + for(i = 0; i < 2; i++) { + if (!JS_IsUndefined(func_data[i])) + return JS_ThrowTypeError(ctx, "resolving function already set"); + func_data[i] = js_dup(argv[i]); + } + return JS_UNDEFINED; +} + +static JSValue js_promise_executor_new(JSContext *ctx) +{ + JSValue func_data[2]; + + func_data[0] = JS_UNDEFINED; + func_data[1] = JS_UNDEFINED; + return JS_NewCFunctionData(ctx, js_promise_executor, 2, + 0, 2, func_data); +} + +static JSValue js_new_promise_capability(JSContext *ctx, + JSValue *resolving_funcs, + JSValue ctor) +{ + JSValue executor, result_promise; + JSCFunctionDataRecord *s; + int i; + + executor = js_promise_executor_new(ctx); + if (JS_IsException(executor)) + return executor; + + if (JS_IsUndefined(ctor)) { + result_promise = js_promise_constructor(ctx, ctor, 1, + &executor); + } else { + result_promise = JS_CallConstructor(ctx, ctor, 1, + &executor); + } + if (JS_IsException(result_promise)) + goto fail; + s = JS_GetOpaque(executor, JS_CLASS_C_FUNCTION_DATA); + for(i = 0; i < 2; i++) { + if (check_function(ctx, s->data[i])) + goto fail; + } + for(i = 0; i < 2; i++) + resolving_funcs[i] = js_dup(s->data[i]); + JS_FreeValue(ctx, executor); + return result_promise; + fail: + JS_FreeValue(ctx, executor); + JS_FreeValue(ctx, result_promise); + return JS_EXCEPTION; +} + +JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs) +{ + return js_new_promise_capability(ctx, resolving_funcs, JS_UNDEFINED); +} + +static JSValue js_promise_resolve(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue result_promise, resolving_funcs[2], ret; + BOOL is_reject = magic; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + if (!is_reject && JS_GetOpaque(argv[0], JS_CLASS_PROMISE)) { + JSValue ctor; + BOOL is_same; + ctor = JS_GetProperty(ctx, argv[0], JS_ATOM_constructor); + if (JS_IsException(ctor)) + return ctor; + is_same = js_same_value(ctx, ctor, this_val); + JS_FreeValue(ctx, ctor); + if (is_same) + return js_dup(argv[0]); + } + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); + if (JS_IsException(result_promise)) + return result_promise; + ret = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, argv); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, result_promise); + return ret; + } + JS_FreeValue(ctx, ret); + return result_promise; +} + +static JSValue js_promise_withResolvers(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue result_promise, resolving_funcs[2], obj; + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); + if (JS_IsException(result_promise)) + return JS_EXCEPTION; + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, result_promise); + return JS_EXCEPTION; + } + JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, JS_PROP_C_W_E); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs[0], JS_PROP_C_W_E); + JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs[1], JS_PROP_C_W_E); + return obj; +} + +static JSValue js_promise_try(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue result_promise, resolving_funcs[2], ret, ret2; + BOOL is_reject = 0; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); + if (JS_IsException(result_promise)) + return result_promise; + ret = JS_Call(ctx, argv[0], JS_UNDEFINED, argc - 1, argv + 1); + if (JS_IsException(ret)) { + is_reject = 1; + ret = JS_GetException(ctx); + } + ret2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, &ret); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, ret); + if (JS_IsException(ret2)) { + JS_FreeValue(ctx, result_promise); + return ret2; + } + JS_FreeValue(ctx, ret2); + return result_promise; +} + +static __exception int remainingElementsCount_add(JSContext *ctx, + JSValue resolve_element_env, + int addend) +{ + JSValue val; + int remainingElementsCount; + + val = JS_GetPropertyUint32(ctx, resolve_element_env, 0); + if (JS_IsException(val)) + return -1; + if (JS_ToInt32Free(ctx, &remainingElementsCount, val)) + return -1; + remainingElementsCount += addend; + if (JS_SetPropertyUint32(ctx, resolve_element_env, 0, + js_int32(remainingElementsCount)) < 0) + return -1; + return (remainingElementsCount == 0); +} + +#define PROMISE_MAGIC_all 0 +#define PROMISE_MAGIC_allSettled 1 +#define PROMISE_MAGIC_any 2 + +static JSValue js_promise_all_resolve_element(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, + int magic, + JSValue *func_data) +{ + int resolve_type = magic & 3; + int is_reject = magic & 4; + BOOL alreadyCalled = JS_ToBool(ctx, func_data[0]); + JSValue values = func_data[2]; + JSValue resolve = func_data[3]; + JSValue resolve_element_env = func_data[4]; + JSValue ret, obj; + int is_zero, index; + + if (JS_ToInt32(ctx, &index, func_data[1])) + return JS_EXCEPTION; + if (alreadyCalled) + return JS_UNDEFINED; + func_data[0] = js_bool(TRUE); + + if (resolve_type == PROMISE_MAGIC_allSettled) { + JSValue str; + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) + return JS_EXCEPTION; + str = js_new_string8(ctx, is_reject ? "rejected" : "fulfilled"); + if (JS_IsException(str)) + goto fail1; + if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_status, + str, + JS_PROP_C_W_E) < 0) + goto fail1; + if (JS_DefinePropertyValue(ctx, obj, + is_reject ? JS_ATOM_reason : JS_ATOM_value, + js_dup(argv[0]), + JS_PROP_C_W_E) < 0) { + fail1: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + } else { + obj = js_dup(argv[0]); + } + if (JS_DefinePropertyValueUint32(ctx, values, index, + obj, JS_PROP_C_W_E) < 0) + return JS_EXCEPTION; + + is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1); + if (is_zero < 0) + return JS_EXCEPTION; + if (is_zero) { + if (resolve_type == PROMISE_MAGIC_any) { + JSValue error; + error = js_aggregate_error_constructor(ctx, values); + if (JS_IsException(error)) + return JS_EXCEPTION; + ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, &error); + JS_FreeValue(ctx, error); + } else { + ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, &values); + } + if (JS_IsException(ret)) + return ret; + JS_FreeValue(ctx, ret); + } + return JS_UNDEFINED; +} + +/* magic = 0: Promise.all 1: Promise.allSettled */ +static JSValue js_promise_all(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + JSValue result_promise, resolving_funcs[2], item, next_promise, ret; + JSValue next_method = JS_UNDEFINED, values = JS_UNDEFINED; + JSValue resolve_element_env = JS_UNDEFINED, resolve_element, reject_element; + JSValue promise_resolve = JS_UNDEFINED, iter = JS_UNDEFINED; + JSValue then_args[2], resolve_element_data[5]; + BOOL done; + int index, is_zero, is_promise_any = (magic == PROMISE_MAGIC_any); + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); + if (JS_IsException(result_promise)) + return result_promise; + promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); + if (JS_IsException(promise_resolve) || + check_function(ctx, promise_resolve)) + goto fail_reject; + iter = JS_GetIterator(ctx, argv[0], FALSE); + if (JS_IsException(iter)) { + JSValue error; + fail_reject: + error = JS_GetException(ctx); + ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, + &error); + JS_FreeValue(ctx, error); + if (JS_IsException(ret)) + goto fail; + JS_FreeValue(ctx, ret); + } else { + next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next_method)) + goto fail_reject; + values = JS_NewArray(ctx); + if (JS_IsException(values)) + goto fail_reject; + resolve_element_env = JS_NewArray(ctx); + if (JS_IsException(resolve_element_env)) + goto fail_reject; + /* remainingElementsCount field */ + if (JS_DefinePropertyValueUint32(ctx, resolve_element_env, 0, + js_int32(1), + JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0) + goto fail_reject; + + index = 0; + for(;;) { + /* XXX: conformance: should close the iterator if error on 'done' + access, but not on 'value' access */ + item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail_reject; + if (done) + break; + next_promise = JS_Call(ctx, promise_resolve, + this_val, 1, &item); + JS_FreeValue(ctx, item); + if (JS_IsException(next_promise)) { + fail_reject1: + JS_IteratorClose(ctx, iter, TRUE); + goto fail_reject; + } + resolve_element_data[0] = js_bool(FALSE); + resolve_element_data[1] = js_int32(index); + resolve_element_data[2] = values; + resolve_element_data[3] = resolving_funcs[is_promise_any]; + resolve_element_data[4] = resolve_element_env; + resolve_element = + JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1, + magic, 5, resolve_element_data); + if (JS_IsException(resolve_element)) { + JS_FreeValue(ctx, next_promise); + goto fail_reject1; + } + + if (magic == PROMISE_MAGIC_allSettled) { + reject_element = + JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1, + magic | 4, 5, resolve_element_data); + if (JS_IsException(reject_element)) { + JS_FreeValue(ctx, next_promise); + goto fail_reject1; + } + } else if (magic == PROMISE_MAGIC_any) { + if (JS_DefinePropertyValueUint32(ctx, values, index, + JS_UNDEFINED, JS_PROP_C_W_E) < 0) + goto fail_reject1; + reject_element = resolve_element; + resolve_element = js_dup(resolving_funcs[0]); + } else { + reject_element = js_dup(resolving_funcs[1]); + } + + if (remainingElementsCount_add(ctx, resolve_element_env, 1) < 0) { + JS_FreeValue(ctx, next_promise); + JS_FreeValue(ctx, resolve_element); + JS_FreeValue(ctx, reject_element); + goto fail_reject1; + } + + then_args[0] = resolve_element; + then_args[1] = reject_element; + ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, then_args); + JS_FreeValue(ctx, resolve_element); + JS_FreeValue(ctx, reject_element); + if (check_exception_free(ctx, ret)) + goto fail_reject1; + index++; + } + + is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1); + if (is_zero < 0) + goto fail_reject; + if (is_zero) { + if (magic == PROMISE_MAGIC_any) { + JSValue error; + error = js_aggregate_error_constructor(ctx, values); + if (JS_IsException(error)) + goto fail_reject; + JS_FreeValue(ctx, values); + values = error; + } + ret = JS_Call(ctx, resolving_funcs[is_promise_any], JS_UNDEFINED, + 1, &values); + if (check_exception_free(ctx, ret)) + goto fail_reject; + } + } + done: + JS_FreeValue(ctx, promise_resolve); + JS_FreeValue(ctx, resolve_element_env); + JS_FreeValue(ctx, values); + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return result_promise; + fail: + JS_FreeValue(ctx, result_promise); + result_promise = JS_EXCEPTION; + goto done; +} + +static JSValue js_promise_race(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue result_promise, resolving_funcs[2], item, next_promise, ret; + JSValue next_method = JS_UNDEFINED, iter = JS_UNDEFINED; + JSValue promise_resolve = JS_UNDEFINED; + BOOL done; + + if (!JS_IsObject(this_val)) + return JS_ThrowTypeErrorNotAnObject(ctx); + result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val); + if (JS_IsException(result_promise)) + return result_promise; + promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve); + if (JS_IsException(promise_resolve) || + check_function(ctx, promise_resolve)) + goto fail_reject; + iter = JS_GetIterator(ctx, argv[0], FALSE); + if (JS_IsException(iter)) { + JSValue error; + fail_reject: + error = JS_GetException(ctx); + ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, + &error); + JS_FreeValue(ctx, error); + if (JS_IsException(ret)) + goto fail; + JS_FreeValue(ctx, ret); + } else { + next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next_method)) + goto fail_reject; + + for(;;) { + /* XXX: conformance: should close the iterator if error on 'done' + access, but not on 'value' access */ + item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); + if (JS_IsException(item)) + goto fail_reject; + if (done) + break; + next_promise = JS_Call(ctx, promise_resolve, + this_val, 1, &item); + JS_FreeValue(ctx, item); + if (JS_IsException(next_promise)) { + fail_reject1: + JS_IteratorClose(ctx, iter, TRUE); + goto fail_reject; + } + ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, + resolving_funcs); + if (check_exception_free(ctx, ret)) + goto fail_reject1; + } + } + done: + JS_FreeValue(ctx, promise_resolve); + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return result_promise; + fail: + //JS_FreeValue(ctx, next_method); // why not??? + JS_FreeValue(ctx, result_promise); + result_promise = JS_EXCEPTION; + goto done; +} + +static __exception int perform_promise_then(JSContext *ctx, + JSValue promise, + JSValue *resolve_reject, + JSValue *cap_resolving_funcs) +{ + JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE); + JSPromiseReactionData *rd_array[2], *rd; + int i, j; + + rd_array[0] = NULL; + rd_array[1] = NULL; + for(i = 0; i < 2; i++) { + JSValue handler; + rd = js_mallocz(ctx, sizeof(*rd)); + if (!rd) { + if (i == 1) + promise_reaction_data_free(ctx->rt, rd_array[0]); + return -1; + } + for(j = 0; j < 2; j++) + rd->resolving_funcs[j] = js_dup(cap_resolving_funcs[j]); + handler = resolve_reject[i]; + if (!JS_IsFunction(ctx, handler)) + handler = JS_UNDEFINED; + rd->handler = js_dup(handler); + rd_array[i] = rd; + } + + if (s->promise_state == JS_PROMISE_PENDING) { + for(i = 0; i < 2; i++) + list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]); + } else { + JSValue args[5]; + if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) { + JSRuntime *rt = ctx->rt; + if (rt->host_promise_rejection_tracker) { + rt->host_promise_rejection_tracker(ctx, promise, s->promise_result, + TRUE, rt->host_promise_rejection_tracker_opaque); + } + } + i = s->promise_state - JS_PROMISE_FULFILLED; + rd = rd_array[i]; + args[0] = rd->resolving_funcs[0]; + args[1] = rd->resolving_funcs[1]; + args[2] = rd->handler; + args[3] = js_bool(i); + args[4] = s->promise_result; + JS_EnqueueJob(ctx, promise_reaction_job, 5, args); + for(i = 0; i < 2; i++) + promise_reaction_data_free(ctx->rt, rd_array[i]); + } + s->is_handled = TRUE; + return 0; +} + +static JSValue js_promise_then(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue ctor, result_promise, resolving_funcs[2]; + JSPromiseData *s; + int i, ret; + + s = JS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE); + if (!s) + return JS_EXCEPTION; + + ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED); + if (JS_IsException(ctor)) + return ctor; + result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor); + JS_FreeValue(ctx, ctor); + if (JS_IsException(result_promise)) + return result_promise; + ret = perform_promise_then(ctx, this_val, argv, + resolving_funcs); + for(i = 0; i < 2; i++) + JS_FreeValue(ctx, resolving_funcs[i]); + if (ret) { + JS_FreeValue(ctx, result_promise); + return JS_EXCEPTION; + } + return result_promise; +} + +static JSValue js_promise_catch(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue args[2]; + args[0] = JS_UNDEFINED; + args[1] = argv[0]; + return JS_Invoke(ctx, this_val, JS_ATOM_then, 2, args); +} + +static JSValue js_promise_finally_value_thunk(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int magic, JSValue *func_data) +{ + return js_dup(func_data[0]); +} + +static JSValue js_promise_finally_thrower(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int magic, JSValue *func_data) +{ + return JS_Throw(ctx, js_dup(func_data[0])); +} + +static JSValue js_promise_then_finally_func(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int magic, JSValue *func_data) +{ + JSValue ctor = func_data[0]; + JSValue onFinally = func_data[1]; + JSValue res, promise, ret, then_func; + + res = JS_Call(ctx, onFinally, JS_UNDEFINED, 0, NULL); + if (JS_IsException(res)) + return res; + promise = js_promise_resolve(ctx, ctor, 1, &res, 0); + JS_FreeValue(ctx, res); + if (JS_IsException(promise)) + return promise; + if (magic == 0) { + then_func = JS_NewCFunctionData(ctx, js_promise_finally_value_thunk, 0, + 0, 1, argv); + } else { + then_func = JS_NewCFunctionData(ctx, js_promise_finally_thrower, 0, + 0, 1, argv); + } + if (JS_IsException(then_func)) { + JS_FreeValue(ctx, promise); + return then_func; + } + ret = JS_InvokeFree(ctx, promise, JS_ATOM_then, 1, &then_func); + JS_FreeValue(ctx, then_func); + return ret; +} + +static JSValue js_promise_finally(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue onFinally = argv[0]; + JSValue ctor, ret; + JSValue then_funcs[2]; + JSValue func_data[2]; + int i; + + ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED); + if (JS_IsException(ctor)) + return ctor; + if (!JS_IsFunction(ctx, onFinally)) { + then_funcs[0] = js_dup(onFinally); + then_funcs[1] = js_dup(onFinally); + } else { + func_data[0] = ctor; + func_data[1] = onFinally; + for(i = 0; i < 2; i++) { + then_funcs[i] = JS_NewCFunctionData(ctx, js_promise_then_finally_func, 1, i, 2, func_data); + if (JS_IsException(then_funcs[i])) { + if (i == 1) + JS_FreeValue(ctx, then_funcs[0]); + JS_FreeValue(ctx, ctor); + return JS_EXCEPTION; + } + } + } + JS_FreeValue(ctx, ctor); + ret = JS_Invoke(ctx, this_val, JS_ATOM_then, 2, then_funcs); + JS_FreeValue(ctx, then_funcs[0]); + JS_FreeValue(ctx, then_funcs[1]); + return ret; +} + +static const JSCFunctionListEntry js_promise_funcs[] = { + JS_CFUNC_MAGIC_DEF("resolve", 1, js_promise_resolve, 0 ), + JS_CFUNC_MAGIC_DEF("reject", 1, js_promise_resolve, 1 ), + JS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, PROMISE_MAGIC_all ), + JS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all, PROMISE_MAGIC_allSettled ), + JS_CFUNC_MAGIC_DEF("any", 1, js_promise_all, PROMISE_MAGIC_any ), + JS_CFUNC_DEF("try", 1, js_promise_try ), + JS_CFUNC_DEF("race", 1, js_promise_race ), + JS_CFUNC_DEF("withResolvers", 0, js_promise_withResolvers ), + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL), +}; + +static const JSCFunctionListEntry js_promise_proto_funcs[] = { + JS_CFUNC_DEF("then", 2, js_promise_then ), + JS_CFUNC_DEF("catch", 1, js_promise_catch ), + JS_CFUNC_DEF("finally", 1, js_promise_finally ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Promise", JS_PROP_CONFIGURABLE ), +}; + +/* AsyncFunction */ +static const JSCFunctionListEntry js_async_function_proto_funcs[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction", JS_PROP_CONFIGURABLE ), +}; + +static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, + int magic, JSValue *func_data) +{ + return js_create_iterator_result(ctx, js_dup(argv[0]), + JS_ToBool(ctx, func_data[0])); +} + +static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx, + BOOL done) +{ + JSValue func_data[1]; + + func_data[0] = js_bool(done); + return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap, + 1, 0, 1, func_data); +} + +/* AsyncIteratorPrototype */ + +static const JSCFunctionListEntry js_async_iterator_proto_funcs[] = { + JS_CFUNC_DEF("[Symbol.asyncIterator]", 0, js_iterator_proto_iterator ), +}; + +/* AsyncFromSyncIteratorPrototype */ + +typedef struct JSAsyncFromSyncIteratorData { + JSValue sync_iter; + JSValue next_method; +} JSAsyncFromSyncIteratorData; + +static void js_async_from_sync_iterator_finalizer(JSRuntime *rt, JSValue val) +{ + JSAsyncFromSyncIteratorData *s = + JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); + if (s) { + JS_FreeValueRT(rt, s->sync_iter); + JS_FreeValueRT(rt, s->next_method); + js_free_rt(rt, s); + } +} + +static void js_async_from_sync_iterator_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSAsyncFromSyncIteratorData *s = + JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); + if (s) { + JS_MarkValue(rt, s->sync_iter, mark_func); + JS_MarkValue(rt, s->next_method, mark_func); + } +} + +static JSValue JS_CreateAsyncFromSyncIterator(JSContext *ctx, + JSValue sync_iter) +{ + JSValue async_iter, next_method; + JSAsyncFromSyncIteratorData *s; + + next_method = JS_GetProperty(ctx, sync_iter, JS_ATOM_next); + if (JS_IsException(next_method)) + return JS_EXCEPTION; + async_iter = JS_NewObjectClass(ctx, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); + if (JS_IsException(async_iter)) { + JS_FreeValue(ctx, next_method); + return async_iter; + } + s = js_mallocz(ctx, sizeof(*s)); + if (!s) { + JS_FreeValue(ctx, async_iter); + JS_FreeValue(ctx, next_method); + return JS_EXCEPTION; + } + s->sync_iter = js_dup(sync_iter); + s->next_method = next_method; + JS_SetOpaqueInternal(async_iter, s); + return async_iter; +} + +static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int magic) +{ + JSValue promise, resolving_funcs[2], value, err, method; + JSAsyncFromSyncIteratorData *s; + int done; + int is_reject; + + promise = JS_NewPromiseCapability(ctx, resolving_funcs); + if (JS_IsException(promise)) + return JS_EXCEPTION; + s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR); + if (!s) { + JS_ThrowTypeError(ctx, "not an Async-from-Sync Iterator"); + goto reject; + } + + if (magic == GEN_MAGIC_NEXT) { + method = js_dup(s->next_method); + } else { + method = JS_GetProperty(ctx, s->sync_iter, + magic == GEN_MAGIC_RETURN ? JS_ATOM_return : + JS_ATOM_throw); + if (JS_IsException(method)) + goto reject; + if (JS_IsUndefined(method) || JS_IsNull(method)) { + if (magic == GEN_MAGIC_RETURN) { + err = js_create_iterator_result(ctx, js_dup(argv[0]), TRUE); + is_reject = 0; + } else { + err = JS_MakeError(ctx, JS_TYPE_ERROR, "throw is not a method", + TRUE); + is_reject = 1; + } + goto done_resolve; + } + } + value = JS_IteratorNext2(ctx, s->sync_iter, method, + argc >= 1 ? 1 : 0, argv, &done); + JS_FreeValue(ctx, method); + if (JS_IsException(value)) + goto reject; + if (done == 2) { + JSValue obj = value; + value = JS_IteratorGetCompleteValue(ctx, obj, &done); + JS_FreeValue(ctx, obj); + if (JS_IsException(value)) + goto reject; + } + + if (JS_IsException(value)) { + JSValue res2; + reject: + err = JS_GetException(ctx); + is_reject = 1; + done_resolve: + res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, + 1, &err); + JS_FreeValue(ctx, err); + JS_FreeValue(ctx, res2); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + return promise; + } + { + JSValue value_wrapper_promise, resolve_reject[2]; + int res; + + value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor, + 1, &value, 0); + if (JS_IsException(value_wrapper_promise)) { + JS_FreeValue(ctx, value); + goto reject; + } + + resolve_reject[0] = + js_async_from_sync_iterator_unwrap_func_create(ctx, done); + if (JS_IsException(resolve_reject[0])) { + JS_FreeValue(ctx, value_wrapper_promise); + goto fail; + } + JS_FreeValue(ctx, value); + resolve_reject[1] = JS_UNDEFINED; + + res = perform_promise_then(ctx, value_wrapper_promise, + resolve_reject, + resolving_funcs); + JS_FreeValue(ctx, resolve_reject[0]); + JS_FreeValue(ctx, value_wrapper_promise); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + if (res) { + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; + } + } + return promise; + fail: + JS_FreeValue(ctx, value); + JS_FreeValue(ctx, resolving_funcs[0]); + JS_FreeValue(ctx, resolving_funcs[1]); + JS_FreeValue(ctx, promise); + return JS_EXCEPTION; +} + +static const JSCFunctionListEntry js_async_from_sync_iterator_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("next", 1, js_async_from_sync_iterator_next, GEN_MAGIC_NEXT ), + JS_CFUNC_MAGIC_DEF("return", 1, js_async_from_sync_iterator_next, GEN_MAGIC_RETURN ), + JS_CFUNC_MAGIC_DEF("throw", 1, js_async_from_sync_iterator_next, GEN_MAGIC_THROW ), +}; + +/* AsyncGeneratorFunction */ + +static const JSCFunctionListEntry js_async_generator_function_proto_funcs[] = { + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGeneratorFunction", JS_PROP_CONFIGURABLE ), +}; + +/* AsyncGenerator prototype */ + +static const JSCFunctionListEntry js_async_generator_proto_funcs[] = { + JS_CFUNC_MAGIC_DEF("next", 1, js_async_generator_next, GEN_MAGIC_NEXT ), + JS_CFUNC_MAGIC_DEF("return", 1, js_async_generator_next, GEN_MAGIC_RETURN ), + JS_CFUNC_MAGIC_DEF("throw", 1, js_async_generator_next, GEN_MAGIC_THROW ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGenerator", JS_PROP_CONFIGURABLE ), +}; + +static JSClassShortDef const js_async_class_def[] = { + { JS_ATOM_Promise, js_promise_finalizer, js_promise_mark }, /* JS_CLASS_PROMISE */ + { JS_ATOM_PromiseResolveFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_RESOLVE_FUNCTION */ + { JS_ATOM_PromiseRejectFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_REJECT_FUNCTION */ + { JS_ATOM_AsyncFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_FUNCTION */ + { JS_ATOM_AsyncFunctionResolve, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_RESOLVE */ + { JS_ATOM_AsyncFunctionReject, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_REJECT */ + { JS_ATOM_empty_string, js_async_from_sync_iterator_finalizer, js_async_from_sync_iterator_mark }, /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ + { JS_ATOM_AsyncGeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + { JS_ATOM_AsyncGenerator, js_async_generator_finalizer, js_async_generator_mark }, /* JS_CLASS_ASYNC_GENERATOR */ +}; + +void JS_AddIntrinsicPromise(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + JSValue obj1; + + if (!JS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) { + init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE, + countof(js_async_class_def)); + rt->class_array[JS_CLASS_PROMISE_RESOLVE_FUNCTION].call = js_promise_resolve_function_call; + rt->class_array[JS_CLASS_PROMISE_REJECT_FUNCTION].call = js_promise_resolve_function_call; + rt->class_array[JS_CLASS_ASYNC_FUNCTION].call = js_async_function_call; + rt->class_array[JS_CLASS_ASYNC_FUNCTION_RESOLVE].call = js_async_function_resolve_call; + rt->class_array[JS_CLASS_ASYNC_FUNCTION_REJECT].call = js_async_function_resolve_call; + rt->class_array[JS_CLASS_ASYNC_GENERATOR_FUNCTION].call = js_async_generator_function_call; + } + + /* Promise */ + ctx->class_proto[JS_CLASS_PROMISE] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_PROMISE], + js_promise_proto_funcs, + countof(js_promise_proto_funcs)); + obj1 = JS_NewCFunction2(ctx, js_promise_constructor, "Promise", 1, + JS_CFUNC_constructor, 0); + ctx->promise_ctor = js_dup(obj1); + JS_SetPropertyFunctionList(ctx, obj1, + js_promise_funcs, + countof(js_promise_funcs)); + JS_NewGlobalCConstructor2(ctx, obj1, "Promise", + ctx->class_proto[JS_CLASS_PROMISE]); + + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft; + + /* AsyncFunction */ + ctx->class_proto[JS_CLASS_ASYNC_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto); + ft.generic_magic = js_function_constructor; + obj1 = JS_NewCFunction3(ctx, ft.generic, + "AsyncFunction", 1, + JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC, + ctx->function_ctor); + JS_SetPropertyFunctionList(ctx, + ctx->class_proto[JS_CLASS_ASYNC_FUNCTION], + js_async_function_proto_funcs, + countof(js_async_function_proto_funcs)); + JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_FUNCTION], + 0, JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, obj1); + + /* AsyncIteratorPrototype */ + ctx->async_iterator_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->async_iterator_proto, + js_async_iterator_proto_funcs, + countof(js_async_iterator_proto_funcs)); + + /* AsyncFromSyncIteratorPrototype */ + ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR] = + JS_NewObjectProto(ctx, ctx->async_iterator_proto); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR], + js_async_from_sync_iterator_proto_funcs, + countof(js_async_from_sync_iterator_proto_funcs)); + + /* AsyncGeneratorPrototype */ + ctx->class_proto[JS_CLASS_ASYNC_GENERATOR] = + JS_NewObjectProto(ctx, ctx->async_iterator_proto); + JS_SetPropertyFunctionList(ctx, + ctx->class_proto[JS_CLASS_ASYNC_GENERATOR], + js_async_generator_proto_funcs, + countof(js_async_generator_proto_funcs)); + + /* AsyncGeneratorFunction */ + ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION] = + JS_NewObjectProto(ctx, ctx->function_proto); + ft.generic_magic = js_function_constructor; + obj1 = JS_NewCFunction3(ctx, ft.generic, + "AsyncGeneratorFunction", 1, + JS_CFUNC_constructor_or_func_magic, + JS_FUNC_ASYNC_GENERATOR, + ctx->function_ctor); + JS_SetPropertyFunctionList(ctx, + ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION], + js_async_generator_function_proto_funcs, + countof(js_async_generator_function_proto_funcs)); + JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION], + ctx->class_proto[JS_CLASS_ASYNC_GENERATOR], + JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); + JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION], + 0, JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, obj1); +} + +/* URI handling */ + +static int string_get_hex(JSString *p, int k, int n) { + int c = 0, h; + while (n-- > 0) { + if ((h = from_hex(string_get(p, k++))) < 0) + return -1; + c = (c << 4) | h; + } + return c; +} + +static int isURIReserved(int c) { + return c < 0x100 && memchr(";/?:@&=+$,#", c, sizeof(";/?:@&=+$,#") - 1) != NULL; +} + +static int __attribute__((format(printf, 2, 3))) js_throw_URIError(JSContext *ctx, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + JS_ThrowError(ctx, JS_URI_ERROR, fmt, ap); + va_end(ap); + return -1; +} + +static int hex_decode(JSContext *ctx, JSString *p, int k) { + int c; + + if (k >= p->len || string_get(p, k) != '%') + return js_throw_URIError(ctx, "expecting %%"); + if (k + 2 >= p->len || (c = string_get_hex(p, k + 1, 2)) < 0) + return js_throw_URIError(ctx, "expecting hex digit"); + + return c; +} + +static JSValue js_global_decodeURI(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int isComponent) +{ + JSValue str; + StringBuffer b_s, *b = &b_s; + JSString *p; + int k, c, c1, n, c_min; + + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + + string_buffer_init(ctx, b, 0); + + p = JS_VALUE_GET_STRING(str); + for (k = 0; k < p->len;) { + c = string_get(p, k); + if (c == '%') { + c = hex_decode(ctx, p, k); + if (c < 0) + goto fail; + k += 3; + if (c < 0x80) { + if (!isComponent && isURIReserved(c)) { + c = '%'; + k -= 2; + } + } else { + /* Decode URI-encoded UTF-8 sequence */ + if (c >= 0xc0 && c <= 0xdf) { + n = 1; + c_min = 0x80; + c &= 0x1f; + } else if (c >= 0xe0 && c <= 0xef) { + n = 2; + c_min = 0x800; + c &= 0xf; + } else if (c >= 0xf0 && c <= 0xf7) { + n = 3; + c_min = 0x10000; + c &= 0x7; + } else { + n = 0; + c_min = 1; + c = 0; + } + while (n-- > 0) { + c1 = hex_decode(ctx, p, k); + if (c1 < 0) + goto fail; + k += 3; + if ((c1 & 0xc0) != 0x80) { + c = 0; + break; + } + c = (c << 6) | (c1 & 0x3f); + } + if (c < c_min || c > 0x10FFFF || is_surrogate(c)) { + js_throw_URIError(ctx, "malformed UTF-8"); + goto fail; + } + } + } else { + k++; + } + string_buffer_putc(b, c); + } + JS_FreeValue(ctx, str); + return string_buffer_end(b); + +fail: + JS_FreeValue(ctx, str); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static int isUnescaped(int c) { + static char const unescaped_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "@*_+-./"; + return c < 0x100 && + memchr(unescaped_chars, c, sizeof(unescaped_chars) - 1); +} + +static int isURIUnescaped(int c, int isComponent) { + return c < 0x100 && + ((c >= 0x61 && c <= 0x7a) || + (c >= 0x41 && c <= 0x5a) || + (c >= 0x30 && c <= 0x39) || + memchr("-_.!~*'()", c, sizeof("-_.!~*'()") - 1) != NULL || + (!isComponent && isURIReserved(c))); +} + +static int encodeURI_hex(StringBuffer *b, int c) { + uint8_t buf[6]; + int n = 0; + const char *hex = "0123456789ABCDEF"; + + buf[n++] = '%'; + if (c >= 256) { + buf[n++] = 'u'; + buf[n++] = hex[(c >> 12) & 15]; + buf[n++] = hex[(c >> 8) & 15]; + } + buf[n++] = hex[(c >> 4) & 15]; + buf[n++] = hex[(c >> 0) & 15]; + return string_buffer_write8(b, buf, n); +} + +static JSValue js_global_encodeURI(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, + int isComponent) +{ + JSValue str; + StringBuffer b_s, *b = &b_s; + JSString *p; + int k, c, c1; + + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + + p = JS_VALUE_GET_STRING(str); + string_buffer_init(ctx, b, p->len); + for (k = 0; k < p->len;) { + c = string_get(p, k); + k++; + if (isURIUnescaped(c, isComponent)) { + string_buffer_putc16(b, c); + } else { + if (is_lo_surrogate(c)) { + js_throw_URIError(ctx, "invalid character"); + goto fail; + } else if (is_hi_surrogate(c)) { + if (k >= p->len) { + js_throw_URIError(ctx, "expecting surrogate pair"); + goto fail; + } + c1 = string_get(p, k); + k++; + if (!is_lo_surrogate(c1)) { + js_throw_URIError(ctx, "expecting surrogate pair"); + goto fail; + } + c = from_surrogate(c, c1); + } + if (c < 0x80) { + encodeURI_hex(b, c); + } else { + /* XXX: use C UTF-8 conversion ? */ + if (c < 0x800) { + encodeURI_hex(b, (c >> 6) | 0xc0); + } else { + if (c < 0x10000) { + encodeURI_hex(b, (c >> 12) | 0xe0); + } else { + encodeURI_hex(b, (c >> 18) | 0xf0); + encodeURI_hex(b, ((c >> 12) & 0x3f) | 0x80); + } + encodeURI_hex(b, ((c >> 6) & 0x3f) | 0x80); + } + encodeURI_hex(b, (c & 0x3f) | 0x80); + } + } + } + JS_FreeValue(ctx, str); + return string_buffer_end(b); + +fail: + JS_FreeValue(ctx, str); + string_buffer_free(b); + return JS_EXCEPTION; +} + +static JSValue js_global_escape(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str; + StringBuffer b_s, *b = &b_s; + JSString *p; + int i, len, c; + + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + + p = JS_VALUE_GET_STRING(str); + string_buffer_init(ctx, b, p->len); + for (i = 0, len = p->len; i < len; i++) { + c = string_get(p, i); + if (isUnescaped(c)) { + string_buffer_putc16(b, c); + } else { + encodeURI_hex(b, c); + } + } + JS_FreeValue(ctx, str); + return string_buffer_end(b); +} + +static JSValue js_global_unescape(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue str; + StringBuffer b_s, *b = &b_s; + JSString *p; + int i, len, c, n; + + str = JS_ToString(ctx, argv[0]); + if (JS_IsException(str)) + return str; + + string_buffer_init(ctx, b, 0); + p = JS_VALUE_GET_STRING(str); + for (i = 0, len = p->len; i < len; i++) { + c = string_get(p, i); + if (c == '%') { + if (i + 6 <= len + && string_get(p, i + 1) == 'u' + && (n = string_get_hex(p, i + 2, 4)) >= 0) { + c = n; + i += 6 - 1; + } else + if (i + 3 <= len + && (n = string_get_hex(p, i + 1, 2)) >= 0) { + c = n; + i += 3 - 1; + } + } + string_buffer_putc16(b, c); + } + JS_FreeValue(ctx, str); + return string_buffer_end(b); +} + +/* global object */ + +static const JSCFunctionListEntry js_global_funcs[] = { + JS_CFUNC_DEF("parseInt", 2, js_parseInt ), + JS_CFUNC_DEF("parseFloat", 1, js_parseFloat ), + JS_CFUNC_DEF("isNaN", 1, js_global_isNaN ), + JS_CFUNC_DEF("isFinite", 1, js_global_isFinite ), + JS_CFUNC_DEF("queueMicrotask", 1, js_global_queueMicrotask ), + + JS_CFUNC_MAGIC_DEF("decodeURI", 1, js_global_decodeURI, 0 ), + JS_CFUNC_MAGIC_DEF("decodeURIComponent", 1, js_global_decodeURI, 1 ), + JS_CFUNC_MAGIC_DEF("encodeURI", 1, js_global_encodeURI, 0 ), + JS_CFUNC_MAGIC_DEF("encodeURIComponent", 1, js_global_encodeURI, 1 ), + JS_CFUNC_DEF("escape", 1, js_global_escape ), + JS_CFUNC_DEF("unescape", 1, js_global_unescape ), + JS_PROP_DOUBLE_DEF("Infinity", 1.0 / 0.0, 0 ), + JS_PROP_U2D_DEF("NaN", 0x7FF8ull<<48, 0 ), // workaround for msvc + JS_PROP_UNDEFINED_DEF("undefined", 0 ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "global", JS_PROP_CONFIGURABLE ), +}; + +/* Date */ + +static int64_t math_mod(int64_t a, int64_t b) { + /* return positive modulo */ + int64_t m = a % b; + return m + (m < 0) * b; +} + +static int64_t floor_div_int64(int64_t a, int64_t b) { + /* integer division rounding toward -Infinity */ + int64_t m = a % b; + return (a - (m + (m < 0) * b)) / b; +} + +static JSValue js_Date_parse(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv); + +static __exception int JS_ThisTimeValue(JSContext *ctx, double *valp, JSValue this_val) +{ + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data)) + return JS_ToFloat64(ctx, valp, p->u.object_data); + } + JS_ThrowTypeError(ctx, "not a Date object"); + return -1; +} + +static JSValue JS_SetThisTimeValue(JSContext *ctx, JSValue this_val, double v) +{ + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_DATE) { + JS_FreeValue(ctx, p->u.object_data); + p->u.object_data = js_float64(v); + return js_dup(p->u.object_data); + } + } + return JS_ThrowTypeError(ctx, "not a Date object"); +} + +static int64_t days_from_year(int64_t y) { + return 365 * (y - 1970) + floor_div_int64(y - 1969, 4) - + floor_div_int64(y - 1901, 100) + floor_div_int64(y - 1601, 400); +} + +static int64_t days_in_year(int64_t y) { + return 365 + !(y % 4) - !(y % 100) + !(y % 400); +} + +/* return the year, update days */ +static int64_t year_from_days(int64_t *days) { + int64_t y, d1, nd, d = *days; + y = floor_div_int64(d * 10000, 3652425) + 1970; + /* the initial approximation is very good, so only a few + iterations are necessary */ + for(;;) { + d1 = d - days_from_year(y); + if (d1 < 0) { + y--; + d1 += days_in_year(y); + } else { + nd = days_in_year(y); + if (d1 < nd) + break; + d1 -= nd; + y++; + } + } + *days = d1; + return y; +} + +static int const month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +static char const month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec"; +static char const day_names[] = "SunMonTueWedThuFriSat"; + +static __exception int get_date_fields(JSContext *ctx, JSValue obj, + double fields[minimum_length(9)], + int is_local, int force) +{ + double dval; + int64_t d, days, wd, y, i, md, h, m, s, ms, tz = 0; + + if (JS_ThisTimeValue(ctx, &dval, obj)) + return -1; + + if (isnan(dval)) { + if (!force) + return FALSE; /* NaN */ + d = 0; /* initialize all fields to 0 */ + } else { + d = dval; /* assuming -8.64e15 <= dval <= -8.64e15 */ + if (is_local) { + tz = -getTimezoneOffset(d); + d += tz * 60000; + } + } + + /* result is >= 0, we can use % */ + h = math_mod(d, 86400000); + days = (d - h) / 86400000; + ms = h % 1000; + h = (h - ms) / 1000; + s = h % 60; + h = (h - s) / 60; + m = h % 60; + h = (h - m) / 60; + wd = math_mod(days + 4, 7); /* week day */ + y = year_from_days(&days); + + for(i = 0; i < 11; i++) { + md = month_days[i]; + if (i == 1) + md += days_in_year(y) - 365; + if (days < md) + break; + days -= md; + } + fields[0] = y; + fields[1] = i; + fields[2] = days + 1; + fields[3] = h; + fields[4] = m; + fields[5] = s; + fields[6] = ms; + fields[7] = wd; + fields[8] = tz; + return TRUE; +} + +static double time_clip(double t) { + if (t >= -8.64e15 && t <= 8.64e15) + return trunc(t) + 0.0; /* convert -0 to +0 */ + else + return NAN; +} + +/* The spec mandates the use of 'double' and it specifies the order + of the operations */ +static double set_date_fields(double fields[minimum_length(7)], int is_local) { + double y, m, dt, ym, mn, day, h, s, milli, time, tv; + int yi, mi, i; + int64_t days; + volatile double temp; /* enforce evaluation order */ + + /* emulate 21.4.1.15 MakeDay ( year, month, date ) */ + y = fields[0]; + m = fields[1]; + dt = fields[2]; + ym = y + floor(m / 12); + mn = fmod(m, 12); + if (mn < 0) + mn += 12; + if (ym < -271821 || ym > 275760) + return NAN; + + yi = ym; + mi = mn; + days = days_from_year(yi); + for(i = 0; i < mi; i++) { + days += month_days[i]; + if (i == 1) + days += days_in_year(yi) - 365; + } + day = days + dt - 1; + + /* emulate 21.4.1.14 MakeTime ( hour, min, sec, ms ) */ + h = fields[3]; + m = fields[4]; + s = fields[5]; + milli = fields[6]; + /* Use a volatile intermediary variable to ensure order of evaluation + * as specified in ECMA. This fixes a test262 error on + * test262/test/built-ins/Date/UTC/fp-evaluation-order.js. + * Without the volatile qualifier, the compile can generate code + * that performs the computation in a different order or with instructions + * that produce a different result such as FMA (float multiply and add). + */ + time = h * 3600000; + time += (temp = m * 60000); + time += (temp = s * 1000); + time += milli; + + /* emulate 21.4.1.16 MakeDate ( day, time ) */ + tv = (temp = day * 86400000) + time; /* prevent generation of FMA */ + if (!isfinite(tv)) + return NAN; + + /* adjust for local time and clip */ + if (is_local) { + int64_t ti = tv < INT64_MIN ? INT64_MIN : tv >= 0x1p63 ? INT64_MAX : (int64_t)tv; + tv += getTimezoneOffset(ti) * 60000; + } + return time_clip(tv); +} + +static JSValue get_date_field(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + // get_date_field(obj, n, is_local) + double fields[9]; + int res, n, is_local; + + is_local = magic & 0x0F; + n = (magic >> 4) & 0x0F; + res = get_date_fields(ctx, this_val, fields, is_local, 0); + if (res < 0) + return JS_EXCEPTION; + if (!res) + return JS_NAN; + + if (magic & 0x100) { // getYear + fields[0] -= 1900; + } + return js_number(fields[n]); +} + +static JSValue set_date_field(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + // _field(obj, first_field, end_field, args, is_local) + double fields[9]; + int res, first_field, end_field, is_local, i, n; + double d, a; + + d = NAN; + first_field = (magic >> 8) & 0x0F; + end_field = (magic >> 4) & 0x0F; + is_local = magic & 0x0F; + + res = get_date_fields(ctx, this_val, fields, is_local, first_field == 0); + if (res < 0) + return JS_EXCEPTION; + + // Argument coercion is observable and must be done unconditionally. + n = min_int(argc, end_field - first_field); + for(i = 0; i < n; i++) { + if (JS_ToFloat64(ctx, &a, argv[i])) + return JS_EXCEPTION; + if (!isfinite(a)) + res = FALSE; + fields[first_field + i] = trunc(a); + } + + if (!res) + return JS_NAN; + + if (argc > 0) + d = set_date_fields(fields, is_local); + + return JS_SetThisTimeValue(ctx, this_val, d); +} + +/* fmt: + 0: toUTCString: "Tue, 02 Jan 2018 23:04:46 GMT" + 1: toString: "Wed Jan 03 2018 00:05:22 GMT+0100 (CET)" + 2: toISOString: "2018-01-02T23:02:56.927Z" + 3: toLocaleString: "1/2/2018, 11:40:40 PM" + part: 1=date, 2=time 3=all + XXX: should use a variant of strftime(). + */ +static JSValue get_date_string(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + // _string(obj, fmt, part) + char buf[64]; + double fields[9]; + int res, fmt, part, pos; + int y, mon, d, h, m, s, ms, wd, tz; + + fmt = (magic >> 4) & 0x0F; + part = magic & 0x0F; + + res = get_date_fields(ctx, this_val, fields, fmt & 1, 0); + if (res < 0) + return JS_EXCEPTION; + if (!res) { + if (fmt == 2) + return JS_ThrowRangeError(ctx, "Date value is NaN"); + else + return js_new_string8(ctx, "Invalid Date"); + } + + y = fields[0]; + mon = fields[1]; + d = fields[2]; + h = fields[3]; + m = fields[4]; + s = fields[5]; + ms = fields[6]; + wd = fields[7]; + tz = fields[8]; + + pos = 0; + + if (part & 1) { /* date part */ + switch(fmt) { + case 0: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%.3s, %02d %.3s %0*d ", + day_names + wd * 3, d, + month_names + mon * 3, 4 + (y < 0), y); + break; + case 1: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%.3s %.3s %02d %0*d", + day_names + wd * 3, + month_names + mon * 3, d, 4 + (y < 0), y); + if (part == 3) { + buf[pos++] = ' '; + } + break; + case 2: + if (y >= 0 && y <= 9999) { + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%04d", y); + } else { + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%+07d", y); + } + pos += snprintf(buf + pos, sizeof(buf) - pos, + "-%02d-%02dT", mon + 1, d); + break; + case 3: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%02d/%02d/%0*d", mon + 1, d, 4 + (y < 0), y); + if (part == 3) { + buf[pos++] = ','; + buf[pos++] = ' '; + } + break; + } + } + if (part & 2) { /* time part */ + switch(fmt) { + case 0: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%02d:%02d:%02d GMT", h, m, s); + break; + case 1: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%02d:%02d:%02d GMT", h, m, s); + if (tz < 0) { + buf[pos++] = '-'; + tz = -tz; + } else { + buf[pos++] = '+'; + } + /* tz is >= 0, can use % */ + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%02d%02d", tz / 60, tz % 60); + /* XXX: tack the time zone code? */ + break; + case 2: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%02d:%02d:%02d.%03dZ", h, m, s, ms); + break; + case 3: + pos += snprintf(buf + pos, sizeof(buf) - pos, + "%02d:%02d:%02d %cM", (h + 11) % 12 + 1, m, s, + (h < 12) ? 'A' : 'P'); + break; + } + } + if (!pos) { + // XXX: should throw exception? + return JS_AtomToString(ctx, JS_ATOM_empty_string); + } + return js_new_string8_len(ctx, buf, pos); +} + +/* OS dependent: return the UTC time in ms since 1970. */ +static int64_t date_now(void) { + return js__gettimeofday_us() / 1000; +} + +static JSValue js_date_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + // Date(y, mon, d, h, m, s, ms) + JSValue rv; + int i, n; + double a, val; + + if (JS_IsUndefined(new_target)) { + /* invoked as function */ + argc = 0; + } + n = argc; + if (n == 0) { + val = date_now(); + } else if (n == 1) { + JSValue v, dv; + if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(argv[0]); + if (p->class_id == JS_CLASS_DATE && JS_IsNumber(p->u.object_data)) { + if (JS_ToFloat64(ctx, &val, p->u.object_data)) + return JS_EXCEPTION; + val = time_clip(val); + goto has_val; + } + } + v = JS_ToPrimitive(ctx, argv[0], HINT_NONE); + if (JS_IsString(v)) { + dv = js_Date_parse(ctx, JS_UNDEFINED, 1, &v); + JS_FreeValue(ctx, v); + if (JS_IsException(dv)) + return JS_EXCEPTION; + if (JS_ToFloat64Free(ctx, &val, dv)) + return JS_EXCEPTION; + } else { + if (JS_ToFloat64Free(ctx, &val, v)) + return JS_EXCEPTION; + } + val = time_clip(val); + } else { + double fields[] = { 0, 0, 1, 0, 0, 0, 0 }; + if (n > 7) + n = 7; + for(i = 0; i < n; i++) { + if (JS_ToFloat64(ctx, &a, argv[i])) + return JS_EXCEPTION; + if (!isfinite(a)) + break; + fields[i] = trunc(a); + if (i == 0 && fields[0] >= 0 && fields[0] < 100) + fields[0] += 1900; + } + val = (i == n) ? set_date_fields(fields, 1) : NAN; + } +has_val: + rv = js_create_from_ctor(ctx, new_target, JS_CLASS_DATE); + if (!JS_IsException(rv)) + JS_SetObjectData(ctx, rv, js_float64(val)); + if (!JS_IsException(rv) && JS_IsUndefined(new_target)) { + /* invoked as a function, return (new Date()).toString(); */ + JSValue s; + s = get_date_string(ctx, rv, 0, NULL, 0x13); + JS_FreeValue(ctx, rv); + rv = s; + } + return rv; +} + +static JSValue js_Date_UTC(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // UTC(y, mon, d, h, m, s, ms) + double fields[] = { 0, 0, 1, 0, 0, 0, 0 }; + int i, n; + double a; + + n = argc; + if (n == 0) + return JS_NAN; + if (n > 7) + n = 7; + for(i = 0; i < n; i++) { + if (JS_ToFloat64(ctx, &a, argv[i])) + return JS_EXCEPTION; + if (!isfinite(a)) + return JS_NAN; + fields[i] = trunc(a); + if (i == 0 && fields[0] >= 0 && fields[0] < 100) + fields[0] += 1900; + } + return js_float64(set_date_fields(fields, 0)); +} + +/* Date string parsing */ + +static BOOL string_skip_char(const uint8_t *sp, int *pp, int c) { + if (sp[*pp] == c) { + *pp += 1; + return TRUE; + } else { + return FALSE; + } +} + +/* skip spaces, update offset, return next char */ +static int string_skip_spaces(const uint8_t *sp, int *pp) { + int c; + while ((c = sp[*pp]) == ' ') + *pp += 1; + return c; +} + +/* skip dashes dots and commas */ +static int string_skip_separators(const uint8_t *sp, int *pp) { + int c; + while ((c = sp[*pp]) == '-' || c == '/' || c == '.' || c == ',') + *pp += 1; + return c; +} + +/* skip a word, stop on spaces, digits and separators, update offset */ +static int string_skip_until(const uint8_t *sp, int *pp, const char *stoplist) { + int c; + while (!strchr(stoplist, c = sp[*pp])) + *pp += 1; + return c; +} + +/* parse a numeric field (max_digits = 0 -> no maximum) */ +static BOOL string_get_digits(const uint8_t *sp, int *pp, int *pval, + int min_digits, int max_digits) +{ + int v = 0; + int c, p = *pp, p_start; + + p_start = p; + while ((c = sp[p]) >= '0' && c <= '9') { + v = v * 10 + c - '0'; + p++; + if (p - p_start == max_digits) + break; + } + if (p - p_start < min_digits) + return FALSE; + *pval = v; + *pp = p; + return TRUE; +} + +static BOOL string_get_milliseconds(const uint8_t *sp, int *pp, int *pval) { + /* parse optional fractional part as milliseconds and truncate. */ + /* spec does not indicate which rounding should be used */ + int mul = 100, ms = 0, c, p_start, p = *pp; + + c = sp[p]; + if (c == '.' || c == ',') { + p++; + p_start = p; + while ((c = sp[p]) >= '0' && c <= '9') { + ms += (c - '0') * mul; + mul /= 10; + p++; + if (p - p_start == 9) + break; + } + if (p > p_start) { + /* only consume the separator if digits are present */ + *pval = ms; + *pp = p; + } + } + return TRUE; +} + +static BOOL string_get_tzoffset(const uint8_t *sp, int *pp, int *tzp, BOOL strict) { + int tz = 0, sgn, hh, mm, p = *pp; + + sgn = sp[p++]; + if (sgn == '+' || sgn == '-') { + int n = p; + if (!string_get_digits(sp, &p, &hh, 1, 9)) + return FALSE; + n = p - n; + if (strict && n != 2 && n != 4) + return FALSE; + while (n > 4) { + n -= 2; + hh /= 100; + } + if (n > 2) { + mm = hh % 100; + hh = hh / 100; + } else { + mm = 0; + if (string_skip_char(sp, &p, ':') /* optional separator */ + && !string_get_digits(sp, &p, &mm, 2, 2)) + return FALSE; + } + if (hh > 23 || mm > 59) + return FALSE; + tz = hh * 60 + mm; + if (sgn != '+') + tz = -tz; + } else + if (sgn != 'Z') { + return FALSE; + } + *pp = p; + *tzp = tz; + return TRUE; +} + +static BOOL string_match(const uint8_t *sp, int *pp, const char *s) { + int p = *pp; + while (*s != '\0') { + if (to_upper_ascii(sp[p]) != to_upper_ascii(*s++)) + return FALSE; + p++; + } + *pp = p; + return TRUE; +} + +static int find_abbrev(const uint8_t *sp, int p, const char *list, int count) { + int n, i; + + for (n = 0; n < count; n++) { + for (i = 0;; i++) { + if (to_upper_ascii(sp[p + i]) != to_upper_ascii(list[n * 3 + i])) + break; + if (i == 2) + return n; + } + } + return -1; +} + +static BOOL string_get_month(const uint8_t *sp, int *pp, int *pval) { + int n; + + n = find_abbrev(sp, *pp, month_names, 12); + if (n < 0) + return FALSE; + + *pval = n + 1; + *pp += 3; + return TRUE; +} + +/* parse toISOString format */ +static BOOL js_date_parse_isostring(const uint8_t *sp, int fields[9], BOOL *is_local) { + int sgn, i, p = 0; + + /* initialize fields to the beginning of the Epoch */ + for (i = 0; i < 9; i++) { + fields[i] = (i == 2); + } + *is_local = FALSE; + + /* year is either yyyy digits or [+-]yyyyyy */ + sgn = sp[p]; + if (sgn == '-' || sgn == '+') { + p++; + if (!string_get_digits(sp, &p, &fields[0], 6, 6)) + return FALSE; + if (sgn == '-') { + if (fields[0] == 0) + return FALSE; // reject -000000 + fields[0] = -fields[0]; + } + } else { + if (!string_get_digits(sp, &p, &fields[0], 4, 4)) + return FALSE; + } + if (string_skip_char(sp, &p, '-')) { + if (!string_get_digits(sp, &p, &fields[1], 2, 2)) /* month */ + return FALSE; + if (fields[1] < 1) + return FALSE; + fields[1] -= 1; + if (string_skip_char(sp, &p, '-')) { + if (!string_get_digits(sp, &p, &fields[2], 2, 2)) /* day */ + return FALSE; + if (fields[2] < 1) + return FALSE; + } + } + if (string_skip_char(sp, &p, 'T')) { + *is_local = TRUE; + if (!string_get_digits(sp, &p, &fields[3], 2, 2) /* hour */ + || !string_skip_char(sp, &p, ':') + || !string_get_digits(sp, &p, &fields[4], 2, 2)) { /* minute */ + fields[3] = 100; // reject unconditionally + return TRUE; + } + if (string_skip_char(sp, &p, ':')) { + if (!string_get_digits(sp, &p, &fields[5], 2, 2)) /* second */ + return FALSE; + string_get_milliseconds(sp, &p, &fields[6]); + } + } + /* parse the time zone offset if present: [+-]HH:mm or [+-]HHmm */ + if (sp[p]) { + *is_local = FALSE; + if (!string_get_tzoffset(sp, &p, &fields[8], TRUE)) + return FALSE; + } + /* error if extraneous characters */ + return sp[p] == '\0'; +} + +static struct { + char name[6]; + int16_t offset; +} const js_tzabbr[] = { + { "GMT", 0 }, // Greenwich Mean Time + { "UTC", 0 }, // Coordinated Universal Time + { "UT", 0 }, // Universal Time + { "Z", 0 }, // Zulu Time + { "EDT", -4 * 60 }, // Eastern Daylight Time + { "EST", -5 * 60 }, // Eastern Standard Time + { "CDT", -5 * 60 }, // Central Daylight Time + { "CST", -6 * 60 }, // Central Standard Time + { "MDT", -6 * 60 }, // Mountain Daylight Time + { "MST", -7 * 60 }, // Mountain Standard Time + { "PDT", -7 * 60 }, // Pacific Daylight Time + { "PST", -8 * 60 }, // Pacific Standard Time + { "WET", +0 * 60 }, // Western European Time + { "WEST", +1 * 60 }, // Western European Summer Time + { "CET", +1 * 60 }, // Central European Time + { "CEST", +2 * 60 }, // Central European Summer Time + { "EET", +2 * 60 }, // Eastern European Time + { "EEST", +3 * 60 }, // Eastern European Summer Time +}; + +static BOOL string_get_tzabbr(const uint8_t *sp, int *pp, int *offset) { + size_t i; + + for (i = 0; i < countof(js_tzabbr); i++) { + if (string_match(sp, pp, js_tzabbr[i].name)) { + *offset = js_tzabbr[i].offset; + return TRUE; + } + } + return FALSE; +} + +/* parse toString, toUTCString and other formats */ +static BOOL js_date_parse_otherstring(const uint8_t *sp, + int fields[minimum_length(9)], + BOOL *is_local) { + int c, i, val, p = 0, p_start; + int num[3]; + BOOL has_year = FALSE; + BOOL has_mon = FALSE; + BOOL has_time = FALSE; + int num_index = 0; + + /* initialize fields to the beginning of 2001-01-01 */ + fields[0] = 2001; + fields[1] = 1; + fields[2] = 1; + for (i = 3; i < 9; i++) { + fields[i] = 0; + } + *is_local = TRUE; + + while (string_skip_spaces(sp, &p)) { + p_start = p; + if ((c = sp[p]) == '+' || c == '-') { + if (has_time && string_get_tzoffset(sp, &p, &fields[8], FALSE)) { + *is_local = FALSE; + } else { + p++; + if (string_get_digits(sp, &p, &val, 1, 9)) { + if (c == '-') { + if (val == 0) + return FALSE; + val = -val; + } + fields[0] = val; + has_year = TRUE; + } + } + } else + if (string_get_digits(sp, &p, &val, 1, 9)) { + if (string_skip_char(sp, &p, ':')) { + /* time part */ + fields[3] = val; + if (!string_get_digits(sp, &p, &fields[4], 1, 2)) + return FALSE; + if (string_skip_char(sp, &p, ':')) { + if (!string_get_digits(sp, &p, &fields[5], 1, 2)) + return FALSE; + string_get_milliseconds(sp, &p, &fields[6]); + } else + if (sp[p] != '\0' && sp[p] != ' ') + return FALSE; + has_time = TRUE; + } else { + if (p - p_start > 2) { + fields[0] = val; + has_year = TRUE; + } else + if (val < 1 || val > 31) { + fields[0] = val + (val < 100) * 1900 + (val < 50) * 100; + has_year = TRUE; + } else { + if (num_index == 3) + return FALSE; + num[num_index++] = val; + } + } + } else + if (string_get_month(sp, &p, &fields[1])) { + has_mon = TRUE; + string_skip_until(sp, &p, "0123456789 -/("); + } else + if (has_time && string_match(sp, &p, "PM")) { + /* hours greater than 12 will be incremented and + rejected by the range check in js_Date_parse */ + /* 00:00 PM will be silently converted as 12:00 */ + if (fields[3] != 12) + fields[3] += 12; + continue; + } else + if (has_time && string_match(sp, &p, "AM")) { + /* 00:00 AM will be silently accepted */ + if (fields[3] > 12) + return FALSE; + if (fields[3] == 12) + fields[3] -= 12; + continue; + } else + if (string_get_tzabbr(sp, &p, &fields[8])) { + *is_local = FALSE; + continue; + } else + if (c == '(') { /* skip parenthesized phrase */ + int level = 0; + while ((c = sp[p]) != '\0') { + p++; + level += (c == '('); + level -= (c == ')'); + if (!level) + break; + } + if (level > 0) + return FALSE; + } else + if (c == ')') { + return FALSE; + } else { + if (has_year + has_mon + has_time + num_index) + return FALSE; + /* skip a word */ + string_skip_until(sp, &p, " -/("); + } + string_skip_separators(sp, &p); + } + if (num_index + has_year + has_mon > 3) + return FALSE; + + switch (num_index) { + case 0: + if (!has_year) + return FALSE; + break; + case 1: + if (has_mon) + fields[2] = num[0]; + else + fields[1] = num[0]; + break; + case 2: + if (has_year) { + fields[1] = num[0]; + fields[2] = num[1]; + } else + if (has_mon) { + fields[0] = num[1] + (num[1] < 100) * 1900 + (num[1] < 50) * 100; + fields[2] = num[0]; + } else { + fields[1] = num[0]; + fields[2] = num[1]; + } + break; + case 3: + fields[0] = num[2] + (num[2] < 100) * 1900 + (num[2] < 50) * 100; + fields[1] = num[0]; + fields[2] = num[1]; + break; + default: + return FALSE; + } + if (fields[1] < 1 || fields[2] < 1) + return FALSE; + fields[1] -= 1; + return TRUE; +} + +static JSValue js_Date_parse(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue s, rv; + int fields[9]; + double fields1[9]; + double d; + int i, c; + JSString *sp; + uint8_t buf[128]; + BOOL is_local; + + rv = JS_NAN; + + s = JS_ToString(ctx, argv[0]); + if (JS_IsException(s)) + return JS_EXCEPTION; + + sp = JS_VALUE_GET_STRING(s); + /* convert the string as a byte array */ + for (i = 0; i < sp->len && i < (int)countof(buf) - 1; i++) { + c = string_get(sp, i); + if (c > 255) + c = (c == 0x2212) ? '-' : 'x'; + buf[i] = c; + } + buf[i] = '\0'; + if (js_date_parse_isostring(buf, fields, &is_local) + || js_date_parse_otherstring(buf, fields, &is_local)) { + static int const field_max[6] = { 0, 11, 31, 24, 59, 59 }; + BOOL valid = TRUE; + /* check field maximum values */ + for (i = 1; i < 6; i++) { + if (fields[i] > field_max[i]) + valid = FALSE; + } + /* special case 24:00:00.000 */ + if (fields[3] == 24 && (fields[4] | fields[5] | fields[6])) + valid = FALSE; + if (valid) { + for(i = 0; i < 7; i++) + fields1[i] = fields[i]; + d = set_date_fields(fields1, is_local) - fields[8] * 60000; + rv = JS_NewFloat64(ctx, d); + } + } + JS_FreeValue(ctx, s); + return rv; +} + +static JSValue js_Date_now(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // now() + return js_int64(date_now()); +} + +static JSValue js_date_Symbol_toPrimitive(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // Symbol_toPrimitive(hint) + JSValue obj = this_val; + JSAtom hint = JS_ATOM_NULL; + int hint_num; + + if (!JS_IsObject(obj)) + return JS_ThrowTypeErrorNotAnObject(ctx); + + if (JS_IsString(argv[0])) { + hint = JS_ValueToAtom(ctx, argv[0]); + if (hint == JS_ATOM_NULL) + return JS_EXCEPTION; + JS_FreeAtom(ctx, hint); + } + switch (hint) { + case JS_ATOM_number: + case JS_ATOM_integer: + hint_num = HINT_NUMBER; + break; + case JS_ATOM_string: + case JS_ATOM_default: + hint_num = HINT_STRING; + break; + default: + return JS_ThrowTypeError(ctx, "invalid hint"); + } + return JS_ToPrimitive(ctx, obj, hint_num | HINT_FORCE_ORDINARY); +} + +static JSValue js_date_getTimezoneOffset(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // getTimezoneOffset() + double v; + + if (JS_ThisTimeValue(ctx, &v, this_val)) + return JS_EXCEPTION; + if (isnan(v)) + return JS_NAN; + else + /* assuming -8.64e15 <= v <= -8.64e15 */ + return js_int64(getTimezoneOffset((int64_t)trunc(v))); +} + +static JSValue js_date_getTime(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // getTime() + double v; + + if (JS_ThisTimeValue(ctx, &v, this_val)) + return JS_EXCEPTION; + return js_float64(v); +} + +static JSValue js_date_setTime(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // setTime(v) + double v; + + if (JS_ThisTimeValue(ctx, &v, this_val) || JS_ToFloat64(ctx, &v, argv[0])) + return JS_EXCEPTION; + return JS_SetThisTimeValue(ctx, this_val, time_clip(v)); +} + +static JSValue js_date_setYear(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // setYear(y) + double y; + JSValue args[1]; + + if (JS_ThisTimeValue(ctx, &y, this_val) || JS_ToFloat64(ctx, &y, argv[0])) + return JS_EXCEPTION; + y = +y; + if (isnan(y)) + return JS_SetThisTimeValue(ctx, this_val, y); + if (isfinite(y)) { + y = trunc(y); + if (y >= 0 && y < 100) + y += 1900; + } + args[0] = js_float64(y); + return set_date_field(ctx, this_val, 1, args, 0x011); +} + +static JSValue js_date_toJSON(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // toJSON(key) + JSValue obj, tv, method, rv; + double d; + + rv = JS_EXCEPTION; + tv = JS_UNDEFINED; + + obj = JS_ToObject(ctx, this_val); + tv = JS_ToPrimitive(ctx, obj, HINT_NUMBER); + if (JS_IsException(tv)) + goto exception; + if (JS_IsNumber(tv)) { + if (JS_ToFloat64(ctx, &d, tv) < 0) + goto exception; + if (!isfinite(d)) { + rv = JS_NULL; + goto done; + } + } + method = JS_GetPropertyStr(ctx, obj, "toISOString"); + if (JS_IsException(method)) + goto exception; + if (!JS_IsFunction(ctx, method)) { + JS_ThrowTypeError(ctx, "object needs toISOString method"); + JS_FreeValue(ctx, method); + goto exception; + } + rv = JS_CallFree(ctx, method, obj, 0, NULL); +exception: +done: + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, tv); + return rv; +} + +static const JSCFunctionListEntry js_date_funcs[] = { + JS_CFUNC_DEF("now", 0, js_Date_now ), + JS_CFUNC_DEF("parse", 1, js_Date_parse ), + JS_CFUNC_DEF("UTC", 7, js_Date_UTC ), +}; + +static const JSCFunctionListEntry js_date_proto_funcs[] = { + JS_CFUNC_DEF("valueOf", 0, js_date_getTime ), + JS_CFUNC_MAGIC_DEF("toString", 0, get_date_string, 0x13 ), + JS_CFUNC_DEF("[Symbol.toPrimitive]", 1, js_date_Symbol_toPrimitive ), + JS_CFUNC_MAGIC_DEF("toUTCString", 0, get_date_string, 0x03 ), + JS_ALIAS_DEF("toGMTString", "toUTCString" ), + JS_CFUNC_MAGIC_DEF("toISOString", 0, get_date_string, 0x23 ), + JS_CFUNC_MAGIC_DEF("toDateString", 0, get_date_string, 0x11 ), + JS_CFUNC_MAGIC_DEF("toTimeString", 0, get_date_string, 0x12 ), + JS_CFUNC_MAGIC_DEF("toLocaleString", 0, get_date_string, 0x33 ), + JS_CFUNC_MAGIC_DEF("toLocaleDateString", 0, get_date_string, 0x31 ), + JS_CFUNC_MAGIC_DEF("toLocaleTimeString", 0, get_date_string, 0x32 ), + JS_CFUNC_DEF("getTimezoneOffset", 0, js_date_getTimezoneOffset ), + JS_CFUNC_DEF("getTime", 0, js_date_getTime ), + JS_CFUNC_MAGIC_DEF("getYear", 0, get_date_field, 0x101 ), + JS_CFUNC_MAGIC_DEF("getFullYear", 0, get_date_field, 0x01 ), + JS_CFUNC_MAGIC_DEF("getUTCFullYear", 0, get_date_field, 0x00 ), + JS_CFUNC_MAGIC_DEF("getMonth", 0, get_date_field, 0x11 ), + JS_CFUNC_MAGIC_DEF("getUTCMonth", 0, get_date_field, 0x10 ), + JS_CFUNC_MAGIC_DEF("getDate", 0, get_date_field, 0x21 ), + JS_CFUNC_MAGIC_DEF("getUTCDate", 0, get_date_field, 0x20 ), + JS_CFUNC_MAGIC_DEF("getHours", 0, get_date_field, 0x31 ), + JS_CFUNC_MAGIC_DEF("getUTCHours", 0, get_date_field, 0x30 ), + JS_CFUNC_MAGIC_DEF("getMinutes", 0, get_date_field, 0x41 ), + JS_CFUNC_MAGIC_DEF("getUTCMinutes", 0, get_date_field, 0x40 ), + JS_CFUNC_MAGIC_DEF("getSeconds", 0, get_date_field, 0x51 ), + JS_CFUNC_MAGIC_DEF("getUTCSeconds", 0, get_date_field, 0x50 ), + JS_CFUNC_MAGIC_DEF("getMilliseconds", 0, get_date_field, 0x61 ), + JS_CFUNC_MAGIC_DEF("getUTCMilliseconds", 0, get_date_field, 0x60 ), + JS_CFUNC_MAGIC_DEF("getDay", 0, get_date_field, 0x71 ), + JS_CFUNC_MAGIC_DEF("getUTCDay", 0, get_date_field, 0x70 ), + JS_CFUNC_DEF("setTime", 1, js_date_setTime ), + JS_CFUNC_MAGIC_DEF("setMilliseconds", 1, set_date_field, 0x671 ), + JS_CFUNC_MAGIC_DEF("setUTCMilliseconds", 1, set_date_field, 0x670 ), + JS_CFUNC_MAGIC_DEF("setSeconds", 2, set_date_field, 0x571 ), + JS_CFUNC_MAGIC_DEF("setUTCSeconds", 2, set_date_field, 0x570 ), + JS_CFUNC_MAGIC_DEF("setMinutes", 3, set_date_field, 0x471 ), + JS_CFUNC_MAGIC_DEF("setUTCMinutes", 3, set_date_field, 0x470 ), + JS_CFUNC_MAGIC_DEF("setHours", 4, set_date_field, 0x371 ), + JS_CFUNC_MAGIC_DEF("setUTCHours", 4, set_date_field, 0x370 ), + JS_CFUNC_MAGIC_DEF("setDate", 1, set_date_field, 0x231 ), + JS_CFUNC_MAGIC_DEF("setUTCDate", 1, set_date_field, 0x230 ), + JS_CFUNC_MAGIC_DEF("setMonth", 2, set_date_field, 0x131 ), + JS_CFUNC_MAGIC_DEF("setUTCMonth", 2, set_date_field, 0x130 ), + JS_CFUNC_DEF("setYear", 1, js_date_setYear ), + JS_CFUNC_MAGIC_DEF("setFullYear", 3, set_date_field, 0x031 ), + JS_CFUNC_MAGIC_DEF("setUTCFullYear", 3, set_date_field, 0x030 ), + JS_CFUNC_DEF("toJSON", 1, js_date_toJSON ), +}; + +JSValue JS_NewDate(JSContext *ctx, double epoch_ms) +{ + JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_DATE); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JS_SetObjectData(ctx, obj, js_float64(time_clip(epoch_ms))); + return obj; +} + +void JS_AddIntrinsicDate(JSContext *ctx) +{ + JSValue obj; + + /* Date */ + ctx->class_proto[JS_CLASS_DATE] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATE], js_date_proto_funcs, + countof(js_date_proto_funcs)); + obj = JS_NewGlobalCConstructor(ctx, "Date", js_date_constructor, 7, + ctx->class_proto[JS_CLASS_DATE]); + JS_SetPropertyFunctionList(ctx, obj, js_date_funcs, countof(js_date_funcs)); +} + +/* eval */ + +void JS_AddIntrinsicEval(JSContext *ctx) +{ + ctx->eval_internal = __JS_EvalInternal; +} + +/* BigInt */ + +static JSValue JS_ToBigIntCtorFree(JSContext *ctx, JSValue val) +{ + uint32_t tag; + + redo: + tag = JS_VALUE_GET_NORM_TAG(val); + switch(tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + val = JS_NewBigInt64(ctx, JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BIG_INT: + break; + case JS_TAG_FLOAT64: + { + bf_t *a, a_s; + + a = JS_ToBigInt1(ctx, &a_s, val); + if (!bf_is_finite(a)) { + JS_FreeValue(ctx, val); + val = JS_ThrowRangeError(ctx, "cannot convert NaN or Infinity to BigInt"); + } else { + JSValue val1 = JS_NewBigInt(ctx); + bf_t *r; + int ret; + if (JS_IsException(val1)) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + r = JS_GetBigInt(val1); + ret = bf_set(r, a); + ret |= bf_rint(r, BF_RNDZ); + JS_FreeValue(ctx, val); + if (ret & BF_ST_MEM_ERROR) { + JS_FreeValue(ctx, val1); + val = JS_ThrowOutOfMemory(ctx); + } else if (ret & BF_ST_INEXACT) { + JS_FreeValue(ctx, val1); + val = JS_ThrowRangeError(ctx, "cannot convert to BigInt: not an integer"); + } else { + val = JS_CompactBigInt(ctx, val1); + } + } + if (a == &a_s) + bf_delete(a); + } + break; + case JS_TAG_STRING: + val = JS_StringToBigIntErr(ctx, val); + break; + case JS_TAG_OBJECT: + val = JS_ToPrimitiveFree(ctx, val, HINT_NUMBER); + if (JS_IsException(val)) + break; + goto redo; + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + default: + JS_FreeValue(ctx, val); + return JS_ThrowTypeError(ctx, "cannot convert to BigInt"); + } + return val; +} + +static JSValue js_bigint_constructor(JSContext *ctx, + JSValue new_target, + int argc, JSValue *argv) +{ + if (!JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "not a constructor"); + return JS_ToBigIntCtorFree(ctx, js_dup(argv[0])); +} + +static JSValue js_thisBigIntValue(JSContext *ctx, JSValue this_val) +{ + if (JS_IsBigInt(ctx, this_val)) + return js_dup(this_val); + + if (JS_VALUE_GET_TAG(this_val) == JS_TAG_OBJECT) { + JSObject *p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id == JS_CLASS_BIG_INT) { + if (JS_IsBigInt(ctx, p->u.object_data)) + return js_dup(p->u.object_data); + } + } + return JS_ThrowTypeError(ctx, "not a BigInt"); +} + +static JSValue js_bigint_toString(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue val; + int base; + JSValue ret; + + val = js_thisBigIntValue(ctx, this_val); + if (JS_IsException(val)) + return val; + if (argc == 0 || JS_IsUndefined(argv[0])) { + base = 10; + } else { + base = js_get_radix(ctx, argv[0]); + if (base < 0) + goto fail; + } + ret = js_bigint_to_string1(ctx, val, base); + JS_FreeValue(ctx, val); + return ret; + fail: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +static JSValue js_bigint_valueOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + return js_thisBigIntValue(ctx, this_val); +} + +static JSValue js_bigint_asUintN(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, int asIntN) +{ + uint64_t bits; + bf_t a_s, *a = &a_s, *r, mask_s, *mask = &mask_s; + JSValue res; + + if (JS_ToIndex(ctx, &bits, argv[0])) + return JS_EXCEPTION; + res = JS_NewBigInt(ctx); + if (JS_IsException(res)) + return JS_EXCEPTION; + a = JS_ToBigInt(ctx, &a_s, argv[1]); + if (!a) { + JS_FreeValue(ctx, res); + return JS_EXCEPTION; + } + /* XXX: optimize */ + r = JS_GetBigInt(res); + bf_init(ctx->bf_ctx, mask); + bf_set_ui(mask, 1); + bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ); + bf_add_si(mask, mask, -1, BF_PREC_INF, BF_RNDZ); + bf_logic_and(r, a, mask); + if (asIntN && bits != 0) { + bf_set_ui(mask, 1); + bf_mul_2exp(mask, bits - 1, BF_PREC_INF, BF_RNDZ); + if (bf_cmpu(r, mask) >= 0) { + bf_set_ui(mask, 1); + bf_mul_2exp(mask, bits, BF_PREC_INF, BF_RNDZ); + bf_sub(r, r, mask, BF_PREC_INF, BF_RNDZ); + } + } + bf_delete(mask); + JS_FreeBigInt(ctx, a, &a_s); + return JS_CompactBigInt(ctx, res); +} + +static const JSCFunctionListEntry js_bigint_funcs[] = { + JS_CFUNC_MAGIC_DEF("asUintN", 2, js_bigint_asUintN, 0 ), + JS_CFUNC_MAGIC_DEF("asIntN", 2, js_bigint_asUintN, 1 ), +}; + +static const JSCFunctionListEntry js_bigint_proto_funcs[] = { + JS_CFUNC_DEF("toString", 0, js_bigint_toString ), + JS_CFUNC_DEF("valueOf", 0, js_bigint_valueOf ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "BigInt", JS_PROP_CONFIGURABLE ), +}; + +void JS_AddIntrinsicBigInt(JSContext *ctx) +{ + JSValue obj1; + + ctx->class_proto[JS_CLASS_BIG_INT] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BIG_INT], + js_bigint_proto_funcs, + countof(js_bigint_proto_funcs)); + obj1 = JS_NewGlobalCConstructor(ctx, "BigInt", js_bigint_constructor, 1, + ctx->class_proto[JS_CLASS_BIG_INT]); + JS_SetPropertyFunctionList(ctx, obj1, js_bigint_funcs, + countof(js_bigint_funcs)); +} + +static const char * const native_error_name[JS_NATIVE_ERROR_COUNT] = { + "EvalError", "RangeError", "ReferenceError", + "SyntaxError", "TypeError", "URIError", + "InternalError", "AggregateError", +}; + +/* Minimum amount of objects to be able to compile code and display + error messages. No JSAtom should be allocated by this function. */ +static void JS_AddIntrinsicBasicObjects(JSContext *ctx) +{ + JSValue proto; + int i; + + ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto(ctx, JS_NULL); + ctx->function_proto = JS_NewCFunction3(ctx, js_function_proto, "", 0, + JS_CFUNC_generic, 0, + ctx->class_proto[JS_CLASS_OBJECT]); + ctx->class_proto[JS_CLASS_BYTECODE_FUNCTION] = js_dup(ctx->function_proto); + ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ERROR], + js_error_proto_funcs, + countof(js_error_proto_funcs)); + + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + proto = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ERROR]); + JS_DefinePropertyValue(ctx, proto, JS_ATOM_name, + JS_NewAtomString(ctx, native_error_name[i]), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + JS_DefinePropertyValue(ctx, proto, JS_ATOM_message, + JS_AtomToString(ctx, JS_ATOM_empty_string), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + ctx->native_error_proto[i] = proto; + } + + /* the array prototype is an array */ + ctx->class_proto[JS_CLASS_ARRAY] = + JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_ARRAY); + + ctx->array_shape = js_new_shape2(ctx, get_proto_obj(ctx->class_proto[JS_CLASS_ARRAY]), + JS_PROP_INITIAL_HASH_SIZE, 1); + add_shape_property(ctx, &ctx->array_shape, NULL, + JS_ATOM_length, JS_PROP_WRITABLE | JS_PROP_LENGTH); + + /* XXX: could test it on first context creation to ensure that no + new atoms are created in JS_AddIntrinsicBasicObjects(). It is + necessary to avoid useless renumbering of atoms after + JS_EvalBinary() if it is done just after + JS_AddIntrinsicBasicObjects(). */ + // assert(ctx->rt->atom_count == JS_ATOM_END); +} + +void JS_AddIntrinsicBaseObjects(JSContext *ctx) +{ + int i; + JSValue obj, number_obj; + JSValue obj1; + + ctx->throw_type_error = JS_NewCFunction(ctx, js_throw_type_error, NULL, 0); + + /* add caller and arguments properties to throw a TypeError */ + obj1 = JS_NewCFunction(ctx, js_function_proto_caller, NULL, 0); + JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_caller, JS_UNDEFINED, + obj1, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); + JS_DefineProperty(ctx, ctx->function_proto, JS_ATOM_arguments, JS_UNDEFINED, + obj1, ctx->throw_type_error, + JS_PROP_HAS_GET | JS_PROP_HAS_SET | + JS_PROP_HAS_CONFIGURABLE | JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, obj1); + JS_FreeValue(ctx, js_object_seal(ctx, JS_UNDEFINED, 1, &ctx->throw_type_error, 1)); + + ctx->global_obj = JS_NewObject(ctx); + ctx->global_var_obj = JS_NewObjectProto(ctx, JS_NULL); + + /* Object */ + obj = JS_NewGlobalCConstructor(ctx, "Object", js_object_constructor, 1, + ctx->class_proto[JS_CLASS_OBJECT]); + JS_SetPropertyFunctionList(ctx, obj, js_object_funcs, countof(js_object_funcs)); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_OBJECT], + js_object_proto_funcs, countof(js_object_proto_funcs)); + + /* Function */ + JS_SetPropertyFunctionList(ctx, ctx->function_proto, js_function_proto_funcs, countof(js_function_proto_funcs)); + ctx->function_ctor = JS_NewCFunctionMagic(ctx, js_function_constructor, + "Function", 1, JS_CFUNC_constructor_or_func_magic, + JS_FUNC_NORMAL); + JS_NewGlobalCConstructor2(ctx, js_dup(ctx->function_ctor), "Function", + ctx->function_proto); + + /* Error */ + ctx->error_ctor = JS_NewCFunctionMagic(ctx, js_error_constructor, + "Error", 1, JS_CFUNC_constructor_or_func_magic, -1); + JS_NewGlobalCConstructor2(ctx, js_dup(ctx->error_ctor), + "Error", ctx->class_proto[JS_CLASS_ERROR]); + JS_SetPropertyFunctionList(ctx, ctx->error_ctor, js_error_funcs, countof(js_error_funcs)); + + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = js_error_constructor }; + for(i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + JSValue func_obj; + int n_args; + n_args = 1 + (i == JS_AGGREGATE_ERROR); + func_obj = JS_NewCFunction3(ctx, ft.generic, + native_error_name[i], n_args, + JS_CFUNC_constructor_or_func_magic, i, + ctx->error_ctor); + JS_NewGlobalCConstructor2(ctx, func_obj, native_error_name[i], + ctx->native_error_proto[i]); + } + + /* CallSite */ + _JS_AddIntrinsicCallSite(ctx); + + /* Iterator */ + ctx->class_proto[JS_CLASS_ITERATOR] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ITERATOR], + js_iterator_proto_funcs, + countof(js_iterator_proto_funcs)); + obj = JS_NewGlobalCConstructor(ctx, "Iterator", js_iterator_constructor, 0, + ctx->class_proto[JS_CLASS_ITERATOR]); + ctx->iterator_ctor = js_dup(obj); + JS_SetPropertyFunctionList(ctx, obj, + js_iterator_funcs, + countof(js_iterator_funcs)); + + ctx->class_proto[JS_CLASS_ITERATOR_HELPER] = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ITERATOR_HELPER], + js_iterator_helper_proto_funcs, + countof(js_iterator_helper_proto_funcs)); + + ctx->class_proto[JS_CLASS_ITERATOR_WRAP] = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ITERATOR_WRAP], + js_iterator_wrap_proto_funcs, + countof(js_iterator_wrap_proto_funcs)); + + /* Array */ + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY], + js_array_proto_funcs, + countof(js_array_proto_funcs)); + + obj = JS_NewGlobalCConstructor(ctx, "Array", js_array_constructor, 1, + ctx->class_proto[JS_CLASS_ARRAY]); + ctx->array_ctor = js_dup(obj); + JS_SetPropertyFunctionList(ctx, obj, js_array_funcs, + countof(js_array_funcs)); + + /* XXX: create auto_initializer */ + { + /* initialize Array.prototype[Symbol.unscopables] */ + static const char unscopables[] = + "copyWithin" "\0" + "entries" "\0" + "fill" "\0" + "find" "\0" + "findIndex" "\0" + "findLast" "\0" + "findLastIndex" "\0" + "flat" "\0" + "flatMap" "\0" + "includes" "\0" + "keys" "\0" + "toReversed" "\0" + "toSorted" "\0" + "toSpliced" "\0" + "values" "\0"; + const char *p = unscopables; + obj1 = JS_NewObjectProto(ctx, JS_NULL); + for(p = unscopables; *p; p += strlen(p) + 1) { + JS_DefinePropertyValueStr(ctx, obj1, p, JS_TRUE, JS_PROP_C_W_E); + } + JS_DefinePropertyValue(ctx, ctx->class_proto[JS_CLASS_ARRAY], + JS_ATOM_Symbol_unscopables, obj1, + JS_PROP_CONFIGURABLE); + } + + /* needed to initialize arguments[Symbol.iterator] */ + ctx->array_proto_values = + JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_values); + + ctx->class_proto[JS_CLASS_ARRAY_ITERATOR] = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_ITERATOR], + js_array_iterator_proto_funcs, + countof(js_array_iterator_proto_funcs)); + + /* parseFloat and parseInteger must be defined before Number + because of the Number.parseFloat and Number.parseInteger + aliases */ + JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_global_funcs, + countof(js_global_funcs)); + + /* Number */ + ctx->class_proto[JS_CLASS_NUMBER] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_NUMBER); + JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_NUMBER], js_int32(0)); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_NUMBER], + js_number_proto_funcs, + countof(js_number_proto_funcs)); + number_obj = JS_NewGlobalCConstructor(ctx, "Number", js_number_constructor, 1, + ctx->class_proto[JS_CLASS_NUMBER]); + JS_SetPropertyFunctionList(ctx, number_obj, js_number_funcs, countof(js_number_funcs)); + + /* Boolean */ + ctx->class_proto[JS_CLASS_BOOLEAN] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_BOOLEAN); + JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], js_bool(FALSE)); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_BOOLEAN], js_boolean_proto_funcs, + countof(js_boolean_proto_funcs)); + JS_NewGlobalCConstructor(ctx, "Boolean", js_boolean_constructor, 1, + ctx->class_proto[JS_CLASS_BOOLEAN]); + + /* String */ + ctx->class_proto[JS_CLASS_STRING] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], + JS_CLASS_STRING); + JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_STRING], JS_AtomToString(ctx, JS_ATOM_empty_string)); + obj = JS_NewGlobalCConstructor(ctx, "String", js_string_constructor, 1, + ctx->class_proto[JS_CLASS_STRING]); + JS_SetPropertyFunctionList(ctx, obj, js_string_funcs, + countof(js_string_funcs)); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING], js_string_proto_funcs, + countof(js_string_proto_funcs)); + + ctx->class_proto[JS_CLASS_STRING_ITERATOR] = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_STRING_ITERATOR], + js_string_iterator_proto_funcs, + countof(js_string_iterator_proto_funcs)); + + /* Math: create as autoinit object */ + js_random_init(ctx); + JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_math_obj, countof(js_math_obj)); + + /* ES6 Reflect: create as autoinit object */ + JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_reflect_obj, countof(js_reflect_obj)); + + /* ES6 Symbol */ + ctx->class_proto[JS_CLASS_SYMBOL] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SYMBOL], js_symbol_proto_funcs, + countof(js_symbol_proto_funcs)); + obj = JS_NewGlobalCConstructor(ctx, "Symbol", js_symbol_constructor, 0, + ctx->class_proto[JS_CLASS_SYMBOL]); + JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs, + countof(js_symbol_funcs)); + for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) { + char buf[ATOM_GET_STR_BUF_SIZE]; + const char *str, *p; + str = JS_AtomGetStr(ctx, buf, sizeof(buf), i); + /* skip "Symbol." */ + p = strchr(str, '.'); + if (p) + str = p + 1; + JS_DefinePropertyValueStr(ctx, obj, str, JS_AtomToValue(ctx, i), 0); + } + + /* ES6 Generator */ + ctx->class_proto[JS_CLASS_GENERATOR] = JS_NewObjectProto(ctx, ctx->class_proto[JS_CLASS_ITERATOR]); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_GENERATOR], + js_generator_proto_funcs, + countof(js_generator_proto_funcs)); + + ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto); + obj1 = JS_NewCFunctionMagic(ctx, js_function_constructor, + "GeneratorFunction", 1, + JS_CFUNC_constructor_or_func_magic, JS_FUNC_GENERATOR); + JS_SetPropertyFunctionList(ctx, + ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION], + js_generator_function_proto_funcs, + countof(js_generator_function_proto_funcs)); + JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION], + ctx->class_proto[JS_CLASS_GENERATOR], + JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE); + JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_GENERATOR_FUNCTION], + 0, JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, obj1); + + /* global properties */ + ctx->eval_obj = JS_NewCFunction(ctx, js_global_eval, "eval", 1); + JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_eval, + js_dup(ctx->eval_obj), + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + JS_DefinePropertyValue(ctx, ctx->global_obj, JS_ATOM_globalThis, + js_dup(ctx->global_obj), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE); +} + +/* Typed Arrays */ + +static uint8_t const typed_array_size_log2[JS_TYPED_ARRAY_COUNT] = { + 0, 0, 0, 1, 1, 2, 2, + 3, 3, // BigInt64Array, BigUint64Array + 1, 2, 3 // Float16Array, Float32Array, Float64Array +}; + +static JSValue js_array_buffer_constructor3(JSContext *ctx, + JSValue new_target, + uint64_t len, uint64_t *max_len, + JSClassID class_id, + uint8_t *buf, + JSFreeArrayBufferDataFunc *free_func, + void *opaque, BOOL alloc_flag) +{ + JSRuntime *rt = ctx->rt; + JSValue obj; + JSArrayBuffer *abuf = NULL; + uint64_t sab_alloc_len; + + if (!alloc_flag && buf && max_len && free_func != js_array_buffer_free) { + // not observable from JS land, only through C API misuse; + // JS code cannot create externally managed buffers directly + return JS_ThrowInternalError(ctx, + "resizable ArrayBuffers not supported " + "for externally managed buffers"); + } + obj = js_create_from_ctor(ctx, new_target, class_id); + if (JS_IsException(obj)) + return obj; + /* XXX: we are currently limited to 2 GB */ + if (len > INT32_MAX) { + JS_ThrowRangeError(ctx, "invalid array buffer length"); + goto fail; + } + if (max_len && *max_len > INT32_MAX) { + JS_ThrowRangeError(ctx, "invalid max array buffer length"); + goto fail; + } + abuf = js_malloc(ctx, sizeof(*abuf)); + if (!abuf) + goto fail; + abuf->byte_length = len; + abuf->max_byte_length = max_len ? *max_len : -1; + if (alloc_flag) { + if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER && + rt->sab_funcs.sab_alloc) { + // TOOD(bnoordhuis) resizing backing memory for SABs atomically + // is hard so we cheat and allocate |maxByteLength| bytes upfront + sab_alloc_len = max_len ? *max_len : len; + abuf->data = rt->sab_funcs.sab_alloc(rt->sab_funcs.sab_opaque, + max_int(sab_alloc_len, 1)); + if (!abuf->data) + goto fail; + memset(abuf->data, 0, sab_alloc_len); + } else { + /* the allocation must be done after the object creation */ + abuf->data = js_mallocz(ctx, max_int(len, 1)); + if (!abuf->data) + goto fail; + } + } else { + if (class_id == JS_CLASS_SHARED_ARRAY_BUFFER && + rt->sab_funcs.sab_dup) { + rt->sab_funcs.sab_dup(rt->sab_funcs.sab_opaque, buf); + } + abuf->data = buf; + } + init_list_head(&abuf->array_list); + abuf->detached = FALSE; + abuf->shared = (class_id == JS_CLASS_SHARED_ARRAY_BUFFER); + abuf->opaque = opaque; + abuf->free_func = free_func; + if (alloc_flag && buf) + memcpy(abuf->data, buf, len); + JS_SetOpaqueInternal(obj, abuf); + return obj; + fail: + JS_FreeValue(ctx, obj); + js_free(ctx, abuf); + return JS_EXCEPTION; +} + +static void js_array_buffer_free(JSRuntime *rt, void *opaque, void *ptr) +{ + js_free_rt(rt, ptr); +} + +static JSValue js_array_buffer_constructor2(JSContext *ctx, + JSValue new_target, + uint64_t len, uint64_t *max_len, + JSClassID class_id) +{ + return js_array_buffer_constructor3(ctx, new_target, len, max_len, + class_id, NULL, js_array_buffer_free, + NULL, TRUE); +} + +static JSValue js_array_buffer_constructor1(JSContext *ctx, + JSValue new_target, + uint64_t len, uint64_t *max_len) +{ + return js_array_buffer_constructor2(ctx, new_target, len, max_len, + JS_CLASS_ARRAY_BUFFER); +} + +JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len, + JSFreeArrayBufferDataFunc *free_func, void *opaque, + BOOL is_shared) +{ + JSClassID class_id = + is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER; + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, class_id, + buf, free_func, opaque, FALSE); +} + +JS_BOOL JS_IsArrayBuffer(JSValue obj) { + return JS_GetClassID(obj) == JS_CLASS_ARRAY_BUFFER; +} + +/* create a new ArrayBuffer of length 'len' and copy 'buf' to it */ +JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len) +{ + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, + JS_CLASS_ARRAY_BUFFER, + (uint8_t *)buf, + js_array_buffer_free, NULL, + TRUE); +} + +static JSValue js_array_buffer_constructor0(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv, + JSClassID class_id) +{ + uint64_t len, max_len, *pmax_len = NULL; + JSValue obj, val; + int64_t i; + + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + if (argc < 2) + goto next; + if (!JS_IsObject(argv[1])) + goto next; + obj = JS_ToObject(ctx, argv[1]); + if (JS_IsException(obj)) + return JS_EXCEPTION; + val = JS_GetProperty(ctx, obj, JS_ATOM_maxByteLength); + JS_FreeValue(ctx, obj); + if (JS_IsException(val)) + return JS_EXCEPTION; + if (JS_IsUndefined(val)) + goto next; + if (JS_ToInt64Free(ctx, &i, val)) + return JS_EXCEPTION; + // don't have to check i < 0 because len >= 0 + if (len > i || i > MAX_SAFE_INTEGER) + return JS_ThrowRangeError(ctx, "invalid array buffer max length"); + max_len = i; + pmax_len = &max_len; +next: + return js_array_buffer_constructor2(ctx, new_target, len, pmax_len, + class_id); +} + +static JSValue js_array_buffer_constructor(JSContext *ctx, JSValue new_target, + int argc, JSValue *argv) +{ + return js_array_buffer_constructor0(ctx, new_target, argc, argv, + JS_CLASS_ARRAY_BUFFER); +} + +static JSValue js_shared_array_buffer_constructor(JSContext *ctx, + JSValue new_target, + int argc, JSValue *argv) +{ + return js_array_buffer_constructor0(ctx, new_target, argc, argv, + JS_CLASS_SHARED_ARRAY_BUFFER); +} + +/* also used for SharedArrayBuffer */ +static void js_array_buffer_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSArrayBuffer *abuf = p->u.array_buffer; + struct list_head *el, *el1; + + if (abuf) { + /* The ArrayBuffer finalizer may be called before the typed + array finalizers using it, so abuf->array_list is not + necessarily empty. */ + list_for_each_safe(el, el1, &abuf->array_list) { + JSTypedArray *ta; + JSObject *p1; + + ta = list_entry(el, JSTypedArray, link); + ta->link.prev = NULL; + ta->link.next = NULL; + p1 = ta->obj; + /* Note: the typed array length and offset fields are not modified */ + if (p1->class_id != JS_CLASS_DATAVIEW) { + p1->u.array.count = 0; + p1->u.array.u.ptr = NULL; + } + } + if (abuf->shared && rt->sab_funcs.sab_free) { + rt->sab_funcs.sab_free(rt->sab_funcs.sab_opaque, abuf->data); + } else { + if (abuf->free_func) + abuf->free_func(rt, abuf->opaque, abuf->data); + } + js_free_rt(rt, abuf); + } +} + +static JSValue js_array_buffer_isView(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + BOOL res; + res = FALSE; + if (JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT) { + p = JS_VALUE_GET_OBJ(argv[0]); + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_DATAVIEW) { + res = TRUE; + } + } + return js_bool(res); +} + +static const JSCFunctionListEntry js_array_buffer_funcs[] = { + JS_CFUNC_DEF("isView", 1, js_array_buffer_isView ), + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static JSValue JS_ThrowTypeErrorDetachedArrayBuffer(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "ArrayBuffer is detached"); +} + +static JSValue JS_ThrowTypeErrorArrayBufferOOB(JSContext *ctx) +{ + return JS_ThrowTypeError(ctx, "ArrayBuffer is detached or resized"); +} + +// #sec-get-arraybuffer.prototype.detached +static JSValue js_array_buffer_get_detached(JSContext *ctx, + JSValue this_val) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER); + if (!abuf) + return JS_EXCEPTION; + if (abuf->shared) + return JS_ThrowTypeError(ctx, "detached called on SharedArrayBuffer"); + return js_bool(abuf->detached); +} + +static JSValue js_array_buffer_get_byteLength(JSContext *ctx, + JSValue this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + /* return 0 if detached */ + return js_uint32(abuf->byte_length); +} + +static JSValue js_array_buffer_get_maxByteLength(JSContext *ctx, + JSValue this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (array_buffer_is_resizable(abuf)) + return js_uint32(abuf->max_byte_length); + return js_uint32(abuf->byte_length); +} + +static JSValue js_array_buffer_get_resizable(JSContext *ctx, JSValue this_val, + int class_id) +{ + JSArrayBuffer *abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + return js_bool(array_buffer_is_resizable(abuf)); +} + +void JS_DetachArrayBuffer(JSContext *ctx, JSValue obj) +{ + JSArrayBuffer *abuf = JS_GetOpaque(obj, JS_CLASS_ARRAY_BUFFER); + struct list_head *el; + + if (!abuf || abuf->detached) + return; + if (abuf->free_func) + abuf->free_func(ctx->rt, abuf->opaque, abuf->data); + abuf->data = NULL; + abuf->byte_length = 0; + abuf->detached = TRUE; + + list_for_each(el, &abuf->array_list) { + JSTypedArray *ta; + JSObject *p; + + ta = list_entry(el, JSTypedArray, link); + p = ta->obj; + /* Note: the typed array length and offset fields are not modified */ + if (p->class_id != JS_CLASS_DATAVIEW) { + p->u.array.count = 0; + p->u.array.u.ptr = NULL; + } + } +} + +/* get an ArrayBuffer or SharedArrayBuffer */ +static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValue obj) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(obj); + if (p->class_id != JS_CLASS_ARRAY_BUFFER && + p->class_id != JS_CLASS_SHARED_ARRAY_BUFFER) { + fail: + JS_ThrowTypeErrorInvalidClass(ctx, JS_CLASS_ARRAY_BUFFER); + return NULL; + } + return p->u.array_buffer; +} + +/* return NULL if exception. WARNING: any JS call can detach the + buffer and render the returned pointer invalid */ +uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValue obj) +{ + JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj); + if (!abuf) + goto fail; + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } + *psize = abuf->byte_length; + return abuf->data; + fail: + *psize = 0; + return NULL; +} + +static BOOL array_buffer_is_resizable(const JSArrayBuffer *abuf) +{ + return abuf->max_byte_length >= 0; +} + +// ES #sec-arraybuffer.prototype.transfer +static JSValue js_array_buffer_transfer(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + BOOL transfer_to_fixed_length = magic & 1; + JSArrayBuffer *abuf; + uint64_t new_len, old_len, max_len, *pmax_len; + uint8_t *bs, *new_bs; + + abuf = JS_GetOpaque2(ctx, this_val, JS_CLASS_ARRAY_BUFFER); + if (!abuf) + return JS_EXCEPTION; + if (abuf->shared) + return JS_ThrowTypeError(ctx, "cannot transfer a SharedArrayBuffer"); + if (argc < 1 || JS_IsUndefined(argv[0])) + new_len = abuf->byte_length; + else if (JS_ToIndex(ctx, &new_len, argv[0])) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + pmax_len = NULL; + if (!transfer_to_fixed_length) { + if (array_buffer_is_resizable(abuf)) { // carry over maxByteLength + max_len = abuf->max_byte_length; + if (new_len > max_len) + return JS_ThrowTypeError(ctx, "invalid array buffer length"); + // TODO(bnoordhuis) support externally managed RABs + if (abuf->free_func == js_array_buffer_free) + pmax_len = &max_len; + } + } + /* create an empty AB */ + if (new_len == 0) { + JS_DetachArrayBuffer(ctx, this_val); + return js_array_buffer_constructor2(ctx, JS_UNDEFINED, 0, pmax_len, + JS_CLASS_ARRAY_BUFFER); + } + bs = abuf->data; + old_len = abuf->byte_length; + /* if length mismatch, realloc. Otherwise, use the same backing buffer. */ + if (new_len != old_len) { + new_bs = js_realloc(ctx, bs, new_len); + if (!new_bs) + return JS_EXCEPTION; + bs = new_bs; + if (new_len > old_len) + memset(bs + old_len, 0, new_len - old_len); + } + /* neuter the backing buffer */ + abuf->data = NULL; + abuf->byte_length = 0; + abuf->detached = TRUE; + return js_array_buffer_constructor3(ctx, JS_UNDEFINED, new_len, pmax_len, + JS_CLASS_ARRAY_BUFFER, + bs, abuf->free_func, + NULL, FALSE); +} + +static JSValue js_array_buffer_resize(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int class_id) +{ + uint32_t size_log2, size_elem; + struct list_head *el; + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSObject *p; + uint8_t *data; + int64_t len; + + abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (JS_ToInt64(ctx, &len, argv[0])) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (!array_buffer_is_resizable(abuf)) + return JS_ThrowTypeError(ctx, "array buffer is not resizable"); + // TODO(bnoordhuis) support externally managed RABs + if (abuf->free_func != js_array_buffer_free) + return JS_ThrowTypeError(ctx, "external array buffer is not resizable"); + if (len < 0 || len > abuf->max_byte_length) { + bad_length: + return JS_ThrowRangeError(ctx, "invalid array buffer length"); + } + // SABs can only grow and we don't need to realloc because + // js_array_buffer_constructor3 commits all memory upfront; + // regular RABs are resizable both ways and realloc + if (abuf->shared) { + if (len < abuf->byte_length) + goto bad_length; + // Note this is off-spec; there's supposed to be a single atomic + // |byteLength| property that's shared across SABs but we store + // it per SAB instead. That means when thread A calls sab.grow(2) + // at time t0, and thread B calls sab.grow(1) at time t1, we don't + // throw a TypeError in thread B as the spec says we should, + // instead both threads get their own view of the backing memory, + // 2 bytes big in A, and 1 byte big in B + abuf->byte_length = len; + } else { + data = js_realloc(ctx, abuf->data, max_int(len, 1)); + if (!data) + return JS_EXCEPTION; + if (len > abuf->byte_length) + memset(&data[abuf->byte_length], 0, len - abuf->byte_length); + abuf->byte_length = len; + abuf->data = data; + } + data = abuf->data; + // update lengths of all typed arrays backed by this array buffer + list_for_each(el, &abuf->array_list) { + ta = list_entry(el, JSTypedArray, link); + p = ta->obj; + if (p->class_id == JS_CLASS_DATAVIEW) + continue; + p->u.array.count = 0; + p->u.array.u.ptr = NULL; + size_log2 = typed_array_size_log2(p->class_id); + size_elem = 1 << size_log2; + if (ta->track_rab) { + if (len >= (int64_t)ta->offset + size_elem) { + p->u.array.count = (len - ta->offset) >> size_log2; + p->u.array.u.ptr = &data[ta->offset]; + } + } else { + if (len >= (int64_t)ta->offset + ta->length) { + p->u.array.count = ta->length >> size_log2; + p->u.array.u.ptr = &data[ta->offset]; + } + } + } + return JS_UNDEFINED; +} + +static JSValue js_array_buffer_slice(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv, int class_id) +{ + JSArrayBuffer *abuf, *new_abuf; + int64_t len, start, end, new_len; + JSValue ctor, new_obj; + + abuf = JS_GetOpaque2(ctx, this_val, class_id); + if (!abuf) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + len = abuf->byte_length; + + if (JS_ToInt64Clamp(ctx, &start, argv[0], 0, len, len)) + return JS_EXCEPTION; + + end = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt64Clamp(ctx, &end, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + new_len = max_int64(end - start, 0); + ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED); + if (JS_IsException(ctor)) + return ctor; + if (JS_IsUndefined(ctor)) { + new_obj = js_array_buffer_constructor2(ctx, JS_UNDEFINED, new_len, + NULL, class_id); + } else { + JSValue args[1]; + args[0] = js_int64(new_len); + new_obj = JS_CallConstructor(ctx, ctor, 1, args); + JS_FreeValue(ctx, ctor); + JS_FreeValue(ctx, args[0]); + } + if (JS_IsException(new_obj)) + return new_obj; + new_abuf = JS_GetOpaque2(ctx, new_obj, class_id); + if (!new_abuf) + goto fail; + if (js_same_value(ctx, new_obj, this_val)) { + JS_ThrowTypeError(ctx, "cannot use identical ArrayBuffer"); + goto fail; + } + if (new_abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } + if (new_abuf->byte_length < new_len) { + JS_ThrowTypeError(ctx, "new ArrayBuffer is too small"); + goto fail; + } + /* must test again because of side effects */ + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } + memcpy(new_abuf->data, abuf->data + start, new_len); + return new_obj; + fail: + JS_FreeValue(ctx, new_obj); + return JS_EXCEPTION; +} + +static const JSCFunctionListEntry js_array_buffer_proto_funcs[] = { + JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("maxByteLength", js_array_buffer_get_maxByteLength, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("resizable", js_array_buffer_get_resizable, NULL, JS_CLASS_ARRAY_BUFFER ), + JS_CGETSET_DEF("detached", js_array_buffer_get_detached, NULL ), + JS_CFUNC_MAGIC_DEF("resize", 1, js_array_buffer_resize, JS_CLASS_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("transfer", 0, js_array_buffer_transfer, 0 ), + JS_CFUNC_MAGIC_DEF("transferToFixedLength", 0, js_array_buffer_transfer, 1 ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "ArrayBuffer", JS_PROP_CONFIGURABLE ), +}; + +/* SharedArrayBuffer */ + +static const JSCFunctionListEntry js_shared_array_buffer_funcs[] = { + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static const JSCFunctionListEntry js_shared_array_buffer_proto_funcs[] = { + JS_CGETSET_MAGIC_DEF("byteLength", js_array_buffer_get_byteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("maxByteLength", js_array_buffer_get_maxByteLength, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CGETSET_MAGIC_DEF("growable", js_array_buffer_get_resizable, NULL, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("grow", 1, js_array_buffer_resize, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_CFUNC_MAGIC_DEF("slice", 2, js_array_buffer_slice, JS_CLASS_SHARED_ARRAY_BUFFER ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "SharedArrayBuffer", JS_PROP_CONFIGURABLE ), +}; + +// is the typed array detached or out of bounds relative to its RAB? +// |p| must be a typed array, *not* a DataView +static BOOL typed_array_is_oob(JSObject *p) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + int len, size_elem; + int64_t end; + + assert(p->class_id >= JS_CLASS_UINT8C_ARRAY); + assert(p->class_id <= JS_CLASS_FLOAT64_ARRAY); + + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return TRUE; + len = abuf->byte_length; + if (ta->offset > len) + return TRUE; + if (ta->track_rab) + return FALSE; + if (len < (int64_t)ta->offset + ta->length) + return TRUE; + size_elem = 1 << typed_array_size_log2(p->class_id); + end = (int64_t)ta->offset + (int64_t)p->u.array.count * size_elem; + return end > len; +} + +/* WARNING: 'p' must be a typed array. Works even if the array buffer + is detached */ +static uint32_t typed_array_get_length(JSContext *ctx, JSObject *p) +{ + JSTypedArray *ta = p->u.typed_array; + int size_log2 = typed_array_size_log2(p->class_id); + return ta->length >> size_log2; +} + +static int validate_typed_array(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + p = get_typed_array(ctx, this_val); + if (!p) + return -1; + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + return -1; + } + return 0; +} + +static JSValue js_typed_array_get_length(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + return js_int32(p->u.array.count); +} + +static JSValue js_typed_array_get_buffer(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + JSTypedArray *ta; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + ta = p->u.typed_array; + return js_dup(JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); +} + +static JSValue js_typed_array_get_byteLength(JSContext *ctx, JSValue this_val) +{ + uint32_t size_log2; + JSTypedArray *ta; + JSObject *p; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return js_int32(0); + ta = p->u.typed_array; + if (!ta->track_rab) + return js_uint32(ta->length); + size_log2 = typed_array_size_log2(p->class_id); + return js_int64((int64_t)p->u.array.count << size_log2); +} + +static JSValue js_typed_array_get_byteOffset(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + JSTypedArray *ta; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return js_int32(0); + ta = p->u.typed_array; + return js_uint32(ta->offset); +} + +JSValue JS_NewTypedArray(JSContext *ctx, int argc, JSValue *argv, + JSTypedArrayEnum type) +{ + if (type < JS_TYPED_ARRAY_UINT8C || type > JS_TYPED_ARRAY_FLOAT64) + return JS_ThrowRangeError(ctx, "invalid typed array type"); + + return js_typed_array_constructor(ctx, JS_UNDEFINED, argc, argv, + JS_CLASS_UINT8C_ARRAY + type); +} + +/* Return the buffer associated to the typed array or an exception if + it is not a typed array or if the buffer is detached. pbyte_offset, + pbyte_length or pbytes_per_element can be NULL. */ +JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValue obj, + size_t *pbyte_offset, + size_t *pbyte_length, + size_t *pbytes_per_element) +{ + JSObject *p; + JSTypedArray *ta; + p = get_typed_array(ctx, obj); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + if (pbyte_offset) + *pbyte_offset = ta->offset; + if (pbyte_length) + *pbyte_length = ta->length; + if (pbytes_per_element) { + *pbytes_per_element = 1 << typed_array_size_log2(p->class_id); + } + return js_dup(JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); +} + +/* return NULL if exception. WARNING: any JS call can detach the + buffer and render the returned pointer invalid */ +uint8_t *JS_GetUint8Array(JSContext *ctx, size_t *psize, JSValue obj) +{ + JSObject *p; + JSTypedArray *ta; + JSArrayBuffer *abuf; + p = get_typed_array(ctx, obj); + if (!p) + goto fail; + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + goto fail; + } + if (p->class_id != JS_CLASS_UINT8_ARRAY && p->class_id != JS_CLASS_UINT8C_ARRAY) { + JS_ThrowTypeError(ctx, "not a Uint8Array"); + goto fail; + } + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + + *psize = ta->length; + return abuf->data + ta->offset; + fail: + *psize = 0; + return NULL; +} + +static JSValue js_typed_array_get_toStringTag(JSContext *ctx, + JSValue this_val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + return JS_UNDEFINED; + p = JS_VALUE_GET_OBJ(this_val); + if (!(p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY)) + return JS_UNDEFINED; + return JS_AtomToString(ctx, ctx->rt->class_array[p->class_id].class_name); +} + +static JSValue js_typed_array_set_internal(JSContext *ctx, + JSValue dst, + JSValue src, + JSValue off) +{ + JSObject *p; + JSObject *src_p; + uint32_t i; + int64_t dst_len, src_len, offset; + JSValue val, src_obj = JS_UNDEFINED; + + p = get_typed_array(ctx, dst); + if (!p) + goto fail; + if (JS_ToInt64Sat(ctx, &offset, off)) + goto fail; + if (offset < 0) + goto range_error; + if (typed_array_is_oob(p)) { + detached: + JS_ThrowTypeErrorArrayBufferOOB(ctx); + goto fail; + } + dst_len = p->u.array.count; + src_obj = JS_ToObject(ctx, src); + if (JS_IsException(src_obj)) + goto fail; + src_p = JS_VALUE_GET_OBJ(src_obj); + if (src_p->class_id >= JS_CLASS_UINT8C_ARRAY && + src_p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + JSTypedArray *dest_ta = p->u.typed_array; + JSArrayBuffer *dest_abuf = dest_ta->buffer->u.array_buffer; + JSTypedArray *src_ta = src_p->u.typed_array; + JSArrayBuffer *src_abuf = src_ta->buffer->u.array_buffer; + int shift = typed_array_size_log2(p->class_id); + + if (typed_array_is_oob(src_p)) + goto detached; + + src_len = src_p->u.array.count; + if (offset > dst_len - src_len) + goto range_error; + + /* copying between typed objects */ + if (src_p->class_id == p->class_id) { + /* same type, use memmove */ + memmove(dest_abuf->data + dest_ta->offset + (offset << shift), + src_abuf->data + src_ta->offset, src_len << shift); + goto done; + } + if (dest_abuf->data == src_abuf->data) { + /* copying between the same buffer using different types of mappings + would require a temporary buffer */ + } + /* otherwise, default behavior is slow but correct */ + } else { + // can change |dst| as a side effect; per spec, + // perform the range check against its old length + if (js_get_length64(ctx, &src_len, src_obj)) + goto fail; + if (offset > dst_len - src_len) { + range_error: + JS_ThrowRangeError(ctx, "invalid array length"); + goto fail; + } + } + for(i = 0; i < src_len; i++) { + val = JS_GetPropertyUint32(ctx, src_obj, i); + if (JS_IsException(val)) + goto fail; + // Per spec: detaching the TA mid-iteration is allowed and should + // not throw an exception. Because iteration over the source array is + // observable, we cannot bail out early when the TA is first detached. + if (typed_array_is_oob(p)) { + JS_FreeValue(ctx, val); + } else if (JS_SetPropertyUint32(ctx, dst, offset + i, val) < 0) { + goto fail; + } + } +done: + JS_FreeValue(ctx, src_obj); + return JS_UNDEFINED; +fail: + JS_FreeValue(ctx, src_obj); + return JS_EXCEPTION; +} + +static JSValue js_typed_array_at(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int64_t idx, len; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; + + // note: can change p->u.array.count + if (JS_ToInt64Sat(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + if (idx < 0) + idx = len + idx; + + if (idx < 0 || idx >= p->u.array.count) + return JS_UNDEFINED; + + switch (p->class_id) { + case JS_CLASS_INT8_ARRAY: + return js_int32(p->u.array.u.int8_ptr[idx]); + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + return js_int32(p->u.array.u.uint8_ptr[idx]); + case JS_CLASS_INT16_ARRAY: + return js_int32(p->u.array.u.int16_ptr[idx]); + case JS_CLASS_UINT16_ARRAY: + return js_int32(p->u.array.u.uint16_ptr[idx]); + case JS_CLASS_INT32_ARRAY: + return js_int32(p->u.array.u.int32_ptr[idx]); + case JS_CLASS_UINT32_ARRAY: + return js_uint32(p->u.array.u.uint32_ptr[idx]); + case JS_CLASS_FLOAT16_ARRAY: + return js_float64(fromfp16(p->u.array.u.fp16_ptr[idx])); + case JS_CLASS_FLOAT32_ARRAY: + return js_float64(p->u.array.u.float_ptr[idx]); + case JS_CLASS_FLOAT64_ARRAY: + return js_float64(p->u.array.u.double_ptr[idx]); + case JS_CLASS_BIG_INT64_ARRAY: + return JS_NewBigInt64(ctx, p->u.array.u.int64_ptr[idx]); + case JS_CLASS_BIG_UINT64_ARRAY: + return JS_NewBigUint64(ctx, p->u.array.u.uint64_ptr[idx]); + } + + abort(); /* unreachable */ + return JS_UNDEFINED; +} + +static JSValue js_typed_array_with(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, val; + JSObject *p; + int64_t idx; + uint32_t len, oldlen, newlen; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + + oldlen = p->u.array.count; + if (JS_ToInt64Sat(ctx, &idx, argv[0])) + return JS_EXCEPTION; + + val = JS_ToPrimitive(ctx, argv[1], HINT_NUMBER); + if (JS_IsException(val)) + return JS_EXCEPTION; + + newlen = p->u.array.count; + if (idx < 0) + idx = newlen + idx; + if (idx < 0 || idx >= newlen) { + JS_FreeValue(ctx, val); + return JS_ThrowRangeError(ctx, "invalid array index"); + } + + len = min_uint32(oldlen, newlen); + arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, + p->class_id, len); + if (JS_IsException(arr)) { + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + if (idx < len && JS_SetPropertyInt64(ctx, arr, idx, val) < 0) { + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; + } + return arr; +} + +static JSValue js_typed_array_set(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv) +{ + JSValue offset = JS_UNDEFINED; + if (argc > 1) { + offset = argv[1]; + } + return js_typed_array_set_internal(ctx, this_val, argv[0], offset); +} + +static JSValue js_create_typed_array_iterator(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int magic) +{ + if (validate_typed_array(ctx, this_val)) + return JS_EXCEPTION; + return js_create_array_iterator(ctx, this_val, argc, argv, magic); +} + +static JSValue js_typed_array_create(JSContext *ctx, JSValue ctor, + int argc, JSValue *argv) +{ + JSValue ret; + int new_len; + int64_t len; + + ret = JS_CallConstructor(ctx, ctor, argc, argv); + if (JS_IsException(ret)) + return ret; + /* validate the typed array */ + new_len = js_typed_array_get_length_unsafe(ctx, ret); + if (new_len < 0) + goto fail; + if (argc == 1) { + /* ensure that it is large enough */ + if (JS_ToLengthFree(ctx, &len, js_dup(argv[0]))) + goto fail; + if (new_len < len) { + JS_ThrowTypeError(ctx, "TypedArray length is too small"); + fail: + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; + } + } + return ret; +} + +static JSValue js_typed_array___speciesCreate(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSObject *p; + JSValue ctor, ret; + int argc1; + + obj = argv[0]; + p = get_typed_array(ctx, obj); + if (!p) + return JS_EXCEPTION; + ctor = JS_SpeciesConstructor(ctx, obj, JS_UNDEFINED); + if (JS_IsException(ctor)) + return ctor; + argc1 = max_int(argc - 1, 0); + if (JS_IsUndefined(ctor)) { + ret = js_typed_array_constructor(ctx, JS_UNDEFINED, argc1, argv + 1, + p->class_id); + } else { + ret = js_typed_array_create(ctx, ctor, argc1, argv + 1); + JS_FreeValue(ctx, ctor); + } + return ret; +} + +static JSValue js_typed_array_from(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + // from(items, mapfn = void 0, this_arg = void 0) + JSValue items = argv[0], mapfn, this_arg; + JSValue args[2]; + JSValue stack[2]; + JSValue iter, arr, r, v, v2; + int64_t k, len; + int done, mapping; + + mapping = FALSE; + mapfn = JS_UNDEFINED; + this_arg = JS_UNDEFINED; + r = JS_UNDEFINED; + arr = JS_UNDEFINED; + stack[0] = JS_UNDEFINED; + stack[1] = JS_UNDEFINED; + + if (argc > 1) { + mapfn = argv[1]; + if (!JS_IsUndefined(mapfn)) { + if (check_function(ctx, mapfn)) + goto exception; + mapping = 1; + if (argc > 2) + this_arg = argv[2]; + } + } + iter = JS_GetProperty(ctx, items, JS_ATOM_Symbol_iterator); + if (JS_IsException(iter)) + goto exception; + if (!JS_IsUndefined(iter)) { + JS_FreeValue(ctx, iter); + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + goto exception; + stack[0] = js_dup(items); + if (js_for_of_start(ctx, &stack[1], FALSE)) + goto exception; + for (k = 0;; k++) { + v = JS_IteratorNext(ctx, stack[0], stack[1], 0, NULL, &done); + if (JS_IsException(v)) + goto exception_close; + if (done) + break; + if (JS_DefinePropertyValueInt64(ctx, arr, k, v, JS_PROP_C_W_E | JS_PROP_THROW) < 0) + goto exception_close; + } + } else { + arr = JS_ToObject(ctx, items); + if (JS_IsException(arr)) + goto exception; + } + if (js_get_length64(ctx, &len, arr) < 0) + goto exception; + v = js_int64(len); + args[0] = v; + r = js_typed_array_create(ctx, this_val, 1, args); + JS_FreeValue(ctx, v); + if (JS_IsException(r)) + goto exception; + for(k = 0; k < len; k++) { + v = JS_GetPropertyInt64(ctx, arr, k); + if (JS_IsException(v)) + goto exception; + if (mapping) { + args[0] = v; + args[1] = js_int32(k); + v2 = JS_Call(ctx, mapfn, this_arg, 2, args); + JS_FreeValue(ctx, v); + v = v2; + if (JS_IsException(v)) + goto exception; + } + if (JS_SetPropertyInt64(ctx, r, k, v) < 0) + goto exception; + } + goto done; + + exception_close: + if (!JS_IsUndefined(stack[0])) + JS_IteratorClose(ctx, stack[0], TRUE); + exception: + JS_FreeValue(ctx, r); + r = JS_EXCEPTION; + done: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, stack[0]); + JS_FreeValue(ctx, stack[1]); + return r; +} + +static JSValue js_typed_array_of(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue obj; + JSValue args[1]; + int i; + + args[0] = js_int32(argc); + obj = js_typed_array_create(ctx, this_val, 1, args); + if (JS_IsException(obj)) + return obj; + + for(i = 0; i < argc; i++) { + if (JS_SetPropertyUint32(ctx, obj, i, js_dup(argv[i])) < 0) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + } + return obj; +} + +static JSValue js_typed_array_copyWithin(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int len, to, from, final, count, shift, space; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; + + if (JS_ToInt32Clamp(ctx, &to, argv[0], 0, len, len)) + return JS_EXCEPTION; + + if (JS_ToInt32Clamp(ctx, &from, argv[1], 0, len, len)) + return JS_EXCEPTION; + + final = len; + if (argc > 2 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len)) + return JS_EXCEPTION; + } + + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + + // RAB may have been resized by evil .valueOf method + space = p->u.array.count - max_int(to, from); + count = min_int(final - from, len - to); + count = min_int(count, space); + if (count > 0) { + shift = typed_array_size_log2(p->class_id); + memmove(p->u.array.u.uint8_ptr + (to << shift), + p->u.array.u.uint8_ptr + (from << shift), + count << shift); + } + return js_dup(this_val); +} + +static JSValue js_typed_array_fill(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int len, k, final, shift; + uint64_t v64; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; + + if (p->class_id == JS_CLASS_UINT8C_ARRAY) { + int32_t v; + if (JS_ToUint8ClampFree(ctx, &v, js_dup(argv[0]))) + return JS_EXCEPTION; + v64 = v; + } else if (p->class_id <= JS_CLASS_UINT32_ARRAY) { + uint32_t v; + if (JS_ToUint32(ctx, &v, argv[0])) + return JS_EXCEPTION; + v64 = v; + } else + if (p->class_id <= JS_CLASS_BIG_UINT64_ARRAY) { + if (JS_ToBigInt64(ctx, (int64_t *)&v64, argv[0])) + return JS_EXCEPTION; + } else { + double d; + if (JS_ToFloat64(ctx, &d, argv[0])) + return JS_EXCEPTION; + if (p->class_id == JS_CLASS_FLOAT16_ARRAY) { + v64 = tofp16(d); + } else if (p->class_id == JS_CLASS_FLOAT32_ARRAY) { + union { + float f; + uint32_t u32; + } u; + u.f = d; + v64 = u.u32; + } else { + JSFloat64Union u; + u.d = d; + v64 = u.u64; + } + } + + k = 0; + if (argc > 1) { + if (JS_ToInt32Clamp(ctx, &k, argv[1], 0, len, len)) + return JS_EXCEPTION; + } + + final = len; + if (argc > 2 && !JS_IsUndefined(argv[2])) { + if (JS_ToInt32Clamp(ctx, &final, argv[2], 0, len, len)) + return JS_EXCEPTION; + } + + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + + // RAB may have been resized by evil .valueOf method + final = min_int(final, p->u.array.count); + shift = typed_array_size_log2(p->class_id); + switch(shift) { + case 0: + if (k < final) { + memset(p->u.array.u.uint8_ptr + k, v64, final - k); + } + break; + case 1: + for(; k < final; k++) { + p->u.array.u.uint16_ptr[k] = v64; + } + break; + case 2: + for(; k < final; k++) { + p->u.array.u.uint32_ptr[k] = v64; + } + break; + case 3: + for(; k < final; k++) { + p->u.array.u.uint64_ptr[k] = v64; + } + break; + default: + abort(); + } + return js_dup(this_val); +} + +static JSValue js_typed_array_find(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int mode) +{ + JSValue func, this_arg; + JSValue args[3]; + JSValue val, index_val, res; + int len, k, end; + int dir; + + val = JS_UNDEFINED; + len = js_typed_array_get_length_unsafe(ctx, this_val); + if (len < 0) + goto exception; + + func = argv[0]; + if (check_function(ctx, func)) + goto exception; + + this_arg = JS_UNDEFINED; + if (argc > 1) + this_arg = argv[1]; + + k = 0; + dir = 1; + end = len; + if (mode == ArrayFindLast || mode == ArrayFindLastIndex) { + k = len - 1; + dir = -1; + end = -1; + } + + for(; k != end; k += dir) { + index_val = js_int32(k); + val = JS_GetPropertyValue(ctx, this_val, index_val); + if (JS_IsException(val)) + goto exception; + args[0] = val; + args[1] = index_val; + args[2] = this_val; + res = JS_Call(ctx, func, this_arg, 3, args); + if (JS_IsException(res)) + goto exception; + if (JS_ToBoolFree(ctx, res)) { + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) { + JS_FreeValue(ctx, val); + return index_val; + } else { + return val; + } + } + JS_FreeValue(ctx, val); + } + if (mode == ArrayFindIndex || mode == ArrayFindLastIndex) + return js_int32(-1); + else + return JS_UNDEFINED; + +exception: + JS_FreeValue(ctx, val); + return JS_EXCEPTION; +} + +#define special_indexOf 0 +#define special_lastIndexOf 1 +#define special_includes -1 + +static JSValue js_typed_array_indexOf(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int special) +{ + JSObject *p; + int len, tag, is_int, is_bigint, k, stop, inc, res = -1; + int64_t v64; + double d; + float f; + uint16_t hf; + BOOL oob; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; + + if (len == 0) + goto done; + + oob = FALSE; + if (special == special_lastIndexOf) { + k = len - 1; + if (argc > 1) { + if (JS_ToFloat64(ctx, &d, argv[1])) + goto exception; + if (isnan(d)) { + k = 0; + } else { + if (d >= 0) { + if (d < k) { + k = d; + } + } else { + d += len; + if (d < 0) + goto done; + k = d; + } + } + } + stop = -1; + inc = -1; + } else { + k = 0; + if (argc > 1) { + if (JS_ToInt32Sat(ctx, &k, argv[1])) + goto exception; + if (k < 0) { + k += len; + if (k < 0) + k = 0; + } else if (k > len) { + k = len; + oob = TRUE; + } + } + stop = len; + inc = 1; + } + + /* if the array was detached, no need to go further (but no + exception is raised) */ + if (typed_array_is_oob(p) || len > p->u.array.count) { + /* "includes" scans all the properties, so "undefined" can match */ + if (special == special_includes && JS_IsUndefined(argv[0]) && len > 0) + res = oob ? -1 : 0; + goto done; + } + + // RAB may have been resized by evil .valueOf method + len = min_int(len, p->u.array.count); + if (len == 0) + goto done; + k = min_int(k, len); + stop = min_int(stop, len); + + is_bigint = 0; + is_int = 0; /* avoid warning */ + v64 = 0; /* avoid warning */ + tag = JS_VALUE_GET_NORM_TAG(argv[0]); + if (tag == JS_TAG_INT) { + is_int = 1; + v64 = JS_VALUE_GET_INT(argv[0]); + d = v64; + } else + if (tag == JS_TAG_FLOAT64) { + d = JS_VALUE_GET_FLOAT64(argv[0]); + if (d >= INT64_MIN && d < 0x1p63) { + v64 = d; + is_int = (v64 == d); + } + } else + if (tag == JS_TAG_BIG_INT) { + JSBigInt *p1 = JS_VALUE_GET_PTR(argv[0]); + + if (p->class_id == JS_CLASS_BIG_INT64_ARRAY) { + if (bf_get_int64(&v64, &p1->num, 0) != 0) + goto done; + } else if (p->class_id == JS_CLASS_BIG_UINT64_ARRAY) { + if (bf_get_uint64((uint64_t *)&v64, &p1->num) != 0) + goto done; + } else { + goto done; + } + d = 0; + is_bigint = 1; + } else { + goto done; + } + + switch (p->class_id) { + case JS_CLASS_INT8_ARRAY: + if (is_int && (int8_t)v64 == v64) + goto scan8; + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + if (is_int && (uint8_t)v64 == v64) { + const uint8_t *pv, *pp; + uint16_t v; + scan8: + pv = p->u.array.u.uint8_ptr; + v = v64; + if (inc > 0) { + pp = NULL; + if (pv) + pp = memchr(pv + k, v, len - k); + if (pp) + res = pp - pv; + } else { + for (; k != stop; k += inc) { + if (pv[k] == v) { + res = k; + break; + } + } + } + } + break; + case JS_CLASS_INT16_ARRAY: + if (is_int && (int16_t)v64 == v64) + goto scan16; + break; + case JS_CLASS_UINT16_ARRAY: + if (is_int && (uint16_t)v64 == v64) { + const uint16_t *pv; + uint16_t v; + scan16: + pv = p->u.array.u.uint16_ptr; + v = v64; + for (; k != stop; k += inc) { + if (pv[k] == v) { + res = k; + break; + } + } + } + break; + case JS_CLASS_INT32_ARRAY: + if (is_int && (int32_t)v64 == v64) + goto scan32; + break; + case JS_CLASS_UINT32_ARRAY: + if (is_int && (uint32_t)v64 == v64) { + const uint32_t *pv; + uint32_t v; + scan32: + pv = p->u.array.u.uint32_ptr; + v = v64; + for (; k != stop; k += inc) { + if (pv[k] == v) { + res = k; + break; + } + } + } + break; + case JS_CLASS_FLOAT16_ARRAY: + if (is_bigint) + break; + if (isnan(d)) { + const uint16_t *pv = p->u.array.u.fp16_ptr; + /* special case: indexOf returns -1, includes finds NaN */ + if (special != special_includes) + goto done; + for (; k != stop; k += inc) { + if (isfp16nan(pv[k])) { + res = k; + break; + } + } + } else if (d == 0) { + // special case: includes also finds negative zero + const uint16_t *pv = p->u.array.u.fp16_ptr; + for (; k != stop; k += inc) { + if (isfp16zero(pv[k])) { + res = k; + break; + } + } + } else if (hf = tofp16(d), d == fromfp16(hf)) { + const uint16_t *pv = p->u.array.u.fp16_ptr; + for (; k != stop; k += inc) { + if (pv[k] == hf) { + res = k; + break; + } + } + } + break; + case JS_CLASS_FLOAT32_ARRAY: + if (is_bigint) + break; + if (isnan(d)) { + const float *pv = p->u.array.u.float_ptr; + /* special case: indexOf returns -1, includes finds NaN */ + if (special != special_includes) + goto done; + for (; k != stop; k += inc) { + if (isnan(pv[k])) { + res = k; + break; + } + } + } else if ((f = (float)d) == d) { + const float *pv = p->u.array.u.float_ptr; + for (; k != stop; k += inc) { + if (pv[k] == f) { + res = k; + break; + } + } + } + break; + case JS_CLASS_FLOAT64_ARRAY: + if (is_bigint) + break; + if (isnan(d)) { + const double *pv = p->u.array.u.double_ptr; + /* special case: indexOf returns -1, includes finds NaN */ + if (special != special_includes) + goto done; + for (; k != stop; k += inc) { + if (isnan(pv[k])) { + res = k; + break; + } + } + } else { + const double *pv = p->u.array.u.double_ptr; + for (; k != stop; k += inc) { + if (pv[k] == d) { + res = k; + break; + } + } + } + break; + case JS_CLASS_BIG_INT64_ARRAY: + if (is_bigint) { + goto scan64; + } + break; + case JS_CLASS_BIG_UINT64_ARRAY: + if (is_bigint) { + const uint64_t *pv; + uint64_t v; + scan64: + pv = p->u.array.u.uint64_ptr; + v = v64; + for (; k != stop; k += inc) { + if (pv[k] == v) { + res = k; + break; + } + } + } + break; + } + +done: + if (special == special_includes) + return js_bool(res >= 0); + else + return js_int32(res); + +exception: + return JS_EXCEPTION; +} + +static JSValue js_typed_array_join(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int toLocaleString) +{ + JSValue sep = JS_UNDEFINED, el; + StringBuffer b_s, *b = &b_s; + JSString *s = NULL; + JSObject *p; + int i, len, oldlen, newlen; + int c; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = oldlen = newlen = p->u.array.count; + + c = ','; /* default separator */ + if (!toLocaleString && argc > 0 && !JS_IsUndefined(argv[0])) { + sep = JS_ToString(ctx, argv[0]); + if (JS_IsException(sep)) + goto exception; + s = JS_VALUE_GET_STRING(sep); + if (s->len == 1 && !s->is_wide_char) + c = s->u.str8[0]; + else + c = -1; + // ToString(sep) can detach or resize the arraybuffer as a side effect + newlen = p->u.array.count; + len = min_int(len, newlen); + } + string_buffer_init(ctx, b, 0); + + /* XXX: optimize with direct access */ + for(i = 0; i < len; i++) { + if (i > 0) { + if (c >= 0) { + if (string_buffer_putc8(b, c)) + goto fail; + } else { + if (string_buffer_concat(b, s, 0, s->len)) + goto fail; + } + } + el = JS_GetPropertyUint32(ctx, this_val, i); + /* Can return undefined for example if the typed array is detached */ + if (!JS_IsNull(el) && !JS_IsUndefined(el)) { + if (JS_IsException(el)) + goto fail; + if (toLocaleString) { + el = JS_ToLocaleStringFree(ctx, el); + } + if (string_buffer_concat_value_free(b, el)) + goto fail; + } + } + + // add extra separators in case RAB was resized by evil .valueOf method + i = max_int(1, newlen); + for(/*empty*/; i < oldlen; i++) { + if (c >= 0) { + if (string_buffer_putc8(b, c)) + goto fail; + } else { + if (string_buffer_concat(b, s, 0, s->len)) + goto fail; + } + } + + JS_FreeValue(ctx, sep); + return string_buffer_end(b); + +fail: + string_buffer_free(b); + JS_FreeValue(ctx, sep); +exception: + return JS_EXCEPTION; +} + +static JSValue js_typed_array_reverse(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int len; + + len = js_typed_array_get_length_unsafe(ctx, this_val); + if (len < 0) + return JS_EXCEPTION; + if (len > 0) { + p = JS_VALUE_GET_OBJ(this_val); + switch (typed_array_size_log2(p->class_id)) { + case 0: + { + uint8_t *p1 = p->u.array.u.uint8_ptr; + uint8_t *p2 = p1 + len - 1; + while (p1 < p2) { + uint8_t v = *p1; + *p1++ = *p2; + *p2-- = v; + } + } + break; + case 1: + { + uint16_t *p1 = p->u.array.u.uint16_ptr; + uint16_t *p2 = p1 + len - 1; + while (p1 < p2) { + uint16_t v = *p1; + *p1++ = *p2; + *p2-- = v; + } + } + break; + case 2: + { + uint32_t *p1 = p->u.array.u.uint32_ptr; + uint32_t *p2 = p1 + len - 1; + while (p1 < p2) { + uint32_t v = *p1; + *p1++ = *p2; + *p2-- = v; + } + } + break; + case 3: + { + uint64_t *p1 = p->u.array.u.uint64_ptr; + uint64_t *p2 = p1 + len - 1; + while (p1 < p2) { + uint64_t v = *p1; + *p1++ = *p2; + *p2-- = v; + } + } + break; + default: + abort(); + } + } + return js_dup(this_val); +} + +static JSValue js_typed_array_toReversed(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, ret; + JSObject *p; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, + p->class_id, p->u.array.count); + if (JS_IsException(arr)) + return JS_EXCEPTION; + ret = js_typed_array_reverse(ctx, arr, argc, argv); + JS_FreeValue(ctx, arr); + return ret; +} + +static JSValue js_typed_array_slice(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue args[2]; + JSValue arr, val; + JSObject *p, *p1; + int n, len, start, final, count, shift, space; + + arr = JS_UNDEFINED; + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + len = p->u.array.count; + + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + goto exception; + final = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) + goto exception; + } + count = max_int(final - start, 0); + + args[0] = this_val; + args[1] = js_int32(count); + arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 2, args); + if (JS_IsException(arr)) + goto exception; + + if (count > 0) { + if (validate_typed_array(ctx, this_val) + || validate_typed_array(ctx, arr)) + goto exception; + + if (len != p->u.array.count) + goto slow_path; + + p1 = get_typed_array(ctx, arr); + if (p1 != NULL && p->class_id == p1->class_id && + typed_array_get_length(ctx, p1) >= count && + typed_array_get_length(ctx, p) >= start + count) { + shift = typed_array_size_log2(p->class_id); + memmove(p1->u.array.u.uint8_ptr, + p->u.array.u.uint8_ptr + (start << shift), + count << shift); + } else { + slow_path: + space = max_int(0, p->u.array.count - start); + count = min_int(count, space); + for (n = 0; n < count; n++) { + val = JS_GetPropertyValue(ctx, this_val, js_int32(start + n)); + if (JS_IsException(val)) + goto exception; + if (JS_SetPropertyValue(ctx, arr, js_int32(n), val, + JS_PROP_THROW) < 0) + goto exception; + } + } + } + return arr; + + exception: + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; +} + +static JSValue js_typed_array_subarray(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSValue args[4]; + JSValue arr, byteOffset, ta_buffer; + JSObject *p; + int len, start, final, count, shift, offset; + + p = get_typed_array(ctx, this_val); + if (!p) + goto exception; + len = p->u.array.count; + if (JS_ToInt32Clamp(ctx, &start, argv[0], 0, len, len)) + goto exception; + final = len; + if (!JS_IsUndefined(argv[1])) { + if (JS_ToInt32Clamp(ctx, &final, argv[1], 0, len, len)) + goto exception; + } + count = max_int(final - start, 0); + byteOffset = js_typed_array_get_byteOffset(ctx, this_val); + if (JS_IsException(byteOffset)) + goto exception; + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (ta->offset > abuf->byte_length) + goto range_error; + if (ta->offset == abuf->byte_length && count > 0) { + range_error: + JS_ThrowRangeError(ctx, "invalid offset"); + goto exception; + } + shift = typed_array_size_log2(p->class_id); + offset = JS_VALUE_GET_INT(byteOffset) + (start << shift); + JS_FreeValue(ctx, byteOffset); + ta_buffer = js_typed_array_get_buffer(ctx, this_val); + if (JS_IsException(ta_buffer)) + goto exception; + args[0] = this_val; + args[1] = ta_buffer; + args[2] = js_int32(offset); + args[3] = js_int32(count); + // result is length-tracking if source TA is and no explicit count is given + if (ta->track_rab && JS_IsUndefined(argv[1])) + args[3] = JS_UNDEFINED; + arr = js_typed_array___speciesCreate(ctx, JS_UNDEFINED, 4, args); + JS_FreeValue(ctx, ta_buffer); + return arr; + exception: + return JS_EXCEPTION; +} + +/* TypedArray.prototype.sort */ + +static int js_cmp_doubles(double x, double y) +{ + if (isnan(x)) return isnan(y) ? 0 : +1; + if (isnan(y)) return -1; + if (x < y) return -1; + if (x > y) return 1; + if (x != 0) return 0; + if (signbit(x)) return signbit(y) ? 0 : -1; + else return signbit(y) ? 1 : 0; +} + +static int js_TA_cmp_int8(const void *a, const void *b, void *opaque) { + return *(const int8_t *)a - *(const int8_t *)b; +} + +static int js_TA_cmp_uint8(const void *a, const void *b, void *opaque) { + return *(const uint8_t *)a - *(const uint8_t *)b; +} + +static int js_TA_cmp_int16(const void *a, const void *b, void *opaque) { + return *(const int16_t *)a - *(const int16_t *)b; +} + +static int js_TA_cmp_uint16(const void *a, const void *b, void *opaque) { + return *(const uint16_t *)a - *(const uint16_t *)b; +} + +static int js_TA_cmp_int32(const void *a, const void *b, void *opaque) { + int32_t x = *(const int32_t *)a; + int32_t y = *(const int32_t *)b; + return (y < x) - (y > x); +} + +static int js_TA_cmp_uint32(const void *a, const void *b, void *opaque) { + uint32_t x = *(const uint32_t *)a; + uint32_t y = *(const uint32_t *)b; + return (y < x) - (y > x); +} + +static int js_TA_cmp_int64(const void *a, const void *b, void *opaque) { + int64_t x = *(const int64_t *)a; + int64_t y = *(const int64_t *)b; + return (y < x) - (y > x); +} + +static int js_TA_cmp_uint64(const void *a, const void *b, void *opaque) { + uint64_t x = *(const uint64_t *)a; + uint64_t y = *(const uint64_t *)b; + return (y < x) - (y > x); +} + +static int js_TA_cmp_float16(const void *a, const void *b, void *opaque) { + return js_cmp_doubles(fromfp16(*(const uint16_t *)a), + fromfp16(*(const uint16_t *)b)); +} + +static int js_TA_cmp_float32(const void *a, const void *b, void *opaque) { + return js_cmp_doubles(*(const float *)a, *(const float *)b); +} + +static int js_TA_cmp_float64(const void *a, const void *b, void *opaque) { + return js_cmp_doubles(*(const double *)a, *(const double *)b); +} + +static JSValue js_TA_get_int8(JSContext *ctx, const void *a) { + return js_int32(*(const int8_t *)a); +} + +static JSValue js_TA_get_uint8(JSContext *ctx, const void *a) { + return js_int32(*(const uint8_t *)a); +} + +static JSValue js_TA_get_int16(JSContext *ctx, const void *a) { + return js_int32(*(const int16_t *)a); +} + +static JSValue js_TA_get_uint16(JSContext *ctx, const void *a) { + return js_int32(*(const uint16_t *)a); +} + +static JSValue js_TA_get_int32(JSContext *ctx, const void *a) { + return js_int32(*(const int32_t *)a); +} + +static JSValue js_TA_get_uint32(JSContext *ctx, const void *a) { + return js_uint32(*(const uint32_t *)a); +} + +static JSValue js_TA_get_int64(JSContext *ctx, const void *a) { + return JS_NewBigInt64(ctx, *(int64_t *)a); +} + +static JSValue js_TA_get_uint64(JSContext *ctx, const void *a) { + return JS_NewBigUint64(ctx, *(uint64_t *)a); +} + +static JSValue js_TA_get_float16(JSContext *ctx, const void *a) { + return js_float64(fromfp16(*(const uint16_t *)a)); +} + +static JSValue js_TA_get_float32(JSContext *ctx, const void *a) { + return js_float64(*(const float *)a); +} + +static JSValue js_TA_get_float64(JSContext *ctx, const void *a) { + return js_float64(*(const double *)a); +} + +struct TA_sort_context { + JSContext *ctx; + int exception; + JSValue arr; + JSValue cmp; + JSValue (*getfun)(JSContext *ctx, const void *a); + int elt_size; +}; + +static int js_TA_cmp_generic(const void *a, const void *b, void *opaque) { + struct TA_sort_context *psc = opaque; + JSContext *ctx = psc->ctx; + uint32_t a_idx, b_idx; + JSValue argv[2]; + JSValue res; + JSObject *p; + int cmp; + + p = JS_VALUE_GET_OBJ(psc->arr); + if (typed_array_is_oob(p)) + return 0; + + cmp = 0; + if (!psc->exception) { + a_idx = *(uint32_t *)a; + b_idx = *(uint32_t *)b; + if (a_idx >= p->u.array.count || b_idx >= p->u.array.count) + return 0; + argv[0] = psc->getfun(ctx, (char *)p->u.array.u.ptr + + a_idx * (size_t)psc->elt_size); + argv[1] = psc->getfun(ctx, (char *)p->u.array.u.ptr + + b_idx * (size_t)(psc->elt_size)); + res = JS_Call(ctx, psc->cmp, JS_UNDEFINED, 2, argv); + if (JS_IsException(res)) { + psc->exception = 1; + goto done; + } + if (JS_VALUE_GET_TAG(res) == JS_TAG_INT) { + int val = JS_VALUE_GET_INT(res); + cmp = (val > 0) - (val < 0); + } else { + double val; + if (JS_ToFloat64Free(ctx, &val, res) < 0) { + psc->exception = 1; + goto done; + } else { + cmp = (val > 0) - (val < 0); + } + } + if (cmp == 0) { + /* make sort stable: compare array offsets */ + cmp = (a_idx > b_idx) - (a_idx < b_idx); + } + done: + JS_FreeValue(ctx, argv[0]); + JS_FreeValue(ctx, argv[1]); + } + return cmp; +} + +static JSValue js_typed_array_sort(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSObject *p; + int len; + size_t elt_size; + struct TA_sort_context tsc; + int (*cmpfun)(const void *a, const void *b, void *opaque); + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (typed_array_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + + tsc.ctx = ctx; + tsc.exception = 0; + tsc.arr = this_val; + tsc.cmp = argv[0]; + + if (!JS_IsUndefined(tsc.cmp) && check_function(ctx, tsc.cmp)) + return JS_EXCEPTION; + + len = p->u.array.count; + if (len > 1) { + switch (p->class_id) { + case JS_CLASS_INT8_ARRAY: + tsc.getfun = js_TA_get_int8; + cmpfun = js_TA_cmp_int8; + break; + case JS_CLASS_UINT8C_ARRAY: + case JS_CLASS_UINT8_ARRAY: + tsc.getfun = js_TA_get_uint8; + cmpfun = js_TA_cmp_uint8; + break; + case JS_CLASS_INT16_ARRAY: + tsc.getfun = js_TA_get_int16; + cmpfun = js_TA_cmp_int16; + break; + case JS_CLASS_UINT16_ARRAY: + tsc.getfun = js_TA_get_uint16; + cmpfun = js_TA_cmp_uint16; + break; + case JS_CLASS_INT32_ARRAY: + tsc.getfun = js_TA_get_int32; + cmpfun = js_TA_cmp_int32; + break; + case JS_CLASS_UINT32_ARRAY: + tsc.getfun = js_TA_get_uint32; + cmpfun = js_TA_cmp_uint32; + break; + case JS_CLASS_BIG_INT64_ARRAY: + tsc.getfun = js_TA_get_int64; + cmpfun = js_TA_cmp_int64; + break; + case JS_CLASS_BIG_UINT64_ARRAY: + tsc.getfun = js_TA_get_uint64; + cmpfun = js_TA_cmp_uint64; + break; + case JS_CLASS_FLOAT16_ARRAY: + tsc.getfun = js_TA_get_float16; + cmpfun = js_TA_cmp_float16; + break; + case JS_CLASS_FLOAT32_ARRAY: + tsc.getfun = js_TA_get_float32; + cmpfun = js_TA_cmp_float32; + break; + case JS_CLASS_FLOAT64_ARRAY: + tsc.getfun = js_TA_get_float64; + cmpfun = js_TA_cmp_float64; + break; + default: + abort(); + } + elt_size = 1 << typed_array_size_log2(p->class_id); + if (!JS_IsUndefined(tsc.cmp)) { + uint32_t *array_idx; + void *array_tmp; + size_t i, j; + + /* XXX: a stable sort would use less memory */ + array_idx = js_malloc(ctx, len * sizeof(array_idx[0])); + if (!array_idx) + return JS_EXCEPTION; + for(i = 0; i < len; i++) + array_idx[i] = i; + tsc.elt_size = elt_size; + rqsort(array_idx, len, sizeof(array_idx[0]), + js_TA_cmp_generic, &tsc); + if (tsc.exception) + goto fail; + // per spec: typed array can be detached mid-iteration + if (typed_array_is_oob(p)) + goto done; + len = min_int(len, p->u.array.count); + if (len == 0) + goto done; + array_tmp = js_malloc(ctx, len * elt_size); + if (!array_tmp) { + fail: + js_free(ctx, array_idx); + return JS_EXCEPTION; + } + memcpy(array_tmp, p->u.array.u.ptr, len * elt_size); + switch(elt_size) { + case 1: + for(i = 0; i < len; i++) { + j = array_idx[i]; + p->u.array.u.uint8_ptr[i] = ((uint8_t *)array_tmp)[j]; + } + break; + case 2: + for(i = 0; i < len; i++) { + j = array_idx[i]; + p->u.array.u.uint16_ptr[i] = ((uint16_t *)array_tmp)[j]; + } + break; + case 4: + for(i = 0; i < len; i++) { + j = array_idx[i]; + p->u.array.u.uint32_ptr[i] = ((uint32_t *)array_tmp)[j]; + } + break; + case 8: + for(i = 0; i < len; i++) { + j = array_idx[i]; + p->u.array.u.uint64_ptr[i] = ((uint64_t *)array_tmp)[j]; + } + break; + default: + abort(); + } + js_free(ctx, array_tmp); + done: + js_free(ctx, array_idx); + } else { + rqsort(p->u.array.u.ptr, len, elt_size, cmpfun, &tsc); + if (tsc.exception) + return JS_EXCEPTION; + } + } + return js_dup(this_val); +} + +static JSValue js_typed_array_toSorted(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv) +{ + JSValue arr, ret; + JSObject *p; + + p = get_typed_array(ctx, this_val); + if (!p) + return JS_EXCEPTION; + arr = js_typed_array_constructor_ta(ctx, JS_UNDEFINED, this_val, + p->class_id, p->u.array.count); + if (JS_IsException(arr)) + return JS_EXCEPTION; + ret = js_typed_array_sort(ctx, arr, argc, argv); + JS_FreeValue(ctx, arr); + return ret; +} + +static const JSCFunctionListEntry js_typed_array_base_funcs[] = { + JS_CFUNC_DEF("from", 1, js_typed_array_from ), + JS_CFUNC_DEF("of", 0, js_typed_array_of ), + JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ), +}; + +static const JSCFunctionListEntry js_typed_array_base_proto_funcs[] = { + JS_CGETSET_DEF("length", js_typed_array_get_length, NULL ), + JS_CFUNC_DEF("at", 1, js_typed_array_at ), + JS_CFUNC_DEF("with", 2, js_typed_array_with ), + JS_CGETSET_DEF("buffer", js_typed_array_get_buffer, NULL ), + JS_CGETSET_DEF("byteLength", js_typed_array_get_byteLength, NULL ), + JS_CGETSET_DEF("byteOffset", js_typed_array_get_byteOffset, NULL ), + JS_CFUNC_DEF("set", 1, js_typed_array_set ), + JS_CFUNC_MAGIC_DEF("values", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_VALUE ), + JS_ALIAS_DEF("[Symbol.iterator]", "values" ), + JS_CFUNC_MAGIC_DEF("keys", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY ), + JS_CFUNC_MAGIC_DEF("entries", 0, js_create_typed_array_iterator, JS_ITERATOR_KIND_KEY_AND_VALUE ), + JS_CGETSET_DEF("[Symbol.toStringTag]", js_typed_array_get_toStringTag, NULL ), + JS_CFUNC_DEF("copyWithin", 2, js_typed_array_copyWithin ), + JS_CFUNC_MAGIC_DEF("every", 1, js_array_every, special_every | special_TA ), + JS_CFUNC_MAGIC_DEF("some", 1, js_array_every, special_some | special_TA ), + JS_CFUNC_MAGIC_DEF("forEach", 1, js_array_every, special_forEach | special_TA ), + JS_CFUNC_MAGIC_DEF("map", 1, js_array_every, special_map | special_TA ), + JS_CFUNC_MAGIC_DEF("filter", 1, js_array_every, special_filter | special_TA ), + JS_CFUNC_MAGIC_DEF("reduce", 1, js_array_reduce, special_reduce | special_TA ), + JS_CFUNC_MAGIC_DEF("reduceRight", 1, js_array_reduce, special_reduceRight | special_TA ), + JS_CFUNC_DEF("fill", 1, js_typed_array_fill ), + JS_CFUNC_MAGIC_DEF("find", 1, js_typed_array_find, ArrayFind ), + JS_CFUNC_MAGIC_DEF("findIndex", 1, js_typed_array_find, ArrayFindIndex ), + JS_CFUNC_MAGIC_DEF("findLast", 1, js_typed_array_find, ArrayFindLast ), + JS_CFUNC_MAGIC_DEF("findLastIndex", 1, js_typed_array_find, ArrayFindLastIndex ), + JS_CFUNC_DEF("reverse", 0, js_typed_array_reverse ), + JS_CFUNC_DEF("toReversed", 0, js_typed_array_toReversed ), + JS_CFUNC_DEF("slice", 2, js_typed_array_slice ), + JS_CFUNC_DEF("subarray", 2, js_typed_array_subarray ), + JS_CFUNC_DEF("sort", 1, js_typed_array_sort ), + JS_CFUNC_DEF("toSorted", 1, js_typed_array_toSorted ), + JS_CFUNC_MAGIC_DEF("join", 1, js_typed_array_join, 0 ), + JS_CFUNC_MAGIC_DEF("toLocaleString", 0, js_typed_array_join, 1 ), + JS_CFUNC_MAGIC_DEF("indexOf", 1, js_typed_array_indexOf, special_indexOf ), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 1, js_typed_array_indexOf, special_lastIndexOf ), + JS_CFUNC_MAGIC_DEF("includes", 1, js_typed_array_indexOf, special_includes ), + //JS_ALIAS_BASE_DEF("toString", "toString", 2 /* Array.prototype. */), @@@ +}; + +static JSValue js_typed_array_base_constructor(JSContext *ctx, + JSValue this_val, + int argc, JSValue *argv) +{ + return JS_ThrowTypeError(ctx, "cannot be called"); +} + +/* 'obj' must be an allocated typed array object */ +static int typed_array_init(JSContext *ctx, JSValue obj, JSValue buffer, + uint64_t offset, uint64_t len, BOOL track_rab) +{ + JSTypedArray *ta; + JSObject *p, *pbuffer; + JSArrayBuffer *abuf; + int size_log2; + + p = JS_VALUE_GET_OBJ(obj); + size_log2 = typed_array_size_log2(p->class_id); + ta = js_malloc(ctx, sizeof(*ta)); + if (!ta) { + JS_FreeValue(ctx, buffer); + return -1; + } + pbuffer = JS_VALUE_GET_OBJ(buffer); + abuf = pbuffer->u.array_buffer; + ta->obj = p; + ta->buffer = pbuffer; + ta->offset = offset; + ta->length = len << size_log2; + ta->track_rab = track_rab; + list_add_tail(&ta->link, &abuf->array_list); + p->u.typed_array = ta; + p->u.array.count = len; + p->u.array.u.ptr = abuf->data + offset; + return 0; +} + + +static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen, + JSValue obj, JSValue method) +{ + JSValue arr, iter, next_method = JS_UNDEFINED, val; + BOOL done; + uint32_t k; + + *plen = 0; + arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return arr; + iter = JS_GetIterator2(ctx, obj, method); + if (JS_IsException(iter)) + goto fail; + next_method = JS_GetProperty(ctx, iter, JS_ATOM_next); + if (JS_IsException(next_method)) + goto fail; + k = 0; + for(;;) { + val = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done); + if (JS_IsException(val)) + goto fail; + if (done) { + JS_FreeValue(ctx, val); + break; + } + if (JS_CreateDataPropertyUint32(ctx, arr, k, val, JS_PROP_THROW) < 0) + goto fail; + k++; + } + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + *plen = k; + return arr; + fail: + JS_FreeValue(ctx, next_method); + JS_FreeValue(ctx, iter); + JS_FreeValue(ctx, arr); + return JS_EXCEPTION; +} + +static JSValue js_typed_array_constructor_obj(JSContext *ctx, + JSValue new_target, + JSValue obj, + int classid) +{ + JSValue iter, ret, arr = JS_UNDEFINED, val, buffer; + uint32_t i; + int size_log2; + int64_t len; + + size_log2 = typed_array_size_log2(classid); + ret = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(ret)) + return JS_EXCEPTION; + + iter = JS_GetProperty(ctx, obj, JS_ATOM_Symbol_iterator); + if (JS_IsException(iter)) + goto fail; + if (!JS_IsUndefined(iter) && !JS_IsNull(iter)) { + uint32_t len1; + arr = js_array_from_iterator(ctx, &len1, obj, iter); + JS_FreeValue(ctx, iter); + if (JS_IsException(arr)) + goto fail; + len = len1; + } else { + if (js_get_length64(ctx, &len, obj)) + goto fail; + arr = js_dup(obj); + } + + buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, + len << size_log2, + NULL); + if (JS_IsException(buffer)) + goto fail; + if (typed_array_init(ctx, ret, buffer, 0, len, /*track_rab*/FALSE)) + goto fail; + + for(i = 0; i < len; i++) { + val = JS_GetPropertyUint32(ctx, arr, i); + if (JS_IsException(val)) + goto fail; + if (JS_SetPropertyUint32(ctx, ret, i, val) < 0) + goto fail; + } + JS_FreeValue(ctx, arr); + return ret; + fail: + JS_FreeValue(ctx, arr); + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; +} + +static JSValue js_typed_array_constructor_ta(JSContext *ctx, + JSValue new_target, + JSValue src_obj, + int classid, uint32_t len) +{ + JSObject *p, *src_buffer; + JSTypedArray *ta; + JSValue obj, buffer; + uint32_t i; + int size_log2; + JSArrayBuffer *src_abuf, *abuf; + + obj = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(obj)) + return obj; + p = JS_VALUE_GET_OBJ(src_obj); + if (typed_array_is_oob(p)) { + JS_ThrowTypeErrorArrayBufferOOB(ctx); + goto fail; + } + ta = p->u.typed_array; + src_buffer = ta->buffer; + src_abuf = src_buffer->u.array_buffer; + size_log2 = typed_array_size_log2(classid); + buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, + (uint64_t)len << size_log2, + NULL); + if (JS_IsException(buffer)) + goto fail; + /* necessary because it could have been detached */ + if (typed_array_is_oob(p)) { + JS_FreeValue(ctx, buffer); + JS_ThrowTypeErrorArrayBufferOOB(ctx); + goto fail; + } + abuf = JS_GetOpaque(buffer, JS_CLASS_ARRAY_BUFFER); + if (typed_array_init(ctx, obj, buffer, 0, len, /*track_rab*/FALSE)) + goto fail; + if (p->class_id == classid) { + /* same type: copy the content */ + memcpy(abuf->data, src_abuf->data + ta->offset, abuf->byte_length); + } else { + for(i = 0; i < len; i++) { + JSValue val; + val = JS_GetPropertyUint32(ctx, src_obj, i); + if (JS_IsException(val)) + goto fail; + if (JS_SetPropertyUint32(ctx, obj, i, val) < 0) + goto fail; + } + } + return obj; + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +static JSValue js_typed_array_constructor(JSContext *ctx, + JSValue new_target, + int argc, JSValue *argv, + int classid) +{ + BOOL track_rab = FALSE; + JSValue buffer, obj; + JSArrayBuffer *abuf; + int size_log2; + uint64_t len, offset; + + size_log2 = typed_array_size_log2(classid); + if (JS_VALUE_GET_TAG(argv[0]) != JS_TAG_OBJECT) { + if (JS_ToIndex(ctx, &len, argv[0])) + return JS_EXCEPTION; + buffer = js_array_buffer_constructor1(ctx, JS_UNDEFINED, + len << size_log2, + NULL); + if (JS_IsException(buffer)) + return JS_EXCEPTION; + offset = 0; + } else { + JSObject *p = JS_VALUE_GET_OBJ(argv[0]); + if (p->class_id == JS_CLASS_ARRAY_BUFFER || + p->class_id == JS_CLASS_SHARED_ARRAY_BUFFER) { + abuf = p->u.array_buffer; + if (JS_ToIndex(ctx, &offset, argv[1])) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if ((offset & ((1 << size_log2) - 1)) != 0 || + offset > abuf->byte_length) + return JS_ThrowRangeError(ctx, "invalid offset"); + if (JS_IsUndefined(argv[2])) { + track_rab = array_buffer_is_resizable(abuf); + if (!track_rab) + if ((abuf->byte_length & ((1 << size_log2) - 1)) != 0) + goto invalid_length; + len = (abuf->byte_length - offset) >> size_log2; + } else { + if (JS_ToIndex(ctx, &len, argv[2])) + return JS_EXCEPTION; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if ((offset + (len << size_log2)) > abuf->byte_length) { + invalid_length: + return JS_ThrowRangeError(ctx, "invalid length"); + } + } + buffer = js_dup(argv[0]); + } else { + if (p->class_id >= JS_CLASS_UINT8C_ARRAY && + p->class_id <= JS_CLASS_FLOAT64_ARRAY) { + return js_typed_array_constructor_ta(ctx, new_target, argv[0], + classid, p->u.array.count); + } else { + return js_typed_array_constructor_obj(ctx, new_target, argv[0], classid); + } + } + } + + obj = js_create_from_ctor(ctx, new_target, classid); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + if (typed_array_init(ctx, obj, buffer, offset, len, track_rab)) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +static void js_typed_array_finalizer(JSRuntime *rt, JSValue val) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSTypedArray *ta = p->u.typed_array; + if (ta) { + /* during the GC the finalizers are called in an arbitrary + order so the ArrayBuffer finalizer may have been called */ + if (ta->link.next) { + list_del(&ta->link); + } + JS_FreeValueRT(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); + js_free_rt(rt, ta); + } +} + +static void js_typed_array_mark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func) +{ + JSObject *p = JS_VALUE_GET_OBJ(val); + JSTypedArray *ta = p->u.typed_array; + if (ta) { + JS_MarkValue(rt, JS_MKPTR(JS_TAG_OBJECT, ta->buffer), mark_func); + } +} + +static JSValue js_dataview_constructor(JSContext *ctx, + JSValue new_target, + int argc, JSValue *argv) +{ + BOOL recompute_len = FALSE; + BOOL track_rab = FALSE; + JSArrayBuffer *abuf; + uint64_t offset; + uint32_t len; + JSValue buffer; + JSValue obj; + JSTypedArray *ta; + JSObject *p; + + buffer = argv[0]; + abuf = js_get_array_buffer(ctx, buffer); + if (!abuf) + return JS_EXCEPTION; + offset = 0; + if (argc > 1) { + if (JS_ToIndex(ctx, &offset, argv[1])) + return JS_EXCEPTION; + } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + if (offset > abuf->byte_length) + return JS_ThrowRangeError(ctx, "invalid byteOffset"); + len = abuf->byte_length - offset; + if (argc > 2 && !JS_IsUndefined(argv[2])) { + uint64_t l; + if (JS_ToIndex(ctx, &l, argv[2])) + return JS_EXCEPTION; + if (l > len) + return JS_ThrowRangeError(ctx, "invalid byteLength"); + len = l; + } else { + recompute_len = TRUE; + track_rab = array_buffer_is_resizable(abuf); + } + + obj = js_create_from_ctor(ctx, new_target, JS_CLASS_DATAVIEW); + if (JS_IsException(obj)) + return JS_EXCEPTION; + if (abuf->detached) { + /* could have been detached in js_create_from_ctor() */ + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + goto fail; + } + // RAB could have been resized in js_create_from_ctor() + if (offset > abuf->byte_length) { + goto out_of_bound; + } else if (recompute_len) { + len = abuf->byte_length - offset; + } else if (offset + len > abuf->byte_length) { + out_of_bound: + JS_ThrowRangeError(ctx, "invalid byteOffset or byteLength"); + goto fail; + } + ta = js_malloc(ctx, sizeof(*ta)); + if (!ta) { + fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + p = JS_VALUE_GET_OBJ(obj); + ta->obj = p; + ta->buffer = JS_VALUE_GET_OBJ(js_dup(buffer)); + ta->offset = offset; + ta->length = len; + ta->track_rab = track_rab; + list_add_tail(&ta->link, &abuf->array_list); + p->u.typed_array = ta; + return obj; +} + +// is the DataView out of bounds relative to its parent arraybuffer? +static BOOL dataview_is_oob(JSObject *p) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + + assert(p->class_id == JS_CLASS_DATAVIEW); + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return TRUE; + if (ta->offset > abuf->byte_length) + return TRUE; + if (ta->track_rab) + return FALSE; + return (int64_t)ta->offset + ta->length > abuf->byte_length; +} + +static JSObject *get_dataview(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + if (JS_VALUE_GET_TAG(this_val) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(this_val); + if (p->class_id != JS_CLASS_DATAVIEW) { + fail: + JS_ThrowTypeError(ctx, "not a DataView"); + return NULL; + } + return p; +} + +static JSValue js_dataview_get_buffer(JSContext *ctx, JSValue this_val) +{ + JSObject *p; + JSTypedArray *ta; + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + ta = p->u.typed_array; + return js_dup(JS_MKPTR(JS_TAG_OBJECT, ta->buffer)); +} + +static JSValue js_dataview_get_byteLength(JSContext *ctx, JSValue this_val) +{ + JSArrayBuffer *abuf; + JSTypedArray *ta; + JSObject *p; + + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (dataview_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + if (ta->track_rab) { + abuf = ta->buffer->u.array_buffer; + return js_uint32(abuf->byte_length - ta->offset); + } + return js_uint32(ta->length); +} + +static JSValue js_dataview_get_byteOffset(JSContext *ctx, JSValue this_val) +{ + JSTypedArray *ta; + JSObject *p; + + p = get_dataview(ctx, this_val); + if (!p) + return JS_EXCEPTION; + if (dataview_is_oob(p)) + return JS_ThrowTypeErrorArrayBufferOOB(ctx); + ta = p->u.typed_array; + return js_uint32(ta->offset); +} + +static JSValue js_dataview_getValue(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv, int class_id) +{ + JSTypedArray *ta; + JSArrayBuffer *abuf; + BOOL littleEndian, is_swap; + int size; + uint8_t *ptr; + uint32_t v; + uint64_t pos; + + ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW); + if (!ta) + return JS_EXCEPTION; + size = 1 << typed_array_size_log2(class_id); + if (JS_ToIndex(ctx, &pos, argv[0])) + return JS_EXCEPTION; + littleEndian = argc > 1 && JS_ToBool(ctx, argv[1]); + is_swap = littleEndian ^ !is_be(); + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + // order matters: this check should come before the next one + if ((pos + size) > ta->length) + return JS_ThrowRangeError(ctx, "out of bound"); + // test262 expects a TypeError for this and V8, in its infinite wisdom, + // throws a "detached array buffer" exception, but IMO that doesn't make + // sense because the buffer is not in fact detached, it's still there + if ((int64_t)ta->offset + ta->length > abuf->byte_length) + return JS_ThrowTypeError(ctx, "out of bound"); + ptr = abuf->data + ta->offset + pos; + + switch(class_id) { + case JS_CLASS_INT8_ARRAY: + return js_int32(*(int8_t *)ptr); + case JS_CLASS_UINT8_ARRAY: + return js_int32(*(uint8_t *)ptr); + case JS_CLASS_INT16_ARRAY: + v = get_u16(ptr); + if (is_swap) + v = bswap16(v); + return js_int32((int16_t)v); + case JS_CLASS_UINT16_ARRAY: + v = get_u16(ptr); + if (is_swap) + v = bswap16(v); + return js_int32(v); + case JS_CLASS_INT32_ARRAY: + v = get_u32(ptr); + if (is_swap) + v = bswap32(v); + return js_int32(v); + case JS_CLASS_UINT32_ARRAY: + v = get_u32(ptr); + if (is_swap) + v = bswap32(v); + return js_uint32(v); + case JS_CLASS_BIG_INT64_ARRAY: + { + uint64_t v; + v = get_u64(ptr); + if (is_swap) + v = bswap64(v); + return JS_NewBigInt64(ctx, v); + } + break; + case JS_CLASS_BIG_UINT64_ARRAY: + { + uint64_t v; + v = get_u64(ptr); + if (is_swap) + v = bswap64(v); + return JS_NewBigUint64(ctx, v); + } + break; + case JS_CLASS_FLOAT16_ARRAY: + { + uint16_t v; + v = get_u16(ptr); + if (is_swap) + v = bswap16(v); + return js_float64(fromfp16(v)); + } + case JS_CLASS_FLOAT32_ARRAY: + { + union { + float f; + uint32_t i; + } u; + v = get_u32(ptr); + if (is_swap) + v = bswap32(v); + u.i = v; + return js_float64(u.f); + } + case JS_CLASS_FLOAT64_ARRAY: + { + union { + double f; + uint64_t i; + } u; + u.i = get_u64(ptr); + if (is_swap) + u.i = bswap64(u.i); + return js_float64(u.f); + } + default: + abort(); + } + return JS_EXCEPTION; // pacify compiler +} + +static JSValue js_dataview_setValue(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv, int class_id) +{ + JSTypedArray *ta; + JSArrayBuffer *abuf; + BOOL littleEndian, is_swap; + int size; + uint8_t *ptr; + uint64_t v64; + uint32_t v; + uint64_t pos; + JSValue val; + + ta = JS_GetOpaque2(ctx, this_obj, JS_CLASS_DATAVIEW); + if (!ta) + return JS_EXCEPTION; + size = 1 << typed_array_size_log2(class_id); + if (JS_ToIndex(ctx, &pos, argv[0])) + return JS_EXCEPTION; + val = argv[1]; + v = 0; /* avoid warning */ + v64 = 0; /* avoid warning */ + if (class_id <= JS_CLASS_UINT32_ARRAY) { + if (JS_ToUint32(ctx, &v, val)) + return JS_EXCEPTION; + } else + if (class_id <= JS_CLASS_BIG_UINT64_ARRAY) { + if (JS_ToBigInt64(ctx, (int64_t *)&v64, val)) + return JS_EXCEPTION; + } else { + double d; + if (JS_ToFloat64(ctx, &d, val)) + return JS_EXCEPTION; + if (class_id == JS_CLASS_FLOAT16_ARRAY) { + v = tofp16(d); + } else if (class_id == JS_CLASS_FLOAT32_ARRAY) { + union { + float f; + uint32_t i; + } u; + u.f = d; + v = u.i; + } else { + JSFloat64Union u; + u.d = d; + v64 = u.u64; + } + } + littleEndian = argc > 2 && JS_ToBool(ctx, argv[2]); + is_swap = littleEndian ^ !is_be(); + abuf = ta->buffer->u.array_buffer; + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + // order matters: this check should come before the next one + if ((pos + size) > ta->length) + return JS_ThrowRangeError(ctx, "out of bound"); + // test262 expects a TypeError for this and V8, in its infinite wisdom, + // throws a "detached array buffer" exception, but IMO that doesn't make + // sense because the buffer is not in fact detached, it's still there + if ((int64_t)ta->offset + ta->length > abuf->byte_length) + return JS_ThrowTypeError(ctx, "out of bound"); + ptr = abuf->data + ta->offset + pos; + + switch(class_id) { + case JS_CLASS_INT8_ARRAY: + case JS_CLASS_UINT8_ARRAY: + *ptr = v; + break; + case JS_CLASS_INT16_ARRAY: + case JS_CLASS_UINT16_ARRAY: + case JS_CLASS_FLOAT16_ARRAY: + if (is_swap) + v = bswap16(v); + put_u16(ptr, v); + break; + case JS_CLASS_INT32_ARRAY: + case JS_CLASS_UINT32_ARRAY: + case JS_CLASS_FLOAT32_ARRAY: + if (is_swap) + v = bswap32(v); + put_u32(ptr, v); + break; + case JS_CLASS_BIG_INT64_ARRAY: + case JS_CLASS_BIG_UINT64_ARRAY: + case JS_CLASS_FLOAT64_ARRAY: + if (is_swap) + v64 = bswap64(v64); + put_u64(ptr, v64); + break; + default: + abort(); + } + return JS_UNDEFINED; +} + +static const JSCFunctionListEntry js_dataview_proto_funcs[] = { + JS_CGETSET_DEF("buffer", js_dataview_get_buffer, NULL ), + JS_CGETSET_DEF("byteLength", js_dataview_get_byteLength, NULL ), + JS_CGETSET_DEF("byteOffset", js_dataview_get_byteOffset, NULL ), + JS_CFUNC_MAGIC_DEF("getInt8", 1, js_dataview_getValue, JS_CLASS_INT8_ARRAY ), + JS_CFUNC_MAGIC_DEF("getUint8", 1, js_dataview_getValue, JS_CLASS_UINT8_ARRAY ), + JS_CFUNC_MAGIC_DEF("getInt16", 1, js_dataview_getValue, JS_CLASS_INT16_ARRAY ), + JS_CFUNC_MAGIC_DEF("getUint16", 1, js_dataview_getValue, JS_CLASS_UINT16_ARRAY ), + JS_CFUNC_MAGIC_DEF("getInt32", 1, js_dataview_getValue, JS_CLASS_INT32_ARRAY ), + JS_CFUNC_MAGIC_DEF("getUint32", 1, js_dataview_getValue, JS_CLASS_UINT32_ARRAY ), + JS_CFUNC_MAGIC_DEF("getBigInt64", 1, js_dataview_getValue, JS_CLASS_BIG_INT64_ARRAY ), + JS_CFUNC_MAGIC_DEF("getBigUint64", 1, js_dataview_getValue, JS_CLASS_BIG_UINT64_ARRAY ), + JS_CFUNC_MAGIC_DEF("getFloat16", 1, js_dataview_getValue, JS_CLASS_FLOAT16_ARRAY ), + JS_CFUNC_MAGIC_DEF("getFloat32", 1, js_dataview_getValue, JS_CLASS_FLOAT32_ARRAY ), + JS_CFUNC_MAGIC_DEF("getFloat64", 1, js_dataview_getValue, JS_CLASS_FLOAT64_ARRAY ), + JS_CFUNC_MAGIC_DEF("setInt8", 2, js_dataview_setValue, JS_CLASS_INT8_ARRAY ), + JS_CFUNC_MAGIC_DEF("setUint8", 2, js_dataview_setValue, JS_CLASS_UINT8_ARRAY ), + JS_CFUNC_MAGIC_DEF("setInt16", 2, js_dataview_setValue, JS_CLASS_INT16_ARRAY ), + JS_CFUNC_MAGIC_DEF("setUint16", 2, js_dataview_setValue, JS_CLASS_UINT16_ARRAY ), + JS_CFUNC_MAGIC_DEF("setInt32", 2, js_dataview_setValue, JS_CLASS_INT32_ARRAY ), + JS_CFUNC_MAGIC_DEF("setUint32", 2, js_dataview_setValue, JS_CLASS_UINT32_ARRAY ), + JS_CFUNC_MAGIC_DEF("setBigInt64", 2, js_dataview_setValue, JS_CLASS_BIG_INT64_ARRAY ), + JS_CFUNC_MAGIC_DEF("setBigUint64", 2, js_dataview_setValue, JS_CLASS_BIG_UINT64_ARRAY ), + JS_CFUNC_MAGIC_DEF("setFloat16", 2, js_dataview_setValue, JS_CLASS_FLOAT16_ARRAY ), + JS_CFUNC_MAGIC_DEF("setFloat32", 2, js_dataview_setValue, JS_CLASS_FLOAT32_ARRAY ), + JS_CFUNC_MAGIC_DEF("setFloat64", 2, js_dataview_setValue, JS_CLASS_FLOAT64_ARRAY ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "DataView", JS_PROP_CONFIGURABLE ), +}; + +static JSValue js_new_uint8array(JSContext *ctx, JSValue buffer) +{ + if (JS_IsException(buffer)) + return JS_EXCEPTION; + JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_UINT8_ARRAY); + if (JS_IsException(obj)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + JSArrayBuffer *abuf = js_get_array_buffer(ctx, buffer); + assert(abuf != NULL); + if (typed_array_init(ctx, obj, buffer, 0, abuf->byte_length, /*track_rab*/FALSE)) { + // 'buffer' is freed on error above. + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + return obj; +} + +JSValue JS_NewUint8Array(JSContext *ctx, uint8_t *buf, size_t len, + JSFreeArrayBufferDataFunc *free_func, void *opaque, + JS_BOOL is_shared) +{ + JSClassID class_id = + is_shared ? JS_CLASS_SHARED_ARRAY_BUFFER : JS_CLASS_ARRAY_BUFFER; + JSValue buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, + class_id, buf, free_func, + opaque, FALSE); + return js_new_uint8array(ctx, buffer); +} + +JSValue JS_NewUint8ArrayCopy(JSContext *ctx, const uint8_t *buf, size_t len) +{ + JSValue buffer = js_array_buffer_constructor3(ctx, JS_UNDEFINED, len, NULL, + JS_CLASS_ARRAY_BUFFER, + (uint8_t *)buf, + js_array_buffer_free, NULL, + TRUE); + return js_new_uint8array(ctx, buffer); +} + +int JS_GetTypedArrayType(JSValue obj) +{ + JSClassID class_id = JS_GetClassID(obj); + if (class_id >= JS_CLASS_UINT8C_ARRAY && class_id <= JS_CLASS_FLOAT64_ARRAY) + return class_id - JS_CLASS_UINT8C_ARRAY; + else + return -1; +} + +/* Atomics */ +#ifdef CONFIG_ATOMICS + +typedef enum AtomicsOpEnum { + ATOMICS_OP_ADD, + ATOMICS_OP_AND, + ATOMICS_OP_OR, + ATOMICS_OP_SUB, + ATOMICS_OP_XOR, + ATOMICS_OP_EXCHANGE, + ATOMICS_OP_COMPARE_EXCHANGE, + ATOMICS_OP_LOAD, +} AtomicsOpEnum; + +static void *js_atomics_get_ptr(JSContext *ctx, + JSArrayBuffer **pabuf, + int *psize_log2, JSClassID *pclass_id, + JSValue obj, JSValue idx_val, + int is_waitable) +{ + JSObject *p; + JSTypedArray *ta; + JSArrayBuffer *abuf; + void *ptr; + uint64_t idx; + BOOL err; + int size_log2; + + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + goto fail; + p = JS_VALUE_GET_OBJ(obj); + if (is_waitable) + err = (p->class_id != JS_CLASS_INT32_ARRAY && + p->class_id != JS_CLASS_BIG_INT64_ARRAY); + else + err = !(p->class_id >= JS_CLASS_INT8_ARRAY && + p->class_id <= JS_CLASS_BIG_UINT64_ARRAY); + if (err) { + fail: + JS_ThrowTypeError(ctx, "integer TypedArray expected"); + return NULL; + } + ta = p->u.typed_array; + abuf = ta->buffer->u.array_buffer; + if (!abuf->shared) { + if (is_waitable == 2) { + JS_ThrowTypeError(ctx, "not a SharedArrayBuffer TypedArray"); + return NULL; + } + if (abuf->detached) { + JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + return NULL; + } + } + if (JS_ToIndex(ctx, &idx, idx_val)) { + return NULL; + } + /* if the array buffer is detached, p->u.array.count = 0 */ + if (idx >= p->u.array.count) { + JS_ThrowRangeError(ctx, "out-of-bound access"); + return NULL; + } + size_log2 = typed_array_size_log2(p->class_id); + ptr = p->u.array.u.uint8_ptr + ((uintptr_t)idx << size_log2); + if (pabuf) + *pabuf = abuf; + if (psize_log2) + *psize_log2 = size_log2; + if (pclass_id) + *pclass_id = p->class_id; + return ptr; +} + +static JSValue js_atomics_op(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv, int op) +{ + int size_log2; + uint64_t v, a, rep_val; + void *ptr; + JSValue ret; + JSClassID class_id; + JSArrayBuffer *abuf; + + ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, &class_id, + argv[0], argv[1], 0); + if (!ptr) + return JS_EXCEPTION; + rep_val = 0; + if (op == ATOMICS_OP_LOAD) { + v = 0; + } else { + if (size_log2 == 3) { + int64_t v64; + if (JS_ToBigInt64(ctx, &v64, argv[2])) + return JS_EXCEPTION; + v = v64; + if (op == ATOMICS_OP_COMPARE_EXCHANGE) { + if (JS_ToBigInt64(ctx, &v64, argv[3])) + return JS_EXCEPTION; + rep_val = v64; + } + } else { + uint32_t v32; + if (JS_ToUint32(ctx, &v32, argv[2])) + return JS_EXCEPTION; + v = v32; + if (op == ATOMICS_OP_COMPARE_EXCHANGE) { + if (JS_ToUint32(ctx, &v32, argv[3])) + return JS_EXCEPTION; + rep_val = v32; + } + } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + } + + switch(op | (size_log2 << 3)) { + +#define OP(op_name, func_name) \ + case ATOMICS_OP_ ## op_name | (0 << 3): \ + a = func_name((_Atomic uint8_t *)ptr, v); \ + break; \ + case ATOMICS_OP_ ## op_name | (1 << 3): \ + a = func_name((_Atomic uint16_t *)ptr, v); \ + break; \ + case ATOMICS_OP_ ## op_name | (2 << 3): \ + a = func_name((_Atomic uint32_t *)ptr, v); \ + break; \ + case ATOMICS_OP_ ## op_name | (3 << 3): \ + a = func_name((_Atomic uint64_t *)ptr, v); \ + break; + OP(ADD, atomic_fetch_add) + OP(AND, atomic_fetch_and) + OP(OR, atomic_fetch_or) + OP(SUB, atomic_fetch_sub) + OP(XOR, atomic_fetch_xor) + OP(EXCHANGE, atomic_exchange) +#undef OP + + case ATOMICS_OP_LOAD | (0 << 3): + a = atomic_load((_Atomic uint8_t *)ptr); + break; + case ATOMICS_OP_LOAD | (1 << 3): + a = atomic_load((_Atomic uint16_t *)ptr); + break; + case ATOMICS_OP_LOAD | (2 << 3): + a = atomic_load((_Atomic uint32_t *)ptr); + break; + case ATOMICS_OP_LOAD | (3 << 3): + a = atomic_load((_Atomic uint64_t *)ptr); + break; + case ATOMICS_OP_COMPARE_EXCHANGE | (0 << 3): + { + uint8_t v1 = v; + atomic_compare_exchange_strong((_Atomic uint8_t *)ptr, &v1, rep_val); + a = v1; + } + break; + case ATOMICS_OP_COMPARE_EXCHANGE | (1 << 3): + { + uint16_t v1 = v; + atomic_compare_exchange_strong((_Atomic uint16_t *)ptr, &v1, rep_val); + a = v1; + } + break; + case ATOMICS_OP_COMPARE_EXCHANGE | (2 << 3): + { + uint32_t v1 = v; + atomic_compare_exchange_strong((_Atomic uint32_t *)ptr, &v1, rep_val); + a = v1; + } + break; + case ATOMICS_OP_COMPARE_EXCHANGE | (3 << 3): + { + uint64_t v1 = v; + atomic_compare_exchange_strong((_Atomic uint64_t *)ptr, &v1, rep_val); + a = v1; + } + break; + default: + abort(); + } + + switch(class_id) { + case JS_CLASS_INT8_ARRAY: + a = (int8_t)a; + goto done; + case JS_CLASS_UINT8_ARRAY: + a = (uint8_t)a; + goto done; + case JS_CLASS_INT16_ARRAY: + a = (int16_t)a; + goto done; + case JS_CLASS_UINT16_ARRAY: + a = (uint16_t)a; + goto done; + case JS_CLASS_INT32_ARRAY: + done: + ret = js_int32(a); + break; + case JS_CLASS_UINT32_ARRAY: + ret = js_uint32(a); + break; + case JS_CLASS_BIG_INT64_ARRAY: + ret = JS_NewBigInt64(ctx, a); + break; + case JS_CLASS_BIG_UINT64_ARRAY: + ret = JS_NewBigUint64(ctx, a); + break; + default: + abort(); + } + return ret; +} + +static JSValue js_atomics_store(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv) +{ + int size_log2; + void *ptr; + JSValue ret; + JSArrayBuffer *abuf; + + ptr = js_atomics_get_ptr(ctx, &abuf, &size_log2, NULL, + argv[0], argv[1], 0); + if (!ptr) + return JS_EXCEPTION; + if (size_log2 == 3) { + int64_t v64; + ret = JS_ToBigIntValueFree(ctx, js_dup(argv[2])); + if (JS_IsException(ret)) + return ret; + if (JS_ToBigInt64(ctx, &v64, ret)) { + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; + } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + atomic_store((_Atomic uint64_t *)ptr, v64); + } else { + uint32_t v; + /* XXX: spec, would be simpler to return the written value */ + ret = JS_ToIntegerFree(ctx, js_dup(argv[2])); + if (JS_IsException(ret)) + return ret; + if (JS_ToUint32(ctx, &v, ret)) { + JS_FreeValue(ctx, ret); + return JS_EXCEPTION; + } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + switch(size_log2) { + case 0: + atomic_store((_Atomic uint8_t *)ptr, v); + break; + case 1: + atomic_store((_Atomic uint16_t *)ptr, v); + break; + case 2: + atomic_store((_Atomic uint32_t *)ptr, v); + break; + default: + abort(); + } + } + return ret; +} + +static JSValue js_atomics_isLockFree(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv) +{ + int v, ret; + if (JS_ToInt32Sat(ctx, &v, argv[0])) + return JS_EXCEPTION; + ret = (v == 1 || v == 2 || v == 4 || v == 8); + return js_bool(ret); +} + +typedef struct JSAtomicsWaiter { + struct list_head link; + BOOL linked; + js_cond_t cond; + int32_t *ptr; +} JSAtomicsWaiter; + +static js_once_t js_atomics_once = JS_ONCE_INIT; +static js_mutex_t js_atomics_mutex; +static struct list_head js_atomics_waiter_list = + LIST_HEAD_INIT(js_atomics_waiter_list); + +// no-op: Atomics.pause() is not allowed to block or yield to another +// thread, only to hint the CPU that it should back off for a bit; +// the amount of work we do here is a good enough substitute +static JSValue js_atomics_pause(JSContext *ctx, JSValue this_obj, + int argc, JSValue *argv) +{ + double d; + + if (argc > 0) { + switch (JS_VALUE_GET_TAG(argv[0])) { + case JS_TAG_FLOAT64: // accepted if and only if fraction == 0.0 + d = JS_VALUE_GET_FLOAT64(argv[0]); + if (isfinite(d)) + if (0 == modf(d, &d)) + break; + // fallthru + default: + return JS_ThrowTypeError(ctx, "not an integral number"); + case JS_TAG_UNDEFINED: + case JS_TAG_INT: + break; + } + } + return JS_UNDEFINED; +} + +static JSValue js_atomics_wait(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv) +{ + int64_t v; + int32_t v32; + void *ptr; + int64_t timeout; + JSAtomicsWaiter waiter_s, *waiter; + int ret, size_log2, res; + double d; + + ptr = js_atomics_get_ptr(ctx, NULL, &size_log2, NULL, + argv[0], argv[1], 2); + if (!ptr) + return JS_EXCEPTION; + if (size_log2 == 3) { + if (JS_ToBigInt64(ctx, &v, argv[2])) + return JS_EXCEPTION; + } else { + if (JS_ToInt32(ctx, &v32, argv[2])) + return JS_EXCEPTION; + v = v32; + } + if (JS_ToFloat64(ctx, &d, argv[3])) + return JS_EXCEPTION; + if (isnan(d) || d >= 0x1p63) + timeout = INT64_MAX; + else if (d < 0) + timeout = 0; + else + timeout = (int64_t)d; + if (!ctx->rt->can_block) + return JS_ThrowTypeError(ctx, "cannot block in this thread"); + + /* XXX: inefficient if large number of waiters, should hash on + 'ptr' value */ + js_mutex_lock(&js_atomics_mutex); + if (size_log2 == 3) { + res = *(int64_t *)ptr != v; + } else { + res = *(int32_t *)ptr != v; + } + if (res) { + js_mutex_unlock(&js_atomics_mutex); + return JS_AtomToString(ctx, JS_ATOM_not_equal); + } + + waiter = &waiter_s; + waiter->ptr = ptr; + js_cond_init(&waiter->cond); + waiter->linked = TRUE; + list_add_tail(&waiter->link, &js_atomics_waiter_list); + + if (timeout == INT64_MAX) { + js_cond_wait(&waiter->cond, &js_atomics_mutex); + ret = 0; + } else { + ret = js_cond_timedwait(&waiter->cond, &js_atomics_mutex, timeout * 1e6 /* to ns */); + } + if (waiter->linked) + list_del(&waiter->link); + js_mutex_unlock(&js_atomics_mutex); + js_cond_destroy(&waiter->cond); + if (ret == -1) { + return JS_AtomToString(ctx, JS_ATOM_timed_out); + } else { + return JS_AtomToString(ctx, JS_ATOM_ok); + } +} + +static JSValue js_atomics_notify(JSContext *ctx, + JSValue this_obj, + int argc, JSValue *argv) +{ + struct list_head *el, *el1, waiter_list; + int32_t count, n; + void *ptr; + JSAtomicsWaiter *waiter; + JSArrayBuffer *abuf; + + ptr = js_atomics_get_ptr(ctx, &abuf, NULL, NULL, argv[0], argv[1], 1); + if (!ptr) + return JS_EXCEPTION; + + if (JS_IsUndefined(argv[2])) { + count = INT32_MAX; + } else { + if (JS_ToInt32Clamp(ctx, &count, argv[2], 0, INT32_MAX, 0)) + return JS_EXCEPTION; + } + if (abuf->detached) + return JS_ThrowTypeErrorDetachedArrayBuffer(ctx); + + n = 0; + if (abuf->shared && count > 0) { + js_mutex_lock(&js_atomics_mutex); + init_list_head(&waiter_list); + list_for_each_safe(el, el1, &js_atomics_waiter_list) { + waiter = list_entry(el, JSAtomicsWaiter, link); + if (waiter->ptr == ptr) { + list_del(&waiter->link); + waiter->linked = FALSE; + list_add_tail(&waiter->link, &waiter_list); + n++; + if (n >= count) + break; + } + } + list_for_each(el, &waiter_list) { + waiter = list_entry(el, JSAtomicsWaiter, link); + js_cond_signal(&waiter->cond); + } + js_mutex_unlock(&js_atomics_mutex); + } + return js_int32(n); +} + +static const JSCFunctionListEntry js_atomics_funcs[] = { + JS_CFUNC_MAGIC_DEF("add", 3, js_atomics_op, ATOMICS_OP_ADD ), + JS_CFUNC_MAGIC_DEF("and", 3, js_atomics_op, ATOMICS_OP_AND ), + JS_CFUNC_MAGIC_DEF("or", 3, js_atomics_op, ATOMICS_OP_OR ), + JS_CFUNC_MAGIC_DEF("sub", 3, js_atomics_op, ATOMICS_OP_SUB ), + JS_CFUNC_MAGIC_DEF("xor", 3, js_atomics_op, ATOMICS_OP_XOR ), + JS_CFUNC_MAGIC_DEF("exchange", 3, js_atomics_op, ATOMICS_OP_EXCHANGE ), + JS_CFUNC_MAGIC_DEF("compareExchange", 4, js_atomics_op, ATOMICS_OP_COMPARE_EXCHANGE ), + JS_CFUNC_MAGIC_DEF("load", 2, js_atomics_op, ATOMICS_OP_LOAD ), + JS_CFUNC_DEF("store", 3, js_atomics_store ), + JS_CFUNC_DEF("isLockFree", 1, js_atomics_isLockFree ), + JS_CFUNC_DEF("pause", 0, js_atomics_pause ), + JS_CFUNC_DEF("wait", 4, js_atomics_wait ), + JS_CFUNC_DEF("notify", 3, js_atomics_notify ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Atomics", JS_PROP_CONFIGURABLE ), +}; + +static const JSCFunctionListEntry js_atomics_obj[] = { + JS_OBJECT_DEF("Atomics", js_atomics_funcs, countof(js_atomics_funcs), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE ), +}; + +static void js__atomics_init(void) { + js_mutex_init(&js_atomics_mutex); +} + +/* TODO(saghul) make this public and not dependent on typed arrays? */ +void JS_AddIntrinsicAtomics(JSContext *ctx) +{ + js_once(&js_atomics_once, js__atomics_init); + + /* add Atomics as autoinit object */ + JS_SetPropertyFunctionList(ctx, ctx->global_obj, js_atomics_obj, countof(js_atomics_obj)); +} + +#endif /* CONFIG_ATOMICS */ + +void JS_AddIntrinsicTypedArrays(JSContext *ctx) +{ + JSValue typed_array_base_proto, typed_array_base_func; + JSValue array_buffer_func, shared_array_buffer_func; + int i; + + ctx->class_proto[JS_CLASS_ARRAY_BUFFER] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ARRAY_BUFFER], + js_array_buffer_proto_funcs, + countof(js_array_buffer_proto_funcs)); + + array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "ArrayBuffer", + js_array_buffer_constructor, 1, + ctx->class_proto[JS_CLASS_ARRAY_BUFFER]); + JS_SetPropertyFunctionList(ctx, array_buffer_func, + js_array_buffer_funcs, + countof(js_array_buffer_funcs)); + + ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER], + js_shared_array_buffer_proto_funcs, + countof(js_shared_array_buffer_proto_funcs)); + + shared_array_buffer_func = JS_NewGlobalCConstructorOnly(ctx, "SharedArrayBuffer", + js_shared_array_buffer_constructor, 1, + ctx->class_proto[JS_CLASS_SHARED_ARRAY_BUFFER]); + JS_SetPropertyFunctionList(ctx, shared_array_buffer_func, + js_shared_array_buffer_funcs, + countof(js_shared_array_buffer_funcs)); + + typed_array_base_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, typed_array_base_proto, + js_typed_array_base_proto_funcs, + countof(js_typed_array_base_proto_funcs)); + + /* TypedArray.prototype.toString must be the same object as Array.prototype.toString */ + JSValue obj = JS_GetProperty(ctx, ctx->class_proto[JS_CLASS_ARRAY], JS_ATOM_toString); + /* XXX: should use alias method in JSCFunctionListEntry */ //@@@ + JS_DefinePropertyValue(ctx, typed_array_base_proto, JS_ATOM_toString, obj, + JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + + typed_array_base_func = JS_NewCFunction(ctx, js_typed_array_base_constructor, + "TypedArray", 0); + JS_SetPropertyFunctionList(ctx, typed_array_base_func, + js_typed_array_base_funcs, + countof(js_typed_array_base_funcs)); + JS_SetConstructor(ctx, typed_array_base_func, typed_array_base_proto); + + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft = { .generic_magic = js_typed_array_constructor }; + for(i = JS_CLASS_UINT8C_ARRAY; i < JS_CLASS_UINT8C_ARRAY + JS_TYPED_ARRAY_COUNT; i++) { + JSValue func_obj; + char buf[ATOM_GET_STR_BUF_SIZE]; + const char *name; + + ctx->class_proto[i] = JS_NewObjectProto(ctx, typed_array_base_proto); + JS_DefinePropertyValueStr(ctx, ctx->class_proto[i], + "BYTES_PER_ELEMENT", + js_int32(1 << typed_array_size_log2(i)), + 0); + name = JS_AtomGetStr(ctx, buf, sizeof(buf), + JS_ATOM_Uint8ClampedArray + i - JS_CLASS_UINT8C_ARRAY); + func_obj = JS_NewCFunction3(ctx, ft.generic, + name, 3, JS_CFUNC_constructor_magic, i, + typed_array_base_func); + JS_NewGlobalCConstructor2(ctx, func_obj, name, ctx->class_proto[i]); + JS_DefinePropertyValueStr(ctx, func_obj, + "BYTES_PER_ELEMENT", + js_int32(1 << typed_array_size_log2(i)), + 0); + } + JS_FreeValue(ctx, typed_array_base_proto); + JS_FreeValue(ctx, typed_array_base_func); + + /* DataView */ + ctx->class_proto[JS_CLASS_DATAVIEW] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_DATAVIEW], + js_dataview_proto_funcs, + countof(js_dataview_proto_funcs)); + JS_NewGlobalCConstructorOnly(ctx, "DataView", + js_dataview_constructor, 1, + ctx->class_proto[JS_CLASS_DATAVIEW]); + /* Atomics */ +#ifdef CONFIG_ATOMICS + JS_AddIntrinsicAtomics(ctx); +#endif +} + +/* Performance */ + +static double js__now_ms(void) +{ + return js__hrtime_ns() / 1e6; +} + +static JSValue js_perf_now(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + return js_float64(js__now_ms() - ctx->time_origin); +} + +static const JSCFunctionListEntry js_perf_proto_funcs[] = { + JS_CFUNC_DEF2("now", 0, js_perf_now, JS_PROP_ENUMERABLE), +}; + +void JS_AddPerformance(JSContext *ctx) +{ + ctx->time_origin = js__now_ms(); + + JSValue performance = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, performance, js_perf_proto_funcs, countof(js_perf_proto_funcs)); + JS_DefinePropertyValueStr(ctx, performance, "timeOrigin", + js_float64(ctx->time_origin), + JS_PROP_ENUMERABLE); + JS_DefinePropertyValueStr(ctx, ctx->global_obj, "performance", + js_dup(performance), + JS_PROP_WRITABLE | JS_PROP_ENUMERABLE | JS_PROP_CONFIGURABLE); + JS_FreeValue(ctx, performance); +} + +/* Equality comparisons and sameness */ +int JS_IsEqual(JSContext *ctx, JSValue op1, JSValue op2) +{ + JSValue sp[2] = { js_dup(op1), js_dup(op2) }; + if (js_eq_slow(ctx, endof(sp), 0)) + return -1; + return JS_VALUE_GET_BOOL(sp[0]); +} + +JS_BOOL JS_IsStrictEqual(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_strict_eq2(ctx, js_dup(op1), js_dup(op2), JS_EQ_STRICT); +} + +JS_BOOL JS_IsSameValue(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_same_value(ctx, op1, op2); +} + +JS_BOOL JS_IsSameValueZero(JSContext *ctx, JSValue op1, JSValue op2) +{ + return js_same_value_zero(ctx, op1, op2); +} + +/* WeakRef */ + +typedef struct JSWeakRefData { + JSValue target; + JSValue obj; +} JSWeakRefData; + +static JSWeakRefData js_weakref_sentinel; + +static void js_weakref_finalizer(JSRuntime *rt, JSValue val) +{ + JSWeakRefData *wrd = JS_GetOpaque(val, JS_CLASS_WEAK_REF); + if (!wrd || wrd == &js_weakref_sentinel) + return; + + /* Delete weak ref */ + JSWeakRefRecord **pwr, *wr; + + pwr = get_first_weak_ref(wrd->target); + for(;;) { + wr = *pwr; + assert(wr != NULL); + if (wr->kind == JS_WEAK_REF_KIND_WEAK_REF && wr->u.weak_ref_data == wrd) + break; + pwr = &wr->next_weak_ref; + } + *pwr = wr->next_weak_ref; + js_free_rt(rt, wrd); + js_free_rt(rt, wr); +} + +static JSValue js_weakref_constructor(JSContext *ctx, JSValue new_target, int argc, JSValue *argv) +{ + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + JSValue arg = argv[0]; + if (!is_valid_weakref_target(arg)) + return JS_ThrowTypeError(ctx, "invalid target"); + // TODO(saghul): short-circuit if the refcount is 1? + JSValue obj = js_create_from_ctor(ctx, new_target, JS_CLASS_WEAK_REF); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JSWeakRefData *wrd = js_malloc(ctx, sizeof(*wrd)); + if (!wrd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + JSWeakRefRecord *wr = js_malloc(ctx, sizeof(*wr)); + if (!wr) { + JS_FreeValue(ctx, obj); + js_free(ctx, wrd); + return JS_EXCEPTION; + } + wrd->target = arg; + wrd->obj = obj; + wr->kind = JS_WEAK_REF_KIND_WEAK_REF; + wr->u.weak_ref_data = wrd; + insert_weakref_record(arg, wr); + + JS_SetOpaqueInternal(obj, wrd); + return obj; +} + +static JSValue js_weakref_deref(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + JSWeakRefData *wrd = JS_GetOpaque2(ctx, this_val, JS_CLASS_WEAK_REF); + if (!wrd) + return JS_EXCEPTION; + if (wrd == &js_weakref_sentinel) + return JS_UNDEFINED; + return js_dup(wrd->target); +} + +static const JSCFunctionListEntry js_weakref_proto_funcs[] = { + JS_CFUNC_DEF("deref", 0, js_weakref_deref ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakRef", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_weakref_class_def[] = { + { JS_ATOM_WeakRef, js_weakref_finalizer, NULL }, /* JS_CLASS_WEAK_REF */ +}; + +typedef struct JSFinRecEntry { + struct list_head link; + JSValue obj; + JSValue target; + JSValue held_val; + JSValue token; +} JSFinRecEntry; + +typedef struct JSFinalizationRegistryData { + struct list_head entries; + JSContext *ctx; + JSValue cb; +} JSFinalizationRegistryData; + +static void delete_finrec_weakref(JSRuntime *rt, JSFinRecEntry *fre) +{ + JSWeakRefRecord **pwr, *wr; + + pwr = get_first_weak_ref(fre->target); + for(;;) { + wr = *pwr; + assert(wr != NULL); + if (wr->kind == JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY && wr->u.fin_rec_entry == fre) + break; + pwr = &wr->next_weak_ref; + } + *pwr = wr->next_weak_ref; + js_free_rt(rt, wr); +} + +static void js_finrec_finalizer(JSRuntime *rt, JSValue val) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY); + if (frd) { + struct list_head *el, *el1; + /* first pass to remove the weak ref entries and avoid having them modified + by freeing a token / held value. */ + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + delete_finrec_weakref(rt, fre); + } + /* second pass to actually free all objects. */ + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + list_del(&fre->link); + JS_FreeValueRT(rt, fre->held_val); + JS_FreeValueRT(rt, fre->token); + js_free_rt(rt, fre); + } + JS_FreeValueRT(rt, frd->cb); + js_free_rt(rt, frd); + } +} + +static void js_finrec_mark(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque(val, JS_CLASS_FINALIZATION_REGISTRY); + if (frd) { + JS_MarkValue(rt, frd->cb, mark_func); + struct list_head *el; + list_for_each(el, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + JS_MarkValue(rt, fre->held_val, mark_func); + JS_MarkValue(rt, fre->token, mark_func); + } + } +} + +static JSValue js_finrec_constructor(JSContext *ctx, JSValue new_target, int argc, JSValue *argv) +{ + if (JS_IsUndefined(new_target)) + return JS_ThrowTypeError(ctx, "constructor requires 'new'"); + JSValue cb = argv[0]; + if (!JS_IsFunction(ctx, cb)) + return JS_ThrowTypeError(ctx, "argument must be a function"); + + JSValue obj = js_create_from_ctor(ctx, new_target, JS_CLASS_FINALIZATION_REGISTRY); + if (JS_IsException(obj)) + return JS_EXCEPTION; + JSFinalizationRegistryData *frd = js_malloc(ctx, sizeof(*frd)); + if (!frd) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + init_list_head(&frd->entries); + frd->ctx = ctx; + frd->cb = js_dup(cb); + JS_SetOpaqueInternal(obj, frd); + return obj; +} + +static JSValue js_finrec_register(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY); + if (!frd) + return JS_EXCEPTION; + + JSValue target = argv[0]; + JSValue held_val = argv[1]; + // The function length needs to return 2, so the 3rd argument won't be initialized. + JSValue token = argc > 2 ? argv[2] : JS_UNDEFINED; + + if (!is_valid_weakref_target(target)) + return JS_ThrowTypeError(ctx, "invalid target"); + if (js_same_value(ctx, target, this_val)) + return JS_UNDEFINED; + if (!JS_IsUndefined(held_val) && js_same_value(ctx, target, held_val)) + return JS_ThrowTypeError(ctx, "held value cannot be the target"); + if (!JS_IsUndefined(token) && !is_valid_weakref_target(token)) + return JS_ThrowTypeError(ctx, "invalid unregister token"); + + JSFinRecEntry *fre = js_malloc(ctx, sizeof(*fre)); + if (!fre) + return JS_EXCEPTION; + JSWeakRefRecord *wr = js_malloc(ctx, sizeof(*wr)); + if (!wr) { + js_free(ctx, fre); + return JS_EXCEPTION; + } + fre->obj = this_val; + fre->target = target; + fre->held_val = js_dup(held_val); + fre->token = js_dup(token); + list_add_tail(&fre->link, &frd->entries); + wr->kind = JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY; + wr->u.fin_rec_entry = fre; + insert_weakref_record(target, wr); + + return JS_UNDEFINED; +} + +static JSValue js_finrec_unregister(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + JSFinalizationRegistryData *frd = JS_GetOpaque2(ctx, this_val, JS_CLASS_FINALIZATION_REGISTRY); + if (!frd) + return JS_EXCEPTION; + + JSValue token = argv[0]; + if (!is_valid_weakref_target(token)) + return JS_ThrowTypeError(ctx, "invalid unregister token"); + + struct list_head *el, *el1; + BOOL removed = FALSE; + list_for_each_safe(el, el1, &frd->entries) { + JSFinRecEntry *fre = list_entry(el, JSFinRecEntry, link); + if (js_same_value(ctx, fre->token, token)) { + list_del(&fre->link); + delete_finrec_weakref(ctx->rt, fre); + JS_FreeValue(ctx, fre->held_val); + JS_FreeValue(ctx, fre->token); + js_free(ctx, fre); + removed = TRUE; + } + } + + return js_bool(removed); +} + +static const JSCFunctionListEntry js_finrec_proto_funcs[] = { + JS_CFUNC_DEF("register", 2, js_finrec_register ), + JS_CFUNC_DEF("unregister", 1, js_finrec_unregister ), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "FinalizationRegistry", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_finrec_class_def[] = { + { JS_ATOM_FinalizationRegistry, js_finrec_finalizer, js_finrec_mark }, /* JS_CLASS_FINALIZATION_REGISTRY */ +}; + +static JSValue js_finrec_job(JSContext *ctx, int argc, JSValue *argv) +{ + return JS_Call(ctx, argv[0], JS_UNDEFINED, 1, &argv[1]); +} + +void JS_AddIntrinsicWeakRef(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + + /* WeakRef */ + if (!JS_IsRegisteredClass(rt, JS_CLASS_WEAK_REF)) { + init_class_range(rt, js_weakref_class_def, JS_CLASS_WEAK_REF, + countof(js_weakref_class_def)); + } + ctx->class_proto[JS_CLASS_WEAK_REF] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_WEAK_REF], + js_weakref_proto_funcs, + countof(js_weakref_proto_funcs)); + JS_NewGlobalCConstructor(ctx, "WeakRef", js_weakref_constructor, 1, ctx->class_proto[JS_CLASS_WEAK_REF]); + + /* FinalizationRegistry */ + if (!JS_IsRegisteredClass(rt, JS_CLASS_FINALIZATION_REGISTRY)) { + init_class_range(rt, js_finrec_class_def, JS_CLASS_FINALIZATION_REGISTRY, + countof(js_finrec_class_def)); + } + ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY], + js_finrec_proto_funcs, + countof(js_finrec_proto_funcs)); + JS_NewGlobalCConstructor(ctx, "FinalizationRegistry", js_finrec_constructor, 1, ctx->class_proto[JS_CLASS_FINALIZATION_REGISTRY]); +} + +static void reset_weak_ref(JSRuntime *rt, JSWeakRefRecord **first_weak_ref) +{ + JSWeakRefRecord *wr, *wr_next; + JSWeakRefData *wrd; + JSMapRecord *mr; + JSMapState *s; + JSFinRecEntry *fre; + + /* first pass to remove the records from the WeakMap/WeakSet + lists */ + for(wr = *first_weak_ref; wr != NULL; wr = wr->next_weak_ref) { + switch(wr->kind) { + case JS_WEAK_REF_KIND_MAP: + mr = wr->u.map_record; + s = mr->map; + assert(s->is_weak); + assert(!mr->empty); /* no iterator on WeakMap/WeakSet */ + list_del(&mr->hash_link); + list_del(&mr->link); + break; + case JS_WEAK_REF_KIND_WEAK_REF: + wrd = wr->u.weak_ref_data; + wrd->target = JS_UNDEFINED; + break; + case JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY: + fre = wr->u.fin_rec_entry; + list_del(&fre->link); + break; + default: + abort(); + } + } + + /* second pass to free the values to avoid modifying the weak + reference list while traversing it. */ + for(wr = *first_weak_ref; wr != NULL; wr = wr_next) { + wr_next = wr->next_weak_ref; + switch(wr->kind) { + case JS_WEAK_REF_KIND_MAP: + mr = wr->u.map_record; + JS_FreeValueRT(rt, mr->value); + js_free_rt(rt, mr); + break; + case JS_WEAK_REF_KIND_WEAK_REF: + wrd = wr->u.weak_ref_data; + JS_SetOpaqueInternal(wrd->obj, &js_weakref_sentinel); + js_free_rt(rt, wrd); + break; + case JS_WEAK_REF_KIND_FINALIZATION_REGISTRY_ENTRY: { + fre = wr->u.fin_rec_entry; + JSFinalizationRegistryData *frd = JS_GetOpaque(fre->obj, JS_CLASS_FINALIZATION_REGISTRY); + assert(frd != NULL); + /** + * During the GC sweep phase the held object might be collected first. + */ + if (!rt->in_free && (!JS_IsObject(fre->held_val) || JS_IsLiveObject(rt, fre->held_val))) { + JSValue args[2]; + args[0] = frd->cb; + args[1] = fre->held_val; + JS_EnqueueJob(frd->ctx, js_finrec_job, 2, args); + } + JS_FreeValueRT(rt, fre->held_val); + JS_FreeValueRT(rt, fre->token); + js_free_rt(rt, fre); + break; + } + default: + abort(); + } + js_free_rt(rt, wr); + } + + *first_weak_ref = NULL; /* fail safe */ +} + +static BOOL is_valid_weakref_target(JSValue val) +{ + switch (JS_VALUE_GET_TAG(val)) { + case JS_TAG_OBJECT: + break; + case JS_TAG_SYMBOL: { + // Per spec: prohibit symbols registered with Symbol.for() + JSAtomStruct *p = JS_VALUE_GET_PTR(val); + if (p->atom_type != JS_ATOM_TYPE_GLOBAL_SYMBOL) + break; + // fallthru + } + default: + return FALSE; + } + + return TRUE; +} + +static void insert_weakref_record(JSValue target, struct JSWeakRefRecord *wr) +{ + JSWeakRefRecord **pwr = get_first_weak_ref(target); + /* Add the weak reference */ + wr->next_weak_ref = *pwr; + *pwr = wr; +} + +/* Poly IC */ + +JSInlineCache *init_ic(JSContext *ctx) +{ + JSInlineCache *ic; + ic = js_malloc(ctx, sizeof(JSInlineCache)); + if (unlikely(!ic)) + goto fail; + ic->count = 0; + ic->hash_bits = 2; + ic->capacity = 1 << ic->hash_bits; + ic->hash = js_mallocz(ctx, sizeof(ic->hash[0]) * ic->capacity); + if (unlikely(!ic->hash)) + goto fail; + ic->cache = NULL; + return ic; +fail: + js_free(ctx, ic); + return NULL; +} + +int rebuild_ic(JSContext *ctx, JSInlineCache *ic) +{ + uint32_t i, count; + JSInlineCacheHashSlot *ch; + if (ic->count == 0) + goto end; + count = 0; + ic->cache = js_mallocz(ctx, sizeof(JSInlineCacheRingSlot) * ic->count); + if (unlikely(!ic->cache)) + goto fail; + for (i = 0; i < ic->capacity; i++) { + for (ch = ic->hash[i]; ch != NULL; ch = ch->next) { + ch->index = count++; + ic->cache[ch->index].atom = JS_DupAtom(ctx, ch->atom); + ic->cache[ch->index].index = 0; + } + } +end: + return 0; +fail: + return -1; +} + +int resize_ic_hash(JSContext *ctx, JSInlineCache *ic) +{ + uint32_t new_capacity, i, h; + JSInlineCacheHashSlot *ch, *ch_next; + JSInlineCacheHashSlot **new_hash; + new_capacity = 1 << (ic->hash_bits + 1); + new_hash = js_mallocz(ctx, sizeof(ic->hash[0]) * new_capacity); + if (unlikely(!new_hash)) + goto fail; + ic->hash_bits += 1; + for (i = 0; i < ic->capacity; i++) { + for (ch = ic->hash[i]; ch != NULL; ch = ch_next) { + h = get_index_hash(ch->atom, ic->hash_bits); + ch_next = ch->next; + ch->next = new_hash[h]; + new_hash[h] = ch; + } + } + js_free(ctx, ic->hash); + ic->hash = new_hash; + ic->capacity = new_capacity; + return 0; +fail: + return -1; +} + +int free_ic(JSRuntime* rt, JSInlineCache *ic) +{ + uint32_t i; + JSInlineCacheHashSlot *ch, *ch_next; + JSShape **shape, *(*shapes)[IC_CACHE_ITEM_CAPACITY]; + if (ic->cache) { + for (i = 0; i < ic->count; i++) { + shapes = &ic->cache[i].shape; + JS_FreeAtomRT(rt, ic->cache[i].atom); + for (shape = *shapes; shape != endof(*shapes); shape++) + js_free_shape_null(rt, *shape); + } + } + for (i = 0; i < ic->capacity; i++) { + for (ch = ic->hash[i]; ch != NULL; ch = ch_next) { + ch_next = ch->next; + JS_FreeAtomRT(rt, ch->atom); + js_free_rt(rt, ch); + } + } + if (ic->count > 0) + js_free_rt(rt, ic->cache); + js_free_rt(rt, ic->hash); + js_free_rt(rt, ic); + return 0; +} + +static void add_ic_slot(JSContext *ctx, JSInlineCacheUpdate *icu, + JSAtom atom, JSObject *object, uint32_t prop_offset) +{ + int32_t i; + uint32_t h; + JSInlineCacheHashSlot *ch; + JSInlineCacheRingSlot *cr; + JSInlineCache *ic; + JSShape *sh; + + if (!icu) + return; + ic = icu->ic; + if (!ic) + return; + sh = object->shape; + if (!sh->is_hashed) + return; + cr = NULL; + h = get_index_hash(atom, ic->hash_bits); + for (ch = ic->hash[h]; ch != NULL; ch = ch->next) { + if (ch->atom == atom) { + cr = ic->cache + ch->index; + break; + } + } + assert(cr != NULL); + i = cr->index; + do { + if (sh == cr->shape[i]) { + cr->prop_offset[i] = prop_offset; + goto end; + } + i = (i + 1) % countof(cr->shape); + } while (i != cr->index); + js_free_shape_null(ctx->rt, cr->shape[i]); + cr->shape[i] = js_dup_shape(sh); + cr->prop_offset[i] = prop_offset; +end: + icu->offset = ch->index; +} + +/* CallSite */ + +static void js_callsite_finalizer(JSRuntime *rt, JSValue val) +{ + JSCallSiteData *csd = JS_GetOpaque(val, JS_CLASS_CALL_SITE); + if (csd) { + JS_FreeValueRT(rt, csd->filename); + JS_FreeValueRT(rt, csd->func); + JS_FreeValueRT(rt, csd->func_name); + js_free_rt(rt, csd); + } +} + +static void js_callsite_mark(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) +{ + JSCallSiteData *csd = JS_GetOpaque(val, JS_CLASS_CALL_SITE); + if (csd) { + JS_MarkValue(rt, csd->filename, mark_func); + JS_MarkValue(rt, csd->func, mark_func); + JS_MarkValue(rt, csd->func_name, mark_func); + } +} + +static JSValue js_new_callsite(JSContext *ctx, JSCallSiteData *csd) { + JSValue obj = js_create_from_ctor(ctx, JS_UNDEFINED, JS_CLASS_CALL_SITE); + if (JS_IsException(obj)) + return JS_EXCEPTION; + + JSCallSiteData *csd1 = js_malloc(ctx, sizeof(*csd)); + if (!csd1) { + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; + } + + memcpy(csd1, csd, sizeof(*csd)); + + JS_SetOpaqueInternal(obj, csd1); + + return obj; +} + +static void js_new_callsite_data(JSContext *ctx, JSCallSiteData *csd, JSStackFrame *sf) +{ + const char *func_name_str; + JSObject *p; + + csd->func = js_dup(sf->cur_func); + /* func_name_str is UTF-8 encoded if needed */ + func_name_str = get_func_name(ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + csd->func_name = JS_NULL; + else + csd->func_name = JS_NewString(ctx, func_name_str); + JS_FreeCString(ctx, func_name_str); + if (JS_IsException(csd->func_name)) + csd->func_name = JS_NULL; + + p = JS_VALUE_GET_OBJ(sf->cur_func); + if (js_class_has_bytecode(p->class_id)) { + JSFunctionBytecode *b = p->u.func.function_bytecode; + int line_num1, col_num1; + line_num1 = find_line_num(ctx, b, + sf->cur_pc - b->byte_code_buf - 1, + &col_num1); + csd->native = FALSE; + csd->line_num = line_num1; + csd->col_num = col_num1; + csd->filename = JS_AtomToString(ctx, b->filename); + if (JS_IsException(csd->filename)) { + csd->filename = JS_NULL; + JS_FreeValue(ctx, JS_GetException(ctx)); // Clear exception. + } + } else { + csd->native = TRUE; + csd->line_num = -1; + csd->col_num = -1; + csd->filename = JS_NULL; + } +} + +static void js_new_callsite_data2(JSContext *ctx, JSCallSiteData *csd, const char *filename, int line_num, int col_num) +{ + csd->func = JS_NULL; + csd->func_name = JS_NULL; + csd->native = FALSE; + csd->line_num = line_num; + csd->col_num = col_num; + /* filename is UTF-8 encoded if needed (original argument to __JS_EvalInternal()) */ + csd->filename = JS_NewString(ctx, filename); + if (JS_IsException(csd->filename)) { + csd->filename = JS_NULL; + JS_FreeValue(ctx, JS_GetException(ctx)); // Clear exception. + } +} + +static JSValue js_callsite_getfield(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic) +{ + JSCallSiteData *csd = JS_GetOpaque2(ctx, this_val, JS_CLASS_CALL_SITE); + if (!csd) + return JS_EXCEPTION; + JSValue *field = (void *)((char *)csd + magic); + return js_dup(*field); +} + +static JSValue js_callsite_isnative(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) +{ + JSCallSiteData *csd = JS_GetOpaque2(ctx, this_val, JS_CLASS_CALL_SITE); + if (!csd) + return JS_EXCEPTION; + return JS_NewBool(ctx, csd->native); +} + +static JSValue js_callsite_getnumber(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic) +{ + JSCallSiteData *csd = JS_GetOpaque2(ctx, this_val, JS_CLASS_CALL_SITE); + if (!csd) + return JS_EXCEPTION; + int *field = (void *)((char *)csd + magic); + return JS_NewInt32(ctx, *field); +} + +static const JSCFunctionListEntry js_callsite_proto_funcs[] = { + JS_CFUNC_DEF("isNative", 0, js_callsite_isnative), + JS_CFUNC_MAGIC_DEF("getFileName", 0, js_callsite_getfield, offsetof(JSCallSiteData, filename)), + JS_CFUNC_MAGIC_DEF("getFunction", 0, js_callsite_getfield, offsetof(JSCallSiteData, func)), + JS_CFUNC_MAGIC_DEF("getFunctionName", 0, js_callsite_getfield, offsetof(JSCallSiteData, func_name)), + JS_CFUNC_MAGIC_DEF("getColumnNumber", 0, js_callsite_getnumber, offsetof(JSCallSiteData, col_num)), + JS_CFUNC_MAGIC_DEF("getLineNumber", 0, js_callsite_getnumber, offsetof(JSCallSiteData, line_num)), + JS_PROP_STRING_DEF("[Symbol.toStringTag]", "CallSite", JS_PROP_CONFIGURABLE ), +}; + +static const JSClassShortDef js_callsite_class_def[] = { + { JS_ATOM_CallSite, js_callsite_finalizer, js_callsite_mark }, /* JS_CLASS_CALL_SITE */ +}; + +static void _JS_AddIntrinsicCallSite(JSContext *ctx) +{ + JSRuntime *rt = ctx->rt; + + if (!JS_IsRegisteredClass(rt, JS_CLASS_CALL_SITE)) { + init_class_range(rt, js_callsite_class_def, JS_CLASS_CALL_SITE, + countof(js_callsite_class_def)); + } + ctx->class_proto[JS_CLASS_CALL_SITE] = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_CALL_SITE], + js_callsite_proto_funcs, + countof(js_callsite_proto_funcs)); +} + +BOOL JS_DetectModule(const char *input, size_t input_len) +{ + JSRuntime *rt; + JSContext *ctx; + JSValue val; + BOOL is_module; + + is_module = TRUE; + rt = JS_NewRuntime(); + if (!rt) + return FALSE; + ctx = JS_NewContextRaw(rt); + if (!ctx) { + JS_FreeRuntime(rt); + return FALSE; + } + JS_AddIntrinsicRegExp(ctx); // otherwise regexp literals don't parse + val = __JS_EvalInternal(ctx, JS_UNDEFINED, input, input_len, "<unnamed>", + JS_EVAL_TYPE_MODULE|JS_EVAL_FLAG_COMPILE_ONLY, -1); + if (JS_IsException(val)) { + const char *msg = JS_ToCString(ctx, rt->current_exception); + // gruesome hack to recognize exceptions from import statements; + // necessary because we don't pass in a module loader + is_module = !!strstr(msg, "ReferenceError: could not load module"); + JS_FreeCString(ctx, msg); + } + JS_FreeValue(ctx, val); + JS_FreeContext(ctx); + JS_FreeRuntime(rt); + return is_module; +} + +uintptr_t js_std_cmd(int cmd, ...) { + JSRuntime *rt; + uintptr_t rv; + va_list ap; + + rv = 0; + va_start(ap, cmd); + switch (cmd) { + case 0: // GetOpaque + rt = va_arg(ap, JSRuntime *); + rv = (uintptr_t)rt->libc_opaque; + break; + case 1: // SetOpaque + rt = va_arg(ap, JSRuntime *); + rt->libc_opaque = va_arg(ap, void *); + break; + default: + rv = -1; + } + va_end(ap); + + return rv; +} + +#undef malloc +#undef free +#undef realloc diff --git a/lib/monoucha0/monoucha/qjs/quickjs.h b/lib/monoucha0/monoucha/qjs/quickjs.h new file mode 100644 index 00000000..5ac75942 --- /dev/null +++ b/lib/monoucha0/monoucha/qjs/quickjs.h @@ -0,0 +1,1082 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2021 Fabrice Bellard + * Copyright (c) 2017-2021 Charlie Gordon + * Copyright (c) 2023 Ben Noordhuis + * Copyright (c) 2023 Saúl Ibarra Corretgé + * + * 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 QUICKJS_H +#define QUICKJS_H + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <math.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define js_force_inline inline __attribute__((always_inline)) +#define __js_printf_like(f, a) __attribute__((format(printf, f, a))) +#define JS_EXTERN __attribute__((visibility("default"))) +#else +#define js_force_inline inline +#define __js_printf_like(a, b) +#define JS_EXTERN /* nothing */ +#endif + +#define JS_BOOL int + +typedef struct JSRuntime JSRuntime; +typedef struct JSContext JSContext; +typedef struct JSObject JSObject; +typedef struct JSClass JSClass; +typedef uint32_t JSClassID; +typedef uint32_t JSAtom; + +/* Unless documented otherwise, C string pointers (`char *` or `const char *`) + are assumed to verify these constraints: + - unless a length is passed separately, the string has a null terminator + - string contents is either pure ASCII or is UTF-8 encoded. + */ + +/* Overridable purely for testing purposes; don't touch. */ +#ifndef JS_NAN_BOXING +#if INTPTR_MAX < INT64_MAX +#define JS_NAN_BOXING 1 /* Use NAN boxing for 32bit builds. */ +#endif +#endif + +enum { + /* all tags with a reference count are negative */ + JS_TAG_FIRST = -9, /* first negative tag */ + JS_TAG_BIG_INT = -9, + JS_TAG_SYMBOL = -8, + JS_TAG_STRING = -7, + JS_TAG_MODULE = -3, /* used internally */ + JS_TAG_FUNCTION_BYTECODE = -2, /* used internally */ + JS_TAG_OBJECT = -1, + + JS_TAG_INT = 0, + JS_TAG_BOOL = 1, + JS_TAG_NULL = 2, + JS_TAG_UNDEFINED = 3, + JS_TAG_UNINITIALIZED = 4, + JS_TAG_CATCH_OFFSET = 5, + JS_TAG_EXCEPTION = 6, + JS_TAG_FLOAT64 = 7, + /* any larger tag is FLOAT64 if JS_NAN_BOXING */ +}; + +#define JS_FLOAT64_NAN NAN +#define JSValueConst JSValue /* For backwards compatibility. */ + +#if defined(JS_NAN_BOXING) && JS_NAN_BOXING + +typedef uint64_t JSValue; + +#define JS_VALUE_GET_TAG(v) (int)((v) >> 32) +#define JS_VALUE_GET_INT(v) (int)(v) +#define JS_VALUE_GET_BOOL(v) (int)(v) +#define JS_VALUE_GET_PTR(v) (void *)(intptr_t)(v) + +#define JS_MKVAL(tag, val) (((uint64_t)(tag) << 32) | (uint32_t)(val)) +#define JS_MKPTR(tag, ptr) (((uint64_t)(tag) << 32) | (uintptr_t)(ptr)) + +#define JS_FLOAT64_TAG_ADDEND (0x7ff80000 - JS_TAG_FIRST + 1) /* quiet NaN encoding */ + +static inline double JS_VALUE_GET_FLOAT64(JSValue v) +{ + union { + JSValue v; + double d; + } u; + u.v = v; + u.v += (uint64_t)JS_FLOAT64_TAG_ADDEND << 32; + return u.d; +} + +#define JS_NAN (0x7ff8000000000000 - ((uint64_t)JS_FLOAT64_TAG_ADDEND << 32)) + +static inline JSValue __JS_NewFloat64(double d) +{ + union { + double d; + uint64_t u64; + } u; + JSValue v; + u.d = d; + /* normalize NaN */ + if ((u.u64 & 0x7fffffffffffffff) > 0x7ff0000000000000) + v = JS_NAN; + else + v = u.u64 - ((uint64_t)JS_FLOAT64_TAG_ADDEND << 32); + return v; +} + +#define JS_TAG_IS_FLOAT64(tag) ((unsigned)((tag) - JS_TAG_FIRST) >= (JS_TAG_FLOAT64 - JS_TAG_FIRST)) + +/* same as JS_VALUE_GET_TAG, but return JS_TAG_FLOAT64 with NaN boxing */ +static inline int JS_VALUE_GET_NORM_TAG(JSValue v) +{ + uint32_t tag; + tag = JS_VALUE_GET_TAG(v); + if (JS_TAG_IS_FLOAT64(tag)) + return JS_TAG_FLOAT64; + else + return tag; +} + +static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) +{ + uint32_t tag; + tag = JS_VALUE_GET_TAG(v); + return tag == (JS_NAN >> 32); +} + +#else /* !JS_NAN_BOXING */ + +typedef union JSValueUnion { + int32_t int32; + double float64; + void *ptr; +} JSValueUnion; + +typedef struct JSValue { + JSValueUnion u; + int64_t tag; +} JSValue; + +#define JS_VALUE_GET_TAG(v) ((int32_t)(v).tag) +/* same as JS_VALUE_GET_TAG, but return JS_TAG_FLOAT64 with NaN boxing */ +#define JS_VALUE_GET_NORM_TAG(v) JS_VALUE_GET_TAG(v) +#define JS_VALUE_GET_INT(v) ((v).u.int32) +#define JS_VALUE_GET_BOOL(v) ((v).u.int32) +#define JS_VALUE_GET_FLOAT64(v) ((v).u.float64) +#define JS_VALUE_GET_PTR(v) ((v).u.ptr) + +/* msvc doesn't understand designated initializers without /std:c++20 */ +#ifdef __cplusplus +static inline JSValue JS_MKPTR(int64_t tag, void *ptr) +{ + JSValue v; + v.u.ptr = ptr; + v.tag = tag; + return v; +} +static inline JSValue JS_MKVAL(int64_t tag, int32_t int32) +{ + JSValue v; + v.u.int32 = int32; + v.tag = tag; + return v; +} +static inline JSValue JS_MKNAN(void) +{ + JSValue v; + v.u.float64 = JS_FLOAT64_NAN; + v.tag = JS_TAG_FLOAT64; + return v; +} +/* provide as macros for consistency and backward compat reasons */ +#define JS_MKPTR(tag, ptr) JS_MKPTR(tag, ptr) +#define JS_MKVAL(tag, val) JS_MKVAL(tag, val) +#define JS_NAN JS_MKNAN() /* alas, not a constant expression */ +#else +#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag } +#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .int32 = val }, tag } +#define JS_NAN (JSValue){ (JSValueUnion){ .float64 = JS_FLOAT64_NAN }, JS_TAG_FLOAT64 } +#endif + +#define JS_TAG_IS_FLOAT64(tag) ((unsigned)(tag) == JS_TAG_FLOAT64) + +static inline JSValue __JS_NewFloat64(double d) +{ + JSValue v; + v.tag = JS_TAG_FLOAT64; + v.u.float64 = d; + return v; +} + +static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) +{ + union { + double d; + uint64_t u64; + } u; + if (v.tag != JS_TAG_FLOAT64) + return 0; + u.d = v.u.float64; + return (u.u64 & 0x7fffffffffffffff) > 0x7ff0000000000000; +} + +#endif /* !JS_NAN_BOXING */ + +#define JS_VALUE_IS_BOTH_INT(v1, v2) ((JS_VALUE_GET_TAG(v1) | JS_VALUE_GET_TAG(v2)) == 0) +#define JS_VALUE_IS_BOTH_FLOAT(v1, v2) (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v1)) && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v2))) + +#define JS_VALUE_GET_OBJ(v) ((JSObject *)JS_VALUE_GET_PTR(v)) +#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST) + +/* special values */ +#define JS_NULL JS_MKVAL(JS_TAG_NULL, 0) +#define JS_UNDEFINED JS_MKVAL(JS_TAG_UNDEFINED, 0) +#define JS_FALSE JS_MKVAL(JS_TAG_BOOL, 0) +#define JS_TRUE JS_MKVAL(JS_TAG_BOOL, 1) +#define JS_EXCEPTION JS_MKVAL(JS_TAG_EXCEPTION, 0) +#define JS_UNINITIALIZED JS_MKVAL(JS_TAG_UNINITIALIZED, 0) + +/* flags for object properties */ +#define JS_PROP_CONFIGURABLE (1 << 0) +#define JS_PROP_WRITABLE (1 << 1) +#define JS_PROP_ENUMERABLE (1 << 2) +#define JS_PROP_C_W_E (JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE) +#define JS_PROP_LENGTH (1 << 3) /* used internally in Arrays */ +#define JS_PROP_TMASK (3 << 4) /* mask for NORMAL, GETSET, VARREF, AUTOINIT */ +#define JS_PROP_NORMAL (0 << 4) +#define JS_PROP_GETSET (1 << 4) +#define JS_PROP_VARREF (2 << 4) /* used internally */ +#define JS_PROP_AUTOINIT (3 << 4) /* used internally */ + +/* flags for JS_DefineProperty */ +#define JS_PROP_HAS_SHIFT 8 +#define JS_PROP_HAS_CONFIGURABLE (1 << 8) +#define JS_PROP_HAS_WRITABLE (1 << 9) +#define JS_PROP_HAS_ENUMERABLE (1 << 10) +#define JS_PROP_HAS_GET (1 << 11) +#define JS_PROP_HAS_SET (1 << 12) +#define JS_PROP_HAS_VALUE (1 << 13) + +/* throw an exception if false would be returned + (JS_DefineProperty/JS_SetProperty) */ +#define JS_PROP_THROW (1 << 14) +/* throw an exception if false would be returned in strict mode + (JS_SetProperty) */ +#define JS_PROP_THROW_STRICT (1 << 15) + +#define JS_PROP_NO_ADD (1 << 16) /* internal use */ +#define JS_PROP_NO_EXOTIC (1 << 17) /* internal use */ +#define JS_PROP_DEFINE_PROPERTY (1 << 18) /* internal use */ +#define JS_PROP_REFLECT_DEFINE_PROPERTY (1 << 19) /* internal use */ + +#ifndef JS_DEFAULT_STACK_SIZE +#define JS_DEFAULT_STACK_SIZE (1024 * 1024) +#endif + +/* JS_Eval() flags */ +#define JS_EVAL_TYPE_GLOBAL (0 << 0) /* global code (default) */ +#define JS_EVAL_TYPE_MODULE (1 << 0) /* module code */ +#define JS_EVAL_TYPE_DIRECT (2 << 0) /* direct call (internal use) */ +#define JS_EVAL_TYPE_INDIRECT (3 << 0) /* indirect call (internal use) */ +#define JS_EVAL_TYPE_MASK (3 << 0) + +#define JS_EVAL_FLAG_STRICT (1 << 3) /* force 'strict' mode */ +#define JS_EVAL_FLAG_UNUSED (1 << 4) /* unused */ +/* compile but do not run. The result is an object with a + JS_TAG_FUNCTION_BYTECODE or JS_TAG_MODULE tag. It can be executed + with JS_EvalFunction(). */ +#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5) +/* don't include the stack frames before this eval in the Error() backtraces */ +#define JS_EVAL_FLAG_BACKTRACE_BARRIER (1 << 6) +/* allow top-level await in normal script. JS_Eval() returns a + promise. Only allowed with JS_EVAL_TYPE_GLOBAL */ +#define JS_EVAL_FLAG_ASYNC (1 << 7) + +typedef JSValue JSCFunction(JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic); +typedef JSValue JSCFunctionData(JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic, JSValue *func_data); + +typedef struct JSMallocFunctions { + void *(*js_calloc)(void *opaque, size_t count, size_t size); + void *(*js_malloc)(void *opaque, size_t size); + void (*js_free)(void *opaque, void *ptr); + void *(*js_realloc)(void *opaque, void *ptr, size_t size); + size_t (*js_malloc_usable_size)(const void *ptr); +} JSMallocFunctions; + +// Finalizers run in LIFO order at the very end of JS_FreeRuntime. +// Intended for cleanup of associated resources; the runtime itself +// is no longer usable. +typedef void JSRuntimeFinalizer(JSRuntime *rt, void *arg); + +typedef struct JSGCObjectHeader JSGCObjectHeader; + +typedef void JSRuntimeCleanUpFunc(JSRuntime *rt); + +JS_EXTERN JSRuntime *JS_NewRuntime(void); +/* info lifetime must exceed that of rt */ +JS_EXTERN void JS_SetRuntimeInfo(JSRuntime *rt, const char *info); +/* use 0 to disable memory limit */ +JS_EXTERN void JS_SetMemoryLimit(JSRuntime *rt, size_t limit); +JS_EXTERN void JS_SetDumpFlags(JSRuntime *rt, uint64_t flags); +JS_EXTERN size_t JS_GetGCThreshold(JSRuntime *rt); +JS_EXTERN void JS_SetGCThreshold(JSRuntime *rt, size_t gc_threshold); +/* use 0 to disable maximum stack size check */ +JS_EXTERN void JS_SetMaxStackSize(JSRuntime *rt, size_t stack_size); +/* should be called when changing thread to update the stack top value + used to check stack overflow. */ +JS_EXTERN void JS_UpdateStackTop(JSRuntime *rt); +JS_EXTERN JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque); +JS_EXTERN void JS_FreeRuntime(JSRuntime *rt); +JS_EXTERN void *JS_GetRuntimeOpaque(JSRuntime *rt); +JS_EXTERN void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque); +JS_EXTERN int JS_AddRuntimeFinalizer(JSRuntime *rt, + JSRuntimeFinalizer *finalizer, void *arg); +JS_EXTERN void JS_SetRuntimeCleanUpFunc(JSRuntime *rt, + JSRuntimeCleanUpFunc cleanup_func); +typedef void JS_MarkFunc(JSRuntime *rt, JSGCObjectHeader *gp); +JS_EXTERN void JS_MarkValue(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func); +JS_EXTERN void JS_RunGC(JSRuntime *rt); +JS_EXTERN JS_BOOL JS_IsLiveObject(JSRuntime *rt, JSValue obj); + +JS_EXTERN JSContext *JS_NewContext(JSRuntime *rt); +JS_EXTERN void JS_FreeContext(JSContext *s); +JS_EXTERN JSContext *JS_DupContext(JSContext *ctx); +JS_EXTERN void *JS_GetContextOpaque(JSContext *ctx); +JS_EXTERN void JS_SetContextOpaque(JSContext *ctx, void *opaque); +JS_EXTERN JSRuntime *JS_GetRuntime(JSContext *ctx); +JS_EXTERN void JS_SetClassProto(JSContext *ctx, JSClassID class_id, JSValue obj); +JS_EXTERN JSValue JS_GetClassProto(JSContext *ctx, JSClassID class_id); +JS_EXTERN JSValue JS_GetFunctionProto(JSContext *ctx); + +/* the following functions are used to select the intrinsic object to + save memory */ +JS_EXTERN JSContext *JS_NewContextRaw(JSRuntime *rt); +JS_EXTERN void JS_AddIntrinsicBaseObjects(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicDate(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicEval(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicRegExpCompiler(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicRegExp(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicJSON(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicProxy(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicMapSet(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicTypedArrays(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicPromise(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicBigInt(JSContext *ctx); +JS_EXTERN void JS_AddIntrinsicWeakRef(JSContext *ctx); +JS_EXTERN void JS_AddPerformance(JSContext *ctx); + +/* for equality comparisons and sameness */ +JS_EXTERN int JS_IsEqual(JSContext *ctx, JSValue op1, JSValue op2); +JS_EXTERN JS_BOOL JS_IsStrictEqual(JSContext *ctx, JSValue op1, JSValue op2); +JS_EXTERN JS_BOOL JS_IsSameValue(JSContext *ctx, JSValue op1, JSValue op2); +/* Similar to same-value equality, but +0 and -0 are considered equal. */ +JS_EXTERN JS_BOOL JS_IsSameValueZero(JSContext *ctx, JSValue op1, JSValue op2); + +/* Only used for running 262 tests. TODO(saghul) add build time flag. */ +JS_EXTERN JSValue js_string_codePointRange(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv); + +JS_EXTERN void *js_calloc_rt(JSRuntime *rt, size_t count, size_t size); +JS_EXTERN void *js_malloc_rt(JSRuntime *rt, size_t size); +JS_EXTERN void js_free_rt(JSRuntime *rt, void *ptr); +JS_EXTERN void *js_realloc_rt(JSRuntime *rt, void *ptr, size_t size); +JS_EXTERN size_t js_malloc_usable_size_rt(JSRuntime *rt, const void *ptr); +JS_EXTERN void *js_mallocz_rt(JSRuntime *rt, size_t size); + +JS_EXTERN void *js_calloc(JSContext *ctx, size_t count, size_t size); +JS_EXTERN void *js_malloc(JSContext *ctx, size_t size); +JS_EXTERN void js_free(JSContext *ctx, void *ptr); +JS_EXTERN void *js_realloc(JSContext *ctx, void *ptr, size_t size); +JS_EXTERN size_t js_malloc_usable_size(JSContext *ctx, const void *ptr); +JS_EXTERN void *js_realloc2(JSContext *ctx, void *ptr, size_t size, size_t *pslack); +JS_EXTERN void *js_mallocz(JSContext *ctx, size_t size); +JS_EXTERN char *js_strdup(JSContext *ctx, const char *str); +JS_EXTERN char *js_strndup(JSContext *ctx, const char *s, size_t n); + +typedef struct JSMemoryUsage { + int64_t malloc_size, malloc_limit, memory_used_size; + int64_t malloc_count; + int64_t memory_used_count; + int64_t atom_count, atom_size; + int64_t str_count, str_size; + int64_t obj_count, obj_size; + int64_t prop_count, prop_size; + int64_t shape_count, shape_size; + int64_t js_func_count, js_func_size, js_func_code_size; + int64_t js_func_pc2line_count, js_func_pc2line_size; + int64_t c_func_count, array_count; + int64_t fast_array_count, fast_array_elements; + int64_t binary_object_count, binary_object_size; +} JSMemoryUsage; + +JS_EXTERN void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s); +JS_EXTERN void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); + +/* atom support */ +#define JS_ATOM_NULL 0 + +JS_EXTERN JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len); +JS_EXTERN JSAtom JS_NewAtom(JSContext *ctx, const char *str); +JS_EXTERN JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n); +JS_EXTERN JSAtom JS_DupAtom(JSContext *ctx, JSAtom v); +JS_EXTERN void JS_FreeAtom(JSContext *ctx, JSAtom v); +JS_EXTERN void JS_FreeAtomRT(JSRuntime *rt, JSAtom v); +JS_EXTERN JSValue JS_AtomToValue(JSContext *ctx, JSAtom atom); +JS_EXTERN JSValue JS_AtomToString(JSContext *ctx, JSAtom atom); +JS_EXTERN const char *JS_AtomToCString(JSContext *ctx, JSAtom atom); +JS_EXTERN JSAtom JS_ValueToAtom(JSContext *ctx, JSValue val); + +/* object class support */ + +typedef struct JSPropertyEnum { + JS_BOOL is_enumerable; + JSAtom atom; +} JSPropertyEnum; + +typedef struct JSPropertyDescriptor { + int flags; + JSValue value; + JSValue getter; + JSValue setter; +} JSPropertyDescriptor; + +typedef struct JSClassExoticMethods { + /* Return -1 if exception (can only happen in case of Proxy object), + FALSE if the property does not exists, TRUE if it exists. If 1 is + returned, the property descriptor 'desc' is filled if != NULL. */ + int (*get_own_property)(JSContext *ctx, JSPropertyDescriptor *desc, + JSValue obj, JSAtom prop); + /* '*ptab' should hold the '*plen' property keys. Return 0 if OK, + -1 if exception. The 'is_enumerable' field is ignored. + */ + int (*get_own_property_names)(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, + JSValue obj); + /* return < 0 if exception, or TRUE/FALSE */ + int (*delete_property)(JSContext *ctx, JSValue obj, JSAtom prop); + /* return < 0 if exception or TRUE/FALSE */ + int (*define_own_property)(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, + int flags); + /* The following methods can be emulated with the previous ones, + so they are usually not needed */ + /* return < 0 if exception or TRUE/FALSE */ + int (*has_property)(JSContext *ctx, JSValue obj, JSAtom atom); + JSValue (*get_property)(JSContext *ctx, JSValue obj, JSAtom atom, + JSValue receiver); + /* return < 0 if exception or TRUE/FALSE */ + int (*set_property)(JSContext *ctx, JSValue obj, JSAtom atom, + JSValue value, JSValue receiver, int flags); +} JSClassExoticMethods; + +typedef void JSClassFinalizer(JSRuntime *rt, JSValue val); +typedef void JSClassGCMark(JSRuntime *rt, JSValue val, + JS_MarkFunc *mark_func); +#define JS_CALL_FLAG_CONSTRUCTOR (1 << 0) +typedef JSValue JSClassCall(JSContext *ctx, JSValue func_obj, + JSValue this_val, int argc, JSValue *argv, + int flags); +typedef JS_BOOL JSClassCanDestroy(JSRuntime *rt, JSValue val); + +typedef struct JSClassDef { + const char *class_name; /* pure ASCII only! */ + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + /* if call != NULL, the object is a function. If (flags & + JS_CALL_FLAG_CONSTRUCTOR) != 0, the function is called as a + constructor. In this case, 'this_val' is new.target. A + constructor call only happens if the object constructor bit is + set (see JS_SetConstructorBit()). */ + JSClassCall *call; + /* XXX: suppress this indirection ? It is here only to save memory + because only a few classes need these methods */ + JSClassExoticMethods *exotic; + JSClassCanDestroy *can_destroy; +} JSClassDef; + +#define JS_INVALID_CLASS_ID 0 +JS_EXTERN JSClassID JS_NewClassID(JSRuntime *rt, JSClassID *pclass_id); +/* Returns the class ID if `v` is an object, otherwise returns JS_INVALID_CLASS_ID. */ +JS_EXTERN JSClassID JS_GetClassID(JSValue v); +JS_EXTERN int JS_NewClass(JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def); +JS_EXTERN int JS_IsRegisteredClass(JSRuntime *rt, JSClassID class_id); + +/* value handling */ + +static js_force_inline JSValue JS_NewBool(JSContext *ctx, JS_BOOL val) +{ + (void)&ctx; + return JS_MKVAL(JS_TAG_BOOL, (val != 0)); +} + +static js_force_inline JSValue JS_NewInt32(JSContext *ctx, int32_t val) +{ + (void)&ctx; + return JS_MKVAL(JS_TAG_INT, val); +} + +static js_force_inline JSValue JS_NewFloat64(JSContext *ctx, double val) +{ + (void)&ctx; + return __JS_NewFloat64(val); +} + +static js_force_inline JSValue JS_NewCatchOffset(JSContext *ctx, int32_t val) +{ + (void)&ctx; + return JS_MKVAL(JS_TAG_CATCH_OFFSET, val); +} + +static js_force_inline JSValue JS_NewInt64(JSContext *ctx, int64_t val) +{ + JSValue v; + if (val >= INT32_MIN && val <= INT32_MAX) { + v = JS_NewInt32(ctx, (int32_t)val); + } else { + v = JS_NewFloat64(ctx, (double)val); + } + return v; +} + +static js_force_inline JSValue JS_NewUint32(JSContext *ctx, uint32_t val) +{ + JSValue v; + if (val <= INT32_MAX) { + v = JS_NewInt32(ctx, (int32_t)val); + } else { + v = JS_NewFloat64(ctx, (double)val); + } + return v; +} + +JS_EXTERN JSValue JS_NewNumber(JSContext *ctx, double d); +JS_EXTERN JSValue JS_NewBigInt64(JSContext *ctx, int64_t v); +JS_EXTERN JSValue JS_NewBigUint64(JSContext *ctx, uint64_t v); + +static inline JS_BOOL JS_IsNumber(JSValue v) +{ + int tag = JS_VALUE_GET_TAG(v); + return tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag); +} + +static inline JS_BOOL JS_IsBigInt(JSContext *ctx, JSValue v) +{ + (void)&ctx; + return JS_VALUE_GET_TAG(v) == JS_TAG_BIG_INT; +} + +static inline JS_BOOL JS_IsBool(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_BOOL; +} + +static inline JS_BOOL JS_IsNull(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_NULL; +} + +static inline JS_BOOL JS_IsUndefined(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_UNDEFINED; +} + +static inline JS_BOOL JS_IsException(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_EXCEPTION; +} + +static inline JS_BOOL JS_IsUninitialized(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_UNINITIALIZED; +} + +static inline JS_BOOL JS_IsString(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_STRING; +} + +static inline JS_BOOL JS_IsSymbol(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_SYMBOL; +} + +static inline JS_BOOL JS_IsObject(JSValue v) +{ + return JS_VALUE_GET_TAG(v) == JS_TAG_OBJECT; +} + +JS_EXTERN JSValue JS_Throw(JSContext *ctx, JSValue obj); +JS_EXTERN JSValue JS_GetException(JSContext *ctx); +JS_BOOL JS_HasException(JSContext *ctx); +JS_EXTERN JS_BOOL JS_IsError(JSContext *ctx, JSValue val); +JS_EXTERN JS_BOOL JS_IsUncatchableError(JSContext* ctx, JSValue val); +JS_EXTERN void JS_SetUncatchableError(JSContext *ctx, JSValue val, + JS_BOOL flag); +JS_EXTERN void JS_ResetUncatchableError(JSContext *ctx); +JS_EXTERN JSValue JS_NewError(JSContext *ctx); +JS_EXTERN JSValue __js_printf_like(2, 3) JS_ThrowPlainError(JSContext *ctx, const char *fmt, ...); +JS_EXTERN JSValue __js_printf_like(2, 3) JS_ThrowSyntaxError(JSContext *ctx, const char *fmt, ...); +JS_EXTERN JSValue __js_printf_like(2, 3) JS_ThrowTypeError(JSContext *ctx, const char *fmt, ...); +JS_EXTERN JSValue __js_printf_like(2, 3) JS_ThrowReferenceError(JSContext *ctx, const char *fmt, ...); +JS_EXTERN JSValue __js_printf_like(2, 3) JS_ThrowRangeError(JSContext *ctx, const char *fmt, ...); +JS_EXTERN JSValue __js_printf_like(2, 3) JS_ThrowInternalError(JSContext *ctx, const char *fmt, ...); +JS_EXTERN JSValue JS_ThrowOutOfMemory(JSContext *ctx); +JS_EXTERN void JS_FreeValue(JSContext *ctx, JSValue v); +JS_EXTERN void JS_FreeValueRT(JSRuntime *rt, JSValue v); +JS_EXTERN JSValue JS_DupValue(JSContext *ctx, JSValue v); +JS_EXTERN JSValue JS_DupValueRT(JSRuntime *rt, JSValue v); +JS_EXTERN int JS_ToBool(JSContext *ctx, JSValue val); /* return -1 for JS_EXCEPTION */ +JS_EXTERN int JS_ToInt32(JSContext *ctx, int32_t *pres, JSValue val); +static inline int JS_ToUint32(JSContext *ctx, uint32_t *pres, JSValue val) +{ + return JS_ToInt32(ctx, (int32_t*)pres, val); +} +JS_EXTERN int JS_ToInt64(JSContext *ctx, int64_t *pres, JSValue val); +JS_EXTERN int JS_ToIndex(JSContext *ctx, uint64_t *plen, JSValue val); +JS_EXTERN int JS_ToFloat64(JSContext *ctx, double *pres, JSValue val); +/* return an exception if 'val' is a Number */ +JS_EXTERN int JS_ToBigInt64(JSContext *ctx, int64_t *pres, JSValue val); +JS_EXTERN int JS_ToBigUint64(JSContext *ctx, uint64_t *pres, JSValue val); +/* same as JS_ToInt64() but allow BigInt */ +JS_EXTERN int JS_ToInt64Ext(JSContext *ctx, int64_t *pres, JSValue val); + +JS_EXTERN JSValue JS_NewStringLen(JSContext *ctx, const char *str1, size_t len1); +static inline JSValue JS_NewString(JSContext *ctx, const char *str) { + return JS_NewStringLen(ctx, str, strlen(str)); +} +JS_EXTERN JSValue JS_NewAtomString(JSContext *ctx, const char *str); +JS_EXTERN JSValue JS_ToString(JSContext *ctx, JSValue val); +JS_EXTERN JSValue JS_ToPropertyKey(JSContext *ctx, JSValue val); +JS_EXTERN const char *JS_ToCStringLen2(JSContext *ctx, size_t *plen, JSValue val1, JS_BOOL cesu8); +static inline const char *JS_ToCStringLen(JSContext *ctx, size_t *plen, JSValue val1) +{ + return JS_ToCStringLen2(ctx, plen, val1, 0); +} +static inline const char *JS_ToCString(JSContext *ctx, JSValue val1) +{ + return JS_ToCStringLen2(ctx, NULL, val1, 0); +} +JS_EXTERN void JS_FreeCString(JSContext *ctx, const char *ptr); + +JS_EXTERN JSValue JS_NewNarrowStringLen(JSContext *ctx, const char *str, + size_t len); +JS_EXTERN JS_BOOL JS_IsStringWideChar(JSValue value); +JS_EXTERN uint8_t *JS_GetNarrowStringBuffer(JSValue value); +JS_EXTERN uint32_t JS_GetStringLength(JSValue value); + +JS_EXTERN JSValue JS_NewObjectProtoClass(JSContext *ctx, JSValue proto, JSClassID class_id); +JS_EXTERN JSValue JS_NewObjectClass(JSContext *ctx, int class_id); +JS_EXTERN JSValue JS_NewObjectProto(JSContext *ctx, JSValue proto); +JS_EXTERN JSValue JS_NewObject(JSContext *ctx); + +JS_EXTERN JS_BOOL JS_IsFunction(JSContext* ctx, JSValue val); +JS_EXTERN JS_BOOL JS_IsConstructor(JSContext* ctx, JSValue val); +JS_EXTERN JS_BOOL JS_SetConstructorBit(JSContext *ctx, JSValue func_obj, JS_BOOL val); + +JS_EXTERN JSValue JS_NewArray(JSContext *ctx); +JS_EXTERN int JS_IsArray(JSContext *ctx, JSValue val); + +JS_EXTERN JSValue JS_NewDate(JSContext *ctx, double epoch_ms); + +JS_EXTERN JSValue JS_GetProperty(JSContext *ctx, JSValue this_obj, JSAtom prop); +JS_EXTERN JSValue JS_GetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx); +JS_EXTERN JSValue JS_GetPropertyInt64(JSContext *ctx, JSValue this_obj, + int64_t idx); +JS_EXTERN JSValue JS_GetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *prop); + +JS_EXTERN int JS_SetProperty(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val); +JS_EXTERN int JS_SetPropertyUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val); +JS_EXTERN int JS_SetPropertyInt64(JSContext *ctx, JSValue this_obj, + int64_t idx, JSValue val); +JS_EXTERN int JS_SetPropertyStr(JSContext *ctx, JSValue this_obj, + const char *prop, JSValue val); +JS_EXTERN int JS_HasProperty(JSContext *ctx, JSValue this_obj, JSAtom prop); +JS_EXTERN int JS_IsExtensible(JSContext *ctx, JSValue obj); +JS_EXTERN int JS_PreventExtensions(JSContext *ctx, JSValue obj); +JS_EXTERN int JS_DeleteProperty(JSContext *ctx, JSValue obj, JSAtom prop, int flags); +JS_EXTERN int JS_SetPrototype(JSContext *ctx, JSValue obj, JSValue proto_val); +JS_EXTERN JSValue JS_GetPrototype(JSContext *ctx, JSValue val); +JS_EXTERN int JS_GetLength(JSContext *ctx, JSValue obj, int64_t *pres); +JS_EXTERN int JS_SetLength(JSContext *ctx, JSValue obj, int64_t len); + +#define JS_GPN_STRING_MASK (1 << 0) +#define JS_GPN_SYMBOL_MASK (1 << 1) +#define JS_GPN_PRIVATE_MASK (1 << 2) +/* only include the enumerable properties */ +#define JS_GPN_ENUM_ONLY (1 << 4) +/* set theJSPropertyEnum.is_enumerable field */ +#define JS_GPN_SET_ENUM (1 << 5) + +JS_EXTERN int JS_GetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, JSValue obj, int flags); +JS_EXTERN int JS_GetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValue obj, JSAtom prop); +JS_EXTERN void JS_FreePropertyEnum(JSContext *ctx, JSPropertyEnum *tab, + uint32_t len); + +JS_EXTERN JSValue JS_Call(JSContext *ctx, JSValue func_obj, JSValue this_obj, + int argc, JSValue *argv); +JSValue JS_NewObjectFromCtor(JSContext *ctx, JSValueConst ctor, + JSClassID class_id); +JS_EXTERN JSValue JS_Invoke(JSContext *ctx, JSValue this_val, JSAtom atom, + int argc, JSValue *argv); +JS_EXTERN JSValue JS_CallConstructor(JSContext *ctx, JSValue func_obj, + int argc, JSValue *argv); +JS_EXTERN JSValue JS_CallConstructor2(JSContext *ctx, JSValue func_obj, + JSValue new_target, + int argc, JSValue *argv); +/* Try to detect if the input is a module. Returns TRUE if parsing the input + * as a module produces no syntax errors. It's a naive approach that is not + * wholly infallible: non-strict classic scripts may _parse_ okay as a module + * but not _execute_ as one (different runtime semantics.) Use with caution. + * |input| can be either ASCII or UTF-8 encoded source code. + */ +JS_EXTERN JS_BOOL JS_DetectModule(const char *input, size_t input_len); +/* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ +JS_EXTERN JSValue JS_Eval(JSContext *ctx, const char *input, size_t input_len, + const char *filename, int eval_flags); +/* same as JS_Eval() but with an explicit 'this_obj' parameter */ +JS_EXTERN JSValue JS_EvalThis(JSContext *ctx, JSValue this_obj, + const char *input, size_t input_len, + const char *filename, int eval_flags); +JS_EXTERN JSValue JS_GetGlobalObject(JSContext *ctx); +JS_EXTERN int JS_IsInstanceOf(JSContext *ctx, JSValue val, JSValue obj); +JS_EXTERN int JS_DefineProperty(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, + JSValue getter, JSValue setter, int flags); +JS_EXTERN int JS_DefinePropertyValue(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue val, int flags); +JS_EXTERN int JS_DefinePropertyValueUint32(JSContext *ctx, JSValue this_obj, + uint32_t idx, JSValue val, int flags); +JS_EXTERN int JS_DefinePropertyValueStr(JSContext *ctx, JSValue this_obj, + const char *prop, JSValue val, int flags); +JS_EXTERN int JS_DefinePropertyGetSet(JSContext *ctx, JSValue this_obj, + JSAtom prop, JSValue getter, JSValue setter, + int flags); +/* Only supported for custom classes, returns 0 on success < 0 otherwise. */ +JS_EXTERN int JS_SetOpaque(JSValue obj, void *opaque); +JS_EXTERN void *JS_GetOpaque(JSValue obj, JSClassID class_id); +JS_EXTERN void *JS_GetOpaque2(JSContext *ctx, JSValue obj, JSClassID class_id); +JS_EXTERN void *JS_GetAnyOpaque(JSValue obj, JSClassID *class_id); + +/* 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. */ +JS_EXTERN JSValue JS_ParseJSON(JSContext *ctx, const char *buf, size_t buf_len, + const char *filename); +JS_EXTERN JSValue JS_JSONStringify(JSContext *ctx, JSValue obj, + JSValue replacer, JSValue space0); + +typedef void JSFreeArrayBufferDataFunc(JSRuntime *rt, void *opaque, void *ptr); +JS_EXTERN JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len, + JSFreeArrayBufferDataFunc *free_func, void *opaque, + JS_BOOL is_shared); +JS_EXTERN JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len); +JS_EXTERN void JS_DetachArrayBuffer(JSContext *ctx, JSValue obj); +JS_EXTERN uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValue obj); +JS_EXTERN JS_BOOL JS_IsArrayBuffer(JSValue obj); +JS_EXTERN uint8_t *JS_GetUint8Array(JSContext *ctx, size_t *psize, JSValue obj); + +typedef enum JSTypedArrayEnum { + JS_TYPED_ARRAY_UINT8C = 0, + JS_TYPED_ARRAY_INT8, + JS_TYPED_ARRAY_UINT8, + JS_TYPED_ARRAY_INT16, + JS_TYPED_ARRAY_UINT16, + JS_TYPED_ARRAY_INT32, + JS_TYPED_ARRAY_UINT32, + JS_TYPED_ARRAY_BIG_INT64, + JS_TYPED_ARRAY_BIG_UINT64, + JS_TYPED_ARRAY_FLOAT16, + JS_TYPED_ARRAY_FLOAT32, + JS_TYPED_ARRAY_FLOAT64, +} JSTypedArrayEnum; + +JS_EXTERN JSValue JS_NewTypedArray(JSContext *ctx, int argc, JSValue *argv, + JSTypedArrayEnum array_type); +JS_EXTERN JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValue obj, + size_t *pbyte_offset, + size_t *pbyte_length, + size_t *pbytes_per_element); +JS_EXTERN JSValue JS_NewUint8Array(JSContext *ctx, uint8_t *buf, size_t len, + JSFreeArrayBufferDataFunc *free_func, void *opaque, + JS_BOOL is_shared); +/* returns -1 if not a typed array otherwise return a JSTypedArrayEnum value */ +JS_EXTERN int JS_GetTypedArrayType(JSValue obj); +JS_EXTERN JSValue JS_NewUint8ArrayCopy(JSContext *ctx, const uint8_t *buf, size_t len); +typedef struct { + void *(*sab_alloc)(void *opaque, size_t size); + void (*sab_free)(void *opaque, void *ptr); + void (*sab_dup)(void *opaque, void *ptr); + void *sab_opaque; +} JSSharedArrayBufferFunctions; +JS_EXTERN void JS_SetSharedArrayBufferFunctions(JSRuntime *rt, const JSSharedArrayBufferFunctions *sf); + +typedef enum JSPromiseStateEnum { + JS_PROMISE_PENDING, + JS_PROMISE_FULFILLED, + JS_PROMISE_REJECTED, +} JSPromiseStateEnum; + +JS_EXTERN JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs); +JS_EXTERN JSPromiseStateEnum JS_PromiseState(JSContext *ctx, JSValue promise); +JS_EXTERN JSValue JS_PromiseResult(JSContext *ctx, JSValue promise); + +JS_EXTERN JSValue JS_NewSymbol(JSContext *ctx, const char *description, JS_BOOL is_global); + +/* is_handled = TRUE means that the rejection is handled */ +typedef void JSHostPromiseRejectionTracker(JSContext *ctx, JSValue promise, + JSValue reason, + JS_BOOL is_handled, void *opaque); +JS_EXTERN void JS_SetHostPromiseRejectionTracker(JSRuntime *rt, JSHostPromiseRejectionTracker *cb, void *opaque); + +/* return != 0 if the JS code needs to be interrupted */ +typedef int JSInterruptHandler(JSRuntime *rt, void *opaque); +JS_EXTERN void JS_SetInterruptHandler(JSRuntime *rt, JSInterruptHandler *cb, void *opaque); +/* if can_block is TRUE, Atomics.wait() can be used */ +JS_EXTERN void JS_SetCanBlock(JSRuntime *rt, JS_BOOL can_block); +/* set the [IsHTMLDDA] internal slot */ +JS_EXTERN void JS_SetIsHTMLDDA(JSContext *ctx, JSValue obj); + +typedef struct JSModuleDef JSModuleDef; + +/* return the module specifier (allocated with js_malloc()) or NULL if + exception */ +typedef char *JSModuleNormalizeFunc(JSContext *ctx, + const char *module_base_name, + const char *module_name, void *opaque); +typedef JSModuleDef *JSModuleLoaderFunc(JSContext *ctx, + const char *module_name, void *opaque); + +/* module_normalize = NULL is allowed and invokes the default module + filename normalizer */ +JS_EXTERN void JS_SetModuleLoaderFunc(JSRuntime *rt, + JSModuleNormalizeFunc *module_normalize, + JSModuleLoaderFunc *module_loader, void *opaque); +/* return the import.meta object of a module */ +JS_EXTERN JSValue JS_GetImportMeta(JSContext *ctx, JSModuleDef *m); +JS_EXTERN JSAtom JS_GetModuleName(JSContext *ctx, JSModuleDef *m); +JS_EXTERN JSValue JS_GetModuleNamespace(JSContext *ctx, JSModuleDef *m); + +/* JS Job support */ + +typedef JSValue JSJobFunc(JSContext *ctx, int argc, JSValue *argv); +JS_EXTERN int JS_EnqueueJob(JSContext *ctx, JSJobFunc *job_func, int argc, JSValue *argv); + +JS_EXTERN JS_BOOL JS_IsJobPending(JSRuntime *rt); +JS_EXTERN int JS_ExecutePendingJob(JSRuntime *rt, JSContext **pctx); + +/* Structure to retrieve (de)serialized SharedArrayBuffer objects. */ +typedef struct JSSABTab { + uint8_t **tab; + size_t len; +} JSSABTab; + +/* Object Writer/Reader (currently only used to handle precompiled code) */ +#define JS_WRITE_OBJ_BYTECODE (1 << 0) /* allow function/module */ +#define JS_WRITE_OBJ_BSWAP (0) /* byte swapped output (obsolete, handled transparently) */ +#define JS_WRITE_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */ +#define JS_WRITE_OBJ_REFERENCE (1 << 3) /* allow object references to encode arbitrary object graph */ +#define JS_WRITE_OBJ_STRIP_SOURCE (1 << 4) /* do not write source code information */ +#define JS_WRITE_OBJ_STRIP_DEBUG (1 << 5) /* do not write debug information */ +JS_EXTERN uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValue obj, int flags); +JS_EXTERN uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValue obj, + int flags, JSSABTab *psab_tab); + +#define JS_READ_OBJ_BYTECODE (1 << 0) /* allow function/module */ +#define JS_READ_OBJ_ROM_DATA (0) /* avoid duplicating 'buf' data (obsolete, broken by ICs) */ +#define JS_READ_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */ +#define JS_READ_OBJ_REFERENCE (1 << 3) /* allow object references */ +JS_EXTERN JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags); +JS_EXTERN JSValue JS_ReadObject2(JSContext *ctx, const uint8_t *buf, size_t buf_len, + int flags, JSSABTab *psab_tab); +/* instantiate and evaluate a bytecode function. Only used when + reading a script or module with JS_ReadObject() */ +JS_EXTERN JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj); +/* load the dependencies of the module 'obj'. Useful when JS_ReadObject() + returns a module. */ +JS_EXTERN int JS_ResolveModule(JSContext *ctx, JSValue obj); + +/* only exported for os.Worker() */ +JS_EXTERN JSAtom JS_GetScriptOrModuleName(JSContext *ctx, int n_stack_levels); +/* only exported for os.Worker() */ +JS_EXTERN JSValue JS_LoadModule(JSContext *ctx, const char *basename, + const char *filename); + +/* C function definition */ +typedef enum JSCFunctionEnum { /* XXX: should rename for namespace isolation */ + JS_CFUNC_generic, + JS_CFUNC_generic_magic, + JS_CFUNC_constructor, + JS_CFUNC_constructor_magic, + JS_CFUNC_constructor_or_func, + JS_CFUNC_constructor_or_func_magic, + JS_CFUNC_f_f, + JS_CFUNC_f_f_f, + JS_CFUNC_getter, + JS_CFUNC_setter, + JS_CFUNC_getter_magic, + JS_CFUNC_setter_magic, + JS_CFUNC_iterator_next, +} JSCFunctionEnum; + +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 new_target, int argc, JSValue *argv, int magic); + JSCFunction *constructor_or_func; + double (*f_f)(double); + double (*f_f_f)(double, double); + JSValue (*getter)(JSContext *ctx, JSValue this_val); + JSValue (*setter)(JSContext *ctx, JSValue this_val, JSValue val); + JSValue (*getter_magic)(JSContext *ctx, JSValue this_val, int magic); + JSValue (*setter_magic)(JSContext *ctx, JSValue this_val, JSValue val, int magic); + JSValue (*iterator_next)(JSContext *ctx, JSValue this_val, + int argc, JSValue *argv, int *pdone, int magic); +} JSCFunctionType; + +JS_EXTERN JSValue JS_NewCFunction2(JSContext *ctx, JSCFunction *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic); +JS_EXTERN JSValue JS_NewCFunctionData(JSContext *ctx, JSCFunctionData *func, + int length, int magic, int data_len, + JSValue *data); + +static inline JSValue JS_NewCFunction(JSContext *ctx, JSCFunction *func, const char *name, + int length) +{ + return JS_NewCFunction2(ctx, func, name, length, JS_CFUNC_generic, 0); +} + +static inline JSValue JS_NewCFunctionMagic(JSContext *ctx, JSCFunctionMagic *func, + const char *name, + int length, JSCFunctionEnum cproto, int magic) +{ + /* Used to squelch a -Wcast-function-type warning. */ + JSCFunctionType ft; + ft.generic_magic = func; + return JS_NewCFunction2(ctx, ft.generic, name, length, cproto, magic); +} +JS_EXTERN void JS_SetConstructor(JSContext *ctx, JSValue func_obj, + JSValue proto); + +/* C property definition */ + +typedef struct JSCFunctionListEntry { + const char *name; /* pure ASCII or UTF-8 encoded */ + uint8_t prop_flags; + uint8_t def_type; + int16_t magic; + union { + struct { + uint8_t length; /* XXX: should move outside union */ + uint8_t cproto; /* XXX: should move outside union */ + JSCFunctionType cfunc; + } func; + struct { + JSCFunctionType get; + JSCFunctionType set; + } getset; + struct { + const char *name; + int base; + } alias; + struct { + const struct JSCFunctionListEntry *tab; + int len; + } prop_list; + const char *str; /* pure ASCII or UTF-8 encoded */ + int32_t i32; + int64_t i64; + uint64_t u64; + double f64; + } u; +} JSCFunctionListEntry; + +#define JS_DEF_CFUNC 0 +#define JS_DEF_CGETSET 1 +#define JS_DEF_CGETSET_MAGIC 2 +#define JS_DEF_PROP_STRING 3 +#define JS_DEF_PROP_INT32 4 +#define JS_DEF_PROP_INT64 5 +#define JS_DEF_PROP_DOUBLE 6 +#define JS_DEF_PROP_UNDEFINED 7 +#define JS_DEF_OBJECT 8 +#define JS_DEF_ALIAS 9 + +/* Note: c++ does not like nested designators */ +#define JS_CFUNC_DEF(name, length, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, 0, { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } } +#define JS_CFUNC_DEF2(name, length, func1, prop_flags) { name, prop_flags, JS_DEF_CFUNC, 0, { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } } +#define JS_CFUNC_MAGIC_DEF(name, length, func1, magic) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, magic, { .func = { length, JS_CFUNC_generic_magic, { .generic_magic = func1 } } } } +#define JS_CFUNC_SPECIAL_DEF(name, length, cproto, func1) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, 0, { .func = { length, JS_CFUNC_ ## cproto, { .cproto = func1 } } } } +#define JS_ITERATOR_NEXT_DEF(name, length, func1, magic) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_CFUNC, magic, { .func = { length, JS_CFUNC_iterator_next, { .iterator_next = func1 } } } } +#define JS_CGETSET_DEF(name, fgetter, fsetter) { name, JS_PROP_CONFIGURABLE, JS_DEF_CGETSET, 0, { .getset = { .get = { .getter = fgetter }, .set = { .setter = fsetter } } } } +#define JS_CGETSET_DEF2(name, fgetter, fsetter, prop_flags) { name, prop_flags, JS_DEF_CGETSET, 0, { .getset = { .get = { .getter = fgetter }, .set = { .setter = fsetter } } } } +#define JS_CGETSET_MAGIC_DEF(name, fgetter, fsetter, magic) { name, JS_PROP_CONFIGURABLE, JS_DEF_CGETSET_MAGIC, magic, { .getset = { .get = { .getter_magic = fgetter }, .set = { .setter_magic = fsetter } } } } +#define JS_PROP_STRING_DEF(name, cstr, prop_flags) { name, prop_flags, JS_DEF_PROP_STRING, 0, { .str = cstr } } +#define JS_PROP_INT32_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_INT32, 0, { .i32 = val } } +#define JS_PROP_INT64_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_INT64, 0, { .i64 = val } } +#define JS_PROP_DOUBLE_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_DOUBLE, 0, { .f64 = val } } +#define JS_PROP_U2D_DEF(name, val, prop_flags) { name, prop_flags, JS_DEF_PROP_DOUBLE, 0, { .u64 = val } } +#define JS_PROP_UNDEFINED_DEF(name, prop_flags) { name, prop_flags, JS_DEF_PROP_UNDEFINED, 0, { .i32 = 0 } } +#define JS_OBJECT_DEF(name, tab, len, prop_flags) { name, prop_flags, JS_DEF_OBJECT, 0, { .prop_list = { tab, len } } } +#define JS_ALIAS_DEF(name, from) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_ALIAS, 0, { .alias = { from, -1 } } } +#define JS_ALIAS_BASE_DEF(name, from, base) { name, JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE, JS_DEF_ALIAS, 0, { .alias = { from, base } } } + +JS_EXTERN void JS_SetPropertyFunctionList(JSContext *ctx, JSValue obj, + const JSCFunctionListEntry *tab, + int len); + +/* C module definition */ + +typedef int JSModuleInitFunc(JSContext *ctx, JSModuleDef *m); + +JS_EXTERN JSModuleDef *JS_NewCModule(JSContext *ctx, const char *name_str, + JSModuleInitFunc *func); +/* can only be called before the module is instantiated */ +JS_EXTERN int JS_AddModuleExport(JSContext *ctx, JSModuleDef *m, const char *name_str); +JS_EXTERN int JS_AddModuleExportList(JSContext *ctx, JSModuleDef *m, + const JSCFunctionListEntry *tab, int len); +/* can only be called after the module is instantiated */ +JS_EXTERN int JS_SetModuleExport(JSContext *ctx, JSModuleDef *m, const char *export_name, + JSValue val); +JS_EXTERN int JS_SetModuleExportList(JSContext *ctx, JSModuleDef *m, + const JSCFunctionListEntry *tab, int len); + +/* Version */ + +#define QJS_VERSION_MAJOR 0 +#define QJS_VERSION_MINOR 8 +#define QJS_VERSION_PATCH 0 +#define QJS_VERSION_SUFFIX "" + +JS_EXTERN const char* JS_GetVersion(void); + +/* Integration point for quickjs-libc.c, not for public use. */ +JS_EXTERN uintptr_t js_std_cmd(int cmd, ...); + +#undef JS_EXTERN +#undef js_force_inline +#undef __js_printf_like + +#ifdef __cplusplus +} /* extern "C" { */ +#endif + +#endif /* QUICKJS_H */ diff --git a/lib/monoucha0/monoucha/quickjs.nim b/lib/monoucha0/monoucha/quickjs.nim new file mode 100644 index 00000000..ea70672c --- /dev/null +++ b/lib/monoucha0/monoucha/quickjs.nim @@ -0,0 +1,811 @@ +{.push raises: [].} + +from std/os import parentDir + +import constcharp +import libregexp + +export constcharp + +{.passc: "-DNOT_LRE_ONLY".} + +{.passl: "-lm".} + +when not compileOption("threads"): + const CFLAGS = "-O2 -fwrapv -DMNC_NO_THREADS" +else: + const CFLAGS = "-O2 -fwrapv" + {.passl: "-lpthread".} + +{.compile("qjs/quickjs.c", CFLAGS).} +{.compile("qjs/libbf.c", CFLAGS).} + +{.passc: "-I" & currentSourcePath().parentDir().} + +const qjsheader = "qjs/quickjs.h" + +const ## all tags with a reference count are negative + JS_TAG_FIRST* = -9 ## first negative tag + JS_TAG_BIG_FLOAT* = -9 + JS_TAG_SYMBOL* = -8 + JS_TAG_STRING* = -7 + JS_TAG_SHAPE* = -6 ## used internally during GC + JS_TAG_ASYNC_FUNCTION* = -5 ## used internally during GC + JS_TAG_VAR_REF* = -4 ## used internally during GC + JS_TAG_MODULE* = -3 ## used internally + JS_TAG_FUNCTION_BYTECODE* = -2 ## used internally + JS_TAG_OBJECT* = -1 + JS_TAG_INT* = 0 + JS_TAG_BOOL* = 1 + JS_TAG_NULL* = 2 + JS_TAG_UNDEFINED* = 3 + JS_TAG_UNINITIALIZED* = 4 + JS_TAG_CATCH_OFFSET* = 5 + JS_TAG_EXCEPTION* = 6 + JS_TAG_FLOAT64* = 7 ## any larger tag is FLOAT64 if JS_NAN_BOXING + +when sizeof(int) < sizeof(int64): + {.passc: "-DJS_NAN_BOXING".} + type + JSValue* {.importc, header: qjsheader.} = distinct uint64 + + template JS_VALUE_GET_TAG*(v: untyped): int32 = + cast[int32](cast[uint64](v) shr 32) + + template JS_VALUE_GET_PTR*(v: untyped): pointer = + cast[pointer](v) + + template JS_MKVAL*(t, val: untyped): JSValue = + JSValue((cast[uint64](int64(t)) shl 32) or uint32(val)) + + template JS_MKPTR*(t, p: untyped): JSValue = + JSValue((cast[uint64](int64(t)) shl 32) or cast[uint](p)) + + proc `==`*(a, b: JSValue): bool {.borrow.} +else: + type + JSValueUnion* {.importc, header: qjsheader, union.} = object + int32*: int32 + float64*: float64 + `ptr`*: pointer + JSValue* {.importc, header: qjsheader.} = object + u*: JSValueUnion + tag*: int64 + + template JS_VALUE_GET_TAG*(v: untyped): int32 = + cast[int32](v.tag) + + template JS_VALUE_GET_PTR*(v: untyped): pointer = + cast[pointer](v.u) + + template JS_MKVAL*(t, val: untyped): JSValue = + JSValue(u: JSValueUnion(`int32`: val), tag: t) + + template JS_MKPTR*(t, p: untyped): JSValue = + JSValue(u: JSValueUnion(`ptr`: p), tag: t) + +type + JSRuntimeT {.importc: "JSRuntime", header: qjsheader, + incompleteStruct.} = object + JSContextT {.importc: "JSContext", header: qjsheader, + incompleteStruct.} = object + JSModuleDefT {.importc: "JSModuleDef", header: qjsheader, + incompleteStruct.} = object + + JSRuntime* = ptr JSRuntimeT + JSContext* = ptr JSContextT + JSModuleDef* = ptr JSModuleDefT + JSCFunction* = proc(ctx: JSContext; this_val: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} + JSCFunctionMagic* = proc(ctx: JSContext; this_val: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]; magic: cint): JSValue {.cdecl.} + JSCFunctionData* = proc(ctx: JSContext; this_val: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]; magic: cint; + func_data: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} + JSGetterFunction* = proc(ctx: JSContext; this_val: JSValue): JSValue {.cdecl.} + JSSetterFunction* = proc(ctx: JSContext; this_val, val: JSValue): + JSValue {.cdecl.} + JSGetterMagicFunction* = proc(ctx: JSContext; this_val: JSValue; magic: cint): + JSValue {.cdecl.} + JSSetterMagicFunction* = proc(ctx: JSContext; this_val, val: JSValue; + magic: cint): JSValue {.cdecl.} + JSClassID* = uint32 + JSAtom* = distinct uint32 + JSClassFinalizer* = proc(rt: JSRuntime; val: JSValue) {.cdecl.} + JSClassCheckDestroy* = proc(rt: JSRuntime; val: JSValue): JS_BOOL {.cdecl.} + JSClassGCMark* = proc(rt: JSRuntime; val: JSValue; mark_func: JS_MarkFunc) + {.cdecl.} + JS_MarkFunc* = proc(rt: JSRuntime; gp: ptr JSGCObjectHeader) {.cdecl.} + JSModuleNormalizeFunc* = proc(ctx: JSContext; module_base_name, + module_name: cstringConst; opaque: pointer): cstring {.cdecl.} + JSModuleLoaderFunc* = proc(ctx: JSContext; module_name: cstringConst, + opaque: pointer): JSModuleDef {.cdecl.} + JSJobFunc* = proc(ctx: JSContext; argc: cint; + argv: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} + JSGCObjectHeader* {.importc, header: qjsheader.} = object + JSFreeArrayBufferDataFunc* = proc(rt: JSRuntime; opaque, p: pointer) {.cdecl.} + + JSPropertyDescriptor* {.importc, header: qjsheader.} = object + flags*: cint + value*: JSValue + getter*: JSValue + setter*: JSValue + + JSClassExoticMethods* {.importc, header: qjsheader.} = object + # Return -1 if exception (can only happen in case of Proxy object), + # FALSE if the property does not exists, TRUE if it exists. If 1 is + # returned, the property descriptor 'desc' is filled if != NULL. + get_own_property*: proc(ctx: JSContext; desc: ptr JSPropertyDescriptor; + obj: JSValue; prop: JSAtom): cint {.cdecl.} + # '*ptab' should hold the '*plen' property keys. Return 0 if OK, + # -1 if exception. The 'is_enumerable' field is ignored. + get_own_property_names*: proc(ctx: JSContext; + ptab: ptr ptr UncheckedArray[JSPropertyEnum]; plen: ptr uint32; + obj: JSValue): cint {.cdecl.} + # return < 0 if exception, or TRUE/FALSE + delete_property*: proc(ctx: JSContext; obj: JSValue; prop: JSAtom): cint + {.cdecl.} + # return < 0 if exception or TRUE/FALSE + define_own_property*: proc(ctx: JSContext; this_obj: JSValue; + prop: JSAtom; val, getter, setter: JSValue; flags: cint): cint {.cdecl.} + # The following methods can be emulated with the previous ones, + # so they are usually not needed + # return < 0 if exception or TRUE/FALSE + has_property*: proc(ctx: JSContext; obj: JSValue; atom: JSAtom): cint + {.cdecl.} + get_property*: proc(ctx: JSContext; obj: JSValue; atom: JSAtom; + receiver: JSValue): JSValue {.cdecl.} + set_property*: proc(ctx: JSContext; obj: JSValue; atom: JSAtom; + value, receiver: JSValue; flags: cint): cint {.cdecl.} + + JSClassExoticMethodsConst* {.importc: "const JSClassExoticMethods *", + header: qjsheader.} = ptr JSClassExoticMethods + + JSRuntimeCleanUpFunc* {.importc.} = proc(rt: JSRuntime) {.cdecl.} + + JSClassCallP* {.importc: "JSClassCall *".} = + proc(ctx: JSContext; func_obj, this_val: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]; flags: cint): JSValue {.cdecl.} + + JSClassDef* {.importc, header: qjsheader.} = object + class_name*: cstring # pure ASCII only! + finalizer*: JSClassFinalizer + gc_mark*: JSClassGCMark + # if call != NULL, the object is a function. If (flags & + # JS_CALL_FLAG_CONSTRUCTOR) != 0, the function is called as a constructor. + # In this case, 'this_val' is new.target. A constructor call only happens + # if the object constructor bit is set (see JS_SetConstructorBit()). + call*: JSClassCallP + exotic*: JSClassExoticMethodsConst + can_destroy*: JSClassCheckDestroy + + JSClassDefConst* {.importc: "const JSClassDef *", + header: qjsheader.} = ptr JSClassDef + + JSMemoryUsage* {.importc, header: qjsheader.} = object + malloc_size*, malloc_limit*, memory_used_size*: int64 + malloc_count*: int64 + memory_used_count*: int64 + atom_count*, atom_size*: int64 + str_count*, str_size*: int64 + obj_count*, obj_size*: int64 + prop_count*, prop_size*: int64 + shape_count*, shape_size*: int64 + js_func_count*, js_func_size*, js_func_code_size*: int64 + js_func_pc2line_count*, js_func_pc2line_size*: int64 + c_func_count*, array_count*: int64 + fast_array_count*, fast_array_elements*: int64 + binary_object_count*, binary_object_size*: int64 + + JSCFunctionEnum* {.size: sizeof(uint8).} = enum + JS_CFUNC_generic, JS_CFUNC_generic_magic, JS_CFUNC_constructor, + JS_CFUNC_constructor_magic, JS_CFUNC_constructor_or_func, + JS_CFUNC_constructor_or_func_magic, JS_CFUNC_f_f, JS_CFUNC_f_f_f, + JS_CFUNC_getter, JS_CFUNC_setter, JS_CFUNC_getter_magic, + JS_CFUNC_setter_magic, JS_CFUNC_iterator_next + + JSCFunctionType* {.importc, union.} = object + generic*: JSCFunction + getter*: JSGetterFunction + setter*: JSSetterFunction + getter_magic*: JSGetterMagicFunction + setter_magic*: JSSetterMagicFunction + + JSCFunctionListEntryFunc = object + length*: uint8 + cproto*: JSCFunctionEnum + cfunc*: JSCFunctionType + + JSCFunctionListEntryGetSet = object + get*: JSCFunctionType + set*: JSCFunctionType + + JSCFunctionListEntryAlias = object + name: cstring + base: cint + + JSCFunctionListEntryPropList = object + tab: ptr UncheckedArray[JSCFunctionListEntry] + len: cint + + JSCFunctionListEntryU* {.union.} = object + `func`* {.importc: "func".}: JSCFunctionListEntryFunc + getset: JSCFunctionListEntryGetSet + alias: JSCFunctionListEntryAlias + prop_list: JSCFunctionListEntryPropList + str: cstring # pure ASCII or UTF-8 encoded + i32: int32 + i64: int64 + f64: cdouble + + JSCFunctionListEntry* {.importc.} = object + name*: cstring # pure ASCII or UTF-8 encoded + prop_flags*: uint8 + def_type*: uint8 + magic*: int16 + u* {.importc.}: JSCFunctionListEntryU + + JS_BOOL* = distinct cint + + JSPropertyEnum* {.importc.} = object + is_enumerable*: JS_BOOL + atom*: JSAtom + + JSClassEnum* {.size: sizeof(uint32).} = enum + JS_CLASS_OBJECT = 1 + JS_CLASS_ARRAY + JS_CLASS_ERROR + + JSMallocFunctions* {.importc.} = object + js_calloc*: proc(opaque: pointer; count, size: csize_t): pointer {.cdecl.} + js_malloc*: proc(opaque: pointer; size: csize_t): pointer {.cdecl.} + js_free*: proc(opaque, p: pointer) {.cdecl.} + js_realloc*: proc(opaque, p: pointer; size: csize_t): pointer + {.cdecl.} + js_malloc_usable_size*: proc(p: pointer) {.cdecl.} + + JSSharedArrayBufferFunctions* {.importc.} = object + sab_alloc*: proc(opaque: pointer; size: csize_t): pointer {.cdecl.} + sab_free*: proc(opaque: pointer) {.cdecl.} + sab_dup*: proc(opaque: pointer): pointer {.cdecl.} + sab_opaque*: pointer + + JSPromiseStateEnum* {.size: sizeof(cint).} = enum + JS_PROMISE_PENDING + JS_PROMISE_FULFILLED + JS_PROMISE_REJECTED + +func `==`*(a, b: JSAtom): bool {.borrow.} + +converter toBool*(js: JS_BOOL): bool {.inline.} = + cast[cint](js) != 0 + +converter toJSBool*(b: bool): JS_BOOL {.inline.} = + cast[JS_BOOL](cint(b)) + +converter toJSClassID*(e: JSClassEnum): JSClassID {.inline.} = + JSClassID(e) + +template JS_NULL*(): untyped = JS_MKVAL(JS_TAG_NULL, 0) +template JS_UNDEFINED*(): untyped = JS_MKVAL(JS_TAG_UNDEFINED, 0) +template JS_FALSE*(): untyped = JS_MKVAL(JS_TAG_BOOL, 0) +template JS_TRUE*(): untyped = JS_MKVAL(JS_TAG_BOOL, 1) +template JS_EXCEPTION*(): untyped = JS_MKVAL(JS_TAG_EXCEPTION, 0) +template JS_UNINITIALIZED*(): untyped = JS_MKVAL(JS_TAG_UNINITIALIZED, 0) + +const + JS_EVAL_TYPE_GLOBAL* = (0 shl 0) ## global code (default) + JS_EVAL_TYPE_MODULE* = (1 shl 0) ## module code + JS_EVAL_TYPE_DIRECT* = (2 shl 0) ## direct call (internal use) + JS_EVAL_TYPE_INDIRECT* = (3 shl 0) ## indirect call (internal use) + JS_EVAL_TYPE_MASK* = (3 shl 0) + JS_EVAL_FLAG_SHEBANG* = (1 shl 2) ## skip first line beginning with '#!' + JS_EVAL_FLAG_STRICT* = (1 shl 3) ## force 'strict' mode + JS_EVAL_FLAG_UNUSED* = (1 shl 4) ## unused + JS_EVAL_FLAG_COMPILE_ONLY* = (1 shl 5) ## internal use + +const + JS_DEF_CFUNC* = 0 + JS_DEF_CGETSET* = 1 + JS_DEF_CGETSET_MAGIC* = 2 + JS_DEF_PROP_STRING* = 3 + JS_DEF_PROP_INT32* = 4 + JS_DEF_PROP_INT64* = 5 + JS_DEF_PROP_DOUBLE* = 6 + JS_DEF_PROP_UNDEFINED* = 7 + JS_DEF_OBJECT* = 8 + JS_DEF_ALIAS* = 9 + +const + JS_PROP_CONFIGURABLE* = (1 shl 0) + JS_PROP_WRITABLE* = (1 shl 1) + JS_PROP_ENUMERABLE* = (1 shl 2) + JS_PROP_C_W_E* = (JS_PROP_CONFIGURABLE or JS_PROP_WRITABLE or + JS_PROP_ENUMERABLE) + JS_PROP_LENGTH* = (1 shl 3) # used internally in Arrays + JS_PROP_TMASK* = (3 shl 4) # mask for NORMAL, GETSET, VARREF, AUTOINIT + JS_PROP_NORMAL* = (0 shl 4) + JS_PROP_GETSET* = (1 shl 4) + JS_PROP_VARREF* = (2 shl 4) # used internally + JS_PROP_AUTOINIT* = (3 shl 4) # used internally + JS_PROP_THROW* = (1 shl 14) + +const + JS_GPN_STRING_MASK* = (1 shl 0) + JS_GPN_SYMBOL_MASK* = (1 shl 1) + JS_GPN_PRIVATE_MASK* = (1 shl 2) + JS_GPN_ENUM_ONLY* = (1 shl 3) + JS_GPN_SET_ENUM* = (1 shl 4) + + +template JS_CFUNC_DEF*(n: string; len: uint8; func1: JSCFunction): + JSCFunctionListEntry = + JSCFunctionListEntry(name: cstring(n), + prop_flags: JS_PROP_WRITABLE or JS_PROP_CONFIGURABLE, + def_type: JS_DEF_CFUNC, + u: JSCFunctionListEntryU( + `func`: JSCFunctionListEntryFunc( + length: len, + cproto: JS_CFUNC_generic, + cfunc: JSCFunctionType(generic: func1)))) + +template JS_CFUNC_DEF_NOCONF*(n: string; len: uint8; func1: JSCFunction): + JSCFunctionListEntry = + JSCFunctionListEntry(name: cstring(n), + prop_flags: JS_PROP_ENUMERABLE, + def_type: JS_DEF_CFUNC, + u: JSCFunctionListEntryU( + `func`: JSCFunctionListEntryFunc( + length: len, + cproto: JS_CFUNC_generic, + cfunc: JSCFunctionType(generic: func1)))) + +template JS_CGETSET_DEF*(n: string; fgetter: JSGetterFunction; + fsetter: JSSetterFunction): JSCFunctionListEntry = + JSCFunctionListEntry(name: cstring(n), + prop_flags: JS_PROP_CONFIGURABLE, + def_type: JS_DEF_CGETSET, + u: JSCFunctionListEntryU( + getset: JSCFunctionListEntryGetSet( + get: JSCFunctionType(getter: fgetter), + set: JSCFunctionType(setter: fsetter)))) + +template JS_CGETSET_DEF_NOCONF*(n: string; fgetter: JSGetterFunction; + fsetter: JSSetterFunction): JSCFunctionListEntry = + JSCFunctionListEntry(name: cstring(n), + prop_flags: JS_PROP_ENUMERABLE, + def_type: JS_DEF_CGETSET, + u: JSCFunctionListEntryU( + getset: JSCFunctionListEntryGetSet( + get: JSCFunctionType(getter: fgetter), + set: JSCFunctionType(setter: fsetter)))) + +template JS_CGETSET_MAGIC_DEF*(n: string; fgetter, fsetter: typed; + m: int16): JSCFunctionListEntry = + JSCFunctionListEntry(name: cstring(n), + prop_flags: JS_PROP_CONFIGURABLE, + def_type: JS_DEF_CGETSET_MAGIC, + magic: m, + u: JSCFunctionListEntryU( + getset: JSCFunctionListEntryGetSet( + get: JSCFunctionType(getter_magic: fgetter), + set: JSCFunctionType(setter_magic: fsetter)))) + +{.push header: qjsheader, importc.} + +proc JS_NewRuntime*(): JSRuntime +proc JS_SetRuntimeInfo*(rt: JSRuntime; info: cstringConst) +# use 0 to disable memory limit +proc JS_SetMemoryLimit*(rt: JSRuntime; limit: csize_t) +proc JS_SetDumpFlags*(rt: JSRuntime; flags: uint64) +proc JS_GetGCThreshold*(rt: JSRuntime): csize_t +proc JS_SetGCThreshold*(rt: JSRuntime; gc_threshold: csize_t) +proc JS_SetMaxStackSize*(rt: JSRuntime; stack_size: csize_t) +proc JS_UpdateStackTop*(rt: JSRuntime) +proc JS_NewRuntime2*(mf: ptr JSMallocFunctions; opaque: pointer): JSRuntime +proc JS_FreeRuntime*(rt: JSRuntime) +proc JS_GetRuntimeOpaque*(rt: JSRuntime): pointer +proc JS_SetRuntimeOpaque*(rt: JSRuntime; p: pointer) +proc JS_SetRuntimeCleanUpFunc*(rt: JSRuntime; + cleanup_func: JSRuntimeCleanUpFunc) +proc JS_MarkValue*(rt: JSRuntime; val: JSValue; mark_func: JS_MarkFunc) +proc JS_RunGC*(rt: JSRuntime) +proc JS_IsLiveObject*(rt: JSRuntime; obj: JSValue): JS_BOOL + +proc JS_NewContext*(rt: JSRuntime): JSContext +proc JS_FreeContext*(ctx: JSContext) +proc JS_DupContext*(ctx: JSContext): JSContext +proc JS_SetContextOpaque*(ctx: JSContext; opaque: pointer) +proc JS_GetContextOpaque*(ctx: JSContext): pointer +proc JS_GetRuntime*(ctx: JSContext): JSRuntime +proc JS_SetClassProto*(ctx: JSContext; class_id: JSClassID; obj: JSValue) +proc JS_GetClassProto*(ctx: JSContext; class_id: JSClassID): JSValue + +# the following functions are used to select the intrinsic object to save memory +proc JS_NewContextRaw*(rt: JSRuntime): JSContext +proc JS_AddIntrinsicBaseObjects*(ctx: JSContext) +proc JS_AddIntrinsicDate*(ctx: JSContext) +proc JS_AddIntrinsicEval*(ctx: JSContext) +proc JS_AddIntrinsicRegExpCompiler*(ctx: JSContext) +proc JS_AddIntrinsicRegExp*(ctx: JSContext) +proc JS_AddIntrinsicJSON*(ctx: JSContext) +proc JS_AddIntrinsicProxy*(ctx: JSContext) +proc JS_AddIntrinsicMapSet*(ctx: JSContext) +proc JS_AddIntrinsicTypedArrays*(ctx: JSContext) +proc JS_AddIntrinsicPromise*(ctx: JSContext) +proc JS_AddIntrinsicBigInt*(ctx: JSContext) +proc JS_AddIntrinsicWeakRef*(ctx: JSContext) +proc JS_AddPerformance*(ctx: JSContext) + +# for equality comparisons and sameness +proc JS_IsEqual*(ctx: JSContext; op1, op2: JSValue): cint +proc JS_IsStrictEqual*(ctx: JSContext; op1, op2: JSValue): JS_BOOL +proc JS_IsSameValue*(ctx: JSContext; op1, op2: JSValue): JS_BOOL +# Similar to same-value equality, but +0 and -0 are considered equal. +proc JS_IsSameValueZero*(ctx: JSContext; op1, op2: JSValue): JS_BOOL + +proc js_string_codePointRange*(ctx: JSContext; this_val: JSValue; + argc: cint; argv: ptr UncheckedArray[JSValue]): JSValue + +proc js_calloc_rt*(rt: JSRuntime; count, size: csize_t): pointer +proc js_malloc_rt*(rt: JSRuntime; size: csize_t): pointer +proc js_free_rt*(rt: JSRuntime; p: pointer) +proc js_realloc_rt*(rt: JSRuntime; p: pointer; size: csize_t): pointer +proc js_malloc_usable_size_rt*(rt: JSRuntime; p: pointer): csize_t +proc js_mallocz_rt*(rt: JSRuntime; size: csize_t): pointer + +proc js_calloc*(ctx: JSContext; count, size: csize_t): pointer +proc js_malloc*(ctx: JSContext; size: csize_t): pointer +proc js_free*(ctx: JSContext; p: pointer) +proc js_realloc*(ctx: JSContext; p: pointer; size: csize_t): pointer +proc js_malloc_usable_size*(ctx: JSContext; p: pointer): csize_t +proc js_mallocz*(ctx: JSContext; size: csize_t): pointer +proc js_strdup*(ctx: JSContext; str: cstringConst): cstring +proc js_strndup*(ctx: JSContext; str: cstringConst; n: csize_t): cstring + +proc JS_ComputeMemoryUsage*(rt: JSRuntime; s: out JSMemoryUsage) +# DumpMemoryUsage omitted; use getMemoryUsage instead + +# atom support +const JS_ATOM_NULL* = JSAtom(0) + +proc JS_NewAtomLen*(ctx: JSContext; str: cstringConst; len: csize_t): JSAtom +proc JS_NewAtom*(ctx: JSContext; str: cstringConst): JSAtom +proc JS_NewAtomUInt32*(ctx: JSContext; u: uint32): JSAtom +proc JS_DupAtom*(ctx: JSContext; v: JSAtom): JSAtom +proc JS_FreeAtom*(ctx: JSContext; atom: JSAtom) +proc JS_FreeAtomRT*(rt: JSRuntime; atom: JSAtom) +proc JS_AtomToValue*(ctx: JSContext; atom: JSAtom): JSValue +proc JS_AtomToString*(ctx: JSContext; atom: JSAtom): JSValue +proc JS_AtomToCString*(ctx: JSContext; atom: JSAtom): cstringConst +proc JS_ValueToAtom*(ctx: JSContext; val: JSValue): JSAtom + +# object class support +const JS_INVALID_CLASS_ID* = cint(0) + +proc JS_NewClassID*(rt: JSRuntime; pclass_id: var JSClassID): JSClassID +proc JS_GetClassID*(obj: JSValue): JSClassID +proc JS_NewClass*(rt: JSRuntime; class_id: JSClassID; + class_def: ptr JSClassDef): cint +proc JS_IsRegisteredClass*(rt: JSRuntime; class_id: JSClassID): cint + +# value handling +proc JS_NewBool*(ctx: JSContext; val: JS_BOOL): JSValue +proc JS_NewInt32*(ctx: JSContext; val: int32): JSValue +proc JS_NewCatchOffset*(ctx: JSContext; val: int32): JSValue +proc JS_NewInt64*(ctx: JSContext; val: int64): JSValue +proc JS_NewUint32*(ctx: JSContext; val: uint32): JSValue +proc JS_NewNumber*(ctx: JSContext; val: cdouble): JSValue +proc JS_NewBigInt64*(ctx: JSContext; val: int64): JSValue +proc JS_NewBigUInt64*(ctx: JSContext; val: uint64): JSValue +proc JS_NewFloat64*(ctx: JSContext; val: cdouble): JSValue +proc JS_IsNumber*(v: JSValue): JS_BOOL +proc JS_IsBigInt*(v: JSValue): JS_BOOL +proc JS_IsBool*(v: JSValue): JS_BOOL +proc JS_IsNull*(v: JSValue): JS_BOOL +proc JS_IsUndefined*(v: JSValue): JS_BOOL +proc JS_IsException*(v: JSValue): JS_BOOL +proc JS_IsUninitialized*(v: JSValue): JS_BOOL +proc JS_IsString*(v: JSValue): JS_BOOL +proc JS_IsSymbol*(v: JSValue): JS_BOOL +proc JS_IsObject*(v: JSValue): JS_BOOL + +proc JS_Throw*(ctx: JSContext; obj: JSValue): JSValue +proc JS_GetException*(ctx: JSContext): JSValue +proc JS_IsError*(ctx: JSContext; v: JSValue): JS_BOOL +proc JS_IsUncatchableError*(ctx: JSContext; val: JSValue): JS_BOOL +proc JS_SetUncatchableError*(ctx: JSContext; val: JSValue; flag: JS_BOOL) +proc JS_ResetUncatchableError*(ctx: JSContext) +proc JS_NewError*(ctx: JSContext): JSValue +proc JS_ThrowPlainError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, + discardable.} +proc JS_ThrowSyntaxError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, + discardable.} +proc JS_ThrowTypeError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, + discardable.} +proc JS_ThrowReferenceError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, + discardable.} +proc JS_ThrowRangeError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, + discardable.} +proc JS_ThrowInternalError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, + discardable.} + +proc JS_FreeValue*(ctx: JSContext; v: JSValue) +proc JS_FreeValueRT*(rt: JSRuntime; v: JSValue) +proc JS_DupValue*(ctx: JSContext; v: JSValue): JSValue +proc JS_DupValueRT*(rt: JSRuntime; v: JSValue): JSValue + +# return -1 for JS_EXCEPTION +proc JS_ToBool*(ctx: JSContext; val: JSValue): cint +proc JS_ToInt32*(ctx: JSContext; pres: var int32; val: JSValue): cint +proc JS_ToUint32*(ctx: JSContext; pres: var uint32; val: JSValue): cint +proc JS_ToInt64*(ctx: JSContext; pres: var int64; val: JSValue): cint +proc JS_ToIndex*(ctx: JSContext; plen: var uint64; val: JSValue): cint +proc JS_ToFloat64*(ctx: JSContext; pres: var float64; val: JSValue): cint +# return an exception if 'val' is a Number +proc JS_ToBigInt64*(ctx: JSContext; pres: var int64; val: JSValue): cint +proc JS_ToBigUint64*(ctx: JSContext; pres: var int64; val: JSValue): cint +# same as JS_ToInt64 but allow BigInt +proc JS_ToInt64Ext*(ctx: JSContext; pres: var int64; val: JSValue): cint + +proc JS_NewStringLen*(ctx: JSContext; str: cstringConst; len1: csize_t): JSValue +proc JS_NewString*(ctx: JSContext; str: cstring): JSValue +proc JS_NewAtomString*(ctx: JSContext; str: cstring): JSValue +proc JS_ToString*(ctx: JSContext; val: JSValue): JSValue +proc JS_ToPropertyKey*(ctx: JSContext; val: JSValue): JSValue +proc JS_ToCStringLen2*(ctx: JSContext; plen: var csize_t; val1: JSValue; + cesu8: JS_BOOL): cstringConst +proc JS_ToCStringLen*(ctx: JSContext; plen: var csize_t; val1: JSValue): + cstringConst +proc JS_ToCString*(ctx: JSContext; val1: JSValue): cstringConst +proc JS_FreeCString*(ctx: JSContext, p: cstringConst) + +# Monoucha extensions - unstable API! +proc JS_NewNarrowStringLen*(ctx: JSContext; s: cstring; len: csize_t): JSValue +proc JS_IsStringWideChar*(str: JSValue): JS_BOOL +proc JS_GetNarrowStringBuffer*(str: JSValue): ptr UncheckedArray[uint8] +proc JS_GetStringLength*(str: JSValue): uint32 + +proc JS_NewObjectProtoClass*(ctx: JSContext; proto: JSValue; + class_id: JSClassID): JSValue +proc JS_NewObjectClass*(ctx: JSContext; class_id: cint): JSValue +proc JS_NewObjectProto*(ctx: JSContext; proto: JSValue): JSValue +proc JS_NewObject*(ctx: JSContext): JSValue + +proc JS_IsFunction*(ctx: JSContext; val: JSValue): JS_BOOL +proc JS_IsConstructor*(ctx: JSContext; val: JSValue): JS_BOOL +proc JS_SetConstructorBit*(ctx: JSContext; func_obj: JSValue; + val: JS_BOOL): JS_BOOL + +proc JS_NewArray*(ctx: JSContext): JSValue +proc JS_IsArray*(ctx: JSContext; v: JSValue): cint + +proc JS_NewDate*(ctx: JSContext; epoch_ms: float64): JSValue + +proc JS_GetProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom): + JSValue +proc JS_GetPropertyStr*(ctx: JSContext; this_obj: JSValue; prop: cstring): + JSValue +proc JS_GetPropertyUint32*(ctx: JSContext; this_obj: JSValue; idx: uint32): + JSValue + +proc JS_SetProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom; + val: JSValue): cint +proc JS_SetPropertyUint32*(ctx: JSContext; this_obj: JSValue; idx: uint32; + val: JSValue): cint +proc JS_SetPropertyInt64*(ctx: JSContext; this_obj: JSValue; idx: int64; + val: JSValue): cint +proc JS_SetPropertyStr*(ctx: JSContext; this_obj: JSValue; prop: cstring; + val: JSValue): cint +proc JS_HasProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom): cint +proc JS_IsExtensible*(ctx: JSContext; obj: JSValue): cint +proc JS_PreventExtensions*(ctx: JSContext; obj: JSValue): cint +proc JS_DeleteProperty*(ctx: JSContext; obj: JSValue; prop: JSAtom; + flags: cint): cint +proc JS_SetPrototype*(ctx: JSContext; obj, proto_val: JSValue): cint +proc JS_GetPrototype*(ctx: JSContext; val: JSValue): JSValue +proc JS_GetLength*(ctx: JSContext; obj: JSValue; pres: ptr uint64): JSValue +proc JS_SetLength*(ctx: JSContext; obj: JSValue; len: uint64): cint + +proc JS_GetOwnPropertyNames*(ctx: JSContext; + ptab: ptr ptr UncheckedArray[JSPropertyEnum]; plen: ptr uint32; + obj: JSValue; flags: cint): cint +proc JS_GetOwnProperty*(ctx: JSContext; desc: ptr JSPropertyDescriptor; + obj: JSValue; prop: JSAtom): cint +proc JS_FreePropertyEnum*(ctx: JSContext; + tab: ptr UncheckedArray[JSPropertyEnum]; len: uint32) + +proc JS_Call*(ctx: JSContext; func_obj, this_obj: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]): JSValue +# Monoucha extension - unstable API! +proc JS_NewObjectFromCtor*(ctx: JSContext; ctor: JSValue; + class_id: JSClassID): JSValue +proc JS_Invoke*(ctx: JSContext; this_obj: JSValue; atom: JSAtom; + argc: cint; argv: ptr UncheckedArray[JSValue]): JSValue +proc JS_CallConstructor*(ctx: JSContext; func_obj: JSValue; argc: cint; + argv: ptr UncheckedArray[JSValue]): JSValue +proc JS_CallConstructor2*(ctx: JSContext; func_obj, new_target: JSValue; + argc: cint; argv: ptr UncheckedArray[JSValue]): JSValue +proc JS_DetectModule*(input: cstringConst; input_len: csize_t): JS_BOOL +# 'input' must be zero terminated i.e. input[input_len] = '\0'. +proc JS_Eval*(ctx: JSContext; input: cstringConst; input_len: csize_t; + filename: cstring; eval_flags: cint): JSValue +# same as JS_Eval() but with an explicit 'this_obj' parameter +proc JS_EvalThis*(ctx: JSContext; this_obj: JSValue; input: cstringConst; + input_len: csize_t; filename: cstringConst; eval_flags: cint): JSValue +proc JS_GetGlobalObject*(ctx: JSContext): JSValue +proc JS_IsInstanceOf*(ctx: JSContext; val, obj: JSValue): cint +proc JS_DefineProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom; + val, getter, setter: JSValue; flags: cint): cint +proc JS_DefinePropertyValue*(ctx: JSContext; this_obj: JSValue; + prop: JSAtom; val: JSValue; flags: cint): cint +proc JS_DefinePropertyValueUint32*(ctx: JSContext; this_obj: JSValue; + idx: uint32; val: JSValue; flags: cint): cint +proc JS_DefinePropertyValueStr*(ctx: JSContext; this_obj: JSValue; + prop: cstring; val: JSValue; flags: cint): cint +proc JS_DefinePropertyValueGetSet*(ctx: JSContext; this_obj: JSValue; + prop: JSAtom; getter, setter: JSValue; flags: cint): cint +# Always returns 1. +proc JS_SetOpaque*(obj: JSValue; opaque: pointer): cint {.discardable.} +proc JS_GetOpaque*(obj: JSValue; class_id: JSClassID): pointer +proc JS_GetOpaque2*(ctx: JSContext; obj: JSValue; class_id: JSClassID): + pointer + +# 'buf' must be zero terminated i.e. buf[buf_len] = '\0'. +proc JS_ParseJSON*(ctx: JSContext; buf: cstringConst; buf_len: csize_t; + filename: cstringConst): JSValue +proc JS_JSONStringify*(ctx: JSContext; obj, replacer, space0: JSValue): + JSValue +proc JS_NewArrayBuffer*(ctx: JSContext; buf: ptr UncheckedArray[uint8]; + len: csize_t; free_func: JSFreeArrayBufferDataFunc; opaque: pointer; + is_shared: JS_BOOL): JSValue +proc JS_NewArrayBufferCopy*(ctx: JSContext; buf: ptr UncheckedArray[uint8]; + len: csize_t): JSValue +proc JS_DetachArrayBuffer*(ctx: JSContext; obj: JSValue) +proc JS_GetArrayBuffer*(ctx: JSContext; psize: var csize_t; obj: JSValue): + ptr uint8 + +proc JS_IsArrayBuffer*(obj: JSValue): JS_BOOL +proc JS_GetUint8Array*(ctx: JSContext; psize: ptr csize_t; obj: JSValue): + ptr UncheckedArray[uint8] + +type JSTypedArrayEnum* {.size: sizeof(cint).} = enum + JS_TYPED_ARRAY_UINT8C = 0 + JS_TYPED_ARRAY_INT8 + JS_TYPED_ARRAY_UINT8 + JS_TYPED_ARRAY_INT16 + JS_TYPED_ARRAY_UINT16 + JS_TYPED_ARRAY_INT32 + JS_TYPED_ARRAY_UINT32 + JS_TYPED_ARRAY_BIG_INT64 + JS_TYPED_ARRAY_BIG_UINT64 + JS_TYPED_ARRAY_FLOAT16 + JS_TYPED_ARRAY_FLOAT32 + JS_TYPED_ARRAY_FLOAT64 + +proc JS_NewTypedArray*(ctx: JSContext; argc: cint; + argv: ptr UncheckedArray[JSValue]; array_type: JSTypedArrayEnum): JSValue +proc JS_GetTypedArrayBuffer*(ctx: JSContext; obj: JSValue; + pbyte_offset, pbyte_length, pbytes_per_element: var csize_t): JSValue +proc JS_NewUint8Array*(ctx: JSContext; buf: ptr UncheckedArray[uint8]; + len: csize_t; free_func: JSFreeArrayBufferDataFunc; opaque: pointer; + is_shared: JS_BOOL): JSValue +proc JS_GetTypedArrayType*(obj: JSValue): cint +proc JS_IsUint8Array*(obj: JSValue): JS_BOOL +proc JS_NewUint8ArrayCopy*(ctx: JSContext; buf: ptr UncheckedArray[uint8]; + len: csize_t): JSValue +proc JS_SetSharedArrayBufferFunctions*(rt: JSRuntime; + sf: ptr JSSharedArrayBufferFunctions) + +proc JS_NewPromiseCapability*(ctx: JSContext; + resolving_funcs: ptr UncheckedArray[JSValue]): JSValue +proc JS_PromiseState*(ctx: JSContext; promise: JSValue): JSPromiseStateEnum +proc JS_PromiseResult*(ctx: JSContext; promise: JSValue): JSValue + +proc JS_NewSymbol*(ctx: JSContext; description: cstringConst; + is_global: JS_BOOL): JSValue + +# is_handled = TRUE means that the rejection is handled +type JSHostPromiseRejectionTracker = + proc(ctx: JSContext; promise, reason: JSValue; is_handled: JS_BOOL; + opaque: pointer) {.cdecl.} +proc JS_SetHostPromiseRejectionTracker*(rt: JSRuntime; + cb: JSHostPromiseRejectionTracker; opaque: pointer) + + +# return != 0 if the JS code needs to be interrupted +type JSInterruptHandler* = proc(rt: JSRuntime; opaque: pointer): cint {.cdecl.} +proc JS_SetInterruptHandler*(rt: JSRuntime; cb: JSInterruptHandler; + opaque: pointer) +# if can_block is TRUE, Atomics.wait() can be used +proc JS_SetCanBlock*(rt: JSRuntime; can_block: JS_BOOL) +# set the [IsHTMLDDA] internal slot +proc JS_SetIsHTMLDDA*(ctx: JSContext; obj: JSValue) + +proc JS_SetModuleLoaderFunc*(rt: JSRuntime; + module_normalize: JSModuleNormalizeFunc; module_loader: JSModuleLoaderFunc; + opaque: pointer) +proc JS_GetImportMeta*(ctx: JSContext; m: JSModuleDef): JSValue +proc JS_GetModuleName*(ctx: JSContext; m: JSModuleDef): JSAtom +proc JS_GetModuleNamespace*(ctx: JSContext; m: JSModuleDef): JSValue + +# JS Job support +proc JS_EnqueueJob*(ctx: JSContext; job_func: JSJobFunc; argc: cint; + argv: ptr UncheckedArray[JSValue]): cint + +proc JS_IsJobPending*(rt: JSRuntime): JS_BOOL +proc JS_ExecutePendingJob*(rt: JSRuntime; pctx: out JSContext): cint + +type JSSABTab* {.importc.} = object + tab*: ptr ptr UncheckedArray[uint8] + len*: csize_t + +# Object Writer/Reader (currently only used to handle precompiled code) +const JS_WRITE_OBJ_BYTECODE* = (1 shl 0) # allow function/module +const JS_WRITE_OBJ_BSWAP* = 0 # byte swapped output (obsolete, handled + # transparently) +const JS_WRITE_OBJ_SAB* = (1 shl 2) # allow SharedArrayBuffer +const JS_WRITE_OBJ_REFERENCE* = (1 shl 3) # allow object references to encode + # arbitrary object graph +const JS_WRITE_OBJ_STRIP_SOURCE* = (1 shl 4) # do not write source code + # information +const JS_WRITE_OBJ_STRIP_DEBUG* = (1 shl 5) # do not write debug information +proc JS_WriteObject*(ctx: JSContext; psize: ptr csize_t; obj: JSValue; + flags: cint): ptr uint8 +proc JS_WriteObject2*(ctx: JSContext; psize: ptr csize_t; obj: JSValue; + flags: cint; psab_tab: ptr JSSABTab; psab_tab_len: ptr csize_t): + ptr uint8 + +const JS_READ_OBJ_BYTECODE* = (1 shl 0) # allow function/module +const JS_READ_OBJ_ROM_DATA* = 0 # avoid duplicating 'buf' data + # (obsolete, broken by ICs) +const JS_READ_OBJ_SAB* = (1 shl 2) # allow SharedArrayBuffer +const JS_READ_OBJ_REFERENCE* = (1 shl 3) # allow object references +proc JS_ReadObject*(ctx: JSContext; buf: ptr uint8; buf_len: csize_t; + flags: cint): JSValue +proc JS_ReadObject2*(ctx: JSContext; buf: ptr uint8; buf_len: csize_t; + flags: cint; psab_tab: ptr JSSABTab): JSValue +# instantiate and evaluate a bytecode function. Only used when reading a script +# or module with JS_ReadObject() +proc JS_EvalFunction*(ctx: JSContext; val: JSValue): JSValue +# load the dependencies of the module 'obj'. Useful when JS_ReadObject() returns +# a module. +proc JS_ResolveModule*(ctx: JSContext; obj: JSValue): cint + +# only exported for os.Worker() +proc JS_GetScriptOrModuleName*(ctx: JSContext; n_stack_levels: cint): JSAtom +# only exported for os.Worker() +proc JS_LoadModule*(ctx: JSContext; basename, filename: cstringConst): JSValue + +# C function definition +proc JS_NewCFunction2*(ctx: JSContext; cfunc: JSCFunction; name: cstring; + length: cint; proto: JSCFunctionEnum; magic: cint): JSValue +proc JS_NewCFunctionData*(ctx: JSContext; cfunc: JSCFunctionData; + length, magic, data_len: cint; data: ptr UncheckedArray[JSValue]): JSValue +proc JS_NewCFunction*(ctx: JSContext; cfunc: JSCFunction; name: cstring; + length: cint): JSValue +proc JS_SetConstructor*(ctx: JSContext; func_obj: JSValue; proto: JSValue) + +# C property definition +proc JS_SetPropertyFunctionList*(ctx: JSContext; obj: JSValue; + tab: ptr UncheckedArray[JSCFunctionListEntry]; len: cint) + +# C module definition +type JSModuleInitFunc* = proc(ctx: JSContext; m: JSModuleDef): cint +proc JS_NewCModule*(ctx: JSContext; name_str: cstringConst; + fun: JSModuleInitFunc): JSModuleDef +# can only be called before the module is instantiated +proc JS_AddModuleExport*(ctx: JSContext; m: JSModuleDef; name_str: cstringConst): + cint +proc JS_AddModuleExportList*(ctx: JSContext; m: JSModuleDef; + tab: ptr UncheckedArray[JSCFunctionListEntry]; len: cint): cint +# can only be called after the module is instantiated +proc JS_SetModuleExport*(ctx: JSContext; m: JSModuleDef; + export_name: cstringConst; val: JSValue): cint +proc JS_SetModuleExportList*(ctx: JSContext; m: JSModuleDef; + tab: ptr UncheckedArray[JSCFunctionListEntry]; len: cint): cint + +proc JS_GetVersion*(): cstring + +{.pop.} # header, importc +{.pop.} # raises diff --git a/lib/monoucha0/monoucha/tojs.nim b/lib/monoucha0/monoucha/tojs.nim new file mode 100644 index 00000000..2e4b73c9 --- /dev/null +++ b/lib/monoucha0/monoucha/tojs.nim @@ -0,0 +1,396 @@ +# Automatic conversion of Nim types to JavaScript types. +# +# Every conversion involves copying unless explicitly noted below. +# +# * Primitives are converted to their respective JavaScript counterparts. +# * seq is converted to a JS array. Note: this always copies the seq's contents. +# * enum is converted to its stringifier's output. +# * JSValue is returned as-is, *without* a DupValue operation. +# * JSError is converted to a new error object corresponding to the error +# it represents. +# * JSArrayBuffer, JSUint8Array are converted to a JS object without copying +# their contents. +# * NarrowString is converted to a JS narrow string (with copying). For more +# information on JS string handling, see js/jstypes.nim. +# * Finally, ref object is converted to a JS object whose opaque is the ref +# object. (See below.) +# +# Note that ref objects can be seamlessly converted to JS objects, despite +# the fact that they are managed by two separate garbage collectors. This +# works thanks to a patch in QJS and machine oil. Basically: +# +# * Nim objects registered with registerType can be paired with one (1) +# JS object each. +# * This happens on-demand, whenever the Nim object has to be converted into JS. +# * Once the conversion happened, the JS object will be kept alive until the +# Nim object is destroyed, so that JS properties on the JS object are not +# lost during a re-conversion. +# * Similarly, the Nim object is kept alive so long as the JS object is alive. +# * The patched in can_destroy hook is used to synchronize reference counts +# of the two objects; this way, no memory leak occurs. +# +# There are also toJSP variants of object converters. These work identically +# to ref object converters, except the reference count of the closest +# `ref object' ancestor is incremented/decremented when synchronizing refcounts +# with the JS object pair. + +import std/options +import std/tables + +import jserror +import jsopaque +import jstypes +import jsutils +import optshim +import quickjs + +# Convert Nim types to the corresponding JavaScript type. +# This does not work with var objects. +proc toJS*(ctx: JSContext; s: string): JSValue +proc toJS*(ctx: JSContext; n: int64): JSValue +proc toJS*(ctx: JSContext; n: int32): JSValue +proc toJS*(ctx: JSContext; n: int): JSValue +proc toJS*(ctx: JSContext; n: uint16): JSValue +proc toJS*(ctx: JSContext; n: uint32): JSValue +proc toJS*(ctx: JSContext; n: uint64): JSValue +proc toJS*(ctx: JSContext; n: float64): JSValue +proc toJS*(ctx: JSContext; b: bool): JSValue +proc toJS*[U, V](ctx: JSContext; t: Table[U, V]): JSValue +proc toJS*(ctx: JSContext; opt: Option): JSValue +proc toJS*[T, E](ctx: JSContext; opt: Result[T, E]): JSValue +proc toJS*(ctx: JSContext; s: seq): JSValue +proc toJS*[T](ctx: JSContext; s: set[T]): JSValue +proc toJS*(ctx: JSContext; t: tuple): JSValue +proc toJS*(ctx: JSContext; e: enum): JSValue +proc toJS*(ctx: JSContext; j: JSValue): JSValue +proc toJS*(ctx: JSContext; obj: ref object): JSValue +proc toJS*(ctx: JSContext; err: JSError): JSValue +proc toJS*(ctx: JSContext; abuf: JSArrayBuffer): JSValue +proc toJS*(ctx: JSContext; u8a: JSUint8Array): JSValue +proc toJS*(ctx: JSContext; ns: NarrowString): JSValue +proc toJS*(ctx: JSContext; dict: JSDict): JSValue + +# Convert Nim types to the corresponding JavaScript type, with knowledge of +# the parent object. +# This supports conversion of var object types. +# +# The idea here is to allow conversion of var objects to quasi-reference types +# by saving a pointer to their ancestor and incrementing/decrementing the +# ancestor's reference count instead. +proc toJSP*(ctx: JSContext; parent: ref object; child: var object): JSValue +proc toJSP*(ctx: JSContext; parent: ptr object; child: var object): JSValue + +# Same as toJS, but used in constructors. ctor contains the target prototype, +# used for subclassing from JS. +proc toJSNew*(ctx: JSContext; obj: ref object; ctor: JSValue): JSValue +proc toJSNew*[T, E](ctx: JSContext; opt: Result[T, E]; ctor: JSValue): JSValue + +# Avoid accidentally calling toJSP on objects that we have explicit toJS +# converters for. +template makeToJSP(typ: untyped) = + template toJSP*(ctx: JSContext; parent: ref object; child: var typ): JSValue = + toJS(ctx, child) + template toJSP*(ctx: JSContext; parent: ptr object; child: var typ): JSValue = + toJS(ctx, child) +makeToJSP(Table) +makeToJSP(Option) +makeToJSP(Result) +makeToJSP(JSValue) +makeToJSP(JSDict) + +# Note: this consumes `prop'. +proc defineProperty*(ctx: JSContext; this: JSValue; name: JSAtom; + prop: JSValue; flags = cint(0)) = + if JS_DefinePropertyValue(ctx, this, name, prop, flags) <= 0: + raise newException(Defect, "Failed to define property string") + +# Note: this consumes `prop'. +proc defineProperty(ctx: JSContext; this: JSValue; name: int64; + prop: JSValue; flags = cint(0)) = + let name = JS_NewInt64(ctx, name) + let atom = JS_ValueToAtom(ctx, name) + JS_FreeValue(ctx, name) + if unlikely(atom == JS_ATOM_NULL): + raise newException(Defect, "Failed to define property string") + ctx.defineProperty(this, atom, prop, flags) + JS_FreeAtom(ctx, atom) + +proc definePropertyC*(ctx: JSContext; this: JSValue; name: JSAtom; + prop: JSValue) = + ctx.defineProperty(this, name, prop, JS_PROP_CONFIGURABLE) + +proc defineProperty(ctx: JSContext; this: JSValue; name: string; + prop: JSValue; flags = cint(0)) = + if JS_DefinePropertyValueStr(ctx, this, cstring(name), prop, flags) <= 0: + raise newException(Defect, "Failed to define property string: " & name) + +proc definePropertyC*(ctx: JSContext; this: JSValue; name: string; + prop: JSValue) = + ctx.defineProperty(this, name, prop, JS_PROP_CONFIGURABLE) + +proc defineProperty*[T](ctx: JSContext; this: JSValue; name: string; prop: T; + flags = cint(0)) = + defineProperty(ctx, this, name, toJS(ctx, prop), flags) + +proc definePropertyE*[T](ctx: JSContext; this: JSValue; name: string; + prop: T) = + defineProperty(ctx, this, name, prop, JS_PROP_ENUMERABLE) + +proc definePropertyCW*[T](ctx: JSContext; this: JSValue; name: string; + prop: T) = + defineProperty(ctx, this, name, prop, JS_PROP_CONFIGURABLE or + JS_PROP_WRITABLE) + +proc definePropertyCWE*[T](ctx: JSContext; this: JSValue; name: string; + prop: T) = + defineProperty(ctx, this, name, prop, JS_PROP_C_W_E) + +proc newFunction*(ctx: JSContext; args: openArray[string]; body: string): + JSValue = + var paramList: seq[JSValue] = @[] + for arg in args: + paramList.add(toJS(ctx, arg)) + paramList.add(toJS(ctx, body)) + let fun = JS_CallConstructor(ctx, ctx.getOpaque().valRefs[jsvFunction], + cint(paramList.len), paramList.toJSValueArray()) + for param in paramList: + JS_FreeValue(ctx, param) + return fun + +proc toJS*(ctx: JSContext; s: cstring): JSValue = + return JS_NewString(ctx, s) + +proc toJS*(ctx: JSContext; s: string): JSValue = + return JS_NewStringLen(ctx, cstring(s), csize_t(s.len)) + +proc toJS*(ctx: JSContext; n: int32): JSValue = + return JS_NewInt32(ctx, n) + +proc toJS*(ctx: JSContext; n: int64): JSValue = + return JS_NewInt64(ctx, n) + +proc toJS*(ctx: JSContext; n: int): JSValue = + when sizeof(int) > 4: + return toJS(ctx, int64(n)) + else: + return toJS(ctx, int32(n)) + +proc toJS*(ctx: JSContext; n: uint16): JSValue = + return JS_NewUint32(ctx, uint32(n)) + +proc toJS*(ctx: JSContext; n: uint32): JSValue = + return JS_NewUint32(ctx, n) + +proc toJS*(ctx: JSContext; n: uint64): JSValue = + #TODO this is incorrect + return JS_NewFloat64(ctx, float64(n)) + +proc toJS*(ctx: JSContext; n: float64): JSValue = + return JS_NewFloat64(ctx, n) + +proc toJS*(ctx: JSContext; b: bool): JSValue = + return JS_NewBool(ctx, b) + +proc toJS*[U, V](ctx: JSContext; t: Table[U, V]): JSValue = + let obj = JS_NewObject(ctx) + if not JS_IsException(obj): + for k, v in t: + definePropertyCWE(ctx, obj, k, v) + return obj + +proc toJS*(ctx: JSContext; opt: Option): JSValue = + if opt.isSome: + return toJS(ctx, opt.get) + return JS_NULL + +proc toJS*[T, E](ctx: JSContext; opt: Result[T, E]): JSValue = + if opt.isSome: + when not (T is void): + return toJS(ctx, opt.get) + else: + return JS_UNDEFINED + else: + when not (E is void): + if opt.error != nil: + return JS_Throw(ctx, toJS(ctx, opt.error)) + return JS_EXCEPTION + +proc toJS*(ctx: JSContext; s: seq): JSValue = + let a = JS_NewArray(ctx) + if not JS_IsException(a): + for i, x in s: + let val = toJS(ctx, x) + if JS_IsException(val): + return val + ctx.defineProperty(a, int64(i), val, JS_PROP_C_W_E or JS_PROP_THROW) + return a + +proc toJS*[T](ctx: JSContext; s: set[T]): JSValue = + #TODO this is a bit lazy :p + var x = newSeq[T]() + for e in s: + x.add(e) + var a = toJS(ctx, x) + if JS_IsException(a): + return a + let ret = JS_CallConstructor(ctx, ctx.getOpaque().valRefs[jsvSet], 1, + a.toJSValueArray()) + JS_FreeValue(ctx, a) + return ret + +proc toJS(ctx: JSContext; t: tuple): JSValue = + let a = JS_NewArray(ctx) + if not JS_IsException(a): + var i = 0 + for f in t.fields: + let val = toJS(ctx, f) + if JS_IsException(val): + return val + ctx.defineProperty(a, int64(i), val, JS_PROP_C_W_E or JS_PROP_THROW) + inc i + return a + +proc toJSP0(ctx: JSContext; p, tp: pointer; ctor: JSValue; + needsref: var bool): JSValue = + JS_GetRuntime(ctx).getOpaque().plist.withValue(p, obj): + # a JSValue already points to this object. + return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, obj[])) + let ctxOpaque = ctx.getOpaque() + let class = ctxOpaque.typemap.getOrDefault(tp, 0) + let jsObj = JS_NewObjectFromCtor(ctx, ctor, class) + if JS_IsException(jsObj): + return jsObj + setOpaque(ctx, jsObj, p) + # We are constructing a new JS object, so we must add unforgeable properties + # here. + if int(class) < ctxOpaque.unforgeable.len and + ctxOpaque.unforgeable[int(class)].len > 0: + let ufp0 = addr ctxOpaque.unforgeable[int(class)][0] + let ufp = cast[ptr UncheckedArray[JSCFunctionListEntry]](ufp0) + JS_SetPropertyFunctionList(ctx, jsObj, ufp, + cint(ctxOpaque.unforgeable[int(class)].len)) + needsref = true + if unlikely(ctxOpaque.htmldda == class): + JS_SetIsHTMLDDA(ctx, jsObj) + return jsObj + +# Get a unique pointer for each type. +proc getTypePtr*[T](x: T): pointer = + when T is RootRef: + # I'm so sorry. + # (This dereferences the object's first member, m_type. Probably.) + return cast[ptr pointer](x)[] + elif T is RootObj: + return cast[pointer](x) + else: + return getTypeInfo(x) + +func getTypePtr*(t: typedesc[ref object]): pointer = + var x = t() + return getTypePtr(x) + +func getTypePtr*(t: type): pointer = + var x: t + return getTypePtr(x) + +proc toJSRefObj(ctx: JSContext; obj: ref object): JSValue = + if obj == nil: + return JS_NULL + let p = cast[pointer](obj) + let tp = getTypePtr(obj) + var needsref = false + let val = toJSP0(ctx, p, tp, JS_UNDEFINED, needsref) + if needsref: + GC_ref(obj) + return val + +proc toJS*(ctx: JSContext; obj: ref object): JSValue = + return toJSRefObj(ctx, obj) + +proc toJSNew*(ctx: JSContext; obj: ref object; ctor: JSValue): JSValue = + if obj == nil: + return JS_NULL + let p = cast[pointer](obj) + let tp = getTypePtr(obj) + var needsref = false + let val = toJSP0(ctx, p, tp, ctor, needsref) + if needsref: + GC_ref(obj) + return val + +proc toJSNew[T, E](ctx: JSContext; opt: Result[T, E]; ctor: JSValue): JSValue = + if opt.isSome: + when not (T is void): + return toJSNew(ctx, opt.get, ctor) + else: + return JS_UNDEFINED + else: + when not (E is void): + let res = toJS(ctx, opt.error) + if not JS_IsNull(res): + return JS_Throw(ctx, res) + else: + return JS_NULL + +proc toJS(ctx: JSContext; e: enum): JSValue = + return toJS(ctx, $e) + +proc toJS(ctx: JSContext; j: JSValue): JSValue = + return j + +proc toJS*(ctx: JSContext; err: JSError): JSValue = + if err == nil: + return JS_EXCEPTION + if err.e notin QuickJSErrors: + return toJSRefObj(ctx, err) + var msg = toJS(ctx, err.message) + if JS_IsException(msg): + return msg + let ctor = ctx.getOpaque().errCtorRefs[err.e] + let ret = JS_CallConstructor(ctx, ctor, 1, msg.toJSValueArray()) + JS_FreeValue(ctx, msg) + return ret + +proc toJS*(ctx: JSContext; abuf: JSArrayBuffer): JSValue = + return JS_NewArrayBuffer(ctx, abuf.p, abuf.len, abuf.dealloc, nil, false) + +proc toJS*(ctx: JSContext; u8a: JSUint8Array): JSValue = + let jsabuf = toJS(ctx, u8a.abuf) + let ctor = ctx.getOpaque().valRefs[jsvUint8Array] + let ret = JS_CallConstructor(ctx, ctor, 1, jsabuf.toJSValueArray()) + JS_FreeValue(ctx, jsabuf) + return ret + +proc toJS*(ctx: JSContext; ns: NarrowString): JSValue = + return JS_NewNarrowStringLen(ctx, cstring(ns), csize_t(string(ns).len)) + +proc toJS*(ctx: JSContext; dict: JSDict): JSValue = + let obj = JS_NewObject(ctx) + if JS_IsException(obj): + return obj + for k, v in dict.fieldPairs: + when k != "toFree": + ctx.defineProperty(obj, k, v) + return obj + +proc toJSP(ctx: JSContext; parent: ref object; child: var object): JSValue = + let p = addr child + # Save parent as the original ancestor for this tree. + JS_GetRuntime(ctx).getOpaque().parentMap[p] = cast[pointer](parent) + let tp = getTypePtr(child) + var needsref = false + let val = toJSP0(ctx, p, tp, JS_UNDEFINED, needsref) + if needsref: + GC_ref(parent) + return val + +proc toJSP(ctx: JSContext; parent: ptr object; child: var object): JSValue = + let p = addr child + # Increment the reference count of parent's root ancestor, and save the + # increment/decrement callbacks for the child as well. + let rtOpaque = JS_GetRuntime(ctx).getOpaque() + let grandparent = rtOpaque.refmap[parent] + GC_ref(cast[RootRef](grandparent)) + rtOpaque.parentMap[p] = grandparent + let tp = getTypePtr(child) + return toJSP0(ctx, p, tp) diff --git a/lib/monoucha0/monoucha/version.nim b/lib/monoucha0/monoucha/version.nim new file mode 100644 index 00000000..b485e67a --- /dev/null +++ b/lib/monoucha0/monoucha/version.nim @@ -0,0 +1,3 @@ +const Major* = 0 +const Minor* = 9 +const Patch* = 1 diff --git a/lib/monoucha0/nim.cfg b/lib/monoucha0/nim.cfg new file mode 100644 index 00000000..e0c8273c --- /dev/null +++ b/lib/monoucha0/nim.cfg @@ -0,0 +1,3 @@ +--import:"monoucha/eprint" +--styleCheck:usages +--styleCheck:hint diff --git a/lib/monoucha0/test/basic.nim b/lib/monoucha0/test/basic.nim new file mode 100644 index 00000000..25161b1a --- /dev/null +++ b/lib/monoucha0/test/basic.nim @@ -0,0 +1,26 @@ +import std/unittest + +import monoucha/fromjs +import monoucha/javascript +import monoucha/optshim + +type MyGlobal = ref object + s: string + +proc testFun(x: MyGlobal): string {.jsfunc.} = + return "Hello, " & x.s + +test "hello JS": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let global = MyGlobal(s: "world!") + ctx.registerType(MyGlobal, asglobal = true) + ctx.setGlobal(global) + const code = "testFun()" + let val = ctx.eval(code, "<test>", 0) + var res: string + check ctx.fromJS(val, res).isSome + check res == "Hello, world!" + JS_FreeValue(ctx, val) + ctx.free() + rt.free() diff --git a/lib/monoucha0/test/etc.nim b/lib/monoucha0/test/etc.nim new file mode 100644 index 00000000..47f123a6 --- /dev/null +++ b/lib/monoucha0/test/etc.nim @@ -0,0 +1,150 @@ +import std/unittest + +import monoucha/fromjs +import monoucha/javascript +import monoucha/jspropenumlist +import monoucha/jstypes +import monoucha/optshim +import monoucha/quickjs +import monoucha/tojs + +type TestEnum = enum + teA = "a", teB = "b", teC = "c" + +type TestEnum2 = enum + te2C = "c", te2B = "b", te2A = "a" + +test "enums": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let val = ctx.toJS(teB) + var e: TestEnum + assert ctx.fromJS(val, e).isSome + assert e == teB + var e2: TestEnum2 + let val2 = ctx.toJS(te2A) + assert ctx.fromJS(val2, e2).isSome + assert e2 == te2A + ctx.free() + rt.free() + +test "enums null": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let val = ctx.toJS("b\0c") + var e: TestEnum + assert ctx.fromJS(val, e).isNone + ctx.free() + rt.free() + +type + TestDict0 = object of JSDict + a {.jsdefault: true.}: bool + b: int + c {.jsdefault.}: TestEnum + d: TestDict1 + e {.jsdefault.}: int32 + f {.jsdefault.}: Option[JSValue] + + TestDict1 = object of JSDict + a: Option[JSValue] + + TestDict2 = object of JSDict + a {.jsdefault.}: Option[JSValue] + b {.jsdefault: 2.}: int + c {.jsdefault.}: string + + TestDict3 = object of TestDict2 + +proc default(e: typedesc[TestEnum]): TestEnum = + return teB + +test "jsdict undefined missing fields": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + var res: TestDict0 + assert ctx.fromJS(JS_UNDEFINED, res).isNone + ctx.free() + rt.free() + +test "optional jsdict undefined": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + var res: TestDict2 + assert ctx.fromJS(JS_UNDEFINED, res).isSome, ctx.getExceptionMsg() + ctx.free() + rt.free() + +test "optional jsdict inherited": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + var res: TestDict3 + assert ctx.fromJS(JS_UNDEFINED, res).isSome, ctx.getExceptionMsg() + assert res.b == 2 + ctx.free() + rt.free() + +proc subroutine(ctx: JSContext; val: JSValue) = + var res: TestDict0 + assert ctx.fromJS(val, res).isSome, ctx.getExceptionMsg() + discard ctx.eval("delete val.f", "<input>") + assert res.a + assert res.b == 1 + assert res.c == teB + assert res.e == 0 + assert res.d.a.isNone + ctx.defineProperty(res.f.get, "x", JS_NewInt32(ctx, 9)) + +test "jsdict transitive JSValue descendant": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + const code = """ +const val = { + b: 1, + d: { a: null }, + f: { x: 1 } +} +val""" + let val = ctx.eval(code, "<input>") + ctx.subroutine(val) + GC_fullCollect() + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +test "jspropenumlist": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + var list = newJSPropertyEnumList(ctx, 0) + list.add(1) + list.add("hi") + list.add(3) + list.add(4) + assert list.len == 4 + js_free(ctx, list.buffer) + ctx.free() + rt.free() + +test "fromjs-seq": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + var test = @[1, 2, 3, 4] + let jsTest = ctx.toJS(test) + var test2: seq[int] + assert ctx.fromJS(jsTest, test2).isSome + assert test2 == test + JS_FreeValue(ctx, jsTest) + ctx.free() + rt.free() + +test "fromjs-tuple": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + var test = (2, "hi") + let jsTest = ctx.toJS(test) + var test2: tuple[n: int; s: string] + assert ctx.fromJS(jsTest, test2).isSome + assert test2 == test + JS_FreeValue(ctx, jsTest) + ctx.free() + rt.free() diff --git a/lib/monoucha0/test/jsfile.nim b/lib/monoucha0/test/jsfile.nim new file mode 100644 index 00000000..8a17f024 --- /dev/null +++ b/lib/monoucha0/test/jsfile.nim @@ -0,0 +1,25 @@ +import std/posix +import std/unittest + +import monoucha/javascript +import monoucha/tojs + +type + JSFile = ref object + +jsDestructor(JSFile) + +proc newJSFile(): JSFile {.jsctor.} = + return JSFile() + +test "jsfin: object finalizers": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(JSFile) + const code = """ +{ const file = new JSFile(); } +const file = new JSFile(); + """ + JS_FreeValue(ctx, ctx.eval(code)) + ctx.free() + rt.free() diff --git a/lib/monoucha0/test/manual.nim b/lib/monoucha0/test/manual.nim new file mode 100644 index 00000000..2aa8c684 --- /dev/null +++ b/lib/monoucha0/test/manual.nim @@ -0,0 +1,296 @@ +import std/os +import std/posix +import std/strutils +import std/unittest + +import monoucha/fromjs +import monoucha/javascript +import monoucha/optshim +import monoucha/tojs + +test "Hello, world": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + const code = "'Hello from JS!'" + let val = ctx.eval(code) + var res: string + check ctx.fromJS(val, res).isSome + check res == "Hello from JS!" + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +proc evalConvert[T](ctx: JSContext; code: string; + file = "<input>"): Result[T, string] = + let val = ctx.eval(code, file) + defer: JS_FreeValue(ctx, val) # unref result before returning + var res: T + if ctx.fromJS(val, res).isNone: + # Conversion failed; return the exception message. + return err(ctx.getExceptionMsg()) + # All ok! Return the converted object. + return ok(res) + +test "Error handling": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + const code = "abcd" + let res = ctx.eval(code, "<test>") + check JS_IsException(res) + const ex = """ +ReferenceError: abcd is not defined + at <eval> (<test>:1:1) +""" + check ctx.getExceptionMsg() == ex + check evalConvert[string](ctx, code, "<test>").error == ex + JS_FreeValue(ctx, res) + ctx.free() + rt.free() + +type Earth = ref object + +proc jsAssert(earth: Earth; pred: bool) {.jsfunc: "assert".} = + assert pred + +test "registerType: registering type interfaces": + type Moon = ref object + jsDestructor(Moon) + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(Moon) + const code = "Moon" + let val = ctx.eval(code) + var res: string + check ctx.fromJS(val, res).isSome + check res == """ +function Moon() { + [native code] +}""" + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +test "Global objects": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let earth = Earth() + ctx.registerType(Earth, asglobal = true) + ctx.setGlobal(earth) + const code = "assert(globalThis instanceof Earth)" + let val = ctx.eval(code) + check not JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +test "Inheritance": + type + Planet = ref object of RootObj + Earth = ref object of Planet + Moon = ref object of Planet + jsDestructor(Moon) + jsDestructor(Planet) + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let planetCID = ctx.registerType(Planet) + ctx.registerType(Earth, parent = planetCID, asglobal = true) + ctx.registerType(Moon, parent = planetCID) + ctx.setGlobal(Earth()) + const code = "assert(globalThis instanceof Planet)" + let val = ctx.eval(code) + check not JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +test "jsget, jsset: basic property reflectors": + type + Moon = ref object + + Earth = ref object + moon {.jsget.}: Moon + name {.jsgetset.}: string + population {.jsset.}: int64 + + jsDestructor(Moon) + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let earth = Earth(moon: Moon(), population: 1, name: "Earth") + ctx.registerType(Earth, asglobal = true) + ctx.registerType(Moon) + ctx.setGlobal(earth) + const code = """ +globalThis.population = 8e9; +"name: " + globalThis.name + ", moon: " + globalThis.moon; +""" + let val = ctx.eval(code) + var res: string + check ctx.fromJS(val, res).isSome + check res == "name: Earth, moon: [object Moon]" + check earth.population == int64(8e9) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +type + Window = ref object + console {.jsget.}: Console + Console = ref object + +jsDestructor(Console) + +# aux stuff for tests +proc jsAssert(window: Window; pred: bool) {.jsfunc: "assert".} = + assert pred + +test "jsfunc: regular functions": + proc log(console: Console; s: string) {.jsfunc.} = + echo s + let rt = newJSRuntime() + let ctx = rt.newJSContext() + let window = Window(console: Console()) + ctx.registerType(Window, asglobal = true) + ctx.registerType(Console) + ctx.setGlobal(window) + const code = """ +console.log('Hello, world!') +""" + let val = ctx.eval(code) + check not JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +type + JSFile = ref object + buffer: pointer # some internal buffer handled as managed memory + path {.jsget.}: string + +jsDestructor(JSFile) + +proc newJSFile(path: string): JSFile {.jsctor.} = + return JSFile( + path: path, + buffer: alloc(4096) + ) + +test "jsctor: constructors": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(Window, asglobal = true) + ctx.registerType(JSFile, name = "File") + ctx.setGlobal(Window()) + const code = """ +assert(new File('/path/to/file') + '' == '[object File]') +""" + let val = ctx.eval(code) + check not JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +func name(file: JSFile): string {.jsfget.} = + return file.path.substr(file.path.rfind('/') + 1) + +proc setName(file: JSFile; s: string) {.jsfset: "name".} = + let i = file.path.rfind('/') + file.path = file.path.substr(0, i) & s + +test "jsfget, jsfset: custom property reflectors": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(Window, asglobal = true) + ctx.registerType(JSFile, name = "File") + ctx.setGlobal(Window()) + const code = """ +const file = new File("/path/to/file"); +assert(file.path === "/path/to/file"); +assert(file.name === "file"); /* file */ +file.name = "new-name"; +assert(file.path === "/path/to/new-name"); + """ + let val = ctx.eval(code) + check not JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +proc jsExists(path: string): bool {.jsstfunc: "JSFile.exists".} = + return fileExists(path) + +test "jsstfunc: static functions": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(Window, asglobal = true) + ctx.registerType(JSFile, name = "File") + ctx.setGlobal(Window()) + const code = """ +assert(File.exists("doc/manual.md")); + """ + let val = ctx.eval(code) + check not JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +# this will always return the result of the fstat call. +proc owner(file: JSFile): int {.jsuffget.} = + let fd = open(cstring(file.path), O_RDONLY, 0) + if fd == -1: return -1 + var stats = Stat.default + if fstat(fd, stats) == -1: + discard close(fd) + return -1 + return int(stats.st_uid) + +proc getOwner(file: JSFile): int {.jsuffget.} = + return file.owner + +test "jsuffunc, jsufget, jsuffget: the LegacyUnforgeable property": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(Window, asglobal = true) + ctx.registerType(JSFile, name = "File") + const code = """ +const file = new File("doc/manual.md"); +const oldGetOwner = file.getOwner; +file.getOwner = () => -2; /* doesn't work */ +assert(oldGetOwner == file.getOwner); +Object.defineProperty(file, "owner", { value: -2 }); /* throws */ + """ + let val = ctx.eval(code) + check JS_IsException(val) + JS_FreeValue(ctx, val) + ctx.free() + rt.free() + +var unrefd {.global.} = 0 +proc finalize(file: JSFile) {.jsfin.} = + if file.buffer != nil: + dealloc(file.buffer) + # Note: it is not necessary to nil out the pointer; it's just me being + # paranoid :P + file.buffer = nil + inc unrefd + +test "jsfin: object finalizers": + let rt = newJSRuntime() + let ctx = rt.newJSContext() + ctx.registerType(Window, asglobal = true) + ctx.registerType(JSFile, name = "File") + const code = """ +/* this doesn't leak. yay :D */ +{ const file = new File("doc/manual.md"); } +/* note that I put the above call in a separate scope, so QJS can unref + * it immediately. in contrast, following file will not be deallocated until + * the runtime is gone. */ +const file = new File("doc/manual.md"); + """ + JS_FreeValue(ctx, ctx.eval(code)) + GC_fullCollect() # ensure refc runs + check unrefd == 1 # first file is already deallocated + ctx.free() + GC_fullCollect() # ensure refc runs + check unrefd == 1 # the second file is still available + rt.free() + check unrefd == 2 # runtime is freed, so the second file gets deallocated too diff --git a/lib/monoucha0/test/regexonly.nim b/lib/monoucha0/test/regexonly.nim new file mode 100644 index 00000000..345cd1f9 --- /dev/null +++ b/lib/monoucha0/test/regexonly.nim @@ -0,0 +1,12 @@ +import std/unittest + +import monoucha/jsregex +import monoucha/optshim + +test "regex only": + let re = compileRegex(".*").get + check re.match("whatever") + +test r"\b": + let re = compileRegex"\bth\b".get + check not re.match("Weather") diff --git a/lib/monoucha0/test/results.nim b/lib/monoucha0/test/results.nim new file mode 100644 index 00000000..1cb516fb --- /dev/null +++ b/lib/monoucha0/test/results.nim @@ -0,0 +1,1640 @@ +# Copyright (c) 2019 Jacek Sieka +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +type + ResultError*[E] = object of ValueError + ## Error raised when using `tryValue` value of result when error is set + ## See also Exception bridge mode + error*: E + + ResultDefect* = object of Defect + ## Defect raised when accessing value when error is set and vice versa + ## See also Exception bridge mode + + Result*[T, E] = object + ## Result type that can hold either a value or an error, but not both + ## + ## # Example + ## + ## ``` + ## import results + ## + ## # Re-export `results` so that API is always available to users of your module! + ## export results + ## + ## # It's convenient to create an alias - most likely, you'll do just fine + ## # with strings or cstrings as error for a start + ## + ## type R = Result[int, string] + ## + ## # Once you have a type, use `ok` and `err`: + ## + ## func works(): R = + ## # ok says it went... ok! + ## R.ok 42 + ## func fails(): R = + ## # or type it like this, to not repeat the type: + ## result.err "bad luck" + ## + ## func alsoWorks(): R = + ## # or just use the shortcut - auto-deduced from the return type! + ## ok(24) + ## + ## if (let w = works(); w.isOk): + ## echo w[], " or use value: ", w.value + ## + ## # In case you think your callers want to differentiate between errors: + ## type + ## Error = enum + ## a, b, c + ## type RE[T] = Result[T, Error] + ## + ## # You can use the question mark operator to pass errors up the call stack + ## func f(): R = + ## let x = ?works() - ?fails() + ## assert false, "will never reach" + ## + ## # If you provide this exception converter, this exception will be raised on + ## # `tryValue`: + ## func toException(v: Error): ref CatchableError = (ref CatchableError)(msg: $v) + ## try: + ## RE[int].err(a).tryValue() + ## except CatchableError: + ## echo "in here!" + ## + ## # You can use `Opt[T]` as a replacement for `Option` = `Opt` is an alias for + ## # `Result[T, void]`, meaning you can use the full `Result` API on it: + ## let x = Opt[int].ok(42) + ## echo x.get() + ## + ## # ... or `Result[void, E]` as a replacement for `bool`, providing extra error + ## # information! + ## let y = Result[void, string].err("computation failed") + ## echo y.error() + ## + ## ``` + ## + ## See the tests for more practical examples, specially when working with + ## back and forth with the exception world! + ## + ## # Potential benefits: + ## + ## * Handling errors becomes explicit and mandatory at the call site - + ## goodbye "out of sight, out of mind" + ## * Errors are a visible part of the API - when they change, so must the + ## calling code and compiler will point this out - nice! + ## * Errors are a visible part of the API - your fellow programmer is + ## reminded that things actually can go wrong + ## * Jives well with Nim `discard` + ## * Jives well with the new Defect exception hierarchy, where defects + ## are raised for unrecoverable errors and the rest of the API uses + ## results + ## * Error and value return have similar performance characteristics + ## * Caller can choose to turn them into exceptions at low cost - flexible + ## for libraries! + ## * Mostly relies on simple Nim features - though this library is no + ## exception in that compiler bugs were discovered writing it :) + ## + ## # Potential costs: + ## + ## * Handling errors becomes explicit and mandatory - if you'd rather ignore + ## them or just pass them to some catch-all, this is noise + ## * When composing operations, value must be lifted before processing, + ## adding potential verbosity / noise (fancy macro, anyone?) + ## * There's no call stack captured by default (see also `catch` and + ## `capture`) + ## * The extra branching may be more expensive for the non-error path + ## (though this can be minimized with PGO) + ## + ## The API visibility issue of exceptions can also be solved with + ## `{.raises.}` annotations - as of now, the compiler doesn't remind + ## you to do so, even though it knows what the right annotation should be. + ## `{.raises.}` does not participate in generic typing, making it just as + ## verbose but less flexible in some ways, if you want to type it out. + ## + ## Many system languages make a distinction between errors you want to + ## handle and those that are simply bugs or unrealistic to deal with.. + ## handling the latter will often involve aborting or crashing the funcess - + ## reliable systems like Erlang will try to relaunch it. + ## + ## On the flip side we have dynamic languages like python where there's + ## nothing exceptional about exceptions (hello StopIterator). Python is + ## rarely used to build reliable systems - its strengths lie elsewhere. + ## + ## # Exception bridge mode + ## + ## When the error of a `Result` is an `Exception`, or a `toException` helper + ## is present for your error type, the "Exception bridge mode" is + ## enabled and instead of raising `ResultError`, `tryValue` will raise the + ## given `Exception` on access. `[]` and `value` will continue to raise a + ## `Defect`. + ## + ## This is an experimental feature that may be removed. + ## + ## # Other languages + ## + ## Result-style error handling seems pretty popular lately, specially with + ## statically typed languages: + ## Haskell: https://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Either.html + ## Rust: https://doc.rust-lang.org/std/result/enum.Result.html + ## Modern C++: https://en.cppreference.com/w/cpp/utility/expected + ## More C++: https://github.com/ned14/outcome + ## + ## Swift is interesting in that it uses a non-exception implementation but + ## calls errors exceptions and has lots of syntactic sugar to make them feel + ## that way by implicitly passing them up the call chain - with a mandatory + ## annotation that function may throw: + ## https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html + ## + ## # Considerations for the error type + ## + ## * Use a `string` or a `cstring` if you want to provide a diagnostic for + ## the caller without an expectation that they will differentiate between + ## different errors. Callers should never parse the given string! + ## * Use an `enum` to provide in-depth errors where the caller is expected + ## to have different logic for different errors + ## * Use a complex type to include error-specific meta-data - or make the + ## meta-data collection a visible part of your API in another way - this + ## way it remains discoverable by the caller! + ## + ## A natural "error API" progression is starting with `Opt[T]`, then + ## `Result[T, cstring]`, `Result[T, enum]` and `Result[T, object]` in + ## escalating order of complexity. + ## + ## # Result equivalences with other types + ## + ## Result allows tightly controlling the amount of information that a + ## function gives to the caller: + ## + ## ## `Result[void, void] == bool` + ## + ## Neither value nor error information, it either worked or didn't. Most + ## often used for `proc`:s with side effects. + ## + ## ## `Result[T, void] == Option[T]` + ## + ## Return value if it worked, else tell the caller it failed. Most often + ## used for simple computations. + ## + ## Works as a replacement for `Option[T]` (aliased as `Opt[T]`) + ## + ## ## `Result[T, E]` - + ## + ## Return value if it worked, or a statically known piece of information + ## when it didn't - most often used when a function can fail in more than + ## one way - E is typically a `string` or an `enum`. + ## + ## ## `Result[T, ref E]` + ## + ## Returning a `ref E` allows introducing dynamically typed error + ## information, similar to exceptions. + ## + ## # Other implemenations in nim + ## + ## There are other implementations in nim that you might prefer: + ## * Either from nimfp: https://github.com/vegansk/nimfp/blob/master/src/fp/either.nim + ## * result_type: https://github.com/kapralos/result_type/ + ## + ## `Option` compatibility + ## + ## `Result[T, void]` is similar to `Option[T]`, except it can be used with + ## all `Result` operators and helpers. + ## + ## One difference is `Option[ref|ptr T]` which disallows `nil` - `Opt[T]` + ## allows an "ok" result to hold `nil` - this can be useful when `nil` is + ## a valid outcome of a function, but increases complexity for the caller. + ## + ## # Implementation notes + ## + ## This implementation is mostly based on the one in rust. Compared to it, + ## there are a few differences - if know of creative ways to improve things, + ## I'm all ears. + ## + ## * Rust has the enum variants which lend themselves to nice construction + ## where the full Result type isn't needed: `Err("some error")` doesn't + ## need to know value type - maybe some creative converter or something + ## can deal with this? + ## * Nim templates allow us to fail fast without extra effort, meaning the + ## other side of `and`/`or` isn't evaluated unless necessary - nice! + ## * Rust uses From traits to deal with result translation as the result + ## travels up the call stack - needs more tinkering - some implicit + ## conversions would be nice here + ## * Pattern matching in rust allows convenient extraction of value or error + ## in one go. + ## + ## # Performance considerations + ## + ## When returning a Result instead of a simple value, there are a few things + ## to take into consideration - in general, we are returning more + ## information directly to the caller which has an associated cost. + ## + ## Result is a value type, thus its performance characteristics + ## generally follow the performance of copying the value or error that + ## it stores. `Result` would benefit greatly from "move" support in the + ## language. + ## + ## In many cases, these performance costs are negligeable, but nonetheless + ## they are important to be aware of, to structure your code in an efficient + ## manner: + ## + ## * Memory overhead + ## Result is stored in memory as a union with a `bool` discriminator - + ## alignment makes it somewhat tricky to give an exact size, but in + ## general, `Result[int, int]` will take up `2*sizeof(int)` bytes: + ## 1 `int` for the discriminator and padding, 1 `int` for either the value + ## or the error. The additional size means that returning may take up more + ## registers or spill onto the stack. + ## * Loss of RVO + ## Nim does return-value-optimization by rewriting `proc f(): X` into + ## `proc f(result: var X)` - in an expression like `let x = f()`, this + ## allows it to avoid a copy from the "temporary" return value to `x` - + ## when using Result, this copy currently happens always because you need + ## to fetch the value from the Result in a second step: `let x = f().value` + ## * Extra copies + ## To avoid spurious evaluation of expressions in templates, we use a + ## temporary variable sometimes - this means an unnecessary copy for some + ## types. + ## * Bad codegen + ## When doing RVO, Nim generates poor and slow code: it uses a construct + ## called `genericReset` that will zero-initialize a value using dynamic + ## RTTI - a process that the C compiler subsequently is unable to + ## optimize. This applies to all types, but is exacerbated with Result + ## because of its bigger footprint - this should be fixed in compiler. + ## * Double zero-initialization bug + ## Nim has an initialization bug that causes additional poor performance: + ## `var x = f()` will be expanded into `var x; zeroInit(x); f(x)` where + ## `f(x)` will call the slow `genericReset` and zero-init `x` again, + ## unnecessarily. + ## + ## Comparing `Result` performance to exceptions in Nim is difficult - it + ## will depend on the error type used, the frequency at which exceptions + ## happen, the amount of error handling code in the application and the + ## compiler and backend used. + ## + ## * the default C backend in nim uses `setjmp` for exception handling - + ## the relative performance of the happy path will depend on the structure + ## of the code: how many exception handlers there are, how much unwinding + ## happens. `setjmp` works by taking a snapshot of the full CPU state and + ## saving it to memory when enterting a try block (or an implict try + ## block, such as is introduced with `defer` and similar constructs). + ## * an efficient exception handling mechanism (like the C++ backend or + ## `nlvm`) will usually have a lower cost on the happy path because the + ## value can be returned more efficiently. However, there is still a code + ## and data size increase depending on the specific situation, as well as + ## loss of optimization opportunities to consider. + ## * raising an exception is usually (a lot) slower than returning an error + ## through a Result - at raise time, capturing a call stack and allocating + ## memory for the Exception is expensive, so the performance difference + ## comes down to the complexity of the error type used. + ## * checking for errors with Result is local branching operation that also + ## happens on the happy path - this may be a cost. + ## + ## An accurate summary might be that Exceptions are at its most efficient + ## when errors are not handled and don't happen. + ## + ## # Relevant nim bugs + ## + ## https://github.com/nim-lang/Nim/issues/13799 - type issues + ## https://github.com/nim-lang/Nim/issues/8745 - genericReset slow + ## https://github.com/nim-lang/Nim/issues/13879 - double-zero-init slow + ## https://github.com/nim-lang/Nim/issues/14318 - generic error raises pragma (fixed in 1.6.14+) + ## https://github.com/nim-lang/Nim/issues/23741 - inefficient codegen for temporaries + ## https://github.com/nim-lang/Nim/issues/20699 (fixed in 2.0.0+) + # case oResultPrivate: bool + # of false: + # eResultPrivate: E + # of true: + # vResultPrivate: T + + # ResultPrivate* works around (fixed in 1.6.14+): + # * https://github.com/nim-lang/Nim/issues/3770 + # * https://github.com/nim-lang/Nim/issues/20900 + # + # Do not use these fields directly in your code, they're not meant to be + # public! + when T is void: + when E is void: + oResultPrivate*: bool + else: + case oResultPrivate*: bool + of false: + eResultPrivate*: E + of true: + discard + else: + when E is void: + case oResultPrivate*: bool + of false: + discard + of true: + vResultPrivate*: T + else: + case oResultPrivate*: bool + of false: + eResultPrivate*: E + of true: + vResultPrivate*: T + + Opt*[T] = Result[T, void] + +const + resultsGenericsOpenSym* {.booldefine.} = true + ## Enable the experimental `genericsOpenSym` feature or a workaround for the + ## template injection problem in the issue linked below where scoped symbol + ## resolution works differently for expanded bodies in templates depending on + ## whether we're in a generic context or not. + ## + ## The issue leads to surprising errors where symbols from outer scopes get + ## bound instead of the symbol created in the template scope which should be + ## seen as a better candidate, breaking access to `error` in `valueOr` and + ## friends. + ## + ## In Nim versions that do not support `genericsOpenSym`, a macro is used + ## instead to reassign symbol matches which may or may not work depending on + ## the complexity of the code. + ## + ## Nim 2.0.8 was released with an incomplete fix but already declares + ## `nimHasGenericsOpenSym`. + # TODO https://github.com/nim-lang/Nim/issues/22605 + # TODO https://github.com/arnetheduck/nim-results/issues/34 + # TODO https://github.com/nim-lang/Nim/issues/23386 + # TODO https://github.com/nim-lang/Nim/issues/23385 + # + # Related PR:s (there's more probably, but this gives an overview) + # https://github.com/nim-lang/Nim/pull/23102 + # https://github.com/nim-lang/Nim/pull/23572 + # https://github.com/nim-lang/Nim/pull/23873 + # https://github.com/nim-lang/Nim/pull/23892 + # https://github.com/nim-lang/Nim/pull/23939 + + resultsGenericsOpenSymWorkaround* {.booldefine.} = + resultsGenericsOpenSym and not defined(nimHasGenericsOpenSym2) + ## Prefer macro workaround to solve genericsOpenSym issue + # TODO https://github.com/nim-lang/Nim/pull/23892#discussion_r1713434311 + + resultsGenericsOpenSymWorkaroundHint* {.booldefine.} = true + + resultsLent {.booldefine.} = + (NimMajor, NimMinor, NimPatch) >= (2, 2, 0) or + (defined(gcRefc) and ((NimMajor, NimMinor, NimPatch) >= (2, 0, 8))) + ## Enable return of `lent` types - this *mostly* works in Nim 1.6.18+ but + ## there have been edge cases reported as late as 1.6.14 - YMMV - + ## conservatively, `lent` is therefore enabled only with the latest Nim + ## version at the time of writing, where it could be verified to work with + ## several large applications. + ## + ## ORC is not expected to work until 2.2. + ## https://github.com/nim-lang/Nim/issues/23973 + +when resultsLent: + template maybeLent(T: untyped): untyped = + lent T + +else: + template maybeLent(T: untyped): untyped = + T + +func raiseResultOk[T, E](self: Result[T, E]) {.noreturn, noinline.} = + # noinline because raising should take as little space as possible at call + # site + when T is void: + raise (ref ResultError[void])(msg: "Trying to access error with value") + else: + raise (ref ResultError[T])( + msg: "Trying to access error with value", error: self.vResultPrivate + ) + +func raiseResultError[T, E](self: Result[T, E]) {.noreturn, noinline.} = + # noinline because raising should take as little space as possible at call + # site + mixin toException + + when E is ref Exception: + if self.eResultPrivate.isNil: # for example Result.default()! + raise (ref ResultError[void])(msg: "Trying to access value with err (nil)") + raise self.eResultPrivate + elif E is void: + raise (ref ResultError[void])(msg: "Trying to access value with err") + elif compiles(toException(self.eResultPrivate)): + raise toException(self.eResultPrivate) + elif compiles($self.eResultPrivate): + raise (ref ResultError[E])(error: self.eResultPrivate, msg: $self.eResultPrivate) + else: + raise (ref ResultError[E])( + msg: "Trying to access value with err", error: self.eResultPrivate + ) + +func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} = + mixin `$` + when compiles($v): + raise (ref ResultDefect)(msg: m & ": " & $v) + else: + raise (ref ResultDefect)(msg: m) + +func raiseResultDefect(m: string) {.noreturn, noinline.} = + raise (ref ResultDefect)(msg: m) + +template withAssertOk(self: Result, body: untyped): untyped = + # Careful - `self` evaluated multiple times, which is fine in all current uses + case self.oResultPrivate + of false: + when self.E isnot void: + raiseResultDefect("Trying to access value with err Result", self.eResultPrivate) + else: + raiseResultDefect("Trying to access value with err Result") + of true: + body + +template ok*[T: not void, E](R: type Result[T, E], x: untyped): R = + ## Initialize a result with a success and value + ## Example: `Result[int, string].ok(42)` + R(oResultPrivate: true, vResultPrivate: x) + +template ok*[E](R: type Result[void, E]): R = + ## Initialize a result with a success and value + ## Example: `Result[void, string].ok()` + R(oResultPrivate: true) + +template ok*[T: not void, E](self: var Result[T, E], x: untyped) = + ## Set the result to success and update value + ## Example: `result.ok(42)` + self = ok(type self, x) + +template ok*[E](self: var Result[void, E]) = + ## Set the result to success and update value + ## Example: `result.ok()` + self = (type self).ok() + +template err*[T; E: not void](R: type Result[T, E], x: untyped): R = + ## Initialize the result to an error + ## Example: `Result[int, string].err("uh-oh")` + R(oResultPrivate: false, eResultPrivate: x) + +template err*[T](R: type Result[T, cstring], x: string): R = + ## Initialize the result to an error + ## Example: `Result[int, string].err("uh-oh")` + const s = x # avoid dangling cstring pointers + R(oResultPrivate: false, eResultPrivate: cstring(s)) + +template err*[T](R: type Result[T, void]): R = + ## Initialize the result to an error + ## Example: `Result[int, void].err()` + R(oResultPrivate: false) + +template err*[T; E: not void](self: var Result[T, E], x: untyped) = + ## Set the result as an error + ## Example: `result.err("uh-oh")` + self = err(type self, x) + +template err*[T](self: var Result[T, cstring], x: string) = + const s = x # Make sure we don't return a dangling pointer + self = err(type self, cstring(s)) + +template err*[T](self: var Result[T, void]) = + ## Set the result as an error + ## Example: `result.err()` + self = err(type self) + +template ok*(v: auto): auto = + ok(typeof(result), v) + +template ok*(): auto = + ok(typeof(result)) + +template err*(v: auto): auto = + err(typeof(result), v) + +template err*(): auto = + err(typeof(result)) + +template isOk*(self: Result): bool = + self.oResultPrivate + +template isErr*(self: Result): bool = + not self.oResultPrivate + +when not defined(nimHasEffectsOfs): + template effectsOf(f: untyped) {.pragma, used.} + +func map*[T0: not void, E; T1: not void]( + self: Result[T0, E], f: proc(x: T0): T1 +): Result[T1, E] {.inline, effectsOf: f.} = + ## Transform value using f, or return error + ## + ## ``` + ## let r = Result[int, cstring).ok(42) + ## assert r.map(proc (v: int): int = $v).value() == "42" + ## ``` + case self.oResultPrivate + of true: + when T1 is void: + f(self.vResultPrivate) + result.ok() + else: + result.ok(f(self.vResultPrivate)) + of false: + when E is void: + result.err() + else: + result.err(self.eResultPrivate) + +func map*[T: not void, E]( + self: Result[T, E], f: proc(x: T) +): Result[void, E] {.inline, effectsOf: f.} = + ## Transform value using f, or return error + ## + ## ``` + ## let r = Result[int, cstring).ok(42) + ## assert r.map(proc (v: int): int = $v).value() == "42" + ## ``` + case self.oResultPrivate + of true: + f(self.vResultPrivate) + result.ok() + of false: + when E is void: + result.err() + else: + result.err(self.eResultPrivate) + +func map*[E; T1: not void]( + self: Result[void, E], f: proc(): T1 +): Result[T1, E] {.inline, effectsOf: f.} = + ## Transform value using f, or return error + case self.oResultPrivate + of true: + result.ok(f()) + of false: + when E is void: + result.err() + else: + result.err(self.eResultPrivate) + +func map*[E]( + self: Result[void, E], f: proc() +): Result[void, E] {.inline, effectsOf: f.} = + ## Call f if `self` is ok + case self.oResultPrivate + of true: + f() + result.ok() + of false: + when E is void: + result.err() + else: + result.err(self.eResultPrivate) + +func flatMap*[T0: not void, E, T1]( + self: Result[T0, E], f: proc(x: T0): Result[T1, E] +): Result[T1, E] {.inline, effectsOf: f.} = + case self.oResultPrivate + of true: + f(self.vResultPrivate) + of false: + when E is void: + Result[T1, void].err() + else: + Result[T1, E].err(self.eResultPrivate) + +func flatMap*[E, T1]( + self: Result[void, E], f: proc(): Result[T1, E] +): Result[T1, E] {.inline, effectsOf: f.} = + case self.oResultPrivate + of true: + f() + of false: + when E is void: + Result[T1, void].err() + else: + Result[T1, E].err(self.eResultPrivate) + +func mapErr*[T; E0: not void, E1: not void]( + self: Result[T, E0], f: proc(x: E0): E1 +): Result[T, E1] {.inline, effectsOf: f.} = + ## Transform error using f, or leave untouched + case self.oResultPrivate + of true: + when T is void: + result.ok() + else: + result.ok(self.vResultPrivate) + of false: + result.err(f(self.eResultPrivate)) + +func mapErr*[T; E1: not void]( + self: Result[T, void], f: proc(): E1 +): Result[T, E1] {.inline, effectsOf: f.} = + ## Transform error using f, or return value + case self.oResultPrivate + of true: + when T is void: + result.ok() + else: + result.ok(self.vResultPrivate) + of false: + result.err(f()) + +func mapErr*[T; E0: not void]( + self: Result[T, E0], f: proc(x: E0) +): Result[T, void] {.inline, effectsOf: f.} = + ## Transform error using f, or return value + case self.oResultPrivate + of true: + when T is void: + result.ok() + else: + result.ok(self.vResultPrivate) + of false: + f(self.eResultPrivate) + result.err() + +func mapErr*[T]( + self: Result[T, void], f: proc() +): Result[T, void] {.inline, effectsOf: f.} = + ## Transform error using f, or return value + case self.oResultPrivate + of true: + when T is void: + result.ok() + else: + result.ok(self.vResultPrivate) + of false: + f() + result.err() + +func mapConvert*[T0: not void, E]( + self: Result[T0, E], T1: type +): Result[T1, E] {.inline.} = + ## Convert result value to A using an conversion + # Would be nice if it was automatic... + case self.oResultPrivate + of true: + when T1 is void: + result.ok() + else: + result.ok(T1(self.vResultPrivate)) + of false: + when E is void: + result.err() + else: + result.err(self.eResultPrivate) + +func mapCast*[T0: not void, E]( + self: Result[T0, E], T1: type +): Result[T1, E] {.inline.} = + ## Convert result value to A using a cast + ## Would be nice with nicer syntax... + case self.oResultPrivate + of true: + when T1 is void: + result.ok() + else: + result.ok(cast[T1](self.vResultPrivate)) + of false: + when E is void: + result.err() + else: + result.err(self.eResultPrivate) + +func mapConvertErr*[T, E0](self: Result[T, E0], E1: type): Result[T, E1] {.inline.} = + ## Convert result error to E1 using an conversion + # Would be nice if it was automatic... + when E0 is E1: + result = self + else: + if self.oResultPrivate: + when T is void: + result.ok() + else: + result.ok(self.vResultPrivate) + else: + when E1 is void: + result.err() + else: + result.err(E1(self.eResultPrivate)) + +func mapCastErr*[T, E0](self: Result[T, E0], E1: type): Result[T, E1] {.inline.} = + ## Convert result value to A using a cast + ## Would be nice with nicer syntax... + if self.oResultPrivate: + when T is void: + result.ok() + else: + result.ok(self.vResultPrivate) + else: + result.err(cast[E1](self.eResultPrivate)) + +template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] = + ## Evaluate `other` iff self.isOk, else return error + ## fail-fast - will not evaluate other if a is an error + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + other + of false: + when type(self) is type(other): + s + else: + type R = type(other) + when E is void: + err(R) + else: + err(R, s.eResultPrivate) + +template `or`*[T, E0, E1](self: Result[T, E0], other: Result[T, E1]): Result[T, E1] = + ## Evaluate `other` iff `not self.isOk`, else return `self` + ## fail-fast - will not evaluate `other` if `self` is ok + ## + ## ``` + ## func f(): Result[int, SomeEnum] = + ## f2() or err(SomeEnum.V) # Collapse errors from other module / function + ## ``` + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + when type(self) is type(other): + s + else: + type R = type(other) + when T is void: + ok(R) + else: + ok(R, s.vResultPrivate) + of false: + other + +template orErr*[T, E0, E1](self: Result[T, E0], error: E1): Result[T, E1] = + ## Evaluate `other` iff `not self.isOk`, else return `self` + ## fail-fast - will not evaluate `error` if `self` is ok + ## + ## ``` + ## func f(): Result[int, SomeEnum] = + ## f2().orErr(SomeEnum.V) # Collapse errors from other module / function + ## ``` + ## + ## ** Experimental, may be removed ** + let s = (self) # TODO avoid copy + type R = Result[T, E1] + case s.oResultPrivate + of true: + when type(self) is R: + s + else: + when T is void: + ok(R) + else: + ok(R, s.vResultPrivate) + of false: + err(R, error) + +template catch*(body: typed): Result[type(body), ref CatchableError] = + ## Catch exceptions for body and store them in the Result + ## + ## ``` + ## let r = catch: someFuncThatMayRaise() + ## ``` + type R = Result[type(body), ref CatchableError] + + try: + when type(body) is void: + body + R.ok() + else: + R.ok(body) + except CatchableError as eResultPrivate: + R.err(eResultPrivate) + +template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, ref E] = + ## Evaluate someExceptionExpr and put the exception into a result, making sure + ## to capture a call stack at the capture site: + ## + ## ``` + ## let eResultPrivate: Result[void, ValueError] = void.capture((ref ValueError)(msg: "test")) + ## echo eResultPrivate.error().getStackTrace() + ## ``` + type R = Result[T, ref E] + + var ret: R + try: + # TODO is this needed? I think so, in order to grab a call stack, but + # haven't actually tested... + if true: + # I'm sure there's a nicer way - this just works :) + raise someExceptionExpr + except E as caught: + ret = R.err(caught) + ret + +func `==`*[T0: not void, E0: not void, T1: not void, E1: not void]( + lhs: Result[T0, E0], rhs: Result[T1, E1] +): bool {.inline.} = + if lhs.oResultPrivate != rhs.oResultPrivate: + false + else: + case lhs.oResultPrivate # and rhs.oResultPrivate implied + of true: + lhs.vResultPrivate == rhs.vResultPrivate + of false: + lhs.eResultPrivate == rhs.eResultPrivate + +func `==`*[E0, E1](lhs: Result[void, E0], rhs: Result[void, E1]): bool {.inline.} = + if lhs.oResultPrivate != rhs.oResultPrivate: + false + else: + case lhs.oResultPrivate # and rhs.oResultPrivate implied + of true: + true + of false: + lhs.eResultPrivate == rhs.eResultPrivate + +func `==`*[T0, T1](lhs: Result[T0, void], rhs: Result[T1, void]): bool {.inline.} = + if lhs.oResultPrivate != rhs.oResultPrivate: + false + else: + case lhs.oResultPrivate # and rhs.oResultPrivate implied + of true: + lhs.vResultPrivate == rhs.vResultPrivate + of false: + true + +func value*[E](self: Result[void, E]) {.inline.} = + ## Fetch value of result if set, or raise Defect + ## Exception bridge mode: raise given Exception instead + ## See also: Option.get + withAssertOk(self): + discard + +func value*[T: not void, E](self: Result[T, E]): maybeLent T {.inline.} = + ## Fetch value of result if set, or raise Defect + ## Exception bridge mode: raise given Exception instead + ## See also: Option.get + withAssertOk(self): + when T isnot void: + # TODO: remove result usage. + # A workaround for nim VM bug: + # https://github.com/nim-lang/Nim/issues/22216 + result = self.vResultPrivate + +func value*[T: not void, E](self: var Result[T, E]): var T {.inline.} = + ## Fetch value of result if set, or raise Defect + ## Exception bridge mode: raise given Exception instead + ## See also: Option.get + + ( + block: + withAssertOk(self): + addr self.vResultPrivate + )[] + +template `[]`*[T: not void, E](self: Result[T, E]): T = + ## Fetch value of result if set, or raise Defect + ## Exception bridge mode: raise given Exception instead + self.value() + +template `[]`*[E](self: Result[void, E]) = + ## Fetch value of result if set, or raise Defect + ## Exception bridge mode: raise given Exception instead + self.value() + +template unsafeValue*[T: not void, E](self: Result[T, E]): T = + ## Fetch value of result if set, undefined behavior if unset + ## See also: `unsafeError` + self.vResultPrivate + +template unsafeValue*[E](self: Result[void, E]) = + ## Fetch value of result if set, undefined behavior if unset + ## See also: `unsafeError` + assert self.oResultPrivate # Emulate field access defect in debug builds + +func tryValue*[E](self: Result[void, E]) {.inline.} = + ## Fetch value of result if set, or raise + ## When E is an Exception, raise that exception - otherwise, raise a ResultError[E] + mixin raiseResultError + case self.oResultPrivate + of false: + self.raiseResultError() + of true: + discard + +func tryValue*[T: not void, E](self: Result[T, E]): maybeLent T {.inline.} = + ## Fetch value of result if set, or raise + ## When E is an Exception, raise that exception - otherwise, raise a ResultError[E] + mixin raiseResultError + case self.oResultPrivate + of false: + self.raiseResultError() + of true: + # TODO https://github.com/nim-lang/Nim/issues/22216 + result = self.vResultPrivate + +func expect*[E](self: Result[void, E], m: string) = + ## Return value of Result, or raise a `Defect` with the given message - use + ## this helper to extract the value when an error is not expected, for example + ## because the program logic dictates that the operation should never fail + ## + ## ```nim + ## let r = Result[int, int].ok(42) + ## # Put here a helpful comment why you think this won't fail + ## echo r.expect("r was just set to ok(42)") + ## ``` + case self.oResultPrivate + of false: + when E isnot void: + raiseResultDefect(m, self.eResultPrivate) + else: + raiseResultDefect(m) + of true: + discard + +func expect*[T: not void, E](self: Result[T, E], m: string): maybeLent T = + ## Return value of Result, or raise a `Defect` with the given message - use + ## this helper to extract the value when an error is not expected, for example + ## because the program logic dictates that the operation should never fail + ## + ## ```nim + ## let r = Result[int, int].ok(42) + ## # Put here a helpful comment why you think this won't fail + ## echo r.expect("r was just set to ok(42)") + ## ``` + case self.oResultPrivate + of false: + when E isnot void: + raiseResultDefect(m, self.eResultPrivate) + else: + raiseResultDefect(m) + of true: + # TODO https://github.com/nim-lang/Nim/issues/22216 + result = self.vResultPrivate + +func expect*[T: not void, E](self: var Result[T, E], m: string): var T = + ( + case self.oResultPrivate + of false: + when E isnot void: + raiseResultDefect(m, self.eResultPrivate) + else: + raiseResultDefect(m) + of true: + addr self.vResultPrivate + )[] + +func `$`*[T, E](self: Result[T, E]): string = + ## Returns string representation of `self` + case self.oResultPrivate + of true: + when T is void: + "ok()" + else: + "ok(" & $self.vResultPrivate & ")" + of false: + when E is void: + "none()" + else: + "err(" & $self.eResultPrivate & ")" + +func error*[T](self: Result[T, void]) = + ## Fetch error of result if set, or raise Defect + case self.oResultPrivate + of true: + when T isnot void: + raiseResultDefect("Trying to access error when value is set", self.vResultPrivate) + else: + raiseResultDefect("Trying to access error when value is set") + of false: + discard + +func error*[T; E: not void](self: Result[T, E]): maybeLent E = + ## Fetch error of result if set, or raise Defect + case self.oResultPrivate + of true: + when T isnot void: + raiseResultDefect("Trying to access error when value is set", self.vResultPrivate) + else: + raiseResultDefect("Trying to access error when value is set") + of false: + # TODO https://github.com/nim-lang/Nim/issues/22216 + result = self.eResultPrivate + +func tryError*[T](self: Result[T, void]) {.inline.} = + ## Fetch error of result if set, or raise + ## Raises a ResultError[T] + mixin raiseResultOk + case self.oResultPrivate + of true: + self.raiseResultOk() + of false: + discard + +func tryError*[T; E: not void](self: Result[T, E]): maybeLent E {.inline.} = + ## Fetch error of result if set, or raise + ## Raises a ResultError[T] + mixin raiseResultOk + case self.oResultPrivate + of true: + self.raiseResultOk() + of false: + # TODO https://github.com/nim-lang/Nim/issues/22216 + result = self.eResultPrivate + +template unsafeError*[T; E: not void](self: Result[T, E]): E = + ## Fetch error of result if set, undefined behavior if unset + ## See also: `unsafeValue` + self.eResultPrivate + +template unsafeError*[T](self: Result[T, void]) = + ## Fetch error of result if set, undefined behavior if unset + ## See also: `unsafeValue` + assert not self.oResultPrivate # Emulate field access defect in debug builds + +func optValue*[T, E](self: Result[T, E]): Opt[T] = + ## Return the value of a Result as an Opt, or none if Result is an error + case self.oResultPrivate + of true: + when T is void: + Opt[void].ok() + else: + Opt.some(self.vResultPrivate) + of false: + Opt.none(T) + +func optError*[T, E](self: Result[T, E]): Opt[E] = + ## Return the error of a Result as an Opt, or none if Result is a value + case self.oResultPrivate + of true: + Opt.none(E) + of false: + Opt.some(self.eResultPrivate) + +# Alternative spellings for `value`, for `options` compatibility +template get*[T: not void, E](self: Result[T, E]): T = + self.value() + +template get*[E](self: Result[void, E]) = + self.value() + +template tryGet*[T: not void, E](self: Result[T, E]): T = + self.tryValue() + +template tryGet*[E](self: Result[void, E]) = + self.tryValue() + +template unsafeGet*[T: not void, E](self: Result[T, E]): T = + self.unsafeValue() + +template unsafeGet*[E](self: Result[void, E]) = + self.unsafeValue() + +# `var` overloads should not be needed but result in invalid codegen (!): +# https://github.com/nim-lang/Nim/issues/22049 (fixed in 1.6.16+) +func get*[T: not void, E](self: var Result[T, E]): var T = + self.value() + +func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} = + ## Fetch value of result if set, or return the value `otherwise` + ## See `valueOr` for a template version that avoids evaluating `otherwise` + ## unless necessary + case self.oResultPrivate + of true: self.vResultPrivate + of false: otherwise + +when resultsGenericsOpenSymWorkaround: + import macros + + proc containsHack(n: NimNode): bool = + if n.len == 0: + n.eqIdent("isOkOr") or n.eqIdent("isErrOr") or n.eqIdent("valueOr") or + n.eqIdent("errorOr") + else: + for child in n: + if containsHack(child): + return true + false + + proc containsIdent(n: NimNode, what: string, with: NimNode): bool = + if n == with: + false # Don't replace if the right symbol is already being used + elif n.eqIdent(what): + true + else: + for child in n: + if containsIdent(child, what, with): + return true + + false + + proc replace(n: NimNode, what: string, with: NimNode): NimNode = + if not containsIdent(n, what, with): # Fast path that avoids copies altogether + return n + + if n == with: + result = with + elif n.eqIdent(what): + when resultsGenericsOpenSymWorkaroundHint: + hint("Replaced conflicting external symbol " & what, n) + result = with + else: + case n.kind + of nnkCallKinds: + if n[0].containsHack(): + # Don't replace inside nested expansion + result = n + elif n[0].eqIdent(what): + if n.len == 1: + if n[0] == with: + result = n + else: + # No arguments - replace call symbol + result = copyNimNode(n) + result.add with + when resultsGenericsOpenSymWorkaroundHint: + hint("Replaced conflicting external symbol " & what, n[0]) + else: + # `error(...)` - replace args but not function name + result = copyNimNode(n) + result.add n[0] + for i in 1 ..< n.len: + result.add replace(n[i], what, with) + else: + result = copyNimNode(n) + for i in 0 ..< n.len: + result.add replace(n[i], what, with) + of nnkExprEqExpr: + # "error = xxx" - function call with named parameters and other weird stuff + result = copyNimNode(n) + result.add n[0] + for i in 1 ..< n.len: + result.add replace(n[i], what, with) + of nnkLetSection, nnkVarSection, nnkFormalParams: + result = copyNimNode(n) + for i in 0 ..< n.len: + result.add replace(n[i], what, with) + of nnkDotExpr: + # Ignore rhs in "abc.error" + result = copyNimNode(n) + result.add(replace(n[0], what, with)) + result.add(n[1]) + else: + if ( + n.kind == nnkForStmt and + (n[0].eqIdent(what) or (n.len == 4 and n[1].eqIdent(what))) or + (n.kind == nnkIdentDefs and n[0].eqIdent(what)) + ): + # Naming the symbol the same way requires lots of magic here - just + # say no + error("Shadowing variable declarations of `" & what & "` not supported", n[0]) + + result = copyNimNode(n) + for i in 0 ..< n.len: + result.add replace(n[i], what, with) + + macro replaceHack(body, what, with: untyped): untyped = + # This hack replaces the `what` identifier with `with` except where + # this replacing is not expected - this is an approximation of the intent + # of injecting a template and likely doesn't cover all applicable cases + result = replace(body, $what, with) + + template isOkOr*[T, E](self: Result[T, E], body: untyped) = + ## Evaluate `body` iff result has been assigned an error + ## `body` is evaluated lazily. + ## + ## Example: + ## ``` + ## let + ## v = Result[int, string].err("hello") + ## x = v.isOkOr: echo "not ok" + ## # experimental: direct error access using an unqualified `error` symbol + ## z = v.isOkOr: echo error + ## ``` + ## + ## `error` access: + ## + ## TODO experimental, might change in the future + ## + ## The template contains a shortcut for accessing the error of the result, + ## it can only be used outside of generic code, + ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 + + let s = (self) # TODO avoid copy + case s.oResultPrivate + of false: + when E isnot void: + template error(): E {.used, gensym.} = + s.eResultPrivate + + replaceHack(body, "error", error) + else: + body + of true: + discard + + template isErrOr*[T, E](self: Result[T, E], body: untyped) = + ## Evaluate `body` iff result has been assigned a value + ## `body` is evaluated lazily. + ## + ## Example: + ## ``` + ## let + ## v = Result[int, string].ok(42) + ## x = v.isErrOr: echo "not err" + ## # experimental: direct value access using an unqualified `value` symbol + ## z = v.isErrOr: echo value + ## ``` + ## + ## `value` access: + ## + ## TODO experimental, might change in the future + ## + ## The template contains a shortcut for accessing the value of the result, + ## it can only be used outside of generic code, + ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 + + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + when T isnot void: + template value(): T {.used, gensym.} = + s.vResultPrivate + + replaceHack(body, "value", s.vResultPrivate) + else: + body + of false: + discard + + template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T = + ## Fetch value of result if set, or evaluate `def` + ## `def` is evaluated lazily, and must be an expression of `T` or exit + ## the scope (for example using `return` / `raise`) + ## + ## See `isOkOr` for a version that works with `Result[void, E]`. + ## + ## Example: + ## ``` + ## let + ## v = Result[int, string].err("hello") + ## x = v.valueOr: 42 # x == 42 now + ## y = v.valueOr: raise (ref ValueError)(msg: "v is an error, gasp!") + ## # experimental: direct error access using an unqualified `error` symbol + ## z = v.valueOr: raise (ref ValueError)(msg: error) + ## ``` + ## + ## `error` access: + ## + ## TODO experimental, might change in the future + ## + ## The template contains a shortcut for accessing the error of the result, + ## it can only be used outside of generic code, + ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 + ## + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + s.vResultPrivate + of false: + when E isnot void: + template error(): E {.used, gensym.} = + s.eResultPrivate + + replaceHack(def, "error", error) + else: + def + + template errorOr*[T; E: not void](self: Result[T, E], def: untyped): E = + ## Fetch error of result if not set, or evaluate `def` + ## `def` is evaluated lazily, and must be an expression of `T` or exit + ## the scope (for example using `return` / `raise`) + ## + ## See `isErrOr` for a version that works with `Result[T, void]`. + let s = (self) # TODO avoid copy + case s.oResultPrivate + of false: + s.eResultPrivate + of true: + when T isnot void: + template value(): T {.used, gensym.} = + s.vResultPrivate + + replaceHack(def, "value", value) + else: + def + +else: + # TODO https://github.com/nim-lang/Nim/pull/23892#discussion_r1713434311 + const pushGenericsOpenSym = defined(nimHasGenericsOpenSym2) and resultsGenericsOpenSym + + template isOkOr*[T, E](self: Result[T, E], body: untyped) = + ## Evaluate `body` iff result has been assigned an error + ## `body` is evaluated lazily. + ## + ## Example: + ## ``` + ## let + ## v = Result[int, string].err("hello") + ## x = v.isOkOr: echo "not ok" + ## # experimental: direct error access using an unqualified `error` symbol + ## z = v.isOkOr: echo error + ## ``` + ## + ## `error` access: + ## + ## TODO experimental, might change in the future + ## + ## The template contains a shortcut for accessing the error of the result, + ## it can only be used outside of generic code, + ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 + + let s = (self) # TODO avoid copy + case s.oResultPrivate + of false: + when E isnot void: + when pushGenericsOpenSym: + {.push experimental: "genericsOpenSym".} + template error(): E {.used.} = + s.eResultPrivate + + body + of true: + discard + + template isErrOr*[T, E](self: Result[T, E], body: untyped) = + ## Evaluate `body` iff result has been assigned a value + ## `body` is evaluated lazily. + ## + ## Example: + ## ``` + ## let + ## v = Result[int, string].ok(42) + ## x = v.isErrOr: echo "not err" + ## # experimental: direct value access using an unqualified `value` symbol + ## z = v.isErrOr: echo value + ## ``` + ## + ## `value` access: + ## + ## TODO experimental, might change in the future + ## + ## The template contains a shortcut for accessing the value of the result, + ## it can only be used outside of generic code, + ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 + + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + when T isnot void: + when pushGenericsOpenSym: + {.push experimental: "genericsOpenSym".} + template value(): T {.used.} = + s.vResultPrivate + + body + of false: + discard + + template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T = + ## Fetch value of result if set, or evaluate `def` + ## `def` is evaluated lazily, and must be an expression of `T` or exit + ## the scope (for example using `return` / `raise`) + ## + ## See `isOkOr` for a version that works with `Result[void, E]`. + ## + ## Example: + ## ``` + ## let + ## v = Result[int, string].err("hello") + ## x = v.valueOr: 42 # x == 42 now + ## y = v.valueOr: raise (ref ValueError)(msg: "v is an error, gasp!") + ## # experimental: direct error access using an unqualified `error` symbol + ## z = v.valueOr: raise (ref ValueError)(msg: error) + ## ``` + ## + ## `error` access: + ## + ## TODO experimental, might change in the future + ## + ## The template contains a shortcut for accessing the error of the result, + ## it can only be used outside of generic code, + ## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386 + ## + let s = (self) # TODO avoid copy + case s.oResultPrivate + of true: + s.vResultPrivate + of false: + when E isnot void: + when pushGenericsOpenSym: + {.push experimental: "genericsOpenSym".} + template error(): E {.used.} = + s.eResultPrivate + + def + + template errorOr*[T; E: not void](self: Result[T, E], def: untyped): E = + ## Fetch error of result if not set, or evaluate `def` + ## `def` is evaluated lazily, and must be an expression of `T` or exit + ## the scope (for example using `return` / `raise`) + ## + ## See `isErrOr` for a version that works with `Result[T, void]`. + let s = (self) # TODO avoid copy + case s.oResultPrivate + of false: + s.eResultPrivate + of true: + when T isnot void: + when pushGenericsOpenSym: + {.push experimental: "genericsOpenSym".} + template value(): T {.used.} = + s.vResultPrivate + + def + +func flatten*[T, E](self: Result[Result[T, E], E]): Result[T, E] = + ## Remove one level of nesting + case self.oResultPrivate + of true: + self.vResultPrivate + of false: + when E is void: + err(Result[T, E]) + else: + err(Result[T, E], self.error) + +func filter*[T, E]( + self: Result[T, E], callback: proc(x: T): Result[void, E] +): Result[T, E] {.effectsOf: callback.} = + ## Apply `callback` to the `self`, iff `self` is not an error. If `callback` + ## returns an error, return that error, else return `self` + + case self.oResultPrivate + of true: + callback(self.vResultPrivate) and self + of false: + self + +func filter*[E]( + self: Result[void, E], callback: proc(): Result[void, E] +): Result[void, E] {.effectsOf: callback.} = + ## Apply `callback` to the `self`, iff `self` is not an error. If `callback` + ## returns an error, return that error, else return `self` + + case self.oResultPrivate + of true: + callback() and self + of false: + self + +func filter*[T]( + self: Result[T, void], callback: proc(x: T): bool +): Result[T, void] {.effectsOf: callback.} = + ## Apply `callback` to the `self`, iff `self` is not an error. If `callback` + ## returns an error, return that error, else return `self` + + case self.oResultPrivate + of true: + if callback(self.vResultPrivate): + self + else: + Result[T, void].err() + of false: + self + +# Options compatibility + +template some*[T](O: type Opt, v: T): Opt[T] = + ## Create an `Opt` set to a value + ## + ## ``` + ## let oResultPrivate = Opt.some(42) + ## assert oResultPrivate.isSome and oResultPrivate.value() == 42 + ## ``` + Opt[T].ok(v) + +template none*(O: type Opt, T: type): Opt[T] = + ## Create an `Opt` set to none + ## + ## ``` + ## let oResultPrivate = Opt.none(int) + ## assert oResultPrivate.isNone + ## ``` + Opt[T].err() + +template isSome*(oResultPrivate: Opt): bool = + ## Alias for `isOk` + isOk oResultPrivate + +template isNone*(oResultPrivate: Opt): bool = + ## Alias of `isErr` + isErr oResultPrivate + +# Syntactic convenience + +template `?`*[T, E](self: Result[T, E]): auto = + ## Early return - if self is an error, we will return from the current + ## function, else we'll move on.. + ## + ## ``` + ## let v = ? funcWithResult() + ## echo v # prints value, not Result! + ## ``` + ## Experimental + # TODO the v copy is here to prevent multiple evaluations of self - could + # probably avoid it with some fancy macro magic.. + let v = (self) + case v.oResultPrivate + of false: + # Instead of `return xxx` we use `result = xxx; return` which avoids the + # need to rewrite `return` in macros that take over the `result` symbol and + # its associated control flow - this hack increases compatibility with + # chronos' async - see https://github.com/status-im/nim-stew/issues/37 for + # more in-depth discussion. + when typeof(result) is typeof(v): + result = v + return + else: + when E is void: + result = err(typeof(result)) + return + else: + result = err(typeof(result), v.eResultPrivate) + return + of true: + when not (T is void): + v.vResultPrivate + +# Collection integration + +iterator values*[T, E](self: Result[T, E]): maybeLent T = + ## Iterate over a Result as a 0/1-item collection, returning its value if set + case self.oResultPrivate + of true: + yield self.vResultPrivate + of false: + discard + +iterator errors*[T, E](self: Result[T, E]): maybeLent E = + ## Iterate over a Result as a 0/1-item collection, returning its error if set + case self.oResultPrivate + of false: + yield self.eResultPrivate + of true: + discard + +iterator items*[T](self: Opt[T]): maybeLent T = + ## Iterate over an Opt as a 0/1-item collection, returning its value if set + case self.oResultPrivate + of true: + yield self.vResultPrivate + of false: + discard + +iterator mvalues*[T, E](self: var Result[T, E]): var T = + case self.oResultPrivate + of true: + yield self.vResultPrivate + of false: + discard + +iterator merrors*[T, E](self: var Result[T, E]): var E = + case self.oResultPrivate + of false: + yield self.eResultPrivate + of true: + discard + +iterator mitems*[T](self: var Opt[T]): var T = + case self.oResultPrivate + of true: + yield self.vResultPrivate + of false: + discard + +func containsValue*(self: Result, v: auto): bool = + ## Return true iff the given result is set to a value that equals `v` + case self.oResultPrivate + of true: + self.vResultPrivate == v + of false: + false + +func containsError*(self: Result, e: auto): bool = + ## Return true iff the given result is set to an error that equals `e` + case self.oResultPrivate + of false: + self.eResultPrivate == e + of true: + false + +func contains*(self: Opt, v: auto): bool = + ## Return true iff the given `Opt` is set to a value that equals `v` - can + ## also be used in the "infix" `in` form: + ## + ## ```nim + ## assert "value" in Opt.some("value") + ## ``` + case self.oResultPrivate + of true: + self.vResultPrivate == v + of false: + false diff --git a/lib/monoucha0/test/types/opt.nim b/lib/monoucha0/test/types/opt.nim new file mode 100644 index 00000000..de6e001b --- /dev/null +++ b/lib/monoucha0/test/types/opt.nim @@ -0,0 +1,95 @@ +# Inspired by nim-results. + +type + Result*[T, E] = object + when E is void and T is void: # weirdness + has*: bool + elif E is void and not (T is void): # opt + case has*: bool + of true: + val*: T + else: + discard + elif not (E is void) and T is void: # err + case has*: bool + of true: + discard + else: + ex*: E + else: # result + case has*: bool + of true: + val*: T + else: + ex*: E + + Opt*[T] = Result[T, void] + + Err*[E] = Result[void, E] + +template ok*[E](t: type Err[E]): Err[E] = + Err[E](has: true) + +template ok*[T, E](t: type Result[T, E]; x: T): Result[T, E] = + Result[T, E](val: x, has: true) + +template ok*[T](x: T): auto = + ok(typeof(result), x) + +template ok*(): auto = + ok(typeof(result)) + +template err*[T, E](t: type Result[T, E]; e: E): Result[T, E] = + Result[T, E](has: false, ex: e) + +template err*[T](t: type Result[T, ref object]): auto = + t(has: false, ex: nil) + +template err*[T](t: type Result[T, void]): Result[T, void] = + Result[T, void](has: false) + +template err*(): auto = + err(typeof(result)) + +template err*[E](e: E): auto = + err(typeof(result), e) + +template opt*[T](v: T): auto = + ok(Opt[T], v) + +template opt*(t: typedesc): auto = + err(Result[t, void]) + +template opt*[T, E: not void](r: Result[T, E]): Opt[T] = + if r.isSome: + Opt[T].ok(r.get) + else: + Opt[T].err() + +template isSome*(res: Result): bool = res.has +template isNone*(res: Result): bool = not res.has +func get*[T, E](res: Result[T, E]): T {.inline.} = res.val +func get*[T, E](res: var Result[T, E]): var T = res.val +func get*[T, E](res: Result[T, E]; v: T): T = + if res.has: + res.val + else: + v +func error*[T, E](res: Result[T, E]): lent E {.inline.} = res.ex +template valType*[T, E](res: type Result[T, E]): auto = T +template errType*[T, E](res: type Result[T, E]): auto = E + +template `?`*[T, E](res: Result[T, E]): auto = + let x = res # for when res is a funcall + if x.has: + when not (T is void): + x.get + else: + discard + else: + when typeof(result) is Result[T, E]: + return x + elif not (E is void) and typeof(result).errType is E: + return err(x.error) + else: + return err() diff --git a/lib/monoucha0/todo b/lib/monoucha0/todo new file mode 100644 index 00000000..290f3b37 --- /dev/null +++ b/lib/monoucha0/todo @@ -0,0 +1,2 @@ +* import QJS test suite +* re-enable timezone offset as external hook diff --git a/lib/monoucha0/updver b/lib/monoucha0/updver new file mode 100755 index 00000000..4667b5e5 --- /dev/null +++ b/lib/monoucha0/updver @@ -0,0 +1,59 @@ +#!/bin/sh + +die() +{ + echo "$*" 2>&1 + exit 1 +} + +test "$1" || die "Usage: updver [version]" + +vv=$(printf '%s\n' "$1" | sed 's/v//') + +major=$(printf '%s\n' "$vv" | sed 's/\..*//') +minor=$(printf '%s\n' "$vv" | sed 's/[^.]*\.\([^.]*\)\..*/\1/') +patch=$(printf '%s\n' "$vv" | sed 's/.*\.//') + +vs="$major.$minor.$patch" + +oldhdr=$(head -1 NEWS) + +msgfile=$(mktemp) + +printf '%s (%s)\n\n' "$vs" "$(date +'%Y.%m.%d')" > "$msgfile" +cat NEWS >> "$msgfile" + +test -n "$EDITOR" || die 'missing $EDITOR env var' +$EDITOR "$msgfile" + +printf 'Ok? (y/n) ' +read -r ok +case "$ok" in +y|Y) ;; +*) die "Aborted. (File is $msgfile.)" ;; +esac + +cp "$msgfile" NEWS +echo "/version/c +version = \"$vs\" +. +p +wq" | ed -s monoucha.nimble + +echo "/Major/s/[0-9]*$/$major/ +/Minor/s/[0-9]*$/$minor/ +/Patch/s/[0-9]*$/$patch/ +g/.*/p +wq" | ed -s monoucha/version.nim + +git add . +git commit -m "Version $vs" + +tmp2=$(mktemp) + +while read line +do if test "$line" = "$oldhdr"; then break; fi + printf '%s\n' "$line" +done <NEWS >"$tmp2" + +git tag -faeF "$tmp2" "v$vs" |