about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bindings/quickjs.nim11
-rw-r--r--src/html/env.nim2
-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
-rw-r--r--src/local/client.nim2
-rw-r--r--src/version.nim2
10 files changed, 194 insertions, 1 deletions
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim
index 17bac15a..a401bf1b 100644
--- a/src/bindings/quickjs.nim
+++ b/src/bindings/quickjs.nim
@@ -87,6 +87,8 @@ type
     opaque: pointer): JSModuleDef {.cdecl.}
   JSJobFunc* = proc (ctx: JSContext, argc: cint, argv: ptr JSValue): JSValue {.cdecl.}
   JSGCObjectHeader* {.importc, header: qjsheader.} = object
+  JSFreeArrayBufferDataFunc* = proc (rt: JSRuntime,
+    opaque, p: pointer) {.cdecl.}
 
   JSPropertyDescriptor* {.importc, header: qjsheader.} = object
     flags*: cint
@@ -342,10 +344,19 @@ proc JS_SetOpaque*(obj: JSValue, opaque: pointer)
 proc JS_GetOpaque*(obj: JSValue, class_id: JSClassID): pointer
 proc JS_GetOpaque2*(ctx: JSContext, obj: JSValue, class_id: JSClassID): pointer
 proc JS_GetClassID*(obj: JSValue): JSClassID
+
 proc JS_ParseJSON*(ctx: JSContext, buf: cstring, buf_len: csize_t, filename: cstring): JSValue
 proc JS_ParseJSON2*(ctx: JSContext, buf: cstring, buf_len: csize_t,
   filename: cstring, flags: cint): JSValue
 
+proc JS_NewArrayBuffer*(ctx: JSContext, buf: ptr UncheckedArray[uint8],
+  len: csize_t, free_func: JSFreeArrayBufferDataFunc, opaque: pointer,
+  is_shared: JS_BOOL): JSValue
+proc JS_GetArrayBuffer*(ctx: JSContext, psize: ptr csize_t, obj: JSValue): ptr uint8
+proc JS_GetUint8Array*(ctx: JSContext, psize: ptr csize_t, obj: JSValue): ptr uint8
+proc JS_GetTypedArrayBuffer*(ctx: JSContext, obj: JSValue, pbyte_offset,
+  pbyte_length, pbytes_per_element: ptr csize_t): JSValue
+
 proc JS_NewClassID*(pclass_id: ptr JSClassID): JSClassID
 proc JS_NewClass*(rt: JSRuntime, class_id: JSClassID, class_def: ptr JSClassDef): cint
 proc JS_IsRegisteredClass*(rt: JSRuntime, class_id: JSClassID): cint
diff --git a/src/html/env.nim b/src/html/env.nim
index 6046ee2b..d300400b 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -9,6 +9,7 @@ import io/promise
 import js/base64
 import js/console
 import js/domexception
+import js/encoding
 import js/error
 import js/intl
 import js/javascript
@@ -144,6 +145,7 @@ proc addScripting*(window: Window, selector: Selector[int]) =
   ctx.addHeadersModule()
   ctx.addRequestModule()
   ctx.addResponseModule()
+  ctx.addEncodingModule()
 
 proc runJSJobs*(window: Window) =
   window.jsrt.runJSJobs(window.console.err)
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.
diff --git a/src/local/client.nim b/src/local/client.nim
index 91b111d9..9cff0f2f 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -28,6 +28,7 @@ import io/socketstream
 import js/base64
 import js/console
 import js/domexception
+import js/encoding
 import js/error
 import js/fromjs
 import js/intl
@@ -640,6 +641,7 @@ proc addJSModules(client: Client, ctx: JSContext) =
   ctx.addConfigModule()
   ctx.addPagerModule()
   ctx.addContainerModule()
+  ctx.addEncodingModule()
 
 func getClient(client: Client): Client {.jsfget: "client".} =
   return client
diff --git a/src/version.nim b/src/version.nim
index e4205bc2..e2a35886 100644
--- a/src/version.nim
+++ b/src/version.nim
@@ -27,5 +27,5 @@ tryImport chakasu/version, "chakasu"
 tryImport chame/version, "chame"
 
 static:
-  checkVersion("chakasu", 0, 2, 1)
+  checkVersion("chakasu", 0, 3, 0)
   checkVersion("chame", 0, 11, 2)