packages feed

hs-0.1.0.0: src/HS/Types/CompilerTool.hs

{-# LANGUAGE DeriveAnyClass             #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE DerivingVia                #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}
{-# LANGUAGE MultiWayIf                 #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE RecordWildCards            #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE StandaloneDeriving         #-}
{-# LANGUAGE TupleSections              #-}

module HS.Types.CompilerTool
  ( Compiler(..)
  , Tool(..)
  , CompilerVersion(..)
  , ToolName(..)
  , compiler
  , compiler'
  , compilerVersion
  , toolVersion
  , compilerToGhcTool
  , compilerToTool
  , toolToCompiler
  , toolToCompiler'
  ) where

import           Data.Char
import           Data.Coerce
import           Data.Default
import           Data.Possibly
import           Data.Text(Text)
import qualified Data.Text              as T
import           Fmt
import           Text.Enum.Text


-- | @ghc@ or @ghc-8.10.4@, etc.
newtype Compiler = Compiler { getCompiler :: Maybe CompilerVersion }
  deriving (Eq,Ord,Show)

-- | @ghci@ or @ghc-pkg-9.0.1@, etc.
newtype Tool = Tool { getTool :: (ToolName,Maybe CompilerVersion) }
  deriving (Eq,Ord,Show)

-- | @8.6.5@, etc.
newtype CompilerVersion = CompilerVersion { getCompilerVersion :: (Int,Int,Int) }
  deriving (Eq,Ord,Show)

-- | @ghc@, @ghc-pkg@, @ghci@, etc.
data ToolName
  = TN_ghc
  | TN_ghc_pkg
  | TN_ghci
  | TN_haddock
  | TN_h2ps
  | TN_hpc
  | TN_hsc2hs
  | TN_runghc
  | TN_runhaskell
  deriving stock    (Bounded,Enum,Eq,Ord,Show)
  deriving anyclass (EnumText)
  deriving (Buildable,TextParsable) via UsingEnumText ToolName

instance Buildable    Compiler where build     = build_compiler
instance TextParsable Compiler where parseText = parse_compiler
instance Buildable    Tool     where build     = build_tool
instance TextParsable Tool     where parseText = parse_tool

instance Buildable CompilerVersion where
  build (CompilerVersion (a,b,c)) = ""+|a|+"."+|b|+"."+|c|+""

instance TextParsable CompilerVersion where
  parseText vrn =
    case T.splitOn "." vrn of
      [a_t,b_t,c_t] -> fmap CompilerVersion $
                            (,,) <$> parseText a_t <*> parseText b_t <*> parseText c_t
      _             -> Left $ "expected version a.b.c : "+|vrn|+""

instance Default CompilerVersion where
  def = CompilerVersion (8,10,4)

compiler :: CompilerVersion -> Compiler
compiler = compiler' . Just

compiler' :: Maybe CompilerVersion -> Compiler
compiler' = coerce

compilerVersion :: Compiler -> Maybe CompilerVersion
compilerVersion = coerce

toolVersion :: Tool -> Maybe CompilerVersion
toolVersion = compilerVersion . toolToCompiler

compilerToGhcTool :: Compiler -> Tool
compilerToGhcTool = compilerToTool . (,) TN_ghc

compilerToTool :: (ToolName,Compiler) -> Tool
compilerToTool = coerce

toolToCompiler :: Tool -> Compiler
toolToCompiler = snd . toolToCompiler'

toolToCompiler' :: Tool -> (ToolName,Compiler)
toolToCompiler' = coerce


----------------------------------------------------------------------------------------------------
-- build_compiler, parse_compiler
----------------------------------------------------------------------------------------------------

build_compiler :: Compiler -> Builder
build_compiler = build . compilerToGhcTool

parse_compiler :: Text -> Possibly Compiler
parse_compiler txt = chk =<< parseText txt
  where
    chk :: Tool -> Possibly Compiler
    chk tl | tn == TN_ghc = return cp
           | otherwise    = Left $ "expected 'ghc' but saw "+|tn|+" in: "+|txt|+""
      where
        (tn,cp) = toolToCompiler' tl


----------------------------------------------------------------------------------------------------
-- build_tool, parse_tool
----------------------------------------------------------------------------------------------------

build_tool :: Tool -> Builder
build_tool tl = ""+|tn|++|maybe "" (("-"<>) . build) mb_vrn
  where
    mb_vrn  = compilerVersion cp
    (tn,cp) = toolToCompiler' tl

parse_tool :: Text -> Possibly Tool
parse_tool txt0 = case T.breakOnEnd "-" txt0 of
    (frt,vrn) | not (T.null frt) && plausible_vrn vrn
                          -> with_vrn (T.dropWhileEnd (=='-') frt) vrn
              | otherwise -> Tool . (,Nothing) <$> parseText txt0
  where
    with_vrn :: Text -> Text -> Possibly Tool
    with_vrn tool vrn = fmap Tool $
          (,) <$> parseText tool <*> (Just <$> parseText vrn)

plausible_vrn :: Text -> Bool
plausible_vrn = T.all is_vrn_c
  where
    is_vrn_c :: Char -> Bool
    is_vrn_c c = isDigit c || c=='.'