1 {-# LANGUAGE ConstraintKinds #-}
2 {-# LANGUAGE UndecidableInstances #-} -- For Cursorable (Cursor inp)
3 -- | Build the 'Instr'uction 'Program' of a 'Machine'
4 -- from the 'Comb'inators of a 'Grammar'.
5 -- 'Instr'uctions are kept introspectable
6 -- to enable more optimizations now possible because
7 -- of a broader knowledge of the 'Instr'uctions around
8 -- those generated (eg. by using 'joinNext').
9 module Symantic.Parser.Machine.Program where
11 import Control.Monad (Monad(..), (<=<), (=<<), liftM, liftM2, sequence)
12 import Data.Function (($))
14 import Type.Reflection (Typeable)
15 import Control.DeepSeq (NFData)
16 import Data.Bool (Bool(..))
17 import Data.Eq (Eq(..))
18 import Data.Function ((.))
19 import Text.Show (Show(..))
20 import qualified Data.Functor as Functor
21 import qualified Data.HashMap.Strict as HM
22 import qualified Data.Set as Set
23 import qualified Data.Traversable as Traversable
24 import qualified Language.Haskell.TH as TH
25 import qualified Language.Haskell.TH.Syntax as TH
26 import qualified Symantic.Univariant.Lang as H
28 import Symantic.Parser.Grammar
29 import Symantic.Parser.Machine.Input
30 import Symantic.Parser.Machine.Instructions
31 import Symantic.Parser.Machine.Optimize
32 import Symantic.Univariant.Trans
36 -- | A 'Program' is a tree of 'Instr'uctions,
37 -- where each 'Instr'uction is built by a continuation
38 -- to be able to introspect, duplicate and/or change
39 -- the next 'Instr'uction.
40 data Program repr inp a = Program { unProgram ::
42 -- This is the next instruction
43 SomeInstr repr inp (a ': vs) ret ->
44 -- This is the current instruction
45 -- IO is needed for 'TH.newName' in 'joinNext'.
46 IO (SomeInstr repr inp vs ret)
49 -- | Build an interpreter of the 'Program' of the given 'Machinable'.
52 Machinable (InputToken inp) repr =>
55 optimizeMachine (Program f) = trans Functor.<$> f @'[] ret
57 -- * Class 'Machinable'
58 -- | All the 'Instr'uctions.
59 type Machinable tok repr =
60 ( InstrBranchable repr
61 , InstrExceptionable repr
66 , InstrReadable tok repr
75 ( Cursorable (Cursor inp)
76 , InstrBranchable repr
77 , InstrExceptionable repr
81 , InstrReadable (InputToken inp) repr
82 , Typeable (InputToken inp)
84 Trans (Comb CombAlternable (Program repr inp)) (Program repr inp) where
87 (Comb (SatisfyOrFail _fs p :: Comb (CombSatisfiable (InputToken inp)) (Program repr inp) a))
88 (Comb (Failure sf)) ->
89 Program $ return . trace "trans.read" . read (Set.singleton sf) (trace "read.prodCode" (prodCode p))
90 Alt exn x y -> alt exn (trans x) (trans y)
92 Failure sf -> failure sf
93 Throw exn -> throw exn
94 Try x -> try (trans x)
97 ( Cursorable (Cursor inp)
98 , InstrBranchable repr
99 , InstrExceptionable repr
100 , InstrInputable repr
103 ) => CombAlternable (Program repr inp) where
104 alt exn (Program l) (Program r) = joinNext $ Program $ \next ->
106 (l (commit exn next))
107 (failIfConsumed exn Functor.<$> r next)
108 throw exn = Program $ \_next -> return $ raise exn
109 failure flr = Program $ \_next -> return $ fail (Set.singleton flr)
110 empty = Program $ \_next -> return $ fail (Set.singleton (SomeFailure FailureEmpty))
111 try (Program x) = Program $ \next ->
112 liftM2 (catch ExceptionFailure)
113 (x (commit ExceptionFailure next))
114 -- On exception, reset the input, and propagate the failure.
115 (return $ loadInput $ fail Set.empty)
117 -- | If no input has been consumed by the failing alternative
118 -- then continue with the given continuation.
119 -- Otherwise, propagate the failure.
121 Cursorable (Cursor inp) =>
122 InstrBranchable repr =>
123 InstrExceptionable repr =>
124 InstrInputable repr =>
125 InstrValuable repr =>
127 SomeInstr repr inp vs ret ->
128 SomeInstr repr inp (Cursor inp ': vs) ret
129 failIfConsumed exn k =
131 lift2Value (splice sameOffset) $
134 ExceptionLabel lbl -> raise lbl
135 ExceptionFailure -> fail Set.empty
137 -- | @('joinNext' m)@ factorize the next 'Instr'uction
138 -- to be able to reuse it multiple times without duplication.
139 -- It does so by introducing a 'defJoin'
140 -- and passing the corresponding 'refJoin'
141 -- as next 'Instr'uction to @(m)@,
142 -- unless factorizing is useless because the next 'Instr'uction
143 -- is already a 'refJoin' or a 'ret'.
144 -- It should be used each time the next 'Instr'uction
145 -- is used multiple times.
147 InstrJoinable repr =>
148 Program repr inp v ->
150 joinNext (Program m) = Program $ \case
151 -- Double refJoin Optimization:
152 -- If a join-node points directly to another join-node,
154 next@(Instr RefJoin{}) -> m next
155 -- Terminal refJoin Optimization:
156 -- If a join-node points directly to a terminal operation,
157 -- then it's useless to introduce a join-node.
158 next@(Instr Ret{}) -> m next
159 -- Introduce a join-node.
161 !joinName <- TH.newName "join"
162 defJoin (LetName joinName) next
163 Functor.<$> m (refJoin (LetName joinName))
166 InstrValuable repr =>
167 CombApplicable (Program repr inp) where
168 pure x = Program $ return . pushValue (prodCode (trace "pushValue.prodCode" x))
169 Program f <*> Program x = Program $ (f <=< x) . applyValue
170 liftA2 f (Program x) (Program y) = Program $ (x <=< y) . lift2Value (prodCode f)
171 Program x *> Program y = Program (x <=< return . popValue <=< y)
172 Program x <* Program y = Program (x <=< y <=< return . popValue)
174 ( Cursorable (Cursor inp)
175 , InstrBranchable repr
176 , InstrExceptionable repr
177 , InstrInputable repr
180 ) => CombFoldable (Program repr inp) where
182 chainPre op p = go <*> p
183 where go = (H..) <$> op <*> go <|> pure H.id
184 chainPost p op = p <**> go
185 where go = (H..) <$> op <*> go <|> pure H.id
188 InstrCallable repr =>
189 Letable TH.Name (Program repr inp) where
190 shareable n (Program sub) = Program $ \next -> do
192 return $ defLet (HM.singleton n (SomeLet sub')) (call (LetName n) next)
193 ref _isRec n = Program $ \case
194 -- Returning just after a 'call' is useless:
195 -- using 'jump' lets the 'ret' of the 'defLet'
196 -- directly return where it would in two 'ret's.
197 Instr Ret{} -> return $ jump (LetName n)
198 next -> return $ call (LetName n) next
200 InstrCallable repr =>
201 Letsable TH.Name (Program repr inp) where
202 lets defs (Program x) = Program $ \next -> do
203 defs' <- Traversable.traverse (\(SomeLet (Program val)) -> liftM SomeLet (val ret)) defs
204 liftM (defLet defs') (x next)
206 ( Eq (InputToken inp)
207 , Cursorable (Cursor inp)
208 , InstrBranchable repr
209 , InstrExceptionable repr
210 , InstrInputable repr
212 , InstrReadable (InputToken inp) repr
213 , Typeable (InputToken inp)
215 ) => CombLookable (Program repr inp) where
216 look (Program x) = Program $ \next ->
217 liftM pushInput (x (swapValue (loadInput next)))
218 eof = negLook (satisfy (H.lam1 (\_x -> H.bool True)))
219 -- This sets a better failure message
220 <|> (Program $ \_next -> return $ fail (Set.singleton (SomeFailure FailureEnd)))
221 negLook (Program x) = Program $ \next ->
222 liftM2 (catch ExceptionFailure)
223 -- On x success, discard the result,
224 -- and replace this 'Catcher' by a failure whose 'farthestExpecting' is negated,
225 -- then a failure is raised from the input
226 -- when entering 'negLook', to avoid odd cases:
227 -- - where the failure that made (negLook x)
228 -- succeed can get the blame for the overall
229 -- failure of the grammar.
230 -- - where the overall failure of
231 -- the grammar might be blamed on something in x
232 -- that, if corrected, still makes x succeed
233 -- and (negLook x) fail.
234 (liftM pushInput $ x $
235 popValue $ commit ExceptionFailure $
236 loadInput $ fail Set.empty)
237 -- On x failure, reset the input,
238 -- and go on with the next 'Instr'uctions.
239 (return $ loadInput $ pushValue H.unit next)
241 ( InstrBranchable repr
243 ) => CombMatchable (Program repr inp) where
244 conditional (Program a) ps bs (Program d) = joinNext $ Program $ \next -> do
245 bs' <- Control.Monad.sequence $ (\(Program b) -> b next) Functor.<$> bs
246 a =<< liftM (choicesBranch (prodCode Functor.<$> ps) bs') (d next)
248 ( tok ~ InputToken inp
249 , InstrReadable tok repr
251 ) => CombSatisfiable tok (Program repr inp) where
252 satisfyOrFail fs p = Program $ return . read fs (trace "satisfyOrFail.read.prodCode" (prodCode p))
254 ( InstrBranchable repr
257 ) => CombSelectable (Program repr inp) where
258 branch (Program lr) (Program l) (Program r) = joinNext $ Program $ \next ->
259 lr =<< liftM2 caseBranch
260 (l (swapValue (applyValue next)))
261 (r (swapValue (applyValue next)))