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.nim6
-rw-r--r--src/buffer/buffer.nim119
-rw-r--r--src/buffer/container.nim14
-rw-r--r--src/display/client.nim10
-rw-r--r--src/io/promise.nim107
-rw-r--r--src/io/request.nim4
-rw-r--r--src/ips/forkserver.nim2
-rw-r--r--src/js/javascript.nim33
8 files changed, 205 insertions, 90 deletions
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim
index b5aeaf37..6dbc1f2d 100644
--- a/src/bindings/quickjs.nim
+++ b/src/bindings/quickjs.nim
@@ -97,6 +97,7 @@ type
   JSClassFinalizer* = proc (rt: JSRuntime, val: JSValue) {.cdecl.}
   JSClassGCMark* = proc (rt: JSRuntime, val: JSValue, mark_func: JS_MarkFunc) {.cdecl.}
   JS_MarkFunc* = proc (rt: JSRuntime, gp: ptr JSGCObjectHeader) {.cdecl.}
+  JSJobFunc* = proc (ctx: JSContext, argc: cint, argv: ptr JSValue): JSValue
   JSGCObjectHeader* {.importc, header: qjsheader.} = object
 
   JSPropertyDescriptor* {.importc, header: qjsheader.} = object
@@ -312,6 +313,7 @@ proc JS_NewObject*(ctx: JSContext): JSValue
 proc JS_NewObjectClass*(ctx: JSContext, class_id: JSClassID): JSValue
 proc JS_NewObjectProto*(ctx: JSContext, proto: JSValue): JSValue
 proc JS_NewObjectProtoClass*(ctx: JSContext, proto: JSValue, class_id: JSClassID): JSValue
+proc JS_NewPromiseCapability*(ctx: JSContext, resolving_funcs: ptr JSValue): JSValue
 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
@@ -416,6 +418,10 @@ proc JS_ThrowReferenceError*(ctx: JSContext, fmt: cstring): JSValue {.varargs, d
 proc JS_ThrowRangeError*(ctx: JSContext, fmt: cstring): JSValue {.varargs, discardable.}
 proc JS_ThrowInternalError*(ctx: JSContext, fmt: cstring): JSValue {.varargs, discardable.}
 
+proc JS_EnqueueJob*(ctx: JSContext, job_func: JSJobFunc, argc: cint, argv: ptr JSValue): cint
+proc JS_IsJobPending*(rt: JSRuntime): JS_BOOL
+proc JS_ExecutePendingJob*(rt: JSRuntime, pctx: ptr JSContext): cint
+
 proc JS_GetRuntimeOpaque*(rt: JSRuntime): pointer
 proc JS_SetRuntimeOpaque*(rt: JSRuntime, p: pointer)
 
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim
index 17b6e4b8..b4a1c202 100644
--- a/src/buffer/buffer.nim
+++ b/src/buffer/buffer.nim
@@ -26,6 +26,7 @@ import html/tags
 import io/loader
 import io/request
 import io/posixstream
+import io/promise
 import io/teestream
 import ips/serialize
 import ips/serversocket
@@ -96,78 +97,36 @@ type
     savetask: bool
     hovertext: array[HoverType, string]
 
-  # async, but worse
-  EmptyPromise = ref object of RootObj
-    cb: (proc())
-    next: EmptyPromise
+  InterfaceOpaque = ref object
     stream: Stream
-
-  Promise*[T] = ref object of EmptyPromise
-    res: T
+    len: int
 
   BufferInterface* = ref object
-    stream*: Stream
+    map: PromiseMap
     packetid: int
-    promises: Table[int, EmptyPromise]
+    opaque: InterfaceOpaque
+    stream*: Stream
 
-proc newBufferInterface*(ostream: Stream): BufferInterface =
+proc getFromOpaque[T](opaque: pointer, res: var T) =
+  let opaque = cast[InterfaceOpaque](opaque)
+  if opaque.len != 0:
+    opaque.stream.sread(res)
+
+proc newBufferInterface*(stream: Stream): BufferInterface =
+  let opaque = InterfaceOpaque(stream: stream)
   result = BufferInterface(
-    stream: ostream,
-    packetid: 1 # ids below 1 are invalid
+    map: newPromiseMap(cast[pointer](opaque)),
+    packetid: 1, # ids below 1 are invalid
+    opaque: opaque,
+    stream: stream
   )
 
-proc fulfill*(iface: BufferInterface, packetid, len: int) =
-  var promise: EmptyPromise
-  if iface.promises.pop(packetid, promise):
-    if promise.stream != nil and promise.cb == nil and len != 0:
-      var abc = alloc(len)
-      var x = 0
-      while x < len:
-        x += promise.stream.readData(abc, len)
-      dealloc(abc)
-    while promise != nil:
-      if promise.cb != nil:
-        promise.cb()
-      promise = promise.next
+proc resolve*(iface: BufferInterface, packetid, len: int) =
+  iface.opaque.len = len
+  iface.map.resolve(packetid)
 
 proc hasPromises*(iface: BufferInterface): bool =
-  return iface.promises.len > 0
-
-proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} =
-  if promise == nil: return
-  promise.cb = cb
-  promise.next = EmptyPromise()
-  return promise.next
-
-proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} =
-  if promise == nil: return
-  return promise.then(proc() =
-    if promise.stream != nil:
-      promise.stream.sread(promise.res)
-    cb(promise.res))
-
-# Warning: we assume these aren't discarded.
-proc then*[T](promise: EmptyPromise, cb: (proc(): Promise[T])): Promise[T] =
-  if promise == nil: return
-  let next = Promise[T]()
-  promise.then(proc() =
-    let p2 = cb()
-    if p2 != nil:
-      p2.then(proc(x: T) =
-        next.res = x
-        next.cb()))
-  return next
-
-proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] =
-  if promise == nil: return
-  let next = Promise[U]()
-  promise.then(proc(x: T) =
-    let p2 = cb(x)
-    if p2 != nil:
-      p2.then(proc(y: U) =
-        next.res = y
-        next.cb()))
-  return next
+  return not iface.map.empty()
 
 # get enum identifier of proxy function
 func getFunId(fun: NimNode): string =
