1 {-# LANGUAGE AllowAmbiguousTypes #-}
2 {-# LANGUAGE DeriveAnyClass #-} -- For NFData instances
3 {-# LANGUAGE DeriveGeneric #-} -- For NFData instances
4 {-# LANGUAGE StandaloneDeriving #-} -- For Show (ParsingError inp)
5 {-# LANGUAGE ConstraintKinds #-} -- For Dict
6 {-# LANGUAGE TemplateHaskell #-}
7 {-# LANGUAGE TupleSections #-}
8 {-# LANGUAGE MagicHash #-}
9 {-# LANGUAGE UnboxedTuples #-} -- For nextInput
10 {-# LANGUAGE UndecidableInstances #-} -- For Show (ParsingError inp)
11 {-# OPTIONS_GHC -fno-warn-orphans #-}
12 module Symantic.Parser.Machine.Generate where
14 import Control.DeepSeq (NFData(..))
15 import Control.Monad (Monad(..))
16 import Control.Monad.ST (ST, runST)
17 import Data.Bool (Bool(..))
18 import Data.Char (Char)
19 import Data.Either (Either(..), either)
20 import Data.Foldable (toList, null)
21 import Data.Function (($), (.), id, on)
22 import Data.Functor ((<$>), (<$))
24 import Data.List.NonEmpty (NonEmpty(..))
26 import Data.Maybe (Maybe(..))
27 import Data.Ord (Ord(..), Ordering(..))
28 import Data.Proxy (Proxy(..))
29 import Data.Semigroup (Semigroup(..))
31 import Data.String (String)
32 import Data.Traversable (Traversable(..))
33 import Data.Tuple (snd)
34 import Data.Typeable (Typeable)
35 import Data.Word (Word8)
36 import GHC.Generics (Generic)
37 import GHC.Show (showCommaSpace)
38 import Language.Haskell.TH (CodeQ)
39 import Prelude ((+), (-), error)
40 import Text.Show (Show(..), showParen, showString)
41 import qualified Data.HashMap.Strict as HM
42 import qualified Data.List as List
43 import qualified Data.List.NonEmpty as NE
44 import qualified Data.Map.Internal as Map_
45 import qualified Data.Map.Strict as Map
46 import qualified Data.Set as Set
47 import qualified Data.Set.Internal as Set_
48 import qualified Data.STRef as ST
49 import qualified Language.Haskell.TH as TH
50 import qualified Language.Haskell.TH.Syntax as TH
52 import Symantic.Derive
53 import Symantic.ObserveSharing
54 import Symantic.Parser.Grammar.Combinators
55 ( UnscopedRegister(..)
61 import Symantic.Parser.Machine.Input
62 import Symantic.Parser.Machine.Instructions
63 import qualified Language.Haskell.TH.HideName as TH
64 import qualified Symantic.Lang as Prod
65 import qualified Symantic.Optimize as Prod
69 -- | Convenient utility to generate some final 'TH.CodeQ'.
70 genCode :: Splice a -> CodeQ a
71 genCode = derive . Prod.normalOrderReduction
74 -- | Generate the 'CodeQ' parsing the input.
75 data Gen inp vs a = Gen
76 { genAnalysisByLet :: OpenRecs TH.Name GenAnalysis
77 -- ^ 'genAnalysis' for each 'defLet' and 'defJoin' of the 'Machine'.
78 , genAnalysis :: OpenRec TH.Name GenAnalysis
79 -- ^ Synthetized (bottom-up) static genAnalysis of the 'Machine'.
82 CodeQ (ST st (Either (ParsingError inp) a))
85 {-# INLINE returnST #-}
86 returnST :: forall s a. a -> ST s a
87 returnST = return @(ST s)
89 -- | @('generateCode' input mach)@ generates @TemplateHaskell@ code
90 -- parsing the given 'input' according to the given 'Machine'.
93 Eq (InputToken inp) =>
94 NFData (InputToken inp) =>
95 Show (InputToken inp) =>
96 Typeable (InputToken inp) =>
97 TH.Lift (InputToken inp) =>
99 -- InputToken inp ~ Char =>
104 CodeQ (inp -> Either (ParsingError inp) a)
105 generateCode Gen{unGen=k, ..} = [|| \(input :: inp) ->
106 -- Pattern bindings containing unlifted types
107 -- should use an outermost bang pattern.
108 let !(# init, readMore, readNext #) = $$(cursorOf [||input||])
109 finalRet = \_farInp _farExp v _inp -> returnST $ Right v
110 finalRaise :: forall st b. (Catcher st inp b)
111 = \ !exn _failInp !farInp !farExp ->
112 returnST $ Left ParsingError
113 { parsingErrorOffset = offset farInp
114 , parsingErrorException = exn
115 , parsingErrorUnexpected =
117 then Just (let (# c, _ #) = readNext farInp in c)
119 , parsingErrorExpecting = farExp
123 -- | Defines 'inputTokenProxy' so that the TemplateHaskell code
124 -- can refer to @(InputToken inp)@ through it.
125 defInputTokenProxy :: TH.CodeQ a -> TH.CodeQ a
126 defInputTokenProxy exprCode =
127 TH.unsafeCodeCoerce [|
128 let $(return (TH.VarP inputTokenProxy)) = Proxy :: Proxy (InputToken inp) in
129 $(TH.unTypeQ (TH.examineCode exprCode))
134 { valueStack = ValueStackEmpty
135 , catchStackByLabel = Map.empty :: Map Exception (NonEmpty (TH.CodeQ (Catcher s inp a)))
136 , defaultCatch = [||finalRaise||]
137 , returnCall = [||finalRet||] :: CodeQ (Return s inp a a)
139 , nextInput = [||readNext||]
140 , moreInput = [||readMore||]
141 -- , farthestError = [||Nothing||]
142 , farthestInput = [||init||]
143 , farthestExpecting = [||Set.empty||]
146 , finalGenAnalysisByLet = mutualFix genAnalysisByLet
151 -- ** Type 'ParsingError'
152 data ParsingError inp
154 { parsingErrorOffset :: Offset
155 , parsingErrorException :: Exception
156 -- | Note: if a 'FailureHorizon' greater than 1
157 -- is amongst the 'parsingErrorExpecting'
158 -- then 'parsingErrorUnexpected' is only the 'InputToken'
159 -- at the begining of the expected 'Horizon'.
160 , parsingErrorUnexpected :: Maybe (InputToken inp)
161 , parsingErrorExpecting :: Set SomeFailure
163 deriving instance NFData (InputToken inp) => NFData (ParsingError inp)
164 --deriving instance Show (InputToken inp) => Show (ParsingError inp)
165 instance Show (InputToken inp) => Show (ParsingError inp) where
166 showsPrec p ParsingError{..} =
167 showParen (p >= 11) $
168 showString "ParsingErrorStandard {" .
169 showString "parsingErrorOffset = " .
170 showsPrec 0 parsingErrorOffset .
172 showString "parsingErrorException = " .
173 showsPrec 0 parsingErrorException .
175 showString "parsingErrorUnexpected = " .
176 showsPrec 0 parsingErrorUnexpected .
178 showString "parsingErrorExpecting = fromList " .
180 -- Sort on the string representation
181 -- because the 'Ord' of the 'SomeFailure'
182 -- is based upon hashes ('typeRepFingerprint')
183 -- depending on packages' ABI and whether
184 -- cabal-install's setup is --inplace or not,
185 -- and that would be too unstable for golden tests.
186 List.sortBy (compare `on` show) $
187 Set.toList parsingErrorExpecting
191 -- ** Type 'ErrorLabel'
192 type ErrorLabel = String
194 -- * Type 'GenAnalysis'
195 data GenAnalysis = GenAnalysis
196 { minReads :: Either Exception Horizon
197 , mayRaise :: Map Exception ()
198 , freeRegs :: Set TH.Name
204 -- | Minimal input length required for a successful parsing.
205 type Horizon = Offset
207 -- altGenAnalysis = List.foldl' (\acc x -> either Left (\h -> Right (either (const h) (min h) acc)) x)
208 -- | Merge given 'GenAnalysis' as sequences.
209 seqGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
210 seqGenAnalysis aas@(a:|as) = GenAnalysis
211 { minReads = List.foldl' (\acc x ->
212 acc >>= \r -> (r +) <$> minReads x
214 , mayRaise = sconcat (mayRaise <$> aas)
215 , freeRegs = sconcat (freeRegs <$> aas)
217 -- | Merge given 'GenAnalysis' as alternatives.
218 altGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
219 altGenAnalysis aas@(a:|as) = GenAnalysis
220 { minReads = List.foldl' (\acc x ->
229 Right r' -> Right (min r r')
231 , mayRaise = sconcat (mayRaise <$> aas)
232 , freeRegs = sconcat (freeRegs <$> aas)
237 -- *** Type 'FarthestError'
238 data FarthestError inp = FarthestError
239 { farthestInput :: Cursor inp
240 , farthestExpecting :: [Failure (InputToken inp)]
245 -- | This is an inherited (top-down) context
246 -- only present at compile-time, to build TemplateHaskell splices.
247 data GenCtx st inp vs a =
248 ( Cursorable (Cursor inp)
250 , TH.Lift (InputToken inp)
251 , Show (InputToken inp)
252 , Eq (InputToken inp)
253 , Typeable (InputToken inp)
254 , NFData (InputToken inp)
257 { valueStack :: ValueStack vs
258 , catchStackByLabel :: Map Exception (NonEmpty (CodeQ (Catcher st inp a)))
259 -- | Default 'Catcher' defined at the begining of the generated 'CodeQ',
260 -- hence a constant within the 'Gen'eration.
261 , defaultCatch :: forall b. CodeQ (Catcher st inp b)
262 , returnCall :: CodeQ (Return st inp a a)
263 , input :: CodeQ (Cursor inp)
264 , moreInput :: CodeQ (Cursor inp -> Bool)
265 , nextInput :: CodeQ (Cursor inp -> (# InputToken inp, Cursor inp #))
266 , farthestInput :: CodeQ (Cursor inp)
267 , farthestExpecting :: CodeQ (Set SomeFailure)
268 -- | Remaining horizon already checked.
269 -- Use to factorize 'input' length checks,
270 -- instead of checking the 'input' length
271 -- one 'InputToken' at a time at each 'read'.
272 -- Updated by 'checkHorizon'
273 -- and reset elsewhere when needed.
274 , checkedHorizon :: Horizon
275 -- | Used by 'pushInput' and 'loadInput'
276 -- to restore the 'Horizon' at the restored 'input'.
277 , horizonStack :: [Horizon]
278 -- | Output of 'mutualFix'.
279 , finalGenAnalysisByLet :: LetRecs TH.Name GenAnalysis
282 -- ** Type 'ValueStack'
283 data ValueStack vs where
284 ValueStackEmpty :: ValueStack '[]
286 { valueStackHead :: Splice v
287 , valueStackTail :: ValueStack vs
288 } -> ValueStack (v ': vs)
290 instance InstrValuable Gen where
292 { unGen = \ctx -> {-trace "unGen.pushValue" $-} unGen k ctx
293 { valueStack = ValueStackCons x (valueStack ctx) }
296 { unGen = \ctx -> {-trace "unGen.popValue" $-} unGen k ctx
297 { valueStack = valueStackTail (valueStack ctx) }
300 { unGen = \ctx -> {-trace "unGen.lift2Value" $-} unGen k ctx
302 let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
303 ValueStackCons (f Prod..@ x Prod..@ y) vs
307 { unGen = \ctx -> {-trace "unGen.swapValue" $-} unGen k ctx
309 let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
310 ValueStackCons x (ValueStackCons y vs)
313 instance InstrBranchable Gen where
314 caseBranch kx ky = Gen
315 { genAnalysisByLet = genAnalysisByLet kx <> genAnalysisByLet ky
316 , genAnalysis = \final -> altGenAnalysis $ genAnalysis kx final :| [genAnalysis ky final]
317 , unGen = \ctx -> {-trace "unGen.caseBranch" $-}
318 let ValueStackCons v vs = valueStack ctx in
320 case $$(genCode v) of
321 Left x -> $$(unGen kx ctx{ valueStack = ValueStackCons (splice [||x||]) vs })
322 Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (splice [||y||]) vs })
325 choicesBranch bs default_ = Gen
326 { genAnalysisByLet = sconcat $ genAnalysisByLet default_ :| (genAnalysisByLet . snd <$> bs)
327 , genAnalysis = \final -> altGenAnalysis $
328 (\k -> genAnalysis k final)
329 <$> (default_:|(snd <$> bs))
330 , unGen = \ctx0 -> {-trace "unGen.choicesBranch" $-}
331 let ValueStackCons v vs = valueStack ctx0 in
332 let ctx = ctx0{valueStack = vs} in
334 go x ((p,b):bs') = [||
335 if $$(genCode (p Prod..@ x))
337 let _ = "choicesBranch.then" in
338 $$({-trace "unGen.choicesBranch.b" $-} unGen b ctx)
340 let _ = "choicesBranch.else" in
343 go _ _ = unGen default_ ctx
346 instance InstrExceptionable Gen where
348 { genAnalysisByLet = HM.empty
349 , genAnalysis = \_final -> GenAnalysis
350 { minReads = Left (ExceptionLabel exn)
351 , mayRaise = Map.singleton (ExceptionLabel exn) ()
352 , freeRegs = Set.empty
354 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.raise: "<>show exn) $-} [||
355 $$(raiseException ctx (ExceptionLabel exn))
356 (ExceptionLabel $$(TH.liftTyped exn))
357 {-failInp-}$$(input ctx)
358 {-farInp-}$$(input ctx)
359 $$(farthestExpecting ctx)
363 { genAnalysisByLet = HM.empty
364 , genAnalysis = \_final -> GenAnalysis
365 { minReads = Left ExceptionFailure
366 , mayRaise = Map.singleton ExceptionFailure ()
367 , freeRegs = Set.empty
369 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.fail: "<>show exn) $-}
371 then [|| -- Raise without updating the farthest error.
372 $$(raiseException ctx ExceptionFailure)
374 {-failInp-}$$(input ctx)
375 $$(farthestInput ctx)
376 $$(farthestExpecting ctx)
378 else raiseFailure ctx [||fs||]
381 { unGen = \ctx -> {-trace ("unGen.commit: "<>show exn) $-}
382 unGen k ctx{catchStackByLabel =
384 _r0:|(r1:rs) -> Just (r1:|rs)
387 exn (catchStackByLabel ctx)
390 catch exn ok ko = Gen
391 { genAnalysisByLet = genAnalysisByLet ok <> genAnalysisByLet ko
392 , genAnalysis = \final ->
393 let okAnalysis = genAnalysis ok final in
395 okAnalysis{ mayRaise = Map.delete exn (mayRaise okAnalysis) } :|
396 [ genAnalysis ko final ]
397 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.catch: "<>show exn) $-} [||
398 let _ = $$(liftTypedString ("catch "<>show exn)) in
399 let catchHandler !_exn !failInp !farInp !farExp =
400 let _ = $$(liftTypedString ("catch.ko "<>show exn)) in
401 $$({-trace ("unGen.catch.ko: "<>show exn) $-} unGen ko ctx
402 -- Push 'input' and 'checkedHorizon'
403 -- as they were when entering 'catch',
404 -- they will be available to 'loadInput', if any.
406 ValueStackCons (splice (input ctx)) $
407 --ValueStackCons (Prod.var [||exn||]) $
410 checkedHorizon ctx : horizonStack ctx
411 -- Note that 'catchStackByLabel' is reset.
412 -- Move the input to the failing position.
413 , input = [||failInp||]
414 -- The 'checkedHorizon' at the 'raise's are not known here.
415 -- Nor whether 'failInp' is after 'checkedHorizon' or not.
416 -- Hence fallback to a safe value.
418 -- Set the farthestInput to the farthest computed in 'fail'.
419 , farthestInput = [||farInp||]
420 , farthestExpecting = [||farExp||]
423 $$({-trace ("unGen.catch.ok: "<>show es) $-} unGen ok ctx
424 { catchStackByLabel =
425 Map.insertWith (<>) exn
426 (NE.singleton [||catchHandler||])
427 (catchStackByLabel ctx)
431 instance InstrInputable Gen where
434 {-trace "unGen.pushInput" $-}
436 { valueStack = splice (input ctx) `ValueStackCons` valueStack ctx
437 , horizonStack = checkedHorizon ctx : horizonStack ctx
442 {-trace "unGen.loadInput" $-}
443 let ValueStackCons input vs = valueStack ctx in
444 let (h, hs) = case horizonStack ctx of
450 , input = genCode input
453 , genAnalysis = \final ->
454 let analysis = genAnalysis k final in
455 analysis{minReads = 0 <$ minReads analysis}
457 instance InstrCallable Gen where
459 { unGen = \ctx@GenCtx{} ->
460 {-trace ("unGen.defLet: defs="<>show (HM.keys defs)) $-}
461 TH.unsafeCodeCoerce $ do
462 decls <- traverse (makeDecl ctx) (HM.toList defs)
463 body <- TH.unTypeQ $ TH.examineCode $
464 {-trace "unGen.defLet.body" $-}
467 -- | Use 'List.sortBy' to output more deterministic code
468 -- to be able to golden test it, at the cost of more computations
469 -- (at compile-time only though).
470 List.sortBy (compare `on` TH.hideName) $
476 : ((\(SomeLet sub) -> genAnalysis sub) <$> defs)
477 : ((\(SomeLet sub) -> genAnalysisByLet sub) <$> HM.elems defs)
480 makeDecl ctx (subName, SomeLet sub) = do
481 let analysis = finalGenAnalysisByLet ctx HM.! subName
482 body <- takeFreeRegs (freeRegs analysis) $
483 TH.unTypeQ $ TH.examineCode $ [|| -- buildRec in Parsley
484 -- Called by 'call' or 'jump'.
485 \ !callReturn{-from generateSuspend or returnCall-}
487 !callCatchStackByLabel{- 'catchStackByLabel' from the 'call'-site -} ->
488 $$({-trace ("unGen.defLet.sub: "<>show subName) $-} unGen sub ctx
489 { valueStack = ValueStackEmpty
490 -- Build a 'catchStackByLabel' for the 'mayRaise' of the subroutine,
491 -- where each 'Catcher' calls the one passed by the 'call'-site (in 'callCatchStackByLabel').
492 -- Note that currently the 'call'-site can supply in 'callCatchStackByLabel'
493 -- a subset of the 'mayRaise' needed by this subroutine,
494 -- because 'Map.findWithDefault' is used instead of 'Map.!'.
495 , catchStackByLabel = Map.mapWithKey
496 (\lbl () -> NE.singleton [||Map.findWithDefault $$(defaultCatch ctx) lbl callCatchStackByLabel||])
497 ({-trace ("mayRaise: "<>show subName) $-}
499 , input = [||callInput||]
500 , returnCall = {-trace ("unGen.defLet.sub.returnCall: "<>show subName) $-} [||callReturn||]
502 -- These are passed by the caller via 'callReturn' or 'ko'
504 -- , farthestExpecting =
506 -- Some callers can call this 'defLet'
507 -- with zero 'checkedHorizon', hence use this minimum.
508 -- TODO: maybe it could be improved a bit
509 -- by taking the minimum of the checked horizons
510 -- before all the 'call's and 'jump's to this 'defLet'.
514 let decl = TH.FunD subName [TH.Clause [] (TH.NormalB body) []]
516 jump isRec (LetName subName) = Gen
517 { genAnalysisByLet = HM.empty
518 , genAnalysis = \final ->
522 , mayRaise = Map.empty
523 , freeRegs = Set.empty
525 else final HM.! subName
526 , unGen = \ctx -> {-trace ("unGen.jump: "<>show subName) $-}
527 let analysis = finalGenAnalysisByLet ctx HM.! subName in
530 $$(TH.unsafeCodeCoerce $
531 giveFreeRegs (freeRegs analysis) $
532 return (TH.VarE subName))
533 {-ok-}$$(returnCall ctx)
535 $$(liftTypedRaiseByLabel $
536 catchStackByLabel ctx
537 -- Pass only the labels raised by the 'defLet'.
543 call isRec (LetName subName) k = k
544 { genAnalysis = \final ->
548 , mayRaise = Map.empty
549 , freeRegs = Set.empty
551 else seqGenAnalysis $ (final HM.! subName) :| [ genAnalysis k final ]
552 , unGen = {-trace ("unGen.call: "<>show subName) $-} \ctx ->
553 -- let ks = (Map.keys (catchStackByLabel ctx)) in
554 let analysis = finalGenAnalysisByLet ctx HM.! subName in
556 -- let _ = $$(liftTypedString $ "call exceptByLet("<>show subName<>")="<>show (Map.keys (Map.findWithDefault Map.empty subName (exceptByLet ctx))) <> " catchStackByLabel(ctx)="<> show ks) in
557 $$(TH.unsafeCodeCoerce $
558 giveFreeRegs (freeRegs analysis) $
559 return (TH.VarE subName))
560 {-ok-}$$(generateSuspend k ctx)
562 $$(liftTypedRaiseByLabel $
563 -- FIXME: maybe it should rather pass all the 'mayRaise' of 'subName'
564 -- and 'defaultCatch' be removed from 'makeDecl''s 'catchStackByLabel'.
565 catchStackByLabel ctx
566 -- Pass only the labels raised by the 'defLet'.
573 { genAnalysisByLet = HM.empty
574 , genAnalysis = \_final -> GenAnalysis
576 , mayRaise = Map.empty
577 , freeRegs = Set.empty
579 , unGen = \ctx -> {-trace "unGen.ret" $-}
580 {-trace "unGen.ret.generateResume" $-}
581 generateResume ({-trace "unGen.ret.returnCall" $-} returnCall ctx) ctx
584 takeFreeRegs :: TH.Quote m => Set TH.Name -> m TH.Exp -> m TH.Exp
585 takeFreeRegs frs k = go (Set.toList frs)
588 go (r:rs) = [| \ $(return (TH.VarP r)) -> $(go rs) |]
590 giveFreeRegs :: TH.Quote m => Set TH.Name -> m TH.Exp -> m TH.Exp
591 giveFreeRegs frs k = go (Set.toList frs)
594 go (r:rs) = [| $(go rs) $(return (TH.VarE r)) |]
596 -- | Like 'TH.liftString' but on 'TH.Code'.
597 -- Useful to get a 'TH.StringL' instead of a 'TH.ListE'.
598 liftTypedString :: String -> TH.Code TH.Q a
599 liftTypedString = TH.unsafeCodeCoerce . TH.liftString
601 -- | Like 'TH.liftTyped' but adjusted to work on 'catchStackByLabel'
602 -- which already contains 'CodeQ' terms.
603 -- Moreover, only the 'Catcher' at the top of the stack
604 -- is needed and thus generated in the resulting 'CodeQ'.
606 -- TODO: Use an 'Array' instead of a 'Map'?
607 liftTypedRaiseByLabel :: TH.Lift k => Map k (NonEmpty (CodeQ a)) -> CodeQ (Map k a)
608 liftTypedRaiseByLabel Map_.Tip = [|| Map_.Tip ||]
609 liftTypedRaiseByLabel (Map_.Bin s k (h:|_hs) l r) =
610 [|| Map_.Bin s k $$h $$(liftTypedRaiseByLabel l) $$(liftTypedRaiseByLabel r) ||]
612 instance TH.Lift a => TH.Lift (Set a) where
613 liftTyped Set_.Tip = [|| Set_.Tip ||]
614 liftTyped (Set_.Bin s a l r) = [|| Set_.Bin $$(TH.liftTyped s) $$(TH.liftTyped a) $$(TH.liftTyped l) $$(TH.liftTyped r) ||]
617 type Return st inp v a =
618 {-farthestInput-}Cursor inp ->
619 {-farthestExpecting-}(Set SomeFailure) ->
622 ST st (Either (ParsingError inp) a)
624 -- | Generate a 'returnCall' 'Return'inuation to be called with 'generateResume'.
625 -- Used when 'call' 'ret'urns.
626 -- The return 'v'alue is 'pushValue'-ed on the 'valueStack'.
628 {-k-}Gen inp (v ': vs) a ->
629 GenCtx st inp vs a ->
630 CodeQ (Return st inp v a)
631 generateSuspend k ctx = [||
632 let _ = $$(liftTypedString $ "suspend") in
633 \farInp farExp v !inp ->
634 $$({-trace "unGen.generateSuspend" $-} unGen k ctx
635 { valueStack = ValueStackCons ({-trace "unGen.generateSuspend.value" $-} splice [||v||]) (valueStack ctx)
637 , farthestInput = [||farInp||]
638 , farthestExpecting = [||farExp||]
644 -- | Generate a call to the 'generateSuspend' continuation.
645 -- Used when 'call' 'ret'urns.
647 CodeQ (Return st inp v a) ->
648 GenCtx st inp (v ': vs) a ->
649 CodeQ (ST st (Either (ParsingError inp) a))
650 generateResume k = \ctx -> {-trace "generateResume" $-} [||
653 $$(farthestInput ctx)
654 $$(farthestExpecting ctx)
655 (let _ = "resume.genCode" in $$({-trace "generateResume.genCode" $-}
656 genCode $ valueStackHead $ valueStack ctx))
661 type Catcher st inp a =
663 {-failInp-}Cursor inp ->
664 {-farInp-}Cursor inp ->
665 {-farExp-}(Set SomeFailure) ->
666 ST st (Either (ParsingError inp) a)
668 instance InstrJoinable Gen where
669 defJoin (LetName n) sub k = k
671 {-trace ("unGen.defJoin: "<>show n) $-}
672 TH.unsafeCodeCoerce [|
673 let $(return (TH.VarP n)) = $(TH.unTypeQ $ TH.examineCode [||
674 -- Called by 'generateResume'.
675 \farInp farExp v !inp ->
676 $$({-trace ("unGen.defJoin.next: "<>show n) $-} unGen sub ctx
677 { valueStack = ValueStackCons (splice [||v||]) (valueStack ctx)
679 , farthestInput = [||farInp||]
680 , farthestExpecting = [||farExp||]
683 , catchStackByLabel = Map.mapWithKey
684 (\lbl () -> NE.singleton [||koByLabel Map.! lbl||])
685 (mayRaise sub raiseLabelsByLetButSub)
689 in $(TH.unTypeQ $ TH.examineCode $
690 {-trace ("unGen.defJoin.expr: "<>show n) $-}
694 (genAnalysisByLet sub <>) $
695 HM.insert n (genAnalysis sub) $
698 refJoin (LetName n) = Gen
700 {-trace ("unGen.refJoin: "<>show n) $-}
702 (TH.unsafeCodeCoerce (return (TH.VarE n))) ctx
703 , genAnalysisByLet = HM.empty
704 , genAnalysis = \final ->
706 (error (show (n,HM.keys final)))
709 instance InstrReadable Char Gen where
710 read fs p = checkHorizon . checkToken fs p
711 instance InstrReadable Word8 Gen where
712 read fs p = checkHorizon . checkToken fs p
713 instance InstrIterable Gen where
714 iter (LetName jumpName) loop done = Gen
715 { genAnalysisByLet = HM.unions
716 [ -- No need to give 'freeRegs' when 'call'ing 'jumpName'
717 -- they're passed when 'call'ing 'iter'.
718 HM.singleton jumpName (\final -> (genAnalysis loop final){freeRegs = Set.empty})
719 , genAnalysisByLet loop
720 , genAnalysisByLet done
722 , genAnalysis = \final ->
723 let loopAnalysis = genAnalysis loop final in
724 let doneAnalysis = genAnalysis done final in
726 { minReads = minReads doneAnalysis
728 Map.delete ExceptionFailure (mayRaise loopAnalysis) <>
729 mayRaise doneAnalysis
730 , freeRegs = freeRegs loopAnalysis <> freeRegs doneAnalysis
732 , unGen = \ctx -> TH.unsafeCodeCoerce [|
735 catchHandler loopInput !_exn !failInp !farInp !farExp =
736 $(TH.unTypeCode $ {-trace ("unGen.catch.ko: "<>show exn) $-} unGen done ctx
737 -- Push 'input' and 'checkedHorizon'
738 -- as they were when entering 'catch',
739 -- they will be available to 'loadInput', if any.
740 { valueStack = ValueStackCons (splice (TH.unsafeCodeCoerce [|loopInput|])) (valueStack ctx)
741 , horizonStack = checkedHorizon ctx : horizonStack ctx
742 -- Note that 'catchStackByLabel' is reset.
743 -- Move the input to the failing position.
744 , input = TH.unsafeCodeCoerce [|failInp|]
745 -- The 'checkedHorizon' at the 'raise's are not known here.
746 -- Nor whether 'failInp' is after 'checkedHorizon' or not.
747 -- Hence fallback to a safe value.
749 -- Set the farthestInput to the farthest computed in 'fail'.
750 , farthestInput = TH.unsafeCodeCoerce [|farInp|]
751 , farthestExpecting = TH.unsafeCodeCoerce [|farExp|]
753 $(return $ TH.VarP jumpName) = \_callReturn callInput callCatchStackByLabel ->
754 $(TH.unTypeCode $ unGen loop ctx
755 { valueStack = ValueStackEmpty
756 , catchStackByLabel =
759 (\lbl () -> NE.singleton $ TH.unsafeCodeCoerce [|
760 Map.findWithDefault $(TH.unTypeCode $ defaultCatch ctx) lbl koByLabel
762 (mayRaise (finalGenAnalysisByLet ctx HM.! jumpName))
764 Map.insertWith (<>) ExceptionFailure
765 (NE.singleton $ TH.unsafeCodeCoerce [|catchHandler callInput|])
766 (catchStackByLabel ctx)
767 , input = TH.unsafeCodeCoerce [|callInput|]
768 -- FIXME: promote to compile time error?
769 , returnCall = TH.unsafeCodeCoerce [|error "invalid return"|]
772 in $(TH.unTypeCode $ unGen (jump True (LetName jumpName)) ctx{valueStack=ValueStackEmpty})
775 instance InstrRegisterable Gen where
776 newRegister (UnscopedRegister r) k = k
777 { genAnalysis = \final ->
778 let analysis = genAnalysis k final in
779 analysis{freeRegs = Set.delete r $ freeRegs analysis}
781 let ValueStackCons v vs = valueStack ctx in
782 TH.unsafeCodeCoerce [|
784 let dupv = $(TH.unTypeCode $ genCode v)
785 $(return (TH.VarP r)) <- ST.newSTRef dupv
786 $(TH.unTypeCode $ unGen k ctx{valueStack=vs})
789 readRegister (UnscopedRegister r) k = k
790 { genAnalysis = \final ->
791 let analysis = genAnalysis k final in
792 analysis{freeRegs = Set.insert r $ freeRegs analysis}
793 , unGen = \ctx -> [|| do
794 sr <- ST.readSTRef $$(TH.unsafeCodeCoerce (return (TH.VarE r)))
795 $$(unGen k ctx{valueStack=ValueStackCons (splice [||sr||]) (valueStack ctx)})
798 writeRegister (UnscopedRegister r) k = k
799 { genAnalysis = \final ->
800 let analysis = genAnalysis k final in
801 analysis{freeRegs = Set.insert r $ freeRegs analysis}
803 let ValueStackCons v vs = valueStack ctx in
805 let dupv = $$(genCode v)
806 ST.writeSTRef $$(TH.unsafeCodeCoerce (return (TH.VarE r))) dupv
807 $$(unGen k ctx{valueStack=vs})
813 -- Those constraints are not used anyway
814 -- because (TH.Lift SomeFailure) uses 'inputTokenProxy'.
815 Ord (InputToken inp) =>
816 Show (InputToken inp) =>
817 TH.Lift (InputToken inp) =>
818 NFData (InputToken inp) =>
819 Typeable (InputToken inp) =>
820 {-ok-}Gen inp vs a ->
823 { genAnalysis = \final -> seqGenAnalysis $
824 GenAnalysis { minReads = Right 1
825 , mayRaise = Map.singleton ExceptionFailure ()
826 , freeRegs = Set.empty
828 [ genAnalysis ok final ]
829 , unGen = \ctx0@GenCtx{} ->
830 {-trace "unGen.checkHorizon" $-}
831 let raiseFail = raiseException ctx0 ExceptionFailure in
833 -- Factorize generated code for raising the "fail".
834 let readFail = $$(raiseFail) in
836 let ctx = ctx0{catchStackByLabel =
837 Map.adjust (\(_r:|rs) -> [||readFail||] :| rs)
838 ExceptionFailure (catchStackByLabel ctx0)} in
839 if checkedHorizon ctx >= 1
840 then unGen ok ctx0{checkedHorizon = checkedHorizon ctx - 1}
842 either (\_err -> 0) id $
843 minReads $ finalGenAnalysis ctx ok in
847 then [||$$shiftRight minHoriz $$(input ctx)||]
849 then $$(unGen ok ctx{checkedHorizon = minHoriz})
850 else let _ = "checkHorizon.else" in
851 -- TODO: return a resuming continuation (eg. Partial)
852 $$(unGen (fail (Set.singleton $ SomeFailure $ FailureHorizon @(InputToken inp) (minHoriz + 1))) ctx)
858 -- | @('raiseFailure' ctx fs)@ raises 'ExceptionFailure'
859 -- with farthest parameters set to or updated with @(fs)@
860 -- according to the relative position of 'input' wrt. 'farthestInput'.
862 Cursorable (Cursor inp) =>
863 GenCtx st inp cs a ->
864 TH.CodeQ (Set SomeFailure) ->
865 TH.CodeQ (ST st (Either (ParsingError inp) a))
866 raiseFailure ctx fs = [||
867 let failExp = $$fs in
868 let (# farInp, farExp #) =
869 case $$compareOffset $$(farthestInput ctx) $$(input ctx) of
870 LT -> (# $$(input ctx), failExp #)
871 EQ -> (# $$(farthestInput ctx), failExp <> $$(farthestExpecting ctx) #)
872 GT -> (# $$(farthestInput ctx), $$(farthestExpecting ctx) #)
873 in $$(raiseException ctx ExceptionFailure)
875 {-failInp-}$$(input ctx) farInp farExp
877 -- | @('raiseException' ctx exn)@ raises exception @(exn)@
878 -- using any entry in 'catchStackByLabel', or 'defaultCatch' if none.
880 GenCtx st inp vs a -> Exception ->
881 CodeQ (Exception -> Cursor inp -> Cursor inp -> Set SomeFailure -> ST st (Either (ParsingError inp) a))
882 raiseException ctx exn =
883 NE.head $ Map.findWithDefault
884 (NE.singleton (defaultCatch ctx))
885 exn (catchStackByLabel ctx)
887 finalGenAnalysis :: GenCtx st inp vs a -> Gen inp cs a -> GenAnalysis
888 finalGenAnalysis ctx k = genAnalysis k $ finalGenAnalysisByLet ctx
892 {-predicate-}Splice (InputToken inp -> Bool) ->
893 {-ok-}Gen inp (InputToken inp ': vs) a ->
895 checkToken fs p ok = ok
896 { unGen = \ctx -> {-trace "unGen.read" $-} [||
897 let !(# c, cs #) = $$(nextInput ctx) $$(input ctx) in
900 (p Prod..@ splice [||c||])
901 (splice $ unGen ok ctx
902 { valueStack = ValueStackCons (splice [||c||]) (valueStack ctx)
906 let _ = "checkToken.else" in
907 $$(unGen (fail fs) ctx)