diff options
Diffstat (limited to 'lib/js')
-rw-r--r-- | lib/js/asyncjs.nim | 269 | ||||
-rw-r--r-- | lib/js/dom.nim | 1841 | ||||
-rw-r--r-- | lib/js/jsconsole.nim | 125 | ||||
-rw-r--r-- | lib/js/jscore.nim | 153 | ||||
-rw-r--r-- | lib/js/jsffi.nim | 527 | ||||
-rw-r--r-- | lib/js/jsre.nim | 97 |
6 files changed, 3012 insertions, 0 deletions
diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim new file mode 100644 index 000000000..9b043f3e5 --- /dev/null +++ b/lib/js/asyncjs.nim @@ -0,0 +1,269 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim Authors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. + +## This module implements types and macros for writing asynchronous code +## for the JS backend. It provides tools for interaction with JavaScript async API-s +## and libraries, writing async procedures in Nim and converting callback-based code +## to promises. +## +## A Nim procedure is asynchronous when it includes the `{.async.}` pragma. It +## should always have a `Future[T]` return type or not have a return type at all. +## A `Future[void]` return type is assumed by default. +## +## This is roughly equivalent to the `async` keyword in JavaScript code. +## +## ```nim +## proc loadGame(name: string): Future[Game] {.async.} = +## # code +## ``` +## +## should be equivalent to +## +## ```javascript +## async function loadGame(name) { +## // code +## } +## ``` +## +## A call to an asynchronous procedure usually needs `await` to wait for +## the completion of the `Future`. +## +## ```nim +## var game = await loadGame(name) +## ``` +## +## Often, you might work with callback-based API-s. You can wrap them with +## asynchronous procedures using promises and `newPromise`: +## +## ```nim +## proc loadGame(name: string): Future[Game] = +## var promise = newPromise() do (resolve: proc(response: Game)): +## cbBasedLoadGame(name) do (game: Game): +## resolve(game) +## return promise +## ``` +## +## Forward definitions work properly, you just need to always add the `{.async.}` pragma: +## +## ```nim +## proc loadGame(name: string): Future[Game] {.async.} +## ``` +## +## JavaScript compatibility +## ======================== +## +## Nim currently generates `async/await` JavaScript code which is supported in modern +## EcmaScript and most modern versions of browsers, Node.js and Electron. +## If you need to use this module with older versions of JavaScript, you can +## use a tool that backports the resulting JavaScript code, as babel. + +# xxx code: javascript above gives `LanguageXNotSupported` warning. + +when not defined(js) and not defined(nimsuggest): + {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".} + +import std/jsffi +import std/macros +import std/private/since + +type + Future*[T] = ref object + future*: T + ## Wraps the return type of an asynchronous procedure. + + PromiseJs* {.importjs: "Promise".} = ref object + ## A JavaScript Promise. + + +proc replaceReturn(node: var NimNode) = + var z = 0 + for s in node: + var son = node[z] + let jsResolve = ident("jsResolve") + if son.kind == nnkReturnStmt: + let value = if son[0].kind != nnkEmpty: nnkCall.newTree(jsResolve, son[0]) else: jsResolve + node[z] = nnkReturnStmt.newTree(value) + elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result": + node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(jsResolve, son[1])) + elif son.kind in RoutineNodes: + discard + else: + replaceReturn(son) + inc z + +proc isFutureVoid(node: NimNode): bool = + result = node.kind == nnkBracketExpr and + node[0].kind == nnkIdent and $node[0] == "Future" and + node[1].kind == nnkIdent and $node[1] == "void" + +proc generateJsasync(arg: NimNode): NimNode = + if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo, nnkProcTy}: + error("Cannot transform this node kind into an async proc." & + " proc/method definition or lambda node expected.") + + # Transform type X = proc (): something {.async.} + # into type X = proc (): Future[something] + if arg.kind == nnkProcTy: + result = arg + if arg[0][0].kind == nnkEmpty: + result[0][0] = quote do: Future[void] + return result + + result = arg + var isVoid = false + let jsResolve = ident("jsResolve") + if arg.params[0].kind == nnkEmpty: + result.params[0] = nnkBracketExpr.newTree(ident("Future"), ident("void")) + isVoid = true + elif isFutureVoid(arg.params[0]): + isVoid = true + + var code = result.body + replaceReturn(code) + result.body = nnkStmtList.newTree() + + if len(code) > 0: + var awaitFunction = quote: + proc await[T](f: Future[T]): T {.importjs: "(await #)", used.} + result.body.add(awaitFunction) + + var resolve: NimNode + if isVoid: + resolve = quote: + var `jsResolve` {.importjs: "undefined".}: Future[void] + else: + resolve = quote: + proc jsResolve[T](a: T): Future[T] {.importjs: "#", used.} + proc jsResolve[T](a: Future[T]): Future[T] {.importjs: "#", used.} + result.body.add(resolve) + else: + result.body = newEmptyNode() + for child in code: + result.body.add(child) + + if len(code) > 0 and isVoid: + var voidFix = quote: + return `jsResolve` + result.body.add(voidFix) + + let asyncPragma = quote: + {.codegenDecl: "async function $2($3)".} + + result.addPragma(asyncPragma[0]) + +macro async*(arg: untyped): untyped = + ## Macro which converts normal procedures into + ## javascript-compatible async procedures. + if arg.kind == nnkStmtList: + result = newStmtList() + for oneProc in arg: + result.add generateJsasync(oneProc) + else: + result = generateJsasync(arg) + +proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importjs: "(new Promise(#))".} + ## A helper for wrapping callback-based functions + ## into promises and async procedures. + +proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importjs: "(new Promise(#))".} + ## A helper for wrapping callback-based functions + ## into promises and async procedures. + +template maybeFuture(T): untyped = + # avoids `Future[Future[T]]` + when T is Future: T + else: Future[T] + + +since (1, 5, 1): + #[ + TODO: + * map `Promise.all()` + * proc toString*(a: Error): cstring {.importjs: "#.toString()".} + + Note: + We probably can't have a `waitFor` in js in browser (single threaded), but maybe it would be possible + in in nodejs, see https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options + and https://stackoverflow.com/questions/61377358/javascript-wait-for-async-call-to-finish-before-returning-from-function-witho + ]# + + type Error* {.importjs: "Error".} = ref object of JsRoot + ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error + message*: cstring + name*: cstring + + type OnReject* = proc(reason: Error) + + proc then*[T](future: Future[T], onSuccess: proc, onReject: OnReject = nil): auto = + ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then + ## Returns a `Future` from the return type of `onSuccess(T.default)`. + runnableExamples("-r:off"): + from std/sugar import `=>` + + proc fn(n: int): Future[int] {.async.} = + if n >= 7: raise newException(ValueError, "foobar: " & $n) + else: result = n * 2 + + proc asyncFact(n: int): Future[int] {.async.} = + if n > 0: result = n * await asyncFact(n-1) + else: result = 1 + + proc main() {.async.} = + block: # then + assert asyncFact(3).await == 3*2 + assert asyncFact(3).then(asyncFact).await == 6*5*4*3*2 + let x1 = await fn(3) + assert x1 == 3 * 2 + let x2 = await fn(4) + .then((a: int) => a.float) + .then((a: float) => $a) + assert x2 == "8.0" + + block: # then with `onReject` callback + var witness = 1 + await fn(6).then((a: int) => (witness = 2), (r: Error) => (witness = 3)) + assert witness == 2 + await fn(7).then((a: int) => (witness = 2), (r: Error) => (witness = 3)) + assert witness == 3 + + template impl(call): untyped = + # see D20210421T014713 + when typeof(block: call) is void: + var ret: Future[void] + else: + var ret = default(maybeFuture(typeof(call))) + typeof(ret) + when T is void: + type A = impl(onSuccess()) + else: + type A = impl(onSuccess(default(T))) + var ret: A + {.emit: "`ret` = `future`.then(`onSuccess`, `onReject`);".} + return ret + + proc catch*[T](future: Future[T], onReject: OnReject): Future[void] = + ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch + runnableExamples("-r:off"): + from std/sugar import `=>` + from std/strutils import contains + + proc fn(n: int): Future[int] {.async.} = + if n >= 7: raise newException(ValueError, "foobar: " & $n) + else: result = n * 2 + + proc main() {.async.} = + var reason: Error + await fn(6).catch((r: Error) => (reason = r)) # note: `()` are needed, `=> reason = r` would not work + assert reason == nil + await fn(7).catch((r: Error) => (reason = r)) + assert reason != nil + assert "foobar: 7" in $reason.message + + discard main() + + {.emit: "`result` = `future`.catch(`onReject`);".} diff --git a/lib/js/dom.nim b/lib/js/dom.nim new file mode 100644 index 000000000..be2a34db1 --- /dev/null +++ b/lib/js/dom.nim @@ -0,0 +1,1841 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Declaration of the Document Object Model for the `JavaScript backend +## <backends.html#backends-the-javascript-target>`_. +## +## +## Document Ready +## -------------- +## +## * Basic example of a document ready: +runnableExamples"-b:js -r:off": + proc example(e: Event) = echo "Document is ready" + document.addEventListener("DOMContentLoaded", example) # You can also use "load" event. +## * This example runs 5 seconds after the document ready: +runnableExamples"-b:js -r:off": + proc example() = echo "5 seconds after document ready" + proc domReady(e: Event) = discard setTimeout(example, 5_000) # Document is ready. + document.addEventListener("DOMContentLoaded", domReady) +## Document onUnload +## ----------------- +## +## * Simple example of how to implement code that runs when the page unloads: +runnableExamples"-b:js -r:off": + proc example(e: Event) = echo "Document is unloaded" + document.addEventListener("unload", example) # You can also use "beforeunload". +## Document Autorefresh +## -------------------- +## +## * Minimal example of a document autorefresh: +runnableExamples"-b:js -r:off": + proc example() = window.location.reload() + discard setTimeout(example, 5_000) +## - For more examples, see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + + +import std/private/since +when not defined(js): + {.error: "This module only works on the JavaScript platform".} + +const + DomApiVersion* = 3 ## the version of DOM API we try to follow. No guarantees though. + +type + EventTarget* {.importc.} = ref object of RootObj + onabort*: proc (event: Event) {.closure.} + onblur*: proc (event: Event) {.closure.} + onchange*: proc (event: Event) {.closure.} + onclick*: proc (event: Event) {.closure.} + ondblclick*: proc (event: Event) {.closure.} + onerror*: proc (event: Event) {.closure.} + onfocus*: proc (event: Event) {.closure.} + onkeydown*: proc (event: Event) {.closure.} + onkeypress*: proc (event: Event) {.closure.} + onkeyup*: proc (event: Event) {.closure.} + onload*: proc (event: Event) {.closure.} + onmousedown*: proc (event: Event) {.closure.} + onmousemove*: proc (event: Event) {.closure.} + onmouseout*: proc (event: Event) {.closure.} + onmouseover*: proc (event: Event) {.closure.} + onmouseup*: proc (event: Event) {.closure.} + onreset*: proc (event: Event) {.closure.} + onselect*: proc (event: Event) {.closure.} + onstorage*: proc (event: Event) {.closure.} + onsubmit*: proc (event: Event) {.closure.} + onunload*: proc (event: Event) {.closure.} + onloadstart*: proc (event: Event) {.closure.} + onprogress*: proc (event: Event) {.closure.} + onloadend*: proc (event: Event) {.closure.} + + DomEvent* {.pure.} = enum + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/Events>`_ + Abort = "abort", + BeforeInput = "beforeinput", + Blur = "blur", + Click = "click", + CompositionEnd = "compositionend", + CompositionStart = "compositionstart", + CompositionUpdate = "compositionupdate", + DblClick = "dblclick", + Error = "error", + Focus = "focus", + FocusIn = "focusin", + FocusOut = "focusout", + Input = "input", + KeyDown = "keydown", + KeyPress = "keypress", + KeyUp = "keyup", + Load = "load", + MouseDown = "mousedown", + MouseEnter = "mouseenter", + MouseLeave = "mouseleave", + MouseMove = "mousemove", + MouseOut = "mouseout", + MouseOver = "mouseover", + MouseUp = "mouseup", + Resize = "resize", + Scroll = "scroll", + Select = "select", + Storage = "storage", + Unload = "unload", + Wheel = "wheel" + + PerformanceMemory* {.importc.} = ref object + jsHeapSizeLimit*: float + totalJSHeapSize*: float + usedJSHeapSize*: float + + PerformanceTiming* {.importc.} = ref object + connectStart*: float + domComplete*: float + domContentLoadedEventEnd*: float + domContentLoadedEventStart*: float + domInteractive*: float + domLoading*: float + domainLookupEnd*: float + domainLookupStart*: float + fetchStart*: float + loadEventEnd*: float + loadEventStart*: float + navigationStart*: float + redirectEnd*: float + redirectStart*: float + requestStart*: float + responseEnd*: float + responseStart*: float + secureConnectionStart*: float + unloadEventEnd*: float + unloadEventStart*: float + + Performance* {.importc.} = ref object + memory*: PerformanceMemory + timing*: PerformanceTiming + + Range* {.importc.} = ref object + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Range>`_ + collapsed*: bool + commonAncestorContainer*: Node + endContainer*: Node + endOffset*: int + startContainer*: Node + startOffset*: int + + Selection* {.importc.} = ref object + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Selection>`_ + anchorNode*: Node + anchorOffset*: int + focusNode*: Node + focusOffset*: int + isCollapsed*: bool + rangeCount*: int + `type`*: cstring + + Storage* {.importc.} = ref object + + Window* {.importc.} = ref object of EventTarget + document*: Document + event*: Event + history*: History + location*: Location + closed*: bool + defaultStatus*: cstring + devicePixelRatio*: float + innerHeight*, innerWidth*: int + locationbar*: ref LocationBar + menubar*: ref MenuBar + name*: cstring + outerHeight*, outerWidth*: int + pageXOffset*, pageYOffset*: int + scrollX*: float + scrollY*: float + personalbar*: ref PersonalBar + scrollbars*: ref ScrollBars + statusbar*: ref StatusBar + status*: cstring + toolbar*: ref ToolBar + frames*: seq[Frame] + screen*: Screen + performance*: Performance + onpopstate*: proc (event: Event) + localStorage*: Storage + sessionStorage*: Storage + parent*: Window + + Frame* {.importc.} = ref object of Window + + ClassList* {.importc.} = ref object of RootObj + + NodeType* = enum + ElementNode = 1, + AttributeNode, + TextNode, + CDATANode, + EntityRefNode, + EntityNode, + ProcessingInstructionNode, + CommentNode, + DocumentNode, + DocumentTypeNode, + DocumentFragmentNode, + NotationNode + + Node* {.importc.} = ref object of EventTarget + attributes*: seq[Node] + childNodes*: seq[Node] + children*: seq[Node] + data*: cstring + firstChild*: Node + lastChild*: Node + nextSibling*: Node + nodeName*: cstring + nodeType*: NodeType + nodeValue*: cstring + parentNode*: Node + content*: Node + previousSibling*: Node + ownerDocument*: Document + innerHTML*: cstring + outerHTML*: cstring + innerText*: cstring + textContent*: cstring + style*: Style + baseURI*: cstring + parentElement*: Element + isConnected*: bool + + Document* {.importc.} = ref object of Node + activeElement*: Element + documentElement*: Element + alinkColor*: cstring + bgColor*: cstring + body*: Element + charset*: cstring + cookie*: cstring + defaultCharset*: cstring + fgColor*: cstring + head*: Element + hidden*: bool + lastModified*: cstring + linkColor*: cstring + referrer*: cstring + title*: cstring + URL*: cstring + visibilityState*: cstring + vlinkColor*: cstring + anchors*: seq[AnchorElement] + forms*: seq[FormElement] + images*: seq[ImageElement] + applets*: seq[Element] + embeds*: seq[EmbedElement] + links*: seq[LinkElement] + fonts*: FontFaceSet + + Element* {.importc.} = ref object of Node + className*: cstring + classList*: ClassList + checked*: bool + defaultChecked*: bool + defaultValue*: cstring + disabled*: bool + form*: FormElement + name*: cstring + readOnly*: bool + options*: seq[OptionElement] + selectedOptions*: seq[OptionElement] + clientWidth*, clientHeight*: int + contentEditable*: cstring + isContentEditable*: bool + dir*: cstring + offsetHeight*: int + offsetWidth*: int + offsetLeft*: int + offsetTop*: int + + ValidityState* {.importc.} = ref object ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_ + badInput*: bool + customError*: bool + patternMismatch*: bool + rangeOverflow*: bool + rangeUnderflow*: bool + stepMismatch*: bool + tooLong*: bool + tooShort*: bool + typeMismatch*: bool + valid*: bool + valueMissing*: bool + + Blob* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_ + size*: int + `type`*: cstring + + File* {.importc.} = ref object of Blob ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_ + lastModified*: int + name*: cstring + + TextAreaElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement>`_ + value*: cstring + selectionStart*, selectionEnd*: int + selectionDirection*: cstring + rows*, cols*: int + + InputElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement>`_ + # Properties related to the parent form + formAction*: cstring + formEncType*: cstring + formMethod*: cstring + formNoValidate*: bool + formTarget*: cstring + + # Properties that apply to any type of input element that is not hidden + `type`*: cstring + autofocus*: bool + required*: bool + value*: cstring + validity*: ValidityState + validationMessage*: cstring + willValidate*: bool + + # Properties that apply only to elements of type "checkbox" or "radio" + indeterminate*: bool + + # Properties that apply only to elements of type "image" + alt*: cstring + height*: cstring + src*: cstring + width*: cstring + + # Properties that apply only to elements of type "file" + accept*: cstring + files*: seq[Blob] + + # Properties that apply only to text/number-containing or elements + autocomplete*: cstring + maxLength*: int + size*: int + pattern*: cstring + placeholder*: cstring + min*: cstring + max*: cstring + selectionStart*: int + selectionEnd*: int + selectionDirection*: cstring + + # Properties not yet categorized + dirName*: cstring + accessKey*: cstring + list*: Element + multiple*: bool + labels*: seq[Element] + step*: cstring + valueAsDate*: cstring + valueAsNumber*: float + + LinkElement* {.importc.} = ref object of Element + target*: cstring + text*: cstring + x*: int + y*: int + + EmbedElement* {.importc.} = ref object of Element + height*: int + hspace*: int + src*: cstring + width*: int + `type`*: cstring + vspace*: int + + AnchorElement* {.importc.} = ref object of Element + text*: cstring + x*, y*: int + + OptionElement* {.importc.} = ref object of Element + defaultSelected*: bool + selected*: bool + selectedIndex*: int + text*: cstring + value*: cstring + + FormElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_ + acceptCharset*: cstring + action*: cstring + autocomplete*: cstring + elements*: seq[Element] + encoding*: cstring + enctype*: cstring + length*: int + `method`*: cstring + noValidate*: bool + target*: cstring + + ImageElement* {.importc.} = ref object of Element + border*: int + complete*: bool + height*: int + hspace*: int + lowsrc*: cstring + src*: cstring + vspace*: int + width*: int + + Style* {.importc.} = ref object of RootObj + alignContent*: cstring + alignItems*: cstring + alignSelf*: cstring + all*: cstring + animation*: cstring + animationDelay*: cstring + animationDirection*: cstring + animationDuration*: cstring + animationFillMode*: cstring + animationIterationCount*: cstring + animationName*: cstring + animationPlayState*: cstring + animationTimingFunction*: cstring + backdropFilter*: cstring + backfaceVisibility*: cstring + background*: cstring + backgroundAttachment*: cstring + backgroundBlendMode*: cstring + backgroundClip*: cstring + backgroundColor*: cstring + backgroundImage*: cstring + backgroundOrigin*: cstring + backgroundPosition*: cstring + backgroundRepeat*: cstring + backgroundSize*: cstring + blockSize*: cstring + border*: cstring + borderBlock*: cstring + borderBlockColor*: cstring + borderBlockEnd*: cstring + borderBlockEndColor*: cstring + borderBlockEndStyle*: cstring + borderBlockEndWidth*: cstring + borderBlockStart*: cstring + borderBlockStartColor*: cstring + borderBlockStartStyle*: cstring + borderBlockStartWidth*: cstring + borderBlockStyle*: cstring + borderBlockWidth*: cstring + borderBottom*: cstring + borderBottomColor*: cstring + borderBottomLeftRadius*: cstring + borderBottomRightRadius*: cstring + borderBottomStyle*: cstring + borderBottomWidth*: cstring + borderCollapse*: cstring + borderColor*: cstring + borderEndEndRadius*: cstring + borderEndStartRadius*: cstring + borderImage*: cstring + borderImageOutset*: cstring + borderImageRepeat*: cstring + borderImageSlice*: cstring + borderImageSource*: cstring + borderImageWidth*: cstring + borderInline*: cstring + borderInlineColor*: cstring + borderInlineEnd*: cstring + borderInlineEndColor*: cstring + borderInlineEndStyle*: cstring + borderInlineEndWidth*: cstring + borderInlineStart*: cstring + borderInlineStartColor*: cstring + borderInlineStartStyle*: cstring + borderInlineStartWidth*: cstring + borderInlineStyle*: cstring + borderInlineWidth*: cstring + borderLeft*: cstring + borderLeftColor*: cstring + borderLeftStyle*: cstring + borderLeftWidth*: cstring + borderRadius*: cstring + borderRight*: cstring + borderRightColor*: cstring + borderRightStyle*: cstring + borderRightWidth*: cstring + borderSpacing*: cstring + borderStartEndRadius*: cstring + borderStartStartRadius*: cstring + borderStyle*: cstring + borderTop*: cstring + borderTopColor*: cstring + borderTopLeftRadius*: cstring + borderTopRightRadius*: cstring + borderTopStyle*: cstring + borderTopWidth*: cstring + borderWidth*: cstring + bottom*: cstring + boxDecorationBreak*: cstring + boxShadow*: cstring + boxSizing*: cstring + breakAfter*: cstring + breakBefore*: cstring + breakInside*: cstring + captionSide*: cstring + caretColor*: cstring + clear*: cstring + clip*: cstring + clipPath*: cstring + color*: cstring + colorAdjust*: cstring + columnCount*: cstring + columnFill*: cstring + columnGap*: cstring + columnRule*: cstring + columnRuleColor*: cstring + columnRuleStyle*: cstring + columnRuleWidth*: cstring + columnSpan*: cstring + columnWidth*: cstring + columns*: cstring + contain*: cstring + content*: cstring + counterIncrement*: cstring + counterReset*: cstring + counterSet*: cstring + cursor*: cstring + direction*: cstring + display*: cstring + emptyCells*: cstring + filter*: cstring + flex*: cstring + flexBasis*: cstring + flexDirection*: cstring + flexFlow*: cstring + flexGrow*: cstring + flexShrink*: cstring + flexWrap*: cstring + cssFloat*: cstring + font*: cstring + fontFamily*: cstring + fontFeatureSettings*: cstring + fontKerning*: cstring + fontLanguageOverride*: cstring + fontOpticalSizing*: cstring + fontSize*: cstring + fontSizeAdjust*: cstring + fontStretch*: cstring + fontStyle*: cstring + fontSynthesis*: cstring + fontVariant*: cstring + fontVariantAlternates*: cstring + fontVariantCaps*: cstring + fontVariantEastAsian*: cstring + fontVariantLigatures*: cstring + fontVariantNumeric*: cstring + fontVariantPosition*: cstring + fontVariationSettings*: cstring + fontWeight*: cstring + gap*: cstring + grid*: cstring + gridArea*: cstring + gridAutoColumns*: cstring + gridAutoFlow*: cstring + gridAutoRows*: cstring + gridColumn*: cstring + gridColumnEnd*: cstring + gridColumnStart*: cstring + gridRow*: cstring + gridRowEnd*: cstring + gridRowStart*: cstring + gridTemplate*: cstring + gridTemplateAreas*: cstring + gridTemplateColumns*: cstring + gridTemplateRows*: cstring + hangingPunctuation*: cstring + height*: cstring + hyphens*: cstring + imageOrientation*: cstring + imageRendering*: cstring + inlineSize*: cstring + inset*: cstring + insetBlock*: cstring + insetBlockEnd*: cstring + insetBlockStart*: cstring + insetInline*: cstring + insetInlineEnd*: cstring + insetInlineStart*: cstring + isolation*: cstring + justifyContent*: cstring + justifyItems*: cstring + justifySelf*: cstring + left*: cstring + letterSpacing*: cstring + lineBreak*: cstring + lineHeight*: cstring + listStyle*: cstring + listStyleImage*: cstring + listStylePosition*: cstring + listStyleType*: cstring + margin*: cstring + marginBlock*: cstring + marginBlockEnd*: cstring + marginBlockStart*: cstring + marginBottom*: cstring + marginInline*: cstring + marginInlineEnd*: cstring + marginInlineStart*: cstring + marginLeft*: cstring + marginRight*: cstring + marginTop*: cstring + mask*: cstring + maskBorder*: cstring + maskBorderMode*: cstring + maskBorderOutset*: cstring + maskBorderRepeat*: cstring + maskBorderSlice*: cstring + maskBorderSource*: cstring + maskBorderWidth*: cstring + maskClip*: cstring + maskComposite*: cstring + maskImage*: cstring + maskMode*: cstring + maskOrigin*: cstring + maskPosition*: cstring + maskRepeat*: cstring + maskSize*: cstring + maskType*: cstring + maxBlockSize*: cstring + maxHeight*: cstring + maxInlineSize*: cstring + maxWidth*: cstring + minBlockSize*: cstring + minHeight*: cstring + minInlineSize*: cstring + minWidth*: cstring + mixBlendMode*: cstring + objectFit*: cstring + objectPosition*: cstring + offset*: cstring + offsetAnchor*: cstring + offsetDistance*: cstring + offsetPath*: cstring + offsetRotate*: cstring + opacity*: cstring + order*: cstring + orphans*: cstring + outline*: cstring + outlineColor*: cstring + outlineOffset*: cstring + outlineStyle*: cstring + outlineWidth*: cstring + overflow*: cstring + overflowAnchor*: cstring + overflowBlock*: cstring + overflowInline*: cstring + overflowWrap*: cstring + overflowX*: cstring + overflowY*: cstring + overscrollBehavior*: cstring + overscrollBehaviorBlock*: cstring + overscrollBehaviorInline*: cstring + overscrollBehaviorX*: cstring + overscrollBehaviorY*: cstring + padding*: cstring + paddingBlock*: cstring + paddingBlockEnd*: cstring + paddingBlockStart*: cstring + paddingBottom*: cstring + paddingInline*: cstring + paddingInlineEnd*: cstring + paddingInlineStart*: cstring + paddingLeft*: cstring + paddingRight*: cstring + paddingTop*: cstring + pageBreakAfter*: cstring + pageBreakBefore*: cstring + pageBreakInside*: cstring + paintOrder*: cstring + perspective*: cstring + perspectiveOrigin*: cstring + placeContent*: cstring + placeItems*: cstring + placeSelf*: cstring + pointerEvents*: cstring + position*: cstring + quotes*: cstring + resize*: cstring + right*: cstring + rotate*: cstring + rowGap*: cstring + scale*: cstring + scrollBehavior*: cstring + scrollMargin*: cstring + scrollMarginBlock*: cstring + scrollMarginBlockEnd*: cstring + scrollMarginBlockStart*: cstring + scrollMarginBottom*: cstring + scrollMarginInline*: cstring + scrollMarginInlineEnd*: cstring + scrollMarginInlineStart*: cstring + scrollMarginLeft*: cstring + scrollMarginRight*: cstring + scrollMarginTop*: cstring + scrollPadding*: cstring + scrollPaddingBlock*: cstring + scrollPaddingBlockEnd*: cstring + scrollPaddingBlockStart*: cstring + scrollPaddingBottom*: cstring + scrollPaddingInline*: cstring + scrollPaddingInlineEnd*: cstring + scrollPaddingInlineStart*: cstring + scrollPaddingLeft*: cstring + scrollPaddingRight*: cstring + scrollPaddingTop*: cstring + scrollSnapAlign*: cstring + scrollSnapStop*: cstring + scrollSnapType*: cstring + scrollbar3dLightColor*: cstring + scrollbarArrowColor*: cstring + scrollbarBaseColor*: cstring + scrollbarColor*: cstring + scrollbarDarkshadowColor*: cstring + scrollbarFaceColor*: cstring + scrollbarHighlightColor*: cstring + scrollbarShadowColor*: cstring + scrollbarTrackColor*: cstring + scrollbarWidth*: cstring + shapeImageThreshold*: cstring + shapeMargin*: cstring + shapeOutside*: cstring + tabSize*: cstring + tableLayout*: cstring + textAlign*: cstring + textAlignLast*: cstring + textCombineUpright*: cstring + textDecoration*: cstring + textDecorationColor*: cstring + textDecorationLine*: cstring + textDecorationSkipInk*: cstring + textDecorationStyle*: cstring + textDecorationThickness*: cstring + textEmphasis*: cstring + textEmphasisColor*: cstring + textEmphasisPosition*: cstring + textEmphasisStyle*: cstring + textIndent*: cstring + textJustify*: cstring + textOrientation*: cstring + textOverflow*: cstring + textRendering*: cstring + textShadow*: cstring + textTransform*: cstring + textUnderlineOffset*: cstring + textUnderlinePosition*: cstring + top*: cstring + touchAction*: cstring + transform*: cstring + transformBox*: cstring + transformOrigin*: cstring + transformStyle*: cstring + transition*: cstring + transitionDelay*: cstring + transitionDuration*: cstring + transitionProperty*: cstring + transitionTimingFunction*: cstring + translate*: cstring + unicodeBidi*: cstring + verticalAlign*: cstring + visibility*: cstring + whiteSpace*: cstring + widows*: cstring + width*: cstring + willChange*: cstring + wordBreak*: cstring + wordSpacing*: cstring + writingMode*: cstring + zIndex*: cstring + + EventPhase* = enum + None = 0, + CapturingPhase, + AtTarget, + BubblingPhase + + Event* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_ + bubbles*: bool + cancelBubble*: bool + cancelable*: bool + composed*: bool + currentTarget*: Node + defaultPrevented*: bool + eventPhase*: int + target*: Node + `type`*: cstring + isTrusted*: bool + + UIEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_ + detail*: int64 + view*: Window + + KeyboardEvent* {.importc.} = ref object of UIEvent ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent>`_ + altKey*, ctrlKey*, metaKey*, shiftKey*: bool + code*: cstring + isComposing*: bool + key*: cstring + keyCode*: int + location*: int + + KeyboardEventKey* {.pure.} = enum ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values>`_ + # Modifier keys + Alt, + AltGraph, + CapsLock, + Control, + Fn, + FnLock, + Hyper, + Meta, + NumLock, + ScrollLock, + Shift, + Super, + Symbol, + SymbolLock, + + # Whitespace keys + ArrowDown, + ArrowLeft, + ArrowRight, + ArrowUp, + End, + Home, + PageDown, + PageUp, + + # Editing keys + Backspace, + Clear, + Copy, + CrSel, + Cut, + Delete, + EraseEof, + ExSel, + Insert, + Paste, + Redo, + Undo, + + # UI keys + Accept, + Again, + Attn, + Cancel, + ContextMenu, + Escape, + Execute, + Find, + Finish, + Help, + Pause, + Play, + Props, + Select, + ZoomIn, + ZoomOut, + + # Device keys + BrigtnessDown, + BrigtnessUp, + Eject, + LogOff, + Power, + PowerOff, + PrintScreen, + Hibernate, + Standby, + WakeUp, + + # Common IME keys + AllCandidates, + Alphanumeric, + CodeInput, + Compose, + Convert, + Dead, + FinalMode, + GroupFirst, + GroupLast, + GroupNext, + GroupPrevious, + ModeChange, + NextCandidate, + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + + # Korean keyboards only + HangulMode, + HanjaMode, + JunjaMode, + + # Japanese keyboards only + Eisu, + Hankaku, + Hiragana, + HiraganaKatakana, + KanaMode, + KanjiMode, + Katakana, + Romaji, + Zenkaku, + ZenkakuHanaku, + + # Function keys + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + Soft1, + Soft2, + Soft3, + Soft4, + + # Phone keys + AppSwitch, + Call, + Camera, + CameraFocus, + EndCall, + GoBack, + GoHome, + HeadsetHook, + LastNumberRedial, + Notification, + MannerMode, + VoiceDial, + + # Multimedia keys + ChannelDown, + ChannelUp, + MediaFastForward, + MediaPause, + MediaPlay, + MediaPlayPause, + MediaRecord, + MediaRewind, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + + # Audio control keys + AudioBalanceLeft, + AudioBalanceRight, + AudioBassDown, + AudioBassBoostDown, + AudioBassBoostToggle, + AudioBassBoostUp, + AudioBassUp, + AudioFaderFront, + AudioFaderRear, + AudioSurroundModeNext, + AudioTrebleDown, + AudioTrebleUp, + AudioVolumeDown, + AUdioVolumeMute, + AudioVolumeUp, + MicrophoneToggle, + MicrophoneVolumeDown, + MicrophoneVolumeMute, + MicrophoneVolumeUp, + + # TV control keys + TV, + TV3DMode, + TVAntennaCable, + TVAudioDescription, + TVAudioDescriptionMixDown, + TVAudioDescriptionMixUp, + TVContentsMenu, + TVDataService, + TVInput, + TVInputComponent1, + TVInputComponent2, + TVInputComposite1, + TVInputComposite2, + TVInputHDMI1, + TVInputHDMI2, + TVInputHDMI3, + TVInputHDMI4, + TVInputVGA1, + TVMediaContext, + TVNetwork, + TVNumberEntry, + TVPower, + TVRadioService, + TVSatellite, + TVSatelliteBS, + TVSatelliteCS, + TVSatelliteToggle, + TVTerrestrialAnalog, + TVTerrestrialDigital, + TVTimer, + + # Media controller keys + AVRInput, + AVRPower, + ColorF0Red, + ColorF1Green, + ColorF2Yellow, + ColorF3Blue, + ColorF4Grey, + ColorF5Brown, + ClosedCaptionToggle, + Dimmer, + DisplaySwap, + DVR, + Exit, + FavoriteClear0, + FavoriteClear1, + FavoriteClear2, + FavoriteClear3, + FavoriteRecall0, + FavoriteRecall1, + FavoriteRecall2, + FavoriteRecall3, + FavoriteStore0, + FavoriteStore1, + FavoriteStore2, + FavoriteStore3, + Guide, + GuideNextDay, + GuidePreviousDay, + Info, + InstantReplay, + Link, + ListProgram, + LiveContent, + Lock, + MediaApps, + MediaAudioTrack, + MediaLast, + MediaSkipBackward, + MediaSkipForward, + MediaStepBackward, + MediaStepForward, + MediaTopMenu, + NavigateIn, + NavigateNext, + NavigateOut, + NavigatePrevious, + NextFavoriteChannel, + NextUserProfile, + OnDemand, + Pairing, + PinPDown, + PinPMove, + PinPUp, + PlaySpeedDown, + PlaySpeedReset, + PlaySpeedUp, + RandomToggle, + RcLowBattery, + RecordSpeedNext, + RfBypass, + ScanChannelsToggle, + ScreenModeNext, + Settings, + SplitScreenToggle, + STBInput, + STBPower, + Subtitle, + Teletext, + VideoModeNext, + Wink, + ZoomToggle, + + # Speech recognition keys + SpeechCorrectionList, + SpeechInputToggle, + + # Document keys + Close, + New, + Open, + Print, + Save, + SpellCheck, + MailForward, + MailReply, + MailSend, + + # Application selector keys + LaunchCalculator, + LaunchCalendar, + LaunchContacts, + LaunchMail, + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchMyComputer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + LaunchApplication1, + LaunchApplication2, + LaunchApplication3, + LaunchApplication4, + LaunchApplication5, + LaunchApplication6, + LaunchApplication7, + LaunchApplication8, + LaunchApplication9, + LaunchApplication10, + LaunchApplication11, + LaunchApplication12, + LaunchApplication13, + LaunchApplication14, + LaunchApplication15, + LaunchApplication16, + + # Browser control keys + BrowserBack, + BrowserFavorites, + BrowserForward, + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + + # Numeric keypad keys + Key11, + Key12, + Separator + + MouseButtons* = enum + NoButton = 0, + PrimaryButton = 1, + SecondaryButton = 2, + AuxilaryButton = 4, + FourthButton = 8, + FifthButton = 16 + + MouseEvent* {.importc.} = ref object of UIEvent ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent>`_ + altKey*, ctrlKey*, metaKey*, shiftKey*: bool + button*: int + buttons*: int + clientX*, clientY*: int + movementX*, movementY*: int + offsetX*, offsetY*: int + pageX*, pageY*: int + relatedTarget*: EventTarget + #region*: cstring + screenX*, screenY*: int + x*, y*: int + + DataTransferItemKind* {.pure.} = enum + File = "file", + String = "string" + + DataTransferItem* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_ + kind*: cstring + `type`*: cstring + + DataTransfer* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer>`_ + dropEffect*: cstring + effectAllowed*: cstring + files*: seq[Element] + items*: seq[DataTransferItem] + types*: seq[cstring] + + DataTransferDropEffect* {.pure.} = enum + None = "none", + Copy = "copy", + Link = "link", + Move = "move" + + DataTransferEffectAllowed* {.pure.} = enum + None = "none", + Copy = "copy", + CopyLink = "copyLink", + CopyMove = "copyMove", + Link = "link", + LinkMove = "linkMove", + Move = "move", + All = "all", + Uninitialized = "uninitialized" + + DragEventTypes* = enum + Drag = "drag", + DragEnd = "dragend", + DragEnter = "dragenter", + DragExit = "dragexit", + DragLeave = "dragleave", + DragOver = "dragover", + DragStart = "dragstart", + Drop = "drop" + + DragEvent* {.importc.} = object of MouseEvent + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DragEvent>`_ + dataTransfer*: DataTransfer + + ClipboardEvent* {.importc.} = object of Event + ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent>`_ + clipboardData*: DataTransfer + + StorageEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent>`_ + key*: cstring + newValue*, oldValue*: cstring + storageArea*: Storage + url*: cstring + + TouchList* {.importc.} = ref object of RootObj + length*: int + + Touch* {.importc.} = ref object of RootObj + identifier*: int + screenX*, screenY*, clientX*, clientY*, pageX*, pageY*: int + target*: Element + radiusX*, radiusY*: int + rotationAngle*: int + force*: float + + TouchEvent* {.importc.} = ref object of UIEvent + changedTouches*, targetTouches*, touches*: seq[Touch] + + Location* {.importc.} = ref object of RootObj + hash*: cstring + host*: cstring + hostname*: cstring + href*: cstring + pathname*: cstring + port*: cstring + protocol*: cstring + search*: cstring + origin*: cstring + + History* {.importc.} = ref object of RootObj + length*: int + + Navigator* {.importc.} = ref object of RootObj + appCodeName*: cstring + appName*: cstring + appVersion*: cstring + buildID*: cstring ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/buildID + cookieEnabled*: bool + deviceMemory*: float ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/deviceMemory + doNotTrack*: cstring ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/doNotTrack + language*: cstring + languages*: seq[cstring] ## https://developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/languages + maxTouchPoints*: cint ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/maxTouchPoints + onLine*: bool ## https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/onLine + oscpu*: cstring ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/oscpu + platform*: cstring + userAgent*: cstring + vendor*: cstring ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vendor + webdriver*: bool ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/webdriver + mimeTypes*: seq[ref MimeType] + + Plugin* {.importc.} = object of RootObj + description*: cstring + filename*: cstring + name*: cstring + + MimeType* {.importc.} = object of RootObj + description*: cstring + enabledPlugin*: ref Plugin + suffixes*: seq[cstring] + `type`*: cstring + + LocationBar* {.importc.} = object of RootObj + visible*: bool + MenuBar* = LocationBar + PersonalBar* = LocationBar + ScrollBars* = LocationBar + ToolBar* = LocationBar + StatusBar* = LocationBar + + Screen* {.importc.} = ref object of RootObj + availHeight*: int + availWidth*: int + colorDepth*: int + height*: int + pixelDepth*: int + width*: int + + TimeOut* {.importc.} = ref object of RootObj + Interval* {.importc.} = ref object of RootObj + + AddEventListenerOptions* = object + capture*: bool + once*: bool + passive*: bool + + FontFaceSetReady* {.importc.} = ref object + ## see: `docs<https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready>`_ + then*: proc(cb: proc()) + + FontFaceSet* {.importc.} = ref object + ## see: `docs<https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet>`_ + ready*: FontFaceSetReady + onloadingdone*: proc(event: Event) + + ScrollIntoViewOptions* = object + behavior*: cstring + `block`*: cstring + inline*: cstring + + MediaQueryList* {.importc.} = ref object of EventTarget + matches*: bool + media*: cstring + +since (1, 3): + type + DomParser* = ref object + ## DOM Parser object (defined on browser only, may not be on NodeJS). + ## * https://developer.mozilla.org/en-US/docs/Web/API/DOMParser + ## + ## ```nim + ## let prsr = newDomParser() + ## discard prsr.parseFromString("<html><marquee>Hello World</marquee></html>".cstring, "text/html".cstring) + ## ``` + + DomException* {.importc.} = ref object + ## The DOMException interface represents an abnormal event (called an exception) + ## which occurs as a result of calling a method or accessing a property of a web API. + ## Each exception has a name, which is a short "CamelCase" style string identifying + ## the error or abnormal condition. + ## https://developer.mozilla.org/en-US/docs/Web/API/DOMException + + FileReader* {.importc.} = ref object of EventTarget + ## The FileReader object lets web applications asynchronously read the contents of files + ## (or raw data buffers) stored on the user's computer, using File or Blob objects to specify + ## the file or data to read. + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader + + FileReaderState* = distinct range[0'u16..2'u16] + RootNodeOptions* = object of RootObj + composed*: bool + DocumentOrShadowRoot* {.importc.} = object of RootObj + activeElement*: Element + # styleSheets*: StyleSheetList + ShadowRoot* {.importc.} = ref object of DocumentOrShadowRoot + delegatesFocus*: bool + host*: Element + innerHTML*: cstring + mode*: cstring # "open" or "closed" + ShadowRootInit* = object of RootObj + mode*: cstring + delegatesFocus*: bool + + HTMLSlotElement* {.importc.} = ref object of RootObj + name*: cstring + SlotOptions* = object of RootObj + flatten*: bool + + const + fileReaderEmpty* = 0.FileReaderState + fileReaderLoading* = 1.FileReaderState + fileReaderDone* = 2.FileReaderState + +proc id*(n: Node): cstring {.importcpp: "#.id", nodecl.} +proc `id=`*(n: Node; x: cstring) {.importcpp: "#.id = #", nodecl.} +proc class*(n: Node): cstring {.importcpp: "#.className", nodecl.} +proc `class=`*(n: Node; v: cstring) {.importcpp: "#.className = #", nodecl.} + +proc value*(n: Node): cstring {.importcpp: "#.value", nodecl.} +proc `value=`*(n: Node; v: cstring) {.importcpp: "#.value = #", nodecl.} + +proc checked*(n: Node): bool {.importcpp: "#.checked", nodecl.} +proc `checked=`*(n: Node; v: bool) {.importcpp: "#.checked = #", nodecl.} + +proc `disabled=`*(n: Node; v: bool) {.importcpp: "#.disabled = #", nodecl.} + +when defined(nodejs): + # we provide a dummy DOM for nodejs for testing purposes + proc len*(x: Node): int = x.childNodes.len + proc `[]`*(x: Node; idx: int): Element = + assert idx >= 0 and idx < x.childNodes.len + result = cast[Element](x.childNodes[idx]) + + var document* = Document(nodeType: DocumentNode) + document.ownerDocument = document + + proc getElem(x: Element; id: cstring): Element = + if x.id == id: return x + for i in 0..<x.len: + result = getElem(x[i], id) + if result != nil: return result + + proc getElementById*(doc: Document; id: cstring): Element = + getElem(doc.body, id) + proc getElementById*(id: cstring): Element = document.getElementById(id) + + proc appendChild*(parent, n: Node) = + n.parentNode = parent + n.ownerDocument = parent.ownerDocument + parent.childNodes.add n + + proc replaceChild*(parent, newNode, oldNode: Node) = + newNode.parentNode = parent + oldNode.parentNode = nil + var i = 0 + while i < parent.len: + if Node(parent[i]) == oldNode: + parent.childNodes[i] = newNode + return + inc i + raiseAssert "old node not in node list" + + proc removeChild*(parent, child: Node) = + child.parentNode = nil + var i = 0 + while i < parent.len: + if Node(parent[i]) == child: + parent.childNodes.delete(i) + return + inc i + raiseAssert "old node not in node list" + + proc insertBefore*(parent, newNode, before: Node) = + appendChild(parent, newNode) + var i = 0 + while i < parent.len-1: + if Node(parent[i]) == before: + for j in countdown(parent.len-1, i-1): + parent.childNodes[j] = parent.childNodes[j-1] + parent.childNodes[i-1] = newNode + return + inc i + #raiseAssert "before not in node list" + + proc createElement*(d: Document, identifier: cstring): Element = + new(result) + result.nodeName = identifier + result.nodeType = NodeType.ElementNode + + proc createTextNode*(d: Document, identifier: cstring): Node = + new(result) + result.nodeName = "#text" + result.nodeValue = identifier + result.nodeType = NodeType.TextNode + + proc createComment*(d: Document, data: cstring): Node = + new(result) + result.nodeName = "#comment" + result.nodeValue = data + result.nodeType = NodeType.CommentNode + +else: + proc len*(x: Node): int {.importcpp: "#.childNodes.length".} + proc `[]`*(x: Node; idx: int): Element {.importcpp: "#.childNodes[#]".} + proc getElementById*(id: cstring): Element {.importc: "document.getElementById", nodecl.} + proc appendChild*(n, child: Node) {.importcpp.} + proc removeChild*(n, child: Node) {.importcpp.} + proc remove*(child: Node) {.importcpp.} + proc replaceChild*(n, newNode, oldNode: Node) {.importcpp.} + proc insertBefore*(n, newNode, before: Node) {.importcpp.} + proc getElementById*(d: Document, id: cstring): Element {.importcpp.} + proc createElement*(d: Document, identifier: cstring): Element {.importcpp.} + proc createElementNS*(d: Document, namespaceURI, qualifiedIdentifier: cstring): Element {.importcpp.} + proc createTextNode*(d: Document, identifier: cstring): Node {.importcpp.} + proc createComment*(d: Document, data: cstring): Node {.importcpp.} + +proc setTimeout*(action: proc(); ms: int): TimeOut {.importc, nodecl.} +proc clearTimeout*(t: TimeOut) {.importc, nodecl.} +proc setInterval*(action: proc(); ms: int): Interval {.importc, nodecl.} +proc clearInterval*(i: Interval) {.importc, nodecl.} + +{.push importcpp.} + +# EventTarget "methods" +proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) +proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions) +proc dispatchEvent*(et: EventTarget, ev: Event) +proc removeEventListener*(et: EventTarget; ev: cstring; cb: proc(ev: Event)) + +# Window "methods" +proc alert*(w: Window, msg: cstring) +proc back*(w: Window) +proc blur*(w: Window) +proc clearInterval*(w: Window, interval: Interval) +proc clearTimeout*(w: Window, timeout: TimeOut) +proc close*(w: Window) +proc confirm*(w: Window, msg: cstring): bool +proc disableExternalCapture*(w: Window) +proc enableExternalCapture*(w: Window) +proc find*(w: Window, text: cstring, caseSensitive = false, + backwards = false): bool +proc focus*(w: Window) +proc forward*(w: Window) +proc getComputedStyle*(w: Window, e: Node, pe: Node = nil): Style + ## .. warning:: The returned Style may or may not be read-only at run-time in the browser. getComputedStyle is performance costly. + +proc handleEvent*(w: Window, e: Event) +proc home*(w: Window) +proc moveBy*(w: Window, x, y: int) +proc moveTo*(w: Window, x, y: int) +proc open*(w: Window, uri, windowname: cstring, + properties: cstring = nil): Window +proc print*(w: Window) +proc prompt*(w: Window, text, default: cstring): cstring +proc resizeBy*(w: Window, x, y: int) +proc resizeTo*(w: Window, x, y: int) +proc routeEvent*(w: Window, event: Event) +proc scrollBy*(w: Window, x, y: int) +proc scrollTo*(w: Window, x, y: int) +proc setInterval*(w: Window, code: cstring, pause: int): Interval +proc setInterval*(w: Window, function: proc (), pause: int): Interval +proc setTimeout*(w: Window, code: cstring, pause: int): TimeOut +proc setTimeout*(w: Window, function: proc (), pause: int): Interval +proc stop*(w: Window) +proc requestAnimationFrame*(w: Window, function: proc (time: float)): int +proc cancelAnimationFrame*(w: Window, id: int) +proc matchMedia*(w: Window, mediaQueryString: cstring): MediaQueryList + +# Node "methods" +proc appendData*(n: Node, data: cstring) +proc cloneNode*(n: Node, copyContent: bool): Node +proc deleteData*(n: Node, start, len: int) +proc focus*(e: Node) +proc getAttribute*(n: Node, attr: cstring): cstring +proc getAttributeNode*(n: Node, attr: cstring): Node +proc hasAttribute*(n: Node, attr: cstring): bool +proc hasChildNodes*(n: Node): bool +proc normalize*(n: Node) +proc insertData*(n: Node, position: int, data: cstring) +proc removeAttribute*(n: Node, attr: cstring) +proc removeAttributeNode*(n, attr: Node) +proc replaceData*(n: Node, start, len: int, text: cstring) +proc scrollIntoView*(n: Node) +proc scrollIntoView*(n: Node, options: ScrollIntoViewOptions) +proc setAttribute*(n: Node, name, value: cstring) +proc setAttributeNode*(n: Node, attr: Node) +proc querySelector*(n: Node, selectors: cstring): Element +proc querySelectorAll*(n: Node, selectors: cstring): seq[Element] +proc compareDocumentPosition*(n: Node, otherNode:Node): int +proc lookupPrefix*(n: Node): cstring +proc lookupNamespaceURI*(n: Node): cstring +proc isDefaultNamespace*(n: Node): bool +proc contains*(n: Node): bool +proc isEqualNode*(n: Node): bool +proc isSameNode*(n: Node): bool + +since (1, 3): + proc getRootNode*(n: Node,options: RootNodeOptions): Node + + # DocumentOrShadowRoot + proc getSelection*(n: DocumentOrShadowRoot): Selection + proc elementFromPoint*(n: DocumentOrShadowRoot; x, y: float): Element + + # shadow dom + proc attachShadow*(n: Element): ShadowRoot + proc assignedNodes*(n: HTMLSlotElement; options: SlotOptions): seq[Node] + proc assignedElements*(n: HTMLSlotElement; options: SlotOptions): seq[Element] + +# Document "methods" +proc createAttribute*(d: Document, identifier: cstring): Node +proc getElementsByName*(d: Document, name: cstring): seq[Element] +proc getElementsByTagName*(d: Document, name: cstring): seq[Element] +proc getElementsByClassName*(d: Document, name: cstring): seq[Element] +proc insertNode*(range: Range, node: Node) +proc getSelection*(d: Document): Selection +proc handleEvent*(d: Document, event: Event) +proc open*(d: Document) +proc routeEvent*(d: Document, event: Event) +proc write*(d: Document, text: cstring) +proc writeln*(d: Document, text: cstring) +proc querySelector*(d: Document, selectors: cstring): Element +proc querySelectorAll*(d: Document, selectors: cstring): seq[Element] + +# Element "methods" +proc blur*(e: Element) +proc click*(e: Element) +proc focus*(e: Element) +proc handleEvent*(e: Element, event: Event) +proc select*(e: Element) +proc getElementsByTagName*(e: Element, name: cstring): seq[Element] +proc getElementsByClassName*(e: Element, name: cstring): seq[Element] + +# FormElement "methods" +proc reset*(f: FormElement) +proc submit*(f: FormElement) +proc checkValidity*(e: FormElement): bool +proc reportValidity*(e: FormElement): bool + +# EmbedElement "methods" +proc play*(e: EmbedElement) +proc stop*(e: EmbedElement) + +# Location "methods" +proc reload*(loc: Location) +proc replace*(loc: Location, s: cstring) + +# History "methods" +proc back*(h: History) +proc forward*(h: History) +proc go*(h: History, pagesToJump: int) +proc pushState*[T](h: History, stateObject: T, title, url: cstring) + +# Navigator "methods" +proc javaEnabled*(h: Navigator): bool +since (1, 3): + proc canShare*(self: Navigator; data: cstring): bool ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/canShare + proc sendBeacon*(self: Navigator; url, data: cstring): bool ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon + proc vibrate*(self: Navigator; pattern: cint): bool ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate + proc vibrate*(self: Navigator; pattern: openArray[cint]): bool ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vibrate + proc registerProtocolHandler*(self: Navigator; scheme, url, title: cstring) ## https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler + +# ClassList "methods" +proc add*(c: ClassList, class: cstring) +proc remove*(c: ClassList, class: cstring) +proc contains*(c: ClassList, class: cstring): bool +proc toggle*(c: ClassList, class: cstring) + +# Style "methods" +proc getPropertyValue*(s: Style, property: cstring): cstring +proc removeProperty*(s: Style, property: cstring) +proc setProperty*(s: Style, property, value: cstring, priority = "") +proc getPropertyPriority*(s: Style, property: cstring): cstring + +# Event "methods" +proc preventDefault*(ev: Event) +proc stopImmediatePropagation*(ev: Event) +proc stopPropagation*(ev: Event) + +# KeyboardEvent "methods" +proc getModifierState*(ev: KeyboardEvent, keyArg: cstring): bool + +# MouseEvent "methods" +proc getModifierState*(ev: MouseEvent, keyArg: cstring): bool + +# TouchEvent "methods" +proc identifiedTouch*(list: TouchList): Touch +proc item*(list: TouchList, i: int): Touch + +# DataTransfer "methods" +proc clearData*(dt: DataTransfer, format: cstring) +proc getData*(dt: DataTransfer, format: cstring): cstring +proc setData*(dt: DataTransfer, format: cstring, data: cstring) +proc setDragImage*(dt: DataTransfer, img: Element, xOffset: int, yOffset: int) + +# DataTransferItem "methods" +proc getAsFile*(dti: DataTransferItem): File + +# InputElement "methods" +proc setSelectionRange*(e: InputElement, selectionStart: int, selectionEnd: int, selectionDirection: cstring = "none") +proc setRangeText*(e: InputElement, replacement: cstring, startindex: int = 0, endindex: int = 0, selectionMode: cstring = "preserve") +proc setCustomValidity*(e: InputElement, error: cstring) +proc checkValidity*(e: InputElement): bool + +# Blob "methods" +proc slice*(e: Blob, startindex: int = 0, endindex: int = e.size, contentType: cstring = "") + +# Performance "methods" +proc now*(p: Performance): float + +# Selection "methods" +proc removeAllRanges*(s: Selection) +proc deleteFromDocument*(s: Selection) +proc getRangeAt*(s: Selection, index: int): Range +converter toString*(s: Selection): cstring +proc `$`*(s: Selection): string = $(s.toString()) + +# Storage "methods" +proc getItem*(s: Storage, key: cstring): cstring +proc setItem*(s: Storage, key, value: cstring) +proc clear*(s: Storage) +proc removeItem*(s: Storage, key: cstring) + +{.pop.} + +proc setAttr*(n: Node; key, val: cstring) {.importcpp: "#.setAttribute(@)".} + +var + window* {.importc, nodecl.}: Window + navigator* {.importc, nodecl.}: Navigator + screen* {.importc, nodecl.}: Screen + +when not defined(nodejs): + var document* {.importc, nodecl.}: Document + +proc decodeURI*(uri: cstring): cstring {.importc, nodecl.} +proc encodeURI*(uri: cstring): cstring {.importc, nodecl.} + +proc escape*(uri: cstring): cstring {.importc, nodecl.} +proc unescape*(uri: cstring): cstring {.importc, nodecl.} + +proc decodeURIComponent*(uri: cstring): cstring {.importc, nodecl.} +proc encodeURIComponent*(uri: cstring): cstring {.importc, nodecl.} +proc isFinite*(x: BiggestFloat): bool {.importc, nodecl.} +proc isNaN*(x: BiggestFloat): bool {.importc, nodecl.} + ## see also `math.isNaN`. + +proc newEvent*(name: cstring): Event {.importcpp: "new Event(@)", constructor.} + +proc getElementsByClass*(n: Node; name: cstring): seq[Node] {. + importcpp: "#.getElementsByClassName(#)", nodecl.} + + +type + BoundingRect* {.importc.} = object + top*, bottom*, left*, right*, x*, y*, width*, height*: float + +proc getBoundingClientRect*(e: Node): BoundingRect {. + importcpp: "getBoundingClientRect", nodecl.} +proc clientHeight*(): int {. + importcpp: "(window.innerHeight || document.documentElement.clientHeight)@", nodecl.} +proc clientWidth*(): int {. + importcpp: "(window.innerWidth || document.documentElement.clientWidth)@", nodecl.} + +proc inViewport*(el: Node): bool = + let rect = el.getBoundingClientRect() + result = rect.top >= 0 and rect.left >= 0 and + rect.bottom <= clientHeight().float and + rect.right <= clientWidth().float + +proc scrollTop*(e: Node): int {.importcpp: "#.scrollTop", nodecl.} +proc `scrollTop=`*(e: Node, value: int) {.importcpp: "#.scrollTop = #", nodecl.} +proc scrollLeft*(e: Node): int {.importcpp: "#.scrollLeft", nodecl.} +proc scrollHeight*(e: Node): int {.importcpp: "#.scrollHeight", nodecl.} +proc scrollWidth*(e: Node): int {.importcpp: "#.scrollWidth", nodecl.} +proc offsetHeight*(e: Node): int {.importcpp: "#.offsetHeight", nodecl.} +proc offsetWidth*(e: Node): int {.importcpp: "#.offsetWidth", nodecl.} +proc offsetTop*(e: Node): int {.importcpp: "#.offsetTop", nodecl.} +proc offsetLeft*(e: Node): int {.importcpp: "#.offsetLeft", nodecl.} + +since (1, 3): + func newDomParser*(): DomParser {.importcpp: "new DOMParser()".} + ## DOM Parser constructor. + func parseFromString*(this: DomParser; str: cstring; mimeType: cstring): Document {.importcpp.} + ## Parse from string to `Document`. + + proc newDomException*(): DomException {.importcpp: "new DomException()", constructor.} + ## DOM Exception constructor + proc message*(ex: DomException): cstring {.importcpp: "#.message", nodecl.} + ## https://developer.mozilla.org/en-US/docs/Web/API/DOMException/message + proc name*(ex: DomException): cstring {.importcpp: "#.name", nodecl.} + ## https://developer.mozilla.org/en-US/docs/Web/API/DOMException/name + + proc newFileReader*(): FileReader {.importcpp: "new FileReader()", constructor.} + ## File Reader constructor + proc error*(f: FileReader): DomException {.importcpp: "#.error", nodecl.} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/error + proc readyState*(f: FileReader): FileReaderState {.importcpp: "#.readyState", nodecl.} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readyState + proc resultAsString*(f: FileReader): cstring {.importcpp: "#.result", nodecl.} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/result + proc abort*(f: FileReader) {.importcpp: "#.abort()".} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/abort + proc readAsBinaryString*(f: FileReader, b: Blob) {.importcpp: "#.readAsBinaryString(#)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString + proc readAsDataURL*(f: FileReader, b: Blob) {.importcpp: "#.readAsDataURL(#)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL + proc readAsText*(f: FileReader, b: Blob|File, encoding = cstring"UTF-8") {.importcpp: "#.readAsText(#, #)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsText + +since (1, 5): + proc elementsFromPoint*(n: DocumentOrShadowRoot; x, y: float): seq[Element] {.importcpp.} + + +since (1, 7): + + proc insertAdjacentText*(self: Node; position, data: cstring) {.importjs: "#.$1(#, #)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentText + + proc insertAdjacentElement*(self: Node; position: cstring; element: Node) {.importjs: "#.$1(#, #)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement + + proc insertAdjacentHTML*(self: Node; position, html: cstring) {.importjs: "#.$1(#, #)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML + + proc after*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/after + + proc before*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/before + + proc append*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/append + + proc closest*(self: Node; cssSelector: cstring): Node {.importjs: "#.$1(#)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/closest + + proc hasAttributeNS*(self: Node; namespace, localName: cstring): bool {.importjs: "(#.$1(#, #) || false)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttributeNS + + proc removeAttributeNS*(self: Node; namespace, attributeName: cstring) {.importjs: "#.$1(#, #)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttributeNS + + proc hasPointerCapture*(self: Node; pointerId: SomeNumber): bool {.importjs: "(#.$1(#) || false)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/hasPointerCapture + + proc releasePointerCapture*(self: Node; pointerId: SomeNumber) {.importjs: "#.$1(#)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/releasePointerCapture + + proc requestPointerLock*(self: Node) {.importjs: "#.$1()".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/requestPointerLock + + proc replaceChildren*(self: Node; replacements: Node) {.importjs: "#.$1(@)", varargs.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren + + proc replaceWith*(self: Node; replacements: Node) {.importjs: "#.$1(@)", varargs.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith + + proc scrollIntoViewIfNeeded*(self: Node; centerIfNeeded: bool) {.importjs: "#.$1(#)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded + + proc setHTML*(self: Node; html: cstring) {.importjs: "#.$1(#)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML + + proc toggleAttribute*(self: Node; name: cstring; force = false): bool {.importjs: "(#.$1(#, #) || false)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute + + proc matches*(self: Node; cssSelector: cstring): bool {.importjs: "(#.$1(#) || false)".} + ## https://developer.mozilla.org/en-US/docs/Web/API/Element/matches + + +since (2, 1): + type VisualViewport* {.importc.} = ref object of EventTarget + offsetLeft*, offsetTop*, pageLeft*, pageTop*, width*, height*, scale*: float + onResize*, onScroll*: proc (event: Event) {.closure.} + + func visualViewport*(self: Window): VisualViewport {.importjs: "#.$1", nodecl.} diff --git a/lib/js/jsconsole.nim b/lib/js/jsconsole.nim new file mode 100644 index 000000000..e74127334 --- /dev/null +++ b/lib/js/jsconsole.nim @@ -0,0 +1,125 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Wrapper for the `console` object for the `JavaScript backend +## <backends.html#backends-the-javascript-target>`_. +## +## Styled Messages +## =============== +## +## CSS-styled messages in the browser are useful for debugging purposes. +## To use them, prefix the message with one or more `%c`, +## and provide the CSS style as the last argument. +## The amount of `%c`'s must match the amount of CSS-styled strings. +## +runnableExamples("-r:off"): + console.log "%c My Debug Message", "color: red" # Notice the "%c" + console.log "%c My Debug %c Message", "color: red", "font-size: 2em" + +import std/private/since, std/private/miscdollars # toLocation + +when not defined(js): + {.error: "This module only works on the JavaScript platform".} + +type Console* = ref object of JsRoot + +proc log*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/log + +proc debug*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/debug + +proc info*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/info + +proc error*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/error + +template exception*(console: Console, args: varargs[untyped]) = + ## Alias for `console.error()`. + error(console, args) + +proc trace*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/trace + +proc warn*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/warn + +proc clear*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/clear + +proc count*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/count + +proc countReset*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/countReset + +proc group*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/group + +proc groupCollapsed*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Console/groupCollapsed + +proc groupEnd*(console: Console) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/groupEnd + +proc time*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/time + +proc timeEnd*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/timeEnd + +proc timeLog*(console: Console, label = "".cstring) {.importcpp.} + ## https://developer.mozilla.org/docs/Web/API/Console/timeLog + +proc table*(console: Console) {.importcpp, varargs.} + ## https://developer.mozilla.org/docs/Web/API/Console/table + +since (1, 5): + type InstantiationInfo = tuple[filename: string, line: int, column: int] + + func getMsg(info: InstantiationInfo; msg: string): string = + var temp = "" + temp.toLocation(info.filename, info.line, info.column + 1) + result.addQuoted("[jsAssert] " & temp) + result.add ',' + result.addQuoted(msg) + + template jsAssert*(console: Console; assertion) = + ## JavaScript `console.assert`, for NodeJS this prints to stderr, + ## assert failure just prints to console and do not quit the program, + ## this is not meant to be better or even equal than normal assertions, + ## is just for when you need faster performance *and* assertions, + ## otherwise use the normal assertions for better user experience. + ## https://developer.mozilla.org/en-US/docs/Web/API/Console/assert + runnableExamples: + console.jsAssert(42 == 42) # OK + console.jsAssert(42 != 42) # Fail, prints "Assertion failed" and continues + console.jsAssert('`' == '\n' and '\t' == '\0') # Message correctly formatted + assert 42 == 42 # Normal assertions keep working + + const + loc = instantiationInfo(fullPaths = compileOption("excessiveStackTrace")) + msg = getMsg(loc, astToStr(assertion)).cstring + {.line: loc.}: + {.emit: ["console.assert(", assertion, ", ", msg, ");"].} + + func dir*(console: Console; obj: auto) {.importcpp.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Console/dir + + func dirxml*(console: Console; obj: auto) {.importcpp.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Console/dirxml + + func timeStamp*(console: Console; label: cstring) {.importcpp.} + ## https://developer.mozilla.org/en-US/docs/Web/API/Console/timeStamp + ## + ## ..warning:: non-standard + + +var console* {.importc, nodecl.}: Console diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim new file mode 100644 index 000000000..be353875c --- /dev/null +++ b/lib/js/jscore.nim @@ -0,0 +1,153 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module wraps core JavaScript functions. +## +## Unless your application has very +## specific requirements and solely targets JavaScript, you should be using +## the relevant functions in the `math`, `json`, and `times` stdlib +## modules instead. +import std/private/[since, jsutils] + +when not defined(js): + {.error: "This module only works on the JavaScript platform".} + +type + MathLib* = ref object + JsonLib* = ref object + DateLib* = ref object + DateTime* = ref object + +var + Math* {.importc, nodecl.}: MathLib + Date* {.importc, nodecl.}: DateLib + JSON* {.importc, nodecl.}: JsonLib + +# Math library +proc abs*(m: MathLib, a: SomeNumber): SomeNumber {.importcpp.} +proc acos*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc acosh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc asin*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc asinh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc atan*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc atan2*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc atanh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc cbrt*(m: MathLib, f: SomeFloat): SomeFloat {.importcpp.} +proc ceil*(m: MathLib, f: SomeFloat): SomeFloat {.importcpp.} +proc clz32*(m: MathLib, f: SomeInteger): int {.importcpp.} +proc cos*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc cosh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc exp*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc expm1*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc floor*(m: MathLib, f: SomeFloat): int {.importcpp.} +proc fround*(m: MathLib, f: SomeFloat): float32 {.importcpp.} +proc hypot*(m: MathLib, args: varargs[distinct SomeNumber]): float {.importcpp.} +proc imul*(m: MathLib, a, b: int32): int32 {.importcpp.} +proc log*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc log10*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc log1p*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc log2*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc max*(m: MathLib, a, b: SomeNumber): SomeNumber {.importcpp.} +proc min*[T: SomeNumber | JsRoot](m: MathLib, a, b: T): T {.importcpp.} +proc pow*(m: MathLib, a, b: distinct SomeNumber): float {.importcpp.} +proc random*(m: MathLib): float {.importcpp.} +proc round*(m: MathLib, f: SomeFloat): int {.importcpp.} +proc sign*(m: MathLib, f: SomeNumber): int {.importcpp.} +proc sin*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc sinh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc sqrt*(m: MathLib, f: SomeFloat): SomeFloat {.importcpp.} +proc tan*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc tanh*(m: MathLib, a: SomeNumber): float {.importcpp.} +proc trunc*(m: MathLib, f: SomeFloat): int {.importcpp.} + +# Date library +proc now*(d: DateLib): int {.importcpp.} +proc UTC*(d: DateLib): int {.importcpp.} +proc parse*(d: DateLib, s: cstring): int {.importcpp.} + +proc newDate*(): DateTime {. + importcpp: "new Date()".} + +proc newDate*(date: int|string): DateTime {. + importcpp: "new Date(#)".} + +whenJsNoBigInt64: + proc newDate*(date: int64): DateTime {. + importcpp: "new Date(#)".} +do: + proc newDate*(date: int64): DateTime {. + importcpp: "new Date(Number(#))".} + +proc newDate*(year, month, day, hours, minutes, + seconds, milliseconds: int): DateTime {. + importcpp: "new Date(#,#,#,#,#,#,#)".} + +proc getDay*(d: DateTime): int {.importcpp.} +proc getFullYear*(d: DateTime): int {.importcpp.} +proc getHours*(d: DateTime): int {.importcpp.} +proc getMilliseconds*(d: DateTime): int {.importcpp.} +proc getMinutes*(d: DateTime): int {.importcpp.} +proc getMonth*(d: DateTime): int {.importcpp.} +proc getSeconds*(d: DateTime): int {.importcpp.} +proc getTime*(d: DateTime): int {.importcpp.} +proc getTimezoneOffset*(d: DateTime): int {.importcpp.} +proc getUTCDate*(d: DateTime): int {.importcpp.} +proc getUTCDay*(d: DateTime): int {.importcpp.} +proc getUTCFullYear*(d: DateTime): int {.importcpp.} +proc getUTCHours*(d: DateTime): int {.importcpp.} +proc getUTCMilliseconds*(d: DateTime): int {.importcpp.} +proc getUTCMinutes*(d: DateTime): int {.importcpp.} +proc getUTCMonth*(d: DateTime): int {.importcpp.} +proc getUTCSeconds*(d: DateTime): int {.importcpp.} +proc getYear*(d: DateTime): int {.importcpp.} + +proc setFullYear*(d: DateTime, year: int) {.importcpp.} + +func toDateString*(d: DateTime): cstring {.importcpp.} +func toISOString*(d: DateTime): cstring {.importcpp.} +func toJSON*(d: DateTime): cstring {.importcpp.} +proc toString*(d: DateTime): cstring {.importcpp.} +func toTimeString*(d: DateTime): cstring {.importcpp.} +func toUTCString*(d: DateTime): cstring {.importcpp.} + +#JSON library +proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.} +proc parse*(l: JsonLib, s: cstring): JsRoot {.importcpp.} + + +since (1, 5): + func debugger*() {.importjs: "debugger@".} + ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger + + func copyWithin*[T](self: openArray[T]; target: int): seq[T] {.importjs: "#.copyWithin(#)".} + func copyWithin*[T](self: openArray[T]; target, start: int): seq[T] {.importjs: "#.copyWithin(#, #)".} + func copyWithin*[T](self: openArray[T]; target, start, ends: int): seq[T] {.importjs: "#.copyWithin(#, #, #)".} = + ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin + ## `copyWithin` uses shallow copy. + runnableExamples: + assert ['a', 'b', 'c', 'd', 'e'].copyWithin(0, 3, 4) == @['d', 'b', 'c', 'd', 'e'] + assert ['a', 'b', 'c', 'd', 'e'].copyWithin(1, 3) == @['a', 'd', 'e', 'd', 'e'] + assert [1, 2, 3, 4, 5].copyWithin(-2) == @[1, 2, 3, 1, 2] + assert [1, 2, 3, 4, 5].copyWithin(0, 3) == @[4, 5, 3, 4, 5] + assert [1, 2, 3, 4, 5].copyWithin(0, 3, 4) == @[4, 2, 3, 4, 5] + assert [1, 2, 3, 4, 5].copyWithin(-2, -3, -1) == @[1, 2, 3, 3, 4] + + +since (1, 7): + func shift*[T](self: seq[T]): T {.importjs: "#.$1()".} = + ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift + runnableExamples: + var arrai = @[1, 2, 3] + assert arrai.shift() == 1 + assert arrai == @[2, 3] + + func queueMicrotask*(function: proc) {.importjs: "$1(#)".} = + ## * https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask + ## * https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide + runnableExamples"-r:off": queueMicrotask(proc() = echo "Microtask") diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim new file mode 100644 index 000000000..d50d58ae5 --- /dev/null +++ b/lib/js/jsffi.nim @@ -0,0 +1,527 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2017 Nim Authors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This Module implements types and macros to facilitate the wrapping of, and +## interaction with JavaScript libraries. Using the provided types `JsObject` +## and `JsAssoc` together with the provided macros allows for smoother +## interfacing with JavaScript, allowing for example quick and easy imports of +## JavaScript variables: + +runnableExamples: + # Here, we are using jQuery for just a few calls and do not want to wrap the + # whole library: + + # import the document object and the console + var document {.importc, nodecl.}: JsObject + var console {.importc, nodecl.}: JsObject + # import the "$" function + proc jq(selector: JsObject): JsObject {.importjs: "$$(#)".} + + # Use jQuery to make the following code run, after the document is ready. + # This uses an experimental `.()` operator for `JsObject`, to emit + # JavaScript calls, when no corresponding proc exists for `JsObject`. + proc main = + jq(document).ready(proc() = + console.log("Hello JavaScript!") + ) + + +when not defined(js) and not defined(nimsuggest): + {.fatal: "Module jsFFI is designed to be used with the JavaScript backend.".} + +import std/[macros, tables] + +const + setImpl = "#[#] = #" + getImpl = "#[#]" + +var + mangledNames {.compileTime.} = initTable[string, string]() + nameCounter {.compileTime.} = 0 + +proc validJsName(name: string): bool = + result = true + const reservedWords = ["break", "case", "catch", "class", "const", "continue", + "debugger", "default", "delete", "do", "else", "export", "extends", + "finally", "for", "function", "if", "import", "in", "instanceof", "new", + "return", "super", "switch", "this", "throw", "try", "typeof", "var", + "void", "while", "with", "yield", "enum", "implements", "interface", + "let", "package", "private", "protected", "public", "static", "await", + "abstract", "boolean", "byte", "char", "double", "final", "float", "goto", + "int", "long", "native", "short", "synchronized", "throws", "transient", + "volatile", "null", "true", "false"] + case name + of reservedWords: return false + else: discard + if name[0] notin {'A'..'Z','a'..'z','_','$'}: return false + for chr in name: + if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}: + return false + +template mangleJsName(name: string): string = + inc nameCounter + "mangledName" & $nameCounter + +# only values that can be mapped 1 to 1 with cstring should be keys: they have an injective function with cstring + +proc toJsKey*[T: SomeInteger](text: cstring, t: type T): T {.importjs: "parseInt(#)".} + +proc toJsKey*[T: enum](text: cstring, t: type T): T = + T(text.toJsKey(int)) + +proc toJsKey*(text: cstring, t: type cstring): cstring = + text + +proc toJsKey*[T: SomeFloat](text: cstring, t: type T): T {.importjs: "parseFloat(#)".} + +type + JsKey* = concept a, type T + cstring.toJsKey(T) is T + + JsObject* = ref object of JsRoot + ## Dynamically typed wrapper around a JavaScript object. + JsAssoc*[K: JsKey, V] = ref object of JsRoot + ## Statically typed wrapper around a JavaScript object. + + js* = JsObject + +var + jsArguments* {.importc: "arguments", nodecl}: JsObject + ## JavaScript's arguments pseudo-variable. + jsNull* {.importc: "null", nodecl.}: JsObject + ## JavaScript's null literal. + jsUndefined* {.importc: "undefined", nodecl.}: JsObject + ## JavaScript's undefined literal. + jsDirname* {.importc: "__dirname", nodecl.}: cstring + ## JavaScript's __dirname pseudo-variable. + jsFilename* {.importc: "__filename", nodecl.}: cstring + ## JavaScript's __filename pseudo-variable. + +proc isNull*[T](x: T): bool {.noSideEffect, importjs: "(# === null)".} + ## Checks if a value is exactly null. + +proc isUndefined*[T](x: T): bool {.noSideEffect, importjs: "(# === undefined)".} + ## Checks if a value is exactly undefined. + +# Exceptions +type + JsError* {.importc: "Error".} = object of JsRoot + message*: cstring + JsEvalError* {.importc: "EvalError".} = object of JsError + JsRangeError* {.importc: "RangeError".} = object of JsError + JsReferenceError* {.importc: "ReferenceError".} = object of JsError + JsSyntaxError* {.importc: "SyntaxError".} = object of JsError + JsTypeError* {.importc: "TypeError".} = object of JsError + JsURIError* {.importc: "URIError".} = object of JsError + +# New +proc newJsObject*: JsObject {.importjs: "{@}".} + ## Creates a new empty JsObject. + +proc newJsAssoc*[K: JsKey, V]: JsAssoc[K, V] {.importjs: "{@}".} + ## Creates a new empty JsAssoc with key type `K` and value type `V`. + +# Checks +proc hasOwnProperty*(x: JsObject, prop: cstring): bool + {.importjs: "#.hasOwnProperty(#)".} + ## Checks, whether `x` has a property of name `prop`. + +proc jsTypeOf*(x: JsObject): cstring {.importjs: "typeof(#)".} + ## Returns the name of the JsObject's JavaScript type as a cstring. + +proc jsNew*(x: auto): JsObject {.importjs: "(new #)".} + ## Turns a regular function call into an invocation of the + ## JavaScript's `new` operator. + +proc jsDelete*(x: auto): JsObject {.importjs: "(delete #)".} + ## JavaScript's `delete` operator. + +proc require*(module: cstring): JsObject {.importc.} + ## JavaScript's `require` function. + +# Conversion to and from JsObject +proc to*(x: JsObject, T: typedesc): T {.importjs: "(#)".} + ## Converts a JsObject `x` to type `T`. + +proc toJs*[T](val: T): JsObject {.importjs: "(#)".} + ## Converts a value of any type to type JsObject. + +template toJs*(s: string): JsObject = cstring(s).toJs + +macro jsFromAst*(n: untyped): untyped = + result = n + if n.kind == nnkStmtList: + result = newProc(procType = nnkDo, body = result) + return quote: toJs(`result`) + +proc `&`*(a, b: cstring): cstring {.importjs: "(# + #)".} + ## Concatenation operator for JavaScript strings. + +proc `+`*(x, y: JsObject): JsObject {.importjs: "(# + #)".} +proc `-`*(x, y: JsObject): JsObject {.importjs: "(# - #)".} +proc `*`*(x, y: JsObject): JsObject {.importjs: "(# * #)".} +proc `/`*(x, y: JsObject): JsObject {.importjs: "(# / #)".} +proc `%`*(x, y: JsObject): JsObject {.importjs: "(# % #)".} +proc `+=`*(x, y: JsObject): JsObject {.importjs: "(# += #)", discardable.} +proc `-=`*(x, y: JsObject): JsObject {.importjs: "(# -= #)", discardable.} +proc `*=`*(x, y: JsObject): JsObject {.importjs: "(# *= #)", discardable.} +proc `/=`*(x, y: JsObject): JsObject {.importjs: "(# /= #)", discardable.} +proc `%=`*(x, y: JsObject): JsObject {.importjs: "(# %= #)", discardable.} +proc `++`*(x: JsObject): JsObject {.importjs: "(++#)".} +proc `--`*(x: JsObject): JsObject {.importjs: "(--#)".} +proc `>`*(x, y: JsObject): JsObject {.importjs: "(# > #)".} +proc `<`*(x, y: JsObject): JsObject {.importjs: "(# < #)".} +proc `>=`*(x, y: JsObject): JsObject {.importjs: "(# >= #)".} +proc `<=`*(x, y: JsObject): JsObject {.importjs: "(# <= #)".} +proc `**`*(x, y: JsObject): JsObject {.importjs: "((#) ** #)".} + # (#) needed, refs https://github.com/nim-lang/Nim/pull/16409#issuecomment-760550812 +proc `and`*(x, y: JsObject): JsObject {.importjs: "(# && #)".} +proc `or`*(x, y: JsObject): JsObject {.importjs: "(# || #)".} +proc `not`*(x: JsObject): JsObject {.importjs: "(!#)".} +proc `in`*(x, y: JsObject): JsObject {.importjs: "(# in #)".} + +proc `[]`*(obj: JsObject, field: cstring): JsObject {.importjs: getImpl.} + ## Returns the value of a property of name `field` from a JsObject `obj`. + +proc `[]`*(obj: JsObject, field: int): JsObject {.importjs: getImpl.} + ## Returns the value of a property of name `field` from a JsObject `obj`. + +proc `[]=`*[T](obj: JsObject, field: cstring, val: T) {.importjs: setImpl.} + ## Sets the value of a property of name `field` in a JsObject `obj` to `v`. + +proc `[]=`*[T](obj: JsObject, field: int, val: T) {.importjs: setImpl.} + ## Sets the value of a property of name `field` in a JsObject `obj` to `v`. + +proc `[]`*[K: JsKey, V](obj: JsAssoc[K, V], field: K): V + {.importjs: getImpl.} + ## Returns the value of a property of name `field` from a JsAssoc `obj`. + +proc `[]=`*[K: JsKey, V](obj: JsAssoc[K, V], field: K, val: V) + {.importjs: setImpl.} + ## Sets the value of a property of name `field` in a JsAssoc `obj` to `v`. + +proc `[]`*[V](obj: JsAssoc[cstring, V], field: string): V = + obj[cstring(field)] + +proc `[]=`*[V](obj: JsAssoc[cstring, V], field: string, val: V) = + obj[cstring(field)] = val + +proc `==`*(x, y: JsRoot): bool {.importjs: "(# === #)".} + ## Compares two JsObjects or JsAssocs. Be careful though, as this is comparison + ## like in JavaScript, so if your JsObjects are in fact JavaScript Objects, + ## and not strings or numbers, this is a *comparison of references*. + +{.experimental.} +macro `.`*(obj: JsObject, field: untyped): JsObject = + ## Experimental dot accessor (get) for type JsObject. + ## Returns the value of a property of name `field` from a JsObject `x`. + runnableExamples: + let obj = newJsObject() + obj.a = 20 + assert obj.a.to(int) == 20 + if validJsName($field): + let importString = "#." & $field + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: JsObject): JsObject + {.importjs: `importString`.} + `helperName`(`obj`) + else: + if not mangledNames.hasKey($field): + mangledNames[$field] = mangleJsName($field) + let importString = "#." & mangledNames[$field] + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: JsObject): JsObject + {.importjs: `importString`.} + `helperName`(`obj`) + +macro `.=`*(obj: JsObject, field, value: untyped): untyped = + ## Experimental dot accessor (set) for type JsObject. + ## Sets the value of a property of name `field` in a JsObject `x` to `value`. + if validJsName($field): + let importString = "#." & $field & " = #" + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: JsObject, v: auto) + {.importjs: `importString`.} + `helperName`(`obj`, `value`) + else: + if not mangledNames.hasKey($field): + mangledNames[$field] = mangleJsName($field) + let importString = "#." & mangledNames[$field] & " = #" + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: JsObject, v: auto) + {.importjs: `importString`.} + `helperName`(`obj`, `value`) + +macro `.()`*(obj: JsObject, + field: untyped, + args: varargs[JsObject, jsFromAst]): JsObject = + ## Experimental "method call" operator for type JsObject. + ## Takes the name of a method of the JavaScript object (`field`) and calls + ## it with `args` as arguments, returning a JsObject (which may be discarded, + ## and may be `undefined`, if the method does not return anything, + ## so be careful when using this.) + ## + ## Example: + ## ```nim + ## # Let's get back to the console example: + ## var console {.importc, nodecl.}: JsObject + ## let res = console.log("I return undefined!") + ## console.log(res) # This prints undefined, as console.log always returns + ## # undefined. Thus one has to be careful, when using + ## # JsObject calls. + ## ``` + var importString: string + if validJsName($field): + importString = "#." & $field & "(@)" + else: + if not mangledNames.hasKey($field): + mangledNames[$field] = mangleJsName($field) + importString = "#." & mangledNames[$field] & "(@)" + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: JsObject): JsObject + {.importjs: `importString`, discardable.} + `helperName`(`obj`) + for idx in 0 ..< args.len: + let paramName = newIdentNode("param" & $idx) + result[0][3].add newIdentDefs(paramName, newIdentNode("JsObject")) + result[1].add args[idx].copyNimTree + +macro `.`*[K: cstring, V](obj: JsAssoc[K, V], + field: untyped): V = + ## Experimental dot accessor (get) for type JsAssoc. + ## Returns the value of a property of name `field` from a JsObject `x`. + var importString: string + if validJsName($field): + importString = "#." & $field + else: + if not mangledNames.hasKey($field): + mangledNames[$field] = mangleJsName($field) + importString = "#." & mangledNames[$field] + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: type(`obj`)): `obj`.V + {.importjs: `importString`.} + `helperName`(`obj`) + +macro `.=`*[K: cstring, V](obj: JsAssoc[K, V], + field: untyped, + value: V): untyped = + ## Experimental dot accessor (set) for type JsAssoc. + ## Sets the value of a property of name `field` in a JsObject `x` to `value`. + var importString: string + if validJsName($field): + importString = "#." & $field & " = #" + else: + if not mangledNames.hasKey($field): + mangledNames[$field] = mangleJsName($field) + importString = "#." & mangledNames[$field] & " = #" + let helperName = genSym(nskProc, "helper") + result = quote do: + proc `helperName`(o: type(`obj`), v: `obj`.V) + {.importjs: `importString`.} + `helperName`(`obj`, `value`) + +macro `.()`*[K: cstring, V: proc](obj: JsAssoc[K, V], + field: untyped, + args: varargs[untyped]): auto = + ## Experimental "method call" operator for type JsAssoc. + ## Takes the name of a method of the JavaScript object (`field`) and calls + ## it with `args` as arguments. Here, everything is typechecked, so you do not + ## have to worry about `undefined` return values. + let dotOp = bindSym"." + result = quote do: + (`dotOp`(`obj`, `field`))() + for elem in args: + result.add elem + +# Iterators: + +iterator pairs*(obj: JsObject): (cstring, JsObject) = + ## Yields tuples of type `(cstring, JsObject)`, with the first entry + ## being the `name` of a fields in the JsObject and the second being its + ## value wrapped into a JsObject. + var k: cstring + var v: JsObject + {.emit: "for (var `k` in `obj`) {".} + {.emit: " if (!`obj`.hasOwnProperty(`k`)) { continue; }".} + {.emit: " `v` = `obj`[`k`];".} + yield (k, v) + {.emit: "}".} + +iterator items*(obj: JsObject): JsObject = + ## Yields the `values` of each field in a JsObject, wrapped into a JsObject. + var v: JsObject + {.emit: "for (var k in `obj`) {".} + {.emit: " if (!`obj`.hasOwnProperty(k)) { continue; }".} + {.emit: " `v` = `obj`[k];".} + yield v + {.emit: "}".} + +iterator keys*(obj: JsObject): cstring = + ## Yields the `names` of each field in a JsObject. + var k: cstring + {.emit: "for (var `k` in `obj`) {".} + {.emit: " if (!`obj`.hasOwnProperty(`k`)) { continue; }".} + yield k + {.emit: "}".} + +iterator pairs*[K: JsKey, V](assoc: JsAssoc[K, V]): (K,V) = + ## Yields tuples of type `(K, V)`, with the first entry + ## being a `key` in the JsAssoc and the second being its corresponding value. + var k: cstring + var v: V + {.emit: "for (var `k` in `assoc`) {".} + {.emit: " if (!`assoc`.hasOwnProperty(`k`)) { continue; }".} + {.emit: " `v` = `assoc`[`k`];".} + yield (k.toJsKey(K), v) + {.emit: "}".} + +iterator items*[K, V](assoc: JsAssoc[K, V]): V = + ## Yields the `values` in a JsAssoc. + var v: V + {.emit: "for (var k in `assoc`) {".} + {.emit: " if (!`assoc`.hasOwnProperty(k)) { continue; }".} + {.emit: " `v` = `assoc`[k];".} + yield v + {.emit: "}".} + +iterator keys*[K: JsKey, V](assoc: JsAssoc[K, V]): K = + ## Yields the `keys` in a JsAssoc. + var k: cstring + {.emit: "for (var `k` in `assoc`) {".} + {.emit: " if (!`assoc`.hasOwnProperty(`k`)) { continue; }".} + yield k.toJsKey(K) + {.emit: "}".} + +# Literal generation + +macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto = + ## Takes a `typedesc` as its first argument, and a series of expressions of + ## type `key: value`, and returns a value of the specified type with each + ## field `key` set to `value`, as specified in the arguments of `{}`. + ## + ## Example: + ## + ## ```nim + ## # Let's say we have a type with a ton of fields, where some fields do not + ## # need to be set, and we do not want those fields to be set to `nil`: + ## type + ## ExtremelyHugeType = ref object + ## a, b, c, d, e, f, g: int + ## h, i, j, k, l: cstring + ## # And even more fields ... + ## + ## let obj = ExtremelyHugeType{ a: 1, k: "foo".cstring, d: 42 } + ## + ## # This generates roughly the same JavaScript as: + ## {.emit: "var obj = {a: 1, k: "foo", d: 42};".} + ## ``` + let a = ident"a" + var body = quote do: + var `a` {.noinit.}: `typ` + {.emit: "`a` = {};".} + for x in xs.children: + if x.kind == nnkExprColonExpr: + let + k = x[0] + kString = quote do: + when compiles($`k`): $`k` else: "invalid" + v = x[1] + body.add quote do: + when compiles(`a`.`k`): + `a`.`k` = `v` + elif compiles(`a`[`k`]): + `a`[`k`] = `v` + else: + `a`[`kString`] = `v` + + else: + error("Expression `" & $x.toStrLit & "` not allowed in `{}` macro") + + body.add quote do: + return `a` + + result = quote do: + proc inner(): `typ` {.gensym.} = + `body` + inner() + +# Macro to build a lambda using JavaScript's `this` +# from a proc, `this` being the first argument. + +proc replaceSyms(n: NimNode): NimNode = + if n.kind == nnkSym: + result = newIdentNode($n) + else: + result = n + for i in 0..<n.len: + result[i] = replaceSyms(n[i]) + +macro bindMethod*(procedure: typed): auto {.deprecated: "Don't use it with closures".} = + ## Takes the name of a procedure and wraps it into a lambda missing the first + ## argument, which passes the JavaScript builtin `this` as the first + ## argument to the procedure. Returns the resulting lambda. + ## + ## Example: + ## + ## We want to generate roughly this JavaScript: + ## ```js + ## var obj = {a: 10}; + ## obj.someMethod = function() { + ## return this.a + 42; + ## }; + ## ``` + ## + ## We can achieve this using the `bindMethod` macro: + ## + ## ```nim + ## let obj = JsObject{ a: 10 } + ## proc someMethodImpl(that: JsObject): int = + ## that.a.to(int) + 42 + ## obj.someMethod = bindMethod someMethodImpl + ## + ## # Alternatively: + ## obj.someMethod = bindMethod + ## proc(that: JsObject): int = that.a.to(int) + 42 + ## ``` + if not (procedure.kind == nnkSym or procedure.kind == nnkLambda): + error("Argument has to be a proc or a symbol corresponding to a proc.") + var + rawProc = if procedure.kind == nnkSym: + getImpl(procedure) + else: + procedure + args = rawProc[3].copyNimTree.replaceSyms + thisType = args[1][1] + params = newNimNode(nnkFormalParams).add(args[0]) + body = newNimNode(nnkLambda) + this = newIdentNode("this") + # construct the `this` parameter: + thisQuote = quote do: + var `this` {.nodecl, importc: "this".}: `thisType` + call = newNimNode(nnkCall).add(rawProc[0], thisQuote[0][0][0]) + # construct the procedure call inside the method + if args.len > 2: + for idx in 2..args.len-1: + params.add(args[idx]) + call.add(args[idx][0]) + body.add(newNimNode(nnkEmpty), + rawProc[1], + rawProc[2], + params, + rawProc[4], + rawProc[5], + newTree(nnkStmtList, thisQuote, call) + ) + result = body diff --git a/lib/js/jsre.nim b/lib/js/jsre.nim new file mode 100644 index 000000000..2d931eb20 --- /dev/null +++ b/lib/js/jsre.nim @@ -0,0 +1,97 @@ +## Regular Expressions for the JavaScript target. +## * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +when not defined(js): + {.error: "This module only works on the JavaScript platform".} + +type RegExp* = ref object of JsRoot + ## Regular Expressions for JavaScript target. + ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp + flags*: cstring ## cstring that contains the flags of the RegExp object. + dotAll*: bool ## Whether `.` matches newlines or not. + global*: bool ## Whether to test against all possible matches in a string, or only against the first. + ignoreCase*: bool ## Whether to ignore case while attempting a match in a string. + multiline*: bool ## Whether to search in strings across multiple lines. + source*: cstring ## The text of the pattern. + sticky*: bool ## Whether the search is sticky. + unicode*: bool ## Whether Unicode features are enabled. + lastIndex*: cint ## Index at which to start the next match (read/write property). + input*: cstring ## Read-only and modified on successful match. + lastMatch*: cstring ## Ditto. + lastParen*: cstring ## Ditto. + leftContext*: cstring ## Ditto. + rightContext*: cstring ## Ditto. + hasIndices*: bool ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/hasIndices + + +func newRegExp*(pattern: cstring; flags: cstring): RegExp {.importjs: "new RegExp(@)".} + ## Creates a new RegExp object. + +func newRegExp*(pattern: cstring): RegExp {.importjs: "new RegExp(@)".} + +func compile*(self: RegExp; pattern: cstring; flags: cstring) {.importjs: "#.compile(@)".} + ## Recompiles a regular expression during execution of a script. + +func replace*(pattern: cstring; self: RegExp; replacement: cstring): cstring {.importjs: "#.replace(#, #)".} + ## Returns a new string with some or all matches of a pattern replaced by given replacement + +func replace*(pattern: cstring, self: RegExp, cb: proc (args: varargs[cstring]): cstring): cstring {.importcpp.} + ## Returns a new string with some or all matches of a pattern replaced by given callback function + +func split*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.split(#) || [])".} + ## Divides a string into an ordered list of substrings and returns the array + +func match*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.match(#) || [])".} + ## Returns an array of matches of a RegExp against given string + +func exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "(#.exec(#) || [])".} + ## Executes a search for a match in its string parameter. + +func toCstring*(self: RegExp): cstring {.importjs: "#.toString()".} + ## Returns a string representing the RegExp object. + +func `$`*(self: RegExp): string = $toCstring(self) + +func contains*(pattern: cstring; self: RegExp): bool = + ## Tests for a substring match in its string parameter. + runnableExamples: + let jsregex: RegExp = newRegExp(r"bc$", r"i") + assert jsregex in r"abc" + assert jsregex notin r"abcd" + assert "xabc".contains jsregex + {.emit: "`result` = `self`.test(`pattern`);".} + +func startsWith*(pattern: cstring; self: RegExp): bool = + ## Tests if string starts with given RegExp + runnableExamples: + let jsregex: RegExp = newRegExp(r"abc", r"i") + assert "abcd".startsWith jsregex + pattern.contains(newRegExp(("^" & $(self.source)).cstring, self.flags)) + +func endsWith*(pattern: cstring; self: RegExp): bool = + ## Tests if string ends with given RegExp + runnableExamples: + let jsregex: RegExp = newRegExp(r"bcd", r"i") + assert "abcd".endsWith jsregex + pattern.contains(newRegExp(($(self.source) & "$").cstring, self.flags)) + + +runnableExamples: + let jsregex: RegExp = newRegExp(r"\s+", r"i") + jsregex.compile(r"\w+", r"i") + assert "nim javascript".contains jsregex + assert jsregex.exec(r"nim javascript") == @["nim".cstring] + assert jsregex.toCstring() == r"/\w+/i" + jsregex.compile(r"[0-9]", r"i") + assert "0123456789abcd".contains jsregex + assert $jsregex == "/[0-9]/i" + jsregex.compile(r"abc", r"i") + assert "abcd".startsWith jsregex + assert "dabc".endsWith jsregex + jsregex.compile(r"\d", r"i") + assert "do1ne".split(jsregex) == @["do".cstring, "ne".cstring] + jsregex.compile(r"[lw]", r"i") + assert "hello world".replace(jsregex,"X") == "heXlo world" + jsregex.compile(r"([a-z])\1*", r"g") + assert "abbcccdddd".replace(jsregex, proc (m: varargs[cstring]): cstring = ($m[0] & $(m.len)).cstring) == "a1b2c3d4" + let digitsRegex: RegExp = newRegExp(r"\d") + assert "foo".match(digitsRegex) == @[] |