diff --git a/Data/Stdf.hs b/Data/Stdf.hs
--- a/Data/Stdf.hs
+++ b/Data/Stdf.hs
@@ -1,29 +1,43 @@
 
 {-# LANGUAGE NegativeLiterals #-}
-{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoImplicitPrelude #-}
 
 module Data.Stdf ( parse
+                 , parseFile
                  , Stdf(..)
                  , Rec(..)
                  ) where
 
-import Data.Binary.Get
-import Data.ByteString.Lazy.Char8 as BL hiding (show)
-import Data.Word
-import Data.Int
-import Data.Bits (testBit)
-import Foreign.C.Types
+import Data.Stdf.Types
+import Data.Binary.Get hiding (Fail)
+import Data.ByteString.Lazy.Char8 as BL hiding (show, elem, notElem, all, concatMap, concat, zipWith, map, head)
+import Data.Bits (testBit, (.&.), shiftR)
 import Control.Applicative
-import Prelude hiding (show)
+import Prelude hiding (show, Left, Right)
+import qualified Prelude
 import Text.Show
 import Control.Monad
-import Data.Text.Lazy
 import qualified Data.ByteString.Base64.Lazy as Base64
 import Data.Text.Lazy.Encoding
+import Data.Text.Lazy hiding (all, concatMap, concat, zipWith, map, head)
+import GHC.Char
+import Data.Sequence (replicateA)
+import Data.Ix (range)
+import Data.Binary.IEEE754
+import Codec.Compression.GZip
+import Control.Exception
+import Data.UnixTime
+-- import Data.Time.Compat
+import Data.Time.Clock
+import Data.Time.Clock.POSIX
+import Data.Maybe
 
-import GHC.Generics hiding (U1)
-import Data.Aeson
+import Debug.Trace
 
+-- TODO: Travis
+-- TODO: JSON/XML data model for STDF
+
 -- JSON gotcha: can't encode ByteString
 -- Have to convert character strings to Text-latin1
 -- Encoding base64 raw binary data
@@ -34,81 +48,206 @@
 -- and I was like fuck that. All my testers and all my computers are x86
 -- anyway.
 
-type U1 = Word8  -- unsigned 1 byte
-type U2 = Word16 -- unsigned 2 bytes
-type U4 = Word32 -- unsigned 4 bytes
-type I1 = Int8
-type I2 = Int16
-type I4 = Int32
-type R4 = CFloat
-type R8 = CDouble
 
-instance ToJSON Rec
-instance ToJSON PartFlg
+getFar :: Get Rec
+getFar = Far <$> getWord8 <*> getWord8
 
-data BinRec = BinRec 
-    { header :: Header
-    , rec :: Rec } deriving (Generic, Show)
+getAtr :: Get Rec
+getAtr = Atr <$> getTime <*> mcn
 
-data Header = Header
-    { len :: !Word16
-    , typ :: !Word8
-    , sub :: !Word8
-    } deriving (Generic, Show)
+getTime :: Get (Maybe UTCTime)
+getTime = do
+    secs <- u4
+    return $ if secs == 0 then Nothing else Just $ posixSecondsToUTCTime $ realToFrac secs
 
-type Stdf = [Rec]
+getMilliseconds :: Get (Maybe Milliseconds)
+getMilliseconds = do
+  milliseconds <- mu4
+  return $ Milliseconds <$> milliseconds
 
-data Rec  = Raw { raw :: Text } -- base64 TODO: maybe don't bother. what's this good for?
-          | Far { cpuType  :: !U1
-               , stdfVer  :: !U1 }
-          | Prr { headNum  :: !U1
-                , siteNum  :: !U1
-                , partFlg  :: !PartFlg
-                , numTest  :: !U2
-                , hardBin  :: !U2
-                , softBin  :: Maybe U2
-                , xCoord   :: Maybe I2
-                , yCoord   :: Maybe I2
-                , testTime :: Maybe U4
-                , partID   :: Maybe Text
-                , partTxt  :: Maybe Text
-                , partFix  :: Maybe Text }
-          deriving (Generic, Show)
+getMinutes :: Get (Maybe Minutes)
+getMinutes = do
+    mins <- u2
+    return $ if mins == 65535 then Nothing else Just $ Minutes mins
 
-data PartFlg = PartFlg { supersedesPartId :: Bool -- 1 bit
-                       , supersedesXY :: Bool -- 1 bit
-                       , abnormalEnd :: Bool
-                       , failed :: Bool
-                       , noPassFailInfo :: Bool }
-                       deriving (Generic, Show)
+getMir :: Get Rec
+getMir = Mir <$> getTime <*> getTime <*> u1 <*> mc1<*> mc1  <*> mc1 <*> getMinutes
+             <*> mc1 <*> cn <*> cn <*> cn <*> cn <*> cn <*> mcn 
+             <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn 
+             <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn 
+             <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn
+             <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn
 
-getFar :: Get Rec
-getFar = Far <$> getWord8 <*> getWord8
+getMrr :: Get Rec
+getMrr = Mrr <$> getTime <*> mc1 <*> mcn <*> mcn
 
+getPcr :: Get Rec
+getPcr = Pcr <$> u1 <*> u1 <*> u4 <*> mu4 <*> mu4 <*> mu4 <*> mu4 
+
+getHbr :: Get Rec
+getHbr = Hbr <$> u1 <*> u1 <*> u2 <*> u4 <*> getPassFailBin <*> mcn
+
+getSbr :: Get Rec
+getSbr = Sbr <$> u1 <*> u1 <*> u2 <*> u4 <*> getPassFailBin <*> mcn
+
+getPmr :: Get Rec
+getPmr = Pmr <$> u2 <*> mu2e0 <*> mcn <*> mcn <*> mcn <*> u1 <*> u1
+
+getPgr :: Get Rec
+getPgr = Pgr <$> u2 <*> mcn <*> getU2List
+
+getPlr :: Get Rec
+getPlr = do
+    k <- fromIntegral <$> u2
+    indecies <- replicateM k u2
+    grpMode <- replicateM k u2
+    let groupModes = map toGroupMode grpMode
+    grpRdx <-  replicateM k u1
+    let radixes = map toRadix grpRdx
+    pgmChaR <- replicateM k mcn
+    rtnChaR <- replicateM k mcn
+    pgmChaL <- replicateM k mcn
+    rtnChaL <- replicateM k mcn
+
+    -- let pgmChars = zipWith (++) pgmChaL pgmChaR
+    -- let rtnChars = zipWith (++) rtnChaL rtnChaR
+
+    return $ Plr indecies groupModes radixes pgmChaR rtnChaR pgmChaL rtnChaL
+
+toGroupMode :: U2 -> GroupMode
+toGroupMode 00 = UnknownGroupMode
+toGroupMode 10 = Normal
+toGroupMode 20 = SameCycleIO
+toGroupMode 21 = SameCycleMidband
+toGroupMode 22 = SameCycleValid
+toGroupMode 23 = SameCycleWindowSustain
+toGroupMode 30 = DualDrive
+toGroupMode 31 = DualDriveMidband
+toGroupMode 32 = DualDriveValid
+toGroupMode 33 = DualDriveWindowSustain
+toGroupMode x = OtherGroupMode x
+
+toRadix :: U1 -> Radix
+toRadix 0  = DefaultRadix
+toRadix 2  = Binary
+toRadix 8  = Octal
+toRadix 10 = Decimal
+toRadix 16 = Hexadecimal
+toRadix 20 = Symbolic
+toRadix x  = OtherRadix x
+
+getRdr :: Get Rec
+getRdr = Rdr <$> getU2List
+
+getSdr :: Get Rec
+getSdr = Sdr <$> u1 <*> u1 <*> getU1List <*> mcn <*> mcn 
+             <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn
+             <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn
+-- getSdr = Sdr <$> u1 <*> u1 <*> u1 <*> getU1List <*> replicateA 16 mcn
+
+getWir :: Get Rec
+getWir = Wir <$> u1 <*> u1 <*> getTime <*> mcn
+
+getWrr :: Get Rec
+getWrr = Wrr <$> u1 <*> u1 <*> getTime <*> u4 <*> mu4 <*> mu4 <*> mu4
+             <*> mu4 <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn <*> mcn
+
+getWcr :: Get Rec
+getWcr = Wcr <$> mr4 <*> mr4 <*> mr4 <*> getWaferUnits <*> getDirection 
+             <*> mi2 <*> mi2 <*> getDirection <*> getDirection
+
+getPassFailBin :: Get PassFailBin
+getPassFailBin = do
+    c <- c1
+    return $ case c of
+        'P' -> PassBin
+        'F' -> FailBin
+        ' ' -> UnknownBin
+        _   -> OtherBin c
+
+getDirection :: Get (Maybe Direction)
+getDirection = charToDirection <$> c1
+    where charToDirection 'U' = Just Up
+          charToDirection 'D' = Just Down
+          charToDirection 'L' = Just Left
+          charToDirection 'R' = Just Right
+          charToDirection ' ' = Nothing
+          charToDirection x = Just $ OtherDirection x
+
+getWaferUnits :: Get (Maybe WaferUnits)
+getWaferUnits = numToUnits <$> u1
+    where numToUnits 0 = Nothing
+          numToUnits 1 = Just Inches
+          numToUnits 2 = Just Centimeters
+          numToUnits 3 = Just Millimeters
+          numToUnits 4 = Just Mils
+          numToUnits x = Just $ OtherUnits x
+
+-- TODO: there's a pattern here
+getU1List :: Get [U1]
+getU1List = do
+    cnt <- u1
+    let count = fromIntegral cnt
+    replicateM count u1
+
+getU2List :: Get [U2]
+getU2List = do
+    cnt <- u2
+    let count = fromIntegral cnt
+    replicateM count u2
+
 u1 = getWord8
 u2 = getWord16le
 u4 = getWord32le
-cn = getVarLen
 
+r4 :: Get R4
+r4 = getFloat32le
+
+r8 :: Get R8
+r8 = getFloat64le
+
 i1 :: Get I1
 i1 = fromIntegral <$> u1
 
 i2 :: Get I2
 i2 = fromIntegral <$> u2
 
-i4 :: Get I1
-i4 = fromIntegral <$> u1
+i4 :: Get I4
+i4 = fromIntegral <$> u4
 
-mu2 :: Get (Maybe U2)
+mu2, mu2e0 :: Get (Maybe U2)
 mu2 = missing (65535 :: U2) <$> u2
+mu2e0 = missing (0 :: U2) <$> u2
 
+mu1e0 :: Get (Maybe U1)
+mu1e0 = missing (0 :: U1) <$> u1
+mu1e255 = missing (255 :: U1) <$> u1
+
 missing :: Eq a => a -> a -> Maybe a
 missing n m =
     if m == n
         then Nothing
         else Just m
 
+c1 :: Get Char
+c1 = (chr . fromIntegral) <$> u1
 
+mc1 :: Get (Maybe C1)
+mc1 = getMaybe c1 ' '
+
+mcn :: Get (Maybe Text)
+mcn = getMaybe cn ""
+
+mr4 :: Get (Maybe R4)
+mr4 = getMaybe r4 0.0
+
+getMaybe :: Eq a => Get a -> a -> Get (Maybe a)
+getMaybe get missingVal = do
+    x <- get
+    return $ if x == missingVal
+             then Nothing
+             else Just x
+
 mi2 :: Get (Maybe I2)
 mi2 = do
     x <- i2
@@ -120,31 +259,356 @@
 mu4 = do
     x <- u4
     return $ case x of
-        0 -> Nothing
+        4294967295 -> Nothing
         otherwise -> Just x
 
-getVarLen :: Get (Maybe Text)
-getVarLen = do
+cn :: Get Text
+cn = do
     len <- u1
     case len of
-        0         -> return Nothing
-        otherwise -> liftM (Just . decodeUtf8) $ getLazyByteString (fromIntegral len)
+        0         -> return ""
+        otherwise -> liftM decodeUtf8 $ getLazyByteString (fromIntegral len)
 
 bit :: U1 -> Int -> Bool
 bit = testBit
 
-getPartFlg :: Get PartFlg
-getPartFlg = do
+getPartFlag :: Get PartFlag
+getPartFlag = do
     b <- u1
-    return PartFlg { supersedesPartId = bit b 0
+    return PartFlag { supersedesPartId = bit b 0
                    , supersedesXY     = bit b 1
                    , abnormalEnd      = bit b 2
                    , failed           = bit b 3
                    , noPassFailInfo   = bit b 4 }
 
 getPrr :: Get Rec
-getPrr = Prr <$> u1 <*> u1 <*> getPartFlg <*> u2 <*> u2 <*> mu2 <*> mi2 <*> mi2 <*> mu4 <*> cn <*> cn <*> cn
+getPrr = Prr <$> u1 <*> u1 <*> getPartFlag <*> u2 <*> u2 <*> mu2
+             <*> mi2 <*> mi2 <*> getMilliseconds <*> mcn <*> mcn <*> mcn
 
+getPir :: Get Rec
+getPir = Pir <$> u1 <*> u1
+
+getTsr :: Get Rec
+getTsr = Tsr <$> u1 <*> u1 <*> getTestType <*> u4 <*> mu4 <*> mu4 <*> mu4 
+             <*> mcn <*> mcn <*> mcn -- TODO: actually get the optional data
+             <*> pure Nothing
+             <*> pure Nothing
+             <*> pure Nothing
+             <*> pure Nothing
+             <*> pure Nothing
+         where 
+               getTestType = charToTestType <$> c1
+               charToTestType 'P' = Just Parametric
+               charToTestType 'F' = Just Functional
+               charToTestType 'M' = Just MultiResultParametric
+               charToTestType ' ' = Nothing
+               charToTestType x = Just $ OtherTestType x
+
+decodeTestFlags :: U1 -> [TestFlag]
+decodeTestFlags fl = if fl == 0
+                        then [Pass]
+                        else mkFlagList mapTf fl
+                     where
+                         mapTf 0 = Alarm
+                         mapTf 1 = Invalid     -- bit 1
+                         mapTf 2 = Unreliable  -- bit 2
+                         mapTf 3 = Timeout     -- bit 3
+                         mapTf 4 = NotExecuted -- bit 4
+                         mapTf 5 = Aborted     -- bit 5
+                         mapTf 6 = InValid     -- bit 6
+                         mapTf 7 = Fail        -- bit 7
+
+decodeParametricFlags :: U1 -> [ParametricFlag]
+decodeParametricFlags = mkFlagList mapPf
+    where
+        mapPf 0 =  ScaleError -- I could probably get this from Enum duh
+        mapPf 1 =  DriftError          -- bit 1
+        mapPf 2 =  Oscillation         -- bit 2
+        mapPf 3 =  FailHighLimit       -- bit 3
+        mapPf 4 =  FailLowLimit        -- bit 4
+        mapPf 5 =  PassAlternateLimits -- bit 5
+        mapPf 6 =  PassOnEqLowLimit    -- bit 6
+        mapPf 7 =  PassOnEqHighLimit   -- bit 7
+
+mkFlagList :: (Int -> a) -> U1 -> [a]
+mkFlagList func flags = [func bi | bi <- range (0, 7), testBit flags bi]
+
+getDecodeFlags :: (U1 -> [a]) -> Get [a]
+getDecodeFlags decodeF = do
+    fl <- u1
+    return $ decodeF fl
+
+getTestFlags :: Get [TestFlag]
+getTestFlags = getDecodeFlags decodeTestFlags
+
+getParametricFlags :: Get [ParametricFlag]
+getParametricFlags = getDecodeFlags decodeParametricFlags
+
+-- TODO: Make sure result is Nothing if invalid bit set in testFlags
+getPtr :: Get Rec
+getPtr = do
+        ptrTestNum <- u4
+        ptrHeadNum <- u1
+        ptrSiteNum <- u1
+        testFlags <- getTestFlags
+        parametricFlags <- getParametricFlags
+        result <- r4 -- depends on testFlags and parametric flags
+        testText <- mcn
+        alarmId <- mcn -- part of optionalInfo now
+        -- optionalInfo <- getOptionalInfo
+        let optionalInfo = [] -- TODO
+        let mresult = if validResult testFlags parametricFlags
+                        then Just result
+                        else Nothing
+
+        return $ Ptr  ptrTestNum
+                      ptrHeadNum
+                      ptrSiteNum
+                      testFlags
+                      parametricFlags
+                      mresult
+                      testText
+                      optionalInfo
+        where
+            validResult tf pf = all (`elem` [Pass, Fail]) tf 
+                             && all (`notElem` [ScaleError, DriftError, Oscillation]) pf
+{-
+            getOptionalInfo :: Get (Maybe OptionalInfo)
+            getOptionalInfo = do
+                    -- check we're not at the end of the buffer
+                    noInfo <- isEmpty
+                    if noInfo 
+                        then return Nothing 
+                        else do
+                            optinfo <- getOptionalInfo'
+                            return $ Just optinfo
+
+            getOptionalInfo' :: Get OptionalInfo
+            getOptionalInfo' = do
+                optFlag <- u1 -- getOptFlag
+
+                let [ invalidResultExp,      -- bit 0
+                    _,                     -- bit 1
+                    invalidLowSpecLimit,   -- bit 2
+                    invalidHighSpecLimit,  -- bit 3
+                    invalidLowLimit,       -- bit 4
+                    invalidHighLimit,      -- bit 5
+                    invalidLowTestLimit,   -- bit 6
+                    invalidHighTestLimit ] -- bit 7
+                    = Prelude.map (testBit optFlag) $ range (0,7)
+
+                let invalidLowTest  = invalidLowLimit || invalidLowTestLimit
+                let invalidHighTest = invalidHighLimit || invalidHighTestLimit
+
+                OptionalInfo <$> getOnFalse invalidResultExp i1
+                            <*> getOnFalse invalidLowTest i1
+                            <*> getOnFalse invalidHighTest i1
+                            <*> getOnFalse invalidLowTest r4
+                            <*> getOnFalse invalidHighTest r4
+                            <*> mcn <*> mcn <*> mcn <*> mcn 
+                            <*> getOnFalse invalidLowSpecLimit r4
+                            <*> getOnFalse invalidHighSpecLimit r4
+-}
+
+-- TODO: So Mpr doesn't use the Invalid flag
+getMpr :: Get Rec
+getMpr = do
+    testNum <- u4
+    headNum <- u1
+    siteNum <- u1
+    testFlg <- getTestFlags
+    parmFlg <- getParametricFlags
+    -- TODO: record may end here if no more info
+    j <- fromIntegral <$> u2
+    -- TODO: record may end here if no more info
+    k <- fromIntegral <$> u2
+    rtnStat <- getNibbles j :: Get [U1]
+    rtnRslt <- replicateM k r4
+    testTxt <- mcn
+    -- TODO: record may end here if no more info
+    let info = [] -- TODO: parse OptionalInfo
+
+    return $ Mpr testNum headNum siteNum testFlg parmFlg rtnStat rtnRslt testTxt info
+
+-- Take a list of bytes and split into exactly n nibbles
+fromNibbles :: [U1] -> Int -> [U1]
+fromNibbles [byte] 0         = []
+fromNibbles [byte] 1         = [0xF .&. byte]
+fromNibbles [byte] 2         = [0xF .&. byte, byte `shiftR` 4]
+fromNibbles (byte:bytes) n = 0xF .&. byte : byte `shiftR` 4 : fromNibbles bytes (n - 2)
+
+getNibbles :: Int -> Get [U1]
+getNibbles nnibs = do
+    let nbytes = nnibs `div` 2 + if odd nnibs then 1 else 0
+    bytes <- replicateM nbytes u1
+    return $ fromNibbles bytes nnibs
+
+
+getOnFalse :: Bool -> Get a -> Get (Maybe a)
+getOnFalse cond get = do
+    x <- get
+    return $ if cond
+            then Nothing
+            else Just x
+
+getFtr :: Get Rec
+getFtr = do
+    testNum <- u4
+    headNum <- u1
+    siteNum <- u1
+    testFlag <- getTestFlags  -- doesn't use the invalid bit
+    -- TODO: record may end here, before rtnIcnt or pgmIcnt -- check isEmpty
+    empty <- isEmpty
+    info <- if empty then return []
+            else getInfo
+    -- may end here
+    emptyj <- isEmpty
+    j0 <- if emptyj then return 0
+         else fromIntegral <$> u2
+    -- may end here
+    emptyk <- isEmpty
+    k0 <- if emptyk then return 0
+         else fromIntegral <$> u2
+
+    emptymi <- isEmpty
+    moreInfo <- if emptymi then return []
+                else getMoreInfo j0 k0
+
+    let allInfo = info ++ moreInfo
+
+    return $ Ftr testNum headNum siteNum testFlag allInfo
+                -- cycleCnt relVadr reptCnt numFail 
+                -- xFail yFail vectOff rtnIndx rtnStat pgmIndx pgmStat
+                -- failPin vecNam timeSet opCode testTxt alarmId progTxt rsltTxt patgNum spinMap
+
+  where 
+   getMoreInfo j k = do
+    rtnIndx <- replicateM j u2
+    rtnStat <- getNibbles j
+    pgmIndx <- replicateM k u2
+    pgmStat <- getNibbles k
+
+    failPin <- getBitField
+    vecNam  <- mcn
+    timeSet <- mcn
+    opCode  <- mcn
+    testTxt <- mcn
+    alarmId <- mcn
+    progTxt <- mcn
+    rsltTxt <- mcn
+
+    -- can it end here?
+    patgNum <- mu1e255
+    spinMap <- getBitField
+    let patgNum = Nothing
+    let spinMap = []
+
+    let rtnIndx' = if j == 0 then Nothing else Just rtnIndx
+    let rtnStat' = if j == 0 then Nothing else Just rtnStat
+    let pgmIndx' = if k == 0 then Nothing else Just pgmIndx
+    let pgmStat' = if k == 0 then Nothing else Just pgmStat
+    let failPin' = if Prelude.null failPin then Nothing else Just failPin
+    let spinMap' = if Prelude.null spinMap then Nothing else Just spinMap
+
+    return $ catMaybes [ ReturnPinIndecies <$> rtnIndx'
+                       , ReturnedStates <$> rtnStat'
+                       , PgmStateIndecies <$> pgmIndx'
+                       , PgmStates <$> pgmStat'
+                       , FailPin <$> failPin'
+                       , VectorName <$> vecNam
+                       , TimeSet <$> timeSet
+                       , OpCode <$> opCode
+                       , Label <$> testTxt
+                       , AlarmId <$> alarmId
+                       , ProgramText <$> progTxt
+                       , ResultText <$> rsltTxt
+                       , PatternGen <$> patgNum
+                       , EnabledPins <$> spinMap' ]
+
+   getInfo = do
+    optFlag <- u1
+
+    let [noCycleCnt,
+         noRelVadr,
+         noReptCnt,
+         noNumFail,
+         noXYFail,
+         noVectOff] = Prelude.map (testBit optFlag) $ range (0, 5)
+
+    -- Turns out you're supposed to get the byte
+    -- even if it's invalid
+    -- For a format seemingly obsessed with saving
+    -- a few bytes, this is surprising
+    cycleCnt <- getOnFalse noCycleCnt u4
+    relVadr  <- getOnFalse noRelVadr u4
+    reptCnt  <- getOnFalse noReptCnt u4
+    numFail  <- getOnFalse noNumFail u4
+    xFail    <- getOnFalse noXYFail i4
+    yFail    <- getOnFalse noXYFail i4
+    vectOff  <- getOnFalse noVectOff i2
+
+    return $ catMaybes
+              [CycleCount <$> cycleCnt,
+              RelativeVectorAddr <$> relVadr,
+              RepeatCount <$> reptCnt,
+              NumFailingPins <$> numFail,
+              XLogicalFailureAddr <$> xFail,
+              YLogicalFailureAddr <$> yFail,
+              OffsetFromVector <$> vectOff]
+
+{-
+
+    -}
+
+-- TODO: BitField type like [U1] which toJson prints as hex "FF AF 12 ..."
+getBitField :: Get [U1]
+getBitField = do
+    nbits <- u2
+    let nbytes = let (mbytes, mbits) = nbits `divMod` 8
+                     obytes = mbytes + if mbits == 0 then 0 else 1
+                 in fromIntegral obytes
+    replicateM nbytes u1
+
+getByteField :: Get [U1]
+getByteField = do
+    nbytes <- fromIntegral <$> u1
+    replicateM nbytes u1
+
+getBps :: Get Rec
+getBps = Bps <$> mcn
+
+getEps :: Get Rec
+getEps = return Eps
+
+getGdr :: Get Rec
+getGdr = do
+    fieldCount <- fromIntegral <$> u2
+    fields <- replicateM fieldCount getGdrField
+    return $ Gdr fields
+
+getGdrField :: Get GdrField
+getGdrField = do
+    fieldType <- fromIntegral <$> u1
+    case fieldType of
+        0 -> return GPad
+        1 -> GU1 <$> u1
+        2 -> GU2 <$> u2
+        3 -> GU4 <$> u4
+        4 -> GI1 <$> i1
+        5 -> GI2 <$> i2
+        6 -> GI4 <$> i4
+        7 -> GFloat <$> r4
+        8 -> GDouble <$> r8
+        10 -> GStr <$> cn
+        11 -> GBytes <$> getByteField -- first 2 bytes are length in bytes
+        12 -> GData <$> getBitField -- first 2 bytes are length in bits. I think I wrote something like this already
+        13 -> GNibble <$> (liftM head . getNibbles) 1
+        _ -> return GPad -- So don't crash on broken GDR
+
+getDtr :: Get Rec
+getDtr = Dtr <$> cn
+
+----------------------------------------------------------------
 getRawRec :: Integral a => a -> Get Rec
 getRawRec len = do
     bytes <- getLazyByteString (fromIntegral len)
@@ -165,9 +629,77 @@
 processRec :: Header -> ByteString -> Rec
 processRec hdr = runGet (specificGet hdr)
 
+typStdfInfo    = 0
+subFar = 10
+subAtr = 20
+typPerLot      = 1
+subMir = 10
+subMrr = 20
+subPcr = 30
+subHbr = 40
+subSbr = 50
+subPmr = 60
+subPgr = 62
+subPlr = 63
+subRdr = 70
+subSdr = 80
+typPerWafer    = 2
+subWir = 10
+subWrr = 20
+subWcr = 30
+typPerPart     = 5
+subPir = 10
+subPrr = 20
+typPerTest     = 10
+subTsr = 30
+typPerTestExec = 15
+subPtr = 10
+subMpr = 15
+subFtr = 20
+typPerProgSeg  = 20
+subBps = 10
+subEps = 20
+typGeneric     = 50
+subGdr = 10
+subDtr = 30
+
+-- Dispatch table for record types
 specificGet :: Header -> Get Rec
+-- STDF file info
 specificGet (Header _ 0 10) = getFar
+specificGet (Header _ 0 20) = getAtr
+-- Per Lot info
+specificGet (Header _ 1 10) = getMir
+specificGet (Header _ 1 20) = getMrr
+specificGet (Header _ 1 30) = getPcr
+specificGet (Header _ 1 40) = getHbr
+specificGet (Header _ 1 50) = getSbr
+specificGet (Header _ 1 60) = getPmr
+specificGet (Header _ 1 62) = getPgr
+specificGet (Header _ 1 63) = getPlr
+specificGet (Header _ 1 70) = getRdr
+specificGet (Header _ 1 80) = getSdr
+-- Per Wafer Info
+specificGet (Header _ 2 10) = getWir
+specificGet (Header _ 2 20) = getWrr
+specificGet (Header _ 2 30) = getWcr
+-- Per-Part Info
+specificGet (Header _ 5 10) = getPir
 specificGet (Header _ 5 20) = getPrr
+-- Per-Test
+specificGet (Header _ 10 30) = getTsr
+-- -- Per-Test-Execution
+specificGet (Header _ 15 10) = getPtr
+specificGet (Header _ 15 15) = getMpr
+specificGet (Header _ 15 20) = getFtr
+-- -- Per-Program-Segment
+specificGet (Header _ 20 10) = getBps
+specificGet (Header _ 20 20) = getEps
+-- -- Generic Data
+specificGet (Header _ 50 10) = getGdr
+specificGet (Header _ 50 30) = getDtr
+
+--
 specificGet (Header len _ _) = getRawRec len
 
 getBinRec :: Get BinRec
@@ -194,5 +726,20 @@
                 recs <- getStdf
                 return (record:recs)
 
+isGzipped :: ByteString -> IO Bool
+isGzipped bs = do
+    let magic = runGet getWord16le bs
+    return $ magic == 0x8b1f
+
+-- | Parse an optionally-gzipped stdf file
+parseFile :: String -> IO Stdf
+parseFile fn = do
+    bs <- BL.readFile fn
+    isgz <- isGzipped bs
+    let decompressed = decompress bs
+    return $ parse $ if isgz then decompressed else bs
+
+-- | Parse an Stdf from a ByteString in case you want to open your own files or
+-- | parse a stream off the tester or something
 parse :: ByteString -> Stdf
 parse = runGet getStdf
diff --git a/Data/Stdf/Types.hs b/Data/Stdf/Types.hs
new file mode 100644
--- /dev/null
+++ b/Data/Stdf/Types.hs
@@ -0,0 +1,456 @@
+
+{-# LANGUAGE DeriveGeneric #-}
+
+module Data.Stdf.Types where
+
+import Data.Word
+import Data.Int
+import Foreign.C.Types
+import Data.Text.Lazy
+import GHC.Generics hiding (U1, C1)
+import Data.Aeson
+import Data.Aeson.Types
+import Data.Time.Clock
+
+-- Time is local unix time. Shouldn't they put the time zone in the file
+-- in order for this to have any meaning? What if the data isn't being parsed
+-- in the same time zone as it was generated? It will be as if the tester
+-- was transported through time and space to wherever I am. I think I'll
+-- just leave it alone so times can be Word32 representing Unix time
+
+type U1 = Word8  -- unsigned 1 byte
+type U2 = Word16 -- unsigned 2 bytes
+type U4 = Word32 -- unsigned 4 bytes
+type I1 = Int8
+type I2 = Int16
+type I4 = Int32
+type R4 = Float -- CFloat
+type R8 = Double -- CDouble
+type C1 = Char
+
+jsonOptions = defaultOptions {
+      allNullaryToStringTag = False
+    , omitNothingFields = True
+    , sumEncoding = ObjectWithSingleField
+    }
+
+flagOptions = defaultOptions
+
+instance ToJSON Milliseconds where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON Minutes where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON Rec where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON PartFlag where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON GdrField where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON TestType where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON WaferUnits where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON Direction where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON OptionalInfo where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON GroupMode where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON Radix where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON TestFlag where
+    toJSON = genericToJSON flagOptions
+instance ToJSON PassFailBin where
+    toJSON = genericToJSON jsonOptions
+instance ToJSON ParametricFlag where
+    toJSON = genericToJSON flagOptions
+
+data BinRec = BinRec 
+    { header :: Header
+    , rec :: Rec } deriving (Generic, Show)
+
+data Header = Header
+    { len :: !Word16
+    , typ :: !Word8
+    , sub :: !Word8
+    } deriving (Generic, Show)
+
+type Stdf = [Rec]
+
+data Milliseconds = Milliseconds { ms :: !U4 }
+    deriving (Generic, Show)
+
+data Minutes = Minutes { minutes :: !U2 }
+    deriving (Generic, Show)
+
+-- The mother of all datatypes
+data Rec= Raw { raw :: Text } -- base64 TODO: URL encoding or maybe don't bother. what's this good for?
+        | Far { cpuType  :: !U1
+              , stdfVersion  :: !U1 }
+        | Atr { modificationTime :: Maybe UTCTime
+              , commandLine :: Maybe Text }
+        | Mir { setupTime :: Maybe UTCTime
+              , startTime :: Maybe UTCTime
+              , station :: !U1
+              , modeCode :: Maybe C1 -- TODO: MODE_COD record
+              , retestCode :: Maybe C1 -- TODO: RTST_COD record
+              , protectionCode :: Maybe C1 -- ' '
+              , burninTime :: Maybe Minutes -- 65,535
+              , commandCode :: Maybe C1 -- ' '
+              , lotId :: Text
+              , partType :: Text
+              , nodeName :: Text
+              , testerType :: Text
+              , jobName :: Text
+              , jobRevision :: Maybe Text
+              , subLotId :: Maybe Text
+              , operatorName :: Maybe Text
+              , execType :: Maybe Text
+              , execVersion :: Maybe Text
+              , testCode :: Maybe Text
+              , testTemperature :: Maybe Text
+              , userText :: Maybe Text
+              , auxFile :: Maybe Text
+              , packageType :: Maybe Text
+              , familyId :: Maybe Text
+              , dateCode :: Maybe Text
+              , facilityId :: Maybe Text
+              , floorId :: Maybe Text
+              , processId :: Maybe Text
+              , operationFreq :: Maybe Text
+              , specName :: Maybe Text
+              , specVersion :: Maybe Text
+              , flowId :: Maybe Text
+              , setupId :: Maybe Text
+              , designRev :: Maybe Text
+              , engineeringLotId :: Maybe Text
+              , romCodeId :: Maybe Text
+              , testerSerialNum :: Maybe Text
+              , supervisorName :: Maybe Text }
+        | Mrr { finishTime :: Maybe UTCTime
+              , lotDisposition :: Maybe C1
+              , userDescription :: Maybe Text
+              , execDescription :: Maybe Text }
+        | Pcr { headId :: !U1
+              , siteId :: !U1
+              , partCount :: !U4
+              , retestCount :: Maybe U4
+              , abortCount :: Maybe U4
+              , goodCount :: Maybe U4
+              , functionalCount :: Maybe U4 }
+        | Hbr { headId :: !U1
+              , siteId :: !U1
+              , bin :: !U2
+              , binCount :: !U4
+              , passFailBin :: PassFailBin
+              , name :: Maybe Text }
+        | Sbr { headId :: !U1
+              , siteId :: !U1
+              , bin :: !U2
+              , binCount :: !U4
+              , passFail :: PassFailBin
+              , name :: Maybe Text }
+        | Pmr { index :: !U2 -- Maybe rather than index reference by name
+              , channelType :: Maybe U2
+              , channelName :: Maybe Text
+              , physicalName :: Maybe Text
+              , logicalName :: Maybe Text
+              , headId :: !U1
+              , siteId :: !U1 }
+        | Pgr { index :: !U2
+              , name :: Maybe Text
+              , pinIndecies :: [U2] } -- list of pins instead of refering to indecies
+        -- Parsing: 
+        -- Empty arrays (or empty members of arrays) can be omitted 
+        -- if they occur at the end of the record.
+        | Plr { 
+              -- Instead of Maybe [] opt for empty [Maybe]
+                indecies :: [U2]
+              , groupModes :: [GroupMode]
+              , groupRadixes :: [Radix]
+              -- Should really just use a string for state characters
+              -- instead of Left/Right but failed since I don't have example PLR
+              -- , programStateChars :: [Text] -- combine CharR and CharL at parse?
+              -- , returnStateChars :: [Text] }
+              , programStateCharsRight :: [Maybe Text]
+              , returnStateCharsRight :: [Maybe Text]
+              , programStateCharsLeft :: [Maybe Text]
+              , returnStateCharsLeft :: [Maybe Text] }
+        | Rdr { retestBins :: [U2] }
+        | Sdr { headId :: !U1
+              , siteGroup :: !U1
+              , sites :: [U1]
+              , handlerType :: Maybe Text
+              , handlerId :: Maybe Text
+              , probeCardType :: Maybe Text
+              , probeCardId :: Maybe Text
+              , loadBoardType :: Maybe Text
+              , loadBoardId :: Maybe Text
+              , dibType :: Maybe Text
+              , dibId :: Maybe Text
+              , cableType :: Maybe Text
+              , cableId :: Maybe Text
+              , contactorType :: Maybe Text
+              , contactorId :: Maybe Text
+              , laserType :: Maybe Text
+              , laserId :: Maybe Text
+              , extraType :: Maybe Text
+              , extraId :: Maybe Text }
+        | Wir { headId :: !U1
+              , siteGroup :: !U1 -- 255 -> Nothing -- feature removed
+              , startTime :: Maybe UTCTime
+              , waferId :: Maybe Text }
+        | Wrr { headId :: !U1
+              , siteGroup :: !U1  -- 255 means Nothing
+              , finishTime :: Maybe UTCTime
+              , partCount :: !U4
+              , retestCount :: Maybe U4 -- 4,294,967,295 -> Nothing
+              , abortCount :: Maybe U4 -- 4,294,967,295 -> Nothing
+              , goodCount :: Maybe U4 -- 4,294,967,295 -> Nothing
+              , functionalCount :: Maybe U4 -- 4,294,967,295 -> Nothing
+              , waferId :: Maybe Text -- length 0 -> Nothing
+              , fabWaferId :: Maybe Text -- length 0 -> Nothing
+              , waferFrameId :: Maybe Text -- length 0 -> Nothing
+              , waferMaskId :: Maybe Text -- length 0 -> Nothing
+              , userDescription :: Maybe Text -- length 0 -> Nothing
+              , execDescription :: Maybe Text }
+        | Wcr { waferSize :: Maybe R4 -- 0 -> Nothing
+              , dieHeight :: Maybe R4
+              , dieWidth :: Maybe R4
+              , waferUnits :: Maybe WaferUnits
+              , waferFlat :: Maybe Direction
+              , centerX :: Maybe I2
+              , centerY :: Maybe I2
+              , positiveXdirection :: Maybe Direction
+              , positiveYdirection :: Maybe Direction }
+        | Pir { headId :: !U1
+              , siteId :: !U1 }
+        | Prr { headId  :: !U1
+                , siteId  :: !U1
+                , partFlag  :: !PartFlag
+                , numTestsExecuted  :: !U2
+                , hardBin  :: !U2
+                , softBin  :: Maybe U2
+                , xCoord   :: Maybe I2
+                , yCoord   :: Maybe I2
+                , testTime :: Maybe Milliseconds -- TODO: type milliseconds like minutes or call this field milliseconds
+                , partID   :: Maybe Text
+                , partTxt  :: Maybe Text
+                , partFix  :: Maybe Text }
+        | Tsr { headId :: !U1
+              , siteId :: !U1
+              , testType :: Maybe TestType
+              , testId :: !U4
+              , execCount :: Maybe U4
+              , failCount :: Maybe U4
+              , alarmCount :: Maybe U4
+              , testName :: Maybe Text
+              , sequencerName :: Maybe Text
+              , testLabel :: Maybe Text
+              -- , optionalFlags :: !U1 -- parsing optional if last field in record
+              , testTimeAverage :: Maybe R4 -- optional fields based on optionalFlags TODO: name this field averageSeconds or make a Seconds type
+              , valueMin :: Maybe R4 -- may make these another record type
+              , valueMax :: Maybe R4
+              , valueSum :: Maybe R4
+              , valueSumOfSquares :: Maybe R4 }
+        | Ptr { testId :: !U4
+              , headId :: !U1
+              , siteId :: !U1
+              , testFlags :: [TestFlag] -- B1 bitfield further parsing bits
+              , parametricFlags :: [ParametricFlag] -- B1 bitfield further parsing bits
+              , result :: Maybe R4
+              , testText :: Maybe Text
+              -- , alarmId :: Maybe Text -> optionalInfo
+              , info :: [OptionalInfo] } -- TODO: better name -- Maybe so empty ones don't print in Json
+        | Mpr { testId :: !U4
+              , headId :: !U1
+              , siteId :: !U1
+              , testFlags :: [TestFlag]
+              , parametricFlags :: [ParametricFlag]
+              -- j , stateCount :: U2
+              -- k , resultCount :: U2
+              , states :: [U1] -- Nibbles! array of states? j states
+              , results :: [R4] -- k results
+              , testText :: Maybe Text
+              , info :: [OptionalInfo] }
+              -- , alarmId :: Maybe Text
+              -- -- , OPT_FLG B1 optional stuff to parse
+              -- , resultExp :: Maybe I1  -- TODO: put mostly in OptionalInfo
+              -- , lowLimitExp :: Maybe I1
+              -- , highLimitExp :: Maybe I1
+              -- , lowLimit :: Maybe R4
+              -- , highLimit :: Maybe R4
+              -- , startingInput :: Maybe R4
+              -- , incrementInput :: Maybe R4
+              -- , returnPinIndecies :: Maybe [U2] -- k indecies
+              -- , units :: Maybe Text
+              -- , unitsInputCondition  :: Maybe Text
+              -- , printfResultFmt :: Maybe Text
+              -- , printfLowLimitFmt :: Maybe Text
+              -- , printfHighLimitFmt :: Maybe Text
+              -- , lowSpecLimit :: Maybe R4
+              -- , highSpecLimit :: Maybe R4 }
+        | Ftr { testId :: !U4
+              , headId :: !U1
+              , siteId :: !U1
+              , testFlags :: [TestFlag]
+              , info :: [OptionalInfo]
+              -- -- , optFlg :: !U1 -- 8 bit packed binary -- record may have ended by here
+              -- , cycleCount :: Maybe U4    -- To Optional Info
+              -- , relativeVectorAddr :: Maybe U4
+              -- , numFailingPins :: Maybe U4
+              -- , xLogicalFailureAddr :: Maybe I4
+              -- , yLogicalFailureAddr :: Maybe I4
+              -- , offsetFromVector :: Maybe I2
+              -- -- j U2
+              -- -- k U2
+              -- , pinIndecies :: Maybe [U2] -- j x U2
+              -- , returnedStates :: Maybe [U1] -- j NIBBLES!
+              -- , pgmStateIndecies :: Maybe [U2] -- k x U2
+              -- , pgmStates :: Maybe [U1] -- k NIBBLES!
+              -- , failPin :: Maybe [U1] -- bitfield!
+              -- , vector :: Maybe Text
+              -- , timeSet :: Maybe Text
+              -- , opCode :: Maybe Text
+              -- , label :: Maybe Text
+              -- , alarmId :: Maybe Text
+              -- , programText :: Maybe Text
+              -- , resultText :: Maybe Text
+              -- , patternGen :: Maybe U1  -- 255
+              -- , enabledPins :: Maybe [U1] -- bitfield!
+              } -- TODO: this is a long silly record. there's a bunch more things
+        | Bps { sequencerName :: Maybe Text }  -- Begin Program Secion
+        | Eps -- End Program Section: no payload
+        | Gdr [GdrField]
+        | Dtr { textDat :: Text }
+          deriving (Generic, Show)
+
+data GroupMode = UnknownGroupMode
+               | Normal
+               | SameCycleIO
+               | SameCycleMidband
+               | SameCycleValid
+               | SameCycleWindowSustain
+               | DualDrive
+               | DualDriveMidband
+               | DualDriveValid
+               | DualDriveWindowSustain
+               | OtherGroupMode U2
+               deriving (Generic, Show)
+
+data Radix = DefaultRadix
+           | Binary
+           | Octal
+           | Decimal
+           | Hexadecimal
+           | Symbolic
+           | OtherRadix U1
+           deriving (Generic, Show)
+
+data TestFlag = Alarm       -- bit 0
+              | Invalid     -- bit 1
+              | Unreliable  -- bit 2
+              | Timeout     -- bit 3
+              | NotExecuted -- bit 4
+              | Aborted     -- bit 5
+              | InValid     -- bit 6
+              | Pass        -- bit 7 == 0
+              | Fail        -- bit 7
+              deriving (Generic, Show, Eq, Enum)
+
+data PassFailBin = PassBin | FailBin | UnknownBin | OtherBin Char
+    deriving (Generic, Show)
+
+data ParametricFlag = ScaleError          -- bit 0
+                    | DriftError          -- bit 1
+                    | Oscillation         -- bit 2
+                    | FailHighLimit       -- bit 3
+                    | FailLowLimit        -- bit 4
+                    | PassAlternateLimits -- bit 5
+                    | PassOnEqLowLimit    -- bit 6
+                    | PassOnEqHighLimit   -- bit 7
+                    -- bits 6 & 7 seem stupid
+                    deriving (Generic, Show, Eq, Enum)
+
+-- TODO: Another pass at scaling flags
+-- Maybe better as sum type
+data OptionalInfo   = Units Text
+                    | LowSpecLimit Float
+                    | HighSpecLimit Float
+                    | LowSpecLimitStr Text
+                    | HighSpecLimitStr Text
+                    | AlarmId Text
+                    | LowLimit Float
+                    | HighLimit Float
+                    | LowLimitStr Text
+                    | HighLimitStr Text
+                    | ResultStr Text
+                    | StartingInput Float
+                    | StartingInputUnits Text
+                    | IncrementInput Float
+                    -- change to a map of pinName -> state
+                    | ReturnPinIndecies [U2]  -- k
+                    | CycleCount U4
+                    | RepeatCount U4
+                    | RelativeVectorAddr U4
+                    | NumFailingPins U4
+                    | XLogicalFailureAddr I4
+                    | YLogicalFailureAddr I4
+                    | OffsetFromVector I2
+                    | PinIndecies [U2] -- j x U2 -- redundant with ReturnPinIndecies?
+                    | ReturnedStates [U1] -- j NIBBLES!
+                    | PgmStateIndecies [U2] -- k x U2 -> Parse pin states?
+                    | PgmStates [U1] -- k NIBBLES! -> String
+                    | FailPin [U1] -- bitfield! -> [PinName]
+                    | VectorName Text
+                    | TimeSet Text
+                    | OpCode Text
+                    | Label Text
+                    | ProgramText Text
+                    | ResultText Text
+                    | PatternGen U1  -- 255
+                    | EnabledPins [U1] -- bitfield!
+                    deriving (Generic, Show)
+
+data TestType = Parametric
+              | Functional
+              | MultiResultParametric
+              | UnknownTestType
+              | OtherTestType C1
+              deriving (Generic, Show)
+
+data WaferUnits = Inches
+                | Centimeters
+                | Millimeters
+                | Mils
+                | OtherUnits U1
+                deriving (Generic, Show)
+
+data Direction = Up
+               | Down
+               | Left
+               | Right
+               | OtherDirection C1
+               deriving (Generic, Show)
+
+data GdrField = GPad -- discard
+              | GU1 !U1
+              | GU2 !U2
+              | GU4 !U4
+              | GI1 !I1
+              | GI2 !I2
+              | GI4 !I4
+              | GFloat Float -- parse as CFloat
+              | GDouble Double -- parse as CDouble
+              | GStr Text
+              | GBytes [U1] -- encoded ByteStr
+              | GData [U1] -- 2byte length + encoded ByteStr
+              | GNibble !U1 -- a nibble? are you fucking kidding me?
+              deriving (Generic, Show)
+
+data PartFlag = PartFlag { supersedesPartId :: Bool -- 1 bit
+                       , supersedesXY :: Bool -- 1 bit
+                       , abnormalEnd :: Bool
+                       , failed :: Bool
+                       , noPassFailInfo :: Bool }
+                       deriving (Generic, Show)
diff --git a/Data/Stdf/WaferMap.hs b/Data/Stdf/WaferMap.hs
--- a/Data/Stdf/WaferMap.hs
+++ b/Data/Stdf/WaferMap.hs
@@ -7,6 +7,7 @@
                           , xybsToGrid
                           , gridToString 
                           , stdfToWaferMapString 
+                          , map2d
                           ) where
 
 import Data.Stdf
@@ -20,9 +21,9 @@
 
 data XyBin = XyBin { x :: Int
                    , y :: Int
-                   , bin :: Int }
+                   , hbin :: Int }
            | Missing
-    deriving Show
+    deriving (Show, Eq)
 
 stdfToXyBin :: Stdf -> [XyBin]
 stdfToXyBin (prr@(Prr { xCoord = Just xc
@@ -30,7 +31,7 @@
                       , hardBin = hb }):prrs) =
     XyBin { x = fromIntegral xc
           , y = fromIntegral yc
-          , bin = fromIntegral hb } : stdfToXyBin prrs
+          , hbin = fromIntegral hb } : stdfToXyBin prrs
 stdfToXyBin (_:prrs) = stdfToXyBin prrs
 stdfToXyBin [] = []
 
