diff --git a/content/oop_macro.md b/content/oop_macro.md index 3363943..03d1e6a 100644 --- a/content/oop_macro.md +++ b/content/oop_macro.md @@ -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,62 @@ 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 - result.add(node) + 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) @@ -153,19 +208,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 +229,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 +314,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 ```