packages feed

symantic-parser-0.1.0.20210201: src/Symantic/Parser/Machine/Generate.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE StandaloneDeriving #-} -- For Show (ParsingError inp)
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UnboxedTuples #-} -- For nextInput
{-# LANGUAGE UndecidableInstances #-} -- For Show (ParsingError inp)
module Symantic.Parser.Machine.Generate where

import Control.Monad (Monad(..))
import Data.Bool (Bool)
import Data.Char (Char)
import Data.Either (Either(..))
import Data.Function (($), (.))
import Data.Functor ((<$>))
import Data.Int (Int)
import Data.List (minimum)
import Data.Map (Map)
import Data.Maybe (Maybe(..))
import Data.Ord (Ord(..), Ordering(..))
import Data.Semigroup (Semigroup(..))
import Data.Set (Set)
import Language.Haskell.TH (CodeQ, Code(..))
import Prelude ((+), (-))
import Text.Show (Show(..))
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Language.Haskell.TH.Syntax as TH
-- import qualified Control.Monad.Trans.Writer as Writer

import Symantic.Univariant.Trans
import Symantic.Parser.Grammar.Combinators (ErrorItem(..))
import Symantic.Parser.Machine.Input
import Symantic.Parser.Machine.Instructions
import qualified Symantic.Parser.Haskell as H

genCode :: TermInstr a -> CodeQ a
genCode = trans

-- * Type 'Gen'
-- | Generate the 'CodeQ' parsing the input.
data Gen inp vs es a = Gen
  { minHorizon :: Map TH.Name Horizon -> Horizon
    -- ^ Synthetized (bottom-up) minimal input length
    -- required by the parser to not fail.
    -- This requires a 'minHorizonByName'
    -- containing the minimal 'Horizon's of all the 'TH.Name's
    -- this parser 'call's, 'jump's or 'refJoin's to.
  , unGen ::
      GenCtx inp vs es a ->
      CodeQ (Either (ParsingError inp) a)
  }

-- ** Type 'ParsingError'
data ParsingError inp
  =  ParsingErrorStandard
  {  parsingErrorOffset :: Offset
    -- | Note that if an 'ErrorItemHorizon' greater than 1
    -- is amongst the 'parsingErrorExpecting'
    -- then this is only the 'InputToken'
    -- at the begining of the expected 'Horizon'.
  ,  parsingErrorUnexpected :: Maybe (InputToken inp)
  ,  parsingErrorExpecting :: Set (ErrorItem (InputToken inp))
  }
deriving instance Show (InputToken inp) => Show (ParsingError inp)

-- ** Type 'Offset'
type Offset = Int

-- ** Type 'Horizon'
-- | Synthetized minimal input length
-- required for a successful parsing.
-- Used with 'checkedHorizon' to factorize input length checks,
-- instead of checking the input length
-- one 'InputToken' at a time at each 'read'.
type Horizon = Offset

-- ** Type 'Cont'
type Cont inp v a =
  {-farthestInput-}Cursor inp ->
  {-farthestExpecting-}[ErrorItem (InputToken inp)] ->
  v ->
  Cursor inp ->
  Either (ParsingError inp) a

-- ** Type 'FailHandler'
type FailHandler inp a =
  {-failureInput-}Cursor inp ->
  {-farthestInput-}Cursor inp ->
  {-farthestExpecting-}[ErrorItem (InputToken inp)] ->
  Either (ParsingError inp) a

{-
-- *** Type 'FarthestError'
data FarthestError inp = FarthestError
  { farthestInput :: Cursor inp
  , farthestExpecting :: [ErrorItem (InputToken inp)]
  }
-}

-- | @('generateCode' input mach)@ generates @TemplateHaskell@ code
-- parsing the given 'input' according to the given 'Machine'.
generateCode ::
  forall inp ret.
  Ord (InputToken inp) =>
  Show (InputToken inp) =>
  TH.Lift (InputToken inp) =>
  -- InputToken inp ~ Char =>
  Input inp =>
  CodeQ inp ->
  Show (Cursor inp) =>
  Gen inp '[] ('Succ 'Zero) ret ->
  CodeQ (Either (ParsingError inp) ret)
generateCode input k = [||
  -- Pattern bindings containing unlifted types
  -- should use an outermost bang pattern.
  let !(# init, readMore, readNext #) = $$(cursorOf input) in
  let finalRet = \_farInp _farExp v _inp -> Right v in
  let finalFail _failInp !farInp !farExp =
        Left ParsingErrorStandard
        { parsingErrorOffset = offset farInp
        , parsingErrorUnexpected =
            if readMore farInp
            then Just (let (# c, _ #) = readNext farInp in c)
            else Nothing
        , parsingErrorExpecting = Set.fromList farExp
        } in
  $$(unGen k GenCtx
    { valueStack = ValueStackEmpty
    , failStack = FailStackCons [||finalFail||] FailStackEmpty
    , retCode = [||finalRet||]
    , input = [||init||]
    , nextInput = [||readNext||]
    , moreInput = [||readMore||]
    -- , farthestError = [||Nothing||]
    , farthestInput = [||init||]
    , farthestExpecting = [|| [] ||]
    , checkedHorizon = 0
    , minHorizonByName = Map.empty
    })
  ||]

-- ** Type 'GenCtx'
-- | This is an inherited (top-down) context
-- only present at compile-time, to build TemplateHaskell splices.
data GenCtx inp vs (es::Peano) a =
  ( TH.Lift (InputToken inp)
  , Cursorable (Cursor inp)
  , Show (InputToken inp)
  -- , InputToken inp ~ Char
  ) => GenCtx
  { valueStack :: ValueStack vs
  , failStack :: FailStack inp a es
  --, failStacks :: FailStack inp es a
  , retCode :: CodeQ (Cont inp a a)
  , input :: CodeQ (Cursor inp)
  , moreInput :: CodeQ (Cursor inp -> Bool)
  , nextInput :: CodeQ (Cursor inp -> (# InputToken inp, Cursor inp #))
  , farthestInput :: CodeQ (Cursor inp)
  , farthestExpecting :: CodeQ [ErrorItem (InputToken inp)]
    -- | Remaining horizon already checked.
    -- Updated by 'checkHorizon'
    -- and reset elsewhere when needed.
  , checkedHorizon :: Offset
    -- | Minimal horizon for each 'subroutine' or 'defJoin'.
    -- This can be done as an inherited attribute because
    -- 'OverserveSharing' introduces 'def' as an ancestor node
    -- of all the 'ref's pointing to it.
    -- Same for 'defJoin' and its 'refJoin's.
  , minHorizonByName :: Map TH.Name Offset
  }

-- ** Type 'ValueStack'
data ValueStack vs where
  ValueStackEmpty :: ValueStack '[]
  ValueStackCons ::
    { valueStackHead :: TermInstr v
    , valueStackTail :: ValueStack vs
    } -> ValueStack (v ': vs)

-- ** Type 'FailStack'
data FailStack inp a es where
  FailStackEmpty :: FailStack inp a 'Zero
  FailStackCons ::
    { failStackHead :: CodeQ (FailHandler inp a)
    , failStackTail :: FailStack inp a es
    } ->
    FailStack inp a ('Succ es)

instance Stackable Gen where
  push x k = k
    { unGen = \ctx -> unGen k ctx
      { valueStack = ValueStackCons x (valueStack ctx) }
    }
  pop k = k
    { unGen = \ctx -> unGen k ctx
      { valueStack = valueStackTail (valueStack ctx) }
    }
  liftI2 f k = k
    { unGen = \ctx -> unGen k ctx
      { valueStack =
        let ValueStackCons y (ValueStackCons x xs) = valueStack ctx in
        ValueStackCons (f H.:@ x H.:@ y) xs
      }
    }
  swap k = k
    { unGen = \ctx -> unGen k ctx
      { valueStack =
          let ValueStackCons y (ValueStackCons x xs) = valueStack ctx in
          ValueStackCons x (ValueStackCons y xs)
      }
    }
instance Branchable Gen where
  caseI kx ky = Gen
    { minHorizon = \ls ->
      minHorizon kx ls `min` minHorizon ky ls
    , unGen = \ctx ->
      let ValueStackCons v vs = valueStack ctx in
      [||
        case $$(genCode v) of
          Left  x -> $$(unGen kx ctx{ valueStack = ValueStackCons (H.Term [||x||]) vs })
          Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (H.Term [||y||]) vs })
      ||]
    }
  choices fs ks kd = Gen
    { minHorizon = \hs -> minimum $
        minHorizon kd hs :
        (($ hs) . minHorizon <$> ks)
    , unGen = \ctx ->
      let ValueStackCons v vs = valueStack ctx in
      go ctx{valueStack = vs} v fs ks
    }
    where
    go ctx x (f:fs') (k:ks') = [||
      if $$(genCode (f H.:@ x))
      then $$(unGen k ctx)
      else $$(go ctx x fs' ks')
      ||]
    go ctx _ _ _ = unGen kd ctx
instance Failable Gen where
  fail failExp = Gen
    { minHorizon = \_hs -> 0
    , unGen = \ctx@GenCtx{} -> [||
      let (# farInp, farExp #) =
            case $$compareOffset $$(farthestInput ctx) $$(input ctx) of
              LT -> (# $$(input ctx), failExp #)
              EQ -> (# $$(farthestInput ctx), ($$(farthestExpecting ctx) <> failExp) #)
              GT -> (# $$(farthestInput ctx), $$(farthestExpecting ctx) #) in
      $$(failStackHead (failStack ctx))
        $$(input ctx) farInp farExp
      ||]
    }
  popFail k = k
    { unGen = \ctx ->
      unGen k ctx{failStack = failStackTail (failStack ctx)}
    }
  catchFail ok ko = Gen
    { minHorizon = \ls -> minHorizon ok ls `min` minHorizon ko ls
    , unGen = \ctx@GenCtx{} -> unGen ok ctx
        { failStack = FailStackCons [|| \ !failInp !farInp !farExp ->
            -- trace ("catchFail: " <> "farExp="<>show farExp) $
            $$(unGen ko ctx
              -- Push the input as it was when entering the catchFail.
              { valueStack = ValueStackCons (H.Term (input ctx)) (valueStack ctx)
              -- Move the input to the failing position.
              , input = [||failInp||]
              -- Set the farthestInput to the farthest computed by 'fail'
              , farthestInput = [||farInp||]
              , farthestExpecting = [||farExp||]
              })
          ||] (failStack ctx)
        }
    }
instance Inputable Gen where
  loadInput k = k
    { unGen = \ctx ->
      let ValueStackCons input vs = valueStack ctx in
      unGen k ctx
        { valueStack = vs
        , input = genCode input
        , checkedHorizon = 0
        }
    }
  pushInput k = k
    { unGen = \ctx ->
      unGen k ctx{valueStack = ValueStackCons (H.Term (input ctx)) (valueStack ctx)}
    }
instance Routinable Gen where
  subroutine (LetName n) sub k = Gen
    { minHorizon = minHorizon k
    , unGen = \ctx -> Code $ TH.unsafeTExpCoerce $ do
      -- 'sub' is recursively 'call'able within 'sub',
      -- but its maximal 'minHorizon' is not known yet.
      let minHorizonByNameButSub = Map.insert n 0 (minHorizonByName ctx)
      body <- TH.unTypeQ $ TH.examineCode $ [|| -- buildRec in Parsley
        -- subroutine called by 'call' or 'jump'
        \ !ok{-from generateSuspend or retCode-}
          !inp
          !ko{-from failStackHead-} ->
          $$(unGen sub ctx
            { valueStack = ValueStackEmpty
            , failStack = FailStackCons [||ko||] FailStackEmpty
            , input = [||inp||]
            , retCode = [||ok||]

            -- These are passed by the caller via 'ok' or 'ko'
            -- , farthestInput = 
            -- , farthestExpecting = 

            -- Some callers can call this subroutine
            -- with zero checkedHorizon, hence use this minimum.
            -- TODO: maybe it could be improved a bit
            -- by taking the minimum of the checked horizons
            -- before all the 'call's and 'jump's to this subroutine.
            , checkedHorizon = 0
            , minHorizonByName = minHorizonByNameButSub
            })
        ||]
      let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
      expr <- TH.unTypeQ (TH.examineCode (unGen k ctx
        { minHorizonByName =
            -- 'sub' is 'call'able within 'k'.
            Map.insert n
              (minHorizon sub minHorizonByNameButSub)
              (minHorizonByName ctx)
        }))
      return (TH.LetE [decl] expr)
    }
  jump (LetName n) = Gen
    { minHorizon = (Map.! n)
    , unGen = \ctx -> [||
      let _ = "jump" in
      $$(Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
        {-ok-}$$(retCode ctx)
        $$(input ctx)
        $$(failStackHead (failStack ctx))
      ||]
    }
  call (LetName n) k = k
    { minHorizon = (Map.! n)
    , unGen = \ctx -> [||
      let _ = "call" in
      $$(Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
        {-ok-}$$(generateSuspend k ctx)
        $$(input ctx)
        $$(failStackHead (failStack ctx))
      ||]
    }
  ret = Gen
    { minHorizon = \_hs -> 0
    , unGen = \ctx -> unGen (generateResume (retCode ctx)) ctx
    }

-- | Generate a continuation to be called with 'generateResume',
-- used when 'call' 'ret'urns.
-- The return 'v'alue is 'push'ed on the 'valueStack'.
generateSuspend ::
  {-k-}Gen inp (v ': vs) es a ->
  GenCtx inp vs es a ->
  CodeQ (Cont inp v a)
generateSuspend k ctx = [||
  let _ = "suspend" in
  \farInp farExp v !inp ->
    $$(unGen k ctx
      { valueStack = ValueStackCons (H.Term [||v||]) (valueStack ctx)
      , input = [||inp||]
      , farthestInput = [||farInp||]
      , farthestExpecting = [||farExp||]
      , checkedHorizon = 0
      }
    )
  ||]

-- | Generate a call to the 'generateSuspend' continuation.
-- Used when 'call' 'ret'urns.
generateResume ::
  CodeQ (Cont inp v a) ->
  Gen inp (v ': vs) es a
generateResume k = Gen
  { minHorizon = \_hs -> 0
  , unGen = \ctx -> [||
    let _ = "resume" in
    $$k
      $$(farthestInput ctx)
      $$(farthestExpecting ctx)
      (let _ = "resume.genCode" in $$(genCode (valueStackHead (valueStack ctx))))
      $$(input ctx)
    ||]
  }

instance Joinable Gen where
  defJoin (LetName n) joined k = k
    { minHorizon = minHorizon k
    , unGen = \ctx -> Code $ TH.unsafeTExpCoerce $ do
      body <- TH.unTypeQ $ TH.examineCode $ [||
        \farInp farExp v !inp ->
          $$(unGen joined ctx
            { valueStack = ValueStackCons (H.Term [||v||]) (valueStack ctx)
            , input = [||inp||]
            , farthestInput = [||farInp||]
            , farthestExpecting = [||farExp||]
            , checkedHorizon = 0
            })
        ||]
      let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
      expr <- TH.unTypeQ (TH.examineCode (unGen k ctx
        { minHorizonByName =
            -- 'joined' is 'refJoin'able within 'k'.
            Map.insert n
              -- By definition (in 'joinNext')
              -- 'joined' is not recursively 'refJoin'able within 'joined',
              -- hence no need to prevent against recursivity
              -- as has to be done in 'subroutine'.
              (minHorizon joined (minHorizonByName ctx))
              (minHorizonByName ctx)
        }))
      return (TH.LetE [decl] expr)
    }
  refJoin (LetName n) = (generateResume (Code (TH.unsafeTExpCoerce (return (TH.VarE n)))))
    { minHorizon = (Map.! n)
    }
instance Readable Char Gen where
  read farExp p = checkHorizon . checkToken farExp p

checkHorizon ::
  TH.Lift (InputToken inp) =>
  {-ok-}Gen inp vs ('Succ es) a ->
  Gen inp vs ('Succ es) a
checkHorizon ok = ok
  { minHorizon = \hs -> 1 + minHorizon ok hs
  , unGen = \ctx0@GenCtx{failStack = FailStackCons e es} -> [||
      -- Factorize failure code
      let readFail = $$(e) in
      $$(
        let ctx = ctx0{ failStack = FailStackCons [||readFail||] es } in
        if checkedHorizon ctx >= 1
        then unGen ok ctx0{checkedHorizon = checkedHorizon ctx - 1}
        else let minHoriz = minHorizon ok (minHorizonByName ctx) in
          [||
          if $$(moreInput ctx)
               $$(if minHoriz > 0
                 then [||$$shiftRight minHoriz $$(input ctx)||]
                 else input ctx)
          then $$(unGen ok ctx{checkedHorizon = minHoriz})
          else let _ = "checkHorizon.else" in
            $$(unGen (fail [ErrorItemHorizon (minHoriz + 1)]) ctx)
          ||]
      )
    ||]
  }

checkToken ::
  forall inp vs es a.
  Ord (InputToken inp) =>
  TH.Lift (InputToken inp) =>
  [ErrorItem (InputToken inp)] ->
  {-predicate-}TermInstr (InputToken inp -> Bool) ->
  {-ok-}Gen inp (InputToken inp ': vs) ('Succ es) a ->
  Gen inp vs ('Succ es) a
checkToken farExp p ok = ok
  { unGen = \ctx -> [||
    let !(# c, cs #) = $$(nextInput ctx) $$(input ctx) in
    if $$(genCode p) c
    then $$(unGen ok ctx
      { valueStack = ValueStackCons (H.Term [||c||]) (valueStack ctx)
      , input = [||cs||]
      })
    else let _ = "checkToken.else" in $$(unGen (fail farExp) ctx)
    ||]
  }