Skip to content

Commit 5f0dc24

Browse files
committed
associate comments during parsing, rather than after
1 parent 9036157 commit 5f0dc24

2 files changed

Lines changed: 66 additions & 94 deletions

File tree

persistent/Database/Persist/Quasi/Internal/ModelParser.hs

Lines changed: 65 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
2931
import Control.Monad.Trans.State
3032
import Data.List (intercalate)
3133
import Data.List.NonEmpty (NonEmpty (..))
3234
import qualified Data.List.NonEmpty as NEL
3335
import qualified Data.Map as M
3436
import Data.Maybe
35-
import Data.Set (Set)
36-
import qualified Data.Set as Set
3737
import Data.Text (Text)
3838
import qualified Data.Text as Text
3939
import Data.Void
4040
import Database.Persist.Types
4141
import Database.Persist.Types.SourceSpan
4242
import Language.Haskell.TH.Syntax (Lift)
43-
import Replace.Megaparsec (sepCap)
4443
import Text.Megaparsec hiding (Token)
4544
import Text.Megaparsec.Char
4645
import 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

5262
type Parser a =
5363
StateT
@@ -198,9 +208,7 @@ comment = do
198208
skipComment :: Parser ()
199209
skipComment = do
200210
content <- docComment <|> comment
201-
comments <- get
202-
put $ comments ++ [content]
203-
pure ()
211+
void $ appendCommentToState content
204212

205213
spaceConsumer :: Parser ()
206214
spaceConsumer =
@@ -345,25 +353,14 @@ anyToken =
345353
class 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

352357
instance 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

359361
instance 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

368365
blockLastPos :: (Block a) => a -> SourcePos
369366
blockLastPos b = case blockMembers b of
@@ -442,11 +439,6 @@ data BlockAttr = BlockAttr
442439
data 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.
451443
memberEndPos :: Member -> SourcePos
452444
memberEndPos (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+
485509
extraBlock :: Parser Member
486510
extraBlock = 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

502528
extraBlockHeader :: Parser ExtraBlockHeader
503529
extraBlockHeader = 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

514541
blockAttr :: Parser Member
515542
blockAttr = 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
527556
member = try extraBlock <|> blockAttr
528557

529558
entityBlock :: 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

542573
entitiesFromDocument :: Parser [EntityBlock]
543574
entitiesFromDocument = 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-
624596
parseEntities
625597
:: Text
626598
-> String
627599
-> ParseResult [EntityBlock]
628600
parseEntities 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

633605
toParsedEntityDef :: Maybe SourceLoc -> EntityBlock -> ParsedEntityDef
634606
toParsedEntityDef mSourceLoc eb =

persistent/test/Database/Persist/QuasiSpec.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ spec = describe "Quasi" $ do
9393
describe "tokenization" $ do
9494
let
9595
tokenize s = do
96-
(d, c) <- runConfiguredParser [] (some anyToken) "" s
96+
(d, c) <- runConfiguredParser initialExtraState (some anyToken) "" s
9797
pure d
9898
it "handles normal words" $
9999
tokenize "foo bar baz"

0 commit comments

Comments
 (0)