@@ -51,7 +52,7 @@
 
 -- sort by y,x
 -- groupby y,x
--- take lowest bin # from groups
+-- take lowest hbin # from groups
 -- use min max x y to insert Missing dies
 xybsToGrid :: [XyBin] -> [[XyBin]]
 xybsToGrid xybs = mkgrid cols gridlist
@@ -59,7 +60,7 @@
                             EQ -> compare (x a) (x b)
                             ordy -> ordy
             eqXy a b = x a == x b && y a == y b
-            ordBin a b = compare (bin a) (bin b)
+            ordBin a b = compare (hbin a) (hbin b)
             waferxy =  [(atx, aty) | aty <- range (miny, maxy)
                                    , atx <- range (minx, maxx)]
             sortedxy = sortBy ordXy xybs
@@ -88,7 +89,7 @@
         pad w s = s ++ replicate (w - length s) ' '
 
         binOrNothing Missing = Nothing
-        binOrNothing XyBin { bin = bin } = Just bin
+        binOrNothing XyBin { hbin = hbin } = Just hbin
 
         bins = map2d binOrNothing xybs -- start here
         binstrs :: [[String]]
diff --git a/Examples/StdfToJson.hs b/Examples/StdfToJson.hs
--- a/Examples/StdfToJson.hs
+++ b/Examples/StdfToJson.hs
@@ -15,13 +15,16 @@
 notRaw (Raw _) = False
 notRaw _       = True
 
