Skip to content

Instantly share code, notes, and snippets.

@PhilipWitte
Last active August 29, 2015 14:02
Show Gist options
  • Save PhilipWitte/56a2e702a9b6e17feb3a to your computer and use it in GitHub Desktop.
Save PhilipWitte/56a2e702a9b6e17feb3a to your computer and use it in GitHub Desktop.
import macros
macro class*(head:expr, body:stmt): stmt {.immediate.} =
# The macro is immediate so that it doesn't
# resolve identifiers passed to it
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 !"TObject"
typeName = head[1]
baseName = head[2]
else:
quit "Invalide 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 !"this"
# Ident !"age"
# create a new stmtList for the result
result = newStmtList()
# var declarations will be turned into object fields
var recList = newNimNode(nnkRecList)
# 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)
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
result.insert(0, newNimNode(NnkTypeSection).add(
newNimNode(NnkTypeDef).add(
typeName,
newEmptyNode(),
newNimNode(NnkRefTy).add(
newNimNode(NnkObjectTy).add(
newEmptyNode(),
if baseName == nil: newEmptyNode()
else: newNimNode(NnkOfInherit).add(baseName),
recList)))))
# Lets inspect the human-readable version of the output
# echo repr(result)
# Output:
# type
# Animal = ref object of TObject
# name: string
# age: int
#
# method vocalize(this: Animal): string =
# "..."
#
# method age_human_yrs(this: Animal): int =
# 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:
var name: string
var age: int
method vocalize: string = "..."
method age_human_yrs: int = this.age # `this` is injected
class Dog of Animal:
method vocalize: string = "woof"
method age_human_yrs: int = this.age * 7
class Cat of Animal:
method vocalize: string = "meow"
# ---
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()
# Output:
# woof
# 70
# meow
# 10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment