Skip to content

Instantly share code, notes, and snippets.

@PhilipWitte
Last active November 7, 2016 07:23
Show Gist options
  • Save PhilipWitte/f4f0c18f7f6436ee87dc3c506343d859 to your computer and use it in GitHub Desktop.
Save PhilipWitte/f4f0c18f7f6436ee87dc3c506343d859 to your computer and use it in GitHub Desktop.
OOP in Nim
{.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()
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