packages feed

LR-demo-0.0.20251105: src/ParseTable.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE TemplateHaskell #-}

-- | LR-parser.

module ParseTable where

import Control.Monad
import Control.Monad.Except
import Control.Monad.State
import Control.Monad.Trans.Maybe

import qualified Data.Foldable as Fold
import qualified Data.List as List
import qualified Data.List.NonEmpty as List1
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set

import Data.Bifunctor (first, second)
import Data.Function (on)
import Data.Maybe (catMaybes, maybeToList, listToMaybe, fromMaybe)
import Data.Either (partitionEithers)
import Data.Semigroup (Semigroup(..))

-- uses microlens-platform
import Lens.Micro
import Lens.Micro.Extras (view)
import Lens.Micro.TH (makeLenses)

import qualified LBNF.Abs as A
import LBNF.Print (Print, printTree)

import SetMaybe (SetMaybe(SetMaybe))
import qualified SetMaybe

import Util
import Saturation
import CFG

-- Shift-reduce parser.

-- | A stack is a sentential form (reversed).

newtype Stack' x t = Stack [Symbol' x t]
  deriving (Eq, Ord, Show)

type Input' t = [t]

-- | The state of a shift-reduce parser consists of a stack and some input.

data SRState' x t = SRState
  { _srStack :: Stack' x t
  , _srInput :: Input' t
  } deriving (Show)
makeLenses ''SRState'

-- | An action of a shift-reduce parser.

data SRAction' x r t
  = Shift                 -- ^ Shift next token onto stack.
  | Reduce (Rule' x r t)  -- ^ Reduce with given rule.
  deriving (Show)

type Action' x r t = Maybe (SRAction' x r t)  -- ^ Nothing means halt.

data Rule' x r t = Rule (NT' x) (Alt' x r t)
  deriving (Eq, Ord, Show)

-- | A trace is a list of pairs of states and actions.

data TraceItem' x r t = TraceItem
  { _trState  :: SRState' x t
  , _trAction :: Action' x r t
  } deriving (Show)

type Trace' x r t = [TraceItem' x r t]

-- | The next action is decided by a control function.

