packages feed

nanopass-0.0.3.0: src/Nanopass/Internal/Extend.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedRecordDot #-}

module Nanopass.Internal.Extend
  ( extendLang
  , partitionNontermsEdits
  , EditingNonterms
  , addNonterms
  , modNonterms
  , delNonterms
  , extendProductions
  , partitionProductionsEdits
  ) where

import Prelude hiding (mod)
import Nanopass.Internal.Representation

import Control.Monad (forM_,when)
import Data.Functor ((<&>))
import Data.List (nub, (\\))
import Data.Map (Map)
import Data.Set (Set)
import Nanopass.Internal.Error (Error(..))
import Nanopass.Internal.Validate (validateParams,validateNonterm,validateProd)

import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Language.Haskell.TH as TH

extendLang :: Language 'Valid UpDotName -> LangMod -> Either Error (Language 'Valid UpName)
extendLang orig mod = do
  let (additions, mods, deletions) = partitionNontermsEdits mod.nontermsEdit
  forM_ mods $ \(n, _) -> case (n `elem` ((.nontermName.name) <$> additions), n `elem` deletions) of
    (_, True) -> Left $ IllegalNontermModificationAlsoDeleted n
    (True, _) -> Left $ IllegalNontermModificationAlsoAdded n
    _ -> pure ()
  forM_ additions $ \add -> case add.nontermName.name `elem` deletions of
    True -> Left $ IllegalNontermAddedAlsoDeleted add.nontermName.name
    _ -> pure ()
  do
    let modNames = fst <$> mods
        duplicates = modNames \\ nub modNames
    when (not $ null duplicates) $
      Left $ DuplicateNontermMods modNames
  let modifications = Map.fromList mods
  params <- validateParams mod.newParams
  let tvs = Map.fromList $ params <&> \n -> (n.name, n)
      nts = (Set.fromList (Map.keys orig.langInfo.nonterms)
              `Set.difference` Set.fromList deletions)
            `Set.union` Set.fromList ((.nontermName.name) <$> additions)
  newNonterms <-  pure (orig.langInfo.nonterms)
              >>= delNonterms deletions
              >>= modNonterms nts tvs modifications
              >>= addNonterms nts tvs additions
  pure Language
    { langName = ValidName mod.newLang (TH.mkName $ fromUpName mod.newLang)
    , langInfo = LanguageInfo
      { langParams = params
      , nonterms = newNonterms
      , originalProgram = mod.originalModProgram
      , baseDefdLang = Just orig
      }
    }

----------------------------------
------ Nonterminals Editing ------
----------------------------------

