-- | This module contains the definition of a source span data type that is -- used by the AST of the intermediate language to store for every node the -- corresponding portion of the source code. -- -- Source spans can be pretty printed and are used by the error reporting -- engine of the compiler to point the user to the relevant piece of code -- when an error occurs. module FreeC.IR.SrcSpan ( -- * Source Files SrcFile(..) , mkSrcFile , hasSrcFileContents -- * Source File Maps , SrcFileMap , mkSrcFileMap , lookupSrcFile -- * Source Spans , SrcSpan(..) -- * Selectors , srcSpanCodeLines -- * Predicates , hasSrcSpanFile , hasSourceCode , spansMultipleLines -- * Conversion , ConvertibleSrcSpan(..) ) where import Data.Maybe ( fromMaybe ) import Data.Tuple.Extra ( (&&&) ) import FreeC.Pretty ------------------------------------------------------------------------------- -- Source Files -- ------------------------------------------------------------------------------- -- | Data type that contains the name and contents of source files. -- -- The contents of the source file are stored as a string for parsing -- and as a list of lines for error messages. data SrcFile = SrcFile { srcFileName :: FilePath -- ^ The name of the file. , srcFileContents :: String -- ^ The contents of the file. , srcFileLines :: [String] -- ^ The lines of 'srcFileContents'. } | NoSrcFile -- ^ A source file whose contents are unknown. { srcFileName :: FilePath } deriving ( Eq, Show ) -- | Smart constructor for 'SrcFile' that automatically splits the file -- contents into lines. mkSrcFile :: FilePath -> String -> SrcFile mkSrcFile filename contents = SrcFile { srcFileName = filename , srcFileContents = contents , srcFileLines = lines contents } -- | Tests whether the contents of the given source file are known. hasSrcFileContents :: SrcFile -> Bool hasSrcFileContents SrcFile {} = True hasSrcFileContents NoSrcFile {} = False ------------------------------------------------------------------------------- -- Source File Maps -- ------------------------------------------------------------------------------- -- | Type for a map that associates source files with their filename. type SrcFileMap = [(FilePath, SrcFile)] -- | Smart constructor for 'SrcFileMap' for the given 'SrcFile's. mkSrcFileMap :: [SrcFile] -> SrcFileMap mkSrcFileMap = map (srcFileName &&& id) -- | Looks up a 'SrcFile' in a 'SrcFileMap'. -- -- Returns 'NoSrcFile' if the map does not contain such a source file. lookupSrcFile :: FilePath -> SrcFileMap -> SrcFile lookupSrcFile filename = fromMaybe (NoSrcFile filename) . lookup filename ------------------------------------------------------------------------------- -- Source Spans -- ------------------------------------------------------------------------------- -- | Describes the portion of the source code that caused a message to be -- reported. -- -- In contrast to the source spans provided by the @haskell-src-exts@ package -- this source span provides access to the lines of code that contain the -- source span. data SrcSpan = SrcSpan { srcSpanFile :: SrcFile -- ^ The source file if known. , srcSpanStartLine :: Int -- ^ The number of the first line. , srcSpanStartColumn :: Int -- ^ The offset within the first line. , srcSpanEndLine :: Int -- ^ The number of the last line. , srcSpanEndColumn :: Int -- ^ The offset within the last line. } | NoSrcSpan -- ^ Indicates that no location information is available. | FileSpan -- ^ Points to an unknown location in the given file. { srcSpanFile :: SrcFile -- ^ The name of the file. } deriving ( Eq, Show ) ------------------------------------------------------------------------------- -- Selectors -- ------------------------------------------------------------------------------- -- | Gets the lines of source code spanned by the given source span. -- -- Returns an empty list if the given source span does not satisfy the -- 'hasSourceCode' predicate. srcSpanCodeLines :: SrcSpan -> [String] srcSpanCodeLines NoSrcSpan = [] srcSpanCodeLines (FileSpan _) = [] srcSpanCodeLines srcSpan@SrcSpan {} | hasSrcFileContents (srcSpanFile srcSpan) = take (srcSpanEndLine srcSpan - srcSpanStartLine srcSpan + 1) $ drop (srcSpanStartLine srcSpan - 1) $ srcFileLines (srcSpanFile srcSpan) | otherwise = [] ------------------------------------------------------------------------------- -- Predicates -- ------------------------------------------------------------------------------- -- | Tests whether the given 'SrcSpan' contains information about the file -- that is spanned (i.e., there is a field 'srcSpanFile'). hasSrcSpanFile :: SrcSpan -> Bool hasSrcSpanFile NoSrcSpan = False hasSrcSpanFile _ = True -- | Tests whether the given source span has attached source code. hasSourceCode :: SrcSpan -> Bool hasSourceCode NoSrcSpan = False hasSourceCode (FileSpan _) = False hasSourceCode srcSpan@SrcSpan {} = not (null (srcSpanCodeLines srcSpan)) -- | Tests whether the given source span spans multiple lines. spansMultipleLines :: SrcSpan -> Bool spansMultipleLines NoSrcSpan = False spansMultipleLines (FileSpan _) = False spansMultipleLines SrcSpan { srcSpanStartLine = start, srcSpanEndLine = end } = start /= end ------------------------------------------------------------------------------- -- Conversion -- ------------------------------------------------------------------------------- -- | Type class for source spans from other packages that can be converted -- to 'SrcSpan's for pretty printing of messages. class ConvertibleSrcSpan ss where -- | Converts the given third party source span to a 'SrcSpan' by attaching -- the corresponding line of source code. convertSrcSpan :: SrcFileMap -> ss -> SrcSpan ------------------------------------------------------------------------------- -- Pretty Printing Source Spans -- ------------------------------------------------------------------------------- -- | Pretty instance for a source file that displays the filename. instance Pretty SrcFile where pretty = prettyString . srcFileName -- | Pretty instance for a source span that displays the filename and the start -- and end position of the source span. -- -- If the source span spans only a single line, the end position is omitted. instance Pretty SrcSpan where pretty NoSrcSpan = prettyString "<no location info>" pretty (FileSpan srcFile) = prettyString (srcFileName srcFile) pretty srcSpan | spansMultipleLines srcSpan = pretty (srcSpanFile srcSpan) <> colon <> int (srcSpanStartLine srcSpan) <> colon <> int (srcSpanStartColumn srcSpan) <> char '-' <> int (srcSpanEndLine srcSpan) <> colon <> int (srcSpanEndColumn srcSpan) | otherwise = pretty (srcSpanFile srcSpan) <> colon <> int (srcSpanStartLine srcSpan) <> colon <> int (srcSpanStartColumn srcSpan)