@@ -190,9 +149,14 @@ proc buildInterfaceProc(fun: NimNode, funid: string): tuple[fun, name: NimNode]
     `thisval`.stream.swrite(`thisval`.packetid))
   var params2: seq[NimNode]
   var retval2: NimNode
+  var addfun: NimNode
   if retval.kind == nnkEmpty:
+    addfun = quote do:
+      `thisval`.map.addEmptyPromise(`thisval`.packetid)
     retval2 = ident("EmptyPromise")
   else:
+    addfun = quote do:
+      addPromise[`retval`](`thisval`.map, `thisval`.packetid, getFromOpaque[`retval`])
     retval2 = newNimNode(nnkBracketExpr).add(
       ident("Promise"),
       retval)
@@ -210,16 +174,13 @@ proc buildInterfaceProc(fun: NimNode, funid: string): tuple[fun, name: NimNode]
   body.add(quote do:
     `thisval`.stream.flush())
   body.add(quote do:
-    `thisval`.promises[`thisval`.packetid] = `retval2`(stream: `thisval`.stream)
-    inc `thisval`.packetid)
+    let promise = `addfun`
+    inc `thisval`.packetid
+    return promise)
   var pragmas: NimNode
   if retval.kind == nnkEmpty:
-    body.add(quote do:
-      return `thisval`.promises[`thisval`.packetid - 1])
     pragmas = newNimNode(nnkPragma).add(ident("discardable"))
   else:
-    body.add(quote do:
-      return `retval2`(`thisval`.promises[`thisval`.packetid - 1]))
     pragmas = newEmptyNode()
   return (newProc(name, params2, body, pragmas = pragmas), nup)
 
@@ -659,10 +620,10 @@ proc load*(buffer: Buffer): LoadResult {.proxy, task.} =
   else:
     buffer.savetask = true
 
-proc fulfillTask[T](buffer: Buffer, cmd: BufferCommand, res: T) =
+proc resolveTask[T](buffer: Buffer, cmd: BufferCommand, res: T) =
   let packetid = buffer.tasks[cmd]
   if packetid == 0:
-    return # no task to fulfill (TODO this is kind of inefficient)
+    return # no task to resolve (TODO this is kind of inefficient)
   let len = slen(buffer.tasks[cmd]) + slen(res)
   buffer.pstream.swrite(len)
   buffer.pstream.swrite(packetid)
