symantic-parser-0.1.0.20210201: src/Symantic/Parser/Machine/Program.hs
{-# LANGUAGE UndecidableInstances #-} -- For Cursorable (Cursor inp)
-- | Build the 'Instr'uction 'Program' of a 'Machine'
-- from the 'Comb'inators of a 'Grammar'.
-- 'Instr'uctions are kept introspectable
-- to enable more optimizations now possible because
-- of a broader knowledge of the 'Instr'uctions around
-- those generated (eg. by using 'joinNext').
module Symantic.Parser.Machine.Program where
import Data.Bool (Bool(..))
import Data.Ord (Ord)
import Data.Function (($), (.))
import Type.Reflection (Typeable)
import System.IO.Unsafe (unsafePerformIO)
import qualified Data.Functor as Functor
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH
import qualified Symantic.Parser.Haskell as H
import Symantic.Parser.Grammar
import Symantic.Parser.Machine.Input
import Symantic.Parser.Machine.Instructions
import Symantic.Parser.Machine.Optimize
import Symantic.Univariant.Trans
-- * Type 'Program'
-- | A 'Program' is a tree of 'Instr'uctions,
-- where each 'Instr'uction is built by a continuation
-- to be able to introspect, duplicate and/or change
-- the next 'Instr'uction.
data Program repr inp a = Program { unProgram ::
forall vs es ret.
-- This is the next instruction
SomeInstr repr inp (a ': vs) ('Succ es) ret ->
-- This is the current instruction
SomeInstr repr inp vs ('Succ es) ret }
-- | Build an interpreter of the 'Program' of the given 'Machine'.
optimizeMachine ::
forall inp es repr a.
Machine (InputToken inp) repr =>
Program repr inp a ->
repr inp '[] ('Succ es) a
optimizeMachine (Program f) = trans (f @'[] @es ret)
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)
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))
try (Program x) = Program $ \next ->
catchFail
(x (popFail next))
-- On exception, reset the input,
-- and propagate the failure.
(loadInput (fail []))
-- | If no input has been consumed by the failing alternative
-- then continue with the given continuation.
-- Otherwise, propagate the 'Fail'ure.
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 [])))
-- | @('joinNext' m)@ factorize the next 'Instr'uction
-- to be able to reuse it multiple times without duplication.
-- It does so by introducing a 'defJoin'
-- and passing the corresponding 'refJoin'
-- as next 'Instr'uction to @(m)@,
-- unless factorizing is useless because the next 'Instr'uction
-- is already a 'refJoin' or a 'ret'.
-- It should be used each time the next 'Instr'uction
-- is used multiple times.
joinNext ::
Joinable repr =>
Program repr inp v ->
Program repr inp v
joinNext (Program m) = Program $ \case
-- Double refJoin Optimization:
-- If a join-node points directly to another join-node,
-- then reuse it
next@(Instr RefJoin{}) -> 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"
instance
( tok ~ InputToken inp
, Readable tok repr
, Typeable tok
) => Satisfiable tok (Program repr inp) where
satisfy es p = Program $ read es (trans p)
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))))
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))
instance
( Ord (InputToken inp)
, Cursorable (Cursor inp)
, Branchable repr
, Failable repr
, Inputable repr
, Joinable repr
, Readable (InputToken inp) repr
, Typeable (InputToken inp)
, Stackable repr
) => Lookable (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)))
-- This sets a better failure message
<|> (Program $ \_k -> fail [ErrorItemEnd])
negLook (Program x) = Program $ \next ->
catchFail
-- On x success, discard the result,
-- and replace this 'CatchFail''s failure handler
-- by a 'Fail'ure 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)
-- succeed can get the blame for the overall
-- 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 []))))))
-- On x failure, reset the input,
-- and go on with the next 'Instr'uctions.
(loadInput (push H.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
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
-}