docs: def e2h(string):
      """
      Convert english to hangul. ('gksdudxk' -> '한영타')
      During processing, state interleaves INIT, LEAD_GIVEN, MEDI_GIVEN and TAIL_GIVEN
  
      :param string: english that actually represent hangul string
      :return: converted hangul string
      """
      ctx = Context(string)
      for char in ctx.input:
          hchar = E2H_MAPPING.get(char)
  
          if not char.isalpha() or not hchar:
              ctx.do_final()
              ctx.output.append(char)
              ctx.state = State.INIT
              continue
  
          if ctx.state is State.INIT:
              if hchar not in JA_LEAD:
                  ctx.output.append(hchar)
              else:
                  ctx.state = State.LEAD_GIVEN
                  ctx.pending.append(hchar)
  
          elif ctx.state is State.LEAD_GIVEN:
              if hchar in MO:  # ㅇ + ㅏ
                  ctx.state = State.MEDI_GIVEN
                  ctx.pending.append(hchar)
              else:  # ㅇ + ㅇ
                  ctx.state = State.INIT
                  ctx.pending.append(hchar)
                  ctx.flush()
  
          elif ctx.state is State.MEDI_GIVEN:
              if hchar in JA:  # ㅇ + ㅏ + ㅇ
                  ctx.state = State.TAIL_GIVEN
                  ctx.pending.append(hchar)
              else:
                  compose_waiter = ctx.pending[-1]
                  compose_tuple = JAMO_COMPOSE_TABLE.get(compose_waiter)
  
                  if compose_tuple and hchar in compose_tuple:  # (ㅇ + ㅗ + ㅏ)
                      ctx.pending[-1] = chr((ord(compose_waiter) + compose_tuple.index(hchar) + 1))
                  else:  # (ㅇ + ㅏ + ㅏ)
                      ctx.do_final()
                      ctx.state = State.INIT
                      ctx.output.append(hchar)
  
          elif ctx.state is State.TAIL_GIVEN:
              if hchar in MO:
                  if len(ctx.composing_tails) == 2:  # (ㅇ + ㅓ + ㄵ + ㅔ ==> 언제)
                      ctx.pending.pop()  # drop 'ㄵ'
                      ctx.do_final(ctx.composing_tails.pop(0))  # (ㅇ + ㅓ + ㄴ)
                      ctx.pending.append(ctx.composing_tails.pop())  # (ㅈ)
                      ctx.pending.append(hchar)  # (ㅈ + ㅔ)
                      ctx.state = State.MEDI_GIVEN
                  else:  # ㄱ,ㅏ,ㅅ  + ㅏ ==> 가,사
                      last_hchar = ctx.pending.pop()
                      ctx.do_final()
                      ctx.pending.append(last_hchar)
                      ctx.pending.append(hchar)
                      ctx.state = State.MEDI_GIVEN
              else:
                  compose_waiter = ctx.pending[-1]
                  compose_tuple = JAMO_COMPOSE_TABLE.get(compose_waiter)
  
                  if compose_tuple and hchar in compose_tuple:  # (ㄷ + ㅏ + ㄹ + ㄱ)
                      ctx.pending[-1] = chr((ord(compose_waiter) + compose_tuple.index(hchar) + 1))
  
                      ctx.composing_tails.clear()
                      ctx.composing_tails.append(compose_waiter)
                      ctx.composing_tails.append(hchar)
  
                  else:  # (ㄷ + ㅏ + ㅂ + ㄱ) or (ㄷ + ㅏ + ㄹ + ㄱ + ㄱ)
                      ctx.do_final()
                      ctx.state = State.LEAD_GIVEN
                      ctx.pending.append(hchar)
                      ctx.composing_tails.clear()
  
      ctx.do_final()  # finalize remain character if any
      return ''.join(ctx.output)