@@ -673,7 +634,7 @@ proc fulfillTask[T](buffer: Buffer, cmd: BufferCommand, res: T) =
 proc onload(buffer: Buffer) =
   var res: LoadResult = (false, buffer.lines.len, -1)
   if buffer.loaded:
-    buffer.fulfillTask(LOAD, res)
+    buffer.resolveTask(LOAD, res)
     return
   let op = buffer.sstream.getPosition()
   var s = newString(buffer.readbufsize)
@@ -691,11 +652,11 @@ proc onload(buffer: Buffer) =
       res.bytes = buffer.available
     else:
       buffer.do_reshape()
-    buffer.fulfillTask(LOAD, res)
+    buffer.resolveTask(LOAD, res)
   except EOFError:
     res.atend = true
     buffer.finishLoad()
-    buffer.fulfillTask(LOAD, res)
+    buffer.resolveTask(LOAD, res)
   except ErrorAgain, ErrorWouldBlock:
     if buffer.readbufsize > 1:
       buffer.readbufsize = buffer.readbufsize div 2
@@ -858,7 +819,7 @@ proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] =
     of FORM_ENCODING_TYPE_TEXT_PLAIN:
       body = serializePlainTextFormData(entrylist).some
       mimetype = $enctype
-    return newRequest(parsedaction, httpmethod, {"Content-Type": mimetype}, body, multipart).some
+    return newRequest(parsedaction, httpmethod, @{"Content-Type": mimetype}, body, multipart).some
 
   template getActionUrl() =
     return newRequest(parsedaction).some
