array-forth-0.2.1.4: src/Language/ArrayForth/Interpreter.hs
{-# LANGUAGE MonadComprehensions #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module Language.ArrayForth.Interpreter where
import Data.Bits
import Data.Functor ((<$>))
import Data.Maybe (fromJust, fromMaybe, mapMaybe)
import Language.ArrayForth.NativeProgram
import Language.ArrayForth.Opcode
import Language.ArrayForth.State
-- | A trace of a progam is the state after every word is executed.
type Trace = [State]
-- | Runs a single word's worth of instructions starting from the
-- given state, returning the intermediate states for each executed
-- opcode.
wordAll :: Instrs -> State -> [State]
wordAll (Instrs a b c d) state =
let s₁ = [execute a state]
s₂ = if endWord a then s₁ else run b s₁
s₃ = if endWord a || endWord b
then s₂ else run c s₂ in
if endWord a || endWord b || endWord c then s₃ else s₃ ++ run d s₃
wordAll (Jump3 a b c addr) state = let s₁ = [execute a state]
s₂ = if endWord a then s₁ else run b s₁ in
if endWord a || endWord b
then s₂ else s₂ ++ [jump c addr (last s₂)]
wordAll (Jump2 a b addr) state = let s' = execute a state in
if endWord a then [s'] else [s', jump b addr s']
wordAll (Jump1 a addr) state = [jump a addr state]
wordAll (Constant _) _ = error "Cannot execute a constant!"
-- | Runs a single word's worth of instructions, returning only the
-- final state.
word :: Instrs -> State -> State
word instr σ = last $ wordAll instr σ
-- | Executes a single word in the given state, incrementing
-- the program counter and returning all the intermediate states.
stepAll :: State -> [State]
stepAll state = fromMaybe [] $ go <$> next state
where go instrs = wordAll instrs . incrP $ state {i = toBits <$> next state}
-- | Executes a single word in the given state, returning the last
-- resulting state.q
step :: State -> State
step = last . stepAll
-- | Trace the given program, including all the intermediate states.
traceAll :: State -> Trace
traceAll program = let steps = stepAll program in steps ++ traceAll (last steps)
-- | Returns a trace of the program's execution. The trace is a list
-- of the state of the chip after each step.
traceProgram :: State -> Trace
traceProgram = iterate step
-- | Trace a program until it either hits four nops or all 0s.
stepProgram :: State -> Trace
stepProgram = takeWhile (not . done) . traceProgram
where done state = i state == Just 0x39ce7 || i state == Just 0
-- | Runs the program unil it hits a terminal state, returning only
-- the resulting state.
eval :: State -> State
eval state = last $ state : stepProgram state
-- | Executes the specified program on the given state until it hits a
-- "terminal" word--a word made up of four nops or all 0s.
runNativeProgram :: State -> NativeProgram -> State
runNativeProgram start program = eval $ setProgram 0 program start
-- | Estimates the execution time of a program trace.
countTime :: Trace -> Double
countTime = runningTime . mapMaybe (fmap fromBits . i)
-- | Checks that the program trace terminated in at most n steps,
-- returning Nothing otherwise.
throttle :: Int -> Trace -> Either Trace Trace
throttle n states | null res = Right [startState]
| length res == n = Left res
| otherwise = Right res
where res = take n states
-- | Does the given opcode cause the current word to stop executing?
endWord :: Opcode -> Bool
endWord = (`elem` [Ret, Exec, Jmp, Call, Unext, Next, If, MinusIf])
-- | Extends the given trace by a single execution step. The trace
-- cannot be empty.
run :: Opcode -> [State] -> [State]
run op trace = trace ++ [execute op $ last trace]
-- | Executes an opcode on the given state. If the state is blocked on
-- some communication, nothing changes.
execute :: Opcode -> State -> State
execute op state@State {..} = fromMaybe state [ res | res <- result, not $ blocked res ]
where result = case op of
FetchP -> dpush (incrP state) <$> memory ! p
FetchPlus -> dpush (state {a = a + 1}) <$> memory ! a
FetchB -> dpush state <$> memory ! b
Fetch -> dpush state <$> memory ! a
_ -> Just normal
normal = case op of
Ret -> fst . rpop $ state {p = r}
Exec -> state {r = p, p = r}
Unext -> if r == 0 then fst $ rpop state
else state {r = r - 1, p = p - 1}
StoreP -> incrP $ set state' p top
StorePlus -> set (state' { a = a + 1 }) a top
StoreB -> set state' b top
Store -> set state' a top
MultiplyStep -> multiplyStep
Times2 -> state {t = t `shift` 1}
Div2 -> state {t = t `shift` (-1)}
Not -> state {t = complement t}
Plus -> state' {t = s + t}
And -> state' {t = s .&. t}
Or -> state' {t = s `xor` t}
Drop -> fst $ dpop state
Dup -> dpush state t
Pop -> uncurry dpush $ rpop state
Over -> dpush state s
ReadA -> dpush state a
Nop -> state
Push -> rpush state' top
SetB -> state' {b = top}
SetA -> state' {a = top}
_ -> error "Cannot jump without an address!"
(state', top) = dpop state
-- TODO: support different word sizes?
multiplyStep
| even a = let t0 = (t .&. 1) `shift` (size - 1) in
state { a = t0 .|. a `shift` (-1)
, t = t .&. bit (size - 1) .|. t `shift` (-1)}
| otherwise = let sum0 = (s + t) `shift` (size - 1)
sum17 = (s + t) .&. bit (size - 1) in
state { a = sum0 .|. a `shift` (-1)
, t = sum17 .|. (s + t) `shift` (-1) }
size = bitSize t
-- | Execute a jump instruction to the given address.
jump :: Opcode -> F18Word -> State -> State
jump op addr state@State{p, r, t} = case op of
Jmp -> state {p = addr}
Call -> (rpush state p) {p = addr}
Next -> if r == 0 then fst $ rpop state else state {r = r - 1, p = addr}
If -> if t /= 0 then state {p = addr} else state
MinusIf -> if t `testBit` pred size then state else state {p = addr}
_ -> error "Non-jump instruction given a jump address!"
where size = bitSize (0 :: F18Word)