{-# language NoImplicitPrelude #-} {-# language OverloadedStrings #-} {-# language ViewPatterns #-} module Language.Elm.Pretty ( -- * Modules modules , module_ -- * Environments , Environment(..) , emptyEnvironment , extend -- * Pretty-printing names , local , field , constructor , moduleName , qualified -- * Pretty-printing definitions , definition -- * Pretty-printing expressions , expression -- * Pretty-printing pattern , pattern -- * Pretty-printing types , type_ ) where import Protolude hiding (Type, local, list, moduleName) import qualified Bound import qualified Bound.Var as Bound import Data.HashMap.Lazy (HashMap) import qualified Data.HashMap.Lazy as HashMap import Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import Data.String import Data.Text.Prettyprint.Doc import Language.Elm.Definition (Definition) import qualified Language.Elm.Definition as Definition import Language.Elm.Expression (Expression) import qualified Language.Elm.Expression as Expression import qualified Language.Elm.Name as Name import Language.Elm.Pattern (Pattern) import qualified Language.Elm.Pattern as Pattern import Language.Elm.Type (Type) import qualified Language.Elm.Type as Type ------------------------------------------------------------------------------- -- * Modules -- | Group the given definitions by their defining module, and generate an Elm -- module for each group. modules :: [Definition] -> HashMap Name.Module (Doc ann) modules defs = let defsByModule = foldl' (HashMap.unionWith (<>)) mempty [ HashMap.singleton m [def] | def <- defs , let (Name.Qualified m _) = Definition.name def ] in HashMap.mapWithKey module_ defsByModule -- | Generate an Elm module containing the given definitions. module_ :: Name.Module -> [Definition] -> Doc ann module_ mname defs = let usedNames = HashSet.fromList [ Name.Local name | Name.Qualified _ name <- Definition.name <$> defs ] env = (emptyEnvironment mname) { freshLocals = filter (not . (`HashSet.member` usedNames)) $ freshLocals (emptyEnvironment mname) } imports = sort $ HashSet.toList $ flip HashSet.difference defaultImports $ HashSet.filter (/= mname) $ HashSet.map (\(Name.Qualified m _) -> m) $ HashSet.filter (isNothing . defaultImport) $ foldMap (Definition.foldMapGlobals HashSet.singleton) defs in "module" <+> moduleName mname <+> "exposing (..)" <> line <> line <> mconcat ["import" <+> moduleName import_ <> line | import_ <- imports] <> line <> line <> mconcat (intersperse (line <> line <> line) [definition env def | def <- defs]) defaultImports :: HashSet Name.Module defaultImports = HashSet.fromList [ ["Basics"] , ["List"] , ["Maybe"] , ["Result"] , ["String"] , ["Char"] , ["Tuple"] , ["Debug"] , ["Platform"] , ["Cmd"] , ["Sub"] ] ------------------------------------------------------------------------------- -- * Environments -- | A pretty-printing environment with local variables in @v@. data Environment v = Environment { locals :: v -> Name.Local , freshLocals :: [Name.Local] , currentModule :: Name.Module } emptyEnvironment :: Name.Module -> Environment Void emptyEnvironment m = Environment { locals = absurd , freshLocals = (fromString . pure <$> ['a'..'z']) ++ [fromString $ [x] <> show n | x <- ['a'..'z'], n <- [(0 :: Int)..]] , currentModule = m } extend :: Environment v -> (Environment (Bound.Var () v), Name.Local) extend env = case freshLocals env of [] -> panic "Language.Elm.Pretty no locals" fresh:freshLocals' -> ( env { locals = Bound.unvar (\() -> fresh) (locals env) , freshLocals = freshLocals' } , fresh ) extendPat :: Environment v -> Pattern Int -> Environment (Bound.Var Int v) extendPat env pat = let occurrencesSet = foldMap HashSet.singleton pat occurrences = HashSet.toList occurrencesSet bindings = HashMap.fromList $ zip occurrences $ freshLocals env freshLocals' = drop (length occurrences) $ freshLocals env lookupVar i = case HashMap.lookup i bindings of Nothing -> panic "Unbound pattern variable" Just v -> v in env { locals = Bound.unvar lookupVar (locals env) , freshLocals = freshLocals' } ------------------------------------------------------------------------------- -- * Pretty-printing names local :: Name.Local -> Doc ann local (Name.Local l) = pretty l field :: Name.Field -> Doc ann field (Name.Field f) = pretty f constructor :: Name.Constructor -> Doc ann constructor (Name.Constructor c) = pretty c moduleName :: Name.Module -> Doc ann moduleName ms = mconcat (intersperse dot $ pretty <$> ms) qualified :: Environment v -> Name.Qualified -> Doc ann qualified env name@(Name.Qualified ms l) = case defaultImport name of Nothing | ms == currentModule env -> pretty l | otherwise -> case ms of [] -> pretty l _ -> moduleName ms <> dot <> pretty l Just l' -> local l' defaultImport :: Name.Qualified -> Maybe Name.Local defaultImport qname = case qname of Name.Qualified ["Basics"] name -> Just $ Name.Local name "Cmd.Cmd" -> Just "Cmd" "List.List" -> Just "List" "List.::" -> Just "::" "Maybe.Maybe" -> Just "Maybe" "Maybe.Nothing" -> Just "Nothing" "Maybe.Just" -> Just "Just" "Result.Result" -> Just "Result" "Result.Ok" -> Just "Ok" "Result.Err" -> Just "Err" "String.String" -> Just "String" "Char.Char" -> Just "Char" _ -> Nothing fixity :: Name.Qualified -> Maybe (Int, Int, Int) fixity qname = case qname of "Basics.>>" -> leftAssoc 9 "Basics.<<" -> rightAssoc 9 "Basics.^" -> rightAssoc 8 "Basics.*" -> leftAssoc 7 "Basics./" -> leftAssoc 7 "Basics.//" -> leftAssoc 7 "Basics.%" -> leftAssoc 7 "Basics.+" -> leftAssoc 6 "Basics.-" -> leftAssoc 6 "Parser.|=" -> leftAssoc 5 Name.Qualified ["Parser"] "|." -> leftAssoc 6 "Basics.++" -> rightAssoc 5 "Basics.++" -> rightAssoc 5 "List.::" -> rightAssoc 5 "Basics.==" -> noneAssoc 4 "Basics./=" -> noneAssoc 4 "Basics.<" -> noneAssoc 4 "Basics.>" -> noneAssoc 4 "Basics.<=" -> noneAssoc 4 "Basics.>=" -> noneAssoc 4 "Basics.&&" -> rightAssoc 3 "Basics.||" -> leftAssoc 3 "Basics.|>" -> leftAssoc 0 "Basics.<|" -> rightAssoc 0 "Basics.," -> Just (0, -1, 0) _ -> Nothing where leftAssoc n = Just (n, n, n + 1) rightAssoc n = Just (n, n, n + 1) noneAssoc n = Just (n + 1, n, n + 1) twoLineOperator :: Name.Qualified -> Bool twoLineOperator qname = case qname of "Basics.>>" -> True "Basics.<<" -> True "Basics.|>" -> True "Basics.<|" -> True _ -> False ------------------------------------------------------------------------------- -- * Definitions definition :: Environment Void -> Definition -> Doc ann definition env def = case def of Definition.Constant (Name.Qualified _ name) t e -> let (names, body) = lambdas env e in pretty name <+> ":" <+> nest 4 (type_ env 0 t) <> line <> (case names of [] -> pretty name <+> "=" _ -> pretty name <+> hsep (local <$> names) <+> "=") <> line <> indent 4 body Definition.Type (Name.Qualified _ name) constrs -> "type" <+> pretty name <> line <> indent 4 ("=" <+> mconcat (intersperse (line <> "| ") [constructor c <+> hsep (type_ env (appPrec + 1) <$> ts) | (c, ts) <- constrs])) Definition.Alias (Name.Qualified _ name) t -> "type alias" <+> pretty name <+> "=" <> line <> indent 4 (type_ env 0 t) ------------------------------------------------------------------------------- -- * Expressions expression :: Environment v -> Int -> Expression v -> Doc ann expression env prec expr = case expr of Expression.Var var -> local $ locals env var (Expression.appsView -> (Expression.Proj f, arg:args)) -> atomApps (expression env) prec (expression env projPrec arg <> dot <> field f) args (Expression.appsView -> (Expression.Global qname@(Name.Qualified _ name), args)) -> case fixity qname of Nothing -> atomApps (expression env) prec (qualified env qname) args Just (leftPrec, opPrec, rightPrec) -> case args of [arg1, arg2] -> parensWhen (prec > opPrec) $ expression env leftPrec arg1 <+> pretty name <> (if twoLineOperator qname then line else space) <> expression env rightPrec arg2 arg1:arg2:args' -> apps (expression env) prec (Expression.apps (Expression.Global qname) [arg1, arg2]) args' _ -> atomApps (expression env) prec (parens $ pretty name) args (Expression.appsView -> (fun, args@(_:_))) -> apps (expression env) prec fun args Expression.Global _ -> panic "Language.Elm.Pretty expression Global" Expression.App {} -> panic "Language.Elm.Pretty expression App" Expression.Let {} -> parensWhen (prec > letPrec) $ let (bindings, body) = lets env expr in "let" <> line <> indent 4 (mconcat $ intersperse (line <> line) bindings) <> line <> "in" <> line <> body Expression.Lam {} -> parensWhen (prec > lamPrec) $ let (names, body) = lambdas env expr in "\\" <> hsep (local <$> names) <+> "->" <+> body Expression.Record fields -> encloseSep "{ " " }" ", " [ field f <+> "=" <+> expression env 0 expr' | (f, expr') <- fields ] Expression.Proj f -> "." <> field f Expression.Case expr' branches -> parensWhen (prec > casePrec) $ "case" <+> expression env 0 expr' <+> "of" <> line <> indent 4 ( mconcat $ intersperse (line <> line) $ [ pattern env' 0 pat <+> "->" <> line <> indent 4 (expression env' 0 (Bound.fromScope scope)) | (pat, scope) <- branches , let env' = extendPat env pat ] ) Expression.If expr' true false -> parensWhen (prec > ifPrec) $ "if" <+> expression env 0 expr' <+> "then" <> line <> indent 4 (expression env 0 true) <> line <> line <> "else" <> line <> indent 4 (expression env 0 false) Expression.List exprs -> list $ expression env 0 <$> exprs Expression.String s -> "\"" <> pretty s <> "\"" Expression.Int i -> pretty i Expression.Float f -> pretty f lets :: Environment v -> Expression v -> ([Doc ann], Doc ann) lets env expr = case expr of Expression.Let expr' scope -> let (env', name) = extend env (bindings, body) = lets env' (Bound.fromScope scope) binding = local name <+> "=" <> line <> indent 4 (expression env 0 expr') in (binding : bindings , body) _ -> ([], expression env letPrec expr) lambdas :: Environment v -> Expression v -> ([Name.Local], Doc ann) lambdas env expr = case expr of Expression.Lam scope -> let (env', name) = extend env (names, body) = lambdas env' (Bound.fromScope scope) in (name : names, body) _ -> ([], expression env lamPrec expr) ------------------------------------------------------------------------------- -- * Patterns pattern :: Environment (Bound.Var Int v) -> Int -> Pattern Int -> Doc ann pattern env prec pat = case pat of Pattern.Var var -> local $ locals env (Bound.B var) Pattern.Wildcard -> "_" Pattern.Con con [] -> qualified env con Pattern.Con con@(Name.Qualified _ name) pats -> case fixity con of Nothing -> parensWhen (prec > appPrec) $ qualified env con <+> hsep (pattern env (appPrec + 1) <$> pats) Just (leftPrec, opPrec, rightPrec) -> case pats of [pat1, pat2] -> parensWhen (prec > opPrec) $ pattern env leftPrec pat1 <+> pretty name <> (if twoLineOperator con then line else space) <> pattern env rightPrec pat2 pat1:pat2:pats' -> apps (pattern env) prec (Pattern.Con con [pat1, pat2]) pats' _ -> parensWhen (prec > appPrec) $ qualified env con <+> hsep (pattern env (appPrec + 1) <$> pats) Pattern.List pats -> list $ pattern env 0 <$> pats Pattern.String s -> "\"" <> pretty s <> "\"" Pattern.Int i -> pretty i Pattern.Float f -> pretty f ------------------------------------------------------------------------------- -- * Types type_ :: Environment v -> Int -> Type v -> Doc ann type_ env prec t = case t of Type.Var var -> local $ locals env var (Type.appsView -> (Type.Global qname@(Name.Qualified _ name), args)) -> case fixity qname of Nothing -> atomApps (type_ env) prec (qualified env qname) args Just (leftPrec, opPrec, rightPrec) -> case args of [arg1, arg2] -> parensWhen (prec > opPrec) $ type_ env leftPrec arg1 <+> pretty name <> (if twoLineOperator qname then line else space) <> type_ env rightPrec arg2 arg1:arg2:args' -> apps (type_ env) prec (Type.apps (Type.Global qname) [arg1, arg2]) args' _ -> atomApps (type_ env) prec (parens $ pretty name) args (Type.appsView -> (fun, args@(_:_))) -> apps (type_ env) prec fun args Type.Global _ -> panic "Language.Elm.Pretty type_ Global" Type.App {} -> panic "Language.Elm.Pretty type_ App" Type.Fun t1 t2 -> parensWhen (prec > funPrec) $ type_ env (funPrec + 1) t1 <+> "->" <+> type_ env funPrec t2 Type.Record fields -> encloseSep "{ " " }" ", " [ field f <+> ":" <+> type_ env 0 type' | (f, type') <- fields ] ------------------------------------------------------------------------------- -- Utils apps :: (Int -> a -> Doc ann) -> Int -> a -> [a] -> Doc ann apps f prec fun args = case args of [] -> f prec fun _ -> parensWhen (prec > appPrec) $ f appPrec fun <+> hsep (f (appPrec + 1) <$> args) atomApps :: (Int -> a -> Doc ann) -> Int -> Doc ann -> [a] -> Doc ann atomApps f prec fun args = case args of [] -> fun _ -> parensWhen (prec > appPrec) $ fun <+> hsep (f (appPrec + 1) <$> args) parensWhen :: Bool -> Doc ann -> Doc ann parensWhen b = if b then parens else identity appPrec, letPrec, lamPrec, casePrec, ifPrec, funPrec, projPrec :: Int appPrec = 10 letPrec = 0 lamPrec = 0 casePrec = 0 ifPrec = 0 funPrec = 0 projPrec = 11