Nimでclass定義

# はじめに
Nimはサポートは最小限だけどOOP(オブジェクト指向プログラミング)ができる。

# NimでOOPのやり方
以下に良記事があるので参考されたしー。

- [Nimのオブジェクト指向の整理](https://qiita.com/souchan-t/items/778bac39cf8f4c212ed6)
- [Nimのオブジェクト指向の整理その２](https://qiita.com/souchan-t/items/ac974bbbc22348bb5fff)
- [Nim by Example - Object Oriented Programming(英語)](https://nim-by-example.github.io/oop/)

# マクロを使ってclass定義
上のNim by Exampleにマクロを使っていい感じにclass定義できるマクロが載ってる
[Nim by Example - Macros](https://nim-by-example.github.io/macros/)
こんな感じに定義できる。

```nim:クラス定義コード引用
class Animal of RootObj:
  var name: string
  var age: int
  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 = "meow"
```

# イケてるけど
pythonみたいにself使えたり
new+クラス名でコンストラクタを定義できたり
かなりイケてるんだけどちょっと不満点が。

## 継承元を定義する必要がある。
継承元を定義しなかったらRootObjを継承させればいんじゃね？

## exportが不自然
できるけどこんな感じクラス名とアスタリスクの間にスペースがはいるのでちょっと不自然。

```nim
class Animal * of RootObj:
  var name: string
```

もうデフォでexportすればいいんじゃね（過激派）

## オブジェクト以外も定義したい
タプルとかも同じように書いて関数を定義できたらいいなと。
こんな感じでさ。

```nim
class Size of tuple[width, height: int]:
  proc area(): int =
    self.width * self.height
```

## methodとprocしか定義できない
IteratorとかTemplateも定義したいね。

# 作った
個人的に使う分には問題ない。

マクロ

```nim:class.nim
import macros

proc typeName(head: NimNode): NimNode =
  if head.len == 0:head else: head[1]

proc baseName(head: NimNode): NimNode =
  if head.len == 0: newIdentNode("RootObj") else: head[2]

proc isObjectDef(head: NimNode): bool =
  head.len == 0 or head[2].kind == nnkIdent

proc buildObjectTypeDecl(head: NimNode): NimNode =
  template typeDecl(a, b): untyped =
    type a* = ref object of b
  getAst(typeDecl(head.typeName, head.baseName))

proc buildBasicTypeDecl(head: NimNode): NimNode =
  newNimNode(nnkTypeSection)
    .add(newNimNode(nnkTypeDef)
      .add(newIdentNode($head[1]))
      .add(newNimNode(nnkEmpty))
      .add(head[2]))

proc buildTypeDecl(head: NimNode): NimNode =
  if head.isObjectDef:
    head.buildObjectTypeDecl
  else:
    head.buildBasicTypeDecl

macro class*(head, body: untyped): untyped =
  let
    typeName = head.typeName
    ctorName = newIdentNode("new" & $typeName)
    isObjectDef = head.isObjectDef
  var recList = if isObjectDef: newNimNode(nnkRecList) else: nil
  result = newStmtList()
  result.add(head.buildTypeDecl)
  for node in body.children:
    case node.kind:
      of nnkMethodDef, nnkProcDef, nnkIteratorDef, nnkTemplateDef:
        if node.name.kind != nnkAccQuoted and node.name == ctorName:
          node.params[0] = typeName
        else:
          node.params.insert(1, newIdentDefs(ident("self"), typeName))
        result.add(node)
      of nnkVarSection:
        if not isObjectDef:
          error "Invalid node: " & node.lispRepr
        for n in node.children:
          recList.add(n)
      else:
        result.add(node)
  if isObjectDef:
    result[0][0][2][0][2] = recList

when isMainModule:
  class Animal:
    var name: string
    var age: int

    method vocalize: string {.base.} =
      "..."

    method age_human_yrs: int {.base.} =
      self.age

  class Dog of Animal:
    method vocalize: string =
      "woof"

    method age_human_yrs: int =
      self.age * 7

  class Cat of Animal:
    method vocalize: string =
      "meow"

  class Rabbit of Animal:
    proc newRabbit(name: string, age: int) =
      result = Rabbit(name: name, age: age)

    method vocalize: string =
      "meep"

    proc `$`: string =
      "rabbit:" & self.name & ":" & $self.age

  let rabbit = newRabbit("Fluffy", 3)
  var animals: seq[Animal] = @[
    Dog(name: "Sparky", age: 10),
    Cat(name: "Mitten", age: 10),
    rabbit
  ]
  for a in animals:
    echo a.vocalize()
    echo a.age_human_yrs()
  echo rabbit


```

サンプル

```nim:sample.nim
import class
import strformat

class Person:
  var name: string

  proc newPerson(name: string): Person =
    Person(name: name)

  method greed() {.base.} =
    echo fmt"hello my name is {self.name}"

class Size of tuple[width, height: int]:
  proc area(): int =
    self.width * self.height

class FizzBuzz:
  var max: int

  iterator items(): string =
    for n in 1..self.max:
      var str: string
      if n mod 3 == 0: str = "fizz"
      if n mod 5 == 0: str &= "buzz"
      if str.len == 0: str = $n
      yield str

class TemplateSample:
  var name: string

  template nameWith[T](other: T): string =
    self.name & " and " & $other

when isMainModule:
  newPerson("Alice").greed
  echo (width:2, height:3).area
  for s in FizzBuzz(max: 15):
    echo s
  let ts = TemplateSample(name: "Bob")
  echo ts.nameWith(43)
  echo ts.nameWith('a')
  echo ts.nameWith("string")
```

```text:実行結果
hello my name is Alice
6
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
Bob and 43
Bob and a
Bob and string
```

以上〜。