+isFtr :: Rec -> Bool
+isFtr (Ftr{}) = True
+isFtr _ = False
+
 main = do
     args <- getArgs
     when (length args /= 1) $ do
         printUsage
         exitFailure
     let file = head args
-    raw <- BL.readFile file
-    let recs = parse raw
+    recs <- parseFile file
     let goodRecs = filter notRaw recs
     mapM_ (BL.putStrLn . encode) goodRecs
diff --git a/Examples/WaferMap.hs b/Examples/WaferMap.hs
--- a/Examples/WaferMap.hs
+++ b/Examples/WaferMap.hs
@@ -17,5 +17,5 @@
         exitFailure
     let file = head args
     putStrLn $ "File: " ++ file
-    raw <- BL.readFile file
-    putStrLn $ stdfToWaferMapString (parse raw)
+    recs <- parseFile file
+    putStrLn $ stdfToWaferMapString recs
diff --git a/Readme.md b/Readme.md
--- a/Readme.md
+++ b/Readme.md
@@ -5,6 +5,7 @@
 
 There's a couple of good, open-source libraries for parsing STDF, notably [PyStdf](https://github.com/cmars/pystdf).
 
+Latest version: 0.2
 
 The Data.Stdf advantage
 -----------------------
@@ -16,8 +17,10 @@
 Install
 -------
 
