doc: fix reference to Symantic.Typed
[haskell/symantic-parser.git] / src / Symantic / Parser / Machine / Generate.hs
index b9e2bb5683bbed40b9b6652bde99ff9c89e47fcb..bfe106d6650dba2a6a0de0a2380dfe620e44e8d4 100644 (file)
 {-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE DeriveAnyClass #-} -- For NFData instances
+{-# LANGUAGE DeriveGeneric #-} -- For NFData instances
 {-# LANGUAGE StandaloneDeriving #-} -- For Show (ParsingError inp)
+{-# LANGUAGE ConstraintKinds #-} -- For Dict
 {-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE UnboxedTuples #-} -- For nextInput
 {-# LANGUAGE UndecidableInstances #-} -- For Show (ParsingError inp)
+{-# OPTIONS_GHC -fno-warn-orphans #-}
 module Symantic.Parser.Machine.Generate where
 
+import Control.DeepSeq (NFData(..))
 import Control.Monad (Monad(..))
 import Data.Bool (Bool)
 import Data.Char (Char)
-import Data.Either (Either(..))
-import Data.Function (($), (.))
-import Data.Functor ((<$>))
+import Data.Either (Either(..), either)
+import Data.Foldable (foldMap', toList, null)
+import Data.Function (($), (.), id, const, on)
+import Data.Functor (Functor, (<$>), (<$))
 import Data.Int (Int)
-import Data.List (minimum)
+import Data.List.NonEmpty (NonEmpty(..))
 import Data.Map (Map)
 import Data.Maybe (Maybe(..))
 import Data.Ord (Ord(..), Ordering(..))
+import Data.Proxy (Proxy(..))
 import Data.Semigroup (Semigroup(..))
 import Data.Set (Set)
-import Language.Haskell.TH (CodeQ, Code(..))
-import Prelude (($!), (+), (-))
-import Text.Show (Show(..))
+import Data.String (String)
+import Data.Traversable (Traversable(..))
+import Data.Typeable (Typeable)
+import Data.Word (Word8)
+import GHC.Generics (Generic)
+import GHC.Show (showCommaSpace)
+import Language.Haskell.TH (CodeQ)
+import Prelude ((+), (-), error)
+import Text.Show (Show(..), showParen, showString)
+import qualified Data.HashMap.Strict as HM
+import qualified Data.List as List
+import qualified Data.List.NonEmpty as NE
+import qualified Data.Map.Internal as Map_
 import qualified Data.Map.Strict as Map
 import qualified Data.Set as Set
+import qualified Data.Set.Internal as Set_
+import qualified Language.Haskell.TH as TH
 import qualified Language.Haskell.TH.Syntax as TH
--- import qualified Control.Monad.Trans.Writer as Writer
 
-import Symantic.Univariant.Trans
-import Symantic.Parser.Grammar.Combinators (ErrorItem(..))
+import Symantic.Typed.Derive
+import Symantic.Typed.ObserveSharing
+import Symantic.Parser.Grammar.Combinators (Exception(..), Failure(..), SomeFailure(..), inputTokenProxy)
 import Symantic.Parser.Machine.Input
 import Symantic.Parser.Machine.Instructions
-import qualified Symantic.Parser.Haskell as H
+import qualified Language.Haskell.TH.HideName as TH
+import qualified Symantic.Typed.Lang as Prod
+import qualified Symantic.Typed.Optimize as Prod
 
-genCode :: TermInstr a -> CodeQ a
-genCode = trans
+--import Debug.Trace
+
+-- | Convenient utility to generate some final 'TH.CodeQ'.
+genCode :: Splice a -> CodeQ a
+genCode = derive . Prod.normalOrderReduction
 
 -- * Type 'Gen'
 -- | Generate the 'CodeQ' parsing the input.
-data Gen inp vs es a = Gen
-  { minHorizon :: Map TH.Name Horizon -> Horizon
+data Gen inp vs a = Gen
+  { genAnalysisByLet :: LetMapFix (CallTrace -> GenAnalysis)
+    -- ^ 'genAnalysis' for each 'defLet' and 'defJoin' of the 'Machine'.
+  , genAnalysis :: LetMapTo (CallTrace -> GenAnalysis)
+    -- ^ Synthetized (bottom-up) static genAnalysis of the 'Machine'.
   , unGen ::
-      GenCtx inp vs es a ->
+      GenCtx inp vs a ->
       CodeQ (Either (ParsingError inp) a)
   }
 
+-- | @('generateCode' input mach)@ generates @TemplateHaskell@ code
+-- parsing the given 'input' according to the given 'Machine'.
+generateCode ::
+  {-
+  Eq (InputToken inp) =>
+  NFData (InputToken inp) =>
+  Show (InputToken inp) =>
+  Typeable (InputToken inp) =>
+  TH.Lift (InputToken inp) =>
+  -}
+  -- InputToken inp ~ Char =>
+  Inputable inp =>
+  Show (Cursor inp) =>
+  Gen inp '[] a ->
+  CodeQ (inp -> Either (ParsingError inp) a)
+generateCode k = [|| \(input :: inp) ->
+    -- Pattern bindings containing unlifted types
+    -- should use an outermost bang pattern.
+    let !(# init, readMore, readNext #) = $$(cursorOf [||input||])
+        finalRet = \_farInp _farExp v _inp -> Right v
+        finalRaise :: forall b. (Catcher inp b)
+          = \ !exn _failInp !farInp !farExp ->
+          Left ParsingError
+          { parsingErrorOffset = offset farInp
+          , parsingErrorException = exn
+          , parsingErrorUnexpected =
+              if readMore farInp
+              then Just (let (# c, _ #) = readNext farInp in c)
+              else Nothing
+          , parsingErrorExpecting = farExp
+          }
+    in
+    $$(
+      let defInputTokenProxy exprCode =
+            TH.unsafeCodeCoerce $ do
+              value <- TH.unTypeQ $ TH.examineCode [||Proxy :: Proxy (InputToken inp)||]
+              expr <- TH.unTypeQ (TH.examineCode exprCode)
+              return $ TH.LetE [
+                TH.FunD inputTokenProxy [TH.Clause [] (TH.NormalB value) []]
+                ] expr
+      in defInputTokenProxy $
+      unGen k GenCtx
+        { valueStack = ValueStackEmpty
+        , catchStackByLabel = Map.empty
+        , defaultCatch = [||finalRaise||]
+        , callStack = []
+        , retCode = [||finalRet||]
+        , input = [||init||]
+        , nextInput = [||readNext||]
+        , moreInput = [||readMore||]
+        -- , farthestError = [||Nothing||]
+        , farthestInput = [||init||]
+        , farthestExpecting = [||Set.empty||]
+        , checkedHorizon = 0
+        , horizonStack = []
+        , finalGenAnalysisByLet = runGenAnalysis (genAnalysisByLet k)
+        }
+      )
+    ||]
+
 -- ** Type 'ParsingError'
 data ParsingError inp
-  =  ParsingErrorStandard
+  =  ParsingError
   {  parsingErrorOffset :: Offset
-    -- | Note that if an 'ErrorItemHorizon' greater than 1
-    -- is amongst the 'parsingErrorExpecting'
-    -- then this is only the 'InputToken'
-    -- at the begining of the expected 'Horizon'.
+  ,  parsingErrorException :: Exception
+     -- | Note: if a 'FailureHorizon' greater than 1
+     -- is amongst the 'parsingErrorExpecting'
+     -- then 'parsingErrorUnexpected' is only the 'InputToken'
+     -- at the begining of the expected 'Horizon'.
   ,  parsingErrorUnexpected :: Maybe (InputToken inp)
-  ,  parsingErrorExpecting :: Set (ErrorItem (InputToken inp))
-  }
-deriving instance Show (InputToken inp) => Show (ParsingError inp)
+  ,  parsingErrorExpecting :: Set SomeFailure
+  } deriving (Generic)
+deriving instance NFData (InputToken inp) => NFData (ParsingError inp)
+--deriving instance Show (InputToken inp) => Show (ParsingError inp)
+instance Show (InputToken inp) => Show (ParsingError inp) where
+  showsPrec p ParsingError{..} =
+    showParen (p >= 11) $
+      showString "ParsingErrorStandard {" .
+      showString "parsingErrorOffset = " .
+      showsPrec 0 parsingErrorOffset .
+      showCommaSpace .
+      showString "parsingErrorException = " .
+      showsPrec 0 parsingErrorException .
+      showCommaSpace .
+      showString "parsingErrorUnexpected = " .
+      showsPrec 0 parsingErrorUnexpected .
+      showCommaSpace .
+      showString "parsingErrorExpecting = fromList " .
+      showsPrec 0 (
+        -- Sort on the string representation
+        -- because the 'Ord' of the 'SomeFailure'
+        -- is based upon hashes ('typeRepFingerprint')
+        -- depending on packages' ABI and whether
+        -- cabal-install's setup is --inplace or not,
+        -- and that would be too unstable for golden tests.
+        List.sortBy (compare `on` show) $
+        Set.toList parsingErrorExpecting
+      ) .
+      showString "}"
+
+-- ** Type 'ErrorLabel'
+type ErrorLabel = String
+
+-- * Type 'GenAnalysis'
+data GenAnalysis = GenAnalysis
+  { minReads :: Either Exception Horizon
+  , mayRaise :: Map Exception ()
+  } deriving (Show)
+
+-- | Tie the knot between mutually recursive 'TH.Name's
+-- introduced by 'defLet' and 'defJoin'.
+-- and provide the empty initial 'CallTrace' stack
+runGenAnalysis ::
+  LetMapFix (CallTrace -> GenAnalysis) ->
+  LetMap GenAnalysis
+runGenAnalysis ga = (($ []) <$>) $ polyfix ga
+
+-- | Poly-variadic fixpoint combinator.
+-- Used to express mutual recursion and to transparently introduce memoization,
+-- more precisely to "tie the knot"
+-- between observed sharing ('defLet', 'call', 'jump')
+-- and also between join points ('defJoin', 'refJoin').
+-- Because it's enough for its usage here,
+-- all mutually dependent functions are restricted to the same polymorphic type @(a)@.
+-- See http://okmij.org/ftp/Computation/fixed-point-combinators.html#Poly-variadic
+polyfix :: Functor f => f (f a -> a) -> f a
+polyfix fs = fix $ \finals -> ($ finals) <$> fs
+
+fix :: (a -> a) -> a
+fix f = final where final = f final
+
+type LetMap = HM.HashMap TH.Name
+type LetMapTo a = LetMap a -> a
+type LetMapFix a = LetMap (LetMap a -> a)
+
+-- | Call trace stack updated by 'call' and 'refJoin'.
+-- Used to avoid infinite loops when tying the knot with 'polyfix'.
+type CallTrace = [TH.Name]
 
 -- ** Type 'Offset'
 type Offset = Int
-
 -- ** Type 'Horizon'
--- | Synthetized minimal input length
--- required for a successful parsing.
--- Used with 'horizon' to factorize input length checks,
--- instead of checking the input length
--- one 'InputToken' by one 'InputToken' at each 'read'.
+-- | Minimal input length required for a successful parsing.
 type Horizon = Offset
 
--- ** Type 'Cont'
-type Cont inp v a =
-  {-farthestInput-}Cursor inp ->
-  {-farthestExpecting-}[ErrorItem (InputToken inp)] ->
-  v ->
-  Cursor inp ->
-  Either (ParsingError inp) a
-
--- ** Type 'SubRoutine'
-type SubRoutine inp v a =
-  {-ok-}Cont inp v a ->
-  Cursor inp ->
-  {-ko-}FailHandler inp a ->
-  Either (ParsingError inp) a
+-- altGenAnalysis = List.foldl' (\acc x -> either Left (\h -> Right (either (const h) (min h) acc)) x)
+-- | Merge given 'GenAnalysis' as sequences.
+seqGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
+seqGenAnalysis aas@(a:|as) = GenAnalysis
+  { minReads = List.foldl' (\acc x ->
+        acc >>= \r -> (r +) <$> minReads x
+      ) (minReads a) as
+  , mayRaise = sconcat (mayRaise <$> aas)
+  }
+-- | Merge given 'GenAnalysis' as alternatives.
+altGenAnalysis :: NonEmpty GenAnalysis -> GenAnalysis
+altGenAnalysis aas@(a:|as) = GenAnalysis
+  { minReads = List.foldl' (\acc x ->
+      either
+        (\l -> either (const (Left  l)) Right)
+        (\r -> either (const (Right r)) (Right . min r))
+        acc (minReads x)
+      ) (minReads a) as
+  , mayRaise = sconcat (mayRaise <$> aas)
+  }
 
--- ** Type 'FailHandler'
-type FailHandler inp a =
-  {-failureInput-}Cursor inp ->
-  {-farthestInput-}Cursor inp ->
-  {-farthestExpecting-}[ErrorItem (InputToken inp)] ->
-  Either (ParsingError inp) a
 
 {-
 -- *** Type 'FarthestError'
 data FarthestError inp = FarthestError
   { farthestInput :: Cursor inp
-  , farthestExpecting :: [ErrorItem (InputToken inp)]
+  , farthestExpecting :: [Failure (InputToken inp)]
   }
 -}
 
--- | @('generate' input mach)@ generates @TemplateHaskell@ code
--- parsing given 'input' according to given 'mach'ine.
-generate ::
-  forall inp ret.
-  Ord (InputToken inp) =>
-  Show (InputToken inp) =>
-  TH.Lift (InputToken inp) =>
-  -- InputToken inp ~ Char =>
-  Input inp =>
-  CodeQ inp ->
-  Show (Cursor inp) =>
-  Gen inp '[] ('Succ 'Zero) ret ->
-  CodeQ (Either (ParsingError inp) ret)
-generate input k = [||
-  -- Pattern bindings containing unlifted types
-  -- should use an outermost bang pattern.
-  let !(# init, readMore, readNext #) = $$(cursorOf input) in
-  let finalRet = \_farInp _farExp v _inp -> Right v in
-  let finalFail _failInp !farInp !farExp =
-        Left ParsingErrorStandard
-        { parsingErrorOffset = offset farInp
-        , parsingErrorUnexpected =
-            if readMore farInp
-            then Just (let (# c, _ #) = readNext farInp in c)
-            else Nothing
-        , parsingErrorExpecting = Set.fromList farExp
-        } in
-  $$(unGen k GenCtx
-    { valueStack = ValueStackEmpty
-    , failStack = FailStackCons [||finalFail||] FailStackEmpty
-    , retCode = [||finalRet||]
-    , input = [||init||]
-    , nextInput = [||readNext||]
-    , moreInput = [||readMore||]
-    -- , farthestError = [||Nothing||]
-    , farthestInput = [||init||]
-    , farthestExpecting = [|| [] ||]
-    , horizon = 0
-    , horizonByName = Map.empty
-    })
-  ||]
-
 -- ** Type 'GenCtx'
--- | This is a context only present at compile-time.
-data GenCtx inp vs (es::Peano) a =
-  ( TH.Lift (InputToken inp)
-  , Cursorable (Cursor inp)
+-- | This is an inherited (top-down) context
+-- only present at compile-time, to build TemplateHaskell splices.
+data GenCtx inp vs a =
+  ( Cursorable (Cursor inp)
+  {-
+  , TH.Lift (InputToken inp)
   , Show (InputToken inp)
-  -- , InputToken inp ~ Char
+  , Eq (InputToken inp)
+  , Typeable (InputToken inp)
+  , NFData (InputToken inp)
+  -}
   ) => GenCtx
   { valueStack :: ValueStack vs
-  , failStack :: FailStack inp es a
+  , catchStackByLabel :: Map Exception (NonEmpty (CodeQ (Catcher inp a)))
+    -- | Default 'Catcher' defined at the begining of the generated 'CodeQ',
+    -- hence a constant within the 'Gen'eration.
+  , defaultCatch :: forall b. CodeQ (Catcher inp b)
+    -- | Used by 'checkToken' to get 'GenAnalysis' from 'genAnalysis'.
+  , callStack :: [TH.Name]
   , retCode :: CodeQ (Cont inp a a)
   , input :: CodeQ (Cursor inp)
   , moreInput :: CodeQ (Cursor inp -> Bool)
   , nextInput :: CodeQ (Cursor inp -> (# InputToken inp, Cursor inp #))
   , farthestInput :: CodeQ (Cursor inp)
-  , farthestExpecting :: CodeQ [ErrorItem (InputToken inp)]
-    -- | Remaining horizon
-  , horizon :: Offset
-    -- | Horizon for each 'call' or 'jump'.
-  , horizonByName :: Map TH.Name Offset
+  , farthestExpecting :: CodeQ (Set SomeFailure)
+    -- | Remaining horizon already checked.
+    -- Use to factorize 'input' length checks,
+    -- instead of checking the 'input' length
+    -- one 'InputToken' at a time at each 'read'.
+    -- Updated by 'checkHorizon'
+    -- and reset elsewhere when needed.
+  , checkedHorizon :: Horizon
+  -- | Used by 'pushInput' and 'loadInput'
+  -- to restore the 'Horizon' at the restored 'input'.
+  , horizonStack :: [Horizon]
+  -- | Output of 'runGenAnalysis'.
+  , finalGenAnalysisByLet :: LetMap GenAnalysis
   }
 
 -- ** Type 'ValueStack'
 data ValueStack vs where
   ValueStackEmpty :: ValueStack '[]
   ValueStackCons ::
-    { valueStackHead :: TermInstr v
+    { valueStackHead :: Splice v
     , valueStackTail :: ValueStack vs
     } -> ValueStack (v ': vs)
 
--- ** Type 'FailStack'
-data FailStack inp es a where
-  FailStackEmpty :: FailStack inp 'Zero a
-  FailStackCons ::
-    { failStackHead :: CodeQ (FailHandler inp a)
-    , failStackTail :: FailStack inp es a
-    } ->
-    FailStack inp ('Succ es) a
-
-instance Stackable Gen where
-  push x k = k
-    { unGen = \ctx -> unGen k ctx
+instance InstrValuable Gen where
+  pushValue x k = k
+    { unGen = \ctx -> {-trace "unGen.pushValue" $-} unGen k ctx
       { valueStack = ValueStackCons x (valueStack ctx) }
     }
-  pop k = k
-    { unGen = \ctx -> unGen k ctx
+  popValue k = k
+    { unGen = \ctx -> {-trace "unGen.popValue" $-} unGen k ctx
       { valueStack = valueStackTail (valueStack ctx) }
     }
-  liftI2 f k = k
-    { unGen = \ctx -> unGen k ctx
+  lift2Value f k = k
+    { unGen = \ctx -> {-trace "unGen.lift2Value" $-} unGen k ctx
       { valueStack =
-        let ValueStackCons y (ValueStackCons x xs) = valueStack ctx in
-        ValueStackCons (f H.:@ x H.:@ y) xs
+        let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
+        ValueStackCons (f Prod..@ x Prod..@ y) vs
       }
     }
-  swap k = k
-    { unGen = \ctx -> unGen k ctx
+  swapValue k = k
+    { unGen = \ctx -> {-trace "unGen.swapValue" $-} unGen k ctx
       { valueStack =
-          let ValueStackCons y (ValueStackCons x xs) = valueStack ctx in
-          ValueStackCons x (ValueStackCons y xs)
+          let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
+          ValueStackCons x (ValueStackCons y vs)
       }
     }
-instance Branchable Gen where
-  case_ kx ky = Gen
-    { minHorizon = \ls ->
-      minHorizon kx ls `min` minHorizon ky ls
-    , unGen = \ctx ->
+instance InstrBranchable Gen where
+  caseBranch kx ky = Gen
+    { genAnalysisByLet = genAnalysisByLet kx <> genAnalysisByLet ky
+    , genAnalysis = \final ct -> altGenAnalysis $ genAnalysis kx final ct :| [genAnalysis ky final ct]
+    , unGen = \ctx -> {-trace "unGen.caseBranch" $-}
       let ValueStackCons v vs = valueStack ctx in
       [||
         case $$(genCode v) of
-          Left  x -> $$(unGen kx ctx{ valueStack = ValueStackCons (H.Term [||x||]) vs })
-          Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (H.Term [||y||]) vs })
+          Left  x -> $$(unGen kx ctx{ valueStack = ValueStackCons (splice [||x||]) vs })
+          Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (splice [||y||]) vs })
       ||]
     }
-  choices fs ks kd = Gen
-    { minHorizon = \ls -> minimum $
-        minHorizon kd ls :
-        (($ ls) . minHorizon <$> ks)
-    , unGen = \ctx ->
+  choicesBranch fs ks kd = Gen
+    { genAnalysisByLet = sconcat $ genAnalysisByLet kd :| (genAnalysisByLet <$> ks)
+    , genAnalysis = \final ct -> altGenAnalysis $ (\k -> genAnalysis k final ct) <$> (kd:|ks)
+    , unGen = \ctx -> {-trace "unGen.choicesBranch" $-}
       let ValueStackCons v vs = valueStack ctx in
       go ctx{valueStack = vs} v fs ks
     }
     where
     go ctx x (f:fs') (k:ks') = [||
-      if $$(genCode (f H.:@ x))
-      then $$(unGen k ctx)
-      else $$(go ctx x fs' ks')
+      if $$(genCode (f Prod..@ x))
+      then
+        let _ = "choicesBranch.then" in
+        $$({-trace "unGen.choicesBranch.k" $-} unGen k ctx)
+      else
+        let _ = "choicesBranch.else" in
+        $$(go ctx x fs' ks')
       ||]
     go ctx _ _ _ = unGen kd ctx
-instance Failable Gen where
-  fail failExp = Gen
-    { minHorizon = \_hs -> 0
-    , unGen = \ctx@GenCtx{} -> [||
-      let (# farInp, farExp #) =
-            case $$compareOffset $$(farthestInput ctx) $$(input ctx) of
-              LT -> (# $$(input ctx), failExp #)
-              EQ -> (# $$(farthestInput ctx), ($$(farthestExpecting ctx) <> failExp) #)
-              GT -> (# $$(farthestInput ctx), $$(farthestExpecting ctx) #) in
-      $$(failStackHead (failStack ctx))
-        $$(input ctx) farInp farExp
+instance InstrExceptionable Gen where
+  raise exn = Gen
+    { genAnalysisByLet = HM.empty
+    , genAnalysis = \_final _ct -> GenAnalysis
+        { minReads = Left (ExceptionLabel exn)
+        , mayRaise = Map.singleton (ExceptionLabel exn) ()
+        }
+    , unGen = \ctx@GenCtx{} -> {-trace ("unGen.raise: "<>show exn) $-} [||
+        $$(raiseException ctx (ExceptionLabel exn))
+          (ExceptionLabel $$(TH.liftTyped exn))
+          {-failInp-}$$(input ctx)
+          {-farInp-}$$(input ctx)
+          $$(farthestExpecting ctx)
       ||]
     }
-  popFail k = k
-    { unGen = \ctx ->
-      let FailStackCons _e es = failStack ctx in
-      unGen k ctx{failStack = es}
+  fail fs = Gen
+    { genAnalysisByLet = HM.empty
+    , genAnalysis = \_final _ct -> GenAnalysis
+        { minReads = Left ExceptionFailure
+        , mayRaise = Map.singleton ExceptionFailure ()
+        }
+    , unGen = \ctx@GenCtx{} -> {-trace ("unGen.fail: "<>show exn) $-}
+      if null fs
+      then [|| -- Raise without updating the farthest error.
+          $$(raiseException ctx ExceptionFailure)
+            ExceptionFailure
+            {-failInp-}$$(input ctx)
+            $$(farthestInput ctx)
+            $$(farthestExpecting ctx)
+        ||]
+      else raiseFailure ctx [||fs||]
     }
-  catchFail ok ko = Gen
-    { minHorizon = \ls -> minHorizon ok ls `min` minHorizon ko ls
-    , unGen = \ctx@GenCtx{} -> [||
-      let _ = "catchFail" in $$(unGen ok ctx
-        { failStack = FailStackCons [|| \(!failInp) (!farInp) (!farExp) ->
-            -- trace ("catchFail: " <> "farExp="<>show farExp) $
-            $$(unGen ko ctx
-              -- Push the input as it was when entering the catchFail.
-              { valueStack = ValueStackCons (H.Term (input ctx)) (valueStack ctx)
-              -- Move the input to the failing position.
-              , input = [||failInp||]
-              -- Set the farthestInput to the farthest computed by 'fail'
-              , farthestInput = [||farInp||]
-              , farthestExpecting = [||farExp||]
-              })
-          ||] (failStack ctx)
-        })
-      ||]
+  commit exn k = k
+    { unGen = \ctx -> {-trace ("unGen.commit: "<>show exn) $-}
+      unGen k ctx{catchStackByLabel =
+        Map.update (\case
+            _r0:|(r1:rs) -> Just (r1:|rs)
+            _ -> Nothing
+          )
+        exn (catchStackByLabel ctx)
+      }
     }
-instance Inputable Gen where
-  loadInput k = k
-    { unGen = \ctx ->
-      let ValueStackCons input vs = valueStack ctx in
-      unGen k ctx
-        { valueStack = vs
-        , input = genCode input
-        , horizon = 0
+  catch exn ok ko = Gen
+    { genAnalysisByLet = genAnalysisByLet ok <> genAnalysisByLet ko
+    , genAnalysis = \final ct ->
+        let okGA = genAnalysis ok final ct in
+        altGenAnalysis $
+          okGA{ mayRaise = Map.delete exn (mayRaise okGA) } :|
+          [ genAnalysis ko final ct ]
+    , unGen = \ctx@GenCtx{} -> {-trace ("unGen.catch: "<>show exn) $-} [||
+        let _ = $$(liftTypedString ("catch "<>show exn)) in
+        let catchHandler !_exn !failInp !farInp !farExp =
+              let _ = $$(liftTypedString ("catch.ko "<>show exn)) in
+              $$({-trace ("unGen.catch.ko: "<>show exn) $-} unGen ko ctx
+                -- Push 'input' and 'checkedHorizon'
+                -- as they were when entering 'catch',
+                -- they will be available to 'loadInput', if any.
+                { valueStack =
+                    ValueStackCons (splice (input ctx)) $
+                    --ValueStackCons (Prod.var [||exn||]) $
+                    valueStack ctx
+                , horizonStack =
+                    checkedHorizon ctx : horizonStack ctx
+                -- Note that 'catchStackByLabel' is reset.
+                -- Move the input to the failing position.
+                , input = [||failInp||]
+                -- The 'checkedHorizon' at the 'raise's are not known here.
+                -- Nor whether 'failInp' is after 'checkedHorizon' or not.
+                -- Hence fallback to a safe value.
+                , checkedHorizon = 0
+                -- Set the farthestInput to the farthest computed in 'fail'.
+                , farthestInput = [||farInp||]
+                , farthestExpecting = [||farExp||]
+                })
+        in
+        $$({-trace ("unGen.catch.ok: "<>show es) $-} unGen ok ctx
+        { catchStackByLabel =
+            Map.insertWith (<>) exn
+              (NE.singleton [||catchHandler||])
+              (catchStackByLabel ctx)
         }
+      ) ||]
     }
+instance InstrInputable Gen where
   pushInput k = k
     { unGen = \ctx ->
-      unGen k ctx{valueStack = ValueStackCons (H.Term (input ctx)) (valueStack ctx)}
+        {-trace "unGen.pushInput" $-}
+        unGen k ctx
+          { valueStack = splice (input ctx) `ValueStackCons` valueStack ctx
+          , horizonStack = checkedHorizon ctx : horizonStack ctx
+          }
     }
-instance Routinable Gen where
-  call (LetName n) k = k
-    { minHorizon = \hs -> hs Map.! n
-    , unGen = \ctx -> [||
-      let _ = "call" in
-      $$(Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
-        {-ok-}$$(generateSuspend k ctx)
-        $$(input ctx)
-        $! $$(failStackHead (failStack ctx))
-      ||]
-    }
-  jump (LetName n) = Gen
-    { minHorizon = \hs -> hs Map.! n
-    , unGen = \ctx -> [||
-      let _ = "jump" in
-      $$(Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
-        {-ok-}$$(retCode ctx)
-        $$(input ctx)
-        $! $$(failStackHead (failStack ctx))
-      ||]
+  loadInput k = k
+    { unGen = \ctx ->
+        {-trace "unGen.loadInput" $-}
+        let ValueStackCons input vs = valueStack ctx in
+        let (h, hs) = case horizonStack ctx of
+                        [] -> (0, [])
+                        x:xs -> (x, xs) in
+        unGen k ctx
+          { valueStack = vs
+          , horizonStack = hs
+          , input = genCode input
+          , checkedHorizon = h
+          }
+    , genAnalysis = \final ct -> GenAnalysis
+        { minReads = 0 <$ minReads (genAnalysis k final ct)
+        , mayRaise = mayRaise (genAnalysis k final ct)
+        }
     }
-  ret = Gen
-    { minHorizon = \_hs -> 0
-    , unGen = \ctx -> unGen (generateResume (retCode ctx)) ctx
+instance InstrCallable Gen where
+  defLet defs k = k
+    { unGen = \ctx@GenCtx{} ->
+        {-trace ("unGen.defLet: defs="<>show (HM.keys defs)) $-}
+        TH.unsafeCodeCoerce $ do
+          decls <- traverse (makeDecl ctx) (HM.toList defs)
+          body <- TH.unTypeQ $ TH.examineCode $
+            {-trace "unGen.defLet.body" $-}
+            unGen k ctx
+          return $ TH.LetE (
+            -- | Try to output more deterministic code to be able to golden test it,
+            -- at the cost of more computations (at compile-time only though).
+            List.sortBy (compare `on` TH.hideName) $
+            toList decls
+            ) body
+    , genAnalysisByLet =
+        foldMap' (\(SomeLet sub) -> genAnalysisByLet sub) defs <>
+        ((\(SomeLet sub) -> genAnalysis sub) <$> defs) <>
+        genAnalysisByLet k
     }
-  subroutine (LetName n) sub k = Gen
-    { minHorizon = \hs ->
-        minHorizon k $
-          Map.insert n (minHorizon sub (Map.insert n 0 hs)) hs
-    , unGen = \ctx -> Code $ TH.unsafeTExpCoerce $ do
+    where
+    makeDecl ctx (n, SomeLet sub) = do
       body <- TH.unTypeQ $ TH.examineCode $ [|| -- buildRec in Parsley
-        -- SubRoutine
-        -- Why using $! at call site and not ! here on ko?
-        \ !ok !inp ko ->
-          $$(unGen sub ctx
+        -- Called by 'call' or 'jump'.
+        \ !ok{-from generateSuspend or retCode-}
+          !inp
+          !koByLabel{- 'catchStackByLabel' from the 'call'-site -} ->
+          $$({-trace ("unGen.defLet.sub: "<>show n) $-} unGen sub ctx
             { valueStack = ValueStackEmpty
-            , failStack = FailStackCons [||ko||] FailStackEmpty
+            -- Build a 'catchStackByLabel' from the one available at the 'call'-site.
+            -- Note that all the 'mayRaise' of the 'sub'routine may not be available,
+            -- hence 'Map.findWithDefault' is used instead of 'Map.!'.
+            , catchStackByLabel = Map.mapWithKey
+                (\lbl () -> NE.singleton [||Map.findWithDefault $$(defaultCatch ctx) lbl koByLabel||])
+                ({-trace ("mayRaise: "<>show n) $-}
+                  mayRaise (finalGenAnalysisByLet ctx HM.! n))
             , input = [||inp||]
-            , retCode = [||ok||]
-            -- , farthestInput = [|inp|]
-            -- , farthestExpecting = [|| [] ||]
-            , horizon = 0
-            , horizonByName = Map.insert n 0 (horizonByName ctx)
+            , retCode = {-trace ("unGen.defLet.sub.retCode: "<>show n) $-} [||ok||]
+
+            -- These are passed by the caller via 'ok' or 'ko'
+            -- , farthestInput = 
+            -- , farthestExpecting = 
+
+            -- Some callers can call this 'defLet'
+            -- with zero 'checkedHorizon', hence use this minimum.
+            -- TODO: maybe it could be improved a bit
+            -- by taking the minimum of the checked horizons
+            -- before all the 'call's and 'jump's to this 'defLet'.
+            , checkedHorizon = 0
             })
         ||]
       let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
-      expr <- TH.unTypeQ (TH.examineCode (unGen k ctx
-        { horizonByName =
-            Map.insert n
-              (minHorizon sub
-                (Map.insert n 0 (horizonByName ctx)))
-              (horizonByName ctx)
-        }))
-      return (TH.LetE [decl] expr)
+      return decl
+  jump (LetName n) = Gen
+    { genAnalysisByLet = HM.empty
+    , genAnalysis = \final ct ->
+        if n`List.elem`ct
+        then GenAnalysis
+          { minReads = Right 0
+          , mayRaise = Map.empty
+          }
+        else (final HM.! n) (n:ct)
+    , unGen = \ctx -> {-trace ("unGen.jump: "<>show n) $-} [||
+      let _ = "jump" in
+      $$(TH.unsafeCodeCoerce (return (TH.VarE n)))
+        {-ok-}$$(retCode ctx)
+        $$(input ctx)
+        $$(liftTypedRaiseByLabel $
+          catchStackByLabel ctx
+          -- Pass only the labels raised by the 'defLet'.
+          `Map.intersection`
+          (mayRaise $ finalGenAnalysisByLet ctx HM.! n)
+        )
+      ||]
+    }
+  call (LetName n) k = k
+    { genAnalysis = \final ct ->
+        if n`List.elem`ct
+        then GenAnalysis
+          { minReads = Right 0
+          , mayRaise = Map.empty
+          }
+        else seqGenAnalysis $
+          (final HM.! n) (n:ct) :|
+          [ genAnalysis k final ct ]
+    , unGen = {-trace ("unGen.call: "<>show n) $-} \ctx ->
+      -- let ks = (Map.keys (catchStackByLabel ctx)) in
+      [||
+      -- let _ = $$(liftTypedString $ "call exceptByLet("<>show n<>")="<>show (Map.keys (Map.findWithDefault Map.empty n (exceptByLet ctx))) <> " catchStackByLabel(ctx)="<> show ks) in
+      $$(TH.unsafeCodeCoerce (return (TH.VarE n)))
+        {-ok-}$$(generateSuspend k ctx{callStack = n : callStack ctx})
+        $$(input ctx)
+        $$(liftTypedRaiseByLabel $
+          catchStackByLabel ctx
+          -- Pass only the labels raised by the 'defLet'.
+          `Map.intersection`
+          (mayRaise $ finalGenAnalysisByLet ctx HM.! n)
+        )
+      ||]
     }
+  ret = Gen
+    { genAnalysisByLet = HM.empty
+    , genAnalysis = \_final _ct -> GenAnalysis
+        { minReads = Right 0
+        , mayRaise = Map.empty
+        }
+    , unGen = \ctx -> {-trace "unGen.ret" $-} unGen ({-trace "unGen.ret.generateResume" $-} generateResume ({-trace "unGen.ret.retCode" $-} retCode ctx)) ctx
+    }
+
+-- | Like 'TH.liftString' but on 'TH.Code'.
+-- Useful to get a 'TH.StringL' instead of a 'TH.ListE'.
+liftTypedString :: String -> TH.Code TH.Q a
+liftTypedString = TH.unsafeCodeCoerce . TH.liftString
+
+-- | Like 'TH.liftTyped' but adjusted to work on 'catchStackByLabel'
+-- which already contains 'CodeQ' terms.
+-- Moreover, only the 'Catcher' at the top of the stack
+-- is needed and thus generated in the resulting 'CodeQ'.
+--
+-- TODO: Use an 'Array' instead of a 'Map'?
+liftTypedRaiseByLabel :: TH.Lift k => Map k (NonEmpty (CodeQ a)) -> CodeQ (Map k a)
+liftTypedRaiseByLabel Map_.Tip = [|| Map_.Tip ||]
+liftTypedRaiseByLabel (Map_.Bin s k (h:|_hs) l r) =
+  [|| Map_.Bin s k $$h $$(liftTypedRaiseByLabel l) $$(liftTypedRaiseByLabel r) ||]
+
+instance TH.Lift a => TH.Lift (Set a) where
+  liftTyped Set_.Tip = [|| Set_.Tip ||]
+  liftTyped (Set_.Bin s a l r) = [|| Set_.Bin $$(TH.liftTyped s) $$(TH.liftTyped a) $$(TH.liftTyped l) $$(TH.liftTyped r) ||]
 
--- | Generate a continuation to be called with 'generateResume',
--- used when 'call' 'ret'urns.
+-- ** Type 'Cont'
+type Cont inp v a =
+  {-farthestInput-}Cursor inp ->
+  {-farthestExpecting-}(Set SomeFailure) ->
+  v ->
+  Cursor inp ->
+  Either (ParsingError inp) a
+
+-- | Generate a 'retCode' 'Cont'inuation to be called with 'generateResume'.
+-- Used when 'call' 'ret'urns.
+-- The return 'v'alue is 'pushValue'-ed on the 'valueStack'.
 generateSuspend ::
-  {-k-}Gen inp (v ': vs) es a ->
-  GenCtx inp vs es a ->
+  {-k-}Gen inp (v ': vs) a ->
+  GenCtx inp vs a ->
   CodeQ (Cont inp v a)
 generateSuspend k ctx = [||
-  let _ = "suspend" in
+  let _ = $$(liftTypedString $ "suspend") in
   \farInp farExp v !inp ->
-    $$(unGen k ctx
-      { valueStack = ValueStackCons (H.Term [||v||]) (valueStack ctx)
+    $$({-trace "unGen.generateSuspend" $-} unGen k ctx
+      { valueStack = ValueStackCons ({-trace "unGen.generateSuspend.value" $-} splice [||v||]) (valueStack ctx)
       , input = [||inp||]
       , farthestInput = [||farInp||]
       , farthestExpecting = [||farExp||]
-      , horizon = 0
+      , checkedHorizon = 0
       }
     )
   ||]
 
--- | Generate a call to the 'generateSuspend' continuation,
--- used when 'call' 'ret'urns.
+-- | Generate a call to the 'generateSuspend' continuation.
+-- Used when 'call' 'ret'urns.
 generateResume ::
   CodeQ (Cont inp v a) ->
-  Gen inp (v ': vs) es a
+  Gen inp (v ': vs) a
 generateResume k = Gen
-  { minHorizon = \_hs -> 0
-  , unGen = \ctx -> [||
+  { genAnalysisByLet = HM.empty
+  , genAnalysis = \_final _ct -> GenAnalysis
+      { minReads = Right 0
+      , mayRaise = Map.empty
+      }
+  , unGen = \ctx -> {-trace "unGen.generateResume" $-} [||
     let _ = "resume" in
     $$k
       $$(farthestInput ctx)
       $$(farthestExpecting ctx)
-      (let _ = "resume.genCode" in $$(genCode (valueStackHead (valueStack ctx))))
+      (let _ = "resume.genCode" in $$({-trace "unGen.generateResume.genCode" $-}
+        genCode $ valueStackHead $ valueStack ctx))
       $$(input ctx)
     ||]
   }
 
-instance Joinable Gen where
+-- ** Type 'Catcher'
+type Catcher inp a =
+  Exception ->
+  {-failInp-}Cursor inp ->
+  {-farInp-}Cursor inp ->
+  {-farExp-}(Set SomeFailure) ->
+  Either (ParsingError inp) a
+
+instance InstrJoinable Gen where
   defJoin (LetName n) sub k = k
-    { minHorizon = \hs ->
-        minHorizon k $
-          Map.insert n (minHorizon sub (Map.insert n 0 hs)) hs
-    , unGen = \ctx -> Code $ TH.unsafeTExpCoerce $ do
-      body <- TH.unTypeQ $ TH.examineCode $ [||
-        \farInp farExp v !inp ->
-          $$(unGen sub ctx
-            { valueStack = ValueStackCons (H.Term [||v||]) (valueStack ctx)
-            , input = [||inp||]
-            , farthestInput = [||farInp||]
-            , farthestExpecting = [||farExp||]
-            , horizon = 0
-            , horizonByName = Map.insert n 0 (horizonByName ctx)
-            })
-        ||]
-      let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
-      expr <- TH.unTypeQ (TH.examineCode (unGen k ctx
-        { horizonByName =
-            Map.insert n
-              (minHorizon sub
-                (Map.insert n 0 (horizonByName ctx)))
-              (horizonByName ctx)
-        }))
-      return (TH.LetE [decl] expr)
+    { unGen =
+        \ctx ->
+        {-trace ("unGen.defJoin: "<>show n) $-}
+        TH.unsafeCodeCoerce $ do
+          next <- TH.unTypeQ $ TH.examineCode $ [||
+            -- Called by 'generateResume'.
+            \farInp farExp v !inp ->
+              $$({-trace ("unGen.defJoin.next: "<>show n) $-} unGen sub ctx
+                { valueStack = ValueStackCons (splice [||v||]) (valueStack ctx)
+                , input = [||inp||]
+                , farthestInput = [||farInp||]
+                , farthestExpecting = [||farExp||]
+                , checkedHorizon = 0
+                {- FIXME:
+                , catchStackByLabel = Map.mapWithKey
+                    (\lbl () -> NE.singleton [||koByLabel Map.! lbl||])
+                    (mayRaise sub raiseLabelsByLetButSub)
+                -}
+                })
+            ||]
+          let decl = TH.FunD n [TH.Clause [] (TH.NormalB next) []]
+          expr <- TH.unTypeQ (TH.examineCode ({-trace ("unGen.defJoin.expr: "<>show n) $-} unGen k ctx))
+          return (TH.LetE [decl] expr)
+    , genAnalysisByLet =
+        (genAnalysisByLet sub <>) $
+        HM.insert n (genAnalysis sub) $
+        genAnalysisByLet k
+    }
+  refJoin (LetName n) = Gen
+    { unGen = \ctx ->
+        {-trace ("unGen.refJoin: "<>show n) $-}
+        unGen (generateResume
+          (TH.unsafeCodeCoerce (return (TH.VarE n)))) ctx
+    , genAnalysisByLet = HM.empty
+    , genAnalysis = \final ct ->
+        if n`List.elem`ct -- FIXME: useless
+        then GenAnalysis
+          { minReads = Right 0
+          , mayRaise = Map.empty
+          }
+        else HM.findWithDefault
+          (error (show (n,ct,HM.keys final)))
+          n final (n:ct)
     }
-  refJoin (LetName n) =
-    generateResume (Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
-instance Readable Gen Char where
-  read farExp p = checkHorizon . checkToken farExp p
+instance InstrReadable Char Gen where
+  read fs p = checkHorizon . checkToken fs p
+instance InstrReadable Word8 Gen where
+  read fs p = checkHorizon . checkToken fs p
 
 checkHorizon ::
+  forall inp vs a.
+  -- Those constraints are not used anyway
+  -- because (TH.Lift SomeFailure) uses 'inputTokenProxy'.
+  Ord (InputToken inp) =>
+  Show (InputToken inp) =>
   TH.Lift (InputToken inp) =>
-  {-ok-}Gen inp vs ('Succ es) a ->
-  Gen inp vs ('Succ es) a
+  NFData (InputToken inp) =>
+  Typeable (InputToken inp) =>
+  {-ok-}Gen inp vs a ->
+  Gen inp vs a
 checkHorizon ok = ok
-  { minHorizon = \hs -> 1 + minHorizon ok hs
-  , unGen = \ctx0@GenCtx{failStack = FailStackCons e es} -> [||
-      -- Factorize failure code
-      let readFail = $$(e) in
+  { genAnalysis = \final ct -> seqGenAnalysis $
+      GenAnalysis { minReads = Right 1
+                  , mayRaise = Map.singleton ExceptionFailure ()
+                  } :|
+      [ genAnalysis ok final ct ]
+  , unGen = \ctx0@GenCtx{} ->
+    {-trace "unGen.checkHorizon" $-}
+    let raiseFail = raiseException ctx0 ExceptionFailure in
+    [||
+      -- Factorize generated code for raising the "fail".
+      let readFail = $$(raiseFail) in
       $$(
-        let ctx = ctx0{ failStack = FailStackCons [||readFail||] es } in
-        if horizon ctx >= 1
-        then unGen ok ctx0{horizon = horizon ctx - 1}
-        else let minHoz = minHorizon ok (horizonByName ctx) in
+        let ctx = ctx0{catchStackByLabel =
+                    Map.adjust (\(_r:|rs) -> [||readFail||] :| rs)
+                      ExceptionFailure (catchStackByLabel ctx0)} in
+        if checkedHorizon ctx >= 1
+        then unGen ok ctx0{checkedHorizon = checkedHorizon ctx - 1}
+        else let minHoriz =
+                    either (\_err -> 0) id $
+                    minReads $ finalGenAnalysis ctx ok in
           [||
           if $$(moreInput ctx)
-               $$(if minHoz > 0
-                 then [||$$shiftRight minHoz $$(input ctx)||]
+               $$(if minHoriz > 0
+                 then [||$$shiftRight minHoriz $$(input ctx)||]
                  else input ctx)
-          then $$(unGen ok ctx{horizon = minHoz})
+          then $$(unGen ok ctx{checkedHorizon = minHoriz})
           else let _ = "checkHorizon.else" in
-            $$(unGen (fail [ErrorItemHorizon (minHoz + 1)]) ctx)
+            -- TODO: return a resuming continuation (eg. Partial)
+            $$(unGen (fail (Set.singleton $ SomeFailure $ FailureHorizon @(InputToken inp) (minHoriz + 1))) ctx)
           ||]
       )
     ||]
   }
 
+-- | @('raiseFailure' ctx fs)@ raises 'ExceptionFailure'
+-- with farthest parameters set to or updated with @(fs)@
+-- according to the relative position of 'input' wrt. 'farthestInput'.
+raiseFailure ::
+  Cursorable (Cursor inp) =>
+  GenCtx inp cs a ->
+  TH.CodeQ (Set SomeFailure) ->
+  TH.CodeQ (Either (ParsingError inp) a)
+raiseFailure ctx fs = [||
+  let failExp = $$fs in
+  let (# farInp, farExp #) =
+        case $$compareOffset $$(farthestInput ctx) $$(input ctx) of
+          LT -> (# $$(input ctx), failExp #)
+          EQ -> (# $$(farthestInput ctx), failExp <> $$(farthestExpecting ctx) #)
+          GT -> (# $$(farthestInput ctx), $$(farthestExpecting ctx) #)
+  in $$(raiseException ctx ExceptionFailure)
+    ExceptionFailure
+    {-failInp-}$$(input ctx) farInp farExp
+  ||]
+-- | @('raiseException' ctx exn)@ raises exception @(exn)@
+-- using any entry in 'catchStackByLabel', or 'defaultCatch' if none.
+raiseException ::
+  GenCtx inp vs a -> Exception ->
+  CodeQ (Exception -> Cursor inp -> Cursor inp -> Set SomeFailure -> Either (ParsingError inp) a)
+raiseException ctx exn =
+  NE.head $ Map.findWithDefault
+    (NE.singleton (defaultCatch ctx))
+    exn (catchStackByLabel ctx)
+
+finalGenAnalysis :: GenCtx inp vs a -> Gen inp cs a -> GenAnalysis
+finalGenAnalysis ctx k =
+  --(\f -> f (error "callTrace")) $
+  (\f -> f (callStack ctx)) $
+  genAnalysis k $
+  ((\f _ct -> f) <$>) $
+  finalGenAnalysisByLet ctx
+
 checkToken ::
-  forall inp vs es a.
-  Ord (InputToken inp) =>
-  TH.Lift (InputToken inp) =>
-  [ErrorItem (InputToken inp)] ->
-  {-predicate-}TermInstr (InputToken inp -> Bool) ->
-  {-ok-}Gen inp (InputToken inp ': vs) ('Succ es) a ->
-  Gen inp vs ('Succ es) a
-checkToken farExp p ok = ok
-  { unGen = \ctx -> [||
+  Set SomeFailure ->
+  {-predicate-}Splice (InputToken inp -> Bool) ->
+  {-ok-}Gen inp (InputToken inp ': vs) a ->
+  Gen inp vs a
+checkToken fs p ok = ok
+  { unGen = \ctx -> {-trace "unGen.read" $-} [||
     let !(# c, cs #) = $$(nextInput ctx) $$(input ctx) in
-    if $$(genCode p) c
-    then $$(unGen ok ctx
-      { valueStack = ValueStackCons (H.Term [||c||]) (valueStack ctx)
-      , input = [||cs||]
-      })
-    else let _ = "checkToken.else" in $$(unGen (fail farExp) ctx)
-    ||]
+    $$(genCode $
+      Prod.ifThenElse
+        (p Prod..@ splice [||c||])
+        (splice $ unGen ok ctx
+          { valueStack = ValueStackCons (splice [||c||]) (valueStack ctx)
+          , input = [||cs||]
+          })
+        (splice [||
+          let _ = "checkToken.else" in
+          $$(unGen (fail fs) ctx)
+        ||])
+    )||]
   }
-