Skip to content

Commit

Permalink
Merge pull request #2 from guzba/master
Browse files Browse the repository at this point in the history
readme, add nim
  • Loading branch information
treeform authored Aug 25, 2021
2 parents 89ef461 + ba4d0e4 commit d5d4455
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 1 deletion.
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))

0 comments on commit d5d4455

Please sign in to comment.