Skip to content

Commit

Permalink
Add buildinfo-reference-generator
Browse files Browse the repository at this point in the history
  • Loading branch information
phadej committed Feb 13, 2020
1 parent 847fa25 commit af041d0
Show file tree
Hide file tree
Showing 40 changed files with 1,498 additions and 39 deletions.
2 changes: 2 additions & 0 deletions Cabal/Cabal.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ library
Distribution.Compat.CharParsing
Distribution.FieldGrammar
Distribution.FieldGrammar.Class
Distribution.FieldGrammar.Described
Distribution.FieldGrammar.FieldDescrs
Distribution.FieldGrammar.Parsec
Distribution.FieldGrammar.Pretty
Expand Down Expand Up @@ -615,6 +616,7 @@ test-suite unit-tests
UnitTests.Distribution.Compat.CreatePipe
UnitTests.Distribution.Compat.Graph
UnitTests.Distribution.Compat.Time
UnitTests.Distribution.Described
UnitTests.Distribution.Simple.Glob
UnitTests.Distribution.Simple.Program.GHC
UnitTests.Distribution.Simple.Program.Internal
Expand Down
23 changes: 11 additions & 12 deletions Cabal/Distribution/FieldGrammar/Class.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import Distribution.Compat.Lens
import Distribution.Compat.Prelude
import Prelude ()

import Distribution.CabalSpecVersion (CabalSpecVersion)
import Distribution.Compat.Newtype (Newtype)
import Distribution.CabalSpecVersion (CabalSpecVersion)
import Distribution.Compat.Newtype (Newtype)
import Distribution.FieldGrammar.Described (Described)
import Distribution.Fields.Field
import Distribution.Parsec (Parsec)
import Distribution.Pretty (Pretty)
import Distribution.Utils.ShortText

-- | 'FieldGrammar' is parametrised by
Expand All @@ -33,7 +32,7 @@ class FieldGrammar g where

-- | Field which should be defined, exactly once.
uniqueFieldAla
:: (Parsec b, Pretty b, Newtype a b)
:: (Described b, Newtype a b)
=> FieldName -- ^ field name
-> (a -> b) -- ^ 'Newtype' pack
-> ALens' s a -- ^ lens into the field
Expand All @@ -48,15 +47,15 @@ class FieldGrammar g where

-- | Optional field.
optionalFieldAla
:: (Parsec b, Pretty b, Newtype a b)
:: (Described b, Newtype a b)
=> FieldName -- ^ field name
-> (a -> b) -- ^ 'pack'
-> ALens' s (Maybe a) -- ^ lens into the field
-> g s (Maybe a)

-- | Optional field with default value.
optionalFieldDefAla
:: (Parsec b, Pretty b, Newtype a b, Eq a)
:: (Described b, Newtype a b, Eq a)
=> FieldName -- ^ field name
-> (a -> b) -- ^ 'Newtype' pack
-> ALens' s a -- ^ @'Lens'' s a@: lens into the field
Expand Down Expand Up @@ -94,7 +93,7 @@ class FieldGrammar g where
-- /Note:/ 'optionalFieldAla' is a @monoidalField@ with 'Last' monoid.
--
monoidalFieldAla
:: (Parsec b, Pretty b, Monoid a, Newtype a b)
:: (Described b, Monoid a, Newtype a b)
=> FieldName -- ^ field name
-> (a -> b) -- ^ 'pack'
-> ALens' s a -- ^ lens into the field
Expand Down Expand Up @@ -135,23 +134,23 @@ class FieldGrammar g where

-- | Field which can be defined at most once.
uniqueField
:: (FieldGrammar g, Parsec a, Pretty a)
:: (FieldGrammar g, Described a)
=> FieldName -- ^ field name
-> ALens' s a -- ^ lens into the field
-> g s a
uniqueField fn = uniqueFieldAla fn Identity

-- | Field which can be defined at most once.
optionalField
:: (FieldGrammar g, Parsec a, Pretty a)
:: (FieldGrammar g, Described a)
=> FieldName -- ^ field name
-> ALens' s (Maybe a) -- ^ lens into the field
-> g s (Maybe a)
optionalField fn = optionalFieldAla fn Identity

