bump version
[haskell/symantic-parser.git] / src / Symantic / Parser / Machine / Program.hs
index aa7e242c81ca2d4c3c97b4390472718e716b4e8d..816aa6cc454a66073fccb24b1e16170691f4afe6 100644 (file)
@@ -1,3 +1,4 @@
+{-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE UndecidableInstances #-} -- For Cursorable (Cursor inp)
 -- | Build the 'Instr'uction 'Program' of a 'Machine'
 -- from the 'Comb'inators of a 'Grammar'.
@@ -7,21 +8,28 @@
 -- those generated (eg. by using 'joinNext').
 module Symantic.Parser.Machine.Program where
 
-import Data.Bool (Bool(..))
-import Data.Ord (Ord)
-import Data.Function (($), (.))
+import Control.Monad (Monad(..), (<=<), (=<<), liftM, liftM2, sequence)
+import Data.Function (($))
+import System.IO (IO)
 import Type.Reflection (Typeable)
-import System.IO.Unsafe (unsafePerformIO)
+import Control.DeepSeq (NFData)
+import Data.Bool (Bool(..))
+import Data.Eq (Eq(..))
+import Data.Function ((.))
+import Text.Show (Show(..))
 import qualified Data.Functor as Functor
+import qualified Data.HashMap.Strict as HM
+import qualified Data.Set as Set
+import qualified Data.Traversable as Traversable
 import qualified Language.Haskell.TH as TH
 import qualified Language.Haskell.TH.Syntax as TH
-import qualified Symantic.Parser.Haskell as H
+import qualified Symantic.Typed.Lang as Prod
 
 import Symantic.Parser.Grammar
 import Symantic.Parser.Machine.Input
 import Symantic.Parser.Machine.Instructions
 import Symantic.Parser.Machine.Optimize
-import Symantic.Univariant.Trans
+import Symantic.Typed.Trans
 
 -- * Type 'Program'
 -- | A 'Program' is a tree of 'Instr'uctions,
@@ -29,61 +37,101 @@ import Symantic.Univariant.Trans
 -- to be able to introspect, duplicate and/or change
 -- the next 'Instr'uction.
 data Program repr inp a = Program { unProgram ::
-  forall vs es ret.
+  forall vs ret.
   -- This is the next instruction
-  SomeInstr repr inp (a ': vs) ('Succ es) ret ->
+  SomeInstr repr inp (a ': vs) ret ->
   -- This is the current instruction
-  SomeInstr repr inp vs ('Succ es) ret }
+  -- IO is needed for 'TH.newName' in 'joinNext'.
+  IO (SomeInstr repr inp vs ret)
+  }
 
--- | Build an interpreter of the 'Program' of the given 'Machine'.
+-- | Build an interpreter of the 'Program' of the given 'Machinable'.
 optimizeMachine ::
-  forall inp es repr a.
-  Machine (InputToken inp) repr =>
+  forall inp repr a.
+  Machinable (InputToken inp) repr =>
   Program repr inp a ->
-  repr inp '[] ('Succ es) a
-optimizeMachine (Program f) = trans (f @'[] @es ret)
+  IO (repr inp '[] a)
+optimizeMachine (Program f) = trans Functor.<$> f @'[] ret
+
+-- * Class 'Machinable'
+-- | All the 'Instr'uctions.
+type Machinable tok repr =
+  ( InstrBranchable repr
+  , InstrExceptionable repr
+  , InstrInputable repr
+  , InstrJoinable repr
+  , InstrCallable repr
+  , InstrValuable repr
+  , InstrReadable tok repr
+  , Eq tok
+  , TH.Lift tok
+  , NFData tok
+  , Show tok
+  , Typeable tok
+  )
 
 instance
-  Stackable repr =>
-  Applicable (Program repr inp) where
-  pure x = Program (push (trans x))
-  Program f <*> Program x = Program (f . x . appI)
-  liftA2 f (Program x) (Program y) =
-    Program (x . y . liftI2 (trans f))
-  Program x *> Program y = Program (x . pop . y)
-  Program x <* Program y = Program (x . y . pop)
+  ( Cursorable (Cursor inp)
+  , InstrBranchable repr
+  , InstrExceptionable repr
+  , InstrInputable repr
+  , InstrJoinable repr
+  , InstrValuable repr
+  , InstrReadable (InputToken inp) repr
+  , Typeable (InputToken inp)
+  ) =>
+  Trans (Comb CombAlternable (Program repr inp)) (Program repr inp) where
+  trans = \case
+    Alt ExceptionFailure
+      (Comb (SatisfyOrFail _fs p :: Comb (CombSatisfiable (InputToken inp)) (Program repr inp) a))
+      (Comb (Failure sf)) ->
+      Program $ return . read (Set.singleton sf) (prodCode p)
+    Alt exn x y -> alt exn (trans x) (trans y)
+    Empty -> empty
+    Failure sf -> failure sf
+    Throw exn -> throw exn
+    Try x -> try (trans x)
+
 instance
   ( Cursorable (Cursor inp)
-  , Branchable repr
-  , Failable repr
-  , Inputable repr
-  , Joinable repr
-  , Stackable repr
-  ) => Alternable (Program repr inp) where
-  empty = Program $ \_next -> fail []
-  Program l <|> Program r = joinNext $ Program $ \next ->
-    catchFail
-      (l (popFail next))
-      (failIfConsumed (r next))
+  , InstrBranchable repr
+  , InstrExceptionable repr
+  , InstrInputable repr
+  , InstrJoinable repr
+  , InstrValuable repr
+  ) => CombAlternable (Program repr inp) where
+  alt exn (Program l) (Program r) = joinNext $ Program $ \next ->
+    liftM2 (catch exn)
+      (l (commit exn next))
+      (failIfConsumed exn Functor.<$> r next)
+  throw exn = Program $ \_next -> return $ raise exn
+  failure flr = Program $ \_next -> return $ fail (Set.singleton flr)
+  empty = Program $ \_next -> return $ fail (Set.singleton (SomeFailure FailureEmpty))
   try (Program x) = Program $ \next ->
-    catchFail
-      (x (popFail next))
-      -- On exception, reset the input,
-      -- and propagate the failure.
-      (loadInput (fail []))
+    liftM2 (catch ExceptionFailure)
+      (x (commit ExceptionFailure next))
+      -- On exception, reset the input, and propagate the failure.
+      (return $ loadInput $ fail Set.empty)
 
 -- | If no input has been consumed by the failing alternative
 -- then continue with the given continuation.
--- Otherwise, propagate the 'Fail'ure.
+-- Otherwise, propagate the failure.
 failIfConsumed ::
   Cursorable (Cursor inp) =>
-  Branchable repr =>
-  Failable repr =>
-  Inputable repr =>
-  Stackable repr =>
-  SomeInstr repr inp vs ('Succ es) ret ->
-  SomeInstr repr inp (Cursor inp : vs) ('Succ es) ret
-failIfConsumed k = pushInput (liftI2 (H.Term sameOffset) (ifI k (fail [])))
+  InstrBranchable repr =>
+  InstrExceptionable repr =>
+  InstrInputable repr =>
+  InstrValuable repr =>
+  Exception ->
+  SomeInstr repr inp vs ret ->
+  SomeInstr repr inp (Cursor inp ': vs) ret
+failIfConsumed exn k =
+  pushInput $
+  lift2Value (splice sameOffset) $
+  ifBranch k $
+    case exn of
+      ExceptionLabel lbl -> raise lbl
+      ExceptionFailure -> fail Set.empty
 
 -- | @('joinNext' m)@ factorize the next 'Instr'uction
 -- to be able to reuse it multiple times without duplication.
@@ -95,7 +143,7 @@ failIfConsumed k = pushInput (liftI2 (H.Term sameOffset) (ifI k (fail [])))
 -- It should be used each time the next 'Instr'uction
 -- is used multiple times.
 joinNext ::
-  Joinable repr =>
+  InstrJoinable repr =>
   Program repr inp v ->
   Program repr inp v
 joinNext (Program m) = Program $ \case
@@ -103,59 +151,83 @@ joinNext (Program m) = Program $ \case
   -- If a join-node points directly to another join-node,
   -- then reuse it
   next@(Instr RefJoin{}) -> m next
+  -- If a join-node points directly to a 'jump',
+  -- then reuse it.
+  -- Because 'Jump' expects an empty 'valueStack',
+  -- a 'PopValue' has to be here to drop
+  -- the value normaly expected by the 'next' 'Instr'uction.
+  next@(Instr (PopValue (Instr Jump{}))) -> m next
   -- Terminal refJoin Optimization:
   -- If a join-node points directly to a terminal operation,
   -- then it's useless to introduce a join-node.
   next@(Instr Ret{}) -> m next
   -- Introduce a join-node.
-  next -> defJoin joinName next (m (refJoin joinName))
-    where joinName = LetName $ unsafePerformIO $ TH.qNewName "join"
+  next -> do
+    !joinName <- TH.newName "join"
+    defJoin (LetName joinName) next
+      Functor.<$> m (refJoin (LetName joinName))
 
 instance
-  ( tok ~ InputToken inp
-  , Readable tok repr
-  , Typeable tok
-  ) => Satisfiable tok (Program repr inp) where
-  satisfy es p = Program $ read es (trans p)
+  InstrValuable repr =>
+  CombApplicable (Program repr inp) where
+  pure x = Program $ return . pushValue (prodCode x)
+  Program f <*> Program x = Program $ (f <=< x) . applyValue
+  liftA2 f (Program x) (Program y) = Program $ (x <=< y) . lift2Value (prodCode f)
+  Program x *> Program y = Program (x <=< return . popValue <=< y)
+  Program x <* Program y = Program (x <=< y <=< return . popValue)
 instance
-  ( Branchable repr
-  , Joinable repr
-  , Stackable repr
-  ) => Selectable (Program repr inp) where
-  branch (Program lr) (Program l) (Program r) = joinNext $ Program $ \next ->
-    lr (caseI
-      (l (swap (appI next)))
-      (r (swap (appI next))))
+  ( Cursorable (Cursor inp)
+  , InstrBranchable repr
+  , InstrExceptionable repr
+  , InstrInputable repr
+  , InstrJoinable repr
+  , InstrValuable repr
+  ) => CombFoldable (Program repr inp) where
+  {-
+  chainPre op p = go <*> p
+    where go = (Prod..) <$> op <*> go <|> pure Prod.id
+  chainPost p op = p <**> go
+    where go = (Prod..) <$> op <*> go <|> pure Prod.id
+  -}
 instance
-  ( Branchable repr
-  , Joinable repr
-  ) => Matchable (Program repr inp) where
-  conditional (Program a) ps bs (Program d) = joinNext $ Program $ \next ->
-    a (choices
-      (trans Functor.<$> ps)
-      ((\(Program b) -> b next) Functor.<$> bs)
-      (d next))
+  InstrCallable repr =>
+  Letable TH.Name (Program repr inp) where
+  shareable n (Program sub) = Program $ \next -> do
+    sub' <- sub ret
+    return $ defLet (HM.singleton n (SomeLet sub')) (call (LetName n) next)
+  ref _isRec n = Program $ \case
+    -- Tail Call Optimization:
+    -- returning just after a 'call' is useless:
+    -- using 'jump' lets the 'ret' of the 'defLet'
+    -- directly return where it would in two 'ret's.
+    Instr Ret{} -> return $ jump (LetName n)
+    next -> return $ call (LetName n) next
 instance
-  ( Ord (InputToken inp)
+  InstrCallable repr =>
+  Letsable TH.Name (Program repr inp) where
+  lets defs (Program x) = Program $ \next -> do
+    defs' <- Traversable.traverse (\(SomeLet (Program val)) -> liftM SomeLet (val ret)) defs
+    liftM (defLet defs') (x next)
+instance
+  ( Eq (InputToken inp)
   , Cursorable (Cursor inp)
-  , Branchable repr
-  , Failable repr
-  , Inputable repr
-  , Joinable repr
-  , Readable (InputToken inp) repr
+  , InstrBranchable repr
+  , InstrExceptionable repr
+  , InstrInputable repr
+  , InstrJoinable repr
+  , InstrReadable (InputToken inp) repr
   , Typeable (InputToken inp)
-  , Stackable repr
-  ) => Lookable (Program repr inp) where
+  , InstrValuable repr
+  ) => CombLookable (Program repr inp) where
   look (Program x) = Program $ \next ->
-    pushInput (x (swap (loadInput next)))
-  eof = negLook (satisfy [{-discarded by negLook-}] (H.lam1 (\_x -> H.bool True)))
+    liftM pushInput (x (swapValue (loadInput next)))
+  eof = negLook (satisfy (Prod.lam1 (\_x -> Prod.bool True)))
         -- This sets a better failure message
-        <|> (Program $ \_k -> fail [ErrorItemEnd])
+        <|> (Program $ \_next -> return $ fail (Set.singleton (SomeFailure FailureEnd)))
   negLook (Program x) = Program $ \next ->
-    catchFail
+    liftM2 (catch ExceptionFailure)
       -- On x success, discard the result,
-      -- and replace this 'CatchFail''s failure handler
-      -- by a 'Fail'ure whose 'farthestExpecting' is negated,
+      -- and replace this 'Catcher' by a failure whose 'farthestExpecting' is negated,
       -- then a failure is raised from the input
       -- when entering 'negLook', to avoid odd cases:
       -- - where the failure that made (negLook x)
@@ -163,34 +235,33 @@ instance
       --   failure of the grammar.
       -- - where the overall failure of
       --   the grammar might be blamed on something in x
-      --   that, if corrected, still makes x succeed and
-      --   (negLook x) fail.
-      (pushInput (x (pop (popFail (loadInput (fail []))))))
+      --   that, if corrected, still makes x succeed
+      --   and (negLook x) fail.
+      (liftM pushInput $ x $
+        popValue $ commit ExceptionFailure $
+          loadInput $ fail Set.empty)
       -- On x failure, reset the input,
       -- and go on with the next 'Instr'uctions.
-      (loadInput (push H.unit next))
+      (return $ loadInput $ pushValue Prod.unit next)
 instance
-  Routinable repr =>
-  Letable TH.Name (Program repr inp) where
-  def n (Program v) = Program $ \next ->
-    subroutine (LetName n) (v ret) (call (LetName n) next)
-  ref _isRec n = Program $ \case
-    -- Returning just after a 'call' is useless:
-    -- using 'jump' lets the 'ret' of the 'subroutine'
-    -- directly return where it would in two 'ret's.
-    Instr Ret{} -> jump (LetName n)
-    next -> call (LetName n) next
+  ( InstrBranchable repr
+  , InstrJoinable repr
+  ) => CombMatchable (Program repr inp) where
+  conditional (Program a) ps bs (Program d) = joinNext $ Program $ \next -> do
+    bs' <- Control.Monad.sequence $ (\(Program b) -> b next) Functor.<$> bs
+    a =<< liftM (choicesBranch (prodCode Functor.<$> ps) bs') (d next)
 instance
-  ( Cursorable (Cursor inp)
-  , Branchable repr
-  , Failable repr
-  , Inputable repr
-  , Joinable repr
-  , Stackable repr
-  ) => Foldable (Program repr inp) where
-  {-
-  chainPre op p = go <*> p
-    where go = (H..) <$> op <*> go <|> pure H.id
-  chainPost p op = p <**> go
-    where go = (H..) <$> op <*> go <|> pure H.id
-  -}
+  ( tok ~ InputToken inp
+  , InstrReadable tok repr
+  , Typeable tok
+  ) => CombSatisfiable tok (Program repr inp) where
+  satisfyOrFail fs p = Program $ return . read fs (prodCode p)
+instance
+  ( InstrBranchable repr
+  , InstrJoinable repr
+  , InstrValuable repr
+  ) => CombSelectable (Program repr inp) where
+  branch (Program lr) (Program l) (Program r) = joinNext $ Program $ \next ->
+    lr =<< liftM2 caseBranch
+      (l (swapValue (applyValue next)))
+      (r (swapValue (applyValue next)))