├── .gitignore ├── LICENSE ├── README.md ├── jsbind.nim ├── jsbind.nimble ├── jsbind ├── emcc_wrapper_win32.nim ├── emscripten.nim ├── emscripten_api.nim └── wasmrt_glue.nim └── tests ├── test.nim └── test.nims /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yuriy Glukhov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsbind 2 | 3 | Create bindings to JavaScript that work when Nim is compiled to JavaScript as well as when compiled to Asm.js (through [Emscripten](http://emscripten.org)). 4 | 5 | Here's an example of how `XMLHttpRequest` can be defined: 6 | ```nim 7 | import jsbind 8 | 9 | type XMLHTTPRequest* = ref object of JSObj # Define the type. JSObj should be the root class for such types. 10 | 11 | proc newXMLHTTPRequest*(): XMLHTTPRequest {.jsimportgWithName: "function(){return (window.XMLHttpRequest)?new XMLHttpRequest():new ActiveXObject('Microsoft.XMLHTTP')}".} 12 | 13 | proc open*(r: XMLHTTPRequest, httpMethod, url: cstring) {.jsimport.} 14 | proc send*(r: XMLHTTPRequest) {.jsimport.} 15 | proc send*(r: XMLHTTPRequest, body: cstring) {.jsimport.} 16 | 17 | proc addEventListener*(r: XMLHTTPRequest, event: cstring, listener: proc()) {.jsimport.} 18 | proc setRequestHeader*(r: XMLHTTPRequest, header, value: cstring) {.jsimport.} 19 | 20 | proc responseText*(r: XMLHTTPRequest): jsstring {.jsimportProp.} 21 | proc statusText*(r: XMLHTTPRequest): jsstring {.jsimportProp.} 22 | 23 | proc `responseType=`*(r: XMLHTTPRequest, t: cstring) {.jsimportProp.} 24 | proc response*(r: XMLHTTPRequest): JSObj {.jsimportProp.} 25 | ``` 26 | Use the bindings as normal Nim functions. They will work in both JS and Asm.js targets. 27 | ```nim 28 | proc sendRequest*(meth, url, body: string, headers: openarray[(string, string)], handler: Handler) = 29 | let oReq = newXMLHTTPRequest() 30 | var reqListener: proc() 31 | reqListener = proc () = 32 | jsUnref(reqListener) 33 | handler(($oReq.statusText, $oReq.responseText)) 34 | jsRef(reqListener) 35 | oReq.responseType = "text" 36 | oReq.addEventListener("load", reqListener) 37 | oReq.open(meth, url) 38 | for h in headers: 39 | oReq.setRequestHeader(h[0], h[1]) 40 | if body.isNil: 41 | oReq.send() 42 | else: 43 | oReq.send(body) 44 | ``` 45 | 46 | # Low-level Emscripten bindings 47 | `jsbind.emscripten` module defines the types and functions that emscripten defines along with some useful macros and pragmas such as `EM_ASM_INT`, `EM_ASM_FLOAT`, `EMSCRIPTEN_KEEPALIVE`, etc. 48 | ```nim 49 | import jsbind.emscripten 50 | proc foo() {.EMSCRIPTEN_KEEPALIVE.} = # now it's possible to call this function from JS 51 | discard EM_ASM_INT(""" 52 | alert("hello, world!"); 53 | """) # Use EM_ASM_* like you would do it in C 54 | ``` 55 | 56 | # How jsbind works 57 | When compiling to JavaScript, `jsbind` does almost nothing, translating its pragmas to corresponding `importc`, `importcpp`, etc. Basically there is no runtime cost for such bindings. The real magic happens when compiling to Emscripten. The imported functions are wrapped to `EM_ASM_*` calls, inside which the arguments are unpacked to JavaScript types as needed, and their return values are packed back to Asm.js. 58 | 59 | # Passing closures to imported functions 60 | Closures are special because they have environment that can be garbage collected when no references to the closure left. Consider the example above with `sendRequest`. The `reqListener` is passed to Emscripten/JS function and no references are left after `sendRequest` returns. Here we need to explicitly protect it from collection with `jsRef`, and not forget to unprotect it when it is no longer needed with `jsUnref`, otherwise it will leak. `jsRef` and `jsUnref` do nothing when compiled to JavaScript, still its a good practice to place them where appropriate to make your bindings compatible with Asm.js. 61 | -------------------------------------------------------------------------------- /jsbind.nim: -------------------------------------------------------------------------------- 1 | import macros, strutils 2 | 3 | proc unpackedName*(someProc: NimNode): string {.compileTime.} = 4 | var res = someProc[0] 5 | if not res.isNil and res.kind == nnkPostfix: 6 | res = res[1] 7 | if not res.isNil and res.kind == nnkAccQuoted: 8 | result = "" 9 | for c in res: 10 | result &= $c 11 | return 12 | result = $res 13 | 14 | when defined(js): 15 | type JSObj* = ref object of RootObj 16 | type jsstring* = cstring 17 | 18 | macro jsimportWithName*(name: string = "", p: untyped): typed = 19 | p.addPragma(newNimNode(nnkExprColonExpr).add( 20 | newIdentNode("importcpp"), 21 | newLit($name))) 22 | result = p 23 | 24 | macro jsimport*(p: untyped): typed = 25 | p.addPragma(newIdentNode("importcpp")) 26 | result = p 27 | 28 | macro jsimportg*(p: untyped): typed = 29 | p.addPragma(newIdentNode("importc")) 30 | result = p 31 | 32 | macro jsimportgWithName*(name: string, p: untyped): typed = 33 | p.addPragma(newNimNode(nnkExprColonExpr).add( 34 | newIdentNode("importc"), 35 | newLit($name))) 36 | result = p 37 | 38 | macro jsimportProp*(p: untyped): untyped = 39 | let n = p.unpackedName 40 | if n.endsWith("="): 41 | p.addPragma(newNimNode(nnkExprColonExpr).add( 42 | newIdentNode("importcpp"), 43 | newLit("#." & $n & "@"))) 44 | else: 45 | p.addPragma(newNimNode(nnkExprColonExpr).add( 46 | newIdentNode("importcpp"), 47 | newLit("#." & $n))) 48 | result = p 49 | 50 | template jsRef*(e: typed) = discard 51 | template jsUnref*(e: typed) = discard 52 | 53 | elif defined(emscripten) or defined(wasm): 54 | when defined(emscripten): 55 | import jsbind/emscripten 56 | else: 57 | import jsbind/wasmrt_glue 58 | import wasmrt 59 | 60 | type JSObj* = ref object of RootObj 61 | p*: cint # Internal JS handle 62 | type jsstring* = string 63 | 64 | when defined(gcDestructors): 65 | proc nimem_ps(s: cint): ref string {.EMSCRIPTEN_KEEPALIVE.} = 66 | result.new 67 | result[] = newString(s) 68 | 69 | proc nimem_sb(s: ref string): pointer {.EMSCRIPTEN_KEEPALIVE.} = 70 | addr s[][0] 71 | 72 | proc nimem_ee(s: ref string) {.EMSCRIPTEN_KEEPALIVE.} = 73 | # This function is called when wrapped js func has thrown a JS exception 74 | # We need to rethrow it as nim exception 75 | raise newException(Exception, s[]) 76 | else: 77 | proc nimem_ps(s: cint): string {.EMSCRIPTEN_KEEPALIVE.} = newString(s) 78 | proc nimem_sb(s: string): pointer {.EMSCRIPTEN_KEEPALIVE.} = unsafeAddr s[0] 79 | 80 | proc nimem_ee(s: string) {.EMSCRIPTEN_KEEPALIVE.} = 81 | # This function is called when wrapped js func has thrown a JS exception 82 | # We need to rethrow it as nim exception 83 | raise newException(Exception, s) 84 | 85 | when defined(emscripten): 86 | proc initEmbindEnv() = 87 | discard EM_ASM_INT(""" 88 | var g = ((typeof window) === 'undefined') ? global : window; 89 | g._nimem_o = {0: null}; 90 | g._nimem_i = 0; 91 | g._nimem_w = function(o) { 92 | // Wrap a JS object `o` so that it can be used in emscripten 93 | // functions. Returns an int handle to the wrapped object. 94 | if (o === null) return 0; 95 | g._nimem_o[++g._nimem_i] = o; 96 | return g._nimem_i; 97 | }; 98 | 99 | g._nimem_s = function(s) { 100 | // Wrap JS string `s` to nim string. Returns address of the 101 | // resulting nim string. 102 | var l = lengthBytesUTF8(s); 103 | var b = _nimem_ps(l); 104 | if (l != 0) { 105 | stringToUTF8(s, _nimem_sb(b), l + 1); 106 | } 107 | return b; 108 | }; 109 | 110 | g._nimem_e = function(e) { 111 | // This function is called when wrapped function has thrown an exception. 112 | // If exception is originated from nim code, it is propagated further. 113 | // If exception is originated from JS code, it is rethrown as nim exception. 114 | if (typeof e !== 'number' && e !== 'longjmp') { 115 | var s = "" + e.message; 116 | if (e.stack) s += ": " + e.stack; 117 | _nimem_ee(_nimem_s(s)); // Wrap JS exception to nim exception 118 | } 119 | else { 120 | throw e; // Propagate nim exception 121 | } 122 | }; 123 | """) 124 | else: 125 | proc initEmbindEnv() {.importwasmraw: """ 126 | var g = globalThis; 127 | g._nimem_o = _nimo; // Defined in wasmrt 128 | g._nimem_w = _nimok; // Defined in wasmrt 129 | 130 | g._nimem_s = function(s) { 131 | // Wrap JS string `s` to nim string. Returns address of the 132 | // resulting nim string. 133 | var l = s.length; 134 | var b = g._nime.nimem_ps(l); 135 | if (l != 0) { 136 | var bb = g._nime.nimem_sb(b); 137 | var mm = new Int8Array(g._nime.memory.buffer); 138 | for (i = 0; i < l; ++ i) { 139 | mm[bb + i] = s.charCodeAt(i); 140 | } 141 | } 142 | return b; 143 | }; 144 | 145 | g._nimem_p = function(p) { 146 | // Returns pointer value at address p 147 | return new Int32Array(g._nime.memory.buffer)[p >> 2]; 148 | }; 149 | 150 | g._nimem_e = function(e) { 151 | // This function is called when wrapped function has thrown an exception. 152 | // If exception is originated from nim code, it is propagated further. 153 | // If exception is originated from JS code, it is rethrown as nim exception. 154 | if (typeof e !== 'number' && e !== 'longjmp') { 155 | var s = '' + e.message; 156 | if (e.stack) s += ': ' + e.stack; 157 | g._nime.nimem_ee(_nimem_s(s)); // Wrap JS exception to nim exception 158 | } 159 | else 160 | throw e; // Propagate nim exception 161 | }; 162 | g.UTF8ToString = g._nimsj; // _nimsj is defined by wasmrt 163 | """.} 164 | 165 | initEmbindEnv() 166 | 167 | {.push stackTrace: off.} 168 | when defined(emscripten): 169 | proc finalizeEmbindObject(o: JSObj) = 170 | discard EM_ASM_INT("delete _nimem_o[$0]", o.p) 171 | else: 172 | proc finalizeEmbindObject(o: JSObj) = 173 | delete(cast[wasmrt.JSRef](o.p)) 174 | 175 | proc newEmbindObject(t: typedesc, emref: cint): t {.inline.} = 176 | result.new(cast[proc(o: t){.nimcall.}](finalizeEmbindObject)) 177 | result.p = emref 178 | 179 | proc globalEmbindObject*(t: typedesc, name: static[string]): t {.inline.} = 180 | let o = EM_ASM_INT("return _nimem_w(" & name & ")") 181 | newEmbindObject(t, o) 182 | 183 | proc nimem_new(p: cint): JSObj {.EMSCRIPTEN_KEEPALIVE.} = 184 | newEmbindObject(JSObj, p) 185 | 186 | {.pop.} 187 | 188 | template toEmPtr(s: cstring): cstring = cstring(s) 189 | template toEmPtr(s: int | uint | cint | int32 | uint32 | uint16 | int16 | bool): cint = cint(s) 190 | template toEmPtr(s: JSObj): cint = s.p 191 | template toEmPtr(s: float | float32 | float64 | cfloat | cdouble): cdouble = cdouble(s) 192 | 193 | proc getClosureAddr(s: proc): pointer {.importc: "&", nodecl.} 194 | 195 | template toEmPtr(s: proc): cint = 196 | when s is proc {.closure.}: 197 | block: 198 | cast[cint](getClosureAddr(s)) 199 | else: 200 | cast[cint](cast[pointer](s)) 201 | 202 | template emTypeToNimType(T: typedesc, v: untyped) = 203 | let tmp = v 204 | when T is JSObj: 205 | if tmp != 0: result = newEmbindObject(T, tmp) 206 | elif T is string: 207 | when defined(gcDestructors): 208 | result = cast[ref string](tmp)[] 209 | else: 210 | result = cast[string](tmp) 211 | else: 212 | result = T(tmp) 213 | 214 | template emAsmImpl(cintCall: untyped, cdoubleCall: untyped): untyped = 215 | when declared(result): 216 | type R = type(result) 217 | when R is (float32 or float64 or float): 218 | let tmp = cdoubleCall 219 | result = R(tmp) 220 | else: 221 | emTypeToNimType(R, cintCall) 222 | else: 223 | discard cintCall 224 | 225 | proc getArgNames(p: NimNode): NimNode = 226 | result = newNimNode(nnkStmtList) 227 | let parms = p.params 228 | for i in 1 ..< parms.len: 229 | let identDefs = parms[i] 230 | for j in 0 ..< identDefs.len - 2: 231 | result.add(identDefs[j]) 232 | 233 | macro forEveryArg(p: typed, s: untyped): untyped = 234 | result = newNimNode(nnkBracket) 235 | let t = getType(p) 236 | for i in 2 ..< t.len: 237 | result.add(newCall(s, newLit(i - 2), newCall("type", getType(t[i])))) 238 | 239 | template jsConvertJSToNim(t: typedesc, code: string): string = 240 | when t is JSObj: 241 | "_nimem_w(" & code & ")" 242 | elif t is string: 243 | "_nimem_s(" & code & ")" 244 | else: 245 | code 246 | 247 | template packResultCode(code: string): string = 248 | ## Given the JS code that 249 | when declared(result): 250 | "return " & jsConvertJSToNim(type(result), code) 251 | else: 252 | code 253 | 254 | template jsArgSignature(i: int, t: typedesc): string = 255 | when t is (cfloat or cdouble or float32 or float64 or float): "d" 256 | else: "i" 257 | 258 | template jsArgDef(i: int, t: typedesc): string = "a" & $i 259 | 260 | template jsArgFwd(i: int, t: typedesc): string = 261 | when t is JSObj: 262 | "_nimem_new(" & jsConvertJSToNim(t, "a" & $i) & ")" 263 | else: 264 | jsConvertJSToNim(t, "a" & $i) 265 | 266 | # const wrapDynCallInTryCatch = false 267 | 268 | proc unpackFunctionArg(jsParamName, argsSig: string, argDefsParts, argForwardParts: openarray[string], isClosure: bool): string {.compileTime.} = 269 | let argDefs = argDefsParts.join(",") 270 | var dynCall = "" 271 | when defined(emscripten): 272 | let argForwards = argForwardParts.join(",") 273 | if isClosure: 274 | let argForwardsWithEnv = (@argForwardParts & "b").join(",") 275 | dynCall = "dynCall(b?'" & argsSig & "i':'" & argsSig & "',a,b?[" & argForwardsWithEnv & "]:[" & argForwards & "])" 276 | else: 277 | dynCall = "dynCall('" & argsSig & "'," & jsParamName & ",[" & argForwards & "])" 278 | else: 279 | if isClosure: 280 | let argForwards = ("a" & @argForwardParts).join(",") 281 | let argForwardsWithEnv = (@argForwardParts & "b").join(",") 282 | dynCall = "(b?_nime._d" & argsSig & "i(" & argForwardsWithEnv & "):_nime._d" & argsSig & "(" & argForwards & "))" 283 | else: 284 | let argForwards = (jsParamName & @argForwardParts).join(",") 285 | dynCall = "_nime._d" & argsSig & "(" & argForwards & ")" 286 | 287 | 288 | # if wrapDynCallInTryCatch: 289 | # dynCall = "try{" & dynCall & "}catch(e){_nimem_e(e);}" 290 | 291 | if isClosure: 292 | when defined(emscripten): 293 | return "function(a,b){return a?function(" & argDefs & "){" & dynCall & "}:null}(getValue(" & jsParamName & ", '*'), getValue(" & jsParamName & "+4, '*'))" 294 | else: 295 | return "function(a,b){return a?function(" & argDefs & "){" & dynCall & "}:null}(_nimem_p(" & jsParamName & "), _nimem_p(" & jsParamName & "+4))" 296 | 297 | else: 298 | return jsParamName & "?function(" & argDefs & "){" & dynCall & "}:null" 299 | 300 | template unpackArgCode(index: int, arg: typed): string = 301 | ## Returns the chunk of JS code, representing the arg 302 | const jsParamName = "$" & $index 303 | 304 | when type(arg) is (string | cstring): 305 | "UTF8ToString(" & jsParamName & ")" 306 | elif type(arg) is JSObj: 307 | "_nimem_o[" & jsParamName & "]" 308 | elif type(arg) is (proc): 309 | const argsSig = "v" & join(forEveryArg(arg, jsArgSignature)) 310 | const fn = unpackFunctionArg(jsParamName, 311 | argsSig, 312 | forEveryArg(arg, jsArgDef), 313 | forEveryArg(arg, jsArgFwd), 314 | type(arg) is proc {.closure.}) 315 | fn 316 | else: 317 | jsParamName 318 | 319 | template generateDyncallForArg(arg: typed) = 320 | when not defined(emscripten): 321 | when type(arg) is (proc): 322 | const argsSig = "v" & join(forEveryArg(arg, jsArgSignature)) 323 | defineDyncall(argsSig) 324 | when type(arg) is proc {.closure.}: 325 | const argsClosureSig = argsSig & "i" 326 | defineDyncall(argsClosureSig) 327 | 328 | proc wrapIntoPragmaScope(n: NimNode, option, value: string): NimNode = 329 | result = newNimNode(nnkStmtList) 330 | result.add(newNimNode(nnkPragma).add(newIdentNode("push"), newNimNode(nnkExprColonExpr).add(newIdentNode(option), newIdentNode(value)))) 331 | result.add(n) 332 | result.add(newNimNode(nnkPragma).add(newIdentNode("pop"))) 333 | 334 | proc jsImportAux(p: NimNode, infix: bool, pName: string, property: bool = false): NimNode = 335 | let argNames = getArgNames(p) 336 | result = p 337 | 338 | var setter = false 339 | var ppName = pName 340 | if pName.endsWith("="): 341 | ppName = pName[0 .. ^2] 342 | setter = true 343 | let procName = newLit(ppName) 344 | 345 | p.body = newNimNode(nnkStmtList) 346 | 347 | let cintCall = newCall(bindSym"EM_ASM_INT", newLit("")) 348 | let cdoubleCall = newCall(bindSym"EM_ASM_DOUBLE", newLit("")) 349 | 350 | var codeNode: NimNode 351 | var argIndexStart = 0 352 | if infix: 353 | inc argIndexStart 354 | let firstArg = argNames[0] 355 | 356 | cintCall.add(newCall(bindSym"toEmPtr", firstArg)) 357 | cdoubleCall.add(newCall(bindSym"toEmPtr", firstArg)) 358 | p.body.add newCall(bindSym"generateDyncallForArg", firstArg) 359 | 360 | codeNode = quote do: 361 | unpackArgCode(0, `firstArg`) 362 | 363 | codeNode = quote do: 364 | `codeNode` & "." & `procName` 365 | if setter: 366 | codeNode = quote do: 367 | `codeNode` & "=" 368 | elif not property: 369 | codeNode = quote do: 370 | `codeNode` & "(" 371 | else: 372 | codeNode = quote do: 373 | `procName` & "(" 374 | 375 | for i in argIndexStart ..< argNames.len: 376 | let argName = argNames[i] 377 | cintCall.add(newCall(bindSym"toEmPtr", argName)) 378 | cdoubleCall.add(newCall(bindSym"toEmPtr", argName)) 379 | p.body.add newCall(bindSym"generateDyncallForArg", argName) 380 | 381 | if i != argIndexStart: 382 | codeNode = quote do: 383 | `codeNode` & "," 384 | let iLit = newLit(i) 385 | codeNode = quote do: 386 | `codeNode` & unpackArgCode(`iLit`, `argName`) 387 | 388 | if not property: 389 | codeNode = quote do: 390 | `codeNode` & ")" 391 | codeNode = quote do: 392 | packResultCode(`codeNode`) 393 | 394 | const mayThrow = true 395 | if mayThrow: 396 | codeNode = quote do: 397 | "try{" & `codeNode` & "}catch(e){_nimem_e(e)}" 398 | 399 | cintCall[1] = codeNode 400 | cdoubleCall[1] = codeNode 401 | 402 | p.body.add newCall(bindSym"emAsmImpl", cintCall, cdoubleCall) 403 | p.addPragma(newIdentNode("inline")) 404 | # echo repr(p) 405 | result = wrapIntoPragmaScope(p, "stackTrace", "off") 406 | 407 | template jsRef*(e: typed) = 408 | when e is (proc): 409 | let re = rawEnv(e) 410 | if not re.isNil: 411 | GC_ref(cast[ref RootObj](re)) 412 | else: 413 | GC_ref(e) 414 | 415 | template jsUnref*(e: typed) = 416 | when e is (proc): 417 | let re = rawEnv(e) 418 | if not re.isNil: 419 | GC_unref(cast[ref RootObj](re)) 420 | else: 421 | GC_unref(e) 422 | 423 | macro jsimportWithName*(name: string = "", p: untyped): typed = 424 | jsImportAux(p, true, $name) 425 | 426 | macro jsimport*(p: untyped): typed = 427 | jsImportAux(p, true, p.unpackedName) 428 | 429 | macro jsimportg*(p: untyped): typed = 430 | jsImportAux(p, false, p.unpackedName) 431 | 432 | macro jsimportgWithName*(name: string, p: untyped): typed = 433 | jsImportAux(p, false, $name) 434 | 435 | macro jsimportProp*(p: untyped): typed = 436 | jsImportAux(p, true, p.unpackedName, true) 437 | 438 | macro jsimportPropWithName*(name: string = "", p: untyped): typed = 439 | jsImportAux(p, true, $name, true) 440 | 441 | proc setupUnhandledExceptionHandler*() = 442 | onUnhandledException = proc(msg: string) = 443 | discard EM_ASM_INT("throw new Error(UTF8ToString($0));", cstring(msg)) 444 | 445 | template handleJSExceptions*(body: untyped) {.deprecated.} = 446 | body 447 | 448 | when false: 449 | ## Usage example: 450 | type Console* = ref object of JSObj # Declare some JS type 451 | proc log*(c: Console, s: cstring) {.jsimport.} # Declare method 452 | proc anotherLog*(c: Console, s: cstring) {.jsimportWithName: "log".} # Declare method with different name in js 453 | 454 | when defined(js): 455 | var console {.importc, nodecl.}: Console 456 | elif defined(emcsripten): 457 | var console = globalEmbindObject(Console, "console") 458 | console.log("Hello, world!"); 459 | -------------------------------------------------------------------------------- /jsbind.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.1.1" 3 | author = "Yuriy Glukhov" 4 | description = "Bind to JavaScript and Emscripten environments" 5 | license = "MIT" 6 | 7 | requires "https://github.com/yglukhov/wasmrt" 8 | 9 | task tests, "Run tests": 10 | exec "nim c -d:emscripten tests/test.nim" 11 | exec "node test.js" 12 | 13 | task wasmrt_test, "Run wasmrt tests": 14 | exec "nim c -d:wasm -o:test.wasm -d:release -d:danger tests/test.nim" 15 | exec "node $(nimble path wasmrt)/tests/runwasm.js ./test.wasm" 16 | -------------------------------------------------------------------------------- /jsbind/emcc_wrapper_win32.nim: -------------------------------------------------------------------------------- 1 | import 2 | os, osproc, strutils 3 | 4 | proc findEmcc(): string = 5 | result = addFileExt("emcc", ScriptExt) 6 | if existsFile(result): return 7 | let path = string(getEnv("PATH")) 8 | for candidate in split(path, PathSep): 9 | let x = (if candidate[0] == '"' and candidate[^1] == '"': 10 | substr(candidate, 1, candidate.len-2) else: candidate) / 11 | result 12 | if existsFile(x): 13 | let sf = x.splitFile() 14 | return sf.dir / sf.name 15 | 16 | proc main()= 17 | var args = newSeq[string]() 18 | args.add(findEmcc()) 19 | 20 | for i in 1 .. paramCount(): 21 | args.add(paramStr(i).string) 22 | 23 | let 24 | process = startProcess("python", args = args, options = {poParentStreams}) 25 | exitCode = waitForExit(process) 26 | 27 | if exitCode != 0: 28 | echo paramStr(0)," : ", args, " exitWithCode ", exitCode 29 | 30 | quit(exitCode) 31 | 32 | main() -------------------------------------------------------------------------------- /jsbind/emscripten.nim: -------------------------------------------------------------------------------- 1 | when defined(wasm) and not defined(emscripten): 2 | import ./wasmrt_glue 3 | export wasmrt_glue 4 | elif defined(emscripten): 5 | import ./emscripten_api 6 | export emscripten_api 7 | else: 8 | {.error: "emscripten module may be imported only when compiling to emscripten target".} 9 | -------------------------------------------------------------------------------- /jsbind/emscripten_api.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | import wasmrt/minify 3 | 4 | type 5 | EMSCRIPTEN_WEBGL_CONTEXT_HANDLE* = cint 6 | EM_BOOL* = cint 7 | EMSCRIPTEN_RESULT* = cint 8 | 9 | type EmscriptenWebGLContextAttributes* = object 10 | alpha*: EM_BOOL 11 | depth*: EM_BOOL 12 | stencil*: EM_BOOL 13 | antialias*: EM_BOOL 14 | premultipliedAlpha*: EM_BOOL 15 | preserveDrawingBuffer*: EM_BOOL 16 | preferLowPowerToHighPerformance*: EM_BOOL 17 | failIfMajorPerformanceCaveat*: EM_BOOL 18 | 19 | majorVersion*: cint 20 | minorVersion*: cint 21 | 22 | enableExtensionsByDefault*: EM_BOOL 23 | 24 | type EmscriptenMouseEvent* = object 25 | timestamp*: cdouble 26 | screenX*: clong 27 | screenY*: clong 28 | clientX*: clong 29 | clientY*: clong 30 | ctrlKey*: EM_BOOL 31 | shiftKey*: EM_BOOL 32 | altKey*: EM_BOOL 33 | metaKey*: EM_BOOL 34 | button*: uint16 35 | buttons*: uint16 36 | movementX*: clong 37 | movementY*: clong 38 | targetX*: clong 39 | targetY*: clong 40 | canvasX*: clong 41 | canvasY*: clong 42 | padding*: clong 43 | 44 | type EmscriptenTouchPoint* = object 45 | identifier*: clong 46 | screenX*: clong 47 | screenY*: clong 48 | clientX*: clong 49 | clientY*: clong 50 | pageX*: clong 51 | pageY*: clong 52 | isChanged*: EM_BOOL 53 | onTarget*: EM_BOOL 54 | targetX*: clong 55 | targetY*: clong 56 | canvasX*: clong 57 | canvasY*: clong 58 | 59 | type EmscriptenTouchEvent* = object 60 | numTouches*: cint 61 | ctrlKey*: EM_BOOL 62 | shiftKey*: EM_BOOL 63 | altKey*: EM_BOOL 64 | metaKey*: EM_BOOL 65 | touches*: array[32, EmscriptenTouchPoint] 66 | 67 | type EmscriptenUiEvent* = object 68 | detail*: clong 69 | documentBodyClientWidth*: cint 70 | documentBodyClientHeight*: cint 71 | windowInnerWidth*: cint 72 | windowInnerHeight*: cint 73 | windowOuterWidth*: cint 74 | windowOuterHeight*: cint 75 | scrollTop*: cint 76 | scrollLeft*: cint 77 | 78 | type EmscriptenOrientationChangeEvent* = object 79 | orientationIndex*: cint 80 | orientationAngle*: cint 81 | 82 | type EmscriptenWheelEvent* = object 83 | mouse*: EmscriptenMouseEvent 84 | deltaX*: cdouble 85 | deltaY*: cdouble 86 | deltaZ*: cdouble 87 | deltaMode*: culong 88 | 89 | type EmscriptenKeyboardEvent* = object 90 | key*: array[32, char] 91 | code*: array[32, char] 92 | location*: culong 93 | ctrlKey*: EM_BOOL 94 | shiftKey*: EM_BOOL 95 | altKey*: EM_BOOL 96 | metaKey*: EM_BOOL 97 | repeat*: EM_BOOL 98 | locale*: array[32, char] 99 | charValue*: array[32, char] 100 | charCode*: culong 101 | keyCode*: culong 102 | which*: culong 103 | 104 | type EmscriptenFocusEvent* = object 105 | nodeName*: array[128, char] 106 | id*: array[128, char] 107 | 108 | type EmscriptenFullscreenChangeEvent* = object 109 | isFullscreen*: EM_BOOL 110 | fullscreenEnabled*: EM_BOOL 111 | nodeName*: array[128, char] 112 | id*: array[128, char] 113 | elementWidth*: cint 114 | elementHeight*: cint 115 | screenWidth*: cint 116 | screenHeight*: cint 117 | 118 | type EMSCRIPTEN_FULLSCREEN_SCALE* {.size: sizeof(cint).} = enum 119 | EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT = 0 120 | EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH = 1 121 | EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT = 2 122 | EMSCRIPTEN_FULLSCREEN_SCALE_CENTER = 3 123 | 124 | type EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE* {.size: sizeof(cint).} = enum 125 | EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_NONE = 0 126 | EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF = 1 127 | EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF = 2 128 | 129 | type EMSCRIPTEN_FULLSCREEN_FILTERING* {.size: sizeof(cint).} = enum 130 | EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT = 0 131 | EMSCRIPTEN_FULLSCREEN_FILTERING_NEAREST = 1 132 | EMSCRIPTEN_FULLSCREEN_FILTERING_BILINEAR = 2 133 | 134 | type em_canvasresized_callback_func* = proc(eventType: cint, reserved, userData: pointer): EM_BOOL {.cdecl.} 135 | 136 | type EmscriptenFullscreenStrategy* = object 137 | scaleMode*: EMSCRIPTEN_FULLSCREEN_SCALE 138 | canvasResolutionScaleMode*: EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE 139 | filteringMode*: EMSCRIPTEN_FULLSCREEN_FILTERING 140 | canvasResizedCallback*: em_canvasresized_callback_func 141 | canvasResizedCallbackUserData*: pointer 142 | 143 | const EMSCRIPTEN_ORIENTATION_PORTRAIT_PRIMARY* = 1 144 | const EMSCRIPTEN_ORIENTATION_PORTRAIT_SECONDARY* = 2 145 | const EMSCRIPTEN_ORIENTATION_LANDSCAPE_PRIMARY* = 4 146 | const EMSCRIPTEN_ORIENTATION_LANDSCAPE_SECONDARY* = 8 147 | 148 | 149 | type em_callback_func* = proc() {.cdecl.} 150 | type em_arg_callback_func* = proc(p: pointer) {.cdecl.} 151 | type em_str_callback_func* = proc(s: cstring) {.cdecl.} 152 | type em_async_wget_onload_func* = proc(a: pointer, p: pointer, sz: cint) {.cdecl.} 153 | type em_mouse_callback_func* = proc(eventType: cint, mouseEvent: ptr EmscriptenMouseEvent, userData: pointer): EM_BOOL {.cdecl.} 154 | type em_touch_callback_func* = proc(eventType: cint, touchEvent: ptr EmscriptenTouchEvent, userData: pointer): EM_BOOL {.cdecl.} 155 | type em_ui_callback_func* = proc(eventType: cint, uiEvent: ptr EmscriptenUiEvent, userData: pointer): EM_BOOL {.cdecl.} 156 | type em_orientationchange_callback_func* = proc(eventType: cint, uiEvent: ptr EmscriptenOrientationChangeEvent, userData: pointer): EM_BOOL {.cdecl.} 157 | type em_wheel_callback_func* = proc(eventType: cint, wheelEvent: ptr EmscriptenWheelEvent, userData: pointer): EM_BOOL {.cdecl.} 158 | type em_key_callback_func* = proc(eventType: cint, keyEvent: ptr EmscriptenKeyboardEvent, userData: pointer): EM_BOOL {.cdecl.} 159 | type em_focus_callback_func* = proc(eventType: cint, focusEvet: ptr EmscriptenFocusEvent, userData: pointer): EM_BOOL {.cdecl.} 160 | type em_webgl_context_callback* = proc(eventType: cint, reserved: pointer, userData: pointer): EM_BOOL {.cdecl.} 161 | type em_fullscreenchange_callback_func* = proc(eventType: cint, fullscreenChangeEvent: ptr EmscriptenFullscreenChangeEvent, userData: pointer): EM_BOOL {.cdecl.} 162 | 163 | {.push importc.} 164 | proc emscripten_webgl_init_context_attributes*(attributes: ptr EmscriptenWebGLContextAttributes) 165 | proc emscripten_webgl_create_context*(target: cstring, attributes: ptr EmscriptenWebGLContextAttributes): EMSCRIPTEN_WEBGL_CONTEXT_HANDLE 166 | proc emscripten_webgl_make_context_current*(context: EMSCRIPTEN_WEBGL_CONTEXT_HANDLE): EMSCRIPTEN_RESULT 167 | proc emscripten_set_main_loop*(f: em_callback_func, fps, simulate_infinite_loop: cint) 168 | proc emscripten_cancel_main_loop*() 169 | 170 | proc emscripten_set_mousedown_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_mouse_callback_func): EMSCRIPTEN_RESULT 171 | proc emscripten_set_mouseup_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_mouse_callback_func): EMSCRIPTEN_RESULT 172 | proc emscripten_set_mousemove_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_mouse_callback_func): EMSCRIPTEN_RESULT 173 | 174 | proc emscripten_set_touchstart_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT 175 | proc emscripten_set_touchend_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT 176 | proc emscripten_set_touchmove_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT 177 | proc emscripten_set_touchcancel_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT 178 | 179 | proc emscripten_set_wheel_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_wheel_callback_func): EMSCRIPTEN_RESULT 180 | 181 | proc emscripten_set_resize_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_ui_callback_func): EMSCRIPTEN_RESULT 182 | proc emscripten_set_scroll_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_ui_callback_func): EMSCRIPTEN_RESULT 183 | 184 | proc emscripten_set_orientationchange_callback_on_thread*(userData: pointer, useCapture: EM_BOOL, callback: em_orientationchange_callback_func): EMSCRIPTEN_RESULT 185 | 186 | proc emscripten_get_mouse_status*(mouseState: ptr EmscriptenMouseEvent): EMSCRIPTEN_RESULT 187 | 188 | proc emscripten_async_wget_data*(url: cstring, arg: pointer, onload: em_async_wget_onload_func, onerror: em_arg_callback_func) 189 | 190 | proc emscripten_set_keypress_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_key_callback_func): EMSCRIPTEN_RESULT 191 | proc emscripten_set_keydown_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_key_callback_func): EMSCRIPTEN_RESULT 192 | proc emscripten_set_keyup_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_key_callback_func): EMSCRIPTEN_RESULT 193 | 194 | proc emscripten_set_blur_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_focus_callback_func): EMSCRIPTEN_RESULT 195 | proc emscripten_set_focus_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_focus_callback_func): EMSCRIPTEN_RESULT 196 | 197 | proc emscripten_set_webglcontextlost_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_webgl_context_callback): EMSCRIPTEN_RESULT 198 | proc emscripten_set_webglcontextrestored_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_webgl_context_callback): EMSCRIPTEN_RESULT 199 | proc emscripten_is_webgl_context_lost*(target: cstring): EM_BOOL 200 | 201 | proc emscripten_set_element_css_size*(target: cstring, width, height: cdouble): EMSCRIPTEN_RESULT 202 | proc emscripten_get_device_pixel_ratio*(): cdouble 203 | 204 | proc emscripten_request_fullscreen*(target: cstring, deferUntilInEventHandler: EM_BOOL): EMSCRIPTEN_RESULT 205 | proc emscripten_request_fullscreen_strategy*(target: cstring, deferUntilInEventHandler: EM_BOOL, fullscreenStrategy: ptr EmscriptenFullscreenStrategy): EMSCRIPTEN_RESULT 206 | proc emscripten_exit_fullscreen*(): EMSCRIPTEN_RESULT 207 | proc emscripten_enter_soft_fullscreen*(target: cstring, fullscreenStrategy: ptr EmscriptenFullscreenStrategy): EMSCRIPTEN_RESULT 208 | proc emscripten_exit_soft_fullscreen*(): EMSCRIPTEN_RESULT 209 | proc emscripten_get_fullscreen_status*(fullscreenStatus: ptr EmscriptenFullscreenChangeEvent): EMSCRIPTEN_RESULT 210 | proc emscripten_set_fullscreenchange_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_fullscreenchange_callback_func): EMSCRIPTEN_RESULT 211 | 212 | proc emscripten_lock_orientation*(allowedOrientations: cint): EMSCRIPTEN_RESULT 213 | {.pop.} 214 | 215 | # backward compatibility with emcc 1.37 216 | proc emscripten_set_mousedown_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_mouse_callback_func): EMSCRIPTEN_RESULT {.inline.} = 217 | emscripten_set_mousedown_callback_on_thread(target, userData, useCapture, callback) 218 | proc emscripten_set_mouseup_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_mouse_callback_func): EMSCRIPTEN_RESULT {.inline.} = 219 | emscripten_set_mouseup_callback_on_thread(target, userData, useCapture, callback) 220 | proc emscripten_set_mousemove_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_mouse_callback_func): EMSCRIPTEN_RESULT {.inline.} = 221 | emscripten_set_mousemove_callback_on_thread(target, userData, useCapture, callback) 222 | proc emscripten_set_touchstart_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT {.inline.} = 223 | emscripten_set_touchstart_callback_on_thread(target, userData, useCapture, callback) 224 | proc emscripten_set_touchend_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT {.inline.} = 225 | emscripten_set_touchend_callback_on_thread(target, userData, useCapture, callback) 226 | proc emscripten_set_touchmove_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT {.inline.} = 227 | emscripten_set_touchmove_callback_on_thread(target, userData, useCapture, callback) 228 | proc emscripten_set_touchcancel_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_touch_callback_func): EMSCRIPTEN_RESULT {.inline.} = 229 | emscripten_set_touchcancel_callback_on_thread(target, userData, useCapture, callback) 230 | proc emscripten_set_wheel_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_wheel_callback_func): EMSCRIPTEN_RESULT {.inline.} = 231 | emscripten_set_wheel_callback_on_thread(target, userData, useCapture, callback) 232 | proc emscripten_set_resize_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_ui_callback_func): EMSCRIPTEN_RESULT {.inline.} = 233 | emscripten_set_resize_callback_on_thread(target, userData, useCapture, callback) 234 | proc emscripten_set_scroll_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_ui_callback_func): EMSCRIPTEN_RESULT {.inline.} = 235 | emscripten_set_scroll_callback_on_thread(target, userData, useCapture, callback) 236 | proc emscripten_set_keypress_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_key_callback_func): EMSCRIPTEN_RESULT {.inline.} = 237 | emscripten_set_keypress_callback_on_thread(target, userData, useCapture, callback) 238 | proc emscripten_set_keydown_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_key_callback_func): EMSCRIPTEN_RESULT {.inline.} = 239 | emscripten_set_keydown_callback_on_thread(target, userData, useCapture, callback) 240 | proc emscripten_set_keyup_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_key_callback_func): EMSCRIPTEN_RESULT {.inline.} = 241 | emscripten_set_keyup_callback_on_thread(target, userData, useCapture, callback) 242 | proc emscripten_set_blur_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_focus_callback_func): EMSCRIPTEN_RESULT {.inline.} = 243 | emscripten_set_blur_callback_on_thread(target, userData, useCapture, callback) 244 | proc emscripten_set_focus_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_focus_callback_func): EMSCRIPTEN_RESULT {.inline.} = 245 | emscripten_set_focus_callback_on_thread(target, userData, useCapture, callback) 246 | proc emscripten_set_webglcontextlost_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_webgl_context_callback): EMSCRIPTEN_RESULT {.inline.} = 247 | emscripten_set_webglcontextlost_callback_on_thread(target, userData, useCapture, callback) 248 | proc emscripten_set_webglcontextrestored_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_webgl_context_callback): EMSCRIPTEN_RESULT {.inline.} = 249 | emscripten_set_webglcontextrestored_callback_on_thread(target, userData, useCapture, callback) 250 | proc emscripten_set_fullscreenchange_callback*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: em_fullscreenchange_callback_func): EMSCRIPTEN_RESULT {.inline.} = 251 | emscripten_set_fullscreenchange_callback_on_thread(target, userData, useCapture, callback) 252 | 253 | proc emscripten_set_orientationchange_callback*(userData: pointer, useCapture: EM_BOOL, callback: em_orientationchange_callback_func): EMSCRIPTEN_RESULT {.inline.} = 254 | emscripten_set_orientationchange_callback_on_thread(userData, useCapture, callback) 255 | 256 | 257 | macro EMSCRIPTEN_KEEPALIVE*(someProc: untyped): typed = 258 | result = someProc 259 | #[ 260 | Ident !"exportc" 261 | ExprColonExpr 262 | Ident !"codegenDecl" 263 | StrLit __attribute__((used)) $# $#$# 264 | ]# 265 | result.addPragma(newIdentNode("exportc")) 266 | # emcc mangle cpp function names. This code fix it 267 | when defined(cpp): 268 | result.addPragma(newNimNode(nnkExprColonExpr).add( 269 | newIdentNode("codegenDecl"), 270 | newLit("__attribute__((used)) extern \"C\" $# $#$#"))) 271 | else: 272 | result.addPragma(newNimNode(nnkExprColonExpr).add( 273 | newIdentNode("codegenDecl"), 274 | newLit("__attribute__((used)) $# $#$#"))) 275 | 276 | const oldEmAsm = defined(jsbindNoEmJs) 277 | 278 | when oldEmAsm: 279 | macro declTypeWithHeader(h: static[string]): untyped = 280 | result = parseStmt("type DummyHeaderType {.importc: \"void\", header:\"" & $h & "\".} = object") 281 | 282 | template ensureHeaderIncluded(h: static[string]): untyped = 283 | block: 284 | declTypeWithHeader(h) 285 | var tht : ptr DummyHeaderType = nil 286 | 287 | template EM_ASM*(code: static[string]) = 288 | ensureHeaderIncluded("") 289 | {.emit: "EM_ASM(" & code & ");".} 290 | 291 | proc emAsmAux*(code: string, args: NimNode, resTypeName, emResType: string): NimNode = 292 | result = newNimNode(nnkStmtList) 293 | result.add(newCall(bindSym "ensureHeaderIncluded", newLit(""))) 294 | 295 | result.add( 296 | newNimNode(nnkVarSection).add( 297 | newNimNode(nnkIdentDefs).add( 298 | newNimNode(nnkPragmaExpr).add( 299 | newIdentNode("emasmres"), 300 | newNimNode(nnkPragma).add( 301 | newIdentNode("exportc") 302 | ) 303 | ), 304 | newIdentNode(resTypeName), 305 | newEmptyNode() 306 | ) 307 | ) 308 | ) 309 | 310 | var emitStr = "" 311 | if args.len == 0: 312 | emitStr = "emasmres = EM_ASM_" & emResType & "_V({" & code & "});" 313 | else: 314 | let argsSection = newNimNode(nnkLetSection) 315 | for i in 0 ..< args.len: 316 | argsSection.add( 317 | newNimNode(nnkIdentDefs).add( 318 | newNimNode(nnkPragmaExpr).add( 319 | newIdentNode("emasmarg" & $i), 320 | newNimNode(nnkPragma).add( 321 | newIdentNode("exportc") 322 | ) 323 | ), 324 | newEmptyNode(), 325 | args[i] 326 | ) 327 | ) 328 | result.add(argsSection) 329 | emitStr = "emasmres = EM_ASM_" & emResType & "({" & code & "}" 330 | for i in 0 ..< args.len: 331 | emitStr &= ", emasmarg" & $i 332 | emitStr &= ");" 333 | 334 | result.add( 335 | newNimNode(nnkPragma).add( 336 | newNimNode(nnkExprColonExpr).add( 337 | newIdentNode("emit"), 338 | newLit(emitStr) 339 | ) 340 | ) 341 | ) 342 | 343 | result.add(newIdentNode("emasmres")) 344 | 345 | result = newNimNode(nnkBlockStmt).add( 346 | newEmptyNode(), 347 | result 348 | ) 349 | 350 | macro EM_ASM_INT*(code: static[string], args: varargs[typed]): cint = 351 | result = emAsmAux(code, args, "cint", "INT") 352 | 353 | macro EM_ASM_DOUBLE*(code: static[string], args: varargs[typed]): cdouble = 354 | result = emAsmAux(code, args, "cdouble", "DOUBLE") 355 | 356 | proc emscripten_async_wget_data*(url: cstring, onload: proc(data: pointer, sz: cint), onerror: proc()) = 357 | ## Helper wrapper for emscripten_async_wget_data to pass nim closures around 358 | type Ctx = ref object 359 | onload: proc(data: pointer, sz: cint) 360 | onerror: proc() 361 | 362 | var ctx: Ctx 363 | ctx.new() 364 | ctx.onload = onload 365 | ctx.onerror = onerror 366 | GC_ref(ctx) 367 | 368 | proc onLoadWrapper(arg: pointer, data: pointer, sz: cint) {.cdecl.} = 369 | let c = cast[Ctx](arg) 370 | GC_unref(c) 371 | c.onload(data, sz) 372 | 373 | proc onErrorWrapper(arg: pointer) {.cdecl.} = 374 | let c = cast[Ctx](arg) 375 | GC_unref(c) 376 | c.onerror() 377 | 378 | emscripten_async_wget_data(url, cast[pointer](ctx), onLoadWrapper, onErrorWrapper) 379 | 380 | proc skipStmtList(n: NimNode): NimNode = 381 | result = n 382 | while result.kind == nnkStmtList and result.len == 1: 383 | result = result[0] 384 | 385 | var procNameCounter {.compileTime.} = 0 386 | 387 | proc concatStrings(args: varargs[string]): string = 388 | for a in args: result &= a 389 | 390 | iterator arguments(formalParams: NimNode): tuple[idx: int, name, typ, default: NimNode] = 391 | formalParams.expectKind(nnkFormalParams) 392 | var iParam = 0 393 | for i in 1 ..< formalParams.len: 394 | let pp = formalParams[i] 395 | for j in 0 .. pp.len - 3: 396 | yield (iParam, pp[j], copyNimTree(pp[^2]), pp[^1]) 397 | inc iParam 398 | 399 | proc cTypeName(T: typedesc): string = 400 | when T is cstring: 401 | "char*" 402 | elif T is cint: 403 | "int" 404 | elif T is cfloat: 405 | "float" 406 | elif T is cdouble: 407 | "double" 408 | elif T is pointer: 409 | "void*" 410 | elif T is ptr: 411 | "void*" 412 | else: 413 | {.error: "Ensupported type: " & $T.} 414 | 415 | proc declareEmJsExporter(cName, paramStr, jsImpl: static[string]) = 416 | const code = jsImpl.minifyJs().escapeJs() 417 | proc theExporter() {.importc: cName, codegenDecl: """__attribute__((used, visibility("default"))) const char* """ & cName & "() { return \"(" & paramStr & ")<::>{" & code & "}\"; }\n".} 418 | theExporter() 419 | 420 | macro EM_JS*(jsImpl: string, p: untyped): untyped = 421 | p.expectKind(nnkProcDef) 422 | var procName: string 423 | when not defined(release): 424 | procName = $p.name & "_" 425 | else: 426 | procName = "jsbind" 427 | 428 | procName &= $procNameCounter 429 | inc procNameCounter 430 | 431 | let importedProc = copyNimTree(p) 432 | 433 | importedProc.body = newEmptyNode() 434 | importedProc.addPragma(newTree(nnkExprColonExpr, ident"importc", newLit(procName))) 435 | importedProc.addPragma(ident"cdecl") 436 | 437 | result = newNimNode(nnkStmtList) 438 | result.add(importedProc) 439 | 440 | var paramStr = newCall(bindSym"concatStrings") 441 | 442 | let exportedProcCIdent = newLit("__em_js__" & procName) 443 | # let exportedProcIdent = ident("em_js_" & procName) 444 | for (i, name, typ, _) in arguments(importedProc.params): 445 | if i != 0: 446 | paramStr.add(newLit(",")) 447 | paramStr.add(newCall(bindSym"cTypeName", typ)) 448 | paramStr.add(newLit(" " & $name)) 449 | 450 | let declareEmJsExporter = bindSym("declareEmJsExporter") 451 | 452 | result.add quote do: 453 | `declareEmJsExporter`(`exportedProcCIdent`, `paramStr`, `jsImpl`) 454 | 455 | when not oldEmAsm: 456 | import strutils 457 | proc emAsmAux*(code: string, args: NimNode, resTypeName: string): NimNode = 458 | let prcName = genSym(nskProc, "emasm") 459 | var code = code 460 | var nimProcArgs: seq[NimNode] 461 | let theCall = newCall(prcName) 462 | nimProcArgs.add ident(resTypeName) 463 | var i = 0 464 | for a in args: 465 | let argTyp = newCall(ident"typeof", a) 466 | nimProcArgs.add(newIdentDefs(ident("a" & $i), argTyp)) 467 | code = code.replace("$" & $i, "a" & $i) 468 | theCall.add(a) 469 | inc i 470 | 471 | let theProc = newProc(prcName, nimProcArgs, newEmptyNode(), nnkProcDef) 472 | result = newNimNode(nnkStmtListExpr) 473 | result.add(newCall(bindSym"EM_JS", newLit(code), theProc)) 474 | result.add(theCall) 475 | 476 | macro EM_ASM_INT*(code: static[string], args: varargs[typed]): cint = 477 | result = emAsmAux(code, args, "cint") 478 | 479 | macro EM_ASM_DOUBLE*(code: static[string], args: varargs[typed]): cdouble = 480 | result = emAsmAux(code, args, "cdouble") 481 | 482 | macro EM_ASM*(code: static[string], args: varargs[typed]) = 483 | result = emAsmAux(code, args, "void") 484 | 485 | #[ 486 | dumpTree: 487 | block: 488 | ensureHeaderIncluded("") 489 | var emasmres {.exportc.}: cint 490 | var arg1 = 0 491 | var arg2 = 0 492 | let 493 | emasmarg1 {.exportc.} = arg1 494 | emasmarg2 {.exportc.} = arg2 495 | 496 | {.emit: "emasmres = EM_ASM_INT({" & "asdf" & "}, emasmarg1);".} 497 | emasmres 498 | ]# 499 | -------------------------------------------------------------------------------- /jsbind/wasmrt_glue.nim: -------------------------------------------------------------------------------- 1 | import wasmrt, macros, strutils 2 | 3 | template EMSCRIPTEN_KEEPALIVE*(p: untyped) = exportwasm(p) 4 | template EM_JS*(s: string, p: untyped) = importwasm(s, p) 5 | 6 | var counter {.compileTime.} = 0 7 | 8 | proc emAsmAux*(code: string, args: NimNode, resTypeName: string): NimNode = 9 | let prcName = ident("emasm" & $counter) #genSym(nskProc, "emasm" & $counter) 10 | inc counter 11 | var nimProcArgs: seq[NimNode] 12 | let theCall = newCall(prcName) 13 | nimProcArgs.add ident(resTypeName) 14 | var i = 0 15 | for a in args: 16 | let argTyp = newCall(ident"typeof", a) 17 | nimProcArgs.add(newIdentDefs(ident("a" & $i), argTyp)) 18 | theCall.add(a) 19 | inc i 20 | 21 | let theProc = newProc(prcName, nimProcArgs, newEmptyNode(), nnkProcDef) 22 | result = newNimNode(nnkStmtListExpr) 23 | result.add(newCall(bindSym"importwasmraw", newLit(code), theProc)) 24 | result.add(theCall) 25 | 26 | macro EM_ASM_INT*(code: static[string], args: varargs[typed]): cint = 27 | result = emAsmAux(code, args, "cint") 28 | 29 | macro EM_ASM_DOUBLE*(code: static[string], args: varargs[typed]): cdouble = 30 | result = emAsmAux(code, args, "cdouble") 31 | 32 | macro EM_ASM*(code: static[string], args: varargs[typed]) = 33 | result = emAsmAux(code, args, "void") 34 | -------------------------------------------------------------------------------- /tests/test.nim: -------------------------------------------------------------------------------- 1 | import ../jsbind 2 | 3 | when defined(emscripten): 4 | import ../jsbind/emscripten 5 | else: 6 | import ../jsbind/wasmrt_glue 7 | 8 | import strutils 9 | proc setup() = 10 | discard EM_ASM_INT """ 11 | var g = ((typeof window) === 'undefined') ? global : window; 12 | g.raiseJSException = function() { 13 | throw new Error("JS Exception"); 14 | }; 15 | 16 | g.callNimFunc = function(f) { 17 | f(); 18 | }; 19 | 20 | g.getSomeString = function() { 21 | return "hello"; 22 | }; 23 | 24 | g.callNimFuncAfterTimeout = function(f) { 25 | setTimeout(f, 1); 26 | }; 27 | 28 | g.consoleLog = function(f) { 29 | console.log(f); 30 | }; 31 | """ 32 | 33 | setup() 34 | 35 | type Global = ref object of JSObj 36 | 37 | proc raiseJSException(g: Global) {.jsImport.} 38 | proc callNimFunc(g: Global, p: proc()) {.jsImport.} 39 | proc callNimFuncAfterTimeout(g: Global, p: proc()) {.jsImport.} 40 | proc getString(): string {.jsImportgWithName: "getSomeString".} 41 | proc consoleLog(s: jsstring) {.jsImportgWithName:"(function(a){console.log(a)})".} 42 | 43 | proc runTest() = 44 | when not defined(gcDestructors): 45 | GC_disable() 46 | 47 | let g = globalEmbindObject(Global, "global") 48 | 49 | doAssert(getString() == "hello") 50 | 51 | block: # We should catch exceptions coming from JS 52 | var msg = "" 53 | try: 54 | g.raiseJSException() 55 | except: 56 | msg = getCurrentExceptionMsg() 57 | doAssert(msg.startsWith("JS Exception")) 58 | 59 | block: # We should catch exceptions coming from nim called by JS 60 | var msg = "" 61 | try: 62 | g.callNimFunc() do(): 63 | raise newException(Exception, "Nim exception") 64 | except: 65 | msg = getCurrentExceptionMsg() 66 | doAssert(msg == "Nim exception") 67 | 68 | block: 69 | var msg = "" 70 | try: 71 | g.callNimFuncAfterTimeout() do(): 72 | raise newException(Exception, "Nim exception") 73 | except: 74 | msg = getCurrentExceptionMsg() 75 | 76 | onUnhandledException = proc(msg: string) {.nimcall.} = 77 | if not msg.endsWith("unhandled exception: Nim exception [Exception]\l"): 78 | echo "Unexpected msg: ", msg 79 | doAssert(false) 80 | onUnhandledException = nil 81 | 82 | consoleLog("Test complete!") 83 | 84 | when not defined(gcDestructors): 85 | GC_enable() 86 | 87 | runTest() 88 | -------------------------------------------------------------------------------- /tests/test.nims: -------------------------------------------------------------------------------- 1 | import ospaths 2 | when defined(emscripten): 3 | --cpu:i386 4 | --cc:clang 5 | --os:linux 6 | 7 | let emcc = findExe("emcc") 8 | --clang.exe:emcc 9 | --clang.linkerexe:emcc 10 | --o:test.js 11 | --linedir:on 12 | --d:noSignalHandler 13 | #--d:release 14 | 15 | proc passEmcc(s: string) = 16 | switch("passC", s) 17 | switch("passL", s) 18 | 19 | proc passEmccS(s: string) = 20 | passEmcc("-s " & s) 21 | 22 | passEmccS "NO_EXIT_RUNTIME=1" 23 | passEmccS "ASSERTIONS=2" 24 | elif defined(wasm): 25 | --os:linux 26 | --cpu:i386 27 | --cc:clang 28 | --gc:orc 29 | --d:release 30 | --nomain 31 | --opt:size 32 | --listCmd 33 | --stackTrace:off 34 | --d:noSignalHandler 35 | --exceptions:goto 36 | --app:lib 37 | 38 | let llTarget = "wasm32-unknown-unknown-wasm" 39 | 40 | switch("passC", "--target=" & llTarget) 41 | switch("passL", "--target=" & llTarget) 42 | 43 | switch("passC", "-I/usr/include") # Wouldn't compile without this :( 44 | 45 | switch("passC", "-flto") # Important for code size! 46 | 47 | # gc-sections seems to not have any effect 48 | var linkerOptions = "-nostdlib -Wl,--no-entry,--allow-undefined,--export-dynamic,--gc-sections,--strip-all" 49 | 50 | switch("clang.options.linker", linkerOptions) 51 | switch("clang.cpp.options.linker", linkerOptions) 52 | 53 | else: 54 | echo "test nims not emscripten" 55 | --------------------------------------------------------------------------------