{-
 -   The Lambda Shell, an interactive environment for evaluating pure untyped lambda terms.
 -   Copyright (C) 2005-2011, Robert Dockins
 -
 -   This program is free software; you can redistribute it and/or modify
 -   it under the terms of the GNU General Public License as published by
 -   the Free Software Foundation; either version 2 of the License, or
 -   (at your option) any later version.
 -
 -   This program is distributed in the hope that it will be useful,
 -   but WITHOUT ANY WARRANTY; without even the implied warranty of
 -   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 -   GNU General Public License for more details.
 -
 -   You should have received a copy of the GNU General Public License
 -   along with this program; if not, write to the Free Software
 -   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 -}

{- |
  This module defines CPS transformations on lambda terms.
-}

module CPS
( simple_cps
, eta_cps
, onepass_cps
, CPS
) where

import Lambda

type CPS m
     = Bindings () String
    -> PureLambda () String
    -> m (PureLambda () String)

-- | The simple CPS transform defined by Plotkin
simple_cps :: Monad m => CPS m

simple_cps b t = do
    x <- do_simple_cps b t
    return (App () x (Lam () "q" (Var () 0)))

do_simple_cps :: Monad m => CPS m

do_simple_cps b (Binding _ name) =
    lookupBindingM name b >>=
       maybe (return (Lam () "k" $ App () (Var () 0) $ (Binding () name)))
             (do_simple_cps b)

do_simple_cps b (Var _ i) =
    return (Lam () "k" $ App () (Var () 0) $ (Var () (i+1)))

do_simple_cps b (Lam _ l t) = do
    t' <- do_simple_cps b (lamShift 1 1 t)
    return
      (Lam () "k" $ App () (Var () 0) $ Lam () l t')

do_simple_cps b (App _ t1 t2) = do
    t1' <- do_simple_cps b (lamShift 0 1 t1)
    t2' <- do_simple_cps b (lamShift 0 2 t2)
    return
      (Lam () "k" $ App () t1' $
         Lam () "m" $ App () t2' $
           Lam () "n" $
             App () (App () (Var () 1) (Var () 0)) (Var () 2))


-- | A version of Plotkin's CPS transform with additional
--   eta expansions, preparing for the one-pass
--   simplifying transform
eta_cps :: Monad m => CPS m
eta_cps b t = do
   x <- do_eta_cps b t
   return (App () x (Lam () "q" (Var () 0)))

do_eta_cps :: Monad m => CPS m

do_eta_cps b (Binding _ name) =
    lookupBindingM name b >>=
      maybe (return (Lam () "k" $ App () (Var () 0) $ (Binding () name)))
            (do_simple_cps b)

do_eta_cps b (Var _ i) =
    return (Lam () "k" $ App () (Var () 0) $ (Var () (i+1)))


do_eta_cps b (Lam _ l t) = do
    t' <- do_eta_cps b (lamShift 2 1 (lamShift 0 1 t))
    return
      (Lam () "k" $ App () (Var () 0) $
        Lam () l $
          Lam () "kk" $ App () t' $
            Lam () "m" $ App () (Var () 1) (Var () 0)
      )


do_eta_cps b (App _ t1 t2) = do
    t1' <- do_eta_cps b (lamShift 0 1 t1)
    t2' <- do_eta_cps b (lamShift 0 2 t2)
    return
      (Lam () "k" $ App () t1' $
        Lam () "m" $ App () t2' $
          Lam () "n" $
            App () (App () (Var () 1) (Var () 0)) $
              Lam () "a" $ App () (Var () 3) (Var () 0)
      )




-- | The \"one-pass\" properly-tail-recursive CPS tranform due to Danvy and Filinski,
--   from the paper \"Representing Control: A Study of the CPS Transformation\",
--   in /Mathematical Structures in Computer Science/, 1991.
--
--   Here is is actually implemented as a two-pass tranform.
--   In the first pass we create the eta-expanded version and note
--   which redexes are adminstrative.  In the second pass all administrative
--   redexes are reduced, leaving only the dynamic redexes.

onepass_cps :: Monad m => CPS m
onepass_cps b t = do
   x <- do_onepass_cps b t
   simplify_onepass (App True x (Lam True "q" (Var True 0)))


simplify_onepass
    :: Monad m
    => PureLambda Bool String
    -> m (PureLambda () String)

simplify_onepass (Binding _ _) = fail "bug: binding found in simplify_onepass"

simplify_onepass (Var False i)     = return (Var () i)
simplify_onepass (Lam False l t)   = simplify_onepass t >>= \t' -> return (Lam () l t')
simplify_onepass (App False t1 t2) = do
    t1' <- simplify_onepass t1
    t2' <- simplify_onepass t2
    return (App () t1' t2')

simplify_onepass (App True (Lam True _ t) s) = simplify_onepass (lamSubst s t)
simplify_onepass t  = fail $ "bug: found unexpected administrative terms in simplify_onepass\n"++(show t)


do_onepass_cps
    :: Monad m
    => Bindings () String
    -> PureLambda () String
    -> m (PureLambda Bool String)

do_onepass_cps b (Binding _ name) =
     lookupBindingM name b >>= 
        maybe (return (Lam True "k" $ App True (Var True 0) $ (Binding False name)))
              (do_onepass_cps b)

do_onepass_cps b (Var _ i) =
     return (Lam True "k" $ App True (Var True 0) $ (Var False (i+1)))

do_onepass_cps b (Lam _ l t) = do
    t' <- do_onepass_cps_tail b (lamShift 2 1 (lamShift 0 1 t))
    return
       (Lam True "k0" $ App True (Var True 0) $
          Lam False l $ Lam False "k" $ App True t' $ (Var False 0)
       )

do_onepass_cps b (App _ t1 t2) = do
    t1' <- do_onepass_cps b (lamShift 0 1 t1)
    t2' <- do_onepass_cps b (lamShift 0 2 t2)
    return
        (Lam True "k0" $ App True t1' $
           Lam True "m0" $ App True t2' $
             Lam True "n" $
               App False (App False (Var True 1) (Var True 0)) $
                 Lam False "a" $ App True (Var True 3) (Var False 0)
        )


do_onepass_cps_tail
    :: Monad m
    => Bindings () String
    -> PureLambda () String
    -> m (PureLambda Bool String)

do_onepass_cps_tail b (Binding _ name) =
     lookupBindingM name b >>=
       maybe (return (Lam True "k0" (App False (Var True 0) (Binding False name))))
             (do_onepass_cps_tail b)

do_onepass_cps_tail b (Var _ i) =
    return (Lam True "k0" (App False (Var True 0) (Var False (i+1))))

do_onepass_cps_tail b (Lam _ l t) = do
    t' <- do_onepass_cps_tail b (lamShift 2 1 (lamShift 0 1 t))
    return
      (Lam True "k0" $ App False (Var True 0) $
         Lam False l $ Lam False "k" $ App True t' $ (Var False 0)
      )

do_onepass_cps_tail b (App _ t1 t2) = do
    t1' <- do_onepass_cps b (lamShift 0 1 t1)
    t2' <- do_onepass_cps b (lamShift 0 2 t2)
    return
      (Lam True "k0" $ App True t1' $
         Lam True "m" $ App True t2' $
           Lam True "n" $
             App False (App False (Var True 1) (Var True 0))
               (Var True 2)
      )
