doc: fix reference to Symantic.Typed
[haskell/symantic-parser.git] / src / Symantic / Parser / Machine / Generate.hs
index c4a22ee2ce56102df5e38d6f445a2a2e547d40f4..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.NonEmpty (NonEmpty(..))
+import Data.Map (Map)
 import Data.Maybe (Maybe(..))
-import Data.Ord (Ord, Ordering(..))
+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 qualified Data.Eq as Eq
+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 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
+
+--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.
-newtype Gen inp vs es a = Gen { unGen ::
-  GenCtx inp vs es a ->
-  CodeQ (Either (ParsingError inp) a)
-}
+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 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
+  ,  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'
+-- | 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
+-- 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 'SubRoutine'
-type SubRoutine inp v a =
-  {-ok-}Cont inp v a ->
-  Cursor inp ->
-  {-ko-}FailHandler inp a ->
-  Either (ParsingError inp) a
-
--- ** 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 (Gen 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
-  $$(k GenCtx
-    { valueStack = ValueStackEmpty
-    , failStack = FailStackCons [||finalFail||] FailStackEmpty
-    , retCode = [||finalRet||]
-    , input = [||init||]
-    , nextInput = [||readNext||]
-    , moreInput = [||readMore||]
-    -- , farthestError = [||Nothing||]
-    , farthestInput = [||init||]
-    , farthestExpecting = [|| [] ||]
-    })
-  ||]
-
 -- ** 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)]
+  , 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 ::
-    -- TODO: maybe use H.Haskell instead of CodeQ ?
-    -- as in https://github.com/j-mie6/ParsleyHaskell/popFail/3ec0986a5017866919a6404c14fe78678b7afb46
-    { valueStackHead :: CodeQ 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 = Gen $ \ctx -> unGen k ctx
-    { valueStack = ValueStackCons (liftCode x) (valueStack ctx) }
-  pop k = Gen $ \ctx -> unGen k ctx
-    { valueStack = valueStackTail (valueStack ctx) }
-  liftI2 f k = Gen $ \ctx -> unGen k ctx
-    { valueStack =
-      let ValueStackCons y (ValueStackCons x xs) = valueStack ctx in
-      ValueStackCons (liftCode2 f x y) xs
+instance InstrValuable Gen where
+  pushValue x k = k
+    { unGen = \ctx -> {-trace "unGen.pushValue" $-} unGen k ctx
+      { valueStack = ValueStackCons x (valueStack ctx) }
     }
-  swap k = Gen $ \ctx -> unGen k ctx
-    { valueStack =
-        let ValueStackCons y (ValueStackCons x xs) = valueStack ctx in
-        ValueStackCons x (ValueStackCons y xs)
+  popValue k = k
+    { unGen = \ctx -> {-trace "unGen.popValue" $-} unGen k ctx
+      { valueStack = valueStackTail (valueStack ctx) }
+    }
+  lift2Value f k = k
+    { unGen = \ctx -> {-trace "unGen.lift2Value" $-} unGen k ctx
+      { valueStack =
+        let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
+        ValueStackCons (f Prod..@ x Prod..@ y) vs
+      }
+    }
+  swapValue k = k
+    { unGen = \ctx -> {-trace "unGen.swapValue" $-} unGen k ctx
+      { valueStack =
+          let ValueStackCons y (ValueStackCons x vs) = valueStack ctx in
+          ValueStackCons x (ValueStackCons y vs)
+      }
+    }
+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 (splice [||x||]) vs })
+          Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons (splice [||y||]) vs })
+      ||]
+    }
+  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
     }
-instance Branchable Gen where
-  case_ kx ky = Gen $ \ctx ->
-    let ValueStackCons v vs = valueStack ctx in
-    [||
-      case $$v of
-        Left  x -> $$(unGen kx ctx{ valueStack = ValueStackCons [||x||] vs })
-        Right y -> $$(unGen ky ctx{ valueStack = ValueStackCons [||y||] vs })
-    ||]
-  choices fs ks kd = Gen $ \ctx ->
-    let ValueStackCons v vs = valueStack ctx in
-    go ctx{valueStack = vs} v fs ks
     where
