diff options
Diffstat (limited to 'src/js/tojs.nim')
-rw-r--r-- | src/js/tojs.nim | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/src/js/tojs.nim b/src/js/tojs.nim new file mode 100644 index 00000000..4cde2e15 --- /dev/null +++ b/src/js/tojs.nim @@ -0,0 +1,273 @@ +import options +import tables +import unicode + +import bindings/quickjs +import io/promise +import js/error +import js/opaque +import js/typeptr +import utils/opt + +# 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 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) +makeToJSP(JSValue) + +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) + +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 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)) + +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 JS_NewCFunction(ctx, f, cstring"", 0) + +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) |