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 ()
203 -- | Minimal input length required for a successful parsing.
204 type Horizon = Offset
206 -- altGenAnalysis = List.foldl' (\acc x -> either Left (\h -> Right (either (const h) (min h) acc)) x)
207 -- | Merge given 'GenAnalysis' as sequences.
208 seqGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
209 seqGenAnalysis aas@(a:|as) = GenAnalysis
210 { minReads = List.foldl' (\acc x ->
211 acc >>= \r -> (r +) <$> minReads x
213 , mayRaise = sconcat (mayRaise <$> aas)
215 -- | Merge given 'GenAnalysis' as alternatives.
216 altGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
217 altGenAnalysis aas@(a:|as) = GenAnalysis
218 { minReads = List.foldl' (\acc x ->
227 Right r' -> Right (min r r')
229 , mayRaise = sconcat (mayRaise <$> aas)
234 -- *** Type 'FarthestError'
235 data FarthestError inp = FarthestError
236 { farthestInput :: Cursor inp
237 , farthestExpecting :: [Failure (InputToken inp)]
242 -- | This is an inherited (top-down) context
243 -- only present at compile-time, to build TemplateHaskell splices.
244 data GenCtx st inp vs a =
245 ( Cursorable (Cursor inp)
247 , TH.Lift (InputToken inp)
248 , Show (InputToken inp)
249 , Eq (InputToken inp)
250 , Typeable (InputToken inp)
251 , NFData (InputToken inp)
254 { valueStack :: ValueStack vs
255 , catchStackByLabel :: Map Exception (NonEmpty (CodeQ (Catcher st inp a)))
256 -- | Default 'Catcher' defined at the begining of the generated 'CodeQ',
257 -- hence a constant within the 'Gen'eration.
258 , defaultCatch :: forall b. CodeQ (Catcher st inp b)
259 , returnCall :: CodeQ (Return st inp a a)
260 , input :: CodeQ (Cursor inp)
261 , moreInput :: CodeQ (Cursor inp -> Bool)
262 , nextInput :: CodeQ (Cursor inp -> (# InputToken inp, Cursor inp #))
263 , farthestInput :: CodeQ (Cursor inp)
264 , farthestExpecting :: CodeQ (Set SomeFailure)
265 -- | Remaining horizon already checked.
266 -- Use to factorize 'input' length checks,
267 -- instead of checking the 'input' length
268 -- one 'InputToken' at a time at each 'read'.
269 -- Updated by 'checkHorizon'
270 -- and reset elsewhere when needed.
271 , checkedHorizon :: Horizon
272 -- | Used by 'pushInput' and 'loadInput'
273 -- to restore the 'Horizon' at the restored 'input'.
274 , horizonStack :: [Horizon]
275 -- | Output of 'mutualFix'.
276 , finalGenAnalysisByLet :: LetRecs TH.Name GenAnalysis
279 -- ** Type 'ValueStack'
280 data ValueStack vs where
281 ValueStackEmpty :: ValueStack '[]
283 { valueStackHead :: Splice v
284 , valueStackTail :: ValueStack vs
285 } -> ValueStack (v ': vs)
287 instance InstrValuable Gen where
289 { unGen = \ctx -> {-trace "unGen.pushValue" $-} unGen k ctx
290 { valueStack = ValueStackCons x (valueStack ctx) }
293 { unGen = \ctx -> {-trace "unGen.popValue" $-} unGen k ctx
294 { valueStack = valueStackTail (valueStack ctx) }
297 { unGen = \ctx -> {-trace "unGen.lift2Value" $-} unGen k ctx
299 let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
300 ValueStackCons (f Prod..@ x Prod..@ y) vs
304 { unGen = \ctx -> {-trace "unGen.swapValue" $-} unGen k ctx
306 let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
307 ValueStackCons x (ValueStackCons y vs)
310 instance InstrBranchable Gen where
311 caseBranch kx ky = Gen
312 { genAnalysisByLet = genAnalysisByLet kx <> genAnalysisByLet ky
313 , genAnalysis = \final -> altGenAnalysis $ genAnalysis kx final :| [genAnalysis ky final]
314 , unGen = \ctx -> {-trace "unGen.caseBranch" $-}
315 let ValueStackCons v vs = valueStack ctx in
317 case $$(genCode v) of
318 Left x -> $$(unGen kx ctx{ valueStack = ValueStackCons (splice [||x||]) vs })
319 Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (splice [||y||]) vs })
322 choicesBranch bs default_ = Gen
323 { genAnalysisByLet = sconcat $ genAnalysisByLet default_ :| (genAnalysisByLet . snd <$> bs)
324 , genAnalysis = \final -> altGenAnalysis $
325 (\k -> genAnalysis k final)
326 <$> (default_:|(snd <$> bs))
327 , unGen = \ctx0 -> {-trace "unGen.choicesBranch" $-}
328 let ValueStackCons v vs = valueStack ctx0 in
329 let ctx = ctx0{valueStack = vs} in
331 go x ((p,b):bs') = [||
332 if $$(genCode (p Prod..@ x))
334 let _ = "choicesBranch.then" in
335 $$({-trace "unGen.choicesBranch.b" $-} unGen b ctx)
337 let _ = "choicesBranch.else" in
340 go _ _ = unGen default_ ctx
343 instance InstrExceptionable Gen where
345 { genAnalysisByLet = HM.empty
346 , genAnalysis = \_final -> GenAnalysis
347 { minReads = Left (ExceptionLabel exn)
348 , mayRaise = Map.singleton (ExceptionLabel exn) ()
350 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.raise: "<>show exn) $-} [||
351 $$(raiseException ctx (ExceptionLabel exn))
352 (ExceptionLabel $$(TH.liftTyped exn))
353 {-failInp-}$$(input ctx)
354 {-farInp-}$$(input ctx)
355 $$(farthestExpecting ctx)
359 { genAnalysisByLet = HM.empty
360 , genAnalysis = \_final -> GenAnalysis
361 { minReads = Left ExceptionFailure
362 , mayRaise = Map.singleton ExceptionFailure ()
364 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.fail: "<>show exn) $-}
366 then [|| -- Raise without updating the farthest error.
367 $$(raiseException ctx ExceptionFailure)
369 {-failInp-}$$(input ctx)
370 $$(farthestInput ctx)
371 $$(farthestExpecting ctx)
373 else raiseFailure ctx [||fs||]
376 { unGen = \ctx -> {-trace ("unGen.commit: "<>show exn) $-}
377 unGen k ctx{catchStackByLabel =
379 _r0:|(r1:rs) -> Just (r1:|rs)
382 exn (catchStackByLabel ctx)
385 catch exn ok ko = Gen
386 { genAnalysisByLet = genAnalysisByLet ok <> genAnalysisByLet ko
387 , genAnalysis = \final ->
388 let okGA = genAnalysis ok final in
390 okGA{ mayRaise = Map.delete exn (mayRaise okGA) } :|
391 [ genAnalysis ko final ]
392 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.catch: "<>show exn) $-} [||
393 let _ = $$(liftTypedString ("catch "<>show exn)) in
394 let catchHandler !_exn !failInp !farInp !farExp =
395 let _ = $$(liftTypedString ("catch.ko "<>show exn)) in
396 $$({-trace ("unGen.catch.ko: "<>show exn) $-} unGen ko ctx
397 -- Push 'input' and 'checkedHorizon'
398 -- as they were when entering 'catch',
399 -- they will be available to 'loadInput', if any.
401 ValueStackCons (splice (input ctx)) $
402 --ValueStackCons (Prod.var [||exn||]) $
405 checkedHorizon ctx : horizonStack ctx
406 -- Note that 'catchStackByLabel' is reset.
407 -- Move the input to the failing position.
408 , input = [||failInp||]
409 -- The 'checkedHorizon' at the 'raise's are not known here.
410 -- Nor whether 'failInp' is after 'checkedHorizon' or not.
411 -- Hence fallback to a safe value.
413 -- Set the farthestInput to the farthest computed in 'fail'.
414 , farthestInput = [||farInp||]
415 , farthestExpecting = [||farExp||]
418 $$({-trace ("unGen.catch.ok: "<>show es) $-} unGen ok ctx
419 { catchStackByLabel =
420 Map.insertWith (<>) exn
421 (NE.singleton [||catchHandler||])
422 (catchStackByLabel ctx)
426 instance InstrInputable Gen where
429 {-trace "unGen.pushInput" $-}
431 { valueStack = splice (input ctx) `ValueStackCons` valueStack ctx
432 , horizonStack = checkedHorizon ctx : horizonStack ctx
437 {-trace "unGen.loadInput" $-}
438 let ValueStackCons input vs = valueStack ctx in
439 let (h, hs) = case horizonStack ctx of
445 , input = genCode input
448 , genAnalysis = \final -> GenAnalysis
449 { minReads = 0 <$ minReads (genAnalysis k final)
450 , mayRaise = mayRaise (genAnalysis k final)
453 instance InstrCallable Gen where
455 { unGen = \ctx@GenCtx{} ->
456 {-trace ("unGen.defLet: defs="<>show (HM.keys defs)) $-}
457 TH.unsafeCodeCoerce $ do
458 decls <- traverse (makeDecl ctx) (HM.toList defs)
459 body <- TH.unTypeQ $ TH.examineCode $
460 {-trace "unGen.defLet.body" $-}
463 -- | Use 'List.sortBy' to output more deterministic code
464 -- to be able to golden test it, at the cost of more computations
465 -- (at compile-time only though).
466 List.sortBy (compare `on` TH.hideName) $
472 : ((\(SomeLet sub) -> genAnalysis sub) <$> defs)
473 : ((\(SomeLet sub) -> genAnalysisByLet sub) <$> HM.elems defs)
476 makeDecl ctx (subName, SomeLet sub) = do
477 body <- TH.unTypeQ $ TH.examineCode $ [|| -- buildRec in Parsley
478 -- TODO: takeFreeRegisters
479 -- Called by 'call' or 'jump'.
480 \ !callReturn{-from generateSuspend or returnCall-}
482 !callCatchStackByLabel{- 'catchStackByLabel' from the 'call'-site -} ->
483 $$({-trace ("unGen.defLet.sub: "<>show subName) $-} unGen sub ctx
484 { valueStack = ValueStackEmpty
485 -- Build a 'catchStackByLabel' for the 'mayRaise' of the subroutine,
486 -- where each 'Catcher' calls the one passed by the 'call'-site (in 'callCatchStackByLabel').
487 -- Note that currently the 'call'-site can supply in 'callCatchStackByLabel'
488 -- a subset of the 'mayRaise' needed by this subroutine,
489 -- because 'Map.findWithDefault' is used instead of 'Map.!'.
490 , catchStackByLabel = Map.mapWithKey
491 (\lbl () -> NE.singleton [||Map.findWithDefault $$(defaultCatch ctx) lbl callCatchStackByLabel||])
492 ({-trace ("mayRaise: "<>show subName) $-}
493 mayRaise (finalGenAnalysisByLet ctx HM.! subName))
494 , input = [||callInput||]
495 , returnCall = {-trace ("unGen.defLet.sub.returnCall: "<>show subName) $-} [||callReturn||]
497 -- These are passed by the caller via 'callReturn' or 'ko'
499 -- , farthestExpecting =
501 -- Some callers can call this 'defLet'
502 -- with zero 'checkedHorizon', hence use this minimum.
503 -- TODO: maybe it could be improved a bit
504 -- by taking the minimum of the checked horizons
505 -- before all the 'call's and 'jump's to this 'defLet'.
509 let decl = TH.FunD subName [TH.Clause [] (TH.NormalB body) []]
511 jump isRec (LetName n) = Gen
512 { genAnalysisByLet = HM.empty
513 , genAnalysis = \final ->
517 , mayRaise = Map.empty
520 , unGen = \ctx -> {-trace ("unGen.jump: "<>show n) $-} [||
522 $$(TH.unsafeCodeCoerce (return (TH.VarE n)))
523 {-ok-}$$(returnCall ctx)
525 $$(liftTypedRaiseByLabel $
526 catchStackByLabel ctx
527 -- Pass only the labels raised by the 'defLet'.
529 (mayRaise $ finalGenAnalysisByLet ctx HM.! n)
533 call isRec (LetName n) k = k
534 { genAnalysis = \final ->
538 , mayRaise = Map.empty
540 else seqGenAnalysis $
542 [ genAnalysis k final ]
543 , unGen = {-trace ("unGen.call: "<>show n) $-} \ctx ->
544 -- let ks = (Map.keys (catchStackByLabel ctx)) in
546 -- let _ = $$(liftTypedString $ "call exceptByLet("<>show n<>")="<>show (Map.keys (Map.findWithDefault Map.empty n (exceptByLet ctx))) <> " catchStackByLabel(ctx)="<> show ks) in
547 $$(TH.unsafeCodeCoerce (return (TH.VarE n)))
548 {-ok-}$$(generateSuspend k ctx)
550 $$(liftTypedRaiseByLabel $
551 -- FIXME: maybe it should rather pass all the 'mayRaise' of 'n'
552 -- and 'defaultCatch' be removed from 'makeDecl''s 'catchStackByLabel'.
553 catchStackByLabel ctx
554 -- Pass only the labels raised by the 'defLet'.
556 (mayRaise $ finalGenAnalysisByLet ctx HM.! n)
561 { genAnalysisByLet = HM.empty
562 , genAnalysis = \_final -> GenAnalysis
564 , mayRaise = Map.empty
566 , unGen = \ctx -> {-trace "unGen.ret" $-}
567 {-trace "unGen.ret.generateResume" $-}
568 generateResume ({-trace "unGen.ret.returnCall" $-} returnCall ctx) ctx
571 -- | Like 'TH.liftString' but on 'TH.Code'.
572 -- Useful to get a 'TH.StringL' instead of a 'TH.ListE'.
573 liftTypedString :: String -> TH.Code TH.Q a
574 liftTypedString = TH.unsafeCodeCoerce . TH.liftString
576 -- | Like 'TH.liftTyped' but adjusted to work on 'catchStackByLabel'
577 -- which already contains 'CodeQ' terms.
578 -- Moreover, only the 'Catcher' at the top of the stack
579 -- is needed and thus generated in the resulting 'CodeQ'.
581 -- TODO: Use an 'Array' instead of a 'Map'?
582 liftTypedRaiseByLabel :: TH.Lift k => Map k (NonEmpty (CodeQ a)) -> CodeQ (Map k a)
583 liftTypedRaiseByLabel Map_.Tip = [|| Map_.Tip ||]
584 liftTypedRaiseByLabel (Map_.Bin s k (h:|_hs) l r) =
585 [|| Map_.Bin s k $$h $$(liftTypedRaiseByLabel l) $$(liftTypedRaiseByLabel r) ||]
587 instance TH.Lift a => TH.Lift (Set a) where
588 liftTyped Set_.Tip = [|| Set_.Tip ||]
589 liftTyped (Set_.Bin s a l r) = [|| Set_.Bin $$(TH.liftTyped s) $$(TH.liftTyped a) $$(TH.liftTyped l) $$(TH.liftTyped r) ||]
592 type Return st inp v a =
593 {-farthestInput-}Cursor inp ->
594 {-farthestExpecting-}(Set SomeFailure) ->
597 ST st (Either (ParsingError inp) a)
599 -- | Generate a 'returnCall' 'Return'inuation to be called with 'generateResume'.
600 -- Used when 'call' 'ret'urns.
601 -- The return 'v'alue is 'pushValue'-ed on the 'valueStack'.
603 {-k-}Gen inp (v ': vs) a ->
604 GenCtx st inp vs a ->
605 CodeQ (Return st inp v a)
606 generateSuspend k ctx = [||
607 let _ = $$(liftTypedString $ "suspend") in
608 \farInp farExp v !inp ->
609 $$({-trace "unGen.generateSuspend" $-} unGen k ctx
610 { valueStack = ValueStackCons ({-trace "unGen.generateSuspend.value" $-} splice [||v||]) (valueStack ctx)
612 , farthestInput = [||farInp||]
613 , farthestExpecting = [||farExp||]
619 -- | Generate a call to the 'generateSuspend' continuation.
620 -- Used when 'call' 'ret'urns.
622 CodeQ (Return st inp v a) ->
623 GenCtx st inp (v ': vs) a ->
624 CodeQ (ST st (Either (ParsingError inp) a))
625 generateResume k = \ctx -> {-trace "generateResume" $-} [||
628 $$(farthestInput ctx)
629 $$(farthestExpecting ctx)
630 (let _ = "resume.genCode" in $$({-trace "generateResume.genCode" $-}
631 genCode $ valueStackHead $ valueStack ctx))
636 type Catcher st inp a =
638 {-failInp-}Cursor inp ->
639 {-farInp-}Cursor inp ->
640 {-farExp-}(Set SomeFailure) ->
641 ST st (Either (ParsingError inp) a)
643 instance InstrJoinable Gen where
644 defJoin (LetName n) sub k = k
646 {-trace ("unGen.defJoin: "<>show n) $-}
647 TH.unsafeCodeCoerce [|
648 let $(return (TH.VarP n)) = $(TH.unTypeQ $ TH.examineCode [||
649 -- Called by 'generateResume'.
650 \farInp farExp v !inp ->
651 $$({-trace ("unGen.defJoin.next: "<>show n) $-} unGen sub ctx
652 { valueStack = ValueStackCons (splice [||v||]) (valueStack ctx)
654 , farthestInput = [||farInp||]
655 , farthestExpecting = [||farExp||]
658 , catchStackByLabel = Map.mapWithKey
659 (\lbl () -> NE.singleton [||koByLabel Map.! lbl||])
660 (mayRaise sub raiseLabelsByLetButSub)
664 in $(TH.unTypeQ $ TH.examineCode $
665 {-trace ("unGen.defJoin.expr: "<>show n) $-}
669 (genAnalysisByLet sub <>) $
670 HM.insert n (genAnalysis sub) $
673 refJoin (LetName n) = Gen
675 {-trace ("unGen.refJoin: "<>show n) $-}
677 (TH.unsafeCodeCoerce (return (TH.VarE n))) ctx
678 , genAnalysisByLet = HM.empty
679 , genAnalysis = \final ->
681 (error (show (n,HM.keys final)))
684 instance InstrReadable Char Gen where
685 read fs p = checkHorizon . checkToken fs p
686 instance InstrReadable Word8 Gen where
687 read fs p = checkHorizon . checkToken fs p
688 instance InstrIterable Gen where
689 iter (LetName jumpName) loop done = Gen
690 { genAnalysisByLet = HM.unions
691 [ HM.singleton jumpName (genAnalysis loop)
692 , genAnalysisByLet loop
693 , genAnalysisByLet done
695 , genAnalysis = \final -> GenAnalysis
696 { minReads = minReads (genAnalysis done final)
698 Map.delete ExceptionFailure
699 (mayRaise (genAnalysis loop final)) <>
700 mayRaise (genAnalysis done final)
702 , unGen = \ctx -> TH.unsafeCodeCoerce [|
707 {-failInp-}Cursor inp ->
708 {-farInp-}Cursor inp ->
709 {-farExp-}(Set SomeFailure) ->
710 ST st (Either (ParsingError inp) a)
712 catchHandler loopInput !_exn !failInp !farInp !farExp =
713 $(TH.unTypeCode $ {-trace ("unGen.catch.ko: "<>show exn) $-} unGen done ctx
714 -- Push 'input' and 'checkedHorizon'
715 -- as they were when entering 'catch',
716 -- they will be available to 'loadInput', if any.
717 { valueStack = ValueStackCons (splice (TH.unsafeCodeCoerce [|loopInput|])) (valueStack ctx)
718 , horizonStack = checkedHorizon ctx : horizonStack ctx
719 -- Note that 'catchStackByLabel' is reset.
720 -- Move the input to the failing position.
721 , input = TH.unsafeCodeCoerce [|failInp|]
722 -- The 'checkedHorizon' at the 'raise's are not known here.
723 -- Nor whether 'failInp' is after 'checkedHorizon' or not.
724 -- Hence fallback to a safe value.
726 -- Set the farthestInput to the farthest computed in 'fail'.
727 , farthestInput = TH.unsafeCodeCoerce [|farInp|]
728 , farthestExpecting = TH.unsafeCodeCoerce [|farExp|]
730 $(return $ TH.VarP jumpName) = \_callReturn callInput callCatchStackByLabel ->
731 $(TH.unTypeCode $ unGen loop ctx
732 { valueStack = ValueStackEmpty
733 , catchStackByLabel =
736 (\lbl () -> NE.singleton $ TH.unsafeCodeCoerce [|
737 Map.findWithDefault $(TH.unTypeCode $ defaultCatch ctx) lbl koByLabel
739 (mayRaise (finalGenAnalysisByLet ctx HM.! jumpName))
741 Map.insertWith (<>) ExceptionFailure
742 (NE.singleton $ TH.unsafeCodeCoerce [|catchHandler callInput|])
743 (catchStackByLabel ctx)
744 , input = TH.unsafeCodeCoerce [|callInput|]
745 -- FIXME: promote to compile time error?
746 , returnCall = TH.unsafeCodeCoerce [|error "invalid return"|]
749 in $(TH.unTypeCode $ unGen (jump True (LetName jumpName)) ctx{valueStack=ValueStackEmpty})
752 instance InstrRegisterable Gen where
753 newRegister (UnscopedRegister r) k = k
755 let ValueStackCons v vs = valueStack ctx in
756 TH.unsafeCodeCoerce [|
758 let dupv = $(TH.unTypeCode $ genCode v)
759 $(return (TH.VarP r)) <- ST.newSTRef dupv
760 $(TH.unTypeCode $ unGen k ctx{valueStack=vs})
763 readRegister (UnscopedRegister r) k = k
764 { unGen = \ctx -> [|| do
765 sr <- ST.readSTRef $$(TH.unsafeCodeCoerce (return (TH.VarE r)))
766 $$(unGen k ctx{valueStack=ValueStackCons (splice [||sr||]) (valueStack ctx)})
769 writeRegister (UnscopedRegister r) k = k
771 let ValueStackCons v vs = valueStack ctx in
773 let dupv = $$(genCode v)
774 ST.writeSTRef $$(TH.unsafeCodeCoerce (return (TH.VarE r))) dupv
775 $$(unGen k ctx{valueStack=vs})
781 -- Those constraints are not used anyway
782 -- because (TH.Lift SomeFailure) uses 'inputTokenProxy'.
783 Ord (InputToken inp) =>
784 Show (InputToken inp) =>
785 TH.Lift (InputToken inp) =>
786 NFData (InputToken inp) =>
787 Typeable (InputToken inp) =>
788 {-ok-}Gen inp vs a ->
791 { genAnalysis = \final -> seqGenAnalysis $
792 GenAnalysis { minReads = Right 1
793 , mayRaise = Map.singleton ExceptionFailure ()
795 [ genAnalysis ok final ]
796 , unGen = \ctx0@GenCtx{} ->
797 {-trace "unGen.checkHorizon" $-}
798 let raiseFail = raiseException ctx0 ExceptionFailure in
800 -- Factorize generated code for raising the "fail".
801 let readFail = $$(raiseFail) in
803 let ctx = ctx0{catchStackByLabel =
804 Map.adjust (\(_r:|rs) -> [||readFail||] :| rs)
805 ExceptionFailure (catchStackByLabel ctx0)} in
806 if checkedHorizon ctx >= 1
807 then unGen ok ctx0{checkedHorizon = checkedHorizon ctx - 1}
809 either (\_err -> 0) id $
810 minReads $ finalGenAnalysis ctx ok in
814 then [||$$shiftRight minHoriz $$(input ctx)||]
816 then $$(unGen ok ctx{checkedHorizon = minHoriz})
817 else let _ = "checkHorizon.else" in
818 -- TODO: return a resuming continuation (eg. Partial)
819 $$(unGen (fail (Set.singleton $ SomeFailure $ FailureHorizon @(InputToken inp) (minHoriz + 1))) ctx)
825 -- | @('raiseFailure' ctx fs)@ raises 'ExceptionFailure'
826 -- with farthest parameters set to or updated with @(fs)@
827 -- according to the relative position of 'input' wrt. 'farthestInput'.
829 Cursorable (Cursor inp) =>
830 GenCtx st inp cs a ->
831 TH.CodeQ (Set SomeFailure) ->
832 TH.CodeQ (ST st (Either (ParsingError inp) a))
833 raiseFailure ctx fs = [||
834 let failExp = $$fs in
835 let (# farInp, farExp #) =
836 case $$compareOffset $$(farthestInput ctx) $$(input ctx) of
837 LT -> (# $$(input ctx), failExp #)
838 EQ -> (# $$(farthestInput ctx), failExp <> $$(farthestExpecting ctx) #)
839 GT -> (# $$(farthestInput ctx), $$(farthestExpecting ctx) #)
840 in $$(raiseException ctx ExceptionFailure)
842 {-failInp-}$$(input ctx) farInp farExp
844 -- | @('raiseException' ctx exn)@ raises exception @(exn)@
845 -- using any entry in 'catchStackByLabel', or 'defaultCatch' if none.
847 GenCtx st inp vs a -> Exception ->
848 CodeQ (Exception -> Cursor inp -> Cursor inp -> Set SomeFailure -> ST st (Either (ParsingError inp) a))
849 raiseException ctx exn =
850 NE.head $ Map.findWithDefault
851 (NE.singleton (defaultCatch ctx))
852 exn (catchStackByLabel ctx)
854 finalGenAnalysis :: GenCtx st inp vs a -> Gen inp cs a -> GenAnalysis
855 finalGenAnalysis ctx k = genAnalysis k $ finalGenAnalysisByLet ctx
859 {-predicate-}Splice (InputToken inp -> Bool) ->
860 {-ok-}Gen inp (InputToken inp ': vs) a ->
862 checkToken fs p ok = ok
863 { unGen = \ctx -> {-trace "unGen.read" $-} [||
864 let !(# c, cs #) = $$(nextInput ctx) $$(input ctx) in
867 (p Prod..@ splice [||c||])
868 (splice $ unGen ok ctx
869 { valueStack = ValueStackCons (splice [||c||]) (valueStack ctx)
873 let _ = "checkToken.else" in
874 $$(unGen (fail fs) ctx)