kcd-0.1.0.0: src/Kcd/Parser.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
-- |
-- > import qualified Kcd.Parser as KCD
-- > main = do
-- > kcd <- KCD.parseKcdFile "example.kcd"
-- > print kcd
module Kcd.Parser
(
parseKcdFile
, parseNetworkDefinition
, BasicLabelTypeValue (..)
, Bus (..)
, Consumer (..)
, Document (..)
, Endianess (..)
, Label (..)
, LabelGroup (..)
, LabelSet (..)
, Message (..)
, MessageId (..)
, MessageLength (..)
, Multiplex (..)
, MuxGroup (..)
, NetworkDefinition (..)
, Node (..)
, NodeRef (..)
, Notes (..)
, Producer (..)
, Signal (..)
, Value (..)
, ValueType (..)
, Var (..)
)
where
import Control.Applicative ((<|>))
import Control.Lens.TH (makeLenses)
import Control.Monad (join)
import Control.Monad.Catch (MonadCatch, MonadThrow)
import Control.Monad.Trans.Resource (runResourceT)
import Data.Conduit (($$), ConduitM)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Data.Text (Text, pack, unpack)
import Data.Text.Read (Reader, hexadecimal)
import Data.XML.Types (Event, Name(..))
import Text.XML.Stream.Parse
-- | Definition of one or more CAN bus networks in one file.
data NetworkDefinition = NetworkDefinition
{ _networkDefinitionDocument :: Document
, _networkDefinitionNodes :: [Node]
, _networkDefinitionBuses :: [Bus]
} deriving (Show, Eq)
-- | Describes the scope of application e.g. the target vehicle or controlled device.
data Document = Document
{ -- | The name of the network definition document.
_documentName :: Maybe Text
-- | The version of the network definition document.
, _documentVersion :: Maybe Text
-- | The owner or author of the network definition document.
, _documentAuthor :: Maybe Text
-- | The owner company of the network definition document.
, _documentCompany :: Maybe Text
-- | The release date of this version of the network definition document.
, _documentDate :: Maybe Text
, _documentContent :: Text
} deriving (Show, Eq)
-- | network transport system that transfers the data between several nodes.
data Bus = Bus
{ _busMessages :: [Message]
-- | Human-readable name of the bus network (e.g. "Comfort").
, _busName :: Text
-- | Nominal data transfer rate in baud (e.g. 500000, 125000, 100000 or 83333).
, _busBaudrate :: Int
} deriving (Show, Eq)
-- | An endpoint connected to the network (e.g. an electronic control unit) that is able to
-- | send messages to or receive messages from other endpoints.
data Node = Node
{ -- | Unique identifier of the network node.
_nodeId :: Text
-- | Human-readable name of the network node (e.g. "Brake").
, _nodeName :: Maybe Text
, _nodeVars :: [Var]
} deriving (Show, Eq)
-- | A variable, a symbolic name associated to a chunk of information (e.g. a string or a value).
data Var = Var
{ _varValue :: Value
, _varNotes :: Maybe Notes
-- | Unique name of the variable.
, _varName :: Text
} deriving (Show, Eq)
newtype MessageId = MessageId { _unMessageId :: Int } deriving (Eq, Show, Ord)
data MessageLength =
Auto
| LengthValue Int deriving (Show, Eq)
data Message = Message
{ _messageNotes :: Maybe Notes
, _messageProducer :: Maybe Producer
, _messageMultiplex :: Maybe Multiplex
, _messageSignals :: [Signal]
, _messageId :: MessageId
, _messageName :: Text
, _messageLength :: MessageLength
-- | Repetition interval of a cyclic network message in milliseconds
, _messageInterval :: Int
-- | Sending behavior of the network message. True, if message is triggered by signal changes.
, _messageTriggered :: Bool
-- | Frame format of the network message.
, _messageFormat :: Text
-- | True, if message is a remote frame
, _messageRemote :: Bool
} deriving (Show, Eq)
-- | A looping counter to make a group of signals (MuxGroup) alternately active at a time.
data Multiplex = Multiplex
{ _multiplexMuxGroup :: [MuxGroup]
, _multiplexNotes :: Maybe Notes
, _multiplexConsumer :: Maybe Consumer
, _multiplexValue :: Maybe Value
, _multiplexLabels :: [Label]
, _multiplexEndianess :: Endianess
-- | Bit length of the signal.
, _multiplexLength :: Int
-- | Human readable name of the signal
, _multiplexName :: Text
-- | Least significant bit offset of the signal relative to the least significant bit of the messages data payload
, _multiplexOffset :: Int
} deriving (Show, Eq)
-- | A group of signals that is just valid when the count value of the group matches with the looping
-- | counter (Multiplex).
data MuxGroup = MuxGroup
{ _muxGroupSignals :: [Signal]
-- | count value of the Multiplex when the signals of this group become valid.
, _muxGroupCount :: Int
} deriving (Show, Eq)
-- | A discrete part of information contained in the payload of a message.
data Signal = Signal
{ -- | Describes the purpose of the signal/variable and/or comments on its usage.
_signalNotes :: Maybe Notes
, _signalConsumer :: Maybe Consumer
, _signalValue :: Maybe Value
-- | A set of label and label groups. Each label describes the meaning of a single raw value by an alias name.
-- | A single value can only belong to a one label or label group.
, _signalLabelSet :: [LabelSet]
-- | Determines if Byteorder is big-endian (Motorola), little-endian (Intel) otherwise.
, _signalEndianess :: Endianess
-- | Bit length of the signal.
, _signalLength :: Int
-- | Human readable name of the signal
, _signalName :: Text
-- | Least significant bit offset of the signal relative to the least significant bit of the messages data payload
, _signalOffset :: Int
} deriving (Show, Eq)
data ValueType =
ValueTypeUnsigned -- Unsigned
| ValueTypeSigned -- Signed
| ValueTypeSingle -- Single
| ValueTypeDouble -- Double
deriving (Show, Eq)
-- | Details of how the raw value of the signal/variable shall be interpreted.
data Value = Value
{ -- | Lower validity limit of the interpreted value after using the slope/intercept equation.
_valueMin :: Double
-- | Upper validity limit of the interpreted value after using the slope/intercept equation.
, _valueMax :: Double
-- | The slope "m" of a linear equation y = mx + b.
, _valueSlope :: Double
-- | The y-axis intercept "b" of a linear equation y = mx + b.
, _valueIntercept :: Double
-- | Physical unit of the value written as unit term as described in "The Unified Code for Units of Measure"
-- | (http :://unitsofmeasure.org/ucum.html)
, _valueUnit :: Text
-- | Datatype of the value e.g. "unsigned","signed" or IEE754 "single", "double".
, _valueType :: ValueType
} deriving (Show, Eq)
-- | Network node that is a user/receiver of the assigned signal.
data Consumer = Consumer
{ _consumerNodeRef :: [NodeRef]
} deriving (Show, Eq)
-- | Origin network node that is the sender of the assigned message.
data Producer = Producer
{ _producerNodeRef :: [NodeRef]
} deriving (Show, Eq)
-- | An endpoint connected to the network that is able to send messages to or receive messages from other endpoints.
data NodeRef = NodeRef
{ -- | Referencing a network node by its unique identifier.
_nodeRefId :: Text
} deriving (Show, Eq)
-- | Descriptive name for a single value e.g. to describe an enumeration, mark special,invalid or error values.
data Label = Label
{ -- | Signal raw value that is described here.
_labelValue :: Int
-- | Human readable name of the signal
, _labelName :: Text
, _labelType :: BasicLabelTypeValue
} deriving (Show, Eq)
data LabelGroup = LabelGroup
{ -- | Signal raw value the label group is starting with.
_labelGroupFrom :: Text
-- | Signal raw value the label group is ending with.
, _labelGroupTo :: Text
-- | Human-readable name for this value.
, _labelGroupName :: Text
-- | Type of value :: "value", "invalid" or "error"
, _labelGroupType :: BasicLabelTypeValue
} deriving (Show, Eq)
data LabelSet = LabelSet
{ _labelSetLabel :: [Label]
, _labelSetLabelGroup :: [LabelGroup]
} deriving (Show, Eq)
data BasicLabelTypeValue =
TypeValue -- Value
| TypeInvalid -- Invalid
| TypeError -- Error
deriving (Show, Eq)
data Endianess =
LittleEndian -- Little
| BigEndian -- Big
deriving (Show, Eq)
-- | Describes the purpose of the signal/variable and/or comments on its usage.
newtype Notes = Notes Text deriving (Show, Eq);
parseSettings :: ParseSettings
parseSettings = def { psDecodeEntities = decodeHtmlEntities
, psRetainNamespaces = False }
parseKcdFile :: String -> IO NetworkDefinition
parseKcdFile f = runResourceT $
parseFile parseSettings f $$
force "Missing NetworkDefinition" parseNetworkDefinition
parseNetworkDefinition :: MonadThrow m => ConduitM Event o m (Maybe NetworkDefinition)
parseNetworkDefinition = tagIgnoreAttrs (ns "NetworkDefinition") $ do
document <- force "Missing document" parseDocument
nodes <- many parseNode
buses <- many parseBus
return $ NetworkDefinition document nodes buses
parseDocument :: MonadThrow m => ConduitM Event o m (Maybe Document)
parseDocument = tagName (ns "Document") attrs $ \(name, version, author, company, date) -> do
docContent <- content
return $ Document name version author company date docContent
where attrs = do
name <- attr "name"
version <- attr "version"
author <- attr "author"
company <- attr "company"
date <- attr "date"
return $ (,,,,) name version author company date
parseVar :: MonadThrow m => ConduitM Event o m (Maybe Var)
parseVar = tagName (ns "Var") (requireAttr "name") $ \name -> do
notes <- parseNotes
value <- force "Missing Value" parseValue
return $ Var value notes name
parseBus :: MonadThrow m => ConduitM Event o m (Maybe Bus)
parseBus = tagName (ns "Bus") attrs $ \(name, baudrate)-> do
messages <- many parseMessage
return $ Bus messages name baudrate
where attrs = do
name <- requireAttr "name"
baudrate <- fromMaybe 500000 <$> attrRead "baudrate"
return (name, baudrate)
parseMessageLength :: Text -> MessageLength
parseMessageLength "auto" = Auto
parseMessageLength i = LengthValue $ read $ unpack i
parseMessage :: MonadThrow m => ConduitM Event o m (Maybe Message)
parseMessage = tagName (ns "Message") attrs $ \(id, name, length, interval, triggered, count, format, remote) -> do
notes <- parseNotes
producer <- parseProducer
multiplex <- parseMultiplex
signals <- many parseSignal
return $ Message notes producer multiplex signals id name length interval triggered format remote
where attrs = do
id <- (MessageId . readHexNumber) <$> requireAttr "id"
name <- requireAttr "name"
length <- maybe Auto parseMessageLength <$> attr "length"
interval <- fromMaybe 0 <$> attrRead "interval"
triggered <- fromMaybe False <$> attrRead "triggered"
count <- fromMaybe 0 <$> attrRead "count"
format <- fromMaybe "standard" <$> attr "format"
remote <- fromMaybe False <$> attrRead "remote"
return (id, name, length, interval, triggered, count, format, remote)
readHexNumber :: Text -> Int
readHexNumber s = let (Right (n, _)) = hexadecimal s in n
basicSignalAttrs :: AttrParser (Endianess, Int, Text, Int)
basicSignalAttrs = do
endianess <- (fromMaybe LittleEndian . join . fmap parseEndianess) <$> attr "endianess"
length <- fromMaybe 1 <$> attrRead "length"
name <- requireAttr "name"
offset <- (read . unpack) <$> requireAttr "offset"
return (endianess, length, name, offset)
parseMultiplex :: MonadThrow m => ConduitM Event o m (Maybe Multiplex)
parseMultiplex = tagName (ns "Multiplex") basicSignalAttrs $ \(endianess, length, name, offset) -> do
muxGroup <- many parseMuxGroup
notes <- parseNotes
consumer <- parseConsumer
value <- parseValue
labels <- many parseLabel
return $ Multiplex muxGroup notes consumer value labels endianess length name offset
parseMuxGroup :: MonadThrow m => ConduitM Event o m (Maybe MuxGroup)
parseMuxGroup = tagName (ns "MuxGroup") countAttr $ \count -> do
signals <- many parseSignal
return $ MuxGroup signals count
where countAttr = (read . unpack) <$> requireAttr "count"
parseSignal :: MonadThrow m => ConduitM Event o m (Maybe Signal)
parseSignal = tagName (ns "Signal") basicSignalAttrs $ \(endianess, length, name, offset) -> do
notes <- parseNotes
consumer <- parseConsumer
value <- parseValue
labelSet <- many parseLabelSet
return $ Signal notes consumer value labelSet endianess length name offset
parseValueType :: Text -> Maybe ValueType
parseValueType "unsigned" = Just ValueTypeUnsigned
parseValueType "signed" = Just ValueTypeSigned
parseValueType "single" = Just ValueTypeSingle
parseValueType "double" = Just ValueTypeDouble
parseValueType _ = Nothing
parseValue :: MonadThrow m => ConduitM Event o m (Maybe Value)
parseValue = tagName (ns "Value") attrs $ \value -> return value
where attrs = do
min <- fromMaybe 0.0 <$> attrRead "min"
max <- fromMaybe 0.0 <$> attrRead "max"
slope <- fromMaybe 0.0 <$> attrRead "slope"
intercept <- fromMaybe 0.0 <$> attrRead "intercept"
unit <- fromMaybe "1" <$> attr "unit"
type' <- (fromMaybe ValueTypeUnsigned . join . fmap parseValueType) <$> attr "type"
return $ Value min max slope intercept unit type'
parseConsumer :: MonadThrow m => ConduitM Event o m (Maybe Consumer)
parseConsumer = tagNoAttr (ns "Consumer") $ do
nodeRefs <- many parseNodeRef
return $ Consumer nodeRefs
parseProducer :: MonadThrow m => ConduitM Event o m (Maybe Producer)
parseProducer = tagNoAttr (ns "Producer") $ do
nodeRefs <- many parseNodeRef
return $ Producer nodeRefs
parseNode :: MonadThrow m => ConduitM Event o m (Maybe Node)
parseNode = tagName (ns "Node") attrs $ \(id, name) -> do
vars <- many parseVar
return $ Node id name vars
where attrs = do
id <- requireAttr "id"
name <- attr "name"
return (id, name)
parseNodeRef :: MonadThrow m => ConduitM Event o m (Maybe NodeRef)
parseNodeRef = tagName (ns "NodeRef") (requireAttr "id") $ \id -> return $ NodeRef id
parseLabel :: MonadThrow m => ConduitM Event o m (Maybe Label)
parseLabel = tagName (ns "Label") attrs $ \(value, name, type') ->
return $ Label value name type'
where attrs = do
value <- (read . unpack) <$> requireAttr "value"
name <- requireAttr "name"
type' <- (fromMaybe TypeValue . join) . fmap parseBasicLabelTypeValue <$> attr "type"
return (value, name, type')
parseLabelGroup :: MonadThrow m => ConduitM Event o m (Maybe LabelGroup)
parseLabelGroup = tagName (ns "LabelGroup") attrs $ \(from, to, name, type') -> return $ LabelGroup from to name type'
where attrs = do
from <- requireAttr "from"
to <- requireAttr "to"
name <- requireAttr "name"
type' <- (fromMaybe TypeValue . join . fmap parseBasicLabelTypeValue) <$> attr "type"
return (from, to, name, type')
parseNotes :: MonadThrow m => ConduitM Event o m (Maybe Notes)
parseNotes = tagNoAttr (ns "Notes") $ do
note <- content
return $ Notes note
parseBasicLabelTypeValue :: Text -> Maybe BasicLabelTypeValue
parseBasicLabelTypeValue "value" = Just TypeValue
parseBasicLabelTypeValue "invalid" = Just TypeInvalid
parseBasicLabelTypeValue "error" = Just TypeError
parseBasicLabelTypeValue _ = Nothing
parseEndianess :: Text -> Maybe Endianess
parseEndianess "little" = Just LittleEndian
parseEndianess "big" = Just BigEndian
parseEndianess _ = Nothing
parseLabelSet :: MonadThrow m => ConduitM Event o m (Maybe LabelSet)
parseLabelSet = tagNoAttr (ns "LabelSet") $ do
labels <- many parseLabel
labelGroups <- many parseLabelGroup
return $ LabelSet labels labelGroups
attrRead :: Read a => Name -> AttrParser (Maybe a)
attrRead name = fmap (read . unpack) <$> attr name
ns :: Text -> Name
ns n = let ns' = Just "http://kayak.2codeornot2code.org/1.0"
pfx = Nothing
in Name n ns' pfx
makeLenses ''Bus
makeLenses ''Consumer
makeLenses ''Document
makeLenses ''Label
makeLenses ''LabelGroup
makeLenses ''LabelSet
makeLenses ''Message
makeLenses ''Multiplex
makeLenses ''MuxGroup
makeLenses ''NetworkDefinition
makeLenses ''Node
makeLenses ''NodeRef
makeLenses ''Producer
makeLenses ''Signal
makeLenses ''Value
makeLenses ''Var