packages feed

proto3-suite-0.9.4: src/Proto3/Suite/Haskell/Parser.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}

module Proto3.Suite.Haskell.Parser
  ( Logger
  , initLogger
  , parseModule
  , renderSDoc
  ) where

import qualified GHC.Data.EnumSet as EnumSet
import GHC.Data.StringBuffer (StringBuffer)
import GHC.Driver.Session (languageExtensions)
import qualified GHC.Hs
import qualified GHC.Parser
import GHC.Parser.Lexer (P(..), PState, ParseResult(..))
import GHC.Types.SrcLoc (Located, RealSrcLoc)
import GHC.Utils.Outputable (SDoc)

#if MIN_VERSION_ghc_lib_parser(9,8,0)
import qualified GHC.Driver.Errors (printMessages)
import GHC.Parser.Errors.Types (PsMessage)
import GHC.Parser.Lexer (getPsMessages, initParserState, mkParserOpts)
import GHC.Types.Error (Messages, NoDiagnosticOpts(..), partitionMessages, unionMessages)
import GHC.Utils.Error (DiagOpts, emptyDiagOpts)
import GHC.Utils.Logger (Logger, initLogger)
import GHC.Utils.Outputable (defaultSDocContext, renderWithContext)
#elif MIN_VERSION_ghc_lib_parser(9,6,0)
import qualified GHC.Driver.Errors (printMessages)
import GHC.Parser.Errors.Types (PsMessage)
import GHC.Parser.Lexer (getPsMessages, initParserState, mkParserOpts)
import GHC.Types.Error (Messages, NoDiagnosticOpts(..), partitionMessages, unionMessages)
import GHC.Utils.Error (DiagOpts(..))
import GHC.Utils.Logger (Logger, initLogger)
import GHC.Utils.Outputable (defaultSDocContext, renderWithContext)
#elif MIN_VERSION_ghc_lib_parser(9,4,0)
import qualified GHC.Driver.Errors (printMessages)
import GHC.Parser.Errors.Types (PsMessage)
import GHC.Parser.Lexer (getPsMessages, initParserState, mkParserOpts)
import GHC.Types.Error (Messages, partitionMessages, unionMessages)
import GHC.Utils.Error (DiagOpts(..))
import GHC.Utils.Logger (Logger, initLogger)
import GHC.Utils.Outputable (defaultSDocContext, renderWithContext)
#else
import Control.Arrow ((***))
import Data.Foldable (traverse_)
import GHC.ByteOrder (targetByteOrder)
import GHC.Data.Bag (Bag)
import GHC.Driver.Session
         (DynFlags(..), FileSettings(..), GhcNameVersion(..),
          LlvmConfig(..), Settings(..), defaultDynFlags)
import GHC.Parser.Errors.Ppr (pprError, pprWarning)
import GHC.Parser.Lexer (getMessages, initParserState, mkParserOpts)
import GHC.Platform (ArchOS(..))
import GHC.Settings (Platform(..), PlatformMisc(..), ToolSettings(..))
import GHC.Types.Error (DecoratedSDoc, MsgEnvelope(..), renderDiagnostic)
import GHC.Utils.Error (formatBulleted, sortMsgBag)
import GHC.Utils.Fingerprint (fingerprint0)
import GHC.Utils.Logger (Logger, initLogger, putLogMsg)
import GHC.Utils.Outputable (defaultSDocContext, mkErrStyle, renderWithContext, withPprStyle)
#endif

-- | Parses the module with the specified location and content,
-- returning 'Nothing' on parse failure.  Errors and warnings
-- are sent to the given 'Logger', except on GHC 9.0, which
-- would require a 'DynFlags' in order to report messages.
-- Unfortunately, creating a 'DynFlags' would require a directory
-- containing a GHC installation; we do not wish to require that.
parseModule ::
  Logger ->
  RealSrcLoc ->
  StringBuffer ->
  IO (Maybe (Located (GHC.Hs.HsModule
#if MIN_VERSION_ghc_lib_parser(9,6,0)
                                      GHC.Hs.GhcPs
#endif
                     )))
parseModule logger location input = do
    case unP GHC.Parser.parseModule initialState of
      POk _finalState m -> do
        printWarningsAndErrors logger diagOpts _finalState
        pure (Just m)
      PFailed _finalState -> do
        printWarningsAndErrors logger diagOpts _finalState
        pure Nothing
  where
    exts = EnumSet.fromList (languageExtensions Nothing)
#if MIN_VERSION_ghc_lib_parser(9,4,0)
    diagOpts =
#if MIN_VERSION_ghc_lib_parser(9,8,0)
      emptyDiagOpts
#else
      DiagOpts
        { diag_warning_flags = mempty
        , diag_fatal_warning_flags = mempty
        , diag_warn_is_error = False
        , diag_reverse_errors = False
        , diag_max_errors = Nothing
        , diag_ppr_ctx = defaultSDocContext
        }
#endif
    parserOpts = mkParserOpts exts diagOpts [] False True True True
    initialState = initParserState parserOpts input location
#else
    diagOpts = DiagOpts
    parserOpts = mkParserOpts EnumSet.empty exts False True True True
    initialState = initParserState parserOpts input location
#endif

printWarningsAndErrors :: Logger -> DiagOpts -> PState -> IO ()
printWarningsAndErrors logger diagOpts state = do
#if MIN_VERSION_ghc_lib_parser(9,4,0)
  let (ws, es) = getPsMessages state
  let (warnings, unionMessages es -> errors) = partitionMessages ws
