1 {-# LANGUAGE UndecidableInstances #-} -- For Cursorable (Cursor inp)
2 -- | Build the 'Instr'uction 'Program' of a 'Machine'
3 -- from the 'Comb'inators of a 'Grammar'.
4 -- 'Instr'uctions are kept introspectable
5 -- to enable more optimizations now possible because
6 -- of a broader knowledge of the 'Instr'uctions around
7 -- those generated (eg. by using 'joinNext').
8 module Symantic.Parser.Machine.Program where
10 import Control.Monad (Monad(..), (<=<), (=<<), liftM, liftM2, sequence)
11 import Data.Bool (Bool(..))
12 import Data.Function (($), (.))
14 import Data.Proxy (Proxy(..))
16 import Type.Reflection (Typeable)
17 import qualified Data.Functor as Functor
18 import qualified Data.HashMap.Strict as HM
19 import qualified Data.Traversable as Traversable
20 import qualified Language.Haskell.TH as TH
21 import qualified Symantic.Parser.Haskell as H
23 import Symantic.Parser.Grammar
24 import Symantic.Parser.Machine.Input
25 import Symantic.Parser.Machine.Instructions
26 import Symantic.Parser.Machine.Optimize
27 import Symantic.Univariant.Trans
30 -- | A 'Program' is a tree of 'Instr'uctions,
31 -- where each 'Instr'uction is built by a continuation
32 -- to be able to introspect, duplicate and/or change
33 -- the next 'Instr'uction.
34 data Program repr inp a = Program { unProgram ::
36 -- This is the next instruction
37 SomeInstr repr inp (a ': vs) ret ->
38 -- This is the current instruction
39 -- IO is needed for 'TH.qNewName'.
40 IO (SomeInstr repr inp vs ret)
43 -- | Build an interpreter of the 'Program' of the given 'Machine'.
46 Machine (InputToken inp) repr =>
49 optimizeMachine (Program f) = trans Functor.<$> f @'[] ret
52 ( Cursorable (Cursor inp)
53 , InstrBranchable repr
54 , InstrExceptionable repr
58 ) => CombAlternable (Program repr inp) where
59 empty = Program $ \_next -> return $ fail []
60 Program l <|> Program r = joinNext $ Program $ \next ->
61 liftM2 (catchException (Proxy @"fail"))
62 (l (popException (Proxy @"fail") next))
63 (failIfConsumed Functor.<$> r next)
64 try (Program x) = Program $ \next ->
65 liftM2 (catchException (Proxy @"fail"))
66 (x (popException (Proxy @"fail") next))
67 -- On exception, reset the input,
68 -- and propagate the failure.
69 (return $ loadInput (fail []))
71 -- | If no input has been consumed by the failing alternative
72 -- then continue with the given continuation.
73 -- Otherwise, propagate the failure.
75 Cursorable (Cursor inp) =>
76 InstrBranchable repr =>
77 InstrExceptionable repr =>
78 InstrInputable repr =>
80 SomeInstr repr inp vs ret ->
81 SomeInstr repr inp (Cursor inp ': vs) ret
84 lift2Value (H.Term sameOffset) $
87 -- | @('joinNext' m)@ factorize the next 'Instr'uction
88 -- to be able to reuse it multiple times without duplication.
89 -- It does so by introducing a 'defJoin'
90 -- and passing the corresponding 'refJoin'
91 -- as next 'Instr'uction to @(m)@,
92 -- unless factorizing is useless because the next 'Instr'uction
93 -- is already a 'refJoin' or a 'ret'.
94 -- It should be used each time the next 'Instr'uction
95 -- is used multiple times.
100 joinNext (Program m) = Program $ \case
101 -- Double refJoin Optimization:
102 -- If a join-node points directly to another join-node,
104 next@(Instr RefJoin{}) -> m next
105 -- Terminal refJoin Optimization:
106 -- If a join-node points directly to a terminal operation,
107 -- then it's useless to introduce a join-node.
108 next@(Instr Ret{}) -> m next
109 -- Introduce a join-node.
111 !joinName <- TH.newName "join"
112 defJoin (LetName joinName) next
113 Functor.<$> m (refJoin (LetName joinName))
116 InstrValuable repr =>
117 CombApplicable (Program repr inp) where
118 pure x = Program $ return . pushValue (trans x)
119 Program f <*> Program x = Program $ (f <=< x) . applyValue
120 liftA2 f (Program x) (Program y) = Program $ (x <=< y) . lift2Value (trans f)
121 Program x *> Program y = Program (x <=< return . popValue <=< y)
122 Program x <* Program y = Program (x <=< y <=< return . popValue)
124 ( Cursorable (Cursor inp)
125 , InstrBranchable repr
126 , InstrExceptionable repr
127 , InstrInputable repr
130 ) => CombFoldable (Program repr inp) where
132 chainPre op p = go <*> p
133 where go = (H..) <$> op <*> go <|> pure H.id
134 chainPost p op = p <**> go
135 where go = (H..) <$> op <*> go <|> pure H.id
138 InstrCallable repr =>
139 Letable TH.Name (Program repr inp) where
140 shareable n (Program sub) = Program $ \next -> do
142 return $ defLet (HM.singleton n (SomeLet sub')) (call (LetName n) next)
143 ref _isRec n = Program $ \case
144 -- Returning just after a 'call' is useless:
145 -- using 'jump' lets the 'ret' of the 'defLet'
146 -- directly return where it would in two 'ret's.
147 Instr Ret{} -> return $ jump (LetName n)
148 next -> return $ call (LetName n) next
150 InstrCallable repr =>
151 Letsable TH.Name (Program repr inp) where
152 lets defs (Program x) = Program $ \next -> do
153 defs' <- Traversable.traverse (\(SomeLet (Program val)) -> liftM SomeLet (val ret)) defs
154 liftM (defLet defs') (x next)
156 ( Ord (InputToken inp)
157 , Cursorable (Cursor inp)
158 , InstrBranchable repr
159 , InstrExceptionable repr
160 , InstrInputable repr
162 , InstrReadable (InputToken inp) repr
163 , Typeable (InputToken inp)
165 ) => CombLookable (Program repr inp) where
166 look (Program x) = Program $ \next ->
167 liftM pushInput (x (swapValue (loadInput next)))
168 eof = negLook (satisfy [{-discarded by negLook-}] (H.lam1 (\_x -> H.bool True)))
169 -- This sets a better failure message
170 <|> (Program $ \_next -> return $ fail [ErrorItemEnd])
171 negLook (Program x) = Program $ \next ->
172 liftM2 (catchException (Proxy @"fail"))
173 -- On x success, discard the result,
174 -- and replace this 'CatchException''s failure handler
175 -- by a failure whose 'farthestExpecting' is negated,
176 -- then a failure is raised from the input
177 -- when entering 'negLook', to avoid odd cases:
178 -- - where the failure that made (negLook x)
179 -- succeed can get the blame for the overall
180 -- failure of the grammar.
181 -- - where the overall failure of
182 -- the grammar might be blamed on something in x
183 -- that, if corrected, still makes x succeed and
186 (popValue (popException (Proxy @"fail") (loadInput
188 -- On x failure, reset the input,
189 -- and go on with the next 'Instr'uctions.
190 (return $ loadInput $ pushValue H.unit next)
192 ( InstrBranchable repr
194 ) => CombMatchable (Program repr inp) where
195 conditional (Program a) ps bs (Program d) = joinNext $ Program $ \next -> do
196 bs' <- Control.Monad.sequence $ (\(Program b) -> b next) Functor.<$> bs
197 a =<< liftM (choicesBranch (trans Functor.<$> ps) bs') (d next)
199 ( tok ~ InputToken inp
200 , InstrReadable tok repr
202 ) => CombSatisfiable tok (Program repr inp) where
203 satisfy es p = Program $ return . read es (trans p)
205 ( InstrBranchable repr
208 ) => CombSelectable (Program repr inp) where
209 branch (Program lr) (Program l) (Program r) = joinNext $ Program $ \next ->
210 lr =<< liftM2 caseBranch
211 (l (swapValue (applyValue next)))
212 (r (swapValue (applyValue next)))
214 InstrExceptionable repr =>
215 CombThrowable (Program repr inp) where
216 throw lbl = Program $ \_next -> return $ raiseException lbl []