oChar <$> traverse parseLetter chars
        pure (pos, i', s)
  where
    letterToChar (Char c) = Just c
    letterToChar _ = Nothing

deadKey ∷ (Logger m, Parser m, MonadFail m) ⇒ m [(Char, ActionMap)]
deadKey = do
    ["DEADKEY", s] ← readLine
    let i = maybeToList (readMaybe ('0':'x':s))
    c ← chr <$> i <$ when (null i) (tell ["unknown dead key ‘" ⊕ s ⊕ "’"])
    m ← many (isHex *> deadPair)
    pure (zip c [m])

deadPair ∷ (Parser m, MonadFail m) ⇒ m (Letter
ligature ∷ (Logger m, Parser m, MonadFail m) ⇒ m (Maybe (Pos, Int, String))
ligature = do
    sc:i:chars ← readLine
    guard (not (null chars))
    runMaybeT $ do
        pos ← parseShortcutPos sc
        i' ← maybe (tellMaybeT ["unknown index ‘" ⊕ i ⊕ "’"]) pure $ readMaybe ('0':'x':i)
        s ← mapMaybe letterToChar <$> traverse parseLetter chars
        pure (pos, i', s)
  where
    letterToChar (Char c) = Just c
    letterToChar _ = Nothing
ligature = do
    sc:i:chars ← readLine
    guard (not (null chars))
    runMaybeT $ do
        pos ← parseShortcutPos sc
        i' ← maybe (tellMaybeT ["unknown index ‘" ⊕ i ⊕ "’"]) pure $ readMaybe ('0':'x':i)
        s ← mapMaybe letterToChar <$> traverse parseLetter chars
        pure (pos, i', s)
  where
    letterToChar (Char c) = Just c
    letterToChar _ = Nothing