55{-# LANGUAGE RecordWildCards #-}
66{-# LANGUAGE StandaloneDeriving #-}
77{-# LANGUAGE StrictData #-}
8+ {-# LANGUAGE TupleSections #-}
89{-# LANGUAGE UndecidableInstances #-}
910{-# LANGUAGE ViewPatterns #-}
1011
@@ -21,6 +22,8 @@ module Database.Persist.Quasi.Internal
2122 , toFKNameInfixed
2223 , Token (.. )
2324 , Line (.. )
25+ , SourceLoc (.. )
26+ , sourceLocFromTHLoc
2427 , preparse
2528 , parseLine
2629 , parseFieldType
@@ -66,7 +69,7 @@ import qualified Data.Text as T
6669import Database.Persist.EntityDef.Internal
6770import Database.Persist.Types
6871import Database.Persist.Types.Base
69- import Language.Haskell.TH.Syntax (Lift )
72+ import Language.Haskell.TH.Syntax (Lift , Loc ( .. ) )
7073import qualified Text.Read as R
7174
7275data ParseState a = PSDone | PSFail String | PSSuccess a Text deriving Show
@@ -205,14 +208,48 @@ toFKNameInfixed :: Text -> EntityNameHS -> ConstraintNameHS -> Text
205208toFKNameInfixed inf (EntityNameHS entName) (ConstraintNameHS conName) =
206209 entName <> inf <> conName
207210
211+ -- | Source location: file and line/col information. This is half of a 'Span'.
212+ data SourceLoc = SourceLoc
213+ { locFile :: Text
214+ , locStartLine :: Int
215+ , locStartCol :: Int
216+ } deriving (Show , Lift )
217+
218+ sourceLocFromTHLoc :: Loc -> SourceLoc
219+ sourceLocFromTHLoc Loc {loc_filename= filename, loc_start= start} =
220+ SourceLoc {locFile = T. pack filename, locStartLine = fst start, locStartCol = snd start}
221+
222+
208223-- | Parses a quasi-quoted syntax into a list of entity definitions.
209- parse :: PersistSettings -> Text -> [UnboundEntityDef ]
210- parse ps = maybe [] (parseLines ps) . preparse
224+ parse :: PersistSettings -> [(Maybe SourceLoc , Text )] -> [UnboundEntityDef ]
225+ parse ps blocks =
226+ mconcat $ handleBlock <$> blocks
227+ where
228+ handleBlock (mLoc, block) =
229+ maybe []
230+ (\ (numLines, lns) -> parseLines ps (approximateSpan numLines block <$> mLoc) lns)
231+ (preparse block)
232+ -- FIXME: put an actually truthful span into here
233+ -- We can't give a better result if we push any of this down into the
234+ -- parser at the moment since the parser throws out location info by
235+ -- e.g. discarding comment lines completely without keeping track of
236+ -- where it is. The realistic fix to this is rewriting the parser in
237+ -- Megaparsec, which is a reasonable idea that should actually be done.
238+ approximateSpan numLines block loc =
239+ Span
240+ { spanFile = locFile loc
241+ , spanStartLine = locStartLine loc
242+ , spanStartCol = locStartCol loc
243+ , spanEndLine = locStartLine loc + numLines - 1
244+ -- Last line's length plus one (since we are one-past-the-end)
245+ , spanEndCol = (+ 1 ) . T. length . T. takeWhileEnd (/= ' \n ' ) $ block
246+ }
211247
212- preparse :: Text -> Maybe (NonEmpty Line )
248+ preparse :: Text -> Maybe (Int , NonEmpty Line )
213249preparse txt = do
214250 lns <- NEL. nonEmpty (T. lines txt)
215- NEL. nonEmpty $ mapMaybe parseLine (NEL. toList lns)
251+ let rawLineCount = length lns
252+ (rawLineCount,) <$> NEL. nonEmpty (mapMaybe parseLine (NEL. toList lns))
216253
217254parseLine :: Text -> Maybe Line
218255parseLine txt = do
@@ -303,9 +340,9 @@ lowestIndent :: NonEmpty Line -> Int
303340lowestIndent = minimum . fmap lineIndent
304341
305342-- | Divide lines into blocks and make entity definitions.
306- parseLines :: PersistSettings -> NonEmpty Line -> [UnboundEntityDef ]
307- parseLines ps = do
308- fmap (mkUnboundEntityDef ps . toParsedEntityDef) . associateLines
343+ parseLines :: PersistSettings -> Maybe Span -> NonEmpty Line -> [UnboundEntityDef ]
344+ parseLines ps mSpan = do
345+ fmap (mkUnboundEntityDef ps . toParsedEntityDef mSpan ) . associateLines
309346
310347data ParsedEntityDef = ParsedEntityDef
311348 { parsedEntityDefComments :: [Text ]
@@ -314,6 +351,7 @@ data ParsedEntityDef = ParsedEntityDef
314351 , parsedEntityDefEntityAttributes :: [Attr ]
315352 , parsedEntityDefFieldAttributes :: [[Token ]]
316353 , parsedEntityDefExtras :: M. Map Text [ExtraLine ]
354+ , parsedEntityDefSpan :: Maybe Span
317355 }
318356
319357entityNamesFromParsedDef :: PersistSettings -> ParsedEntityDef -> (EntityNameHS , EntityNameDB )
@@ -325,14 +363,15 @@ entityNamesFromParsedDef ps parsedEntDef = (entNameHS, entNameDB)
325363 entNameDB =
326364 EntityNameDB $ getDbName ps (unEntityNameHS entNameHS) (parsedEntityDefEntityAttributes parsedEntDef)
327365
328- toParsedEntityDef :: LinesWithComments -> ParsedEntityDef
329- toParsedEntityDef lwc = ParsedEntityDef
366+ toParsedEntityDef :: Maybe Span -> LinesWithComments -> ParsedEntityDef
367+ toParsedEntityDef mSpan lwc = ParsedEntityDef
330368 { parsedEntityDefComments = lwcComments lwc
331369 , parsedEntityDefEntityName = entNameHS
332370 , parsedEntityDefIsSum = isSum
333371 , parsedEntityDefEntityAttributes = entAttribs
334372 , parsedEntityDefFieldAttributes = attribs
335373 , parsedEntityDefExtras = extras
374+ , parsedEntityDefSpan = mSpan
336375 }
337376 where
338377 entityLine :| fieldLines =
@@ -458,6 +497,10 @@ data UnboundEntityDef
458497 -- the field?" yet, so we defer those to the Template Haskell execution.
459498 --
460499 -- @since 2.13.0.0
500+ , unboundEntityDefSpan :: Maybe Span
501+ -- ^ The source code span of this entity in the models file.
502+ --
503+ -- @since 2.15.0.0
461504 }
462505 deriving (Eq , Ord , Show , Lift )
463506
@@ -481,6 +524,7 @@ unbindEntityDef ed =
481524 ed
482525 , unboundEntityFields =
483526 map unbindFieldDef (entityFields ed)
527+ , unboundEntityDefSpan = entitySpan ed
484528 }
485529
486530-- | Returns the @['UnboundFieldDef']@ for an 'UnboundEntityDef'. This returns
@@ -689,6 +733,7 @@ mkUnboundEntityDef ps parsedEntDef =
689733 DefaultKey (FieldNameDB $ psIdName ps)
690734 , unboundEntityFields =
691735 cols
736+ , unboundEntityDefSpan = parsedEntityDefSpan parsedEntDef
692737 , unboundEntityDef =
693738 EntityDef
694739 { entityHaskell = entNameHS
@@ -712,6 +757,7 @@ mkUnboundEntityDef ps parsedEntDef =
712757 case parsedEntityDefComments parsedEntDef of
713758 [] -> Nothing
714759 comments -> Just (T. unlines comments)
760+ , entitySpan = parsedEntityDefSpan parsedEntDef
715761 }
716762 }
717763 where
0 commit comments