#else
  let (warnings, errors) = (fmap pprWarning *** fmap pprError) (getMessages state)
#endif
  printMessages logger diagOpts warnings
  printMessages logger diagOpts errors

#if MIN_VERSION_ghc_lib_parser(9,6,0)

printMessages :: Logger -> DiagOpts -> Messages PsMessage -> IO ()
printMessages logger = GHC.Driver.Errors.printMessages logger NoDiagnosticOpts

#elif MIN_VERSION_ghc_lib_parser(9,4,0)

printMessages :: Logger -> DiagOpts -> Messages PsMessage -> IO ()
printMessages = GHC.Driver.Errors.printMessages

#else

printMessages :: Logger -> DiagOpts -> Bag (MsgEnvelope DecoratedSDoc) -> IO ()
printMessages logger _ = traverse_ report . sortMsgBag Nothing
  where
    report MsgEnvelope
             { errMsgContext = errCtxt
             , errMsgDiagnostic = diagnostic
             , errMsgReason = reason
             , errMsgSeverity = severity
             , errMsgSpan = sp } =
        putLogMsg logger renderingDynFlags reason severity sp $
          withPprStyle (mkErrStyle errCtxt) $
            formatBulleted defaultSDocContext (renderDiagnostic diagnostic)

#endif

renderSDoc :: SDoc -> String
renderSDoc = renderWithContext defaultSDocContext

#if !MIN_VERSION_ghc_lib_parser(9,4,0)

data DiagOpts = DiagOpts

-- | 'DynFlags' suitable only for rendering
-- Haskell source and diagnostic messages.
--
-- NOTE: For use with GHC 9.0 only.  These flags are rather questionable
-- all of the tools direction information consists of placeholders so
-- that compile-proto-file does not require an actual GHC installation.
renderingDynFlags :: DynFlags
renderingDynFlags = defaultDynFlags placeholderSettings placeholderLlvmConfig
  where
    archUnknown = read "ArchUnknown"
    osUnknown = read "OSUnknown"
    placeholderSettings = Settings
      { sGhcNameVersion = GhcNameVersion "compile-proto-file" "v?"
      , sFileSettings = FileSettings
        { fileSettings_ghcUsagePath = mempty
        , fileSettings_ghciUsagePath = mempty
        , fileSettings_toolDir = Nothing
        , fileSettings_topDir = mempty
        , fileSettings_tmpDir = mempty
        , fileSettings_globalPackageDatabase = mempty
        }
      , sTargetPlatform = Platform
        { platformArchOS = ArchOS archUnknown osUnknown
        , platformWordSize = read "8"
        , platformByteOrder = targetByteOrder
        , platformUnregisterised = True
        , platformHasGnuNonexecStack = False
        , platformHasIdentDirective = False
        , platformHasSubsectionsViaSymbols = False
        , platformIsCrossCompiling = True
        , platformLeadingUnderscore = True
        , platformTablesNextToCode = False
        , platform_constants = Nothing
        }
      , sToolSettings = ToolSettings
        { toolSettings_ldSupportsCompactUnwind = False
        , toolSettings_ldSupportsBuildId = False
        , toolSettings_ldSupportsFilelist = False
        , toolSettings_ldIsGnuLd = False
        , toolSettings_ccSupportsNoPie = False
        , toolSettings_pgm_L = mempty
        , toolSettings_pgm_P = mempty
        , toolSettings_pgm_F = mempty
        , toolSettings_pgm_c = mempty
        , toolSettings_pgm_a = mempty
        , toolSettings_pgm_l = mempty
        , toolSettings_pgm_lm = mempty
        , toolSettings_pgm_dll = mempty
        , toolSettings_pgm_T = mempty
        , toolSettings_pgm_windres = mempty
        , toolSettings_pgm_libtool = mempty
        , toolSettings_pgm_ar = mempty
        , toolSettings_pgm_otool = mempty
        , toolSettings_pgm_install_name_tool = mempty
        , toolSettings_pgm_ranlib = mempty
        , toolSettings_pgm_lo = mempty
        , toolSettings_pgm_lc = mempty
        , toolSettings_pgm_lcc = mempty
        , toolSettings_pgm_i = mempty
        , toolSettings_opt_L = mempty
        , toolSettings_opt_P = mempty
        , toolSettings_opt_P_fingerprint = fingerprint0
        , toolSettings_opt_F = mempty
        , toolSettings_opt_c = mempty
        , toolSettings_opt_cxx = mempty
        , toolSettings_opt_a = mempty
        , toolSettings_opt_l = mempty
        , toolSettings_opt_lm = mempty
        , toolSettings_opt_windres = mempty
        , toolSettings_opt_lo = mempty
        , toolSettings_opt_lc = mempty
        , toolSettings_opt_lcc = mempty
        , toolSettings_opt_i = mempty
        , toolSettings_extraGccViaCFlags = mempty
        }
      , sPlatformMisc = PlatformMisc
        { platformMisc_targetPlatformString = mempty
        , platformMisc_ghcWithInterpreter = False
        , platformMisc_ghcWithSMP = False
        , platformMisc_ghcRTSWays = mempty
        , platformMisc_libFFI = False
        , platformMisc_ghcRtsWithLibdw = False
        , platformMisc_llvmTarget = mempty
        }
      , sRawSettings = mempty
      }

    placeholderLlvmConfig = LlvmConfig
      { llvmTargets = mempty
      , llvmPasses = mempty
      }

#endif