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(..), otherwise)
18 import Data.Char (Char)
19 import Data.Either (Either(..))
20 import Data.Eq (Eq(..))
21 import Data.Foldable (foldr, toList, null)
22 import Data.Function (($), (.), on)
23 import Data.Functor ((<$>))
25 import Data.List.NonEmpty (NonEmpty(..))
27 import Data.Maybe (Maybe(..))
28 import Data.Ord (Ord(..), Ordering(..))
29 import Data.Proxy (Proxy(..))
30 import Data.Semigroup (Semigroup(..))
32 import Data.String (String)
33 import Data.Traversable (Traversable(..))
34 import Data.Tuple (snd)
35 import Data.Typeable (Typeable)
36 import Data.Word (Word8)
37 import GHC.Generics (Generic)
38 import GHC.Show (showCommaSpace)
39 import Language.Haskell.TH (CodeQ)
40 import Prelude ((+), (-), error)
41 import Text.Show (Show(..), showParen, showString)
42 import qualified Data.HashMap.Strict as HM
43 import qualified Data.List as List
44 import qualified Data.List.NonEmpty as NE
45 import qualified Data.Map.Internal as Map_
46 import qualified Data.Map.Strict as Map
47 import qualified Data.Set as Set
48 import qualified Data.Set.Internal as Set_
49 import qualified Data.STRef as ST
50 import qualified Language.Haskell.TH as TH
51 import qualified Language.Haskell.TH.Syntax as TH
53 import qualified Symantic.Data as Sym
54 import Symantic.Derive
55 import Symantic.ObserveSharing
56 import qualified Symantic.Parser.Grammar as Gram
57 import Symantic.Parser.Grammar.Combinators
58 ( UnscopedRegister(..)
65 import Symantic.Parser.Machine.Input
66 import Symantic.Parser.Machine.Instructions
67 import qualified Language.Haskell.TH.HideName as TH
68 import qualified Symantic.Class as Prod
69 import qualified Symantic.Optimize as Prod
73 -- | Convenient utility to generate some final 'TH.CodeQ'.
74 genCode :: Splice a -> CodeQ a
75 genCode = derive . Prod.normalOrderReduction
78 -- | Generate the 'CodeQ' parsing the input.
79 data Gen inp vs a = Gen
80 { genAnalysisByLet :: OpenRecs TH.Name GenAnalysis
81 -- ^ 'genAnalysis' for each 'defLet' and 'defJoin' of the 'Machine'.
82 , genAnalysis :: OpenRec TH.Name GenAnalysis
83 -- ^ Synthetized (bottom-up) static genAnalysis of the 'Machine'.
86 CodeQ (ST st (Either (ParsingError inp) a))
89 {-# INLINE returnST #-}
90 returnST :: forall s a. a -> ST s a
91 returnST = return @(ST s)
93 -- | @('generateCode' input mach)@ generates @TemplateHaskell@ code
94 -- parsing the given 'input' according to the given 'Machine'.
96 -- Not really used constraints,
97 -- just to please 'checkHorizon'.
98 Ord (InputToken inp) =>
99 Show (InputToken inp) =>
100 TH.Lift (InputToken inp) =>
101 NFData (InputToken inp) =>
102 Typeable (InputToken inp) =>
106 CodeQ (inp -> Either (ParsingError inp) a)
108 let Gen{unGen=k, ..} = checkHorizon gen in
109 [|| \(input :: inp) ->
110 -- Pattern bindings containing unlifted types
111 -- should use an outermost bang pattern.
112 let !(# init, readMore, readNext #) = $$(cursorOf [||input||])
113 finalRet = \_farInp _farExp v _inp -> returnST $ Right v
114 finalRaise :: forall st b. (OnException st inp b)
115 = \ !exn _failInp !farInp !farExp ->
116 returnST $ Left ParsingError
117 { parsingErrorOffset = offset farInp
118 , parsingErrorException = exn
119 , parsingErrorUnexpected =
121 then Just (let (# c, _ #) = readNext farInp in c)
123 , parsingErrorExpecting =
124 let (minHoriz, res) =
125 Set.foldr (\f (minH, acc) ->
126 case unSomeFailure f of
127 Just (FailureHorizon h :: Failure (Gram.CombSatisfiable (InputToken inp)))
128 | Just old <- minH -> (Just (min old h), acc)
129 | otherwise -> (Just h, acc)
131 ) (Nothing, []) farExp in
132 Set.fromList $ case minHoriz of
133 Just h -> SomeFailure (FailureHorizon @(InputToken inp) h) : res
138 -- | Defines 'inputTokenProxy' so that the TemplateHaskell code
139 -- can refer to @(InputToken inp)@ through it.
140 defInputTokenProxy :: TH.CodeQ a -> TH.CodeQ a
141 defInputTokenProxy exprCode =
142 TH.unsafeCodeCoerce [|
143 let $(return (TH.VarP inputTokenProxy)) = Proxy :: Proxy (InputToken inp) in
144 $(TH.unTypeQ (TH.examineCode exprCode))
149 { valueStack = ValueStackEmpty
150 , onExceptionStackByLabel = Map.empty :: Map Exception (NonEmpty (TH.CodeQ (OnException s inp a)))
151 , defaultCatch = [||finalRaise||]
152 , onReturn = [||finalRet||] :: CodeQ (OnReturn s inp a a)
154 , nextInput = [||readNext||]
155 , moreInput = [||readMore||]
156 -- , farthestError = [||Nothing||]
157 , farthestInput = [||init||]
158 , farthestExpecting = [||Set.empty||]
160 , analysisByLet = mutualFix genAnalysisByLet
165 -- ** Type 'ParsingError'
166 data ParsingError inp
168 { parsingErrorOffset :: Offset
169 , parsingErrorException :: Exception
170 -- | Note: if a 'FailureHorizon' greater than 1
171 -- is amongst the 'parsingErrorExpecting'
172 -- then 'parsingErrorUnexpected' is only the 'InputToken'
173 -- at the begining of the expected 'Horizon'.
174 , parsingErrorUnexpected :: Maybe (InputToken inp)
175 , parsingErrorExpecting :: Set SomeFailure
177 deriving instance NFData (InputToken inp) => NFData (ParsingError inp)
178 --deriving instance Show (InputToken inp) => Show (ParsingError inp)
179 instance Show (InputToken inp) => Show (ParsingError inp) where
180 showsPrec p ParsingError{..} =
181 showParen (p >= 11) $
182 showString "ParsingErrorStandard {" .
183 showString "parsingErrorOffset = " .
184 showsPrec 0 parsingErrorOffset .
186 showString "parsingErrorException = " .
187 showsPrec 0 parsingErrorException .
189 showString "parsingErrorUnexpected = " .
190 showsPrec 0 parsingErrorUnexpected .
192 showString "parsingErrorExpecting = fromList " .
194 -- Sort on the string representation
195 -- because the 'Ord' of the 'SomeFailure'
196 -- is based upon hashes ('typeRepFingerprint')
197 -- depending on packages' ABI and whether
198 -- cabal-install's setup is --inplace or not,
199 -- and that would be too unstable for golden tests.
200 List.sortBy (compare `on` show) $
201 Set.toList parsingErrorExpecting
205 -- ** Type 'ErrorLabel'
206 type ErrorLabel = String
208 -- * Type 'GenAnalysis'
209 data GenAnalysis = GenAnalysis
210 { minReads :: Horizon
211 -- ^ The minimun number of input tokens to read
212 -- on the current 'input' to reach a success.
213 , mayRaise :: Map Exception ()
214 -- ^ The 'Exception's that may be raised depending on the 'input'.
215 , alwaysRaise :: Set Exception
216 -- ^ The 'Exception's raised whatever is or happen to the 'input'.
217 , freeRegs :: Set TH.Name
218 -- ^ The free registers that are used.
224 -- | Minimal input length required for a successful parsing.
225 type Horizon = Offset
227 -- | Merge given 'GenAnalysis' as sequences.
228 seqGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
229 seqGenAnalysis aas@(a:|as) = GenAnalysis
230 { minReads = List.foldl' (\acc -> (acc +) . minReads) (minReads a) as
231 , mayRaise = sconcat (mayRaise <$> aas)
232 , alwaysRaise = sconcat (alwaysRaise <$> aas)
233 , freeRegs = sconcat (freeRegs <$> aas)
235 -- | Merge given 'GenAnalysis' as alternatives.
236 altGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
237 altGenAnalysis aas = GenAnalysis
240 (`NE.filter` aas) $ \a ->
241 -- If an alternative 'alwaysRaise's 'ExceptionFailure' whatever its 'input' is,
242 -- it __should__ remain semantically the same (up to the exact 'Failure's)
243 -- to raise an 'ExceptionFailure' even before knowing
244 -- whether that alternative branch will be taken or not,
245 -- hence an upstream 'checkHorizon' is allowed to raise an 'ExceptionFailure'
246 -- based only upon the 'minReads' of such alternatives:
247 Set.toList (alwaysRaise a) /= [ExceptionFailure]
250 a:as -> List.foldl' (\acc -> min acc . minReads) (minReads a) as
251 , mayRaise = sconcat (mayRaise <$> aas)
252 , alwaysRaise = foldr Set.intersection Set.empty (alwaysRaise <$> aas)
253 , freeRegs = sconcat (freeRegs <$> aas)
259 -- *** Type 'FarthestError'
260 data FarthestError inp = FarthestError
261 { farthestInput :: Cursor inp
262 , farthestExpecting :: [Failure (InputToken inp)]
267 -- | This is an inherited (top-down) context
268 -- only present at compile-time, to build TemplateHaskell splices.
269 data GenCtx st inp vs a =
270 ( Cursorable (Cursor inp)
272 , TH.Lift (InputToken inp)
273 , Show (InputToken inp)
274 , Eq (InputToken inp)
275 , Ord (InputToken inp)
276 , Typeable (InputToken inp)
277 , NFData (InputToken inp)
279 { valueStack :: ValueStack vs
280 , onExceptionStackByLabel :: Map Exception (NonEmpty (CodeQ (OnException st inp a)))
281 -- | Default 'OnException' defined at the begining of the generated 'CodeQ',
282 -- hence a constant within the 'Gen'eration.
283 , defaultCatch :: forall b. CodeQ (OnException st inp b)
284 , onReturn :: CodeQ (OnReturn st inp a a)
285 , input :: CodeQ (Cursor inp)
286 , moreInput :: CodeQ (Cursor inp -> Bool)
287 , nextInput :: CodeQ (Cursor inp -> (# InputToken inp, Cursor inp #))
288 , farthestInput :: CodeQ (Cursor inp)
289 , farthestExpecting :: CodeQ (Set SomeFailure)
290 -- | Remaining horizon already checked.
291 -- Use to factorize 'input' length checks,
292 -- instead of checking the 'input' length
293 -- one 'InputToken' at a time at each 'read'.
294 -- Updated by 'checkHorizon'
295 -- and reset elsewhere when needed.
296 , checkedHorizon :: Horizon
297 -- | Output of 'mutualFix'.
298 , analysisByLet :: LetRecs TH.Name GenAnalysis
301 -- ** Type 'ValueStack'
302 data ValueStack vs where
303 ValueStackEmpty :: ValueStack '[]
305 { valueStackHead :: Splice v
306 , valueStackTail :: ValueStack vs
307 } -> ValueStack (v ': vs)
309 instance InstrComment Gen where
311 { unGen = \ctx -> {-trace "unGen.comment" $-}
313 let _ = $$(liftTypedString $ "comment: "<>msg) in
317 instance InstrValuable Gen where
319 { unGen = \ctx -> {-trace "unGen.pushValue" $-}
321 let _ = "pushValue" in
323 { valueStack = ValueStackCons x (valueStack ctx) })
327 { unGen = \ctx -> {-trace "unGen.popValue" $-}
329 let _ = "popValue" in
331 { valueStack = valueStackTail (valueStack ctx) })
335 { unGen = \ctx -> {-trace "unGen.lift2Value" $-}
337 let _ = $$(liftTypedString ("lift2Value checkedHorizon="<>show (checkedHorizon ctx))) in
340 let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
341 ValueStackCons (f Prod..@ x Prod..@ y) vs
346 { unGen = \ctx -> {-trace "unGen.swapValue" $-} unGen k ctx
348 let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
349 ValueStackCons x (ValueStackCons y vs)
352 instance InstrBranchable Gen where
353 caseBranch kx ky = Gen
354 { genAnalysisByLet = genAnalysisByLet kx <> genAnalysisByLet ky
355 , genAnalysis = \final -> altGenAnalysis $
356 genAnalysis kx final :|
357 [genAnalysis ky final]
358 , unGen = \ctx -> {-trace "unGen.caseBranch" $-}
359 let ValueStackCons v vs = valueStack ctx in
361 case $$(genCode v) of
362 Left x -> $$(unGen kx ctx{ valueStack = ValueStackCons (splice [||x||]) vs })
363 Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (splice [||y||]) vs })
366 choicesBranch bs default_ = Gen
367 { genAnalysisByLet = sconcat $ genAnalysisByLet default_ :| (genAnalysisByLet . snd <$> bs)
368 , genAnalysis = \final -> altGenAnalysis $
369 (\k -> genAnalysis k final)
370 <$> (default_:|(snd <$> bs))
371 , unGen = \ctx0 -> {-trace "unGen.choicesBranch" $-}
372 let ValueStackCons v vs = valueStack ctx0 in
373 let ctx = ctx0{valueStack = vs} in
375 go x ((p,b):bs') = [||
376 if $$(genCode (p Prod..@ x))
378 let _ = $$(liftTypedString ("choicesBranch checkedHorizon="<>show (checkedHorizon ctx))) in
379 $$({-trace "unGen.choicesBranch.b" $-} unGen b ctx)
381 let _ = "choicesBranch.else" in
384 go _ _ = unGen default_ ctx
387 instance InstrExceptionable Gen where
389 { genAnalysisByLet = HM.empty
390 , genAnalysis = \_final -> GenAnalysis
392 , mayRaise = Map.singleton (ExceptionLabel exn) ()
393 , alwaysRaise = Set.singleton (ExceptionLabel exn)
394 , freeRegs = Set.empty
396 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.raise: "<>show exn) $-} [||
397 $$(raiseException ctx (ExceptionLabel exn))
398 (ExceptionLabel $$(TH.liftTyped exn))
399 {-failInp-}$$(input ctx)
400 {-farInp-}$$(input ctx)
401 $$(farthestExpecting ctx)
405 { genAnalysisByLet = HM.empty
406 , genAnalysis = \_final -> GenAnalysis
408 , mayRaise = Map.singleton ExceptionFailure ()
409 , alwaysRaise = Set.singleton ExceptionFailure
410 , freeRegs = Set.empty
412 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.fail: "<>show exn) $-}
414 then [|| -- Raise without updating the farthest error.
415 $$(raiseException ctx ExceptionFailure)
417 {-failInp-}$$(input ctx)
418 $$(farthestInput ctx)
419 $$(farthestExpecting ctx)
421 else raiseFailure ctx [||fs||]
424 { unGen = \ctx -> {-trace ("unGen.commit: "<>show exn) $-}
427 $$(unGen k ctx{onExceptionStackByLabel =
429 _r0:|(r1:rs) -> Just (r1:|rs)
432 exn (onExceptionStackByLabel ctx)
436 catch exn k onExn = Gen
437 { genAnalysisByLet = genAnalysisByLet k <> genAnalysisByLet onExn
438 , genAnalysis = \final ->
439 let kAnalysis = genAnalysis k final in
440 let onExnAnalysis = genAnalysis onExn final in
443 { mayRaise = Map.delete exn (mayRaise kAnalysis)
444 , alwaysRaise = Set.delete exn (alwaysRaise kAnalysis)
447 , unGen = \ctx@GenCtx{} -> {-trace ("unGen.catch: "<>show exn) $-} [||
448 let _ = $$(liftTypedString ("catch "<>show exn<>" checkedHorizon="<>show (checkedHorizon ctx))) in
449 let onException = $$(onExceptionCode (input ctx) (checkedHorizon ctx) onExn ctx) in
451 { onExceptionStackByLabel =
452 Map.insertWith (<>) exn
453 (NE.singleton [||onException||])
454 (onExceptionStackByLabel ctx)
458 -- ** Class 'SpliceInputable'
459 -- | Record an 'input' and a 'checkedHorizon' together
460 -- to be able to put both of them on the 'valueStack',
461 -- and having them moved together by operations
462 -- on the 'valueStack' (eg. 'lift2Value').
463 -- Used by 'saveInput' and 'loadInput'.
464 class SpliceInputable repr where
465 inputSave :: CodeQ inp -> Horizon -> repr inp
466 data instance Sym.Data SpliceInputable repr a where
467 InputSave :: CodeQ inp -> Horizon -> Sym.Data SpliceInputable repr inp
468 instance SpliceInputable (Sym.Data SpliceInputable repr) where
469 inputSave = InputSave
470 instance SpliceInputable repr => SpliceInputable (Sym.SomeData repr) where
471 inputSave inp = Sym.SomeData . InputSave inp
472 instance SpliceInputable TH.CodeQ where
473 inputSave inp _hor = inp
474 instance SpliceInputable repr => Derivable (Sym.Data SpliceInputable repr) where
476 InputSave inp hor -> inputSave inp hor
477 instance InstrInputable Gen where
480 {-trace "unGen.saveInput" $-}
482 let _ = $$(liftTypedString $ "saveInput checkedHorizon="<>show (checkedHorizon ctx)) in
484 { valueStack = inputSave (input ctx) (checkedHorizon ctx) `ValueStackCons` valueStack ctx
489 { unGen = \ctx@GenCtx{} ->
490 {-trace "unGen.loadInput" $-}
491 let ValueStackCons v vs = valueStack ctx in
492 let (input, checkedHorizon) = case v of
493 Sym.Data (InputSave i h) -> (i, h)
494 -- This case should never happen if 'saveInput' is used.
495 i -> (genCode i, 0) in
497 let _ = $$(liftTypedString $ "loadInput checkedHorizon="<>show checkedHorizon) in
498 $$(unGen (checkHorizon k) ctx
504 , genAnalysis = \final ->
505 let analysis = genAnalysis k final in
506 -- The input is reset and thus any previous 'checkHorizon'
507 -- cannot check after this 'loadInput'.
508 analysis{minReads = 0}
510 instance InstrCallable Gen where
512 { unGen = \ctx@GenCtx{} ->
513 {-trace ("unGen.defLet: defs="<>show (HM.keys defs)) $-}
514 TH.unsafeCodeCoerce $ do
515 decls <- traverse (makeDecl ctx) (HM.toList defs)
516 body <- TH.unTypeQ $ TH.examineCode $
517 {-trace "unGen.defLet.body" $-}
520 -- | Use 'List.sortBy' to output more deterministic code
521 -- to be able to golden test it, at the cost of more computations
522 -- (at compile-time only though).
523 List.sortBy (compare `on` TH.hideName) $
529 : ((\(SomeLet sub) -> genAnalysis sub) <$> defs)
530 : ((\(SomeLet sub) -> genAnalysisByLet sub) <$> HM.elems defs)
533 makeDecl ctx (subName, SomeLet sub) = do
534 let subAnalysis = analysisByLet ctx HM.! subName
535 body <- takeFreeRegs (freeRegs subAnalysis) $
536 TH.unTypeQ $ TH.examineCode $ [|| -- buildRec in Parsley
537 -- Called by 'call' or 'jump'.
538 \ !callerOnReturn{-from onReturnCode-}
540 !callerOnExceptionStackByLabel{- 'onExceptionStackByLabel' from the 'call'-site -} ->
541 $$({-trace ("unGen.defLet.sub: "<>show subName) $-} unGen sub ctx
542 { valueStack = ValueStackEmpty
543 -- Build a 'onExceptionStackByLabel' for the 'mayRaise' of the subroutine,
544 -- where each 'OnException' calls the one passed by the 'call'-site (in 'callerOnExceptionStackByLabel').
545 -- Note that currently the 'call'-site can supply in 'callerOnExceptionStackByLabel'
546 -- a subset of the 'mayRaise' needed by this subroutine,
547 -- because 'Map.findWithDefault' is used instead of 'Map.!'.
548 , onExceptionStackByLabel = Map.mapWithKey
549 (\lbl () -> NE.singleton [||Map.findWithDefault $$(defaultCatch ctx) lbl callerOnExceptionStackByLabel||])
550 ({-trace ("mayRaise: "<>show subName) $-}
551 mayRaise subAnalysis)
552 , input = [||callerInput||]
553 , onReturn = {-trace ("unGen.defLet.sub.onReturn: "<>show subName) $-} [||callerOnReturn||]
555 -- These are passed by the caller via 'callerOnReturn' or 'ko'
557 -- , farthestExpecting =
559 -- Some callers can call this declaration
560 -- with zero 'checkedHorizon', hence use this minimum.
561 -- TODO: maybe it could be improved a bit
562 -- by taking the minimum of the checked horizons
563 -- before all the 'call's and 'jump's to this declaration.
567 let decl = TH.FunD subName [TH.Clause [] (TH.NormalB body) []]
569 jump isRec (LetName subName) = Gen
570 { genAnalysisByLet = HM.empty
571 , genAnalysis = \final ->
575 , mayRaise = Map.empty
576 , alwaysRaise = Set.empty
577 , freeRegs = Set.empty
579 else final HM.! subName
580 , unGen = \ctx -> {-trace ("unGen.jump: "<>show subName) $-}
581 let subAnalysis = analysisByLet ctx HM.! subName in
584 $$(TH.unsafeCodeCoerce $
585 giveFreeRegs (freeRegs subAnalysis) $
586 return (TH.VarE subName))
587 {-ok-}$$(onReturn ctx)
589 $$(liftTypedRaiseByLabel $
590 onExceptionStackByLabel ctx
591 -- Pass only the labels raised by the 'defLet'.
593 (mayRaise subAnalysis)
597 call isRec (LetName subName) k = k
598 { genAnalysis = \final ->
602 , mayRaise = Map.empty
603 , alwaysRaise = Set.empty
604 , freeRegs = Set.empty
606 else seqGenAnalysis $ (final HM.! subName) :| [ genAnalysis k final ]
607 , unGen = {-trace ("unGen.call: "<>show subName) $-} \ctx ->
608 -- let ks = (Map.keys (onExceptionStackByLabel ctx)) in
609 let subAnalysis = analysisByLet ctx HM.! subName in
611 -- let _ = $$(liftTypedString $ "call exceptByLet("<>show subName<>")="<>show (Map.keys (Map.findWithDefault Map.empty subName (exceptByLet ctx))) <> " onExceptionStackByLabel(ctx)="<> show ks) in
612 $$(TH.unsafeCodeCoerce $
613 giveFreeRegs (freeRegs subAnalysis) $
614 return (TH.VarE subName))
615 {-ok-}$$(onReturnCode k ctx)
617 $$(liftTypedRaiseByLabel $
618 -- FIXME: maybe it should rather pass all the 'mayRaise' of 'subName'
619 -- and 'defaultCatch' be removed from 'makeDecl''s 'onExceptionStackByLabel'.
620 onExceptionStackByLabel ctx
621 -- Pass only the labels raised by the 'defLet'.
623 (mayRaise subAnalysis)
628 { genAnalysisByLet = HM.empty
629 , genAnalysis = \_final -> GenAnalysis
631 , mayRaise = Map.empty
632 , alwaysRaise = Set.empty
633 , freeRegs = Set.empty
635 , unGen = \ctx -> {-trace "unGen.ret" $-}
636 {-trace "unGen.ret.returnCode" $-}
637 returnCode ({-trace "unGen.ret.onReturn" $-} onReturn ctx) ctx
640 takeFreeRegs :: TH.Quote m => Set TH.Name -> m TH.Exp -> m TH.Exp
641 takeFreeRegs frs k = go (Set.toList frs)
644 go (r:rs) = [| \ $(return (TH.VarP r)) -> $(go rs) |]
646 giveFreeRegs :: TH.Quote m => Set TH.Name -> m TH.Exp -> m TH.Exp
647 giveFreeRegs frs k = go (Set.toList frs)
650 go (r:rs) = [| $(go rs) $(return (TH.VarE r)) |]
652 -- | Like 'TH.liftString' but on 'TH.Code'.
653 -- Useful to get a 'TH.StringL' instead of a 'TH.ListE'.
654 liftTypedString :: String -> TH.Code TH.Q a
655 liftTypedString = TH.unsafeCodeCoerce . TH.liftString
657 -- | Like 'TH.liftTyped' but adjusted to work on 'onExceptionStackByLabel'
658 -- which already contains 'CodeQ' terms.
659 -- Moreover, only the 'OnException' at the top of the stack
660 -- is needed and thus generated in the resulting 'CodeQ'.
662 -- TODO: Use an 'Array' instead of a 'Map'?
663 liftTypedRaiseByLabel :: TH.Lift k => Map k (NonEmpty (CodeQ a)) -> CodeQ (Map k a)
664 liftTypedRaiseByLabel Map_.Tip = [|| Map_.Tip ||]
665 liftTypedRaiseByLabel (Map_.Bin s k (h:|_hs) l r) =
666 [|| Map_.Bin s k $$h $$(liftTypedRaiseByLabel l) $$(liftTypedRaiseByLabel r) ||]
668 instance TH.Lift a => TH.Lift (Set a) where
669 liftTyped Set_.Tip = [|| Set_.Tip ||]
670 liftTyped (Set_.Bin s a l r) = [|| Set_.Bin $$(TH.liftTyped s) $$(TH.liftTyped a) $$(TH.liftTyped l) $$(TH.liftTyped r) ||]
672 -- ** Type 'OnReturn'
673 -- | A continuation generated by 'onReturnCode' and later called by 'returnCode'.
674 type OnReturn st inp v a =
675 {-farthestInput-}Cursor inp ->
676 {-farthestExpecting-}Set SomeFailure ->
679 ST st (Either (ParsingError inp) a)
681 -- | Generate an 'OnReturn' continuation to be called with 'returnCode'.
682 -- Used when 'call' 'ret'urns.
683 -- The return 'v'alue is 'pushValue'-ed on the 'valueStack'.
685 {-k-}Gen inp (v ': vs) a ->
686 GenCtx st inp vs a ->
687 CodeQ (OnReturn st inp v a)
688 onReturnCode k ctx = [||
689 let _ = $$(liftTypedString $ "onReturn") in
690 \farInp farExp v !inp ->
691 $$({-trace "unGen.onReturnCode" $-} unGen k ctx
692 { valueStack = ValueStackCons ({-trace "unGen.onReturnCode.value" $-} splice [||v||]) (valueStack ctx)
694 , farthestInput = [||farInp||]
695 , farthestExpecting = [||farExp||]
701 -- | Generate a call to the 'onReturnCode' continuation.
702 -- Used when 'call' 'ret'urns.
704 CodeQ (OnReturn st inp v a) ->
705 GenCtx st inp (v ': vs) a ->
706 CodeQ (ST st (Either (ParsingError inp) a))
707 returnCode k = \ctx -> {-trace "returnCode" $-} [||
710 $$(farthestInput ctx)
711 $$(farthestExpecting ctx)
712 (let _ = "resume.genCode" in $$({-trace "returnCode.genCode" $-}
713 genCode $ valueStackHead $ valueStack ctx))
717 -- ** Type 'OnException'
718 -- | A continuation generated by 'catch' and later called by 'raise' or 'fail'.
719 type OnException st inp a =
721 {-failInp-}Cursor inp ->
722 {-farInp-}Cursor inp ->
723 {-farExp-}Set SomeFailure ->
724 ST st (Either (ParsingError inp) a)
726 -- TODO: some static infos should be attached to 'OnException'
727 -- to avoid comparing inputs when they're the same
728 -- and to improve 'checkedHorizon'.
730 CodeQ (Cursor inp) -> Horizon ->
731 Gen inp (Cursor inp : vs) a ->
732 GenCtx st inp vs a -> TH.CodeQ (OnException st inp a)
733 onExceptionCode resetInput resetCheckedHorizon k ctx = [||
734 let _ = $$(liftTypedString $ "onException") in
735 \ !_exn !failInp !farInp !farExp ->
737 -- Push 'input' and 'checkedHorizon'
738 -- as they were when entering the 'catch' or 'iter',
739 -- they will be available to 'loadInput', if any.
740 { valueStack = inputSave resetInput resetCheckedHorizon
741 `ValueStackCons` valueStack ctx
742 -- Note that 'onExceptionStackByLabel' is reset.
743 -- Move the input to the failing position.
744 , input = [||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 those to the farthest error computed in 'raiseFailure'.
750 , farthestInput = [||farInp||]
751 , farthestExpecting = [||farExp||]
755 instance InstrJoinable Gen where
756 defJoin (LetName n) sub k = k
758 {-trace ("unGen.defJoin: "<>show n) $-}
759 TH.unsafeCodeCoerce [|
760 let $(return (TH.VarP n)) = $(TH.unTypeQ $ TH.examineCode [||
761 -- Called by 'returnCode'.
762 \farInp farExp v !inp ->
763 $$({-trace ("unGen.defJoin.next: "<>show n) $-} unGen sub ctx
764 { valueStack = ValueStackCons (splice [||v||]) (valueStack ctx)
766 , farthestInput = [||farInp||]
767 , farthestExpecting = [||farExp||]
770 , onExceptionStackByLabel = Map.mapWithKey
771 (\lbl () -> NE.singleton [||koByLabel Map.! lbl||])
772 (mayRaise sub raiseLabelsByLetButSub)
776 in $(TH.unTypeQ $ TH.examineCode $
777 {-trace ("unGen.defJoin.expr: "<>show n) $-}
781 (genAnalysisByLet sub <>) $
782 HM.insert n (genAnalysis sub) $
785 refJoin (LetName n) = Gen
787 {-trace ("unGen.refJoin: "<>show n) $-}
789 (TH.unsafeCodeCoerce (return (TH.VarE n))) ctx
790 , genAnalysisByLet = HM.empty
791 , genAnalysis = \final ->
793 (error (show (n,HM.keys final)))
796 instance InstrReadable Char Gen where
797 read fs p = checkHorizon . checkToken fs p
798 instance InstrReadable Word8 Gen where
799 read fs p = checkHorizon . checkToken fs p
800 instance InstrIterable Gen where
801 iter (LetName loopJump) loop done = Gen
802 { genAnalysisByLet = HM.unions
803 [ -- No need to give 'freeRegs' when 'call'ing 'loopJump'
804 -- because they're passed when 'call'ing 'iter'.
805 -- This avoids to passing those registers around.
806 HM.singleton loopJump (\final -> (genAnalysis loop final){freeRegs = Set.empty})
807 , genAnalysisByLet loop
808 , genAnalysisByLet done
810 , genAnalysis = \final ->
811 let loopAnalysis = genAnalysis loop final in
812 let doneAnalysis = genAnalysis done final in
814 { minReads = minReads doneAnalysis
816 Map.delete ExceptionFailure (mayRaise loopAnalysis) <>
817 mayRaise doneAnalysis
819 Set.delete ExceptionFailure (alwaysRaise loopAnalysis) <>
820 alwaysRaise doneAnalysis
821 , freeRegs = freeRegs loopAnalysis <> freeRegs doneAnalysis
823 , unGen = \ctx -> TH.unsafeCodeCoerce [|
826 onException loopInput = $(TH.unTypeCode $ onExceptionCode
827 (TH.unsafeCodeCoerce [|loopInput|]) 0 done ctx)
828 $(return $ TH.VarP loopJump) = \_callerOnReturn callerInput callerOnExceptionStackByLabel ->
829 $(TH.unTypeCode $ unGen loop ctx
830 { valueStack = ValueStackEmpty
831 , onExceptionStackByLabel =
832 Map.insertWith (<>) ExceptionFailure
833 (NE.singleton $ TH.unsafeCodeCoerce [|onException callerInput|])
834 (onExceptionStackByLabel ctx)
835 , input = TH.unsafeCodeCoerce [|callerInput|]
836 -- FIXME: promote to compile time error?
837 , onReturn = TH.unsafeCodeCoerce [|error "invalid onReturn"|]
840 in $(TH.unTypeCode $ unGen (jump True (LetName loopJump)) ctx{valueStack=ValueStackEmpty})
843 instance InstrRegisterable Gen where
844 newRegister (UnscopedRegister r) k = k
845 { genAnalysis = \final ->
846 let analysis = genAnalysis k final in
847 analysis{freeRegs = Set.delete r $ freeRegs analysis}
849 let ValueStackCons v vs = valueStack ctx in
850 TH.unsafeCodeCoerce [|
852 let dupv = $(TH.unTypeCode $ genCode v)
853 $(return (TH.VarP r)) <- ST.newSTRef dupv
854 $(TH.unTypeCode $ unGen k ctx{valueStack=vs})
857 readRegister (UnscopedRegister r) k = k
858 { genAnalysis = \final ->
859 let analysis = genAnalysis k final in
860 analysis{freeRegs = Set.insert r $ freeRegs analysis}
861 , unGen = \ctx -> [|| do
862 sr <- ST.readSTRef $$(TH.unsafeCodeCoerce (return (TH.VarE r)))
863 $$(unGen k ctx{valueStack=ValueStackCons (splice [||sr||]) (valueStack ctx)})
866 writeRegister (UnscopedRegister r) k = k
867 { genAnalysis = \final ->
868 let analysis = genAnalysis k final in
869 analysis{freeRegs = Set.insert r $ freeRegs analysis}
871 let ValueStackCons v vs = valueStack ctx in
873 let dupv = $$(genCode v)
874 ST.writeSTRef $$(TH.unsafeCodeCoerce (return (TH.VarE r))) dupv
875 $$(unGen k ctx{valueStack=vs})
881 -- Those constraints are not used anyway
882 -- because (TH.Lift SomeFailure) uses 'inputTokenProxy'.
883 Ord (InputToken inp) =>
884 Show (InputToken inp) =>
885 TH.Lift (InputToken inp) =>
886 NFData (InputToken inp) =>
887 Typeable (InputToken inp) =>
888 {-ok-}Gen inp vs a ->
891 { genAnalysis = \final -> seqGenAnalysis $
892 GenAnalysis { minReads = 0
893 , mayRaise = Map.singleton ExceptionFailure ()
894 , alwaysRaise = Set.empty
895 , freeRegs = Set.empty
897 [ genAnalysis ok final ]
898 , unGen = \ctx0@GenCtx{} ->
899 if checkedHorizon ctx0 >= 1
902 let _ = $$(liftTypedString $ "checkHorizon.oldCheck: checkedHorizon="<>show (checkedHorizon ctx0)) in
903 $$(unGen ok ctx0{checkedHorizon = checkedHorizon ctx0 - 1})
906 let minHoriz = minReads $ genAnalysis ok $ analysisByLet ctx0 in
910 let _ = "checkHorizon.noCheck" in
915 let inp = $$(input ctx0) in
916 --let partialCont inp =
917 -- Factorize generated code for raising the "fail".
918 let readFail = $$(raiseException ctx0{input=[||inp||]} ExceptionFailure) in
921 { onExceptionStackByLabel =
922 Map.adjust (\(_r:|rs) -> [||readFail||] :| rs)
923 ExceptionFailure (onExceptionStackByLabel ctx0)
927 let _ = $$(liftTypedString $ "checkHorizon.newCheck: checkedHorizon="<>show (checkedHorizon ctx)<>" minHoriz="<>show minHoriz) in
930 then [||$$shiftRight $$(TH.liftTyped (minHoriz - 1)) inp||]
932 then $$(unGen ok ctx{checkedHorizon = minHoriz})
934 let _ = $$(liftTypedString $ "checkHorizon.newCheck.fail") in
935 -- TODO: return a resuming continuation (like attoparsec's Partial)
936 -- This could be done with a Buffer for efficient backtracking:
937 -- http://www.serpentine.com/blog/2014/05/31/attoparsec/
938 $$(unGen (fail (Set.singleton $ SomeFailure $ FailureHorizon @(InputToken inp) minHoriz)) ctx)
941 --in partialCont $$(input ctx0)
945 -- | @('raiseFailure' ctx fs)@ raises 'ExceptionFailure'
946 -- with farthest parameters set to or updated with @(fs)@
947 -- according to the relative position of 'input' wrt. 'farthestInput'.
949 Cursorable (Cursor inp) =>
950 GenCtx st inp cs a ->
951 TH.CodeQ (Set SomeFailure) ->
952 TH.CodeQ (ST st (Either (ParsingError inp) a))
953 raiseFailure ctx fs = [||
954 let failExp = $$fs in
955 let (# farInp, farExp #) =
956 case $$compareOffset $$(farthestInput ctx) $$(input ctx) of
957 LT -> (# $$(input ctx), failExp #)
958 EQ -> (# $$(farthestInput ctx), failExp <> $$(farthestExpecting ctx) #)
959 GT -> (# $$(farthestInput ctx), $$(farthestExpecting ctx) #)
960 in $$(raiseException ctx ExceptionFailure)
962 {-failInp-}$$(input ctx) farInp farExp
964 -- | @('raiseException' ctx exn)@ raises exception @(exn)@
965 -- using any entry in 'onExceptionStackByLabel', or 'defaultCatch' if none.
967 GenCtx st inp vs a -> Exception ->
968 CodeQ (Exception -> Cursor inp -> Cursor inp -> Set SomeFailure -> ST st (Either (ParsingError inp) a))
969 raiseException ctx exn =
970 NE.head $ Map.findWithDefault
971 (NE.singleton (defaultCatch ctx))
972 exn (onExceptionStackByLabel ctx)
976 {-predicate-}Splice (InputToken inp -> Bool) ->
977 {-ok-}Gen inp (InputToken inp ': vs) a ->
979 checkToken fs p ok = ok
980 { genAnalysis = \final -> seqGenAnalysis $
981 GenAnalysis { minReads = 1
982 , mayRaise = Map.singleton ExceptionFailure ()
983 , alwaysRaise = Set.empty
984 , freeRegs = Set.empty
986 [ genAnalysis ok final ]
987 , unGen = \ctx -> {-trace "unGen.read" $-} [||
988 let _ = "checkToken" in
989 let !(# c, cs #) = $$(nextInput ctx) $$(input ctx) in
992 (p Prod..@ splice [||c||])
993 (splice $ unGen ok ctx
994 { valueStack = ValueStackCons (splice [||c||]) (valueStack ctx)
998 let _ = "checkToken.fail" in
999 $$(unGen (fail fs) ctx)