import macros import options import strutils import tables import unicode import bindings/quickjs import js/dict import js/error import js/opaque import js/tojs import types/opt proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] func isInstanceOf*(ctx: JSContext, val: JSValue, class: string): bool = let ctxOpaque = ctx.getOpaque() var classid = JS_GetClassID(val) let tclassid = ctxOpaque.creg[class] var found = false while true: if classid == tclassid: found = true break ctxOpaque.parents.withValue(classid, val): classid = val[] do: classid = 0 # not defined by Chawan; assume parent is Object. if classid == 0: break return found func toString(ctx: JSContext, val: JSValue): Opt[string] = var plen: csize_t let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring if outp != nil: var ret = newString(plen) if plen != 0: prepareMutation(ret) copyMem(addr ret[0], outp, plen) result = ok(ret) JS_FreeCString(ctx, outp) func fromJSString(ctx: JSContext, val: JSValue): JSResult[string] = var plen: csize_t let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring if outp == nil: return err() var ret = newString(plen) if plen != 0: prepareMutation(ret) copyMem(addr ret[0], outp, plen) JS_FreeCString(ctx, outp) return ok(ret) func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): JSResult[T] = if not JS_IsNumber(val): return err() when T is int: # Always int32, so we don't risk 32-bit only breakage. # If int64 is needed, specify it explicitly. var ret: int32 if JS_ToInt32(ctx, addr ret, val) < 0: return err() return ok(int(ret)) elif T is uint: var ret: uint32 if JS_ToUint32(ctx, addr ret, val) < 0: return err() return ok(uint(ret)) elif T is int32: var ret: int32 if JS_ToInt32(ctx, addr ret, val) < 0: return err() return ok(ret) elif T is int64: var ret: int64 if JS_ToInt64(ctx, addr ret, val) < 0: return err() return ok(ret) elif T is uint32: var ret: uint32 if JS_ToUint32(ctx, addr ret, val) < 0: return err() return ok(ret) elif T is uint64: var ret: uint32 if JS_ToUint32(ctx, addr ret, val) < 0: return err() return ok(cast[uint64](ret)) proc fromJSFloat[T: SomeFloat](ctx: JSContext, val: JSValue): JSResult[T] = if not JS_IsNumber(val): return err() var f64: float64 if JS_ToFloat64(ctx, addr f64, val) < 0: return err() return ok(cast[T](f64)) macro fromJSTupleBody(a: tuple) = let len = a.getType().len - 1 let done = ident("done") result = newStmtList(quote do: var `done`: bool) for i in 0.. 1: return err() return ok(s[0]) proc fromJSRune(ctx: JSContext, val: JSValue): Opt[Rune] = let s = ?toString(ctx, val) var i = 0 var r: Rune fastRuneAt(s, i, r) if i < s.len: return err() return ok(r) template optionType[T](o: type Option[T]): auto = T # wrap proc fromJSOption[T](ctx: JSContext, val: JSValue): JSResult[Option[T]] = if JS_IsUndefined(val): #TODO what about null? return err() let res = ?fromJS[T](ctx, val) return ok(some(res)) # wrap proc fromJSOpt[T](ctx: JSContext, val: JSValue): JSResult[T] = if JS_IsUndefined(val): #TODO what about null? return err() let res = fromJS[T.valType](ctx, val) if res.isErr: return ok(opt(T.valType)) return ok(opt(res.get)) proc fromJSBool(ctx: JSContext, val: JSValue): JSResult[bool] = let ret = JS_ToBool(ctx, val) if ret == -1: # exception return err() if ret == 0: return ok(false) return ok(true) proc fromJSEnum[T: enum](ctx: JSContext, val: JSValue): JSResult[T] = if JS_IsException(val): return err() let s = ?toString(ctx, val) try: return ok(parseEnum[T](s)) except ValueError: return err(newTypeError("`" & s & "' is not a valid value for enumeration " & $T)) proc fromJSPObj0(ctx: JSContext, val: JSValue, t: string): JSResult[pointer] = if JS_IsException(val): return err(nil) if JS_IsNull(val): return ok(nil) let ctxOpaque = ctx.getOpaque() if ctxOpaque.gclaz == t: return ok(?getGlobalOpaque0(ctx, val)) if not JS_IsObject(val): return err(newTypeError("Value is not an object")) if not isInstanceOf(ctx, val, t): let errmsg = t & " expected" JS_ThrowTypeError(ctx, cstring(errmsg)) return err(newTypeError(errmsg)) let classid = JS_GetClassID(val) let op = JS_GetOpaque(val, classid) return ok(op) proc fromJSObject[T: ref object](ctx: JSContext, val: JSValue): JSResult[T] = return ok(cast[T](?fromJSPObj0(ctx, val, $T))) proc fromJSVoid(ctx: JSContext, val: JSValue): JSResult[void] = if JS_IsException(val): #TODO maybe wrap or something return err() return ok() proc fromJSDict[T: JSDict](ctx: JSContext, val: JSValue): JSResult[T] = if not JS_IsUndefined(val) and not JS_IsNull(val) and not JS_IsObject(val): return err(newTypeError("Dictionary is not an object")) #TODO throw on missing required values var d: T if JS_IsObject(val): for k, v in d.fieldPairs: let esm = JS_GetPropertyStr(ctx, val, k) if not JS_IsUndefined(esm): v = ?fromJS[typeof(v)](ctx, esm) return ok(d) type FromJSAllowedT = (object and not (Result|Option|Table|JSValue|JSDict)) proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] = when T is string: return fromJSString(ctx, val) elif T is char: return fromJSChar(ctx, val) elif T is Rune: return fromJSRune(ctx, val) elif T is (proc): return fromJSFunction[T](ctx, val) elif T is Option: return fromJSOption[optionType(T)](ctx, val) elif T is Opt: # unwrap return fromJSOpt[T](ctx, val) elif T is seq: return fromJSSeq[typeof(result.get.items)](ctx, val) elif T is set: return fromJSSet[typeof(result.get.items)](ctx, val) elif T is tuple: return fromJSTuple[T](ctx, val) elif T is bool: return fromJSBool(ctx, val) elif typeof(result).valType is Table: return fromJSTable[typeof(result.get.keys), typeof(result.get.values)](ctx, val) elif T is SomeInteger: return fromJSInt[T](ctx, val) elif T is SomeFloat: return fromJSFloat[T](ctx, val) elif T is enum: return fromJSEnum[T](ctx, val) elif T is JSValue: return ok(val) elif T is ref object: return fromJSObject[T](ctx, val) elif T is void: return fromJSVoid(ctx, val) elif T is JSDict: return fromJSDict[T](ctx, val) elif compiles(fromJS2(ctx, val, result)): fromJS2(ctx, val, result) else: static: error("Unrecognized type " & $T) const JS_ATOM_TAG_INT = cuint(1u32 shl 31) func JS_IsNumber*(v: JSAtom): JS_BOOL = return (cast[cuint](v) and JS_ATOM_TAG_INT) != 0 func fromJS*[T: string|uint32](ctx: JSContext, atom: JSAtom): Opt[T] = when T is SomeNumber: if JS_IsNumber(atom): return ok(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT))) else: let val = JS_AtomToValue(ctx, atom) return toString(ctx, val) proc fromJSPObj[T](ctx: JSContext, val: JSValue): JSResult[ptr T] = return cast[JSResult[ptr T]](fromJSPObj0(ctx, val, $T)) template fromJSP*[T](ctx: JSContext, val: JSValue): untyped = when T is FromJSAllowedT: fromJSPObj[T](ctx, val) else: fromJS[T](ctx, val)