Jikka-5.6.0.0: src/Jikka/RestrictedPython/Language/Util.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
module Jikka.RestrictedPython.Language.Util
( -- * generating symbols
genType,
genVarName,
genVarName',
-- * free variables
freeTyVars,
freeVars,
freeVars',
freeVarsTarget,
freeVarsTarget',
-- * return-statements
doesAlwaysReturn,
doesPossiblyReturn,
-- * traversing statements
mapStatement,
mapStatementM,
mapLargeStatement,
mapLargeStatementM,
mapStatements,
mapStatementsM,
listStatements,
-- * traversing sub exprs
mapSubExprM,
mapSubExpr,
listSubExprs,
-- * traversing exprs
mapExprTargetM,
mapExprStatementM,
mapExprM,
listExprs,
-- * exprs
hasFunctionCall,
isSmallExpr,
dropLocation,
-- * targets
targetVars,
targetVars',
hasSubscriptTrg,
hasBareNameTrg,
exprToTarget,
targetToExpr,
-- * programs
toplevelMainDef,
-- * variable names
mainVarName,
solveVarName,
underscoreVarName,
)
where
import Control.Monad.Identity
import Control.Monad.Writer.Strict
import Data.List (delete, nub)
import Jikka.Common.Alpha
import Jikka.Common.Location
import Jikka.RestrictedPython.Language.Expr
genType :: MonadAlpha m => m Type
genType = VarTy . TypeName Nothing . Just <$> nextCounter
genVarName :: MonadAlpha m => VarName' -> m VarName'
genVarName x@(WithLoc' _ (VarName occ _ hint)) = do
i <- nextCounter
return . WithLoc' (loc' x) $ VarName occ (Just i) hint
genVarName' :: MonadAlpha m => m VarName'
genVarName' = genVarName (withoutLoc underscoreVarName)
freeTyVars :: Type -> [TypeName]
freeTyVars = nub . go
where
go = \case
VarTy x -> [x]
IntTy -> []
BoolTy -> []
ListTy t -> go t
TupleTy ts -> concat $ mapM go ts
CallableTy ts ret -> concat $ mapM go (ret : ts)
StringTy -> []
SideEffectTy -> []
-- | `freeVars'` reports all free variables.
freeVars :: Expr' -> [VarName]
freeVars = nub . map value' . freeVars'
-- | `freeVars'` reports all free variables with their locations, i.e. occurrences.
-- For examples, @x + x@ and @x@ have the same free variables @x@ but they have different sets of occurrences of free variable.
freeVars' :: Expr' -> [VarName']
freeVars' (WithLoc' _ e0) = case e0 of
BoolOp e1 _ e2 -> freeVars' e1 ++ freeVars' e2
BinOp e1 _ e2 -> freeVars' e1 ++ freeVars' e2
UnaryOp _ e -> freeVars' e
Lambda args e -> foldl (\vars (x, _) -> delete x vars) (freeVars' e) args
IfExp e1 e2 e3 -> freeVars' e1 ++ freeVars' e2 ++ freeVars' e3
ListComp e (Comprehension x iter pred) -> freeVars' iter ++ foldl (\vars x -> delete x vars) (freeVars' e ++ concatMap freeVars' pred) (targetVars' x)
Compare e1 _ e2 -> freeVars' e1 ++ freeVars' e2
Call f args -> concatMap freeVars' (f : args)
Constant _ -> []
Attribute e _ -> freeVars' e
Subscript e1 e2 -> freeVars' e1 ++ freeVars' e2
Starred e -> freeVars' e
Name x -> [x]
List _ es -> concatMap freeVars' es
Tuple es -> concatMap freeVars' es
SubscriptSlice e from to step -> freeVars' e ++ concatMap freeVars' from ++ concatMap freeVars' to ++ concatMap freeVars' step
freeVarsTarget :: Target' -> [VarName]
freeVarsTarget = nub . map value' . freeVarsTarget'
freeVarsTarget' :: Target' -> [VarName']
freeVarsTarget' (WithLoc' _ x) = case x of
SubscriptTrg _ e -> freeVars' e
NameTrg _ -> []
TupleTrg xs -> concatMap freeVarsTarget' xs
doesAlwaysReturn :: Statement -> Bool
doesAlwaysReturn = \case
Return _ -> True
AugAssign _ _ _ -> False
AnnAssign _ _ _ -> False
For _ _ _ -> False
If _ body1 body2 -> any doesAlwaysReturn body1 && any doesAlwaysReturn body2
Assert _ -> False
Expr' _ -> False
doesPossiblyReturn :: Statement -> Bool
doesPossiblyReturn = \case
Return _ -> True
AugAssign _ _ _ -> False
AnnAssign _ _ _ -> False
For _ _ body -> any doesPossiblyReturn body
If _ body1 body2 -> any doesPossiblyReturn body1 || any doesPossiblyReturn body2
Assert _ -> False
Expr' _ -> False
-- | `mapSubExprM` replaces all exprs in a given expr using a given function.
-- This may breaks various constraints.
mapSubExprM :: Monad m => (Expr' -> m Expr') -> Expr' -> m Expr'
mapSubExprM f = go
where
go e0 =
f . WithLoc' (loc' e0) =<< case value' e0 of
BoolOp e1 op e2 -> BoolOp <$> go e1 <*> return op <*> go e2
BinOp e1 op e2 -> BinOp <$> go e1 <*> return op <*> go e2
UnaryOp op e -> UnaryOp op <$> go e
Lambda args body -> Lambda args <$> go body
IfExp e1 e2 e3 -> IfExp <$> go e1 <*> go e2 <*> go e3
ListComp e (Comprehension x iter pred) -> do
e <- go e
x <- mapExprTargetM f x
iter <- go iter
pred <- mapM go pred
return $ ListComp e (Comprehension x iter pred)
Compare e1 op e2 -> Compare <$> go e1 <*> return op <*> go e2
Call g args -> Call <$> go g <*> mapM go args
Constant const -> return $ Constant const
Attribute e x -> Attribute <$> go e <*> pure x
Subscript e1 e2 -> Subscript <$> go e1 <*> go e2
Starred e -> Starred <$> go e
Name x -> return $ Name x
List t es -> List t <$> mapM go es
Tuple es -> Tuple <$> mapM go es
SubscriptSlice e from to step -> SubscriptSlice <$> go e <*> mapM go from <*> mapM go to <*> mapM go step
mapSubExpr :: (Expr' -> Expr') -> Expr' -> Expr'
mapSubExpr f = runIdentity . mapSubExprM (return . f)
listSubExprs :: Expr' -> [Expr']
listSubExprs = reverse . getDual . execWriter . mapSubExprM go
where
go e = do
tell $ Dual [e]
return e
mapExprTargetM :: Monad m => (Expr' -> m Expr') -> Target' -> m Target'
mapExprTargetM f x =
WithLoc' (loc' x) <$> case value' x of
SubscriptTrg x e -> SubscriptTrg <$> mapExprTargetM f x <*> f e
NameTrg x -> return $ NameTrg x
TupleTrg xs -> TupleTrg <$> mapM (mapExprTargetM f) xs
mapExprStatementM :: Monad m => (Expr' -> m Expr') -> Statement -> m Statement
mapExprStatementM f = \case
Return e -> Return <$> f e
AugAssign x op e -> AugAssign <$> mapExprTargetM f x <*> pure op <*> f e
AnnAssign x t e -> AnnAssign <$> mapExprTargetM f x <*> pure t <*> f e
For x iter body -> For <$> mapExprTargetM f x <*> f iter <*> mapM (mapExprStatementM f) body
If e body1 body2 -> If <$> f e <*> mapM (mapExprStatementM f) body1 <*> mapM (mapExprStatementM f) body2
Assert e -> Assert <$> f e
Expr' e -> Expr' <$> f e
mapExprToplevelStatementM :: Monad m => (Expr' -> m Expr') -> ToplevelStatement -> m ToplevelStatement
mapExprToplevelStatementM f = \case
ToplevelAnnAssign x t e -> ToplevelAnnAssign x t <$> f e
ToplevelFunctionDef g args ret body -> ToplevelFunctionDef g args ret <$> mapM (mapExprStatementM f) body
ToplevelAssert e -> ToplevelAssert <$> f e
mapExprM :: Monad m => (Expr' -> m Expr') -> Program -> m Program
mapExprM f = mapM (mapExprToplevelStatementM f)
listExprs :: Program -> [Expr']
listExprs = reverse . getDual . execWriter . mapExprM go
where
go e = do
tell $ Dual [e]
return e
mapStatementStatementM :: Monad m => (Statement -> m [Statement]) -> Statement -> m [Statement]
mapStatementStatementM f = \case
Return e -> f $ Return e
AugAssign x op e -> f $ AugAssign x op e
AnnAssign x t e -> f $ AnnAssign x t e
For x iter body -> do
body <- concat <$> mapM (mapStatementStatementM f) body
f $ For x iter body
If e body1 body2 -> do
body1 <- concat <$> mapM (mapStatementStatementM f) body1
body2 <- concat <$> mapM (mapStatementStatementM f) body2
f $ If e body1 body2
Assert e -> f $ Assert e
Expr' e -> f $ Expr' e
mapStatementToplevelStatementM :: Monad m => (Statement -> m [Statement]) -> ToplevelStatement -> m ToplevelStatement
mapStatementToplevelStatementM go = \case
ToplevelAnnAssign x t e -> return $ ToplevelAnnAssign x t e
ToplevelFunctionDef f args ret body -> do
body <- concat <$> mapM (mapStatementStatementM go) body
return $ ToplevelFunctionDef f args ret body
ToplevelAssert e -> return $ ToplevelAssert e
-- | `mapStatementM` replaces all statements in a given program using a given function.
-- This may breaks various constraints.
mapStatementM :: Monad m => (Statement -> m [Statement]) -> Program -> m Program
mapStatementM f = mapM (mapStatementToplevelStatementM f)
mapStatement :: (Statement -> [Statement]) -> Program -> Program
mapStatement f = runIdentity . mapStatementM (return . f)
mapLargeStatementM :: Monad m => (Expr' -> [Statement] -> [Statement] -> m [Statement]) -> (Target' -> Expr' -> [Statement] -> m [Statement]) -> Program -> m Program
mapLargeStatementM fIf fFor = mapStatementM go
where
go = \case
Return e -> return [Return e]
AugAssign x op e -> return [AugAssign x op e]
AnnAssign x t e -> return [AnnAssign x t e]
For x iter body -> fFor x iter body
If e body1 body2 -> fIf e body1 body2
Assert e -> return [Assert e]
Expr' e -> return [Expr' e]
mapLargeStatement :: (Expr' -> [Statement] -> [Statement] -> [Statement]) -> (Target' -> Expr' -> [Statement] -> [Statement]) -> Program -> Program
mapLargeStatement fIf fFor = runIdentity . mapLargeStatementM fIf' fFor'
where
fIf' e body1 body2 = return $ fIf e body1 body2
fFor' x iter body = return $ fFor x iter body
listStatements :: Program -> [Statement]
listStatements = reverse . getDual . execWriter . mapStatementM go
where
go stmt = do
tell $ Dual [stmt]
return [stmt]
mapStatementsToplevelStatementM :: Monad m => ([Statement] -> m [Statement]) -> ToplevelStatement -> m ToplevelStatement
mapStatementsToplevelStatementM go = \case
ToplevelAnnAssign x t e -> return $ ToplevelAnnAssign x t e
ToplevelFunctionDef f args ret body -> do
let go' = \case
Return e -> return [Return e]
AugAssign x op e -> return [AugAssign x op e]
AnnAssign x t e -> return [AnnAssign x t e]
For x iter body -> do
body <- go body
return [For x iter body]
If e body1 body2 -> do
body1 <- go body1
body2 <- go body2
return [If e body1 body2]
Assert e -> return [Assert e]
Expr' e -> return [Expr' e]
body <- concat <$> mapM (mapStatementStatementM go') body
body <- go body
return $ ToplevelFunctionDef f args ret body
ToplevelAssert e -> return $ ToplevelAssert e
mapStatementsM :: Monad m => ([Statement] -> m [Statement]) -> Program -> m Program
mapStatementsM f = mapM (mapStatementsToplevelStatementM f)
mapStatements :: ([Statement] -> [Statement]) -> Program -> Program
mapStatements f = runIdentity . mapStatementsM (return . f)
hasFunctionCall :: Expr' -> Bool
hasFunctionCall = any (check . value') . listSubExprs
where
check = \case
Call _ _ -> True
_ -> False
-- | `isSmallExpr` is true if the evaluation of a given expr trivially terminates.
isSmallExpr :: Expr' -> Bool
isSmallExpr = not . hasFunctionCall
dropLocation :: Expr' -> Expr'
dropLocation = mapSubExpr go
where
go (WithLoc' _ e) = withoutLoc e
targetVars :: Target' -> [VarName]
targetVars = nub . map value' . targetVars'
targetVars' :: Target' -> [VarName']
targetVars' (WithLoc' _ x) = case x of
SubscriptTrg x _ -> targetVars' x
NameTrg x -> [x]
TupleTrg xs -> concatMap targetVars' xs
hasSubscriptTrg :: Target' -> Bool
hasSubscriptTrg (WithLoc' _ x) = case x of
SubscriptTrg _ _ -> True
NameTrg _ -> False
TupleTrg xs -> any hasSubscriptTrg xs
hasBareNameTrg :: Target' -> Bool
hasBareNameTrg (WithLoc' _ x) = case x of
SubscriptTrg _ _ -> False
NameTrg _ -> True
TupleTrg xs -> any hasSubscriptTrg xs
exprToTarget :: Expr' -> Maybe Target'
exprToTarget e =
WithLoc' (loc' e) <$> case value' e of
Name x -> Just $ NameTrg x
Tuple es -> TupleTrg <$> mapM exprToTarget es
Subscript e1 e2 -> SubscriptTrg <$> exprToTarget e1 <*> pure e2
_ -> Nothing
targetToExpr :: Target' -> Expr'
targetToExpr e =
WithLoc' (loc' e) $ case value' e of
NameTrg x -> Name x
TupleTrg es -> Tuple (map targetToExpr es)
SubscriptTrg e1 e2 -> Subscript (targetToExpr e1) e2
toplevelMainDef :: [Statement] -> Program
toplevelMainDef body = [ToplevelFunctionDef (WithLoc' Nothing mainVarName) [] IntTy body]
mainVarName :: VarName
mainVarName = VarName (Just "main") Nothing Nothing
solveVarName :: VarName
solveVarName = VarName (Just "solve") Nothing Nothing
underscoreVarName :: VarName
underscoreVarName = VarName Nothing Nothing Nothing