packages feed

data-svd-0.1.0.0: src/Data/SVD/IO.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

module Data.SVD.IO
  ( parseSVD
  , parseSVDOptions
  , SVDOptions(..)
  ) where

import Data.Default.Class (Default(def))
import Data.Hashable (Hashable)
import Data.SVD.Types (Device)
import GHC.Generics (Generic)
import Text.XML.HXT.Core (readString, runX, (>>>))

import qualified Data.Bool
import qualified Data.ByteString.Char8
import qualified Data.Hashable
import qualified Data.Serialize
import qualified Data.SVD.Dim
import qualified Data.SVD.Parse
import qualified Data.SVD.Util
import qualified System.Directory

data SVDSort
  = SVDSort_DontSort
  | SVDSort_SortByNames
  | SVDSort_SortByAddresses
  deriving (Eq, Ord, Generic, Show)

instance Hashable SVDSort

data SVDOptions = SVDOptions
  { svdOptionsAddReservedFields :: Bool
  -- ^ Fill in dummy reserved fields where
  -- holes would be in registers
  , svdOptionsCache :: Bool
  -- ^ Cache parsed SVD in /tmp
  -- based on a hash of the input svd file
  , svdOptionsCheckContinuity :: Bool
  -- ^ Check register continuity
  , svdOptionsExpand :: Bool
  -- ^ Expand dimensions and clusters
  , svdOptionsSort :: SVDSort
  -- ^ Sorting
  } deriving (Eq, Ord, Generic, Show)

instance Default SVDOptions where
  def = SVDOptions
    { svdOptionsAddReservedFields = True
    , svdOptionsCache = True
    , svdOptionsCheckContinuity = True
    , svdOptionsExpand = True
    , svdOptionsSort = SVDSort_SortByAddresses
    }

instance Hashable SVDOptions

parseSVDOptions
  :: SVDOptions
  -> String
  -> IO (Either String Device)
parseSVDOptions opts@SVDOptions{..} f = do
  s <- readFile f
  -- If caching is enabled we hash the input
  -- string + options and try to load
  -- serialized binary from cache if it exists
  -- or create one if not for further invocations
  let fHash = Data.Hashable.hash s
      optsHash = Data.Hashable.hash opts
      caFile =
        "/tmp/svdCache-"
        <> show fHash
        <> "-"
        <> show optsHash

  if not svdOptionsCache
  then parseSVDFromString opts s
  else do
    hasCached <- System.Directory.doesFileExist caFile
    if hasCached
    then
      Data.Serialize.decode
      <$> Data.ByteString.Char8.readFile caFile
      >>= \case
        Left e ->
          error
            $ "Can't decode cached svd from "
            <> caFile
            <> " error was "
            <> e
        Right x -> pure x
    else do
      res <- parseSVDFromString opts s
      Data.ByteString.Char8.writeFile
        caFile
        $ Data.Serialize.encode res
      pure res

parseSVDFromString
  :: SVDOptions
  -> String
  -> IO (Either String Device)
parseSVDFromString SVDOptions{..} s = do
  res <- runX (readString [] s >>> Data.SVD.Parse.svd)
  case res of
    [] -> pure $ Left "No device parsed"
    [x] ->
          pure
        . Data.Bool.bool
            Right
            Data.SVD.Util.checkDeviceRegisterContinuity
            svdOptionsCheckContinuity
        . case svdOptionsSort of
            SVDSort_DontSort -> id
            SVDSort_SortByAddresses -> Data.SVD.Util.sortDeviceByAddresses
            SVDSort_SortByNames -> Data.SVD.Util.sortDeviceByNames
        . Data.Bool.bool
            id
            Data.SVD.Util.addReservedFields
            svdOptionsAddReservedFields
        . Data.Bool.bool
            id
            Data.SVD.Dim.expandDevice
            svdOptionsExpand
        $ x
    _ -> pure $ Left "Multiple devices parsed"

parseSVD
  :: String
  -> IO (Either String Device)
parseSVD = parseSVDOptions def