packages feed

atomo-0.3: src/Atomo/Kernel/Continuation.hs

{-# LANGUAGE QuasiQuotes #-}
module Atomo.Kernel.Continuation where

import Data.IORef

import Atomo


load :: VM ()
load = do
    [$p|current-continuation|] =:::
        [$e|{ cc | cc } call/cc|]

    -- call/cc actually makes an object delegating to Continuation
    -- so just add the show definiton here
    [$p|Continuation show|] =:: string "<continuation>"

    [$p|(c: Continuation) yield: v|] =: do
        Continuation c <- here "c" >>= findContinuation
        v <- here "v"
        liftIO (readIORef c) >>= ($ v)

    -- this enables call/cc as well
    [$p|(c: Continuation) call: [v]|] =::: [$e|c yield: v|]

    -- effectively just "jumping" to a continuation
    [$p|(c: Continuation) yield|] =::: [$e|c yield: @ok|]
    [$p|(c: Continuation) call|] =::: [$e|c yield: @ok|]

    [$p|(o: Object) call/cc|] =::: [$e|o call/cc: []|]
    [$p|(o: Object) call/cc: (as: List)|] =: callCC $ \c -> do
        o <- here "o"
        as <- getList [$e|as|]
        cr <- mkContinuation c
        dispatch (keyword ["call"] [o, list (Continuation cr:as)])

    [$p|(v: Block) before: (b: Block) after: (a: Block)|] =: do
        v <- here "v"
        b <- here "b"
        a <- here "a"

        dispatch (single "call" b)

        modify $ \env -> env { unwinds = (b, a) : unwinds env }

        res <- dispatch (single "call" v)

        dispatch (single "call" a)

        modify $ \env -> env { unwinds = tail (unwinds env) }

        return res

mkContinuation :: (Value -> VM Value) -> VM Continuation
mkContinuation c = do
    env <- get
    liftIO . newIORef $ \v -> do
        ws <- gets unwinds
        unwind (unwinds env) (length ws - length (unwinds env))

        modify $ \env' -> env'
            { top = top env
            , dynamic = dynamic env
            }

        put env
        c v

unwind :: [(Value, Value)] -> Int -> VM Value
unwind to d = do
    ws <- gets unwinds
    if ws == to
        then return (particle "ok")
        else do

    if d < 0
        then do
            unwind us (d + 1)
            dispatch (single "call" pre)
            modify $ \env -> env { unwinds = to }
            return (particle "ok")
        else do

    let post = snd (head ws)
    modify $ \env -> env { unwinds = tail (unwinds env) }
    dispatch (single "call" post)
    unwind to (d - 1)
  where
    (u:us) = to
    pre = fst u