git.iko.soy/haskell-generate
on master help sign in
Project import
commit8e03f47c5bbe8f0d837378dd85ced473e005ee1a authorBenno Fünfstück <benno.fuenfstueck@gmail.com> dateSun 22 Dec 2013, 13:34 (13 years ago)
14 files changed+749
.ghci+1 .gitignore+15 .travis.yml+23 .vim.custom+31 LICENSE+30 README.md+6 Setup.hs+69 haskell-generate.cabal+55 src/Language/Haskell/Generate.hs+39 src/Language/Haskell/Generate/Base.hs+204 src/Language/Haskell/Generate/PreludeDef.hs+94 src/Language/Haskell/Generate/TH.hs+52 tests/doctests.hsc+74 travis/script.sh+56
.ghcinew file+1
@@ -0,0 +1 @@
1+:set -isrc -idist/build/autogen -optP-include -optPdist/build/autogen/cabal_macros.h
.gitignorenew file+15
@@ -0,0 +1,15 @@
1+dist
2+docs
3+wiki
4+TAGS
5+tags
6+wip
7+.DS_Store
8+.*.swp
9+.*.swo
10+*.o
11+*.hi
12+*~
13+*#
14+.cabal-sandbox
15+cabal.sandbox.config
.travis.ymlnew file+23
@@ -0,0 +1,23 @@
1+env:
2+ - GHCVER=7.4.2 CABALVER=1.16
3+ - GHCVER=7.6.3 CABALVER=1.18
4+ - GHCVER=head CABALVER=1.18
5+
6+before_install:
7+ - sudo add-apt-repository -y ppa:hvr/ghc
8+ - sudo apt-get update
9+ - sudo apt-get install cabal-install-$CABALVER ghc-$GHCVER happy hlint
10+ - export PATH=/opt/ghc/$GHCVER/bin:$PATH
11+
12+install:
13+ - cabal-$CABALVER update
14+ - cabal-$CABALVER install --only-dependencies --enable-tests --enable-benchmarks -j
15+
16+script:
17+ - travis/script.sh
18+ - hlint src
19+
20+matrix:
21+ allow_failures:
22+ - env: GHCVER=head CABALVER=1.18
23+ fast_finish: true
.vim.customnew file+31
@@ -0,0 +1,31 @@
1+" Add the following to your .vimrc to automatically load this on startup
2+
3+" if filereadable(".vim.custom")
4+" so .vim.custom
5+" endif
6+
7+function StripTrailingWhitespace()
8+ let myline=line(".")
9+ let mycolumn = col(".")
10+ silent %s/ *$//
11+ call cursor(myline, mycolumn)
12+endfunction
13+
14+" enable syntax highlighting
15+syntax on
16+
17+" search for the tags file anywhere between here and /
18+set tags=TAGS;/
19+
20+" highlight tabs and trailing spaces
21+set listchars=tab:‗‗,trail:‗
22+set list
23+
24+" f2 runs hasktags
25+map <F2> :exec ":!hasktags -x -c --ignore src"<CR><CR>
26+
27+" strip trailing whitespace before saving
28+" au BufWritePre *.hs,*.markdown silent! cal StripTrailingWhitespace()
29+
30+" rebuild hasktags after saving
31+au BufWritePost *.hs silent! :exec ":!hasktags -x -c --ignore src"
LICENSEnew file+30
@@ -0,0 +1,30 @@
1+Copyright 2013 Benno Fünfstück
2+
3+All rights reserved.
4+
5+Redistribution and use in source and binary forms, with or without
6+modification, are permitted provided that the following conditions
7+are met:
8+
9+1. Redistributions of source code must retain the above copyright
10+ notice, this list of conditions and the following disclaimer.
11+
12+2. Redistributions in binary form must reproduce the above copyright
13+ notice, this list of conditions and the following disclaimer in the
14+ documentation and/or other materials provided with the distribution.
15+
16+3. Neither the name of the author nor the names of his contributors
17+ may be used to endorse or promote products derived from this software
18+ without specific prior written permission.
19+
20+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
21+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
24+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30+POSSIBILITY OF SUCH DAMAGE.
README.mdnew file+6
@@ -0,0 +1,6 @@
1+haskell-generate
2+====================
3+
4+[![Build Status](https://secure.travis-ci.org/bennofs/haskell-generate.png?branch=master)](http://travis-ci.org/bennofs/haskell-generate)
5+
6+
Setup.hsnew file+69
@@ -0,0 +1,69 @@
1+{-# OPTIONS_GHC -Wall #-}
2+module Main (main) where
3+
4+import Data.IORef
5+import Data.List ( nub )
6+import Data.Version ( showVersion )
7+import Distribution.Package ( PackageName(PackageName), PackageId, InstalledPackageId, packageVersion, packageName )
8+import Distribution.PackageDescription ( PackageDescription(), TestSuite(..), hsSourceDirs, libBuildInfo, buildInfo)
9+import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )
10+import Distribution.Simple.BuildPaths ( autogenModulesDir )
11+import Distribution.Simple.LocalBuildInfo ( withLibLBI, withTestLBI, withExeLBI, ComponentLocalBuildInfo(), LocalBuildInfo(), componentPackageDeps )
12+import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), fromFlag, buildDistPref, defaultDistPref, fromFlagOrDefault )
13+import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose )
14+import Distribution.Verbosity ( Verbosity )
15+import System.Directory ( canonicalizePath )
16+import System.FilePath ( (</>) )
17+
18+main :: IO ()
19+main = defaultMainWithHooks simpleUserHooks
20+ { buildHook = \pkg lbi hooks flags -> do
21+ generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi flags
22+ buildHook simpleUserHooks pkg lbi hooks flags
23+ }
24+
25+-- Very ad-hoc implementation of difference lists
26+singletonDL :: a -> [a] -> [a]
27+singletonDL = (:)
28+
29+emptyDL :: [a] -> [a]
30+emptyDL = id
31+
32+appendDL :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
33+appendDL x y = x . y
34+
35+generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> BuildFlags -> IO ()
36+generateBuildModule verbosity pkg lbi flags = do
37+ let dir = autogenModulesDir lbi
38+ createDirectoryIfMissingVerbose verbosity True dir
39+ withTestLBI pkg lbi $ \suite suitelbi -> do
40+ srcDirs <- mapM canonicalizePath $ hsSourceDirs $ testBuildInfo suite
41+ distDir <- canonicalizePath $ fromFlagOrDefault defaultDistPref $ buildDistPref flags
42+
43+ depsVar <- newIORef emptyDL
44+ withLibLBI pkg lbi $ \lib liblbi ->
45+ modifyIORef depsVar $ appendDL . singletonDL $ depsEntry (libBuildInfo lib) liblbi suitelbi
46+ withExeLBI pkg lbi $ \exe exelbi ->
47+ modifyIORef depsVar $ appendDL . singletonDL $ depsEntry (buildInfo exe) exelbi suitelbi
48+ deps <- fmap ($ []) $ readIORef depsVar
49+
50+ rewriteFile (map fixchar $ dir </> "Build_" ++ testName suite ++ ".hs") $ unlines
51+ [ "module Build_" ++ map fixchar (testName suite) ++ " where"
52+ , "getDistDir :: FilePath"
53+ , "getDistDir = " ++ show distDir
54+ , "getSrcDirs :: [FilePath]"
55+ , "getSrcDirs = " ++ show srcDirs
56+ , "deps :: [([FilePath], [String])]"
57+ , "deps = " ++ show deps
58+ ]
59+
60+ where
61+ formatdeps = map (formatone . snd)
62+ formatone p = case packageName p of
63+ PackageName n -> n ++ "-" ++ showVersion (packageVersion p)
64+ depsEntry targetbi targetlbi suitelbi = (hsSourceDirs targetbi, formatdeps $ testDeps targetlbi suitelbi)
65+ fixchar '-' = '_'
66+ fixchar c = c
67+
68+testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]
69+testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys
haskell-generate.cabalnew file+55
@@ -0,0 +1,55 @@
1+name: haskell-generate
2+version: 0.1
3+license: BSD3
4+cabal-version: >= 1.10
5+license-file: LICENSE
6+author: Benno Fünfstück
7+maintainer: Benno Fünfstück <benno.fuenfstueck@gmail.com>
8+stability: experimental
9+homepage: http://github.com/bennofs/haskell-generate/
10+bug-reports: http://github.com/bennofs/haskell-generate/issues
11+copyright: Copyright (C) 2013 Benno Fünfstück
12+synopsis: haskell-generate
13+description: haskell-generate
14+build-type: Custom
15+
16+extra-source-files:
17+ .ghci
18+ .gitignore
19+ .travis.yml
20+ .vim.custom
21+ README.md
22+
23+source-repository head
24+ type: git
25+ location: https://github.com/bennofs/haskell-generate.git
26+
27+library
28+ hs-source-dirs: src
29+ default-language: Haskell2010
30+ ghc-options: -Wall
31+ build-depends:
32+ base >= 4.4 && < 5
33+ , transformers
34+ , haskell-src-exts
35+ , containers
36+ , template-haskell
37+ exposed-modules:
38+ Language.Haskell.Generate
39+ Language.Haskell.Generate.Base
40+ Language.Haskell.Generate.TH
41+ Language.Haskell.Generate.PreludeDef
42+
43+test-suite doctests
44+ type: exitcode-stdio-1.0
45+ main-is: doctests.hs
46+ default-language: Haskell2010
47+ build-depends:
48+ base
49+ , directory >= 1.0
50+ , doctest >= 0.9.1
51+ , filepath
52+ ghc-options: -Wall -threaded
53+ if impl(ghc<7.6.1)
54+ ghc-options: -Werror
55+ hs-source-dirs: tests
src/Language/Haskell/Generate.hsnew file+39
@@ -0,0 +1,39 @@
1+module Language.Haskell.Generate
2+ ( -- $intro
3+ module X
4+ ) where
5+
6+import Language.Haskell.Generate.Base as X
7+import Language.Haskell.Generate.PreludeDef as X
8+
9+{- $intro
10+ This library allows you to generate type-correct haskell expressions. To use it, you need to import this module:
11+
12+> import Language.Haskell.Generate
13+
14+ This will import the methods for generating haskell expressions and also a list of predefined expressions consisting of functions from the haskell Prelude
15+ that you can use to build your own expressions. For example, here is an expression that reads the file 'names':
16+
17+> readNames :: ExpG (IO String)
18+> readNames = readFile' <>$ expr "names"
19+
20+ First, look at the type of readNames. @'ExpG'@ is a data type provided by haskell-generate for haskell expressions. We want to generate an expression
21+ that returns an IO String, so the type of readNames is @ExpG (IO String)@.
22+ @readFile'@ is an expression for the haskell readFile function. You can use almost any Prelude function in your expression by adding an apostrophe to
23+ the end of the function name.
24+ We use (<>$) to generate a expression that applies 'readFile' to the expression "names". @'expr'@ automagically generates a literal expression.
25+
26+ Now that we have an expression to read the @names@ file, we can generate a module with it:
27+
28+> myModule :: ModuleG
29+> myModule = do
30+> readNamesDecl <- addDecl (Ident "readNames") readNames
31+> return $ Just [exportFun readNamesDecl]
32+
33+
34+
35+> main :: IO ()
36+> main = do
37+> putStrLn $ generateModule myModule "FooBar"
38+
39+-}
src/Language/Haskell/Generate/Base.hsnew file+204
@@ -0,0 +1,204 @@
1+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE TypeFamilies #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE UndecidableInstances #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+module Language.Haskell.Generate.Base
7+ ( ExpM(..), ExpG, ExpType
8+ , runExpM, newName
9+ , useValue, useCon, useVar
10+ , caseE
11+ , applyE, applyE2, applyE3, applyE4, applyE5, applyE6
12+ , (<>$)
13+ , GenExp(..)
14+ , ModuleM(..)
15+ , ModuleG
16+ , FunRef(..)
17+ , Name(..)
18+ , exportFun
19+ , addDecl
20+ , runModuleM
21+ , generateModule
22+ )
23+ where
24+
25+import Control.Applicative
26+import Control.Monad
27+import Control.Monad.Trans.Class
28+import Control.Monad.Trans.State
29+import Control.Monad.Trans.Writer
30+import qualified Data.Set as S
31+import Language.Haskell.Exts.Pretty
32+import Language.Haskell.Exts.SrcLoc
33+import Language.Haskell.Exts.Syntax
34+
35+--------------------------------------------------------------------------------
36+-- Generate expressions
37+
38+-- | A ExpM is a monad used to track the imports that are needed for a given expression. Usually, you don't have to use
39+-- this type directly, but use combinators to combine several ExpM into bigger expressions. The t type parameter tracks
40+-- the type of the expression, so you don't accidently build expression that don't type check.
41+newtype ExpM t a = ExpM { unExpM :: StateT Integer (Writer (S.Set ModuleName)) a } deriving (Functor, Applicative, Monad)
42+
43+-- | The ExpG type is a ExpM computation that returns an expression. Usually, this is the end result of a function generating
44+-- a haskell expression
45+type ExpG t = ExpM t Exp
46+
47+-- | Evaluate a ExpM action, returning the needed modules and the value.
48+runExpM :: ExpM t a -> (a, S.Set ModuleName)
49+runExpM (ExpM expt) = runWriter $ evalStateT expt 0
50+
51+unsafeCoerceE :: ExpM t a -> ExpM t' a
52+unsafeCoerceE (ExpM x) = ExpM x
53+
54+-- | Generate a case expression.
55+caseE :: ExpG x -> [(Pat, ExpG t)] -> ExpG t
56+caseE v alt = do
57+ v' <- unsafeCoerceE v
58+ alt' <- mapM (\(p,a) -> fmap (\a' -> Alt noLoc p (UnGuardedAlt a') (BDecls [])) a) alt
59+ return $ Case v' alt'
60+
61+-- | Import a function from a module. This function is polymorphic in the type of the resulting expression,
62+-- you should probably only use this function to define type-restricted specializations.
63+--
64+-- Example:
65+--
66+-- > addInt :: ExpG (Int -> Int -> Int) -- Here we restricted the type to something sensible
67+-- > addInt = useValue "Prelude" $ Symbol "+"
68+--
69+useValue :: String -> Name -> ExpG a
70+useValue md name = ExpM $ do
71+ lift $ tell $ S.singleton $ ModuleName md
72+ return $ Var $ Qual (ModuleName md) name
73+
74+-- | Import a value constructor from a module. Returns the qualified name of the constructor.
75+useCon :: String -> Name -> ExpM t QName
76+useCon md name = ExpM $ do
77+ lift $ tell $ S.singleton $ ModuleName md
78+ return $ Qual (ModuleName md) name
79+
80+-- | Use the value of a variable with the given name.
81+useVar :: Name -> ExpG t
82+useVar name = return $ Var $ UnQual name
83+
84+-- | Generate a new unique variable name with the given prefix. Note that this new variable name
85+-- is only unique relative to other variable names generated by this function.
86+newName :: String -> ExpM t Name
87+newName pref = ExpM $ do
88+ i <- get <* modify succ
89+ return $ Ident $ pref ++ show i
90+
91+-- | This type family can be used to get the type associated with some expression.
92+type family ExpType a :: *
93+type instance ExpType (ExpM t a) = t
94+
95+-- | Generate a expression from a haskell value.
96+class GenExp t where
97+ type GenExpType t :: *
98+
99+ -- | This function generates the haskell expression from the given haskell value.
100+ expr :: t -> ExpG (GenExpType t)
101+
102+instance GenExp (ExpG a) where
103+ type GenExpType (ExpG a) = a
104+ expr = id
105+
106+instance GenExp Char where
107+ type GenExpType Char = Char
108+ expr = return . Lit . Char
109+
110+instance GenExp Integer where
111+ type GenExpType Integer = Integer
112+ expr = return . Lit . Int
113+
114+instance GenExp Rational where
115+ type GenExpType Rational = Rational
116+ expr = return . Lit . Frac
117+
118+instance GenExp a => GenExp [a] where
119+ type GenExpType [a] = [GenExpType a]
120+ expr = ExpM . fmap List . mapM (unExpM . expr)
121+
122+instance GenExp x => GenExp (ExpG a -> x) where
123+ type GenExpType (ExpG a -> x) = a -> GenExpType x
124+ expr f = do
125+ pvarName <- newName "pvar_"
126+ body <- unsafeCoerceE $ expr $ f $ return $ Var $ UnQual pvarName
127+ return $ Lambda noLoc [PVar pvarName] body
128+
129+--------------------------------------------------------------------------------
130+-- Apply functions
131+
132+-- | Apply a function in a haskell expression to a value.
133+applyE :: ExpG (a -> b) -> ExpG a -> ExpG b
134+applyE a b = unsafeCoerceE $ liftM (foldl1 App) $ sequence [ce a,ce b]
135+ where ce = unsafeCoerceE
136+
137+-- | Operator for 'applyE'.
138+(<>$) :: ExpG (a -> b) -> ExpG a -> ExpG b
139+(<>$) = applyE
140+
141+infixl 1 <>$
142+
143+-- | ApplyE for 2 arguments
144+applyE2 :: ExpG (a -> b -> c) -> ExpG a -> ExpG b -> ExpG c
145+applyE2 a b c = unsafeCoerceE $ liftM (foldl1 App) $ sequence [ce a,ce b,ce c]
146+ where ce = unsafeCoerceE
147+
148+-- | Apply a function to 3 arguments
149+applyE3 :: ExpG (a -> b -> c -> d) -> ExpG a -> ExpG b -> ExpG c -> ExpG d
150+applyE3 a b c d = unsafeCoerceE $ liftM (foldl1 App) $ sequence [ce a,ce b,ce c,ce d]
151+ where ce = unsafeCoerceE
152+
153+-- | Apply a function to 4 arguments
154+applyE4 :: ExpG (a -> b -> c -> d -> e) -> ExpG a -> ExpG b -> ExpG c -> ExpG d -> ExpG e
155+applyE4 a b c d e = unsafeCoerceE $ liftM (foldl1 App) $ sequence [ce a,ce b,ce c,ce d,ce e]
156+ where ce = unsafeCoerceE
157+
158+-- | Apply a function to 5 arguments
159+applyE5 :: ExpG (a -> b -> c -> d -> e -> f) -> ExpG a -> ExpG b -> ExpG c -> ExpG d -> ExpG e -> ExpG f
160+applyE5 a b c d e f = unsafeCoerceE $ liftM (foldl1 App) $ sequence [ce a,ce b,ce c,ce d,ce e,ce f]
161+ where ce = unsafeCoerceE
162+
163+-- | Apply a function to 6 arguments
164+applyE6 :: ExpG (a -> b -> c -> d -> e -> f -> g) -> ExpG a -> ExpG b -> ExpG c -> ExpG d -> ExpG e -> ExpG f -> ExpG g
165+applyE6 a b c d e f g = unsafeCoerceE $ liftM (foldl1 App) $ sequence [ce a,ce b,ce c,ce d,ce e,ce f,ce g]
166+ where ce = unsafeCoerceE
167+
168+--------------------------------------------------------------------------------
169+-- Generate modules
170+
171+-- | A module keeps track of the needed imports, but also has a list of declarations in it.
172+newtype ModuleM a = ModuleM (Writer (S.Set ModuleName, [Decl]) a) deriving (Functor, Applicative, Monad)
173+
174+-- | This is the resulting type of a function generating a module. It is a ModuleM action returning the export list.
175+type ModuleG = ModuleM (Maybe [ExportSpec])
176+
177+-- | A reference to a function. With a reference to a function, you can apply it (by lifting it into ExprT using 'expr') to some value
178+-- or export it using 'exportFun'.
179+data FunRef t = FunRef Name
180+
181+instance GenExp (FunRef t) where
182+ type GenExpType (FunRef t) = t
183+ expr (FunRef n) = return $ Var $ UnQual n
184+
185+-- | Generate a ExportSpec for a given function item.
186+exportFun :: FunRef t -> ExportSpec
187+exportFun (FunRef name) = EVar (UnQual name)
188+
189+-- | Add a declaration to the module. Return a reference to it that can be used to either apply the function to some values or export it.
190+addDecl :: Name -> ExpG t -> ModuleM (FunRef t)
191+addDecl name e = ModuleM $ do
192+ let (body, mods) = runExpM e
193+ tell (mods, [FunBind [Match noLoc name [] Nothing (UnGuardedRhs body) $ BDecls []]])
194+ return $ FunRef name
195+
196+-- | Extract the Module from a module generator.
197+runModuleM :: ModuleG -> String -> Module
198+runModuleM (ModuleM act) name =
199+ Module noLoc (ModuleName name) [] Nothing export (map (\md -> ImportDecl noLoc md True False Nothing Nothing Nothing) $ S.toList imps) decls
200+ where (export, (imps, decls)) = runWriter act
201+
202+-- | Generate the source code for a module.
203+generateModule :: ModuleG -> String -> String
204+generateModule = fmap prettyPrint . runModuleM
src/Language/Haskell/Generate/PreludeDef.hsnew file+94
@@ -0,0 +1,94 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE KindSignatures #-}
4+{-# LANGUAGE DataKinds #-}
5+{-# LANGUAGE TemplateHaskell #-}
6+module Language.Haskell.Generate.PreludeDef where
7+
8+import Language.Haskell.Exts.Syntax
9+import Language.Haskell.Generate.Base
10+import Language.Haskell.Generate.TH
11+
12+--------------------------------------------------------------------------------
13+-- Basic functions
14+
15+fmap concat $ mapM declareFunction
16+ [ 'maybe
17+ , 'either
18+ , 'fst
19+ , 'snd
20+ , 'curry
21+ , 'uncurry
22+ , 'not
23+ , 'negate, 'abs, 'signum, 'fromInteger
24+ , 'quot, 'rem, 'div, 'mod, 'quotRem, 'divMod, 'toInteger
25+ , 'recip, 'fromRational
26+ , 'pi, 'exp, 'log, 'sqrt, 'logBase, 'sin, 'cos, 'tan
27+ , 'asin, 'acos, 'atan, 'sinh, 'cosh, 'tanh, 'asinh, 'acosh, 'atanh
28+ , 'properFraction, 'truncate, 'round, 'ceiling, 'floor
29+ , 'floatRadix, 'floatDigits, 'floatRange, 'decodeFloat
30+ , 'encodeFloat, 'exponent, 'significand, 'scaleFloat, 'isNaN
31+ , 'isInfinite, 'isDenormalized, 'isIEEE, 'isNegativeZero, 'atan2
32+ , 'subtract, 'even, 'odd, 'gcd, 'lcm
33+ , 'fromIntegral, 'realToFrac
34+ , 'fmap, 'return, 'mapM, 'mapM_
35+ , 'id, 'const, 'flip, 'until, 'asTypeOf, 'undefined
36+ , 'map, 'filter, 'head, 'last, 'tail, 'init, 'null, 'length
37+ , 'reverse, 'foldl, 'foldr, 'foldl1, 'foldr1, 'and, 'or, 'any, 'all, 'sum, 'product
38+ , 'concat, 'concatMap, 'maximum, 'minimum
39+ , 'scanl, 'scanr, 'scanl1, 'scanr1
40+ , 'iterate, 'repeat, 'replicate, 'cycle
41+ , 'take, 'drop, 'splitAt, 'takeWhile, 'dropWhile, 'span, 'break
42+ , 'elem, 'notElem, 'lookup
43+ , 'zip, 'zip3, 'zipWith, 'zipWith3, 'unzip, 'unzip3
44+ , 'lines, 'words, 'unlines, 'unwords
45+ , 'read, 'show
46+ , 'putChar, 'putStr, 'putStrLn, 'print
47+ , 'getChar, 'getLine, 'getContents, 'interact
48+ , 'readFile, 'writeFile, 'appendFile, 'readIO, 'readLn
49+ , 'Just, 'Left, 'Right, 'False, 'True, 'Nothing
50+ ]
51+
52+fmap concat $ mapM declareNamedSymbol
53+ [ ('(.), "dot'")
54+ , ('(+), "add'")
55+ , ('(*), "mult'")
56+ , ('(/), "divide'")
57+ , ('(**), "floatPow'")
58+ , ('(>>=), "bind'")
59+ , ('(>>), "then'")
60+ , ('(++), "append'")
61+ , ('(!!), "index'")
62+ , ('(==), "equal'")
63+ ]
64+
65+(<>.) :: ExpG (b -> c) -> ExpG (a -> b) -> ExpG (a -> c)
66+(<>.) a b = dot' <>$ a <>$ b
67+
68+tuple0 :: ExpG ()
69+tuple0 = return $ Var $ Special UnitCon
70+
71+tuple2 :: ExpG (a -> b -> (a,b))
72+tuple2 = return $ Var $ Special $ TupleCon Boxed 2
73+
74+tuple3 :: ExpG (a -> b -> c -> (a,b,c))
75+tuple3 = return $ Var $ Special $ TupleCon Boxed 3
76+
77+tuple4 :: ExpG (a -> b -> c -> d -> (a,b,c,d))
78+tuple4 = return $ Var $ Special $ TupleCon Boxed 4
79+
80+tuple5 :: ExpG (a -> b -> c -> d -> (a,b,c,d,e))
81+tuple5 = return $ Var $ Special $ TupleCon Boxed 5
82+
83+cons :: ExpG (a -> [a] -> [a])
84+cons = return $ Var $ Special Cons
85+
86+instance Num t => Num (ExpG t) where
87+ a + b = add' <>$ a <>$ b
88+ a - b = flip' <>$ subtract' <>$ a <>$ b
89+ a * b = mult' <>$ a <>$ b
90+ negate a = negate' <>$ a
91+ abs a = abs' <>$ a
92+ fromInteger a = return $ Lit $ Int a
93+ signum a = signum' <>$ a
94+
src/Language/Haskell/Generate/TH.hsnew file+52
@@ -0,0 +1,52 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+module Language.Haskell.Generate.TH
3+ ( -- | This module provides functions for automagically generating type-safe ExpG definitions from functions. For an example on how to use this,
4+ -- you can look at the 'Language.Haskell.Generate.Prelude' module.
5+ declareFunction
6+ , declareNamedSymbol
7+ , declareNamedFunction
8+ , declareNamedThing
9+ ) where
10+
11+import Data.Char
12+import Language.Haskell.Exts.Syntax hiding (Name)
13+import Language.Haskell.Generate.Base hiding (Name)
14+import Language.Haskell.TH
15+import Language.Haskell.TH.Syntax
16+
17+-- | Make a ExpG for the given function, using the given name for the definition.
18+declareNamedFunction :: (Name, String) -> DecsQ
19+declareNamedFunction (func, name) = declareNamedThing (func, name, 'Ident)
20+
21+-- | Make a ExpG for some thing, using the given name for the definition. The third tuple element
22+-- specifies the constructor to use for constructing the Name. This can either be @'Symbol@ (for symbols)
23+-- or @'Ident@ (for functions).
24+declareNamedThing :: (Name, String, Name) -> DecsQ
25+declareNamedThing (thing, name, thingClass) = do
26+ info <- reify thing
27+ typ <- case info of
28+ VarI _ t _ _ -> return t
29+ ClassOpI _ t _ _ -> return t
30+ DataConI _ t _ _ -> return t
31+ _ -> fail $ "Not a function: " ++ nameBase thing
32+ md <- maybe (fail "No module name for function!") return $ nameModule thing
33+ sequence
34+ [ sigD (mkName name) $ return $ overQuantifiedType (ConT ''ExpG `AppT`) typ
35+ , funD (mkName name) $ return $ flip (clause []) [] $ normalB
36+ [| useValue $(lift md) $ $(conE thingClass) $(lift $ nameBase thing) |]
37+ ]
38+
39+ where overQuantifiedType f (ForallT bnds ctx t) = ForallT bnds ctx $ overQuantifiedType f t
40+ overQuantifiedType f x = f x
41+
42+-- | Declare a symbol, using the given name for the definition.
43+declareNamedSymbol :: (Name, String) -> DecsQ
44+declareNamedSymbol (func, name) = declareNamedThing (func, name, 'Symbol)
45+
46+-- | Declare a function. The name of the definition will be the name of the function with an added apostrophe. (Example: declareFunction 'add generates
47+-- a definition with the name add').
48+declareFunction :: Name -> DecsQ
49+declareFunction func = declareNamedFunction (func, funcName ++ "'")
50+ where funcName = case nameBase func of
51+ (h:t) -> toLower h:t
52+ x -> x
tests/doctests.hscnew file+74
@@ -0,0 +1,74 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ForeignFunctionInterface #-}
3+-----------------------------------------------------------------------------
4+-- Copyright : (C) 2012-13 Edward Kmett
5+-- License : BSD-style (see the file LICENSE)
6+-- Maintainer : Edward Kmett <ekmett@gmail.com>
7+-- Stability : provisional
8+-- Portability : portable
9+--
10+-- This module provides doctests for a project based on the actual versions
11+-- of the packages it was built with. It requires a corresponding Setup.lhs
12+-- to be added to the project
13+-----------------------------------------------------------------------------
14+module Main where
15+
16+import Build_doctests (deps)
17+import Control.Applicative
18+import Control.Monad
19+import Data.List
20+import System.Directory
21+import System.FilePath
22+import Test.DocTest
23+
24+##ifdef mingw32_HOST_ARCH
25+##ifdef i386_HOST_ARCH
26+##define USE_CP
27+import Control.Applicative
28+import Control.Exception
29+import Foreign.C.Types
30+foreign import stdcall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool
31+foreign import stdcall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt
32+##elif defined(x86_64_HOST_ARCH)
33+##define USE_CP
34+import Control.Applicative
35+import Control.Exception
36+import Foreign.C.Types
37+foreign import ccall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool
38+foreign import ccall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt
39+##endif
40+##endif
41+
42+-- | Run in a modified codepage where we can print UTF-8 values on Windows.
43+withUnicode :: IO a -> IO a
44+##ifdef USE_CP
45+withUnicode m = do
46+ cp <- c_GetConsoleCP
47+ (c_SetConsoleCP 65001 >> m) `finally` c_SetConsoleCP cp
48+##else
49+withUnicode m = m
50+##endif
51+
52+main :: IO ()
53+main = withUnicode $ forM_ deps $ \(dirs, dep) -> do
54+ putStrLn $ ":: Running doctests for source directories: " ++ intercalate " " dirs
55+ mapM getSources dirs >>= \sources -> doctest $
56+ "-idist/build/autogen"
57+ : "-optP-include"
58+ : "-optPdist/build/autogen/cabal_macros.h"
59+ : "-hide-all-packages"
60+ : map ("-package="++) dep
61+ ++ map ("-i"++) dirs
62+ ++ join sources
63+
64+getSources :: FilePath -> IO [FilePath]
65+getSources d = filter (isSuffixOf ".hs") <$> go d
66+ where
67+ go dir = do
68+ (dirs, files) <- getFilesAndDirectories dir
69+ (files ++) . concat <$> mapM go dirs
70+
71+getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])
72+getFilesAndDirectories dir = do
73+ c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir
74+ (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c
travis/script.shnew file+56
@@ -0,0 +1,56 @@
1+#!/usr/bin/env bash
2+
3+green='\e[0;32m'
4+red='\e[0;31m'
5+nc='\e[0m' # No Color
6+
7+function step {
8+ echo -e "${green}$1 ...${nc}"
9+ bash /dev/stdin || exit 1
10+}
11+
12+function step_suppress {
13+ echo -ne "${green}$1 ... ${nc}"
14+ tmp=$(mktemp)
15+ bash /dev/stdin &> $tmp && echo -e "${green}Done${nc}" || (
16+ echo -e "${red}Failed${nc}"
17+ echo "Output: "
18+ cat $tmp
19+ exit 1
20+ )
21+}
22+
23+step "Configuring project" << 'EOF'
24+ tmp=$(mktemp)
25+ cabal-$CABALVER configure --enable-tests --enable-benchmarks -v2 --ghc-options="-Wall -Werror" &> $tmp || (
26+ cat $tmp
27+ exit 1
28+ )
29+ echo "Using packages: "
30+ sed -nre 's/Dependency ([^ ]*) ==([^ :]*).*/\1 \2/p' $tmp | column -t | sed -e "s/^/ /"
31+EOF
32+
33+step "Building project" << EOF
34+ cabal-$CABALVER build
35+EOF
36+
37+step "Running tests" << EOF
38+ cabal-$CABALVER test
39+EOF
40+
41+step "Creating source distribution" << EOF
42+ cabal-$CABALVER check
43+ cabal-$CABALVER sdist # tests that a source-distribution can be generated
44+EOF
45+
46+step_suppress "Checking source distribution" << 'EOF'
47+ # The following scriptlet checks that the resulting source distribution can be built & installed
48+ SRC_TGZ=$(cabal-$CABALVER info . | awk '{print $2 ".tar.gz";exit}')
49+ cd dist/
50+ if [ -f "$SRC_TGZ" ]; then
51+ cabal-$CABALVER install "$SRC_TGZ"
52+ else
53+ echo "expected '$SRC_TGZ' not found"
54+ exit 1
55+ fi
56+EOF