@@ -1099,15 +1060,15 @@ macro bufferDispatcher(funs: static ProxyMap, buffer: Buffer, cmd: BufferCommand
       rval = ident("retval")
       stmts.add(quote do:
         let `rval` = `call`)
-    var fulfill = newStmtList()
+    var resolve = newStmtList()
     if rval == nil:
-      fulfill.add(quote do:
+      resolve.add(quote do:
         let len = slen(`packetid`)
         buffer.pstream.swrite(len)
         buffer.pstream.swrite(`packetid`)
         buffer.pstream.flush())
     else:
-      fulfill.add(quote do:
+      resolve.add(quote do:
         let len = slen(`packetid`) + slen(`rval`)
         buffer.pstream.swrite(len)
         buffer.pstream.swrite(`packetid`)
@@ -1120,9 +1081,9 @@ macro bufferDispatcher(funs: static ProxyMap, buffer: Buffer, cmd: BufferCommand
           buffer.savetask = false
           buffer.tasks[BufferCommand.`en`] = `packetid`
         else:
-          `fulfill`)
+          `resolve`)
     else:
-      stmts.add(fulfill)
+      stmts.add(resolve)
     ofbranch.add(stmts)
     switch.add(ofbranch)
   return switch
diff --git a/src/buffer/container.nim b/src/buffer/container.nim
index 9963f98e..a3406463 100644
--- a/src/buffer/container.nim
+++ b/src/buffer/container.nim
@@ -11,6 +11,7 @@ when defined(posix):
 import buffer/buffer
 import buffer/cell
 import config/config
+import io/promise
 import io/request
 import io/window
 import ips/forkserver
@@ -279,7 +280,7 @@ proc setNumLines(container: Container, lines: int, finish = false) =
     container.triggerEvent(STATUS)
 
 proc requestLines*(container: Container, w = container.lineWindow): auto {.discardable.} =
-  container.iface.getLines(w).then(proc(res: tuple[numLines: int, lines: seq[SimpleFlexibleLine]]) =
+  return container.iface.getLines(w).then(proc(res: tuple[numLines: int, lines: seq[SimpleFlexibleLine]]) =
     container.lines.setLen(w.len)
     container.lineshift = w.a
     for y in 0 ..< min(res.lines.len, w.len):
@@ -724,11 +725,10 @@ proc readSuccess*(container: Container, s: string) =
     if res.open.isSome:
       container.triggerEvent(ContainerEvent(t: OPEN, request: res.open.get)))
 
-proc reshape(container: Container, noreq = false) {.jsfunc.} =
-  container.iface.render().then(proc(lines: int) =
-    container.setNumLines(lines))
-  if not noreq:
-    container.needslines = true
+proc reshape(container: Container): EmptyPromise {.discardable, jsfunc.} =
+  return container.iface.render().then(proc(lines: int): auto =
+    container.setNumLines(lines)
+    return container.requestLines())
 
 proc dupeBuffer*(dispatcher: Dispatcher, container: Container, config: Config, location = none(URL), contenttype = none(string)): Container =
   let source = BufferSource(
@@ -806,7 +806,7 @@ proc handleCommand(container: Container) =
   var packetid, len: int
   container.iface.stream.sread(len)
   container.iface.stream.sread(packetid)
-  container.iface.fulfill(packetid, len - slen(packetid))
+  container.iface.resolve(packetid, len - slen(packetid))
 
 proc setStream*(container: Container, stream: Stream) =
   container.iface = newBufferInterface(stream)
diff --git a/src/display/client.nim b/src/display/client.nim
index 928fd32d..0a1f131c 100644
--- a/src/display/client.nim
+++ b/src/display/client.nim
@@ -13,7 +13,6 @@ when defined(posix):
 import std/exitprocs
 
 import bindings/quickjs
-import buffer/buffer
 import buffer/container
 import css/sheet
 import config/config
@@ -23,6 +22,7 @@ import html/dom
 import html/htmlparser
 import io/lineedit
 import io/loader
+import io/promise
 import io/request
 import io/window
 import ips/forkserver
@@ -101,10 +101,18 @@ proc interruptHandler(rt: JSRuntime, opaque: pointer): int {.cdecl.} =
     discard
   return 0
 
+proc runJSJobs(client: Client) =
+  while JS_IsJobPending(client.jsrt):
+    var ctx: JSContext
+    let r = JS_ExecutePendingJob(client.jsrt, addr ctx)
+    if r == -1:
+      ctx.writeException(client.console.err)
+
 proc evalJS(client: Client, src, filename: string): JSValue =
   if client.console.tty != nil:
     unblockStdin(client.console.tty.getFileHandle())
   result = client.jsctx.eval(src, filename, JS_EVAL_TYPE_GLOBAL)
+  client.runJSJobs()
   if client.console.tty != nil:
     restoreStdin(client.console.tty.getFileHandle())
 
diff --git a/src/io/promise.nim b/src/io/promise.nim
new file mode 100644
index 00000000..46beec6f
--- /dev/null
+++ b/src/io/promise.nim
@@ -0,0 +1,107 @@
+import tables
+
+type
+  PromiseState = enum
+    PROMISE_PENDING, PROMISE_FULFILLED, PROMISE_REJECTED
+
+  EmptyPromise* = ref object of RootObj
+    cb: (proc())
+    next: EmptyPromise
+    opaque: pointer
+    state: PromiseState
+
+  Promise*[T] = ref object of EmptyPromise
+    res: T
+    get: GetValueProc[T]
+
+  GetValueProc[T] = (proc(opaque: pointer, res: var T))
+
+  PromiseMap* = object
+    tab: Table[int, EmptyPromise]
+    opaque*: pointer
+
+proc newPromiseMap*(opaque: pointer): PromiseMap =
+  return PromiseMap(
+    opaque: opaque
+  )
+
+proc addPromise*[T](map: var PromiseMap, id: int, get: GetValueProc[T]): Promise[T] =
+  let promise = Promise[T](state: PROMISE_PENDING, get: get, opaque: map.opaque)
+  map.tab[id] = promise
+  return promise
+
+proc addEmptyPromise*(map: var PromiseMap, id: int): EmptyPromise =
+  let promise = EmptyPromise(state: PROMISE_PENDING, opaque: map.opaque)
+  map.tab[id] = promise
+  return promise
+
+proc resolve*(promise: EmptyPromise) =
+  var promise = promise
+  while true:
+    promise.state = PROMISE_FULFILLED
+    if promise.cb != nil:
+      promise.cb()
+    promise = promise.next
+    if promise == nil:
+      break
+
+proc resolve*[T](promise: Promise[T], res: T) =
+  if promise.cb != nil:
+    if promise.get != nil:
+      promise.get(promise.opaque, promise.res)
+      promise.get = nil
+    promise.res = res
+    promise.resolve()
+
+proc resolve*(map: var PromiseMap, promiseid: int) =
+  var promise: EmptyPromise
+  if map.tab.pop(promiseid, promise):
+    promise.resolve()
+
+func empty*(map: PromiseMap): bool =
+  map.tab.len == 0
+
+proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} =
+  if promise == nil: return
+  promise.cb = cb
+  promise.next = EmptyPromise()
+  return promise.next
+
+proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} =
+  if promise == nil: return
+  return promise.then(proc() =
+    if promise.get != nil:
+      promise.get(promise.opaque, promise.res)
+    cb(promise.res))
+
+proc then*[T](promise: EmptyPromise, cb: (proc(): Promise[T])): Promise[T] {.discardable.} =
+  if promise == nil: return
+  let next = Promise[T]()
+  promise.then(proc() =
+    let p2 = cb()
+    if p2 != nil:
+      p2.then(proc(x: T) =
+        next.res = x
+        next.resolve()))
+  return next
+
+proc then*[T](promise: Promise[T], cb: (proc(x: T): EmptyPromise)): EmptyPromise {.discardable.} =
+  if promise == nil: return
+  let next = EmptyPromise()
+  promise.then(proc(x: T) =
+    let p2 = cb(x)
+    if p2 != nil:
+      p2.then(proc() =
+        next.resolve()))
+  return next
+
+proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] {.discardable.} =
+  if promise == nil: return
+  let next = Promise[U]()
+  promise.then(proc(x: T) =
+    let p2 = cb(x)
+    if p2 != nil:
+      p2.then(proc(y: U) =
+        next.res = y
+        next.resolve()))
+  return next
diff --git a/src/io/request.nim b/src/io/request.nim
index d15696fe..79ba7c0d 100644
--- a/src/io/request.nim
+++ b/src/io/request.nim
@@ -163,8 +163,8 @@ func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaderList(),
     destination: destination
   )
 
-func newRequest*(url: URL, httpmethod = HTTP_GET, headers: openarray[(string, string)] = [],
-                 body = none(string), multipart = none(MimeData), mode = RequestMode.NO_CORS): Request =
+func newRequest*(url: URL, httpmethod = HTTP_GET, headers: seq[(string, string)] = @[],
+                 body = none(string), multipart = none(MimeData), mode = RequestMode.NO_CORS): Request {.jsctor.} =
   let hl = newHeaderList()
   for pair in headers:
     let (k, v) = pair
diff --git a/src/ips/forkserver.nim b/src/ips/forkserver.nim
index e6622213..906d60a4 100644
--- a/src/ips/forkserver.nim
+++ b/src/ips/forkserver.nim
@@ -31,7 +31,7 @@ type
     ostream: Stream
     children: seq[(Pid, Pid)]
 
-proc newFileLoader*(forkserver: ForkServer, defaultHeaders: HeaderList = nil, filter = newURLFilter(), cookiejar: CookieJar = nil): FileLoader =
+proc newFileLoader*(forkserver: ForkServer, defaultHeaders: HeaderList = nil, filter = newURLFilter(default = true), cookiejar: CookieJar = nil): FileLoader =
   forkserver.ostream.swrite(FORK_LOADER)
   var defaultHeaders = defaultHeaders
   if defaultHeaders == nil:
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index d7d0486c..56746a53 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -30,6 +30,8 @@ import strutils
 import tables
 import unicode
 
+import io/promise
+
 import bindings/quickjs
 
 export options
@@ -719,6 +721,33 @@ func toJSObject[T](ctx: JSContext, obj: T): JSValue =
   setOpaque(ctx, jsObj, obj)
   return jsObj
 
+func toJSPromise(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
+
+func toJSPromise[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](ctx: JSContext, obj: T): JSValue =
   when T is string:
     return ctx.toJSString(obj)
@@ -751,6 +780,10 @@ proc toJS*[T](ctx: JSContext, obj: T): JSValue =
     return toJS(ctx, int(obj))
   elif T is JSValue:
     return obj
+  elif T is Promise:
+    return toJSPromise(ctx, obj)
+  elif T is EmptyPromise:
+    return toJSPromise(ctx, obj)
   else:
     if obj == nil:
       return JS_NULL