mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-02 03:02:31 +00:00
committed by
Andreas Rumpf
parent
74ae66ddbc
commit
536015ee7b
@@ -415,6 +415,9 @@ Modules for JS backend
|
||||
* `dom <dom.html>`_
|
||||
Declaration of the Document Object Model for the JS backend.
|
||||
|
||||
* `jsffi <jsffi.html>`_
|
||||
Types and macros for easier interaction with JavaScript.
|
||||
|
||||
|
||||
Deprecated modules
|
||||
------------------
|
||||
|
||||
436
lib/js/jsffi.nim
Normal file
436
lib/js/jsffi.nim
Normal file
@@ -0,0 +1,436 @@
|
||||
#
|
||||
#
|
||||
# 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:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## # 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 {. importcpp: "$(#)" .}
|
||||
##
|
||||
## # 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(nimdoc) and not defined(nimsuggest):
|
||||
{.fatal: "Module jsFFI is designed to be used with the JavaScript backend.".}
|
||||
|
||||
import 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: cstring): cstring =
|
||||
inc nameCounter
|
||||
"mangledName" & $nameCounter
|
||||
|
||||
type
|
||||
JsRoot* = ref object of RootObj
|
||||
## Root type of both JsObject and JsAssoc
|
||||
JsObject* = ref object of JsRoot
|
||||
## Dynamically typed wrapper around a JavaScript object.
|
||||
JsAssoc*[K, V] = ref object of JsRoot
|
||||
## Statically typed wrapper around a JavaScript object.
|
||||
NotString = concept c
|
||||
c isnot string
|
||||
|
||||
# New
|
||||
proc newJsObject*: JsObject {. importcpp: "{@}" .}
|
||||
## Creates a new empty JsObject
|
||||
proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .}
|
||||
## Creates a new empty JsAssoc with key type `K` and value type `V`.
|
||||
|
||||
# Checks
|
||||
proc hasOwnProperty*(x: JsObject, prop: cstring): bool
|
||||
{. importcpp: "#.hasOwnProperty(#)" .}
|
||||
## Checks, whether `x` has a property of name `prop`.
|
||||
|
||||
proc jsTypeOf*(x: JsObject): cstring {. importcpp: "typeof(#)" .}
|
||||
## Returns the name of the JsObject's JavaScript type as a cstring.
|
||||
|
||||
# Conversion to and from JsObject
|
||||
proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .}
|
||||
## Converts a JsObject `x` to type `T`.
|
||||
proc toJs*[T](val: T): JsObject {. importcpp: "(#)" .}
|
||||
## Converts a value of any type to type JsObject
|
||||
|
||||
proc `[]`*(obj: JsObject, field: cstring): 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 `[]`*[K: NotString, V](obj: JsAssoc[K, V], field: K): V
|
||||
{. importcpp: getImpl .}
|
||||
## Return the value of a property of name `field` from a JsAssoc `obj`.
|
||||
|
||||
proc `[]`*[V](obj: JsAssoc[string, V], field: cstring): V
|
||||
{. importcpp: getImpl .}
|
||||
## Return the value of a property of name `field` from a JsAssoc `obj`.
|
||||
|
||||
proc `[]=`*[K: NotString, 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`.
|
||||
|
||||
proc `[]=`*[V](obj: JsAssoc[string, V], field: cstring, val: V)
|
||||
{. importcpp: setImpl .}
|
||||
## Set the value of a property of name `field` in a JsAssoc `obj` to `v`.
|
||||
|
||||
proc `==`*(x, y: JsRoot): bool {. importcpp: "(# === #)" .}
|
||||
## Compare 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: static[cstring]): JsObject =
|
||||
## Experimental dot accessor (get) for type JsObject.
|
||||
## Returns the value of a property of name `field` from a JsObject `x`.
|
||||
##
|
||||
## Example:
|
||||
##
|
||||
## .. code-block:: nim
|
||||
##
|
||||
## let obj = newJsObject()
|
||||
## obj.a = 20
|
||||
## console.log(obj.a) # puts 20 onto the console.
|
||||
if validJsName($field):
|
||||
let importString = "#." & $field
|
||||
result = quote do:
|
||||
proc helper(o: JsObject): JsObject
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`obj`)
|
||||
else:
|
||||
if not mangledNames.hasKey($field):
|
||||
mangledNames[$field] = $mangleJsName(field)
|
||||
let importString = "#." & mangledNames[$field]
|
||||
result = quote do:
|
||||
proc helper(o: JsObject): JsObject
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`obj`)
|
||||
|
||||
macro `.=`*(obj: JsObject, field: static[cstring], 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 & " = #"
|
||||
result = quote do:
|
||||
proc helper(o: JsObject, v: auto)
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`obj`, `value`)
|
||||
else:
|
||||
if not mangledNames.hasKey($field):
|
||||
mangledNames[$field] = $mangleJsName(field)
|
||||
let importString = "#." & mangledNames[$field] & " = #"
|
||||
result = quote do:
|
||||
proc helper(o: JsObject, v: auto)
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`obj`, `value`)
|
||||
|
||||
macro `.()`*(obj: JsObject, field: static[cstring],
|
||||
args: varargs[JsObject, toJs]): 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:
|
||||
##
|
||||
## .. 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.
|
||||
var importString: string
|
||||
if validJsName($field):
|
||||
importString = "#." & $field & "(@)"
|
||||
else:
|
||||
if not mangledNames.hasKey($field):
|
||||
mangledNames[$field] = $mangleJsName(field)
|
||||
importString = "#." & mangledNames[$field] & "(@)"
|
||||
result = quote do:
|
||||
proc helper(o: JsObject): JsObject
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`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: string | cstring, V](obj: JsAssoc[K, V],
|
||||
field: static[cstring]): 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]
|
||||
result = quote do:
|
||||
proc helper(o: type(`obj`)): `obj`.V
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`obj`)
|
||||
|
||||
macro `.=`*[K: string | cstring, V](obj: JsAssoc[K, V],
|
||||
field: static[cstring], 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] & " = #"
|
||||
result = quote do:
|
||||
proc helper(o: type(`obj`), v: `obj`.V)
|
||||
{. importcpp: `importString`, gensym .}
|
||||
helper(`obj`, `value`)
|
||||
|
||||
macro `.()`*[K: string | cstring, V: proc](obj: JsAssoc[K, V],
|
||||
field: static[cstring], 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[0].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, 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.
|
||||
when K is string:
|
||||
var k: cstring
|
||||
else:
|
||||
var k: K
|
||||
var v: V
|
||||
{.emit: "for (var `k` in `assoc`) {".}
|
||||
{.emit: " if (!`assoc`.hasOwnProperty(`k`)) continue;".}
|
||||
{.emit: " `v`=`assoc`[`k`];".}
|
||||
when K is string:
|
||||
yield ($k, v)
|
||||
else:
|
||||
yield (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,V](assoc: JSAssoc[K,V]): K =
|
||||
## Yields the `keys` in a JsAssoc.
|
||||
when K is string:
|
||||
var k: cstring
|
||||
else:
|
||||
var k: K
|
||||
{.emit: "for (var `k` in `assoc`) {".}
|
||||
{.emit: " if (!`assoc`.hasOwnProperty(`k`)) continue;".}
|
||||
when K is string:
|
||||
yield $k
|
||||
else:
|
||||
yield 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:
|
||||
##
|
||||
## .. 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 ...
|
||||
##
|
||||
## 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 = !"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.
|
||||
|
||||
macro bindMethod*(procedure: typed): auto =
|
||||
## 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:
|
||||
##
|
||||
## .. code-block:: js
|
||||
## var obj = {a: 10};
|
||||
## obj.someMethod = function() {
|
||||
## return this.a + 42;
|
||||
## };
|
||||
##
|
||||
## 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
|
||||
##
|
||||
## # 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.symbol)
|
||||
else:
|
||||
procedure
|
||||
args = rawProc[3]
|
||||
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 .} : `thisType`
|
||||
call = newNimNode(nnkCall).add(rawProc[0], thisQuote[0][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[0], call)
|
||||
)
|
||||
result = body
|
||||
267
tests/js/tjsffi.nim
Normal file
267
tests/js/tjsffi.nim
Normal file
@@ -0,0 +1,267 @@
|
||||
discard """
|
||||
output: '''true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true
|
||||
true'''
|
||||
"""
|
||||
|
||||
import macros, jsffi
|
||||
|
||||
# Tests for JsObject
|
||||
# Test JsObject []= and []
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsObject()
|
||||
var working = true
|
||||
obj["a"] = 11
|
||||
obj["b"] = "test"
|
||||
obj["c"] = "test".cstring
|
||||
working = working and obj["a"].to(int) == 11
|
||||
working = working and obj["c"].to(cstring) == "test".cstring
|
||||
working
|
||||
echo test()
|
||||
|
||||
# Test JsObject .= and .
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsObject()
|
||||
var working = true
|
||||
obj.a = 11
|
||||
obj.b = "test"
|
||||
obj.c = "test".cstring
|
||||
obj.`$!&` = 42
|
||||
obj.`while` = 99
|
||||
working = working and obj.a.to(int) == 11
|
||||
working = working and obj.b.to(string) == "test"
|
||||
working = working and obj.c.to(cstring) == "test".cstring
|
||||
working = working and obj.`$!&`.to(int) == 42
|
||||
working = working and obj.`while`.to(int) == 99
|
||||
working
|
||||
echo test()
|
||||
|
||||
# Test JsObject .()
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsObject()
|
||||
obj.`?!$` = proc(x, y, z: int, t: string): string = t & $(x + y + z)
|
||||
obj.`?!$`(1, 2, 3, "Result is: ").to(string) == "Result is: 6"
|
||||
echo test()
|
||||
|
||||
# Test JsObject []()
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsObject()
|
||||
obj.a = proc(x, y, z: int, t: string): string = t & $(x + y + z)
|
||||
let call = obj["a"].to(proc(x, y, z: int, t: string): string)
|
||||
call(1, 2, 3, "Result is: ") == "Result is: 6"
|
||||
echo test()
|
||||
|
||||
# Test JsObject Iterators
|
||||
block:
|
||||
proc testPairs(): bool =
|
||||
let obj = newJsObject()
|
||||
var working = true
|
||||
obj.a = 10
|
||||
obj.b = 20
|
||||
obj.c = 30
|
||||
for k, v in obj.pairs:
|
||||
case $k
|
||||
of "a":
|
||||
working = working and v.to(int) == 10
|
||||
of "b":
|
||||
working = working and v.to(int) == 20
|
||||
of "c":
|
||||
working = working and v.to(int) == 30
|
||||
else:
|
||||
return false
|
||||
working
|
||||
proc testItems(): bool =
|
||||
let obj = newJsObject()
|
||||
var working = true
|
||||
obj.a = 10
|
||||
obj.b = 20
|
||||
obj.c = 30
|
||||
for v in obj.items:
|
||||
working = working and v.to(int) in [10, 20, 30]
|
||||
working
|
||||
proc testKeys(): bool =
|
||||
let obj = newJsObject()
|
||||
var working = true
|
||||
obj.a = 10
|
||||
obj.b = 20
|
||||
obj.c = 30
|
||||
for v in obj.keys:
|
||||
working = working and $v in ["a", "b", "c"]
|
||||
working
|
||||
proc test(): bool = testPairs() and testItems() and testKeys()
|
||||
echo test()
|
||||
|
||||
# Test JsObject equality
|
||||
block:
|
||||
proc test(): bool =
|
||||
{. emit: "var comparison = {a: 22, b: 'test'};" .}
|
||||
var comparison {. importc, nodecl .}: JsObject
|
||||
let obj = newJsObject()
|
||||
obj.a = 22
|
||||
obj.b = "test".cstring
|
||||
obj.a == comparison.a and obj.b == comparison.b
|
||||
echo test()
|
||||
|
||||
# Test JsObject literal
|
||||
block:
|
||||
proc test(): bool =
|
||||
{. emit: "var comparison = {a: 22, b: 'test'};" .}
|
||||
var comparison {. importc, nodecl .}: JsObject
|
||||
let obj = JsObject{ a: 22, b: "test".cstring }
|
||||
obj.a == comparison.a and obj.b == comparison.b
|
||||
echo test()
|
||||
|
||||
# Tests for JsAssoc
|
||||
# Test JsAssoc []= and []
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsAssoc[int, int]()
|
||||
var working = true
|
||||
obj[1] = 11
|
||||
working = working and not compiles(obj["a"] = 11)
|
||||
working = working and not compiles(obj["a"])
|
||||
working = working and not compiles(obj[2] = "test")
|
||||
working = working and not compiles(obj[3] = "test".cstring)
|
||||
working = working and obj[1] == 11
|
||||
working
|
||||
echo test()
|
||||
|
||||
# Test JsAssoc .= and .
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsAssoc[string, int]()
|
||||
var working = true
|
||||
obj.a = 11
|
||||
obj.`$!&` = 42
|
||||
working = working and not compiles(obj.b = "test")
|
||||
working = working and not compiles(obj.c = "test".cstring)
|
||||
working = working and obj.a == 11
|
||||
working = working and obj.`$!&` == 42
|
||||
working
|
||||
echo test()
|
||||
|
||||
# Test JsAssoc .()
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsAssoc[string, proc(e: int): int]()
|
||||
obj.a = proc(e: int): int = e * e
|
||||
obj.a(10) == 100
|
||||
echo test()
|
||||
|
||||
# Test JsAssoc []()
|
||||
block:
|
||||
proc test(): bool =
|
||||
let obj = newJsAssoc[string, proc(e: int): int]()
|
||||
obj.a = proc(e: int): int = e * e
|
||||
let call = obj["a"]
|
||||
call(10) == 100
|
||||
echo test()
|
||||
|
||||
# Test JsAssoc Iterators
|
||||
block:
|
||||
proc testPairs(): bool =
|
||||
let obj = newJsAssoc[string, int]()
|
||||
var working = true
|
||||
obj.a = 10
|
||||
obj.b = 20
|
||||
obj.c = 30
|
||||
for k, v in obj.pairs:
|
||||
case $k
|
||||
of "a":
|
||||
working = working and v == 10
|
||||
of "b":
|
||||
working = working and v == 20
|
||||
of "c":
|
||||
working = working and v == 30
|
||||
else:
|
||||
return false
|
||||
working
|
||||
proc testItems(): bool =
|
||||
let obj = newJsAssoc[string, int]()
|
||||
var working = true
|
||||
obj.a = 10
|
||||
obj.b = 20
|
||||
obj.c = 30
|
||||
for v in obj.items:
|
||||
working = working and v in [10, 20, 30]
|
||||
working
|
||||
proc testKeys(): bool =
|
||||
let obj = newJsAssoc[string, int]()
|
||||
var working = true
|
||||
obj.a = 10
|
||||
obj.b = 20
|
||||
obj.c = 30
|
||||
for v in obj.keys:
|
||||
working = working and v in ["a", "b", "c"]
|
||||
working
|
||||
proc test(): bool = testPairs() and testItems() and testKeys()
|
||||
echo test()
|
||||
|
||||
# Test JsAssoc equality
|
||||
block:
|
||||
proc test(): bool =
|
||||
{. emit: "var comparison = {a: 22, b: 55};" .}
|
||||
var comparison {. importcpp, nodecl .}: JsAssoc[string, int]
|
||||
let obj = newJsAssoc[string, int]()
|
||||
obj.a = 22
|
||||
obj.b = 55
|
||||
obj.a == comparison.a and obj.b == comparison.b
|
||||
echo test()
|
||||
|
||||
# Test JsAssoc literal
|
||||
block:
|
||||
proc test(): bool =
|
||||
{. emit: "var comparison = {a: 22, b: 55};" .}
|
||||
var comparison {. importcpp, nodecl .}: JsAssoc[string, int]
|
||||
let obj = JsAssoc[string, int]{ a: 22, b: 55 }
|
||||
var working = true
|
||||
working = working and
|
||||
compiles(JsAssoc[int, int]{ 1: 22, 2: 55 })
|
||||
working = working and
|
||||
comparison.a == obj.a and comparison.b == obj.b
|
||||
working = working and
|
||||
not compiles(JsAssoc[string, int]{ a: "test" })
|
||||
working
|
||||
echo test()
|
||||
|
||||
# Tests for macros on non-JsRoot objects
|
||||
# Test lit
|
||||
block:
|
||||
type TestObject = object
|
||||
a: int
|
||||
b: cstring
|
||||
proc test(): bool =
|
||||
{. emit: "var comparison = {a: 1};" .}
|
||||
var comparison {. importc, nodecl .}: TestObject
|
||||
let obj = TestObject{ a: 1 }
|
||||
obj == comparison
|
||||
echo test()
|
||||
|
||||
# Test bindMethod
|
||||
block:
|
||||
type TestObject = object
|
||||
a: int
|
||||
onWhatever: proc(e: int): int
|
||||
proc handleWhatever(that: TestObject, e: int): int =
|
||||
e + that.a
|
||||
proc test(): bool =
|
||||
let obj = TestObject(a: 9, onWhatever: bindMethod(handleWhatever))
|
||||
obj.onWhatever(1) == 10
|
||||
echo test()
|
||||
@@ -39,7 +39,7 @@ srcdoc2: "impure/re;impure/nre;pure/typetraits"
|
||||
srcdoc2: "pure/concurrency/threadpool.nim;pure/concurrency/cpuinfo.nim"
|
||||
srcdoc: "system/threads.nim;system/channels.nim;js/dom"
|
||||
srcdoc2: "pure/os;pure/strutils;pure/math;pure/matchers;pure/algorithm"
|
||||
srcdoc2: "pure/stats;impure/nre;windows/winlean;pure/random"
|
||||
srcdoc2: "pure/stats;impure/nre;windows/winlean;pure/random;js/jsffi"
|
||||
srcdoc2: "pure/complex;pure/times;pure/osproc;pure/pegs;pure/dynlib;pure/strscans"
|
||||
srcdoc2: "pure/parseopt;pure/parseopt2;pure/hashes;pure/strtabs;pure/lexbase"
|
||||
srcdoc2: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql"
|
||||
|
||||
Reference in New Issue
Block a user