diff options
author | bptato <nincsnevem662@gmail.com> | 2023-08-28 22:33:31 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-08-28 22:38:00 +0200 |
commit | 1422f3393301cd6105b3939be194c7c119fcf967 (patch) | |
tree | 32969f2af8fe5293c13215a84229915a7970e828 /src/js/javascript.nim | |
parent | ba2a62f6d3a2879c9506ea3fb5aa5552fc4674d9 (diff) | |
download | chawan-1422f3393301cd6105b3939be194c7c119fcf967.tar.gz |
javascript: refactor
Split out parts of the JS module, because it was starting to confuse the compiler a little. (Peakmem is back at 750M. Interesting.)
Diffstat (limited to 'src/js/javascript.nim')
-rw-r--r-- | src/js/javascript.nim | 402 |
1 files changed, 12 insertions, 390 deletions
diff --git a/src/js/javascript.nim b/src/js/javascript.nim index e8de8424..ba8f49dd 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -37,7 +37,10 @@ import strutils import tables import unicode -import io/promise +import js/error +import js/opaque +import js/tojs +import js/typeptr import utils/opt import bindings/quickjs @@ -70,57 +73,8 @@ when sizeof(int) < sizeof(int64): export quickjs.`==` type - JSErrorEnum* = enum - # QuickJS internal errors - JS_EVAL_ERROR0 = "EvalError" - JS_RANGE_ERROR0 = "RangeError" - JS_REFERENCE_ERROR0 = "ReferenceError" - JS_SYNTAX_ERROR0 = "SyntaxError" - JS_TYPE_ERROR0 = "TypeError" - JS_URI_ERROR0 = "URIError" - JS_INTERNAL_ERROR0 = "InternalError" - JS_AGGREGATE_ERROR0 = "AggregateError" - # Chawan errors - JS_DOM_EXCEPTION = "DOMException" - - JSSymbolRefs = enum - ITERATOR = "iterator" - ASYNC_ITERATOR = "asyncIterator" - TO_STRING_TAG = "toStringTag" - - JSStrRefs = enum - DONE = "done" - VALUE = "value" - NEXT = "next" - - JSContextOpaque* = ref object - creg: Table[string, JSClassID] - typemap: Table[pointer, JSClassID] - ctors: Table[JSClassID, JSValue] - parents: Table[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: Table[JSClassID, seq[JSCFunctionListEntry]] - gclaz: string - sym_refs: array[JSSymbolRefs, JSAtom] - str_refs: array[JSStrRefs, JSAtom] - Array_prototype_values: JSValue - Object_prototype_valueOf*: JSValue - err_ctors: array[JSErrorEnum, JSValue] - - JSRuntimeOpaque* = ref object - plist: Table[pointer, pointer] # Nim, JS - flist: seq[seq[JSCFunctionListEntry]] - fins: Table[JSClassID, proc(val: JSValue)] - refmap: Table[pointer, tuple[cref, cunref: (proc() {.closure.})]] - JSFunctionList = openArray[JSCFunctionListEntry] - JSError* = ref object of RootObj - e*: JSErrorEnum - message*: string - BoundFunction = object t: BoundFunctionType name: string @@ -137,70 +91,6 @@ type PROPERTY_HAS = "js_prop_has" FINALIZER = "js_fin" -const QuickJSErrors = [ - JS_EVAL_ERROR0, - JS_RANGE_ERROR0, - JS_REFERENCE_ERROR0, - JS_SYNTAX_ERROR0, - JS_TYPE_ERROR0, - JS_URI_ERROR0, - JS_INTERNAL_ERROR0, - JS_AGGREGATE_ERROR0 -] - -# 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, r: Rune): 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: SomeFloat): 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(ctx: JSContext, e: enum): JSValue -proc toJS(ctx: JSContext, j: JSValue): JSValue -proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue -proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue -proc toJS(ctx: JSContext, promise: EmptyPromise): JSValue -proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue -proc toJS*(ctx: JSContext, obj: ref object): JSValue -proc toJS*(ctx: JSContext, err: JSError): JSValue -proc toJS*(ctx: JSContext, f: JSCFunction): 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 - -# 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) - -func getOpaque*(ctx: JSContext): JSContextOpaque = - return cast[JSContextOpaque](JS_GetContextOpaque(ctx)) - -func getOpaque*(rt: JSRuntime): JSRuntimeOpaque = - return cast[JSRuntimeOpaque](JS_GetRuntimeOpaque(rt)) - var runtimes {.threadVar.}: seq[JSRuntime] proc newJSRuntime*(): JSRuntime = @@ -215,38 +105,8 @@ proc newJSRuntime*(): JSRuntime = proc newJSContext*(rt: JSRuntime): JSContext = let ctx = JS_NewContext(rt) - var opaque = new(JSContextOpaque) + let opaque = newJSContextOpaque(ctx) GC_ref(opaque) - - block: # get well-known symbols and other functions - let global = JS_GetGlobalObject(ctx) - block: - let sym = JS_GetPropertyStr(ctx, global, "Symbol") - for s in JSSymbolRefs: - let name = $s - let val = JS_GetPropertyStr(ctx, sym, cstring(name)) - assert JS_IsSymbol(val) - opaque.sym_refs[s] = JS_ValueToAtom(ctx, val) - JS_FreeValue(ctx, val) - JS_FreeValue(ctx, sym) - for s in JSStrRefs: - let ss = $s - opaque.str_refs[s] = JS_NewAtomLen(ctx, cstring(ss), csize_t(ss.len)) - block: - let arrproto = JS_GetClassProto(ctx, JS_CLASS_ARRAY) - opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto, - "values") - JS_FreeValue(ctx, arrproto) - block: - let objproto = JS_GetClassProto(ctx, JS_CLASS_OBJECT) - opaque.Object_prototype_valueOf = JS_GetPropertyStr(ctx, objproto, "valueOf") - JS_FreeValue(ctx, objproto) - for e in JSErrorEnum: - let s = $e - let err = JS_GetPropertyStr(ctx, global, cstring(s)) - opaque.err_ctors[e] = err - JS_FreeValue(ctx, global) - JS_SetContextOpaque(ctx, cast[pointer](opaque)) return ctx @@ -288,13 +148,6 @@ proc free*(rt: var JSRuntime) = runtimes.del(runtimes.find(rt)) rt = nil -proc setOpaque[T](ctx: JSContext, val: JSValue, opaque: T) = - let rt = JS_GetRuntime(ctx) - let rtOpaque = rt.getOpaque() - let p = JS_VALUE_GET_PTR(val) - rtOpaque.plist[cast[pointer](opaque)] = p - JS_SetOpaque(val, cast[pointer](opaque)) - proc setGlobal*[T](ctx: JSContext, global: JSValue, obj: T) = # Add JSValue reference. let p = JS_VALUE_GET_PTR(global) @@ -391,43 +244,6 @@ func isInstanceOf*(ctx: JSContext, val: JSValue, class: string): bool = break return found -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 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 definePropertyCWE*[T](ctx: JSContext, this: JSValue, name: string, - prop: T) = - defineProperty(ctx, this, name, prop, JS_PROP_C_W_E) - -# 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 - new(x) - return getTypePtr(x) - -func getTypePtr(t: type): pointer = - var x: t - return getTypePtr(x) - # Add all LegacyUnforgeable functions defined on the prototype chain to # the opaque. # Since every prototype has a list of all its ancestor's LegacyUnforgeable @@ -568,14 +384,6 @@ proc newAggregateError*(message: string): JSError = message: message ) -proc defineUnforgeable*(ctx: JSContext, this: JSValue) = - if unlikely(JS_IsException(this)): - return - let ctxOpaque = ctx.getOpaque() - let classid = JS_GetClassID(this) - ctxOpaque.unforgeable.withValue(classid, uf): - JS_SetPropertyFunctionList(ctx, this, addr uf[][0], cint(uf[].len)) - func fromJSString(ctx: JSContext, val: JSValue): Result[string, JSError] = var plen: csize_t let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring @@ -839,7 +647,6 @@ macro unpackReturnType(f: typed) = let rv = params[0] if rv.isErrType(): return quote do: void - doAssert rv[0].strVal == "Result" let rvv = rv[1] return quote do: `rvv` @@ -936,6 +743,8 @@ proc fromJSPObj0(ctx: JSContext, val: JSValue, t: string): proc fromJSObject[T: ref object](ctx: JSContext, val: JSValue): Result[T, JSError] = return ok(cast[T](?fromJSPObj0(ctx, val, $T))) +type FromJSAllowedT = (object and not (Result|Option|Table|JSValue)) + proc fromJS*[T](ctx: JSContext, val: JSValue): Result[T, JSError] = when T is string: return fromJSString(ctx, val) @@ -980,7 +789,7 @@ proc fromJSPObj[T](ctx: JSContext, val: JSValue): Result[ptr T, JSError] = return cast[Result[ptr T, JSError]](fromJSPObj0(ctx, val, $T)) template fromJSP*[T](ctx: JSContext, val: JSValue): untyped = - when T is object and not compiles(fromJS[T](ctx, val)): + when T is FromJSAllowedT: fromJSPObj[T](ctx, val) else: fromJS[T](ctx, val) @@ -1005,197 +814,6 @@ proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): (proc(x: T): Result[U, JSError]) = return fromJSFunction1[T, U](ctx, val) -proc toJS*(ctx: JSContext, s: cstring): JSValue = - return JS_NewString(ctx, s) - -proc toJS*(ctx: JSContext, s: string): JSValue = - return toJS(ctx, cstring(s)) - -proc toJS(ctx: JSContext, r: Rune): JSValue = - return toJS(ctx, $r) - -proc toJS*(ctx: JSContext, n: int32): JSValue = - return JS_NewInt32(ctx, n) - -proc toJS*(ctx: JSContext, n: int64): JSValue = - return JS_NewInt64(ctx, n) - -# Always int32, so we don't risk 32-bit only breakage. -proc toJS*(ctx: JSContext, n: int): JSValue = - 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: SomeFloat): JSValue = - return JS_NewFloat64(ctx, float64(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): - let res = toJS(ctx, opt.error) - if not JS_IsNull(res): - return JS_Throw(ctx, res) - else: - return JS_NULL - -proc toJS(ctx: JSContext, s: seq): JSValue = - let a = JS_NewArray(ctx) - if not JS_IsException(a): - for i in 0..s.high: - let j = toJS(ctx, s[i]) - if JS_IsException(j): - return j - if JS_DefinePropertyValueInt64(ctx, a, int64(i), j, - JS_PROP_C_W_E or JS_PROP_THROW) < 0: - return JS_EXCEPTION - return a - -proc toJSP0(ctx: JSContext, p, tp: pointer): 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 clazz = ctx.getOpaque().typemap[tp] - let jsObj = JS_NewObjectClass(ctx, clazz) - setOpaque(ctx, jsObj, p) - # We are "constructing" a new JS object, so we must add unforgeable - # properties here. - defineUnforgeable(ctx, jsObj) # not an exception - return jsObj - -proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue = - if obj == nil: - return JS_NULL - let p = cast[pointer](obj) - GC_ref(obj) - let tp = getTypePtr(obj) - return toJSP0(ctx, p, tp) - -proc toJS*(ctx: JSContext, obj: ref object): JSValue = - return toJSRefObj(ctx, obj) - -proc toJS(ctx: JSContext, e: enum): JSValue = - return toJS(ctx, $e) - -proc toJS(ctx: JSContext, j: JSValue): JSValue = - return j - -proc toJS(ctx: JSContext, promise: EmptyPromise): JSValue = - var resolving_funcs: array[2, JSValue] - let jsPromise = JS_NewPromiseCapability(ctx, addr resolving_funcs[0]) - if JS_IsException(jsPromise): - return JS_EXCEPTION - promise.then(proc() = - var x = JS_UNDEFINED - let res = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, addr x) - JS_FreeValue(ctx, res) - JS_FreeValue(ctx, resolving_funcs[0]) - JS_FreeValue(ctx, resolving_funcs[1])) - return jsPromise - -proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue = - var resolving_funcs: array[2, JSValue] - let jsPromise = JS_NewPromiseCapability(ctx, addr resolving_funcs[0]) - if JS_IsException(jsPromise): - return JS_EXCEPTION - promise.then(proc(x: T) = - var x = toJS(ctx, x) - let res = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, addr x) - JS_FreeValue(ctx, res) - JS_FreeValue(ctx, x) - JS_FreeValue(ctx, resolving_funcs[0]) - JS_FreeValue(ctx, resolving_funcs[1])) - return jsPromise - -proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue = - var resolving_funcs: array[2, JSValue] - let jsPromise = JS_NewPromiseCapability(ctx, addr resolving_funcs[0]) - if JS_IsException(jsPromise): - return JS_EXCEPTION - promise.then(proc(x: Result[T, E]) = - if x.isOk: - let x = when T is void: - JS_UNDEFINED - else: - toJS(ctx, x.get) - let res = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, unsafeAddr x) - JS_FreeValue(ctx, res) - JS_FreeValue(ctx, x) - else: # err - let x = when E is void: - JS_UNDEFINED - else: - toJS(ctx, x.error) - let res = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, unsafeAddr x) - JS_FreeValue(ctx, res) - JS_FreeValue(ctx, x) - JS_FreeValue(ctx, resolving_funcs[0]) - JS_FreeValue(ctx, resolving_funcs[1])) - return jsPromise - -proc toJS*(ctx: JSContext, err: JSError): JSValue = - 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().err_ctors[err.e] - return JS_CallConstructor(ctx, ctor, 1, addr msg) - -proc toJS*(ctx: JSContext, f: JSCFunction): JSValue = - return ctx.newJSCFunction("", f) - -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().refmap[p] = ( - (proc() = - GC_ref(parent)), - (proc() = - GC_unref(parent)) - ) - GC_ref(parent) - let tp = getTypePtr(child) - return toJSP0(ctx, p, tp) - -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 ru = rtOpaque.refmap[parent] - ru.cref() - rtOpaque.refmap[p] = ru - let tp = getTypePtr(child) - return toJSP0(ctx, p, tp) - proc defineConsts*[T](ctx: JSContext, classid: JSClassID, consts: static openarray[(string, T)]) = let proto = ctx.getOpaque().ctors[classid] @@ -2166,8 +1784,12 @@ proc bindFunctions(stmts: NimNode, info: var RegistryInfo) = do: info.getset[f0] = (newNilLit(), f1, false) of PROPERTY_GET: + if info.propGetFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ property getters.") info.propGetFun = f1 of PROPERTY_HAS: + if info.propHasFun.kind != nnkNilLit: + error("Class " & info.tname & " has 2+ hasprop getters.") info.propHasFun = f1 of FINALIZER: f0 = fun.name |