summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorArne Döring <arne.doering@gmx.net>2019-09-20 20:26:30 +0200
committerAndreas Rumpf <rumpf_a@web.de>2019-09-20 20:26:30 +0200
commit38ab51c44572b78845fa62b0c0a597a46a63ad28 (patch)
treedf3a7ffa585c333fcbe65a3f49c18bbd5d6c5e88
parent7bc5bf83345a0ebaef3dca9395f0bc7d285ba61e (diff)
downloadNim-38ab51c44572b78845fa62b0c0a597a46a63ad28.tar.gz
importjs symbol (#12218)
* importjs symbol
* importjs warning message, minor warning fixes
-rw-r--r--changelog.md3
-rw-r--r--compiler/pragmas.nim18
-rw-r--r--compiler/wordrecg.nim4
-rw-r--r--doc/manual.rst21
-rw-r--r--lib/js/jsffi.nim8
-rw-r--r--tests/js/tjsffi.nim20
-rw-r--r--tests/js/tjsffi_old.nim338
7 files changed, 388 insertions, 24 deletions
diff --git a/changelog.md b/changelog.md
index 7a14c6c28..c558b3127 100644
--- a/changelog.md
+++ b/changelog.md
@@ -75,12 +75,15 @@ iterator myitems[T](x: openarray[T]): lent T
 iterator mypairs[T](x: openarray[T]): tuple[idx: int, val: lent T]
 ```
 
+- `importjs` can now be used to import for ffi on the JS target
+
 ## Language changes
 
 - `uint64` is now finally a regular ordinal type. This means `high(uint64)` compiles
   and yields the correct value.
 
 
+
 ### Tool changes
 
 - The Nim compiler now does not recompile the Nim project via ``nim c -r`` if
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index e9a89bc44..56f5ea126 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -19,7 +19,7 @@ const
   LastCallConv* = wNoconv
 
 const
-  declPragmas = {wImportc, wImportObjC, wImportCpp, wExportc, wExportCpp,
+  declPragmas = {wImportc, wImportObjC, wImportCpp, wImportJs, wExportc, wExportCpp,
     wExportNims, wExtern, wDeprecated, wNodecl, wError, wUsed}
     ## common pragmas for declarations, to a good approximation
   procPragmas* = declPragmas + {FirstCallConv..LastCallConv,
@@ -803,6 +803,22 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
         else: invalidPragma(c, it)
       of wImportCpp:
         processImportCpp(c, sym, getOptionalStr(c, it, "$1"), it.info)
+      of wImportJs:
+        if c.config.cmd != cmdCompileToJS:
+          localError(c.config, it.info, "importjs pragma only supported when compiling to js.")
+        var strArg: PNode = nil
+        if it.kind in nkPragmaCallKinds:
+          strArg = it[1]
+          if strArg.kind notin {nkStrLit..nkTripleStrLit}:
+            localError(c.config, it.info, errStringLiteralExpected)
+        incl(sym.flags, sfImportc)
+        incl(sym.flags, sfInfixCall)
+        if strArg == nil:
+          if sym.kind in skProcKinds:
+            message(c.config, n.info, warnDeprecated, "procedure import should have an import pattern")
+          setExternName(c, sym, sym.name.s, it.info)
+        else:
+          setExternName(c, sym, strArg.strVal, it.info)
       of wImportObjC:
         processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info)
       of wAlign:
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 23c87ac39..75e5ef689 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -42,7 +42,7 @@ type
     wImmediate, wConstructor, wDestructor, wDelegator, wOverride,
     wImportCpp, wImportObjC,
     wImportCompilerProc,
-    wImportc, wExportc, wExportCpp, wExportNims, wIncompleteStruct, wRequiresInit,
+    wImportc, wImportJs, wExportc, wExportCpp, wExportNims, wIncompleteStruct, wRequiresInit,
     wAlign, wNodecl, wPure, wSideEffect, wHeader,
     wNoSideEffect, wGcSafe, wNoreturn, wMerge, wLib, wDynlib,
     wCompilerProc, wCore, wProcVar, wBase, wUsed,
@@ -129,7 +129,7 @@ const
 
     "immediate", "constructor", "destructor", "delegator", "override",
     "importcpp", "importobjc",
-    "importcompilerproc", "importc", "exportc", "exportcpp", "exportnims",
+    "importcompilerproc", "importc", "importjs", "exportc", "exportcpp", "exportnims",
     "incompletestruct",
     "requiresinit", "align", "nodecl", "pure", "sideeffect",
     "header", "nosideeffect", "gcsafe", "noreturn", "merge", "lib", "dynlib",
diff --git a/doc/manual.rst b/doc/manual.rst
index 3c194b1d2..da4517681 100644
--- a/doc/manual.rst
+++ b/doc/manual.rst
@@ -6596,6 +6596,14 @@ this to work. The conditional symbol ``cpp`` is defined when the compiler
 emits C++ code.
 
 
+ImportJs pragma
+---------------
+
+Similar to the `importcpp pragma for C++ <#foreign-function-interface-importc-pragma>`_,
+the ``importjs`` pragma can be used to import Javascript methods or
+symbols in general. The generated code then uses the Javascript method
+calling syntax: ``obj.method(arg)``.
+
 Namespaces
 ~~~~~~~~~~
 
@@ -6993,13 +7001,14 @@ spelled*:
 .. code-block::
   proc printf(formatstr: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}
 
-Note that this pragma is somewhat of a misnomer: Other backends do provide
-the same feature under the same name. Also, if one is interfacing with C++
-the `ImportCpp pragma <manual.html#implementation-specific-pragmas-importcpp-pragma>`_ and
-interfacing with Objective-C the `ImportObjC pragma
-<manual.html#implementation-specific-pragmas-importobjc-pragma>`_ can be used.
+Note that this pragma has been abused in the past to also work in the
+js backand for js objects and functions. : Other backends do provide
+the same feature under the same name. Also, when the target language
+is not set to C, other pragmas are available:
 
-The string literal passed to ``importc`` can be a format string:
+ * `importcpp <manual.html#implementation-specific-pragmas-importcpp-pragma>`_
+ * `importobjc <manual.html#implementation-specific-pragmas-importobjc-pragma>`_
+ * `importjs <manul.html#implementation-specific-pragmas-importjs-pragma>`_
 
 .. code-block:: Nim
   proc p(s: cstring) {.importc: "prefix$1".}
diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim
index 7f01ad77a..c1b835ee9 100644
--- a/lib/js/jsffi.nim
+++ b/lib/js/jsffi.nim
@@ -292,8 +292,8 @@ macro `.()`*(obj: JsObject,
       {.importcpp: `importString`, gensym, discardable.}
     helper(`obj`)
   for idx in 0 ..< args.len:
-    let paramName = newIdentNode(!("param" & $idx))
-    result[0][3].add newIdentDefs(paramName, newIdentNode(!"JsObject"))
+    let paramName = newIdentNode("param" & $idx)
+    result[0][3].add newIdentDefs(paramName, newIdentNode("JsObject"))
     result[1].add args[idx].copyNimTree
 
 macro `.`*[K: cstring, V](obj: JsAssoc[K, V],
@@ -425,7 +425,7 @@ macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto =
   ##  # This generates roughly the same JavaScript as:
   ##  {.emit: "var obj = {a: 1, k: "foo", d: 42};".}
   ##
-  let a = !"a"
+  let a = ident"a"
   var body = quote do:
     var `a` {.noinit.}: `typ`
     {.emit: "`a` = {};".}
@@ -488,7 +488,7 @@ macro bindMethod*(procedure: typed): auto =
     error("Argument has to be a proc or a symbol corresponding to a proc.")
   var
     rawProc = if procedure.kind == nnkSym:
-        getImpl(procedure.symbol)
+        getImpl(procedure)
       else:
         procedure
     args = rawProc[3]
diff --git a/tests/js/tjsffi.nim b/tests/js/tjsffi.nim
index 8bd40a3c4..af33cee8a 100644
--- a/tests/js/tjsffi.nim
+++ b/tests/js/tjsffi.nim
@@ -32,7 +32,7 @@ true
 '''
 """
 
-import macros, jsffi, jsconsole
+import jsffi, jsconsole
 
 # Tests for JsObject
 # Test JsObject []= and []
@@ -127,7 +127,7 @@ block:
 block:
   proc test(): bool =
     {. emit: "var comparison = {a: 22, b: 'test'};" .}
-    var comparison {. importc, nodecl .}: JsObject
+    var comparison {. importjs, nodecl .}: JsObject
     let obj = newJsObject()
     obj.a = 22
     obj.b = "test".cstring
@@ -138,7 +138,7 @@ block:
 block:
   proc test(): bool =
     {. emit: "var comparison = {a: 22, b: 'test'};" .}
-    var comparison {. importc, nodecl .}: JsObject
+    var comparison {. importjs, nodecl .}: JsObject
     let obj = JsObject{ a: 22, b: "test".cstring }
     obj.a == comparison.a and obj.b == comparison.b
   echo test()
@@ -233,7 +233,7 @@ block:
 block:
   proc test(): bool =
     {. emit: "var comparison = {a: 22, b: 55};" .}
-    var comparison {. importcpp, nodecl .}: JsAssoc[cstring, int]
+    var comparison {. importjs, nodecl .}: JsAssoc[cstring, int]
     let obj = newJsAssoc[cstring, int]()
     obj.a = 22
     obj.b = 55
@@ -244,7 +244,7 @@ block:
 block:
   proc test(): bool =
     {. emit: "var comparison = {a: 22, b: 55};" .}
-    var comparison {. importcpp, nodecl .}: JsAssoc[cstring, int]
+    var comparison {. importjs, nodecl .}: JsAssoc[cstring, int]
     let obj = JsAssoc[cstring, int]{ a: 22, b: 55 }
     var working = true
     working = working and
@@ -264,7 +264,7 @@ block:
     b: cstring
   proc test(): bool =
     {. emit: "var comparison = {a: 1};" .}
-    var comparison {. importc, nodecl .}: TestObject
+    var comparison {. importjs, nodecl .}: TestObject
     let obj = TestObject{ a: 1 }
     obj == comparison
   echo test()
@@ -283,7 +283,7 @@ block:
 
 block:
   {.emit: "function jsProc(n) { return n; }" .}
-  proc jsProc(x: int32): JsObject {.importc: "jsProc".}
+  proc jsProc(x: int32): JsObject {.importjs: "jsProc(#)".}
 
   proc test() =
     var x = jsProc(1)
@@ -296,8 +296,6 @@ block:
 
   test()
 
-import macros
-
 block:
   {.emit:
   """
@@ -310,8 +308,8 @@ block:
   type Event = object
     name: cstring
 
-  proc on(event: cstring, handler: proc) {.importc: "on".}
-  var jslib {.importc: "jslib", nodecl.}: JsObject
+  proc on(event: cstring, handler: proc) {.importjs: "on(#,#)".}
+  var jslib {.importjs: "jslib", nodecl.}: JsObject
 
   on("click") do (e: Event):
     console.log e
diff --git a/tests/js/tjsffi_old.nim b/tests/js/tjsffi_old.nim
new file mode 100644
index 000000000..48e06c46c
--- /dev/null
+++ b/tests/js/tjsffi_old.nim
@@ -0,0 +1,338 @@
+discard """
+output: '''
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+true
+3
+2
+12
+Event { name: 'click: test' }
+Event { name: 'reloaded: test' }
+Event { name: 'updates: test' }
+true
+true
+true
+true
+true
+true
+true
+'''
+"""
+
+## same as tjsffi, but this test uses the old names: importc and
+## importcpp. This test is for backwards compatibility.
+
+import macros, jsffi, jsconsole
+
+# 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: cstring): cstring = t & $(x + y + z)
+    obj.`?!$`(1, 2, 3, "Result is: ").to(cstring) == cstring"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[cstring, 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[cstring, 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[cstring, 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[cstring, 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[cstring, 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[cstring, int]()
+    var working = true
+    obj.a = 10
+    obj.b = 20
+    obj.c = 30
+    for v in obj.keys:
+      working = working and v in [cstring"a", cstring"b", cstring"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[cstring, int]
+    let obj = newJsAssoc[cstring, 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[cstring, int]
+    let obj = JsAssoc[cstring, 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[cstring, 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(this: TestObject, e: int): int =
+    e + this.a
+  proc test(): bool =
+    let obj = TestObject(a: 9, onWhatever: bindMethod(handleWhatever))
+    obj.onWhatever(1) == 10
+  echo test()
+
+block:
+  {.emit: "function jsProc(n) { return n; }" .}
+  proc jsProc(x: int32): JsObject {.importc: "jsProc".}
+
+  proc test() =
+    var x = jsProc(1)
+    var y = jsProc(2)
+    console.log x + y
+    console.log ++x
+
+    x += jsProc(10)
+    console.log x
+
+  test()
+
+import macros
+
+block:
+  {.emit:
+  """
+  function Event(name) { this.name = name; }
+  function on(eventName, eventHandler) { eventHandler(new Event(eventName + ": test")); }
+  var jslib = { "on": on, "subscribe": on };
+  """
+  .}
+
+  type Event = object
+    name: cstring
+
+  proc on(event: cstring, handler: proc) {.importc: "on".}
+  var jslib {.importc: "jslib", nodecl.}: JsObject
+
+  on("click") do (e: Event):
+    console.log e
+
+  jslib.on("reloaded") do:
+    console.log jsarguments[0]
+
+  # this test case is different from the above, because
+  # `subscribe` is not overloaded in the current scope
+  jslib.subscribe("updates"):
+    console.log jsarguments[0]
+
+block:
+
+  echo jsUndefined == jsNull
+  echo jsUndefined == nil
+  echo jsNull == nil
+  echo jsUndefined.isNil
+  echo jsNull.isNil
+  echo jsNull.isNull
+  echo jsUndefined.isUndefined