packages feed

ucd-0.0.1.2: Setup.hs

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase, MonadComprehensions #-}
{-# OPTIONS_GHC -Wall
                -Wcompat
                -Wredundant-constraints
                -Wno-name-shadowing
                -Wincomplete-uni-patterns
                -Wincomplete-record-updates
                -Werror=incomplete-patterns
                -Werror=incomplete-uni-patterns
                -Werror=incomplete-record-updates
                -Werror=missing-fields
                -Werror=missing-methods
  #-}
module Main where

import           Prelude hiding (lines)
import qualified Prelude
import           Control.Applicative
import           Control.Monad (join)
import           Data.Bits
import           Data.Bool (bool)
import           Data.Char
import           Data.Foldable
import           Data.List (intercalate)
import           Data.Map (Map)
import qualified Data.Map as Map
import           Numeric (showHex)
import           System.Directory (createDirectoryIfMissing)
import           Text.Regex.Applicative
import           Text.Regex.Applicative.Common (hexadecimal)
import           Util hiding (intercalate)

import Distribution.Simple
import Distribution.Simple.PreProcess
import Distribution.Types.BuildInfo

main :: IO ()
main = defaultMainWithHooks simpleUserHooks
  { hookedPreProcessors =
    ("ucd-range-props", \ _ _ _ -> PreProcessor True $ \ (inBase, inFile) (outBase, outFile) _v ->
         let moduleNameOf = fmap (\ case '/' -> '.'; x -> x) . fst . break (== '.')
         in readFile (inBase ++ "/" ++ inFile) >>=
            writeFile (outBase ++ "/" ++ outFile) .
            maybe (error "bad format") (ppRangeProps (moduleNameOf inFile)) . parseRangeProps) :
    hookedPreProcessors simpleUserHooks
  , preBuild = \ args flags -> do
        let dir = "src/cbits"
        createDirectoryIfMissing True dir
        for_ [("DerivedCoreProperties", maybe (error "bad format") doRangeProps . parseRangeProps),
              ("BidiBrackets",          mkBidiBrackets)] $ \ (name, mk) ->
            readFile ("ucd/" ++ name ++ ".txt") >>= writeFile (dir ++ "/" ++ name ++ ".h") . mk
        [(Just emptyBuildInfo { includeDirs = ["src/cbits"] }, []) <> info | info <- preBuild simpleUserHooks args flags]
  }

ppRangeProps :: [Char] -> RangeProps Char -> [Char]
ppRangeProps moduleName props = unlines $
  [ asum ["module ", moduleName, " (", intercalate ", " (("is" ++) <$> names), ") where"]
  , "import Data.Word"
  , "import Foreign.C.Types"
  , "import Foreign.Ptr"
  , intercalate ", " (("is" ++) <$> names) ++ " :: Char -> Bool"
  , "foreign import ccall unsafe \"range_search\" c_range_search :: Word32 -> Ptr () -> Word -> CInt"
  ] ++ toList (Map.mapWithKey genCode1 props)
  where
    names = Map.keys props
    genCode1 name ranges = (unlines . fmap asum)
      [ ["is", name, " x = 0 /= c_range_search ((fromIntegral . fromEnum) x) data", name, " ", show (length ranges)]
      , ["{-# INLINE is", name, " #-}"]
      , ["foreign import ccall unsafe \"&", name, "\" data", name, " :: Ptr ()"]
      ]

doRangeProps :: RangeProps Char -> [Char]
doRangeProps = unlines . (["#include <stdint.h>", "typedef struct { uint32_t a, b; } RangeU32;"] ++) . toList . Map.mapWithKey genCode1
  where
    genCode1 name ranges = asum
      [ "extern const RangeU32 ", name, "[]; "
      , "const RangeU32 ", name, "[] = {"
      , intercalate ", " [asum ["{", show (fromEnum a), ", ", show (fromEnum b + 1), "}"] | Range a b <- ranges]
      , "};"]

mkBidiBrackets :: [Char] -> [Char]
mkBidiBrackets = ("#include <stdint.h>\nextern const uint32_t BidiBrackets[]; const uint32_t BidiBrackets[] = " ++) . (++ ";") . maybe (error "bad format") generate . parse lineRE
  where
    generate :: [(Char, Char, Bool)] -> [Char]
    generate = ("{" ++) . (++ "}") . intercalate ", " . fmap generate1

    generate1 :: (Char, Char, Bool) -> [Char]
    generate1 (x, y, isC)
      | abs d > 3 = error ("Difference too great: " ++ show (y, x))
      | otherwise = "0x" ++ showHex (fromEnum x .|. shiftL (d .&. 7) 28 .|. bool 0 (bit 31) isC) ""
      where d = fromEnum y - fromEnum x

    lineRE :: RE Char (Char, Char, Bool)
    lineRE = (,,) <$> spaced enum0x `ap'` spaced enum0x `ap'` (spaced . asum) [False <$ "o", True <$ "c"]

parseRangeProps :: [Char] -> Maybe (RangeProps Char)
parseRangeProps = parse lineRE & fmap ((fmap . fmap) pure & Map.fromListWith (flip (++)))
  where
    lineRE :: RE Char ([Char], Range Char)
    lineRE = flip ((,) . filter (/= '_')) <$> spaced rangeRE `ap'` spaced word

type RangeProps a = Map [Char] [Range a]

data Range a = Range a a
  deriving (Foldable, Functor, Traversable)

rangeRE :: Enum n => RE Char (Range n)
rangeRE = join Range <$> enum0x <|> Range <$> enum0x <* string ".." <*> enum0x

infixl 4 `ap'`
ap' :: RE Char (a -> b) -> RE Char a -> RE Char b
ap' f a = f <* sym ';' <*> a

spaced :: RE Char a -> RE Char a
spaced a = spaces *> a <* spaces

comment :: RE Char [Char]
comment = sym '#' *> many (psym (/= '\n'))

lines :: [Char] -> [[Char]]
lines = filter (not . all isSpace) . fmap (fst . break (== '#')) . Prelude.lines

parse :: RE Char a -> [Char] -> Maybe [a]
parse a = traverse (match a) . lines

spaces :: RE Char [Char]
spaces = many (psym isSpace)

enum0x :: Enum n => RE Char n
enum0x = toEnum <$> hexadecimal

word :: RE Char [Char]
word = many (psym (liftA2 (||) isAlphaNum (== '_')))