hnix/src/Nix/Pretty.hs

360 lines
13 KiB
Haskell
Raw Normal View History

{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
2019-03-14 07:24:11 +01:00
{-# LANGUAGE MultiParamTypeClasses #-}
2018-04-07 21:02:50 +02:00
{-# LANGUAGE OverloadedStrings #-}
2019-03-14 07:24:11 +01:00
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
2019-03-14 07:24:11 +01:00
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeSynonymInstances #-}
2018-04-25 08:09:43 +02:00
{-# LANGUAGE ViewPatterns #-}
2018-04-07 21:02:50 +02:00
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# OPTIONS_GHC -Wno-orphans #-}
2018-04-07 21:02:50 +02:00
module Nix.Pretty where
2019-03-10 21:33:29 +01:00
import Control.Applicative ((<|>))
2019-03-14 07:24:11 +01:00
import Control.Comonad
import Control.Monad
import Control.Monad.Free
2018-04-07 21:02:50 +02:00
import Data.Fix
import Data.HashMap.Lazy (toList)
import qualified Data.HashMap.Lazy as M
import qualified Data.HashSet as HashSet
import Data.List (isPrefixOf, sort)
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE
2018-07-28 20:17:37 +02:00
import Data.Maybe (isJust, fromMaybe)
2018-04-07 21:02:50 +02:00
import Data.Text (pack, unpack, replace, strip)
import qualified Data.Text as Text
2018-11-17 05:51:18 +01:00
import Data.Text.Prettyprint.Doc
2019-03-14 07:24:11 +01:00
import Data.Void
2018-04-07 21:02:50 +02:00
import Nix.Atoms
2019-03-14 07:24:11 +01:00
import Nix.Cited
2018-04-07 21:02:50 +02:00
import Nix.Expr
2019-03-14 07:24:11 +01:00
import Nix.Normal
import Nix.Parser
import Nix.String
import Nix.Strings
2018-04-07 21:02:50 +02:00
import Nix.Thunk
#if ENABLE_TRACING
import Nix.Utils
#else
import Nix.Utils hiding ((<$>))
#endif
import Nix.Value
2018-04-07 21:02:50 +02:00
import Prelude hiding ((<$>))
2019-03-10 21:33:29 +01:00
import Text.Read (readMaybe)
2018-04-07 21:02:50 +02:00
-- | This type represents a pretty printed nix expression
-- together with some information about the expression.
2018-11-17 05:51:18 +01:00
data NixDoc ann = NixDoc
2018-04-07 21:02:50 +02:00
{ -- | The rendered expression, without any parentheses.
2018-11-17 05:51:18 +01:00
withoutParens :: Doc ann
2018-04-07 21:02:50 +02:00
-- | The root operator is the operator at the root of
-- the expression tree. For example, in '(a * b) + c', '+' would be the root
-- operator. It is needed to determine if we need to wrap the expression in
-- parentheses.
, rootOp :: OperatorInfo
, wasPath :: Bool -- This is needed so that when a path is used in a selector path
-- we can add brackets appropiately
2018-04-07 21:02:50 +02:00
}
2018-11-17 05:51:18 +01:00
mkNixDoc :: Doc ann -> OperatorInfo -> NixDoc ann
mkNixDoc d o = NixDoc { withoutParens = d, rootOp = o, wasPath = False }
2018-04-07 21:02:50 +02:00
-- | A simple expression is never wrapped in parentheses. The expression
-- behaves as if its root operator had a precedence higher than all
-- other operators (including function application).
2018-11-17 05:51:18 +01:00
simpleExpr :: Doc ann -> NixDoc ann
simpleExpr d = mkNixDoc d (OperatorInfo minBound NAssocNone "simple expr")
2018-11-17 05:51:18 +01:00
pathExpr :: Doc ann -> NixDoc ann
pathExpr d = (simpleExpr d) { wasPath = True }
2018-04-07 21:02:50 +02:00
-- | An expression that behaves as if its root operator had a precedence lower
-- than all other operators. That ensures that the expression is wrapped in
-- parantheses in almost always, but it's still rendered without parentheses
-- in cases where parentheses are never required (such as in the LHS of a
-- binding).
2018-11-17 05:51:18 +01:00
leastPrecedence :: Doc ann -> NixDoc ann
leastPrecedence =
flip mkNixDoc $ OperatorInfo maxBound NAssocNone "least precedence"
2018-04-07 21:02:50 +02:00
appOp :: OperatorInfo
appOp = getBinaryOperator NApp
2018-04-07 21:02:50 +02:00
appOpNonAssoc :: OperatorInfo
appOpNonAssoc = (getBinaryOperator NApp) { associativity = NAssocNone }
selectOp :: OperatorInfo
selectOp = getSpecialOperator NSelectOp
hasAttrOp :: OperatorInfo
hasAttrOp = getSpecialOperator NHasAttrOp
2018-04-07 21:02:50 +02:00
2018-11-17 05:51:18 +01:00
wrapParens :: OperatorInfo -> NixDoc ann -> Doc ann
2018-04-07 21:02:50 +02:00
wrapParens op sub
| precedence (rootOp sub) < precedence op = withoutParens sub
2018-04-07 21:02:50 +02:00
| precedence (rootOp sub) == precedence op
&& associativity (rootOp sub) == associativity op
&& associativity op /= NAssocNone = withoutParens sub
| otherwise = parens $ withoutParens sub
-- Used in the selector case to print a path in a selector as
-- "${./abc}"
2018-11-17 05:51:18 +01:00
wrapPath :: OperatorInfo -> NixDoc ann -> Doc ann
wrapPath op sub =
2018-11-17 05:51:18 +01:00
if wasPath sub
then dquotes $ "$" <> braces (withoutParens sub)
else wrapParens op sub
2018-11-17 05:51:18 +01:00
prettyString :: NString (NixDoc ann)-> Doc ann
2018-04-07 21:02:50 +02:00
prettyString (DoubleQuoted parts) = dquotes . hcat . map prettyPart $ parts
2018-11-17 05:51:18 +01:00
where prettyPart (Plain t) = pretty . concatMap escape . unpack $ t
prettyPart EscapedNewline = "''\\n"
prettyPart (Antiquoted r) = "$" <> braces (withoutParens r)
2018-04-07 21:02:50 +02:00
escape '"' = "\\\""
escape x = maybe [x] (('\\':) . (:[])) $ toEscapeCode x
prettyString (Indented _ parts)
2018-11-17 05:51:18 +01:00
= group $ nest 2 $ vcat [dsquote, content, dsquote]
2018-04-07 21:02:50 +02:00
where
2018-11-17 05:51:18 +01:00
dsquote = squote <> squote
2018-04-07 21:02:50 +02:00
content = vsep . map prettyLine . stripLastIfEmpty . splitLines $ parts
stripLastIfEmpty = reverse . f . reverse where
f ([Plain t] : xs) | Text.null (strip t) = xs
f xs = xs
prettyLine = hcat . map prettyPart
2018-11-17 05:51:18 +01:00
prettyPart (Plain t) = pretty . unpack . replace "${" "''${" . replace "''" "'''" $ t
prettyPart EscapedNewline = "\\n"
prettyPart (Antiquoted r) = "$" <> braces (withoutParens r)
2018-04-07 21:02:50 +02:00
2018-11-17 05:51:18 +01:00
prettyParams :: Params (NixDoc ann) -> Doc ann
prettyParams (Param n) = pretty $ unpack n
2018-04-07 21:02:50 +02:00
prettyParams (ParamSet s v mname) = prettyParamSet s v <> case mname of
2018-11-17 05:51:18 +01:00
Nothing -> mempty
Just name | Text.null name -> mempty
| otherwise -> "@" <> pretty (unpack name)
2018-04-07 21:02:50 +02:00
2018-11-17 05:51:18 +01:00
prettyParamSet :: ParamSet (NixDoc ann) -> Bool -> Doc ann
2018-04-07 21:02:50 +02:00
prettyParamSet args var =
2018-04-20 02:05:20 +02:00
encloseSep (lbrace <> space) (align (space <> rbrace)) sep (map prettySetArg args ++ prettyVariadic)
2018-04-07 21:02:50 +02:00
where
prettySetArg (n, maybeDef) = case maybeDef of
2018-11-17 05:51:18 +01:00
Nothing -> pretty (unpack n)
Just v -> pretty (unpack n) <+> "?" <+> withoutParens v
prettyVariadic = ["..." | var]
2018-04-07 21:02:50 +02:00
sep = align (comma <> space)
2018-11-17 05:51:18 +01:00
prettyBind :: Binding (NixDoc ann) -> Doc ann
2018-07-28 20:17:37 +02:00
prettyBind (NamedVar n v _p) =
2018-04-07 21:02:50 +02:00
prettySelector n <+> equals <+> withoutParens v <> semi
2018-07-28 20:17:37 +02:00
prettyBind (Inherit s ns _p)
2018-11-17 05:51:18 +01:00
= "inherit" <+> scope <> align (fillSep (map prettyKeyName ns)) <> semi
where scope = maybe mempty ((<> space) . parens . withoutParens) s
2018-04-07 21:02:50 +02:00
2018-11-17 05:51:18 +01:00
prettyKeyName :: NKeyName (NixDoc ann) -> Doc ann
prettyKeyName (StaticKey "") = dquotes ""
2018-07-28 20:17:37 +02:00
prettyKeyName (StaticKey key)
2018-11-17 05:51:18 +01:00
| HashSet.member key reservedNames = dquotes $ pretty $ unpack key
prettyKeyName (StaticKey key) = pretty . unpack $ key
2018-04-11 03:45:57 +02:00
prettyKeyName (DynamicKey key) =
runAntiquoted (DoubleQuoted [Plain "\n"])
2018-11-17 05:51:18 +01:00
prettyString (("$" <>) . braces . withoutParens) key
2018-04-07 21:02:50 +02:00
2018-11-17 05:51:18 +01:00
prettySelector :: NAttrPath (NixDoc ann) -> Doc ann
prettySelector = hcat . punctuate dot . map prettyKeyName . NE.toList
2018-04-07 21:02:50 +02:00
2018-11-17 05:51:18 +01:00
prettyAtom :: NAtom -> NixDoc ann
prettyAtom atom = simpleExpr $ pretty $ unpack $ atomText atom
2018-04-07 21:02:50 +02:00
2019-03-14 18:56:20 +01:00
{-
2018-11-17 05:51:18 +01:00
prettyNix :: NExpr -> Doc ann
prettyNix = withoutParens . cata exprFNixDoc
2019-03-14 18:56:20 +01:00
prettyOriginExpr :: HasCitations1 t (NValue t f m) m f
=> NExprLocF (Maybe (NValue t f m)) -> Doc ann
prettyOriginExpr = withoutParens . go
where
go = exprFNixDoc . annotated . getCompose . fmap render
2018-11-17 05:51:18 +01:00
render Nothing = simpleExpr $ "_"
2019-03-14 07:24:11 +01:00
render (Just (reverse . citations1 -> p:_)) = go (_originExpr p)
render _ = simpleExpr "?"
-- render (Just (NValue (citations -> ps))) =
2018-11-17 05:51:18 +01:00
-- simpleExpr $ foldr ((\x y -> vsep [x, y]) . parens . indent 2 . withoutParens
2018-04-25 08:09:43 +02:00
-- . go . originExpr)
-- mempty (reverse ps)
2018-11-17 05:51:18 +01:00
exprFNixDoc :: NExprF (NixDoc ann) -> NixDoc ann
exprFNixDoc = \case
NConstant atom -> prettyAtom atom
NStr str -> simpleExpr $ prettyString str
NList [] -> simpleExpr $ lbracket <> rbracket
2018-11-17 05:51:18 +01:00
NList xs -> simpleExpr $ group $ nest 2 $ vsep $ concat $
[ [lbracket]
, map (wrapParens appOpNonAssoc) xs
, [rbracket]
]
NSet [] -> simpleExpr $ lbrace <> rbrace
2018-11-17 05:51:18 +01:00
NSet xs -> simpleExpr $ group $ nest 2 $ vsep $ concat $
[ [lbrace]
, map prettyBind xs
, [rbrace]
]
NRecSet [] -> simpleExpr $ recPrefix <> lbrace <> rbrace
2018-11-17 05:51:18 +01:00
NRecSet xs -> simpleExpr $ group $ nest 2 $ vsep $ concat $
[ [recPrefix <> lbrace]
, map prettyBind xs
, [rbrace]
]
NAbs args body -> leastPrecedence $ nest 2 $ vsep $
[ prettyParams args <> colon
, withoutParens body
]
NBinary NApp fun arg ->
mkNixDoc (wrapParens appOp fun <+> wrapParens appOpNonAssoc arg) appOp
NBinary op r1 r2 -> flip mkNixDoc opInfo $ hsep
[ wrapParens (f NAssocLeft) r1
2018-11-17 05:51:18 +01:00
, pretty $ unpack $ operatorName opInfo
, wrapParens (f NAssocRight) r2
]
where
opInfo = getBinaryOperator op
f x | associativity opInfo /= x = opInfo { associativity = NAssocNone }
| otherwise = opInfo
NUnary op r1 ->
2018-11-17 05:51:18 +01:00
mkNixDoc (pretty (unpack (operatorName opInfo)) <> wrapParens opInfo r1) opInfo
where opInfo = getUnaryOperator op
NSelect r' attr o ->
(if isJust o then leastPrecedence else flip mkNixDoc selectOp) $
wrapPath selectOp r <> dot <> prettySelector attr <> ordoc
where
r = flip mkNixDoc selectOp $ wrapParens appOpNonAssoc r'
2018-11-17 05:51:18 +01:00
ordoc = maybe mempty (((space <> "or") <+>) . wrapParens appOpNonAssoc) o
NHasAttr r attr ->
2018-11-17 05:51:18 +01:00
mkNixDoc (wrapParens hasAttrOp r <+> "?" <+> prettySelector attr) hasAttrOp
NEnvPath p -> simpleExpr $ pretty ("<" ++ p ++ ">")
NLiteralPath p -> pathExpr $ pretty $ case p of
"./" -> "./."
"../" -> "../."
".." -> "../."
txt | "/" `isPrefixOf` txt -> txt
| "~/" `isPrefixOf` txt -> txt
| "./" `isPrefixOf` txt -> txt
| "../" `isPrefixOf` txt -> txt
| otherwise -> "./" ++ txt
2018-11-17 05:51:18 +01:00
NSym name -> simpleExpr $ pretty (unpack name)
NLet binds body -> leastPrecedence $ group $ vsep $
[ "let"
, indent 2 (vsep (map prettyBind binds))
, "in" <+> withoutParens body
]
NIf cond trueBody falseBody -> leastPrecedence $
2018-11-17 05:51:18 +01:00
group $ nest 2 $ vsep $
[ "if" <+> withoutParens cond
, align ("then" <+> withoutParens trueBody)
, align ("else" <+> withoutParens falseBody)
]
NWith scope body -> leastPrecedence $ vsep $
[ "with" <+> withoutParens scope <> semi
, align $ withoutParens body
]
NAssert cond body -> leastPrecedence $ vsep $
[ "assert" <+> withoutParens cond <> semi
, align $ withoutParens body
]
2018-11-18 00:20:59 +01:00
NSynHole name -> simpleExpr $ pretty ("^" <> unpack name)
where
2018-11-17 05:51:18 +01:00
recPrefix = "rec" <> space
2018-04-07 21:02:50 +02:00
2019-03-14 18:56:20 +01:00
valueToExpr :: MonadDataContext f m => NValueNF t f m -> NExpr
2019-03-14 07:24:11 +01:00
valueToExpr = nValueF
(const (mkStr (principledMakeNixStringWithoutContext "<CYCLE>")))
(phi . extract)
where
2019-03-14 07:24:11 +01:00
phi (NVConstantF a) = Fix $ NConstant a
phi (NVStrF ns) = mkStr ns
phi (NVListF l) = Fix $ NList (fmap valueToExpr l)
phi (NVSetF s p) = Fix $ NSet
[ NamedVar (StaticKey k :| []) v (fromMaybe nullPos (M.lookup k p))
2019-03-14 07:24:11 +01:00
| (k, v) <- toList (fmap valueToExpr s) ]
phi (NVClosureF _ _) = Fix . NSym . pack $ "<closure>"
phi (NVPathF p) = Fix $ NLiteralPath p
phi (NVBuiltinF name _) = Fix . NSym . pack $ "builtins." ++ name
mkStr ns = Fix $ NStr $ DoubleQuoted [Plain (hackyStringIgnoreContext ns)]
2018-04-07 21:02:50 +02:00
2019-03-14 18:56:20 +01:00
prettyNValueNF :: MonadDataContext f m => NValueNF t f m -> Doc ann
2018-11-16 19:29:20 +01:00
prettyNValueNF = prettyNix . valueToExpr
2019-03-14 18:56:20 +01:00
printNix :: MonadDataContext f m => NValueNF t f m -> String
2019-03-14 07:24:11 +01:00
printNix = iterNValueNF (const "<CYCLE>") (phi . extract)
where
2019-03-14 18:56:20 +01:00
phi :: NValueF (NValue t f m) m String -> String
phi (NVConstantF a) = unpack $ atomText a
phi (NVStrF ns) = show $ hackyStringIgnoreContext ns
phi (NVListF l) = "[ " ++ unwords l ++ " ]"
phi (NVSetF s _) =
2019-03-10 21:33:29 +01:00
"{ " ++ concat [ check (unpack k) ++ " = " ++ v ++ "; "
| (k, v) <- sort $ toList s ] ++ "}"
2019-03-10 21:33:29 +01:00
where
check v =
fromMaybe v
((fmap (surround . show) (readMaybe v :: Maybe Int))
<|> (fmap (surround . show) (readMaybe v :: Maybe Float)))
where
surround s = "\"" ++ s ++ "\""
phi NVClosureF {} = "<<lambda>>"
phi (NVPathF fp) = fp
phi (NVBuiltinF name _) = "<<builtin " ++ name ++ ">>"
2019-03-14 07:24:11 +01:00
prettyNValue
2019-03-14 18:56:20 +01:00
:: (MonadThunk t m (NValue t f m),
2019-03-14 07:24:11 +01:00
MonadDataContext f m)
2019-03-14 18:56:20 +01:00
=> NValue t f m -> m (Doc ann)
2019-03-14 07:24:11 +01:00
prettyNValue = fmap prettyNValueNF . removeEffectsM
2019-03-14 18:56:20 +01:00
instance HasCitations1 t (NValue t f m) m f
=> HasCitations t (NValue t f m) m (NValue t f m) where
2019-03-14 07:24:11 +01:00
citations (NValue (Fix (Compose f))) = citations1 f
2019-03-14 18:56:20 +01:00
prettyNValueProv :: (HasCitations1 t (NValue t f m) m f,
2019-03-14 07:24:11 +01:00
MonadDataContext f m)
2019-03-14 18:56:20 +01:00
=> NValue t f m -> m (Doc ann)
2019-03-14 07:24:11 +01:00
prettyNValueProv (NValue (Fix (Compose nv))) = do
let ps = citations nv
Compose v = extract nv
case ps of
[] -> prettyNValueF v
ps -> do
v' <- prettyNValueF v
pure $ fillSep $
[ v'
, indent 2 $ parens $ mconcat
$ "from: "
: map (prettyOriginExpr . _originExpr) ps
]
prettyNThunk
2019-03-14 18:56:20 +01:00
:: forall t f m ann.
(HasCitations1 t (NValue t f m) m t,
HasCitations1 t (NValue t f m) m f,
MonadThunk t m (NValue t f m),
2019-03-14 07:24:11 +01:00
MonadDataContext f m)
2019-03-14 18:56:20 +01:00
=> t -> m (Doc ann)
2019-03-14 07:24:11 +01:00
prettyNThunk t = do
2019-03-14 18:56:20 +01:00
let ps = citations1 @t @(NValue t f m) @m @t t
2019-03-14 07:24:11 +01:00
v' <- prettyNValueNF <$> dethunk t
pure $ fillSep $
[ v'
, indent 2 $ parens $ mconcat
$ "thunk from: "
: map (prettyOriginExpr . _originExpr) ps
]
2019-03-14 18:56:20 +01:00
-}