{-# LANGUAGE Safe #-}

{-|
Module      : Data.Char.Control
Description : Visualizing control characters.
Maintainer  : hapytexeu+gh@gmail.com
Stability   : experimental
Portability : POSIX

Unicode has a <https://www.unicode.org/charts/PDF/U2400.pdf block> named /Control Pictures/ that visualizes control characters such as NULL, SUB, LF, DEL, etc.
This module aims to make it more convenient to convert the control characters to their visualization and vice versa. Only ASCII control characters and
the space are supported.
-}

module Data.Char.Control (
    -- * Conversion to control pictures
    controlPicture, controlPicture', convertToControlPictures
    -- * Conversion from control picturesa
  , fromControlPicture, fromControlPicture'
    -- * Check if a 'Char' is a control 'Char'
  , isAsciiControl, isControl, hasControlVisualization
    -- * Alternative characters
  , blankSymbol, openBox, newLine, alternativeDelete, alternativeSubstitute
  ) where

import Data.Bits((.&.), (.|.))
import Data.Char(chr, ord, isControl)
import Data.Maybe(fromMaybe)
import Data.Text as T

-- | Check if the given 'Char' is a control character in the ASCII range.
isAsciiControl
  :: Char  -- ^ The given 'Char' to check.
  -> Bool  -- ^ 'True' if the given 'Char' is a control character in the ASCII range; otherwise 'False'.
isAsciiControl :: Char -> Bool
isAsciiControl Char
c = Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'\x7f' Bool -> Bool -> Bool
&& Char -> Bool
isControl Char
c

-- | Check if for the given 'Char' there is a visualization.
hasControlVisualization
  :: Char  -- ^ The given 'Char' to check.
  -> Bool  -- ^ 'True' if the given control character can be visualized; 'False' otherwise.
hasControlVisualization :: Char -> Bool
hasControlVisualization Char
' ' = Bool
True
hasControlVisualization Char
c = Char -> Bool
isAsciiControl Char
c

-- | Another symbol used to denote a /space/ that works with @␢@. The 'controlPicture' function uses @␠@.
blankSymbol
  :: Char -- ^ Another character for /space/.
blankSymbol :: Char
blankSymbol = Char
'\x2422'

-- | Another symbol used to denote a /space/ that works with @␣@. The 'controlPicture' function uses @␠@.
openBox
  :: Char -- ^ Another character for /space/.
openBox :: Char
openBox = Char
'\x2423'

-- | Another symbol used to denote a /new line/ that works with @␤@. The control picture function uses @␊@.
newLine
  :: Char -- ^ Another character for a /new line/.
newLine :: Char
newLine = Char
'\x2424'

-- | Another symbol used to denote a /delete/ character that works with @␥@. The control picture function uses @␡@.
alternativeDelete
  :: Char -- ^ Another character for /delete/.
alternativeDelete :: Char
alternativeDelete = Char
'\x2425'

-- | Another symbol used to denote a /substitute/ character that works with @␦@. The control picture function uses @␚@.
alternativeSubstitute
  :: Char -- ^ Another character for /substitute/.
alternativeSubstitute :: Char
alternativeSubstitute = Char
'\x2426'

-- | Convert the given control 'Char' to a 'Char' that visualizes that characters.
-- This is sometimes done by diagonal lettering of the characters denoting the control
-- character. If the given 'Char' is not a control character, 'Nothing' is returned.
controlPicture
  :: Char  -- ^ The given control 'Char' to convert.
  -> Maybe Char  -- The corresponding 'Char' that visualizes the control 'Char' wrapped in a 'Just'; 'Nothing' if the given 'Char' is not a control character.
controlPicture :: Char -> Maybe Char
controlPicture Char
c
  | Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
' ' = Char -> Maybe Char
forall a. a -> Maybe a
Just (Char -> Char
controlPicture' Char
c)
  | Bool
otherwise = Maybe Char
forall a. Maybe a
Nothing

-- | Convert the given control 'Char' to a 'Char' that visualizes that character.
-- If the given 'Char' is not a control character, it is unspecified what happens.
controlPicture'
  :: Char  -- ^ The given control 'Char'.
  -> Char  -- ^ The corresponding 'Char' that visualizes the control 'Char'.
controlPicture' :: Char -> Char
controlPicture' Char
c
  | Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
' ' = Int -> Char
chr (Int
0x2400 Int -> Int -> Int
forall a. Bits a => a -> a -> a
.|. Char -> Int
ord Char
c)
  | Bool
otherwise = Char
'\x2421'

-- | Convert the given visualization of a control 'Char' to that control 'Char' wrapped
-- in a 'Just'. If the given 'Char' is not a visualization of a control character,
-- 'Nothing' is returned.
fromControlPicture
  :: Char  -- ^ The given /visualization/ of control 'Char'.
  -> Maybe Char  -- ^ The corresponding control 'Char' wrapped in a 'Just' if the given character is the visualization of a control character; otherwise 'Nothing'.
fromControlPicture :: Char -> Maybe Char
fromControlPicture Char
c
  | Char
'\x2400' Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
'\x2426' = Char -> Maybe Char
forall a. a -> Maybe a
Just (Char -> Char
fromControlPicture' Char
c)
  | Bool
otherwise = Maybe Char
forall a. Maybe a
Nothing

-- | Convert the given visualization of a control 'Char' to that control 'Char'.
-- If the given 'Char' is not a visualization of a control character,
-- it is unspecified what happens.
fromControlPicture'
  :: Char  -- ^ The given /visualization/ of control 'Char'.
  -> Char  -- ^ The corresponding control 'Char'.
fromControlPicture' :: Char -> Char
fromControlPicture' Char
'\x2421' = Char
'\x7f'
fromControlPicture' Char
'\x2422' = Char
' '
fromControlPicture' Char
'\x2423' = Char
' '
fromControlPicture' Char
'\x2424' = Char
'\x0a'
fromControlPicture' Char
'\x2425' = Char
'\x7f'
fromControlPicture' Char
'\x2426' = Char
'\x1a'
fromControlPicture' Char
c = Int -> Char
chr (Int
0x7f Int -> Int -> Int
forall a. Bits a => a -> a -> a
.&. Char -> Int
ord Char
c)

-- | Convert the given 'Text' to a 'Text' object where the control characters
-- that have in Unicode a control picture block item.
convertToControlPictures
  :: Text  -- ^ The given 'Text' where we want to convert control characters to their control picture characters.
  -> Text  -- ^ The corresponding 'Text' where the control characters are converted to their control picture characters.
convertToControlPictures :: Text -> Text
convertToControlPictures = (Char -> Char) -> Text -> Text
T.map (Char -> Maybe Char -> Char
forall a. a -> Maybe a -> a
fromMaybe (Char -> Maybe Char -> Char)
-> (Char -> Maybe Char) -> Char -> Char
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Char -> Maybe Char
controlPicture)