diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bindings/quickjs.nim | 6 | ||||
-rw-r--r-- | src/buffer/buffer.nim | 119 | ||||
-rw-r--r-- | src/buffer/container.nim | 14 | ||||
-rw-r--r-- | src/display/client.nim | 10 | ||||
-rw-r--r-- | src/io/promise.nim | 107 | ||||
-rw-r--r-- | src/io/request.nim | 4 | ||||
-rw-r--r-- | src/ips/forkserver.nim | 2 | ||||
-rw-r--r-- | src/js/javascript.nim | 33 |
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 |