from typing import Sequence


def _group_dimensions(expr: str) -> Sequence[str]:
  """Splits an expression into its separate grouped dimensions.

  An unqualified dimension index is a group by itself.
  Parentheses are interpreted as demarcating a sequence of dimension indices
  to be grouped into a single dimension.
  '1' is an alias for '()', denoting a dimension of size 1 with no indices.
  Nested parentheses are not permitted.

  Examples:
    'ijk' is grouped as ['i', 'j', 'k']
    '(mn)hwc' is grouped as ['mn', 'h', 'w', 'c']
    'n111' is grouped as ['n', '', '', '']
    'n...' is grouped as ['n', '...'], where '...' stands for multiple groups.

  Args:
    expr: Shape expression to group.

  Returns:
    List of simple expressions, each consisting solely of dimension
    indices, specifying the indices that constitute each grouped dimension.
  """
  groups = []
  i = 0
  while i < len(expr):
    if expr[i].isalpha():
      # Top-level dimension index is a group by itself.
      groups.append(expr[i])
      i += 1

    elif expr[i] == '1':
      # Dimension of size 1 with no indices; equivalent to '()'.
      i += 1
      groups.append('')

    elif expr[i] == '(':
      # Sequence of indices to be grouped as a single dimension.
      i += 1
      group_begin = i
      while i < len(expr) and expr[i].isalpha():
        i += 1
      group_end = i
      if not(i < len(expr) and expr[i] == ')'):
        raise ValueError('Unclosed parenthesis')
      i += 1
      groups.append(expr[group_begin:group_end])

    elif expr[i:].startswith('...'):
      # Wildcard sequence of dimensions.
      i += len('...')
      if '...' in groups:
        raise ValueError('Wildcard "..." may only occur once')
      groups.append('...')

    else:
      raise ValueError(f'Illegal character: {ord(expr[i])}')

  return groups