packages feed

fastmemo-0.1.0.0: src/Data/Function/FastMemo/Char.hs

{-# OPTIONS_GHC -Wno-orphans #-}

module Data.Function.FastMemo.Char () where

import Data.Bits (complement, countLeadingZeros)
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.UTF8 as UTF8
import Data.Function.FastMemo.Class (Memoizable (..))
import Data.Function.FastMemo.Util (memoizeFixedLen)
import Data.Function.FastMemo.Word ()
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Word (Word8)

-- We want ASCII Chars to require only a single Vector lookup, so let's encode as UTF-8
instance Memoizable Char where
  memoize f = memoize (f . codePointToChar) . charToCodePoint

newtype CodePoint = CodePoint {getCodePoint :: NonEmpty Word8}

-- In UTF-8, the first byte of a codepoint tells us how many more bytes that codepoint contains.
-- We can use this fact to reduce lookups.
instance Memoizable CodePoint where
  memoize f =
    let f' = memoize (\w -> memoizeFixedLen (extraBytes w) (f . CodePoint . (w :|)))
     in \(CodePoint (w :| ws)) -> f' w ws

extraBytes :: Word8 -> Int
extraBytes x = case countLeadingOnes x of
  0 -> 0
  n -> n - 1

countLeadingOnes :: Word8 -> Int
countLeadingOnes = countLeadingZeros . complement

charToCodePoint :: Char -> CodePoint
charToCodePoint = CodePoint . NonEmpty.fromList . ByteString.unpack . UTF8.fromString . (: [])

codePointToChar :: CodePoint -> Char
codePointToChar = head . UTF8.toString . ByteString.pack . NonEmpty.toList . getCodePoint