{-# LANGUAGE FlexibleContexts #-} import AoC import Control.Applicative import Control.Monad import Control.Monad.ST import Control.Monad.Trans.Class import Control.Monad.Trans.Except import Control.Monad.Trans.Maybe import Data.Array.ST import Data.Maybe import Text.ParserCombinators.ReadP import Text.Read.Lex data Instruction = Nop Int | Acc Int | Jmp Int deriving (Show) data MemoryCell = MemoryCell { instruction :: Instruction , executed :: Bool } deriving (Show) type Memory s = STArray s Int MemoryCell data Machine s = Machine { pc :: Int , acc :: Int , memory :: Memory s } data RunError = OutOfRange | WouldLoop | Breakpoint deriving (Show) type MachineRunner s = Machine s -> ExceptT RunError (ST s) (Machine s) parseInstruction :: ReadP Instruction parseInstruction = choice [ Nop <$> parseIns "nop" , Acc <$> parseIns "acc" , Jmp <$> parseIns "jmp" ] where parseIns name = string name >> char ' ' >> parseNumber parseNumber = do sign <- option '+' (choice $ map char $ "+-") num <- readDecP return $ case sign of '+' -> num '-' -> -num newMemory :: [Instruction] -> ST s (Memory s) newMemory insns = newListArray (0, length insns-1) $ [MemoryCell { instruction = ins, executed = False } | ins <- insns] newMachine :: [Instruction] -> ST s (Machine s) newMachine insns = do mem <- newMemory insns return $ Machine { pc = 0, acc = 0, memory = mem } stepMachine :: Machine s -> ST s (Machine s) stepMachine m = do cell <- readArray (memory m) (pc m) writeArray (memory m) (pc m) $ cell { executed = True } (pcF, accF) <- case instruction cell of Nop _ -> return ((+1), id) Acc i -> return ((+1), (+i)) Jmp i -> return ((+i), id) let newPc = pcF $ pc m newAcc = accF $ acc m return $ m { pc = newPc, acc = newAcc } runUnless :: (Machine s -> MaybeT (ST s) RunError) -> MachineRunner s -> MachineRunner s runUnless p stepper m = do res <- lift $ runMaybeT $ p m case res of Nothing -> stepper m Just e -> throwE e breakOnLoop :: MachineRunner s -> MachineRunner s breakOnLoop = runUnless alreadyExecuted where alreadyExecuted m = do cell <- lift $ readArray (memory m) (pc m) unless (executed cell) empty return WouldLoop breakOnAddr :: Int -> MachineRunner s -> MachineRunner s breakOnAddr addr = runUnless $ fetchFrom addr where fetchFrom addr m = do unless (pc m == addr) empty return Breakpoint runUntilError :: MachineRunner s -> Machine s -> ST s (RunError, Int, Int) runUntilError step m = do result <- runExceptT $ step m case result of Right m' -> runUntilError step m' Left e -> return (e, (pc m), (acc m)) part1 :: [Instruction] -> Int part1 is = fromJust $ runST $ runMaybeT $ do (WouldLoop, _, acc) <- lift $ newMachine is >>= runUntilError stepper return acc where stepper = breakOnLoop (lift . stepMachine) tryTerminate :: Machine s -> MaybeT (ST s) Int tryTerminate m = do breakpoint <- fmap (\(_, max) -> max + 1) $ lift $ getBounds (memory m) (Breakpoint, _, acc) <- lift $ runUntilError (stepper breakpoint) m return acc where stepper b = breakOnAddr b . breakOnLoop $ (lift . stepMachine) jmpNopFlips :: [Instruction] -> [[Instruction]] jmpNopFlips [] = [] jmpNopFlips (x:xs) = case x of Jmp i -> (Nop i:xs) : rest Nop i -> (Jmp i:xs) : rest _ -> rest where rest = (x:) <$> jmpNopFlips xs part2 :: [Instruction] -> Int part2 instructions = fromJust $ msum $ runST $ mapM testInstructions possibleInstructions where testInstructions is = runMaybeT $ lift (newMachine is) >>= tryTerminate possibleInstructions = instructions : jmpNopFlips instructions main = runAoC (map (fromJust . oneCompleteResult parseInstruction) . lines) part1 part2