about summary refs log tree commit diff stats
path: root/src/js/tojs.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/tojs.nim')
-rw-r--r--src/js/tojs.nim273
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)