packages feed

keiro-dsl-0.8.0.0: src/Keiro/Dsl/Parser/Document.hs

{-# LANGUAGE ImportQualifiedPost #-}

-- | Released-language dispatch and complete document composition.
module Keiro.Dsl.Parser.Document
  ( parseSurfaceSource,
  )
where

import Data.Text (Text)
import Data.Text qualified as T
import Keiro.Dsl.Frontend.Internal
import Keiro.Dsl.Grammar
import Keiro.Dsl.LanguageVersion
import Keiro.Dsl.Parser.Aggregate (pAggregate)
import Keiro.Dsl.Parser.Coordination (pProcess, pRouter)
import Keiro.Dsl.Parser.Core
import Keiro.Dsl.Parser.Declaration (pEnumDecl, pIdDecl, pRuleDecl)
import Keiro.Dsl.Parser.Integration (pContract, pEmit, pIntake, pPublisher)
import Keiro.Dsl.Parser.Mapped (pMappedTopItem)
import Keiro.Dsl.Parser.Preamble
import Keiro.Dsl.Parser.Queue (pPgmqDispatch, pWorkqueue)
import Keiro.Dsl.Parser.ReadModel (pReadModel)
import Keiro.Dsl.Parser.Workflow (pOperation, pWorkflow)
import Keiro.Dsl.Source
import Keiro.Dsl.Syntax
import Text.Megaparsec
import Prelude hiding (span)

parseSurfaceSource :: FilePath -> Text -> Either FrontendFailure SurfaceSource
parseSurfaceSource src input = do
  sourceLanguage <- selectSourceLanguage src input
  definition <- case lookupLanguageDefinition (effectiveLanguageVersion sourceLanguage) of
    Just value -> Right value
    Nothing -> error "keiro-dsl internal invariant: source selection returned an unregistered language"
  parseSelectedBody FrontendContext {source = src, language = sourceLanguage, definition}
  where
    parseSelectedBody context@FrontendContext {language = sourceLanguage} =
      let laterPreambleCode = case sourceLanguage of
            LegacyUnversioned -> MisplacedLanguagePreamble
            DeclaredLanguage {} -> DuplicateLanguagePreamble
          parser =
            sc
              *> case sourceLanguage of
                LegacyUnversioned -> pSurfaceDocument context Nothing laterPreambleCode <* eof
                DeclaredLanguage {} -> do
                  locatedPreamble <- withOwnedSpan (sourceLanguage <$ pDeclaredPreamble)
                  pSurfaceDocument context (Just locatedPreamble) laterPreambleCode <* eof
       in case runParser parser src input of
            Left bundle -> case firstContextualFailure bundle of
              Just contextual ->
                let diagnostic = contextualDiagnostic src sourceLanguage contextual
                    supported = languageVersionsSupportingFeature <$> contextualFailureFeature contextual
                 in Left (frontendFailureFromSourceDiagnostic BodyParsingPhase (contextualFailureSpan contextual) supported diagnostic)
              Nothing ->
                Left
                  ( frontendFailureFromBody
                      BodyParsingPhase
                      (bundleFailureSpan bundle)
                      (bundleMessage bundle)
                      (bundleExpected bundle)
                      (BodyGrammarFailure (T.pack (errorBundlePretty bundle)))
                  )
            Right surface -> Right surface

-- Top level
--------------------------------------------------------------------------------

pContextualPreamble :: SourceLanguageErrorCode -> P a
pContextualPreamble code = do
  locatedPreamble <- withOwnedSpan (try pDeclaredPreamble)
  contextualFailureAt (spanOf locatedPreamble) code

pSurfaceDocument :: FrontendContext -> Maybe (Located SourceLanguage) -> SourceLanguageErrorCode -> P SurfaceSource
pSurfaceDocument context@FrontendContext {source = sourceName, language = sourceLanguage} preamble laterPreambleCode = do
  spec <- pSurfaceSpec context laterPreambleCode
  pure SurfaceSource {source = sourceName, language = sourceLanguage, preamble, spec}

pSurfaceSpec :: FrontendContext -> SourceLanguageErrorCode -> P (Located SurfaceSpec)
pSurfaceSpec context laterPreambleCode = do
  pContextualPreamble laterPreambleCode <|> pure ()
  locatedContext <- withOwnedSpan (keyword "context" *> wireWord)
  moduleRoot <- optional (withOwnedSpan pModuleClause)
  layout <- optional (withOwnedSpan pLayoutClause)
  parsedItems <- many (withOwnedSpan (pTopItem context laterPreambleCode))
  let items = map surfaceItem parsedItems
      elements = concatMap surfaceElements parsedItems
  let contextSpan@SourceSpan {source, start} = spanOf locatedContext
      finalSpan = case reverse items of
        item : _ -> spanOf item
        [] -> case layout of
          Just value -> spanOf value
          Nothing -> maybe contextSpan spanOf moduleRoot
      SourceSpan {end} = finalSpan
  pure
    Located
      { span = SourceSpan {source, start, end},
        value = SurfaceSpec {context = locatedContext, moduleRoot, layout, items, elements}
      }
  where
    surfaceItem Located {span, value = ParsedTopItem item _} = Located {span, value = item}
    surfaceElements Located {value = ParsedTopItem _ elements} = elements

-- | @module Acme.Services@ — the optional namespace-prefix clause.
pModuleClause :: P Text
pModuleClause = keyword "module" *> pModulePrefix

-- | @layout (prefixed|collocated)@ — the optional placement-style clause.
pLayoutClause :: P Placement
pLayoutClause =
  keyword "layout"
    *> choice
      [ GeneratedPrefix <$ keyword "prefixed",
        CollocatedLeaf <$ keyword "collocated"
      ]

-- | A parsed top-level value plus any nested syntax evidence it owns.
data ParsedTopItem = ParsedTopItem !SurfaceTopItem ![Located SurfaceElement]

pTopItem :: FrontendContext -> SourceLanguageErrorCode -> P ParsedTopItem
pTopItem context laterPreambleCode =
  choice
    ( [ pContextualPreamble laterPreambleCode,
        plain (SurfaceId <$> pIdDecl context),
        plain (SurfaceEnum <$> pEnumDecl context),
        do
          (rule, elements) <- pRuleDecl context
          pure (ParsedTopItem (SurfaceRule rule) elements),
        plain (pMappedTopItem context)
      ]
        ++ [ plain (SurfaceNode . NRouter <$> pRouter),
             plain (SurfaceNode . NProcess <$> pProcess),
             plain (SurfaceNode . NContract <$> pContract),
             plain (SurfaceNode . NIntake <$> pIntake),
             plain (SurfaceNode . NEmit <$> pEmit),
             plain (SurfaceNode . NPublisher <$> pPublisher),
             plain (SurfaceNode . NWorkqueue <$> pWorkqueue),
             plain (SurfaceNode . NPgmqDispatch <$> pPgmqDispatch),
             plain (SurfaceNode . NReadModel <$> pReadModel),
             plain (SurfaceNode . NWorkflow <$> pWorkflow),
             plain (SurfaceNode . NOperation <$> pOperation),
             do
               (aggregate, elements) <- pAggregate context
               pure (ParsedTopItem (SurfaceNode (NAggregate aggregate)) elements)
           ]
    )
  where
    plain parser = (`ParsedTopItem` []) <$> parser