-    cabal install stdf -- Is a TODO. Just clone and build the package
+    cabal install stdf
 
+Or for the latest revision, clone and build from here.
+
 Usage
 -----
 
@@ -65,10 +68,22 @@
 * StdfToJson -- prints each Stdf record as a one-line JSON object for easy importing into other languages for further processing
 
 
-Now the Bad News
+Performance
+-----------
+
+Performance is very good but can be 50% better if you build with `-O2`.
+However compiling takes forever with `-O2`.
+
+
+Not Implemented
 ----------------
 
-Data.Stdf currently only parses the PRR record because that's all I needed.
-Well, the code is short and sweet so it won't be too much work to add support
-for some more interesting records. I can't think of why I'd bother with all of
-the rarely-used uninteresting ones. But maybe if I'm bored.
+### Non-Intel floating-point and endianness
+
+It really blows my mind that Stdf expects the parser to support any N CPU bit
+encodings. I won't do it.
+
+### Some tricky records and fields
+
+Not yet covering all fields of all Stdf records.
+Several interesting optional fields in PTR, MPR and FTR are not yet processed.
diff --git a/stdf.cabal b/stdf.cabal
--- a/stdf.cabal
+++ b/stdf.cabal
@@ -10,7 +10,7 @@
 -- PVP summary:      +-+------- breaking API changes
 --                   | | +----- non-breaking API additions
 --                   | | | +--- code changes with no API change
