Last active
November 7, 2016 07:23
-
-
Save PhilipWitte/f4f0c18f7f6436ee87dc3c506343d859 to your computer and use it in GitHub Desktop.
OOP in Nim
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{.this: self.} | |
import syntax | |
type | |
Person = object | |
age: int | |
name: string | |
var | |
boys: seq[Person] | |
girls: seq[Person] | |
impl Person: | |
proc new(name:string, age = 0) {.constructor.} = | |
self.name = name | |
self.age = age | |
proc newBoy(name:string, age = 0) {.constructor.} = | |
self.new(name, age) | |
boys.add(self) | |
proc newGirl(name:string, age = 0) {.constructor.} = | |
self.new(name, age) | |
girls.add(self) | |
proc greet = | |
echo "Hello, I am ", name, "." | |
echo "I'm ", age, " years old." | |
impl type Person: | |
proc setup = | |
boys = @[] | |
girls = @[] | |
proc greetAll = | |
echo "Boys:" | |
for boy in boys: boy.greet() | |
echo "Girls:" | |
for girl in girls: girl.greet() | |
# --- | |
Person.setup() | |
var bob = Person.new("Bob", 45) | |
var joe = Person.newBoy("Joe", 12) | |
var mat = Person.newBoy("Mat", 10) | |
var sue = Person.newGirl("Sue", 9) | |
bob.greet() | |
Person.greetAll() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import macros | |
# ---------- ---------- ---------- # | |
proc hasPragma(procedure:NimNode, name:string): bool = | |
## checks if a procedure has a specific pragma | |
for p in procedure.pragma.children: | |
if $p == name: | |
return true | |
# --- | |
proc isRefType(typeNode:NimNode): bool = | |
return | |
typeNode.kind == nnkBracketExpr and | |
typeNode[0].kind == nnkSym and | |
$typeNode[0] == "ref" | |
#proc isVarType(typeNode:NimNode): bool = | |
# return | |
# typeNode.kind == nnkBracketExpr and | |
# typeNode[0].kind == nnkSym and | |
# $typeNode[0] == "var" | |
# --- | |
proc setupParams(procedure:NimNode, typeIdent:var NimNode, initParams, callParams:var seq[NimNode]) = | |
## adds params to an allocator or constructor | |
let procParams = procedure.params | |
assert procParams.len > 1 | |
typeIdent = procParams[1][1] | |
initParams.add(typeIdent) | |
initParams.add(newIdentDefs(ident"_", newTree(nnkTypeOfExpr, typeIdent))) | |
callParams.add(newIdentNode("result")) | |
for i in 2 .. <procParams.len: | |
initParams.add(procParams[i]) | |
for c in 0 .. procParams[i].len-3: | |
callParams.add(procParams[i][c]) | |
# --- --- --- # | |
macro allocator*(procedure:untyped): typed = | |
## Converts a procedure into an allocator. | |
## | |
## example: | |
## | |
## proc foo(self:Bar, ...) {.allocator, custom...} = | |
## body... | |
## | |
## converts into: | |
## | |
## proc foo(_:type Bar, ...): Bar {.custom...} = | |
## template construct(self:Bar, ...) = | |
## body... | |
## construct(result, ...) | |
## | |
assert procedure.kind in {nnkProcDef, nnkTemplateDef, nnkMacroDef} | |
let noInit = procedure.hasPragma("noinit") | |
let consName = newIdentNode("construct") | |
var initParams = newSeq[NimNode]() | |
var callParams = newSeq[NimNode]() | |
var selfType: NimNode | |
procedure.setupParams(selfType, initParams, callParams) | |
let constructor = newTree( | |
nnkTemplateDef, | |
consName, | |
newEmptyNode(), | |
newEmptyNode(), | |
procedure.params, | |
newEmptyNode(), | |
newEmptyNode(), | |
procedure.body | |
) | |
let initBody = newTree(nnkStmtList, constructor) | |
if not noInit: | |
initBody.add newTree( | |
nnkWhenStmt, | |
newTree( | |
nnkElifBranch, | |
infix(selfType, "is", newNimNode(nnkRefTy)), | |
newCall(newDotExpr(ident"system", ident"new"), ident"result") | |
) | |
) | |
initBody.add newCall(consName, callParams) | |
result = newProc(procedure.name, initParams, initBody) | |
result.pragma = copy(procedure.pragma) | |
if not noInit: | |
result.pragma.add(ident"noinit") | |
# --- --- --- # | |
macro constructor*(procedure:untyped): typed = | |
## Converts a procedure into an constructor. | |
## | |
## example: | |
## | |
## proc foo(self:Bar, ...) {.constructor, custom...} = | |
## body... | |
## | |
## converts into: | |
## | |
## proc foo(self:Bar, ...) {.custom...} = | |
## body... | |
## | |
## proc foo(_:type Bar, ...): Bar {.inline, noinit, custom...} = | |
## system.new(result) | |
## foo(result, ...) | |
## | |
assert procedure.kind in {nnkProcDef, nnkTemplateDef, nnkMacroDef} | |
let initName = procedure.name | |
let consParams = procedure.params | |
var initParams = newSeq[NimNode]() | |
var callParams = newSeq[NimNode]() | |
var selfType: NimNode | |
procedure.setupParams(selfType, initParams, callParams) | |
let thisType = getType(selfType)[1] | |
if not thisType.isRefType: | |
consParams[1][1] = newTree(nnkVarTy, consParams[1][1]) | |
let constructor = newTree( | |
nnkProcDef, | |
initName, | |
newEmptyNode(), | |
newEmptyNode(), | |
consParams, | |
procedure.pragma, | |
newEmptyNode(), | |
procedure.body | |
) | |
let initBody = newTree( | |
nnkStmtList, | |
newTree( | |
nnkWhenStmt, | |
newTree( | |
nnkElifBranch, | |
infix(selfType, "is", newNimNode(nnkRefTy)), | |
newCall(newDotExpr(ident"system", ident"new"), ident"result") | |
) | |
), | |
newCall( | |
if initName.kind in {nnkPrefix, nnkPostfix}: | |
initName.basename | |
else: | |
initName, | |
callParams | |
) | |
) | |
let allocator = newProc(initName, initParams, initBody, procedure.kind) | |
allocator.pragma = copy(procedure.pragma) | |
allocator.pragma.addIdentIfAbsent("inline") | |
allocator.pragma.addIdentIfAbsent("noinit") | |
return newTree(nnkStmtList, constructor, allocator) | |
# ---------- ---------- ---------- # | |
const | |
# default name of first param | |
selfName = "self" | |
# set of node kinds which impliment a type | |
selfProcKinds = { | |
nnkProcDef, | |
nnkTemplateDef, | |
nnkMacroDef, | |
nnkIteratorDef, | |
nnkConverterDef | |
} | |
# --- --- --- # | |
iterator pairs(node:NimNode): (int, NimNode) = | |
for i in 0 .. <node.len: | |
yield (i, node[i]) | |
# --- | |
macro doImpl(head:typed, body:untyped): untyped = | |
## This does the actual work for `impl`. NOTE: We make `impl` separate to restrict it's | |
## `head` to a `typedesc` for call resolution, but we want `doImpl` to take `typed` so we | |
## can correctly extract the specific syntax which was passed to `impl`. Eg, there is a | |
## a differece between `impl Foo` and `impl type Foo` we can make use of here. | |
let self = ident(selfName) | |
for i, node in body: | |
case node.kind: | |
of selfProcKinds: | |
node.params.insert(1, newIdentDefs(self, head)) | |
of nnkCommand: | |
# TODO: handle 'mode', nested 'impl', etc.. | |
warning "unsupported `impl` command: '" & $node[0] & "'" | |
else: | |
error "unsupported node kind: '" & $node.kind & "'" | |
return body | |
# --- | |
template impl*(head:typedesc, body:untyped): typed = | |
## Impliments a set of procedures for a type. | |
## | |
## example: | |
## | |
## impl int: | |
## proc foo = ... | |
## | |
## converts into: | |
## | |
## proc foo(self:int) = ... | |
## | |
doImpl(head, body) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment