summary refs log tree commit diff stats
path: root/tests/vm/tcomponent.nim
blob: b509bc5d79d676d6e9979392db262ecf1cbfa273 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
discard """
  output: '''`:)` @ 0,0
FOO: blah'''
"""

#
# magic.nim
#

# bug #3729

import macros, sequtils, tables
import strutils
import sugar, meta

type
  Component = object
    fields: FieldSeq
    field_index: seq[string]
    procs: ProcSeq
    procs_index: seq[string]

  Registry = object
    field_index: seq[string]
    procs_index: seq[string]
    components: Table[string, Component]
    builtin: Component

proc newRegistry(): Registry =
  result.field_index = @[]
  result.procs_index = @[]
  result.components = initTable[string, Component]()

var registry {.compileTime.} = newRegistry()

proc validateComponent(r: var Registry, name: string, c: Component) =
  if r.components.hasKey(name):
    let msg = "`component` macro cannot consume duplicated identifier: " & name
    raise newException(ValueError, msg)

  for field_name in c.field_index:
    if r.field_index.contains(field_name):
      let msg = "`component` macro cannot delcare duplicated field: " & field_name
      raise newException(ValueError, msg)
    r.field_index.add(field_name)

  for proc_name in c.procs_index:
    if r.procs_index.contains(proc_name):
      let msg = "`component` macro cannot delcare duplicated proc: " & proc_name
      raise newException(ValueError, msg)
    r.procs_index.add(proc_name)

proc addComponent(r: var Registry, name: string, c: Component) =
  r.validateComponent(name, c)
  r.components.add(name, c)

proc parse_component(body: NimNode): Component =
  result.field_index = @[]
  result.procs_index = @[]
  for node in body:
    case node.kind:
      of nnkVarSection:
        result.fields = newFieldSeq(node)
        for field in result.fields:
          result.field_index.add(field.identifier.name)
      of nnkMethodDef, nnkProcDef:
        let new_proc = meta.newProc(node)
        result.procs = result.procs & @[new_proc]
        for procdef in result.procs:
          result.procs_index.add(procdef.identifier.name)
      else: discard

macro component*(name, body: untyped): typed =
  let component = parse_component(body)
  registry.addComponent($name, component)
  parseStmt("discard")

macro component_builtins(body: untyped): typed =
  let builtin = parse_component(body)
  registry.field_index = builtin.field_index
  registry.procs_index = builtin.procs_index
  registry.builtin = builtin

proc bind_methods*(component: var Component, identifier: Ident): seq[NimNode] =
  result = @[]
  for procdef in component.procs.mitems:
    let this_field = newField(newIdent("this"), identifier)
    procdef.params.insert(this_field, 0)
    result.add(procdef.render())

macro bind_components*(type_name, component_names: untyped): typed =
  result = newStmtList()
  let identifier = newIdent(type_name)
  let components = newBracket(component_names)
  var entity_type = newTypeDef(identifier, true, "object", "RootObj")
  entity_type.fields = registry.builtin.fields
  for component_name, component in registry.components:
    if components.contains(newIdent(component_name)):
      entity_type.fields = entity_type.fields & component.fields
  # TODO why doesn't the following snippet work instead of the one above?
  # for name in components:
  #   echo "Registering $1 to $2" % [name.name, identifier.name]
  #   let component = registry.components[name.name]
  #   entity_type.fields = entity_type.fields & component.fields
  let type_section: TypeDefSeq = @[entity_type]
  result.add type_section.render
  var builtin = registry.builtin
  let builtin_methods = bind_methods(builtin, identifier)
  for builtin_proc in builtin_methods:
    result.add(builtin_proc)
  echo "SIGSEV here"
  for component in registry.components.mvalues():
    for method_proc in bind_methods(component, identifier):
      result.add(method_proc)

component_builtins:
  proc foo(msg: string) =
    echo "FOO: $1" % msg

component position:
  var x*, y*: int

component name:
  var name*: string
  proc render*(x, y: int) = echo "`$1` @ $2,$3" % [this.name, $x, $y]

bind_components(Entity, [position, name])

var e = new(Entity)
e.name = ":)"
e.render(e.x, e.y)
e.foo("blah")