module Language.PureScript.Sugar.ObjectWildcards
( desugarObjectConstructors
, desugarDecl
) where
import Prelude.Compat
import Control.Monad (forM)
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.Supply.Class
import Data.Foldable (toList)
import Data.List (foldl')
import Data.Maybe (catMaybes)
import Language.PureScript.AST
import Language.PureScript.Environment (NameKind(..))
import Language.PureScript.Errors
import Language.PureScript.Names
import Language.PureScript.PSString (PSString)
desugarObjectConstructors
:: forall m
. (MonadSupply m, MonadError MultipleErrors m)
=> Module
-> m Module
desugarObjectConstructors (Module ss coms mn ds exts) = Module ss coms mn <$> mapM desugarDecl ds <*> pure exts
desugarDecl :: forall m. (MonadSupply m, MonadError MultipleErrors m) => Declaration -> m Declaration
desugarDecl d = rethrowWithPosition (declSourceSpan d) $ fn d
where
(fn, _, _) = everywhereOnValuesTopDownM return desugarExpr return
desugarExpr :: Expr -> m Expr
desugarExpr AnonymousArgument = throwError . errorMessage $ IncorrectAnonymousArgument
desugarExpr (Parens b)
| b' <- stripPositionInfo b
, BinaryNoParens op val u <- b'
, isAnonymousArgument u = do arg <- freshIdent'
return $ Abs (VarBinder nullSourceSpan arg) $ App (App op val) (Var nullSourceSpan (Qualified Nothing arg))
| b' <- stripPositionInfo b
, BinaryNoParens op u val <- b'
, isAnonymousArgument u = do arg <- freshIdent'
return $ Abs (VarBinder nullSourceSpan arg) $ App (App op (Var nullSourceSpan (Qualified Nothing arg))) val
desugarExpr (Literal ss (ObjectLiteral ps)) = wrapLambdaAssoc (Literal ss . ObjectLiteral) ps
desugarExpr (ObjectUpdateNested obj ps) = transformNestedUpdate obj ps
desugarExpr (Accessor prop u)
| Just props <- peelAnonAccessorChain u = do
arg <- freshIdent'
return $ Abs (VarBinder nullSourceSpan arg) $ foldr Accessor (argToExpr arg) (prop:props)
desugarExpr (Case args cas) | any isAnonymousArgument args = do
argIdents <- forM args freshIfAnon
let args' = zipWith (`maybe` argToExpr) args argIdents
return $ foldr (Abs . VarBinder nullSourceSpan) (Case args' cas) (catMaybes argIdents)
desugarExpr (IfThenElse u t f) | any isAnonymousArgument [u, t, f] = do
u' <- freshIfAnon u
t' <- freshIfAnon t
f' <- freshIfAnon f
let if_ = IfThenElse (maybe u argToExpr u') (maybe t argToExpr t') (maybe f argToExpr f')
return $ foldr (Abs . VarBinder nullSourceSpan) if_ (catMaybes [u', t', f'])
desugarExpr e = return e
transformNestedUpdate :: Expr -> PathTree Expr -> m Expr
transformNestedUpdate obj ps = do
val <- freshIdent'
let valExpr = argToExpr val
if isAnonymousArgument obj
then Abs (VarBinder nullSourceSpan val) <$> wrapLambda (buildUpdates valExpr) ps
else wrapLambda (buildLet val . buildUpdates valExpr) ps
where
buildLet val = Let FromLet [ValueDecl (declSourceSpan d, []) val Public [] [MkUnguarded obj]]
buildUpdates :: Expr -> PathTree Expr -> Expr
buildUpdates val (PathTree vs) = ObjectUpdate val (goLayer [] <$> runAssocList vs) where
goLayer :: [PSString] -> (PSString, PathNode Expr) -> (PSString, Expr)
goLayer _ (key, Leaf expr) = (key, expr)
goLayer path (key, Branch (PathTree branch)) =
let path' = path ++ [key]
updates = goLayer path' <$> runAssocList branch
accessor = foldl' (flip Accessor) val path'
objectUpdate = ObjectUpdate accessor updates
in (key, objectUpdate)
wrapLambda :: forall t. Traversable t => (t Expr -> Expr) -> t Expr -> m Expr
wrapLambda mkVal ps = do
args <- traverse processExpr ps
return $ foldr (Abs . VarBinder nullSourceSpan) (mkVal (snd <$> args)) (catMaybes $ toList (fst <$> args))
where
processExpr :: Expr -> m (Maybe Ident, Expr)
processExpr e = do
arg <- freshIfAnon e
return (arg, maybe e argToExpr arg)
wrapLambdaAssoc :: ([(PSString, Expr)] -> Expr) -> [(PSString, Expr)] -> m Expr
wrapLambdaAssoc mkVal = wrapLambda (mkVal . runAssocList) . AssocList
stripPositionInfo :: Expr -> Expr
stripPositionInfo (PositionedValue _ _ e) = stripPositionInfo e
stripPositionInfo e = e
peelAnonAccessorChain :: Expr -> Maybe [PSString]
peelAnonAccessorChain (Accessor p e) = (p :) <$> peelAnonAccessorChain e
peelAnonAccessorChain (PositionedValue _ _ e) = peelAnonAccessorChain e
peelAnonAccessorChain AnonymousArgument = Just []
peelAnonAccessorChain _ = Nothing
isAnonymousArgument :: Expr -> Bool
isAnonymousArgument AnonymousArgument = True
isAnonymousArgument (PositionedValue _ _ e) = isAnonymousArgument e
isAnonymousArgument _ = False
freshIfAnon :: Expr -> m (Maybe Ident)
freshIfAnon u
| isAnonymousArgument u = Just <$> freshIdent'
| otherwise = return Nothing
argToExpr :: Ident -> Expr
argToExpr = Var nullSourceSpan . Qualified Nothing