-version:             0.1.0.0
+version:             0.2.0.0
 
 -- A short (one-line) description of the package.
 synopsis:            Parse Structured Test Data Format (STDF)
@@ -51,7 +51,7 @@
 
 library
   -- Modules exported by the library.
-  exposed-modules:     Data.Stdf, Data.Stdf.WaferMap
+  exposed-modules:     Data.Stdf, Data.Stdf.WaferMap, Data.Stdf.Types
   
   -- Modules included in this library but not exported.
   -- other-modules:       
@@ -60,25 +60,25 @@
   other-extensions:    NegativeLiterals, DeriveGeneric, OverloadedStrings
   
   -- Other library packages from which modules are imported.
-  build-depends:       base >=4.7 && <4.8, bytestring >=0.10 && <0.11, binary >=0.7 && <0.8, text >=1.1 && <1.2, base64-bytestring >=1.0 && <1.1, aeson >=0.8 && <0.9, split >=0.2 && <0.3
+  build-depends:       base >=4.7 && <4.8, bytestring >=0.10 && <0.11, binary >=0.7 && <0.8, text >=1.1 && <1.2, base64-bytestring >=1.0 && <1.1, aeson >=0.8 && <0.9, split >=0.2 && <0.3, time >= 1.4, unix-time >= 0.3, zlib >= 0.5, data-binary-ieee754 >= 0.4, containers >= 0.5
   
   -- Directories containing source files.
   hs-source-dirs:      .
   
   -- Base language which the package is written in.
   default-language:    Haskell2010
