on master help sign in
Handle OPTIONS_GHC and OPTIONS_HADDOCK pragmas

This change adds an ad-hoc parser for module pragmas to handle OPTIONS_* pragmas. I did not want to use an existing tokenizer, because I felt like tokenizing and pretty printing the GHC options are more prone to error without providing much benefit.

commitb6c29bdf127553760896f35ea2cda07de76f7022 authorUtku Demir <me@utdemir.com> dateFri 9 Aug 2019, 12:11 (7 years ago) parentbe6d09d
14 files changed+178−155
data/examples/other/language-pragma-no-header-out.hs → data/examples/other/pragma-no-header-out.hs data/examples/other/language-pragma-no-header.hs → data/examples/other/pragma-no-header.hs data/examples/other/language-pragma-out.hs → data/examples/other/pragma-out.hs+3 data/examples/other/language-pragma.hs → data/examples/other/pragma.hs+3 ormolu.cabal+3−3 src/Ormolu/Parser/CommentStream.hs+8−10 src/Ormolu/Parser/LanguagePragma.hs−86 src/Ormolu/Parser/Pragma.hs+91 src/Ormolu/Parser/Result.hs+2−2 src/Ormolu/Printer/Meat/LanguagePragma.hs−23 src/Ormolu/Printer/Meat/Module.hs+7−6 src/Ormolu/Printer/Meat/Pragma.hs+39 tests/Ormolu/Parser/LanguagePragmaSpec.hs−25 tests/Ormolu/Parser/PragmaSpec.hs+22
data/examples/other/language-pragma-no-header-out.hs → data/examples/other/pragma-no-header-out.hsrenamed

The content is the same.

data/examples/other/language-pragma-no-header.hs → data/examples/other/pragma-no-header.hsrenamed

The content is the same.

