module Language.ArrayForth.Program where
import Control.Monad ((<=<))
import Data.Functor ((<$>))
import Data.List (find, (\\))
import Data.String (IsString, fromString)
import Language.ArrayForth.Interpreter
import Language.ArrayForth.NativeProgram
import Language.ArrayForth.Opcode
import qualified Language.ArrayForth.Parse as P
import Language.ArrayForth.State (State, setProgram)
data Addr = Concrete F18Word | Abstract String deriving Eq
instance Show Addr where
show (Concrete n) = show n
show (Abstract s) = ':' : s
data Instruction = Opcode Opcode
| Jump Opcode Addr
| Number F18Word
| Label String
| Unused deriving Eq
type Program = [Instruction]
instance Show Instruction where
show (Opcode op) = show op
show (Jump op addr) = show op ++ " " ++ show addr
show (Number n) = show n
show (Label s) = ':' : s
show Unused = "_"
showList = (++) . unwords . map show
readInstruction :: String -> Either P.ParseError Instruction
readInstruction "_" = Right Unused
readInstruction (':':label) = Right $ Label label
readInstruction str | P.isNumber str = Number <$> P.readWord str
| otherwise = Opcode <$> readOpcode str
readProgram :: String -> Either P.ParseError Program
readProgram = fixJumps <=< mapM readInstruction . words
where fixJumps [] = Right []
fixJumps (Opcode op : rest) | isJump op = case rest of
Number n : program -> (Jump op (Concrete n) :) <$> fixJumps program
Label s : program -> (Jump op (Abstract s) :) <$> fixJumps program
_ -> Left . P.NoAddr $ show op
fixJumps (good : rest) = (good :) <$> fixJumps rest
instance Read Program where
readsPrec _ str = [(result, "")]
where result = case readProgram str of
Right res -> res
Left err -> error $ show err
instance IsString Program where fromString = read
toNative :: Program -> NativeProgram
toNative = (>>= toInstrs) . splitWords boundary . fixSlot3 .
(>>= nopsPlus) . labels 0 . filter (/= Unused)
where nopsPlus (Opcode Plus) = ". +"
nopsPlus x = [x]
toInstrs ls = let (ops, numbers) = addFetchP ls in
convert ops : map (\ (Number n) -> Constant n) numbers
addFetchP [] = ([], [])
addFetchP (n@Number{} : rest) =
let (instrs, consts) = addFetchP rest in (Opcode FetchP : instrs, n : consts)
addFetchP (instr : rest) =
let (instrs, consts) = addFetchP rest in (instr : instrs, consts)
convert [Opcode a, Opcode b, Opcode c, Opcode d] = Instrs a b c d
convert [Opcode a, Opcode b, Jump c addr] = Jump3 a b c $ concrete addr
convert [Opcode a, Jump b addr] = Jump2 a b $ concrete addr
convert [Jump a addr] = Jump1 a $ concrete addr
convert instrs = convert . take 4 $ instrs ++ repeat (Opcode Nop)
concrete Abstract{} = error "Need concrete address at this stage."
concrete (Concrete addr) = addr
boundary :: Instruction -> Bool
boundary Jump{} = True
boundary _ = False
labels :: F18Word -> Program -> Program
labels start program = map fixLabel $ filter (not . label) program
where label Label{} = True
label _ = False
values = go start program
go _ [] = []
go n (Label name : rest) = (name, n) : go n rest
go n (_ : rest) = go (n + 1) rest
fixLabel (Jump op (Abstract l)) =
maybe (error $ "Unknown label " ++ l)
(Jump op . Concrete) $ lookup l values
fixLabel x = x
fixSlot3 :: Program -> Program
fixSlot3 program = case splitWords boundary program of
[] -> []
(next:rest) -> take 4 (go next) ++ fixSlot3 (drop 4 (go next) ++ concat rest)
where go instrs@[_, _, _, op3] | valid op3 = instrs
| otherwise = init instrs ++ "." ++ [op3]
go instrs = instrs
valid (Opcode op) = slot3 op
valid Number{} = True
valid _ = False
fromNative :: NativeProgram -> Program
fromNative = fixNumbers . concatMap extract
where extract (Instrs a b c d) = [Opcode a, Opcode b, Opcode c, Opcode d]
extract (Jump3 a b c addr) = [Opcode a, Opcode b, Jump c $ Concrete addr]
extract (Jump2 a b addr) = [Opcode a, Jump b $ Concrete addr]
extract (Jump1 a addr) = [Jump a $ Concrete addr]
extract (Constant n) = [Number n]
fixNumbers [] = []
fixNumbers (Opcode FetchP : rest) = case find isNumber rest of
Just n -> n : (fixNumbers $ rest \\ [n])
Nothing -> Opcode FetchP : fixNumbers rest
fixNumbers (x : rest) = x : fixNumbers rest
isNumber Number{} = True
isNumber _ = False
runProgram :: State -> Program -> State
runProgram start = runNativeProgram start . toNative
load :: Program -> State -> State
load prog state = setProgram 0 (toNative prog) state