-    go ctx x (f:fs') (Gen k:ks') = [||
-      if $$(liftCode1 f x) then $$(k ctx)
-      else $$(go ctx x fs' ks')
+    go ctx x (f:fs') (k: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 $ \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
-    {-
-    trace ("fail: "
-      <>" failExp="<>show @[ErrorItem Char] failExp
-      <>" farthestExpecting="<>show @[ErrorItem Char] ($$(farthestExpecting ctx))
-      <>" farExp="<>show @[ErrorItem Char] farExp) $
-    -}
-    $$(failStackHead (failStack ctx))
-      $$(input ctx) farInp farExp
-    ||]
-  popFail k = Gen $ \ctx ->
-    let FailStackCons _e es = failStack ctx in
-    unGen k ctx{failStack = es}
-  catchFail ok ko = Gen $ \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 (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||]
+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)
+      ||]
+    }
+  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||]
+    }
+  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)
+      }
+    }
+  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 ->
+        {-trace "unGen.pushInput" $-}
+        unGen k ctx
+          { valueStack = splice (input ctx) `ValueStackCons` valueStack ctx
+          , horizonStack = checkedHorizon ctx : horizonStack 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)
+        }
+    }
+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
+    }
+    where
+    makeDecl ctx (n, SomeLet sub) = do
+      body <- TH.unTypeQ $ TH.examineCode $ [|| -- buildRec in Parsley
+        -- 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
+            -- 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 = {-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
             })
-        ||] (failStack ctx)
-      })
-    ||]
-instance Inputable Gen where
-  loadInput k = Gen $ \ctx ->
-    let ValueStackCons input vs = valueStack ctx in
-    unGen k ctx{valueStack = vs, input}
-  pushInput k = Gen $ \ctx ->
-    unGen k ctx{valueStack = ValueStackCons (input ctx) (valueStack ctx)}
-instance Routinable Gen where
-  call (LetName n) k = Gen $ \ctx -> [||
-    let _ = "call" in
-    $$(Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
-      $$(suspend k ctx)
-      $$(input ctx)
-      $! $$(failStackHead (failStack ctx))
-    ||]
-  jump (LetName n) = Gen $ \ctx -> [||
-    let _ = "jump" in
-    $$(Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
-      $$(retCode ctx)
-      $$(input ctx)
-      $! $$(failStackHead (failStack ctx))
-    ||]
-  ret = Gen $ \ctx -> unGen (resume (retCode ctx)) ctx
-  subroutine (LetName n) sub k = Gen $ \ctx -> Code $ TH.unsafeTExpCoerce $ 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
-          { valueStack = ValueStackEmpty
-          , failStack = FailStackCons [||ko||] FailStackEmpty
-          , input = [||inp||]
-          , retCode = [||ok||]
-          -- , farthestInput = [|inp|]
-          -- , farthestExpecting = [|| [] ||]
-          })
+        ||]
+      let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
+      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)
+        )
       ||]
-    let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
-    expr <- TH.unTypeQ (TH.examineCode (unGen k ctx))
-    return (TH.LetE [decl] expr)
+    }
+  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) ||]
+
+-- ** Type 'Cont'
+type Cont inp v a =
+  {-farthestInput-}Cursor inp ->
+  {-farthestExpecting-}(Set SomeFailure) ->
+  v ->
+  Cursor inp ->
+  Either (ParsingError inp) a
 
-suspend ::
-  {-k-}Gen inp (v ': vs) es a ->
-  GenCtx inp vs es 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) a ->
+  GenCtx inp vs a ->
   CodeQ (Cont inp v a)