type Control' x r t m = SRState' x t -> MaybeT m (SRAction' x r t)

-- | Run a shift-reduce parser given by control function on some input,
--   Returning a trace of states and actions.

runShiftReduceParser :: (Eq t, Monad m)
  => Control' x r t m
  -> Input' t
  -> m (Trace' x r t)
runShiftReduceParser nextAction input = loop $ SRState (Stack []) input
  where
  loop st@(SRState (Stack stk) ts0) = do
    act <- runMaybeT $ nextAction st
    (TraceItem st act :) <$> do
      case (act, ts0) of
        (Nothing   , _   ) -> halt
        (Just Shift, t:ts) -> loop $ SRState (Stack $ Term t : stk) ts
        (Just (Reduce (Rule x (Alt r alpha))), _)
          | Just stk' <- matchStack stk alpha
                           -> loop $ SRState (Stack $ NonTerm x : stk') ts0
        _ -> error "runShiftReduceParser: reduce failed"

  matchStack stk (Form alpha) = List.stripPrefix (reverse alpha) stk
  halt = return []


-- | A parse table maps pairs of states and symbols to actions.
--
--   Non-terminal 'Nothing' is the end of file.
--   For non-terminals, either a shift or a reduce action is returned.
--   For terminals, a goto action (next state) is returned.
--   If 'Nothing' is returned, the parser halts.

data ParseTable' x r t s = ParseTable
  { _tabSR   :: s -> Maybe t -> Maybe (Either s (Rule' x r t))  -- ^ S/R-action on terminals.
  , _tabGoto :: s -> NT' x   -> Maybe s                         -- ^ Goto-action on reduction result.
  , _tabInit :: s
  }
makeLenses ''ParseTable'

-- | A LR control stack is a non-empty list of states.
--   The bottom element is the initial state.

type LRStack' s = List1.NonEmpty s

-- | The LR control function modifies a control stack.
--   It interprets the parse table.

lr1Control :: ParseTable' x r t s -> Control' x r t (State (LRStack' s))
lr1Control (ParseTable tabSR tabGoto _) (SRState stk input) = do
  -- Get control stack
  ss <- get
  -- Query table on maybe top state and maybe first input token.
  (MaybeT $ return $ tabSR (List1.head ss) (listToMaybe input)) >>= \case
    -- Shift action:
    Left s -> do
      -- Put new state on top of stack
      modify $ List1.cons s
      return Shift
    -- Reduce action:
    Right rule@(Rule x (Alt _ (Form alpha))) -> do
      -- Pop |alpha| many states
      let n = length alpha
      let (ss1, rest) = List1.splitAt n ss
      -- Rest should be non-empty, otherwise internal error.
      let err = error $ "lr1Control: control stack too short to reduce"
      let ss2 = fromMaybe err $ List1.nonEmpty rest
      -- Execute the goto action (if present)
      s <- MaybeT $ return $ tabGoto (List1.head ss2) x
      put (List1.cons s ss2)
      return $ Reduce rule

-- | Run the LR(1) parser with the given parsetable.

runLR1Parser :: (Eq t) => ParseTable' x r t s -> Input' t -> Trace' x r t
runLR1Parser pt@(ParseTable _ _ s0) input =
  runShiftReduceParser control input `evalState` st
  where
  control = lr1Control pt
  st = s0 List1.:| []  -- List1.singleton only available from base-4.15 (GHC 9.0)

-- * LR(1) parsetable generation.

-- | A parse item is a dotted rule X → α.β.

data ParseItem' x r t = ParseItem
  { _piRule   :: Rule' x r t    -- ^ The rule this item comes from.
  , _piRest   :: [Symbol' x t]  -- ^ The rest after the ".".
  }
  deriving (Eq, Ord, Show)
makeLenses ''ParseItem'

type Lookahead t = SetMaybe t  -- ^ The set of lookahead symbols.

-- | A parse state is a map of parse items to lookahead lists.

newtype ParseState' x r t = ParseState { theParseState :: Map (ParseItem' x r t) (Lookahead t) }
  deriving (Eq, Ord, Show)

instance (Ord r, Ord t) => Semigroup (ParseState' x r t) where
  ParseState is <> ParseState is' = ParseState $ Map.unionWith SetMaybe.union is is'

instance (Ord r, Ord t) => Monoid (ParseState' x r t) where
  mempty = ParseState $ Map.empty
  mappend = (<>)

-- | Completing a parse state.
--
--   For each (X → α.Yβ, ts), add (Y → .γ, FIRST(β)∘ts)).
--   This might add a whole new item or just extend the token list.

complete :: (Ord r, Ord t)
  => EGrammar' x r t
  -> ParseState' x r t
  -> ParseState' x r t
complete = saturate . completeStep

completeStep :: forall x r t. (Ord r, Ord t)
  => EGrammar' x r t
  -> ParseState' x r t
  -> Change (ParseState' x r t)
completeStep (EGrammar (Grammar _ _ ntDefs) _ fs) (ParseState is) =
    mapM_ add
      [ (ParseItem (Rule y alt) gamma, la')
      | (ParseItem _ (NonTerm y : beta), la) <- Map.toList is
      , NTDef _ alts                         <- maybeToList $ IntMap.lookup (ntNum y) ntDefs
      , alt@(Alt _ (Form gamma))             <- alts
      , let la' = getFirst $ concatFirst (firstSet fs $ Form beta) $ First la
      ]
      `execStateT` ParseState is
  where
    -- Add a parse item candidate.
    add :: (ParseItem' x r t, Lookahead t) -> StateT (ParseState' x r t) Change ()
    add (k, new) = do
      ParseState st <- get
      let (mv, st') = Map.insertLookupWithKey (\ _ -> SetMaybe.union) k new st
      put $ ParseState st'
      -- Detect change:
      case mv of
        -- Item is new?
        Nothing -> lift dirty
        -- Item is old, maybe lookahead is new?
        Just old -> unless (SetMaybe.isSubsetOf new old) $ lift dirty


-- | Goto action for a parse state.

successors :: (Ord r, Ord t) => EGrammar' x r t -> ParseState' x r t -> Map (Symbol' x t) (ParseState' x r t)
successors grm (ParseState is) = complete grm <$> Map.fromListWith (<>)
  [ (sy, ParseState $ Map.singleton (ParseItem r alpha) la)
  | (ParseItem r (sy : alpha), la) <- Map.toList is
  ]

-- * ParseState dictionary

type PState = Int

initPState = 0

-- | LALR: LR0 automaton decorated with lookahead.
--   The @LR0State@ is the @keysSet@ of a @ParseState@.

type LR0State' x r t = Set (ParseItem' x r t)

lr0state :: ParseState' x r t -> LR0State' x r t
lr0state (ParseState is) = Map.keysSet is

-- | The dictionary maps LR0 states to state numbers and their best decoration.
type PSDict' x r t = Map (LR0State' x r t) (PState, ParseState' x r t)

-- | Internal parse table.

data IPT' x r t = IPT
  { _iptSR   :: IntMap (ISRActions' x r t)  -- ^ Map from states to shift-reduce actions.
  , _iptGoto :: IntMap IGotoActions         -- ^ Map from states to goto actions.
  }
  deriving (Show)

-- | Goto actions of a state.
--   Mapping non-terminals to successor states.

type IGotoActions = IntMap PState

-- | Shift-reduce actions of a state.

data ISRActions' x r t = ISRActions
  { _iactEof  :: ISRAction' x r t
  , _iactTerm :: Map t (ISRAction' x r t)
  }
  deriving (Eq, Ord, Show)

instance (Ord r, Ord t) => Semigroup (ISRActions' x r t) where
  ISRActions aeof atok <> ISRActions aeof' atok' =
    ISRActions (aeof <> aeof') (Map.unionWith (<>) atok atok')

instance (Ord r, Ord t) => Monoid (ISRActions' x r t) where
  mempty = ISRActions mempty Map.empty
  mappend = (<>)

shiftActions :: (Ord r, Ord t) => Map t (ISRAction' x r t) -> ISRActions' x r t
shiftActions = ISRActions mempty

-- | Entry of a parse table cell: shift and/or reduce action(s).

data ISRAction' x r t = ISRAction
  { _iactShift  :: Maybe PState     -- ^ Possibly a shift action.
  , _iactReduce :: Set (Rule' x r t)  -- ^ Possibly several reduce actions.
  }
  deriving (Eq, Ord, Show)

instance (Ord r, Ord t) => Semigroup (ISRAction' x r t) where
  -- ISRAction Just{} _ <> ISRAction Just{} _ = error $ "impossible: union of shift actions"
  ISRAction ms1   r1 <> ISRAction ms2   r2 = ISRAction ms r
    where
    ms = listToMaybe $ maybeToList ms1 ++ maybeToList ms2
    r  = Set.union r1 r2

instance (Ord r, Ord t) => Monoid (ISRAction' x r t) where
  mempty = emptyAction
  mappend = (<>)

emptyAction :: ISRAction' x r t
emptyAction = ISRAction Nothing Set.empty

shiftAction :: PState -> ISRAction' x r t
shiftAction s = ISRAction (Just s) Set.empty

reduceAction :: Rule' x r t -> ISRAction' x r t
reduceAction rule = ISRAction Nothing $ Set.singleton rule

-- | Compute the reduce actions for a parse state.

reductions :: (Ord r, Ord t) => ParseState' x r t -> ISRActions' x r t
reductions (ParseState is) = mconcat
    [ ISRActions (if eof then ra else emptyAction) (Map.fromSet (const ra) ts)
    | (ParseItem r [], SetMaybe ts eof) <- Map.toList is
    , let ra = reduceAction r
    ]

-- | Parse table generator state

data PTGenState' x r t = PTGenState
  { _stNext   :: Int              -- ^ Next unused state number.
  , _stPSDict :: PSDict' x r t    -- ^ Translation from states to state numbers.
  , _stIPT    :: IPT' x r t       -- ^ Internal parse table.
  }
makeLenses ''ISRAction'
makeLenses ''ISRActions'
makeLenses ''IPT'
makeLenses ''PTGenState'

ptState0 :: (Ord r, Ord t) => EGrammar' x r t -> ParseState' x r t
ptState0 grm@(EGrammar (Grammar _ _ ntDefs) start _fs) =
  -- complete grm $
    ParseState $ Map.fromList items0
  where
    laEOF  = SetMaybe.singleton Nothing
    alts0  = maybe [] (view ntDef) $ IntMap.lookup (ntNum start) ntDefs
    items0 = flip map alts0 $ \ alt@(Alt r (Form alpha)) ->
      (ParseItem (Rule start alt) alpha, laEOF)

ptGen :: forall x r t. (Ord r, Ord t) => EGrammar' x r t -> IPT' x r t
ptGen grm@(EGrammar (Grammar _ _ ntDefs) start fs) =
  view stIPT $ loop [state0] `execState` stInit
  where
  stInit :: PTGenState' x r t
  stInit = PTGenState 1 (Map.singleton (lr0state state0) (0, state0)) $
             IPT IntMap.empty IntMap.empty
             -- IPT (IntMap.singleton 0 $ reductions state0)
             --     (IntMap.singleton 0 $ IntMap.empty)  -- initially no goto actions

  -- The first state contains the productions for the start non-terminal.
  state0 :: ParseState' x r t
  state0 = complete grm $ ParseState $ Map.fromList items0
    where
    laEOF  = SetMaybe.singleton Nothing
    alts0  = maybe [] (view ntDef) $ IntMap.lookup (ntNum start) ntDefs
    items0 = flip map alts0 $ \ alt@(Alt r (Form alpha)) ->
      (ParseItem (Rule start alt) alpha, laEOF)

  -- Work off worklist of registered by not processed parse states.
  loop :: [ParseState' x r t] -> State (PTGenState' x r t) ()
  loop [] = return ()
  loop (is : worklist) = do
    let k = lr0state is  -- the LR0State of is
    (Map.lookup k <$> use stPSDict) >>= \case
      Nothing -> error "impossible: parse state without number"
      Just (snew, is0)  -> do
        -- Lookaheads are already updated by convert.
        -- -- Update the lookaheads
        -- is <- do
        --   let is2 = is <> is0
        --   if is2 == is0 then return is0 else do
        --     modifying stPSDict $ Map.insert k (snew, is2)
        --     return is2
        -- Compute successors of snew.
        let sucs = Map.toList $ successors grm is
        -- Register the successors (if not known yet).
        (news, sucs') <- List.unzip <$> mapM convert sucs
        -- Compute goto actions for state snew.
        let fromSymbol (Term    t, a) = Left  (t, a)
            fromSymbol (NonTerm x, a) = Right (ntNum x, a)
        let (shifts0, gotos0) = partitionEithers $ map fromSymbol sucs'
        -- Equip the state snew with its goto actions.
        unless (null gotos0) $ do
          let gotos   = IntMap.fromList gotos0
          modifying (stIPT . iptGoto) $ IntMap.insertWith IntMap.union snew gotos
        -- Compute shift and reduce actions of snew.
        let shifts  = Map.fromList $ map (\ (t,s) -> (t, shiftAction s)) shifts0
        let reduces = reductions is
        let actions = (shiftActions shifts <> reduces)
        unless (actions == mempty) $ do
        -- Equip the state snew with its shift/reduce actions.
          modifying (stIPT . iptSR) $ IntMap.insertWith (<>) snew actions
        -- Add the new states to the worklist and continue
        loop $ catMaybes news ++ worklist

  -- Register a parse state and decide whether we have to process it.
  convert :: (a, ParseState' x r t) -> State (PTGenState' x r t) (Maybe (ParseState' x r t), (a, PState))
  convert (a, is) = do
    let k = lr0state is
    snew <- use stNext
    (Map.lookup k <$> use stPSDict) >>= \case
      -- Parse state has already been visited.  However, lookahead info might need update.
      Just (s, is0) -> do
        -- Combine old an new lookahead info.
        let is' = is <> is0
        if is' == is0 then return (Nothing, (a, s)) else do
          -- If something changed, update the lookahead info.
          -- Also, we will need to process this state again.
          modifying stPSDict $ Map.insert k (s, is')
          return (Just is', (a, s))
      -- New parse state.
      Nothing -> do
        -- Increase parse state counter.
        modifying stNext succ
        -- Save updated dictionary.
        modifying stPSDict $ Map.insert k (snew, is) -- (const dict')
        return (Just is, (a, snew))

-- | Shift over reduce.
--   First reduce action out of several ones.

chooseAction :: ISRAction' x r t -> Maybe (Either PState (Rule' x r t))
chooseAction (ISRAction (Just s) rs) = Just (Left s)
chooseAction (ISRAction Nothing  rs) = Right <$> Set.lookupMin rs

-- | Construct the extensional parse table.
constructParseTable' :: forall x r t. (Ord r, Ord t) => IPT' x r t -> ParseTable' x r t PState
constructParseTable' (IPT sr goto) = ParseTable tabSR tabGoto tabInit
  where
  tabSR s Nothing  = chooseAction =<< do view iactEof <$> IntMap.lookup s sr
  tabSR s (Just t) = chooseAction =<< Map.lookup t =<< do view iactTerm <$> IntMap.lookup s sr
  tabGoto s x = IntMap.lookup (ntNum x) =<< IntMap.lookup s goto
  tabInit = 0

-- | Construct the extensional parse table.
constructParseTable :: forall x r t. (Ord r, Ord t) => EGrammar' x r t -> ParseTable' x r t PState
constructParseTable = constructParseTable' . ptGen

-- | Add rule @%start -> S@ for new start symbol.
addNewStart :: forall x r t. x -> r -> EGrammar' x r t -> EGrammar' x r t
addNewStart x r (EGrammar grm start fs) = EGrammar (add grm) newstart fs
  where
  add = over grmNTDefs $ IntMap.insert (ntNum newstart) $
          NTDef x $ [Alt r $ Form [NonTerm start]]
  newstart :: NT' x
  newstart = NT (0-1) x