packages feed

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)