-suspend k ctx = [||
-  let _ = "suspend" in
+generateSuspend k ctx = [||
+  let _ = $$(liftTypedString $ "suspend") in
   \farInp farExp v !inp ->
-    $$(unGen k ctx
-      { valueStack = ValueStackCons [||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||]
+      , checkedHorizon = 0
       }
     )
   ||]
 
-resume :: CodeQ (Cont inp v a) -> Gen inp (v ': vs) es a
-resume k = Gen $ \ctx -> [||
-  let _ = "resume" in
-  $$k
-    $$(farthestInput ctx)
-    $$(farthestExpecting ctx)
-    $$(valueStackHead (valueStack ctx))
-    $$(input ctx)
-  ||]
+-- | Generate a call to the 'generateSuspend' continuation.
+-- Used when 'call' 'ret'urns.
+generateResume ::
+  CodeQ (Cont inp v a) ->
+  Gen inp (v ': vs) a
+generateResume k = Gen
+  { 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 $$({-trace "unGen.generateResume.genCode" $-}
+        genCode $ valueStackHead $ valueStack ctx))
+      $$(input ctx)
+    ||]
+  }
 
-instance Joinable Gen where
-  defJoin (LetName n) sub k = Gen $ \ctx -> Code $ TH.unsafeTExpCoerce $ do
-    body <- TH.unTypeQ $ TH.examineCode $ [||
-      \farInp farExp v !inp ->
-        $$(unGen sub ctx
-          { valueStack = ValueStackCons [||v||] (valueStack ctx)
-          , input = [||inp||]
-          , farthestInput = [||farInp||]
-          , farthestExpecting = [||farExp||]
-          })
-      ||]
-    let decl = TH.FunD n [TH.Clause [] (TH.NormalB body) []]
-    expr <- TH.unTypeQ (TH.examineCode (unGen k ctx))
-    return (TH.LetE [decl] expr)
-  refJoin (LetName n) =
-    resume (Code (TH.unsafeTExpCoerce (return (TH.VarE n))))
-instance Readable Gen Char where
-  read farExp p k =
-    -- TODO: piggy bank
-    maybeEmitCheck (Just 1) k
-    where
-    maybeEmitCheck Nothing ok = sat (liftCode p) ok (fail farExp)
-    maybeEmitCheck (Just n) ok = Gen $ \ctx ->
-      let FailStackCons e es = failStack ctx in
-      [||
-      let readFail = $$(e) in -- Factorize failure code
-      $$((`unGen` ctx{failStack = FailStackCons [||readFail||] es}) $ emitLengthCheck n
-        {-ok-}(sat (liftCode p) ok
-          {-ko-}(fail farExp))
-        {-ko-}(fail farExp))
-      ||]
+-- ** Type 'Catcher'
+type Catcher inp a =
+  Exception ->
+  {-failInp-}Cursor inp ->
+  {-farInp-}Cursor inp ->
+  {-farExp-}(Set SomeFailure) ->
+  Either (ParsingError inp) a
 
-sat ::
-  forall inp vs es a.
-  -- Cursorable (Cursor inp) =>
-  -- InputToken inp ~ Char =>
+instance InstrJoinable Gen where
+  defJoin (LetName n) sub k = k
+    { 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)
+    }
+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) =>
-  {-predicate-}CodeQ (InputToken inp -> Bool) ->
-  {-ok-}Gen inp (InputToken inp ': vs) ('Succ es) a ->
-  {-ko-}Gen inp vs ('Succ es) a ->
-  Gen inp vs ('Succ es) a
-sat p ok ko = Gen $ \ctx -> [||
-  let !(# c, cs #) = $$(nextInput ctx) $$(input ctx) in
-  if $$p c
-  then $$(unGen ok ctx
-    { valueStack = ValueStackCons [||c||] (valueStack ctx)
-    , input = [||cs||]
-    })
-  else let _ = "sat.else" in $$(unGen ko ctx)
-  ||]
-
-{-
-evalSat ::
-  -- Cursorable inp =>
-  -- HandlerOps inp =>
-  InstrPure (Char -> Bool) ->
-  Gen inp (Char ': vs) ('Succ es) a ->
-  Gen inp vs ('Succ es) a
-evalSat p k = do
-  bankrupt <- asks isBankrupt
-  hasChange <- asks hasCoin
-  if | bankrupt -> maybeEmitCheck (Just 1) <$> k
-     | hasChange -> maybeEmitCheck Nothing <$> local spendCoin k
-     | otherwise -> local breakPiggy (maybeEmitCheck . Just <$> asks coins <*> local spendCoin k)
-  where
-  maybeEmitCheck Nothing mk ctx = sat (genDefunc p) mk (raise ctx) ctx
-  maybeEmitCheck (Just n) mk ctx =
-    [|| let bad = $$(raise ctx) in $$(emitLengthCheck n (sat (genDefunc p) mk [||bad||]) [||bad||] ctx)||]
--}
+  NFData (InputToken inp) =>
+  Typeable (InputToken inp) =>
+  {-ok-}Gen inp vs a ->
+  Gen inp vs a
+checkHorizon ok = ok
+  { 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{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 minHoriz > 0
+                 then [||$$shiftRight minHoriz $$(input ctx)||]
+                 else input ctx)
+          then $$(unGen ok ctx{checkedHorizon = minHoriz})
+          else let _ = "checkHorizon.else" in
+            -- TODO: return a resuming continuation (eg. Partial)
+            $$(unGen (fail (Set.singleton $ SomeFailure $ FailureHorizon @(InputToken inp) (minHoriz + 1))) ctx)
+          ||]
+      )
+    ||]
+  }
 
-emitLengthCheck ::
-  TH.Lift (InputToken inp) =>
-  Int -> Gen inp vs es a -> Gen inp vs es a -> Gen inp vs es a
-emitLengthCheck 0 ok _ko = ok
-emitLengthCheck 1 ok ko = Gen $ \ctx -> [||
-  if $$(moreInput ctx) $$(input ctx)
-  then $$(unGen ok ctx)
-  else let _ = "sat.length-check.else" in $$(unGen ko ctx)
-  ||]
-{-
-emitLengthCheck n ok ko ctx = Gen $ \ctx -> [||
-  if $$moreInput ($$shiftRight $$(input ctx) (n - 1))
-  then $$(unGen ok ctx)
-  else $$(unGen ko ctx {farthestExpecting = [||farExp||]})
+-- | @('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
 
-liftCode :: InstrPure a -> CodeQ a
-liftCode = trans
-{-# INLINE liftCode #-}
-
-liftCode1 :: InstrPure (a -> b) -> CodeQ a -> CodeQ b
-liftCode1 p a = case p of
-  InstrPureSameOffset -> [|| $$sameOffset $$a ||]
-  InstrPureHaskell h -> go a h
-  where
-  go :: CodeQ a -> H.Haskell (a -> b) -> CodeQ b
-  go qa = \case
-    (H.:$) -> [|| \x -> $$qa x ||]
-    (H.:.) -> [|| \g x -> $$qa (g x) ||]
-    H.Flip -> [|| \x y -> $$qa y x ||]
-    (H.:.) H.:@ f H.:@ g -> [|| $$(go (go qa g) f) ||]
-    H.Const -> [|| \_ -> $$qa ||]
-    H.Flip H.:@ H.Const -> H.id
-    h@(H.Flip H.:@ _f) -> [|| \x -> $$(liftCode2 (InstrPureHaskell h) qa [||x||]) ||]
-    H.Eq x -> [|| $$(trans x) Eq.== $$qa ||]
-    H.Id -> qa
-    h -> [|| $$(trans h) $$qa ||]
-
-liftCode2 :: InstrPure (a -> b -> c) -> CodeQ a -> CodeQ b -> CodeQ c
-liftCode2 p a b = case p of
-  InstrPureSameOffset -> [|| $$sameOffset $$a $$b ||]
-  InstrPureHaskell h -> go a b h
-  where
-  go :: CodeQ a -> CodeQ b -> H.Haskell (a -> b -> c) -> CodeQ c
-  go qa qb = \case
-    (H.:$) -> [|| $$qa $$qb ||]
-    (H.:.) -> [|| \x -> $$qa ($$qb x) ||]
-    H.Flip -> [|| \x -> $$qa x $$qb ||]
-    H.Flip H.:@ H.Const -> [|| $$qb ||]
-    H.Flip H.:@ f -> go qb qa f
-    H.Const -> [|| $$qa ||]
-    H.Cons -> [|| $$qa : $$qb ||]
-    h -> [|| $$(trans h) $$qa $$qb ||]
+checkToken ::
+  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
+    $$(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)
+        ||])
+    )||]
+  }