about summary refs log blame commit diff stats
path: root/src/js/tojs.nim
blob: ca38bd2b0c56a548c5382d32368201557e76b0d5 (plain) (tree)



























































































































































                                                                                
                                                                          








                                                                     
                 





                                                           
                          




                                        





































































                                                                               


                                                      












                                                                            
                            




                                        










                                                                            
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, 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 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
  needsref = true
  return jsObj

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, needsref)
  if needsref:
    GC_ref(obj)
  return val

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]
  let ret = JS_CallConstructor(ctx, ctor, 1, addr msg)
  JS_FreeValue(ctx, msg)
  return ret

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))
  )
  let tp = getTypePtr(child)
  var needsref = false
  let val = toJSP0(ctx, p, tp, 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 ru = rtOpaque.refmap[parent]
  ru.cref()
  rtOpaque.refmap[p] = ru
  let tp = getTypePtr(child)
  return toJSP0(ctx, p, tp)