-- | Sequential Access Machine -- -- This language makes implementation of various features easier by providing common C-like syntax. -- -- Later this will be converted to very abstract graph representation and heavy optimization is -- applied there (rule-based, mathematically-sound). It will be directly converted to BF. -- -- This is the last language where direct debugging is possible. -- -- -- Choice between 'Memory' and 'Locate' -- -- * 'Memory' is for local operation(in a frame), and you can expect it to be heavily optimzied. -- (Why not use 'Locate' manually? - special register optimization is possible for 'Memory') -- -- * 'Locate' causes permanent change, and should be used for moving between frames -- by not-predetermined amount. -- -- * So in principle, you should minimize use of 'Locate', and use 'Memory' instead. -- -- Multi-byte support direction: -- -- * multiplication etc. is supported in this layer (manually) -- -- * 'Val' 'Dispatch' 'Clear' 'While' 'Alloc' 'Delete' 'Move' should be expanded by a new 'Pointer' -- -- * Difference from Integer support in Prelude: fixed size module SAM where import Control.Arrow import Control.Monad import Control.Monad.State import Data.Char import Data.Either import Data.Graph import Data.List import Data.Maybe import Data.Ord import Data.Word import qualified Data.Map as M import qualified Data.Set as S import Numeric import Text.Printf import Util import SCGR import Brainfuck compile :: SAM -> Process SCGR compile (SAM _ [SProc _ [] ss])=return $ BF $ soptBF $ concatMap compileS ss soptBF []=[] soptBF xs=case head xs of BFPInc -> sopAux 0 xs BFPDec -> sopAux 0 xs BFVInc -> sovAux 0 xs BFVDec -> sovAux 0 xs BFLoop s -> BFLoop (soptBF s):xs' BFInput -> BFInput:xs' BFOutput -> BFOutput:xs' where xs'=soptBF $ tail xs sopAux n (BFPInc:xs)=sopAux (n+1) xs sopAux n (BFPDec:xs)=sopAux (n-1) xs sopAux n xs=dP n++soptBF xs sovAux n (BFVInc:xs)=sovAux (n+1) xs sovAux n (BFVDec:xs)=sovAux (n-1) xs sovAux n xs=dV n++soptBF xs compileS (Move p ps)=compileS $ While p $ Val p (-1):map (flip Val 1) ps compileS (While (Memory _ d) ss)=concat [dP d ,[BFLoop $ concat [dP (negate d),concatMap compileS ss,dP d]] ,dP (negate d)] compileS (Val (Memory _ d) v)=concat [dP d,dV v,dP $ negate d] compileS (Input (Memory _ d))=dP d++[BFInput]++dP (negate d) compileS (Output (Memory _ d))=dP d++[BFOutput]++dP (negate d) compileS (Locate d)=dP d dP x=replicateZ x BFPDec BFPInc dV x=replicateZ x BFVDec BFVInc replicateZ x m p |x==0 = [] |x>0 = replicate x p |x<0 = replicate (negate x) m -- | Apply this before 'SAM.compile' -- -- * 'flatten': expand all inline calls -- -- * 'desugar': 'Dispatch' -> 'While' 'Clear' -> 'Move' 'Copy' -> 'Move' -- (don't expand 'Move' here, since they are good for later optimization) -- -- * 'foldMemory': allocate registers simplify :: SAM -> Process SAM simplify s= checkSAM "SAM" s >>= return . flatten "^" >>= checkSAM "SAM:flat" >>= return . desugar >>= checkSAM "SAM:desugar" >>= return . allocateRegister >>= checkSAM "SAM:ralloc" >>= return . foldMemory >>= checkSAM "SAM:folded" -- | no register access foldMemory :: SAM -> SAM foldMemory (SAM rs [SProc name [] ss])=SAM [""] [SProc name [] $ map (foldMS (length rs) rs) ss] foldMS n m (Move p ps)=Move (foldMP n m p) (map (foldMP n m) ps) foldMS n m (While p ss)=While (foldMP n m p) (map (foldMS n m) ss) foldMS n m (Val p d)=Val (foldMP n m p) d foldMS n m (Locate d)=Locate $ n*d foldMS n m (Input p)=Input (foldMP n m p) foldMS n m (Output p)=Output (foldMP n m p) foldMP n m (Memory r x)=Memory "" $ (fromJust $ elemIndex r m)+x*n -- | /very bad/ register allocator allocateRegister :: SAM -> SAM allocateRegister (SAM rs [SProc name [] ss]) |rs' `eqRS` [] = SAM (rs++["R"]) [SProc name [] $ concat sss] |otherwise = error $ "allocateRegister: leaking register: "++show rs' where (rs',sss)=mapAccumL allocateRS [] ss allocateRS :: [Maybe RegName] -> Stmt -> ([Maybe RegName],[Stmt]) allocateRS rs (Alloc r)=case elemIndex Nothing rs of Nothing -> (rs++[Just r],[]) Just ix -> (mapAt ix (const $ Just r) rs,[]) allocateRS rs (Delete r)=case elemIndex (Just r) rs of Just ix -> (mapAt ix (const Nothing) rs,[Move (Memory "R" ix) []]) Nothing -> error $ "allocateRS: deleting unknown register: "++r allocateRS rs (Move p ps)=(rs,[Move (allocateRP rs p) (map (allocateRP rs) ps)]) allocateRS rs (While p ss) |eqRS rs rs' = (rs,[While (allocateRP rs p) $ concat sss]) |otherwise = error "allocateRS: unmatched register scope in while" where (rs',sss)=mapAccumL allocateRS rs ss allocateRS rs (Val p d)=(rs,[Val (allocateRP rs p) d]) allocateRS rs (Input p)=(rs,[Input (allocateRP rs p)]) allocateRS rs (Output p)=(rs,[Output (allocateRP rs p)]) {- allocateRS rs (Locate d) |d/=0 = (rs',mv++[Locate d]) |d==0 = (rs,[]) where rs'=map (\ix->lookup (ix-d) table') area table'=map (snd3 &&& thr3) table area=[0..maximum (map snd3 table)-d-1] mv=map (\(fr,to,_)->Move (Memory "R" fr) [Memory "R" to]) table table=repackRS S.empty (map (second fromJust) $ filter (isJust . snd) $ zip [0..] rs) d -} -- just works (TM) allocateRS rs (Locate d) |d>0 = (rs,mvPos++[Locate d]) |d<0 = (rs,mvNeg++[Locate d]) |d==0 = (rs,[]) where mvPos=reverse mvNeg mvNeg=map (gen . fst) $ filter (isJust . snd) $ zip [0..] rs gen ix=Move (Memory "R" ix) [Memory "R" $ ix+d] -- | repack registers as densely as possible without causing collision repackRS :: S.Set Int -> [(Int,RegName)] -> Int -> [(Int,Int,RegName)] repackRS _ [] d=[] repackRS al rs d |S.null cand = error "repackRS: unknown situation" -- what to do? (this is actually possible) |not $ S.null nocost = allocate (S.findMin nocost) (S.findMin nocost) |otherwise = allocate (fst $ head rs) (S.findMin cand) where allocate fr to=(fr,to,maybe undefined id $ lookup fr rs):rs' where rs'=repackRS (S.insert to al) (filter ((/=fr) . fst) rs) d nocost=S.intersection cand from cand=S.fromList [d..d+length rs-1] S.\\ al from=S.fromList $ map fst rs eqRS :: [Maybe RegName] -> [Maybe RegName] -> Bool eqRS [] []=True eqRS (Nothing:xs) []=eqRS xs [] eqRS [] (Nothing:ys)=eqRS [] ys eqRS (x:xs) (y:ys)=(x==y) && (xs `eqRS` ys) eqRS _ _=False allocateRP :: [Maybe RegName] -> Pointer -> Pointer allocateRP rs (Memory x d)=Memory x d allocateRP rs (Register r) =maybe (error $ "allocateRP: non-allocated register: "++r) (Memory "R") $ elemIndex (Just r) rs desugar :: SAM -> SAM desugar (SAM rs [SProc name [] ss])=SAM rs [SProc name [] ss'] where ss'=[Alloc "_dt"]++concatMap desugarStmt ss++[Delete "_dt"] desugarStmt :: Stmt -> [Stmt] desugarStmt (Dispatch r cs)=concatMap desugarStmt $ expandDispatch r $ sortBy (comparing fst) cs desugarStmt (While ptr ss)=[While ptr $ concatMap desugarStmt ss] desugarStmt (Clear ptr)=[Move ptr []] desugarStmt (Copy p ps)=[Alloc "_ct",Move p [Register "_ct"],Move (Register "_ct") (p:ps),Delete "_ct"] desugarStmt (Comment _)=[] desugarStmt s=[s] -- | Case numbers must be sorted in ascending order. -- _dt must be 0 before and after dispatch -- r must be 0 after dispatch expandDispatch r []=error "expandDispatch: empty dispatch" expandDispatch r [(n,e)] |abs n<3 = [Val (Register r) $ negate n]++e |otherwise = [Clear (Register r)]++e expandDispatch r ((n0,e0):cs)= [Val (Register "_dt") 1 ,Val (Register r) (negate $ n0) ,While (Register r) $ Val (Register "_dt") (-1): expandDispatch r (map (\(n,e)->(n-n0,e)) cs) ,While (Register "_dt") $ Val (Register "_dt") (-1):e0 ] -- | Sequential Access Machine data SAM=SAM [Region] [SProc] deriving(Show) data SProc=SProc ProcName [RegName] [Stmt] deriving(Show) procName :: SProc -> ProcName procName (SProc name _ _)=name -- | Statement set of SAM. -- -- Operations with 'RegName' in their arguments changes scope data Stmt =Locate Int -- ^ ptr+=n |While Pointer [Stmt] |Val Pointer Int |Alloc RegName |Delete RegName |Move Pointer [Pointer] |Copy Pointer [Pointer] -- ^ syntax sugar of 'Move' |Clear Pointer -- ^ syntax sugar of Move p [] |Dispatch RegName [(Int,[Stmt])] -- ^ in case alts, given RegName will be out of scope. This instruction is erratic in many ways... |Inline ProcName [RegName] |Input Pointer |Output Pointer |Comment String -- ^ one-line comment deriving(Show) data Pointer =Register RegName |Memory Region Int instance Show Pointer where show (Register x)=x show (Memory region n) |n==0 = "$"++region |n>0 = "$"++region++"+"++show n |n<0 = "$"++region++show n type Region=String type ProcName=String type RegName=String pprint :: SAM -> String pprint (SAM rs ps)=compileSB $ Group [Line $ Span $ map Prim rs ,EmptyLine ,EmptyLine ,Group $ intersperse EmptyLine $ map pprintSP ps ] pprintSP :: SProc -> SBlock pprintSP (SProc name args st)=Group [Line $ Span [Prim "pr",Pack $ Prim name:darg] ,Indent $ Group $ map pprintStmt st ] where darg|null args = [] |otherwise = [Prim "/",Span $ map Prim args] pprintStmt :: Stmt -> SBlock pprintStmt (While ptr ss)=Group $ [Line $ Span [Prim "while",Prim $ show ptr] ,Indent $ Group $ map pprintStmt ss] pprintStmt (Dispatch n cs)=Group $ [Line $ Span [Prim "dispatch",Prim n] ,Indent $ Group $ map (f . first show) cs ] where f (l,ss)=Group [Line $ Prim l,Indent $ Group $ map pprintStmt ss] pprintStmt s=Line $ Span $ case s of Val p n -> [Prim "val",Prim $ show p,Prim $ show n] Alloc n -> [Prim "alloc",Prim n] Delete n -> [Prim "delete",Prim n] Move d ss -> Prim "move":map (Prim . show) (d:ss) Copy d ss -> Prim "copy":map (Prim . show) (d:ss) Locate n -> [Prim "locate",Prim $ show n] Inline n rs -> map Prim ("inline":n:rs) Clear r -> [Prim "clear",Prim $ show r] Input p -> [Prim "in",Prim $ show p] Output p -> [Prim "out",Prim $ show p] Comment s -> [Prim "--",Prim s] -- | Flatten procedures from given root. flatten :: ProcName -> SAM -> SAM flatten root (SAM rs ps) |not $ null cycles = error $ "flatten: dependency cycles:\n"++unlines (map unwords cycles) |otherwise = SAM rs [m2p root $ foldl expandProc (ps2m ps) vs] where (cycles,vs)=partitionEithers $ map f $ stronglyConnComp $ map procNode ps f (AcyclicSCC x)=Right x f (CyclicSCC xs)=Left xs ps2m=M.fromList . map (\(SProc name args ss)->(name,(args,ss))) m2p r m=uncurry (SProc r) $ m M.! r -- | Construct a node for procedure dependecy graph procNode :: SProc -> (ProcName,ProcName,[ProcName]) procNode (SProc n args ss)=(n,n,S.toList $ S.unions $ map stmtDep ss) -- | Collect 'Inline'd procedures from 'Stmt' stmtDep :: Stmt -> S.Set ProcName stmtDep (While _ ss)=S.unions $ map stmtDep ss stmtDep (Dispatch _ cs)=S.unions $ map stmtDep $ concatMap snd cs stmtDep (Inline n _)=S.singleton n stmtDep _=S.empty -- | Expand the given proc in the map non-recursively. expandProc :: M.Map ProcName ([RegName],[Stmt]) -> ProcName -> M.Map ProcName ([RegName],[Stmt]) expandProc m r=M.adjust (second $ expandStmts m) r m expandStmts :: M.Map ProcName ([RegName],[Stmt]) -> [Stmt] -> [Stmt] expandStmts m=concatMap (expandStmt m) expandStmt :: M.Map ProcName ([RegName],[Stmt]) -> Stmt -> [Stmt] expandStmt m (Inline n rsP)=map (replaceStmt f) ss where (rsC,ss)=M.findWithDefault (error $ "flattenProc:unknown proc "++n) n m f reg=case lookup reg $ zip rsC rsP of Just rsp -> rsp Nothing -> n++"/"++reg expandStmt m (While p ss)=[While p $ expandStmts m ss] expandStmt m (Dispatch p cs)=[Dispatch p $ map (second $ expandStmts m) cs] expandStmt _ s=[s] -- | Apply register name transformation. replaceStmt :: (RegName -> RegName) -> Stmt -> Stmt replaceStmt f (While ptr ss)=While (replacePtr f ptr) $ map (replaceStmt f) ss replaceStmt f (Dispatch n cs)=Dispatch (f n) $ map (second (map $ replaceStmt f)) cs replaceStmt f (Val p n)=Val (replacePtr f p) n replaceStmt f (Alloc n)=Alloc $ f n replaceStmt f (Delete n)=Delete $ f n replaceStmt f (Clear p)=Clear $ replacePtr f p replaceStmt f (Move p ps)=Move (replacePtr f p) (map (replacePtr f) ps) replaceStmt f (Copy p ps)=Copy (replacePtr f p) (map (replacePtr f) ps) replaceStmt f (Inline n ss)=error "replaceStmt: Inline: re-check expansion order" replaceStmt f (Input p)=Input (replacePtr f p) replaceStmt f (Output p)=Output (replacePtr f p) replaceStmt _ s=s replacePtr :: (RegName -> RegName) -> Pointer -> Pointer replacePtr f (Register x)=Register $ f x replacePtr _ p=p -- | 'NRM' instance for use in 'checkProc' type NMRE a=NMR String String a -- | Just a wrapper of 'checkProc' for 'SAM'. No additional checks. checkSAM :: String -> SAM -> Process SAM checkSAM loc s@(SAM x procs) |null errors = return s |otherwise = throwError errors where errors=map (\(pos,msg)->CompileErrorN loc msg pos) $ snd $ runNMR $ mapM_ checkProc procs -- | Find static erros in a 'SProc'. -- -- What's being done here is usual variable scope analysis. But the data dependecy graph will be a -- DAG, not tree. -- -- * unknown registers -- -- * unmatched register in 'While' and 'Dispatch' -- -- TODO: -- -- * 'Alloc' or 'Delete' of argument registers -- -- * modification of flag register in 'Dispatch' checkProc :: SProc -> NMRE () checkProc (SProc name args ss)=within ("proc "++name) $ do let rs=S.fromList args when (S.size rs/=length args) $ report "duplicate arguments" rs'<-checkStmt ss rs when (rs/=rs') $ report $ "leaking registers: "++unwords (S.toList $ rs' S.\\ rs) checkStmt :: [Stmt] -> S.Set RegName -> NMRE (S.Set RegName) checkStmt [] rs=return rs checkStmt ((While ptr ss):xs) rs=do within "while flag" $ checkPointer ptr rs rs'<-within "while body" $ checkStmt ss rs when (rs/=rs') $ within "while" $ report $ "leaking registers: "++unwords (S.toList $ rs' S.\\ rs) checkStmt xs rs checkStmt ((Dispatch n cs):xs) rs=do unless (S.member n rs) $ within "dispatch header" $ report $ "unknown register:"++show n let integrity rsB=when (rsB/=rs) $ report $ "leaking registers:"++unwords (S.toList $ rsB S.\\ rs) forM_ cs (\(n,ss)->within ("dispatch clause "++show n) $ checkStmt ss rs >>= integrity) checkStmt xs rs checkStmt ((Alloc n):xs) rs=do when (S.member n rs) $ report $ "duplicated allocation of "++n checkStmt xs $ S.insert n rs checkStmt ((Delete n):xs) rs=do unless (S.member n rs) $ report $ "deleting unallocated register "++n checkStmt xs $ S.delete n rs checkStmt ((Move p ps):xs) rs=mapM_ (\x->within "move" $ checkPointer x rs) (p:ps) >> checkStmt xs rs checkStmt ((Copy p ps):xs) rs=mapM_ (\x->within "copy" $ checkPointer x rs) (p:ps) >> checkStmt xs rs checkStmt ((Val p _):xs) rs=within "val" (checkPointer p rs) >> checkStmt xs rs checkStmt ((Clear p):xs) rs=within "clear" (checkPointer p rs) >> checkStmt xs rs checkStmt ((Inline name ns):xs) rs=do let s=S.fromList ns unless (s `S.isSubsetOf` rs) $ within ("inline "++name) $ report $ "unknown registers: " ++unwords (S.toList $s S.\\ rs) checkStmt xs rs checkStmt ((Input p):xs) rs=within "input" (checkPointer p rs) >> checkStmt xs rs checkStmt ((Output p):xs) rs=within "output" (checkPointer p rs) >> checkStmt xs rs checkStmt (_:xs) rs=checkStmt xs rs checkPointer :: Pointer -> S.Set RegName -> NMRE () checkPointer (Register x) rs=unless (S.member x rs) $ within "pointer" $ report $ "unknown register: "++x checkPointer _ rs=return () -- | Interpreter of 'SAM', usable for all phases. interpret :: SAM -> IO () interpret=runProcessWithIO f . checkSAM "SAMi" where f (SAM rs procs)=let ptb0=M.fromList $ map (procName &&& id) procs mtb0=(M.fromList $ zip rs $ repeat minit) st0=SAMInternal ptb0 mtb0 M.empty 0 in evalStateT (enterProc "^" []) st0 data SAMInternal=SAMInternal {procTable :: M.Map ProcName SProc ,memTable :: MemTable ,regTable :: RegTable ,pointer :: Int } type MemTable=M.Map Region FlatMemory type RegTable=M.Map ProcName (M.Map RegName Word8,M.Map RegName (ProcName,RegName)) type SAMST=StateT SAMInternal enterProc :: ProcName -> [(ProcName,RegName)] -> SAMST IO () enterProc name args=do liftIO $ putStrLn $ "entering:"++name dumpRegisters dumpMemory ptb<-liftM procTable get rtb<-liftM regTable get let SProc _ rs ss=M.findWithDefault (error $ "SAMi: procedure not found: "++name) name ptb when (length rs/=length args) $ error $ "SAMi: procedure arity error: "++show (name,rs,args) when (M.member name rtb) $ error $ "SAMi: re-entring to precedure: "++name let rtb'=M.insert name (M.empty,M.fromList $ zipWith (\org to->(to,uncurry (reduceReg rtb) org)) args rs) rtb modify (\x->x{regTable=rtb'}) execStmts name ss modify (\x->x{regTable=M.delete name $ regTable x}) liftIO $ putStrLn $ "leaving:"++name dumpMemory :: SAMST IO () dumpMemory=do t<-liftM memTable get p<-liftM pointer get let maxAddr=max 0 $ maximum (map msize $ M.elems t)-1 ss=map (\x->dumpMemoryBetween p t (x*w,(x+1)*w-1)) [0..maxAddr `div` w] liftIO $ putStr $ unlines ss where w=30 dumpMemoryBetween :: Int -> MemTable -> (Int,Int) -> String dumpMemoryBetween p t (a0,a1)=unlines $ map dumpKey ks where ks=M.keys t head=maximum $ map length ks dumpKey k=printf ("%"++show head++"s|") k++dump (t M.! k) dump fm=concatMap (\x->showAddr x $ mread fm x) [a0..a1] showAddr a v=(if a==p then ">" else " ")++(printf "%02s" $ showHex v "") dumpRegisters :: SAMST IO () dumpRegisters=do r<-liftM regTable get let ss=map (uncurry dumpRegisterP) $ M.assocs r liftIO $ putStr $ concat ss dumpRegisterP :: ProcName -> (M.Map RegName Word8,M.Map RegName (ProcName,RegName)) -> String dumpRegisterP proc (m0,m1)=unlines $ ("in "++proc++":"):rs where rs=(map (\(n,v)->" "++n++": "++showHex v "") $ M.assocs m0)++ (map (\(n,a)->" "++n++" -> "++show a) $ M.assocs m1) execStmts p=mapM_ (\x->execStmt p x >> liftIO (putStrLn (p++" "++(take 50 $ show x))) >> dumpRegisters >> dumpMemory >> liftIO (putStrLn "")) execStmt p (Alloc r)=modifyRT $ M.adjust (first $ M.insert r 0) p execStmt p (Delete r)=modifyRT $ M.adjust (first $ M.delete r) p execStmt p (Inline n rs)=enterProc n (zip (repeat p) rs) execStmt p (Val ptr d)=liftM (+fromIntegral d) (readPtr p ptr) >>= writePtr p ptr execStmt p s0@(While ptr ss)=do x<-readPtr p ptr when (x/=0) $ execStmts p ss >> execStmt p s0 execStmt p (Move ptr ptrs)=forM (ptr:ptrs) (readPtr p) >>= zipWithM_ (\ptr x->writePtr p ptr x) (ptr:ptrs) . f where f (x:xs)=0:map (+x) xs execStmt p (Copy ptr ptrs)=forM (ptr:ptrs) (readPtr p) >>= zipWithM_ (\ptr x->writePtr p ptr x) ptrs . f where f (x:xs)=map (+x) xs execStmt p (Locate d)=modifyPointer (+d) execStmt p (Dispatch r cs)=do x<-readPtr p (Register r) writePtr p (Register r) 0 let caluse=lookup (fromIntegral x) cs maybe (error $ "SAMi: dispatch:"++show (x,r,p)) (execStmts p) caluse execStmt p (Clear ptr)=writePtr p ptr 0 execStmt p (Input ptr)=liftIO getChar >>= writePtr p ptr . fromIntegral . ord execStmt p (Output ptr)=readPtr p ptr >>= liftIO . putChar . chr . fromIntegral execStmt p (Comment _)=return () readPtr :: Monad m => ProcName -> Pointer -> SAMST m Word8 readPtr p (Memory r d)=do dp<-liftM pointer get when (dp+d<0) $ error $ "readPtr: invalid op:"++show (p,r,dp,d) liftM (flip mread (dp+d) . (M.! r) . memTable) get readPtr p (Register r)=liftM (flip (flip readReg p) r . regTable) get writePtr :: Monad m => ProcName -> Pointer -> Word8 -> SAMST m () writePtr p (Memory r d) x=do dp<-liftM pointer get when (dp+d<0) $ error $ "writePtr: invalid op:"++show (p,r,dp,d) modifyMT $ M.adjust (flip (flip mwrite (dp+d)) x) r writePtr p (Register r) x=modifyRT (\t->writeReg t p r x) readReg :: RegTable -> ProcName -> RegName -> Word8 readReg t p r=(fst (t M.! p')) M.! r' where (p',r')=reduceReg t p r writeReg :: RegTable -> ProcName -> RegName -> Word8 -> RegTable writeReg t p r x=M.adjust (first $ M.insert r' x) p' t where (p',r')=reduceReg t p r reduceReg :: RegTable -> ProcName -> RegName -> (ProcName,RegName) reduceReg t p r |M.member r org = (p,r) |otherwise = alias M.! r where (org,alias)=t M.! p modifyRT :: Monad m => (RegTable -> RegTable) -> SAMST m () modifyRT f=modify $ \x->x{regTable=f $ regTable x} modifyMT :: Monad m => (MemTable -> MemTable) -> SAMST m () modifyMT f=modify $ \x->x{memTable=f $ memTable x} modifyPointer :: Monad m => (Int -> Int) -> SAMST m () modifyPointer f=modify $ \x->x{pointer=g $ pointer x} where g x=let y=f x in if x<0 then error $ "modifyPointer: invalid pos: "++show y else y