twithin_macro.nim 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import macros
  2. macro class*(head, body: untyped): untyped =
  3. # The macro is immediate, since all its parameters are untyped.
  4. # This means, it doesn't resolve identifiers passed to it.
  5. var typeName, baseName: NimNode
  6. # flag if object should be exported
  7. var exported: bool
  8. if head.kind == nnkInfix and head[0].ident == !"of":
  9. # `head` is expression `typeName of baseClass`
  10. # echo head.treeRepr
  11. # --------------------
  12. # Infix
  13. # Ident !"of"
  14. # Ident !"Animal"
  15. # Ident !"RootObj"
  16. typeName = head[1]
  17. baseName = head[2]
  18. elif head.kind == nnkInfix and head[0].ident == !"*" and
  19. head[2].kind == nnkPrefix and head[2][0].ident == !"of":
  20. # `head` is expression `typeName* of baseClass`
  21. # echo head.treeRepr
  22. # --------------------
  23. # Infix
  24. # Ident !"*"
  25. # Ident !"Animal"
  26. # Prefix
  27. # Ident !"of"
  28. # Ident !"RootObj"
  29. typeName = head[1]
  30. baseName = head[2][1]
  31. exported = true
  32. else:
  33. quit "Invalid node: " & head.lispRepr
  34. # The following prints out the AST structure:
  35. #
  36. # import macros
  37. # dumptree:
  38. # type X = ref object of Y
  39. # z: int
  40. # --------------------
  41. # StmtList
  42. # TypeSection
  43. # TypeDef
  44. # Ident !"X"
  45. # Empty
  46. # RefTy
  47. # ObjectTy
  48. # Empty
  49. # OfInherit
  50. # Ident !"Y"
  51. # RecList
  52. # IdentDefs
  53. # Ident !"z"
  54. # Ident !"int"
  55. # Empty
  56. # create a type section in the result
  57. result =
  58. if exported:
  59. # mark `typeName` with an asterisk
  60. quote do:
  61. type `typeName`* = ref object of `baseName`
  62. else:
  63. quote do:
  64. type `typeName` = ref object of `baseName`
  65. # echo treeRepr(body)
  66. # --------------------
  67. # StmtList
  68. # VarSection
  69. # IdentDefs
  70. # Ident !"name"
  71. # Ident !"string"
  72. # Empty
  73. # IdentDefs
  74. # Ident !"age"
  75. # Ident !"int"
  76. # Empty
  77. # MethodDef
  78. # Ident !"vocalize"
  79. # Empty
  80. # Empty
  81. # FormalParams
  82. # Ident !"string"
  83. # Empty
  84. # Empty
  85. # StmtList
  86. # StrLit ...
  87. # MethodDef
  88. # Ident !"age_human_yrs"
  89. # Empty
  90. # Empty
  91. # FormalParams
  92. # Ident !"int"
  93. # Empty
  94. # Empty
  95. # StmtList
  96. # DotExpr
  97. # Ident !"this"
  98. # Ident !"age"
  99. # var declarations will be turned into object fields
  100. var recList = newNimNode(nnkRecList)
  101. # expected name of constructor
  102. let ctorName = newIdentNode("new" & $typeName)
  103. # Iterate over the statements, adding `this: T`
  104. # to the parameters of functions, unless the
  105. # function is a constructor
  106. for node in body.children:
  107. case node.kind:
  108. of nnkMethodDef, nnkProcDef:
  109. # check if it is the ctor proc
  110. if node.name.kind != nnkAccQuoted and node.name.basename == ctorName:
  111. # specify the return type of the ctor proc
  112. node.params[0] = typeName
  113. else:
  114. # inject `self: T` into the arguments
  115. node.params.insert(1, newIdentDefs(ident("self"), typeName))
  116. result.add(node)
  117. of nnkVarSection:
  118. # variables get turned into fields of the type.
  119. for n in node.children:
  120. recList.add(n)
  121. else:
  122. result.add(node)
  123. # Inspect the tree structure:
  124. #
  125. # echo result.treeRepr
  126. # --------------------
  127. # StmtList
  128. # TypeSection
  129. # TypeDef
  130. # Ident !"Animal"
  131. # Empty
  132. # RefTy
  133. # ObjectTy
  134. # Empty
  135. # OfInherit
  136. # Ident !"RootObj"
  137. # Empty <= We want to replace this
  138. # MethodDef
  139. # ...
  140. result[0][0][2][0][2] = recList
  141. # Lets inspect the human-readable version of the output
  142. #echo repr(result)
  143. # ---
  144. class Animal of RootObj:
  145. var name: string
  146. var age: int
  147. method vocalize: string {.base.} = "..." # use `base` pragma to annonate base methods
  148. method age_human_yrs: int {.base.} = self.age # `this` is injected
  149. proc `$`: string = "animal:" & self.name & ":" & $self.age
  150. class Dog of Animal:
  151. method vocalize: string = "woof"
  152. method age_human_yrs: int = self.age * 7
  153. proc `$`: string = "dog:" & self.name & ":" & $self.age
  154. class Cat of Animal:
  155. method vocalize: string = "meow"
  156. proc `$`: string = "cat:" & self.name & ":" & $self.age
  157. class Rabbit of Animal:
  158. proc newRabbit(name: string, age: int) = # the constructor doesn't need a return type
  159. result = Rabbit(name: name, age: age)
  160. method vocalize: string = "meep"
  161. proc `$`: string =
  162. self.#[!]#
  163. result = "rabbit:" & self.name & ":" & $self.age
  164. # ---
  165. var animals: seq[Animal] = @[]
  166. animals.add(Dog(name: "Sparky", age: 10))
  167. animals.add(Cat(name: "Mitten", age: 10))
  168. for a in animals:
  169. echo a.vocalize()
  170. echo a.age_human_yrs()
  171. let r = newRabbit("Fluffy", 3)
  172. echo r.vocalize()
  173. echo r.age_human_yrs()
  174. echo r
  175. discard """
  176. disabled:true
  177. $nimsuggest --tester $file
  178. >sug $1
  179. sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;None
  180. sug;;skField;;name;;string;;$file;;166;;6;;"";;100;;None
  181. sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;None
  182. sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100;;None
  183. sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100;;None
  184. sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"";;50;;None*
  185. """