-- | Optional field with default value.
optionalFieldDef
:: (FieldGrammar g, Functor (g s), Parsec a, Pretty a, Eq a)
:: (FieldGrammar g, Functor (g s), Described a, Eq a)
=> FieldName -- ^ field name
-> ALens' s a -- ^ @'Lens'' s a@: lens into the field
-> a -- ^ default value
Expand All @@ -160,7 +159,7 @@ optionalFieldDef fn = optionalFieldDefAla fn Identity

-- | Field which can be define multiple times, and the results are @mappend@ed.
monoidalField
:: (FieldGrammar g, Parsec a, Pretty a, Monoid a)
:: (FieldGrammar g, Described a, Monoid a)
=> FieldName -- ^ field name
-> ALens' s a -- ^ lens into the field
-> g s a
Expand Down
266 changes: 266 additions & 0 deletions Cabal/Distribution/FieldGrammar/Described.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Distribution.FieldGrammar.Described (
Described (..),
describeDoc,
-- * Regular expressions
Regex (..),
RTerm (..),
reHsString,
reEps,
reChar,
reChars,
reDot,
reComma,
reSpacedComma,
reMunchCS,
reMunch1CS,
-- * Character Sets
csChar,
csAlphaNum,
csNotSpace,
csNotSpaceOrComma,
-- * Pretty-printing
regexDoc,
-- * Generation
generate,
) where

import Data.Char (isControl)
import Distribution.Compat.Prelude
import Prelude ()

import Distribution.Parsec (Parsec)
import Distribution.Pretty (Pretty)

import qualified Distribution.Utils.AnsiCharSet as ACS
import qualified Text.PrettyPrint as PP

-- | Class describing the pretty/parsec format of a.
class (Pretty a, Parsec a) => Described a where
-- | A pretty document of "regex" describing the field format
describe :: proxy a -> Regex RTerm

-- | Pretty-print description.
--
-- >>> describeDoc ([] :: [Bool])
-- \mathop{\mathord{"}\mathtt{True}\mathord{"}}\mid\mathop{\mathord{"}\mathtt{False}\mathord{"}}
--
describeDoc :: Described a => proxy a -> PP.Doc
describeDoc p = regexDoc (describe p)

instance Described Bool where
describe _ = REUnion ["True", "False"]

instance Described a => Described (Identity a) where
describe _ = describe ([] :: [a])

-------------------------------------------------------------------------------
-- Regex
-------------------------------------------------------------------------------

-- | Regular expressions tuned for 'Described' use-case.
data Regex a
= REAppend [Regex a] -- ^ append @ab@
| REUnion [Regex a] -- ^ union @a|b@
| REMunch (Regex a) (Regex a) -- ^ star @a*@, with a separator
| REMunch1 (Regex a) (Regex a) -- ^ plus @a+@, with a separator
| REOpt (Regex a) -- ^ optional @r?@
| REString String -- ^ literal string @abcd@
| RECharSet ACS.AnsiCharSet -- ^ charset @[:alnum:]@
| RESpaces -- ^ zero-or-more spaces
| RESpaces1 -- ^ one-or-more spaces
| REVar a -- ^ variable
| RELet String (Regex a)
(Regex (Maybe a)) -- ^ named expression
| RERec String (Regex (Maybe a)) -- ^ recursive expressions

| RETodo -- ^ unspecified
deriving (Eq, Ord, Show)

-- | Terminals used by field grammars.
--
-- TODO: remove
data RTerm
= RHaskellString
| RUnqualName
deriving (Eq, Ord, Show)

reHsString :: Regex RTerm
reHsString = REVar RHaskellString

reEps :: Regex a
reEps = REAppend []

reChar :: Char -> Regex a
reChar = RECharSet . ACS.singleton

reChars :: [Char] -> Regex a
reChars = RECharSet . ACS.fromList

reDot :: Regex a
reDot = reChar '.'