partitionNontermsEdits :: [NontermsEdit] -> ([Nonterm 'Unvalidated], [(UpName, [ProductionsEdit])], [UpName])
partitionNontermsEdits = loop ([], [], [])
  where
  loop (as, ms, ds) [] = (reverse as, reverse ms, reverse ds)
  loop (as, ms, ds) (x:xs) = case x of
    AddNonterm a -> loop (a:as, ms, ds) xs
    ModNonterm n m -> loop (as, (n, m):ms, ds) xs
    DelNonterm d -> loop (as, ms, d:ds) xs

type EditingNonterms = Map UpName (Nonterm 'Valid)

addNonterms :: Set UpName -- ^ known non-terminals for the new language
            -> Map LowName (Name 'Valid LowName) -- ^ known type variables for the new language
            -> [Nonterm 'Unvalidated] -- ^ new non-terminals to add
            -> EditingNonterms -- ^ old language's non-terminals
            -> Either Error EditingNonterms
addNonterms nts tvs adds orig = loop orig adds
  where
  loop !new [] = pure new
  loop !new (add:rest) = do
    case Map.lookup add.nontermName.name new of
      Just _ -> Left $ IllegalNontermAdded add.nontermName.name
      Nothing -> pure ()
    okAdd <- validateNonterm nts tvs add
    loop (Map.insert okAdd.nontermName.name okAdd new) rest

modNonterms :: Set UpName -- ^ known non-terminals for the new language
            -> Map LowName (Name 'Valid LowName) -- ^ known type variables for the new language
            -> Map UpName [ProductionsEdit] -- ^ edits to various non-terminals' productions
            -> EditingNonterms -- ^ old language's non-terminals
            -> Either Error EditingNonterms
modNonterms nts tvs mods orig = do
  forM_ (Map.keys mods) $ \n -> case Map.lookup n orig of
    Nothing -> Left $ IllegalNontermModified n
    Just _ -> pure ()
  forWithKeyM orig $ \n oldNonterm -> case Map.lookup n mods of
    Just prodsEdit -> do
      let newName = ValidName oldNonterm.nontermName.name (TH.mkName $ fromUpName oldNonterm.nontermName.name)
      newProductions <- extendProductions nts tvs oldNonterm.productions prodsEdit
      pure $ Nonterm
        { nontermName = newName
        , productions = newProductions
        }
    Nothing -> validateNonterm nts tvs oldNonterm

delNonterms :: [UpName] -- ^ names of non-terminals to remove
            -> EditingNonterms -- ^ old language's non-terminals
            -> Either Error EditingNonterms
delNonterms dels orig = loop orig dels
  where
  loop !new [] = pure new
  loop !new (del:rest) = case Map.lookup del new of
    Just _ -> loop (Map.delete del new) rest
    Nothing -> Left $ IllegalNontermDeleted del

--------------------------------
------ Production Editing ------
--------------------------------

extendProductions :: Set UpName -- ^ known non-terminals for the new language
                  -> Map LowName (Name 'Valid LowName) -- ^ known type variables for the new language
                  -> (Map UpName (Production 'Valid))
                  -> [ProductionsEdit]
                  -> Either Error (Map UpName (Production 'Valid))
extendProductions nts tvs orig mods = do
  let (additions, deletions) = partitionProductionsEdits mods
  -- NOTE this forM_ is over-restricive because I don't have a way to modify/outright replace productions
  -- forM_ additions $ \add -> case add.prodName.name `elem` deletions of
  --   True -> Left $ IllegalProductionAddedAlsoDeleted add.prodName.name
  --   _ -> pure ()
  restricted <- delProductions deletions orig
  revalidated <- forWithKeyM restricted $ \_ oldProd ->
    validateProd nts tvs oldProd
  addProductions nts tvs additions revalidated

partitionProductionsEdits :: [ProductionsEdit] -> ([Production 'Unvalidated], [UpName])
partitionProductionsEdits = loop ([], [])
  where
  loop (as, ds) [] = (reverse as, reverse ds)
  loop (as, ds) (x:xs) = case x of
    AddProd a -> loop (a:as, ds) xs
    DelProd d -> loop (as, d:ds) xs

addProductions :: Set UpName -- ^ known non-terminals for the new language
               -> Map LowName (Name 'Valid LowName) -- ^ known type variables for the new language
               -> [Production 'Unvalidated]
               -> (Map UpName (Production 'Valid))
               -> Either Error (Map UpName (Production 'Valid))
addProductions nts tvs adds orig = loop orig adds
  where
  loop !new [] = pure new
  loop !new (add:rest) = case Map.lookup add.prodName.name new of
    Just _ -> Left $ IllegalProductionAdded add.prodName.name
    Nothing -> do
      okAdd <- validateProd nts tvs add
      loop (Map.insert okAdd.prodName.name okAdd new) rest

delProductions :: [UpName]
               -> Map UpName (Production 'Valid)
               -> Either Error (Map UpName (Production 'Valid))
delProductions dels orig = loop orig dels
  where
  loop !new [] = pure new
  loop !new (del:rest) = case Map.lookup del new of
    Just _ -> loop (Map.delete del new) rest
    Nothing -> Left $ IllegalProductionDeleted del

---------------------
------ Helpers ------
---------------------

forWithKeyM :: (Ord k, Applicative f) => Map k a -> (k -> a -> f b) -> f (Map k b)
forWithKeyM m f = sequenceA $ Map.mapWithKey f m