packages feed

zephyr 0.1.4 → 0.2.0

raw patch · 13 files changed

+692/−179 lines, 13 filesdep +QuickCheckdep ~ansi-terminaldep ~basedep ~language-javascriptPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck

Dependency ranges changed: ansi-terminal, base, language-javascript

API changes (from Hackage documentation)

- Language.PureScript.DCE.Errors: NoEntryPointFound :: DCEError
+ Language.PureScript.DCE.Constants: eqMod :: ModuleName
+ Language.PureScript.DCE.Constants: maybeMod :: ModuleName
+ Language.PureScript.DCE.Errors: EntryModule :: ModuleName -> EntryPoint
+ Language.PureScript.DCE.Errors: EntryPoint :: (Qualified Ident) -> EntryPoint
+ Language.PureScript.DCE.Errors: NoEntryPoint :: DCEError
+ Language.PureScript.DCE.Errors: data EntryPoint
+ Language.PureScript.DCE.Errors: instance GHC.Read.Read Language.PureScript.DCE.Errors.EntryPoint
+ Language.PureScript.DCE.Errors: instance GHC.Show.Show Language.PureScript.DCE.Errors.EntryPoint
+ Language.PureScript.DCE.Errors: showEntryPoint :: EntryPoint -> Text
+ Language.PureScript.DCE.Eval: instance GHC.Classes.Eq Language.PureScript.DCE.Eval.EvalState
+ Language.PureScript.DCE.Eval: instance GHC.Show.Show Language.PureScript.DCE.Eval.EvalState
- Language.PureScript.DCE.CoreFn: dce :: forall m. (MonadError (DCEError 'Error) m, MonadWriter [DCEError 'Warning] m) => [Module Ann] -> [Qualified Ident] -> m [Module Ann]
+ Language.PureScript.DCE.CoreFn: dce :: [Module Ann] -> [Qualified Ident] -> [Module Ann]
- Language.PureScript.DCE.Errors: EntryPointsNotFound :: [Qualified Ident] -> DCEError
+ Language.PureScript.DCE.Errors: EntryPointsNotFound :: [EntryPoint] -> DCEError

Files

