Merge branch 'adding-oop-macro'
This commit is contained in:
commit
e3d9812afe
2 changed files with 259 additions and 0 deletions
26
README.md
26
README.md
|
|
@ -1,2 +1,28 @@
|
|||
# nim-extensions
|
||||
Extensions for the nim programming language.
|
||||
|
||||
These extensions aim to improve the usability of nim in practical applications.
|
||||
|
||||
Extensions so far include:
|
||||
|
||||
## OOP macro
|
||||
usage:
|
||||
|
||||
```nim
|
||||
class BaseObject: # inherits from RootObj
|
||||
# attributes/properties
|
||||
var
|
||||
x: int
|
||||
y: float
|
||||
|
||||
method override_me(argx: float): int=
|
||||
result = int(argx) + self.x
|
||||
|
||||
class ClassName of BaseObject:
|
||||
method method_name(arg1: int, arg2: float=0.3): float=
|
||||
## do stuff here
|
||||
result = arg2
|
||||
|
||||
method override_me(argx: float): int=
|
||||
result = int(self.y) + int(argx) + self.x
|
||||
```
|
||||
|
|
|
|||
233
oop_macro.nim
Normal file
233
oop_macro.nim
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue