importjs symbol (#12218)

* importjs symbol
* importjs warning message, minor warning fixes
This commit is contained in:
Arne Döring 2019-09-20 20:26:30 +02:00 • committed by Andreas Rumpf
commit 38ab51c445
7 changed files with 388 additions and 24 deletions

View file

@ -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

View file

@ -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:

View file

@ -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",

View file

@ -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".}

View file

@ -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]

View file

@ -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

338
tests/js/tjsffi_old.nim Normal file
View file

@ -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