packages feed

oplang-0.4.0.1: src/Language/OpLang/Validation.hs

module Language.OpLang.Validation(validate) where

import Control.Monad(unless)
import Control.Monad.Chronicle(MonadChronicle(..))
import Data.Bifunctor(bimap)
import Data.Foldable(foldMap')
import Data.Functor((<&>))
import Data.Map.Strict(Map)
import Data.Map.Strict qualified as M
import Data.Maybe(fromMaybe)
import Data.Set(Set)
import Data.Set qualified as S
import Data.Text(Text)
import Data.Text qualified as T

import Language.OpLang.Syntax

calledOps :: [Op] -> Set Id
calledOps = foldMap' \case
  Call' op -> S.singleton op
  Loop' ops -> calledOps ops
  _ -> S.empty

undefinedCalls :: Program Op -> [Text]
undefinedCalls Program{..} =
  toMsgs =<< filter (not . S.null . snd) (undefinedInTopLevel : undefinedInDefs)
  where
    defined = M.keysSet opDefs

    undefinedInTopLevel = (Nothing, calledOps topLevel S.\\ defined)
    undefinedInDefs = bimap Just ((S.\\ defined) . calledOps) <$> M.toList opDefs

    fmt = maybe "top level" $ ("definition of " <>) . T.pack . show
    toMsgs (name, ops) = S.toList ops <&> \op ->
      "Error: In " <> fmt name <> ": Call to undefined operator " <> T.pack (show op) <> "."

allUsedOps :: Map Id [Op] -> Set Id -> [Op] -> Set Id
allUsedOps defs seen ops
  | S.null used = seen
  | otherwise = foldMap' ((allUsedOps defs (seen <> used)) . fromMaybe [] . (defs M.!?)) used
  where
    used = calledOps ops S.\\ seen

removeUnusedOps :: Program Op -> ([Text], Program Op)
removeUnusedOps p@Program{..} = (warnings, p{opDefs = usedDefs})
  where
    usedOps = allUsedOps opDefs S.empty topLevel
    unusedDefs = M.withoutKeys opDefs usedOps
    usedDefs = M.restrictKeys opDefs usedOps

    warnings = M.keys unusedDefs <&> \op ->
      "Warning: Unused operator " <> T.pack (show op) <> "."

validate :: MonadChronicle [Text] m => Bool -> Program Op -> m (Program Op)
validate noWarn p = do
  let (warnings, p') = removeUnusedOps p
  unless noWarn $ dictate warnings

  case undefinedCalls p of
    [] -> pure p'
    errs -> confess errs