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