diff options
author | bptato <nincsnevem662@gmail.com> | 2024-04-15 22:09:32 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-04-15 22:13:51 +0200 |
commit | 0d8ebdce2897244d8297eecd175441e541292b94 (patch) | |
tree | 8331642e3a2163530651801c21556ef39c8ecdaa /src | |
parent | 8df9ef9cbbd284afac4d26494d45253a43aa3146 (diff) | |
download | chawan-0d8ebdce2897244d8297eecd175441e541292b94.tar.gz |
js: remove automatic function -> closure conversion
It's a bad idea for several reasons: * it's inefficient; must allocate an environment for a closure in Nim, even though we already have one in JS * writing macros for automatically creating functions with variadic arguments is suprisingly difficult (see the entire `js/javascript' module) * it never really worked properly, because we never freed the associated function pointer. We hardly used it anyway, so the easiest fix is to get rid of it completely.
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config.nim | 22 | ||||
-rw-r--r-- | src/html/dom.nim | 10 | ||||
-rw-r--r-- | src/html/env.nim | 2 | ||||
-rw-r--r-- | src/js/fromjs.nim | 58 | ||||
-rw-r--r-- | src/js/javascript.nim | 26 | ||||
-rw-r--r-- | src/js/jstypes.nim | 8 | ||||
-rw-r--r-- | src/local/pager.nim | 42 | ||||
-rw-r--r-- | src/server/buffer.nim | 12 |
8 files changed, 75 insertions, 105 deletions
diff --git a/src/config/config.nim b/src/config/config.nim index 287c2e54..4f28f4e5 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -12,6 +12,7 @@ import config/toml import js/error import js/fromjs import js/javascript +import js/jstypes import js/propertyenumlist import js/regex import js/tojs @@ -40,7 +41,7 @@ type SiteConfig* = object url*: Option[Regex] host*: Option[Regex] - rewrite_url*: (proc(s: URL): JSResult[URL]) + rewrite_url*: Option[JSValueFunction] cookie*: Option[bool] third_party_cookie*: seq[Regex] share_cookie_jar*: Option[string] @@ -54,7 +55,7 @@ type OmniRule* = object match*: Regex - substitute_url*: (proc(s: string): JSResult[string]) + substitute_url*: Option[JSValueFunction] StartConfig = object visual_home* {.jsgetset.}: string @@ -330,7 +331,7 @@ proc parseConfigValue(ctx: var ConfigParser; x: var Regex; v: TomlValue; k: string) proc parseConfigValue(ctx: var ConfigParser; x: var URL; v: TomlValue; k: string) -proc parseConfigValue[T](ctx: var ConfigParser; x: var proc(x: T): JSResult[T]; +proc parseConfigValue(ctx: var ConfigParser; x: var JSValueFunction; v: TomlValue; k: string) proc parseConfigValue(ctx: var ConfigParser; x: var ChaPathResolved; v: TomlValue; k: string) @@ -581,11 +582,16 @@ proc parseConfigValue(ctx: var ConfigParser; x: var URL; v: TomlValue; raise newException(ValueError, "invalid URL " & k) x = y.get -proc parseConfigValue[T](ctx: var ConfigParser; x: var proc(x: T): JSResult[T]; +proc parseConfigValue(ctx: var ConfigParser; x: var JSValueFunction; v: TomlValue; k: string) = typeCheck(v, tvtString, k) let fun = ctx.config.jsctx.eval(v.s, "<config>", JS_EVAL_TYPE_GLOBAL) - x = getJSFunction[T, T](ctx.config.jsctx, fun) + if JS_IsException(fun): + raise newException(ValueError, "exception in " & k & ": " & + ctx.config.jsctx.getExceptionMsg()) + if not JS_IsFunction(ctx.config.jsctx, fun): + raise newException(ValueError, k & " is not a function") + x = JSValueFunction(fun: fun) proc parseConfigValue(ctx: var ConfigParser; x: var ChaPathResolved; v: TomlValue; k: string) = @@ -761,7 +767,7 @@ proc initCommands*(config: Config): Err[string] = let obj = JS_NewObject(ctx) defer: JS_FreeValue(ctx, obj) if JS_IsException(obj): - return err(ctx.getExceptionStr()) + return err(ctx.getExceptionMsg()) for i in countdown(config.cmd.init.high, 0): let (k, cmd) = config.cmd.init[i] if k in config.cmd.map: @@ -776,14 +782,14 @@ proc initCommands*(config: Config): Err[string] = prop = JS_NewObject(ctx) ctx.definePropertyE(objIt, ss, prop) if JS_IsException(prop): - return err(ctx.getExceptionStr()) + return err(ctx.getExceptionMsg()) objIt = prop if cmd == "": config.cmd.map[k] = JS_UNDEFINED continue let fun = ctx.eval(cmd, "<" & k & ">", JS_EVAL_TYPE_GLOBAL) if JS_IsException(fun): - return err(ctx.getExceptionStr()) + return err(ctx.getExceptionMsg()) if not JS_IsFunction(ctx, fun): return err(k & " is not a function") ctx.definePropertyE(objIt, name, JS_DupValue(ctx, fun)) diff --git a/src/html/dom.nim b/src/html/dom.nim index 28e3f558..e3d62b60 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -2846,9 +2846,8 @@ proc reflectEvent(element: Element; target: EventTarget; name: StaticAtom; let fun = ctx.newFunction(["event"], value) assert ctx != nil if JS_IsException(fun): - let s = ctx.getExceptionStr() document.window.console.log("Exception in body content attribute of", - urls, s) + urls, ctx.getExceptionMsg()) else: let jsTarget = ctx.toJS(target) ctx.definePropertyC(jsTarget, $name, fun) @@ -3628,13 +3627,12 @@ proc execute*(element: HTMLScriptElement) = let urls = script.baseURL.serialize(excludepassword = true) let ctx = window.jsctx if JS_IsException(script.record): - let s = ctx.getExceptionStr() - window.console.log("Exception in document", urls, s) + window.console.log("Exception in document", urls, ctx.getExceptionMsg()) else: let ret = ctx.evalFunction(script.record) if JS_IsException(ret): - let s = ctx.getExceptionStr() - window.console.log("Exception in document", urls, s) + window.console.log("Exception in document", urls, + ctx.getExceptionMsg()) JS_FreeValue(ctx, ret) document.currentScript = oldCurrentScript else: discard #TODO diff --git a/src/html/env.nim b/src/html/env.nim index ebd9e83e..14a12edd 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -169,7 +169,7 @@ proc addScripting*(window: Window; selector: Selector[int]) = let ret = window.jsctx.eval(src, file, JS_EVAL_TYPE_GLOBAL) if JS_IsException(ret): window.console.log("Exception in document", $window.document.url, - window.jsctx.getExceptionStr()) + window.jsctx.getExceptionMsg()) else: JS_FreeValue(ctx, ret) ) diff --git a/src/js/fromjs.nim b/src/js/fromjs.nim index a176cd33..f93e89fa 100644 --- a/src/js/fromjs.nim +++ b/src/js/fromjs.nim @@ -8,7 +8,6 @@ import io/promise import js/error import js/jstypes import js/opaque -import js/tojs import types/opt import utils/twtstr @@ -280,61 +279,6 @@ proc fromJSTable[A, B](ctx: JSContext, val: JSValue): JSResult[Table[A, B]] = res[kn] = vn return ok(res) -#TODO varargs -proc fromJSFunction1*[T, U](ctx: JSContext, val: JSValue): - proc(x: U): JSResult[T] = - #TODO this leaks memory! - let dupval = JS_DupValue(ctx, JS_DupValue(ctx, val)) # save - return proc(x: U): JSResult[T] = - var arg1 = toJS(ctx, x) - #TODO exceptions? - let ret = JS_Call(ctx, dupval, JS_UNDEFINED, 1, addr arg1) - result = fromJS[T](ctx, ret) - JS_FreeValue(ctx, ret) - -proc isErrType(rt: NimNode): bool = - let rtType = rt[0] - let errType = getTypeInst(Err) - return errType.sameType(rtType) and rtType.sameType(errType) - -# unpack brackets -proc getRealTypeFun(x: NimNode): NimNode = - var x = x.getTypeImpl() - while true: - if x.kind == nnkBracketExpr and x.len == 2: - x = x[1].getTypeImpl() - continue - break - return x - -macro unpackReturnType(f: typed) = - var x = f.getRealTypeFun() - let params = x.findChild(it.kind == nnkFormalParams) - let rv = params[0] - if rv.isErrType(): - return quote do: void - let rvv = rv[1] - return quote do: `rvv` - -macro unpackArg0(f: typed) = - var x = f.getRealTypeFun() - let params = x.findChild(it.kind == nnkFormalParams) - let rv = params[1] - doAssert rv.kind == nnkIdentDefs - let rvv = rv[1] - return quote do: `rvv` - -proc fromJSFunction[T](ctx: JSContext, val: JSValue): - JSResult[T] = - #TODO all args... - if not JS_IsFunction(ctx, val): - return err(newTypeError("function expected")) - return ok( - fromJSFunction1[ - typeof(unpackReturnType(T)), - typeof(unpackArg0(T)) - ](ctx, val)) - template optionType[T](o: type Option[T]): auto = T @@ -462,8 +406,6 @@ macro fromJS2(ctx: JSContext; val: JSValue; x: static string): untyped = proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] = when T is string: return fromJSString(ctx, val) - elif T is (proc): - return fromJSFunction[T](ctx, val) elif T is Option: return fromJSOption[optionType(T)](ctx, val) elif T is seq: diff --git a/src/js/javascript.nim b/src/js/javascript.nim index f2cd8a10..ef848038 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -185,7 +185,7 @@ proc free*(rt: var JSRuntime) = runtimes.del(runtimes.find(rt)) rt = nil -proc setGlobal*[T](ctx: JSContext, global: JSValue, obj: T) = +proc setGlobal*[T](ctx: JSContext; global: JSValue; obj: T) = # Add JSValue reference. let p = JS_VALUE_GET_PTR(global) let header = cast[ptr JSRefCountHeader](p) @@ -193,10 +193,11 @@ proc setGlobal*[T](ctx: JSContext, global: JSValue, obj: T) = ctx.setOpaque(global, cast[pointer](obj)) GC_ref(obj) -proc setInterruptHandler*(rt: JSRuntime, cb: JSInterruptHandler, opaque: pointer = nil) = +proc setInterruptHandler*(rt: JSRuntime; cb: JSInterruptHandler; + opaque: pointer = nil) = JS_SetInterruptHandler(rt, cb, opaque) -proc getExceptionStr*(ctx: JSContext): string = +proc getExceptionMsg*(ctx: JSContext): string = result = "" let ex = JS_GetException(ctx) let str = fromJS[string](ctx, ex) @@ -208,11 +209,20 @@ proc getExceptionStr*(ctx: JSContext): string = JS_FreeValue(ctx, stack) JS_FreeValue(ctx, ex) -proc writeException*(ctx: JSContext, s: DynStream) = - s.write(ctx.getExceptionStr()) +proc getExceptionMsg*(ctx: JSContext; err: JSError): string = + if err != nil: + JS_FreeValue(ctx, ctx.toJS(err)) # note: this implicitly throws + return ctx.getExceptionMsg() + +proc writeException*(ctx: JSContext; s: DynStream) = + s.write(ctx.getExceptionMsg()) + s.sflush() + +proc writeException*(ctx: JSContext; s: DynStream; err: JSError) = + s.write(ctx.getExceptionMsg(err)) s.sflush() -proc runJSJobs*(rt: JSRuntime, err: DynStream) = +proc runJSJobs*(rt: JSRuntime; err: DynStream) = while JS_IsJobPending(rt): var ctx: JSContext let r = JS_ExecutePendingJob(rt, addr ctx) @@ -322,10 +332,6 @@ func getMinArgs(params: seq[FuncParam]): int = func fromJSP[T: string|uint32](ctx: JSContext, atom: JSAtom): Opt[T] = return fromJS[T](ctx, atom) -proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): - (proc(x: T): JSResult[U]) = - return fromJSFunction1[T, U](ctx, val) - proc defineConsts*[T](ctx: JSContext, classid: JSClassID, consts: static openArray[(string, T)]) = let proto = ctx.getOpaque().ctors[classid] diff --git a/src/js/jstypes.nim b/src/js/jstypes.nim index 5336f067..9e1d72ea 100644 --- a/src/js/jstypes.nim +++ b/src/js/jstypes.nim @@ -33,3 +33,11 @@ type func high*(abuf: JSArrayBuffer): int = return int(abuf.len) - 1 + +# A specialization of JSValue to make writing generic code for functions +# easier. +type JSValueFunction* = ref object + fun*: JSValue + +converter toJSValue*(f: JSValueFunction): JSValue = + f.fun diff --git a/src/local/pager.nim b/src/local/pager.nim index 4d29f4f0..624a4867 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -3,14 +3,12 @@ import std/net import std/options import std/os import std/osproc +import std/posix import std/selectors import std/streams import std/tables import std/unicode -when defined(posix): - import std/posix - import bindings/libregexp import config/config import config/mailcap @@ -23,6 +21,7 @@ import io/stdio import io/tempfile import io/urlfilter import js/error +import js/fromjs import js/javascript import js/jstypes import js/regex @@ -115,6 +114,7 @@ type inputBuffer*: string # currently uninterpreted characters iregex: Result[Regex, string] isearchpromise: EmptyPromise + jsctx: JSContext lineData: LineData lineedit*: Option[LineEdit] linehist: array[LineMode, LineHistory] @@ -282,7 +282,8 @@ proc newPager*(config: Config; forkserver: ForkServer; ctx: JSContext; config: config, forkserver: forkserver, term: newTerminal(stdout, config), - alerts: alerts + alerts: alerts, + jsctx: ctx ) proc genClientKey(pager: Pager): ClientKey = @@ -865,15 +866,25 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset; var charsets = pager.config.encoding.document_charset var userstyle = pager.config.css.stylesheet var proxy = pager.config.network.proxy + let ctx = pager.jsctx for sc in pager.config.siteconf: if sc.url.isSome and not sc.url.get.match($url): continue elif sc.host.isSome and not sc.host.get.match(host): continue - if sc.rewrite_url != nil: - let s = sc.rewrite_url(url) - if s.isSome and s.get != nil: - url = s.get + if sc.rewrite_url.isSome: + let fun = sc.rewrite_url.get + var arg1 = ctx.toJS(url) + let ret = JS_Call(ctx, fun, JS_UNDEFINED, 1, addr arg1) + let nu = fromJS[URL](ctx, ret) + if nu.isOk: + if nu.get != nil: + url = nu.get + elif JS_IsException(ret): + #TODO should writeException the message to console + pager.alert("Error rewriting URL: " & ctx.getExceptionMsg(nu.error)) + JS_FreeValue(ctx, arg1) + JS_FreeValue(ctx, ret) if sc.cookie.isSome: if sc.cookie.get: # host/url might have changed by now @@ -964,12 +975,15 @@ proc gotoURL(pager: Pager; request: Request; prevurl = none(URL); proc omniRewrite(pager: Pager, s: string): string = for rule in pager.config.omnirule: if rule.match.match(s): - let sub = rule.substitute_url(s) - if sub.isSome: - return sub.get - else: - let buf = $rule.match - pager.alert("Error in substitution of rule " & buf & " for " & s) + let fun = rule.substitute_url.get + let ctx = pager.jsctx + var arg1 = ctx.toJS(s) + let jsRet = JS_Call(ctx, fun, JS_UNDEFINED, 1, addr arg1) + let ret = fromJS[string](ctx, jsRet) + if ret.isOk: + return ret.get + pager.alert("Error in substitution of " & $rule.match & " for " & s & + ": " & ctx.getExceptionMsg(ret.error)) return s # When the user has passed a partial URL as an argument, they might've meant diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 363f431c..a3e4afe2 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -1016,8 +1016,7 @@ proc dispatchDOMContentLoadedEvent(buffer: Buffer) = if el.ctype == "DOMContentLoaded": let e = ctx.invoke(el, event) if JS_IsException(e): - buffer.estream.write(ctx.getExceptionStr()) - buffer.estream.sflush() + ctx.writeException(buffer.estream) JS_FreeValue(ctx, e) called = true if FLAG_STOP_IMMEDIATE_PROPAGATION in event.flags: @@ -1039,8 +1038,7 @@ proc dispatchLoadEvent(buffer: Buffer) = if el.ctype == "load": let e = ctx.invoke(el, event) if JS_IsException(e): - buffer.estream.write(ctx.getExceptionStr()) - buffer.estream.sflush() + ctx.writeException(buffer.estream) JS_FreeValue(ctx, e) called = true if FLAG_STOP_IMMEDIATE_PROPAGATION in event.flags: @@ -1069,8 +1067,7 @@ proc dispatchEvent(buffer: Buffer; ctype, jsName: string; elem: Element): let e = ctx.invoke(el, event) called = true if JS_IsException(e): - buffer.estream.write(ctx.getExceptionStr()) - buffer.estream.sflush() + ctx.writeException(buffer.estream) JS_FreeValue(ctx, e) if FLAG_STOP_IMMEDIATE_PROPAGATION in event.flags: stop = true @@ -1494,8 +1491,7 @@ proc evalJSURL(buffer: Buffer, url: URL): Opt[string] = let ctx = buffer.window.jsctx let ret = ctx.eval(scriptSource, $buffer.baseURL, JS_EVAL_TYPE_GLOBAL) if JS_IsException(ret): - buffer.estream.write(ctx.getExceptionStr()) - buffer.estream.sflush() + ctx.writeException(buffer.estream) return err() # error if JS_IsUndefined(ret): return err() # no need to navigate |