summaryrefslogtreecommitdiff
path: root/Parse.hs
blob: e4eca845d9b9dc68635f9692dca33e9e82ec9ced (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
module Parse (parseProf) where

import Control.Applicative ((<|>))
import Control.Monad (guard)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.ByteString.Short as BSS
import Data.ByteString (ByteString)
import Data.Char
import Data.List (unfoldr)

import Debug.Trace

import Prof


data Columns
    = Columns { cMod :: Int
              , cSrc :: Int
              , cNo  :: Int
              , cCnt :: Int
              , cIvT :: Int  -- individual time/alloc
              , cIvA :: Int
              , cIhT :: Int  -- inherited time/alloc
              , cIhA :: Int }
  deriving (Show)

parseProf :: ByteString -> Either String (Prof ())
parseProf input = do
    (cols, lns) <- maybe (Left "Did not find tree header") Right (findTree (BS.split 10 input))
    traceM (show cols)
    lns' <- concat <$> mapM (parseLine cols) lns
    case buildTree lns' of
      Just (tree, []) -> return (Prof tree)
      Just (_, l) -> Left ("Found " ++ show (length l) ++ " unexpected lines after tree")
      Nothing -> Left "Empty tree found"

parseLine :: Columns -> ByteString -> Either String [(Int, Entry)]
parseLine _ line | BS.null line = Right []
parseLine cols line = either (\err -> Left (err ++ ": <" ++ BS8.unpack line ++ ">")) Right $ do
    let indent = BS.length (BS.takeWhile (== 32) line)
        scs  = getCol 0 (cMod cols)
        smod = getCol (cMod cols) (cSrc cols)
        ssrc = getCol (cSrc cols) (cNo  cols)
        scnt = getCol (cCnt cols) (cIvT cols)
        sivt = getCol (cIvT cols) (cIvA cols)
        siva = getCol (cIvA cols) (cIhT cols)
        siht = getCol (cIhT cols) (cIhA cols)
        siha = getCol (cIhA cols) (BS.length line)
    smod' <- nonEmpty smod "module"
    ssrc' <- nonEmpty ssrc "src"
    ncnt <- parseInt scnt "entries"
    fivt <- parseFloat sivt "individual %time"
    fiva <- parseFloat siva "individual %alloc"
    fiht <- parseFloat siht "inherited %time"
    fiha <- parseFloat siha "inherited %alloc"
    return [(indent, Entry { eCS = BSS.toShort scs
                           , eModule = BSS.toShort smod'
                           , eSrc = BSS.toShort ssrc'
                           , eCount = ncnt
                           , eIndividual = TimeShare fivt fiva
                           , eInherited = TimeShare fiht fiha })]
  where
    getCol :: Int -> Int -> ByteString
    getCol from to =
        let part = BS.drop from (BS.take to line)
        in BS.dropWhile (== 32) (BS.dropWhileEnd (== 32) part)

    nonEmpty bs descr = if BS.null bs then Left ("Unexpected empty " ++ descr ++ " field") else Right bs

    parseInt bs descr = case BS8.readInt bs of
                          Just (n, rest) | BS.null rest -> Right n
                          _ -> Left ("Cannot parse integer from '" ++ BS8.unpack bs ++ "' in " ++ descr ++ " field: <" ++ BS8.unpack bs ++ ">")

    parseFloat bs descr =
        let mres = do
                (whole, bs1) <- BS8.readInt bs
                guard (not (BS.null bs1) && BS.head bs1 == fromIntegral (ord '.'))
                let sfract = BS.tail bs1
                (fract, bs2) <- BS8.readInt sfract
                guard (BS.null bs2)
                return (fromIntegral whole + fromIntegral fract / 10 ^ BS.length sfract)
        in case mres of
             Just f -> Right f
             Nothing -> Left ("Cannot parse float from '" ++ BS8.unpack bs ++ "' in " ++ descr ++ " field: <" ++ BS8.unpack bs ++ ">")

findTree :: [ByteString] -> Maybe (Columns, [ByteString])
findTree [] = Nothing
findTree (ln:lns) = fmap (,lns) (parseLeadingTreeLine ln) <|> findTree lns

parseLeadingTreeLine :: ByteString -> Maybe Columns
parseLeadingTreeLine bs = do
    (0,      bs1) <- findCol "COST CENTRE" 0 bs
    (cmod,   bs2) <- findCol "MODULE"      11 bs1
    (csrc,   bs3) <- findCol "SRC"         (cmod+6) bs2
    (cno,    bs4) <- findCol "no."         (csrc+3) bs3
    (centr,  bs5) <- findCol "entries"     (cno+3) bs4
    (civt,   bs6) <- findCol "%time"       (centr+7) bs5
    (civa,   bs7) <- findCol "%alloc"      (civt+5) bs6
    (ciht,   bs8) <- findCol "%time"       (civa+6) bs7
    (_ciha, _bs9) <- findCol "%alloc"      (ciht+5) bs8
    return $ Columns { cMod = cmod
                     , cSrc = csrc
                     , cNo = cno
                     , cCnt = cno + 6
                     , cIvT = centr + 7
                     , cIvA = civt + 5
                     , cIhT = civa + 6
                     , cIhA = ciht + 5 }
  where
    findCol :: String -> Int -> ByteString -> Maybe (Int, ByteString)
    findCol stag from = \line ->
        case go 0 line of
          Nothing -> Nothing
          Just (col, rest) -> Just (from + col, rest)
      where
        tag :: ByteString
        tag = BS.pack (map (fromIntegral . ord) stag)

        go :: Int -> ByteString -> Maybe (Int, ByteString)
        go !idx line
          | BS.length line < BS.length tag = Nothing
          | BS.take (BS.length tag) line == tag = Just (idx, BS.drop (BS.length tag) line)
          | otherwise = go (idx + 1) (BS.tail line)

buildTree :: [(Int, Entry)] -> Maybe (ProfTree (), [(Int, Entry)])
buildTree [] = Nothing
buildTree ((depth, entry) : items) =
    let (below, after) = span ((> depth) . fst) items
        subs = unfoldr buildTree below
    in Just (Node entry () subs, after)