diff options
author | bptato <nincsnevem662@gmail.com> | 2024-06-03 20:42:16 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-06-03 21:15:44 +0200 |
commit | 3aa8f1e0694d1606c3f3795f8b83e8a82caacd3e (patch) | |
tree | 9708d4599360116a96e4aa7f983eea387e8437c6 /src/js/fromjs.nim | |
parent | 3e12a95ab34e120fb958ba0eeebaada5def7cd11 (diff) | |
download | chawan-3aa8f1e0694d1606c3f3795f8b83e8a82caacd3e.tar.gz |
Move JS wrapper into Monoucha
Operation "modularize Chawan somewhat" part 3
Diffstat (limited to 'src/js/fromjs.nim')
-rw-r--r-- | src/js/fromjs.nim | 445 |
1 files changed, 0 insertions, 445 deletions
diff --git a/src/js/fromjs.nim b/src/js/fromjs.nim deleted file mode 100644 index 055ae19e..00000000 --- a/src/js/fromjs.nim +++ /dev/null @@ -1,445 +0,0 @@ -import std/macros -import std/options -import std/tables -import std/unicode - -import bindings/quickjs -import js/jserror -import js/jstypes -import js/jsopaque -import types/opt -import utils/twtstr - -proc fromJS*[T](ctx: JSContext; val: JSValue): JSResult[T] - -func isInstanceOfNonGlobal(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 isInstanceOfGlobal(ctx: JSContext; val: JSValue; class: string): bool = - let ctxOpaque = ctx.getOpaque() - #TODO gparent only works for a single level. (But this is not really a - # problem right now, because our global objects have at most one inheritance - # level.) - if ctx.isGlobal(class) or ctxOpaque.creg[class] == ctxOpaque.gparent: - # undefined -> global - if JS_IsUndefined(val): - return true - if JS_IsObject(val): - let global = JS_GetGlobalObject(ctx) - let p0 = JS_VALUE_GET_PTR(global) - let p1 = JS_VALUE_GET_PTR(val) - JS_FreeValue(ctx, global) - if p0 == p1: - return true - return false - -func isInstanceOf*(ctx: JSContext; val: JSValue; class: string): bool = - return ctx.isInstanceOfGlobal(val, class) or - ctx.isInstanceOfNonGlobal(val, class) - -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] = - 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 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) - else: - static: - error($T & " cannot be converted to JS automatically") - -proc fromJSFloat64(ctx: JSContext; val: JSValue): JSResult[float64] = - var f64: float64 - if JS_ToFloat64(ctx, addr f64, val) < 0: - return err() - return ok(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..<len: - result.add(quote do: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - `done` = ?fromJS[bool](ctx, doneVal) - if `done`: - return errTypeError("Too few arguments in sequence (got " & $`i` & - ", expected " & $`len` & ")") - let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) - if JS_IsException(valueVal): - return err() - defer: JS_FreeValue(ctx, valueVal) - `a`[`i`] = ?fromJS[typeof(`a`[`i`])](ctx, valueVal) - ) - if i == len - 1: - result.add(quote do: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - `done` = ?fromJS[bool](ctx, doneVal) - var i = `i` - # we're emulating a sequence, so we must query all remaining parameters - # too: - while not `done`: - inc i - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, - ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - `done` = ?fromJS[bool](ctx, doneVal) - if `done`: - let msg = "Too many arguments in sequence (got " & $i & - ", expected " & $`len` & ")" - return err(newTypeError(msg)) - JS_FreeValue(ctx, JS_GetProperty(ctx, next, - ctx.getOpaque().strRefs[jstValue])) - ) - -proc fromJSTuple[T: tuple](ctx: JSContext; val: JSValue): JSResult[T] = - let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) - if JS_IsException(itprop): - return err() - defer: JS_FreeValue(ctx, itprop) - let it = JS_Call(ctx, itprop, val, 0, nil) - if JS_IsException(it): - return err() - defer: JS_FreeValue(ctx, it) - let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) - if JS_IsException(nextMethod): - return err() - defer: JS_FreeValue(ctx, nextMethod) - var x: T - fromJSTupleBody(x) - return ok(x) - -proc fromJSSeq[T](ctx: JSContext; val: JSValue): JSResult[seq[T]] = - let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) - if JS_IsException(itprop): - return err() - defer: JS_FreeValue(ctx, itprop) - let it = JS_Call(ctx, itprop, val, 0, nil) - if JS_IsException(it): - return err() - defer: JS_FreeValue(ctx, it) - let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) - if JS_IsException(nextMethod): - return err() - defer: JS_FreeValue(ctx, nextMethod) - var s = newSeq[T]() - while true: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - let done = ?fromJS[bool](ctx, doneVal) - if done: - break - let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) - if JS_IsException(valueVal): - return err() - defer: JS_FreeValue(ctx, valueVal) - let genericRes = ?fromJS[typeof(s[0])](ctx, valueVal) - s.add(genericRes) - return ok(s) - -proc fromJSSet[T](ctx: JSContext; val: JSValue): JSResult[set[T]] = - let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) - if JS_IsException(itprop): - return err() - defer: JS_FreeValue(ctx, itprop) - let it = JS_Call(ctx, itprop, val, 0, nil) - if JS_IsException(it): - return err() - defer: JS_FreeValue(ctx, it) - let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) - if JS_IsException(nextMethod): - return err() - defer: JS_FreeValue(ctx, nextMethod) - var s: set[T] - while true: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - let done = ?fromJS[bool](ctx, doneVal) - if done: - break - let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) - if JS_IsException(valueVal): - return err() - defer: JS_FreeValue(ctx, valueVal) - let genericRes = ?fromJS[T](ctx, valueVal) - s.incl(genericRes) - return ok(s) - -proc fromJSTable[A, B](ctx: JSContext; val: JSValue): JSResult[Table[A, B]] = - if not JS_IsObject(val): - return err(newTypeError("object expected")) - var ptab: ptr UncheckedArray[JSPropertyEnum] - var plen: uint32 - let flags = cint(JS_GPN_STRING_MASK) - if JS_GetOwnPropertyNames(ctx, addr ptab, addr plen, val, flags) == -1: - # exception - return err() - defer: - for i in 0 ..< plen: - JS_FreeAtom(ctx, ptab[i].atom) - js_free(ctx, ptab) - var res = Table[A, B]() - for i in 0 ..< plen: - let atom = ptab[i].atom - let k = JS_AtomToValue(ctx, atom) - defer: JS_FreeValue(ctx, k) - let kn = ?fromJS[A](ctx, k) - let v = JS_GetProperty(ctx, val, atom) - defer: JS_FreeValue(ctx, v) - let vn = ?fromJS[B](ctx, v) - res[kn] = vn - return ok(res) - -template optionType[T](o: type Option[T]): auto = - T - -# Option vs Opt: -# Option is for nullable types, e.g. if you want to return either a string -# or null. (This is rather pointless for anything else.) -# Opt is for passing down exceptions received up in the chain. -# So e.g. none(T) translates to JS_NULL, but err() translates to JS_EXCEPTION. -proc fromJSOption[T](ctx: JSContext; val: JSValue): JSResult[Option[T]] = - if JS_IsNull(val): - return ok(none(T)) - let res = ?fromJS[T](ctx, val) - return ok(option(res)) - -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) - let r = strictParseEnum[T](s) - if r.isSome: - return ok(r.get) - return errTypeError("`" & 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) - if ctx.isInstanceOfGlobal(val, t): - return ok(?getGlobalOpaque0(ctx, val)) - if not JS_IsObject(val): - return err(newTypeError("Value is not an object")) - if not isInstanceOfNonGlobal(ctx, val, t): - return errTypeError(t & " expected") - 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): - 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) - when v isnot JSValue: - JS_FreeValue(ctx, esm) - return ok(d) - -proc fromJSArrayBuffer(ctx: JSContext; val: JSValue): JSResult[JSArrayBuffer] = - var len: csize_t - let p = JS_GetArrayBuffer(ctx, addr len, val) - if p == nil: - return err() - let abuf = JSArrayBuffer( - len: len, - p: cast[ptr UncheckedArray[uint8]](p) - ) - return ok(abuf) - -proc fromJSArrayBufferView(ctx: JSContext; val: JSValue): - JSResult[JSArrayBufferView] = - var offset: csize_t - var nmemb: csize_t - var nsize: csize_t - let jsbuf = JS_GetTypedArrayBuffer(ctx, val, addr offset, addr nmemb, - addr nsize) - let abuf = ?fromJSArrayBuffer(ctx, jsbuf) - let view = JSArrayBufferView( - abuf: abuf, - offset: offset, - nmemb: nmemb, - nsize: nsize - ) - return ok(view) - -type FromJSAllowedT = (object and not (Result|Option|Table|JSValue|JSDict| - JSArrayBuffer|JSArrayBufferView|JSUint8Array)) - -macro fromJS2(ctx: JSContext; val: JSValue; x: static string): untyped = - let id = ident("fromJS" & x) - return quote do: - `id`(`ctx`, `val`) - -proc fromJS*[T](ctx: JSContext; val: JSValue): JSResult[T] = - when T is string: - return fromJSString(ctx, val) - elif T is Option: - return fromJSOption[optionType(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 float64: - return fromJSFloat64(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 T is JSArrayBuffer: - return fromJSArrayBuffer(ctx, val) - elif T is JSArrayBufferView: - return fromJSArrayBufferView(ctx, val) - else: - return fromJS2(ctx, val, $T) - -const JS_ATOM_TAG_INT = 1u32 shl 31 - -func JS_IsNumber*(v: JSAtom): JS_BOOL = - return (uint32(v) and JS_ATOM_TAG_INT) != 0 - -func fromJS*[T: string|uint32|JSAtom](ctx: JSContext; atom: JSAtom): Opt[T] = - when T is JSAtom: - return ok(atom) - elif T is SomeNumber: - if JS_IsNumber(atom): - return ok(uint32(atom) and (not JS_ATOM_TAG_INT)) - return err() - else: - let cs = JS_AtomToCString(ctx, atom) - if cs == nil: - return err() - let s = $cs - JS_FreeCString(ctx, cs) - return ok(s) - -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) |