returns the instruction pointer to the bytecode added

def _states_to_dfa_bytecode(self, states, \
      tran=None, \
      debug=False, \
      compiled_states=None, \
      gathered_epsilons=None, \
      cached_transitions=None, \
      cached_tcode=None, \
      reltags_cache=None \
      ):
    '''returns the instruction pointer to the bytecode added'''
    pstates = copy.copy(states)

    if reltags_cache == None:
      reltags_cache = {}

    if cached_tcode == None:
      cached_tcode = {}

    if cached_transitions == None:
      cached_transitions = {}

    if gathered_epsilons == None:
      gathered_epsilons = {}

    self._add_epsilon_states(states, gathered_epsilons)

    if tran != None:
      states = self.nextstates(states, tran)
      self._add_epsilon_states(states, gathered_epsilons)

    if self._magic != None:
      states = states.union(self._magic(states))

    tstates = tuple(states)

    # this is used so we only compile each stateset once
    if compiled_states == None:
      compiled_states = {}

    if tstates in compiled_states:
      return compiled_states[tstates]

    # grab the ip from our codeblock
    ip = self._bytecode.newblock(tstates)
    compiled_states[tstates] = ip

    # TODO
    # epsilon transitions are never 'taken' so we need
    # to insert any ltagv/utagv instructions required
    # for all epsilon transitions
    # gathered_epsilons[state] holds a dictionary of dst: src mappings, so we can use that data

    if self.do_tags:
      tags = set()
      rtags = set()
  
      for ts in pstates:
        for dst in gathered_epsilons[ts]:
          rtags.update(self.reltags(dst, reltags_cache))
          src = gathered_epsilons[ts][dst]
          if self.is_tagged(NFA.EPSILON, src, dst):
            tags.add((self.tag(NFA.EPSILON, src, dst), dst))
  
      self._write_transition_code(tags, rtags, ip)

    # run any defined state hooks
    for s in tstates:
      if s in self._state_hooks:
        ip.append(VM.PyCode(self._state_hooks[s]))

    # do a multi-match for any final states
    finals = self._final_states.intersection(states)
    if len(finals) > 0:
      ip.append(VM.MultiMatch(finals))

    # do any interupts required
    interupts = self._interupt_states.intersection(states)
    if len(interupts) > 0:
      ip.append(VM.MultiInterupt(interupts))

    # consume a character
    ip.append(VM.Consume())

    ts = self.transitions(states, cached_transitions)

    if debug:
      print 'compiling bytecode for stateset:\n\t%s\n\t0x%x: %s' % (states,ip,(defaults,ts))

    def mkbytecode(t):
      return lambda: self._transitions_to_dfa_bytecode(states, t, cached_tcode, debug=debug, compiled_states=compiled_states, gathered_epsilons=gathered_epsilons, cached_transitions=cached_transitions, reltags_cache=reltags_cache)

    # for any of the non-default states add a conditional jmp
    for k in ts:

      if k in (NFA.ANY, NFA.EPSILON):
        continue

      jmppoint = VM.DelayedArg(mkbytecode(k))
      ip.append(VM.Compare(k))
      ip.append(VM.CondJmp(jmppoint))

    # jmp to default state if there is one, otherwise leave
    defaults = self.nextstates(states, NFA.ANY)
    if len(defaults) > 0:
      jmppoint = VM.DelayedArg(mkbytecode(NFA.ANY))
      ip.append(VM.Jmp(jmppoint))
    else:
      ip.append(VM.Leave())

    # return the instruction pointer
    return ip