reComma :: Regex a
reComma = reChar ','

reSpacedComma :: Regex a
reSpacedComma = RESpaces <> reComma <> RESpaces

reMunch1CS :: ACS.AnsiCharSet -> Regex a
reMunch1CS = REMunch1 reEps . RECharSet

reMunchCS :: ACS.AnsiCharSet -> Regex a
reMunchCS = REMunch reEps . RECharSet

instance IsString (Regex a) where
fromString = REString

instance Semigroup (Regex a) where
x <> y = REAppend (unAppend x ++ unAppend y) where
unAppend (REAppend rs) = rs
unAppend r = [r]

instance Monoid (Regex a) where
mempty = REAppend []
mappend = (<>)

-------------------------------------------------------------------------------
-- Character sets
-------------------------------------------------------------------------------

csChar :: Char -> ACS.AnsiCharSet
csChar = ACS.singleton

csAlphaNum :: ACS.AnsiCharSet
csAlphaNum = ACS.alphanum

csNotSpace :: ACS.AnsiCharSet
csNotSpace = ACS.filter (\c -> not (isControl c) && c /= ' ') ACS.full

csNotSpaceOrComma :: ACS.AnsiCharSet
csNotSpaceOrComma = ACS.filter (/= ',') csNotSpace

-------------------------------------------------------------------------------
-- Pretty-printing
-------------------------------------------------------------------------------

-- |
--
-- >>> regexDoc $ REString "True"
-- \mathop{\mathord{"}\mathtt{True}\mathord{"}}
--
-- Note: we don't simplify regexps yet:
--
-- >>> regexDoc $ REString "foo" <> REString "bar"
-- \mathop{\mathord{"}\mathtt{foo}\mathord{"}}\mathop{\mathord{"}\mathtt{bar}\mathord{"}}
--
regexDoc :: Regex RTerm -> PP.Doc
regexDoc = go termDoc 0 where
go :: (a -> PP.Doc) -> Int -> Regex a -> PP.Doc
go f d (REAppend rs) = parensIf (d > 2) $ PP.hcat (map (go f 2) rs)
go f d (REUnion rs) = parensIf (d > 1) $ PP.hcat (PP.punctuate (PP.text "\\mid") (map (go f 1) rs))

go f d (REMunch sep r) = parensIf (d > 3) $
PP.text "{" <<>> go f 3 r <<>> PP.text "}^\\ast_{" <<>> go f 0 sep <<>> PP.text "}"
go f d (REMunch1 sep r) = parensIf (d > 3) $
PP.text "{" <<>> go f 3 r <<>> PP.text "}^+_{" <<>> go f 0 sep <<>> PP.text "}"
go f d (REOpt r) = parensIf (d > 3) $
PP.text "{" <<>> go f 3 r <<>> PP.text "}^?"

go _ _ (REString s) = PP.text "\\mathop{\\mathord{\"}\\mathtt{" <<>> PP.hcat (map charDoc s) <<>> PP.text "}\\mathord{\"}}"
go _ _ (RECharSet cs) = charsetDoc cs

go _ _ RESpaces = "\\circ"
go _ _ RESpaces1 = "\\bullet"

go f _ (REVar a) = f a
go f d (RELet n _ r) = go (maybe (terminalDoc n) f) d r
go _ _ (RERec n _) = terminalDoc n

go _ _ RETodo = PP.text "\\mathsf{\\color{red}{TODO}}"

parensIf :: Bool -> PP.Doc -> PP.Doc
parensIf True d = PP.text "\\left(" <<>> d <<>> PP.text "\\right)"
parensIf False d = d

termDoc :: RTerm -> PP.Doc
termDoc RHaskellString = terminalDoc "hs-string"
termDoc RUnqualName = terminalDoc "unqual-name"

terminalDoc :: String -> PP.Doc
terminalDoc s = PP.text "\\mathop{\\mathit{" <<>> PP.hcat (map charDoc s) <<>> PP.text "}}"

