@@ -24,30 +24,40 @@ module Database.Persist.Quasi.Internal.ModelParser
2424 , toCumulativeParseResult
2525 , renderErrors
2626 , runConfiguredParser
27+ , initialExtraState
2728 ) where
2829
30+ import Control.Monad (void )
2931import Control.Monad.Trans.State
3032import Data.List (intercalate )
3133import Data.List.NonEmpty (NonEmpty (.. ))
3234import qualified Data.List.NonEmpty as NEL
3335import qualified Data.Map as M
3436import Data.Maybe
35- import Data.Set (Set )
36- import qualified Data.Set as Set
3737import Data.Text (Text )
3838import qualified Data.Text as Text
3939import Data.Void
4040import Database.Persist.Types
4141import Database.Persist.Types.SourceSpan
4242import Language.Haskell.TH.Syntax (Lift )
43- import Replace.Megaparsec (sepCap )
4443import Text.Megaparsec hiding (Token )
4544import Text.Megaparsec.Char
4645import qualified Text.Megaparsec.Char.Lexer as L
4746
4847-- We'll augment the parser with extra state to accumulate comments seen during parsing.
4948-- Comments are lexed as whitespace, but will be used to generate documentation later.
50- type ExtraState = [(SourcePos , CommentToken )]
49+ data ExtraState = ExtraState
50+ { esPositionedCommentTokens :: [(SourcePos , CommentToken )]
51+ , esLastDocumentablePosition :: Maybe SourcePos
52+ }
53+
54+ -- @since 2.16.1.0
55+ initialExtraState :: ExtraState
56+ initialExtraState =
57+ ExtraState
58+ { esPositionedCommentTokens = []
59+ , esLastDocumentablePosition = Nothing
60+ }
5161
5262type Parser a =
5363 StateT
@@ -198,9 +208,7 @@ comment = do
198208skipComment :: Parser ()
199209skipComment = do
200210 content <- docComment <|> comment
201- comments <- get
202- put $ comments ++ [content]
203- pure ()
211+ void $ appendCommentToState content
204212
205213spaceConsumer :: Parser ()
206214spaceConsumer =
@@ -345,25 +353,14 @@ anyToken =
345353class Block a where
346354 blockFirstPos :: a -> SourcePos
347355 blockMembers :: a -> [Member ]
348- blockSetMembers :: [Member ] -> a -> a
349- blockSetNELMembers :: NonEmpty Member -> a -> a
350- blockSetDocCommentBlock :: Maybe DocCommentBlock -> a -> a
351356
352357instance Block EntityBlock where
353358 blockFirstPos = entityHeaderPos . entityBlockEntityHeader
354359 blockMembers = entityBlockMembers
355- blockSetMembers ms b = b{entityBlockMembers = ms}
356- blockSetNELMembers nel = blockSetMembers (NEL. toList nel)
357- blockSetDocCommentBlock dcb b = b{entityBlockDocCommentBlock = dcb}
358360
359361instance Block ExtraBlock where
360362 blockFirstPos = extraBlockHeaderPos . extraBlockExtraBlockHeader
361363 blockMembers = NEL. toList . extraBlockMembers
362- blockSetMembers ms b = case NEL. nonEmpty ms of
363- Nothing -> b
364- Just nel -> blockSetNELMembers nel b
365- blockSetNELMembers nel b = b{extraBlockMembers = nel}
366- blockSetDocCommentBlock dcb b = b{extraBlockDocCommentBlock = dcb}
367364
368365blockLastPos :: (Block a ) => a -> SourcePos
369366blockLastPos b = case blockMembers b of
@@ -442,11 +439,6 @@ data BlockAttr = BlockAttr
442439data Member = MemberExtraBlock ExtraBlock | MemberBlockAttr BlockAttr
443440 deriving (Show )
444441
445- -- | The source position at the beginning of the member's first line.
446- memberPos :: Member -> SourcePos
447- memberPos (MemberBlockAttr fs) = blockAttrPos fs
448- memberPos (MemberExtraBlock ex) = extraBlockHeaderPos . extraBlockExtraBlockHeader $ ex
449-
450442-- | The source position at the beginning of the member's final line.
451443memberEndPos :: Member -> SourcePos
452444memberEndPos (MemberBlockAttr fs) = blockAttrPos fs
@@ -474,6 +466,7 @@ entityHeader = do
474466 plus <- optional (char ' +' )
475467 en <- hspace *> L. lexeme spaceConsumer blockKey
476468 rest <- L. lexeme spaceConsumer (many anyToken)
469+ _ <- setLastDocumentablePosition
477470 pure
478471 EntityHeader
479472 { entityHeaderSum = isJust plus
@@ -482,28 +475,62 @@ entityHeader = do
482475 , entityHeaderPos = pos
483476 }
484477
478+ appendCommentToState :: (SourcePos , CommentToken ) -> Parser ()
479+ appendCommentToState ptok = do
480+ es <- get
481+ let
482+ comments = esPositionedCommentTokens es
483+ void $ put es{esPositionedCommentTokens = comments ++ [ptok]}
484+
485+ setLastDocumentablePosition :: Parser ()
486+ setLastDocumentablePosition = do
487+ pos <- getSourcePos
488+ es <- get
489+ void $ put es{esLastDocumentablePosition = Just pos}
490+
491+ getDcb :: Parser (Maybe DocCommentBlock )
492+ getDcb = do
493+ es <- get
494+ let
495+ comments = esPositionedCommentTokens es
496+ _ <- put es{esPositionedCommentTokens = [] }
497+ let
498+ candidates = dropWhile (\ (_sp, ct) -> not (isDocComment ct)) comments
499+ let
500+ filteredCandidates = dropWhile (commentIsIncorrectlyPositioned es) candidates
501+ pure $ docCommentBlockFromPositionedTokens filteredCandidates
502+ where
503+ commentIsIncorrectlyPositioned
504+ :: ExtraState -> (SourcePos , CommentToken ) -> Bool
505+ commentIsIncorrectlyPositioned es ptok = case esLastDocumentablePosition es of
506+ Nothing -> False
507+ Just lastDocumentablePos -> (sourceLine . fst ) ptok <= sourceLine lastDocumentablePos
508+
485509extraBlock :: Parser Member
486510extraBlock = L. indentBlock spaceConsumerN innerParser
487511 where
488- mkExtraBlockMember (header, blockAttrs) =
512+ mkExtraBlockMember dcb (header, blockAttrs) =
489513 MemberExtraBlock
490514 ExtraBlock
491515 { extraBlockExtraBlockHeader = header
492516 , extraBlockMembers = ensureNonEmpty blockAttrs
493- , extraBlockDocCommentBlock = Nothing
517+ , extraBlockDocCommentBlock = dcb
494518 }
495519 ensureNonEmpty members = case NEL. nonEmpty members of
496520 Just nel -> nel
497521 Nothing -> error " unreachable" -- members is known to be non-empty
498522 innerParser = do
523+ dcb <- getDcb
499524 header <- extraBlockHeader
500- pure $ L. IndentSome Nothing (return . mkExtraBlockMember . (header,)) blockAttr
525+ pure $
526+ L. IndentSome Nothing (return . mkExtraBlockMember dcb . (header,)) blockAttr
501527
502528extraBlockHeader :: Parser ExtraBlockHeader
503529extraBlockHeader = do
504530 pos <- getSourcePos
505531 tn <- L. lexeme spaceConsumer blockKey
506532 rest <- L. lexeme spaceConsumer (many anyToken)
533+ _ <- setLastDocumentablePosition
507534 pure $
508535 ExtraBlockHeader
509536 { extraBlockHeaderKey = tokenContent tn
@@ -513,12 +540,14 @@ extraBlockHeader = do
513540
514541blockAttr :: Parser Member
515542blockAttr = do
543+ dcb <- getDcb
516544 pos <- getSourcePos
517545 line <- some anyToken
546+ _ <- setLastDocumentablePosition
518547 pure $
519548 MemberBlockAttr
520549 BlockAttr
521- { blockAttrDocCommentBlock = Nothing
550+ { blockAttrDocCommentBlock = dcb
522551 , blockAttrTokens = line
523552 , blockAttrPos = pos
524553 }
@@ -527,17 +556,19 @@ member :: Parser Member
527556member = try extraBlock <|> blockAttr
528557
529558entityBlock :: Parser EntityBlock
530- entityBlock = L. indentBlock spaceConsumerN innerParser
559+ entityBlock = do
560+ L. indentBlock spaceConsumerN innerParser
531561 where
532- mkEntityBlock (header, members) =
562+ mkEntityBlock dcb (header, members) =
533563 EntityBlock
534564 { entityBlockEntityHeader = header
535565 , entityBlockMembers = members
536- , entityBlockDocCommentBlock = Nothing
566+ , entityBlockDocCommentBlock = dcb
537567 }
538568 innerParser = do
569+ dcb <- getDcb
539570 header <- entityHeader
540- pure $ L. IndentMany Nothing (return . mkEntityBlock . (header,)) member
571+ pure $ L. IndentMany Nothing (return . mkEntityBlock dcb . (header,)) member
541572
542573entitiesFromDocument :: Parser [EntityBlock ]
543574entitiesFromDocument = many entityBlock
@@ -562,73 +593,14 @@ docCommentBlockFromPositionedTokens ptoks =
562593 , docCommentBlockPos = fst $ NEL. head nel
563594 }
564595
565- associateCommentLines
566- :: [(SourcePos , CommentToken )] -> [EntityBlock ] -> [EntityBlock ]
567- associateCommentLines _ [] = []
568- associateCommentLines [] es = es
569- associateCommentLines cls (eh : et) =
570- applyCommentLinesToBlock candidateLines eh
571- : associateCommentLines remainingLines et
572- where
573- dcLines = dropWhile (not . isDocComment . snd ) cls
574- candidateLines =
575- takeWhile
576- (\ (spos, _) -> sourceLine spos < sourceLine (blockLastPos eh))
577- dcLines
578- remainingLines = drop (length candidateLines) dcLines
579-
580- -- | Accepts a list of (position, comment) pairs and associates them with the
581- -- block and its members.
582- applyCommentLinesToBlock :: (Block a ) => [(SourcePos , CommentToken )] -> a -> a
583- applyCommentLinesToBlock [] a = a
584- applyCommentLinesToBlock cls a = blockSetDocCommentBlock dcb $ blockSetMembers commentedMembers a
585- where
586- startLine = sourceLine $ blockFirstPos a
587- headerLines = takeWhile (\ (spos, _t) -> sourceLine spos < startLine) cls
588- memberLines = dropWhile (\ (spos, _t) -> sourceLine spos <= startLine) cls
589- dcb = docCommentBlockFromPositionedTokens headerLines
590- commentedMembers = associateCommentLinesWithMembers memberLines (blockMembers a)
591-
592- associateCommentLinesWithMembers
593- :: [(SourcePos , CommentToken )] -> [Member ] -> [Member ]
594- associateCommentLinesWithMembers [] ms = ms
595- associateCommentLinesWithMembers _ [] = []
596- associateCommentLinesWithMembers cls ms@ (mh : mt) = do
597- applyCommentLinesToMember candidateLines mh
598- : associateCommentLinesWithMembers remainingLines mt
599- where
600- -- we must ignore comments that share a line number with any member
601- membersLinePoses = Set. fromDistinctAscList $ fmap (sourceLine . memberPos) ms
602- filteredLines = filter (\ (pos, _t) -> Set. notMember (sourceLine pos) membersLinePoses) cls
603- dcLines = dropWhile (not . isDocComment . snd ) filteredLines
604- candidateLines =
605- takeWhile (\ (spos, _) -> sourceLine spos < sourceLine (memberEndPos mh)) dcLines
606- remainingLines = drop (length candidateLines) dcLines
607-
608- applyCommentLinesToMember :: [(SourcePos , CommentToken )] -> Member -> Member
609- applyCommentLinesToMember cls m = case m of
610- MemberBlockAttr a -> MemberBlockAttr $ applyCommentLinesToBlockAttr cls a
611- MemberExtraBlock b -> MemberExtraBlock $ applyCommentLinesToBlock cls b
612-
613- applyCommentLinesToBlockAttr
614- :: [(SourcePos , CommentToken )] -> BlockAttr -> BlockAttr
615- applyCommentLinesToBlockAttr [] a = a
616- applyCommentLinesToBlockAttr cls a = a{blockAttrDocCommentBlock = dcb}
617- where
618- ls =
619- takeWhile
620- (\ (spos, _t) -> sourceLine spos < sourceLine (blockAttrPos a))
621- cls
622- dcb = docCommentBlockFromPositionedTokens ls
623-
624596parseEntities
625597 :: Text
626598 -> String
627599 -> ParseResult [EntityBlock ]
628600parseEntities fp s = do
629- (entities, comments ) <-
630- runConfiguredParser [] entitiesFromDocument (Text. unpack fp) s
631- pure $ associateCommentLines comments entities
601+ (entities, _comments ) <-
602+ runConfiguredParser initialExtraState entitiesFromDocument (Text. unpack fp) s
603+ pure entities
632604
633605toParsedEntityDef :: Maybe SourceLoc -> EntityBlock -> ParsedEntityDef
634606toParsedEntityDef mSourceLoc eb =
0 commit comments