dahdit-midi-0.5.1: src/Dahdit/Midi/OscAddr.hs
module Dahdit.Midi.OscAddr
( RawAddrPat (..)
)
where
import Control.Exception (Exception)
import Dahdit (Binary (..), ByteCount (..), putText)
import Dahdit.Midi.Binary (getTermText, putTermText)
import Dahdit.Midi.Pad (byteSizePad32, getPad32, putPad32)
import Data.ByteString.Internal (c2w)
import Data.Foldable (foldMap', for_, toList)
import Data.Monoid (Sum (..))
import Data.Sequence (Seq (..))
import Data.Sequence qualified as Seq
import Data.String (IsString (..))
import Data.Text (Text)
import Data.Text qualified as T
import Data.Word (Word8)
slashByte :: Word8
slashByte = c2w '/'
newtype Addr = Addr {unAddr :: Seq Text}
deriving stock (Show)
deriving newtype (Eq, Ord)
instance IsString Addr where
fromString s =
let t = T.pack s
in case parseAddr t of
Left e -> error ("Invalid address " ++ show s ++ " : " ++ show e)
Right a -> a
addrSizer :: Addr -> ByteCount
addrSizer (Addr parts) =
ByteCount (Seq.length parts + getSum (foldMap' (Sum . T.length) parts))
instance Binary Addr where
byteSize = byteSizePad32 addrSizer
get = getPad32 $ do
s <- getTermText
case parseAddr s of
Left e -> fail ("Invalid address " ++ show s ++ " : " ++ show e)
Right a -> pure a
put = putPad32 addrSizer $ \(Addr parts) -> do
for_ parts $ \part -> do
put slashByte
putText part
put @Word8 0
isInvalidAddrPartChar :: Char -> Bool
isInvalidAddrPartChar c =
c == ' '
|| c == '#'
|| c == '*'
|| c == ','
|| c == '/'
|| c == '?'
|| c == '['
|| c == ']'
|| c == '{'
|| c == '}'
data AddrErr = AddrErrPartEmpty | AddrErrInvalidPartChar !Char | AddrErrExpectSlash !Char
deriving stock (Eq, Ord, Show)
instance Exception AddrErr
parseAddr :: Text -> Either AddrErr Addr
parseAddr = goStart . T.unpack
where
goStart = \case
[] -> Right (Addr Empty)
c : cs ->
if c == '/'
then goRest Empty Empty cs
else Left (AddrErrExpectSlash c)
pack = T.pack . toList
goRest !acc !pacc = \case
[] ->
if Seq.null pacc
then Left AddrErrPartEmpty
else Right (Addr (acc :|> pack pacc))
c : cs ->
if c == '/'
then
if Seq.null pacc
then Left AddrErrPartEmpty
else goRest (acc :|> pack pacc) Empty cs
else
if isInvalidAddrPartChar c
then Left (AddrErrInvalidPartChar c)
else goRest acc (pacc :|> c) cs
printAddr :: Addr -> Text
printAddr (Addr xs) =
if Seq.null xs
then T.empty
else T.cons '/' (T.intercalate (T.singleton '/') (toList xs))
data Negate = NegateNo | NegateYes
deriving stock (Eq, Ord, Show, Enum, Bounded)
data PatFrag
= PatFragText !Text
| PatFragAnyMany
| PatFragAnyOne
| PatFragChoose !(Seq Text)
| PatFragRange !Negate !Text !Text
deriving stock (Eq, Ord, Show)
patFragSizer :: PatFrag -> ByteCount
patFragSizer = \case
PatFragText t -> ByteCount (T.length t)
PatFragAnyMany -> 1
PatFragAnyOne -> 1
PatFragChoose ts -> ByteCount (1 + Seq.length ts + getSum (foldMap' (Sum . T.length) ts))
PatFragRange n t1 t2 -> ByteCount (3 + T.length t1 + T.length t2 + if n == NegateNo then 0 else 1)
type PatPart = Seq PatFrag
-- Addr encoding: zero-terminated, aligned to 4-byte boundary
newtype AddrPat = AddrPat {unAddrPat :: Seq PatPart}
deriving stock (Show)
deriving newtype (Eq, Ord)
instance IsString AddrPat where
fromString s =
let t = T.pack s
in case parseAddrPat t of
Left e -> error ("Invalid address pattern " ++ show s ++ " : " ++ show e)
Right a -> a
addrPatSizer :: AddrPat -> ByteCount
addrPatSizer (AddrPat _patParts) = undefined
instance Binary AddrPat where
byteSize = byteSizePad32 addrPatSizer
get = getPad32 $ do
s <- getTermText
case parseAddrPat s of
Left e -> fail ("Invalid address pattern " ++ show s ++ " : " ++ show e)
Right a -> pure a
put = putPad32 addrPatSizer $ \(AddrPat _patParts) -> error "TODO"
data AddrPatErr = AddrPadErr
deriving stock (Eq, Ord, Show)
instance Exception AddrPatErr
parseAddrPat :: Text -> Either AddrPatErr AddrPat
parseAddrPat = error "TODO"
printAddrPat :: AddrPat -> Text
printAddrPat = error "TODO"
matchPart :: PatPart -> Text -> Bool
matchPart = error "TODO"
matchAddr :: AddrPat -> Addr -> Bool
matchAddr (AddrPat patParts) (Addr parts) =
(Seq.length patParts == Seq.length parts)
&& and (zipWith matchPart (toList patParts) (toList parts))
newtype RawAddrPat = RawAddrPat {unRawAddrPat :: Text}
deriving stock (Show)
deriving newtype (Eq, Ord, IsString)
rawAddrPatSizer :: RawAddrPat -> ByteCount
rawAddrPatSizer = ByteCount . succ . T.length . unRawAddrPat
instance Binary RawAddrPat where
byteSize = byteSizePad32 rawAddrPatSizer
get = getPad32 (fmap RawAddrPat getTermText)
put = putPad32 rawAddrPatSizer (putTermText . unRawAddrPat)