Nim: {.exportc.} for -d:nodejs

Created on 3 Jul 2020  路  2Comments  路  Source: nim-lang/Nim

Summary

should the {.exportc.} pragma add the symbol to a module.exports so that it can be used in other nodejs programs?

Description

For example,

foo.nim

proc fib(a: cint): cint {.exportc.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)

When compiled to target node, nim js -d:nodejs foo.nim will not export a compatible function to be used in other node programs (AFAIK, if this issue is noise and there is a way to do this - apologies). To me,

the.js

let fib = require("./foo.js").fib;
console.log(fib(10));

should work. It seems the way to do this would be for jsgen to track exports if the symbol is directed to be exported. I am new to nim's codegen, but would the general fix be to add a proc genExportc(p: PProc; procname, filename: Rope): Rope that extends the file to include module.exports.$1 = $1 on the procname if exportc is present in the procdef? I am happy to do this unless this is misguided, undesirable, etc..

Alternatives

Not doing this

Additional Information

The question prompting the issue was here

Most helpful comment

I don't think this should be done by the codegen. The idea of {.exportc.} is that the symbol is not mangled by the compiler. This doesn't necessary mean that the symbol will be exported cross-module (similar to how you need both {.exportc, dynlib.} to export a Nim function to a DLL).

We can write a simple {.exportNodeJs.} macro to solve this though:

import macros

when defined(nodejs):
  import jsffi

  var module {.importc, nodecl.}: JsObject

macro exportNodeJs(body: typed): untyped =
  when defined(nodejs):
    expectKind body, RoutineNodes

    result = newStmtList()
    result.add body

    let
      exportSym = body[0]
      exportName = ident exportSym.strVal
      exports = newCall(bindSym".", bindSym"module", ident"exports")

    result.add newCall(bindSym".=", exports, exportName, exportSym)
  else:
    result = newStmtList().add body

proc fib(a: cint): cint {.exportNodeJs.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)

And here's the generated JS:

function fib_2651445(a_2651447) {
  var result_2651448 = 0;

  var F={procname:"test.fib",prev:framePtr,filename:"test.nim",line:0};
  framePtr = F;
    if ((a_2651447 <= 2)) {
    F.line = 26;
    result_2651448 = 1;
    }
    else {
      F.line = 28;
      result_2651448 = addInt(fib_2651445(subInt(a_2651447, 1)), fib_2651445(subInt(a_2651447, 2)));
    }

  framePtr = F.prev;

  return result_2651448;

}
var F={procname:"module test",prev:framePtr,filename:"test.nim",line:0};
framePtr = F;
F.line = 24;
module.exports.fib = fib_2651445;
framePtr = F.prev;

We can add this to jsffi, probably.

All 2 comments

I don't think this should be done by the codegen. The idea of {.exportc.} is that the symbol is not mangled by the compiler. This doesn't necessary mean that the symbol will be exported cross-module (similar to how you need both {.exportc, dynlib.} to export a Nim function to a DLL).

We can write a simple {.exportNodeJs.} macro to solve this though:

import macros

when defined(nodejs):
  import jsffi

  var module {.importc, nodecl.}: JsObject

macro exportNodeJs(body: typed): untyped =
  when defined(nodejs):
    expectKind body, RoutineNodes

    result = newStmtList()
    result.add body

    let
      exportSym = body[0]
      exportName = ident exportSym.strVal
      exports = newCall(bindSym".", bindSym"module", ident"exports")

    result.add newCall(bindSym".=", exports, exportName, exportSym)
  else:
    result = newStmtList().add body

proc fib(a: cint): cint {.exportNodeJs.} =
  if a <= 2:
    result = 1
  else:
    result = fib(a - 1) + fib(a - 2)

And here's the generated JS:

function fib_2651445(a_2651447) {
  var result_2651448 = 0;

  var F={procname:"test.fib",prev:framePtr,filename:"test.nim",line:0};
  framePtr = F;
    if ((a_2651447 <= 2)) {
    F.line = 26;
    result_2651448 = 1;
    }
    else {
      F.line = 28;
      result_2651448 = addInt(fib_2651445(subInt(a_2651447, 1)), fib_2651445(subInt(a_2651447, 2)));
    }

  framePtr = F.prev;

  return result_2651448;

}
var F={procname:"module test",prev:framePtr,filename:"test.nim",line:0};
framePtr = F;
F.line = 24;
module.exports.fib = fib_2651445;
framePtr = F.prev;

We can add this to jsffi, probably.

Was this page helpful?
0 / 5 - 0 ratings