├── .gitignore ├── copying.txt ├── examples ├── mathexpr.nim └── wikiinterfaces.nim ├── protocoled.nim ├── protocoled.nimble └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all root files 2 | /* 3 | # Exclude source code files in subdirectories 4 | !/*.nim 5 | !/*.txt 6 | !/*.md 7 | # Exclude root directories 8 | !/*/ 9 | # Include nimcache subdirectories 10 | /*/nimcache/ 11 | # Include all files in subdirectories 12 | /*/* 13 | # Exclude source code files in subdirectories 14 | !/*/*.nim 15 | !/*/*.txt 16 | !/*/*.md 17 | -------------------------------------------------------------------------------- /copying.txt: -------------------------------------------------------------------------------- 1 | Protocoled -- an interface macro for nim 2 | 3 | (c) Copyright 2018 b3liever - A.G. All rights reserved. 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 | 23 | MIT license: http://www.opensource.org/licenses/mit-license.php 24 | -------------------------------------------------------------------------------- /examples/mathexpr.nim: -------------------------------------------------------------------------------- 1 | import "../protocoled" 2 | 3 | protocol PExpr: 4 | proc eval(e): int 5 | 6 | impl PLiteral: 7 | var x: int 8 | 9 | proc eval(e): int = e.x 10 | proc newLit(x: int): PLiteral = 11 | result = PLiteral(x: x) 12 | 13 | impl PPlusExpr: 14 | var a, b: PExpr 15 | 16 | proc eval(e): int = eval(e.a) + eval(e.b) 17 | proc newPlus(a, b: PExpr): PPlusExpr = 18 | result = PPlusExpr(a: a, b: b) 19 | 20 | echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) 21 | -------------------------------------------------------------------------------- /examples/wikiinterfaces.nim: -------------------------------------------------------------------------------- 1 | import "../protocoled" 2 | 3 | # -------------------- 4 | # IUpdatable interface 5 | # -------------------- 6 | 7 | protocol IUpdatable: 8 | proc update(this) 9 | 10 | impl Movable: 11 | proc update(this) = 12 | echo("Moving forward.") 13 | 14 | proc newMovable(): Movable = 15 | result = Movable() 16 | 17 | impl NotMovable: 18 | proc update(this) = 19 | echo("I'm staying put.") 20 | 21 | proc newNotMovable(): NotMovable = 22 | result = NotMovable() 23 | 24 | # --------------------- 25 | # ICollidable interface 26 | # --------------------- 27 | 28 | protocol ICollidable: 29 | proc collide(this) 30 | 31 | impl Solid: 32 | proc collide(this) = 33 | echo("Bang!") 34 | 35 | proc newSolid(): Solid = 36 | result = Solid() 37 | 38 | impl NotSolid: 39 | proc collide(this) = 40 | echo("Splash!") 41 | 42 | proc newNotSolid(): NotSolid = 43 | result = NotSolid() 44 | 45 | # ------------------ 46 | # IVisible interface 47 | # ------------------ 48 | 49 | protocol IVisible: 50 | proc draw(this) 51 | 52 | impl Invisible: 53 | proc draw(this) = 54 | echo("I won't appear.") 55 | 56 | proc newInvisible(): Invisible = 57 | result = Invisible() 58 | 59 | impl Visible: 60 | proc draw(this) = 61 | echo("I'm showing myself.") 62 | 63 | proc newVisible(): Visible = 64 | result = Visible() 65 | -------------------------------------------------------------------------------- /protocoled.nim: -------------------------------------------------------------------------------- 1 | ## Interface macro for Nim 2 | ## ======================= 3 | ## 4 | ## The protocol macro allows writing an interface with less typing. Classes 5 | ## implementing that interface are defined using the ``impl`` command inside 6 | ## the macro statement. Works in a similar way to the class macro. 7 | ## It requires a typeless parameter, acting as the 'self' variable, to be 8 | ## declared in all procedures, but the ctor. Prefix with the export marker to 9 | ## make a class definition public. The constructor and clone function should 10 | ## explicitly use the result variable. 11 | ## 12 | ## Example: 13 | ## ======== 14 | ## 15 | ## .. code-block:: nim 16 | ## protocol *IUpdatable: 17 | ## proc update*(this) 18 | ## 19 | ## impl Movable: 20 | ## proc update(this) = 21 | ## echo("Moving forward.") 22 | ## 23 | ## proc newMovable(): Movable = 24 | ## new(result) 25 | ## 26 | ## impl *NotMovable: 27 | ## proc update(this) = 28 | ## echo("I'm staying put.") 29 | ## 30 | ## proc newNotMovable*(): NotMovable = 31 | ## new(result) 32 | ## 33 | import macros 34 | 35 | type 36 | ClassBuilder = ref object 37 | baseType: NimNode 38 | methNames: seq[string] 39 | 40 | proc createType(node, baseType: NimNode): NimNode = 41 | expectKind(baseType, nnkIdent) 42 | # flag if object should be exported 43 | var isExported = false 44 | if node.kind == nnkPrefix and $node[0] == "*": 45 | isExported = true 46 | elif node.kind != nnkIdent: 47 | error(node.lineInfo & ": Invalid node: " & node.repr) 48 | let classType = node.basename 49 | 50 | template declare(a, b) = 51 | type a = ref object of b 52 | template declarePub(a, b) = 53 | type a* = ref object of b 54 | 55 | result = 56 | if isExported: 57 | getAst(declarePub(classType, baseType)) 58 | else: 59 | getAst(declare(classType, baseType)) 60 | 61 | result[0][2][0][2] = newNimNode(nnkRecList) 62 | 63 | proc transformClass(node: NimNode, b: ClassBuilder): NimNode = 64 | result = newStmtList() 65 | expectKind(node, nnkCommand) 66 | # Create a type section for the derived class 67 | let derivDecl = createType(node[1], b.baseType) 68 | let derivType = derivDecl[0][0].basename 69 | let recList = derivDecl[0][2][0][2] 70 | result.add derivDecl 71 | 72 | template shadowThisVar(body, varName, typ) = 73 | body.insert(0, newLetStmt(varName, newCall(typ, varName))) 74 | template assignResField(body, field, procName) = 75 | body.add(nnkAsgn.newTree(nnkDotExpr.newTree(ident("result"), field), procName)) 76 | 77 | expectKind(node[2], nnkStmtList) 78 | for n in node[2].children: 79 | case n.kind 80 | of nnkProcDef: 81 | # Check if it is the ctor proc or a clone function 82 | if eqIdent(n.params[0], $derivType): 83 | # Assign the fields of the result 84 | for name in b.methNames: 85 | n.body.assignResField(ident(name & "impl"), ident(name & $derivType)) 86 | else: 87 | if n.params.len < 2 or n.params[1][1].kind != nnkEmpty: 88 | error(n.params.lineInfo & ": Proc's 'this' parameter not found") 89 | for name in b.methNames: 90 | if eqIdent(n.name, name): 91 | n.params[1][1] = b.baseType 92 | let thisVar = n.params[1][0] 93 | # cast 'this' variable to class type 94 | n.body.shadowThisVar(thisVar, derivType) 95 | n[0] = ident(name & $derivType) # overrides the export marker 96 | break 97 | # If proc is not a method, 'this' var has the type of the derived class 98 | if n.params.len >= 2 and n.params[1][1].kind == nnkEmpty: 99 | n.params[1][1] = derivType 100 | result.add n 101 | of nnkVarSection, nnkLetSection: 102 | n.copyChildrenTo(recList) 103 | else: 104 | error(n.lineInfo & ": Invalid node: " & n.repr) 105 | 106 | macro protocol*(head, body): untyped = 107 | result = newStmtList() 108 | let b = ClassBuilder() 109 | # Create a type section for the base class 110 | let interDecl = createType(head, ident("RootObj")) 111 | b.baseType = interDecl[0][0].basename 112 | let recList = interDecl[0][2][0][2] 113 | result.add interDecl 114 | 115 | template addObjField(record, name, params) = 116 | record.add(nnkIdentDefs.newTree(name, nnkProcTy.newTree(params, 117 | nnkPragma.newTree(ident("nimcall"))), newEmptyNode())) 118 | template checkNotNil(name, field) = 119 | assert(name.field != nil) 120 | 121 | template forwardCall(body, name, field, params): NimNode = 122 | body.add(nnkCall.newTree(nnkDotExpr.newTree(name, field)).add(params)) 123 | 124 | for n in body.children: 125 | case n.kind 126 | of nnkProcDef: 127 | if n.params.len < 2 or n.params[1][1].kind != nnkEmpty: 128 | error(n.params.lineInfo & ": Method's 'self' parameter not found") 129 | n.params[1][1] = b.baseType 130 | let thisVar = n.params[1][0] 131 | expectKind(n.body, nnkEmpty) # Only a proc signature 132 | # Add proc field to interface type signature 133 | let objField = ident($n.name & "impl") 134 | recList.addObjField(objField, n.params) 135 | # List of the methods defined in the interface 136 | b.methNames.add($n.name) 137 | # Obtain the names of the parameters 138 | var params: seq[NimNode] 139 | for i in 1 ..< n.params.len: 140 | params.add n.params[i][0] 141 | n.body = newStmtList() 142 | n.body.add getAst(checkNotNil(thisVar, objField)) 143 | # Forward call to implementation proc 144 | n.body.forwardCall(thisVar, objField, params) 145 | result.add n 146 | of nnkCommand: 147 | expectKind(n[0], nnkIdent) 148 | if $n[0] != "impl": error("Invalid command " & $n[0]) 149 | assert b.methNames.len > 0, "No methods declared" 150 | let implClass = transformClass(n, b) 151 | result.add implClass 152 | else: 153 | error(n.lineInfo & ": Invalid node: " & n.repr) 154 | 155 | when isMainModule: 156 | protocol *IUpdatable: 157 | proc update*(this) 158 | 159 | impl Movable: 160 | proc update(this) = 161 | echo("Moving forward.") 162 | proc newMovable(): Movable = 163 | new(result) 164 | 165 | impl *NotMovable: 166 | proc update(this) = 167 | echo("I'm staying put.") 168 | proc newNotMovable*(): NotMovable = 169 | new(result) 170 | -------------------------------------------------------------------------------- /protocoled.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Antonis" 5 | description = "An interface macro for Nim" 6 | license = "MIT" 7 | #srcDir = "src" 8 | 9 | 10 | 11 | # Dependencies 12 | 13 | requires "nim >= 1.0.9" 14 | 15 | after install: 16 | when defined(linux): 17 | echo "hello" 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Protocoled — an interface macro for Nim 3 | 4 | ## About 5 | This nimble package contains the ``protocol`` macro for easily implementing 6 | [interfaces](https://en.wikipedia.org/wiki/Composition_over_inheritance) 7 | in Nim. 8 | 9 | ## The `protocol` macro 10 | Example: 11 | 12 | ```nim 13 | import protocoled 14 | 15 | protocol PExpr: 16 | proc eval(e): int 17 | 18 | impl PLiteral: 19 | var x: int 20 | proc eval(e): int = e.x 21 | proc newLit(x: int): PLiteral = 22 | result = PLiteral(x: x) 23 | 24 | impl PPlusExpr: 25 | var a, b: PExpr 26 | proc eval(e): int = eval(e.a) + eval(e.b) 27 | proc newPlus(a, b: PExpr): PPlusExpr = 28 | result = PPlusExpr(a: a, b: b) 29 | ``` 30 | Notice the typeless parameter `e`, the macro takes care of assigning it the 31 | proper type. Then it is translated roughly into this code: 32 | 33 | ```nim 34 | type 35 | PExpr = ref object of RootObj ## abstract base class for an expression 36 | evalImpl: proc(e: PExpr): int {.nimcall.} 37 | PLiteral = ref object of PExpr 38 | x: int 39 | PPlusExpr = ref object of PExpr 40 | a, b: PExpr 41 | 42 | proc eval(e: PExpr): int = 43 | assert e.evalImpl != nil 44 | e.evalImpl(e) 45 | 46 | proc evalLit(e: PExpr): int = PLiteral(e).x 47 | proc evalPlus(e: PExpr): int = eval(PPlusExpr(e).a) + eval(PPlusExpr(e).b) 48 | 49 | proc newLit(x: int): PLiteral = PLiteral(evalImpl: evalLit, x: x) 50 | proc newPlus(a, b: PExpr): PPlusExpr = PPlusExpr(evalImpl: evalPlus, a: a, b: b) 51 | ``` 52 | 53 | ### Known quirks 54 | - You need to separate the `self` parameter from the rest with a semicolon `;`. 55 | - The export marker `*` is using infix notation like so: `impl *Student`. 56 | - In the constructor `proc`, implicit return of the last expression is not supported. 57 | 58 | ## License 59 | 60 | This library is distributed under the MIT license. For more information see `copying.txt`. 61 | --------------------------------------------------------------------------------