about summary refs log tree commit diff stats
path: root/src/js
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-10-21 20:22:21 +0200
committerbptato <nincsnevem662@gmail.com>2023-10-21 20:22:21 +0200
commit69870f3b974e65d61b564b396e01d21cc023e6e9 (patch)
treef773f5446a2232739c4818080e467851deabb4f8 /src/js
parentf77db0bdefd43ec03d7d26ec7a5d793bece25030 (diff)
downloadchawan-69870f3b974e65d61b564b396e01d21cc023e6e9.tar.gz
javascript: add TextEncoder, TextDecoder
Diffstat (limited to 'src/js')
-rw-r--r--src/js/arraybuffer.nim18
-rw-r--r--src/js/encoding.nim112
-rw-r--r--src/js/fromjs.nim32
-rw-r--r--src/js/javascript.nim1
-rw-r--r--src/js/opaque.nim4
-rw-r--r--src/js/tojs.nim11
6 files changed, 178 insertions, 0 deletions
diff --git a/src/js/arraybuffer.nim b/src/js/arraybuffer.nim
new file mode 100644
index 00000000..ff9fa24d
--- /dev/null
+++ b/src/js/arraybuffer.nim
@@ -0,0 +1,18 @@
+import bindings/quickjs
+
+type
+  JSArrayBuffer* = object
+    p*: ptr UncheckedArray[uint8]
+    len*: csize_t
+    dealloc*: JSFreeArrayBufferDataFunc
+
+  JSArrayBufferView* = object
+    abuf*: JSArrayBuffer
+    offset*: csize_t # offset into the buffer
+    nmemb*: csize_t # number of members
+    nsize*: csize_t # member size
+
+  JSUint8Array* = object
+    abuf*: JSArrayBuffer
+    offset*: csize_t # offset into the buffer
+    nmemb*: csize_t # number of members
diff --git a/src/js/encoding.nim b/src/js/encoding.nim
new file mode 100644
index 00000000..31f6f575
--- /dev/null
+++ b/src/js/encoding.nim
@@ -0,0 +1,112 @@
+import std/streams
+
+import js/arraybuffer
+import js/dict
+import js/error
+import js/javascript
+
+import chakasu/charset
+import chakasu/decoderstream
+import chakasu/encoderstream
+
+type
+  TextEncoder = ref object
+
+  TextDecoder = ref object
+    encoding: Charset
+    errorMode: DecoderErrorMode
+    ignoreBOM {.jsget.}: bool
+    doNotFlush: bool
+    bomSeen: bool
+    decoder: DecoderStream
+    encoder: EncoderStream # to return the string to JS
+    istream: StringStream
+
+jsDestructor(TextDecoder)
+jsDestructor(TextEncoder)
+
+type TextDecoderOptions = object of JSDict
+  fatal: bool
+  ignoreBOM: bool
+
+func newTextDecoder(label = "utf-8", options = TextDecoderOptions()):
+    JSResult[TextDecoder] {.jsctor.} =
+  let errorMode = if options.fatal:
+    DECODER_ERROR_MODE_FATAL
+  else:
+    DECODER_ERROR_MODE_REPLACEMENT
+  let encoding = getCharset(label)
+  if encoding in {CHARSET_UNKNOWN, CHARSET_REPLACEMENT}:
+    return err(newRangeError("Invalid encoding label"))
+  return ok(TextDecoder(
+    errorMode: errorMode,
+    ignoreBOM: options.ignoreBOM,
+    encoding: encoding
+  ))
+
+type TextDecodeOptions = object of JSDict
+  stream: bool
+
+#TODO AllowSharedBufferSource
+proc decode(this: TextDecoder, input = none(JSArrayBufferView),
+    options = TextDecodeOptions()): string {.jsfunc.} =
+  if not this.doNotFlush:
+    if this.istream != nil:
+      this.istream.close()
+    if this.decoder != nil:
+      this.decoder.close()
+    if this.encoder != nil:
+      this.encoder.close()
+    this.istream = newStringStream()
+    this.decoder = newDecoderStream(this.istream, cs = this.encoding,
+      errormode = this.errorMode)
+    this.encoder = newEncoderStream(this.decoder, cs = CHARSET_UTF_8)
+    this.bomSeen = false
+  if this.doNotFlush != options.stream:
+    this.doNotFlush = options.stream
+    this.decoder.setInhibitCheckEnd(options.stream)
+  if input.isSome:
+    let input = input.get
+    let pos = this.istream.getPosition()
+    #TODO input offset?
+    this.istream.writeData(input.abuf.p, int(input.abuf.len))
+    this.istream.setPosition(pos)
+  #TODO this should return a JSString, so we do not needlessly re-encode
+  # the output. (Right now we do, implicitly through toJS.)
+  return this.encoder.readAll()
+
+func jencoding(this: TextDecoder): string {.jsfget: "encoding".} =
+  return $this.encoding
+
+func fatal(this: TextDecoder): bool {.jsfget.} =
+  return this.errorMode == DECODER_ERROR_MODE_FATAL
+
+func newTextEncoder(): TextEncoder {.jsctor.} =
+  return TextEncoder()
+
+func jencoding(this: TextEncoder): string {.jsfget: "encoding".} =
+  return "utf-8"
+
+proc dealloc_wrap(rt: JSRuntime, opaque, p: pointer) {.cdecl.} =
+  dealloc(p)
+
+proc encode(this: TextEncoder, input = ""): JSUint8Array {.jsfunc.} =
+  # input is already UTF-8 here :P
+  let buf = cast[ptr UncheckedArray[uint8]](alloc(input.len))
+  copyMem(buf, unsafeAddr input[0], input.len)
+  let abuf = JSArrayBuffer(
+    p: buf,
+    len: csize_t(input.len),
+    dealloc: dealloc_wrap
+  )
+  return JSUint8Array(
+    abuf: abuf,
+    offset: 0,
+    nmemb: csize_t(input.len)
+  )
+
+#TODO encodeInto
+
+proc addEncodingModule*(ctx: JSContext) =
+  ctx.registerType(TextDecoder)
+  ctx.registerType(TextEncoder)
diff --git a/src/js/fromjs.nim b/src/js/fromjs.nim
index dbc009bc..b2e1cc91 100644
--- a/src/js/fromjs.nim
+++ b/src/js/fromjs.nim
@@ -5,6 +5,7 @@ import tables
 import unicode
 
 import bindings/quickjs
