packages feed

datafix-0.0.1.0: examples/SetRecurrences/FirstFollow.hs

{-# LANGUAGE AllowAmbiguousTypes   #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TypeApplications      #-}
{-# OPTIONS_GHC -fexpose-all-unfoldings #-}

module SetRecurrences.FirstFollow
  ( V (..)
  , WithEOF (..)
  , Grammar (..)
  , mkGrammar
  , augmentGrammar
  , first
  , follow
  , dyck
  , llsll
  , emptyL
  , leftrec
  ) where

import           Prelude hiding (words)

import           Datafix

import           Control.Applicative
import           Control.Monad
import           Data.Bifunctor (second)
import           Data.Functor.Identity
import           Data.List (unfoldr)
import           Data.Map (Map)
import qualified Data.Map as Map
import           Data.Maybe (fromMaybe)
import           Data.Set (Set)
import qualified Data.Set as Set

data V t nt
  = T t
  | NT nt
  deriving (Eq, Ord, Show)

eitherV :: (t -> r) -> (nt -> r) -> V t nt -> r
eitherV l _ (T t)   = l t
eitherV _ r (NT nt) = r nt

data WithEOF a
  = EOF
  | NoEOF a
  deriving (Eq, Ord, Show)

instance {-# OVERLAPPING #-} Show [WithEOF Char] where
  show = show . map f
    where
      f EOF       = '#'
      f (NoEOF c) = c

data Grammar t nt
  = G
  { terminals :: Set t
  , nonterminals :: Set nt
  , productions :: Map nt (Set [V t nt])
  , start :: nt
  }

mkGrammar :: (Ord v) => [v] -> [v] -> [(v, [v])] -> v -> Grammar v v
mkGrammar ts nts ps s
  = G
  { terminals    = Set.fromList ts
  , nonterminals = Set.fromList nts
  , productions  = Map.fromListWith Set.union (map (second (Set.singleton . map mkV)) ps)
  , start        = s
  }
  where
    mkV v
      | v `elem` ts  = T v
      | v `elem` nts = NT v
      | otherwise  = error "not part of vocabulary"

augmentGrammar :: (Ord t, Ord nt) => nt -> Grammar t nt -> Grammar t nt
augmentGrammar s' gr
  = G
  { terminals    = terminals gr
  , nonterminals = Set.insert s' (nonterminals gr)
  , productions  = Map.insert s' (Set.singleton [NT (start gr)]) $ productions gr
  , start        = s'
  }

epsilon :: [t]
epsilon = []

kconcat :: Int -> [a] -> [a] -> [a]
kconcat k pref suf = take k (pref ++ suf)

pointwise2 :: Ord c => (a -> b -> c) -> Set a -> Set b -> Set c
pointwise2 f as bs = Set.fromList (liftA2 f (Set.toList as) (Set.toList bs))

-- |
-- NB: A Monad here is strictly more performant than Applicative here, because
-- we can exit early. On the other this means we have this really adhoc fold over
subst :: (Monad m, Ord t) => Int -> (nt -> m (Set [t])) -> [V t nt] -> m (Set [t])
subst k lkup = go (Set.singleton epsilon)
  where
    go prefs [] = pure prefs
    go prefs (v:vs) = do
      suffs <- eitherV (pure . Set.singleton . (:[])) lkup v
      let words = pointwise2 (kconcat k) prefs suffs
      if any ((< k) . length) words
        then go words vs
        else pure words

first :: forall t nt . (Ord t, Ord nt, Datafixable (Set [t])) => Int -> Grammar t nt -> [V t nt] -> Set [t]
first k gr = runIdentity . subst k (Identity . (firstSolutions k gr Map.!))

firstSolutions :: forall t nt . (Ord t, Ord nt, Datafixable (Set [t])) => Int -> Grammar t nt -> Map nt (Set [t])
firstSolutions k gr = evalDenotation @(Set [t]) @(Map nt (Set [t])) plan NeverAbort
  where
    plan :: forall m . (MonadDatafix m, Domain (DepM m) ~ Set [t]) => m (DepM m (Map nt (Set [t])))
    plan = sequence <$> build Map.empty (Set.toList (nonterminals gr))
    build :: forall m . (MonadDatafix m, Domain (DepM m) ~ Set [t]) => Map nt (DepM m (Set [t])) -> [nt] -> m (Map nt (DepM m (Set [t])))
    build env []       = pure env
    build env (nt:nts) = datafixEq $ \self -> do
      env' <- build (Map.insert nt self env) nts
      let rhss = Set.toList (fromMaybe Set.empty (Map.lookup nt (productions gr)))
      let iter = Set.unions <$> mapM (subst k (env' Map.!)) rhss
      pure (env', iter)

-- | Assumes the given grammar is augmented
follow :: forall t nt . (Ord t, Ord nt, Datafixable (Set [WithEOF t])) => Int -> Grammar t nt -> nt -> Set [WithEOF t]
follow k gr = (evalDenotation @(Set [WithEOF t]) plan NeverAbort Map.!)
  where
    prods :: [(nt, [V t nt])]
    prods =
      [ (nt, rhs)
      | (nt, rhss) <- Map.toList (productions gr)
      , rhs <- Set.toList rhss
      ]
    firsts = firstSolutions k gr
    initialEnv :: forall m . Monad m => Map nt (m (Set [WithEOF t]))
    initialEnv = Map.singleton (start gr) (pure (Set.singleton (replicate k EOF)))
    plan :: forall m . (MonadDatafix m, Domain (DepM m) ~ Set [WithEOF t]) => m (DepM m (Map nt (Set [WithEOF t])))
    plan = sequence <$> build initialEnv (Set.toList (Set.delete (start gr) (nonterminals gr)))
    build :: forall m . (MonadDatafix m, Domain (DepM m) ~ Set [WithEOF t]) => Map nt (DepM m (Set [WithEOF t])) -> [nt] -> m (Map nt (DepM m (Set [WithEOF t])))
    build env []       = pure env
    build env (nt:nts) = datafixEq $ \self -> do
      env' <- build (Map.insert nt self env) nts
      let occs = flip concatMap prods $ \(parent, rhs) -> flip unfoldr rhs $ \rest ->
            case dropWhile (/= NT nt) rest of
              [] -> Nothing
              (_:follows) -> Just ((parent, follows), follows)

      let iter = fmap Set.unions $ forM occs $ \(parent, follows) -> do
            let prefs = Set.map (map NoEOF) $ runIdentity $ subst k (Identity . (firsts Map.!)) follows
            let (short, long) = Set.partition ((< k) . length) prefs
            if null short
              then pure prefs
              else Set.union long . pointwise2 (kconcat k) prefs <$> (env' Map.! parent)

      pure (env', iter)

dyck :: Grammar Char Char
dyck = augmentGrammar 'O' $ mkGrammar
  "()"
  "S"
  [ ('S', "")
  , ('S', "(S)S")
  ]
  'S'

llsll :: Grammar Char Char
llsll = augmentGrammar 'O' $ mkGrammar
  "ab"
  "ZA"
  [ ('Z', "aAab")
  , ('Z', "bAb")
  , ('A', "a")
  , ('A', "")
  ]
  'Z'

emptyL :: Grammar Char Char
emptyL = augmentGrammar 'O' $ mkGrammar
  ""
  "ABC"
  [ ('A', "BC")
  , ('B', "CA")
  , ('C', "A")
  ]
  'A'

leftrec :: Grammar Char Char
leftrec = augmentGrammar 'O' $ mkGrammar
  "abc"
  "SA"
  [ ('S', "Aa")
  , ('A', "Sb")
  , ('A', "c")
  ]
  'S'