├── .gitignore ├── LICENSE ├── README.md ├── clibpp.babel ├── clibpp.nim ├── makefile ├── test.cpp └── test.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | clibpp 2 | *.sublime-project 3 | *.sublime-workspace 4 | nimcache/ 5 | 6 | # Object files 7 | *.o 8 | *.ko 9 | *.obj 10 | *.elf 11 | 12 | # Libraries 13 | *.lib 14 | *.a 15 | 16 | # Shared objects (inc. Windows DLLs) 17 | *.dll 18 | *.so 19 | *.so.* 20 | *.dylib 21 | 22 | # Executables 23 | *.exe 24 | *.out 25 | *.app 26 | *.i*86 27 | *.x86_64 28 | *.hex 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 onionhammer 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | clibpp 2 | ====== 3 | 4 | Easy way to 'Mock' C++ interface 5 | 6 | Outline the C++ class 7 | --------------------- 8 | ```nimrod 9 | namespace somelibrary: 10 | class(test, header: "../test.hpp"): 11 | proc multiply[T](value, by: T): int 12 | proc output: void {.isstatic.} 13 | proc max[T](a, b: T): T 14 | var fieldName, notherName: int 15 | ``` 16 | 17 | Use the C++ class 18 | ----------------- 19 | ```nimrod 20 | # Test interface 21 | test.output() 22 | 23 | var item: test 24 | echo item.multiply(5, 9) 25 | echo item.fieldName 26 | echo item.max(2, 10) 27 | echo item.notherName 28 | ``` -------------------------------------------------------------------------------- /clibpp.babel: -------------------------------------------------------------------------------- 1 | [Package] 2 | name = "clibpp" 3 | version = "0.1" 4 | author = "Erik O'Leary" 5 | description = "Easy way to 'Mock' C++ interface" 6 | license = "MIT" -------------------------------------------------------------------------------- /clibpp.nim: -------------------------------------------------------------------------------- 1 | ## Easy way to 'Mock' C++ interface 2 | import macros, parseutils, strutils 3 | 4 | when not defined(CPP): 5 | {.error: "Must be compiled with cpp switch".} 6 | 7 | # Types 8 | type TMacroOptions = tuple 9 | header, importc: PNimrodNode 10 | className: PNimrodNode 11 | ns: string 12 | inheritable: bool 13 | 14 | 15 | # Procedures 16 | proc removePragma(statement: PNimrodNode, pname: string): bool {.compiletime.} = 17 | ## Removes the input pragma and returns whether pragma was removed 18 | var pragmas = statement.pragma() 19 | let pname = !pname 20 | for index in 0 .. < pragmas.len: 21 | if pragmas[index].kind == nnkIdent and pragmas[index].ident == pname: 22 | pragmas.del(index) 23 | return true 24 | 25 | 26 | proc makeProcedure(className, ns: string, statement: PNimrodNode): PNimrodNode {.compiletime.} = 27 | ## Generate an imported procedure definition for the input class name 28 | var procName = $(statement[0].basename) 29 | var pragmas = statement.pragma 30 | var params = statement.params 31 | 32 | if pragmas.kind == nnkEmpty: 33 | statement.pragma = newNimNode(nnkPragma) 34 | pragmas = statement.pragma 35 | 36 | # Add importc (if static) or importcpp pragma 37 | var importCPragma: PNimrodNode 38 | var thisNode: PNimrodNode 39 | 40 | # Check if isstatic is set (and remove istatic pragma) 41 | if statement.removePragma("isstatic"): 42 | importCPragma = newNimNode(nnkExprColonExpr) 43 | .add(newIdentNode("importc")) 44 | .add(newStrLitNode(ns & className & "::" & procName)) 45 | 46 | # If static, insert 'this: typedesc[`className`]' param 47 | thisNode = newNimNode(nnkIdentDefs) 48 | .add(newIdentNode("this")) 49 | .add(parseExpr("typedesc[" & className & "]")) 50 | .add(newNimNode(nnkEmpty)) 51 | 52 | else: 53 | importCPragma = newIdentNode("importcpp") 54 | 55 | # If not static, insert 'this: `className`' param 56 | thisNode = newNimNode(nnkIdentDefs) 57 | .add(newIdentNode("this")) 58 | .add(newIdentNode(className)) 59 | .add(newNimNode(nnkEmpty)) 60 | 61 | params.insert(1, thisNode) 62 | pragmas.add importCPragma 63 | 64 | return statement 65 | 66 | 67 | proc parse_opts(className: PNimrodNode; opts: seq[PNimrodNode]): TMacroOptions {.compileTime.} = 68 | if opts.len == 1 and opts[0].kind == nnkStrLit: 69 | # user passed a header 70 | result.header = opts[0] 71 | 72 | else: 73 | for opt in opts.items: 74 | var handled = true 75 | case opt.kind 76 | of nnkExprEqExpr, nnkExprColonExpr: 77 | case ($ opt[0].ident).toLower 78 | of "header": 79 | result.header = opt[1] 80 | of "importc": 81 | result.importc = opt[1] 82 | of "namespace", "ns": 83 | result.ns = $opt[1] & "::" 84 | else: 85 | handled = false 86 | of nnkIdent: 87 | case ($ opt.ident).toLower 88 | of "inheritable": 89 | result.inheritable = true 90 | else: 91 | handled = false 92 | else: 93 | handled = false 94 | 95 | if not handled: 96 | echo "Warning, unhandled argument: ", repr(opt) 97 | 98 | if not isNil(result.importc) or isNil(result.ns): 99 | result.ns = "" 100 | if not isNil(result.ns): 101 | result.importc = newStrLitNode(result.ns & $className) 102 | 103 | result.className = className 104 | 105 | proc buildStaticAccessor (name,ty, className:NimNode; ns:string): NimNode {.compileTime.} = 106 | result = newProc( 107 | name = name, 108 | procType = nnkProcDef, 109 | body = newEmptyNode(), 110 | params = [ty, newIdentDefs(ident"ty", parseExpr("typedesc["& $className &"]"))] 111 | ) 112 | result.pragma = newNimNode(nnkPragma).add( 113 | ident"noDecl", 114 | newNimNode(nnkExprColonExpr).add( 115 | ident"importcpp", 116 | newLit(ns & $className & "::" & $name.baseName & "@"))) 117 | 118 | template use*(ns: string): stmt {.immediate.} = 119 | {. emit: "using namespace $1;".format(ns) .} 120 | 121 | 122 | macro namespace*(namespaceName: expr, body: stmt): stmt {.immediate.} = 123 | result = newStmtList() 124 | 125 | var newNamespace = newNimNode(nnkExprColonExpr). 126 | add(ident("ns"), namespaceName) 127 | 128 | # Inject new namespace into each class declaration 129 | for i in body.children: 130 | if $i[0] == "class": 131 | i.insert 2, newNamespace 132 | 133 | result.add body 134 | 135 | macro class*(className, opts: expr, body: stmt): stmt {.immediate.} = 136 | ## Defines a C++ class 137 | result = newStmtList() 138 | 139 | var parent: NimNode 140 | var className = className 141 | if className.kind == nnkInfix and className[0].ident == !"of": 142 | parent = className[2] 143 | className = className[1] 144 | 145 | var oseq: seq[PNimrodNode] = @[] 146 | if len(callsite()) > 3: 147 | # slots 2 .. -2 are arguments 148 | for i in 2 .. len(callsite())-2: 149 | oseq.add callsite()[i] 150 | let opts = parse_opts(className, oseq) 151 | 152 | # Declare a type named `className`, importing from C++ 153 | var newType = parseExpr( 154 | "type $1* {.header:$2, importcpp$3.} = object".format( 155 | $ opts.className, repr(opts.header), 156 | (if opts.importc.isNil: "" else: ":"& repr(opts.importc)))) 157 | 158 | var recList = newNimNode(nnkRecList) 159 | newType[0][2][2] = recList 160 | if not parent.isNil: 161 | # Type has a parent 162 | newType[0][2][1] = newNimNode(nnkOfInherit).add(parent) 163 | elif opts.inheritable: 164 | # Add inheritable pragma 165 | newType[0][0][1].add ident"inheritable" 166 | 167 | # Iterate through statements in class definition 168 | var body = callsite()[< callsite().len] 169 | let classname_s = $ opts.className 170 | # Fix for nnkDo showing up here 171 | if body.kind == nnkDo: body = body.body 172 | 173 | for statement in body.children: 174 | case statement.kind: 175 | of nnkProcDef: 176 | # Add procs with header pragma 177 | var headerPragma = newNimNode(nnkExprColonExpr).add( 178 | ident("header"), 179 | opts.header.copyNimNode) 180 | var member = makeProcedure(classname_s, opts.ns, statement) 181 | member.pragma.add headerPragma 182 | result.add member 183 | 184 | of nnkVarSection: 185 | # Add any var declared in the class to the type 186 | # create accessors for any static variables 187 | # proc varname* (ty:typedesc[classtype]): ty{.importcpp:"Class::StaticVar@"} 188 | var 189 | statics: seq[tuple[name,ty: NimNode]] = @[] 190 | fields : seq[tuple[name,ty: NimNode]] = @[] 191 | 192 | for id_def in children(statement): 193 | let ty = id_def[id_def.len - 2] 194 | 195 | for i in 0 .. id_def.len - 3: 196 | # iterate over the var names, check each for isStatic pragma 197 | let this_ident = id_def[i] 198 | var isStatic = false 199 | if this_ident.kind == nnkPragmaExpr: 200 | for prgma in children(this_ident[1]): 201 | if prgma.kind == nnkIdent and ($prgma).eqIdent("isStatic"): 202 | statics.add((this_ident[0], ty)) 203 | isStatic = true 204 | break 205 | if not isStatic: 206 | fields.add((this_ident, ty)) 207 | 208 | # recList.add id_def 209 | 210 | for n,ty in items(fields): 211 | recList.add newIdentDefs(n, ty) 212 | for n,ty in items(statics): 213 | result.add buildStaticAccessor(n, ty, opts.className, opts.ns) 214 | 215 | else: 216 | result.add statement 217 | 218 | # Insert type into resulting statement list 219 | result.insert 0, newType 220 | 221 | when defined(Debug): 222 | echo result.repr 223 | 224 | 225 | when isMainModule: 226 | {.compile: "test.cpp".} 227 | const test_h = "../test.hpp" 228 | 229 | when false: 230 | # Traditional wrapper 231 | type test {.header: test_h, importcpp.} = object 232 | fieldName: cint 233 | notherName: cint 234 | 235 | proc output(this: typedesc[test]) {.header: test_h, importc: "test::output".} 236 | proc multiply(this: test, value, by: cint): cint {.header: test_h, importcpp.} 237 | proc max[T](this: test, a, b: T): T {.header: test_h, importcpp.} 238 | 239 | # Test interface 240 | test.output() 241 | 242 | var item: test 243 | echo item.multiply(4, 6) 244 | echo item.fieldName 245 | echo item.max(2, 10) 246 | echo item.notherName 247 | 248 | else: 249 | # Import "test" class from C++: 250 | namespace pp: 251 | class(test, inheritable, header: test_h): 252 | proc multiply[T](value, by: T): int 253 | proc output {.isstatic.} 254 | proc max[T](a, b: T): T 255 | proc foo(): int 256 | var fieldName, notherName{.isStatic.}: int 257 | class(test_sub of test, header: test_h): 258 | proc childf: int 259 | 260 | # Test interface 261 | test.output() 262 | 263 | var item: test 264 | echo item.multiply(5, 9) 265 | echo item.fieldName 266 | echo item.max(2, 10) 267 | #echo item.notherName 268 | echo test.notherName 269 | 270 | var item2: test_sub 271 | echo item2.childf 272 | assert item2 of test -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | all: clibpp 3 | 4 | clibpp: clibpp.nim test.o 5 | nimrod cpp --parallelBuild:1 clibpp 6 | 7 | test.o: test.cpp test.hpp 8 | clang++ -c -std=c++11 test.cpp 9 | 10 | clean: 11 | rm -rf nimcache 12 | rm test.o clibpp -------------------------------------------------------------------------------- /test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.hpp" 2 | 3 | using std::cout; 4 | using std::endl; 5 | 6 | int pp::test::notherName = 120; 7 | 8 | void pp::test::output() { 9 | cout << "hello world!" << endl; 10 | } 11 | 12 | int pp::test::multiply(int value, int by) { 13 | fieldName = 240; 14 | return value * by; 15 | } 16 | 17 | int pp::test_sub::childf() { return 42; } 18 | -------------------------------------------------------------------------------- /test.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace pp { 4 | class test { 5 | 6 | public: 7 | static void output(); 8 | 9 | int multiply(int value, int by); 10 | 11 | template 12 | T max(T a, T b) { 13 | return a > b ? a : b; 14 | }; 15 | 16 | int fieldName; 17 | 18 | static int notherName; 19 | }; 20 | 21 | class test_sub: test { 22 | public: 23 | int childf(); 24 | }; 25 | } --------------------------------------------------------------------------------