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
type Trace = [State]
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!"
word :: Instrs -> State -> State
word instr σ = last $ wordAll instr σ
stepAll :: State -> [State]
stepAll state = fromMaybe [] $ go <$> next state
where go instrs = wordAll instrs . incrP $ state {i = toBits <$> next state}
step :: State -> State
step = last . stepAll
traceAll :: State -> Trace
traceAll program = let steps = stepAll program in steps ++ traceAll (last steps)
traceProgram :: State -> Trace
traceProgram = iterate step
stepProgram :: State -> Trace
stepProgram = takeWhile (not . done) . traceProgram
where done state = i state == Just 0x39ce7 || i state == Just 0
eval :: State -> State
eval state = last $ state : stepProgram state
runNativeProgram :: State -> NativeProgram -> State
runNativeProgram start program = eval $ setProgram 0 program start
countTime :: Trace -> Double
countTime = runningTime . mapMaybe (fmap fromBits . i)
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
endWord :: Opcode -> Bool
endWord = (`elem` [Ret, Exec, Jmp, Call, Unext, Next, If, MinusIf])
run :: Opcode -> [State] -> [State]
run op trace = trace ++ [execute op $ last trace]
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
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
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)