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")
|