data/examples/other/language-pragma-out.hs → data/examples/other/pragma-out.hsrenamed+3
@@ -4,6 +4,9 @@
44{-# LANGUAGE LambdaCase #-}
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE ViewPatterns #-}
7+{-# OPTIONS_GHC -O2 -H 300 #-}
8+{-# OPTIONS_GHC -Wall -Werror #-}
9+{-# OPTIONS_HADDOCK prune, show-extensions #-}
710
811-- | Header comment.
912module Foo
data/examples/other/language-pragma.hs → data/examples/other/pragma.hsrenamed+3
@@ -3,7 +3,10 @@
33{-# LANGUAGE LambdaCase #-}
44{-#LANGuagE ViewPatterns #-}
55{-# LANGUAGE OverloadedStrings#-}
6+{-# OPTIONS_GHC -Wall -Werror #-}
67{-# language DataKinds, LambdaCase #-}
8+{-# OPTIONS_HADDOCK prune, show-extensions #-}
9+{-# OPTIONS_GHC -O2 -H 300 #-}
710{-# language IncoherentInstances
811 , AllowAmbiguousTypes #-}
912
ormolu.cabal+3−3
@@ -56,7 +56,7 @@ library
5656 , Ormolu.Parser
5757 , Ormolu.Parser.Anns
5858 , Ormolu.Parser.CommentStream
59− , Ormolu.Parser.LanguagePragma
59+ , Ormolu.Parser.Pragma
6060 , Ormolu.Parser.Result
6161 , Ormolu.Printer
6262 , Ormolu.Printer.Combinators
@@ -79,7 +79,7 @@ library
7979 , Ormolu.Printer.Meat.Declaration.Value
8080 , Ormolu.Printer.Meat.Declaration.Warning
8181 , Ormolu.Printer.Meat.ImportExport
82− , Ormolu.Printer.Meat.LanguagePragma
82+ , Ormolu.Printer.Meat.Pragma
8383 , Ormolu.Printer.Meat.Module
8484 , Ormolu.Printer.Meat.Type
8585 , Ormolu.Printer.SpanStream
@@ -109,7 +109,7 @@ test-suite tests
109109 build-tools: hspec-discover >= 2.0 && < 3.0
110110 other-modules:
111111 Ormolu.Printer.CombinatorsSpec
112− , Ormolu.Parser.LanguagePragmaSpec
112+ , Ormolu.Parser.PragmaSpec
113113 , Ormolu.PrinterSpec
114114
115115 if flag(dev)
src/Ormolu/Parser/CommentStream.hs+8−10
@@ -21,12 +21,10 @@ import Data.Either (partitionEithers)
2121import Data.List (isPrefixOf, sortOn, dropWhileEnd)
2222import Data.List.NonEmpty (NonEmpty (..))
2323import Data.Maybe (mapMaybe)
24−import Data.Set (Set)
25−import Ormolu.Parser.LanguagePragma
24+import Ormolu.Parser.Pragma
2625import Ormolu.Utils (showOutputable)
2726import SrcLoc
2827import qualified Data.List.NonEmpty as NE
29−import qualified Data.Set as S
3028import qualified GHC
3129import qualified Lexer as GHC
3230
@@ -49,7 +47,7 @@ newtype Comment = Comment (NonEmpty String)
4947mkCommentStream
5048 :: [Located String] -- ^ Extra comments to include
5149 -> GHC.PState -- ^ Parser state to use for comment extraction
52− -> (CommentStream, Set String)
50+ -> (CommentStream, [Pragma])
5351 -- ^ Comment stream and a set of extensions enabled
5452mkCommentStream extraComments pstate =
5553 ( CommentStream $
@@ -57,10 +55,10 @@ mkCommentStream extraComments pstate =
5755 -- streams. Because this way we need to do it only once and when we
5856 -- perform checking later they'll automatically match.
5957 mkComment <$> sortOn (realSrcSpanStart . getLoc) comments
60− , S.unions exts
58+ , pragma
6159 )
6260 where
63− (comments, exts) = partitionEithers (partitionComments <$> rawComments)
61+ (comments, pragma) = partitionEithers (partitionComments <$> rawComments)
6462 rawComments = mapMaybe toRealSpan $
6563 extraComments ++
6664 (fmap unAnnotationComment <$> GHC.comment_q pstate) ++
@@ -121,13 +119,13 @@ toRealSpan :: Located a -> Maybe (RealLocated a)
121119toRealSpan (L (RealSrcSpan l) a) = Just (L l a)
122120toRealSpan _ = Nothing
123121
124−-- | If a given comment is a LANGUAGE pragma, return the set of extensions
122+-- | If a given comment is a pragma, return the set of extensions
125123-- it enables in 'Right'. Otherwise return the original comment unchanged.
126124
127125partitionComments
128126 :: RealLocated String
129− -> Either (RealLocated String) (Set String)
127+ -> Either (RealLocated String) Pragma
130128partitionComments input =
131− case parseLanguagePragma (unLoc input) of
129+ case parsePragma (unLoc input) of
132130 Nothing -> Left input
133− Just exts -> Right exts
131+ Just pr -> Right pr
src/Ormolu/Parser/LanguagePragma.hsdeleted−86
@@ -1,86 +0,0 @@
1−-- | A module for parsing of language pragmas from comments.
2−
3−{-# LANGUAGE LambdaCase #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−
6−module Ormolu.Parser.LanguagePragma
7− ( parseLanguagePragma
8− )
9−where
10−
11−import Control.Monad
12−import Data.Char (toLower)
13−import Data.List (stripPrefix, isSuffixOf)
14−import Data.Set (Set)
15−import FastString (mkFastString, unpackFS)
16−import Module (newSimpleUnitId, ComponentId (..))
17−import SrcLoc
18−import StringBuffer
19−import qualified Data.Set as S
20−import qualified EnumSet as ES
21−import qualified Lexer as L
22−
23−-- | Extract a 'Set' of language extensions from a comment if it's a
24−-- language pragma or return 'Nothing' otherwise.
25−
26−parseLanguagePragma
27− :: String -- ^ Comment to try to parse
28− -> Maybe (Set String)
29−parseLanguagePragma input = do
30− inputNoPrefix <- stripPrefix "{-#" input
31− guard ("#-}" `isSuffixOf` input)
32− let contents = take (length inputNoPrefix - 3) inputNoPrefix
33− toks0 <- tokenize contents
34− toks1 <- dropLanguage toks0
35− S.fromList <$> parseExtensions toks1
36−
37−-- | Assuming the input consists of a series of tokens from a language
38−-- pragma, return the set of enabled extensions.
39−
40−parseExtensions :: [L.Token] -> Maybe [String]
41−parseExtensions = \case
42− (L.ITconid ext : []) -> return [unpackFS ext]
43− (L.ITconid ext : L.ITcomma : xs) -> (unpackFS ext :) <$> parseExtensions xs
44− _ -> Nothing
45−
46−-- | Drop LANGUAGE pragma text from the token stream.
47−
48−dropLanguage :: [L.Token] -> Maybe [L.Token]
49−dropLanguage = \case
50− (L.ITconid lang : xs) -> go lang xs
51− (L.ITvarid lang : xs) -> go lang xs
52− _ -> Nothing
53− where
54− go lang xs =
55− if (toLower <$> unpackFS lang) == "language"
56− then Just xs
57− else Nothing
58−
59−-- | Tokenize a given input using GHC's lexer.
60−
61−tokenize :: String -> Maybe [L.Token]
62−tokenize input =
63− case L.unP pLexer parseState of
64− L.PFailed {} -> Nothing
65− L.POk _ x -> Just x
66− where
67− location = mkRealSrcLoc (mkFastString "") 1 1
68− buffer = stringToStringBuffer input
69− parseState = L.mkPStatePure parserFlags buffer location
70− parserFlags = L.ParserFlags
71− { L.pWarningFlags = ES.empty
72− , L.pExtensionFlags = ES.empty
73− , L.pThisPackage = newSimpleUnitId (ComponentId (mkFastString ""))
74− , L.pExtsBitmap = 0xffffffffffffffff
75− }
76−
77−-- | Haskell lexer.
78−
79−pLexer :: L.P [L.Token]
80−pLexer = go
81− where
82− go = do
83− r <- L.lexer False return
84− case unLoc r of
85− L.ITeof -> return []
86− x -> (x:) <$> go
src/Ormolu/Parser/Pragma.hsnew file+91
@@ -0,0 +1,91 @@
1+-- | A module for parsing of language pragmas from comments.
2+
3+{-# LANGUAGE LambdaCase #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+
6+module Ormolu.Parser.Pragma
7+ ( Pragma (..)
8+ , parsePragma
9+ )
10+where
11+
12+import Data.Char (toLower, isSpace)
13+import Data.List (stripPrefix, dropWhileEnd)
14+import FastString (mkFastString, unpackFS)
15+import Module (newSimpleUnitId, ComponentId (..))
16+import SrcLoc
17+import StringBuffer
18+import qualified EnumSet as ES
19+import qualified Lexer as L
20+
21+data Pragma
22+ = PragmaLanguage [String]
23+ | PragmaOptionsGHC String
24+ | PragmaOptionsHaddock String
25+ deriving (Show, Eq)
26+
27+-- | Extract a Pragma from a comment if possible, or return 'Nothing'
28+-- otherwise.
29+
30+parsePragma :: String -> Maybe Pragma
31+parsePragma input = do
32+ contents <- trimSpaces <$> stripEnclosing "{-#" "#-}" input
33+ let (name, cs) = separateName contents
34+ case map toLower name of
35+ "language" -> PragmaLanguage <$> parseExtensions cs
36+ "options_ghc" -> Just $ PragmaOptionsGHC (trimSpaces cs)
37+ "options_haddock" -> Just $ PragmaOptionsHaddock (trimSpaces cs)
38+ _ -> Nothing
39+ where
40+ -- stripEnclosing pre suf (pre ++ str ++ suf) == Just str
41+ stripEnclosing :: String -> String -> String -> Maybe String
42+ stripEnclosing pre suf str =
43+ fmap reverse $ stripPrefix pre str >>= stripPrefix (reverse suf) . reverse
44+
45+ trimSpaces :: String -> String
46+ trimSpaces = dropWhileEnd isSpace . dropWhile isSpace
47+
48+-- | Assuming the input consists of a series of tokens from a language
49+-- pragma, return the set of enabled extensions.
50+
51+parseExtensions :: String -> Maybe [String]
52+parseExtensions str = tokenize str >>= go
53+ where
54+ go = \case
55+ (L.ITconid ext : []) -> return [unpackFS ext]
56+ (L.ITconid ext : L.ITcomma : xs) -> (unpackFS ext :) <$> go xs
57+ _ -> Nothing
58+
59+-- | Split a pragma to name and contents
60+
61+separateName :: String -> (String, String)
62+separateName = span (not . isSpace) . dropWhile isSpace
63+
64+-- | Tokenize a given input using GHC's lexer.
65+
66+tokenize :: String -> Maybe [L.Token]
67+tokenize input =
68+ case L.unP pLexer parseState of
69+ L.PFailed {} -> Nothing
70+ L.POk _ x -> Just x
71+ where
72+ location = mkRealSrcLoc (mkFastString "") 1 1
73+ buffer = stringToStringBuffer input
74+ parseState = L.mkPStatePure parserFlags buffer location
75+ parserFlags = L.ParserFlags
76+ { L.pWarningFlags = ES.empty
77+ , L.pExtensionFlags = ES.empty
78+ , L.pThisPackage = newSimpleUnitId (ComponentId (mkFastString ""))
79+ , L.pExtsBitmap = 0xffffffffffffffff
80+ }
81+
82+-- | Haskell lexer.
83+
84+pLexer :: L.P [L.Token]
85+pLexer = go
86+ where
87+ go = do
88+ r <- L.lexer False return
89+ case unLoc r of
90+ L.ITeof -> return []
91+ x -> (x:) <$> go
src/Ormolu/Parser/Result.hs+2−2
@@ -8,10 +8,10 @@ module Ormolu.Parser.Result
88 )
99where
1010
11−import Data.Set (Set)
1211import GHC
1312import Ormolu.Parser.Anns
1413import Ormolu.Parser.CommentStream
14+import Ormolu.Parser.Pragma (Pragma)
1515
1616-- | A collection of data that represents a parsed module in Ormolu.
1717
@@ -22,7 +22,7 @@ data ParseResult = ParseResult
2222 -- ^ Ormolu-specfic representation of annotations
2323 , prCommentStream :: CommentStream
2424 -- ^ Comment stream
25− , prExtensions :: Set String
25+ , prExtensions :: [Pragma]
2626 -- ^ Extensions enabled in that module
2727 }
2828
src/Ormolu/Printer/Meat/LanguagePragma.hsdeleted−23
@@ -1,23 +0,0 @@
1−-- | Pretty-printing of language pragmas.
2−
3−{-# LANGUAGE LambdaCase #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−
6−module Ormolu.Printer.Meat.LanguagePragma
7− ( p_langPragmas
8− )
9−where
10−
11−import Data.Set (Set)
12−import Ormolu.Printer.Combinators
13−import qualified Data.Set as S
14−import qualified Data.Text as T
15−
16−p_langPragmas :: Set String -> R ()
17−p_langPragmas = mapM_ p_langPragma . S.toAscList
18−
19−p_langPragma :: String -> R ()
20−p_langPragma ext = line $ do
21− txt "{-# LANGUAGE "
22− txt (T.pack ext)
23− txt " #-}"
src/Ormolu/Printer/Meat/Module.hs+7−6
@@ -1,3 +1,4 @@
1+{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE RecordWildCards #-}
34
@@ -10,9 +11,9 @@ where
1011
1112import Control.Monad
1213import Data.Maybe (isJust)
13−import Data.Set (Set)
1414import GHC
1515import Ormolu.Imports
16+import Ormolu.Parser.Pragma
1617import Ormolu.Printer.Combinators
1718import Ormolu.Printer.Comments
1819import Ormolu.Printer.Internal (isNewlineModified)
@@ -20,10 +21,10 @@ import Ormolu.Printer.Meat.Common
2021import Ormolu.Printer.Meat.Declaration
2122import Ormolu.Printer.Meat.Declaration.Warning
2223import Ormolu.Printer.Meat.ImportExport
23−import Ormolu.Printer.Meat.LanguagePragma
24+import Ormolu.Printer.Meat.Pragma
2425
25−p_hsModule :: Set String -> ParsedSource -> R ()
26−p_hsModule exts (L moduleSpan HsModule {..}) = do
26+p_hsModule :: [Pragma] -> ParsedSource -> R ()
27+p_hsModule pragmas (L moduleSpan HsModule {..}) = do
2728 -- NOTE If span of exports in multiline, the whole thing is multiline.
2829 -- This is especially important because span of module itself always seems
2930 -- to have length zero, so it's not reliable for layout selection.
@@ -31,11 +32,11 @@ p_hsModule exts (L moduleSpan HsModule {..}) = do
3132 deprecSpan = maybe [] (\(L s _) -> [s]) hsmodDeprecMessage
3233 spans' = exportSpans ++ deprecSpan ++ [moduleSpan]
3334 switchLayout spans' $ do
34− let hasLangPragmas = not (null exts)
35+ let hasLangPragmas = not (null pragmas)
3536 hasModuleHeader = isJust hsmodName
3637 hasImports = not (null hsmodImports)
3738 hasDecls = not (null hsmodDecls)
38− p_langPragmas exts
39+ p_pragmas pragmas
3940 when (hasLangPragmas &&
4041 (hasModuleHeader || hasImports || hasDecls)) $
4142 newline
src/Ormolu/Printer/Meat/Pragma.hsnew file+39
@@ -0,0 +1,39 @@
1+-- | Pretty-printing of language pragmas.
2+
3+{-# LANGUAGE LambdaCase #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE TupleSections #-}
6+
7+module Ormolu.Printer.Meat.Pragma
8+ ( p_pragmas
9+ )
10+where
11+
12+import Ormolu.Printer.Combinators
13+import Ormolu.Parser.Pragma (Pragma (..))
14+import qualified Data.Set as S
15+import qualified Data.Text as T
16+
17+data PragmaTy = Language | OptionsGHC | OptionsHaddock
18+ deriving (Eq, Ord)
19+
20+p_pragmas :: [Pragma] -> R ()
21+p_pragmas ps =
22+ let ps' = concatMap (\case
23+ PragmaLanguage xs -> map (Language,) xs
24+ PragmaOptionsGHC x -> [(OptionsGHC, x)]
25+ PragmaOptionsHaddock x -> [(OptionsHaddock, x)]
26+ ) ps
27+ sorted = S.toAscList . S.fromList $ ps'
28+ in mapM_ (uncurry p_pragma) sorted
29+
30+p_pragma :: PragmaTy -> String -> R ()
31+p_pragma ty c = line $ do
32+ txt "{-# "
33+ txt $ case ty of
34+ Language -> "LANGUAGE"
35+ OptionsGHC -> "OPTIONS_GHC"
36+ OptionsHaddock -> "OPTIONS_HADDOCK"
37+ txt " "
38+ txt (T.pack c)
39+ txt " #-}"
tests/Ormolu/Parser/LanguagePragmaSpec.hsdeleted−25
@@ -1,25 +0,0 @@
1−module Ormolu.Parser.LanguagePragmaSpec (spec) where
2−
3−import Ormolu.Parser.LanguagePragma
4−import Test.Hspec
5−import qualified Data.Set as S
6−
7−spec :: Spec
8−spec = do
9− describe "parseLanguagePragma" $ do
10− stdTest "{-# LANGUAGE Foo #-}" (Just ["Foo"])
11− stdTest "{-# language Foo #-}" (Just ["Foo"])
12− stdTest "{-#LANGUAGE Foo#-}" (Just ["Foo"])
13− stdTest "{-# LANGUAGE Foo#-}" (Just ["Foo"])
14− stdTest "{-#language Foo#-}" (Just ["Foo"])
15− stdTest "{-# lAngUAGe Foo #-}" (Just ["Foo"])
16− stdTest "{-# LANGUAGE Foo, Bar #-}" (Just ["Bar", "Foo"])
17− stdTest "{-# LANGUAGE Foo Bar #-}" Nothing
18− stdTest "{-# BOO Foo #-}" Nothing
19− stdTest "something" Nothing
20− stdTest "{-# LANGUAGE foo, Bar #-}" Nothing
21−
22−stdTest :: String -> Maybe [String] -> Spec
23−stdTest input result = it input $
24− (S.toAscList <$> parseLanguagePragma input) `shouldBe`
25− result
tests/Ormolu/Parser/PragmaSpec.hsnew file+22
@@ -0,0 +1,22 @@
1+module Ormolu.Parser.PragmaSpec (spec) where
2+
3+import Ormolu.Parser.Pragma
4+import Test.Hspec
5+
6+spec :: Spec
7+spec = do
8+ describe "parsePragma" $ do
9+ stdTest "{-# LANGUAGE Foo #-}" (Just $ PragmaLanguage ["Foo"])
10+ stdTest "{-# language Foo #-}" (Just $ PragmaLanguage ["Foo"])
11+ stdTest "{-# lAngUAGe Foo#-}" (Just $ PragmaLanguage ["Foo"])
12+ stdTest "{-# LANGUAGE Foo, Bar #-}" (Just $ PragmaLanguage ["Foo", "Bar"])
13+ stdTest "{-# OPTIONS_GHC foo bar baz #-}" (Just $ PragmaOptionsGHC "foo bar baz")
14+ stdTest "{-#OPTIONS_HADDOCK foo, bar, baz #-}" (Just $ PragmaOptionsHaddock "foo, bar, baz")
15+ stdTest "{-# LANGUAGE Foo Bar #-}" Nothing
16+ stdTest "{-# BOO Foo #-}" Nothing
17+ stdTest "something" Nothing
18+ stdTest "{-# LANGUAGE foo, Bar #-}" Nothing
19+
20+stdTest :: String -> Maybe Pragma -> Spec
21+stdTest input result = it input $
22+ parsePragma input `shouldBe` result