module MiniForth.Engine
    ( pop
    , push
    , define
    , run
    , word
    ) where

import Control.Monad.Except
import Control.Lens

import MiniForth.Types

pop :: VM Double
pop = use stack >>= go where
    go []     = throwError StackUnderflow
    go (x:xs) = do
        stack .= xs
        return x

push :: Double -> VM ()
push x = stack %= (x:)

define :: String -> VM () -> VM ()
define name block = dict . at name ?= block

run :: [Token] -> VM ()
run []     = return ()
run (x:xs) = case x of
    Word w   -> word w >> run xs
    Number n -> push n >> run xs
    Def w b  -> define w (run b) >> run xs

word :: String -> VM ()
word w = use (dict . at w) >>= maybe (throwError (UndefinedWord w)) id