text: # General notes:
  # * Real keys are actual keys in the system
  # * Pseudo keys are groups of keys, any real key is also a valid pseudo key
  # * By default, "key" refers to (to make usage easier & shorter):
  #    * in input position: A pseudo key
  #    * in output position: A real key
  
  import sys
  from typing import *
  from enum import Enum
  from itertools import chain
  
  
  __all__ = [
      "System",
      "Chord",
      "to_multi_chord", "to_plover_str",
      "add", "sub", "prefix", "suffix", "surround", "concat",
      "reverse_concat",
      "Dictionary",
      "EnglishSystem", "EnglishChord", "system",
      "digit_keys", "digits",
      "single_digit_only", "double_digit_only", "hundred00",
      "double_digit_only_hundred00", "hundred1z",
      "double_digit_only_hundred1z", "single_and_double_digit",
      "single_and_double_digit_hundred00",
      "single_and_double_digit_hundred1z", "hundreds", "numbers",
      "fingertyping_lowercase_no_asterisk",
      "fingertyping_uppercase_no_p_no_asterisk",
      "fingertyping_uppercase_p_no_asterisk",
      "fingertyping_no_asterisk", "fingertyping_lowercase",
      "fingertyping_uppercase_no_p", "fingertyping_uppercase_p",
      "fingertyping",
  ]
  
  
  write_warnings: bool = True  # Set this to false to disable warnings on stderr
  
  # Set this to false to disable the steno order warning for pseudo keys:
  warn_steno_order: bool = True
  warn_matching_overlay_keys: bool = True
  warn_overlapping_dict_entries: bool = True
  warn_missing_keys_in_dict_sub: bool = True
  
  
  def warn(s: str) -> None:
      if write_warnings:
          print("WARNING: " + s, file=sys.stderr)
  
  
  T = TypeVar("T")
  
  
  def unwrap_optional_or(a: Optional[T], b: T) -> T:
      return a if a is not None else b
  
  
  def sliding_window(
      iterable: Iterable[T],
      *,
      size: int
  ) -> Generator[Tuple[T, ...], None, None]:
      out: Tuple[T, ...] = ()
      for i in iterable:
          out += (i,)
          if len(out) == size:
              yield out
              out = out[1:]
  
  
  # `int` for now since the normal `typing` module doesn't provide a type for
  # something that can be compared
  def is_sorted(iterable: Iterable[int]) -> bool:
      return all(a <= b for a, b in sliding_window(iterable, size=2))
  
  
  class LayoutBox(NamedTuple):
      key: str
      x: int
      y: int
      w: int = 1
      h: int = 1
  
  
  class ConstantKey(Enum):
      UNUSED = 0
      PRESSED = 1
  
  
  SystemT = TypeVar("SystemT", bound='System')
  
  
  class System(object):
      sys_name: ClassVar[Optional[str]] = None
      sys_key_order: ClassVar[List[str]]
      sys_unordered_keys: ClassVar[List[str]] = []
      sys_combining_keys: ClassVar[List[str]] = []
      sys_optional_replacements: ClassVar[Dict[str, List[str]]] = {}
      sys_overlay_replacements: ClassVar[Dict[str, List[str]]] = {}
      sys_mandatory_replacements: ClassVar[Dict[str, List[str]]] = {}
      sys_layout: ClassVar[Optional[List[LayoutBox]]] = None
      sys_layout_unused: ClassVar[List[str]] = []
      sys_parse_mandatory_replacements: ClassVar[bool] = True
      sys_always_pressed: ClassVar[List[str]] = []
  
      name: Optional[str]
  
      key_order: List[str]  # Includes a hyphen!
      all_keys: List[str]
  
      left_keys: List[str]
      left_middle_keys: List[str]
      middle_keys: List[str]
      right_middle_keys: List[str]
      right_keys: List[str]
  
      unordered_keys: List[str]
      combining_keys: List[str]
  
      layout: Optional[List[LayoutBox]]
      layout_unused: List[str]
  
      parse_mandatory_replacements: bool
  
      always_pressed: List[str]
  
      # Optional replacements are only parsed, not emitted in outputs. This is
      # to allow users to use "pseudo steno" to write out their chords.
      optional_replacements: Dict[str, List[str]]
      # Overlay replacements are parsed and emitted in suggestion outputs & debug
      # info. These are meant for systems that are only mapped onto other systems.
      overlay_replacements: Dict[str, List[str]]
      # Mandatory replacements are not only parsed, but also emitted in outputs.
      # Their order is depending on that of their first ordered key.
      # Not that hyphen emitting is decided based on the base keys; as such this
      # can act as a middle key when emitted if it contains a middle key, but it
      # *cannot* act as such by only having keys on both sides.
      mandatory_replacements: Dict[str, List[str]]
  
      # Only contains parsed replacements
      combined_replacements: Dict[str, List[str]]
  
      left_pseudo_keys: List[str]
      left_middle_pseudo_keys: List[str]
      middle_pseudo_keys: List[str]
      right_middle_pseudo_keys: List[str]
      right_pseudo_keys: List[str]
      pseudo_key_order: List[str]
  
      _empty_chord: 'Chord[Any]'
  
      def __init__(self,
          key_order: Optional[List[str]]=None,
          *,
          name: Optional[str]=None,
          unordered_keys: Optional[List[str]]=None,
          combining_keys: Optional[List[str]]=None,
          optional_replacements: Optional[Dict[str, List[str]]]=None,
          overlay_replacements: Optional[Dict[str, List[str]]]=None,
          mandatory_replacements: Optional[Dict[str, List[str]]]=None,
          layout: Optional[List[LayoutBox]]=None,
          layout_unused: Optional[List[str]]=None,
          parse_mandatory_replacements: Optional[bool]=None,
          always_pressed: Optional[List[str]]=None,
      ) -> None:
          self.name = unwrap_optional_or(name, self.sys_name)
          self.key_order = unwrap_optional_or(key_order, self.sys_key_order)
          self.all_keys = [i for i in self.key_order if i != "-"]
          self.unordered_keys = (
              unwrap_optional_or(unordered_keys, self.sys_unordered_keys)
          )
          self.combining_keys = (
              unwrap_optional_or(combining_keys, self.sys_combining_keys)
          )
          self.optional_replacements = unwrap_optional_or(
              optional_replacements,
              self.sys_optional_replacements
          )
          self.overlay_replacements = unwrap_optional_or(
              overlay_replacements,
              self.sys_overlay_replacements
          )
          self.mandatory_replacements = unwrap_optional_or(
              mandatory_replacements,
              self.sys_mandatory_replacements
          )
          self.layout = unwrap_optional_or(layout, self.sys_layout)
          self.layout_unused = (
              unwrap_optional_or(layout_unused, self.sys_layout_unused)
          )
          self.parse_mandatory_replacements = unwrap_optional_or(
              parse_mandatory_replacements,
              self.sys_parse_mandatory_replacements
          )
          self.always_pressed = (
              unwrap_optional_or(always_pressed, self.sys_always_pressed)
          )
          self.combining_keys += [
              i
              for i in self.always_pressed
              if i not in self.combining_keys
          ]
  
          all_replacement_dicts = (
              self.optional_replacements,
              self.overlay_replacements,
              self.mandatory_replacements
          )
          self.combined_replacements = {}
          for i in all_replacement_dicts:
              for k, v in i.items():
                  if k not in self.combined_replacements:
                      self.assert_real_keys_ordered(*v)
                      self.combined_replacements[k] = v
                  else:
                      if (
                          self.parse_mandatory_replacements and
                          i is not self.mandatory_replacements
                      ):
                          raise ValueError(
                              f"The replacement keys defined in " +
                              f"{self.optional_replacements!r}, " +
                              f"{self.overlay_replacements!r} and " +
                              f"{self.mandatory_replacements!r} overlap"
                          )
  
          self.left_keys = [i for i in self.all_keys if i.endswith("-")]
          self.right_keys = [i for i in self.all_keys if i.startswith("-")]
          middle_keys_and_hyphen = [
              i
              for i in self.key_order
              if ("-" not in i or i == "-") and i not in self.unordered_keys
          ]
          hyphen_idx = middle_keys_and_hyphen.index("-")
          self.left_middle_keys = middle_keys_and_hyphen[:hyphen_idx]
          self.right_middle_keys = middle_keys_and_hyphen[hyphen_idx + 1:]
          self.middle_keys = self.left_middle_keys + self.right_middle_keys
  
          ordered_replacements = [
              i
              for i in self.combined_replacements.keys()
              if not self.key_unordered(i)
          ]
  
          def pseudo_keys_where(
              base: List[str],
              predicate: Callable[[str], bool]
          ) -> List[str]:
              out = base + [i for i in ordered_replacements if predicate(i)]
              out.sort(key=self.key_index)
              return out
  
          self.left_pseudo_keys = pseudo_keys_where(
              self.left_keys,
              lambda i: i.endswith("-")
          )
          self.left_middle_pseudo_keys = pseudo_keys_where(
              self.left_middle_keys,
              lambda i:
                  "-" not in i and any(j in self.left_middle_keys for j in i)
          )
          self.right_middle_pseudo_keys = pseudo_keys_where(
              self.right_middle_keys,
              lambda i:
                  "-" not in i and not any(j in self.left_middle_keys for j in i)
          )
          self.middle_pseudo_keys = (
              self.left_middle_pseudo_keys + self.right_middle_pseudo_keys
          )
          self.right_pseudo_keys = pseudo_keys_where(
              self.right_keys,
              lambda i: i.startswith("-")
          )
          self.pseudo_key_order = (
              self.left_pseudo_keys + self.middle_pseudo_keys +
              self.right_pseudo_keys
          )
  
          self._empty_chord = self.chord()
  
      def assert_real_key(self, *keys: str) -> None:
          for key in keys:
              if key not in self.all_keys:
                  raise ValueError(f"{key!r} is not a key in {self!r}")
  
      def real_key_index(self, key: str) -> int:
          self.assert_real_key(key)
          return self.key_order.index(key)
  
      def real_keys_ordered(
          self,
          *keys: str,
          ignore_unordered: bool=False
      ) -> bool:
          """
          Returns `True` if the arguments are all in steno order.
          Being the same key counts as being in order.
          """
          self.assert_real_key(*keys)
          if not ignore_unordered:
              keys = tuple(i for i in keys if i not in self.unordered_keys)
          return is_sorted(map(self.real_key_index, keys))
  
      def assert_real_keys_ordered(
          self,
          *keys: str,
          ignore_unordered: bool=False
      ) -> None:
          if not self.real_keys_ordered(*keys, ignore_unordered=ignore_unordered):
              raise ValueError(
                  f"{', '.join(repr(i) for i in keys)} are not in steno order " +
                  f"in {self!r}"
              )
  
      def assert_key(self, *keys: str) -> None:
          for key in keys:
              is_real_key = key in self.all_keys 
              is_pseudo_key = key in self.combined_replacements
              if not is_real_key and not is_pseudo_key:
                  raise ValueError(
                      f"{key!r} is not a valid pseudo key in {self!r}"
                  )
  
      def expand_key(self, key: str) -> List[str]:
          self.assert_key(key)
          if key in self.all_keys:
              return [key]
          else:
              return self.combined_replacements[key]
  
      def key_unordered(self, key: str) -> bool:
          return all(
              i in self.unordered_keys
              for i in self.expand_key(key)
          )
  
      def key_index(self, key: str) -> int:
          expanded = self.expand_key(key)
          for i in expanded:
              if i not in self.unordered_keys:
                  return self.real_key_index(i)
          return self.real_key_index(expanded[0])
  
      def keys_ordered(self, *keys: str, ignore_unordered: bool=False) -> bool:
          self.assert_key(*keys)
          if not ignore_unordered:
              keys = tuple(i for i in keys if not self.key_unordered(i))
          return is_sorted(map(self.key_index, keys))
  
      def chord_of_real_keys(
          self: SystemT,
          keys: Iterable[str],
          include_always_pressed: bool=True,
      ) -> 'Chord[SystemT]':
          return Chord(
              self,
              chain(keys, self.always_pressed if include_always_pressed else [])
          )
  
      def chord(
          self: SystemT,
          *keys: str,
          include_always_pressed: bool=True
      ) -> 'Chord[SystemT]':
          return self.chord_of_real_keys(
              chain.from_iterable(self.expand_key(i) for i in keys),
              include_always_pressed=include_always_pressed,
          )
  
      # This is only a property to make it properly typeable :)
      @property
      def empty_chord(self: SystemT) -> 'Chord[SystemT]':
          return self._empty_chord
  
      def single_real_key(
          self: SystemT,
          key: str,
          *,
          include_always_pressed: bool=True,
      ) -> 'Chord[SystemT]':
          return self.chord_of_real_keys(
              [key],
              include_always_pressed=include_always_pressed
          )
  
      def single_key(
          self: SystemT,
          key: str,
          *,
          include_always_pressed: bool=True,
      ) -> 'Chord[SystemT]':
          return self.chord(
              *self.expand_key(key),
              include_always_pressed=include_always_pressed
          )
  
      def parse(
          self: SystemT,
          chord: str,
          *,
          force_parse_mandatory_replacements: bool=False,
          include_always_pressed: bool=True,
      ) -> 'Chord[SystemT]':
          left = chord
          out: List[str] = []
  
          def try_consume_one(pseudo_key: str) -> bool:
              nonlocal left
              nonlocal out
  
              is_real_key = pseudo_key in self.all_keys
              is_allowed_pseudo = (
                  pseudo_key in self.optional_replacements or
                  pseudo_key in self.overlay_replacements or
                  self.parse_mandatory_replacements or
                  force_parse_mandatory_replacements
              )
              if not is_real_key and not is_allowed_pseudo:
                  return False
  
              without_hyphen = pseudo_key.replace("-", "")
              if left.startswith(without_hyphen):
                  left = left[len(without_hyphen):]
                  out += self.expand_key(pseudo_key)
                  return True
              else:
                  return False
  
          all_unordered = (
              self.unordered_keys +
              [
                  i
                  for i in self.combined_replacements.keys()
                  if self.key_unordered(i)
              ]
          )
  
          def try_consume_unordered() -> None:
              while True:
                  done = True
                  for i in all_unordered:
                      if try_consume_one(i):
                          done = False
                  if done:
                      return
  
          def try_consume_all(keys: List[str]) -> None:
              for i in keys:
                  try_consume_unordered()
                  try_consume_one(i)
  
          try_consume_all(self.left_pseudo_keys + self.left_middle_pseudo_keys)
          try_consume_unordered()
  
          if left.startswith("-"):
              left = left[len("-"):]
  
          try_consume_all(self.right_middle_pseudo_keys + self.right_pseudo_keys)
          try_consume_unordered()
  
          as_set = frozenset(out)
  
          # If this is `True`, the chord wasn't valid. In this case `left` could
          # already be empty though; for example `F` would be parsed to `-F`,
          # even though it's not valid due to the missing hyphen - this is what
          # we detect here.
          hyphen_maybe_missing = (
              "-" not in chord and  # there is no hyphen
              not as_set & set(self.middle_keys) and  # there are no middle keys
              as_set & set(self.right_keys)  # but there *are* right keys
          )
  
          if hyphen_maybe_missing or left != "":
              info = (
                  " (it may be missing a hyphen)" if hyphen_maybe_missing else ""
              )
              raise ValueError(
                  f"The chord {chord!r} is not valid{info} in {self!r}"
              )
  
          if warn_steno_order and not self.real_keys_ordered(*out):
              warn(
                  f"Some pseudo keys might not be in steno order in {chord!r} " +
                  f"in {self!r}"
              )
  
          return self.chord_of_real_keys(
              out,
              include_always_pressed=include_always_pressed
          )
      
      def parse_many(
          self: SystemT,
          sequence: str,
          *,
          force_parse_mandatory_replacements: bool=False,
          include_always_pressed: bool=True,
      ) -> Tuple['Chord[SystemT]', ...]:
          return tuple(
              self.parse(
                  i,
                  force_parse_mandatory_replacements=
                      force_parse_mandatory_replacements,
                  include_always_pressed=include_always_pressed,
              )
              for i in sequence.split("/")
          )
  
      def parsed_single_dict(
          self: SystemT,
          dict_: Dict[str, T],
          *,
          force_parse_mandatory_replacements: bool=False,
          include_always_pressed: bool=True,
      ) -> 'Dictionary[Chord[SystemT], T]':
          return Dictionary(
              (
                  self.parse(
                      k,
                      force_parse_mandatory_replacements=
                          force_parse_mandatory_replacements,
                      include_always_pressed=include_always_pressed
                  ),
                  v
              )
              for k, v in dict_.items()
          )
  
      def parsed_seq_dict(
          self: SystemT,
          dict_: Dict[str, T],
          *,
          force_parse_mandatory_replacements: bool=False,
          include_always_pressed: bool=True,
      ) -> 'Dictionary[Tuple[Chord[SystemT], ...], T]':
          return Dictionary(
              (
                  self.parse_many(
                      k,
                      force_parse_mandatory_replacements=
                          force_parse_mandatory_replacements,
                      include_always_pressed=include_always_pressed
                  ),
                  v
              )
              for k, v in dict_.items()
          )
  
      @overload
      def toggle(
          self: SystemT,
          key: Union[str, 'Chord[SystemT]'],
          value: T,
          *,
          default: T
      ) -> 'Dictionary[Chord[SystemT], T]': ...
      @overload
      def toggle(
          self: SystemT,
          key: Union[str, 'Chord[SystemT]'],
          value: str,
          *,
          default: str=""
      ) -> 'Dictionary[Chord[SystemT], str]': ...
      def toggle(
          self: SystemT,
          key: Union[str, 'Chord[SystemT]'],
          value: T,
          *,
          default: Any=""
      ) -> 'Dictionary[Chord[SystemT], T]':
          as_chord = (
              key if isinstance(key, Chord) else self.parse(key)
          )
          return Dictionary({
              as_chord: value,
              self.empty_chord: default,
          })
  
      def render_layout(self) -> str:
          if self.layout is None:
              raise ValueError("The system {self!r} doesn't have a visual layout")
  
          def label(box: LayoutBox, constant: Callable[[ConstantKey], T]) -> Union[str, T]:
              if box.key in self.layout_unused:
                  return constant(ConstantKey.UNUSED)
  
              if box.key in self.always_pressed:
                  return constant(ConstantKey.PRESSED)
  
              matching_overlay_keys = [
                  k
                  for k, v in self.overlay_replacements.items()
                  if v == [box.key]
              ]
  
              if warn_matching_overlay_keys and len(matching_overlay_keys) > 2:
                  warn(
                      f"There are multiple overlay keys matching {i.key!r}: " +
                      f"{matching_overlay_keys!r}"
                  )
  
              if matching_overlay_keys:
                  return matching_overlay_keys[0]
              else:
                  return box.key
  
          column_widths = [
              max(
                  (
                      # there are (i.w - 1) lines that aren't used
                      # (x + y - 1) // y is the same as floor(x / y)
                      (len(label(i, lambda _: " ")) - (i.w - 1) + i.w - 1) // i.w
                      for i in self.layout if i.x <= x < i.x + i.w
                  ),
                  default=0
              )
              for x in range(max(i.x + i.w for i in self.layout))
          ]
  
          out = [
              # |x|yy| → contents + all lines to the right of a cell + left line
              [" "] * (sum(column_widths) + len(column_widths) + 1)
              # contents are only one high
              for y in range(max(i.y + i.h for i in self.layout) * 2 + 1)
          ]
  
          def combine_box_drawing(x: int, y: int, value: str) -> None:
                  # (up, right, down, left)
                  categorization = {
                      " ": (False, False, False, False),
                      "╴": (False, False, False, True),
                      "╷": (False, False, True, False),
                      "┐": (False, False, True, True),
                      "╶": (False, True, False, False),
                      "─": (False, True, False, True),
                      "┌": (False, True, True, False),
                      "┬": (False, True, True, True),
                      "╵": (True, False, False, False),
                      "┘": (True, False, False, True),
                      "│": (True, False, True, False),
                      "┤": (True, False, True, True),
                      "└": (True, True, False, False),
                      "┴": (True, True, False, True),
                      "├": (True, True, True, False),
                      "┼": (True, True, True, True),
                  }
                  reverse_categorization = {
                      v: k
                      for k, v in categorization.items()
                  }
                  
                  out[y][x] = reverse_categorization[
                      cast(Tuple[bool, bool, bool, bool], tuple(
                          l or r
                          for l, r in zip(
                              categorization[out[y][x]],
                              categorization[value]
                          )
                      ))
                  ]
  
          for i in self.layout:
              # |x|THIS CELL → contents + all lines to the left of a cell
              x_offset = sum(column_widths[:i.x]) + i.x
              # Includes (i.w - 1) lines that aren't used
              width = sum(column_widths[i.x:i.x+i.w]) + i.w - 1
              x_end = x_offset + width + 1
              y_offset = i.y * 2
              y_end = y_offset + i.h * 2
  
              # Corners
              combine_box_drawing(x_offset, y_offset, "┌")
              combine_box_drawing(x_offset, y_end, "└")
              combine_box_drawing(x_end, y_offset, "┐")
              combine_box_drawing(x_end, y_end, "┘")
  
              # Horizontal lines
              for line_y in [y_offset, y_end]:
                  for line_x in range(x_offset + 1, x_end):
                      combine_box_drawing(line_x, line_y, "─")
              # Vertical lines
              for line_x in [x_offset, x_end]:
                  for line_y in range(y_offset + 1, y_end):
                      combine_box_drawing(line_x, line_y, "│")
  
              text = label(i, lambda x: x)
              if isinstance(text, ConstantKey):
                  fill = "▓" if text == ConstantKey.PRESSED else "░"
                  for x in range(x_offset + 1, x_end):
                      for y in range(y_offset + 1, y_end):
                          out[y][x] = fill
              else:
                  text_y = y_offset + i.h  # (i.h*2 - 1) // 2 → i.h
                  text_x = x_offset + 1 + (width - len(text)) // 2
                  for char_x, c in enumerate(text, text_x):
                      out[text_y][char_x] = c
  
          return "\n".join("".join(i) for i in out)
  
      def __repr__(self) -> str:
          if self.name is not None:
              return f"System(name={self.name!r})"
          else:
              return (
                  f"System({self.key_order!r}, " +
                  f"unordered_keys={self.unordered_keys!r}, " + 
                  f"optional_replacements={self.optional_replacements!r}, " + 
                  f"overlay_replacements={self.overlay_replacements!r}, " + 
                  f"mandatory_replacements={self.mandatory_replacements!r}, " +
                  f"layout={self.layout!r})"
              )
  
      def __eq__(self, other: object) -> bool:
          return NotImplemented
  
  
  class Chord(Generic[SystemT]):
      def __init__(self, system: SystemT, keys: Iterable[str]) -> None:
          self.system: Final[SystemT] = system
          self.keys: Final[FrozenSet[str]] = frozenset(keys)
          self.system.assert_real_key(*self.keys)
  
      @property
      def left_keys(self) -> FrozenSet[str]:
          return self.keys & set(self.system.left_keys)
  
      @property
      def left_middle_keys(self) -> FrozenSet[str]:
          return self.keys & set(self.system.left_middle_keys)
  
      @property
      def middle_keys(self) -> FrozenSet[str]:
          return self.keys & set(self.system.middle_keys)
  
      @property
      def right_middle_keys(self) -> FrozenSet[str]:
          return self.keys & set(self.system.right_middle_keys)
  
      @property
      def right_keys(self) -> FrozenSet[str]:
          return self.keys & set(self.system.right_keys)
  
      def _to_str(self, replacements: Mapping[str, List[str]]={}) -> str:
          matching_replacements = [
              (k, v)
              for k, v in replacements.items()
              if set(v) <= self.keys
          ]
  
          outputs = (
              [k for k, _ in matching_replacements] +
              [
                  i
                  for i in self.keys
                  if not any(i in j for _, j in matching_replacements)
              ]
          )
  
          needs_dash = (
              (self.keys & set(self.system.right_keys)) and
              not (self.keys & set(self.system.middle_keys))
          )
          if needs_dash:
              outputs.append("-")
  
          outputs.sort(
              key=lambda x:
                  self.system.key_index(x)
                  if x != "-"
                  else self.system.key_order.index("-")
          )
  
          return "".join("-" if i == "-" else i.replace("-", "") for i in outputs)
  
      def assert_same_system(self, other: 'Chord[SystemT]') -> None:
          if self.system is not other.system:
              raise ValueError(
                  f"{self!r} and {other!r} are not from the same system"
              )
  
      def to_multi_chord(self) -> Tuple['Chord[SystemT]', ...]:
          return (self,)
  
      def overlaps(self, other: 'Chord[SystemT]') -> bool:
          self.assert_same_system(other)
          return bool(self.keys & other.keys)
  
      def overlaps_noncombining(self, other: 'Chord[SystemT]') -> bool:
          self.assert_same_system(other)
          return bool(self.keys & other.keys - set(self.system.combining_keys))
  
      def assert_no_overlapping_noncombining(
          self,
          other: 'Chord[SystemT]'
      ) -> None:
          if self.overlaps_noncombining(other):
              raise ValueError(f"{self!r} and {other!r} overlap")
  
      def is_superset(self, other: 'Chord[SystemT]') -> bool:
          self.assert_same_system(other)
          return self.keys >= other.keys
  
      def assert_is_superset(self, other: 'Chord[SystemT]') -> None:
          if not self.is_superset(other):
              raise ValueError(f"{self!r} is not a superset of {other!r}")
  
      def mask(self, other: 'Chord[SystemT]') -> 'Chord[SystemT]':
          return self.system.chord_of_real_keys(self.keys & other.keys)
  
      def lax_combine(self, other: 'Chord[SystemT]') -> 'Chord[SystemT]':
          self.assert_same_system(other)
          return self.system.chord_of_real_keys(self.keys | other.keys)
  
      def combine(self, other: 'Chord[SystemT]') -> 'Chord[SystemT]':
          self.assert_no_overlapping_noncombining(other)
          return self.lax_combine(other)
  
      def lax_remove(self, other: 'Chord[SystemT]') -> 'Chord[SystemT]':
          self.assert_same_system(other)
          return self.system.chord_of_real_keys(self.keys - other.keys)
  
      def remove(self, other: 'Chord[SystemT]') -> 'Chord[SystemT]':
          self.assert_is_superset(other)
          return self.lax_remove(other)
  
      @property
      def plover_str(self) -> str:
          return self._to_str(self.system.mandatory_replacements)
  
      @property
      def no_replacements_str(self) -> str:
          return self._to_str()
  
      def __repr__(self) -> str:
          return f"Chord({self.system!r}, {self})"
  
      def __str__(self) -> str:
          return self._to_str(self.system.overlay_replacements)
  
      def __contains__(self, item: Union['Chord[SystemT]', str]) -> bool:
          if isinstance(item, str):
              self.system.assert_key(item)
              return set(self.system.expand_key(item)) <= self.keys
          else:
              return item.keys <= self.keys
  
      def __len__(self) -> int:
          return len(self.keys)
  
      def __add__(self, other: Union['Chord[SystemT]', str]) -> 'Chord[SystemT]':
          if isinstance(other, str):
              return self + self.system.parse(other)
          elif isinstance(other, Chord):
              return self.combine(other)
          else:
              return NotImplemented
  
      def __radd__(self, other: Union['Chord[SystemT]', str]) -> 'Chord[SystemT]':
          return self.__add__(other)
  
      def __sub__(self, other: Union['Chord[SystemT]', str]) -> 'Chord[SystemT]':
          if isinstance(other, str):
              return self - self.system.parse(other)
          elif isinstance(other, Chord):
              return self.remove(other)
          else:
              return NotImplemented
  
      def __hash__(self) -> int:
          return hash((id(self.system), self.keys))
  
      def __eq__(self, other: object) -> bool:
          if isinstance(other, Chord):
              return self.keys == other.keys
          else:
              return NotImplemented
  
      def __ne__(self, other: object) -> bool:
          return not (self == other)
  
  
  def to_multi_chord(
      value: Union[Chord[SystemT], Tuple[Chord[SystemT], ...]]
  ) -> Tuple[Chord[SystemT], ...]:
      return value if isinstance(value, tuple) else (value,)
  
  
  def to_plover_str(
      value: Union[Chord[SystemT], Tuple[Chord[SystemT], ...]]
  ) -> str:
      return "/".join(i.plover_str for i in to_multi_chord(value))
  
  
  def add(
      chord: Union[Chord[SystemT], str]
  ) -> Callable[[Chord[SystemT]], Chord[SystemT]]:
      return lambda x: chord + x
  
  
  def sub(
      chord: Union[Chord[SystemT], str]
  ) -> Callable[[Chord[SystemT]], Chord[SystemT]]:
      return lambda x: x - chord
  
  
  @overload
  def prefix(
      prefix: Chord[SystemT]
  ) -> Callable[[Chord[SystemT]], Chord[SystemT]]: ...
  @overload
  def prefix(prefix: str) -> Callable[[str], str]: ...
  def prefix(prefix: Union[Chord[SystemT], str]) -> Callable[[Any], Any]:
      return lambda r: prefix + r
  
  
  @overload
  def suffix(
      suffix: Chord[SystemT]
  ) -> Callable[[Chord[SystemT]], Chord[SystemT]]: ...
  @overload
  def suffix(suffix: str) -> Callable[[str], str]: ...
  def suffix(suffix: Union[Chord[SystemT], str]) -> Callable[[Any], Any]:
      return lambda l: l + suffix
  
  
  @overload
  def surround(
      prefix: Chord[SystemT],
      suffix: Chord[SystemT]
  ) -> Callable[[Chord[SystemT]], Chord[SystemT]]: ...
  @overload
  def surround(prefix: str, suffix: str) -> Callable[[str], str]: ...
  def surround(
      prefix: Union[Chord[SystemT], str],
      suffix: Union[Chord[SystemT], str]
  ) -> Callable[[Any], Any]:
      return lambda x: prefix + x + suffix
  
  
  @overload
  def concat(
      left: Union[Chord[SystemT], Tuple[Chord[SystemT], ...]],
      right: Union[Chord[SystemT], Tuple[Chord[SystemT], ...]],
      *,
      middle: Optional[Union[Chord[SystemT], Tuple[Chord[SystemT], ...]]]=None,
  ) -> Tuple[Chord[SystemT], ...]: ...
  @overload
  def concat(left: str, right: str, *, middle: Optional[str]=None) -> str: ...
  def concat(
      left: Union[Chord[SystemT], Tuple[Chord[SystemT], ...], str],
      right: Union[Chord[SystemT], Tuple[Chord[SystemT], ...], str],
      *,
      middle: Optional[
          Union[Chord[SystemT], Tuple[Chord[SystemT], ...], str]
      ]=None,
  ) -> Union[Tuple[Chord[SystemT], ...], str]:
      if isinstance(left, str):
          assert isinstance(right, str)
          assert middle is None or isinstance(middle, str)
          return left + (middle if middle is not None else "") + right
      else:
          assert isinstance(left, Chord) or isinstance(left, tuple)
          assert isinstance(right, Chord) or isinstance(right, tuple)
          assert middle is None or isinstance(middle, Chord) or isinstance(middle, tuple)
          if middle is not None:
              return (
                  to_multi_chord(left) +
                  to_multi_chord(middle) +
                  to_multi_chord(right)
              )
          else:
              return to_multi_chord(left) + to_multi_chord(right)
  
  
  def to_tuple(left: T, right: T) -> Tuple[T, T]:
      return (left, right)
  
  
  @overload
  def reverse_concat(
      right: Union[Chord[SystemT], Tuple[Chord[SystemT], ...]],
      left: Union[Chord[SystemT], Tuple[Chord[SystemT], ...]],
      *,
      middle: Optional[Union[Chord[SystemT], Tuple[Chord[SystemT], ...]]]=None,
  ) -> Tuple[Chord[SystemT], ...]: ...
  @overload
  def reverse_concat(right: str, left: str, *, middle: Optional[str]=None) -> str:
      ...
  def reverse_concat(right: Any, left: Any, *, middle: Optional[Any]=None) -> Any:
      return concat(left, right, middle=middle)
  
  
  def _is_chord_in_single_element_tuple(key: object) -> bool:
      return (
          isinstance(key, tuple) and len(key) == 1 and
          isinstance(key[0], Chord)
      )
  
  
  K = TypeVar("K")
  V = TypeVar("V")
  K2 = TypeVar("K2")
  V2 = TypeVar("V2")
  K3 = TypeVar("K3")
  V3 = TypeVar("V3")
  
  
  class Dictionary(Generic[K, V]):
      def __init__(
          self,
          iterable_or_dict: Union[Iterable[Tuple[K, V]], Dict[K, V]]
      ) -> None:
          self.dict: Dict[K, V]
  
          if isinstance(iterable_or_dict, dict):
              self.dict = iterable_or_dict
          else:
              self.dict = {}
              for k, v in iterable_or_dict:
                  if warn_overlapping_dict_entries:
                      if k in self.dict and self.dict[k] != v:
                          warn(
                              f"The key {k!r} is present more than once while " +
                              f"creating a dictionary, once with the value " +
                              f"{self.dict[k]!r} and once with {v!r}."
                          )
  
                  self.dict[k] = v
  
      def copy(self) -> 'Dictionary[K, V]':
          return Dictionary(self.dict.items())
  
      def keys(self) -> Iterable[K]:
          return self.dict.keys()
  
      def values(self) -> Iterable[V]:
          return self.dict.values()
  
      def keys_for(self, value: V) -> List[K]:
          return [k for k, v in self.dict.items() if v == value]
  
      def inferred_system(
          self: Union[
              'Dictionary[Chord[SystemT], V]',
              'Dictionary[Tuple[Chord[SystemT], ...], V]'
          ]
      ) -> SystemT:
          """
          Limitation: the dictionary cannot be empty, otherwise
          the system cannot be inferred
          """
          try:
              return next(
                  i[0].system if isinstance(i, tuple) else i.system
                  for i in self.keys()
                  if i != ()
              )
          except StopIteration:
              raise ValueError(
                  "Cannot infer the system of an empty dictionary"
              )
  
      @overload
      def with_empty_chord(
          self: 'Dictionary[Chord[SystemT], V]',
          default: V,
          *,
          system: Optional[SystemT]=None,
      ) -> 'Dictionary[Chord[SystemT], V]': ...
      @overload
      def with_empty_chord(
          self: 'Dictionary[Chord[SystemT], str]',
          default: str="",
          *,
          system: Optional[SystemT]=None,
      ) -> 'Dictionary[Chord[SystemT], V]': ...
      def with_empty_chord(
          self: 'Dictionary[Chord[SystemT], V]',
          default: Any="",
          *,
          system: Optional[SystemT]=None,
      ) -> 'Dictionary[Chord[SystemT], V]':
          """
          Do note the limitations of `inferred_system`
          """
          inferred_system = self.inferred_system() if system is None else system
          return self + Dictionary({
              inferred_system.empty_chord: default
          })
  
      # Suggested key maps:
      # * `Chord.to_multi_chord` / `to_multi_chord`
      # * `chord.combine` / `add(chord)` / `add(key)`
      # * `sub(chord)` (removes `chord` from all entries) / `sub(key)`
      # * `prefix(chords)`
      # * `suffix(chords)`
      # * `surround(prefix_chords, suffix_chords)`
      # Suggested value maps:
      # * `prefix(s)`
      # * `suffix(s)`
      # * `surround(prefix_s, suffix_s)`
      @overload
      def map(self) -> 'Dictionary[K, V]': ...
      @overload
      def map(self, *, keys: Callable[[K], K2]) -> 'Dictionary[K2, V]': ...
      @overload
      def map(self, *, values: Callable[[V], V2]) -> 'Dictionary[K, V2]': ...
      @overload
      def map(
          self,
          *,
          keys: Callable[[K], K2],
          values: Callable[[V], V2]
      ) -> 'Dictionary[K2, V2]': ...
      def map(
          self,
          *,
          keys: Callable[[K], Any]=lambda x: x,
          values: Callable[[V], Any]=lambda x: x
      ) -> 'Dictionary[K2, V2]':
          typed_keys: Callable[[K], K2] = keys
          typed_values: Callable[[V], V2] = values
          return Dictionary((typed_keys(k), typed_values(v)) for k, v in self)
  
      def to_multi_chords(
          self: 'Dictionary[Chord[SystemT], V]'
      ) -> 'Dictionary[Tuple[Chord[SystemT], ...], V]':
          return self.map(keys=Chord.to_multi_chord)
  
      # Suggested key combiners:
      # * `Chord.combine` (default) for single chords
      # * `concat` for chord tuples
      #   (treats chords as chord tuples with one element)
      # * `to_tuple`
      # Suggested value combiners:
      # * `concat` (default)
      # * `to_tuple`
      @overload
      def combinations(
          self,
          other: 'Dictionary[K2, V2]',
          *,
          keys: Callable[[K, K2], K3],
          values: Callable[[V, V2], V3],
      ) -> 'Dictionary[K3, V3]': ...
      @overload
      def combinations(
          self: 'Dictionary[Chord[SystemT], V]',
          other: 'Dictionary[Chord[SystemT], V2]',
          *,
          # keys = Chord.combine
          values: Callable[[V, V2], V3],
      ) -> 'Dictionary[Chord[SystemT], V3]': ...
      # We *could* also allow chords and the like on the right side by default
      # here, but that is seldom used, and as such I think requiring the user to
      # explicitly use `keys=concat` is not a big deal, *especially* since this
      # would otherwise require 2 (any `K`, `Chord[...]` as `K`) * 3 (`Chord[...]`
      # as `V`, `Tuple[Chord[...], ...]` as `V`,
      # `Union[Chord[...], Tuple[Chord[...], ...]]` as `V`) = 6 overloads
      @overload
      def combinations(
          self: 'Dictionary[K, str]',
          other: 'Dictionary[K2, str]',
          *,
          keys: Callable[[K, K2], K3],
          # values = concat
      ) -> 'Dictionary[K3, str]': ...
      @overload
      def combinations(
          self: 'Dictionary[Chord[SystemT], str]',
          other: 'Dictionary[Chord[SystemT], str]',
      ) -> 'Dictionary[Chord[SystemT], str]': ...
      def combinations(
          self,
          other: 'Dictionary[K2, V2]',
          *,
          keys: Callable[[Any, Any], Any]=Chord.combine,
          values: Callable[[Any, Any], Any]=concat
      ) -> 'Dictionary[K3, V3]':
          typed_keys: Callable[[K, K2], K3] = keys
          typed_values: Callable[[V, V2], V3] = values
          return Dictionary(
              (typed_keys(kl, kr), typed_values(vl, vr))
              for kl, vl in self
              for kr, vr in other
          )
  
      def to_plover_dict(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ]
      ) -> Dict[str, str]:
          return {to_plover_str(k): v for k, v in self}
  
      def print_as_plover_json_dict(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ]
      ) -> None:
          import json
          print(json.dumps(self.to_plover_dict()))
  
      def longest_key(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ]
      ) -> int:
          return max(len(to_multi_chord(i)) for i in self.keys())
  
      def plover_lookup(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ],
          key: Tuple[str, ...],
          *,
          system: SystemT,
      ) -> str:
          """
          This ignores any and all `ValueError`s due to invalid
          keys!
  
          These should only occur if this is used by the Plover
          Python dictionary plugin when Plovary has a bug, but a
          bug in Plovary shouldn't pull Plover down with it.
          """
  
          try:
              return self[
                  tuple(
                      system.parse(
                          i,
                          force_parse_mandatory_replacements=True,
                          include_always_pressed=False
                      )
                      for i in key
                  )
              ]
          except ValueError as e:
              print(e, file=sys.stderr)
              raise KeyError
  
      def plover_reverse_lookup(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ],
          output: str
      ) -> List[Tuple[str, ...]]:
          return [
              tuple(str(j) for j in to_multi_chord(i))
              for i in self.keys_for(output)
          ]
  
      def install_as_plover_python_dict(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ],
          mod_globals: Dict[str, Any]
      ) -> None:
          mod_globals["LONGEST_KEY"] = self.longest_key()
          # We have to use lambdas here, otherwise it doesn't work for some reason
          try:
              system: SystemT = self.inferred_system()
              mod_globals["lookup"] = (
                  lambda x: self.plover_lookup(x, system=system)
              )
          except StopIteration:
              def only_throw(*args: Any, **kwargs: Any) -> Any:
                  raise KeyError
              mod_globals["lookup"] = only_throw
          mod_globals["reverse_lookup"] = lambda x: self.plover_reverse_lookup(x)
  
      def plover_dict_main(
          self: Union[
              'Dictionary[Chord[SystemT], str]',
              'Dictionary[Tuple[Chord[SystemT], ...], str]'
          ],
          name: str,
          mod_globals: Dict[str, Any]
      ) -> None:
          if name == "__main__":
              import sys
              if "--debug" in sys.argv:
                  print(self)
              elif "--show-system" in sys.argv:
                  # Needed to make `mypy` happy. I have *no* clue why.
                  inferred_system: SystemT = self.inferred_system()
                  print(inferred_system.render_layout())
              else:
                  self.print_as_plover_json_dict()
          else:
              self.install_as_plover_python_dict(mod_globals)
  
      def _add_impl(
          self,
          other: Union['Dictionary[K, V]', Chord[SystemT], str, Any],
          *,
          suffix_or_prefix: Callable[[str], Callable[[str], str]]
      ) -> Any:
          if isinstance(other, Dictionary):
              return Dictionary(chain(self, other))
          elif isinstance(other, Chord):
              self_chord_k = cast(Dictionary[Chord[Any], V], self)
              return self_chord_k.map(keys=add(other))
          elif isinstance(other, str):
              self_str_v = cast(Dictionary[K, str], self)
              return self_str_v.map(values=suffix_or_prefix(other))
          else:
              return NotImplemented
  
      @overload
      def __add__(self, other: 'Dictionary[K, V]') -> 'Dictionary[K, V]':  ...
      @overload
      def __add__(
          self: 'Dictionary[Chord[SystemT], V]',
          other: Chord[SystemT]
      ) -> 'Dictionary[Chord[SystemT], V]': ...
      @overload
      def __add__(self: 'Dictionary[K, str]', other: str) -> 'Dictionary[K, str]':
          ...
      # Using `Any` in the return position because `mypy` doesn't like just using
      # `Dictionary[K, V]` as a return type, even though it would be correct.
      # Using `Any` in the argument position because it could be anything else to
      # allow for `__radd__`
      def __add__(
          self,
          other: Union['Dictionary[K, V]', Chord[SystemT], str, Any]
      ) -> Any:
          """
          Note that strings are *always* assumed to be meant for the value side
          """
          return self._add_impl(other, suffix_or_prefix=suffix)
  
      @overload
      def __radd__(self, other: 'Dictionary[K, V]') -> 'Dictionary[K, V]': ...
      @overload
      def __radd__(
          self: 'Dictionary[Chord[SystemT], V]',
          other: Chord[SystemT]
      ) -> 'Dictionary[Chord[SystemT], V]': ...
      @overload
      def __radd__(
          self: 'Dictionary[K, str]',
          other: str
      ) -> 'Dictionary[K, str]': ...
      def __radd__(
          self,
          other: Union['Dictionary[K, V]', Chord[SystemT], str, Any]
      ) -> Any:
          return self._add_impl(other, suffix_or_prefix=prefix)
  
      def __sub__(self, other: Iterable[K]) -> 'Dictionary[K, V]':
          if hasattr(other, "__iter__"):
              other = list(other)
              if warn_missing_keys_in_dict_sub:
                  if not all(k in self for k in other):
                      warn(f"Not all keys of {other!r} are present in {self!r}")
              return Dictionary((k, v) for k, v in self if k not in other)
          else:
              return NotImplemented
  
      def __mul__(
          self: 'Dictionary[Chord[SystemT], str]',
          other: Union['Dictionary[Chord[SystemT], str]', Any]
      ) -> 'Dictionary[Chord[SystemT], str]':
          if isinstance(other, Dictionary):
              return self.combinations(other)
          else:
              return NotImplemented
  
      @overload
      def __contains__(self, key: K) -> bool: ...
      @overload
      def __contains__(
          self: 'Dictionary[Chord[SystemT], V]',
          key: Tuple[Chord[SystemT], ...]
      ) -> bool: ...
      @overload
      def __contains__(
          self: 'Dictionary[Tuple[Chord[SystemT], ...], V]',
          key: Chord[SystemT]
      ) -> bool: ...
      # Using `Any` because `mypy` doesn't like `Union`ing everything together as
      # of version 0.800
      def __contains__(self, key: Any) -> bool:
          if isinstance(key, Chord) and (key,) in self.dict:
              return True
  
          if _is_chord_in_single_element_tuple(key) and key[0] in self.dict:
              return True
  
          return key in self.dict
  
      @overload
      def __getitem__(self, key: K) -> V: ...
      @overload
      def __getitem__(
          self: 'Dictionary[Chord[SystemT], V]',
          key: Tuple[Chord[SystemT], ...]
      ) -> V: ...
      @overload
      def __getitem__(
          self: 'Dictionary[Tuple[Chord[SystemT], ...], V]',
          key: Chord[SystemT]
      ) -> V: ...
      def __getitem__(self, key: Any) -> V:
          if isinstance(key, Chord) and (key,) in self.dict:
              return self.dict[cast(Any, (key,))]
  
          if _is_chord_in_single_element_tuple(key) and key[0] in self.dict:
              return self.dict[key[0]]
  
          return self.dict[key]
  
      def __setitem__(self, key: K, value: V) -> None:
          self.dict[key] = value
  
      def __iter__(self) -> Iterator[Tuple[K, V]]:
          return iter(self.dict.items())
  
      def __repr__(self) -> str:
          return "Dictionary({{\n{}}})".format(
              "".join(
                  "  {}: {!r},\n".format(
                      (
                          str(k)
                          if isinstance(k, Chord)
                          else (
                              "/".join(str(i) for i in k)
                              if isinstance(k, tuple) and
                                  all(isinstance(i, Chord) for i in k)
                              else repr(k)
                          )
                      ),
                      v
                  )
                  for k, v in self
              )
          )
  
  
  class EnglishSystem(System):
      sys_name = "English Stenotype"
  
      sys_key_order = [
          "#",
          "S-", "T-", "K-", "P-", "W-", "H-", "R-",
          "A", "O", "*", "-", "E", "U",
          "-F", "-R", "-P", "-B", "-L", "-G", "-T", "-S", "-D", "-Z",
      ]
  
      sys_unordered_keys = ["#"]
  
      sys_combining_keys = ["#", "*"]
  
      sys_optional_replacements = {
          # Alphabet
          "B-": ["P-", "W-"],
          "C-": ["K-", "R-"],
          "D-": ["T-", "K-"],
          "F-": ["T-", "P-"],
          "G-": ["T-", "K-", "P-", "W-"],
          "J-": ["S-", "K-", "W-", "R-"],
          "L-": ["H-", "R-"],
          "M-": ["P-", "H-"],
          "N-": ["T-", "P-", "H-"],
          "Q-": ["K-", "W-"],
          "V-": ["S-", "R-"],
          "X-": ["K-", "P-"],
          "Y-": ["K-", "W-", "R-"],
          "Z-": ["S-", "*"],
  
          "I": ["E", "U"],
  
          "-J": ["-P", "-B", "-L", "-G"],
          "-K": ["-B", "-G"],
          "-M": ["-P", "-L"],
          "-N": ["-P", "-B"],
          "-V": ["*", "-F"],
          "-X": ["-B", "-G", "-S"],
      }
  
      sys_mandatory_replacements = {
          "1-": ["#", "S-"],
          "2-": ["#", "T-"],
          "3-": ["#", "P-"],
          "4-": ["#", "H-"],
          "5": ["#", "A"],
          "0": ["#", "O"],
          "-6": ["#", "-F"],
          "-7": ["#", "-P"],
          "-8": ["#", "-L"],
          "-9": ["#", "-T"],
      }
  
      sys_layout = [
          LayoutBox("#", x=0, y=0, w=10),
  
          LayoutBox("S-", x=0, y=1, h=2),
          LayoutBox("T-", x=1, y=1),
          LayoutBox("K-", x=1, y=2),
          LayoutBox("P-", x=2, y=1),
          LayoutBox("W-", x=2, y=2),
          LayoutBox("H-", x=3, y=1),
          LayoutBox("R-", x=3, y=2),
  
          LayoutBox("A", x=2, y=4),
          LayoutBox("O", x=3, y=4),
          LayoutBox("*", x=4, y=1, h=2),
          LayoutBox("E", x=5, y=4),
          LayoutBox("U", x=6, y=4),
  
          LayoutBox("-F", x=5, y=1),
          LayoutBox("-R", x=5, y=2),
          LayoutBox("-P", x=6, y=1),
          LayoutBox("-B", x=6, y=2),
          LayoutBox("-L", x=7, y=1),
          LayoutBox("-G", x=7, y=2),
          LayoutBox("-T", x=8, y=1),
          LayoutBox("-S", x=8, y=2),
          LayoutBox("-D", x=9, y=1),
          LayoutBox("-Z", x=9, y=2),
      ]
  
  
  EnglishChord = Chord[EnglishSystem]
  
  
  # Keeping the name short since I expect it to be used a lot:
  system = EnglishSystem()
  
  digit_keys = [
      "0", "1-", "2-", "3-", "4-", "5", "-6", "-7", "-8", "-9"
  ]
  digits = [system.chord(i) for i in digit_keys]
  
  # A few default dictionaries:
  single_digit_only = Dictionary(
      (digits[i], str(i))
      for i in range(10)
  )
  double_digit_only = Dictionary(
      (digits[i] + digits[j], str(i) + str(j))
      for i in range(10)
      for j in range(10)
      if i != j and system.keys_ordered(digit_keys[i], digit_keys[j])
  ) + Dictionary(
      (digits[i] + digits[j] + system.chord("E", "U"), str(i) + str(j))
      for i in range(10)
      for j in range(10)
      if i != j and system.keys_ordered(digit_keys[j], digit_keys[i])
  ) + Dictionary((digits[i] + "-D", str(i) * 2) for i in range(10))
  hundred00 = system.parsed_single_dict({"0D": "100"})
  double_digit_only_hundred00 = double_digit_only - hundred00.keys() + hundred00
  hundred1z = system.parsed_single_dict({"1-Z": "100"})
  double_digit_only_hundred1z = double_digit_only + hundred1z
  single_and_double_digit = single_digit_only + double_digit_only
  single_and_double_digit_hundred00 = (
      single_digit_only + double_digit_only_hundred00
  )
  single_and_double_digit_hundred1z = (
      single_digit_only + double_digit_only_hundred1z
  )
  hundreds = Dictionary(
      (digits[i] + "-Z", str(i) + "00")
      for i in range(10)
  )
  numbers = single_and_double_digit + hundreds
  
  # Fingertyping
  fingertyping_lowercase_no_asterisk = system.parsed_single_dict({
      "A": "a",
      "PW": "b",
      "KR": "c",
      "TK": "d",
      "E": "e",
      "TP": "f",
      "TKPW": "g",
      "H": "h",
      "EU": "i",
      "SKWR": "j",
      "K": "k",
      "HR": "l",
      "PH": "m",
      "TPH": "n",
      "O": "o",
      "P": "p",
      "KW": "q",
      "R": "r",
      "S": "s",
      "T": "t",
      "U": "u",
      "SR": "v",
      "W": "w",
      "KP": "x",
      "KWR": "y",
      "STKPW": "z",
  })
  fingertyping_uppercase_no_p_no_asterisk = (
      fingertyping_lowercase_no_asterisk.map(values=str.upper)
  )
  fingertyping_uppercase_p_no_asterisk = (
      fingertyping_uppercase_no_p_no_asterisk.map(keys=add("-P"))
  )
  fingertyping_no_asterisk = (
      fingertyping_lowercase_no_asterisk + fingertyping_uppercase_p_no_asterisk
  )
  fingertyping_lowercase = fingertyping_lowercase_no_asterisk.map(keys=add("*"))
  fingertyping_uppercase_no_p = (
      fingertyping_uppercase_no_p_no_asterisk.map(keys=add("*"))
  )
  fingertyping_uppercase_p = (
      fingertyping_uppercase_p_no_asterisk.map(keys=add("*"))
  )
  fingertyping = fingertyping_no_asterisk.map(keys=add("*"))
  