charDoc :: Char -> PP.Doc
charDoc ' ' = PP.text " "
charDoc '{' = PP.text "\\{"
charDoc '}' = PP.text "\\}"
charDoc c
| isAlphaNum c = PP.char c
| otherwise = PP.text ("\\text{" ++ c : "}")

inquotes :: PP.Doc -> PP.Doc
inquotes d = "\\mathop{\\mathord{\"}" <<>> d <<>> "\\mathord{\"}}"

charsetDoc :: ACS.AnsiCharSet -> PP.Doc
charsetDoc acs
| acs == csAlphaNum = terminalDoc "alpha-num"
| acs == csNotSpace = terminalDoc "not-space"
| acs == csNotSpaceOrComma = terminalDoc "not-space-nor-comma"
charsetDoc acs = case ACS.ranges acs of
[] -> PP.brackets PP.empty
[(x,y)] | x == y -> inquotes (charDoc x)
rs -> PP.brackets $ PP.hcat $ map rangeDoc rs
where
rangeDoc :: (Char, Char) -> PP.Doc
rangeDoc (x, y) | x == y = inquotes (charDoc x)
| otherwise = inquotes (charDoc x) <<>> PP.char '-' <<>> inquotes (charDoc y)

-------------------------------------------------------------------------------
-- Generation
-------------------------------------------------------------------------------

-- | Generate an example string.
generate
:: (Monad m, Applicative m)
=> (Int -> Int -> m Int) -- ^ generate integer in range
-> (a -> m String) -- ^ generate variables
-> Regex a -- ^ regex
-> m String -- ^ an example string.
generate rnd f (REAppend rs) = do
xs <- traverse (generate rnd f) rs
return (concat xs)
generate rnd f (REUnion rs) = do
n <- rnd 0 (length rs - 1)
generate rnd f (rs !! n)
generate rnd f (REMunch sep r) = do
n <- rnd 0 5
xs <- traverse (generate rnd f) (intersperse sep (replicate n r))
return (concat xs)
generate rnd f (REMunch1 sep r) = do
n <- rnd 1 5
xs <- traverse (generate rnd f) (intersperse sep (replicate n r))
return (concat xs)
generate rnd f (REOpt r) = do
n <- rnd 0 2
case n of
0 -> return ""
_ -> generate rnd f r
generate _ _ (REString str) = return str
generate rnd _ (RECharSet cs) = return <$> generateCS rnd cs
generate rnd _ RESpaces1 = (\n -> replicate n ' ') <$> rnd 1 3
generate rnd _ RESpaces = (\n -> replicate n ' ') <$> rnd 0 3

generate _ f (REVar x) = f x
generate _ _ (RELet _ _ _) = error "generate let"
generate _ _ (RERec _ _) = error "generate rec"
generate _ _ RETodo = return "TODO"

generateCS
:: Monad m
=> (Int -> Int -> m Int) -- ^ generate integer in range
-> ACS.AnsiCharSet
-> m Char
generateCS rnd asc = do
n <- rnd 0 (ACS.size asc - 1)
return (ACS.toList asc !! n)
8 changes: 6 additions & 2 deletions Cabal/Distribution/ModuleName.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ module Distribution.ModuleName (
import Distribution.Compat.Prelude
import Prelude ()

import Distribution.FieldGrammar.Described
import Distribution.Parsec
import Distribution.Pretty
import Distribution.Utils.ShortText (ShortText, fromShortText, toShortText)
import System.FilePath (pathSeparator)
import Distribution.Utils.ShortText (ShortText, fromShortText, toShortText)
import System.FilePath (pathSeparator)

import qualified Distribution.Compat.CharParsing as P
import qualified Text.PrettyPrint as Disp
Expand Down Expand Up @@ -57,6 +58,9 @@ instance Parsec ModuleName where
cs <- P.munch validModuleChar
return (c:cs)

instance Described ModuleName where
describe _ = RETodo

validModuleChar :: Char -> Bool
validModuleChar c = isAlphaNum c || c == '_' || c == '\''

Expand Down
Loading

0 comments on commit af041d0

Please sign in to comment.