{-# LANGUAGE CPP #-}
module Foreign.NM (
    lookupSymbol
#ifdef TESTING
  , readFunctions'
  , Function(..)
#endif
  ) where

import Prelude hiding (lookup)
import Data.Map (lookup, fromList)
import Data.Char
import Data.List hiding (lookup)
import Data.Maybe
import Numeric
import System.Process

data Function = Function {
    _addr :: Int
  , cname :: String
  , prettyName :: String
  }
  deriving (Show, Eq)

lookupSymbol :: String -> String -> IO (Maybe String)
lookupSymbol filename func = do
  functions <- readFunctions filename
  return $ fmap cname $ find ((==) func . prettyName) functions

readFunctions :: String -> IO [Function]
readFunctions filename = do
  nm <- readProcess "nm" [filename] ""
  filt <- readProcess "c++filt" [] nm
  return $ readFunctions' nm filt

readFunctions' :: String -> String -> [Function]
readFunctions' nm filt =
  let addrLen = length $ takeWhile (/= ' ') $ head $ lines nm
      f = catMaybes . map (splitLine addrLen) . lines
      filt' = fromList $ f filt
  in catMaybes $ map (\(a, v) -> lookup a filt' >>= Just . Function a v) (f nm)

-- for 64 bit, the addrLen is 16 chars
-- for 32 bit, it is 8
splitLine :: Int -> String -> Maybe (Int, String)
splitLine addrLen l =
  let (h, t) = fmap n $ splitAt addrLen l
      [(v, "")] = readHex h
      n :: String -> String
      n (' ':_:' ':x) = x
      n (' ':x) = x
      n _ = error "invalid nm output"
      eh = isSuffixOf "(.eh)" t
  in if all isHexDigit h && not eh
      then Just (v, t)
      else Nothing