Ah, I misunderstood what you were trying to do. @Hlaaftana is correct, in order 
for type safety to remain, Nim needs to know what types you want to include in 
the event registry at compile time. I took a very brief stab at a rough version 
using macros (that can certainly be improved), and here's what I came up with:
    
    
    import tables, strutils
    import macros
    
    
    macro registerEvents(body: untyped): untyped =
      
      template createTriggerProc() =
        proc disconnect(reg: Registry, name: string) =
          for evObj in reg[].fields:
            evObj.connections.del name
      
      template createEventProcs(fieldName, tyName) =
        proc connect(reg: Registry, callback: proc (arg: tyName), name: string) 
=
          reg.fieldName.connections[name] = callback
        
        proc trigger(reg: Registry, val: tyName) =
          for connection in reg.fieldName.connections.values:
            connection(val)
      
      result = newNimNode(nnkStmtList)
      var regRecList = nnkRecList.newNimNode
      var registryTypeDef = nnkTypeDef.newTree(
          ident("Registry"),
          newEmptyNode(),
          nnkRefTy.newTree(
            nnkObjectTy.newTree(
              newEmptyNode(),
              newEmptyNode(),
              regRecList
            )
          )
        )
      
      var typeSection = nnkTypeSection.newNimNode
      result.add typeSection
      
      for node in body:
        let signalName = ident($node.toStrLit & "Signal")
        let tyName = ident($node.toStrLit)
        let lowerTyName = ident(($node.toStrLit).toLowerAscii)
        
        result.add getAst(createEventProcs(lowerTyName, tyName))
        
        regRecList.add nnkIdentDefs.newTree(
          lowerTyName,
          signalName,
          newEmptyNode()
        )
        
        let procTy = nnkProcTy.newTree(
          nnkFormalParams.newTree(
            newEmptyNode(),
            nnkIdentDefs.newTree(
              ident("arg"),
              tyName,
              newEmptyNode()
            )
          ),
          newEmptyNode()
        )
        
        typeSection.add nnkTypeDef.newTree(
          signalName,
          newEmptyNode(),
            nnkObjectTy.newTree(
              newEmptyNode(),
              newEmptyNode(),
              nnkRecList.newTree(
                nnkIdentDefs.newTree(
                  ident("connections"),
                  nnkBracketExpr.newTree(
                    ident("Table"),
                    ident("string"),
                    procTy
                  ),
                  newEmptyNode()
                )
              )
            )
        )
      
      typeSection.add registryTypeDef
      
      result.add getAst(createTriggerProc())
    
    
    type
      MyEvent = object
        hihi: string
        hoho: int
      SomeOtherEvent = object
        ents: seq[int]
    
    # This is all you need to do for each type you want to be in the registry, 
but can only be called once
    # Can be improved to take a name for the Registry type
    registerEvents:
      MyEvent
      # MyEvent2
    
    # These macros currently need to be defined where ever you call 
`registerEvents`, otherwise you
    # must specify the names of the events manually
    macro connect(reg: Registry, callback: untyped): untyped =
      let name = callback.toStrLit
      let regName = reg.toStrLit
      
      template doConnect(reg, regName, callback, name) =
        when compiles(connect(reg, callback, name)):
          connect(reg, callback, name)
        else:
          {.error: "'" & name & "' event not found in registry '" & regName & 
"'".}
      return getAst(doConnect(reg, regName, callback, name))
    
    macro disconnect(reg: Registry, callback: untyped): untyped =
      let name = callback.toStrLit
      template doDisconnect(reg, name) =
        disconnect(reg, name)
      return getAst(doDisconnect(reg, name))
    
    
    
    var reg = Registry()
    
    var obj = "From bound obj: asdads" # A bound obj
    
    proc cbMyEvent(ev: MyEvent)  =
      echo "my event was triggered", ev.hihi, ev.hoho
      echo obj
    
    proc cbMyOtherEvent(ev: SomeOtherEvent)  =
      echo "my event was triggered", ev.ents
      echo obj
    
    proc cbMyEvent2(ev: MyEvent) =
      echo "my event was triggered TWO", ev.hihi, ev.hoho
      echo obj
    
    reg.connect(cbMyEvent) # these are good
    reg.connect(cbMyEvent2) # these are good
    reg.disconnect(cbMyEvent2) # these are good
    
    reg.connect(cbMyOtherEvent) # this now fails at compile time with "Error: 
'cbMyOtherEvent' event not found in registry 'reg'"
    
    var myev = MyEvent()
    myev.hihi = "hihi"
    myev.hoho = 1337
    trigger(reg, myev)
    
    myev.hihi = "HAHAHAH" # change the event a little
    reg.trigger(myev)
    
    var sev = SomeOtherEvent()
    sev.ents = @[1,2,3]
    trigger(reg, sev) # Breaks at compile time
    
    
    Run

The `registerEvents` macro invocation above creates this equivalent code (which 
can be printed using `echo result.toStrLit`):
    
    
    type
      MyEventSignal = object
        connections: Table[string, proc (arg: MyEvent)]
      
      Registry = ref object
        myevent: MyEventSignal
    
    proc connect(reg: Registry; callback: proc (arg: MyEvent); name: string) =
      reg.myevent.connections[name] = callback
    
    proc trigger(reg: Registry; val: MyEvent) =
      for connection in reg.myevent.connections.values:
        connection(val)
    
    proc disconnect(reg: Registry; name: string) =
      for evObj in reg[].fields:
        evObj.connections.del name
    
    
    Run

Reply via email to