Fixed some whitespace issues and added init variables.

This commit is contained in:
Joey Payne 2015-07-05 22:15:14 -06:00
commit f3e7bee8e7

View file

@ -4,136 +4,141 @@ import strutils
{.hint[XDeclaredButNotUsed]: off.} {.hint[XDeclaredButNotUsed]: off.}
macro new*(obj: untyped): untyped {.immediate.}= macro new*(obj: untyped): untyped {.immediate.}=
## Creates a new instance of the class if obj.kind == nnkObjConstr or obj.kind == nnkCall:
## and calls the init() method on it var args: seq[NimNode] = @[]
if obj.kind == nnkObjConstr: var new_obj = copyNimTree(obj)
# Only if the node is an object constructor
template init_object(obj)= # delete the parameters from the object
var init_obj = obj # since we are going to use init() instead
when compiles(init_obj.init()): new_obj.del(1, len(new_obj)-1)
init_obj.init()
init_obj # create a symbol that we can use
result = getAst(init_object(obj)) var sym = genSym(nskVar, "init_obj")
# self = init_obj
var self_eq = newNimNode(nnkExprEqExpr).add(ident"self", sym)
args.add(self_eq)
for ch in obj.children:
if ch.kind == nnkExprColonExpr:
# some_arg = some_val
args.add(newNimNode(nnkExprEqExpr).add(ch[0], ch[1]))
# init(self=init_obj, some_arg=someval, ...)
var init_call = newCall(ident"init", args)
template new_object(symbol, obj_node, init_func)=
var symbol = obj_node
when compiles(init_func):
init_func
symbol
result = getAst(new_object(sym, new_obj, init_call))
else: else:
# Otherwise, just revert to system.new # otherwise, just call system.new on the object
template init_object(obj)= # since we don't care about it
var init_obj = obj template new_object(obj)=
system.new(init_obj) var init_obj = obj
system.new(init_obj)
result = getAst(init_object(obj)) result = getAst(new_object(obj))
macro class*(head: untyped, body: untyped): untyped= macro class*(head: untyped, body: untyped): untyped =
# object reference name inside methods.
# ie: self, self # object reference name inside methods.
let obj_reference = "self" # ie: self, self
var export_class: bool = false # whether or not to export the class to other modules let obj_reference = "self"
var export_class: bool = false
var typeName, baseName: NimNode var typeName, baseName: NimNode
if head.kind == nnkIdent: if head.kind == nnkIdent:
# `head` is expression `typeName` # `head` is expression `typeName`
# echo head.treeRepr # 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]
elif head.kind == nnkInfix and $head[0] == "*" and $head[1] == "of":
# echo head.treeRepr
# -----------
# Infix
# Ident !"*"
# Ident !"Animal
# Prefix
# Ident !"of"
# Ident !"RootObj"
export_class = true
typeName = head[1]
baseName = head[2][1]
elif head.kind == nnkInfix and $head[0] == "*":
export_class = true
typeName = head[1]
else:
quit "Invalid node: " & head.lispRepr
# echo treeRepr(body)
# -------------------- # --------------------
# Ident !"Animal" # StmtList
typeName = head # 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"
elif head.kind == nnkInfix and $head[0] == "of": # create a new stmtList for the result
# `head` is expression `typeName of baseClass` result = newStmtList()
# echo head.treeRepr
# --------------------
# Infix
# Ident !"of"
# Ident !"Animal"
# Ident !"RootObj"
typeName = head[1]
baseName = head[2]
elif head.kind == nnkInfix and $head[0] == "*" and $head[1] == "of": # var declarations will be turned into object fields
# echo head.treeRepr var recList = newNimNode(nnkRecList)
# -----------
# Infix
# Ident !"*"
# Ident !"Animal"
# Prefix
# Ident !"of"
# Ident !"RootObj"
export_class = true
typeName = head[1]
baseName = head[2][1]
elif head.kind == nnkInfix and $head[0] == "*":
export_class = true
typeName = head[1]
else:
quit "Invalid node: " & head.lispRepr
# echo treeRepr(body) # add a super function to simulate OOP
# -------------------- # inheritance tree (Doesn't do what is expected because of dynamic binding)
# StmtList #if not isNil(`baseName`):
# VarSection # var super = quote do:
# IdentDefs # proc super(self: `typeName`): `baseName`=
# Ident !"name" # return `baseName`(self)
# Ident !"string" # result.add(super)
# 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 type_name = $(typeName.toStrLit())
var proc_name = ""
template set_node_name(n2, proc_name, type_name)=
if n2.name.kind == nnkIdent: if n2.name.kind == nnkIdent:
proc_name = $(n2.name.toStrLit()) proc_name = $(n2.name.toStrLit())
n2.name = ident(proc_name & type_name) n2.name = ident(proc_name & type_name)
@ -148,145 +153,159 @@ macro class*(head: untyped, body: untyped): untyped=
proc_name = $(n2.name[0].toStrLit()) proc_name = $(n2.name[0].toStrLit())
n2.name[0] = ident(proc_name & type_name) n2.name[0] = ident(proc_name & type_name)
result.add(n2) result.add(n2)
else:
discard
# Iterate over the statements, adding `self: T` # Make forward declarations so that function order
# to the parameters of functions # does not matter, just like in real OOP!
for node in body.children: for node in body.children:
case node.kind: case node.kind:
of nnkMethodDef, nnkProcDef: of nnkMethodDef, nnkProcDef:
# inject `self: T` into the arguments # inject `self: T` into the arguments
let n = copyNimTree(node) let n = copyNimTree(node)
n.params.insert(1, newIdentDefs(ident(obj_reference), typeName)) n.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
# clear the body so we only get a
# declaration
n.body = newEmptyNode()
result.add(n)
# Copy the proc or method for inheritance # forward declare the inheritable method
# ie: procName_ClassName() let n2 = copyNimTree(n)
let n2 = copyNimTree(node) let type_name = $(typeName.toStrLit())
n2.params.insert(1, newIdentDefs(ident(obj_reference), typeName)) var proc_name = ""
let type_name = $(typeName.toStrLit()) set_node_name(n2, proc_name, type_name)
var proc_name = $(n2.name.toStrLit()) else:
var is_assignment = proc_name.contains("=") discard
if n2.name.kind == nnkIdent: # Iterate over the statements, adding `self: T`
proc_name = $(n2.name.toStrLit()) # to the parameters of functions
n2.name = ident(proc_name & type_name) for node in body.children:
elif n2.name.kind == nnkPostFix: case node.kind:
if n2.name[1].kind == nnkIdent: of nnkMethodDef, nnkProcDef:
proc_name = $(n2.name[1].toStrLit()) # inject `self: T` into the arguments
n2.name[1] = ident(proc_name & type_name) let n = copyNimTree(node)
elif n2.name[1].kind == nnkAccQuoted: n.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
proc_name = $(n2.name[1][0].toStrLit())
n2.name[1][0] = ident(proc_name & type_name)
elif n2.name.kind == nnkAccQuoted:
proc_name = $(n2.name[0].toStrLit())
n2.name[0] = ident(proc_name & type_name)
result.add(n2) # Copy the proc or method for inheritance
# ie: procName_ClassName()
let n2 = copyNimTree(node)
n2.params.insert(1, newIdentDefs(ident(obj_reference), typeName))
# simply call the class method from here let type_name = $(typeName.toStrLit())
# proc procName= var proc_name = $(n2.name.toStrLit())
# procName_ClassName() var is_assignment = proc_name.contains("=")
var p: seq[NimNode] = @[]
for i in 1..n.params.len-1: set_node_name(n2, proc_name, type_name)
p.add(n.params[i][0])
if is_assignment: # simply call the class method from here
let dot = newDotExpr(ident(obj_reference), ident(proc_name & type_name)) # proc procName=
n.body = newStmtList(newAssignment(dot, p[1])) # procName_ClassName()
var p: seq[NimNode] = @[]
for i in 1..n.params.len-1:
p.add(n.params[i][0])
if is_assignment:
let dot = newDotExpr(ident(obj_reference), ident(proc_name & type_name))
n.body = newStmtList(newAssignment(dot, p[1]))
else:
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: NimNode
template declare_type_export(tname, bname)=
type tname* = ref object of bname
template declare_type(tname, bname)=
type tname = ref object of bname
if baseName == nil:
if export_class:
type_decl = getAst(declare_type_export(typeName, RootObj))
else: else:
n.body = newStmtList(newCall(proc_name & type_name, p)) type_decl = getAst(declare_type(typeName, RootObj))
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: NimNode
template declare_type_export(tname, bname)=
type tname* = ref object of bname
template declare_type(tname, bname)=
type tname = ref object of bname
if baseName == nil:
if export_class:
type_decl = getAst(declare_type_export(typeName, RootObj))
else: else:
type_decl = getAst(declare_type(typeName, RootObj)) if export_class:
else: type_decl = getAst(declare_type_export(typeName, baseName))
if export_class: else:
type_decl = getAst(declare_type_export(typeName, baseName)) type_decl = getAst(declare_type(typeName, baseName))
else:
type_decl = getAst(declare_type(typeName, baseName))
# Inspect the tree structure: # Inspect the tree structure:
# #
# echo type_decl.treeRepr # echo type_decl.treeRepr
# -------------------- # --------------------
# StmtList # StmtList
# TypeSection # TypeSection
# TypeDef # TypeDef
# Ident !"Animal" # Ident !"Animal"
# Empty # Empty
# RefTy # RefTy
# ObjectTy # ObjectTy
# Empty # Empty
# OfInherit # OfInherit
# Ident !"RootObj" # Ident !"RootObj"
# Empty <= We want to replace self # Empty <= We want to replace self
type_decl[0][0][2][0][2] = recList type_decl[0][0][2][0][2] = recList
result.insert(0, type_decl) result.insert(0, type_decl)
class Animal* of RootObj: #exports the class. Need to use RootObj for exports class Animal of RootObj:
var name: string var
var age: int name: string
method stuff(s:string): string = s age: int
method vocalize: string = "..."
method age_human_yrs: int = self.age # `self` is injected method init*(name: string, age: int)=
self.name = name
self.age = age
echo "I am a new Animal, ", self.name
method stuff(s:string): string = s
method vocalize: string = "..."
method age_human_yrs: int = self.age # `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 = self.age * 7
class Cat of Animal: class Cat of Animal:
method vocalize: string = method vocalize: string =
# call the base class method # call the base class method
self.vocalize_animal() & "meow" self.vocalize_animal() & "meow"
class Tiger of Cat: class Tiger of Cat:
method init()= method init(name: string="Bob", age: int)=
echo "I am a new tiger" self.init_animal(name, age)
method vocalize: string = echo "I am a new tiger"
# no need for super.super! method vocalize: string =
self.vocalize_animal() & "Rawr!" & self.vocalize_cat() # no need for super.super!
self.vocalize_animal() & "Rawr!"
if isMainModule: if isMainModule:
var animals: seq[Animal] = @[] var animals: seq[Animal] = @[]
@ -295,14 +314,17 @@ if isMainModule:
animals.add(new Tiger(name: "Jean", age: 2)) animals.add(new Tiger(name: "Jean", age: 2))
for a in animals: for a in animals:
echo a.name, " says ", a.vocalize() echo a.name, " says ", a.vocalize()
echo a.age_human_yrs() echo a.age_human_yrs()
# prints: # prints:
# I am a new Animal, Sparky
# I am a new Animal, Mitten
# I am a new Animal, Jean
# I am a new tiger # I am a new tiger
# Sparky says woof # Sparky says woof
# 70 # 70
# Mitten says ...meow # Mitten says ...meow
# 10 # 10
# Jean says ...Rawr!...meow # Jean says ...Rawr!
# 2 # 2