docs: def tokenparser(fmt, keys=None, token_cache={}):
      """Divide the format string into tokens and parse them.
  
      Return stretchy token and list of [initialiser, length, value]
      initialiser is one of: hex, oct, bin, uint, int, se, ue, 0x, 0o, 0b etc.
      length is None if not known, as is value.
  
      If the token is in the keyword dictionary (keys) then it counts as a
      special case and isn't messed with.
  
      tokens must be of the form: [factor*][initialiser][:][length][=value]
  
      """
      try:
          return token_cache[(fmt, keys)]
      except KeyError:
          token_key = (fmt, keys)
      # Very inefficient expanding of brackets.
      fmt = expand_brackets(fmt)
      # Split tokens by ',' and remove whitespace
      # The meta_tokens can either be ordinary single tokens or multiple
      # struct-format token strings.
      meta_tokens = (''.join(f.split()) for f in fmt.split(','))
      return_values = []
      stretchy_token = False
      for meta_token in meta_tokens:
          # See if it has a multiplicative factor
          m = MULTIPLICATIVE_RE.match(meta_token)
          if not m:
              factor = 1
          else:
              factor = int(m.group('factor'))
              meta_token = m.group('token')
          # See if it's a struct-like format
          tokens = structparser(meta_token)
          ret_vals = []
          for token in tokens:
              if keys and token in keys:
                  # Don't bother parsing it, it's a keyword argument
                  ret_vals.append([token, None, None])
                  continue
              value = length = None
              if token == '':
                  continue
              # Match literal tokens of the form 0x... 0o... and 0b...
              m = LITERAL_RE.match(token)
              if m:
                  name = m.group('name')
                  value = m.group('value')
                  ret_vals.append([name, length, value])
                  continue
              # Match everything else:
              m1 = TOKEN_RE.match(token)
              if not m1:
                  # and if you don't specify a 'name' then the default is 'uint':
                  m2 = DEFAULT_UINT.match(token)
                  if not m2:
                      raise ValueError("Don't understand token '{0}'.".format(token))
              if m1:
                  name = m1.group('name')
                  length = m1.group('len')
                  if m1.group('value'):
                      value = m1.group('value')
              else:
                  assert m2
                  name = 'uint'
                  length = m2.group('len')
                  if m2.group('value'):
                      value = m2.group('value')
              if name == 'bool':
                  if length is not None:
                      raise ValueError("You can't specify a length with bool tokens - they are always one bit.")
                  length = 1
              if length is None and name not in ('se', 'ue', 'sie', 'uie'):
                  stretchy_token = True
              if length is not None:
                  # Try converting length to int, otherwise check it's a key.
                  try:
                      length = int(length)
                      if length < 0:
                          raise Error
                      # For the 'bytes' token convert length to bits.
                      if name == 'bytes':
                          length *= 8
                  except Error:
                      raise ValueError("Can't read a token with a negative length.")
                  except ValueError:
                      if not keys or length not in keys:
                          raise ValueError("Don't understand length '{0}' of token.".format(length))
              ret_vals.append([name, length, value])
          # This multiplies by the multiplicative factor, but this means that
          # we can't allow keyword values as multipliers (e.g. n*uint:8).
          # The only way to do this would be to return the factor in some fashion
          # (we can't use the key's value here as it would mean that we couldn't
          # sensibly continue to cache the function's results. (TODO).
          return_values.extend(ret_vals * factor)
      return_values = [tuple(x) for x in return_values]
      if len(token_cache) < CACHE_SIZE:
          token_cache[token_key] = stretchy_token, return_values
      return stretchy_token, return_values