Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

readme, add nim #2

Merged
merged 4 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Bindy - Generate Nim library bindings for many languages

So you made a cool Nim library but you want it to be available to other languages as well. With `bindy` you can generate a dynamically linked library with a simple C API and generated bindings for many languages.

![Github Actions](https://github.com/treeform/bindy/workflows/Github%20Actions/badge.svg)

## Supported features and languages:

Language | Method | Enums | Objects | Ref Objects | Seqs |
------------- | ------------- | ------ | ------- | ----------- | ------ |
Nim | {.importc.} | ✅ | ✅ | ✅ | ✅ |

## Why add Nim support?

"Can't you just import your cool library in Nim?" We though it was important to test the library in a what we call Nim-C-Nim sandwich. It makes sure everyone uses your library API the same way. This also means you can ship huge Nim library DLLs and use them in your Nim programs without recompiling everything every time.
11 changes: 10 additions & 1 deletion src/bindy.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import bindy/internal, bindy/common, macros, strformat, tables
import bindy/internal, bindy/common, bindy/languages/nim, macros, strformat, tables

proc toggleBasicOnly*() =
discard
Expand All @@ -13,6 +13,7 @@ macro exportEnums*(body: typed) =
)

exportEnumInternal(sym)
exportEnumNim(sym)

macro exportProcs*(body: typed) =
for statement in body:
Expand All @@ -38,6 +39,8 @@ macro exportProcs*(body: typed) =
continue

exportProcInternal(procedure)
exportProcNim(procedure)

inc exported

if exported == 0:
Expand Down Expand Up @@ -67,6 +70,7 @@ macro exportObjects*(body: typed) =
)

exportObjectInternal(sym)
exportObjectNim(sym)

