summaryrefslogtreecommitdiff
path: root/compiler/GHC/JS/Transform.hs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/GHC/JS/Transform.hs')
-rw-r--r--compiler/GHC/JS/Transform.hs368
1 files changed, 368 insertions, 0 deletions
diff --git a/compiler/GHC/JS/Transform.hs b/compiler/GHC/JS/Transform.hs
new file mode 100644
index 0000000000..cb99200163
--- /dev/null
+++ b/compiler/GHC/JS/Transform.hs
@@ -0,0 +1,368 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE PatternSynonyms #-}
+
+module GHC.JS.Transform
+ ( mapIdent
+ , mapStatIdent
+ , mapExprIdent
+ , identsS
+ , identsV
+ , identsE
+ -- * Saturation
+ , jsSaturate
+ -- * Generic traversal (via compos)
+ , JMacro(..)
+ , JMGadt(..)
+ , Compos(..)
+ , composOp
+ , composOpM
+ , composOpM_
+ , composOpFold
+ -- * Hygienic transformation
+ , withHygiene
+ , scopify
+ )
+where
+
+import GHC.Prelude
+
+import GHC.JS.Syntax
+
+import qualified Data.Map as M
+import Text.Read (readMaybe)
+import Data.Functor.Identity
+import Control.Monad
+import Data.Bifunctor
+
+
+import qualified GHC.Data.ShortText as ST
+import GHC.Data.ShortText (ShortText)
+import GHC.Utils.Monad.State.Strict
+import GHC.Utils.Panic
+
+mapExprIdent :: (Ident -> JExpr) -> JExpr -> JExpr
+mapExprIdent f = fst (mapIdent f)
+
+mapStatIdent :: (Ident -> JExpr) -> JStat -> JStat
+mapStatIdent f = snd (mapIdent f)
+
+-- | Map on every variable ident
+mapIdent :: (Ident -> JExpr) -> (JExpr -> JExpr, JStat -> JStat)
+mapIdent f = (map_expr, map_stat)
+ where
+ map_expr = \case
+ ValExpr v -> map_val v
+ SelExpr e i -> SelExpr (map_expr e) i
+ IdxExpr e1 e2 -> IdxExpr (map_expr e1) (map_expr e2)
+ InfixExpr o e1 e2 -> InfixExpr o (map_expr e1) (map_expr e2)
+ UOpExpr o e -> UOpExpr o (map_expr e)
+ IfExpr e1 e2 e3 -> IfExpr (map_expr e1) (map_expr e2) (map_expr e3)
+ ApplExpr e es -> ApplExpr (map_expr e) (fmap map_expr es)
+ UnsatExpr me -> UnsatExpr (fmap map_expr me)
+
+ map_val v = case v of
+ JVar i -> f i
+ JList es -> ValExpr $ JList (fmap map_expr es)
+ JDouble{} -> ValExpr $ v
+ JInt{} -> ValExpr $ v
+ JStr{} -> ValExpr $ v
+ JRegEx{} -> ValExpr $ v
+ JHash me -> ValExpr $ JHash (fmap map_expr me)
+ JFunc is s -> ValExpr $ JFunc is (map_stat s)
+ UnsatVal v2 -> ValExpr $ UnsatVal v2
+ -- FIXME: shouldn't we transform this into `UnsatExpr (map_val v2)`?
+
+ map_stat s = case s of
+ DeclStat{} -> s
+ ReturnStat e -> ReturnStat (map_expr e)
+ IfStat e s1 s2 -> IfStat (map_expr e) (map_stat s1) (map_stat s2)
+ WhileStat b e s2 -> WhileStat b (map_expr e) (map_stat s2)
+ ForInStat b i e s2 -> ForInStat b i (map_expr e) (map_stat s2)
+ SwitchStat e les s2 -> SwitchStat (map_expr e) (fmap (bimap map_expr map_stat) les) (map_stat s2)
+ TryStat s2 i s3 s4 -> TryStat (map_stat s2) i (map_stat s3) (map_stat s4)
+ BlockStat ls -> BlockStat (fmap map_stat ls)
+ ApplStat e es -> ApplStat (map_expr e) (fmap map_expr es)
+ UOpStat o e -> UOpStat o (map_expr e)
+ AssignStat e1 e2 -> AssignStat (map_expr e1) (map_expr e2)
+ UnsatBlock ms -> UnsatBlock (fmap map_stat ms)
+ LabelStat l s2 -> LabelStat l (map_stat s2)
+ BreakStat{} -> s
+ ContinueStat{} -> s
+
+{-# INLINE identsS #-}
+identsS :: JStat -> [Ident]
+identsS = \case
+ DeclStat i -> [i]
+ ReturnStat e -> identsE e
+ IfStat e s1 s2 -> identsE e ++ identsS s1 ++ identsS s2
+ WhileStat _ e s -> identsE e ++ identsS s
+ ForInStat _ i e s -> [i] ++ identsE e ++ identsS s
+ SwitchStat e xs s -> identsE e ++ concatMap traverseCase xs ++ identsS s
+ where traverseCase (e,s) = identsE e ++ identsS s
+ TryStat s1 i s2 s3 -> identsS s1 ++ [i] ++ identsS s2 ++ identsS s3
+ BlockStat xs -> concatMap identsS xs
+ ApplStat e es -> identsE e ++ concatMap identsE es
+ UOpStat _op e -> identsE e
+ AssignStat e1 e2 -> identsE e1 ++ identsE e2
+ UnsatBlock{} -> error "identsS: UnsatBlock"
+ LabelStat _l s -> identsS s
+ BreakStat{} -> []
+ ContinueStat{} -> []
+
+{-# INLINE identsE #-}
+identsE :: JExpr -> [Ident]
+identsE = \case
+ ValExpr v -> identsV v
+ SelExpr e _i -> identsE e -- do not rename properties
+ IdxExpr e1 e2 -> identsE e1 ++ identsE e2
+ InfixExpr _ e1 e2 -> identsE e1 ++ identsE e2
+ UOpExpr _ e -> identsE e
+ IfExpr e1 e2 e3 -> identsE e1 ++ identsE e2 ++ identsE e3
+ ApplExpr e es -> identsE e ++ concatMap identsE es
+ UnsatExpr{} -> error "identsE: UnsatExpr"
+
+{-# INLINE identsV #-}
+identsV :: JVal -> [Ident]
+identsV = \case
+ JVar i -> [i]
+ JList xs -> concatMap identsE xs
+ JDouble{} -> []
+ JInt{} -> []
+ JStr{} -> []
+ JRegEx{} -> []
+ JHash m -> concatMap identsE m
+ JFunc args s -> args ++ identsS s
+ UnsatVal{} -> error "identsV: UnsatVal"
+
+
+{--------------------------------------------------------------------
+ Compos
+--------------------------------------------------------------------}
+-- | Compos and ops for generic traversal as defined over
+-- the JMacro ADT.
+
+-- | Utility class to coerce the ADT into a regular structure.
+
+class JMacro a where
+ jtoGADT :: a -> JMGadt a
+ jfromGADT :: JMGadt a -> a
+
+instance JMacro Ident where
+ jtoGADT = JMGId
+ jfromGADT (JMGId x) = x
+
+instance JMacro JStat where
+ jtoGADT = JMGStat
+ jfromGADT (JMGStat x) = x
+
+instance JMacro JExpr where
+ jtoGADT = JMGExpr
+ jfromGADT (JMGExpr x) = x
+
+instance JMacro JVal where
+ jtoGADT = JMGVal
+ jfromGADT (JMGVal x) = x
+
+-- | Union type to allow regular traversal by compos.
+data JMGadt a where
+ JMGId :: Ident -> JMGadt Ident
+ JMGStat :: JStat -> JMGadt JStat
+ JMGExpr :: JExpr -> JMGadt JExpr
+ JMGVal :: JVal -> JMGadt JVal
+
+composOp :: Compos t => (forall a. t a -> t a) -> t b -> t b
+composOp f = runIdentity . composOpM (Identity . f)
+
+composOpM :: (Compos t, Monad m) => (forall a. t a -> m (t a)) -> t b -> m (t b)
+composOpM = compos return ap
+
+composOpM_ :: (Compos t, Monad m) => (forall a. t a -> m ()) -> t b -> m ()
+composOpM_ = composOpFold (return ()) (>>)
+
+composOpFold :: Compos t => b -> (b -> b -> b) -> (forall a. t a -> b) -> t c -> b
+composOpFold z c f = unC . compos (\_ -> C z) (\(C x) (C y) -> C (c x y)) (C . f)
+
+newtype C b a = C { unC :: b }
+
+class Compos t where
+ compos :: (forall a. a -> m a) -> (forall a b. m (a -> b) -> m a -> m b)
+ -> (forall a. t a -> m (t a)) -> t c -> m (t c)
+
+instance Compos JMGadt where
+ compos = jmcompos
+
+jmcompos :: forall m c. (forall a. a -> m a) -> (forall a b. m (a -> b) -> m a -> m b) -> (forall a. JMGadt a -> m (JMGadt a)) -> JMGadt c -> m (JMGadt c)
+jmcompos ret app f' v =
+ case v of
+ JMGId _ -> ret v
+ JMGStat v' -> ret JMGStat `app` case v' of
+ DeclStat i -> ret DeclStat `app` f i
+ ReturnStat i -> ret ReturnStat `app` f i
+ IfStat e s s' -> ret IfStat `app` f e `app` f s `app` f s'
+ WhileStat b e s -> ret (WhileStat b) `app` f e `app` f s
+ ForInStat b i e s -> ret (ForInStat b) `app` f i `app` f e `app` f s
+ SwitchStat e l d -> ret SwitchStat `app` f e `app` l' `app` f d
+ where l' = mapM' (\(c,s) -> ret (,) `app` f c `app` f s) l
+ BlockStat xs -> ret BlockStat `app` mapM' f xs
+ ApplStat e xs -> ret ApplStat `app` f e `app` mapM' f xs
+ TryStat s i s1 s2 -> ret TryStat `app` f s `app` f i `app` f s1 `app` f s2
+ UOpStat o e -> ret (UOpStat o) `app` f e
+ AssignStat e e' -> ret AssignStat `app` f e `app` f e'
+ UnsatBlock _ -> ret v'
+ ContinueStat l -> ret (ContinueStat l)
+ BreakStat l -> ret (BreakStat l)
+ LabelStat l s -> ret (LabelStat l) `app` f s
+ JMGExpr v' -> ret JMGExpr `app` case v' of
+ ValExpr e -> ret ValExpr `app` f e
+ SelExpr e e' -> ret SelExpr `app` f e `app` f e'
+ IdxExpr e e' -> ret IdxExpr `app` f e `app` f e'
+ InfixExpr o e e' -> ret (InfixExpr o) `app` f e `app` f e'
+ UOpExpr o e -> ret (UOpExpr o) `app` f e
+ IfExpr e e' e'' -> ret IfExpr `app` f e `app` f e' `app` f e''
+ ApplExpr e xs -> ret ApplExpr `app` f e `app` mapM' f xs
+ UnsatExpr _ -> ret v'
+ JMGVal v' -> ret JMGVal `app` case v' of
+ JVar i -> ret JVar `app` f i
+ JList xs -> ret JList `app` mapM' f xs
+ JDouble _ -> ret v'
+ JInt _ -> ret v'
+ JStr _ -> ret v'
+ JRegEx _ -> ret v'
+ JHash m -> ret JHash `app` m'
+ where (ls, vs) = unzip (M.toList m)
+ m' = ret (M.fromAscList . zip ls) `app` mapM' f vs
+ JFunc xs s -> ret JFunc `app` mapM' f xs `app` f s
+ UnsatVal _ -> ret v'
+
+ where
+ mapM' :: forall a. (a -> m a) -> [a] -> m [a]
+ mapM' g = foldr (app . app (ret (:)) . g) (ret [])
+ f :: forall b. JMacro b => b -> m b
+ f x = ret jfromGADT `app` f' (jtoGADT x)
+
+{--------------------------------------------------------------------
+ Saturation
+--------------------------------------------------------------------}
+
+-- | Given an optional prefix, fills in all free variable names with a supply
+-- of names generated by the prefix.
+jsSaturate :: (JMacro a) => Maybe ShortText -> a -> a
+jsSaturate str x = evalState (runIdentSupply $ jsSaturate_ x) (newIdentSupply str)
+
+jsSaturate_ :: (JMacro a) => a -> IdentSupply a
+jsSaturate_ e = IS $ jfromGADT <$> go (jtoGADT e)
+ where
+ go :: forall a. JMGadt a -> State [Ident] (JMGadt a)
+ go v = case v of
+ JMGStat (UnsatBlock us) -> go =<< (JMGStat <$> runIdentSupply us)
+ JMGExpr (UnsatExpr us) -> go =<< (JMGExpr <$> runIdentSupply us)
+ JMGVal (UnsatVal us) -> go =<< (JMGVal <$> runIdentSupply us)
+ _ -> composOpM go v
+
+{--------------------------------------------------------------------
+ Transformation
+--------------------------------------------------------------------}
+
+-- doesn't apply to unsaturated bits
+jsReplace_ :: JMacro a => [(Ident, Ident)] -> a -> a
+jsReplace_ xs e = jfromGADT $ go (jtoGADT e)
+ where
+ go :: forall a. JMGadt a -> JMGadt a
+ go v = case v of
+ JMGId i -> maybe v JMGId (M.lookup i mp)
+ _ -> composOp go v
+ mp = M.fromList xs
+
+-- only works on fully saturated things
+jsUnsat_ :: JMacro a => [Ident] -> a -> IdentSupply a
+jsUnsat_ xs e = IS $ do
+ (idents,is') <- splitAt (length xs) <$> get
+ put is'
+ return $ jsReplace_ (zip xs idents) e
+
+-- | Apply a transformation to a fully saturated syntax tree,
+-- taking care to return any free variables back to their free state
+-- following the transformation. As the transformation preserves
+-- free variables, it is hygienic.
+withHygiene :: JMacro a => (a -> a) -> a -> a
+withHygiene f x = jfromGADT $ case jtoGADT x of
+ JMGExpr z -> JMGExpr $ UnsatExpr $ inScope z
+ JMGStat z -> JMGStat $ UnsatBlock $ inScope z
+ JMGVal z -> JMGVal $ UnsatVal $ inScope z
+ JMGId _ -> jtoGADT $ f x
+ where
+ inScope z = IS $ do
+ ti <- get
+ case ti of
+ ((TxtI a):b) -> do
+ put b
+ return $ withHygiene_ a f z
+ _ -> error "withHygiene: empty list"
+
+withHygiene_ :: JMacro a => ShortText -> (a -> a) -> a -> a
+withHygiene_ un f x = jfromGADT $ case jtoGADT x of
+ JMGStat _ -> jtoGADT $ UnsatBlock (jsUnsat_ is' x'')
+ JMGExpr _ -> jtoGADT $ UnsatExpr (jsUnsat_ is' x'')
+ JMGVal _ -> jtoGADT $ UnsatVal (jsUnsat_ is' x'')
+ JMGId _ -> jtoGADT $ f x
+ where
+ (x',l) = case runState (runIdentSupply $ jsSaturate_ x) is of
+ (_ , []) -> panic "withHygiene: empty ident list"
+ (x', TxtI l : _) -> (x',l)
+ is' = take lastVal is
+ x'' = f x'
+ lastVal = case readMaybe (reverse . takeWhile (/= '_') . reverse . ST.unpack $ l) of
+ Nothing -> panic ("inSat" ++ ST.unpack un)
+ Just r -> r :: Int
+ is = newIdentSupply $ Just (ST.pack "inSat" `mappend` un)
+
+-- | Takes a fully saturated expression and transforms it to use unique
+-- variables that respect scope.
+scopify :: JStat -> JStat
+scopify x = evalState (jfromGADT <$> go (jtoGADT x)) (newIdentSupply Nothing)
+ where
+ go :: forall a. JMGadt a -> State [Ident] (JMGadt a)
+ go = \case
+ JMGStat (BlockStat ss) -> JMGStat . BlockStat <$>
+ blocks ss
+ where blocks [] = return []
+ blocks (DeclStat (TxtI i) : xs)
+ | ('!':'!':rs) <- ST.unpack i
+ = (DeclStat (TxtI (ST.pack rs)):) <$> blocks xs
+ | ('!':rs) <- ST.unpack i
+ = (DeclStat (TxtI $ ST.pack rs):) <$> blocks xs
+ | otherwise = do
+ xx <- get
+ case xx of
+ (newI:st) -> do
+ put st
+ rest <- blocks xs
+ return $ [DeclStat newI `mappend` jsReplace_ [(TxtI i, newI)] (BlockStat rest)]
+ _ -> error "scopify: empty list"
+ blocks (x':xs) = (jfromGADT <$> go (jtoGADT x')) <:> blocks xs
+ (<:>) = liftM2 (:)
+ JMGStat (TryStat s (TxtI i) s1 s2) -> do
+ xx <- get
+ case xx of
+ (newI:st) -> do
+ put st
+ t <- jfromGADT <$> go (jtoGADT s)
+ c <- jfromGADT <$> go (jtoGADT s1)
+ f <- jfromGADT <$> go (jtoGADT s2)
+ return . JMGStat . TryStat t newI (jsReplace_ [(TxtI i, newI)] c) $ f
+ _ -> error "scopify: empty list"
+ JMGExpr (ValExpr (JFunc is s)) -> do
+ st <- get
+ let (newIs,newSt) = splitAt (length is) st
+ put newSt
+ rest <- jfromGADT <$> go (jtoGADT s)
+ return . JMGExpr . ValExpr $ JFunc newIs $ (jsReplace_ $ zip is newIs) rest
+ v -> composOpM go v
+