diff --git a/nixfmt.cabal b/nixfmt.cabal index 57073e6f..696477b5 100644 --- a/nixfmt.cabal +++ b/nixfmt.cabal @@ -1,55 +1,53 @@ -cabal-version: 2.0 -name: nixfmt -version: 0.6.0 -synopsis: Official formatter for Nix code +cabal-version: 3.0 +name: nixfmt +version: 0.6.0 +synopsis: Official formatter for Nix code description: A formatter for Nix that ensures consistent and clear formatting by forgetting almost all existing formatting during parsing. -homepage: https://github.com/NixOS/nixfmt -bug-reports: https://github.com/NixOS/nixfmt/issues -license: MPL-2.0 -license-file: LICENSE -author: Serokell and nixfmt contributors -copyright: Serokell and nixfmt contributors -category: Development -build-type: Simple -extra-source-files: README.md, CHANGELOG.md + +homepage: https://github.com/NixOS/nixfmt +bug-reports: https://github.com/NixOS/nixfmt/issues +license: MPL-2.0 +license-file: LICENSE +author: Serokell and nixfmt contributors +copyright: Serokell and nixfmt contributors +category: Development +build-type: Simple +extra-source-files: + CHANGELOG.md + README.md source-repository head type: git location: git://github.com/NixOS/nixfmt.git executable nixfmt - main-is: Main.hs + main-is: Main.hs other-modules: Paths_nixfmt - System.IO.Utf8 System.IO.Atomic - autogen-modules: - Paths_nixfmt - other-extensions: DeriveDataTypeable - hs-source-dirs: main + System.IO.Utf8 + + autogen-modules: Paths_nixfmt + hs-source-dirs: main build-depends: - base >= 4.12.0 && < 4.21 + , base >=4.12.0 && <4.21 , bytestring - , cmdargs >= 0.10.20 && < 0.11 + , cmdargs >=0.10.20 && <0.11 + , directory >=1.3.3 && <1.4 , file-embed + , filepath >=1.4.2 && <1.5 , nixfmt - , unix >= 2.7.2 && < 2.9 - , text >= 1.2.3 && < 2.2 + , safe-exceptions >=0.1.7 && <0.2 + , text >=1.2.3 && <2.2 + , unix >=2.7.2 && <2.9 - -- for System.IO.Atomic - , directory >= 1.3.3 && < 1.4 - , filepath >= 1.4.2 && < 1.5 - , safe-exceptions >= 0.1.7 && < 0.2 - default-language: Haskell2010 + -- for System.IO.Atomic + default-language: Haskell2010 ghc-options: - -Wall - -Wcompat - -Wincomplete-record-updates - -Wincomplete-uni-patterns - -Wredundant-constraints - -threaded + -Wall -Wcompat -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wredundant-constraints -threaded library exposed-modules: @@ -63,33 +61,42 @@ library Nixfmt.Util default-extensions: - PackageImports - - other-extensions: + BangPatterns + BlockArguments + DataKinds + DeriveAnyClass DeriveFoldable DeriveFunctor + DeriveGeneric + DerivingVia FlexibleInstances LambdaCase + NamedFieldPuns + OverloadedLists OverloadedStrings + PackageImports + PatternSynonyms + RankNTypes ScopedTypeVariables - StandaloneDeriving TupleSections + TypeApplications + TypeOperators - hs-source-dirs: src + hs-source-dirs: src build-depends: - base >= 4.12.0 && < 4.21 - , megaparsec >= 9.0.1 && < 9.6 + , base >=4.12.0 && <4.21 + , containers + , deepseq + , megaparsec >=9.0.1 && <9.6 , mtl - , parser-combinators >= 1.0.3 && < 1.4 - , scientific >= 0.3.0 && < 0.4.0 - , text >= 1.2.3 && < 2.2 - , transformers + , parser-combinators >=1.0.3 && <1.4 , pretty-simple - default-language: Haskell2010 + , scientific >=0.3.0 && <0.4.0 + , text >=1.2.3 && <2.2 + , transformers + , vector + + default-language: Haskell2010 ghc-options: - -Wall - -Wcompat - -Wincomplete-record-updates - -Wincomplete-uni-patterns - -Wredundant-constraints - -Wno-orphans + -Wall -Wcompat -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wredundant-constraints -Wno-orphans diff --git a/src/Nixfmt.hs b/src/Nixfmt.hs index fd432a6d..07265c39 100644 --- a/src/Nixfmt.hs +++ b/src/Nixfmt.hs @@ -1,9 +1,6 @@ -{-# LANGUAGE RankNTypes #-} - module Nixfmt ( errorBundlePretty, ParseErrorBundle, - Width, format, formatVerify, printAst, @@ -22,9 +19,6 @@ import qualified Text.Megaparsec as Megaparsec (parse) import Text.Megaparsec.Error (errorBundlePretty) import Text.Pretty.Simple (pShow) --- import Debug.Trace (traceShow, traceShowId) - -type Width = Int type Layouter = forall a. (Pretty a, LanguageElement a) => a -> Text -- | @format w filename source@ returns either a parsing error specifying a @@ -32,11 +26,14 @@ type Layouter = forall a. (Pretty a, LanguageElement a) => a -> Text -- of @w@ columns where possible. format :: Layouter -> FilePath -> Text -> Either String Text format layout filename = - bimap errorBundlePretty layout + bimap errorBundlePretty f . Megaparsec.parse Parser.file filename + where + -- f !x = layout $ maybe () (error . show) (unsafeNoThunks x) `seq` x + f !x = layout x -- | Pretty print the internal AST for debugging -printAst :: FilePath -> Text -> Either String Text +printAst :: FilePath -> Text -> Either String a printAst path unformatted = do Whole unformattedParsed' _ <- first errorBundlePretty . Megaparsec.parse Parser.file path $ unformatted Left (unpack $ toStrict $ pShow unformattedParsed') diff --git a/src/Nixfmt/Lexer.hs b/src/Nixfmt/Lexer.hs index 1cbb798e..9b55006f 100644 --- a/src/Nixfmt/Lexer.hs +++ b/src/Nixfmt/Lexer.hs @@ -1,14 +1,12 @@ -{-# LANGUAGE BlockArguments #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} - module Nixfmt.Lexer (lexeme, pushTrivia, takeTrivia, whole) where -import Control.Monad.State.Strict (MonadState, evalStateT, get, modify, put) +import Control.DeepSeq (NFData, force) +import Control.Monad.State.Strict (StateT, evalStateT, get, modify, put) import Data.Char (isSpace) -import Data.List (dropWhileEnd) +import Data.List (dropWhileEnd, singleton) import Data.Maybe (fromMaybe) +import Data.Sequence (Seq) +import qualified Data.Sequence as Seq import Data.Text as Text ( Text, isPrefixOf, @@ -26,6 +24,7 @@ import Data.Text as Text ( unwords, ) import Data.Void (Void) +import GHC.Generics (Generic) import Nixfmt.Types ( Ann (..), Parser, @@ -54,12 +53,13 @@ import Text.Megaparsec ( import Text.Megaparsec.Char (char, eol) data ParseTrivium - = PTNewlines Int + = PTNewlines {-# UNPACK #-} !Int | -- Track the column where the comment starts - PTLineComment Text Pos + PTLineComment {-# UNPACK #-} !Text !Pos | -- Track whether it is a doc comment - PTBlockComment Bool [Text] - deriving (Show) + PTBlockComment !Bool ![Text] + deriving stock (Show, Generic) + deriving anyclass (NFData) preLexeme :: Parser a -> Parser a preLexeme p = p <* manyP (\x -> isSpace x && x /= '\n' && x /= '\r') @@ -128,8 +128,8 @@ blockComment = try $ preLexeme $ do commonIndentationLength = foldr (min . Text.length . Text.takeWhile (== ' ')) -- This should be called with zero or one elements, as per `span isTrailing` -convertTrailing :: [ParseTrivium] -> Maybe TrailingComment -convertTrailing = toMaybe . join . map toText +convertTrailing :: Seq ParseTrivium -> Maybe TrailingComment +convertTrailing = toMaybe . join . foldMap (singleton . toText) where toText (PTLineComment c _) = strip c toText (PTBlockComment False [c]) = strip c @@ -138,9 +138,9 @@ convertTrailing = toMaybe . join . map toText toMaybe "" = Nothing toMaybe c = Just $ TrailingComment c -convertLeading :: [ParseTrivium] -> Trivia +convertLeading :: Seq ParseTrivium -> Trivia convertLeading = - concatMap + foldMap ( \case PTNewlines 1 -> [] PTNewlines _ -> [EmptyLine] @@ -156,31 +156,34 @@ isTrailing (PTBlockComment False []) = True isTrailing (PTBlockComment False [_]) = True isTrailing _ = False -convertTrivia :: [ParseTrivium] -> Pos -> (Maybe TrailingComment, Trivia) +convertTrivia :: Seq ParseTrivium -> Pos -> (Maybe TrailingComment, Trivia) convertTrivia pts nextCol = - let (trailing, leading) = span isTrailing pts + let (trailing, leading) = Seq.spanl isTrailing pts in case (trailing, leading) of -- Special case: if the trailing comment visually forms a block with the start of the following line, -- then treat it like part of those comments instead of a distinct trailing comment. -- This happens especially often after `{` or `[` tokens, where the comment of the first item -- starts on the same line ase the opening token. - ([PTLineComment _ pos], (PTNewlines 1) : (PTLineComment _ pos') : _) | pos == pos' -> (Nothing, convertLeading pts) + ([PTLineComment _ pos], (PTNewlines 1) Seq.:<| (PTLineComment _ pos') Seq.:<| _) | pos == pos' -> (Nothing, convertLeading pts) ([PTLineComment _ pos], [PTNewlines 1]) | pos == nextCol -> (Nothing, convertLeading pts) _ -> (convertTrailing trailing, convertLeading leading) -trivia :: Parser [ParseTrivium] -trivia = many $ hidden $ lineComment <|> blockComment <|> newlines +trivia :: Parser (Seq ParseTrivium) +trivia = + Seq.fromList <$> do + many $ hidden $ lineComment <|> blockComment <|> newlines -- The following primitives to interact with the state monad that stores trivia -- are designed to prevent trivia from being dropped or duplicated by accident. -takeTrivia :: (MonadState Trivia m) => m Trivia -takeTrivia = get <* put [] +takeTrivia :: (Monad m) => StateT Trivia m Trivia +takeTrivia = get <* put Seq.empty -pushTrivia :: (MonadState Trivia m) => Trivia -> m () +pushTrivia :: (Monad m) => Trivia -> StateT Trivia m () pushTrivia t = modify (<> t) +{-# INLINEABLE pushTrivia #-} -lexeme :: Parser a -> Parser (Ann a) +lexeme :: (NFData a) => Parser a -> Parser (Ann a) lexeme p = do lastLeading <- takeTrivia SourcePos{Text.Megaparsec.sourceLine = line} <- getSourcePos @@ -192,17 +195,18 @@ lexeme p = do pushTrivia nextLeading return $ Ann - { preTrivia = lastLeading, - value = token, - Nixfmt.Types.sourceLine = line, - trailComment = trailing + { preTrivia = force lastLeading, + value = force token, + Nixfmt.Types.sourceLine = force line, + trailComment = force trailing } -- | Tokens normally have only leading trivia and one trailing comment on the same -- line. A whole x also parses and stores final trivia after the x. A whole also -- does not interact with the trivia state of its surroundings. -whole :: Parser a -> Parsec Void Text (Whole a) -whole pa = flip evalStateT [] do +whole :: (NFData a) => Parser a -> Parsec Void Text (Whole a) +whole pa = flip evalStateT Seq.empty do preLexeme $ pure () - pushTrivia . convertLeading =<< trivia - Whole <$> pa <*> takeTrivia + trivia + >>= pushTrivia . convertLeading + Whole <$> (force <$> pa) <*> (force <$> takeTrivia) diff --git a/src/Nixfmt/Parser.hs b/src/Nixfmt/Parser.hs index ddfb558d..4c6368a1 100644 --- a/src/Nixfmt/Parser.hs +++ b/src/Nixfmt/Parser.hs @@ -1,8 +1,12 @@ {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} module Nixfmt.Parser where +import Control.Applicative (liftA2) +import Control.DeepSeq (NFData, force) import Control.Monad (guard, liftM2) import Control.Monad.Combinators (sepBy) import qualified Control.Monad.Combinators.Expr as MPExpr ( @@ -77,7 +81,7 @@ import Prelude hiding (String) -- HELPER FUNCTIONS -ann :: (a -> b) -> Parser a -> Parser (Ann b) +ann :: (NFData b) => (a -> b) -> Parser a -> Parser (Ann b) ann f p = try $ lexeme $ f <$> p -- | parses a token without parsing trivia after it @@ -128,10 +132,7 @@ slash :: Parser Text slash = chunk "/" <* notFollowedBy (char '/') instance (Semigroup a) => Semigroup (Parser a) where - fx <> fy = do - x <- fx - y <- fy - pure $ x <> y + (<>) = liftA2 (<>) envPath :: Parser (Ann Token) envPath = @@ -372,8 +373,8 @@ term = label "term" $ do [] -> t _ -> Selection t sel def -items :: Parser a -> Parser (Items a) -items p = Items <$> many (item p) <> (toList <$> optional itemComment) +items :: (NFData a) => Parser a -> Parser (Items a) +items p = Items . force <$> many (item p) <> (toList <$> optional itemComment) item :: Parser a -> Parser (Item a) item p = itemComment <|> Item <$> p @@ -432,23 +433,23 @@ inherit = <*> symbol TSemicolon assignment :: Parser Binder -assignment = - Assignment - <$> selectorPath - <*> symbol TAssign - <*> expression - <*> symbol TSemicolon +assignment = do + lhs <- selectorPath + assign <- symbol TAssign + expr <- expression + semicolon <- symbol TSemicolon + pure $! Assignment (force lhs) assign (force expr) semicolon binders :: Parser (Items Binder) binders = items (assignment <|> inherit) set :: Parser Term -set = - Set - <$> optional (reserved KRec <|> reserved KLet) - <*> symbol TBraceOpen - <*> binders - <*> symbol TBraceClose +set = do + mleaf <- optional (reserved KRec <|> reserved KLet) + lbrace <- symbol TBraceOpen + els <- binders + rbrace <- symbol TBraceClose + pure $! Set mleaf lbrace (force els) rbrace list :: Parser Term list = List <$> symbol TBrackOpen <*> items term <*> symbol TBrackClose @@ -494,7 +495,7 @@ opCombiner (Op InfixR tok) = MPExpr.InfixR $ flip Operation <$> operator tok operation :: Parser Expression operation = MPExpr.makeExprParser - (Term <$> term <* notFollowedBy (oneOf (":@" :: [Char]))) + (Term <$> term <* notFollowedBy (oneOf @[] ":@")) (map (map opCombiner) operators) -- EXPRESSIONS diff --git a/src/Nixfmt/Parser/Float.hs b/src/Nixfmt/Parser/Float.hs index 00e403dc..7d8239b9 100644 --- a/src/Nixfmt/Parser/Float.hs +++ b/src/Nixfmt/Parser/Float.hs @@ -1,15 +1,11 @@ -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} - module Nixfmt.Parser.Float (floatParse) where -import "base" Control.Monad (void) -import qualified "base" Data.Char as Char -import "base" Data.Foldable (foldl') -import "base" Data.Proxy (Proxy (..)) -import "megaparsec" Text.Megaparsec ( +import Control.Monad (void) +import qualified Data.Char as Char +import Data.Foldable (foldl') +import Data.Proxy (Proxy (..)) +import Data.Scientific (scientific, toRealFloat) +import Text.Megaparsec ( MonadParsec, Token, chunkToTokens, @@ -20,9 +16,8 @@ import "megaparsec" Text.Megaparsec ( (), (<|>), ) -import "megaparsec" Text.Megaparsec.Char (char, char', digitChar) -import "megaparsec" Text.Megaparsec.Char.Lexer (decimal, signed) -import "scientific" Data.Scientific (scientific, toRealFloat) +import Text.Megaparsec.Char (char, char', digitChar) +import Text.Megaparsec.Char.Lexer (decimal, signed) -- copied (and modified) from Text.Megaparsec.Char.Lexer data SP = SP !Integer {-# UNPACK #-} !Int diff --git a/src/Nixfmt/Predoc.hs b/src/Nixfmt/Predoc.hs index 292aab2a..81a54326 100644 --- a/src/Nixfmt/Predoc.hs +++ b/src/Nixfmt/Predoc.hs @@ -1,6 +1,3 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE OverloadedStrings #-} - module Nixfmt.Predoc ( text, comment, @@ -43,6 +40,8 @@ import Data.List (intersperse) import Data.List.NonEmpty (NonEmpty (..), singleton, (<|)) import qualified Data.List.NonEmpty as NonEmpty import Data.Maybe (fromMaybe) +import Data.Sequence (Seq ((:<|), (:|>))) +import qualified Data.Sequence as Seq import Data.Text as Text (Text, concat, length, replicate, strip) import GHC.Stack (HasCallStack) import Nixfmt.Types ( @@ -70,7 +69,7 @@ data Spacing | -- | Two line breaks Emptyline | -- | n line breaks - Newlines Int + Newlines {-# UNPACK #-} !Int deriving (Show, Eq, Ord) -- | `Group docs` indicates that either all or none of the Spaces and Breaks @@ -78,7 +77,7 @@ data Spacing -- those will be expanded only as necessary and with a lower priority. data GroupAnn = RegularG - | -- Group with priority expansion. This is only rarely needed, and mostly useful + | -- | Group with priority expansion. This is only rarely needed, and mostly useful -- to compact things left and right of a multiline element as long as they fit onto one line. -- -- Groups containing priority groups are treated as having three segments: @@ -92,7 +91,7 @@ data GroupAnn -- each one individually, and in *reverse* order. If all of these fail, then the entire group -- will be fully expanded as if it didn't contain any priority groups. Priority - | -- Usually, priority groups are associated and handled by their direct parent group. However, + | -- | Usually, priority groups are associated and handled by their direct parent group. However, -- if the parent is a `Transparent` group, then they will be associated with its parent instead. -- (This goes on transitively until the first non-transparent parent group.) -- In the case of priority group expansion, this group will be treated as non-existent (transparent). @@ -100,7 +99,7 @@ data GroupAnn Transparent deriving (Show, Eq) --- Comments do not count towards some line length limits +-- | Comments do not count towards some line length limits -- Trailing tokens have the property that they will only exist in expanded groups, and "swallowed" in compact groups -- Trailing comments are like comments, but marked differently for special treatment further down the line -- (The difference is that trailing comments are guaranteed to be single "# text" tokens, while all other comments @@ -111,13 +110,13 @@ data TextAnn = RegularT | Comment | TrailingComment | Trailing -- | Single document element. Documents are modeled as lists of these elements -- in order to make concatenation simple. data DocE - = -- nesting depth, offset, kind, text - Text Int Int TextAnn Text - | Spacing Spacing - | Group GroupAnn Doc + = -- | nesting depth, offset, kind, text + Text {-# UNPACK #-} !Int {-# UNPACK #-} !Int !TextAnn {-# UNPACK #-} !Text + | Spacing !Spacing + | Group !GroupAnn !Doc deriving (Show, Eq) -type Doc = [DocE] +type Doc = Seq DocE class Pretty a where pretty :: a -> Doc @@ -159,9 +158,11 @@ trailing t = [Text 0 0 Trailing t] group :: (HasCallStack) => (Pretty a) => a -> Doc group x = pure . Group RegularG $ - if p /= [] && (isSoftSpacing (head p) || isSoftSpacing (last p)) - then error $ "group should not start or end with whitespace, use `group'` if you are sure; " <> show p - else p + case pretty x of + ((hp :<| _) :|> lp) + | isSoftSpacing hp || isSoftSpacing lp -> + error $ "group should not start or end with whitespace, use `group'` if you are sure; " <> show p + _ -> p where p = pretty x @@ -181,19 +182,19 @@ group' ann = pure . Group ann . pretty -- Multiple nesting levels on one line will be compacted and only result in a single -- indentation bump for the next line. This prevents excessive indentation. nest :: (Pretty a) => a -> Doc -nest x = map go $ pretty x +nest x = go <$> pretty x where - go (Text i o ann t) = Text (i + 1) o ann t - go (Group ann inner) = Group ann (map go inner) + go (Text !i o ann t) = Text (i + 1) o ann t + go (Group ann inner) = Group ann (go <$> inner) go spacing = spacing -- This is similar to nest, however it circumvents the "smart" rules that usually apply. -- This should only be useful to manage the indentation within indented strings. offset :: (Pretty a) => Int -> a -> Doc -offset level x = map go $ pretty x +offset !level x = go <$> pretty x where - go (Text i o ann t) = Text i (o + level) ann t - go (Group ann inner) = Group ann (map go inner) + go (Text i !o ann t) = Text i (o + level) ann t + go (Group ann inner) = Group ann (go <$> inner) go spacing = spacing -- | Line break or nothing (soft) @@ -264,33 +265,36 @@ isComment _ = False --- Manually force a group to its compact layout, by replacing all relevant whitespace. --- Does not recurse into inner groups. -unexpandSpacing :: Doc -> Doc -unexpandSpacing [] = [] -unexpandSpacing ((Spacing Space) : xs) = Spacing Hardspace : unexpandSpacing xs -unexpandSpacing ((Spacing Softspace) : xs) = Spacing Hardspace : unexpandSpacing xs -unexpandSpacing ((Spacing Break) : xs) = unexpandSpacing xs -unexpandSpacing ((Spacing Softbreak) : xs) = unexpandSpacing xs -unexpandSpacing (s@(Spacing _) : xs) = s : unexpandSpacing xs -unexpandSpacing (x : xs) = x : unexpandSpacing xs +spanEnd :: (a -> Bool) -> Seq a -> (Seq a, Seq a) +spanEnd p = fmap Seq.reverse . Seq.spanl p . Seq.reverse -spanEnd :: (a -> Bool) -> [a] -> ([a], [a]) -spanEnd p = fmap reverse . span p . reverse +unexpandSpacing :: Doc -> Doc +unexpandSpacing = foldMap \case + Spacing Space -> [Spacing Hardspace] + Spacing Softspace -> [Spacing Hardspace] + Spacing Break -> [] + Spacing Softbreak -> [] + x -> [x] -- Manually force a group to its compact layout, by replacing all relevant whitespace. -- Does recurse into inner groups. -- An optional maximum line length limit may be specified. -- Fails if the doc contains hardlines or exceeds the length limit unexpandSpacing' :: Maybe Int -> Doc -> Maybe Doc -unexpandSpacing' (Just n) _ | n < 0 = Nothing -unexpandSpacing' _ [] = Just [] -unexpandSpacing' n (txt@(Text _ _ _ t) : xs) = (txt :) <$> unexpandSpacing' (n <&> subtract (textWidth t)) xs -unexpandSpacing' n (Spacing Hardspace : xs) = (Spacing Hardspace :) <$> unexpandSpacing' (n <&> subtract 1) xs -unexpandSpacing' n (Spacing Space : xs) = (Spacing Hardspace :) <$> unexpandSpacing' (n <&> subtract 1) xs -unexpandSpacing' n (Spacing Softspace : xs) = (Spacing Hardspace :) <$> unexpandSpacing' (n <&> subtract 1) xs -unexpandSpacing' n (Spacing Break : xs) = unexpandSpacing' n xs -unexpandSpacing' n (Spacing Softbreak : xs) = unexpandSpacing' n xs -unexpandSpacing' _ (Spacing _ : _) = Nothing -unexpandSpacing' n ((Group _ xs) : ys) = unexpandSpacing' n $ xs <> ys +unexpandSpacing' _ Seq.Empty = Just [] +unexpandSpacing' mn (x :<| xs) + | Just n <- mn, n < 0 = Nothing + | otherwise = + let unexpandSubtract t = unexpandSpacing' (subtract t <$> mn) + in case x of + txt@(Text _ _ _ t) -> (txt :<|) <$> unexpandSubtract (textWidth t) xs + (Spacing Hardspace) -> (Spacing Hardspace :<|) <$> unexpandSubtract 1 xs + (Spacing Space) -> (Spacing Hardspace :<|) <$> unexpandSubtract 1 xs + (Spacing Softspace) -> (Spacing Hardspace :<|) <$> unexpandSubtract 1 xs + (Spacing Break) -> unexpandSpacing' mn xs + (Spacing Softbreak) -> unexpandSpacing' mn xs + (Spacing _) -> Nothing + ((Group _ ws)) -> unexpandSpacing' mn $ ws <> xs -- Dissolve some groups with only one item simplifyGroup :: GroupAnn -> Doc -> Doc @@ -309,32 +313,33 @@ simplifyGroup _ x = x -- - Remove empty Groups and Nests -- After running, any nodes are guaranteed to start/end with at most one whitespace element respectively. fixup :: Doc -> Doc -fixup [] = [] +fixup Seq.Empty = [] -- Merge consecutive spacings -fixup (Spacing a : Spacing b : xs) = fixup $ Spacing (mergeSpacings a b) : xs +fixup (Spacing a :<| Spacing b :<| xs) = fixup $ Spacing (mergeSpacings a b) :<| xs -- Merge consecutive texts. Take indentation and offset from the left one -fixup (Text level off ann a : Text _ _ ann' b : xs) | ann == ann' = fixup $ Text level off ann (a <> b) : xs +fixup (Text level off ann a :<| Text _ _ ann' b :<| xs) | ann == ann' = fixup $ Text level off ann (a <> b) :<| xs -- Move/Merge hard spaces into groups -fixup ((Spacing Hardspace) : Group ann xs : ys) = fixup $ Group ann (Spacing Hardspace : xs) : ys +fixup ((Spacing Hardspace) :<| Group ann xs :<| ys) = fixup $ Group ann (Spacing Hardspace :<| xs) :<| ys -- Handle group, with stuff in front of it to potentially merge with -fixup (a@(Spacing _) : Group ann xs : ys) = +fixup (a@(Spacing _) :<| Group ann xs :<| ys) = let -- Recurse onto xs, split out leading and trailing whitespace into pre and post. -- For the leading side, also move out comments out of groups, they are kinda the same thing -- (We could move out trailing comments too but it would make no difference) - (pre, rest) = span (\x -> isHardSpacing x || isComment x) $ fixup xs + (pre, rest) = Seq.spanl (\x -> isHardSpacing x || isComment x) $ fixup xs (post, body) = second (simplifyGroup ann) $ spanEnd isHardSpacing rest in if null body then -- Dissolve empty group - fixup $ (a : pre) ++ post ++ ys - else fixup (a : pre) ++ [Group ann body] ++ fixup (post ++ ys) + + fixup $ (a :<| pre) <> post <> ys + else fixup (a :<| pre) <> [Group ann body] <> fixup (post <> ys) -- Handle group, almost the same thing as above -fixup (Group ann xs : ys) = - let (pre, rest) = span (\x -> isHardSpacing x || isComment x) $ fixup xs +fixup (Group ann xs :<| ys) = + let (pre, rest) = Seq.spanl (\x -> isHardSpacing x || isComment x) $ fixup xs (post, body) = second (simplifyGroup ann) $ spanEnd isHardSpacing rest in if null body - then fixup $ pre ++ post ++ ys - else fixup pre ++ [Group ann body] ++ fixup (post ++ ys) -fixup (x : xs) = x : fixup xs + then fixup $ pre <> post <> ys + else fixup pre <> [Group ann body] <> fixup (post <> ys) +fixup (x :<| xs) = x :<| fixup xs mergeSpacings :: Spacing -> Spacing -> Spacing mergeSpacings x y | x > y = mergeSpacings y x @@ -371,15 +376,15 @@ priorityGroups :: Doc -> [(Doc, Doc, Doc)] priorityGroups = explode . mergeSegments . segments where segments :: Doc -> [(Bool, Doc)] - segments [] = [] - segments ((Group Priority ys) : xs) = (True, ys) : segments xs - segments ((Group Transparent ys) : xs) = segments ys ++ segments xs - segments (x : xs) = (False, pure x) : segments xs + segments Seq.Empty = [] + segments ((Group Priority ys) :<| xs) = (True, ys) : segments xs + segments ((Group Transparent ys) :<| xs) = segments ys ++ segments xs + segments (x :<| xs) = (False, pure x) : segments xs -- Merge subsequent segments of non-priority-group elements mergeSegments :: [(Bool, Doc)] -> [(Bool, Doc)] mergeSegments [] = [] - mergeSegments ((False, content1) : (False, content2) : xs) = mergeSegments $ (False, content1 ++ content2) : xs + mergeSegments ((False, content1) : (False, content2) : xs) = mergeSegments $ (False, content1 <> content2) : xs mergeSegments (x : xs) = x : mergeSegments xs -- Convert the segmented/pre-porcessed input into a list of all groups as (pre, prio, post) triples @@ -389,7 +394,7 @@ priorityGroups = explode . mergeSegments . segments | prio = [([], x, [])] | otherwise = [] explode ((prio, x) : xs) - | prio = ([], x, concatMap snd xs) : map (\(a, b, c) -> (x <> a, b, c)) (explode xs) + | prio = ([], x, foldMap snd xs) : map (\(a, b, c) -> (x <> a, b, c)) (explode xs) | otherwise = map (\(a, b, c) -> (x <> a, b, c)) (explode xs) -- | To support i18n, this function needs to be patched. @@ -401,12 +406,12 @@ textWidth = Text.length -- of the next line relative to the current one. So usuall 2 when the indentation level increases, 0 otherwise. -- c — allowed width fits :: Int -> Int -> Doc -> Maybe Text -fits _ c _ | c < 0 = Nothing -fits _ _ [] = Just "" +fits !_ !c _ | c < 0 = Nothing +fits !_ !_ Seq.Empty = Just "" -- This case is impossible in the input thanks to fixup, but may happen -- due to our recursion on nodes below -fits ni c (Spacing a : Spacing b : xs) = fits ni c (Spacing (mergeSpacings a b) : xs) -fits ni c (x : xs) = case x of +fits !ni !c (Spacing a :<| Spacing b :<| xs) = fits ni c (Spacing (mergeSpacings a b) :<| xs) +fits !ni !c (x :<| xs) = case x of Text _ _ RegularT t -> (t <>) <$> fits (ni - textWidth t) (c - textWidth t) xs Text _ _ Comment t -> (t <>) <$> fits ni c xs Text _ _ TrailingComment t @@ -421,47 +426,47 @@ fits ni c (x : xs) = case x of Spacing Hardline -> Nothing Spacing Emptyline -> Nothing Spacing (Newlines _) -> Nothing - Group _ ys -> fits ni c $ ys ++ xs + Group _ ys -> fits ni c $ ys <> xs -- | Find the width of the first line in a list of documents, using target -- width 0, which always forces line breaks when possible. firstLineWidth :: Doc -> Int -firstLineWidth [] = 0 -firstLineWidth (Text _ _ Comment _ : xs) = firstLineWidth xs -firstLineWidth (Text _ _ TrailingComment _ : xs) = firstLineWidth xs -firstLineWidth (Text _ _ _ t : xs) = textWidth t + firstLineWidth xs +firstLineWidth Seq.Empty = 0 +firstLineWidth (Text _ _ Comment _ :<| xs) = firstLineWidth xs +firstLineWidth (Text _ _ TrailingComment _ :<| xs) = firstLineWidth xs +firstLineWidth (Text _ _ _ t :<| xs) = textWidth t + firstLineWidth xs -- This case is impossible in the input thanks to fixup, but may happen -- due to our recursion on groups below -firstLineWidth (Spacing a : Spacing b : xs) = firstLineWidth (Spacing (mergeSpacings a b) : xs) -firstLineWidth (Spacing Hardspace : xs) = 1 + firstLineWidth xs -firstLineWidth (Spacing _ : _) = 0 -firstLineWidth (Group _ xs : ys) = firstLineWidth $ xs ++ ys +firstLineWidth (Spacing a :<| Spacing b :<| xs) = firstLineWidth (Spacing (mergeSpacings a b) :<| xs) +firstLineWidth (Spacing Hardspace :<| xs) = 1 + firstLineWidth xs +firstLineWidth (Spacing _ :<| _) = 0 +firstLineWidth (Group _ xs :<| ys) = firstLineWidth $ xs <> ys -- | Check if the first line in a document fits a target width given -- a maximum width, without breaking up groups. firstLineFits :: Int -> Int -> Doc -> Bool firstLineFits targetWidth maxWidth docs = go maxWidth docs where - go c _ | c < 0 = False - go c [] = maxWidth - c <= targetWidth - go c (Text _ _ RegularT t : xs) = go (c - textWidth t) xs - go c (Text{} : xs) = go c xs + go !c _ | c < 0 = False + go !c Seq.Empty = maxWidth - c <= targetWidth + go !c (Text _ _ RegularT t :<| xs) = go (c - textWidth t) xs + go !c (Text{} :<| xs) = go c xs -- This case is impossible in the input thanks to fixup, but may happen -- due to our recursion on groups below - go c (Spacing a : Spacing b : xs) = go c $ Spacing (mergeSpacings a b) : xs - go c (Spacing Hardspace : xs) = go (c - 1) xs - go c (Spacing _ : _) = maxWidth - c <= targetWidth - go c (Group _ ys : xs) = + go !c (Spacing a :<| Spacing b :<| xs) = go c $ Spacing (mergeSpacings a b) :<| xs + go !c (Spacing Hardspace :<| xs) = go (c - 1) xs + go !c (Spacing _ :<| _) = maxWidth - c <= targetWidth + go !c (Group _ ys :<| xs) = case fits 0 (c - firstLineWidth xs) ys of - Nothing -> go c (ys ++ xs) + Nothing -> go c (ys <> xs) Just t -> go (c - textWidth t) xs -- Calculate the amount of indentation until the first token -- This assumes the input to be an unexpanded group at the start of a new line nextIndent :: Doc -> (Int, Int) -nextIndent ((Text i o _ _) : _) = (i, o) -nextIndent ((Group _ xs) : _) = nextIndent xs -nextIndent (_ : xs) = nextIndent xs +nextIndent ((Text i o _ _) :<| _) = (i, o) +nextIndent ((Group _ xs) :<| _) = nextIndent xs +nextIndent (_ :<| xs) = nextIndent xs nextIndent _ = (0, 0) -- | Create `n` newlines @@ -519,8 +524,8 @@ layoutGreedy tw doc = Text.concat $ evalState (go [Group RegularG doc] []) (0, s -- First argument: chunks to render -- Second argument: lookahead of following chunks, not rendered go :: Doc -> Doc -> State St [Text] - go [] _ = return [] - go (x : xs) ys = do t <- goOne x (xs ++ ys); ts <- go xs ys; return (t ++ ts) + go Seq.Empty _ = return [] + go (x :<| xs) ys = do t <- goOne x (xs <> ys); ts <- go xs ys; return (t <> ts) -- First argument: chunk to render. This will recurse into nests/groups if the chunk is one. -- Second argument: lookahead of following chunks @@ -591,14 +596,14 @@ layoutGreedy tw doc = Text.concat $ evalState (go [Group RegularG doc] []) (0, s goPriorityGroup :: (Doc, Doc, Doc) -> Doc -> StateT St Maybe [Text] goPriorityGroup (pre, prio, post) rest = do -- Try to fit pre onto one line - preRendered <- goGroup pre (prio ++ post ++ rest) + preRendered <- goGroup pre (prio <> post <> rest) -- Render prio expanded -- We know that post will be rendered compact. So we tell the renderer that by manually removing all -- line breaks in post here. Otherwise we might get into awkward the situation where pre and prio are put -- onto the one line, all three obviously wouldn't fit. prioRendered <- mapStateT (Just . runIdentity) $ - go prio (unexpandSpacing post ++ rest) + go prio (unexpandSpacing post <> rest) -- Try to render post onto one line postRendered <- goGroup post rest -- If none of these failed, put together and return @@ -611,15 +616,15 @@ layoutGreedy tw doc = Text.concat $ evalState (go [Group RegularG doc] []) (0, s goGroup :: Doc -> Doc -> StateT St Maybe [Text] -- In general groups are never empty as empty groups are removed in `fixup`, however this also -- gets called for pre and post of priority groups, which may be empty. - goGroup [] _ = pure [] - goGroup grp rest = StateT $ \(cc, ci) -> + goGroup Seq.Empty _ = pure [] + goGroup grp@(g Seq.:<| gs) rest = StateT $ \(cc, ci) -> if cc == 0 then let -- We know that the last printed character was a line break (cc == 0), -- therefore drop any leading whitespace within the group to avoid duplicate newlines - grp' = case head grp of - Spacing _ -> tail grp - Group ann ((Spacing _) : inner) -> Group ann inner : tail grp + grp' = case g of + Spacing _ -> gs + Group ann ((Spacing _) :<| inner) -> Group ann inner :<| gs _ -> grp (nl, off) = nextIndent grp' diff --git a/src/Nixfmt/Pretty.hs b/src/Nixfmt/Pretty.hs index 120ef92b..e1e4f39e 100644 --- a/src/Nixfmt/Pretty.hs +++ b/src/Nixfmt/Pretty.hs @@ -1,14 +1,10 @@ -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE TupleSections #-} - module Nixfmt.Pretty where import Data.Char (isSpace) -import Data.Maybe (fromJust, fromMaybe, isJust, isNothing, maybeToList) +import Data.Foldable (fold, toList) +import Data.Maybe (fromJust, fromMaybe, isJust, isNothing) +import Data.Sequence (Seq) +import qualified Data.Sequence as Seq import Data.Text (Text) import qualified Data.Text as Text (null, takeWhile) import Nixfmt.Predoc ( @@ -71,7 +67,7 @@ toLineComment (TrailingComment c) = LineComment $ " " <> c moveTrailingCommentUp :: Ann a -> Ann a moveTrailingCommentUp a@Ann{preTrivia, trailComment = Just post} = a - { preTrivia = preTrivia ++ [toLineComment post], + { preTrivia = preTrivia <> [toLineComment post], trailComment = Nothing } moveTrailingCommentUp a = a @@ -108,6 +104,10 @@ instance Pretty [Trivium] where pretty [] = mempty pretty trivia = hardline <> hcat trivia +instance Pretty (Seq Trivium) where + pretty [] = mempty + pretty trivia = hardline <> hcat (toList trivia) + instance (Pretty a) => Pretty (Ann a) where pretty Ann{preTrivia, value, trailComment} = pretty preTrivia <> pretty value <> pretty trailComment @@ -294,7 +294,7 @@ moveParamsComments : xs ) = ParamAttr name maybeDefault (Just (comma{preTrivia = []})) - : moveParamsComments (ParamAttr (name'{preTrivia = trivia ++ trivia'}) maybeDefault' maybeComma' : xs) + : moveParamsComments (ParamAttr (name'{preTrivia = trivia <> trivia'}) maybeDefault' maybeComma' : xs) -- This may seem like a nonsensical case, but keep in mind that blank lines also count as comments (trivia) moveParamsComments -- , name @@ -304,7 +304,7 @@ moveParamsComments ParamEllipsis ellipsis@Ann{preTrivia = trivia'} ] = [ ParamAttr name maybeDefault (Just (comma{preTrivia = []})), - ParamEllipsis (ellipsis{preTrivia = trivia ++ trivia'}) + ParamEllipsis (ellipsis{preTrivia = trivia <> trivia'}) ] -- Inject a trailing comma on the last element if nessecary moveParamsComments [ParamAttr name@Ann{sourceLine} def Nothing] = [ParamAttr name def (Just (ann sourceLine TComma))] @@ -392,7 +392,7 @@ prettyApp indentFunction pre hasPost f a = close ) ) - | isAbsorbableTerm body && not (any hasTrivia [open, name, colon]) = + | isAbsorbableTerm body && not (any @[] hasTrivia [open, name, colon]) = group' Priority $ nest $ pretty open @@ -410,7 +410,7 @@ prettyApp indentFunction pre hasPost f a = close ) ) - | isAbsorbableTerm body && not (any hasTrivia [open, ident, close]) = + | isAbsorbableTerm body && not (any @[] hasTrivia [open, ident, close]) = group' Priority $ nest $ pretty open @@ -497,7 +497,7 @@ absorbParen open@Ann{trailComment = post'} expr close@Ann{preTrivia = pre''} = nest $ pretty ( mapFirstToken - (\a@Ann{preTrivia} -> a{preTrivia = maybeToList (toLineComment <$> post') ++ preTrivia}) + (\a@Ann{preTrivia} -> a{preTrivia = maybe Seq.empty (Seq.singleton . toLineComment) post' <> preTrivia}) expr ) -- Move any leading comments on the closing parenthesis up into the nest @@ -581,8 +581,8 @@ instance Pretty Expression where (Comments inner) | null rest -> -- Only move all non-empty-line trivia below the `in` - let (comments, el) = break (== EmptyLine) (reverse inner) - in (reverse comments : start, Comments (reverse el) : rest) + let (comments, el) = Seq.breakl (== EmptyLine) (Seq.reverse inner) + in (Seq.reverse comments : start, Comments (Seq.reverse el) : rest) _ -> (start, item : rest) ) ([], []) @@ -595,7 +595,7 @@ instance Pretty Expression where pretty in_ <> hardline -- Take our trailing and inject it between `in` and body - <> pretty (concat binderComments ++ preTrivia ++ convertTrailing trailComment) + <> pretty (fold binderComments <> preTrivia <> convertTrailing trailComment) <> pretty expr pretty (Assert assert cond semicolon expr) = group $ @@ -635,11 +635,11 @@ instance Pretty Expression where where absorbAbs :: Int -> Expression -> Doc -- If there are multiple ID parameters to that function, treat them all at once - absorbAbs depth (Abstraction (IDParameter param0) colon0 body0) = + absorbAbs !depth (Abstraction (IDParameter param0) colon0 body0) = hardspace <> pretty param0 <> pretty colon0 <> absorbAbs (depth + 1) body0 - absorbAbs _ expr | isAbsorbableExpr expr = hardspace <> group' Priority (absorbExpr False expr) + absorbAbs !_ expr | isAbsorbableExpr expr = hardspace <> group' Priority (absorbExpr False expr) -- Force the content onto a new line when it is not absorbable and there are more than two arguments - absorbAbs depth x = + absorbAbs !depth x = (if depth <= 2 then line else hardline) <> pretty x -- Attrset parameter @@ -679,7 +679,7 @@ instance Pretty Expression where prettyOperation (Just op', expr) = line <> pretty (moveTrailingCommentUp op') <> nest (absorbOperation expr) in group' RegularG $ - (concatMap prettyOperation . flatten Nothing) operation + (foldMap prettyOperation . flatten Nothing) operation pretty (MemberCheck expr qmark sel) = pretty expr <> softline diff --git a/src/Nixfmt/Types.hs b/src/Nixfmt/Types.hs index 7a3a31ed..49636ea6 100644 --- a/src/Nixfmt/Types.hs +++ b/src/Nixfmt/Types.hs @@ -1,11 +1,4 @@ -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE StrictData #-} module Nixfmt.Types ( ParseErrorBundle, @@ -44,14 +37,17 @@ module Nixfmt.Types ( walkSubprograms, ) where +import Control.DeepSeq (NFData) import Control.Monad.State.Strict (StateT) import Data.Bifunctor (first) import Data.Foldable (toList) import Data.Function (on) import Data.List.NonEmpty as NonEmpty import Data.Maybe (maybeToList) +import Data.Sequence (Seq) import Data.Text (Text, pack) import Data.Void (Void) +import GHC.Generics (Generic) import Text.Megaparsec (Pos) import qualified Text.Megaparsec as MP (ParseErrorBundle, Parsec, pos1) import Prelude hiding (String) @@ -71,10 +67,14 @@ data Trivium -- The bool indicates a doc comment (/**) BlockComment Bool [Text] deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) -type Trivia = [Trivium] +type Trivia = Seq Trivium -newtype TrailingComment = TrailingComment Text deriving (Eq, Show) +newtype TrailingComment = TrailingComment Text + deriving stock (Eq, Show, Generic) + deriving anyclass (NFData) data Ann a = Ann { preTrivia :: Trivia, @@ -83,7 +83,8 @@ data Ann a = Ann value :: a, trailComment :: Maybe TrailingComment } - deriving (Show) + deriving stock (Show, Generic) + deriving anyclass (NFData) removeLineInfo :: Ann a -> Ann a removeLineInfo a = a{sourceLine = MP.pos1} @@ -109,11 +110,7 @@ ann l v = -- | Equality of annotated syntax is defined as equality of their corresponding -- semantics, thus ignoring the annotations. instance (Eq a) => Eq (Ann a) where - Ann{value = x} == Ann{value = y} = x == y - --- Trivia is ignored for Eq, so also don't show --- instance Show a => Show (Ann a) where --- show (Ann _ a _) = show a + (==) = (==) `on` value data Item a = -- | An item @@ -121,8 +118,13 @@ data Item a | -- | Trivia interleaved in items Comments Trivia deriving (Foldable, Show, Functor) + deriving stock (Generic) + deriving anyclass (NFData) -newtype Items a = Items {unItems :: [Item a]} deriving (Functor) +newtype Items a = Items {unItems :: [Item a]} + deriving (Functor) + deriving stock (Generic) + deriving anyclass (NFData) instance (Eq a) => Eq (Items a) where (==) = (==) `on` concatMap Data.Foldable.toList . unItems @@ -137,6 +139,8 @@ data StringPart = TextPart Text | Interpolation (Whole Expression) deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) type Path = Ann [StringPart] @@ -148,18 +152,24 @@ type String = Ann [[StringPart]] data SimpleSelector = IDSelector Leaf | InterpolSelector (Ann StringPart) - | StringSelector String + | StringSelector !String deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data Selector = -- `.selector` Selector (Maybe Leaf) SimpleSelector deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data Binder = Inherit Leaf (Maybe Term) [SimpleSelector] Leaf | Assignment [Selector] Leaf Expression Leaf deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data Term = Token Leaf @@ -173,19 +183,24 @@ data Term | Selection Term [Selector] (Maybe (Leaf, Term)) | Parenthesized Leaf Expression Leaf deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data ParamAttr = -- name, Maybe question mark and default, maybe comma ParamAttr Leaf (Maybe (Leaf, Expression)) (Maybe Leaf) | ParamEllipsis Leaf deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data Parameter = IDParameter Leaf | SetParameter Leaf [ParamAttr] Leaf | ContextParameter Parameter Leaf Parameter deriving (Show) - + deriving stock (Generic) + deriving anyclass (NFData) instance Eq Parameter where (IDParameter l) == (IDParameter r) = l == r (SetParameter l1 l2 l3) == (SetParameter r1 r2 r3) = @@ -214,11 +229,15 @@ data Expression | Negation Leaf Expression | Inversion Leaf Expression deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) -- | A Whole a is an a including final trivia. It's assumed the a stores the -- initial trivia. data Whole a = Whole a Trivia + deriving stock (Generic) + deriving anyclass (NFData) -- | Equality of annotated syntax is defined as equality of their corresponding -- semantics, thus ignoring the annotations. @@ -540,6 +559,8 @@ data Token | TPipeBackward | SOF deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data Fixity = Prefix @@ -548,11 +569,15 @@ data Fixity | InfixR | Postfix deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) data Operator = Op Fixity Token | Apply deriving (Eq, Show) + deriving stock (Generic) + deriving anyclass (NFData) -- | A list of lists of operators where lists that come first contain operators -- that bind more strongly. diff --git a/src/Nixfmt/Util.hs b/src/Nixfmt/Util.hs index 8bc59866..df03dfb7 100644 --- a/src/Nixfmt/Util.hs +++ b/src/Nixfmt/Util.hs @@ -49,18 +49,22 @@ uriChar = charClass "~!@$%&*-=_+:',./?" -- | Match one or more characters that match a predicate. someP :: (MonadParsec e s m) => (Token s -> Bool) -> m (Tokens s) someP = takeWhile1P Nothing +{-# INLINEABLE someP #-} -- | Match zero or more characters that match a predicate. manyP :: (MonadParsec e s m) => (Token s -> Bool) -> m (Tokens s) manyP = takeWhileP Nothing +{-# INLINEABLE manyP #-} -- | Match one or more texts and return the concatenation. someText :: (MonadParsec e s m) => m Text -> m Text someText p = Text.concat <$> some p +{-# INLINEABLE someText #-} -- | Match zero or more texts and return the concatenation. manyText :: (MonadParsec e s m) => m Text -> m Text manyText p = Text.concat <$> many p +{-# INLINEABLE manyText #-} -- | The longest common prefix of the arguments. commonPrefix :: Text -> Text -> Text