-  GHC-Options: -O2
+  -- GHC-Options: -O
 
 Executable StdfToJson
   main-is: StdfToJson.hs
   hs-source-dirs: Examples
   default-language:    Haskell2010
-  GHC-Options: -O2
-  build-depends:       base >=4.7 && <4.8, bytestring >=0.10 && <0.11, binary >=0.7 && <0.8, text >=1.1 && <1.2, base64-bytestring >=1.0 && <1.1, aeson >=0.8 && <0.9, split >=0.2 && <0.3, stdf
+  -- GHC-Options: -O
+  build-depends:       base >=4.7 && <4.8, bytestring >=0.10 && <0.11, binary >=0.7 && <0.8, text >=1.1 && <1.2, base64-bytestring >=1.0 && <1.1, aeson >=0.8 && <0.9, split >=0.2 && <0.3, stdf >= 0.2
 
 Executable WaferMap
   main-is: WaferMap.hs
   hs-source-dirs: Examples
   default-language:    Haskell2010
-  GHC-Options: -O2
-  build-depends:       base >=4.7 && <4.8, bytestring >=0.10 && <0.11, binary >=0.7 && <0.8, text >=1.1 && <1.2, base64-bytestring >=1.0 && <1.1, aeson >=0.8 && <0.9, split >=0.2 && <0.3, stdf
+  -- GHC-Options: -O
+  build-depends:       base >=4.7 && <4.8, bytestring >=0.10 && <0.11, binary >=0.7 && <0.8, text >=1.1 && <1.2, base64-bytestring >=1.0 && <1.1, aeson >=0.8 && <0.9, split >=0.2 && <0.3, stdf >= 0.2
