packages feed

aihc-parser-1.0.0.2: src/Aihc/Parser/Internal/Import.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}

module Aihc.Parser.Internal.Import
  ( languagePragmaParser,
    moduleHeaderParser,
    importDeclParser,
    warningPragmaParser,
  )
where

import Aihc.Parser.Internal.Common
import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind, pattern TkVarAs, pattern TkVarHiding, pattern TkVarQualified, pattern TkVarSafe)
import Aihc.Parser.Syntax
import Aihc.Parser.Types (ParserErrorComponent (..), mkFoundToken)
import Control.Monad (when)
import Data.Char (isUpper)
import Data.Maybe (isJust)
import Data.Text (Text)
import Data.Text qualified as T
import Text.Megaparsec ((<|>))
import Text.Megaparsec qualified as MP

languagePragmaParser :: TokParser [ExtensionSetting]
languagePragmaParser =
  hiddenPragma "LANGUAGE pragma" $ \p -> case pragmaType p of
    PragmaLanguage names -> Just names
    _ -> Nothing

moduleHeaderParser :: TokParser ModuleHead
moduleHeaderParser = withSpan $ do
  expectedTok TkKeywordModule
  name <- moduleNameParser
  mWarning <- MP.optional warningPragmaParser
  exports <- MP.optional exportSpecListParser
  expectedTok TkKeywordWhere
  pure $ \span' ->
    ModuleHead
      { moduleHeadAnns = [mkAnnotation span'],
        moduleHeadName = name,
        moduleHeadWarningPragma = mWarning,
        moduleHeadExports = exports
      }

warningPragmaParser :: TokParser Pragma
warningPragmaParser =
  hiddenPragma "warning pragma" $ \p -> case pragmaType p of
    PragmaWarning _ -> Just p
    PragmaDeprecated _ -> Just p
    _ -> Nothing

exportSpecListParser :: TokParser [ExportSpec]
exportSpecListParser = parens $ exportSpecParser `MP.sepEndBy` expectedTok TkSpecialComma

exportSpecParser :: TokParser ExportSpec
exportSpecParser = withSpanAnn (ExportAnn . mkAnnotation) $ do
  mWarning <- MP.optional warningPragmaParser
  exportModuleParser mWarning <|> exportNameParser mWarning

exportModuleParser :: Maybe Pragma -> TokParser ExportSpec
exportModuleParser mWarning = do
  expectedTok TkKeywordModule
  ExportModule mWarning <$> moduleNameParser

exportNameParser :: Maybe Pragma -> TokParser ExportSpec
exportNameParser mWarning = do
  namespace <- MP.optional exportImportNamespaceParser
  name <- identifierNameParser <|> parens operatorNameParser
  members <- MP.optional exportMembersParser
  pure $
    case members of
      Just MembersAll -> ExportAll mWarning namespace name
      Just (MembersList names) -> ExportWith mWarning namespace name names
      Just (MembersListAll wildcardIndex names) -> ExportWithAll mWarning namespace name wildcardIndex names
      Nothing
        | namespace == Just IEEntityNamespaceType || isTypeName name ->
            ExportAbs mWarning namespace name
        | otherwise ->
            ExportVar mWarning namespace name

data MembersResult
  = MembersAll
  | MembersList [IEBundledMember]
  | MembersListAll Int [IEBundledMember]

exportMembersParser :: TokParser MembersResult
exportMembersParser = membersParser

importMembersParser :: TokParser MembersResult
importMembersParser = membersParser

membersParser :: TokParser MembersResult
membersParser =
  parens (parseDotDotFirst <|> parseMemberList <|> emptyMembers)
  where
    emptyMembers = pure (MembersList [])

    parseDotDotFirst = do
      expectedTok TkReservedDotDot
      MP.optional (expectedTok TkSpecialComma) >>= \case
        Nothing -> pure MembersAll
        Just _ -> do
          trailingMembers <- memberNameParser `MP.sepBy` expectedTok TkSpecialComma
          pure (MembersListAll 0 trailingMembers)

    parseMemberList = do
      firstMember <- memberNameParser
      parseMemberSegments [firstMember]

    parseMemberSegments members =
      MP.optional (expectedTok TkSpecialComma) >>= \case
        Nothing -> pure (MembersList members)
        Just _ ->
          (expectedTok TkReservedDotDot >> parseWildcardTail members)
            <|> do
              nextMember <- memberNameParser
              parseMemberSegments (members <> [nextMember])

    parseWildcardTail members =
      MP.optional (expectedTok TkSpecialComma) >>= \case
        Nothing -> pure (MembersListAll (length members) members)
        Just _ -> do
          trailingMembers <- memberNameParser `MP.sepBy` expectedTok TkSpecialComma
          pure (MembersListAll (length members) (members <> trailingMembers))

    memberNameParser = do
      namespace <- MP.optional bundledNamespaceParser
      name <- identifierNameParser <|> parens operatorNameParser
      pure (IEBundledMember namespace name)

-- | Checks if a name refers to a type/class (as opposed to a variable/function).
-- In Haskell:
-- - Identifiers starting with uppercase letters are type constructors/classes
-- - Symbolic operators starting with ':' are constructor operators (type-level)
isTypeName :: Name -> Bool
isTypeName name =
  case T.uncons (nameText name) of
    Just (c, _) -> isUpper c || c == ':'
    Nothing -> False

importDeclParser :: TokParser ImportDecl
importDeclParser = withSpan $ do
  expectedTok TkKeywordImport
  importedSafe <-
    MP.option False (expectedTok TkVarSafe >> pure True)
  importedSource <- optionalHiddenPragma $ \p -> case pragmaType p of
    PragmaSource {} -> Just p
    _ -> Nothing
  preQualified <-
    MP.option False (expectedTok TkVarQualified >> pure True)
  importedLevel <- MP.optional importLevelParser
  importedPackage <- MP.optional packageNameParser
  importedModule <- moduleNameParser
  postQualified <-
    MP.optional $
      tokenSatisfy "'qualified'" $ \tok ->
        if lexTokenKind tok == TkVarQualified then Just (mkFoundToken tok) else Nothing
  when (preQualified && isJust postQualified) $
    MP.customFailure
      UnexpectedTokenExpecting
        { unexpectedFound = postQualified,
          unexpectedExpecting = "import declaration without duplicate 'qualified'",
          unexpectedContext = []
        }
  importAlias <- MP.optional (expectedTok TkVarAs *> moduleNameParser)
  importSpec <- MP.optional importSpecParser
  let isQualified = preQualified || isJust postQualified
  pure $ \span' ->
    ImportDecl
      { importDeclAnns = [mkAnnotation span'],
        importDeclLevel = importedLevel,
        importDeclPackage = importedPackage,
        importDeclSourcePragma = importedSource,
        importDeclSafe = importedSafe,
        importDeclQualified = isQualified,
        importDeclQualifiedPost = isJust postQualified,
        importDeclModule = importedModule,
        importDeclAs = importAlias,
        importDeclSpec = importSpec
      }

importLevelParser :: TokParser ImportLevel
importLevelParser =
  (varIdTok "quote" >> pure ImportLevelQuote)
    <|> (varIdTok "splice" >> pure ImportLevelSplice)

packageNameParser :: TokParser Text
packageNameParser = stringTextParser

importSpecParser :: TokParser ImportSpec
importSpecParser = withSpan $ do
  isHiding <-
    MP.option False (expectedTok TkVarHiding >> pure True)
  items <- parens $ importItemParser `MP.sepEndBy` expectedTok TkSpecialComma
  pure $ \span' ->
    ImportSpec
      { importSpecAnns = [mkAnnotation span'],
        importSpecHiding = isHiding,
        importSpecItems = items
      }

importItemParser :: TokParser ImportItem
importItemParser = withSpanAnn (ImportAnn . mkAnnotation) $ do
  namespace <- MP.optional exportImportNamespaceParser
  itemName <- identifierUnqualifiedNameParser <|> parens importOperatorParser
  -- When there's no explicit namespace, we still need to try parsing members
  -- for type constructors and type classes (uppercase names or parenthesized operators)
  let shouldTryMembers = case namespace of
        Just _ -> True
        Nothing -> isTypeName (qualifyName Nothing itemName)
  members <- if shouldTryMembers then MP.optional importMembersParser else pure Nothing
  let effectiveNamespace = namespace
  pure $
    case members of
      Just MembersAll -> ImportItemAll effectiveNamespace itemName
      Just (MembersList names) -> ImportItemWith effectiveNamespace itemName names
      Just (MembersListAll wildcardIndex names) -> ImportItemAllWith effectiveNamespace itemName wildcardIndex names
      Nothing
        | effectiveNamespace == Just IEEntityNamespaceType || isTypeName (qualifyName Nothing itemName) -> ImportItemAbs effectiveNamespace itemName
        | otherwise -> ImportItemVar effectiveNamespace itemName

importOperatorParser :: TokParser UnqualifiedName
importOperatorParser = operatorUnqualifiedNameParser

exportImportNamespaceParser :: TokParser IEEntityNamespace
exportImportNamespaceParser =
  (expectedTok TkKeywordType >> pure IEEntityNamespaceType)
    <|> (expectedTok TkKeywordData >> pure IEEntityNamespaceData)
    <|> patternNamespaceParser
  where
    patternNamespaceParser = do
      patSynEnabled <- isExtensionEnabled PatternSynonyms
      if patSynEnabled
        then expectedTok TkKeywordPattern >> pure IEEntityNamespacePattern
        else MP.empty

bundledNamespaceParser :: TokParser IEBundledNamespace
bundledNamespaceParser =
  (expectedTok TkKeywordType >> pure IEBundledNamespaceType)
    <|> (expectedTok TkKeywordData >> pure IEBundledNamespaceData)