Fixed up OOP macro, thanks filwit!
This commit is contained in:
parent
516c398dbd
commit
ec3ba34fb8
1 changed files with 110 additions and 102 deletions
|
|
@ -4,85 +4,71 @@ title: OOP Macro
|
||||||
<!--- Thanks to fowl for creating this page -->
|
<!--- Thanks to fowl for creating this page -->
|
||||||
# OOP Macro
|
# OOP Macro
|
||||||
This is the code that we currently must write to use OOP in nimrod:
|
This is the code that we currently must write to use OOP in nimrod:
|
||||||
```nimrod
|
|
||||||
type Animal =
|
|
||||||
ref object {.inheritable.}
|
|
||||||
name: string
|
|
||||||
method vocalize(self: Animal): string = "..."
|
|
||||||
|
|
||||||
type Dog =
|
```nimrod
|
||||||
ref object of Animal
|
type Animal = ref object of TObject
|
||||||
lastRabiesDate: int
|
name: string
|
||||||
method vocalize(self: Dog): string = "woof"
|
age: int
|
||||||
|
method vocalize(this: Animal): string = "..."
|
||||||
|
method ageHumanYrs(this: Animal): int = this.age
|
||||||
|
|
||||||
|
type Dog = ref object of Animal
|
||||||
|
method vocalize(this: Dog): string = "woof"
|
||||||
|
method ageHummanYrs(this: Dog): int = this.age * 7
|
||||||
|
|
||||||
type Cat = ref object of Animal
|
type Cat = ref object of Animal
|
||||||
method vocalize(self: Cat): string = "meow"
|
method vocalize(this: Cat): string = "meow"
|
||||||
```
|
```
|
||||||
|
|
||||||
All these typedefs and `self: 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 and `this: T` parameters are repetitive, so it'd be good to write a macro to mask them. Something like this would be best:
|
||||||
|
|
||||||
```nimrod
|
```nimrod
|
||||||
class(Animal of TObject):
|
class Animal of TObject:
|
||||||
var name: string
|
var name: string
|
||||||
|
var age: int
|
||||||
method vocalize: string = "..."
|
method vocalize: string = "..."
|
||||||
|
method age_human_yrs: int = this.age # `this` is injected
|
||||||
|
|
||||||
class(Dog of Animal):
|
class Dog of Animal:
|
||||||
var lastRabiesDate: int
|
|
||||||
method vocalize: string = "woof"
|
method vocalize: string = "woof"
|
||||||
|
method age_human_yrs: int = this.age * 7
|
||||||
|
|
||||||
class(Cat of Animal):
|
class Cat of Animal:
|
||||||
method vocalize: string = "meow"
|
method vocalize: string = "meow"
|
||||||
|
|
||||||
# a short test (used later)
|
|
||||||
proc run_test() =
|
|
||||||
var pets: seq[Animal] = @[]
|
|
||||||
pets.add Cat(name: "meowth")
|
|
||||||
pets.add Dog(name: "ruffles")
|
|
||||||
assert(@["meow", "woof"] == @[pets[0].vocalize,
|
|
||||||
pets[1].vocalize])
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```nimrod
|
```nimrod
|
||||||
import macros
|
import macros
|
||||||
|
|
||||||
macro class*(): stmt {.immediate.} =
|
macro class*(head: expr, body: stmt): stmt {.immediate.} =
|
||||||
# The macro is immediate so that it doesn't
|
# The macro is immediate so that it doesn't
|
||||||
# resolve identifiers passed to it
|
# resolve identifiers passed to it
|
||||||
|
|
||||||
let cs = callsite()
|
var typeName, baseName: PNimrodNode
|
||||||
var
|
|
||||||
class_name, in_stmts: PNimrodNode
|
|
||||||
superclass: PNimrodNode
|
|
||||||
|
|
||||||
# Manual parsing of arguments. Here we expect
|
if head.kind == NnkIdent:
|
||||||
# a class name and attached stmts list
|
# `head` is expression `typeName`
|
||||||
if cs[1].kind == nnkInfix and $cs[1][0] == "of":
|
# echo head.treeRepr
|
||||||
# the expression is "class_name" of "superclass"
|
# --------------------
|
||||||
# echo cs[1].treerepr
|
# Ident !"Animal"
|
||||||
# -------------------
|
typeName = head
|
||||||
|
|
||||||
|
elif head.kind == NnkInfix and $head[0] == "of":
|
||||||
|
# `head` is expression `typeName of baseClass`
|
||||||
|
# echo head.treeRepr
|
||||||
|
# --------------------
|
||||||
# Infix
|
# Infix
|
||||||
# Ident !"of"
|
# Ident !"of"
|
||||||
# Ident !"Animal"
|
# Ident !"Animal"
|
||||||
# Ident !"TObject"
|
# Ident !"TObject"
|
||||||
|
typeName = head[1]
|
||||||
|
baseName = head[2]
|
||||||
|
|
||||||
class_name = cs[1][1]
|
|
||||||
superclass = cs[1][2]
|
|
||||||
else:
|
else:
|
||||||
class_name = cs[1]
|
quit "Invalide node: " & head.lispRepr
|
||||||
|
|
||||||
in_stmts = cs[2]
|
# echo treeRepr(body)
|
||||||
|
# --------------------
|
||||||
if in_stmts.kind != nnkStmtList:
|
|
||||||
quit "Malformed arguments for class() macro: expected nnkStmtList, got " &
|
|
||||||
lisprepr(in_stmts)
|
|
||||||
assert class_name.kind == nnkIdent
|
|
||||||
|
|
||||||
# echo treerepr(class_name)
|
|
||||||
# -----------------------------------------
|
|
||||||
# Ident !"Animal"
|
|
||||||
|
|
||||||
# echo treerepr(in_stmts)
|
|
||||||
# ----------------------------------
|
|
||||||
# StmtList
|
# StmtList
|
||||||
# VarSection
|
# VarSection
|
||||||
# IdentDefs
|
# IdentDefs
|
||||||
|
|
@ -113,39 +99,34 @@ macro class*(): stmt {.immediate.} =
|
||||||
# Empty
|
# Empty
|
||||||
# StmtList
|
# StmtList
|
||||||
# DotExpr
|
# DotExpr
|
||||||
# Ident !"self"
|
# Ident !"this"
|
||||||
# Ident !"age"
|
# Ident !"age"
|
||||||
|
|
||||||
|
# create a new stmtList for the result
|
||||||
result = newStmtList()
|
result = newStmtList()
|
||||||
result.add newEmptyNode()
|
|
||||||
# create a new stmtList for the result,
|
|
||||||
# the first slot is a placeholder
|
|
||||||
# the type definition we generate
|
|
||||||
|
|
||||||
var rec_list = newNimNode(nnkRecList)
|
|
||||||
# var declarations will be turned into object fields
|
# var declarations will be turned into object fields
|
||||||
|
var recList = newNimNode(nnkRecList)
|
||||||
|
|
||||||
# Iterate over the statements, adding `self: T`
|
# Iterate over the statements, adding `this: T`
|
||||||
# to the parameters of functions
|
# to the parameters of functions
|
||||||
for node in children(in_stmts):
|
for node in body.children:
|
||||||
case node.kind
|
case node.kind:
|
||||||
of nnkMethodDef, nnkProcDef:
|
|
||||||
# inject self:T into the arguments
|
|
||||||
let p = node.params.copyNimTree
|
|
||||||
p.insert(1, newIdentDefs(ident"self", class_name))
|
|
||||||
node.params = p
|
|
||||||
result.add node
|
|
||||||
|
|
||||||
of nnkVarSection:
|
of NnkMethodDef, NnkProcDef:
|
||||||
# variables get turned into fields on the type,
|
# inject `this: T` into the arguments
|
||||||
# isn't that neat?
|
let p = copyNimTree(node.params)
|
||||||
# here we just collect them though
|
p.insert(1, newIdentDefs(ident"this", typeName))
|
||||||
for N in children(node):
|
node.params = p
|
||||||
assert n.kind == nnkIdentDefs, "Invalid node " & lispRepr(N)
|
result.add(node)
|
||||||
rec_list.add N
|
|
||||||
|
of NnkVarSection:
|
||||||
|
# variables get turned into fields of the type.
|
||||||
|
for n in node.children:
|
||||||
|
recList.add(n)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
result.add node
|
result.add(node)
|
||||||
|
|
||||||
# The following prints out the AST structure:
|
# The following prints out the AST structure:
|
||||||
#
|
#
|
||||||
|
|
@ -153,7 +134,7 @@ macro class*(): stmt {.immediate.} =
|
||||||
# dumptree:
|
# dumptree:
|
||||||
# type X = ref object of Y
|
# type X = ref object of Y
|
||||||
# z: int
|
# z: int
|
||||||
# --------------------------
|
# --------------------
|
||||||
# TypeSection
|
# TypeSection
|
||||||
# TypeDef
|
# TypeDef
|
||||||
# Ident !"X"
|
# Ident !"X"
|
||||||
|
|
@ -169,17 +150,34 @@ macro class*(): stmt {.immediate.} =
|
||||||
# Ident !"int"
|
# Ident !"int"
|
||||||
# Empty
|
# Empty
|
||||||
|
|
||||||
var obj_ty = newNimNode(nnkObjectTy).add(
|
result.insert(0,
|
||||||
newEmptyNode(),
|
if baseName == nil:
|
||||||
(if superclass.isNil: newEmptyNode() else: newNimNode(nnkOfInherit).add(superclass)),
|
quote do:
|
||||||
rec_list
|
type `typeName` = ref object
|
||||||
|
else:
|
||||||
|
quote do:
|
||||||
|
type `typeName` = ref object of `baseName`
|
||||||
)
|
)
|
||||||
result[0] = newNimNode(nnkTypeSection).add(
|
# Inspect the tree structure:
|
||||||
newNimNode(nnkTypeDef).add(
|
#
|
||||||
class_name,
|
# echo result.treeRepr
|
||||||
newEmptyNode(),
|
# --------------------
|
||||||
newNimNode(nnkRefTy).add(obj_ty)
|
# StmtList
|
||||||
))
|
# StmtList
|
||||||
|
# TypeSection
|
||||||
|
# TypeDef
|
||||||
|
# Ident !"Animal"
|
||||||
|
# Empty
|
||||||
|
# RefTy
|
||||||
|
# ObjectTy
|
||||||
|
# Empty
|
||||||
|
# OfInherit
|
||||||
|
# Ident !"TObject"
|
||||||
|
# Empty <= We want to replace this
|
||||||
|
# MethodDef
|
||||||
|
# ...
|
||||||
|
|
||||||
|
result[0][0][0][2][0][2] = recList
|
||||||
|
|
||||||
# Lets inspect the human-readable version of the output
|
# Lets inspect the human-readable version of the output
|
||||||
# echo repr(result)
|
# echo repr(result)
|
||||||
|
|
@ -189,31 +187,41 @@ macro class*(): stmt {.immediate.} =
|
||||||
# name: string
|
# name: string
|
||||||
# age: int
|
# age: int
|
||||||
#
|
#
|
||||||
# method vocalize(self: Animal): string =
|
# method vocalize(this: Animal): string =
|
||||||
# "..."
|
# "..."
|
||||||
#
|
#
|
||||||
# method age_human_yrs(self: Animal): int =
|
# method age_human_yrs(this: Animal): int =
|
||||||
# self.age
|
# this.age
|
||||||
|
|
||||||
# more could be done here, it could be made an
|
# ---
|
||||||
# option to use ref types, export type names, etc
|
|
||||||
|
|
||||||
|
class Animal of TObject:
|
||||||
class(Animal of TObject):
|
var name: string
|
||||||
var
|
var age: int
|
||||||
name: string
|
|
||||||
age: int
|
|
||||||
method vocalize: string = "..."
|
method vocalize: string = "..."
|
||||||
method age_human_yrs: int = self.age
|
method age_human_yrs: int = this.age # `this` is injected
|
||||||
# self is injected
|
|
||||||
|
|
||||||
class(Dog of Animal):
|
class Dog of Animal:
|
||||||
method vocalize: string = "woof"
|
method vocalize: string = "woof"
|
||||||
method age_human_yrs: int = self.age * 7
|
method age_human_yrs: int = this.age * 7
|
||||||
|
|
||||||
class(Cat of Animal) do:
|
class Cat of Animal:
|
||||||
method vocalize: string = "meow"
|
method vocalize: string = "meow"
|
||||||
|
|
||||||
run_test()
|
# ---
|
||||||
echo "Tests passed!"
|
|
||||||
|
var animals: seq[Animal] = @[]
|
||||||
|
animals.add(Dog(name: "Sparky", age: 10))
|
||||||
|
animals.add(Cat(name: "Mitten", age: 10))
|
||||||
|
|
||||||
|
for a in animals:
|
||||||
|
echo a.vocalize()
|
||||||
|
echo a.age_human_yrs()
|
||||||
|
```
|
||||||
|
``` console
|
||||||
|
$ nimrod c -r oopmacro.nim
|
||||||
|
woof
|
||||||
|
70
|
||||||
|
meow
|
||||||
|
10
|
||||||
```
|
```
|
||||||
Loading…
Add table
Add a link
Reference in a new issue