macro exportRefObject*(
sym: typed, whitelist: static[openarray[string]], body: typed
Expand Down Expand Up @@ -141,6 +145,7 @@ macro exportRefObject*(
entries.inc(exportProc.repr)

exportRefObjectInternal(sym, whitelist)
exportRefObjectNim(sym, whitelist)

for procedure in exportProcs:
var prefixes = @[sym]
Expand All @@ -150,6 +155,7 @@ macro exportRefObject*(
if procType[0].len > 2:
prefixes.add(procType[0][2][1])
exportProcInternal(procedure, prefixes)
exportProcNim(procedure, prefixes)

macro exportSeq*(sym: typed, body: typed) =
var exportProcs: seq[NimNode]
Expand All @@ -175,9 +181,12 @@ macro exportSeq*(sym: typed, body: typed) =
exportProcs.add(procedure)

exportSeqInternal(sym)
exportSeqNim(sym)

for procedure in exportProcs:
exportProcInternal(procedure, [sym])
exportProcNim(procedure, [sym])

macro writeFiles*(dir, lib: static[string]) =
writeInternal(dir, lib)
writeNim(dir, lib)
254 changes: 254 additions & 0 deletions src/bindy/languages/nim.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import bindy/common, macros, strformat, strutils

var
types {.compiletime.}: string
procs {.compiletime.}: string

proc exportTypeNim*(sym: NimNode): string =
if sym.kind == nnkBracketExpr:
if sym[0].repr != "seq":
Expand Down Expand Up @@ -34,3 +38,253 @@ proc convertImportToNim*(sym: NimNode): string =
result = ".`$`"
elif sym.repr == "Rune":
result = ".Rune"

proc exportEnumNim*(sym: NimNode) =
let symImpl = sym.getImpl()[2]

types.add &"type {sym.repr}* = enum\n"
for i, entry in symImpl[1 .. ^1]:
types.add &" {entry.repr}\n"
types.add "\n"

proc exportProcNim*(sym: NimNode, prefixes: openarray[NimNode] = []) =
let
procName = sym.repr
procNameSnaked = toSnakeCase(procName)
procType = sym.getTypeInst()
procParams = procType[0][1 .. ^1]
procReturn = procType[0][0]
procRaises = sym.raises()

var apiProcName = &"$lib_"
if prefixes.len > 0:
for prefix in prefixes:
apiProcName.add &"{toSnakeCase(prefix.getName())}_"
apiProcName.add &"{procNameSnaked}"

procs.add &"proc {apiProcName}("
for param in procParams:
for i in 0 .. param.len - 3:
var paramType = param[^2]
if paramType.repr.endsWith(":type"):
paramType = prefixes[0]
procs.add &"{toSnakeCase(param[i].repr)}: {exportTypeNim(paramType)}, "
procs.removeSuffix ", "
procs.add ")"
if procReturn.kind != nnkEmpty:
procs.add &": {exportTypeNim(procReturn)}"
procs.add " {.importc: \""
procs.add &"{apiProcName}"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {procName}*("
for param in procParams:
for i in 0 .. param.len - 3:
var paramType = param[^2]
if paramType.repr.endsWith(":type"):
paramType = prefixes[0]
if param[^2].kind == nnkBracketExpr or paramType.repr.startsWith("Some"):
procs.add &"{param[i].repr}: {exportTypeNim(paramType)}, "
else:
procs.add &"{param[i].repr}: {paramType}, "
procs.removeSuffix ", "
procs.add ")"
if procReturn.kind != nnkEmpty:
procs.add &": {exportTypeNim(procReturn)}"
procs.add " {.inline.} =\n"
if procReturn.kind != nnkEmpty:
procs.add " result = "
else:
procs.add " "
procs.add &"{apiProcName}("
for param in procParams:
for i in 0 .. param.len - 3:
procs.add &"{param[i].repr}{convertExportFromNim(param[^2])}, "
procs.removeSuffix ", "
procs.add ")\n"
if procRaises:
procs.add " if checkError():\n"
procs.add " raise newException(PixieError, $takeError())\n"
procs.add "\n"

proc exportObjectNim*(sym: NimNode) =
let
objName = sym.repr
objType = sym.getType()

if objName in ["Vector2", "Matrix3", "Rectangle", "Color"]:
return

types.add &"type {objName}* = object\n"
for property in objType[2]:
types.add &" {property.repr}*: {property.getTypeInst().repr}\n"
types.add "\n"

proc genRefObject(objName: string) =
types.add &"type {objName}* = object\n"
types.add " reference: pointer\n"
types.add "\n"

let apiProcName = &"$lib_{toSnakeCase(objName)}_unref"
types.add &"proc {apiProcName}*(x: {objName})"
types.add " {.importc: \""
types.add &"{apiProcName}"
types.add "\", cdecl.}"
types.add "\n"
types.add "\n"

types.add &"proc `=destroy`(x: var {objName}) =\n"
types.add &" $lib_{toSnakeCase(objName)}_unref(x)\n"
types.add "\n"

proc genSeqProcs(objName, procPrefix, entryName: string) =
procs.add &"proc {procPrefix}_len(s: {objName}): int"
procs.add " {.importc: \""
procs.add &"{procPrefix}_len"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {procPrefix}_add"
procs.add &"(s: {objName}, v: {entryName})"
procs.add " {.importc: \""
procs.add &"{procPrefix}_add"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {procPrefix}_get"
procs.add &"(s: {objName}, i: int)"
procs.add &": {entryName}"
procs.add " {.importc: \""
procs.add &"{procPrefix}_get"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {procPrefix}_set(s: {objName}, "
procs.add &"i: int, v: {entryName})"
procs.add " {.importc: \""
procs.add &"{procPrefix}_set"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {procPrefix}_remove(s: {objName}, i: int)"
procs.add " {.importc: \""
procs.add &"{procPrefix}_remove"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {procPrefix}_clear(s: {objName})"
procs.add " {.importc: \""
procs.add &"{procPrefix}_clear"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

proc exportRefObjectNim*(sym: NimNode, whitelist: openarray[string]) =
let
objName = sym.getName()
objNameSnaked = toSnakeCase(objName)
objType = sym.getType()[1][1].getType()

genRefObject(objName)

if sym.kind == nnkBracketExpr:
return

for property in objType[2]:
if not property.isExported:
continue
if whitelist != ["*"] and property.repr notin whitelist:
continue

let
propertyName = property.repr
propertyNameSnaked = toSnakeCase(propertyName)
propertyType = property.getTypeInst()

if propertyType.kind == nnkBracketExpr:
let procPrefix = &"$lib_{objNameSnaked}_{propertyNameSnaked}"
genSeqProcs(objName, procPrefix, propertyType[1].repr)
else:
let getProcName = &"$lib_{objNameSnaked}_get_{propertyNameSnaked}"

procs.add &"proc {getProcName}("
procs.add &"{toVarCase(objName)}: {objName}): "
procs.add exportTypeNim(propertyType)
procs.add " {.importc: \""
procs.add &"{getProcName}"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc {propertyName}*("
procs.add &"{toVarCase(objName)}: {objName}): "
procs.add &"{exportTypeNim(propertyType)}"
procs.add " {.inline.} =\n"
procs.add &" {getProcName}({toVarCase(objName)})"
procs.add convertImportToNim(propertyType)
procs.add "\n"
procs.add "\n"

let setProcName = &"$lib_{objNameSnaked}_set_{propertyNameSnaked}"

procs.add &"proc {setProcName}("
procs.add &"{toVarCase(objName)}: {objName}, "
procs.add &"{propertyName}: {exportTypeNim(propertyType)})"
procs.add " {.importc: \""
procs.add &"{setProcName}"
procs.add "\", cdecl.}"
procs.add "\n"
procs.add "\n"

procs.add &"proc `{propertyName}=`*("
procs.add &"{toVarCase(objName)}: {objName}, "
procs.add &"{propertyName}: {propertyType.repr}) =\n"
procs.add &" {setProcName}({toVarCase(objName)}, "
procs.add &"{propertyName}{convertExportFromNim(propertyType)})"
procs.add "\n"
procs.add "\n"

proc exportSeqNim*(sym: NimNode) =
let
seqName = sym.getName()
seqNameSnaked = toSnakeCase(seqName)

genRefObject(seqName)
genSeqProcs(
seqName,
&"$lib_{seqNameSnaked}",
sym[1].repr
)

let newSeqProcName = &"$lib_new_{seqNameSnaked}"

procs.add &"proc {newSeqProcName}*(): {seqName}"
procs.add " {.importc: \""
procs.add newSeqProcName
procs.add "\", cdecl.}\n"
procs.add "\n"

procs.add &"proc new{seqName}*(): {seqName} =\n"
procs.add &" {newSeqProcName}()\n"
procs.add "\n"

const header = """
import bumpy, chroma, unicode, vmath

export bumpy, chroma, unicode, vmath

{.push dynlib: "$lib.dll".}

type PixieError = object of ValueError

"""

proc writeNim*(dir, lib: string) =
writeFile(&"{dir}/{lib}.nim", (header & types & procs).replace("$lib", lib))