packages feed

kure (empty) → 0.2

raw patch · 9 files changed

+602/−0 lines, 9 filesdep +basedep +containerssetup-changed

Dependencies added: base, containers

Files

+ LICENSE view
@@ -0,0 +1,25 @@+Copyright (c) 2006-2008 Andy Gill+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. The names of the authors may not be used to endorse or promote products+   derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ Language/KURE.hs view
@@ -0,0 +1,26 @@+-- |+-- Module: Language.KURE+-- Copyright: (c) 2006-2008 Andy Gill+-- License: BSD3+--+-- Maintainer: Andy Gill <andygill@ku.edu>+-- Stability: unstable+-- Portability: ghc+--+-- This is the main import module for KURE, which exports all the major components.+--+--++module Language.KURE +	( module Language.KURE.RewriteMonad+	, module Language.KURE.Translate+	, module Language.KURE.Rewrite+	, module Language.KURE.Combinators+	, module Language.KURE.Term+	) where++import Language.KURE.RewriteMonad+import Language.KURE.Translate+import Language.KURE.Rewrite+import Language.KURE.Combinators+import Language.KURE.Term
+ Language/KURE/Combinators.hs view
@@ -0,0 +1,141 @@+-- |+-- Module: Language.KURE.Combinators +-- Copyright: (c) 2006-2008 Andy Gill+-- License: BSD3+--+-- Maintainer: Andy Gill <andygill@ku.edu>+-- Stability: unstable+-- Portability: ghc+--+-- This module contains various combinators that use 'Translate' and 'Rewrite'. The convension is that+-- 'Translate' based combinators end with @T@, and 'Rewrite' based combinators end with @R@. Of course,+-- because 'Rewrite' is a type synomim of 'Translate', the 'Rewrite' functions also operate with on 'Translate',+-- and the 'Translate' functions operate with 'Rewrite'. ++module Language.KURE.Combinators +	(  -- * 'Translate' combinators+	  (<+)+	, (>->)+	, failT+	, (?)+	, readerT+	, getDecsT+	, mapDecsT+	, pureT+	, constT+	, concatT+	, -- * 'Rewrite' combinators+	  (.+)+	, (!->)+	, tryR+	, changedR+	, repeatR+	, acceptR+	, idR+	, failR+	) where +	+import Language.KURE.RewriteMonad	+import Language.KURE.Translate	+import Language.KURE.Rewrite	+import Data.Monoid++infixl 3 <+, >->, .+, !->+infixr 3 ?++-- Note: We use < for catching fail, . for catching id.++--------------------------------------------------------------------------------+-- The Translate combinators.++-- | like a catch, '<+' does the first translate , and if it fails, then does the second translate.	+(<+) :: (Monoid dec, Monad m) => Translate m dec a b -> Translate m dec a b -> Translate m dec a b+(<+) rr1 rr2 = translate $ \ e -> transparently $ apply rr1 e `catchM` (\ _ -> apply rr2 e)++-- | like a @;@ If the first translate succeeds, then do to the second translate after the first translate.+(>->) :: (Monoid dec, Monad m) => Translate m dec a b -> Translate m dec b c -> Translate m dec a c+(>->) rr1 rr2 = translate $ \ e -> transparently $ chainM (apply rr1 e) ( \ _i e2 -> apply rr2 e2)++-- | failing translation.+failT :: (Monad m, Monoid dec) => String -> Translate m dec a b+failT msg = translate $ \ _ -> failM msg++-- | Guarded translate.+(?) ::  (Monoid dec, Monad m) => Bool -> Translate m dec a b -> Translate m dec a b+(?) False _rr = failT "(False ?)"+(?) True   rr = rr++-- | look at the argument for the translation before choosing which translation to perform. +readerT :: (Monoid dec, Monad m) => (a -> Translate m dec a b) -> Translate m dec a b+readerT fn = translate $ \ expA -> transparently $ apply (fn expA) expA++-- | look at the @dec@ before choosing which translation to do.+getDecsT :: (Monad m, Monoid dec) => (dec -> Translate m dec a b) -> Translate m dec a b+getDecsT f = translate $ \ e -> transparently $+                                do dec <- getDecsM +                                   apply (f dec) e++-- | change the @dec@'s for a scoped translation.+mapDecsT :: (Monoid dec,Monad m) => (dec -> dec) -> Translate m dec a r -> Translate m dec a r+mapDecsT f_env rr = translate $ \ e -> mapDecsM f_env (apply rr e)++-- | 'pureT' promotes a function into an unfailable, non-identity 'Translate'.+pureT :: (Monad m,Monoid dec) => (a -> b) -> Translate m dec a b+pureT f = translate $ \ a -> return (f a)++-- | 'constT' always translates into an unfailable 'Translate' that returns the first argument.+constT :: (Monad m,Monoid dec) => b -> Translate m dec a b+constT = pureT . const++-- | 'concatT' composes a list of 'Translate' into a single 'Translate' which 'mconcat's its result.+concatT :: (Monad m,Monoid dec,Monoid r) => [Translate m dec a r] -> Translate m dec a r+concatT ts = translate $ \ e -> do+	rs <- sequence [ apply t e | t <- ts ]+	return (mconcat rs)+--------------------------------------------------------------------------------+-- The 'Rewrite' combinators.++-- | if the first rewrite is an identity, then do the second rewrite.+(.+) :: (Monoid dec, Monad m) => Rewrite m dec a -> Rewrite m dec a -> Rewrite m dec a+(.+) a b = a `wasId` (\ i -> if i then b else idR)++-- | if the first rewrite was /not/ an identity, then also do the second rewrite.+(!->) :: (Monoid dec, Monad m) => Rewrite m dec a -> Rewrite m dec a -> Rewrite m dec a +(!->) a b = a `wasId` (\ i -> if i then idR else b)++-- | catch a failing 'Rewrite', making it into an identity.+tryR :: (Monoid dec, Monad m) => Rewrite m dec a -> Rewrite m dec a+tryR s = s <+ idR++-- | if this is an identity rewrite, make it fail. To succeed, something must have changed.+changedR :: (Monoid dec,Monad m) => Rewrite m dec a -> Rewrite m dec a+changedR rr = rr .+ failR "unchanged"++-- | repeat a rewrite until it fails, then return the result before the failure.+repeatR :: (Monoid dec, Monad m) => Rewrite m dec a -> Rewrite m dec a+repeatR s = tryR (s >-> repeatR s) ++-- | look at the argument to a rewrite, and choose to be either a failure of trivial success.+acceptR :: (Monoid dec, Monad m) => (a -> Bool) -> Rewrite m dec a+acceptR fn = translate $ \  expA -> transparently $+                                    if fn expA +				    then return expA+				    else fail "accept failed"+++-- | identity rewrite.+idR :: (Monad m, Monoid dec) => Rewrite m dec exp+idR = rewrite $ \ e -> transparently $ return e++-- | failing rewrite.+failR :: (Monad m, Monoid dec) => String -> Rewrite m dec a+failR = failT+++--------------------------------------------------------------------------------+-- internal to this module.+wasId :: (Monoid dec, Monad m) => Rewrite m dec a -> (Bool -> Rewrite m dec a) -> Rewrite m dec a+wasId rr fn = translate $ \ e -> transparently $+	chainM (apply rr e)+	       (\ i e' -> apply (fn i) e')+
+ Language/KURE/Rewrite.hs view
@@ -0,0 +1,46 @@+-- |+-- Module: Language.KURE.Rewrite +-- Copyright: (c) 2006-2008 Andy Gill+-- License: BSD3+--+-- Maintainer: Andy Gill <andygill@ku.edu>+-- Stability: unstable+-- Portability: ghc+--+-- 'Rewrite' is a synonoym for a 'Translate' with the same source and target type.+-- This module contains the defintion of Rewrite, and some aliases for some translate functions that use +-- Rewrite rather than Translate.++module Language.KURE.Rewrite +	( Rewrite+	, rewrite+	, runRewrite+	) where++import Language.KURE.RewriteMonad+import Language.KURE.Translate+import Data.Monoid++-- | A 'Rewrite' is a 'Translate' that shares the same source and target type. Literally, +-- a 'Rewrite' provides the details about how to /re-write/ a specific type.++type Rewrite m dec exp = Translate m dec exp exp++-- | 'rewrite' is our primitive way of building a Rewrite,+--  where if the rewrite is successful it is automatically marked as a non-identity rewrite. +--+-- @rewrite $ \\ _ e -> return e@ /is not/ an identity rewrite. ++rewrite :: (Monoid dec, Monad m) => (exp1 -> RewriteM m dec exp1) -> Rewrite m dec exp1+rewrite = translate++-- | 'runRewrite' executes the rewrite, returning either a failure message,+-- or a success and the new parts of the environment.+++runRewrite :: (Monoid dec,Monad m) +	   => Rewrite m dec exp+	   -> dec +	   -> exp +	   -> m (Either String (exp,dec))+runRewrite = runTranslate
+ Language/KURE/RewriteMonad.hs view
@@ -0,0 +1,147 @@+-- |+-- Module: Language.KURE.RewriteMonad +-- Copyright: (c) 2006-2008 Andy Gill+-- License: BSD3+--+-- Maintainer: Andy Gill <andygill@ku.edu>+-- Stability: unstable+-- Portability: ghc+--+-- This is the definition of the monad inside KURE.++module Language.KURE.RewriteMonad +        ( RewriteM      -- abstract+        , RewriteStatusM(..)+        , runRewriteM+        , failM+        , catchM+        , chainM+        , liftQ+        , markM+        , transparently+        , getDecsM+        , mapDecsM+        ) where +++import Control.Monad+import Data.Monoid++------------------------------------------------------------------------------++data RewriteM m dec exp = +   RewriteM { -- | 'runRewriteM' runs the 'RewriteM' monad, returning a status.+              runRewriteM :: dec -> m (RewriteStatusM dec exp) +             }++data IdStatus = EmptyId | IsId | NotId++instance Monoid IdStatus where+        mempty = EmptyId+        +        mappend EmptyId y = y+        mappend x EmptyId = x+        mappend IsId IsId = IsId+        mappend _  _      = NotId++data RewriteStatusM dec exp+     = RewriteReturnM exp !(Maybe dec) !IdStatus      -- ^ a regular success+     | RewriteFailureM String           -- ^ a real failure+--     | RewriteIdM exp                 -- ^ identity marker on a value+++-- TWO possible ways of thinking about rewriting:++-- C1 (e1) => C2 (C1 (e2)) => C3 (C2 (C1 (e3))) -- matches the *writer* like status+-- C1 (e1) => C1 (C2 (e2)) => C1 (C2 (C3 (e3))) -- will require mergeing??++instance (Monoid dec,Monad m) => Monad (RewriteM m dec) where+   return e = RewriteM $ \ _ -> return $ RewriteReturnM e Nothing EmptyId+   (RewriteM m) >>= k = RewriteM $ \ dec -> do+           r <- m dec+           case r of+             RewriteReturnM r1 ds ids -> do+                r2 <- runRewriteM (k r1) dec+                return $ case r2 of+                 RewriteReturnM e' ds' ids' -> RewriteReturnM e' (ds' `mappend` ds) (ids' `mappend` ids)+                 RewriteFailureM msg        -> RewriteFailureM msg+             RewriteFailureM msg        -> return $ RewriteFailureM msg++   fail msg = RewriteM $ \ _ -> return $ RewriteFailureM msg++instance (Monoid dec,Monad m) => Functor (RewriteM m dec) where+  fmap f m = liftM f m++-- | 'liftQ' lets you tunnel into the inner monad, because 'RewriteM' is actually monad transformer.+liftQ :: (Monad m,Monoid dec) =>  m a -> RewriteM m dec a   +liftQ m = RewriteM $ \ _ -> do r <- m+                               return $ RewriteReturnM r mempty mempty++-- | 'failM' is our basic failure, with a String message.+failM :: (Monad m, Monoid dec) => String -> RewriteM m dec a+failM msg = RewriteM $ \ _ -> return $ RewriteFailureM msg++-- | 'catchM' catches failures, and tries a second monadic computation.+catchM :: (Monad m) => RewriteM m dec a -> (String -> RewriteM m dec a) -> RewriteM m dec a+catchM (RewriteM m1) m2 = RewriteM $ \ dec -> do+        r <- m1 dec+        case r of+          RewriteReturnM {}    -> return r +          RewriteFailureM msg  -> runRewriteM (m2 msg) dec+          +          +-- | 'chainM' executes the first argument then the second, much like '>>=',+-- except that the second computation can see if the first computation was an identity or not.+-- Used to spot when a rewrite succeeded, but was the identity.++chainM :: (Monoid dec,Monad m) +       => (RewriteM m dec b) +       -> (Bool -> b -> RewriteM m dec c)+       -> RewriteM m dec c+chainM m k = RewriteM $ \ dec -> do+        r <- runRewriteM m dec+        case r of+          RewriteReturnM a ds ids -> +                do r2 <- runRewriteM (k (isId ids) a) (case ds of+                                                         Nothing -> dec+                                                         Just ds2 -> ds2 `mappend` dec)+                   case r2 of+                     RewriteReturnM a' ds' ids' ->+                         return $ RewriteReturnM a' (ds' `mappend` ds) (ids' `mappend` ids)+                     RewriteFailureM msg -> return $ RewriteFailureM msg+          RewriteFailureM msg        -> return $ RewriteFailureM msg -- and still fail +  where+          isId NotId = False+          isId _     = True+          +-- | 'markM' is used to mark a monadic rewrite as a non-identity,+-- unless the congruence flag is set.+markM :: (Monad m) => RewriteM m dec a -> RewriteM m dec a+markM (RewriteM m) = RewriteM $ \ dec -> do+        r <- m dec+        case r of+          RewriteReturnM a ds EmptyId -> return $ RewriteReturnM a ds NotId+          RewriteReturnM a ds IsId    -> return $ RewriteReturnM a ds EmptyId+          RewriteReturnM a ds ids     -> return $ RewriteReturnM a ds ids+          RewriteFailureM msg         -> return $ RewriteFailureM msg+          +-- | 'transparently' sets the congruence flag, such that if the+-- monadic action was identity preserving, then a 'markM' does+-- not set the non-indentity flag.+        +transparently :: (Monad m) => RewriteM m dec a -> RewriteM m dec a+transparently (RewriteM m) = RewriteM $ \ dec -> do+        r <- m dec+        case r of+          RewriteReturnM a ds EmptyId -> return $ RewriteReturnM a ds IsId+          RewriteReturnM a ds ids     -> return $ RewriteReturnM a ds ids+          RewriteFailureM msg         -> return $ RewriteFailureM msg+++-- | 'getDecsM' reads the local environment+getDecsM :: (Monad m, Monoid dec) => RewriteM m dec dec+getDecsM = RewriteM $ \ dec -> return $ RewriteReturnM dec mempty mempty++-- | 'mapDecs" changes the local environment, inside a local monadic invocation.+mapDecsM :: (Monad m, Monoid dec) => (dec -> dec) -> RewriteM m dec a -> RewriteM m dec a+mapDecsM fn (RewriteM m) = RewriteM $ \ dec -> m (fn dec)
+ Language/KURE/Term.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses #-}++-- | This module supports the generic walking of 'Term's. +--+-- The key idea here is that for each type of expression (@exp@), +-- we have a sum of all the interesting children types (@Generic exp@).+-- There is always a type that its own 'Generic', which is used for the +-- deeper syntax tree walks.++module Language.KURE.Term +	( Term(..)+	, Walker(..)+	, extractR+	, promoteR+	, extractU+	, topdownR+	, bottomupR +	, alltdR +	, downupR +	, innermostR +	, foldU +	) where+	+import Language.KURE.RewriteMonad as M	+import Language.KURE.Translate	+import Language.KURE.Rewrite+import Language.KURE.Combinators -- perhaps++import Control.Monad+import Data.Monoid++-- | 'Term's are things that syntax are built from.+class Term exp where+  -- | 'Generic' is a sum of all the interesting sub-types, transitively, of @exp@. +  -- We use @Generic e ~ e@ to signify that something is its own Generic.+  -- Simple expression types might be their own sole 'Generic', more complex examples+  -- will have a new datatype for the 'Generic', which will also be an instance of class 'Term'.+  type Generic exp++  -- | 'project' projects into a 'Generic' exp, to get the exp inside, or fail.+  -- TODO: rename as select+  select :: Generic exp -> Maybe exp++  -- | 'inject' injects an exp into a 'Generic' exp.+  inject  :: exp -> Generic exp+++-- | 'Walker' captures how we walk over @exp@, using a specific @m@ and @dec@.+class (Monoid dec,Monad m,Term exp) => Walker m dec exp where+  -- | 'allR' applies 'Generic' rewrites to all the interesting children of this node.+  allR :: Rewrite m dec (Generic exp) -> Rewrite m dec exp+  -- | 'allU' applied a 'Generic' Translation to a common, 'Monoid'al result, to all the interesting children of this node.+  crushU :: (Monoid result) => Translate m dec (Generic exp) result -> Translate m dec exp result++------------------------------------------------------------------------------++-- | 'extract' converts a 'Rewrite' over a 'Generic' into a rewrite over a specific expression type. ++extractR  :: (Monad m, Term exp, Monoid dec) => Rewrite m dec (Generic exp) -> Rewrite m dec exp	-- at *this* type+extractR rr = rewrite $ \ e -> transparently $ do+            e' <- apply rr (inject e)+            case select e' of+                Nothing -> fail "extractR"+                Just r -> return r+                +-- | 'promote' promotes a 'Rewrite' into a 'Generic' 'Rewrite'; other types inside Generic cause failure.+-- 'try' can be used to convert a failure-by-default promotion into a 'id-by-default' promotion.++promoteR  :: (Monad m, Term exp, Monoid dec) => Rewrite m dec exp -> Rewrite m dec (Generic exp)+promoteR rr = rewrite $ \ e -> transparently $ do+               case select e of+                 Nothing -> fail "promoteR"+                 Just e' -> do+                    r <- apply rr e'+                    return (inject r)++-- | 'accept' ++extractU  :: (Monad m, Term exp, Monoid dec) => Translate m dec (Generic exp) r -> Translate m dec exp r+extractU rr = translate $ \ e -> transparently $ apply rr (inject e)++-------------------------------------------------------------------------------++-- apply a rewrite in a top down manner.+topdownR :: (e ~ Generic e, Walker m dec e) => Rewrite m dec e -> Rewrite m dec e+topdownR  s = s >-> allR (topdownR s)++-- apply a rewrite in a bottom up manner.+bottomupR :: (e ~ Generic e, Walker m dec e) => Rewrite m dec e -> Rewrite m dec e+bottomupR s = allR (bottomupR s) >-> s++-- apply a rewrite in a top down manner, prunning at successful rewrites.+alltdR :: (e ~ Generic e, Walker m dec e) => Rewrite m dec e -> Rewrite m dec e+alltdR    s = s <+ allR (alltdR s)++-- apply a rewrite twice, in a topdown and bottom up way, using one single tree traversal.+downupR :: (e ~ Generic e, Walker m dec e) => Rewrite m dec e -> Rewrite m dec e+downupR   s = s >-> allR (downupR s) >-> s++-- a fixed point traveral, starting with the innermost term.+innermostR :: (e ~ Generic e, Walker m dec e) => Rewrite m dec e -> Rewrite m dec e+innermostR s = bottomupR (tryR (s >-> innermostR s))  ++-- fold a tree using a single translation for each node.+foldU :: (e ~ Generic e, Walker m dec e, Monoid r) => Translate m dec e r -> Translate m dec e r+foldU s = concatT [ s, crushU (foldU s) ]++-------------------------------------------------------------------------------
+ Language/KURE/Translate.hs view
@@ -0,0 +1,63 @@+-- |+-- Module: Language.KURE.Translate +-- Copyright: (c) 2006-2008 Andy Gill+-- License: BSD3+--+-- Maintainer: Andy Gill <andygill@ku.edu>+-- Stability: unstable+-- Portability: ghc+--+-- 'Translate' is the main abstraction inside KURE, and represents a rewriting from a source to a target+-- of a possibly different type.+--+-- Rewrite (defined in 'Language.KURE.Rewrite') is a synonoym for a 'Translate' with the same source and target type.++module Language.KURE.Translate +	( Translate+	, apply+	, runTranslate+	, translate+	) where+		+import Control.Monad+import Data.Monoid++import Language.KURE.RewriteMonad++-- | 'Translate' is a translation or strategy that translates between @exp1@ and @exp2@, with the posiblity of failure,+-- and remembers identity translations.++newtype Translate m dec exp1 exp2 =+    Translate ( exp1 -> RewriteM m dec exp2 )++-- | 'apply' directly applies a 'Translate' value to an argument.+apply :: (Monoid dec, Monad m) => Translate m dec exp1 exp2 -> exp1 -> RewriteM m dec exp2+apply (Translate t) exp1 = t exp1 +++-- | 'translate' is the standard way of building a 'Translate', where if the translation is successful it +-- is automatically marked as a non-identity translation. +--+-- Note: @translate $ \ _ e -> return e@ /is not/ an identity rewrite, but a succesful rewrite that+-- returns its provided argument. ++translate :: (Monoid dec, Monad m) => (exp1 -> RewriteM m dec exp2) -> Translate m dec exp1 exp2+translate f = Translate $ \ e -> markM $ f e+++-- | 'runTranslate' executes the translation, returning either a failure message,+-- or a success and the new parts of the environment.++runTranslate :: (Monoid dec,Monad m) +	   => Translate m dec exp res+	   -> dec +	   -> exp +	   -> m (Either String (res,dec))+runTranslate rr dec e = do+  res <- runRewriteM (apply rr e) dec+  case res of+     RewriteReturnM exp' Nothing _   -> return (Right (exp',mempty))+     RewriteReturnM exp' (Just ds) _ -> return (Right (exp',ds))+     RewriteFailureM msg     -> return (Left msg)++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ kure.cabal view
@@ -0,0 +1,44 @@+Name:                kure+Version:             0.2+Synopsis:            Combinators for Strategic Programming+Description:	     KURE is a DSL for building rewriting DSLs.+	 	     KURE shares combinator names and concepts with Stratego, but unlike Stratego, KURE is strongly typed.+		     KURE is similar to Strafunski, but has a lightweight generic traversal mechanism using type families+		     rather than SYB,+		     and the KURE combinators are parameterized to provide the ability to have context sensitive rewrites.++Category:            Language+License:             BSD3+License-file:        LICENSE+Author:              Andy Gill+Maintainer:          Andy Gill <andygill@ku.edu>+Copyright:           (c) 2006-2008 Andy Gill+Homepage:            http://ittc.ku.edu/~andygill/kure.php+Build-Depends:       base+Stability:	     alpha++build-type: 	     Simple+Cabal-Version:       >= 1.6+++Library+  Build-Depends:        base, containers +  Exposed-modules:+       Language.KURE,+       Language.KURE.RewriteMonad, +       Language.KURE.Translate,+       Language.KURE.Rewrite,+       Language.KURE.Combinators,+       Language.KURE.Term++  Ghc-Options:  -Wall+++--Executable test1+--  Ghc-Options:     -fhpc+--  Main-Is:        Test.hs+--  Hs-Source-Dirs: ., test+--  buildable: True+++