diff options
Diffstat (limited to 'lib/js')
-rw-r--r-- | lib/js/asyncjs.nim | 171 | ||||
-rw-r--r-- | lib/js/dom.nim | 284 | ||||
-rw-r--r-- | lib/js/dom_extensions.nim | 5 | ||||
-rw-r--r-- | lib/js/jsconsole.nim | 27 | ||||
-rw-r--r-- | lib/js/jscore.nim | 61 | ||||
-rw-r--r-- | lib/js/jsffi.nim | 321 | ||||
-rw-r--r-- | lib/js/jsre.nim | 115 |
7 files changed, 656 insertions, 328 deletions
diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim index 219b1bed5..9b043f3e5 100644 --- a/lib/js/asyncjs.nim +++ b/lib/js/asyncjs.nim @@ -11,43 +11,48 @@ ## 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. +## 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. +## This is roughly equivalent to the `async` keyword in JavaScript code. ## -## .. code-block:: nim -## proc loadGame(name: string): Future[Game] {.async.} = -## # code +## ```nim +## proc loadGame(name: string): Future[Game] {.async.} = +## # code +## ``` ## ## should be equivalent to ## -## .. code-block:: javascript +## ```javascript ## async function loadGame(name) { ## // code ## } +## ``` ## -## A call to an asynchronous procedure usually needs ``await`` to wait for -## the completion of the ``Future``. +## A call to an asynchronous procedure usually needs `await` to wait for +## the completion of the `Future`. ## -## .. code-block:: nim +## ```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``: +## asynchronous procedures using promises and `newPromise`: ## -## .. code-block:: nim +## ```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: +## Forward definitions work properly, you just need to always add the `{.async.}` pragma: ## -## .. code-block:: nim +## ```nim ## proc loadGame(name: string): Future[Game] {.async.} +## ``` ## ## JavaScript compatibility ## ======================== @@ -57,19 +62,22 @@ ## 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. -import jsffi -import macros +# xxx code: javascript above gives `LanguageXNotSupported` warning. -when not defined(js) and not defined(nimdoc) and not defined(nimsuggest): +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* {.importcpp: "Promise".} = ref object - ## A JavaScript Promise + PromiseJs* {.importjs: "Promise".} = ref object + ## A JavaScript Promise. proc replaceReturn(node: var NimNode) = @@ -82,6 +90,8 @@ proc replaceReturn(node: var NimNode) = 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 @@ -92,10 +102,18 @@ proc isFutureVoid(node: NimNode): bool = node[1].kind == nnkIdent and $node[1] == "void" proc generateJsasync(arg: NimNode): NimNode = - if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}: + 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") @@ -111,16 +129,17 @@ proc generateJsasync(arg: NimNode): NimNode = if len(code) > 0: var awaitFunction = quote: - proc await[T](f: Future[T]): T {.importcpp: "(await #)", used.} + proc await[T](f: Future[T]): T {.importjs: "(await #)", used.} result.body.add(awaitFunction) var resolve: NimNode if isVoid: resolve = quote: - var `jsResolve` {.importcpp: "undefined".}: Future[void] + var `jsResolve` {.importjs: "undefined".}: Future[void] else: resolve = quote: - proc jsResolve[T](a: T): Future[T] {.importcpp: "#", used.} + 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() @@ -139,7 +158,7 @@ proc generateJsasync(arg: NimNode): NimNode = macro async*(arg: untyped): untyped = ## Macro which converts normal procedures into - ## javascript-compatible async procedures + ## javascript-compatible async procedures. if arg.kind == nnkStmtList: result = newStmtList() for oneProc in arg: @@ -147,10 +166,104 @@ macro async*(arg: untyped): untyped = else: result = generateJsasync(arg) -proc newPromise*[T](handler: proc(resolve: proc(response: T))): Future[T] {.importcpp: "(new Promise(#))".} +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 + ## into promises and async procedures. -proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importcpp: "(new Promise(#))".} +proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importjs: "(new Promise(#))".} ## A helper for wrapping callback-based functions - ## into promises and async procedures + ## 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 index d4e8ce86a..be2a34db1 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -9,16 +9,46 @@ ## 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) and not defined(Nimdoc): +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* = ref EventTargetObj - EventTargetObj {.importc.} = object of RootObj + EventTarget* {.importc.} = ref object of RootObj onabort*: proc (event: Event) {.closure.} onblur*: proc (event: Event) {.closure.} onchange*: proc (event: Event) {.closure.} @@ -37,6 +67,7 @@ type 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.} @@ -72,6 +103,7 @@ type Resize = "resize", Scroll = "scroll", Select = "select", + Storage = "storage", Unload = "unload", Wheel = "wheel" @@ -125,10 +157,9 @@ type rangeCount*: int `type`*: cstring - LocalStorage* {.importc.} = ref object + Storage* {.importc.} = ref object - Window* = ref WindowObj - WindowObj {.importc.} = object of EventTargetObj + Window* {.importc.} = ref object of EventTarget document*: Document event*: Event history*: History @@ -153,14 +184,13 @@ type screen*: Screen performance*: Performance onpopstate*: proc (event: Event) - localStorage*: LocalStorage + localStorage*: Storage + sessionStorage*: Storage parent*: Window - Frame* = ref FrameObj - FrameObj {.importc.} = object of WindowObj + Frame* {.importc.} = ref object of Window - ClassList* = ref ClassListObj - ClassListObj {.importc.} = object of RootObj + ClassList* {.importc.} = ref object of RootObj NodeType* = enum ElementNode = 1, @@ -176,8 +206,7 @@ type DocumentFragmentNode, NotationNode - Node* = ref NodeObj - NodeObj {.importc.} = object of EventTargetObj + Node* {.importc.} = ref object of EventTarget attributes*: seq[Node] childNodes*: seq[Node] children*: seq[Node] @@ -201,8 +230,7 @@ type parentElement*: Element isConnected*: bool - Document* = ref DocumentObj - DocumentObj {.importc.} = object of NodeObj + Document* {.importc.} = ref object of Node activeElement*: Element documentElement*: Element alinkColor*: cstring @@ -213,11 +241,13 @@ type 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] @@ -227,8 +257,7 @@ type links*: seq[LinkElement] fonts*: FontFaceSet - Element* = ref ElementObj - ElementObj {.importc.} = object of NodeObj + Element* {.importc.} = ref object of Node className*: cstring classList*: ClassList checked*: bool @@ -249,8 +278,7 @@ type offsetLeft*: int offsetTop*: int - ValidityState* = ref ValidityStateObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_ - ValidityStateObj {.importc.} = object + ValidityState* {.importc.} = ref object ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_ badInput*: bool customError*: bool patternMismatch*: bool @@ -263,25 +291,21 @@ type valid*: bool valueMissing*: bool - Blob* = ref BlobObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_ - BlobObj {.importc.} = object of RootObj + Blob* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_ size*: int `type`*: cstring - File* = ref FileObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_ - FileObj {.importc.} = object of Blob + File* {.importc.} = ref object of Blob ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_ lastModified*: int name*: cstring - TextAreaElement* = ref TextAreaElementObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement>`_ - TextAreaElementObj {.importc.} = object of Element + 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* = ref InputElementObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement>`_ - InputElementObj {.importc.} = object of Element + 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 @@ -333,15 +357,13 @@ type valueAsDate*: cstring valueAsNumber*: float - LinkElement* = ref LinkObj - LinkObj {.importc.} = object of ElementObj + LinkElement* {.importc.} = ref object of Element target*: cstring text*: cstring x*: int y*: int - EmbedElement* = ref EmbedObj - EmbedObj {.importc.} = object of ElementObj + EmbedElement* {.importc.} = ref object of Element height*: int hspace*: int src*: cstring @@ -349,21 +371,18 @@ type `type`*: cstring vspace*: int - AnchorElement* = ref AnchorObj - AnchorObj {.importc.} = object of ElementObj + AnchorElement* {.importc.} = ref object of Element text*: cstring x*, y*: int - OptionElement* = ref OptionObj - OptionObj {.importc.} = object of ElementObj + OptionElement* {.importc.} = ref object of Element defaultSelected*: bool selected*: bool selectedIndex*: int text*: cstring value*: cstring - FormElement* = ref FormObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_ - FormObj {.importc.} = object of ElementObj + FormElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_ acceptCharset*: cstring action*: cstring autocomplete*: cstring @@ -375,8 +394,7 @@ type noValidate*: bool target*: cstring - ImageElement* = ref ImageObj - ImageObj {.importc.} = object of ElementObj + ImageElement* {.importc.} = ref object of Element border*: int complete*: bool height*: int @@ -386,8 +404,7 @@ type vspace*: int width*: int - Style* = ref StyleObj - StyleObj {.importc.} = object of RootObj + Style* {.importc.} = ref object of RootObj alignContent*: cstring alignItems*: cstring alignSelf*: cstring @@ -763,8 +780,7 @@ type AtTarget, BubblingPhase - Event* = ref EventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_ - EventObj {.importc.} = object of RootObj + Event* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_ bubbles*: bool cancelBubble*: bool cancelable*: bool @@ -776,13 +792,11 @@ type `type`*: cstring isTrusted*: bool - UIEvent* = ref UIEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_ - UIEventObj {.importc.} = object of Event + UIEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_ detail*: int64 view*: Window - KeyboardEvent* = ref KeyboardEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent>`_ - KeyboardEventObj {.importc.} = object of UIEvent + 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 @@ -1144,8 +1158,7 @@ type FourthButton = 8, FifthButton = 16 - MouseEvent* = ref MouseEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent>`_ - MouseEventObj {.importc.} = object of UIEvent + 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 @@ -1162,13 +1175,11 @@ type File = "file", String = "string" - DataTransferItem* = ref DataTransferItemObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_ - DataTransferItemObj {.importc.} = object of RootObj + DataTransferItem* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_ kind*: cstring `type`*: cstring - DataTransfer* = ref DataTransferObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer>`_ - DataTransferObj {.importc.} = object of RootObj + 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] @@ -1210,11 +1221,16 @@ type ## 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* = ref TouchObj - TouchObj {.importc.} = object of RootObj + Touch* {.importc.} = ref object of RootObj identifier*: int screenX*, screenY*, clientX*, clientY*, pageX*, pageY*: int target*: Element @@ -1222,12 +1238,10 @@ type rotationAngle*: int force*: float - TouchEvent* = ref TouchEventObj - TouchEventObj {.importc.} = object of UIEvent + TouchEvent* {.importc.} = ref object of UIEvent changedTouches*, targetTouches*, touches*: seq[Touch] - Location* = ref LocationObj - LocationObj {.importc.} = object of RootObj + Location* {.importc.} = ref object of RootObj hash*: cstring host*: cstring hostname*: cstring @@ -1238,12 +1252,10 @@ type search*: cstring origin*: cstring - History* = ref HistoryObj - HistoryObj {.importc.} = object of RootObj + History* {.importc.} = ref object of RootObj length*: int - Navigator* = ref NavigatorObj - NavigatorObj {.importc.} = object of RootObj + Navigator* {.importc.} = ref object of RootObj appCodeName*: cstring appName*: cstring appVersion*: cstring @@ -1281,8 +1293,7 @@ type ToolBar* = LocationBar StatusBar* = LocationBar - Screen = ref ScreenObj - ScreenObj {.importc.} = object of RootObj + Screen* {.importc.} = ref object of RootObj availHeight*: int availWidth*: int colorDepth*: int @@ -1291,7 +1302,7 @@ type width*: int TimeOut* {.importc.} = ref object of RootObj - Interval* {.importc.} = object of RootObj + Interval* {.importc.} = ref object of RootObj AddEventListenerOptions* = object capture*: bool @@ -1307,41 +1318,46 @@ type 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 ## - ## .. code-block:: nim + ## ```nim ## let prsr = newDomParser() ## discard prsr.parseFromString("<html><marquee>Hello World</marquee></html>".cstring, "text/html".cstring) + ## ``` - DomException* = ref DOMExceptionObj + 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 - DOMExceptionObj {.importc.} = object - - FileReader* = ref FileReaderObj + 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 - FileReaderObj {.importc.} = object of EventTargetObj - FileReaderState* = distinct range[0'u16..2'u16] RootNodeOptions* = object of RootObj composed*: bool DocumentOrShadowRoot* {.importc.} = object of RootObj activeElement*: Element # styleSheets*: StyleSheetList - ShadowRoot* = ref ShadowRootObj - ShadowRootObj {.importc.} = object of DocumentOrShadowRoot + ShadowRoot* {.importc.} = ref object of DocumentOrShadowRoot delegatesFocus*: bool host*: Element innerHTML*: cstring @@ -1350,8 +1366,7 @@ since (1, 3): mode*: cstring delegatesFocus*: bool - HTMLSlotElement* = ref HTMLSlotElementObj - HTMLSlotElementObj {.importc.} = object of RootObj + HTMLSlotElement* {.importc.} = ref object of RootObj name*: cstring SlotOptions* = object of RootObj flatten*: bool @@ -1369,6 +1384,9 @@ 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): @@ -1405,7 +1423,7 @@ when defined(nodejs): parent.childNodes[i] = newNode return inc i - doAssert false, "old node not in node list" + raiseAssert "old node not in node list" proc removeChild*(parent, child: Node) = child.parentNode = nil @@ -1415,7 +1433,7 @@ when defined(nodejs): parent.childNodes.delete(i) return inc i - doAssert false, "old node not in node list" + raiseAssert "old node not in node list" proc insertBefore*(parent, newNode, before: Node) = appendChild(parent, newNode) @@ -1427,7 +1445,7 @@ when defined(nodejs): parent.childNodes[i-1] = newNode return inc i - #doAssert false, "before not in node list" + #raiseAssert "before not in node list" proc createElement*(d: Document, identifier: cstring): Element = new(result) @@ -1457,11 +1475,14 @@ else: 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.} @@ -1475,17 +1496,19 @@ proc removeEventListener*(et: EventTarget; ev: cstring; cb: proc(ev: Event)) proc alert*(w: Window, msg: cstring) proc back*(w: Window) proc blur*(w: Window) -proc clearInterval*(w: Window, interval: ref Interval) -proc clearTimeout*(w: Window, timeout: ref TimeOut) +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) + backwards = false): bool proc focus*(w: Window) proc forward*(w: Window) -proc getComputedStyle*(w: Window, e: Node, pe:Node = nil): Style +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) @@ -1499,13 +1522,14 @@ 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): ref Interval -proc setInterval*(w: Window, function: proc (), pause: int): ref Interval -proc setTimeout*(w: Window, code: cstring, pause: int): ref TimeOut -proc setTimeout*(w: Window, function: proc (), pause: int): ref Interval +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) @@ -1522,6 +1546,7 @@ 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 @@ -1630,7 +1655,7 @@ proc item*(list: TouchList, i: int): Touch 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: int64, yOffset: int64) +proc setDragImage*(dt: DataTransfer, img: Element, xOffset: int, yOffset: int) # DataTransferItem "methods" proc getAsFile*(dti: DataTransferItem): File @@ -1654,12 +1679,11 @@ proc getRangeAt*(s: Selection, index: int): Range converter toString*(s: Selection): cstring proc `$`*(s: Selection): string = $(s.toString()) -# LocalStorage "methods" -proc getItem*(ls: LocalStorage, key: cstring): cstring -proc setItem*(ls: LocalStorage, key, value: cstring) -proc hasItem*(ls: LocalStorage, key: cstring): bool -proc clear*(ls: LocalStorage) -proc removeItem*(ls: LocalStorage, key: cstring) +# 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.} @@ -1745,5 +1769,73 @@ since (1, 3): ## 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, encoding = cstring"UTF-8") {.importcpp: "#.readAsText(#, #)".} + 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/dom_extensions.nim b/lib/js/dom_extensions.nim deleted file mode 100644 index 851ec0c5f..000000000 --- a/lib/js/dom_extensions.nim +++ /dev/null @@ -1,5 +0,0 @@ -import dom - -{.push importcpp.} -proc elementsFromPoint*(n: DocumentOrShadowRoot; x, y: float): seq[Element] -{.pop.} \ No newline at end of file diff --git a/lib/js/jsconsole.nim b/lib/js/jsconsole.nim index 5b9893e75..e74127334 100644 --- a/lib/js/jsconsole.nim +++ b/lib/js/jsconsole.nim @@ -9,10 +9,22 @@ ## 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) and not defined(Nimdoc): +when not defined(js): {.error: "This module only works on the JavaScript platform".} type Console* = ref object of JsRoot @@ -75,7 +87,7 @@ since (1, 5): func getMsg(info: InstantiationInfo; msg: string): string = var temp = "" temp.toLocation(info.filename, info.line, info.column + 1) - result.addQuoted(temp) + result.addQuoted("[jsAssert] " & temp) result.add ',' result.addQuoted(msg) @@ -98,5 +110,16 @@ since (1, 5): {.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 index 2e2bd2402..be353875c 100644 --- a/lib/js/jscore.nim +++ b/lib/js/jscore.nim @@ -11,10 +11,11 @@ ## ## 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 +## the relevant functions in the `math`, `json`, and `times` stdlib ## modules instead. +import std/private/[since, jsutils] -when not defined(js) and not defined(Nimdoc): +when not defined(js): {.error: "This module only works on the JavaScript platform".} type @@ -73,9 +74,16 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.} proc newDate*(): DateTime {. importcpp: "new Date()".} -proc newDate*(date: int|int64|string): DateTime {. +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(#,#,#,#,#,#,#)".} @@ -87,20 +95,59 @@ 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 getYear*(d: DateTime): int {.importcpp.} proc getTime*(d: DateTime): int {.importcpp.} -proc toString*(d: DateTime): cstring {.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 getUTCDay*(d: DateTime): int {.importcpp.} -proc getTimezoneOffset*(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 index 9fe934f89..d50d58ae5 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -8,8 +8,8 @@ # ## 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 +## 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: @@ -21,21 +21,21 @@ runnableExamples: var document {.importc, nodecl.}: JsObject var console {.importc, nodecl.}: JsObject # import the "$" function - proc jq(selector: JsObject): JsObject {.importcpp: "$$(#)".} + 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``. + # 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(nimdoc) and not defined(nimsuggest): +when not defined(js) and not defined(nimsuggest): {.fatal: "Module jsFFI is designed to be used with the JavaScript backend.".} -import macros, tables +import std/[macros, tables] const setImpl = "#[#] = #" @@ -64,13 +64,13 @@ proc validJsName(name: string): bool = if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}: return false -template mangleJsName(name: cstring): cstring = +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 {.importcpp: "parseInt(#)".} +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)) @@ -78,7 +78,7 @@ proc toJsKey*[T: enum](text: cstring, t: type T): T = proc toJsKey*(text: cstring, t: type cstring): cstring = text -proc toJsKey*[T: SomeFloat](text: cstring, t: type T): T {.importcpp: "parseFloat(#)".} +proc toJsKey*[T: SomeFloat](text: cstring, t: type T): T {.importjs: "parseFloat(#)".} type JsKey* = concept a, type T @@ -93,21 +93,21 @@ type var jsArguments* {.importc: "arguments", nodecl}: JsObject - ## JavaScript's arguments pseudo-variable + ## JavaScript's arguments pseudo-variable. jsNull* {.importc: "null", nodecl.}: JsObject - ## JavaScript's null literal + ## JavaScript's null literal. jsUndefined* {.importc: "undefined", nodecl.}: JsObject - ## JavaScript's undefined literal + ## JavaScript's undefined literal. jsDirname* {.importc: "__dirname", nodecl.}: cstring - ## JavaScript's __dirname pseudo-variable + ## JavaScript's __dirname pseudo-variable. jsFilename* {.importc: "__filename", nodecl.}: cstring - ## JavaScript's __filename pseudo-variable + ## JavaScript's __filename pseudo-variable. -proc isNull*[T](x: T): bool {.noSideEffect, importcpp: "(# === null)".} - ## check if a value is exactly null +proc isNull*[T](x: T): bool {.noSideEffect, importjs: "(# === null)".} + ## Checks if a value is exactly null. -proc isUndefined*[T](x: T): bool {.noSideEffect, importcpp: "(# === undefined)".} - ## check if a value is exactly undefined +proc isUndefined*[T](x: T): bool {.noSideEffect, importjs: "(# === undefined)".} + ## Checks if a value is exactly undefined. # Exceptions type @@ -121,36 +121,36 @@ type JsURIError* {.importc: "URIError".} = object of JsError # New -proc newJsObject*: JsObject {.importcpp: "{@}".} - ## Creates a new empty JsObject +proc newJsObject*: JsObject {.importjs: "{@}".} + ## Creates a new empty JsObject. -proc newJsAssoc*[K: JsKey, V]: JsAssoc[K, V] {.importcpp: "{@}".} +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 - {.importcpp: "#.hasOwnProperty(#)".} + {.importjs: "#.hasOwnProperty(#)".} ## Checks, whether `x` has a property of name `prop`. -proc jsTypeOf*(x: JsObject): cstring {.importcpp: "typeof(#)".} +proc jsTypeOf*(x: JsObject): cstring {.importjs: "typeof(#)".} ## Returns the name of the JsObject's JavaScript type as a cstring. -proc jsNew*(x: auto): JsObject {.importcpp: "(new #)".} +proc jsNew*(x: auto): JsObject {.importjs: "(new #)".} ## Turns a regular function call into an invocation of the - ## JavaScript's `new` operator + ## JavaScript's `new` operator. -proc jsDelete*(x: auto): JsObject {.importcpp: "(delete #)".} - ## JavaScript's `delete` operator +proc jsDelete*(x: auto): JsObject {.importjs: "(delete #)".} + ## JavaScript's `delete` operator. proc require*(module: cstring): JsObject {.importc.} - ## JavaScript's `require` function + ## JavaScript's `require` function. # Conversion to and from JsObject -proc to*(x: JsObject, T: typedesc): T {.importcpp: "(#)".} +proc to*(x: JsObject, T: typedesc): T {.importjs: "(#)".} ## Converts a JsObject `x` to type `T`. -proc toJs*[T](val: T): JsObject {.importcpp: "(#)".} - ## Converts a value of any type to type JsObject +proc toJs*[T](val: T): JsObject {.importjs: "(#)".} + ## Converts a value of any type to type JsObject. template toJs*(s: string): JsObject = cstring(s).toJs @@ -160,50 +160,51 @@ macro jsFromAst*(n: untyped): untyped = result = newProc(procType = nnkDo, body = result) return quote: toJs(`result`) -proc `&`*(a, b: cstring): cstring {.importcpp: "(# + #)".} - ## Concatenation operator for JavaScript strings - -proc `+` *(x, y: JsObject): JsObject {.importcpp: "(# + #)".} -proc `-` *(x, y: JsObject): JsObject {.importcpp: "(# - #)".} -proc `*` *(x, y: JsObject): JsObject {.importcpp: "(# * #)".} -proc `/` *(x, y: JsObject): JsObject {.importcpp: "(# / #)".} -proc `%` *(x, y: JsObject): JsObject {.importcpp: "(# % #)".} -proc `+=` *(x, y: JsObject): JsObject {.importcpp: "(# += #)", discardable.} -proc `-=` *(x, y: JsObject): JsObject {.importcpp: "(# -= #)", discardable.} -proc `*=` *(x, y: JsObject): JsObject {.importcpp: "(# *= #)", discardable.} -proc `/=` *(x, y: JsObject): JsObject {.importcpp: "(# /= #)", discardable.} -proc `%=` *(x, y: JsObject): JsObject {.importcpp: "(# %= #)", discardable.} -proc `++` *(x: JsObject): JsObject {.importcpp: "(++#)".} -proc `--` *(x: JsObject): JsObject {.importcpp: "(--#)".} -proc `>` *(x, y: JsObject): JsObject {.importcpp: "(# > #)".} -proc `<` *(x, y: JsObject): JsObject {.importcpp: "(# < #)".} -proc `>=` *(x, y: JsObject): JsObject {.importcpp: "(# >= #)".} -proc `<=` *(x, y: JsObject): JsObject {.importcpp: "(# <= #)".} -proc `**` *(x, y: JsObject): JsObject {.importcpp: "((#) ** #)".} -proc `and`*(x, y: JsObject): JsObject {.importcpp: "(# && #)".} -proc `or` *(x, y: JsObject): JsObject {.importcpp: "(# || #)".} -proc `not`*(x: JsObject): JsObject {.importcpp: "(!#)".} -proc `in` *(x, y: JsObject): JsObject {.importcpp: "(# in #)".} - -proc `[]`*(obj: JsObject, field: cstring): JsObject {.importcpp: getImpl.} - ## Return the value of a property of name `field` from a JsObject `obj`. - -proc `[]`*(obj: JsObject, field: int): JsObject {.importcpp: getImpl.} - ## Return the value of a property of name `field` from a JsObject `obj`. - -proc `[]=`*[T](obj: JsObject, field: cstring, val: T) {.importcpp: setImpl.} - ## Set the value of a property of name `field` in a JsObject `obj` to `v`. - -proc `[]=`*[T](obj: JsObject, field: int, val: T) {.importcpp: setImpl.} - ## Set the value of a property of name `field` in a JsObject `obj` to `v`. +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 - {.importcpp: getImpl.} - ## Return the value of a property of name `field` from a JsAssoc `obj`. + {.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) - {.importcpp: setImpl.} - ## Set the value of a property of name `field` in a JsAssoc `obj` to `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)] @@ -211,8 +212,8 @@ proc `[]`*[V](obj: JsAssoc[cstring, V], field: string): V = proc `[]=`*[V](obj: JsAssoc[cstring, V], field: string, val: V) = obj[cstring(field)] = val -proc `==`*(x, y: JsRoot): bool {.importcpp: "(# === #)".} - ## Compare two JsObjects or JsAssocs. Be careful though, as this is comparison +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*. @@ -226,36 +227,40 @@ macro `.`*(obj: JsObject, field: untyped): JsObject = assert obj.a.to(int) == 20 if validJsName($field): let importString = "#." & $field + let helperName = genSym(nskProc, "helper") result = quote do: - proc helper(o: JsObject): JsObject - {.importcpp: `importString`, gensym.} - helper(`obj`) + proc `helperName`(o: JsObject): JsObject + {.importjs: `importString`.} + `helperName`(`obj`) else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName($field) + mangledNames[$field] = mangleJsName($field) let importString = "#." & mangledNames[$field] + let helperName = genSym(nskProc, "helper") result = quote do: - proc helper(o: JsObject): JsObject - {.importcpp: `importString`, gensym.} - helper(`obj`) + 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 helper(o: JsObject, v: auto) - {.importcpp: `importString`, gensym.} - helper(`obj`, `value`) + proc `helperName`(o: JsObject, v: auto) + {.importjs: `importString`.} + `helperName`(`obj`, `value`) else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName($field) + mangledNames[$field] = mangleJsName($field) let importString = "#." & mangledNames[$field] & " = #" + let helperName = genSym(nskProc, "helper") result = quote do: - proc helper(o: JsObject, v: auto) - {.importcpp: `importString`, gensym.} - helper(`obj`, `value`) + proc `helperName`(o: JsObject, v: auto) + {.importjs: `importString`.} + `helperName`(`obj`, `value`) macro `.()`*(obj: JsObject, field: untyped, @@ -267,26 +272,26 @@ macro `.()`*(obj: JsObject, ## so be careful when using this.) ## ## Example: - ## - ## .. code-block:: 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. + ## ```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) + mangledNames[$field] = mangleJsName($field) importString = "#." & mangledNames[$field] & "(@)" - result = quote: - proc helper(o: JsObject): JsObject - {.importcpp: `importString`, gensym, discardable.} - helper(`obj`) + 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")) @@ -301,12 +306,13 @@ macro `.`*[K: cstring, V](obj: JsAssoc[K, V], importString = "#." & $field else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName($field) + mangledNames[$field] = mangleJsName($field) importString = "#." & mangledNames[$field] + let helperName = genSym(nskProc, "helper") result = quote do: - proc helper(o: type(`obj`)): `obj`.V - {.importcpp: `importString`, gensym.} - helper(`obj`) + proc `helperName`(o: type(`obj`)): `obj`.V + {.importjs: `importString`.} + `helperName`(`obj`) macro `.=`*[K: cstring, V](obj: JsAssoc[K, V], field: untyped, @@ -318,12 +324,13 @@ macro `.=`*[K: cstring, V](obj: JsAssoc[K, V], importString = "#." & $field & " = #" else: if not mangledNames.hasKey($field): - mangledNames[$field] = $mangleJsName($field) + mangledNames[$field] = mangleJsName($field) importString = "#." & mangledNames[$field] & " = #" + let helperName = genSym(nskProc, "helper") result = quote do: - proc helper(o: type(`obj`), v: `obj`.V) - {.importcpp: `importString`, gensym.} - helper(`obj`, `value`) + proc `helperName`(o: type(`obj`), v: `obj`.V) + {.importjs: `importString`.} + `helperName`(`obj`, `value`) macro `.()`*[K: cstring, V: proc](obj: JsAssoc[K, V], field: untyped, @@ -341,14 +348,14 @@ macro `.()`*[K: cstring, V: proc](obj: JsAssoc[K, V], # Iterators: iterator pairs*(obj: JsObject): (cstring, JsObject) = - ## Yields tuples of type ``(cstring, JsObject)``, with the first entry + ## 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`];".} + {.emit: " if (!`obj`.hasOwnProperty(`k`)) { continue; }".} + {.emit: " `v` = `obj`[`k`];".} yield (k, v) {.emit: "}".} @@ -356,8 +363,8 @@ 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];".} + {.emit: " if (!`obj`.hasOwnProperty(k)) { continue; }".} + {.emit: " `v` = `obj`[k];".} yield v {.emit: "}".} @@ -365,18 +372,18 @@ 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;".} + {.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 + ## 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`];".} + {.emit: " if (!`assoc`.hasOwnProperty(`k`)) { continue; }".} + {.emit: " `v` = `assoc`[`k`];".} yield (k.toJsKey(K), v) {.emit: "}".} @@ -384,8 +391,8 @@ 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];".} + {.emit: " if (!`assoc`.hasOwnProperty(k)) { continue; }".} + {.emit: " `v` = `assoc`[k];".} yield v {.emit: "}".} @@ -393,34 +400,33 @@ 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;".} + {.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 ``{}``. + ## 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: ## - ## .. code-block:: 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 ... + ## ```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 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` @@ -455,39 +461,40 @@ macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto = # from a proc, `this` being the first argument. proc replaceSyms(n: NimNode): NimNode = - if n.kind == nnkSym: + if n.kind == nnkSym: result = newIdentNode($n) - else: + else: result = n for i in 0..<n.len: result[i] = replaceSyms(n[i]) -macro bindMethod*(procedure: typed): auto = +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, 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; + ## }; + ## ``` ## - ## .. code-block:: js - ## var obj = {a: 10}; - ## obj.someMethod = function() { - ## return this.a + 42; - ## }; - ## - ## We can achieve this using the ``bindMethod`` macro: + ## We can achieve this using the `bindMethod` macro: ## - ## .. code-block:: nim - ## let obj = JsObject{ a: 10 } - ## proc someMethodImpl(that: JsObject): int = - ## that.a.to(int) + 42 - ## obj.someMethod = bindMethod someMethodImpl + ## ```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 + ## # 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 diff --git a/lib/js/jsre.nim b/lib/js/jsre.nim index f5c3cc1ac..2d931eb20 100644 --- a/lib/js/jsre.nim +++ b/lib/js/jsre.nim @@ -1,46 +1,97 @@ ## Regular Expressions for the JavaScript target. ## * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - -runnableExamples: - let jsregex: RegExp = newRegExp(r"\s+", r"i") - jsregex.compile(r"\w+", r"i") - doAssert jsregex.test(r"nim javascript") - doAssert jsregex.exec(r"nim javascript") == @["nim".cstring] - doAssert jsregex.toString() == r"/\w+/i" - jsregex.compile(r"[0-9]", r"i") - doAssert jsregex.test(r"0123456789abcd") - - -when not defined(js) and not defined(Nimdoc): +when not defined(js): {.error: "This module only works on the JavaScript platform".} -type RegExp* {.importjs.} = object ## Regular Expressions for JavaScript target. - flags* {.importjs.}: cstring ## cstring that contains the flags of the RegExp object. - dotAll* {.importjs.}: bool ## Whether `.` matches newlines or not. - global* {.importjs.}: bool ## Whether to test against all possible matches in a string, or only against the first. - ignoreCase* {.importjs.}: bool ## Whether to ignore case while attempting a match in a string. - multiline* {.importjs.}: bool ## Whether to search in strings across multiple lines. - source* {.importjs.}: cstring ## The text of the pattern. - sticky* {.importjs.}: bool ## Whether the search is sticky. - unicode* {.importjs.}: bool ## Whether Unicode features are enabled. - lastIndex* {.importjs.}: cint ## Index at which to start the next match (read/write property). - input* {.importjs.}: cstring ## Read-only and modified on successful match. - lastMatch* {.importjs.}: cstring ## Ditto. - lastParen* {.importjs.}: cstring ## Ditto. - leftContext* {.importjs.}: cstring ## Ditto. - rightContext* {.importjs.}: cstring ## Ditto. +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 exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "#.exec(#)".} - ## Executes a search for a match in its string parameter. +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 test*(self: RegExp; pattern: cstring): bool {.importjs: "#.test(#)".} - ## Tests for a match in its string parameter. +func match*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.match(#) || [])".} + ## Returns an array of matches of a RegExp against given string -func toString*(self: RegExp): cstring {.importjs: "#.toString()".} +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) == @[] |