mima-tools/src/Mima/Parse/Assembly/Weed/Statement.hs
Joscha 702f58e2a4 Weed assembly statements
There's still an undefined in Assembly.hs. To get rid of it, I'll need to adjust
how I weed labels. Once that's finished, the rest should be fairly straightforward.
2019-11-21 15:12:21 +00:00

152 lines
5.6 KiB
Haskell

{-# LANGUAGE RecordWildCards #-}
module Mima.Parse.Assembly.Weed.Statement
( weedStatements
) where
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Set as Set
import Mima.Flag
import Mima.Label
import Mima.Parse.Assembly.Common
import Mima.Parse.Assembly.Directive
import Mima.Parse.Assembly.RawInstruction
import Mima.Parse.Assembly.Statement
import Mima.Parse.Assembly.Weed.Common
import Mima.Parse.Weed
import Mima.Word
data WeedState = WeedState
{ wsAt :: MimaAddress
, wsOccupied :: Bool
, wsOpenFlags :: Map.Map Char MimaAddress
, wsResult :: WeedResult Address
} deriving (Show)
initialState :: WeedState
initialState = WeedState
{ wsAt = minBound
, wsOccupied = False
, wsOpenFlags = Map.empty
, wsResult = emptyResult
}
-- Sweet!
type SWeed a = StateT WeedState (Weed WeedError) a
{- State manipulation -}
-- Yes, I know that lenses would probably make the below code much nicer. I may
-- get around to that eventually.
modifyResult :: (WeedResult Address -> WeedResult Address) -> SWeed ()
modifyResult f = modify (\s -> s{wsResult = f (wsResult s)})
{- Let's start weeding -}
-- | Advance to the next unoccupied address and return that. This function
-- either returns the current wsAt (if not wsOccupied) or advances wsAt and
-- returns that.
--
-- This function takes an object with an offset, which it uses to produce an
-- error if it could not advance to an unoccupied address.
toNextFree :: WithOffset a -> SWeed MimaAddress
toNextFree thing = do
s@WeedState{..} <- get
if wsOccupied
then if wsAt >= maxBound
then lift $ critical $ errorAt thing "No more space in memory, already at max address"
else let next = wsAt + 1 in next <$ put s{wsAt = next, wsOccupied = False}
else pure wsAt
helpSetRegister :: WithOffset a
-> (Registers Address -> Maybe c)
-> (Registers Address -> Registers Address)
-> SWeed ()
helpSetRegister thing readF writeF = do
WeedState{..} <- get
case readF (wrRegisters wsResult) of
Nothing -> modifyResult (\r -> r{wrRegisters = writeF (wrRegisters r)})
Just _ -> lift $ harmless $ errorAt thing "Register was already set earlier"
setRegister :: WithOffset a -> SetRegister Address -> SWeed ()
setRegister thing (SetIAR a) = helpSetRegister thing rIAR (\r -> r{rIAR = Just a})
setRegister thing (SetACC a) = helpSetRegister thing rACC (\r -> r{rACC = Just a})
setRegister thing (SetRA a) = helpSetRegister thing rRA (\r -> r{rRA = Just a})
setRegister thing (SetSP a) = helpSetRegister thing rSP (\r -> r{rSP = Just a})
setRegister thing (SetFP a) = helpSetRegister thing rFP (\r -> r{rFP = Just a})
setAddressTo :: WithOffset a -> MimaAddress -> SWeed ()
setAddressTo thing addr = do
s@WeedState{..} <- get
if (addr > wsAt) || (not wsOccupied && addr == wsAt)
then put s{wsAt = addr, wsOccupied = False}
else lift $ harmless $ errorAt thing "Can only increase address"
addAlmostWord :: WithOffset a -> AlmostWord Address -> SWeed ()
addAlmostWord thing aw = do
addr <- toNextFree thing
modifyResult (\r -> r{wrMemory = Map.insert addr aw (wrMemory r)})
addLabel :: WithOffset a -> LabelName -> SWeed ()
addLabel thing l = do
addr <- toNextFree thing
WeedState{..} <- get
case wrLabels wsResult Map.!? l of
Nothing -> modifyResult (\r -> r{wrLabels = Map.insert l addr (wrLabels r)})
Just _ -> lift $ harmless $ errorAt thing "Label was already defined earlier"
addFlagRange :: Char -> AddressRange -> SWeed()
addFlagRange c r =
modifyResult (\res -> res{wrFlags = Map.alter (Just . (r:) . fromMaybe []) c (wrFlags res)})
setFlag :: WithOffset a -> Char -> SWeed ()
setFlag thing c = do
addr <- toNextFree thing
addFlagRange c $ range addr addr
turnFlagOn :: WithOffset a -> Char -> SWeed ()
turnFlagOn thing c = do
s@WeedState{..} <- get
case wsOpenFlags Map.!? c of
Just _ -> lift $ harmless $ errorAt thing "Flag is already active at this address"
Nothing -> put s{wsOpenFlags = Map.insert c wsAt wsOpenFlags}
turnFlagOff :: WithOffset a -> Char -> SWeed ()
turnFlagOff thing c = do
s@WeedState{..} <- get
case wsOpenFlags Map.!? c of
Nothing -> lift $ harmless $ errorAt thing "Flag is not active at this address"
Just start -> do
put s {wsOpenFlags = Map.delete c wsOpenFlags}
addFlagRange c $ range start wsAt
{- Weeding at a larger scale -}
weedDirective :: WithOffset a -> Directive Address -> SWeed ()
weedDirective thing d = do
case d of
DReg sr -> setRegister thing sr
DOrg addr -> setAddressTo thing addr
DLit w -> addAlmostWord thing (ALiteral w)
DArr ws -> mapM_ (addAlmostWord thing . ALiteral) ws
DFlag chars -> mapM_ (setFlag thing) (Set.toList chars)
DFlagOn chars -> mapM_ (turnFlagOn thing) (Set.toList chars)
DFlagOff chars -> mapM_ (turnFlagOff thing) (Set.toList chars)
weedInstruction :: WithOffset a -> RawInstruction Address -> SWeed ()
weedInstruction thing i = addAlmostWord thing $ AInstruction i
weedStep :: WithOffset (Statement Address) -> SWeed ()
weedStep thing =
case woValue thing of
SDirective d -> weedDirective thing d
SRawInstruction i -> weedInstruction thing i
SLabel l -> addLabel thing l
weedStatements :: [WithOffset (Statement Address)] -> Weed WeedError (WeedResult Address)
weedStatements statements = wsResult <$> execStateT (mapM_ weedStep statements) initialState