packages feed

data-svd-0.1.3.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, (>>>))
import Text.XML.HXT.Arrow.XmlState.RunIOStateArrow (runXIOState, initialState)

import qualified Control.Monad
import qualified Data.Bool
import qualified Data.ByteString.Char8
import qualified Data.Either
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
      Control.Monad.unless
        (Data.Either.isLeft res)
        $ Data.ByteString.Char8.writeFile
            caFile
            $ Data.Serialize.encode res
      pure res

parseSVDFromString
  :: SVDOptions
  -> String
  -> IO (Either String Device)
parseSVDFromString SVDOptions{..} s = do
  res <-
    runXIOState
      (initialState 0)
      (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