diff options
-rw-r--r-- | changelog.md | 3 | ||||
-rw-r--r-- | compiler/pragmas.nim | 18 | ||||
-rw-r--r-- | compiler/wordrecg.nim | 4 | ||||
-rw-r--r-- | doc/manual.rst | 21 | ||||
-rw-r--r-- | lib/js/jsffi.nim | 8 | ||||
-rw-r--r-- | tests/js/tjsffi.nim | 20 | ||||
-rw-r--r-- | tests/js/tjsffi_old.nim | 338 |
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 |