README.md view
@@ -1,12 +1,13 @@ # zephyr [![Maintainer: coot](https://img.shields.io/badge/maintainer-coot-lightgrey.svg)](http://github.com/coot)-[![Build Status](https://travis-ci.org/coot/zephyr.svg?branch=master)](https://travis-ci.org/coot/zephyr)+[![Travis Build Status](https://travis-ci.org/coot/zephyr.svg?branch=master)](https://travis-ci.org/coot/zephyr)+[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/coot/zephyr)  Experimental tree shaking tool for [PureScript](https://github.com/purescript/purescript).  # Usage ```-# compile your project (or use `pulp build -- --dump-corefn`)+# compile your project (or use `pulp build -- -g corefn`) purs compile -g corefn bower_components/purescript-*/src/**/*.purs src/**/*.purs  # run `zephyr`@@ -16,6 +17,9 @@ webpack ``` +You can also specify modules as entry points, which is the same as specifying+all exported identifiers.+ `zephyr` reads corefn json representation from `output` directory, removes non transitive dependencies of entry points and dumps common js modules (or corefn representation) to `dce-output` directory.@@ -61,22 +65,3 @@ It is good to run `webpack` or `rollup` to run a javascript tree shaking algorithm on the javascript code that is pulled in your bundle by your by your foreign imports.--# Tests--It is tested on dozeon of various projects of-[@alexmingoia](https://github.com/alexmingoia),-[@bodil](https://github.com/bodil), [@coot](https://github.com/coot),-[@purescript-contrib](https://github.com/purescript-contrib) and-[@slamdata](https://github.com/slamdata) including:-* [purescript-pux](https://github.com/alexmingoia/purescript-pux)-* [purescript-smolder](https://github.com/bodil/purescript-smolder)-* [purescript-signal](https://github.com/bodil/purescript-signal)-* [purescript-aff](https://github.com/slamdata/purescript-aff)-* [purescript-matryoshka](https://github.com/slamdata/purescript-matryoshka)-* [purescript-argonaout](https://github.com/purescript-contrib/purescript-argonaut)-* [purescript-profunctor-lenses](https://github.com/purescript-contrib/purescript-profunctor-lenses)-* [purescript-react-hocs](https://github.com/coot/purescript-react-hocs) (_karma tests_)-* [purescript-react-redox](https://github.com/coot/purescript-react-redox) (_karma test_)--Checkout unit test suite to see all of them.
app/Command/DCE.hs view
@@ -17,11 +17,11 @@ import qualified Data.Aeson.Internal as A import           Data.Aeson.Parser (eitherDecodeWith, json) import           Data.Bifunctor (first)-import qualified Data.ByteString.Char8 as B-import qualified Data.ByteString.Lazy.Char8 as B (fromStrict, toStrict)-import qualified Data.ByteString.UTF8 as BU8+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Lazy.Char8 as BSL.Char8 (unpack)+import qualified Data.ByteString.Lazy.UTF8 as BU8 import           Data.Bool (bool)-import           Data.Either (Either, lefts, rights)+import           Data.Either (Either, lefts, rights, partitionEithers) import           Data.Foldable (traverse_) import           Data.List (intercalate, null) import qualified Data.Map as M@@ -30,9 +30,11 @@ import qualified Data.Set as S import           Data.Text (Text) import qualified Data.Text as T+import qualified Data.Text.Lazy.Encoding as TE import           Data.Traversable (for) import           Data.Version (Version) import           Formatting (sformat, string, stext, (%))+import qualified Language.JavaScript.Parser as JS import qualified Language.PureScript as P import qualified Language.PureScript.CoreFn as CoreFn import qualified Language.PureScript.CoreFn.FromJSON as CoreFn@@ -42,10 +44,12 @@ import qualified System.Console.ANSI as ANSI import           System.Directory (doesDirectoryExist, getCurrentDirectory) import           System.Exit (exitFailure, exitSuccess)+import           System.FilePath ((</>)) import           System.FilePath.Glob (compile, globDir1) import           System.IO (hPutStrLn, stderr)  import           Command.DCEOptions+import           Language.PureScript.DCE.Errors (EntryPoint (..))  inputDirectoryOpt :: Opts.Parser FilePath inputDirectoryOpt = Opts.strOption $@@ -66,7 +70,7 @@ entryPointOpt :: Opts.Parser EntryPoint entryPointOpt = Opts.argument (Opts.auto >>= checkIfQualified) $      Opts.metavar "entry-point"-  <> Opts.help "Qualified identifier. All code which is not a transitive dependency of an entry point will be removed. You can pass multiple entry points."+  <> Opts.help "Qualified identifier or a module name. All code which is not a transitive dependency of an entry point (or any exported identifier from a give module) will be removed. You can pass multiple entry points."   where   checkIfQualified (EntryPoint q@(P.Qualified Nothing _)) = fail $     "not a qualified indentifier: '" ++ T.unpack (P.showQualified P.runIdent q) ++ "'"@@ -162,10 +166,10 @@   <*> jsonErrors  readInput :: [FilePath] -> IO [Either (FilePath, JSONPath, String) (Version, CoreFn.Module CoreFn.Ann)]-readInput inputFiles = forM inputFiles (\f -> addPath f . decodeCoreFn <$> B.readFile f)+readInput inputFiles = forM inputFiles (\f -> addPath f . decodeCoreFn <$> BSL.readFile f)   where-  decodeCoreFn :: B.ByteString -> Either (JSONPath, String) (Version, CoreFn.Module CoreFn.Ann)-  decodeCoreFn = eitherDecodeWith json (A.iparse CoreFn.moduleFromJSON) . B.fromStrict+  decodeCoreFn :: BSL.ByteString -> Either (JSONPath, String) (Version, CoreFn.Module CoreFn.Ann)+  decodeCoreFn = eitherDecodeWith json (A.iparse CoreFn.moduleFromJSON)    addPath     :: FilePath@@ -188,7 +192,7 @@       exitFailure     Right _ -> return () printWarningsAndErrors verbose True warnings errors = do-  hPutStrLn stderr . BU8.toString . B.toStrict . A.encode $+  hPutStrLn stderr . BU8.toString . A.encode $     P.JSONResult (P.toJSONErrors verbose P.Warning warnings)                (either (P.toJSONErrors verbose P.Error) (const []) errors)   either (const exitFailure) (const (return ())) errors@@ -226,6 +230,36 @@ formatDCEAppError _ relPath (CompilationError err)   = T.pack $ displayDCEError relPath err ++getEntryPoints+  :: [CoreFn.Module CoreFn.Ann]+  -> [EntryPoint]+  -> [Either EntryPoint (P.Qualified P.Ident)]+getEntryPoints mods = go []+  where+  go acc [] = acc+  go acc ((EntryPoint i) : eps)  = +    if i `fnd` mods+      then go (Right i : acc) eps+      else go (Left (EntryPoint i)  : acc) eps+  go acc ((EntryModule mn) : eps) = go (modExports mn mods ++ acc) eps++  modExports :: P.ModuleName -> [CoreFn.Module CoreFn.Ann] -> [Either EntryPoint (P.Qualified P.Ident)]+  modExports mn [] = [Left (EntryModule mn)]+  modExports mn (CoreFn.Module{moduleName,moduleExports} : ms)+    | mn == moduleName+    = (Right . flip P.mkQualified mn) `map` moduleExports+    | otherwise+    = modExports mn ms++  fnd :: P.Qualified P.Ident -> [CoreFn.Module CoreFn.Ann] -> Bool+  fnd _ [] = False+  fnd qi@(P.Qualified (Just mn) i) (CoreFn.Module{moduleName,moduleExports} : ms)+    = if moduleName == mn && i `elem` moduleExports+        then True+        else fnd qi ms+  fnd _ _ = False+ dceCommand :: DCEOptions -> ExceptT DCEAppError IO () dceCommand DCEOptions {..} = do     -- initial checks@@ -234,8 +268,7 @@       throwError (InputNotDirectory dceInputDir)      -- read files, parse errors-    let entryPoints = runEntryPoint <$> dceEntryPoints-        cfnGlb = compile "**/corefn.json"+    let cfnGlb = compile "**/corefn.json"     inpts <- liftIO $ globDir1 cfnGlb dceInputDir >>= readInput     let errs = lefts inpts     unless (null errs) $@@ -245,10 +278,18 @@     when (isNothing mPursVer) $       throwError (NoInputs dceInputDir) +    let (notFound, entryPoints) = partitionEithers $ getEntryPoints (fmap snd . rights $ inpts) dceEntryPoints++    when (not $ null notFound) $+      throwError (CompilationError $ EntryPointsNotFound notFound)++    when (null $ entryPoints) $+      throwError (CompilationError $ NoEntryPoint)+     -- run `dceEval` and `dce` on the `CoreFn`     (mods, warns) <- mapExceptT (fmap $ first CompilationError)         $ runWriterT-        $ dceEval (snd `map` rights inpts) >>= flip dce entryPoints+        $ dceEval (snd `map` rights inpts) >>= return . flip dce entryPoints     relPath <- liftIO getCurrentDirectory     liftIO $ traverse_ (hPutStrLn stderr . uncurry (displayDCEWarning relPath)) (zip (zip [1..] (repeat (length warns))) warns)     let filePathMap = M.fromList $ map (\m -> (CoreFn.moduleName m, Right $ CoreFn.modulePath m)) mods@@ -258,6 +299,22 @@         liftIO         $ P.runMake dcePureScriptOptions         $ runSupplyT 0 $ traverse (\m -> P.codegen makeActions m P.initEnvironment mempty) mods+    when dceForeign $+      forM_ mods $ \(CoreFn.Module{moduleName,moduleForeign}) -> liftIO $+        case moduleName `M.lookup` foreigns of+          Nothing -> return ()+          Just fp -> do+            jsCode <- BSL.Char8.unpack <$> BSL.readFile fp+            case JS.parse jsCode fp of+              Left _ -> return ()+              Right (JS.JSAstProgram ss ann) ->+                let ss'    = dceForeignModule moduleForeign ss+                    jsAst' = JS.JSAstProgram ss' ann+                    foreignFile+                          = dceOutputDir </> T.unpack (P.runModuleName moduleName) </> "foreign.js"+                in+                  BSL.writeFile foreignFile (TE.encodeUtf8 $ JS.renderToText jsAst')+              Right _ -> return ()     liftIO $ printWarningsAndErrors (P.optionsVerboseErrors dcePureScriptOptions) dceJsonErrors makeWarnings makeErrors     return ()   where
app/Command/DCEOptions.hs view
@@ -2,28 +2,16 @@ -- Helper module for `zephyr`. module Command.DCEOptions where -import qualified Data.Text as T import qualified Language.PureScript as P--newtype EntryPoint = EntryPoint { runEntryPoint :: P.Qualified P.Ident }--instance Read EntryPoint where-  readsPrec _ s = case unsnoc (T.splitOn "." (T.pack s)) of-      Just (as, a) | not (null as)  -> [(EntryPoint (P.mkQualified (P.Ident a) (P.ModuleName $ P.ProperName <$> as)), "")]-                   | otherwise      -> [(EntryPoint (P.Qualified Nothing (P.Ident a)), "")]-      Nothing                       -> []-    where-    unsnoc :: [a] -> Maybe ([a], a)-    unsnoc [] = Nothing-    unsnoc as = Just (init as, last as)+import Language.PureScript.DCE.Errors (EntryPoint)  data DCEOptions = DCEOptions-  { dceEntryPoints :: [EntryPoint]-  , dceInputDir :: FilePath-  , dceOutputDir :: FilePath-  , dceVerbose :: Bool-  , dceForeign :: Bool+  { dceEntryPoints       :: [EntryPoint]+  , dceInputDir          :: FilePath+  , dceOutputDir         :: FilePath+  , dceVerbose           :: Bool+  , dceForeign           :: Bool   , dcePureScriptOptions :: P.Options-  , dceUsePrefix :: Bool-  , dceJsonErrors :: Bool+  , dceUsePrefix         :: Bool+  , dceJsonErrors        :: Bool   }
src/Language/PureScript/DCE/Constants.hs view
@@ -2,6 +2,8 @@ -- Various constants used by zephyr. module Language.PureScript.DCE.Constants where +import Prelude hiding (maybe)+ import Language.PureScript.Names  unit :: ModuleName@@ -13,6 +15,9 @@ semigroup :: ModuleName semigroup = ModuleName [ProperName "Data", ProperName "Semigroup"]  +maybeMod :: ModuleName+maybeMod = ModuleName [ProperName "Data", ProperName "Maybe"] + pattern Semigroup :: ModuleName pattern Semigroup = ModuleName [ProperName "Data", ProperName "Semigroup"]  @@ -39,3 +44,9 @@  unsafeCoerce :: ModuleName unsafeCoerce = ModuleName [ProperName "Unsafe", ProperName "Coerce"]++eqMod :: ModuleName+eqMod = ModuleName [ProperName "Data", ProperName "Eq"]++pattern Eq :: ModuleName+pattern Eq = ModuleName [ProperName "Data", ProperName "Eq"]
src/Language/PureScript/DCE/CoreFn.hs view
@@ -8,15 +8,12 @@ import           Prelude.Compat import           Control.Arrow ((***)) import           Control.Monad-import           Control.Monad.Except-import           Control.Monad.Writer import           Data.Graph import           Data.Foldable (foldl', foldr') import           Data.List (any, elem, filter, groupBy, sortBy) import           Data.Maybe (catMaybes, mapMaybe) import qualified Data.Set as S import           Language.PureScript.CoreFn-import           Language.PureScript.DCE.Errors import           Language.PureScript.DCE.Utils (bindIdents, unBind) import           Language.PureScript.Names @@ -29,20 +26,11 @@ -- | -- Dead code elimination of a list of modules module dce-  :: forall m-   . (MonadError (DCEError 'Error) m, MonadWriter [DCEError 'Warning] m)-  => [Module Ann]       -- ^ modules to dce+  :: [Module Ann]       -- ^ modules to dce   -> [Qualified Ident]  -- ^ entry points used to build the graph of                         --   dependencies across module boundaries-  -> m [Module Ann]     -- ^ dead code eliminated modules-dce _ [] = throwError NoEntryPointFound-dce modules entryPoints =-  if null entryPointVertices then throwError NoEntryPointFound else do-    let found = ((\(_, qi, _) -> qi) . keyForVertex) `map` entryPointVertices-        notFound = filter (not . (`elem` found)) entryPoints-    unless (null notFound)-      (tell [EntryPointsNotFound notFound])-    return (uncurry runDCE `map` reachableInModule)+  -> [Module Ann]       -- ^ dead code eliminated modules+dce modules entryPoints = uncurry runDCE `map` reachableInModule   where    -- |
src/Language/PureScript/DCE/Errors.hs view
@@ -1,7 +1,9 @@ -- | -- Errors used in dead call elimination. module Language.PureScript.DCE.Errors-  ( DCEError(..)+  ( EntryPoint (..)+  , showEntryPoint+  , DCEError(..)   , displayDCEError   , displayDCEWarning   , Level(..)@@ -15,7 +17,7 @@  import Prelude.Compat -import           Data.Char (isSpace)+import           Data.Char (isLower, isSpace) import           Data.List (intersperse, dropWhileEnd) import           Data.Monoid ((<>)) import           Data.Text (Text)@@ -31,18 +33,44 @@ import qualified Text.PrettyPrint.Boxes as Box import qualified System.Console.ANSI as ANSI -data Level = Error | Warning deriving (Show)+data EntryPoint+  = EntryPoint (Qualified Ident)+  | EntryModule ModuleName+  deriving Show +showEntryPoint :: EntryPoint -> Text+showEntryPoint (EntryPoint qi) = showQualified showIdent qi+showEntryPoint (EntryModule mn) = runModuleName mn++instance Read EntryPoint where+  readsPrec _ s = case unsnoc (T.splitOn "." (T.pack s)) of+      Just (as, a)+        | not (null as)+        , True <- not (T.null a)+        , True <- isLower (T.head a)+          -> [(EntryPoint (mkQualified (Ident a) (ModuleName $ ProperName <$> as)), "")]+        | True <- not (T.null a)+          -> [(EntryModule $ ModuleName $ ProperName <$> as ++ [a], "")]+        | otherwise+          -> []+      Nothing -> []+    where+    unsnoc :: [a] -> Maybe ([a], a)+    unsnoc [] = Nothing+    unsnoc as = Just (init as, last as)+ -- | -- Error type shared by `dce` and `dceEval`. data DCEError (a :: Level)   = IdentifierNotFound ModuleName Ann (Qualified Ident)   | ArrayIdxOutOfBound ModuleName Ann Integer   | AccessorNotFound ModuleName Ann PSString-  | NoEntryPointFound-  | EntryPointsNotFound [Qualified Ident]+  | NoEntryPoint+  | EntryPointsNotFound [EntryPoint]   deriving (Show) +data Level = Error | Warning deriving (Show)+ getAnn :: DCEError a -> Maybe (ModuleName, SourceSpan) getAnn (IdentifierNotFound mn (ss, _, _, _) _) = Just (mn, ss) getAnn (ArrayIdxOutOfBound mn (ss, _, _, _) _) = Just (mn, ss)@@ -62,11 +90,11 @@           Box.text "Object literal lacks required label"   Box.<+> colorBox codeColor (T.unpack $ prettyPrintString acc)   Box.<> "."-formatDCEError NoEntryPointFound = "No entry point found."-formatDCEError (EntryPointsNotFound qis) =-          Box.text ("Entry point" ++ if length qis > 1 then "s:" else "")+formatDCEError NoEntryPoint = "No entry point given."+formatDCEError (EntryPointsNotFound eps) =+          Box.text ("Entry point" ++ if length eps > 1 then "s:" else "")   Box.<+> foldr1 (Box.<>) (intersperse (Box.text ", ")-            $ map (colorBox codeColor . T.unpack . showQualified runIdent) qis)+            $ map (colorBox codeColor . T.unpack . showEntryPoint) eps)   Box.<+> "not found."  renderDCEError :: FilePath -> DCEError a -> Box.Box
src/Language/PureScript/DCE/Eval.hs view
@@ -8,6 +8,7 @@ import Control.Monad.State import Control.Monad.Writer import Data.Functor (($>))+import Data.Maybe (fromMaybe) import Language.PureScript.AST.Literals import Language.PureScript.CoreFn import Language.PureScript.DCE.Errors@@ -19,12 +20,32 @@ import Control.Arrow (second) import Data.Maybe (Maybe(..), fromJust, isJust, maybeToList) import Data.Monoid (First(..))-import Language.PureScript.DCE.Constants as C+import qualified Language.PureScript.DCE.Constants as C import Prelude.Compat hiding (mod) import Safe (atMay) -type Stack = [[(Ident, Expr Ann)]]+data EvalState+  = NotYet -- ^ an expression has not yet been evaluated+  | Done   -- ^ an expression has been evaluated+  deriving (Eq, Show) +type Stack = [[(Ident, (Expr Ann, EvalState))]]++initStack :: [(Ident, Expr Ann)] -> [(Ident, (Expr Ann, EvalState))]+initStack = map (\(i, e) -> (i, (e, NotYet)))++-- Mark first found expression as evaluated to avoid infinite loops.+markDone :: Ident -> Stack -> Stack+markDone _ [] = []+markDone i (l : ls) =+  case foldr fn ([], False) l of+    (l', True)  -> l' : ls+    (l', False) -> l' : markDone i ls+  where+  fn (i', v) (is, done)+    | i == i' = ((i', (fst v, Done)) : is, True)+    | otherwise = ((i', v) : is, done)+ -- | -- Evaluate expressions in a module: --@@ -61,7 +82,7 @@     -- pop recent value in the stack (it was added in `onBinders`)    onBind :: Bind Ann -> StateT (ModuleName, Stack) m (Bind Ann)-  onBind b = modify (second (unBind b :)) $> b+  onBind b = modify (second (initStack (unBind b) :)) $> b    -- |   -- Track local identifiers in case binders, push them onto the stack.@@ -71,7 +92,7 @@     -> StateT (ModuleName, Stack) m [Binder Ann]   onBinders es bs = do     let bes = concatMap fn (zip bs es)-    modify (second (bes :))+    modify (second (initStack bes :))     return bs     where     fn :: (Binder Ann, Expr Ann) -> [(Ident, Expr Ann)]@@ -123,12 +144,12 @@           | otherwise -- guard expression must evaluate to a Boolean           -> fltGuards rest         _ -> ((g,e) :) <$> fltGuards rest-  onExpr l@Let {} = modify (second (drop 1)) $> l+  onExpr l@Let{} = modify (second (drop 1)) $> l   onExpr e@Var{} = do     v <- eval e     case v of       Just l@(Literal _ NumericLiteral{}) -> return l-      Just l@(Literal _ CharLiteral{}) -> return l+      Just l@(Literal _ CharLiteral{})    -> return l       Just l@(Literal _ BooleanLiteral{}) -> return l       -- preserve string, array and object literals       Just _  -> return e@@ -150,16 +171,28 @@   eval :: Expr Ann -> StateT (ModuleName, Stack) m (Maybe (Expr Ann))   eval (Var _ (Qualified Nothing i)) = do     (_, s) <- get-    join <$> traverse eval (fnd i s)+    join <$> traverse eval' (fnd i s)     where-      fnd :: Ident -> Stack -> Maybe (Expr Ann)+      fnd :: Ident -> Stack -> Maybe (Expr Ann, EvalState)       fnd j s = getFirst $ foldMap (First . lookup j) s++      eval' :: (Expr Ann, EvalState) -> StateT (ModuleName, Stack) m (Maybe (Expr Ann))+      eval' (e, Done) = return (Just e)+      eval' (e, _)    = do+        modify (\(mn, s) -> (mn, markDone i s))+        eval e   eval (Var ann qi@(Qualified (Just mn) i)) = do     (cmn, _) <- get     case findQualifiedExpr mn i of       Nothing -> throwError (IdentifierNotFound cmn ann qi)       Just (Right e)  -> eval e       Just (Left _)   -> return Nothing+  eval (Literal ann (ArrayLiteral es)) = do+    es' <- traverse (\e -> fromMaybe e <$> eval e) es+    return $ Just (Literal ann (ArrayLiteral es'))+  eval (Literal ann (ObjectLiteral as)) = do+    as' <- traverse (\(n, e) -> maybe (n,e) ((n,)) <$> eval e) as+    return $ Just (Literal ann (ObjectLiteral as'))   eval e@Literal{} = return (Just e)   eval     (App ann@@ -167,31 +200,28 @@         (App _           (Var _             (Qualified-              (Just (ModuleName [ProperName "Data", ProperName "Eq"]))+              (Just C.Eq)               (Ident "eq")))           (Var _ inst))           e1)       e2)     = if inst `elem`-          [ Qualified (Just mn) (Ident "eqBoolean")-          , Qualified (Just mn) (Ident "eqInt")-          , Qualified (Just mn) (Ident "eqNumber")-          , Qualified (Just mn) (Ident "eqChar")-          , Qualified (Just mn) (Ident "eqString")-          , Qualified (Just mn) (Ident "eqUnit")-          , Qualified (Just mn) (Ident "eqVoid")+          [ Qualified (Just C.eqMod) (Ident "eqBoolean")+          , Qualified (Just C.eqMod) (Ident "eqInt")+          , Qualified (Just C.eqMod) (Ident "eqNumber")+          , Qualified (Just C.eqMod) (Ident "eqChar")+          , Qualified (Just C.eqMod) (Ident "eqString")+          , Qualified (Just C.eqMod) (Ident "eqUnit")+          , Qualified (Just C.eqMod) (Ident "eqVoid")           ]         then do           v1 <- eval e1           v2 <- eval e2           case (v1, v2) of             (Just (Literal _ l1), Just (Literal _ l2))-              -> return $ Just $ Literal ann  $ BooleanLiteral (eqLit l1 l2)-            (_, _)-              -> return Nothing+              -> return $ Just $ Literal ann $ BooleanLiteral (eqLit l1 l2)+            _ -> return Nothing         else return Nothing-    where-      mn = ModuleName [ProperName "Data", ProperName "Eq"]   eval (Accessor ann a (Literal _ (ObjectLiteral as))) = do     (mn, _) <- get     e <- maybe (throwError (AccessorNotFound mn ann a)) return (a `lookup` as)@@ -214,7 +244,7 @@           $ App ann               (Var (ss, [], Nothing, Just (IsConstructor SumType [Ident "value0"]))                 (Qualified-                  (Just (ModuleName [ProperName "Data", ProperName "Maybe"]))+                  (Just C.maybeMod)                   (Ident "Just")))         <$> e   -- | Eval Semigroup
src/Language/PureScript/DCE/Foreign.hs view
@@ -24,6 +24,7 @@                   , JSArrayElement(..)                   , JSObjectProperty(..)                   , JSCommaTrailingList(..)+                  , JSCommaList(..)                   ) import           Language.PureScript.Names 
+ test/Generators.hs view
@@ -0,0 +1,306 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NamedFieldPuns   #-}+module Generators where++import Data.List (foldl')+import Data.String (IsString (..))+import Test.QuickCheck++import Language.PureScript.Names (Ident (..), ModuleName (..), ProperName (..), Qualified (..), moduleNameFromString)+import Language.PureScript.PSString (PSString)+import Language.PureScript.AST.SourcePos (SourceSpan (..), SourcePos (..))+import Language.PureScript.AST (Literal (..))+import Language.PureScript.CoreFn (Ann, Bind (..), Binder (..), CaseAlternative (..), Expr (..), Guard, ssAnn)++import qualified Language.PureScript.DCE.Constants as C++ann :: Ann+ann = ssAnn (SourceSpan "src/Test.purs" (SourcePos 0 0) (SourcePos 0 0))++genPSString :: Gen PSString+genPSString = fromString <$> elements+  ["a", "b", "c", "d", "value0"]++genProperName :: Gen (ProperName a)+genProperName = ProperName <$> elements+  ["A", "B", "C", "D", "E"]++genIdent :: Gen Ident+genIdent = Ident <$> elements+  ["value0", "value1", "value2"]++unusedIdents :: [Ident]+unusedIdents =+  Ident <$> ["u1", "u2", "u3", "u4", "u5"]++genUnusedIdent :: Gen Ident+genUnusedIdent = elements unusedIdents++genModuleName :: Gen ModuleName+genModuleName = elements+  [ moduleNameFromString "Data.Eq"+  , moduleNameFromString "Data.Array"+  , moduleNameFromString "Data.Maybe"+  , C.semigroup+  , C.unsafeCoerce+  , C.unit+  , C.semiring+  ]++genQualifiedIdent :: Gen (Qualified Ident)+genQualifiedIdent = oneof+  [ Qualified <$> liftArbitrary genModuleName <*> genIdent+  , return (Qualified (Just C.unit) (Ident "unit"))+  , return (Qualified (Just C.semiring) (Ident "add"))+  , return (Qualified (Just C.semiring) (Ident "semiringInt"))+  , return (Qualified (Just C.semiring) (Ident "semiringUnit"))+  , return (Qualified (Just C.maybeMod) (Ident "Just"))+  , return (Qualified (Just C.eqMod) (Ident "eq"))+  , return (Qualified (Just C.ring) (Ident "negate"))+  , return (Qualified (Just C.ring) (Ident "ringNumber"))+  , return (Qualified (Just C.ring) (Ident "unitRing"))+  ]++genQualified :: Gen a -> Gen (Qualified a)+genQualified gen = Qualified <$> liftArbitrary genModuleName <*> gen++genLiteral :: Gen (Literal (Expr Ann))+genLiteral = oneof+  [ NumericLiteral <$> arbitrary+  , StringLiteral  <$> genPSString+  , CharLiteral    <$> arbitrary+  , BooleanLiteral <$> arbitrary+  , ArrayLiteral . map unPSExpr <$> arbitrary+  , ObjectLiteral . map (\(k, v) -> (fromString k, unPSExpr v)) <$> arbitrary+  ]++genLiteral' :: Gen (Expr Ann)+genLiteral' = oneof+  [ Literal ann . NumericLiteral <$> arbitrary+  , Literal ann . StringLiteral <$> genPSString+  , Literal ann . BooleanLiteral <$> arbitrary+  , Literal ann . CharLiteral <$> arbitrary+  ]++genExpr :: Gen (Expr Ann)+genExpr = unPSExpr <$> arbitrary++genCaseAlternative :: Gen (CaseAlternative Ann)+genCaseAlternative = sized $ \n -> +  CaseAlternative <$> vectorOf n genBinder <*> genCaseAlternativeResult n+  where+  genCaseAlternativeResult :: Int -> Gen (Either [(Guard Ann, Expr Ann)] (Expr Ann))+  genCaseAlternativeResult n = oneof+    [ Left  <$> vectorOf n ((,) <$> resize n genExpr <*> resize n genExpr)+    , Right <$> resize n genExpr+    ]++newtype PSBinder = PSBinder { unPSBinder :: Binder Ann }+  deriving Show++instance Arbitrary PSBinder where+  arbitrary = resize 5 $ PSBinder <$> sized go+    where+    go :: Int -> Gen (Binder Ann)+    go 0 = oneof+      [ return $ NullBinder ann+      , VarBinder ann <$> genIdent+      ]+    go n = frequency+      [ (1, return $ NullBinder ann)+      , (2, LiteralBinder ann . ArrayLiteral  <$> listOf (go (n - 1)))+      , (2, LiteralBinder ann . ObjectLiteral <$> listOf ((,) <$> genPSString <*> (go (n - 1))))+      , (3, ConstructorBinder ann <$> genQualified genProperName <*> genQualified genProperName <*> listOf (go (n - 1)))+      , (3, NamedBinder ann <$> genIdent <*> (go (n - 1)))+      ]++  shrink (PSBinder (LiteralBinder _ (ArrayLiteral bs))) =+    (PSBinder . LiteralBinder ann . ArrayLiteral . map unPSBinder+    <$> (shrinkList shrink (PSBinder <$> bs)))+    ++ map PSBinder bs+  shrink (PSBinder (LiteralBinder _ (ObjectLiteral o))) =+    (PSBinder . LiteralBinder ann . ObjectLiteral+    <$> shrinkList (\(n, b) -> (n,) . unPSBinder <$> shrink (PSBinder b)) o)+    ++ map (PSBinder . snd) o+  shrink (PSBinder (ConstructorBinder _ tn cn bs)) =+    (PSBinder . ConstructorBinder ann tn cn . map unPSBinder+    <$> (shrinkList shrink (PSBinder <$> bs)))+    ++ map PSBinder bs+  shrink (PSBinder (NamedBinder _ n b)) =+    PSBinder b+    : (PSBinder . NamedBinder ann n . unPSBinder <$> shrink (PSBinder b))+  shrink _ = []++genBinder :: Gen (Binder Ann)+genBinder = unPSBinder <$> arbitrary++prop_binderDistribution :: PSBinder -> Property+prop_binderDistribution (PSBinder c) =+    classify True (show . depth $ c)+  $ tabulate "Binders" (cls c) True+  where+  cls NullBinder{}                 = ["NullBinder"]+  cls LiteralBinder{}              = ["LiteralBinder"]+  cls VarBinder{}                  = ["VarBinder"]+  cls (ConstructorBinder _ _ _ bs) = "ConstructorBinder" : concatMap cls bs+  cls (NamedBinder _ _ b)          = "NamedBinder" : cls b++  depth :: Binder a -> Int+  depth NullBinder{}                        = 1+  depth (LiteralBinder _ (ArrayLiteral bs)) = foldr (\b x -> depth b `max` x) 1 bs + 1 +  depth (LiteralBinder _ (ObjectLiteral o)) = foldr (\(_, b) x -> depth b `max` x) 0 o + 1+  depth LiteralBinder{}                     = 1+  depth VarBinder{}                         = 1+  depth (ConstructorBinder _ _ _ bs)        = foldr (\b x -> depth b `max` x) 1 bs + 1+  depth (NamedBinder _ _ b)                 = depth b++genBind :: Gen (Bind Ann)+genBind = frequency+  [ (3, NonRec ann <$> gen  <*> genExpr)+  , (1, Rec <$> listOf ((\i e -> ((ann, i), e)) <$> gen <*> genExpr))+  ]+  where+  gen = frequency [(3, genIdent), (2, genUnusedIdent)]++newtype PSExpr a = PSExpr { unPSExpr :: Expr a }+  deriving Show++-- Generate simple curried functions+genApp :: Gen (PSExpr Ann)+genApp =+  (\x y -> PSExpr $ App ann x y)+    <$> frequency+        [ (1, unPSExpr <$> genApp)+        , (2, Var ann <$> genQualifiedIdent)+        ]+    <*> frequency+        [ (2, Var ann <$> genQualifiedIdent)+        , (3, genLiteral')+        ]++instance Arbitrary (PSExpr Ann) where+  arbitrary = resize 5 $ sized go+    where+    go :: Int -> Gen (PSExpr Ann)+    go 0 = oneof+      [ PSExpr . Literal ann <$> genLiteral+      , fmap PSExpr $ Constructor ann <$> genProperName <*> genProperName <*> listOf genIdent+      , fmap PSExpr $ Var ann <$> genQualifiedIdent+      ]+    go n = frequency+      [ (3, PSExpr . Literal ann <$> genLiteral)+      , (3, fmap PSExpr $ Constructor ann <$> genProperName <*> genProperName <*> listOf genIdent)+      , (3, fmap PSExpr $ Var ann <$> genQualifiedIdent)+      , (4, fmap PSExpr $ Accessor ann <$> genPSString <*> (unPSExpr <$> go (n - 1)))+      , (1, fmap PSExpr $ ObjectUpdate ann <$> genExpr <*> resize (max 3 (n - 1)) (listOf ((,) <$> genPSString <*> (unPSExpr <$> go (n - 1)))))+      , (2, fmap PSExpr $ Abs ann <$> genIdent <*> (unPSExpr <$> go (n - 1)))+      , (1, fmap PSExpr $ App ann <$> (unPSExpr <$> go (n - 1)) <*> (unPSExpr <$> go (n - 1)))+      , (4, genApp)+      , (1, fmap PSExpr $ Case ann <$> resize (max 3 (n `div` 2)) (listOf (unPSExpr <$> go (n - 1))) <*> resize (max 2 (n `div` 2)) (listOf (resize (n - 1) genCaseAlternative)))+      , (4, fmap PSExpr $ Let ann <$> listOf genBind <*> (unPSExpr <$> go (n - 1)))+      ]++  shrink (PSExpr expr) = map PSExpr $ go expr+    where+    go :: Expr Ann -> [Expr Ann]+    go (Literal ann' (ArrayLiteral es)) =+      (Literal ann' . ArrayLiteral <$> shrinkList shrinkExpr es)+      ++ es+    go (Literal ann' (ObjectLiteral o)) =+      (Literal ann' . ObjectLiteral+      <$> shrinkList (\(n, e) -> (n,) <$> shrinkExpr e) o)+      ++ map snd o+    go (Accessor ann' n e) =+      e : (Accessor ann' n <$> shrinkExpr e)+    go (ObjectUpdate ann' e es) =+      e : map snd es+      +++        [ ObjectUpdate ann' e' es'+        | e'  <- shrinkExpr e+        , es' <- shrinkList (\(n, f) -> map (n,) $ shrinkExpr f) es+        ]+    go (Abs ann' n e) =+      let es = shrinkExpr e+      in e : es ++ map (Abs ann' n) es+    go (App ann' e f) =+      e : f : [ App ann' e' f' | e' <- shrinkExpr e, f' <- shrinkExpr f ]+    go Var{} = []+    go (Case ann' es cs) =+      es+      ++ concatMap+          (\(CaseAlternative _ r) ->+            either+              (\es' -> map fst es' ++ map snd es')+              (\e' -> [e'])+              r+          )+          cs+      ++ [ Case ann' [e'] [c']+         | e' <- if length es > 1 then es else []+         , c' <- if length cs > 1 then cs else []+         ]+      ++ [ Case ann' es' cs'+         | es' <- shrinkList shrinkExpr es+         , cs' <- shrinkList shrinkCS cs+         ]+      where+      shrinkCS :: CaseAlternative Ann -> [CaseAlternative Ann]+      shrinkCS (CaseAlternative bs r) =+        [ CaseAlternative bs' r'+        | bs' <- shrinkList (\x -> [x]) bs+        , r'  <- rs+        ]+        where+        rs = case r of+          Right e -> Right <$> shrinkExpr e+          Left es' -> Left  <$> shrinkList (\(g, f) -> [(g', f') | g' <- shrinkExpr g, f' <- shrinkExpr f]) es'+    go (Let ann' bs e) =+      e : [ Let ann' bs' e' | bs' <- shrinkList shrinkBind bs, e' <- shrinkExpr e ]+    go _ = []++shrinkExpr :: Expr Ann -> [Expr Ann]+shrinkExpr = map unPSExpr . shrink . PSExpr++shrinkBind :: Bind Ann -> [Bind Ann]+shrinkBind (NonRec ann' n e) = NonRec ann' n <$> shrinkExpr e+shrinkBind (Rec as) = Rec <$> shrinkList (\(x, e) -> map (x,) $ shrinkExpr e) as++exprDepth :: Expr a -> Int+exprDepth (Literal _ (ArrayLiteral es)) = foldr (\e x -> exprDepth e `max` x) 1 es + 1+exprDepth (Literal _ (ObjectLiteral o)) = foldr (\(_, e) x -> exprDepth e `max` x) 1 o + 1+exprDepth (Literal{})   = 1+exprDepth Constructor{} = 1+exprDepth (Accessor _ _ e) = 1 + exprDepth e+exprDepth (ObjectUpdate _ e es) = 1 + exprDepth e + foldr (\(_, f) x -> exprDepth f `max` x) 1 es+exprDepth (Abs _ _ e) = 1 + exprDepth e+exprDepth (App _ e f) = 1 + exprDepth e `max` exprDepth f+exprDepth Var{}       = 1+exprDepth (Case _ es cs) = 1 + foldr (\f x -> exprDepth f `max` x) cdepth es+  where+  cdepth = foldr (\(CaseAlternative _ r) x -> either (foldr (\(g, e) y -> exprDepth g `max` exprDepth e `max` y) 1) exprDepth r `max` x) 1 cs+exprDepth (Let _ _ e) = 1 + exprDepth e++prop_exprDistribution :: PSExpr Ann -> Property+prop_exprDistribution (PSExpr e) =+    collect (exprDepth' e)+  $ tabulate "classify expressions" (cls e) True+  where+  cls :: Expr a -> [String]+  cls Literal{}      = ["Literal"]+  cls Constructor{}  = ["Constructor"]+  cls Accessor{}     = ["Accessor"]+  cls ObjectUpdate{} = ["ObjectUpdate"]+  cls Abs{}          = ["Abs"]+  cls App{}          = ["App"]+  cls Var{}          = ["Var"]+  cls (Case _ _ cs)  = "Case" : foldl' (\x c -> clsCaseAlternative c ++ x) [] cs+    where+    clsCaseAlternative (CaseAlternative {caseAlternativeResult}) =+      either (foldl' (\x (g, f) -> cls g ++ cls f ++ x) []) cls caseAlternativeResult+  cls Let{}          = ["Let"]++  exprDepth' expr = case exprDepth expr of+    n | n < 10     -> n+      | n < 100   -> 10 * (n `div` 10)+      | n < 1000  -> 25 * (n `div` 25)+      | otherwise -> 100 * (n `div` 100)
test/Main.hs view
@@ -1,4 +1,10 @@-module Main (main) where+{-# LANGUAGE CPP #-}+module Main+  ( main+  , coreLibSpec+  , karmaSpec+  , libSpec+  ) where  import           Prelude () import           Prelude.Compat hiding (exp)@@ -27,6 +33,16 @@ import qualified TestDCECoreFn import qualified TestDCEEval +test_prg  :: String+#ifdef TEST_WITH_CABAL+test_prg = "cabal"+#else+test_prg = "stack"+#endif++test_args :: [String]+test_args = ["exec", "zephyr", "--"]+ data CoreLibTest = CoreLibTest   { coreLibTestRepo :: Text   -- ^ git repo@@ -119,19 +135,26 @@   , libTestZephyrOptions :: Maybe [Text]   , libTestJsCmd :: Text   , libTestShouldPass :: Bool-  -- ^ true if should run without error, false if should error+  -- ^ true if it should run without error, false if it should error   }  libTests :: [LibTest] libTests =   [ LibTest ["Unsafe.Coerce.Test.unsafeX"] Nothing "require('./dce-output/Unsafe.Coerce.Test').unsafeX(1)(1);" True+  , LibTest ["Foreign.Test.add"] Nothing "require('./dce-output/Foreign.Test').add(1)(1);" True+  , LibTest ["Foreign.Test.add"] Nothing "require('./dce-output/Foreign.Test/foreign.js').mult(1)(1);" False+  , LibTest ["Eval.makeAppQueue"] Nothing "require('./dce-output/Eval').makeAppQueue;" True+  , LibTest ["Eval.evalUnderArrayLiteral"] Nothing "require('./dce-output/Eval').evalUnderArrayLiteral;" True+  , LibTest ["Eval.evalUnderObjectLiteral"] Nothing "require('./dce-output/Eval').evalUnderObjectLiteral;" True+  , LibTest ["Eval.evalVars"] Nothing "require('./dce-output/Eval').evalVars;" True+  , LibTest ["Eval"] Nothing "require('./dce-output/Eval').evalVars;" True   ]  data KarmaTest = KarmaTest   { karmaTestRepo :: Text   -- ^ git repo   , karmaTestEntry :: Text-  -- ^ `zephyre entry point+  -- ^ zephyr entry point   }  karmaTests :: [KarmaTest]@@ -218,11 +241,8 @@   when (not outputDirExists) $ do     (ecPurs, _, errPurs) <- lift       $ readProcessWithExitCode-          "stack"-          [ "exec"-          , "purs"-          , "--"-          , "compile"+          "purs"+          [ "compile"           , "--codegen" , "corefn"           , "bower_components/purescript-*/src/**/*.purs"           , "src/**/*.purs"@@ -240,7 +260,7 @@   outputDirExists <- lift $ doesDirectoryExist "dce-output"   when outputDirExists $     lift $ removeDirectoryRecursive "dce-output"-  (ecZephyr, _, errZephyr) <- lift $ readProcessWithExitCode "stack" (["exec", "zephyr", "--"] ++ T.unpack `map` fromMaybe ["-f"] zephyrOptions ++ T.unpack `map` coreLibTestEntries) ""+  (ecZephyr, _, errZephyr) <- lift $ readProcessWithExitCode test_prg (test_args ++ T.unpack `map` fromMaybe ["-f"] zephyrOptions ++ T.unpack `map` coreLibTestEntries) ""   when (ecZephyr /= ExitSuccess) (throwError $ ZephyrError coreLibTestRepo ecZephyr errZephyr)    @@ -361,7 +381,7 @@         specify (T.unpack repo) $ assertCoreLib l  libSpec :: Spec-libSpec = do+libSpec =   context "TestLib" $     forM_ libTests $ \l ->       specify (T.unpack $ T.intercalate (T.pack " ") $ libTestEntries l) $ assertLib l@@ -383,7 +403,7 @@  main :: IO () main = do-  readProcess "stack" ["exec", "purs", "--", "--version"] "" >>= putStrLn . (\v -> "\npurs version: " ++ v)+  readProcess "purs" ["--version"] "" >>= putStrLn . (\v -> "\npurs version: " ++ v)    hSetEncoding stdout utf8   hSetEncoding stderr utf8@@ -392,5 +412,5 @@   TestDCEEval.main    hspec $ changeDir "test/tests" libSpec-  hspec $ changeDir ".temp" coreLibSpec-  hspec karmaSpec+  -- hspec $ changeDir ".temp" coreLibSpec+  -- hspec karmaSpec
test/TestDCECoreFn.hs view
@@ -1,9 +1,10 @@-module TestDCECoreFn (main) where+module TestDCECoreFn  where  import Prelude () import Prelude.Compat -import Data.List (concatMap)+import Data.List (concatMap, foldl', intersect)+import qualified Data.List as L  import Language.PureScript.AST.Literals import Language.PureScript.AST.SourcePos@@ -13,7 +14,10 @@ import Language.PureScript.PSString  import Test.Hspec+import Test.QuickCheck +import Generators hiding (ann)+ main :: IO () main = hspec spec @@ -27,9 +31,70 @@ ann :: Ann ann = ssAnn (SourceSpan "src/Test.purs" (SourcePos 0 0) (SourcePos 0 0)) +prop_exprDepth :: PSExpr Ann -> Property+prop_exprDepth (PSExpr e) =+  let b = NonRec ann (Ident "x") e+      NonRec _ _ e' = dceExpr b+      d  = exprDepth e+      d' = exprDepth e'+  in collect (10 * (d' * 100 `div` (10 * d)))+    $ counterexample (show e)+    $ d' <= d++prop_lets :: PSExpr Ann -> Property+prop_lets (PSExpr f) =+  let b = NonRec ann (Ident "x") f+      NonRec _ _ f' = dceExpr b+      d  = countLets f+      d' = countLets f'+      idents = findBindIdents f'+  in label ((if d > 0 then show (10 * ((d' * 100 `div` d) `div` 10)) ++ "%" else "-") ++ " of removed let bindings")+    $ counterexample (show f)+    $  d' <= d+    && L.null (intersect idents unusedIdents)+  where+  countLets :: Expr a -> Int+  countLets (Literal _ (ArrayLiteral es)) = foldl' (\x e -> x + countLets e) 0 es+  countLets (Literal _ (ObjectLiteral o)) = foldl' (\x (_, e) -> x + countLets e) 0 o+  countLets Literal{} = 0+  countLets Constructor{} = 0+  countLets (Accessor _ _ e) = countLets e+  countLets (ObjectUpdate _ e o) = countLets e + foldl' (\x (_, e') -> x + countLets e') 0 o+  countLets (Abs _ _ e) = countLets e+  countLets (App _ e f') = countLets e + countLets f'+  countLets Var{} = 0+  countLets (Case _ es cs) = foldl' (\x e -> x + countLets e) 0 es + foldl countLetsInCaseAlternative 0 cs+    where+    countLetsInCaseAlternative x (CaseAlternative _ r) = +      x + either (foldl' (\y (g, e) -> y + countLets g + countLets e) 0) countLets r+  countLets (Let _ _ e) = 1 + countLets e++  findBindIdents :: Expr a -> [Ident]+  findBindIdents (Literal _ (ArrayLiteral es)) = concatMap findBindIdents es+  findBindIdents (Literal _ (ObjectLiteral o)) = concatMap (findBindIdents . snd) o+  findBindIdents Literal{} = []+  findBindIdents Constructor{} = []+  findBindIdents (Accessor _ _ e) = findBindIdents e+  findBindIdents (ObjectUpdate _ e o) = findBindIdents e ++ concatMap (findBindIdents . snd) o+  findBindIdents (Abs _ _ e) = findBindIdents e+  findBindIdents (App _ e f') = findBindIdents e ++ findBindIdents f'+  findBindIdents Var{} = []+  findBindIdents (Case _ es cs) = concatMap findBindIdents es ++ concatMap countLetsInCaseAlternative cs+    where+    countLetsInCaseAlternative (CaseAlternative _ r) = +      either (concatMap (\(g, e1) -> findBindIdents g ++ findBindIdents e1)) findBindIdents r+  findBindIdents (Let _ bs e) = concatMap fn bs ++ findBindIdents e+    where+    fn (NonRec _ i e1) = i : findBindIdents e1+    fn (Rec as)        = foldl' (\acc ((_, i), e1) -> i : findBindIdents e1 ++ acc) [] as+ spec :: Spec-spec =+spec = do+  context "generators" $ do+    specify "should generate Expr" $ property $ prop_exprDistribution   context "dceExpr" $ do+    specify "should reduce the depth of the tree" $ property $ withMaxSuccess 10000 prop_exprDepth+    specify "should reduce the number of let bindings" $ property $ withMaxSuccess 10000 prop_lets     specify "should remove unused identifier" $ do       let e :: Expr Ann           e = Let ann
test/TestDCEEval.hs view
@@ -1,4 +1,4 @@-module TestDCEEval (main) where+module TestDCEEval where  import Prelude () import Prelude.Compat@@ -9,7 +9,7 @@ import Language.PureScript.AST.SourcePos import Language.PureScript.CoreFn import Language.PureScript.DCE-import Language.PureScript.DCE.Constants as C+import qualified Language.PureScript.DCE.Constants as C import Language.PureScript.Names import Language.PureScript.PSString @@ -17,7 +17,10 @@  import Test.Hspec import Test.HUnit (assertFailure)+import Test.QuickCheck +import Generators hiding (ann)+ main :: IO () main = hspec spec @@ -27,61 +30,80 @@ ann :: Ann ann = ssAnn ss -spec :: Spec-spec =-  context "dceEval" $ do-    let eqModName = ModuleName [ProperName "Data", ProperName "Eq"]-        eq = Qualified (Just eqModName) (Ident "eq")-        eqBoolean  = Qualified (Just eqModName) (Ident "eqBoolean")-        eqMod = Module ss [] eqModName "" [] []-          [ Ident "refEq" ]-          [ NonRec ann (Ident "eq")  -              (Abs ann (Ident "dictEq")-                (Abs ann (Ident "x")-                  (Abs ann (Ident "y")-                    (Literal ann (BooleanLiteral True)))))-          , NonRec ann (Ident "eqBoolean")-              (App ann-                (Var ann (Qualified (Just eqModName) (Ident "Eq")))-                (Var ann (Qualified (Just eqModName) (Ident "refEq"))))-          , NonRec ann (Ident "Eq")-              (Abs ann (Ident "eq")-                (Literal ann (ObjectLiteral [(mkString "eq", Var ann (Qualified Nothing (Ident "eq")))])))-          ]-        booleanMod = Module ss [] (ModuleName [ProperName "Data", ProperName "Boolean"]) "" [] [] []-          [ NonRec ann (Ident "otherwise") (Literal ann (BooleanLiteral True)) ]-        arrayMod = Module ss [] (ModuleName [ProperName "Data", ProperName "Array"]) ""-          [] [] []-          [ NonRec ann (Ident "index")-              (Abs ann (Ident "as")-                (Abs ann (Ident "ix")-                  (Literal ann (CharLiteral 'f'))))-          ]-        unsafeCoerceMod = Module ss [] C.unsafeCoerce ""-          [] [] []-          [ NonRec ann (Ident "unsafeCoerce")-              (Abs ann (Ident "x")-                (Var ann (Qualified Nothing (Ident "x"))))-          ]-        testMod e = Module ss [] mn mp [] [] []-          [ NonRec ann (Ident "v") e-          , NonRec ann (Ident "f")-              (Abs ann (Ident "x") (Var ann (Qualified Nothing (Ident "x"))))-          ]+eq :: Qualified Ident+eq = Qualified (Just C.eqMod) (Ident "eq")++eqBoolean :: Qualified Ident+eqBoolean = Qualified (Just eqModName) (Ident "eqBoolean")++eqModName :: ModuleName+eqModName = ModuleName [ProperName "Data", ProperName "Eq"]           -        mn = ModuleName [ProperName "Test"]-        mp = "src/Test.purs"+mn :: ModuleName+mn = ModuleName [ProperName "Test"] -        dceEvalExpr' :: Expr Ann -> [Module Ann] -> Either (DCEError 'Error) (Expr Ann)-        dceEvalExpr' e mods = case runWriterT $ dceEval ([testMod e , eqMod , booleanMod , arrayMod, unsafeCoerceMod] ++ mods) of-          Right (((Module _ _ _ _ _ _ _ [NonRec _ _ e', _]): _), _) -> Right e'-          Right _   -> undefined-          Left err  -> Left err+mp :: FilePath+mp = "src/Test.purs" -        dceEvalExpr :: Expr Ann -> Either (DCEError 'Error) (Expr Ann)-        dceEvalExpr e = dceEvalExpr' e []+dceEvalExpr' :: Expr Ann -> [Module Ann] -> Either (DCEError 'Error) (Expr Ann)+dceEvalExpr' e mods = case runWriterT $ dceEval ([testMod , eqMod , booleanMod , arrayMod, unsafeCoerceMod] ++ mods) of+  Right ((Module _ _ _ _ _ _ _ [NonRec _ _ e', _]): _, _) -> Right e'+  Right _   -> undefined+  Left err  -> Left err+  where+  testMod = Module ss [] mn mp [] [] []+    [ NonRec ann (Ident "v") e+    , NonRec ann (Ident "f")+        (Abs ann (Ident "x") (Var ann (Qualified Nothing (Ident "x"))))+    ]+  eqMod = Module ss [] C.eqMod "" [] []+    [ Ident "refEq" ]+    [ NonRec ann (Ident "eq")  +        (Abs ann (Ident "dictEq")+          (Abs ann (Ident "x")+            (Abs ann (Ident "y")+              (Literal ann (BooleanLiteral True)))))+    , NonRec ann (Ident "eqBoolean")+        (App ann+          (Var ann (Qualified (Just C.eqMod) (Ident "Eq")))+          (Var ann (Qualified (Just C.eqMod) (Ident "refEq"))))+    , NonRec ann (Ident "Eq")+        (Abs ann (Ident "eq")+          (Literal ann (ObjectLiteral [(mkString "eq", Var ann (Qualified Nothing (Ident "eq")))])))+    ]+  booleanMod = Module ss [] (ModuleName [ProperName "Data", ProperName "Boolean"]) "" [] [] []+    [ NonRec ann (Ident "otherwise") (Literal ann (BooleanLiteral True)) ]+  arrayMod = Module ss [] (ModuleName [ProperName "Data", ProperName "Array"]) ""+    [] [] []+    [ NonRec ann (Ident "index")+        (Abs ann (Ident "as")+          (Abs ann (Ident "ix")+            (Literal ann (CharLiteral 'f'))))+    ]+  unsafeCoerceMod = Module ss [] C.unsafeCoerce ""+    [] [] []+    [ NonRec ann (Ident "unsafeCoerce")+        (Abs ann (Ident "x")+          (Var ann (Qualified Nothing (Ident "x"))))+    ] -    specify "should simplify if when comparing two literal values" $ do+dceEvalExpr :: Expr Ann -> Either (DCEError 'Error) (Expr Ann)+dceEvalExpr e = dceEvalExpr' e []++prop_eval :: PSExpr Ann -> Property+prop_eval (PSExpr g) = +  let d  = exprDepth g+      d' = either (const Nothing) (Just . exprDepth) $ dceEvalExpr g+  in+    collect ((\x -> if d > 0 then 10 * (x * 100 `div` (10 * d)) else 0) <$> d')+    $ counterexample (show g)+    $ maybe True (\x -> x <= d) d'++spec :: Spec+spec =+  context "dceEval" $ do+    specify "should evaluate" $ property $ withMaxSuccess 100000 prop_eval+    specify "should simplify when comparing two literal values" $ do       let v :: Expr Ann           v =             App ann@@ -281,8 +303,8 @@     context "Var inlining" $ do       let oModName = ModuleName [ProperName "Other"]           oMod = Module ss [] oModName "" [] [] []-            [ NonRec ann (Ident "o") $ Literal ann (ObjectLiteral [(mkString "a", Var ann (Qualified (Just eqModName) (Ident "eq"))) ])-            , NonRec ann (Ident "a") $ Literal ann (ArrayLiteral [ Var ann (Qualified (Just eqModName) (Ident "eq")) ])+            [ NonRec ann (Ident "o") $ Literal ann (ObjectLiteral [(mkString "a", Var ann (Qualified (Just C.eqMod) (Ident "eq"))) ])+            , NonRec ann (Ident "a") $ Literal ann (ArrayLiteral [ Var ann (Qualified (Just C.eqMod) (Ident "eq")) ])             , NonRec ann (Ident "s") $ Literal ann (StringLiteral (mkString "very-long-string"))             , NonRec ann (Ident "b") $ Literal ann (BooleanLiteral True)             , NonRec ann (Ident "c") $ Literal ann (CharLiteral 'a')
zephyr.cabal view
@@ -1,5 +1,5 @@ name:                zephyr-version:             0.1.4+version:             0.2.0 synopsis:              Zephyr tree shaking for PureScript Language description:@@ -17,6 +17,11 @@ cabal-version:       >=1.10 tested-with:         ghc +flag test-with-cabal+  description: use `cabal exec zephyr` in tests+  manual: True+  default: False+ library   hs-source-dirs:      src   default-extensions:@@ -29,6 +34,7 @@     LambdaCase     MultiParamTypeClasses     NoImplicitPrelude+    NamedFieldPuns     OverloadedStrings     PatternGuards     PatternSynonyms@@ -50,8 +56,8 @@     , Language.PureScript.DCE.Utils   build-depends:             aeson >=1.0 && <1.3-    , ansi-terminal >=0.7.1 && <0.8-    , base >= 4.7 && < 5+    , ansi-terminal >=0.7.1 && <0.9+    , base >= 4.8 && < 4.11     , base-compat >=0.6.0     , bytestring     , boxes >=0.1.4 && <0.2.0@@ -60,7 +66,7 @@     , filepath     , formatting     , Glob >=0.9 && <0.10-    , language-javascript >=0.6.0.9 && <0.7+    , language-javascript >=0.6.0.11 && <0.7     , mtl >=2.1.0 && <2.3.0     , optparse-applicative >=0.13.0     , purescript@@ -82,6 +88,7 @@   default-extensions:     DataKinds     FlexibleContexts+    NamedFieldPuns     OverloadedStrings     RecordWildCards   ghc-options:@@ -93,7 +100,7 @@     -with-rtsopts=-N   build-depends:       aeson >=1.0 && <1.3-    , ansi-terminal >=0.7.1 && <0.8+    , ansi-terminal >=0.7.1 && <0.9     , ansi-wl-pprint     , base     , base-compat >=0.6.0@@ -103,7 +110,7 @@     , filepath     , formatting     , Glob >=0.9 && <0.10-    , language-javascript >=0.6.0.9 && <0.7+    , language-javascript >=0.6.0.11 && <0.7     , mtl >=2.1.0 && <2.3.0     , optparse-applicative >=0.13.0     , purescript >= 0.12 && < 0.13@@ -128,8 +135,8 @@   main-is: Main.hs   build-depends:             aeson >=1.0 && <1.3-    , ansi-terminal >=0.7.1 && <0.8-    , base >=4.8 && <5+    , ansi-terminal >=0.7.1 && < 0.9+    , base >= 4.8 && < 4.11     , base-compat >=0.6.0     , bytestring     , containers@@ -138,11 +145,12 @@     , hspec     , hspec-core     , HUnit-    , language-javascript >=0.6.0.9 && <0.7+    , language-javascript >=0.6.0.11 && <0.7     , mtl >=2.1.0 && <2.3.0     , optparse-applicative >=0.13.0     , process < 1.7.0.0     , purescript+    , QuickCheck >= 2.12.1     , text     , transformers >=0.3.0 && <0.6     , transformers-base >=0.4.0 && <0.5@@ -152,6 +160,10 @@   other-modules:       TestDCECoreFn     , TestDCEEval+    , Generators+  if flag(test-with-cabal)+    cpp-options:+      -DTEST_WITH_CABAL=1   ghc-options:     -Wall     -threaded