array-forth-0.2.0.2: src/Language/ArrayForth/Opcode.hs
module Language.ArrayForth.Opcode where
import Data.List (elemIndex)
import Data.Word.Odd (Word18)
import Language.ArrayForth.Parse (ParseError (..))
-- | The 18-bit word type used by Greenarrays chips.
type F18Word = Word18
-- | Each F18A instruction, ordered by opcode.
data Opcode = Ret -- ;
| Exec -- ex
| Jmp -- name ;
| Call -- name
| Unext -- unext
| Next -- next
| If -- if
| MinusIf -- -if
| FetchP -- @p
| FetchPlus -- @+
| FetchB -- @b
| Fetch -- @
| StoreP -- !p
| StorePlus -- !+
| StoreB -- !b
| Store -- !
| MultiplyStep -- +*
| Times2 -- 2*
| Div2 -- 2/
| Not -- -
| Plus -- +
| And -- and
| Or -- or
| Drop -- drop
| Dup -- dup
| Pop -- pop
| Over -- over
| ReadA -- a
| Nop -- .
| Push -- push
| SetB -- b!
| SetA -- a!
deriving (Eq, Bounded, Enum)
-- | The names of the different instructions, ordered by opcode.
names :: [String]
names = [";", "ex", "jump", "call", "unext", "next", "if", "-if", "@p", "@+", "@b", "@",
"!p", "!+", "!b", "!", "+*", "2*", "2/", "-", "+", "and", "or", "drop", "dup",
"pop", "over", "a", ".", "push", "b!", "a!"]
-- | All of the opcodes, in order.
opcodes :: [Opcode]
opcodes = [minBound..maxBound]
instance Show Opcode where show op = names !! fromEnum op
-- | Tries to read a given string as an opcode from the list of names.
readOpcode :: String -> Either ParseError Opcode
readOpcode token = case elemIndex token names of
Just res -> Right $ toEnum res
Nothing -> Left $ BadOpcode token
instance Read Opcode where readsPrec _ str = case readOpcode str of
Left err -> error $ show err
Right r -> [(r, "")]
-- | Converts a word to an opcode. The word has to be < 32.
toOpcode :: F18Word -> Opcode
toOpcode = toEnum . fromIntegral
-- | Converts an Opcode to its 18-bit word representation.
fromOpcode :: Opcode -> F18Word
fromOpcode = fromIntegral . fromEnum
-- | Returns whether the given opcode is a jump instruction expecting
-- an address.
isJump :: Opcode -> Bool
isJump = (`elem` [Jmp, Call, Next, If, MinusIf])
-- | Can the given opcode go in the last slot?
slot3 :: Opcode -> Bool
slot3 = (`elem` [Ret, MultiplyStep, Unext, Plus, FetchP, Dup, StoreP, Nop])
-- | Estimates how long a given opcode will take to execute. Normal
-- opcodes take 1.5 nanoseconds where ones that access the memory take
-- 5 nanoseconds.
opcodeTime :: Opcode -> Double
opcodeTime op = if memoryOp op then 5 else 1.5
where memoryOp = (`elem` [FetchP, FetchPlus, FetchB, Fetch, StoreP,
StorePlus, StoreB, Store])