diff options
-rw-r--r-- | src/bindings/quickjs.nim | 11 | ||||
-rw-r--r-- | src/buffer/buffer.nim | 37 | ||||
-rw-r--r-- | src/buffer/container.nim | 4 | ||||
-rw-r--r-- | src/config/config.nim | 4 | ||||
-rw-r--r-- | src/display/client.nim | 3 | ||||
-rw-r--r-- | src/html/env.nim | 2 | ||||
-rw-r--r-- | src/io/http.nim | 3 | ||||
-rw-r--r-- | src/io/lineedit.nim | 9 | ||||
-rw-r--r-- | src/io/promise.nim | 16 | ||||
-rw-r--r-- | src/io/request.nim | 16 | ||||
-rw-r--r-- | src/ips/serialize.nim | 41 | ||||
-rw-r--r-- | src/js/exception.nim | 96 | ||||
-rw-r--r-- | src/js/javascript.nim | 342 | ||||
-rw-r--r-- | src/types/url.nim | 2 | ||||
-rw-r--r-- | src/utils/opt.nim | 68 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 4 |
16 files changed, 480 insertions, 178 deletions
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim index 306a3b3d..9a76a484 100644 --- a/src/bindings/quickjs.nim +++ b/src/bindings/quickjs.nim @@ -212,12 +212,20 @@ type is_enumerable*: JS_BOOL atom*: JSAtom + JSClassEnum* {.size: sizeof(uint32).} = enum + JS_CLASS_OBJECT = 1 + JS_CLASS_ARRAY + JS_CLASS_ERROR + converter toBool*(js: JS_BOOl): bool {.inline.} = cast[cint](js) != 0 converter toJSBool*(b: bool): JS_BOOL {.inline.} = cast[JS_BOOL](cint(b)) +converter toJSClassID*(e: JSClassEnum): JSClassID {.inline.} = + JSClassID(e) + const JS_NULL* = JS_MKVAL(JS_TAG_NULL, 0) JS_UNDEFINED* = JS_MKVAL(JS_TAG_UNDEFINED, 0) @@ -338,6 +346,7 @@ proc JS_SetClassProto*(ctx: JSContext, class_id: JSClassID, obj: JSValue) proc JS_GetClassProto*(ctx: JSContext, class_id: JSClassID): JSValue proc JS_SetConstructor*(ctx: JSContext, func_obj: JSValue, proto: JSValue) proc JS_SetPrototype*(ctx: JSContext, obj: JSValue, proto_val: JSValue): cint +proc JS_GetPrototype*(ctx: JSContext, val: JSValue): JSValue proc JS_NewBool*(ctx: JSContext, val: JS_BOOL): JSValue proc JS_NewInt32*(ctx: JSContext, val: int32): JSValue @@ -374,6 +383,8 @@ proc JS_GetOwnPropertyNames*(ctx: JSContext, ptab: ptr ptr JSPropertyEnum, plen: proc JS_GetOwnProperty*(ctx: JSContext, desc: ptr JSPropertyDescriptor, obj: JSValue, prop: JSAtom): cint proc JS_Call*(ctx: JSContext, func_obj, this_obj: JSValue, argc: cint, argv: ptr JSValue): JSValue +proc JS_CallConstructor*(ctx: JSContext, func_obj: JSValue, argc: cint, + argv: ptr JSValue): JSValue proc JS_DefineProperty*(ctx: JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue, getter: JSValue, setter: JSValue, flags: cint): cint proc JS_DefinePropertyValue*(ctx: JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue, flags: cint): cint diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index 6c2a8eda..e0b24cae 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -20,6 +20,7 @@ import css/sheet import css/stylednode import css/values import data/charset +import encoding/decoderstream import html/dom import html/env import html/htmlparser @@ -44,6 +45,7 @@ import types/cookie import types/formdata import types/referer import types/url +import utils/opt import utils/twtstr import xhr/formdata as formdata_impl @@ -555,17 +557,26 @@ proc loadResource(buffer: Buffer, elem: HTMLLinkElement): EmptyPromise = let url = url.get let media = elem.media if media != "": - let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) + let cvals = parseListOfComponentValues(newStringStream(media)) + let media = parseMediaQueryList(cvals) if not media.applies(document.window): return - return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = + return buffer.loader.fetch(newRequest(url)).then(proc(res: Response): + Opt[Promise[string]] = if res.res == 0: #TODO remove res #TODO we should use ReadableStreams for this (which would allow us to # parse CSS asynchronously) - # yet another hack: needed because closing a stream before - # unregistering breaks if res.contenttype == "text/css": - elem.sheet = parseStylesheet(res.body) - res.unregisterFun()) + return ok(res.text()) + res.unregisterFun() + ).then(proc(s: Opt[string]) = + if s.isOk: + #TODO this is extremely inefficient, and text() should return + # utf8 anyways + let ss = newStringStream(s.get) + #TODO non-utf-8 css + let source = newDecoderStream(ss, cs = CHARSET_UTF_8).readAll() + let ss2 = newStringStream(source) + elem.sheet = parseStylesheet(ss2)) proc loadResource(buffer: Buffer, elem: HTMLImageElement): EmptyPromise = let document = buffer.document @@ -843,22 +854,22 @@ func submitForm(form: HTMLFormElement, submitter: Element): Option[Request] = template submitAsEntityBody() = var mimetype: string - var body = none(string) - var multipart = none(FormData) + var body: Opt[string] + var multipart: Opt[FormData] case enctype of FORM_ENCODING_TYPE_URLENCODED: let kvlist = entrylist.toNameValuePairs() - body = some(serializeApplicationXWWWFormUrlEncoded(kvlist)) + body.ok(serializeApplicationXWWWFormUrlEncoded(kvlist)) mimeType = $enctype of FORM_ENCODING_TYPE_MULTIPART: - multipart = some(serializeMultipartFormData(entrylist)) + multipart.ok(serializeMultipartFormData(entrylist)) mimetype = $enctype of FORM_ENCODING_TYPE_TEXT_PLAIN: let kvlist = entrylist.toNameValuePairs() - body = some(serializePlainTextFormData(kvlist)) + body.ok(serializePlainTextFormData(kvlist)) mimetype = $enctype - let req = newRequest(parsedaction, httpmethod, - @{"Content-Type": mimetype}, body) + let req = newRequest(parsedaction, httpmethod, @{"Content-Type": mimetype}, + body) return some(req) #TODO multipart template getActionUrl() = diff --git a/src/buffer/container.nim b/src/buffer/container.nim index e301e05c..57685784 100644 --- a/src/buffer/container.nim +++ b/src/buffer/container.nim @@ -638,13 +638,13 @@ proc onMatch(container: Container, res: BufferMatch) = container.needslines = true container.hlon = false -proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} = +proc cursorNextMatch*(container: Container, regex: Regex, wrap: bool) = container.iface .findNextMatch(regex, container.cursorx, container.cursory, wrap) .then(proc(res: BufferMatch) = container.onMatch(res)) -proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) {.jsfunc.} = +proc cursorPrevMatch*(container: Container, regex: Regex, wrap: bool) = container.iface .findPrevMatch(regex, container.cursorx, container.cursory, wrap) .then(proc(res: BufferMatch) = diff --git a/src/config/config.nim b/src/config/config.nim index 45bca17d..48c0dde9 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -44,7 +44,7 @@ type SiteConfig* = object url*: Option[Regex] host*: Option[Regex] - rewrite_url*: (proc(s: URL): Option[URL]) + rewrite_url*: (proc(s: URL): Opt[URL]) cookie*: Option[bool] third_party_cookie*: seq[Regex] share_cookie_jar*: Option[string] @@ -55,7 +55,7 @@ type OmniRule* = object match*: Regex - substitute_url*: (proc(s: string): Option[string]) + substitute_url*: (proc(s: string): Opt[string]) StartConfig = object visual_home*: string diff --git a/src/display/client.nim b/src/display/client.nim index 88185fb7..8196df26 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -31,6 +31,7 @@ import ips/forkserver import ips/serialize import ips/serversocket import ips/socketstream +import js/exception import js/intl import js/javascript import js/module @@ -39,6 +40,7 @@ import types/blob import types/cookie import types/dispatcher import types/url +import utils/opt import xhr/formdata as formdata_impl type @@ -544,6 +546,7 @@ proc newClient*(config: Config, dispatcher: Dispatcher): Client = ctx.registerType(Console) + ctx.addDOMExceptionModule() ctx.addCookieModule() ctx.addURLModule() ctx.addDOMModule() diff --git a/src/html/env.nim b/src/html/env.nim index 5883dabf..a70508be 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -8,6 +8,7 @@ import io/promise import io/request import io/response import io/window +import js/exception import js/intl import js/javascript import js/timeout @@ -112,6 +113,7 @@ proc addScripting*(window: Window, selector: Selector[int]) = ctx.setOpaque(global, window) ctx.defineProperty(global, "window", global) JS_FreeValue(ctx, global) + ctx.addDOMExceptionModule() ctx.addconsoleModule() ctx.addNavigatorModule() ctx.addDOMModule() diff --git a/src/io/http.nim b/src/io/http.nim index f9023f5f..cd6234fd 100644 --- a/src/io/http.nim +++ b/src/io/http.nim @@ -8,6 +8,7 @@ import ips/serialize import types/blob import types/formdata import types/url +import utils/opt import utils/twtstr type @@ -84,7 +85,7 @@ proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer) return nmemb proc applyPostBody(curl: CURL, request: Request, handleData: HandleData) = - if request.multipart.issome: + if request.multipart.isOk: handleData.mime = curl_mime_init(curl) if handleData.mime == nil: # fail (TODO: raise?) diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim index ee25d9a3..39ef480c 100644 --- a/src/io/lineedit.nim +++ b/src/io/lineedit.nim @@ -8,6 +8,7 @@ import buffer/cell import display/term import js/javascript import types/color +import utils/opt import utils/twtstr type @@ -231,7 +232,7 @@ proc forward(edit: LineEdit) {.jsfunc.} = else: edit.fullRedraw() -proc prevWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc prevWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = let oc = edit.cursor while edit.cursor > 0: dec edit.cursor @@ -243,7 +244,7 @@ proc prevWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = else: edit.fullRedraw() -proc nextWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc nextWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = let oc = edit.cursor let ow = edit.news.width(edit.shift, edit.cursor) while edit.cursor < edit.news.len: @@ -258,7 +259,7 @@ proc nextWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = else: edit.fullRedraw() -proc clearWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc clearWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = var i = edit.cursor if i > 0: # point to the previous character @@ -273,7 +274,7 @@ proc clearWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = edit.cursor = i edit.fullRedraw() -proc killWord(edit: LineEdit, check = none(BoundaryFunction)) {.jsfunc.} = +proc killWord(edit: LineEdit, check = opt(BoundaryFunction)) {.jsfunc.} = var i = edit.cursor if i < edit.news.len and edit.news[i].breaksWord(check): inc i diff --git a/src/io/promise.nim b/src/io/promise.nim index d825c4fa..27288be8 100644 --- a/src/io/promise.nim +++ b/src/io/promise.nim @@ -1,5 +1,7 @@ import tables +import utils/opt + type PromiseState* = enum PROMISE_PENDING, PROMISE_FULFILLED, PROMISE_REJECTED @@ -127,6 +129,20 @@ proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] next.resolve()) return next +proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Opt[Promise[U]])): + Promise[Opt[U]] {.discardable.} = + doAssert promise != nil + let next = Promise[Opt[U]]() + promise.then(proc(x: T) = + let p2 = cb(x) + if p2.isOk: + p2.get.then(proc(y: U) = + next.res = opt(y) + next.resolve()) + else: + next.resolve()) + return next + proc all*(promises: seq[EmptyPromise]): EmptyPromise = let res = EmptyPromise() var i = 0 diff --git a/src/io/request.nim b/src/io/request.nim index 11e16718..8bc2bfc8 100644 --- a/src/io/request.nim +++ b/src/io/request.nim @@ -66,8 +66,8 @@ type httpmethod*: HttpMethod url*: Url headers* {.jsget.}: Headers - body*: Option[string] - multipart*: Option[FormData] + body*: Opt[string] + multipart*: Opt[FormData] referer*: URL mode* {.jsget.}: RequestMode destination* {.jsget.}: RequestDestination @@ -185,7 +185,7 @@ func newHeaders*(table: Table[string, string]): Headers = result.table[k] = @[v] func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(), - body = none(string), multipart = none(FormData), mode = RequestMode.NO_CORS, + body = opt(string), multipart = opt(FormData), mode = RequestMode.NO_CORS, credentialsMode = CredentialsMode.SAME_ORIGIN, destination = RequestDestination.NO_DESTINATION, proxy: URL = nil): Request = return Request( @@ -201,8 +201,8 @@ func newRequest*(url: URL, httpmethod = HTTP_GET, headers = newHeaders(), ) func newRequest*(url: URL, httpmethod = HTTP_GET, - headers: seq[(string, string)] = @[], body = none(string), - multipart = none(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil): + headers: seq[(string, string)] = @[], body = opt(string), + multipart = opt(FormData), mode = RequestMode.NO_CORS, proxy: URL = nil): Request = let hl = newHeaders() for pair in headers: @@ -234,12 +234,12 @@ func newRequest*(ctx: JSContext, resource: string, let url = x.get let fallbackMode = some(RequestMode.CORS) #TODO none if resource is request var httpMethod = HTTP_GET - var body = none(string) + var body = opt(string) var credentials = CredentialsMode.SAME_ORIGIN var mode = fallbackMode.get(RequestMode.NO_CORS) let hl = newHeaders() - var proxyUrl = none(URL) - var multipart = none(FormData) + var proxyUrl: Opt[URL] + var multipart: Opt[FormData] #TODO fallback mode, origin, window, request mode, ... if init.isSome: let init = init.get diff --git a/src/ips/serialize.nim b/src/ips/serialize.nim index be5bbe28..f7fb934e 100644 --- a/src/ips/serialize.nim +++ b/src/ips/serialize.nim @@ -11,6 +11,7 @@ import types/blob import types/buffersource import types/formdata import types/url +import utils/opt proc swrite*(stream: Stream, n: SomeNumber) proc sread*(stream: Stream, n: var SomeNumber) @@ -68,6 +69,10 @@ proc swrite*[T](stream: Stream, o: Option[T]) proc sread*[T](stream: Stream, o: var Option[T]) func slen*[T](o: Option[T]): int +proc swrite*[T, E](stream: Stream, o: Result[T, E]) +proc sread*[T, E](stream: Stream, o: var Result[T, E]) +func slen*[T, E](o: Result[T, E]): int + proc swrite*(stream: Stream, regex: Regex) proc sread*(stream: Stream, regex: var Regex) func slen*(regex: Regex): int @@ -339,6 +344,42 @@ func slen*[T](o: Option[T]): int = if o.isSome: result += slen(o.get) +proc swrite*[T, E](stream: Stream, o: Result[T, E]) = + stream.swrite(o.isOk) + if o.isOk: + when not (T is void): + stream.swrite(o.get) + else: + when not (E is void): + stream.swrite(o.error) + +proc sread*[T, E](stream: Stream, o: var Result[T, E]) = + var x: bool + stream.sread(x) + if x: + when not (T is void): + var m: T + stream.sread(m) + o.ok(m) + else: + o.ok() + else: + when not (E is void): + var e: E + stream.sread(e) + o.err(e) + else: + o.err() + +func slen*[T, E](o: Result[T, E]): int = + result = slen(o.isSome) + if o.isSome: + when not (T is void): + result += slen(o.get) + else: + when not (E is void): + result += slen(o.error) + proc swrite*(stream: Stream, regex: Regex) = stream.swrite(regex.plen) stream.writeData(regex.bytecode, regex.plen) diff --git a/src/js/exception.nim b/src/js/exception.nim new file mode 100644 index 00000000..1550d431 --- /dev/null +++ b/src/js/exception.nim @@ -0,0 +1,96 @@ +import tables + +import bindings/quickjs +import js/javascript + +const NamesTable = { + "IndexSizeError": 1u16, + "HierarchyRequestError": 3u16, + "WrongDocumentError": 4u16, + "InvalidCharacterError": 5u16, + "NoModificationAllowedError": 7u16, + "NotFoundError": 8u16, + "NotSupportedError": 9u16, + "InUseAttributeError": 10u16, + "InvalidStateError": 11u16, + "SyntaxError": 12u16, + "InvalidModificationError": 13u16, + "NamespaceError": 14u16, + "InvalidAccessError": 15u16, + "TypeMismatchError": 17u16, + "SecurityError": 18u16, + "NetworkError": 19u16, + "AbortError": 20u16, + "URLMismatchError": 21u16, + "QuotaExceededError": 22u16, + "TimeoutError": 23u16, + "InvalidNodeTypeError": 24u16, + "DataCloneError": 25u16 +}.toTable() + +type DOMException* = ref object of JSError + name* {.jsget.}: string + +proc newDOMException*(message = "", name = "Error"): DOMException {.jsctor.} = + return DOMException( + e: JS_DOM_EXCEPTION, + name: name, + message: message + ) + +func message0(this: DOMException): string {.jsfget: "message".} = + return this.message + +func code(this: DOMException): uint16 {.jsfget.} = + return NamesTable.getOrDefault(this.name, 0u16) + +proc newEvalError*(message: string): JSError = + return JSError( + e: JS_EVAL_ERROR0, + message: message + ) + +proc newRangeError*(message: string): JSError = + return JSError( + e: JS_RANGE_ERROR0, + message: message + ) + +proc newReferenceError*(message: string): JSError = + return JSError( + e: JS_REFERENCE_ERROR0, + message: message + ) + +proc newSyntaxError*(message: string): JSError = + return JSError( + e: JS_SYNTAX_ERROR0, + message: message + ) + +proc newTypeError*(message: string): JSError = + return JSError( + e: JS_TYPE_ERROR0, + message: message + ) + +proc newURIError*(message: string): JSError = + return JSError( + e: JS_URI_ERROR0, + message: message + ) + +proc newInternalError*(message: string): JSError = + return JSError( + e: JS_INTERNAL_ERROR0, + message: message + ) + +proc newAggregateError*(message: string): JSError = + return JSError( + e: JS_AGGREGATE_ERROR0, + message: message + ) + +proc addDOMExceptionModule*(ctx: JSContext) = + ctx.registerType(DOMException, JS_CLASS_ERROR, errid = opt(JS_DOM_EXCEPTION)) diff --git a/src/js/javascript.nim b/src/js/javascript.nim index deda1ca5..fb020e5c 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -31,9 +31,11 @@ import tables import unicode import io/promise +import utils/opt import bindings/quickjs +export opt export options export @@ -60,6 +62,19 @@ when sizeof(int) < sizeof(int64): export quickjs.`==` type + JSErrorEnum* = enum + # QuickJS internal errors + JS_EVAL_ERROR0 = "EvalError" + JS_RANGE_ERROR0 = "RangeError" + JS_REFERENCE_ERROR0 = "ReferenceError" + JS_SYNTAX_ERROR0 = "SyntaxError" + JS_TYPE_ERROR0 = "TypeError" + JS_URI_ERROR0 = "URIError" + JS_INTERNAL_ERROR0 = "InternalError" + JS_AGGREGATE_ERROR0 = "AggregateError" + # Chawan errors + JS_DOM_EXCEPTION = "DOMException" + JSContextOpaque* = ref object creg: Table[string, JSClassID] typemap: Table[pointer, JSClassID] @@ -72,6 +87,7 @@ type next: JSAtom value: JSAtom Array_prototype_values: JSValue + err_ctors: array[JSErrorEnum, JSValue] JSRuntimeOpaque* = ref object plist: Table[pointer, pointer] # Nim, JS @@ -85,6 +101,30 @@ type JSFunctionList* = openArray[JSCFunctionListEntry] + JSError* = ref object of RootObj + e*: JSErrorEnum + message*: string + + LegacyJSError* = object of CatchableError + + #TODO remove these + JS_SyntaxError* = object of LegacyJSError + JS_TypeError* = object of LegacyJSError + JS_ReferenceError* = object of LegacyJSError + JS_RangeError* = object of LegacyJSError + JS_InternalError* = object of LegacyJSError + +const QuickJSErrors = [ + JS_EVAL_ERROR0, + JS_RANGE_ERROR0, + JS_REFERENCE_ERROR0, + JS_SYNTAX_ERROR0, + JS_TYPE_ERROR0, + JS_URI_ERROR0, + JS_INTERNAL_ERROR0, + JS_AGGREGATE_ERROR0 +] + func getOpaque*(ctx: JSContext): JSContextOpaque = return cast[JSContextOpaque](JS_GetContextOpaque(ctx)) @@ -134,11 +174,15 @@ proc newJSContext*(rt: JSRuntime): JSContext = let s = "next" opaque.next = JS_NewAtomLen(ctx, cstring(s), csize_t(s.len)) block: - # 2 - JS_CLASS_ARRAY - let arrproto = JS_GetClassProto(ctx, 2) - opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto, "values") + let arrproto = JS_GetClassProto(ctx, JS_CLASS_ARRAY) + opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto, + "values") JS_FreeValue(ctx, arrproto) JS_FreeValue(ctx, sym) + for e in JSErrorEnum: + let s = $e + let err = JS_GetPropertyStr(ctx, global, cstring(s)) + opaque.err_ctors[e] = err JS_FreeValue(ctx, global) JS_SetContextOpaque(ctx, cast[pointer](opaque)) @@ -172,6 +216,8 @@ proc free*(ctx: var JSContext) = JS_FreeAtom(ctx, opaque.done) JS_FreeAtom(ctx, opaque.next) JS_FreeValue(ctx, opaque.Array_prototype_values) + for v in opaque.err_ctors: + JS_FreeValue(ctx, v) GC_unref(opaque) JS_FreeContext(ctx) ctx = nil @@ -218,7 +264,7 @@ func getOpaque*(ctx: JSContext, val: JSValue, class: string): pointer = # This needs further investigation. if ctx.isGlobal(class): let global = JS_GetGlobalObject(ctx) - let opaque = JS_GetOpaque(global, 1) # JS_CLASS_OBJECT + let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT) JS_FreeValue(ctx, global) return opaque return getOpaque0(val) @@ -229,7 +275,7 @@ func getOpaque*[T](ctx: JSContext, val: JSValue): pointer = proc setInterruptHandler*(rt: JSRuntime, cb: JSInterruptHandler, opaque: pointer = nil) = JS_SetInterruptHandler(rt, cb, opaque) -func toString*(ctx: JSContext, val: JSValue): Option[string] = +func toString*(ctx: JSContext, val: JSValue): Opt[string] = var plen: csize_t let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring if outp != nil: @@ -237,7 +283,7 @@ func toString*(ctx: JSContext, val: JSValue): Option[string] = if plen != 0: prepareMutation(ret) copyMem(addr ret[0], outp, plen) - result = some(ret) + result = ok(ret) JS_FreeCString(ctx, outp) proc writeException*(ctx: JSContext, s: Stream) = @@ -297,10 +343,9 @@ proc definePropertyCWE*[T](ctx: JSContext, this: JSValue, name: string, definePropertyCWE(ctx, this, name, toJS(ctx, prop)) func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string, - ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer, - parent: JSClassID, asglobal: bool, nointerface: bool, - finalizer: proc(val: JSValue), - namespace: JSValue): JSClassID {.discardable.} = + ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer, parent: JSClassID, + asglobal: bool, nointerface: bool, finalizer: proc(val: JSValue), + namespace: JSValue, errid: Opt[JSErrorEnum]): JSClassID {.discardable.} = let rt = JS_GetRuntime(ctx) discard JS_NewClassID(addr result) var ctxOpaque = ctx.getOpaque() @@ -339,6 +384,8 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string, JS_FreeValue(ctx, global) let jctor = ctx.newJSCFunction($cdef.class_name, ctor, 0, JS_CFUNC_constructor) JS_SetConstructor(ctx, jctor, proto) + if errid.isSome: + ctx.getOpaque().err_ctors[errid.get] = JS_DupValue(ctx, jctor) ctxOpaque.ctors[result] = JS_DupValue(ctx, jctor) if not nointerface: if JS_IsNull(namespace): @@ -362,41 +409,41 @@ func getMinArgs(params: seq[FuncParam]): int = return i return params.len -func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): Option[T] = +func fromJSInt[T: SomeInteger](ctx: JSContext, val: JSValue): Opt[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 none(T) - return some(int(ret)) + return err() + return ok(int(ret)) elif T is uint: var ret: uint32 if JS_ToUint32(ctx, addr ret, val) < 0: - return none(T) - return some(uint(ret)) + return err() + return ok(uint(ret)) elif T is int32: var ret: int32 if JS_ToInt32(ctx, addr ret, val) < 0: - return none(T) - return some(ret) + return err() + return ok(ret) elif T is int64: var ret: int64 if JS_ToInt64(ctx, addr ret, val) < 0: - return none(T) - return some(ret) + return err() + return ok(ret) elif T is uint32: var ret: uint32 if JS_ToUint32(ctx, addr ret, val) < 0: - return none(T) - return some(ret) + return err() + return ok(ret) elif T is uint64: var ret: uint32 if JS_ToUint32(ctx, addr ret, val) < 0: - return none(T) - return some(cast[uint64](ret)) + return err() + return ok(cast[uint64](ret)) -proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] +proc fromJS*[T](ctx: JSContext, val: JSValue): Opt[T] macro len(t: type tuple): int = let i = t.getType()[1].len - 1 # - tuple @@ -406,182 +453,183 @@ macro fromJSTupleBody(a: tuple) = let len = a.getType().len - 1 let done = ident("done") result = newStmtList(quote do: - var `done`: Option[bool]) + var `done`: Opt[bool]) for i in 0..<len: result.add(quote do: let next = JS_Call(ctx, next_method, it, 0, nil) if JS_IsException(next): - return none(T) + return err() defer: JS_FreeValue(ctx, next) let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done) if JS_IsException(doneVal): - return none(T) + return err() defer: JS_FreeValue(ctx, doneVal) `done` = fromJS[bool](ctx, doneVal) if `done`.isnone: # exception - return none(T) + return err() if `done`.get: JS_ThrowTypeError(ctx, "Too few arguments in sequence (got %d, expected %d)", `i`, `len`) - return none(T) + return err() let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value) if JS_IsException(valueVal): - return none(T) + return err() defer: JS_FreeValue(ctx, valueVal) let genericRes = fromJS[typeof(result.get[`i`])](ctx, valueVal) - if genericRes.isnone: # exception - return none(T) + if genericRes.isErr: # exception + return err() `a`[`i`] = genericRes.get ) if i == len - 1: result.add(quote do: let next = JS_Call(ctx, next_method, it, 0, nil) if JS_IsException(next): - return none(T) + return err() defer: JS_FreeValue(ctx, next) let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done) `done` = fromJS[bool](ctx, doneVal) if `done`.isnone: # exception - return none(T) + return err() var i = `i` # we're emulating a sequence, so we must query all remaining parameters too: while not `done`.get: inc i let next = JS_Call(ctx, next_method, it, 0, nil) if JS_IsException(next): - return none(T) + return err() defer: JS_FreeValue(ctx, next) let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done) if JS_IsException(doneVal): - return none(T) + return err() defer: JS_FreeValue(ctx, doneVal) `done` = fromJS[bool](ctx, doneVal) if `done`.isnone: # exception - return none(T) + return err() if `done`.get: JS_ThrowTypeError(ctx, "Too many arguments in sequence (got %d, expected %d)", i, `len`) - return none(T) + return err() JS_FreeValue(ctx, JS_GetProperty(ctx, next, ctx.getOpaque().value)) ) -proc fromJSTuple[T: tuple](ctx: JSContext, val: JSValue): Option[T] = +proc fromJSTuple[T: tuple](ctx: JSContext, val: JSValue): Opt[T] = let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator) if JS_IsException(itprop): - return none(T) + return err() defer: JS_FreeValue(ctx, itprop) let it = JS_Call(ctx, itprop, val, 0, nil) if JS_IsException(it): - return none(T) + return err() defer: JS_FreeValue(ctx, it) let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next) if JS_IsException(next_method): - return none(T) + return err() defer: JS_FreeValue(ctx, next_method) var x: T fromJSTupleBody(x) - return some(x) + return ok(x) -proc fromJSSeq[T](ctx: JSContext, val: JSValue): Option[seq[T]] = +proc fromJSSeq[T](ctx: JSContext, val: JSValue): Opt[seq[T]] = let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator) if JS_IsException(itprop): - return none(seq[T]) + return err() defer: JS_FreeValue(ctx, itprop) let it = JS_Call(ctx, itprop, val, 0, nil) if JS_IsException(it): - return none(seq[T]) + return err() defer: JS_FreeValue(ctx, it) let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next) if JS_IsException(next_method): - return none(seq[T]) + return err() defer: JS_FreeValue(ctx, next_method) - result = some(newSeq[T]()) + result = ok(newSeq[T]()) while true: let next = JS_Call(ctx, next_method, it, 0, nil) if JS_IsException(next): - return none(seq[T]) + return err() defer: JS_FreeValue(ctx, next) let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done) if JS_IsException(doneVal): - return none(seq[T]) + return err() defer: JS_FreeValue(ctx, doneVal) let done = fromJS[bool](ctx, doneVal) if done.isnone: # exception - return none(seq[T]) + return err() if done.get: break let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value) if JS_IsException(valueVal): - return none(seq[T]) + return err() defer: JS_FreeValue(ctx, valueVal) let genericRes = fromJS[typeof(result.get[0])](ctx, valueVal) if genericRes.isnone: # exception - return none(seq[T]) + return err() result.get.add(genericRes.get) -proc fromJSSet[T](ctx: JSContext, val: JSValue): Option[set[T]] = +proc fromJSSet[T](ctx: JSContext, val: JSValue): Opt[set[T]] = let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().sym_iterator) if JS_IsException(itprop): - return none(set[T]) + return err() defer: JS_FreeValue(ctx, itprop) let it = JS_Call(ctx, itprop, val, 0, nil) if JS_IsException(it): - return none(set[T]) + return err() defer: JS_FreeValue(ctx, it) let next_method = JS_GetProperty(ctx, it, ctx.getOpaque().next) if JS_IsException(next_method): - return none(set[T]) + return err() defer: JS_FreeValue(ctx, next_method) var s: set[T] result = some(s) while true: let next = JS_Call(ctx, next_method, it, 0, nil) if JS_IsException(next): - return none(set[T]) + return err() defer: JS_FreeValue(ctx, next) let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().done) if JS_IsException(doneVal): - return none(set[T]) + return err() defer: JS_FreeValue(ctx, doneVal) let done = fromJS[bool](ctx, doneVal) if done.isnone: # exception - return none(set[T]) + return err() if done.get: break let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().value) if JS_IsException(valueVal): - return none(set[T]) + return err() defer: JS_FreeValue(ctx, valueVal) let genericRes = fromJS[typeof(result.get.items)](ctx, valueVal) if genericRes.isnone: # exception - return none(set[T]) + return err() result.get.incl(genericRes.get) -proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Option[Table[A, B]] = +proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Opt[Table[A, B]] = var ptab: ptr JSPropertyEnum var plen: uint32 let flags = cint(JS_GPN_STRING_MASK) if JS_GetOwnPropertyNames(ctx, addr ptab, addr plen, val, flags) == -1: # exception - return none(Table[A, B]) + return err() defer: for i in 0..<plen: let prop = cast[ptr JSPropertyEnum](cast[int](ptab) + sizeof(ptab[]) * int(i)) JS_FreeAtom(ctx, prop.atom) js_free(ctx, ptab) - result = some(Table[A, B]()) + var res = Table[A, B]() for i in 0..<plen: let prop = cast[ptr JSPropertyEnum](cast[int](ptab) + sizeof(ptab[]) * int(i)) let atom = prop.atom let k = JS_AtomToValue(ctx, atom) defer: JS_FreeValue(ctx, k) let kn = fromJS[A](ctx, k) - if kn.isnone: # exception - return none(Table[A, B]) + if kn.isErr: # exception + return err() let v = JS_GetProperty(ctx, val, atom) defer: JS_FreeValue(ctx, v) let vn = fromJS[B](ctx, v) - if vn.isnone: # exception - return none(Table[A, B]) + if vn.isErr: # exception + return err() result.get[kn.get] = vn.get + return ok(res) proc toJS*(ctx: JSContext, s: cstring): JSValue proc toJS*(ctx: JSContext, s: string): JSValue @@ -595,17 +643,21 @@ proc toJS*(ctx: JSContext, n: uint64): JSValue proc toJS(ctx: JSContext, n: SomeFloat): JSValue proc toJS*(ctx: JSContext, b: bool): JSValue proc toJS[U, V](ctx: JSContext, t: Table[U, V]): JSValue -proc toJS(ctx: JSContext, opt: Option): JSValue +proc toJS*(ctx: JSContext, opt: Option): JSValue +proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue proc toJS(ctx: JSContext, s: seq): JSValue proc toJS(ctx: JSContext, e: enum): JSValue proc toJS(ctx: JSContext, j: JSValue): JSValue proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue +proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): JSValue proc toJS(ctx: JSContext, promise: EmptyPromise): JSValue +proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue proc toJS*(ctx: JSContext, obj: ref object): JSValue +proc toJS*(ctx: JSContext, err: JSError): JSValue # ew.... -proc fromJSFunction1[T, U](ctx: JSContext, val: JSValue): Option[proc(x: U): Option[T]] = - return some(proc(x: U): Option[T] = +proc fromJSFunction1[T, U](ctx: JSContext, val: JSValue): Opt[proc(x: U): Opt[T]] = + return ok(proc(x: U): Opt[T] = var arg1 = toJS(ctx, x) let ret = JS_Call(ctx, val, JS_UNDEFINED, 1, addr arg1) return fromJS[T](ctx, ret) @@ -617,7 +669,7 @@ macro unpackReturnType(f: typed) = x = x[1].getTypeImpl() let params = x.findChild(it.kind == nnkFormalParams) let rv = params[0] - assert rv[0].strVal == "Option" + doAssert rv[0].strVal == "Opt" let rvv = rv[1] result = quote do: `rvv` @@ -631,7 +683,7 @@ macro unpackArg0(f: typed) = let rvv = rv[1] result = quote do: `rvv` -proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] = +proc fromJS*[T](ctx: JSContext, val: JSValue): Opt[T] = when T is string: return toString(ctx, val) elif T is char: @@ -653,14 +705,22 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] = return some(r) elif T is (proc): return fromJSFunction1[typeof(unpackReturnType(T)), typeof(unpackArg0(T))](ctx, val) - elif typeof(result.unsafeGet) is Option: # unwrap + elif T is Option: # convert if JS_IsUndefined(val): #TODO what about null? - return none(T) + return err() let res = fromJS[typeof(result.get.get)](ctx, val) if res.isNone: - return none(T) - return some(res) + return err() + return ok(some(res.get)) + elif typeof(result).valType is Opt: # unwrap + if JS_IsUndefined(val): + #TODO what about null? + return err() + let res = fromJS[typeof(result.get.get)](ctx, val) + if res.isNone: + return err() + return ok(res) elif T is seq: return fromJSSeq[typeof(result.get.items)](ctx, val) elif T is set: @@ -670,11 +730,11 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] = elif T is bool: let ret = JS_ToBool(ctx, val) if ret == -1: # exception - return none(T) + return err() if ret == 0: - return some(false) - return some(true) - elif typeof(result.get) is Table: + return ok(false) + return ok(true) + elif typeof(result).valType is Table: return fromJSTable[typeof(result.get.keys), typeof(result.get.values)](ctx, val) elif T is SomeInteger: if JS_IsNumber(val): @@ -683,50 +743,50 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): Option[T] = if JS_IsNumber(val): var f64: float64 if JS_ToFloat64(ctx, addr f64, val) < 0: - return none(T) - return some(cast[T](f64)) + return err() + return ok(cast[T](f64)) elif T is enum: #TODO implement enum handling... if JS_IsException(val): - return none(T) + return err() let s = toString(ctx, val) - if s.isnone: - return none(T) + if s.isErr: + return err() try: - return some(parseEnum[T](s.get)) + return ok(parseEnum[T](s.get)) except ValueError: JS_ThrowTypeError(ctx, "`%s' is not a valid value for enumeration %s", cstring(s.get), $T) - return none(T) + return err() elif T is JSValue: - return some(val) - elif T is object: - doAssert false, "Dictionary case has not been implemented yet!" - #TODO TODO TODO implement dictionary case - return none(T) - else: + return ok(val) + elif T is ref object: if JS_IsException(val): - return none(T) + return err() let op = cast[T](getOpaque(ctx, val, $T)) if op == nil: JS_ThrowTypeError(ctx, "Value is not an instance of %s", $T) - return none(T) - return some(op) + return err() + return ok(op) + else: + static: + doAssert false const JS_ATOM_TAG_INT = cuint(1u32 shl 31) func JS_IsNumber(v: JSAtom): JS_BOOL = return (cast[cuint](v) and JS_ATOM_TAG_INT) != 0 -func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Option[T] = +func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Opt[T] = when T is SomeNumber: if JS_IsNumber(atom): - return some(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT))) + return ok(T(cast[uint32](atom) and (not JS_ATOM_TAG_INT))) else: let val = JS_AtomToValue(ctx, atom) return fromJS[T](ctx, val) -proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): Option[(proc(x: T): Option[U])] = - return fromJS[(proc(x: T): Option[U])](ctx, val) +proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): + Opt[(proc(x: T): Opt[U])] = + return fromJS[(proc(x: T): Opt[U])](ctx, val) proc toJS*(ctx: JSContext, s: cstring): JSValue = return JS_NewString(ctx, s) @@ -770,11 +830,23 @@ proc toJS[U, V](ctx: JSContext, t: Table[U, V]): JSValue = setProperty(ctx, obj, k, toJS(ctx, v)) return obj -proc toJS(ctx: JSContext, opt: Option): JSValue = +proc toJS*(ctx: JSContext, opt: Option): JSValue = if opt.isSome: return toJS(ctx, opt.get) return JS_NULL +proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue = + if opt.isSome: + when not (T is void): + return toJS(ctx, opt.get) + return JS_UNDEFINED + else: + when not (E is void): + let res = toJS(ctx, opt.error) + if not JS_IsNull(res): + return JS_Throw(ctx, res) + return JS_NULL + proc toJS(ctx: JSContext, s: seq): JSValue = let a = JS_NewArray(ctx) if not JS_IsException(a): @@ -794,7 +866,7 @@ proc getTypePtr[T](x: T): pointer = else: return getTypeInfo(x) -proc toJS*(ctx: JSContext, obj: ref object): JSValue = +proc toJSRefObj(ctx: JSContext, obj: ref object): JSValue = if obj == nil: return JS_NULL let op = JS_GetRuntime(ctx).getOpaque() @@ -807,6 +879,9 @@ proc toJS*(ctx: JSContext, obj: ref object): JSValue = setOpaque(ctx, jsObj, obj) return jsObj +proc toJS*(ctx: JSContext, obj: ref object): JSValue = + return toJSRefObj(ctx, obj) + proc toJS(ctx: JSContext, e: enum): JSValue = return toJS(ctx, $e) @@ -840,15 +915,42 @@ proc toJS[T](ctx: JSContext, promise: Promise[T]): JSValue = JS_FreeValue(ctx, resolving_funcs[1])) return jsPromise -type - JS_Error = object of CatchableError +proc toJS[T, E](ctx: JSContext, promise: Promise[Result[T, E]]): 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: Result[T, E]) = + if x.isOk: + let x = when T is void: + JS_UNDEFINED + else: + toJS(ctx, x.get) + let res = JS_Call(ctx, resolving_funcs[0], JS_UNDEFINED, 1, unsafeAddr x) + JS_FreeValue(ctx, res) + JS_FreeValue(ctx, x) + else: # err + let x = when E is void: + JS_UNDEFINED + else: + toJS(ctx, x.get) + let res = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1, unsafeAddr x) + JS_FreeValue(ctx, res) + JS_FreeValue(ctx, x) + JS_FreeValue(ctx, resolving_funcs[0]) + JS_FreeValue(ctx, resolving_funcs[1])) + return jsPromise - JS_SyntaxError* = object of JS_Error - JS_TypeError* = object of JS_Error - JS_ReferenceError* = object of JS_Error - JS_RangeError* = object of JS_Error - JS_InternalError* = object of JS_Error +proc toJS*(ctx: JSContext, err: JSError): JSValue = + if err.e notin QuickJSErrors: + return toJSRefObj(ctx, err) + var msg = toJS(ctx, err.message) + if JS_IsException(msg): + return msg + let ctor = ctx.getOpaque().err_ctors[err.e] + return JS_CallConstructor(ctx, ctor, 1, addr msg) +type JSFuncGenerator = object t: BoundFunctionType original: NimNode @@ -1020,7 +1122,7 @@ template fromJS_or_die*(t, ctx, val, ev, dl: untyped): untyped = if JS_IsException(val): return ev let x = fromJS[t](ctx, val) - if x.isnone: + if x.isNone: break dl x.get @@ -1248,7 +1350,7 @@ proc newJSProcBody(gen: var JSFuncGenerator, isva: bool): NimNode = var ma = gen.actualMinArgs result = newStmtList() if isva: - result.add(quote do: + result.add(quote do: if argc < `ma`: return JS_ThrowTypeError(ctx, "At least %d arguments required, " & "but only %d passed", `ma`, argc) @@ -1288,7 +1390,7 @@ proc newJSProc(gen: var JSFuncGenerator, params: openArray[NimNode], isva = true # declared on the parent function. # Note: this causes the entire nim function body to be inlined inside the JS # interface function. -#TODO: implement actual inlining (so we can e.g. get rid of JS_Error, use format strings, etc.) +#TODO: remove this. macro JS_ERR*(a: typed, b: string) = result = quote do: block when_js: @@ -1379,7 +1481,7 @@ proc rewriteExceptions(gen: var JSFuncGenerator, errors: var seq[string], node: errors.add(c[1].strVal) elif c.len > 0: gen.rewriteExceptions(errors, c) - + proc rewriteExceptions(gen: var JSFuncGenerator) = let ostmts = gen.original.findChild(it.kind == nnkStmtList) var errors: seq[string] @@ -1700,10 +1802,10 @@ type name*: string fun*: JSCFunction -macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, asglobal = - false, nointerface = false, name: static string = "", - extra_getset: static openarray[TabGetSet] = [], - namespace: JSValue = JS_NULL): JSClassID = +macro registerType*(ctx: typed, t: typed, parent: JSClassID = 0, + asglobal = false, nointerface = false, name: static string = "", + extra_getset: static openarray[TabGetSet] = [], + namespace: JSValue = JS_NULL, errid = opt(JSErrorEnum)): JSClassID = result = newStmtList() let tname = t.strVal # the nim type's name. let name = if name == "": tname else: name # possibly a different name, e.g. Buffer for Container @@ -1868,7 +1970,7 @@ static JSClassDef """, `cdname`, """ = { var x: `t` new(x, nim_finalize_for_js) `ctx`.newJSClass(`classDef`, `tname`, `sctr`, `tabList`, getTypePtr(x), - `parent`, `asglobal`, `nointerface`, `finName`, `namespace`) + `parent`, `asglobal`, `nointerface`, `finName`, `namespace`, `errid`) ) result.add(newBlockStmt(endstmts)) diff --git a/src/types/url.nim b/src/types/url.nim index cba83adb..9e786347 100644 --- a/src/types/url.nim +++ b/src/types/url.nim @@ -862,7 +862,7 @@ func serialize*(url: Option[Url], excludefragment = false): string = func equals*(a, b: Url, excludefragment = false): bool = return a.serialize(excludefragment) == b.serialize(excludefragment) -func `$`*(url: Url): string {.jsfunc, inline.} = url.serialize() +func `$`*(url: URL): string {.jsfunc.} = url.serialize() func `$`*(path: UrlPath): string {.inline.} = path.serialize() diff --git a/src/utils/opt.nim b/src/utils/opt.nim index caba236e..3766516b 100644 --- a/src/utils/opt.nim +++ b/src/utils/opt.nim @@ -2,18 +2,26 @@ type Result*[T, E] = object - when (T is void) and (E is void): - has: bool - else: - case has: bool + when E is void and T is void: # weirdness + has*: bool + elif E is void and not (T is void): # opt + case has*: bool + of true: + val*: T + else: + discard + elif not (E is void) and T is void: # err + case has*: bool + of true: + discard + else: + ex*: E + else: # result + case has*: bool of true: - when not (T is void): - val: T + val*: T else: - when not (E is void): - ex: E - else: - discard + ex*: E Opt*[T] = Result[T, void] @@ -31,9 +39,12 @@ template ok*[T](x: T): auto = template ok*(): auto = ok(typeof(result)) -template ok*[T, E](res: var Result[T, E], x: T): Result[T, E] = +template ok*[T, E](res: var Result[T, E], x: T) = res = Result[T, E](has: true, val: x) +template ok*[E](res: var Result[void, E]) = + res = Result[void, E](has: true) + template err*[T, E](t: type Result[T, E], e: E): Result[T, E] = Result[T, E](has: false, ex: e) @@ -44,22 +55,39 @@ template err*(): auto = err(typeof(result)) template err*[T, E](res: var Result[T, E], e: E) = - res.ex = e + res = Result[T, E](has: false, ex: e) + +template err*[T, E](res: var Result[T, E]) = + res = Result[T, E](has: false) template err*[E](e: E): auto = err(typeof(result), e) +template opt*[T](v: T): auto = + ok(Opt[T], v) + +template opt*(t: typedesc): auto = + err(Result[t, void]) + template isOk*(res: Result): bool = res.has template isErr*(res: Result): bool = not res.has template isSome*(res: Result): bool = res.isOk template isNone*(res: Result): bool = res.isErr -template get*[T, E](res: Result[T, E]): T = res.val -template error*[T, E](res: Result[T, E]): E = res.ex +func get*[T, E](res: Result[T, E]): T {.inline.} = res.val +func get*[T, E](res: var Result[T, E]): var T = res.val +func get*[T, E](res: Result[T, E], v: T): T = + if res.has: + res.val + else: + v +func error*[T, E](res: Result[T, E]): E {.inline.} = res.ex +template valType*[T, E](res: type Result[T, E]): auto = T +template errType*[T, E](res: type Result[T, E]): auto = E func isSameErr[T, E, F](a: type Result[T, E], b: type F): bool = return E is F -template `?`*[T, E](res: Result[T, E]): T = +template `?`*[T, E](res: Result[T, E]): auto = let x = res # for when res is a funcall if x.has: when not (T is void): @@ -73,13 +101,3 @@ template `?`*[T, E](res: Result[T, E]): T = return err(x.error) else: return err() - -template `?`*[E](res: Err[E]) = - let x = res # for when res is a funcall - if not x.has: - when typeof(result) is Err[E]: - return x - elif isSameErr(typeof(result), E): - return err(x.error) - else: - return err() diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index a988c976..6d3c6f08 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -1074,9 +1074,9 @@ func twidth*(s: string, w: int): int = func breaksWord*(r: Rune): bool = return not (r.isDigitAscii() or r.width() == 0 or r.isAlpha()) -type BoundaryFunction* = proc(x: Rune): Option[bool] +type BoundaryFunction* = proc(x: Rune): Opt[bool] -proc breaksWord*(r: Rune, check: Option[BoundaryFunction]): bool = +proc breaksWord*(r: Rune, check: Opt[BoundaryFunction]): bool = if check.isSome: let f = check.get() let v = f(r) |