1 {-# LANGUAGE UndecidableInstances #-} -- For Cursorable (Cursor inp)
2 -- | Build the 'Instr'uctions 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 'joinNextInstr').
8 module Symantic.Parser.Machine.Build where
10 import Data.Bool (Bool(..))
12 import Data.Function (($), (.))
13 import Type.Reflection (Typeable)
14 import System.IO.Unsafe (unsafePerformIO)
15 import qualified Data.Functor as Functor
16 import qualified Language.Haskell.TH as TH
17 import qualified Language.Haskell.TH.Syntax as TH
18 import qualified Symantic.Parser.Haskell as H
20 import Symantic.Parser.Grammar
21 import Symantic.Parser.Machine.Input
22 import Symantic.Parser.Machine.Instructions
23 import Symantic.Parser.Machine.Optimize
24 import Symantic.Univariant.Trans
27 -- | A 'Machine' is a tree of 'Instr'uctions,
28 -- where each 'Instr'uction is built by a continuation
29 -- to be able to introspect, duplicate and/or change the next 'Instr'uction.
30 data Machine repr inp a = Machine { unMachine ::
32 -- This is the next instruction
33 SomeInstr repr inp (a ': vs) ('Succ es) ret ->
34 -- This is the built instruction
35 SomeInstr repr inp vs ('Succ es) ret
38 -- | Build a 'Machine'.
41 Executable (InputToken inp) repr =>
43 repr inp '[] ('Succ es) a
44 optimizeMachine (Machine m) = trans (m @'[] @es ret)
48 Applicable (Machine repr inp) where
49 pure x = Machine (push (trans x))
50 Machine f <*> Machine x = Machine (f . x . appI)
51 liftA2 f (Machine x) (Machine y) =
52 Machine (x . y . liftI2 (trans f))
53 Machine x *> Machine y = Machine (x . pop . y)
54 Machine x <* Machine y = Machine (x . y . pop)
56 ( Cursorable (Cursor inp)
62 ) => Alternable (Machine repr inp) where
63 empty = Machine $ \_next -> fail []
64 Machine l <|> Machine r = joinNextInstr $ Machine $ \next ->
67 (instrFailIfConsumed (r next))
68 try (Machine x) = Machine $ \next ->
71 -- On exception, reset the input,
72 -- and propagate the failure.
75 -- | If no input has been consumed by the failing alternative
76 -- then continue with the given continuation.
77 -- Otherwise, propagate the 'Fail'ure.
78 instrFailIfConsumed ::
79 Cursorable (Cursor inp) =>
84 SomeInstr repr inp vs ('Succ es) ret ->
85 SomeInstr repr inp (Cursor inp : vs) ('Succ es) ret
86 instrFailIfConsumed k = pushInput (liftI2 (H.Term sameOffset) (ifI k (fail [])))
88 -- | @('joinNextInstr' m)@ factorize the next 'Instr'uction
89 -- to be able to reuse it multiple times without duplication.
90 -- It does so by introducing a 'DefJoin'
91 -- and passing the corresponding 'RefJoin' 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 joinNextInstr (Machine m) = Machine $ \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-point.
108 next@(Instr Ret{}) -> m next
109 -- Introduce a join-node.
110 next -> defJoin joinName next (m (refJoin joinName))
111 where joinName = LetName $ unsafePerformIO $ TH.qNewName "join"
114 ( tok ~ InputToken inp
117 ) => Satisfiable tok (Machine repr inp) where
118 satisfy es p = Machine $ read es (trans p)
123 ) => Selectable (Machine repr inp) where
124 branch (Machine lr) (Machine l) (Machine r) = joinNextInstr $ Machine $ \next ->
126 (l (swap (appI next)))
127 (r (swap (appI next))))
131 ) => Matchable (Machine repr inp) where
132 conditional (Machine a) ps bs (Machine d) = joinNextInstr $ Machine $ \next ->
134 (trans Functor.<$> ps)
135 ((\(Machine b) -> b next) Functor.<$> bs)
138 ( Ord (InputToken inp)
139 , Cursorable (Cursor inp)
144 , Readable (InputToken inp) repr
145 , Typeable (InputToken inp)
147 ) => Lookable (Machine repr inp) where
148 look (Machine x) = Machine $ \next ->
149 pushInput (x (swap (loadInput next)))
150 eof = negLook (satisfy [{-discarded by negLook-}] (H.lam1 (\_x -> H.bool True)))
151 -- This sets a better failure message
152 <|> (Machine $ \_k -> fail [ErrorItemEnd])
153 negLook (Machine x) = Machine $ \next ->
155 -- On x success, discard the result,
156 -- and replace this 'CatchFail''s failure handler
157 -- by a 'Fail'ure whose 'farthestExpecting' is negated,
158 -- then a failure is raised from the input
159 -- when entering 'negLook', to avoid odd cases:
160 -- - where the failure that made (negLook x)
161 -- succeed can get the blame for the overall
162 -- failure of the grammar.
163 -- - where the overall failure of
164 -- the grammar might be blamed on something in x
165 -- that, if corrected, still makes x succeed and
167 (pushInput (x (pop (popFail (loadInput (fail []))))))
168 -- On x failure, reset the input,
169 -- and go on with the next 'Instr'uctions.
170 (loadInput (push H.unit next))
173 Letable TH.Name (Machine repr inp) where
174 def n (Machine v) = Machine $ \next ->
175 subroutine (LetName n) (v ret) (call (LetName n) next)
176 ref _isRec n = Machine $ \case
177 -- Returning just after a 'call' is useless:
178 -- using 'jump' lets the 'ret' of the 'subroutine'
179 -- directly return where it would in two 'ret's.
180 Instr Ret{} -> jump (LetName n)
181 next -> call (LetName n) next
183 ( Cursorable (Cursor inp)
189 ) => Foldable (Machine repr inp) where
191 chainPre op p = go <*> p
192 where go = (H..) <$> op <*> go <|> pure H.id
193 chainPost p op = p <**> go
194 where go = (H..) <$> op <*> go <|> pure H.id