packages feed

intcode 0.2.0.0 → 0.3.0.0

raw patch · 7 files changed

+401/−220 lines, 7 filesdep +doctestdep ~basedep ~containersdep ~primitivePVP ok

version bump matches the API change (PVP)

Dependencies added: doctest

Dependency ranges changed: base, containers, primitive

API changes (from Hackage documentation)

- Intcode: Abs :: Mode
- Intcode: Add :: !a -> !a -> !a -> Opcode a
- Intcode: Arb :: !a -> Opcode a
- Intcode: Eq :: !a -> !a -> !a -> Opcode a
- Intcode: Hlt :: Opcode a
- Intcode: Imm :: Mode
- Intcode: Inp :: !a -> Opcode a
- Intcode: Jnz :: !a -> !a -> Opcode a
- Intcode: Jz :: !a -> !a -> Opcode a
- Intcode: Lt :: !a -> !a -> !a -> Opcode a
- Intcode: Machine :: !Int -> !Int -> !IntMap Int -> {-# UNPACK #-} !PrimArray Int -> Machine
- Intcode: Mul :: !a -> !a -> !a -> Opcode a
- Intcode: Out :: !a -> Opcode a
- Intcode: Rel :: Mode
- Intcode: [image] :: Machine -> {-# UNPACK #-} !PrimArray Int
- Intcode: [memory] :: Machine -> !IntMap Int
- Intcode: [pc] :: Machine -> !Int
- Intcode: [relBase] :: Machine -> !Int
- Intcode: data Mode
- Intcode: data Opcode a
- Intcode: decode :: Int -> Maybe (Opcode Mode)
- Intcode: instance Data.Foldable.Foldable Intcode.Opcode
- Intcode: instance Data.Traversable.Traversable Intcode.Opcode
- Intcode: instance GHC.Base.Functor Intcode.Opcode
- Intcode: instance GHC.Classes.Eq Intcode.Machine
- Intcode: instance GHC.Classes.Eq Intcode.Mode
- Intcode: instance GHC.Classes.Eq a => GHC.Classes.Eq (Intcode.Opcode a)
- Intcode: instance GHC.Classes.Ord Intcode.Machine
- Intcode: instance GHC.Classes.Ord Intcode.Mode
- Intcode: instance GHC.Classes.Ord a => GHC.Classes.Ord (Intcode.Opcode a)
- Intcode: instance GHC.Read.Read Intcode.Mode
- Intcode: instance GHC.Read.Read a => GHC.Read.Read (Intcode.Opcode a)
- Intcode: instance GHC.Show.Show Intcode.Machine
- Intcode: instance GHC.Show.Show Intcode.Mode
- Intcode: instance GHC.Show.Show a => GHC.Show.Show (Intcode.Opcode a)
+ Intcode: hRunIO :: Handle -> Handle -> Effect -> IO ()
+ Intcode.Machine: (!) :: Machine -> Int -> Int
+ Intcode.Machine: Machine :: !Int -> !Int -> !IntMap Int -> {-# UNPACK #-} !PrimArray Int -> Machine
+ Intcode.Machine: [memInitial] :: Machine -> {-# UNPACK #-} !PrimArray Int
+ Intcode.Machine: [memUpdates] :: Machine -> !IntMap Int
+ Intcode.Machine: [pc] :: Machine -> !Int
+ Intcode.Machine: [relBase] :: Machine -> !Int
+ Intcode.Machine: addRelBase :: Int -> Machine -> Machine
+ Intcode.Machine: data Machine
+ Intcode.Machine: instance GHC.Classes.Eq Intcode.Machine.Machine
+ Intcode.Machine: instance GHC.Classes.Ord Intcode.Machine.Machine
+ Intcode.Machine: instance GHC.Show.Show Intcode.Machine.Machine
+ Intcode.Machine: jmp :: Int -> Machine -> Machine
+ Intcode.Machine: memoryList :: Machine -> [Int]
+ Intcode.Machine: new :: [Int] -> Machine
+ Intcode.Machine: set :: Int -> Int -> Machine -> Machine
+ Intcode.Opcode: Abs :: Mode
+ Intcode.Opcode: Add :: !a -> !a -> !a -> Opcode a
+ Intcode.Opcode: Arb :: !a -> Opcode a
+ Intcode.Opcode: Eq :: !a -> !a -> !a -> Opcode a
+ Intcode.Opcode: Hlt :: Opcode a
+ Intcode.Opcode: Imm :: Mode
+ Intcode.Opcode: Inp :: !a -> Opcode a
+ Intcode.Opcode: Jnz :: !a -> !a -> Opcode a
+ Intcode.Opcode: Jz :: !a -> !a -> Opcode a
+ Intcode.Opcode: Lt :: !a -> !a -> !a -> Opcode a
+ Intcode.Opcode: Mul :: !a -> !a -> !a -> Opcode a
+ Intcode.Opcode: Out :: !a -> Opcode a
+ Intcode.Opcode: Rel :: Mode
+ Intcode.Opcode: data Mode
+ Intcode.Opcode: data Opcode a
+ Intcode.Opcode: decode :: Int -> Maybe (Opcode Mode)
+ Intcode.Opcode: instance Data.Foldable.Foldable Intcode.Opcode.Opcode
+ Intcode.Opcode: instance Data.Traversable.Traversable Intcode.Opcode.Opcode
+ Intcode.Opcode: instance GHC.Base.Functor Intcode.Opcode.Opcode
+ Intcode.Opcode: instance GHC.Classes.Eq Intcode.Opcode.Mode
+ Intcode.Opcode: instance GHC.Classes.Eq a => GHC.Classes.Eq (Intcode.Opcode.Opcode a)
+ Intcode.Opcode: instance GHC.Classes.Ord Intcode.Opcode.Mode
+ Intcode.Opcode: instance GHC.Classes.Ord a => GHC.Classes.Ord (Intcode.Opcode.Opcode a)
+ Intcode.Opcode: instance GHC.Read.Read Intcode.Opcode.Mode
+ Intcode.Opcode: instance GHC.Read.Read a => GHC.Read.Read (Intcode.Opcode.Opcode a)
+ Intcode.Opcode: instance GHC.Show.Show Intcode.Opcode.Mode
+ Intcode.Opcode: instance GHC.Show.Show a => GHC.Show.Show (Intcode.Opcode.Opcode a)
+ Intcode.Parse: parseInts :: String -> Maybe [Int]

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for intcode +## 0.3.0.0+* Split machine-state and opcodes into separate modules.+* Add `Intcode.Parse`+* Add `Intcode.hRunIO`+ ## 0.2.0.0 * Rename 'intCodeToList' to 'intcodeToList' 
+ doctests.hs view
@@ -0,0 +1,3 @@+import Test.DocTest+main :: IO ()+main = doctest ["-isrc", "Intcode", "Intcode.Machine", "Intcode.Opcode", "Intcode.Parse"]
intcode.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.4  name:                intcode-version:             0.2.0.0+version:             0.3.0.0 synopsis:            Advent of Code 2019 intcode interpreter category:            Compilers/Interpreters license:             ISC@@ -12,7 +12,7 @@ build-type:          Simple homepage:            https://github.com/glguy/intcode bug-reports:         https://github.com/glguy/intcode/issues-tested-with:         GHC ==8.6.5, GHC ==8.8.1+tested-with:         GHC ==8.6.5, GHC ==8.8.2  description:   Implementation of the Intcode virtual machine as defined by@@ -36,8 +36,22 @@    exposed-modules:     Intcode+    Intcode.Machine+    Intcode.Opcode+    Intcode.Parse    build-depends:     base         >= 4.12 && < 4.14,     containers   ^>= 0.6,     primitive    ^>= 0.7,++test-suite doctests+  type:              exitcode-stdio-1.0+  main-is:           doctests.hs+  default-language:  Haskell2010+  ghc-options:       -threaded+  build-depends:+    base,+    containers,+    primitive,+    doctest ^>= 0.16,
src/Intcode.hs view
@@ -1,4 +1,4 @@-{-# Language DeriveTraversable, Trustworthy #-}+{-# Language Safe #-} {-| Module      : Intcode Description : Intcode interpreter@@ -6,56 +6,70 @@ License     : ISC Maintainer  : emertens@gmail.com -This Intcode interpreter is defined across multiple Advent of Code days:--* <https://adventofcode.com/2019/day/2>-* <https://adventofcode.com/2019/day/5>-* <https://adventofcode.com/2019/day/7>-* <https://adventofcode.com/2019/day/9>+Intcode is a virtual machine environment defined to have some arithmetic,+conditional jumps, and simple input and output facilities. -This implementation works with the following passes:+The instruction set is designed with independently selectable address modes for+each of its input and output parameters. The architecture is designed to be+simple to implement while powerful enough to write interesting programs+efficiently. The addition of a /relative base pointer/ makes it easy to+implement function calls in the language. -  1. Parse input text file into a list of numbers-  2. Execute op codes to single-step input/output effects.-  3. Execute single-stop effects into big-step effects.+This Intcode architecture is defined across multiple+<https://adventofcode.com/2019/about Advent of Code 2019> tasks:+<https://adventofcode.com/2019/day/2 2>,+<https://adventofcode.com/2019/day/5 5>,+<https://adventofcode.com/2019/day/7 7>, and+<https://adventofcode.com/2019/day/9 9>  Common use modes: -* List functions: 'intcodeToList'-* Individual machine step processing: 'Step', 'new', 'step'-* Input/output interpretation: 'Effect', 'new', 'run'+* Machine construction: 'new'+* List functions: 'intcodeToList', 'effectList'+* Individual machine step processing: 'Step', 'step'+* Input/output interpretation: 'Effect', 'run' +Submodules:++* "Intcode.Machine" exposes the implementation details of the interpreter state.+* "Intcode.Parse" provides a parser for intcode text files.+* "Intcode.Opcode" provides types and the decoder for opcodes.+ -} module Intcode   (-  -- * Simple interface+  -- * Simple list interface   intcodeToList,-  runIO,    -- * Machine state-  Machine(..), (!), new, set, memoryList,+  Machine, (!), new, set, memoryList, -  -- * Effects-  Effect(..), run, (>>>), effectList, followedBy, feedInput,+  -- * Big-step semantics+  Effect(..), run, -  -- * Small-step-  Step(..), step,+  -- * Effect operations+  (>>>), followedBy, feedInput, effectList, -  -- * Opcodes-  Mode(..), Opcode(..), decode,+  -- * Small-step semantics+  Step(..), step,    -- * Exceptions-  IntcodeFault(..)+  IntcodeFault(..),++  -- * ASCII I/O interface+  runIO, hRunIO,+   ) where -import           Control.Exception (Exception(..), throwIO, throw)-import           Data.Char         (chr, ord)-import           Data.IntMap       (IntMap)-import           Data.Traversable  (mapAccumL)-import qualified Data.IntMap as IntMap-import qualified Data.Primitive.PrimArray as P-import           Text.Show.Functions ()+import Control.Exception   (Exception(..), throw, throwIO)+import Data.Char           (chr, ord)+import Data.Traversable    (mapAccumL)+import System.IO           (Handle, hGetChar, hPutChar, hPutStrLn, stdin, stdout)+import Text.Show.Functions () +import Intcode.Machine     (Machine(..), (!), addRelBase, jmp, memoryList, new, set)+import Intcode.Opcode      (Mode(..), Opcode(..), decode)+ ------------------------------------------------------------------------ -- ASCII I/O ------------------------------------------------------------------------@@ -63,16 +77,32 @@ -- | Run intcode program using stdio. Non-ASCII outputs are printed as -- integers. --+-- Note that input and output is affected by handle buffering modes.+-- -- >>> runIO (run (new [104,72,104,101,104,108,104,108,104,111,104,33,104,10,99])) -- Hello!+--+-- >>> runIO (run (new [104,-50,104,1000,99]))+-- <<-50>>+-- <<1000>> runIO :: Effect -> IO ()-runIO (Output o e)-  | 0 <= o, o < 0x80  = putChar (chr (fromIntegral o))    >> runIO e-  | otherwise         = putStrLn ("<<" ++ show o ++ ">>") >> runIO e-runIO (Input f)       = runIO . f . fromIntegral . ord =<< getChar-runIO Halt            = return ()-runIO Fault           = throwIO IntcodeFault+runIO = hRunIO stdin stdout +-- | 'runIO' generalized to an arbitrary input and output handle.+hRunIO ::+  Handle {- ^ input handle  -} ->+  Handle {- ^ output handle -} ->+  Effect {- ^ effect        -} ->+  IO ()+hRunIO inH outH = go+  where+    go (Output o e)+      | 0 <= o, o < 0x80 = hPutChar outH (chr (fromIntegral o)) >> go e+      | otherwise        = hPutStrLn outH ("<<" ++ show o ++ ">>") >> go e+    go (Input f)         = go . f . fromIntegral . ord =<< hGetChar inH+    go Halt              = return ()+    go Fault             = throwIO IntcodeFault+ ------------------------------------------------------------------------ -- High-level interface ------------------------------------------------------------------------@@ -114,90 +144,13 @@   [Int]  {- ^ outputs        -} effectList effect inputs =   case effect of-    Input f | x:xs <- inputs -> effectList (f x) xs-            | otherwise      -> throw IntcodeFault-    Output o e               -> o : effectList e inputs-    Halt                     -> []-    Fault                    -> throw IntcodeFault----------------------------------------------------------------------------- Machine state----------------------------------------------------------------------------- | Machine state.-data Machine = Machine-  { pc      :: !Int          -- ^ program counter-  , relBase :: !Int          -- ^ relative base pointer-  , memory  :: !(IntMap Int) -- ^ memory updates-  , image   :: {-# Unpack #-} !(P.PrimArray Int) -- ^ initial memory-  }-  deriving (Eq, Ord, Show)---- | Value stored in initial memory image at given index.-indexImage ::-  Machine {- ^ machine  -} ->-  Int     {- ^ position -} ->-  Int     {- ^ value    -}-indexImage m i-  | a `seq` True, 0 <= i, i < P.sizeofPrimArray a = P.indexPrimArray a i-  | otherwise                                     = 0-  where-    a = image m-{-# INLINE indexImage #-}---- | Memory lookup from 0-based index.-(!) ::-  Machine {- ^ machine  -} ->-  Int     {- ^ position -} ->-  Int     {- ^ value    -}-m ! i = IntMap.findWithDefault (indexImage m i) i (memory m)---- | Construct machine from a list of initial values starting--- at address 0. Program counter and relative base start at 0.-new ::-  [Int]   {- ^ initial memory -} ->-  Machine {- ^ new machine    -}-new initialValues = Machine-  { pc      = 0-  , relBase = 0-  , memory  = IntMap.empty-  , image   = P.primArrayFromList initialValues-  }---- | Update the value stored at a given location in memory.-set ::-  Int {- ^ position  -} ->-  Int {- ^ new value -} ->-  Machine -> Machine-set i v m-  | v == o    = m { memory = IntMap.delete i   (memory m) }-  | otherwise = m { memory = IntMap.insert i v (memory m) }-  where-    o = indexImage m i---- | Update the relative base pointer by adding an offset to it.-adjustRelBase :: Int {- ^ offset -} -> Machine -> Machine-adjustRelBase i mach = mach { relBase = relBase mach + i }---- | Set program counter to a new address.-jmp ::-  Int {- ^ program counter -} ->-  Machine -> Machine-jmp i mach = mach { pc = i }---- | Generate a list representation of memory starting from--- zero. This can get big for sparsely filled memory using--- large addresses. Returned values start at position 0.------ >>> memoryList (set 8 10 (new [1,2,3]))--- [1,2,3,0,0,0,0,0,10]-memoryList :: Machine -> [Int]-memoryList mach-  | IntMap.null (memory mach) = P.primArrayToList (image mach)-  | otherwise                 = [mach ! i | i <- [0 .. top]]-  where-    top = max (P.sizeofPrimArray (image mach) - 1)-              (fst (IntMap.findMax (memory mach)))+    Fault      -> throw IntcodeFault+    Halt       -> []+    Output o e -> o : effectList e inputs+    Input f    ->+      case inputs of+        x:xs -> effectList (f x) xs+        []   -> throw IntcodeFault  ------------------------------------------------------------------------ -- Big-step semantics@@ -238,13 +191,15 @@ -- >>> effectList (mult 3 >>> add 1) [4] -- [13] (>>>) :: Effect -> Effect -> Effect-x          >>> Output o y = Output o (x >>> y)-_          >>> Halt       = Halt-_          >>> Fault      = Fault-Output o x >>> Input f    = x >>> f o-Halt       >>> Input _    = Fault-Fault      >>> Input _    = Fault-Input f    >>> y          = Input (\i -> f i >>> y)+_ >>> Fault      = Fault+_ >>> Halt       = Halt+x >>> Output o e = Output o (x >>> e)+x >>> Input g    = input x+  where+    input Fault        = Fault+    input Halt         = Fault+    input (Output o e) = e >>> g o+    input (Input f)    = Input (input . f)  infixl 9 >>> @@ -252,24 +207,32 @@ -- -- >>> Output 1 Halt `followedBy` Output 2 Halt -- Output 1 (Output 2 Halt)+--+-- >>> Output 1 Halt `followedBy` Fault+-- Output 1 Fault+--+-- >>> Fault `followedBy` undefined+-- Fault followedBy :: Effect -> Effect -> Effect followedBy Halt         y = y followedBy Fault        _ = Fault followedBy (Output o x) y = Output o (followedBy x y)-followedBy (Input  f  ) y = Input (\i -> followedBy (f i) y)+followedBy (Input f)    y = Input (\i -> followedBy (f i) y)  -- | Provide an input to the first occurrence of an input request -- in a program effect. It is considered a fault if a program -- terminates before using the input. ----- >>> let mult n = Input (\i -> Output (i*n) Halt)--- >>> feedInput [6] (mult 5)+-- >>> feedInput [5,6] (Input (\x -> Input (\y -> Output (x*y) Halt))) -- Output 30 Halt+--+-- >>> feedInput [7] Halt+-- Fault feedInput :: [Int] {- ^ inputs -} -> Effect -> Effect feedInput []     e            = e feedInput xs     (Output o e) = Output o (feedInput xs e) feedInput (x:xs) (Input f)    = feedInput xs (f x)-feedInput _ _                 = Fault+feedInput _      _            = Fault  ------------------------------------------------------------------------ -- Small-step semantics@@ -315,7 +278,7 @@     Jz  a b   -> Step    (if at a == 0 then jmp (at b) m else m)     Lt  a b c -> Step    (set c (if at a <  at b then 1 else 0) m)     Eq  a b c -> Step    (set c (if at a == at b then 1 else 0) m)-    Arb a     -> Step    (adjustRelBase (at a) m)+    Arb a     -> Step    (addRelBase (at a) m)     Hlt       -> StepHalt   where     at i = m ! i@@ -323,91 +286,6 @@ mapWithIndex :: (Int -> a -> b) -> Int -> Opcode a -> (Int, Opcode b) mapWithIndex f = mapAccumL (\i a -> (i+1, f i a)) {-# INLINE mapWithIndex #-}----------------------------------------------------------------------------- Opcode decoder----------------------------------------------------------------------------- | Parameter modes-data Mode-  = Abs -- ^ absolute position-  | Imm -- ^ immediate-  | Rel -- ^ relative position-  deriving (Eq, Ord, Read, Show)---- | Opcodes parameterized over argument representations.-data Opcode a-  = Add !a !a !a -- ^ addition:        @c = a + b@-  | Mul !a !a !a -- ^ multiplication:  @c = a * b@-  | Inp !a       -- ^ input:           @a = input()@-  | Out !a       -- ^ output:          @output(a)@-  | Jnz !a !a    -- ^ jump-if-true:    @if a then goto b@-  | Jz  !a !a    -- ^ jump-if-false:   @if !a then goto b@-  | Lt  !a !a !a -- ^ less-than:       @c = a < b@-  | Eq  !a !a !a -- ^ equals:          @c = a == b@-  | Arb !a       -- ^ adjust-rel-base: @rel += a@-  | Hlt          -- ^ halt-  deriving (Eq, Ord, Read, Show, Functor, Foldable)---- | Decode an instruction to determine the opcode and parameter modes.------ >>> decode 1002--- Just (Mul Abs Imm Abs)-decode :: Int {- ^ opcode -} -> Maybe (Opcode Mode)-decode n =-  case n `rem` 100 of-    1  -> fill (Add 1 2 3)-    2  -> fill (Mul 1 2 3)-    3  -> fill (Inp 1)-    4  -> fill (Out 1)-    5  -> fill (Jnz 1 2)-    6  -> fill (Jz  1 2)-    7  -> fill (Lt  1 2 3)-    8  -> fill (Eq  1 2 3)-    9  -> fill (Arb 1)-    99 -> fill Hlt-    _  -> Nothing-  where-    fill = traverse (parameter n)---- | Compute the parameter mode for an argument at a given position.-parameter ::-  Int {- ^ opcode   -} ->-  Int {- ^ position -} ->-  Maybe Mode-parameter n i =-  case digit (i+1) n of-    0 -> Just Abs-    1 -> Just Imm-    2 -> Just Rel-    _ -> Nothing---- | Arguments visited from left to right.-instance Traversable Opcode where-  {-# INLINE traverse #-}-  traverse f o =-    case o of-      Add x y z -> Add <$> f x <*> f y <*> f z-      Mul x y z -> Mul <$> f x <*> f y <*> f z-      Inp x     -> Inp <$> f x-      Out x     -> Out <$> f x-      Jnz x y   -> Jnz <$> f x <*> f y-      Jz  x y   -> Jz  <$> f x <*> f y-      Lt  x y z -> Lt  <$> f x <*> f y <*> f z-      Eq  x y z -> Eq  <$> f x <*> f y <*> f z-      Arb x     -> Arb <$> f x-      Hlt       -> pure Hlt---- | Extract the ith digit from a number.------ >>> digit 0 2468--- 8--- >>> digit 3 2468--- 2--- >>> digit 4 2468--- 0-digit :: Int {- ^ position -} -> Int {- ^ number -} -> Int {- ^ digit -}-digit i x = x `quot` (10^i) `rem` 10  ------------------------------------------------------------------------ -- Exceptions
+ src/Intcode/Machine.hs view
@@ -0,0 +1,130 @@+{-# Language Trustworthy #-}+{-|+Module      : Intcode.Machine+Description : Intcode machine representation+Copyright   : (c) Eric Mertens, 2019+License     : ISC+Maintainer  : emertens@gmail.com++The module implements the representation of the intcode machine state.++The 'Machine' type stores the initial memory image in an array and+only stores changes to that initial image. This allows for more efficient+comparisons of machine states for equality when there are few changes to+memory.++This implementation of the machine supports negative memory addresses.+These are defined not to be used in the Advent of Code problems.++This implementation stores machine-sized 'Int' values in memory.++-}+module Intcode.Machine+  (+  -- * Machine state+  Machine(..), new,++  -- * Register operations+  jmp, addRelBase,++  -- * Memory operations+  (!), set, memoryList,+  )+ where++import           Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import qualified Data.Primitive.PrimArray as P++-- | Machine state is comprised of the program counter, relative base+-- pointer, and memory.+--+-- * Interact with registers using: 'jmp', 'addRelBase'+-- * Interact with memory using: ('!'), 'set'+-- * Build new machines with: 'new'+--+-- Updates to memory are stored separately from the initial values+-- which can enable equality comparisons to be relatively efficient.+-- This efficiency comes from being able to compare the inital memory+-- using only pointer equality when two machines are created by the+-- same call to 'new'.+data Machine = Machine+  { pc         :: !Int          -- ^ program counter+  , relBase    :: !Int          -- ^ relative base pointer+  , memUpdates :: !(IntMap Int) -- ^ memory updates+  , memInitial :: {-# Unpack #-} !(P.PrimArray Int) -- ^ initial memory+  }+  deriving (Eq, Ord, Show)++-- | Value stored in initial memory image at given index.+indexImage ::+  Machine {- ^ machine  -} ->+  Int     {- ^ position -} ->+  Int     {- ^ value    -}+indexImage m i+  | i < P.sizeofPrimArray a, 0 <= i = P.indexPrimArray a i+  | otherwise                       = 0+  where+    a = memInitial m+{-# INLINE indexImage #-}++-- | Memory lookup.+(!) ::+  Machine {- ^ machine  -} ->+  Int     {- ^ position -} ->+  Int     {- ^ value    -}+m ! i = IntMap.findWithDefault (indexImage m i) i (memUpdates m)+{-# INLINE (!) #-}++-- | Construct machine from a list of initial values starting+-- at address 0. Program counter and relative base start at 0.+new ::+  [Int] {- ^ initial memory -} ->+  Machine+new initialValues = Machine+  { pc         = 0+  , relBase    = 0+  , memUpdates = IntMap.empty+  , memInitial = P.primArrayFromList initialValues+  }++-- | Store value at given memory position.+set ::+  Int {- ^ position -} ->+  Int {- ^ value    -} ->+  Machine -> Machine+set i v m+  | v == o    = m { memUpdates = IntMap.delete i   (memUpdates m) }+  | otherwise = m { memUpdates = IntMap.insert i v (memUpdates m) }+  where+    o = indexImage m i++-- | Add offset to relative base pointer.+addRelBase ::+  Int {- ^ offset -} ->+  Machine -> Machine+addRelBase i mach = mach { relBase = relBase mach + i }+{-# INLINE addRelBase #-}++-- | Set program counter to a new address.+jmp ::+  Int {- ^ program counter -} ->+  Machine -> Machine+jmp i mach = mach { pc = i }+{-# INLINE jmp #-}++-- | Generate a list representation of memory starting from+-- zero. This can get big for sparsely filled memory using+-- large addresses. Returned values start at position 0.+--+-- >>> memoryList (set 8 10 (new [1,2,3]))+-- [1,2,3,0,0,0,0,0,10]+memoryList ::+  Machine ->+  [Int] {- ^ memory values -}+memoryList mach+  | IntMap.null (memUpdates mach) = P.primArrayToList (memInitial mach)+  | otherwise                  = [mach ! i | i <- [0 .. top]]+  where+    top = max (P.sizeofPrimArray (memInitial mach) - 1)+              (fst (IntMap.findMax (memUpdates mach)))
+ src/Intcode/Opcode.hs view
@@ -0,0 +1,109 @@+{-# Language DeriveTraversable, Safe #-}+{-|+Module      : Intcode.Opcode+Description : Intcode opcodes+Copyright   : (c) Eric Mertens, 2019+License     : ISC+Maintainer  : emertens@gmail.com++This module provides a representation of the intcode machine's opcodes.++Opcodes are parameterized over their parameters. This allows the+implementation to store both parameter modes and resolved parameter+pointers in the same constructors.++-}+module Intcode.Opcode+  (+  -- * Types+  Opcode(..), Mode(..),++  -- * Decoder+  decode,+  ) where++------------------------------------------------------------------------+-- Opcode decoder+------------------------------------------------------------------------++-- | Parameter modes+data Mode+  = Abs -- ^ absolute position+  | Imm -- ^ immediate+  | Rel -- ^ relative position+  deriving (Eq, Ord, Read, Show)++-- | Opcodes parameterized over argument representations.+data Opcode a+  = Add !a !a !a -- ^ __addition:__        @c = a + b@+  | Mul !a !a !a -- ^ __multiplication:__  @c = a * b@+  | Inp !a       -- ^ __input:__           @a = input()@+  | Out !a       -- ^ __output:__          @output(a)@+  | Jnz !a !a    -- ^ __jump-if-true:__    @if a then goto b@+  | Jz  !a !a    -- ^ __jump-if-false:__   @if !a then goto b@+  | Lt  !a !a !a -- ^ __less-than:__       @c = a < b@+  | Eq  !a !a !a -- ^ __equals:__          @c = a == b@+  | Arb !a       -- ^ __adjust-rel-base:__ @rel += a@+  | Hlt          -- ^ __halt__+  deriving (Eq, Ord, Read, Show, Functor, Foldable)++-- | Decode an instruction to determine the opcode and parameter modes.+--+-- >>> decode 1002+-- Just (Mul Abs Imm Abs)+decode :: Int {- ^ opcode -} -> Maybe (Opcode Mode)+decode n =+  case n `rem` 100 of+    1  -> fill (Add 1 2 3)+    2  -> fill (Mul 1 2 3)+    3  -> fill (Inp 1    )+    4  -> fill (Out 1    )+    5  -> fill (Jnz 1 2  )+    6  -> fill (Jz  1 2  )+    7  -> fill (Lt  1 2 3)+    8  -> fill (Eq  1 2 3)+    9  -> fill (Arb 1    )+    99 -> fill Hlt+    _  -> Nothing+  where+    fill = traverse (parameter n)+{-# INLINABLE decode #-}++-- | Compute the parameter mode for an argument at a given position.+parameter ::+  Int {- ^ opcode   -} ->+  Int {- ^ position -} ->+  Maybe Mode+parameter n i =+  case digit (i+1) n of+    0 -> Just Abs+    1 -> Just Imm+    2 -> Just Rel+    _ -> Nothing++-- | Arguments visited from left to right.+instance Traversable Opcode where+  {-# INLINE traverse #-}+  traverse f o =+    case o of+      Add x y z -> Add <$> f x <*> f y <*> f z+      Mul x y z -> Mul <$> f x <*> f y <*> f z+      Inp x     -> Inp <$> f x+      Out x     -> Out <$> f x+      Jnz x y   -> Jnz <$> f x <*> f y+      Jz  x y   -> Jz  <$> f x <*> f y+      Lt  x y z -> Lt  <$> f x <*> f y <*> f z+      Eq  x y z -> Eq  <$> f x <*> f y <*> f z+      Arb x     -> Arb <$> f x+      Hlt       -> pure Hlt++-- | Extract the ith digit from a number.+--+-- >>> digit 0 2468+-- 8+-- >>> digit 3 2468+-- 2+-- >>> digit 4 2468+-- 0+digit :: Int {- ^ position -} -> Int {- ^ number -} -> Int {- ^ digit -}+digit i x = x `quot` (10^i) `rem` 10
+ src/Intcode/Parse.hs view
@@ -0,0 +1,42 @@+{-# Language Safe #-}+{-|+Module      : Intcode.Parse+Description : Intcode source file parser+Copyright   : (c) Eric Mertens, 2019+License     : ISC+Maintainer  : emertens@gmail.com++This module implements a parser for the simple comma, separated format+used in the Advent of Code input files.++-}+module Intcode.Parse (parseInts) where++-- | Parse a list of comma separated integers.+--+-- >>> parseInts "1, - 2, 3,-4"+-- Just [1,-2,3,-4]+--+-- >>> parseInts " "+-- Just []+--+-- >>> parseInts "1,2,3,x"+-- Nothing+parseInts ::+  String      {- ^ parser input    -} ->+  Maybe [Int] {- ^ parsed integers -}+parseInts str+  | [(i,str1)] <- reads str = parseInts' [i] str1+  | [("","")]  <- lex str   = Just []+  | otherwise               = Nothing++-- | Helper function for 'parseInts'+parseInts' ::+  [Int]       {- ^ reversed accumulator -} ->+  String      {- ^ parser input         -} ->+  Maybe [Int] {- ^ parsed integers      -}+parseInts' xs str =+  case lex str of+    [(",",str1)] | [(x,str2)] <- reads str1 -> parseInts' (x:xs) str2+    [("","")]                               -> Just (reverse xs)+    _                                       -> Nothing