+import js/arraybuffer
 import js/dict
 import js/error
 import js/opaque
@@ -416,6 +417,33 @@ proc fromJSDict[T: JSDict](ctx: JSContext, val: JSValue): JSResult[T] =
         v = ?fromJS[typeof(v)](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))
 
 proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] =
@@ -456,6 +484,10 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] =
     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)
   elif compiles(fromJS2(ctx, val, result)):
     fromJS2(ctx, val, result)
   else:
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index c744345d..996137cd 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -144,6 +144,7 @@ proc free*(ctx: var JSContext) =
       JS_FreeValue(ctx, v)
     JS_FreeValue(ctx, opaque.Array_prototype_values)
     JS_FreeValue(ctx, opaque.Object_prototype_valueOf)
+    JS_FreeValue(ctx, opaque.Uint8Array_ctor)
     for v in opaque.err_ctors:
       JS_FreeValue(ctx, v)
     GC_unref(opaque)
diff --git a/src/js/opaque.nim b/src/js/opaque.nim
index bdc84b5a..30456602 100644
--- a/src/js/opaque.nim
+++ b/src/js/opaque.nim
@@ -29,6 +29,7 @@ type
     str_refs*: array[JSStrRefs, JSAtom]
     Array_prototype_values*: JSValue
     Object_prototype_valueOf*: JSValue
+    Uint8Array_ctor*: JSValue
     err_ctors*: array[JSErrorEnum, JSValue]
     htmldda*: JSClassID # only one of these exists: document.all.
 
@@ -64,6 +65,9 @@ func newJSContextOpaque*(ctx: JSContext): JSContextOpaque =
       let objproto = JS_GetClassProto(ctx, JS_CLASS_OBJECT)
       opaque.Object_prototype_valueOf = JS_GetPropertyStr(ctx, objproto, "valueOf")
       JS_FreeValue(ctx, objproto)
+    block:
+      let u8actor = JS_GetPropertyStr(ctx, global, "Uint8Array")
+      opaque.Uint8Array_ctor = u8actor
     for e in JSErrorEnum:
       let s = $e
       let err = JS_GetPropertyStr(ctx, global, cstring(s))
diff --git a/src/js/tojs.nim b/src/js/tojs.nim
index 89ca4cd4..e614c451 100644
--- a/src/js/tojs.nim
+++ b/src/js/tojs.nim
@@ -4,6 +4,7 @@ import unicode
 
 import bindings/quickjs
 import io/promise
+import js/arraybuffer
 import js/dict
 import js/error
 import js/opaque
@@ -259,6 +260,16 @@ proc toJS*(ctx: JSContext, err: JSError): JSValue =
 proc toJS*(ctx: JSContext, f: JSCFunction): JSValue =
   return JS_NewCFunction(ctx, f, cstring"", 0)
 
+proc toJS*(ctx: JSContext, abuf: JSArrayBuffer): JSValue =
+  return JS_NewArrayBuffer(ctx, abuf.p, abuf.len, abuf.dealloc, nil, false)
+
+proc toJS*(ctx: JSContext, u8a: JSUint8Array): JSValue =
+  var jsabuf = toJS(ctx, u8a.abuf)
+  let ctor = ctx.getOpaque().Uint8Array_ctor
+  let ret = JS_CallConstructor(ctx, ctor, 1, addr jsabuf)
+  JS_FreeValue(ctx, jsabuf)
+  return ret
+
 proc toJSP(ctx: JSContext, parent: ref object, child: var object): JSValue =
   let p = addr child
   # Save parent as the original ancestor for this tree.