nim-extensions/oop_macro.nim
2015-02-06 22:12:19 +13:00

233 lines
5.9 KiB
Nim

import macros
{.hint[XDeclaredButNotUsed]: off.}
macro new*(obj: expr): expr {.immediate.}=
## Replaces the new keyword to call an init
## function if it exists
quote do:
var init_obj = `obj`
when compiles(init_obj.init()):
init_obj.init()
init_obj
macro class*(head: expr, body: stmt): stmt {.immediate.} =
# The macro is immediate so that it doesn't
# resolve identifiers passed to it
# object reference name inside methods.
# ie: self, self
let obj_reference = "self"
var typeName, baseName: PNimrodNode
if head.kind == nnkIdent:
# `head` is expression `typeName`
# echo head.treeRepr
# --------------------
# Ident !"Animal"
typeName = head
elif head.kind == nnkInfix and $head[0] == "of":
# `head` is expression `typeName of baseClass`
# echo head.treeRepr
# --------------------
# Infix
# Ident !"of"
# Ident !"Animal"
# Ident !"RootObj"
typeName = head[1]
baseName = head[2]
else:
quit "Invalid node: " & head.lispRepr
# echo treeRepr(body)
# --------------------
# StmtList
# VarSection
# IdentDefs
# Ident !"name"
# Ident !"string"
# Empty
# IdentDefs
# Ident !"age"
# Ident !"int"
# Empty
# MethodDef
# Ident !"vocalize"
# Empty
# Empty
# FormalParams
# Ident !"string"
# Empty
# Empty
# StmtList
# StrLit ...
# MethodDef
# Ident !"age_human_yrs"
# Empty
# Empty
# FormalParams
# Ident !"int"
# Empty
# Empty
# StmtList
# DotExpr
# Ident !"self"
# Ident !"age"
# create a new stmtList for the result
result = newStmtList()
# var declarations will be turned into object fields
var recList = newNimNode(nnkRecList)
# add a super function to simulate OOP
# inheritance tree (Doesn't do what is expected because of dynamic binding)
#if not isNil(`baseName`):
# var super = quote do:
# proc super(self: `typeName`): `baseName`=
# return `baseName`(self)
# result.add(super)
# Make forward declarations so that function order
# does not matter, just like in real OOP!
for node in body.children:
case node.kind:
of nnkMethodDef, nnkProcDef:
# inject `self: T` into the arguments
let n = copyNimTree(node)
n.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
# clear the body so we only get a
# declaration
n.body = newEmptyNode()
result.add(n)
# forward declare the inheritable method
let n2 = copyNimTree(n)
let proc_name = $(n2.name.toStrLit())
let type_name = $(typeName.toStrLit())
let new_name = ident(proc_name & type_name)
n2.name = new_name
result.add(n2)
else:
discard
# Iterate over the statements, adding `self: T`
# to the parameters of functions
for node in body.children:
case node.kind:
of nnkMethodDef, nnkProcDef:
# inject `self: T` into the arguments
let n = copyNimTree(node)
n.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
# Copy the proc or method for inheritance
# ie: procName_ClassName()
let n2 = copyNimTree(node)
n2.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
let proc_name = $(n2.name.toStrLit())
let type_name = $(typeName.toStrLit())
let new_name = ident(proc_name & type_name)
n2.name = new_name
result.add(n2)
# simply call the class method from here
# proc procName=
# procName_ClassName()
var p: seq[PNimrodNode] = @[]
for i in 1..n.params.len-1:
p.add(n.params[i][0])
n.body = newStmtList(newCall(proc_name & type_name, p))
result.add(n)
of nnkVarSection:
# variables get turned into fields of the type.
for n in node.children:
recList.add(n)
else:
result.add(node)
# The following prints out the AST structure:
#
# import macros
# dumptree:
# type X = ref object of Y
# z: int
# --------------------
# TypeSection
# TypeDef
# Ident !"X"
# Empty
# RefTy
# ObjectTy
# Empty
# OfInherit
# Ident !"Y"
# RecList
# IdentDefs
# Ident !"z"
# Ident !"int"
# Empty
var type_decl: PNimrodNode
if baseName == nil:
type_decl = quote do:
type `typeName` = ref object of RootObj
else:
type_decl = quote do:
type `typeName` = ref object of `baseName`
# Inspect the tree structure:
#
# echo type_decl.treeRepr
# --------------------
# StmtList
# TypeSection
# TypeDef
# Ident !"Animal"
# Empty
# RefTy
# ObjectTy
# Empty
# OfInherit
# Ident !"RootObj"
# Empty <= We want to replace self
type_decl[0][0][2][0][2] = recList
result.insert(0, type_decl)
class Animal of RootObj:
var name: string
var age: int
method stuff(s:string): string = s
method vocalize: string = "..."
method age_human_yrs: int = self.age # `self` is injected
class Dog of Animal:
method vocalize: string = "woof"
method age_human_yrs: int = self.age * 7
class Cat of Animal:
method vocalize: string =
# call the base class method
self.vocalize_animal() & "meow"
class Tiger of Cat:
method init()=
echo "I am a new tiger"
method vocalize: string =
# no need for super.super!
self.vocalize_animal() & "Rawr!"
if isMainModule:
var animals: seq[Animal] = @[]
animals.add(new(Dog(name: "Sparky", age: 10)))
animals.add(new(Cat(name: "Mitten", age: 10)))
animals.add(new(Tiger(name: "Jean", age: 2)))
for a in animals:
echo a.name, " says ", a.vocalize()
echo a.age_human_yrs()