Updated oop_macro.nim with some improvements.

Added out of order method execution inside the class.
Added super-like functionality to call parent methods from child methods.
This commit is contained in:
Joey 2015-01-29 16:38:05 +13:00
commit 4d3905dd23

View file

@ -11,6 +11,7 @@ type Animal = ref object of RootObj
name: string
age: int
method vocalize(this: Animal): string = "..."
method vocalizeAnimal(this: Animal): string = "..."
method ageHumanYrs(this: Animal): int = this.age
type Dog = ref object of Animal
@ -18,10 +19,14 @@ method vocalize(this: Dog): string = "woof"
method ageHumanYrs(this: Dog): int = this.age * 7
type Cat = ref object of Animal
method vocalize(this: Cat): string = "meow"
method vocalize(this: Cat): string = this.vocalize_animal() & "meow"
method vocalizeCat(this: Cat): string = this.vocalize_animal() & "meow"
type Tiger = ref object of Cat
method vocalize(this: Tiger): string = this.vocalize_animal() & "Rawr!"
```
All these typedefs and `this: T` parameters are repetitive, so it'd be good to write a macro to mask them. Something like this would be best:
All these typedefs, `this: T` parameters, and extra methods are repetitive, so it'd be good to write a macro to mask them. Something like this would be best:
```nimrod
class Animal of RootObj:
@ -35,10 +40,17 @@ class Dog of Animal:
method age_human_yrs: int = this.age * 7
class Cat of Animal:
method vocalize: string = "meow"
method vocalize: string =
# call the base class method
this.vocalize_animal() & "meow"
class Tiger of Cat:
method vocalize: string =
# no need for super.super!
this.vocalize_animal() & "Rawr!"
```
To get that nice notation, we can use a macro:
To get that nice notation, we can use a macro. Note that this looks pretty complicated, and it is, but it will give us almost full OOP capabilities like other languages such as Java and Python. Here's the macro in all it's glory:
```nimrod
import macros
@ -47,6 +59,10 @@ 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, this
let obj_reference = "this"
var typeName, baseName: PNimrodNode
if head.kind == nnkIdent:
@ -63,7 +79,7 @@ macro class*(head: expr, body: stmt): stmt {.immediate.} =
# Infix
# Ident !"of"
# Ident !"Animal"
# Ident !"RootObj"
# Ident !"TObject"
typeName = head[1]
baseName = head[2]
@ -111,23 +127,50 @@ macro class*(head: expr, body: stmt): stmt {.immediate.} =
# var declarations will be turned into object fields
var recList = newNimNode(nnkRecList)
# 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 `this: 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 `this: T`
# to the parameters of functions
for node in body.children:
case node.kind:
of nnkMethodDef, nnkProcDef:
# inject `this: T` into the arguments
let p = copyNimTree(node.params)
p.insert(1, newIdentDefs(ident"this", typeName))
node.params = p
node.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
result.add(node)
# Copy the proc or method for inheritance
# ie: procName_ClassName()
let n2 = copyNimTree(node)
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)
of nnkVarSection:
# variables get turned into fields of the type.
for n in node.children:
recList.add(n)
else:
result.add(node)
@ -153,19 +196,18 @@ macro class*(head: expr, body: stmt): stmt {.immediate.} =
# Ident !"int"
# Empty
result.insert(0,
if baseName == nil:
quote do:
type `typeName` = ref object
else:
quote do:
type `typeName` = ref object of `baseName`
)
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 result.treeRepr
# echo type_decl.treeRepr
# --------------------
# StmtList
# StmtList
# TypeSection
# TypeDef
@ -175,28 +217,79 @@ macro class*(head: expr, body: stmt): stmt {.immediate.} =
# ObjectTy
# Empty
# OfInherit
# Ident !"RootObj"
# Ident !"TObject"
# Empty <= We want to replace this
# MethodDef
# ...
result[0][0][0][2][0][2] = recList
type_decl[0][0][2][0][2] = recList
result.insert(0, type_decl)
# Lets inspect the human-readable version of the output
# echo repr(result)
# Output:
# type
# type
# Animal = ref object of RootObj
# name: string
# age: int
#
# method vocalize(this: Animal): string =
# method vocalize(this: Animal): string
# method vocalizeAnimal(this: Animal): string <= this is the magic for calling base class methods
# method age_human_yrs(this: Animal): int
# method age_human_yrsAnimal(this: Animal): int <= this is the magic for calling base class methods
# method vocalize(this: Animal): string =
# "..."
#
# method vocalizeAnimal(this: Animal): string =
# "..."
#
# method age_human_yrs(this: Animal): int =
# method age_human_yrs(this: Animal): int =
# this.age
#
# method age_human_yrsAnimal(this: Animal): int =
# this.age
#
#
# type
# Dog = ref object of Animal
#
# method vocalize(this: Dog): string
# method vocalizeDog(this: Dog): string
# method age_human_yrs(this: Dog): int
# method age_human_yrsDog(this: Dog): int
# method vocalize(this: Dog): string =
# "woof"
#
# method vocalizeDog(this: Dog): string =
# "woof"
#
# method age_human_yrs(this: Dog): int =
# this.age * 7
#
# method age_human_yrsDog(this: Dog): int =
# this.age * 7
#
#
# type
# Cat = ref object of Animal
#
# method vocalize(this: Cat): string
# method vocalizeCat(this: Cat): string
# method vocalize(this: Cat): string =
# this.vocalize_animal() & "meow"
#
# method vocalizeCat(this: Cat): string =
# this.vocalize_animal() & "meow"
#
#
# type
# Tiger = ref object of Cat
#
# method vocalize(this: Tiger): string
# method vocalizeTiger(this: Tiger): string
# method vocalize(this: Tiger): string =
# this.vocalize_animal() & "Rawr!"
#
# method vocalizeTiger(this: Tiger): string =
# this.vocalize_animal() & "Rawr!"
# ---
class Animal of RootObj:
var name: string
@ -209,22 +302,30 @@ class Dog of Animal:
method age_human_yrs: int = this.age * 7
class Cat of Animal:
method vocalize: string = "meow"
method vocalize: string =
# call the base class method
this.vocalize_animal() & "meow"
# ---
class Tiger of Cat:
method vocalize: string =
# no need for super.super!
this.vocalize_animal() & "Rawr!"
var animals: seq[Animal] = @[]
animals.add(Dog(name: "Sparky", age: 10))
animals.add(Cat(name: "Mitten", age: 10))
animals.add(Tiger(name: "Jean", age: 2))
for a in animals:
echo a.vocalize()
echo a.name, " says ", a.vocalize()
echo a.age_human_yrs()
```
``` console
$ nim c -r oopmacro.nim
woof
Sparky says woof
70
meow
Mitten says ...meow
10
Jean says ...Rawr!
2
```