Trying to apply CPS to an interpreter

后端 未结 1 713
孤独总比滥情好
孤独总比滥情好 2021-02-07 23:41

I\'m trying to use CPS to simplify control-flow implementation in my Python interpreter. Specifically, when implementing return/break/continue

相关标签:
1条回答
  • 2021-02-07 23:55

    This finally gave me a good excuse to try using ContT!

    Here's one possible way of doing this: store (in a Reader wrapped in ContT) the continuation of exiting the current (innermost) loop:

    newtype M r a = M{ unM :: ContT r (ReaderT (M r ()) (StateT (Map Id Value) IO)) a }
                  deriving ( Functor, Applicative, Monad
                           , MonadReader (M r ()), MonadCont, MonadState (Map Id Value)
                           , MonadIO
                           )
    
    runM :: M a a -> IO a
    runM m = evalStateT (runReaderT (runContT (unM m) return) (error "not in a loop")) M.empty
    
    withBreakHere :: M r () -> M r ()
    withBreakHere act = callCC $ \break -> local (const $ break ()) act
    
    break :: M r ()
    break = join ask
    

    (I've also added IO for easy printing in my toy interpreter, and State (Map Id Value) for variables).

    Using this setup, you can write Break and While as:

    eval Break = break
    eval (While condition block) = withBreakHere $ fix $ \loop -> do
        result <- evalExpr condition
        unless (isTruthy result)
          break
        evalBlock block
        loop
    

    Here's the full code for reference:

    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    module Interp where
    
    import Prelude hiding (break)
    import Control.Applicative
    import Control.Monad.Cont
    import Control.Monad.State
    import Control.Monad.Reader
    import Data.Function
    import Data.Map (Map)
    import qualified Data.Map as M
    import Data.Maybe
    
    type Id = String
    
    data Statement
        = Print Expression
        | Assign Id Expression
        | Break
        | While Expression [Statement]
        | If Expression [Statement]
        deriving Show
    
    data Expression
        = Var Id
        | Constant Value
        | Add Expression Expression
        | Not Expression
        deriving Show
    
    data Value
        = String String
        | Int Integer
        | None
        deriving Show
    
    data Env = Env{ loopLevel :: Int
                  , flow :: Flow
                  }
    
    data Flow
        = Breaking
        | Continuing
        | Next
        deriving Eq
    
    newtype M r a = M{ unM :: ContT r (ReaderT (M r ()) (StateT (Map Id Value) IO)) a }
                  deriving ( Functor, Applicative, Monad
                           , MonadReader (M r ()), MonadCont, MonadState (Map Id Value)
                           , MonadIO
                           )
    
    runM :: M a a -> IO a
    runM m = evalStateT (runReaderT (runContT (unM m) return) (error "not in a loop")) M.empty
    
    withBreakHere :: M r () -> M r ()
    withBreakHere act = callCC $ \break -> local (const $ break ()) act
    
    break :: M r ()
    break = join ask
    
    evalExpr :: Expression -> M r Value
    evalExpr (Constant val) = return val
    evalExpr (Var v) = gets $ fromMaybe err . M.lookup v
      where
        err = error $ unwords ["Variable not in scope:", show v]
    evalExpr (Add e1 e2) = do
        Int val1 <- evalExpr e1
        Int val2 <- evalExpr e2
        return $ Int $ val1 + val2
    evalExpr (Not e) = do
        val <- evalExpr e
        return $ if isTruthy val then None else Int 1
    
    isTruthy (String s) = not $ null s
    isTruthy (Int n) = n /= 0
    isTruthy None = False
    
    evalBlock = mapM_ eval
    
    eval :: Statement -> M r ()
    eval (Assign v e) = do
        val <- evalExpr e
        modify $ M.insert v val
    eval (Print e) = do
        val <- evalExpr e
        liftIO $ print val
    eval (If cond block) = do
        val <- evalExpr cond
        when (isTruthy val) $
          evalBlock block
    eval Break = break
    eval (While condition block) = withBreakHere $ fix $ \loop -> do
        result <- evalExpr condition
        unless (isTruthy result)
          break
        evalBlock block
        loop
    

    and here's a neat test example:

    prog = [ Assign "i" $ Constant $ Int 10
           , While (Var "i") [ Print (Var "i")
                             , Assign "i" (Add (Var "i") (Constant $ Int (-1)))
                             , Assign "j" $ Constant $ Int 10
                             , While (Var "j") [ Print (Var "j")
                                               , Assign "j" (Add (Var "j") (Constant $ Int (-1)))
                                               , If (Not (Add (Var "j") (Constant $ Int (-4)))) [ Break ]
                                               ]
                             ]
           , Print $ Constant $ String "Done"
           ]
    

    which is

    i = 10
    while i:
      print i
      i = i - 1
      j = 10
      while j:
        print j
        j = j - 1
        if j == 4:
          break
    

    so it will print

    10 10 9 8 7 6 5
     9 10 9 8 7 6 5
     8 10 9 8 7 6 5
    ...
     1 10 9 8 7 6 5
    
    0 讨论(0)
提交回复
热议问题